diff --git a/esphome/components/ld2415h/__init__.py b/esphome/components/ld2415h/__init__.py new file mode 100644 index 0000000000..eee632b903 --- /dev/null +++ b/esphome/components/ld2415h/__init__.py @@ -0,0 +1,38 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import uart +from esphome.const import CONF_ID + +CODEOWNERS = ["@cptskippy"] + +DEPENDENCIES = ["uart"] + +MULTI_CONF = True + +ld2415h_ns = cg.esphome_ns.namespace("ld2415h") +LD2415HComponent = ld2415h_ns.class_("LD2415HComponent", cg.Component, uart.UARTDevice) + +CONF_LD2415H_ID = "ld2415h_id" + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(LD2415HComponent), + } + ) + .extend(uart.UART_DEVICE_SCHEMA) + .extend(cv.COMPONENT_SCHEMA) +) + +FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema( + "ld2415h_uart", + require_tx=True, + require_rx=True, + parity="NONE", + stop_bits=1, +) + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await uart.register_uart_device(var, config) diff --git a/esphome/components/ld2415h/ld2415h.cpp b/esphome/components/ld2415h/ld2415h.cpp new file mode 100644 index 0000000000..310f8e5362 --- /dev/null +++ b/esphome/components/ld2415h/ld2415h.cpp @@ -0,0 +1,473 @@ +#include "ld2415h.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace ld2415h { + +static const char *const TAG = "ld2415h"; + +static const uint8_t LD2415H_CMD_SET_SPEED_ANGLE_SENSE[] = {0x43, 0x46, 0x01, 0x01, 0x00, 0x05, 0x0d, 0x0a}; +static const uint8_t LD2415H_CMD_SET_MODE_RATE_UOM[] = {0x43, 0x46, 0x02, 0x01, 0x01, 0x00, 0x0d, 0x0a}; +static const uint8_t LD2415H_CMD_SET_ANTI_VIB_COMP[] = {0x43, 0x46, 0x03, 0x05, 0x00, 0x00, 0x0d, 0x0a}; +static const uint8_t LD2415H_CMD_SET_RELAY_DURATION_SPEED[] = {0x43, 0x46, 0x04, 0x03, 0x01, 0x00, 0x0d, 0x0a}; +static const uint8_t LD2415H_CMD_GET_CONFIG[] = {0x43, 0x46, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + +/* TODO :: + * Create controls that expose settings +*/ + +LD2415HComponent::LD2415HComponent() + : cmd_speed_angle_sense_ {0x43, 0x46, 0x01, 0x01, 0x00, 0x05, 0x0d, 0x0a}, + cmd_mode_rate_uom_ {0x43, 0x46, 0x02, 0x01, 0x01, 0x00, 0x0d, 0x0a}, + cmd_anti_vib_comp_ {0x43, 0x46, 0x03, 0x05, 0x00, 0x00, 0x0d, 0x0a}, + cmd_relay_duration_speed_ {0x43, 0x46, 0x04, 0x03, 0x01, 0x00, 0x0d, 0x0a}, + cmd_config_ {0x43, 0x46, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} {} + + +void LD2415HComponent::setup() { + // This triggers current sensor configurations to be dumped + //this->issue_command_(LD2415H_CMD_GET_CONFIG, sizeof(LD2415H_CMD_GET_CONFIG)); + this->update_config_ = true; +#ifdef USE_NUMBER + this->min_speed_threshold_number_->publish_state(this->min_speed_threshold_); + this->compensation_angle_number_->publish_state(this->compensation_angle_); + this->sensitivity_number_->publish_state(this->sensitivity_); + this->vibration_correction_number_->publish_state(this->vibration_correction_); + this->relay_trigger_duration_number_->publish_state(this->relay_trigger_duration_); + this->relay_trigger_speed_number_->publish_state(this->relay_trigger_speed_); +#endif +#ifdef USE_SELECT + this->sample_rate_selector_->publish_state(i_to_s_(SAMPLE_RATE_STR_TO_INT, this->sample_rate_)); + this->tracking_mode_selector_->publish_state(i_to_s_(TRACKING_MODE_STR_TO_INT, this->tracking_mode_)); +#endif +} + +void LD2415HComponent::dump_config() { + ESP_LOGCONFIG(TAG, "LD2415H:"); + ESP_LOGCONFIG(TAG, " Firmware: %s", this->firmware_); + ESP_LOGCONFIG(TAG, " Minimum Speed Threshold: %u KPH", this->min_speed_threshold_); + ESP_LOGCONFIG(TAG, " Compensation Angle: %u", this->compensation_angle_); + ESP_LOGCONFIG(TAG, " Sensitivity: %u", this->sensitivity_); + ESP_LOGCONFIG(TAG, " Tracking Mode: %s", TrackingMode_to_s_(this->tracking_mode_)); + ESP_LOGCONFIG(TAG, " Sampling Rate: %u", this->sample_rate_); + ESP_LOGCONFIG(TAG, " Unit of Measure: %s", UnitOfMeasure_to_s_(this->unit_of_measure_)); + ESP_LOGCONFIG(TAG, " Vibration Correction: %u", this->vibration_correction_); + ESP_LOGCONFIG(TAG, " Relay Trigger Duration: %u", this->relay_trigger_duration_); + ESP_LOGCONFIG(TAG, " Relay Trigger Speed: %u KPH", this->relay_trigger_speed_); + ESP_LOGCONFIG(TAG, " Negotiation Mode: %s", NegotiationMode_to_s_(this->negotiation_mode_)); + + //LOG_SELECT(TAG, " Sample Rate", this->sample_rate_); + //LOG_SELECT(TAG, " Tracking Mode", this->tracking_mode_); +} + +void LD2415HComponent::loop() { + // Process the stream from the sensor UART + while (this->available()) { + if (this->fill_buffer_(this->read())) { + this->parse_buffer_(); + } + } + + if(this->update_speed_angle_sense_) { + ESP_LOGD(TAG, "LD2415H_CMD_SET_SPEED_ANGLE_SENSE: "); + this->cmd_speed_angle_sense_[3] = this->min_speed_threshold_; + this->cmd_speed_angle_sense_[4] = this->compensation_angle_; + this->cmd_speed_angle_sense_[5] = this->sensitivity_; + + this->issue_command_(this->cmd_speed_angle_sense_, sizeof(this->cmd_speed_angle_sense_)); + this->update_speed_angle_sense_ = false; + return; + } + + if(this->update_mode_rate_uom_) { + ESP_LOGD(TAG, "LD2415H_CMD_SET_MODE_RATE_UOM: "); + this->cmd_mode_rate_uom_[3] = static_cast(this->tracking_mode_); + this->cmd_mode_rate_uom_[4] = this->sample_rate_; + + this->issue_command_(this->cmd_mode_rate_uom_, sizeof(this->cmd_mode_rate_uom_)); + this->update_mode_rate_uom_ = false; + return; + } + + if(this->update_anti_vib_comp_) { + ESP_LOGD(TAG, "LD2415H_CMD_SET_ANTI_VIB_COMP: "); + this->cmd_anti_vib_comp_[3] = this->vibration_correction_; + + this->issue_command_(this->cmd_anti_vib_comp_, sizeof(this->cmd_anti_vib_comp_)); + this->update_anti_vib_comp_ = false; + return; + } + + if(this->update_relay_duration_speed_) { + ESP_LOGD(TAG, "LD2415H_CMD_SET_RELAY_DURATION_SPEED: "); + this->cmd_relay_duration_speed_[3] = this->relay_trigger_duration_; + this->cmd_relay_duration_speed_[4] = this->relay_trigger_speed_; + + this->issue_command_(this->cmd_relay_duration_speed_, sizeof(this->cmd_relay_duration_speed_)); + this->update_relay_duration_speed_ = false; + return; + } + + if(this->update_config_) { + ESP_LOGD(TAG, "LD2415H_CMD_GET_CONFIG: "); + + this->issue_command_(this->cmd_config_, sizeof(this->cmd_config_)); + this->update_config_ = false; + return; + } +} + +void LD2415HComponent::set_min_speed_threshold(uint8_t speed) { + this->min_speed_threshold_ = speed; + this->update_speed_angle_sense_ = true; +} + +void LD2415HComponent::set_compensation_angle(uint8_t angle) { + this->compensation_angle_ = angle; + this->update_speed_angle_sense_ = true; +} + +void LD2415HComponent::set_sensitivity(uint8_t sensitivity) { + this->sensitivity_ = sensitivity; + this->update_speed_angle_sense_ = true; +} + +void LD2415HComponent::set_tracking_mode(const std::string &state) { + uint8_t mode = TRACKING_MODE_STR_TO_INT.at(state); + this->set_tracking_mode(mode); + this->tracking_mode_selector_->publish_state(state); +} + +void LD2415HComponent::set_tracking_mode(TrackingMode mode) { + this->tracking_mode_ = mode; + this->update_mode_rate_uom_ = true; +} + +void LD2415HComponent::set_tracking_mode(uint8_t mode) { + this->set_tracking_mode(i_to_TrackingMode_(mode)); +} + +void LD2415HComponent::set_sample_rate(const std::string &state) { + uint8_t rate = SAMPLE_RATE_STR_TO_INT.at(state); + this->set_sample_rate(rate); + this->sample_rate_selector_->publish_state(state); +} + +void LD2415HComponent::set_sample_rate(uint8_t rate) { + ESP_LOGD(TAG, "set_sample_rate: %i", rate); + this->sample_rate_ = rate; + this->update_mode_rate_uom_ = true; +} + +void LD2415HComponent::set_vibration_correction(uint8_t correction) { + this->vibration_correction_ = correction; + this->update_anti_vib_comp_ = true; +} + +void LD2415HComponent::set_relay_trigger_duration(uint8_t duration) { + this->relay_trigger_duration_ = duration; + this->update_relay_duration_speed_ = true; +} + +void LD2415HComponent::set_relay_trigger_speed(uint8_t speed) { + this->relay_trigger_speed_ = speed; + this->update_relay_duration_speed_ = true; +} + +void LD2415HComponent::issue_command_(const uint8_t cmd[], const uint8_t size) { + for(uint8_t i = 0; i < size; i++) + ESP_LOGD(TAG, " 0x%02x", cmd[i]); + + // Don't assume the response buffer is empty, clear it before issuing a command. + clear_remaining_buffer_(0); + this->write_array(cmd, size); +} + +bool LD2415HComponent::fill_buffer_(char c) { + switch(c) { + case 0x00: + case 0xFF: + case '\r': + // Ignore these characters + break; + + case '\n': + // End of response + if(this->response_buffer_index_ == 0) + break; + + clear_remaining_buffer_(this->response_buffer_index_); + ESP_LOGV(TAG, "Response Received:: %s", this->response_buffer_); + return true; + + default: + // Append to response + this->response_buffer_[this->response_buffer_index_] = c; + this->response_buffer_index_++; + break; + } + + return false; +} + +void LD2415HComponent::clear_remaining_buffer_(uint8_t pos) { + while(pos < sizeof(this->response_buffer_)) { + this->response_buffer_[pos] = 0x00; + pos++; + } + + this->response_buffer_index_ = 0; +} + +void LD2415HComponent::parse_buffer_() { + char c = this->response_buffer_[0]; + + switch(c) { + case 'N': + // Firmware Version + this->parse_firmware_(); + break; + case 'X': + // Config Response + this->parse_config_(); + break; + case 'V': + // Speed + this->parse_speed_(); + break; + + default: + ESP_LOGE(TAG, "Unknown Response: %s", this->response_buffer_); + break; + } +} + +void LD2415HComponent::parse_config_() { + // Example: "X1:01 X2:00 X3:05 X4:01 X5:00 X6:00 X7:05 X8:03 X9:01 X0:01" + + const char* delim = ": "; + uint8_t token_len = 2; + char* key; + char* val; + + char* token = strtok(this->response_buffer_, delim); + + while (token != NULL) + { + if(std::strlen(token) != token_len) { + ESP_LOGE(TAG, "Configuration key length invalid."); + break; + } + key = token; + + token = strtok(NULL, delim); + if(std::strlen(token) != token_len) { + ESP_LOGE(TAG, "Configuration value length invalid."); + break; + } + val = token; + + this->parse_config_param_(key, val); + + token = strtok(NULL, delim); + } + + ESP_LOGD(TAG, "Configuration received:"); + ESP_LOGCONFIG(TAG, "LD2415H:"); + ESP_LOGCONFIG(TAG, " Firmware: %s", this->firmware_); + ESP_LOGCONFIG(TAG, " Minimum Speed Threshold: %u KPH", this->min_speed_threshold_); + ESP_LOGCONFIG(TAG, " Compensation Angle: %u", this->compensation_angle_); + ESP_LOGCONFIG(TAG, " Sensitivity: %u", this->sensitivity_); + ESP_LOGCONFIG(TAG, " Tracking Mode: %s", TrackingMode_to_s_(this->tracking_mode_)); + ESP_LOGCONFIG(TAG, " Sampling Rate: %u", this->sample_rate_); + ESP_LOGCONFIG(TAG, " Unit of Measure: %s", UnitOfMeasure_to_s_(this->unit_of_measure_)); + ESP_LOGCONFIG(TAG, " Vibration Correction: %u", this->vibration_correction_); + ESP_LOGCONFIG(TAG, " Relay Trigger Duration: %u", this->relay_trigger_duration_); + ESP_LOGCONFIG(TAG, " Relay Trigger Speed: %u KPH", this->relay_trigger_speed_); + ESP_LOGCONFIG(TAG, " Negotiation Mode: %s", NegotiationMode_to_s_(this->negotiation_mode_)); + +} + +void LD2415HComponent::parse_firmware_() { + // Example: "No.:20230801E v5.0" + + const char* fw = strchr(this->response_buffer_, ':'); + + if (fw != nullptr) { + // Move p to the character after ':' + ++fw; + + // Copy string into firmware + std::strcpy(this->firmware_, fw); + } else { + ESP_LOGE(TAG, "Firmware value invalid."); + } +} + +void LD2415HComponent::parse_speed_() { + // Example: "V+001.9" + + const char* p = strchr(this->response_buffer_, 'V'); + + if (p != nullptr) { + ++p; + this->approaching_ = (*p == '+'); + ++p; + this->speed_ = atof(p); + + ESP_LOGV(TAG, "Speed updated: %f KPH", this->speed_); + + for (auto &listener : this->listeners_) { + listener->on_speed(this->speed_); + //listener->on_approaching(this->approaching_); + } + + if (this->speed_sensor_ != nullptr) + this->speed_sensor_->publish_state(this->speed_); + + } else { + ESP_LOGE(TAG, "Firmware value invalid."); + } +} + +void LD2415HComponent::parse_config_param_(char* key, char* value) { + if(std::strlen(key) != 2 || std::strlen(value) != 2 || key[0] != 'X') { + ESP_LOGE(TAG, "Invalid Parameter %s:%s", key, value); + return; + } + + uint8_t v = std::stoi(value, nullptr, 16); + + switch(key[1]) { + case '1': + this->min_speed_threshold_ = v; + break; + case '2': + this->compensation_angle_ = std::stoi(value, nullptr, 16); + break; + case '3': + this->sensitivity_ = std::stoi(value, nullptr, 16); + break; + case '4': + this->tracking_mode_ = this->i_to_TrackingMode_(v); + break; + case '5': + this->sample_rate_ = v; + break; + case '6': + this->unit_of_measure_ = this->i_to_UnitOfMeasure_(v); + break; + case '7': + this->vibration_correction_ = v; + break; + case '8': + this->relay_trigger_duration_ = v; + break; + case '9': + this->relay_trigger_speed_ = v; + break; + case '0': + this->negotiation_mode_ = this->i_to_NegotiationMode_(v); + break; + default: + ESP_LOGD(TAG, "Unknown Parameter %s:%s", key, value); + break; + } +} + +TrackingMode LD2415HComponent::i_to_TrackingMode_(uint8_t value) { + TrackingMode u = TrackingMode(value); + ESP_LOGD(TAG, "i_to_TrackingMode_: %i, %i", value, static_cast(u)); + switch (u) + { + case TrackingMode::APPROACHING_AND_RETREATING: + return TrackingMode::APPROACHING_AND_RETREATING; + case TrackingMode::APPROACHING: + return TrackingMode::APPROACHING; + case TrackingMode::RETREATING: + return TrackingMode::RETREATING; + default: + ESP_LOGE(TAG, "Invalid TrackingMode:%u", value); + return TrackingMode::APPROACHING_AND_RETREATING; + } +} + +UnitOfMeasure LD2415HComponent::i_to_UnitOfMeasure_(uint8_t value) { + UnitOfMeasure u = UnitOfMeasure(value); + switch (u) + { + case UnitOfMeasure::MPS: + return UnitOfMeasure::MPS; + case UnitOfMeasure::MPH: + return UnitOfMeasure::MPH; + case UnitOfMeasure::KPH: + return UnitOfMeasure::KPH; + default: + ESP_LOGE(TAG, "Invalid UnitOfMeasure:%u", value); + return UnitOfMeasure::KPH; + } +} + +NegotiationMode LD2415HComponent::i_to_NegotiationMode_(uint8_t value) { + NegotiationMode u = NegotiationMode(value); + + switch (u) + { + case NegotiationMode::CUSTOM_AGREEMENT: + return NegotiationMode::CUSTOM_AGREEMENT; + case NegotiationMode::STANDARD_PROTOCOL: + return NegotiationMode::STANDARD_PROTOCOL; + default: + ESP_LOGE(TAG, "Invalid UnitOfMeasure:%u", value); + return NegotiationMode::CUSTOM_AGREEMENT; + } +} + +const char* LD2415HComponent::TrackingMode_to_s_(TrackingMode value) { + switch (value) + { + case TrackingMode::APPROACHING_AND_RETREATING: + return "APPROACHING_AND_RETREATING"; + case TrackingMode::APPROACHING: + return "APPROACHING"; + case TrackingMode::RETREATING: + default: + return "RETREATING"; + } +} + +const char* LD2415HComponent::UnitOfMeasure_to_s_(UnitOfMeasure value) { + switch (value) + { + case UnitOfMeasure::MPS: + return "MPS"; + case UnitOfMeasure::MPH: + return "MPH"; + case UnitOfMeasure::KPH: + default: + return "KPH"; + } +} + +const char* LD2415HComponent::NegotiationMode_to_s_(NegotiationMode value) { + switch (value) + { + case NegotiationMode::CUSTOM_AGREEMENT: + return "CUSTOM_AGREEMENT"; + case NegotiationMode::STANDARD_PROTOCOL: + default: + return "STANDARD_PROTOCOL"; + } +} + +const char* i_to_s_(std::map map, uint8_t i) { + for (const auto& pair : map) { + if (pair.second == i) { + return pair.first; + } + } + return "Unknown"; +} + +} // namespace ld2415h +} // namespace esphome \ No newline at end of file diff --git a/esphome/components/ld2415h/ld2415h.h b/esphome/components/ld2415h/ld2415h.h new file mode 100644 index 0000000000..ff6c3043e8 --- /dev/null +++ b/esphome/components/ld2415h/ld2415h.h @@ -0,0 +1,163 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/uart/uart.h" +#include "esphome/components/sensor/sensor.h" +#ifdef USE_NUMBER +#include "esphome/components/number/number.h" +#endif +#ifdef USE_SELECT +#include "esphome/components/select/select.h" +#endif +#include + +namespace esphome { +namespace ld2415h { + +enum NegotiationMode : uint8_t{ + CUSTOM_AGREEMENT = 0x01, + STANDARD_PROTOCOL = 0x02 +}; + +enum SampleRateStructure : uint8_t { + SAMPLE_RATE_22FPS = 0x00, + SAMPLE_RATE_11FPS = 0x01, + SAMPLE_RATE_6FPS = 0x02 +}; + +static const std::map SAMPLE_RATE_STR_TO_INT{ + {"~22 fps", SAMPLE_RATE_22FPS}, + {"~11 fps", SAMPLE_RATE_11FPS}, + {"~6 fps", SAMPLE_RATE_6FPS} +}; + +enum TrackingMode : uint8_t { + APPROACHING_AND_RETREATING = 0x00, + APPROACHING = 0x01, + RETREATING = 0x02 +}; + +static const std::map TRACKING_MODE_STR_TO_INT{ + {"Approaching and Restreating", APPROACHING_AND_RETREATING}, + {"Approaching", APPROACHING}, + {"Restreating", RETREATING} +}; + +enum UnitOfMeasure : uint8_t { + KPH = 0x00, + MPH = 0x01, + MPS = 0x02 +}; + +class LD2415HListener { + public: + virtual void on_speed(uint8_t speed){}; +}; + +class LD2415HComponent : public Component, public uart::UARTDevice { + public: + // Constructor declaration + LD2415HComponent(); + void setup() override; + void dump_config() override; + void loop() override; + +#ifdef USE_NUMBER + void set_min_speed_threshold_number(number::Number *number) { this->min_speed_threshold_number_ = number; }; + void set_compensation_angle_number(number::Number *number) { this->compensation_angle_number_ = number; }; + void set_sensitivity_number(number::Number *number) { this->sensitivity_number_ = number; }; + void set_vibration_correction_number(number::Number *number) { this->vibration_correction_number_ = number; }; + void set_relay_trigger_duration_number(number::Number *number) { this->relay_trigger_duration_number_ = number; }; + void set_relay_trigger_speed_number(number::Number *number) { this->relay_trigger_speed_number_ = number; }; +#endif +#ifdef USE_SELECT + void set_sample_rate_select(select::Select *selector) { this->sample_rate_selector_ = selector; }; + void set_tracking_mode_select(select::Select *selector) { this->tracking_mode_selector_ = selector; }; +#endif + float get_setup_priority() const override { return setup_priority::HARDWARE; } + //void set_speed_sensor(sensor::Sensor *speed_sensor) { this->speed_sensor_ = speed_sensor; } + void register_listener(LD2415HListener *listener) { this->listeners_.push_back(listener); } + + void set_min_speed_threshold(uint8_t speed); + void set_compensation_angle(uint8_t angle); + void set_sensitivity(uint8_t sensitivity); + void set_tracking_mode(const std::string &state); + void set_tracking_mode(TrackingMode mode); + void set_tracking_mode(uint8_t mode); + void set_sample_rate(const std::string &state); + void set_sample_rate(uint8_t rate); + void set_vibration_correction(uint8_t correction); + void set_relay_trigger_duration(uint8_t duration); + void set_relay_trigger_speed(uint8_t speed); + +#ifdef USE_NUMBER + number::Number *min_speed_threshold_number_{nullptr}; + number::Number *compensation_angle_number_{nullptr}; + number::Number *sensitivity_number_{nullptr}; + number::Number *vibration_correction_number_{nullptr}; + number::Number *relay_trigger_duration_number_{nullptr}; + number::Number *relay_trigger_speed_number_{nullptr}; +#endif +#ifdef USE_SELECT + select::Select *sample_rate_selector_{nullptr}; + select::Select *tracking_mode_selector_{nullptr}; +#endif + + protected: + sensor::Sensor *speed_sensor_{nullptr}; + + // Configuration + uint8_t min_speed_threshold_ = 0; + uint8_t compensation_angle_ = 0; + uint8_t sensitivity_ = 0; + TrackingMode tracking_mode_ = TrackingMode::APPROACHING_AND_RETREATING; + uint8_t sample_rate_ = 0; + UnitOfMeasure unit_of_measure_ = UnitOfMeasure::KPH; + uint8_t vibration_correction_ = 0; + uint8_t relay_trigger_duration_ = 0; + uint8_t relay_trigger_speed_ = 0; + NegotiationMode negotiation_mode_ = NegotiationMode::CUSTOM_AGREEMENT; + + // State + uint8_t cmd_speed_angle_sense_[8]; + uint8_t cmd_mode_rate_uom_[8]; + uint8_t cmd_anti_vib_comp_[8]; + uint8_t cmd_relay_duration_speed_[8]; + uint8_t cmd_config_[13]; + + bool update_speed_angle_sense_ = false; + bool update_mode_rate_uom_ = false; + bool update_anti_vib_comp_ = false; + bool update_relay_duration_speed_ = false; + bool update_config_ = false; + + char firmware_[20] = ""; + float speed_ = 0; + bool approaching_ = 1; + char response_buffer_[64]; + uint8_t response_buffer_index_ = 0; + + // Processing + void issue_command_(const uint8_t cmd[], const uint8_t size); + bool fill_buffer_(char c); + void clear_remaining_buffer_(uint8_t pos); + void parse_buffer_(); + void parse_config_(); + void parse_firmware_(); + void parse_speed_(); + void parse_config_param_(char* key, char* value); + + // Helpers + TrackingMode i_to_TrackingMode_(uint8_t value); + UnitOfMeasure i_to_UnitOfMeasure_(uint8_t value); + NegotiationMode i_to_NegotiationMode_(uint8_t value); + const char* TrackingMode_to_s_(TrackingMode value); + const char* UnitOfMeasure_to_s_(UnitOfMeasure value); + const char* NegotiationMode_to_s_(NegotiationMode value); + const char* i_to_s_(std::map map, uint8_t i); + + std::vector listeners_{}; + }; + +} // namespace ld2415h +} // namespace esphome \ No newline at end of file diff --git a/esphome/components/ld2415h/number/__init__.py b/esphome/components/ld2415h/number/__init__.py new file mode 100644 index 0000000000..39d16e7518 --- /dev/null +++ b/esphome/components/ld2415h/number/__init__.py @@ -0,0 +1,129 @@ +import esphome.codegen as cg +from esphome.components import number +import esphome.config_validation as cv +from esphome.const import ( + ENTITY_CATEGORY_CONFIG, + UNIT_DEGREES, + UNIT_EMPTY, + UNIT_KILOMETER_PER_HOUR, + UNIT_SECOND, +) +from .. import CONF_LD2415H_ID, LD2415HComponent, ld2415h_ns + +ICON_COMPENSATION_ANGLE = "mdi:angle-acute" +ICON_SENSITIVITY = "mdi:ear-hearing" +ICON_SPEEDOMETER = "mdi:speedometer" +ICON_TIMELAPSE = "mdi:timelapse" +ICON_VIBRATE = "mdi:vibrate" + +CONF_MIN_SPEED_THRESHOLD = "min_speed_threshold" +CONF_COMPENSATION_ANGLE = "compensation_angle" +CONF_SENSITIVITY = "sensitivity" +CONF_VIBRATION_CORRECTION = "vibration_correction" +CONF_RELAY_TRIGGER_DURATION = "relay_trigger_duration" +CONF_RELAY_TRIGGER_SPEED = "relay_trigger_speed" + +MinSpeedThresholdNumber = ld2415h_ns.class_("MinSpeedThresholdNumber" , number.Number) +CompensationAngleNumber = ld2415h_ns.class_("CompensationAngleNumber" , number.Number) +SensitivityNumber = ld2415h_ns.class_("SensitivityNumber" , number.Number) +VibrationCorrectionNumber = ld2415h_ns.class_("VibrationCorrectionNumber" , number.Number) +RelayTriggerDurationNumber = ld2415h_ns.class_("RelayTriggerDurationNumber", number.Number) +RelayTriggerSpeedNumber = ld2415h_ns.class_("RelayTriggerSpeedNumber" , number.Number) + +CONFIG_SCHEMA = { + cv.GenerateID(CONF_LD2415H_ID): cv.use_id(LD2415HComponent), + cv.Optional(CONF_MIN_SPEED_THRESHOLD): number.number_schema( + MinSpeedThresholdNumber, + unit_of_measurement=UNIT_KILOMETER_PER_HOUR, + entity_category=ENTITY_CATEGORY_CONFIG, + icon=ICON_SPEEDOMETER, + ), + cv.Optional(CONF_COMPENSATION_ANGLE): number.number_schema( + CompensationAngleNumber, + unit_of_measurement=UNIT_DEGREES, + entity_category=ENTITY_CATEGORY_CONFIG, + icon=ICON_COMPENSATION_ANGLE, + ), + cv.Optional(CONF_SENSITIVITY): number.number_schema( + SensitivityNumber, + unit_of_measurement=UNIT_EMPTY, + entity_category=ENTITY_CATEGORY_CONFIG, + icon=ICON_SENSITIVITY, + ), + cv.Optional(CONF_VIBRATION_CORRECTION): number.number_schema( + VibrationCorrectionNumber, + unit_of_measurement=UNIT_EMPTY, + entity_category=ENTITY_CATEGORY_CONFIG, + icon=ICON_VIBRATE, + ), + cv.Optional(CONF_RELAY_TRIGGER_DURATION): number.number_schema( + RelayTriggerDurationNumber, + unit_of_measurement=UNIT_SECOND, + entity_category=ENTITY_CATEGORY_CONFIG, + icon=ICON_TIMELAPSE, + ), + cv.Optional(CONF_RELAY_TRIGGER_SPEED): number.number_schema( + RelayTriggerSpeedNumber, + unit_of_measurement=UNIT_KILOMETER_PER_HOUR, + entity_category=ENTITY_CATEGORY_CONFIG, + icon=ICON_SPEEDOMETER, + ), +} + + +async def to_code(config): + ld2415h_component = await cg.get_variable(config[CONF_LD2415H_ID]) + if min_speed_threshold_config := config.get(CONF_MIN_SPEED_THRESHOLD): + num = await number.new_number( + min_speed_threshold_config, + min_value=1, + max_value=60, + step=1, + ) + await cg.register_parented(num, config[CONF_LD2415H_ID]) + cg.add(ld2415h_component.set_min_speed_threshold_number(num)) + if compensation_angle_config := config.get(CONF_COMPENSATION_ANGLE): + num = await number.new_number( + compensation_angle_config, + min_value=0, + max_value=90, + step=1, + ) + await cg.register_parented(num, config[CONF_LD2415H_ID]) + cg.add(ld2415h_component.set_compensation_angle_number(num)) + if sensitivity_config := config.get(CONF_SENSITIVITY): + num = await number.new_number( + sensitivity_config, + min_value=0, + max_value=15, + step=1, + ) + await cg.register_parented(num, config[CONF_LD2415H_ID]) + cg.add(ld2415h_component.set_sensitivity_number(num)) + if vibration_correction_config := config.get(CONF_VIBRATION_CORRECTION): + num = await number.new_number( + vibration_correction_config, + min_value=0, + max_value=112, + step=1, + ) + await cg.register_parented(num, config[CONF_LD2415H_ID]) + cg.add(ld2415h_component.set_vibration_correction_number(num)) + if relay_trigger_duration_config := config.get(CONF_RELAY_TRIGGER_DURATION): + num = await number.new_number( + relay_trigger_duration_config, + min_value=0, + max_value=255, + step=1, + ) + await cg.register_parented(num, config[CONF_LD2415H_ID]) + cg.add(ld2415h_component.set_relay_trigger_duration_number(num)) + if relay_trigger_speed_config := config.get(CONF_RELAY_TRIGGER_SPEED): + num = await number.new_number( + relay_trigger_speed_config, + min_value=0, + max_value=255, + step=1, + ) + await cg.register_parented(num, config[CONF_LD2415H_ID]) + cg.add(ld2415h_component.set_relay_trigger_speed_number(num)) \ No newline at end of file diff --git a/esphome/components/ld2415h/number/compensation_angle_number.cpp b/esphome/components/ld2415h/number/compensation_angle_number.cpp new file mode 100644 index 0000000000..0bb772a7bd --- /dev/null +++ b/esphome/components/ld2415h/number/compensation_angle_number.cpp @@ -0,0 +1,16 @@ +#include "compensation_angle_number.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace ld2415h { + +static const char *const TAG = "LD2415H.compensation_angle_number"; + +void CompensationAngleNumber::control(float angle) { + this->publish_state(angle); + this->parent_->set_compensation_angle(angle); +} + +} // namespace ld2415h +} // namespace esphome diff --git a/esphome/components/ld2415h/number/compensation_angle_number.h b/esphome/components/ld2415h/number/compensation_angle_number.h new file mode 100644 index 0000000000..8fc5d20bed --- /dev/null +++ b/esphome/components/ld2415h/number/compensation_angle_number.h @@ -0,0 +1,18 @@ +#pragma once + +#include "esphome/components/number/number.h" +#include "../ld2415h.h" + +namespace esphome { +namespace ld2415h { + +class CompensationAngleNumber : public number::Number, public Parented { + public: + CompensationAngleNumber() = default; + + protected: + void control(float angle) override; +}; + +} // namespace ld2415h +} // namespace esphome diff --git a/esphome/components/ld2415h/number/min_speed_threshold_number.cpp b/esphome/components/ld2415h/number/min_speed_threshold_number.cpp new file mode 100644 index 0000000000..d043876324 --- /dev/null +++ b/esphome/components/ld2415h/number/min_speed_threshold_number.cpp @@ -0,0 +1,16 @@ +#include "min_speed_threshold_number.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace ld2415h { + +static const char *const TAG = "LD2415H.min_speed_threshold_number"; + +void MinSpeedThresholdNumber::control(float speed) { + this->publish_state(speed); + this->parent_->set_min_speed_threshold(speed); +} + +} // namespace ld2415h +} // namespace esphome diff --git a/esphome/components/ld2415h/number/min_speed_threshold_number.h b/esphome/components/ld2415h/number/min_speed_threshold_number.h new file mode 100644 index 0000000000..e026b9760b --- /dev/null +++ b/esphome/components/ld2415h/number/min_speed_threshold_number.h @@ -0,0 +1,18 @@ +#pragma once + +#include "esphome/components/number/number.h" +#include "../ld2415h.h" + +namespace esphome { +namespace ld2415h { + +class MinSpeedThresholdNumber : public number::Number, public Parented { + public: + MinSpeedThresholdNumber() = default; + + protected: + void control(float speed) override; +}; + +} // namespace ld2415h +} // namespace esphome diff --git a/esphome/components/ld2415h/number/relay_trigger_duration_number.cpp b/esphome/components/ld2415h/number/relay_trigger_duration_number.cpp new file mode 100644 index 0000000000..bf806772b6 --- /dev/null +++ b/esphome/components/ld2415h/number/relay_trigger_duration_number.cpp @@ -0,0 +1,16 @@ +#include "relay_trigger_duration_number.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace ld2415h { + +static const char *const TAG = "LD2415H.relay_trigger_duration_number"; + +void RelayTriggerDurationNumber::control(float duration) { + this->publish_state(duration); + this->parent_->set_relay_trigger_duration(duration); +} + +} // namespace ld2415h +} // namespace esphome diff --git a/esphome/components/ld2415h/number/relay_trigger_duration_number.h b/esphome/components/ld2415h/number/relay_trigger_duration_number.h new file mode 100644 index 0000000000..21626be714 --- /dev/null +++ b/esphome/components/ld2415h/number/relay_trigger_duration_number.h @@ -0,0 +1,18 @@ +#pragma once + +#include "esphome/components/number/number.h" +#include "../ld2415h.h" + +namespace esphome { +namespace ld2415h { + +class RelayTriggerDurationNumber : public number::Number, public Parented { + public: + RelayTriggerDurationNumber() = default; + + protected: + void control(float duration) override; +}; + +} // namespace ld2415h +} // namespace esphome diff --git a/esphome/components/ld2415h/number/relay_trigger_speed_number.cpp b/esphome/components/ld2415h/number/relay_trigger_speed_number.cpp new file mode 100644 index 0000000000..9b90efe912 --- /dev/null +++ b/esphome/components/ld2415h/number/relay_trigger_speed_number.cpp @@ -0,0 +1,16 @@ +#include "relay_trigger_speed_number.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace ld2415h { + +static const char *const TAG = "LD2415H.relay_trigger_speed_number"; + +void RelayTriggerSpeedNumber::control(float speed) { + this->publish_state(speed); + this->parent_->set_relay_trigger_speed(speed); +} + +} // namespace ld2415h +} // namespace esphome diff --git a/esphome/components/ld2415h/number/relay_trigger_speed_number.h b/esphome/components/ld2415h/number/relay_trigger_speed_number.h new file mode 100644 index 0000000000..81303cba6a --- /dev/null +++ b/esphome/components/ld2415h/number/relay_trigger_speed_number.h @@ -0,0 +1,18 @@ +#pragma once + +#include "esphome/components/number/number.h" +#include "../ld2415h.h" + +namespace esphome { +namespace ld2415h { + +class RelayTriggerSpeedNumber : public number::Number, public Parented { + public: + RelayTriggerSpeedNumber() = default; + + protected: + void control(float speed) override; +}; + +} // namespace ld2415h +} // namespace esphome diff --git a/esphome/components/ld2415h/number/sensitivity_number.cpp b/esphome/components/ld2415h/number/sensitivity_number.cpp new file mode 100644 index 0000000000..f06956f487 --- /dev/null +++ b/esphome/components/ld2415h/number/sensitivity_number.cpp @@ -0,0 +1,16 @@ +#include "sensitivity_number.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace ld2415h { + +static const char *const TAG = "LD2415H.sensitivity_number"; + +void SensitivityNumber::control(float sensitivity) { + this->publish_state(sensitivity); + this->parent_->set_sensitivity(sensitivity); +} + +} // namespace ld2415h +} // namespace esphome diff --git a/esphome/components/ld2415h/number/sensitivity_number.h b/esphome/components/ld2415h/number/sensitivity_number.h new file mode 100644 index 0000000000..10f0e2887c --- /dev/null +++ b/esphome/components/ld2415h/number/sensitivity_number.h @@ -0,0 +1,18 @@ +#pragma once + +#include "esphome/components/number/number.h" +#include "../ld2415h.h" + +namespace esphome { +namespace ld2415h { + +class SensitivityNumber : public number::Number, public Parented { + public: + SensitivityNumber() = default; + + protected: + void control(float sensitivity) override; +}; + +} // namespace ld2415h +} // namespace esphome diff --git a/esphome/components/ld2415h/number/vibration_correction_number.cpp b/esphome/components/ld2415h/number/vibration_correction_number.cpp new file mode 100644 index 0000000000..c1e98c3772 --- /dev/null +++ b/esphome/components/ld2415h/number/vibration_correction_number.cpp @@ -0,0 +1,16 @@ +#include "vibration_correction_number.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace ld2415h { + +static const char *const TAG = "LD2415H.vibration_correction_number"; + +void VibrationCorrectionNumber::control(float correction) { + this->publish_state(correction); + this->parent_->set_vibration_correction(correction); +} + +} // namespace ld2415h +} // namespace esphome diff --git a/esphome/components/ld2415h/number/vibration_correction_number.h b/esphome/components/ld2415h/number/vibration_correction_number.h new file mode 100644 index 0000000000..137ce6cdff --- /dev/null +++ b/esphome/components/ld2415h/number/vibration_correction_number.h @@ -0,0 +1,18 @@ +#pragma once + +#include "esphome/components/number/number.h" +#include "../ld2415h.h" + +namespace esphome { +namespace ld2415h { + +class VibrationCorrectionNumber : public number::Number, public Parented { + public: + VibrationCorrectionNumber() = default; + + protected: + void control(float correction) override; +}; + +} // namespace ld2415h +} // namespace esphome diff --git a/esphome/components/ld2415h/select/__init__.py b/esphome/components/ld2415h/select/__init__.py new file mode 100644 index 0000000000..5d3d036123 --- /dev/null +++ b/esphome/components/ld2415h/select/__init__.py @@ -0,0 +1,56 @@ +import esphome.codegen as cg +from esphome.components import select +import esphome.config_validation as cv +from esphome.const import ENTITY_CATEGORY_CONFIG +from .. import CONF_LD2415H_ID, LD2415HComponent, ld2415h_ns + +CONF_SAMPLE_RATE = "sample_rate" +CONF_SAMPLE_RATE_SELECTS = [ + "~22 fps", + "~11 fps", + "~6 fps", +] +ICON_CLOCK_FAST = "mdi:clock-fast" + +CONF_TRACKING_MODE = "tracking_mode" +CONF_TRACKING_MODE_SELECTS = [ + "Approaching and Restreating", + "Approaching", + "Restreating", +] +ICON_RADAR = "mdi:radar" + +SampleRateSelect = ld2415h_ns.class_("SampleRateSelect", select.Select) +TrackingModeSelect = ld2415h_ns.class_("TrackingModeSelect", select.Select) + +CONFIG_SCHEMA = { + cv.GenerateID(CONF_LD2415H_ID): cv.use_id(LD2415HComponent), + cv.Optional(CONF_SAMPLE_RATE, default=1): select.select_schema( + SampleRateSelect, + entity_category=ENTITY_CATEGORY_CONFIG, + icon=ICON_CLOCK_FAST, + ), + cv.Optional(CONF_TRACKING_MODE, default=0): select.select_schema( + TrackingModeSelect, + entity_category=ENTITY_CATEGORY_CONFIG, + icon=ICON_RADAR, + ), +} + + +async def to_code(config): + ld2415h_component = await cg.get_variable(config[CONF_LD2415H_ID]) + if sample_rate_config := config.get(CONF_SAMPLE_RATE): + sel = await select.new_select( + sample_rate_config, + options=[CONF_SAMPLE_RATE_SELECTS], + ) + await cg.register_parented(sel, config[CONF_LD2415H_ID]) + cg.add(ld2415h_component.set_sample_rate_select(sel)) + if tracking_mode_config := config.get(CONF_TRACKING_MODE): + sel = await select.new_select( + tracking_mode_config, + options=[CONF_TRACKING_MODE_SELECTS], + ) + await cg.register_parented(sel, config[CONF_LD2415H_ID]) + cg.add(ld2415h_component.set_tracking_mode_select(sel)) diff --git a/esphome/components/ld2415h/select/sample_rate_select.cpp b/esphome/components/ld2415h/select/sample_rate_select.cpp new file mode 100644 index 0000000000..794b294cab --- /dev/null +++ b/esphome/components/ld2415h/select/sample_rate_select.cpp @@ -0,0 +1,16 @@ +#include "sample_rate_select.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace ld2415h { + +static const char *const TAG = "LD2415H.sample_rate_select"; + +void SampleRateSelect::control(const std::string &value) { + this->publish_state(value); + this->parent_->set_sample_rate(state); +} + +} // namespace ld2415h +} // namespace esphome diff --git a/esphome/components/ld2415h/select/sample_rate_select.h b/esphome/components/ld2415h/select/sample_rate_select.h new file mode 100644 index 0000000000..a93368be9a --- /dev/null +++ b/esphome/components/ld2415h/select/sample_rate_select.h @@ -0,0 +1,18 @@ +#pragma once + +#include "../ld2415h.h" +#include "esphome/components/select/select.h" + +namespace esphome { +namespace ld2415h { + +class SampleRateSelect : public Component, public select::Select, public Parented { + public: + SampleRateSelect() = default; + + protected: + void control(const std::string &value) override; +}; + +} // namespace ld2415h +} // namespace esphome diff --git a/esphome/components/ld2415h/select/tracking_mode_select.cpp b/esphome/components/ld2415h/select/tracking_mode_select.cpp new file mode 100644 index 0000000000..becc565a44 --- /dev/null +++ b/esphome/components/ld2415h/select/tracking_mode_select.cpp @@ -0,0 +1,16 @@ +#include "tracking_mode_select.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace ld2415h { + +static const char *const TAG = "LD2415H.tracking_mode_select"; + +void TrackingModeSelect::control(const std::string &value) { + this->publish_state(value); + this->parent_->set_tracking_mode(state); +} + +} // namespace ld2415h +} // namespace esphome diff --git a/esphome/components/ld2415h/select/tracking_mode_select.h b/esphome/components/ld2415h/select/tracking_mode_select.h new file mode 100644 index 0000000000..a2cfa5ab82 --- /dev/null +++ b/esphome/components/ld2415h/select/tracking_mode_select.h @@ -0,0 +1,18 @@ +#pragma once + +#include "../ld2415h.h" +#include "esphome/components/select/select.h" + +namespace esphome { +namespace ld2415h { + +class TrackingModeSelect : public Component, public select::Select, public Parented { + public: + TrackingModeSelect() = default; + + protected: + void control(const std::string &value) override; +}; + +} // namespace ld2415h +} // namespace esphome diff --git a/esphome/components/ld2415h/sensor/__init__.py b/esphome/components/ld2415h/sensor/__init__.py new file mode 100644 index 0000000000..9c23be2578 --- /dev/null +++ b/esphome/components/ld2415h/sensor/__init__.py @@ -0,0 +1,41 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor +from esphome.const import ( + CONF_ID, + CONF_SPEED, + DEVICE_CLASS_SPEED, + STATE_CLASS_MEASUREMENT, + UNIT_KILOMETER_PER_HOUR, +) +from .. import ld2415h_ns, LD2415HComponent, CONF_LD2415H_ID + +LD2415HSensor = ld2415h_ns.class_("LD2415HSensor", sensor.Sensor, cg.Component) + +ICON_SPEEDOMETER = "mdi:speedometer" + +CONFIG_SCHEMA = cv.All( + cv.COMPONENT_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(LD2415HSensor), + cv.GenerateID(CONF_LD2415H_ID): cv.use_id(LD2415HComponent), + cv.Optional(CONF_SPEED): sensor.sensor_schema( + device_class=DEVICE_CLASS_SPEED, + state_class=STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_KILOMETER_PER_HOUR, + icon=ICON_SPEEDOMETER, + accuracy_decimals=1, + ), + } + ), +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + if speed := config.get(CONF_SPEED): + sens = await sensor.new_sensor(speed) + cg.add(var.set_speed_sensor(sens)) + ld2415h = await cg.get_variable(config[CONF_LD2415H_ID]) + cg.add(ld2415h.register_listener(var)) diff --git a/esphome/components/ld2415h/sensor/ld2415h_sensor.cpp b/esphome/components/ld2415h/sensor/ld2415h_sensor.cpp new file mode 100644 index 0000000000..596e779357 --- /dev/null +++ b/esphome/components/ld2415h/sensor/ld2415h_sensor.cpp @@ -0,0 +1,16 @@ +#include "ld2415h_sensor.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace ld2415h { + +static const char *const TAG = "LD2415H.sensor"; + +void LD2415HSensor::dump_config() { + ESP_LOGCONFIG(TAG, "LD2415H Sensor:"); + LOG_SENSOR(" ", "Speed", this->speed_sensor_); +} + +} // namespace ld2415H +} // namespace esphome diff --git a/esphome/components/ld2415h/sensor/ld2415h_sensor.h b/esphome/components/ld2415h/sensor/ld2415h_sensor.h new file mode 100644 index 0000000000..454ae85f48 --- /dev/null +++ b/esphome/components/ld2415h/sensor/ld2415h_sensor.h @@ -0,0 +1,26 @@ +#pragma once + +#include "../ld2415h.h" +#include "esphome/components/sensor/sensor.h" + +namespace esphome { +namespace ld2415h { + +class LD2415HSensor : public LD2415HListener, public Component, sensor::Sensor { + public: + void dump_config() override; + void set_speed_sensor(sensor::Sensor *sensor) { this->speed_sensor_ = sensor; } + void on_speed(uint8_t speed) override { + if (this->speed_sensor_ != nullptr) { + if (this->speed_sensor_->get_state() != speed) { + this->speed_sensor_->publish_state(speed); + } + } + } + + protected: + sensor::Sensor *speed_sensor_{nullptr}; +}; + +} // namespace ld2415h +} // namespace esphome