mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 06:33:51 +00:00 
			
		
		
		
	Add HLK-LD2420 mmWave Radar module component (#4847)
Co-authored-by: descipher <120155735+GelidusResearch@users.noreply.github.com>
This commit is contained in:
		
							
								
								
									
										39
									
								
								esphome/components/ld2420/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								esphome/components/ld2420/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import uart | ||||
| from esphome.const import CONF_ID | ||||
|  | ||||
| CODEOWNERS = ["@descipher"] | ||||
|  | ||||
| DEPENDENCIES = ["uart"] | ||||
|  | ||||
| MULTI_CONF = True | ||||
|  | ||||
| ld2420_ns = cg.esphome_ns.namespace("ld2420") | ||||
| LD2420Component = ld2420_ns.class_("LD2420Component", cg.Component, uart.UARTDevice) | ||||
|  | ||||
| CONF_LD2420_ID = "ld2420_id" | ||||
|  | ||||
| CONFIG_SCHEMA = cv.All( | ||||
|     cv.Schema( | ||||
|         { | ||||
|             cv.GenerateID(): cv.declare_id(LD2420Component), | ||||
|         } | ||||
|     ) | ||||
|     .extend(uart.UART_DEVICE_SCHEMA) | ||||
|     .extend(cv.COMPONENT_SCHEMA) | ||||
| ) | ||||
|  | ||||
| FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema( | ||||
|     "ld2420_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) | ||||
							
								
								
									
										33
									
								
								esphome/components/ld2420/binary_sensor/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								esphome/components/ld2420/binary_sensor/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import binary_sensor | ||||
| from esphome.const import CONF_ID, DEVICE_CLASS_OCCUPANCY | ||||
| from .. import ld2420_ns, LD2420Component, CONF_LD2420_ID | ||||
|  | ||||
| LD2420BinarySensor = ld2420_ns.class_( | ||||
|     "LD2420BinarySensor", binary_sensor.BinarySensor, cg.Component | ||||
| ) | ||||
|  | ||||
| CONF_HAS_TARGET = "has_target" | ||||
|  | ||||
| CONFIG_SCHEMA = cv.All( | ||||
|     cv.COMPONENT_SCHEMA.extend( | ||||
|         { | ||||
|             cv.GenerateID(): cv.declare_id(LD2420BinarySensor), | ||||
|             cv.GenerateID(CONF_LD2420_ID): cv.use_id(LD2420Component), | ||||
|             cv.Optional(CONF_HAS_TARGET): binary_sensor.binary_sensor_schema( | ||||
|                 device_class=DEVICE_CLASS_OCCUPANCY | ||||
|             ), | ||||
|         } | ||||
|     ), | ||||
| ) | ||||
|  | ||||
|  | ||||
| async def to_code(config): | ||||
|     var = cg.new_Pvariable(config[CONF_ID]) | ||||
|     await cg.register_component(var, config) | ||||
|     if CONF_HAS_TARGET in config: | ||||
|         sens = await binary_sensor.new_binary_sensor(config[CONF_HAS_TARGET]) | ||||
|         cg.add(var.set_presence_sensor(sens)) | ||||
|     ld2420 = await cg.get_variable(config[CONF_LD2420_ID]) | ||||
|     cg.add(ld2420.register_listener(var)) | ||||
| @@ -0,0 +1,16 @@ | ||||
| #include "ld2420_binary_sensor.h" | ||||
| #include "esphome/core/helpers.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace ld2420 { | ||||
|  | ||||
| static const char *const TAG = "LD2420.binary_sensor"; | ||||
|  | ||||
| void LD2420BinarySensor::dump_config() { | ||||
|   ESP_LOGCONFIG(TAG, "LD2420 BinarySensor:"); | ||||
|   LOG_BINARY_SENSOR("  ", "Presence", this->presence_bsensor_); | ||||
| } | ||||
|  | ||||
| }  // namespace ld2420 | ||||
| }  // namespace esphome | ||||
| @@ -0,0 +1,25 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "../ld2420.h" | ||||
| #include "esphome/components/binary_sensor/binary_sensor.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace ld2420 { | ||||
|  | ||||
| class LD2420BinarySensor : public LD2420Listener, public Component, binary_sensor::BinarySensor { | ||||
|  public: | ||||
|   void dump_config() override; | ||||
|   void set_presence_sensor(binary_sensor::BinarySensor *bsensor) { this->presence_bsensor_ = bsensor; }; | ||||
|   void on_presence(bool presence) override { | ||||
|     if (this->presence_bsensor_ != nullptr) { | ||||
|       if (this->presence_bsensor_->state != presence) | ||||
|         this->presence_bsensor_->publish_state(presence); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|  protected: | ||||
|   binary_sensor::BinarySensor *presence_bsensor_{nullptr}; | ||||
| }; | ||||
|  | ||||
| }  // namespace ld2420 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										69
									
								
								esphome/components/ld2420/button/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								esphome/components/ld2420/button/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,69 @@ | ||||
| import esphome.codegen as cg | ||||
| from esphome.components import button | ||||
| import esphome.config_validation as cv | ||||
| from esphome.const import ( | ||||
|     DEVICE_CLASS_RESTART, | ||||
|     ENTITY_CATEGORY_DIAGNOSTIC, | ||||
|     ENTITY_CATEGORY_CONFIG, | ||||
|     ICON_RESTART, | ||||
|     ICON_RESTART_ALERT, | ||||
|     ICON_DATABASE, | ||||
| ) | ||||
| from .. import CONF_LD2420_ID, LD2420Component, ld2420_ns | ||||
|  | ||||
| LD2420ApplyConfigButton = ld2420_ns.class_("LD2420ApplyConfigButton", button.Button) | ||||
| LD2420RevertConfigButton = ld2420_ns.class_("LD2420RevertConfigButton", button.Button) | ||||
| LD2420RestartModuleButton = ld2420_ns.class_("LD2420RestartModuleButton", button.Button) | ||||
| LD2420FactoryResetButton = ld2420_ns.class_("LD2420FactoryResetButton", button.Button) | ||||
|  | ||||
| CONF_APPLY_CONFIG = "apply_config" | ||||
| CONF_REVERT_CONFIG = "revert_config" | ||||
| CONF_RESTART_MODULE = "restart_module" | ||||
| CONF_FACTORY_RESET = "factory_reset" | ||||
|  | ||||
|  | ||||
| CONFIG_SCHEMA = { | ||||
|     cv.GenerateID(CONF_LD2420_ID): cv.use_id(LD2420Component), | ||||
|     cv.Required(CONF_APPLY_CONFIG): button.button_schema( | ||||
|         LD2420ApplyConfigButton, | ||||
|         device_class=DEVICE_CLASS_RESTART, | ||||
|         entity_category=ENTITY_CATEGORY_CONFIG, | ||||
|         icon=ICON_RESTART_ALERT, | ||||
|     ), | ||||
|     cv.Optional(CONF_REVERT_CONFIG): button.button_schema( | ||||
|         LD2420RevertConfigButton, | ||||
|         device_class=DEVICE_CLASS_RESTART, | ||||
|         entity_category=ENTITY_CATEGORY_CONFIG, | ||||
|         icon=ICON_RESTART, | ||||
|     ), | ||||
|     cv.Optional(CONF_RESTART_MODULE): button.button_schema( | ||||
|         LD2420RestartModuleButton, | ||||
|         entity_category=ENTITY_CATEGORY_DIAGNOSTIC, | ||||
|         icon=ICON_DATABASE, | ||||
|     ), | ||||
|     cv.Optional(CONF_FACTORY_RESET): button.button_schema( | ||||
|         LD2420FactoryResetButton, | ||||
|         entity_category=ENTITY_CATEGORY_CONFIG, | ||||
|         icon=ICON_DATABASE, | ||||
|     ), | ||||
| } | ||||
|  | ||||
|  | ||||
| async def to_code(config): | ||||
|     ld2420_component = await cg.get_variable(config[CONF_LD2420_ID]) | ||||
|     if apply_config := config.get(CONF_APPLY_CONFIG): | ||||
|         b = await button.new_button(apply_config) | ||||
|         await cg.register_parented(b, config[CONF_LD2420_ID]) | ||||
|         cg.add(ld2420_component.set_apply_config_button(b)) | ||||
|     if revert_config := config.get(CONF_REVERT_CONFIG): | ||||
|         b = await button.new_button(revert_config) | ||||
|         await cg.register_parented(b, config[CONF_LD2420_ID]) | ||||
|         cg.add(ld2420_component.set_revert_config_button(b)) | ||||
|     if restart_config := config.get(CONF_RESTART_MODULE): | ||||
|         b = await button.new_button(restart_config) | ||||
|         await cg.register_parented(b, config[CONF_LD2420_ID]) | ||||
|         cg.add(ld2420_component.set_restart_module_button(b)) | ||||
|     if factory_reset := config.get(CONF_FACTORY_RESET): | ||||
|         b = await button.new_button(factory_reset) | ||||
|         await cg.register_parented(b, config[CONF_LD2420_ID]) | ||||
|         cg.add(ld2420_component.set_factory_reset_button(b)) | ||||
							
								
								
									
										16
									
								
								esphome/components/ld2420/button/reconfig_buttons.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								esphome/components/ld2420/button/reconfig_buttons.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| #include "reconfig_buttons.h" | ||||
| #include "esphome/core/helpers.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| static const char *const TAG = "LD2420.button"; | ||||
|  | ||||
| namespace esphome { | ||||
| namespace ld2420 { | ||||
|  | ||||
| void LD2420ApplyConfigButton::press_action() { this->parent_->apply_config_action(); } | ||||
| void LD2420RevertConfigButton::press_action() { this->parent_->revert_config_action(); } | ||||
| void LD2420RestartModuleButton::press_action() { this->parent_->restart_module_action(); } | ||||
| void LD2420FactoryResetButton::press_action() { this->parent_->factory_reset_action(); } | ||||
|  | ||||
| }  // namespace ld2420 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										42
									
								
								esphome/components/ld2420/button/reconfig_buttons.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								esphome/components/ld2420/button/reconfig_buttons.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,42 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/components/button/button.h" | ||||
| #include "../ld2420.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace ld2420 { | ||||
|  | ||||
| class LD2420ApplyConfigButton : public button::Button, public Parented<LD2420Component> { | ||||
|  public: | ||||
|   LD2420ApplyConfigButton() = default; | ||||
|  | ||||
|  protected: | ||||
|   void press_action() override; | ||||
| }; | ||||
|  | ||||
| class LD2420RevertConfigButton : public button::Button, public Parented<LD2420Component> { | ||||
|  public: | ||||
|   LD2420RevertConfigButton() = default; | ||||
|  | ||||
|  protected: | ||||
|   void press_action() override; | ||||
| }; | ||||
|  | ||||
| class LD2420RestartModuleButton : public button::Button, public Parented<LD2420Component> { | ||||
|  public: | ||||
|   LD2420RestartModuleButton() = default; | ||||
|  | ||||
|  protected: | ||||
|   void press_action() override; | ||||
| }; | ||||
|  | ||||
| class LD2420FactoryResetButton : public button::Button, public Parented<LD2420Component> { | ||||
|  public: | ||||
|   LD2420FactoryResetButton() = default; | ||||
|  | ||||
|  protected: | ||||
|   void press_action() override; | ||||
| }; | ||||
|  | ||||
| }  // namespace ld2420 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										775
									
								
								esphome/components/ld2420/ld2420.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										775
									
								
								esphome/components/ld2420/ld2420.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,775 @@ | ||||
| #include "ld2420.h" | ||||
| #include "esphome/core/helpers.h" | ||||
|  | ||||
| /* | ||||
| Configure commands - little endian | ||||
|  | ||||
| No command can exceed 64 bytes, otherwise they would need be to be split up into multiple sends. | ||||
|  | ||||
| All send command frames will have: | ||||
|   Header = FD FC FB FA, Bytes 0 - 3, uint32_t 0xFAFBFCFD | ||||
|   Length, bytes 4 - 5, uint16_t 0x0002, must be at least 2 for the command byte if no addon data. | ||||
|   Command bytes 6 - 7, uint16_t | ||||
|   Footer = 04 03 02 01 - uint32_t 0x01020304, Always last 4 Bytes. | ||||
| Receive | ||||
|   Error bytes 8-9 uint16_t, 0 = success, all other positive values = error | ||||
|  | ||||
| Enable config mode: | ||||
| Send: | ||||
|   UART Tx: FD FC FB FA 04 00 FF 00 02 00 04 03 02 01 | ||||
|   Command = FF 00 - uint16_t 0x00FF | ||||
|   Protocol version = 02 00, can be 1 or 2 - uint16_t 0x0002 | ||||
| Reply: | ||||
|   UART Rx: FD FC FB FA 06 00 FF 01 00 00 02 00 04 03 02 01 | ||||
|  | ||||
| Disable config mode: | ||||
| Send: | ||||
|   UART Tx: FD FC FB FA 02 00 FE 00 04 03 02 01 | ||||
|   Command = FE 00 - uint16_t 0x00FE | ||||
| Receive: | ||||
|   UART Rx: FD FC FB FA 04 00 FE 01 00 00 04 03 02 01 | ||||
|  | ||||
| Configure system parameters: | ||||
|  | ||||
| UART Tx: FD FC FB FA 08 00 12 00 00 00 64 00 00 00 04 03 02 01  Set system parms | ||||
| Command = 12 00 - uint16_t 0x0012, Param | ||||
| There are three documented parameters for modes: | ||||
|   00 64 = Basic status mode | ||||
|     This mode outputs text as presence "ON" or  "OFF" and "Range XXXX" | ||||
|     where XXXX is a decimal value for distance in cm | ||||
|   00 04 = Energy output mode | ||||
|     This mode outputs detailed signal energy values for each gate and the target distance. | ||||
|     The data format consist of the following. | ||||
|     Header HH, Length LL, Persence PP, Distance DD, Range Gate GG, 16 Gate Energies EE, Footer FF | ||||
|     HH HH HH HH LL LL PP DD DD GG GG EE EE .. 16x   .. FF FF FF FF | ||||
|     F4 F3 F2 F1 00 23 00 00 00 00 01 00 00 .. .. .. .. F8 F7 F6 F5 | ||||
|   00 00 = debug output mode | ||||
|     This mode outputs detailed values consisting of 20 Dopplers, 16 Ranges for a total 20 * 16 * 4 bytes | ||||
|     The data format consist of the following. | ||||
|     Header HH, Doppler DD, Range RR, Footer FF | ||||
|     HH HH HH HH DD DD DD DD .. 20x   .. RR RR RR RR .. 16x   .. FF FF FF FF | ||||
|     AA BF 10 14 00 00 00 00 .. .. .. .. 00 00 00 00 .. .. .. .. FD FC FB FA | ||||
|  | ||||
| Configure gate sensitivity parameters: | ||||
| UART Tx: FD FC FB FA 0E 00 07 00 10 00 60 EA 00 00 20 00 60 EA 00 00 04 03 02 01 | ||||
| Command = 12 00 - uint16_t 0x0007 | ||||
| Gate 0 high thresh = 10 00 uint16_t 0x0010, Threshold value = 60 EA 00 00 uint32_t 0x0000EA60 | ||||
| Gate 0 low thresh = 20 00 uint16_t 0x0020, Threshold value = 60 EA 00 00 uint32_t 0x0000EA60 | ||||
| */ | ||||
|  | ||||
| namespace esphome { | ||||
| namespace ld2420 { | ||||
|  | ||||
| static const char *const TAG = "ld2420"; | ||||
|  | ||||
| float LD2420Component::get_setup_priority() const { return setup_priority::BUS; } | ||||
|  | ||||
| void LD2420Component::dump_config() { | ||||
|   ESP_LOGCONFIG(TAG, "LD2420:"); | ||||
|   ESP_LOGCONFIG(TAG, "  Firmware Version : %7s", this->ld2420_firmware_ver_); | ||||
|   ESP_LOGCONFIG(TAG, "LD2420 Number:"); | ||||
|   LOG_NUMBER(TAG, "  Gate Timeout:", this->gate_timeout_number_); | ||||
|   LOG_NUMBER(TAG, "  Gate Max Distance:", this->max_gate_distance_number_); | ||||
|   LOG_NUMBER(TAG, "  Gate Min Distance:", this->min_gate_distance_number_); | ||||
|   LOG_NUMBER(TAG, "  Gate Select:", this->gate_select_number_); | ||||
|   for (uint8_t gate = 0; gate < LD2420_TOTAL_GATES; gate++) { | ||||
|     LOG_NUMBER(TAG, "  Gate Move Threshold:", this->gate_move_threshold_numbers_[gate]); | ||||
|     LOG_NUMBER(TAG, "  Gate Still Threshold::", this->gate_still_threshold_numbers_[gate]); | ||||
|   } | ||||
|   LOG_BUTTON(TAG, "  Apply Config:", this->apply_config_button_); | ||||
|   LOG_BUTTON(TAG, "  Revert Edits:", this->revert_config_button_); | ||||
|   LOG_BUTTON(TAG, "  Factory Reset:", this->factory_reset_button_); | ||||
|   LOG_BUTTON(TAG, "  Restart Module:", this->restart_module_button_); | ||||
|   ESP_LOGCONFIG(TAG, "LD2420 Select:"); | ||||
|   LOG_SELECT(TAG, "  Operating Mode", this->operating_selector_); | ||||
|   if (this->get_firmware_int_(ld2420_firmware_ver_) < CALIBRATE_VERSION_MIN) { | ||||
|     ESP_LOGW(TAG, "LD2420 Firmware Version %s and older are only supported in Simple Mode", ld2420_firmware_ver_); | ||||
|   } | ||||
| } | ||||
|  | ||||
| uint8_t LD2420Component::calc_checksum(void *data, size_t size) { | ||||
|   uint8_t checksum = 0; | ||||
|   uint8_t *data_bytes = (uint8_t *) data; | ||||
|   for (size_t i = 0; i < size; i++) { | ||||
|     checksum ^= data_bytes[i];  // XOR operation | ||||
|   } | ||||
|   return checksum; | ||||
| } | ||||
|  | ||||
| int LD2420Component::get_firmware_int_(const char *version_string) { | ||||
|   std::string version_str = version_string; | ||||
|   if (version_str[0] == 'v') { | ||||
|     version_str = version_str.substr(1); | ||||
|   } | ||||
|   version_str.erase(remove(version_str.begin(), version_str.end(), '.'), version_str.end()); | ||||
|   int version_integer = stoi(version_str); | ||||
|   return version_integer; | ||||
| } | ||||
|  | ||||
| void LD2420Component::setup() { | ||||
|   ESP_LOGCONFIG(TAG, "Setting up LD2420..."); | ||||
|   if (this->set_config_mode(true) == LD2420_ERROR_TIMEOUT) { | ||||
|     ESP_LOGE(TAG, "LD2420 module has failed to respond, check baud rate and serial connections."); | ||||
|     this->mark_failed(); | ||||
|     return; | ||||
|   } | ||||
|   this->get_min_max_distances_timeout_(); | ||||
| #ifdef USE_NUMBER | ||||
|   this->init_gate_config_numbers(); | ||||
| #endif | ||||
|   this->get_firmware_version_(); | ||||
|   const char *pfw = this->ld2420_firmware_ver_; | ||||
|   std::string fw_str(pfw); | ||||
|  | ||||
|   for (auto &listener : listeners_) { | ||||
|     listener->on_fw_version(fw_str); | ||||
|   } | ||||
|  | ||||
|   for (uint8_t gate = 0; gate < LD2420_TOTAL_GATES; gate++) { | ||||
|     delay_microseconds_safe(125); | ||||
|     this->get_gate_threshold_(gate); | ||||
|   } | ||||
|  | ||||
|   memcpy(&this->new_config, &this->current_config, sizeof(this->current_config)); | ||||
|   if (get_firmware_int_(ld2420_firmware_ver_) < CALIBRATE_VERSION_MIN) { | ||||
|     this->set_operating_mode(OP_SIMPLE_MODE_STRING); | ||||
|     this->operating_selector_->publish_state(OP_SIMPLE_MODE_STRING); | ||||
|     this->set_mode_(CMD_SYSTEM_MODE_SIMPLE); | ||||
|     ESP_LOGW(TAG, "LD2420 Frimware Version %s and older are only supported in Simple Mode", ld2420_firmware_ver_); | ||||
|   } else { | ||||
|     this->set_mode_(CMD_SYSTEM_MODE_ENERGY); | ||||
|     this->operating_selector_->publish_state(OP_NORMAL_MODE_STRING); | ||||
|   } | ||||
| #ifdef USE_NUMBER | ||||
|   this->init_gate_config_numbers(); | ||||
| #endif | ||||
|   this->set_system_mode(this->system_mode_); | ||||
|   this->set_config_mode(false); | ||||
|   ESP_LOGCONFIG(TAG, "LD2420 setup complete."); | ||||
| } | ||||
|  | ||||
| void LD2420Component::apply_config_action() { | ||||
|   const uint8_t checksum = calc_checksum(&this->new_config, sizeof(this->new_config)); | ||||
|   if (checksum == calc_checksum(&this->current_config, sizeof(this->current_config))) { | ||||
|     ESP_LOGCONFIG(TAG, "No configuration change detected"); | ||||
|     return; | ||||
|   } | ||||
|   ESP_LOGCONFIG(TAG, "Reconfiguring LD2420..."); | ||||
|   if (this->set_config_mode(true) == LD2420_ERROR_TIMEOUT) { | ||||
|     ESP_LOGE(TAG, "LD2420 module has failed to respond, check baud rate and serial connections."); | ||||
|     this->mark_failed(); | ||||
|     return; | ||||
|   } | ||||
|   this->set_min_max_distances_timeout(this->new_config.max_gate, this->new_config.min_gate, this->new_config.timeout); | ||||
|   for (uint8_t gate = 0; gate < LD2420_TOTAL_GATES; gate++) { | ||||
|     delay_microseconds_safe(125); | ||||
|     this->set_gate_threshold(gate); | ||||
|   } | ||||
|   memcpy(¤t_config, &new_config, sizeof(new_config)); | ||||
| #ifdef USE_NUMBER | ||||
|   this->init_gate_config_numbers(); | ||||
| #endif | ||||
|   this->set_system_mode(this->system_mode_); | ||||
|   this->set_config_mode(false);  // Disable config mode to save new values in LD2420 nvm | ||||
|   this->set_operating_mode(OP_NORMAL_MODE_STRING); | ||||
|   ESP_LOGCONFIG(TAG, "LD2420 reconfig complete."); | ||||
| } | ||||
|  | ||||
| void LD2420Component::factory_reset_action() { | ||||
|   ESP_LOGCONFIG(TAG, "Setiing factory defaults..."); | ||||
|   if (this->set_config_mode(true) == LD2420_ERROR_TIMEOUT) { | ||||
|     ESP_LOGE(TAG, "LD2420 module has failed to respond, check baud rate and serial connections."); | ||||
|     this->mark_failed(); | ||||
|     return; | ||||
|   } | ||||
|   this->set_min_max_distances_timeout(FACTORY_MAX_GATE, FACTORY_MIN_GATE, FACTORY_TIMEOUT); | ||||
|   this->gate_timeout_number_->state = FACTORY_TIMEOUT; | ||||
|   this->min_gate_distance_number_->state = FACTORY_MIN_GATE; | ||||
|   this->max_gate_distance_number_->state = FACTORY_MAX_GATE; | ||||
|   for (uint8_t gate = 0; gate < LD2420_TOTAL_GATES; gate++) { | ||||
|     this->new_config.move_thresh[gate] = FACTORY_MOVE_THRESH[gate]; | ||||
|     this->new_config.still_thresh[gate] = FACTORY_STILL_THRESH[gate]; | ||||
|     delay_microseconds_safe(125); | ||||
|     this->set_gate_threshold(gate); | ||||
|   } | ||||
|   memcpy(&this->current_config, &this->new_config, sizeof(this->new_config)); | ||||
|   this->set_system_mode(this->system_mode_); | ||||
|   this->set_config_mode(false); | ||||
| #ifdef USE_NUMBER | ||||
|   this->init_gate_config_numbers(); | ||||
|   this->refresh_gate_config_numbers(); | ||||
| #endif | ||||
|   ESP_LOGCONFIG(TAG, "LD2420 factory reset complete."); | ||||
| } | ||||
|  | ||||
| void LD2420Component::restart_module_action() { | ||||
|   ESP_LOGCONFIG(TAG, "Restarting LD2420 module..."); | ||||
|   this->send_module_restart(); | ||||
|   delay_microseconds_safe(45000); | ||||
|   this->set_config_mode(true); | ||||
|   this->set_system_mode(system_mode_); | ||||
|   this->set_config_mode(false); | ||||
|   ESP_LOGCONFIG(TAG, "LD2420 Restarted."); | ||||
| } | ||||
|  | ||||
| void LD2420Component::revert_config_action() { | ||||
|   memcpy(&this->new_config, &this->current_config, sizeof(this->current_config)); | ||||
| #ifdef USE_NUMBER | ||||
|   this->init_gate_config_numbers(); | ||||
| #endif | ||||
|   ESP_LOGCONFIG(TAG, "Reverted config number edits."); | ||||
| } | ||||
|  | ||||
| void LD2420Component::loop() { | ||||
|   // If there is a active send command do not process it here, the send command call will handle it. | ||||
|   if (!get_cmd_active_()) { | ||||
|     if (!available()) | ||||
|       return; | ||||
|     static uint8_t buffer[2048]; | ||||
|     static uint8_t rx_data; | ||||
|     while (available()) { | ||||
|       rx_data = read(); | ||||
|       this->readline_(rx_data, buffer, sizeof(buffer)); | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| void LD2420Component::update_radar_data(uint16_t const *gate_energy, uint8_t sample_number) { | ||||
|   for (uint8_t gate = 0; gate < LD2420_TOTAL_GATES; ++gate) { | ||||
|     this->radar_data[gate][sample_number] = gate_energy[gate]; | ||||
|   } | ||||
|   this->total_sample_number_counter++; | ||||
| } | ||||
|  | ||||
| void LD2420Component::auto_calibrate_sensitivity() { | ||||
|   // Calculate average and peak values for each gate | ||||
|   const float move_factor = gate_move_sensitivity_factor + 1; | ||||
|   const float still_factor = (gate_still_sensitivity_factor / 2) + 1; | ||||
|   for (uint8_t gate = 0; gate < LD2420_TOTAL_GATES; ++gate) { | ||||
|     uint32_t sum = 0; | ||||
|     uint16_t peak = 0; | ||||
|  | ||||
|     for (uint8_t sample_number = 0; sample_number < CALIBRATE_SAMPLES; ++sample_number) { | ||||
|       // Calculate average | ||||
|       sum += this->radar_data[gate][sample_number]; | ||||
|  | ||||
|       // Calculate max value | ||||
|       if (this->radar_data[gate][sample_number] > peak) { | ||||
|         peak = this->radar_data[gate][sample_number]; | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     // Store average and peak values | ||||
|     this->gate_avg[gate] = sum / CALIBRATE_SAMPLES; | ||||
|     if (this->gate_peak[gate] < peak) | ||||
|       this->gate_peak[gate] = peak; | ||||
|  | ||||
|     uint32_t calculated_value = | ||||
|         (static_cast<uint32_t>(this->gate_peak[gate]) + (move_factor * static_cast<uint32_t>(this->gate_peak[gate]))); | ||||
|     this->new_config.move_thresh[gate] = static_cast<uint16_t>(calculated_value <= 65535 ? calculated_value : 65535); | ||||
|     calculated_value = | ||||
|         (static_cast<uint32_t>(this->gate_peak[gate]) + (still_factor * static_cast<uint32_t>(this->gate_peak[gate]))); | ||||
|     this->new_config.still_thresh[gate] = static_cast<uint16_t>(calculated_value <= 65535 ? calculated_value : 65535); | ||||
|   } | ||||
| } | ||||
|  | ||||
| void LD2420Component::report_gate_data() { | ||||
|   for (uint8_t gate = 0; gate < LD2420_TOTAL_GATES; ++gate) { | ||||
|     // Output results | ||||
|     ESP_LOGI(TAG, "Gate: %2d Avg: %5d Peak: %5d", gate, this->gate_avg[gate], this->gate_peak[gate]); | ||||
|   } | ||||
|   ESP_LOGI(TAG, "Total samples: %d", this->total_sample_number_counter); | ||||
| } | ||||
|  | ||||
| void LD2420Component::set_operating_mode(const std::string &state) { | ||||
|   // If unsupported firmware ignore mode select | ||||
|   if (get_firmware_int_(ld2420_firmware_ver_) >= CALIBRATE_VERSION_MIN) { | ||||
|     this->current_operating_mode = OP_MODE_TO_UINT.at(state); | ||||
|     // Entering Auto Calibrate we need to clear the privoiuos data collection | ||||
|     this->operating_selector_->publish_state(state); | ||||
|     if (current_operating_mode == OP_CALIBRATE_MODE) { | ||||
|       this->set_calibration_(true); | ||||
|       for (uint8_t gate = 0; gate < LD2420_TOTAL_GATES; gate++) { | ||||
|         this->gate_avg[gate] = 0; | ||||
|         this->gate_peak[gate] = 0; | ||||
|         for (uint8_t i = 0; i < CALIBRATE_SAMPLES; i++) { | ||||
|           this->radar_data[gate][i] = 0; | ||||
|         } | ||||
|         this->total_sample_number_counter = 0; | ||||
|       } | ||||
|     } else { | ||||
|       // Set the current data back so we don't have new data that can be applied in error. | ||||
|       if (this->get_calibration_()) | ||||
|         memcpy(&this->new_config, &this->current_config, sizeof(this->current_config)); | ||||
|       this->set_calibration_(false); | ||||
|     } | ||||
|   } else { | ||||
|     this->current_operating_mode = OP_SIMPLE_MODE; | ||||
|     this->operating_selector_->publish_state(OP_SIMPLE_MODE_STRING); | ||||
|   } | ||||
| } | ||||
|  | ||||
| void LD2420Component::readline_(int rx_data, uint8_t *buffer, int len) { | ||||
|   static int pos = 0; | ||||
|  | ||||
|   if (rx_data >= 0) { | ||||
|     if (pos < len - 1) { | ||||
|       buffer[pos++] = rx_data; | ||||
|       buffer[pos] = 0; | ||||
|     } else { | ||||
|       pos = 0; | ||||
|     } | ||||
|     if (pos >= 4) { | ||||
|       if (memcmp(&buffer[pos - 4], &CMD_FRAME_FOOTER, sizeof(CMD_FRAME_FOOTER)) == 0) { | ||||
|         this->set_cmd_active_(false);  // Set command state to inactive after responce. | ||||
|         this->handle_ack_data_(buffer, pos); | ||||
|         pos = 0; | ||||
|       } else if ((buffer[pos - 2] == 0x0D && buffer[pos - 1] == 0x0A) && (get_mode_() == CMD_SYSTEM_MODE_SIMPLE)) { | ||||
|         this->handle_simple_mode_(buffer, pos); | ||||
|         pos = 0; | ||||
|       } else if ((memcmp(&buffer[pos - 4], &ENERGY_FRAME_FOOTER, sizeof(ENERGY_FRAME_FOOTER)) == 0) && | ||||
|                  (get_mode_() == CMD_SYSTEM_MODE_ENERGY)) { | ||||
|         this->handle_energy_mode_(buffer, pos); | ||||
|         pos = 0; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| void LD2420Component::handle_energy_mode_(uint8_t *buffer, int len) { | ||||
|   uint8_t index = 6;  // Start at presence byte position | ||||
|   uint16_t range; | ||||
|   const uint8_t elements = sizeof(this->gate_energy_) / sizeof(this->gate_energy_[0]); | ||||
|   this->set_presence_(buffer[index]); | ||||
|   index++; | ||||
|   memcpy(&range, &buffer[index], sizeof(range)); | ||||
|   index += sizeof(range); | ||||
|   this->set_distance_(range); | ||||
|   for (uint8_t i = 0; i < elements; i++) {  // NOLINT | ||||
|     memcpy(&this->gate_energy_[i], &buffer[index], sizeof(this->gate_energy_[0])); | ||||
|     index += sizeof(this->gate_energy_[0]); | ||||
|   } | ||||
|  | ||||
|   if (this->current_operating_mode == OP_CALIBRATE_MODE) { | ||||
|     this->update_radar_data(gate_energy_, sample_number_counter); | ||||
|     this->sample_number_counter > CALIBRATE_SAMPLES ? this->sample_number_counter = 0 : this->sample_number_counter++; | ||||
|   } | ||||
|  | ||||
|   // Resonable refresh rate for home assistant database size health | ||||
|   const int32_t current_millis = millis(); | ||||
|   if (current_millis - this->last_periodic_millis < REFRESH_RATE_MS) | ||||
|     return; | ||||
|   this->last_periodic_millis = current_millis; | ||||
|   for (auto &listener : this->listeners_) { | ||||
|     listener->on_distance(get_distance_()); | ||||
|     listener->on_presence(get_presence_()); | ||||
|     listener->on_energy(this->gate_energy_, sizeof(this->gate_energy_) / sizeof(this->gate_energy_[0])); | ||||
|   } | ||||
|  | ||||
|   if (this->current_operating_mode == OP_CALIBRATE_MODE) { | ||||
|     this->auto_calibrate_sensitivity(); | ||||
|     if (current_millis - this->report_periodic_millis > REFRESH_RATE_MS * CALIBRATE_REPORT_INTERVAL) { | ||||
|       this->report_periodic_millis = current_millis; | ||||
|       this->report_gate_data(); | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| void LD2420Component::handle_simple_mode_(const uint8_t *inbuf, int len) { | ||||
|   const uint8_t bufsize = 16; | ||||
|   uint8_t index{0}; | ||||
|   uint8_t pos{0}; | ||||
|   char *endptr{nullptr}; | ||||
|   char outbuf[bufsize]{0}; | ||||
|   while (true) { | ||||
|     if (inbuf[pos - 2] == 'O' && inbuf[pos - 1] == 'F' && inbuf[pos] == 'F') { | ||||
|       set_presence_(false); | ||||
|     } else if (inbuf[pos - 1] == 'O' && inbuf[pos] == 'N') { | ||||
|       set_presence_(true); | ||||
|     } | ||||
|     if (inbuf[pos] >= '0' && inbuf[pos] <= '9') { | ||||
|       if (index < bufsize - 1) { | ||||
|         outbuf[index++] = inbuf[pos]; | ||||
|         pos++; | ||||
|       } | ||||
|     } else { | ||||
|       if (pos < len - 1) { | ||||
|         pos++; | ||||
|       } else { | ||||
|         break; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|   outbuf[index] = '\0'; | ||||
|   if (index > 1) | ||||
|     set_distance_(strtol(outbuf, &endptr, 10)); | ||||
|  | ||||
|   if (get_mode_() == CMD_SYSTEM_MODE_SIMPLE) { | ||||
|     // Resonable refresh rate for home assistant database size health | ||||
|     const int32_t current_millis = millis(); | ||||
|     if (current_millis - this->last_normal_periodic_millis < REFRESH_RATE_MS) | ||||
|       return; | ||||
|     this->last_normal_periodic_millis = current_millis; | ||||
|     for (auto &listener : this->listeners_) | ||||
|       listener->on_distance(get_distance_()); | ||||
|     for (auto &listener : this->listeners_) | ||||
|       listener->on_presence(get_presence_()); | ||||
|   } | ||||
| } | ||||
|  | ||||
| void LD2420Component::handle_ack_data_(uint8_t *buffer, int len) { | ||||
|   this->cmd_reply_.command = buffer[CMD_FRAME_COMMAND]; | ||||
|   this->cmd_reply_.length = buffer[CMD_FRAME_DATA_LENGTH]; | ||||
|   uint8_t reg_element = 0; | ||||
|   uint8_t data_element = 0; | ||||
|   uint16_t data_pos = 0; | ||||
|   if (this->cmd_reply_.length > CMD_MAX_BYTES) { | ||||
|     ESP_LOGW(TAG, "LD2420 reply - received command reply frame is corrupt, length exceeds %d bytes.", CMD_MAX_BYTES); | ||||
|     return; | ||||
|   } else if (this->cmd_reply_.length < 2) { | ||||
|     ESP_LOGW(TAG, "LD2420 reply - received command frame is corrupt, length is less than 2 bytes."); | ||||
|     return; | ||||
|   } | ||||
|   memcpy(&this->cmd_reply_.error, &buffer[CMD_ERROR_WORD], sizeof(this->cmd_reply_.error)); | ||||
|   const char *result = this->cmd_reply_.error ? "failure" : "success"; | ||||
|   if (this->cmd_reply_.error > 0) { | ||||
|     return; | ||||
|   }; | ||||
|   this->cmd_reply_.ack = true; | ||||
|   switch ((uint16_t) this->cmd_reply_.command) { | ||||
|     case (CMD_ENABLE_CONF): | ||||
|       ESP_LOGD(TAG, "LD2420 reply - set config enable: CMD = %2X %s", CMD_ENABLE_CONF, result); | ||||
|       break; | ||||
|     case (CMD_DISABLE_CONF): | ||||
|       ESP_LOGD(TAG, "LD2420 reply - set config disable: CMD = %2X %s", CMD_DISABLE_CONF, result); | ||||
|       break; | ||||
|     case (CMD_READ_REGISTER): | ||||
|       ESP_LOGD(TAG, "LD2420 reply - read register: CMD = %2X %s", CMD_READ_REGISTER, result); | ||||
|       // TODO Read/Write register is not implemented yet, this will get flushed out to a proper header file | ||||
|       data_pos = 0x0A; | ||||
|       for (uint16_t index = 0; index < (CMD_REG_DATA_REPLY_SIZE *  // NOLINT | ||||
|                                         ((buffer[CMD_FRAME_DATA_LENGTH] - 4) / CMD_REG_DATA_REPLY_SIZE)); | ||||
|            index += CMD_REG_DATA_REPLY_SIZE) { | ||||
|         memcpy(&this->cmd_reply_.data[reg_element], &buffer[data_pos + index], sizeof(CMD_REG_DATA_REPLY_SIZE)); | ||||
|         byteswap(this->cmd_reply_.data[reg_element]); | ||||
|         reg_element++; | ||||
|       } | ||||
|       break; | ||||
|     case (CMD_WRITE_REGISTER): | ||||
|       ESP_LOGD(TAG, "LD2420 reply - write register: CMD = %2X %s", CMD_WRITE_REGISTER, result); | ||||
|       break; | ||||
|     case (CMD_WRITE_ABD_PARAM): | ||||
|       ESP_LOGD(TAG, "LD2420 reply - write gate parameter(s): %2X %s", CMD_WRITE_ABD_PARAM, result); | ||||
|       break; | ||||
|     case (CMD_READ_ABD_PARAM): | ||||
|       ESP_LOGD(TAG, "LD2420 reply - read gate parameter(s): %2X %s", CMD_READ_ABD_PARAM, result); | ||||
|       data_pos = CMD_ABD_DATA_REPLY_START; | ||||
|       for (uint16_t index = 0; index < (CMD_ABD_DATA_REPLY_SIZE *  // NOLINT | ||||
|                                         ((buffer[CMD_FRAME_DATA_LENGTH] - 4) / CMD_ABD_DATA_REPLY_SIZE)); | ||||
|            index += CMD_ABD_DATA_REPLY_SIZE) { | ||||
|         memcpy(&this->cmd_reply_.data[data_element], &buffer[data_pos + index], | ||||
|                sizeof(this->cmd_reply_.data[data_element])); | ||||
|         byteswap(this->cmd_reply_.data[data_element]); | ||||
|         data_element++; | ||||
|       } | ||||
|       break; | ||||
|     case (CMD_WRITE_SYS_PARAM): | ||||
|       ESP_LOGD(TAG, "LD2420 reply - set system parameter(s): %2X %s", CMD_WRITE_SYS_PARAM, result); | ||||
|       break; | ||||
|     case (CMD_READ_VERSION): | ||||
|       memcpy(this->ld2420_firmware_ver_, &buffer[12], buffer[10]); | ||||
|       ESP_LOGD(TAG, "LD2420 reply - module firmware version: %7s %s", this->ld2420_firmware_ver_, result); | ||||
|       break; | ||||
|     default: | ||||
|       break; | ||||
|   } | ||||
| } | ||||
|  | ||||
| int LD2420Component::send_cmd_from_array(CmdFrameT frame) { | ||||
|   uint8_t error = 0; | ||||
|   uint8_t ack_buffer[64]; | ||||
|   uint8_t cmd_buffer[64]; | ||||
|   uint16_t loop_count; | ||||
|   this->cmd_reply_.ack = false; | ||||
|   if (frame.command != CMD_RESTART) | ||||
|     this->set_cmd_active_(true);  // Restart does not reply, thus no ack state required. | ||||
|   uint8_t retry = 3; | ||||
|   while (retry) { | ||||
|     // TODO setup a dynamic method e.g. millis time count etc. to tune for non ESP32 240Mhz devices | ||||
|     // this is ok for now since the module firmware is changing like the weather atm | ||||
|     frame.length = 0; | ||||
|     loop_count = 1250; | ||||
|     uint16_t frame_data_bytes = frame.data_length + 2;  // Always add two bytes for the cmd size | ||||
|  | ||||
|     memcpy(&cmd_buffer[frame.length], &frame.header, sizeof(frame.header)); | ||||
|     frame.length += sizeof(frame.header); | ||||
|  | ||||
|     memcpy(&cmd_buffer[frame.length], &frame_data_bytes, sizeof(frame.data_length)); | ||||
|     frame.length += sizeof(frame.data_length); | ||||
|  | ||||
|     memcpy(&cmd_buffer[frame.length], &frame.command, sizeof(frame.command)); | ||||
|     frame.length += sizeof(frame.command); | ||||
|  | ||||
|     for (uint16_t index = 0; index < frame.data_length; index++) { | ||||
|       memcpy(&cmd_buffer[frame.length], &frame.data[index], sizeof(frame.data[index])); | ||||
|       frame.length += sizeof(frame.data[index]); | ||||
|     } | ||||
|  | ||||
|     memcpy(cmd_buffer + frame.length, &frame.footer, sizeof(frame.footer)); | ||||
|     frame.length += sizeof(frame.footer); | ||||
|     for (uint16_t index = 0; index < frame.length; index++) { | ||||
|       this->write_byte(cmd_buffer[index]); | ||||
|     } | ||||
|  | ||||
|     delay_microseconds_safe(500);  // give the module a moment to process it | ||||
|     error = 0; | ||||
|     if (frame.command == CMD_RESTART) { | ||||
|       delay_microseconds_safe(25000);  // Wait for the restart | ||||
|       return 0;                        // restart does not reply exit now | ||||
|     } | ||||
|  | ||||
|     while (!this->cmd_reply_.ack) { | ||||
|       while (available()) { | ||||
|         this->readline_(read(), ack_buffer, sizeof(ack_buffer)); | ||||
|       } | ||||
|       delay_microseconds_safe(250); | ||||
|       if (loop_count <= 0) { | ||||
|         error = LD2420_ERROR_TIMEOUT; | ||||
|         retry--; | ||||
|         break; | ||||
|       } | ||||
|       loop_count--; | ||||
|     } | ||||
|     if (this->cmd_reply_.ack) | ||||
|       retry = 0; | ||||
|     if (this->cmd_reply_.error > 0) | ||||
|       handle_cmd_error(error); | ||||
|   } | ||||
|   return error; | ||||
| } | ||||
|  | ||||
| uint8_t LD2420Component::set_config_mode(bool enable) { | ||||
|   CmdFrameT cmd_frame; | ||||
|   cmd_frame.data_length = 0; | ||||
|   cmd_frame.header = CMD_FRAME_HEADER; | ||||
|   cmd_frame.command = enable ? CMD_ENABLE_CONF : CMD_DISABLE_CONF; | ||||
|   if (enable) { | ||||
|     memcpy(&cmd_frame.data[0], &CMD_PROTOCOL_VER, sizeof(CMD_PROTOCOL_VER)); | ||||
|     cmd_frame.data_length += sizeof(CMD_PROTOCOL_VER); | ||||
|   } | ||||
|   cmd_frame.footer = CMD_FRAME_FOOTER; | ||||
|   ESP_LOGD(TAG, "Sending set config %s command: %2X", enable ? "enable" : "disable", cmd_frame.command); | ||||
|   return this->send_cmd_from_array(cmd_frame); | ||||
| } | ||||
|  | ||||
| // Sends a restart and set system running mode to normal | ||||
| void LD2420Component::send_module_restart() { this->ld2420_restart(); } | ||||
|  | ||||
| void LD2420Component::ld2420_restart() { | ||||
|   CmdFrameT cmd_frame; | ||||
|   cmd_frame.data_length = 0; | ||||
|   cmd_frame.header = CMD_FRAME_HEADER; | ||||
|   cmd_frame.command = CMD_RESTART; | ||||
|   cmd_frame.footer = CMD_FRAME_FOOTER; | ||||
|   ESP_LOGD(TAG, "Sending restart command: %2X", cmd_frame.command); | ||||
|   this->send_cmd_from_array(cmd_frame); | ||||
| } | ||||
|  | ||||
| void LD2420Component::get_reg_value_(uint16_t reg) { | ||||
|   CmdFrameT cmd_frame; | ||||
|   cmd_frame.data_length = 0; | ||||
|   cmd_frame.header = CMD_FRAME_HEADER; | ||||
|   cmd_frame.command = CMD_READ_REGISTER; | ||||
|   cmd_frame.data[1] = reg; | ||||
|   cmd_frame.data_length += 2; | ||||
|   cmd_frame.footer = CMD_FRAME_FOOTER; | ||||
|   ESP_LOGD(TAG, "Sending read register %4X command: %2X", reg, cmd_frame.command); | ||||
|   this->send_cmd_from_array(cmd_frame); | ||||
| } | ||||
|  | ||||
| void LD2420Component::set_reg_value(uint16_t reg, uint16_t value) { | ||||
|   CmdFrameT cmd_frame; | ||||
|   cmd_frame.data_length = 0; | ||||
|   cmd_frame.header = CMD_FRAME_HEADER; | ||||
|   cmd_frame.command = CMD_WRITE_REGISTER; | ||||
|   memcpy(&cmd_frame.data[cmd_frame.data_length], ®, sizeof(CMD_REG_DATA_REPLY_SIZE)); | ||||
|   cmd_frame.data_length += 2; | ||||
|   memcpy(&cmd_frame.data[cmd_frame.data_length], &value, sizeof(CMD_REG_DATA_REPLY_SIZE)); | ||||
|   cmd_frame.data_length += 2; | ||||
|   cmd_frame.footer = CMD_FRAME_FOOTER; | ||||
|   ESP_LOGD(TAG, "Sending write register %4X command: %2X data = %4X", reg, cmd_frame.command, value); | ||||
|   this->send_cmd_from_array(cmd_frame); | ||||
| } | ||||
|  | ||||
| void LD2420Component::handle_cmd_error(uint8_t error) { ESP_LOGI(TAG, "Command failed: %s", ERR_MESSAGE[error]); } | ||||
|  | ||||
| int LD2420Component::get_gate_threshold_(uint8_t gate) { | ||||
|   uint8_t error; | ||||
|   CmdFrameT cmd_frame; | ||||
|   cmd_frame.data_length = 0; | ||||
|   cmd_frame.header = CMD_FRAME_HEADER; | ||||
|   cmd_frame.command = CMD_READ_ABD_PARAM; | ||||
|   memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_GATE_MOVE_THRESH[gate], sizeof(CMD_GATE_MOVE_THRESH[gate])); | ||||
|   cmd_frame.data_length += 2; | ||||
|   memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_GATE_STILL_THRESH[gate], sizeof(CMD_GATE_STILL_THRESH[gate])); | ||||
|   cmd_frame.data_length += 2; | ||||
|   cmd_frame.footer = CMD_FRAME_FOOTER; | ||||
|   ESP_LOGD(TAG, "Sending read gate %d high/low theshold command: %2X", gate, cmd_frame.command); | ||||
|   error = this->send_cmd_from_array(cmd_frame); | ||||
|   if (error == 0) { | ||||
|     this->current_config.move_thresh[gate] = cmd_reply_.data[0]; | ||||
|     this->current_config.still_thresh[gate] = cmd_reply_.data[1]; | ||||
|   } | ||||
|   return error; | ||||
| } | ||||
|  | ||||
| int LD2420Component::get_min_max_distances_timeout_() { | ||||
|   uint8_t error; | ||||
|   CmdFrameT cmd_frame; | ||||
|   cmd_frame.data_length = 0; | ||||
|   cmd_frame.header = CMD_FRAME_HEADER; | ||||
|   cmd_frame.command = CMD_READ_ABD_PARAM; | ||||
|   memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_MIN_GATE_REG, | ||||
|          sizeof(CMD_MIN_GATE_REG));  // Register: global min detect gate number | ||||
|   cmd_frame.data_length += sizeof(CMD_MIN_GATE_REG); | ||||
|   memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_MAX_GATE_REG, | ||||
|          sizeof(CMD_MAX_GATE_REG));  // Register: global max detect gate number | ||||
|   cmd_frame.data_length += sizeof(CMD_MAX_GATE_REG); | ||||
|   memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_TIMEOUT_REG, | ||||
|          sizeof(CMD_TIMEOUT_REG));  // Register: global delay time | ||||
|   cmd_frame.data_length += sizeof(CMD_TIMEOUT_REG); | ||||
|   cmd_frame.footer = CMD_FRAME_FOOTER; | ||||
|   ESP_LOGD(TAG, "Sending read gate min max and timeout command: %2X", cmd_frame.command); | ||||
|   error = this->send_cmd_from_array(cmd_frame); | ||||
|   if (error == 0) { | ||||
|     this->current_config.min_gate = (uint16_t) cmd_reply_.data[0]; | ||||
|     this->current_config.max_gate = (uint16_t) cmd_reply_.data[1]; | ||||
|     this->current_config.timeout = (uint16_t) cmd_reply_.data[2]; | ||||
|   } | ||||
|   return error; | ||||
| } | ||||
|  | ||||
| void LD2420Component::set_system_mode(uint16_t mode) { | ||||
|   CmdFrameT cmd_frame; | ||||
|   uint16_t unknown_parm = 0x0000; | ||||
|   cmd_frame.data_length = 0; | ||||
|   cmd_frame.header = CMD_FRAME_HEADER; | ||||
|   cmd_frame.command = CMD_WRITE_SYS_PARAM; | ||||
|   memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_SYSTEM_MODE, sizeof(CMD_SYSTEM_MODE)); | ||||
|   cmd_frame.data_length += sizeof(CMD_SYSTEM_MODE); | ||||
|   memcpy(&cmd_frame.data[cmd_frame.data_length], &mode, sizeof(mode)); | ||||
|   cmd_frame.data_length += sizeof(mode); | ||||
|   memcpy(&cmd_frame.data[cmd_frame.data_length], &unknown_parm, sizeof(unknown_parm)); | ||||
|   cmd_frame.data_length += sizeof(unknown_parm); | ||||
|   cmd_frame.footer = CMD_FRAME_FOOTER; | ||||
|   ESP_LOGD(TAG, "Sending write system mode command: %2X", cmd_frame.command); | ||||
|   if (this->send_cmd_from_array(cmd_frame) == 0) | ||||
|     set_mode_(mode); | ||||
| } | ||||
|  | ||||
| void LD2420Component::get_firmware_version_() { | ||||
|   CmdFrameT cmd_frame; | ||||
|   cmd_frame.data_length = 0; | ||||
|   cmd_frame.header = CMD_FRAME_HEADER; | ||||
|   cmd_frame.command = CMD_READ_VERSION; | ||||
|   cmd_frame.footer = CMD_FRAME_FOOTER; | ||||
|  | ||||
|   ESP_LOGD(TAG, "Sending read firmware version command: %2X", cmd_frame.command); | ||||
|   this->send_cmd_from_array(cmd_frame); | ||||
| } | ||||
|  | ||||
| void LD2420Component::set_min_max_distances_timeout(uint32_t max_gate_distance, uint32_t min_gate_distance,  // NOLINT | ||||
|                                                     uint32_t timeout) { | ||||
|   // Header H, Length L, Register R, Value V, Footer F | ||||
|   //                        |Min Gate         |Max Gate         |Timeout          | | ||||
|   // HH HH HH HH LL LL CC CC RR RR VV VV VV VV RR RR VV VV VV VV RR RR VV VV VV VV FF FF FF FF | ||||
|   // FD FC FB FA 14 00 07 00 00 00 01 00 00 00 01 00 09 00 00 00 04 00 0A 00 00 00 04 03 02 01 e.g. | ||||
|  | ||||
|   CmdFrameT cmd_frame; | ||||
|   cmd_frame.data_length = 0; | ||||
|   cmd_frame.header = CMD_FRAME_HEADER; | ||||
|   cmd_frame.command = CMD_WRITE_ABD_PARAM; | ||||
|   memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_MIN_GATE_REG, | ||||
|          sizeof(CMD_MIN_GATE_REG));  // Register: global min detect gate number | ||||
|   cmd_frame.data_length += sizeof(CMD_MIN_GATE_REG); | ||||
|   memcpy(&cmd_frame.data[cmd_frame.data_length], &min_gate_distance, sizeof(min_gate_distance)); | ||||
|   cmd_frame.data_length += sizeof(min_gate_distance); | ||||
|   memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_MAX_GATE_REG, | ||||
|          sizeof(CMD_MAX_GATE_REG));  // Register: global max detect gate number | ||||
|   cmd_frame.data_length += sizeof(CMD_MAX_GATE_REG); | ||||
|   memcpy(&cmd_frame.data[cmd_frame.data_length], &max_gate_distance, sizeof(max_gate_distance)); | ||||
|   cmd_frame.data_length += sizeof(max_gate_distance); | ||||
|   memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_TIMEOUT_REG, | ||||
|          sizeof(CMD_TIMEOUT_REG));  // Register: global delay time | ||||
|   cmd_frame.data_length += sizeof(CMD_TIMEOUT_REG); | ||||
|   memcpy(&cmd_frame.data[cmd_frame.data_length], &timeout, sizeof(timeout)); | ||||
|   ; | ||||
|   cmd_frame.data_length += sizeof(timeout); | ||||
|   cmd_frame.footer = CMD_FRAME_FOOTER; | ||||
|  | ||||
|   ESP_LOGD(TAG, "Sending write gate min max and timeout command: %2X", cmd_frame.command); | ||||
|   this->send_cmd_from_array(cmd_frame); | ||||
| } | ||||
|  | ||||
| void LD2420Component::set_gate_threshold(uint8_t gate) { | ||||
|   // Header H, Length L, Command C, Register R, Value V, Footer F | ||||
|   // HH HH HH HH LL LL CC CC RR RR VV VV VV VV RR RR VV VV VV VV FF FF FF FF | ||||
|   // FD FC FB FA 14 00 07 00 10 00 00 FF 00 00 00 01 00 0F 00 00 04 03 02 01 | ||||
|  | ||||
|   uint16_t move_threshold_gate = CMD_GATE_MOVE_THRESH[gate]; | ||||
|   uint16_t still_threshold_gate = CMD_GATE_STILL_THRESH[gate]; | ||||
|   CmdFrameT cmd_frame; | ||||
|   cmd_frame.data_length = 0; | ||||
|   cmd_frame.header = CMD_FRAME_HEADER; | ||||
|   cmd_frame.command = CMD_WRITE_ABD_PARAM; | ||||
|   memcpy(&cmd_frame.data[cmd_frame.data_length], &move_threshold_gate, sizeof(move_threshold_gate)); | ||||
|   cmd_frame.data_length += sizeof(move_threshold_gate); | ||||
|   memcpy(&cmd_frame.data[cmd_frame.data_length], &this->new_config.move_thresh[gate], | ||||
|          sizeof(this->new_config.move_thresh[gate])); | ||||
|   cmd_frame.data_length += sizeof(this->new_config.move_thresh[gate]); | ||||
|   memcpy(&cmd_frame.data[cmd_frame.data_length], &still_threshold_gate, sizeof(still_threshold_gate)); | ||||
|   cmd_frame.data_length += sizeof(still_threshold_gate); | ||||
|   memcpy(&cmd_frame.data[cmd_frame.data_length], &this->new_config.still_thresh[gate], | ||||
|          sizeof(this->new_config.still_thresh[gate])); | ||||
|   cmd_frame.data_length += sizeof(this->new_config.still_thresh[gate]); | ||||
|   cmd_frame.footer = CMD_FRAME_FOOTER; | ||||
|   ESP_LOGD(TAG, "Sending set gate %4X sensitivity command: %2X", gate, cmd_frame.command); | ||||
|   this->send_cmd_from_array(cmd_frame); | ||||
| } | ||||
|  | ||||
| #ifdef USE_NUMBER | ||||
| void LD2420Component::init_gate_config_numbers() { | ||||
|   if (this->gate_timeout_number_ != nullptr) | ||||
|     this->gate_timeout_number_->publish_state(static_cast<uint16_t>(this->current_config.timeout)); | ||||
|   if (this->gate_select_number_ != nullptr) | ||||
|     this->gate_select_number_->publish_state(0); | ||||
|   if (this->min_gate_distance_number_ != nullptr) | ||||
|     this->min_gate_distance_number_->publish_state(static_cast<uint16_t>(this->current_config.min_gate)); | ||||
|   if (this->max_gate_distance_number_ != nullptr) | ||||
|     this->max_gate_distance_number_->publish_state(static_cast<uint16_t>(this->current_config.max_gate)); | ||||
|   if (this->gate_move_sensitivity_factor_number_ != nullptr) | ||||
|     this->gate_move_sensitivity_factor_number_->publish_state(this->gate_move_sensitivity_factor); | ||||
|   if (this->gate_still_sensitivity_factor_number_ != nullptr) | ||||
|     this->gate_still_sensitivity_factor_number_->publish_state(this->gate_still_sensitivity_factor); | ||||
|   for (uint8_t gate = 0; gate < LD2420_TOTAL_GATES; gate++) { | ||||
|     if (this->gate_still_threshold_numbers_[gate] != nullptr) { | ||||
|       this->gate_still_threshold_numbers_[gate]->publish_state( | ||||
|           static_cast<uint16_t>(this->current_config.still_thresh[gate])); | ||||
|     } | ||||
|     if (this->gate_move_threshold_numbers_[gate] != nullptr) { | ||||
|       this->gate_move_threshold_numbers_[gate]->publish_state( | ||||
|           static_cast<uint16_t>(this->current_config.move_thresh[gate])); | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| void LD2420Component::refresh_gate_config_numbers() { | ||||
|   this->gate_timeout_number_->publish_state(this->new_config.timeout); | ||||
|   this->min_gate_distance_number_->publish_state(this->new_config.min_gate); | ||||
|   this->max_gate_distance_number_->publish_state(this->new_config.max_gate); | ||||
| } | ||||
|  | ||||
| #endif | ||||
|  | ||||
| }  // namespace ld2420 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										272
									
								
								esphome/components/ld2420/ld2420.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										272
									
								
								esphome/components/ld2420/ld2420.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,272 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/components/uart/uart.h" | ||||
| #include "esphome/core/automation.h" | ||||
| #include "esphome/core/helpers.h" | ||||
| #ifdef USE_TEXT_SENSOR | ||||
| #include "esphome/components/text_sensor/text_sensor.h" | ||||
| #endif | ||||
| #ifdef USE_SELECT | ||||
| #include "esphome/components/select/select.h" | ||||
| #endif | ||||
| #ifdef USE_NUMBER | ||||
| #include "esphome/components/number/number.h" | ||||
| #endif | ||||
| #ifdef USE_BUTTON | ||||
| #include "esphome/components/button/button.h" | ||||
| #endif | ||||
| #include <map> | ||||
| #include <functional> | ||||
|  | ||||
| namespace esphome { | ||||
| namespace ld2420 { | ||||
|  | ||||
| // Local const's | ||||
| static const uint16_t REFRESH_RATE_MS = 1000; | ||||
|  | ||||
| // Command sets | ||||
| static const uint8_t CMD_ABD_DATA_REPLY_SIZE = 0x04; | ||||
| static const uint8_t CMD_ABD_DATA_REPLY_START = 0x0A; | ||||
| static const uint16_t CMD_DISABLE_CONF = 0x00FE; | ||||
| static const uint16_t CMD_ENABLE_CONF = 0x00FF; | ||||
| static const uint8_t CMD_MAX_BYTES = 0x64; | ||||
| static const uint16_t CMD_PARM_HIGH_TRESH = 0x0012; | ||||
| static const uint16_t CMD_PARM_LOW_TRESH = 0x0021; | ||||
| static const uint16_t CMD_PROTOCOL_VER = 0x0002; | ||||
| static const uint16_t CMD_READ_ABD_PARAM = 0x0008; | ||||
| static const uint16_t CMD_READ_REG_ADDR = 0x0020; | ||||
| static const uint16_t CMD_READ_REGISTER = 0x0002; | ||||
| static const uint16_t CMD_READ_SERIAL_NUM = 0x0011; | ||||
| static const uint16_t CMD_READ_SYS_PARAM = 0x0013; | ||||
| static const uint16_t CMD_READ_VERSION = 0x0000; | ||||
| static const uint8_t CMD_REG_DATA_REPLY_SIZE = 0x02; | ||||
| static const uint16_t CMD_RESTART = 0x0068; | ||||
| static const uint16_t CMD_SYSTEM_MODE = 0x0000; | ||||
| static const uint16_t CMD_SYSTEM_MODE_GR = 0x0003; | ||||
| static const uint16_t CMD_SYSTEM_MODE_MTT = 0x0001; | ||||
| static const uint16_t CMD_SYSTEM_MODE_SIMPLE = 0x0064; | ||||
| static const uint16_t CMD_SYSTEM_MODE_DEBUG = 0x0000; | ||||
| static const uint16_t CMD_SYSTEM_MODE_ENERGY = 0x0004; | ||||
| static const uint16_t CMD_SYSTEM_MODE_VS = 0x0002; | ||||
| static const uint16_t CMD_WRITE_ABD_PARAM = 0x0007; | ||||
| static const uint16_t CMD_WRITE_REGISTER = 0x0001; | ||||
| static const uint16_t CMD_WRITE_SYS_PARAM = 0x0012; | ||||
|  | ||||
| static const uint8_t LD2420_ERROR_NONE = 0x00; | ||||
| static const uint8_t LD2420_ERROR_TIMEOUT = 0x02; | ||||
| static const uint8_t LD2420_ERROR_UNKNOWN = 0x01; | ||||
| static const uint8_t LD2420_TOTAL_GATES = 16; | ||||
| static const uint8_t CALIBRATE_SAMPLES = 64; | ||||
|  | ||||
| // Register address values | ||||
| static const uint16_t CMD_MIN_GATE_REG = 0x0000; | ||||
| static const uint16_t CMD_MAX_GATE_REG = 0x0001; | ||||
| static const uint16_t CMD_TIMEOUT_REG = 0x0004; | ||||
| static const uint16_t CMD_GATE_MOVE_THRESH[LD2420_TOTAL_GATES] = {0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, | ||||
|                                                                   0x0016, 0x0017, 0x0018, 0x0019, 0x001A, 0x001B, | ||||
|                                                                   0x001C, 0x001D, 0x001E, 0x001F}; | ||||
| static const uint16_t CMD_GATE_STILL_THRESH[LD2420_TOTAL_GATES] = {0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, | ||||
|                                                                    0x0026, 0x0027, 0x0028, 0x0029, 0x002A, 0x002B, | ||||
|                                                                    0x002C, 0x002D, 0x002E, 0x002F}; | ||||
| static const uint32_t FACTORY_MOVE_THRESH[LD2420_TOTAL_GATES] = {60000, 30000, 400, 250, 250, 250, 250, 250, | ||||
|                                                                  250,   250,   250, 250, 250, 250, 250, 250}; | ||||
| static const uint32_t FACTORY_STILL_THRESH[LD2420_TOTAL_GATES] = {40000, 20000, 200, 200, 200, 200, 200, 150, | ||||
|                                                                   150,   100,   100, 100, 100, 100, 100, 100}; | ||||
| static const uint16_t FACTORY_TIMEOUT = 120; | ||||
| static const uint16_t FACTORY_MIN_GATE = 1; | ||||
| static const uint16_t FACTORY_MAX_GATE = 12; | ||||
|  | ||||
| // COMMAND_BYTE Header & Footer | ||||
| static const uint8_t CMD_FRAME_COMMAND = 6; | ||||
| static const uint8_t CMD_FRAME_DATA_LENGTH = 4; | ||||
| static const uint32_t CMD_FRAME_FOOTER = 0x01020304; | ||||
| static const uint32_t CMD_FRAME_HEADER = 0xFAFBFCFD; | ||||
| static const uint32_t DEBUG_FRAME_FOOTER = 0xFAFBFCFD; | ||||
| static const uint32_t DEBUG_FRAME_HEADER = 0x1410BFAA; | ||||
| static const uint32_t ENERGY_FRAME_FOOTER = 0xF5F6F7F8; | ||||
| static const uint32_t ENERGY_FRAME_HEADER = 0xF1F2F3F4; | ||||
| static const uint8_t CMD_FRAME_STATUS = 7; | ||||
| static const uint8_t CMD_ERROR_WORD = 8; | ||||
| static const uint8_t ENERGY_SENSOR_START = 9; | ||||
| static const uint8_t CALIBRATE_REPORT_INTERVAL = 4; | ||||
| static const int CALIBRATE_VERSION_MIN = 154; | ||||
| static const std::string OP_NORMAL_MODE_STRING = "Normal"; | ||||
| static const std::string OP_SIMPLE_MODE_STRING = "Simple"; | ||||
|  | ||||
| enum OpModeStruct : uint8_t { OP_NORMAL_MODE = 1, OP_CALIBRATE_MODE = 2, OP_SIMPLE_MODE = 3 }; | ||||
| static const std::map<std::string, uint8_t> OP_MODE_TO_UINT{ | ||||
|     {"Normal", OP_NORMAL_MODE}, {"Calibrate", OP_CALIBRATE_MODE}, {"Simple", OP_SIMPLE_MODE}}; | ||||
| static constexpr const char *ERR_MESSAGE[] = {"None", "Unknown", "Timeout"}; | ||||
|  | ||||
| class LD2420Listener { | ||||
|  public: | ||||
|   virtual void on_presence(bool presence){}; | ||||
|   virtual void on_distance(uint16_t distance){}; | ||||
|   virtual void on_energy(uint16_t *sensor_energy, size_t size){}; | ||||
|   virtual void on_fw_version(std::string &fw){}; | ||||
| }; | ||||
|  | ||||
| class LD2420Component : public Component, public uart::UARTDevice { | ||||
|  public: | ||||
|   void setup() override; | ||||
|   void dump_config() override; | ||||
|   void loop() override; | ||||
| #ifdef USE_SELECT | ||||
|   void set_operating_mode_select(select::Select *selector) { this->operating_selector_ = selector; }; | ||||
| #endif | ||||
| #ifdef USE_NUMBER | ||||
|   void set_gate_timeout_number(number::Number *number) { this->gate_timeout_number_ = number; }; | ||||
|   void set_gate_select_number(number::Number *number) { this->gate_select_number_ = number; }; | ||||
|   void set_min_gate_distance_number(number::Number *number) { this->min_gate_distance_number_ = number; }; | ||||
|   void set_max_gate_distance_number(number::Number *number) { this->max_gate_distance_number_ = number; }; | ||||
|   void set_gate_move_sensitivity_factor_number(number::Number *number) { | ||||
|     this->gate_move_sensitivity_factor_number_ = number; | ||||
|   }; | ||||
|   void set_gate_still_sensitivity_factor_number(number::Number *number) { | ||||
|     this->gate_still_sensitivity_factor_number_ = number; | ||||
|   }; | ||||
|   void set_gate_still_threshold_numbers(int gate, number::Number *n) { this->gate_still_threshold_numbers_[gate] = n; }; | ||||
|   void set_gate_move_threshold_numbers(int gate, number::Number *n) { this->gate_move_threshold_numbers_[gate] = n; }; | ||||
|   bool is_gate_select() { return gate_select_number_ != nullptr; }; | ||||
|   uint8_t get_gate_select_value() { return static_cast<uint8_t>(this->gate_select_number_->state); }; | ||||
|   float get_min_gate_distance_value() { return min_gate_distance_number_->state; }; | ||||
|   float get_max_gate_distance_value() { return max_gate_distance_number_->state; }; | ||||
|   void publish_gate_move_threshold(uint8_t gate) { | ||||
|     // With gate_select we only use 1 number pointer, thus we hard code [0] | ||||
|     this->gate_move_threshold_numbers_[0]->publish_state(this->new_config.move_thresh[gate]); | ||||
|   }; | ||||
|   void publish_gate_still_threshold(uint8_t gate) { | ||||
|     this->gate_still_threshold_numbers_[0]->publish_state(this->new_config.still_thresh[gate]); | ||||
|   }; | ||||
|   void init_gate_config_numbers(); | ||||
|   void refresh_gate_config_numbers(); | ||||
| #endif | ||||
| #ifdef USE_BUTTON | ||||
|   void set_apply_config_button(button::Button *button) { this->apply_config_button_ = button; }; | ||||
|   void set_revert_config_button(button::Button *button) { this->revert_config_button_ = button; }; | ||||
|   void set_restart_module_button(button::Button *button) { this->restart_module_button_ = button; }; | ||||
|   void set_factory_reset_button(button::Button *button) { this->factory_reset_button_ = button; }; | ||||
| #endif | ||||
|   void register_listener(LD2420Listener *listener) { this->listeners_.push_back(listener); } | ||||
|  | ||||
|   struct CmdFrameT { | ||||
|     uint32_t header{0}; | ||||
|     uint16_t length{0}; | ||||
|     uint16_t command{0}; | ||||
|     uint8_t data[18]; | ||||
|     uint16_t data_length{0}; | ||||
|     uint32_t footer{0}; | ||||
|   }; | ||||
|  | ||||
|   struct RegConfigT { | ||||
|     uint16_t min_gate{0}; | ||||
|     uint16_t max_gate{0}; | ||||
|     uint16_t timeout{0}; | ||||
|     uint32_t move_thresh[LD2420_TOTAL_GATES]; | ||||
|     uint32_t still_thresh[LD2420_TOTAL_GATES]; | ||||
|   }; | ||||
|  | ||||
|   void send_module_restart(); | ||||
|   void restart_module_action(); | ||||
|   void apply_config_action(); | ||||
|   void factory_reset_action(); | ||||
|   void revert_config_action(); | ||||
|   float get_setup_priority() const override; | ||||
|   int send_cmd_from_array(CmdFrameT cmd_frame); | ||||
|   void report_gate_data(); | ||||
|   void handle_cmd_error(uint8_t error); | ||||
|   void set_operating_mode(const std::string &state); | ||||
|   void auto_calibrate_sensitivity(); | ||||
|   void update_radar_data(uint16_t const *gate_energy, uint8_t sample_number); | ||||
|   uint8_t calc_checksum(void *data, size_t size); | ||||
|  | ||||
|   RegConfigT current_config; | ||||
|   RegConfigT new_config; | ||||
|   int32_t last_periodic_millis = millis(); | ||||
|   int32_t report_periodic_millis = millis(); | ||||
|   int32_t monitor_periodic_millis = millis(); | ||||
|   int32_t last_normal_periodic_millis = millis(); | ||||
|   bool output_energy_state{false}; | ||||
|   uint8_t current_operating_mode{OP_NORMAL_MODE}; | ||||
|   uint16_t radar_data[LD2420_TOTAL_GATES][CALIBRATE_SAMPLES]; | ||||
|   uint16_t gate_avg[LD2420_TOTAL_GATES]; | ||||
|   uint16_t gate_peak[LD2420_TOTAL_GATES]; | ||||
|   uint8_t sample_number_counter{0}; | ||||
|   uint16_t total_sample_number_counter{0}; | ||||
|   float gate_move_sensitivity_factor{0.5}; | ||||
|   float gate_still_sensitivity_factor{0.5}; | ||||
| #ifdef USE_SELECT | ||||
|   select::Select *operating_selector_{nullptr}; | ||||
| #endif | ||||
| #ifdef USE_BUTTON | ||||
|   button::Button *apply_config_button_{nullptr}; | ||||
|   button::Button *revert_config_button_{nullptr}; | ||||
|   button::Button *restart_module_button_{nullptr}; | ||||
|   button::Button *factory_reset_button_{nullptr}; | ||||
| #endif | ||||
|   void set_min_max_distances_timeout(uint32_t max_gate_distance, uint32_t min_gate_distance, uint32_t timeout); | ||||
|   void set_gate_threshold(uint8_t gate); | ||||
|   void set_reg_value(uint16_t reg, uint16_t value); | ||||
|   uint8_t set_config_mode(bool enable); | ||||
|   void set_system_mode(uint16_t mode); | ||||
|   void ld2420_restart(); | ||||
|  | ||||
|  protected: | ||||
|   struct CmdReplyT { | ||||
|     uint8_t command; | ||||
|     uint8_t status; | ||||
|     uint32_t data[4]; | ||||
|     uint8_t length; | ||||
|     uint16_t error; | ||||
|     volatile bool ack; | ||||
|   }; | ||||
|  | ||||
|   int get_firmware_int_(const char *version_string); | ||||
|   void get_firmware_version_(); | ||||
|   int get_gate_threshold_(uint8_t gate); | ||||
|   void get_reg_value_(uint16_t reg); | ||||
|   int get_min_max_distances_timeout_(); | ||||
|   uint16_t get_mode_() { return this->system_mode_; }; | ||||
|   void set_mode_(uint16_t mode) { this->system_mode_ = mode; }; | ||||
|   bool get_presence_() { return this->presence_; }; | ||||
|   void set_presence_(bool presence) { this->presence_ = presence; }; | ||||
|   uint16_t get_distance_() { return this->distance_; }; | ||||
|   void set_distance_(uint16_t distance) { this->distance_ = distance; }; | ||||
|   bool get_cmd_active_() { return this->cmd_active_; }; | ||||
|   void set_cmd_active_(bool active) { this->cmd_active_ = active; }; | ||||
|   void handle_simple_mode_(const uint8_t *inbuf, int len); | ||||
|   void handle_energy_mode_(uint8_t *buffer, int len); | ||||
|   void handle_ack_data_(uint8_t *buffer, int len); | ||||
|   void readline_(int rx_data, uint8_t *buffer, int len); | ||||
|   void set_calibration_(bool state) { this->calibration_ = state; }; | ||||
|   bool get_calibration_() { return this->calibration_; }; | ||||
|  | ||||
| #ifdef USE_NUMBER | ||||
|   number::Number *gate_timeout_number_{nullptr}; | ||||
|   number::Number *gate_select_number_{nullptr}; | ||||
|   number::Number *min_gate_distance_number_{nullptr}; | ||||
|   number::Number *max_gate_distance_number_{nullptr}; | ||||
|   number::Number *gate_move_sensitivity_factor_number_{nullptr}; | ||||
|   number::Number *gate_still_sensitivity_factor_number_{nullptr}; | ||||
|   std::vector<number::Number *> gate_still_threshold_numbers_ = std::vector<number::Number *>(16); | ||||
|   std::vector<number::Number *> gate_move_threshold_numbers_ = std::vector<number::Number *>(16); | ||||
| #endif | ||||
|  | ||||
|   uint16_t gate_energy_[LD2420_TOTAL_GATES]; | ||||
|   CmdReplyT cmd_reply_; | ||||
|   uint32_t timeout_; | ||||
|   uint32_t max_distance_gate_; | ||||
|   uint32_t min_distance_gate_; | ||||
|   uint16_t system_mode_{CMD_SYSTEM_MODE_ENERGY}; | ||||
|   bool cmd_active_{false}; | ||||
|   char ld2420_firmware_ver_[8]; | ||||
|   bool presence_{false}; | ||||
|   bool calibration_{false}; | ||||
|   uint16_t distance_{0}; | ||||
|   uint8_t config_checksum_{0}; | ||||
|   std::vector<LD2420Listener *> listeners_{}; | ||||
| }; | ||||
|  | ||||
| }  // namespace ld2420 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										183
									
								
								esphome/components/ld2420/number/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										183
									
								
								esphome/components/ld2420/number/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,183 @@ | ||||
| import esphome.codegen as cg | ||||
| from esphome.components import number | ||||
| import esphome.config_validation as cv | ||||
| from esphome.const import ( | ||||
|     CONF_ID, | ||||
|     DEVICE_CLASS_DISTANCE, | ||||
|     UNIT_SECOND, | ||||
|     ENTITY_CATEGORY_CONFIG, | ||||
|     ICON_MOTION_SENSOR, | ||||
|     ICON_TIMELAPSE, | ||||
|     ICON_SCALE, | ||||
| ) | ||||
| from .. import CONF_LD2420_ID, LD2420Component, ld2420_ns | ||||
|  | ||||
| LD2420TimeoutNumber = ld2420_ns.class_("LD2420TimeoutNumber", number.Number) | ||||
| LD2420MoveSensFactorNumber = ld2420_ns.class_( | ||||
|     "LD2420MoveSensFactorNumber", number.Number | ||||
| ) | ||||
| LD2420StillSensFactorNumber = ld2420_ns.class_( | ||||
|     "LD2420StillSensFactorNumber", number.Number | ||||
| ) | ||||
| LD2420MinDistanceNumber = ld2420_ns.class_("LD2420MinDistanceNumber", number.Number) | ||||
| LD2420MaxDistanceNumber = ld2420_ns.class_("LD2420MaxDistanceNumber", number.Number) | ||||
| LD2420GateSelectNumber = ld2420_ns.class_("LD2420GateSelectNumber", number.Number) | ||||
| LD2420MoveThresholdNumbers = ld2420_ns.class_( | ||||
|     "LD2420MoveThresholdNumbers", number.Number | ||||
| ) | ||||
| LD2420StillThresholdNumbers = ld2420_ns.class_( | ||||
|     "LD2420StillThresholdNumbers", number.Number | ||||
| ) | ||||
| CONF_MIN_GATE_DISTANCE = "min_gate_distance" | ||||
| CONF_MAX_GATE_DISTANCE = "max_gate_distance" | ||||
| CONF_STILL_THRESHOLD = "still_threshold" | ||||
| CONF_MOVE_THRESHOLD = "move_threshold" | ||||
| CONF_GATE_MOVE_SENSITIVITY = "gate_move_sensitivity" | ||||
| CONF_GATE_STILL_SENSITIVITY = "gate_still_sensitivity" | ||||
| CONF_GATE_SELECT = "gate_select" | ||||
| CONF_PRESENCE_TIMEOUT = "presence_timeout" | ||||
| GATE_GROUP = "gate_group" | ||||
| TIMEOUT_GROUP = "timeout_group" | ||||
|  | ||||
|  | ||||
| CONFIG_SCHEMA = cv.Schema( | ||||
|     { | ||||
|         cv.GenerateID(CONF_LD2420_ID): cv.use_id(LD2420Component), | ||||
|         cv.Inclusive(CONF_PRESENCE_TIMEOUT, TIMEOUT_GROUP): number.number_schema( | ||||
|             LD2420TimeoutNumber, | ||||
|             unit_of_measurement=UNIT_SECOND, | ||||
|             entity_category=ENTITY_CATEGORY_CONFIG, | ||||
|             icon=ICON_TIMELAPSE, | ||||
|         ), | ||||
|         cv.Inclusive(CONF_MIN_GATE_DISTANCE, TIMEOUT_GROUP): number.number_schema( | ||||
|             LD2420MinDistanceNumber, | ||||
|             device_class=DEVICE_CLASS_DISTANCE, | ||||
|             entity_category=ENTITY_CATEGORY_CONFIG, | ||||
|             icon=ICON_MOTION_SENSOR, | ||||
|         ), | ||||
|         cv.Inclusive(CONF_MAX_GATE_DISTANCE, TIMEOUT_GROUP): number.number_schema( | ||||
|             LD2420MaxDistanceNumber, | ||||
|             device_class=DEVICE_CLASS_DISTANCE, | ||||
|             entity_category=ENTITY_CATEGORY_CONFIG, | ||||
|             icon=ICON_MOTION_SENSOR, | ||||
|         ), | ||||
|         cv.Inclusive(CONF_GATE_SELECT, GATE_GROUP): number.number_schema( | ||||
|             LD2420GateSelectNumber, | ||||
|             device_class=DEVICE_CLASS_DISTANCE, | ||||
|             entity_category=ENTITY_CATEGORY_CONFIG, | ||||
|             icon=ICON_MOTION_SENSOR, | ||||
|         ), | ||||
|         cv.Inclusive(CONF_STILL_THRESHOLD, GATE_GROUP): number.number_schema( | ||||
|             LD2420StillThresholdNumbers, | ||||
|             entity_category=ENTITY_CATEGORY_CONFIG, | ||||
|             icon=ICON_MOTION_SENSOR, | ||||
|         ), | ||||
|         cv.Inclusive(CONF_MOVE_THRESHOLD, GATE_GROUP): number.number_schema( | ||||
|             LD2420MoveThresholdNumbers, | ||||
|             entity_category=ENTITY_CATEGORY_CONFIG, | ||||
|             icon=ICON_MOTION_SENSOR, | ||||
|         ), | ||||
|         cv.Optional(CONF_GATE_MOVE_SENSITIVITY): number.number_schema( | ||||
|             LD2420MoveSensFactorNumber, | ||||
|             device_class=DEVICE_CLASS_DISTANCE, | ||||
|             entity_category=ENTITY_CATEGORY_CONFIG, | ||||
|             icon=ICON_SCALE, | ||||
|         ), | ||||
|         cv.Optional(CONF_GATE_STILL_SENSITIVITY): number.number_schema( | ||||
|             LD2420StillSensFactorNumber, | ||||
|             device_class=DEVICE_CLASS_DISTANCE, | ||||
|             entity_category=ENTITY_CATEGORY_CONFIG, | ||||
|             icon=ICON_SCALE, | ||||
|         ), | ||||
|     } | ||||
| ) | ||||
| CONFIG_SCHEMA = CONFIG_SCHEMA.extend( | ||||
|     { | ||||
|         cv.Optional(f"gate_{x}"): ( | ||||
|             { | ||||
|                 cv.Required(CONF_MOVE_THRESHOLD): number.number_schema( | ||||
|                     LD2420MoveThresholdNumbers, | ||||
|                     entity_category=ENTITY_CATEGORY_CONFIG, | ||||
|                     icon=ICON_MOTION_SENSOR, | ||||
|                 ), | ||||
|                 cv.Required(CONF_STILL_THRESHOLD): number.number_schema( | ||||
|                     LD2420StillThresholdNumbers, | ||||
|                     entity_category=ENTITY_CATEGORY_CONFIG, | ||||
|                     icon=ICON_MOTION_SENSOR, | ||||
|                 ), | ||||
|             } | ||||
|         ) | ||||
|         for x in range(16) | ||||
|     } | ||||
| ) | ||||
|  | ||||
|  | ||||
| async def to_code(config): | ||||
|     LD2420_component = await cg.get_variable(config[CONF_LD2420_ID]) | ||||
|     if gate_timeout_config := config.get(CONF_PRESENCE_TIMEOUT): | ||||
|         n = await number.new_number( | ||||
|             gate_timeout_config, min_value=0, max_value=255, step=5 | ||||
|         ) | ||||
|         await cg.register_parented(n, config[CONF_LD2420_ID]) | ||||
|         cg.add(LD2420_component.set_gate_timeout_number(n)) | ||||
|     if min_distance_gate_config := config.get(CONF_MIN_GATE_DISTANCE): | ||||
|         n = await number.new_number( | ||||
|             min_distance_gate_config, min_value=0, max_value=15, step=1 | ||||
|         ) | ||||
|         await cg.register_parented(n, config[CONF_LD2420_ID]) | ||||
|         cg.add(LD2420_component.set_min_gate_distance_number(n)) | ||||
|     if max_distance_gate_config := config.get(CONF_MAX_GATE_DISTANCE): | ||||
|         n = await number.new_number( | ||||
|             max_distance_gate_config, min_value=1, max_value=15, step=1 | ||||
|         ) | ||||
|         await cg.register_parented(n, config[CONF_LD2420_ID]) | ||||
|         cg.add(LD2420_component.set_max_gate_distance_number(n)) | ||||
|     if gate_move_sensitivity_config := config.get(CONF_GATE_MOVE_SENSITIVITY): | ||||
|         n = await number.new_number( | ||||
|             gate_move_sensitivity_config, min_value=0.05, max_value=1, step=0.025 | ||||
|         ) | ||||
|         await cg.register_parented(n, config[CONF_LD2420_ID]) | ||||
|         cg.add(LD2420_component.set_gate_move_sensitivity_factor_number(n)) | ||||
|     if gate_still_sensitivity_config := config.get(CONF_GATE_STILL_SENSITIVITY): | ||||
|         n = await number.new_number( | ||||
|             gate_still_sensitivity_config, min_value=0.05, max_value=1, step=0.025 | ||||
|         ) | ||||
|         await cg.register_parented(n, config[CONF_LD2420_ID]) | ||||
|         cg.add(LD2420_component.set_gate_still_sensitivity_factor_number(n)) | ||||
|     if config.get(CONF_GATE_SELECT): | ||||
|         if gate_number := config.get(CONF_GATE_SELECT): | ||||
|             n = await number.new_number(gate_number, min_value=0, max_value=15, step=1) | ||||
|             await cg.register_parented(n, config[CONF_LD2420_ID]) | ||||
|             cg.add(LD2420_component.set_gate_select_number(n)) | ||||
|         if gate_still_threshold := config.get(CONF_STILL_THRESHOLD): | ||||
|             n = cg.new_Pvariable(gate_still_threshold[CONF_ID]) | ||||
|             await number.register_number( | ||||
|                 n, gate_still_threshold, min_value=0, max_value=65535, step=25 | ||||
|             ) | ||||
|             await cg.register_parented(n, config[CONF_LD2420_ID]) | ||||
|             cg.add(LD2420_component.set_gate_still_threshold_numbers(0, n)) | ||||
|         if gate_move_threshold := config.get(CONF_MOVE_THRESHOLD): | ||||
|             n = cg.new_Pvariable(gate_move_threshold[CONF_ID]) | ||||
|             await number.register_number( | ||||
|                 n, gate_move_threshold, min_value=0, max_value=65535, step=25 | ||||
|             ) | ||||
|             await cg.register_parented(n, config[CONF_LD2420_ID]) | ||||
|             cg.add(LD2420_component.set_gate_move_threshold_numbers(0, n)) | ||||
|     else: | ||||
|         for x in range(16): | ||||
|             if gate_conf := config.get(f"gate_{x}"): | ||||
|                 move_config = gate_conf[CONF_MOVE_THRESHOLD] | ||||
|                 n = cg.new_Pvariable(move_config[CONF_ID], x) | ||||
|                 await number.register_number( | ||||
|                     n, move_config, min_value=0, max_value=65535, step=25 | ||||
|                 ) | ||||
|                 await cg.register_parented(n, config[CONF_LD2420_ID]) | ||||
|                 cg.add(LD2420_component.set_gate_move_threshold_numbers(x, n)) | ||||
|  | ||||
|                 still_config = gate_conf[CONF_STILL_THRESHOLD] | ||||
|                 n = cg.new_Pvariable(still_config[CONF_ID], x) | ||||
|                 await number.register_number( | ||||
|                     n, still_config, min_value=0, max_value=65535, step=25 | ||||
|                 ) | ||||
|                 await cg.register_parented(n, config[CONF_LD2420_ID]) | ||||
|                 cg.add(LD2420_component.set_gate_still_threshold_numbers(x, n)) | ||||
							
								
								
									
										73
									
								
								esphome/components/ld2420/number/gate_config_number.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								esphome/components/ld2420/number/gate_config_number.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,73 @@ | ||||
| #include "gate_config_number.h" | ||||
| #include "esphome/core/helpers.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| static const char *const TAG = "LD2420.number"; | ||||
|  | ||||
| namespace esphome { | ||||
| namespace ld2420 { | ||||
|  | ||||
| void LD2420TimeoutNumber::control(float timeout) { | ||||
|   this->publish_state(timeout); | ||||
|   this->parent_->new_config.timeout = timeout; | ||||
| } | ||||
|  | ||||
| void LD2420MinDistanceNumber::control(float min_gate) { | ||||
|   if ((uint16_t) min_gate > this->parent_->new_config.max_gate) { | ||||
|     min_gate = this->parent_->get_min_gate_distance_value(); | ||||
|   } else { | ||||
|     this->parent_->new_config.min_gate = (uint16_t) min_gate; | ||||
|   } | ||||
|   this->publish_state(min_gate); | ||||
| } | ||||
|  | ||||
| void LD2420MaxDistanceNumber::control(float max_gate) { | ||||
|   if ((uint16_t) max_gate < this->parent_->new_config.min_gate) { | ||||
|     max_gate = this->parent_->get_max_gate_distance_value(); | ||||
|   } else { | ||||
|     this->parent_->new_config.max_gate = (uint16_t) max_gate; | ||||
|   } | ||||
|   this->publish_state(max_gate); | ||||
| } | ||||
|  | ||||
| void LD2420GateSelectNumber::control(float gate_select) { | ||||
|   const uint8_t gate = (uint8_t) gate_select; | ||||
|   this->publish_state(gate_select); | ||||
|   this->parent_->publish_gate_move_threshold(gate); | ||||
|   this->parent_->publish_gate_still_threshold(gate); | ||||
| } | ||||
|  | ||||
| void LD2420MoveSensFactorNumber::control(float move_factor) { | ||||
|   this->publish_state(move_factor); | ||||
|   this->parent_->gate_move_sensitivity_factor = move_factor; | ||||
| } | ||||
|  | ||||
| void LD2420StillSensFactorNumber::control(float still_factor) { | ||||
|   this->publish_state(still_factor); | ||||
|   this->parent_->gate_still_sensitivity_factor = still_factor; | ||||
| } | ||||
|  | ||||
| LD2420MoveThresholdNumbers::LD2420MoveThresholdNumbers(uint8_t gate) : gate_(gate) {} | ||||
|  | ||||
| void LD2420MoveThresholdNumbers::control(float move_threshold) { | ||||
|   this->publish_state(move_threshold); | ||||
|   if (!this->parent_->is_gate_select()) { | ||||
|     this->parent_->new_config.move_thresh[this->gate_] = move_threshold; | ||||
|   } else { | ||||
|     this->parent_->new_config.move_thresh[this->parent_->get_gate_select_value()] = move_threshold; | ||||
|   } | ||||
| } | ||||
|  | ||||
| LD2420StillThresholdNumbers::LD2420StillThresholdNumbers(uint8_t gate) : gate_(gate) {} | ||||
|  | ||||
| void LD2420StillThresholdNumbers::control(float still_threshold) { | ||||
|   this->publish_state(still_threshold); | ||||
|   if (!this->parent_->is_gate_select()) { | ||||
|     this->parent_->new_config.still_thresh[this->gate_] = still_threshold; | ||||
|   } else { | ||||
|     this->parent_->new_config.still_thresh[this->parent_->get_gate_select_value()] = still_threshold; | ||||
|   } | ||||
| } | ||||
|  | ||||
| }  // namespace ld2420 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										78
									
								
								esphome/components/ld2420/number/gate_config_number.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								esphome/components/ld2420/number/gate_config_number.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,78 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/components/number/number.h" | ||||
| #include "../ld2420.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace ld2420 { | ||||
|  | ||||
| class LD2420TimeoutNumber : public number::Number, public Parented<LD2420Component> { | ||||
|  public: | ||||
|   LD2420TimeoutNumber() = default; | ||||
|  | ||||
|  protected: | ||||
|   void control(float timeout) override; | ||||
| }; | ||||
|  | ||||
| class LD2420MinDistanceNumber : public number::Number, public Parented<LD2420Component> { | ||||
|  public: | ||||
|   LD2420MinDistanceNumber() = default; | ||||
|  | ||||
|  protected: | ||||
|   void control(float min_gate) override; | ||||
| }; | ||||
|  | ||||
| class LD2420MaxDistanceNumber : public number::Number, public Parented<LD2420Component> { | ||||
|  public: | ||||
|   LD2420MaxDistanceNumber() = default; | ||||
|  | ||||
|  protected: | ||||
|   void control(float max_gate) override; | ||||
| }; | ||||
|  | ||||
| class LD2420GateSelectNumber : public number::Number, public Parented<LD2420Component> { | ||||
|  public: | ||||
|   LD2420GateSelectNumber() = default; | ||||
|  | ||||
|  protected: | ||||
|   void control(float gate_select) override; | ||||
| }; | ||||
|  | ||||
| class LD2420MoveSensFactorNumber : public number::Number, public Parented<LD2420Component> { | ||||
|  public: | ||||
|   LD2420MoveSensFactorNumber() = default; | ||||
|  | ||||
|  protected: | ||||
|   void control(float move_factor) override; | ||||
| }; | ||||
|  | ||||
| class LD2420StillSensFactorNumber : public number::Number, public Parented<LD2420Component> { | ||||
|  public: | ||||
|   LD2420StillSensFactorNumber() = default; | ||||
|  | ||||
|  protected: | ||||
|   void control(float still_factor) override; | ||||
| }; | ||||
|  | ||||
| class LD2420StillThresholdNumbers : public number::Number, public Parented<LD2420Component> { | ||||
|  public: | ||||
|   LD2420StillThresholdNumbers() = default; | ||||
|   LD2420StillThresholdNumbers(uint8_t gate); | ||||
|  | ||||
|  protected: | ||||
|   uint8_t gate_; | ||||
|   void control(float still_threshold) override; | ||||
| }; | ||||
|  | ||||
| class LD2420MoveThresholdNumbers : public number::Number, public Parented<LD2420Component> { | ||||
|  public: | ||||
|   LD2420MoveThresholdNumbers() = default; | ||||
|   LD2420MoveThresholdNumbers(uint8_t gate); | ||||
|  | ||||
|  protected: | ||||
|   uint8_t gate_; | ||||
|   void control(float move_threshold) override; | ||||
| }; | ||||
|  | ||||
| }  // namespace ld2420 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										33
									
								
								esphome/components/ld2420/select/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								esphome/components/ld2420/select/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | ||||
| 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_LD2420_ID, LD2420Component, ld2420_ns | ||||
|  | ||||
| CONF_OPERATING_MODE = "operating_mode" | ||||
| CONF_SELECTS = [ | ||||
|     "Normal", | ||||
|     "Calibrate", | ||||
|     "Simple", | ||||
| ] | ||||
|  | ||||
| LD2420Select = ld2420_ns.class_("LD2420Select", cg.Component) | ||||
|  | ||||
| CONFIG_SCHEMA = { | ||||
|     cv.GenerateID(CONF_LD2420_ID): cv.use_id(LD2420Component), | ||||
|     cv.Required(CONF_OPERATING_MODE): select.select_schema( | ||||
|         LD2420Select, | ||||
|         entity_category=ENTITY_CATEGORY_CONFIG, | ||||
|     ), | ||||
| } | ||||
|  | ||||
|  | ||||
| async def to_code(config): | ||||
|     LD2420_component = await cg.get_variable(config[CONF_LD2420_ID]) | ||||
|     if operating_mode_config := config.get(CONF_OPERATING_MODE): | ||||
|         sel = await select.new_select( | ||||
|             operating_mode_config, | ||||
|             options=[CONF_SELECTS], | ||||
|         ) | ||||
|         await cg.register_parented(sel, config[CONF_LD2420_ID]) | ||||
|         cg.add(LD2420_component.set_operating_mode_select(sel)) | ||||
							
								
								
									
										16
									
								
								esphome/components/ld2420/select/operating_mode_select.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								esphome/components/ld2420/select/operating_mode_select.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| #include "operating_mode_select.h" | ||||
| #include "esphome/core/helpers.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace ld2420 { | ||||
|  | ||||
| static const char *const TAG = "LD2420.select"; | ||||
|  | ||||
| void LD2420Select::control(const std::string &value) { | ||||
|   this->publish_state(value); | ||||
|   this->parent_->set_operating_mode(value); | ||||
| } | ||||
|  | ||||
| }  // namespace ld2420 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										18
									
								
								esphome/components/ld2420/select/operating_mode_select.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								esphome/components/ld2420/select/operating_mode_select.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "../ld2420.h" | ||||
| #include "esphome/components/select/select.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace ld2420 { | ||||
|  | ||||
| class LD2420Select : public Component, public select::Select, public Parented<LD2420Component> { | ||||
|  public: | ||||
|   LD2420Select() = default; | ||||
|  | ||||
|  protected: | ||||
|   void control(const std::string &value) override; | ||||
| }; | ||||
|  | ||||
| }  // namespace ld2420 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										35
									
								
								esphome/components/ld2420/sensor/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								esphome/components/ld2420/sensor/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import sensor | ||||
| from esphome.const import CONF_ID, DEVICE_CLASS_DISTANCE, UNIT_CENTIMETER | ||||
| from .. import ld2420_ns, LD2420Component, CONF_LD2420_ID | ||||
|  | ||||
| LD2420Sensor = ld2420_ns.class_("LD2420Sensor", sensor.Sensor, cg.Component) | ||||
|  | ||||
| CONF_MOVING_DISTANCE = "moving_distance" | ||||
| CONF_GATE_ENERGY = "gate_energy" | ||||
|  | ||||
| CONFIG_SCHEMA = cv.All( | ||||
|     cv.COMPONENT_SCHEMA.extend( | ||||
|         { | ||||
|             cv.GenerateID(): cv.declare_id(LD2420Sensor), | ||||
|             cv.GenerateID(CONF_LD2420_ID): cv.use_id(LD2420Component), | ||||
|             cv.Optional(CONF_MOVING_DISTANCE): sensor.sensor_schema( | ||||
|                 device_class=DEVICE_CLASS_DISTANCE, unit_of_measurement=UNIT_CENTIMETER | ||||
|             ), | ||||
|         } | ||||
|     ), | ||||
| ) | ||||
|  | ||||
|  | ||||
| async def to_code(config): | ||||
|     var = cg.new_Pvariable(config[CONF_ID]) | ||||
|     await cg.register_component(var, config) | ||||
|     if CONF_MOVING_DISTANCE in config: | ||||
|         sens = await sensor.new_sensor(config[CONF_MOVING_DISTANCE]) | ||||
|         cg.add(var.set_distance_sensor(sens)) | ||||
|     if CONF_GATE_ENERGY in config: | ||||
|         sens = await sensor.new_sensor(config[CONF_GATE_ENERGY]) | ||||
|         cg.add(var.set_energy_sensor(sens)) | ||||
|     ld2420 = await cg.get_variable(config[CONF_LD2420_ID]) | ||||
|     cg.add(ld2420.register_listener(var)) | ||||
							
								
								
									
										16
									
								
								esphome/components/ld2420/sensor/ld2420_sensor.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								esphome/components/ld2420/sensor/ld2420_sensor.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| #include "ld2420_sensor.h" | ||||
| #include "esphome/core/helpers.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace ld2420 { | ||||
|  | ||||
| static const char *const TAG = "LD2420.sensor"; | ||||
|  | ||||
| void LD2420Sensor::dump_config() { | ||||
|   ESP_LOGCONFIG(TAG, "LD2420 Sensor:"); | ||||
|   LOG_SENSOR("  ", "Distance", this->distance_sensor_); | ||||
| } | ||||
|  | ||||
| }  // namespace ld2420 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										34
									
								
								esphome/components/ld2420/sensor/ld2420_sensor.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								esphome/components/ld2420/sensor/ld2420_sensor.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "../ld2420.h" | ||||
| #include "esphome/components/sensor/sensor.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace ld2420 { | ||||
|  | ||||
| class LD2420Sensor : public LD2420Listener, public Component, sensor::Sensor { | ||||
|  public: | ||||
|   void dump_config() override; | ||||
|   void set_distance_sensor(sensor::Sensor *sensor) { this->distance_sensor_ = sensor; } | ||||
|   void on_distance(uint16_t distance) override { | ||||
|     if (this->distance_sensor_ != nullptr) { | ||||
|       if (this->distance_sensor_->get_state() != distance) { | ||||
|         this->distance_sensor_->publish_state(distance); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|   void on_energy(uint16_t *gate_energy, size_t size) override { | ||||
|     for (size_t active = 0; active < size; active++) { | ||||
|       if (this->energy_sensors_[active] != nullptr) { | ||||
|         this->energy_sensors_[active]->publish_state(gate_energy[active]); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|  protected: | ||||
|   sensor::Sensor *distance_sensor_{nullptr}; | ||||
|   std::vector<sensor::Sensor *> energy_sensors_ = std::vector<sensor::Sensor *>(LD2420_TOTAL_GATES); | ||||
| }; | ||||
|  | ||||
| }  // namespace ld2420 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										38
									
								
								esphome/components/ld2420/text_sensor/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								esphome/components/ld2420/text_sensor/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | ||||
| import esphome.codegen as cg | ||||
| from esphome.components import text_sensor | ||||
| import esphome.config_validation as cv | ||||
| from esphome.const import ( | ||||
|     CONF_ID, | ||||
|     ENTITY_CATEGORY_DIAGNOSTIC, | ||||
|     ICON_CHIP, | ||||
| ) | ||||
|  | ||||
| from .. import ld2420_ns, LD2420Component, CONF_LD2420_ID | ||||
|  | ||||
| LD2420TextSensor = ld2420_ns.class_( | ||||
|     "LD2420TextSensor", text_sensor.TextSensor, cg.Component | ||||
| ) | ||||
|  | ||||
| CONF_FW_VERSION = "fw_version" | ||||
|  | ||||
| CONFIG_SCHEMA = cv.All( | ||||
|     cv.COMPONENT_SCHEMA.extend( | ||||
|         { | ||||
|             cv.GenerateID(): cv.declare_id(LD2420TextSensor), | ||||
|             cv.GenerateID(CONF_LD2420_ID): cv.use_id(LD2420Component), | ||||
|             cv.Optional(CONF_FW_VERSION): text_sensor.text_sensor_schema( | ||||
|                 entity_category=ENTITY_CATEGORY_DIAGNOSTIC, icon=ICON_CHIP | ||||
|             ), | ||||
|         } | ||||
|     ), | ||||
| ) | ||||
|  | ||||
|  | ||||
| async def to_code(config): | ||||
|     var = cg.new_Pvariable(config[CONF_ID]) | ||||
|     await cg.register_component(var, config) | ||||
|     if CONF_FW_VERSION in config: | ||||
|         sens = await text_sensor.new_text_sensor(config[CONF_FW_VERSION]) | ||||
|         cg.add(var.set_fw_version_text_sensor(sens)) | ||||
|     ld2420 = await cg.get_variable(config[CONF_LD2420_ID]) | ||||
|     cg.add(ld2420.register_listener(var)) | ||||
							
								
								
									
										16
									
								
								esphome/components/ld2420/text_sensor/text_sensor.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								esphome/components/ld2420/text_sensor/text_sensor.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| #include "text_sensor.h" | ||||
| #include "esphome/core/helpers.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace ld2420 { | ||||
|  | ||||
| static const char *const TAG = "LD2420.text_sensor"; | ||||
|  | ||||
| void LD2420TextSensor::dump_config() { | ||||
|   ESP_LOGCONFIG(TAG, "LD2420 TextSensor:"); | ||||
|   LOG_TEXT_SENSOR("  ", "Firmware", this->fw_version_text_sensor_); | ||||
| } | ||||
|  | ||||
| }  // namespace ld2420 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										24
									
								
								esphome/components/ld2420/text_sensor/text_sensor.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								esphome/components/ld2420/text_sensor/text_sensor.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "../ld2420.h" | ||||
| #include "esphome/components/text_sensor/text_sensor.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace ld2420 { | ||||
|  | ||||
| class LD2420TextSensor : public LD2420Listener, public Component, text_sensor::TextSensor { | ||||
|  public: | ||||
|   void dump_config() override; | ||||
|   void set_fw_version_text_sensor(text_sensor::TextSensor *tsensor) { this->fw_version_text_sensor_ = tsensor; }; | ||||
|   void on_fw_version(std::string &fw) override { | ||||
|     if (this->fw_version_text_sensor_ != nullptr) { | ||||
|       this->fw_version_text_sensor_->publish_state(fw); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|  protected: | ||||
|   text_sensor::TextSensor *fw_version_text_sensor_{nullptr}; | ||||
| }; | ||||
|  | ||||
| }  // namespace ld2420 | ||||
| }  // namespace esphome | ||||
		Reference in New Issue
	
	Block a user