test response and custom entity raw, #1212

This commit is contained in:
MichaelDvP
2023-07-13 11:09:42 +02:00
parent 25ff2bd150
commit 17b4964b01
15 changed files with 152 additions and 80 deletions

View File

@@ -17,7 +17,7 @@ import {
} from '@mui/material';
import { useEffect, useState } from 'react';
import { DeviceValueUOM_s } from './types';
import { DeviceValueUOM_s, DeviceValueType } from './types';
import type { EntityItem } from './types';
import type Schema from 'async-validator';
import type { ValidateFieldsError } from 'async-validator';
@@ -166,17 +166,18 @@ const SettingsEntitiesDialog = ({
fullWidth
select
>
<MenuItem value={0}>BOOL</MenuItem>
<MenuItem value={1}>INT</MenuItem>
<MenuItem value={2}>UINT</MenuItem>
<MenuItem value={3}>SHORT</MenuItem>
<MenuItem value={4}>USHORT</MenuItem>
<MenuItem value={5}>ULONG</MenuItem>
<MenuItem value={6}>TIME</MenuItem>
<MenuItem value={DeviceValueType.BOOL}>BOOL</MenuItem>
<MenuItem value={DeviceValueType.INT}>INT</MenuItem>
<MenuItem value={DeviceValueType.UINT}>UINT</MenuItem>
<MenuItem value={DeviceValueType.SHORT}>SHORT</MenuItem>
<MenuItem value={DeviceValueType.USHORT}>USHORT</MenuItem>
<MenuItem value={DeviceValueType.ULONG}>ULONG</MenuItem>
<MenuItem value={DeviceValueType.TIME}>TIME</MenuItem>
<MenuItem value={DeviceValueType.STRING}>RAW</MenuItem>
</TextField>
</Grid>
{editItem.value_type !== 0 && (
{editItem.value_type !== DeviceValueType.BOOL && editItem.value_type !== DeviceValueType.STRING && (
<>
<Grid item xs={4}>
<TextField
@@ -210,6 +211,21 @@ const SettingsEntitiesDialog = ({
</Grid>
</>
)}
{editItem.value_type === DeviceValueType.STRING && (
<Grid item xs={4}>
<TextField
name="factor"
label="Bytes"
value={editItem.factor}
variant="outlined"
onChange={updateFormValue}
fullWidth
margin="normal"
type="number"
inputProps={{ min: '1', max: '28', step: '1' }}
/>
</Grid>
)}
</Grid>
</DialogContent>

View File

@@ -364,3 +364,17 @@ export const enum DeviceType {
CUSTOM,
UNKNOWN
}
// matches emsdevicevalue.h
export const enum DeviceValueType {
BOOL,
INT,
UINT,
SHORT,
USHORT,
ULONG,
TIME, // same as ULONG (32 bits)
ENUM,
STRING,
CMD
}

View File

@@ -410,14 +410,16 @@ static void setup_commands(std::shared_ptr<Commands> & commands) {
if (arguments.size() == 4) {
uint16_t offset = Helpers::hextoint(arguments[2].c_str());
uint8_t length = Helpers::hextoint(arguments.back().c_str());
to_app(shell).send_read_request(type_id, device_id, offset, length);
to_app(shell).send_read_request(type_id, device_id, offset, length, true);
} else if (arguments.size() == 3) {
uint16_t offset = Helpers::hextoint(arguments.back().c_str());
to_app(shell).send_read_request(type_id, device_id, offset, EMS_MAX_TELEGRAM_LENGTH);
to_app(shell).send_read_request(type_id, device_id, offset, EMS_MAX_TELEGRAM_LENGTH, true);
} else {
// send with length to send immediately and trigger publish read_id
to_app(shell).send_read_request(type_id, device_id, 0, EMS_MAX_TELEGRAM_LENGTH);
to_app(shell).send_read_request(type_id, device_id, 0, EMS_MAX_TELEGRAM_LENGTH, true);
}
to_app(shell).set_read_id(type_id);
});
commands->add_command(ShellContext::MAIN,

View File

@@ -76,6 +76,7 @@ uint8_t EMSESP::watch_ = 0; // trace off
uint16_t EMSESP::read_id_ = WATCH_ID_NONE;
bool EMSESP::read_next_ = false;
uint16_t EMSESP::publish_id_ = 0;
uint16_t EMSESP::response_id_ = 0;
bool EMSESP::tap_water_active_ = false; // for when Boiler states we having running warm water. used in Shower()
uint32_t EMSESP::last_fetch_ = 0;
uint8_t EMSESP::publish_all_idx_ = 0;
@@ -847,10 +848,11 @@ void EMSESP::process_version(std::shared_ptr<const Telegram> telegram) {
// returns false if there are none found
bool EMSESP::process_telegram(std::shared_ptr<const Telegram> telegram) {
// if watching or reading...
if ((telegram->type_id == read_id_) && (telegram->dest == txservice_.ems_bus_id())) {
LOG_INFO("%s", pretty_telegram(telegram).c_str());
if (Mqtt::send_response()) {
if ((telegram->type_id == read_id_ || telegram->type_id == response_id_) && (telegram->dest == txservice_.ems_bus_id())) {
LOG_NOTICE("%s", pretty_telegram(telegram).c_str());
if (telegram->type_id == response_id_) {
publish_response(telegram);
response_id_ = 0;
}
if (!read_next_) {
@@ -1233,8 +1235,8 @@ bool EMSESP::command_info(uint8_t device_type, JsonObject & output, const int8_t
}
// send a read request, passing it into to the Tx Service, with optional offset and length
void EMSESP::send_read_request(const uint16_t type_id, const uint8_t dest, const uint8_t offset, const uint8_t length) {
txservice_.read_request(type_id, dest, offset, length);
void EMSESP::send_read_request(const uint16_t type_id, const uint8_t dest, const uint8_t offset, const uint8_t length, const bool front) {
txservice_.read_request(type_id, dest, offset, length, front);
}
// sends write request

View File

@@ -117,7 +117,7 @@ class EMSESP {
static bool process_telegram(std::shared_ptr<const Telegram> telegram);
static std::string pretty_telegram(std::shared_ptr<const Telegram> telegram);
static void send_read_request(const uint16_t type_id, const uint8_t dest, const uint8_t offset = 0, const uint8_t length = 0);
static void send_read_request(const uint16_t type_id, const uint8_t dest, const uint8_t offset = 0, const uint8_t length = 0, const bool front = false);
static void send_write_request(const uint16_t type_id,
const uint8_t dest,
const uint8_t offset,
@@ -176,6 +176,10 @@ class EMSESP {
read_id_ = id;
}
static void set_response_id(uint16_t id) {
response_id_ = id;
}
static bool wait_validate() {
return (wait_validate_ != 0);
}
@@ -259,6 +263,7 @@ class EMSESP {
static uint16_t read_id_;
static bool read_next_;
static uint16_t publish_id_;
static uint16_t response_id_;
static bool tap_water_active_;
static uint8_t publish_all_idx_;
static uint8_t unique_id_count_;

View File

@@ -69,6 +69,7 @@ MAKE_WORD_TRANSLATION(publish_cmd, "publish all to MQTT", "Publiziere MQTT", "",
MAKE_WORD_TRANSLATION(system_info_cmd, "show system status", "Zeige System-Status", "", "", "pokaż status systemu", "vis system status", "", "Sistem Durumunu Göster", "visualizza stati di sistema") // TODO translate
MAKE_WORD_TRANSLATION(schedule_cmd, "enable schedule item", "Aktiviere Zeitplan", "", "", "aktywuj wybrany harmonogram", "", "", "", "abilitare l'elemento programmato") // TODO translate
MAKE_WORD_TRANSLATION(entity_cmd, "set custom value on ems", "Sende eigene Entitäten zu EMS", "", "", "wyślij własną wartość na EMS", "", "", "", "imposta valori personalizzati su EMS") // TODO translate
MAKE_WORD_TRANSLATION(commands_response, "get response") // TODO translate
// tags
MAKE_WORD_TRANSLATION(tag_boiler_data_ww, "dhw", "WW", "dhw", "VV", "CWU", "dhw", "ecs", "SKS", "dhw")

View File

@@ -60,6 +60,7 @@ char will_topic_[Mqtt::MQTT_TOPIC_MAX_SIZE]; // because MQTT library keeps o
std::string Mqtt::lasttopic_ = "";
std::string Mqtt::lastpayload_ = "";
std::string Mqtt::lastresponse_ = "";
// Home Assistant specific
// icons from https://materialdesignicons.com used with the UOMs (unit of measurements)
@@ -284,15 +285,11 @@ void Mqtt::on_message(const char * topic, const char * payload, size_t len) cons
snprintf(error, sizeof(error), "Call failed with error code (%s)", Command::return_code_string(return_code).c_str());
}
LOG_ERROR(error);
if (send_response_) {
Mqtt::queue_publish("response", error);
}
} else {
// all good, send back json output from call
if (send_response_) {
Mqtt::queue_publish("response", output);
}
}
}
// print all the topics related to a specific device type
@@ -479,7 +476,7 @@ void Mqtt::on_disconnect(espMqttClientTypes::DisconnectReason reason) {
}
}
// MQTT onConnect - when an MQTT connect is established
// MQTT on_connect - when an MQTT connect is established
void Mqtt::on_connect() {
if (connecting_) {
return; // prevent duplicated connections
@@ -588,7 +585,13 @@ void Mqtt::ha_status() {
// add sub or pub task to the queue.
// the base is not included in the topic
bool Mqtt::queue_message(const uint8_t operation, const std::string & topic, const std::string & payload, const bool retain) {
if (topic.empty()) {
if (topic == "response" && operation == Operation::PUBLISH) {
lastresponse_ = payload;
if (!send_response_) {
return true;
}
}
if (!enabled() || topic.empty()) {
return false;
}
uint16_t packet_id = 0;
@@ -621,9 +624,6 @@ bool Mqtt::queue_message(const uint8_t operation, const std::string & topic, con
// add MQTT message to queue, payload is a string
bool Mqtt::queue_publish_message(const std::string & topic, const std::string & payload, const bool retain) {
if (!enabled()) {
return false;
};
return queue_message(Operation::PUBLISH, topic, payload, retain);
}
@@ -672,8 +672,9 @@ bool Mqtt::queue_publish_retain(const std::string & topic, const JsonObject & pa
}
bool Mqtt::queue_publish_retain(const char * topic, const JsonObject & payload, const bool retain) {
if (enabled() && payload.size()) {
if (payload.size()) {
std::string payload_text;
payload_text.reserve(measureJson(payload) + 1);
serializeJson(payload, payload_text); // convert json to string
return queue_publish_message(topic, payload_text, retain);
}
@@ -682,10 +683,6 @@ bool Mqtt::queue_publish_retain(const char * topic, const JsonObject & payload,
// publish empty payload to remove the topic
bool Mqtt::queue_remove_topic(const char * topic) {
if (!enabled()) {
return false;
}
if (ha_enabled_) {
return queue_publish_message(Mqtt::discovery_prefix() + topic, "", true); // publish with retain to remove from broker
} else {

View File

@@ -205,12 +205,8 @@ class Mqtt {
ha_climate_reset_ = reset;
}
static bool send_response() {
return send_response_;
}
static void send_response(bool send_response) {
send_response_ = send_response;
static std::string get_response() {
return lastresponse_;
}
void set_qos(uint8_t mqtt_qos) const {
@@ -274,6 +270,7 @@ class Mqtt {
static std::string lasttopic_;
static std::string lastpayload_;
static std::string lastresponse_;
// settings, copied over
static std::string mqtt_base_;

View File

@@ -98,6 +98,18 @@ bool System::command_send(const char * value, const int8_t id) {
return EMSESP::txservice_.send_raw(value); // ignore id
}
bool System::command_response(const char * value, const int8_t id, JsonObject & output) {
StaticJsonDocument<EMSESP_JSON_SIZE_SMALL> doc;
if (DeserializationError::Ok == deserializeJson(doc, Mqtt::get_response())) {
for (JsonPair p : doc.as<JsonObject>()) {
output[p.key()] = p.value();
}
} else {
output["response"] = Mqtt::get_response();
}
return true;
}
// fetch device values
bool System::command_fetch(const char * value, const int8_t id) {
std::string value_s;
@@ -753,6 +765,7 @@ void System::commands_init() {
// these commands will return data in JSON format
Command::add(EMSdevice::DeviceType::SYSTEM, F_(info), System::command_info, FL_(system_info_cmd));
Command::add(EMSdevice::DeviceType::SYSTEM, F_(commands), System::command_commands, FL_(commands_cmd));
Command::add(EMSdevice::DeviceType::SYSTEM, F("response"), System::command_response, FL_(commands_response));
// MQTT subscribe "ems-esp/system/#"
Mqtt::subscribe(EMSdevice::DeviceType::SYSTEM, "system/#", nullptr); // use empty function callback

View File

@@ -59,6 +59,7 @@ class System {
static bool command_watch(const char * value, const int8_t id);
static bool command_info(const char * value, const int8_t id, JsonObject & output);
static bool command_commands(const char * value, const int8_t id, JsonObject & output);
static bool command_response(const char * value, const int8_t id, JsonObject & output);
#if defined(EMSESP_TEST)
static bool command_test(const char * value, const int8_t id);
#endif

View File

@@ -512,11 +512,11 @@ void TxService::add(uint8_t operation, const uint8_t * data, const uint8_t lengt
operation = Telegram::Operation::NONE; // do not check reply/ack for other ids
} else if (dest & 0x80) {
operation = Telegram::Operation::TX_READ;
EMSESP::set_response_id(type_id);
} else {
operation = Telegram::Operation::TX_WRITE;
validate_id = type_id;
}
EMSESP::set_read_id(type_id);
}
auto telegram = std::make_shared<Telegram>(operation, src, dest, type_id, offset, message_data, message_length); // operation is TX_WRITE or TX_READ
@@ -546,16 +546,14 @@ void TxService::add(uint8_t operation, const uint8_t * data, const uint8_t lengt
}
// send a Tx telegram to request data from an EMS device
void TxService::read_request(const uint16_t type_id, const uint8_t dest, const uint8_t offset, const uint8_t length) {
void TxService::read_request(const uint16_t type_id, const uint8_t dest, const uint8_t offset, const uint8_t length, const bool front) {
LOG_DEBUG("Tx read request to device 0x%02X for type ID 0x%02X", dest, type_id);
uint8_t message_data = (type_id > 0xFF) ? (EMS_MAX_TELEGRAM_MESSAGE_LENGTH - 2) : EMS_MAX_TELEGRAM_MESSAGE_LENGTH;
// if length set, publish result and set telegram to front
if (length) {
if (length > 0 && length < message_data) {
message_data = length;
EMSESP::set_read_id(type_id);
}
add(Telegram::Operation::TX_READ, dest, type_id, offset, &message_data, 1, 0, length != 0);
add(Telegram::Operation::TX_READ, dest, type_id, offset, &message_data, 1, 0, front);
}
// Send a raw telegram to the bus, telegram is a text string of hex values
@@ -566,32 +564,16 @@ bool TxService::send_raw(const char * telegram_data) {
// since the telegram data is a const, make a copy. add 1 to grab the \0 EOS
char telegram[EMS_MAX_TELEGRAM_LENGTH * 3];
for (uint8_t i = 0; i < strlen(telegram_data); i++) {
telegram[i] = telegram_data[i];
}
telegram[strlen(telegram_data)] = '\0'; // make sure its terminated
strlcpy(telegram, telegram_data, sizeof(telegram));
uint8_t count = 0;
char * p;
char value[10] = {0};
uint8_t data[EMS_MAX_TELEGRAM_LENGTH];
// get first value, which should be the src
if ((p = strtok(telegram, " ,"))) { // delimiter
strlcpy(value, p, sizeof(value));
data[0] = (uint8_t)strtol(value, 0, 16);
} else {
return false;
}
// and iterate until end
while (p != 0) {
if ((p = strtok(nullptr, " ,"))) {
strlcpy(value, p, sizeof(value));
auto val = (uint8_t)strtol(value, 0, 16);
data[++count] = val;
}
// get values
char * p = strtok(telegram, " ,"); // delimiter
while (p != nullptr) {
data[count++] = (uint8_t)strtol(p, 0, 16);
p = strtok(nullptr, " ,");
}
// check valid length
@@ -599,7 +581,7 @@ bool TxService::send_raw(const char * telegram_data) {
return false;
}
add(Telegram::Operation::TX_RAW, data, count + 1, 0, true); // add to top/front of Tx queue
add(Telegram::Operation::TX_RAW, data, count, 0, true); // add to top/front of Tx queue
return true;
}

View File

@@ -317,7 +317,7 @@ class TxService : public EMSbus {
const uint16_t validateid,
const bool front = false);
void add(const uint8_t operation, const uint8_t * data, const uint8_t length, const uint16_t validateid, const bool front = false);
void read_request(const uint16_t type_id, const uint8_t dest, const uint8_t offset = 0, const uint8_t length = 0);
void read_request(const uint16_t type_id, const uint8_t dest, const uint8_t offset = 0, const uint8_t length = 0, const bool readId = false);
bool send_raw(const char * telegram_data);
void send_poll() const;
void retry_tx(const uint8_t operation, const uint8_t * data, const uint8_t length);

View File

@@ -90,7 +90,6 @@ StateUpdateResult WebEntity::update(JsonObject & root, WebEntity & webEntity) {
entityItem.factor = 1;
}
webEntity.entityItems.push_back(entityItem); // add to list
if (entityItem.writeable) {
Command::add(
EMSdevice::DeviceType::CUSTOM,
@@ -111,7 +110,21 @@ bool WebEntityService::command_setvalue(const char * value, const std::string na
EMSESP::webEntityService.read([&](WebEntity & webEntity) { entityItems = &webEntity.entityItems; });
for (EntityItem & entityItem : *entityItems) {
if (Helpers::toLower(entityItem.name) == Helpers::toLower(name)) {
if (entityItem.value_type == DeviceValueType::BOOL) {
if (entityItem.value_type == DeviceValueType::STRING) {
char telegram[84];
strlcpy(telegram, value, sizeof(telegram));
uint8_t data[EMS_MAX_TELEGRAM_LENGTH];
uint8_t count = 0;
char * p = strtok(telegram, " ,"); // delimiter
while (p != nullptr) {
data[count++] = (uint8_t)strtol(p, 0, 16);
p = strtok(nullptr, " ,");
}
if (count == 0) {
return false;
}
EMSESP::send_write_request(entityItem.type_id, entityItem.device_id, entityItem.offset, data, count, 0);
} else if (entityItem.value_type == DeviceValueType::BOOL) {
bool v;
if (!Helpers::value2bool(value, v)) {
return false;
@@ -188,6 +201,11 @@ void WebEntityService::render_value(JsonObject & output, EntityItem entity, cons
output[name] = serialized(Helpers::render_value(payload, entity.factor * entity.value, 2));
}
break;
case DeviceValueType::STRING:
if (entity.data.length() > 0) {
output[name] = entity.data;
}
break;
default:
// EMSESP::logger().warning("unknown value type");
break;
@@ -317,6 +335,8 @@ void WebEntityService::publish(const bool force) {
if (entityItem.writeable) {
if (entityItem.value_type == DeviceValueType::BOOL) {
snprintf(topic, sizeof(topic), "switch/%s/custom_%s/config", Mqtt::basename().c_str(), entityItem.name.c_str());
} else if (entityItem.value_type == DeviceValueType::STRING) {
snprintf(topic, sizeof(topic), "sensor/%s/custom_%s/config", Mqtt::basename().c_str(), entityItem.name.c_str());
} else if (Mqtt::discovery_type() == Mqtt::discoveryType::HOMEASSISTANT) {
snprintf(topic, sizeof(topic), "number/%s/custom_%s/config", Mqtt::basename().c_str(), entityItem.name.c_str());
} else {
@@ -402,7 +422,7 @@ void WebEntityService::generate_value_web(JsonObject & output) {
obj["u"] = entity.uom;
if (entity.writeable) {
obj["c"] = entity.name;
if (entity.value_type != DeviceValueType::BOOL) {
if (entity.value_type != DeviceValueType::BOOL && entity.value_type != DeviceValueType::STRING) {
char s[10];
obj["s"] = Helpers::render_value(s, entity.factor, 1);
}
@@ -443,9 +463,15 @@ void WebEntityService::generate_value_web(JsonObject & output) {
obj["v"] = Helpers::transformNumFloat(entity.factor * entity.value, 0);
}
break;
case DeviceValueType::STRING:
if (entity.data.length() > 0) {
obj["v"] = entity.data;
}
break;
default:
break;
}
// show only entities with value or command
if (!obj.containsKey("v") && !obj.containsKey("c")) {
data.remove(index);
} else {
@@ -457,8 +483,12 @@ void WebEntityService::generate_value_web(JsonObject & output) {
// fetch telegram, called from emsesp::fetch
void WebEntityService::fetch() {
EMSESP::webEntityService.read([&](WebEntity & webEntity) { entityItems = &webEntity.entityItems; });
const uint8_t len[] = {1, 1, 1, 2, 2, 3, 3};
for (auto & entity : *entityItems) {
EMSESP::send_read_request(entity.type_id, entity.device_id, entity.offset);
EMSESP::send_read_request(entity.type_id,
entity.device_id,
entity.offset,
entity.value_type == DeviceValueType::STRING ? (uint8_t)entity.factor : len[entity.value_type]);
}
// EMSESP::logger().debug("fetch custom entities");
}
@@ -470,8 +500,20 @@ bool WebEntityService::get_value(std::shared_ptr<const Telegram> telegram) {
// read-length of BOOL, INT, UINT, SHORT, USHORT, ULONG, TIME
const uint8_t len[] = {1, 1, 1, 2, 2, 3, 3};
for (auto & entity : *entityItems) {
if (telegram->type_id == entity.type_id && telegram->src == entity.device_id && telegram->offset <= entity.offset
&& (telegram->offset + telegram->message_length) >= (entity.offset + len[entity.value_type])) {
if (entity.value_type == DeviceValueType::STRING && telegram->type_id == entity.type_id && telegram->src == entity.device_id
&& telegram->offset == entity.offset) {
auto data = Helpers::data_to_hex(telegram->message_data, telegram->message_length);
if (entity.data != data) {
entity.data = data;
if (Mqtt::publish_single()) {
publish_single(entity);
} else if (EMSESP::mqtt_.get_publish_onchange(0)) {
has_change = true;
}
}
}
if (entity.value_type != DeviceValueType::STRING && telegram->type_id == entity.type_id && telegram->src == entity.device_id
&& telegram->offset <= entity.offset && (telegram->offset + telegram->message_length) >= (entity.offset + len[entity.value_type])) {
uint32_t value = 0;
for (uint8_t i = 0; i < len[entity.value_type]; i++) {
value = (value << 8) + telegram->message_data[i + entity.offset - telegram->offset];
@@ -485,7 +527,6 @@ bool WebEntityService::get_value(std::shared_ptr<const Telegram> telegram) {
}
}
// EMSESP::logger().debug("custom entity %s received with value %d", entity.name.c_str(), (int)entity.val);
break;
}
}
if (has_change) {

View File

@@ -37,6 +37,7 @@ class EntityItem {
double factor;
bool writeable;
uint32_t value;
std::string data;
};
class WebEntity {

View File

@@ -316,7 +316,7 @@ bool WebSchedulerService::command(const char * cmd, const char * data) {
if (return_code == CommandRet::OK) {
EMSESP::logger().debug("Scheduled command %s with data %s successfully", cmd, data);
if (strlen(data) == 0 && Mqtt::enabled() && Mqtt::send_response() && output.size()) {
if (strlen(data) == 0 && output.size()) {
Mqtt::queue_publish("response", output);
}
return true;