diff --git a/homeassistant/esphome/ld2410/mw22.yaml b/homeassistant/esphome/ld2410/mw22.yaml new file mode 100644 index 0000000..2f5f714 --- /dev/null +++ b/homeassistant/esphome/ld2410/mw22.yaml @@ -0,0 +1,261 @@ +# https://github.com/esphome/esphome/pull/4434 + +substitutions: + pretty: MW22 + RXPIN: GPIO1 + TXPIN: GPIO3 + out_pin: GPIO16 + +esphome: + name: mw22 + comment: LD2410C + friendly_name: MW22 + +esp32: + board: mhetesp32minikit + framework: + type: arduino + +mqtt: + broker: 192.168.10.102 + +ota: + password: !secret ota + +wifi: + ssid: !secret wifi_ssid + password: !secret wifi_password + + ap: + ssid: !secret ap_ssid + password: !secret ap_password + +web_server: + port: 80 + + +external_components: + source: github://regevbr/esphome@ld2410 + components: [ld2410] + +api: + reboot_timeout: 0s + services: + - service: set_ld2410_bluetooth_password + variables: + password: string + then: + - bluetooth_password.set: + id: ld2410_comp + password: !lambda 'return password;' + +uart: + id: ld2410_uart + tx_pin: ${TXPIN} + rx_pin: ${RXPIN} + baud_rate: 115200 #256000 + parity: NONE + stop_bits: 1 + +ld2410: + uart_id: ld2410_uart + throttle: 1500ms + id: ld2410_comp + +select: + - platform: ld2410 + distance_resolution: + name: distance resolution + baud_rate: + name: baud rate + light_function: + name: light function + out_pin_level: + name: out pin level + +button: + - platform: ld2410 + factory_reset: + name: "factory reset" + restart: + name: "restart" + query_params: + name: query params + +number: + - platform: ld2410 + timeout: + name: timeout + max_move_distance_gate: + name: max move distance gate + max_still_distance_gate: + name: max still distance gate + g0: + move_threshold: + name: g0 move threshold + still_threshold: + name: g0 still threshold + g1: + move_threshold: + name: g1 move threshold + still_threshold: + name: g1 still threshold + g2: + move_threshold: + name: g2 move threshold + still_threshold: + name: g2 still threshold + g3: + move_threshold: + name: g3 move threshold + still_threshold: + name: g3 still threshold + g4: + move_threshold: + name: g4 move threshold + still_threshold: + name: g4 still threshold + g5: + move_threshold: + name: g5 move threshold + still_threshold: + name: g5 still threshold + g6: + move_threshold: + name: g6 move threshold + still_threshold: + name: g6 still threshold + g7: + move_threshold: + name: g7 move threshold + still_threshold: + name: g7 still threshold + g8: + move_threshold: + name: g8 move threshold + still_threshold: + name: g8 still threshold + light_threshold: + name: light threshold + +text_sensor: + - platform: ld2410 + version: + name: "presenece sensor version" + mac_address: + name: "presenece sensor mac address" + +switch: + - platform: ld2410 + engineering_mode: + name: "engineering mode" + bluetooth: + name: control Bluetooth + +binary_sensor: + - platform: ld2410 + has_target: + name: "presence" + has_moving_target: + name: "movement" + has_still_target: + name: "still" + out_pin_presence_status: + name: out pin presence status + - platform: gpio + pin: ${out_pin} + name: ${pretty} Presence2 + device_class: presence + +sensor: + - platform: ld2410 + moving_distance: + name: "Moving distance" + still_distance: + name: "Still Distance" + moving_energy: + name: "Move Energy" + still_energy: + name: "Still Energy" + detection_distance: + name: "Distance Detection" + g0: + move_energy: + name: g0 move energy + still_energy: + name: g0 still energy + g1: + move_energy: + name: g1 move energy + still_energy: + name: g1 still energy + g2: + move_energy: + name: g2 move energy + still_energy: + name: g2 still energy + g3: + move_energy: + name: g3 move energy + still_energy: + name: g3 still energy + g4: + move_energy: + name: g4 move energy + still_energy: + name: g4 still energy + g5: + move_energy: + name: g5 move energy + still_energy: + name: g5 still energy + g6: + move_energy: + name: g6 move energy + still_energy: + name: g6 still energy + g7: + move_energy: + name: g7 move energy + still_energy: + name: g7 still energy + g8: + move_energy: + name: g8 move energy + still_energy: + name: g8 still energy + light: + name: light + + - platform: wifi_signal + name: WiFi Signal + id: wifi_signal_db + update_interval: 60s + entity_category: "diagnostic" + - platform: copy # Reports the WiFi signal strength in % + source_id: wifi_signal_db + name: "WiFi Signal Percent" + filters: + - lambda: return min(max(2 * (x + 100.0), 0.0), 100.0); + unit_of_measurement: "%" + entity_category: "diagnostic" + - platform: uptime + name: Uptime + - platform: template + id: sys_esp_temperature + name: ESP Temperature + lambda: return temperatureRead(); + unit_of_measurement: °C + device_class: TEMPERATURE + update_interval: 45s + entity_category: "diagnostic" + - platform: template + id: esp_memory + icon: mdi:memory + name: ESP Free Memory + lambda: return heap_caps_get_free_size(MALLOC_CAP_INTERNAL) / 1024; + unit_of_measurement: 'kB' + state_class: measurement + entity_category: "diagnostic" + update_interval: 60s + diff --git a/homeassistant/esphome/ld2450/ld2410_uart.h b/homeassistant/esphome/ld2450/ld2410_uart.h new file mode 100644 index 0000000..53941bd --- /dev/null +++ b/homeassistant/esphome/ld2450/ld2410_uart.h @@ -0,0 +1,280 @@ +#include "esphome.h" + +#define CHECK_BIT(var, pos) (((var) >> (pos)) & 1) + +class LD2410 : public PollingComponent, public UARTDevice +{ +public: + LD2410(UARTComponent *parent) : UARTDevice(parent) {} + + BinarySensor *hasTarget = new BinarySensor(); + BinarySensor *hasMovingTarget = new BinarySensor(); + BinarySensor *hasStillTarget = new BinarySensor(); + BinarySensor *lastCommandSuccess = new BinarySensor(); + Sensor *movingTargetDistance = new Sensor(); + Sensor *movingTargetEnergy = new Sensor(); + Sensor *stillTargetDistance = new Sensor(); + Sensor *stillTargetEnergy = new Sensor(); + Sensor *detectDistance = new Sensor(); + + Number *maxMovingDistanceRange; + Number *maxStillDistanceRange; + int movingSensitivities[9] = {0}; + int stillSensitivities[9] = {0}; + Number *noneDuration; + + long lastPeriodicMillis = millis(); + + void setNumbers(Number *maxMovingDistanceRange_, Number *maxStillDistanceRange_, Number *noneDuration_){ + maxMovingDistanceRange = maxMovingDistanceRange_; + maxStillDistanceRange = maxStillDistanceRange_; + noneDuration = noneDuration_; + } + + void sendCommand(char *commandStr, char *commandValue, int commandValueLen) + { + lastCommandSuccess->publish_state(false); + // frame start bytes + write_byte(0xFD); + write_byte(0xFC); + write_byte(0xFB); + write_byte(0xFA); + // length bytes + int len = 2; + if (commandValue != nullptr) + len += commandValueLen; + write_byte(lowByte(len)); + write_byte(highByte(len)); + // command string bytes + write_byte(commandStr[0]); + write_byte(commandStr[1]); + // command value bytes + if (commandValue != nullptr) + { + for (int i = 0; i < commandValueLen; i++) + { + write_byte(commandValue[i]); + } + } + // frame end bytes + write_byte(0x04); + write_byte(0x03); + write_byte(0x02); + write_byte(0x01); + delay(50); + } + + int twoByteToInt(char firstByte, char secondByte) + { + return (int16_t)(secondByte << 8) + firstByte; + } + + void handlePeriodicData(char *buffer, int len) + { + if (len < 12) + return; // 4 frame start bytes + 2 length bytes + 1 data end byte + 1 crc byte + 4 frame end bytes + if (buffer[0] != 0xF4 || buffer[1] != 0xF3 || buffer[2] != 0xF2 || buffer[3] != 0xF1) + return; // check 4 frame start bytes + if (buffer[7] != 0xAA || buffer[len - 6] != 0x55 || buffer[len - 5] != 0x00) + return; // data head=0xAA, data end=0x55, crc=0x00 + /* + Data Type: 6th byte + 0x01: Engineering mode + 0x02: Normal mode + */ + char dataType = buffer[5]; + /* + Target states: 9th byte + 0x00 = No target + 0x01 = Moving targets + 0x02 = Still targets + 0x03 = Moving+Still targets + */ + char stateByte = buffer[8]; + hasTarget->publish_state(stateByte != 0x00); + /* + Reduce data update rate to prevent home assistant database size glow fast + */ + long currentMillis = millis(); + if (currentMillis - lastPeriodicMillis < 1000) + return; + lastPeriodicMillis = currentMillis; + + hasMovingTarget->publish_state(CHECK_BIT(stateByte, 0)); + hasStillTarget->publish_state(CHECK_BIT(stateByte, 1)); + + /* + Moving target distance: 10~11th bytes + Moving target energy: 12th byte + Still target distance: 13~14th bytes + Still target energy: 15th byte + Detect distance: 16~17th bytes + */ + int newMovingTargetDistance = twoByteToInt(buffer[9], buffer[10]); + if (movingTargetDistance->get_state() != newMovingTargetDistance) + movingTargetDistance->publish_state(newMovingTargetDistance); + int newMovingTargetEnergy = buffer[11]; + if (movingTargetEnergy->get_state() != newMovingTargetEnergy) + movingTargetEnergy->publish_state(newMovingTargetEnergy); + int newStillTargetDistance = twoByteToInt(buffer[12], buffer[13]); + if (stillTargetDistance->get_state() != newStillTargetDistance) + stillTargetDistance->publish_state(newStillTargetDistance); + int newStillTargetEnergy = buffer[14]; + if (stillTargetEnergy->get_state() != newStillTargetEnergy) + stillTargetEnergy->publish_state(buffer[14]); + int newDetectDistance = twoByteToInt(buffer[15], buffer[16]); + if (detectDistance->get_state() != newDetectDistance) + detectDistance->publish_state(newDetectDistance); + if (dataType == 0x01) + { // engineering mode + // todo: support engineering mode data + } + } + + void handleACKData(char *buffer, int len) + { + if (len < 10) + return; + if (buffer[0] != 0xFD || buffer[1] != 0xFC || buffer[2] != 0xFB || buffer[3] != 0xFA) + return; // check 4 frame start bytes + if (buffer[7] != 0x01) + return; + if (twoByteToInt(buffer[8], buffer[9]) != 0x00) + { + lastCommandSuccess->publish_state(false); + return; + } + lastCommandSuccess->publish_state(true); + switch (buffer[6]) + { + case 0x61: // Query parameters response + { + if (buffer[10] != 0xAA) + return; // value head=0xAA + /* + Moving distance range: 13th byte + Still distance range: 14th byte + */ + maxMovingDistanceRange->publish_state(buffer[12]); + maxStillDistanceRange->publish_state(buffer[13]); + /* + Moving Sensitivities: 15~23th bytes + Still Sensitivities: 24~32th bytes + */ + for (int i = 0; i < 9; i++) + { + movingSensitivities[i] = buffer[14 + i]; + } + for (int i = 0; i < 9; i++) + { + stillSensitivities[i] = buffer[23 + i]; + } + /* + None Duration: 33~34th bytes + */ + noneDuration->publish_state(twoByteToInt(buffer[32], buffer[33])); + } + break; + default: + break; + } + } + + void readline(int readch, char *buffer, int len) + { + static int pos = 0; + + if (readch >= 0) + { + if (pos < len - 1) + { + buffer[pos++] = readch; + buffer[pos] = 0; + } + else + { + pos = 0; + } + if (pos >= 4) + { + if (buffer[pos - 4] == 0xF8 && buffer[pos - 3] == 0xF7 && buffer[pos - 2] == 0xF6 && buffer[pos - 1] == 0xF5) + { + handlePeriodicData(buffer, pos); + pos = 0; // Reset position index ready for next time + } + else if (buffer[pos - 4] == 0x04 && buffer[pos - 3] == 0x03 && buffer[pos - 2] == 0x02 && buffer[pos - 1] == 0x01) + { + handleACKData(buffer, pos); + pos = 0; // Reset position index ready for next time + } + } + } + return; + } + + void setConfigMode(bool enable) + { + char cmd[2] = {enable ? 0xFF : 0xFE, 0x00}; + char value[2] = {0x01, 0x00}; + sendCommand(cmd, enable ? value : nullptr, 2); + } + + void queryParameters() + { + char cmd_query[2] = {0x61, 0x00}; + sendCommand(cmd_query, nullptr, 0); + } + + void setup() override + { + set_update_interval(15000); + } + + void loop() override + { + const int max_line_length = 80; + static char buffer[max_line_length]; + while (available()) + { + readline(read(), buffer, max_line_length); + } + } + + void setEngineeringMode(bool enable) + { + char cmd[2] = {enable ? 0x62 : 0x63, 0x00}; + sendCommand(cmd, nullptr, 0); + } + + void setMaxDistancesAndNoneDuration(int maxMovingDistanceRange, int maxStillDistanceRange, int noneDuration) + { + char cmd[2] = {0x60, 0x00}; + char value[18] = {0x00, 0x00, lowByte(maxMovingDistanceRange), highByte(maxMovingDistanceRange), 0x00, 0x00, 0x01, 0x00, lowByte(maxStillDistanceRange), highByte(maxStillDistanceRange), 0x00, 0x00, 0x02, 0x00, lowByte(noneDuration), highByte(noneDuration), 0x00, 0x00}; + sendCommand(cmd, value, 18); + queryParameters(); + } + + void factoryReset() + { + char cmd[2] = {0xA2, 0x00}; + sendCommand(cmd, nullptr, 0); + } + + void reboot() + { + char cmd[2] = {0xA3, 0x00}; + sendCommand(cmd, nullptr, 0); + // not need to exit config mode because the ld2410 will reboot automatically + } + + void setBaudrate(int index) + { + char cmd[2] = {0xA1, 0x00}; + char value[2] = {index, 0x00}; + sendCommand(cmd, value, 2); + } + + void update() + { + } +}; diff --git a/homeassistant/esphome/ld2450/mw25-ld2450-esp32.yaml b/homeassistant/esphome/ld2450/mw25-ld2450-esp32.yaml new file mode 100644 index 0000000..df4571a --- /dev/null +++ b/homeassistant/esphome/ld2450/mw25-ld2450-esp32.yaml @@ -0,0 +1,189 @@ +substitutions: + devicename: mw25 + upper_devicename: "Hi-Link LD2450" + +esphome: + name: ${devicename} + comment: ${upper_devicename} + includes: + - ld2450_uart.h + platformio_options: + board_build.flash_mode: dio + board_build.extra_flags: + - "-DARDUINO_USB_CDC_ON_BOOT=0" + +esp32: + board: seeed_xiao_esp32c3 + framework: + type: arduino + + +wifi: + ssid: !secret wifi_ssid + password: !secret wifi_password +# reboot_timeout: 0s + ap: + password: "admin1234" + ap_timeout: 30s + +# Enable logging +logger: + baud_rate: 0 + +# Enable Home Assistant API +api: + +ota: + password: !secret ota + +web_server: + port: 80 + +mqtt: + broker: 192.168.10.102 + +uart: + id: uart_ld2450 + tx_pin: + number: GPIO21 + mode: + input: true + pullup: true + rx_pin: + number: GPIO20 + mode: + input: true + pullup: true + baud_rate: 256000 + parity: NONE + stop_bits: 1 +# debug: +# direction: BOTH +# dummy_receiver: false + +custom_component: + - lambda: |- + return {new LD2450(id(uart_ld2450))}; + components: + - id: ld2450 + +binary_sensor: + - platform: template + name: "Presence Detected" + device_class: occupancy + lambda: |- + if (id(target1).state > 0 + or id(target2).state > 0 + or id(target3).state > 0) { + return true; + } else { + return false; + } + +sensor: + - platform: custom + lambda: |- + auto uart_component = static_cast(ld2450); + return {uart_component->target1Resolution, uart_component->target1Speed, uart_component->target1X, uart_component->target1Y, + uart_component->target2Resolution, uart_component->target2Speed, uart_component->target2X, uart_component->target2Y, + uart_component->target3Resolution, uart_component->target3Speed, uart_component->target3X, uart_component->target3Y, + }; + sensors: + - name: "Target1 Resolution" + unit_of_measurement: "nm" + accuracy_decimals: 0 + icon: mdi:artboard + id: target1 + - name: "Target1 Speed" + unit_of_measurement: "cm/s" + accuracy_decimals: 0 + icon: mdi:speedometer + - name: "Target1 X" + unit_of_measurement: "cm" + accuracy_decimals: 0 + icon: mdi:map-marker-right + - name: "Target1 Y" + unit_of_measurement: "cm" + accuracy_decimals: 0 + icon: mdi:map-marker-down + - name: "Target2 Resolution" + unit_of_measurement: "nm" + accuracy_decimals: 0 + icon: mdi:artboard + id: target2 + - name: "Target2 Speed" + unit_of_measurement: "cm/s" + accuracy_decimals: 0 + icon: mdi:speedometer + - name: "Target2 X" + unit_of_measurement: "cm" + accuracy_decimals: 0 + icon: mdi:map-marker-right + - name: "Target2 Y" + unit_of_measurement: "cm" + accuracy_decimals: 0 + icon: mdi:map-marker-down + - name: "Target3 Resolution" + unit_of_measurement: "nm" + accuracy_decimals: 0 + icon: mdi:artboard + id: target3 + - name: "Target3 Speed" + unit_of_measurement: "cm/s" + accuracy_decimals: 0 + icon: mdi:speedometer + - name: "Target3 X" + unit_of_measurement: "cm" + accuracy_decimals: 0 + icon: mdi:map-marker-right + - name: "Target3 Y" + unit_of_measurement: "cm" + accuracy_decimals: 0 + icon: mdi:map-marker-down + - platform: template + name: "Targets Detected" + update_interval: 1s + lambda: |- + int num = 0; + if (id(target1).state > 0) { + num += 1; + } + if (id(target2).state > 0) { + num += 1; + } + if (id(target3).state > 0) { + num += 1; + } + return num; + - platform: wifi_signal + name: WiFi Signal + id: wifi_signal_db + update_interval: 60s + entity_category: "diagnostic" + - platform: copy # Reports the WiFi signal strength in % + source_id: wifi_signal_db + name: "WiFi Signal Percent" + filters: + - lambda: return min(max(2 * (x + 100.0), 0.0), 100.0); + unit_of_measurement: "%" + entity_category: "diagnostic" + - platform: uptime + name: Uptime + - platform: template + id: sys_esp_temperature + name: MW25 ESP Temperature + lambda: return temperatureRead(); + unit_of_measurement: °C + device_class: TEMPERATURE + update_interval: 45s + entity_category: "diagnostic" + - platform: template + id: esp_memory + icon: mdi:memory + name: MW25 ESP Free Memory + lambda: return heap_caps_get_free_size(MALLOC_CAP_INTERNAL) / 1024; + unit_of_measurement: 'kB' + state_class: measurement + entity_category: "diagnostic" + update_interval: 60s +