diff --git a/esphome/components/dfrobot_sen0395/automation.h b/esphome/components/dfrobot_sen0395/automation.h index 58dbb9afb09..1aa6e675cdb 100644 --- a/esphome/components/dfrobot_sen0395/automation.h +++ b/esphome/components/dfrobot_sen0395/automation.h @@ -27,9 +27,9 @@ class DfrobotSen0395SettingsAction final : public Action, public Parented TEMPLATABLE_VALUE(float, det_max4) void play(const Ts &...x) override { - this->parent_->enqueue(make_unique(0)); + this->parent_->enqueue(CommandType::COMMAND_TYPE_POWER, false); if (this->factory_reset_.has_value() && this->factory_reset_.value(x...) == true) { - this->parent_->enqueue(make_unique()); + this->parent_->enqueue(CommandType::COMMAND_TYPE_FACTORY_RESET); } if (this->det_min1_.has_value() && this->det_max1_.has_value()) { // Each segment is read once, so the guard and the command see the same values. @@ -37,10 +37,10 @@ class DfrobotSen0395SettingsAction final : public Action, public Parented if (min1 >= 0) { const float max1 = this->det_max1_.value(x...); if (max1 >= 0) { - this->parent_->enqueue(make_unique( - min1, max1, this->det_min2_.value_or(x..., -1), this->det_max2_.value_or(x..., -1), - this->det_min3_.value_or(x..., -1), this->det_max3_.value_or(x..., -1), - this->det_min4_.value_or(x..., -1), this->det_max4_.value_or(x..., -1))); + this->parent_->enqueue_det_range_cfg(min1, max1, this->det_min2_.value_or(x..., -1), + this->det_max2_.value_or(x..., -1), this->det_min3_.value_or(x..., -1), + this->det_max3_.value_or(x..., -1), this->det_min4_.value_or(x..., -1), + this->det_max4_.value_or(x..., -1)); } } } @@ -48,38 +48,35 @@ class DfrobotSen0395SettingsAction final : public Action, public Parented float detect = this->delay_after_detect_.value(x...); float disappear = this->delay_after_disappear_.value(x...); if (detect >= 0 && disappear >= 0) { - this->parent_->enqueue(make_unique(detect, disappear)); + this->parent_->enqueue_set_latency(detect, disappear); } } if (this->start_after_power_on_.has_value()) { int8_t val = this->start_after_power_on_.value(x...); if (val >= 0) { - this->parent_->enqueue(make_unique(val)); + this->parent_->enqueue(CommandType::COMMAND_TYPE_SENSOR_CFG_START, val); } } if (this->turn_on_led_.has_value()) { int8_t val = this->turn_on_led_.value(x...); if (val >= 0) { - this->parent_->enqueue(make_unique(val)); + this->parent_->enqueue(CommandType::COMMAND_TYPE_LED_MODE, val); } } if (this->presence_via_uart_.has_value()) { int8_t val = this->presence_via_uart_.value(x...); if (val >= 0) { - this->parent_->enqueue(make_unique(val)); + this->parent_->enqueue(CommandType::COMMAND_TYPE_UART_OUTPUT, val); } } if (this->sensitivity_.has_value()) { int8_t val = this->sensitivity_.value(x...); if (val >= 0) { - if (val > 9) { - val = 9; - } - this->parent_->enqueue(make_unique(val)); + this->parent_->enqueue(CommandType::COMMAND_TYPE_SENSITIVITY, val); } } - this->parent_->enqueue(make_unique()); - this->parent_->enqueue(make_unique(1)); + this->parent_->enqueue(CommandType::COMMAND_TYPE_SAVE_CFG); + this->parent_->enqueue(CommandType::COMMAND_TYPE_POWER, true); } }; diff --git a/esphome/components/dfrobot_sen0395/commands.cpp b/esphome/components/dfrobot_sen0395/commands.cpp index f5aee549435..f7f9cc6db83 100644 --- a/esphome/components/dfrobot_sen0395/commands.cpp +++ b/esphome/components/dfrobot_sen0395/commands.cpp @@ -1,7 +1,9 @@ #include "commands.h" #include +#include +#include "esphome/core/helpers.h" #include "esphome/core/log.h" #include "dfrobot_sen0395.h" @@ -10,323 +12,236 @@ namespace esphome::dfrobot_sen0395 { ESPHOME_LOG_TAG(TAG, "dfrobot_sen0395.commands"); -uint8_t Command::execute(DfrobotSen0395Component *parent) { - this->parent_ = parent; - if (this->cmd_sent_) { - if (this->parent_->read_message_()) { - std::string message(this->parent_->read_buffer_); - if (message.rfind("is not recognized as a CLI command") != std::string::npos) { - ESP_LOGD(TAG, "Command not recognized properly by sensor"); - if (this->retries_left_ > 0) { - this->retries_left_ -= 1; - this->cmd_sent_ = false; - ESP_LOGD(TAG, "Retrying"); - return 0; - } else { - this->parent_->find_prompt_(); - return 1; // Command done - } - } - uint8_t rc = on_message(message); - if (rc == 2) { - if (this->retries_left_ > 0) { - this->retries_left_ -= 1; - this->cmd_sent_ = false; - ESP_LOGD(TAG, "Retrying"); - return 0; - } else { - this->parent_->find_prompt_(); - return 1; // Command done - } - } else if (rc == 0) { - return 0; - } else { - this->parent_->find_prompt_(); - return 1; - } - } - if (millis() - this->parent_->ts_last_cmd_sent_ > this->timeout_ms_) { - ESP_LOGD(TAG, "Command timeout"); - if (this->retries_left_ > 0) { - this->retries_left_ -= 1; - this->cmd_sent_ = false; - ESP_LOGD(TAG, "Retrying"); - } else { - return 1; // Command done - } - } - } else if (this->parent_->send_cmd_(this->cmd_.c_str(), this->cmd_duration_ms_)) { - this->cmd_sent_ = true; - } - return 0; // Command not done yet -} +static constexpr float DET_RANGE_STEP_M = 0.15f; +static constexpr float LATENCY_STEP_S = 0.025f; -uint8_t ReadStateCommand::execute(DfrobotSen0395Component *parent) { - this->parent_ = parent; - if (this->parent_->read_message_()) { - std::string message(this->parent_->read_buffer_); - if (message.rfind("$JYBSS,0, , , *") != std::string::npos) { - this->parent_->set_detected_(false); - this->parent_->set_active(true); - return 1; // Command done - } else if (message.rfind("$JYBSS,1, , , *") != std::string::npos) { - this->parent_->set_detected_(true); - this->parent_->set_active(true); - return 1; // Command done - } - } - if (millis() - this->parent_->ts_last_cmd_sent_ > this->timeout_ms_) { - return 1; // Command done, timeout - } - return 0; // Command not done yet. -} - -uint8_t ReadStateCommand::on_message(std::string &message) { return 1; } - -uint8_t PowerCommand::on_message(std::string &message) { - if (message == "sensor stopped already") { - this->parent_->set_active(false); - ESP_LOGI(TAG, "Stopped sensor (already stopped)"); - return 1; // Command done - } else if (message == "sensor started already") { - this->parent_->set_active(true); - ESP_LOGI(TAG, "Started sensor (already started)"); - return 1; // Command done - } else if (message == "new parameter isn't save, can't startSensor") { - this->parent_->set_active(false); - ESP_LOGE(TAG, "Can't start sensor! (Use SaveCfgCommand to save config first)"); - return 1; // Command done - } else if (message == "Done") { - this->parent_->set_active(this->power_on_); - if (this->power_on_) { - ESP_LOGI(TAG, "Started sensor"); - } else { - ESP_LOGI(TAG, "Stopped sensor"); - } - return 1; // Command done - } - return 0; // Command not done yet. -} - -DetRangeCfgCommand::DetRangeCfgCommand(float min1, float max1, float min2, float max2, float min3, float max3, - float min4, float max4) { +void DfrobotSen0395Component::enqueue_det_range_cfg(float min1, float max1, float min2, float max2, float min3, + float max3, float min4, float max4) { // TODO: Print warning when values are rounded - if (min1 < 0 || max1 < 0) { - this->min1_ = min1 = 0; - this->max1_ = max1 = 0; - this->min2_ = min2 = this->max2_ = max2 = this->min3_ = min3 = this->max3_ = max3 = this->min4_ = min4 = - this->max4_ = max4 = -1; - + const float values[8] = {min1, max1, min2, max2, min3, max3, min4, max4}; + int16_t steps[8]; + // Segments are used up to the first one with a negative bound + bool in_use = true; + for (size_t i = 0; i < 8; i += 2) { + in_use = in_use && values[i] >= 0 && values[i + 1] >= 0; + for (size_t j = i; j < i + 2; j++) { + steps[j] = in_use ? static_cast(clamp(rintf(values[j] / DET_RANGE_STEP_M), 0.0f, 32767.0f)) : -1; + } + } + if (steps[0] < 0) { ESP_LOGW(TAG, "DetRangeCfgCommand invalid input parameters. Using range config 0 0."); - - this->cmd_ = "detRangeCfg -1 0 0"; - } else if (min2 < 0 || max2 < 0) { - this->min1_ = min1 = roundf(min1 / 0.15f) * 0.15f; - this->max1_ = max1 = roundf(max1 / 0.15f) * 0.15f; - this->min2_ = min2 = this->max2_ = max2 = this->min3_ = min3 = this->max3_ = max3 = this->min4_ = min4 = - this->max4_ = max4 = -1; - - char buf[72]; // max 72: "detRangeCfg -1 "(15) + 8 * (float(5) + space(1)) + null - snprintf(buf, sizeof(buf), "detRangeCfg -1 %.0f %.0f", min1 / 0.15f, max1 / 0.15f); - this->cmd_ = buf; - } else if (min3 < 0 || max3 < 0) { - this->min1_ = min1 = roundf(min1 / 0.15f) * 0.15f; - this->max1_ = max1 = roundf(max1 / 0.15f) * 0.15f; - this->min2_ = min2 = roundf(min2 / 0.15f) * 0.15f; - this->max2_ = max2 = roundf(max2 / 0.15f) * 0.15f; - this->min3_ = min3 = this->max3_ = max3 = this->min4_ = min4 = this->max4_ = max4 = -1; - - char buf[72]; // max 72: "detRangeCfg -1 "(15) + 8 * (float(5) + space(1)) + null - snprintf(buf, sizeof(buf), "detRangeCfg -1 %.0f %.0f %.0f %.0f", min1 / 0.15f, max1 / 0.15f, min2 / 0.15f, - max2 / 0.15f); - this->cmd_ = buf; - } else if (min4 < 0 || max4 < 0) { - this->min1_ = min1 = roundf(min1 / 0.15f) * 0.15f; - this->max1_ = max1 = roundf(max1 / 0.15f) * 0.15f; - this->min2_ = min2 = roundf(min2 / 0.15f) * 0.15f; - this->max2_ = max2 = roundf(max2 / 0.15f) * 0.15f; - this->min3_ = min3 = roundf(min3 / 0.15f) * 0.15f; - this->max3_ = max3 = roundf(max3 / 0.15f) * 0.15f; - this->min4_ = min4 = this->max4_ = max4 = -1; - - char buf[72]; // max 72: "detRangeCfg -1 "(15) + 8 * (float(5) + space(1)) + null - snprintf(buf, sizeof(buf), "detRangeCfg -1 %.0f %.0f %.0f %.0f %.0f %.0f", min1 / 0.15f, max1 / 0.15f, min2 / 0.15f, - max2 / 0.15f, min3 / 0.15f, max3 / 0.15f); - this->cmd_ = buf; - } else { - this->min1_ = min1 = roundf(min1 / 0.15f) * 0.15f; - this->max1_ = max1 = roundf(max1 / 0.15f) * 0.15f; - this->min2_ = min2 = roundf(min2 / 0.15f) * 0.15f; - this->max2_ = max2 = roundf(max2 / 0.15f) * 0.15f; - this->min3_ = min3 = roundf(min3 / 0.15f) * 0.15f; - this->max3_ = max3 = roundf(max3 / 0.15f) * 0.15f; - this->min4_ = min4 = roundf(min4 / 0.15f) * 0.15f; - this->max4_ = max4 = roundf(max4 / 0.15f) * 0.15f; - - char buf[72]; // max 72: "detRangeCfg -1 "(15) + 8 * (float(5) + space(1)) + null - snprintf(buf, sizeof(buf), "detRangeCfg -1 %.0f %.0f %.0f %.0f %.0f %.0f %.0f %.0f", min1 / 0.15f, max1 / 0.15f, - min2 / 0.15f, max2 / 0.15f, min3 / 0.15f, max3 / 0.15f, min4 / 0.15f, max4 / 0.15f); - this->cmd_ = buf; + steps[0] = steps[1] = 0; } + if (this->enqueue(CommandType::COMMAND_TYPE_DET_RANGE_CFG)) + memcpy(this->det_range_steps_, steps, sizeof(steps)); +} - this->min1_ = min1; - this->max1_ = max1; - this->min2_ = min2; - this->max2_ = max2; - this->min3_ = min3; - this->max3_ = max3; - this->min4_ = min4; - this->max4_ = max4; -}; +void DfrobotSen0395Component::enqueue_set_latency(float delay_after_detection, float delay_after_disappear) { + if (!this->enqueue(CommandType::COMMAND_TYPE_SET_LATENCY)) + return; + // 1638.375 s is 65535 steps + this->latency_steps_[0] = + static_cast(clamp(std::round(delay_after_detection / LATENCY_STEP_S), 0.0f, 65535.0f)); + this->latency_steps_[1] = + static_cast(clamp(std::round(delay_after_disappear / LATENCY_STEP_S), 0.0f, 65535.0f)); +} -uint8_t DetRangeCfgCommand::on_message(std::string &message) { - if (message == "sensor is not stopped") { - ESP_LOGE(TAG, "Cannot configure range config. Sensor is not stopped!"); - return 1; // Command done - } else if (message == "Done") { - ESP_LOGI(TAG, - "Updated detection area config:\n" - " Detection area 1 from %.02fm to %.02fm.", - this->min1_, this->max1_); - if (this->min2_ >= 0 && this->max2_ >= 0) { - ESP_LOGI(TAG, " Detection area 2 from %.02fm to %.02fm.", this->min2_, this->max2_); +void DfrobotSen0395Component::format_command_(const Command &cmd, char *buf, size_t size) { + const char *text = ""; + switch (cmd.type) { + case CommandType::COMMAND_TYPE_POWER: + text = cmd.arg ? "sensorStart" : "sensorStop"; + break; + case CommandType::COMMAND_TYPE_DET_RANGE_CFG: { + size_t pos = buf_append_printf(buf, size, 0, "detRangeCfg -1"); + for (size_t i = 0; i < 8 && this->det_range_steps_[i] >= 0; i += 2) { + pos = buf_append_printf(buf, size, pos, " %d %d", this->det_range_steps_[i], this->det_range_steps_[i + 1]); + } + return; } - if (this->min3_ >= 0 && this->max3_ >= 0) { - ESP_LOGI(TAG, " Detection area 3 from %.02fm to %.02fm.", this->min3_, this->max3_); - } - if (this->min4_ >= 0 && this->max4_ >= 0) { - ESP_LOGI(TAG, " Detection area 4 from %.02fm to %.02fm.", this->min4_, this->max4_); - } - ESP_LOGD(TAG, " Used command: %s", this->cmd_.c_str()); - return 1; // Command done + case CommandType::COMMAND_TYPE_SET_LATENCY: + snprintf(buf, size, "setLatency %.03f %.03f", this->latency_steps_[0] * LATENCY_STEP_S, + this->latency_steps_[1] * LATENCY_STEP_S); + return; + case CommandType::COMMAND_TYPE_SENSOR_CFG_START: + snprintf(buf, size, "sensorCfgStart %d", cmd.arg); + return; + case CommandType::COMMAND_TYPE_FACTORY_RESET: + text = "factoryReset 0x45670123 0xCDEF89AB 0x956128C6 0xDF54AC89"; + break; + case CommandType::COMMAND_TYPE_RESET_SYSTEM: + text = "resetSystem"; + break; + case CommandType::COMMAND_TYPE_SAVE_CFG: + text = "saveCfg 0x45670123 0xCDEF89AB 0x956128C6 0xDF54AC89"; + break; + case CommandType::COMMAND_TYPE_LED_MODE: + text = cmd.arg ? "setLedMode 1 0" : "setLedMode 1 1"; + break; + case CommandType::COMMAND_TYPE_UART_OUTPUT: + text = cmd.arg ? "setUartOutput 1 1" : "setUartOutput 1 0"; + break; + case CommandType::COMMAND_TYPE_SENSITIVITY: + snprintf(buf, size, "setSensitivity %d", cmd.arg); + return; + case CommandType::COMMAND_TYPE_READ_STATE: + break; } - return 0; // Command not done yet. + snprintf(buf, size, "%s", text); } -SetLatencyCommand::SetLatencyCommand(float delay_after_detection, float delay_after_disappear) { - delay_after_detection = std::round(delay_after_detection / 0.025f) * 0.025f; - delay_after_disappear = std::round(delay_after_disappear / 0.025f) * 0.025f; - this->delay_after_detection_ = clamp(delay_after_detection, 0.0f, 1638.375f); - this->delay_after_disappear_ = clamp(delay_after_disappear, 0.0f, 1638.375f); - // max 32: "setLatency "(11) + float(8) + " "(1) + float(8) + null, rounded to 32 - char buf[32]; - snprintf(buf, sizeof(buf), "setLatency %.03f %.03f", this->delay_after_detection_, this->delay_after_disappear_); - this->cmd_ = buf; -}; +bool DfrobotSen0395Component::handle_reply_(const Command &cmd) { + const char *message = this->read_buffer_; + auto is = [message](const char *text) { return strcmp(message, text) == 0; }; + const bool not_stopped = is("sensor is not stopped"); + const bool done = is("Done"); + char used[72]; -uint8_t SetLatencyCommand::on_message(std::string &message) { - if (message == "sensor is not stopped") { - ESP_LOGE(TAG, "Cannot configure output latency. Sensor is not stopped!"); - return 1; // Command done - } else if (message == "Done") { - ESP_LOGI(TAG, - "Updated output latency config:\n" - " Signal that someone was detected is delayed by %.03f s.\n" - " Signal that nobody is detected anymore is delayed by %.03f s.", - this->delay_after_detection_, this->delay_after_disappear_); - ESP_LOGD(TAG, " Used command: %s", this->cmd_.c_str()); - return 1; // Command done - } - return 0; // Command not done yet -} + switch (cmd.type) { + case CommandType::COMMAND_TYPE_READ_STATE: + return true; -uint8_t SensorCfgStartCommand::on_message(std::string &message) { - if (message == "sensor is not stopped") { - ESP_LOGE(TAG, "Cannot configure sensor startup behavior. Sensor is not stopped!"); - return 1; // Command done - } else if (message == "Done") { - ESP_LOGI(TAG, "Updated sensor startup behavior:"); - if (startup_mode_) { - this->parent_->set_start_after_boot(true); - ESP_LOGI(TAG, "Sensor will start automatically after power-on."); - } else { - this->parent_->set_start_after_boot(false); - ESP_LOGI(TAG, "Sensor needs to be started manually after power-on."); - } - ESP_LOGD(TAG, "Used command: %s", this->cmd_.c_str()); - return 1; // Command done - } - return 0; // Command not done yet -} + case CommandType::COMMAND_TYPE_POWER: + if (is("sensor stopped already")) { + this->set_active(false); + ESP_LOGI(TAG, "Stopped sensor (already stopped)"); + } else if (is("sensor started already")) { + this->set_active(true); + ESP_LOGI(TAG, "Started sensor (already started)"); + } else if (is("new parameter isn't save, can't startSensor")) { + this->set_active(false); + ESP_LOGE(TAG, "Can't start sensor! (Use SaveCfgCommand to save config first)"); + } else if (done) { + this->set_active(cmd.arg); + if (cmd.arg) { + ESP_LOGI(TAG, "Started sensor"); + } else { + ESP_LOGI(TAG, "Stopped sensor"); + } + } else { + return false; + } + return true; -uint8_t FactoryResetCommand::on_message(std::string &message) { - if (message == "sensor is not stopped") { - ESP_LOGE(TAG, "Cannot factory reset. Sensor is not stopped!"); - return 1; // Command done - } else if (message == "Done") { - ESP_LOGI(TAG, "Sensor factory reset done."); - return 1; // Command done - } - return 0; // Command not done yet -} + case CommandType::COMMAND_TYPE_DET_RANGE_CFG: + if (not_stopped) { + ESP_LOGE(TAG, "Cannot configure range config. Sensor is not stopped!"); + return true; + } + if (!done) + return false; + ESP_LOGI(TAG, + "Updated detection area config:\n" + " Detection area 1 from %.02fm to %.02fm.", + this->det_range_steps_[0] * DET_RANGE_STEP_M, this->det_range_steps_[1] * DET_RANGE_STEP_M); + for (size_t i = 2; i < 8 && this->det_range_steps_[i] >= 0; i += 2) { + ESP_LOGI(TAG, " Detection area %u from %.02fm to %.02fm.", static_cast(i / 2 + 1), + this->det_range_steps_[i] * DET_RANGE_STEP_M, this->det_range_steps_[i + 1] * DET_RANGE_STEP_M); + } + break; -uint8_t ResetSystemCommand::on_message(std::string &message) { - if (message == "leapMMW:/>") { - ESP_LOGI(TAG, "Restarted sensor."); - return 1; // Command done - } - return 0; // Command not done yet -} + case CommandType::COMMAND_TYPE_SET_LATENCY: + if (not_stopped) { + ESP_LOGE(TAG, "Cannot configure output latency. Sensor is not stopped!"); + return true; + } + if (!done) + return false; + ESP_LOGI(TAG, + "Updated output latency config:\n" + " Signal that someone was detected is delayed by %.03f s.\n" + " Signal that nobody is detected anymore is delayed by %.03f s.", + this->latency_steps_[0] * LATENCY_STEP_S, this->latency_steps_[1] * LATENCY_STEP_S); + break; -uint8_t SaveCfgCommand::on_message(std::string &message) { - if (message == "no parameter has changed") { - ESP_LOGI(TAG, "Not saving config (no parameter changed)."); - return 1; // Command done - } else if (message == "Done") { - ESP_LOGI(TAG, "Saved config. Saving a lot may damage the sensor."); - return 1; // Command done - } - return 0; // Command not done yet -} + case CommandType::COMMAND_TYPE_SENSOR_CFG_START: + if (not_stopped) { + ESP_LOGE(TAG, "Cannot configure sensor startup behavior. Sensor is not stopped!"); + return true; + } + if (!done) + return false; + ESP_LOGI(TAG, "Updated sensor startup behavior:"); + this->set_start_after_boot(cmd.arg); + if (cmd.arg) { + ESP_LOGI(TAG, "Sensor will start automatically after power-on."); + } else { + ESP_LOGI(TAG, "Sensor needs to be started manually after power-on."); + } + break; -uint8_t LedModeCommand::on_message(std::string &message) { - if (message == "sensor is not stopped") { - ESP_LOGE(TAG, "Cannot set led mode. Sensor is not stopped!"); - return 1; // Command done - } else if (message == "Done") { - ESP_LOGI(TAG, "Set led mode done."); - if (this->active_) { - this->parent_->set_led_active(true); - ESP_LOGI(TAG, "Sensor LED will blink."); - } else { - this->parent_->set_led_active(false); - ESP_LOGI(TAG, "Turned off LED."); - } - ESP_LOGD(TAG, "Used command: %s", this->cmd_.c_str()); - return 1; // Command done - } - return 0; // Command not done yet -} + case CommandType::COMMAND_TYPE_FACTORY_RESET: + if (not_stopped) { + ESP_LOGE(TAG, "Cannot factory reset. Sensor is not stopped!"); + return true; + } + if (!done) + return false; + ESP_LOGI(TAG, "Sensor factory reset done."); + return true; -uint8_t UartOutputCommand::on_message(std::string &message) { - if (message == "sensor is not stopped") { - ESP_LOGE(TAG, "Cannot set uart output mode. Sensor is not stopped!"); - return 1; // Command done - } else if (message == "Done") { - ESP_LOGI(TAG, "Set uart mode done."); - if (this->active_) { - this->parent_->set_uart_presence_active(true); - ESP_LOGI(TAG, "Presence information is sent via UART and GPIO."); - } else { - this->parent_->set_uart_presence_active(false); - ESP_LOGI(TAG, "Presence information is only sent via GPIO."); - } - ESP_LOGD(TAG, "Used command: %s", this->cmd_.c_str()); - return 1; // Command done - } - return 0; // Command not done yet -} + case CommandType::COMMAND_TYPE_RESET_SYSTEM: + if (!is("leapMMW:/>")) + return false; + ESP_LOGI(TAG, "Restarted sensor."); + return true; -uint8_t SensitivityCommand::on_message(std::string &message) { - if (message == "sensor is not stopped") { - ESP_LOGE(TAG, "Cannot set sensitivity. Sensor is not stopped!"); - return 1; // Command done - } else if (message == "Done") { - ESP_LOGI(TAG, "Set sensitivity done. Set to value %d.", this->sensitivity_); - ESP_LOGD(TAG, "Used command: %s", this->cmd_.c_str()); - return 1; // Command done + case CommandType::COMMAND_TYPE_SAVE_CFG: + if (is("no parameter has changed")) { + ESP_LOGI(TAG, "Not saving config (no parameter changed)."); + return true; + } + if (!done) + return false; + ESP_LOGI(TAG, "Saved config. Saving a lot may damage the sensor."); + return true; + + case CommandType::COMMAND_TYPE_LED_MODE: + if (not_stopped) { + ESP_LOGE(TAG, "Cannot set led mode. Sensor is not stopped!"); + return true; + } + if (!done) + return false; + ESP_LOGI(TAG, "Set led mode done."); + this->set_led_active(cmd.arg); + if (cmd.arg) { + ESP_LOGI(TAG, "Sensor LED will blink."); + } else { + ESP_LOGI(TAG, "Turned off LED."); + } + break; + + case CommandType::COMMAND_TYPE_UART_OUTPUT: + if (not_stopped) { + ESP_LOGE(TAG, "Cannot set uart output mode. Sensor is not stopped!"); + return true; + } + if (!done) + return false; + ESP_LOGI(TAG, "Set uart mode done."); + this->set_uart_presence_active(cmd.arg); + if (cmd.arg) { + ESP_LOGI(TAG, "Presence information is sent via UART and GPIO."); + } else { + ESP_LOGI(TAG, "Presence information is only sent via GPIO."); + } + break; + + case CommandType::COMMAND_TYPE_SENSITIVITY: + if (not_stopped) { + ESP_LOGE(TAG, "Cannot set sensitivity. Sensor is not stopped!"); + return true; + } + if (!done) + return false; + ESP_LOGI(TAG, "Set sensitivity done. Set to value %d.", cmd.arg); + break; } - return 0; // Command not done yet + + this->format_command_(cmd, used, sizeof(used)); + ESP_LOGD(TAG, "Used command: %s", used); + return true; } } // namespace esphome::dfrobot_sen0395 diff --git a/esphome/components/dfrobot_sen0395/commands.h b/esphome/components/dfrobot_sen0395/commands.h index 765131bcfe8..07c20bfcfe1 100644 --- a/esphome/components/dfrobot_sen0395/commands.h +++ b/esphome/components/dfrobot_sen0395/commands.h @@ -1,152 +1,31 @@ #pragma once #include -#include - -#include "esphome/core/helpers.h" namespace esphome::dfrobot_sen0395 { -class DfrobotSen0395Component; - // Use command queue and time stamps to avoid blocking. // When component has run time, check if minimum time (1s) between // commands has passed. After that run a command from the queue. -class Command { - public: - virtual ~Command() = default; - virtual uint8_t execute(DfrobotSen0395Component *parent); - virtual uint8_t on_message(std::string &message) = 0; - - protected: - DfrobotSen0395Component *parent_{nullptr}; - std::string cmd_; - bool cmd_sent_{false}; - int8_t retries_left_{2}; - uint32_t cmd_duration_ms_{1000}; - uint32_t timeout_ms_{1500}; +enum class CommandType : uint8_t { + COMMAND_TYPE_READ_STATE, + COMMAND_TYPE_POWER, + COMMAND_TYPE_DET_RANGE_CFG, + COMMAND_TYPE_SET_LATENCY, + COMMAND_TYPE_SENSOR_CFG_START, + COMMAND_TYPE_FACTORY_RESET, + COMMAND_TYPE_RESET_SYSTEM, + COMMAND_TYPE_SAVE_CFG, + COMMAND_TYPE_LED_MODE, + COMMAND_TYPE_UART_OUTPUT, + COMMAND_TYPE_SENSITIVITY, }; -class ReadStateCommand : public Command { - public: - ReadStateCommand() { timeout_ms_ = 500; } - uint8_t execute(DfrobotSen0395Component *parent) override; - uint8_t on_message(std::string &message) override; -}; - -class PowerCommand : public Command { - public: - PowerCommand(bool power_on) : power_on_(power_on) { - if (power_on) { - cmd_ = "sensorStart"; - } else { - cmd_ = "sensorStop"; - } - }; - uint8_t on_message(std::string &message) override; - - protected: - bool power_on_; -}; - -class DetRangeCfgCommand : public Command { - public: - DetRangeCfgCommand(float min1, float max1, float min2, float max2, float min3, float max3, float min4, float max4); - uint8_t on_message(std::string &message) override; - - protected: - float min1_, max1_, min2_, max2_, min3_, max3_, min4_, max4_; - // TODO: Set min max values in component, so they can be published as sensor. -}; - -class SetLatencyCommand : public Command { - public: - SetLatencyCommand(float delay_after_detection, float delay_after_disappear); - uint8_t on_message(std::string &message) override; - - protected: - float delay_after_detection_; - float delay_after_disappear_; -}; - -class SensorCfgStartCommand : public Command { - public: - SensorCfgStartCommand(bool startup_mode) : startup_mode_(startup_mode) { - char tmp_cmd[20]; // "sensorCfgStart " (15) + "0/1" (1) + null = 17 - buf_append_printf(tmp_cmd, sizeof(tmp_cmd), 0, "sensorCfgStart %d", startup_mode); - cmd_ = std::string(tmp_cmd); - } - uint8_t on_message(std::string &message) override; - - protected: - bool startup_mode_; -}; - -class FactoryResetCommand : public Command { - public: - FactoryResetCommand() { cmd_ = "factoryReset 0x45670123 0xCDEF89AB 0x956128C6 0xDF54AC89"; }; - uint8_t on_message(std::string &message) override; -}; - -class ResetSystemCommand : public Command { - public: - ResetSystemCommand() { cmd_ = "resetSystem"; } - uint8_t on_message(std::string &message) override; -}; - -class SaveCfgCommand : public Command { - public: - SaveCfgCommand() { - cmd_ = "saveCfg 0x45670123 0xCDEF89AB 0x956128C6 0xDF54AC89"; - cmd_duration_ms_ = 3000; - timeout_ms_ = 3500; - } - uint8_t on_message(std::string &message) override; -}; - -class LedModeCommand : public Command { - public: - LedModeCommand(bool active) : active_(active) { - if (active) { - cmd_ = "setLedMode 1 0"; - } else { - cmd_ = "setLedMode 1 1"; - } - }; - uint8_t on_message(std::string &message) override; - - protected: - bool active_; -}; - -class UartOutputCommand : public Command { - public: - UartOutputCommand(bool active) : active_(active) { - if (active) { - cmd_ = "setUartOutput 1 1"; - } else { - cmd_ = "setUartOutput 1 0"; - } - }; - uint8_t on_message(std::string &message) override; - - protected: - bool active_; -}; - -class SensitivityCommand : public Command { - public: - SensitivityCommand(uint8_t sensitivity) : sensitivity_(sensitivity) { - if (sensitivity > 9) - sensitivity_ = sensitivity = 9; - char tmp_cmd[20]; // "setSensitivity " (15) + "0-9" (1) + null = 17 - buf_append_printf(tmp_cmd, sizeof(tmp_cmd), 0, "setSensitivity %d", sensitivity); - cmd_ = std::string(tmp_cmd); - }; - uint8_t on_message(std::string &message) override; - - protected: - uint8_t sensitivity_; +/// A queued command. arg is the on/off flag, startup mode or sensitivity; the detection range and +/// latency values are held by the component. +struct Command { + CommandType type; + uint8_t arg; }; } // namespace esphome::dfrobot_sen0395 diff --git a/esphome/components/dfrobot_sen0395/dfrobot_sen0395.cpp b/esphome/components/dfrobot_sen0395/dfrobot_sen0395.cpp index 9c092982e55..3eed6e520a6 100644 --- a/esphome/components/dfrobot_sen0395/dfrobot_sen0395.cpp +++ b/esphome/components/dfrobot_sen0395/dfrobot_sen0395.cpp @@ -1,5 +1,7 @@ #include "dfrobot_sen0395.h" +#include + #include "esphome/core/helpers.h" #include "esphome/core/log.h" @@ -23,20 +25,84 @@ void DfrobotSen0395Component::dump_config() { } void DfrobotSen0395Component::loop() { - if (cmd_queue_.is_empty()) { + if (this->cmd_queue_.is_empty()) { // Command queue empty. Read sensor state. - cmd_queue_.enqueue(make_unique()); + this->cmd_queue_.enqueue({CommandType::COMMAND_TYPE_READ_STATE, 0}); } // Commands are non-blocking and need to be called repeatedly. - if (cmd_queue_.process(this)) { + if (this->run_command_(this->cmd_queue_.front())) { // Dequeue if command is done - cmd_queue_.dequeue(); + this->cmd_queue_.dequeue(); + this->cmd_sent_ = false; + this->retries_left_ = 2; } } -int8_t DfrobotSen0395Component::enqueue(std::unique_ptr cmd) { - return cmd_queue_.enqueue(std::move(cmd)); // Transfer ownership using std::move +bool DfrobotSen0395Component::enqueue(CommandType type, uint8_t arg) { + if (type == CommandType::COMMAND_TYPE_SENSITIVITY && arg > 9) + arg = 9; + return this->cmd_queue_.enqueue({type, arg}); +} + +bool DfrobotSen0395Component::run_command_(const Command &cmd) { + if (cmd.type == CommandType::COMMAND_TYPE_READ_STATE) + return this->run_read_state_(); + + const bool save_cfg = cmd.type == CommandType::COMMAND_TYPE_SAVE_CFG; + if (!this->cmd_sent_) { + // The interval between two commands must be larger than the command duration + if (millis() - this->ts_last_cmd_sent_ > (save_cfg ? 3000 : 1000)) { + char buf[72]; // "detRangeCfg -1" + 8 * " 32767" + null + this->format_command_(cmd, buf, sizeof(buf)); + this->write_str(buf); + this->ts_last_cmd_sent_ = millis(); + this->cmd_sent_ = true; + } + return false; // Command not done yet + } + + if (this->read_message_()) { + if (strstr(this->read_buffer_, "is not recognized as a CLI command") != nullptr) { + ESP_LOGD(TAG, "Command not recognized properly by sensor"); + } else if (this->handle_reply_(cmd)) { + this->find_prompt_(); + return true; // Command done + } else { + return false; // Command not done yet + } + } else if (millis() - this->ts_last_cmd_sent_ > (save_cfg ? 3500 : 1500)) { + ESP_LOGD(TAG, "Command timeout"); + if (this->retries_left_ == 0) + return true; // Command done + } else { + return false; // Command not done yet + } + + if (this->retries_left_ == 0) { + this->find_prompt_(); + return true; // Command done + } + this->retries_left_--; + this->cmd_sent_ = false; + ESP_LOGD(TAG, "Retrying"); + return false; +} + +bool DfrobotSen0395Component::run_read_state_() { + if (this->read_message_()) { + if (strstr(this->read_buffer_, "$JYBSS,0, , , *") != nullptr) { + this->set_detected_(false); + this->set_active(true); + return true; + } + if (strstr(this->read_buffer_, "$JYBSS,1, , , *") != nullptr) { + this->set_detected_(true); + this->set_active(true); + return true; + } + } + return millis() - this->ts_last_cmd_sent_ > 500; // Done on timeout } uint8_t DfrobotSen0395Component::read_message_() { @@ -71,26 +137,12 @@ uint8_t DfrobotSen0395Component::read_message_() { } uint8_t DfrobotSen0395Component::find_prompt_() { - if (this->read_message_()) { - std::string message(this->read_buffer_); - if (message.rfind("leapMMW:/>") != std::string::npos) { - return 1; // Prompt found - } + if (this->read_message_() && strstr(this->read_buffer_, "leapMMW:/>") != nullptr) { + return 1; // Prompt found } return 0; // Not found yet } -uint8_t DfrobotSen0395Component::send_cmd_(const char *cmd, uint32_t duration) { - // The interval between two commands must be larger than the specified duration (in ms). - if (millis() - ts_last_cmd_sent_ > duration) { - this->write_str(cmd); - ts_last_cmd_sent_ = millis(); - return 1; // Command sent - } - // Could not send command yet as command duration did not fully pass yet. - return 0; -} - void DfrobotSen0395Component::set_detected_(bool detected) { this->detected_ = detected; #ifdef USE_BINARY_SENSOR @@ -99,43 +151,26 @@ void DfrobotSen0395Component::set_detected_(bool detected) { #endif } -int8_t CircularCommandQueue::enqueue(std::unique_ptr cmd) { +bool CircularCommandQueue::enqueue(Command cmd) { if (this->is_full()) { ESP_LOGE(TAG, "Command queue is full"); - return -1; - } else if (this->is_empty()) { - front_++; + return false; } - rear_ = (rear_ + 1) % COMMAND_QUEUE_SIZE; - commands_[rear_] = std::move(cmd); // Transfer ownership using std::move - return 1; -} - -std::unique_ptr CircularCommandQueue::dequeue() { if (this->is_empty()) - return nullptr; - std::unique_ptr dequeued_cmd = std::move(commands_[front_]); - if (front_ == rear_) { - front_ = -1; - rear_ = -1; - } else { - front_ = (front_ + 1) % COMMAND_QUEUE_SIZE; - } - - return dequeued_cmd; + this->front_ = 0; + this->rear_ = (this->rear_ + 1) % COMMAND_QUEUE_SIZE; + this->commands_[this->rear_] = cmd; + return true; } -bool CircularCommandQueue::is_empty() { return front_ == -1; } - -bool CircularCommandQueue::is_full() { return (rear_ + 1) % COMMAND_QUEUE_SIZE == front_; } - -// Run execute method of first in line command. -// Execute is non-blocking and has to be called until it returns 1. -uint8_t CircularCommandQueue::process(DfrobotSen0395Component *parent) { - if (!is_empty()) { - return commands_[front_]->execute(parent); +void CircularCommandQueue::dequeue() { + if (this->is_empty()) + return; + if (this->front_ == this->rear_) { + this->front_ = -1; + this->rear_ = -1; } else { - return 1; + this->front_ = (this->front_ + 1) % COMMAND_QUEUE_SIZE; } } diff --git a/esphome/components/dfrobot_sen0395/dfrobot_sen0395.h b/esphome/components/dfrobot_sen0395/dfrobot_sen0395.h index 500f20d8dab..59aaaa7125c 100644 --- a/esphome/components/dfrobot_sen0395/dfrobot_sen0395.h +++ b/esphome/components/dfrobot_sen0395/dfrobot_sen0395.h @@ -24,16 +24,16 @@ static const uint8_t COMMAND_QUEUE_SIZE = 20; class CircularCommandQueue { public: - int8_t enqueue(std::unique_ptr cmd); - std::unique_ptr dequeue(); - bool is_empty(); - bool is_full(); - uint8_t process(DfrobotSen0395Component *parent); + bool enqueue(Command cmd); + void dequeue(); + bool is_empty() const { return this->front_ == -1; } + bool is_full() const { return (this->rear_ + 1) % COMMAND_QUEUE_SIZE == this->front_; } + const Command &front() const { return this->commands_[this->front_]; } protected: - int front_{-1}; - int rear_{-1}; - std::unique_ptr commands_[COMMAND_QUEUE_SIZE]; + int8_t front_{-1}; + int8_t rear_{-1}; + Command commands_[COMMAND_QUEUE_SIZE]; }; class DfrobotSen0395Component final : public uart::UARTDevice, public Component { @@ -93,9 +93,14 @@ class DfrobotSen0395Component final : public uart::UARTDevice, public Component } #endif - int8_t enqueue(std::unique_ptr cmd); + bool enqueue(CommandType type, uint8_t arg = 0); + /// Queue a detection range config; a negative value ends the list of segments + void enqueue_det_range_cfg(float min1, float max1, float min2, float max2, float min3, float max3, float min4, + float max4); + /// Queue an output latency config, both delays in seconds + void enqueue_set_latency(float delay_after_detection, float delay_after_disappear); /// Queue a sensor reset - void reset_system() { this->enqueue(make_unique()); } + void reset_system() { this->enqueue(CommandType::COMMAND_TYPE_RESET_SYSTEM); } protected: #ifdef USE_BINARY_SENSOR @@ -109,17 +114,25 @@ class DfrobotSen0395Component final : public uart::UARTDevice, public Component bool start_after_boot_{false}; char read_buffer_[MMWAVE_READ_BUFFER_LENGTH]; size_t read_pos_{0}; + bool cmd_sent_{false}; + int8_t retries_left_{2}; CircularCommandQueue cmd_queue_; uint32_t ts_last_cmd_sent_{0}; + // Values of the queued range and latency commands, in the sensor's steps of 0.15 m and 0.025 s. + // A later command replaces them, which leaves the sensor in the same final state. + int16_t det_range_steps_[8]{}; + uint16_t latency_steps_[2]{}; uint8_t read_message_(); uint8_t find_prompt_(); - uint8_t send_cmd_(const char *cmd, uint32_t duration); + /// Run the command at the front of the queue; true when it is done + bool run_command_(const Command &cmd); + bool run_read_state_(); + /// Handle a reply to a sent command; true when the command is done + bool handle_reply_(const Command &cmd); + void format_command_(const Command &cmd, char *buf, size_t size); void set_detected_(bool detected); - - friend class Command; - friend class ReadStateCommand; }; } // namespace esphome::dfrobot_sen0395 diff --git a/esphome/components/dfrobot_sen0395/switch/dfrobot_sen0395_switch.cpp b/esphome/components/dfrobot_sen0395/switch/dfrobot_sen0395_switch.cpp index 477a6f98af0..ff4fcdb5780 100644 --- a/esphome/components/dfrobot_sen0395/switch/dfrobot_sen0395_switch.cpp +++ b/esphome/components/dfrobot_sen0395/switch/dfrobot_sen0395_switch.cpp @@ -2,18 +2,18 @@ namespace esphome::dfrobot_sen0395 { -void Sen0395PowerSwitch::write_state(bool state) { this->parent_->enqueue(make_unique(state)); } +void Sen0395PowerSwitch::write_state(bool state) { this->parent_->enqueue(CommandType::COMMAND_TYPE_POWER, state); } void Sen0395LedSwitch::write_state(bool state) { bool was_active = false; if (this->parent_->is_active()) { was_active = true; - this->parent_->enqueue(make_unique(false)); + this->parent_->enqueue(CommandType::COMMAND_TYPE_POWER, false); } - this->parent_->enqueue(make_unique(state)); - this->parent_->enqueue(make_unique()); + this->parent_->enqueue(CommandType::COMMAND_TYPE_LED_MODE, state); + this->parent_->enqueue(CommandType::COMMAND_TYPE_SAVE_CFG); if (was_active) { - this->parent_->enqueue(make_unique(true)); + this->parent_->enqueue(CommandType::COMMAND_TYPE_POWER, true); } } @@ -21,12 +21,12 @@ void Sen0395UartPresenceSwitch::write_state(bool state) { bool was_active = false; if (this->parent_->is_active()) { was_active = true; - this->parent_->enqueue(make_unique(false)); + this->parent_->enqueue(CommandType::COMMAND_TYPE_POWER, false); } - this->parent_->enqueue(make_unique(state)); - this->parent_->enqueue(make_unique()); + this->parent_->enqueue(CommandType::COMMAND_TYPE_UART_OUTPUT, state); + this->parent_->enqueue(CommandType::COMMAND_TYPE_SAVE_CFG); if (was_active) { - this->parent_->enqueue(make_unique(true)); + this->parent_->enqueue(CommandType::COMMAND_TYPE_POWER, true); } } @@ -34,12 +34,12 @@ void Sen0395StartAfterBootSwitch::write_state(bool state) { bool was_active = false; if (this->parent_->is_active()) { was_active = true; - this->parent_->enqueue(make_unique(false)); + this->parent_->enqueue(CommandType::COMMAND_TYPE_POWER, false); } - this->parent_->enqueue(make_unique(state)); - this->parent_->enqueue(make_unique()); + this->parent_->enqueue(CommandType::COMMAND_TYPE_SENSOR_CFG_START, state); + this->parent_->enqueue(CommandType::COMMAND_TYPE_SAVE_CFG); if (was_active) { - this->parent_->enqueue(make_unique(true)); + this->parent_->enqueue(CommandType::COMMAND_TYPE_POWER, true); } } diff --git a/tests/components/dfrobot_sen0395/settings_action.cpp b/tests/components/dfrobot_sen0395/settings_action.cpp index 92a75b81677..c5099220319 100644 --- a/tests/components/dfrobot_sen0395/settings_action.cpp +++ b/tests/components/dfrobot_sen0395/settings_action.cpp @@ -29,7 +29,7 @@ float segment_value(size_t index, float x, int y = 0) { } // namespace // The component is final and its command queue protected, so these tests check the segment -// reads and their arguments; the enqueued DetRangeCfgCommand itself is not inspected. +// reads and their arguments; the queued detection range command itself is not inspected. class SettingsActionTest : public ::testing::Test { protected: void SetUp() override { call_record() = CallRecord{}; }