refactor device value rendering (to Web, Console or MQTT) to base class #632

This commit is contained in:
proddy
2020-12-13 22:52:34 +01:00
parent f72e549850
commit ffa313ebe4
60 changed files with 2579 additions and 3367 deletions

View File

@@ -12,6 +12,7 @@
- WM10 switch telegrams - WM10 switch telegrams
- boiler information (#633) - boiler information (#633)
- maintenance message and command - maintenance message and command
- thermostat program, reducemode, controlmode
### Fixed ### Fixed
- mixer IPM pumpstatus - mixer IPM pumpstatus

View File

@@ -106,17 +106,6 @@ class MqttSettingsForm extends React.Component<MqttSettingsFormProps> {
onChange={handleValueChange('max_topic_length')} onChange={handleValueChange('max_topic_length')}
margin="normal" margin="normal"
/> />
<SelectValidator name="mqtt_format"
label="Format"
value={data.mqtt_format}
fullWidth
variant="outlined"
onChange={handleValueChange('mqtt_format')}
margin="normal">
<MenuItem value={1}>Single</MenuItem>
<MenuItem value={2}>Nested</MenuItem>
<MenuItem value={3}>Home Assistant</MenuItem>
</SelectValidator>
<SelectValidator name="mqtt_qos" <SelectValidator name="mqtt_qos"
label="QoS" label="QoS"
value={data.mqtt_qos} value={data.mqtt_qos}
@@ -149,6 +138,43 @@ class MqttSettingsForm extends React.Component<MqttSettingsFormProps> {
label="Retain Flag" label="Retain Flag"
/> />
<br></br> <br></br>
<Typography variant="h6" color="primary" >
Formatting
</Typography>
<SelectValidator name="dallas_format"
label="Dallas Sensor Payload Grouping"
value={data.dallas_format}
fullWidth
variant="outlined"
onChange={handleValueChange('dallas_format')}
margin="normal">
<MenuItem value={1}>by Sensor ID</MenuItem>
<MenuItem value={2}>by Number</MenuItem>
</SelectValidator>
<BlockFormControlLabel
control={
<Checkbox
checked={data.ha_enabled}
onChange={handleValueChange('ha_enabled')}
value="ha_enabled"
/>
}
label="Home Assistant MQTT Discovery"
/>
{ data.ha_enabled &&
<SelectValidator name="ha_climate_format"
label="Thermostat Room Temperature"
value={data.ha_climate_format}
fullWidth
variant="outlined"
onChange={handleValueChange('ha_climate_format')}
margin="normal">
<MenuItem value={1}>use Current temperature (default)</MenuItem>
<MenuItem value={2}>use Setpoint temperature</MenuItem>
<MenuItem value={3}>Fix to 0</MenuItem>
</SelectValidator>
}
<br></br>
<Typography variant="h6" color="primary" > <Typography variant="h6" color="primary" >
Publish Intervals Publish Intervals
</Typography> </Typography>

View File

@@ -33,7 +33,9 @@ export interface MqttSettings {
publish_time_mixer: number; publish_time_mixer: number;
publish_time_other: number; publish_time_other: number;
publish_time_sensor: number; publish_time_sensor: number;
mqtt_format: number; dallas_format: number;
mqtt_qos: number; mqtt_qos: number;
mqtt_retain: boolean; mqtt_retain: boolean;
ha_enabled: boolean;
ha_climate_format: number;
} }

View File

@@ -342,13 +342,13 @@ class EMSESPDevicesForm extends Component<
<TableHead></TableHead> <TableHead></TableHead>
<TableBody> <TableBody>
{deviceData.data.map((item, i) => { {deviceData.data.map((item, i) => {
if (i % 2) { if (i % 3) {
return null; return null;
} else { } else {
return ( return (
<TableRow key={i}> <TableRow key={i}>
<TableCell component="th" scope="row">{deviceData.data[i]}</TableCell> <TableCell component="th" scope="row">{deviceData.data[i+2]}</TableCell>
<TableCell align="right">{deviceData.data[i + 1]}</TableCell> <TableCell align="right">{deviceData.data[i]}{deviceData.data[i + 1]}</TableCell>
</TableRow> </TableRow>
); );
} }

View File

@@ -60,30 +60,16 @@ class EMSESPStatusForm extends Component<EMSESPStatusFormProps> {
<TableBody> <TableBody>
<TableRow> <TableRow>
<TableCell> <TableCell>
Received telegrams # Telegrams Received
</TableCell> </TableCell>
<TableCell align="right">{formatNumber(data.rx_received)} <TableCell align="right">{formatNumber(data.rx_received)}&nbsp;({data.rx_quality}%)
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
<TableCell > <TableCell >
Rx line quality # Telegrams Sent
</TableCell>
<TableCell align="right">{data.rx_quality}&nbsp;%
</TableCell>
</TableRow>
<TableRow>
<TableCell >
Sent telegrams
</TableCell > </TableCell >
<TableCell align="right">{formatNumber(data.tx_sent)} <TableCell align="right">{formatNumber(data.tx_sent)}&nbsp;({data.tx_quality}%)
</TableCell>
</TableRow>
<TableRow>
<TableCell >
Tx line quality
</TableCell>
<TableCell align="right">{data.tx_quality}&nbsp;%
</TableCell> </TableCell>
</TableRow> </TableRow>
</TableBody> </TableBody>

View File

@@ -3,7 +3,7 @@ import { Redirect, Switch } from 'react-router';
import { AuthenticatedRoute } from '../authentication'; import { AuthenticatedRoute } from '../authentication';
import EMSESP from './EMSESP'; import EMSESPDashboard from './EMSESPDashboard';
import EMSESPSettings from './EMSESPSettings'; import EMSESPSettings from './EMSESPSettings';
class ProjectRouting extends Component { class ProjectRouting extends Component {
@@ -11,9 +11,9 @@ class ProjectRouting extends Component {
render() { render() {
return ( return (
<Switch> <Switch>
<AuthenticatedRoute exact path="/ems-esp/status/*" component={EMSESP} /> <AuthenticatedRoute exact path="/ems-esp/status/*" component={EMSESPDashboard} />
<AuthenticatedRoute exact path="/ems-esp/settings" component={EMSESPSettings} /> <AuthenticatedRoute exact path="/ems-esp/settings" component={EMSESPSettings} />
<AuthenticatedRoute exact path="/ems-esp/*" component={EMSESP} /> <AuthenticatedRoute exact path="/ems-esp/*" component={EMSESPDashboard} />
{ {
/* /*
* The redirect below caters for the default project route and redirecting invalid paths. * The redirect below caters for the default project route and redirecting invalid paths.

View File

@@ -188,9 +188,11 @@ void MqttSettings::read(MqttSettings & settings, JsonObject & root) {
root["publish_time_mixer"] = settings.publish_time_mixer; root["publish_time_mixer"] = settings.publish_time_mixer;
root["publish_time_other"] = settings.publish_time_other; root["publish_time_other"] = settings.publish_time_other;
root["publish_time_sensor"] = settings.publish_time_sensor; root["publish_time_sensor"] = settings.publish_time_sensor;
root["mqtt_format"] = settings.mqtt_format;
root["mqtt_qos"] = settings.mqtt_qos; root["mqtt_qos"] = settings.mqtt_qos;
root["mqtt_retain"] = settings.mqtt_retain; root["mqtt_retain"] = settings.mqtt_retain;
root["dallas_format"] = settings.dallas_format;
root["ha_climate_format"] = settings.ha_climate_format;
root["ha_enabled"] = settings.ha_enabled;
} }
StateUpdateResult MqttSettings::update(JsonObject & root, MqttSettings & settings) { StateUpdateResult MqttSettings::update(JsonObject & root, MqttSettings & settings) {
@@ -205,6 +207,8 @@ StateUpdateResult MqttSettings::update(JsonObject & root, MqttSettings & setting
newSettings.keepAlive = root["keep_alive"] | FACTORY_MQTT_KEEP_ALIVE; newSettings.keepAlive = root["keep_alive"] | FACTORY_MQTT_KEEP_ALIVE;
newSettings.cleanSession = root["clean_session"] | FACTORY_MQTT_CLEAN_SESSION; newSettings.cleanSession = root["clean_session"] | FACTORY_MQTT_CLEAN_SESSION;
newSettings.maxTopicLength = root["max_topic_length"] | FACTORY_MQTT_MAX_TOPIC_LENGTH; newSettings.maxTopicLength = root["max_topic_length"] | FACTORY_MQTT_MAX_TOPIC_LENGTH;
newSettings.mqtt_qos = root["mqtt_qos"] | EMSESP_DEFAULT_MQTT_QOS;
newSettings.mqtt_retain = root["mqtt_retain"] | EMSESP_DEFAULT_MQTT_RETAIN;
newSettings.publish_time_boiler = root["publish_time_boiler"] | EMSESP_DEFAULT_PUBLISH_TIME; newSettings.publish_time_boiler = root["publish_time_boiler"] | EMSESP_DEFAULT_PUBLISH_TIME;
newSettings.publish_time_thermostat = root["publish_time_thermostat"] | EMSESP_DEFAULT_PUBLISH_TIME; newSettings.publish_time_thermostat = root["publish_time_thermostat"] | EMSESP_DEFAULT_PUBLISH_TIME;
@@ -212,16 +216,25 @@ StateUpdateResult MqttSettings::update(JsonObject & root, MqttSettings & setting
newSettings.publish_time_mixer = root["publish_time_mixer"] | EMSESP_DEFAULT_PUBLISH_TIME; newSettings.publish_time_mixer = root["publish_time_mixer"] | EMSESP_DEFAULT_PUBLISH_TIME;
newSettings.publish_time_other = root["publish_time_other"] | EMSESP_DEFAULT_PUBLISH_TIME; newSettings.publish_time_other = root["publish_time_other"] | EMSESP_DEFAULT_PUBLISH_TIME;
newSettings.publish_time_sensor = root["publish_time_sensor"] | EMSESP_DEFAULT_PUBLISH_TIME; newSettings.publish_time_sensor = root["publish_time_sensor"] | EMSESP_DEFAULT_PUBLISH_TIME;
newSettings.mqtt_format = root["mqtt_format"] | EMSESP_DEFAULT_MQTT_FORMAT;
newSettings.mqtt_qos = root["mqtt_qos"] | EMSESP_DEFAULT_MQTT_QOS; newSettings.dallas_format = root["dallas_format"] | EMSESP_DEFAULT_DALLAS_FORMAT;
newSettings.mqtt_retain = root["mqtt_retain"] | EMSESP_DEFAULT_MQTT_RETAIN; newSettings.ha_climate_format = root["ha_climate_format"] | EMSESP_DEFAULT_HA_CLIMATE_FORMAT;
newSettings.ha_enabled = root["ha_enabled"] | EMSESP_DEFAULT_HA_ENABLED;
if (newSettings.mqtt_qos != settings.mqtt_qos) { if (newSettings.mqtt_qos != settings.mqtt_qos) {
emsesp::EMSESP::mqtt_.set_qos(newSettings.mqtt_qos); emsesp::EMSESP::mqtt_.set_qos(newSettings.mqtt_qos);
} }
if (newSettings.mqtt_format != settings.mqtt_format) { if (newSettings.dallas_format != settings.dallas_format) {
emsesp::EMSESP::mqtt_.set_format(newSettings.mqtt_format); emsesp::EMSESP::mqtt_.dallas_format(newSettings.dallas_format);
}
if (newSettings.ha_climate_format != settings.ha_climate_format) {
emsesp::EMSESP::mqtt_.ha_climate_format(newSettings.ha_climate_format);
}
if (newSettings.ha_enabled != settings.ha_enabled) {
emsesp::EMSESP::mqtt_.ha_enabled(newSettings.ha_enabled);
} }
if (newSettings.mqtt_retain != settings.mqtt_retain) { if (newSettings.mqtt_retain != settings.mqtt_retain) {

View File

@@ -60,9 +60,11 @@ static String generateClientId() {
#define FACTORY_MQTT_MAX_TOPIC_LENGTH 128 #define FACTORY_MQTT_MAX_TOPIC_LENGTH 128
#endif #endif
#define EMSESP_DEFAULT_MQTT_FORMAT 2 // nested #define EMSESP_DEFAULT_DALLAS_FORMAT 1 // sensorid
#define EMSESP_DEFAULT_HA_CLIMATE_FORMAT 1 // current temp
#define EMSESP_DEFAULT_MQTT_QOS 0 #define EMSESP_DEFAULT_MQTT_QOS 0
#define EMSESP_DEFAULT_MQTT_RETAIN false #define EMSESP_DEFAULT_MQTT_RETAIN false
#define EMSESP_DEFAULT_HA_ENABLED false
#define EMSESP_DEFAULT_PUBLISH_TIME 10 #define EMSESP_DEFAULT_PUBLISH_TIME 10
class MqttSettings { class MqttSettings {
@@ -91,9 +93,11 @@ class MqttSettings {
uint16_t publish_time_mixer; uint16_t publish_time_mixer;
uint16_t publish_time_other; uint16_t publish_time_other;
uint16_t publish_time_sensor; uint16_t publish_time_sensor;
uint8_t mqtt_format; // 1=single, 2=nested, 3=ha, 4=custom
uint8_t mqtt_qos; uint8_t mqtt_qos;
bool mqtt_retain; bool mqtt_retain;
uint8_t dallas_format;
uint8_t ha_climate_format;
bool ha_enabled;
static void read(MqttSettings & settings, JsonObject & root); static void read(MqttSettings & settings, JsonObject & root);
static StateUpdateResult update(JsonObject & root, MqttSettings & settings); static StateUpdateResult update(JsonObject & root, MqttSettings & settings);

View File

@@ -0,0 +1,71 @@
/*
* EMS-ESP - https://github.com/proddy/EMS-ESP
* Copyright 2020 Paul Derbyshire
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <uuid/common.h>
// #ifdef ESP8266
// #include <pgmspace.h>
// #else
// #include <avr/pgmspace.h>
// #endif
#include <Arduino.h>
namespace uuid {
// On ESP8266, pgm_read_byte() already takes care of 4-byte alignment, and
// memcpy_P(s, p, 4) makes 4 calls to pgm_read_byte() anyway, so don't bother
// optimizing for 4-byte alignment here.
// class __FlashStringHelper;
int compare_flash_string(const __FlashStringHelper * a, const __FlashStringHelper * b) {
const char * aa = reinterpret_cast<const char *>(a);
const char * bb = reinterpret_cast<const char *>(b);
while (true) {
uint8_t ca = pgm_read_byte(aa);
uint8_t cb = pgm_read_byte(bb);
if (ca != cb)
return (int)ca - (int)cb;
if (ca == 0)
return 0;
aa++;
bb++;
}
}
int compare_flash_string(const __FlashStringHelper * a, const __FlashStringHelper * b, size_t n) {
const char * aa = reinterpret_cast<const char *>(a);
const char * bb = reinterpret_cast<const char *>(b);
while (n > 0) {
uint8_t ca = pgm_read_byte(aa);
uint8_t cb = pgm_read_byte(bb);
if (ca != cb)
return (int)ca - (int)cb;
if (ca == 0)
return 0;
aa++;
bb++;
n--;
}
return 0;
}
} // namespace uuid

View File

@@ -16,6 +16,8 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
// compare_flash_string added by Proddy
#ifndef UUID_COMMON_H_ #ifndef UUID_COMMON_H_
#define UUID_COMMON_H_ #define UUID_COMMON_H_
@@ -32,6 +34,21 @@
*/ */
namespace uuid { namespace uuid {
/**
* String compare two flash strings
*
* The flash string must be stored with appropriate alignment for
* reading it on the platform.
*
* @param[in] a Pointer to string stored in flash.
* @param[in] b Pointer to string stored in flash.
* @param[in] n optional max length
* @return 0 for match, otherwise diff
* @since 1.0.0
*/
int compare_flash_string(const __FlashStringHelper * a, const __FlashStringHelper * b);
int compare_flash_string(const __FlashStringHelper * a, const __FlashStringHelper * b, size_t n);
/** /**
* Read a string from flash and convert it to a std::string. * Read a string from flash and convert it to a std::string.
* *

View File

@@ -20,15 +20,20 @@ class DummySettings {
uint32_t syslog_mark_interval = 0; uint32_t syslog_mark_interval = 0;
String syslog_host = "192.168.1.4"; String syslog_host = "192.168.1.4";
uint8_t master_thermostat = 0; uint8_t master_thermostat = 0;
bool shower_timer = false; bool shower_timer = true;
bool shower_alert = false; bool shower_alert = false;
bool hide_led = false; bool hide_led = false;
bool api_enabled = true; bool api_enabled = true;
// MQTT
uint16_t publish_time = 10; // seconds uint16_t publish_time = 10; // seconds
uint8_t mqtt_format = 3; // 1=single, 2=nested, 3=ha, 4=custom
uint8_t mqtt_qos = 0; uint8_t mqtt_qos = 0;
bool mqtt_retain = false; bool mqtt_retain = false;
bool enabled = true; // MQTT bool enabled = true;
uint8_t dallas_format = 1;
uint8_t ha_climate_format = 1;
bool ha_enabled = false;
String hostname = "ems-esp"; String hostname = "ems-esp";
String jwtSecret = "ems-esp"; String jwtSecret = "ems-esp";
String ssid = "ems-esp"; String ssid = "ems-esp";

View File

@@ -28,7 +28,7 @@ WebAPIService::WebAPIService(AsyncWebServer * server) {
server->on(EMSESP_API_SERVICE_PATH, HTTP_GET, std::bind(&WebAPIService::webAPIService, this, std::placeholders::_1)); server->on(EMSESP_API_SERVICE_PATH, HTTP_GET, std::bind(&WebAPIService::webAPIService, this, std::placeholders::_1));
} }
// http://ems-esp/api?device=boiler&cmd=wwtemp&data=20&id=1 // e.g. http://ems-esp/api?device=boiler&cmd=wwtemp&data=20&id=1
void WebAPIService::webAPIService(AsyncWebServerRequest * request) { void WebAPIService::webAPIService(AsyncWebServerRequest * request) {
// see if the API is enabled // see if the API is enabled
bool api_enabled; bool api_enabled;
@@ -71,7 +71,7 @@ void WebAPIService::webAPIService(AsyncWebServerRequest * request) {
id = "-1"; id = "-1";
} }
DynamicJsonDocument doc(EMSESP_MAX_JSON_SIZE_MEDIUM_DYN); DynamicJsonDocument doc(EMSESP_JSON_SIZE_LARGE_DYN);
JsonObject json = doc.to<JsonObject>(); JsonObject json = doc.to<JsonObject>();
bool ok = false; bool ok = false;
@@ -82,39 +82,20 @@ void WebAPIService::webAPIService(AsyncWebServerRequest * request) {
if (api_enabled) { if (api_enabled) {
// we only allow commands with parameters if the API is enabled // we only allow commands with parameters if the API is enabled
ok = Command::call(device_type, cmd.c_str(), data.c_str(), id.toInt(), json); // has cmd, data and id ok = Command::call(device_type, cmd.c_str(), data.c_str(), id.toInt(), json); // has cmd, data and id
if (ok && json.size()) {
// send json output back to web
std::string buffer;
serializeJsonPretty(doc, buffer);
request->send(200, "text/plain", buffer.c_str());
return;
}
} else { } else {
request->send(401, "text/plain", F("Unauthorized")); request->send(401, "text/plain", F("Unauthorized"));
return; return;
} }
} }
// debug
#if defined(EMSESP_DEBUG)
std::string debug(200, '\0');
snprintf_P(&debug[0],
debug.capacity() + 1,
PSTR("[DEBUG] API: device=%s cmd=%s data=%s id=%s [%s]"),
device.c_str(),
cmd.c_str(),
data.c_str(),
id.c_str(),
ok ? PSTR("OK") : PSTR("Invalid"));
EMSESP::logger().debug(debug.c_str());
if (json.size()) {
std::string buffer2;
serializeJson(doc, buffer2);
EMSESP::logger().debug("json (max 255 chars): %s", buffer2.c_str());
}
#endif
// if we have returned data in JSON format, send this to the WEB
if (json.size()) {
std::string buffer;
serializeJsonPretty(doc, buffer);
request->send(200, "text/plain", buffer.c_str());
} else {
request->send(200, "text/plain", ok ? F("OK") : F("Invalid")); request->send(200, "text/plain", ok ? F("OK") : F("Invalid"));
}
} }
} // namespace emsesp } // namespace emsesp

View File

@@ -45,7 +45,7 @@ void WebDevicesService::scan_devices(AsyncWebServerRequest * request) {
} }
void WebDevicesService::all_devices(AsyncWebServerRequest * request) { void WebDevicesService::all_devices(AsyncWebServerRequest * request) {
AsyncJsonResponse * response = new AsyncJsonResponse(false, EMSESP_MAX_JSON_SIZE_LARGE_DYN); AsyncJsonResponse * response = new AsyncJsonResponse(false, EMSESP_JSON_SIZE_LARGE_DYN);
JsonObject root = response->getRoot(); JsonObject root = response->getRoot();
JsonArray devices = root.createNestedArray("devices"); JsonArray devices = root.createNestedArray("devices");
@@ -78,19 +78,28 @@ void WebDevicesService::all_devices(AsyncWebServerRequest * request) {
request->send(response); request->send(response);
} }
// The unique_id is the unique record ID from the Web table to identify which device to load
void WebDevicesService::device_data(AsyncWebServerRequest * request, JsonVariant & json) { void WebDevicesService::device_data(AsyncWebServerRequest * request, JsonVariant & json) {
if (json.is<JsonObject>()) { if (json.is<JsonObject>()) {
AsyncJsonResponse * response = new AsyncJsonResponse(false, EMSESP_MAX_JSON_SIZE_MAX_DYN); AsyncJsonResponse * response = new AsyncJsonResponse(false, EMSESP_JSON_SIZE_XLARGE_DYN);
for (const auto & emsdevice : EMSESP::emsdevices) {
if (emsdevice) {
if (emsdevice->unique_id() == json["id"]) {
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
uint8_t id = json["id"]; // get id from selected table row JsonObject root = response->getRoot();
EMSESP::device_info_web(id, (JsonObject &)response->getRoot()); emsdevice->generate_values_json_web(root);
#endif #endif
response->setLength(); response->setLength();
request->send(response); request->send(response);
} else { return;
}
}
}
}
// invalid
AsyncWebServerResponse * response = request->beginResponse(200); AsyncWebServerResponse * response = request->beginResponse(200);
request->send(response); request->send(response);
}
} }
} // namespace emsesp } // namespace emsesp

View File

@@ -44,7 +44,7 @@ void WebStatusService::onStationModeDisconnected(WiFiEvent_t event, WiFiEventInf
} }
void WebStatusService::onStationModeGotIP(WiFiEvent_t event, WiFiEventInfo_t info) { void WebStatusService::onStationModeGotIP(WiFiEvent_t event, WiFiEventInfo_t info) {
EMSESP::logger().info(F("WiFi Connected with IP=%s, hostname=%s"), WiFi.localIP().toString().c_str(), WiFi.getHostname()); EMSESP::logger().info(F("WiFi Connected with IP=%s, hostname=%s"), WiFi.localIP().toString().c_str(), WiFi.getHostname());
EMSESP::system_.send_heartbeat(); // send out heartbeat MQTT as soon as we have a connection EMSESP::system_.reset_system_check(); // send out heartbeat MQTT as soon as we have a connection
} }
#elif defined(ESP8266) #elif defined(ESP8266)
void WebStatusService::onStationModeDisconnected(const WiFiEventStationModeDisconnected & event) { void WebStatusService::onStationModeDisconnected(const WiFiEventStationModeDisconnected & event) {
@@ -52,12 +52,12 @@ void WebStatusService::onStationModeDisconnected(const WiFiEventStationModeDisco
} }
void WebStatusService::onStationModeGotIP(const WiFiEventStationModeGotIP & event) { void WebStatusService::onStationModeGotIP(const WiFiEventStationModeGotIP & event) {
EMSESP::logger().info(F("WiFi Connected with IP=%s, hostname=%s"), event.ip.toString().c_str(), WiFi.hostname().c_str()); EMSESP::logger().info(F("WiFi Connected with IP=%s, hostname=%s"), event.ip.toString().c_str(), WiFi.hostname().c_str());
EMSESP::system_.send_heartbeat(); // send out heartbeat MQTT as soon as we have a connection EMSESP::system_.reset_system_check(); // send out heartbeat MQTT as soon as we have a connection
} }
#endif #endif
void WebStatusService::webStatusService(AsyncWebServerRequest * request) { void WebStatusService::webStatusService(AsyncWebServerRequest * request) {
AsyncJsonResponse * response = new AsyncJsonResponse(false, EMSESP_MAX_JSON_SIZE_MEDIUM_DYN); AsyncJsonResponse * response = new AsyncJsonResponse(false, EMSESP_JSON_SIZE_MEDIUM_DYN);
JsonObject root = response->getRoot(); JsonObject root = response->getRoot();
root["status"] = EMSESP::bus_status(); // 0, 1 or 2 root["status"] = EMSESP::bus_status(); // 0, 1 or 2

View File

@@ -26,7 +26,7 @@ uuid::log::Logger Command::logger_{F_(command), uuid::log::Facility::DAEMON};
std::vector<Command::CmdFunction> Command::cmdfunctions_; std::vector<Command::CmdFunction> Command::cmdfunctions_;
// calls a command, context is the device_type // calls a command
// id may be used to represent a heating circuit for example // id may be used to represent a heating circuit for example
// returns false if error or not found // returns false if error or not found
bool Command::call(const uint8_t device_type, const char * cmd, const char * value, const int8_t id) { bool Command::call(const uint8_t device_type, const char * cmd, const char * value, const int8_t id) {
@@ -49,7 +49,7 @@ bool Command::call(const uint8_t device_type, const char * cmd, const char * val
return ((cf->cmdfunction_)(value, id)); return ((cf->cmdfunction_)(value, id));
} }
// calls a command, context is the device_type. Takes a json object for output. // calls a command. Takes a json object for output.
// id may be used to represent a heating circuit for example // id may be used to represent a heating circuit for example
// returns false if error or not found // returns false if error or not found
bool Command::call(const uint8_t device_type, const char * cmd, const char * value, const int8_t id, JsonObject & json) { bool Command::call(const uint8_t device_type, const char * cmd, const char * value, const int8_t id, JsonObject & json) {
@@ -92,7 +92,7 @@ void Command::add(const uint8_t device_type, const uint8_t device_id, const __Fl
// see if we need to subscribe // see if we need to subscribe
if (Mqtt::enabled()) { if (Mqtt::enabled()) {
Mqtt::register_command(device_type, device_id, cmd, cb); Mqtt::register_command(device_type, cmd, cb);
} }
} }

View File

@@ -387,7 +387,7 @@ void EMSESPShell::add_console_commands() {
return; return;
} }
DynamicJsonDocument doc(EMSESP_MAX_JSON_SIZE_MEDIUM_DYN); DynamicJsonDocument doc(EMSESP_JSON_SIZE_XLARGE_DYN);
JsonObject json = doc.to<JsonObject>(); JsonObject json = doc.to<JsonObject>();
bool ok = false; bool ok = false;

View File

@@ -54,7 +54,7 @@ void DallasSensor::reload() {
parasite_ = settings.dallas_parasite; parasite_ = settings.dallas_parasite;
}); });
if (Mqtt::mqtt_format() == Mqtt::Format::HA) { if (Mqtt::ha_enabled()) {
for (uint8_t i = 0; i < MAX_SENSORS; registered_ha_[i++] = false) for (uint8_t i = 0; i < MAX_SENSORS; registered_ha_[i++] = false)
; ;
} }
@@ -282,14 +282,10 @@ bool DallasSensor::updated_values() {
return false; return false;
} }
bool DallasSensor::command_info(const char * value, const int8_t id, JsonObject & json) {
return (export_values(json));
}
// creates JSON doc from values // creates JSON doc from values
// returns false if empty // returns false if empty
// e.g. dallassensor_data = {"sensor1":{"id":"28-EA41-9497-0E03-5F","temp":23.30},"sensor2":{"id":"28-233D-9497-0C03-8B","temp":24.0}} // e.g. dallassensor_data = {"sensor1":{"id":"28-EA41-9497-0E03-5F","temp":23.30},"sensor2":{"id":"28-233D-9497-0C03-8B","temp":24.0}}
bool DallasSensor::export_values(JsonObject & json) { bool DallasSensor::command_info(const char * value, const int8_t id, JsonObject & json) {
if (sensors_.size() == 0) { if (sensors_.size() == 0) {
return false; return false;
} }
@@ -318,17 +314,19 @@ void DallasSensor::publish_values(const bool force) {
DynamicJsonDocument doc(100 * num_sensors); DynamicJsonDocument doc(100 * num_sensors);
uint8_t sensor_no = 1; uint8_t sensor_no = 1;
uint8_t mqtt_format_ = Mqtt::mqtt_format();
// dallas format is overriden when using Home Assistant
uint8_t dallas_format = Mqtt::ha_enabled() ? Mqtt::Dallas_Format::SENSORID : Mqtt::dallas_format();
for (const auto & sensor : sensors_) { for (const auto & sensor : sensors_) {
char sensorID[10]; // sensor{1-n} char sensorID[10]; // sensor{1-n}
snprintf_P(sensorID, 10, PSTR("sensor%d"), sensor_no); snprintf_P(sensorID, 10, PSTR("sensor%d"), sensor_no);
if (mqtt_format_ == Mqtt::Format::SINGLE) { if (dallas_format == Mqtt::Dallas_Format::NUMBER) {
// e.g. dallassensor_data = {"28-EA41-9497-0E03":23.3,"28-233D-9497-0C03":24.0} // e.g. dallassensor_data = {"28-EA41-9497-0E03":23.3,"28-233D-9497-0C03":24.0}
if (Helpers::hasValue(sensor.temperature_c)) { if (Helpers::hasValue(sensor.temperature_c)) {
doc[sensor.to_string()] = (float)(sensor.temperature_c) / 10; doc[sensor.to_string()] = (float)(sensor.temperature_c) / 10;
} }
} else { } else if (dallas_format == Mqtt::Dallas_Format::SENSORID) {
// e.g. dallassensor_data = {"sensor1":{"id":"28-EA41-9497-0E03","temp":23.3},"sensor2":{"id":"28-233D-9497-0C03","temp":24.0}} // e.g. dallassensor_data = {"sensor1":{"id":"28-EA41-9497-0E03","temp":23.3},"sensor2":{"id":"28-233D-9497-0C03","temp":24.0}}
JsonObject dataSensor = doc.createNestedObject(sensorID); JsonObject dataSensor = doc.createNestedObject(sensorID);
dataSensor["id"] = sensor.to_string(); dataSensor["id"] = sensor.to_string();
@@ -339,9 +337,9 @@ void DallasSensor::publish_values(const bool force) {
// create the HA MQTT config // create the HA MQTT config
// to e.g. homeassistant/sensor/ems-esp/dallas_28-233D-9497-0C03/config // to e.g. homeassistant/sensor/ems-esp/dallas_28-233D-9497-0C03/config
if (mqtt_format_ == Mqtt::Format::HA) { if (Mqtt::ha_enabled()) {
if (!(registered_ha_[sensor_no - 1]) || force) { if (!(registered_ha_[sensor_no - 1]) || force) {
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> config; StaticJsonDocument<EMSESP_JSON_SIZE_MEDIUM> config;
config["dev_cla"] = FJSON("temperature"); config["dev_cla"] = FJSON("temperature");
char stat_t[50]; char stat_t[50];
@@ -365,8 +363,8 @@ void DallasSensor::publish_values(const bool force) {
JsonArray ids = dev.createNestedArray("ids"); JsonArray ids = dev.createNestedArray("ids");
ids.add("ems-esp"); ids.add("ems-esp");
std::string topic(100, '\0'); char topic[100];
snprintf_P(&topic[0], 100, PSTR("homeassistant/sensor/ems-esp/dallas_%s/config"), sensor.to_string().c_str()); snprintf_P(topic, sizeof(topic), PSTR("homeassistant/sensor/%s/dallas_%s/config"), System::hostname().c_str(), sensor.to_string().c_str());
Mqtt::publish_ha(topic, config.as<JsonObject>()); Mqtt::publish_ha(topic, config.as<JsonObject>());
registered_ha_[sensor_no - 1] = true; registered_ha_[sensor_no - 1] = true;

View File

@@ -102,7 +102,6 @@ class DallasSensor {
uint64_t get_id(const uint8_t addr[]); uint64_t get_id(const uint8_t addr[]);
bool command_info(const char * value, const int8_t id, JsonObject & json); bool command_info(const char * value, const int8_t id, JsonObject & json);
bool export_values(JsonObject & doc);
uint32_t last_activity_ = uuid::get_uptime(); uint32_t last_activity_ = uuid::get_uptime();
uint32_t last_publish_ = uuid::get_uptime(); uint32_t last_publish_ = uuid::get_uptime();

View File

@@ -63,7 +63,7 @@
{202, DeviceType::THERMOSTAT, F("Logamatic TC100/Moduline Easy"), DeviceFlags::EMS_DEVICE_FLAG_EASY | DeviceFlags::EMS_DEVICE_FLAG_NO_WRITE}, // 0x18, cannot write {202, DeviceType::THERMOSTAT, F("Logamatic TC100/Moduline Easy"), DeviceFlags::EMS_DEVICE_FLAG_EASY | DeviceFlags::EMS_DEVICE_FLAG_NO_WRITE}, // 0x18, cannot write
{203, DeviceType::THERMOSTAT, F("EasyControl CT200"), DeviceFlags::EMS_DEVICE_FLAG_EASY | DeviceFlags::EMS_DEVICE_FLAG_NO_WRITE}, // 0x18, cannot write {203, DeviceType::THERMOSTAT, F("EasyControl CT200"), DeviceFlags::EMS_DEVICE_FLAG_EASY | DeviceFlags::EMS_DEVICE_FLAG_NO_WRITE}, // 0x18, cannot write
// Thermostat - Buderus/Nefit/Bosch specific - 0x17 / 0x10 / 0x18 / 0x19 // Thermostat - Buderus/Nefit/Bosch specific - 0x17 / 0x10 / 0x18 / 0x19 / 0x38
{ 67, DeviceType::THERMOSTAT, F("RC30"), DeviceFlags::EMS_DEVICE_FLAG_RC30_1},// 0x10 - based on RC35 { 67, DeviceType::THERMOSTAT, F("RC30"), DeviceFlags::EMS_DEVICE_FLAG_RC30_1},// 0x10 - based on RC35
{ 77, DeviceType::THERMOSTAT, F("RC20/Moduline 300"), DeviceFlags::EMS_DEVICE_FLAG_RC20},// 0x17 { 77, DeviceType::THERMOSTAT, F("RC20/Moduline 300"), DeviceFlags::EMS_DEVICE_FLAG_RC20},// 0x17
{ 78, DeviceType::THERMOSTAT, F("Moduline 400"), DeviceFlags::EMS_DEVICE_FLAG_RC30}, // 0x10 { 78, DeviceType::THERMOSTAT, F("Moduline 400"), DeviceFlags::EMS_DEVICE_FLAG_RC30}, // 0x10
@@ -91,7 +91,7 @@
{191, DeviceType::THERMOSTAT, F("FR120"), DeviceFlags::EMS_DEVICE_FLAG_JUNKERS | DeviceFlags::EMS_DEVICE_FLAG_JUNKERS_OLD}, // older model {191, DeviceType::THERMOSTAT, F("FR120"), DeviceFlags::EMS_DEVICE_FLAG_JUNKERS | DeviceFlags::EMS_DEVICE_FLAG_JUNKERS_OLD}, // older model
{192, DeviceType::THERMOSTAT, F("FW120"), DeviceFlags::EMS_DEVICE_FLAG_JUNKERS}, {192, DeviceType::THERMOSTAT, F("FW120"), DeviceFlags::EMS_DEVICE_FLAG_JUNKERS},
// Solar Modules - 0x30 // Solar Modules - 0x30, 0x2A (for ww)
{ 73, DeviceType::SOLAR, F("SM10"), DeviceFlags::EMS_DEVICE_FLAG_SM10}, { 73, DeviceType::SOLAR, F("SM10"), DeviceFlags::EMS_DEVICE_FLAG_SM10},
{101, DeviceType::SOLAR, F("ISM1"), DeviceFlags::EMS_DEVICE_FLAG_ISM}, {101, DeviceType::SOLAR, F("ISM1"), DeviceFlags::EMS_DEVICE_FLAG_ISM},
{162, DeviceType::SOLAR, F("SM50"), DeviceFlags::EMS_DEVICE_FLAG_SM100}, {162, DeviceType::SOLAR, F("SM50"), DeviceFlags::EMS_DEVICE_FLAG_SM100},

File diff suppressed because it is too large Load Diff

View File

@@ -38,23 +38,14 @@ class Boiler : public EMSdevice {
public: public:
Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand); Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand);
virtual void publish_values(JsonObject & json, bool force); virtual bool publish_ha_config();
virtual bool export_values(JsonObject & json);
virtual void device_info_web(JsonArray & root);
virtual bool updated_values();
private: private:
static uuid::log::Logger logger_; static uuid::log::Logger logger_;
void register_mqtt_ha_config();
void register_mqtt_ha_config_ww();
void check_active(const bool force = false); void check_active(const bool force = false);
bool export_values_main(JsonObject & doc, const bool textformat = false);
bool export_values_ww(JsonObject & doc, const bool textformat = false);
bool changed_ = false; uint8_t boilerState_ = EMS_VALUE_UINT_NOTSET; // Boiler state flag - FOR INTERNAL USE
bool mqtt_ha_config_ = false; // HA MQTT Discovery
bool mqtt_ha_config_ww_ = false; // HA MQTT Discovery
static constexpr uint8_t EMS_TYPE_UBAParameterWW = 0x33; static constexpr uint8_t EMS_TYPE_UBAParameterWW = 0x33;
static constexpr uint8_t EMS_TYPE_UBAFunctionTest = 0x1D; static constexpr uint8_t EMS_TYPE_UBAFunctionTest = 0x1D;
@@ -68,120 +59,99 @@ class Boiler : public EMSdevice {
static constexpr uint8_t EMS_BOILER_SELFLOWTEMP_HEATING = 20; // was originally 70, changed to 30 for issue #193, then to 20 with issue #344 static constexpr uint8_t EMS_BOILER_SELFLOWTEMP_HEATING = 20; // was originally 70, changed to 30 for issue #193, then to 20 with issue #344
// UBAParameterWW // ww
uint8_t wWActivated_ = EMS_VALUE_BOOL_NOTSET; // Warm Water activated uint8_t wWSetTemp_; // Warm Water set temperature
uint8_t wWSelTemp_ = EMS_VALUE_UINT_NOTSET; // Warm Water selected temperature uint8_t wWSelTemp_; // Warm Water selected temperature
uint8_t wWCircPump_ = EMS_VALUE_BOOL_NOTSET; // Warm Water circulation pump available uint8_t wWType_; // 0-off, 1-flow, 2-flowbuffer, 3-buffer, 4-layered buffer
uint8_t wWCircPumpMode_ = EMS_VALUE_UINT_NOTSET; // Warm Water circulation pump mode uint8_t wWComfort_; // WW comfort mode
uint8_t wWChargeType_ = EMS_VALUE_BOOL_NOTSET; // Warm Water charge type (pump or 3-way-valve) uint8_t wWCircPump_; // Warm Water circulation pump available
uint8_t wWDisinfectionTemp_ = EMS_VALUE_UINT_NOTSET; // Warm Water disinfection temperature to prevent infection uint8_t wWChargeType_; // Warm Water charge type (pump or 3-way-valve)
uint8_t wWComfort_ = EMS_VALUE_UINT_NOTSET; // WW comfort mode uint8_t wWDisinfectionTemp_; // Warm Water disinfection temperature to prevent infection
uint8_t wWCircPumpMode_; // Warm Water circulation pump mode
uint8_t wWCirc_; // Circulation on/off
uint16_t wWCurTemp_; // Warm Water current temperature
uint16_t wWCurTemp2_; // Warm Water current temperature storage
uint8_t wWCurFlow_; // Warm Water current flow temp in l/min
uint16_t wwStorageTemp1_; // warm water storage temp 1
uint16_t wwStorageTemp2_; // warm water storage temp 2
uint8_t wWActivated_; // Warm Water activated
uint8_t wWOneTime_; // Warm Water one time function on/off
uint8_t wWDisinfecting_; // Warm Water disinfection on/off
uint8_t wWCharging_; // Warm Water charging on/off
uint8_t wWRecharging_; // Warm Water recharge on/off
uint8_t wWTempOK_; // Warm Water temperature ok on/off
uint8_t wWActive_;
uint8_t wWHeat_; // 3-way valve on WW
uint8_t wWSetPumpPower_; // ww pump speed/power?
uint16_t wwMixTemperature_; // mixing temperature
uint16_t wwBufferTemperature_; // buffertemperature
uint32_t wWStarts_; // Warm Water # starts
uint32_t wWStarts2_; // Warm water starts (control)
uint32_t wWWorkM_; // Warm Water # minutes
// MC10Status // main
uint16_t wwMixTemperature_ = EMS_VALUE_USHORT_NOTSET; // mengertemperatuur uint8_t heatingActive_; // Central heating is on/off
uint16_t wwBufferTemperature_ = EMS_VALUE_USHORT_NOTSET; // buffertemperature uint8_t tapwaterActive_; // Hot tap water is on/off
uint8_t selFlowTemp_; // Selected flow temperature
// UBAMonitorFast - 0x18 on EMS1 uint8_t selBurnPow_; // Burner max power %
uint8_t selFlowTemp_ = EMS_VALUE_UINT_NOTSET; // Selected flow temperature uint8_t pumpMod2_; // heatpump modulation from 0xE3 (heatpumps)
uint16_t curFlowTemp_ = EMS_VALUE_USHORT_NOTSET; // Current flow temperature uint8_t pumpMod_; // Pump modulation %
uint16_t wwStorageTemp1_ = EMS_VALUE_USHORT_NOTSET; // warm water storage temp 1 int16_t outdoorTemp_; // Outside temperature
uint16_t wwStorageTemp2_ = EMS_VALUE_USHORT_NOTSET; // warm water storage temp 2 uint16_t curFlowTemp_; // Current flow temperature
uint16_t retTemp_ = EMS_VALUE_USHORT_NOTSET; // Return temperature uint16_t retTemp_; // Return temperature
uint8_t burnGas_ = EMS_VALUE_BOOL_NOTSET; // Gas on/off uint16_t switchTemp_; // Switch temperature
uint8_t fanWork_ = EMS_VALUE_BOOL_NOTSET; // Fan on/off uint8_t sysPress_; // System pressure
uint8_t ignWork_ = EMS_VALUE_BOOL_NOTSET; // Ignition on/off uint16_t boilTemp_; // Boiler temperature
uint8_t heatPump_ = EMS_VALUE_BOOL_NOTSET; // Boiler pump on/off uint16_t exhaustTemp_; // Exhaust temperature
uint8_t wWHeat_ = EMS_VALUE_BOOL_NOTSET; // 3-way valve on WW uint8_t burnGas_; // Gas on/off
uint8_t wWCirc_ = EMS_VALUE_BOOL_NOTSET; // Circulation on/off uint16_t flameCurr_; // Flame current in micro amps
uint8_t selBurnPow_ = EMS_VALUE_UINT_NOTSET; // Burner max power % uint8_t heatPump_; // Boiler pump on/off
uint8_t curBurnPow_ = EMS_VALUE_UINT_NOTSET; // Burner current power % uint8_t fanWork_; // Fan on/off
uint16_t flameCurr_ = EMS_VALUE_USHORT_NOTSET; // Flame current in micro amps uint8_t ignWork_; // Ignition on/off
uint8_t sysPress_ = EMS_VALUE_UINT_NOTSET; // System pressure uint8_t heatingActivated_; // Heating activated on the boiler
char serviceCode_[4] = {'\0'}; // 3 character status/service code uint8_t heatingTemp_; // Heating temperature setting on the boiler
uint16_t serviceCodeNumber_ = EMS_VALUE_USHORT_NOTSET; // error/service code uint8_t pumpModMax_; // Boiler circuit pump modulation max. power %
uint8_t boilerState_ = EMS_VALUE_UINT_NOTSET; // Boiler state flag uint8_t pumpModMin_; // Boiler circuit pump modulation min. power
char lastCode_[30] = {'\0'}; uint8_t pumpDelay_;
uint32_t lastCodeDate_ = 0; uint8_t burnMinPeriod_;
uint8_t burnMinPower_;
// UBAMonitorSlow - 0x19 on EMS1 uint8_t burnMaxPower_;
int16_t outdoorTemp_ = EMS_VALUE_SHORT_NOTSET; // Outside temperature int8_t boilHystOn_;
uint16_t boilTemp_ = EMS_VALUE_USHORT_NOTSET; // Boiler temperature int8_t boilHystOff_;
uint16_t exhaustTemp_ = EMS_VALUE_USHORT_NOTSET; // Exhaust temperature uint8_t setFlowTemp_; // boiler setpoint temp
uint8_t pumpMod_ = EMS_VALUE_UINT_NOTSET; // Pump modulation % uint8_t curBurnPow_; // Burner current power %
uint32_t burnStarts_ = EMS_VALUE_ULONG_NOTSET; // # burner restarts uint8_t setBurnPow_; // max output power in %
uint32_t burnWorkMin_ = EMS_VALUE_ULONG_NOTSET; // Total burner operating time uint32_t burnStarts_; // # burner restarts
uint32_t heatWorkMin_ = EMS_VALUE_ULONG_NOTSET; // Total heat operating time uint32_t burnWorkMin_; // Total burner operating time
uint16_t switchTemp_ = EMS_VALUE_USHORT_NOTSET; // Switch temperature uint32_t heatWorkMin_; // Total heat operating time
uint32_t UBAuptime_; // Total UBA working hours
// UBAMonitorWW char lastCode_[30]; // last error code
uint8_t wWSetTemp_ = EMS_VALUE_UINT_NOTSET; // Warm Water set temperature char serviceCode_[4]; // 3 character status/service code
uint16_t wWCurTemp_ = EMS_VALUE_USHORT_NOTSET; // Warm Water current temperature uint16_t serviceCodeNumber_; // error/service code
uint16_t wWCurTemp2_ = EMS_VALUE_USHORT_NOTSET; // Warm Water current temperature storage
uint32_t wWStarts_ = EMS_VALUE_ULONG_NOTSET; // Warm Water # starts
uint32_t wWWorkM_ = EMS_VALUE_ULONG_NOTSET; // Warm Water # minutes
uint8_t wWOneTime_ = EMS_VALUE_BOOL_NOTSET; // Warm Water one time function on/off
uint8_t wWDisinfecting_ = EMS_VALUE_BOOL_NOTSET; // Warm Water disinfection on/off
uint8_t wWCharging_ = EMS_VALUE_BOOL_NOTSET; // Warm Water charging on/off
uint8_t wWRecharging_ = EMS_VALUE_BOOL_NOTSET; // Warm Water recharge on/off
uint8_t wWTempOK_ = EMS_VALUE_BOOL_NOTSET; // Warm Water temperature ok on/off
uint8_t wWCurFlow_ = EMS_VALUE_UINT_NOTSET; // Warm Water current flow temp in l/min
uint8_t wWType_ = EMS_VALUE_UINT_NOTSET; // 0-off, 1-flow, 2-flowbuffer, 3-buffer, 4-layered buffer
uint8_t wWActive_ = EMS_VALUE_BOOL_NOTSET;
// UBATotalUptime
uint32_t UBAuptime_ = EMS_VALUE_ULONG_NOTSET; // Total UBA working hours
// UBAParameters
uint8_t heatingActivated_ = EMS_VALUE_BOOL_NOTSET; // Heating activated on the boiler
uint8_t heatingTemp_ = EMS_VALUE_UINT_NOTSET; // Heating temperature setting on the boiler
uint8_t pumpModMax_ = EMS_VALUE_UINT_NOTSET; // Boiler circuit pump modulation max. power %
uint8_t pumpModMin_ = EMS_VALUE_UINT_NOTSET; // Boiler circuit pump modulation min. power
uint8_t burnMinPower_ = EMS_VALUE_UINT_NOTSET;
uint8_t burnMaxPower_ = EMS_VALUE_UINT_NOTSET;
int8_t boilHystOff_ = EMS_VALUE_INT_NOTSET;
int8_t boilHystOn_ = EMS_VALUE_INT_NOTSET;
uint8_t burnMinPeriod_ = EMS_VALUE_UINT_NOTSET;
uint8_t pumpDelay_ = EMS_VALUE_UINT_NOTSET;
// UBASetPoint
uint8_t setFlowTemp_ = EMS_VALUE_UINT_NOTSET; // boiler setpoint temp
uint8_t setBurnPow_ = EMS_VALUE_UINT_NOTSET; // max output power in %
uint8_t wWSetPumpPower_ = EMS_VALUE_UINT_NOTSET; // ww pump speed/power?
// other internal calculated params
uint8_t tapwaterActive_ = EMS_VALUE_BOOL_NOTSET; // Hot tap water is on/off
uint8_t heatingActive_ = EMS_VALUE_BOOL_NOTSET; // Central heating is on/off
uint8_t pumpMod2_ = EMS_VALUE_UINT_NOTSET; // heatpump modulation from 0xE3 (heatpumps)
// UBAInformation
uint32_t upTimeControl_ = EMS_VALUE_ULONG_NOTSET; // Operating time control
uint32_t upTimeCompHeating_ = EMS_VALUE_ULONG_NOTSET; // Operating time compressor heating
uint32_t upTimeCompCooling_ = EMS_VALUE_ULONG_NOTSET; // Operating time compressor cooling
uint32_t upTimeCompWw_ = EMS_VALUE_ULONG_NOTSET; // Operating time compressor warm water
uint32_t heatingStarts_ = EMS_VALUE_ULONG_NOTSET; // Heating starts (control)
uint32_t coolingStarts_ = EMS_VALUE_ULONG_NOTSET; // Cooling starts (control)
uint32_t wWStarts2_ = EMS_VALUE_ULONG_NOTSET; // Warm water starts (control)
uint32_t nrgConsTotal_ = EMS_VALUE_ULONG_NOTSET; // Energy consumption total
uint32_t auxElecHeatNrgConsTotal_ = EMS_VALUE_ULONG_NOTSET; // Auxiliary electrical heater energy consumption total
uint32_t auxElecHeatNrgConsHeating_ = EMS_VALUE_ULONG_NOTSET; // Auxiliary electrical heater energy consumption heating
uint32_t auxElecHeatNrgConsDHW_ = EMS_VALUE_ULONG_NOTSET; // Auxiliary electrical heater energ consumption DHW
uint32_t nrgConsCompTotal_ = EMS_VALUE_ULONG_NOTSET; // Energy consumption compressor total
uint32_t nrgConsCompHeating_ = EMS_VALUE_ULONG_NOTSET; // Energy consumption compressor heating
uint32_t nrgConsCompWw_ = EMS_VALUE_ULONG_NOTSET; // Energy consumption compressor warm water
uint32_t nrgConsCompCooling_ = EMS_VALUE_ULONG_NOTSET; // Energy consumption compressor cooling
// UBAEnergySupplied
uint32_t nrgSuppTotal_ = EMS_VALUE_ULONG_NOTSET; // Energy supplied total
uint32_t nrgSuppHeating_ = EMS_VALUE_ULONG_NOTSET; // Energy supplied heating
uint32_t nrgSuppWw_ = EMS_VALUE_ULONG_NOTSET; // Energy supplied warm water
uint32_t nrgSuppCooling_ = EMS_VALUE_ULONG_NOTSET; // Energy supplied cooling
// _UBAMaintenanceData
uint8_t maintenanceMessage_ = EMS_VALUE_UINT_NOTSET;
uint8_t maintenanceType_ = EMS_VALUE_UINT_NOTSET;
uint8_t maintenanceTime_ = EMS_VALUE_UINT_NOTSET;
char maintenanceDate_[12] = {'\0'};
// info
uint32_t upTimeControl_; // Operating time control
uint32_t upTimeCompHeating_; // Operating time compressor heating
uint32_t upTimeCompCooling_; // Operating time compressor cooling
uint32_t upTimeCompWw_; // Operating time compressor warm water
uint32_t heatingStarts_; // Heating starts (control)
uint32_t coolingStarts_; // Cooling starts (control)
uint32_t nrgConsTotal_; // Energy consumption total
uint32_t nrgConsCompTotal_; // Energy consumption compressor total
uint32_t nrgConsCompHeating_; // Energy consumption compressor heating
uint32_t nrgConsCompWw_; // Energy consumption compressor warm water
uint32_t nrgConsCompCooling_; // Energy consumption compressor cooling
uint32_t nrgSuppTotal_; // Energy supplied total
uint32_t nrgSuppHeating_; // Energy supplied heating
uint32_t nrgSuppWw_; // Energy supplied warm water
uint32_t nrgSuppCooling_; // Energy supplied cooling
uint32_t auxElecHeatNrgConsTotal_; // Auxiliary electrical heater energy consumption total
uint32_t auxElecHeatNrgConsHeating_; // Auxiliary electrical heater energy consumption heating
uint32_t auxElecHeatNrgConsDHW_; // Auxiliary electrical heater energy consumption DHW
char maintenanceMessage_[4];
char maintenanceDate_[12];
uint8_t maintenanceType_;
uint8_t maintenanceTime_;
void process_UBAParameterWW(std::shared_ptr<const Telegram> telegram); void process_UBAParameterWW(std::shared_ptr<const Telegram> telegram);
void process_UBAMonitorFast(std::shared_ptr<const Telegram> telegram); void process_UBAMonitorFast(std::shared_ptr<const Telegram> telegram);

View File

@@ -28,21 +28,9 @@ Connect::Connect(uint8_t device_type, uint8_t device_id, uint8_t product_id, con
: EMSdevice(device_type, device_id, product_id, version, name, flags, brand) { : EMSdevice(device_type, device_id, product_id, version, name, flags, brand) {
} }
void Connect::device_info_web(JsonArray & root) { // publish HA config
} bool Connect::publish_ha_config() {
// publish values via MQTT
void Connect::publish_values(JsonObject & json, bool force) {
}
// export values to JSON
bool Connect::export_values(JsonObject & json) {
return true; return true;
} }
// check to see if values have been updated
bool Connect::updated_values() {
return false;
}
} // namespace emsesp } // namespace emsesp

View File

@@ -35,10 +35,7 @@ class Connect : public EMSdevice {
public: public:
Connect(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand); Connect(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand);
virtual void publish_values(JsonObject & json, bool force); virtual bool publish_ha_config();
virtual bool export_values(JsonObject & json);
virtual void device_info_web(JsonArray & root);
virtual bool updated_values();
private: private:
static uuid::log::Logger logger_; static uuid::log::Logger logger_;

View File

@@ -28,21 +28,9 @@ Controller::Controller(uint8_t device_type, uint8_t device_id, uint8_t product_i
: EMSdevice(device_type, device_id, product_id, version, name, flags, brand) { : EMSdevice(device_type, device_id, product_id, version, name, flags, brand) {
} }
void Controller::device_info_web(JsonArray & root) { // publish HA config
} bool Controller::publish_ha_config() {
// publish values via MQTT
void Controller::publish_values(JsonObject & json, bool force) {
}
// export values to JSON
bool Controller::export_values(JsonObject & json) {
return true; return true;
} }
// check to see if values have been updated
bool Controller::updated_values() {
return false;
}
} // namespace emsesp } // namespace emsesp

View File

@@ -35,10 +35,7 @@ class Controller : public EMSdevice {
public: public:
Controller(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand); Controller(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand);
virtual void publish_values(JsonObject & json, bool force); virtual bool publish_ha_config();
virtual bool export_values(JsonObject & json);
virtual void device_info_web(JsonArray & root);
virtual bool updated_values();
private: private:
static uuid::log::Logger logger_; static uuid::log::Logger logger_;

View File

@@ -28,21 +28,9 @@ Gateway::Gateway(uint8_t device_type, uint8_t device_id, uint8_t product_id, con
: EMSdevice(device_type, device_id, product_id, version, name, flags, brand) { : EMSdevice(device_type, device_id, product_id, version, name, flags, brand) {
} }
void Gateway::device_info_web(JsonArray & root) { // publish HA config
} bool Gateway::publish_ha_config() {
// publish values via MQTT
void Gateway::publish_values(JsonObject & json, bool force) {
}
// export values to JSON
bool Gateway::export_values(JsonObject & json) {
return true; return true;
} }
// check to see if values have been updated
bool Gateway::updated_values() {
return false;
}
} // namespace emsesp } // namespace emsesp

View File

@@ -35,10 +35,7 @@ class Gateway : public EMSdevice {
public: public:
Gateway(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand); Gateway(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand);
virtual void publish_values(JsonObject & json, bool force); virtual bool publish_ha_config();
virtual bool export_values(JsonObject & json);
virtual void device_info_web(JsonArray & root);
virtual bool updated_values();
private: private:
static uuid::log::Logger logger_; static uuid::log::Logger logger_;

View File

@@ -28,21 +28,9 @@ Generic::Generic(uint8_t device_type, uint8_t device_id, uint8_t product_id, con
: EMSdevice(device_type, device_id, product_id, version, name, flags, brand) { : EMSdevice(device_type, device_id, product_id, version, name, flags, brand) {
} }
void Generic::device_info_web(JsonArray & root) { // publish HA config
} bool Generic::publish_ha_config() {
// publish values via MQTT
void Generic::publish_values(JsonObject & json, bool force) {
}
// export values to JSON
bool Generic::export_values(JsonObject & json) {
return true; return true;
} }
// check to see if values have been updated
bool Generic::updated_values() {
return false;
}
} // namespace emsesp } // namespace emsesp

View File

@@ -35,10 +35,7 @@ class Generic : public EMSdevice {
public: public:
Generic(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand); Generic(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand);
virtual void publish_values(JsonObject & json, bool force); virtual bool publish_ha_config();
virtual bool export_values(JsonObject & json);
virtual void device_info_web(JsonArray & root);
virtual bool updated_values();
private: private:
static uuid::log::Logger logger_; static uuid::log::Logger logger_;

View File

@@ -31,66 +31,23 @@ Heatpump::Heatpump(uint8_t device_type, uint8_t device_id, uint8_t product_id, c
// telegram handlers // telegram handlers
register_telegram_type(0x042B, F("HP1"), true, [&](std::shared_ptr<const Telegram> t) { process_HPMonitor1(t); }); register_telegram_type(0x042B, F("HP1"), true, [&](std::shared_ptr<const Telegram> t) { process_HPMonitor1(t); });
register_telegram_type(0x047B, F("HP2"), true, [&](std::shared_ptr<const Telegram> t) { process_HPMonitor2(t); }); register_telegram_type(0x047B, F("HP2"), true, [&](std::shared_ptr<const Telegram> t) { process_HPMonitor2(t); });
std::string empty("");
register_device_value(empty, &airHumidity_, DeviceValueType::UINT, flash_string_vector{F("2")}, F("airHumidity"), F("Relative air humidity"), DeviceValueUOM::NONE);
register_device_value(empty, &dewTemperature_, DeviceValueType::UINT, {}, F("dewTemperature"), F("Dew point temperature"), DeviceValueUOM::NONE);
} }
// creates JSON doc from values // publish HA config
// returns false if empty bool Heatpump::publish_ha_config() {
bool Heatpump::export_values(JsonObject & json) { StaticJsonDocument<EMSESP_JSON_SIZE_HA_CONFIG> doc;
if (Helpers::hasValue(airHumidity_)) {
json["airHumidity"] = (float)airHumidity_ / 2;
}
if (Helpers::hasValue(dewTemperature_)) {
json["dewTemperature"] = dewTemperature_;
}
return json.size();
}
void Heatpump::device_info_web(JsonArray & root) {
// fetch the values into a JSON document
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc;
JsonObject json = doc.to<JsonObject>();
if (!export_values(json)) {
return; // empty
}
create_value_json(root, F("airHumidity"), nullptr, F_(airHumidity), F_(percent), json);
create_value_json(root, F("dewTemperature"), nullptr, F_(dewTemperature), F_(degrees), json);
}
// publish values via MQTT
void Heatpump::publish_values(JsonObject & json, bool force) {
// handle HA first
if (Mqtt::mqtt_format() == Mqtt::Format::HA) {
if (!mqtt_ha_config_ || force) {
register_mqtt_ha_config();
return;
}
}
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc;
JsonObject json_data = doc.to<JsonObject>();
if (export_values(json_data)) {
Mqtt::publish(F("heatpump_data"), doc.as<JsonObject>());
}
}
void Heatpump::register_mqtt_ha_config() {
if (!Mqtt::connected()) {
return;
}
// Create the Master device
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_HA_CONFIG> doc;
doc["name"] = F_(EMSESP);
doc["uniq_id"] = F_(heatpump); doc["uniq_id"] = F_(heatpump);
doc["ic"] = F_(iconheatpump); doc["ic"] = F_(iconvalve);
char stat_t[50]; char stat_t[50];
snprintf_P(stat_t, sizeof(stat_t), PSTR("%s/heatpump_data"), System::hostname().c_str()); snprintf_P(stat_t, sizeof(stat_t), PSTR("%s/heatpump_data"), System::hostname().c_str());
doc["stat_t"] = stat_t; doc["stat_t"] = stat_t;
doc["name"] = FJSON("Humidity");
doc["val_tpl"] = FJSON("{{value_json.airHumidity}}"); doc["val_tpl"] = FJSON("{{value_json.airHumidity}}");
JsonObject dev = doc.createNestedObject("dev"); JsonObject dev = doc.createNestedObject("dev");
@@ -100,21 +57,12 @@ void Heatpump::register_mqtt_ha_config() {
dev["mdl"] = this->name(); dev["mdl"] = this->name();
JsonArray ids = dev.createNestedArray("ids"); JsonArray ids = dev.createNestedArray("ids");
ids.add("ems-esp-heatpump"); ids.add("ems-esp-heatpump");
Mqtt::publish_ha(F("homeassistant/sensor/ems-esp/heatpump/config"), doc.as<JsonObject>()); // publish the config payload with retain flag
Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(airHumidity), device_type(), "airHumidity", F_(percent), nullptr); char topic[100];
Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(dewTemperature), device_type(), "dewTemperature", F_(degrees), nullptr); snprintf_P(topic, sizeof(topic), PSTR("homeassistant/sensor/%s/heatpump/config"), System::hostname().c_str());
Mqtt::publish_ha(topic, doc.as<JsonObject>()); // publish the config payload with retain flag
mqtt_ha_config_ = true; // done
}
// check to see if values have been updated
bool Heatpump::updated_values() {
if (changed_) {
changed_ = false;
return true; return true;
}
return false;
} }
/* /*
@@ -122,8 +70,8 @@ bool Heatpump::updated_values() {
* e.g. "38 10 FF 00 03 7B 08 24 00 4B" * e.g. "38 10 FF 00 03 7B 08 24 00 4B"
*/ */
void Heatpump::process_HPMonitor2(std::shared_ptr<const Telegram> telegram) { void Heatpump::process_HPMonitor2(std::shared_ptr<const Telegram> telegram) {
changed_ |= telegram->read_value(dewTemperature_, 0); has_update(telegram->read_value(dewTemperature_, 0));
changed_ |= telegram->read_value(airHumidity_, 1); has_update(telegram->read_value(airHumidity_, 1));
} }
#pragma GCC diagnostic push #pragma GCC diagnostic push

View File

@@ -36,21 +36,13 @@ class Heatpump : public EMSdevice {
public: public:
Heatpump(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand); Heatpump(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand);
virtual void publish_values(JsonObject & json, bool force); virtual bool publish_ha_config();
virtual bool export_values(JsonObject & json);
virtual void device_info_web(JsonArray & root);
virtual bool updated_values();
private: private:
static uuid::log::Logger logger_; static uuid::log::Logger logger_;
void register_mqtt_ha_config(); uint8_t airHumidity_;
uint8_t dewTemperature_;
uint8_t airHumidity_ = EMS_VALUE_UINT_NOTSET;
uint8_t dewTemperature_ = EMS_VALUE_UINT_NOTSET;
bool changed_ = false;
bool mqtt_ha_config_ = false; // for HA MQTT Discovery
void process_HPMonitor1(std::shared_ptr<const Telegram> telegram); void process_HPMonitor1(std::shared_ptr<const Telegram> telegram);
void process_HPMonitor2(std::shared_ptr<const Telegram> telegram); void process_HPMonitor2(std::shared_ptr<const Telegram> telegram);

View File

@@ -55,103 +55,50 @@ Mixer::Mixer(uint8_t device_type, uint8_t device_id, uint8_t product_id, const s
} }
} }
// output json to web UI
void Mixer::device_info_web(JsonArray & root) { // register values, depending on type (hc or wwc)
if (type() == Type::NONE) { void Mixer::register_values(const Type type, uint16_t hc) {
return; // don't have any values yet if (type == Type::NONE) {
return; // already done
} }
// fetch the values into a JSON document // store the heating circuit and type
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc; hc_ = hc + 1;
JsonObject json = doc.to<JsonObject>(); type_ = type;
if (!export_values_format(Mqtt::Format::SINGLE, json)) { std::string prefix(10, '\0');
return; // empty snprintf_P(&prefix[0], sizeof(prefix), PSTR("%s%d"), (type_ == Type::HC) ? "hc" : "wwc", hc + 1);
}
char prefix_str[10]; register_device_value(
if (type() == Type::HC) { prefix, &flowTemp_, DeviceValueType::USHORT, flash_string_vector{F("10")}, F("flowTemp"), F("Current flow temperature"), DeviceValueUOM::DEGREES);
snprintf_P(prefix_str, sizeof(prefix_str), PSTR("(hc %d) "), hc_); register_device_value(prefix, &flowSetTemp_, DeviceValueType::UINT, {}, F("flowSetTemp"), F("Setpoint flow temperature"), DeviceValueUOM::DEGREES);
create_value_json(root, F("flowTemp"), FPSTR(prefix_str), F_(flowTemp), F_(degrees), json); register_device_value(prefix, &pumpStatus_, DeviceValueType::BOOL, {}, F("pumpStatus"), F("Pump/Valve status"), DeviceValueUOM::NONE);
create_value_json(root, F("flowSetTemp"), FPSTR(prefix_str), F_(flowSetTemp), F_(degrees), json); register_device_value(prefix, &status_, DeviceValueType::INT, {}, F("status"), F("Current status"), DeviceValueUOM::NONE);
create_value_json(root, F("pumpStatus"), FPSTR(prefix_str), F_(pumpStatus), nullptr, json);
create_value_json(root, F("valveStatus"), FPSTR(prefix_str), F_(valveStatus), F_(percent), json);
} else {
snprintf_P(prefix_str, sizeof(prefix_str), PSTR("(wwc %d) "), hc_);
create_value_json(root, F("wwTemp"), FPSTR(prefix_str), F_(wwTemp), F_(degrees), json);
create_value_json(root, F("pumpStatus"), FPSTR(prefix_str), F_(pumpStatus), nullptr, json);
create_value_json(root, F("tempStatus"), FPSTR(prefix_str), F_(tempStatus), nullptr, json);
}
} }
// check to see if values have been updated
bool Mixer::updated_values() {
if (changed_) {
changed_ = false;
return true;
}
return false;
}
// publish values via MQTT
// topic is mixer_data<id>
void Mixer::publish_values(JsonObject & json, bool force) {
// handle HA first
if (Mqtt::mqtt_format() == Mqtt::Format::HA) {
if (!mqtt_ha_config_ || force) {
register_mqtt_ha_config();
return;
}
}
if (Mqtt::mqtt_format() == Mqtt::Format::SINGLE) {
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc;
JsonObject json_data = doc.to<JsonObject>();
if (export_values_format(Mqtt::mqtt_format(), json_data)) {
char topic[30];
if (type() == Type::HC) {
snprintf_P(topic, 30, PSTR("mixer_data_hc%d"), hc_);
} else {
snprintf_P(topic, 30, PSTR("mixer_data_wwc%d"), hc_);
}
Mqtt::publish(topic, doc.as<JsonObject>());
}
} else {
// format is HA or Nested. This is bundled together and sent in emsesp.cpp
export_values_format(Mqtt::mqtt_format(), json);
}
}
// publish config topic for HA MQTT Discovery
void Mixer::register_mqtt_ha_config() {
if (!Mqtt::connected()) {
return;
}
// publish HA config
bool Mixer::publish_ha_config() {
// if we don't have valid values for this HC don't add it ever again // if we don't have valid values for this HC don't add it ever again
if (!Helpers::hasValue(pumpStatus_)) { if (!Helpers::hasValue(pumpStatus_)) {
return; return false;
} }
// Create the Master device StaticJsonDocument<EMSESP_JSON_SIZE_HA_CONFIG> doc;
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_HA_CONFIG> doc;
char name[20];
snprintf_P(name, sizeof(name), PSTR("Mixer %02X"), device_id() - 0x20 + 1);
doc["name"] = name;
char uniq_id[20]; char uniq_id[20];
snprintf_P(uniq_id, sizeof(uniq_id), PSTR("mixer%02X"), device_id() - 0x20 + 1); snprintf_P(uniq_id, sizeof(uniq_id), PSTR("Mixer%02X"), device_id() - 0x20 + 1);
doc["uniq_id"] = uniq_id; doc["uniq_id"] = uniq_id;
doc["ic"] = FJSON("mdi:home-thermometer-outline");
char stat_t[50]; char stat_t[50];
snprintf_P(stat_t, sizeof(stat_t), PSTR("%s/mixer_data"), System::hostname().c_str()); snprintf_P(stat_t, sizeof(stat_t), PSTR("%s/mixer_data"), System::hostname().c_str());
doc["stat_t"] = stat_t; doc["stat_t"] = stat_t;
doc["val_tpl"] = FJSON("{{value_json.type}}"); // HA needs a single value. We take the type which is wwc or hc char name[20];
snprintf_P(name, sizeof(name), PSTR("Mixer %02X Type"), device_id() - 0x20 + 1);
doc["name"] = name;
doc["val_tpl"] = FJSON("{{value_json.type}}"); // HA needs a single value. We take the type which is wwc or hc
JsonObject dev = doc.createNestedObject("dev"); JsonObject dev = doc.createNestedObject("dev");
dev["name"] = FJSON("EMS-ESP Mixer"); dev["name"] = FJSON("EMS-ESP Mixer");
dev["sw"] = EMSESP_APP_VERSION; dev["sw"] = EMSESP_APP_VERSION;
@@ -160,127 +107,45 @@ void Mixer::register_mqtt_ha_config() {
JsonArray ids = dev.createNestedArray("ids"); JsonArray ids = dev.createNestedArray("ids");
ids.add("ems-esp-mixer"); ids.add("ems-esp-mixer");
// determine the topic, if its HC and WWC. This is determined by the incoming telegram types.
std::string topic(100, '\0'); std::string topic(100, '\0');
if (type() == Type::HC) { if (type_ == Type::HC) {
snprintf_P(&topic[0], topic.capacity() + 1, PSTR("homeassistant/sensor/ems-esp/mixer_hc%d/config"), hc_); snprintf_P(&topic[0], topic.capacity() + 1, PSTR("homeassistant/sensor/%s/mixer_hc%d/config"), System::hostname().c_str(), hc_);
} else {
snprintf_P(&topic[0], topic.capacity() + 1, PSTR("homeassistant/sensor/%s/mixer_wwc%d/config"), System::hostname().c_str(), hc_); // WWC
}
Mqtt::publish_ha(topic, doc.as<JsonObject>()); // publish the config payload with retain flag Mqtt::publish_ha(topic, doc.as<JsonObject>()); // publish the config payload with retain flag
char hc_name[10];
snprintf_P(hc_name, sizeof(hc_name), PSTR("hc%d"), hc_);
Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(flowTemp), device_type(), "flowTemp", F_(degrees), F_(icontemperature));
Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(flowSetTemp), device_type(), "flowSetTemp", F_(degrees), F_(icontemperature));
Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(pumpStatus), device_type(), "pumpStatus", nullptr, nullptr);
Mqtt::register_mqtt_ha_sensor(hc_name, nullptr, F_(valveStatus), device_type(), "valveStatus", F_(percent), F_(iconpercent));
} else {
// WWC
snprintf_P(&topic[0], topic.capacity() + 1, PSTR("homeassistant/sensor/ems-esp/mixer_wwc%d/config"), hc_);
Mqtt::publish_ha(topic, doc.as<JsonObject>()); // publish the config payload with retain flag
char wwc_name[10];
snprintf_P(wwc_name, sizeof(wwc_name), PSTR("wwc%d"), hc_);
Mqtt::register_mqtt_ha_sensor(wwc_name, nullptr, F_(wwTemp), device_type(), "wwTemp", F_(degrees), F_(icontemperature));
Mqtt::register_mqtt_ha_sensor(wwc_name, nullptr, F_(pumpStatus), device_type(), "pumpStatus", nullptr, nullptr);
Mqtt::register_mqtt_ha_sensor(wwc_name, nullptr, F_(tempStatus), device_type(), "tempStatus", nullptr, nullptr);
}
mqtt_ha_config_ = true; // done return true;
}
bool Mixer::export_values(JsonObject & json) {
return export_values_format(Mqtt::Format::NESTED, json);
}
// creates JSON doc from values
// returns false if empty
bool Mixer::export_values_format(uint8_t mqtt_format, JsonObject & json) {
// check if there is data for the mixer unit
if (type() == Type::NONE) {
return 0;
}
JsonObject json_hc;
char hc_name[10]; // hc{1-4}
if (type() == Type::HC) {
snprintf_P(hc_name, sizeof(hc_name), PSTR("hc%d"), hc_);
if (mqtt_format == Mqtt::Format::SINGLE) {
json_hc = json;
json["type"] = FJSON("hc");
} else if (mqtt_format == Mqtt::Format::HA) {
json_hc = json.createNestedObject(hc_name);
json_hc["type"] = FJSON("hc");
} else {
json_hc = json.createNestedObject(hc_name);
}
if (Helpers::hasValue(flowTemp_)) {
json_hc["flowTemp"] = (float)flowTemp_ / 10;
}
if (Helpers::hasValue(flowSetTemp_)) {
json_hc["flowSetTemp"] = flowSetTemp_;
}
if (Helpers::hasValue(pumpStatus_)) {
char s[7];
json_hc["pumpStatus"] = Helpers::render_value(s, pumpStatus_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(status_)) {
json_hc["valveStatus"] = status_;
}
return json_hc.size();
}
// WWC
snprintf_P(hc_name, sizeof(hc_name), PSTR("wwc%d"), hc_);
if (mqtt_format == Mqtt::Format::SINGLE) {
json_hc = json;
json["type"] = FJSON("wwc");
} else if (mqtt_format == Mqtt::Format::HA) {
json_hc = json.createNestedObject(hc_name);
json_hc["type"] = FJSON("wwc");
} else {
json_hc = json.createNestedObject(hc_name);
}
if (Helpers::hasValue(flowTemp_)) {
json_hc["wwTemp"] = (float)flowTemp_ / 10;
}
if (Helpers::hasValue(pumpStatus_)) {
char s[7];
json_hc["pumpStatus"] = Helpers::render_value(s, pumpStatus_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(status_)) {
json_hc["tempStatus"] = status_;
}
return json_hc.size();
} }
// heating circuits 0x02D7, 0x02D8 etc... // heating circuits 0x02D7, 0x02D8 etc...
// e.g. A0 00 FF 00 01 D7 00 00 00 80 00 00 00 00 03 C5 // e.g. A0 00 FF 00 01 D7 00 00 00 80 00 00 00 00 03 C5
// A0 0B FF 00 01 D7 00 00 00 80 00 00 00 00 03 80 // A0 0B FF 00 01 D7 00 00 00 80 00 00 00 00 03 80
void Mixer::process_MMPLUSStatusMessage_HC(std::shared_ptr<const Telegram> telegram) { void Mixer::process_MMPLUSStatusMessage_HC(std::shared_ptr<const Telegram> telegram) {
type(Type::HC); register_values(Type::HC, telegram->type_id - 0x02D7);
hc_ = telegram->type_id - 0x02D7 + 1; // determine which circuit this is has_update(telegram->read_value(flowTemp_, 3)); // is * 10
changed_ |= telegram->read_value(flowTemp_, 3); // is * 10 has_update(telegram->read_value(flowSetTemp_, 5));
changed_ |= telegram->read_value(flowSetTemp_, 5); has_update(telegram->read_bitvalue(pumpStatus_, 0, 0));
changed_ |= telegram->read_bitvalue(pumpStatus_, 0, 0); has_update(telegram->read_value(status_, 2)); // valve status
changed_ |= telegram->read_value(status_, 2); // valve status
} }
// Mixer warm water loading/DHW - 0x0331, 0x0332 // Mixer warm water loading/DHW - 0x0331, 0x0332
// e.g. A9 00 FF 00 02 32 02 6C 00 3C 00 3C 3C 46 02 03 03 00 3C // on 0x28 // e.g. A9 00 FF 00 02 32 02 6C 00 3C 00 3C 3C 46 02 03 03 00 3C // on 0x28
// A8 00 FF 00 02 31 02 35 00 3C 00 3C 3C 46 02 03 03 00 3C // in 0x29 // A8 00 FF 00 02 31 02 35 00 3C 00 3C 3C 46 02 03 03 00 3C // in 0x29
void Mixer::process_MMPLUSStatusMessage_WWC(std::shared_ptr<const Telegram> telegram) { void Mixer::process_MMPLUSStatusMessage_WWC(std::shared_ptr<const Telegram> telegram) {
type(Type::WWC); register_values(Type::WWC, telegram->type_id - 0x0331);
hc_ = telegram->type_id - 0x0331 + 1; // determine which circuit this is. There are max 2. has_update(telegram->read_value(flowTemp_, 0)); // is * 10
changed_ |= telegram->read_value(flowTemp_, 0); // is * 10 has_update(telegram->read_bitvalue(pumpStatus_, 2, 0));
changed_ |= telegram->read_bitvalue(pumpStatus_, 2, 0); has_update(telegram->read_value(status_, 11)); // temp status
changed_ |= telegram->read_value(status_, 11); // temp status
} }
// Mixer IMP - 0x010C // Mixer IMP - 0x010C
// e.g. A0 00 FF 00 00 0C 01 00 00 00 00 00 54 // e.g. A0 00 FF 00 00 0C 01 00 00 00 00 00 54
// A1 00 FF 00 00 0C 02 04 00 01 1D 00 82 // A1 00 FF 00 00 0C 02 04 00 01 1D 00 82
void Mixer::process_IPMStatusMessage(std::shared_ptr<const Telegram> telegram) { void Mixer::process_IPMStatusMessage(std::shared_ptr<const Telegram> telegram) {
type(Type::HC); register_values(Type::HC, device_id() - 0x20);
hc_ = device_id() - 0x20 + 1;
// check if circuit is active, 0-off, 1-unmixed, 2-mixed // check if circuit is active, 0-off, 1-unmixed, 2-mixed
uint8_t ismixed = 0; uint8_t ismixed = 0;
@@ -291,28 +156,27 @@ void Mixer::process_IPMStatusMessage(std::shared_ptr<const Telegram> telegram) {
// do we have a mixed circuit // do we have a mixed circuit
if (ismixed == 2) { if (ismixed == 2) {
changed_ |= telegram->read_value(flowTemp_, 3); // is * 10 has_update(telegram->read_value(flowTemp_, 3)); // is * 10
changed_ |= telegram->read_value(flowSetTemp_, 5); has_update(telegram->read_value(flowSetTemp_, 5));
changed_ |= telegram->read_value(status_, 2); // valve status has_update(telegram->read_value(status_, 2)); // valve status
} }
changed_ |= telegram->read_bitvalue(pumpStatus_, 1, 0); // pump is also in unmixed circuits has_update(telegram->read_bitvalue(pumpStatus_, 1, 0)); // pump is also in unmixed circuits
} }
// Mixer on a MM10 - 0xAB // Mixer on a MM10 - 0xAB
// e.g. Mixer Module -> All, type 0xAB, telegram: 21 00 AB 00 2D 01 BE 64 04 01 00 (CRC=15) #data=7 // e.g. Mixer Module -> All, type 0xAB, telegram: 21 00 AB 00 2D 01 BE 64 04 01 00 (CRC=15) #data=7
// see also https://github.com/proddy/EMS-ESP/issues/386 // see also https://github.com/proddy/EMS-ESP/issues/386
void Mixer::process_MMStatusMessage(std::shared_ptr<const Telegram> telegram) { void Mixer::process_MMStatusMessage(std::shared_ptr<const Telegram> telegram) {
type(Type::HC);
// the heating circuit is determine by which device_id it is, 0x20 - 0x23 // the heating circuit is determine by which device_id it is, 0x20 - 0x23
// 0x21 is position 2. 0x20 is typically reserved for the WM10 switch module // 0x21 is position 2. 0x20 is typically reserved for the WM10 switch module
// see https://github.com/proddy/EMS-ESP/issues/270 and https://github.com/proddy/EMS-ESP/issues/386#issuecomment-629610918 // see https://github.com/proddy/EMS-ESP/issues/270 and https://github.com/proddy/EMS-ESP/issues/386#issuecomment-629610918
hc_ = device_id() - 0x20 + 1; register_values(Type::HC, device_id() - 0x20);
changed_ |= telegram->read_value(flowTemp_, 1); // is * 10
changed_ |= telegram->read_bitvalue(pumpStatus_, 3, 2); // is 0 or 0x64 (100%), check only bit 2 has_update(telegram->read_value(flowTemp_, 1)); // is * 10
changed_ |= telegram->read_value(flowSetTemp_, 0); has_update(telegram->read_bitvalue(pumpStatus_, 3, 2)); // is 0 or 0x64 (100%), check only bit 2
changed_ |= telegram->read_value(status_, 4); // valve status -100 to 100 has_update(telegram->read_value(flowSetTemp_, 0));
has_update(telegram->read_value(status_, 4)); // valve status -100 to 100
} }
#pragma GCC diagnostic push #pragma GCC diagnostic push
@@ -321,7 +185,7 @@ void Mixer::process_MMStatusMessage(std::shared_ptr<const Telegram> telegram) {
// Mixer on a MM10 - 0xAA // Mixer on a MM10 - 0xAA
// e.g. Thermostat -> Mixer Module, type 0xAA, telegram: 10 21 AA 00 FF 0C 0A 11 0A 32 xx // e.g. Thermostat -> Mixer Module, type 0xAA, telegram: 10 21 AA 00 FF 0C 0A 11 0A 32 xx
void Mixer::process_MMConfigMessage(std::shared_ptr<const Telegram> telegram) { void Mixer::process_MMConfigMessage(std::shared_ptr<const Telegram> telegram) {
hc_ = device_id() - 0x20 + 1; register_values(Type::HC, device_id() - 0x20);
// pos 0: active FF = on // pos 0: active FF = on
// pos 1: valve runtime 0C = 120 sec in units of 10 sec // pos 1: valve runtime 0C = 120 sec in units of 10 sec
} }
@@ -329,7 +193,7 @@ void Mixer::process_MMConfigMessage(std::shared_ptr<const Telegram> telegram) {
// Mixer on a MM10 - 0xAC // Mixer on a MM10 - 0xAC
// e.g. Thermostat -> Mixer Module, type 0xAC, telegram: 10 21 AC 00 1E 64 01 AB // e.g. Thermostat -> Mixer Module, type 0xAC, telegram: 10 21 AC 00 1E 64 01 AB
void Mixer::process_MMSetMessage(std::shared_ptr<const Telegram> telegram) { void Mixer::process_MMSetMessage(std::shared_ptr<const Telegram> telegram) {
hc_ = device_id() - 0x20 + 1; register_values(Type::HC, device_id() - 0x20);
// pos 0: flowtemp setpoint 1E = 30°C // pos 0: flowtemp setpoint 1E = 30°C
// pos 1: position in % // pos 1: position in %
} }

View File

@@ -36,17 +36,11 @@ class Mixer : public EMSdevice {
public: public:
Mixer(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand); Mixer(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand);
virtual void publish_values(JsonObject & json, bool force); virtual bool publish_ha_config();
virtual bool export_values(JsonObject & json);
virtual void device_info_web(JsonArray & root);
virtual bool updated_values();
private: private:
static uuid::log::Logger logger_; static uuid::log::Logger logger_;
bool export_values_format(uint8_t mqtt_format, JsonObject & doc);
void register_mqtt_ha_config();
void process_MMPLUSStatusMessage_HC(std::shared_ptr<const Telegram> telegram); void process_MMPLUSStatusMessage_HC(std::shared_ptr<const Telegram> telegram);
void process_MMPLUSStatusMessage_WWC(std::shared_ptr<const Telegram> telegram); void process_MMPLUSStatusMessage_WWC(std::shared_ptr<const Telegram> telegram);
void process_IPMStatusMessage(std::shared_ptr<const Telegram> telegram); void process_IPMStatusMessage(std::shared_ptr<const Telegram> telegram);
@@ -60,25 +54,15 @@ class Mixer : public EMSdevice {
WWC // warm water circuit WWC // warm water circuit
}; };
Type type() const {
return type_;
}
void type(Type new_type) {
type_ = new_type;
}
private: private:
uint16_t hc_ = EMS_VALUE_USHORT_NOTSET; uint16_t flowTemp_;
uint16_t flowTemp_ = EMS_VALUE_USHORT_NOTSET; uint8_t pumpStatus_;
uint8_t pumpStatus_ = EMS_VALUE_UINT_NOTSET; int8_t status_;
int8_t status_ = EMS_VALUE_INT_NOTSET; uint8_t flowSetTemp_;
uint8_t flowSetTemp_ = EMS_VALUE_UINT_NOTSET;
void register_values(const Type type, const uint16_t hc);
Type type_ = Type::NONE; Type type_ = Type::NONE;
uint16_t hc_ = EMS_VALUE_USHORT_NOTSET;
bool changed_ = false;
bool mqtt_ha_config_ = false; // for HA MQTT Discovery
}; };
} // namespace emsesp } // namespace emsesp

View File

@@ -59,67 +59,62 @@ Solar::Solar(uint8_t device_type, uint8_t device_id, uint8_t product_id, const s
register_telegram_type(0x0103, F("ISM1StatusMessage"), true, [&](std::shared_ptr<const Telegram> t) { process_ISM1StatusMessage(t); }); register_telegram_type(0x0103, F("ISM1StatusMessage"), true, [&](std::shared_ptr<const Telegram> t) { process_ISM1StatusMessage(t); });
register_telegram_type(0x0101, F("ISM1Set"), false, [&](std::shared_ptr<const Telegram> t) { process_ISM1Set(t); }); register_telegram_type(0x0101, F("ISM1Set"), false, [&](std::shared_ptr<const Telegram> t) { process_ISM1Set(t); });
} }
std::string empty("");
// special case for a device_id with 0x2A where it's not actual a solar module
if (device_id == 0x2A) {
register_device_value(empty, &type_, DeviceValueType::TEXT, {}, F("type"), F("Type"), DeviceValueUOM::NONE);
strncpy(type_, "warm water circuit", sizeof(type_));
}
register_device_value(empty,
&collectorTemp_,
DeviceValueType::SHORT,
flash_string_vector{F("10")},
F("collectorTemp"),
F("Collector temperature (TS1)"),
DeviceValueUOM::DEGREES);
register_device_value(empty,
&tankBottomTemp_,
DeviceValueType::SHORT,
flash_string_vector{F("10")},
F("tankBottomTemp"),
F("Bottom temperature (TS2)"),
DeviceValueUOM::DEGREES);
register_device_value(empty,
&tankBottomTemp2_,
DeviceValueType::SHORT,
flash_string_vector{F("10")},
F("tankBottomTemp2"),
F("Bottom temperature (TS5)"),
DeviceValueUOM::DEGREES);
register_device_value(
empty, &heatExchangerTemp_, DeviceValueType::SHORT, {F("10")}, F("heatExchangerTemp"), F("Heat exchanger temperature (TS6)"), DeviceValueUOM::DEGREES);
register_device_value(empty, &tank1MaxTempCurrent_, DeviceValueType::UINT, {}, F("tank1MaxTempCurrent"), F("Maximum Tank temperature"), DeviceValueUOM::NONE);
register_device_value(empty, &solarPumpModulation_, DeviceValueType::UINT, {}, F("solarPumpModulation"), F("Solar pump modulation (PS1)"), DeviceValueUOM::PERCENT);
register_device_value(
empty, &cylinderPumpModulation_, DeviceValueType::UINT, {}, F("cylinderPumpModulation"), F("Cylinder pump modulation (PS5)"), DeviceValueUOM::PERCENT);
register_device_value(empty, &solarPump_, DeviceValueType::BOOL, {}, F("solarPump"), F("Solar pump (PS1) active"), DeviceValueUOM::NONE);
register_device_value(empty, &valveStatus_, DeviceValueType::BOOL, {}, F("valveStatus"), F("Valve status"), DeviceValueUOM::NONE);
register_device_value(empty, &tankHeated_, DeviceValueType::BOOL, {}, F("tankHeated"), F("Tank heated"), DeviceValueUOM::NONE);
register_device_value(empty, &collectorShutdown_, DeviceValueType::BOOL, {}, F("collectorShutdown"), F("Collector shutdown"), DeviceValueUOM::NONE);
register_device_value(empty, &pumpWorkMin_, DeviceValueType::TIME, {}, F("pumpWorkMin"), F("Pump working time"), DeviceValueUOM::MINUTES);
register_device_value(
empty, &energyLastHour_, DeviceValueType::ULONG, flash_string_vector{F("10")}, F("energyLastHour"), F("Energy last hour"), DeviceValueUOM::WH);
register_device_value(empty, &energyTotal_, DeviceValueType::ULONG, flash_string_vector{F("10")}, F("energyTotal"), F("Energy total"), DeviceValueUOM::KWH);
register_device_value(empty, &energyToday_, DeviceValueType::ULONG, {}, F("energyToday"), F("Energy today"), DeviceValueUOM::WH);
} }
// print to web // publish HA config
void Solar::device_info_web(JsonArray & root) { bool Solar::publish_ha_config() {
// fetch the values into a JSON document StaticJsonDocument<EMSESP_JSON_SIZE_HA_CONFIG> doc;
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> doc; doc["name"] = FJSON("Solar Status");
JsonObject json = doc.to<JsonObject>();
if (!export_values(json)) {
return; // empty
}
create_value_json(root, F("collectorTemp"), nullptr, F_(collectorTemp), F_(degrees), json);
create_value_json(root, F("tankBottomTemp"), nullptr, F_(tankBottomTemp), F_(degrees), json);
create_value_json(root, F("tankBottomTemp2"), nullptr, F_(tankBottomTemp2), F_(degrees), json);
create_value_json(root, F("tank1MaxTempCurrent"), nullptr, F_(tank1MaxTempCurrent), F_(degrees), json);
create_value_json(root, F("heatExchangerTemp"), nullptr, F_(heatExchangerTemp), F_(degrees), json);
create_value_json(root, F("solarPumpModulation"), nullptr, F_(solarPumpModulation), F_(percent), json);
create_value_json(root, F("cylinderPumpModulation"), nullptr, F_(cylinderPumpModulation), F_(percent), json);
create_value_json(root, F("valveStatus"), nullptr, F_(valveStatus), nullptr, json);
create_value_json(root, F("solarPump"), nullptr, F_(solarPump), nullptr, json);
create_value_json(root, F("tankHeated"), nullptr, F_(tankHeated), nullptr, json);
create_value_json(root, F("collectorShutdown"), nullptr, F_(collectorShutdown), nullptr, json);
create_value_json(root, F("energyLastHour"), nullptr, F_(energyLastHour), F_(wh), json);
create_value_json(root, F("energyToday"), nullptr, F_(energyToday), F_(wh), json);
create_value_json(root, F("energyTotal"), nullptr, F_(energyTotal), F_(kwh), json);
create_value_json(root, F("pumpWorkMin"), nullptr, F_(pumpWorkMin), F_(min), json);
create_value_json(root, F("pumpWorkMintxt"), nullptr, F_(pumpWorkMintxt), F_(min), json);
}
// publish values via MQTT
void Solar::publish_values(JsonObject & json, bool force) {
// handle HA first
if (Mqtt::mqtt_format() == Mqtt::Format::HA) {
if ((!mqtt_ha_config_ || force)) {
register_mqtt_ha_config();
return;
}
}
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> doc;
JsonObject json_payload = doc.to<JsonObject>();
if (export_values(json_payload)) {
if (device_id() == 0x2A) {
Mqtt::publish(F("ww_data"), doc.as<JsonObject>());
} else {
Mqtt::publish(F("solar_data"), doc.as<JsonObject>());
}
}
}
// publish config topic for HA MQTT Discovery
void Solar::register_mqtt_ha_config() {
if (!Mqtt::connected()) {
return;
}
// Create the Master device
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_HA_CONFIG> doc;
doc["name"] = F_(EMSESP);
doc["uniq_id"] = F_(solar); doc["uniq_id"] = F_(solar);
doc["ic"] = F_(iconthermostat);
char stat_t[50]; char stat_t[50];
snprintf_P(stat_t, sizeof(stat_t), PSTR("%s/solar_data"), System::hostname().c_str()); snprintf_P(stat_t, sizeof(stat_t), PSTR("%s/solar_data"), System::hostname().c_str());
@@ -133,113 +128,21 @@ void Solar::register_mqtt_ha_config() {
dev["mdl"] = name(); dev["mdl"] = name();
JsonArray ids = dev.createNestedArray("ids"); JsonArray ids = dev.createNestedArray("ids");
ids.add("ems-esp-solar"); ids.add("ems-esp-solar");
Mqtt::publish_ha(F("homeassistant/sensor/ems-esp/solar/config"), doc.as<JsonObject>()); // publish the config payload with retain flag
Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(collectorTemp), device_type(), "collectorTemp", F_(degrees), nullptr); char topic[100];
Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(tankBottomTemp), device_type(), "tankBottomTemp", F_(degrees), nullptr); snprintf_P(topic, sizeof(topic), PSTR("homeassistant/sensor/%s/solar/config"), System::hostname().c_str());
Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(tankBottomTemp2), device_type(), "tankBottomTemp2", F_(degrees), nullptr); Mqtt::publish_ha(topic, doc.as<JsonObject>()); // publish the config payload with retain flag
Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(tank1MaxTempCurrent), device_type(), "tank1MaxTempCurrent", F_(degrees), nullptr);
Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(heatExchangerTemp), device_type(), "heatExchangerTemp", F_(degrees), nullptr);
Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(solarPumpModulation), device_type(), "solarPumpModulation", F_(percent), nullptr);
Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(cylinderPumpModulation), device_type(), "cylinderPumpModulation", F_(percent), nullptr);
Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(pumpWorkMin), device_type(), "pumpWorkMin", F_(min), nullptr);
Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(energyLastHour), device_type(), "energyLastHour", F_(wh), nullptr);
Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(energyToday), device_type(), "energyToday", F_(wh), nullptr);
Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(energyTotal), device_type(), "energyTotal", F_(kwh), nullptr);
Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(solarPump), device_type(), "solarPump", nullptr, nullptr);
Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(valveStatus), device_type(), "valveStatus", nullptr, nullptr);
Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(tankHeated), device_type(), "tankHeated", nullptr, nullptr);
Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(collectorShutdown), device_type(), "collectorShutdown", nullptr, nullptr);
mqtt_ha_config_ = true; // done
}
// creates JSON doc from values
// returns false if empty
bool Solar::export_values(JsonObject & json) {
char s[10]; // for formatting strings
if (Helpers::hasValue(collectorTemp_)) {
json["collectorTemp"] = (float)collectorTemp_ / 10;
}
if (Helpers::hasValue(tankBottomTemp_)) {
json["tankBottomTemp"] = (float)tankBottomTemp_ / 10;
}
if (Helpers::hasValue(tankBottomTemp2_)) {
json["tankBottomTemp2"] = (float)tankBottomTemp2_ / 10;
}
if (Helpers::hasValue(tank1MaxTempCurrent_)) {
json["tank1MaxTempCurrent"] = tank1MaxTempCurrent_;
}
if (Helpers::hasValue(heatExchangerTemp_)) {
json["heatExchangerTemp"] = (float)heatExchangerTemp_ / 10;
}
if (Helpers::hasValue(solarPumpModulation_)) {
json["solarPumpModulation"] = solarPumpModulation_;
}
if (Helpers::hasValue(cylinderPumpModulation_)) {
json["cylinderPumpModulation"] = cylinderPumpModulation_;
}
if (Helpers::hasValue(solarPump_, EMS_VALUE_BOOL)) {
json["solarPump"] = Helpers::render_value(s, solarPump_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(valveStatus_, EMS_VALUE_BOOL)) {
json["valveStatus"] = Helpers::render_value(s, valveStatus_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(pumpWorkMin_)) {
json["pumpWorkMin"] = pumpWorkMin_;
char slong[40];
json["pumpWorkMintxt"] = Helpers::render_value(slong, pumpWorkMin_, EMS_VALUE_TIME);
}
if (Helpers::hasValue(tankHeated_, EMS_VALUE_BOOL)) {
json["tankHeated"] = Helpers::render_value(s, tankHeated_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(collectorShutdown_, EMS_VALUE_BOOL)) {
json["collectorShutdown"] = Helpers::render_value(s, collectorShutdown_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(energyLastHour_)) {
json["energyLastHour"] = (float)energyLastHour_ / 10;
}
if (Helpers::hasValue(energyToday_)) {
json["energyToday"] = energyToday_;
}
if (Helpers::hasValue(energyTotal_)) {
json["energyTotal"] = (float)energyTotal_ / 10;
}
return json.size();
}
// check to see if values have been updated
bool Solar::updated_values() {
if (changed_) {
changed_ = false;
return true; return true;
}
return false;
} }
// SM10Monitor - type 0x97 // SM10Monitor - type 0x97
void Solar::process_SM10Monitor(std::shared_ptr<const Telegram> telegram) { void Solar::process_SM10Monitor(std::shared_ptr<const Telegram> telegram) {
changed_ |= telegram->read_value(collectorTemp_, 2); // collector temp from SM10, is *10 has_update(telegram->read_value(collectorTemp_, 2)); // collector temp from SM10, is *10
changed_ |= telegram->read_value(tankBottomTemp_, 5); // bottom temp from SM10, is *10 has_update(telegram->read_value(tankBottomTemp_, 5)); // bottom temp from SM10, is *10
changed_ |= telegram->read_value(solarPumpModulation_, 4); // modulation solar pump has_update(telegram->read_value(solarPumpModulation_, 4)); // modulation solar pump
changed_ |= telegram->read_bitvalue(solarPump_, 7, 1); has_update(telegram->read_bitvalue(solarPump_, 7, 1));
changed_ |= telegram->read_value(pumpWorkMin_, 8, 3); has_update(telegram->read_value(pumpWorkMin_, 8, 3));
} }
/* /*
@@ -247,11 +150,11 @@ void Solar::process_SM10Monitor(std::shared_ptr<const Telegram> telegram) {
* e.g. B0 0B FF 00 02 58 FF 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 FF 00 FF 01 00 00 * e.g. B0 0B FF 00 02 58 FF 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 FF 00 FF 01 00 00
*/ */
void Solar::process_SM100SystemConfig(std::shared_ptr<const Telegram> telegram) { void Solar::process_SM100SystemConfig(std::shared_ptr<const Telegram> telegram) {
changed_ |= telegram->read_value(heatTransferSystem_, 5, 1); has_update(telegram->read_value(heatTransferSystem_, 5, 1));
changed_ |= telegram->read_value(externalTank_, 9, 1); has_update(telegram->read_value(externalTank_, 9, 1));
changed_ |= telegram->read_value(thermalDisinfect_, 10, 1); has_update(telegram->read_value(thermalDisinfect_, 10, 1));
changed_ |= telegram->read_value(heatMetering_, 14, 1); has_update(telegram->read_value(heatMetering_, 14, 1));
changed_ |= telegram->read_value(solarIsEnabled_, 19, 1); has_update(telegram->read_value(solarIsEnabled_, 19, 1));
} }
/* /*
@@ -259,16 +162,16 @@ void Solar::process_SM100SystemConfig(std::shared_ptr<const Telegram> telegram)
* e.g. B0 0B FF 00 02 5A 64 05 00 58 14 01 01 32 64 00 00 00 5A 0C * e.g. B0 0B FF 00 02 5A 64 05 00 58 14 01 01 32 64 00 00 00 5A 0C
*/ */
void Solar::process_SM100SolarCircuitConfig(std::shared_ptr<const Telegram> telegram) { void Solar::process_SM100SolarCircuitConfig(std::shared_ptr<const Telegram> telegram) {
changed_ |= telegram->read_value(collectorTempMax_, 0, 1); has_update(telegram->read_value(collectorTempMax_, 0, 1));
changed_ |= telegram->read_value(tank1MaxTempCurrent_, 3, 1); has_update(telegram->read_value(tank1MaxTempCurrent_, 3, 1));
changed_ |= telegram->read_value(collectorTempMin_, 4, 1); has_update(telegram->read_value(collectorTempMin_, 4, 1));
changed_ |= telegram->read_value(solarPumpMode_, 5, 1); has_update(telegram->read_value(solarPumpMode_, 5, 1));
changed_ |= telegram->read_value(solarPumpMinRPM_, 6, 1); has_update(telegram->read_value(solarPumpMinRPM_, 6, 1));
changed_ |= telegram->read_value(solarPumpTurnoffDiff_, 7, 1); has_update(telegram->read_value(solarPumpTurnoffDiff_, 7, 1));
changed_ |= telegram->read_value(solarPumpTurnonDiff_, 8, 1); has_update(telegram->read_value(solarPumpTurnonDiff_, 8, 1));
changed_ |= telegram->read_value(solarPumpKick_, 9, 1); has_update(telegram->read_value(solarPumpKick_, 9, 1));
changed_ |= telegram->read_value(plainWaterMode_, 10, 1); has_update(telegram->read_value(plainWaterMode_, 10, 1));
changed_ |= telegram->read_value(doubleMatchFlow_, 11, 1); has_update(telegram->read_value(doubleMatchFlow_, 11, 1));
} }
/* process_SM100ParamCfg - type 0xF9 EMS 1.0 /* process_SM100ParamCfg - type 0xF9 EMS 1.0
@@ -290,14 +193,14 @@ void Solar::process_SM100ParamCfg(std::shared_ptr<const Telegram> telegram) {
uint16_t t_id; uint16_t t_id;
uint8_t of; uint8_t of;
int32_t min, def, max, cur; int32_t min, def, max, cur;
telegram->read_value(t_id, 1); has_update(telegram->read_value(t_id, 1));
telegram->read_value(of, 3); has_update(telegram->read_value(of, 3));
telegram->read_value(min, 5); has_update(telegram->read_value(min, 5));
telegram->read_value(def, 9); has_update(telegram->read_value(def, 9));
telegram->read_value(max, 13); has_update(telegram->read_value(max, 13));
telegram->read_value(cur, 17); has_update(telegram->read_value(cur, 17));
// LOG_DEBUG(F("SM100ParamCfg param=0x%04X, offset=%d, min=%d, default=%d, max=%d, current=%d"), t_id, of, min, def, max, cur); // LOG_DEBUG(F("SM100ParamCfg param=0x%04X, offset=%d, min=%d, default=%d, max=%d, current=%d"), t_id, of, min, def, max, cur));
} }
/* /*
@@ -312,10 +215,10 @@ void Solar::process_SM100ParamCfg(std::shared_ptr<const Telegram> telegram) {
* bytes 20+21 = TS6 Temperature sensor external heat exchanger * bytes 20+21 = TS6 Temperature sensor external heat exchanger
*/ */
void Solar::process_SM100Monitor(std::shared_ptr<const Telegram> telegram) { void Solar::process_SM100Monitor(std::shared_ptr<const Telegram> telegram) {
changed_ |= telegram->read_value(collectorTemp_, 0); // is *10 - TS1: Temperature sensor for collector array 1 has_update(telegram->read_value(collectorTemp_, 0)); // is *10 - TS1: Temperature sensor for collector array 1
changed_ |= telegram->read_value(tankBottomTemp_, 2); // is *10 - TS2: Temperature sensor 1 cylinder, bottom has_update(telegram->read_value(tankBottomTemp_, 2)); // is *10 - TS2: Temperature sensor 1 cylinder, bottom
changed_ |= telegram->read_value(tankBottomTemp2_, 16); // is *10 - TS5: Temperature sensor 2 cylinder, bottom, or swimming pool has_update(telegram->read_value(tankBottomTemp2_, 16)); // is *10 - TS5: Temperature sensor 2 cylinder, bottom, or swimming pool
changed_ |= telegram->read_value(heatExchangerTemp_, 20); // is *10 - TS6: Heat exchanger temperature sensor has_update(telegram->read_value(heatExchangerTemp_, 20)); // is *10 - TS6: Heat exchanger temperature sensor
} }
#pragma GCC diagnostic push #pragma GCC diagnostic push
@@ -330,17 +233,17 @@ void Solar::process_SM100Monitor2(std::shared_ptr<const Telegram> telegram) {
// SM100wwTemperatur - 0x07D6 // SM100wwTemperatur - 0x07D6
// Solar Module(0x2A) -> (0x00), (0x7D6), data: 01 C1 00 00 02 5B 01 AF 01 AD 80 00 01 90 // Solar Module(0x2A) -> (0x00), (0x7D6), data: 01 C1 00 00 02 5B 01 AF 01 AD 80 00 01 90
void Solar::process_SM100wwTemperature(std::shared_ptr<const Telegram> telegram) { void Solar::process_SM100wwTemperature(std::shared_ptr<const Telegram> telegram) {
// changed_ |= telegram->read_value(wwTemp_1_, 0); // has_update(telegram->read_value(wwTemp_1_, 0));
// changed_ |= telegram->read_value(wwTemp_3_, 4); // has_update(telegram->read_value(wwTemp_3_, 4));
// changed_ |= telegram->read_value(wwTemp_4_, 6); // has_update(telegram->read_value(wwTemp_4_, 6));
// changed_ |= telegram->read_value(wwTemp_5_, 8); // has_update(telegram->read_value(wwTemp_5_, 8));
// changed_ |= telegram->read_value(wwTemp_7_, 12); // has_update(telegram->read_value(wwTemp_7_, 12));
} }
// SM100wwStatus - 0x07AA // SM100wwStatus - 0x07AA
// Solar Module(0x2A) -> (0x00), (0x7AA), data: 64 00 04 00 03 00 28 01 0F // Solar Module(0x2A) -> (0x00), (0x7AA), data: 64 00 04 00 03 00 28 01 0F
void Solar::process_SM100wwStatus(std::shared_ptr<const Telegram> telegram) { void Solar::process_SM100wwStatus(std::shared_ptr<const Telegram> telegram) {
// changed_ |= telegram->read_value(wwPump_, 0); // has_update(telegram->read_value(wwPump_, 0));
} }
// SM100wwCommand - 0x07AB // SM100wwCommand - 0x07AB
@@ -349,15 +252,14 @@ void Solar::process_SM100wwCommand(std::shared_ptr<const Telegram> telegram) {
// not implemented yet // not implemented yet
} }
#pragma GCC diagnostic pop #pragma GCC diagnostic pop
// SM100Config - 0x0366 // SM100Config - 0x0366
// e.g. B0 00 FF 00 02 66 01 62 00 13 40 14 // e.g. B0 00 FF 00 02 66 01 62 00 13 40 14
void Solar::process_SM100Config(std::shared_ptr<const Telegram> telegram) { void Solar::process_SM100Config(std::shared_ptr<const Telegram> telegram) {
changed_ |= telegram->read_value(availabilityFlag_, 0); has_update(telegram->read_value(availabilityFlag_, 0));
changed_ |= telegram->read_value(configFlag_, 1); has_update(telegram->read_value(configFlag_, 1));
changed_ |= telegram->read_value(userFlag_, 2); has_update(telegram->read_value(userFlag_, 2));
} }
/* /*
@@ -369,8 +271,8 @@ void Solar::process_SM100Config(std::shared_ptr<const Telegram> telegram) {
void Solar::process_SM100Status(std::shared_ptr<const Telegram> telegram) { void Solar::process_SM100Status(std::shared_ptr<const Telegram> telegram) {
uint8_t solarpumpmod = solarPumpModulation_; uint8_t solarpumpmod = solarPumpModulation_;
uint8_t cylinderpumpmod = cylinderPumpModulation_; uint8_t cylinderpumpmod = cylinderPumpModulation_;
changed_ |= telegram->read_value(cylinderPumpModulation_, 8); has_update(telegram->read_value(cylinderPumpModulation_, 8));
changed_ |= telegram->read_value(solarPumpModulation_, 9); has_update(telegram->read_value(solarPumpModulation_, 9));
if (solarpumpmod == 0 && solarPumpModulation_ == 100) { // mask out boosts if (solarpumpmod == 0 && solarPumpModulation_ == 100) { // mask out boosts
solarPumpModulation_ = 15; // set to minimum solarPumpModulation_ = 15; // set to minimum
@@ -379,8 +281,8 @@ void Solar::process_SM100Status(std::shared_ptr<const Telegram> telegram) {
if (cylinderpumpmod == 0 && cylinderPumpModulation_ == 100) { // mask out boosts if (cylinderpumpmod == 0 && cylinderPumpModulation_ == 100) { // mask out boosts
cylinderPumpModulation_ = 15; // set to minimum cylinderPumpModulation_ = 15; // set to minimum
} }
changed_ |= telegram->read_bitvalue(tankHeated_, 3, 1); // issue #422 has_update(telegram->read_bitvalue(tankHeated_, 3, 1)); // issue #422
changed_ |= telegram->read_bitvalue(collectorShutdown_, 3, 0); // collector shutdown has_update(telegram->read_bitvalue(collectorShutdown_, 3, 0)); // collector shutdown
} }
/* /*
@@ -390,8 +292,8 @@ void Solar::process_SM100Status(std::shared_ptr<const Telegram> telegram) {
* byte 10 = PS1 Solar circuit pump for collector array 1: test=b0001(1), on=b0100(4) and off=b0011(3) * byte 10 = PS1 Solar circuit pump for collector array 1: test=b0001(1), on=b0100(4) and off=b0011(3)
*/ */
void Solar::process_SM100Status2(std::shared_ptr<const Telegram> telegram) { void Solar::process_SM100Status2(std::shared_ptr<const Telegram> telegram) {
changed_ |= telegram->read_bitvalue(valveStatus_, 4, 2); // on if bit 2 set has_update(telegram->read_bitvalue(valveStatus_, 4, 2)); // on if bit 2 set
changed_ |= telegram->read_bitvalue(solarPump_, 10, 2); // on if bit 2 set has_update(telegram->read_bitvalue(solarPump_, 10, 2)); // on if bit 2 set
} }
/* /*
@@ -399,9 +301,9 @@ void Solar::process_SM100Status2(std::shared_ptr<const Telegram> telegram) {
* e.g. B0 0B FF 00 02 80 50 64 00 00 29 01 00 00 01 * e.g. B0 0B FF 00 02 80 50 64 00 00 29 01 00 00 01
*/ */
void Solar::process_SM100CollectorConfig(std::shared_ptr<const Telegram> telegram) { void Solar::process_SM100CollectorConfig(std::shared_ptr<const Telegram> telegram) {
changed_ |= telegram->read_value(climateZone_, 0, 1); has_update(telegram->read_value(climateZone_, 0, 1));
changed_ |= telegram->read_value(collector1Area_, 3, 2); has_update(telegram->read_value(collector1Area_, 3, 2));
changed_ |= telegram->read_value(collector1Type_, 5, 1); has_update(telegram->read_value(collector1Type_, 5, 1));
} }
/* /*
@@ -409,16 +311,16 @@ void Solar::process_SM100CollectorConfig(std::shared_ptr<const Telegram> telegra
* e.g. 30 00 FF 00 02 8E 00 00 00 00 00 00 06 C5 00 00 76 35 * e.g. 30 00 FF 00 02 8E 00 00 00 00 00 00 06 C5 00 00 76 35
*/ */
void Solar::process_SM100Energy(std::shared_ptr<const Telegram> telegram) { void Solar::process_SM100Energy(std::shared_ptr<const Telegram> telegram) {
changed_ |= telegram->read_value(energyLastHour_, 0); // last hour / 10 in Wh has_update(telegram->read_value(energyLastHour_, 0)); // last hour / 10 in Wh
changed_ |= telegram->read_value(energyToday_, 4); // todays in Wh has_update(telegram->read_value(energyToday_, 4)); // todays in Wh
changed_ |= telegram->read_value(energyTotal_, 8); // total / 10 in kWh has_update(telegram->read_value(energyTotal_, 8)); // total / 10 in kWh
} }
/* /*
* SM100Time - type 0x0391 EMS+ for pump working time * SM100Time - type 0x0391 EMS+ for pump working time
*/ */
void Solar::process_SM100Time(std::shared_ptr<const Telegram> telegram) { void Solar::process_SM100Time(std::shared_ptr<const Telegram> telegram) {
changed_ |= telegram->read_value(pumpWorkMin_, 1, 3); has_update(telegram->read_value(pumpWorkMin_, 1, 3));
} }
/* /*
@@ -426,26 +328,26 @@ void Solar::process_SM100Time(std::shared_ptr<const Telegram> telegram) {
* e.g. B0 00 FF 00 00 03 32 00 00 00 00 13 00 D6 00 00 00 FB D0 F0 * e.g. B0 00 FF 00 00 03 32 00 00 00 00 13 00 D6 00 00 00 FB D0 F0
*/ */
void Solar::process_ISM1StatusMessage(std::shared_ptr<const Telegram> telegram) { void Solar::process_ISM1StatusMessage(std::shared_ptr<const Telegram> telegram) {
changed_ |= telegram->read_value(collectorTemp_, 4); // Collector Temperature has_update(telegram->read_value(collectorTemp_, 4)); // Collector Temperature
changed_ |= telegram->read_value(tankBottomTemp_, 6); // Temperature Bottom of Solar Boiler has_update(telegram->read_value(tankBottomTemp_, 6)); // Temperature Bottom of Solar Boiler
uint16_t Wh = 0xFFFF; uint16_t Wh = 0xFFFF;
changed_ |= telegram->read_value(Wh, 2); // Solar Energy produced in last hour only ushort, is not * 10 has_update(telegram->read_value(Wh, 2)); // Solar Energy produced in last hour only ushort, is not * 10
if (Wh != 0xFFFF) { if (Wh != 0xFFFF) {
energyLastHour_ = Wh * 10; // set to *10 energyLastHour_ = Wh * 10; // set to *10
} }
changed_ |= telegram->read_bitvalue(solarPump_, 8, 0); // PS1 Solar pump on (1) or off (0) has_update(telegram->read_bitvalue(solarPump_, 8, 0)); // PS1 Solar pump on (1) or off (0)
changed_ |= telegram->read_value(pumpWorkMin_, 10, 3); // force to 3 bytes has_update(telegram->read_value(pumpWorkMin_, 10, 3)); // force to 3 bytes
changed_ |= telegram->read_bitvalue(collectorShutdown_, 9, 0); // collector shutdown on/off has_update(telegram->read_bitvalue(collectorShutdown_, 9, 0)); // collector shutdown on/off
changed_ |= telegram->read_bitvalue(tankHeated_, 9, 2); // tank full has_update(telegram->read_bitvalue(tankHeated_, 9, 2)); // tank full
} }
/* /*
* Junkers ISM1 Solar Module - type 0x0101 EMS+ for setting values * Junkers ISM1 Solar Module - type 0x0101 EMS+ for setting values
*/ */
void Solar::process_ISM1Set(std::shared_ptr<const Telegram> telegram) { void Solar::process_ISM1Set(std::shared_ptr<const Telegram> telegram) {
changed_ |= telegram->read_value(setpoint_maxBottomTemp_, 6); has_update(telegram->read_value(setpoint_maxBottomTemp_, 6));
} }
// set temperature for tank // set temperature for tank

View File

@@ -36,61 +36,56 @@ class Solar : public EMSdevice {
public: public:
Solar(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand); Solar(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand);
virtual void publish_values(JsonObject & json, bool force); virtual bool publish_ha_config();
virtual bool export_values(JsonObject & json);
virtual void device_info_web(JsonArray & root);
virtual bool updated_values();
private: private:
static uuid::log::Logger logger_; static uuid::log::Logger logger_;
void register_mqtt_ha_config();
int16_t collectorTemp_ = EMS_VALUE_SHORT_NOTSET; // TS1: Temperature sensor for collector array 1 int16_t collectorTemp_; // TS1: Temperature sensor for collector array 1
int16_t tankBottomTemp_ = EMS_VALUE_SHORT_NOTSET; // TS2: Temperature sensor 1 cylinder, bottom (solar thermal system) int16_t tankBottomTemp_; // TS2: Temperature sensor 1 cylinder, bottom (solar thermal system)
int16_t tankBottomTemp2_ = EMS_VALUE_SHORT_NOTSET; // TS5: Temperature sensor 2 cylinder, bottom, or swimming pool (solar thermal system) int16_t tankBottomTemp2_; // TS5: Temperature sensor 2 cylinder, bottom, or swimming pool (solar thermal system)
int16_t heatExchangerTemp_ = EMS_VALUE_SHORT_NOTSET; // TS6: Heat exchanger temperature sensor int16_t heatExchangerTemp_; // TS6: Heat exchanger temperature sensor
uint8_t solarPumpModulation_ = EMS_VALUE_UINT_NOTSET; // PS1: modulation solar pump uint8_t solarPumpModulation_; // PS1: modulation solar pump
uint8_t cylinderPumpModulation_ = EMS_VALUE_UINT_NOTSET; // PS5: modulation cylinder pump uint8_t cylinderPumpModulation_; // PS5: modulation cylinder pump
uint8_t solarPump_ = EMS_VALUE_BOOL_NOTSET; // PS1: solar pump active uint8_t solarPump_; // PS1: solar pump active
uint8_t valveStatus_ = EMS_VALUE_BOOL_NOTSET; // VS2: status 3-way valve for cylinder 2 (solar thermal system) with valve uint8_t valveStatus_; // VS2: status 3-way valve for cylinder 2 (solar thermal system) with valve
int16_t setpoint_maxBottomTemp_ = EMS_VALUE_SHORT_NOTSET; // setpoint for maximum collector temp int16_t setpoint_maxBottomTemp_; // setpoint for maximum collector temp
uint32_t energyLastHour_ = EMS_VALUE_ULONG_NOTSET; uint32_t energyLastHour_;
uint32_t energyToday_ = EMS_VALUE_ULONG_NOTSET; uint32_t energyToday_;
uint32_t energyTotal_ = EMS_VALUE_ULONG_NOTSET; uint32_t energyTotal_;
uint32_t pumpWorkMin_ = EMS_VALUE_ULONG_NOTSET; // Total solar pump operating time uint32_t pumpWorkMin_; // Total solar pump operating time
uint8_t tankHeated_ = EMS_VALUE_BOOL_NOTSET; uint8_t tankHeated_;
uint8_t collectorShutdown_ = EMS_VALUE_BOOL_NOTSET; // Collector shutdown on/off uint8_t collectorShutdown_; // Collector shutdown on/off
uint8_t availabilityFlag_ = EMS_VALUE_BOOL_NOTSET; uint8_t availabilityFlag_;
uint8_t configFlag_ = EMS_VALUE_BOOL_NOTSET; uint8_t configFlag_;
uint8_t userFlag_ = EMS_VALUE_BOOL_NOTSET; uint8_t userFlag_;
// telegram 0x0358 // telegram 0x0358
uint8_t heatTransferSystem_ = EMS_VALUE_UINT_NOTSET; // Umladesystem, 00=no uint8_t heatTransferSystem_; // Umladesystem, 00=no
uint8_t externalTank_ = EMS_VALUE_UINT_NOTSET; // Heat exchanger, 00=no uint8_t externalTank_; // Heat exchanger, 00=no
uint8_t thermalDisinfect_ = EMS_VALUE_UINT_NOTSET; // Daily heatup for disinfection, 00=no uint8_t thermalDisinfect_; // Daily heatup for disinfection, 00=no
uint8_t heatMetering_ = EMS_VALUE_UINT_NOTSET; // Wärmemengenzählung, 00=no uint8_t heatMetering_; // Wärmemengenzählung, 00=no
uint8_t solarIsEnabled_ = EMS_VALUE_UINT_NOTSET; // System enable, 00=no uint8_t solarIsEnabled_; // System enable, 00=no
// telegram 0x035A // telegram 0x035A
uint8_t collectorTempMax_ = EMS_VALUE_UINT_NOTSET; // maximum allowable temperature for collector uint8_t collectorTempMax_; // maximum allowable temperature for collector
uint8_t tank1MaxTempCurrent_ = EMS_VALUE_UINT_NOTSET; // Current value for max tank temp uint8_t tank1MaxTempCurrent_; // Current value for max tank temp
uint8_t collectorTempMin_ = EMS_VALUE_UINT_NOTSET; // minimum allowable temperature for collector uint8_t collectorTempMin_; // minimum allowable temperature for collector
uint8_t solarPumpMode_ = EMS_VALUE_UINT_NOTSET; // 00=off, 01=PWM, 02=10V uint8_t solarPumpMode_; // 00=off, 01=PWM, 02=10V
uint8_t solarPumpMinRPM_ = EMS_VALUE_UINT_NOTSET; // minimum RPM setting, *5 % uint8_t solarPumpMinRPM_; // minimum RPM setting, *5 %
uint8_t solarPumpTurnoffDiff_ = EMS_VALUE_UINT_NOTSET; // solar pump turnoff collector/tank diff uint8_t solarPumpTurnoffDiff_; // solar pump turnoff collector/tank diff
uint8_t solarPumpTurnonDiff_ = EMS_VALUE_UINT_NOTSET; // solar pump turnon collector/tank diff uint8_t solarPumpTurnonDiff_; // solar pump turnon collector/tank diff
uint8_t solarPumpKick_ = EMS_VALUE_UINT_NOTSET; // pump kick for vacuum collector, 00=off uint8_t solarPumpKick_; // pump kick for vacuum collector, 00=off
uint8_t plainWaterMode_ = EMS_VALUE_UINT_NOTSET; // system does not use antifreeze, 00=off uint8_t plainWaterMode_; // system does not use antifreeze, 00=off
uint8_t doubleMatchFlow_ = EMS_VALUE_UINT_NOTSET; // double Match Flow, 00=off uint8_t doubleMatchFlow_; // double Match Flow, 00=off
// telegram 0x380 // telegram 0x380
uint8_t climateZone_ = EMS_VALUE_UINT_NOTSET; // climate zone identifier uint8_t climateZone_; // climate zone identifier
uint16_t collector1Area_ = EMS_VALUE_USHORT_NOTSET; // Area of collector field 1 uint16_t collector1Area_; // Area of collector field 1
uint8_t collector1Type_ = EMS_VALUE_UINT_NOTSET; // Type of collector field 1, 01=flat, 02=vacuum uint8_t collector1Type_; // Type of collector field 1, 01=flat, 02=vacuum
bool changed_ = false; char type_[20]; // Solar of WWC
bool mqtt_ha_config_ = false; // for HA MQTT Discovery
void process_SM10Monitor(std::shared_ptr<const Telegram> telegram); void process_SM10Monitor(std::shared_ptr<const Telegram> telegram);
void process_SM100SystemConfig(std::shared_ptr<const Telegram> telegram); void process_SM100SystemConfig(std::shared_ptr<const Telegram> telegram);

View File

@@ -32,92 +32,30 @@ Switch::Switch(uint8_t device_type, uint8_t device_id, uint8_t product_id, const
register_telegram_type(0x9C, F("WM10MonitorMessage"), false, [&](std::shared_ptr<const Telegram> t) { process_WM10MonitorMessage(t); }); register_telegram_type(0x9C, F("WM10MonitorMessage"), false, [&](std::shared_ptr<const Telegram> t) { process_WM10MonitorMessage(t); });
register_telegram_type(0x9B, F("WM10SetMessage"), false, [&](std::shared_ptr<const Telegram> t) { process_WM10SetMessage(t); }); register_telegram_type(0x9B, F("WM10SetMessage"), false, [&](std::shared_ptr<const Telegram> t) { process_WM10SetMessage(t); });
std::string empty("");
register_device_value(empty, &activated_, DeviceValueType::BOOL, {}, F("activated"), F("Activated"), DeviceValueUOM::NONE);
register_device_value(
empty, &flowTemp_, DeviceValueType::USHORT, flash_string_vector{F("10")}, F("flowTemp"), F("Current flow temperature"), DeviceValueUOM::DEGREES);
register_device_value(empty, &status_, DeviceValueType::INT, {}, F("status"), F("Status"), DeviceValueUOM::NONE);
} }
// fetch the values into a JSON document for display in the web // publish HA config
void Switch::device_info_web(JsonArray & root) { bool Switch::publish_ha_config() {
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc; // if we don't have valid values don't add it ever again
JsonObject json = doc.to<JsonObject>();
if (export_values(json)) {
create_value_json(root, F("activated"), nullptr, F_(activated), nullptr, json);
create_value_json(root, F("flowTemp"), nullptr, F_(flowTemp), F_(degrees), json);
create_value_json(root, F("status"), nullptr, F_(status), nullptr, json);
}
}
// publish values via MQTT
void Switch::publish_values(JsonObject & json, bool force) {
if (Mqtt::mqtt_format() == Mqtt::Format::HA) {
if (!mqtt_ha_config_ || force) {
register_mqtt_ha_config();
return;
}
}
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc;
JsonObject json_data = doc.to<JsonObject>();
if (export_values(json_data)) {
Mqtt::publish(F("switch_data"), doc.as<JsonObject>());
}
}
// export values to JSON
bool Switch::export_values(JsonObject & json) {
if (Helpers::hasValue(flowTemp_)) {
char s[7];
json["activated"] = Helpers::render_value(s, activated_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(flowTemp_)) {
json["flowTemp"] = (float)flowTemp_ / 10;
}
if (Helpers::hasValue(flowTemp_)) {
json["status"] = status_;
}
return true;
}
// check to see if values have been updated
bool Switch::updated_values() {
if (changed_) {
changed_ = false;
return true;
}
return false;
}
// publish config topic for HA MQTT Discovery
void Switch::register_mqtt_ha_config() {
if (!Mqtt::connected()) {
return;
}
// if we don't have valid values for this HC don't add it ever again
if (!Helpers::hasValue(flowTemp_)) { if (!Helpers::hasValue(flowTemp_)) {
return; return false;
} }
// Create the Master device StaticJsonDocument<EMSESP_JSON_SIZE_HA_CONFIG> doc;
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_HA_CONFIG> doc; doc["uniq_id"] = F_(switch);
char name[10];
snprintf_P(name, sizeof(name), PSTR("Switch"));
doc["name"] = name;
char uniq_id[10];
snprintf_P(uniq_id, sizeof(uniq_id), PSTR("switch"));
doc["uniq_id"] = uniq_id;
doc["ic"] = FJSON("mdi:home-thermometer-outline");
char stat_t[50]; char stat_t[50];
snprintf_P(stat_t, sizeof(stat_t), PSTR("%s/switch_data"), System::hostname().c_str()); snprintf_P(stat_t, sizeof(stat_t), PSTR("%s/switch_data"), System::hostname().c_str());
doc["stat_t"] = stat_t; doc["stat_t"] = stat_t;
doc["name"] = FJSON("Type");
doc["val_tpl"] = FJSON("{{value_json.type}}"); // HA needs a single value. We take the type which is wwc or hc doc["val_tpl"] = FJSON("{{value_json.type}}"); // HA needs a single value. We take the type which is wwc or hc
JsonObject dev = doc.createNestedObject("dev"); JsonObject dev = doc.createNestedObject("dev");
dev["name"] = FJSON("EMS-ESP Switch"); dev["name"] = FJSON("EMS-ESP Switch");
dev["sw"] = EMSESP_APP_VERSION; dev["sw"] = EMSESP_APP_VERSION;
@@ -126,24 +64,22 @@ void Switch::register_mqtt_ha_config() {
JsonArray ids = dev.createNestedArray("ids"); JsonArray ids = dev.createNestedArray("ids");
ids.add("ems-esp-switch"); ids.add("ems-esp-switch");
Mqtt::publish_ha(F("homeassistant/sensor/ems-esp/switch/config"), doc.as<JsonObject>()); // publish the config payload with retain flag char topic[100];
snprintf_P(topic, sizeof(topic), PSTR("homeassistant/sensor/%s/switch/config"), System::hostname().c_str());
Mqtt::publish_ha(topic, doc.as<JsonObject>()); // publish the config payload with retain flag
Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(activated), device_type(), "activated", nullptr, nullptr); return true;
Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(flowTemp), device_type(), "flowTemp", F_(degrees), F_(icontemperature));
Mqtt::register_mqtt_ha_sensor(nullptr, nullptr, F_(status), device_type(), "status", nullptr, nullptr);
mqtt_ha_config_ = true; // done
} }
// message 0x9B switch on/off // message 0x9B switch on/off
void Switch::process_WM10SetMessage(std::shared_ptr<const Telegram> telegram) { void Switch::process_WM10SetMessage(std::shared_ptr<const Telegram> telegram) {
changed_ |= telegram->read_value(activated_, 0); has_update(telegram->read_value(activated_, 0));
} }
// message 0x9C holds flowtemp and unknown status value // message 0x9C holds flowtemp and unknown status value
void Switch::process_WM10MonitorMessage(std::shared_ptr<const Telegram> telegram) { void Switch::process_WM10MonitorMessage(std::shared_ptr<const Telegram> telegram) {
changed_ |= telegram->read_value(flowTemp_, 0); // is * 10 has_update(telegram->read_value(flowTemp_, 0)); // is * 10
changed_ |= telegram->read_value(status_, 2); has_update(telegram->read_value(status_, 2));
} }
} // namespace emsesp } // namespace emsesp

View File

@@ -36,23 +36,17 @@ class Switch : public EMSdevice {
public: public:
Switch(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand); Switch(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand);
virtual void publish_values(JsonObject & json, bool force); virtual bool publish_ha_config();
virtual bool export_values(JsonObject & json);
virtual void device_info_web(JsonArray & root);
virtual bool updated_values();
private: private:
static uuid::log::Logger logger_; static uuid::log::Logger logger_;
void process_WM10SetMessage(std::shared_ptr<const Telegram> telegram); void process_WM10SetMessage(std::shared_ptr<const Telegram> telegram);
void process_WM10MonitorMessage(std::shared_ptr<const Telegram> telegram); void process_WM10MonitorMessage(std::shared_ptr<const Telegram> telegram);
void register_mqtt_ha_config();
uint16_t flowTemp_ = EMS_VALUE_USHORT_NOTSET; uint16_t flowTemp_;
uint8_t status_ = EMS_VALUE_UINT_NOTSET; uint8_t status_;
uint8_t activated_ = EMS_VALUE_BOOL_NOTSET; uint8_t activated_;
bool changed_ = false;
bool mqtt_ha_config_ = false; // for HA MQTT Discovery
}; };
} // namespace emsesp } // namespace emsesp

File diff suppressed because it is too large Load Diff

View File

@@ -40,44 +40,43 @@ class Thermostat : public EMSdevice {
Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand); Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand);
class HeatingCircuit { class HeatingCircuit {
public: public:
HeatingCircuit(const uint8_t hc_num) HeatingCircuit(const uint8_t hc_num, const uint8_t model)
: hc_num_(hc_num) : hc_num_(hc_num)
, ha_registered_(false) { , model_(model) {
} }
~HeatingCircuit() = default; ~HeatingCircuit() = default;
int16_t setpoint_roomTemp = EMS_VALUE_SHORT_NOTSET; int16_t setpoint_roomTemp;
int16_t curr_roomTemp = EMS_VALUE_SHORT_NOTSET; int16_t curr_roomTemp;
uint8_t mode = EMS_VALUE_UINT_NOTSET; uint8_t mode;
uint8_t mode_type = EMS_VALUE_UINT_NOTSET; uint8_t modetype;
uint8_t summer_mode = EMS_VALUE_UINT_NOTSET; uint8_t summermode;
uint8_t holiday_mode = EMS_VALUE_UINT_NOTSET; uint8_t holidaymode;
uint8_t daytemp = EMS_VALUE_UINT_NOTSET; uint8_t daytemp;
uint8_t nighttemp = EMS_VALUE_UINT_NOTSET; uint8_t nighttemp;
uint8_t holidaytemp = EMS_VALUE_UINT_NOTSET; uint8_t holidaytemp;
uint8_t heatingtype = EMS_VALUE_UINT_NOTSET; // type of heating: 1 radiator, 2 convectors, 3 floors, 4 room supply uint8_t heatingtype; // type of heating: 1 radiator, 2 convectors, 3 floors, 4 room supply
uint8_t targetflowtemp = EMS_VALUE_UINT_NOTSET; uint8_t targetflowtemp;
uint8_t summertemp = EMS_VALUE_UINT_NOTSET; uint8_t summertemp;
int8_t nofrosttemp = EMS_VALUE_INT_NOTSET; // signed -20°C to +10°C int8_t nofrosttemp; // signed -20°C to +10°C
uint8_t designtemp = EMS_VALUE_UINT_NOTSET; // heating curve design temp at MinExtTemp uint8_t designtemp; // heating curve design temp at MinExtTemp
int8_t offsettemp = EMS_VALUE_INT_NOTSET; // heating curve offest temp at roomtemp signed! int8_t offsettemp; // heating curve offest temp at roomtemp signed!
uint8_t manualtemp = EMS_VALUE_UINT_NOTSET; uint8_t manualtemp;
uint8_t summer_setmode = EMS_VALUE_UINT_NOTSET; uint8_t summer_setmode;
uint8_t roominfluence = EMS_VALUE_UINT_NOTSET; uint8_t roominfluence;
uint8_t flowtempoffset = EMS_VALUE_UINT_NOTSET; uint8_t flowtempoffset;
uint8_t minflowtemp = EMS_VALUE_UINT_NOTSET; uint8_t minflowtemp;
uint8_t maxflowtemp = EMS_VALUE_UINT_NOTSET; uint8_t maxflowtemp;
uint8_t reducemode;
uint8_t program;
uint8_t controlmode;
uint8_t hc_num() const { uint8_t hc_num() const {
return hc_num_; return hc_num_;
} }
bool ha_registered() const { uint8_t get_model() const {
return ha_registered_; return model_;
}
void ha_registered(bool b) {
ha_registered_ = b;
} }
// determines if the heating circuit is actually present and has data // determines if the heating circuit is actually present and has data
@@ -85,11 +84,10 @@ class Thermostat : public EMSdevice {
return Helpers::hasValue(setpoint_roomTemp); return Helpers::hasValue(setpoint_roomTemp);
} }
uint8_t get_mode(uint8_t model) const; uint8_t get_mode() const;
uint8_t get_mode_type(uint8_t model) const; uint8_t get_mode_type() const;
enum Mode : uint8_t { enum Mode : uint8_t {
UNKNOWN,
OFF, OFF,
MANUAL, MANUAL,
AUTO, AUTO,
@@ -106,7 +104,8 @@ class Thermostat : public EMSdevice {
FLOWOFFSET, FLOWOFFSET,
MINFLOW, MINFLOW,
MAXFLOW, MAXFLOW,
ROOMINFLUENCE ROOMINFLUENCE,
UNKNOWN
}; };
// for sorting based on hc number // for sorting based on hc number
@@ -116,30 +115,20 @@ class Thermostat : public EMSdevice {
private: private:
uint8_t hc_num_; // heating circuit number 1..10 uint8_t hc_num_; // heating circuit number 1..10
bool ha_registered_; // whether it has been registered for HA MQTT Discovery uint8_t model_; // the model type
}; };
static std::string mode_tostring(uint8_t mode); static std::string mode_tostring(uint8_t mode);
virtual void publish_values(JsonObject & json, bool force); virtual bool publish_ha_config();
virtual bool export_values(JsonObject & json);
virtual void device_info_web(JsonArray & root);
virtual bool updated_values();
private: private:
static uuid::log::Logger logger_; static uuid::log::Logger logger_;
void add_commands(); void add_commands();
bool export_values_main(JsonObject & doc);
bool export_values_hc(uint8_t mqtt_format, JsonObject & doc);
bool ha_registered() const { void register_device_values();
return ha_registered_; void register_device_values(uint8_t hc_num);
}
void ha_registered(bool b) {
ha_registered_ = b;
}
// specific thermostat characteristics, stripping the top 4 bits // specific thermostat characteristics, stripping the top 4 bits
inline uint8_t model() const { inline uint8_t model() const {
@@ -153,40 +142,38 @@ class Thermostat : public EMSdevice {
std::vector<uint16_t> summer_typeids; std::vector<uint16_t> summer_typeids;
std::vector<uint16_t> curve_typeids; std::vector<uint16_t> curve_typeids;
std::string datetime_; // date and time stamp char dateTime_[25]; // date and time stamp
std::string errorCode_; // code from 0xA2 as string i.e. "A22(816)" char errorCode_[15]; // code from 0xA2 as string i.e. "A22(816)"
bool changed_ = false;
bool ha_registered_ = false;
// Installation parameters // Installation parameters
uint8_t ibaMainDisplay_ = uint8_t ibaMainDisplay_; // display on Thermostat: 0 int temp, 1 int setpoint, 2 ext temp, 3 burner temp, 4 ww temp, 5 functioning mode, 6 time, 7 data, 9 smoke temp
EMS_VALUE_UINT_NOTSET; // display on Thermostat: 0 int temp, 1 int setpoint, 2 ext temp, 3 burner temp, 4 ww temp, 5 functioning mode, 6 time, 7 data, 9 smoke temp uint8_t ibaLanguage_; // language on Thermostat: 0 german, 1 dutch, 2 french, 3 italian
uint8_t ibaLanguage_ = EMS_VALUE_UINT_NOTSET; // language on Thermostat: 0 german, 1 dutch, 2 french, 3 italian int8_t ibaCalIntTemperature_; // offset int. temperature sensor, by * 0.1 Kelvin (-5.0 to 5.0K)
int8_t ibaCalIntTemperature_ = EMS_VALUE_INT_NOTSET; // offset int. temperature sensor, by * 0.1 Kelvin (-5.0 to 5.0K) int8_t ibaMinExtTemperature_; // min ext temp for heating curve, in deg., 0xF6=-10, 0x0 = 0, 0xFF=-1
int8_t ibaMinExtTemperature_ = EMS_VALUE_INT_NOTSET; // min ext temp for heating curve, in deg., 0xF6=-10, 0x0 = 0, 0xFF=-1 uint8_t ibaBuildingType_; // building type: 0 = light, 1 = medium, 2 = heavy
uint8_t ibaBuildingType_ = EMS_VALUE_UINT_NOTSET; // building type: 0 = light, 1 = medium, 2 = heavy uint8_t ibaClockOffset_; // offset (in sec) to clock, 0xff = -1 s, 0x02 = 2 s
uint8_t ibaClockOffset_ = EMS_VALUE_UINT_NOTSET; // offset (in sec) to clock, 0xff = -1 s, 0x02 = 2 s
uint16_t errorNumber_ = EMS_VALUE_USHORT_NOTSET; uint16_t errorNumber_;
char lastCode_[30] = {'\0'}; char lastCode_[30];
int8_t dampedoutdoortemp_ = EMS_VALUE_INT_NOTSET; int8_t dampedoutdoortemp_;
uint16_t tempsensor1_ = EMS_VALUE_USHORT_NOTSET; uint16_t tempsensor1_;
uint16_t tempsensor2_ = EMS_VALUE_USHORT_NOTSET; uint16_t tempsensor2_;
int16_t dampedoutdoortemp2_ = EMS_VALUE_SHORT_NOTSET; int16_t dampedoutdoortemp2_;
uint8_t floordrystatus_ = EMS_VALUE_UINT_NOTSET; uint8_t floordrystatus_;
uint8_t floordrytemp_ = EMS_VALUE_UINT_NOTSET; uint8_t floordrytemp_;
uint8_t wwExtra1_ = EMS_VALUE_UINT_NOTSET; // wwExtra active for wwSystem 1 uint8_t wwExtra1_; // wwExtra active for wwSystem 1
uint8_t wwExtra2_ = EMS_VALUE_UINT_NOTSET; uint8_t wwExtra2_;
uint8_t wwMode_ = EMS_VALUE_UINT_NOTSET; uint8_t wwMode_;
uint8_t wwCircPump_ = EMS_VALUE_UINT_NOTSET; uint8_t wwCircPump_;
uint8_t wwCircMode_ = EMS_VALUE_UINT_NOTSET; uint8_t wwCircMode_;
uint8_t wwTemp_ = EMS_VALUE_UINT_NOTSET; uint8_t wwTemp_;
uint8_t wwTempLow_ = EMS_VALUE_UINT_NOTSET; uint8_t wwTempLow_;
std::vector<std::shared_ptr<HeatingCircuit>> heating_circuits_; // each thermostat can have multiple heating circuits std::vector<std::shared_ptr<HeatingCircuit>> heating_circuits_; // each thermostat can have multiple heating circuits
uint8_t zero_value_ = 0; // for fixing current room temperature to 0 for HA
// Generic Types // Generic Types
static constexpr uint16_t EMS_TYPE_RCTime = 0x06; // time static constexpr uint16_t EMS_TYPE_RCTime = 0x06; // time
static constexpr uint16_t EMS_TYPE_RCOutdoorTemp = 0xA3; // is an automatic thermostat broadcast, outdoor external temp static constexpr uint16_t EMS_TYPE_RCOutdoorTemp = 0xA3; // is an automatic thermostat broadcast, outdoor external temp
@@ -266,9 +253,9 @@ class Thermostat : public EMSdevice {
std::shared_ptr<Thermostat::HeatingCircuit> heating_circuit(std::shared_ptr<const Telegram> telegram); std::shared_ptr<Thermostat::HeatingCircuit> heating_circuit(std::shared_ptr<const Telegram> telegram);
std::shared_ptr<Thermostat::HeatingCircuit> heating_circuit(const uint8_t hc_num); std::shared_ptr<Thermostat::HeatingCircuit> heating_circuit(const uint8_t hc_num);
void register_mqtt_ha_config(); void register_mqtt_ha_config_hc(uint8_t hc_num);
void register_mqtt_ha_config(uint8_t hc_num); void register_device_values_hc(std::shared_ptr<emsesp::Thermostat::HeatingCircuit> hc);
bool ha_config(bool force = false);
bool thermostat_ha_cmd(const char * message, uint8_t hc_num); bool thermostat_ha_cmd(const char * message, uint8_t hc_num);
void process_RCOutdoorTemp(std::shared_ptr<const Telegram> telegram); void process_RCOutdoorTemp(std::shared_ptr<const Telegram> telegram);
@@ -279,6 +266,7 @@ class Thermostat : public EMSdevice {
void process_RC35wwSettings(std::shared_ptr<const Telegram> telegram); void process_RC35wwSettings(std::shared_ptr<const Telegram> telegram);
void process_RC35Monitor(std::shared_ptr<const Telegram> telegram); void process_RC35Monitor(std::shared_ptr<const Telegram> telegram);
void process_RC35Set(std::shared_ptr<const Telegram> telegram); void process_RC35Set(std::shared_ptr<const Telegram> telegram);
void process_RC35Timer(std::shared_ptr<const Telegram> telegram);
void process_RC30Monitor(std::shared_ptr<const Telegram> telegram); void process_RC30Monitor(std::shared_ptr<const Telegram> telegram);
void process_RC30Set(std::shared_ptr<const Telegram> telegram); void process_RC30Set(std::shared_ptr<const Telegram> telegram);
void process_RC20Monitor(std::shared_ptr<const Telegram> telegram); void process_RC20Monitor(std::shared_ptr<const Telegram> telegram);
@@ -335,6 +323,9 @@ class Thermostat : public EMSdevice {
bool set_flowtempoffset(const char * value, const int8_t id); bool set_flowtempoffset(const char * value, const int8_t id);
bool set_minflowtemp(const char * value, const int8_t id); bool set_minflowtemp(const char * value, const int8_t id);
bool set_maxflowtemp(const char * value, const int8_t id); bool set_maxflowtemp(const char * value, const int8_t id);
bool set_reducemode(const char * value, const int8_t id);
bool set_program(const char * value, const int8_t id);
bool set_controlmode(const char * value, const int8_t id);
// set functions - these don't use the id/hc, the parameters are ignored // set functions - these don't use the id/hc, the parameters are ignored
bool set_wwmode(const char * value, const int8_t id); bool set_wwmode(const char * value, const int8_t id);

View File

@@ -21,6 +21,32 @@
namespace emsesp { namespace emsesp {
// mapping of UOM, to match order in DeviceValueUOM enum
static const __FlashStringHelper * DeviceValueUOM_s[(uint8_t)10] __attribute__((__aligned__(sizeof(int)))) PROGMEM = {
F_(degrees),
F_(percent),
F_(lmin),
F_(kwh),
F_(wh),
F_(hours),
F_(minutes),
F_(ua),
F_(bar)
};
const __FlashStringHelper * EMSdevice::uom_to_string(uint8_t uom) {
if (uom == DeviceValueUOM::NONE) {
return nullptr;
}
return DeviceValueUOM_s[uom];
}
const std::vector<EMSdevice::DeviceValue> EMSdevice::devicevalues() const {
return devicevalues_;
}
std::string EMSdevice::brand_to_string() const { std::string EMSdevice::brand_to_string() const {
switch (brand_) { switch (brand_) {
case EMSdevice::Brand::BOSCH: case EMSdevice::Brand::BOSCH:
@@ -276,7 +302,6 @@ char * EMSdevice::show_telegram_handlers(char * result) {
return result; return result;
} }
// list all the mqtt handlers for this device // list all the mqtt handlers for this device
void EMSdevice::show_mqtt_handlers(uuid::console::Shell & shell) { void EMSdevice::show_mqtt_handlers(uuid::console::Shell & shell) {
Mqtt::show_topic_handlers(shell, device_type_); Mqtt::show_topic_handlers(shell, device_type_);
@@ -296,6 +321,317 @@ void EMSdevice::register_telegram_type(const uint16_t telegram_type_id, const __
telegram_functions_.emplace_back(telegram_type_id, telegram_type_name, fetch, f); telegram_functions_.emplace_back(telegram_type_id, telegram_type_name, fetch, f);
} }
// add to device value library
// arguments are:
// tag: to be used to group mqtt together, either as separate topics as a nested object
// value: pointer to the value from the .h file
// type: one of DeviceValueType
// options: options for enum or a divider for int (e.g. F("10"))
// short_name: used in Mqtt as keys
// full name: used in Web and Console
// uom: unit of measure from DeviceValueUOM
// icon (optional): the HA mdi icon to use, from locale_*.h file
void EMSdevice::register_device_value(std::string & tag,
void * value_p,
uint8_t type,
const flash_string_vector & options,
const __FlashStringHelper * short_name,
const __FlashStringHelper * full_name,
uint8_t uom,
const __FlashStringHelper * icon) {
// init the value depending on it's type
if (type == DeviceValueType::TEXT) {
*(char *)(value_p) = {'\0'};
} else if (type == DeviceValueType::INT) {
*(int8_t *)(value_p) = EMS_VALUE_INT_NOTSET;
} else if (type == DeviceValueType::SHORT) {
*(int16_t *)(value_p) = EMS_VALUE_SHORT_NOTSET;
} else if (type == DeviceValueType::USHORT) {
*(uint16_t *)(value_p) = EMS_VALUE_USHORT_NOTSET;
} else if ((type == DeviceValueType::ULONG) || (type == DeviceValueType::TIME)) {
*(uint32_t *)(value_p) = EMS_VALUE_ULONG_NOTSET;
} else {
// enums, uint8_t, bool behave as uint8_t
*(uint8_t *)(value_p) = EMS_VALUE_UINT_NOTSET;
}
// add to our library
devicevalues_.emplace_back(device_type_, tag, value_p, type, options, short_name, full_name, uom, icon);
}
// looks up the uom (suffix) for a given key from the device value table
std::string EMSdevice::get_value_uom(const char * key) {
// the key may have a suffix at the start which is between brackets. remove it.
char new_key[80];
strncpy(new_key, key, sizeof(new_key));
char * p = new_key;
if (key[0] == '(') {
while ((*p++ != ')') && (*p != '\0'))
;
p++;
}
for (const auto & dv : devicevalues_) {
if (dv.full_name != nullptr) {
if (uuid::read_flash_string(dv.full_name) == p) {
// ignore TIME since "minutes" is already included
if ((dv.uom == DeviceValueUOM::NONE) || (dv.uom == DeviceValueUOM::MINUTES)) {
break;
}
return uuid::read_flash_string(EMSdevice::uom_to_string(dv.uom));
}
}
}
return {}; // not found
}
// prepare array of device values, as 3 elements serialized (name, value, uom) in array to send to Web UI
// returns number of elements
bool EMSdevice::generate_values_json_web(JsonObject & json) {
json["name"] = to_string_short();
JsonArray data = json.createNestedArray("data");
uint8_t num_elements = 0;
for (const auto & dv : devicevalues_) {
// ignore if full_name empty
if (dv.full_name != nullptr) {
// handle Booleans (true, false)
if ((dv.type == DeviceValueType::BOOL) && Helpers::hasValue(*(uint8_t *)(dv.value_p), EMS_VALUE_BOOL)) {
// see if we have options for the bool's
if (dv.options.size() == 2) {
data.add(*(uint8_t *)(dv.value_p) ? dv.options[0] : dv.options[1]);
} else {
// see how to render the value depending on the setting
if (Helpers::bool_format() == BOOL_FORMAT_ONOFF) {
// on or off as strings
data.add(*(uint8_t *)(dv.value_p) ? F_(on) : F_(off));
} else if (Helpers::bool_format() == BOOL_FORMAT_TRUEFALSE) {
// true or false values (not strings)
data.add((bool)(*(uint8_t *)(dv.value_p)) ? true : false);
} else {
// 1 or 0
data.add((uint8_t)(*(uint8_t *)(dv.value_p)) ? 1 : 0);
}
}
}
// handle TEXT strings
else if ((dv.type == DeviceValueType::TEXT) && (Helpers::hasValue((char *)(dv.value_p)))) {
data.add((char *)(dv.value_p));
}
// handle ENUMs
else if ((dv.type == DeviceValueType::ENUM) && Helpers::hasValue(*(uint8_t *)(dv.value_p))) {
if (*(uint8_t *)(dv.value_p) < dv.options.size()) {
data.add(dv.options[*(uint8_t *)(dv.value_p)]);
}
}
else {
// handle Integers and Floats
// If a divider is specified, do the division to 2 decimals places and send back as double/float
// otherwise force as an integer whole
// the nested if's is necessary due to the way the ArduinoJson templates are pre-processed by the compiler
uint8_t divider = (dv.options.size() == 1) ? Helpers::atoint(uuid::read_flash_string(dv.options[0]).c_str()) : 0;
// INT
if ((dv.type == DeviceValueType::INT) && Helpers::hasValue(*(int8_t *)(dv.value_p))) {
if (divider) {
data.add(Helpers::round2(*(int8_t *)(dv.value_p), divider));
} else {
data.add(*(int8_t *)(dv.value_p));
}
} else if ((dv.type == DeviceValueType::UINT) && Helpers::hasValue(*(uint8_t *)(dv.value_p))) {
if (divider) {
data.add(Helpers::round2(*(uint8_t *)(dv.value_p), divider));
} else {
data.add(*(uint8_t *)(dv.value_p));
}
} else if ((dv.type == DeviceValueType::SHORT) && Helpers::hasValue(*(int16_t *)(dv.value_p))) {
if (divider) {
data.add(Helpers::round2(*(int16_t *)(dv.value_p), divider));
} else {
data.add(*(int16_t *)(dv.value_p));
}
} else if ((dv.type == DeviceValueType::USHORT) && Helpers::hasValue(*(uint16_t *)(dv.value_p))) {
if (divider) {
data.add(Helpers::round2(*(uint16_t *)(dv.value_p), divider));
} else {
data.add(*(uint16_t *)(dv.value_p));
}
} else if ((dv.type == DeviceValueType::ULONG) && Helpers::hasValue(*(uint32_t *)(dv.value_p))) {
if (divider) {
data.add(Helpers::round2(*(uint32_t *)(dv.value_p), divider));
} else {
data.add(*(uint32_t *)(dv.value_p));
}
} else if ((dv.type == DeviceValueType::TIME) && Helpers::hasValue(*(uint32_t *)(dv.value_p))) {
uint32_t time_value = *(uint32_t *)(dv.value_p);
time_value = (divider) ? time_value / divider : time_value; // sometimes we need to divide by 60
char time_s[40];
snprintf_P(time_s, 40, PSTR("%d days %d hours %d minutes"), (time_value / 1440), ((time_value % 1440) / 60), (time_value % 60));
data.add(time_s);
}
}
// check if we've added a data element by comparing the size
// then add the remaining elements
uint8_t sz = data.size();
if (sz > num_elements) {
// add the unit of measure (uom)
if (dv.uom == DeviceValueUOM::MINUTES) {
data.add(nullptr);
} else {
data.add(uom_to_string(dv.uom));
}
// add name, prefixing the tag if it exists
// if we're a boiler, ignore the tag
if (dv.tag.empty() || (device_type_ == DeviceType::BOILER)) {
data.add(dv.full_name);
} else {
char name[50];
snprintf_P(name, sizeof(name), "(%s) %s", dv.tag.c_str(), uuid::read_flash_string(dv.full_name).c_str());
data.add(name);
}
num_elements = sz + 2;
}
}
}
return (num_elements != 0);
}
// For each value in the device create the json object pair and add it to given json
// return false if empty
bool EMSdevice::generate_values_json(JsonObject & root, const std::string & tag_filter, const bool verbose) {
bool has_value = false; // to see if we've added a value. it's faster than doing a json.size() at the end
std::string old_tag(40, '\0');
JsonObject json = root;
for (const auto & dv : devicevalues_) {
// only show if tag is either empty or matches a value, and don't show if full_name is empty unless we're outputing for mqtt payloads
if (((tag_filter.empty()) || (tag_filter == dv.tag)) && (dv.full_name != nullptr || !verbose)) {
bool have_tag = (!dv.tag.empty() && (dv.device_type != DeviceType::BOILER));
char name[80];
if (verbose) {
// prefix the tag in brackets, unless it's Boiler because we're naughty and use tag for the MQTT topic
if (have_tag) {
snprintf_P(name, 80, "(%s) %s", dv.tag.c_str(), uuid::read_flash_string(dv.full_name).c_str());
} else {
strcpy(name, uuid::read_flash_string(dv.full_name).c_str()); // use full name
}
} else {
strcpy(name, uuid::read_flash_string(dv.short_name).c_str()); // use short name
// if we have a tag, and its different to the last one create a nested object
if (have_tag && (dv.tag != old_tag)) {
old_tag = dv.tag;
json = root.createNestedObject(dv.tag);
}
}
// handle Booleans (true, false)
if ((dv.type == DeviceValueType::BOOL) && Helpers::hasValue(*(uint8_t *)(dv.value_p), EMS_VALUE_BOOL)) {
// see if we have options for the bool's
if (dv.options.size() == 2) {
json[name] = *(uint8_t *)(dv.value_p) ? dv.options[0] : dv.options[1];
has_value = true;
} else {
// see how to render the value depending on the setting
if (Helpers::bool_format() == BOOL_FORMAT_ONOFF) {
// on or off as strings
json[name] = *(uint8_t *)(dv.value_p) ? F_(on) : F_(off);
has_value = true;
} else if (Helpers::bool_format() == BOOL_FORMAT_TRUEFALSE) {
// true or false values (not strings)
json[name] = (bool)(*(uint8_t *)(dv.value_p)) ? true : false;
has_value = true;
} else {
// 1 or 0
json[name] = (uint8_t)(*(uint8_t *)(dv.value_p)) ? 1 : 0;
has_value = true;
}
}
}
// handle TEXT strings
else if ((dv.type == DeviceValueType::TEXT) && (Helpers::hasValue((char *)(dv.value_p)))) {
json[name] = (char *)(dv.value_p);
has_value = true;
}
// handle ENUMs
else if ((dv.type == DeviceValueType::ENUM) && Helpers::hasValue(*(uint8_t *)(dv.value_p))) {
if (*(uint8_t *)(dv.value_p) < dv.options.size()) {
json[name] = dv.options[*(uint8_t *)(dv.value_p)];
has_value = true;
}
}
// handle Integers and Floats
else {
// If a divider is specified, do the division to 2 decimals places and send back as double/float
// otherwise force as an integer whole
// the nested if's is necessary due to the way the ArduinoJson templates are pre-processed by the compiler
uint8_t divider = (dv.options.size() == 1) ? Helpers::atoint(uuid::read_flash_string(dv.options[0]).c_str()) : 0;
// INT
if ((dv.type == DeviceValueType::INT) && Helpers::hasValue(*(int8_t *)(dv.value_p))) {
if (divider) {
json[name] = Helpers::round2(*(int8_t *)(dv.value_p), divider);
} else {
json[name] = *(int8_t *)(dv.value_p);
}
has_value = true;
} else if ((dv.type == DeviceValueType::UINT) && Helpers::hasValue(*(uint8_t *)(dv.value_p))) {
if (divider) {
json[name] = Helpers::round2(*(uint8_t *)(dv.value_p), divider);
} else {
json[name] = *(uint8_t *)(dv.value_p);
}
has_value = true;
} else if ((dv.type == DeviceValueType::SHORT) && Helpers::hasValue(*(int16_t *)(dv.value_p))) {
if (divider) {
json[name] = Helpers::round2(*(int16_t *)(dv.value_p), divider);
} else {
json[name] = *(int16_t *)(dv.value_p);
}
has_value = true;
} else if ((dv.type == DeviceValueType::USHORT) && Helpers::hasValue(*(uint16_t *)(dv.value_p))) {
if (divider) {
json[name] = Helpers::round2(*(uint16_t *)(dv.value_p), divider);
} else {
json[name] = *(uint16_t *)(dv.value_p);
}
has_value = true;
} else if ((dv.type == DeviceValueType::ULONG) && Helpers::hasValue(*(uint32_t *)(dv.value_p))) {
if (divider) {
json[name] = Helpers::round2(*(uint32_t *)(dv.value_p), divider);
} else {
json[name] = *(uint32_t *)(dv.value_p);
}
has_value = true;
} else if ((dv.type == DeviceValueType::TIME) && Helpers::hasValue(*(uint32_t *)(dv.value_p))) {
uint32_t time_value = *(uint32_t *)(dv.value_p);
time_value = (divider) ? time_value / divider : time_value; // sometimes we need to divide by 60
if (verbose) {
char time_s[40];
snprintf_P(time_s, sizeof(time_s), PSTR("%d days %d hours %d minutes"), (time_value / 1440), ((time_value % 1440) / 60), (time_value % 60));
json[name] = time_s;
} else {
json[name] = time_value;
}
has_value = true;
}
}
}
}
return has_value;
}
// return the name of the telegram type // return the name of the telegram type
std::string EMSdevice::telegram_type_name(std::shared_ptr<const Telegram> telegram) { std::string EMSdevice::telegram_type_name(std::shared_ptr<const Telegram> telegram) {
// see if it's one of the common ones, like Version // see if it's one of the common ones, like Version
@@ -354,50 +690,4 @@ void EMSdevice::read_command(const uint16_t type_id) {
EMSESP::send_read_request(type_id, device_id()); EMSESP::send_read_request(type_id, device_id());
} }
// create json key/value pair
void EMSdevice::create_value_json(JsonArray & root,
const __FlashStringHelper * key,
const __FlashStringHelper * prefix,
const __FlashStringHelper * name,
const __FlashStringHelper * suffix,
JsonObject & json) {
JsonVariant data = json[uuid::read_flash_string(key)];
if (data == nullptr) {
return; // doesn't exist
}
// add prefix to name
if (prefix != nullptr) {
char name_text[100];
snprintf_P(name_text, sizeof(name_text), PSTR("%s%s"), uuid::read_flash_string(prefix).c_str(), uuid::read_flash_string(name).c_str());
root.add(name_text);
} else {
root.add(name);
}
// convert to string and add the suffix, this is to save space when sending to the web as json
// which is why we use n and v instead of name and value
std::string suffix_string(10, '\0');
if (suffix == nullptr) {
suffix_string = "";
} else {
suffix_string = " " + uuid::read_flash_string(suffix);
}
char data_string[40];
if (data.is<char *>()) {
snprintf_P(data_string, sizeof(data_string), PSTR("%s%s"), data.as<char *>(), suffix_string.c_str());
} else if (data.is<int>()) {
snprintf_P(data_string, sizeof(data_string), PSTR("%d%s"), data.as<int>(), suffix_string.c_str());
} else if (data.is<float>()) {
char s[10];
snprintf_P(data_string, sizeof(data_string), PSTR("%s%s"), Helpers::render_value(s, (float)data.as<float>(), 1), suffix_string.c_str());
} else if (data.is<bool>()) {
char s[10];
snprintf_P(data_string, sizeof(data_string), PSTR("%s%s"), Helpers::render_boolean(s, data.as<bool>()), suffix_string.c_str());
}
root.add(data_string);
}
} // namespace emsesp } // namespace emsesp

View File

@@ -30,10 +30,43 @@
namespace emsesp { namespace emsesp {
enum DeviceValueType : uint8_t {
BOOL,
INT,
UINT,
SHORT,
USHORT,
ULONG,
TIME, // same as ULONG
ENUM,
TEXT
};
// Unit Of Measurement mapping
enum DeviceValueUOM : uint8_t {
DEGREES,
PERCENT,
LMIN,
KWH,
WH,
HOURS,
MINUTES,
UA,
BAR,
NONE
};
class EMSdevice { class EMSdevice {
public: public:
virtual ~EMSdevice() = default; // destructor of base class must always be virtual because it's a polymorphic class
static constexpr uint8_t EMS_DEVICES_MAX_TELEGRAMS = 20; static constexpr uint8_t EMS_DEVICES_MAX_TELEGRAMS = 20;
// virtual functions overrules by derived classes
virtual bool publish_ha_config() = 0;
// device_type defines which derived class to use, e.g. BOILER, THERMOSTAT etc.. // device_type defines which derived class to use, e.g. BOILER, THERMOSTAT etc..
EMSdevice(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand) EMSdevice(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand)
: device_type_(device_type) : device_type_(device_type)
@@ -45,8 +78,6 @@ class EMSdevice {
, brand_(brand) { , brand_(brand) {
} }
virtual ~EMSdevice() = default; // destructor of base class must always be virtual because it's a polymorphic class
inline uint8_t device_id() const { inline uint8_t device_id() const {
return device_id_; return device_id_;
} }
@@ -55,6 +86,8 @@ class EMSdevice {
static std::string device_type_2_device_name(const uint8_t device_type); static std::string device_type_2_device_name(const uint8_t device_type);
static uint8_t device_name_2_device_type(const char * topic); static uint8_t device_name_2_device_type(const char * topic);
static const __FlashStringHelper * uom_to_string(uint8_t uom);
inline uint8_t product_id() const { inline uint8_t product_id() const {
return product_id_; return product_id_;
} }
@@ -117,6 +150,14 @@ class EMSdevice {
unique_id_ = unique_id; unique_id_ = unique_id;
} }
inline bool has_update() const {
return has_update_;
}
inline void has_update(bool has_update) {
has_update_ |= has_update;
}
std::string brand_to_string() const; std::string brand_to_string() const;
static uint8_t decode_brand(uint8_t value); static uint8_t decode_brand(uint8_t value);
@@ -131,6 +172,19 @@ class EMSdevice {
void register_telegram_type(const uint16_t telegram_type_id, const __FlashStringHelper * telegram_type_name, bool fetch, process_function_p cb); void register_telegram_type(const uint16_t telegram_type_id, const __FlashStringHelper * telegram_type_name, bool fetch, process_function_p cb);
bool handle_telegram(std::shared_ptr<const Telegram> telegram); bool handle_telegram(std::shared_ptr<const Telegram> telegram);
std::string get_value_uom(const char * key);
bool generate_values_json(JsonObject & json, const std::string & tag_filter, const bool verbose = false);
bool generate_values_json_web(JsonObject & json);
void register_device_value(std::string & tag,
void * value_p,
uint8_t type,
const flash_string_vector & options,
const __FlashStringHelper * short_name,
const __FlashStringHelper * full_name,
uint8_t uom,
const __FlashStringHelper * icon = nullptr);
void write_command(const uint16_t type_id, const uint8_t offset, uint8_t * message_data, const uint8_t message_length, const uint16_t validate_typeid); void write_command(const uint16_t type_id, const uint8_t offset, uint8_t * message_data, const uint8_t message_length, const uint16_t validate_typeid);
void write_command(const uint16_t type_id, const uint8_t offset, const uint8_t value, const uint16_t validate_typeid); void write_command(const uint16_t type_id, const uint8_t offset, const uint8_t value, const uint16_t validate_typeid);
void write_command(const uint16_t type_id, const uint8_t offset, const uint8_t value); void write_command(const uint16_t type_id, const uint8_t offset, const uint8_t value);
@@ -139,12 +193,6 @@ class EMSdevice {
void register_mqtt_topic(const std::string & topic, mqtt_subfunction_p f); void register_mqtt_topic(const std::string & topic, mqtt_subfunction_p f);
void register_mqtt_cmd(const __FlashStringHelper * cmd, cmdfunction_p f); void register_mqtt_cmd(const __FlashStringHelper * cmd, cmdfunction_p f);
// virtual functions overrules by derived classes
virtual void publish_values(JsonObject & json, bool force = false) = 0;
virtual bool export_values(JsonObject & json) = 0;
virtual bool updated_values() = 0;
virtual void device_info_web(JsonArray & root) = 0;
std::string telegram_type_name(std::shared_ptr<const Telegram> telegram); std::string telegram_type_name(std::shared_ptr<const Telegram> telegram);
void fetch_values(); void fetch_values();
@@ -155,12 +203,13 @@ class EMSdevice {
telegram_functions_.reserve(n); telegram_functions_.reserve(n);
} }
static void create_value_json(JsonArray & root, bool ha_config_done() const {
const __FlashStringHelper * key, return ha_config_done_;
const __FlashStringHelper * prefix, }
const __FlashStringHelper * name,
const __FlashStringHelper * suffix, void ha_config_done(const bool v) {
JsonObject & json); ha_config_done_ = v;
}
enum Brand : uint8_t { enum Brand : uint8_t {
NO_BRAND = 0, // 0 NO_BRAND = 0, // 0
@@ -214,14 +263,47 @@ class EMSdevice {
static constexpr uint8_t EMS_DEVICE_FLAG_EASY = 1; static constexpr uint8_t EMS_DEVICE_FLAG_EASY = 1;
static constexpr uint8_t EMS_DEVICE_FLAG_RC10 = 2; static constexpr uint8_t EMS_DEVICE_FLAG_RC10 = 2;
static constexpr uint8_t EMS_DEVICE_FLAG_RC20 = 3; static constexpr uint8_t EMS_DEVICE_FLAG_RC20 = 3;
static constexpr uint8_t EMS_DEVICE_FLAG_RC20_2 = 4; // Variation on RC20, Older, like ES72? static constexpr uint8_t EMS_DEVICE_FLAG_RC20_2 = 4; // Variation on RC20, Older, like ES72
static constexpr uint8_t EMS_DEVICE_FLAG_RC30_1 = 5; // variation on RC30, Newer? static constexpr uint8_t EMS_DEVICE_FLAG_RC30_1 = 5; // variation on RC30, Newer models
static constexpr uint8_t EMS_DEVICE_FLAG_RC30 = 6; static constexpr uint8_t EMS_DEVICE_FLAG_RC30 = 6;
static constexpr uint8_t EMS_DEVICE_FLAG_RC35 = 7; static constexpr uint8_t EMS_DEVICE_FLAG_RC35 = 7;
static constexpr uint8_t EMS_DEVICE_FLAG_RC300 = 8; static constexpr uint8_t EMS_DEVICE_FLAG_RC300 = 8;
static constexpr uint8_t EMS_DEVICE_FLAG_RC100 = 9; static constexpr uint8_t EMS_DEVICE_FLAG_RC100 = 9;
static constexpr uint8_t EMS_DEVICE_FLAG_JUNKERS = 10; static constexpr uint8_t EMS_DEVICE_FLAG_JUNKERS = 10;
struct DeviceValue {
uint8_t device_type; // EMSdevice::DeviceType
const std::string tag; // MQTT topic or ID
void * value_p; // pointer to variable of any type
uint8_t type; // DeviceValueType::*
const flash_string_vector options; // list of options for ENUM, or divider
const __FlashStringHelper * short_name; // used in MQTT
const __FlashStringHelper * full_name; // used in Web and Console
uint8_t uom; // DeviceValueUOM::*
const __FlashStringHelper * icon; // HA icon
DeviceValue(uint8_t device_type,
const std::string & tag,
void * value_p,
uint8_t type,
const flash_string_vector options,
const __FlashStringHelper * short_name,
const __FlashStringHelper * full_name,
uint8_t uom,
const __FlashStringHelper * icon)
: device_type(device_type)
, tag(tag)
, value_p(value_p)
, type(type)
, options(options)
, short_name(short_name)
, full_name(full_name)
, uom(uom)
, icon(icon) {
}
};
const std::vector<DeviceValue> devicevalues() const;
private: private:
uint8_t unique_id_; uint8_t unique_id_;
uint8_t device_type_ = DeviceType::SYSTEM; uint8_t device_type_ = DeviceType::SYSTEM;
@@ -232,6 +314,9 @@ class EMSdevice {
uint8_t flags_ = 0; uint8_t flags_ = 0;
uint8_t brand_ = Brand::NO_BRAND; uint8_t brand_ = Brand::NO_BRAND;
bool ha_config_done_ = false;
bool has_update_ = false;
struct TelegramFunction { struct TelegramFunction {
uint16_t telegram_type_id_; // it's type_id uint16_t telegram_type_id_; // it's type_id
const __FlashStringHelper * telegram_type_name_; // e.g. RC20Message const __FlashStringHelper * telegram_type_name_; // e.g. RC20Message
@@ -246,6 +331,8 @@ class EMSdevice {
} }
}; };
std::vector<TelegramFunction> telegram_functions_; // each EMS device has its own set of registered telegram types std::vector<TelegramFunction> telegram_functions_; // each EMS device has its own set of registered telegram types
std::vector<DeviceValue> devicevalues_;
}; };
} // namespace emsesp } // namespace emsesp

View File

@@ -41,7 +41,7 @@ WebAPIService EMSESP::webAPIService = WebAPIService(&webServer);
using DeviceFlags = emsesp::EMSdevice; using DeviceFlags = emsesp::EMSdevice;
using DeviceType = emsesp::EMSdevice::DeviceType; using DeviceType = emsesp::EMSdevice::DeviceType;
std::vector<std::unique_ptr<EMSdevice>> EMSESP::emsdevices; // array of all the detected EMS devices std::vector<std::unique_ptr<EMSdevice>> EMSESP::emsdevices; // array of all the detected EMS devices
std::vector<emsesp::EMSESP::Device_record> EMSESP::device_library_; // libary of all our known EMS devices so far std::vector<emsesp::EMSESP::Device_record> EMSESP::device_library_; // library of all our known EMS devices so far
uuid::log::Logger EMSESP::logger_{F_(emsesp), uuid::log::Facility::KERN}; uuid::log::Logger EMSESP::logger_{F_(emsesp), uuid::log::Facility::KERN};
@@ -256,7 +256,7 @@ void EMSESP::show_ems(uuid::console::Shell & shell) {
shell.println(); shell.println();
} }
// show EMS device values // show EMS device values to the shell console
void EMSESP::show_device_values(uuid::console::Shell & shell) { void EMSESP::show_device_values(uuid::console::Shell & shell) {
if (emsdevices.empty()) { if (emsdevices.empty()) {
shell.printfln(F("No EMS devices detected. Try using 'scan devices' from the ems menu.")); shell.printfln(F("No EMS devices detected. Try using 'scan devices' from the ems menu."));
@@ -264,8 +264,6 @@ void EMSESP::show_device_values(uuid::console::Shell & shell) {
return; return;
} }
DynamicJsonDocument doc(EMSESP_MAX_JSON_SIZE_MAX_DYN);
// do this in the order of factory classes to keep a consistent order when displaying // do this in the order of factory classes to keep a consistent order when displaying
for (const auto & device_class : EMSFactory::device_handlers()) { for (const auto & device_class : EMSFactory::device_handlers()) {
for (const auto & emsdevice : emsdevices) { for (const auto & emsdevice : emsdevices) {
@@ -273,16 +271,40 @@ void EMSESP::show_device_values(uuid::console::Shell & shell) {
// print header // print header
shell.printfln(F("%s: %s"), emsdevice->device_type_name().c_str(), emsdevice->to_string().c_str()); shell.printfln(F("%s: %s"), emsdevice->device_type_name().c_str(), emsdevice->to_string().c_str());
doc.clear(); // clear so we can re-use for each device DynamicJsonDocument doc(EMSESP_JSON_SIZE_XLARGE_DYN); // use max size
JsonArray root = doc.to<JsonArray>(); JsonObject json = doc.to<JsonObject>();
emsdevice->device_info_web(root); // create array emsdevice->generate_values_json(json, "", true); // verbose mode
// iterate values and print to shell // print line
uint8_t key_value = 0; uint8_t id = 0;
for (const JsonVariant & value : root) { for (JsonPair p : json) {
shell.printf((++key_value & 1) ? " %s: " : "%s\r\n", value.as<const char *>()); const char * key = p.key().c_str();
shell.printf(" %s: ", key);
JsonVariant data = p.value();
shell.print(COLOR_BRIGHT_GREEN);
if (data.is<char *>()) {
shell.print(data.as<char *>());
} else if (data.is<int>()) {
shell.print(data.as<int>());
} else if (data.is<float>()) {
char s[10];
shell.print(Helpers::render_value(s, (float)data.as<float>(), 1));
} else if (data.is<bool>()) {
char s[10];
shell.print(Helpers::render_boolean(s, data.as<bool>()));
} }
// if there is a uom print it
std::string uom = emsdevice->get_value_uom(key);
if (!uom.empty()) {
shell.print(' ');
shell.print(uom);
}
shell.print(COLOR_RESET);
shell.println();
id++;
}
shell.println(); shell.println();
} }
} }
@@ -311,12 +333,12 @@ void EMSESP::publish_all(bool force) {
return; return;
} }
if (Mqtt::connected()) { if (Mqtt::connected()) {
publish_device_values(EMSdevice::DeviceType::BOILER, false); publish_device_values(EMSdevice::DeviceType::BOILER);
publish_device_values(EMSdevice::DeviceType::THERMOSTAT, false); publish_device_values(EMSdevice::DeviceType::THERMOSTAT);
publish_device_values(EMSdevice::DeviceType::SOLAR, false); publish_device_values(EMSdevice::DeviceType::SOLAR);
publish_device_values(EMSdevice::DeviceType::MIXER, false); publish_device_values(EMSdevice::DeviceType::MIXER);
publish_other_values(); publish_other_values();
publish_sensor_values(true, false); publish_sensor_values(true);
system_.send_heartbeat(); system_.send_heartbeat();
} }
} }
@@ -327,29 +349,31 @@ void EMSESP::publish_all_loop() {
if (!Mqtt::connected() || !publish_all_idx_) { if (!Mqtt::connected() || !publish_all_idx_) {
return; return;
} }
// every HA-sensor takes 20 ms, wait ~2 sec to finish (boiler have ~70 sensors)
// every HA-sensor takes 20 ms, wait ~2 sec to finish (boiler has ~70 sensors)
if ((uuid::get_uptime() - last < 2000)) { if ((uuid::get_uptime() - last < 2000)) {
return; return;
} }
last = uuid::get_uptime(); last = uuid::get_uptime();
switch (publish_all_idx_++) { switch (publish_all_idx_++) {
case 1: case 1:
publish_device_values(EMSdevice::DeviceType::BOILER, true); publish_device_values(EMSdevice::DeviceType::BOILER);
break; break;
case 2: case 2:
publish_device_values(EMSdevice::DeviceType::THERMOSTAT, true); publish_device_values(EMSdevice::DeviceType::THERMOSTAT);
break; break;
case 3: case 3:
publish_device_values(EMSdevice::DeviceType::SOLAR, true); publish_device_values(EMSdevice::DeviceType::SOLAR);
break; break;
case 4: case 4:
publish_device_values(EMSdevice::DeviceType::MIXER, true); publish_device_values(EMSdevice::DeviceType::MIXER);
break; break;
case 5: case 5:
publish_other_values(); publish_other_values();
break; break;
case 6: case 6:
publish_sensor_values(true, true); publish_sensor_values(true);
break; break;
case 7: case 7:
system_.send_heartbeat(); system_.send_heartbeat();
@@ -362,37 +386,61 @@ void EMSESP::publish_all_loop() {
} }
// create json doc for the devices values and add to MQTT publish queue // create json doc for the devices values and add to MQTT publish queue
// special case for Mixer units, since we want to bundle all devices together into one payload void EMSESP::publish_device_values(uint8_t device_type) {
void EMSESP::publish_device_values(uint8_t device_type, bool force) { DynamicJsonDocument doc(EMSESP_JSON_SIZE_XLARGE_DYN); // use max size
if (device_type == EMSdevice::DeviceType::MIXER && Mqtt::mqtt_format() != Mqtt::Format::SINGLE) {
// DynamicJsonDocument doc(EMSESP_MAX_JSON_SIZE_LARGE);
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_LARGE> doc;
JsonObject json = doc.to<JsonObject>(); JsonObject json = doc.to<JsonObject>();
bool has_value = false;
for (const auto & emsdevice : emsdevices) { for (const auto & emsdevice : emsdevices) {
if (emsdevice && (emsdevice->device_type() == device_type)) { if (emsdevice && (emsdevice->device_type() == device_type)) {
emsdevice->publish_values(json, force); // if we're using HA and it's not already done, send the config topics first. only do this once
if (Mqtt::ha_enabled() && (!emsdevice->ha_config_done())) {
// create the configs for each value as a sensor
for (const auto & dv : emsdevice->devicevalues()) {
if (dv.device_type == device_type) {
Mqtt::register_mqtt_ha_sensor(dv.type, dv.tag.c_str(), dv.full_name, device_type, dv.short_name, dv.uom, dv.icon);
} }
} }
Mqtt::publish("mixer_data", doc.as<JsonObject>());
// create HA device
// if this is done early, it may fail for some reason
emsdevice->ha_config_done(emsdevice->publish_ha_config());
}
// if its a boiler, generate json for each group and publish it
if (device_type == DeviceType::BOILER) {
emsdevice->generate_values_json(json, "boiler_data");
Mqtt::publish("boiler_data", json);
json.clear();
emsdevice->generate_values_json(json, "boiler_data_ww");
Mqtt::publish("boiler_data_ww", json);
json.clear();
emsdevice->generate_values_json(json, "boiler_data_info");
Mqtt::publish("boiler_data_info", json);
return; return;
} }
for (const auto & emsdevice : emsdevices) { // for all other devices add the values to the json, without verbose mode
if (emsdevice && (emsdevice->device_type() == device_type)) { has_value |= emsdevice->generate_values_json(json, "");
JsonObject dummy;
emsdevice->publish_values(dummy, force);
} }
} }
// if there is nothing to publish, exit
if (!has_value) {
return;
}
// publish it under a single topic
char topic[20];
snprintf_P(topic, sizeof(topic), PSTR("%s_data"), EMSdevice::device_type_2_device_name(device_type).c_str());
Mqtt::publish(topic, json);
} }
// call the devices that don't need special attention
void EMSESP::publish_other_values() { void EMSESP::publish_other_values() {
for (const auto & emsdevice : emsdevices) { publish_device_values(EMSdevice::DeviceType::SWITCH);
if (emsdevice && (emsdevice->device_type() != EMSdevice::DeviceType::BOILER) && (emsdevice->device_type() != EMSdevice::DeviceType::THERMOSTAT) publish_device_values(EMSdevice::DeviceType::HEATPUMP);
&& (emsdevice->device_type() != EMSdevice::DeviceType::SOLAR) && (emsdevice->device_type() != EMSdevice::DeviceType::MIXER)) {
JsonObject dummy;
emsdevice->publish_values(dummy);
}
}
} }
void EMSESP::publish_sensor_values(const bool time, const bool force) { void EMSESP::publish_sensor_values(const bool time, const bool force) {
@@ -407,7 +455,7 @@ void EMSESP::publish_response(std::shared_ptr<const Telegram> telegram) {
return; return;
} }
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc; StaticJsonDocument<EMSESP_JSON_SIZE_SMALL> doc;
char buffer[100]; char buffer[100];
doc["src"] = Helpers::hextoa(buffer, telegram->src); doc["src"] = Helpers::hextoa(buffer, telegram->src);
@@ -641,7 +689,7 @@ bool EMSESP::process_telegram(std::shared_ptr<const Telegram> telegram) {
// match device_id and type_id // match device_id and type_id
// calls the associated process function for that EMS device // calls the associated process function for that EMS device
// returns false if the device_id doesn't recognize it // returns false if the device_id doesn't recognize it
// after the telegram has been processed, call the updated_values() function to see if we need to force an MQTT publish // after the telegram has been processed, call see if there have been values changed and we need to do a MQTT publish
bool found = false; bool found = false;
for (const auto & emsdevice : emsdevices) { for (const auto & emsdevice : emsdevices) {
if (emsdevice) { if (emsdevice) {
@@ -649,10 +697,11 @@ bool EMSESP::process_telegram(std::shared_ptr<const Telegram> telegram) {
found = emsdevice->handle_telegram(telegram); found = emsdevice->handle_telegram(telegram);
// if we correctly processes the telegram follow up with sending it via MQTT if needed // if we correctly processes the telegram follow up with sending it via MQTT if needed
if (found && Mqtt::connected()) { if (found && Mqtt::connected()) {
if ((mqtt_.get_publish_onchange(emsdevice->device_type()) && emsdevice->updated_values()) || telegram->type_id == publish_id_) { if ((mqtt_.get_publish_onchange(emsdevice->device_type()) && emsdevice->has_update()) || telegram->type_id == publish_id_) {
if (telegram->type_id == publish_id_) { if (telegram->type_id == publish_id_) {
publish_id_ = 0; publish_id_ = 0;
} }
emsdevice->has_update(false); // reset flag
publish_device_values(emsdevice->device_type()); // publish to MQTT if we explicitly have too publish_device_values(emsdevice->device_type()); // publish to MQTT if we explicitly have too
} }
} }
@@ -671,21 +720,6 @@ bool EMSESP::process_telegram(std::shared_ptr<const Telegram> telegram) {
return found; return found;
} }
// calls the device handler's function to populate a json doc with device info
// to be used in the Web UI. The unique_id is the unique record ID from the Web table to identify which device to load
void EMSESP::device_info_web(const uint8_t unique_id, JsonObject & root) {
for (const auto & emsdevice : emsdevices) {
if (emsdevice) {
if (emsdevice->unique_id() == unique_id) {
root["name"] = emsdevice->to_string_short(); // can't use c_str() because of scope
JsonArray data = root.createNestedArray("data");
emsdevice->device_info_web(data);
return;
}
}
}
}
// return true if we have this device already registered // return true if we have this device already registered
bool EMSESP::device_exists(const uint8_t device_id) { bool EMSESP::device_exists(const uint8_t device_id) {
for (const auto & emsdevice : emsdevices) { for (const auto & emsdevice : emsdevices) {
@@ -813,14 +847,14 @@ bool EMSESP::add_device(const uint8_t device_id, const uint8_t product_id, std::
// export all values to info command // export all values to info command
// value and id are ignored // value and id are ignored
bool EMSESP::command_info(uint8_t device_type, JsonObject & json) { bool EMSESP::command_info(uint8_t device_type, JsonObject & json) {
bool ok = false; bool has_value = false;
for (const auto & emsdevice : emsdevices) { for (const auto & emsdevice : emsdevices) {
if (emsdevice && (emsdevice->device_type() == device_type)) { if (emsdevice && (emsdevice->device_type() == device_type)) {
ok |= emsdevice->export_values(json); has_value |= emsdevice->generate_values_json(json, "", true); // verbose mode
} }
} }
return ok; return has_value;
} }
// send a read request, passing it into to the Tx Service, with offset // send a read request, passing it into to the Tx Service, with offset
@@ -972,8 +1006,8 @@ void EMSESP::start() {
webSettingsService.begin(); // load EMS-ESP specific settings webSettingsService.begin(); // load EMS-ESP specific settings
} }
// Load our library of known devices. Names are stored in Flash mem. // Load our library of known devices into stack mem. Names are stored in Flash mem.
device_library_.reserve(80); // device_library_.reserve(80);
device_library_ = { device_library_ = {
#include "device_library.h" #include "device_library.h"
}; };
@@ -985,13 +1019,8 @@ void EMSESP::start() {
dallassensor_.start(); // dallas external sensors dallassensor_.start(); // dallas external sensors
webServer.begin(); // start web server webServer.begin(); // start web server
emsdevices.reserve(5); // reserve space for initially 5 devices to avoid mem emsdevices.reserve(5); // reserve space for initially 5 devices to avoid mem frag issues
LOG_INFO(F("EMS Device library loaded with %d records"), device_library_.size()); LOG_INFO(F("EMS Device library loaded with %d records"), device_library_.size());
#if defined(EMSESP_STANDALONE)
mqtt_.on_connect(); // simulate an MQTT connection
#endif
} }
// main loop calling all services // main loop calling all services

View File

@@ -50,15 +50,22 @@
#include "roomcontrol.h" #include "roomcontrol.h"
#include "command.h" #include "command.h"
#include "devices/boiler.h"
#define WATCH_ID_NONE 0 // no watch id set #define WATCH_ID_NONE 0 // no watch id set
#define EMSESP_MAX_JSON_SIZE_HA_CONFIG 384 // for small HA config payloads, using StaticJsonDocument #define EMSESP_JSON_SIZE_HA_CONFIG 768 // for HA config payloads, using StaticJsonDocument
#define EMSESP_MAX_JSON_SIZE_SMALL 256 // for smaller json docs, using StaticJsonDocument #define EMSESP_JSON_SIZE_SMALL 256 // for smaller json docs, using StaticJsonDocument
#define EMSESP_MAX_JSON_SIZE_MEDIUM 768 // for medium json docs from ems devices, using StaticJsonDocument #define EMSESP_JSON_SIZE_MEDIUM 768 // for medium json docs from ems devices, using StaticJsonDocument
#define EMSESP_MAX_JSON_SIZE_LARGE 1024 // for large json docs from ems devices, like boiler or thermostat data, using StaticJsonDocument #define EMSESP_JSON_SIZE_LARGE 1024 // for large json docs from ems devices, like boiler or thermostat data, using StaticJsonDocument
#define EMSESP_MAX_JSON_SIZE_MEDIUM_DYN 1024 // for large json docs, using DynamicJsonDocument #define EMSESP_JSON_SIZE_MEDIUM_DYN 1024 // for large json docs, using DynamicJsonDocument
#define EMSESP_MAX_JSON_SIZE_LARGE_DYN 2048 // for very large json docs, using DynamicJsonDocument #define EMSESP_JSON_SIZE_LARGE_DYN 2048 // for very large json docs, using DynamicJsonDocument
#define EMSESP_MAX_JSON_SIZE_MAX_DYN 4096 // for very very large json docs, using DynamicJsonDocument
#if defined(EMSESP_STANDALONE)
#define EMSESP_JSON_SIZE_XLARGE_DYN 7000 // for very very large json docs, using DynamicJsonDocument
#else
#define EMSESP_JSON_SIZE_XLARGE_DYN 4096 // for very very large json docs, using DynamicJsonDocument
#endif
namespace emsesp { namespace emsesp {
@@ -69,7 +76,7 @@ class EMSESP {
static void start(); static void start();
static void loop(); static void loop();
static void publish_device_values(uint8_t device_type, bool force = false); static void publish_device_values(uint8_t device_type);
static void publish_other_values(); static void publish_other_values();
static void publish_sensor_values(const bool time, const bool force = false); static void publish_sensor_values(const bool time, const bool force = false);
static void publish_all(bool force = false); static void publish_all(bool force = false);
@@ -98,8 +105,6 @@ class EMSESP {
static void send_raw_telegram(const char * data); static void send_raw_telegram(const char * data);
static bool device_exists(const uint8_t device_id); static bool device_exists(const uint8_t device_id);
static void device_info_web(const uint8_t unique_id, JsonObject & root);
static uint8_t count_devices(const uint8_t device_type); static uint8_t count_devices(const uint8_t device_type);
static uint8_t actual_master_thermostat(); static uint8_t actual_master_thermostat();
@@ -193,7 +198,6 @@ class EMSESP {
static void process_version(std::shared_ptr<const Telegram> telegram); static void process_version(std::shared_ptr<const Telegram> telegram);
static void publish_response(std::shared_ptr<const Telegram> telegram); static void publish_response(std::shared_ptr<const Telegram> telegram);
static void publish_all_loop(); static void publish_all_loop();
static bool command_info(uint8_t device_type, JsonObject & json); static bool command_info(uint8_t device_type, JsonObject & json);
static constexpr uint32_t EMS_FETCH_FREQUENCY = 60000; // check every minute static constexpr uint32_t EMS_FETCH_FREQUENCY = 60000; // check every minute
@@ -205,7 +209,6 @@ class EMSESP {
const __FlashStringHelper * name; const __FlashStringHelper * name;
uint8_t flags; uint8_t flags;
}; };
static std::vector<Device_record> device_library_; static std::vector<Device_record> device_library_;
static uint8_t actual_master_thermostat_; static uint8_t actual_master_thermostat_;

View File

@@ -136,28 +136,8 @@ char * Helpers::render_boolean(char * result, bool value) {
return result; return result;
} }
// depending on format render a number or a string
char * Helpers::render_enum(char * result, const std::vector<const __FlashStringHelper *> & value, const uint8_t no) {
if (no >= value.size()) {
return nullptr; // out of bounds
}
strcpy(result, uuid::read_flash_string(value[no]).c_str());
if (bool_format() == BOOL_FORMAT_TRUEFALSE) {
if (no == 0 && uuid::read_flash_string(value[0]) == "off") {
strlcpy(result, "false", 7);
} else if (no == 1 && uuid::read_flash_string(value[1]) == "on") {
strlcpy(result, "true", 6);
}
} else if (bool_format() == BOOL_FORMAT_NUMBERS) {
itoa(result, no);
}
return result;
}
// render for native char strings // render for native char strings
char * Helpers::render_value(char * result, const char * value, uint8_t format) { char * Helpers::render_value(char * result, const char * value, uint8_t format __attribute__((unused))) {
strcpy(result, value); strcpy(result, value);
return result; return result;
} }
@@ -293,13 +273,6 @@ char * Helpers::render_value(char * result, const uint32_t value, const uint8_t
} }
result[0] = '\0'; result[0] = '\0';
// check if we're converting from minutes to a time string
if (format == EMS_VALUE_TIME) {
snprintf_P(result, 40, PSTR("%d days %d hours %d minutes"), (value / 1440), ((value % 1440) / 60), (value % 60));
return result;
}
char s[20]; char s[20];
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
@@ -378,8 +351,8 @@ uint16_t Helpers::atoint(const char * value) {
// rounds a number to 2 decimal places // rounds a number to 2 decimal places
// example: round2(3.14159) -> 3.14 // example: round2(3.14159) -> 3.14
double Helpers::round2(double value) { double Helpers::round2(double value, const uint8_t divider) {
return (int)(value * 100 + 0.5) / 100.0; return (int)((value / divider) * 100 + 0.5) / 100.0;
} }
bool Helpers::check_abs(const int32_t i) { bool Helpers::check_abs(const int32_t i) {
@@ -398,6 +371,14 @@ bool Helpers::hasValue(const int8_t & v) {
return (v != EMS_VALUE_INT_NOTSET); return (v != EMS_VALUE_INT_NOTSET);
} }
bool Helpers::hasValue(char * v) {
if ((v == nullptr) || (strlen(v) == 0)) {
return false;
}
return (v[0] != '\0');
}
// for short these are typically 0x8300, 0x7D00 and sometimes 0x8000 // for short these are typically 0x8300, 0x7D00 and sometimes 0x8000
bool Helpers::hasValue(const int16_t & v) { bool Helpers::hasValue(const int16_t & v) {
return (abs(v) < EMS_VALUE_USHORT_NOTSET); return (abs(v) < EMS_VALUE_USHORT_NOTSET);
@@ -471,7 +452,7 @@ bool Helpers::value2bool(const char * v, bool & value) {
} }
// checks to see if a string is member of a vector and return the index, also allow true/false for on/off // checks to see if a string is member of a vector and return the index, also allow true/false for on/off
bool Helpers::value2enum(const char * v, uint8_t & value, const std::vector<const __FlashStringHelper *> & strs) { bool Helpers::value2enum(const char * v, uint8_t & value, const flash_string_vector & strs) {
if ((v == nullptr) || (strlen(v) == 0)) { if ((v == nullptr) || (strlen(v) == 0)) {
return false; return false;
} }
@@ -485,4 +466,7 @@ bool Helpers::value2enum(const char * v, uint8_t & value, const std::vector<cons
return false; return false;
} }
} // namespace emsesp } // namespace emsesp

View File

@@ -20,19 +20,18 @@
#define EMSESP_HELPERS_H #define EMSESP_HELPERS_H
#include <Arduino.h> #include <Arduino.h>
#include <uuid/common.h>
#include "telegram.h" // for EMS_VALUE_* settings #include "telegram.h" // for EMS_VALUE_* settings
#define BOOL_FORMAT_ONOFF 1 enum { BOOL_FORMAT_ONOFF = 1, BOOL_FORMAT_TRUEFALSE, BOOL_FORMAT_NUMBERS }; // matches Web UI settings
#define BOOL_FORMAT_TRUEFALSE 2
#define BOOL_FORMAT_NUMBERS 3
// #define FJSON(x) x // #define FJSON(x) x
#define FJSON(x) F(x) #define FJSON(x) F(x)
namespace emsesp { namespace emsesp {
using flash_string_vector = std::vector<const __FlashStringHelper *>;
class Helpers { class Helpers {
public: public:
static char * render_value(char * result, const float value, const uint8_t format); // format is the precision static char * render_value(char * result, const float value, const uint8_t format); // format is the precision
@@ -43,7 +42,6 @@ class Helpers {
static char * render_value(char * result, const int16_t value, const uint8_t format); static char * render_value(char * result, const int16_t value, const uint8_t format);
static char * render_value(char * result, const char * value, uint8_t format); static char * render_value(char * result, const char * value, uint8_t format);
static char * render_boolean(char * result, bool value); static char * render_boolean(char * result, bool value);
static char * render_enum(char * result, const std::vector<const __FlashStringHelper *> & value, const uint8_t no);
static char * hextoa(char * result, const uint8_t value); static char * hextoa(char * result, const uint8_t value);
static std::string data_to_hex(const uint8_t * data, const uint8_t length); static std::string data_to_hex(const uint8_t * data, const uint8_t length);
@@ -53,7 +51,7 @@ class Helpers {
static uint32_t hextoint(const char * hex); static uint32_t hextoint(const char * hex);
static uint16_t atoint(const char * value); static uint16_t atoint(const char * value);
static bool check_abs(const int32_t i); static bool check_abs(const int32_t i);
static double round2(double value); static double round2(double value, const uint8_t divider);
static std::string toLower(std::string const & s); static std::string toLower(std::string const & s);
static bool hasValue(const uint8_t & v, const uint8_t isBool = 0); static bool hasValue(const uint8_t & v, const uint8_t isBool = 0);
@@ -61,12 +59,13 @@ class Helpers {
static bool hasValue(const int16_t & v); static bool hasValue(const int16_t & v);
static bool hasValue(const uint16_t & v); static bool hasValue(const uint16_t & v);
static bool hasValue(const uint32_t & v); static bool hasValue(const uint32_t & v);
static bool hasValue(char * v);
static bool value2number(const char * v, int & value); static bool value2number(const char * v, int & value);
static bool value2float(const char * v, float & value); static bool value2float(const char * v, float & value);
static bool value2bool(const char * v, bool & value); static bool value2bool(const char * v, bool & value);
static bool value2string(const char * v, std::string & value); static bool value2string(const char * v, std::string & value);
static bool value2enum(const char * v, uint8_t & value, const std::vector<const __FlashStringHelper *> & strs); static bool value2enum(const char * v, uint8_t & value, const flash_string_vector & strs);
static void bool_format(uint8_t bool_format) { static void bool_format(uint8_t bool_format) {
bool_format_ = bool_format; bool_format_ = bool_format;

View File

@@ -62,10 +62,6 @@ MAKE_PSTR_WORD(users)
MAKE_PSTR_WORD(master) MAKE_PSTR_WORD(master)
MAKE_PSTR_WORD(pin) MAKE_PSTR_WORD(pin)
MAKE_PSTR_WORD(publish) MAKE_PSTR_WORD(publish)
MAKE_PSTR_WORD(bar)
MAKE_PSTR_WORD(min)
MAKE_PSTR_WORD(hours)
MAKE_PSTR_WORD(uA)
MAKE_PSTR_WORD(timeout) MAKE_PSTR_WORD(timeout)
// for commands // for commands
@@ -94,10 +90,6 @@ MAKE_PSTR_WORD(generic)
MAKE_PSTR_WORD(dallassensor) MAKE_PSTR_WORD(dallassensor)
MAKE_PSTR_WORD(unknown) MAKE_PSTR_WORD(unknown)
MAKE_PSTR(1space, " ")
MAKE_PSTR(2spaces, " ")
MAKE_PSTR(kwh, "kWh")
MAKE_PSTR(wh, "Wh")
MAKE_PSTR(EMSESP, "EMS-ESP") MAKE_PSTR(EMSESP, "EMS-ESP")
MAKE_PSTR(master_thermostat_fmt, "Master Thermostat Device ID = %s") MAKE_PSTR(master_thermostat_fmt, "Master Thermostat Device ID = %s")
MAKE_PSTR(host_fmt, "Host = %s") MAKE_PSTR(host_fmt, "Host = %s")
@@ -115,8 +107,26 @@ MAKE_PSTR(watchid_optional, "[ID]")
MAKE_PSTR(watch_format_optional, "[off | on | raw | unknown]") MAKE_PSTR(watch_format_optional, "[off | on | raw | unknown]")
MAKE_PSTR(invalid_watch, "Invalid watch type") MAKE_PSTR(invalid_watch, "Invalid watch type")
MAKE_PSTR(data_mandatory, "\"XX XX ...\"") MAKE_PSTR(data_mandatory, "\"XX XX ...\"")
// uom - also used with HA
MAKE_PSTR(percent, "%") MAKE_PSTR(percent, "%")
MAKE_PSTR(degrees, "°C") MAKE_PSTR(degrees, "°C")
MAKE_PSTR(kwh, "kWh")
MAKE_PSTR(wh, "Wh")
MAKE_PSTR(bar, "bar")
MAKE_PSTR(minutes, "minutes")
MAKE_PSTR(hours, "hours")
MAKE_PSTR(ua, "uA")
MAKE_PSTR(lmin, "l/min")
// Home Assistant icons (https://materialdesignicons.com/)
MAKE_PSTR(icontemperature, "mdi:temperature-celsius")
MAKE_PSTR(iconpercent, "mdi:percent-outline")
MAKE_PSTR(iconfire, "mdi:fire")
MAKE_PSTR(iconfan, "mdi:fan")
MAKE_PSTR(iconflame, "mdi:flash")
MAKE_PSTR(iconvalve, "mdi:valve")
MAKE_PSTR(asterisks, "********") MAKE_PSTR(asterisks, "********")
MAKE_PSTR(n_mandatory, "<n>") MAKE_PSTR(n_mandatory, "<n>")
MAKE_PSTR(id_optional, "[id|hc]") MAKE_PSTR(id_optional, "[id|hc]")
@@ -134,193 +144,3 @@ MAKE_PSTR(new_password_prompt1, "Enter new password: ")
MAKE_PSTR(new_password_prompt2, "Retype new password: ") MAKE_PSTR(new_password_prompt2, "Retype new password: ")
MAKE_PSTR(password_prompt, "Password: ") MAKE_PSTR(password_prompt, "Password: ")
MAKE_PSTR(unset, "<unset>") MAKE_PSTR(unset, "<unset>")
// boiler
MAKE_PSTR(heatingActive, "Heating active")
MAKE_PSTR(tapwaterActive, "Warm water/DHW active")
MAKE_PSTR(serviceCode, "Service code")
MAKE_PSTR(serviceCodeNumber, "Service code number")
MAKE_PSTR(lastCode, "Last error")
MAKE_PSTR(wWSelTemp, "Warm water selected temperature")
MAKE_PSTR(wWSetTemp, "Warm water set temperature")
MAKE_PSTR(wWDisinfectionTemp, "Warm water disinfection temperature")
MAKE_PSTR(selFlowTemp, "Selected flow temperature")
MAKE_PSTR(selBurnPow, "Burner selected max power")
MAKE_PSTR(curBurnPow, "Burner current power")
MAKE_PSTR(pumpMod, "Pump modulation")
MAKE_PSTR(pumpMod2, "Heat pump modulation")
MAKE_PSTR(wWType, "Warm water type")
MAKE_PSTR(wWChargeType, "Warm water charging type")
MAKE_PSTR(wWCircPump, "Warm water circulation pump available")
MAKE_PSTR(wWCircPumpMode, "Warm water circulation pump freq")
MAKE_PSTR(wWCirc, "Warm water circulation active")
MAKE_PSTR(outdoorTemp, "Outside temperature")
MAKE_PSTR(wWCurTemp, "Warm water current temperature (intern)")
MAKE_PSTR(wWCurTemp2, "Warm water current temperature (extern)")
MAKE_PSTR(wWCurFlow, "Warm water current tap water flow")
MAKE_PSTR(curFlowTemp, "Current flow temperature")
MAKE_PSTR(retTemp, "Return temperature")
MAKE_PSTR(switchTemp, "Mixer switch temperature")
MAKE_PSTR(sysPress, "System pressure")
MAKE_PSTR(boilTemp, "Max temperature")
MAKE_PSTR(wwStorageTemp1, "Warm water storage temperature (intern)")
MAKE_PSTR(wwStorageTemp2, "Warm water storage temperature (extern)")
MAKE_PSTR(exhaustTemp, "Exhaust temperature")
MAKE_PSTR(wWActivated, "Warm water activated")
MAKE_PSTR(wWOneTime, "Warm water one time charging")
MAKE_PSTR(wWDisinfecting, "Warm water disinfecting")
MAKE_PSTR(wWCharging, "Warm water charging")
MAKE_PSTR(wWRecharging, "Warm water recharging")
MAKE_PSTR(wWTempOK, "Warm water temperature ok")
MAKE_PSTR(wWActive, "Warm water active")
MAKE_PSTR(burnGas, "Gas")
MAKE_PSTR(flameCurr, "Flame current")
MAKE_PSTR(heatPump, "Pump")
MAKE_PSTR(fanWork, "Fan")
MAKE_PSTR(ignWork, "Ignition")
MAKE_PSTR(wWHeat, "Warm water heating")
MAKE_PSTR(heatingActivated, "Heating activated")
MAKE_PSTR(heatingTemp, "Heating temperature setting")
MAKE_PSTR(pumpModMax, "Circuit pump modulation max power")
MAKE_PSTR(pumpModMin, "Circuit pump modulation min power")
MAKE_PSTR(pumpDelay, "Circuit pump delay time")
MAKE_PSTR(burnMinPeriod, "Burner min period")
MAKE_PSTR(burnMinPower, "Burner min power")
MAKE_PSTR(burnMaxPower, "Burner max power")
MAKE_PSTR(boilHystOn, "Temperature hysteresis on")
MAKE_PSTR(boilHystOff, "Temperature hysteresis off")
MAKE_PSTR(setFlowTemp, "Set flow temperature")
MAKE_PSTR(wWSetPumpPower, "Warm water pump set power")
MAKE_PSTR(wwMixTemperature, "Warm water mix temperature")
MAKE_PSTR(wwBufferTemperature, "Warm water buffer temperature")
MAKE_PSTR(wWStarts, "Warm water starts")
MAKE_PSTR(wWWorkM, "Warm water active time")
MAKE_PSTR(setBurnPow, "Burner set power")
MAKE_PSTR(burnStarts, "Burner starts")
MAKE_PSTR(burnWorkMin, "Burner active time")
MAKE_PSTR(heatWorkMin, "Heating active time")
MAKE_PSTR(UBAuptime, "Boiler total uptime")
MAKE_PSTR(upTimeControl, "Operating time control")
MAKE_PSTR(upTimeCompHeating, "Operating time compressor heating")
MAKE_PSTR(upTimeCompCooling, "Operating time compressor cooling")
MAKE_PSTR(upTimeCompWw, "Operating time compressor warm water")
MAKE_PSTR(heatingStarts, "Heating starts (control)")
MAKE_PSTR(coolingStarts, "Cooling starts (control)")
MAKE_PSTR(wWStarts2, "Warm water starts (control)")
MAKE_PSTR(nrgConsTotal, "Energy consumption total")
MAKE_PSTR(auxElecHeatNrgConsTotal, "Auxiliary electrical heater energy consumption total")
MAKE_PSTR(auxElecHeatNrgConsHeating, "Auxiliary electrical heater energy consumption heating")
MAKE_PSTR(auxElecHeatNrgConsDHW, "Auxiliary electrical heater energy consumption DHW")
MAKE_PSTR(nrgConsCompTotal, "Energy consumption compressor total")
MAKE_PSTR(nrgConsCompHeating, "Energy consumption compressor heating")
MAKE_PSTR(nrgConsCompWw, "Energy consumption compressor warm water")
MAKE_PSTR(nrgConsCompCooling, "Energy consumption compressor total")
MAKE_PSTR(nrgSuppTotal, "Energy supplied total")
MAKE_PSTR(nrgSuppHeating, "Energy supplied heating")
MAKE_PSTR(nrgSuppWw, "Energy supplied warm water")
MAKE_PSTR(nrgSuppCooling, "Energy supplied cooling")
MAKE_PSTR(maintenanceMessage, "Maintenance message")
MAKE_PSTR(maintenance, "Scheduled maintenance")
MAKE_PSTR(maintenanceTime, "Next maintenance in")
MAKE_PSTR(maintenanceDate, "Next maintenance on")
// solar
MAKE_PSTR(collectorTemp, "Collector temperature (TS1)")
MAKE_PSTR(tankBottomTemp, "Bottom temperature (TS2)")
MAKE_PSTR(tankBottomTemp2, "Bottom temperature (TS5)")
MAKE_PSTR(tank1MaxTempCurrent, "Maximum Tank temperature")
MAKE_PSTR(heatExchangerTemp, "Heat exchanger temperature (TS6)")
MAKE_PSTR(solarPumpModulation, "Solar pump modulation (PS1)")
MAKE_PSTR(cylinderPumpModulation, "Cylinder pump modulation (PS5)")
MAKE_PSTR(pumpWorkMin, "Pump working time (min)")
MAKE_PSTR(pumpWorkMintxt, "Pump working time")
MAKE_PSTR(energyLastHour, "Energy last hour")
MAKE_PSTR(energyToday, "Energy today")
MAKE_PSTR(energyTotal, "Energy total")
MAKE_PSTR(solarPump, "Solar pump (PS1) active")
MAKE_PSTR(valveStatus, "Valve status")
MAKE_PSTR(tankHeated, "Tank heated")
MAKE_PSTR(collectorShutdown, "Collector shutdown")
// mixer
MAKE_PSTR(ww_hc, " Warm water circuit %d:")
MAKE_PSTR(wwTemp, "Current warm water temperature")
MAKE_PSTR(pumpStatus, "Current pump status")
MAKE_PSTR(tempStatus, "Current temperature status")
MAKE_PSTR(hc, " Heating circuit %d:")
MAKE_PSTR(flowTemp, "Current flow temperature")
MAKE_PSTR(flowSetTemp, "Setpoint flow temperature")
// thermostat
MAKE_PSTR(time, "Time")
MAKE_PSTR(error, "Error code")
MAKE_PSTR(display, "Display")
MAKE_PSTR(language, "Language")
MAKE_PSTR(offsetclock, "Offset clock")
MAKE_PSTR(dampedtemp, "Damped outdoor temperature")
MAKE_PSTR(inttemp1, "Temperature sensor 1")
MAKE_PSTR(inttemp2, "Temperature sensor 2")
MAKE_PSTR(intoffset, "Offset int. temperature")
MAKE_PSTR(minexttemp, "Min ext. temperature")
MAKE_PSTR(building, "Building")
MAKE_PSTR(floordry, "Floordrying")
MAKE_PSTR(floordrytemp, "Floordrying temperature")
MAKE_PSTR(wwmode, "Warm water mode")
MAKE_PSTR(wwtemp, "Warm water high temperature")
MAKE_PSTR(wwtemplow, "Warm water low temperature")
MAKE_PSTR(wwextra1, "Warm water circuit 1 extra")
MAKE_PSTR(wwextra2, "Warm water circuit 2 extra")
MAKE_PSTR(wwcircmode, "Warm water circulation mode")
// thermostat - per heating circuit
MAKE_PSTR(seltemp, "Setpoint room temperature")
MAKE_PSTR(currtemp, "Current room temperature")
MAKE_PSTR(heattemp, "Heat temperature")
MAKE_PSTR(comforttemp, "Comfort temperature")
MAKE_PSTR(daytemp, "Day temperature")
MAKE_PSTR(ecotemp, "Eco temperature")
MAKE_PSTR(nighttemp, "Night temperature")
MAKE_PSTR(manualtemp, "Manual temperature")
MAKE_PSTR(holidaytemp, "Holiday temperature")
MAKE_PSTR(nofrosttemp, "Nofrost temperature")
MAKE_PSTR(heatingtype, "Heating type")
MAKE_PSTR(targetflowtemp, "Target flow temperature")
MAKE_PSTR(offsettemp, "Offset temperature")
MAKE_PSTR(designtemp, "Design temperature")
MAKE_PSTR(summertemp, "Summer temperature")
MAKE_PSTR(summermode, "Summer mode")
MAKE_PSTR(roominfluence, "Room influence")
MAKE_PSTR(flowtempoffset, "Flow temperature offset")
MAKE_PSTR(minflowtemp, "Min. flow temperature")
MAKE_PSTR(maxflowtemp, "Max. flow temperature")
MAKE_PSTR(mode, "Mode")
MAKE_PSTR(modetype, "Mode type")
// heat pump
MAKE_PSTR(airHumidity, "Relative air humidity")
MAKE_PSTR(dewTemperature, "Dew point temperature")
// other
MAKE_PSTR(activated, "Switch activated")
MAKE_PSTR(status, "Switch status")
// Home Assistant icons
MAKE_PSTR(icontemperature, "mdi:coolant-temperature")
MAKE_PSTR(iconpercent, "mdi:sine-wave")
MAKE_PSTR(iconfire, "mdi:fire")
MAKE_PSTR(iconfan, "mdi:fan")
MAKE_PSTR(iconflash, "mdi:flash")
MAKE_PSTR(iconwaterpump, "mdi:water-pump")
MAKE_PSTR(iconexport, "mdi:home-export-outline")
MAKE_PSTR(iconimport, "mdi:home-import-outline")
MAKE_PSTR(iconcruise, "mdi:car-cruise-control")
MAKE_PSTR(iconvalve, "mdi:valve")
MAKE_PSTR(iconpower, "mdi:power-cycle")
MAKE_PSTR(iconthermostat, "mdi:home-thermometer-outline")
MAKE_PSTR(iconheatpump, "mdi:water-pump")
// MQTT topic suffix
MAKE_PSTR(mqtt_suffix_ww, "_ww")

View File

@@ -32,10 +32,12 @@ uint32_t Mqtt::publish_time_boiler_;
uint32_t Mqtt::publish_time_thermostat_; uint32_t Mqtt::publish_time_thermostat_;
uint32_t Mqtt::publish_time_solar_; uint32_t Mqtt::publish_time_solar_;
uint32_t Mqtt::publish_time_mixer_; uint32_t Mqtt::publish_time_mixer_;
uint32_t Mqtt::publish_time_other_;
uint32_t Mqtt::publish_time_sensor_; uint32_t Mqtt::publish_time_sensor_;
uint8_t Mqtt::mqtt_format_; uint32_t Mqtt::publish_time_other_;
bool Mqtt::mqtt_enabled_; bool Mqtt::mqtt_enabled_;
uint8_t Mqtt::dallas_format_;
uint8_t Mqtt::ha_climate_format_;
bool Mqtt::ha_enabled_;
std::vector<Mqtt::MQTTSubFunction> Mqtt::mqtt_subfunctions_; std::vector<Mqtt::MQTTSubFunction> Mqtt::mqtt_subfunctions_;
@@ -84,7 +86,7 @@ void Mqtt::subscribe(const uint8_t device_type, const std::string & topic, mqtt_
} }
// subscribe to the command topic if it doesn't exist yet // subscribe to the command topic if it doesn't exist yet
void Mqtt::register_command(const uint8_t device_type, const uint8_t device_id, const __FlashStringHelper * cmd, cmdfunction_p cb) { void Mqtt::register_command(const uint8_t device_type, const __FlashStringHelper * cmd, cmdfunction_p cb) {
std::string cmd_topic = EMSdevice::device_type_2_device_name(device_type); std::string cmd_topic = EMSdevice::device_type_2_device_name(device_type);
bool exists = false; bool exists = false;
@@ -149,11 +151,6 @@ void Mqtt::loop() {
EMSESP::publish_device_values(EMSdevice::DeviceType::MIXER); EMSESP::publish_device_values(EMSdevice::DeviceType::MIXER);
} }
if (publish_time_other_ && (currentMillis - last_publish_other_ > publish_time_other_)) {
last_publish_other_ = currentMillis;
EMSESP::publish_other_values();
}
if (currentMillis - last_publish_sensor_ > publish_time_sensor_) { if (currentMillis - last_publish_sensor_ > publish_time_sensor_) {
last_publish_sensor_ = currentMillis; last_publish_sensor_ = currentMillis;
EMSESP::publish_sensor_values(publish_time_sensor_ != 0); EMSESP::publish_sensor_values(publish_time_sensor_ != 0);
@@ -249,7 +246,7 @@ void Mqtt::on_message(const char * topic, const char * payload, size_t len) {
} }
// empty function. It's a command then. Find the command from the json and call it directly. // empty function. It's a command then. Find the command from the json and call it directly.
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc; StaticJsonDocument<EMSESP_JSON_SIZE_SMALL> doc;
DeserializationError error = deserializeJson(doc, message); DeserializationError error = deserializeJson(doc, message);
if (error) { if (error) {
LOG_ERROR(F("MQTT error: payload %s, error %s"), message, error.c_str()); LOG_ERROR(F("MQTT error: payload %s, error %s"), message, error.c_str());
@@ -356,8 +353,10 @@ void Mqtt::start() {
publish_time_sensor_ = mqttSettings.publish_time_sensor * 1000; publish_time_sensor_ = mqttSettings.publish_time_sensor * 1000;
mqtt_qos_ = mqttSettings.mqtt_qos; mqtt_qos_ = mqttSettings.mqtt_qos;
mqtt_retain_ = mqttSettings.mqtt_retain; mqtt_retain_ = mqttSettings.mqtt_retain;
mqtt_format_ = mqttSettings.mqtt_format;
mqtt_enabled_ = mqttSettings.enabled; mqtt_enabled_ = mqttSettings.enabled;
ha_enabled_ = mqttSettings.ha_enabled;
ha_climate_format_ = mqttSettings.ha_climate_format;
dallas_format_ = mqttSettings.dallas_format;
}); });
// if MQTT disabled, quit // if MQTT disabled, quit
@@ -406,6 +405,10 @@ void Mqtt::start() {
// create space for command buffer, to avoid heap memory fragmentation // create space for command buffer, to avoid heap memory fragmentation
mqtt_subfunctions_.reserve(10); mqtt_subfunctions_.reserve(10);
#if defined(EMSESP_STANDALONE)
on_connect(); // simulate an MQTT connection
#endif
} }
void Mqtt::set_publish_time_boiler(uint16_t publish_time) { void Mqtt::set_publish_time_boiler(uint16_t publish_time) {
@@ -455,19 +458,8 @@ bool Mqtt::get_publish_onchange(uint8_t device_type) {
return false; return false;
} }
void Mqtt::set_qos(uint8_t mqtt_qos) { // MQTT onConnect - when an MQTT connect is established
mqtt_qos_ = mqtt_qos; // send out some inital MQTT messages
}
void Mqtt::set_retain(bool mqtt_retain) {
mqtt_retain_ = mqtt_retain;
}
void Mqtt::set_format(uint8_t mqtt_format) {
mqtt_format_ = mqtt_format;
}
// MQTT onConnect - when a connect is established
void Mqtt::on_connect() { void Mqtt::on_connect() {
if (connecting_) { if (connecting_) {
return; return;
@@ -479,7 +471,7 @@ void Mqtt::on_connect() {
// first time to connect // first time to connect
if (connectcount_ == 1) { if (connectcount_ == 1) {
// send info topic appended with the version information as JSON // send info topic appended with the version information as JSON
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc; StaticJsonDocument<EMSESP_JSON_SIZE_SMALL> doc;
doc["event"] = FJSON("start"); doc["event"] = FJSON("start");
doc["version"] = EMSESP_APP_VERSION; doc["version"] = EMSESP_APP_VERSION;
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
@@ -488,9 +480,14 @@ void Mqtt::on_connect() {
publish(F_(info), doc.as<JsonObject>()); publish(F_(info), doc.as<JsonObject>());
// create the EMS-ESP device in HA, which is MQTT retained // create the EMS-ESP device in HA, which is MQTT retained
if (mqtt_format() == Format::HA) { if (ha_enabled()) {
ha_status(); ha_status();
} }
// send initial MQTT messages for some of our services
EMSESP::shower_.send_mqtt_stat(false); // Send shower_activated as false
EMSESP::system_.send_heartbeat(); // send heatbeat
} else { } else {
// we doing a re-connect from a TCP break // we doing a re-connect from a TCP break
// only re-subscribe again to all MQTT topics // only re-subscribe again to all MQTT topics
@@ -504,19 +501,18 @@ void Mqtt::on_connect() {
} }
// Home Assistant Discovery - the main master Device // Home Assistant Discovery - the main master Device
// homeassistant/sensor/ems-esp/status/config // e.g. homeassistant/sensor/ems-esp/status/config
// all the values from the heartbeat payload will be added as attributes to the entity state // all the values from the heartbeat payload will be added as attributes to the entity state
void Mqtt::ha_status() { void Mqtt::ha_status() {
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_HA_CONFIG> doc; StaticJsonDocument<EMSESP_JSON_SIZE_HA_CONFIG> doc;
doc["name"] = FJSON("EMS-ESP status");
doc["uniq_id"] = FJSON("status"); doc["uniq_id"] = FJSON("status");
doc["~"] = System::hostname(); // ems-esp doc["~"] = System::hostname(); // default ems-esp
doc["avty_t"] = FJSON("~/status"); // doc["avty_t"] = FJSON("~/status");
doc["json_attr_t"] = FJSON("~/heartbeat"); doc["json_attr_t"] = FJSON("~/heartbeat");
doc["stat_t"] = FJSON("~/heartbeat"); doc["stat_t"] = FJSON("~/heartbeat");
doc["name"] = FJSON("EMS-ESP status");
doc["val_tpl"] = FJSON("{{value_json['status']}}"); doc["val_tpl"] = FJSON("{{value_json['status']}}");
doc["ic"] = FJSON("mdi:home-thermometer-outline");
JsonObject dev = doc.createNestedObject("dev"); JsonObject dev = doc.createNestedObject("dev");
dev["name"] = FJSON("EMS-ESP"); dev["name"] = FJSON("EMS-ESP");
@@ -526,7 +522,9 @@ void Mqtt::ha_status() {
JsonArray ids = dev.createNestedArray("ids"); JsonArray ids = dev.createNestedArray("ids");
ids.add("ems-esp"); ids.add("ems-esp");
Mqtt::publish_ha(F("homeassistant/sensor/ems-esp/status/config"), doc.as<JsonObject>()); // publish the config payload with retain flag char topic[100];
snprintf_P(topic, sizeof(topic), PSTR("homeassistant/sensor/%s/status/config"), System::hostname().c_str());
Mqtt::publish_ha(topic, doc.as<JsonObject>()); // publish the config payload with retain flag
} }
// add sub or pub task to the queue. // add sub or pub task to the queue.
@@ -560,7 +558,7 @@ std::shared_ptr<const MqttMessage> Mqtt::queue_message(const uint8_t operation,
// add MQTT message to queue, payload is a string // add MQTT message to queue, payload is a string
std::shared_ptr<const MqttMessage> Mqtt::queue_publish_message(const std::string & topic, const std::string & payload, bool retain) { std::shared_ptr<const MqttMessage> Mqtt::queue_publish_message(const std::string & topic, const std::string & payload, bool retain) {
if (!enabled() || !connected()) { if (!enabled() || !connecting_) {
return nullptr; return nullptr;
}; };
return queue_message(Operation::PUBLISH, topic, payload, retain); return queue_message(Operation::PUBLISH, topic, payload, retain);
@@ -625,7 +623,7 @@ void Mqtt::publish_ha(const __FlashStringHelper * topic, const JsonObject & payl
// publish a Home Assistant config topic and payload, with retain flag off. // publish a Home Assistant config topic and payload, with retain flag off.
// for ESP32 its added to the queue, for ESP8266 is sent immediatelty // for ESP32 its added to the queue, for ESP8266 is sent immediatelty
void Mqtt::publish_ha(const std::string & topic, const JsonObject & payload) { void Mqtt::publish_ha(const std::string & topic, const JsonObject & payload) {
if (!enabled() || !payload.size()) { if (!enabled()) {
return; return;
} }
@@ -638,20 +636,14 @@ void Mqtt::publish_ha(const std::string & topic, const JsonObject & payload) {
#if defined(EMSESP_STANDALONE) #if defined(EMSESP_STANDALONE)
LOG_DEBUG(F("Publishing HA topic=%s, payload=%s"), topic.c_str(), payload_text.c_str()); LOG_DEBUG(F("Publishing HA topic=%s, payload=%s"), topic.c_str(), payload_text.c_str());
#else
LOG_DEBUG(F("Publishing HA topic %s"), topic.c_str());
#endif #endif
#if defined(ESP32) #if defined(EMSESP_DEBUG)
bool queued = true; // queue MQTT publish LOG_DEBUG(F("[debug] Publishing HA topic=%s, payload=%s"), topic.c_str(), payload_text.c_str());
#else
bool queued = false; // publish immediately
#endif #endif
// if MQTT is not connected, then we have to queue the msg until the MQTT is online // queue messages if the MQTT connection is not yet established. to ensure we don't miss messages
if (!connected()) { bool queued = !connected();
queued = true; // override
}
if (queued) { if (queued) {
queue_publish_message(topic, payload_text, true); // with retain true queue_publish_message(topic, payload_text, true); // with retain true
@@ -661,6 +653,7 @@ void Mqtt::publish_ha(const std::string & topic, const JsonObject & payload) {
// send immediately and then wait a while // send immediately and then wait a while
if (!mqttClient_->publish(topic.c_str(), 0, true, payload_text.c_str())) { if (!mqttClient_->publish(topic.c_str(), 0, true, payload_text.c_str())) {
LOG_ERROR(F("Failed to publish topic %s"), topic.c_str()); LOG_ERROR(F("Failed to publish topic %s"), topic.c_str());
mqtt_publish_fails_++; // increment failure counter
} }
delay(MQTT_HA_PUBLISH_DELAY); // enough time to send the short message out delay(MQTT_HA_PUBLISH_DELAY); // enough time to send the short message out
@@ -734,103 +727,65 @@ void Mqtt::process_queue() {
mqtt_messages_.pop_front(); // remove the message from the queue mqtt_messages_.pop_front(); // remove the message from the queue
} }
// HA config for a binary_sensor // HA config for a sensor and binary_sensor entity
void Mqtt::register_mqtt_ha_binary_sensor(const __FlashStringHelper * name, const uint8_t device_type, const char * entity) { // entity must match the key/value pair in the *_data topic
if (mqtt_format() != Format::HA) {
return;
}
// StaticJsonDocument<EMSESP_MAX_JSON_SIZE_HA_CONFIG> doc;
DynamicJsonDocument doc(EMSESP_MAX_JSON_SIZE_HA_CONFIG);
doc["name"] = name;
doc["uniq_id"] = entity;
char state_t[50];
snprintf_P(state_t, sizeof(state_t), PSTR("%s/%s"), hostname_.c_str(), entity);
doc["stat_t"] = state_t;
EMSESP::webSettingsService.read([&](WebSettings & settings) {
if (settings.bool_format == BOOL_FORMAT_ONOFF) {
doc[F("payload_on")] = FJSON("on");
doc[F("payload_off")] = FJSON("off");
} else if (settings.bool_format == BOOL_FORMAT_TRUEFALSE) {
doc[F("payload_on")] = FJSON("true");
doc[F("payload_off")] = FJSON("false");
} else {
doc[F("payload_on")] = FJSON("1");
doc[F("payload_off")] = FJSON("0");
}
});
JsonObject dev = doc.createNestedObject("dev");
JsonArray ids = dev.createNestedArray("ids");
char ha_device[40];
snprintf_P(ha_device, sizeof(ha_device), PSTR("ems-esp-%s"), EMSdevice::device_type_2_device_name(device_type).c_str());
ids.add(ha_device);
char topic[MQTT_TOPIC_MAX_SIZE];
snprintf_P(topic, sizeof(topic), PSTR("homeassistant/binary_sensor/ems-esp/%s/config"), entity);
publish_ha(topic, doc.as<JsonObject>());
}
// HA config for a normal 'sensor' type
// entity must match the key/value pair in the _data topic
// some string copying here into chars, it looks messy but does help with heap fragmentation issues // some string copying here into chars, it looks messy but does help with heap fragmentation issues
void Mqtt::register_mqtt_ha_sensor(const char * prefix, void Mqtt::register_mqtt_ha_sensor(uint8_t type, // device value type
const __FlashStringHelper * suffix, const char * prefix,
const __FlashStringHelper * name, const __FlashStringHelper * name,
const uint8_t device_type, const uint8_t device_type,
const char * entity, const __FlashStringHelper * entity,
const __FlashStringHelper * uom, const uint8_t uom,
const __FlashStringHelper * icon) { const __FlashStringHelper * icon) {
if (mqtt_format() != Format::HA) { // ignore if name (fullname) is empty
if (name == nullptr) {
return; return;
} }
// StaticJsonDocument<EMSESP_MAX_JSON_SIZE_HA_CONFIG> doc; // DynamicJsonDocument doc(EMSESP_JSON_SIZE_HA_CONFIG);
DynamicJsonDocument doc(EMSESP_MAX_JSON_SIZE_HA_CONFIG); StaticJsonDocument<EMSESP_JSON_SIZE_HA_CONFIG> doc; // TODO see if this crashes ESP8266?
// create entity by prefixing any given prefix bool have_prefix = ((prefix[0] != '\0') && (device_type != EMSdevice::DeviceType::BOILER));
// create entity by inserting any given prefix
// we ignore the prefix (tag) if BOILER
char new_entity[50]; char new_entity[50];
if (prefix != nullptr) { // special case for boiler - don't use the prefix
snprintf_P(new_entity, sizeof(new_entity), PSTR("%s.%s"), prefix, entity); if (have_prefix) {
snprintf_P(new_entity, sizeof(new_entity), PSTR("%s.%s"), prefix, uuid::read_flash_string(entity).c_str());
} else { } else {
strncpy(new_entity, entity, sizeof(new_entity)); snprintf_P(new_entity, sizeof(new_entity), PSTR("%s"), uuid::read_flash_string(entity).c_str());
} }
// device name
char device_name[50]; char device_name[50];
strncpy(device_name, EMSdevice::device_type_2_device_name(device_type).c_str(), sizeof(device_name)); strncpy(device_name, EMSdevice::device_type_2_device_name(device_type).c_str(), sizeof(device_name));
// build unique identifier, replacing all . with _ as not to break HA // build unique identifier which will be used in the topic
// and replacing all . with _ as not to break HA
std::string uniq(50, '\0'); std::string uniq(50, '\0');
snprintf_P(&uniq[0], uniq.capacity() + 1, PSTR("%s_%s"), device_name, new_entity); snprintf_P(&uniq[0], uniq.capacity() + 1, PSTR("%s_%s"), device_name, new_entity);
std::replace(uniq.begin(), uniq.end(), '.', '_'); std::replace(uniq.begin(), uniq.end(), '.', '_');
// topic // topic
char topic[MQTT_TOPIC_MAX_SIZE]; char topic[MQTT_TOPIC_MAX_SIZE];
snprintf_P(topic, sizeof(topic), PSTR("homeassistant/sensor/ems-esp/%s/config"), uniq.c_str());
// state topic // state topic
// if its a boiler we use the tag
char stat_t[MQTT_TOPIC_MAX_SIZE]; char stat_t[MQTT_TOPIC_MAX_SIZE];
if (suffix != nullptr) { if (device_type == EMSdevice::DeviceType::BOILER) {
snprintf_P(stat_t, sizeof(stat_t), PSTR("%s/%s_data%s"), hostname_.c_str(), device_name, uuid::read_flash_string(suffix).c_str()); snprintf_P(stat_t, sizeof(stat_t), PSTR("%s/%s"), hostname_.c_str(), prefix);
} else { } else {
snprintf_P(stat_t, sizeof(stat_t), PSTR("%s/%s_data"), hostname_.c_str(), device_name); snprintf_P(stat_t, sizeof(stat_t), PSTR("%s/%s_data"), hostname_.c_str(), device_name);
} }
// value template
char val_tpl[50];
snprintf_P(val_tpl, sizeof(val_tpl), PSTR("{{value_json.%s}}"), new_entity);
// ha device // ha device
char ha_device[40]; char ha_device[40];
snprintf_P(ha_device, sizeof(ha_device), PSTR("ems-esp-%s"), device_name); snprintf_P(ha_device, sizeof(ha_device), PSTR("ems-esp-%s"), device_name);
// name // name
char new_name[50]; char new_name[50];
if (prefix != nullptr) { if (have_prefix) {
snprintf_P(new_name, sizeof(new_name), PSTR("%s %s %s"), device_name, prefix, uuid::read_flash_string(name).c_str()); snprintf_P(new_name, sizeof(new_name), PSTR("%s %s %s"), device_name, prefix, uuid::read_flash_string(name).c_str());
} else { } else {
snprintf_P(new_name, sizeof(new_name), PSTR("%s %s"), device_name, uuid::read_flash_string(name).c_str()); snprintf_P(new_name, sizeof(new_name), PSTR("%s %s"), device_name, uuid::read_flash_string(name).c_str());
@@ -839,13 +794,55 @@ void Mqtt::register_mqtt_ha_sensor(const char * prefix,
doc["name"] = new_name; doc["name"] = new_name;
doc["uniq_id"] = uniq; doc["uniq_id"] = uniq;
if (uom != nullptr) {
doc["unit_of_meas"] = uom;
}
doc["stat_t"] = stat_t; doc["stat_t"] = stat_t;
// look at the device value type
if (type != DeviceValueType::BOOL) {
//
// normal HA sensor
//
// topic
snprintf_P(topic, sizeof(topic), PSTR("homeassistant/sensor/%s/%s/config"), System::hostname().c_str(), uniq.c_str());
// value template
char val_tpl[50];
snprintf_P(val_tpl, sizeof(val_tpl), PSTR("{{value_json.%s}}"), new_entity);
doc["val_tpl"] = val_tpl; doc["val_tpl"] = val_tpl;
if (icon != nullptr) {
doc["ic"] = icon; // unit of measure
if (uom != DeviceValueUOM::NONE) {
doc["unit_of_meas"] = EMSdevice::uom_to_string(uom);
}
// if there was no icon supplied, resort to the default one
if (icon == nullptr) {
switch (uom) {
case DeviceValueUOM::DEGREES:
doc["ic"] = F_(icontemperature);
break;
case DeviceValueUOM::PERCENT:
doc["ic"] = F_(iconpercent);
break;
case DeviceValueUOM::NONE:
default:
break;
}
} else {
doc["ic"] = icon; // must be prefixed with mdi:
}
} else {
//
// binary sensor
//
snprintf_P(topic, sizeof(topic), PSTR("homeassistant/binary_sensor/%s/%s/config"), System::hostname().c_str(), uniq.c_str());
// boolean
EMSESP::webSettingsService.read([&](WebSettings & settings) {
char result[10];
doc[F("payload_on")] = Helpers::render_boolean(result, true);
doc[F("payload_off")] = Helpers::render_boolean(result, false);
});
} }
JsonObject dev = doc.createNestedObject("dev"); JsonObject dev = doc.createNestedObject("dev");

View File

@@ -71,14 +71,12 @@ class Mqtt {
void set_publish_time_mixer(uint16_t publish_time); void set_publish_time_mixer(uint16_t publish_time);
void set_publish_time_other(uint16_t publish_time); void set_publish_time_other(uint16_t publish_time);
void set_publish_time_sensor(uint16_t publish_time); void set_publish_time_sensor(uint16_t publish_time);
void set_qos(uint8_t mqtt_qos);
void set_retain(bool mqtt_retain);
void set_format(uint8_t mqtt_format);
bool get_publish_onchange(uint8_t device_type); bool get_publish_onchange(uint8_t device_type);
enum Operation { PUBLISH, SUBSCRIBE }; enum Operation { PUBLISH, SUBSCRIBE };
enum Format : uint8_t { NONE = 0, SINGLE, NESTED, HA }; enum Dallas_Format : uint8_t { SENSORID = 1, NUMBER };
enum HA_Climate_Format : uint8_t { CURRENT = 1, SETPOINT, ZERO };
static constexpr uint8_t MQTT_TOPIC_MAX_SIZE = 128; // note this should really match the user setting in mqttSettings.maxTopicLength static constexpr uint8_t MQTT_TOPIC_MAX_SIZE = 128; // note this should really match the user setting in mqttSettings.maxTopicLength
@@ -100,15 +98,14 @@ class Mqtt {
static void publish_ha(const std::string & topic, const JsonObject & payload); static void publish_ha(const std::string & topic, const JsonObject & payload);
static void publish_ha(const __FlashStringHelper * topic, const JsonObject & payload); static void publish_ha(const __FlashStringHelper * topic, const JsonObject & payload);
static void register_mqtt_ha_binary_sensor(const __FlashStringHelper * name, const uint8_t device_type, const char * entity); static void register_mqtt_ha_sensor(uint8_t type,
static void register_mqtt_ha_sensor(const char * prefix, const char * prefix,
const __FlashStringHelper * suffix,
const __FlashStringHelper * name, const __FlashStringHelper * name,
const uint8_t device_type, const uint8_t device_type,
const char * entity, const __FlashStringHelper * entity,
const __FlashStringHelper * uom, const uint8_t uom,
const __FlashStringHelper * icon); const __FlashStringHelper * icon);
static void register_command(const uint8_t device_type, const uint8_t device_id, const __FlashStringHelper * cmd, cmdfunction_p cb); static void register_command(const uint8_t device_type, const __FlashStringHelper * cmd, cmdfunction_p cb);
static void show_topic_handlers(uuid::console::Shell & shell, const uint8_t device_type); static void show_topic_handlers(uuid::console::Shell & shell, const uint8_t device_type);
static void show_mqtt(uuid::console::Shell & shell); static void show_mqtt(uuid::console::Shell & shell);
@@ -129,6 +126,10 @@ class Mqtt {
#endif #endif
} }
static AsyncMqttClient * client() {
return mqttClient_;
}
static bool enabled() { static bool enabled() {
return mqtt_enabled_; return mqtt_enabled_;
} }
@@ -141,12 +142,36 @@ class Mqtt {
mqtt_publish_fails_ = 0; mqtt_publish_fails_ = 0;
} }
static uint8_t mqtt_format() { static uint8_t ha_climate_format() {
return mqtt_format_; return ha_climate_format_;
} }
static AsyncMqttClient * client() { static uint8_t dallas_format() {
return mqttClient_; return dallas_format_;
}
static bool ha_enabled() {
return ha_enabled_;
}
static void ha_climate_format(uint8_t ha_climate_format) {
ha_climate_format_ = ha_climate_format;
}
static void dallas_format(uint8_t dallas_format) {
dallas_format_ = dallas_format;
}
static void ha_enabled(bool ha_enabled) {
ha_enabled_ = ha_enabled;
}
void set_qos(uint8_t mqtt_qos) {
mqtt_qos_ = mqtt_qos;
}
void set_retain(bool mqtt_retain) {
mqtt_retain_ = mqtt_retain;
} }
private: private:
@@ -232,8 +257,10 @@ class Mqtt {
static uint32_t publish_time_mixer_; static uint32_t publish_time_mixer_;
static uint32_t publish_time_other_; static uint32_t publish_time_other_;
static uint32_t publish_time_sensor_; static uint32_t publish_time_sensor_;
static uint8_t mqtt_format_;
static bool mqtt_enabled_; static bool mqtt_enabled_;
static uint8_t dallas_format_;
static uint8_t ha_climate_format_;
static bool ha_enabled_;
}; };
} // namespace emsesp } // namespace emsesp

View File

@@ -27,10 +27,6 @@ void Shower::start() {
shower_timer_ = settings.shower_timer; shower_timer_ = settings.shower_timer;
shower_alert_ = settings.shower_alert; shower_alert_ = settings.shower_alert;
}); });
if (Mqtt::enabled()) {
send_mqtt_stat(false); // send first MQTT publish
}
} }
void Shower::loop() { void Shower::loop() {
@@ -100,14 +96,26 @@ void Shower::send_mqtt_stat(bool state) {
return; return;
} }
// if we're in HA mode make sure we've first sent out the HA MQTT Discovery config topic
if ((Mqtt::mqtt_format() == Mqtt::Format::HA) && (!ha_config_)) {
Mqtt::register_mqtt_ha_binary_sensor(F("Shower Active"), EMSdevice::DeviceType::BOILER, "shower_active");
ha_config_ = true;
}
char s[7]; char s[7];
Mqtt::publish(F("shower_active"), Helpers::render_boolean(s, state)); Mqtt::publish(F("shower_active"), Helpers::render_boolean(s, state));
// if we're in HA mode make sure we've first sent out the HA MQTT Discovery config topic
if ((Mqtt::ha_enabled()) && (!ha_configdone_)) {
ha_configdone_ = true;
StaticJsonDocument<EMSESP_JSON_SIZE_HA_CONFIG> doc;
doc["name"] = FJSON("Shower Active");
doc["uniq_id"] = FJSON("shower_active");
doc["~"] = System::hostname(); // default ems-esp
doc["stat_t"] = FJSON("~/shower_active");
JsonObject dev = doc.createNestedObject("dev");
JsonArray ids = dev.createNestedArray("ids");
ids.add("ems-esp");
char topic[100];
snprintf_P(topic, sizeof(topic), PSTR("homeassistant/binary_sensor/%s/shower_active/config"), System::hostname().c_str());
Mqtt::publish_ha(topic, doc.as<JsonObject>()); // publish the config payload with retain flag
}
} }
// turn back on the hot water for the shower // turn back on the hot water for the shower
@@ -134,7 +142,7 @@ void Shower::shower_alert_start() {
// Publish shower data // Publish shower data
// returns true if added to MQTT queue went ok // returns true if added to MQTT queue went ok
void Shower::publish_values() { void Shower::publish_values() {
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc; StaticJsonDocument<EMSESP_JSON_SIZE_SMALL> doc;
char s[50]; char s[50];
doc["shower_timer"] = Helpers::render_boolean(s, shower_timer_); doc["shower_timer"] = Helpers::render_boolean(s, shower_timer_);

View File

@@ -37,6 +37,8 @@ class Shower {
void start(); void start();
void loop(); void loop();
void send_mqtt_stat(bool state);
bool shower_alert() const { bool shower_alert() const {
return shower_alert_; return shower_alert_;
} }
@@ -65,11 +67,10 @@ class Shower {
void publish_values(); void publish_values();
void shower_alert_start(); void shower_alert_start();
void shower_alert_stop(); void shower_alert_stop();
void send_mqtt_stat(bool state);
bool shower_timer_; // true if we want to report back on shower times bool shower_timer_; // true if we want to report back on shower times
bool shower_alert_; // true if we want the alert of cold water bool shower_alert_; // true if we want the alert of cold water
bool ha_config_ = false; // for HA MQTT Discovery bool ha_configdone_ = false; // for HA MQTT Discovery
bool shower_on_; bool shower_on_;
uint32_t timer_start_; // ms uint32_t timer_start_; // ms
uint32_t timer_pause_; // ms uint32_t timer_pause_; // ms

View File

@@ -34,7 +34,7 @@ uuid::syslog::SyslogService System::syslog_;
#endif #endif
// init statics // init statics
uint32_t System::heap_start_ = 0; uint32_t System::heap_start_ = 1; // avoid using 0 to divide-by-zero later
bool System::upload_status_ = false; bool System::upload_status_ = false;
bool System::hide_led_ = false; bool System::hide_led_ = false;
uint8_t System::led_gpio_ = 0; uint8_t System::led_gpio_ = 0;
@@ -45,7 +45,7 @@ std::string System::hostname_;
// send on/off to a gpio pin // send on/off to a gpio pin
// value: true = HIGH, false = LOW // value: true = HIGH, false = LOW
// http://ems-esp/api?device=system&cmd=pin&data=1&id=2 // e.g. http://ems-esp/api?device=system&cmd=pin&data=1&id=2
bool System::command_pin(const char * value, const int8_t id) { bool System::command_pin(const char * value, const int8_t id) {
if (id < 0) { if (id < 0) {
return false; return false;
@@ -184,7 +184,7 @@ void System::syslog_init() {
// first call. Sets memory and starts up the UART Serial bridge // first call. Sets memory and starts up the UART Serial bridge
void System::start() { void System::start() {
// set the inital free mem // set the inital free mem
if (heap_start_ == 0) { if (heap_start_ < 2) {
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
heap_start_ = ESP.getFreeHeap(); heap_start_ = ESP.getFreeHeap();
#else #else
@@ -225,7 +225,7 @@ void System::other_init() {
void System::init() { void System::init() {
led_init(); // init LED led_init(); // init LED
other_init(); other_init(); // boolean format and analog setting
syslog_init(); // init SysLog syslog_init(); // init SysLog
@@ -319,7 +319,7 @@ void System::send_heartbeat() {
uint8_t frag_memory = ESP.getHeapFragmentation(); uint8_t frag_memory = ESP.getHeapFragmentation();
#endif #endif
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc; StaticJsonDocument<EMSESP_JSON_SIZE_SMALL> doc;
uint8_t ems_status = EMSESP::bus_status(); uint8_t ems_status = EMSESP::bus_status();
if (ems_status == EMSESP::BUS_STATUS_TX_ERRORS) { if (ems_status == EMSESP::BUS_STATUS_TX_ERRORS) {
@@ -378,10 +378,13 @@ void System::set_led_speed(uint32_t speed) {
led_monitor(); led_monitor();
} }
void System::reset_system_check() {
last_system_check_ = 0; // force the LED to go from fast flash to pulse
send_heartbeat();
}
// check health of system, done every few seconds // check health of system, done every few seconds
void System::system_check() { void System::system_check() {
static uint32_t last_system_check_ = 0;
if (!last_system_check_ || ((uint32_t)(uuid::get_uptime() - last_system_check_) >= SYSTEM_CHECK_FREQUENCY)) { if (!last_system_check_ || ((uint32_t)(uuid::get_uptime() - last_system_check_) >= SYSTEM_CHECK_FREQUENCY)) {
last_system_check_ = uuid::get_uptime(); last_system_check_ = uuid::get_uptime();
@@ -551,11 +554,11 @@ void System::show_system(uuid::console::Shell & shell) {
shell.printfln(F("Syslog: disabled")); shell.printfln(F("Syslog: disabled"));
} else { } else {
shell.printfln(F("Syslog:")); shell.printfln(F("Syslog:"));
shell.print(F_(1space)); shell.print(F(" "));
shell.printfln(F_(host_fmt), !settings.syslog_host.isEmpty() ? settings.syslog_host.c_str() : uuid::read_flash_string(F_(unset)).c_str()); shell.printfln(F_(host_fmt), !settings.syslog_host.isEmpty() ? settings.syslog_host.c_str() : uuid::read_flash_string(F_(unset)).c_str());
shell.print(F_(1space)); shell.print(F(" "));
shell.printfln(F_(log_level_fmt), uuid::log::format_level_lowercase(static_cast<uuid::log::Level>(settings.syslog_level))); shell.printfln(F_(log_level_fmt), uuid::log::format_level_lowercase(static_cast<uuid::log::Level>(settings.syslog_level)));
shell.print(F_(1space)); shell.print(F(" "));
shell.printfln(F_(mark_interval_fmt), settings.syslog_mark_interval); shell.printfln(F_(mark_interval_fmt), settings.syslog_mark_interval);
} }
}); });
@@ -688,18 +691,18 @@ void System::console_commands(Shell & shell, unsigned int context) {
flash_string_vector{F_(set)}, flash_string_vector{F_(set)},
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) { [](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
EMSESP::esp8266React.getWiFiSettingsService()->read([&](WiFiSettings & wifiSettings) { EMSESP::esp8266React.getWiFiSettingsService()->read([&](WiFiSettings & wifiSettings) {
shell.print(F_(1space)); shell.print(F(" "));
shell.printfln(F_(hostname_fmt), shell.printfln(F_(hostname_fmt),
wifiSettings.hostname.isEmpty() ? uuid::read_flash_string(F_(unset)).c_str() wifiSettings.hostname.isEmpty() ? uuid::read_flash_string(F_(unset)).c_str()
: wifiSettings.hostname.c_str()); : wifiSettings.hostname.c_str());
}); });
EMSESP::esp8266React.getWiFiSettingsService()->read([&](WiFiSettings & wifiSettings) { EMSESP::esp8266React.getWiFiSettingsService()->read([&](WiFiSettings & wifiSettings) {
shell.print(F_(1space)); shell.print(F(" "));
shell.printfln(F_(wifi_ssid_fmt), shell.printfln(F_(wifi_ssid_fmt),
wifiSettings.ssid.isEmpty() ? uuid::read_flash_string(F_(unset)).c_str() wifiSettings.ssid.isEmpty() ? uuid::read_flash_string(F_(unset)).c_str()
: wifiSettings.ssid.c_str()); : wifiSettings.ssid.c_str());
shell.print(F_(1space)); shell.print(F(" "));
shell.printfln(F_(wifi_password_fmt), wifiSettings.ssid.isEmpty() ? F_(unset) : F_(asterisks)); shell.printfln(F_(wifi_password_fmt), wifiSettings.ssid.isEmpty() ? F_(unset) : F_(asterisks));
}); });
}); });
@@ -724,7 +727,7 @@ bool System::check_upgrade() {
if (LittleFS.begin()) { if (LittleFS.begin()) {
#if defined(EMSESP_FORCE_SERIAL) #if defined(EMSESP_FORCE_SERIAL)
Serial.begin(115200); Serial.begin(115200);
Serial.println(F("FS is Littlefs")); Serial.println(F("FS is already LittleFS"));
Serial.end(); Serial.end();
#endif #endif
return false; return false;
@@ -753,7 +756,7 @@ bool System::check_upgrade() {
bool failed = false; bool failed = false;
File file; File file;
JsonObject network, general, mqtt, custom_settings; JsonObject network, general, mqtt, custom_settings;
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_LARGE> doc; StaticJsonDocument<EMSESP_JSON_SIZE_LARGE> doc;
// open the system settings: // open the system settings:
// { // {
@@ -816,7 +819,6 @@ bool System::check_upgrade() {
EMSESP::esp8266React.getMqttSettingsService()->update( EMSESP::esp8266React.getMqttSettingsService()->update(
[&](MqttSettings & mqttSettings) { [&](MqttSettings & mqttSettings) {
mqttSettings.host = mqtt["ip"] | FACTORY_MQTT_HOST; mqttSettings.host = mqtt["ip"] | FACTORY_MQTT_HOST;
mqttSettings.mqtt_format = (mqtt["nestedjson"] ? Mqtt::Format::NESTED : Mqtt::Format::SINGLE);
mqttSettings.mqtt_qos = mqtt["qos"] | 0; mqttSettings.mqtt_qos = mqtt["qos"] | 0;
mqttSettings.mqtt_retain = mqtt["retain"]; mqttSettings.mqtt_retain = mqtt["retain"];
mqttSettings.username = mqtt["user"] | ""; mqttSettings.username = mqtt["user"] | "";
@@ -899,26 +901,25 @@ bool System::check_upgrade() {
Serial.end(); Serial.end();
delay(1000); delay(1000);
restart(); restart();
return true; return true; // will never get here
#else #else
return false; return false;
#endif #endif
} }
// export all settings to JSON text // export all settings to JSON text
// http://ems-esp/api?device=system&cmd=settings // e.g. http://ems-esp/api?device=system&cmd=settings
// value and id are ignored // value and id are ignored
bool System::command_settings(const char * value, const int8_t id, JsonObject & json) { bool System::command_settings(const char * value, const int8_t id, JsonObject & json) {
#ifdef EMSESP_STANDALONE #ifdef EMSESP_STANDALONE
json["test"] = "testing system info command"; json["test"] = "testing system info command";
#else #else
EMSESP::esp8266React.getWiFiSettingsService()->read([&](WiFiSettings & settings) { EMSESP::esp8266React.getWiFiSettingsService()->read([&](WiFiSettings & settings) {
char s[7];
JsonObject node = json.createNestedObject("WIFI"); JsonObject node = json.createNestedObject("WIFI");
node["ssid"] = settings.ssid; node["ssid"] = settings.ssid;
// node["password"] = settings.password; // node["password"] = settings.password;
node["hostname"] = settings.hostname; node["hostname"] = settings.hostname;
node["static_ip_config"] = Helpers::render_boolean(s, settings.staticIPConfig); node["static_ip_config"] = settings.staticIPConfig;
JsonUtils::writeIP(node, "local_ip", settings.localIP); JsonUtils::writeIP(node, "local_ip", settings.localIP);
JsonUtils::writeIP(node, "gateway_ip", settings.gatewayIP); JsonUtils::writeIP(node, "gateway_ip", settings.gatewayIP);
JsonUtils::writeIP(node, "subnet_mask", settings.subnetMask); JsonUtils::writeIP(node, "subnet_mask", settings.subnetMask);
@@ -954,7 +955,9 @@ bool System::command_settings(const char * value, const int8_t id, JsonObject &
node["publish_time_mixer"] = settings.publish_time_mixer; node["publish_time_mixer"] = settings.publish_time_mixer;
node["publish_time_other"] = settings.publish_time_other; node["publish_time_other"] = settings.publish_time_other;
node["publish_time_sensor"] = settings.publish_time_sensor; node["publish_time_sensor"] = settings.publish_time_sensor;
node["mqtt_format"] = settings.mqtt_format; node["dallas_format"] = settings.dallas_format;
node["ha_climate_format"] = settings.ha_climate_format;
node["ha_enabled"] = settings.ha_enabled;
node["mqtt_qos"] = settings.mqtt_qos; node["mqtt_qos"] = settings.mqtt_qos;
node["mqtt_retain"] = Helpers::render_boolean(s, settings.mqtt_retain); node["mqtt_retain"] = Helpers::render_boolean(s, settings.mqtt_retain);
}); });
@@ -1004,7 +1007,7 @@ bool System::command_settings(const char * value, const int8_t id, JsonObject &
} }
// export status information including some basic settings // export status information including some basic settings
// http://ems-esp/api?device=system&cmd=info // e.g. http://ems-esp/api?device=system&cmd=info
bool System::command_info(const char * value, const int8_t id, JsonObject & json) { bool System::command_info(const char * value, const int8_t id, JsonObject & json) {
JsonObject node; JsonObject node;
@@ -1028,7 +1031,9 @@ bool System::command_info(const char * value, const int8_t id, JsonObject & json
node["publish_time_mixer"] = settings.publish_time_mixer; node["publish_time_mixer"] = settings.publish_time_mixer;
node["publish_time_other"] = settings.publish_time_other; node["publish_time_other"] = settings.publish_time_other;
node["publish_time_sensor"] = settings.publish_time_sensor; node["publish_time_sensor"] = settings.publish_time_sensor;
node["mqtt_format"] = settings.mqtt_format; node["dallas_format"] = settings.dallas_format;
node["ha_enabled"] = settings.ha_enabled;
node["ha_climate_format"] = settings.ha_climate_format;
node["mqtt_qos"] = settings.mqtt_qos; node["mqtt_qos"] = settings.mqtt_qos;
node["mqtt_retain"] = Helpers::render_boolean(s, settings.mqtt_retain); node["mqtt_retain"] = Helpers::render_boolean(s, settings.mqtt_retain);
}); });
@@ -1095,10 +1100,10 @@ bool System::command_info(const char * value, const int8_t id, JsonObject & json
} }
#if defined(EMSESP_TEST) #if defined(EMSESP_TEST)
// run a test // run a test, e.g. http://ems-esp/api?device=system&cmd=test&data=boiler
// e.g. http://ems-esp/api?device=system&cmd=test&data=boiler
bool System::command_test(const char * value, const int8_t id) { bool System::command_test(const char * value, const int8_t id) {
return (Test::run_test(value, id)); Test::run_test(value, id);
return true;
} }
#endif #endif

View File

@@ -63,7 +63,7 @@ class System {
static void upload_status(bool in_progress); static void upload_status(bool in_progress);
static bool upload_status(); static bool upload_status();
static void show_mem(const char * note); static void show_mem(const char * note);
void reset_system_check();
static void init(); static void init();
static void led_init(); static void led_init();
static void syslog_init(); static void syslog_init();
@@ -109,6 +109,7 @@ class System {
bool system_healthy_ = false; bool system_healthy_ = false;
uint32_t led_flash_speed_ = LED_WARNING_BLINK_FAST; // default boot flashes quickly uint32_t led_flash_speed_ = LED_WARNING_BLINK_FAST; // default boot flashes quickly
uint32_t last_heartbeat_ = 0; uint32_t last_heartbeat_ = 0;
uint32_t last_system_check_ = 0;
static bool upload_status_; // true if we're in the middle of a OTA firmware upload static bool upload_status_; // true if we're in the middle of a OTA firmware upload
static uint32_t heap_start_; static uint32_t heap_start_;

View File

@@ -73,6 +73,7 @@ Telegram::Telegram(const uint8_t operation,
, offset(offset) , offset(offset)
, message_length(message_length) { , message_length(message_length) {
// copy complete telegram data over, preventing buffer overflow // copy complete telegram data over, preventing buffer overflow
// faster than using std::move()
for (uint8_t i = 0; ((i < message_length) && (i < EMS_MAX_TELEGRAM_MESSAGE_LENGTH)); i++) { for (uint8_t i = 0; ((i < message_length) && (i < EMS_MAX_TELEGRAM_MESSAGE_LENGTH)); i++) {
message_data[i] = data[i]; message_data[i] = data[i];
} }

View File

@@ -40,8 +40,6 @@ static constexpr uint8_t EMS_VALUE_BOOL = 0xFF; // used to mark that somethi
static constexpr uint8_t EMS_VALUE_BOOL_OFF = 0x00; // boolean false static constexpr uint8_t EMS_VALUE_BOOL_OFF = 0x00; // boolean false
static constexpr uint8_t EMS_VALUE_BOOL_ON = 0x01; // boolean true. True can be 0x01 or 0xFF sometimes static constexpr uint8_t EMS_VALUE_BOOL_ON = 0x01; // boolean true. True can be 0x01 or 0xFF sometimes
static constexpr uint8_t EMS_VALUE_TIME = 0xFD; // for converting uint32 to time strings
static constexpr uint8_t EMS_VALUE_BOOL_NOTSET = 0xFE; // random number for booleans, that's not 0, 1 or FF static constexpr uint8_t EMS_VALUE_BOOL_NOTSET = 0xFE; // random number for booleans, that's not 0, 1 or FF
static constexpr uint8_t EMS_VALUE_UINT_NOTSET = 0xFF; // for 8-bit unsigned ints/bytes static constexpr uint8_t EMS_VALUE_UINT_NOTSET = 0xFF; // for 8-bit unsigned ints/bytes
static constexpr int8_t EMS_VALUE_INT_NOTSET = 0x7F; // for signed 8-bit ints/bytes static constexpr int8_t EMS_VALUE_INT_NOTSET = 0x7F; // for signed 8-bit ints/bytes

View File

@@ -105,8 +105,8 @@ bool Test::run_test(const char * command, int8_t id) {
// add controller // add controller
add_device(0x09, 114); add_device(0x09, 114);
add_device(0x28, 160); // MM100, WWC add_device(0x28, 160); // MM100
add_device(0x29, 161); // MM200, WWC add_device(0x29, 161); // MM200
add_device(0x20, 160); // MM100 add_device(0x20, 160); // MM100
// WWC1 on 0x29 // WWC1 on 0x29
@@ -115,6 +115,9 @@ bool Test::run_test(const char * command, int8_t id) {
// WWC2 on 0x28 // WWC2 on 0x28
uart_telegram({0xA8, 0x00, 0xFF, 0x00, 0x02, 0x31, 0x02, 0x35, 0x00, 0x3C, 0x00, 0x3C, 0x3C, 0x46, 0x02, 0x03, 0x03, 0x00, 0x3C}); uart_telegram({0xA8, 0x00, 0xFF, 0x00, 0x02, 0x31, 0x02, 0x35, 0x00, 0x3C, 0x00, 0x3C, 0x3C, 0x46, 0x02, 0x03, 0x03, 0x00, 0x3C});
// HC1 on 0x20
uart_telegram({0xA0, 00, 0xFF, 00, 01, 0xD7, 00, 00, 00, 0x80, 00, 00, 00, 00, 03, 0xC5});
return true; return true;
} }
@@ -149,28 +152,26 @@ bool Test::run_test(const char * command, int8_t id) {
if (strcmp(command, "solar") == 0) { if (strcmp(command, "solar") == 0) {
EMSESP::logger().info(F("Testing solar...")); EMSESP::logger().info(F("Testing solar..."));
EMSESP::rxservice_.ems_mask(EMSbus::EMS_MASK_BUDERUS);
add_device(0x30, 163); // SM100 add_device(0x30, 163); // SM100
// SM100Monitor - type 0x0362 EMS+ - for SM100 and SM200 // SM100Monitor - type 0x0362 EMS+ - for SM100 and SM200
// B0 0B FF 00 02 62 00 44 02 7A 80 00 80 00 80 00 80 00 80 00 80 00 00 7C 80 00 80 00 80 00 80 uart_telegram({0xB0, 0x0B, 0xFF, 00, 0x02, 0x62, 00, 0x44, 0x02, 0x7A, 0x80, 00, 0x80, 0x00, 0x80, 00,
rx_telegram({0xB0, 0x0B, 0xFF, 00, 0x02, 0x62, 00, 0x44, 0x02, 0x7A, 0x80, 00, 0x80, 0x00, 0x80, 00,
0x80, 00, 0x80, 00, 0x80, 00, 00, 0x7C, 0x80, 00, 0x80, 00, 0x80, 00, 0x80}); 0x80, 00, 0x80, 00, 0x80, 00, 00, 0x7C, 0x80, 00, 0x80, 00, 0x80, 00, 0x80});
rx_telegram({0xB0, 0x0B, 0xFF, 0x00, 0x02, 0x62, 0x01, 0x44, 0x03, 0x30, 0x80, 00, 0x80, 00, 0x80, 00, uart_telegram({0xB0, 0x0B, 0xFF, 0x00, 0x02, 0x62, 0x01, 0x44, 0x03, 0x30, 0x80, 00, 0x80, 00, 0x80, 00,
0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 0x33}); 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 0x33});
rx_telegram({0xB0, 00, 0xFF, 0x18, 02, 0x62, 0x80, 00, 0xB8}); uart_telegram({0xB0, 00, 0xFF, 0x18, 02, 0x62, 0x80, 00, 0xB8});
EMSESP::send_raw_telegram("B0 00 FF 18 02 62 80 00 B8"); uart_telegram("30 00 FF 00 02 64 00 00 00 04 00 00 FF 00 00 1E 0B 09 64 00 00 00 00"); // SM100 modulation
return true; return true;
} }
if (strcmp(command, "heatpump") == 0) { if (strcmp(command, "heatpump") == 0) {
EMSESP::logger().info(F("Testing heatpump...")); EMSESP::logger().info(F("Testing heatpump..."));
EMSESP::rxservice_.ems_mask(EMSbus::EMS_MASK_BUDERUS);
add_device(0x38, 200); // Enviline module add_device(0x38, 200); // Enviline module
add_device(0x10, 192); // FW120 thermostat add_device(0x10, 192); // FW120 thermostat
@@ -189,13 +190,12 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd) {
// switch to su // switch to su
shell.add_flags(CommandFlags::ADMIN); shell.add_flags(CommandFlags::ADMIN);
// change MQTT format // init stuff
EMSESP::esp8266React.getMqttSettingsService()->updateWithoutPropagation([&](MqttSettings & mqttSettings) { Mqtt::ha_enabled(true);
// mqttSettings.mqtt_format = Mqtt::Format::SINGLE; Mqtt::dallas_format(1);
// mqttSettings.mqtt_format = Mqtt::Format::NESTED; Mqtt::ha_climate_format(1);
mqttSettings.mqtt_format = Mqtt::Format::HA; EMSESP::rxservice_.ems_mask(EMSbus::EMS_MASK_BUDERUS);
return StateUpdateResult::CHANGED; emsesp::EMSESP::watch(EMSESP::Watch::WATCH_RAW); // raw
});
std::string command(20, '\0'); std::string command(20, '\0');
if ((cmd.empty()) || (cmd == "default")) { if ((cmd.empty()) || (cmd == "default")) {
@@ -273,8 +273,6 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd) {
if (command == "devices") { if (command == "devices") {
shell.printfln(F("Testing devices...")); shell.printfln(F("Testing devices..."));
EMSESP::rxservice_.ems_mask(EMSbus::EMS_MASK_BUDERUS); // this is important otherwise nothing will be picked up!
// A fake response - UBADevices(0x07) // A fake response - UBADevices(0x07)
rx_telegram({0x08, 0x00, 0x07, 0x00, 0x0B, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}); rx_telegram({0x08, 0x00, 0x07, 0x00, 0x0B, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
} }
@@ -355,19 +353,58 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd) {
uart_telegram({0x98, 0x00, 0x06, 0x00, 0x00, 0x03, 0x04, 0x0C, 0x02, 0x33, 0x06, 00, 00, 00, 00, 00, 00}); uart_telegram({0x98, 0x00, 0x06, 0x00, 0x00, 0x03, 0x04, 0x0C, 0x02, 0x33, 0x06, 00, 00, 00, 00, 00, 00});
shell.invoke_command("show"); shell.invoke_command("show");
shell.invoke_command("call boiler info");
StaticJsonDocument<500> doc; // test call
JsonObject root = doc.to<JsonObject>(); DynamicJsonDocument doc(EMSESP_JSON_SIZE_XLARGE_DYN);
EMSESP::device_info_web(2, root); // show thermostat. use 1 for boiler JsonObject json = doc.to<JsonObject>();
serializeJsonPretty(doc, shell); (void)emsesp::Command::call(EMSdevice::DeviceType::BOILER, "info", nullptr, -1, json);
// bool has_data = emsesp::Command::call(EMSdevice::DeviceType::SYSTEM, "test", "boiler", -1, json);
#if defined(EMSESP_STANDALONE)
Serial.print(COLOR_BRIGHT_MAGENTA);
if (json.size() != 0) {
serializeJson(doc, Serial);
}
shell.println(); shell.println();
Serial.print(COLOR_RESET);
#endif
for (const auto & emsdevice : EMSESP::emsdevices) {
if (emsdevice) {
if (emsdevice->unique_id() == 1) {
DynamicJsonDocument doc(EMSESP_JSON_SIZE_XLARGE_DYN);
JsonObject root = doc.to<JsonObject>();
emsdevice->generate_values_json_web(root);
#if defined(EMSESP_STANDALONE)
Serial.print(COLOR_BRIGHT_MAGENTA);
Serial.print("memoryUsage=");
Serial.print(doc.memoryUsage());
Serial.println();
Serial.print("measureMsgPack=");
Serial.print(measureMsgPack(doc));
Serial.println();
Serial.print("measureJson=");
Serial.print(measureJson(doc));
Serial.println();
serializeJson(doc, Serial);
Serial.print(COLOR_RESET);
Serial.println();
#endif
}
}
}
return;
} }
if (command == "boiler") { if (command == "boiler") {
shell.printfln(F("Testing boiler...")); shell.printfln(F("Testing boiler..."));
run_test("boiler"); run_test("boiler");
shell.invoke_command("show"); shell.invoke_command("show");
// shell.invoke_command("call boiler info"); shell.invoke_command("call boiler info");
shell.invoke_command("call system publish");
shell.invoke_command("show mqtt");
} }
if (command == "fr120") { if (command == "fr120") {
@@ -408,11 +445,14 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd) {
if (command == "solar") { if (command == "solar") {
shell.printfln(F("Testing Solar")); shell.printfln(F("Testing Solar"));
run_test("solar"); run_test("solar");
uart_telegram("30 00 FF 0A 02 6A 04"); // SM100 pump on 1
uart_telegram("30 00 FF 00 02 64 00 00 00 04 00 00 FF 00 00 1E 0B 09 64 00 00 00 00"); // SM100 modulation uart_telegram("30 00 FF 0A 02 6A 04"); // SM100 pump on (1)
EMSESP::show_device_values(shell); EMSESP::show_device_values(shell);
uart_telegram("30 00 FF 0A 02 6A 03"); // SM100 pump off 0 uart_telegram("30 00 FF 0A 02 6A 03"); // SM100 pump off (0)
EMSESP::show_device_values(shell); EMSESP::show_device_values(shell);
shell.invoke_command("call system publish");
// EMSESP::send_raw_telegram("B0 00 FF 18 02 62 80 00 B8");
} }
if (command == "heatpump") { if (command == "heatpump") {
@@ -425,8 +465,6 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd) {
if (command == "solar200") { if (command == "solar200") {
shell.printfln(F("Testing Solar SM200")); shell.printfln(F("Testing Solar SM200"));
EMSESP::rxservice_.ems_mask(EMSbus::EMS_MASK_BUDERUS);
add_device(0x30, 164); // SM200 add_device(0x30, 164); // SM200
// SM100Monitor - type 0x0362 EMS+ - for SM100 and SM200 // SM100Monitor - type 0x0362 EMS+ - for SM100 and SM200
@@ -452,10 +490,6 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd) {
if (command == "km") { if (command == "km") {
shell.printfln(F("Testing KM200 Gateway")); shell.printfln(F("Testing KM200 Gateway"));
emsesp::EMSESP::watch(EMSESP::Watch::WATCH_RAW); // raw
EMSESP::rxservice_.ems_mask(EMSbus::EMS_MASK_BUDERUS);
add_device(0x10, 158); // RC300 add_device(0x10, 158); // RC300
add_device(0x48, 189); // KM200 add_device(0x48, 189); // KM200
@@ -514,10 +548,6 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd) {
if (command == "cr100") { if (command == "cr100") {
shell.printfln(F("Testing CR100")); shell.printfln(F("Testing CR100"));
emsesp::EMSESP::watch(EMSESP::Watch::WATCH_RAW); // raw
EMSESP::rxservice_.ems_mask(EMSbus::EMS_MASK_HT3); // switch to junkers
add_device(0x18, 157); // Bosch CR100 - https://github.com/proddy/EMS-ESP/issues/355 add_device(0x18, 157); // Bosch CR100 - https://github.com/proddy/EMS-ESP/issues/355
// RCPLUSStatusMessage_HC1(0x01A5) // RCPLUSStatusMessage_HC1(0x01A5)
@@ -822,16 +852,6 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd) {
if (command == "mixer") { if (command == "mixer") {
shell.printfln(F("Testing Mixer...")); shell.printfln(F("Testing Mixer..."));
// change MQTT format
EMSESP::esp8266React.getMqttSettingsService()->updateWithoutPropagation([&](MqttSettings & mqttSettings) {
// mqttSettings.mqtt_format = Mqtt::Format::SINGLE;
// mqttSettings.mqtt_format = Mqtt::Format::NESTED;
mqttSettings.mqtt_format = Mqtt::Format::HA;
return StateUpdateResult::CHANGED;
});
EMSESP::rxservice_.ems_mask(EMSbus::EMS_MASK_BUDERUS);
run_test("mixer"); run_test("mixer");
// check for error "No telegram type handler found for ID 0x255 (src 0x20)" // check for error "No telegram type handler found for ID 0x255 (src 0x20)"

View File

@@ -35,10 +35,16 @@
#include "telegram.h" #include "telegram.h"
#include "mqtt.h" #include "mqtt.h"
#include "emsesp.h" #include "emsesp.h"
#include "command.h"
namespace emsesp { namespace emsesp {
#define EMSESP_TEST_DEFAULT "boiler" // #define EMSESP_TEST_DEFAULT "thermostat"
// #define EMSESP_TEST_DEFAULT "solar"
// #define EMSESP_TEST_DEFAULT "mixer"
// #define EMSESP_TEST_DEFAULT "web"
#define EMSESP_TEST_DEFAULT "general"
// #define EMSESP_TEST_DEFAULT "boiler"
class Test { class Test {
public: public:

View File

@@ -186,6 +186,9 @@ uint16_t EMSuart::transmit(const uint8_t * buf, const uint8_t len) {
if (len == 0 || len >= EMS_MAXBUFFERSIZE) { if (len == 0 || len >= EMS_MAXBUFFERSIZE) {
return EMS_TX_STATUS_ERR; return EMS_TX_STATUS_ERR;
} }
if (tx_mode_ == 0) {
return EMS_TX_STATUS_OK;
}
if (tx_mode_ > 5) { // timer controlled modes if (tx_mode_ > 5) { // timer controlled modes
for (uint8_t i = 0; i < len; i++) { for (uint8_t i = 0; i < len; i++) {

View File

@@ -41,7 +41,6 @@ bool EMSuart::sending_ = false;
// Important: must not use ICACHE_FLASH_ATTR // Important: must not use ICACHE_FLASH_ATTR
// //
void ICACHE_RAM_ATTR EMSuart::emsuart_rx_intr_handler(void * para) { void ICACHE_RAM_ATTR EMSuart::emsuart_rx_intr_handler(void * para) {
if (USIS(EMSUART_UART) & ((1 << UIBD))) { // BREAK detection = End of EMS data block if (USIS(EMSUART_UART) & ((1 << UIBD))) { // BREAK detection = End of EMS data block
USC0(EMSUART_UART) &= ~(1 << UCBRK); // reset tx-brk USC0(EMSUART_UART) &= ~(1 << UCBRK); // reset tx-brk
if (sending_) { // irq tx_mode is interrupted by <brk>, should never happen if (sending_) { // irq tx_mode is interrupted by <brk>, should never happen
@@ -233,6 +232,9 @@ uint16_t ICACHE_FLASH_ATTR EMSuart::transmit(uint8_t * buf, uint8_t len) {
if (len == 0 || len >= EMS_MAXBUFFERSIZE) { if (len == 0 || len >= EMS_MAXBUFFERSIZE) {
return EMS_TX_STATUS_ERR; // nothing or to much to send return EMS_TX_STATUS_ERR; // nothing or to much to send
} }
if (tx_mode_ == 0) {
return EMS_TX_STATUS_OK;
}
// timer controlled modes with extra delay // timer controlled modes with extra delay
if (tx_mode_ >= 5) { if (tx_mode_ >= 5) {

View File

@@ -1 +1 @@
#define EMSESP_APP_VERSION "2.1.1b6" #define EMSESP_APP_VERSION "2.3.0b0"