mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2025-12-06 15:59:52 +03:00
refactor device value rendering (to Web, Console or MQTT) to base class #632
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)} ({data.rx_quality}%)
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell >
|
<TableCell >
|
||||||
Rx line quality
|
# Telegrams Sent
|
||||||
</TableCell>
|
|
||||||
<TableCell align="right">{data.rx_quality} %
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell >
|
|
||||||
Sent telegrams
|
|
||||||
</TableCell >
|
</TableCell >
|
||||||
<TableCell align="right">{formatNumber(data.tx_sent)}
|
<TableCell align="right">{formatNumber(data.tx_sent)} ({data.tx_quality}%)
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell >
|
|
||||||
Tx line quality
|
|
||||||
</TableCell>
|
|
||||||
<TableCell align="right">{data.tx_quality} %
|
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableBody>
|
</TableBody>
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
71
lib/uuid-common/src/compare_flash_string.cpp
Normal file
71
lib/uuid-common/src/compare_flash_string.cpp
Normal 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
|
||||||
@@ -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.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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
@@ -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);
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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_;
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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_;
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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_;
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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_;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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 %
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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
@@ -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);
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
119
src/emsdevice.h
119
src/emsdevice.h
@@ -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
|
||||||
|
|||||||
167
src/emsesp.cpp
167
src/emsesp.cpp
@@ -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
|
||||||
|
|||||||
27
src/emsesp.h
27
src/emsesp.h
@@ -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_;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
216
src/locale_EN.h
216
src/locale_EN.h
@@ -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")
|
|
||||||
|
|||||||
221
src/mqtt.cpp
221
src/mqtt.cpp
@@ -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");
|
||||||
|
|||||||
57
src/mqtt.h
57
src/mqtt.h
@@ -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
|
||||||
|
|||||||
@@ -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_);
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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_;
|
||||||
|
|||||||
@@ -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];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)"
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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++) {
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
#define EMSESP_APP_VERSION "2.1.1b6"
|
#define EMSESP_APP_VERSION "2.3.0b0"
|
||||||
|
|||||||
Reference in New Issue
Block a user