mqtt/commands for scheduler

This commit is contained in:
MichaelDvP
2023-03-04 14:10:32 +01:00
parent 78c4a646d2
commit 0380fe1fff
19 changed files with 225 additions and 39 deletions

View File

@@ -14,8 +14,6 @@ const de: Translation = {
SU_PASSWORD: 'su Passwort',
DASHBOARD: 'Kontrollzentrum',
SETTINGS_OF: '{0} Einstellungen',
APPLY_CHANGES: 'Apply Changes ({0})', // TODO translate
UPDATE: 'Update', // TODO translate
HELP_OF: '{0} Hilfe',
LOGGED_IN: 'Eingeloggt als {name}',
PLEASE_SIGNIN: 'Bitte einloggen, um fortzufahren',
@@ -32,11 +30,11 @@ const de: Translation = {
REFRESH: 'Aktualisieren',
EXPORT: 'Exportieren',
DEVICE_DETAILS: 'Geräte Details',
BRAND: 'Marke',
ID_OF: '{0} ID',
DEVICE: 'Geräte',
PRODUCT: 'Produkt',
VERSION: 'Version',
BRAND: 'Marke',
ENTITY_NAME: 'Entitätsname',
VALUE: '{{Wert|wert}}',
SHOW_FAV: 'nur Favoriten anzeigen',
@@ -48,6 +46,8 @@ const de: Translation = {
CANCEL: 'Abbrechen',
RESET: 'Zurücksetzen',
SEND: 'Senden',
APPLY_CHANGES: 'Apply Changes ({0})', // TODO translate
UPDATE: 'Update', // TODO translate
REMOVE: 'Entfernen',
PROBLEM_UPDATING: 'Problem beim Aktualisieren',
PROBLEM_LOADING: 'Problem beim Laden',
@@ -208,7 +208,7 @@ const de: Translation = {
COMPACT: 'Kompakte Darstellung',
ENABLE_OTA: 'OTA Updates verwenden',
DOWNLOAD_CUSTOMIZATION_TEXT: 'Herunterladen der individuellen Entitätsanpassungen',
DOWNLOAD_SCHEDULE_TEXT: 'Download Scheduler Events', // TODO translate
DOWNLOAD_SCHEDULE_TEXT: 'Herunterladen geplanter Befehle',
DOWNLOAD_SETTINGS_TEXT: 'Herunterladen der Anwendungseinstellungen. Vorsicht beim Teilen der Einstellungen, da sie Passwörter und andere sensitive Einstellungen enthalten',
UPLOAD_TEXT: 'Hochladen von neuer Firmware (.bin), Geräte- oder Entitätseinstellungen (.json), zur optionalen Validitätsprüfung zuerst die (.md5) Datei hochladen',
UPLOADING: 'Hochladen',
@@ -305,20 +305,20 @@ const de: Translation = {
ENTITY: 'Entität',
MIN: 'min',
MAX: 'max',
BLOCK_NAVIGATE_1: 'You have unsaved changes', // TODO translate
BLOCK_NAVIGATE_2: 'If you navigate to a different page, your unsaved changes will be lost. Are you sure you want to leave this page?', // TODO translate
STAY: 'Stay', // TODO translate
LEAVE: 'Leave', // TODO translate
SCHEDULER: 'Scheduler', // TODO translate
SCHEDULER_HELP_1: 'Add custom scheduled commands to automate EMS-ESP', // TODO translate
SCHEDULE: 'Schedule', // TODO translate
TIME: 'Time', // TODO translate
TIMER: 'Timer', // TODO translate
WEEKLY: 'Weekly', // TODO translate
SCHEDULE_SAVED: 'Schedule updated', // TODO translate
SCHEDULE_TIMER_1: 'on startup', // TODO translate
SCHEDULE_TIMER_2: 'every minute', // TODO translate
SCHEDULE_TIMER_3: 'every hour' // TODO translate
BLOCK_NAVIGATE_1: 'Sie haben ungesicherte Änderungen',
BLOCK_NAVIGATE_2: 'Beim verlassen der Seite verlieren Sie ungesicherte Einstellungen. Wollen Sie die Seite wirklich verlassen?',
STAY: 'Bleiben',
LEAVE: 'Verlassen',
SCHEDULER: 'Planer',
SCHEDULER_HELP_1: 'Fügen Sie eigene, geplante Befehle zur Automatisierung hinzu. Vergeben Sie einen Entitätsnamen um die Aktivierung über API/Mqtt zu steuern',
SCHEDULE: 'Zeitplan',
TIME: 'Zeit',
TIMER: 'Timer',
WEEKLY: 'Wöchentlich',
SCHEDULE_SAVED: 'Plan gespeichert',
SCHEDULE_TIMER_1: 'beim Start',
SCHEDULE_TIMER_2: 'jede Minute',
SCHEDULE_TIMER_3: 'jede Stunde'
};
export default de;

View File

@@ -310,7 +310,7 @@ const en: Translation = {
STAY: 'Stay',
LEAVE: 'Leave',
SCHEDULER: 'Scheduler',
SCHEDULER_HELP_1: 'Add custom scheduled commands to automate EMS-ESP',
SCHEDULER_HELP_1: 'Add custom scheduled commands to automate EMS-ESP. Add entity name to control activation by api/mqtt',
SCHEDULE: 'Schedule',
TIME: 'Time',
TIMER: 'Timer',

View File

@@ -310,7 +310,7 @@ const fr: Translation = {
STAY: 'Stay', // TODO translate
LEAVE: 'Leave', // TODO translate
SCHEDULER: 'Scheduler', // TODO translate
SCHEDULER_HELP_1: 'Add custom scheduled commands to automate EMS-ESP', // TODO translate
SCHEDULER_HELP_1: 'Add custom scheduled commands to automate EMS-ESP. Add entity name to control activation by api/mqtt', // TODO translate
SCHEDULE: 'Schedule', // TODO translate
TIME: 'Time', // TODO translate
TIMER: 'Timer', // TODO translate

View File

@@ -310,7 +310,7 @@ const nl: Translation = {
STAY: 'Stay', // TODO translate
LEAVE: 'Leave', // TODO translate
SCHEDULER: 'Scheduler', // TODO translate
SCHEDULER_HELP_1: 'Add custom scheduled commands to automate EMS-ESP', // TODO translate
SCHEDULER_HELP_1: 'Add custom scheduled commands to automate EMS-ESP. Add entity name to control activation by api/mqtt', // TODO translate
SCHEDULE: 'Schedule', // TODO translate
TIME: 'Time', // TODO translate
TIMER: 'Timer', // TODO translate

View File

@@ -310,7 +310,7 @@ const no: Translation = {
STAY: 'Stay', // TODO translate
LEAVE: 'Leave', // TODO translate
SCHEDULER: 'Scheduler', // TODO translate
SCHEDULER_HELP_1: 'Add custom scheduled commands to automate EMS-ESP', // TODO translate
SCHEDULER_HELP_1: 'Add custom scheduled commands to automate EMS-ESP. Add entity name to control activation by api/mqtt', // TODO translate
SCHEDULE: 'Schedule', // TODO translate
TIME: 'Time', // TODO translate
TIMER: 'Timer', // TODO translate

View File

@@ -310,7 +310,7 @@ const pl: BaseTranslation = {
STAY: 'Stay', // TODO translate
LEAVE: 'Leave', // TODO translate
SCHEDULER: 'Scheduler', // TODO translate
SCHEDULER_HELP_1: 'Add custom scheduled commands to automate EMS-ESP', // TODO translate
SCHEDULER_HELP_1: 'Add custom scheduled commands to automate EMS-ESP. Add entity name to control activation by api/mqtt', // TODO translate
SCHEDULE: 'Schedule', // TODO translate SCHEDULE: 'Schedule', // TODO translate
TIME: 'Time', // TODO translate
TIMER: 'Timer', // TODO translate

View File

@@ -310,7 +310,7 @@ const sv: Translation = {
STAY: 'Stay', // TODO translate
LEAVE: 'Leave', // TODO translate
SCHEDULER: 'Scheduler', // TODO translate
SCHEDULER_HELP_1: 'Add custom scheduled commands to automate EMS-ESP', // TODO translate
SCHEDULER_HELP_1: 'Add custom scheduled commands to automate EMS-ESP. Add entity name to control activation by api/mqtt', // TODO translate
SCHEDULE: 'Schedule', // TODO translate
TIME: 'Time', // TODO translate
TIMER: 'Timer', // TODO translate

View File

@@ -310,7 +310,7 @@ const tr: Translation = {
STAY: 'Stay', // TODO translate
LEAVE: 'Leave', // TODO translate
SCHEDULER: 'Scheduler', // TODO translate
SCHEDULER_HELP_1: 'Add custom scheduled commands to automate EMS-ESP.', // TODO translate
SCHEDULER_HELP_1: 'Add custom scheduled commands to automate EMS-ESP. Add entity name to control activation by api/mqtt', // TODO translate
SCHEDULE: 'Schedule', // TODO translate
TIME: 'Time', // TODO translate
TIMER: 'Timer', // TODO translate

View File

@@ -464,7 +464,7 @@ const SettingsScheduler: FC = () => {
)}
<TextField
name="description"
label={LL.DESCRIPTION()}
label={LL.ENTITY_NAME()}
value={scheduleItem.description}
fullWidth
margin="normal"

View File

@@ -128,7 +128,8 @@ void AnalogSensor::reload() {
[&](const char * value, const int8_t id) { return command_setvalue(value, sensor.gpio); },
sensor.type == AnalogType::COUNTER ? FL_(counter)
: sensor.type == AnalogType::DIGITAL_OUT ? FL_(digital_out)
: FL_(pwm));
: FL_(pwm),
CommandFlag::ADMIN_ONLY);
}
}
return true;

View File

@@ -145,6 +145,33 @@ uint8_t Command::process(const char * path, const bool is_admin, const JsonObjec
data = input["value"];
}
// check if data is entity like device/hc/name/value
if (data.is<const char *>()) {
const char * d = data.as<const char *>();
if (strlen(d)) {
char * device_end = strchr(d, '/');
if (device_end != nullptr) {
char device_s[15] = {'\0'};
const char * device_p = device_s;
const char * data_p = nullptr;
strlcpy(device_s, d, device_end - d + 1);
data_p = device_end + 1;
int8_t id_d = -1;
data_p = parse_command_string(data_p, id_d);
char data_s[50];
strcpy(data_s, data_p);
strcat(data_s, "/value");
uint8_t device_type = EMSdevice::device_name_2_device_type(device_p);
if (CommandRet::OK == Command::call(device_type, data_s, "", true, id_d, output)) {
if (output.containsKey("api_data")) {
data = output["api_data"];
}
}
output.clear();
}
}
}
// call the command based on the type
uint8_t return_code = CommandRet::ERROR;
if (data.is<const char *>()) {
@@ -204,11 +231,13 @@ const char * Command::parse_command_string(const char * command, int8_t & id) {
id = command[2] - '0';
command += 3;
} else if (!strncmp(lowerCmd, "wwc", 3) && command[3] == '1' && command[4] == '0') {
id = DeviceValueTAG::TAG_WWC10 - DeviceValueTAG::TAG_HC1 + 1; //18; } else if (!strncmp(lowerCmd, "wwc", 3) && command[3] >= '1' && command[3] <= '9') {
id = command[3] - '0' + 8;
id = DeviceValueTAG::TAG_WWC10 - DeviceValueTAG::TAG_HC1 + 1; //18;
command += 5;
} else if (!strncmp(lowerCmd, "wwc", 3) && command[3] >= '1' && command[3] <= '9') {
id = command[3] - '1' + DeviceValueTAG::TAG_WWC1 - DeviceValueTAG::TAG_HC1 + 1; //9;
command += 4;
} else if (!strncmp(lowerCmd, "id", 2) && command[2] == '1' && command[3] >= '0' && command[3] <= '9') {
id = command[3] - '1' + DeviceValueTAG::TAG_WWC1 - DeviceValueTAG::TAG_HC1 + 1; //9;
id = command[3] - '0' + 10;
command += 4;
} else if (!strncmp(lowerCmd, "id", 2) && command[2] >= '1' && command[2] <= '9') {
id = command[2] - '0';
@@ -499,6 +528,10 @@ bool Command::device_has_commands(const uint8_t device_type) {
return true; // we always have System
}
if (device_type == EMSdevice::DeviceType::SCHEDULER) {
return EMSESP::webSchedulerService.has_commands();
}
if (device_type == EMSdevice::DeviceType::DALLASSENSOR) {
return (EMSESP::dallassensor_.have_sensors());
}
@@ -555,6 +588,13 @@ void Command::show_all(uuid::console::Shell & shell) {
shell.print(COLOR_RESET);
show(shell, EMSdevice::DeviceType::SYSTEM, true);
// show scheduler
shell.print(COLOR_BOLD_ON);
shell.print(COLOR_YELLOW);
shell.printf(" %s: ", EMSdevice::device_type_2_device_name(EMSdevice::DeviceType::SCHEDULER));
shell.print(COLOR_RESET);
show(shell, EMSdevice::DeviceType::SCHEDULER, true);
// show sensors
if (EMSESP::dallassensor_.have_sensors()) {
shell.print(COLOR_BOLD_ON);

View File

@@ -105,6 +105,8 @@ const char * EMSdevice::device_type_2_device_name(const uint8_t device_type) {
switch (device_type) {
case DeviceType::SYSTEM:
return F_(system);
case DeviceType::SCHEDULER:
return F_(scheduler);
case DeviceType::BOILER:
return F_(boiler);
case DeviceType::THERMOSTAT:
@@ -194,6 +196,9 @@ uint8_t EMSdevice::device_name_2_device_type(const char * topic) {
if (!strcmp(lowtopic, F_(system))) {
return DeviceType::SYSTEM;
}
if (!strcmp(lowtopic, F_(scheduler))) {
return DeviceType::SCHEDULER;
}
if (!strcmp(lowtopic, F_(heatpump))) {
return DeviceType::HEATPUMP;
}
@@ -225,10 +230,6 @@ uint8_t EMSdevice::device_name_2_device_type(const char * topic) {
return DeviceType::HEATSOURCE;
}
if (!strcmp(lowtopic, F_(heatsource))) {
return DeviceType::HEATSOURCE;
}
return DeviceType::UNKNOWN;
}
@@ -550,10 +551,6 @@ void EMSdevice::add_device_value(uint8_t tag, // to b
return;
}
if (ignore) {
return;
}
// add the device entity
devicevalues_.emplace_back(
device_type_, tag, value_p, type, options, options_single, numeric_operator, short_name, fullname, custom_fullname, uom, has_cmd, min, max, state);

View File

@@ -322,6 +322,7 @@ class EMSdevice {
SYSTEM = 0, // this is us (EMS-ESP)
DALLASSENSOR, // for internal dallas sensors
ANALOGSENSOR, // for internal analog sensors
SCHEDULER,
BOILER,
THERMOSTAT,
MIXER,

View File

@@ -479,6 +479,7 @@ void EMSESP::publish_all(bool force) {
publish_device_values(EMSdevice::DeviceType::SOLAR);
publish_device_values(EMSdevice::DeviceType::MIXER);
publish_other_values(); // switch and heat pump, ...
webSchedulerService.publish();
publish_sensor_values(true); // includes dallas and analog sensors
system_.send_heartbeat();
}
@@ -510,6 +511,7 @@ void EMSESP::publish_all_loop() {
break;
case 5:
publish_other_values(); // switch and heat pump
webSchedulerService.publish(true);
break;
case 6:
publish_sensor_values(true, true);

View File

@@ -97,6 +97,7 @@ MAKE_WORD(dallassensor)
MAKE_WORD(alert)
MAKE_WORD(pump)
MAKE_WORD(heatsource)
MAKE_WORD(scheduler)
// brands
MAKE_WORD_CUSTOM(bosch, "Bosch")

View File

@@ -61,6 +61,7 @@ MAKE_TRANSLATION(restart_cmd, "restart EMS-ESP", "Neustart", "", "", "", "", "",
MAKE_TRANSLATION(watch_cmd, "watch incoming telegrams", "Watch auf eingehende Telegramme", "", "", "", "", "", "Gelen telegramları ")
MAKE_TRANSLATION(publish_cmd, "publish all to MQTT", "Publiziere MQTT", "", "", "", "", "", "Hepsini MQTTye gönder")
MAKE_TRANSLATION(system_info_cmd, "show system status", "Zeige System-Status", "", "", "", "", "", "Sistem Durumunu Göster")
MAKE_TRANSLATION(schedule_cmd, "enable schedule item", "Aktiviere Zeitplan", "", "", "", "", "", "")
// tags
MAKE_TRANSLATION(tag_boiler_data_ww, "dhw", "WW", "dhw", "VV", "CWU", "dhw", "ecs", "SKS")

View File

@@ -182,6 +182,7 @@ void Mqtt::loop() {
if (publish_time_other_ && (currentMillis - last_publish_other_ > publish_time_other_)) {
last_publish_other_ = (currentMillis / publish_time_other_) * publish_time_other_;
EMSESP::publish_other_values(); // switch and heatpump
EMSESP::webSchedulerService.publish();
} else
if (publish_time_sensor_ && (currentMillis - last_publish_sensor_ > publish_time_sensor_)) {

View File

@@ -64,6 +64,9 @@ StateUpdateResult WebScheduler::update(JsonObject & root, WebScheduler & webSche
Serial.println(COLOR_RESET);
#endif
for (ScheduleItem & scheduleItem : webScheduler.scheduleItems) {
Command::erase_command(EMSdevice::DeviceType::SCHEDULER, scheduleItem.description.c_str());
}
webScheduler.scheduleItems.clear();
if (root["schedule"].is<JsonArray>()) {
@@ -84,11 +87,146 @@ StateUpdateResult WebScheduler::update(JsonObject & root, WebScheduler & webSche
si.retry_cnt = 0xFF; // no starup retries
webScheduler.scheduleItems.push_back(si); // add to list
if (!webScheduler.scheduleItems.back().description.empty()) {
Command::add(
EMSdevice::DeviceType::SCHEDULER,
webScheduler.scheduleItems.back().description.c_str(),
[webScheduler](const char * value, const int8_t id) {
return EMSESP::webSchedulerService.command_setvalue(value, webScheduler.scheduleItems.back().description);
},
FL_(schedule_cmd),
CommandFlag::ADMIN_ONLY);
}
}
}
EMSESP::webSchedulerService.publish(true);
return StateUpdateResult::CHANGED;
}
// set active by api command
bool WebSchedulerService::command_setvalue(const char * value, const std::string name) {
bool v;
if (!Helpers::value2bool(value, v)) {
return false;
}
EMSESP::webSchedulerService.read([&](WebScheduler & webScheduler) { scheduleItems = &webScheduler.scheduleItems; });
for (ScheduleItem & scheduleItem : *scheduleItems) {
if (scheduleItem.description == name) {
if (scheduleItem.active == v) {
return true;
}
scheduleItem.active = v;
publish_single(name.c_str(), v);
if (EMSESP::mqtt_.get_publish_onchange(0)) {
publish();
}
return true;
}
}
return false;
}
void WebSchedulerService::publish_single(const char * name, const bool state) {
if (!Mqtt::publish_single() || name == nullptr || name[0] == '\0') {
return;
}
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
if (Mqtt::publish_single2cmd()) {
snprintf(topic, sizeof(topic), "%s/%s", F_(scheduler), name);
} else {
snprintf(topic, sizeof(topic), "%s%s/%s", F_(scheduler), "_data", name);
}
char payload[12];
Mqtt::queue_publish(topic, Helpers::render_boolean(payload, state));
}
// publish to Mqtt
void WebSchedulerService::publish(const bool force) {
EMSESP::webSchedulerService.read([&](WebScheduler & webScheduler) { scheduleItems = &webScheduler.scheduleItems; });
if (scheduleItems->size() == 0) {
return;
}
if (Mqtt::publish_single() && force) {
for (const ScheduleItem & scheduleItem : *scheduleItems) {
publish_single(scheduleItem.description.c_str(), scheduleItem.active);
}
}
DynamicJsonDocument doc(EMSESP_JSON_SIZE_XLARGE);
for (const ScheduleItem & scheduleItem : *scheduleItems) {
if (!scheduleItem.description.empty() && !doc.containsKey(scheduleItem.description)) {
if (EMSESP::system_.bool_format() == BOOL_FORMAT_TRUEFALSE) {
doc[scheduleItem.description] = scheduleItem.active;
} else if (EMSESP::system_.bool_format() == BOOL_FORMAT_10) {
doc[scheduleItem.description] = scheduleItem.active ? 1 : 0;
} else {
char result[12];
doc[scheduleItem.description] = Helpers::render_boolean(result, scheduleItem.active);
}
// create HA config
if (Mqtt::ha_enabled() && force) {
StaticJsonDocument<EMSESP_JSON_SIZE_MEDIUM> config;
char stat_t[50];
snprintf(stat_t, sizeof(stat_t), "%s/scheduler_data", Mqtt::base().c_str());
config["stat_t"] = stat_t;
char val_obj[50];
char val_cond[65];
snprintf(val_obj, sizeof(val_obj), "value_json['%s']", scheduleItem.description.c_str());
snprintf(val_cond, sizeof(val_cond), "%s is defined", val_obj);
config["val_tpl"] = (std::string) "{{" + val_obj + " if " + val_cond + "}}";
char uniq_s[70];
snprintf(uniq_s, sizeof(uniq_s), "scheduler_%s", scheduleItem.description.c_str());
config["obj_id"] = uniq_s;
config["uniq_id"] = uniq_s; // same as object_id
config["name"] = scheduleItem.description.c_str();
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
char command_topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
snprintf(topic, sizeof(topic), "switch/%s/scheduler_%s/config", Mqtt::basename().c_str(), scheduleItem.description.c_str());
snprintf(command_topic, sizeof(command_topic), "%s/scheduler/%s", Mqtt::basename().c_str(), scheduleItem.description.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);
}
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);
Mqtt::queue_ha(topic, config.as<JsonObject>());
}
}
}
Mqtt::queue_publish("scheduler_data", doc.as<JsonObject>());
}
bool WebSchedulerService::has_commands() {
EMSESP::webSchedulerService.read([&](WebScheduler & webScheduler) { scheduleItems = &webScheduler.scheduleItems; });
if (scheduleItems->size() == 0) {
return false;
}
for (const ScheduleItem & scheduleItem : *scheduleItems) {
if (!scheduleItem.description.empty()) {
return true;
}
}
return false;
}
// execute scheduled command
bool WebSchedulerService::command(const char * cmd, const char * data) {
StaticJsonDocument<EMSESP_JSON_SIZE_SMALL> doc_input;

View File

@@ -54,6 +54,10 @@ class WebSchedulerService : public StatefulService<WebScheduler> {
void begin();
void loop();
void publish_single(const char * name, const bool state);
void publish(const bool force = false);
bool has_commands();
bool command_setvalue(const char * value, const std::string name);
private:
bool command(const char * cmd, const char * data);