From 836b01cb5a5b558b67c8d18ddf024310e54f5b04 Mon Sep 17 00:00:00 2001 From: sen <126383355+screek-workshop@users.noreply.github.com> Date: Mon, 1 May 2023 21:30:23 +0800 Subject: [PATCH] add s2+cdc uart support --- .../screek-humen-dectet-1u.yaml | 0 .../screek-mod-components/ld2410/__init__.py | 0 .../ld2410/binary_sensor.py | 0 .../screek-mod-components/ld2410/ld2410.cpp | 0 .../screek-mod-components/ld2410/ld2410.h | 0 .../screek-mod-components/ld2410/sensor.py | 0 .../screek-humen-dectet-1u.yaml | 158 +++++++ .../screek-mod-components/ld2410/__init__.py | 158 +++++++ .../ld2410/binary_sensor.py | 36 ++ .../screek-mod-components/ld2410/ld2410.cpp | 416 ++++++++++++++++++ .../screek-mod-components/ld2410/ld2410.h | 158 +++++++ .../screek-mod-components/ld2410/sensor.py | 70 +++ .../screek-mod-components/uart/__init__.py | 351 +++++++++++++++ .../screek-mod-components/uart/automation.h | 38 ++ .../uart/switch/__init__.py | 37 ++ .../uart/switch/uart_switch.cpp | 47 ++ .../uart/switch/uart_switch.h | 30 ++ .../screek-mod-components/uart/uart.cpp | 46 ++ .../screek-mod-components/uart/uart.h | 72 +++ .../uart/uart_component.cpp | 24 + .../uart/uart_component.h | 89 ++++ .../uart/uart_component_esp32_arduino.cpp | 184 ++++++++ .../uart/uart_component_esp32_arduino.h | 41 ++ .../uart/uart_component_esp8266.cpp | 304 +++++++++++++ .../uart/uart_component_esp8266.h | 79 ++++ .../uart/uart_component_esp_idf.cpp | 206 +++++++++ .../uart/uart_component_esp_idf.h | 39 ++ .../uart/uart_component_rp2040.cpp | 184 ++++++++ .../uart/uart_component_rp2040.h | 43 ++ .../uart/uart_debugger.cpp | 202 +++++++++ .../uart/uart_debugger.h | 101 +++++ 31 files changed, 3113 insertions(+) rename 1u/yaml/{ => disbale_uart_output}/screek-humen-dectet-1u.yaml (100%) rename 1u/yaml/{ => disbale_uart_output}/screek-mod-components/ld2410/__init__.py (100%) rename 1u/yaml/{ => disbale_uart_output}/screek-mod-components/ld2410/binary_sensor.py (100%) rename 1u/yaml/{ => disbale_uart_output}/screek-mod-components/ld2410/ld2410.cpp (100%) rename 1u/yaml/{ => disbale_uart_output}/screek-mod-components/ld2410/ld2410.h (100%) rename 1u/yaml/{ => disbale_uart_output}/screek-mod-components/ld2410/sensor.py (100%) create mode 100644 1u/yaml/with_uart_output/screek-humen-dectet-1u.yaml create mode 100644 1u/yaml/with_uart_output/screek-mod-components/ld2410/__init__.py create mode 100644 1u/yaml/with_uart_output/screek-mod-components/ld2410/binary_sensor.py create mode 100644 1u/yaml/with_uart_output/screek-mod-components/ld2410/ld2410.cpp create mode 100644 1u/yaml/with_uart_output/screek-mod-components/ld2410/ld2410.h create mode 100644 1u/yaml/with_uart_output/screek-mod-components/ld2410/sensor.py create mode 100644 1u/yaml/with_uart_output/screek-mod-components/uart/__init__.py create mode 100644 1u/yaml/with_uart_output/screek-mod-components/uart/automation.h create mode 100644 1u/yaml/with_uart_output/screek-mod-components/uart/switch/__init__.py create mode 100644 1u/yaml/with_uart_output/screek-mod-components/uart/switch/uart_switch.cpp create mode 100644 1u/yaml/with_uart_output/screek-mod-components/uart/switch/uart_switch.h create mode 100644 1u/yaml/with_uart_output/screek-mod-components/uart/uart.cpp create mode 100644 1u/yaml/with_uart_output/screek-mod-components/uart/uart.h create mode 100644 1u/yaml/with_uart_output/screek-mod-components/uart/uart_component.cpp create mode 100644 1u/yaml/with_uart_output/screek-mod-components/uart/uart_component.h create mode 100644 1u/yaml/with_uart_output/screek-mod-components/uart/uart_component_esp32_arduino.cpp create mode 100644 1u/yaml/with_uart_output/screek-mod-components/uart/uart_component_esp32_arduino.h create mode 100644 1u/yaml/with_uart_output/screek-mod-components/uart/uart_component_esp8266.cpp create mode 100644 1u/yaml/with_uart_output/screek-mod-components/uart/uart_component_esp8266.h create mode 100644 1u/yaml/with_uart_output/screek-mod-components/uart/uart_component_esp_idf.cpp create mode 100644 1u/yaml/with_uart_output/screek-mod-components/uart/uart_component_esp_idf.h create mode 100644 1u/yaml/with_uart_output/screek-mod-components/uart/uart_component_rp2040.cpp create mode 100644 1u/yaml/with_uart_output/screek-mod-components/uart/uart_component_rp2040.h create mode 100644 1u/yaml/with_uart_output/screek-mod-components/uart/uart_debugger.cpp create mode 100644 1u/yaml/with_uart_output/screek-mod-components/uart/uart_debugger.h diff --git a/1u/yaml/screek-humen-dectet-1u.yaml b/1u/yaml/disbale_uart_output/screek-humen-dectet-1u.yaml similarity index 100% rename from 1u/yaml/screek-humen-dectet-1u.yaml rename to 1u/yaml/disbale_uart_output/screek-humen-dectet-1u.yaml diff --git a/1u/yaml/screek-mod-components/ld2410/__init__.py b/1u/yaml/disbale_uart_output/screek-mod-components/ld2410/__init__.py similarity index 100% rename from 1u/yaml/screek-mod-components/ld2410/__init__.py rename to 1u/yaml/disbale_uart_output/screek-mod-components/ld2410/__init__.py diff --git a/1u/yaml/screek-mod-components/ld2410/binary_sensor.py b/1u/yaml/disbale_uart_output/screek-mod-components/ld2410/binary_sensor.py similarity index 100% rename from 1u/yaml/screek-mod-components/ld2410/binary_sensor.py rename to 1u/yaml/disbale_uart_output/screek-mod-components/ld2410/binary_sensor.py diff --git a/1u/yaml/screek-mod-components/ld2410/ld2410.cpp b/1u/yaml/disbale_uart_output/screek-mod-components/ld2410/ld2410.cpp similarity index 100% rename from 1u/yaml/screek-mod-components/ld2410/ld2410.cpp rename to 1u/yaml/disbale_uart_output/screek-mod-components/ld2410/ld2410.cpp diff --git a/1u/yaml/screek-mod-components/ld2410/ld2410.h b/1u/yaml/disbale_uart_output/screek-mod-components/ld2410/ld2410.h similarity index 100% rename from 1u/yaml/screek-mod-components/ld2410/ld2410.h rename to 1u/yaml/disbale_uart_output/screek-mod-components/ld2410/ld2410.h diff --git a/1u/yaml/screek-mod-components/ld2410/sensor.py b/1u/yaml/disbale_uart_output/screek-mod-components/ld2410/sensor.py similarity index 100% rename from 1u/yaml/screek-mod-components/ld2410/sensor.py rename to 1u/yaml/disbale_uart_output/screek-mod-components/ld2410/sensor.py diff --git a/1u/yaml/with_uart_output/screek-humen-dectet-1u.yaml b/1u/yaml/with_uart_output/screek-humen-dectet-1u.yaml new file mode 100644 index 0000000..72bd91b --- /dev/null +++ b/1u/yaml/with_uart_output/screek-humen-dectet-1u.yaml @@ -0,0 +1,158 @@ +esphome: + name: screek-humen-dectet-1u + comment: Screek Human Presence Sensor 24GHz PS-HPS 1U + friendly_name: Screek Human Presence Sensor 1U + name_add_mac_suffix: True + platformio_options: + board_build.flash_mode: dio + min_version: 2023.3.2 + project: + name: Screek.Human_Presence_Sensor + version: 1U + +external_components: + - source: screek-mod-components + components: [ ld2410, uart ] + +esp32: + board: lolin_s2_mini + framework: + type: arduino + version: 2.0.7 + platform_version: 6.0.1 + +improv_serial: + +logger: + hardware_uart: uart0 + +api: + + +ota: + password: "YOUR_OTA_PASSWORD" + +wifi: + # ssid: !secret wifi_ssid + # password: !secret wifi_password + # fast_connect: True + + power_save_mode: NONE + ap: + ssid: "SCREEK HUMAN-SENSOR" + +captive_portal: + +web_server: + port: 80 + +binary_sensor: + - platform: status + name: Online + id: ink_ha_connected + - platform: ld2410 + has_target: + name: Presence + has_moving_target: + name: Moving Target + has_still_target: + name: Still Target + +sensor: + - platform: template + id: sys_esp_temperature + name: ESP Temperature + lambda: return temperatureRead(); + unit_of_measurement: °C + device_class: TEMPERATURE + update_interval: 30s + entity_category: "diagnostic" + - platform: uptime + name: Uptime + id: sys_uptime + update_interval: 10s + - platform: wifi_signal + name: RSSI + id: wifi_signal_db + update_interval: 1s + 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" + - platform: ld2410 + moving_distance: + name : Moving Distance + id: moving_distance + still_distance: + name: Still Distance + id: still_distance + moving_energy: + name: Move Energy + still_energy: + name: Still Energy + detection_distance: + name: Detection Distance + light: + name: Sun Light + +light: + - platform: status_led + name: sys_status + pin: GPIO15 + internal: True + restore_mode: ALWAYS_OFF + +time: + - platform: sntp + id: time_now + servers: + - ntp.aliyun.com + +uart: + id: uart1 + tx_pin: GPIO18 + rx_pin: GPIO33 + baud_rate: 256000 + parity: NONE + stop_bits: 1 + +ld2410: + timeout: 150s + id: ld2410_radar + +button: + - platform: template + name: "Enable LD2410 BLE" + entity_category: "config" + icon: mdi:bluetooth + on_press: + lambda: |- + id(ld2410_radar) -> ble_control(true); + - platform: template + name: "Disable LD2410 BLE" + entity_category: "config" + icon: mdi:bluetooth-off + on_press: + lambda: |- + id(ld2410_radar) -> ble_control(false); + - platform: template + name: "LD2410 Reboot" + icon: mdi:radar + entity_category: "config" + on_press: + lambda: |- + // auto* radar = LD2410Component::get(ld2410); + // radar -> roboot(); + id(ld2410_radar) -> reboot(); + - platform: restart + icon: mdi:power-cycle + name: "ESP Reboot" + - platform: factory_reset + disabled_by_default: True + name: Factory Reset + id: factory_reset_all \ No newline at end of file diff --git a/1u/yaml/with_uart_output/screek-mod-components/ld2410/__init__.py b/1u/yaml/with_uart_output/screek-mod-components/ld2410/__init__.py new file mode 100644 index 0000000..be39cc2 --- /dev/null +++ b/1u/yaml/with_uart_output/screek-mod-components/ld2410/__init__.py @@ -0,0 +1,158 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import uart +from esphome.const import CONF_ID, CONF_TIMEOUT +from esphome import automation +from esphome.automation import maybe_simple_id + +DEPENDENCIES = ["uart"] +CODEOWNERS = ["@sebcaps"] +MULTI_CONF = True + +ld2410_ns = cg.esphome_ns.namespace("ld2410") +LD2410Component = ld2410_ns.class_("LD2410Component", cg.Component, uart.UARTDevice) +LD2410Restart = ld2410_ns.class_("LD2410Restart", automation.Action) +CONF_LD2410_ID = "ld2410_id" +CONF_MAX_MOVE_DISTANCE = "max_move_distance" +CONF_MAX_STILL_DISTANCE = "max_still_distance" +CONF_G0_MOVE_THRESHOLD = "g0_move_threshold" +CONF_G0_STILL_THRESHOLD = "g0_still_threshold" +CONF_G1_MOVE_THRESHOLD = "g1_move_threshold" +CONF_G1_STILL_THRESHOLD = "g1_still_threshold" +CONF_G2_MOVE_THRESHOLD = "g2_move_threshold" +CONF_G2_STILL_THRESHOLD = "g2_still_threshold" +CONF_G3_MOVE_THRESHOLD = "g3_move_threshold" +CONF_G3_STILL_THRESHOLD = "g3_still_threshold" +CONF_G4_MOVE_THRESHOLD = "g4_move_threshold" +CONF_G4_STILL_THRESHOLD = "g4_still_threshold" +CONF_G5_MOVE_THRESHOLD = "g5_move_threshold" +CONF_G5_STILL_THRESHOLD = "g5_still_threshold" +CONF_G6_MOVE_THRESHOLD = "g6_move_threshold" +CONF_G6_STILL_THRESHOLD = "g6_still_threshold" +CONF_G7_MOVE_THRESHOLD = "g7_move_threshold" +CONF_G7_STILL_THRESHOLD = "g7_still_threshold" +CONF_G8_MOVE_THRESHOLD = "g8_move_threshold" +CONF_G8_STILL_THRESHOLD = "g8_still_threshold" + +DISTANCES = [0.75, 1.5, 2.25, 3, 3.75, 4.5, 5.25, 6] + +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(LD2410Component), + cv.Optional(CONF_MAX_MOVE_DISTANCE, default="4.5m"): cv.All( + cv.distance, cv.one_of(*DISTANCES, float=True) + ), + cv.Optional(CONF_MAX_STILL_DISTANCE, default="4.5m"): cv.All( + cv.distance, cv.one_of(*DISTANCES, float=True) + ), + cv.Optional(CONF_TIMEOUT, default="5s"): cv.All( + cv.positive_time_period_seconds, + cv.Range(max=cv.TimePeriod(seconds=32767)), + ), + cv.Optional(CONF_G0_MOVE_THRESHOLD, default=50): cv.int_range( + min=0, max=100 + ), + cv.Optional(CONF_G0_STILL_THRESHOLD, default=0): cv.int_range( + min=0, max=100 + ), + cv.Optional(CONF_G1_MOVE_THRESHOLD, default=50): cv.int_range( + min=0, max=100 + ), + cv.Optional(CONF_G1_STILL_THRESHOLD, default=0): cv.int_range( + min=0, max=100 + ), + cv.Optional(CONF_G2_MOVE_THRESHOLD, default=40): cv.int_range( + min=0, max=100 + ), + cv.Optional(CONF_G2_STILL_THRESHOLD, default=40): cv.int_range( + min=0, max=100 + ), + cv.Optional(CONF_G3_MOVE_THRESHOLD, default=40): cv.int_range( + min=0, max=100 + ), + cv.Optional(CONF_G3_STILL_THRESHOLD, default=40): cv.int_range( + min=0, max=100 + ), + cv.Optional(CONF_G4_MOVE_THRESHOLD, default=40): cv.int_range( + min=0, max=100 + ), + cv.Optional(CONF_G4_STILL_THRESHOLD, default=40): cv.int_range( + min=0, max=100 + ), + cv.Optional(CONF_G5_MOVE_THRESHOLD, default=40): cv.int_range( + min=0, max=100 + ), + cv.Optional(CONF_G5_STILL_THRESHOLD, default=40): cv.int_range( + min=0, max=100 + ), + cv.Optional(CONF_G6_MOVE_THRESHOLD, default=30): cv.int_range( + min=0, max=100 + ), + cv.Optional(CONF_G6_STILL_THRESHOLD, default=15): cv.int_range( + min=0, max=100 + ), + cv.Optional(CONF_G7_MOVE_THRESHOLD, default=30): cv.int_range( + min=0, max=100 + ), + cv.Optional(CONF_G7_STILL_THRESHOLD, default=15): cv.int_range( + min=0, max=100 + ), + cv.Optional(CONF_G8_MOVE_THRESHOLD, default=30): cv.int_range( + min=0, max=100 + ), + cv.Optional(CONF_G8_STILL_THRESHOLD, default=15): cv.int_range( + min=0, max=100 + ), + } + ) + .extend(uart.UART_DEVICE_SCHEMA) + .extend(cv.COMPONENT_SCHEMA) +) + +FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema( + "ld2410", + baud_rate=256000, + 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) + cg.add(var.set_timeout(config[CONF_TIMEOUT])) + cg.add(var.set_max_move_distance(int(config[CONF_MAX_MOVE_DISTANCE] / 0.75))) + cg.add(var.set_max_still_distance(int(config[CONF_MAX_STILL_DISTANCE] / 0.75))) + cg.add( + var.set_range_config( + config[CONF_G0_MOVE_THRESHOLD], + config[CONF_G0_STILL_THRESHOLD], + config[CONF_G1_MOVE_THRESHOLD], + config[CONF_G1_STILL_THRESHOLD], + config[CONF_G2_MOVE_THRESHOLD], + config[CONF_G2_STILL_THRESHOLD], + config[CONF_G3_MOVE_THRESHOLD], + config[CONF_G3_STILL_THRESHOLD], + config[CONF_G4_MOVE_THRESHOLD], + config[CONF_G4_STILL_THRESHOLD], + config[CONF_G5_MOVE_THRESHOLD], + config[CONF_G5_STILL_THRESHOLD], + config[CONF_G6_MOVE_THRESHOLD], + config[CONF_G6_STILL_THRESHOLD], + config[CONF_G7_MOVE_THRESHOLD], + config[CONF_G7_STILL_THRESHOLD], + config[CONF_G8_MOVE_THRESHOLD], + config[CONF_G8_STILL_THRESHOLD], + ) + ) + + +CALIBRATION_ACTION_SCHEMA = maybe_simple_id( + { + cv.Required(CONF_ID): cv.use_id(LD2410Component), + } +) diff --git a/1u/yaml/with_uart_output/screek-mod-components/ld2410/binary_sensor.py b/1u/yaml/with_uart_output/screek-mod-components/ld2410/binary_sensor.py new file mode 100644 index 0000000..02f73d5 --- /dev/null +++ b/1u/yaml/with_uart_output/screek-mod-components/ld2410/binary_sensor.py @@ -0,0 +1,36 @@ +import esphome.codegen as cg +from esphome.components import binary_sensor +import esphome.config_validation as cv +from esphome.const import DEVICE_CLASS_MOTION, DEVICE_CLASS_OCCUPANCY +from . import CONF_LD2410_ID, LD2410Component + +DEPENDENCIES = ["ld2410"] +CONF_HAS_TARGET = "has_target" +CONF_HAS_MOVING_TARGET = "has_moving_target" +CONF_HAS_STILL_TARGET = "has_still_target" + +CONFIG_SCHEMA = { + cv.GenerateID(CONF_LD2410_ID): cv.use_id(LD2410Component), + cv.Optional(CONF_HAS_TARGET): binary_sensor.binary_sensor_schema( + device_class=DEVICE_CLASS_OCCUPANCY + ), + cv.Optional(CONF_HAS_MOVING_TARGET): binary_sensor.binary_sensor_schema( + device_class=DEVICE_CLASS_MOTION + ), + cv.Optional(CONF_HAS_STILL_TARGET): binary_sensor.binary_sensor_schema( + device_class=DEVICE_CLASS_OCCUPANCY + ), +} + + +async def to_code(config): + ld2410_component = await cg.get_variable(config[CONF_LD2410_ID]) + if CONF_HAS_TARGET in config: + sens = await binary_sensor.new_binary_sensor(config[CONF_HAS_TARGET]) + cg.add(ld2410_component.set_target_sensor(sens)) + if CONF_HAS_MOVING_TARGET in config: + sens = await binary_sensor.new_binary_sensor(config[CONF_HAS_MOVING_TARGET]) + cg.add(ld2410_component.set_moving_target_sensor(sens)) + if CONF_HAS_STILL_TARGET in config: + sens = await binary_sensor.new_binary_sensor(config[CONF_HAS_STILL_TARGET]) + cg.add(ld2410_component.set_still_target_sensor(sens)) diff --git a/1u/yaml/with_uart_output/screek-mod-components/ld2410/ld2410.cpp b/1u/yaml/with_uart_output/screek-mod-components/ld2410/ld2410.cpp new file mode 100644 index 0000000..5dfd4bc --- /dev/null +++ b/1u/yaml/with_uart_output/screek-mod-components/ld2410/ld2410.cpp @@ -0,0 +1,416 @@ +#include "ld2410.h" + +#define highbyte(val) (uint8_t)((val) >> 8) +#define lowbyte(val) (uint8_t)((val) &0xff) + +namespace esphome { +namespace ld2410 { + +static const char *const TAG = "ld2410"; + +void LD2410Component::dump_config() { + ESP_LOGCONFIG(TAG, "LD2410:"); +#ifdef USE_BINARY_SENSOR + LOG_BINARY_SENSOR(" ", "HasTargetSensor", this->target_binary_sensor_); + LOG_BINARY_SENSOR(" ", "MovingSensor", this->moving_binary_sensor_); + LOG_BINARY_SENSOR(" ", "StillSensor", this->still_binary_sensor_); +#endif +#ifdef USE_SENSOR + LOG_SENSOR(" ", "Moving Distance", this->moving_target_distance_sensor_); + LOG_SENSOR(" ", "Still Distance", this->still_target_distance_sensor_); + LOG_SENSOR(" ", "Moving Energy", this->moving_target_energy_sensor_); + LOG_SENSOR(" ", "Still Energy", this->still_target_energy_sensor_); + LOG_SENSOR(" ", "Detection Distance", this->detection_distance_sensor_); + + LOG_SENSOR(" ", "Light", this->light_sensor_); +#endif + this->set_config_mode_(true); + this->get_version_(); + this->set_config_mode_(false); + ESP_LOGCONFIG(TAG, " Firmware Version : %u.%u.%u%u%u%u", this->version_[0], this->version_[1], this->version_[2], + this->version_[3], this->version_[4], this->version_[5]); +} + +void LD2410Component::setup() { + ESP_LOGCONFIG(TAG, "Setting up LD2410..."); + // ESP_LOGCONFIG(TAG, "Apply screek patch..."); + this->set_config_mode_(true); + + /* + this->set_max_distances_timeout_(this->max_move_distance_, this->max_still_distance_, this->timeout_); + // Configure Gates sensitivity + this->set_gate_threshold_(0, this->rg0_move_threshold_, this->rg0_still_threshold_); + this->set_gate_threshold_(1, this->rg1_move_threshold_, this->rg1_still_threshold_); + this->set_gate_threshold_(2, this->rg2_move_threshold_, this->rg2_still_threshold_); + this->set_gate_threshold_(3, this->rg3_move_threshold_, this->rg3_still_threshold_); + this->set_gate_threshold_(4, this->rg4_move_threshold_, this->rg4_still_threshold_); + this->set_gate_threshold_(5, this->rg5_move_threshold_, this->rg5_still_threshold_); + this->set_gate_threshold_(6, this->rg6_move_threshold_, this->rg6_still_threshold_); + this->set_gate_threshold_(7, this->rg7_move_threshold_, this->rg7_still_threshold_); + this->set_gate_threshold_(8, this->rg8_move_threshold_, this->rg8_still_threshold_); + */ + this->get_version_(); + this->set_config_mode_(false); + + // this->factory_mode(true); + + ESP_LOGCONFIG(TAG, "Firmware Version : %u.%u.%u%u%u%u", this->version_[0], this->version_[1], this->version_[2], + this->version_[3], this->version_[4], this->version_[5]); + ESP_LOGCONFIG(TAG, "LD2410 setup complete."); +} + +void LD2410Component::loop() { + const int max_line_length = 80; + static uint8_t buffer[max_line_length]; + + while (available()) { + this->readline_(read(), buffer, max_line_length); + } +} + +void LD2410Component::send_command_(uint8_t command, uint8_t *command_value, int command_value_len) { + // lastCommandSuccess->publish_state(false); + + // frame start bytes + this->write_array(CMD_FRAME_HEADER, 4); + // length bytes + int len = 2; + if (command_value != nullptr) + len += command_value_len; + this->write_byte(lowbyte(len)); + this->write_byte(highbyte(len)); + + // command + this->write_byte(lowbyte(command)); + this->write_byte(highbyte(command)); + + // command value bytes + if (command_value != nullptr) { + for (int i = 0; i < command_value_len; i++) { + this->write_byte(command_value[i]); + } + } + // frame end bytes + this->write_array(CMD_FRAME_END, 4); + // FIXME to remove + delay(50); // NOLINT +} + +long map(long x, long in_min, long in_max, long out_min, long out_max) { + return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; +} + +void LD2410Component::handle_periodic_data_(uint8_t *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) // check 4 frame start bytes + return; + if (buffer[7] != HEAD || buffer[len - 6] != END || buffer[len - 5] != CHECK) // Check constant values + return; // data head=0xAA, data end=0x55, crc=0x00 + + /* + Data Type: 6th + 0x01: Engineering mode + 0x02: Normal mode + */ + // char data_type = buffer[DATA_TYPES]; + /* + Target states: 9th + 0x00 = No target + 0x01 = Moving targets + 0x02 = Still targets + 0x03 = Moving+Still targets + */ +#ifdef USE_BINARY_SENSOR + char target_state = buffer[TARGET_STATES]; + if (this->target_binary_sensor_ != nullptr) { + this->target_binary_sensor_->publish_state(target_state != 0x00); + } +#endif + + /* + Reduce data update rate to prevent home assistant database size grow fast + */ + int32_t current_millis = millis(); + if (current_millis - last_periodic_millis < 1000) + return; + last_periodic_millis = current_millis; + +#ifdef USE_BINARY_SENSOR + if (this->moving_binary_sensor_ != nullptr) { + this->moving_binary_sensor_->publish_state(CHECK_BIT(target_state, 0)); + } + if (this->still_binary_sensor_ != nullptr) { + this->still_binary_sensor_->publish_state(CHECK_BIT(target_state, 1)); + } +#endif + /* + 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 + */ +#ifdef USE_SENSOR + if (this->moving_target_distance_sensor_ != nullptr) { + int new_moving_target_distance = this->two_byte_to_int_(buffer[MOVING_TARGET_LOW], buffer[MOVING_TARGET_HIGH]); + if (this->moving_target_distance_sensor_->get_state() != new_moving_target_distance) + this->moving_target_distance_sensor_->publish_state(new_moving_target_distance); + } + if (this->moving_target_energy_sensor_ != nullptr) { + int new_moving_target_energy = buffer[MOVING_ENERGY]; + if (this->moving_target_energy_sensor_->get_state() != new_moving_target_energy) + this->moving_target_energy_sensor_->publish_state(new_moving_target_energy); + } + if (this->still_target_distance_sensor_ != nullptr) { + int new_still_target_distance = this->two_byte_to_int_(buffer[STILL_TARGET_LOW], buffer[STILL_TARGET_HIGH]); + if (this->still_target_distance_sensor_->get_state() != new_still_target_distance) + this->still_target_distance_sensor_->publish_state(new_still_target_distance); + } + if (this->still_target_energy_sensor_ != nullptr) { + int new_still_target_energy = buffer[STILL_ENERGY]; + if (this->still_target_energy_sensor_->get_state() != new_still_target_energy) + this->still_target_energy_sensor_->publish_state(new_still_target_energy); + } + if (this->detection_distance_sensor_ != nullptr) { + int new_detect_distance = this->two_byte_to_int_(buffer[DETECT_DISTANCE_LOW], buffer[DETECT_DISTANCE_HIGH]); + if (this->detection_distance_sensor_->get_state() != new_detect_distance) + this->detection_distance_sensor_->publish_state(new_detect_distance); + } + + if (this->light_sensor_ != nullptr) { + int data_type = buffer[6]; + int new_light = -1; + if (data_type == 0x01){ // 0x01 = 工程模式! + new_light = buffer[37]; + + new_light = map(new_light, 85, 255, 0, 100); + if (new_light < 0){ + new_light = 0; + } + if (new_light > 100){ + new_light = 100; + } + + ESP_LOGD(TAG,"LD2410 Sun Light: %d%%", new_light); + }else{ + int32_t now_millis = millis(); + + if (now_millis - last_change_fatory_mode_millis > 2000){ + ESP_LOGD(TAG,"Normal mode no light, change to factory mode"); + + this->factory_mode(true); + last_change_fatory_mode_millis = now_millis; + } + } + if (this->light_sensor_->get_state() != new_light){ + this->light_sensor_->publish_state(new_light); + } + } + +#endif + +} + +void LD2410Component::handle_ack_data_(uint8_t *buffer, int len) { + ESP_LOGV(TAG, "Handling ACK DATA for COMMAND"); + if (len < 10) { + ESP_LOGE(TAG, "Error with last command : incorrect length"); + return; + } + if (buffer[0] != 0xFD || buffer[1] != 0xFC || buffer[2] != 0xFB || buffer[3] != 0xFA) { // check 4 frame start bytes + ESP_LOGE(TAG, "Error with last command : incorrect Header"); + return; + } + if (buffer[COMMAND_STATUS] != 0x01) { + ESP_LOGE(TAG, "Error with last command : status != 0x01"); + return; + } + if (this->two_byte_to_int_(buffer[8], buffer[9]) != 0x00) { + ESP_LOGE(TAG, "Error with last command , last buffer was: %u , %u", buffer[8], buffer[9]); + return; + } + + switch (buffer[COMMAND]) { + case lowbyte(CMD_ENABLE_CONF): + ESP_LOGV(TAG, "Handled Enable conf command"); + break; + case lowbyte(CMD_DISABLE_CONF): + ESP_LOGV(TAG, "Handled Disabled conf command"); + break; + case lowbyte(CMD_VERSION): + ESP_LOGV(TAG, "FW Version is: %u.%u.%u%u%u%u", buffer[13], buffer[12], buffer[17], buffer[16], buffer[15], + buffer[14]); + this->version_[0] = buffer[13]; + this->version_[1] = buffer[12]; + this->version_[2] = buffer[17]; + this->version_[3] = buffer[16]; + this->version_[4] = buffer[15]; + this->version_[5] = buffer[14]; + + break; + case lowbyte(CMD_GATE_SENS): + ESP_LOGV(TAG, "Handled sensitivity command"); + break; + case lowbyte(CMD_QUERY): // Query parameters response + { + if (buffer[10] != 0xAA) + return; // value head=0xAA + /* + Moving distance range: 13th byte + Still distance range: 14th byte + */ + // TODO + // 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++) { + moving_sensitivities[i] = buffer[14 + i]; + } + for (int i = 0; i < 9; i++) { + still_sensitivities[i] = buffer[23 + i]; + } + /* + None Duration: 33~34th bytes + */ + // noneDuration->publish_state(this->two_byte_to_int_(buffer[32], buffer[33])); + } break; + default: + break; + } +} + +void LD2410Component::readline_(int readch, uint8_t *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) { + ESP_LOGV(TAG, "Will handle Periodic Data"); + this->handle_periodic_data_(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) { + ESP_LOGV(TAG, "Will handle ACK Data"); + this->handle_ack_data_(buffer, pos); + pos = 0; // Reset position index ready for next time + } + } + } +} + +void LD2410Component::set_config_mode_(bool enable) { + uint8_t cmd = enable ? CMD_ENABLE_CONF : CMD_DISABLE_CONF; + uint8_t cmd_value[2] = {0x01, 0x00}; + this->send_command_(cmd, enable ? cmd_value : nullptr, 2); +} + +void LD2410Component::query_parameters_() { this->send_command_(CMD_QUERY, nullptr, 0); } +void LD2410Component::get_version_() { this->send_command_(CMD_VERSION, nullptr, 0); } + +void LD2410Component::set_max_distances_timeout_(uint8_t max_moving_distance_range, uint8_t max_still_distance_range, + uint16_t timeout) { + uint8_t value[18] = {0x00, + 0x00, + lowbyte(max_moving_distance_range), + highbyte(max_moving_distance_range), + 0x00, + 0x00, + 0x01, + 0x00, + lowbyte(max_still_distance_range), + highbyte(max_still_distance_range), + 0x00, + 0x00, + 0x02, + 0x00, + lowbyte(timeout), + highbyte(timeout), + 0x00, + 0x00}; + this->send_command_(CMD_MAXDIST_DURATION, value, 18); + this->query_parameters_(); +} +void LD2410Component::set_gate_threshold_(uint8_t gate, uint8_t motionsens, uint8_t stillsens) { + // reference + // https://drive.google.com/drive/folders/1p4dhbEJA3YubyIjIIC7wwVsSo8x29Fq-?spm=a2g0o.detail.1000023.17.93465697yFwVxH + // Send data: configure the motion sensitivity of distance gate 3 to 40, and the static sensitivity of 40 + // 00 00 (gate) + // 03 00 00 00 (gate number) + // 01 00 (motion sensitivity) + // 28 00 00 00 (value) + // 02 00 (still sensitivtiy) + // 28 00 00 00 (value) + uint8_t value[18] = {0x00, 0x00, lowbyte(gate), highbyte(gate), 0x00, 0x00, + 0x01, 0x00, lowbyte(motionsens), highbyte(motionsens), 0x00, 0x00, + 0x02, 0x00, lowbyte(stillsens), highbyte(stillsens), 0x00, 0x00}; + this->send_command_(CMD_GATE_SENS, value, 18); +} + +void LD2410Component::factoryReset() + { + this->set_config_mode_(true); + + uint8_t cmd = 0x00A2; + this -> send_command_(cmd, nullptr, 0); + } + +void LD2410Component::reboot() +{ + ESP_LOGD(TAG, "reboot ld2410b..."); + this->set_config_mode_(true); + + uint8_t cmd = 0x00A3; + this -> send_command_(cmd, nullptr, 0); + // not need to exit config mode because the ld2410 will reboot automatically +} + +void LD2410Component::ble_control(bool enable) +{ + this->set_config_mode_(true); + uint8_t CMD_BLE_CONF = 0x00A4; + uint8_t CMD_REBOOT = 0x00A3; + + uint8_t cmd_value[2] = {0x00, 0x00}; + + if (enable){ + cmd_value[0] = 0x01; + + ESP_LOGD(TAG, "Enable BLE..."); + }else{ + ESP_LOGD(TAG, "Disable BLE..."); + } + + this -> send_command_(CMD_BLE_CONF, cmd_value, 2); + + //this -> send_command_(CMD_REBOOT, nullptr, 0); + + this->set_config_mode_(false); + + this -> reboot(); +} + +void LD2410Component::factory_mode(bool enable){ + uint8_t CMD_FACTORY_ON = 0x0062; + uint8_t CMD_FACTORY_OFF = 0x0063; + this->set_config_mode_(true); + + uint8_t cmd = enable ? CMD_FACTORY_ON: CMD_FACTORY_OFF; + + this->send_command_(cmd, nullptr, 0); + + this->set_config_mode_(false); + +} + +} // namespace ld2410 +} // namespace esphome diff --git a/1u/yaml/with_uart_output/screek-mod-components/ld2410/ld2410.h b/1u/yaml/with_uart_output/screek-mod-components/ld2410/ld2410.h new file mode 100644 index 0000000..1d0f8e1 --- /dev/null +++ b/1u/yaml/with_uart_output/screek-mod-components/ld2410/ld2410.h @@ -0,0 +1,158 @@ +#pragma once +#include "esphome/core/defines.h" +#include "esphome/core/component.h" +#ifdef USE_BINARY_SENSOR +#include "esphome/components/binary_sensor/binary_sensor.h" +#endif +#ifdef USE_SENSOR +#include "esphome/components/sensor/sensor.h" +#endif +#include "esphome/components/uart/uart.h" +#include "esphome/core/automation.h" +#include "esphome/core/helpers.h" + +namespace esphome { +namespace ld2410 { + +#define CHECK_BIT(var, pos) (((var) >> (pos)) & 1) + +// Commands +static const uint8_t CMD_ENABLE_CONF = 0x00FF; +static const uint8_t CMD_DISABLE_CONF = 0x00FE; +static const uint8_t CMD_MAXDIST_DURATION = 0x0060; +static const uint8_t CMD_QUERY = 0x0061; +static const uint8_t CMD_GATE_SENS = 0x0064; +static const uint8_t CMD_VERSION = 0x00A0; + +// Commands values +static const uint8_t CMD_MAX_MOVE_VALUE = 0x0000; +static const uint8_t CMD_MAX_STILL_VALUE = 0x0001; +static const uint8_t CMD_DURATION_VALUE = 0x0002; +// Command Header & Footer +static const uint8_t CMD_FRAME_HEADER[4] = {0xFD, 0xFC, 0xFB, 0xFA}; +static const uint8_t CMD_FRAME_END[4] = {0x04, 0x03, 0x02, 0x01}; +// Data Header & Footer +static const uint8_t DATA_FRAME_HEADER[4] = {0xF4, 0xF3, 0xF2, 0xF1}; +static const uint8_t DATA_FRAME_END[4] = {0xF8, 0xF7, 0xF6, 0xF5}; +/* +Data Type: 6th byte +Target states: 9th byte + 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 +*/ +enum PeriodicDataStructure : uint8_t { + DATA_TYPES = 5, + TARGET_STATES = 8, + MOVING_TARGET_LOW = 9, + MOVING_TARGET_HIGH = 10, + MOVING_ENERGY = 11, + STILL_TARGET_LOW = 12, + STILL_TARGET_HIGH = 13, + STILL_ENERGY = 14, + DETECT_DISTANCE_LOW = 15, + DETECT_DISTANCE_HIGH = 16, +}; +// 上报数据的固定结构。(23年3月13日_16时54分_) +enum PeriodicDataValue : uint8_t { HEAD = 0XAA, END = 0x55, CHECK = 0x00 }; + +enum AckDataStructure : uint8_t { COMMAND = 6, COMMAND_STATUS = 7 }; + +// char cmd[2] = {enable ? 0xFF : 0xFE, 0x00}; +class LD2410Component : public Component, public uart::UARTDevice { +#ifdef USE_SENSOR + SUB_SENSOR(moving_target_distance) + SUB_SENSOR(still_target_distance) + SUB_SENSOR(moving_target_energy) + SUB_SENSOR(still_target_energy) + SUB_SENSOR(detection_distance) + SUB_SENSOR(light) +#endif + + public: + void setup() override; + void dump_config() override; + void loop() override; + +#ifdef USE_BINARY_SENSOR + void set_target_sensor(binary_sensor::BinarySensor *sens) { this->target_binary_sensor_ = sens; }; + void set_moving_target_sensor(binary_sensor::BinarySensor *sens) { this->moving_binary_sensor_ = sens; }; + void set_still_target_sensor(binary_sensor::BinarySensor *sens) { this->still_binary_sensor_ = sens; }; +#endif + + void set_timeout(uint16_t value) { this->timeout_ = value; }; + void set_max_move_distance(uint8_t value) { this->max_move_distance_ = value; }; + void set_max_still_distance(uint8_t value) { this->max_still_distance_ = value; }; + void set_range_config(int rg0_move, int rg0_still, int rg1_move, int rg1_still, int rg2_move, int rg2_still, + int rg3_move, int rg3_still, int rg4_move, int rg4_still, int rg5_move, int rg5_still, + int rg6_move, int rg6_still, int rg7_move, int rg7_still, int rg8_move, int rg8_still) { + this->rg0_move_threshold_ = rg0_move; + this->rg0_still_threshold_ = rg0_still; + this->rg1_move_threshold_ = rg1_move; + this->rg1_still_threshold_ = rg1_still; + this->rg2_move_threshold_ = rg2_move; + this->rg2_still_threshold_ = rg2_still; + this->rg3_move_threshold_ = rg3_move; + this->rg3_still_threshold_ = rg3_still; + this->rg4_move_threshold_ = rg4_move; + this->rg4_still_threshold_ = rg4_still; + this->rg5_move_threshold_ = rg5_move; + this->rg5_still_threshold_ = rg5_still; + this->rg6_move_threshold_ = rg6_move; + this->rg6_still_threshold_ = rg6_still; + this->rg7_move_threshold_ = rg7_move; + this->rg7_still_threshold_ = rg7_still; + this->rg8_move_threshold_ = rg8_move; + this->rg8_still_threshold_ = rg8_still; + }; + int moving_sensitivities[9] = {0}; + int still_sensitivities[9] = {0}; + + int32_t last_periodic_millis = millis(); + + int32_t last_change_fatory_mode_millis = 0; + + void factoryReset(); + + void reboot(); + + void ble_control(bool enable); + + void factory_mode(bool enable); + + protected: +#ifdef USE_BINARY_SENSOR + binary_sensor::BinarySensor *target_binary_sensor_{nullptr}; + binary_sensor::BinarySensor *moving_binary_sensor_{nullptr}; + binary_sensor::BinarySensor *still_binary_sensor_{nullptr}; +#endif + + std::vector rx_buffer_; + int two_byte_to_int_(char firstbyte, char secondbyte) { return (int16_t)(secondbyte << 8) + firstbyte; } + void send_command_(uint8_t command_str, uint8_t *command_value, int command_value_len); + + void set_max_distances_timeout_(uint8_t max_moving_distance_range, uint8_t max_still_distance_range, + uint16_t timeout); + void set_gate_threshold_(uint8_t gate, uint8_t motionsens, uint8_t stillsens); + void set_config_mode_(bool enable); + void handle_periodic_data_(uint8_t *buffer, int len); + void handle_ack_data_(uint8_t *buffer, int len); + void readline_(int readch, uint8_t *buffer, int len); + void query_parameters_(); + void get_version_(); + + uint16_t timeout_; + uint8_t max_move_distance_; + uint8_t max_still_distance_; + + uint8_t version_[6]; + uint8_t rg0_move_threshold_, rg0_still_threshold_, rg1_move_threshold_, rg1_still_threshold_, rg2_move_threshold_, + rg2_still_threshold_, rg3_move_threshold_, rg3_still_threshold_, rg4_move_threshold_, rg4_still_threshold_, + rg5_move_threshold_, rg5_still_threshold_, rg6_move_threshold_, rg6_still_threshold_, rg7_move_threshold_, + rg7_still_threshold_, rg8_move_threshold_, rg8_still_threshold_; +}; + +} // namespace ld2410 +} // namespace esphome diff --git a/1u/yaml/with_uart_output/screek-mod-components/ld2410/sensor.py b/1u/yaml/with_uart_output/screek-mod-components/ld2410/sensor.py new file mode 100644 index 0000000..c5dc934 --- /dev/null +++ b/1u/yaml/with_uart_output/screek-mod-components/ld2410/sensor.py @@ -0,0 +1,70 @@ +import esphome.codegen as cg +from esphome.components import sensor +import esphome.config_validation as cv +from esphome.const import ( + DEVICE_CLASS_DISTANCE, + DEVICE_CLASS_ENERGY, + UNIT_CENTIMETER, + UNIT_PERCENT, + STATE_CLASS_NONE, + ICON_BRIGHTNESS_5, + UNIT_EMPTY, + UNIT_LUX, + +) +from . import CONF_LD2410_ID, LD2410Component + +DEPENDENCIES = ["ld2410"] +CONF_MOVING_DISTANCE = "moving_distance" +CONF_STILL_DISTANCE = "still_distance" +CONF_MOVING_ENERGY = "moving_energy" +CONF_STILL_ENERGY = "still_energy" +CONF_DETECTION_DISTANCE = "detection_distance" +CONF_LIGHT = "light" + +CONFIG_SCHEMA = { + cv.GenerateID(CONF_LD2410_ID): cv.use_id(LD2410Component), + cv.Optional(CONF_MOVING_DISTANCE): sensor.sensor_schema( + device_class=DEVICE_CLASS_DISTANCE, unit_of_measurement=UNIT_CENTIMETER + ), + cv.Optional(CONF_STILL_DISTANCE): sensor.sensor_schema( + device_class=DEVICE_CLASS_DISTANCE, unit_of_measurement=UNIT_CENTIMETER + ), + cv.Optional(CONF_MOVING_ENERGY): sensor.sensor_schema( + device_class=DEVICE_CLASS_ENERGY, unit_of_measurement=UNIT_PERCENT + ), + cv.Optional(CONF_STILL_ENERGY): sensor.sensor_schema( + device_class=DEVICE_CLASS_ENERGY, unit_of_measurement=UNIT_PERCENT + ), + cv.Optional(CONF_DETECTION_DISTANCE): sensor.sensor_schema( + device_class=DEVICE_CLASS_DISTANCE, unit_of_measurement=UNIT_CENTIMETER + ), + + cv.Optional(CONF_LIGHT): sensor.sensor_schema( + device_class=STATE_CLASS_NONE, + icon=ICON_BRIGHTNESS_5, + unit_of_measurement=UNIT_PERCENT + ), +} + +async def to_code(config): + ld2410_component = await cg.get_variable(config[CONF_LD2410_ID]) + if CONF_MOVING_DISTANCE in config: + sens = await sensor.new_sensor(config[CONF_MOVING_DISTANCE]) + cg.add(ld2410_component.set_moving_target_distance_sensor(sens)) + if CONF_STILL_DISTANCE in config: + sens = await sensor.new_sensor(config[CONF_STILL_DISTANCE]) + cg.add(ld2410_component.set_still_target_distance_sensor(sens)) + if CONF_MOVING_ENERGY in config: + sens = await sensor.new_sensor(config[CONF_MOVING_ENERGY]) + cg.add(ld2410_component.set_moving_target_energy_sensor(sens)) + if CONF_STILL_ENERGY in config: + sens = await sensor.new_sensor(config[CONF_STILL_ENERGY]) + cg.add(ld2410_component.set_still_target_energy_sensor(sens)) + if CONF_DETECTION_DISTANCE in config: + sens = await sensor.new_sensor(config[CONF_DETECTION_DISTANCE]) + cg.add(ld2410_component.set_detection_distance_sensor(sens)) + + if CONF_LIGHT in config: + sens = await sensor.new_sensor(config[CONF_LIGHT]) + cg.add(ld2410_component.set_light_sensor(sens)) \ No newline at end of file diff --git a/1u/yaml/with_uart_output/screek-mod-components/uart/__init__.py b/1u/yaml/with_uart_output/screek-mod-components/uart/__init__.py new file mode 100644 index 0000000..ed60a9f --- /dev/null +++ b/1u/yaml/with_uart_output/screek-mod-components/uart/__init__.py @@ -0,0 +1,351 @@ +from typing import Optional + +import esphome.codegen as cg +import esphome.config_validation as cv +import esphome.final_validate as fv +from esphome.yaml_util import make_data_base +from esphome import pins, automation +from esphome.const import ( + CONF_BAUD_RATE, + CONF_ID, + CONF_NUMBER, + CONF_RX_PIN, + CONF_TX_PIN, + CONF_UART_ID, + CONF_DATA, + CONF_RX_BUFFER_SIZE, + CONF_INVERTED, + CONF_INVERT, + CONF_TRIGGER_ID, + CONF_SEQUENCE, + CONF_TIMEOUT, + CONF_DEBUG, + CONF_DIRECTION, + CONF_AFTER, + CONF_BYTES, + CONF_DELIMITER, + CONF_DUMMY_RECEIVER, + CONF_DUMMY_RECEIVER_ID, + CONF_LAMBDA, +) +from esphome.core import CORE + +CODEOWNERS = ["@esphome/core"] +uart_ns = cg.esphome_ns.namespace("uart") +UARTComponent = uart_ns.class_("UARTComponent") + +IDFUARTComponent = uart_ns.class_("IDFUARTComponent", UARTComponent, cg.Component) +ESP32ArduinoUARTComponent = uart_ns.class_( + "ESP32ArduinoUARTComponent", UARTComponent, cg.Component +) +ESP8266UartComponent = uart_ns.class_( + "ESP8266UartComponent", UARTComponent, cg.Component +) +RP2040UartComponent = uart_ns.class_("RP2040UartComponent", UARTComponent, cg.Component) + +UARTDevice = uart_ns.class_("UARTDevice") +UARTWriteAction = uart_ns.class_("UARTWriteAction", automation.Action) +UARTDebugger = uart_ns.class_("UARTDebugger", cg.Component, automation.Action) +UARTDummyReceiver = uart_ns.class_("UARTDummyReceiver", cg.Component) +MULTI_CONF = True + + +def validate_raw_data(value): + if isinstance(value, str): + return value.encode("utf-8") + if isinstance(value, str): + return value + if isinstance(value, list): + return cv.Schema([cv.hex_uint8_t])(value) + raise cv.Invalid( + "data must either be a string wrapped in quotes or a list of bytes" + ) + + +def validate_rx_pin(value): + value = pins.internal_gpio_input_pin_schema(value) + if CORE.is_esp8266 and value[CONF_NUMBER] >= 16: + raise cv.Invalid("Pins GPIO16 and GPIO17 cannot be used as RX pins on ESP8266.") + return value + + +def validate_invert_esp32(config): + if ( + CORE.is_esp32 + and CONF_TX_PIN in config + and CONF_RX_PIN in config + and config[CONF_TX_PIN][CONF_INVERTED] != config[CONF_RX_PIN][CONF_INVERTED] + ): + raise cv.Invalid( + "Different invert values for TX and RX pin are not (yet) supported for ESP32." + ) + return config + + +def _uart_declare_type(value): + if CORE.is_esp8266: + return cv.declare_id(ESP8266UartComponent)(value) + if CORE.is_esp32: + if CORE.using_arduino: + return cv.declare_id(ESP32ArduinoUARTComponent)(value) + if CORE.using_esp_idf: + return cv.declare_id(IDFUARTComponent)(value) + if CORE.is_rp2040: + return cv.declare_id(RP2040UartComponent)(value) + raise NotImplementedError + + +UARTParityOptions = uart_ns.enum("UARTParityOptions") +UART_PARITY_OPTIONS = { + "NONE": UARTParityOptions.UART_CONFIG_PARITY_NONE, + "EVEN": UARTParityOptions.UART_CONFIG_PARITY_EVEN, + "ODD": UARTParityOptions.UART_CONFIG_PARITY_ODD, +} + +CONF_STOP_BITS = "stop_bits" +CONF_DATA_BITS = "data_bits" +CONF_PARITY = "parity" + +UARTDirection = uart_ns.enum("UARTDirection") +UART_DIRECTIONS = { + "RX": UARTDirection.UART_DIRECTION_RX, + "TX": UARTDirection.UART_DIRECTION_TX, + "BOTH": UARTDirection.UART_DIRECTION_BOTH, +} + +# The reason for having CONF_BYTES at 150 by default: +# +# The log message buffer size is 512 bytes by default. About 35 bytes are +# used for the log prefix. That leaves us with 477 bytes for logging data. +# The default log output is hex, which uses 3 characters per represented +# byte (2 hex chars + 1 separator). That means that 477 / 3 = 159 bytes +# can be represented in a single log line. Using 150, because people love +# round numbers. +AFTER_DEFAULTS = {CONF_BYTES: 150, CONF_TIMEOUT: "100ms"} + +# By default, log in hex format when no specific sequence is provided. +DEFAULT_DEBUG_OUTPUT = "UARTDebug::log_hex(direction, bytes, ':');" +DEFAULT_SEQUENCE = [{CONF_LAMBDA: make_data_base(DEFAULT_DEBUG_OUTPUT)}] + + +def maybe_empty_debug(value): + if value is None: + value = {} + return DEBUG_SCHEMA(value) + + +DEBUG_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(UARTDebugger), + cv.Optional(CONF_DIRECTION, default="BOTH"): cv.enum( + UART_DIRECTIONS, upper=True + ), + cv.Optional(CONF_AFTER, default=AFTER_DEFAULTS): cv.Schema( + { + cv.Optional( + CONF_BYTES, default=AFTER_DEFAULTS[CONF_BYTES] + ): cv.validate_bytes, + cv.Optional( + CONF_TIMEOUT, default=AFTER_DEFAULTS[CONF_TIMEOUT] + ): cv.positive_time_period_milliseconds, + cv.Optional(CONF_DELIMITER): cv.templatable(validate_raw_data), + } + ), + cv.Optional( + CONF_SEQUENCE, default=DEFAULT_SEQUENCE + ): automation.validate_automation(), + cv.Optional(CONF_DUMMY_RECEIVER, default=False): cv.boolean, + cv.GenerateID(CONF_DUMMY_RECEIVER_ID): cv.declare_id(UARTDummyReceiver), + } +) + +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): _uart_declare_type, + cv.Required(CONF_BAUD_RATE): cv.int_range(min=1), + cv.Optional(CONF_TX_PIN): pins.internal_gpio_output_pin_schema, + cv.Optional(CONF_RX_PIN): validate_rx_pin, + cv.Optional(CONF_RX_BUFFER_SIZE, default=256): cv.validate_bytes, + cv.Optional(CONF_STOP_BITS, default=1): cv.one_of(1, 2, int=True), + cv.Optional(CONF_DATA_BITS, default=8): cv.int_range(min=5, max=8), + cv.Optional(CONF_PARITY, default="NONE"): cv.enum( + UART_PARITY_OPTIONS, upper=True + ), + cv.Optional(CONF_INVERT): cv.invalid( + "This option has been removed. Please instead use invert in the tx/rx pin schemas." + ), + cv.Optional(CONF_DEBUG): maybe_empty_debug, + } + ).extend(cv.COMPONENT_SCHEMA), + cv.has_at_least_one_key(CONF_TX_PIN, CONF_RX_PIN), + validate_invert_esp32, +) + + +async def debug_to_code(config, parent): + trigger = cg.new_Pvariable(config[CONF_TRIGGER_ID], parent) + await cg.register_component(trigger, config) + for action in config[CONF_SEQUENCE]: + await automation.build_automation( + trigger, + [(UARTDirection, "direction"), (cg.std_vector.template(cg.uint8), "bytes")], + action, + ) + cg.add(trigger.set_direction(config[CONF_DIRECTION])) + after = config[CONF_AFTER] + cg.add(trigger.set_after_bytes(after[CONF_BYTES])) + cg.add(trigger.set_after_timeout(after[CONF_TIMEOUT])) + if CONF_DELIMITER in after: + data = after[CONF_DELIMITER] + if isinstance(data, bytes): + data = list(data) + for byte in after[CONF_DELIMITER]: + cg.add(trigger.add_delimiter_byte(byte)) + if config[CONF_DUMMY_RECEIVER]: + dummy = cg.new_Pvariable(config[CONF_DUMMY_RECEIVER_ID], parent) + await cg.register_component(dummy, {}) + cg.add_define("USE_UART_DEBUGGER") + + +async def to_code(config): + cg.add_global(uart_ns.using) + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + + cg.add(var.set_baud_rate(config[CONF_BAUD_RATE])) + + if CONF_TX_PIN in config: + tx_pin = await cg.gpio_pin_expression(config[CONF_TX_PIN]) + cg.add(var.set_tx_pin(tx_pin)) + if CONF_RX_PIN in config: + rx_pin = await cg.gpio_pin_expression(config[CONF_RX_PIN]) + cg.add(var.set_rx_pin(rx_pin)) + cg.add(var.set_rx_buffer_size(config[CONF_RX_BUFFER_SIZE])) + cg.add(var.set_stop_bits(config[CONF_STOP_BITS])) + cg.add(var.set_data_bits(config[CONF_DATA_BITS])) + cg.add(var.set_parity(config[CONF_PARITY])) + + if CONF_DEBUG in config: + await debug_to_code(config[CONF_DEBUG], var) + + +# A schema to use for all UART devices, all UART integrations must extend this! +UART_DEVICE_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_UART_ID): cv.use_id(UARTComponent), + } +) + +KEY_UART_DEVICES = "uart_devices" + + +def final_validate_device_schema( + name: str, + *, + baud_rate: Optional[int] = None, + require_tx: bool = False, + require_rx: bool = False, + parity: Optional[str] = None, + stop_bits: Optional[int] = None, +): + def validate_baud_rate(value): + if value != baud_rate: + raise cv.Invalid( + f"Component {name} requires baud rate {baud_rate} for the uart bus" + ) + return value + + def validate_pin(opt, device): + def validator(value): + if opt in device: + raise cv.Invalid( + f"The uart {opt} is used both by {name} and {device[opt]}, " + f"but can only be used by one. Please create a new uart bus for {name}." + ) + device[opt] = name + return value + + return validator + + def validate_parity(value): + if value != parity: + raise cv.Invalid( + f"Component {name} requires parity {parity} for the uart bus" + ) + return value + + def validate_stop_bits(value): + if value != stop_bits: + raise cv.Invalid( + f"Component {name} requires stop bits {stop_bits} for the uart bus" + ) + return value + + def validate_hub(hub_config): + hub_schema = {} + uart_id = hub_config[CONF_ID] + devices = fv.full_config.get().data.setdefault(KEY_UART_DEVICES, {}) + device = devices.setdefault(uart_id, {}) + + if require_tx: + hub_schema[ + cv.Required( + CONF_TX_PIN, + msg=f"Component {name} requires this uart bus to declare a tx_pin", + ) + ] = validate_pin(CONF_TX_PIN, device) + if require_rx: + hub_schema[ + cv.Required( + CONF_RX_PIN, + msg=f"Component {name} requires this uart bus to declare a rx_pin", + ) + ] = validate_pin(CONF_RX_PIN, device) + if baud_rate is not None: + hub_schema[cv.Required(CONF_BAUD_RATE)] = validate_baud_rate + if parity is not None: + hub_schema[cv.Required(CONF_PARITY)] = validate_parity + if stop_bits is not None: + hub_schema[cv.Required(CONF_STOP_BITS)] = validate_stop_bits + return cv.Schema(hub_schema, extra=cv.ALLOW_EXTRA)(hub_config) + + return cv.Schema( + {cv.Required(CONF_UART_ID): fv.id_declaration_match_schema(validate_hub)}, + extra=cv.ALLOW_EXTRA, + ) + + +async def register_uart_device(var, config): + """Register a UART device, setting up all the internal values. + + This is a coroutine, you need to await it with a 'yield' expression! + """ + parent = await cg.get_variable(config[CONF_UART_ID]) + cg.add(var.set_uart_parent(parent)) + + +@automation.register_action( + "uart.write", + UARTWriteAction, + cv.maybe_simple_value( + { + cv.GenerateID(): cv.use_id(UARTComponent), + cv.Required(CONF_DATA): cv.templatable(validate_raw_data), + }, + key=CONF_DATA, + ), +) +async def uart_write_to_code(config, action_id, template_arg, args): + var = cg.new_Pvariable(action_id, template_arg) + await cg.register_parented(var, config[CONF_ID]) + data = config[CONF_DATA] + if isinstance(data, bytes): + data = list(data) + + if cg.is_template(data): + templ = await cg.templatable(data, args, cg.std_vector.template(cg.uint8)) + cg.add(var.set_data_template(templ)) + else: + cg.add(var.set_data_static(data)) + return var diff --git a/1u/yaml/with_uart_output/screek-mod-components/uart/automation.h b/1u/yaml/with_uart_output/screek-mod-components/uart/automation.h new file mode 100644 index 0000000..b6a50ea --- /dev/null +++ b/1u/yaml/with_uart_output/screek-mod-components/uart/automation.h @@ -0,0 +1,38 @@ +#pragma once + +#include "uart.h" +#include "esphome/core/automation.h" + +#include + +namespace esphome { +namespace uart { + +template class UARTWriteAction : public Action, public Parented { + public: + void set_data_template(std::function(Ts...)> func) { + this->data_func_ = func; + this->static_ = false; + } + void set_data_static(const std::vector &data) { + this->data_static_ = data; + this->static_ = true; + } + + void play(Ts... x) override { + if (this->static_) { + this->parent_->write_array(this->data_static_); + } else { + auto val = this->data_func_(x...); + this->parent_->write_array(val); + } + } + + protected: + bool static_{false}; + std::function(Ts...)> data_func_{}; + std::vector data_static_{}; +}; + +} // namespace uart +} // namespace esphome diff --git a/1u/yaml/with_uart_output/screek-mod-components/uart/switch/__init__.py b/1u/yaml/with_uart_output/screek-mod-components/uart/switch/__init__.py new file mode 100644 index 0000000..60f5dda --- /dev/null +++ b/1u/yaml/with_uart_output/screek-mod-components/uart/switch/__init__.py @@ -0,0 +1,37 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import switch, uart +from esphome.const import CONF_DATA, CONF_SEND_EVERY +from esphome.core import HexInt +from .. import uart_ns, validate_raw_data + +DEPENDENCIES = ["uart"] + +UARTSwitch = uart_ns.class_("UARTSwitch", switch.Switch, uart.UARTDevice, cg.Component) + + +CONFIG_SCHEMA = ( + switch.switch_schema(UARTSwitch, block_inverted=True) + .extend( + { + cv.Required(CONF_DATA): validate_raw_data, + cv.Optional(CONF_SEND_EVERY): cv.positive_time_period_milliseconds, + } + ) + .extend(uart.UART_DEVICE_SCHEMA) + .extend(cv.COMPONENT_SCHEMA) +) + + +async def to_code(config): + var = await switch.new_switch(config) + await cg.register_component(var, config) + await uart.register_uart_device(var, config) + + data = config[CONF_DATA] + if isinstance(data, bytes): + data = [HexInt(x) for x in data] + cg.add(var.set_data(data)) + + if CONF_SEND_EVERY in config: + cg.add(var.set_send_every(config[CONF_SEND_EVERY])) diff --git a/1u/yaml/with_uart_output/screek-mod-components/uart/switch/uart_switch.cpp b/1u/yaml/with_uart_output/screek-mod-components/uart/switch/uart_switch.cpp new file mode 100644 index 0000000..ffed08c --- /dev/null +++ b/1u/yaml/with_uart_output/screek-mod-components/uart/switch/uart_switch.cpp @@ -0,0 +1,47 @@ +#include "uart_switch.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace uart { + +static const char *const TAG = "uart.switch"; + +void UARTSwitch::loop() { + if (this->state && this->send_every_) { + const uint32_t now = millis(); + if (now - this->last_transmission_ > this->send_every_) { + this->write_command_(); + this->last_transmission_ = now; + } + } +} + +void UARTSwitch::write_command_() { + ESP_LOGD(TAG, "'%s': Sending data...", this->get_name().c_str()); + this->write_array(this->data_.data(), this->data_.size()); +} + +void UARTSwitch::write_state(bool state) { + if (!state) { + this->publish_state(false); + return; + } + + this->publish_state(true); + this->write_command_(); + + if (this->send_every_ == 0) { + this->publish_state(false); + } else { + this->last_transmission_ = millis(); + } +} +void UARTSwitch::dump_config() { + LOG_SWITCH("", "UART Switch", this); + if (this->send_every_) { + ESP_LOGCONFIG(TAG, " Send Every: %u", this->send_every_); + } +} + +} // namespace uart +} // namespace esphome diff --git a/1u/yaml/with_uart_output/screek-mod-components/uart/switch/uart_switch.h b/1u/yaml/with_uart_output/screek-mod-components/uart/switch/uart_switch.h new file mode 100644 index 0000000..4f24d76 --- /dev/null +++ b/1u/yaml/with_uart_output/screek-mod-components/uart/switch/uart_switch.h @@ -0,0 +1,30 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/uart/uart.h" +#include "esphome/components/switch/switch.h" + +#include + +namespace esphome { +namespace uart { + +class UARTSwitch : public switch_::Switch, public UARTDevice, public Component { + public: + void loop() override; + + void set_data(const std::vector &data) { data_ = data; } + void set_send_every(uint32_t send_every) { this->send_every_ = send_every; } + + void dump_config() override; + + protected: + void write_command_(); + void write_state(bool state) override; + std::vector data_; + uint32_t send_every_; + uint32_t last_transmission_; +}; + +} // namespace uart +} // namespace esphome diff --git a/1u/yaml/with_uart_output/screek-mod-components/uart/uart.cpp b/1u/yaml/with_uart_output/screek-mod-components/uart/uart.cpp new file mode 100644 index 0000000..22a22e2 --- /dev/null +++ b/1u/yaml/with_uart_output/screek-mod-components/uart/uart.cpp @@ -0,0 +1,46 @@ +#include "uart.h" +#include "esphome/core/log.h" +#include "esphome/core/helpers.h" +#include "esphome/core/application.h" +#include "esphome/core/defines.h" + +namespace esphome { +namespace uart { + +static const char *const TAG = "uart"; + +void UARTDevice::check_uart_settings(uint32_t baud_rate, uint8_t stop_bits, UARTParityOptions parity, + uint8_t data_bits) { + if (this->parent_->get_baud_rate() != baud_rate) { + ESP_LOGE(TAG, " Invalid baud_rate: Integration requested baud_rate %u but you have %u!", baud_rate, + this->parent_->get_baud_rate()); + } + if (this->parent_->get_stop_bits() != stop_bits) { + ESP_LOGE(TAG, " Invalid stop bits: Integration requested stop_bits %u but you have %u!", stop_bits, + this->parent_->get_stop_bits()); + } + if (this->parent_->get_data_bits() != data_bits) { + ESP_LOGE(TAG, " Invalid number of data bits: Integration requested %u data bits but you have %u!", data_bits, + this->parent_->get_data_bits()); + } + if (this->parent_->get_parity() != parity) { + ESP_LOGE(TAG, " Invalid parity: Integration requested parity %s but you have %s!", + LOG_STR_ARG(parity_to_str(parity)), LOG_STR_ARG(parity_to_str(this->parent_->get_parity()))); + } +} + +const LogString *parity_to_str(UARTParityOptions parity) { + switch (parity) { + case UART_CONFIG_PARITY_NONE: + return LOG_STR("NONE"); + case UART_CONFIG_PARITY_EVEN: + return LOG_STR("EVEN"); + case UART_CONFIG_PARITY_ODD: + return LOG_STR("ODD"); + default: + return LOG_STR("UNKNOWN"); + } +} + +} // namespace uart +} // namespace esphome diff --git a/1u/yaml/with_uart_output/screek-mod-components/uart/uart.h b/1u/yaml/with_uart_output/screek-mod-components/uart/uart.h new file mode 100644 index 0000000..d41dbe2 --- /dev/null +++ b/1u/yaml/with_uart_output/screek-mod-components/uart/uart.h @@ -0,0 +1,72 @@ +#pragma once + +#include +#include "esphome/core/component.h" +#include "esphome/core/hal.h" +#include "esphome/core/log.h" +#include "uart_component.h" + +namespace esphome { +namespace uart { + +class UARTDevice { + public: + UARTDevice() = default; + UARTDevice(UARTComponent *parent) : parent_(parent) {} + + void set_uart_parent(UARTComponent *parent) { this->parent_ = parent; } + + void write_byte(uint8_t data) { this->parent_->write_byte(data); } + + void write_array(const uint8_t *data, size_t len) { this->parent_->write_array(data, len); } + void write_array(const std::vector &data) { this->parent_->write_array(data); } + template void write_array(const std::array &data) { + this->parent_->write_array(data.data(), data.size()); + } + + void write_str(const char *str) { this->parent_->write_str(str); } + + bool read_byte(uint8_t *data) { return this->parent_->read_byte(data); } + bool peek_byte(uint8_t *data) { return this->parent_->peek_byte(data); } + + bool read_array(uint8_t *data, size_t len) { return this->parent_->read_array(data, len); } + template optional> read_array() { // NOLINT + std::array res; + if (!this->read_array(res.data(), N)) { + return {}; + } + return res; + } + + int available() { return this->parent_->available(); } + + void flush() { return this->parent_->flush(); } + + // Compat APIs + int read() { + uint8_t data; + if (!this->read_byte(&data)) + return -1; + return data; + } + size_t write(uint8_t data) { + this->write_byte(data); + return 1; + } + int peek() { + uint8_t data; + if (!this->peek_byte(&data)) + return -1; + return data; + } + + /// Check that the configuration of the UART bus matches the provided values and otherwise print a warning + void check_uart_settings(uint32_t baud_rate, uint8_t stop_bits = 1, + UARTParityOptions parity = UART_CONFIG_PARITY_NONE, uint8_t data_bits = 8); + + protected: + UARTComponent *parent_{nullptr}; +}; + +} // namespace uart +} // namespace esphome diff --git a/1u/yaml/with_uart_output/screek-mod-components/uart/uart_component.cpp b/1u/yaml/with_uart_output/screek-mod-components/uart/uart_component.cpp new file mode 100644 index 0000000..09b8c97 --- /dev/null +++ b/1u/yaml/with_uart_output/screek-mod-components/uart/uart_component.cpp @@ -0,0 +1,24 @@ +#include "uart_component.h" + +namespace esphome { +namespace uart { + +static const char *const TAG = "uart"; + +bool UARTComponent::check_read_timeout_(size_t len) { + if (this->available() >= int(len)) + return true; + + uint32_t start_time = millis(); + while (this->available() < int(len)) { + if (millis() - start_time > 100) { + ESP_LOGE(TAG, "Reading from UART timed out at byte %u!", this->available()); + return false; + } + yield(); + } + return true; +} + +} // namespace uart +} // namespace esphome diff --git a/1u/yaml/with_uart_output/screek-mod-components/uart/uart_component.h b/1u/yaml/with_uart_output/screek-mod-components/uart/uart_component.h new file mode 100644 index 0000000..42702cf --- /dev/null +++ b/1u/yaml/with_uart_output/screek-mod-components/uart/uart_component.h @@ -0,0 +1,89 @@ +#pragma once + +#include +#include +#include "esphome/core/defines.h" +#include "esphome/core/component.h" +#include "esphome/core/hal.h" +#include "esphome/core/log.h" +#ifdef USE_UART_DEBUGGER +#include "esphome/core/automation.h" +#endif + +namespace esphome { +namespace uart { + +enum UARTParityOptions { + UART_CONFIG_PARITY_NONE, + UART_CONFIG_PARITY_EVEN, + UART_CONFIG_PARITY_ODD, +}; + +#ifdef USE_UART_DEBUGGER +enum UARTDirection { + UART_DIRECTION_RX, + UART_DIRECTION_TX, + UART_DIRECTION_BOTH, +}; +#endif + +const LogString *parity_to_str(UARTParityOptions parity); + +class UARTComponent { + public: + void write_array(const std::vector &data) { this->write_array(&data[0], data.size()); } + void write_byte(uint8_t data) { this->write_array(&data, 1); }; + void write_str(const char *str) { + const auto *data = reinterpret_cast(str); + this->write_array(data, strlen(str)); + }; + + virtual void write_array(const uint8_t *data, size_t len) = 0; + + bool read_byte(uint8_t *data) { return this->read_array(data, 1); }; + virtual bool peek_byte(uint8_t *data) = 0; + virtual bool read_array(uint8_t *data, size_t len) = 0; + + /// Return available number of bytes. + virtual int available() = 0; + /// Block until all bytes have been written to the UART bus. + virtual void flush() = 0; + + void set_tx_pin(InternalGPIOPin *tx_pin) { this->tx_pin_ = tx_pin; } + void set_rx_pin(InternalGPIOPin *rx_pin) { this->rx_pin_ = rx_pin; } + void set_rx_buffer_size(size_t rx_buffer_size) { this->rx_buffer_size_ = rx_buffer_size; } + size_t get_rx_buffer_size() { return this->rx_buffer_size_; } + + void set_stop_bits(uint8_t stop_bits) { this->stop_bits_ = stop_bits; } + uint8_t get_stop_bits() const { return this->stop_bits_; } + void set_data_bits(uint8_t data_bits) { this->data_bits_ = data_bits; } + uint8_t get_data_bits() const { return this->data_bits_; } + void set_parity(UARTParityOptions parity) { this->parity_ = parity; } + UARTParityOptions get_parity() const { return this->parity_; } + void set_baud_rate(uint32_t baud_rate) { baud_rate_ = baud_rate; } + uint32_t get_baud_rate() const { return baud_rate_; } + +#ifdef USE_UART_DEBUGGER + void add_debug_callback(std::function &&callback) { + this->debug_callback_.add(std::move(callback)); + } +#endif + + protected: + virtual void check_logger_conflict() = 0; + bool check_read_timeout_(size_t len = 1); + + InternalGPIOPin *tx_pin_; + InternalGPIOPin *rx_pin_; + size_t rx_buffer_size_; + uint32_t baud_rate_; + uint8_t stop_bits_; + uint8_t data_bits_; + UARTParityOptions parity_; +#ifdef USE_UART_DEBUGGER + CallbackManager debug_callback_{}; +#endif +}; + +} // namespace uart +} // namespace esphome diff --git a/1u/yaml/with_uart_output/screek-mod-components/uart/uart_component_esp32_arduino.cpp b/1u/yaml/with_uart_output/screek-mod-components/uart/uart_component_esp32_arduino.cpp new file mode 100644 index 0000000..8bfbe3c --- /dev/null +++ b/1u/yaml/with_uart_output/screek-mod-components/uart/uart_component_esp32_arduino.cpp @@ -0,0 +1,184 @@ +#ifdef USE_ESP32_FRAMEWORK_ARDUINO +#include "esphome/core/application.h" +#include "esphome/core/defines.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" +#include "uart_component_esp32_arduino.h" + +#ifdef USE_LOGGER +#include "esphome/components/logger/logger.h" +#endif + +namespace esphome { +namespace uart { +static const char *const TAG = "uart.arduino_esp32"; + +static const uint32_t UART_PARITY_EVEN = 0 << 0; +static const uint32_t UART_PARITY_ODD = 1 << 0; +static const uint32_t UART_PARITY_ENABLE = 1 << 1; +static const uint32_t UART_NB_BIT_5 = 0 << 2; +static const uint32_t UART_NB_BIT_6 = 1 << 2; +static const uint32_t UART_NB_BIT_7 = 2 << 2; +static const uint32_t UART_NB_BIT_8 = 3 << 2; +static const uint32_t UART_NB_STOP_BIT_1 = 1 << 4; +static const uint32_t UART_NB_STOP_BIT_2 = 3 << 4; +static const uint32_t UART_TICK_APB_CLOCK = 1 << 27; + +uint32_t ESP32ArduinoUARTComponent::get_config() { + uint32_t config = 0; + + /* + * All bits numbers below come from + * framework-arduinoespressif32/cores/esp32/esp32-hal-uart.h + * And more specifically conf0 union in uart_dev_t. + * + * Below is bit used from conf0 union. + * : + * parity:0 0:even 1:odd + * parity_en:1 Set this bit to enable uart parity check. + * bit_num:2-4 0:5bits 1:6bits 2:7bits 3:8bits + * stop_bit_num:4-6 stop bit. 1:1bit 2:1.5bits 3:2bits + * tick_ref_always_on:27 select the clock.1:apb clock:ref_tick + */ + + if (this->parity_ == UART_CONFIG_PARITY_EVEN) { + config |= UART_PARITY_EVEN | UART_PARITY_ENABLE; + } else if (this->parity_ == UART_CONFIG_PARITY_ODD) { + config |= UART_PARITY_ODD | UART_PARITY_ENABLE; + } + + switch (this->data_bits_) { + case 5: + config |= UART_NB_BIT_5; + break; + case 6: + config |= UART_NB_BIT_6; + break; + case 7: + config |= UART_NB_BIT_7; + break; + case 8: + config |= UART_NB_BIT_8; + break; + } + + if (this->stop_bits_ == 1) { + config |= UART_NB_STOP_BIT_1; + } else { + config |= UART_NB_STOP_BIT_2; + } + + config |= UART_TICK_APB_CLOCK; + + return config; +} + +void ESP32ArduinoUARTComponent::setup() { + ESP_LOGCONFIG(TAG, "Setting up UART..."); + // Use Arduino HardwareSerial UARTs if all used pins match the ones + // preconfigured by the platform. For example if RX disabled but TX pin + // is 1 we still want to use Serial. + bool is_default_tx, is_default_rx; +#ifdef CONFIG_IDF_TARGET_ESP32C3 + is_default_tx = tx_pin_ == nullptr || tx_pin_->get_pin() == 21; + is_default_rx = rx_pin_ == nullptr || rx_pin_->get_pin() == 20; + +#else + is_default_tx = tx_pin_ == nullptr || tx_pin_->get_pin() == 1; + is_default_rx = rx_pin_ == nullptr || rx_pin_->get_pin() == 3; +#endif + +// 一个临时补丁,让USB-CDC串口和UART串口共存!(*23年3月7日*17时15分) +#if defined(USE_ARDUINO) && defined(ARDUINO_USB_CDC_ON_BOOT) && ((defined(USE_ESP32_VARIANT_ESP32C3) && defined(ARDUINO_USB_MODE)) || defined(USE_ESP32_VARIANT_ESP32S2)) + + static uint8_t next_uart_num = 1; + + this->number_ = next_uart_num; + this->hw_serial_ = new HardwareSerial(next_uart_num++); // NOLINT(cppcoreguidelines-owning-memory) + +#else + // 传统方式,进行检查,进行判断代码。(*23年3月7日*17时22分) + if (is_default_tx && is_default_rx) { + this->hw_serial_ = &Serial; + } else { + static uint8_t next_uart_num = 1; + this->number_ = next_uart_num; + this->hw_serial_ = new HardwareSerial(next_uart_num++); // NOLINT(cppcoreguidelines-owning-memory) + } +#endif + + int8_t tx = this->tx_pin_ != nullptr ? this->tx_pin_->get_pin() : -1; + int8_t rx = this->rx_pin_ != nullptr ? this->rx_pin_->get_pin() : -1; + bool invert = false; + if (tx_pin_ != nullptr && tx_pin_->is_inverted()) + invert = true; + if (rx_pin_ != nullptr && rx_pin_->is_inverted()) + invert = true; + this->hw_serial_->begin(this->baud_rate_, get_config(), rx, tx, invert); + this->hw_serial_->setRxBufferSize(this->rx_buffer_size_); +} + +void ESP32ArduinoUARTComponent::dump_config() { + ESP_LOGCONFIG(TAG, "UART Bus %d:", this->number_); + LOG_PIN(" TX Pin: ", tx_pin_); + LOG_PIN(" RX Pin: ", rx_pin_); + if (this->rx_pin_ != nullptr) { + ESP_LOGCONFIG(TAG, " RX Buffer Size: %u", this->rx_buffer_size_); + } + ESP_LOGCONFIG(TAG, " Baud Rate: %u baud", this->baud_rate_); + ESP_LOGCONFIG(TAG, " Data Bits: %u", this->data_bits_); + ESP_LOGCONFIG(TAG, " Parity: %s", LOG_STR_ARG(parity_to_str(this->parity_))); + ESP_LOGCONFIG(TAG, " Stop bits: %u", this->stop_bits_); + this->check_logger_conflict(); +} + +void ESP32ArduinoUARTComponent::write_array(const uint8_t *data, size_t len) { + this->hw_serial_->write(data, len); +#ifdef USE_UART_DEBUGGER + for (size_t i = 0; i < len; i++) { + this->debug_callback_.call(UART_DIRECTION_TX, data[i]); + } +#endif +} + +bool ESP32ArduinoUARTComponent::peek_byte(uint8_t *data) { + if (!this->check_read_timeout_()) + return false; + *data = this->hw_serial_->peek(); + return true; +} + +bool ESP32ArduinoUARTComponent::read_array(uint8_t *data, size_t len) { + if (!this->check_read_timeout_(len)) + return false; + this->hw_serial_->readBytes(data, len); +#ifdef USE_UART_DEBUGGER + for (size_t i = 0; i < len; i++) { + this->debug_callback_.call(UART_DIRECTION_RX, data[i]); + } +#endif + return true; +} + +int ESP32ArduinoUARTComponent::available() { return this->hw_serial_->available(); } +void ESP32ArduinoUARTComponent::flush() { + ESP_LOGVV(TAG, " Flushing..."); + this->hw_serial_->flush(); +} + +void ESP32ArduinoUARTComponent::check_logger_conflict() { +#ifdef USE_LOGGER + if (this->hw_serial_ == nullptr || logger::global_logger->get_baud_rate() == 0) { + return; + } + + if (this->hw_serial_ == logger::global_logger->get_hw_serial()) { + ESP_LOGW(TAG, " You're using the same serial port for logging and the UART component. Please " + "disable logging over the serial port by setting logger->baud_rate to 0."); + } +#endif +} + +} // namespace uart +} // namespace esphome +#endif // USE_ESP32_FRAMEWORK_ARDUINO diff --git a/1u/yaml/with_uart_output/screek-mod-components/uart/uart_component_esp32_arduino.h b/1u/yaml/with_uart_output/screek-mod-components/uart/uart_component_esp32_arduino.h new file mode 100644 index 0000000..4a000b1 --- /dev/null +++ b/1u/yaml/with_uart_output/screek-mod-components/uart/uart_component_esp32_arduino.h @@ -0,0 +1,41 @@ +#pragma once + +#ifdef USE_ESP32_FRAMEWORK_ARDUINO + +#include +#include +#include "esphome/core/component.h" +#include "esphome/core/hal.h" +#include "esphome/core/log.h" +#include "uart_component.h" + +namespace esphome { +namespace uart { + +class ESP32ArduinoUARTComponent : public UARTComponent, public Component { + public: + void setup() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::BUS; } + + void write_array(const uint8_t *data, size_t len) override; + + bool peek_byte(uint8_t *data) override; + bool read_array(uint8_t *data, size_t len) override; + + int available() override; + void flush() override; + + uint32_t get_config(); + + protected: + void check_logger_conflict() override; + + HardwareSerial *hw_serial_{nullptr}; + uint8_t number_{0}; +}; + +} // namespace uart +} // namespace esphome + +#endif // USE_ESP32_FRAMEWORK_ARDUINO diff --git a/1u/yaml/with_uart_output/screek-mod-components/uart/uart_component_esp8266.cpp b/1u/yaml/with_uart_output/screek-mod-components/uart/uart_component_esp8266.cpp new file mode 100644 index 0000000..529108f --- /dev/null +++ b/1u/yaml/with_uart_output/screek-mod-components/uart/uart_component_esp8266.cpp @@ -0,0 +1,304 @@ +#ifdef USE_ESP8266 +#include "uart_component_esp8266.h" +#include "esphome/core/application.h" +#include "esphome/core/defines.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +#ifdef USE_LOGGER +#include "esphome/components/logger/logger.h" +#endif + +namespace esphome { +namespace uart { + +static const char *const TAG = "uart.arduino_esp8266"; +bool ESP8266UartComponent::serial0_in_use = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + +uint32_t ESP8266UartComponent::get_config() { + uint32_t config = 0; + + if (this->parity_ == UART_CONFIG_PARITY_NONE) { + config |= UART_PARITY_NONE; + } else if (this->parity_ == UART_CONFIG_PARITY_EVEN) { + config |= UART_PARITY_EVEN; + } else if (this->parity_ == UART_CONFIG_PARITY_ODD) { + config |= UART_PARITY_ODD; + } + + switch (this->data_bits_) { + case 5: + config |= UART_NB_BIT_5; + break; + case 6: + config |= UART_NB_BIT_6; + break; + case 7: + config |= UART_NB_BIT_7; + break; + case 8: + config |= UART_NB_BIT_8; + break; + } + + if (this->stop_bits_ == 1) { + config |= UART_NB_STOP_BIT_1; + } else { + config |= UART_NB_STOP_BIT_2; + } + + if (this->tx_pin_ != nullptr && this->tx_pin_->is_inverted()) + config |= BIT(22); + if (this->rx_pin_ != nullptr && this->rx_pin_->is_inverted()) + config |= BIT(19); + + return config; +} + +void ESP8266UartComponent::setup() { + ESP_LOGCONFIG(TAG, "Setting up UART bus..."); + // Use Arduino HardwareSerial UARTs if all used pins match the ones + // preconfigured by the platform. For example if RX disabled but TX pin + // is 1 we still want to use Serial. + SerialConfig config = static_cast(get_config()); + + if (!ESP8266UartComponent::serial0_in_use && (tx_pin_ == nullptr || tx_pin_->get_pin() == 1) && + (rx_pin_ == nullptr || rx_pin_->get_pin() == 3) +#ifdef USE_LOGGER + // we will use UART0 if logger isn't using it in swapped mode + && (logger::global_logger->get_hw_serial() == nullptr || + logger::global_logger->get_uart() != logger::UART_SELECTION_UART0_SWAP) +#endif + ) { + this->hw_serial_ = &Serial; + this->hw_serial_->begin(this->baud_rate_, config); + this->hw_serial_->setRxBufferSize(this->rx_buffer_size_); + ESP8266UartComponent::serial0_in_use = true; + } else if (!ESP8266UartComponent::serial0_in_use && (tx_pin_ == nullptr || tx_pin_->get_pin() == 15) && + (rx_pin_ == nullptr || rx_pin_->get_pin() == 13) +#ifdef USE_LOGGER + // we will use UART0 swapped if logger isn't using it in regular mode + && (logger::global_logger->get_hw_serial() == nullptr || + logger::global_logger->get_uart() != logger::UART_SELECTION_UART0) +#endif + ) { + this->hw_serial_ = &Serial; + this->hw_serial_->begin(this->baud_rate_, config); + this->hw_serial_->setRxBufferSize(this->rx_buffer_size_); + this->hw_serial_->swap(); + ESP8266UartComponent::serial0_in_use = true; + } else if ((tx_pin_ == nullptr || tx_pin_->get_pin() == 2) && (rx_pin_ == nullptr || rx_pin_->get_pin() == 8)) { + this->hw_serial_ = &Serial1; + this->hw_serial_->begin(this->baud_rate_, config); + this->hw_serial_->setRxBufferSize(this->rx_buffer_size_); + } else { + this->sw_serial_ = new ESP8266SoftwareSerial(); // NOLINT + this->sw_serial_->setup(tx_pin_, rx_pin_, this->baud_rate_, this->stop_bits_, this->data_bits_, this->parity_, + this->rx_buffer_size_); + } +} + +void ESP8266UartComponent::dump_config() { + ESP_LOGCONFIG(TAG, "UART Bus:"); + LOG_PIN(" TX Pin: ", tx_pin_); + LOG_PIN(" RX Pin: ", rx_pin_); + if (this->rx_pin_ != nullptr) { + ESP_LOGCONFIG(TAG, " RX Buffer Size: %u", this->rx_buffer_size_); // NOLINT + } + ESP_LOGCONFIG(TAG, " Baud Rate: %u baud", this->baud_rate_); + ESP_LOGCONFIG(TAG, " Data Bits: %u", this->data_bits_); + ESP_LOGCONFIG(TAG, " Parity: %s", LOG_STR_ARG(parity_to_str(this->parity_))); + ESP_LOGCONFIG(TAG, " Stop bits: %u", this->stop_bits_); + if (this->hw_serial_ != nullptr) { + ESP_LOGCONFIG(TAG, " Using hardware serial interface."); + } else { + ESP_LOGCONFIG(TAG, " Using software serial"); + } + this->check_logger_conflict(); +} + +void ESP8266UartComponent::check_logger_conflict() { +#ifdef USE_LOGGER + if (this->hw_serial_ == nullptr || logger::global_logger->get_baud_rate() == 0) { + return; + } + + if (this->hw_serial_ == logger::global_logger->get_hw_serial()) { + ESP_LOGW(TAG, " You're using the same serial port for logging and the UART component. Please " + "disable logging over the serial port by setting logger->baud_rate to 0."); + } +#endif +} + +void ESP8266UartComponent::write_array(const uint8_t *data, size_t len) { + if (this->hw_serial_ != nullptr) { + this->hw_serial_->write(data, len); + } else { + for (size_t i = 0; i < len; i++) + this->sw_serial_->write_byte(data[i]); + } +#ifdef USE_UART_DEBUGGER + for (size_t i = 0; i < len; i++) { + this->debug_callback_.call(UART_DIRECTION_TX, data[i]); + } +#endif +} +bool ESP8266UartComponent::peek_byte(uint8_t *data) { + if (!this->check_read_timeout_()) + return false; + if (this->hw_serial_ != nullptr) { + *data = this->hw_serial_->peek(); + } else { + *data = this->sw_serial_->peek_byte(); + } + return true; +} +bool ESP8266UartComponent::read_array(uint8_t *data, size_t len) { + if (!this->check_read_timeout_(len)) + return false; + if (this->hw_serial_ != nullptr) { + this->hw_serial_->readBytes(data, len); + } else { + for (size_t i = 0; i < len; i++) + data[i] = this->sw_serial_->read_byte(); + } +#ifdef USE_UART_DEBUGGER + for (size_t i = 0; i < len; i++) { + this->debug_callback_.call(UART_DIRECTION_RX, data[i]); + } +#endif + return true; +} +int ESP8266UartComponent::available() { + if (this->hw_serial_ != nullptr) { + return this->hw_serial_->available(); + } else { + return this->sw_serial_->available(); + } +} +void ESP8266UartComponent::flush() { + ESP_LOGVV(TAG, " Flushing..."); + if (this->hw_serial_ != nullptr) { + this->hw_serial_->flush(); + } else { + this->sw_serial_->flush(); + } +} +void ESP8266SoftwareSerial::setup(InternalGPIOPin *tx_pin, InternalGPIOPin *rx_pin, uint32_t baud_rate, + uint8_t stop_bits, uint32_t data_bits, UARTParityOptions parity, + size_t rx_buffer_size) { + this->bit_time_ = F_CPU / baud_rate; + this->rx_buffer_size_ = rx_buffer_size; + this->stop_bits_ = stop_bits; + this->data_bits_ = data_bits; + this->parity_ = parity; + if (tx_pin != nullptr) { + gpio_tx_pin_ = tx_pin; + gpio_tx_pin_->setup(); + tx_pin_ = gpio_tx_pin_->to_isr(); + tx_pin_.digital_write(true); + } + if (rx_pin != nullptr) { + gpio_rx_pin_ = rx_pin; + gpio_rx_pin_->setup(); + rx_pin_ = gpio_rx_pin_->to_isr(); + rx_buffer_ = new uint8_t[this->rx_buffer_size_]; // NOLINT + gpio_rx_pin_->attach_interrupt(ESP8266SoftwareSerial::gpio_intr, this, gpio::INTERRUPT_FALLING_EDGE); + } +} +void IRAM_ATTR ESP8266SoftwareSerial::gpio_intr(ESP8266SoftwareSerial *arg) { + uint32_t wait = arg->bit_time_ + arg->bit_time_ / 3 - 500; + const uint32_t start = arch_get_cpu_cycle_count(); + uint8_t rec = 0; + // Manually unroll the loop + for (int i = 0; i < arg->data_bits_; i++) + rec |= arg->read_bit_(&wait, start) << i; + + /* If parity is enabled, just read it and ignore it. */ + /* TODO: Should we check parity? Or is it too slow for nothing added..*/ + if (arg->parity_ == UART_CONFIG_PARITY_EVEN || arg->parity_ == UART_CONFIG_PARITY_ODD) + arg->read_bit_(&wait, start); + + // Stop bit + arg->wait_(&wait, start); + if (arg->stop_bits_ == 2) + arg->wait_(&wait, start); + + arg->rx_buffer_[arg->rx_in_pos_] = rec; + arg->rx_in_pos_ = (arg->rx_in_pos_ + 1) % arg->rx_buffer_size_; + // Clear RX pin so that the interrupt doesn't re-trigger right away again. + arg->rx_pin_.clear_interrupt(); +} +void IRAM_ATTR HOT ESP8266SoftwareSerial::write_byte(uint8_t data) { + if (this->gpio_tx_pin_ == nullptr) { + ESP_LOGE(TAG, "UART doesn't have TX pins set!"); + return; + } + bool parity_bit = false; + bool need_parity_bit = true; + if (this->parity_ == UART_CONFIG_PARITY_EVEN) { + parity_bit = false; + } else if (this->parity_ == UART_CONFIG_PARITY_ODD) { + parity_bit = true; + } else { + need_parity_bit = false; + } + + { + InterruptLock lock; + uint32_t wait = this->bit_time_; + const uint32_t start = arch_get_cpu_cycle_count(); + // Start bit + this->write_bit_(false, &wait, start); + for (int i = 0; i < this->data_bits_; i++) { + bool bit = data & (1 << i); + this->write_bit_(bit, &wait, start); + if (need_parity_bit) + parity_bit ^= bit; + } + if (need_parity_bit) + this->write_bit_(parity_bit, &wait, start); + // Stop bit + this->write_bit_(true, &wait, start); + if (this->stop_bits_ == 2) + this->wait_(&wait, start); + } +} +void IRAM_ATTR ESP8266SoftwareSerial::wait_(uint32_t *wait, const uint32_t &start) { + while (arch_get_cpu_cycle_count() - start < *wait) + ; + *wait += this->bit_time_; +} +bool IRAM_ATTR ESP8266SoftwareSerial::read_bit_(uint32_t *wait, const uint32_t &start) { + this->wait_(wait, start); + return this->rx_pin_.digital_read(); +} +void IRAM_ATTR ESP8266SoftwareSerial::write_bit_(bool bit, uint32_t *wait, const uint32_t &start) { + this->tx_pin_.digital_write(bit); + this->wait_(wait, start); +} +uint8_t ESP8266SoftwareSerial::read_byte() { + if (this->rx_in_pos_ == this->rx_out_pos_) + return 0; + uint8_t data = this->rx_buffer_[this->rx_out_pos_]; + this->rx_out_pos_ = (this->rx_out_pos_ + 1) % this->rx_buffer_size_; + return data; +} +uint8_t ESP8266SoftwareSerial::peek_byte() { + if (this->rx_in_pos_ == this->rx_out_pos_) + return 0; + return this->rx_buffer_[this->rx_out_pos_]; +} +void ESP8266SoftwareSerial::flush() { + // Flush is a NO-OP with software serial, all bytes are written immediately. +} +int ESP8266SoftwareSerial::available() { + int avail = int(this->rx_in_pos_) - int(this->rx_out_pos_); + if (avail < 0) + return avail + this->rx_buffer_size_; + return avail; +} + +} // namespace uart +} // namespace esphome +#endif // USE_ESP8266 diff --git a/1u/yaml/with_uart_output/screek-mod-components/uart/uart_component_esp8266.h b/1u/yaml/with_uart_output/screek-mod-components/uart/uart_component_esp8266.h new file mode 100644 index 0000000..eed14f3 --- /dev/null +++ b/1u/yaml/with_uart_output/screek-mod-components/uart/uart_component_esp8266.h @@ -0,0 +1,79 @@ +#pragma once + +#ifdef USE_ESP8266 + +#include +#include +#include "esphome/core/component.h" +#include "esphome/core/hal.h" +#include "esphome/core/log.h" +#include "uart_component.h" + +namespace esphome { +namespace uart { + +class ESP8266SoftwareSerial { + public: + void setup(InternalGPIOPin *tx_pin, InternalGPIOPin *rx_pin, uint32_t baud_rate, uint8_t stop_bits, + uint32_t data_bits, UARTParityOptions parity, size_t rx_buffer_size); + + uint8_t read_byte(); + uint8_t peek_byte(); + + void flush(); + + void write_byte(uint8_t data); + + int available(); + + protected: + static void gpio_intr(ESP8266SoftwareSerial *arg); + + void wait_(uint32_t *wait, const uint32_t &start); + bool read_bit_(uint32_t *wait, const uint32_t &start); + void write_bit_(bool bit, uint32_t *wait, const uint32_t &start); + + uint32_t bit_time_{0}; + uint8_t *rx_buffer_{nullptr}; + size_t rx_buffer_size_; + volatile size_t rx_in_pos_{0}; + size_t rx_out_pos_{0}; + uint8_t stop_bits_; + uint8_t data_bits_; + UARTParityOptions parity_; + InternalGPIOPin *gpio_tx_pin_{nullptr}; + ISRInternalGPIOPin tx_pin_; + InternalGPIOPin *gpio_rx_pin_{nullptr}; + ISRInternalGPIOPin rx_pin_; +}; + +class ESP8266UartComponent : public UARTComponent, public Component { + public: + void setup() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::BUS; } + + void write_array(const uint8_t *data, size_t len) override; + + bool peek_byte(uint8_t *data) override; + bool read_array(uint8_t *data, size_t len) override; + + int available() override; + void flush() override; + + uint32_t get_config(); + + protected: + void check_logger_conflict() override; + + HardwareSerial *hw_serial_{nullptr}; + ESP8266SoftwareSerial *sw_serial_{nullptr}; + + private: + static bool serial0_in_use; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +}; + +} // namespace uart +} // namespace esphome + +#endif // USE_ESP8266 diff --git a/1u/yaml/with_uart_output/screek-mod-components/uart/uart_component_esp_idf.cpp b/1u/yaml/with_uart_output/screek-mod-components/uart/uart_component_esp_idf.cpp new file mode 100644 index 0000000..80255cc --- /dev/null +++ b/1u/yaml/with_uart_output/screek-mod-components/uart/uart_component_esp_idf.cpp @@ -0,0 +1,206 @@ +#ifdef USE_ESP_IDF + +#include "uart_component_esp_idf.h" +#include "esphome/core/application.h" +#include "esphome/core/defines.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +#ifdef USE_LOGGER +#include "esphome/components/logger/logger.h" +#endif + +namespace esphome { +namespace uart { +static const char *const TAG = "uart.idf"; + +uart_config_t IDFUARTComponent::get_config_() { + uart_parity_t parity = UART_PARITY_DISABLE; + if (this->parity_ == UART_CONFIG_PARITY_EVEN) { + parity = UART_PARITY_EVEN; + } else if (this->parity_ == UART_CONFIG_PARITY_ODD) { + parity = UART_PARITY_ODD; + } + + uart_word_length_t data_bits; + switch (this->data_bits_) { + case 5: + data_bits = UART_DATA_5_BITS; + break; + case 6: + data_bits = UART_DATA_6_BITS; + break; + case 7: + data_bits = UART_DATA_7_BITS; + break; + case 8: + data_bits = UART_DATA_8_BITS; + break; + default: + data_bits = UART_DATA_BITS_MAX; + break; + } + + uart_config_t uart_config; + uart_config.baud_rate = this->baud_rate_; + uart_config.data_bits = data_bits; + uart_config.parity = parity; + uart_config.stop_bits = this->stop_bits_ == 1 ? UART_STOP_BITS_1 : UART_STOP_BITS_2; + uart_config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE; + uart_config.source_clk = UART_SCLK_APB; + uart_config.rx_flow_ctrl_thresh = 122; + + return uart_config; +} + +void IDFUARTComponent::setup() { + static uint8_t next_uart_num = 0; +#ifdef USE_LOGGER + if (logger::global_logger->get_uart_num() == next_uart_num) + next_uart_num++; +#endif + if (next_uart_num >= UART_NUM_MAX) { + ESP_LOGW(TAG, "Maximum number of UART components created already."); + this->mark_failed(); + return; + } + this->uart_num_ = next_uart_num++; + ESP_LOGCONFIG(TAG, "Setting up UART %u...", this->uart_num_); + + this->lock_ = xSemaphoreCreateMutex(); + + xSemaphoreTake(this->lock_, portMAX_DELAY); + + uart_config_t uart_config = this->get_config_(); + esp_err_t err = uart_param_config(this->uart_num_, &uart_config); + if (err != ESP_OK) { + ESP_LOGW(TAG, "uart_param_config failed: %s", esp_err_to_name(err)); + this->mark_failed(); + return; + } + + err = uart_driver_install(this->uart_num_, this->rx_buffer_size_, 0, 0, nullptr, 0); + if (err != ESP_OK) { + ESP_LOGW(TAG, "uart_driver_install failed: %s", esp_err_to_name(err)); + this->mark_failed(); + return; + } + + int8_t tx = this->tx_pin_ != nullptr ? this->tx_pin_->get_pin() : -1; + int8_t rx = this->rx_pin_ != nullptr ? this->rx_pin_->get_pin() : -1; + + err = uart_set_pin(this->uart_num_, tx, rx, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE); + if (err != ESP_OK) { + ESP_LOGW(TAG, "uart_set_pin failed: %s", esp_err_to_name(err)); + this->mark_failed(); + return; + } + + uint32_t invert = 0; + if (this->tx_pin_ != nullptr && this->tx_pin_->is_inverted()) + invert |= UART_SIGNAL_TXD_INV; + if (this->rx_pin_ != nullptr && this->rx_pin_->is_inverted()) + invert |= UART_SIGNAL_RXD_INV; + + err = uart_set_line_inverse(this->uart_num_, invert); + if (err != ESP_OK) { + ESP_LOGW(TAG, "uart_set_line_inverse failed: %s", esp_err_to_name(err)); + this->mark_failed(); + return; + } + + xSemaphoreGive(this->lock_); +} + +void IDFUARTComponent::dump_config() { + ESP_LOGCONFIG(TAG, "UART Bus:"); + ESP_LOGCONFIG(TAG, " Number: %u", this->uart_num_); + LOG_PIN(" TX Pin: ", tx_pin_); + LOG_PIN(" RX Pin: ", rx_pin_); + if (this->rx_pin_ != nullptr) { + ESP_LOGCONFIG(TAG, " RX Buffer Size: %u", this->rx_buffer_size_); + } + ESP_LOGCONFIG(TAG, " Baud Rate: %u baud", this->baud_rate_); + ESP_LOGCONFIG(TAG, " Data Bits: %u", this->data_bits_); + ESP_LOGCONFIG(TAG, " Parity: %s", LOG_STR_ARG(parity_to_str(this->parity_))); + ESP_LOGCONFIG(TAG, " Stop bits: %u", this->stop_bits_); + this->check_logger_conflict(); +} + +void IDFUARTComponent::write_array(const uint8_t *data, size_t len) { + xSemaphoreTake(this->lock_, portMAX_DELAY); + uart_write_bytes(this->uart_num_, data, len); + xSemaphoreGive(this->lock_); +#ifdef USE_UART_DEBUGGER + for (size_t i = 0; i < len; i++) { + this->debug_callback_.call(UART_DIRECTION_TX, data[i]); + } +#endif +} + +bool IDFUARTComponent::peek_byte(uint8_t *data) { + if (!this->check_read_timeout_()) + return false; + xSemaphoreTake(this->lock_, portMAX_DELAY); + if (this->has_peek_) { + *data = this->peek_byte_; + } else { + int len = uart_read_bytes(this->uart_num_, data, 1, 20 / portTICK_RATE_MS); + if (len == 0) { + *data = 0; + } else { + this->has_peek_ = true; + this->peek_byte_ = *data; + } + } + xSemaphoreGive(this->lock_); + return true; +} + +bool IDFUARTComponent::read_array(uint8_t *data, size_t len) { + size_t length_to_read = len; + if (!this->check_read_timeout_(len)) + return false; + xSemaphoreTake(this->lock_, portMAX_DELAY); + if (this->has_peek_) { + length_to_read--; + *data = this->peek_byte_; + data++; + this->has_peek_ = false; + } + if (length_to_read > 0) + uart_read_bytes(this->uart_num_, data, length_to_read, 20 / portTICK_RATE_MS); + xSemaphoreGive(this->lock_); +#ifdef USE_UART_DEBUGGER + for (size_t i = 0; i < len; i++) { + this->debug_callback_.call(UART_DIRECTION_RX, data[i]); + } +#endif + return true; +} + +int IDFUARTComponent::available() { + size_t available; + + xSemaphoreTake(this->lock_, portMAX_DELAY); + uart_get_buffered_data_len(this->uart_num_, &available); + if (this->has_peek_) + available++; + xSemaphoreGive(this->lock_); + + return available; +} + +void IDFUARTComponent::flush() { + ESP_LOGVV(TAG, " Flushing..."); + xSemaphoreTake(this->lock_, portMAX_DELAY); + uart_wait_tx_done(this->uart_num_, portMAX_DELAY); + xSemaphoreGive(this->lock_); +} + +void IDFUARTComponent::check_logger_conflict() {} + +} // namespace uart +} // namespace esphome + +#endif // USE_ESP32 diff --git a/1u/yaml/with_uart_output/screek-mod-components/uart/uart_component_esp_idf.h b/1u/yaml/with_uart_output/screek-mod-components/uart/uart_component_esp_idf.h new file mode 100644 index 0000000..27fb80d --- /dev/null +++ b/1u/yaml/with_uart_output/screek-mod-components/uart/uart_component_esp_idf.h @@ -0,0 +1,39 @@ +#pragma once + +#ifdef USE_ESP_IDF + +#include +#include "esphome/core/component.h" +#include "uart_component.h" + +namespace esphome { +namespace uart { + +class IDFUARTComponent : public UARTComponent, public Component { + public: + void setup() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::BUS; } + + void write_array(const uint8_t *data, size_t len) override; + + bool peek_byte(uint8_t *data) override; + bool read_array(uint8_t *data, size_t len) override; + + int available() override; + void flush() override; + + protected: + void check_logger_conflict() override; + uart_port_t uart_num_; + uart_config_t get_config_(); + SemaphoreHandle_t lock_; + + bool has_peek_{false}; + uint8_t peek_byte_; +}; + +} // namespace uart +} // namespace esphome + +#endif // USE_ESP_IDF diff --git a/1u/yaml/with_uart_output/screek-mod-components/uart/uart_component_rp2040.cpp b/1u/yaml/with_uart_output/screek-mod-components/uart/uart_component_rp2040.cpp new file mode 100644 index 0000000..e2c4708 --- /dev/null +++ b/1u/yaml/with_uart_output/screek-mod-components/uart/uart_component_rp2040.cpp @@ -0,0 +1,184 @@ +#ifdef USE_RP2040 +#include "uart_component_rp2040.h" +#include "esphome/core/application.h" +#include "esphome/core/defines.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +#include + +#ifdef USE_LOGGER +#include "esphome/components/logger/logger.h" +#endif + +namespace esphome { +namespace uart { + +static const char *const TAG = "uart.arduino_rp2040"; + +uint16_t RP2040UartComponent::get_config() { + uint16_t config = 0; + + if (this->parity_ == UART_CONFIG_PARITY_NONE) { + config |= UART_PARITY_NONE; + } else if (this->parity_ == UART_CONFIG_PARITY_EVEN) { + config |= UART_PARITY_EVEN; + } else if (this->parity_ == UART_CONFIG_PARITY_ODD) { + config |= UART_PARITY_ODD; + } + + switch (this->data_bits_) { + case 5: + config |= SERIAL_DATA_5; + break; + case 6: + config |= SERIAL_DATA_6; + break; + case 7: + config |= SERIAL_DATA_7; + break; + case 8: + config |= SERIAL_DATA_8; + break; + } + + if (this->stop_bits_ == 1) { + config |= SERIAL_STOP_BIT_1; + } else { + config |= SERIAL_STOP_BIT_2; + } + + return config; +} + +void RP2040UartComponent::setup() { + ESP_LOGCONFIG(TAG, "Setting up UART bus..."); + + uint16_t config = get_config(); + + constexpr uint32_t valid_tx_uart_0 = __bitset({0, 12, 16, 28}); + constexpr uint32_t valid_tx_uart_1 = __bitset({4, 8, 20, 24}); + + constexpr uint32_t valid_rx_uart_0 = __bitset({1, 13, 17, 29}); + constexpr uint32_t valid_rx_uart_1 = __bitset({5, 9, 21, 25}); + + int8_t tx_hw = -1; + int8_t rx_hw = -1; + + if (this->tx_pin_ != nullptr) { + if (this->tx_pin_->is_inverted()) { + ESP_LOGD(TAG, "An inverted TX pin %u can only be used with SerialPIO", this->tx_pin_->get_pin()); + } else { + if (((1 << this->tx_pin_->get_pin()) & valid_tx_uart_0) != 0) { + tx_hw = 0; + } else if (((1 << this->tx_pin_->get_pin()) & valid_tx_uart_1) != 0) { + tx_hw = 1; + } else { + ESP_LOGD(TAG, "TX pin %u can only be used with SerialPIO", this->tx_pin_->get_pin()); + } + } + } + + if (this->rx_pin_ != nullptr) { + if (this->rx_pin_->is_inverted()) { + ESP_LOGD(TAG, "An inverted RX pin %u can only be used with SerialPIO", this->rx_pin_->get_pin()); + } else { + if (((1 << this->rx_pin_->get_pin()) & valid_rx_uart_0) != 0) { + rx_hw = 0; + } else if (((1 << this->rx_pin_->get_pin()) & valid_rx_uart_1) != 0) { + rx_hw = 1; + } else { + ESP_LOGD(TAG, "RX pin %u can only be used with SerialPIO", this->rx_pin_->get_pin()); + } + } + } + +#ifdef USE_LOGGER + if (tx_hw == rx_hw && logger::global_logger->get_uart() == tx_hw) { + ESP_LOGD(TAG, "Using SerialPIO as UART%d is taken by the logger", tx_hw); + tx_hw = -1; + rx_hw = -1; + } +#endif + + if (tx_hw == -1 || rx_hw == -1 || tx_hw != rx_hw) { + ESP_LOGV(TAG, "Using SerialPIO"); + pin_size_t tx = this->tx_pin_ == nullptr ? SerialPIO::NOPIN : this->tx_pin_->get_pin(); + pin_size_t rx = this->rx_pin_ == nullptr ? SerialPIO::NOPIN : this->rx_pin_->get_pin(); + auto *serial = new SerialPIO(tx, rx, this->rx_buffer_size_); // NOLINT(cppcoreguidelines-owning-memory) + serial->begin(this->baud_rate_, config); + if (this->tx_pin_ != nullptr && this->tx_pin_->is_inverted()) + gpio_set_outover(tx, GPIO_OVERRIDE_INVERT); + if (this->rx_pin_ != nullptr && this->rx_pin_->is_inverted()) + gpio_set_inover(rx, GPIO_OVERRIDE_INVERT); + this->serial_ = serial; + } else { + ESP_LOGV(TAG, "Using Hardware Serial"); + SerialUART *serial; + if (tx_hw == 0) { + serial = &Serial1; + } else { + serial = &Serial2; + } + serial->setTX(this->tx_pin_->get_pin()); + serial->setRX(this->rx_pin_->get_pin()); + serial->setFIFOSize(this->rx_buffer_size_); + serial->begin(this->baud_rate_, config); + this->serial_ = serial; + this->hw_serial_ = true; + } +} + +void RP2040UartComponent::dump_config() { + ESP_LOGCONFIG(TAG, "UART Bus:"); + LOG_PIN(" TX Pin: ", tx_pin_); + LOG_PIN(" RX Pin: ", rx_pin_); + if (this->rx_pin_ != nullptr) { + ESP_LOGCONFIG(TAG, " RX Buffer Size: %u", this->rx_buffer_size_); + } + ESP_LOGCONFIG(TAG, " Baud Rate: %u baud", this->baud_rate_); + ESP_LOGCONFIG(TAG, " Data Bits: %u", this->data_bits_); + ESP_LOGCONFIG(TAG, " Parity: %s", LOG_STR_ARG(parity_to_str(this->parity_))); + ESP_LOGCONFIG(TAG, " Stop bits: %u", this->stop_bits_); + if (this->hw_serial_) { + ESP_LOGCONFIG(TAG, " Using hardware serial"); + } else { + ESP_LOGCONFIG(TAG, " Using SerialPIO"); + } +} + +void RP2040UartComponent::write_array(const uint8_t *data, size_t len) { + this->serial_->write(data, len); +#ifdef USE_UART_DEBUGGER + for (size_t i = 0; i < len; i++) { + this->debug_callback_.call(UART_DIRECTION_TX, data[i]); + } +#endif +} +bool RP2040UartComponent::peek_byte(uint8_t *data) { + if (!this->check_read_timeout_()) + return false; + *data = this->serial_->peek(); + return true; +} +bool RP2040UartComponent::read_array(uint8_t *data, size_t len) { + if (!this->check_read_timeout_(len)) + return false; + this->serial_->readBytes(data, len); +#ifdef USE_UART_DEBUGGER + for (size_t i = 0; i < len; i++) { + this->debug_callback_.call(UART_DIRECTION_RX, data[i]); + } +#endif + return true; +} +int RP2040UartComponent::available() { return this->serial_->available(); } +void RP2040UartComponent::flush() { + ESP_LOGVV(TAG, " Flushing..."); + this->serial_->flush(); +} + +} // namespace uart +} // namespace esphome + +#endif // USE_RP2040 diff --git a/1u/yaml/with_uart_output/screek-mod-components/uart/uart_component_rp2040.h b/1u/yaml/with_uart_output/screek-mod-components/uart/uart_component_rp2040.h new file mode 100644 index 0000000..163315d --- /dev/null +++ b/1u/yaml/with_uart_output/screek-mod-components/uart/uart_component_rp2040.h @@ -0,0 +1,43 @@ +#pragma once + +#ifdef USE_RP2040 + +#include +#include + +#include +#include "esphome/core/component.h" +#include "esphome/core/hal.h" +#include "esphome/core/log.h" +#include "uart_component.h" + +namespace esphome { +namespace uart { + +class RP2040UartComponent : public UARTComponent, public Component { + public: + void setup() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::BUS; } + + void write_array(const uint8_t *data, size_t len) override; + + bool peek_byte(uint8_t *data) override; + bool read_array(uint8_t *data, size_t len) override; + + int available() override; + void flush() override; + + uint16_t get_config(); + + protected: + void check_logger_conflict() override {} + bool hw_serial_{false}; + + HardwareSerial *serial_{nullptr}; +}; + +} // namespace uart +} // namespace esphome + +#endif // USE_RP2040 diff --git a/1u/yaml/with_uart_output/screek-mod-components/uart/uart_debugger.cpp b/1u/yaml/with_uart_output/screek-mod-components/uart/uart_debugger.cpp new file mode 100644 index 0000000..e2d92ea --- /dev/null +++ b/1u/yaml/with_uart_output/screek-mod-components/uart/uart_debugger.cpp @@ -0,0 +1,202 @@ +#include "esphome/core/defines.h" +#ifdef USE_UART_DEBUGGER + +#include +#include "uart_debugger.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace uart { + +static const char *const TAG = "uart_debug"; + +UARTDebugger::UARTDebugger(UARTComponent *parent) { + parent->add_debug_callback([this](UARTDirection direction, uint8_t byte) { + if (!this->is_my_direction_(direction) || this->is_recursive_()) { + return; + } + this->trigger_after_direction_change_(direction); + this->store_byte_(direction, byte); + this->trigger_after_delimiter_(byte); + this->trigger_after_bytes_(); + }); +} + +void UARTDebugger::loop() { this->trigger_after_timeout_(); } + +bool UARTDebugger::is_my_direction_(UARTDirection direction) { + return this->for_direction_ == UART_DIRECTION_BOTH || this->for_direction_ == direction; +} + +bool UARTDebugger::is_recursive_() { return this->is_triggering_; } + +void UARTDebugger::trigger_after_direction_change_(UARTDirection direction) { + if (this->has_buffered_bytes_() && this->for_direction_ == UART_DIRECTION_BOTH && + this->last_direction_ != direction) { + this->fire_trigger_(); + } +} + +void UARTDebugger::store_byte_(UARTDirection direction, uint8_t byte) { + this->bytes_.push_back(byte); + this->last_direction_ = direction; + this->last_time_ = millis(); +} + +void UARTDebugger::trigger_after_delimiter_(uint8_t byte) { + if (this->after_delimiter_.empty() || !this->has_buffered_bytes_()) { + return; + } + if (this->after_delimiter_[this->after_delimiter_pos_] != byte) { + this->after_delimiter_pos_ = 0; + return; + } + this->after_delimiter_pos_++; + if (this->after_delimiter_pos_ == this->after_delimiter_.size()) { + this->fire_trigger_(); + this->after_delimiter_pos_ = 0; + } +} + +void UARTDebugger::trigger_after_bytes_() { + if (this->has_buffered_bytes_() && this->after_bytes_ > 0 && this->bytes_.size() >= this->after_bytes_) { + this->fire_trigger_(); + } +} + +void UARTDebugger::trigger_after_timeout_() { + if (this->has_buffered_bytes_() && this->after_timeout_ > 0 && millis() - this->last_time_ >= this->after_timeout_) { + this->fire_trigger_(); + } +} + +bool UARTDebugger::has_buffered_bytes_() { return !this->bytes_.empty(); } + +void UARTDebugger::fire_trigger_() { + this->is_triggering_ = true; + trigger(this->last_direction_, this->bytes_); + this->bytes_.clear(); + this->is_triggering_ = false; +} + +void UARTDummyReceiver::loop() { + // Reading up to a limited number of bytes, to make sure that this loop() + // won't lock up the system on a continuous incoming stream of bytes. + uint8_t data; + int count = 50; + while (this->available() && count--) { + this->read_byte(&data); + } +} + +// In the upcoming log functions, a delay was added after all log calls. +// This is done to allow the system to ship the log lines via the API +// TCP connection(s). Without these delays, debug log lines could go +// missing when UART devices block the main loop for too long. + +void UARTDebug::log_hex(UARTDirection direction, std::vector bytes, uint8_t separator) { + std::string res; + if (direction == UART_DIRECTION_RX) { + res += "<<< "; + } else { + res += ">>> "; + } + size_t len = bytes.size(); + char buf[5]; + for (size_t i = 0; i < len; i++) { + if (i > 0) { + res += separator; + } + sprintf(buf, "%02X", bytes[i]); + res += buf; + } + ESP_LOGD(TAG, "%s", res.c_str()); + delay(10); +} + +void UARTDebug::log_string(UARTDirection direction, std::vector bytes) { + std::string res; + if (direction == UART_DIRECTION_RX) { + res += "<<< \""; + } else { + res += ">>> \""; + } + size_t len = bytes.size(); + char buf[5]; + for (size_t i = 0; i < len; i++) { + if (bytes[i] == 7) { + res += "\\a"; + } else if (bytes[i] == 8) { + res += "\\b"; + } else if (bytes[i] == 9) { + res += "\\t"; + } else if (bytes[i] == 10) { + res += "\\n"; + } else if (bytes[i] == 11) { + res += "\\v"; + } else if (bytes[i] == 12) { + res += "\\f"; + } else if (bytes[i] == 13) { + res += "\\r"; + } else if (bytes[i] == 27) { + res += "\\e"; + } else if (bytes[i] == 34) { + res += "\\\""; + } else if (bytes[i] == 39) { + res += "\\'"; + } else if (bytes[i] == 92) { + res += "\\\\"; + } else if (bytes[i] < 32 || bytes[i] > 127) { + sprintf(buf, "\\x%02X", bytes[i]); + res += buf; + } else { + res += bytes[i]; + } + } + res += '"'; + ESP_LOGD(TAG, "%s", res.c_str()); + delay(10); +} + +void UARTDebug::log_int(UARTDirection direction, std::vector bytes, uint8_t separator) { + std::string res; + size_t len = bytes.size(); + if (direction == UART_DIRECTION_RX) { + res += "<<< "; + } else { + res += ">>> "; + } + for (size_t i = 0; i < len; i++) { + if (i > 0) { + res += separator; + } + res += to_string(bytes[i]); + } + ESP_LOGD(TAG, "%s", res.c_str()); + delay(10); +} + +void UARTDebug::log_binary(UARTDirection direction, std::vector bytes, uint8_t separator) { + std::string res; + size_t len = bytes.size(); + if (direction == UART_DIRECTION_RX) { + res += "<<< "; + } else { + res += ">>> "; + } + char buf[20]; + for (size_t i = 0; i < len; i++) { + if (i > 0) { + res += separator; + } + sprintf(buf, "0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(bytes[i]), bytes[i]); + res += buf; + } + ESP_LOGD(TAG, "%s", res.c_str()); + delay(10); +} + +} // namespace uart +} // namespace esphome +#endif diff --git a/1u/yaml/with_uart_output/screek-mod-components/uart/uart_debugger.h b/1u/yaml/with_uart_output/screek-mod-components/uart/uart_debugger.h new file mode 100644 index 0000000..4f9b6d0 --- /dev/null +++ b/1u/yaml/with_uart_output/screek-mod-components/uart/uart_debugger.h @@ -0,0 +1,101 @@ +#pragma once +#include "esphome/core/defines.h" +#ifdef USE_UART_DEBUGGER + +#include +#include "esphome/core/component.h" +#include "esphome/core/automation.h" +#include "uart.h" +#include "uart_component.h" + +namespace esphome { +namespace uart { + +/// The UARTDebugger class adds debugging support to a UART bus. +/// +/// It accumulates bytes that travel over the UART bus and triggers one or +/// more actions that can log the data at an appropriate time. What +/// 'appropriate time' means exactly, is determined by a number of +/// configurable constraints. E.g. when a given number of bytes is gathered +/// and/or when no more data has been seen for a given time interval. +class UARTDebugger : public Component, public Trigger> { + public: + explicit UARTDebugger(UARTComponent *parent); + void loop() override; + + /// Set the direction in which to inspect the bytes: incoming, outgoing + /// or both. When debugging in both directions, logging will be triggered + /// when the direction of the data stream changes. + void set_direction(UARTDirection direction) { this->for_direction_ = direction; } + + /// Set the maximum number of bytes to accumulate. When the number of bytes + /// is reached, logging will be triggered. + void set_after_bytes(size_t size) { this->after_bytes_ = size; } + + /// Set a timeout for the data stream. When no new bytes are seen during + /// this timeout, logging will be triggered. + void set_after_timeout(uint32_t timeout) { this->after_timeout_ = timeout; } + + /// Add a delimiter byte. This can be called multiple times to setup a + /// multi-byte delimiter (a typical example would be '\r\n'). + /// When the constructed byte sequence is found in the data stream, + /// logging will be triggered. + void add_delimiter_byte(uint8_t byte) { this->after_delimiter_.push_back(byte); } + + protected: + UARTDirection for_direction_; + UARTDirection last_direction_{}; + std::vector bytes_{}; + size_t after_bytes_; + uint32_t after_timeout_; + uint32_t last_time_{}; + std::vector after_delimiter_{}; + size_t after_delimiter_pos_{}; + bool is_triggering_{false}; + + bool is_my_direction_(UARTDirection direction); + bool is_recursive_(); + void store_byte_(UARTDirection direction, uint8_t byte); + void trigger_after_direction_change_(UARTDirection direction); + void trigger_after_delimiter_(uint8_t byte); + void trigger_after_bytes_(); + void trigger_after_timeout_(); + bool has_buffered_bytes_(); + void fire_trigger_(); +}; + +/// This UARTDevice is used by the serial debugger to read data from a +/// serial interface when the 'dummy_receiver' option is enabled. +/// The data are not stored, nor processed. This is most useful when the +/// debugger is used to reverse engineer a serial protocol, for which no +/// specific UARTDevice implementation exists (yet), but for which the +/// incoming bytes must be read to drive the debugger. +class UARTDummyReceiver : public Component, public UARTDevice { + public: + UARTDummyReceiver(UARTComponent *parent) : UARTDevice(parent) {} + void loop() override; +}; + +/// This class contains some static methods, that can be used to easily +/// create a logging action for the debugger. +class UARTDebug { + public: + /// Log the bytes as hex values, separated by the provided separator + /// character. + static void log_hex(UARTDirection direction, std::vector bytes, uint8_t separator); + + /// Log the bytes as string values, escaping unprintable characters. + static void log_string(UARTDirection direction, std::vector bytes); + + /// Log the bytes as integer values, separated by the provided separator + /// character. + static void log_int(UARTDirection direction, std::vector bytes, uint8_t separator); + + /// Log the bytes as ' ()' values, separated by the provided + /// separator. + static void log_binary(UARTDirection direction, std::vector bytes, uint8_t separator); +}; + +} // namespace uart +} // namespace esphome +#endif