mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2025-12-09 01:09:51 +03:00
Change name of entity within WebUI #612
This commit is contained in:
@@ -259,8 +259,8 @@ bool EMSdevice::has_tag(const uint8_t tag) const {
|
||||
// called from the command 'entities'
|
||||
void EMSdevice::list_device_entries(JsonObject & output) const {
|
||||
for (const auto & dv : devicevalues_) {
|
||||
auto fullname = Helpers::translated_fword(dv.fullname);
|
||||
if (!dv.has_state(DeviceValueState::DV_WEB_EXCLUDE) && dv.type != DeviceValueType::CMD && fullname) {
|
||||
std::string fullname = dv.get_fullname();
|
||||
if (!dv.has_state(DeviceValueState::DV_WEB_EXCLUDE) && dv.type != DeviceValueType::CMD && !fullname.empty()) {
|
||||
// if we have a tag prefix it
|
||||
char key[50];
|
||||
if (!EMSdevice::tag_to_mqtt(dv.tag).empty()) {
|
||||
@@ -433,15 +433,51 @@ void EMSdevice::add_device_value(uint8_t tag,
|
||||
// determine state
|
||||
uint8_t state = DeviceValueState::DV_DEFAULT;
|
||||
|
||||
// custom fullname
|
||||
std::string custom_fullname = std::string("");
|
||||
|
||||
// scan through customizations to see if it's on the exclusion list by matching the productID and deviceID
|
||||
EMSESP::webCustomizationService.read([&](WebCustomization & settings) {
|
||||
for (EntityCustomization entityCustomization : settings.entityCustomizations) {
|
||||
if ((entityCustomization.product_id == product_id()) && (entityCustomization.device_id == device_id())) {
|
||||
std::string entity = tag < DeviceValueTAG::TAG_HC1 ? read_flash_string(short_name) : tag_to_string(tag) + "/" + read_flash_string(short_name);
|
||||
for (std::string entity_id : entityCustomization.entity_ids) {
|
||||
if (entity_id.substr(2) == entity) {
|
||||
// if there is an appended custom name, strip it to get the true entity name
|
||||
// and extract the new custom name
|
||||
std::string matched_entity;
|
||||
auto custom_name_pos = entity_id.find('|');
|
||||
bool has_custom_name = (custom_name_pos != std::string::npos);
|
||||
if (has_custom_name) {
|
||||
matched_entity = entity_id.substr(2, custom_name_pos - 2);
|
||||
} else {
|
||||
matched_entity = entity_id.substr(2);
|
||||
}
|
||||
|
||||
/*
|
||||
Serial.print(COLOR_BRIGHT_GREEN);
|
||||
Serial.print(" entity name=");
|
||||
Serial.print(entity_id.c_str());
|
||||
Serial.print(" ,matched name=");
|
||||
Serial.print(matched_entity.c_str());
|
||||
Serial.print(" ,matched pos=");
|
||||
Serial.println(custom_name_pos);
|
||||
Serial.print(COLOR_RESET);
|
||||
*/
|
||||
|
||||
// we found the device entity
|
||||
if (matched_entity == entity) {
|
||||
// get Mask
|
||||
uint8_t mask = Helpers::hextoint(entity_id.substr(0, 2).c_str());
|
||||
state = mask << 4; // set state high bits to flag, turn off active and ha flags
|
||||
// see if there is a custom name in the entity string
|
||||
if (has_custom_name) {
|
||||
/*
|
||||
Serial.print(COLOR_BRIGHT_MAGENTA);
|
||||
Serial.println(entity_id.substr(custom_name_pos + 1).c_str());
|
||||
Serial.print(COLOR_RESET);
|
||||
*/
|
||||
custom_fullname = entity_id.substr(custom_name_pos + 1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -449,11 +485,6 @@ void EMSdevice::add_device_value(uint8_t tag,
|
||||
}
|
||||
});
|
||||
|
||||
// TODO
|
||||
const __FlashStringHelper * custom_fullname;
|
||||
|
||||
custom_fullname = nullptr;
|
||||
|
||||
// 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);
|
||||
@@ -475,6 +506,7 @@ void EMSdevice::add_device_value(uint8_t tag,
|
||||
|
||||
// add the command to our library
|
||||
// cmd is the short_name and the description is the fullname
|
||||
// TODO this needs adapting to take the custom fullname since its not a FPTR()
|
||||
Command::add(device_type_, short_name, f, Helpers::translated_fword(fullname), flags);
|
||||
}
|
||||
|
||||
@@ -700,6 +732,8 @@ void EMSdevice::publish_value(void * value_p) const {
|
||||
}
|
||||
|
||||
// looks up the UOM for a given key from the device value table
|
||||
// key is the fullname
|
||||
// TODO really should be using the shortname as the identifier
|
||||
std::string EMSdevice::get_value_uom(const char * key) const {
|
||||
// the key may have a TAG string prefixed at the beginning. If so, remove it
|
||||
char new_key[80];
|
||||
@@ -719,8 +753,8 @@ std::string EMSdevice::get_value_uom(const char * key) const {
|
||||
|
||||
// look up key in our device value list
|
||||
for (const auto & dv : devicevalues_) {
|
||||
auto fullname = Helpers::translated_fword(dv.fullname);
|
||||
if ((!dv.has_state(DeviceValueState::DV_WEB_EXCLUDE) && fullname) && (read_flash_string(fullname) == key_p)) {
|
||||
auto fullname = dv.get_fullname();
|
||||
if ((!dv.has_state(DeviceValueState::DV_WEB_EXCLUDE) && !fullname.empty()) && (fullname == key_p)) {
|
||||
// ignore TIME since "minutes" is already added to the string value
|
||||
if ((dv.uom == DeviceValueUOM::NONE) || (dv.uom == DeviceValueUOM::MINUTES)) {
|
||||
break;
|
||||
@@ -741,13 +775,13 @@ void EMSdevice::generate_values_web(JsonObject & output) {
|
||||
JsonArray data = output.createNestedArray("data");
|
||||
|
||||
for (auto & dv : devicevalues_) {
|
||||
auto fullname = Helpers::translated_fword(dv.fullname);
|
||||
auto fullname = dv.get_fullname();
|
||||
|
||||
// check conditions:
|
||||
// 1. fullname cannot be empty
|
||||
// 2. it must have a valid value, if it is not a command like 'reset'
|
||||
// 3. show favorites first
|
||||
if (!dv.has_state(DeviceValueState::DV_WEB_EXCLUDE) && fullname && (dv.hasValue() || (dv.type == DeviceValueType::CMD))) {
|
||||
if (!dv.has_state(DeviceValueState::DV_WEB_EXCLUDE) && !fullname.empty() && (dv.hasValue() || (dv.type == DeviceValueType::CMD))) {
|
||||
JsonObject obj = data.createNestedObject(); // create the object, we know there is a value
|
||||
uint8_t fahrenheit = 0;
|
||||
|
||||
@@ -797,9 +831,9 @@ void EMSdevice::generate_values_web(JsonObject & output) {
|
||||
|
||||
// add name, prefixing the tag if it exists. This is the id used in the WebUI table and must be unique
|
||||
if ((dv.tag == DeviceValueTAG::TAG_NONE) || tag_to_string(dv.tag).empty()) {
|
||||
obj["id"] = mask + Helpers::translated_word(dv.fullname);
|
||||
obj["id"] = mask + dv.get_fullname();
|
||||
} else {
|
||||
obj["id"] = mask + tag_to_string(dv.tag) + " " + Helpers::translated_word(dv.fullname);
|
||||
obj["id"] = mask + tag_to_string(dv.tag) + " " + dv.get_fullname();
|
||||
}
|
||||
|
||||
// add commands and options
|
||||
@@ -928,14 +962,14 @@ void EMSdevice::generate_values_web_customization(JsonArray & output) {
|
||||
|
||||
// n is the fullname, and can be optional
|
||||
// don't add the fullname if its a command
|
||||
auto translated_full_name = Helpers::translated_fword(dv.fullname);
|
||||
auto fullname = dv.get_fullname();
|
||||
if (dv.type != DeviceValueType::CMD) {
|
||||
if (translated_full_name) {
|
||||
if (!fullname.empty()) {
|
||||
if ((dv.tag == DeviceValueTAG::TAG_NONE) || tag_to_string(dv.tag).empty()) {
|
||||
obj["n"] = read_flash_string(translated_full_name);
|
||||
obj["n"] = fullname;
|
||||
} else {
|
||||
char name[50];
|
||||
snprintf(name, sizeof(name), "%s %s", tag_to_string(dv.tag).c_str(), read_flash_string(translated_full_name).c_str());
|
||||
snprintf(name, sizeof(name), "%s %s", tag_to_string(dv.tag).c_str(), fullname.c_str());
|
||||
obj["n"] = name;
|
||||
}
|
||||
}
|
||||
@@ -943,6 +977,11 @@ void EMSdevice::generate_values_web_customization(JsonArray & output) {
|
||||
obj["n"] = "";
|
||||
}
|
||||
|
||||
// add the custom name, is optional
|
||||
if (!dv.custom_fullname.empty()) {
|
||||
obj["cn"] = dv.custom_fullname;
|
||||
}
|
||||
|
||||
obj["m"] = dv.state >> 4; // send back the mask state. We're only interested in the high nibble
|
||||
obj["w"] = dv.has_cmd; // if writable
|
||||
}
|
||||
@@ -1018,12 +1057,12 @@ bool EMSdevice::get_value_info(JsonObject & output, const char * cmd, const int8
|
||||
|
||||
json["name"] = dv.short_name;
|
||||
|
||||
auto fullname = Helpers::translated_fword(dv.fullname);
|
||||
if (fullname != nullptr) {
|
||||
auto fullname = dv.get_fullname();
|
||||
if (!fullname.empty()) {
|
||||
if ((dv.tag == DeviceValueTAG::TAG_NONE) || tag_to_string(dv.tag).empty()) {
|
||||
json["fullname"] = fullname;
|
||||
} else {
|
||||
json["fullname"] = tag_to_string(dv.tag) + " " + read_flash_string(fullname);
|
||||
json["fullname"] = tag_to_string(dv.tag) + " " + fullname;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1202,14 +1241,14 @@ bool EMSdevice::generate_values(JsonObject & output, const uint8_t tag_filter, c
|
||||
dv.remove_state(DeviceValueState::DV_ACTIVE);
|
||||
}
|
||||
|
||||
auto fullname = Helpers::translated_fword(dv.fullname);
|
||||
auto fullname = dv.get_fullname();
|
||||
|
||||
// check conditions:
|
||||
// 1. it must have a valid value (state is active)
|
||||
// 2. it must have a visible flag
|
||||
// 3. it must match the given tag filter or have an empty tag
|
||||
// 4. it must not have the exclude flag set or outputs to console
|
||||
if (dv.has_state(DeviceValueState::DV_ACTIVE) && fullname && (tag_filter == DeviceValueTAG::TAG_NONE || tag_filter == dv.tag)
|
||||
if (dv.has_state(DeviceValueState::DV_ACTIVE) && !fullname.empty() && (tag_filter == DeviceValueTAG::TAG_NONE || tag_filter == dv.tag)
|
||||
&& (output_target == OUTPUT_TARGET::CONSOLE || !dv.has_state(DeviceValueState::DV_API_MQTT_EXCLUDE))) {
|
||||
has_values = true; // flagged if we actually have data
|
||||
|
||||
@@ -1221,9 +1260,9 @@ bool EMSdevice::generate_values(JsonObject & output, const uint8_t tag_filter, c
|
||||
|
||||
if (output_target == OUTPUT_TARGET::API_VERBOSE || output_target == OUTPUT_TARGET::CONSOLE) {
|
||||
if (have_tag) {
|
||||
snprintf(name, 80, "%s %s", tag_to_string(dv.tag).c_str(), read_flash_string(fullname).c_str()); // prefix the tag
|
||||
snprintf(name, 80, "%s %s", tag_to_string(dv.tag).c_str(), fullname.c_str()); // prefix the tag
|
||||
} else {
|
||||
strlcpy(name, read_flash_string(fullname).c_str(), sizeof(name)); // use full name
|
||||
strlcpy(name, fullname.c_str(), sizeof(name)); // use full name
|
||||
}
|
||||
} else {
|
||||
strlcpy(name, read_flash_string(dv.short_name).c_str(), sizeof(name)); // use short name
|
||||
|
||||
@@ -32,12 +32,12 @@ DeviceValue::DeviceValue(uint8_t device_type,
|
||||
int8_t numeric_operator,
|
||||
const __FlashStringHelper * const short_name,
|
||||
const __FlashStringHelper * const * fullname,
|
||||
const __FlashStringHelper * const custom_fullname,
|
||||
uint8_t uom,
|
||||
bool has_cmd,
|
||||
int16_t min,
|
||||
uint16_t max,
|
||||
uint8_t state)
|
||||
const std::string & custom_fullname,
|
||||
uint8_t uom,
|
||||
bool has_cmd,
|
||||
int16_t min,
|
||||
uint16_t max,
|
||||
uint8_t state)
|
||||
: device_type(device_type)
|
||||
, tag(tag)
|
||||
, value_p(value_p)
|
||||
@@ -65,8 +65,13 @@ DeviceValue::DeviceValue(uint8_t device_type,
|
||||
Serial.print("registering entity: ");
|
||||
Serial.print(read_flash_string(short_name).c_str());
|
||||
Serial.print("/");
|
||||
auto trans_fullname = Helpers::translated_word(fullname);
|
||||
Serial.print(trans_fullname.c_str());
|
||||
if (!custom_fullname.empty()) {
|
||||
Serial.print(COLOR_BRIGHT_CYAN);
|
||||
Serial.print(custom_fullname.c_str());
|
||||
Serial.print(COLOR_RESET);
|
||||
} else {
|
||||
Serial.print(Helpers::translated_word(fullname).c_str());
|
||||
}
|
||||
Serial.print(" (#options=");
|
||||
Serial.print(options_size);
|
||||
Serial.print(",numop=");
|
||||
@@ -321,4 +326,13 @@ bool DeviceValue::get_min_max(int16_t & dv_set_min, int16_t & dv_set_max) {
|
||||
return false; // nothing changed, not supported
|
||||
}
|
||||
|
||||
// returns the translated fullname or the custom fullname (if provided)
|
||||
// always returns a std::string
|
||||
std::string DeviceValue::get_fullname() const {
|
||||
if (custom_fullname.empty()) {
|
||||
return Helpers::translated_word(fullname);
|
||||
}
|
||||
return custom_fullname;
|
||||
}
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
@@ -153,7 +153,7 @@ class DeviceValue {
|
||||
uint8_t options_size; // number of options in the char array, calculated
|
||||
const __FlashStringHelper * const short_name; // used in MQTT and API
|
||||
const __FlashStringHelper * const * fullname; // used in Web and Console, is translated
|
||||
const __FlashStringHelper * const custom_fullname; // optional, from customization
|
||||
const std::string custom_fullname; // optional, from customization
|
||||
uint8_t uom; // DeviceValueUOM::*
|
||||
bool has_cmd; // true if there is a Console/MQTT command which matches the short_name
|
||||
int16_t min; // min range
|
||||
@@ -169,7 +169,7 @@ class DeviceValue {
|
||||
int8_t numeric_operator,
|
||||
const __FlashStringHelper * const short_name,
|
||||
const __FlashStringHelper * const * fullname,
|
||||
const __FlashStringHelper * const custom_fullname,
|
||||
const std::string & custom_fullname,
|
||||
uint8_t uom,
|
||||
bool has_cmd,
|
||||
int16_t min,
|
||||
@@ -179,6 +179,8 @@ class DeviceValue {
|
||||
bool hasValue() const;
|
||||
bool get_min_max(int16_t & dv_set_min, int16_t & dv_set_max);
|
||||
|
||||
std::string get_fullname() const;
|
||||
|
||||
// dv state flags
|
||||
void add_state(uint8_t s) {
|
||||
state |= s;
|
||||
|
||||
14
src/mqtt.cpp
14
src/mqtt.cpp
@@ -937,7 +937,7 @@ void Mqtt::publish_ha_sensor_config(DeviceValue & dv, const std::string & model,
|
||||
|
||||
publish_ha_sensor_config(dv.type,
|
||||
dv.tag,
|
||||
Helpers::translated_fword(dv.fullname),
|
||||
dv.get_fullname(),
|
||||
dv.device_type,
|
||||
dv.short_name,
|
||||
dv.uom,
|
||||
@@ -958,7 +958,9 @@ void Mqtt::publish_system_ha_sensor_config(uint8_t type, const __FlashStringHelp
|
||||
JsonArray ids = dev_json.createNestedArray("ids");
|
||||
ids.add("ems-esp");
|
||||
|
||||
publish_ha_sensor_config(type, DeviceValueTAG::TAG_HEARTBEAT, name, EMSdevice::DeviceType::SYSTEM, entity, uom, false, false, nullptr, 0, 0, 0, dev_json);
|
||||
auto fullname = read_flash_string(name); // TODO make sure it works, it may need a std::move()?
|
||||
|
||||
publish_ha_sensor_config(type, DeviceValueTAG::TAG_HEARTBEAT, fullname, EMSdevice::DeviceType::SYSTEM, entity, uom, false, false, nullptr, 0, 0, 0, dev_json);
|
||||
}
|
||||
|
||||
// MQTT discovery configs
|
||||
@@ -966,7 +968,7 @@ void Mqtt::publish_system_ha_sensor_config(uint8_t type, const __FlashStringHelp
|
||||
// note: some extra string copying done here, it looks messy but does help with heap fragmentation issues
|
||||
void Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdevice::DeviceValueType
|
||||
uint8_t tag, // EMSdevice::DeviceValueTAG
|
||||
const __FlashStringHelper * const fullname, // fullname, already translated
|
||||
const std::string & fullname, // fullname, already translated
|
||||
const uint8_t device_type, // EMSdevice::DeviceType
|
||||
const __FlashStringHelper * const entity, // same as shortname
|
||||
const uint8_t uom, // EMSdevice::DeviceValueUOM (0=NONE)
|
||||
@@ -978,7 +980,7 @@ void Mqtt::publish_ha_sensor_config(uint8_t type,
|
||||
const int16_t dv_set_max,
|
||||
const JsonObject & dev_json) {
|
||||
// ignore if name (fullname) is empty
|
||||
if (fullname == nullptr) {
|
||||
if (fullname.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1114,9 +1116,9 @@ void Mqtt::publish_ha_sensor_config(uint8_t type,
|
||||
// friendly name = <tag> <name>
|
||||
char ha_name[70];
|
||||
if (have_tag) {
|
||||
snprintf(ha_name, sizeof(ha_name), "%s %s", EMSdevice::tag_to_string(tag).c_str(), read_flash_string(fullname).c_str());
|
||||
snprintf(ha_name, sizeof(ha_name), "%s %s", EMSdevice::tag_to_string(tag).c_str(), fullname.c_str());
|
||||
} else {
|
||||
snprintf(ha_name, sizeof(ha_name), "%s", read_flash_string(fullname).c_str());
|
||||
snprintf(ha_name, sizeof(ha_name), "%s", fullname.c_str());
|
||||
}
|
||||
ha_name[0] = toupper(ha_name[0]); // capitalize first letter
|
||||
doc["name"] = ha_name;
|
||||
|
||||
@@ -94,7 +94,7 @@ class Mqtt {
|
||||
publish_ha_sensor_config(DeviceValue & dv, const std::string & model, const std::string & brand, const bool remove, const bool create_device_config = false);
|
||||
static void publish_ha_sensor_config(uint8_t type,
|
||||
uint8_t tag,
|
||||
const __FlashStringHelper * const fullname,
|
||||
const std::string & fullname,
|
||||
const uint8_t device_type,
|
||||
const __FlashStringHelper * const entity,
|
||||
const uint8_t uom,
|
||||
|
||||
@@ -399,7 +399,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
|
||||
|
||||
#if defined(EMSESP_STANDALONE)
|
||||
|
||||
DynamicJsonDocument doc(8000); // some absurb high number
|
||||
DynamicJsonDocument doc(8000); // some absurd high number
|
||||
for (const auto & emsdevice : EMSESP::emsdevices) {
|
||||
if (emsdevice) {
|
||||
doc.clear();
|
||||
|
||||
@@ -95,11 +95,27 @@ void WebCustomization::read(WebCustomization & settings, JsonObject & root) {
|
||||
// call on initialization and also when the page is saved via web UI
|
||||
// this loads the data into the internal class
|
||||
StateUpdateResult WebCustomization::update(JsonObject & root, WebCustomization & settings) {
|
||||
#ifdef EMSESP_STANDALONE
|
||||
// invoke some fake data for testing
|
||||
// using https://arduinojson.org/v5/assistant/
|
||||
const char * json =
|
||||
"{\"sensors\":[],\"analogs\":[],\"masked_entities\":[{\"product_id\":123,\"device_id\":8,\"entity_ids\":[\"08heatingactive|my custom name for heating active\",\"08tapwateractive\"]}]}";
|
||||
|
||||
StaticJsonDocument<500> doc;
|
||||
deserializeJson(doc, json);
|
||||
root = doc.as<JsonObject>();
|
||||
|
||||
Serial.println(COLOR_BRIGHT_MAGENTA);
|
||||
Serial.print("Using custom file: ");
|
||||
serializeJson(root, Serial);
|
||||
Serial.println(COLOR_RESET);
|
||||
#endif
|
||||
|
||||
// Dallas Sensor customization
|
||||
settings.sensorCustomizations.clear();
|
||||
if (root["sensors"].is<JsonArray>()) {
|
||||
for (const JsonObject sensorJson : root["sensors"].as<JsonArray>()) {
|
||||
// create each of the sensor, overwritting any previous settings
|
||||
// create each of the sensor, overwriting any previous settings
|
||||
auto sensor = SensorCustomization();
|
||||
sensor.id = sensorJson["id"].as<std::string>();
|
||||
sensor.name = sensorJson["name"].as<std::string>();
|
||||
@@ -112,7 +128,7 @@ StateUpdateResult WebCustomization::update(JsonObject & root, WebCustomization &
|
||||
settings.analogCustomizations.clear();
|
||||
if (root["analogs"].is<JsonArray>()) {
|
||||
for (const JsonObject analogJson : root["analogs"].as<JsonArray>()) {
|
||||
// create each of the sensor, overwritting any previous settings
|
||||
// create each of the sensor, overwriting any previous settings
|
||||
auto sensor = AnalogCustomization();
|
||||
sensor.gpio = analogJson["gpio"];
|
||||
sensor.name = analogJson["name"].as<std::string>();
|
||||
@@ -160,7 +176,7 @@ void WebCustomizationService::reset_customization(AsyncWebServerRequest * reques
|
||||
#endif
|
||||
}
|
||||
|
||||
// send back a list of devices used to the customization web page
|
||||
// send back a list of devices used in the customization web page
|
||||
void WebCustomizationService::devices(AsyncWebServerRequest * request) {
|
||||
auto * response = new AsyncJsonResponse(false, EMSESP_JSON_SIZE_LARGE_DYN);
|
||||
JsonObject root = response->getRoot();
|
||||
@@ -173,19 +189,7 @@ void WebCustomizationService::devices(AsyncWebServerRequest * request) {
|
||||
JsonObject obj = devices.createNestedObject();
|
||||
obj["i"] = emsdevice->unique_id(); // its unique id
|
||||
obj["s"] = emsdevice->device_type_name() + " (" + emsdevice->name() + ")"; // shortname
|
||||
|
||||
// device type name. We may have one than one (e.g. multiple thermostats) so postfix name with index
|
||||
// code block not needed - see https://github.com/emsesp/EMS-ESP32/pull/586#issuecomment-1193779668
|
||||
/*
|
||||
uint8_t device_index = EMSESP::device_index(emsdevice->device_type(), emsdevice->unique_id());
|
||||
if (device_index) {
|
||||
char s[10];
|
||||
obj["t"] = Helpers::toLower(emsdevice->device_type_name()) + Helpers::smallitoa(s, device_index);
|
||||
} else {
|
||||
obj["t"] = Helpers::toLower(emsdevice->device_type_name());
|
||||
}
|
||||
*/
|
||||
obj["t"] = Helpers::toLower(emsdevice->device_type_name());
|
||||
obj["t"] = Helpers::toLower(emsdevice->device_type_name());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user