mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2025-12-06 07:49:52 +03:00
Compare commits
43 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0f6ad533f6 | ||
|
|
4ae68fc3fd | ||
|
|
b97a9e3e5b | ||
|
|
e79e104837 | ||
|
|
7851e83162 | ||
|
|
389f0bd2e8 | ||
|
|
a4bbbb460a | ||
|
|
d01697a065 | ||
|
|
a027d17a7d | ||
|
|
a27787d0d5 | ||
|
|
24baf844fd | ||
|
|
6f125b7fbb | ||
|
|
51b00cb280 | ||
|
|
31ffa8483e | ||
|
|
e7cbd97662 | ||
|
|
0b0ca1efd1 | ||
|
|
4518396833 | ||
|
|
8de6448b74 | ||
|
|
6f1b65e70b | ||
|
|
b93890440a | ||
|
|
7cee4916a2 | ||
|
|
3e7b9a7dd9 | ||
|
|
069c63b55a | ||
|
|
e9bd1d4b81 | ||
|
|
1fa1ee5b24 | ||
|
|
8800b88f62 | ||
|
|
fd6df7279b | ||
|
|
0dceb25569 | ||
|
|
96d5324945 | ||
|
|
971df73f13 | ||
|
|
24a4cb85ff | ||
|
|
7f9582d01a | ||
|
|
dfa5e23e90 | ||
|
|
016e18002c | ||
|
|
e0b89ae3ed | ||
|
|
daad2ffe6c | ||
|
|
a9caadaf5e | ||
|
|
0d8d750e46 | ||
|
|
7a394c8e89 | ||
|
|
bcf83616f8 | ||
|
|
32bf13ca9a | ||
|
|
608500e417 | ||
|
|
bfe498758c |
@@ -1,19 +1,20 @@
|
||||
# Changelog
|
||||
|
||||
# [3.6.0]
|
||||
|
||||
## **IMPORTANT! BREAKING CHANGES**
|
||||
|
||||
-
|
||||
# [3.5.1]
|
||||
|
||||
## Added
|
||||
|
||||
-
|
||||
- Detect old Tado thermostat, device-id 0x19, no entities
|
||||
- Some more HM200 entities [#500](https://github.com/emsesp/EMS-ESP32/issues/500)
|
||||
- Add entity to force heating off (for systems without thermostat) [#951](https://github.com/emsesp/EMS-ESP32/issues/951)
|
||||
|
||||
## Fixed
|
||||
|
||||
-
|
||||
- HA-discovery for analog sensor commands [#1035](https://github.com/emsesp/EMS-ESP32/issues/1035)
|
||||
|
||||
## Changed
|
||||
|
||||
-
|
||||
- Use byte 0 for detection RC30 active heatingcircuit [#786](https://github.com/emsesp/EMS-ESP32/issues/786)
|
||||
- Write repeated selflowtemp if tx-queue is empty without verify [#954](https://github.com/emsesp/EMS-ESP32/issues/954)
|
||||
- HA discovery recreate after disconnect by device [#1067](https://github.com/emsesp/EMS-ESP32/issues/1067)
|
||||
- File upload: check flash size (overflow) instead of filesize
|
||||
|
||||
14038
interface/package-lock.json
generated
14038
interface/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -8,15 +8,15 @@
|
||||
"@emotion/styled": "^11.10.6",
|
||||
"@msgpack/msgpack": "^2.8.0",
|
||||
"@mui/icons-material": "^5.11.9",
|
||||
"@mui/material": "^5.11.9",
|
||||
"@table-library/react-table-library": "4.0.25",
|
||||
"@mui/material": "^5.11.10",
|
||||
"@table-library/react-table-library": "4.0.26",
|
||||
"@types/lodash": "^4.14.191",
|
||||
"@types/node": "^18.14.0",
|
||||
"@types/react": "^18.0.28",
|
||||
"@types/react-dom": "^18.0.11",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"async-validator": "^4.2.5",
|
||||
"axios": "^1.3.3",
|
||||
"axios": "^1.3.4",
|
||||
"jwt-decode": "^3.1.2",
|
||||
"lodash": "^4.17.21",
|
||||
"notistack": "^2.0.8",
|
||||
|
||||
@@ -73,7 +73,6 @@ const SystemLog: FC = () => {
|
||||
});
|
||||
|
||||
const [errorMessage, setErrorMessage] = useState<string>();
|
||||
const [reconnectTimeout, setReconnectTimeout] = useState<NodeJS.Timeout>();
|
||||
const [logEntries, setLogEntries] = useState<LogEntries>({ events: [] });
|
||||
const [lastIndex, setLastIndex] = useState<number>(0);
|
||||
|
||||
@@ -162,7 +161,7 @@ const SystemLog: FC = () => {
|
||||
|
||||
const fetchLog = useCallback(async () => {
|
||||
try {
|
||||
setLogEntries((await SystemApi.readLogEntries()).data);
|
||||
await SystemApi.readLogEntries();
|
||||
} catch (error) {
|
||||
setErrorMessage(extractErrorMessage(error, LL.PROBLEM_LOADING()));
|
||||
}
|
||||
@@ -176,20 +175,14 @@ const SystemLog: FC = () => {
|
||||
const es = new EventSource(addAccessTokenParameter(LOG_EVENTSOURCE_URL));
|
||||
es.onmessage = onMessage;
|
||||
es.onerror = () => {
|
||||
if (reconnectTimeout) {
|
||||
es.close();
|
||||
setReconnectTimeout(setTimeout(reloadPage, 1000));
|
||||
}
|
||||
es.close();
|
||||
reloadPage();
|
||||
};
|
||||
|
||||
return () => {
|
||||
es.close();
|
||||
if (reconnectTimeout) {
|
||||
clearTimeout(reconnectTimeout);
|
||||
}
|
||||
};
|
||||
// eslint-disable-next-line
|
||||
}, [reconnectTimeout]);
|
||||
}, []);
|
||||
|
||||
const content = () => {
|
||||
if (!data) {
|
||||
|
||||
@@ -127,6 +127,7 @@ const de: Translation = {
|
||||
BYPASS_TOKEN: 'Zugriffstoken-Autorisierung bei API-Aufrufen umgehen',
|
||||
READONLY: 'Nur-Lese-Modus aktivieren (blockiert alle ausgehenden EMS Tx Write-Befehle)',
|
||||
UNDERCLOCK_CPU: 'CPU-Geschwindigkeit untertakten',
|
||||
HEATINGOFF: 'Boiler Start mit Heizung ausgeschaltet',
|
||||
ENABLE_SHOWER_TIMER: 'Duschtimer aktivieren',
|
||||
ENABLE_SHOWER_ALERT: 'Duschalarm aktivieren',
|
||||
TRIGGER_TIME: 'Auslösezeit',
|
||||
|
||||
@@ -127,6 +127,7 @@ const en: Translation = {
|
||||
BYPASS_TOKEN: 'Bypass Access Token authorization on API calls',
|
||||
READONLY: 'Enable read-only mode (blocks all outgoing EMS Tx Write commands)',
|
||||
UNDERCLOCK_CPU: 'Underclock CPU speed',
|
||||
HEATINGOFF: 'Start boiler with forced heating off',
|
||||
ENABLE_SHOWER_TIMER: 'Enable Shower Timer',
|
||||
ENABLE_SHOWER_ALERT: 'Enable Shower Alert',
|
||||
TRIGGER_TIME: 'Trigger Time',
|
||||
|
||||
@@ -127,6 +127,7 @@ const fr: Translation = {
|
||||
BYPASS_TOKEN: 'Contourner l\'autorisation du jeton d\'accès sur les appels API',
|
||||
READONLY: 'Activer le mode lecture uniquement (bloque toutes les commandes EMS sortantes en écriture Tx)',
|
||||
UNDERCLOCK_CPU: 'Underclock du CPU',
|
||||
HEATINGOFF: 'Start boiler with forced heating off',
|
||||
ENABLE_SHOWER_TIMER: 'Activer la minuterie de la douche',
|
||||
ENABLE_SHOWER_ALERT: 'Activer les alertes de durée de douche',
|
||||
TRIGGER_TIME: 'Durée avant déclenchement',
|
||||
|
||||
@@ -127,6 +127,7 @@ const nl: Translation = {
|
||||
BYPASS_TOKEN: 'API Access Token authenticatie uitschakelen',
|
||||
READONLY: 'Activeer read-only modus (blokkeert alle outgaande EMS Tx schrijf commandos)',
|
||||
UNDERCLOCK_CPU: 'Underclock CPU snelheid',
|
||||
HEATINGOFF: 'Start boiler with forced heating off',
|
||||
ENABLE_SHOWER_TIMER: 'Activeer Douche Timer (tijdmeting)',
|
||||
ENABLE_SHOWER_ALERT: 'Activeer Douchemelding',
|
||||
TRIGGER_TIME: 'Trigger tijd',
|
||||
|
||||
@@ -127,6 +127,7 @@ const no: Translation = {
|
||||
BYPASS_TOKEN: 'Utelat Aksess Token authorisering av API kall',
|
||||
READONLY: 'Aktiver read-only modus (blokker all EMS Tx Skriving)',
|
||||
UNDERCLOCK_CPU: 'Underklokking av prosessorhastighet',
|
||||
HEATINGOFF: 'Start boiler with forced heating off',
|
||||
ENABLE_SHOWER_TIMER: 'Aktiver Dusjtimer',
|
||||
ENABLE_SHOWER_ALERT: 'Aktiver Dusj-varsling',
|
||||
TRIGGER_TIME: 'Aktiveringstid',
|
||||
|
||||
@@ -127,6 +127,7 @@ const pl: BaseTranslation = {
|
||||
BYPASS_TOKEN: 'Pomiń autoryzację tokenem w wywołaniach API',
|
||||
READONLY: 'Tryb pracy "tylko do odczytu" (blokuje wszystkie komendy zapisu na magistralę EMS)',
|
||||
UNDERCLOCK_CPU: 'Obniż taktowanie CPU',
|
||||
HEATINGOFF: 'Start boiler with forced heating off',
|
||||
ENABLE_SHOWER_TIMER: 'Aktywuj minutnik prysznica',
|
||||
ENABLE_SHOWER_ALERT: 'Aktywuj alarm prysznica',
|
||||
TRIGGER_TIME: 'Wyzwalaj po czasie',
|
||||
@@ -157,7 +158,7 @@ const pl: BaseTranslation = {
|
||||
CUSTOMIZATIONS_HELP_3: 'zablokuj akcje zapisu',
|
||||
CUSTOMIZATIONS_HELP_4: 'wyklucz z MQTT i API',
|
||||
CUSTOMIZATIONS_HELP_5: 'ukryj na pulpicie',
|
||||
CUSTOMIZATIONS_HELP_6: 'remove from memory',
|
||||
CUSTOMIZATIONS_HELP_6: 'usuń z pamięci',
|
||||
SELECT_DEVICE: 'wybierz urządzenie',
|
||||
SET_ALL: 'Ustaw wszystko jako',
|
||||
OPTIONS: 'Opcje',
|
||||
@@ -202,7 +203,7 @@ const pl: BaseTranslation = {
|
||||
CPU_FREQ: 'Taktowanie CPU',
|
||||
HEAP: 'HEAP (wolne / maksymalny przydział)',
|
||||
PSRAM: 'PSRAM (rozmiar / wolne)',
|
||||
FLASH: 'Flash (rozmiar / taktowanie)',
|
||||
FLASH: 'FLASH (rozmiar / taktowanie)',
|
||||
APPSIZE: 'Aplikacja (wykorzystane / wolne)',
|
||||
FILESYSTEM: 'System plików (wykorzystane / wolne)',
|
||||
BUFFER_SIZE: 'Maksymalna pojemność bufora (ilość wpisów)',
|
||||
|
||||
@@ -127,6 +127,7 @@ const sv: Translation = {
|
||||
BYPASS_TOKEN: 'Inaktivera Token-autensiering för API-anrop',
|
||||
READONLY: 'Aktivera read-only (blockerar alla utgående skrivkommandon mot EMS-bussen)',
|
||||
UNDERCLOCK_CPU: 'Nedklocka Processorhastighet',
|
||||
HEATINGOFF: 'Start boiler with forced heating off',
|
||||
ENABLE_SHOWER_TIMER: 'Aktivera Dusch-timer',
|
||||
ENABLE_SHOWER_ALERT: 'Aktivera Dusch-varning',
|
||||
TRIGGER_TIME: 'Aktiveringstid',
|
||||
|
||||
@@ -650,7 +650,7 @@ const DashboardData: FC = () => {
|
||||
<DialogContent dividers>
|
||||
<List dense={true}>
|
||||
<ListItem>
|
||||
<ListItemText primary={LL.TYPE()} secondary={coreData.devices[deviceDialog].t} />
|
||||
<ListItemText primary={LL.TYPE()} secondary={coreData.devices[deviceDialog].tn} />
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<ListItemText primary={LL.NAME(0)} secondary={coreData.devices[deviceDialog].n} />
|
||||
|
||||
@@ -408,6 +408,11 @@ const SettingsApplication: FC = () => {
|
||||
label={LL.UNDERCLOCK_CPU()}
|
||||
disabled={saving}
|
||||
/>
|
||||
<BlockFormControlLabel
|
||||
control={<Checkbox checked={data.boiler_heatingoff} onChange={updateFormValue} name="boiler_heatingoff" />}
|
||||
label={LL.HEATINGOFF()}
|
||||
disabled={saving}
|
||||
/>
|
||||
<Grid container spacing={0} direction="row" justifyContent="flex-start" alignItems="flex-start">
|
||||
<BlockFormControlLabel
|
||||
control={<Checkbox checked={data.shower_timer} onChange={updateFormValue} name="shower_timer" />}
|
||||
|
||||
@@ -7,6 +7,7 @@ export interface Settings {
|
||||
syslog_mark_interval: number;
|
||||
syslog_host: string;
|
||||
syslog_port: number;
|
||||
boiler_heatingoff: boolean;
|
||||
shower_timer: boolean;
|
||||
shower_alert: boolean;
|
||||
shower_alert_coldshot: number;
|
||||
|
||||
@@ -71,13 +71,13 @@ void UploadFileService::handleUpload(AsyncWebServerRequest * request, const Stri
|
||||
return;
|
||||
}
|
||||
#elif CONFIG_IDF_TARGET_ESP32S3
|
||||
if (len > 12 && (data[0] != 0xE9 || data[12] != 3)) {
|
||||
if (len > 12 && (data[0] != 0xE9 || data[12] != 9)) {
|
||||
handleError(request, 503); // service unavailable
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
// it's firmware - initialize the ArduinoOTA updater
|
||||
if (Update.begin(fsize)) {
|
||||
if (Update.begin()) {
|
||||
if (strlen(md5) == 32) {
|
||||
Update.setMD5(md5);
|
||||
md5[0] = '\0';
|
||||
|
||||
@@ -68,7 +68,7 @@ void AnalogSensor::reload() {
|
||||
analog_enabled_ = true; // for local offline testing
|
||||
#endif
|
||||
for (auto sensor : sensors_) {
|
||||
remove_ha_topic(sensor.gpio());
|
||||
remove_ha_topic(sensor.type(), sensor.gpio());
|
||||
sensor.ha_registered = false;
|
||||
}
|
||||
if (!analog_enabled_) {
|
||||
@@ -333,7 +333,7 @@ bool AnalogSensor::update(uint8_t gpio, const std::string & name, double offset,
|
||||
|
||||
// if the sensor exists and we're using HA, delete the old HA record
|
||||
if (found_sensor && Mqtt::ha_enabled()) {
|
||||
remove_ha_topic(gpio); // the GPIO
|
||||
remove_ha_topic(type, gpio); // the GPIO
|
||||
}
|
||||
|
||||
// we didn't find it, it's new, so create and store it
|
||||
@@ -384,7 +384,7 @@ void AnalogSensor::publish_sensor(const Sensor & sensor) const {
|
||||
}
|
||||
|
||||
// send empty config topic to remove the entry from HA
|
||||
void AnalogSensor::remove_ha_topic(const uint8_t gpio) const {
|
||||
void AnalogSensor::remove_ha_topic(const int8_t type, const uint8_t gpio) const {
|
||||
if (!Mqtt::ha_enabled()) {
|
||||
return;
|
||||
}
|
||||
@@ -392,7 +392,21 @@ void AnalogSensor::remove_ha_topic(const uint8_t gpio) const {
|
||||
LOG_DEBUG("Removing HA config for analog sensor GPIO %02d", gpio);
|
||||
#endif
|
||||
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
|
||||
snprintf(topic, sizeof(topic), "sensor/%s/analogsensor_%02d/config", Mqtt::basename().c_str(), gpio);
|
||||
#if CONFIG_IDF_TARGET_ESP32
|
||||
if (type == AnalogType::DIGITAL_OUT && gpio != 25 && gpio != 26) {
|
||||
#else
|
||||
if (type == AnalogType::DIGITAL_OUT)
|
||||
#endif
|
||||
snprintf(topic, sizeof(topic), "switch/%s/analogsensor_%02d/config", Mqtt::basename().c_str(), gpio);
|
||||
} else if (type == AnalogType::DIGITAL_OUT) { // DAC
|
||||
snprintf(topic, sizeof(topic), "number/%s/analogsensor_%02d/config", Mqtt::basename().c_str(), gpio);
|
||||
} else if (type >= AnalogType::PWM_0) {
|
||||
snprintf(topic, sizeof(topic), "number/%s/analogsensor_%02d/config", Mqtt::basename().c_str(), gpio);
|
||||
} else if (type == AnalogType::DIGITAL_IN) {
|
||||
snprintf(topic, sizeof(topic), "binary_sensor/%s/analogsensor_%02d/config", Mqtt::basename().c_str(), gpio);
|
||||
} else {
|
||||
snprintf(topic, sizeof(topic), "sensor/%s/analogsensor_%02d/config", Mqtt::basename().c_str(), gpio);
|
||||
}
|
||||
Mqtt::publish_ha(topic);
|
||||
}
|
||||
|
||||
@@ -447,13 +461,16 @@ void AnalogSensor::publish_values(const bool force) {
|
||||
snprintf(stat_t, sizeof(stat_t), "%s/analogsensor_data", Mqtt::base().c_str()); // use base path
|
||||
config["stat_t"] = stat_t;
|
||||
|
||||
char str[50];
|
||||
char val_obj[50];
|
||||
char val_cond[65];
|
||||
if (Mqtt::is_nested()) {
|
||||
snprintf(str, sizeof(str), "{{value_json['%02d'].value}}", sensor.gpio());
|
||||
snprintf(val_obj, sizeof(val_obj), "value_json['%02d'].value", sensor.gpio());
|
||||
snprintf(val_cond, sizeof(val_cond), "value_json['%02d'] is defined", sensor.gpio());
|
||||
} else {
|
||||
snprintf(str, sizeof(str), "{{value_json['%s']}", sensor.name().c_str());
|
||||
snprintf(val_obj, sizeof(val_obj), "value_json['%s']", sensor.name().c_str());
|
||||
snprintf(val_cond, sizeof(val_cond), "%s is defined", val_obj);
|
||||
}
|
||||
config["val_tpl"] = str;
|
||||
config["val_tpl"] = (std::string) "{{" + val_obj + " if " + val_cond + "}}";
|
||||
|
||||
char uniq_s[70];
|
||||
if (Mqtt::entity_format() == 2) {
|
||||
@@ -462,22 +479,76 @@ void AnalogSensor::publish_values(const bool force) {
|
||||
snprintf(uniq_s, sizeof(uniq_s), "analogsensor_%02d", sensor.gpio());
|
||||
}
|
||||
|
||||
config["object_id"] = uniq_s;
|
||||
config["uniq_id"] = uniq_s; // same as object_id
|
||||
config["obj_id"] = uniq_s;
|
||||
config["uniq_id"] = uniq_s; // same as object_id
|
||||
|
||||
snprintf(str, sizeof(str), "%s", sensor.name().c_str());
|
||||
config["name"] = str;
|
||||
char name[50];
|
||||
snprintf(name, sizeof(name), "%s", sensor.name().c_str());
|
||||
config["name"] = name;
|
||||
|
||||
if (sensor.uom() != DeviceValueUOM::NONE) {
|
||||
config["unit_of_meas"] = EMSdevice::uom_to_string(sensor.uom());
|
||||
}
|
||||
|
||||
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
|
||||
|
||||
// Set commands for some analog types
|
||||
char command_topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
|
||||
#if CONFIG_IDF_TARGET_ESP32
|
||||
if (sensor.type() == AnalogType::DIGITAL_OUT && sensor.gpio() != 25 && sensor.gpio() != 26) {
|
||||
#else
|
||||
if (sensor.type() == AnalogType::DIGITAL_OUT)
|
||||
#endif
|
||||
snprintf(topic, sizeof(topic), "switch/%s/analogsensor_%02d/config", Mqtt::basename().c_str(), sensor.gpio());
|
||||
snprintf(command_topic, sizeof(command_topic), "%s/analogsensor/%s", Mqtt::basename().c_str(), sensor.name().c_str());
|
||||
config["cmd_t"] = command_topic;
|
||||
if (EMSESP::system_.bool_format() == BOOL_FORMAT_TRUEFALSE) {
|
||||
config["pl_on"] = true;
|
||||
config["pl_off"] = false;
|
||||
} else if (EMSESP::system_.bool_format() == BOOL_FORMAT_10) {
|
||||
config["pl_on"] = 1;
|
||||
config["pl_off"] = 0;
|
||||
} else {
|
||||
char result[12];
|
||||
config["pl_on"] = Helpers::render_boolean(result, true);
|
||||
config["pl_off"] = Helpers::render_boolean(result, false);
|
||||
}
|
||||
} else if (sensor.type() == AnalogType::DIGITAL_OUT) { // DAC
|
||||
snprintf(topic, sizeof(topic), "number/%s/analogsensor_%02d/config", Mqtt::basename().c_str(), sensor.gpio());
|
||||
snprintf(command_topic, sizeof(command_topic), "%s/analogsensor/%s", Mqtt::basename().c_str(), sensor.name().c_str());
|
||||
config["cmd_t"] = command_topic;
|
||||
config["min"] = 0;
|
||||
config["max"] = 255;
|
||||
config["mode"] = "box"; // auto, slider or box
|
||||
config["step"] = 1;
|
||||
} else if (sensor.type() >= AnalogType::PWM_0) {
|
||||
snprintf(topic, sizeof(topic), "number/%s/analogsensor_%02d/config", Mqtt::basename().c_str(), sensor.gpio());
|
||||
snprintf(command_topic, sizeof(command_topic), "%s/analogsensor/%s", Mqtt::basename().c_str(), sensor.name().c_str());
|
||||
config["cmd_t"] = command_topic;
|
||||
config["min"] = 0;
|
||||
config["max"] = 100;
|
||||
config["mode"] = "box"; // auto, slider or box
|
||||
config["step"] = 0.1;
|
||||
} else if (sensor.type() == AnalogType::COUNTER) {
|
||||
snprintf(topic, sizeof(topic), "sensor/%s/analogsensor_%02d/config", Mqtt::basename().c_str(), sensor.gpio());
|
||||
snprintf(command_topic, sizeof(command_topic), "%s/analogsensor/%s", Mqtt::basename().c_str(), sensor.name().c_str());
|
||||
config["cmd_t"] = command_topic;
|
||||
config["stat_cla"] = "total_increasing";
|
||||
// config["mode"] = "box"; // auto, slider or box
|
||||
// config["step"] = sensor.factor();
|
||||
} else if (sensor.type() == AnalogType::DIGITAL_IN) {
|
||||
snprintf(topic, sizeof(topic), "binary_sensor/%s/analogsensor_%02d/config", Mqtt::basename().c_str(), sensor.gpio());
|
||||
} else {
|
||||
snprintf(topic, sizeof(topic), "sensor/%s/analogsensor_%02d/config", Mqtt::basename().c_str(), sensor.gpio());
|
||||
config["stat_cla"] = "measurement";
|
||||
}
|
||||
|
||||
JsonObject dev = config.createNestedObject("dev");
|
||||
JsonArray ids = dev.createNestedArray("ids");
|
||||
ids.add("ems-esp");
|
||||
|
||||
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
|
||||
snprintf(topic, sizeof(topic), "sensor/%s/analogsensor_%02d/config", Mqtt::basename().c_str(), sensor.gpio());
|
||||
// add "availability" section
|
||||
Mqtt::add_avty_to_doc(stat_t, config.as<JsonObject>(), val_cond);
|
||||
|
||||
Mqtt::publish_ha(topic, config.as<JsonObject>());
|
||||
|
||||
|
||||
@@ -170,7 +170,7 @@ class AnalogSensor {
|
||||
|
||||
static uuid::log::Logger logger_;
|
||||
|
||||
void remove_ha_topic(const uint8_t id) const;
|
||||
void remove_ha_topic(const int8_t type, const uint8_t id) const;
|
||||
bool command_setvalue(const char * value, const int8_t gpio);
|
||||
void measure();
|
||||
bool command_info(const char * value, const int8_t id, JsonObject & output) const;
|
||||
|
||||
@@ -514,13 +514,16 @@ void DallasSensor::publish_values(const bool force) {
|
||||
|
||||
config["unit_of_meas"] = EMSdevice::uom_to_string(DeviceValueUOM::DEGREES);
|
||||
|
||||
char str[50];
|
||||
char val_obj[50];
|
||||
char val_cond[65];
|
||||
if (Mqtt::is_nested()) {
|
||||
snprintf(str, sizeof(str), "{{value_json['%s'].temp}}", sensor.id().c_str());
|
||||
snprintf(val_obj, sizeof(val_obj), "value_json['%s'].temp", sensor.id().c_str());
|
||||
snprintf(val_cond, sizeof(val_cond), "value_json['%s'] is defined", sensor.id().c_str());
|
||||
} else {
|
||||
snprintf(str, sizeof(str), "{{value_json['%s']}}", sensor.name().c_str());
|
||||
snprintf(val_obj, sizeof(val_obj), "value_json['%s']", sensor.name().c_str());
|
||||
snprintf(val_cond, sizeof(val_cond), "%s is defined", val_obj);
|
||||
}
|
||||
config["val_tpl"] = str;
|
||||
config["val_tpl"] = (std::string) "{{" + val_obj + " if " + val_cond + "}}";
|
||||
|
||||
char uniq_s[70];
|
||||
if (Mqtt::entity_format() == 2) {
|
||||
@@ -529,16 +532,20 @@ void DallasSensor::publish_values(const bool force) {
|
||||
snprintf(uniq_s, sizeof(uniq_s), "dallassensor_%s", sensor.id().c_str());
|
||||
}
|
||||
|
||||
config["object_id"] = uniq_s;
|
||||
config["obj_id"] = uniq_s;
|
||||
config["uniq_id"] = uniq_s; // same as object_id
|
||||
|
||||
snprintf(str, sizeof(str), "%s", sensor.name().c_str());
|
||||
config["name"] = str;
|
||||
char name[50];
|
||||
snprintf(name, sizeof(name), "%s", sensor.name().c_str());
|
||||
config["name"] = name;
|
||||
|
||||
JsonObject dev = config.createNestedObject("dev");
|
||||
JsonArray ids = dev.createNestedArray("ids");
|
||||
ids.add("ems-esp");
|
||||
|
||||
// add "availability" section
|
||||
Mqtt::add_avty_to_doc(stat_t, config.as<JsonObject>(), val_cond);
|
||||
|
||||
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
|
||||
// use '_' as HA doesn't like '-' in the topic name
|
||||
std::string sensorid = sensor.id();
|
||||
|
||||
@@ -61,6 +61,10 @@
|
||||
#define EMSESP_DEFAULT_TRACELOG_RAW false
|
||||
#endif
|
||||
|
||||
#ifndef EMSESP_DEFAULT_BOILER_HEATINGOFF
|
||||
#define EMSESP_DEFAULT_BOILER_HEATINGOFF false
|
||||
#endif
|
||||
|
||||
#ifndef EMSESP_DEFAULT_SHOWER_TIMER
|
||||
#define EMSESP_DEFAULT_SHOWER_TIMER false
|
||||
#endif
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
{208, DeviceType::BOILER, "Logamax Plus/GB192/Condens GC9000/Greenstar ErP", DeviceFlags::EMS_DEVICE_FLAG_NONE},
|
||||
{210, DeviceType::BOILER, "Cascade MC400", DeviceFlags::EMS_DEVICE_FLAG_NONE},
|
||||
{211, DeviceType::BOILER, "EasyControl Adapter", DeviceFlags::EMS_DEVICE_FLAG_NONE},
|
||||
{219, DeviceType::BOILER, "Greenstar HIU", DeviceFlags::EMS_DEVICE_FLAG_NONE},
|
||||
{234, DeviceType::BOILER, "Logamax Plus GB122/Condense 2300", DeviceFlags::EMS_DEVICE_FLAG_NONE},
|
||||
|
||||
// Controllers - 0x09 / 0x10 / 0x50
|
||||
|
||||
@@ -100,7 +100,12 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
|
||||
// reset is a command uses a dummy variable which is always zero, shown as blank, but provides command enum options
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &reset_, DeviceValueType::CMD, FL_(enum_reset), FL_(reset), DeviceValueUOM::NONE, MAKE_CF_CB(set_reset));
|
||||
has_update(reset_, 0);
|
||||
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
|
||||
&forceHeatingOff_,
|
||||
DeviceValueType::BOOL,
|
||||
FL_(forceHeatingOff),
|
||||
DeviceValueUOM::NONE,
|
||||
MAKE_CF_CB(set_forceHeatingOff));
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &heatingActive_, DeviceValueType::BOOL, FL_(heatingActive), DeviceValueUOM::NONE);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &tapwaterActive_, DeviceValueType::BOOL, FL_(tapwaterActive), DeviceValueUOM::NONE);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &selFlowTemp_, DeviceValueType::UINT, FL_(selFlowTemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_flow_temp));
|
||||
@@ -875,6 +880,11 @@ void Boiler::check_active(const bool force) {
|
||||
Mqtt::publish(F_(tapwater_active), Helpers::render_boolean(s, b));
|
||||
EMSESP::tap_water_active(b); // let EMS-ESP know, used in the Shower class
|
||||
}
|
||||
|
||||
if (!Helpers::hasValue(forceHeatingOff_, EMS_VALUE_BOOL)) {
|
||||
EMSESP::webSettingsService.read([&](WebSettings & settings) { forceHeatingOff_ = (settings.boiler_heatingoff || selFlowTemp_ == 0) ? 1 : 0; });
|
||||
has_update(&forceHeatingOff_);
|
||||
}
|
||||
}
|
||||
|
||||
// 0x18
|
||||
@@ -1075,6 +1085,11 @@ void Boiler::process_UBAMonitorSlow(std::shared_ptr<const Telegram> telegram) {
|
||||
has_update(telegram, burn2WorkMin_, 16, 3); // force to 3 bytes
|
||||
has_update(telegram, heatWorkMin_, 19, 3); // force to 3 bytes
|
||||
has_update(telegram, heatStarts_, 22, 3); // force to 3 bytes
|
||||
|
||||
if (forceHeatingOff_ == EMS_VALUE_BOOL_ON && telegram->dest == 0) {
|
||||
uint8_t data[] = {0, 0, 0, 0};
|
||||
write_command(EMS_TYPE_UBASetPoints, 0, data, sizeof(data), 0);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1105,6 +1120,11 @@ void Boiler::process_UBAMonitorSlowPlus(std::shared_ptr<const Telegram> telegram
|
||||
has_update(telegram, heatStarts_, 22, 3); // force to 3 bytes
|
||||
has_update(telegram, heatingPumpMod_, 25);
|
||||
// temperature measurements at 4, see #620
|
||||
|
||||
if (forceHeatingOff_ == EMS_VALUE_BOOL_ON && telegram->dest == 0) {
|
||||
uint8_t data[] = {0, 0, 0, 0};
|
||||
write_command(EMS_TYPE_UBASetPoints, 0, data, sizeof(data), 0);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1711,7 +1731,9 @@ bool Boiler::set_flow_temp(const char * value, const int8_t id) {
|
||||
}
|
||||
|
||||
// no write/verify if there is no change, see https://github.com/emsesp/EMS-ESP32/issues/654
|
||||
// put it to end of tx-queue
|
||||
if (v == selFlowTemp_) {
|
||||
EMSESP::txservice_.add(Telegram::Operation::TX_WRITE, device_id(), EMS_TYPE_UBASetPoints, 0, (uint8_t *) &v, 1, 0, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -2574,4 +2596,17 @@ bool Boiler::set_wwAltOpPrio(const char * value, const int8_t id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Boiler::set_forceHeatingOff(const char * value, const int8_t id) {
|
||||
bool v;
|
||||
if (Helpers::value2bool(value, v)) {
|
||||
has_update(forceHeatingOff_, v);
|
||||
if (!v && Helpers::hasValue(heatingTemp_)) {
|
||||
uint8_t data[] = {heatingTemp_, (Helpers::hasValue(burnMaxPower_) ? burnMaxPower_ : (uint8_t)100), (Helpers::hasValue(pumpModMax_) ? pumpModMax_ : (uint8_t)0), 0};
|
||||
write_command(EMS_TYPE_UBASetPoints, 0, data, sizeof(data), 0);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
@@ -87,7 +87,6 @@ class Boiler : public EMSdevice {
|
||||
uint32_t wwWorkM_; // DHW minutes
|
||||
int8_t wwHystOn_;
|
||||
int8_t wwHystOff_;
|
||||
uint8_t wwTapActivated_; // maintenance-mode to switch DHW off
|
||||
uint16_t wwMixerTemp_; // mixing temperature
|
||||
uint16_t wwCylMiddleTemp_; // Cyl middle temperature (TS3)
|
||||
uint16_t wwSolarTemp_;
|
||||
@@ -95,6 +94,11 @@ class Boiler : public EMSdevice {
|
||||
uint8_t wwAltOpPrioHeat_; // alternating operation, prioritise heat time
|
||||
uint8_t wwAltOpPrioWw_; // alternating operation, prioritise dhw time
|
||||
|
||||
// special function
|
||||
uint8_t forceHeatingOff_;
|
||||
uint8_t wwTapActivated_; // maintenance-mode to switch DHW off
|
||||
|
||||
|
||||
// main
|
||||
uint8_t reset_; // for reset command
|
||||
uint8_t heatingActive_; // Central heating is on/off
|
||||
@@ -447,6 +451,8 @@ class Boiler : public EMSdevice {
|
||||
inline bool set_wwAltOpPrioWw(const char * value, const int8_t id) {
|
||||
return set_wwAltOpPrio(value, 3);
|
||||
}
|
||||
bool set_forceHeatingOff(const char * value, const int8_t id);
|
||||
|
||||
/*
|
||||
bool set_hybridStrategy(const char * value, const int8_t id);
|
||||
bool set_switchOverTemp(const char * value, const int8_t id);
|
||||
|
||||
@@ -32,14 +32,25 @@ Heatpump::Heatpump(uint8_t device_type, uint8_t device_id, uint8_t product_id, c
|
||||
register_telegram_type(0x999, "HPFunctionTest", true, MAKE_PF_CB(process_HPFunctionTest));
|
||||
register_telegram_type(0x9A0, "HPTemperature", false, MAKE_PF_CB(process_HPTemperature));
|
||||
register_telegram_type(0x99B, "HPFlowTemp", false, MAKE_PF_CB(process_HPFlowTemp));
|
||||
register_telegram_type(0x99C, "HPComp", false, MAKE_PF_CB(process_HPComp));
|
||||
|
||||
// device values
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &airHumidity_, DeviceValueType::UINT, FL_(airHumidity), DeviceValueUOM::PERCENT);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &dewTemperature_, DeviceValueType::UINT, FL_(dewTemperature), DeviceValueUOM::DEGREES);
|
||||
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &flowTemp_, DeviceValueType::UINT, FL_(curFlowTemp), DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &retTemp_, DeviceValueType::UINT, FL_(retTemp), DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &sysRetTemp_, DeviceValueType::UINT, FL_(sysRetTemp), DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
|
||||
&flowTemp_,
|
||||
DeviceValueType::SHORT,
|
||||
DeviceValueNumOp::DV_NUMOP_DIV10,
|
||||
FL_(curFlowTemp),
|
||||
DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &retTemp_, DeviceValueType::SHORT, DeviceValueNumOp::DV_NUMOP_DIV10, FL_(retTemp), DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
|
||||
&sysRetTemp_,
|
||||
DeviceValueType::SHORT,
|
||||
DeviceValueNumOp::DV_NUMOP_DIV10,
|
||||
FL_(sysRetTemp),
|
||||
DeviceValueUOM::DEGREES);
|
||||
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &hpTa4_, DeviceValueType::SHORT, DeviceValueNumOp::DV_NUMOP_DIV10, FL_(hpTa4), DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &hpTr1_, DeviceValueType::SHORT, DeviceValueNumOp::DV_NUMOP_DIV10, FL_(hpTr1), DeviceValueUOM::DEGREES);
|
||||
@@ -51,6 +62,9 @@ Heatpump::Heatpump(uint8_t device_type, uint8_t device_id, uint8_t product_id, c
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &hpJr0_, DeviceValueType::SHORT, DeviceValueNumOp::DV_NUMOP_DIV10, FL_(hpPl1), DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &hpJr1_, DeviceValueType::SHORT, DeviceValueNumOp::DV_NUMOP_DIV10, FL_(hpPh1), DeviceValueUOM::DEGREES);
|
||||
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &heatingPumpMod_, DeviceValueType::UINT, FL_(heatingPumpMod), DeviceValueUOM::PERCENT);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &hpCompSpd_, DeviceValueType::UINT, FL_(hpCompSpd), DeviceValueUOM::PERCENT);
|
||||
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
|
||||
&controlStrategy_,
|
||||
DeviceValueType::ENUM,
|
||||
@@ -177,6 +191,7 @@ void Heatpump::process_HPFlowTemp(std::shared_ptr<const Telegram> telegram) {
|
||||
has_update(telegram, flowTemp_, 4);
|
||||
has_update(telegram, retTemp_, 6);
|
||||
has_update(telegram, sysRetTemp_, 14);
|
||||
has_update(telegram, heatingPumpMod_, 19);
|
||||
}
|
||||
|
||||
// 0x0998 HPSettings
|
||||
@@ -193,6 +208,14 @@ void Heatpump::process_HPSettings(std::shared_ptr<const Telegram> telegram) {
|
||||
has_update(telegram, switchOverTemp_, 14);
|
||||
}
|
||||
|
||||
// 0x099C HPComp
|
||||
// Broadcast (0x099C), data: 00 04 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 02 76 00 00
|
||||
// data: 00 2B 00 03 04 13 00 00 00 00 00 02 02 02 (offset 24)
|
||||
void Heatpump::process_HPComp(std::shared_ptr<const Telegram> telegram) {
|
||||
has_update(telegram, hpCompSpd_, 51);
|
||||
}
|
||||
|
||||
// 0x999 HPFunctionTest
|
||||
void Heatpump::process_HPFunctionTest(std::shared_ptr<const Telegram> telegram) {
|
||||
has_update(telegram, airPurgeMode_, 0);
|
||||
has_update(telegram, heatPumpOutput_, 2);
|
||||
|
||||
@@ -41,6 +41,8 @@ class Heatpump : public EMSdevice {
|
||||
uint8_t energyPriceEl_;
|
||||
uint8_t energyPricePV_;
|
||||
int8_t switchOverTemp_;
|
||||
uint8_t heatingPumpMod_;
|
||||
uint8_t hpCompSpd_;
|
||||
|
||||
// Function test
|
||||
uint8_t airPurgeMode_;
|
||||
@@ -51,19 +53,19 @@ class Heatpump : public EMSdevice {
|
||||
uint8_t heatCable_;
|
||||
|
||||
// HM200 temperature
|
||||
int16_t flowTemp_;
|
||||
int16_t retTemp_;
|
||||
int16_t sysRetTemp_;
|
||||
int16_t hpTc3_; // condenser temp.
|
||||
int16_t hpTr1_; // compressor temp.
|
||||
int16_t hpTr3_; // cond. temp. heating
|
||||
int16_t hpTr4_; // cond. temp. clg
|
||||
int16_t hpTr5_; // suction line temp.
|
||||
int16_t hpTr6_; // hot gas temp.
|
||||
int16_t hpTl2_; // inlet air temperature
|
||||
int16_t hpTa4_; // drain pan temp.
|
||||
int16_t hpJr0_; // low pressure sensor
|
||||
int16_t hpJr1_; // high pressure sensor
|
||||
int16_t flowTemp_; // TH1
|
||||
int16_t retTemp_; // TH2
|
||||
int16_t sysRetTemp_; // TH3
|
||||
int16_t hpTc3_; // condenser temp.
|
||||
int16_t hpTr1_; // compressor temp.
|
||||
int16_t hpTr3_; // cond. temp. heating
|
||||
int16_t hpTr4_; // cond. temp. clg
|
||||
int16_t hpTr5_; // suction line temp.
|
||||
int16_t hpTr6_; // hot gas temp.
|
||||
int16_t hpTl2_; // inlet air temperature
|
||||
int16_t hpTa4_; // drain pan temp.
|
||||
int16_t hpJr0_; // low pressure sensor
|
||||
int16_t hpJr1_; // high pressure sensor
|
||||
|
||||
void process_HPMonitor1(std::shared_ptr<const Telegram> telegram);
|
||||
void process_HPMonitor2(std::shared_ptr<const Telegram> telegram);
|
||||
@@ -71,6 +73,8 @@ class Heatpump : public EMSdevice {
|
||||
void process_HPFunctionTest(std::shared_ptr<const Telegram> telegram);
|
||||
void process_HPTemperature(std::shared_ptr<const Telegram> telegram);
|
||||
void process_HPFlowTemp(std::shared_ptr<const Telegram> telegram);
|
||||
void process_HPComp(std::shared_ptr<const Telegram> telegram);
|
||||
|
||||
bool set_controlStrategy(const char * value, const int8_t id);
|
||||
bool set_lowNoiseMode(const char * value, const int8_t id);
|
||||
bool set_lowNoiseStart(const char * value, const int8_t id);
|
||||
|
||||
@@ -317,6 +317,7 @@ Solar::Solar(uint8_t device_type, uint8_t device_id, uint8_t product_id, const c
|
||||
DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &cylPumpMod_, DeviceValueType::UINT, FL_(cylPumpMod), DeviceValueUOM::PERCENT);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &valveStatus_, DeviceValueType::BOOL, FL_(valveStatus), DeviceValueUOM::NONE);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &vs1Status_, DeviceValueType::BOOL, FL_(vs1Status), DeviceValueUOM::NONE);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &cylHeated_, DeviceValueType::BOOL, FL_(cylHeated), DeviceValueUOM::NONE);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &collectorShutdown_, DeviceValueType::BOOL, FL_(collectorShutdown), DeviceValueUOM::NONE);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
|
||||
@@ -820,6 +821,7 @@ 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)
|
||||
*/
|
||||
void Solar::process_SM100Status2(std::shared_ptr<const Telegram> telegram) {
|
||||
has_bitupdate(telegram, vs1Status_, 0, 2); // on if bit 2 set
|
||||
has_bitupdate(telegram, valveStatus_, 4, 2); // on if bit 2 set
|
||||
has_bitupdate(telegram, solarPump_, 10, 2); // on if bit 2 set
|
||||
has_bitupdate(telegram, solarPump2_, 1, 2); // on if bit 2 set
|
||||
|
||||
@@ -45,6 +45,7 @@ class Solar : public EMSdevice {
|
||||
uint8_t solarPump2Mod_; // PS4: modulation solar pump
|
||||
uint8_t m1Valve_; // M1: heat assistance valve
|
||||
uint8_t m1Power_; // M1: heat assistance valve
|
||||
uint8_t vs1Status_; // VS1: status
|
||||
|
||||
// 0x363 heat counter
|
||||
uint16_t heatCntFlowTemp_;
|
||||
|
||||
@@ -1192,14 +1192,9 @@ void Thermostat::process_RC30Temp(std::shared_ptr<const Telegram> telegram) {
|
||||
|
||||
// type 0x3E (HC1), 0x48 (HC2), 0x52 (HC3), 0x5C (HC4) - data from the RC35 thermostat (0x10) - 16 bytes
|
||||
void Thermostat::process_RC35Monitor(std::shared_ptr<const Telegram> telegram) {
|
||||
// exit if the 15th byte (second from last) is 0x00, which I think is calculated flow setpoint temperature
|
||||
// with weather controlled RC35s this value is >=5, otherwise can be zero and our setpoint temps will be incorrect
|
||||
// see https://github.com/emsesp/EMS-ESP/issues/373#issuecomment-627907301
|
||||
if (telegram->offset > 0 || telegram->message_length < 15) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (telegram->message_data[14] == 0x00) {
|
||||
// Check if heatingciruit is active, see https://github.com/emsesp/EMS-ESP32/issues/786
|
||||
uint16_t active = 0;
|
||||
if (!telegram->read_value(active, 3)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1642,22 +1642,6 @@ bool EMSdevice::generate_values(JsonObject & output, const uint8_t tag_filter, c
|
||||
return has_values;
|
||||
}
|
||||
|
||||
// remove the Home Assistant configs for each device value/entity if its not visible or active or marked as read-only
|
||||
// this is called when an MQTT publish is done via an EMS Device in emsesp.cpp::publish_device_values()
|
||||
void EMSdevice::mqtt_ha_entity_config_remove() {
|
||||
for (auto & dv : devicevalues_) {
|
||||
if (dv.has_state(DeviceValueState::DV_HA_CONFIG_CREATED)
|
||||
&& ((dv.has_state(DeviceValueState::DV_API_MQTT_EXCLUDE)) || (!dv.has_state(DeviceValueState::DV_ACTIVE)))) {
|
||||
dv.remove_state(DeviceValueState::DV_HA_CONFIG_CREATED);
|
||||
if (dv.short_name == FL_(climate)[0]) {
|
||||
Mqtt::publish_ha_climate_config(dv.tag, false, true); // delete topic (remove = true)
|
||||
} else {
|
||||
Mqtt::publish_ha_sensor_config(dv, "", "", true); // delete topic (remove = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// create the Home Assistant configs for each device value / entity
|
||||
// this is called when an MQTT publish is done via an EMS Device in emsesp.cpp::publish_device_values()
|
||||
void EMSdevice::mqtt_ha_entity_config_create() {
|
||||
@@ -1686,6 +1670,9 @@ void EMSdevice::mqtt_ha_entity_config_create() {
|
||||
dv.add_state(DeviceValueState::DV_HA_CONFIG_CREATED);
|
||||
create_device_config = false; // only create the main config once
|
||||
}
|
||||
if (ESP.getFreeHeap() < (65 * 1024)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ha_config_done(!create_device_config);
|
||||
@@ -1694,7 +1681,6 @@ void EMSdevice::mqtt_ha_entity_config_create() {
|
||||
// remove all config topics in HA
|
||||
void EMSdevice::ha_config_clear() {
|
||||
for (auto & dv : devicevalues_) {
|
||||
Mqtt::publish_ha_sensor_config(dv, "", "", true); // delete topic (remove = true)
|
||||
dv.remove_state(DeviceValueState::DV_HA_CONFIG_CREATED);
|
||||
}
|
||||
|
||||
|
||||
@@ -289,7 +289,6 @@ class EMSdevice {
|
||||
void publish_all_values();
|
||||
|
||||
void mqtt_ha_entity_config_create();
|
||||
void mqtt_ha_entity_config_remove();
|
||||
|
||||
std::string telegram_type_name(std::shared_ptr<const Telegram> telegram) const;
|
||||
|
||||
@@ -306,13 +305,6 @@ class EMSdevice {
|
||||
ha_config_done_ = v;
|
||||
}
|
||||
|
||||
bool ha_config_firstrun() const {
|
||||
return ha_config_firstrun_;
|
||||
}
|
||||
void ha_config_firstrun(const bool v) {
|
||||
ha_config_firstrun_ = v;
|
||||
}
|
||||
|
||||
enum Brand : uint8_t {
|
||||
NO_BRAND = 0, // 0
|
||||
BOSCH, // 1
|
||||
@@ -366,6 +358,7 @@ class EMSdevice {
|
||||
static constexpr uint8_t EMS_DEVICE_ID_RFSENSOR = 0x40; // RF sensor only sending, no reply
|
||||
static constexpr uint8_t EMS_DEVICE_ID_RFBASE = 0x50;
|
||||
static constexpr uint8_t EMS_DEVICE_ID_ROOMTHERMOSTAT = 0x17; // TADO using this with no version reply
|
||||
static constexpr uint8_t EMS_DEVICE_ID_TADO_OLD = 0x19; // TADO using this with no broadcast and version
|
||||
|
||||
// generic type IDs
|
||||
static constexpr uint16_t EMS_TYPE_VERSION = 0x02; // type ID for Version information. Generic across all EMS devices.
|
||||
@@ -431,7 +424,6 @@ class EMSdevice {
|
||||
|
||||
bool ha_config_done_ = false;
|
||||
bool has_update_ = false;
|
||||
bool ha_config_firstrun_ = true; // this means a first setup of HA is needed after a restart
|
||||
|
||||
struct TelegramFunction {
|
||||
uint16_t telegram_type_id_; // it's type_id
|
||||
|
||||
@@ -535,24 +535,6 @@ void EMSESP::publish_device_values(uint8_t device_type) {
|
||||
bool nested = (Mqtt::is_nested());
|
||||
|
||||
// group by device type
|
||||
if (Mqtt::ha_enabled()) {
|
||||
for (const auto & emsdevice : emsdevices) {
|
||||
if (emsdevice && (emsdevice->device_type() == device_type)) {
|
||||
// specially for MQTT Discovery
|
||||
// we may have some RETAINED /config topics that reference fields in the data payloads that no longer exist
|
||||
// remove them immediately to prevent HA from complaining
|
||||
// we need to do this first before the data payload is published, and only done once!
|
||||
if (emsdevice->ha_config_firstrun()) {
|
||||
emsdevice->ha_config_clear();
|
||||
emsdevice->ha_config_firstrun(false);
|
||||
return;
|
||||
} else {
|
||||
// see if we need to delete and /config topics before adding the payloads
|
||||
emsdevice->mqtt_ha_entity_config_remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (uint8_t tag = DeviceValueTAG::TAG_BOILER_DATA_WW; tag <= DeviceValueTAG::TAG_HS16; tag++) {
|
||||
JsonObject json_hc = json;
|
||||
bool nest_created = false;
|
||||
@@ -1057,7 +1039,7 @@ bool EMSESP::add_device(const uint8_t device_id, const uint8_t product_id, const
|
||||
// see: https://github.com/emsesp/EMS-ESP32/issues/103#issuecomment-911717342 and https://github.com/emsesp/EMS-ESP32/issues/624
|
||||
name = "RF room temperature sensor";
|
||||
device_type = DeviceType::THERMOSTAT;
|
||||
} else if (device_id == EMSdevice::EMS_DEVICE_ID_ROOMTHERMOSTAT) {
|
||||
} else if (device_id == EMSdevice::EMS_DEVICE_ID_ROOMTHERMOSTAT || device_id == EMSdevice::EMS_DEVICE_ID_TADO_OLD) {
|
||||
name = "Generic thermostat";
|
||||
device_type = DeviceType::THERMOSTAT;
|
||||
flags = DeviceFlags::EMS_DEVICE_FLAG_RC10 | DeviceFlags::EMS_DEVICE_FLAG_NO_WRITE;
|
||||
|
||||
@@ -291,7 +291,7 @@ MAKE_PSTR_ENUM(enum_modetype5, FL_(off), FL_(on))
|
||||
|
||||
MAKE_PSTR_ENUM(enum_reducemode, FL_(nofrost), FL_(reduce), FL_(room), FL_(outdoor))
|
||||
MAKE_PSTR_ENUM(enum_reducemode1, FL_(outdoor), FL_(room), FL_(reduce)) // RC310 values: 1-3
|
||||
MAKE_PSTR_ENUM(enum_nofrostmode, FL_(off), FL_(room), FL_(outdoor))
|
||||
MAKE_PSTR_ENUM(enum_nofrostmode, FL_(off), FL_(outdoor), FL_(room))
|
||||
MAKE_PSTR_ENUM(enum_nofrostmode1, FL_(room), FL_(outdoor), FL_(room_outdoor))
|
||||
|
||||
MAKE_PSTR_ENUM(enum_controlmode, FL_(off), FL_(optimized), FL_(simple), FL_(mpc), FL_(room), FL_(power), FL_(constant))
|
||||
|
||||
@@ -269,6 +269,7 @@ MAKE_PSTR_LIST(cyl2, "cyl 2", "Zyl_2", "Cil 2", "Cyl 2", "cyl 2", "cyl 2", "cyl
|
||||
|
||||
// Entity translations
|
||||
// Boiler
|
||||
MAKE_PSTR_LIST(forceHeatingOff, "heatingoff", "force heating off", "Heizen abschalten", "", "", "", "", "")
|
||||
MAKE_PSTR_LIST(wwtapactivated, "wwtapactivated", "turn on/off", "Durchlauferhitzer aktiv", "zet aan/uit", "på/av", "system przygotowywania", "slå på/av", "ecs activée")
|
||||
MAKE_PSTR_LIST(reset, "reset", "Reset", "Reset", "Reset", "Nollställ", "kasowanie komunikatu", "nullstill", "reset")
|
||||
MAKE_PSTR_LIST(oilPreHeat, "oilpreheat", "oil preheating", "Ölvorwärmung", "Olie voorverwarming", "Förvärmning olja", "podgrzewanie oleju", "oljeforvarming", "préchauffage de l'huile")
|
||||
@@ -683,6 +684,7 @@ MAKE_PSTR_LIST(solarPump, "solarpump", "pump (PS1)", "Pumpe (PS1)", "Pomp (PS1)"
|
||||
MAKE_PSTR_LIST(solarPump2, "solarpump2", "pump 2 (PS4)", "Pumpe 2 (PS4)", "Pomp 2 (PS4)", "Pump 2 (PS4)", "pompa solarna 2 (PS4)", "solpumpe 2 (PS4)", "pompe 2 (PS4)")
|
||||
MAKE_PSTR_LIST(solarPump2Mod, "solarpump2mod", "pump 2 modulation (PS4)", "Pumpe 2 Modulation (PS4)", "Modulatie pomp 2 (PS4)", "Pump 2 Modulering (PS4)", "modulacja pompy solarnej 2 (PS4)", "solpumpe2modulering (PS4)", "modulation pompe solaire 2 (PS4)")
|
||||
MAKE_PSTR_LIST(valveStatus, "valvestatus", "valve status", "Ventilstatus", "Klepstatus", "Ventilstatus", "stan zaworu", "ventilstatus", "statut valve")
|
||||
MAKE_PSTR_LIST(vs1Status, "vs1status", "valve status VS1", "Ventilstatus VS1", "Klepstatus VS1", "Ventilstatus VS1", "stan zaworu VS1", "ventilstatus VS1", "statut valve VS1")
|
||||
MAKE_PSTR_LIST(cylHeated, "cylheated", "cyl heated", "Speichertemperatur erreicht", "Boilertemperatuur behaald", "Värmepanna Uppvärmd", "zasobnik został nagrzany", "bereder oppvarmt", "cylindre chauffé")
|
||||
MAKE_PSTR_LIST(collectorShutdown, "collectorshutdown", "collector shutdown", "Kollektorabschaltung", "Collector afschakeling", "Kollektor Avstängning", "wyłączenie kolektora", "kollektor stengt", "arrêt collecteur")
|
||||
MAKE_PSTR_LIST(pumpWorkTime, "pumpworktime", "pump working time", "Pumpenlaufzeit", "Pomplooptijd", "Pump Drifttid", "czas pracy pompy", "driftstid pumpe", "durée fonctionnement pompe")
|
||||
|
||||
105
src/mqtt.cpp
105
src/mqtt.cpp
@@ -685,7 +685,7 @@ std::shared_ptr<const MqttMessage> Mqtt::queue_message(const uint8_t operation,
|
||||
#endif
|
||||
|
||||
// if the queue is full, make room but removing the last one
|
||||
if (mqtt_messages_.size() >= MAX_MQTT_MESSAGES) {
|
||||
if (mqtt_messages_.size() >= MAX_MQTT_MESSAGES || ESP.getFreeHeap() < (60 * 1024)) {
|
||||
mqtt_messages_.pop_front();
|
||||
LOG_WARNING("Queue overflow, removing one message");
|
||||
mqtt_publish_fails_++;
|
||||
@@ -815,8 +815,8 @@ void Mqtt::process_queue() {
|
||||
#if defined(EMSESP_DEBUG)
|
||||
LOG_DEBUG("[DEBUG] Waiting for QOS-ACK");
|
||||
#endif
|
||||
// if we don't get the ack within 10 minutes, republish with new packet_id
|
||||
if (uuid::get_uptime_sec() - last_publish_queue_ < 600) {
|
||||
// if we don't get the ack within 10 seconds, republish with new packet_id
|
||||
if (uuid::get_uptime_sec() - last_publish_queue_ < 10) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -1074,6 +1074,8 @@ void Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev
|
||||
const char * sc_ha = "stat_cla"; // state class
|
||||
const char * uom_ha = "unit_of_meas"; // unit of measure
|
||||
|
||||
char sample_val[30] = "0"; // sample, correct(!) entity value, used only to prevent warning/error in HA if real value is not published yet
|
||||
|
||||
// handle commands, which are device entities that are writable
|
||||
// we add the command topic parameter
|
||||
// note: there is no way to handle strings in HA so datetimes (e.g. set_datetime, set_holiday, set_wwswitchtime etc) are excluded
|
||||
@@ -1093,6 +1095,7 @@ void Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev
|
||||
for (uint8_t i = 0; i < options_size; i++) {
|
||||
option_list.add(Helpers::translated_word(options[i]));
|
||||
}
|
||||
snprintf(sample_val, sizeof(sample_val), "'%s'", Helpers::translated_word(options[0]));
|
||||
} else if (type != DeviceValueType::STRING && type != DeviceValueType::BOOL) {
|
||||
// Must be Numeric....
|
||||
doc["mode"] = "box"; // auto, slider or box
|
||||
@@ -1109,6 +1112,7 @@ void Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev
|
||||
if (dv_set_min != 0 || dv_set_max != 0) {
|
||||
doc["min"] = dv_set_min;
|
||||
doc["max"] = dv_set_max;
|
||||
snprintf(sample_val, sizeof(sample_val), "%i", dv_set_min);
|
||||
}
|
||||
|
||||
// set icons
|
||||
@@ -1146,17 +1150,15 @@ void Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev
|
||||
|
||||
// value template
|
||||
// if its nested mqtt format then use the appended entity name, otherwise take the original name
|
||||
char val_tpl[75];
|
||||
if (is_nested()) {
|
||||
if (tag >= DeviceValueTAG::TAG_HC1) {
|
||||
snprintf(val_tpl, sizeof(val_tpl), "{{value_json.%s.%s}}", EMSdevice::tag_to_mqtt(tag).c_str(), entity);
|
||||
} else {
|
||||
snprintf(val_tpl, sizeof(val_tpl), "{{value_json.%s}}", entity);
|
||||
}
|
||||
char val_obj[100];
|
||||
char val_cond[200];
|
||||
if (is_nested() && tag >= DeviceValueTAG::TAG_HC1) {
|
||||
snprintf(val_obj, sizeof(val_obj), "value_json.%s.%s", EMSdevice::tag_to_mqtt(tag).c_str(), entity);
|
||||
snprintf(val_cond, sizeof(val_cond), "value_json.%s is defined and %s is defined", EMSdevice::tag_to_mqtt(tag).c_str(), val_obj);
|
||||
} else {
|
||||
snprintf(val_tpl, sizeof(val_tpl), "{{value_json.%s}}", entity);
|
||||
snprintf(val_obj, sizeof(val_obj), "value_json.%s", entity);
|
||||
snprintf(val_cond, sizeof(val_cond), "%s is defined", val_obj);
|
||||
}
|
||||
doc["val_tpl"] = val_tpl;
|
||||
|
||||
// special case to handle booleans
|
||||
// applies to both Binary Sensor (read only) and a Switch (for a command)
|
||||
@@ -1165,6 +1167,7 @@ void Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev
|
||||
if (EMSESP::system_.bool_format() == BOOL_FORMAT_TRUEFALSE) {
|
||||
doc["pl_on"] = true;
|
||||
doc["pl_off"] = false;
|
||||
snprintf(sample_val, sizeof(sample_val), "false");
|
||||
} else if (EMSESP::system_.bool_format() == BOOL_FORMAT_10) {
|
||||
doc["pl_on"] = 1;
|
||||
doc["pl_off"] = 0;
|
||||
@@ -1172,6 +1175,7 @@ void Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev
|
||||
char result[12];
|
||||
doc["pl_on"] = Helpers::render_boolean(result, true);
|
||||
doc["pl_off"] = Helpers::render_boolean(result, false);
|
||||
snprintf(sample_val, sizeof(sample_val), "'%s'", Helpers::render_boolean(result, false));
|
||||
}
|
||||
doc[sc_ha] = F_(measurement); //do we want this???
|
||||
} else {
|
||||
@@ -1189,6 +1193,8 @@ void Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev
|
||||
}
|
||||
}
|
||||
|
||||
doc["val_tpl"] = (std::string) "{{" + val_obj + " if " + val_cond + " else " + sample_val + "}}";
|
||||
|
||||
// this next section is adding the state class, device class and sometimes the icon
|
||||
// used for Sensor and Binary Sensor Entities in HA
|
||||
if (set_ha_classes) {
|
||||
@@ -1284,6 +1290,9 @@ void Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev
|
||||
// add the dev json object to the end
|
||||
doc["dev"] = dev_json;
|
||||
|
||||
// add "availability" section
|
||||
add_avty_to_doc(stat_t, doc.as<JsonObject>(), val_cond);
|
||||
|
||||
publish_ha(topic, doc.as<JsonObject>());
|
||||
}
|
||||
|
||||
@@ -1295,6 +1304,9 @@ void Mqtt::publish_ha_climate_config(const uint8_t tag, const bool has_roomtemp,
|
||||
char hc_mode_s[30];
|
||||
char seltemp_s[30];
|
||||
char currtemp_s[30];
|
||||
char hc_mode_cond[80];
|
||||
char seltemp_cond[80];
|
||||
char currtemp_cond[170];
|
||||
char mode_str_tpl[400];
|
||||
char name_s[10];
|
||||
char uniq_id_s[60];
|
||||
@@ -1312,24 +1324,31 @@ void Mqtt::publish_ha_climate_config(const uint8_t tag, const bool has_roomtemp,
|
||||
if (Mqtt::is_nested()) {
|
||||
// nested format
|
||||
snprintf(hc_mode_s, sizeof(hc_mode_s), "value_json.hc%d.mode", hc_num);
|
||||
snprintf(seltemp_s, sizeof(seltemp_s), "{{value_json.hc%d.seltemp}}", hc_num);
|
||||
snprintf(hc_mode_cond, sizeof(hc_mode_cond), "value_json.hc%d is undefined or %s is undefined", hc_num, hc_mode_s);
|
||||
snprintf(seltemp_s, sizeof(seltemp_s), "value_json.hc%d.seltemp", hc_num);
|
||||
snprintf(seltemp_cond, sizeof(seltemp_cond), "value_json.hc%d is defined and %s is defined", hc_num, seltemp_s);
|
||||
if (has_roomtemp) {
|
||||
snprintf(currtemp_s, sizeof(currtemp_s), "{{value_json.hc%d.currtemp}}", hc_num);
|
||||
snprintf(currtemp_s, sizeof(currtemp_s), "value_json.hc%d.currtemp", hc_num);
|
||||
snprintf(currtemp_cond, sizeof(currtemp_cond), "value_json.hc%d is defined and %s is defined", hc_num, currtemp_s);
|
||||
}
|
||||
snprintf(topic_t, sizeof(topic_t), "~/%s", Mqtt::tag_to_topic(EMSdevice::DeviceType::THERMOSTAT, DeviceValueTAG::TAG_NONE).c_str());
|
||||
} else {
|
||||
// single format
|
||||
snprintf(hc_mode_s, sizeof(hc_mode_s), "value_json.mode");
|
||||
snprintf(seltemp_s, sizeof(seltemp_s), "{{value_json.seltemp}}");
|
||||
snprintf(hc_mode_cond, sizeof(hc_mode_cond), "%s is undefined", hc_mode_s);
|
||||
snprintf(seltemp_s, sizeof(seltemp_s), "value_json.seltemp");
|
||||
snprintf(seltemp_cond, sizeof(seltemp_cond), "%s is defined", seltemp_s);
|
||||
if (has_roomtemp) {
|
||||
snprintf(currtemp_s, sizeof(currtemp_s), "{{value_json.currtemp}}");
|
||||
snprintf(currtemp_s, sizeof(currtemp_s), "value_json.currtemp");
|
||||
snprintf(currtemp_cond, sizeof(currtemp_cond), "%s is defined", currtemp_s);
|
||||
}
|
||||
snprintf(topic_t, sizeof(topic_t), "~/%s", Mqtt::tag_to_topic(EMSdevice::DeviceType::THERMOSTAT, DeviceValueTAG::TAG_HC1 + hc_num - 1).c_str());
|
||||
}
|
||||
|
||||
snprintf(mode_str_tpl,
|
||||
sizeof(mode_str_tpl),
|
||||
"{%%if %s=='manual'%%}heat{%%elif %s=='day'%%}heat{%%elif %s=='night'%%}off{%%elif %s=='off'%%}off{%%else%%}auto{%%endif%%}",
|
||||
"{%%if %s%%}off{%%elif %s=='manual'%%}heat{%%elif %s=='day'%%}heat{%%elif %s=='night'%%}off{%%elif %s=='off'%%}off{%%else%%}auto{%%endif%%}",
|
||||
hc_mode_cond,
|
||||
hc_mode_s,
|
||||
hc_mode_s,
|
||||
hc_mode_s,
|
||||
@@ -1346,7 +1365,7 @@ void Mqtt::publish_ha_climate_config(const uint8_t tag, const bool has_roomtemp,
|
||||
snprintf(temp_cmd_s, sizeof(temp_cmd_s), "~/thermostat/hc%d/seltemp", hc_num);
|
||||
snprintf(mode_cmd_s, sizeof(temp_cmd_s), "~/thermostat/hc%d/mode", hc_num);
|
||||
|
||||
StaticJsonDocument<EMSESP_JSON_SIZE_HA_CONFIG> doc;
|
||||
StaticJsonDocument<EMSESP_JSON_SIZE_HA_CONFIG + 512> doc;
|
||||
|
||||
doc["~"] = mqtt_base_;
|
||||
doc["uniq_id"] = uniq_id_s;
|
||||
@@ -1356,12 +1375,10 @@ void Mqtt::publish_ha_climate_config(const uint8_t tag, const bool has_roomtemp,
|
||||
doc["mode_stat_tpl"] = mode_str_tpl;
|
||||
doc["temp_cmd_t"] = temp_cmd_s;
|
||||
doc["temp_stat_t"] = topic_t;
|
||||
doc["temp_stat_tpl"] = seltemp_s;
|
||||
doc["mode_cmd_t"] = mode_cmd_s;
|
||||
|
||||
doc["temp_stat_tpl"] = (std::string) "{{" + seltemp_s + " if " + seltemp_cond + " else 0}}";
|
||||
if (has_roomtemp) {
|
||||
doc["curr_temp_t"] = topic_t;
|
||||
doc["curr_temp_tpl"] = currtemp_s;
|
||||
doc["curr_temp_tpl"] = (std::string) "{{" + currtemp_s + " if " + currtemp_cond + " else 0}}";
|
||||
}
|
||||
|
||||
doc["min_temp"] = Helpers::render_value(min_s, min, 0, EMSESP::system_.fahrenheit() ? 2 : 0);
|
||||
@@ -1369,8 +1386,8 @@ void Mqtt::publish_ha_climate_config(const uint8_t tag, const bool has_roomtemp,
|
||||
doc["temp_step"] = "0.5";
|
||||
|
||||
// the HA climate component only responds to auto, heat and off
|
||||
JsonArray modes = doc.createNestedArray("modes");
|
||||
|
||||
doc["mode_cmd_t"] = mode_cmd_s;
|
||||
JsonArray modes = doc.createNestedArray("modes");
|
||||
modes.add("auto");
|
||||
modes.add("heat");
|
||||
modes.add("off");
|
||||
@@ -1379,6 +1396,9 @@ void Mqtt::publish_ha_climate_config(const uint8_t tag, const bool has_roomtemp,
|
||||
JsonArray ids = dev.createNestedArray("ids");
|
||||
ids.add("ems-esp-thermostat");
|
||||
|
||||
// add "availability" section
|
||||
add_avty_to_doc(topic_t, doc.as<JsonObject>(), seltemp_cond, has_roomtemp ? currtemp_cond : nullptr, hc_mode_cond);
|
||||
|
||||
publish_ha(topic, doc.as<JsonObject>()); // publish the config payload with retain flag
|
||||
}
|
||||
|
||||
@@ -1401,4 +1421,41 @@ std::string Mqtt::tag_to_topic(uint8_t device_type, uint8_t tag) {
|
||||
}
|
||||
}
|
||||
|
||||
// adds "availability" section to HA Discovery config
|
||||
void Mqtt::add_avty_to_doc(const char * state_t, const JsonObject & doc, const char * cond1, const char * cond2, const char * negcond) {
|
||||
const char * tpl_draft = "{{'online' if %s else 'offline'}}";
|
||||
char tpl[150];
|
||||
JsonArray avty = doc.createNestedArray("avty");
|
||||
|
||||
StaticJsonDocument<512> avty_json;
|
||||
|
||||
snprintf(tpl, sizeof(tpl), "%s/status", mqtt_base_.c_str());
|
||||
avty_json["t"] = tpl;
|
||||
snprintf(tpl, sizeof(tpl), tpl_draft, "value == 'online'");
|
||||
avty_json["val_tpl"] = tpl;
|
||||
avty.add(avty_json);
|
||||
avty_json.clear();
|
||||
|
||||
avty_json["t"] = state_t;
|
||||
snprintf(tpl, sizeof(tpl), tpl_draft, cond1 == nullptr ? "value is defined" : cond1);
|
||||
avty_json["val_tpl"] = tpl;
|
||||
avty.add(avty_json);
|
||||
if (cond2 != nullptr) {
|
||||
avty_json.clear();
|
||||
avty_json["t"] = state_t;
|
||||
snprintf(tpl, sizeof(tpl), tpl_draft, cond2);
|
||||
avty_json["val_tpl"] = tpl;
|
||||
avty.add(avty_json);
|
||||
}
|
||||
if (negcond != nullptr) {
|
||||
avty_json.clear();
|
||||
avty_json["t"] = state_t;
|
||||
snprintf(tpl, sizeof(tpl), "{{'offline' if %s else 'online'}}", negcond);
|
||||
avty_json["val_tpl"] = tpl;
|
||||
avty.add(avty_json);
|
||||
}
|
||||
|
||||
doc["avty_mode"] = "all";
|
||||
}
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
@@ -240,6 +240,9 @@ class Mqtt {
|
||||
|
||||
static std::string tag_to_topic(uint8_t device_type, uint8_t tag);
|
||||
|
||||
static void
|
||||
add_avty_to_doc(const char * state_t, const JsonObject & doc, const char * cond1 = nullptr, const char * cond2 = nullptr, const char * negcond = nullptr);
|
||||
|
||||
struct QueuedMqttMessage {
|
||||
const uint32_t id_;
|
||||
const std::shared_ptr<const MqttMessage> content_;
|
||||
|
||||
@@ -168,8 +168,8 @@ void Shower::set_shower_state(bool state, bool force) {
|
||||
doc["stat_t"] = stat_t;
|
||||
|
||||
if (EMSESP::system_.bool_format() == BOOL_FORMAT_TRUEFALSE) {
|
||||
doc["pl_on"] = true;
|
||||
doc["pl_off"] = false;
|
||||
doc["pl_on"] = "true";
|
||||
doc["pl_off"] = "false";
|
||||
} else if (EMSESP::system_.bool_format() == BOOL_FORMAT_10) {
|
||||
doc["pl_on"] = 1;
|
||||
doc["pl_off"] = 0;
|
||||
@@ -183,6 +183,9 @@ void Shower::set_shower_state(bool state, bool force) {
|
||||
JsonArray ids = dev.createNestedArray("ids");
|
||||
ids.add("ems-esp");
|
||||
|
||||
// add "availability" section
|
||||
Mqtt::add_avty_to_doc(stat_t, doc.as<JsonObject>());
|
||||
|
||||
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
|
||||
snprintf(topic, sizeof(topic), "binary_sensor/%s/shower_active/config", Mqtt::basename().c_str());
|
||||
Mqtt::publish_ha(topic, doc.as<JsonObject>()); // publish the config payload with retain flag
|
||||
|
||||
@@ -659,7 +659,6 @@ void System::network_init(bool refresh) {
|
||||
}
|
||||
|
||||
last_system_check_ = 0; // force the LED to go from fast flash to pulse
|
||||
send_heartbeat();
|
||||
|
||||
if (phy_type_ == PHY_type::PHY_TYPE_NONE) {
|
||||
return;
|
||||
@@ -715,7 +714,6 @@ void System::system_check() {
|
||||
digitalWrite(led_gpio_, hide_led_ ? !LED_ON : LED_ON);
|
||||
#endif
|
||||
}
|
||||
send_heartbeat();
|
||||
} else {
|
||||
// turn off LED so we're ready to the flashes
|
||||
if (led_gpio_) {
|
||||
|
||||
@@ -1 +1 @@
|
||||
#define EMSESP_APP_VERSION "3.5.1-dev.0"
|
||||
#define EMSESP_APP_VERSION "3.5.2-patch.0"
|
||||
|
||||
@@ -136,7 +136,7 @@ void WebLogService::operator<<(std::shared_ptr<uuid::log::Message> message) {
|
||||
log_messages_.pop_front();
|
||||
}
|
||||
|
||||
log_messages_.emplace_back(log_message_id_++, std::move(message));
|
||||
log_messages_.emplace_back(++log_message_id_, std::move(message));
|
||||
|
||||
EMSESP::esp8266React.getNTPSettingsService()->read([&](NTPSettings & settings) {
|
||||
if (!settings.enabled || (time(nullptr) < 1500000000L)) {
|
||||
@@ -191,9 +191,9 @@ char * WebLogService::messagetime(char * out, const uint64_t t, const size_t buf
|
||||
|
||||
// send to web eventsource
|
||||
void WebLogService::transmit(const QueuedLogMessage & message) {
|
||||
auto jsonDocument = DynamicJsonDocument(EMSESP_JSON_SIZE_MEDIUM);
|
||||
JsonObject logEvent = jsonDocument.to<JsonObject>();
|
||||
char time_string[25];
|
||||
StaticJsonDocument<EMSESP_JSON_SIZE_MEDIUM> jsonDocument;
|
||||
JsonObject logEvent = jsonDocument.to<JsonObject>();
|
||||
char time_string[25];
|
||||
|
||||
logEvent["t"] = messagetime(time_string, message.content_->uptime_ms, sizeof(time_string));
|
||||
logEvent["l"] = message.content_->level;
|
||||
@@ -212,34 +212,11 @@ void WebLogService::transmit(const QueuedLogMessage & message) {
|
||||
|
||||
// send the complete log buffer to the API, not filtering on log level
|
||||
void WebLogService::fetchLog(AsyncWebServerRequest * request) {
|
||||
// auto * response = new MsgpackAsyncJsonResponse(false, EMSESP_JSON_SIZE_LARGE_DYN + 192 * log_messages_.size());
|
||||
size_t buffer = EMSESP_JSON_SIZE_XLARGE_DYN + 192 * log_messages_.size();
|
||||
auto * response = new MsgpackAsyncJsonResponse(false, buffer);
|
||||
while (!response->getSize()) {
|
||||
delete response;
|
||||
buffer -= 1024;
|
||||
response = new MsgpackAsyncJsonResponse(false, buffer);
|
||||
}
|
||||
JsonObject root = response->getRoot();
|
||||
JsonArray log = root.createNestedArray("events");
|
||||
|
||||
log_message_id_tail_ = log_messages_.back().id_;
|
||||
last_transmit_ = uuid::get_uptime_ms();
|
||||
for (const auto & message : log_messages_) {
|
||||
JsonObject logEvent = log.createNestedObject();
|
||||
char time_string[25];
|
||||
|
||||
logEvent["t"] = messagetime(time_string, message.content_->uptime_ms, sizeof(time_string));
|
||||
logEvent["l"] = message.content_->level;
|
||||
logEvent["i"] = message.id_;
|
||||
logEvent["n"] = message.content_->name;
|
||||
logEvent["m"] = message.content_->text;
|
||||
}
|
||||
log_message_id_tail_ = log_messages_.back().id_;
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
log_message_id_tail_ = 0;
|
||||
request->send(200);
|
||||
}
|
||||
|
||||
|
||||
// sets the values like level after a POST
|
||||
void WebLogService::setValues(AsyncWebServerRequest * request, JsonVariant & json) {
|
||||
if (!json.is<JsonObject>()) {
|
||||
|
||||
@@ -47,6 +47,7 @@ void WebSettings::read(WebSettings & settings, JsonObject & root) {
|
||||
root["syslog_mark_interval"] = settings.syslog_mark_interval;
|
||||
root["syslog_host"] = settings.syslog_host;
|
||||
root["syslog_port"] = settings.syslog_port;
|
||||
root["boiler_heatingoff"] = settings.boiler_heatingoff;
|
||||
root["shower_timer"] = settings.shower_timer;
|
||||
root["shower_alert"] = settings.shower_alert;
|
||||
root["shower_alert_coldshot"] = settings.shower_alert_coldshot;
|
||||
@@ -240,38 +241,40 @@ StateUpdateResult WebSettings::update(JsonObject & root, WebSettings & settings)
|
||||
settings.low_clock = root["low_clock"] | false;
|
||||
check_flag(prev, settings.low_clock, ChangeFlags::RESTART);
|
||||
|
||||
String old_local = settings.locale;
|
||||
settings.locale = root["locale"] | EMSESP_DEFAULT_LOCALE;
|
||||
EMSESP::system_.locale(settings.locale);
|
||||
#ifndef EMSESP_STANDALONE
|
||||
if (!old_local.equals(settings.locale)) {
|
||||
add_flags(ChangeFlags::RESTART); // force restart
|
||||
}
|
||||
#endif
|
||||
|
||||
//
|
||||
// these may need mqtt restart to rebuild HA discovery topics
|
||||
//
|
||||
prev = settings.bool_format;
|
||||
settings.bool_format = root["bool_format"] | EMSESP_DEFAULT_BOOL_FORMAT;
|
||||
EMSESP::system_.bool_format(settings.bool_format);
|
||||
if (Mqtt::ha_enabled())
|
||||
if (Mqtt::ha_enabled()) {
|
||||
check_flag(prev, settings.bool_format, ChangeFlags::MQTT);
|
||||
}
|
||||
|
||||
prev = settings.enum_format;
|
||||
settings.enum_format = root["enum_format"] | EMSESP_DEFAULT_ENUM_FORMAT;
|
||||
EMSESP::system_.enum_format(settings.enum_format);
|
||||
if (Mqtt::ha_enabled())
|
||||
if (Mqtt::ha_enabled()) {
|
||||
check_flag(prev, settings.enum_format, ChangeFlags::MQTT);
|
||||
}
|
||||
|
||||
String old_locale = settings.locale;
|
||||
settings.locale = root["locale"] | EMSESP_DEFAULT_LOCALE;
|
||||
EMSESP::system_.locale(settings.locale);
|
||||
if (Mqtt::ha_enabled() && !old_locale.equals(settings.locale)) {
|
||||
add_flags(ChangeFlags::MQTT);
|
||||
}
|
||||
|
||||
//
|
||||
// without checks or necessary restarts...
|
||||
//
|
||||
|
||||
settings.trace_raw = root["trace_raw"] | EMSESP_DEFAULT_TRACELOG_RAW;
|
||||
EMSESP::trace_raw(settings.trace_raw);
|
||||
|
||||
settings.notoken_api = root["notoken_api"] | EMSESP_DEFAULT_NOTOKEN_API;
|
||||
settings.solar_maxflow = root["solar_maxflow"] | EMSESP_DEFAULT_SOLAR_MAXFLOW;
|
||||
settings.notoken_api = root["notoken_api"] | EMSESP_DEFAULT_NOTOKEN_API;
|
||||
settings.solar_maxflow = root["solar_maxflow"] | EMSESP_DEFAULT_SOLAR_MAXFLOW;
|
||||
settings.boiler_heatingoff = root["boiler_heatingoff"] | EMSESP_DEFAULT_BOILER_HEATINGOFF;
|
||||
|
||||
settings.fahrenheit = root["fahrenheit"] | false;
|
||||
EMSESP::system_.fahrenheit(settings.fahrenheit);
|
||||
|
||||
@@ -33,6 +33,7 @@ class WebSettings {
|
||||
String locale;
|
||||
uint8_t tx_mode;
|
||||
uint8_t ems_bus_id;
|
||||
bool boiler_heatingoff;
|
||||
bool shower_timer;
|
||||
bool shower_alert;
|
||||
uint8_t shower_alert_trigger;
|
||||
|
||||
Reference in New Issue
Block a user