Command values for analog/dallas sensors, writeable for analog-out, #758

This commit is contained in:
MichaelDvP
2022-11-21 18:08:16 +01:00
parent 702103aa66
commit 781fe03b5d
8 changed files with 112 additions and 43 deletions

View File

@@ -18,6 +18,7 @@
- Add MD5 check [#637](https://github.com/emsesp/EMS-ESP32/issues/637)
- Add more bus-ids [#673](https://github.com/emsesp/EMS-ESP32/issues/673)
- Use HA connectivity device class for Status [#751](https://github.com/emsesp/EMS-ESP32/issues/751)
- Add commands for analog sensors outputs
## Fixed
@@ -30,6 +31,8 @@
- render mqtt float json values with trailing zero
- removed flash strings
- reload page after restart button is pressed
- analog/dallas values command as list like ems-devices
- analog/dallas HA-entities based on id
## **BREAKING CHANGES:**

View File

@@ -1141,7 +1141,7 @@ const DashboardData: FC = () => {
</Grid>
</>
)}
{analog.t === AnalogType.DIGITAL_OUT && (analog.id === '25' || analog.id === '26') && (
{analog.t === AnalogType.DIGITAL_OUT && (analog.g === 25 || analog.g === 26) && (
<>
<Grid item xs={4}>
<ValidatedTextField
@@ -1157,7 +1157,7 @@ const DashboardData: FC = () => {
</Grid>
</>
)}
{analog.t === AnalogType.DIGITAL_OUT && analog.id !== '25' && analog.id !== '26' && (
{analog.t === AnalogType.DIGITAL_OUT && analog.g !== 25 && analog.g !== 26 && (
<>
<Grid item xs={4}>
<ValidatedTextField

View File

@@ -39,6 +39,12 @@ void AnalogSensor::start() {
F_(info),
[&](const char * value, const int8_t id, JsonObject & output) { return command_info(value, id, output); },
FL_(info_cmd));
Command::add(
EMSdevice::DeviceType::ANALOGSENSOR,
F_(values),
[&](const char * value, const int8_t id, JsonObject & output) { return command_info(value, 0, output); },
nullptr,
CommandFlag::HIDDEN); // this command is hidden
Command::add(
EMSdevice::DeviceType::ANALOGSENSOR,
F_(setvalue),
@@ -111,6 +117,15 @@ void AnalogSensor::reload() {
sensors_.back().set_value(0); // reset value only for new sensors
}
}
if (sensor.type == AnalogType::COUNTER || sensor.type >= AnalogType::DIGITAL_OUT) {
Command::add(
EMSdevice::DeviceType::ANALOGSENSOR,
sensor.name.c_str(),
[&](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));
}
}
return true;
});
@@ -287,6 +302,9 @@ bool AnalogSensor::update(uint8_t gpio, const std::string & name, float offset,
EMSESP::webCustomizationService.update(
[&](WebCustomization & settings) {
for (auto & AnalogCustomization : settings.analogCustomizations) {
if (AnalogCustomization.type == AnalogType::COUNTER || AnalogCustomization.type >= AnalogType::DIGITAL_OUT) {
Command::erase_command(EMSdevice::DeviceType::ANALOGSENSOR, AnalogCustomization.name.c_str());
}
if (AnalogCustomization.gpio == gpio) {
found_sensor = true; // found the record
// see if it's marked for deletion
@@ -434,7 +452,7 @@ void AnalogSensor::publish_values(const bool force) {
config["val_tpl"] = str;
// snprintf(str, sizeof(str), "%s_analog_sensor_%s", Mqtt::basename().c_str(), sensor.name().c_str());
snprintf(str, sizeof(str), "analog_sensor_%s", sensor.name().c_str());
snprintf(str, sizeof(str), "analog_sensor_%d", sensor.gpio());
config["object_id"] = str;
snprintf(str, sizeof(str), "%s", sensor.name().c_str());
@@ -467,6 +485,9 @@ void AnalogSensor::publish_values(const bool force) {
// called from emsesp.cpp, similar to the emsdevice->get_value_info
// searches by name
bool AnalogSensor::get_value_info(JsonObject & output, const char * cmd, const int8_t id) const {
if (sensors_.empty()) {
return false;
}
// make a copy of the string command for parsing
char command_s[30];
strlcpy(command_s, cmd, sizeof(command_s));
@@ -480,7 +501,7 @@ bool AnalogSensor::get_value_info(JsonObject & output, const char * cmd, const i
}
for (const auto & sensor : sensors_) {
if (strcmp(command_s, sensor.name().c_str()) == 0) {
if (strcmp(command_s, sensor.name().c_str()) == 0 || Helpers::atoint(command_s) == sensor.gpio()) {
output["gpio"] = sensor.gpio();
output["name"] = sensor.name();
output["type"] = F_(number);
@@ -489,6 +510,18 @@ bool AnalogSensor::get_value_info(JsonObject & output, const char * cmd, const i
output["offset"] = sensor.offset();
output["factor"] = sensor.factor();
output["value"] = sensor.value();
output["writeable"] = sensor.type() == AnalogType::COUNTER || sensor.type() >= AnalogType::DIGITAL_OUT;
// min/max for writeable analogs
if (sensor.type() == AnalogType::COUNTER) {
output["min"] = 0;
output["max"] = 4000000;
} else if (sensor.type() == AnalogType::DIGITAL_OUT) {
output["min"] = 0;
output["max"] = sensor.gpio() == 25 || sensor.gpio() == 26 ? 255 : 1;
} else if (sensor.type() >= AnalogType::PWM_0) {
output["min"] = 0;
output["max"] = 100;
}
// if we're filtering on an attribute, go find it
if (attribute_s) {
if (output.containsKey(attribute_s)) {
@@ -518,7 +551,7 @@ bool AnalogSensor::command_info(const char * value, const int8_t id, JsonObject
}
for (const auto & sensor : sensors_) {
if (id == -1) { // show number and id
if (id == -1) { // show number and id for info command
JsonObject dataSensor = output.createNestedObject(sensor.name());
dataSensor["gpio"] = sensor.gpio();
dataSensor["type"] = F_(number);
@@ -538,8 +571,12 @@ bool AnalogSensor::command_info(const char * value, const int8_t id, JsonObject
dataSensor["frequency"] = sensor.factor();
}
dataSensor["value"] = sensor.value();
} else {
} else if (id == 0) { // output values command
output[sensor.name()] = sensor.value();
} else { // if someone wants gpio numbers
char gpio_str[9];
snprintf(gpio_str, sizeof(gpio_str), "gpio_%02d", sensor.gpio());
output[gpio_str] = sensor.value();
}
}

View File

@@ -43,6 +43,7 @@ class AnalogSensor {
}
std::string name() const;
void set_name(const std::string & name) {
name_ = name;
}

View File

@@ -116,7 +116,7 @@ uint8_t Command::process(const char * path, const bool is_admin, const JsonObjec
// handle dead endpoints like api/system or api/boiler
// default to 'info' for SYSTEM, DALLASENSOR and ANALOGSENSOR, the other devices to 'values' for shortname version
if (num_paths < (id_n > 0 ? 4 : 3)) {
if (device_type < EMSdevice::DeviceType::BOILER) {
if (device_type == EMSdevice::DeviceType::SYSTEM) {
command_p = F_(info);
} else {
command_p = F_(values);
@@ -193,20 +193,26 @@ const char * Command::parse_command_string(const char * command, int8_t & id) {
return nullptr;
}
// convert cmd to lowercase and compare
char * lowerCmd = strdup(command);
for (char * p = lowerCmd; *p; p++) {
*p = tolower(*p);
}
// check prefix and valid number range, also check 'id'
if (!strncmp(command, "hc", 2) && command[2] >= '1' && command[2] <= '8') {
if (!strncmp(lowerCmd, "hc", 2) && command[2] >= '1' && command[2] <= '8') {
id = command[2] - '0';
command += 3;
} else if (!strncmp(command, "wwc", 3) && command[3] == '1' && command[4] == '0') {
} else if (!strncmp(lowerCmd, "wwc", 3) && command[3] == '1' && command[4] == '0') {
id = 19;
command += 5;
} else if (!strncmp(command, "wwc", 3) && command[3] >= '1' && command[3] <= '9') {
} else if (!strncmp(lowerCmd, "wwc", 3) && command[3] >= '1' && command[3] <= '9') {
id = command[3] - '0' + 8;
command += 4;
} else if (!strncmp(command, "id", 2) && command[2] == '1' && command[3] >= '0' && command[3] <= '9') {
} else if (!strncmp(lowerCmd, "id", 2) && command[2] == '1' && command[3] >= '0' && command[3] <= '9') {
id = command[3] - '0' + 10;
command += 4;
} else if (!strncmp(command, "id", 2) && command[2] >= '1' && command[2] <= '9') {
} else if (!strncmp(lowerCmd, "id", 2) && command[2] >= '1' && command[2] <= '9') {
id = command[2] - '0';
command += 3;
}
@@ -214,6 +220,7 @@ const char * Command::parse_command_string(const char * command, int8_t & id) {
if (command[0] == '/' || command[0] == '.' || command[0] == '_') {
command++;
}
free(lowerCmd);
// return null for empty command
if (command[0] == '\0') {
return nullptr;
@@ -332,15 +339,8 @@ Command::CmdFunction * Command::find_command(const uint8_t device_type, const ch
return nullptr;
}
// convert cmd to lowercase and compare
char lowerCmd[30];
strlcpy(lowerCmd, cmd, sizeof(lowerCmd));
for (char * p = lowerCmd; *p; p++) {
*p = tolower(*p);
}
for (auto & cf : cmdfunctions_) {
if (!strcmp(lowerCmd, cf.cmd_) && (cf.device_type_ == device_type)) {
if (Helpers::toLower(cmd) == Helpers::toLower(cf.cmd_) && (cf.device_type_ == device_type)) {
return &cf;
}
}
@@ -348,6 +348,20 @@ Command::CmdFunction * Command::find_command(const uint8_t device_type, const ch
return nullptr; // command not found
}
void Command::erase_command(const uint8_t device_type, const char * cmd) {
if ((cmd == nullptr) || (strlen(cmd) == 0) || (cmdfunctions_.empty())) {
return;
}
auto it = cmdfunctions_.begin();
for (auto & cf : cmdfunctions_) {
if (Helpers::toLower(cmd) == Helpers::toLower(cf.cmd_) && (cf.device_type_ == device_type)) {
cmdfunctions_.erase(it);
return;
}
it++;
}
}
// list all commands for a specific device, output as json
bool Command::list(const uint8_t device_type, JsonObject & output) {
if (cmdfunctions_.empty()) {

View File

@@ -57,8 +57,8 @@ class Command {
uint8_t device_type_; // DeviceType::
uint8_t flags_; // mqtt flags for command subscriptions
const char * cmd_;
const cmd_function_p cmdfunction_;
const cmd_json_function_p cmdfunction_json_;
cmd_function_p cmdfunction_;
cmd_json_function_p cmdfunction_json_;
const char * const * description_;
CmdFunction(const uint8_t device_type,
@@ -113,6 +113,7 @@ class Command {
static void show_all(uuid::console::Shell & shell);
static Command::CmdFunction * find_command(const uint8_t device_type, const char * cmd);
static void erase_command(const uint8_t device_type, const char * cmd);
static void show(uuid::console::Shell & shell, uint8_t device_type, bool verbose);
static void show_devices(uuid::console::Shell & shell);
static bool device_has_commands(const uint8_t device_type);

View File

@@ -51,6 +51,12 @@ void DallasSensor::start() {
F_(info),
[&](const char * value, const int8_t id, JsonObject & output) { return command_info(value, id, output); },
FL_(info_cmd));
Command::add(
EMSdevice::DeviceType::DALLASSENSOR,
F_(values),
[&](const char * value, const int8_t id, JsonObject & output) { return command_info(value, 0, output); },
nullptr,
CommandFlag::HIDDEN); // this command is hidden
Command::add(
EMSdevice::DeviceType::DALLASSENSOR,
F_(commands),
@@ -361,14 +367,18 @@ bool DallasSensor::command_info(const char * value, const int8_t id, JsonObject
for (const auto & sensor : sensors_) {
char val[10];
if (id == -1) { // show number and id
if (id == -1) { // show number and id, info command
JsonObject dataSensor = output.createNestedObject(sensor.name());
dataSensor["id"] = sensor.id();
dataSensor["uom"] = EMSdevice::uom_to_string(DeviceValueUOM::DEGREES);
dataSensor["type"] = F_(number);
if (Helpers::hasValue(sensor.temperature_c)) {
dataSensor["temp"] = serialized(Helpers::render_value(val, sensor.temperature_c, 10, EMSESP::system_.fahrenheit() ? 2 : 0));
}
} else if (Helpers::hasValue(sensor.temperature_c)) {
} else if (id == 0 && Helpers::hasValue(sensor.temperature_c)) { // values command
output[sensor.name()] = serialized(Helpers::render_value(val, sensor.temperature_c, 10, EMSESP::system_.fahrenheit() ? 2 : 0));
} else if (Helpers::hasValue(sensor.temperature_c)) {
output[sensor.id()] = serialized(Helpers::render_value(val, sensor.temperature_c, 10, EMSESP::system_.fahrenheit() ? 2 : 0));
}
}
@@ -377,6 +387,9 @@ bool DallasSensor::command_info(const char * value, const int8_t id, JsonObject
// called from emsesp.cpp, similar to the emsdevice->get_value_info
bool DallasSensor::get_value_info(JsonObject & output, const char * cmd, const int8_t id) {
if (sensors_.empty()) {
return false;
}
// make a copy of the string command for parsing
char command_s[30];
strlcpy(command_s, cmd, sizeof(command_s));
@@ -390,7 +403,7 @@ bool DallasSensor::get_value_info(JsonObject & output, const char * cmd, const i
}
for (const auto & sensor : sensors_) {
if (strcmp(command_s, sensor.name().c_str()) == 0) {
if (strcmp(command_s, sensor.name().c_str()) == 0 || strcmp(command_s, sensor.id().c_str()) == 0) {
output["id"] = sensor.id();
output["name"] = sensor.name();
char val[10];
@@ -399,8 +412,6 @@ bool DallasSensor::get_value_info(JsonObject & output, const char * cmd, const i
}
output["type"] = F_(number);
output["min"] = serialized(Helpers::render_value(val, (int8_t)-55, 0, EMSESP::system_.fahrenheit() ? (uint8_t)2 : (uint8_t)0));
output["max"] = serialized(Helpers::render_value(val, (int8_t)125, 0, EMSESP::system_.fahrenheit() ? (uint8_t)2 : (uint8_t)0));
output["uom"] = EMSdevice::uom_to_string(DeviceValueUOM::DEGREES);
output["writeable"] = false;
@@ -507,8 +518,8 @@ void DallasSensor::publish_values(const bool force) {
}
config["val_tpl"] = str;
// snprintf(str, sizeof(str), "%s_temperature_sensor_%s", Mqtt::basename().c_str(), sensor.name().c_str());
snprintf(str, sizeof(str), "temperature_sensor_%s", sensor.name().c_str());
// snprintf(str, sizeof(str), "%s_temperature_sensor_%s", Mqtt::basename().c_str(), sensor.id().c_str());
snprintf(str, sizeof(str), "temperature_sensor_%s", sensor.id().c_str());
config["object_id"] = str;
snprintf(str, sizeof(str), "%s", sensor.name().c_str());

View File

@@ -230,6 +230,8 @@ MAKE_PSTR_LIST(climate, "HA climate config creation")
MAKE_PSTR_LIST(list_syslog_level, "off", "emerg", "alert", "crit", "error", "warn", "notice", "info", "debug", "trace", "all")
// sensors
MAKE_PSTR_LIST(counter, "counter")
MAKE_PSTR_LIST(digital_out, "digital_out")
MAKE_PSTR_LIST(list_sensortype, "none", "digital in", "counter", "adc", "timer", "rate", "digital out", "pwm 0", "pwm 1", "pwm 2")
// watch