From db9dc110220cdd54f7da42de61a1bdf371698655 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 10 Aug 2023 17:30:26 +1200 Subject: [PATCH 001/111] Bump version to 2023.9.0-dev --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 4977726361..373d6bd8c9 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2023.8.0-dev" +__version__ = "2023.9.0-dev" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = ( From a84365659bb56f0ea9dbe61ba69914c579bda6a6 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 11 Aug 2023 16:20:58 +1200 Subject: [PATCH 002/111] Read string of bool env and match against well known values (#5232) --- esphome/helpers.py | 9 ++++++++- tests/unit_tests/test_helpers.py | 4 ++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/esphome/helpers.py b/esphome/helpers.py index fd8893ad99..4012b2067f 100644 --- a/esphome/helpers.py +++ b/esphome/helpers.py @@ -144,7 +144,14 @@ def resolve_ip_address(host): def get_bool_env(var, default=False): - return bool(os.getenv(var, default)) + value = os.getenv(var, default) + if isinstance(value, str): + value = value.lower() + if value in ["1", "true"]: + return True + if value in ["0", "false"]: + return False + return bool(value) def get_str_env(var, default=None): diff --git a/tests/unit_tests/test_helpers.py b/tests/unit_tests/test_helpers.py index b98838024f..67fabd7af8 100644 --- a/tests/unit_tests/test_helpers.py +++ b/tests/unit_tests/test_helpers.py @@ -108,6 +108,10 @@ def test_is_ip_address__valid(value): ("FOO", None, False, False), ("FOO", None, True, True), ("FOO", "", False, False), + ("FOO", "False", False, False), + ("FOO", "True", False, True), + ("FOO", "FALSE", True, False), + ("FOO", "fAlSe", True, False), ("FOO", "Yes", False, True), ("FOO", "123", False, True), ), From 283d9a0f5fc579a0a36614bb7248fed16d7d2e75 Mon Sep 17 00:00:00 2001 From: Samuel Sieb Date: Thu, 10 Aug 2023 21:21:24 -0700 Subject: [PATCH 003/111] fix aeha data template (#5231) Co-authored-by: Samuel Sieb --- esphome/components/remote_base/__init__.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/esphome/components/remote_base/__init__.py b/esphome/components/remote_base/__init__.py index 0666b96d1e..24993e84d3 100644 --- a/esphome/components/remote_base/__init__.py +++ b/esphome/components/remote_base/__init__.py @@ -1569,4 +1569,7 @@ def aeha_dumper(var, config): async def aeha_action(var, config, args): template_ = await cg.templatable(config[CONF_ADDRESS], args, cg.uint16) cg.add(var.set_address(template_)) - cg.add(var.set_data(config[CONF_DATA])) + template_ = await cg.templatable( + config[CONF_DATA], args, cg.std_vector.template(cg.uint8) + ) + cg.add(var.set_data(template_)) From 3eef80506bb2d7e0c4bed0fe8a6d49aca8975bea Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 11 Aug 2023 16:21:44 +1200 Subject: [PATCH 004/111] Expose start to speaker interface (#5228) --- esphome/components/i2s_audio/speaker/i2s_audio_speaker.h | 2 +- esphome/components/speaker/speaker.h | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/esphome/components/i2s_audio/speaker/i2s_audio_speaker.h b/esphome/components/i2s_audio/speaker/i2s_audio_speaker.h index f2e83142b3..b075722e1b 100644 --- a/esphome/components/i2s_audio/speaker/i2s_audio_speaker.h +++ b/esphome/components/i2s_audio/speaker/i2s_audio_speaker.h @@ -51,7 +51,7 @@ class I2SAudioSpeaker : public Component, public speaker::Speaker, public I2SAud #endif void set_external_dac_channels(uint8_t channels) { this->external_dac_channels_ = channels; } - void start(); + void start() override; void stop() override; size_t play(const uint8_t *data, size_t length) override; diff --git a/esphome/components/speaker/speaker.h b/esphome/components/speaker/speaker.h index 53f97da5ac..3f520e3c5e 100644 --- a/esphome/components/speaker/speaker.h +++ b/esphome/components/speaker/speaker.h @@ -13,8 +13,9 @@ enum State : uint8_t { class Speaker { public: virtual size_t play(const uint8_t *data, size_t length) = 0; - virtual size_t play(const std::vector &data) { return this->play(data.data(), data.size()); } + size_t play(const std::vector &data) { return this->play(data.data(), data.size()); } + virtual void start() = 0; virtual void stop() = 0; bool is_running() const { return this->state_ == STATE_RUNNING; } From 5cb21324a1f0045a2ea4cb8c395e9d7976f2279c Mon Sep 17 00:00:00 2001 From: Pavlo Dudnytskyi Date: Fri, 11 Aug 2023 07:51:53 +0200 Subject: [PATCH 005/111] New features added for Haier integration (#5196) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/haier/climate.py | 74 ++++++- esphome/components/haier/haier_base.cpp | 75 +++++-- esphome/components/haier/haier_base.h | 43 ++-- esphome/components/haier/hon_climate.cpp | 144 ++++++------- esphome/components/haier/hon_climate.h | 5 +- esphome/components/haier/hon_packet.h | 16 +- .../components/haier/smartair2_climate.cpp | 204 +++++++++++++----- esphome/components/haier/smartair2_climate.h | 13 +- esphome/components/haier/smartair2_packet.h | 7 +- platformio.ini | 2 +- 10 files changed, 394 insertions(+), 189 deletions(-) diff --git a/esphome/components/haier/climate.py b/esphome/components/haier/climate.py index c518282bfa..fec39d2967 100644 --- a/esphome/components/haier/climate.py +++ b/esphome/components/haier/climate.py @@ -14,7 +14,10 @@ from esphome.const import ( CONF_MIN_TEMPERATURE, CONF_PROTOCOL, CONF_SUPPORTED_MODES, + CONF_SUPPORTED_PRESETS, CONF_SUPPORTED_SWING_MODES, + CONF_TARGET_TEMPERATURE, + CONF_TEMPERATURE_STEP, CONF_VISUAL, CONF_WIFI, DEVICE_CLASS_TEMPERATURE, @@ -23,25 +26,29 @@ from esphome.const import ( UNIT_CELSIUS, ) from esphome.components.climate import ( - ClimateSwingMode, ClimateMode, + ClimatePreset, + ClimateSwingMode, + CONF_CURRENT_TEMPERATURE, ) _LOGGER = logging.getLogger(__name__) PROTOCOL_MIN_TEMPERATURE = 16.0 PROTOCOL_MAX_TEMPERATURE = 30.0 -PROTOCOL_TEMPERATURE_STEP = 1.0 +PROTOCOL_TARGET_TEMPERATURE_STEP = 1.0 +PROTOCOL_CURRENT_TEMPERATURE_STEP = 0.5 CODEOWNERS = ["@paveldn"] AUTO_LOAD = ["sensor"] DEPENDENCIES = ["climate", "uart"] CONF_WIFI_SIGNAL = "wifi_signal" +CONF_ANSWER_TIMEOUT = "answer_timeout" +CONF_DISPLAY = "display" CONF_OUTDOOR_TEMPERATURE = "outdoor_temperature" CONF_VERTICAL_AIRFLOW = "vertical_airflow" CONF_HORIZONTAL_AIRFLOW = "horizontal_airflow" - PROTOCOL_HON = "HON" PROTOCOL_SMARTAIR2 = "SMARTAIR2" PROTOCOLS_SUPPORTED = [PROTOCOL_HON, PROTOCOL_SMARTAIR2] @@ -89,6 +96,17 @@ SUPPORTED_CLIMATE_MODES_OPTIONS = { "FAN_ONLY": ClimateMode.CLIMATE_MODE_FAN_ONLY, } +SUPPORTED_CLIMATE_PRESETS_SMARTAIR2_OPTIONS = { + "BOOST": ClimatePreset.CLIMATE_PRESET_BOOST, + "COMFORT": ClimatePreset.CLIMATE_PRESET_COMFORT, +} + +SUPPORTED_CLIMATE_PRESETS_HON_OPTIONS = { + "ECO": ClimatePreset.CLIMATE_PRESET_ECO, + "BOOST": ClimatePreset.CLIMATE_PRESET_BOOST, + "SLEEP": ClimatePreset.CLIMATE_PRESET_SLEEP, +} + def validate_visual(config): if CONF_VISUAL in config: @@ -109,10 +127,29 @@ def validate_visual(config): ) else: config[CONF_VISUAL][CONF_MAX_TEMPERATURE] = PROTOCOL_MAX_TEMPERATURE + if CONF_TEMPERATURE_STEP in visual_config: + temp_step = config[CONF_VISUAL][CONF_TEMPERATURE_STEP][ + CONF_TARGET_TEMPERATURE + ] + if ((int)(temp_step * 2)) / 2 != temp_step: + raise cv.Invalid( + f"Configured visual temperature step {temp_step} is wrong, it should be a multiple of 0.5" + ) + else: + config[CONF_VISUAL][CONF_TEMPERATURE_STEP] = ( + { + CONF_TARGET_TEMPERATURE: PROTOCOL_TARGET_TEMPERATURE_STEP, + CONF_CURRENT_TEMPERATURE: PROTOCOL_CURRENT_TEMPERATURE_STEP, + }, + ) else: config[CONF_VISUAL] = { CONF_MIN_TEMPERATURE: PROTOCOL_MIN_TEMPERATURE, CONF_MAX_TEMPERATURE: PROTOCOL_MAX_TEMPERATURE, + CONF_TEMPERATURE_STEP: { + CONF_TARGET_TEMPERATURE: PROTOCOL_TARGET_TEMPERATURE_STEP, + CONF_CURRENT_TEMPERATURE: PROTOCOL_CURRENT_TEMPERATURE_STEP, + }, } return config @@ -132,6 +169,11 @@ BASE_CONFIG_SCHEMA = ( "BOTH", ], ): cv.ensure_list(cv.enum(SUPPORTED_SWING_MODES_OPTIONS, upper=True)), + cv.Optional(CONF_WIFI_SIGNAL, default=False): cv.boolean, + cv.Optional(CONF_DISPLAY): cv.boolean, + cv.Optional( + CONF_ANSWER_TIMEOUT, + ): cv.positive_time_period_milliseconds, } ) .extend(uart.UART_DEVICE_SCHEMA) @@ -144,13 +186,26 @@ CONFIG_SCHEMA = cv.All( PROTOCOL_SMARTAIR2: BASE_CONFIG_SCHEMA.extend( { cv.GenerateID(): cv.declare_id(Smartair2Climate), + cv.Optional( + CONF_SUPPORTED_PRESETS, + default=list( + SUPPORTED_CLIMATE_PRESETS_SMARTAIR2_OPTIONS.keys() + ), + ): cv.ensure_list( + cv.enum(SUPPORTED_CLIMATE_PRESETS_SMARTAIR2_OPTIONS, upper=True) + ), } ), PROTOCOL_HON: BASE_CONFIG_SCHEMA.extend( { cv.GenerateID(): cv.declare_id(HonClimate), - cv.Optional(CONF_WIFI_SIGNAL, default=True): cv.boolean, cv.Optional(CONF_BEEPER, default=True): cv.boolean, + cv.Optional( + CONF_SUPPORTED_PRESETS, + default=list(SUPPORTED_CLIMATE_PRESETS_HON_OPTIONS.keys()), + ): cv.ensure_list( + cv.enum(SUPPORTED_CLIMATE_PRESETS_HON_OPTIONS, upper=True) + ), cv.Optional(CONF_OUTDOOR_TEMPERATURE): sensor.sensor_schema( unit_of_measurement=UNIT_CELSIUS, icon=ICON_THERMOMETER, @@ -354,10 +409,11 @@ async def to_code(config): await uart.register_uart_device(var, config) await climate.register_climate(var, config) - if (CONF_WIFI_SIGNAL in config) and (config[CONF_WIFI_SIGNAL]): - cg.add(var.set_send_wifi(config[CONF_WIFI_SIGNAL])) + cg.add(var.set_send_wifi(config[CONF_WIFI_SIGNAL])) if CONF_BEEPER in config: cg.add(var.set_beeper_state(config[CONF_BEEPER])) + if CONF_DISPLAY in config: + cg.add(var.set_display_state(config[CONF_DISPLAY])) if CONF_OUTDOOR_TEMPERATURE in config: sens = await sensor.new_sensor(config[CONF_OUTDOOR_TEMPERATURE]) cg.add(var.set_outdoor_temperature_sensor(sens)) @@ -365,5 +421,9 @@ async def to_code(config): cg.add(var.set_supported_modes(config[CONF_SUPPORTED_MODES])) if CONF_SUPPORTED_SWING_MODES in config: cg.add(var.set_supported_swing_modes(config[CONF_SUPPORTED_SWING_MODES])) + if CONF_SUPPORTED_PRESETS in config: + cg.add(var.set_supported_presets(config[CONF_SUPPORTED_PRESETS])) + if CONF_ANSWER_TIMEOUT in config: + cg.add(var.set_answer_timeout(config[CONF_ANSWER_TIMEOUT])) # https://github.com/paveldn/HaierProtocol - cg.add_library("pavlodn/HaierProtocol", "0.9.18") + cg.add_library("pavlodn/HaierProtocol", "0.9.20") diff --git a/esphome/components/haier/haier_base.cpp b/esphome/components/haier/haier_base.cpp index d9349cb8fe..5faee5207b 100644 --- a/esphome/components/haier/haier_base.cpp +++ b/esphome/components/haier/haier_base.cpp @@ -2,6 +2,9 @@ #include #include "esphome/components/climate/climate.h" #include "esphome/components/uart/uart.h" +#ifdef USE_WIFI +#include "esphome/components/wifi/wifi_component.h" +#endif #include "haier_base.h" using namespace esphome::climate; @@ -24,14 +27,15 @@ constexpr size_t NO_COMMAND = 0xFF; // Indicate that there is no command suppli const char *HaierClimateBase::phase_to_string_(ProtocolPhases phase) { static const char *phase_names[] = { "SENDING_INIT_1", - "WAITING_ANSWER_INIT_1", + "WAITING_INIT_1_ANSWER", "SENDING_INIT_2", - "WAITING_ANSWER_INIT_2", + "WAITING_INIT_2_ANSWER", "SENDING_FIRST_STATUS_REQUEST", "WAITING_FIRST_STATUS_ANSWER", "SENDING_ALARM_STATUS_REQUEST", "WAITING_ALARM_STATUS_ANSWER", "IDLE", + "UNKNOWN", "SENDING_STATUS_REQUEST", "WAITING_STATUS_ANSWER", "SENDING_UPDATE_SIGNAL_REQUEST", @@ -63,7 +67,8 @@ HaierClimateBase::HaierClimateBase() forced_publish_(false), forced_request_status_(false), first_control_attempt_(false), - reset_protocol_request_(false) { + reset_protocol_request_(false), + send_wifi_signal_(true) { this->traits_ = climate::ClimateTraits(); this->traits_.set_supported_modes({climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_COOL, climate::CLIMATE_MODE_HEAT, climate::CLIMATE_MODE_FAN_ONLY, climate::CLIMATE_MODE_DRY, @@ -77,7 +82,7 @@ HaierClimateBase::HaierClimateBase() HaierClimateBase::~HaierClimateBase() {} -void HaierClimateBase::set_phase_(ProtocolPhases phase) { +void HaierClimateBase::set_phase(ProtocolPhases phase) { if (this->protocol_phase_ != phase) { #if (HAIER_LOG_LEVEL > 4) ESP_LOGV(TAG, "Phase transition: %s => %s", phase_to_string_(this->protocol_phase_), phase_to_string_(phase)); @@ -109,10 +114,27 @@ bool HaierClimateBase::is_control_message_interval_exceeded_(std::chrono::steady return this->check_timeout_(now, this->last_request_timestamp_, CONTROL_MESSAGES_INTERVAL_MS); } -bool HaierClimateBase::is_protocol_initialisation_interval_exceded_(std::chrono::steady_clock::time_point now) { +bool HaierClimateBase::is_protocol_initialisation_interval_exceeded_(std::chrono::steady_clock::time_point now) { return this->check_timeout_(now, this->last_request_timestamp_, PROTOCOL_INITIALIZATION_INTERVAL); } +#ifdef USE_WIFI +haier_protocol::HaierMessage HaierClimateBase::get_wifi_signal_message_(uint8_t message_type) { + static uint8_t wifi_status_data[4] = {0x00, 0x00, 0x00, 0x00}; + if (wifi::global_wifi_component->is_connected()) { + wifi_status_data[1] = 0; + int8_t rssi = wifi::global_wifi_component->wifi_rssi(); + wifi_status_data[3] = uint8_t((128 + rssi) / 1.28f); + ESP_LOGD(TAG, "WiFi signal is: %ddBm => %d%%", rssi, wifi_status_data[3]); + } else { + ESP_LOGD(TAG, "WiFi is not connected"); + wifi_status_data[1] = 1; + wifi_status_data[3] = 0; + } + return haier_protocol::HaierMessage(message_type, wifi_status_data, sizeof(wifi_status_data)); +} +#endif + bool HaierClimateBase::get_display_state() const { return this->display_status_; } void HaierClimateBase::set_display_state(bool state) { @@ -136,10 +158,15 @@ void HaierClimateBase::send_power_on_command() { this->action_request_ = ActionR void HaierClimateBase::send_power_off_command() { this->action_request_ = ActionRequest::TURN_POWER_OFF; } void HaierClimateBase::toggle_power() { this->action_request_ = ActionRequest::TOGGLE_POWER; } + void HaierClimateBase::set_supported_swing_modes(const std::set &modes) { this->traits_.set_supported_swing_modes(modes); - this->traits_.add_supported_swing_mode(climate::CLIMATE_SWING_OFF); // Always available - this->traits_.add_supported_swing_mode(climate::CLIMATE_SWING_VERTICAL); // Always available + if (!modes.empty()) + this->traits_.add_supported_swing_mode(climate::CLIMATE_SWING_OFF); +} + +void HaierClimateBase::set_answer_timeout(uint32_t timeout) { + this->answer_timeout_ = std::chrono::milliseconds(timeout); } void HaierClimateBase::set_supported_modes(const std::set &modes) { @@ -148,6 +175,14 @@ void HaierClimateBase::set_supported_modes(const std::set this->traits_.add_supported_mode(climate::CLIMATE_MODE_AUTO); // Always available } +void HaierClimateBase::set_supported_presets(const std::set &presets) { + this->traits_.set_supported_presets(presets); + if (!presets.empty()) + this->traits_.add_supported_preset(climate::CLIMATE_PRESET_NONE); +} + +void HaierClimateBase::set_send_wifi(bool send_wifi) { this->send_wifi_signal_ = send_wifi; } + haier_protocol::HandlerError HaierClimateBase::answer_preprocess_(uint8_t request_message_type, uint8_t expected_request_message_type, uint8_t answer_message_type, @@ -155,9 +190,9 @@ haier_protocol::HandlerError HaierClimateBase::answer_preprocess_(uint8_t reques ProtocolPhases expected_phase) { haier_protocol::HandlerError result = haier_protocol::HandlerError::HANDLER_OK; if ((expected_request_message_type != NO_COMMAND) && (request_message_type != expected_request_message_type)) - result = haier_protocol::HandlerError::UNSUPORTED_MESSAGE; + result = haier_protocol::HandlerError::UNSUPPORTED_MESSAGE; if ((expected_answer_message_type != NO_COMMAND) && (answer_message_type != expected_answer_message_type)) - result = haier_protocol::HandlerError::UNSUPORTED_MESSAGE; + result = haier_protocol::HandlerError::UNSUPPORTED_MESSAGE; if ((expected_phase != ProtocolPhases::UNKNOWN) && (expected_phase != this->protocol_phase_)) result = haier_protocol::HandlerError::UNEXPECTED_MESSAGE; if (is_message_invalid(answer_message_type)) @@ -172,9 +207,9 @@ haier_protocol::HandlerError HaierClimateBase::timeout_default_handler_(uint8_t ESP_LOGW(TAG, "Answer timeout for command %02X, phase %d", request_type, (int) this->protocol_phase_); #endif if (this->protocol_phase_ > ProtocolPhases::IDLE) { - this->set_phase_(ProtocolPhases::IDLE); + this->set_phase(ProtocolPhases::IDLE); } else { - this->set_phase_(ProtocolPhases::SENDING_INIT_1); + this->set_phase(ProtocolPhases::SENDING_INIT_1); } return haier_protocol::HandlerError::HANDLER_OK; } @@ -183,8 +218,8 @@ void HaierClimateBase::setup() { ESP_LOGI(TAG, "Haier initialization..."); // Set timestamp here to give AC time to boot this->last_request_timestamp_ = std::chrono::steady_clock::now(); - this->set_phase_(ProtocolPhases::SENDING_INIT_1); - this->set_answers_handlers(); + this->set_phase(ProtocolPhases::SENDING_INIT_1); + this->set_handlers(); this->haier_protocol_.set_default_timeout_handler( std::bind(&esphome::haier::HaierClimateBase::timeout_default_handler_, this, std::placeholders::_1)); } @@ -212,7 +247,7 @@ void HaierClimateBase::loop() { this->set_force_send_control_(false); if (this->hvac_settings_.valid) this->hvac_settings_.reset(); - this->set_phase_(ProtocolPhases::SENDING_INIT_1); + this->set_phase(ProtocolPhases::SENDING_INIT_1); return; } else { // No need to reset protocol if we didn't pass initialization phase @@ -229,7 +264,7 @@ void HaierClimateBase::loop() { this->process_pending_action(); } else if (this->hvac_settings_.valid || this->force_send_control_) { ESP_LOGV(TAG, "Control packet is pending..."); - this->set_phase_(ProtocolPhases::SENDING_CONTROL); + this->set_phase(ProtocolPhases::SENDING_CONTROL); } } this->process_phase(now); @@ -243,10 +278,10 @@ void HaierClimateBase::process_pending_action() { } switch (request) { case ActionRequest::TURN_POWER_ON: - this->set_phase_(ProtocolPhases::SENDING_POWER_ON_COMMAND); + this->set_phase(ProtocolPhases::SENDING_POWER_ON_COMMAND); break; case ActionRequest::TURN_POWER_OFF: - this->set_phase_(ProtocolPhases::SENDING_POWER_OFF_COMMAND); + this->set_phase(ProtocolPhases::SENDING_POWER_OFF_COMMAND); break; case ActionRequest::TOGGLE_POWER: case ActionRequest::NO_ACTION: @@ -303,7 +338,11 @@ void HaierClimateBase::set_force_send_control_(bool status) { } void HaierClimateBase::send_message_(const haier_protocol::HaierMessage &command, bool use_crc) { - this->haier_protocol_.send_message(command, use_crc); + if (this->answer_timeout_.has_value()) { + this->haier_protocol_.send_message(command, use_crc, this->answer_timeout_.value()); + } else { + this->haier_protocol_.send_message(command, use_crc); + } this->last_request_timestamp_ = std::chrono::steady_clock::now(); } diff --git a/esphome/components/haier/haier_base.h b/esphome/components/haier/haier_base.h index 046b59af96..b2446d6fb5 100644 --- a/esphome/components/haier/haier_base.h +++ b/esphome/components/haier/haier_base.h @@ -44,6 +44,7 @@ class HaierClimateBase : public esphome::Component, void reset_protocol() { this->reset_protocol_request_ = true; }; void set_supported_modes(const std::set &modes); void set_supported_swing_modes(const std::set &modes); + void set_supported_presets(const std::set &presets); size_t available() noexcept override { return esphome::uart::UARTDevice::available(); }; size_t read_array(uint8_t *data, size_t len) noexcept override { return esphome::uart::UARTDevice::read_array(data, len) ? len : 0; @@ -52,39 +53,41 @@ class HaierClimateBase : public esphome::Component, esphome::uart::UARTDevice::write_array(data, len); }; bool can_send_message() const { return haier_protocol_.get_outgoing_queue_size() == 0; }; + void set_answer_timeout(uint32_t timeout); + void set_send_wifi(bool send_wifi); protected: enum class ProtocolPhases { UNKNOWN = -1, // INITIALIZATION SENDING_INIT_1 = 0, - WAITING_ANSWER_INIT_1 = 1, + WAITING_INIT_1_ANSWER = 1, SENDING_INIT_2 = 2, - WAITING_ANSWER_INIT_2 = 3, + WAITING_INIT_2_ANSWER = 3, SENDING_FIRST_STATUS_REQUEST = 4, WAITING_FIRST_STATUS_ANSWER = 5, SENDING_ALARM_STATUS_REQUEST = 6, WAITING_ALARM_STATUS_ANSWER = 7, // FUNCTIONAL STATE IDLE = 8, - SENDING_STATUS_REQUEST = 9, - WAITING_STATUS_ANSWER = 10, - SENDING_UPDATE_SIGNAL_REQUEST = 11, - WAITING_UPDATE_SIGNAL_ANSWER = 12, - SENDING_SIGNAL_LEVEL = 13, - WAITING_SIGNAL_LEVEL_ANSWER = 14, - SENDING_CONTROL = 15, - WAITING_CONTROL_ANSWER = 16, - SENDING_POWER_ON_COMMAND = 17, - WAITING_POWER_ON_ANSWER = 18, - SENDING_POWER_OFF_COMMAND = 19, - WAITING_POWER_OFF_ANSWER = 20, + SENDING_STATUS_REQUEST = 10, + WAITING_STATUS_ANSWER = 11, + SENDING_UPDATE_SIGNAL_REQUEST = 12, + WAITING_UPDATE_SIGNAL_ANSWER = 13, + SENDING_SIGNAL_LEVEL = 14, + WAITING_SIGNAL_LEVEL_ANSWER = 15, + SENDING_CONTROL = 16, + WAITING_CONTROL_ANSWER = 17, + SENDING_POWER_ON_COMMAND = 18, + WAITING_POWER_ON_ANSWER = 19, + SENDING_POWER_OFF_COMMAND = 20, + WAITING_POWER_OFF_ANSWER = 21, NUM_PROTOCOL_PHASES }; #if (HAIER_LOG_LEVEL > 4) const char *phase_to_string_(ProtocolPhases phase); #endif - virtual void set_answers_handlers() = 0; + virtual void set_handlers() = 0; virtual void process_phase(std::chrono::steady_clock::time_point now) = 0; virtual haier_protocol::HaierMessage get_control_message() = 0; virtual bool is_message_invalid(uint8_t message_type) = 0; @@ -99,14 +102,17 @@ class HaierClimateBase : public esphome::Component, // Helper functions void set_force_send_control_(bool status); void send_message_(const haier_protocol::HaierMessage &command, bool use_crc); - void set_phase_(ProtocolPhases phase); + virtual void set_phase(ProtocolPhases phase); bool check_timeout_(std::chrono::steady_clock::time_point now, std::chrono::steady_clock::time_point tpoint, size_t timeout); bool is_message_interval_exceeded_(std::chrono::steady_clock::time_point now); bool is_status_request_interval_exceeded_(std::chrono::steady_clock::time_point now); bool is_control_message_timeout_exceeded_(std::chrono::steady_clock::time_point now); bool is_control_message_interval_exceeded_(std::chrono::steady_clock::time_point now); - bool is_protocol_initialisation_interval_exceded_(std::chrono::steady_clock::time_point now); + bool is_protocol_initialisation_interval_exceeded_(std::chrono::steady_clock::time_point now); +#ifdef USE_WIFI + haier_protocol::HaierMessage get_wifi_signal_message_(uint8_t message_type); +#endif struct HvacSettings { esphome::optional mode; @@ -136,6 +142,9 @@ class HaierClimateBase : public esphome::Component, std::chrono::steady_clock::time_point last_valid_status_timestamp_; // For protocol timeout std::chrono::steady_clock::time_point last_status_request_; // To request AC status std::chrono::steady_clock::time_point control_request_timestamp_; // To send control message + optional answer_timeout_; // Message answer timeout + bool send_wifi_signal_; + std::chrono::steady_clock::time_point last_signal_request_; // To send WiFI signal level }; } // namespace haier diff --git a/esphome/components/haier/hon_climate.cpp b/esphome/components/haier/hon_climate.cpp index 3950b34724..feb1e019d8 100644 --- a/esphome/components/haier/hon_climate.cpp +++ b/esphome/components/haier/hon_climate.cpp @@ -2,9 +2,6 @@ #include #include "esphome/components/climate/climate.h" #include "esphome/components/uart/uart.h" -#ifdef USE_WIFI -#include "esphome/components/wifi/wifi_component.h" -#endif #include "hon_climate.h" #include "hon_packet.h" @@ -58,14 +55,7 @@ HonClimate::HonClimate() hvac_functions_{false, false, false, false, false}, use_crc_(hvac_functions_[2]), active_alarms_{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - outdoor_sensor_(nullptr), - send_wifi_signal_(true) { - this->traits_.set_supported_presets({ - climate::CLIMATE_PRESET_NONE, - climate::CLIMATE_PRESET_ECO, - climate::CLIMATE_PRESET_BOOST, - climate::CLIMATE_PRESET_SLEEP, - }); + outdoor_sensor_(nullptr) { this->fan_mode_speed_ = (uint8_t) hon_protocol::FanMode::FAN_MID; this->other_modes_fan_speed_ = (uint8_t) hon_protocol::FanMode::FAN_AUTO; } @@ -121,17 +111,22 @@ void HonClimate::start_steri_cleaning() { } } -void HonClimate::set_send_wifi(bool send_wifi) { this->send_wifi_signal_ = send_wifi; } - haier_protocol::HandlerError HonClimate::get_device_version_answer_handler_(uint8_t request_type, uint8_t message_type, const uint8_t *data, size_t data_size) { + // Should check this before preprocess + if (message_type == (uint8_t) hon_protocol::FrameType::INVALID) { + ESP_LOGW(TAG, "It looks like your ESPHome Haier climate configuration is wrong. You should use the smartAir2 " + "protocol instead of hOn"); + this->set_phase(ProtocolPhases::SENDING_INIT_1); + return haier_protocol::HandlerError::INVALID_ANSWER; + } haier_protocol::HandlerError result = this->answer_preprocess_( request_type, (uint8_t) hon_protocol::FrameType::GET_DEVICE_VERSION, message_type, - (uint8_t) hon_protocol::FrameType::GET_DEVICE_VERSION_RESPONSE, ProtocolPhases::WAITING_ANSWER_INIT_1); + (uint8_t) hon_protocol::FrameType::GET_DEVICE_VERSION_RESPONSE, ProtocolPhases::WAITING_INIT_1_ANSWER); if (result == haier_protocol::HandlerError::HANDLER_OK) { if (data_size < sizeof(hon_protocol::DeviceVersionAnswer)) { // Wrong structure - this->set_phase_(ProtocolPhases::SENDING_INIT_1); + this->set_phase(ProtocolPhases::SENDING_INIT_1); return haier_protocol::HandlerError::WRONG_MESSAGE_STRUCTURE; } // All OK @@ -152,11 +147,11 @@ haier_protocol::HandlerError HonClimate::get_device_version_answer_handler_(uint this->hvac_functions_[3] = (answr->functions[1] & 0x08) != 0; // multiple AC support this->hvac_functions_[4] = (answr->functions[1] & 0x20) != 0; // roles support this->hvac_hardware_info_available_ = true; - this->set_phase_(ProtocolPhases::SENDING_INIT_2); + this->set_phase(ProtocolPhases::SENDING_INIT_2); return result; } else { - this->set_phase_((this->protocol_phase_ >= ProtocolPhases::IDLE) ? ProtocolPhases::IDLE - : ProtocolPhases::SENDING_INIT_1); + this->set_phase((this->protocol_phase_ >= ProtocolPhases::IDLE) ? ProtocolPhases::IDLE + : ProtocolPhases::SENDING_INIT_1); return result; } } @@ -165,13 +160,13 @@ haier_protocol::HandlerError HonClimate::get_device_id_answer_handler_(uint8_t r const uint8_t *data, size_t data_size) { haier_protocol::HandlerError result = this->answer_preprocess_( request_type, (uint8_t) hon_protocol::FrameType::GET_DEVICE_ID, message_type, - (uint8_t) hon_protocol::FrameType::GET_DEVICE_ID_RESPONSE, ProtocolPhases::WAITING_ANSWER_INIT_2); + (uint8_t) hon_protocol::FrameType::GET_DEVICE_ID_RESPONSE, ProtocolPhases::WAITING_INIT_2_ANSWER); if (result == haier_protocol::HandlerError::HANDLER_OK) { - this->set_phase_(ProtocolPhases::SENDING_FIRST_STATUS_REQUEST); + this->set_phase(ProtocolPhases::SENDING_FIRST_STATUS_REQUEST); return result; } else { - this->set_phase_((this->protocol_phase_ >= ProtocolPhases::IDLE) ? ProtocolPhases::IDLE - : ProtocolPhases::SENDING_INIT_1); + this->set_phase((this->protocol_phase_ >= ProtocolPhases::IDLE) ? ProtocolPhases::IDLE + : ProtocolPhases::SENDING_INIT_1); return result; } } @@ -185,8 +180,8 @@ haier_protocol::HandlerError HonClimate::status_handler_(uint8_t request_type, u result = this->process_status_message_(data, data_size); if (result != haier_protocol::HandlerError::HANDLER_OK) { ESP_LOGW(TAG, "Error %d while parsing Status packet", (int) result); - this->set_phase_((this->protocol_phase_ >= ProtocolPhases::IDLE) ? ProtocolPhases::IDLE - : ProtocolPhases::SENDING_INIT_1); + this->set_phase((this->protocol_phase_ >= ProtocolPhases::IDLE) ? ProtocolPhases::IDLE + : ProtocolPhases::SENDING_INIT_1); } else { if (data_size >= sizeof(hon_protocol::HaierPacketControl) + 2) { memcpy(this->last_status_message_.get(), data + 2, sizeof(hon_protocol::HaierPacketControl)); @@ -196,13 +191,13 @@ haier_protocol::HandlerError HonClimate::status_handler_(uint8_t request_type, u } if (this->protocol_phase_ == ProtocolPhases::WAITING_FIRST_STATUS_ANSWER) { ESP_LOGI(TAG, "First HVAC status received"); - this->set_phase_(ProtocolPhases::SENDING_ALARM_STATUS_REQUEST); + this->set_phase(ProtocolPhases::SENDING_ALARM_STATUS_REQUEST); } else if ((this->protocol_phase_ == ProtocolPhases::WAITING_STATUS_ANSWER) || (this->protocol_phase_ == ProtocolPhases::WAITING_POWER_ON_ANSWER) || (this->protocol_phase_ == ProtocolPhases::WAITING_POWER_OFF_ANSWER)) { - this->set_phase_(ProtocolPhases::IDLE); + this->set_phase(ProtocolPhases::IDLE); } else if (this->protocol_phase_ == ProtocolPhases::WAITING_CONTROL_ANSWER) { - this->set_phase_(ProtocolPhases::IDLE); + this->set_phase(ProtocolPhases::IDLE); this->set_force_send_control_(false); if (this->hvac_settings_.valid) this->hvac_settings_.reset(); @@ -210,8 +205,8 @@ haier_protocol::HandlerError HonClimate::status_handler_(uint8_t request_type, u } return result; } else { - this->set_phase_((this->protocol_phase_ >= ProtocolPhases::IDLE) ? ProtocolPhases::IDLE - : ProtocolPhases::SENDING_INIT_1); + this->set_phase((this->protocol_phase_ >= ProtocolPhases::IDLE) ? ProtocolPhases::IDLE + : ProtocolPhases::SENDING_INIT_1); return result; } } @@ -225,10 +220,10 @@ haier_protocol::HandlerError HonClimate::get_management_information_answer_handl message_type, (uint8_t) hon_protocol::FrameType::GET_MANAGEMENT_INFORMATION_RESPONSE, ProtocolPhases::WAITING_UPDATE_SIGNAL_ANSWER); if (result == haier_protocol::HandlerError::HANDLER_OK) { - this->set_phase_(ProtocolPhases::SENDING_SIGNAL_LEVEL); + this->set_phase(ProtocolPhases::SENDING_SIGNAL_LEVEL); return result; } else { - this->set_phase_(ProtocolPhases::IDLE); + this->set_phase(ProtocolPhases::IDLE); return result; } } @@ -239,7 +234,7 @@ haier_protocol::HandlerError HonClimate::report_network_status_answer_handler_(u haier_protocol::HandlerError result = this->answer_preprocess_(request_type, (uint8_t) hon_protocol::FrameType::REPORT_NETWORK_STATUS, message_type, (uint8_t) hon_protocol::FrameType::CONFIRM, ProtocolPhases::WAITING_SIGNAL_LEVEL_ANSWER); - this->set_phase_(ProtocolPhases::IDLE); + this->set_phase(ProtocolPhases::IDLE); return result; } @@ -248,24 +243,24 @@ haier_protocol::HandlerError HonClimate::get_alarm_status_answer_handler_(uint8_ if (request_type == (uint8_t) hon_protocol::FrameType::GET_ALARM_STATUS) { if (message_type != (uint8_t) hon_protocol::FrameType::GET_ALARM_STATUS_RESPONSE) { // Unexpected answer to request - this->set_phase_(ProtocolPhases::IDLE); - return haier_protocol::HandlerError::UNSUPORTED_MESSAGE; + this->set_phase(ProtocolPhases::IDLE); + return haier_protocol::HandlerError::UNSUPPORTED_MESSAGE; } if (this->protocol_phase_ != ProtocolPhases::WAITING_ALARM_STATUS_ANSWER) { // Don't expect this answer now - this->set_phase_(ProtocolPhases::IDLE); + this->set_phase(ProtocolPhases::IDLE); return haier_protocol::HandlerError::UNEXPECTED_MESSAGE; } memcpy(this->active_alarms_, data + 2, 8); - this->set_phase_(ProtocolPhases::IDLE); + this->set_phase(ProtocolPhases::IDLE); return haier_protocol::HandlerError::HANDLER_OK; } else { - this->set_phase_(ProtocolPhases::IDLE); - return haier_protocol::HandlerError::UNSUPORTED_MESSAGE; + this->set_phase(ProtocolPhases::IDLE); + return haier_protocol::HandlerError::UNSUPPORTED_MESSAGE; } } -void HonClimate::set_answers_handlers() { +void HonClimate::set_handlers() { // Set handlers this->haier_protocol_.set_answer_handler( (uint8_t) (hon_protocol::FrameType::GET_DEVICE_VERSION), @@ -311,7 +306,7 @@ void HonClimate::dump_config() { void HonClimate::process_phase(std::chrono::steady_clock::time_point now) { switch (this->protocol_phase_) { case ProtocolPhases::SENDING_INIT_1: - if (this->can_send_message() && this->is_protocol_initialisation_interval_exceded_(now)) { + if (this->can_send_message() && this->is_protocol_initialisation_interval_exceeded_(now)) { this->hvac_hardware_info_available_ = false; // Indicate device capabilities: // bit 0 - if 1 module support interactive mode @@ -323,24 +318,24 @@ void HonClimate::process_phase(std::chrono::steady_clock::time_point now) { static const haier_protocol::HaierMessage DEVICE_VERSION_REQUEST( (uint8_t) hon_protocol::FrameType::GET_DEVICE_VERSION, module_capabilities, sizeof(module_capabilities)); this->send_message_(DEVICE_VERSION_REQUEST, this->use_crc_); - this->set_phase_(ProtocolPhases::WAITING_ANSWER_INIT_1); + this->set_phase(ProtocolPhases::WAITING_INIT_1_ANSWER); } break; case ProtocolPhases::SENDING_INIT_2: if (this->can_send_message() && this->is_message_interval_exceeded_(now)) { static const haier_protocol::HaierMessage DEVICEID_REQUEST((uint8_t) hon_protocol::FrameType::GET_DEVICE_ID); this->send_message_(DEVICEID_REQUEST, this->use_crc_); - this->set_phase_(ProtocolPhases::WAITING_ANSWER_INIT_2); + this->set_phase(ProtocolPhases::WAITING_INIT_2_ANSWER); } break; case ProtocolPhases::SENDING_FIRST_STATUS_REQUEST: case ProtocolPhases::SENDING_STATUS_REQUEST: if (this->can_send_message() && this->is_message_interval_exceeded_(now)) { static const haier_protocol::HaierMessage STATUS_REQUEST( - (uint8_t) hon_protocol::FrameType::CONTROL, (uint16_t) hon_protocol::SubcomandsControl::GET_USER_DATA); + (uint8_t) hon_protocol::FrameType::CONTROL, (uint16_t) hon_protocol::SubcommandsControl::GET_USER_DATA); this->send_message_(STATUS_REQUEST, this->use_crc_); this->last_status_request_ = now; - this->set_phase_((ProtocolPhases) ((uint8_t) this->protocol_phase_ + 1)); + this->set_phase((ProtocolPhases) ((uint8_t) this->protocol_phase_ + 1)); } break; #ifdef USE_WIFI @@ -350,26 +345,14 @@ void HonClimate::process_phase(std::chrono::steady_clock::time_point now) { (uint8_t) hon_protocol::FrameType::GET_MANAGEMENT_INFORMATION); this->send_message_(UPDATE_SIGNAL_REQUEST, this->use_crc_); this->last_signal_request_ = now; - this->set_phase_(ProtocolPhases::WAITING_UPDATE_SIGNAL_ANSWER); + this->set_phase(ProtocolPhases::WAITING_UPDATE_SIGNAL_ANSWER); } break; case ProtocolPhases::SENDING_SIGNAL_LEVEL: if (this->can_send_message() && this->is_message_interval_exceeded_(now)) { - static uint8_t wifi_status_data[4] = {0x00, 0x00, 0x00, 0x00}; - if (wifi::global_wifi_component->is_connected()) { - wifi_status_data[1] = 0; - int8_t rssi = wifi::global_wifi_component->wifi_rssi(); - wifi_status_data[3] = uint8_t((128 + rssi) / 1.28f); - ESP_LOGD(TAG, "WiFi signal is: %ddBm => %d%%", rssi, wifi_status_data[3]); - } else { - ESP_LOGD(TAG, "WiFi is not connected"); - wifi_status_data[1] = 1; - wifi_status_data[3] = 0; - } - haier_protocol::HaierMessage wifi_status_request((uint8_t) hon_protocol::FrameType::REPORT_NETWORK_STATUS, - wifi_status_data, sizeof(wifi_status_data)); - this->send_message_(wifi_status_request, this->use_crc_); - this->set_phase_(ProtocolPhases::WAITING_SIGNAL_LEVEL_ANSWER); + this->send_message_(this->get_wifi_signal_message_((uint8_t) hon_protocol::FrameType::REPORT_NETWORK_STATUS), + this->use_crc_); + this->set_phase(ProtocolPhases::WAITING_SIGNAL_LEVEL_ANSWER); } break; case ProtocolPhases::WAITING_UPDATE_SIGNAL_ANSWER: @@ -380,7 +363,7 @@ void HonClimate::process_phase(std::chrono::steady_clock::time_point now) { case ProtocolPhases::SENDING_SIGNAL_LEVEL: case ProtocolPhases::WAITING_UPDATE_SIGNAL_ANSWER: case ProtocolPhases::WAITING_SIGNAL_LEVEL_ANSWER: - this->set_phase_(ProtocolPhases::IDLE); + this->set_phase(ProtocolPhases::IDLE); break; #endif case ProtocolPhases::SENDING_ALARM_STATUS_REQUEST: @@ -388,7 +371,7 @@ void HonClimate::process_phase(std::chrono::steady_clock::time_point now) { static const haier_protocol::HaierMessage ALARM_STATUS_REQUEST( (uint8_t) hon_protocol::FrameType::GET_ALARM_STATUS); this->send_message_(ALARM_STATUS_REQUEST, this->use_crc_); - this->set_phase_(ProtocolPhases::WAITING_ALARM_STATUS_ANSWER); + this->set_phase(ProtocolPhases::WAITING_ALARM_STATUS_ANSWER); } break; case ProtocolPhases::SENDING_CONTROL: @@ -403,12 +386,12 @@ void HonClimate::process_phase(std::chrono::steady_clock::time_point now) { this->hvac_settings_.reset(); this->forced_request_status_ = true; this->forced_publish_ = true; - this->set_phase_(ProtocolPhases::IDLE); + this->set_phase(ProtocolPhases::IDLE); } else if (this->can_send_message() && this->is_control_message_interval_exceeded_(now)) { haier_protocol::HaierMessage control_message = get_control_message(); this->send_message_(control_message, this->use_crc_); ESP_LOGI(TAG, "Control packet sent"); - this->set_phase_(ProtocolPhases::WAITING_CONTROL_ANSWER); + this->set_phase(ProtocolPhases::WAITING_CONTROL_ANSWER); } break; case ProtocolPhases::SENDING_POWER_ON_COMMAND: @@ -418,17 +401,17 @@ void HonClimate::process_phase(std::chrono::steady_clock::time_point now) { if (this->protocol_phase_ == ProtocolPhases::SENDING_POWER_ON_COMMAND) pwr_cmd_buf[1] = 0x01; haier_protocol::HaierMessage power_cmd((uint8_t) hon_protocol::FrameType::CONTROL, - ((uint16_t) hon_protocol::SubcomandsControl::SET_SINGLE_PARAMETER) + 1, + ((uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER) + 1, pwr_cmd_buf, sizeof(pwr_cmd_buf)); this->send_message_(power_cmd, this->use_crc_); - this->set_phase_(this->protocol_phase_ == ProtocolPhases::SENDING_POWER_ON_COMMAND - ? ProtocolPhases::WAITING_POWER_ON_ANSWER - : ProtocolPhases::WAITING_POWER_OFF_ANSWER); + this->set_phase(this->protocol_phase_ == ProtocolPhases::SENDING_POWER_ON_COMMAND + ? ProtocolPhases::WAITING_POWER_ON_ANSWER + : ProtocolPhases::WAITING_POWER_OFF_ANSWER); } break; - case ProtocolPhases::WAITING_ANSWER_INIT_1: - case ProtocolPhases::WAITING_ANSWER_INIT_2: + case ProtocolPhases::WAITING_INIT_1_ANSWER: + case ProtocolPhases::WAITING_INIT_2_ANSWER: case ProtocolPhases::WAITING_FIRST_STATUS_ANSWER: case ProtocolPhases::WAITING_ALARM_STATUS_ANSWER: case ProtocolPhases::WAITING_STATUS_ANSWER: @@ -438,14 +421,14 @@ void HonClimate::process_phase(std::chrono::steady_clock::time_point now) { break; case ProtocolPhases::IDLE: { if (this->forced_request_status_ || this->is_status_request_interval_exceeded_(now)) { - this->set_phase_(ProtocolPhases::SENDING_STATUS_REQUEST); + this->set_phase(ProtocolPhases::SENDING_STATUS_REQUEST); this->forced_request_status_ = false; } #ifdef USE_WIFI else if (this->send_wifi_signal_ && (std::chrono::duration_cast(now - this->last_signal_request_).count() > SIGNAL_LEVEL_UPDATE_INTERVAL_MS)) - this->set_phase_(ProtocolPhases::SENDING_UPDATE_SIGNAL_REQUEST); + this->set_phase(ProtocolPhases::SENDING_UPDATE_SIGNAL_REQUEST); #endif } break; default: @@ -456,7 +439,7 @@ void HonClimate::process_phase(std::chrono::steady_clock::time_point now) { #else ESP_LOGE(TAG, "Wrong protocol handler state: %d, resetting communication", (int) this->protocol_phase_); #endif - this->set_phase_(ProtocolPhases::SENDING_INIT_1); + this->set_phase(ProtocolPhases::SENDING_INIT_1); break; } } @@ -551,11 +534,12 @@ haier_protocol::HaierMessage HonClimate::get_control_message() { } } if (climate_control.target_temperature.has_value()) { - out_data->set_point = - climate_control.target_temperature.value() - 16; // set the temperature at our offset, subtract 16. + float target_temp = climate_control.target_temperature.value(); + out_data->set_point = ((int) target_temp) - 16; // set the temperature at our offset, subtract 16. + out_data->half_degree = (target_temp - ((int) target_temp) >= 0.49) ? 1 : 0; } if (out_data->ac_power == 0) { - // If AC is off - no presets alowed + // If AC is off - no presets allowed out_data->quiet_mode = 0; out_data->fast_mode = 0; out_data->sleep_mode = 0; @@ -631,7 +615,7 @@ haier_protocol::HaierMessage HonClimate::get_control_message() { break; } return haier_protocol::HaierMessage((uint8_t) hon_protocol::FrameType::CONTROL, - (uint16_t) hon_protocol::SubcomandsControl::SET_GROUP_PARAMETERS, + (uint16_t) hon_protocol::SubcommandsControl::SET_GROUP_PARAMETERS, control_out_buffer, sizeof(hon_protocol::HaierPacketControl)); } @@ -669,7 +653,7 @@ haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t * { // Target temperature float old_target_temperature = this->target_temperature; - this->target_temperature = packet.control.set_point + 16.0f; + this->target_temperature = packet.control.set_point + 16.0f + ((packet.control.half_degree == 1) ? 0.5f : 0.0f); should_publish = should_publish || (old_target_temperature != this->target_temperature); } { @@ -747,7 +731,7 @@ haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t * if (new_cleaning != this->cleaning_status_) { ESP_LOGD(TAG, "Cleaning status change: %d => %d", (uint8_t) this->cleaning_status_, (uint8_t) new_cleaning); if (new_cleaning == CleaningState::NO_CLEANING) { - // Turnuin AC off after cleaning + // Turning AC off after cleaning this->action_request_ = ActionRequest::TURN_POWER_OFF; } this->cleaning_status_ = new_cleaning; @@ -837,7 +821,7 @@ void HonClimate::process_pending_action() { case ActionRequest::START_SELF_CLEAN: case ActionRequest::START_STERI_CLEAN: // Will reset action with control message sending - this->set_phase_(ProtocolPhases::SENDING_CONTROL); + this->set_phase(ProtocolPhases::SENDING_CONTROL); break; default: HaierClimateBase::process_pending_action(); diff --git a/esphome/components/haier/hon_climate.h b/esphome/components/haier/hon_climate.h index ab913f44e2..cf566e3b8e 100644 --- a/esphome/components/haier/hon_climate.h +++ b/esphome/components/haier/hon_climate.h @@ -48,10 +48,9 @@ class HonClimate : public HaierClimateBase { CleaningState get_cleaning_status() const; void start_self_cleaning(); void start_steri_cleaning(); - void set_send_wifi(bool send_wifi); protected: - void set_answers_handlers() override; + void set_handlers() override; void process_phase(std::chrono::steady_clock::time_point now) override; haier_protocol::HaierMessage get_control_message() override; bool is_message_invalid(uint8_t message_type) override; @@ -87,8 +86,6 @@ class HonClimate : public HaierClimateBase { bool &use_crc_; uint8_t active_alarms_[8]; esphome::sensor::Sensor *outdoor_sensor_; - bool send_wifi_signal_; - std::chrono::steady_clock::time_point last_signal_request_; // To send WiFI signal level }; } // namespace haier diff --git a/esphome/components/haier/hon_packet.h b/esphome/components/haier/hon_packet.h index d572ce80d9..c6b32df200 100644 --- a/esphome/components/haier/hon_packet.h +++ b/esphome/components/haier/hon_packet.h @@ -53,12 +53,12 @@ struct HaierPacketControl { // 13 uint8_t : 8; // 14 - uint8_t ten_degree : 1; // 10 degree status - uint8_t display_status : 1; // If 0 disables AC's display - uint8_t half_degree : 1; // Use half degree - uint8_t intelegence_status : 1; // Intelligence status - uint8_t pmv_status : 1; // Comfort/PMV status - uint8_t use_fahrenheit : 1; // Use Fahrenheit instead of Celsius + uint8_t ten_degree : 1; // 10 degree status + uint8_t display_status : 1; // If 0 disables AC's display + uint8_t half_degree : 1; // Use half degree + uint8_t intelligence_status : 1; // Intelligence status + uint8_t pmv_status : 1; // Comfort/PMV status + uint8_t use_fahrenheit : 1; // Use Fahrenheit instead of Celsius uint8_t : 1; uint8_t steri_clean : 1; // 15 @@ -153,7 +153,7 @@ enum class FrameType : uint8_t { // <-> device, required) REPORT = 0x06, // Report frame (module <-> device, interactive, required) STOP_FAULT_ALARM = 0x09, // Stop fault alarm frame (module -> device, interactive, required) - SYSTEM_DOWNLIK = 0x11, // System downlink frame (module -> device, optional) + SYSTEM_DOWNLINK = 0x11, // System downlink frame (module -> device, optional) DEVICE_UPLINK = 0x12, // Device uplink frame (module <- device , interactive, optional) SYSTEM_QUERY = 0x13, // System query frame (module -> device, optional) SYSTEM_QUERY_RESPONSE = 0x14, // System query response frame (module <- device , optional) @@ -210,7 +210,7 @@ enum class FrameType : uint8_t { WAKE_UP = 0xFE, // Request to wake up (module <-> device, optional) }; -enum class SubcomandsControl : uint16_t { +enum class SubcommandsControl : uint16_t { GET_PARAMETERS = 0x4C01, // Request specific parameters (packet content: parameter ID1 + parameter ID2 + ...) GET_USER_DATA = 0x4D01, // Request all user data from device (packet content: None) GET_BIG_DATA = 0x4DFE, // Request big data information from device (packet content: None) diff --git a/esphome/components/haier/smartair2_climate.cpp b/esphome/components/haier/smartair2_climate.cpp index 9c0fbac350..91b6bb0545 100644 --- a/esphome/components/haier/smartair2_climate.cpp +++ b/esphome/components/haier/smartair2_climate.cpp @@ -11,15 +11,10 @@ namespace esphome { namespace haier { static const char *const TAG = "haier.climate"; +constexpr size_t SIGNAL_LEVEL_UPDATE_INTERVAL_MS = 10000; Smartair2Climate::Smartair2Climate() - : last_status_message_(new uint8_t[sizeof(smartair2_protocol::HaierPacketControl)]) { - this->traits_.set_supported_presets({ - climate::CLIMATE_PRESET_NONE, - climate::CLIMATE_PRESET_BOOST, - climate::CLIMATE_PRESET_COMFORT, - }); -} + : last_status_message_(new uint8_t[sizeof(smartair2_protocol::HaierPacketControl)]), timeouts_counter_(0) {} haier_protocol::HandlerError Smartair2Climate::status_handler_(uint8_t request_type, uint8_t message_type, const uint8_t *data, size_t data_size) { @@ -30,8 +25,8 @@ haier_protocol::HandlerError Smartair2Climate::status_handler_(uint8_t request_t result = this->process_status_message_(data, data_size); if (result != haier_protocol::HandlerError::HANDLER_OK) { ESP_LOGW(TAG, "Error %d while parsing Status packet", (int) result); - this->set_phase_((this->protocol_phase_ >= ProtocolPhases::IDLE) ? ProtocolPhases::IDLE - : ProtocolPhases::SENDING_FIRST_STATUS_REQUEST); + this->set_phase((this->protocol_phase_ >= ProtocolPhases::IDLE) ? ProtocolPhases::IDLE + : ProtocolPhases::SENDING_FIRST_STATUS_REQUEST); } else { if (data_size >= sizeof(smartair2_protocol::HaierPacketControl) + 2) { memcpy(this->last_status_message_.get(), data + 2, sizeof(smartair2_protocol::HaierPacketControl)); @@ -41,11 +36,11 @@ haier_protocol::HandlerError Smartair2Climate::status_handler_(uint8_t request_t } if (this->protocol_phase_ == ProtocolPhases::WAITING_FIRST_STATUS_ANSWER) { ESP_LOGI(TAG, "First HVAC status received"); - this->set_phase_(ProtocolPhases::IDLE); + this->set_phase(ProtocolPhases::IDLE); } else if (this->protocol_phase_ == ProtocolPhases::WAITING_STATUS_ANSWER) { - this->set_phase_(ProtocolPhases::IDLE); + this->set_phase(ProtocolPhases::IDLE); } else if (this->protocol_phase_ == ProtocolPhases::WAITING_CONTROL_ANSWER) { - this->set_phase_(ProtocolPhases::IDLE); + this->set_phase(ProtocolPhases::IDLE); this->set_force_send_control_(false); if (this->hvac_settings_.valid) this->hvac_settings_.reset(); @@ -53,17 +48,82 @@ haier_protocol::HandlerError Smartair2Climate::status_handler_(uint8_t request_t } return result; } else { - this->set_phase_((this->protocol_phase_ >= ProtocolPhases::IDLE) ? ProtocolPhases::IDLE - : ProtocolPhases::SENDING_FIRST_STATUS_REQUEST); + this->set_phase((this->protocol_phase_ >= ProtocolPhases::IDLE) ? ProtocolPhases::IDLE + : ProtocolPhases::SENDING_FIRST_STATUS_REQUEST); return result; } } -void Smartair2Climate::set_answers_handlers() { +haier_protocol::HandlerError Smartair2Climate::get_device_version_answer_handler_(uint8_t request_type, + uint8_t message_type, + const uint8_t *data, + size_t data_size) { + if (request_type != (uint8_t) smartair2_protocol::FrameType::GET_DEVICE_VERSION) + return haier_protocol::HandlerError::UNSUPPORTED_MESSAGE; + if (ProtocolPhases::WAITING_INIT_1_ANSWER != this->protocol_phase_) + return haier_protocol::HandlerError::UNEXPECTED_MESSAGE; + // Invalid packet is expected answer + if ((message_type == (uint8_t) smartair2_protocol::FrameType::GET_DEVICE_VERSION_RESPONSE) && (data_size >= 39) && + ((data[37] & 0x04) != 0)) { + ESP_LOGW(TAG, "It looks like your ESPHome Haier climate configuration is wrong. You should use the hOn protocol " + "instead of smartAir2"); + } + this->set_phase(ProtocolPhases::SENDING_INIT_2); + return haier_protocol::HandlerError::HANDLER_OK; +} + +haier_protocol::HandlerError Smartair2Climate::report_network_status_answer_handler_(uint8_t request_type, + uint8_t message_type, + const uint8_t *data, + size_t data_size) { + haier_protocol::HandlerError result = this->answer_preprocess_( + request_type, (uint8_t) smartair2_protocol::FrameType::REPORT_NETWORK_STATUS, message_type, + (uint8_t) smartair2_protocol::FrameType::CONFIRM, ProtocolPhases::WAITING_SIGNAL_LEVEL_ANSWER); + this->set_phase(ProtocolPhases::IDLE); + return result; +} + +haier_protocol::HandlerError Smartair2Climate::initial_messages_timeout_handler_(uint8_t message_type) { + if (this->protocol_phase_ >= ProtocolPhases::IDLE) + return HaierClimateBase::timeout_default_handler_(message_type); + this->timeouts_counter_++; + ESP_LOGI(TAG, "Answer timeout for command %02X, phase %d, timeout counter %d", message_type, + (int) this->protocol_phase_, this->timeouts_counter_); + if (this->timeouts_counter_ >= 3) { + ProtocolPhases new_phase = (ProtocolPhases) ((int) this->protocol_phase_ + 1); + if (new_phase >= ProtocolPhases::SENDING_ALARM_STATUS_REQUEST) + new_phase = ProtocolPhases::SENDING_INIT_1; + this->set_phase(new_phase); + } else { + // Returning to the previous state to try again + this->set_phase((ProtocolPhases) ((int) this->protocol_phase_ - 1)); + } + return haier_protocol::HandlerError::HANDLER_OK; +} + +void Smartair2Climate::set_handlers() { + // Set handlers + this->haier_protocol_.set_answer_handler( + (uint8_t) (smartair2_protocol::FrameType::GET_DEVICE_VERSION), + std::bind(&Smartair2Climate::get_device_version_answer_handler_, this, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3, std::placeholders::_4)); this->haier_protocol_.set_answer_handler( (uint8_t) (smartair2_protocol::FrameType::CONTROL), std::bind(&Smartair2Climate::status_handler_, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4)); + this->haier_protocol_.set_answer_handler( + (uint8_t) (smartair2_protocol::FrameType::REPORT_NETWORK_STATUS), + std::bind(&Smartair2Climate::report_network_status_answer_handler_, this, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3, std::placeholders::_4)); + this->haier_protocol_.set_timeout_handler( + (uint8_t) (smartair2_protocol::FrameType::GET_DEVICE_ID), + std::bind(&Smartair2Climate::initial_messages_timeout_handler_, this, std::placeholders::_1)); + this->haier_protocol_.set_timeout_handler( + (uint8_t) (smartair2_protocol::FrameType::GET_DEVICE_VERSION), + std::bind(&Smartair2Climate::initial_messages_timeout_handler_, this, std::placeholders::_1)); + this->haier_protocol_.set_timeout_handler( + (uint8_t) (smartair2_protocol::FrameType::CONTROL), + std::bind(&Smartair2Climate::initial_messages_timeout_handler_, this, std::placeholders::_1)); } void Smartair2Climate::dump_config() { @@ -74,39 +134,60 @@ void Smartair2Climate::dump_config() { void Smartair2Climate::process_phase(std::chrono::steady_clock::time_point now) { switch (this->protocol_phase_) { case ProtocolPhases::SENDING_INIT_1: - this->set_phase_(ProtocolPhases::SENDING_FIRST_STATUS_REQUEST); - break; - case ProtocolPhases::WAITING_ANSWER_INIT_1: - case ProtocolPhases::SENDING_INIT_2: - case ProtocolPhases::WAITING_ANSWER_INIT_2: - case ProtocolPhases::SENDING_ALARM_STATUS_REQUEST: - case ProtocolPhases::WAITING_ALARM_STATUS_ANSWER: - this->set_phase_(ProtocolPhases::SENDING_INIT_1); - break; - case ProtocolPhases::SENDING_UPDATE_SIGNAL_REQUEST: - case ProtocolPhases::WAITING_UPDATE_SIGNAL_ANSWER: - case ProtocolPhases::SENDING_SIGNAL_LEVEL: - case ProtocolPhases::WAITING_SIGNAL_LEVEL_ANSWER: - this->set_phase_(ProtocolPhases::IDLE); - break; - case ProtocolPhases::SENDING_FIRST_STATUS_REQUEST: - if (this->can_send_message() && this->is_protocol_initialisation_interval_exceded_(now)) { - static const haier_protocol::HaierMessage STATUS_REQUEST((uint8_t) smartair2_protocol::FrameType::CONTROL, - 0x4D01); - this->send_message_(STATUS_REQUEST, false); - this->last_status_request_ = now; - this->set_phase_(ProtocolPhases::WAITING_FIRST_STATUS_ANSWER); + if (this->can_send_message() && + (((this->timeouts_counter_ == 0) && (this->is_protocol_initialisation_interval_exceeded_(now))) || + ((this->timeouts_counter_ > 0) && (this->is_message_interval_exceeded_(now))))) { + // Indicate device capabilities: + // bit 0 - if 1 module support interactive mode + // bit 1 - if 1 module support controller-device mode + // bit 2 - if 1 module support crc + // bit 3 - if 1 module support multiple devices + // bit 4..bit 15 - not used + uint8_t module_capabilities[2] = {0b00000000, 0b00000111}; + static const haier_protocol::HaierMessage DEVICE_VERSION_REQUEST( + (uint8_t) smartair2_protocol::FrameType::GET_DEVICE_VERSION, module_capabilities, + sizeof(module_capabilities)); + this->send_message_(DEVICE_VERSION_REQUEST, false); + this->set_phase(ProtocolPhases::WAITING_INIT_1_ANSWER); } break; + case ProtocolPhases::SENDING_INIT_2: + case ProtocolPhases::WAITING_INIT_2_ANSWER: + this->set_phase(ProtocolPhases::SENDING_FIRST_STATUS_REQUEST); + break; + case ProtocolPhases::SENDING_FIRST_STATUS_REQUEST: case ProtocolPhases::SENDING_STATUS_REQUEST: if (this->can_send_message() && this->is_message_interval_exceeded_(now)) { static const haier_protocol::HaierMessage STATUS_REQUEST((uint8_t) smartair2_protocol::FrameType::CONTROL, 0x4D01); this->send_message_(STATUS_REQUEST, false); this->last_status_request_ = now; - this->set_phase_(ProtocolPhases::WAITING_STATUS_ANSWER); + this->set_phase((ProtocolPhases) ((uint8_t) this->protocol_phase_ + 1)); } break; +#ifdef USE_WIFI + case ProtocolPhases::SENDING_SIGNAL_LEVEL: + if (this->can_send_message() && this->is_message_interval_exceeded_(now)) { + this->send_message_( + this->get_wifi_signal_message_((uint8_t) smartair2_protocol::FrameType::REPORT_NETWORK_STATUS), false); + this->last_signal_request_ = now; + this->set_phase(ProtocolPhases::WAITING_SIGNAL_LEVEL_ANSWER); + } + break; + case ProtocolPhases::WAITING_SIGNAL_LEVEL_ANSWER: + break; +#else + case ProtocolPhases::SENDING_SIGNAL_LEVEL: + case ProtocolPhases::WAITING_SIGNAL_LEVEL_ANSWER this->set_phase(ProtocolPhases::IDLE); break; +#endif + case ProtocolPhases::SENDING_UPDATE_SIGNAL_REQUEST: + case ProtocolPhases::WAITING_UPDATE_SIGNAL_ANSWER: + this->set_phase(ProtocolPhases::SENDING_SIGNAL_LEVEL); + break; + case ProtocolPhases::SENDING_ALARM_STATUS_REQUEST: + case ProtocolPhases::WAITING_ALARM_STATUS_ANSWER: + this->set_phase(ProtocolPhases::SENDING_INIT_1); + break; case ProtocolPhases::SENDING_CONTROL: if (this->first_control_attempt_) { this->control_request_timestamp_ = now; @@ -119,14 +200,14 @@ void Smartair2Climate::process_phase(std::chrono::steady_clock::time_point now) this->hvac_settings_.reset(); this->forced_request_status_ = true; this->forced_publish_ = true; - this->set_phase_(ProtocolPhases::IDLE); + this->set_phase(ProtocolPhases::IDLE); } else if (this->can_send_message() && this->is_control_message_interval_exceeded_( now)) // Using CONTROL_MESSAGES_INTERVAL_MS to speedup requests { haier_protocol::HaierMessage control_message = get_control_message(); this->send_message_(control_message, false); ESP_LOGI(TAG, "Control packet sent"); - this->set_phase_(ProtocolPhases::WAITING_CONTROL_ANSWER); + this->set_phase(ProtocolPhases::WAITING_CONTROL_ANSWER); } break; case ProtocolPhases::SENDING_POWER_ON_COMMAND: @@ -136,11 +217,12 @@ void Smartair2Climate::process_phase(std::chrono::steady_clock::time_point now) (uint8_t) smartair2_protocol::FrameType::CONTROL, this->protocol_phase_ == ProtocolPhases::SENDING_POWER_ON_COMMAND ? 0x4D02 : 0x4D03); this->send_message_(power_cmd, false); - this->set_phase_(this->protocol_phase_ == ProtocolPhases::SENDING_POWER_ON_COMMAND - ? ProtocolPhases::WAITING_POWER_ON_ANSWER - : ProtocolPhases::WAITING_POWER_OFF_ANSWER); + this->set_phase(this->protocol_phase_ == ProtocolPhases::SENDING_POWER_ON_COMMAND + ? ProtocolPhases::WAITING_POWER_ON_ANSWER + : ProtocolPhases::WAITING_POWER_OFF_ANSWER); } break; + case ProtocolPhases::WAITING_INIT_1_ANSWER: case ProtocolPhases::WAITING_FIRST_STATUS_ANSWER: case ProtocolPhases::WAITING_STATUS_ANSWER: case ProtocolPhases::WAITING_CONTROL_ANSWER: @@ -149,14 +231,25 @@ void Smartair2Climate::process_phase(std::chrono::steady_clock::time_point now) break; case ProtocolPhases::IDLE: { if (this->forced_request_status_ || this->is_status_request_interval_exceeded_(now)) { - this->set_phase_(ProtocolPhases::SENDING_STATUS_REQUEST); + this->set_phase(ProtocolPhases::SENDING_STATUS_REQUEST); this->forced_request_status_ = false; } +#ifdef USE_WIFI + else if (this->send_wifi_signal_ && + (std::chrono::duration_cast(now - this->last_signal_request_).count() > + SIGNAL_LEVEL_UPDATE_INTERVAL_MS)) + this->set_phase(ProtocolPhases::SENDING_UPDATE_SIGNAL_REQUEST); +#endif } break; default: // Shouldn't get here +#if (HAIER_LOG_LEVEL > 4) + ESP_LOGE(TAG, "Wrong protocol handler state: %s (%d), resetting communication", + phase_to_string_(this->protocol_phase_), (int) this->protocol_phase_); +#else ESP_LOGE(TAG, "Wrong protocol handler state: %d, resetting communication", (int) this->protocol_phase_); - this->set_phase_(ProtocolPhases::SENDING_FIRST_STATUS_REQUEST); +#endif + this->set_phase(ProtocolPhases::SENDING_INIT_1); break; } } @@ -256,11 +349,12 @@ haier_protocol::HaierMessage Smartair2Climate::get_control_message() { } } if (climate_control.target_temperature.has_value()) { - out_data->set_point = - climate_control.target_temperature.value() - 16; // set the temperature at our offset, subtract 16. + float target_temp = climate_control.target_temperature.value(); + out_data->set_point = target_temp - 16; // set the temperature with offset 16 + out_data->half_degree = (target_temp - ((int) target_temp) >= 0.49) ? 1 : 0; } if (out_data->ac_power == 0) { - // If AC is off - no presets alowed + // If AC is off - no presets allowed out_data->turbo_mode = 0; out_data->quiet_mode = 0; } else if (climate_control.preset.has_value()) { @@ -312,7 +406,7 @@ haier_protocol::HandlerError Smartair2Climate::process_status_message_(const uin { // Target temperature float old_target_temperature = this->target_temperature; - this->target_temperature = packet.control.set_point + 16.0f; + this->target_temperature = packet.control.set_point + 16.0f + ((packet.control.half_degree == 1) ? 0.5f : 0.0f); should_publish = should_publish || (old_target_temperature != this->target_temperature); } { @@ -333,7 +427,7 @@ haier_protocol::HandlerError Smartair2Climate::process_status_message_(const uin } switch (packet.control.fan_mode) { case (uint8_t) smartair2_protocol::FanMode::FAN_AUTO: - // Somtimes AC reports in fan only mode that fan speed is auto + // Sometimes AC reports in fan only mode that fan speed is auto // but never accept this value back if (packet.control.ac_mode != (uint8_t) smartair2_protocol::ConditioningMode::FAN) { this->fan_mode = CLIMATE_FAN_AUTO; @@ -453,5 +547,15 @@ bool Smartair2Climate::is_message_invalid(uint8_t message_type) { return message_type == (uint8_t) smartair2_protocol::FrameType::INVALID; } +void Smartair2Climate::set_phase(HaierClimateBase::ProtocolPhases phase) { + int old_phase = (int) this->protocol_phase_; + int new_phase = (int) phase; + int min_p = std::min(old_phase, new_phase); + int max_p = std::max(old_phase, new_phase); + if ((min_p % 2 != 0) || (max_p - min_p > 1)) + this->timeouts_counter_ = 0; + HaierClimateBase::set_phase(phase); +} + } // namespace haier } // namespace esphome diff --git a/esphome/components/haier/smartair2_climate.h b/esphome/components/haier/smartair2_climate.h index c89d1f0be9..f173b10749 100644 --- a/esphome/components/haier/smartair2_climate.h +++ b/esphome/components/haier/smartair2_climate.h @@ -15,16 +15,25 @@ class Smartair2Climate : public HaierClimateBase { void dump_config() override; protected: - void set_answers_handlers() override; + void set_handlers() override; void process_phase(std::chrono::steady_clock::time_point now) override; haier_protocol::HaierMessage get_control_message() override; bool is_message_invalid(uint8_t message_type) override; - // Answers handlers + void set_phase(HaierClimateBase::ProtocolPhases phase) override; + // Answer and timeout handlers haier_protocol::HandlerError status_handler_(uint8_t request_type, uint8_t message_type, const uint8_t *data, size_t data_size); + haier_protocol::HandlerError get_device_version_answer_handler_(uint8_t request_type, uint8_t message_type, + const uint8_t *data, size_t data_size); + haier_protocol::HandlerError get_device_id_answer_handler_(uint8_t request_type, uint8_t message_type, + const uint8_t *data, size_t data_size); + haier_protocol::HandlerError report_network_status_answer_handler_(uint8_t request_type, uint8_t message_type, + const uint8_t *data, size_t data_size); + haier_protocol::HandlerError initial_messages_timeout_handler_(uint8_t message_type); // Helper functions haier_protocol::HandlerError process_status_message_(const uint8_t *packet, uint8_t size); std::unique_ptr last_status_message_; + unsigned int timeouts_counter_; }; } // namespace haier diff --git a/esphome/components/haier/smartair2_packet.h b/esphome/components/haier/smartair2_packet.h index 8046516c5f..f791c21af2 100644 --- a/esphome/components/haier/smartair2_packet.h +++ b/esphome/components/haier/smartair2_packet.h @@ -53,8 +53,8 @@ struct HaierPacketControl { uint8_t : 2; uint8_t health_mode : 1; // Health mode on or off uint8_t compressor : 1; // Compressor on or off ??? - uint8_t : 1; - uint8_t ten_degree : 1; // 10 degree status (only work in heat mode) + uint8_t half_degree : 1; // Use half degree + uint8_t ten_degree : 1; // 10 degree status (only work in heat mode) uint8_t : 0; // 28 uint8_t : 8; @@ -88,6 +88,9 @@ enum class FrameType : uint8_t { INVALID = 0x03, CONFIRM = 0x05, GET_DEVICE_VERSION = 0x61, + GET_DEVICE_VERSION_RESPONSE = 0x62, + GET_DEVICE_ID = 0x70, + GET_DEVICE_ID_RESPONSE = 0x71, REPORT_NETWORK_STATUS = 0xF7, NO_COMMAND = 0xFF, }; diff --git a/platformio.ini b/platformio.ini index ba149ce99e..5da3b9f978 100644 --- a/platformio.ini +++ b/platformio.ini @@ -39,7 +39,7 @@ lib_deps = bblanchon/ArduinoJson@6.18.5 ; json wjtje/qr-code-generator-library@1.7.0 ; qr_code functionpointer/arduino-MLX90393@1.0.0 ; mlx90393 - pavlodn/HaierProtocol@0.9.18 ; haier + pavlodn/HaierProtocol@0.9.20 ; haier ; This is using the repository until a new release is published to PlatformIO https://github.com/Sensirion/arduino-gas-index-algorithm.git#3.2.1 ; Sensirion Gas Index Algorithm Arduino Library build_flags = From 1269bf979166e6624a9e92b01de1a9e84cb8024e Mon Sep 17 00:00:00 2001 From: Steve Rodgers Date: Fri, 11 Aug 2023 17:50:33 -0700 Subject: [PATCH 006/111] pca9554 cache reads (#5137) --- esphome/components/pca9554/pca9554.cpp | 25 +++++++++++++++++++++++-- esphome/components/pca9554/pca9554.h | 6 ++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/esphome/components/pca9554/pca9554.cpp b/esphome/components/pca9554/pca9554.cpp index 39093fcf54..74c64dffaa 100644 --- a/esphome/components/pca9554/pca9554.cpp +++ b/esphome/components/pca9554/pca9554.cpp @@ -26,7 +26,7 @@ void PCA9554Component::setup() { this->config_mask_ = 0; // Invert mask as the part sees a 1 as an input this->write_register_(CONFIG_REG, ~this->config_mask_); - // All ouputs low + // All outputs low this->output_mask_ = 0; this->write_register_(OUTPUT_REG, this->output_mask_); // Read the inputs @@ -34,6 +34,14 @@ void PCA9554Component::setup() { ESP_LOGD(TAG, "Initialization complete. Warning: %d, Error: %d", this->status_has_warning(), this->status_has_error()); } + +void PCA9554Component::loop() { + // The read_inputs_() method will cache the input values from the chip. + this->read_inputs_(); + // Clear all the previously read flags. + this->was_previously_read_ = 0x00; +} + void PCA9554Component::dump_config() { ESP_LOGCONFIG(TAG, "PCA9554:"); LOG_I2C_DEVICE(this) @@ -43,7 +51,16 @@ void PCA9554Component::dump_config() { } bool PCA9554Component::digital_read(uint8_t pin) { - this->read_inputs_(); + // Note: We want to try and avoid doing any I2C bus read transactions here + // to conserve I2C bus bandwidth. So what we do is check to see if we + // have seen a read during the time esphome is running this loop. If we have, + // we do an I2C bus transaction to get the latest value. If we haven't + // we return a cached value which was read at the time loop() was called. + if (this->was_previously_read_ & (1 << pin)) + this->read_inputs_(); // Force a read of a new value + // Indicate we saw a read request for this pin in case a + // read happens later in the same loop. + this->was_previously_read_ |= (1 << pin); return this->input_mask_ & (1 << pin); } @@ -98,6 +115,10 @@ bool PCA9554Component::write_register_(uint8_t reg, uint8_t value) { float PCA9554Component::get_setup_priority() const { return setup_priority::IO; } +// Run our loop() method very early in the loop, so that we cache read values before +// before other components call our digital_read() method. +float PCA9554Component::get_loop_priority() const { return 9.0f; } // Just after WIFI + void PCA9554GPIOPin::setup() { pin_mode(flags_); } void PCA9554GPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); } bool PCA9554GPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; } diff --git a/esphome/components/pca9554/pca9554.h b/esphome/components/pca9554/pca9554.h index d1bfc36bec..c2aa5c30ed 100644 --- a/esphome/components/pca9554/pca9554.h +++ b/esphome/components/pca9554/pca9554.h @@ -13,6 +13,8 @@ class PCA9554Component : public Component, public i2c::I2CDevice { /// Check i2c availability and setup masks void setup() override; + /// Poll for input changes periodically + void loop() override; /// Helper function to read the value of a pin. bool digital_read(uint8_t pin); /// Helper function to write the value of a pin. @@ -22,6 +24,8 @@ class PCA9554Component : public Component, public i2c::I2CDevice { float get_setup_priority() const override; + float get_loop_priority() const override; + void dump_config() override; protected: @@ -35,6 +39,8 @@ class PCA9554Component : public Component, public i2c::I2CDevice { uint8_t output_mask_{0x00}; /// The state of the actual input pin states - 1 means HIGH, 0 means LOW uint8_t input_mask_{0x00}; + /// Flags to check if read previously during this loop + uint8_t was_previously_read_ = {0x00}; /// Storage for last I2C error seen esphome::i2c::ErrorCode last_error_; }; From 0daf4545a9036293b2035a90730704774cefcdb2 Mon Sep 17 00:00:00 2001 From: Sergey Dudanov Date: Mon, 14 Aug 2023 01:06:04 +0400 Subject: [PATCH 007/111] fix midea: undo approved PR#4053 (#5233) --- esphome/components/remote_base/__init__.py | 18 +++++++---------- .../components/remote_base/midea_protocol.h | 20 +++++-------------- tests/test1.yaml | 2 ++ 3 files changed, 14 insertions(+), 26 deletions(-) diff --git a/esphome/components/remote_base/__init__.py b/esphome/components/remote_base/__init__.py index 24993e84d3..9e46506b3c 100644 --- a/esphome/components/remote_base/__init__.py +++ b/esphome/components/remote_base/__init__.py @@ -1488,11 +1488,9 @@ MideaData, MideaBinarySensor, MideaTrigger, MideaAction, MideaDumper = declare_p MideaAction = ns.class_("MideaAction", RemoteTransmitterActionBase) MIDEA_SCHEMA = cv.Schema( { - cv.Required(CONF_CODE): cv.templatable( - cv.All( - [cv.Any(cv.hex_uint8_t, cv.uint8_t)], - cv.Length(min=5, max=5), - ) + cv.Required(CONF_CODE): cv.All( + [cv.Any(cv.hex_uint8_t, cv.uint8_t)], + cv.Length(min=5, max=5), ), } ) @@ -1519,12 +1517,10 @@ def midea_dumper(var, config): MIDEA_SCHEMA, ) async def midea_action(var, config, args): - code_ = config[CONF_CODE] - if cg.is_template(code_): - template_ = await cg.templatable(code_, args, cg.std_vector.template(cg.uint8)) - cg.add(var.set_code_template(template_)) - else: - cg.add(var.set_code_static(code_)) + template_ = await cg.templatable( + config[CONF_CODE], args, cg.std_vector.template(cg.uint8) + ) + cg.add(var.set_code(template_)) # AEHA diff --git a/esphome/components/remote_base/midea_protocol.h b/esphome/components/remote_base/midea_protocol.h index d81a50241b..f5db313579 100644 --- a/esphome/components/remote_base/midea_protocol.h +++ b/esphome/components/remote_base/midea_protocol.h @@ -1,11 +1,11 @@ #pragma once +#include +#include + #include "esphome/core/component.h" #include "esphome/core/helpers.h" #include "remote_base.h" -#include -#include -#include namespace esphome { namespace remote_base { @@ -84,23 +84,13 @@ using MideaDumper = RemoteReceiverDumper; template class MideaAction : public RemoteTransmitterActionBase { TEMPLATABLE_VALUE(std::vector, code) - void set_code_static(std::vector code) { code_static_ = std::move(code); } - void set_code_template(std::function(Ts...)> func) { this->code_func_ = func; } + void set_code(std::initializer_list code) { this->code_ = code; } void encode(RemoteTransmitData *dst, Ts... x) override { - MideaData data; - if (!this->code_static_.empty()) { - data = MideaData(this->code_static_); - } else { - data = MideaData(this->code_func_(x...)); - } + MideaData data(this->code_.value(x...)); data.finalize(); MideaProtocol().encode(dst, data); } - - protected: - std::function(Ts...)> code_func_{}; - std::vector code_static_{}; }; } // namespace remote_base diff --git a/tests/test1.yaml b/tests/test1.yaml index 5c9b83a915..4eb78515c9 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -2333,6 +2333,8 @@ switch: second: !lambda "return 0xB21F98;" - remote_transmitter.transmit_midea: code: [0xA2, 0x08, 0xFF, 0xFF, 0xFF] + - remote_transmitter.transmit_midea: + code: !lambda "return {0xA2, 0x08, 0xFF, 0xFF, 0xFF};" - platform: gpio name: "MCP23S08 Pin #0" pin: From 08013be6dd9136957878639a7d981d3dd0c09c70 Mon Sep 17 00:00:00 2001 From: Pavlo Dudnytskyi Date: Sun, 13 Aug 2023 23:08:18 +0200 Subject: [PATCH 008/111] Fixing smartair2 protocol implementation if no Wi-Fi (#5238) --- esphome/components/haier/smartair2_climate.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/esphome/components/haier/smartair2_climate.cpp b/esphome/components/haier/smartair2_climate.cpp index 91b6bb0545..8bee37dadf 100644 --- a/esphome/components/haier/smartair2_climate.cpp +++ b/esphome/components/haier/smartair2_climate.cpp @@ -178,7 +178,9 @@ void Smartair2Climate::process_phase(std::chrono::steady_clock::time_point now) break; #else case ProtocolPhases::SENDING_SIGNAL_LEVEL: - case ProtocolPhases::WAITING_SIGNAL_LEVEL_ANSWER this->set_phase(ProtocolPhases::IDLE); break; + case ProtocolPhases::WAITING_SIGNAL_LEVEL_ANSWER: + this->set_phase(ProtocolPhases::IDLE); + break; #endif case ProtocolPhases::SENDING_UPDATE_SIGNAL_REQUEST: case ProtocolPhases::WAITING_UPDATE_SIGNAL_ANSWER: From 4c1af007ca9f90a64683b2dfd5c18eb209bfde6c Mon Sep 17 00:00:00 2001 From: Kjell Braden Date: Sun, 13 Aug 2023 23:09:51 +0200 Subject: [PATCH 009/111] tuya: add time sync callback only once to prevent memleak (#5234) --- esphome/components/tuya/tuya.cpp | 9 +++++++-- esphome/components/tuya/tuya.h | 1 + 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/esphome/components/tuya/tuya.cpp b/esphome/components/tuya/tuya.cpp index 7e6b1d53fe..0fad151488 100644 --- a/esphome/components/tuya/tuya.cpp +++ b/esphome/components/tuya/tuya.cpp @@ -246,8 +246,13 @@ void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buff #ifdef USE_TIME if (this->time_id_.has_value()) { this->send_local_time_(); - auto *time_id = *this->time_id_; - time_id->add_on_time_sync_callback([this] { this->send_local_time_(); }); + + if (!this->time_sync_callback_registered_) { + // tuya mcu supports time, so we let them know when our time changed + auto *time_id = *this->time_id_; + time_id->add_on_time_sync_callback([this] { this->send_local_time_(); }); + this->time_sync_callback_registered_ = true; + } } else { ESP_LOGW(TAG, "LOCAL_TIME_QUERY is not handled because time is not configured"); } diff --git a/esphome/components/tuya/tuya.h b/esphome/components/tuya/tuya.h index b9901dd5e7..26f6f65912 100644 --- a/esphome/components/tuya/tuya.h +++ b/esphome/components/tuya/tuya.h @@ -130,6 +130,7 @@ class Tuya : public Component, public uart::UARTDevice { #ifdef USE_TIME void send_local_time_(); optional time_id_{}; + bool time_sync_callback_registered_{false}; #endif TuyaInitState init_state_ = TuyaInitState::INIT_HEARTBEAT; bool init_failed_{false}; From 2dd4aa7bf677073463a14ef8147c129ace19cf95 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 14 Aug 2023 10:09:20 +1200 Subject: [PATCH 010/111] Fix duplicate tuya time warning (#5243) --- esphome/components/tuya/tuya.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/esphome/components/tuya/tuya.cpp b/esphome/components/tuya/tuya.cpp index 0fad151488..daf5080e7a 100644 --- a/esphome/components/tuya/tuya.cpp +++ b/esphome/components/tuya/tuya.cpp @@ -1,9 +1,9 @@ #include "tuya.h" #include "esphome/components/network/util.h" +#include "esphome/core/gpio.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" #include "esphome/core/util.h" -#include "esphome/core/gpio.h" #ifdef USE_WIFI #include "esphome/components/wifi/wifi_component.h" @@ -253,12 +253,11 @@ void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buff time_id->add_on_time_sync_callback([this] { this->send_local_time_(); }); this->time_sync_callback_registered_ = true; } - } else { + } else +#endif + { ESP_LOGW(TAG, "LOCAL_TIME_QUERY is not handled because time is not configured"); } -#else - ESP_LOGE(TAG, "LOCAL_TIME_QUERY is not handled"); -#endif break; case TuyaCommandType::VACUUM_MAP_UPLOAD: this->send_command_( From 3b2c61e813d576aca954118647d2b528178783f0 Mon Sep 17 00:00:00 2001 From: MrEditor97 Date: Mon, 14 Aug 2023 20:12:03 +0100 Subject: [PATCH 011/111] Updated my username in Code Owners (#5247) --- CODEOWNERS | 6 +++--- esphome/components/cap1188/__init__.py | 2 +- esphome/components/ina260/sensor.py | 2 +- esphome/components/mcp9600/sensor.py | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index 408caee4f2..d815252ba3 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -54,7 +54,7 @@ esphome/components/bp1658cj/* @Cossid esphome/components/bp5758d/* @Cossid esphome/components/button/* @esphome/core esphome/components/canbus/* @danielschramm @mvturnho -esphome/components/cap1188/* @MrEditor97 +esphome/components/cap1188/* @mreditor97 esphome/components/captive_portal/* @OttoWinter esphome/components/ccs811/* @habbie esphome/components/cd74hc4067/* @asoehlke @@ -132,7 +132,7 @@ esphome/components/i2s_audio/speaker/* @jesserockz esphome/components/ili9xxx/* @nielsnl68 esphome/components/improv_base/* @esphome/core esphome/components/improv_serial/* @esphome/core -esphome/components/ina260/* @MrEditor97 +esphome/components/ina260/* @mreditor97 esphome/components/inkbird_ibsth1_mini/* @fkirill esphome/components/inkplate6/* @jesserockz esphome/components/integration/* @OttoWinter @@ -168,7 +168,7 @@ esphome/components/mcp2515/* @danielschramm @mvturnho esphome/components/mcp3204/* @rsumner esphome/components/mcp4728/* @berfenger esphome/components/mcp47a1/* @jesserockz -esphome/components/mcp9600/* @MrEditor97 +esphome/components/mcp9600/* @mreditor97 esphome/components/mcp9808/* @k7hpn esphome/components/md5/* @esphome/core esphome/components/mdns/* @esphome/core diff --git a/esphome/components/cap1188/__init__.py b/esphome/components/cap1188/__init__.py index 74be9df186..f22e6d6c9e 100644 --- a/esphome/components/cap1188/__init__.py +++ b/esphome/components/cap1188/__init__.py @@ -9,7 +9,7 @@ CONF_ALLOW_MULTIPLE_TOUCHES = "allow_multiple_touches" DEPENDENCIES = ["i2c"] AUTO_LOAD = ["binary_sensor", "output"] -CODEOWNERS = ["@MrEditor97"] +CODEOWNERS = ["@mreditor97"] cap1188_ns = cg.esphome_ns.namespace("cap1188") CONF_CAP1188_ID = "cap1188_id" diff --git a/esphome/components/ina260/sensor.py b/esphome/components/ina260/sensor.py index 048e713afa..732d15d9ca 100644 --- a/esphome/components/ina260/sensor.py +++ b/esphome/components/ina260/sensor.py @@ -16,7 +16,7 @@ from esphome.const import ( ) DEPENDENCIES = ["i2c"] -CODEOWNERS = ["@MrEditor97"] +CODEOWNERS = ["@mreditor97"] ina260_ns = cg.esphome_ns.namespace("ina260") INA260Component = ina260_ns.class_( diff --git a/esphome/components/mcp9600/sensor.py b/esphome/components/mcp9600/sensor.py index 392ee4e773..8557c7205e 100644 --- a/esphome/components/mcp9600/sensor.py +++ b/esphome/components/mcp9600/sensor.py @@ -13,7 +13,7 @@ CONF_HOT_JUNCTION = "hot_junction" CONF_COLD_JUNCTION = "cold_junction" DEPENDENCIES = ["i2c"] -CODEOWNERS = ["@MrEditor97"] +CODEOWNERS = ["@mreditor97"] mcp9600_ns = cg.esphome_ns.namespace("mcp9600") MCP9600Component = mcp9600_ns.class_( From e963eedb6405b6ae931d0b96570c3352908d63cf Mon Sep 17 00:00:00 2001 From: MrEditor97 Date: Mon, 14 Aug 2023 20:14:08 +0100 Subject: [PATCH 012/111] Change XL9535 `setup_priority` to IO (#5246) --- esphome/components/xl9535/xl9535.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/xl9535/xl9535.h b/esphome/components/xl9535/xl9535.h index 8f0a868c42..dd67990fa8 100644 --- a/esphome/components/xl9535/xl9535.h +++ b/esphome/components/xl9535/xl9535.h @@ -26,7 +26,7 @@ class XL9535Component : public Component, public i2c::I2CDevice { void setup() override; void dump_config() override; - float get_setup_priority() const override { return setup_priority::DATA; } + float get_setup_priority() const override { return setup_priority::IO; } }; class XL9535GPIOPin : public GPIOPin { From b9e9223fddfa47e8641c25d4e87a7c90866606f8 Mon Sep 17 00:00:00 2001 From: Sergey Dudanov Date: Mon, 14 Aug 2023 23:21:22 +0400 Subject: [PATCH 013/111] rmt_base additional minor changes (#5245) --- esphome/components/remote_base/__init__.py | 21 +++++-------------- .../components/remote_base/midea_protocol.h | 1 - 2 files changed, 5 insertions(+), 17 deletions(-) diff --git a/esphome/components/remote_base/__init__.py b/esphome/components/remote_base/__init__.py index 9e46506b3c..e2d96c9472 100644 --- a/esphome/components/remote_base/__init__.py +++ b/esphome/components/remote_base/__init__.py @@ -1488,10 +1488,7 @@ MideaData, MideaBinarySensor, MideaTrigger, MideaAction, MideaDumper = declare_p MideaAction = ns.class_("MideaAction", RemoteTransmitterActionBase) MIDEA_SCHEMA = cv.Schema( { - cv.Required(CONF_CODE): cv.All( - [cv.Any(cv.hex_uint8_t, cv.uint8_t)], - cv.Length(min=5, max=5), - ), + cv.Required(CONF_CODE): cv.All([cv.hex_uint8_t], cv.Length(min=5, max=5)), } ) @@ -1511,15 +1508,10 @@ def midea_dumper(var, config): pass -@register_action( - "midea", - MideaAction, - MIDEA_SCHEMA, -) +@register_action("midea", MideaAction, MIDEA_SCHEMA) async def midea_action(var, config, args): - template_ = await cg.templatable( - config[CONF_CODE], args, cg.std_vector.template(cg.uint8) - ) + vec_ = cg.std_vector.template(cg.uint8) + template_ = await cg.templatable(config[CONF_CODE], args, vec_, vec_) cg.add(var.set_code(template_)) @@ -1530,10 +1522,7 @@ AEHAData, AEHABinarySensor, AEHATrigger, AEHAAction, AEHADumper = declare_protoc AEHA_SCHEMA = cv.Schema( { cv.Required(CONF_ADDRESS): cv.hex_uint16_t, - cv.Required(CONF_DATA): cv.All( - [cv.Any(cv.hex_uint8_t, cv.uint8_t)], - cv.Length(min=2, max=35), - ), + cv.Required(CONF_DATA): cv.All([cv.hex_uint8_t], cv.Length(min=2, max=35)), } ) diff --git a/esphome/components/remote_base/midea_protocol.h b/esphome/components/remote_base/midea_protocol.h index f5db313579..6925686b34 100644 --- a/esphome/components/remote_base/midea_protocol.h +++ b/esphome/components/remote_base/midea_protocol.h @@ -84,7 +84,6 @@ using MideaDumper = RemoteReceiverDumper; template class MideaAction : public RemoteTransmitterActionBase { TEMPLATABLE_VALUE(std::vector, code) - void set_code(std::initializer_list code) { this->code_ = code; } void encode(RemoteTransmitData *dst, Ts... x) override { MideaData data(this->code_.value(x...)); From 6089526975ee74b73d5b4975b7b481f37d50ac58 Mon Sep 17 00:00:00 2001 From: mulder-fbi Date: Wed, 16 Aug 2023 00:52:56 +0200 Subject: [PATCH 014/111] Fix 24 bit signed integer parsing in sml parser (#5250) --- esphome/components/sml/sml_parser.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/esphome/components/sml/sml_parser.cpp b/esphome/components/sml/sml_parser.cpp index ff7da4cabd..91b320a30e 100644 --- a/esphome/components/sml/sml_parser.cpp +++ b/esphome/components/sml/sml_parser.cpp @@ -88,6 +88,11 @@ uint64_t bytes_to_uint(const bytes &buffer) { for (auto const value : buffer) { val = (val << 8) + value; } + // Some smart meters send 24 bit signed integers. Sign extend to 64 bit if the + // 24 bit value is negative. + if (buffer.size() == 3 && buffer[0] & 0x80) { + val |= 0xFFFFFFFFFF000000; + } return val; } From 4a518e3e7adec588aad250f211b2f13ad03be98e Mon Sep 17 00:00:00 2001 From: Sergey Dudanov Date: Wed, 16 Aug 2023 03:11:44 +0400 Subject: [PATCH 015/111] remote_base: change dumpers log level (#5253) --- esphome/components/remote_base/aeha_protocol.cpp | 2 +- esphome/components/remote_base/canalsat_protocol.cpp | 4 ++-- esphome/components/remote_base/coolix_protocol.cpp | 6 +++--- esphome/components/remote_base/dish_protocol.cpp | 2 +- esphome/components/remote_base/drayton_protocol.cpp | 2 +- esphome/components/remote_base/jvc_protocol.cpp | 2 +- esphome/components/remote_base/lg_protocol.cpp | 2 +- esphome/components/remote_base/magiquest_protocol.cpp | 2 +- esphome/components/remote_base/midea_protocol.cpp | 2 +- esphome/components/remote_base/nec_protocol.cpp | 2 +- esphome/components/remote_base/nexa_protocol.cpp | 2 +- esphome/components/remote_base/panasonic_protocol.cpp | 2 +- esphome/components/remote_base/pioneer_protocol.cpp | 4 ++-- esphome/components/remote_base/pronto_protocol.cpp | 4 ++-- esphome/components/remote_base/raw_protocol.cpp | 4 ++-- esphome/components/remote_base/rc5_protocol.cpp | 2 +- esphome/components/remote_base/rc6_protocol.cpp | 2 +- esphome/components/remote_base/rc_switch_protocol.cpp | 2 +- esphome/components/remote_base/samsung36_protocol.cpp | 2 +- esphome/components/remote_base/samsung_protocol.cpp | 2 +- esphome/components/remote_base/sony_protocol.cpp | 2 +- esphome/components/remote_base/toshiba_ac_protocol.cpp | 4 ++-- 22 files changed, 29 insertions(+), 29 deletions(-) diff --git a/esphome/components/remote_base/aeha_protocol.cpp b/esphome/components/remote_base/aeha_protocol.cpp index ee1616ed6d..40bdadf634 100644 --- a/esphome/components/remote_base/aeha_protocol.cpp +++ b/esphome/components/remote_base/aeha_protocol.cpp @@ -96,7 +96,7 @@ std::string AEHAProtocol::format_data_(const std::vector &data) { void AEHAProtocol::dump(const AEHAData &data) { auto data_str = format_data_(data.data); - ESP_LOGD(TAG, "Received AEHA: address=0x%04X, data=[%s]", data.address, data_str.c_str()); + ESP_LOGI(TAG, "Received AEHA: address=0x%04X, data=[%s]", data.address, data_str.c_str()); } } // namespace remote_base diff --git a/esphome/components/remote_base/canalsat_protocol.cpp b/esphome/components/remote_base/canalsat_protocol.cpp index 1ea47750fd..bee3d57fd8 100644 --- a/esphome/components/remote_base/canalsat_protocol.cpp +++ b/esphome/components/remote_base/canalsat_protocol.cpp @@ -96,10 +96,10 @@ optional CanalSatBaseProtocol::decode(RemoteReceiveData src) { void CanalSatBaseProtocol::dump(const CanalSatData &data) { if (this->tag_ == CANALSATLD_TAG) { - ESP_LOGD(this->tag_, "Received CanalSatLD: device=0x%02X, address=0x%02X, command=0x%02X, repeat=0x%X", data.device, + ESP_LOGI(this->tag_, "Received CanalSatLD: device=0x%02X, address=0x%02X, command=0x%02X, repeat=0x%X", data.device, data.address, data.command, data.repeat); } else { - ESP_LOGD(this->tag_, "Received CanalSat: device=0x%02X, address=0x%02X, command=0x%02X, repeat=0x%X", data.device, + ESP_LOGI(this->tag_, "Received CanalSat: device=0x%02X, address=0x%02X, command=0x%02X, repeat=0x%X", data.device, data.address, data.command, data.repeat); } } diff --git a/esphome/components/remote_base/coolix_protocol.cpp b/esphome/components/remote_base/coolix_protocol.cpp index 3c9dadcd1c..295fccb762 100644 --- a/esphome/components/remote_base/coolix_protocol.cpp +++ b/esphome/components/remote_base/coolix_protocol.cpp @@ -101,11 +101,11 @@ optional CoolixProtocol::decode(RemoteReceiveData data) { void CoolixProtocol::dump(const CoolixData &data) { if (data.is_strict()) { - ESP_LOGD(TAG, "Received Coolix: 0x%06X", data.first); + ESP_LOGI(TAG, "Received Coolix: 0x%06X", data.first); } else if (data.has_second()) { - ESP_LOGD(TAG, "Received unstrict Coolix: [0x%06X, 0x%06X]", data.first, data.second); + ESP_LOGI(TAG, "Received unstrict Coolix: [0x%06X, 0x%06X]", data.first, data.second); } else { - ESP_LOGD(TAG, "Received unstrict Coolix: [0x%06X]", data.first); + ESP_LOGI(TAG, "Received unstrict Coolix: [0x%06X]", data.first); } } diff --git a/esphome/components/remote_base/dish_protocol.cpp b/esphome/components/remote_base/dish_protocol.cpp index 47bfdc5c58..754b6c3b12 100644 --- a/esphome/components/remote_base/dish_protocol.cpp +++ b/esphome/components/remote_base/dish_protocol.cpp @@ -87,7 +87,7 @@ optional DishProtocol::decode(RemoteReceiveData src) { } void DishProtocol::dump(const DishData &data) { - ESP_LOGD(TAG, "Received Dish: address=0x%02X, command=0x%02X", data.address, data.command); + ESP_LOGI(TAG, "Received Dish: address=0x%02X, command=0x%02X", data.address, data.command); } } // namespace remote_base diff --git a/esphome/components/remote_base/drayton_protocol.cpp b/esphome/components/remote_base/drayton_protocol.cpp index f5eae49058..56a3dec1e0 100644 --- a/esphome/components/remote_base/drayton_protocol.cpp +++ b/esphome/components/remote_base/drayton_protocol.cpp @@ -205,7 +205,7 @@ optional DraytonProtocol::decode(RemoteReceiveData src) { return out; } void DraytonProtocol::dump(const DraytonData &data) { - ESP_LOGD(TAG, "Received Drayton: address=0x%04X (0x%04x), channel=0x%03x command=0x%03X", data.address, + ESP_LOGI(TAG, "Received Drayton: address=0x%04X (0x%04x), channel=0x%03x command=0x%03X", data.address, ((data.address << 1) & 0xffff), data.channel, data.command); } diff --git a/esphome/components/remote_base/jvc_protocol.cpp b/esphome/components/remote_base/jvc_protocol.cpp index 169b1d00bf..3d34cc614e 100644 --- a/esphome/components/remote_base/jvc_protocol.cpp +++ b/esphome/components/remote_base/jvc_protocol.cpp @@ -46,7 +46,7 @@ optional JVCProtocol::decode(RemoteReceiveData src) { } return out; } -void JVCProtocol::dump(const JVCData &data) { ESP_LOGD(TAG, "Received JVC: data=0x%04X", data.data); } +void JVCProtocol::dump(const JVCData &data) { ESP_LOGI(TAG, "Received JVC: data=0x%04X", data.data); } } // namespace remote_base } // namespace esphome diff --git a/esphome/components/remote_base/lg_protocol.cpp b/esphome/components/remote_base/lg_protocol.cpp index 8040b0f3fc..d7d3a5ac7d 100644 --- a/esphome/components/remote_base/lg_protocol.cpp +++ b/esphome/components/remote_base/lg_protocol.cpp @@ -51,7 +51,7 @@ optional LGProtocol::decode(RemoteReceiveData src) { return out; } void LGProtocol::dump(const LGData &data) { - ESP_LOGD(TAG, "Received LG: data=0x%08X, nbits=%d", data.data, data.nbits); + ESP_LOGI(TAG, "Received LG: data=0x%08X, nbits=%d", data.data, data.nbits); } } // namespace remote_base diff --git a/esphome/components/remote_base/magiquest_protocol.cpp b/esphome/components/remote_base/magiquest_protocol.cpp index 20b40ef201..76024b1eaf 100644 --- a/esphome/components/remote_base/magiquest_protocol.cpp +++ b/esphome/components/remote_base/magiquest_protocol.cpp @@ -76,7 +76,7 @@ optional MagiQuestProtocol::decode(RemoteReceiveData src) { return data; } void MagiQuestProtocol::dump(const MagiQuestData &data) { - ESP_LOGD(TAG, "Received MagiQuest: wand_id=0x%08X, magnitude=0x%04X", data.wand_id, data.magnitude); + ESP_LOGI(TAG, "Received MagiQuest: wand_id=0x%08X, magnitude=0x%04X", data.wand_id, data.magnitude); } } // namespace remote_base diff --git a/esphome/components/remote_base/midea_protocol.cpp b/esphome/components/remote_base/midea_protocol.cpp index f619d201bc..8006fe4048 100644 --- a/esphome/components/remote_base/midea_protocol.cpp +++ b/esphome/components/remote_base/midea_protocol.cpp @@ -70,7 +70,7 @@ optional MideaProtocol::decode(RemoteReceiveData src) { return {}; } -void MideaProtocol::dump(const MideaData &data) { ESP_LOGD(TAG, "Received Midea: %s", data.to_string().c_str()); } +void MideaProtocol::dump(const MideaData &data) { ESP_LOGI(TAG, "Received Midea: %s", data.to_string().c_str()); } } // namespace remote_base } // namespace esphome diff --git a/esphome/components/remote_base/nec_protocol.cpp b/esphome/components/remote_base/nec_protocol.cpp index ee3fec5992..d5c68784ee 100644 --- a/esphome/components/remote_base/nec_protocol.cpp +++ b/esphome/components/remote_base/nec_protocol.cpp @@ -67,7 +67,7 @@ optional NECProtocol::decode(RemoteReceiveData src) { return data; } void NECProtocol::dump(const NECData &data) { - ESP_LOGD(TAG, "Received NEC: address=0x%04X, command=0x%04X", data.address, data.command); + ESP_LOGI(TAG, "Received NEC: address=0x%04X, command=0x%04X", data.address, data.command); } } // namespace remote_base diff --git a/esphome/components/remote_base/nexa_protocol.cpp b/esphome/components/remote_base/nexa_protocol.cpp index a0066ea5d4..f4e7d14187 100644 --- a/esphome/components/remote_base/nexa_protocol.cpp +++ b/esphome/components/remote_base/nexa_protocol.cpp @@ -232,7 +232,7 @@ optional NexaProtocol::decode(RemoteReceiveData src) { } void NexaProtocol::dump(const NexaData &data) { - ESP_LOGD(TAG, "Received NEXA: device=0x%04X group=%d state=%d channel=%d level=%d", data.device, data.group, + ESP_LOGI(TAG, "Received NEXA: device=0x%04X group=%d state=%d channel=%d level=%d", data.device, data.group, data.state, data.channel, data.level); } diff --git a/esphome/components/remote_base/panasonic_protocol.cpp b/esphome/components/remote_base/panasonic_protocol.cpp index fe1060e935..460ca3b164 100644 --- a/esphome/components/remote_base/panasonic_protocol.cpp +++ b/esphome/components/remote_base/panasonic_protocol.cpp @@ -67,7 +67,7 @@ optional PanasonicProtocol::decode(RemoteReceiveData src) { return out; } void PanasonicProtocol::dump(const PanasonicData &data) { - ESP_LOGD(TAG, "Received Panasonic: address=0x%04X, command=0x%08X", data.address, data.command); + ESP_LOGI(TAG, "Received Panasonic: address=0x%04X, command=0x%08X", data.address, data.command); } } // namespace remote_base diff --git a/esphome/components/remote_base/pioneer_protocol.cpp b/esphome/components/remote_base/pioneer_protocol.cpp index 5c10eab48d..043565282d 100644 --- a/esphome/components/remote_base/pioneer_protocol.cpp +++ b/esphome/components/remote_base/pioneer_protocol.cpp @@ -146,9 +146,9 @@ optional PioneerProtocol::decode(RemoteReceiveData src) { } void PioneerProtocol::dump(const PioneerData &data) { if (data.rc_code_2 == 0) { - ESP_LOGD(TAG, "Received Pioneer: rc_code_X=0x%04X", data.rc_code_1); + ESP_LOGI(TAG, "Received Pioneer: rc_code_X=0x%04X", data.rc_code_1); } else { - ESP_LOGD(TAG, "Received Pioneer: rc_code_1=0x%04X, rc_code_2=0x%04X", data.rc_code_1, data.rc_code_2); + ESP_LOGI(TAG, "Received Pioneer: rc_code_1=0x%04X, rc_code_2=0x%04X", data.rc_code_1, data.rc_code_2); } } diff --git a/esphome/components/remote_base/pronto_protocol.cpp b/esphome/components/remote_base/pronto_protocol.cpp index 81ac176666..4b6977e1a2 100644 --- a/esphome/components/remote_base/pronto_protocol.cpp +++ b/esphome/components/remote_base/pronto_protocol.cpp @@ -234,9 +234,9 @@ void ProntoProtocol::dump(const ProntoData &data) { first = data.data.substr(0, 229); rest = data.data.substr(230); } - ESP_LOGD(TAG, "Received Pronto: data=%s", first.c_str()); + ESP_LOGI(TAG, "Received Pronto: data=%s", first.c_str()); if (!rest.empty()) { - ESP_LOGD(TAG, "%s", rest.c_str()); + ESP_LOGI(TAG, "%s", rest.c_str()); } } diff --git a/esphome/components/remote_base/raw_protocol.cpp b/esphome/components/remote_base/raw_protocol.cpp index 3446dcdbb8..9304aa3e3d 100644 --- a/esphome/components/remote_base/raw_protocol.cpp +++ b/esphome/components/remote_base/raw_protocol.cpp @@ -25,7 +25,7 @@ bool RawDumper::dump(RemoteReceiveData src) { if (written < 0 || written >= int(remaining_length)) { // write failed, flush... buffer[buffer_offset] = '\0'; - ESP_LOGD(TAG, "%s", buffer); + ESP_LOGI(TAG, "%s", buffer); buffer_offset = 0; written = sprintf(buffer, " "); if (i + 1 < src.size()) { @@ -38,7 +38,7 @@ bool RawDumper::dump(RemoteReceiveData src) { buffer_offset += written; } if (buffer_offset != 0) { - ESP_LOGD(TAG, "%s", buffer); + ESP_LOGI(TAG, "%s", buffer); } return true; } diff --git a/esphome/components/remote_base/rc5_protocol.cpp b/esphome/components/remote_base/rc5_protocol.cpp index cb6eed4c6c..08f2f2eaa3 100644 --- a/esphome/components/remote_base/rc5_protocol.cpp +++ b/esphome/components/remote_base/rc5_protocol.cpp @@ -83,7 +83,7 @@ optional RC5Protocol::decode(RemoteReceiveData src) { return out; } void RC5Protocol::dump(const RC5Data &data) { - ESP_LOGD(TAG, "Received RC5: address=0x%02X, command=0x%02X", data.address, data.command); + ESP_LOGI(TAG, "Received RC5: address=0x%02X, command=0x%02X", data.address, data.command); } } // namespace remote_base diff --git a/esphome/components/remote_base/rc6_protocol.cpp b/esphome/components/remote_base/rc6_protocol.cpp index ad12c7c208..fcb4da11a4 100644 --- a/esphome/components/remote_base/rc6_protocol.cpp +++ b/esphome/components/remote_base/rc6_protocol.cpp @@ -173,7 +173,7 @@ optional RC6Protocol::decode(RemoteReceiveData src) { } void RC6Protocol::dump(const RC6Data &data) { - ESP_LOGD(RC6_TAG, "Received RC6: mode=0x%X, address=0x%02X, command=0x%02X, toggle=0x%X", data.mode, data.address, + ESP_LOGI(RC6_TAG, "Received RC6: mode=0x%X, address=0x%02X, command=0x%02X, toggle=0x%X", data.mode, data.address, data.command, data.toggle); } diff --git a/esphome/components/remote_base/rc_switch_protocol.cpp b/esphome/components/remote_base/rc_switch_protocol.cpp index 5b6284a86f..1f38fdca67 100644 --- a/esphome/components/remote_base/rc_switch_protocol.cpp +++ b/esphome/components/remote_base/rc_switch_protocol.cpp @@ -258,7 +258,7 @@ bool RCSwitchDumper::dump(RemoteReceiveData src) { buffer[j] = (out_data & ((uint64_t) 1 << (out_nbits - j - 1))) ? '1' : '0'; buffer[out_nbits] = '\0'; - ESP_LOGD(TAG, "Received RCSwitch Raw: protocol=%u data='%s'", i, buffer); + ESP_LOGI(TAG, "Received RCSwitch Raw: protocol=%u data='%s'", i, buffer); // only send first decoded protocol return true; diff --git a/esphome/components/remote_base/samsung36_protocol.cpp b/esphome/components/remote_base/samsung36_protocol.cpp index 9ef8ade205..2396986670 100644 --- a/esphome/components/remote_base/samsung36_protocol.cpp +++ b/esphome/components/remote_base/samsung36_protocol.cpp @@ -96,7 +96,7 @@ optional Samsung36Protocol::decode(RemoteReceiveData src) { return out; } void Samsung36Protocol::dump(const Samsung36Data &data) { - ESP_LOGD(TAG, "Received Samsung36: address=0x%04X, command=0x%08X", data.address, data.command); + ESP_LOGI(TAG, "Received Samsung36: address=0x%04X, command=0x%08X", data.address, data.command); } } // namespace remote_base diff --git a/esphome/components/remote_base/samsung_protocol.cpp b/esphome/components/remote_base/samsung_protocol.cpp index 20e8285a03..2d6d5531e5 100644 --- a/esphome/components/remote_base/samsung_protocol.cpp +++ b/esphome/components/remote_base/samsung_protocol.cpp @@ -58,7 +58,7 @@ optional SamsungProtocol::decode(RemoteReceiveData src) { return out; } void SamsungProtocol::dump(const SamsungData &data) { - ESP_LOGD(TAG, "Received Samsung: data=0x%" PRIX64 ", nbits=%d", data.data, data.nbits); + ESP_LOGI(TAG, "Received Samsung: data=0x%" PRIX64 ", nbits=%d", data.data, data.nbits); } } // namespace remote_base diff --git a/esphome/components/remote_base/sony_protocol.cpp b/esphome/components/remote_base/sony_protocol.cpp index 8634cf7d61..bcd8e4c8cf 100644 --- a/esphome/components/remote_base/sony_protocol.cpp +++ b/esphome/components/remote_base/sony_protocol.cpp @@ -62,7 +62,7 @@ optional SonyProtocol::decode(RemoteReceiveData src) { return out; } void SonyProtocol::dump(const SonyData &data) { - ESP_LOGD(TAG, "Received Sony: data=0x%08X, nbits=%d", data.data, data.nbits); + ESP_LOGI(TAG, "Received Sony: data=0x%08X, nbits=%d", data.data, data.nbits); } } // namespace remote_base diff --git a/esphome/components/remote_base/toshiba_ac_protocol.cpp b/esphome/components/remote_base/toshiba_ac_protocol.cpp index 1a19f534f8..42241eea8c 100644 --- a/esphome/components/remote_base/toshiba_ac_protocol.cpp +++ b/esphome/components/remote_base/toshiba_ac_protocol.cpp @@ -105,9 +105,9 @@ optional ToshibaAcProtocol::decode(RemoteReceiveData src) { void ToshibaAcProtocol::dump(const ToshibaAcData &data) { if (data.rc_code_2 != 0) { - ESP_LOGD(TAG, "Received Toshiba AC: rc_code_1=0x%" PRIX64 ", rc_code_2=0x%" PRIX64, data.rc_code_1, data.rc_code_2); + ESP_LOGI(TAG, "Received Toshiba AC: rc_code_1=0x%" PRIX64 ", rc_code_2=0x%" PRIX64, data.rc_code_1, data.rc_code_2); } else { - ESP_LOGD(TAG, "Received Toshiba AC: rc_code_1=0x%" PRIX64, data.rc_code_1); + ESP_LOGI(TAG, "Received Toshiba AC: rc_code_1=0x%" PRIX64, data.rc_code_1); } } From 87629191b31fb96bf0df15d06657827c56862a34 Mon Sep 17 00:00:00 2001 From: Carson Full Date: Tue, 15 Aug 2023 18:13:43 -0500 Subject: [PATCH 016/111] Fix IDFI2CBus::writev ignoring stop parameter (#4840) Co-authored-by: Alexander Dimitrov --- esphome/components/i2c/i2c_bus_esp_idf.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/esphome/components/i2c/i2c_bus_esp_idf.cpp b/esphome/components/i2c/i2c_bus_esp_idf.cpp index e2c7e7ddcb..5d35c1968b 100644 --- a/esphome/components/i2c/i2c_bus_esp_idf.cpp +++ b/esphome/components/i2c/i2c_bus_esp_idf.cpp @@ -202,11 +202,13 @@ ErrorCode IDFI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cnt, b return ERROR_UNKNOWN; } } - err = i2c_master_stop(cmd); - if (err != ESP_OK) { - ESP_LOGVV(TAG, "TX to %02X master stop failed: %s", address, esp_err_to_name(err)); - i2c_cmd_link_delete(cmd); - return ERROR_UNKNOWN; + if (stop) { + err = i2c_master_stop(cmd); + if (err != ESP_OK) { + ESP_LOGVV(TAG, "TX to %02X master stop failed: %s", address, esp_err_to_name(err)); + i2c_cmd_link_delete(cmd); + return ERROR_UNKNOWN; + } } err = i2c_master_cmd_begin(port_, cmd, 20 / portTICK_PERIOD_MS); i2c_cmd_link_delete(cmd); From 5cb5594288f65fe4639e442a0c3772ed7c96e578 Mon Sep 17 00:00:00 2001 From: Regev Brody Date: Wed, 16 Aug 2023 02:31:18 +0300 Subject: [PATCH 017/111] Add configuration flow abilites to the ld2410 component (#4434) --- CODEOWNERS | 2 +- esphome/components/ld2410/__init__.py | 188 +++--- esphome/components/ld2410/automation.h | 22 + esphome/components/ld2410/binary_sensor.py | 45 +- esphome/components/ld2410/button/__init__.py | 57 ++ .../components/ld2410/button/query_button.cpp | 9 + .../components/ld2410/button/query_button.h | 18 + .../components/ld2410/button/reset_button.cpp | 9 + .../components/ld2410/button/reset_button.h | 18 + .../ld2410/button/restart_button.cpp | 9 + .../components/ld2410/button/restart_button.h | 18 + esphome/components/ld2410/ld2410.cpp | 557 +++++++++++++++--- esphome/components/ld2410/ld2410.h | 207 +++++-- esphome/components/ld2410/number/__init__.py | 128 ++++ .../ld2410/number/gate_threshold_number.cpp | 14 + .../ld2410/number/gate_threshold_number.h | 19 + .../ld2410/number/light_threshold_number.cpp | 12 + .../ld2410/number/light_threshold_number.h | 18 + .../number/max_distance_timeout_number.cpp | 12 + .../number/max_distance_timeout_number.h | 18 + esphome/components/ld2410/select/__init__.py | 81 +++ .../ld2410/select/baud_rate_select.cpp | 12 + .../ld2410/select/baud_rate_select.h | 18 + .../select/distance_resolution_select.cpp | 12 + .../select/distance_resolution_select.h | 18 + .../select/light_out_control_select.cpp | 12 + .../ld2410/select/light_out_control_select.h | 18 + esphome/components/ld2410/sensor.py | 111 +++- esphome/components/ld2410/switch/__init__.py | 44 ++ .../ld2410/switch/bluetooth_switch.cpp | 12 + .../ld2410/switch/bluetooth_switch.h | 18 + .../ld2410/switch/engineering_mode_switch.cpp | 12 + .../ld2410/switch/engineering_mode_switch.h | 18 + esphome/components/ld2410/text_sensor.py | 33 ++ tests/test1.yaml | 149 ++++- 35 files changed, 1621 insertions(+), 327 deletions(-) create mode 100644 esphome/components/ld2410/automation.h create mode 100644 esphome/components/ld2410/button/__init__.py create mode 100644 esphome/components/ld2410/button/query_button.cpp create mode 100644 esphome/components/ld2410/button/query_button.h create mode 100644 esphome/components/ld2410/button/reset_button.cpp create mode 100644 esphome/components/ld2410/button/reset_button.h create mode 100644 esphome/components/ld2410/button/restart_button.cpp create mode 100644 esphome/components/ld2410/button/restart_button.h create mode 100644 esphome/components/ld2410/number/__init__.py create mode 100644 esphome/components/ld2410/number/gate_threshold_number.cpp create mode 100644 esphome/components/ld2410/number/gate_threshold_number.h create mode 100644 esphome/components/ld2410/number/light_threshold_number.cpp create mode 100644 esphome/components/ld2410/number/light_threshold_number.h create mode 100644 esphome/components/ld2410/number/max_distance_timeout_number.cpp create mode 100644 esphome/components/ld2410/number/max_distance_timeout_number.h create mode 100644 esphome/components/ld2410/select/__init__.py create mode 100644 esphome/components/ld2410/select/baud_rate_select.cpp create mode 100644 esphome/components/ld2410/select/baud_rate_select.h create mode 100644 esphome/components/ld2410/select/distance_resolution_select.cpp create mode 100644 esphome/components/ld2410/select/distance_resolution_select.h create mode 100644 esphome/components/ld2410/select/light_out_control_select.cpp create mode 100644 esphome/components/ld2410/select/light_out_control_select.h create mode 100644 esphome/components/ld2410/switch/__init__.py create mode 100644 esphome/components/ld2410/switch/bluetooth_switch.cpp create mode 100644 esphome/components/ld2410/switch/bluetooth_switch.h create mode 100644 esphome/components/ld2410/switch/engineering_mode_switch.cpp create mode 100644 esphome/components/ld2410/switch/engineering_mode_switch.h create mode 100644 esphome/components/ld2410/text_sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index d815252ba3..f50700c502 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -144,7 +144,7 @@ esphome/components/key_collector/* @ssieb esphome/components/key_provider/* @ssieb esphome/components/kuntze/* @ssieb esphome/components/lcd_menu/* @numo68 -esphome/components/ld2410/* @sebcaps +esphome/components/ld2410/* @regevbr @sebcaps esphome/components/ledc/* @OttoWinter esphome/components/light/* @esphome/core esphome/components/lilygo_t5_47/touchscreen/* @jesserockz diff --git a/esphome/components/ld2410/__init__.py b/esphome/components/ld2410/__init__.py index 47c4cdb0bd..2b30b65f46 100644 --- a/esphome/components/ld2410/__init__.py +++ b/esphome/components/ld2410/__init__.py @@ -1,113 +1,64 @@ 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.const import CONF_ID, CONF_THROTTLE, CONF_TIMEOUT, CONF_PASSWORD from esphome import automation from esphome.automation import maybe_simple_id DEPENDENCIES = ["uart"] -CODEOWNERS = ["@sebcaps"] +CODEOWNERS = ["@sebcaps", "@regevbr"] 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" +CONF_STILL_THRESHOLDS = [f"g{x}_still_threshold" for x in range(9)] +CONF_MOVE_THRESHOLDS = [f"g{x}_move_threshold" for x in range(9)] -DISTANCES = [0.75, 1.5, 2.25, 3, 3.75, 4.5, 5.25, 6] +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(LD2410Component), + cv.Optional(CONF_THROTTLE, default="1000ms"): cv.All( + cv.positive_time_period_milliseconds, + cv.Range(min=cv.TimePeriod(milliseconds=1)), + ), + cv.Optional(CONF_MAX_MOVE_DISTANCE): cv.invalid( + f"The '{CONF_MAX_MOVE_DISTANCE}' option has been moved to the '{CONF_MAX_MOVE_DISTANCE}'" + f" number component" + ), + cv.Optional(CONF_MAX_STILL_DISTANCE): cv.invalid( + f"The '{CONF_MAX_STILL_DISTANCE}' option has been moved to the '{CONF_MAX_STILL_DISTANCE}'" + f" number component" + ), + cv.Optional(CONF_TIMEOUT): cv.invalid( + f"The '{CONF_TIMEOUT}' option has been moved to the '{CONF_TIMEOUT}'" + f" number component" + ), + } +) + +for i in range(9): + CONFIG_SCHEMA = CONFIG_SCHEMA.extend( + cv.Schema( + { + cv.Optional(CONF_MOVE_THRESHOLDS[i]): cv.invalid( + f"The '{CONF_MOVE_THRESHOLDS[i]}' option has been moved to the '{CONF_MOVE_THRESHOLDS[i]}'" + f" number component" + ), + cv.Optional(CONF_STILL_THRESHOLDS[i]): cv.invalid( + f"The '{CONF_STILL_THRESHOLDS[i]}' option has been moved to the '{CONF_STILL_THRESHOLDS[i]}'" + f" number component" + ), + } + ) + ) 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) + CONFIG_SCHEMA.extend(uart.UART_DEVICE_SCHEMA).extend(cv.COMPONENT_SCHEMA) ) FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema( @@ -123,31 +74,7 @@ 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], - ) - ) + cg.add(var.set_throttle(config[CONF_THROTTLE])) CALIBRATION_ACTION_SCHEMA = maybe_simple_id( @@ -155,3 +82,28 @@ CALIBRATION_ACTION_SCHEMA = maybe_simple_id( cv.Required(CONF_ID): cv.use_id(LD2410Component), } ) + + +# Actions +BluetoothPasswordSetAction = ld2410_ns.class_( + "BluetoothPasswordSetAction", automation.Action +) + + +BLUETOOTH_PASSWORD_SET_SCHEMA = cv.Schema( + { + cv.Required(CONF_ID): cv.use_id(LD2410Component), + cv.Required(CONF_PASSWORD): cv.templatable(cv.string_strict), + } +) + + +@automation.register_action( + "bluetooth_password.set", BluetoothPasswordSetAction, BLUETOOTH_PASSWORD_SET_SCHEMA +) +async def bluetooth_password_set_to_code(config, action_id, template_arg, args): + paren = await cg.get_variable(config[CONF_ID]) + var = cg.new_Pvariable(action_id, template_arg, paren) + template_ = await cg.templatable(config[CONF_PASSWORD], args, cg.std_string) + cg.add(var.set_password(template_)) + return var diff --git a/esphome/components/ld2410/automation.h b/esphome/components/ld2410/automation.h new file mode 100644 index 0000000000..7cb9855f84 --- /dev/null +++ b/esphome/components/ld2410/automation.h @@ -0,0 +1,22 @@ +#pragma once + +#include "esphome/core/automation.h" +#include "esphome/core/component.h" +#include "ld2410.h" + +namespace esphome { +namespace ld2410 { + +template class BluetoothPasswordSetAction : public Action { + public: + explicit BluetoothPasswordSetAction(LD2410Component *ld2410_comp) : ld2410_comp_(ld2410_comp) {} + TEMPLATABLE_VALUE(std::string, password) + + void play(Ts... x) override { this->ld2410_comp_->set_bluetooth_password(this->password_.value(x...)); } + + protected: + LD2410Component *ld2410_comp_; +}; + +} // namespace ld2410 +} // namespace esphome diff --git a/esphome/components/ld2410/binary_sensor.py b/esphome/components/ld2410/binary_sensor.py index 02f73d57b7..3057480d25 100644 --- a/esphome/components/ld2410/binary_sensor.py +++ b/esphome/components/ld2410/binary_sensor.py @@ -1,36 +1,55 @@ 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 esphome.const import ( + DEVICE_CLASS_MOTION, + DEVICE_CLASS_OCCUPANCY, + DEVICE_CLASS_PRESENCE, + ENTITY_CATEGORY_DIAGNOSTIC, + ICON_MOTION_SENSOR, + ICON_ACCOUNT, +) 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" +CONF_OUT_PIN_PRESENCE_STATUS = "out_pin_presence_status" 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 + device_class=DEVICE_CLASS_OCCUPANCY, + icon=ICON_ACCOUNT, ), cv.Optional(CONF_HAS_MOVING_TARGET): binary_sensor.binary_sensor_schema( - device_class=DEVICE_CLASS_MOTION + device_class=DEVICE_CLASS_MOTION, + icon=ICON_MOTION_SENSOR, ), cv.Optional(CONF_HAS_STILL_TARGET): binary_sensor.binary_sensor_schema( - device_class=DEVICE_CLASS_OCCUPANCY + device_class=DEVICE_CLASS_OCCUPANCY, + icon=ICON_MOTION_SENSOR, + ), + cv.Optional(CONF_OUT_PIN_PRESENCE_STATUS): binary_sensor.binary_sensor_schema( + device_class=DEVICE_CLASS_PRESENCE, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + icon=ICON_ACCOUNT, ), } 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)) + if has_target_config := config.get(CONF_HAS_TARGET): + sens = await binary_sensor.new_binary_sensor(has_target_config) + cg.add(ld2410_component.set_target_binary_sensor(sens)) + if has_moving_target_config := config.get(CONF_HAS_MOVING_TARGET): + sens = await binary_sensor.new_binary_sensor(has_moving_target_config) + cg.add(ld2410_component.set_moving_target_binary_sensor(sens)) + if has_still_target_config := config.get(CONF_HAS_STILL_TARGET): + sens = await binary_sensor.new_binary_sensor(has_still_target_config) + cg.add(ld2410_component.set_still_target_binary_sensor(sens)) + if out_pin_presence_status_config := config.get(CONF_OUT_PIN_PRESENCE_STATUS): + sens = await binary_sensor.new_binary_sensor(out_pin_presence_status_config) + cg.add(ld2410_component.set_out_pin_presence_status_binary_sensor(sens)) diff --git a/esphome/components/ld2410/button/__init__.py b/esphome/components/ld2410/button/__init__.py new file mode 100644 index 0000000000..3567114c2c --- /dev/null +++ b/esphome/components/ld2410/button/__init__.py @@ -0,0 +1,57 @@ +import esphome.codegen as cg +from esphome.components import button +import esphome.config_validation as cv +from esphome.const import ( + DEVICE_CLASS_RESTART, + ENTITY_CATEGORY_DIAGNOSTIC, + ENTITY_CATEGORY_CONFIG, + ICON_RESTART, + ICON_RESTART_ALERT, + ICON_DATABASE, +) +from .. import CONF_LD2410_ID, LD2410Component, ld2410_ns + +QueryButton = ld2410_ns.class_("QueryButton", button.Button) +ResetButton = ld2410_ns.class_("ResetButton", button.Button) +RestartButton = ld2410_ns.class_("RestartButton", button.Button) + +CONF_FACTORY_RESET = "factory_reset" +CONF_RESTART = "restart" +CONF_QUERY_PARAMS = "query_params" + +CONFIG_SCHEMA = { + cv.GenerateID(CONF_LD2410_ID): cv.use_id(LD2410Component), + cv.Optional(CONF_FACTORY_RESET): button.button_schema( + ResetButton, + device_class=DEVICE_CLASS_RESTART, + entity_category=ENTITY_CATEGORY_CONFIG, + icon=ICON_RESTART_ALERT, + ), + cv.Optional(CONF_RESTART): button.button_schema( + RestartButton, + device_class=DEVICE_CLASS_RESTART, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + icon=ICON_RESTART, + ), + cv.Optional(CONF_QUERY_PARAMS): button.button_schema( + QueryButton, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + icon=ICON_DATABASE, + ), +} + + +async def to_code(config): + ld2410_component = await cg.get_variable(config[CONF_LD2410_ID]) + if factory_reset_config := config.get(CONF_FACTORY_RESET): + b = await button.new_button(factory_reset_config) + await cg.register_parented(b, config[CONF_LD2410_ID]) + cg.add(ld2410_component.set_reset_button(b)) + if restart_config := config.get(CONF_RESTART): + b = await button.new_button(restart_config) + await cg.register_parented(b, config[CONF_LD2410_ID]) + cg.add(ld2410_component.set_restart_button(b)) + if query_params_config := config.get(CONF_QUERY_PARAMS): + b = await button.new_button(query_params_config) + await cg.register_parented(b, config[CONF_LD2410_ID]) + cg.add(ld2410_component.set_query_button(b)) diff --git a/esphome/components/ld2410/button/query_button.cpp b/esphome/components/ld2410/button/query_button.cpp new file mode 100644 index 0000000000..47ab416f5a --- /dev/null +++ b/esphome/components/ld2410/button/query_button.cpp @@ -0,0 +1,9 @@ +#include "query_button.h" + +namespace esphome { +namespace ld2410 { + +void QueryButton::press_action() { this->parent_->read_all_info(); } + +} // namespace ld2410 +} // namespace esphome diff --git a/esphome/components/ld2410/button/query_button.h b/esphome/components/ld2410/button/query_button.h new file mode 100644 index 0000000000..c7a47e32d8 --- /dev/null +++ b/esphome/components/ld2410/button/query_button.h @@ -0,0 +1,18 @@ +#pragma once + +#include "esphome/components/button/button.h" +#include "../ld2410.h" + +namespace esphome { +namespace ld2410 { + +class QueryButton : public button::Button, public Parented { + public: + QueryButton() = default; + + protected: + void press_action() override; +}; + +} // namespace ld2410 +} // namespace esphome diff --git a/esphome/components/ld2410/button/reset_button.cpp b/esphome/components/ld2410/button/reset_button.cpp new file mode 100644 index 0000000000..f16c5faa79 --- /dev/null +++ b/esphome/components/ld2410/button/reset_button.cpp @@ -0,0 +1,9 @@ +#include "reset_button.h" + +namespace esphome { +namespace ld2410 { + +void ResetButton::press_action() { this->parent_->factory_reset(); } + +} // namespace ld2410 +} // namespace esphome diff --git a/esphome/components/ld2410/button/reset_button.h b/esphome/components/ld2410/button/reset_button.h new file mode 100644 index 0000000000..78dd92c9f5 --- /dev/null +++ b/esphome/components/ld2410/button/reset_button.h @@ -0,0 +1,18 @@ +#pragma once + +#include "esphome/components/button/button.h" +#include "../ld2410.h" + +namespace esphome { +namespace ld2410 { + +class ResetButton : public button::Button, public Parented { + public: + ResetButton() = default; + + protected: + void press_action() override; +}; + +} // namespace ld2410 +} // namespace esphome diff --git a/esphome/components/ld2410/button/restart_button.cpp b/esphome/components/ld2410/button/restart_button.cpp new file mode 100644 index 0000000000..de0d36c1ef --- /dev/null +++ b/esphome/components/ld2410/button/restart_button.cpp @@ -0,0 +1,9 @@ +#include "restart_button.h" + +namespace esphome { +namespace ld2410 { + +void RestartButton::press_action() { this->parent_->restart_and_read_all_info(); } + +} // namespace ld2410 +} // namespace esphome diff --git a/esphome/components/ld2410/button/restart_button.h b/esphome/components/ld2410/button/restart_button.h new file mode 100644 index 0000000000..d00dc05a53 --- /dev/null +++ b/esphome/components/ld2410/button/restart_button.h @@ -0,0 +1,18 @@ +#pragma once + +#include "esphome/components/button/button.h" +#include "../ld2410.h" + +namespace esphome { +namespace ld2410 { + +class RestartButton : public button::Button, public Parented { + public: + RestartButton() = default; + + protected: + void press_action() override; +}; + +} // namespace ld2410 +} // namespace esphome diff --git a/esphome/components/ld2410/ld2410.cpp b/esphome/components/ld2410/ld2410.cpp index 8e67ba54d7..c3b57815d6 100644 --- a/esphome/components/ld2410/ld2410.cpp +++ b/esphome/components/ld2410/ld2410.cpp @@ -1,5 +1,13 @@ #include "ld2410.h" +#include +#ifdef USE_NUMBER +#include "esphome/components/number/number.h" +#endif +#ifdef USE_SENSOR +#include "esphome/components/sensor/sensor.h" +#endif + #define highbyte(val) (uint8_t)((val) >> 8) #define lowbyte(val) (uint8_t)((val) &0xff) @@ -8,48 +16,97 @@ namespace ld2410 { static const char *const TAG = "ld2410"; +LD2410Component::LD2410Component() {} + 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_); + LOG_BINARY_SENSOR(" ", "TargetBinarySensor", this->target_binary_sensor_); + LOG_BINARY_SENSOR(" ", "MovingTargetBinarySensor", this->moving_target_binary_sensor_); + LOG_BINARY_SENSOR(" ", "StillTargetBinarySensor", this->still_target_binary_sensor_); + LOG_BINARY_SENSOR(" ", "OutPinPresenceStatusBinarySensor", this->out_pin_presence_status_binary_sensor_); +#endif +#ifdef USE_SWITCH + LOG_SWITCH(" ", "EngineeringModeSwitch", this->engineering_mode_switch_); + LOG_SWITCH(" ", "BluetoothSwitch", this->bluetooth_switch_); +#endif +#ifdef USE_BUTTON + LOG_BUTTON(" ", "ResetButton", this->reset_button_); + LOG_BUTTON(" ", "RestartButton", this->restart_button_); + LOG_BUTTON(" ", "QueryButton", this->query_button_); #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(" ", "LightSensor", this->light_sensor_); + LOG_SENSOR(" ", "MovingTargetDistanceSensor", this->moving_target_distance_sensor_); + LOG_SENSOR(" ", "StillTargetDistanceSensor", this->still_target_distance_sensor_); + LOG_SENSOR(" ", "MovingTargetEnergySensor", this->moving_target_energy_sensor_); + LOG_SENSOR(" ", "StillTargetEnergySensor", this->still_target_energy_sensor_); + LOG_SENSOR(" ", "DetectionDistanceSensor", this->detection_distance_sensor_); + for (sensor::Sensor *s : this->gate_still_sensors_) { + LOG_SENSOR(" ", "NthGateStillSesnsor", s); + } + for (sensor::Sensor *s : this->gate_move_sensors_) { + LOG_SENSOR(" ", "NthGateMoveSesnsor", s); + } #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]); +#ifdef USE_TEXT_SENSOR + LOG_TEXT_SENSOR(" ", "VersionTextSensor", this->version_text_sensor_); + LOG_TEXT_SENSOR(" ", "MacTextSensor", this->mac_text_sensor_); +#endif +#ifdef USE_SELECT + LOG_SELECT(" ", "LightFunctionSelect", this->light_function_select_); + LOG_SELECT(" ", "OutPinLevelSelect", this->out_pin_level_select_); + LOG_SELECT(" ", "DistanceResolutionSelect", this->distance_resolution_select_); + LOG_SELECT(" ", "BaudRateSelect", this->baud_rate_select_); +#endif +#ifdef USE_NUMBER + LOG_NUMBER(" ", "LightThresholdNumber", this->light_threshold_number_); + LOG_NUMBER(" ", "MaxStillDistanceGateNumber", this->max_still_distance_gate_number_); + LOG_NUMBER(" ", "MaxMoveDistanceGateNumber", this->max_move_distance_gate_number_); + LOG_NUMBER(" ", "TimeoutNumber", this->timeout_number_); + for (number::Number *n : this->gate_still_threshold_numbers_) { + LOG_NUMBER(" ", "Still Thresholds Number", n); + } + for (number::Number *n : this->gate_move_threshold_numbers_) { + LOG_NUMBER(" ", "Move Thresholds Number", n); + } +#endif + this->read_all_info(); + ESP_LOGCONFIG(TAG, " Throttle_ : %ums", this->throttle_); + ESP_LOGCONFIG(TAG, " MAC Address : %s", const_cast(this->mac_.c_str())); + ESP_LOGCONFIG(TAG, " Firmware Version : %s", const_cast(this->version_.c_str())); } void LD2410Component::setup() { ESP_LOGCONFIG(TAG, "Setting up LD2410..."); - 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); - 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]); + this->read_all_info(); + ESP_LOGCONFIG(TAG, "Mac Address : %s", const_cast(this->mac_.c_str())); + ESP_LOGCONFIG(TAG, "Firmware Version : %s", const_cast(this->version_.c_str())); ESP_LOGCONFIG(TAG, "LD2410 setup complete."); } +void LD2410Component::read_all_info() { + this->set_config_mode_(true); + this->get_version_(); + this->get_mac_(); + this->get_distance_resolution_(); + this->get_light_control_(); + this->query_parameters_(); + this->set_config_mode_(false); +#ifdef USE_SELECT + const auto baud_rate = std::to_string(this->parent_->get_baud_rate()); + if (this->baud_rate_select_ != nullptr && this->baud_rate_select_->state != baud_rate) { + this->baud_rate_select_->publish_state(baud_rate); + } +#endif +} + +void LD2410Component::restart_and_read_all_info() { + this->set_config_mode_(true); + this->restart_(); + this->set_timeout(1000, [this]() { this->read_all_info(); }); +} + void LD2410Component::loop() { const int max_line_length = 80; static uint8_t buffer[max_line_length]; @@ -59,9 +116,8 @@ void LD2410Component::loop() { } } -void LD2410Component::send_command_(uint8_t command, uint8_t *command_value, int command_value_len) { - // lastCommandSuccess->publish_state(false); - +void LD2410Component::send_command_(uint8_t command, const uint8_t *command_value, int command_value_len) { + ESP_LOGV(TAG, "Sending COMMAND %02X", command); // frame start bytes this->write_array(CMD_FRAME_HEADER, 4); // length bytes @@ -95,40 +151,43 @@ void LD2410Component::handle_periodic_data_(uint8_t *buffer, int len) { 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) + if (current_millis - last_periodic_millis_ < this->throttle_) return; - last_periodic_millis = current_millis; + 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)); + /* + Data Type: 7th + 0x01: Engineering mode + 0x02: Normal mode + */ + bool engineering_mode = buffer[DATA_TYPES] == 0x01; +#ifdef USE_SWITCH + if (this->engineering_mode_switch_ != nullptr && + current_millis - last_engineering_mode_change_millis_ > this->throttle_) { + this->engineering_mode_switch_->publish_state(engineering_mode); } - if (this->still_binary_sensor_ != nullptr) { - this->still_binary_sensor_->publish_state(CHECK_BIT(target_state, 1)); +#endif +#ifdef USE_BINARY_SENSOR + /* + Target states: 9th + 0x00 = No target + 0x01 = Moving targets + 0x02 = Still targets + 0x03 = Moving+Still targets + */ + char target_state = buffer[TARGET_STATES]; + if (this->target_binary_sensor_ != nullptr) { + this->target_binary_sensor_->publish_state(target_state != 0x00); + } + if (this->moving_target_binary_sensor_ != nullptr) { + this->moving_target_binary_sensor_->publish_state(CHECK_BIT(target_state, 0)); + } + if (this->still_target_binary_sensor_ != nullptr) { + this->still_target_binary_sensor_->publish_state(CHECK_BIT(target_state, 1)); } #endif /* @@ -164,26 +223,126 @@ void LD2410Component::handle_periodic_data_(uint8_t *buffer, int len) { if (this->detection_distance_sensor_->get_state() != new_detect_distance) this->detection_distance_sensor_->publish_state(new_detect_distance); } + if (engineering_mode) { + /* + Moving distance range: 18th byte + Still distance range: 19th byte + Moving enery: 20~28th bytes + */ + for (std::vector::size_type i = 0; i != this->gate_move_sensors_.size(); i++) { + sensor::Sensor *s = this->gate_move_sensors_[i]; + if (s != nullptr) { + s->publish_state(buffer[MOVING_SENSOR_START + i]); + } + } + /* + Still energy: 29~37th bytes + */ + for (std::vector::size_type i = 0; i != this->gate_still_sensors_.size(); i++) { + sensor::Sensor *s = this->gate_still_sensors_[i]; + if (s != nullptr) { + s->publish_state(buffer[STILL_SENSOR_START + i]); + } + } + /* + Light sensor: 38th bytes + */ + if (this->light_sensor_ != nullptr) { + int new_light_sensor = buffer[LIGHT_SENSOR]; + if (this->light_sensor_->get_state() != new_light_sensor) + this->light_sensor_->publish_state(new_light_sensor); + } + } else { + for (auto *s : this->gate_move_sensors_) { + if (s != nullptr && !std::isnan(s->get_state())) { + s->publish_state(NAN); + } + } + for (auto *s : this->gate_still_sensors_) { + if (s != nullptr && !std::isnan(s->get_state())) { + s->publish_state(NAN); + } + } + if (this->light_sensor_ != nullptr && !std::isnan(this->light_sensor_->get_state())) { + this->light_sensor_->publish_state(NAN); + } + } +#endif +#ifdef USE_BINARY_SENSOR + if (engineering_mode) { + if (this->out_pin_presence_status_binary_sensor_ != nullptr) { + this->out_pin_presence_status_binary_sensor_->publish_state(buffer[OUT_PIN_SENSOR] == 0x01); + } + } else { + if (this->out_pin_presence_status_binary_sensor_ != nullptr) { + this->out_pin_presence_status_binary_sensor_->publish_state(false); + } + } #endif } -void LD2410Component::handle_ack_data_(uint8_t *buffer, int len) { - ESP_LOGV(TAG, "Handling ACK DATA for COMMAND"); +const char VERSION_FMT[] = "%u.%02X.%02X%02X%02X%02X"; + +std::string format_version(uint8_t *buffer) { + std::string::size_type version_size = 256; + std::string version; + do { + version.resize(version_size + 1); + version_size = std::snprintf(&version[0], version.size(), VERSION_FMT, buffer[13], buffer[12], buffer[17], + buffer[16], buffer[15], buffer[14]); + } while (version_size + 1 > version.size()); + version.resize(version_size); + return version; +} + +const char MAC_FMT[] = "%02X:%02X:%02X:%02X:%02X:%02X"; + +const std::string UNKNOWN_MAC("unknown"); +const std::string NO_MAC("08:05:04:03:02:01"); + +std::string format_mac(uint8_t *buffer) { + std::string::size_type mac_size = 256; + std::string mac; + do { + mac.resize(mac_size + 1); + mac_size = std::snprintf(&mac[0], mac.size(), MAC_FMT, buffer[10], buffer[11], buffer[12], buffer[13], buffer[14], + buffer[15]); + } while (mac_size + 1 > mac.size()); + mac.resize(mac_size); + if (mac == NO_MAC) { + return UNKNOWN_MAC; + } + return mac; +} + +#ifdef USE_NUMBER +std::function set_number_value(number::Number *n, float value) { + float normalized_value = value * 1.0; + if (n != nullptr && (!n->has_state() || n->state != normalized_value)) { + n->state = normalized_value; + return [n, normalized_value]() { n->publish_state(normalized_value); }; + } + return []() {}; +} +#endif + +bool LD2410Component::handle_ack_data_(uint8_t *buffer, int len) { + ESP_LOGV(TAG, "Handling ACK DATA for COMMAND %02X", buffer[COMMAND]); if (len < 10) { ESP_LOGE(TAG, "Error with last command : incorrect length"); - return; + return true; } 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; + return true; } if (buffer[COMMAND_STATUS] != 0x01) { ESP_LOGE(TAG, "Error with last command : status != 0x01"); - return; + return true; } 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; + return true; } switch (buffer[COMMAND]) { @@ -193,49 +352,127 @@ void LD2410Component::handle_ack_data_(uint8_t *buffer, int len) { case lowbyte(CMD_DISABLE_CONF): ESP_LOGV(TAG, "Handled Disabled conf command"); break; + case lowbyte(CMD_SET_BAUD_RATE): + ESP_LOGV(TAG, "Handled baud rate change command"); +#ifdef USE_SELECT + if (this->baud_rate_select_ != nullptr) { + ESP_LOGE(TAG, "Change baud rate component config to %s and reinstall", this->baud_rate_select_->state.c_str()); + } +#endif + 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]; - + this->version_ = format_version(buffer); + ESP_LOGV(TAG, "FW Version is: %s", const_cast(this->version_.c_str())); +#ifdef USE_TEXT_SENSOR + if (this->version_text_sensor_ != nullptr) { + this->version_text_sensor_->publish_state(this->version_); + } +#endif + break; + case lowbyte(CMD_QUERY_DISTANCE_RESOLUTION): { + std::string distance_resolution = + DISTANCE_RESOLUTION_INT_TO_ENUM.at(this->two_byte_to_int_(buffer[10], buffer[11])); + ESP_LOGV(TAG, "Distance resolution is: %s", const_cast(distance_resolution.c_str())); +#ifdef USE_SELECT + if (this->distance_resolution_select_ != nullptr && + this->distance_resolution_select_->state != distance_resolution) { + this->distance_resolution_select_->publish_state(distance_resolution); + } +#endif + } break; + case lowbyte(CMD_QUERY_LIGHT_CONTROL): { + this->light_function_ = LIGHT_FUNCTION_INT_TO_ENUM.at(buffer[10]); + this->light_threshold_ = buffer[11] * 1.0; + this->out_pin_level_ = OUT_PIN_LEVEL_INT_TO_ENUM.at(buffer[12]); + ESP_LOGV(TAG, "Light function is: %s", const_cast(this->light_function_.c_str())); + ESP_LOGV(TAG, "Light threshold is: %f", this->light_threshold_); + ESP_LOGV(TAG, "Out pin level is: %s", const_cast(this->out_pin_level_.c_str())); +#ifdef USE_SELECT + if (this->light_function_select_ != nullptr && this->light_function_select_->state != this->light_function_) { + this->light_function_select_->publish_state(this->light_function_); + } + if (this->out_pin_level_select_ != nullptr && this->out_pin_level_select_->state != this->out_pin_level_) { + this->out_pin_level_select_->publish_state(this->out_pin_level_); + } +#endif +#ifdef USE_NUMBER + if (this->light_threshold_number_ != nullptr && + (!this->light_threshold_number_->has_state() || + this->light_threshold_number_->state != this->light_threshold_)) { + this->light_threshold_number_->publish_state(this->light_threshold_); + } +#endif + } break; + case lowbyte(CMD_MAC): + if (len < 20) { + return false; + } + this->mac_ = format_mac(buffer); + ESP_LOGV(TAG, "MAC Address is: %s", const_cast(this->mac_.c_str())); +#ifdef USE_TEXT_SENSOR + if (this->mac_text_sensor_ != nullptr) { + this->mac_text_sensor_->publish_state(this->mac_); + } +#endif +#ifdef USE_SWITCH + if (this->bluetooth_switch_ != nullptr) { + this->bluetooth_switch_->publish_state(this->mac_ != UNKNOWN_MAC); + } +#endif break; case lowbyte(CMD_GATE_SENS): ESP_LOGV(TAG, "Handled sensitivity command"); break; + case lowbyte(CMD_BLUETOOTH): + ESP_LOGV(TAG, "Handled bluetooth command"); + break; + case lowbyte(CMD_SET_DISTANCE_RESOLUTION): + ESP_LOGV(TAG, "Handled set distance resolution command"); + break; + case lowbyte(CMD_SET_LIGHT_CONTROL): + ESP_LOGV(TAG, "Handled set light control command"); + break; + case lowbyte(CMD_BT_PASSWORD): + ESP_LOGV(TAG, "Handled set bluetooth password command"); + break; case lowbyte(CMD_QUERY): // Query parameters response { if (buffer[10] != 0xAA) - return; // value head=0xAA + return true; // value head=0xAA +#ifdef USE_NUMBER /* Moving distance range: 13th byte Still distance range: 14th byte */ - // TODO - // maxMovingDistanceRange->publish_state(buffer[12]); - // maxStillDistanceRange->publish_state(buffer[13]); + std::vector> updates; + updates.push_back(set_number_value(this->max_move_distance_gate_number_, buffer[12])); + updates.push_back(set_number_value(this->max_still_distance_gate_number_, buffer[13])); /* Moving Sensitivities: 15~23th bytes + */ + for (std::vector::size_type i = 0; i != this->gate_move_threshold_numbers_.size(); i++) { + updates.push_back(set_number_value(this->gate_move_threshold_numbers_[i], buffer[14 + i])); + } + /* 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]; + for (std::vector::size_type i = 0; i != this->gate_still_threshold_numbers_.size(); i++) { + updates.push_back(set_number_value(this->gate_still_threshold_numbers_[i], buffer[23 + i])); } /* None Duration: 33~34th bytes */ - // noneDuration->publish_state(this->two_byte_to_int_(buffer[32], buffer[33])); + updates.push_back(set_number_value(this->timeout_number_, this->two_byte_to_int_(buffer[32], buffer[33]))); + for (auto &update : updates) { + update(); + } +#endif } break; default: break; } + + return true; } void LD2410Component::readline_(int readch, uint8_t *buffer, int len) { @@ -256,8 +493,11 @@ void LD2410Component::readline_(int readch, uint8_t *buffer, int len) { } 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 + if (this->handle_ack_data_(buffer, pos)) { + pos = 0; // Reset position index ready for next time + } else { + ESP_LOGV(TAG, "ACK Data incomplete"); + } } } } @@ -269,21 +509,85 @@ void LD2410Component::set_config_mode_(bool enable) { this->send_command_(cmd, enable ? cmd_value : nullptr, 2); } +void LD2410Component::set_bluetooth(bool enable) { + this->set_config_mode_(true); + uint8_t enable_cmd_value[2] = {0x01, 0x00}; + uint8_t disable_cmd_value[2] = {0x00, 0x00}; + this->send_command_(CMD_BLUETOOTH, enable ? enable_cmd_value : disable_cmd_value, 2); + this->set_timeout(200, [this]() { this->restart_and_read_all_info(); }); +} + +void LD2410Component::set_distance_resolution(const std::string &state) { + this->set_config_mode_(true); + uint8_t cmd_value[2] = {DISTANCE_RESOLUTION_ENUM_TO_INT.at(state), 0x00}; + this->send_command_(CMD_SET_DISTANCE_RESOLUTION, cmd_value, 2); + this->set_timeout(200, [this]() { this->restart_and_read_all_info(); }); +} + +void LD2410Component::set_baud_rate(const std::string &state) { + this->set_config_mode_(true); + uint8_t cmd_value[2] = {BAUD_RATE_ENUM_TO_INT.at(state), 0x00}; + this->send_command_(CMD_SET_BAUD_RATE, cmd_value, 2); + this->set_timeout(200, [this]() { this->restart_(); }); +} + +void LD2410Component::set_bluetooth_password(const std::string &password) { + if (password.length() != 6) { + ESP_LOGE(TAG, "set_bluetooth_password(): invalid password length, must be exactly 6 chars '%s'", password.c_str()); + return; + } + this->set_config_mode_(true); + uint8_t cmd_value[6]; + std::copy(password.begin(), password.end(), std::begin(cmd_value)); + this->send_command_(CMD_BT_PASSWORD, cmd_value, 6); + this->set_config_mode_(false); +} + +void LD2410Component::set_engineering_mode(bool enable) { + this->set_config_mode_(true); + last_engineering_mode_change_millis_ = millis(); + uint8_t cmd = enable ? CMD_ENABLE_ENG : CMD_DISABLE_ENG; + this->send_command_(cmd, nullptr, 0); + this->set_config_mode_(false); +} + +void LD2410Component::factory_reset() { + this->set_config_mode_(true); + this->send_command_(CMD_RESET, nullptr, 0); + this->set_timeout(200, [this]() { this->restart_and_read_all_info(); }); +} + +void LD2410Component::restart_() { this->send_command_(CMD_RESTART, nullptr, 0); } + void LD2410Component::query_parameters_() { this->send_command_(CMD_QUERY, nullptr, 0); } void LD2410Component::get_version_() { this->send_command_(CMD_VERSION, nullptr, 0); } +void LD2410Component::get_mac_() { + uint8_t cmd_value[2] = {0x01, 0x00}; + this->send_command_(CMD_MAC, cmd_value, 2); +} +void LD2410Component::get_distance_resolution_() { this->send_command_(CMD_QUERY_DISTANCE_RESOLUTION, nullptr, 0); } -void LD2410Component::set_max_distances_timeout_(uint8_t max_moving_distance_range, uint8_t max_still_distance_range, - uint16_t timeout) { +void LD2410Component::get_light_control_() { this->send_command_(CMD_QUERY_LIGHT_CONTROL, nullptr, 0); } + +#ifdef USE_NUMBER +void LD2410Component::set_max_distances_timeout() { + if (!this->max_move_distance_gate_number_->has_state() || !this->max_still_distance_gate_number_->has_state() || + !this->timeout_number_->has_state()) { + return; + } + int max_moving_distance_gate_range = static_cast(this->max_move_distance_gate_number_->state); + int max_still_distance_gate_range = static_cast(this->max_still_distance_gate_number_->state); + int timeout = static_cast(this->timeout_number_->state); uint8_t value[18] = {0x00, 0x00, - lowbyte(max_moving_distance_range), - highbyte(max_moving_distance_range), + lowbyte(max_moving_distance_gate_range), + highbyte(max_moving_distance_gate_range), 0x00, 0x00, 0x01, 0x00, - lowbyte(max_still_distance_range), - highbyte(max_still_distance_range), + lowbyte(max_still_distance_gate_range), + highbyte(max_still_distance_gate_range), 0x00, 0x00, 0x02, @@ -292,10 +596,25 @@ void LD2410Component::set_max_distances_timeout_(uint8_t max_moving_distance_ran highbyte(timeout), 0x00, 0x00}; + this->set_config_mode_(true); this->send_command_(CMD_MAXDIST_DURATION, value, 18); + delay(50); // NOLINT this->query_parameters_(); + this->set_timeout(200, [this]() { this->restart_and_read_all_info(); }); + this->set_config_mode_(false); } -void LD2410Component::set_gate_threshold_(uint8_t gate, uint8_t motionsens, uint8_t stillsens) { + +void LD2410Component::set_gate_threshold(uint8_t gate) { + number::Number *motionsens = this->gate_move_threshold_numbers_[gate]; + number::Number *stillsens = this->gate_still_threshold_numbers_[gate]; + + if (!motionsens->has_state() || !stillsens->has_state()) { + return; + } + int motion = static_cast(motionsens->state); + int still = static_cast(stillsens->state); + + this->set_config_mode_(true); // 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 @@ -305,11 +624,57 @@ void LD2410Component::set_gate_threshold_(uint8_t gate, uint8_t motionsens, uint // 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}; + uint8_t value[18] = {0x00, 0x00, lowbyte(gate), highbyte(gate), 0x00, 0x00, + 0x01, 0x00, lowbyte(motion), highbyte(motion), 0x00, 0x00, + 0x02, 0x00, lowbyte(still), highbyte(still), 0x00, 0x00}; this->send_command_(CMD_GATE_SENS, value, 18); + delay(50); // NOLINT + this->query_parameters_(); + this->set_config_mode_(false); } +void LD2410Component::set_gate_still_threshold_number(int gate, number::Number *n) { + this->gate_still_threshold_numbers_[gate] = n; +} + +void LD2410Component::set_gate_move_threshold_number(int gate, number::Number *n) { + this->gate_move_threshold_numbers_[gate] = n; +} +#endif + +void LD2410Component::set_light_out_control() { +#ifdef USE_NUMBER + if (this->light_threshold_number_ != nullptr && this->light_threshold_number_->has_state()) { + this->light_threshold_ = this->light_threshold_number_->state; + } +#endif +#ifdef USE_SELECT + if (this->light_function_select_ != nullptr && this->light_function_select_->has_state()) { + this->light_function_ = this->light_function_select_->state; + } + if (this->out_pin_level_select_ != nullptr && this->out_pin_level_select_->has_state()) { + this->out_pin_level_ = this->out_pin_level_select_->state; + } +#endif + if (this->light_function_.empty() || this->out_pin_level_.empty() || this->light_threshold_ < 0) { + return; + } + this->set_config_mode_(true); + uint8_t light_function = LIGHT_FUNCTION_ENUM_TO_INT.at(this->light_function_); + uint8_t light_threshold = static_cast(this->light_threshold_); + uint8_t out_pin_level = OUT_PIN_LEVEL_ENUM_TO_INT.at(this->out_pin_level_); + uint8_t value[4] = {light_function, light_threshold, out_pin_level, 0x00}; + this->send_command_(CMD_SET_LIGHT_CONTROL, value, 4); + delay(50); // NOLINT + this->get_light_control_(); + this->set_timeout(200, [this]() { this->restart_and_read_all_info(); }); + this->set_config_mode_(false); +} + +#ifdef USE_SENSOR +void LD2410Component::set_gate_move_sensor(int gate, sensor::Sensor *s) { this->gate_move_sensors_[gate] = s; } +void LD2410Component::set_gate_still_sensor(int gate, sensor::Sensor *s) { this->gate_still_sensors_[gate] = s; } +#endif + } // namespace ld2410 } // namespace esphome diff --git a/esphome/components/ld2410/ld2410.h b/esphome/components/ld2410/ld2410.h index 8edb83a8d5..8084d4c33e 100644 --- a/esphome/components/ld2410/ld2410.h +++ b/esphome/components/ld2410/ld2410.h @@ -7,10 +7,27 @@ #ifdef USE_SENSOR #include "esphome/components/sensor/sensor.h" #endif +#ifdef USE_NUMBER +#include "esphome/components/number/number.h" +#endif +#ifdef USE_SWITCH +#include "esphome/components/switch/switch.h" +#endif +#ifdef USE_BUTTON +#include "esphome/components/button/button.h" +#endif +#ifdef USE_SELECT +#include "esphome/components/select/select.h" +#endif +#ifdef USE_TEXT_SENSOR +#include "esphome/components/text_sensor/text_sensor.h" +#endif #include "esphome/components/uart/uart.h" #include "esphome/core/automation.h" #include "esphome/core/helpers.h" +#include + namespace esphome { namespace ld2410 { @@ -19,10 +36,63 @@ namespace ld2410 { // Commands static const uint8_t CMD_ENABLE_CONF = 0x00FF; static const uint8_t CMD_DISABLE_CONF = 0x00FE; +static const uint8_t CMD_ENABLE_ENG = 0x0062; +static const uint8_t CMD_DISABLE_ENG = 0x0063; 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; +static const uint8_t CMD_QUERY_DISTANCE_RESOLUTION = 0x00AB; +static const uint8_t CMD_SET_DISTANCE_RESOLUTION = 0x00AA; +static const uint8_t CMD_QUERY_LIGHT_CONTROL = 0x00AE; +static const uint8_t CMD_SET_LIGHT_CONTROL = 0x00AD; +static const uint8_t CMD_SET_BAUD_RATE = 0x00A1; +static const uint8_t CMD_BT_PASSWORD = 0x00A9; +static const uint8_t CMD_MAC = 0x00A5; +static const uint8_t CMD_RESET = 0x00A2; +static const uint8_t CMD_RESTART = 0x00A3; +static const uint8_t CMD_BLUETOOTH = 0x00A4; + +enum BaudRateStructure : uint8_t { + BAUD_RATE_9600 = 1, + BAUD_RATE_19200 = 2, + BAUD_RATE_38400 = 3, + BAUD_RATE_57600 = 4, + BAUD_RATE_115200 = 5, + BAUD_RATE_230400 = 6, + BAUD_RATE_256000 = 7, + BAUD_RATE_460800 = 8 +}; + +static const std::map BAUD_RATE_ENUM_TO_INT{ + {"9600", BAUD_RATE_9600}, {"19200", BAUD_RATE_19200}, {"38400", BAUD_RATE_38400}, + {"57600", BAUD_RATE_57600}, {"115200", BAUD_RATE_115200}, {"230400", BAUD_RATE_230400}, + {"256000", BAUD_RATE_256000}, {"460800", BAUD_RATE_460800}}; + +enum DistanceResolutionStructure : uint8_t { DISTANCE_RESOLUTION_0_2 = 0x01, DISTANCE_RESOLUTION_0_75 = 0x00 }; + +static const std::map DISTANCE_RESOLUTION_ENUM_TO_INT{{"0.2m", DISTANCE_RESOLUTION_0_2}, + {"0.75m", DISTANCE_RESOLUTION_0_75}}; +static const std::map DISTANCE_RESOLUTION_INT_TO_ENUM{{DISTANCE_RESOLUTION_0_2, "0.2m"}, + {DISTANCE_RESOLUTION_0_75, "0.75m"}}; + +enum LightFunctionStructure : uint8_t { + LIGHT_FUNCTION_OFF = 0x00, + LIGHT_FUNCTION_BELOW = 0x01, + LIGHT_FUNCTION_ABOVE = 0x02 +}; + +static const std::map LIGHT_FUNCTION_ENUM_TO_INT{ + {"off", LIGHT_FUNCTION_OFF}, {"below", LIGHT_FUNCTION_BELOW}, {"above", LIGHT_FUNCTION_ABOVE}}; +static const std::map LIGHT_FUNCTION_INT_TO_ENUM{ + {LIGHT_FUNCTION_OFF, "off"}, {LIGHT_FUNCTION_BELOW, "below"}, {LIGHT_FUNCTION_ABOVE, "above"}}; + +enum OutPinLevelStructure : uint8_t { OUT_PIN_LEVEL_LOW = 0x00, OUT_PIN_LEVEL_HIGH = 0x01 }; + +static const std::map OUT_PIN_LEVEL_ENUM_TO_INT{{"low", OUT_PIN_LEVEL_LOW}, + {"high", OUT_PIN_LEVEL_HIGH}}; +static const std::map OUT_PIN_LEVEL_INT_TO_ENUM{{OUT_PIN_LEVEL_LOW, "low"}, + {OUT_PIN_LEVEL_HIGH, "high"}}; // Commands values static const uint8_t CMD_MAX_MOVE_VALUE = 0x0000; @@ -44,7 +114,7 @@ Target states: 9th byte Detect distance: 16~17th bytes */ enum PeriodicDataStructure : uint8_t { - DATA_TYPES = 5, + DATA_TYPES = 6, TARGET_STATES = 8, MOVING_TARGET_LOW = 9, MOVING_TARGET_HIGH = 10, @@ -54,6 +124,10 @@ enum PeriodicDataStructure : uint8_t { STILL_ENERGY = 14, DETECT_DISTANCE_LOW = 15, DETECT_DISTANCE_HIGH = 16, + MOVING_SENSOR_START = 19, + STILL_SENSOR_START = 28, + LIGHT_SENSOR = 37, + OUT_PIN_SENSOR = 38, }; enum PeriodicDataValue : uint8_t { HEAD = 0XAA, END = 0x55, CHECK = 0x00 }; @@ -66,80 +140,97 @@ class LD2410Component : public Component, public uart::UARTDevice { SUB_SENSOR(still_target_distance) SUB_SENSOR(moving_target_energy) SUB_SENSOR(still_target_energy) + SUB_SENSOR(light) SUB_SENSOR(detection_distance) #endif +#ifdef USE_BINARY_SENSOR + SUB_BINARY_SENSOR(target) + SUB_BINARY_SENSOR(moving_target) + SUB_BINARY_SENSOR(still_target) + SUB_BINARY_SENSOR(out_pin_presence_status) +#endif +#ifdef USE_TEXT_SENSOR + SUB_TEXT_SENSOR(version) + SUB_TEXT_SENSOR(mac) +#endif +#ifdef USE_SELECT + SUB_SELECT(distance_resolution) + SUB_SELECT(baud_rate) + SUB_SELECT(light_function) + SUB_SELECT(out_pin_level) +#endif +#ifdef USE_SWITCH + SUB_SWITCH(engineering_mode) + SUB_SWITCH(bluetooth) +#endif +#ifdef USE_BUTTON + SUB_BUTTON(reset) + SUB_BUTTON(restart) + SUB_BUTTON(query) +#endif +#ifdef USE_NUMBER + SUB_NUMBER(max_still_distance_gate) + SUB_NUMBER(max_move_distance_gate) + SUB_NUMBER(timeout) + SUB_NUMBER(light_threshold) +#endif public: + LD2410Component(); 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; }; + void set_light_out_control(); +#ifdef USE_NUMBER + void set_gate_still_threshold_number(int gate, number::Number *n); + void set_gate_move_threshold_number(int gate, number::Number *n); + void set_max_distances_timeout(); + void set_gate_threshold(uint8_t gate); #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(); +#ifdef USE_SENSOR + void set_gate_move_sensor(int gate, sensor::Sensor *s); + void set_gate_still_sensor(int gate, sensor::Sensor *s); +#endif + void set_throttle(uint16_t value) { this->throttle_ = value; }; + void set_bluetooth_password(const std::string &password); + void set_engineering_mode(bool enable); + void read_all_info(); + void restart_and_read_all_info(); + void set_bluetooth(bool enable); + void set_distance_resolution(const std::string &state); + void set_baud_rate(const std::string &state); + void factory_reset(); 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 send_command_(uint8_t command_str, const uint8_t *command_value, int command_value_len); void set_config_mode_(bool enable); void handle_periodic_data_(uint8_t *buffer, int len); - void handle_ack_data_(uint8_t *buffer, int len); + bool handle_ack_data_(uint8_t *buffer, int len); void readline_(int readch, uint8_t *buffer, int len); void query_parameters_(); void get_version_(); + void get_mac_(); + void get_distance_resolution_(); + void get_light_control_(); + void restart_(); - 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_; + int32_t last_periodic_millis_ = millis(); + int32_t last_engineering_mode_change_millis_ = millis(); + uint16_t throttle_; + std::string version_; + std::string mac_; + std::string out_pin_level_; + std::string light_function_; + float light_threshold_ = -1; +#ifdef USE_NUMBER + std::vector gate_still_threshold_numbers_ = std::vector(9); + std::vector gate_move_threshold_numbers_ = std::vector(9); +#endif +#ifdef USE_SENSOR + std::vector gate_still_sensors_ = std::vector(9); + std::vector gate_move_sensors_ = std::vector(9); +#endif }; } // namespace ld2410 diff --git a/esphome/components/ld2410/number/__init__.py b/esphome/components/ld2410/number/__init__.py new file mode 100644 index 0000000000..557b226dfe --- /dev/null +++ b/esphome/components/ld2410/number/__init__.py @@ -0,0 +1,128 @@ +import esphome.codegen as cg +from esphome.components import number +import esphome.config_validation as cv +from esphome.const import ( + CONF_ID, + CONF_TIMEOUT, + DEVICE_CLASS_DISTANCE, + DEVICE_CLASS_SIGNAL_STRENGTH, + DEVICE_CLASS_ILLUMINANCE, + UNIT_SECOND, + UNIT_PERCENT, + ENTITY_CATEGORY_CONFIG, + ICON_MOTION_SENSOR, + ICON_TIMELAPSE, + ICON_LIGHTBULB, +) +from .. import CONF_LD2410_ID, LD2410Component, ld2410_ns + +GateThresholdNumber = ld2410_ns.class_("GateThresholdNumber", number.Number) +LightThresholdNumber = ld2410_ns.class_("LightThresholdNumber", number.Number) +MaxDistanceTimeoutNumber = ld2410_ns.class_("MaxDistanceTimeoutNumber", number.Number) + +CONF_MAX_MOVE_DISTANCE_GATE = "max_move_distance_gate" +CONF_MAX_STILL_DISTANCE_GATE = "max_still_distance_gate" +CONF_LIGHT_THRESHOLD = "light_threshold" +CONF_STILL_THRESHOLD = "still_threshold" +CONF_MOVE_THRESHOLD = "move_threshold" + +TIMEOUT_GROUP = "timeout" + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_LD2410_ID): cv.use_id(LD2410Component), + cv.Inclusive(CONF_TIMEOUT, TIMEOUT_GROUP): number.number_schema( + MaxDistanceTimeoutNumber, + unit_of_measurement=UNIT_SECOND, + entity_category=ENTITY_CATEGORY_CONFIG, + icon=ICON_TIMELAPSE, + ), + cv.Inclusive(CONF_MAX_MOVE_DISTANCE_GATE, TIMEOUT_GROUP): number.number_schema( + MaxDistanceTimeoutNumber, + device_class=DEVICE_CLASS_DISTANCE, + entity_category=ENTITY_CATEGORY_CONFIG, + icon=ICON_MOTION_SENSOR, + ), + cv.Inclusive(CONF_MAX_STILL_DISTANCE_GATE, TIMEOUT_GROUP): number.number_schema( + MaxDistanceTimeoutNumber, + device_class=DEVICE_CLASS_DISTANCE, + entity_category=ENTITY_CATEGORY_CONFIG, + icon=ICON_MOTION_SENSOR, + ), + cv.Optional(CONF_LIGHT_THRESHOLD): number.number_schema( + LightThresholdNumber, + device_class=DEVICE_CLASS_ILLUMINANCE, + entity_category=ENTITY_CATEGORY_CONFIG, + icon=ICON_LIGHTBULB, + ), + } +) + +CONFIG_SCHEMA = CONFIG_SCHEMA.extend( + { + cv.Optional(f"g{x}"): cv.Schema( + { + cv.Required(CONF_MOVE_THRESHOLD): number.number_schema( + GateThresholdNumber, + device_class=DEVICE_CLASS_SIGNAL_STRENGTH, + unit_of_measurement=UNIT_PERCENT, + entity_category=ENTITY_CATEGORY_CONFIG, + icon=ICON_MOTION_SENSOR, + ), + cv.Required(CONF_STILL_THRESHOLD): number.number_schema( + GateThresholdNumber, + device_class=DEVICE_CLASS_SIGNAL_STRENGTH, + unit_of_measurement=UNIT_PERCENT, + entity_category=ENTITY_CATEGORY_CONFIG, + icon=ICON_MOTION_SENSOR, + ), + } + ) + for x in range(9) + } +) + + +async def to_code(config): + ld2410_component = await cg.get_variable(config[CONF_LD2410_ID]) + if timeout_config := config.get(CONF_TIMEOUT): + n = await number.new_number( + timeout_config, min_value=0, max_value=65535, step=1 + ) + await cg.register_parented(n, config[CONF_LD2410_ID]) + cg.add(ld2410_component.set_timeout_number(n)) + if max_move_distance_gate_config := config.get(CONF_MAX_MOVE_DISTANCE_GATE): + n = await number.new_number( + max_move_distance_gate_config, min_value=2, max_value=8, step=1 + ) + await cg.register_parented(n, config[CONF_LD2410_ID]) + cg.add(ld2410_component.set_max_move_distance_gate_number(n)) + if max_still_distance_gate_config := config.get(CONF_MAX_STILL_DISTANCE_GATE): + n = await number.new_number( + max_still_distance_gate_config, min_value=2, max_value=8, step=1 + ) + await cg.register_parented(n, config[CONF_LD2410_ID]) + cg.add(ld2410_component.set_max_still_distance_gate_number(n)) + if light_threshold_config := config.get(CONF_LIGHT_THRESHOLD): + n = await number.new_number( + light_threshold_config, min_value=0, max_value=255, step=1 + ) + await cg.register_parented(n, config[CONF_LD2410_ID]) + cg.add(ld2410_component.set_light_threshold_number(n)) + for x in range(9): + if gate_conf := config.get(f"g{x}"): + move_config = gate_conf[CONF_MOVE_THRESHOLD] + n = cg.new_Pvariable(move_config[CONF_ID], x) + await number.register_number( + n, move_config, min_value=0, max_value=100, step=1 + ) + await cg.register_parented(n, config[CONF_LD2410_ID]) + cg.add(ld2410_component.set_gate_move_threshold_number(x, n)) + + still_config = gate_conf[CONF_STILL_THRESHOLD] + n = cg.new_Pvariable(still_config[CONF_ID], x) + await number.register_number( + n, still_config, min_value=0, max_value=100, step=1 + ) + await cg.register_parented(n, config[CONF_LD2410_ID]) + cg.add(ld2410_component.set_gate_still_threshold_number(x, n)) diff --git a/esphome/components/ld2410/number/gate_threshold_number.cpp b/esphome/components/ld2410/number/gate_threshold_number.cpp new file mode 100644 index 0000000000..5d040554d7 --- /dev/null +++ b/esphome/components/ld2410/number/gate_threshold_number.cpp @@ -0,0 +1,14 @@ +#include "gate_threshold_number.h" + +namespace esphome { +namespace ld2410 { + +GateThresholdNumber::GateThresholdNumber(uint8_t gate) : gate_(gate) {} + +void GateThresholdNumber::control(float value) { + this->publish_state(value); + this->parent_->set_gate_threshold(this->gate_); +} + +} // namespace ld2410 +} // namespace esphome diff --git a/esphome/components/ld2410/number/gate_threshold_number.h b/esphome/components/ld2410/number/gate_threshold_number.h new file mode 100644 index 0000000000..2806ecce63 --- /dev/null +++ b/esphome/components/ld2410/number/gate_threshold_number.h @@ -0,0 +1,19 @@ +#pragma once + +#include "esphome/components/number/number.h" +#include "../ld2410.h" + +namespace esphome { +namespace ld2410 { + +class GateThresholdNumber : public number::Number, public Parented { + public: + GateThresholdNumber(uint8_t gate); + + protected: + uint8_t gate_; + void control(float value) override; +}; + +} // namespace ld2410 +} // namespace esphome diff --git a/esphome/components/ld2410/number/light_threshold_number.cpp b/esphome/components/ld2410/number/light_threshold_number.cpp new file mode 100644 index 0000000000..0ff35782cd --- /dev/null +++ b/esphome/components/ld2410/number/light_threshold_number.cpp @@ -0,0 +1,12 @@ +#include "light_threshold_number.h" + +namespace esphome { +namespace ld2410 { + +void LightThresholdNumber::control(float value) { + this->publish_state(value); + this->parent_->set_light_out_control(); +} + +} // namespace ld2410 +} // namespace esphome diff --git a/esphome/components/ld2410/number/light_threshold_number.h b/esphome/components/ld2410/number/light_threshold_number.h new file mode 100644 index 0000000000..8f014373c0 --- /dev/null +++ b/esphome/components/ld2410/number/light_threshold_number.h @@ -0,0 +1,18 @@ +#pragma once + +#include "esphome/components/number/number.h" +#include "../ld2410.h" + +namespace esphome { +namespace ld2410 { + +class LightThresholdNumber : public number::Number, public Parented { + public: + LightThresholdNumber() = default; + + protected: + void control(float value) override; +}; + +} // namespace ld2410 +} // namespace esphome diff --git a/esphome/components/ld2410/number/max_distance_timeout_number.cpp b/esphome/components/ld2410/number/max_distance_timeout_number.cpp new file mode 100644 index 0000000000..8a946f7ea9 --- /dev/null +++ b/esphome/components/ld2410/number/max_distance_timeout_number.cpp @@ -0,0 +1,12 @@ +#include "max_distance_timeout_number.h" + +namespace esphome { +namespace ld2410 { + +void MaxDistanceTimeoutNumber::control(float value) { + this->publish_state(value); + this->parent_->set_max_distances_timeout(); +} + +} // namespace ld2410 +} // namespace esphome diff --git a/esphome/components/ld2410/number/max_distance_timeout_number.h b/esphome/components/ld2410/number/max_distance_timeout_number.h new file mode 100644 index 0000000000..7d91b4b5fe --- /dev/null +++ b/esphome/components/ld2410/number/max_distance_timeout_number.h @@ -0,0 +1,18 @@ +#pragma once + +#include "esphome/components/number/number.h" +#include "../ld2410.h" + +namespace esphome { +namespace ld2410 { + +class MaxDistanceTimeoutNumber : public number::Number, public Parented { + public: + MaxDistanceTimeoutNumber() = default; + + protected: + void control(float value) override; +}; + +} // namespace ld2410 +} // namespace esphome diff --git a/esphome/components/ld2410/select/__init__.py b/esphome/components/ld2410/select/__init__.py new file mode 100644 index 0000000000..6c34a85ac6 --- /dev/null +++ b/esphome/components/ld2410/select/__init__.py @@ -0,0 +1,81 @@ +import esphome.codegen as cg +from esphome.components import select +import esphome.config_validation as cv +from esphome.const import ( + ENTITY_CATEGORY_CONFIG, + CONF_BAUD_RATE, + ICON_THERMOMETER, + ICON_SCALE, + ICON_LIGHTBULB, + ICON_RULER, +) +from .. import CONF_LD2410_ID, LD2410Component, ld2410_ns + +BaudRateSelect = ld2410_ns.class_("BaudRateSelect", select.Select) +DistanceResolutionSelect = ld2410_ns.class_("DistanceResolutionSelect", select.Select) +LightOutControlSelect = ld2410_ns.class_("LightOutControlSelect", select.Select) + +CONF_DISTANCE_RESOLUTION = "distance_resolution" +CONF_LIGHT_FUNCTION = "light_function" +CONF_OUT_PIN_LEVEL = "out_pin_level" + + +CONFIG_SCHEMA = { + cv.GenerateID(CONF_LD2410_ID): cv.use_id(LD2410Component), + cv.Optional(CONF_DISTANCE_RESOLUTION): select.select_schema( + DistanceResolutionSelect, + entity_category=ENTITY_CATEGORY_CONFIG, + icon=ICON_RULER, + ), + cv.Optional(CONF_LIGHT_FUNCTION): select.select_schema( + LightOutControlSelect, + entity_category=ENTITY_CATEGORY_CONFIG, + icon=ICON_LIGHTBULB, + ), + cv.Optional(CONF_OUT_PIN_LEVEL): select.select_schema( + LightOutControlSelect, + entity_category=ENTITY_CATEGORY_CONFIG, + icon=ICON_SCALE, + ), + cv.Optional(CONF_BAUD_RATE): select.select_schema( + BaudRateSelect, + entity_category=ENTITY_CATEGORY_CONFIG, + icon=ICON_THERMOMETER, + ), +} + + +async def to_code(config): + ld2410_component = await cg.get_variable(config[CONF_LD2410_ID]) + if distance_resolution_config := config.get(CONF_DISTANCE_RESOLUTION): + s = await select.new_select( + distance_resolution_config, options=["0.2m", "0.75m"] + ) + await cg.register_parented(s, config[CONF_LD2410_ID]) + cg.add(ld2410_component.set_distance_resolution_select(s)) + if out_pin_level_config := config.get(CONF_OUT_PIN_LEVEL): + s = await select.new_select(out_pin_level_config, options=["low", "high"]) + await cg.register_parented(s, config[CONF_LD2410_ID]) + cg.add(ld2410_component.set_out_pin_level_select(s)) + if light_function_config := config.get(CONF_LIGHT_FUNCTION): + s = await select.new_select( + light_function_config, options=["off", "below", "above"] + ) + await cg.register_parented(s, config[CONF_LD2410_ID]) + cg.add(ld2410_component.set_light_function_select(s)) + if baud_rate_config := config.get(CONF_BAUD_RATE): + s = await select.new_select( + baud_rate_config, + options=[ + "9600", + "19200", + "38400", + "57600", + "115200", + "230400", + "256000", + "460800", + ], + ) + await cg.register_parented(s, config[CONF_LD2410_ID]) + cg.add(ld2410_component.set_baud_rate_select(s)) diff --git a/esphome/components/ld2410/select/baud_rate_select.cpp b/esphome/components/ld2410/select/baud_rate_select.cpp new file mode 100644 index 0000000000..f4e0b90e2e --- /dev/null +++ b/esphome/components/ld2410/select/baud_rate_select.cpp @@ -0,0 +1,12 @@ +#include "baud_rate_select.h" + +namespace esphome { +namespace ld2410 { + +void BaudRateSelect::control(const std::string &value) { + this->publish_state(value); + this->parent_->set_baud_rate(state); +} + +} // namespace ld2410 +} // namespace esphome diff --git a/esphome/components/ld2410/select/baud_rate_select.h b/esphome/components/ld2410/select/baud_rate_select.h new file mode 100644 index 0000000000..3827b6a48a --- /dev/null +++ b/esphome/components/ld2410/select/baud_rate_select.h @@ -0,0 +1,18 @@ +#pragma once + +#include "esphome/components/select/select.h" +#include "../ld2410.h" + +namespace esphome { +namespace ld2410 { + +class BaudRateSelect : public select::Select, public Parented { + public: + BaudRateSelect() = default; + + protected: + void control(const std::string &value) override; +}; + +} // namespace ld2410 +} // namespace esphome diff --git a/esphome/components/ld2410/select/distance_resolution_select.cpp b/esphome/components/ld2410/select/distance_resolution_select.cpp new file mode 100644 index 0000000000..eef34bda63 --- /dev/null +++ b/esphome/components/ld2410/select/distance_resolution_select.cpp @@ -0,0 +1,12 @@ +#include "distance_resolution_select.h" + +namespace esphome { +namespace ld2410 { + +void DistanceResolutionSelect::control(const std::string &value) { + this->publish_state(value); + this->parent_->set_distance_resolution(state); +} + +} // namespace ld2410 +} // namespace esphome diff --git a/esphome/components/ld2410/select/distance_resolution_select.h b/esphome/components/ld2410/select/distance_resolution_select.h new file mode 100644 index 0000000000..d6affb1020 --- /dev/null +++ b/esphome/components/ld2410/select/distance_resolution_select.h @@ -0,0 +1,18 @@ +#pragma once + +#include "esphome/components/select/select.h" +#include "../ld2410.h" + +namespace esphome { +namespace ld2410 { + +class DistanceResolutionSelect : public select::Select, public Parented { + public: + DistanceResolutionSelect() = default; + + protected: + void control(const std::string &value) override; +}; + +} // namespace ld2410 +} // namespace esphome diff --git a/esphome/components/ld2410/select/light_out_control_select.cpp b/esphome/components/ld2410/select/light_out_control_select.cpp new file mode 100644 index 0000000000..ac23248a64 --- /dev/null +++ b/esphome/components/ld2410/select/light_out_control_select.cpp @@ -0,0 +1,12 @@ +#include "light_out_control_select.h" + +namespace esphome { +namespace ld2410 { + +void LightOutControlSelect::control(const std::string &value) { + this->publish_state(value); + this->parent_->set_light_out_control(); +} + +} // namespace ld2410 +} // namespace esphome diff --git a/esphome/components/ld2410/select/light_out_control_select.h b/esphome/components/ld2410/select/light_out_control_select.h new file mode 100644 index 0000000000..5d72e1774e --- /dev/null +++ b/esphome/components/ld2410/select/light_out_control_select.h @@ -0,0 +1,18 @@ +#pragma once + +#include "esphome/components/select/select.h" +#include "../ld2410.h" + +namespace esphome { +namespace ld2410 { + +class LightOutControlSelect : public select::Select, public Parented { + public: + LightOutControlSelect() = default; + + protected: + void control(const std::string &value) override; +}; + +} // namespace ld2410 +} // namespace esphome diff --git a/esphome/components/ld2410/sensor.py b/esphome/components/ld2410/sensor.py index b941263134..83361db58a 100644 --- a/esphome/components/ld2410/sensor.py +++ b/esphome/components/ld2410/sensor.py @@ -3,9 +3,15 @@ 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, + CONF_LIGHT, + DEVICE_CLASS_ILLUMINANCE, + ENTITY_CATEGORY_DIAGNOSTIC, + ICON_SIGNAL, + ICON_FLASH, + ICON_MOTION_SENSOR, + ICON_LIGHTBULB, ) from . import CONF_LD2410_ID, LD2410Component @@ -15,41 +21,88 @@ CONF_STILL_DISTANCE = "still_distance" CONF_MOVING_ENERGY = "moving_energy" CONF_STILL_ENERGY = "still_energy" CONF_DETECTION_DISTANCE = "detection_distance" +CONF_MOVE_ENERGY = "move_energy" -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 - ), -} +CONFIG_SCHEMA = cv.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, + icon=ICON_SIGNAL, + ), + cv.Optional(CONF_STILL_DISTANCE): sensor.sensor_schema( + device_class=DEVICE_CLASS_DISTANCE, + unit_of_measurement=UNIT_CENTIMETER, + icon=ICON_SIGNAL, + ), + cv.Optional(CONF_MOVING_ENERGY): sensor.sensor_schema( + unit_of_measurement=UNIT_PERCENT, + icon=ICON_MOTION_SENSOR, + ), + cv.Optional(CONF_STILL_ENERGY): sensor.sensor_schema( + unit_of_measurement=UNIT_PERCENT, + icon=ICON_FLASH, + ), + cv.Optional(CONF_LIGHT): sensor.sensor_schema( + device_class=DEVICE_CLASS_ILLUMINANCE, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + icon=ICON_LIGHTBULB, + ), + cv.Optional(CONF_DETECTION_DISTANCE): sensor.sensor_schema( + device_class=DEVICE_CLASS_DISTANCE, + unit_of_measurement=UNIT_CENTIMETER, + icon=ICON_SIGNAL, + ), + } +) + +CONFIG_SCHEMA = CONFIG_SCHEMA.extend( + { + cv.Optional(f"g{x}"): cv.Schema( + { + cv.Optional(CONF_MOVE_ENERGY): sensor.sensor_schema( + unit_of_measurement=UNIT_PERCENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + icon=ICON_MOTION_SENSOR, + ), + cv.Optional(CONF_STILL_ENERGY): sensor.sensor_schema( + unit_of_measurement=UNIT_PERCENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + icon=ICON_FLASH, + ), + } + ) + for x in range(9) + } +) 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]) + if moving_distance_config := config.get(CONF_MOVING_DISTANCE): + sens = await sensor.new_sensor(moving_distance_config) 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]) + if still_distance_config := config.get(CONF_STILL_DISTANCE): + sens = await sensor.new_sensor(still_distance_config) 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]) + if moving_energy_config := config.get(CONF_MOVING_ENERGY): + sens = await sensor.new_sensor(moving_energy_config) 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]) + if still_energy_config := config.get(CONF_STILL_ENERGY): + sens = await sensor.new_sensor(still_energy_config) 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]) + if light_config := config.get(CONF_LIGHT): + sens = await sensor.new_sensor(light_config) + cg.add(ld2410_component.set_light_sensor(sens)) + if detection_distance_config := config.get(CONF_DETECTION_DISTANCE): + sens = await sensor.new_sensor(detection_distance_config) cg.add(ld2410_component.set_detection_distance_sensor(sens)) + for x in range(9): + if gate_conf := config.get(f"g{x}"): + if move_config := gate_conf.get(CONF_MOVE_ENERGY): + sens = await sensor.new_sensor(move_config) + cg.add(ld2410_component.set_gate_move_sensor(x, sens)) + if still_config := gate_conf.get(CONF_STILL_ENERGY): + sens = await sensor.new_sensor(still_config) + cg.add(ld2410_component.set_gate_still_sensor(x, sens)) diff --git a/esphome/components/ld2410/switch/__init__.py b/esphome/components/ld2410/switch/__init__.py new file mode 100644 index 0000000000..096cb5f67a --- /dev/null +++ b/esphome/components/ld2410/switch/__init__.py @@ -0,0 +1,44 @@ +import esphome.codegen as cg +from esphome.components import switch +import esphome.config_validation as cv +from esphome.const import ( + DEVICE_CLASS_SWITCH, + ICON_BLUETOOTH, + ENTITY_CATEGORY_CONFIG, + ICON_PULSE, +) +from .. import CONF_LD2410_ID, LD2410Component, ld2410_ns + +BluetoothSwitch = ld2410_ns.class_("BluetoothSwitch", switch.Switch) +EngineeringModeSwitch = ld2410_ns.class_("EngineeringModeSwitch", switch.Switch) + +CONF_ENGINEERING_MODE = "engineering_mode" +CONF_BLUETOOTH = "bluetooth" + +CONFIG_SCHEMA = { + cv.GenerateID(CONF_LD2410_ID): cv.use_id(LD2410Component), + cv.Optional(CONF_ENGINEERING_MODE): switch.switch_schema( + EngineeringModeSwitch, + device_class=DEVICE_CLASS_SWITCH, + entity_category=ENTITY_CATEGORY_CONFIG, + icon=ICON_PULSE, + ), + cv.Optional(CONF_BLUETOOTH): switch.switch_schema( + BluetoothSwitch, + device_class=DEVICE_CLASS_SWITCH, + entity_category=ENTITY_CATEGORY_CONFIG, + icon=ICON_BLUETOOTH, + ), +} + + +async def to_code(config): + ld2410_component = await cg.get_variable(config[CONF_LD2410_ID]) + if engineering_mode_config := config.get(CONF_ENGINEERING_MODE): + s = await switch.new_switch(engineering_mode_config) + await cg.register_parented(s, config[CONF_LD2410_ID]) + cg.add(ld2410_component.set_engineering_mode_switch(s)) + if bluetooth_config := config.get(CONF_BLUETOOTH): + s = await switch.new_switch(bluetooth_config) + await cg.register_parented(s, config[CONF_LD2410_ID]) + cg.add(ld2410_component.set_bluetooth_switch(s)) diff --git a/esphome/components/ld2410/switch/bluetooth_switch.cpp b/esphome/components/ld2410/switch/bluetooth_switch.cpp new file mode 100644 index 0000000000..9bcee9b049 --- /dev/null +++ b/esphome/components/ld2410/switch/bluetooth_switch.cpp @@ -0,0 +1,12 @@ +#include "bluetooth_switch.h" + +namespace esphome { +namespace ld2410 { + +void BluetoothSwitch::write_state(bool state) { + this->publish_state(state); + this->parent_->set_bluetooth(state); +} + +} // namespace ld2410 +} // namespace esphome diff --git a/esphome/components/ld2410/switch/bluetooth_switch.h b/esphome/components/ld2410/switch/bluetooth_switch.h new file mode 100644 index 0000000000..35ae1ec0c9 --- /dev/null +++ b/esphome/components/ld2410/switch/bluetooth_switch.h @@ -0,0 +1,18 @@ +#pragma once + +#include "esphome/components/switch/switch.h" +#include "../ld2410.h" + +namespace esphome { +namespace ld2410 { + +class BluetoothSwitch : public switch_::Switch, public Parented { + public: + BluetoothSwitch() = default; + + protected: + void write_state(bool state) override; +}; + +} // namespace ld2410 +} // namespace esphome diff --git a/esphome/components/ld2410/switch/engineering_mode_switch.cpp b/esphome/components/ld2410/switch/engineering_mode_switch.cpp new file mode 100644 index 0000000000..967c87c887 --- /dev/null +++ b/esphome/components/ld2410/switch/engineering_mode_switch.cpp @@ -0,0 +1,12 @@ +#include "engineering_mode_switch.h" + +namespace esphome { +namespace ld2410 { + +void EngineeringModeSwitch::write_state(bool state) { + this->publish_state(state); + this->parent_->set_engineering_mode(state); +} + +} // namespace ld2410 +} // namespace esphome diff --git a/esphome/components/ld2410/switch/engineering_mode_switch.h b/esphome/components/ld2410/switch/engineering_mode_switch.h new file mode 100644 index 0000000000..e521200cd6 --- /dev/null +++ b/esphome/components/ld2410/switch/engineering_mode_switch.h @@ -0,0 +1,18 @@ +#pragma once + +#include "esphome/components/switch/switch.h" +#include "../ld2410.h" + +namespace esphome { +namespace ld2410 { + +class EngineeringModeSwitch : public switch_::Switch, public Parented { + public: + EngineeringModeSwitch() = default; + + protected: + void write_state(bool state) override; +}; + +} // namespace ld2410 +} // namespace esphome diff --git a/esphome/components/ld2410/text_sensor.py b/esphome/components/ld2410/text_sensor.py new file mode 100644 index 0000000000..d64472a7d3 --- /dev/null +++ b/esphome/components/ld2410/text_sensor.py @@ -0,0 +1,33 @@ +import esphome.codegen as cg +from esphome.components import text_sensor +import esphome.config_validation as cv +from esphome.const import ( + ENTITY_CATEGORY_DIAGNOSTIC, + CONF_VERSION, + CONF_MAC_ADDRESS, + ICON_BLUETOOTH, + ICON_CHIP, +) +from . import CONF_LD2410_ID, LD2410Component + +DEPENDENCIES = ["ld2410"] + +CONFIG_SCHEMA = { + cv.GenerateID(CONF_LD2410_ID): cv.use_id(LD2410Component), + cv.Optional(CONF_VERSION): text_sensor.text_sensor_schema( + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, icon=ICON_CHIP + ), + cv.Optional(CONF_MAC_ADDRESS): text_sensor.text_sensor_schema( + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, icon=ICON_BLUETOOTH + ), +} + + +async def to_code(config): + ld2410_component = await cg.get_variable(config[CONF_LD2410_ID]) + if version_config := config.get(CONF_VERSION): + sens = await text_sensor.new_text_sensor(version_config) + cg.add(ld2410_component.set_version_text_sensor(sens)) + if mac_address_config := config.get(CONF_MAC_ADDRESS): + sens = await text_sensor.new_text_sensor(mac_address_config) + cg.add(ld2410_component.set_mac_text_sensor(sens)) diff --git a/tests/test1.yaml b/tests/test1.yaml index 4eb78515c9..66caad961a 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -170,6 +170,9 @@ mqtt: id: uart_0 data: !lambda |- return {}; + - bluetooth_password.set: + id: my_ld2410 + password: abcdef on_connect: - light.turn_on: ${roomname}_lights - mqtt.publish: @@ -1333,16 +1336,64 @@ sensor: speed: name: "Radiator Pump Speed" - platform: ld2410 + light: + name: light moving_distance: name: "Moving distance (cm)" still_distance: name: "Still Distance (cm)" moving_energy: - name: "Move Energy" + name: "Move Energy (%)" still_energy: - name: "Still Energy" + name: "Still Energy (%)" detection_distance: - name: "Distance Detection" + name: "Distance Detection (cm)" + g0: + move_energy: + name: g0 move energy + still_energy: + name: g0 still energy + g1: + move_energy: + name: g1 move energy + still_energy: + name: g1 still energy + g2: + move_energy: + name: g2 move energy + still_energy: + name: g2 still energy + g3: + move_energy: + name: g3 move energy + still_energy: + name: g3 still energy + g4: + move_energy: + name: g4 move energy + still_energy: + name: g4 still energy + g5: + move_energy: + name: g5 move energy + still_energy: + name: g5 still energy + g6: + move_energy: + name: g6 move energy + still_energy: + name: g6 still energy + g7: + move_energy: + name: g7 move energy + still_energy: + name: g7 still energy + g8: + move_energy: + name: g8 move energy + still_energy: + name: g8 still energy + - platform: sen21231 name: "Person Sensor" i2c_id: i2c_bus @@ -1684,6 +1735,8 @@ binary_sensor: name: movement has_still_target: name: still + out_pin_presence_status: + name: out pin presence status pca9685: frequency: 500 @@ -2626,6 +2679,11 @@ switch: id: outlet_switch optimistic: true device_class: outlet + - platform: ld2410 + engineering_mode: + name: "control ld2410 engineering mode" + bluetooth: + name: "control ld2410 bluetooth" fan: - platform: binary @@ -3207,6 +3265,11 @@ text_sensor: tag_name: OPTARIF name: optarif teleinfo_id: myteleinfo + - platform: ld2410 + version: + name: "presenece sensor version" + mac_address: + name: "presenece sensor mac address" sn74hc595: - id: sn74hc595_hub @@ -3311,6 +3374,61 @@ number: step: 1 max_value: 10 optimistic: true + - platform: ld2410 + light_threshold: + name: light threshold + timeout: + name: timeout + max_move_distance_gate: + name: max move distance gate + max_still_distance_gate: + name: max still distance gate + g0: + move_threshold: + name: g0 move threshold + still_threshold: + name: g0 still threshold + g1: + move_threshold: + name: g1 move threshold + still_threshold: + name: g1 still threshold + g2: + move_threshold: + name: g2 move threshold + still_threshold: + name: g2 still threshold + g3: + move_threshold: + name: g3 move threshold + still_threshold: + name: g3 still threshold + g4: + move_threshold: + name: g4 move threshold + still_threshold: + name: g4 still threshold + g5: + move_threshold: + name: g5 move threshold + still_threshold: + name: g5 still threshold + g6: + move_threshold: + name: g6 move threshold + still_threshold: + name: g6 still threshold + g7: + move_threshold: + name: g7 move threshold + still_threshold: + name: g7 still threshold + g8: + move_threshold: + name: g8 move threshold + still_threshold: + name: g8 still threshold + select: - platform: template @@ -3324,6 +3442,15 @@ select: - platform: copy source_id: test_select name: Test Select Copy + - platform: ld2410 + distance_resolution: + name: distance resolution + baud_rate: + name: baud rate + light_function: + name: light function + out_pin_level: + name: out ping level qr_code: - id: homepage_qr @@ -3386,19 +3513,17 @@ button: name: Midea Power Inverse on_press: midea_ac.power_toggle: + - platform: ld2410 + factory_reset: + name: "factory reset" + restart: + name: "restart" + query_params: + name: query params ld2410: id: my_ld2410 uart_id: ld2410_uart - timeout: 150s - max_move_distance: 6m - max_still_distance: 0.75m - g0_move_threshold: 10 - g0_still_threshold: 20 - g2_move_threshold: 20 - g2_still_threshold: 21 - g8_move_threshold: 80 - g8_still_threshold: 81 lcd_menu: display_id: my_lcd_gpio From 5f99ed943ae81892e62ea1a542e3d48c1e1cf100 Mon Sep 17 00:00:00 2001 From: Pierre Gordon <16200219+pierlon@users.noreply.github.com> Date: Wed, 16 Aug 2023 20:22:04 -0400 Subject: [PATCH 018/111] Add `libfreetype-dev` Debian package for armv7 Docker builds (#5262) --- docker/Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index e1f3c46a3e..4aaea9da89 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -35,7 +35,8 @@ RUN \ python3-dev=3.9.2-3 \ zlib1g-dev=1:1.2.11.dfsg-2+deb11u2 \ libjpeg-dev=1:2.0.6-4 \ - libcairo2=1.16.0-5; \ + libcairo2=1.16.0-5 \ + libfreetype-dev=2.10.4+dfsg-1+deb11u1; \ fi; \ rm -rf \ /tmp/* \ From 63fc16d8722ca913b6def0eda53fbb6d15197338 Mon Sep 17 00:00:00 2001 From: Jimmy Hedman Date: Thu, 17 Aug 2023 02:22:37 +0200 Subject: [PATCH 019/111] Add delay before enabling ipv6 (#5256) --- esphome/components/wifi/wifi_component_esp32_arduino.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/wifi/wifi_component_esp32_arduino.cpp b/esphome/components/wifi/wifi_component_esp32_arduino.cpp index 995e5e587e..5bfb6bb9a8 100644 --- a/esphome/components/wifi/wifi_component_esp32_arduino.cpp +++ b/esphome/components/wifi/wifi_component_esp32_arduino.cpp @@ -486,7 +486,7 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ ESP_LOGV(TAG, "Event: Connected ssid='%s' bssid=" LOG_SECRET("%s") " channel=%u, authmode=%s", buf, format_mac_addr(it.bssid).c_str(), it.channel, get_auth_mode_str(it.authmode)); #if LWIP_IPV6 - WiFi.enableIpV6(); + this->set_timeout(100, [] { WiFi.enableIpV6(); }); #endif /* LWIP_IPV6 */ break; From c5be5e6d12316035b70175bed1a2cae5be0dc41b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 17 Aug 2023 12:23:44 +1200 Subject: [PATCH 020/111] Bump zeroconf from 0.74.0 to 0.80.0 (#5260) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 6330a0996e..a1f73d930a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,7 +11,7 @@ esptool==4.6.2 click==8.1.6 esphome-dashboard==20230711.0 aioesphomeapi==15.0.0 -zeroconf==0.74.0 +zeroconf==0.80.0 # esp-idf requires this, but doesn't bundle it by default # https://github.com/espressif/esp-idf/blob/220590d599e134d7a5e7f1e683cc4550349ffbf8/requirements.txt#L24 From 9fc50e8dbc287f1ea5bed9acbaff612cb196998f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 17 Aug 2023 19:41:24 +0000 Subject: [PATCH 021/111] Bump click from 8.1.6 to 8.1.7 (#5272) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index a1f73d930a..4a046014d6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,7 +8,7 @@ tzdata>=2021.1 # from time pyserial==3.5 platformio==6.1.9 # When updating platformio, also update Dockerfile esptool==4.6.2 -click==8.1.6 +click==8.1.7 esphome-dashboard==20230711.0 aioesphomeapi==15.0.0 zeroconf==0.80.0 From 2b4ed0c2738cae095f5803d539d1a1d0d4a738a9 Mon Sep 17 00:00:00 2001 From: Mat931 <49403702+Mat931@users.noreply.github.com> Date: Thu, 17 Aug 2023 19:57:18 +0000 Subject: [PATCH 022/111] Fix checksum calculation for sml (#5271) --- esphome/components/sml/sml.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/sml/sml.cpp b/esphome/components/sml/sml.cpp index 87dc25c220..921623d4fd 100644 --- a/esphome/components/sml/sml.cpp +++ b/esphome/components/sml/sml.cpp @@ -100,14 +100,14 @@ bool check_sml_data(const bytes &buffer) { } uint16_t crc_received = (buffer.at(buffer.size() - 2) << 8) | buffer.at(buffer.size() - 1); - uint16_t crc_calculated = crc16(buffer.data(), buffer.size(), 0x6e23, 0x8408, true, true); + uint16_t crc_calculated = crc16(buffer.data(), buffer.size() - 2, 0x6e23, 0x8408, true, true); crc_calculated = (crc_calculated >> 8) | (crc_calculated << 8); if (crc_received == crc_calculated) { ESP_LOGV(TAG, "Checksum verification successful with CRC16/X25."); return true; } - crc_calculated = crc16(buffer.data(), buffer.size(), 0xed50, 0x8408); + crc_calculated = crc16(buffer.data(), buffer.size() - 2, 0xed50, 0x8408); if (crc_received == crc_calculated) { ESP_LOGV(TAG, "Checksum verification successful with CRC16/KERMIT."); return true; From 0af8d0b7eac67807749c16b2eea2b7d98debdd56 Mon Sep 17 00:00:00 2001 From: Jimmy Hedman Date: Thu, 17 Aug 2023 22:02:57 +0200 Subject: [PATCH 023/111] Remove support for ESP-IDF version < 4 (#5261) --- esphome/components/debug/debug_component.cpp | 4 -- esphome/components/esp32/core.cpp | 12 +--- esphome/components/esp32_touch/esp32_touch.h | 4 -- .../components/socket/bsd_sockets_impl.cpp | 41 +----------- esphome/components/wifi/wifi_component.h | 4 -- .../wifi/wifi_component_esp32_arduino.cpp | 63 ------------------- .../wifi/wifi_component_esp_idf.cpp | 4 -- 7 files changed, 3 insertions(+), 129 deletions(-) diff --git a/esphome/components/debug/debug_component.cpp b/esphome/components/debug/debug_component.cpp index 8b6a97068b..baf537a12b 100644 --- a/esphome/components/debug/debug_component.cpp +++ b/esphome/components/debug/debug_component.cpp @@ -12,12 +12,8 @@ #include #include -#if ESP_IDF_VERSION_MAJOR >= 4 #include #include -#else -#include -#endif #endif // USE_ESP32 diff --git a/esphome/components/esp32/core.cpp b/esphome/components/esp32/core.cpp index 512a8857b6..16aa93c232 100644 --- a/esphome/components/esp32/core.cpp +++ b/esphome/components/esp32/core.cpp @@ -10,9 +10,7 @@ #include #include -#if ESP_IDF_VERSION_MAJOR >= 4 #include -#endif #ifdef USE_ARDUINO #include @@ -55,15 +53,7 @@ void arch_init() { void IRAM_ATTR HOT arch_feed_wdt() { esp_task_wdt_reset(); } uint8_t progmem_read_byte(const uint8_t *addr) { return *addr; } -uint32_t arch_get_cpu_cycle_count() { -#if ESP_IDF_VERSION_MAJOR >= 4 - return cpu_hal_get_cycle_count(); -#else - uint32_t ccount; - __asm__ __volatile__("esync; rsr %0,ccount" : "=a"(ccount)); - return ccount; -#endif -} +uint32_t arch_get_cpu_cycle_count() { return cpu_hal_get_cycle_count(); } uint32_t arch_get_cpu_freq_hz() { return rtc_clk_apb_freq_get(); } #ifdef USE_ESP_IDF diff --git a/esphome/components/esp32_touch/esp32_touch.h b/esphome/components/esp32_touch/esp32_touch.h index 0ba7ed6255..0eac590ce7 100644 --- a/esphome/components/esp32_touch/esp32_touch.h +++ b/esphome/components/esp32_touch/esp32_touch.h @@ -8,11 +8,7 @@ #include -#if ESP_IDF_VERSION_MAJOR >= 4 #include -#else -#include -#endif namespace esphome { namespace esp32_touch { diff --git a/esphome/components/socket/bsd_sockets_impl.cpp b/esphome/components/socket/bsd_sockets_impl.cpp index 2dea4af277..5d44cd7689 100644 --- a/esphome/components/socket/bsd_sockets_impl.cpp +++ b/esphome/components/socket/bsd_sockets_impl.cpp @@ -87,25 +87,7 @@ class BSDSocketImpl : public Socket { int listen(int backlog) override { return ::listen(fd_, backlog); } ssize_t read(void *buf, size_t len) override { return ::read(fd_, buf, len); } ssize_t readv(const struct iovec *iov, int iovcnt) override { -#if defined(USE_ESP32) && ESP_IDF_VERSION_MAJOR < 4 - // esp-idf v3 doesn't have readv, emulate it - ssize_t ret = 0; - for (int i = 0; i < iovcnt; i++) { - ssize_t err = this->read(reinterpret_cast(iov[i].iov_base), iov[i].iov_len); - if (err == -1) { - if (ret != 0) { - // if we already read some don't return an error - break; - } - return err; - } - ret += err; - if (err != iov[i].iov_len) - break; - } - return ret; -#elif defined(USE_ESP32) - // ESP-IDF v4 only has symbol lwip_readv +#if defined(USE_ESP32) return ::lwip_readv(fd_, iov, iovcnt); #else return ::readv(fd_, iov, iovcnt); @@ -114,26 +96,7 @@ class BSDSocketImpl : public Socket { ssize_t write(const void *buf, size_t len) override { return ::write(fd_, buf, len); } ssize_t send(void *buf, size_t len, int flags) { return ::send(fd_, buf, len, flags); } ssize_t writev(const struct iovec *iov, int iovcnt) override { -#if defined(USE_ESP32) && ESP_IDF_VERSION_MAJOR < 4 - // esp-idf v3 doesn't have writev, emulate it - ssize_t ret = 0; - for (int i = 0; i < iovcnt; i++) { - ssize_t err = - this->send(reinterpret_cast(iov[i].iov_base), iov[i].iov_len, i == iovcnt - 1 ? 0 : MSG_MORE); - if (err == -1) { - if (ret != 0) { - // if we already wrote some don't return an error - break; - } - return err; - } - ret += err; - if (err != iov[i].iov_len) - break; - } - return ret; -#elif defined(USE_ESP32) - // ESP-IDF v4 only has symbol lwip_writev +#if defined(USE_ESP32) return ::lwip_writev(fd_, iov, iovcnt); #else return ::writev(fd_, iov, iovcnt); diff --git a/esphome/components/wifi/wifi_component.h b/esphome/components/wifi/wifi_component.h index d39b062990..c17246fd00 100644 --- a/esphome/components/wifi/wifi_component.h +++ b/esphome/components/wifi/wifi_component.h @@ -324,11 +324,7 @@ class WiFiComponent : public Component { #endif #ifdef USE_ESP32_FRAMEWORK_ARDUINO -#if ESP_IDF_VERSION_MAJOR >= 4 void wifi_event_callback_(arduino_event_id_t event, arduino_event_info_t info); -#else - void wifi_event_callback_(system_event_id_t event, system_event_info_t info); -#endif void wifi_scan_done_callback_(); #endif #ifdef USE_ESP_IDF diff --git a/esphome/components/wifi/wifi_component_esp32_arduino.cpp b/esphome/components/wifi/wifi_component_esp32_arduino.cpp index 5bfb6bb9a8..95f4e2ce92 100644 --- a/esphome/components/wifi/wifi_component_esp32_arduino.cpp +++ b/esphome/components/wifi/wifi_component_esp32_arduino.cpp @@ -203,12 +203,10 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { // Units: AP beacon intervals. Defaults to 3 if set to 0. conf.sta.listen_interval = 0; -#if ESP_IDF_VERSION_MAJOR >= 4 // Protected Management Frame // Device will prefer to connect in PMF mode if other device also advertises PMF capability. conf.sta.pmf_cfg.capable = true; conf.sta.pmf_cfg.required = false; -#endif // note, we do our own filtering // The minimum rssi to accept in the fast scan mode @@ -314,11 +312,7 @@ const char *get_auth_mode_str(uint8_t mode) { } } -#if ESP_IDF_VERSION_MAJOR >= 4 using esphome_ip4_addr_t = esp_ip4_addr_t; -#else -using esphome_ip4_addr_t = ip4_addr_t; -#endif std::string format_ip4_addr(const esphome_ip4_addr_t &ip) { char buf[20]; @@ -404,8 +398,6 @@ const char *get_disconnect_reason_str(uint8_t reason) { } } -#if ESP_IDF_VERSION_MAJOR >= 4 - #define ESPHOME_EVENT_ID_WIFI_READY ARDUINO_EVENT_WIFI_READY #define ESPHOME_EVENT_ID_WIFI_SCAN_DONE ARDUINO_EVENT_WIFI_SCAN_DONE #define ESPHOME_EVENT_ID_WIFI_STA_START ARDUINO_EVENT_WIFI_STA_START @@ -426,28 +418,6 @@ const char *get_disconnect_reason_str(uint8_t reason) { using esphome_wifi_event_id_t = arduino_event_id_t; using esphome_wifi_event_info_t = arduino_event_info_t; -#else // ESP_IDF_VERSION_MAJOR >= 4 - -#define ESPHOME_EVENT_ID_WIFI_READY SYSTEM_EVENT_WIFI_READY -#define ESPHOME_EVENT_ID_WIFI_SCAN_DONE SYSTEM_EVENT_SCAN_DONE -#define ESPHOME_EVENT_ID_WIFI_STA_START SYSTEM_EVENT_STA_START -#define ESPHOME_EVENT_ID_WIFI_STA_STOP SYSTEM_EVENT_STA_STOP -#define ESPHOME_EVENT_ID_WIFI_STA_CONNECTED SYSTEM_EVENT_STA_CONNECTED -#define ESPHOME_EVENT_ID_WIFI_STA_DISCONNECTED SYSTEM_EVENT_STA_DISCONNECTED -#define ESPHOME_EVENT_ID_WIFI_STA_AUTHMODE_CHANGE SYSTEM_EVENT_STA_AUTHMODE_CHANGE -#define ESPHOME_EVENT_ID_WIFI_STA_GOT_IP SYSTEM_EVENT_STA_GOT_IP -#define ESPHOME_EVENT_ID_WIFI_STA_LOST_IP SYSTEM_EVENT_STA_LOST_IP -#define ESPHOME_EVENT_ID_WIFI_AP_START SYSTEM_EVENT_AP_START -#define ESPHOME_EVENT_ID_WIFI_AP_STOP SYSTEM_EVENT_AP_STOP -#define ESPHOME_EVENT_ID_WIFI_AP_STACONNECTED SYSTEM_EVENT_AP_STACONNECTED -#define ESPHOME_EVENT_ID_WIFI_AP_STADISCONNECTED SYSTEM_EVENT_AP_STADISCONNECTED -#define ESPHOME_EVENT_ID_WIFI_AP_STAIPASSIGNED SYSTEM_EVENT_AP_STAIPASSIGNED -#define ESPHOME_EVENT_ID_WIFI_AP_PROBEREQRECVED SYSTEM_EVENT_AP_PROBEREQRECVED -using esphome_wifi_event_id_t = system_event_id_t; -using esphome_wifi_event_info_t = system_event_info_t; - -#endif // !(ESP_IDF_VERSION_MAJOR >= 4) - void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_wifi_event_info_t info) { switch (event) { case ESPHOME_EVENT_ID_WIFI_READY: { @@ -455,11 +425,7 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ break; } case ESPHOME_EVENT_ID_WIFI_SCAN_DONE: { -#if ESP_IDF_VERSION_MAJOR >= 4 auto it = info.wifi_scan_done; -#else - auto it = info.scan_done; -#endif ESP_LOGV(TAG, "Event: WiFi Scan Done status=%u number=%u scan_id=%u", it.status, it.number, it.scan_id); this->wifi_scan_done_callback_(); @@ -475,11 +441,7 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ break; } case ESPHOME_EVENT_ID_WIFI_STA_CONNECTED: { -#if ESP_IDF_VERSION_MAJOR >= 4 auto it = info.wifi_sta_connected; -#else - auto it = info.connected; -#endif char buf[33]; memcpy(buf, it.ssid, it.ssid_len); buf[it.ssid_len] = '\0'; @@ -492,11 +454,7 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ break; } case ESPHOME_EVENT_ID_WIFI_STA_DISCONNECTED: { -#if ESP_IDF_VERSION_MAJOR >= 4 auto it = info.wifi_sta_disconnected; -#else - auto it = info.disconnected; -#endif char buf[33]; memcpy(buf, it.ssid, it.ssid_len); buf[it.ssid_len] = '\0'; @@ -522,11 +480,7 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ break; } case ESPHOME_EVENT_ID_WIFI_STA_AUTHMODE_CHANGE: { -#if ESP_IDF_VERSION_MAJOR >= 4 auto it = info.wifi_sta_authmode_change; -#else - auto it = info.auth_change; -#endif ESP_LOGV(TAG, "Event: Authmode Change old=%s new=%s", get_auth_mode_str(it.old_mode), get_auth_mode_str(it.new_mode)); // Mitigate CVE-2020-12638 @@ -570,24 +524,14 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ break; } case ESPHOME_EVENT_ID_WIFI_AP_STACONNECTED: { -#if ESP_IDF_VERSION_MAJOR >= 4 auto it = info.wifi_sta_connected; auto &mac = it.bssid; -#else - auto it = info.sta_connected; - auto &mac = it.mac; -#endif ESP_LOGV(TAG, "Event: AP client connected MAC=%s", format_mac_addr(mac).c_str()); break; } case ESPHOME_EVENT_ID_WIFI_AP_STADISCONNECTED: { -#if ESP_IDF_VERSION_MAJOR >= 4 auto it = info.wifi_sta_disconnected; auto &mac = it.bssid; -#else - auto it = info.sta_disconnected; - auto &mac = it.mac; -#endif ESP_LOGV(TAG, "Event: AP client disconnected MAC=%s", format_mac_addr(mac).c_str()); break; } @@ -596,11 +540,7 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ break; } case ESPHOME_EVENT_ID_WIFI_AP_PROBEREQRECVED: { -#if ESP_IDF_VERSION_MAJOR >= 4 auto it = info.wifi_ap_probereqrecved; -#else - auto it = info.ap_probereqrecved; -#endif ESP_LOGVV(TAG, "Event: AP receive Probe Request MAC=%s RSSI=%d", format_mac_addr(it.mac).c_str(), it.rssi); break; } @@ -742,10 +682,7 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) { strncpy(reinterpret_cast(conf.ap.password), ap.get_password().c_str(), sizeof(conf.ap.ssid)); } -#if ESP_IDF_VERSION_MAJOR >= 4 - // pairwise cipher of SoftAP, group cipher will be derived using this. conf.ap.pairwise_cipher = WIFI_CIPHER_TYPE_CCMP; -#endif esp_err_t err = esp_wifi_set_config(WIFI_IF_AP, &conf); if (err != ESP_OK) { diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp index e9d74116cf..9041679ccf 100644 --- a/esphome/components/wifi/wifi_component_esp_idf.cpp +++ b/esphome/components/wifi/wifi_component_esp_idf.cpp @@ -312,12 +312,10 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { // Units: AP beacon intervals. Defaults to 3 if set to 0. conf.sta.listen_interval = 0; -#if ESP_IDF_VERSION_MAJOR >= 4 // Protected Management Frame // Device will prefer to connect in PMF mode if other device also advertises PMF capability. conf.sta.pmf_cfg.capable = true; conf.sta.pmf_cfg.required = false; -#endif // note, we do our own filtering // The minimum rssi to accept in the fast scan mode @@ -838,10 +836,8 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) { strncpy(reinterpret_cast(conf.ap.password), ap.get_password().c_str(), sizeof(conf.ap.password)); } -#if ESP_IDF_VERSION_MAJOR >= 4 // pairwise cipher of SoftAP, group cipher will be derived using this. conf.ap.pairwise_cipher = WIFI_CIPHER_TYPE_CCMP; -#endif esp_err_t err = esp_wifi_set_config(WIFI_IF_AP, &conf); if (err != ESP_OK) { From c11c4dad2f4721d52c3f7eb895c79154393f3570 Mon Sep 17 00:00:00 2001 From: SeByDocKy Date: Thu, 17 Aug 2023 22:03:39 +0200 Subject: [PATCH 024/111] Add pmwcs3 capacitive soil moisture & temperature sensor component (#4624) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/pmwcs3/__init__.py | 1 + esphome/components/pmwcs3/pmwcs3.cpp | 115 +++++++++++++++++++++ esphome/components/pmwcs3/pmwcs3.h | 70 +++++++++++++ esphome/components/pmwcs3/sensor.py | 140 ++++++++++++++++++++++++++ tests/test1.yaml | 10 ++ 6 files changed, 337 insertions(+) create mode 100644 esphome/components/pmwcs3/__init__.py create mode 100644 esphome/components/pmwcs3/pmwcs3.cpp create mode 100644 esphome/components/pmwcs3/pmwcs3.h create mode 100644 esphome/components/pmwcs3/sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index f50700c502..0cea8d48ba 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -212,6 +212,7 @@ esphome/components/pid/* @OttoWinter esphome/components/pipsolar/* @andreashergert1984 esphome/components/pm1006/* @habbie esphome/components/pmsa003i/* @sjtrny +esphome/components/pmwcs3/* @SeByDocKy esphome/components/pn532/* @OttoWinter @jesserockz esphome/components/pn532_i2c/* @OttoWinter @jesserockz esphome/components/pn532_spi/* @OttoWinter @jesserockz diff --git a/esphome/components/pmwcs3/__init__.py b/esphome/components/pmwcs3/__init__.py new file mode 100644 index 0000000000..b04c30a005 --- /dev/null +++ b/esphome/components/pmwcs3/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@SeByDocKy"] diff --git a/esphome/components/pmwcs3/pmwcs3.cpp b/esphome/components/pmwcs3/pmwcs3.cpp new file mode 100644 index 0000000000..812018b52e --- /dev/null +++ b/esphome/components/pmwcs3/pmwcs3.cpp @@ -0,0 +1,115 @@ +#include "pmwcs3.h" +#include "esphome/core/hal.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace pmwcs3 { + +static const uint8_t PMWCS3_I2C_ADDRESS = 0x63; +static const uint8_t PMWCS3_REG_READ_START = 0x01; +static const uint8_t PMWCS3_REG_READ_E25 = 0x02; +static const uint8_t PMWCS3_REG_READ_EC = 0x03; +static const uint8_t PMWCS3_REG_READ_TEMP = 0x04; +static const uint8_t PMWCS3_REG_READ_VWC = 0x05; +static const uint8_t PMWCS3_REG_CALIBRATE_AIR = 0x06; +static const uint8_t PMWCS3_REG_CALIBRATE_WATER = 0x07; +static const uint8_t PMWCS3_SET_I2C_ADDRESS = 0x08; +static const uint8_t PMWCS3_REG_GET_DATA = 0x09; +static const uint8_t PMWCS3_REG_CALIBRATE_EC = 0x10; +static const uint8_t PMWCS3_REG_CAP = 0x0A; +static const uint8_t PMWCS3_REG_RES = 0x0B; +static const uint8_t PMWCS3_REG_RC = 0x0C; +static const uint8_t PMWCS3_REG_RT = 0x0D; + +static const char *const TAG = "pmwcs3"; + +void PMWCS3Component::new_i2c_address(uint8_t address) { + if (!this->write_byte(PMWCS3_SET_I2C_ADDRESS, address)) { + this->status_set_warning(); + ESP_LOGW(TAG, "couldn't write the new I2C address %d", address); + return; + } + this->set_i2c_address(address); // Allows device to continue working until new firmware is written with new address. + ESP_LOGVV(TAG, "changed I2C address to %d", address); + this->status_clear_warning(); +} + +void PMWCS3Component::air_calibration() { + if (!this->write_bytes(PMWCS3_REG_CALIBRATE_AIR, nullptr, 0)) { + this->status_set_warning(); + ESP_LOGW(TAG, "couldn't start air calibration"); + return; + } + ESP_LOGW(TAG, "Start air calibration during the next 300s"); +} +void PMWCS3Component::water_calibration() { + if (!this->write_bytes(PMWCS3_REG_CALIBRATE_WATER, nullptr, 0)) { + this->status_set_warning(); + ESP_LOGW(TAG, "couldn't start water calibration"); + return; + } + ESP_LOGW(TAG, "Start water calibration during the next 300s"); +} + +void PMWCS3Component::setup() { ESP_LOGCONFIG(TAG, "Setting up PMWCS3..."); } + +void PMWCS3Component::update() { this->read_data_(); } + +float PMWCS3Component::get_setup_priority() const { return setup_priority::DATA; } + +void PMWCS3Component::dump_config() { + ESP_LOGCONFIG(TAG, "PMWCS3"); + LOG_I2C_DEVICE(this); + if (this->is_failed()) { + ESP_LOGE(TAG, "Communication with PMWCS3 failed!"); + } + ESP_LOGI(TAG, "%s", this->is_failed() ? "FAILED" : "OK"); + + LOG_UPDATE_INTERVAL(this); + LOG_SENSOR(" ", "e25", this->e25_sensor_); + LOG_SENSOR(" ", "ec", this->ec_sensor_); + LOG_SENSOR(" ", "temperature", this->temperature_sensor_); + LOG_SENSOR(" ", "vwc", this->vwc_sensor_); +} +void PMWCS3Component::read_data_() { + uint8_t data[8]; + float e25, ec, temperature, vwc; + + /////// Super important !!!! first activate reading PMWCS3_REG_READ_START (if not, return always the same values) //// + + if (!this->write_bytes(PMWCS3_REG_READ_START, nullptr, 0)) { + this->status_set_warning(); + ESP_LOGVV(TAG, "Failed to write into REG_READ_START register !!!"); + return; + } + // NOLINT delay(100); + + if (!this->read_bytes(PMWCS3_REG_GET_DATA, (uint8_t *) &data, 8)) { + ESP_LOGVV(TAG, "Error reading PMWCS3_REG_GET_DATA registers"); + this->mark_failed(); + return; + } + if (this->e25_sensor_ != nullptr) { + e25 = ((data[1] << 8) | data[0]) / 100.0; + this->e25_sensor_->publish_state(e25); + ESP_LOGVV(TAG, "e25: data[0]=%d, data[1]=%d, result=%f", data[0], data[1], e25); + } + if (this->ec_sensor_ != nullptr) { + ec = ((data[3] << 8) | data[2]) / 10.0; + this->ec_sensor_->publish_state(ec); + ESP_LOGVV(TAG, "ec: data[2]=%d, data[3]=%d, result=%f", data[2], data[3], ec); + } + if (this->temperature_sensor_ != nullptr) { + temperature = ((data[5] << 8) | data[4]) / 100.0; + this->temperature_sensor_->publish_state(temperature); + ESP_LOGVV(TAG, "temp: data[4]=%d, data[5]=%d, result=%f", data[4], data[5], temperature); + } + if (this->vwc_sensor_ != nullptr) { + vwc = ((data[7] << 8) | data[6]) / 10.0; + this->vwc_sensor_->publish_state(vwc); + ESP_LOGVV(TAG, "vwc: data[6]=%d, data[7]=%d, result=%f", data[6], data[7], vwc); + } +} + +} // namespace pmwcs3 +} // namespace esphome diff --git a/esphome/components/pmwcs3/pmwcs3.h b/esphome/components/pmwcs3/pmwcs3.h new file mode 100644 index 0000000000..f3916bb868 --- /dev/null +++ b/esphome/components/pmwcs3/pmwcs3.h @@ -0,0 +1,70 @@ +#pragma once +#include "esphome/core/automation.h" +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/i2c/i2c.h" + +// ref: +// https://github.com/tinovi/i2cArduino/blob/master/i2cArduino.h + +namespace esphome { +namespace pmwcs3 { + +class PMWCS3Component : public PollingComponent, public i2c::I2CDevice { + public: + void setup() override; + void update() override; + void dump_config() override; + float get_setup_priority() const override; + + void set_e25_sensor(sensor::Sensor *e25_sensor) { e25_sensor_ = e25_sensor; } + void set_ec_sensor(sensor::Sensor *ec_sensor) { ec_sensor_ = ec_sensor; } + void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; } + void set_vwc_sensor(sensor::Sensor *vwc_sensor) { vwc_sensor_ = vwc_sensor; } + + void new_i2c_address(uint8_t newaddress); + void air_calibration(); + void water_calibration(); + + protected: + void read_data_(); + + sensor::Sensor *e25_sensor_{nullptr}; + sensor::Sensor *ec_sensor_{nullptr}; + sensor::Sensor *temperature_sensor_{nullptr}; + sensor::Sensor *vwc_sensor_{nullptr}; +}; + +template class PMWCS3AirCalibrationAction : public Action { + public: + PMWCS3AirCalibrationAction(PMWCS3Component *parent) : parent_(parent) {} + + void play(Ts... x) override { this->parent_->air_calibration(); } + + protected: + PMWCS3Component *parent_; +}; + +template class PMWCS3WaterCalibrationAction : public Action { + public: + PMWCS3WaterCalibrationAction(PMWCS3Component *parent) : parent_(parent) {} + + void play(Ts... x) override { this->parent_->water_calibration(); } + + protected: + PMWCS3Component *parent_; +}; + +template class PMWCS3NewI2cAddressAction : public Action { + public: + PMWCS3NewI2cAddressAction(PMWCS3Component *parent) : parent_(parent) {} + TEMPLATABLE_VALUE(int, new_address) + + void play(Ts... x) override { this->parent_->new_i2c_address(this->new_address_.value(x...)); } + + protected: + PMWCS3Component *parent_; +}; + +} // namespace pmwcs3 +} // namespace esphome diff --git a/esphome/components/pmwcs3/sensor.py b/esphome/components/pmwcs3/sensor.py new file mode 100644 index 0000000000..81be327d14 --- /dev/null +++ b/esphome/components/pmwcs3/sensor.py @@ -0,0 +1,140 @@ +import esphome.codegen as cg +from esphome import automation +import esphome.config_validation as cv +from esphome.components import i2c, sensor +from esphome.const import ( + CONF_ID, + CONF_ADDRESS, + CONF_TEMPERATURE, + CONF_EC, + STATE_CLASS_MEASUREMENT, + ICON_THERMOMETER, +) + +CODEOWNERS = ["@SeByDocKy"] +DEPENDENCIES = ["i2c"] + +CONF_E25 = "e25" +CONF_VWC = "vwc" + +ICON_EPSILON = "mdi:epsilon" +ICON_SIGMA = "mdi:sigma-lower" +ICON_ALPHA = "mdi:alpha-h-circle-outline" + +pmwcs3_ns = cg.esphome_ns.namespace("pmwcs3") +PMWCS3Component = pmwcs3_ns.class_( + "PMWCS3Component", cg.PollingComponent, i2c.I2CDevice +) + +# Actions +PMWCS3AirCalibrationAction = pmwcs3_ns.class_( + "PMWCS3AirCalibrationAction", automation.Action +) +PMWCS3WaterCalibrationAction = pmwcs3_ns.class_( + "PMWCS3WaterCalibrationAction", automation.Action +) +PMWCS3NewI2cAddressAction = pmwcs3_ns.class_( + "PMWCS3NewI2cAddressAction", automation.Action +) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(PMWCS3Component), + cv.Optional(CONF_E25): sensor.sensor_schema( + icon=ICON_EPSILON, + accuracy_decimals=3, + unit_of_measurement="dS/m", + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_EC): sensor.sensor_schema( + icon=ICON_SIGMA, + accuracy_decimals=2, + unit_of_measurement="mS/m", + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( + icon=ICON_THERMOMETER, + accuracy_decimals=3, + unit_of_measurement="°C", + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_VWC): sensor.sensor_schema( + icon=ICON_ALPHA, + accuracy_decimals=3, + unit_of_measurement="cm3cm−3", + state_class=STATE_CLASS_MEASUREMENT, + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x63)) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) + + if CONF_E25 in config: + sens = await sensor.new_sensor(config[CONF_E25]) + cg.add(var.set_e25_sensor(sens)) + + if CONF_EC in config: + sens = await sensor.new_sensor(config[CONF_EC]) + cg.add(var.set_ec_sensor(sens)) + + if CONF_TEMPERATURE in config: + sens = await sensor.new_sensor(config[CONF_TEMPERATURE]) + cg.add(var.set_temperature_sensor(sens)) + + if CONF_VWC in config: + sens = await sensor.new_sensor(config[CONF_VWC]) + cg.add(var.set_vwc_sensor(sens)) + + +# Actions +PMWCS3_CALIBRATION_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.use_id(PMWCS3Component), + } +) + + +@automation.register_action( + "pmwcs3.air_calibration", + PMWCS3AirCalibrationAction, + PMWCS3_CALIBRATION_SCHEMA, +) +@automation.register_action( + "pmwcs3.water_calibration", + PMWCS3WaterCalibrationAction, + PMWCS3_CALIBRATION_SCHEMA, +) +async def pmwcs3_calibration_to_code(config, action_id, template_arg, args): + parent = await cg.get_variable(config[CONF_ID]) + var = cg.new_Pvariable(action_id, template_arg, parent) + return var + + +PMWCS3_NEW_I2C_ADDRESS_SCHEMA = cv.maybe_simple_value( + { + cv.GenerateID(): cv.use_id(PMWCS3Component), + cv.Required(CONF_ADDRESS): cv.templatable(cv.i2c_address), + }, + key=CONF_ADDRESS, +) + + +@automation.register_action( + "pmwcs3.new_i2c_address", + PMWCS3NewI2cAddressAction, + PMWCS3_NEW_I2C_ADDRESS_SCHEMA, +) +async def pmwcs3newi2caddress_to_code(config, action_id, template_arg, args): + parent = await cg.get_variable(config[CONF_ID]) + var = cg.new_Pvariable(action_id, template_arg, parent) + address = await cg.templatable(config[CONF_ADDRESS], args, int) + cg.add(var.set_new_address(address)) + return var diff --git a/tests/test1.yaml b/tests/test1.yaml index 66caad961a..80cd9e3987 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -348,6 +348,16 @@ mcp23s17: deviceaddress: 1 sensor: + - platform: pmwcs3 + i2c_id: i2c_bus + e25: + name: pmwcs3_e25 + ec: + name: pmwcs3_ec + temperature: + name: pmwcs3_temperature + vwc: + name: pmwcs3_vwc - platform: gcja5 pm_1_0: name: "Particulate Matter <1.0µm Concentration" From 164d05fdcea31ea295e3ac98cd06966aacb236a1 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Fri, 18 Aug 2023 06:05:25 +1000 Subject: [PATCH 025/111] Add manufacturer data config to BLE server (#5251) --- CODEOWNERS | 2 +- .../components/esp32_ble/ble_advertising.cpp | 37 ++++++++++++------- .../components/esp32_ble/ble_advertising.h | 2 +- .../components/esp32_ble_server/__init__.py | 6 ++- .../esp32_ble_server/ble_server.cpp | 8 ++++ .../components/esp32_ble_server/ble_server.h | 6 +++ tests/test2.yaml | 5 +++ 7 files changed, 50 insertions(+), 16 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index 0cea8d48ba..49746cf013 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -87,7 +87,7 @@ esphome/components/ens210/* @itn3rd77 esphome/components/esp32/* @esphome/core esphome/components/esp32_ble/* @jesserockz esphome/components/esp32_ble_client/* @jesserockz -esphome/components/esp32_ble_server/* @jesserockz +esphome/components/esp32_ble_server/* @clydebarrow @jesserockz esphome/components/esp32_camera_web_server/* @ayufan esphome/components/esp32_can/* @Sympatron esphome/components/esp32_improv/* @jesserockz diff --git a/esphome/components/esp32_ble/ble_advertising.cpp b/esphome/components/esp32_ble/ble_advertising.cpp index 2083bf5f08..072bb38c07 100644 --- a/esphome/components/esp32_ble/ble_advertising.cpp +++ b/esphome/components/esp32_ble/ble_advertising.cpp @@ -42,9 +42,15 @@ void BLEAdvertising::remove_service_uuid(ESPBTUUID uuid) { this->advertising_uuids_.end()); } -void BLEAdvertising::set_manufacturer_data(uint8_t *data, uint16_t size) { - this->advertising_data_.p_manufacturer_data = data; - this->advertising_data_.manufacturer_len = size; +void BLEAdvertising::set_manufacturer_data(const std::vector &data) { + delete[] this->advertising_data_.p_manufacturer_data; + this->advertising_data_.p_manufacturer_data = nullptr; + this->advertising_data_.manufacturer_len = data.size(); + if (!data.empty()) { + // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) + this->advertising_data_.p_manufacturer_data = new uint8_t[data.size()]; + memcpy(this->advertising_data_.p_manufacturer_data, data.data(), data.size()); + } } void BLEAdvertising::start() { @@ -74,16 +80,21 @@ void BLEAdvertising::start() { return; } - memcpy(&this->scan_response_data_, &this->advertising_data_, sizeof(esp_ble_adv_data_t)); - this->scan_response_data_.set_scan_rsp = true; - this->scan_response_data_.include_name = true; - this->scan_response_data_.include_txpower = true; - this->scan_response_data_.appearance = 0; - this->scan_response_data_.flag = 0; - err = esp_ble_gap_config_adv_data(&this->scan_response_data_); - if (err != ESP_OK) { - ESP_LOGE(TAG, "esp_ble_gap_config_adv_data failed (Scan response): %d", err); - return; + if (this->scan_response_) { + memcpy(&this->scan_response_data_, &this->advertising_data_, sizeof(esp_ble_adv_data_t)); + this->scan_response_data_.set_scan_rsp = true; + this->scan_response_data_.include_name = true; + this->scan_response_data_.include_txpower = true; + this->scan_response_data_.min_interval = 0; + this->scan_response_data_.max_interval = 0; + this->scan_response_data_.manufacturer_len = 0; + this->scan_response_data_.appearance = 0; + this->scan_response_data_.flag = 0; + err = esp_ble_gap_config_adv_data(&this->scan_response_data_); + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_ble_gap_config_adv_data failed (Scan response): %d", err); + return; + } } if (this->advertising_data_.service_uuid_len > 0) { diff --git a/esphome/components/esp32_ble/ble_advertising.h b/esphome/components/esp32_ble/ble_advertising.h index 079bd6c14c..9e4e2b7701 100644 --- a/esphome/components/esp32_ble/ble_advertising.h +++ b/esphome/components/esp32_ble/ble_advertising.h @@ -20,7 +20,7 @@ class BLEAdvertising { void remove_service_uuid(ESPBTUUID uuid); void set_scan_response(bool scan_response) { this->scan_response_ = scan_response; } void set_min_preferred_interval(uint16_t interval) { this->advertising_data_.min_interval = interval; } - void set_manufacturer_data(uint8_t *data, uint16_t size); + void set_manufacturer_data(const std::vector &data); void start(); void stop(); diff --git a/esphome/components/esp32_ble_server/__init__.py b/esphome/components/esp32_ble_server/__init__.py index 0ddfa62c1b..5e1ad71501 100644 --- a/esphome/components/esp32_ble_server/__init__.py +++ b/esphome/components/esp32_ble_server/__init__.py @@ -6,11 +6,12 @@ from esphome.core import CORE from esphome.components.esp32 import add_idf_sdkconfig_option AUTO_LOAD = ["esp32_ble"] -CODEOWNERS = ["@jesserockz"] +CODEOWNERS = ["@jesserockz", "@clydebarrow"] CONFLICTS_WITH = ["esp32_ble_beacon"] DEPENDENCIES = ["esp32"] CONF_MANUFACTURER = "manufacturer" +CONF_MANUFACTURER_DATA = "manufacturer_data" esp32_ble_server_ns = cg.esphome_ns.namespace("esp32_ble_server") BLEServer = esp32_ble_server_ns.class_( @@ -27,6 +28,7 @@ CONFIG_SCHEMA = cv.Schema( cv.GenerateID(): cv.declare_id(BLEServer), cv.GenerateID(esp32_ble.CONF_BLE_ID): cv.use_id(esp32_ble.ESP32BLE), cv.Optional(CONF_MANUFACTURER, default="ESPHome"): cv.string, + cv.Optional(CONF_MANUFACTURER_DATA): cv.Schema([cv.hex_uint8_t]), cv.Optional(CONF_MODEL): cv.string, } ).extend(cv.COMPONENT_SCHEMA) @@ -42,6 +44,8 @@ async def to_code(config): cg.add(var.set_parent(parent)) cg.add(var.set_manufacturer(config[CONF_MANUFACTURER])) + if CONF_MANUFACTURER_DATA in config: + cg.add(var.set_manufacturer_data(config[CONF_MANUFACTURER_DATA])) if CONF_MODEL in config: cg.add(var.set_model(config[CONF_MODEL])) cg.add_define("USE_ESP32_BLE_SERVER") diff --git a/esphome/components/esp32_ble_server/ble_server.cpp b/esphome/components/esp32_ble_server/ble_server.cpp index 7cbf40c076..ca244aba95 100644 --- a/esphome/components/esp32_ble_server/ble_server.cpp +++ b/esphome/components/esp32_ble_server/ble_server.cpp @@ -68,6 +68,7 @@ void BLEServer::loop() { if (this->device_information_service_->is_running()) { this->state_ = RUNNING; this->can_proceed_ = true; + this->restart_advertising_(); ESP_LOGD(TAG, "BLE server setup successfully"); } else if (!this->device_information_service_->is_starting()) { this->device_information_service_->start(); @@ -77,6 +78,13 @@ void BLEServer::loop() { } } +void BLEServer::restart_advertising_() { + if (this->state_ == RUNNING) { + esp32_ble::global_ble->get_advertising()->set_manufacturer_data(this->manufacturer_data_); + esp32_ble::global_ble->get_advertising()->start(); + } +} + bool BLEServer::create_device_characteristics_() { if (this->model_.has_value()) { BLECharacteristic *model = diff --git a/esphome/components/esp32_ble_server/ble_server.h b/esphome/components/esp32_ble_server/ble_server.h index ac759f2dcd..14c88be1a2 100644 --- a/esphome/components/esp32_ble_server/ble_server.h +++ b/esphome/components/esp32_ble_server/ble_server.h @@ -45,6 +45,10 @@ class BLEServer : public Component, public GATTsEventHandler, public Parentedmanufacturer_ = manufacturer; } void set_model(const std::string &model) { this->model_ = model; } + void set_manufacturer_data(const std::vector &data) { + this->manufacturer_data_ = data; + this->restart_advertising_(); + } std::shared_ptr create_service(const uint8_t *uuid, bool advertise = false); std::shared_ptr create_service(uint16_t uuid, bool advertise = false); @@ -63,6 +67,7 @@ class BLEServer : public Component, public GATTsEventHandler, public Parentedclients_.insert(std::pair(conn_id, client)); @@ -73,6 +78,7 @@ class BLEServer : public Component, public GATTsEventHandler, public Parented model_; + std::vector manufacturer_data_; esp_gatt_if_t gatts_if_{0}; bool registered_{false}; diff --git a/tests/test2.yaml b/tests/test2.yaml index 291dc240dc..d1508632b3 100644 --- a/tests/test2.yaml +++ b/tests/test2.yaml @@ -768,3 +768,8 @@ switch: characteristic_uuid: 6490FAFE-0734-732C-8705-91B653A081FC value: !lambda |- return {0x13, 0x37}; + + +esp32_ble_server: + id: ble + manufacturer_data: [0x72, 0x4, 0x00, 0x23] From c287e529a88e70dc7ec2fd8115a74d81e8c72fa3 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 18 Aug 2023 08:06:21 +1200 Subject: [PATCH 026/111] Change haier from AUTO to HEAT_COOL (#5267) --- esphome/components/haier/climate.py | 2 +- esphome/components/haier/haier_base.cpp | 6 +++--- esphome/components/haier/hon_climate.cpp | 4 ++-- esphome/components/haier/smartair2_climate.cpp | 4 ++-- tests/test3.yaml | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/esphome/components/haier/climate.py b/esphome/components/haier/climate.py index fec39d2967..acb079c822 100644 --- a/esphome/components/haier/climate.py +++ b/esphome/components/haier/climate.py @@ -89,7 +89,7 @@ SUPPORTED_SWING_MODES_OPTIONS = { SUPPORTED_CLIMATE_MODES_OPTIONS = { "OFF": ClimateMode.CLIMATE_MODE_OFF, # always available - "AUTO": ClimateMode.CLIMATE_MODE_AUTO, # always available + "HEAT_COOL": ClimateMode.CLIMATE_MODE_HEAT_COOL, # always available "COOL": ClimateMode.CLIMATE_MODE_COOL, "HEAT": ClimateMode.CLIMATE_MODE_HEAT, "DRY": ClimateMode.CLIMATE_MODE_DRY, diff --git a/esphome/components/haier/haier_base.cpp b/esphome/components/haier/haier_base.cpp index 5faee5207b..22899b1a70 100644 --- a/esphome/components/haier/haier_base.cpp +++ b/esphome/components/haier/haier_base.cpp @@ -72,7 +72,7 @@ HaierClimateBase::HaierClimateBase() this->traits_ = climate::ClimateTraits(); this->traits_.set_supported_modes({climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_COOL, climate::CLIMATE_MODE_HEAT, climate::CLIMATE_MODE_FAN_ONLY, climate::CLIMATE_MODE_DRY, - climate::CLIMATE_MODE_AUTO}); + climate::CLIMATE_MODE_HEAT_COOL}); this->traits_.set_supported_fan_modes( {climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM, climate::CLIMATE_FAN_HIGH}); this->traits_.set_supported_swing_modes({climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_BOTH, @@ -171,8 +171,8 @@ void HaierClimateBase::set_answer_timeout(uint32_t timeout) { void HaierClimateBase::set_supported_modes(const std::set &modes) { this->traits_.set_supported_modes(modes); - this->traits_.add_supported_mode(climate::CLIMATE_MODE_OFF); // Always available - this->traits_.add_supported_mode(climate::CLIMATE_MODE_AUTO); // Always available + this->traits_.add_supported_mode(climate::CLIMATE_MODE_OFF); // Always available + this->traits_.add_supported_mode(climate::CLIMATE_MODE_HEAT_COOL); // Always available } void HaierClimateBase::set_supported_presets(const std::set &presets) { diff --git a/esphome/components/haier/hon_climate.cpp b/esphome/components/haier/hon_climate.cpp index feb1e019d8..d4944410f7 100644 --- a/esphome/components/haier/hon_climate.cpp +++ b/esphome/components/haier/hon_climate.cpp @@ -458,7 +458,7 @@ haier_protocol::HaierMessage HonClimate::get_control_message() { case CLIMATE_MODE_OFF: out_data->ac_power = 0; break; - case CLIMATE_MODE_AUTO: + case CLIMATE_MODE_HEAT_COOL: out_data->ac_power = 1; out_data->ac_mode = (uint8_t) hon_protocol::ConditioningMode::AUTO; out_data->fan_mode = this->other_modes_fan_speed_; @@ -758,7 +758,7 @@ haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t * this->mode = CLIMATE_MODE_FAN_ONLY; break; case (uint8_t) hon_protocol::ConditioningMode::AUTO: - this->mode = CLIMATE_MODE_AUTO; + this->mode = CLIMATE_MODE_HEAT_COOL; break; } } diff --git a/esphome/components/haier/smartair2_climate.cpp b/esphome/components/haier/smartair2_climate.cpp index 8bee37dadf..f29f840088 100644 --- a/esphome/components/haier/smartair2_climate.cpp +++ b/esphome/components/haier/smartair2_climate.cpp @@ -270,7 +270,7 @@ haier_protocol::HaierMessage Smartair2Climate::get_control_message() { out_data->ac_power = 0; break; - case CLIMATE_MODE_AUTO: + case CLIMATE_MODE_HEAT_COOL: out_data->ac_power = 1; out_data->ac_mode = (uint8_t) smartair2_protocol::ConditioningMode::AUTO; out_data->fan_mode = this->other_modes_fan_speed_; @@ -487,7 +487,7 @@ haier_protocol::HandlerError Smartair2Climate::process_status_message_(const uin this->mode = CLIMATE_MODE_FAN_ONLY; break; case (uint8_t) smartair2_protocol::ConditioningMode::AUTO: - this->mode = CLIMATE_MODE_AUTO; + this->mode = CLIMATE_MODE_HEAT_COOL; break; } } diff --git a/tests/test3.yaml b/tests/test3.yaml index 5bda0afb1b..471b7d97b6 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -957,7 +957,7 @@ climate: temperature_step: 1 °C supported_modes: - 'OFF' - - AUTO + - HEAT_COOL - COOL - HEAT - DRY From f16a24ddf460c48176391b06078a70bdedd974b1 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 18 Aug 2023 19:13:46 +1200 Subject: [PATCH 027/111] Move libcairo to all architectures in docker (#5276) --- docker/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 4aaea9da89..b942b650cb 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -28,14 +28,14 @@ RUN \ git=1:2.30.2-1+deb11u2 \ curl=7.74.0-1.3+deb11u7 \ openssh-client=1:8.4p1-5+deb11u1 \ - python3-cffi=1.14.5-1; \ + python3-cffi=1.14.5-1 \ + libcairo2=1.16.0-5; \ if [ "$TARGETARCH$TARGETVARIANT" = "armv7" ]; then \ apt-get install -y --no-install-recommends \ build-essential=12.9 \ python3-dev=3.9.2-3 \ zlib1g-dev=1:1.2.11.dfsg-2+deb11u2 \ libjpeg-dev=1:2.0.6-4 \ - libcairo2=1.16.0-5 \ libfreetype-dev=2.10.4+dfsg-1+deb11u1; \ fi; \ rm -rf \ From c47c1a78675ace5a314cddf3ab059684f07d98d8 Mon Sep 17 00:00:00 2001 From: mwolter805 <24851651+mwolter805@users.noreply.github.com> Date: Sun, 20 Aug 2023 12:15:45 -0700 Subject: [PATCH 028/111] Resolve offline ESPs in dashboard when using ESPHOME_DASHBOARD_USE_PING=true (#5281) --- esphome/dashboard/dashboard.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index b33cb2df5e..eae004fa09 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -868,9 +868,6 @@ class PingStatusThread(threading.Thread): entries = _list_dashboard_entries() queue = collections.deque() for entry in entries: - if entry.no_mdns is True: - continue - if entry.address is None: PING_RESULT[entry.filename] = None continue From bfdcfa476664ea1dc4c14731a8c6a60dd0d7737c Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Mon, 21 Aug 2023 06:57:02 +1000 Subject: [PATCH 029/111] Align SPI data rates in C++ code with Python (#5284) --- esphome/components/spi/__init__.py | 2 ++ esphome/components/spi/spi.h | 1 + 2 files changed, 3 insertions(+) diff --git a/esphome/components/spi/__init__.py b/esphome/components/spi/__init__.py index 1528a05734..57a4fa9f4e 100644 --- a/esphome/components/spi/__init__.py +++ b/esphome/components/spi/__init__.py @@ -23,7 +23,9 @@ SPI_DATA_RATE_OPTIONS = { 40e6: SPIDataRate.DATA_RATE_40MHZ, 20e6: SPIDataRate.DATA_RATE_20MHZ, 10e6: SPIDataRate.DATA_RATE_10MHZ, + 8e6: SPIDataRate.DATA_RATE_8MHZ, 5e6: SPIDataRate.DATA_RATE_5MHZ, + 4e6: SPIDataRate.DATA_RATE_4MHZ, 2e6: SPIDataRate.DATA_RATE_2MHZ, 1e6: SPIDataRate.DATA_RATE_1MHZ, 2e5: SPIDataRate.DATA_RATE_200KHZ, diff --git a/esphome/components/spi/spi.h b/esphome/components/spi/spi.h index f19518caae..159d117533 100644 --- a/esphome/components/spi/spi.h +++ b/esphome/components/spi/spi.h @@ -63,6 +63,7 @@ enum SPIDataRate : uint32_t { DATA_RATE_1MHZ = 1000000, DATA_RATE_2MHZ = 2000000, DATA_RATE_4MHZ = 4000000, + DATA_RATE_5MHZ = 5000000, DATA_RATE_8MHZ = 8000000, DATA_RATE_10MHZ = 10000000, DATA_RATE_20MHZ = 20000000, From d19bf5d6ee585ec1033c04397dbcadb251d03c42 Mon Sep 17 00:00:00 2001 From: jayme-github Date: Sun, 20 Aug 2023 23:34:50 +0200 Subject: [PATCH 030/111] Add support for ESP32-{S2,S3,C3} to debug component (#4731) --- esphome/components/debug/debug_component.cpp | 75 +++++++++++++++++--- 1 file changed, 67 insertions(+), 8 deletions(-) diff --git a/esphome/components/debug/debug_component.cpp b/esphome/components/debug/debug_component.cpp index baf537a12b..5ee1960267 100644 --- a/esphome/components/debug/debug_component.cpp +++ b/esphome/components/debug/debug_component.cpp @@ -12,8 +12,16 @@ #include #include -#include #include +#if defined(USE_ESP32_VARIANT_ESP32) +#include +#elif defined(USE_ESP32_VARIANT_ESP32C3) +#include +#elif defined(USE_ESP32_VARIANT_ESP32S2) +#include +#elif defined(USE_ESP32_VARIANT_ESP32S3) +#include +#endif #endif // USE_ESP32 @@ -105,13 +113,19 @@ void DebugComponent::dump_config() { esp_chip_info_t info; esp_chip_info(&info); const char *model; - switch (info.model) { - case CHIP_ESP32: - model = "ESP32"; - break; - default: - model = "UNKNOWN"; - } +#if defined(USE_ESP32_VARIANT_ESP32) + model = "ESP32"; +#elif defined(USE_ESP32_VARIANT_ESP32C3) + model = "ESP32-C3"; +#elif defined(USE_ESP32_VARIANT_ESP32S2) + model = "ESP32-S2"; +#elif defined(USE_ESP32_VARIANT_ESP32S3) + model = "ESP32-S3"; +#elif defined(USE_ESP32_VARIANT_ESP32H2) + model = "ESP32-H2"; +#else + model = "UNKNOWN"; +#endif std::string features; if (info.features & CHIP_FEATURE_EMB_FLASH) { features += "EMB_FLASH,"; @@ -153,18 +167,26 @@ void DebugComponent::dump_config() { case POWERON_RESET: reset_reason = "Power On Reset"; break; +#if defined(USE_ESP32_VARIANT_ESP32) case SW_RESET: +#elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) + case RTC_SW_SYS_RESET: +#endif reset_reason = "Software Reset Digital Core"; break; +#if defined(USE_ESP32_VARIANT_ESP32) case OWDT_RESET: reset_reason = "Watch Dog Reset Digital Core"; break; +#endif case DEEPSLEEP_RESET: reset_reason = "Deep Sleep Reset Digital Core"; break; +#if defined(USE_ESP32_VARIANT_ESP32) case SDIO_RESET: reset_reason = "SLC Module Reset Digital Core"; break; +#endif case TG0WDT_SYS_RESET: reset_reason = "Timer Group 0 Watch Dog Reset Digital Core"; break; @@ -177,24 +199,61 @@ void DebugComponent::dump_config() { case INTRUSION_RESET: reset_reason = "Intrusion Reset CPU"; break; +#if defined(USE_ESP32_VARIANT_ESP32) case TGWDT_CPU_RESET: reset_reason = "Timer Group Reset CPU"; break; +#elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) + case TG0WDT_CPU_RESET: + reset_reason = "Timer Group 0 Reset CPU"; + break; +#endif +#if defined(USE_ESP32_VARIANT_ESP32) case SW_CPU_RESET: +#elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) + case RTC_SW_CPU_RESET: +#endif reset_reason = "Software Reset CPU"; break; case RTCWDT_CPU_RESET: reset_reason = "RTC Watch Dog Reset CPU"; break; +#if defined(USE_ESP32_VARIANT_ESP32) case EXT_CPU_RESET: reset_reason = "External CPU Reset"; break; +#endif case RTCWDT_BROWN_OUT_RESET: reset_reason = "Voltage Unstable Reset"; break; case RTCWDT_RTC_RESET: reset_reason = "RTC Watch Dog Reset Digital Core And RTC Module"; break; +#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) + case TG1WDT_CPU_RESET: + reset_reason = "Timer Group 1 Reset CPU"; + break; + case SUPER_WDT_RESET: + reset_reason = "Super Watchdog Reset Digital Core And RTC Module"; + break; + case GLITCH_RTC_RESET: + reset_reason = "Glitch Reset Digital Core And RTC Module"; + break; + case EFUSE_RESET: + reset_reason = "eFuse Reset Digital Core"; + break; +#endif +#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S3) + case USB_UART_CHIP_RESET: + reset_reason = "USB UART Reset Digital Core"; + break; + case USB_JTAG_CHIP_RESET: + reset_reason = "USB JTAG Reset Digital Core"; + break; + case POWER_GLITCH_RESET: + reset_reason = "Power Glitch Reset Digital Core And RTC Module"; + break; +#endif default: reset_reason = "Unknown Reset Reason"; } From fe7893d1b3178912ad8e016d0ffba2bf6836928d Mon Sep 17 00:00:00 2001 From: Austin Date: Sun, 20 Aug 2023 17:42:03 -0400 Subject: [PATCH 031/111] Support for ESP32-C2 & ESP32-C6 (#4377) Co-authored-by: Stijn Tintel --- esphome/components/adc/__init__.py | 18 +++++++ esphome/components/deep_sleep/__init__.py | 4 ++ esphome/components/esp32/__init__.py | 1 - esphome/components/esp32/const.py | 6 +++ esphome/components/esp32/gpio.py | 12 +++++ esphome/components/esp32/gpio_esp32_c2.py | 37 ++++++++++++++ esphome/components/esp32/gpio_esp32_c6.py | 50 +++++++++++++++++++ .../components/esp32_rmt_led_strip/light.py | 1 + esphome/components/logger/__init__.py | 4 ++ esphome/components/logger/logger.cpp | 16 +++--- esphome/components/logger/logger.h | 5 +- esphome/components/spi/spi.cpp | 3 +- 12 files changed, 146 insertions(+), 11 deletions(-) create mode 100644 esphome/components/esp32/gpio_esp32_c2.py create mode 100644 esphome/components/esp32/gpio_esp32_c6.py diff --git a/esphome/components/adc/__init__.py b/esphome/components/adc/__init__.py index 99dad68501..015d6edd21 100644 --- a/esphome/components/adc/__init__.py +++ b/esphome/components/adc/__init__.py @@ -7,7 +7,9 @@ from esphome.core import CORE from esphome.components.esp32 import get_esp32_variant from esphome.components.esp32.const import ( VARIANT_ESP32, + VARIANT_ESP32C2, VARIANT_ESP32C3, + VARIANT_ESP32C6, VARIANT_ESP32H2, VARIANT_ESP32S2, VARIANT_ESP32S3, @@ -70,6 +72,22 @@ ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = { 3: adc1_channel_t.ADC1_CHANNEL_3, 4: adc1_channel_t.ADC1_CHANNEL_4, }, + VARIANT_ESP32C2: { + 0: adc1_channel_t.ADC1_CHANNEL_0, + 1: adc1_channel_t.ADC1_CHANNEL_1, + 2: adc1_channel_t.ADC1_CHANNEL_2, + 3: adc1_channel_t.ADC1_CHANNEL_3, + 4: adc1_channel_t.ADC1_CHANNEL_4, + }, + VARIANT_ESP32C6: { + 0: adc1_channel_t.ADC1_CHANNEL_0, + 1: adc1_channel_t.ADC1_CHANNEL_1, + 2: adc1_channel_t.ADC1_CHANNEL_2, + 3: adc1_channel_t.ADC1_CHANNEL_3, + 4: adc1_channel_t.ADC1_CHANNEL_4, + 5: adc1_channel_t.ADC1_CHANNEL_5, + 6: adc1_channel_t.ADC1_CHANNEL_6, + }, VARIANT_ESP32H2: { 0: adc1_channel_t.ADC1_CHANNEL_0, 1: adc1_channel_t.ADC1_CHANNEL_1, diff --git a/esphome/components/deep_sleep/__init__.py b/esphome/components/deep_sleep/__init__.py index bbd10d58c5..6e71f7bbf6 100644 --- a/esphome/components/deep_sleep/__init__.py +++ b/esphome/components/deep_sleep/__init__.py @@ -22,6 +22,8 @@ from esphome.components.esp32.const import ( VARIANT_ESP32C3, VARIANT_ESP32S2, VARIANT_ESP32S3, + VARIANT_ESP32C2, + VARIANT_ESP32C6, ) WAKEUP_PINS = { @@ -94,6 +96,8 @@ WAKEUP_PINS = { 20, 21, ], + VARIANT_ESP32C2: [0, 1, 2, 3, 4, 5], + VARIANT_ESP32C6: [0, 1, 2, 3, 4, 5, 6, 7], } diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index aa9f8cd66e..d158528066 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -44,7 +44,6 @@ from .const import ( # noqa KEY_SDKCONFIG_OPTIONS, KEY_SUBMODULES, KEY_VARIANT, - VARIANT_ESP32C3, VARIANT_FRIENDLY, VARIANTS, ) diff --git a/esphome/components/esp32/const.py b/esphome/components/esp32/const.py index 698310dacb..9e997bdeb5 100644 --- a/esphome/components/esp32/const.py +++ b/esphome/components/esp32/const.py @@ -14,13 +14,17 @@ KEY_SUBMODULES = "submodules" VARIANT_ESP32 = "ESP32" VARIANT_ESP32S2 = "ESP32S2" VARIANT_ESP32S3 = "ESP32S3" +VARIANT_ESP32C2 = "ESP32C2" VARIANT_ESP32C3 = "ESP32C3" +VARIANT_ESP32C6 = "ESP32C6" VARIANT_ESP32H2 = "ESP32H2" VARIANTS = [ VARIANT_ESP32, VARIANT_ESP32S2, VARIANT_ESP32S3, + VARIANT_ESP32C2, VARIANT_ESP32C3, + VARIANT_ESP32C6, VARIANT_ESP32H2, ] @@ -28,7 +32,9 @@ VARIANT_FRIENDLY = { VARIANT_ESP32: "ESP32", VARIANT_ESP32S2: "ESP32-S2", VARIANT_ESP32S3: "ESP32-S3", + VARIANT_ESP32C2: "ESP32-C2", VARIANT_ESP32C3: "ESP32-C3", + VARIANT_ESP32C6: "ESP32-C6", VARIANT_ESP32H2: "ESP32-H2", } diff --git a/esphome/components/esp32/gpio.py b/esphome/components/esp32/gpio.py index 7848d1d552..6950cd58a0 100644 --- a/esphome/components/esp32/gpio.py +++ b/esphome/components/esp32/gpio.py @@ -26,6 +26,8 @@ from .const import ( VARIANT_ESP32C3, VARIANT_ESP32S2, VARIANT_ESP32S3, + VARIANT_ESP32C2, + VARIANT_ESP32C6, VARIANT_ESP32H2, esp32_ns, ) @@ -35,6 +37,8 @@ from .gpio_esp32 import esp32_validate_gpio_pin, esp32_validate_supports from .gpio_esp32_s2 import esp32_s2_validate_gpio_pin, esp32_s2_validate_supports from .gpio_esp32_c3 import esp32_c3_validate_gpio_pin, esp32_c3_validate_supports from .gpio_esp32_s3 import esp32_s3_validate_gpio_pin, esp32_s3_validate_supports +from .gpio_esp32_c2 import esp32_c2_validate_gpio_pin, esp32_c2_validate_supports +from .gpio_esp32_c6 import esp32_c6_validate_gpio_pin, esp32_c6_validate_supports from .gpio_esp32_h2 import esp32_h2_validate_gpio_pin, esp32_h2_validate_supports @@ -95,6 +99,14 @@ _esp32_validations = { pin_validation=esp32_s3_validate_gpio_pin, usage_validation=esp32_s3_validate_supports, ), + VARIANT_ESP32C2: ESP32ValidationFunctions( + pin_validation=esp32_c2_validate_gpio_pin, + usage_validation=esp32_c2_validate_supports, + ), + VARIANT_ESP32C6: ESP32ValidationFunctions( + pin_validation=esp32_c6_validate_gpio_pin, + usage_validation=esp32_c6_validate_supports, + ), VARIANT_ESP32H2: ESP32ValidationFunctions( pin_validation=esp32_h2_validate_gpio_pin, usage_validation=esp32_h2_validate_supports, diff --git a/esphome/components/esp32/gpio_esp32_c2.py b/esphome/components/esp32/gpio_esp32_c2.py new file mode 100644 index 0000000000..c444f804a3 --- /dev/null +++ b/esphome/components/esp32/gpio_esp32_c2.py @@ -0,0 +1,37 @@ +import logging + +from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER + +import esphome.config_validation as cv + +_ESP32C2_STRAPPING_PINS = {8, 9} + +_LOGGER = logging.getLogger(__name__) + + +def esp32_c2_validate_gpio_pin(value): + if value < 0 or value > 20: + raise cv.Invalid(f"Invalid pin number: {value} (must be 0-20)") + if value in _ESP32C2_STRAPPING_PINS: + _LOGGER.warning( + "GPIO%d is a Strapping PIN and should be avoided.\n" + "Attaching external pullup/down resistors to strapping pins can cause unexpected failures.\n" + "See https://esphome.io/guides/faq.html#why-am-i-getting-a-warning-about-strapping-pins", + value, + ) + + return value + + +def esp32_c2_validate_supports(value): + num = value[CONF_NUMBER] + mode = value[CONF_MODE] + is_input = mode[CONF_INPUT] + + if num < 0 or num > 20: + raise cv.Invalid(f"Invalid pin number: {value} (must be 0-20)") + + if is_input: + # All ESP32 pins support input mode + pass + return value diff --git a/esphome/components/esp32/gpio_esp32_c6.py b/esphome/components/esp32/gpio_esp32_c6.py new file mode 100644 index 0000000000..b26b6bc6b4 --- /dev/null +++ b/esphome/components/esp32/gpio_esp32_c6.py @@ -0,0 +1,50 @@ +import logging + +from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER + +import esphome.config_validation as cv + +_ESP32C6_SPI_PSRAM_PINS = { + 24: "SPICS0", + 25: "SPIQ", + 26: "SPIWP", + 27: "VDD_SPI", + 28: "SPIHD", + 29: "SPICLK", + 30: "SPID", +} + +_ESP32C6_STRAPPING_PINS = {8, 9, 15} + +_LOGGER = logging.getLogger(__name__) + + +def esp32_c6_validate_gpio_pin(value): + if value < 0 or value > 23: + raise cv.Invalid(f"Invalid pin number: {value} (must be 0-23)") + if value in _ESP32C6_SPI_PSRAM_PINS: + raise cv.Invalid( + f"This pin cannot be used on ESP32-C6s and is already used by the SPI/PSRAM interface (function: {_ESP32C6_SPI_PSRAM_PINS[value]})" + ) + if value in _ESP32C6_STRAPPING_PINS: + _LOGGER.warning( + "GPIO%d is a Strapping PIN and should be avoided.\n" + "Attaching external pullup/down resistors to strapping pins can cause unexpected failures.\n" + "See https://esphome.io/guides/faq.html#why-am-i-getting-a-warning-about-strapping-pins", + value, + ) + + return value + + +def esp32_c6_validate_supports(value): + num = value[CONF_NUMBER] + mode = value[CONF_MODE] + is_input = mode[CONF_INPUT] + + if num < 0 or num > 23: + raise cv.Invalid(f"Invalid pin number: {value} (must be 0-23)") + if is_input: + # All ESP32 pins support input mode + pass + return value diff --git a/esphome/components/esp32_rmt_led_strip/light.py b/esphome/components/esp32_rmt_led_strip/light.py index 3ca758c1e1..5b65ecdabf 100644 --- a/esphome/components/esp32_rmt_led_strip/light.py +++ b/esphome/components/esp32_rmt_led_strip/light.py @@ -63,6 +63,7 @@ RMT_CHANNELS = { esp32.const.VARIANT_ESP32S2: [0, 1, 2, 3], esp32.const.VARIANT_ESP32S3: [0, 1, 2, 3], esp32.const.VARIANT_ESP32C3: [0, 1], + esp32.const.VARIANT_ESP32C6: [0, 1], } diff --git a/esphome/components/logger/__init__.py b/esphome/components/logger/__init__.py index af96b03c8e..5c87bb9d91 100644 --- a/esphome/components/logger/__init__.py +++ b/esphome/components/logger/__init__.py @@ -28,6 +28,8 @@ from esphome.components.esp32.const import ( VARIANT_ESP32S2, VARIANT_ESP32C3, VARIANT_ESP32S3, + VARIANT_ESP32C2, + VARIANT_ESP32C6, ) CODEOWNERS = ["@esphome/core"] @@ -74,6 +76,8 @@ UART_SELECTION_ESP32 = { VARIANT_ESP32S2: [UART0, UART1, USB_CDC], VARIANT_ESP32S3: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG], VARIANT_ESP32C3: [UART0, UART1, USB_SERIAL_JTAG], + VARIANT_ESP32C2: [UART0, UART1], + VARIANT_ESP32C6: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG], } UART_SELECTION_ESP8266 = [UART0, UART0_SWAP, UART1] diff --git a/esphome/components/logger/logger.cpp b/esphome/components/logger/logger.cpp index ca4cc64007..86ebb53764 100644 --- a/esphome/components/logger/logger.cpp +++ b/esphome/components/logger/logger.cpp @@ -120,7 +120,7 @@ void HOT Logger::log_message_(int level, const char *tag, int offset) { if ( #if defined(USE_ESP32_VARIANT_ESP32S2) uart_ == UART_SELECTION_USB_CDC -#elif defined(USE_ESP32_VARIANT_ESP32C3) +#elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) uart_ == UART_SELECTION_USB_SERIAL_JTAG #elif defined(USE_ESP32_VARIANT_ESP32S3) uart_ == UART_SELECTION_USB_CDC || uart_ == UART_SELECTION_USB_SERIAL_JTAG @@ -191,8 +191,8 @@ void Logger::pre_setup() { Serial1.setDebugOutput(ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE); #endif break; -#if defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32S2) && \ - !defined(USE_ESP32_VARIANT_ESP32S3) +#if defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32C6) && \ + !defined(USE_ESP32_VARIANT_ESP32S2) && !defined(USE_ESP32_VARIANT_ESP32S3) case UART_SELECTION_UART2: this->hw_serial_ = &Serial2; Serial2.begin(this->baud_rate_); @@ -215,7 +215,8 @@ void Logger::pre_setup() { case UART_SELECTION_UART1: uart_num_ = UART_NUM_1; break; -#if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32S2) && !defined(USE_ESP32_VARIANT_ESP32S3) +#if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32C6) && \ + !defined(USE_ESP32_VARIANT_ESP32S2) && !defined(USE_ESP32_VARIANT_ESP32S3) case UART_SELECTION_UART2: uart_num_ = UART_NUM_2; break; @@ -225,11 +226,11 @@ void Logger::pre_setup() { uart_num_ = -1; break; #endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 -#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S3) +#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S3) case UART_SELECTION_USB_SERIAL_JTAG: uart_num_ = -1; break; -#endif // USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32S3 +#endif // USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C6 || USE_ESP32_VARIANT_ESP32S3 } if (uart_num_ >= 0) { uart_config_t uart_config{}; @@ -278,7 +279,8 @@ const char *const LOG_LEVELS[] = {"NONE", "ERROR", "WARN", "INFO", "CONFIG", "DE #ifdef USE_ESP32 const char *const UART_SELECTIONS[] = { "UART0", "UART1", -#if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32S2) && !defined(USE_ESP32_VARIANT_ESP32S3) +#if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32C6) && \ + !defined(USE_ESP32_VARIANT_ESP32S2) && !defined(USE_ESP32_VARIANT_ESP32S3) "UART2", #endif // !USE_ESP32_VARIANT_ESP32C3 && !USE_ESP32_VARIANT_ESP32S2 && !USE_ESP32_VARIANT_ESP32S3 #if defined(USE_ESP_IDF) diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index 54a5236cd8..47cde45c29 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -34,14 +34,15 @@ enum UARTSelection { UART_SELECTION_UART0 = 0, UART_SELECTION_UART1, #if defined(USE_ESP32) -#if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32S2) && !defined(USE_ESP32_VARIANT_ESP32S3) +#if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32C6) && \ + !defined(USE_ESP32_VARIANT_ESP32S2) && !defined(USE_ESP32_VARIANT_ESP32S3) UART_SELECTION_UART2, #endif // !USE_ESP32_VARIANT_ESP32C3 && !USE_ESP32_VARIANT_ESP32S2 && !USE_ESP32_VARIANT_ESP32S3 #ifdef USE_ESP_IDF #if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) UART_SELECTION_USB_CDC, #endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 -#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S3) +#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S3) UART_SELECTION_USB_SERIAL_JTAG, #endif // USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32S3 #endif // USE_ESP_IDF diff --git a/esphome/components/spi/spi.cpp b/esphome/components/spi/spi.cpp index c9bb075fb5..33630897f6 100644 --- a/esphome/components/spi/spi.cpp +++ b/esphome/components/spi/spi.cpp @@ -76,7 +76,8 @@ void SPIComponent::setup() { if (spi_bus_num == 0) { this->hw_spi_ = &SPI; } else { -#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) +#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || \ + defined(USE_ESP32_VARIANT_ESP32C2) || defined(USE_ESP32_VARIANT_ESP32C6) this->hw_spi_ = new SPIClass(FSPI); // NOLINT(cppcoreguidelines-owning-memory) #else this->hw_spi_ = new SPIClass(HSPI); // NOLINT(cppcoreguidelines-owning-memory) From da8afd36b26a23eed3d1863b4f94fcdad40366ea Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 21 Aug 2023 12:16:14 +1200 Subject: [PATCH 032/111] Change htu21d sensors from required to optional (#5285) --- esphome/components/htu21d/sensor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/htu21d/sensor.py b/esphome/components/htu21d/sensor.py index 37422f0329..2ed318f1c9 100644 --- a/esphome/components/htu21d/sensor.py +++ b/esphome/components/htu21d/sensor.py @@ -23,13 +23,13 @@ CONFIG_SCHEMA = ( cv.Schema( { cv.GenerateID(): cv.declare_id(HTU21DComponent), - cv.Required(CONF_TEMPERATURE): sensor.sensor_schema( + cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( unit_of_measurement=UNIT_CELSIUS, accuracy_decimals=1, device_class=DEVICE_CLASS_TEMPERATURE, state_class=STATE_CLASS_MEASUREMENT, ), - cv.Required(CONF_HUMIDITY): sensor.sensor_schema( + cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( unit_of_measurement=UNIT_PERCENT, accuracy_decimals=1, device_class=DEVICE_CLASS_HUMIDITY, From 03ab23fec8c66680c38b1286b26c25f7805fd7e4 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Mon, 21 Aug 2023 10:17:22 +1000 Subject: [PATCH 033/111] Reserve keyword "clock" (#5279) --- esphome/config_validation.py | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/config_validation.py b/esphome/config_validation.py index cf0b1d3aca..3720757828 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -125,6 +125,7 @@ RESERVED_IDS = [ "char16_t", "char32_t", "class", + "clock", "compl", "concept", "const", From 04433103853f0430e1e73a0817661f729bdd2792 Mon Sep 17 00:00:00 2001 From: Rob Deutsch Date: Mon, 21 Aug 2023 10:20:00 +1000 Subject: [PATCH 034/111] Bump arduino-heatpumpir to v1.0.23 (#5269) --- esphome/components/heatpumpir/climate.py | 3 ++- esphome/components/heatpumpir/heatpumpir.cpp | 1 + esphome/components/heatpumpir/heatpumpir.h | 1 + platformio.ini | 2 +- tests/test1.yaml | 7 +++++++ 5 files changed, 12 insertions(+), 2 deletions(-) diff --git a/esphome/components/heatpumpir/climate.py b/esphome/components/heatpumpir/climate.py index cc8e75dcbd..a043b4a61b 100644 --- a/esphome/components/heatpumpir/climate.py +++ b/esphome/components/heatpumpir/climate.py @@ -33,6 +33,7 @@ PROTOCOLS = { "greeya": Protocol.PROTOCOL_GREEYAA, "greeyan": Protocol.PROTOCOL_GREEYAN, "greeyac": Protocol.PROTOCOL_GREEYAC, + "greeyt": Protocol.PROTOCOL_GREEYT, "hisense_aud": Protocol.PROTOCOL_HISENSE_AUD, "hitachi": Protocol.PROTOCOL_HITACHI, "hyundai": Protocol.PROTOCOL_HYUNDAI, @@ -115,7 +116,7 @@ def to_code(config): cg.add(var.set_max_temperature(config[CONF_MAX_TEMPERATURE])) cg.add(var.set_min_temperature(config[CONF_MIN_TEMPERATURE])) - cg.add_library("tonia/HeatpumpIR", "1.0.20") + cg.add_library("tonia/HeatpumpIR", "1.0.23") if CORE.is_esp8266 or CORE.is_esp32: cg.add_library("crankyoldgit/IRremoteESP8266", "2.7.12") diff --git a/esphome/components/heatpumpir/heatpumpir.cpp b/esphome/components/heatpumpir/heatpumpir.cpp index bed1dc76c0..5e7237b63c 100644 --- a/esphome/components/heatpumpir/heatpumpir.cpp +++ b/esphome/components/heatpumpir/heatpumpir.cpp @@ -27,6 +27,7 @@ const std::map> PROTOCOL_CONSTRUCTOR_MAP {PROTOCOL_GREEYAA, []() { return new GreeYAAHeatpumpIR(); }}, // NOLINT {PROTOCOL_GREEYAN, []() { return new GreeYANHeatpumpIR(); }}, // NOLINT {PROTOCOL_GREEYAC, []() { return new GreeYACHeatpumpIR(); }}, // NOLINT + {PROTOCOL_GREEYT, []() { return new GreeYTHeatpumpIR(); }}, // NOLINT {PROTOCOL_HISENSE_AUD, []() { return new HisenseHeatpumpIR(); }}, // NOLINT {PROTOCOL_HITACHI, []() { return new HitachiHeatpumpIR(); }}, // NOLINT {PROTOCOL_HYUNDAI, []() { return new HyundaiHeatpumpIR(); }}, // NOLINT diff --git a/esphome/components/heatpumpir/heatpumpir.h b/esphome/components/heatpumpir/heatpumpir.h index c60b944111..e8b03b4c26 100644 --- a/esphome/components/heatpumpir/heatpumpir.h +++ b/esphome/components/heatpumpir/heatpumpir.h @@ -27,6 +27,7 @@ enum Protocol { PROTOCOL_GREEYAA, PROTOCOL_GREEYAN, PROTOCOL_GREEYAC, + PROTOCOL_GREEYT, PROTOCOL_HISENSE_AUD, PROTOCOL_HITACHI, PROTOCOL_HYUNDAI, diff --git a/platformio.ini b/platformio.ini index 5da3b9f978..6341d7bde5 100644 --- a/platformio.ini +++ b/platformio.ini @@ -64,7 +64,7 @@ lib_deps = glmnet/Dsmr@0.7 ; dsmr rweather/Crypto@0.4.0 ; dsmr dudanov/MideaUART@1.1.8 ; midea - tonia/HeatpumpIR@1.0.20 ; heatpumpir + tonia/HeatpumpIR@1.0.23 ; heatpumpir build_flags = ${common.build_flags} -DUSE_ARDUINO diff --git a/tests/test1.yaml b/tests/test1.yaml index 80cd9e3987..3a6cfa0c4b 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -2299,6 +2299,13 @@ climate: name: HeatpumpIR Climate min_temperature: 18 max_temperature: 30 + - platform: heatpumpir + protocol: greeyt + horizontal_default: left + vertical_default: up + name: HeatpumpIR Climate + min_temperature: 18 + max_temperature: 30 - platform: midea_ir name: Midea IR use_fahrenheit: true From 2a48b810a4bed9853b899d5fa7e7efaf6f76ef2d Mon Sep 17 00:00:00 2001 From: Stefan Rado Date: Mon, 21 Aug 2023 02:35:13 +0200 Subject: [PATCH 035/111] Fix equality check when setting current-based cover position (#5167) --- esphome/components/current_based/current_based_cover.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/current_based/current_based_cover.cpp b/esphome/components/current_based/current_based_cover.cpp index ff5ad43997..17f67002a3 100644 --- a/esphome/components/current_based/current_based_cover.cpp +++ b/esphome/components/current_based/current_based_cover.cpp @@ -38,7 +38,7 @@ void CurrentBasedCover::control(const CoverCall &call) { } if (call.get_position().has_value()) { auto pos = *call.get_position(); - if (pos == this->position) { + if (fabsf(this->position - pos) < 0.01) { // already at target } else { auto op = pos < this->position ? COVER_OPERATION_CLOSING : COVER_OPERATION_OPENING; From f814b6d47c6de9cef53b374c65e3671d2e10a879 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Aug 2023 04:27:17 +0000 Subject: [PATCH 036/111] Bump platformio from 6.1.9 to 6.1.10 (#5237) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- docker/Dockerfile | 2 +- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index b942b650cb..a590445de9 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -61,7 +61,7 @@ RUN \ # Ubuntu python3-pip is missing wheel pip3 install --no-cache-dir \ wheel==0.37.1 \ - platformio==6.1.7 \ + platformio==6.1.10 \ # Change some platformio settings && platformio settings set enable_telemetry No \ && platformio settings set check_platformio_interval 1000000 \ diff --git a/requirements.txt b/requirements.txt index 4a046014d6..dccb418e8d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,7 @@ tornado==6.3.2 tzlocal==5.0.1 # from time tzdata>=2021.1 # from time pyserial==3.5 -platformio==6.1.9 # When updating platformio, also update Dockerfile +platformio==6.1.10 # When updating platformio, also update Dockerfile esptool==4.6.2 click==8.1.7 esphome-dashboard==20230711.0 From 11ed2d5f1830a475dc7325f4c2e678b316bd14af Mon Sep 17 00:00:00 2001 From: Christian Date: Tue, 22 Aug 2023 23:13:38 +0100 Subject: [PATCH 037/111] Add Invert method for SSD1306 (#5292) --- esphome/components/ssd1306_base/ssd1306_base.cpp | 8 +++++++- esphome/components/ssd1306_base/ssd1306_base.h | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/esphome/components/ssd1306_base/ssd1306_base.cpp b/esphome/components/ssd1306_base/ssd1306_base.cpp index 730e1c8f35..3cacd473d1 100644 --- a/esphome/components/ssd1306_base/ssd1306_base.cpp +++ b/esphome/components/ssd1306_base/ssd1306_base.cpp @@ -132,7 +132,7 @@ void SSD1306::setup() { this->command(SSD1306_COMMAND_DISPLAY_ALL_ON_RESUME); // Inverse display mode (0xA6, 0xA7) - this->command(SSD1306_COMMAND_NORMAL_DISPLAY | this->invert_); + this->set_invert(this->invert_); // Disable scrolling mode (0x2E) this->command(SSD1306_COMMAND_DEACTIVATE_SCROLL); @@ -190,6 +190,12 @@ void SSD1306::update() { this->do_update_(); this->display(); } + +void SSD1306::set_invert(bool invert) { + this->invert_ = invert; + // Inverse display mode (0xA6, 0xA7) + this->command(SSD1306_COMMAND_NORMAL_DISPLAY | this->invert_); +} void SSD1306::set_contrast(float contrast) { // validation this->contrast_ = clamp(contrast, 0.0F, 1.0F); diff --git a/esphome/components/ssd1306_base/ssd1306_base.h b/esphome/components/ssd1306_base/ssd1306_base.h index 7402ae3af2..4b0e9bb80e 100644 --- a/esphome/components/ssd1306_base/ssd1306_base.h +++ b/esphome/components/ssd1306_base/ssd1306_base.h @@ -43,6 +43,7 @@ class SSD1306 : public PollingComponent, public display::DisplayBuffer { void init_offset_x(uint8_t offset_x) { this->offset_x_ = offset_x; } void init_offset_y(uint8_t offset_y) { this->offset_y_ = offset_y; } void init_invert(bool invert) { this->invert_ = invert; } + void set_invert(bool invert); bool is_on(); void turn_on(); void turn_off(); From b20bae23ccac3903239ca68bfb3a4add25b7b44d Mon Sep 17 00:00:00 2001 From: Sebastian Rasor <92653912+sebastianrasor@users.noreply.github.com> Date: Tue, 22 Aug 2023 20:01:34 -0500 Subject: [PATCH 038/111] Introduce cv.temperature_delta and fix problematic thermostat configuration behavior (#5297) --- esphome/components/thermostat/climate.py | 14 +++++++------- esphome/config_validation.py | 21 +++++++++++++++++++++ 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/esphome/components/thermostat/climate.py b/esphome/components/thermostat/climate.py index 9a57f6a337..cca46609db 100644 --- a/esphome/components/thermostat/climate.py +++ b/esphome/components/thermostat/climate.py @@ -591,11 +591,11 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_DEFAULT_TARGET_TEMPERATURE_LOW): cv.temperature, cv.Optional( CONF_SET_POINT_MINIMUM_DIFFERENTIAL, default=0.5 - ): cv.temperature, - cv.Optional(CONF_COOL_DEADBAND, default=0.5): cv.temperature, - cv.Optional(CONF_COOL_OVERRUN, default=0.5): cv.temperature, - cv.Optional(CONF_HEAT_DEADBAND, default=0.5): cv.temperature, - cv.Optional(CONF_HEAT_OVERRUN, default=0.5): cv.temperature, + ): cv.temperature_delta, + cv.Optional(CONF_COOL_DEADBAND, default=0.5): cv.temperature_delta, + cv.Optional(CONF_COOL_OVERRUN, default=0.5): cv.temperature_delta, + cv.Optional(CONF_HEAT_DEADBAND, default=0.5): cv.temperature_delta, + cv.Optional(CONF_HEAT_OVERRUN, default=0.5): cv.temperature_delta, cv.Optional(CONF_MAX_COOLING_RUN_TIME): cv.positive_time_period_seconds, cv.Optional(CONF_MAX_HEATING_RUN_TIME): cv.positive_time_period_seconds, cv.Optional(CONF_MIN_COOLING_OFF_TIME): cv.positive_time_period_seconds, @@ -608,8 +608,8 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_MIN_HEATING_OFF_TIME): cv.positive_time_period_seconds, cv.Optional(CONF_MIN_HEATING_RUN_TIME): cv.positive_time_period_seconds, cv.Required(CONF_MIN_IDLE_TIME): cv.positive_time_period_seconds, - cv.Optional(CONF_SUPPLEMENTAL_COOLING_DELTA): cv.temperature, - cv.Optional(CONF_SUPPLEMENTAL_HEATING_DELTA): cv.temperature, + cv.Optional(CONF_SUPPLEMENTAL_COOLING_DELTA): cv.temperature_delta, + cv.Optional(CONF_SUPPLEMENTAL_HEATING_DELTA): cv.temperature_delta, cv.Optional( CONF_FAN_ONLY_ACTION_USES_FAN_MODE_TIMER, default=False ): cv.boolean, diff --git a/esphome/config_validation.py b/esphome/config_validation.py index 3720757828..ed87e98078 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -929,6 +929,27 @@ def temperature(value): raise err +def temperature_delta(value): + err = None + try: + return _temperature_c(value) + except Invalid as orig_err: + err = orig_err + + try: + return _temperature_k(value) + except Invalid: + pass + + try: + fahrenheit = _temperature_f(value) + return fahrenheit * (5 / 9) + except Invalid: + pass + + raise err + + _color_temperature_mireds = float_with_unit("Color Temperature", r"(mireds|Mireds)") _color_temperature_kelvin = float_with_unit("Color Temperature", r"(K|Kelvin)") From c4adb30ab2b355a28c6977088a68c59d95ea1c5e Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Tue, 29 Aug 2023 00:11:58 -0500 Subject: [PATCH 039/111] Update PSRAM config params for IDF4+ (#5298) --- esphome/components/psram/__init__.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/esphome/components/psram/__init__.py b/esphome/components/psram/__init__.py index ac6d034514..9399e51ded 100644 --- a/esphome/components/psram/__init__.py +++ b/esphome/components/psram/__init__.py @@ -1,6 +1,6 @@ import esphome.codegen as cg import esphome.config_validation as cv -from esphome.components.esp32 import add_idf_sdkconfig_option +from esphome.components.esp32 import add_idf_sdkconfig_option, get_esp32_variant from esphome.core import CORE from esphome.const import ( CONF_ID, @@ -21,7 +21,11 @@ async def to_code(config): cg.add_build_flag("-DBOARD_HAS_PSRAM") if CORE.using_esp_idf: - add_idf_sdkconfig_option("CONFIG_ESP32_SPIRAM_SUPPORT", True) + add_idf_sdkconfig_option( + f"CONFIG_{get_esp32_variant().upper()}_SPIRAM_SUPPORT", True + ) + add_idf_sdkconfig_option("CONFIG_SPIRAM", True) + add_idf_sdkconfig_option("CONFIG_SPIRAM_USE", True) add_idf_sdkconfig_option("CONFIG_SPIRAM_USE_CAPS_ALLOC", True) add_idf_sdkconfig_option("CONFIG_SPIRAM_IGNORE_NOTFOUND", True) From 45879e3100c27c49ff103e70e9e4d12a8cdac713 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 29 Aug 2023 00:25:43 -0500 Subject: [PATCH 040/111] Fix legacy zeroconf record update method (#5294) --- esphome/zeroconf.py | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/esphome/zeroconf.py b/esphome/zeroconf.py index b0dddfd152..924d7253df 100644 --- a/esphome/zeroconf.py +++ b/esphome/zeroconf.py @@ -1,19 +1,19 @@ +import logging import socket import threading import time -from typing import Optional -import logging from dataclasses import dataclass +from typing import Optional from zeroconf import ( DNSAddress, DNSOutgoing, - DNSRecord, DNSQuestion, + RecordUpdate, RecordUpdateListener, - Zeroconf, ServiceBrowser, ServiceStateChange, + Zeroconf, current_time_millis, ) @@ -24,17 +24,28 @@ _LOGGER = logging.getLogger(__name__) class HostResolver(RecordUpdateListener): + """Resolve a host name to an IP address.""" + def __init__(self, name: str): self.name = name self.address: Optional[bytes] = None - def update_record(self, zc: Zeroconf, now: float, record: DNSRecord) -> None: - if record is None: - return - if record.type == _TYPE_A: - assert isinstance(record, DNSAddress) - if record.name == self.name: - self.address = record.address + def async_update_records( + self, zc: Zeroconf, now: float, records: list[RecordUpdate] + ) -> None: + """Update multiple records in one shot. + + This will run in zeroconf's event loop thread so it + must be thread-safe. + """ + for record_update in records: + record, _ = record_update + if record is None: + continue + if record.type == _TYPE_A: + assert isinstance(record, DNSAddress) + if record.name == self.name: + self.address = record.address def request(self, zc: Zeroconf, timeout: float) -> bool: now = time.time() From 45152ad55edf0d05500e28d5aaa590889b4c897a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 29 Aug 2023 19:04:37 +1200 Subject: [PATCH 041/111] Bump zeroconf from 0.80.0 to 0.86.0 (#5308) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index dccb418e8d..b23964828c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,7 +11,7 @@ esptool==4.6.2 click==8.1.7 esphome-dashboard==20230711.0 aioesphomeapi==15.0.0 -zeroconf==0.80.0 +zeroconf==0.86.0 # esp-idf requires this, but doesn't bundle it by default # https://github.com/espressif/esp-idf/blob/220590d599e134d7a5e7f1e683cc4550349ffbf8/requirements.txt#L24 From 78cb0986914aa3763058f9f46175782ce4218bf2 Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Tue, 29 Aug 2023 03:50:29 -0500 Subject: [PATCH 042/111] Add PSRAM mode and speed config (#5312) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/psram/__init__.py | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/esphome/components/psram/__init__.py b/esphome/components/psram/__init__.py index 9399e51ded..f7a2ef7b92 100644 --- a/esphome/components/psram/__init__.py +++ b/esphome/components/psram/__init__.py @@ -4,6 +4,8 @@ from esphome.components.esp32 import add_idf_sdkconfig_option, get_esp32_variant from esphome.core import CORE from esphome.const import ( CONF_ID, + CONF_MODE, + CONF_SPEED, ) CODEOWNERS = ["@esphome/core"] @@ -11,8 +13,26 @@ CODEOWNERS = ["@esphome/core"] psram_ns = cg.esphome_ns.namespace("psram") PsramComponent = psram_ns.class_("PsramComponent", cg.Component) +SPIRAM_MODES = { + "quad": "CONFIG_SPIRAM_MODE_QUAD", + "octal": "CONFIG_SPIRAM_MODE_OCT", +} + +SPIRAM_SPEEDS = { + 40e6: "CONFIG_SPIRAM_SPEED_40M", + 80e6: "CONFIG_SPIRAM_SPEED_80M", + 120e6: "CONFIG_SPIRAM_SPEED_120M", +} + CONFIG_SCHEMA = cv.All( - cv.Schema({cv.GenerateID(): cv.declare_id(PsramComponent)}), cv.only_on_esp32 + cv.Schema( + { + cv.GenerateID(): cv.declare_id(PsramComponent), + cv.Optional(CONF_MODE): cv.enum(SPIRAM_MODES, lower=True), + cv.Optional(CONF_SPEED): cv.All(cv.frequency, cv.one_of(*SPIRAM_SPEEDS)), + } + ), + cv.only_on_esp32, ) @@ -29,5 +49,10 @@ async def to_code(config): add_idf_sdkconfig_option("CONFIG_SPIRAM_USE_CAPS_ALLOC", True) add_idf_sdkconfig_option("CONFIG_SPIRAM_IGNORE_NOTFOUND", True) + if CONF_MODE in config: + add_idf_sdkconfig_option(f"{SPIRAM_MODES[config[CONF_MODE]]}", True) + if CONF_SPEED in config: + add_idf_sdkconfig_option(f"{SPIRAM_SPEEDS[config[CONF_SPEED]]}", True) + var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) From cdb67fc90e974cca8d463b7c466c3ca29225956e Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Thu, 31 Aug 2023 19:12:25 +1000 Subject: [PATCH 043/111] Add extra SLPOUT for waking up some ST7789 chips (#5319) --- esphome/components/st7789v/st7789v.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/st7789v/st7789v.cpp b/esphome/components/st7789v/st7789v.cpp index 0e7c9b9123..c1e3f07e38 100644 --- a/esphome/components/st7789v/st7789v.cpp +++ b/esphome/components/st7789v/st7789v.cpp @@ -19,6 +19,7 @@ void ST7789V::setup() { this->write_command_(ST7789_SLPOUT); // Sleep out delay(120); // NOLINT + this->write_command_(ST7789_SLPOUT); // this->write_command_(ST7789_NORON); // Normal display mode on From 01f6791d1c1fc2efd64575784663643f64c5f6bf Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Fri, 1 Sep 2023 09:43:24 +1000 Subject: [PATCH 044/111] 7789 controller fixes take 2 (#5320) * Fix 7789 clock mode and increase clock rate. * Reverse change from dev. * Speed up 8 bit color. * Tweak buffer size --- esphome/components/st7789v/st7789v.cpp | 17 +++++++++++++---- esphome/components/st7789v/st7789v.h | 4 ++-- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/esphome/components/st7789v/st7789v.cpp b/esphome/components/st7789v/st7789v.cpp index c1e3f07e38..f29182e634 100644 --- a/esphome/components/st7789v/st7789v.cpp +++ b/esphome/components/st7789v/st7789v.cpp @@ -5,6 +5,7 @@ namespace esphome { namespace st7789v { static const char *const TAG = "st7789v"; +static const size_t TEMP_BUFFER_SIZE = 128; void ST7789V::setup() { ESP_LOGCONFIG(TAG, "Setting up SPI ST7789V..."); @@ -19,7 +20,6 @@ void ST7789V::setup() { this->write_command_(ST7789_SLPOUT); // Sleep out delay(120); // NOLINT - this->write_command_(ST7789_SLPOUT); // this->write_command_(ST7789_NORON); // Normal display mode on @@ -206,15 +206,23 @@ void ST7789V::write_display_data() { this->dc_pin_->digital_write(true); if (this->eightbitcolor_) { + uint8_t temp_buffer[TEMP_BUFFER_SIZE]; + size_t temp_index = 0; for (int line = 0; line < this->get_buffer_length_(); line = line + this->get_width_internal()) { for (int index = 0; index < this->get_width_internal(); ++index) { auto color = display::ColorUtil::color_to_565( display::ColorUtil::to_color(this->buffer_[index + line], display::ColorOrder::COLOR_ORDER_RGB, display::ColorBitness::COLOR_BITNESS_332, true)); - this->write_byte((color >> 8) & 0xff); - this->write_byte(color & 0xff); + temp_buffer[temp_index++] = (uint8_t) (color >> 8); + temp_buffer[temp_index++] = (uint8_t) color; + if (temp_index == TEMP_BUFFER_SIZE) { + this->write_array(temp_buffer, TEMP_BUFFER_SIZE); + temp_index = 0; + } } } + if (temp_index != 0) + this->write_array(temp_buffer, temp_index); } else { this->write_array(this->buffer_, this->get_buffer_length_()); } @@ -229,9 +237,10 @@ void ST7789V::init_reset_() { delay(1); // Trigger Reset this->reset_pin_->digital_write(false); - delay(10); + delay(1); // Wake up this->reset_pin_->digital_write(true); + delay(5); } } diff --git a/esphome/components/st7789v/st7789v.h b/esphome/components/st7789v/st7789v.h index ccbe50cf85..56132e8ea2 100644 --- a/esphome/components/st7789v/st7789v.h +++ b/esphome/components/st7789v/st7789v.h @@ -117,8 +117,8 @@ static const uint8_t ST7789_MADCTL_COLOR_ORDER = ST7789_MADCTL_BGR; class ST7789V : public PollingComponent, public display::DisplayBuffer, - public spi::SPIDevice { + public spi::SPIDevice { public: void set_model(ST7789VModel model); void set_dc_pin(GPIOPin *dc_pin) { this->dc_pin_ = dc_pin; } From 3003485dc63e850c4e8cc4b67c3b87826265ea0b Mon Sep 17 00:00:00 2001 From: luka6000 Date: Fri, 1 Sep 2023 03:20:21 +0200 Subject: [PATCH 045/111] fix to PR # 3887 MQTT connection not using discovery: false (#5275) --- esphome/components/mqtt/mqtt_client.cpp | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/esphome/components/mqtt/mqtt_client.cpp b/esphome/components/mqtt/mqtt_client.cpp index d3f759c072..1d804170f6 100644 --- a/esphome/components/mqtt/mqtt_client.cpp +++ b/esphome/components/mqtt/mqtt_client.cpp @@ -66,25 +66,28 @@ void MQTTClientComponent::setup() { } #endif - this->subscribe( - "esphome/discover", [this](const std::string &topic, const std::string &payload) { this->send_device_info_(); }, - 2); + if (this->is_discovery_enabled()) { + this->subscribe( + "esphome/discover", [this](const std::string &topic, const std::string &payload) { this->send_device_info_(); }, + 2); - std::string topic = "esphome/ping/"; - topic.append(App.get_name()); - this->subscribe( - topic, [this](const std::string &topic, const std::string &payload) { this->send_device_info_(); }, 2); + std::string topic = "esphome/ping/"; + topic.append(App.get_name()); + this->subscribe( + topic, [this](const std::string &topic, const std::string &payload) { this->send_device_info_(); }, 2); + } this->last_connected_ = millis(); this->start_dnslookup_(); } void MQTTClientComponent::send_device_info_() { - if (!this->is_connected()) { + if (!this->is_connected() or !this->is_discovery_enabled()) { return; } std::string topic = "esphome/discover/"; topic.append(App.get_name()); + this->publish_json( topic, [](JsonObject root) { From f14419bab589d911d55f845b21f7ba238cd79d07 Mon Sep 17 00:00:00 2001 From: Jimmy Hedman Date: Fri, 1 Sep 2023 03:21:01 +0200 Subject: [PATCH 046/111] Bump Arduino Pico to 3.4.0 (#5321) --- esphome/components/rp2040/__init__.py | 6 +++--- platformio.ini | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/esphome/components/rp2040/__init__.py b/esphome/components/rp2040/__init__.py index dafafc531c..9e9db02de1 100644 --- a/esphome/components/rp2040/__init__.py +++ b/esphome/components/rp2040/__init__.py @@ -62,7 +62,7 @@ def _format_framework_arduino_version(ver: cv.Version) -> str: # The default/recommended arduino framework version # - https://github.com/earlephilhower/arduino-pico/releases # - https://api.registry.platformio.org/v3/packages/earlephilhower/tool/framework-arduinopico -RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(3, 3, 0) +RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(3, 4, 0) # The platformio/raspberrypi version to use for arduino frameworks # - https://github.com/platformio/platform-raspberrypi/releases @@ -73,8 +73,8 @@ ARDUINO_PLATFORM_VERSION = cv.Version(1, 9, 0) def _arduino_check_versions(value): value = value.copy() lookups = { - "dev": (cv.Version(3, 3, 0), "https://github.com/earlephilhower/arduino-pico"), - "latest": (cv.Version(3, 3, 0), None), + "dev": (cv.Version(3, 4, 0), "https://github.com/earlephilhower/arduino-pico"), + "latest": (cv.Version(3, 4, 0), None), "recommended": (RECOMMENDED_ARDUINO_FRAMEWORK_VERSION, None), } diff --git a/platformio.ini b/platformio.ini index 6341d7bde5..64c7bec6e8 100644 --- a/platformio.ini +++ b/platformio.ini @@ -157,7 +157,7 @@ board_build.filesystem_size = 0.5m platform = https://github.com/maxgerhardt/platform-raspberrypi.git platform_packages = ; earlephilhower/framework-arduinopico@~1.20602.0 ; Cannot use the platformio package until old releases stop getting deleted - earlephilhower/framework-arduinopico@https://github.com/earlephilhower/arduino-pico/releases/download/3.3.0/rp2040-3.3.0.zip + earlephilhower/framework-arduinopico@https://github.com/earlephilhower/arduino-pico/releases/download/3.4.0/rp2040-3.4.0.zip framework = arduino lib_deps = From 19d53c6643df22823c2b20b05858f06e093c628f Mon Sep 17 00:00:00 2001 From: Daniel Dunn Date: Thu, 31 Aug 2023 20:02:26 -0600 Subject: [PATCH 047/111] Use gzip compression for the web server component's static resources (#5291) Co-authored-by: Daniel Dunn --- esphome/components/web_server/__init__.py | 10 ++++++++-- esphome/components/web_server/web_server.cpp | 3 +++ tests/test3.1.yaml | 4 ++++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/esphome/components/web_server/__init__.py b/esphome/components/web_server/__init__.py index ab54ae8582..b1cf8a5de6 100644 --- a/esphome/components/web_server/__init__.py +++ b/esphome/components/web_server/__init__.py @@ -1,3 +1,4 @@ +import gzip import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import web_server_base @@ -109,9 +110,13 @@ def build_index_html(config) -> str: return html -def add_resource_as_progmem(resource_name: str, content: str) -> None: +def add_resource_as_progmem( + resource_name: str, content: str, compress: bool = True +) -> None: """Add a resource to progmem.""" content_encoded = content.encode("utf-8") + if compress: + content_encoded = gzip.compress(content_encoded) content_encoded_size = len(content_encoded) bytes_as_int = ", ".join(str(x) for x in content_encoded) uint8_t = f"const uint8_t ESPHOME_WEBSERVER_{resource_name}[{content_encoded_size}] PROGMEM = {{{bytes_as_int}}}" @@ -137,7 +142,8 @@ async def to_code(config): cg.add_define("USE_WEBSERVER_PORT", config[CONF_PORT]) cg.add_define("USE_WEBSERVER_VERSION", version) if version == 2: - add_resource_as_progmem("INDEX_HTML", build_index_html(config)) + # Don't compress the index HTML as the data sizes are almost the same. + add_resource_as_progmem("INDEX_HTML", build_index_html(config), compress=False) else: cg.add(var.set_css_url(config[CONF_CSS_URL])) cg.add(var.set_js_url(config[CONF_JS_URL])) diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 01057fead6..e350e1b140 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -328,6 +328,7 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) { void WebServer::handle_index_request(AsyncWebServerRequest *request) { AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", ESPHOME_WEBSERVER_INDEX_HTML, ESPHOME_WEBSERVER_INDEX_HTML_SIZE); + // No gzip header here because the HTML file is so small request->send(response); } #endif @@ -336,6 +337,7 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) { void WebServer::handle_css_request(AsyncWebServerRequest *request) { AsyncWebServerResponse *response = request->beginResponse_P(200, "text/css", ESPHOME_WEBSERVER_CSS_INCLUDE, ESPHOME_WEBSERVER_CSS_INCLUDE_SIZE); + response->addHeader("Content-Encoding", "gzip"); request->send(response); } #endif @@ -344,6 +346,7 @@ void WebServer::handle_css_request(AsyncWebServerRequest *request) { void WebServer::handle_js_request(AsyncWebServerRequest *request) { AsyncWebServerResponse *response = request->beginResponse_P(200, "text/javascript", ESPHOME_WEBSERVER_JS_INCLUDE, ESPHOME_WEBSERVER_JS_INCLUDE_SIZE); + response->addHeader("Content-Encoding", "gzip"); request->send(response); } #endif diff --git a/tests/test3.1.yaml b/tests/test3.1.yaml index 46bc014204..ea8dc337be 100644 --- a/tests/test3.1.yaml +++ b/tests/test3.1.yaml @@ -21,6 +21,10 @@ wifi: ssid: "MySSID" password: "password1" +web_server: + port: 80 + version: 2 + i2c: sda: 4 scl: 5 From 712634b30102026b950b416efac4b17aae249ff3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Sep 2023 14:03:10 +1200 Subject: [PATCH 048/111] Bump zeroconf from 0.86.0 to 0.88.0 (#5315) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index b23964828c..715dbef4f2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,7 +11,7 @@ esptool==4.6.2 click==8.1.7 esphome-dashboard==20230711.0 aioesphomeapi==15.0.0 -zeroconf==0.86.0 +zeroconf==0.88.0 # esp-idf requires this, but doesn't bundle it by default # https://github.com/espressif/esp-idf/blob/220590d599e134d7a5e7f1e683cc4550349ffbf8/requirements.txt#L24 From f8a03be2f1fbc641131679a6be628deab277e8fb Mon Sep 17 00:00:00 2001 From: Josh Barnard Date: Thu, 31 Aug 2023 22:10:42 -0700 Subject: [PATCH 049/111] Adding heating coil and fan icons, enum device_class (#5325) * Adding heating cool and fan icons. * Adding Enum device_class as well. --- esphome/const.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/esphome/const.py b/esphome/const.py index 373d6bd8c9..e0642247ab 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -842,6 +842,7 @@ ICON_COUNTER = "mdi:counter" ICON_CURRENT_AC = "mdi:current-ac" ICON_DATABASE = "mdi:database" ICON_EMPTY = "" +ICON_FAN = "mdi:fan" ICON_FINGERPRINT = "mdi:fingerprint" ICON_FLASH = "mdi:flash" ICON_FLASK = "mdi:flask" @@ -850,6 +851,7 @@ ICON_FLOWER = "mdi:flower" ICON_GAS_CYLINDER = "mdi:gas-cylinder" ICON_GAUGE = "mdi:gauge" ICON_GRAIN = "mdi:grain" +ICON_HEATING_COIL = "mdi:heating-coil" ICON_KEY_PLUS = "mdi:key-plus" ICON_LIGHTBULB = "mdi:lightbulb" ICON_MAGNET = "mdi:magnet" @@ -965,6 +967,7 @@ DEVICE_CLASS_DURATION = "duration" DEVICE_CLASS_EMPTY = "" DEVICE_CLASS_ENERGY = "energy" DEVICE_CLASS_ENERGY_STORAGE = "energy_storage" +DEVICE_CLASS_ENUM = "enum" DEVICE_CLASS_FREQUENCY = "frequency" DEVICE_CLASS_GARAGE = "garage" DEVICE_CLASS_GARAGE_DOOR = "garage_door" From c3332e4a394b6fb4d1d44e321e168c1fc39fec09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Fri, 1 Sep 2023 08:17:33 +0200 Subject: [PATCH 050/111] Add dashboard API to get firmware binaries (#4675) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/esp32/__init__.py | 17 ++++++ esphome/components/esp8266/__init__.py | 11 ++++ esphome/components/rp2040/__init__.py | 11 ++++ esphome/dashboard/dashboard.py | 80 ++++++++++++++++++-------- 4 files changed, 94 insertions(+), 25 deletions(-) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index d158528066..ee18315518 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -84,6 +84,23 @@ def get_board(core_obj=None): return (core_obj or CORE).data[KEY_ESP32][KEY_BOARD] +def get_download_types(storage_json): + return [ + { + "title": "Modern format", + "description": "For use with ESPHome Web and other tools.", + "file": "firmware-factory.bin", + "download": f"{storage_json.name}-factory.bin", + }, + { + "title": "Legacy format", + "description": "For use with ESPHome Flasher.", + "file": "firmware.bin", + "download": f"{storage_json.name}.bin", + }, + ] + + def only_on_variant(*, supported=None, unsupported=None): """Config validator for features only available on some ESP32 variants.""" if supported is not None and not isinstance(supported, list): diff --git a/esphome/components/esp8266/__init__.py b/esphome/components/esp8266/__init__.py index 674f433d52..412c2d903f 100644 --- a/esphome/components/esp8266/__init__.py +++ b/esphome/components/esp8266/__init__.py @@ -50,6 +50,17 @@ def set_core_data(config): return config +def get_download_types(storage_json): + return [ + { + "title": "Standard format", + "description": "For flashing ESP8266.", + "file": "firmware.bin", + "download": f"{storage_json.name}.bin", + }, + ] + + def _format_framework_arduino_version(ver: cv.Version) -> str: # format the given arduino (https://github.com/esp8266/Arduino/releases) version to # a PIO platformio/framework-arduinoespressif8266 value diff --git a/esphome/components/rp2040/__init__.py b/esphome/components/rp2040/__init__.py index 9e9db02de1..b31192f73f 100644 --- a/esphome/components/rp2040/__init__.py +++ b/esphome/components/rp2040/__init__.py @@ -42,6 +42,17 @@ def set_core_data(config): return config +def get_download_types(storage_json): + return [ + { + "title": "UF2 format", + "description": "For copying to RP2040 over USB.", + "file": "firmware.uf2", + "download": f"{storage_json.name}.uf2", + }, + ] + + def _format_framework_arduino_version(ver: cv.Version) -> str: # The most recent releases have not been uploaded to platformio so grabbing them directly from # the GitHub release is one path forward for now. diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index eae004fa09..c05e1fcfcc 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -530,11 +530,43 @@ class ImportRequestHandler(BaseHandler): self.finish() +class DownloadListRequestHandler(BaseHandler): + @authenticated + @bind_config + def get(self, configuration=None): + storage_path = ext_storage_path(settings.config_dir, configuration) + storage_json = StorageJSON.load(storage_path) + if storage_json is None: + self.send_error(404) + return + + from esphome.components.esp32 import get_download_types as esp32_types + from esphome.components.esp8266 import get_download_types as esp8266_types + from esphome.components.rp2040 import get_download_types as rp2040_types + + downloads = [] + platform = storage_json.target_platform.lower() + if platform == const.PLATFORM_RP2040: + downloads = rp2040_types(storage_json) + elif platform == const.PLATFORM_ESP8266: + downloads = esp8266_types(storage_json) + elif platform == const.PLATFORM_ESP32: + downloads = esp32_types(storage_json) + else: + self.send_error(418) + return + + self.set_status(200) + self.set_header("content-type", "application/json") + self.write(json.dumps(downloads)) + self.finish() + return + + class DownloadBinaryRequestHandler(BaseHandler): @authenticated @bind_config def get(self, configuration=None): - type = self.get_argument("type", "firmware.bin") compressed = self.get_argument("compressed", "0") == "1" storage_path = ext_storage_path(settings.config_dir, configuration) @@ -543,27 +575,22 @@ class DownloadBinaryRequestHandler(BaseHandler): self.send_error(404) return - if storage_json.target_platform.lower() == const.PLATFORM_RP2040: - filename = f"{storage_json.name}.uf2" - path = storage_json.firmware_bin_path.replace( - "firmware.bin", "firmware.uf2" - ) + # fallback to type=, but prioritize file= + file_name = self.get_argument("type", None) + file_name = self.get_argument("file", file_name) + if file_name is None: + self.send_error(400) + return + file_name = file_name.replace("..", "").lstrip("/") + # get requested download name, or build it based on filename + download_name = self.get_argument( + "download", + f"{storage_json.name}-{file_name}", + ) + path = os.path.dirname(storage_json.firmware_bin_path) + path = os.path.join(path, file_name) - elif storage_json.target_platform.lower() == const.PLATFORM_ESP8266: - filename = f"{storage_json.name}.bin" - path = storage_json.firmware_bin_path - - elif type == "firmware.bin": - filename = f"{storage_json.name}.bin" - path = storage_json.firmware_bin_path - - elif type == "firmware-factory.bin": - filename = f"{storage_json.name}-factory.bin" - path = storage_json.firmware_bin_path.replace( - "firmware.bin", "firmware-factory.bin" - ) - - else: + if not Path(path).is_file(): args = ["esphome", "idedata", settings.rel_path(configuration)] rc, stdout, _ = run_system_command(*args) @@ -575,9 +602,9 @@ class DownloadBinaryRequestHandler(BaseHandler): found = False for image in idedata.extra_flash_images: - if image.path.endswith(type): + if image.path.endswith(file_name): path = image.path - filename = type + download_name = file_name found = True break @@ -585,10 +612,12 @@ class DownloadBinaryRequestHandler(BaseHandler): self.send_error(404) return - filename = filename + ".gz" if compressed else filename + download_name = download_name + ".gz" if compressed else download_name self.set_header("Content-Type", "application/octet-stream") - self.set_header("Content-Disposition", f'attachment; filename="{filename}"') + self.set_header( + "Content-Disposition", f'attachment; filename="{download_name}"' + ) self.set_header("Cache-Control", "no-cache") if not Path(path).is_file(): self.send_error(404) @@ -1259,6 +1288,7 @@ def make_app(debug=get_bool_env(ENV_DEV)): (f"{rel}update-all", EsphomeUpdateAllHandler), (f"{rel}info", InfoRequestHandler), (f"{rel}edit", EditRequestHandler), + (f"{rel}downloads", DownloadListRequestHandler), (f"{rel}download.bin", DownloadBinaryRequestHandler), (f"{rel}serial-ports", SerialPortRequestHandler), (f"{rel}ping", PingRequestHandler), From bec53f97a251efa343796146796e6c4f2fb59ee1 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Sat, 2 Sep 2023 08:41:52 +1200 Subject: [PATCH 051/111] Attempt to fix secret blurring (#5326) --- esphome/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/__main__.py b/esphome/__main__.py index ca5fc1c008..697adc03a3 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -371,7 +371,7 @@ def command_config(args, config): # add the console decoration so the front-end can hide the secrets if not args.show_secrets: output = re.sub( - r"(password|key|psk|ssid)\:\s(.*)", r"\1: \\033[5m\2\\033[6m", output + r"(password|key|psk|ssid)\: (.+)", r"\1: \\033[5m\2\\033[6m", output ) safe_print(output) _LOGGER.info("Configuration is valid!") From 211b3eddeaa5372e7fb3128fe7fc42653fa71ce2 Mon Sep 17 00:00:00 2001 From: kahrendt Date: Fri, 1 Sep 2023 16:55:59 -0400 Subject: [PATCH 052/111] Bugfix: disable channels after IO if multiple tca9548a I2C multiplexers are configured (#5317) --- esphome/components/tca9548a/tca9548a.cpp | 27 ++++++++++++++---------- esphome/components/tca9548a/tca9548a.h | 4 +++- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/esphome/components/tca9548a/tca9548a.cpp b/esphome/components/tca9548a/tca9548a.cpp index caa3dd0655..770fd5e47c 100644 --- a/esphome/components/tca9548a/tca9548a.cpp +++ b/esphome/components/tca9548a/tca9548a.cpp @@ -7,23 +7,27 @@ namespace tca9548a { static const char *const TAG = "tca9548a"; i2c::ErrorCode TCA9548AChannel::readv(uint8_t address, i2c::ReadBuffer *buffers, size_t cnt) { - auto err = parent_->switch_to_channel(channel_); + auto err = this->parent_->switch_to_channel(channel_); if (err != i2c::ERROR_OK) return err; - return parent_->bus_->readv(address, buffers, cnt); + err = this->parent_->bus_->readv(address, buffers, cnt); + this->parent_->disable_all_channels(); + return err; } i2c::ErrorCode TCA9548AChannel::writev(uint8_t address, i2c::WriteBuffer *buffers, size_t cnt, bool stop) { - auto err = parent_->switch_to_channel(channel_); + auto err = this->parent_->switch_to_channel(channel_); if (err != i2c::ERROR_OK) return err; - return parent_->bus_->writev(address, buffers, cnt, stop); + err = this->parent_->bus_->writev(address, buffers, cnt, stop); + this->parent_->disable_all_channels(); + return err; } void TCA9548AComponent::setup() { ESP_LOGCONFIG(TAG, "Setting up TCA9548A..."); uint8_t status = 0; if (this->read(&status, 1) != i2c::ERROR_OK) { - ESP_LOGI(TAG, "TCA9548A failed"); + ESP_LOGE(TAG, "TCA9548A failed"); this->mark_failed(); return; } @@ -37,15 +41,16 @@ void TCA9548AComponent::dump_config() { i2c::ErrorCode TCA9548AComponent::switch_to_channel(uint8_t channel) { if (this->is_failed()) return i2c::ERROR_NOT_INITIALIZED; - if (current_channel_ == channel) - return i2c::ERROR_OK; uint8_t channel_val = 1 << channel; - auto err = this->write(&channel_val, 1); - if (err == i2c::ERROR_OK) { - current_channel_ = channel; + return this->write(&channel_val, 1); +} + +void TCA9548AComponent::disable_all_channels() { + if (this->write(&TCA9548A_DISABLE_CHANNELS_COMMAND, 1) != i2c::ERROR_OK) { + ESP_LOGE(TAG, "Failed to disable all channels."); + this->status_set_error(); // couldn't disable channels, set error status } - return err; } } // namespace tca9548a diff --git a/esphome/components/tca9548a/tca9548a.h b/esphome/components/tca9548a/tca9548a.h index 02553f8cd0..08f1674d11 100644 --- a/esphome/components/tca9548a/tca9548a.h +++ b/esphome/components/tca9548a/tca9548a.h @@ -6,6 +6,8 @@ namespace esphome { namespace tca9548a { +static const uint8_t TCA9548A_DISABLE_CHANNELS_COMMAND = 0x00; + class TCA9548AComponent; class TCA9548AChannel : public i2c::I2CBus { public: @@ -28,10 +30,10 @@ class TCA9548AComponent : public Component, public i2c::I2CDevice { void update(); i2c::ErrorCode switch_to_channel(uint8_t channel); + void disable_all_channels(); protected: friend class TCA9548AChannel; - uint8_t current_channel_ = 255; }; } // namespace tca9548a } // namespace esphome From 2bb5f53b98fbbca09755d1715a8f6d066786fa25 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Sat, 2 Sep 2023 08:10:08 +1000 Subject: [PATCH 053/111] Make uart error message go away (#5329) * Make error message in log go away. * Test for IDF version. --- esphome/components/logger/logger.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/esphome/components/logger/logger.cpp b/esphome/components/logger/logger.cpp index 86ebb53764..758e9c1f98 100644 --- a/esphome/components/logger/logger.cpp +++ b/esphome/components/logger/logger.cpp @@ -4,6 +4,7 @@ #ifdef USE_ESP_IDF #include #include "freertos/FreeRTOS.h" +#include "esp_idf_version.h" #endif // USE_ESP_IDF #if defined(USE_ESP32_FRAMEWORK_ARDUINO) || defined(USE_ESP_IDF) @@ -239,6 +240,9 @@ void Logger::pre_setup() { uart_config.parity = UART_PARITY_DISABLE; uart_config.stop_bits = UART_STOP_BITS_1; uart_config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE; +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) + uart_config.source_clk = UART_SCLK_DEFAULT; +#endif uart_param_config(uart_num_, &uart_config); const int uart_buffer_size = tx_buffer_size_; // Install UART driver using an event queue here From 2165960ba171e9b3d775622f0bc012ffc51fb295 Mon Sep 17 00:00:00 2001 From: Christian Date: Sat, 2 Sep 2023 01:03:30 +0100 Subject: [PATCH 054/111] add heating functionality to SI7021 (#4828) * add heating functoinality * add test * add heat * fix * fix * fix * fix * fix * fix sensor * restore class * Update esphome/components/htu21d/sensor.py * Update esphome/components/htu21d/sensor.py Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> * Update esphome/components/htu21d/sensor.py Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --------- Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Co-authored-by: Keith Burzinski --- esphome/components/htu21d/htu21d.cpp | 58 +++++++++++++++++++++++++++- esphome/components/htu21d/htu21d.h | 30 ++++++++++++++ esphome/components/htu21d/sensor.py | 56 +++++++++++++++++++++++++++ tests/test1.yaml | 2 + 4 files changed, 145 insertions(+), 1 deletion(-) diff --git a/esphome/components/htu21d/htu21d.cpp b/esphome/components/htu21d/htu21d.cpp index a38ec73019..5030ac4d0f 100644 --- a/esphome/components/htu21d/htu21d.cpp +++ b/esphome/components/htu21d/htu21d.cpp @@ -11,7 +11,11 @@ static const uint8_t HTU21D_ADDRESS = 0x40; static const uint8_t HTU21D_REGISTER_RESET = 0xFE; static const uint8_t HTU21D_REGISTER_TEMPERATURE = 0xF3; static const uint8_t HTU21D_REGISTER_HUMIDITY = 0xF5; +static const uint8_t HTU21D_WRITERHT_REG_CMD = 0xE6; /**< Write RH/T User Register 1 */ static const uint8_t HTU21D_REGISTER_STATUS = 0xE7; +static const uint8_t HTU21D_WRITEHEATER_REG_CMD = 0x51; /**< Write Heater Control Register */ +static const uint8_t HTU21D_READHEATER_REG_CMD = 0x11; /**< Read Heater Control Register */ +static const uint8_t HTU21D_REG_HTRE_BIT = 0x02; /**< Control Register Heater Bit */ void HTU21DComponent::setup() { ESP_LOGCONFIG(TAG, "Setting up HTU21D..."); @@ -62,14 +66,66 @@ void HTU21DComponent::update() { raw_humidity = i2c::i2ctohs(raw_humidity); float humidity = (float(raw_humidity & 0xFFFC)) * 125.0f / 65536.0f - 6.0f; - ESP_LOGD(TAG, "Got Temperature=%.1f°C Humidity=%.1f%%", temperature, humidity); + + int8_t heater_level = this->get_heater_level(); + + ESP_LOGD(TAG, "Got Temperature=%.1f°C Humidity=%.1f%% Heater Level=%d", temperature, humidity, heater_level); if (this->temperature_ != nullptr) this->temperature_->publish_state(temperature); if (this->humidity_ != nullptr) this->humidity_->publish_state(humidity); + if (this->heater_ != nullptr) + this->heater_->publish_state(humidity); this->status_clear_warning(); } + +bool HTU21DComponent::is_heater_enabled() { + uint8_t raw_heater; + if (this->read_register(HTU21D_REGISTER_STATUS, reinterpret_cast(&raw_heater), 2) != i2c::ERROR_OK) { + this->status_set_warning(); + return false; + } + raw_heater = i2c::i2ctohs(raw_heater); + return (bool) (((raw_heater) >> (HTU21D_REG_HTRE_BIT)) & 0x01); +} + +void HTU21DComponent::set_heater(bool status) { + uint8_t raw_heater; + if (this->read_register(HTU21D_REGISTER_STATUS, reinterpret_cast(&raw_heater), 2) != i2c::ERROR_OK) { + this->status_set_warning(); + return; + } + raw_heater = i2c::i2ctohs(raw_heater); + if (status) { + raw_heater |= (1 << (HTU21D_REG_HTRE_BIT)); + } else { + raw_heater &= ~(1 << (HTU21D_REG_HTRE_BIT)); + } + + if (this->write_register(HTU21D_WRITERHT_REG_CMD, &raw_heater, 1) != i2c::ERROR_OK) { + this->status_set_warning(); + return; + } +} + +void HTU21DComponent::set_heater_level(uint8_t level) { + if (this->write_register(HTU21D_WRITEHEATER_REG_CMD, &level, 1) != i2c::ERROR_OK) { + this->status_set_warning(); + return; + } +} + +int8_t HTU21DComponent::get_heater_level() { + int8_t raw_heater; + if (this->read_register(HTU21D_READHEATER_REG_CMD, reinterpret_cast(&raw_heater), 2) != i2c::ERROR_OK) { + this->status_set_warning(); + return 0; + } + raw_heater = i2c::i2ctohs(raw_heater); + return raw_heater; +} + float HTU21DComponent::get_setup_priority() const { return setup_priority::DATA; } } // namespace htu21d diff --git a/esphome/components/htu21d/htu21d.h b/esphome/components/htu21d/htu21d.h index a408f06d01..a77a8e3ada 100644 --- a/esphome/components/htu21d/htu21d.h +++ b/esphome/components/htu21d/htu21d.h @@ -3,6 +3,7 @@ #include "esphome/core/component.h" #include "esphome/components/sensor/sensor.h" #include "esphome/components/i2c/i2c.h" +#include "esphome/core/automation.h" namespace esphome { namespace htu21d { @@ -11,6 +12,7 @@ class HTU21DComponent : public PollingComponent, public i2c::I2CDevice { public: void set_temperature(sensor::Sensor *temperature) { temperature_ = temperature; } void set_humidity(sensor::Sensor *humidity) { humidity_ = humidity; } + void set_heater(sensor::Sensor *heater) { heater_ = heater; } /// Setup (reset) the sensor and check connection. void setup() override; @@ -18,11 +20,39 @@ class HTU21DComponent : public PollingComponent, public i2c::I2CDevice { /// Update the sensor values (temperature+humidity). void update() override; + bool is_heater_enabled(); + void set_heater(bool status); + void set_heater_level(uint8_t level); + int8_t get_heater_level(); + float get_setup_priority() const override; protected: sensor::Sensor *temperature_{nullptr}; sensor::Sensor *humidity_{nullptr}; + sensor::Sensor *heater_{nullptr}; +}; + +template class SetHeaterLevelAction : public Action, public Parented { + public: + TEMPLATABLE_VALUE(uint8_t, level) + + void play(Ts... x) override { + auto level = this->level_.value(x...); + + this->parent_->set_heater_level(level); + } +}; + +template class SetHeaterAction : public Action, public Parented { + public: + TEMPLATABLE_VALUE(bool, status) + + void play(Ts... x) override { + auto status = this->status_.value(x...); + + this->parent_->set_heater(status); + } }; } // namespace htu21d diff --git a/esphome/components/htu21d/sensor.py b/esphome/components/htu21d/sensor.py index 2ed318f1c9..1f878230f8 100644 --- a/esphome/components/htu21d/sensor.py +++ b/esphome/components/htu21d/sensor.py @@ -1,6 +1,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import i2c, sensor +from esphome import automation from esphome.const import ( CONF_HUMIDITY, CONF_ID, @@ -10,6 +11,10 @@ from esphome.const import ( STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, UNIT_PERCENT, + CONF_HEATER, + UNIT_EMPTY, + CONF_LEVEL, + CONF_STATUS, ) DEPENDENCIES = ["i2c"] @@ -19,6 +24,10 @@ HTU21DComponent = htu21d_ns.class_( "HTU21DComponent", cg.PollingComponent, i2c.I2CDevice ) +SetHeaterLevelAction = htu21d_ns.class_("SetHeaterLevelAction", automation.Action) +SetHeaterAction = htu21d_ns.class_("SetHeaterAction", automation.Action) + + CONFIG_SCHEMA = ( cv.Schema( { @@ -35,6 +44,11 @@ CONFIG_SCHEMA = ( device_class=DEVICE_CLASS_HUMIDITY, state_class=STATE_CLASS_MEASUREMENT, ), + cv.Optional(CONF_HEATER): sensor.sensor_schema( + unit_of_measurement=UNIT_EMPTY, + accuracy_decimals=1, + state_class=STATE_CLASS_MEASUREMENT, + ), } ) .extend(cv.polling_component_schema("60s")) @@ -54,3 +68,45 @@ async def to_code(config): if CONF_HUMIDITY in config: sens = await sensor.new_sensor(config[CONF_HUMIDITY]) cg.add(var.set_humidity(sens)) + + if CONF_HEATER in config: + sens = await sensor.new_sensor(config[CONF_HEATER]) + cg.add(var.set_heater(sens)) + + +@automation.register_action( + "htu21d.set_heater_level", + SetHeaterLevelAction, + cv.maybe_simple_value( + { + cv.GenerateID(): cv.use_id(HTU21DComponent), + cv.Required(CONF_LEVEL): cv.templatable(cv.int_), + }, + key=CONF_LEVEL, + ), +) +async def set_heater_level_to_code(config, action_id, template_arg, args): + var = cg.new_Pvariable(action_id, template_arg) + await cg.register_parented(var, config[CONF_ID]) + level_ = await cg.templatable(config[CONF_LEVEL], args, int) + cg.add(var.set_level(level_)) + return var + + +@automation.register_action( + "htu21d.set_heater", + SetHeaterAction, + cv.maybe_simple_value( + { + cv.GenerateID(): cv.use_id(HTU21DComponent), + cv.Required(CONF_STATUS): cv.templatable(cv.boolean), + }, + key=CONF_STATUS, + ), +) +async def set_heater_to_code(config, action_id, template_arg, args): + var = cg.new_Pvariable(action_id, template_arg) + await cg.register_parented(var, config[CONF_ID]) + status_ = await cg.templatable(config[CONF_LEVEL], args, bool) + cg.add(var.set_status(status_)) + return var diff --git a/tests/test1.yaml b/tests/test1.yaml index 3a6cfa0c4b..efca34247b 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -848,6 +848,8 @@ sensor: name: Living Room Temperature 6 humidity: name: Living Room Humidity 6 + heater: + name: Living Room Heater 6 update_interval: 15s i2c_id: i2c_bus - platform: max6675 From 5fdafc00e665f846a8b3e9b4b318df6c5ddca183 Mon Sep 17 00:00:00 2001 From: Mat931 <49403702+Mat931@users.noreply.github.com> Date: Sat, 2 Sep 2023 09:54:03 +0000 Subject: [PATCH 055/111] Fix checksum calculation for pipsolar (#5299) --- esphome/components/pipsolar/pipsolar.cpp | 20 ++++++++++++++++---- esphome/components/pipsolar/pipsolar.h | 2 +- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/esphome/components/pipsolar/pipsolar.cpp b/esphome/components/pipsolar/pipsolar.cpp index 62e4fbd341..2cd1aeba44 100644 --- a/esphome/components/pipsolar/pipsolar.cpp +++ b/esphome/components/pipsolar/pipsolar.cpp @@ -769,7 +769,7 @@ uint8_t Pipsolar::check_incoming_length_(uint8_t length) { uint8_t Pipsolar::check_incoming_crc_() { uint16_t crc16; - crc16 = crc16be(read_buffer_, read_pos_ - 3); + crc16 = this->pipsolar_crc_(read_buffer_, read_pos_ - 3); ESP_LOGD(TAG, "checking crc on incoming message"); if (((uint8_t) ((crc16) >> 8)) == read_buffer_[read_pos_ - 3] && ((uint8_t) ((crc16) &0xff)) == read_buffer_[read_pos_ - 2]) { @@ -798,7 +798,7 @@ uint8_t Pipsolar::send_next_command_() { this->command_start_millis_ = millis(); this->empty_uart_buffer_(); this->read_pos_ = 0; - crc16 = crc16be(byte_command, length); + crc16 = this->pipsolar_crc_(byte_command, length); this->write_str(command); // checksum this->write(((uint8_t) ((crc16) >> 8))); // highbyte @@ -825,8 +825,8 @@ void Pipsolar::send_next_poll_() { this->command_start_millis_ = millis(); this->empty_uart_buffer_(); this->read_pos_ = 0; - crc16 = crc16be(this->used_polling_commands_[this->last_polling_command_].command, - this->used_polling_commands_[this->last_polling_command_].length); + crc16 = this->pipsolar_crc_(this->used_polling_commands_[this->last_polling_command_].command, + this->used_polling_commands_[this->last_polling_command_].length); this->write_array(this->used_polling_commands_[this->last_polling_command_].command, this->used_polling_commands_[this->last_polling_command_].length); // checksum @@ -893,5 +893,17 @@ void Pipsolar::add_polling_command_(const char *command, ENUMPollingCommand poll } } +uint16_t Pipsolar::pipsolar_crc_(uint8_t *msg, uint8_t len) { + uint16_t crc = crc16be(msg, len); + uint8_t crc_low = crc & 0xff; + uint8_t crc_high = crc >> 8; + if (crc_low == 0x28 || crc_low == 0x0d || crc_low == 0x0a) + crc_low++; + if (crc_high == 0x28 || crc_high == 0x0d || crc_high == 0x0a) + crc_high++; + crc = (crc_high << 8) | crc_low; + return crc; +} + } // namespace pipsolar } // namespace esphome diff --git a/esphome/components/pipsolar/pipsolar.h b/esphome/components/pipsolar/pipsolar.h index 65fd3c670d..f20f44f095 100644 --- a/esphome/components/pipsolar/pipsolar.h +++ b/esphome/components/pipsolar/pipsolar.h @@ -193,7 +193,7 @@ class Pipsolar : public uart::UARTDevice, public PollingComponent { void empty_uart_buffer_(); uint8_t check_incoming_crc_(); uint8_t check_incoming_length_(uint8_t length); - uint16_t cal_crc_half_(uint8_t *msg, uint8_t len); + uint16_t pipsolar_crc_(uint8_t *msg, uint8_t len); uint8_t send_next_command_(); void send_next_poll_(); void queue_command_(const char *command, uint8_t length); From 4ae582c3051ba06c811bdfaf89a15b00a19a571b Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 4 Sep 2023 20:43:17 +1200 Subject: [PATCH 056/111] Bump esphome-dashboard to 20230904.0 (#5339) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 715dbef4f2..c52b8e1d8e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ pyserial==3.5 platformio==6.1.10 # When updating platformio, also update Dockerfile esptool==4.6.2 click==8.1.7 -esphome-dashboard==20230711.0 +esphome-dashboard==20230904.0 aioesphomeapi==15.0.0 zeroconf==0.88.0 From 3d9af2a67c3de1ff9761d099364c399f07a93803 Mon Sep 17 00:00:00 2001 From: croessi <87674139+croessi@users.noreply.github.com> Date: Mon, 4 Sep 2023 22:40:46 +0200 Subject: [PATCH 057/111] Added Handling for Nack "file not found" (#5338) --- esphome/components/dfplayer/dfplayer.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/esphome/components/dfplayer/dfplayer.cpp b/esphome/components/dfplayer/dfplayer.cpp index a6339dc988..39a30d035e 100644 --- a/esphome/components/dfplayer/dfplayer.cpp +++ b/esphome/components/dfplayer/dfplayer.cpp @@ -101,6 +101,11 @@ void DFPlayer::loop() { ESP_LOGV(TAG, "Nack"); this->ack_set_is_playing_ = false; this->ack_reset_is_playing_ = false; + if (argument == 6) { + ESP_LOGV(TAG, "File not found"); + this->is_playing_ = false; + } + break; case 0x41: ESP_LOGV(TAG, "Ack ok"); this->is_playing_ |= this->ack_set_is_playing_; From aabe0091ccf1c666e1a6771c40614e8c310aec7f Mon Sep 17 00:00:00 2001 From: Jimmy Hedman Date: Mon, 4 Sep 2023 22:51:04 +0200 Subject: [PATCH 058/111] Prepare api and time for ESP-IDF >= 5 (#5332) --- esphome/components/api/api_pb2.cpp | 184 ++++++++++---------- esphome/components/time/real_time_clock.cpp | 2 +- 2 files changed, 94 insertions(+), 92 deletions(-) diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 3a2d980e57..6149a970ee 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -3,6 +3,8 @@ #include "api_pb2.h" #include "esphome/core/log.h" +#include + namespace esphome { namespace api { @@ -522,12 +524,12 @@ void HelloRequest::dump_to(std::string &out) const { out.append("\n"); out.append(" api_version_major: "); - sprintf(buffer, "%u", this->api_version_major); + sprintf(buffer, "%" PRIu32, this->api_version_major); out.append(buffer); out.append("\n"); out.append(" api_version_minor: "); - sprintf(buffer, "%u", this->api_version_minor); + sprintf(buffer, "%" PRIu32, this->api_version_minor); out.append(buffer); out.append("\n"); out.append("}"); @@ -572,12 +574,12 @@ void HelloResponse::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("HelloResponse {\n"); out.append(" api_version_major: "); - sprintf(buffer, "%u", this->api_version_major); + sprintf(buffer, "%" PRIu32, this->api_version_major); out.append(buffer); out.append("\n"); out.append(" api_version_minor: "); - sprintf(buffer, "%u", this->api_version_minor); + sprintf(buffer, "%" PRIu32, this->api_version_minor); out.append(buffer); out.append("\n"); @@ -783,17 +785,17 @@ void DeviceInfoResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" webserver_port: "); - sprintf(buffer, "%u", this->webserver_port); + sprintf(buffer, "%" PRIu32, this->webserver_port); out.append(buffer); out.append("\n"); out.append(" legacy_bluetooth_proxy_version: "); - sprintf(buffer, "%u", this->legacy_bluetooth_proxy_version); + sprintf(buffer, "%" PRIu32, this->legacy_bluetooth_proxy_version); out.append(buffer); out.append("\n"); out.append(" bluetooth_proxy_feature_flags: "); - sprintf(buffer, "%u", this->bluetooth_proxy_feature_flags); + sprintf(buffer, "%" PRIu32, this->bluetooth_proxy_feature_flags); out.append(buffer); out.append("\n"); @@ -806,7 +808,7 @@ void DeviceInfoResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" voice_assistant_version: "); - sprintf(buffer, "%u", this->voice_assistant_version); + sprintf(buffer, "%" PRIu32, this->voice_assistant_version); out.append(buffer); out.append("\n"); out.append("}"); @@ -898,7 +900,7 @@ void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" key: "); - sprintf(buffer, "%u", this->key); + sprintf(buffer, "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -966,7 +968,7 @@ void BinarySensorStateResponse::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("BinarySensorStateResponse {\n"); out.append(" key: "); - sprintf(buffer, "%u", this->key); + sprintf(buffer, "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -1069,7 +1071,7 @@ void ListEntitiesCoverResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" key: "); - sprintf(buffer, "%u", this->key); + sprintf(buffer, "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -1159,7 +1161,7 @@ void CoverStateResponse::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("CoverStateResponse {\n"); out.append(" key: "); - sprintf(buffer, "%u", this->key); + sprintf(buffer, "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -1242,7 +1244,7 @@ void CoverCommandRequest::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("CoverCommandRequest {\n"); out.append(" key: "); - sprintf(buffer, "%u", this->key); + sprintf(buffer, "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -1362,7 +1364,7 @@ void ListEntitiesFanResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" key: "); - sprintf(buffer, "%u", this->key); + sprintf(buffer, "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -1387,7 +1389,7 @@ void ListEntitiesFanResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" supported_speed_count: "); - sprintf(buffer, "%d", this->supported_speed_count); + sprintf(buffer, "%" PRId32, this->supported_speed_count); out.append(buffer); out.append("\n"); @@ -1454,7 +1456,7 @@ void FanStateResponse::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("FanStateResponse {\n"); out.append(" key: "); - sprintf(buffer, "%u", this->key); + sprintf(buffer, "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -1475,7 +1477,7 @@ void FanStateResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" speed_level: "); - sprintf(buffer, "%d", this->speed_level); + sprintf(buffer, "%" PRId32, this->speed_level); out.append(buffer); out.append("\n"); out.append("}"); @@ -1555,7 +1557,7 @@ void FanCommandRequest::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("FanCommandRequest {\n"); out.append(" key: "); - sprintf(buffer, "%u", this->key); + sprintf(buffer, "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -1596,7 +1598,7 @@ void FanCommandRequest::dump_to(std::string &out) const { out.append("\n"); out.append(" speed_level: "); - sprintf(buffer, "%d", this->speed_level); + sprintf(buffer, "%" PRId32, this->speed_level); out.append(buffer); out.append("\n"); out.append("}"); @@ -1710,7 +1712,7 @@ void ListEntitiesLightResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" key: "); - sprintf(buffer, "%u", this->key); + sprintf(buffer, "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -1864,7 +1866,7 @@ void LightStateResponse::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("LightStateResponse {\n"); out.append(" key: "); - sprintf(buffer, "%u", this->key); + sprintf(buffer, "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -2087,7 +2089,7 @@ void LightCommandRequest::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("LightCommandRequest {\n"); out.append(" key: "); - sprintf(buffer, "%u", this->key); + sprintf(buffer, "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -2185,7 +2187,7 @@ void LightCommandRequest::dump_to(std::string &out) const { out.append("\n"); out.append(" transition_length: "); - sprintf(buffer, "%u", this->transition_length); + sprintf(buffer, "%" PRIu32, this->transition_length); out.append(buffer); out.append("\n"); @@ -2194,7 +2196,7 @@ void LightCommandRequest::dump_to(std::string &out) const { out.append("\n"); out.append(" flash_length: "); - sprintf(buffer, "%u", this->flash_length); + sprintf(buffer, "%" PRIu32, this->flash_length); out.append(buffer); out.append("\n"); @@ -2302,7 +2304,7 @@ void ListEntitiesSensorResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" key: "); - sprintf(buffer, "%u", this->key); + sprintf(buffer, "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -2323,7 +2325,7 @@ void ListEntitiesSensorResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" accuracy_decimals: "); - sprintf(buffer, "%d", this->accuracy_decimals); + sprintf(buffer, "%" PRId32, this->accuracy_decimals); out.append(buffer); out.append("\n"); @@ -2387,7 +2389,7 @@ void SensorStateResponse::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("SensorStateResponse {\n"); out.append(" key: "); - sprintf(buffer, "%u", this->key); + sprintf(buffer, "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -2476,7 +2478,7 @@ void ListEntitiesSwitchResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" key: "); - sprintf(buffer, "%u", this->key); + sprintf(buffer, "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -2539,7 +2541,7 @@ void SwitchStateResponse::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("SwitchStateResponse {\n"); out.append(" key: "); - sprintf(buffer, "%u", this->key); + sprintf(buffer, "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -2578,7 +2580,7 @@ void SwitchCommandRequest::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("SwitchCommandRequest {\n"); out.append(" key: "); - sprintf(buffer, "%u", this->key); + sprintf(buffer, "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -2652,7 +2654,7 @@ void ListEntitiesTextSensorResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" key: "); - sprintf(buffer, "%u", this->key); + sprintf(buffer, "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -2718,7 +2720,7 @@ void TextSensorStateResponse::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("TextSensorStateResponse {\n"); out.append(" key: "); - sprintf(buffer, "%u", this->key); + sprintf(buffer, "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -3025,7 +3027,7 @@ void GetTimeResponse::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("GetTimeResponse {\n"); out.append(" epoch_seconds: "); - sprintf(buffer, "%u", this->epoch_seconds); + sprintf(buffer, "%" PRIu32, this->epoch_seconds); out.append(buffer); out.append("\n"); out.append("}"); @@ -3109,7 +3111,7 @@ void ListEntitiesServicesResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" key: "); - sprintf(buffer, "%u", this->key); + sprintf(buffer, "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -3203,7 +3205,7 @@ void ExecuteServiceArgument::dump_to(std::string &out) const { out.append("\n"); out.append(" legacy_int: "); - sprintf(buffer, "%d", this->legacy_int); + sprintf(buffer, "%" PRId32, this->legacy_int); out.append(buffer); out.append("\n"); @@ -3217,7 +3219,7 @@ void ExecuteServiceArgument::dump_to(std::string &out) const { out.append("\n"); out.append(" int_: "); - sprintf(buffer, "%d", this->int_); + sprintf(buffer, "%" PRId32, this->int_); out.append(buffer); out.append("\n"); @@ -3229,7 +3231,7 @@ void ExecuteServiceArgument::dump_to(std::string &out) const { for (const auto &it : this->int_array) { out.append(" int_array: "); - sprintf(buffer, "%d", it); + sprintf(buffer, "%" PRId32, it); out.append(buffer); out.append("\n"); } @@ -3280,7 +3282,7 @@ void ExecuteServiceRequest::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("ExecuteServiceRequest {\n"); out.append(" key: "); - sprintf(buffer, "%u", this->key); + sprintf(buffer, "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -3356,7 +3358,7 @@ void ListEntitiesCameraResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" key: "); - sprintf(buffer, "%u", this->key); + sprintf(buffer, "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -3422,7 +3424,7 @@ void CameraImageResponse::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("CameraImageResponse {\n"); out.append(" key: "); - sprintf(buffer, "%u", this->key); + sprintf(buffer, "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -3614,7 +3616,7 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" key: "); - sprintf(buffer, "%u", this->key); + sprintf(buffer, "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -3802,7 +3804,7 @@ void ClimateStateResponse::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("ClimateStateResponse {\n"); out.append(" key: "); - sprintf(buffer, "%u", this->key); + sprintf(buffer, "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -3990,7 +3992,7 @@ void ClimateCommandRequest::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("ClimateCommandRequest {\n"); out.append(" key: "); - sprintf(buffer, "%u", this->key); + sprintf(buffer, "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -4173,7 +4175,7 @@ void ListEntitiesNumberResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" key: "); - sprintf(buffer, "%u", this->key); + sprintf(buffer, "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -4260,7 +4262,7 @@ void NumberStateResponse::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("NumberStateResponse {\n"); out.append(" key: "); - sprintf(buffer, "%u", this->key); + sprintf(buffer, "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -4298,7 +4300,7 @@ void NumberCommandRequest::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("NumberCommandRequest {\n"); out.append(" key: "); - sprintf(buffer, "%u", this->key); + sprintf(buffer, "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -4380,7 +4382,7 @@ void ListEntitiesSelectResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" key: "); - sprintf(buffer, "%u", this->key); + sprintf(buffer, "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -4452,7 +4454,7 @@ void SelectStateResponse::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("SelectStateResponse {\n"); out.append(" key: "); - sprintf(buffer, "%u", this->key); + sprintf(buffer, "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -4495,7 +4497,7 @@ void SelectCommandRequest::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("SelectCommandRequest {\n"); out.append(" key: "); - sprintf(buffer, "%u", this->key); + sprintf(buffer, "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -4589,7 +4591,7 @@ void ListEntitiesLockResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" key: "); - sprintf(buffer, "%u", this->key); + sprintf(buffer, "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -4660,7 +4662,7 @@ void LockStateResponse::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("LockStateResponse {\n"); out.append(" key: "); - sprintf(buffer, "%u", this->key); + sprintf(buffer, "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -4715,7 +4717,7 @@ void LockCommandRequest::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("LockCommandRequest {\n"); out.append(" key: "); - sprintf(buffer, "%u", this->key); + sprintf(buffer, "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -4802,7 +4804,7 @@ void ListEntitiesButtonResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" key: "); - sprintf(buffer, "%u", this->key); + sprintf(buffer, "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -4848,7 +4850,7 @@ void ButtonCommandRequest::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("ButtonCommandRequest {\n"); out.append(" key: "); - sprintf(buffer, "%u", this->key); + sprintf(buffer, "%" PRIu32, this->key); out.append(buffer); out.append("\n"); out.append("}"); @@ -4923,7 +4925,7 @@ void ListEntitiesMediaPlayerResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" key: "); - sprintf(buffer, "%u", this->key); + sprintf(buffer, "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -4992,7 +4994,7 @@ void MediaPlayerStateResponse::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("MediaPlayerStateResponse {\n"); out.append(" key: "); - sprintf(buffer, "%u", this->key); + sprintf(buffer, "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -5071,7 +5073,7 @@ void MediaPlayerCommandRequest::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("MediaPlayerCommandRequest {\n"); out.append(" key: "); - sprintf(buffer, "%u", this->key); + sprintf(buffer, "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -5120,7 +5122,7 @@ void SubscribeBluetoothLEAdvertisementsRequest::dump_to(std::string &out) const __attribute__((unused)) char buffer[64]; out.append("SubscribeBluetoothLEAdvertisementsRequest {\n"); out.append(" flags: "); - sprintf(buffer, "%u", this->flags); + sprintf(buffer, "%" PRIu32, this->flags); out.append(buffer); out.append("\n"); out.append("}"); @@ -5167,7 +5169,7 @@ void BluetoothServiceData::dump_to(std::string &out) const { for (const auto &it : this->legacy_data) { out.append(" legacy_data: "); - sprintf(buffer, "%u", it); + sprintf(buffer, "%" PRIu32, it); out.append(buffer); out.append("\n"); } @@ -5247,7 +5249,7 @@ void BluetoothLEAdvertisementResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" rssi: "); - sprintf(buffer, "%d", this->rssi); + sprintf(buffer, "%" PRId32, this->rssi); out.append(buffer); out.append("\n"); @@ -5270,7 +5272,7 @@ void BluetoothLEAdvertisementResponse::dump_to(std::string &out) const { } out.append(" address_type: "); - sprintf(buffer, "%u", this->address_type); + sprintf(buffer, "%" PRIu32, this->address_type); out.append(buffer); out.append("\n"); out.append("}"); @@ -5320,12 +5322,12 @@ void BluetoothLERawAdvertisement::dump_to(std::string &out) const { out.append("\n"); out.append(" rssi: "); - sprintf(buffer, "%d", this->rssi); + sprintf(buffer, "%" PRId32, this->rssi); out.append(buffer); out.append("\n"); out.append(" address_type: "); - sprintf(buffer, "%u", this->address_type); + sprintf(buffer, "%" PRIu32, this->address_type); out.append(buffer); out.append("\n"); @@ -5408,7 +5410,7 @@ void BluetoothDeviceRequest::dump_to(std::string &out) const { out.append("\n"); out.append(" address_type: "); - sprintf(buffer, "%u", this->address_type); + sprintf(buffer, "%" PRIu32, this->address_type); out.append(buffer); out.append("\n"); out.append("}"); @@ -5456,12 +5458,12 @@ void BluetoothDeviceConnectionResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" mtu: "); - sprintf(buffer, "%u", this->mtu); + sprintf(buffer, "%" PRIu32, this->mtu); out.append(buffer); out.append("\n"); out.append(" error: "); - sprintf(buffer, "%d", this->error); + sprintf(buffer, "%" PRId32, this->error); out.append(buffer); out.append("\n"); out.append("}"); @@ -5521,7 +5523,7 @@ void BluetoothGATTDescriptor::dump_to(std::string &out) const { } out.append(" handle: "); - sprintf(buffer, "%u", this->handle); + sprintf(buffer, "%" PRIu32, this->handle); out.append(buffer); out.append("\n"); out.append("}"); @@ -5577,12 +5579,12 @@ void BluetoothGATTCharacteristic::dump_to(std::string &out) const { } out.append(" handle: "); - sprintf(buffer, "%u", this->handle); + sprintf(buffer, "%" PRIu32, this->handle); out.append(buffer); out.append("\n"); out.append(" properties: "); - sprintf(buffer, "%u", this->properties); + sprintf(buffer, "%" PRIu32, this->properties); out.append(buffer); out.append("\n"); @@ -5639,7 +5641,7 @@ void BluetoothGATTService::dump_to(std::string &out) const { } out.append(" handle: "); - sprintf(buffer, "%u", this->handle); + sprintf(buffer, "%" PRIu32, this->handle); out.append(buffer); out.append("\n"); @@ -5746,7 +5748,7 @@ void BluetoothGATTReadRequest::dump_to(std::string &out) const { out.append("\n"); out.append(" handle: "); - sprintf(buffer, "%u", this->handle); + sprintf(buffer, "%" PRIu32, this->handle); out.append(buffer); out.append("\n"); out.append("}"); @@ -5791,7 +5793,7 @@ void BluetoothGATTReadResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" handle: "); - sprintf(buffer, "%u", this->handle); + sprintf(buffer, "%" PRIu32, this->handle); out.append(buffer); out.append("\n"); @@ -5845,7 +5847,7 @@ void BluetoothGATTWriteRequest::dump_to(std::string &out) const { out.append("\n"); out.append(" handle: "); - sprintf(buffer, "%u", this->handle); + sprintf(buffer, "%" PRIu32, this->handle); out.append(buffer); out.append("\n"); @@ -5887,7 +5889,7 @@ void BluetoothGATTReadDescriptorRequest::dump_to(std::string &out) const { out.append("\n"); out.append(" handle: "); - sprintf(buffer, "%u", this->handle); + sprintf(buffer, "%" PRIu32, this->handle); out.append(buffer); out.append("\n"); out.append("}"); @@ -5932,7 +5934,7 @@ void BluetoothGATTWriteDescriptorRequest::dump_to(std::string &out) const { out.append("\n"); out.append(" handle: "); - sprintf(buffer, "%u", this->handle); + sprintf(buffer, "%" PRIu32, this->handle); out.append(buffer); out.append("\n"); @@ -5975,7 +5977,7 @@ void BluetoothGATTNotifyRequest::dump_to(std::string &out) const { out.append("\n"); out.append(" handle: "); - sprintf(buffer, "%u", this->handle); + sprintf(buffer, "%" PRIu32, this->handle); out.append(buffer); out.append("\n"); @@ -6024,7 +6026,7 @@ void BluetoothGATTNotifyDataResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" handle: "); - sprintf(buffer, "%u", this->handle); + sprintf(buffer, "%" PRIu32, this->handle); out.append(buffer); out.append("\n"); @@ -6063,12 +6065,12 @@ void BluetoothConnectionsFreeResponse::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("BluetoothConnectionsFreeResponse {\n"); out.append(" free: "); - sprintf(buffer, "%u", this->free); + sprintf(buffer, "%" PRIu32, this->free); out.append(buffer); out.append("\n"); out.append(" limit: "); - sprintf(buffer, "%u", this->limit); + sprintf(buffer, "%" PRIu32, this->limit); out.append(buffer); out.append("\n"); out.append("}"); @@ -6107,12 +6109,12 @@ void BluetoothGATTErrorResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" handle: "); - sprintf(buffer, "%u", this->handle); + sprintf(buffer, "%" PRIu32, this->handle); out.append(buffer); out.append("\n"); out.append(" error: "); - sprintf(buffer, "%d", this->error); + sprintf(buffer, "%" PRId32, this->error); out.append(buffer); out.append("\n"); out.append("}"); @@ -6146,7 +6148,7 @@ void BluetoothGATTWriteResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" handle: "); - sprintf(buffer, "%u", this->handle); + sprintf(buffer, "%" PRIu32, this->handle); out.append(buffer); out.append("\n"); out.append("}"); @@ -6180,7 +6182,7 @@ void BluetoothGATTNotifyResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" handle: "); - sprintf(buffer, "%u", this->handle); + sprintf(buffer, "%" PRIu32, this->handle); out.append(buffer); out.append("\n"); out.append("}"); @@ -6223,7 +6225,7 @@ void BluetoothDevicePairingResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" error: "); - sprintf(buffer, "%d", this->error); + sprintf(buffer, "%" PRId32, this->error); out.append(buffer); out.append("\n"); out.append("}"); @@ -6266,7 +6268,7 @@ void BluetoothDeviceUnpairingResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" error: "); - sprintf(buffer, "%d", this->error); + sprintf(buffer, "%" PRId32, this->error); out.append(buffer); out.append("\n"); out.append("}"); @@ -6315,7 +6317,7 @@ void BluetoothDeviceClearCacheResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" error: "); - sprintf(buffer, "%d", this->error); + sprintf(buffer, "%" PRId32, this->error); out.append(buffer); out.append("\n"); out.append("}"); @@ -6412,7 +6414,7 @@ void VoiceAssistantResponse::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("VoiceAssistantResponse {\n"); out.append(" port: "); - sprintf(buffer, "%u", this->port); + sprintf(buffer, "%" PRIu32, this->port); out.append(buffer); out.append("\n"); @@ -6575,7 +6577,7 @@ void ListEntitiesAlarmControlPanelResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" key: "); - sprintf(buffer, "%u", this->key); + sprintf(buffer, "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -6600,7 +6602,7 @@ void ListEntitiesAlarmControlPanelResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" supported_features: "); - sprintf(buffer, "%u", this->supported_features); + sprintf(buffer, "%" PRIu32, this->supported_features); out.append(buffer); out.append("\n"); @@ -6643,7 +6645,7 @@ void AlarmControlPanelStateResponse::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("AlarmControlPanelStateResponse {\n"); out.append(" key: "); - sprintf(buffer, "%u", this->key); + sprintf(buffer, "%" PRIu32, this->key); out.append(buffer); out.append("\n"); @@ -6693,7 +6695,7 @@ void AlarmControlPanelCommandRequest::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("AlarmControlPanelCommandRequest {\n"); out.append(" key: "); - sprintf(buffer, "%u", this->key); + sprintf(buffer, "%" PRIu32, this->key); out.append(buffer); out.append("\n"); diff --git a/esphome/components/time/real_time_clock.cpp b/esphome/components/time/real_time_clock.cpp index 10fa9597b9..0573c7de9d 100644 --- a/esphome/components/time/real_time_clock.cpp +++ b/esphome/components/time/real_time_clock.cpp @@ -24,7 +24,7 @@ void RealTimeClock::synchronize_epoch_(uint32_t epoch) { struct timeval timev { .tv_sec = static_cast(epoch), .tv_usec = 0, }; - ESP_LOGVV(TAG, "Got epoch %u", epoch); + ESP_LOGVV(TAG, "Got epoch %" PRIu32, epoch); timezone tz = {0, 0}; int ret = settimeofday(&timev, &tz); if (ret == EINVAL) { From 22c0b0abaa548bd00938135acecc401a7a12aecf Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Mon, 4 Sep 2023 16:47:53 -0500 Subject: [PATCH 059/111] Tweak Improv serial to build in IDF 5 (#5331) --- esphome/components/improv_serial/improv_serial_component.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/improv_serial/improv_serial_component.cpp b/esphome/components/improv_serial/improv_serial_component.cpp index fe19e2f085..1dd1c9cf6f 100644 --- a/esphome/components/improv_serial/improv_serial_component.cpp +++ b/esphome/components/improv_serial/improv_serial_component.cpp @@ -48,7 +48,7 @@ uint8_t ImprovSerialComponent::read_byte_() { this->hw_serial_->readBytes(&data, 1); #endif #ifdef USE_ESP_IDF - uart_read_bytes(this->uart_num_, &data, 1, 20 / portTICK_RATE_MS); + uart_read_bytes(this->uart_num_, &data, 1, 20 / portTICK_PERIOD_MS); #endif return data; } From a9630ac847a292bc6ad671356804deb809eb6dc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Tue, 5 Sep 2023 00:16:08 +0200 Subject: [PATCH 060/111] Support for LibreTiny platform (RTL8710, BK7231 & other modules) (#3509) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Kuba Szczodrzyński Co-authored-by: Sam Neirinck Co-authored-by: David Buezas Co-authored-by: Stroe Andrei Catalin Co-authored-by: Sam Neirinck Co-authored-by: Péter Sárközi Co-authored-by: Hajo Noerenberg --- CODEOWNERS | 4 + esphome/__main__.py | 21 +- esphome/components/adc/__init__.py | 9 +- esphome/components/adc/adc_sensor.cpp | 13 +- esphome/components/api/api_connection.cpp | 4 + esphome/components/async_tcp/__init__.py | 6 +- esphome/components/bk72xx/__init__.py | 51 + esphome/components/bk72xx/boards.py | 1264 +++++++++++++++ esphome/components/captive_portal/__init__.py | 4 +- esphome/components/debug/debug_component.cpp | 31 +- esphome/components/esp8266/gpio.py | 2 +- esphome/components/i2c/__init__.py | 37 +- esphome/components/json/json_util.cpp | 4 + esphome/components/libretiny/__init__.py | 336 ++++ esphome/components/libretiny/const.py | 90 ++ esphome/components/libretiny/core.cpp | 40 + esphome/components/libretiny/core.h | 11 + .../libretiny/generate_components.py | 329 ++++ esphome/components/libretiny/gpio.py | 216 +++ esphome/components/libretiny/gpio_arduino.cpp | 105 ++ esphome/components/libretiny/gpio_arduino.h | 36 + esphome/components/libretiny/lt_component.cpp | 29 + esphome/components/libretiny/lt_component.h | 36 + esphome/components/libretiny/preferences.cpp | 182 +++ esphome/components/libretiny/preferences.h | 13 + esphome/components/libretiny/text_sensor.py | 31 + esphome/components/libretiny_pwm/__init__.py | 1 + .../libretiny_pwm/libretiny_pwm.cpp | 53 + .../components/libretiny_pwm/libretiny_pwm.h | 55 + esphome/components/libretiny_pwm/output.py | 49 + esphome/components/logger/__init__.py | 34 +- esphome/components/logger/logger.cpp | 58 +- esphome/components/logger/logger.h | 16 +- esphome/components/md5/md5.h | 5 + esphome/components/mdns/mdns_component.cpp | 3 + esphome/components/mdns/mdns_libretiny.cpp | 43 + esphome/components/ota/__init__.py | 9 +- .../ota/ota_backend_arduino_libretiny.cpp | 46 + .../ota/ota_backend_arduino_libretiny.h | 24 + esphome/components/ota/ota_component.cpp | 4 + .../components/remote_receiver/__init__.py | 6 +- .../remote_receiver/remote_receiver.h | 4 +- .../remote_receiver_libretiny.cpp | 122 ++ .../remote_transmitter/remote_transmitter.h | 2 +- .../remote_transmitter_libretiny.cpp | 104 ++ esphome/components/rp2040/gpio.py | 3 +- esphome/components/rtl87xx/__init__.py | 51 + esphome/components/rtl87xx/boards.py | 1390 +++++++++++++++++ esphome/components/sntp/sntp_component.cpp | 4 +- esphome/components/socket/__init__.py | 11 +- esphome/components/socket/headers.h | 29 + .../components/socket/lwip_sockets_impl.cpp | 115 ++ esphome/components/spi/__init__.py | 1 + esphome/components/uart/__init__.py | 5 + .../uart/uart_component_libretiny.cpp | 168 ++ .../uart/uart_component_libretiny.h | 43 + esphome/components/web_server/__init__.py | 9 +- .../components/web_server_base/__init__.py | 2 +- .../web_server_base/web_server_base.cpp | 4 +- esphome/components/wifi/__init__.py | 7 +- esphome/components/wifi/wifi_component.h | 9 + .../wifi/wifi_component_libretiny.cpp | 467 ++++++ esphome/config_validation.py | 8 + esphome/const.py | 15 +- esphome/core/__init__.py | 14 + esphome/core/config.py | 2 +- esphome/core/defines.h | 4 + esphome/core/helpers.cpp | 15 +- esphome/core/helpers.h | 5 +- esphome/core/log.h | 3 + esphome/dashboard/dashboard.py | 9 + esphome/voluptuous_schema.py | 5 + esphome/wizard.py | 80 +- platformio.ini | 41 +- script/ci-custom.py | 6 +- tests/test9.1.yaml | 28 + tests/test9.yaml | 28 + tests/unit_tests/test_wizard.py | 51 +- 78 files changed, 6085 insertions(+), 89 deletions(-) create mode 100644 esphome/components/bk72xx/__init__.py create mode 100644 esphome/components/bk72xx/boards.py create mode 100644 esphome/components/libretiny/__init__.py create mode 100644 esphome/components/libretiny/const.py create mode 100644 esphome/components/libretiny/core.cpp create mode 100644 esphome/components/libretiny/core.h create mode 100644 esphome/components/libretiny/generate_components.py create mode 100644 esphome/components/libretiny/gpio.py create mode 100644 esphome/components/libretiny/gpio_arduino.cpp create mode 100644 esphome/components/libretiny/gpio_arduino.h create mode 100644 esphome/components/libretiny/lt_component.cpp create mode 100644 esphome/components/libretiny/lt_component.h create mode 100644 esphome/components/libretiny/preferences.cpp create mode 100644 esphome/components/libretiny/preferences.h create mode 100644 esphome/components/libretiny/text_sensor.py create mode 100644 esphome/components/libretiny_pwm/__init__.py create mode 100644 esphome/components/libretiny_pwm/libretiny_pwm.cpp create mode 100644 esphome/components/libretiny_pwm/libretiny_pwm.h create mode 100644 esphome/components/libretiny_pwm/output.py create mode 100644 esphome/components/mdns/mdns_libretiny.cpp create mode 100644 esphome/components/ota/ota_backend_arduino_libretiny.cpp create mode 100644 esphome/components/ota/ota_backend_arduino_libretiny.h create mode 100644 esphome/components/remote_receiver/remote_receiver_libretiny.cpp create mode 100644 esphome/components/remote_transmitter/remote_transmitter_libretiny.cpp create mode 100644 esphome/components/rtl87xx/__init__.py create mode 100644 esphome/components/rtl87xx/boards.py create mode 100644 esphome/components/socket/lwip_sockets_impl.cpp create mode 100644 esphome/components/uart/uart_component_libretiny.cpp create mode 100644 esphome/components/uart/uart_component_libretiny.h create mode 100644 esphome/components/wifi/wifi_component_libretiny.cpp create mode 100644 tests/test9.1.yaml create mode 100644 tests/test9.yaml diff --git a/CODEOWNERS b/CODEOWNERS index 49746cf013..1455643550 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -42,6 +42,7 @@ esphome/components/bedjet/climate/* @jhansche esphome/components/bedjet/fan/* @jhansche esphome/components/bh1750/* @OttoWinter esphome/components/binary_sensor/* @esphome/core +esphome/components/bk72xx/* @kuba2k2 esphome/components/bl0939/* @ziceva esphome/components/bl0940/* @tobias- esphome/components/bl0942/* @dbuezas @@ -146,6 +147,8 @@ esphome/components/kuntze/* @ssieb esphome/components/lcd_menu/* @numo68 esphome/components/ld2410/* @regevbr @sebcaps esphome/components/ledc/* @OttoWinter +esphome/components/libretiny/* @kuba2k2 +esphome/components/libretiny_pwm/* @kuba2k2 esphome/components/light/* @esphome/core esphome/components/lilygo_t5_47/touchscreen/* @jesserockz esphome/components/lock/* @esphome/core @@ -234,6 +237,7 @@ esphome/components/rgbct/* @jesserockz esphome/components/rp2040/* @jesserockz esphome/components/rp2040_pio_led_strip/* @Papa-DMan esphome/components/rp2040_pwm/* @jesserockz +esphome/components/rtl87xx/* @kuba2k2 esphome/components/rtttl/* @glmnet esphome/components/safe_mode/* @jsuanet @paulmonigatti esphome/components/scd4x/* @martgras @sjtrny diff --git a/esphome/__main__.py b/esphome/__main__.py index 697adc03a3..9b208c2280 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -26,6 +26,8 @@ from esphome.const import ( CONF_ESPHOME, CONF_PLATFORMIO_OPTIONS, CONF_SUBSTITUTIONS, + PLATFORM_BK72XX, + PLATFORM_RTL87XX, PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_RP2040, @@ -278,20 +280,25 @@ def upload_using_esptool(config, port): return run_esptool(115200) +def upload_using_platformio(config, port): + from esphome import platformio_api + + upload_args = ["-t", "upload", "-t", "nobuild"] + if port is not None: + upload_args += ["--upload-port", port] + return platformio_api.run_platformio_cli_run(config, CORE.verbose, *upload_args) + + def upload_program(config, args, host): if get_port_type(host) == "SERIAL": if CORE.target_platform in (PLATFORM_ESP32, PLATFORM_ESP8266): return upload_using_esptool(config, host) if CORE.target_platform in (PLATFORM_RP2040): - from esphome import platformio_api + return upload_using_platformio(config, args.device) - upload_args = ["-t", "upload"] - if args.device is not None: - upload_args += ["--upload-port", args.device] - return platformio_api.run_platformio_cli_run( - config, CORE.verbose, *upload_args - ) + if CORE.target_platform in (PLATFORM_BK72XX, PLATFORM_RTL87XX): + return upload_using_platformio(config, host) return 1 # Unknown target platform diff --git a/esphome/components/adc/__init__.py b/esphome/components/adc/__init__.py index 015d6edd21..ba72951777 100644 --- a/esphome/components/adc/__init__.py +++ b/esphome/components/adc/__init__.py @@ -1,7 +1,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome import pins -from esphome.const import CONF_INPUT +from esphome.const import CONF_ANALOG, CONF_INPUT from esphome.core import CORE from esphome.components.esp32 import get_esp32_variant @@ -166,8 +166,6 @@ def validate_adc_pin(value): return pins.internal_gpio_input_pin_schema(value) if CORE.is_esp8266: - from esphome.components.esp8266.gpio import CONF_ANALOG - value = pins.internal_gpio_pin_number({CONF_ANALOG: True, CONF_INPUT: True})( value ) @@ -184,4 +182,9 @@ def validate_adc_pin(value): raise cv.Invalid("RP2040: Only pins 26, 27, 28 and 29 support ADC") return pins.internal_gpio_input_pin_schema(value) + if CORE.is_libretiny: + return pins.gpio_pin_schema( + {CONF_ANALOG: True, CONF_INPUT: True}, internal=True + )(value) + raise NotImplementedError diff --git a/esphome/components/adc/adc_sensor.cpp b/esphome/components/adc/adc_sensor.cpp index 665ecfd6b5..0642cd7f3f 100644 --- a/esphome/components/adc/adc_sensor.cpp +++ b/esphome/components/adc/adc_sensor.cpp @@ -92,13 +92,13 @@ extern "C" void ADCSensor::dump_config() { LOG_SENSOR("", "ADC Sensor", this); -#ifdef USE_ESP8266 +#if defined(USE_ESP8266) || defined(USE_LIBRETINY) #ifdef USE_ADC_SENSOR_VCC ESP_LOGCONFIG(TAG, " Pin: VCC"); #else LOG_PIN(" Pin: ", pin_); #endif -#endif // USE_ESP8266 +#endif // USE_ESP8266 || USE_LIBRETINY #ifdef USE_ESP32 LOG_PIN(" Pin: ", pin_); @@ -254,6 +254,15 @@ float ADCSensor::sample() { } #endif +#ifdef USE_LIBRETINY +float ADCSensor::sample() { + if (output_raw_) { + return analogRead(this->pin_->get_pin()); // NOLINT + } + return analogReadVoltage(this->pin_->get_pin()) / 1000.0f; // NOLINT +} +#endif // USE_LIBRETINY + #ifdef USE_ESP8266 std::string ADCSensor::unique_id() { return get_mac_address() + "-adc"; } #endif diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index a46efd80e5..ceec53bb65 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -1051,6 +1051,10 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) { resp.manufacturer = "Espressif"; #elif defined(USE_RP2040) resp.manufacturer = "Raspberry Pi"; +#elif defined(USE_BK72XX) + resp.manufacturer = "Beken"; +#elif defined(USE_RTL87XX) + resp.manufacturer = "Realtek"; #elif defined(USE_HOST) resp.manufacturer = "Host"; #endif diff --git a/esphome/components/async_tcp/__init__.py b/esphome/components/async_tcp/__init__.py index 1d127623f1..1677d4b9a8 100644 --- a/esphome/components/async_tcp/__init__.py +++ b/esphome/components/async_tcp/__init__.py @@ -8,15 +8,15 @@ CODEOWNERS = ["@OttoWinter"] CONFIG_SCHEMA = cv.All( cv.Schema({}), cv.only_with_arduino, - cv.only_on(["esp32", "esp8266"]), + cv.only_on(["esp32", "esp8266", "bk72xx", "rtl87xx"]), ) @coroutine_with_priority(200.0) async def to_code(config): - if CORE.is_esp32: + if CORE.is_esp32 or CORE.is_libretiny: # https://github.com/esphome/AsyncTCP/blob/master/library.json - cg.add_library("esphome/AsyncTCP-esphome", "1.2.2") + cg.add_library("esphome/AsyncTCP-esphome", "2.0.1") elif CORE.is_esp8266: # https://github.com/esphome/ESPAsyncTCP cg.add_library("esphome/ESPAsyncTCP-esphome", "1.2.3") diff --git a/esphome/components/bk72xx/__init__.py b/esphome/components/bk72xx/__init__.py new file mode 100644 index 0000000000..6737631ac7 --- /dev/null +++ b/esphome/components/bk72xx/__init__.py @@ -0,0 +1,51 @@ +# This file was auto-generated by libretiny/generate_components.py +# Do not modify its contents. +# For custom pin validators, put validate_pin() or validate_usage() +# in gpio.py file in this directory. +# For changing schema/pin schema, put COMPONENT_SCHEMA or COMPONENT_PIN_SCHEMA +# in schema.py file in this directory. + +from esphome import pins +from esphome.components import libretiny +from esphome.components.libretiny.const import ( + COMPONENT_BK72XX, + KEY_COMPONENT_DATA, + KEY_LIBRETINY, + LibreTinyComponent, +) +from esphome.core import CORE + +from .boards import BK72XX_BOARDS, BK72XX_BOARD_PINS + +CODEOWNERS = ["@kuba2k2"] +AUTO_LOAD = ["libretiny"] + +COMPONENT_DATA = LibreTinyComponent( + name=COMPONENT_BK72XX, + boards=BK72XX_BOARDS, + board_pins=BK72XX_BOARD_PINS, + pin_validation=None, + usage_validation=None, +) + + +def _set_core_data(config): + CORE.data[KEY_LIBRETINY] = {} + CORE.data[KEY_LIBRETINY][KEY_COMPONENT_DATA] = COMPONENT_DATA + return config + + +CONFIG_SCHEMA = libretiny.BASE_SCHEMA + +PIN_SCHEMA = libretiny.gpio.BASE_PIN_SCHEMA + +CONFIG_SCHEMA.prepend_extra(_set_core_data) + + +async def to_code(config): + return await libretiny.component_to_code(config) + + +@pins.PIN_SCHEMA_REGISTRY.register("bk72xx", PIN_SCHEMA) +async def pin_to_code(config): + return await libretiny.gpio.component_pin_to_code(config) diff --git a/esphome/components/bk72xx/boards.py b/esphome/components/bk72xx/boards.py new file mode 100644 index 0000000000..8e3e8a97a2 --- /dev/null +++ b/esphome/components/bk72xx/boards.py @@ -0,0 +1,1264 @@ +# This file was auto-generated by libretiny/generate_components.py +# Do not modify its contents. + +from esphome.components.libretiny.const import ( + FAMILY_BK7231N, + FAMILY_BK7231Q, + FAMILY_BK7231T, + FAMILY_BK7251, +) + +BK72XX_BOARDS = { + "cb1s": { + "name": "CB1S Wi-Fi Module", + "family": FAMILY_BK7231N, + }, + "cb2l": { + "name": "CB2L Wi-Fi Module", + "family": FAMILY_BK7231N, + }, + "cb2s": { + "name": "CB2S Wi-Fi Module", + "family": FAMILY_BK7231N, + }, + "cb3l": { + "name": "CB3L Wi-Fi Module", + "family": FAMILY_BK7231N, + }, + "cb3s": { + "name": "CB3S Wi-Fi Module", + "family": FAMILY_BK7231N, + }, + "cb3se": { + "name": "CB3SE Wi-Fi Module", + "family": FAMILY_BK7231N, + }, + "cblc5": { + "name": "CBLC5 Wi-Fi Module", + "family": FAMILY_BK7231N, + }, + "cbu": { + "name": "CBU Wi-Fi Module", + "family": FAMILY_BK7231N, + }, + "generic-bk7231n-qfn32-tuya": { + "name": "Generic - BK7231N (Tuya QFN32)", + "family": FAMILY_BK7231N, + }, + "generic-bk7231t-qfn32-tuya": { + "name": "Generic - BK7231T (Tuya QFN32)", + "family": FAMILY_BK7231T, + }, + "generic-bk7252": { + "name": "Generic - BK7252", + "family": FAMILY_BK7251, + }, + "lsc-lma35-t": { + "name": "LSC LMA35 BK7231T", + "family": FAMILY_BK7231T, + }, + "lsc-lma35": { + "name": "LSC LMA35 BK7231N", + "family": FAMILY_BK7231N, + }, + "wa2": { + "name": "WA2 Wi-Fi Module", + "family": FAMILY_BK7231Q, + }, + "wb1s": { + "name": "WB1S Wi-Fi Module", + "family": FAMILY_BK7231T, + }, + "wb2l-m1": { + "name": "WB2L_M1 Wi-Fi Module", + "family": FAMILY_BK7231N, + }, + "wb2l": { + "name": "WB2L Wi-Fi Module", + "family": FAMILY_BK7231T, + }, + "wb2s": { + "name": "WB2S Wi-Fi Module", + "family": FAMILY_BK7231T, + }, + "wb3l": { + "name": "WB3L Wi-Fi Module", + "family": FAMILY_BK7231T, + }, + "wb3s": { + "name": "WB3S Wi-Fi Module", + "family": FAMILY_BK7231T, + }, + "wblc5": { + "name": "WBLC5 Wi-Fi Module", + "family": FAMILY_BK7231T, + }, +} + +BK72XX_BOARD_PINS = { + "cb1s": { + "WIRE1_SCL": 20, + "WIRE1_SDA": 21, + "WIRE2_SCL": 0, + "WIRE2_SDA": 1, + "SERIAL1_RX": 10, + "SERIAL1_TX": 11, + "SERIAL2_RX": 1, + "SERIAL2_TX": 0, + "ADC3": 23, + "P0": 0, + "P1": 1, + "P6": 6, + "P7": 7, + "P8": 8, + "P9": 9, + "P10": 10, + "P11": 11, + "P20": 20, + "P21": 21, + "P22": 22, + "P23": 23, + "P24": 24, + "P26": 26, + "PWM0": 6, + "PWM1": 7, + "PWM2": 8, + "PWM3": 9, + "PWM4": 24, + "PWM5": 26, + "RX1": 10, + "RX2": 1, + "SCL1": 20, + "SCL2": 0, + "SDA1": 21, + "SDA2": 1, + "TX1": 11, + "TX2": 0, + "D0": 11, + "D1": 10, + "D2": 6, + "D3": 7, + "D4": 0, + "D5": 9, + "D6": 8, + "D7": 1, + "D8": 24, + "D9": 26, + "D10": 23, + "D11": 20, + "D12": 21, + "D13": 22, + "A0": 23, + }, + "cb2l": { + "SERIAL1_RX": 10, + "SERIAL1_TX": 11, + "SERIAL2_TX": 0, + "P0": 0, + "P6": 6, + "P7": 7, + "P8": 8, + "P10": 10, + "P11": 11, + "P21": 21, + "P24": 24, + "P26": 26, + "PWM0": 6, + "PWM1": 7, + "PWM2": 8, + "PWM4": 24, + "PWM5": 26, + "RX1": 10, + "SCL2": 0, + "SDA1": 21, + "TX1": 11, + "TX2": 0, + "D0": 8, + "D1": 7, + "D2": 6, + "D3": 26, + "D4": 24, + "D5": 10, + "D6": 0, + "D7": 11, + "D8": 21, + }, + "cb2s": { + "WIRE2_SCL": 0, + "WIRE2_SDA": 1, + "SERIAL1_RX": 10, + "SERIAL1_TX": 11, + "SERIAL2_RX": 1, + "SERIAL2_TX": 0, + "ADC3": 23, + "P0": 0, + "P1": 1, + "P6": 6, + "P7": 7, + "P8": 8, + "P10": 10, + "P11": 11, + "P21": 21, + "P23": 23, + "P24": 24, + "P26": 26, + "PWM0": 6, + "PWM1": 7, + "PWM2": 8, + "PWM4": 24, + "PWM5": 26, + "RX1": 10, + "RX2": 1, + "SCL2": 0, + "SDA1": 21, + "SDA2": 1, + "TX1": 11, + "TX2": 0, + "D0": 6, + "D1": 7, + "D2": 8, + "D3": 23, + "D4": 10, + "D5": 11, + "D6": 24, + "D7": 26, + "D8": 0, + "D9": 1, + "D10": 21, + "A0": 23, + }, + "cb3l": { + "SERIAL1_RX": 10, + "SERIAL1_TX": 11, + "SERIAL2_TX": 0, + "ADC3": 23, + "P0": 0, + "P6": 6, + "P7": 7, + "P8": 8, + "P9": 9, + "P10": 10, + "P11": 11, + "P14": 14, + "P21": 21, + "P23": 23, + "P24": 24, + "P26": 26, + "PWM0": 6, + "PWM1": 7, + "PWM2": 8, + "PWM3": 9, + "PWM4": 24, + "PWM5": 26, + "RX1": 10, + "SCK": 14, + "SCL2": 0, + "SDA1": 21, + "TX1": 11, + "TX2": 0, + "D0": 23, + "D1": 14, + "D2": 26, + "D3": 24, + "D4": 6, + "D5": 9, + "D6": 0, + "D7": 21, + "D8": 8, + "D9": 7, + "D10": 10, + "D11": 11, + "A0": 23, + }, + "cb3s": { + "WIRE1_SCL": 20, + "WIRE1_SDA_0": 21, + "WIRE1_SDA_1": 21, + "SERIAL1_RX": 10, + "SERIAL1_TX": 11, + "SERIAL2_TX": 0, + "ADC3": 23, + "P0": 0, + "P6": 6, + "P7": 7, + "P8": 8, + "P9": 9, + "P10": 10, + "P11": 11, + "P14": 14, + "P20": 20, + "P21": 21, + "P22": 22, + "P23": 23, + "P24": 24, + "P26": 26, + "PWM0": 6, + "PWM1": 7, + "PWM2": 8, + "PWM3": 9, + "PWM4": 24, + "PWM5": 26, + "RX1": 10, + "SCK": 14, + "SCL1": 20, + "SCL2": 0, + "SDA1": 21, + "TX1": 11, + "TX2": 0, + "D0": 23, + "D1": 14, + "D2": 26, + "D3": 24, + "D4": 6, + "D5": 9, + "D6": 0, + "D7": 21, + "D8": 8, + "D9": 7, + "D10": 10, + "D11": 11, + "D12": 22, + "D13": 20, + "A0": 23, + }, + "cb3se": { + "WIRE2_SCL": 0, + "WIRE2_SDA": 1, + "SERIAL1_RX": 10, + "SERIAL1_TX": 11, + "SERIAL2_RX": 1, + "SERIAL2_TX": 0, + "ADC3": 23, + "CS": 15, + "MISO": 17, + "MOSI": 16, + "P0": 0, + "P1": 1, + "P6": 6, + "P7": 7, + "P8": 8, + "P9": 9, + "P10": 10, + "P11": 11, + "P14": 14, + "P15": 15, + "P16": 16, + "P17": 17, + "P20": 20, + "P22": 22, + "P23": 23, + "P24": 24, + "P26": 26, + "PWM0": 6, + "PWM1": 7, + "PWM2": 8, + "PWM3": 9, + "PWM4": 24, + "PWM5": 26, + "RX1": 10, + "RX2": 1, + "SCK": 14, + "SCL1": 20, + "SCL2": 0, + "SDA2": 1, + "TX1": 11, + "TX2": 0, + "D0": 23, + "D1": 14, + "D2": 26, + "D3": 24, + "D4": 6, + "D5": 9, + "D6": 0, + "D7": 1, + "D8": 8, + "D9": 7, + "D10": 10, + "D11": 11, + "D12": 15, + "D13": 22, + "D14": 20, + "D15": 17, + "D16": 16, + "A0": 23, + }, + "cblc5": { + "WIRE2_SCL": 0, + "WIRE2_SDA": 1, + "SERIAL1_RX": 10, + "SERIAL1_TX": 11, + "SERIAL2_RX": 1, + "SERIAL2_TX": 0, + "P0": 0, + "P1": 1, + "P6": 6, + "P10": 10, + "P11": 11, + "P21": 21, + "P24": 24, + "P26": 26, + "PWM0": 6, + "PWM4": 24, + "PWM5": 26, + "RX1": 10, + "RX2": 1, + "SCL2": 0, + "SDA1": 21, + "SDA2": 1, + "TX1": 11, + "TX2": 0, + "D0": 24, + "D1": 6, + "D2": 26, + "D3": 11, + "D4": 10, + "D5": 1, + "D6": 0, + "D7": 21, + }, + "cbu": { + "WIRE1_SCL": 20, + "WIRE1_SDA": 21, + "WIRE2_SCL": 0, + "WIRE2_SDA": 1, + "SERIAL1_RX": 10, + "SERIAL1_TX": 11, + "SERIAL2_RX": 1, + "SERIAL2_TX": 0, + "ADC3": 23, + "CS": 15, + "MISO": 17, + "MOSI": 16, + "P0": 0, + "P1": 1, + "P6": 6, + "P7": 7, + "P8": 8, + "P9": 9, + "P10": 10, + "P11": 11, + "P14": 14, + "P15": 15, + "P16": 16, + "P17": 17, + "P20": 20, + "P21": 21, + "P22": 22, + "P23": 23, + "P24": 24, + "P26": 26, + "P28": 28, + "PWM0": 6, + "PWM1": 7, + "PWM2": 8, + "PWM3": 9, + "PWM4": 24, + "PWM5": 26, + "RX1": 10, + "RX2": 1, + "SCK": 14, + "SCL1": 20, + "SCL2": 0, + "SDA1": 21, + "SDA2": 1, + "TX1": 11, + "TX2": 0, + "D0": 14, + "D1": 16, + "D2": 20, + "D3": 22, + "D4": 23, + "D5": 1, + "D6": 0, + "D7": 8, + "D8": 7, + "D9": 6, + "D10": 26, + "D11": 24, + "D12": 11, + "D13": 10, + "D14": 28, + "D15": 9, + "D16": 17, + "D17": 15, + "D18": 21, + "A0": 23, + }, + "generic-bk7231n-qfn32-tuya": { + "WIRE1_SCL": 20, + "WIRE1_SDA": 21, + "WIRE2_SCL": 0, + "WIRE2_SDA": 1, + "SERIAL1_RX": 10, + "SERIAL1_TX": 11, + "SERIAL2_RX": 1, + "SERIAL2_TX": 0, + "ADC3": 23, + "CS": 15, + "MISO": 17, + "MOSI": 16, + "P0": 0, + "P1": 1, + "P6": 6, + "P7": 7, + "P8": 8, + "P9": 9, + "P10": 10, + "P11": 11, + "P14": 14, + "P15": 15, + "P16": 16, + "P17": 17, + "P20": 20, + "P21": 21, + "P22": 22, + "P23": 23, + "P24": 24, + "P26": 26, + "P28": 28, + "PWM0": 6, + "PWM1": 7, + "PWM2": 8, + "PWM3": 9, + "PWM4": 24, + "PWM5": 26, + "RX1": 10, + "RX2": 1, + "SCK": 14, + "SCL1": 20, + "SCL2": 0, + "SDA1": 21, + "SDA2": 1, + "TX1": 11, + "TX2": 0, + "D0": 0, + "D1": 1, + "D2": 6, + "D3": 7, + "D4": 8, + "D5": 9, + "D6": 10, + "D7": 11, + "D8": 14, + "D9": 15, + "D10": 16, + "D11": 17, + "D12": 20, + "D13": 21, + "D14": 22, + "D15": 23, + "D16": 24, + "D17": 26, + "D18": 28, + "A0": 23, + }, + "generic-bk7231t-qfn32-tuya": { + "WIRE1_SCL": 20, + "WIRE1_SDA": 21, + "WIRE2_SCL": 0, + "WIRE2_SDA": 1, + "SERIAL1_RX": 10, + "SERIAL1_TX": 11, + "SERIAL2_RX": 1, + "SERIAL2_TX": 0, + "ADC3": 23, + "CS": 15, + "MISO": 17, + "MOSI": 16, + "P0": 0, + "P1": 1, + "P6": 6, + "P7": 7, + "P8": 8, + "P9": 9, + "P10": 10, + "P11": 11, + "P14": 14, + "P15": 15, + "P16": 16, + "P17": 17, + "P20": 20, + "P21": 21, + "P22": 22, + "P23": 23, + "P24": 24, + "P26": 26, + "P28": 28, + "PWM0": 6, + "PWM1": 7, + "PWM2": 8, + "PWM3": 9, + "PWM4": 24, + "PWM5": 26, + "RX1": 10, + "RX2": 1, + "SCK": 14, + "SCL1": 20, + "SCL2": 0, + "SDA1": 21, + "SDA2": 1, + "TX1": 11, + "TX2": 0, + "D0": 0, + "D1": 1, + "D2": 6, + "D3": 7, + "D4": 8, + "D5": 9, + "D6": 10, + "D7": 11, + "D8": 14, + "D9": 15, + "D10": 16, + "D11": 17, + "D12": 20, + "D13": 21, + "D14": 22, + "D15": 23, + "D16": 24, + "D17": 26, + "D18": 28, + "A0": 23, + }, + "generic-bk7252": { + "WIRE1_SCL": 20, + "WIRE1_SDA": 21, + "WIRE2_SCL": 0, + "WIRE2_SDA": 1, + "SERIAL1_CTS": 12, + "SERIAL1_RTS": 13, + "SERIAL1_RX": 10, + "SERIAL1_TX": 11, + "SERIAL2_RX": 1, + "SERIAL2_TX": 0, + "ADC1": 4, + "ADC2": 5, + "ADC3": 23, + "ADC4": 2, + "ADC5": 3, + "ADC6": 12, + "ADC7": 13, + "CS": 15, + "CTS1": 12, + "MISO": 17, + "MOSI": 16, + "P0": 0, + "P1": 1, + "P2": 2, + "P3": 3, + "P4": 4, + "P5": 5, + "P6": 6, + "P7": 7, + "P10": 10, + "P11": 11, + "P12": 12, + "P13": 13, + "P14": 14, + "P15": 15, + "P16": 16, + "P17": 17, + "P18": 18, + "P19": 19, + "P20": 20, + "P21": 21, + "P22": 22, + "P23": 23, + "P24": 24, + "P25": 25, + "P26": 26, + "P27": 27, + "P28": 28, + "P29": 29, + "P30": 30, + "P31": 31, + "P32": 32, + "P33": 33, + "P34": 34, + "P35": 35, + "P36": 36, + "P37": 37, + "P38": 38, + "P39": 39, + "PWM0": 6, + "PWM1": 7, + "PWM4": 24, + "PWM5": 26, + "RTS1": 13, + "RX1": 10, + "RX2": 1, + "SCK": 14, + "SCL1": 20, + "SCL2": 0, + "SDA1": 21, + "SDA2": 1, + "TX1": 11, + "TX2": 0, + "D0": 0, + "D1": 1, + "D2": 2, + "D3": 3, + "D4": 4, + "D5": 5, + "D6": 6, + "D7": 7, + "D8": 10, + "D9": 11, + "D10": 12, + "D11": 13, + "D12": 14, + "D13": 15, + "D14": 16, + "D15": 17, + "D16": 18, + "D17": 19, + "D18": 20, + "D19": 21, + "D20": 22, + "D21": 23, + "D22": 24, + "D23": 25, + "D24": 26, + "D25": 27, + "D26": 28, + "D27": 29, + "D28": 30, + "D29": 31, + "D30": 32, + "D31": 33, + "D32": 34, + "D33": 35, + "D34": 36, + "D35": 37, + "D36": 38, + "D37": 39, + "A1": 4, + "A2": 5, + "A3": 23, + "A4": 3, + "A5": 2, + "A6": 12, + "A7": 13, + }, + "lsc-lma35-t": { + "WIRE2_SCL": 0, + "WIRE2_SDA": 1, + "SERIAL1_RX": 10, + "SERIAL1_TX": 11, + "SERIAL2_RX": 1, + "SERIAL2_TX": 0, + "ADC3": 23, + "MOSI": 16, + "P0": 0, + "P1": 1, + "P6": 6, + "P7": 7, + "P8": 8, + "P9": 9, + "P10": 10, + "P11": 11, + "P14": 14, + "P16": 16, + "P21": 21, + "P22": 22, + "P23": 23, + "P24": 24, + "P26": 26, + "PWM0": 6, + "PWM1": 7, + "PWM2": 8, + "PWM3": 9, + "PWM4": 24, + "PWM5": 26, + "RX1": 10, + "RX2": 1, + "SCK": 14, + "SCL2": 0, + "SDA1": 21, + "SDA2": 1, + "TX1": 11, + "TX2": 0, + "D0": 26, + "D1": 14, + "D2": 16, + "D3": 24, + "D4": 22, + "D5": 0, + "D6": 23, + "D7": 8, + "D8": 9, + "D9": 21, + "D10": 6, + "D11": 7, + "D12": 10, + "D13": 11, + "D14": 1, + "A0": 23, + }, + "lsc-lma35": { + "WIRE2_SCL": 0, + "WIRE2_SDA": 1, + "SERIAL1_RX": 10, + "SERIAL1_TX": 11, + "SERIAL2_RX": 1, + "SERIAL2_TX": 0, + "ADC3": 23, + "MOSI": 16, + "P0": 0, + "P1": 1, + "P6": 6, + "P7": 7, + "P8": 8, + "P9": 9, + "P10": 10, + "P11": 11, + "P14": 14, + "P16": 16, + "P21": 21, + "P22": 22, + "P23": 23, + "P24": 24, + "P26": 26, + "PWM0": 6, + "PWM1": 7, + "PWM2": 8, + "PWM3": 9, + "PWM4": 24, + "PWM5": 26, + "RX1": 10, + "RX2": 1, + "SCK": 14, + "SCL2": 0, + "SDA1": 21, + "SDA2": 1, + "TX1": 11, + "TX2": 0, + "D0": 26, + "D1": 14, + "D2": 16, + "D3": 24, + "D4": 22, + "D5": 0, + "D6": 23, + "D7": 8, + "D8": 9, + "D9": 21, + "D10": 6, + "D11": 7, + "D12": 10, + "D13": 11, + "D14": 1, + "A0": 23, + }, + "wa2": { + "WIRE1_SCL": 20, + "WIRE1_SDA": 21, + "SERIAL1_RX": 10, + "SERIAL1_TX": 11, + "SERIAL2_TX": 0, + "ADC1": 4, + "ADC3": 23, + "P0": 0, + "P4": 4, + "P6": 6, + "P7": 7, + "P8": 8, + "P10": 10, + "P11": 11, + "P18": 18, + "P19": 19, + "P20": 20, + "P21": 21, + "P22": 22, + "P23": 23, + "PWM0": 6, + "PWM1": 7, + "PWM2": 8, + "PWM4": 18, + "PWM5": 19, + "RX1": 10, + "SCL1": 20, + "SCL2": 0, + "SDA1": 21, + "TX1": 11, + "TX2": 0, + "D0": 8, + "D1": 7, + "D2": 6, + "D3": 23, + "D4": 10, + "D5": 11, + "D6": 18, + "D7": 19, + "D8": 20, + "D9": 4, + "D10": 0, + "D11": 21, + "D12": 22, + "A0": 23, + }, + "wb1s": { + "WIRE2_SCL": 0, + "WIRE2_SDA": 1, + "SERIAL1_RX": 10, + "SERIAL1_TX": 11, + "SERIAL2_RX": 1, + "SERIAL2_TX": 0, + "ADC3": 23, + "P0": 0, + "P1": 1, + "P6": 6, + "P7": 7, + "P8": 8, + "P9": 9, + "P10": 10, + "P11": 11, + "P23": 23, + "P24": 24, + "P26": 26, + "PWM0": 6, + "PWM1": 7, + "PWM2": 8, + "PWM3": 9, + "PWM4": 24, + "PWM5": 26, + "RX1": 10, + "RX2": 1, + "SCL2": 0, + "SDA2": 1, + "TX1": 11, + "TX2": 0, + "D0": 11, + "D1": 10, + "D2": 26, + "D3": 24, + "D4": 0, + "D5": 8, + "D6": 7, + "D7": 1, + "D8": 9, + "D9": 6, + "D10": 23, + "A0": 23, + }, + "wb2l-m1": { + "WIRE1_SCL": 20, + "WIRE1_SDA": 21, + "WIRE2_SCL": 0, + "WIRE2_SDA": 1, + "SERIAL1_RX": 10, + "SERIAL1_TX": 11, + "SERIAL2_RX": 1, + "SERIAL2_TX": 0, + "ADC3": 23, + "P0": 0, + "P1": 1, + "P6": 6, + "P7": 7, + "P8": 8, + "P10": 10, + "P11": 11, + "P20": 20, + "P21": 21, + "P22": 22, + "P23": 23, + "P24": 24, + "P26": 26, + "PWM0": 6, + "PWM1": 7, + "PWM2": 8, + "PWM4": 24, + "PWM5": 26, + "RX1": 10, + "RX2": 1, + "SCL1": 20, + "SCL2": 0, + "SDA1": 21, + "SDA2": 1, + "TX1": 11, + "TX2": 0, + "D0": 8, + "D1": 7, + "D2": 6, + "D3": 26, + "D4": 24, + "D5": 10, + "D6": 11, + "D7": 1, + "D8": 0, + "D9": 20, + "D10": 21, + "D11": 23, + "D12": 22, + "A0": 23, + }, + "wb2l": { + "WIRE1_SCL": 20, + "WIRE1_SDA": 21, + "WIRE2_SCL": 0, + "WIRE2_SDA": 1, + "SERIAL1_RX": 10, + "SERIAL1_TX": 11, + "SERIAL2_RX": 1, + "SERIAL2_TX": 0, + "ADC3": 23, + "P0": 0, + "P1": 1, + "P6": 6, + "P7": 7, + "P8": 8, + "P10": 10, + "P11": 11, + "P20": 20, + "P21": 21, + "P22": 22, + "P23": 23, + "P24": 24, + "P26": 26, + "PWM0": 6, + "PWM1": 7, + "PWM2": 8, + "PWM4": 24, + "PWM5": 26, + "RX1": 10, + "RX2": 1, + "SCL1": 20, + "SCL2": 0, + "SDA1": 21, + "SDA2": 1, + "TX1": 11, + "TX2": 0, + "D0": 8, + "D1": 7, + "D2": 6, + "D3": 26, + "D4": 24, + "D5": 10, + "D6": 11, + "D7": 1, + "D8": 0, + "D9": 20, + "D10": 21, + "D11": 23, + "D12": 22, + "A0": 23, + }, + "wb2s": { + "WIRE1_SCL": 20, + "WIRE1_SDA": 21, + "WIRE2_SCL": 0, + "WIRE2_SDA": 1, + "SERIAL1_RX": 10, + "SERIAL1_TX": 11, + "SERIAL2_RX": 1, + "SERIAL2_TX": 0, + "ADC3": 23, + "P0": 0, + "P1": 1, + "P6": 6, + "P7": 7, + "P8": 8, + "P9": 9, + "P10": 10, + "P11": 11, + "P20": 20, + "P21": 21, + "P22": 22, + "P23": 23, + "P24": 24, + "P26": 26, + "PWM0": 6, + "PWM1": 7, + "PWM2": 8, + "PWM3": 9, + "PWM4": 24, + "PWM5": 26, + "RX1": 10, + "RX2": 1, + "SCL1": 20, + "SCL2": 0, + "SDA1": 21, + "SDA2": 1, + "TX1": 11, + "TX2": 0, + "D0": 8, + "D1": 7, + "D2": 6, + "D3": 23, + "D4": 10, + "D5": 11, + "D6": 24, + "D7": 26, + "D8": 20, + "D9": 9, + "D10": 1, + "D11": 0, + "D12": 21, + "D13": 22, + "A0": 23, + }, + "wb3l": { + "WIRE1_SCL": 20, + "WIRE1_SDA": 21, + "WIRE2_SCL": 0, + "WIRE2_SDA": 1, + "SERIAL1_RX": 10, + "SERIAL1_TX": 11, + "SERIAL2_RX": 1, + "SERIAL2_TX": 0, + "ADC3": 23, + "MOSI": 16, + "P0": 0, + "P1": 1, + "P6": 6, + "P7": 7, + "P8": 8, + "P9": 9, + "P10": 10, + "P11": 11, + "P14": 14, + "P16": 16, + "P20": 20, + "P21": 21, + "P22": 22, + "P23": 23, + "P24": 24, + "P26": 26, + "PWM0": 6, + "PWM1": 7, + "PWM2": 8, + "PWM3": 9, + "PWM4": 24, + "PWM5": 26, + "RX1": 10, + "RX2": 1, + "SCK": 14, + "SCL1": 20, + "SCL2": 0, + "SDA1": 21, + "SDA2": 1, + "TX1": 11, + "TX2": 0, + "D0": 23, + "D1": 14, + "D2": 26, + "D3": 24, + "D4": 6, + "D5": 9, + "D6": 0, + "D7": 16, + "D8": 8, + "D9": 7, + "D10": 10, + "D11": 11, + "D12": 22, + "D13": 21, + "D14": 20, + "D15": 1, + "A0": 23, + }, + "wb3s": { + "WIRE1_SCL": 20, + "WIRE1_SDA": 21, + "WIRE2_SCL": 0, + "WIRE2_SDA": 1, + "SERIAL1_RX": 10, + "SERIAL1_TX": 11, + "SERIAL2_RX": 1, + "SERIAL2_TX": 0, + "ADC3": 23, + "P0": 0, + "P1": 1, + "P6": 6, + "P7": 7, + "P8": 8, + "P9": 9, + "P10": 10, + "P11": 11, + "P14": 14, + "P20": 20, + "P21": 21, + "P22": 22, + "P23": 23, + "P24": 24, + "P26": 26, + "PWM0": 6, + "PWM1": 7, + "PWM2": 8, + "PWM3": 9, + "PWM4": 24, + "PWM5": 26, + "RX1": 10, + "RX2": 1, + "SCK": 14, + "SCL1": 20, + "SCL2": 0, + "SDA1": 21, + "SDA2": 1, + "TX1": 11, + "TX2": 0, + "D0": 23, + "D1": 14, + "D2": 26, + "D3": 24, + "D4": 6, + "D5": 7, + "D6": 0, + "D7": 1, + "D8": 9, + "D9": 8, + "D10": 10, + "D11": 11, + "D12": 22, + "D13": 21, + "D14": 20, + "A0": 23, + }, + "wblc5": { + "WIRE1_SCL": 20, + "WIRE1_SDA": 21, + "WIRE2_SCL": 0, + "WIRE2_SDA": 1, + "SERIAL1_RX": 10, + "SERIAL1_TX": 11, + "SERIAL2_RX": 1, + "SERIAL2_TX": 0, + "ADC3": 23, + "P0": 0, + "P1": 1, + "P6": 6, + "P10": 10, + "P11": 11, + "P20": 20, + "P21": 21, + "P22": 22, + "P23": 23, + "P24": 24, + "P26": 26, + "PWM0": 6, + "PWM4": 24, + "PWM5": 26, + "RX1": 10, + "RX2": 1, + "SCL1": 20, + "SCL2": 0, + "SDA1": 21, + "SDA2": 1, + "TX1": 11, + "TX2": 0, + "D0": 24, + "D1": 6, + "D2": 26, + "D3": 10, + "D4": 11, + "D5": 1, + "D6": 0, + "D7": 20, + "D8": 21, + "D9": 22, + "D10": 23, + "A0": 23, + }, +} + +BOARDS = BK72XX_BOARDS diff --git a/esphome/components/captive_portal/__init__.py b/esphome/components/captive_portal/__init__.py index db4a17f6f7..6af741c6b3 100644 --- a/esphome/components/captive_portal/__init__.py +++ b/esphome/components/captive_portal/__init__.py @@ -21,7 +21,7 @@ CONFIG_SCHEMA = cv.All( ), } ).extend(cv.COMPONENT_SCHEMA), - cv.only_on(["esp32", "esp8266"]), + cv.only_on(["esp32", "esp8266", "bk72xx", "rtl87xx"]), ) @@ -39,3 +39,5 @@ async def to_code(config): cg.add_library("WiFi", None) if CORE.is_esp8266: cg.add_library("DNSServer", None) + if CORE.is_libretiny: + cg.add_library("DNSServer", None) diff --git a/esphome/components/debug/debug_component.cpp b/esphome/components/debug/debug_component.cpp index 5ee1960267..52ee4b070e 100644 --- a/esphome/components/debug/debug_component.cpp +++ b/esphome/components/debug/debug_component.cpp @@ -28,7 +28,7 @@ #ifdef USE_ARDUINO #ifdef USE_RP2040 #include -#else +#elif defined(USE_ESP32) || defined(USE_ESP8266) #include #endif #endif @@ -45,6 +45,8 @@ static uint32_t get_free_heap() { return heap_caps_get_free_size(MALLOC_CAP_INTERNAL); #elif defined(USE_RP2040) return rp2040.getFreeHeap(); +#elif defined(USE_LIBRETINY) + return lt_heap_get_free(); #endif } @@ -75,7 +77,7 @@ void DebugComponent::dump_config() { this->free_heap_ = get_free_heap(); ESP_LOGD(TAG, "Free Heap Size: %" PRIu32 " bytes", this->free_heap_); -#if defined(USE_ARDUINO) && !defined(USE_RP2040) +#if defined(USE_ARDUINO) && (defined(USE_ESP32) || defined(USE_ESP8266)) const char *flash_mode; switch (ESP.getFlashChipMode()) { // NOLINT(readability-static-accessed-through-instance) case FM_QIO: @@ -107,7 +109,7 @@ void DebugComponent::dump_config() { device_info += "|Flash: " + to_string(ESP.getFlashChipSize() / 1024) + // NOLINT "kB Speed:" + to_string(ESP.getFlashChipSpeed() / 1000000) + "MHz Mode:"; // NOLINT device_info += flash_mode; -#endif // USE_ARDUINO +#endif // USE_ARDUINO && (USE_ESP32 || USE_ESP8266) #ifdef USE_ESP32 esp_chip_info_t info; @@ -340,6 +342,27 @@ void DebugComponent::dump_config() { device_info += "CPU Frequency: " + to_string(rp2040.f_cpu()); #endif // USE_RP2040 +#ifdef USE_LIBRETINY + ESP_LOGD(TAG, "LibreTiny Version: %s", lt_get_version()); + ESP_LOGD(TAG, "Chip: %s (%04x) @ %u MHz", lt_cpu_get_model_name(), lt_cpu_get_model(), lt_cpu_get_freq_mhz()); + ESP_LOGD(TAG, "Chip ID: 0x%06X", lt_cpu_get_mac_id()); + ESP_LOGD(TAG, "Board: %s", lt_get_board_code()); + ESP_LOGD(TAG, "Flash: %u KiB / RAM: %u KiB", lt_flash_get_size() / 1024, lt_ram_get_size() / 1024); + ESP_LOGD(TAG, "Reset Reason: %s", lt_get_reboot_reason_name(lt_get_reboot_reason())); + + device_info += "|Version: "; + device_info += LT_BANNER_STR + 10; + device_info += "|Reset Reason: "; + device_info += lt_get_reboot_reason_name(lt_get_reboot_reason()); + device_info += "|Chip Name: "; + device_info += lt_cpu_get_model_name(); + device_info += "|Chip ID: 0x" + format_hex(lt_cpu_get_mac_id()); + device_info += "|Flash: " + to_string(lt_flash_get_size() / 1024) + " KiB"; + device_info += "|RAM: " + to_string(lt_ram_get_size() / 1024) + " KiB"; + + reset_reason = lt_get_reboot_reason_name(lt_get_reboot_reason()); +#endif // USE_LIBRETINY + #ifdef USE_TEXT_SENSOR if (this->device_info_ != nullptr) { if (device_info.length() > 255) @@ -384,6 +407,8 @@ void DebugComponent::update() { this->block_sensor_->publish_state(ESP.getMaxFreeBlockSize()); #elif defined(USE_ESP32) this->block_sensor_->publish_state(heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL)); +#elif defined(USE_LIBRETINY) + this->block_sensor_->publish_state(lt_heap_get_max_alloc()); #endif } diff --git a/esphome/components/esp8266/gpio.py b/esphome/components/esp8266/gpio.py index d4b2078524..e75578cc16 100644 --- a/esphome/components/esp8266/gpio.py +++ b/esphome/components/esp8266/gpio.py @@ -2,6 +2,7 @@ import logging from dataclasses import dataclass from esphome.const import ( + CONF_ANALOG, CONF_ID, CONF_INPUT, CONF_INVERTED, @@ -140,7 +141,6 @@ def validate_supports(value): return value -CONF_ANALOG = "analog" ESP8266_PIN_SCHEMA = cv.All( { cv.GenerateID(): cv.declare_id(ESP8266GPIOPin), diff --git a/esphome/components/i2c/__init__.py b/esphome/components/i2c/__init__.py index a04e63e789..e38cfd23fa 100644 --- a/esphome/components/i2c/__init__.py +++ b/esphome/components/i2c/__init__.py @@ -42,23 +42,26 @@ pin_with_input_and_output_support = cv.All( ) -CONFIG_SCHEMA = cv.Schema( - { - cv.GenerateID(): _bus_declare_type, - cv.Optional(CONF_SDA, default="SDA"): pin_with_input_and_output_support, - cv.SplitDefault(CONF_SDA_PULLUP_ENABLED, esp32_idf=True): cv.All( - cv.only_with_esp_idf, cv.boolean - ), - cv.Optional(CONF_SCL, default="SCL"): pin_with_input_and_output_support, - cv.SplitDefault(CONF_SCL_PULLUP_ENABLED, esp32_idf=True): cv.All( - cv.only_with_esp_idf, cv.boolean - ), - cv.Optional(CONF_FREQUENCY, default="50kHz"): cv.All( - cv.frequency, cv.Range(min=0, min_included=False) - ), - cv.Optional(CONF_SCAN, default=True): cv.boolean, - } -).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): _bus_declare_type, + cv.Optional(CONF_SDA, default="SDA"): pin_with_input_and_output_support, + cv.SplitDefault(CONF_SDA_PULLUP_ENABLED, esp32_idf=True): cv.All( + cv.only_with_esp_idf, cv.boolean + ), + cv.Optional(CONF_SCL, default="SCL"): pin_with_input_and_output_support, + cv.SplitDefault(CONF_SCL_PULLUP_ENABLED, esp32_idf=True): cv.All( + cv.only_with_esp_idf, cv.boolean + ), + cv.Optional(CONF_FREQUENCY, default="50kHz"): cv.All( + cv.frequency, cv.Range(min=0, min_included=False) + ), + cv.Optional(CONF_SCAN, default=True): cv.boolean, + } + ).extend(cv.COMPONENT_SCHEMA), + cv.only_on(["esp32", "esp8266", "rp2040"]), +) @coroutine_with_priority(1.0) diff --git a/esphome/components/json/json_util.cpp b/esphome/components/json/json_util.cpp index f27d441804..bef494b64d 100644 --- a/esphome/components/json/json_util.cpp +++ b/esphome/components/json/json_util.cpp @@ -29,6 +29,8 @@ std::string build_json(const json_build_t &f) { const size_t free_heap = heap_caps_get_largest_free_block(MALLOC_CAP_8BIT); #elif defined(USE_RP2040) const size_t free_heap = rp2040.getFreeHeap(); +#elif defined(USE_LIBRETINY) + const size_t free_heap = lt_heap_get_free(); #endif size_t request_size = std::min(free_heap, (size_t) 512); @@ -71,6 +73,8 @@ void parse_json(const std::string &data, const json_parse_t &f) { const size_t free_heap = heap_caps_get_largest_free_block(MALLOC_CAP_8BIT); #elif defined(USE_RP2040) const size_t free_heap = rp2040.getFreeHeap(); +#elif defined(USE_LIBRETINY) + const size_t free_heap = lt_heap_get_free(); #endif bool pass = false; size_t request_size = std::min(free_heap, (size_t) (data.size() * 1.5)); diff --git a/esphome/components/libretiny/__init__.py b/esphome/components/libretiny/__init__.py new file mode 100644 index 0000000000..c6c63b48c8 --- /dev/null +++ b/esphome/components/libretiny/__init__.py @@ -0,0 +1,336 @@ +import json +import logging +from os.path import dirname, isfile, join + +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.const import ( + CONF_BOARD, + CONF_COMPONENT_ID, + CONF_DEBUG, + CONF_FAMILY, + CONF_FRAMEWORK, + CONF_ID, + CONF_NAME, + CONF_OPTIONS, + CONF_PROJECT, + CONF_SOURCE, + CONF_VERSION, + KEY_CORE, + KEY_FRAMEWORK_VERSION, + KEY_TARGET_FRAMEWORK, + KEY_TARGET_PLATFORM, + __version__, +) +from esphome.core import CORE + +from . import gpio # noqa +from .const import ( + CONF_GPIO_RECOVER, + CONF_LOGLEVEL, + CONF_SDK_SILENT, + CONF_UART_PORT, + FAMILIES, + FAMILY_COMPONENT, + FAMILY_FRIENDLY, + KEY_BOARD, + KEY_COMPONENT, + KEY_COMPONENT_DATA, + KEY_FAMILY, + KEY_LIBRETINY, + LT_DEBUG_MODULES, + LT_LOGLEVELS, + LibreTinyComponent, + LTComponent, +) + +_LOGGER = logging.getLogger(__name__) +CODEOWNERS = ["@kuba2k2"] +AUTO_LOAD = [] + + +def _detect_variant(value): + if KEY_LIBRETINY not in CORE.data: + raise cv.Invalid("Family component didn't populate core data properly!") + component: LibreTinyComponent = CORE.data[KEY_LIBRETINY][KEY_COMPONENT_DATA] + board = value[CONF_BOARD] + # read board-default family if not specified + if CONF_FAMILY not in value: + if board not in component.boards: + raise cv.Invalid( + "This board is unknown, please set the family manually. " + "Also, make sure the chosen chip component is correct.", + path=[CONF_BOARD], + ) + value = value.copy() + value[CONF_FAMILY] = component.boards[board][KEY_FAMILY] + # read component name matching this family + value[CONF_COMPONENT_ID] = FAMILY_COMPONENT[value[CONF_FAMILY]] + # make sure the chosen component matches the family + if value[CONF_COMPONENT_ID] != component.name: + raise cv.Invalid( + f"The chosen family doesn't belong to '{component.name}' component. The correct component is '{value[CONF_COMPONENT_ID]}'", + path=[CONF_FAMILY], + ) + # warn anyway if the board wasn't found + if board not in component.boards: + _LOGGER.warning( + "This board is unknown. Make sure the chosen chip component is correct.", + ) + return value + + +def _update_core_data(config): + CORE.data[KEY_CORE][KEY_TARGET_PLATFORM] = config[CONF_COMPONENT_ID] + CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK] = "arduino" + CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] = cv.Version.parse( + config[CONF_FRAMEWORK][CONF_VERSION] + ) + CORE.data[KEY_LIBRETINY][KEY_BOARD] = config[CONF_BOARD] + CORE.data[KEY_LIBRETINY][KEY_COMPONENT] = config[CONF_COMPONENT_ID] + CORE.data[KEY_LIBRETINY][KEY_FAMILY] = config[CONF_FAMILY] + return config + + +def get_libretiny_component(core_obj=None): + return (core_obj or CORE).data[KEY_LIBRETINY][KEY_COMPONENT] + + +def get_libretiny_family(core_obj=None): + return (core_obj or CORE).data[KEY_LIBRETINY][KEY_FAMILY] + + +def only_on_family(*, supported=None, unsupported=None): + """Config validator for features only available on some LibreTiny families.""" + if supported is not None and not isinstance(supported, list): + supported = [supported] + if unsupported is not None and not isinstance(unsupported, list): + unsupported = [unsupported] + + def validator_(obj): + family = get_libretiny_family() + if supported is not None and family not in supported: + raise cv.Invalid( + f"This feature is only available on {', '.join(supported)}" + ) + if unsupported is not None and family in unsupported: + raise cv.Invalid( + f"This feature is not available on {', '.join(unsupported)}" + ) + return obj + + return validator_ + + +def get_download_types(storage_json=None): + types = [ + { + "title": "UF2 package (recommended)", + "description": "For flashing via web_server OTA or with ltchiptool (UART)", + "file": "firmware.uf2", + "download": f"{storage_json.name}.uf2", + }, + ] + + build_dir = dirname(storage_json.firmware_bin_path) + outputs = join(build_dir, "firmware.json") + if not isfile(outputs): + return types + with open(outputs, encoding="utf-8") as f: + outputs = json.load(f) + for output in outputs: + if not output["public"]: + continue + suffix = output["filename"].partition(".")[2] + suffix = f"-{suffix}" if "." in suffix else f".{suffix}" + types.append( + { + "title": output["title"], + "description": output["description"], + "file": output["filename"], + "download": storage_json.name + suffix, + } + ) + return types + + +def _notify_old_style(config): + if config: + raise cv.Invalid( + "The LibreTiny component is now split between supported chip families.\n" + "Migrate your config file to include a chip-based configuration, " + "instead of the 'libretiny:' block.\n" + "For example 'bk72xx:' or 'rtl87xx:'." + ) + return config + + +# NOTE: Keep this in mind when updating the recommended version: +# * For all constants below, update platformio.ini (in this repo) +ARDUINO_VERSIONS = { + "dev": (cv.Version(0, 0, 0), "https://github.com/kuba2k2/libretiny.git"), + "latest": (cv.Version(0, 0, 0), None), + "recommended": (cv.Version(1, 3, 0), None), +} + + +def _check_framework_version(value): + value = value.copy() + + if value[CONF_VERSION] in ARDUINO_VERSIONS: + if CONF_SOURCE in value: + raise cv.Invalid( + "Framework version needs to be explicitly specified when custom source is used." + ) + + version, source = ARDUINO_VERSIONS[value[CONF_VERSION]] + else: + version = cv.Version.parse(cv.version_number(value[CONF_VERSION])) + source = value.get(CONF_SOURCE, None) + + value[CONF_VERSION] = str(version) + value[CONF_SOURCE] = source + + return value + + +def _check_debug_order(value): + debug = value[CONF_DEBUG] + if "NONE" in debug and "NONE" in debug[1:]: + raise cv.Invalid( + "'none' has to be specified before other modules, and only once", + path=[CONF_DEBUG], + ) + return value + + +FRAMEWORK_SCHEMA = cv.All( + cv.Schema( + { + cv.Optional(CONF_VERSION, default="recommended"): cv.string_strict, + cv.Optional(CONF_SOURCE): cv.string_strict, + cv.Optional(CONF_LOGLEVEL, default="warn"): ( + cv.one_of(*LT_LOGLEVELS, upper=True) + ), + cv.Optional(CONF_DEBUG, default=[]): cv.ensure_list( + cv.one_of("NONE", *LT_DEBUG_MODULES, upper=True) + ), + cv.Optional(CONF_SDK_SILENT, default="all"): ( + cv.one_of("all", "auto", "none", lower=True) + ), + cv.Optional(CONF_UART_PORT, default=None): cv.one_of(0, 1, 2, int=True), + cv.Optional(CONF_GPIO_RECOVER, default=True): cv.boolean, + cv.Optional(CONF_OPTIONS, default={}): { + cv.string_strict: cv.string, + }, + } + ), + _check_framework_version, + _check_debug_order, +) + +CONFIG_SCHEMA = cv.All(_notify_old_style) + +BASE_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(LTComponent), + cv.Required(CONF_BOARD): cv.string_strict, + cv.Optional(CONF_FAMILY): cv.one_of(*FAMILIES, upper=True), + cv.Optional(CONF_FRAMEWORK, default={}): FRAMEWORK_SCHEMA, + }, +) + +BASE_SCHEMA.add_extra(_detect_variant) +BASE_SCHEMA.add_extra(_update_core_data) + + +# pylint: disable=use-dict-literal +async def component_to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + + # setup board config + cg.add_platformio_option("board", config[CONF_BOARD]) + cg.add_build_flag("-DUSE_LIBRETINY") + cg.add_build_flag(f"-DUSE_{config[CONF_COMPONENT_ID]}") + cg.add_build_flag(f"-DUSE_LIBRETINY_VARIANT_{config[CONF_FAMILY]}") + cg.add_define("ESPHOME_BOARD", config[CONF_BOARD]) + cg.add_define("ESPHOME_VARIANT", FAMILY_FRIENDLY[config[CONF_FAMILY]]) + + # force using arduino framework + cg.add_platformio_option("framework", "arduino") + cg.add_build_flag("-DUSE_ARDUINO") + + # disable library compatibility checks + cg.add_platformio_option("lib_ldf_mode", "off") + # include in every file + cg.add_platformio_option("build_src_flags", "-include Arduino.h") + # dummy version code + cg.add_define("USE_ARDUINO_VERSION_CODE", cg.RawExpression("VERSION_CODE(0, 0, 0)")) + # decrease web server stack size (16k words -> 4k words) + cg.add_build_flag("-DCONFIG_ASYNC_TCP_STACK_SIZE=4096") + + # build framework version + # if platform version is a valid version constraint, prefix the default package + framework = config[CONF_FRAMEWORK] + cv.platformio_version_constraint(framework[CONF_VERSION]) + if str(framework[CONF_VERSION]) != "0.0.0": + cg.add_platformio_option("platform", f"libretiny @ {framework[CONF_VERSION]}") + elif framework[CONF_SOURCE]: + cg.add_platformio_option("platform", framework[CONF_SOURCE]) + else: + cg.add_platformio_option("platform", "libretiny") + + # apply LibreTiny options from framework: block + # setup LT logger to work nicely with ESPHome logger + lt_options = dict( + LT_LOGLEVEL="LT_LEVEL_" + framework[CONF_LOGLEVEL], + LT_LOGGER_CALLER=0, + LT_LOGGER_TASK=0, + LT_LOGGER_COLOR=1, + LT_USE_TIME=1, + ) + # enable/disable per-module debugging + for module in framework[CONF_DEBUG]: + if module == "NONE": + # disable all modules + for module in LT_DEBUG_MODULES: + lt_options[f"LT_DEBUG_{module}"] = 0 + else: + # enable one module + lt_options[f"LT_DEBUG_{module}"] = 1 + # set SDK silencing mode + if framework[CONF_SDK_SILENT] == "all": + lt_options["LT_UART_SILENT_ENABLED"] = 1 + lt_options["LT_UART_SILENT_ALL"] = 1 + elif framework[CONF_SDK_SILENT] == "auto": + lt_options["LT_UART_SILENT_ENABLED"] = 1 + lt_options["LT_UART_SILENT_ALL"] = 0 + else: + lt_options["LT_UART_SILENT_ENABLED"] = 0 + lt_options["LT_UART_SILENT_ALL"] = 0 + # set default UART port + if framework[CONF_UART_PORT] is not None: + lt_options["LT_UART_DEFAULT_PORT"] = framework[CONF_UART_PORT] + # add custom options + lt_options.update(framework[CONF_OPTIONS]) + + # apply ESPHome options from framework: block + cg.add_define("LT_GPIO_RECOVER", int(framework[CONF_GPIO_RECOVER])) + + # build PlatformIO compiler flags + for name, value in sorted(lt_options.items()): + cg.add_build_flag(f"-D{name}={value}") + + # custom output firmware name and version + if CONF_PROJECT in config: + cg.add_platformio_option( + "custom_fw_name", "esphome." + config[CONF_PROJECT][CONF_NAME] + ) + cg.add_platformio_option( + "custom_fw_version", config[CONF_PROJECT][CONF_VERSION] + ) + else: + cg.add_platformio_option("custom_fw_name", "esphome") + cg.add_platformio_option("custom_fw_version", __version__) + + await cg.register_component(var, config) diff --git a/esphome/components/libretiny/const.py b/esphome/components/libretiny/const.py new file mode 100644 index 0000000000..525d8b7786 --- /dev/null +++ b/esphome/components/libretiny/const.py @@ -0,0 +1,90 @@ +from dataclasses import dataclass +from typing import Callable + +import esphome.codegen as cg + + +@dataclass +class LibreTinyComponent: + name: str + boards: dict[str, dict[str, str]] + board_pins: dict[str, dict[str, int]] + pin_validation: Callable[[int], int] + usage_validation: Callable[[dict], dict] + + +CONF_LIBRETINY = "libretiny" +CONF_LOGLEVEL = "loglevel" +CONF_SDK_SILENT = "sdk_silent" +CONF_GPIO_RECOVER = "gpio_recover" +CONF_UART_PORT = "uart_port" + +LT_LOGLEVELS = [ + "VERBOSE", + "TRACE", + "DEBUG", + "INFO", + "WARN", + "ERROR", + "FATAL", + "NONE", +] + +LT_DEBUG_MODULES = [ + "WIFI", + "CLIENT", + "SERVER", + "SSL", + "OTA", + "FDB", + "MDNS", + "LWIP", + "LWIP_ASSERT", +] + +KEY_LIBRETINY = "libretiny" +KEY_BOARD = "board" +KEY_COMPONENT = "component" +KEY_COMPONENT_DATA = "component_data" +KEY_FAMILY = "family" + +# COMPONENTS - auto-generated! Do not modify this block. +COMPONENT_BK72XX = "bk72xx" +COMPONENT_RTL87XX = "rtl87xx" +# COMPONENTS - end + +# FAMILIES - auto-generated! Do not modify this block. +FAMILY_BK7231N = "BK7231N" +FAMILY_BK7231Q = "BK7231Q" +FAMILY_BK7231T = "BK7231T" +FAMILY_BK7251 = "BK7251" +FAMILY_RTL8710B = "RTL8710B" +FAMILY_RTL8720C = "RTL8720C" +FAMILIES = [ + FAMILY_BK7231N, + FAMILY_BK7231Q, + FAMILY_BK7231T, + FAMILY_BK7251, + FAMILY_RTL8710B, + FAMILY_RTL8720C, +] +FAMILY_FRIENDLY = { + FAMILY_BK7231N: "BK7231N", + FAMILY_BK7231Q: "BK7231Q", + FAMILY_BK7231T: "BK7231T", + FAMILY_BK7251: "BK7251", + FAMILY_RTL8710B: "RTL8710B", + FAMILY_RTL8720C: "RTL8720C", +} +FAMILY_COMPONENT = { + FAMILY_BK7231N: COMPONENT_BK72XX, + FAMILY_BK7231Q: COMPONENT_BK72XX, + FAMILY_BK7231T: COMPONENT_BK72XX, + FAMILY_BK7251: COMPONENT_BK72XX, + FAMILY_RTL8710B: COMPONENT_RTL87XX, + FAMILY_RTL8720C: COMPONENT_RTL87XX, +} +# FAMILIES - end + +libretiny_ns = cg.esphome_ns.namespace("libretiny") +LTComponent = libretiny_ns.class_("LTComponent", cg.PollingComponent) diff --git a/esphome/components/libretiny/core.cpp b/esphome/components/libretiny/core.cpp new file mode 100644 index 0000000000..b22740f02a --- /dev/null +++ b/esphome/components/libretiny/core.cpp @@ -0,0 +1,40 @@ +#ifdef USE_LIBRETINY + +#include "core.h" +#include "esphome/core/defines.h" +#include "esphome/core/hal.h" +#include "esphome/core/helpers.h" +#include "preferences.h" + +void setup(); +void loop(); + +namespace esphome { + +void IRAM_ATTR HOT yield() { ::yield(); } +uint32_t IRAM_ATTR HOT millis() { return ::millis(); } +uint32_t IRAM_ATTR HOT micros() { return ::micros(); } +void IRAM_ATTR HOT delay(uint32_t ms) { ::delay(ms); } +void IRAM_ATTR HOT delayMicroseconds(uint32_t us) { ::delayMicroseconds(us); } + +void arch_init() { + libretiny::setup_preferences(); + lt_wdt_enable(10000L); +#if LT_GPIO_RECOVER + lt_gpio_recover(); +#endif +} + +void arch_restart() { + lt_reboot(); + while (1) { + } +} +void IRAM_ATTR HOT arch_feed_wdt() { lt_wdt_feed(); } +uint32_t arch_get_cpu_cycle_count() { return lt_cpu_get_cycle_count(); } +uint32_t arch_get_cpu_freq_hz() { return lt_cpu_get_freq(); } +uint8_t progmem_read_byte(const uint8_t *addr) { return *addr; } + +} // namespace esphome + +#endif // USE_LIBRETINY diff --git a/esphome/components/libretiny/core.h b/esphome/components/libretiny/core.h new file mode 100644 index 0000000000..9458df1f16 --- /dev/null +++ b/esphome/components/libretiny/core.h @@ -0,0 +1,11 @@ +#pragma once + +#ifdef USE_LIBRETINY + +#include + +namespace esphome { +namespace libretiny {} // namespace libretiny +} // namespace esphome + +#endif // USE_LIBRETINY diff --git a/esphome/components/libretiny/generate_components.py b/esphome/components/libretiny/generate_components.py new file mode 100644 index 0000000000..ae55fd9e40 --- /dev/null +++ b/esphome/components/libretiny/generate_components.py @@ -0,0 +1,329 @@ +# Copyright (c) Kuba Szczodrzyński 2023-06-01. + +# pylint: skip-file +# flake8: noqa + +import json +import re +from pathlib import Path + +from black import FileMode, format_str +from ltchiptool import Board, Family +from ltchiptool.util.lvm import LVM + +BASE_CODE_INIT = """ +# This file was auto-generated by libretiny/generate_components.py +# Do not modify its contents. +# For custom pin validators, put validate_pin() or validate_usage() +# in gpio.py file in this directory. +# For changing schema/pin schema, put COMPONENT_SCHEMA or COMPONENT_PIN_SCHEMA +# in schema.py file in this directory. + +from esphome import pins +from esphome.components import libretiny +from esphome.components.libretiny.const import ( + COMPONENT_{COMPONENT}, + KEY_COMPONENT_DATA, + KEY_LIBRETINY, + LibreTinyComponent, +) +from esphome.core import CORE + +{IMPORTS} + +CODEOWNERS = ["@kuba2k2"] +AUTO_LOAD = ["libretiny"] + +COMPONENT_DATA = LibreTinyComponent( + name=COMPONENT_{COMPONENT}, + boards={COMPONENT}_BOARDS, + board_pins={COMPONENT}_BOARD_PINS, + pin_validation={PIN_VALIDATION}, + usage_validation={USAGE_VALIDATION}, +) + + +def _set_core_data(config): + CORE.data[KEY_LIBRETINY] = {} + CORE.data[KEY_LIBRETINY][KEY_COMPONENT_DATA] = COMPONENT_DATA + return config + + +CONFIG_SCHEMA = {SCHEMA} + +PIN_SCHEMA = {PIN_SCHEMA} + +CONFIG_SCHEMA.prepend_extra(_set_core_data) + + +async def to_code(config): + return await libretiny.component_to_code(config) + + +@pins.PIN_SCHEMA_REGISTRY.register("{COMPONENT_LOWER}", PIN_SCHEMA) +async def pin_to_code(config): + return await libretiny.gpio.component_pin_to_code(config) +""" + +BASE_CODE_BOARDS = """ +# This file was auto-generated by libretiny/generate_components.py +# Do not modify its contents. + +from esphome.components.libretiny.const import {FAMILIES} + +{COMPONENT}_BOARDS = {BOARDS_JSON} + +{COMPONENT}_BOARD_PINS = {PINS_JSON} + +BOARDS = {COMPONENT}_BOARDS +""" + +# variable names in component extension code +VAR_SCHEMA = "COMPONENT_SCHEMA" +VAR_PIN_SCHEMA = "COMPONENT_PIN_SCHEMA" +VAR_GPIO_PIN = "validate_pin" +VAR_GPIO_USAGE = "validate_usage" + +# lines for code snippets +SCHEMA_BASE = "libretiny.BASE_SCHEMA" +SCHEMA_EXTRA = f"libretiny.BASE_SCHEMA.extend({VAR_SCHEMA})" +PIN_SCHEMA_BASE = "libretiny.gpio.BASE_PIN_SCHEMA" +PIN_SCHEMA_EXTRA = f"libretiny.BASE_PIN_SCHEMA.extend({VAR_PIN_SCHEMA})" + +# supported root components +COMPONENT_MAP = { + "rtl87xx": "realtek-amb", + "bk72xx": "beken-72xx", +} + + +def subst(code: str, key: str, value: str) -> str: + return code.replace(f"{{{key}}}", value) + + +def subst_all(code: str, value: str) -> str: + return re.sub(r"{.+?}", value, code) + + +def subst_many(code: str, *templates: tuple[str, str]) -> str: + while True: + prev_code = code + for key, value in templates: + code = subst(code, key, value) + if code == prev_code: + break + return code + + +def check_base_code(code: str) -> None: + code = subst_all(code, "DUMMY") + formatted = format_str(code, mode=FileMode()) + if code.strip() != formatted.strip(): + print(formatted) + raise RuntimeError("Base code is not formatted properly") + + +def write_component_code( + component_dir: Path, + component: str, +) -> None: + code = BASE_CODE_INIT + gpio_py = component_dir.joinpath("gpio.py") + schema_py = component_dir.joinpath("schema.py") + init_py = component_dir.joinpath("__init__.py") + + # gather all imports + imports = { + "gpio": set(), + "schema": set(), + "boards": {"{COMPONENT}_BOARDS", "{COMPONENT}_BOARD_PINS"}, + } + # substitution values + values = dict( + COMPONENT=component.upper(), + COMPONENT_LOWER=component.lower(), + SCHEMA=SCHEMA_BASE, + PIN_SCHEMA=PIN_SCHEMA_BASE, + PIN_VALIDATION="None", + USAGE_VALIDATION="None", + ) + + # parse gpio.py file to find custom validators + if gpio_py.is_file(): + gpio_code = gpio_py.read_text() + if VAR_GPIO_PIN in gpio_code: + values["PIN_VALIDATION"] = VAR_GPIO_PIN + imports["gpio"].add(VAR_GPIO_PIN) + + # parse schema.py file to find schema extension + if schema_py.is_file(): + schema_code = schema_py.read_text() + if VAR_SCHEMA in schema_code: + values["SCHEMA"] = SCHEMA_EXTRA + imports["schema"].add(VAR_SCHEMA) + if VAR_PIN_SCHEMA in schema_code: + values["PIN_SCHEMA"] = PIN_SCHEMA_EXTRA + imports["schema"].add(VAR_PIN_SCHEMA) + + # add import lines if needed + import_lines = "\n".join( + f"from .{m} import {', '.join(sorted(v))}" for m, v in imports.items() if v + ) + code = subst_many( + code, + ("IMPORTS", import_lines), + *values.items(), + ) + # format with black + code = format_str(code, mode=FileMode()) + # write back to file + init_py.write_text(code) + + +def write_component_boards( + component_dir: Path, + component: str, + boards: list[Board], +) -> list[Family]: + code = BASE_CODE_BOARDS + variants_dir = Path(LVM.path(), "boards", "variants") + boards_py = component_dir.joinpath("boards.py") + pin_regex = r"#define PIN_(\w+)\s+(\d+)" + pin_number_regex = r"0*(\d+)$" + + # families to import + families = set() + # found root families + root_families = [] + # substitution values + values = dict( + COMPONENT=component.upper(), + ) + # resulting JSON objects + boards_json = {} + pins_json = {} + + # go through all boards found for this root family + for board in boards: + family = "FAMILY_" + board.family.short_name + boards_json[board.name] = { + "name": board.title, + "family": family, + } + families.add(family) + if board.family not in root_families: + root_families.append(board.family) + + board_h = variants_dir.joinpath(f"{board.name}.h") + board_code = board_h.read_text() + board_pins = {} + for match in re.finditer(pin_regex, board_code): + pin_name = match[1] + pin_value = match[2] + board_pins[pin_name] = int(pin_value) + # trim leading zeroes in GPIO numbers + pin_name = re.sub(pin_number_regex, r"\1", pin_name) + board_pins[pin_name] = int(pin_value) + pins_json[board.name] = board_pins + + # make the JSONs format as non-inline + boards_json = json.dumps(boards_json).replace("}", ",}") + pins_json = json.dumps(pins_json).replace("}", ",}") + # remove quotes from family constants + for family in families: + boards_json = boards_json.replace(f'"{family}"', family) + code = subst_many( + code, + ("FAMILIES", ", ".join(sorted(families))), + ("BOARDS_JSON", boards_json), + ("PINS_JSON", pins_json), + *values.items(), + ) + # format with black + code = format_str(code, mode=FileMode()) + # write back to file + boards_py.write_text(code) + return root_families + + +def write_const( + components_dir: Path, + components: set[str], + families: dict[str, str], +) -> None: + const_py = components_dir.joinpath("libretiny").joinpath("const.py") + if not const_py.is_file(): + raise FileNotFoundError(const_py) + code = const_py.read_text() + components = sorted(components) + v2f = families + families = sorted(families) + + # regex for finding the component list block + comp_regex = r"(# COMPONENTS.+?\n)(.*?)(\n# COMPONENTS)" + # build component constants + comp_str = "\n".join(f'COMPONENT_{f} = "{f.lower()}"' for f in components) + # replace the 2nd regex group only + repl = lambda m: m.group(1) + comp_str + m.group(3) + code = re.sub(comp_regex, repl, code, flags=re.DOTALL | re.MULTILINE) + + # regex for finding the family list block + fam_regex = r"(# FAMILIES.+?\n)(.*?)(\n# FAMILIES)" + # build family constants + fam_defs = "\n".join(f'FAMILY_{v} = "{v}"' for v in families) + fam_list = ", ".join(f"FAMILY_{v}" for v in families) + fam_friendly = ", ".join(f'FAMILY_{v}: "{v}"' for v in families) + fam_component = ", ".join(f"FAMILY_{v}: COMPONENT_{v2f[v]}" for v in families) + fam_lines = [ + fam_defs, + "FAMILIES = [", + fam_list, + ",]", + "FAMILY_FRIENDLY = {", + fam_friendly, + ",}", + "FAMILY_COMPONENT = {", + fam_component, + ",}", + ] + var_str = "\n".join(fam_lines) + # replace the 2nd regex group only + repl = lambda m: m.group(1) + var_str + m.group(3) + code = re.sub(fam_regex, repl, code, flags=re.DOTALL | re.MULTILINE) + + # format with black + code = format_str(code, mode=FileMode()) + # write back to file + const_py.write_text(code) + + +if __name__ == "__main__": + # safety check if code is properly formatted + check_base_code(BASE_CODE_INIT) + # list all boards from ltchiptool + components_dir = Path(__file__).parent.parent + boards = [Board(b) for b in Board.get_list()] + # keep track of all supported root- and chip-families + components = set() + families = {} + # loop through supported components + for component, family_name in COMPONENT_MAP.items(): + family = Family.get(name=family_name) + # make family component directory + component_dir = components_dir.joinpath(component) + component_dir.mkdir(exist_ok=True) + # filter boards list + family_boards = [b for b in boards if family in b.family.inheritance] + # write __init__.py + write_component_code(component_dir, component) + # write boards.py + component_families = write_component_boards( + component_dir, component, family_boards + ) + # store current root component name + components.add(component.upper()) + # add all chip families + for family in component_families: + families[family.short_name] = component.upper() + # update libretiny/const.py + write_const(components_dir, components, families) diff --git a/esphome/components/libretiny/gpio.py b/esphome/components/libretiny/gpio.py new file mode 100644 index 0000000000..ba9bfffcc9 --- /dev/null +++ b/esphome/components/libretiny/gpio.py @@ -0,0 +1,216 @@ +import logging + +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome import pins +from esphome.const import ( + CONF_ANALOG, + CONF_ID, + CONF_INPUT, + CONF_INVERTED, + CONF_MODE, + CONF_NUMBER, + CONF_OPEN_DRAIN, + CONF_OUTPUT, + CONF_PULLDOWN, + CONF_PULLUP, +) +from esphome.core import CORE + +from .const import ( + KEY_BOARD, + KEY_COMPONENT_DATA, + KEY_LIBRETINY, + LibreTinyComponent, + libretiny_ns, +) + +_LOGGER = logging.getLogger(__name__) + +ArduinoInternalGPIOPin = libretiny_ns.class_( + "ArduinoInternalGPIOPin", cg.InternalGPIOPin +) + + +def _is_name_deprecated(value): + return value[0] in "DA" and value[1:].isnumeric() + + +def _lookup_board_pins(board): + component: LibreTinyComponent = CORE.data[KEY_LIBRETINY][KEY_COMPONENT_DATA] + board_pins = component.board_pins.get(board, {}) + # Resolve aliased board pins (shorthand when two boards have the same pin configuration) + while isinstance(board_pins, str): + board_pins = board_pins[board_pins] + return board_pins + + +def _lookup_pin(value): + board: str = CORE.data[KEY_LIBRETINY][KEY_BOARD] + board_pins = _lookup_board_pins(board) + + # check numeric pin values + if isinstance(value, int): + if value in board_pins.values() or not board_pins: + # accept if pin number present in board pins + # if board is not found, just accept all numeric values + return value + raise cv.Invalid(f"Pin number '{value}' is not usable for board {board}.") + + # check textual pin names + if isinstance(value, str): + if not board_pins: + # can't remap without known pin name + raise cv.Invalid( + f"Board {board} wasn't found. " + f"Use 'GPIO#' (numeric value) instead of '{value}'." + ) + + if value in board_pins: + # pin name found, remap to numeric value + if _is_name_deprecated(value): + number = board_pins[value] + # find all alternative pin names (except the deprecated) + names = ( + k + for k, v in board_pins.items() + if v == number and not _is_name_deprecated(k) + ) + # sort by shortest + # favor P# or PA# names + names = sorted( + names, + key=lambda x: len(x) - 99 if x[0] == "P" else len(x), + ) + _LOGGER.warning( + "Using D# and A# pin numbering is deprecated. " + "Please replace '%s' with one of: %s", + value, + ", ".join(names), + ) + return board_pins[value] + + # pin name not found and not numeric + raise cv.Invalid(f"Cannot resolve pin name '{value}' for board {board}.") + + # unknown type of the value + raise cv.Invalid(f"Unrecognized pin value '{value}'.") + + +def _translate_pin(value): + if isinstance(value, dict) or value is None: + raise cv.Invalid( + "This variable only supports pin numbers, not full pin schemas " + "(with inverted and mode)." + ) + if isinstance(value, int): + return value + try: + return int(value) + except ValueError: + pass + # translate GPIO* and P* to a number, if possible + # otherwise return unchanged value (i.e. pin PA05) + try: + if value.startswith("GPIO"): + value = int(value[4:]) + elif value.startswith("P"): + value = int(value[1:]) + except ValueError: + pass + return value + + +def validate_gpio_pin(value): + value = _translate_pin(value) + value = _lookup_pin(value) + + component: LibreTinyComponent = CORE.data[KEY_LIBRETINY][KEY_COMPONENT_DATA] + if component.pin_validation: + value = component.pin_validation(value) + + return value + + +def validate_gpio_usage(value): + mode = value[CONF_MODE] + is_analog = mode[CONF_ANALOG] + is_input = mode[CONF_INPUT] + is_output = mode[CONF_OUTPUT] + is_open_drain = mode[CONF_OPEN_DRAIN] + is_pullup = mode[CONF_PULLUP] + is_pulldown = mode[CONF_PULLDOWN] + + if is_open_drain and not is_output: + raise cv.Invalid( + "Open-drain only works with output mode", [CONF_MODE, CONF_OPEN_DRAIN] + ) + if is_analog and not is_input: + raise cv.Invalid("Analog pins must be an input", [CONF_MODE, CONF_ANALOG]) + if is_analog: + # expect analog pin numbers to be available as either ADC# or A# + number = value[CONF_NUMBER] + board: str = CORE.data[KEY_LIBRETINY][KEY_BOARD] + board_pins = _lookup_board_pins(board) + analog_pins = [ + v + for k, v in board_pins.items() + if k[0] == "A" and k[1:].isnumeric() or k[0:3] == "ADC" + ] + if number not in analog_pins: + raise cv.Invalid( + f"Pin '{number}' is not an analog pin", [CONF_MODE, CONF_ANALOG] + ) + + # (input, output, open_drain, pullup, pulldown) + supported_modes = { + # INPUT + (True, False, False, False, False), + # OUTPUT + (False, True, False, False, False), + # INPUT_PULLUP + (True, False, False, True, False), + # INPUT_PULLDOWN + (True, False, False, False, True), + # OUTPUT_OPEN_DRAIN + (False, True, True, False, False), + } + key = (is_input, is_output, is_open_drain, is_pullup, is_pulldown) + if key not in supported_modes: + raise cv.Invalid("This pin mode is not supported", [CONF_MODE]) + + component: LibreTinyComponent = CORE.data[KEY_LIBRETINY][KEY_COMPONENT_DATA] + if component.usage_validation: + value = component.usage_validation(value) + + return value + + +BASE_PIN_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(ArduinoInternalGPIOPin), + cv.Required(CONF_NUMBER): validate_gpio_pin, + cv.Optional(CONF_MODE, default={}): cv.Schema( + { + cv.Optional(CONF_ANALOG, default=False): cv.boolean, + cv.Optional(CONF_INPUT, default=False): cv.boolean, + cv.Optional(CONF_OUTPUT, default=False): cv.boolean, + cv.Optional(CONF_OPEN_DRAIN, default=False): cv.boolean, + cv.Optional(CONF_PULLUP, default=False): cv.boolean, + cv.Optional(CONF_PULLDOWN, default=False): cv.boolean, + } + ), + cv.Optional(CONF_INVERTED, default=False): cv.boolean, + }, +) + +BASE_PIN_SCHEMA.add_extra(validate_gpio_usage) + + +async def component_pin_to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + num = config[CONF_NUMBER] + cg.add(var.set_pin(num)) + cg.add(var.set_inverted(config[CONF_INVERTED])) + cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE]))) + return var diff --git a/esphome/components/libretiny/gpio_arduino.cpp b/esphome/components/libretiny/gpio_arduino.cpp new file mode 100644 index 0000000000..7a1e014ea4 --- /dev/null +++ b/esphome/components/libretiny/gpio_arduino.cpp @@ -0,0 +1,105 @@ +#ifdef USE_LIBRETINY + +#include "gpio_arduino.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace libretiny { + +static const char *const TAG = "lt.gpio"; + +static int IRAM_ATTR flags_to_mode(gpio::Flags flags) { + if (flags == gpio::FLAG_INPUT) { + return INPUT; + } else if (flags == gpio::FLAG_OUTPUT) { + return OUTPUT; + } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLUP)) { + return INPUT_PULLUP; + } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLDOWN)) { + return INPUT_PULLDOWN; + } else if (flags == (gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN)) { + return OUTPUT_OPEN_DRAIN; + } else { + return 0; + } +} + +struct ISRPinArg { + uint8_t pin; + bool inverted; +}; + +ISRInternalGPIOPin ArduinoInternalGPIOPin::to_isr() const { + auto *arg = new ISRPinArg{}; // NOLINT(cppcoreguidelines-owning-memory) + arg->pin = pin_; + arg->inverted = inverted_; + return ISRInternalGPIOPin((void *) arg); +} + +void ArduinoInternalGPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const { + PinStatus arduino_mode = (PinStatus) 255; + switch (type) { + case gpio::INTERRUPT_RISING_EDGE: + arduino_mode = inverted_ ? FALLING : RISING; + break; + case gpio::INTERRUPT_FALLING_EDGE: + arduino_mode = inverted_ ? RISING : FALLING; + break; + case gpio::INTERRUPT_ANY_EDGE: + arduino_mode = CHANGE; + break; + case gpio::INTERRUPT_LOW_LEVEL: + arduino_mode = inverted_ ? HIGH : LOW; + break; + case gpio::INTERRUPT_HIGH_LEVEL: + arduino_mode = inverted_ ? LOW : HIGH; + break; + } + + attachInterruptParam(pin_, func, arduino_mode, arg); +} + +void ArduinoInternalGPIOPin::pin_mode(gpio::Flags flags) { + pinMode(pin_, flags_to_mode(flags)); // NOLINT +} + +std::string ArduinoInternalGPIOPin::dump_summary() const { + char buffer[32]; + snprintf(buffer, sizeof(buffer), "%u", pin_); + return buffer; +} + +bool ArduinoInternalGPIOPin::digital_read() { + return bool(digitalRead(pin_)) ^ inverted_; // NOLINT +} +void ArduinoInternalGPIOPin::digital_write(bool value) { + digitalWrite(pin_, value ^ inverted_); // NOLINT +} +void ArduinoInternalGPIOPin::detach_interrupt() const { + detachInterrupt(pin_); // NOLINT +} + +} // namespace libretiny + +using namespace libretiny; + +bool IRAM_ATTR ISRInternalGPIOPin::digital_read() { + auto *arg = reinterpret_cast(arg_); + return bool(digitalRead(arg->pin)) ^ arg->inverted; // NOLINT +} +void IRAM_ATTR ISRInternalGPIOPin::digital_write(bool value) { + auto *arg = reinterpret_cast(arg_); + digitalWrite(arg->pin, value ^ arg->inverted); // NOLINT +} +void IRAM_ATTR ISRInternalGPIOPin::clear_interrupt() { + auto *arg = reinterpret_cast(arg_); + detachInterrupt(arg->pin); +} +void IRAM_ATTR ISRInternalGPIOPin::pin_mode(gpio::Flags flags) { + auto *arg = reinterpret_cast(arg_); + pinMode(arg->pin, flags_to_mode(flags)); // NOLINT +} + +} // namespace esphome + +#endif // USE_LIBRETINY diff --git a/esphome/components/libretiny/gpio_arduino.h b/esphome/components/libretiny/gpio_arduino.h new file mode 100644 index 0000000000..a43ed28c5e --- /dev/null +++ b/esphome/components/libretiny/gpio_arduino.h @@ -0,0 +1,36 @@ +#pragma once + +#ifdef USE_LIBRETINY +#include "esphome/core/hal.h" + +namespace esphome { +namespace libretiny { + +class ArduinoInternalGPIOPin : public InternalGPIOPin { + public: + void set_pin(uint8_t pin) { pin_ = pin; } + void set_inverted(bool inverted) { inverted_ = inverted; } + void set_flags(gpio::Flags flags) { flags_ = flags; } + + void setup() override { pin_mode(flags_); } + void pin_mode(gpio::Flags flags) override; + bool digital_read() override; + void digital_write(bool value) override; + std::string dump_summary() const override; + void detach_interrupt() const override; + ISRInternalGPIOPin to_isr() const override; + uint8_t get_pin() const override { return pin_; } + bool is_inverted() const override { return inverted_; } + + protected: + void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const override; + + uint8_t pin_; + bool inverted_; + gpio::Flags flags_; +}; + +} // namespace libretiny +} // namespace esphome + +#endif // USE_LIBRETINY diff --git a/esphome/components/libretiny/lt_component.cpp b/esphome/components/libretiny/lt_component.cpp new file mode 100644 index 0000000000..ec4b60eaeb --- /dev/null +++ b/esphome/components/libretiny/lt_component.cpp @@ -0,0 +1,29 @@ +#include "lt_component.h" + +#ifdef USE_LIBRETINY + +#include "esphome/core/log.h" + +namespace esphome { +namespace libretiny { + +static const char *const TAG = "lt.component"; + +void LTComponent::dump_config() { + ESP_LOGCONFIG(TAG, "LibreTiny:"); + ESP_LOGCONFIG(TAG, " Version: %s", LT_BANNER_STR + 10); + ESP_LOGCONFIG(TAG, " Loglevel: %u", LT_LOGLEVEL); + +#ifdef USE_TEXT_SENSOR + if (this->version_ != nullptr) { + this->version_->publish_state(LT_BANNER_STR + 10); + } +#endif // USE_TEXT_SENSOR +} + +float LTComponent::get_setup_priority() const { return setup_priority::LATE; } + +} // namespace libretiny +} // namespace esphome + +#endif // USE_LIBRETINY diff --git a/esphome/components/libretiny/lt_component.h b/esphome/components/libretiny/lt_component.h new file mode 100644 index 0000000000..3d4483ab5d --- /dev/null +++ b/esphome/components/libretiny/lt_component.h @@ -0,0 +1,36 @@ +#pragma once + +#ifdef USE_LIBRETINY + +#include "esphome/core/component.h" +#include "esphome/core/defines.h" + +#ifdef USE_SENSOR +#include "esphome/components/sensor/sensor.h" +#endif +#ifdef USE_TEXT_SENSOR +#include "esphome/components/text_sensor/text_sensor.h" +#endif + +namespace esphome { +namespace libretiny { + +class LTComponent : public Component { + public: + float get_setup_priority() const override; + void dump_config() override; + +#ifdef USE_TEXT_SENSOR + void set_version_sensor(text_sensor::TextSensor *version) { version_ = version; } +#endif // USE_TEXT_SENSOR + + protected: +#ifdef USE_TEXT_SENSOR + text_sensor::TextSensor *version_{nullptr}; +#endif // USE_TEXT_SENSOR +}; + +} // namespace libretiny +} // namespace esphome + +#endif // USE_LIBRETINY diff --git a/esphome/components/libretiny/preferences.cpp b/esphome/components/libretiny/preferences.cpp new file mode 100644 index 0000000000..ceeb30baf5 --- /dev/null +++ b/esphome/components/libretiny/preferences.cpp @@ -0,0 +1,182 @@ +#ifdef USE_LIBRETINY + +#include "esphome/core/preferences.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" +#include +#include +#include +#include + +namespace esphome { +namespace libretiny { + +static const char *const TAG = "lt.preferences"; + +struct NVSData { + std::string key; + std::vector data; +}; + +static std::vector s_pending_save; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + +class LibreTinyPreferenceBackend : public ESPPreferenceBackend { + public: + std::string key; + fdb_kvdb_t db; + fdb_blob_t blob; + + bool save(const uint8_t *data, size_t len) override { + // try find in pending saves and update that + for (auto &obj : s_pending_save) { + if (obj.key == key) { + obj.data.assign(data, data + len); + return true; + } + } + NVSData save{}; + save.key = key; + save.data.assign(data, data + len); + s_pending_save.emplace_back(save); + ESP_LOGVV(TAG, "s_pending_save: key: %s, len: %d", key.c_str(), len); + return true; + } + + bool load(uint8_t *data, size_t len) override { + // try find in pending saves and load from that + for (auto &obj : s_pending_save) { + if (obj.key == key) { + if (obj.data.size() != len) { + // size mismatch + return false; + } + memcpy(data, obj.data.data(), len); + return true; + } + } + + fdb_blob_make(blob, data, len); + size_t actual_len = fdb_kv_get_blob(db, key.c_str(), blob); + if (actual_len != len) { + ESP_LOGVV(TAG, "NVS length does not match (%u!=%u)", actual_len, len); + return false; + } else { + ESP_LOGVV(TAG, "fdb_kv_get_blob: key: %s, len: %d", key.c_str(), len); + } + return true; + } +}; + +class LibreTinyPreferences : public ESPPreferences { + public: + struct fdb_kvdb db; + struct fdb_blob blob; + + void open() { + // + fdb_err_t err = fdb_kvdb_init(&db, "esphome", "kvs", NULL, NULL); + if (err != FDB_NO_ERR) { + LT_E("fdb_kvdb_init(...) failed: %d", err); + } else { + LT_I("Preferences initialized"); + } + } + + ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) override { + return make_preference(length, type); + } + + ESPPreferenceObject make_preference(size_t length, uint32_t type) override { + auto *pref = new LibreTinyPreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory) + pref->db = &db; + pref->blob = &blob; + + uint32_t keyval = type; + pref->key = str_sprintf("%u", keyval); + + return ESPPreferenceObject(pref); + } + + bool sync() override { + if (s_pending_save.empty()) + return true; + + ESP_LOGD(TAG, "Saving %d preferences to flash...", s_pending_save.size()); + // goal try write all pending saves even if one fails + int cached = 0, written = 0, failed = 0; + fdb_err_t last_err = FDB_NO_ERR; + std::string last_key{}; + + // go through vector from back to front (makes erase easier/more efficient) + for (ssize_t i = s_pending_save.size() - 1; i >= 0; i--) { + const auto &save = s_pending_save[i]; + ESP_LOGVV(TAG, "Checking if FDB data %s has changed", save.key.c_str()); + if (is_changed(&db, save)) { + ESP_LOGV(TAG, "sync: key: %s, len: %d", save.key.c_str(), save.data.size()); + fdb_blob_make(&blob, save.data.data(), save.data.size()); + fdb_err_t err = fdb_kv_set_blob(&db, save.key.c_str(), &blob); + if (err != FDB_NO_ERR) { + ESP_LOGV(TAG, "fdb_kv_set_blob('%s', len=%u) failed: %d", save.key.c_str(), save.data.size(), err); + failed++; + last_err = err; + last_key = save.key; + continue; + } + written++; + } else { + ESP_LOGD(TAG, "FDB data not changed; skipping %s len=%u", save.key.c_str(), save.data.size()); + cached++; + } + s_pending_save.erase(s_pending_save.begin() + i); + } + ESP_LOGD(TAG, "Saving %d preferences to flash: %d cached, %d written, %d failed", cached + written + failed, cached, + written, failed); + if (failed > 0) { + ESP_LOGE(TAG, "Error saving %d preferences to flash. Last error=%d for key=%s", failed, last_err, + last_key.c_str()); + } + + return failed == 0; + } + + bool is_changed(const fdb_kvdb_t db, const NVSData &to_save) { + NVSData stored_data{}; + struct fdb_kv kv; + fdb_kv_t kvp = fdb_kv_get_obj(db, to_save.key.c_str(), &kv); + if (kvp == nullptr) { + ESP_LOGV(TAG, "fdb_kv_get_obj('%s'): nullptr - the key might not be set yet", to_save.key.c_str()); + return true; + } + stored_data.data.reserve(kv.value_len); + fdb_blob_make(&blob, stored_data.data.data(), kv.value_len); + size_t actual_len = fdb_kv_get_blob(db, to_save.key.c_str(), &blob); + if (actual_len != kv.value_len) { + ESP_LOGV(TAG, "fdb_kv_get_blob('%s') len mismatch: %u != %u", to_save.key.c_str(), actual_len, kv.value_len); + return true; + } + return to_save.data != stored_data.data; + } + + bool reset() override { + ESP_LOGD(TAG, "Cleaning up preferences in flash..."); + s_pending_save.clear(); + + fdb_kv_set_default(&db); + fdb_kvdb_deinit(&db); + return true; + } +}; + +void setup_preferences() { + auto *prefs = new LibreTinyPreferences(); // NOLINT(cppcoreguidelines-owning-memory) + prefs->open(); + global_preferences = prefs; +} + +} // namespace libretiny + +ESPPreferences *global_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + +} // namespace esphome + +#endif // USE_LIBRETINY diff --git a/esphome/components/libretiny/preferences.h b/esphome/components/libretiny/preferences.h new file mode 100644 index 0000000000..8ec3cd31b1 --- /dev/null +++ b/esphome/components/libretiny/preferences.h @@ -0,0 +1,13 @@ +#pragma once + +#ifdef USE_LIBRETINY + +namespace esphome { +namespace libretiny { + +void setup_preferences(); + +} // namespace libretiny +} // namespace esphome + +#endif // USE_LIBRETINY diff --git a/esphome/components/libretiny/text_sensor.py b/esphome/components/libretiny/text_sensor.py new file mode 100644 index 0000000000..df10ee7229 --- /dev/null +++ b/esphome/components/libretiny/text_sensor.py @@ -0,0 +1,31 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import text_sensor +from esphome.const import ( + CONF_VERSION, + ENTITY_CATEGORY_DIAGNOSTIC, + ICON_CELLPHONE_ARROW_DOWN, +) + +from .const import CONF_LIBRETINY, LTComponent + +DEPENDENCIES = ["libretiny"] + + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_LIBRETINY): cv.use_id(LTComponent), + cv.Optional(CONF_VERSION): text_sensor.text_sensor_schema( + icon=ICON_CELLPHONE_ARROW_DOWN, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + } +) + + +async def to_code(config): + lt_component = await cg.get_variable(config[CONF_LIBRETINY]) + + if CONF_VERSION in config: + sens = await text_sensor.new_text_sensor(config[CONF_VERSION]) + cg.add(lt_component.set_version_sensor(sens)) diff --git a/esphome/components/libretiny_pwm/__init__.py b/esphome/components/libretiny_pwm/__init__.py new file mode 100644 index 0000000000..4db724f8ad --- /dev/null +++ b/esphome/components/libretiny_pwm/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@kuba2k2"] diff --git a/esphome/components/libretiny_pwm/libretiny_pwm.cpp b/esphome/components/libretiny_pwm/libretiny_pwm.cpp new file mode 100644 index 0000000000..92e4097c0e --- /dev/null +++ b/esphome/components/libretiny_pwm/libretiny_pwm.cpp @@ -0,0 +1,53 @@ +#include "libretiny_pwm.h" +#include "esphome/core/log.h" + +#ifdef USE_LIBRETINY + +namespace esphome { +namespace libretiny_pwm { + +static const char *const TAG = "libretiny.pwm"; + +void LibreTinyPWM::write_state(float state) { + if (!this->initialized_) { + ESP_LOGW(TAG, "LibreTinyPWM output hasn't been initialized yet!"); + return; + } + + if (this->pin_->is_inverted()) + state = 1.0f - state; + + this->duty_ = state; + const uint32_t max_duty = (uint32_t(1) << this->bit_depth_) - 1; + const float duty_rounded = roundf(state * max_duty); + auto duty = static_cast(duty_rounded); + + analogWrite(this->pin_->get_pin(), duty); // NOLINT +} + +void LibreTinyPWM::setup() { + this->update_frequency(this->frequency_); + this->turn_off(); +} + +void LibreTinyPWM::dump_config() { + ESP_LOGCONFIG(TAG, "PWM Output:"); + LOG_PIN(" Pin ", this->pin_); + ESP_LOGCONFIG(TAG, " Frequency: %.1f Hz", this->frequency_); +} + +void LibreTinyPWM::update_frequency(float frequency) { + this->frequency_ = frequency; + // force changing the frequency by removing PWM mode + this->pin_->pin_mode(gpio::FLAG_INPUT); + analogWriteResolution(this->bit_depth_); // NOLINT + analogWriteFrequency(frequency); // NOLINT + this->initialized_ = true; + // re-apply duty + this->write_state(this->duty_); +} + +} // namespace libretiny_pwm +} // namespace esphome + +#endif diff --git a/esphome/components/libretiny_pwm/libretiny_pwm.h b/esphome/components/libretiny_pwm/libretiny_pwm.h new file mode 100644 index 0000000000..42ce40ca39 --- /dev/null +++ b/esphome/components/libretiny_pwm/libretiny_pwm.h @@ -0,0 +1,55 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/hal.h" +#include "esphome/core/automation.h" +#include "esphome/components/output/float_output.h" + +#ifdef USE_LIBRETINY + +namespace esphome { +namespace libretiny_pwm { + +class LibreTinyPWM : public output::FloatOutput, public Component { + public: + explicit LibreTinyPWM(InternalGPIOPin *pin) : pin_(pin) {} + + void set_frequency(float frequency) { this->frequency_ = frequency; } + /// Dynamically change frequency at runtime + void update_frequency(float frequency) override; + + /// Setup LibreTinyPWM. + void setup() override; + void dump_config() override; + /// HARDWARE setup priority + float get_setup_priority() const override { return setup_priority::HARDWARE; } + + /// Override FloatOutput's write_state. + void write_state(float state) override; + + protected: + InternalGPIOPin *pin_; + uint8_t bit_depth_{10}; + float frequency_{}; + float duty_{0.0f}; + bool initialized_ = false; +}; + +template class SetFrequencyAction : public Action { + public: + SetFrequencyAction(LibreTinyPWM *parent) : parent_(parent) {} + TEMPLATABLE_VALUE(float, frequency); + + void play(Ts... x) { + float freq = this->frequency_.value(x...); + this->parent_->update_frequency(freq); + } + + protected: + LibreTinyPWM *parent_; +}; + +} // namespace libretiny_pwm +} // namespace esphome + +#endif diff --git a/esphome/components/libretiny_pwm/output.py b/esphome/components/libretiny_pwm/output.py new file mode 100644 index 0000000000..e74bc8f129 --- /dev/null +++ b/esphome/components/libretiny_pwm/output.py @@ -0,0 +1,49 @@ +from esphome import pins, automation +from esphome.components import output +import esphome.config_validation as cv +import esphome.codegen as cg +from esphome.const import ( + CONF_FREQUENCY, + CONF_ID, + CONF_PIN, +) + +DEPENDENCIES = ["libretiny"] + +libretinypwm_ns = cg.esphome_ns.namespace("libretiny_pwm") +LibreTinyPWM = libretinypwm_ns.class_("LibreTinyPWM", output.FloatOutput, cg.Component) +SetFrequencyAction = libretinypwm_ns.class_("SetFrequencyAction", automation.Action) + +CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend( + { + cv.Required(CONF_ID): cv.declare_id(LibreTinyPWM), + cv.Required(CONF_PIN): pins.internal_gpio_output_pin_schema, + cv.Optional(CONF_FREQUENCY, default="1kHz"): cv.frequency, + } +).extend(cv.COMPONENT_SCHEMA) + + +async def to_code(config): + gpio = await cg.gpio_pin_expression(config[CONF_PIN]) + var = cg.new_Pvariable(config[CONF_ID], gpio) + await cg.register_component(var, config) + await output.register_output(var, config) + cg.add(var.set_frequency(config[CONF_FREQUENCY])) + + +@automation.register_action( + "output.libretiny_pwm.set_frequency", + SetFrequencyAction, + cv.Schema( + { + cv.Required(CONF_ID): cv.use_id(LibreTinyPWM), + cv.Required(CONF_FREQUENCY): cv.templatable(cv.int_), + } + ), +) +async def libretiny_pwm_set_frequency_to_code(config, action_id, template_arg, args): + paren = await cg.get_variable(config[CONF_ID]) + var = cg.new_Pvariable(action_id, template_arg, paren) + template_ = await cg.templatable(config[CONF_FREQUENCY], args, float) + cg.add(var.set_frequency(template_)) + return var diff --git a/esphome/components/logger/__init__.py b/esphome/components/logger/__init__.py index 5c87bb9d91..5225a227ec 100644 --- a/esphome/components/logger/__init__.py +++ b/esphome/components/logger/__init__.py @@ -17,6 +17,8 @@ from esphome.const import ( CONF_TAG, CONF_TRIGGER_ID, CONF_TX_BUFFER_SIZE, + PLATFORM_BK72XX, + PLATFORM_RTL87XX, PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_RP2040, @@ -31,6 +33,11 @@ from esphome.components.esp32.const import ( VARIANT_ESP32C2, VARIANT_ESP32C6, ) +from esphome.components.libretiny import get_libretiny_component, get_libretiny_family +from esphome.components.libretiny.const import ( + COMPONENT_BK72XX, + COMPONENT_RTL87XX, +) CODEOWNERS = ["@esphome/core"] logger_ns = cg.esphome_ns.namespace("logger") @@ -70,6 +77,7 @@ UART2 = "UART2" UART0_SWAP = "UART0_SWAP" USB_SERIAL_JTAG = "USB_SERIAL_JTAG" USB_CDC = "USB_CDC" +DEFAULT = "DEFAULT" UART_SELECTION_ESP32 = { VARIANT_ESP32: [UART0, UART1, UART2], @@ -82,6 +90,11 @@ UART_SELECTION_ESP32 = { UART_SELECTION_ESP8266 = [UART0, UART0_SWAP, UART1] +UART_SELECTION_LIBRETINY = { + COMPONENT_BK72XX: [DEFAULT, UART1, UART2], + COMPONENT_RTL87XX: [DEFAULT, UART0, UART1, UART2], +} + ESP_IDF_UARTS = [USB_CDC, USB_SERIAL_JTAG] UART_SELECTION_RP2040 = [USB_CDC, UART0, UART1] @@ -93,6 +106,7 @@ HARDWARE_UART_TO_UART_SELECTION = { UART2: logger_ns.UART_SELECTION_UART2, USB_CDC: logger_ns.UART_SELECTION_USB_CDC, USB_SERIAL_JTAG: logger_ns.UART_SELECTION_USB_SERIAL_JTAG, + DEFAULT: logger_ns.UART_SELECTION_DEFAULT, } HARDWARE_UART_TO_SERIAL = { @@ -100,6 +114,7 @@ HARDWARE_UART_TO_SERIAL = { UART0_SWAP: cg.global_ns.Serial, UART1: cg.global_ns.Serial1, UART2: cg.global_ns.Serial2, + DEFAULT: cg.global_ns.Serial, } is_log_level = cv.one_of(*LOG_LEVELS, upper=True) @@ -116,6 +131,13 @@ def uart_selection(value): return cv.one_of(*UART_SELECTION_ESP8266, upper=True)(value) if CORE.is_rp2040: return cv.one_of(*UART_SELECTION_RP2040, upper=True)(value) + if CORE.is_libretiny: + family = get_libretiny_family() + if family in UART_SELECTION_LIBRETINY: + return cv.one_of(*UART_SELECTION_LIBRETINY[family], upper=True)(value) + component = get_libretiny_component() + if component in UART_SELECTION_LIBRETINY: + return cv.one_of(*UART_SELECTION_LIBRETINY[component], upper=True)(value) raise NotImplementedError @@ -148,8 +170,18 @@ CONFIG_SCHEMA = cv.All( esp8266=UART0, esp32=UART0, rp2040=USB_CDC, + bk72xx=DEFAULT, + rtl87xx=DEFAULT, ): cv.All( - cv.only_on([PLATFORM_ESP8266, PLATFORM_ESP32, PLATFORM_RP2040]), + cv.only_on( + [ + PLATFORM_ESP8266, + PLATFORM_ESP32, + PLATFORM_RP2040, + PLATFORM_BK72XX, + PLATFORM_RTL87XX, + ] + ), uart_selection, ), cv.Optional(CONF_LEVEL, default="DEBUG"): is_log_level, diff --git a/esphome/components/logger/logger.cpp b/esphome/components/logger/logger.cpp index 758e9c1f98..df4662024f 100644 --- a/esphome/components/logger/logger.cpp +++ b/esphome/components/logger/logger.cpp @@ -158,6 +158,7 @@ Logger::Logger(uint32_t baud_rate, size_t tx_buffer_size) : baud_rate_(baud_rate this->tx_buffer_ = new char[this->tx_buffer_size_ + 1]; // NOLINT } +#ifndef USE_LIBRETINY void Logger::pre_setup() { if (this->baud_rate_ > 0) { #ifdef USE_ARDUINO @@ -266,12 +267,58 @@ void Logger::pre_setup() { ESP_LOGI(TAG, "Log initialized"); } +#else // USE_LIBRETINY +void Logger::pre_setup() { + if (this->baud_rate_ > 0) { + switch (this->uart_) { +#if LT_HW_UART0 + case UART_SELECTION_UART0: + this->hw_serial_ = &Serial0; + Serial0.begin(this->baud_rate_); + break; +#endif +#if LT_HW_UART1 + case UART_SELECTION_UART1: + this->hw_serial_ = &Serial1; + Serial1.begin(this->baud_rate_); + break; +#endif +#if LT_HW_UART2 + case UART_SELECTION_UART2: + this->hw_serial_ = &Serial2; + Serial2.begin(this->baud_rate_); + break; +#endif + default: + this->hw_serial_ = &Serial; + Serial.begin(this->baud_rate_); + if (this->uart_ != UART_SELECTION_DEFAULT) { + ESP_LOGW(TAG, " The chosen logger UART port is not available on this board." + "The default port was used instead."); + } + break; + } + + // change lt_log() port to match default Serial + if (this->uart_ == UART_SELECTION_DEFAULT) { + this->uart_ = (UARTSelection) (LT_UART_DEFAULT_SERIAL + 1); + lt_log_set_port(LT_UART_DEFAULT_SERIAL); + } else { + lt_log_set_port(this->uart_ - 1); + } + } + + global_logger = this; + ESP_LOGI(TAG, "Log initialized"); +} +#endif // USE_LIBRETINY + void Logger::set_baud_rate(uint32_t baud_rate) { this->baud_rate_ = baud_rate; } void Logger::set_log_level(const std::string &tag, int log_level) { this->log_levels_.push_back(LogLevelOverride{tag, log_level}); } -#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) +#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) UARTSelection Logger::get_uart() const { return this->uart_; } #endif @@ -299,15 +346,18 @@ const char *const UART_SELECTIONS[] = { #endif // USE_ESP32 #ifdef USE_ESP8266 const char *const UART_SELECTIONS[] = {"UART0", "UART1", "UART0_SWAP"}; -#endif +#endif // USE_ESP8266 #ifdef USE_RP2040 const char *const UART_SELECTIONS[] = {"UART0", "UART1", "USB_CDC"}; -#endif // USE_ESP8266 +#endif // USE_RP2040 +#ifdef USE_LIBRETINY +const char *const UART_SELECTIONS[] = {"DEFAULT", "UART0", "UART1", "UART2"}; +#endif // USE_LIBRETINY void Logger::dump_config() { ESP_LOGCONFIG(TAG, "Logger:"); ESP_LOGCONFIG(TAG, " Level: %s", LOG_LEVELS[ESPHOME_LOG_LEVEL]); ESP_LOGCONFIG(TAG, " Log Baud Rate: %" PRIu32, this->baud_rate_); -#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) +#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) ESP_LOGCONFIG(TAG, " Hardware UART: %s", UART_SELECTIONS[this->uart_]); #endif diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index 47cde45c29..4a7a43c7c2 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -25,12 +25,18 @@ namespace esphome { namespace logger { -#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) +#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) /** Enum for logging UART selection * * Advanced configuration (pin selection, etc) is not supported. */ enum UARTSelection { +#ifdef USE_LIBRETINY + UART_SELECTION_DEFAULT = 0, + UART_SELECTION_UART0, + UART_SELECTION_UART1, + UART_SELECTION_UART2, +#else UART_SELECTION_UART0 = 0, UART_SELECTION_UART1, #if defined(USE_ESP32) @@ -53,8 +59,9 @@ enum UARTSelection { #ifdef USE_RP2040 UART_SELECTION_USB_CDC, #endif // USE_RP2040 +#endif // USE_LIBRETINY }; -#endif // USE_ESP32 || USE_ESP8266 +#endif // USE_ESP32 || USE_ESP8266 || USE_RP2040 || USE_LIBRETINY class Logger : public Component { public: @@ -69,7 +76,7 @@ class Logger : public Component { #ifdef USE_ESP_IDF uart_port_t get_uart_num() const { return uart_num_; } #endif -#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) +#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) void set_uart_selection(UARTSelection uart_selection) { uart_ = uart_selection; } /// Get the UART used by the logger. UARTSelection get_uart() const; @@ -146,6 +153,9 @@ class Logger : public Component { #if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) UARTSelection uart_{UART_SELECTION_UART0}; #endif +#ifdef USE_LIBRETINY + UARTSelection uart_{UART_SELECTION_DEFAULT}; +#endif #ifdef USE_ARDUINO Stream *hw_serial_{nullptr}; #endif diff --git a/esphome/components/md5/md5.h b/esphome/components/md5/md5.h index 738a312267..4ec8a8a12c 100644 --- a/esphome/components/md5/md5.h +++ b/esphome/components/md5/md5.h @@ -22,6 +22,11 @@ #define MD5_CTX_TYPE br_md5_context #endif +#if defined(USE_LIBRETINY) +#include +#define MD5_CTX_TYPE LT_MD5_CTX_T +#endif + namespace esphome { namespace md5 { diff --git a/esphome/components/mdns/mdns_component.cpp b/esphome/components/mdns/mdns_component.cpp index 581758cf2d..e2e562670b 100644 --- a/esphome/components/mdns/mdns_component.cpp +++ b/esphome/components/mdns/mdns_component.cpp @@ -44,6 +44,9 @@ void MDNSComponent::compile_records_() { #endif #ifdef USE_RP2040 platform = "RP2040"; +#endif +#ifdef USE_LIBRETINY + platform = lt_cpu_get_model_name(); #endif if (platform != nullptr) { service.txt_records.push_back({"platform", platform}); diff --git a/esphome/components/mdns/mdns_libretiny.cpp b/esphome/components/mdns/mdns_libretiny.cpp new file mode 100644 index 0000000000..ccb79c88b9 --- /dev/null +++ b/esphome/components/mdns/mdns_libretiny.cpp @@ -0,0 +1,43 @@ +#ifdef USE_LIBRETINY + +#include "esphome/components/network/ip_address.h" +#include "esphome/components/network/util.h" +#include "esphome/core/log.h" +#include "mdns_component.h" + +#include + +namespace esphome { +namespace mdns { + +void MDNSComponent::setup() { + this->compile_records_(); + + MDNS.begin(this->hostname_.c_str()); + + for (const auto &service : this->services_) { + // Strip the leading underscore from the proto and service_type. While it is + // part of the wire protocol to have an underscore, and for example ESP-IDF + // expects the underscore to be there, the ESP8266 implementation always adds + // the underscore itself. + auto *proto = service.proto.c_str(); + while (*proto == '_') { + proto++; + } + auto *service_type = service.service_type.c_str(); + while (*service_type == '_') { + service_type++; + } + MDNS.addService(service_type, proto, service.port); + for (const auto &record : service.txt_records) { + MDNS.addServiceTxt(service_type, proto, record.key.c_str(), record.value.c_str()); + } + } +} + +void MDNSComponent::on_shutdown() {} + +} // namespace mdns +} // namespace esphome + +#endif diff --git a/esphome/components/ota/__init__.py b/esphome/components/ota/__init__.py index a966157ffa..eb2a83272d 100644 --- a/esphome/components/ota/__init__.py +++ b/esphome/components/ota/__init__.py @@ -41,7 +41,14 @@ CONFIG_SCHEMA = cv.Schema( { cv.GenerateID(): cv.declare_id(OTAComponent), cv.Optional(CONF_SAFE_MODE, default=True): cv.boolean, - cv.SplitDefault(CONF_PORT, esp8266=8266, esp32=3232, rp2040=2040): cv.port, + cv.SplitDefault( + CONF_PORT, + esp8266=8266, + esp32=3232, + rp2040=2040, + bk72xx=8892, + rtl87xx=8892, + ): cv.port, cv.Optional(CONF_PASSWORD): cv.string, cv.Optional( CONF_REBOOT_TIMEOUT, default="5min" diff --git a/esphome/components/ota/ota_backend_arduino_libretiny.cpp b/esphome/components/ota/ota_backend_arduino_libretiny.cpp new file mode 100644 index 0000000000..dbf6c97988 --- /dev/null +++ b/esphome/components/ota/ota_backend_arduino_libretiny.cpp @@ -0,0 +1,46 @@ +#include "esphome/core/defines.h" +#ifdef USE_LIBRETINY + +#include "ota_backend_arduino_libretiny.h" +#include "ota_component.h" +#include "ota_backend.h" + +#include + +namespace esphome { +namespace ota { + +OTAResponseTypes ArduinoLibreTinyOTABackend::begin(size_t image_size) { + bool ret = Update.begin(image_size, U_FLASH); + if (ret) { + return OTA_RESPONSE_OK; + } + + uint8_t error = Update.getError(); + if (error == UPDATE_ERROR_SIZE) + return OTA_RESPONSE_ERROR_ESP32_NOT_ENOUGH_SPACE; + return OTA_RESPONSE_ERROR_UNKNOWN; +} + +void ArduinoLibreTinyOTABackend::set_update_md5(const char *md5) { Update.setMD5(md5); } + +OTAResponseTypes ArduinoLibreTinyOTABackend::write(uint8_t *data, size_t len) { + size_t written = Update.write(data, len); + if (written != len) { + return OTA_RESPONSE_ERROR_WRITING_FLASH; + } + return OTA_RESPONSE_OK; +} + +OTAResponseTypes ArduinoLibreTinyOTABackend::end() { + if (!Update.end()) + return OTA_RESPONSE_ERROR_UPDATE_END; + return OTA_RESPONSE_OK; +} + +void ArduinoLibreTinyOTABackend::abort() { Update.abort(); } + +} // namespace ota +} // namespace esphome + +#endif // USE_LIBRETINY diff --git a/esphome/components/ota/ota_backend_arduino_libretiny.h b/esphome/components/ota/ota_backend_arduino_libretiny.h new file mode 100644 index 0000000000..79656bb353 --- /dev/null +++ b/esphome/components/ota/ota_backend_arduino_libretiny.h @@ -0,0 +1,24 @@ +#pragma once +#include "esphome/core/defines.h" +#ifdef USE_LIBRETINY + +#include "ota_component.h" +#include "ota_backend.h" + +namespace esphome { +namespace ota { + +class ArduinoLibreTinyOTABackend : public OTABackend { + public: + OTAResponseTypes begin(size_t image_size) override; + void set_update_md5(const char *md5) override; + OTAResponseTypes write(uint8_t *data, size_t len) override; + OTAResponseTypes end() override; + void abort() override; + bool supports_compression() override { return false; } +}; + +} // namespace ota +} // namespace esphome + +#endif // USE_LIBRETINY diff --git a/esphome/components/ota/ota_component.cpp b/esphome/components/ota/ota_component.cpp index acf9e923b6..41cf333be9 100644 --- a/esphome/components/ota/ota_component.cpp +++ b/esphome/components/ota/ota_component.cpp @@ -3,6 +3,7 @@ #include "ota_backend_arduino_esp32.h" #include "ota_backend_arduino_esp8266.h" #include "ota_backend_arduino_rp2040.h" +#include "ota_backend_arduino_libretiny.h" #include "ota_backend_esp_idf.h" #include "esphome/core/log.h" @@ -39,6 +40,9 @@ std::unique_ptr make_ota_backend() { #ifdef USE_RP2040 return make_unique(); #endif // USE_RP2040 +#ifdef USE_LIBRETINY + return make_unique(); +#endif } OTAComponent::OTAComponent() { global_ota_component = this; } diff --git a/esphome/components/remote_receiver/__init__.py b/esphome/components/remote_receiver/__init__.py index d59ad5c7f1..5737957adb 100644 --- a/esphome/components/remote_receiver/__init__.py +++ b/esphome/components/remote_receiver/__init__.py @@ -31,7 +31,11 @@ CONFIG_SCHEMA = remote_base.validate_triggers( cv.percentage_int, cv.Range(min=0) ), cv.SplitDefault( - CONF_BUFFER_SIZE, esp32="10000b", esp8266="1000b" + CONF_BUFFER_SIZE, + esp32="10000b", + esp8266="1000b", + bk72xx="1000b", + rtl87xx="1000b", ): cv.validate_bytes, cv.Optional(CONF_FILTER, default="50us"): cv.All( cv.positive_time_period_microseconds, diff --git a/esphome/components/remote_receiver/remote_receiver.h b/esphome/components/remote_receiver/remote_receiver.h index 50153c105d..82c66e3cd0 100644 --- a/esphome/components/remote_receiver/remote_receiver.h +++ b/esphome/components/remote_receiver/remote_receiver.h @@ -6,7 +6,7 @@ namespace esphome { namespace remote_receiver { -#ifdef USE_ESP8266 +#if defined(USE_ESP8266) || defined(USE_LIBRETINY) struct RemoteReceiverComponentStore { static void gpio_intr(RemoteReceiverComponentStore *arg); @@ -55,7 +55,7 @@ class RemoteReceiverComponent : public remote_base::RemoteReceiverBase, esp_err_t error_code_{ESP_OK}; #endif -#ifdef USE_ESP8266 +#if defined(USE_ESP8266) || defined(USE_LIBRETINY) RemoteReceiverComponentStore store_; HighFrequencyLoopRequester high_freq_; #endif diff --git a/esphome/components/remote_receiver/remote_receiver_libretiny.cpp b/esphome/components/remote_receiver/remote_receiver_libretiny.cpp new file mode 100644 index 0000000000..ac85b6b520 --- /dev/null +++ b/esphome/components/remote_receiver/remote_receiver_libretiny.cpp @@ -0,0 +1,122 @@ +#include "remote_receiver.h" +#include "esphome/core/hal.h" +#include "esphome/core/log.h" +#include "esphome/core/helpers.h" + +#ifdef USE_LIBRETINY + +namespace esphome { +namespace remote_receiver { + +static const char *const TAG = "remote_receiver.libretiny"; + +void IRAM_ATTR HOT RemoteReceiverComponentStore::gpio_intr(RemoteReceiverComponentStore *arg) { + const uint32_t now = micros(); + // If the lhs is 1 (rising edge) we should write to an uneven index and vice versa + const uint32_t next = (arg->buffer_write_at + 1) % arg->buffer_size; + const bool level = arg->pin.digital_read(); + if (level != next % 2) + return; + + // If next is buffer_read, we have hit an overflow + if (next == arg->buffer_read_at) + return; + + const uint32_t last_change = arg->buffer[arg->buffer_write_at]; + const uint32_t time_since_change = now - last_change; + if (time_since_change <= arg->filter_us) + return; + + arg->buffer[arg->buffer_write_at = next] = now; +} + +void RemoteReceiverComponent::setup() { + ESP_LOGCONFIG(TAG, "Setting up Remote Receiver..."); + this->pin_->setup(); + auto &s = this->store_; + s.filter_us = this->filter_us_; + s.pin = this->pin_->to_isr(); + s.buffer_size = this->buffer_size_; + + this->high_freq_.start(); + if (s.buffer_size % 2 != 0) { + // Make sure divisible by two. This way, we know that every 0bxxx0 index is a space and every 0bxxx1 index is a mark + s.buffer_size++; + } + + s.buffer = new uint32_t[s.buffer_size]; + void *buf = (void *) s.buffer; + memset(buf, 0, s.buffer_size * sizeof(uint32_t)); + + // First index is a space. + if (this->pin_->digital_read()) { + s.buffer_write_at = s.buffer_read_at = 1; + } else { + s.buffer_write_at = s.buffer_read_at = 0; + } + this->pin_->attach_interrupt(RemoteReceiverComponentStore::gpio_intr, &this->store_, gpio::INTERRUPT_ANY_EDGE); +} +void RemoteReceiverComponent::dump_config() { + ESP_LOGCONFIG(TAG, "Remote Receiver:"); + LOG_PIN(" Pin: ", this->pin_); + if (this->pin_->digital_read()) { + ESP_LOGW(TAG, "Remote Receiver Signal starts with a HIGH value. Usually this means you have to " + "invert the signal using 'inverted: True' in the pin schema!"); + } + ESP_LOGCONFIG(TAG, " Buffer Size: %u", this->buffer_size_); + ESP_LOGCONFIG(TAG, " Tolerance: %u%%", this->tolerance_); + ESP_LOGCONFIG(TAG, " Filter out pulses shorter than: %u us", this->filter_us_); + ESP_LOGCONFIG(TAG, " Signal is done after %u us of no changes", this->idle_us_); +} + +void RemoteReceiverComponent::loop() { + auto &s = this->store_; + + // copy write at to local variables, as it's volatile + const uint32_t write_at = s.buffer_write_at; + const uint32_t dist = (s.buffer_size + write_at - s.buffer_read_at) % s.buffer_size; + // signals must at least one rising and one leading edge + if (dist <= 1) + return; + const uint32_t now = micros(); + if (now - s.buffer[write_at] < this->idle_us_) { + // The last change was fewer than the configured idle time ago. + return; + } + + ESP_LOGVV(TAG, "read_at=%u write_at=%u dist=%u now=%u end=%u", s.buffer_read_at, write_at, dist, now, + s.buffer[write_at]); + + // Skip first value, it's from the previous idle level + s.buffer_read_at = (s.buffer_read_at + 1) % s.buffer_size; + uint32_t prev = s.buffer_read_at; + s.buffer_read_at = (s.buffer_read_at + 1) % s.buffer_size; + const uint32_t reserve_size = 1 + (s.buffer_size + write_at - s.buffer_read_at) % s.buffer_size; + this->temp_.clear(); + this->temp_.reserve(reserve_size); + int32_t multiplier = s.buffer_read_at % 2 == 0 ? 1 : -1; + + for (uint32_t i = 0; prev != write_at; i++) { + int32_t delta = s.buffer[s.buffer_read_at] - s.buffer[prev]; + if (uint32_t(delta) >= this->idle_us_) { + // already found a space longer than idle. There must have been two pulses + break; + } + + ESP_LOGVV(TAG, " i=%u buffer[%u]=%u - buffer[%u]=%u -> %d", i, s.buffer_read_at, s.buffer[s.buffer_read_at], prev, + s.buffer[prev], multiplier * delta); + this->temp_.push_back(multiplier * delta); + prev = s.buffer_read_at; + s.buffer_read_at = (s.buffer_read_at + 1) % s.buffer_size; + multiplier *= -1; + } + s.buffer_read_at = (s.buffer_size + s.buffer_read_at - 1) % s.buffer_size; + this->temp_.push_back(this->idle_us_ * multiplier); + + this->call_listeners_dumpers_(); +} + +} // namespace remote_receiver +} // namespace esphome + +#endif diff --git a/esphome/components/remote_transmitter/remote_transmitter.h b/esphome/components/remote_transmitter/remote_transmitter.h index a20df0cc62..686a6ec09b 100644 --- a/esphome/components/remote_transmitter/remote_transmitter.h +++ b/esphome/components/remote_transmitter/remote_transmitter.h @@ -28,7 +28,7 @@ class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase, protected: void send_internal(uint32_t send_times, uint32_t send_wait) override; -#ifdef USE_ESP8266 +#if defined(USE_ESP8266) || defined(USE_LIBRETINY) void calculate_on_off_time_(uint32_t carrier_frequency, uint32_t *on_time_period, uint32_t *off_time_period); void mark_(uint32_t on_time, uint32_t off_time, uint32_t usec); diff --git a/esphome/components/remote_transmitter/remote_transmitter_libretiny.cpp b/esphome/components/remote_transmitter/remote_transmitter_libretiny.cpp new file mode 100644 index 0000000000..78bb280482 --- /dev/null +++ b/esphome/components/remote_transmitter/remote_transmitter_libretiny.cpp @@ -0,0 +1,104 @@ +#include "remote_transmitter.h" +#include "esphome/core/log.h" +#include "esphome/core/application.h" + +#ifdef USE_LIBRETINY + +namespace esphome { +namespace remote_transmitter { + +static const char *const TAG = "remote_transmitter"; + +void RemoteTransmitterComponent::setup() { + this->pin_->setup(); + this->pin_->digital_write(false); +} + +void RemoteTransmitterComponent::dump_config() { + ESP_LOGCONFIG(TAG, "Remote Transmitter..."); + ESP_LOGCONFIG(TAG, " Carrier Duty: %u%%", this->carrier_duty_percent_); + LOG_PIN(" Pin: ", this->pin_); +} + +void RemoteTransmitterComponent::calculate_on_off_time_(uint32_t carrier_frequency, uint32_t *on_time_period, + uint32_t *off_time_period) { + if (carrier_frequency == 0) { + *on_time_period = 0; + *off_time_period = 0; + return; + } + uint32_t period = (1000000UL + carrier_frequency / 2) / carrier_frequency; // round(1000000/freq) + period = std::max(uint32_t(1), period); + *on_time_period = (period * this->carrier_duty_percent_) / 100; + *off_time_period = period - *on_time_period; +} + +void RemoteTransmitterComponent::await_target_time_() { + const uint32_t current_time = micros(); + if (this->target_time_ == 0) { + this->target_time_ = current_time; + } else { + while (this->target_time_ > micros()) { + // busy loop that ensures micros is constantly called + } + } +} + +void RemoteTransmitterComponent::mark_(uint32_t on_time, uint32_t off_time, uint32_t usec) { + this->await_target_time_(); + this->pin_->digital_write(true); + + const uint32_t target = this->target_time_ + usec; + if (this->carrier_duty_percent_ < 100 && (on_time > 0 || off_time > 0)) { + while (true) { // Modulate with carrier frequency + this->target_time_ += on_time; + if (this->target_time_ >= target) + break; + this->await_target_time_(); + this->pin_->digital_write(false); + + this->target_time_ += off_time; + if (this->target_time_ >= target) + break; + this->await_target_time_(); + this->pin_->digital_write(true); + } + } + this->target_time_ = target; +} + +void RemoteTransmitterComponent::space_(uint32_t usec) { + this->await_target_time_(); + this->pin_->digital_write(false); + this->target_time_ += usec; +} + +void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t send_wait) { + ESP_LOGD(TAG, "Sending remote code..."); + uint32_t on_time, off_time; + this->calculate_on_off_time_(this->temp_.get_carrier_frequency(), &on_time, &off_time); + this->target_time_ = 0; + for (uint32_t i = 0; i < send_times; i++) { + InterruptLock lock; + for (int32_t item : this->temp_.get_data()) { + if (item > 0) { + const auto length = uint32_t(item); + this->mark_(on_time, off_time, length); + } else { + const auto length = uint32_t(-item); + this->space_(length); + } + App.feed_wdt(); + } + this->await_target_time_(); // wait for duration of last pulse + this->pin_->digital_write(false); + + if (i + 1 < send_times) + this->target_time_ += send_wait; + } +} + +} // namespace remote_transmitter +} // namespace esphome + +#endif diff --git a/esphome/components/rp2040/gpio.py b/esphome/components/rp2040/gpio.py index 2340bed892..4823a6d22a 100644 --- a/esphome/components/rp2040/gpio.py +++ b/esphome/components/rp2040/gpio.py @@ -1,6 +1,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.const import ( + CONF_ANALOG, CONF_ID, CONF_INPUT, CONF_INVERTED, @@ -76,8 +77,6 @@ def validate_supports(value): return value -CONF_ANALOG = "analog" - RP2040_PIN_SCHEMA = cv.All( cv.Schema( { diff --git a/esphome/components/rtl87xx/__init__.py b/esphome/components/rtl87xx/__init__.py new file mode 100644 index 0000000000..9060a7c4a6 --- /dev/null +++ b/esphome/components/rtl87xx/__init__.py @@ -0,0 +1,51 @@ +# This file was auto-generated by libretiny/generate_components.py +# Do not modify its contents. +# For custom pin validators, put validate_pin() or validate_usage() +# in gpio.py file in this directory. +# For changing schema/pin schema, put COMPONENT_SCHEMA or COMPONENT_PIN_SCHEMA +# in schema.py file in this directory. + +from esphome import pins +from esphome.components import libretiny +from esphome.components.libretiny.const import ( + COMPONENT_RTL87XX, + KEY_COMPONENT_DATA, + KEY_LIBRETINY, + LibreTinyComponent, +) +from esphome.core import CORE + +from .boards import RTL87XX_BOARDS, RTL87XX_BOARD_PINS + +CODEOWNERS = ["@kuba2k2"] +AUTO_LOAD = ["libretiny"] + +COMPONENT_DATA = LibreTinyComponent( + name=COMPONENT_RTL87XX, + boards=RTL87XX_BOARDS, + board_pins=RTL87XX_BOARD_PINS, + pin_validation=None, + usage_validation=None, +) + + +def _set_core_data(config): + CORE.data[KEY_LIBRETINY] = {} + CORE.data[KEY_LIBRETINY][KEY_COMPONENT_DATA] = COMPONENT_DATA + return config + + +CONFIG_SCHEMA = libretiny.BASE_SCHEMA + +PIN_SCHEMA = libretiny.gpio.BASE_PIN_SCHEMA + +CONFIG_SCHEMA.prepend_extra(_set_core_data) + + +async def to_code(config): + return await libretiny.component_to_code(config) + + +@pins.PIN_SCHEMA_REGISTRY.register("rtl87xx", PIN_SCHEMA) +async def pin_to_code(config): + return await libretiny.gpio.component_pin_to_code(config) diff --git a/esphome/components/rtl87xx/boards.py b/esphome/components/rtl87xx/boards.py new file mode 100644 index 0000000000..6c29467f6e --- /dev/null +++ b/esphome/components/rtl87xx/boards.py @@ -0,0 +1,1390 @@ +# This file was auto-generated by libretiny/generate_components.py +# Do not modify its contents. + +from esphome.components.libretiny.const import FAMILY_RTL8710B, FAMILY_RTL8720C + +RTL87XX_BOARDS = { + "bw12": { + "name": "BW12", + "family": FAMILY_RTL8710B, + }, + "bw15": { + "name": "BW15", + "family": FAMILY_RTL8720C, + }, + "generic-rtl8710bn-2mb-468k": { + "name": "Generic - RTL8710BN (2M/468k)", + "family": FAMILY_RTL8710B, + }, + "generic-rtl8710bn-2mb-788k": { + "name": "Generic - RTL8710BN (2M/788k)", + "family": FAMILY_RTL8710B, + }, + "generic-rtl8710bx-4mb-980k": { + "name": "Generic - RTL8710BX (4M/980k)", + "family": FAMILY_RTL8710B, + }, + "generic-rtl8720cf-2mb-992k": { + "name": "Generic - RTL8720CF (2M/992k)", + "family": FAMILY_RTL8720C, + }, + "t102-v1.1": { + "name": "T102_V1.1", + "family": FAMILY_RTL8710B, + }, + "t103-v1.0": { + "name": "T103_V1.0", + "family": FAMILY_RTL8710B, + }, + "wr1": { + "name": "WR1 Wi-Fi Module", + "family": FAMILY_RTL8710B, + }, + "wr1e": { + "name": "WR1E Wi-Fi Module", + "family": FAMILY_RTL8710B, + }, + "wr2": { + "name": "WR2 Wi-Fi Module", + "family": FAMILY_RTL8710B, + }, + "wr2e": { + "name": "WR2E Wi-Fi Module", + "family": FAMILY_RTL8710B, + }, + "wr2l": { + "name": "WR2L Wi-Fi Module", + "family": FAMILY_RTL8710B, + }, + "wr2le": { + "name": "WR2LE Wi-Fi Module", + "family": FAMILY_RTL8710B, + }, + "wr3": { + "name": "WR3 Wi-Fi Module", + "family": FAMILY_RTL8710B, + }, + "wr3e": { + "name": "WR3E Wi-Fi Module", + "family": FAMILY_RTL8710B, + }, + "wr3l": { + "name": "WR3L Wi-Fi Module", + "family": FAMILY_RTL8710B, + }, + "wr3le": { + "name": "WR3LE Wi-Fi Module", + "family": FAMILY_RTL8710B, + }, + "wr3n": { + "name": "WR3N Wi-Fi Module", + "family": FAMILY_RTL8710B, + }, +} + +RTL87XX_BOARD_PINS = { + "bw12": { + "SPI0_CS": 19, + "SPI0_MISO": 22, + "SPI0_MOSI": 23, + "SPI0_SCK": 18, + "SPI1_CS": 19, + "SPI1_MISO": 22, + "SPI1_MOSI": 23, + "SPI1_SCK": 18, + "WIRE0_SCL_0": 29, + "WIRE0_SCL_1": 22, + "WIRE0_SDA_0": 19, + "WIRE0_SDA_1": 30, + "WIRE1_SCL": 18, + "WIRE1_SDA": 23, + "SERIAL0_CTS": 19, + "SERIAL0_RTS": 22, + "SERIAL0_RX": 18, + "SERIAL0_TX": 23, + "SERIAL2_RX": 29, + "SERIAL2_TX": 30, + "ADC1": 19, + "CS0": 19, + "CS1": 19, + "CTS0": 19, + "MISO0": 22, + "MISO1": 22, + "MOSI0": 23, + "MOSI1": 23, + "PA00": 0, + "PA0": 0, + "PA05": 5, + "PA5": 5, + "PA12": 12, + "PA14": 14, + "PA15": 15, + "PA18": 18, + "PA19": 19, + "PA22": 22, + "PA23": 23, + "PA29": 29, + "PA30": 30, + "PWM0": 23, + "PWM1": 15, + "PWM2": 0, + "PWM3": 12, + "PWM4": 30, + "PWM5": 22, + "RTS0": 22, + "RX0": 18, + "RX2": 29, + "SCK0": 18, + "SCK1": 18, + "SCL0": 22, + "SCL1": 18, + "SDA0": 30, + "SDA1": 23, + "TX0": 23, + "TX2": 30, + "D0": 5, + "D1": 29, + "D2": 0, + "D3": 19, + "D4": 22, + "D5": 30, + "D6": 14, + "D7": 12, + "D8": 15, + "D9": 18, + "D10": 23, + "A0": 19, + }, + "bw15": { + "SPI0_CS_0": 2, + "SPI0_CS_1": 15, + "SPI0_MISO": 20, + "SPI0_MOSI_0": 4, + "SPI0_MOSI_1": 19, + "SPI0_SCK_0": 16, + "SPI0_SCK_1": 3, + "WIRE0_SCL_0": 2, + "WIRE0_SCL_1": 15, + "WIRE0_SCL_2": 19, + "WIRE0_SDA_0": 20, + "WIRE0_SDA_1": 16, + "WIRE0_SDA_2": 3, + "SERIAL0_RX": 13, + "SERIAL0_TX": 14, + "SERIAL1_CTS": 4, + "SERIAL1_RX_0": 2, + "SERIAL1_RX_1": 0, + "SERIAL1_TX_0": 3, + "SERIAL1_TX_1": 1, + "SERIAL2_CTS": 19, + "SERIAL2_RTS": 20, + "SERIAL2_RX": 15, + "SERIAL2_TX": 16, + "CS0": 15, + "CTS1": 4, + "CTS2": 19, + "MISO0": 20, + "MOSI0": 19, + "PA00": 0, + "PA0": 0, + "PA01": 1, + "PA1": 1, + "PA02": 2, + "PA2": 2, + "PA03": 3, + "PA3": 3, + "PA04": 4, + "PA4": 4, + "PA13": 13, + "PA14": 14, + "PA15": 15, + "PA16": 16, + "PA17": 17, + "PA18": 18, + "PA19": 19, + "PA20": 20, + "PWM0": 0, + "PWM1": 1, + "PWM2": 14, + "PWM3": 3, + "PWM4": 16, + "PWM5": 17, + "PWM6": 18, + "PWM7": 13, + "RTS2": 20, + "RX0": 13, + "RX1": 0, + "RX2": 15, + "SCK0": 3, + "SCL0": 19, + "SDA0": 3, + "TX0": 14, + "TX1": 1, + "TX2": 16, + "D0": 17, + "D1": 18, + "D2": 2, + "D3": 15, + "D4": 4, + "D5": 19, + "D6": 20, + "D7": 16, + "D8": 0, + "D9": 3, + "D10": 1, + "D11": 13, + "D12": 14, + }, + "generic-rtl8710bn-2mb-468k": { + "SPI0_CS": 19, + "SPI0_MISO": 22, + "SPI0_MOSI": 23, + "SPI0_SCK": 18, + "SPI1_CS": 19, + "SPI1_MISO": 22, + "SPI1_MOSI": 23, + "SPI1_SCK": 18, + "WIRE0_SCL_0": 22, + "WIRE0_SCL_1": 29, + "WIRE0_SDA_0": 19, + "WIRE0_SDA_1": 30, + "WIRE1_SCL": 18, + "WIRE1_SDA": 23, + "SERIAL0_CTS": 19, + "SERIAL0_RTS": 22, + "SERIAL0_RX": 18, + "SERIAL0_TX": 23, + "SERIAL2_RX": 29, + "SERIAL2_TX": 30, + "ADC1": 19, + "ADC2": 41, + "CS0": 19, + "CS1": 19, + "CTS0": 19, + "FCS": 6, + "FD0": 9, + "FD1": 7, + "FD2": 8, + "FD3": 11, + "FSCK": 10, + "MISO0": 22, + "MISO1": 22, + "MOSI0": 23, + "MOSI1": 23, + "PA00": 0, + "PA0": 0, + "PA05": 5, + "PA5": 5, + "PA06": 6, + "PA6": 6, + "PA07": 7, + "PA7": 7, + "PA08": 8, + "PA8": 8, + "PA09": 9, + "PA9": 9, + "PA10": 10, + "PA11": 11, + "PA12": 12, + "PA14": 14, + "PA15": 15, + "PA18": 18, + "PA19": 19, + "PA22": 22, + "PA23": 23, + "PA29": 29, + "PA30": 30, + "PWM0": 23, + "PWM1": 15, + "PWM2": 0, + "PWM3": 12, + "PWM4": 30, + "PWM5": 22, + "RTS0": 22, + "RX0": 18, + "RX2": 29, + "SCK0": 18, + "SCK1": 18, + "SCL0": 29, + "SCL1": 18, + "SDA0": 30, + "SDA1": 23, + "TX0": 23, + "TX2": 30, + "D0": 0, + "D1": 5, + "D2": 6, + "D3": 7, + "D4": 8, + "D5": 9, + "D6": 10, + "D7": 11, + "D8": 12, + "D9": 14, + "D10": 15, + "D11": 18, + "D12": 19, + "D13": 22, + "D14": 23, + "D15": 29, + "D16": 30, + "A0": 19, + "A1": 41, + }, + "generic-rtl8710bn-2mb-788k": { + "SPI0_CS": 19, + "SPI0_MISO": 22, + "SPI0_MOSI": 23, + "SPI0_SCK": 18, + "SPI1_CS": 19, + "SPI1_MISO": 22, + "SPI1_MOSI": 23, + "SPI1_SCK": 18, + "WIRE0_SCL_0": 22, + "WIRE0_SCL_1": 29, + "WIRE0_SDA_0": 19, + "WIRE0_SDA_1": 30, + "WIRE1_SCL": 18, + "WIRE1_SDA": 23, + "SERIAL0_CTS": 19, + "SERIAL0_RTS": 22, + "SERIAL0_RX": 18, + "SERIAL0_TX": 23, + "SERIAL2_RX": 29, + "SERIAL2_TX": 30, + "ADC1": 19, + "ADC2": 41, + "CS0": 19, + "CS1": 19, + "CTS0": 19, + "FCS": 6, + "FD0": 9, + "FD1": 7, + "FD2": 8, + "FD3": 11, + "FSCK": 10, + "MISO0": 22, + "MISO1": 22, + "MOSI0": 23, + "MOSI1": 23, + "PA00": 0, + "PA0": 0, + "PA05": 5, + "PA5": 5, + "PA06": 6, + "PA6": 6, + "PA07": 7, + "PA7": 7, + "PA08": 8, + "PA8": 8, + "PA09": 9, + "PA9": 9, + "PA10": 10, + "PA11": 11, + "PA12": 12, + "PA14": 14, + "PA15": 15, + "PA18": 18, + "PA19": 19, + "PA22": 22, + "PA23": 23, + "PA29": 29, + "PA30": 30, + "PWM0": 23, + "PWM1": 15, + "PWM2": 0, + "PWM3": 12, + "PWM4": 30, + "PWM5": 22, + "RTS0": 22, + "RX0": 18, + "RX2": 29, + "SCK0": 18, + "SCK1": 18, + "SCL0": 29, + "SCL1": 18, + "SDA0": 30, + "SDA1": 23, + "TX0": 23, + "TX2": 30, + "D0": 0, + "D1": 5, + "D2": 6, + "D3": 7, + "D4": 8, + "D5": 9, + "D6": 10, + "D7": 11, + "D8": 12, + "D9": 14, + "D10": 15, + "D11": 18, + "D12": 19, + "D13": 22, + "D14": 23, + "D15": 29, + "D16": 30, + "A0": 19, + "A1": 41, + }, + "generic-rtl8710bx-4mb-980k": { + "SPI0_CS": 19, + "SPI0_MISO": 22, + "SPI0_MOSI": 23, + "SPI0_SCK": 18, + "SPI1_CS": 19, + "SPI1_MISO": 22, + "SPI1_MOSI": 23, + "SPI1_SCK": 18, + "WIRE0_SCL_0": 22, + "WIRE0_SCL_1": 29, + "WIRE0_SDA_0": 19, + "WIRE0_SDA_1": 30, + "WIRE1_SCL": 18, + "WIRE1_SDA": 23, + "SERIAL0_CTS": 19, + "SERIAL0_RTS": 22, + "SERIAL0_RX": 18, + "SERIAL0_TX": 23, + "SERIAL2_RX": 29, + "SERIAL2_TX": 30, + "ADC1": 19, + "CS0": 19, + "CS1": 19, + "CTS0": 19, + "FCS": 6, + "FD0": 9, + "FD1": 7, + "FD2": 8, + "FD3": 11, + "FSCK": 10, + "MISO0": 22, + "MISO1": 22, + "MOSI0": 23, + "MOSI1": 23, + "PA00": 0, + "PA0": 0, + "PA05": 5, + "PA5": 5, + "PA06": 6, + "PA6": 6, + "PA07": 7, + "PA7": 7, + "PA08": 8, + "PA8": 8, + "PA09": 9, + "PA9": 9, + "PA10": 10, + "PA11": 11, + "PA12": 12, + "PA14": 14, + "PA15": 15, + "PA18": 18, + "PA19": 19, + "PA22": 22, + "PA23": 23, + "PA29": 29, + "PA30": 30, + "PWM0": 23, + "PWM1": 15, + "PWM2": 0, + "PWM3": 12, + "PWM4": 30, + "PWM5": 22, + "RTS0": 22, + "RX0": 18, + "RX2": 29, + "SCK0": 18, + "SCK1": 18, + "SCL0": 29, + "SCL1": 18, + "SDA0": 30, + "SDA1": 23, + "TX0": 23, + "TX2": 30, + "D0": 0, + "D1": 5, + "D2": 6, + "D3": 7, + "D4": 8, + "D5": 9, + "D6": 10, + "D7": 11, + "D8": 12, + "D9": 14, + "D10": 15, + "D11": 18, + "D12": 19, + "D13": 22, + "D14": 23, + "D15": 29, + "D16": 30, + "A0": 19, + }, + "generic-rtl8720cf-2mb-992k": { + "SPI0_CS_0": 2, + "SPI0_CS_1": 7, + "SPI0_CS_2": 15, + "SPI0_MISO_0": 10, + "SPI0_MISO_1": 20, + "SPI0_MOSI_0": 4, + "SPI0_MOSI_1": 9, + "SPI0_MOSI_2": 19, + "SPI0_SCK_0": 3, + "SPI0_SCK_1": 8, + "SPI0_SCK_2": 16, + "WIRE0_SCL_0": 2, + "WIRE0_SCL_1": 11, + "WIRE0_SCL_2": 15, + "WIRE0_SCL_3": 19, + "WIRE0_SDA_0": 3, + "WIRE0_SDA_1": 12, + "WIRE0_SDA_2": 16, + "WIRE0_SDA_3": 20, + "SERIAL0_CTS": 10, + "SERIAL0_RTS": 9, + "SERIAL0_RX_0": 12, + "SERIAL0_RX_1": 13, + "SERIAL0_TX_0": 11, + "SERIAL0_TX_1": 14, + "SERIAL1_CTS": 4, + "SERIAL1_RX_0": 0, + "SERIAL1_RX_1": 2, + "SERIAL1_TX_0": 1, + "SERIAL1_TX_1": 3, + "SERIAL2_CTS": 19, + "SERIAL2_RTS": 20, + "SERIAL2_RX": 15, + "SERIAL2_TX": 16, + "CS0": 15, + "CTS0": 10, + "CTS1": 4, + "CTS2": 19, + "MISO0": 20, + "MOSI0": 19, + "PA00": 0, + "PA0": 0, + "PA01": 1, + "PA1": 1, + "PA02": 2, + "PA2": 2, + "PA03": 3, + "PA3": 3, + "PA04": 4, + "PA4": 4, + "PA07": 7, + "PA7": 7, + "PA08": 8, + "PA8": 8, + "PA09": 9, + "PA9": 9, + "PA10": 10, + "PA11": 11, + "PA12": 12, + "PA13": 13, + "PA14": 14, + "PA15": 15, + "PA16": 16, + "PA17": 17, + "PA18": 18, + "PA19": 19, + "PA20": 20, + "PA23": 23, + "PWM0": 20, + "PWM1": 12, + "PWM2": 14, + "PWM3": 15, + "PWM4": 16, + "PWM5": 17, + "PWM6": 18, + "PWM7": 23, + "RTS0": 9, + "RTS2": 20, + "RX0": 13, + "RX1": 2, + "RX2": 15, + "SCK0": 16, + "SCL0": 19, + "SDA0": 20, + "TX0": 14, + "TX1": 3, + "TX2": 16, + "D0": 0, + "D1": 1, + "D2": 2, + "D3": 3, + "D4": 4, + "D5": 7, + "D6": 8, + "D7": 9, + "D8": 10, + "D9": 11, + "D10": 12, + "D11": 13, + "D12": 14, + "D13": 15, + "D14": 16, + "D15": 17, + "D16": 18, + "D17": 19, + "D18": 20, + "D19": 23, + }, + "t102-v1.1": { + "WIRE0_SCL": 29, + "WIRE0_SDA": 30, + "WIRE1_SCL": 18, + "WIRE1_SDA": 23, + "SERIAL0_RX": 18, + "SERIAL0_TX": 23, + "SERIAL2_RX": 29, + "SERIAL2_TX": 30, + "MOSI0": 23, + "MOSI1": 23, + "PA00": 0, + "PA0": 0, + "PA05": 5, + "PA5": 5, + "PA12": 12, + "PA14": 14, + "PA15": 15, + "PA18": 18, + "PA23": 23, + "PA29": 29, + "PA30": 30, + "PWM0": 14, + "PWM1": 15, + "PWM2": 0, + "PWM3": 12, + "PWM4": 29, + "RX0": 18, + "RX2": 29, + "SCK0": 18, + "SCK1": 18, + "SCL0": 29, + "SCL1": 18, + "SDA0": 30, + "SDA1": 23, + "TX0": 23, + "TX2": 30, + "D0": 12, + "D1": 0, + "D2": 5, + "D3": 30, + "D4": 29, + "D5": 18, + "D6": 23, + "D7": 14, + "D8": 15, + }, + "t103-v1.0": { + "SPI0_CS": 19, + "SPI0_MISO": 22, + "SPI0_MOSI": 23, + "SPI0_SCK": 18, + "SPI1_CS": 19, + "SPI1_MISO": 22, + "SPI1_MOSI": 23, + "SPI1_SCK": 18, + "WIRE0_SCL_0": 22, + "WIRE0_SCL_1": 29, + "WIRE0_SDA_0": 19, + "WIRE0_SDA_1": 30, + "WIRE1_SCL": 18, + "WIRE1_SDA": 23, + "SERIAL0_CTS": 19, + "SERIAL0_RTS": 22, + "SERIAL0_RX": 18, + "SERIAL0_TX": 23, + "SERIAL2_RX": 29, + "SERIAL2_TX": 30, + "ADC1": 19, + "ADC2": 41, + "CS0": 19, + "CS1": 19, + "CTS0": 19, + "MISO0": 22, + "MISO1": 22, + "MOSI0": 23, + "MOSI1": 23, + "PA00": 0, + "PA0": 0, + "PA05": 5, + "PA5": 5, + "PA12": 12, + "PA14": 14, + "PA15": 15, + "PA18": 18, + "PA19": 19, + "PA22": 22, + "PA23": 23, + "PA29": 29, + "PA30": 30, + "PWM0": 23, + "PWM1": 15, + "PWM2": 0, + "PWM3": 12, + "PWM4": 5, + "PWM5": 22, + "RTS0": 22, + "RX0": 18, + "RX2": 29, + "SCK0": 18, + "SCK1": 18, + "SCL0": 29, + "SCL1": 18, + "SDA0": 30, + "SDA1": 23, + "TX0": 23, + "TX2": 30, + "D0": 19, + "D1": 14, + "D2": 15, + "D3": 0, + "D4": 22, + "D5": 29, + "D6": 30, + "D7": 5, + "D8": 12, + "D9": 18, + "D10": 23, + "A0": 19, + "A1": 41, + }, + "wr1": { + "SPI0_CS": 19, + "SPI0_MISO": 22, + "SPI0_MOSI": 23, + "SPI0_SCK": 18, + "SPI1_CS": 19, + "SPI1_MISO": 22, + "SPI1_MOSI": 23, + "SPI1_SCK": 18, + "WIRE0_SCL_0": 29, + "WIRE0_SCL_1": 22, + "WIRE0_SDA_0": 30, + "WIRE0_SDA_1": 19, + "WIRE1_SCL": 18, + "WIRE1_SDA": 23, + "SERIAL0_CTS": 19, + "SERIAL0_RTS": 22, + "SERIAL0_RX": 18, + "SERIAL0_TX": 23, + "SERIAL2_RX": 29, + "SERIAL2_TX": 30, + "ADC1": 19, + "ADC2": 41, + "CS0": 19, + "CS1": 19, + "CTS0": 19, + "MISO0": 22, + "MISO1": 22, + "MOSI0": 23, + "MOSI1": 23, + "PA00": 0, + "PA0": 0, + "PA05": 5, + "PA5": 5, + "PA14": 14, + "PA15": 15, + "PA18": 18, + "PA19": 19, + "PA22": 22, + "PA23": 23, + "PA29": 29, + "PA30": 30, + "PWM0": 14, + "PWM1": 15, + "PWM2": 0, + "PWM4": 29, + "PWM5": 22, + "RTS0": 22, + "RX0": 18, + "RX2": 29, + "SCK0": 18, + "SCK1": 18, + "SCL0": 22, + "SCL1": 18, + "SDA0": 19, + "SDA1": 23, + "TX0": 23, + "TX2": 30, + "D0": 23, + "D1": 18, + "D2": 14, + "D3": 15, + "D4": 30, + "D5": 0, + "D6": 5, + "D7": 29, + "D8": 19, + "D9": 22, + "A0": 19, + "A1": 41, + }, + "wr1e": { + "SPI0_CS": 19, + "SPI0_MISO": 22, + "SPI0_MOSI": 23, + "SPI0_SCK": 18, + "SPI1_CS": 19, + "SPI1_MISO": 22, + "SPI1_MOSI": 23, + "SPI1_SCK": 18, + "WIRE0_SCL_0": 29, + "WIRE0_SCL_1": 22, + "WIRE0_SDA_0": 30, + "WIRE0_SDA_1": 19, + "WIRE1_SCL": 18, + "WIRE1_SDA": 23, + "SERIAL0_CTS": 19, + "SERIAL0_RTS": 22, + "SERIAL0_RX": 18, + "SERIAL0_TX": 23, + "SERIAL2_RX": 29, + "SERIAL2_TX": 30, + "ADC1": 19, + "ADC2": 41, + "CS0": 19, + "CS1": 19, + "CTS0": 19, + "MISO0": 22, + "MISO1": 22, + "MOSI0": 23, + "MOSI1": 23, + "PA05": 5, + "PA5": 5, + "PA12": 12, + "PA14": 14, + "PA15": 15, + "PA18": 18, + "PA19": 19, + "PA22": 22, + "PA23": 23, + "PA29": 29, + "PA30": 30, + "PWM0": 14, + "PWM1": 15, + "PWM3": 12, + "PWM4": 29, + "PWM5": 22, + "RTS0": 22, + "RX0": 18, + "RX2": 29, + "SCK0": 18, + "SCK1": 18, + "SCL0": 22, + "SCL1": 18, + "SDA0": 19, + "SDA1": 23, + "TX0": 23, + "TX2": 30, + "D0": 23, + "D1": 18, + "D2": 14, + "D3": 15, + "D4": 30, + "D5": 12, + "D6": 5, + "D7": 29, + "D8": 19, + "D9": 22, + "A0": 19, + "A1": 41, + }, + "wr2": { + "WIRE0_SCL": 29, + "WIRE0_SDA": 30, + "WIRE1_SCL": 18, + "WIRE1_SDA": 23, + "SERIAL0_RX": 18, + "SERIAL0_TX": 23, + "SERIAL2_RX": 29, + "SERIAL2_TX": 30, + "ADC2": 41, + "MOSI0": 23, + "MOSI1": 23, + "PA00": 0, + "PA0": 0, + "PA05": 5, + "PA5": 5, + "PA12": 12, + "PA14": 14, + "PA15": 15, + "PA18": 18, + "PA23": 23, + "PA29": 29, + "PA30": 30, + "PWM0": 14, + "PWM1": 15, + "PWM2": 0, + "PWM3": 12, + "PWM4": 29, + "RX0": 18, + "RX2": 29, + "SCK0": 18, + "SCK1": 18, + "SCL0": 29, + "SCL1": 18, + "SDA0": 30, + "SDA1": 23, + "TX0": 23, + "TX2": 30, + "D0": 12, + "D1": 0, + "D2": 5, + "D4": 18, + "D5": 23, + "D6": 14, + "D7": 15, + "D8": 30, + "D9": 29, + "A1": 41, + }, + "wr2e": { + "WIRE0_SCL": 29, + "WIRE0_SDA_0": 19, + "WIRE0_SDA_1": 30, + "WIRE1_SCL": 18, + "WIRE1_SDA": 23, + "SERIAL0_CTS": 19, + "SERIAL0_RX": 18, + "SERIAL0_TX": 23, + "SERIAL2_RX": 29, + "SERIAL2_TX": 30, + "ADC1": 19, + "ADC2": 41, + "CS0": 19, + "CS1": 19, + "CTS0": 19, + "MOSI0": 23, + "MOSI1": 23, + "PA05": 5, + "PA5": 5, + "PA12": 12, + "PA14": 14, + "PA15": 15, + "PA18": 18, + "PA19": 19, + "PA23": 23, + "PA29": 29, + "PA30": 30, + "PWM0": 14, + "PWM1": 15, + "PWM3": 12, + "PWM4": 29, + "RX0": 18, + "RX2": 29, + "SCK0": 18, + "SCK1": 18, + "SCL0": 29, + "SCL1": 18, + "SDA0": 30, + "SDA1": 23, + "TX0": 23, + "TX2": 30, + "D0": 12, + "D1": 19, + "D2": 5, + "D3": 18, + "D4": 23, + "D5": 14, + "D6": 15, + "D7": 30, + "D8": 29, + "A0": 19, + "A1": 41, + }, + "wr2l": { + "ADC1": 19, + "CS0": 19, + "CS1": 19, + "CTS0": 19, + "PA05": 5, + "PA5": 5, + "PA12": 12, + "PA14": 14, + "PA15": 15, + "PA19": 19, + "PWM0": 14, + "PWM1": 15, + "PWM3": 12, + "PWM4": 5, + "SDA0": 19, + "D0": 15, + "D1": 14, + "D2": 5, + "D3": 19, + "D4": 12, + "A0": 19, + }, + "wr2le": { + "MISO0": 22, + "MISO1": 22, + "PA05": 5, + "PA5": 5, + "PA12": 12, + "PA14": 14, + "PA15": 15, + "PA22": 22, + "PWM0": 14, + "PWM1": 15, + "PWM3": 12, + "PWM4": 5, + "PWM5": 22, + "RTS0": 22, + "SCL0": 22, + "D0": 15, + "D1": 14, + "D2": 5, + "D3": 22, + "D4": 12, + }, + "wr3": { + "SPI0_CS": 19, + "SPI0_MISO": 22, + "SPI0_MOSI": 23, + "SPI0_SCK": 18, + "SPI1_CS": 19, + "SPI1_MISO": 22, + "SPI1_MOSI": 23, + "SPI1_SCK": 18, + "WIRE0_SCL_0": 22, + "WIRE0_SCL_1": 29, + "WIRE0_SDA_0": 19, + "WIRE0_SDA_1": 30, + "WIRE1_SCL": 18, + "WIRE1_SDA": 23, + "SERIAL0_CTS": 19, + "SERIAL0_RTS": 22, + "SERIAL0_RX": 18, + "SERIAL0_TX": 23, + "SERIAL2_RX": 29, + "SERIAL2_TX": 30, + "ADC1": 19, + "ADC2": 41, + "CS0": 19, + "CS1": 19, + "CTS0": 19, + "MISO0": 22, + "MISO1": 22, + "MOSI0": 23, + "MOSI1": 23, + "PA00": 0, + "PA0": 0, + "PA05": 5, + "PA5": 5, + "PA12": 12, + "PA14": 14, + "PA15": 15, + "PA18": 18, + "PA19": 19, + "PA22": 22, + "PA23": 23, + "PA29": 29, + "PA30": 30, + "PWM0": 23, + "PWM1": 15, + "PWM2": 0, + "PWM3": 12, + "PWM4": 5, + "PWM5": 22, + "RTS0": 22, + "RX0": 18, + "RX2": 29, + "SCK0": 18, + "SCK1": 18, + "SCL0": 29, + "SCL1": 18, + "SDA0": 30, + "SDA1": 23, + "TX0": 23, + "TX2": 30, + "D0": 22, + "D1": 19, + "D2": 14, + "D3": 15, + "D4": 0, + "D5": 29, + "D6": 30, + "D7": 5, + "D8": 12, + "D9": 18, + "D10": 23, + "A0": 19, + "A1": 41, + }, + "wr3e": { + "SPI0_CS": 19, + "SPI0_MISO": 22, + "SPI0_MOSI": 23, + "SPI0_SCK": 18, + "SPI1_CS": 19, + "SPI1_MISO": 22, + "SPI1_MOSI": 23, + "SPI1_SCK": 18, + "WIRE0_SCL_0": 29, + "WIRE0_SCL_1": 22, + "WIRE0_SDA_0": 30, + "WIRE0_SDA_1": 19, + "WIRE1_SCL": 18, + "WIRE1_SDA": 23, + "SERIAL0_CTS": 19, + "SERIAL0_RTS": 22, + "SERIAL0_RX": 18, + "SERIAL0_TX": 23, + "SERIAL2_RX": 29, + "SERIAL2_TX": 30, + "ADC1": 19, + "ADC2": 41, + "CS0": 19, + "CS1": 19, + "CTS0": 19, + "MISO0": 22, + "MISO1": 22, + "MOSI0": 23, + "MOSI1": 23, + "PA00": 0, + "PA0": 0, + "PA05": 5, + "PA5": 5, + "PA12": 12, + "PA14": 14, + "PA15": 15, + "PA18": 18, + "PA19": 19, + "PA22": 22, + "PA23": 23, + "PA29": 29, + "PA30": 30, + "PWM0": 23, + "PWM1": 15, + "PWM2": 0, + "PWM3": 12, + "PWM4": 5, + "PWM5": 22, + "RTS0": 22, + "RX0": 18, + "RX2": 29, + "SCK0": 18, + "SCK1": 18, + "SCL0": 22, + "SCL1": 18, + "SDA0": 19, + "SDA1": 23, + "TX0": 23, + "TX2": 30, + "D0": 29, + "D1": 14, + "D2": 15, + "D3": 22, + "D4": 0, + "D5": 30, + "D6": 19, + "D7": 5, + "D8": 12, + "D9": 18, + "D10": 23, + "A0": 19, + "A1": 41, + }, + "wr3l": { + "SPI0_CS": 19, + "SPI0_MISO": 22, + "SPI0_MOSI": 23, + "SPI0_SCK": 18, + "SPI1_CS": 19, + "SPI1_MISO": 22, + "SPI1_MOSI": 23, + "SPI1_SCK": 18, + "WIRE0_SCL_0": 22, + "WIRE0_SCL_1": 29, + "WIRE0_SDA_0": 19, + "WIRE0_SDA_1": 30, + "WIRE1_SCL": 18, + "WIRE1_SDA": 23, + "SERIAL0_CTS": 19, + "SERIAL0_RTS": 22, + "SERIAL0_RX": 18, + "SERIAL0_TX": 23, + "SERIAL2_RX": 29, + "SERIAL2_TX": 30, + "ADC1": 19, + "ADC2": 41, + "CS0": 19, + "CS1": 19, + "CTS0": 19, + "MISO0": 22, + "MISO1": 22, + "MOSI0": 23, + "MOSI1": 23, + "PA00": 0, + "PA0": 0, + "PA05": 5, + "PA5": 5, + "PA12": 12, + "PA14": 14, + "PA15": 15, + "PA18": 18, + "PA19": 19, + "PA22": 22, + "PA23": 23, + "PA29": 29, + "PA30": 30, + "PWM0": 23, + "PWM1": 15, + "PWM2": 0, + "PWM3": 12, + "PWM4": 5, + "PWM5": 22, + "RTS0": 22, + "RX0": 18, + "RX2": 29, + "SCK0": 18, + "SCK1": 18, + "SCL0": 29, + "SCL1": 18, + "SDA0": 30, + "SDA1": 23, + "TX0": 23, + "TX2": 30, + "D0": 22, + "D1": 19, + "D2": 14, + "D3": 15, + "D4": 0, + "D5": 29, + "D6": 30, + "D7": 5, + "D8": 12, + "D9": 18, + "D10": 23, + "A0": 19, + "A1": 41, + }, + "wr3le": { + "SPI0_CS": 19, + "SPI0_MISO": 22, + "SPI0_MOSI": 23, + "SPI0_SCK": 18, + "SPI1_CS": 19, + "SPI1_MISO": 22, + "SPI1_MOSI": 23, + "SPI1_SCK": 18, + "WIRE0_SCL_0": 29, + "WIRE0_SCL_1": 22, + "WIRE0_SDA_0": 30, + "WIRE0_SDA_1": 19, + "WIRE1_SCL": 18, + "WIRE1_SDA": 23, + "SERIAL0_CTS": 19, + "SERIAL0_RTS": 22, + "SERIAL0_RX": 18, + "SERIAL0_TX": 23, + "SERIAL2_RX": 29, + "SERIAL2_TX": 30, + "ADC1": 19, + "ADC2": 41, + "CS0": 19, + "CS1": 19, + "CTS0": 19, + "MISO0": 22, + "MISO1": 22, + "MOSI0": 23, + "MOSI1": 23, + "PA00": 0, + "PA0": 0, + "PA05": 5, + "PA5": 5, + "PA12": 12, + "PA14": 14, + "PA15": 15, + "PA18": 18, + "PA19": 19, + "PA22": 22, + "PA23": 23, + "PA29": 29, + "PA30": 30, + "PWM0": 23, + "PWM1": 15, + "PWM2": 0, + "PWM3": 12, + "PWM4": 5, + "PWM5": 22, + "RTS0": 22, + "RX0": 18, + "RX2": 29, + "SCK0": 18, + "SCK1": 18, + "SCL0": 22, + "SCL1": 18, + "SDA0": 19, + "SDA1": 23, + "TX0": 23, + "TX2": 30, + "D0": 29, + "D1": 14, + "D2": 15, + "D3": 22, + "D4": 0, + "D5": 30, + "D6": 19, + "D7": 5, + "D8": 12, + "D9": 18, + "D10": 23, + "A0": 19, + "A1": 41, + }, + "wr3n": { + "WIRE0_SCL": 29, + "WIRE0_SDA": 30, + "WIRE1_SCL": 18, + "WIRE1_SDA": 23, + "SERIAL0_RX": 18, + "SERIAL0_TX": 23, + "SERIAL2_RX": 29, + "SERIAL2_TX": 30, + "ADC2": 41, + "MOSI0": 23, + "MOSI1": 23, + "PA00": 0, + "PA0": 0, + "PA05": 5, + "PA5": 5, + "PA12": 12, + "PA14": 14, + "PA15": 15, + "PA18": 18, + "PA23": 23, + "PA29": 29, + "PA30": 30, + "PWM0": 23, + "PWM1": 15, + "PWM2": 0, + "PWM3": 12, + "PWM4": 5, + "RX0": 18, + "RX2": 29, + "SCK0": 18, + "SCK1": 18, + "SCL0": 29, + "SCL1": 18, + "SDA0": 30, + "SDA1": 23, + "TX0": 23, + "TX2": 30, + "D0": 29, + "D1": 14, + "D2": 15, + "D3": 0, + "D4": 30, + "D5": 5, + "D6": 12, + "D7": 18, + "D8": 23, + "A1": 41, + }, +} + +BOARDS = RTL87XX_BOARDS diff --git a/esphome/components/sntp/sntp_component.cpp b/esphome/components/sntp/sntp_component.cpp index 3af21a9b23..418eacd870 100644 --- a/esphome/components/sntp/sntp_component.cpp +++ b/esphome/components/sntp/sntp_component.cpp @@ -1,7 +1,7 @@ #include "sntp_component.h" #include "esphome/core/log.h" -#ifdef USE_ESP32 +#if defined(USE_ESP32) || defined(USE_LIBRETINY) #include "lwip/apps/sntp.h" #ifdef USE_ESP_IDF #include "esp_sntp.h" @@ -26,7 +26,7 @@ static const char *const TAG = "sntp"; void SNTPComponent::setup() { ESP_LOGCONFIG(TAG, "Setting up SNTP..."); -#ifdef USE_ESP32 +#if defined(USE_ESP32) || defined(USE_LIBRETINY) if (sntp_enabled()) { sntp_stop(); } diff --git a/esphome/components/socket/__init__.py b/esphome/components/socket/__init__.py index 1757ec4668..19952aeece 100644 --- a/esphome/components/socket/__init__.py +++ b/esphome/components/socket/__init__.py @@ -5,6 +5,7 @@ CODEOWNERS = ["@esphome/core"] CONF_IMPLEMENTATION = "implementation" IMPLEMENTATION_LWIP_TCP = "lwip_tcp" +IMPLEMENTATION_LWIP_SOCKETS = "lwip_sockets" IMPLEMENTATION_BSD_SOCKETS = "bsd_sockets" CONFIG_SCHEMA = cv.Schema( @@ -14,9 +15,15 @@ CONFIG_SCHEMA = cv.Schema( esp8266=IMPLEMENTATION_LWIP_TCP, esp32=IMPLEMENTATION_BSD_SOCKETS, rp2040=IMPLEMENTATION_LWIP_TCP, + bk72xx=IMPLEMENTATION_LWIP_SOCKETS, + rtl87xx=IMPLEMENTATION_LWIP_SOCKETS, host=IMPLEMENTATION_BSD_SOCKETS, ): cv.one_of( - IMPLEMENTATION_LWIP_TCP, IMPLEMENTATION_BSD_SOCKETS, lower=True, space="_" + IMPLEMENTATION_LWIP_TCP, + IMPLEMENTATION_LWIP_SOCKETS, + IMPLEMENTATION_BSD_SOCKETS, + lower=True, + space="_", ), } ) @@ -26,5 +33,7 @@ async def to_code(config): impl = config[CONF_IMPLEMENTATION] if impl == IMPLEMENTATION_LWIP_TCP: cg.add_define("USE_SOCKET_IMPL_LWIP_TCP") + elif impl == IMPLEMENTATION_LWIP_SOCKETS: + cg.add_define("USE_SOCKET_IMPL_LWIP_SOCKETS") elif impl == IMPLEMENTATION_BSD_SOCKETS: cg.add_define("USE_SOCKET_IMPL_BSD_SOCKETS") diff --git a/esphome/components/socket/headers.h b/esphome/components/socket/headers.h index 1922885ac0..032892072d 100644 --- a/esphome/components/socket/headers.h +++ b/esphome/components/socket/headers.h @@ -120,6 +120,35 @@ struct iovec { #endif // USE_SOCKET_IMPL_LWIP_TCP +#ifdef USE_SOCKET_IMPL_LWIP_SOCKETS + +// standard lwIP's compatibility macros will interfere +// with Socket class function names - disable the macros +// and use real function names instead +#undef LWIP_COMPAT_SOCKETS +#define LWIP_COMPAT_SOCKETS 0 + +#include "lwip/sockets.h" +#include + +#ifdef USE_ARDUINO +// arduino-esp32 declares a global var called INADDR_NONE which is replaced +// by the define +#ifdef INADDR_NONE +#undef INADDR_NONE +#endif +// not defined for ESP32 +using socklen_t = uint32_t; + +#define ESPHOME_INADDR_ANY ((uint32_t) 0x00000000UL) +#define ESPHOME_INADDR_NONE ((uint32_t) 0xFFFFFFFFUL) +#else // !USE_ESP32 +#define ESPHOME_INADDR_ANY INADDR_ANY +#define ESPHOME_INADDR_NONE INADDR_NONE +#endif + +#endif // USE_SOCKET_IMPL_LWIP_SOCKETS + #ifdef USE_SOCKET_IMPL_BSD_SOCKETS #include diff --git a/esphome/components/socket/lwip_sockets_impl.cpp b/esphome/components/socket/lwip_sockets_impl.cpp new file mode 100644 index 0000000000..eaf6ac2c6f --- /dev/null +++ b/esphome/components/socket/lwip_sockets_impl.cpp @@ -0,0 +1,115 @@ +#include "socket.h" +#include "esphome/core/defines.h" +#include "esphome/core/helpers.h" + +#ifdef USE_SOCKET_IMPL_LWIP_SOCKETS + +#include + +namespace esphome { +namespace socket { + +std::string format_sockaddr(const struct sockaddr_storage &storage) { + if (storage.ss_family == AF_INET) { + const struct sockaddr_in *addr = reinterpret_cast(&storage); + char buf[INET_ADDRSTRLEN]; + const char *ret = lwip_inet_ntop(AF_INET, &addr->sin_addr, buf, sizeof(buf)); + if (ret == nullptr) + return {}; + return std::string{buf}; + } +#if LWIP_IPV6 + else if (storage.ss_family == AF_INET6) { + const struct sockaddr_in6 *addr = reinterpret_cast(&storage); + char buf[INET6_ADDRSTRLEN]; + const char *ret = lwip_inet_ntop(AF_INET6, &addr->sin6_addr, buf, sizeof(buf)); + if (ret == nullptr) + return {}; + return std::string{buf}; + } +#endif + return {}; +} + +class LwIPSocketImpl : public Socket { + public: + LwIPSocketImpl(int fd) : fd_(fd) {} + ~LwIPSocketImpl() override { + if (!closed_) { + close(); // NOLINT(clang-analyzer-optin.cplusplus.VirtualCall) + } + } + std::unique_ptr accept(struct sockaddr *addr, socklen_t *addrlen) override { + int fd = lwip_accept(fd_, addr, addrlen); + if (fd == -1) + return {}; + return make_unique(fd); + } + int bind(const struct sockaddr *addr, socklen_t addrlen) override { return lwip_bind(fd_, addr, addrlen); } + int close() override { + int ret = lwip_close(fd_); + closed_ = true; + return ret; + } + int shutdown(int how) override { return lwip_shutdown(fd_, how); } + + int getpeername(struct sockaddr *addr, socklen_t *addrlen) override { return lwip_getpeername(fd_, addr, addrlen); } + std::string getpeername() override { + struct sockaddr_storage storage; + socklen_t len = sizeof(storage); + int err = this->getpeername((struct sockaddr *) &storage, &len); + if (err != 0) + return {}; + return format_sockaddr(storage); + } + int getsockname(struct sockaddr *addr, socklen_t *addrlen) override { return lwip_getsockname(fd_, addr, addrlen); } + std::string getsockname() override { + struct sockaddr_storage storage; + socklen_t len = sizeof(storage); + int err = this->getsockname((struct sockaddr *) &storage, &len); + if (err != 0) + return {}; + return format_sockaddr(storage); + } + int getsockopt(int level, int optname, void *optval, socklen_t *optlen) override { + return lwip_getsockopt(fd_, level, optname, optval, optlen); + } + int setsockopt(int level, int optname, const void *optval, socklen_t optlen) override { + return lwip_setsockopt(fd_, level, optname, optval, optlen); + } + int listen(int backlog) override { return lwip_listen(fd_, backlog); } + ssize_t read(void *buf, size_t len) override { return lwip_read(fd_, buf, len); } + ssize_t readv(const struct iovec *iov, int iovcnt) override { return lwip_readv(fd_, iov, iovcnt); } + ssize_t write(const void *buf, size_t len) override { return lwip_write(fd_, buf, len); } + ssize_t send(void *buf, size_t len, int flags) { return lwip_send(fd_, buf, len, flags); } + ssize_t writev(const struct iovec *iov, int iovcnt) override { return lwip_writev(fd_, iov, iovcnt); } + ssize_t sendto(const void *buf, size_t len, int flags, const struct sockaddr *to, socklen_t tolen) override { + return lwip_sendto(fd_, buf, len, flags, to, tolen); + } + int setblocking(bool blocking) override { + int fl = lwip_fcntl(fd_, F_GETFL, 0); + if (blocking) { + fl &= ~O_NONBLOCK; + } else { + fl |= O_NONBLOCK; + } + lwip_fcntl(fd_, F_SETFL, fl); + return 0; + } + + protected: + int fd_; + bool closed_ = false; +}; + +std::unique_ptr socket(int domain, int type, int protocol) { + int ret = lwip_socket(domain, type, protocol); + if (ret == -1) + return nullptr; + return std::unique_ptr{new LwIPSocketImpl(ret)}; +} + +} // namespace socket +} // namespace esphome + +#endif // USE_SOCKET_IMPL_LWIP_SOCKETS diff --git a/esphome/components/spi/__init__.py b/esphome/components/spi/__init__.py index 57a4fa9f4e..a2ef956200 100644 --- a/esphome/components/spi/__init__.py +++ b/esphome/components/spi/__init__.py @@ -48,6 +48,7 @@ CONFIG_SCHEMA = cv.All( } ), cv.has_at_least_one_key(CONF_MISO_PIN, CONF_MOSI_PIN), + cv.only_on(["esp32", "esp8266", "rp2040"]), ) diff --git a/esphome/components/uart/__init__.py b/esphome/components/uart/__init__.py index aea59d9d8b..36f2bb5851 100644 --- a/esphome/components/uart/__init__.py +++ b/esphome/components/uart/__init__.py @@ -42,6 +42,9 @@ ESP8266UartComponent = uart_ns.class_( "ESP8266UartComponent", UARTComponent, cg.Component ) RP2040UartComponent = uart_ns.class_("RP2040UartComponent", UARTComponent, cg.Component) +LibreTinyUARTComponent = uart_ns.class_( + "LibreTinyUARTComponent", UARTComponent, cg.Component +) UARTDevice = uart_ns.class_("UARTDevice") UARTWriteAction = uart_ns.class_("UARTWriteAction", automation.Action) @@ -92,6 +95,8 @@ def _uart_declare_type(value): return cv.declare_id(IDFUARTComponent)(value) if CORE.is_rp2040: return cv.declare_id(RP2040UartComponent)(value) + if CORE.is_libretiny: + return cv.declare_id(LibreTinyUARTComponent)(value) raise NotImplementedError diff --git a/esphome/components/uart/uart_component_libretiny.cpp b/esphome/components/uart/uart_component_libretiny.cpp new file mode 100644 index 0000000000..c5e299e9d1 --- /dev/null +++ b/esphome/components/uart/uart_component_libretiny.cpp @@ -0,0 +1,168 @@ +#ifdef USE_LIBRETINY + +#include "esphome/core/application.h" +#include "esphome/core/defines.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" +#include "uart_component_libretiny.h" + +#ifdef USE_LOGGER +#include "esphome/components/logger/logger.h" +#endif + +#if LT_ARD_HAS_SOFTSERIAL +#include +#endif + +namespace esphome { +namespace uart { + +static const char *const TAG = "uart.lt"; + +static const char *UART_TYPE[] = { + "hardware", + "software", +}; + +uint16_t LibreTinyUARTComponent::get_config() { + uint16_t config = 0; + + switch (this->parity_) { + case UART_CONFIG_PARITY_NONE: + config |= SERIAL_PARITY_NONE; + break; + case UART_CONFIG_PARITY_EVEN: + config |= SERIAL_PARITY_EVEN; + break; + case UART_CONFIG_PARITY_ODD: + config |= SERIAL_PARITY_ODD; + break; + } + + config |= (this->data_bits_ - 4) << 8; + config |= 0x10 + (this->stop_bits_ - 1) * 0x20; + + return config; +} + +void LibreTinyUARTComponent::setup() { + ESP_LOGCONFIG(TAG, "Setting up UART..."); + + int8_t tx_pin = tx_pin_ == nullptr ? -1 : tx_pin_->get_pin(); + int8_t rx_pin = rx_pin_ == nullptr ? -1 : rx_pin_->get_pin(); + bool tx_inverted = tx_pin_ != nullptr && tx_pin_->is_inverted(); + bool rx_inverted = rx_pin_ != nullptr && rx_pin_->is_inverted(); + + if (false) + return; +#if LT_HW_UART0 + else if ((tx_pin == -1 || tx_pin == PIN_SERIAL0_TX) && (rx_pin == -1 || rx_pin == PIN_SERIAL0_RX)) { + this->serial_ = &Serial0; + this->hardware_idx_ = 0; + } +#endif +#if LT_HW_UART1 + else if ((tx_pin == -1 || tx_pin == PIN_SERIAL1_TX) && (rx_pin == -1 || rx_pin == PIN_SERIAL1_RX)) { + this->serial_ = &Serial1; + this->hardware_idx_ = 1; + } +#endif +#if LT_HW_UART2 + else if ((tx_pin == -1 || tx_pin == PIN_SERIAL2_TX) && (rx_pin == -1 || rx_pin == PIN_SERIAL2_RX)) { + this->serial_ = &Serial2; + this->hardware_idx_ = 2; + } +#endif + else { +#if LT_ARD_HAS_SOFTSERIAL + this->serial_ = new SoftwareSerial(rx_pin, tx_pin, rx_inverted || tx_inverted); +#else + this->serial_ = &Serial; + ESP_LOGE(TAG, " SoftwareSerial is not implemented for this chip. Only hardware pins are supported:"); +#if LT_HW_UART0 + ESP_LOGE(TAG, " TX=%u, RX=%u", PIN_SERIAL0_TX, PIN_SERIAL0_RX); +#endif +#if LT_HW_UART1 + ESP_LOGE(TAG, " TX=%u, RX=%u", PIN_SERIAL1_TX, PIN_SERIAL1_RX); +#endif +#if LT_HW_UART2 + ESP_LOGE(TAG, " TX=%u, RX=%u", PIN_SERIAL2_TX, PIN_SERIAL2_RX); +#endif + this->mark_failed(); + return; +#endif + } + + this->serial_->begin(this->baud_rate_, get_config()); +} + +void LibreTinyUARTComponent::dump_config() { + bool is_software = this->hardware_idx_ == -1; + ESP_LOGCONFIG(TAG, "UART Bus:"); + ESP_LOGCONFIG(TAG, " Type: %s", UART_TYPE[is_software]); + if (!is_software) { + ESP_LOGCONFIG(TAG, " Port number: %d", this->hardware_idx_); + } + 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 LibreTinyUARTComponent::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 LibreTinyUARTComponent::peek_byte(uint8_t *data) { + if (!this->check_read_timeout_()) + return false; + *data = this->serial_->peek(); + return true; +} + +bool LibreTinyUARTComponent::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 LibreTinyUARTComponent::available() { return this->serial_->available(); } +void LibreTinyUARTComponent::flush() { + ESP_LOGVV(TAG, " Flushing..."); + this->serial_->flush(); +} + +void LibreTinyUARTComponent::check_logger_conflict() { +#ifdef USE_LOGGER + if (this->hardware_idx_ == -1 || logger::global_logger->get_baud_rate() == 0) { + return; + } + + if (this->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_LIBRETINY diff --git a/esphome/components/uart/uart_component_libretiny.h b/esphome/components/uart/uart_component_libretiny.h new file mode 100644 index 0000000000..00982fd297 --- /dev/null +++ b/esphome/components/uart/uart_component_libretiny.h @@ -0,0 +1,43 @@ +#pragma once + +#ifdef USE_LIBRETINY + +#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 LibreTinyUARTComponent : 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(); + + HardwareSerial *get_hw_serial() { return this->serial_; } + int8_t get_hw_serial_number() { return this->hardware_idx_; } + + protected: + void check_logger_conflict() override; + + HardwareSerial *serial_{nullptr}; + int8_t hardware_idx_{-1}; +}; + +} // namespace uart +} // namespace esphome + +#endif // USE_LIBRETINY diff --git a/esphome/components/web_server/__init__.py b/esphome/components/web_server/__init__.py index b1cf8a5de6..c6d9c31e93 100644 --- a/esphome/components/web_server/__init__.py +++ b/esphome/components/web_server/__init__.py @@ -79,13 +79,18 @@ CONFIG_SCHEMA = cv.All( ), cv.Optional(CONF_INCLUDE_INTERNAL, default=False): cv.boolean, cv.SplitDefault( - CONF_OTA, esp8266=True, esp32_arduino=True, esp32_idf=False + CONF_OTA, + esp8266=True, + esp32_arduino=True, + esp32_idf=False, + bk72xx=True, + rtl87xx=True, ): cv.boolean, cv.Optional(CONF_LOG, default=True): cv.boolean, cv.Optional(CONF_LOCAL): cv.boolean, } ).extend(cv.COMPONENT_SCHEMA), - cv.only_on(["esp32", "esp8266"]), + cv.only_on(["esp32", "esp8266", "bk72xx", "rtl87xx"]), default_url, validate_local, validate_ota, diff --git a/esphome/components/web_server_base/__init__.py b/esphome/components/web_server_base/__init__.py index 87f23a990a..6491446bcc 100644 --- a/esphome/components/web_server_base/__init__.py +++ b/esphome/components/web_server_base/__init__.py @@ -37,4 +37,4 @@ async def to_code(config): cg.add_library("FS", None) cg.add_library("Update", None) # https://github.com/esphome/ESPAsyncWebServer/blob/master/library.json - cg.add_library("esphome/ESPAsyncWebServer-esphome", "2.1.0") + cg.add_library("esphome/ESPAsyncWebServer-esphome", "3.1.0") diff --git a/esphome/components/web_server_base/web_server_base.cpp b/esphome/components/web_server_base/web_server_base.cpp index 997ce0798a..f90c7e56a3 100644 --- a/esphome/components/web_server_base/web_server_base.cpp +++ b/esphome/components/web_server_base/web_server_base.cpp @@ -5,7 +5,7 @@ #ifdef USE_ARDUINO #include -#ifdef USE_ESP32 +#if defined(USE_ESP32) || defined(USE_LIBRETINY) #include #endif #ifdef USE_ESP8266 @@ -50,7 +50,7 @@ void OTARequestHandler::handleUpload(AsyncWebServerRequest *request, const Strin // NOLINTNEXTLINE(readability-static-accessed-through-instance) success = Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000); #endif -#ifdef USE_ESP32_FRAMEWORK_ARDUINO +#if defined(USE_ESP32_FRAMEWORK_ARDUINO) || defined(USE_LIBRETINY) if (Update.isRunning()) { Update.abort(); } diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py index 068d015732..1baffcbfcc 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -272,7 +272,12 @@ CONFIG_SCHEMA = cv.All( CONF_REBOOT_TIMEOUT, default="15min" ): cv.positive_time_period_milliseconds, cv.SplitDefault( - CONF_POWER_SAVE_MODE, esp8266="none", esp32="light", rp2040="light" + CONF_POWER_SAVE_MODE, + esp8266="none", + esp32="light", + rp2040="light", + bk72xx="none", + rtl87xx="none", ): cv.enum(WIFI_POWER_SAVE_MODES, upper=True), cv.Optional(CONF_FAST_CONNECT, default=False): cv.boolean, cv.Optional(CONF_USE_ADDRESS): cv.string_strict, diff --git a/esphome/components/wifi/wifi_component.h b/esphome/components/wifi/wifi_component.h index c17246fd00..b418a5b353 100644 --- a/esphome/components/wifi/wifi_component.h +++ b/esphome/components/wifi/wifi_component.h @@ -15,6 +15,10 @@ #include #endif +#ifdef USE_LIBRETINY +#include +#endif + #ifdef USE_ESP8266 #include #include @@ -336,6 +340,11 @@ class WiFiComponent : public Component { void wifi_scan_result(void *env, const cyw43_ev_scan_result_t *result); #endif +#ifdef USE_LIBRETINY + void wifi_event_callback_(arduino_event_id_t event, arduino_event_info_t info); + void wifi_scan_done_callback_(); +#endif + std::string use_address_; std::vector sta_; std::vector sta_priorities_; diff --git a/esphome/components/wifi/wifi_component_libretiny.cpp b/esphome/components/wifi/wifi_component_libretiny.cpp new file mode 100644 index 0000000000..abad5aca9c --- /dev/null +++ b/esphome/components/wifi/wifi_component_libretiny.cpp @@ -0,0 +1,467 @@ +#include "wifi_component.h" + +#ifdef USE_LIBRETINY + +#include +#include +#include "lwip/ip_addr.h" +#include "lwip/err.h" +#include "lwip/dns.h" + +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" +#include "esphome/core/hal.h" +#include "esphome/core/application.h" +#include "esphome/core/util.h" + +namespace esphome { +namespace wifi { + +static const char *const TAG = "wifi_lt"; + +static bool s_sta_connecting = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + +bool WiFiComponent::wifi_mode_(optional sta, optional ap) { + uint8_t current_mode = WiFi.getMode(); + bool current_sta = current_mode & 0b01; + bool current_ap = current_mode & 0b10; + bool enable_sta = sta.value_or(current_sta); + bool enable_ap = ap.value_or(current_ap); + if (current_sta == enable_sta && current_ap == enable_ap) + return true; + + if (enable_sta && !current_sta) { + ESP_LOGV(TAG, "Enabling STA."); + } else if (!enable_sta && current_sta) { + ESP_LOGV(TAG, "Disabling STA."); + } + if (enable_ap && !current_ap) { + ESP_LOGV(TAG, "Enabling AP."); + } else if (!enable_ap && current_ap) { + ESP_LOGV(TAG, "Disabling AP."); + } + + uint8_t mode = 0; + if (enable_sta) + mode |= 0b01; + if (enable_ap) + mode |= 0b10; + bool ret = WiFi.mode(static_cast(mode)); + + if (!ret) { + ESP_LOGW(TAG, "Setting WiFi mode failed!"); + } + + return ret; +} +bool WiFiComponent::wifi_apply_output_power_(float output_power) { + int8_t val = static_cast(output_power * 4); + return WiFi.setTxPower(val); +} +bool WiFiComponent::wifi_sta_pre_setup_() { + if (!this->wifi_mode_(true, {})) + return false; + + WiFi.setAutoReconnect(false); + delay(10); + return true; +} +bool WiFiComponent::wifi_apply_power_save_() { return WiFi.setSleep(this->power_save_ != WIFI_POWER_SAVE_NONE); } +bool WiFiComponent::wifi_sta_ip_config_(optional manual_ip) { + // enable STA + if (!this->wifi_mode_(true, {})) + return false; + + if (!manual_ip.has_value()) { + return true; + } + + WiFi.config(static_cast(manual_ip->static_ip), static_cast(manual_ip->gateway), + static_cast(manual_ip->subnet), static_cast(manual_ip->dns1), + static_cast(manual_ip->dns2)); + + return true; +} + +network::IPAddress WiFiComponent::wifi_sta_ip() { + if (!this->has_sta()) + return {}; + return {WiFi.localIP()}; +} + +bool WiFiComponent::wifi_apply_hostname_() { + // setting is done in SYSTEM_EVENT_STA_START callback too + WiFi.setHostname(App.get_name().c_str()); + return true; +} +bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { + // enable STA + if (!this->wifi_mode_(true, {})) + return false; + + String ssid = WiFi.SSID(); + if (ssid && strcmp(ssid.c_str(), ap.get_ssid().c_str()) != 0) { + WiFi.disconnect(); + } + + if (!this->wifi_sta_ip_config_(ap.get_manual_ip())) { + return false; + } + + this->wifi_apply_hostname_(); + + s_sta_connecting = true; + + WiFiStatus status = WiFi.begin(ap.get_ssid().c_str(), ap.get_password().empty() ? NULL : ap.get_password().c_str(), + ap.get_channel().has_value() ? *ap.get_channel() : 0, + ap.get_bssid().has_value() ? ap.get_bssid()->data() : NULL); + if (status != WL_CONNECTED) { + ESP_LOGW(TAG, "esp_wifi_connect failed! %d", status); + return false; + } + + return true; +} +const char *get_auth_mode_str(uint8_t mode) { + switch (mode) { + case WIFI_AUTH_OPEN: + return "OPEN"; + case WIFI_AUTH_WEP: + return "WEP"; + case WIFI_AUTH_WPA_PSK: + return "WPA PSK"; + case WIFI_AUTH_WPA2_PSK: + return "WPA2 PSK"; + case WIFI_AUTH_WPA_WPA2_PSK: + return "WPA/WPA2 PSK"; + default: + return "UNKNOWN"; + } +} + +using esphome_ip4_addr_t = IPAddress; + +std::string format_ip4_addr(const esphome_ip4_addr_t &ip) { + char buf[20]; + uint32_t addr = ip; + sprintf(buf, "%u.%u.%u.%u", uint8_t(addr >> 0), uint8_t(addr >> 8), uint8_t(addr >> 16), uint8_t(addr >> 24)); + return buf; +} +const char *get_op_mode_str(uint8_t mode) { + switch (mode) { + case WIFI_OFF: + return "OFF"; + case WIFI_STA: + return "STA"; + case WIFI_AP: + return "AP"; + case WIFI_AP_STA: + return "AP+STA"; + default: + return "UNKNOWN"; + } +} +const char *get_disconnect_reason_str(uint8_t reason) { + switch (reason) { + case WIFI_REASON_AUTH_EXPIRE: + return "Auth Expired"; + case WIFI_REASON_AUTH_LEAVE: + return "Auth Leave"; + case WIFI_REASON_ASSOC_EXPIRE: + return "Association Expired"; + case WIFI_REASON_ASSOC_TOOMANY: + return "Too Many Associations"; + case WIFI_REASON_NOT_AUTHED: + return "Not Authenticated"; + case WIFI_REASON_NOT_ASSOCED: + return "Not Associated"; + case WIFI_REASON_ASSOC_LEAVE: + return "Association Leave"; + case WIFI_REASON_ASSOC_NOT_AUTHED: + return "Association not Authenticated"; + case WIFI_REASON_DISASSOC_PWRCAP_BAD: + return "Disassociate Power Cap Bad"; + case WIFI_REASON_DISASSOC_SUPCHAN_BAD: + return "Disassociate Supported Channel Bad"; + case WIFI_REASON_IE_INVALID: + return "IE Invalid"; + case WIFI_REASON_MIC_FAILURE: + return "Mic Failure"; + case WIFI_REASON_4WAY_HANDSHAKE_TIMEOUT: + return "4-Way Handshake Timeout"; + case WIFI_REASON_GROUP_KEY_UPDATE_TIMEOUT: + return "Group Key Update Timeout"; + case WIFI_REASON_IE_IN_4WAY_DIFFERS: + return "IE In 4-Way Handshake Differs"; + case WIFI_REASON_GROUP_CIPHER_INVALID: + return "Group Cipher Invalid"; + case WIFI_REASON_PAIRWISE_CIPHER_INVALID: + return "Pairwise Cipher Invalid"; + case WIFI_REASON_AKMP_INVALID: + return "AKMP Invalid"; + case WIFI_REASON_UNSUPP_RSN_IE_VERSION: + return "Unsupported RSN IE version"; + case WIFI_REASON_INVALID_RSN_IE_CAP: + return "Invalid RSN IE Cap"; + case WIFI_REASON_802_1X_AUTH_FAILED: + return "802.1x Authentication Failed"; + case WIFI_REASON_CIPHER_SUITE_REJECTED: + return "Cipher Suite Rejected"; + case WIFI_REASON_BEACON_TIMEOUT: + return "Beacon Timeout"; + case WIFI_REASON_NO_AP_FOUND: + return "AP Not Found"; + case WIFI_REASON_AUTH_FAIL: + return "Authentication Failed"; + case WIFI_REASON_ASSOC_FAIL: + return "Association Failed"; + case WIFI_REASON_HANDSHAKE_TIMEOUT: + return "Handshake Failed"; + case WIFI_REASON_CONNECTION_FAIL: + return "Connection Failed"; + case WIFI_REASON_UNSPECIFIED: + default: + return "Unspecified"; + } +} + +#define ESPHOME_EVENT_ID_WIFI_READY ARDUINO_EVENT_WIFI_READY +#define ESPHOME_EVENT_ID_WIFI_SCAN_DONE ARDUINO_EVENT_WIFI_SCAN_DONE +#define ESPHOME_EVENT_ID_WIFI_STA_START ARDUINO_EVENT_WIFI_STA_START +#define ESPHOME_EVENT_ID_WIFI_STA_STOP ARDUINO_EVENT_WIFI_STA_STOP +#define ESPHOME_EVENT_ID_WIFI_STA_CONNECTED ARDUINO_EVENT_WIFI_STA_CONNECTED +#define ESPHOME_EVENT_ID_WIFI_STA_DISCONNECTED ARDUINO_EVENT_WIFI_STA_DISCONNECTED +#define ESPHOME_EVENT_ID_WIFI_STA_AUTHMODE_CHANGE ARDUINO_EVENT_WIFI_STA_AUTHMODE_CHANGE +#define ESPHOME_EVENT_ID_WIFI_STA_GOT_IP ARDUINO_EVENT_WIFI_STA_GOT_IP +#define ESPHOME_EVENT_ID_WIFI_STA_GOT_IP6 ARDUINO_EVENT_WIFI_STA_GOT_IP6 +#define ESPHOME_EVENT_ID_WIFI_STA_LOST_IP ARDUINO_EVENT_WIFI_STA_LOST_IP +#define ESPHOME_EVENT_ID_WIFI_AP_START ARDUINO_EVENT_WIFI_AP_START +#define ESPHOME_EVENT_ID_WIFI_AP_STOP ARDUINO_EVENT_WIFI_AP_STOP +#define ESPHOME_EVENT_ID_WIFI_AP_STACONNECTED ARDUINO_EVENT_WIFI_AP_STACONNECTED +#define ESPHOME_EVENT_ID_WIFI_AP_STADISCONNECTED ARDUINO_EVENT_WIFI_AP_STADISCONNECTED +#define ESPHOME_EVENT_ID_WIFI_AP_STAIPASSIGNED ARDUINO_EVENT_WIFI_AP_STAIPASSIGNED +#define ESPHOME_EVENT_ID_WIFI_AP_PROBEREQRECVED ARDUINO_EVENT_WIFI_AP_PROBEREQRECVED +#define ESPHOME_EVENT_ID_WIFI_AP_GOT_IP6 ARDUINO_EVENT_WIFI_AP_GOT_IP6 +using esphome_wifi_event_id_t = arduino_event_id_t; +using esphome_wifi_event_info_t = arduino_event_info_t; + +void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_wifi_event_info_t info) { + switch (event) { + case ESPHOME_EVENT_ID_WIFI_READY: { + ESP_LOGV(TAG, "Event: WiFi ready"); + break; + } + case ESPHOME_EVENT_ID_WIFI_SCAN_DONE: { + auto it = info.wifi_scan_done; + ESP_LOGV(TAG, "Event: WiFi Scan Done status=%u number=%u scan_id=%u", it.status, it.number, it.scan_id); + + this->wifi_scan_done_callback_(); + break; + } + case ESPHOME_EVENT_ID_WIFI_STA_START: { + ESP_LOGV(TAG, "Event: WiFi STA start"); + WiFi.setHostname(App.get_name().c_str()); + break; + } + case ESPHOME_EVENT_ID_WIFI_STA_STOP: { + ESP_LOGV(TAG, "Event: WiFi STA stop"); + break; + } + case ESPHOME_EVENT_ID_WIFI_STA_CONNECTED: { + auto it = info.wifi_sta_connected; + char buf[33]; + memcpy(buf, it.ssid, it.ssid_len); + buf[it.ssid_len] = '\0'; + ESP_LOGV(TAG, "Event: Connected ssid='%s' bssid=" LOG_SECRET("%s") " channel=%u, authmode=%s", buf, + format_mac_addr(it.bssid).c_str(), it.channel, get_auth_mode_str(it.authmode)); + + break; + } + case ESPHOME_EVENT_ID_WIFI_STA_DISCONNECTED: { + auto it = info.wifi_sta_disconnected; + char buf[33]; + memcpy(buf, it.ssid, it.ssid_len); + buf[it.ssid_len] = '\0'; + if (it.reason == WIFI_REASON_NO_AP_FOUND) { + ESP_LOGW(TAG, "Event: Disconnected ssid='%s' reason='Probe Request Unsuccessful'", buf); + } else { + ESP_LOGW(TAG, "Event: Disconnected ssid='%s' bssid=" LOG_SECRET("%s") " reason='%s'", buf, + format_mac_addr(it.bssid).c_str(), get_disconnect_reason_str(it.reason)); + } + + uint8_t reason = it.reason; + if (reason == WIFI_REASON_AUTH_EXPIRE || reason == WIFI_REASON_BEACON_TIMEOUT || + reason == WIFI_REASON_NO_AP_FOUND || reason == WIFI_REASON_ASSOC_FAIL || + reason == WIFI_REASON_HANDSHAKE_TIMEOUT) { + WiFi.disconnect(); + this->error_from_callback_ = true; + } + + s_sta_connecting = false; + break; + } + case ESPHOME_EVENT_ID_WIFI_STA_AUTHMODE_CHANGE: { + auto it = info.wifi_sta_authmode_change; + ESP_LOGV(TAG, "Event: Authmode Change old=%s new=%s", get_auth_mode_str(it.old_mode), + get_auth_mode_str(it.new_mode)); + // Mitigate CVE-2020-12638 + // https://lbsfilm.at/blog/wpa2-authenticationmode-downgrade-in-espressif-microprocessors + if (it.old_mode != WIFI_AUTH_OPEN && it.new_mode == WIFI_AUTH_OPEN) { + ESP_LOGW(TAG, "Potential Authmode downgrade detected, disconnecting..."); + // we can't call retry_connect() from this context, so disconnect immediately + // and notify main thread with error_from_callback_ + WiFi.disconnect(); + this->error_from_callback_ = true; + } + break; + } + case ESPHOME_EVENT_ID_WIFI_STA_GOT_IP: { + // auto it = info.got_ip.ip_info; + ESP_LOGV(TAG, "Event: Got IP static_ip=%s gateway=%s", format_ip4_addr(WiFi.localIP()).c_str(), + format_ip4_addr(WiFi.gatewayIP()).c_str()); + s_sta_connecting = false; + break; + } + case ESPHOME_EVENT_ID_WIFI_STA_LOST_IP: { + ESP_LOGV(TAG, "Event: Lost IP"); + break; + } + case ESPHOME_EVENT_ID_WIFI_AP_START: { + ESP_LOGV(TAG, "Event: WiFi AP start"); + break; + } + case ESPHOME_EVENT_ID_WIFI_AP_STOP: { + ESP_LOGV(TAG, "Event: WiFi AP stop"); + break; + } + case ESPHOME_EVENT_ID_WIFI_AP_STACONNECTED: { + auto it = info.wifi_sta_connected; + auto &mac = it.bssid; + ESP_LOGV(TAG, "Event: AP client connected MAC=%s", format_mac_addr(mac).c_str()); + break; + } + case ESPHOME_EVENT_ID_WIFI_AP_STADISCONNECTED: { + auto it = info.wifi_sta_disconnected; + auto &mac = it.bssid; + ESP_LOGV(TAG, "Event: AP client disconnected MAC=%s", format_mac_addr(mac).c_str()); + break; + } + case ESPHOME_EVENT_ID_WIFI_AP_STAIPASSIGNED: { + ESP_LOGV(TAG, "Event: AP client assigned IP"); + break; + } + case ESPHOME_EVENT_ID_WIFI_AP_PROBEREQRECVED: { + auto it = info.wifi_ap_probereqrecved; + ESP_LOGVV(TAG, "Event: AP receive Probe Request MAC=%s RSSI=%d", format_mac_addr(it.mac).c_str(), it.rssi); + break; + } + default: + break; + } +} +void WiFiComponent::wifi_pre_setup_() { + auto f = std::bind(&WiFiComponent::wifi_event_callback_, this, std::placeholders::_1, std::placeholders::_2); + WiFi.onEvent(f); + // Make sure WiFi is in clean state before anything starts + this->wifi_mode_(false, false); +} +WiFiSTAConnectStatus WiFiComponent::wifi_sta_connect_status_() { + auto status = WiFi.status(); + if (status == WL_CONNECTED) { + return WiFiSTAConnectStatus::CONNECTED; + } else if (status == WL_CONNECT_FAILED || status == WL_CONNECTION_LOST) { + return WiFiSTAConnectStatus::ERROR_CONNECT_FAILED; + } else if (status == WL_NO_SSID_AVAIL) { + return WiFiSTAConnectStatus::ERROR_NETWORK_NOT_FOUND; + } else if (s_sta_connecting) { + return WiFiSTAConnectStatus::CONNECTING; + } + return WiFiSTAConnectStatus::IDLE; +} +bool WiFiComponent::wifi_scan_start_(bool passive) { + // enable STA + if (!this->wifi_mode_(true, {})) + return false; + + // need to use WiFi because of WiFiScanClass allocations :( + int16_t err = WiFi.scanNetworks(true, true, passive, 200); + if (err != WIFI_SCAN_RUNNING) { + ESP_LOGV(TAG, "WiFi.scanNetworks failed! %d", err); + return false; + } + + return true; +} +void WiFiComponent::wifi_scan_done_callback_() { + this->scan_result_.clear(); + + int16_t num = WiFi.scanComplete(); + if (num < 0) + return; + + this->scan_result_.reserve(static_cast(num)); + for (int i = 0; i < num; i++) { + String ssid = WiFi.SSID(i); + wifi_auth_mode_t authmode = WiFi.encryptionType(i); + int32_t rssi = WiFi.RSSI(i); + uint8_t *bssid = WiFi.BSSID(i); + int32_t channel = WiFi.channel(i); + + WiFiScanResult scan({bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5]}, std::string(ssid.c_str()), + channel, rssi, authmode != WIFI_AUTH_OPEN, ssid.length() == 0); + this->scan_result_.push_back(scan); + } + WiFi.scanDelete(); + this->scan_done_ = true; +} +bool WiFiComponent::wifi_ap_ip_config_(optional manual_ip) { + // enable AP + if (!this->wifi_mode_({}, true)) + return false; + + if (manual_ip.has_value()) { + return WiFi.softAPConfig(static_cast(manual_ip->static_ip), static_cast(manual_ip->gateway), + static_cast(manual_ip->subnet)); + } else { + return WiFi.softAPConfig(IPAddress(192, 168, 4, 1), IPAddress(192, 168, 4, 1), IPAddress(255, 255, 255, 0)); + } +} +bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) { + // enable AP + if (!this->wifi_mode_({}, true)) + return false; + + if (!this->wifi_ap_ip_config_(ap.get_manual_ip())) { + ESP_LOGV(TAG, "wifi_ap_ip_config_ failed!"); + return false; + } + + yield(); + + return WiFi.softAP(ap.get_ssid().c_str(), ap.get_password().empty() ? NULL : ap.get_password().c_str(), + ap.get_channel().value_or(1), ap.get_hidden()); +} +network::IPAddress WiFiComponent::wifi_soft_ap_ip() { return {WiFi.softAPIP()}; } +bool WiFiComponent::wifi_disconnect_() { return WiFi.disconnect(); } + +bssid_t WiFiComponent::wifi_bssid() { + bssid_t bssid{}; + uint8_t *raw_bssid = WiFi.BSSID(); + if (raw_bssid != nullptr) { + for (size_t i = 0; i < bssid.size(); i++) + bssid[i] = raw_bssid[i]; + } + return bssid; +} +std::string WiFiComponent::wifi_ssid() { return WiFi.SSID().c_str(); } +int8_t WiFiComponent::wifi_rssi() { return WiFi.RSSI(); } +int32_t WiFiComponent::wifi_channel_() { return WiFi.channel(); } +network::IPAddress WiFiComponent::wifi_subnet_mask_() { return {WiFi.subnetMask()}; } +network::IPAddress WiFiComponent::wifi_gateway_ip_() { return {WiFi.gatewayIP()}; } +network::IPAddress WiFiComponent::wifi_dns_ip_(int num) { return {WiFi.dnsIP(num)}; } +void WiFiComponent::wifi_loop_() {} + +} // namespace wifi +} // namespace esphome + +#endif // USE_LIBRETINY diff --git a/esphome/config_validation.py b/esphome/config_validation.py index ed87e98078..b3f24d9d17 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -1500,6 +1500,8 @@ class SplitDefault(Optional): esp32_arduino=vol.UNDEFINED, esp32_idf=vol.UNDEFINED, rp2040=vol.UNDEFINED, + bk72xx=vol.UNDEFINED, + rtl87xx=vol.UNDEFINED, host=vol.UNDEFINED, ): super().__init__(key) @@ -1511,6 +1513,8 @@ class SplitDefault(Optional): esp32_idf if esp32 is vol.UNDEFINED else esp32 ) self._rp2040_default = vol.default_factory(rp2040) + self._bk72xx_default = vol.default_factory(bk72xx) + self._rtl87xx_default = vol.default_factory(rtl87xx) self._host_default = vol.default_factory(host) @property @@ -1523,6 +1527,10 @@ class SplitDefault(Optional): return self._esp32_idf_default if CORE.is_rp2040: return self._rp2040_default + if CORE.is_bk72xx: + return self._bk72xx_default + if CORE.is_rtl87xx: + return self._rtl87xx_default if CORE.is_host: return self._host_default raise NotImplementedError diff --git a/esphome/const.py b/esphome/const.py index e0642247ab..d0575a6ebd 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -11,8 +11,19 @@ PLATFORM_ESP32 = "esp32" PLATFORM_ESP8266 = "esp8266" PLATFORM_RP2040 = "rp2040" PLATFORM_HOST = "host" +PLATFORM_BK72XX = "bk72xx" +PLATFORM_RTL87XX = "rtl87xx" +PLATFORM_LIBRETINY_OLDSTYLE = "libretiny" -TARGET_PLATFORMS = [PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_RP2040, PLATFORM_HOST] +TARGET_PLATFORMS = [ + PLATFORM_ESP32, + PLATFORM_ESP8266, + PLATFORM_RP2040, + PLATFORM_HOST, + PLATFORM_BK72XX, + PLATFORM_RTL87XX, + PLATFORM_LIBRETINY_OLDSTYLE, +] SOURCE_FILE_EXTENSIONS = {".cpp", ".hpp", ".h", ".c", ".tcc", ".ino"} HEADER_FILE_EXTENSIONS = {".h", ".hpp", ".tcc"} @@ -37,6 +48,7 @@ CONF_ADVANCED = "advanced" CONF_AFTER = "after" CONF_ALPHA = "alpha" CONF_ALTITUDE = "altitude" +CONF_ANALOG = "analog" CONF_AND = "and" CONF_AP = "ap" CONF_APPARENT_POWER = "apparent_power" @@ -835,6 +847,7 @@ ICON_BRIEFCASE_DOWNLOAD = "mdi:briefcase-download" ICON_BRIGHTNESS_5 = "mdi:brightness-5" ICON_BRIGHTNESS_6 = "mdi:brightness-6" ICON_BUG = "mdi:bug" +ICON_CELLPHONE_ARROW_DOWN = "mdi:cellphone-arrow-down" ICON_CHECK_CIRCLE_OUTLINE = "mdi:check-circle-outline" ICON_CHEMICAL_WEAPON = "mdi:chemical-weapon" ICON_CHIP = "mdi:chip" diff --git a/esphome/core/__init__.py b/esphome/core/__init__.py index 891936adc3..d9b1603894 100644 --- a/esphome/core/__init__.py +++ b/esphome/core/__init__.py @@ -584,6 +584,8 @@ class EsphomeCore: @property def firmware_bin(self): + if self.is_libretiny: + return self.relative_pioenvs_path(self.name, "firmware.uf2") return self.relative_pioenvs_path(self.name, "firmware.bin") @property @@ -602,6 +604,18 @@ class EsphomeCore: def is_rp2040(self): return self.target_platform == "rp2040" + @property + def is_bk72xx(self): + return self.target_platform == "bk72xx" + + @property + def is_rtl87xx(self): + return self.target_platform == "rtl87xx" + + @property + def is_libretiny(self): + return self.is_bk72xx or self.is_rtl87xx + @property def is_host(self): return self.target_platform == "host" diff --git a/esphome/core/config.py b/esphome/core/config.py index ef6553026e..a09252e4b4 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -380,7 +380,7 @@ async def to_code(config): cg.add_build_flag("-Wno-unused-but-set-variable") cg.add_build_flag("-Wno-sign-compare") - if CORE.using_arduino: + if CORE.using_arduino and not CORE.is_bk72xx: CORE.add_job(add_arduino_global_workaround) if config[CONF_INCLUDES]: diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 8d4d7e3f22..1e0df74eec 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -95,6 +95,10 @@ #define USE_HTTP_REQUEST_ESP8266_HTTPS #define USE_SOCKET_IMPL_LWIP_TCP +#ifdef USE_LIBRETINY +#define USE_SOCKET_IMPL_LWIP_SOCKETS +#endif + // Dummy firmware payload for shelly_dimmer #define USE_SHD_FIRMWARE_MAJOR_VERSION 56 #define USE_SHD_FIRMWARE_MINOR_VERSION 5 diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index c65928556a..714a1642f8 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -43,6 +43,10 @@ #include "esp_efuse_table.h" #endif +#ifdef USE_LIBRETINY +#include // for macAddress() +#endif + namespace esphome { static const char *const TAG = "helpers"; @@ -190,6 +194,8 @@ uint32_t random_uint32() { result |= rosc_hw->randombit; } return result; +#elif defined(USE_LIBRETINY) + return rand(); #elif defined(USE_HOST) std::random_device dev; std::mt19937 rng(dev()); @@ -216,6 +222,9 @@ bool random_bytes(uint8_t *data, size_t len) { *data++ = result; } return true; +#elif defined(USE_LIBRETINY) + lt_rand_bytes(data, len); + return true; #elif defined(USE_HOST) FILE *fp = fopen("/dev/urandom", "r"); if (fp == nullptr) { @@ -503,7 +512,7 @@ Mutex::Mutex() {} void Mutex::lock() {} bool Mutex::try_lock() { return true; } void Mutex::unlock() {} -#elif defined(USE_ESP32) +#elif defined(USE_ESP32) || defined(USE_LIBRETINY) Mutex::Mutex() { handle_ = xSemaphoreCreateMutex(); } void Mutex::lock() { xSemaphoreTake(this->handle_, portMAX_DELAY); } bool Mutex::try_lock() { return xSemaphoreTake(this->handle_, 0) == pdTRUE; } @@ -513,7 +522,7 @@ void Mutex::unlock() { xSemaphoreGive(this->handle_); } #if defined(USE_ESP8266) IRAM_ATTR InterruptLock::InterruptLock() { state_ = xt_rsil(15); } IRAM_ATTR InterruptLock::~InterruptLock() { xt_wsr_ps(state_); } -#elif defined(USE_ESP32) +#elif defined(USE_ESP32) || defined(USE_LIBRETINY) // only affects the executing core // so should not be used as a mutex lock, only to get accurate timing IRAM_ATTR InterruptLock::InterruptLock() { portDISABLE_INTERRUPTS(); } @@ -555,6 +564,8 @@ void get_mac_address_raw(uint8_t *mac) { // NOLINT(readability-non-const-parame wifi_get_macaddr(STATION_IF, mac); #elif defined(USE_RP2040) && defined(USE_WIFI) WiFi.macAddress(mac); +#elif defined(USE_LIBRETINY) + WiFi.macAddress(mac); #endif } std::string get_mac_address() { diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 115073de80..c3ed443bf0 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -17,6 +17,9 @@ #if defined(USE_ESP32) #include #include +#elif defined(USE_LIBRETINY) +#include +#include #endif #define HOT __attribute__((hot)) @@ -543,7 +546,7 @@ class Mutex { Mutex &operator=(const Mutex &) = delete; private: -#if defined(USE_ESP32) +#if defined(USE_ESP32) || defined(USE_LIBRETINY) SemaphoreHandle_t handle_; #endif }; diff --git a/esphome/core/log.h b/esphome/core/log.h index 6775aa5ac5..86af534f98 100644 --- a/esphome/core/log.h +++ b/esphome/core/log.h @@ -17,6 +17,9 @@ #ifdef USE_ESP32_FRAMEWORK_ARDUINO #include #endif +#ifdef USE_LIBRETINY +#include +#endif namespace esphome { diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index c05e1fcfcc..0d6ec8dc13 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -543,6 +543,7 @@ class DownloadListRequestHandler(BaseHandler): from esphome.components.esp32 import get_download_types as esp32_types from esphome.components.esp8266 import get_download_types as esp8266_types from esphome.components.rp2040 import get_download_types as rp2040_types + from esphome.components.libretiny import get_download_types as libretiny_types downloads = [] platform = storage_json.target_platform.lower() @@ -552,6 +553,10 @@ class DownloadListRequestHandler(BaseHandler): downloads = esp8266_types(storage_json) elif platform == const.PLATFORM_ESP32: downloads = esp32_types(storage_json) + elif platform == const.PLATFORM_BK72XX: + downloads = libretiny_types(storage_json) + elif platform == const.PLATFORM_RTL87XX: + downloads = libretiny_types(storage_json) else: self.send_error(418) return @@ -826,11 +831,15 @@ class BoardsRequestHandler(BaseHandler): from esphome.components.esp32.boards import BOARDS as ESP32_BOARDS from esphome.components.esp8266.boards import BOARDS as ESP8266_BOARDS from esphome.components.rp2040.boards import BOARDS as RP2040_BOARDS + from esphome.components.bk72xx.boards import BOARDS as BK72XX_BOARDS + from esphome.components.rtl87xx.boards import BOARDS as RTL87XX_BOARDS platform_to_boards = { "esp32": ESP32_BOARDS, "esp8266": ESP8266_BOARDS, "rp2040": RP2040_BOARDS, + "bk72xx": BK72XX_BOARDS, + "rtl87xx": RTL87XX_BOARDS, } # filter all ESP32 variants by requested platform if platform.startswith("esp32"): diff --git a/esphome/voluptuous_schema.py b/esphome/voluptuous_schema.py index 281ef10964..e2171cabed 100644 --- a/esphome/voluptuous_schema.py +++ b/esphome/voluptuous_schema.py @@ -203,6 +203,11 @@ class _Schema(vol.Schema): self._extra_schemas.append(validator) return self + def prepend_extra(self, validator): + validator = _Schema(validator) + self._extra_schemas.insert(0, validator) + return self + @schema_extractor_extended def extend(self, *schemas, **kwargs): extra = kwargs.pop("extra", None) diff --git a/esphome/wizard.py b/esphome/wizard.py index fd661af639..17a0882e1c 100644 --- a/esphome/wizard.py +++ b/esphome/wizard.py @@ -93,12 +93,24 @@ rp2040: platform_version: https://github.com/maxgerhardt/platform-raspberrypi.git """ +BK72XX_CONFIG = """ +bk72xx: + board: {board} +""" + +RTL87XX_CONFIG = """ +rtl87xx: + board: {board} +""" + HARDWARE_BASE_CONFIGS = { "ESP8266": ESP8266_CONFIG, "ESP32": ESP32_CONFIG, "ESP32S2": ESP32S2_CONFIG, "ESP32C3": ESP32C3_CONFIG, "RP2040": RP2040_CONFIG, + "BK72XX": BK72XX_CONFIG, + "RTL87XX": RTL87XX_CONFIG, } @@ -156,7 +168,7 @@ def wizard_file(**kwargs): """ # pylint: disable=consider-using-f-string - if kwargs["platform"] in ["ESP8266", "ESP32"]: + if kwargs["platform"] in ["ESP8266", "ESP32", "BK72XX", "RTL87XX"]: config += """ # Enable fallback hotspot (captive portal) in case wifi connection fails ap: @@ -182,7 +194,10 @@ captive_portal: def wizard_write(path, **kwargs): from esphome.components.esp8266 import boards as esp8266_boards + from esphome.components.esp32 import boards as esp32_boards from esphome.components.rp2040 import boards as rp2040_boards + from esphome.components.bk72xx import boards as bk72xx_boards + from esphome.components.rtl87xx import boards as rtl87xx_boards name = kwargs["name"] board = kwargs["board"] @@ -192,12 +207,19 @@ def wizard_write(path, **kwargs): kwargs[key] = sanitize_double_quotes(kwargs[key]) if "platform" not in kwargs: - if board in esp8266_boards.ESP8266_BOARD_PINS: + if board in esp8266_boards.BOARDS: platform = "ESP8266" - elif board in rp2040_boards.RP2040_BOARD_PINS: - platform = "RP2040" - else: + elif board in esp32_boards.BOARDS: platform = "ESP32" + elif board in rp2040_boards.BOARDS: + platform = "RP2040" + elif board in bk72xx_boards.BOARDS: + platform = "BK72XX" + elif board in rtl87xx_boards.BOARDS: + platform = "RTL87XX" + else: + safe_print(color(Fore.RED, f'The board "{board}" is unknown.')) + return False kwargs["platform"] = platform hardware = kwargs["platform"] @@ -206,6 +228,8 @@ def wizard_write(path, **kwargs): storage_path = ext_storage_path(os.path.dirname(path), os.path.basename(path)) storage.save(storage_path) + return True + if get_bool_env(ENV_QUICKWIZARD): @@ -243,6 +267,8 @@ def strip_accents(value): def wizard(path): from esphome.components.esp32 import boards as esp32_boards from esphome.components.esp8266 import boards as esp8266_boards + from esphome.components.bk72xx import boards as bk72xx_boards + from esphome.components.rtl87xx import boards as rtl87xx_boards if not path.endswith(".yaml") and not path.endswith(".yml"): safe_print( @@ -262,7 +288,7 @@ def wizard(path): sleep(2.0) safe_print( "In 4 steps I'm going to guide you through creating a basic " - "configuration file for your custom ESP8266/ESP32 firmware. Yay!" + "configuration file for your custom firmware. Yay!" ) sleep(3.0) safe_print() @@ -305,16 +331,18 @@ def wizard(path): "Now I'd like to know what microcontroller you're using so that I can compile " "firmwares for it." ) + + wizard_platforms = ["ESP32", "ESP8266", "BK72XX", "RTL87XX"] safe_print( - f"Are you using an {color(Fore.GREEN, 'ESP32')} or {color(Fore.GREEN, 'ESP8266')} platform? (Choose ESP8266 for Sonoff devices)" + "Please choose one of the supported microcontrollers " + "(Use ESP8266 for Sonoff devices)." ) while True: sleep(0.5) safe_print() - safe_print("Please enter either ESP32 or ESP8266.") - platform = input(color(Fore.BOLD_WHITE, "(ESP32/ESP8266): ")) + platform = input(color(Fore.BOLD_WHITE, f"({'/'.join(wizard_platforms)}): ")) try: - platform = vol.All(vol.Upper, vol.Any("ESP32", "ESP8266"))(platform) + platform = vol.All(vol.Upper, vol.Any(*wizard_platforms))(platform.upper()) break except vol.Invalid: safe_print( @@ -328,10 +356,14 @@ def wizard(path): board_link = ( "http://docs.platformio.org/en/latest/platforms/espressif32.html#boards" ) - else: + elif platform == "ESP8266": board_link = ( "http://docs.platformio.org/en/latest/platforms/espressif8266.html#boards" ) + elif platform in ["BK72XX", "RTL87XX"]: + board_link = "https://docs.libretiny.eu/docs/status/supported/" + else: + raise NotImplementedError("Unknown platform!") safe_print(f"Next, I need to know what {color(Fore.GREEN, 'board')} you're using.") sleep(0.5) @@ -342,11 +374,24 @@ def wizard(path): # Don't sleep because user needs to copy link if platform == "ESP32": safe_print(f"For example \"{color(Fore.BOLD_WHITE, 'nodemcu-32s')}\".") - boards = list(esp32_boards.ESP32_BOARD_PINS.keys()) - else: + boards_list = esp32_boards.BOARDS.items() + elif platform == "ESP8266": safe_print(f"For example \"{color(Fore.BOLD_WHITE, 'nodemcuv2')}\".") - boards = list(esp8266_boards.ESP8266_BOARD_PINS.keys()) - safe_print(f"Options: {', '.join(sorted(boards))}") + boards_list = esp8266_boards.BOARDS.items() + elif platform == "BK72XX": + safe_print(f"For example \"{color(Fore.BOLD_WHITE, 'cb2s')}\".") + boards_list = bk72xx_boards.BOARDS.items() + elif platform == "RTL87XX": + safe_print(f"For example \"{color(Fore.BOLD_WHITE, 'wr3')}\".") + boards_list = rtl87xx_boards.BOARDS.items() + else: + raise NotImplementedError("Unknown platform!") + + boards = [] + safe_print("Options:") + for board_id, board_data in boards_list: + safe_print(f" - {board_id} - {board_data['name']}") + boards.append(board_id) while True: board = input(color(Fore.BOLD_WHITE, "(board): ")) @@ -420,7 +465,7 @@ def wizard(path): safe_print("Press ENTER for no password") password = input(color(Fore.BOLD_WHITE, "(password): ")) - wizard_write( + if not wizard_write( path=path, name=name, platform=platform, @@ -428,7 +473,8 @@ def wizard(path): ssid=ssid, psk=psk, password=password, - ) + ): + return 1 safe_print() safe_print( diff --git a/platformio.ini b/platformio.ini index 64c7bec6e8..ab9584d9b8 100644 --- a/platformio.ini +++ b/platformio.ini @@ -4,7 +4,7 @@ ; It's *not* used during runtime. [platformio] -default_envs = esp8266-arduino, esp32-arduino, esp32-idf +default_envs = esp8266-arduino, esp32-arduino, esp32-idf, bk72xx-arduino ; Ideally, we want src_dir to be the root directory of the repository, to mimic the runtime build ; environment as best as possible. Unfortunately, the ESP-IDF toolchain really doesn't like this ; being the root directory. Instead, set esphome/ as the source directory, all our sources are in @@ -167,6 +167,16 @@ build_flags = -DUSE_RP2040 -DUSE_RP2040_FRAMEWORK_ARDUINO +; This are common settings for the LibreTiny (all variants) using Arduino. +[common:libretiny-arduino] +extends = common:arduino +platform = libretiny +framework = arduino +build_flags = + ${common:arduino.build_flags} + -DUSE_LIBRETINY +build_src_flags = -include Arduino.h + ; All the actual environments are defined below. ;;;;;;;; ESP8266 ;;;;;;;; @@ -339,6 +349,35 @@ build_flags = ${common:rp2040-arduino.build_flags} ${flags:runtime.build_flags} +;;;;;;;; LibreTiny ;;;;;;;; + +[env:bk72xx-arduino] +extends = common:libretiny-arduino +board = generic-bk7231n-qfn32-tuya +build_flags = + ${common:libretiny-arduino.build_flags} + ${flags:runtime.build_flags} + -DUSE_BK72XX + -DUSE_LIBRETINY_VARIANT_BK7231N + +[env:rtl87xxb-arduino] +extends = common:libretiny-arduino +board = generic-rtl8710bn-2mb-788k +build_flags = + ${common:libretiny-arduino.build_flags} + ${flags:runtime.build_flags} + -DUSE_RTL87XX + -DUSE_LIBRETINY_VARIANT_RTL8710B + +[env:rtl87xxc-arduino] +extends = common:libretiny-arduino +board = generic-rtl8720cf-2mb-992k +build_flags = + ${common:libretiny-arduino.build_flags} + ${flags:runtime.build_flags} + -DUSE_RTL87XX + -DUSE_LIBRETINY_VARIANT_RTL8720C + [env:host] extends = common platform = platformio/native diff --git a/script/ci-custom.py b/script/ci-custom.py index a731e2e5b8..da4da50d7e 100755 --- a/script/ci-custom.py +++ b/script/ci-custom.py @@ -512,7 +512,10 @@ def relative_py_search_text(fname, content): @lint_content_find_check( relative_py_search_text, include=["esphome/components/*.py"], - exclude=["esphome/components/web_server/__init__.py"], + exclude=[ + "esphome/components/libretiny/generate_components.py", + "esphome/components/web_server/__init__.py", + ], ) def lint_relative_py_import(fname): return ( @@ -536,6 +539,7 @@ def lint_relative_py_import(fname): "esphome/components/esp32/core.cpp", "esphome/components/esp8266/core.cpp", "esphome/components/rp2040/core.cpp", + "esphome/components/libretiny/core.cpp", "esphome/components/host/core.cpp", ], ) diff --git a/tests/test9.1.yaml b/tests/test9.1.yaml new file mode 100644 index 0000000000..f7455b7668 --- /dev/null +++ b/tests/test9.1.yaml @@ -0,0 +1,28 @@ +# Tests for rtl87xx boards using LibreTiny +--- +wifi: + ssid: "ssid" + +rtl87xx: + board: generic-rtl8710bn-2mb-788k + +esphome: + name: rtl87xx-test + +logger: + +ota: + +captive_portal: + +binary_sensor: + - platform: gpio + name: Home Button + pin: GPIO11 + +sensor: + - platform: adc + id: adc_sensor + name: ADC + pin: PA19 + update_interval: 1s diff --git a/tests/test9.yaml b/tests/test9.yaml new file mode 100644 index 0000000000..ccf5f4b5b0 --- /dev/null +++ b/tests/test9.yaml @@ -0,0 +1,28 @@ +# Tests for bk7xx boards using LibreTiny +--- +wifi: + ssid: "ssid" + +bk72xx: + board: cb2s + +esphome: + name: bk72xx-test + +logger: + +ota: + +captive_portal: + +binary_sensor: + - platform: gpio + name: Home Button + pin: GPIO24 + +sensor: + - platform: adc + id: adc_sensor + name: ADC + pin: GPIO23 + update_interval: 1s diff --git a/tests/unit_tests/test_wizard.py b/tests/unit_tests/test_wizard.py index 79a5894075..d94624d1e4 100644 --- a/tests/unit_tests/test_wizard.py +++ b/tests/unit_tests/test_wizard.py @@ -3,6 +3,9 @@ import esphome.wizard as wz import pytest from esphome.components.esp8266.boards import ESP8266_BOARD_PINS +from esphome.components.esp32.boards import ESP32_BOARD_PINS +from esphome.components.bk72xx.boards import BK72XX_BOARD_PINS +from esphome.components.rtl87xx.boards import RTL87XX_BOARD_PINS from unittest.mock import MagicMock @@ -140,11 +143,11 @@ def test_wizard_write_defaults_platform_from_board_esp32( default_config, tmp_path, monkeypatch ): """ - If the platform is not explicitly set, use "ESP32" if the board is not one of the ESP8266 boards + If the platform is not explicitly set, use "ESP32" if the board is one of the ESP32 boards """ # Given del default_config["platform"] - default_config["board"] = "foo" + default_config["board"] = [*ESP32_BOARD_PINS][0] monkeypatch.setattr(wz, "write_file", MagicMock()) @@ -156,6 +159,46 @@ def test_wizard_write_defaults_platform_from_board_esp32( assert "esp32:" in generated_config +def test_wizard_write_defaults_platform_from_board_bk72xx( + default_config, tmp_path, monkeypatch +): + """ + If the platform is not explicitly set, use "BK72XX" if the board is one of BK72XX boards + """ + # Given + del default_config["platform"] + default_config["board"] = [*BK72XX_BOARD_PINS][0] + + monkeypatch.setattr(wz, "write_file", MagicMock()) + + # When + wz.wizard_write(tmp_path, **default_config) + + # Then + generated_config = wz.write_file.call_args.args[1] + assert "bk72xx:" in generated_config + + +def test_wizard_write_defaults_platform_from_board_rtl87xx( + default_config, tmp_path, monkeypatch +): + """ + If the platform is not explicitly set, use "RTL87XX" if the board is one of RTL87XX boards + """ + # Given + del default_config["platform"] + default_config["board"] = [*RTL87XX_BOARD_PINS][0] + + monkeypatch.setattr(wz, "write_file", MagicMock()) + + # When + wz.wizard_write(tmp_path, **default_config) + + # Then + generated_config = wz.write_file.call_args.args[1] + assert "rtl87xx:" in generated_config + + def test_safe_print_step_prints_step_number_and_description(monkeypatch): """ The safe_print_step function prints the step number and the passed description @@ -186,7 +229,7 @@ def test_default_input_uses_default_if_no_input_supplied(monkeypatch): """ # Given - monkeypatch.setattr("builtins.input", lambda _: "") + monkeypatch.setattr("builtins.input", lambda _=None: "") default_string = "foobar" # When @@ -203,7 +246,7 @@ def test_default_input_uses_user_supplied_value(monkeypatch): # Given user_input = "A value" - monkeypatch.setattr("builtins.input", lambda _: user_input) + monkeypatch.setattr("builtins.input", lambda _=None: user_input) default_string = "foobar" # When From d382ca2401581579c8edceca52547f1ded3e8eda Mon Sep 17 00:00:00 2001 From: mkaiser <29856783+mkaiser@users.noreply.github.com> Date: Tue, 5 Sep 2023 00:27:58 +0200 Subject: [PATCH 061/111] Extend ESP32 CAN bit rates /bus speed support (#5280) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Co-authored-by: mkaiser --- esphome/components/canbus/__init__.py | 6 ++- esphome/components/canbus/canbus.h | 5 +++ esphome/components/esp32_can/canbus.py | 51 +++++++++++++++++++++- esphome/components/esp32_can/esp32_can.cpp | 27 ++++++++++++ 4 files changed, 86 insertions(+), 3 deletions(-) diff --git a/esphome/components/canbus/__init__.py b/esphome/components/canbus/__init__.py index c5a9924644..f49398858c 100644 --- a/esphome/components/canbus/__init__.py +++ b/esphome/components/canbus/__init__.py @@ -45,9 +45,13 @@ CanbusTrigger = canbus_ns.class_( CanSpeed = canbus_ns.enum("CAN_SPEED") CAN_SPEEDS = { + "1KBPS": CanSpeed.CAN_1KBPS, "5KBPS": CanSpeed.CAN_5KBPS, "10KBPS": CanSpeed.CAN_10KBPS, + "12K5BPS": CanSpeed.CAN_12K5BPS, + "16KBPS": CanSpeed.CAN_16KBPS, "20KBPS": CanSpeed.CAN_20KBPS, + "25KBPS": CanSpeed.CAN_25KBPS, "31K25BPS": CanSpeed.CAN_31K25BPS, "33KBPS": CanSpeed.CAN_33KBPS, "40KBPS": CanSpeed.CAN_40KBPS, @@ -60,9 +64,9 @@ CAN_SPEEDS = { "200KBPS": CanSpeed.CAN_200KBPS, "250KBPS": CanSpeed.CAN_250KBPS, "500KBPS": CanSpeed.CAN_500KBPS, + "800KBPS": CanSpeed.CAN_800KBPS, "1000KBPS": CanSpeed.CAN_1000KBPS, } - CANBUS_SCHEMA = cv.Schema( { cv.GenerateID(): cv.declare_id(CanbusComponent), diff --git a/esphome/components/canbus/canbus.h b/esphome/components/canbus/canbus.h index 4a12742627..c0ccff4866 100644 --- a/esphome/components/canbus/canbus.h +++ b/esphome/components/canbus/canbus.h @@ -19,9 +19,13 @@ enum Error : uint8_t { }; enum CanSpeed : uint8_t { + CAN_1KBPS, CAN_5KBPS, CAN_10KBPS, + CAN_12K5BPS, + CAN_16KBPS, CAN_20KBPS, + CAN_25KBPS, CAN_31K25BPS, CAN_33KBPS, CAN_40KBPS, @@ -34,6 +38,7 @@ enum CanSpeed : uint8_t { CAN_200KBPS, CAN_250KBPS, CAN_500KBPS, + CAN_800KBPS, CAN_1000KBPS }; diff --git a/esphome/components/esp32_can/canbus.py b/esphome/components/esp32_can/canbus.py index 7761418c6a..74f331f30b 100644 --- a/esphome/components/esp32_can/canbus.py +++ b/esphome/components/esp32_can/canbus.py @@ -5,6 +5,15 @@ from esphome.components import canbus from esphome.const import CONF_ID, CONF_RX_PIN, CONF_TX_PIN from esphome.components.canbus import CanbusComponent, CanSpeed, CONF_BIT_RATE +from esphome.components.esp32 import get_esp32_variant +from esphome.components.esp32.const import ( + VARIANT_ESP32, + VARIANT_ESP32S2, + VARIANT_ESP32S3, + VARIANT_ESP32C3, + VARIANT_ESP32H2, +) + CODEOWNERS = ["@Sympatron"] DEPENDENCIES = ["esp32"] @@ -12,19 +21,57 @@ esp32_can_ns = cg.esphome_ns.namespace("esp32_can") esp32_can = esp32_can_ns.class_("ESP32Can", CanbusComponent) # Currently the driver only supports a subset of the bit rates defined in canbus -CAN_SPEEDS = { +# The supported bit rates differ between ESP32 variants. +# See ESP-IDF Programming Guide --> API Reference --> Two-Wire Automotive Interface (TWAI) + +CAN_SPEEDS_ESP32 = { + "25KBPS": CanSpeed.CAN_25KBPS, "50KBPS": CanSpeed.CAN_50KBPS, "100KBPS": CanSpeed.CAN_100KBPS, "125KBPS": CanSpeed.CAN_125KBPS, "250KBPS": CanSpeed.CAN_250KBPS, "500KBPS": CanSpeed.CAN_500KBPS, + "800KBPS": CanSpeed.CAN_800KBPS, "1000KBPS": CanSpeed.CAN_1000KBPS, } +CAN_SPEEDS_ESP32_S2 = { + "1KBPS": CanSpeed.CAN_1KBPS, + "5KBPS": CanSpeed.CAN_5KBPS, + "10KBPS": CanSpeed.CAN_10KBPS, + "12K5BPS": CanSpeed.CAN_12K5BPS, + "16KBPS": CanSpeed.CAN_16KBPS, + "20KBPS": CanSpeed.CAN_20KBPS, + **CAN_SPEEDS_ESP32, +} + +CAN_SPEEDS_ESP32_S3 = {**CAN_SPEEDS_ESP32_S2} +CAN_SPEEDS_ESP32_C3 = {**CAN_SPEEDS_ESP32_S2} +CAN_SPEEDS_ESP32_H2 = {**CAN_SPEEDS_ESP32_S2} + +CAN_SPEEDS = { + VARIANT_ESP32: CAN_SPEEDS_ESP32, + VARIANT_ESP32S2: CAN_SPEEDS_ESP32_S2, + VARIANT_ESP32S3: CAN_SPEEDS_ESP32_S3, + VARIANT_ESP32C3: CAN_SPEEDS_ESP32_C3, + VARIANT_ESP32H2: CAN_SPEEDS_ESP32_H2, +} + + +def validate_bit_rate(value): + variant = get_esp32_variant() + if variant not in CAN_SPEEDS: + raise cv.Invalid(f"{variant} is not supported by component {esp32_can_ns}") + value = value.upper() + if value not in CAN_SPEEDS[variant]: + raise cv.Invalid(f"Bit rate {value} is not supported on {variant}") + return cv.enum(CAN_SPEEDS[variant])(value) + + CONFIG_SCHEMA = canbus.CANBUS_SCHEMA.extend( { cv.GenerateID(): cv.declare_id(esp32_can), - cv.Optional(CONF_BIT_RATE, default="125KBPS"): cv.enum(CAN_SPEEDS, upper=True), + cv.Optional(CONF_BIT_RATE, default="125KBPS"): validate_bit_rate, cv.Required(CONF_RX_PIN): pins.internal_gpio_input_pin_number, cv.Required(CONF_TX_PIN): pins.internal_gpio_output_pin_number, } diff --git a/esphome/components/esp32_can/esp32_can.cpp b/esphome/components/esp32_can/esp32_can.cpp index 3eb2d1f035..79e4b70f97 100644 --- a/esphome/components/esp32_can/esp32_can.cpp +++ b/esphome/components/esp32_can/esp32_can.cpp @@ -16,6 +16,30 @@ static const char *const TAG = "esp32_can"; static bool get_bitrate(canbus::CanSpeed bitrate, twai_timing_config_t *t_config) { switch (bitrate) { +#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32C3) || \ + defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32H6) + case canbus::CAN_1KBPS: + *t_config = (twai_timing_config_t) TWAI_TIMING_CONFIG_1KBITS(); + return true; + case canbus::CAN_5KBPS: + *t_config = (twai_timing_config_t) TWAI_TIMING_CONFIG_5KBITS(); + return true; + case canbus::CAN_10KBPS: + *t_config = (twai_timing_config_t) TWAI_TIMING_CONFIG_10KBITS(); + return true; + case canbus::CAN_12K5BPS: + *t_config = (twai_timing_config_t) TWAI_TIMING_CONFIG_12_5KBITS(); + return true; + case canbus::CAN_16KBPS: + *t_config = (twai_timing_config_t) TWAI_TIMING_CONFIG_16KBITS(); + return true; + case canbus::CAN_20KBPS: + *t_config = (twai_timing_config_t) TWAI_TIMING_CONFIG_20KBITS(); + return true; +#endif + case canbus::CAN_25KBPS: + *t_config = (twai_timing_config_t) TWAI_TIMING_CONFIG_25KBITS(); + return true; case canbus::CAN_50KBPS: *t_config = (twai_timing_config_t) TWAI_TIMING_CONFIG_50KBITS(); return true; @@ -31,6 +55,9 @@ static bool get_bitrate(canbus::CanSpeed bitrate, twai_timing_config_t *t_config case canbus::CAN_500KBPS: *t_config = (twai_timing_config_t) TWAI_TIMING_CONFIG_500KBITS(); return true; + case canbus::CAN_800KBPS: + *t_config = (twai_timing_config_t) TWAI_TIMING_CONFIG_800KBITS(); + return true; case canbus::CAN_1000KBPS: *t_config = (twai_timing_config_t) TWAI_TIMING_CONFIG_1MBITS(); return true; From 562f7c8718912de531780037ddb47199340b49d0 Mon Sep 17 00:00:00 2001 From: kahrendt Date: Mon, 4 Sep 2023 22:02:59 -0400 Subject: [PATCH 062/111] Debug component: add free PSRAM sensor (#5334) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/debug/debug_component.cpp | 10 ++++++ esphome/components/debug/debug_component.h | 6 ++++ esphome/components/debug/sensor.py | 32 +++++++++++++++----- tests/test1.yaml | 10 +++++- tests/test5.yaml | 14 +++++++++ tests/test8.yaml | 15 +++++++++ 6 files changed, 78 insertions(+), 9 deletions(-) diff --git a/esphome/components/debug/debug_component.cpp b/esphome/components/debug/debug_component.cpp index 52ee4b070e..67b07237b7 100644 --- a/esphome/components/debug/debug_component.cpp +++ b/esphome/components/debug/debug_component.cpp @@ -145,6 +145,10 @@ void DebugComponent::dump_config() { features += "BT,"; info.features &= ~CHIP_FEATURE_BT; } + if (info.features & CHIP_FEATURE_EMB_PSRAM) { + features += "EMB_PSRAM,"; + info.features &= ~CHIP_FEATURE_EMB_PSRAM; + } if (info.features) features += "Other:" + format_hex(info.features); ESP_LOGD(TAG, "Chip: Model=%s, Features=%s Cores=%u, Revision=%u", model, features.c_str(), info.cores, @@ -423,6 +427,12 @@ void DebugComponent::update() { this->loop_time_sensor_->publish_state(this->max_loop_time_); this->max_loop_time_ = 0; } + +#ifdef USE_ESP32 + if (this->psram_sensor_ != nullptr) { + this->psram_sensor_->publish_state(heap_caps_get_free_size(MALLOC_CAP_SPIRAM)); + } +#endif // USE_ESP32 #endif // USE_SENSOR } diff --git a/esphome/components/debug/debug_component.h b/esphome/components/debug/debug_component.h index b80fda55eb..93e3ba4857 100644 --- a/esphome/components/debug/debug_component.h +++ b/esphome/components/debug/debug_component.h @@ -33,6 +33,9 @@ class DebugComponent : public PollingComponent { void set_fragmentation_sensor(sensor::Sensor *fragmentation_sensor) { fragmentation_sensor_ = fragmentation_sensor; } #endif void set_loop_time_sensor(sensor::Sensor *loop_time_sensor) { loop_time_sensor_ = loop_time_sensor; } +#ifdef USE_ESP32 + void set_psram_sensor(sensor::Sensor *psram_sensor) { this->psram_sensor_ = psram_sensor; } +#endif // USE_ESP32 #endif // USE_SENSOR protected: uint32_t free_heap_{}; @@ -47,6 +50,9 @@ class DebugComponent : public PollingComponent { sensor::Sensor *fragmentation_sensor_{nullptr}; #endif sensor::Sensor *loop_time_sensor_{nullptr}; +#ifdef USE_ESP32 + sensor::Sensor *psram_sensor_{nullptr}; +#endif // USE_ESP32 #endif // USE_SENSOR #ifdef USE_TEXT_SENSOR diff --git a/esphome/components/debug/sensor.py b/esphome/components/debug/sensor.py index f7ea07d138..061c2750e4 100644 --- a/esphome/components/debug/sensor.py +++ b/esphome/components/debug/sensor.py @@ -17,6 +17,8 @@ from . import CONF_DEBUG_ID, DebugComponent DEPENDENCIES = ["debug"] +CONF_PSRAM = "psram" + CONFIG_SCHEMA = { cv.GenerateID(CONF_DEBUG_ID): cv.use_id(DebugComponent), cv.Optional(CONF_FREE): sensor.sensor_schema( @@ -47,24 +49,38 @@ CONFIG_SCHEMA = { accuracy_decimals=0, entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), + cv.Optional(CONF_PSRAM): cv.All( + cv.only_on_esp32, + cv.requires_component("psram"), + sensor.sensor_schema( + unit_of_measurement=UNIT_BYTES, + icon=ICON_COUNTER, + accuracy_decimals=0, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + ), } async def to_code(config): debug_component = await cg.get_variable(config[CONF_DEBUG_ID]) - if CONF_FREE in config: - sens = await sensor.new_sensor(config[CONF_FREE]) + if free_conf := config.get(CONF_FREE): + sens = await sensor.new_sensor(free_conf) cg.add(debug_component.set_free_sensor(sens)) - if CONF_BLOCK in config: - sens = await sensor.new_sensor(config[CONF_BLOCK]) + if block_conf := config.get(CONF_BLOCK): + sens = await sensor.new_sensor(block_conf) cg.add(debug_component.set_block_sensor(sens)) - if CONF_FRAGMENTATION in config: - sens = await sensor.new_sensor(config[CONF_FRAGMENTATION]) + if fragmentation_conf := config.get(CONF_FRAGMENTATION): + sens = await sensor.new_sensor(fragmentation_conf) cg.add(debug_component.set_fragmentation_sensor(sens)) - if CONF_LOOP_TIME in config: - sens = await sensor.new_sensor(config[CONF_LOOP_TIME]) + if loop_time_conf := config.get(CONF_LOOP_TIME): + sens = await sensor.new_sensor(loop_time_conf) cg.add(debug_component.set_loop_time_sensor(sens)) + + if psram_conf := config.get(CONF_PSRAM): + sens = await sensor.new_sensor(psram_conf) + cg.add(debug_component.set_psram_sensor(sens)) diff --git a/tests/test1.yaml b/tests/test1.yaml index efca34247b..b78407069d 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -1448,6 +1448,15 @@ sensor: pressure: name: "BMP581 Pressure" oversampling: 128x + - platform: debug + free: + name: "Heap Free" + block: + name: "Heap Max Block" + loop_time: + name: "Loop Time" + psram: + name: "PSRAM Free" esp32_touch: setup_mode: false @@ -3448,7 +3457,6 @@ number: still_threshold: name: g8 still threshold - select: - platform: template id: test_select diff --git a/tests/test5.yaml b/tests/test5.yaml index a2530d799a..a1cc3103d7 100644 --- a/tests/test5.yaml +++ b/tests/test5.yaml @@ -28,6 +28,10 @@ ota: logger: +debug: + +psram: + uart: - id: uart_1 tx_pin: 1 @@ -525,6 +529,16 @@ sensor: time: name: System Time + - platform: debug + free: + name: "Heap Free" + block: + name: "Heap Max Block" + loop_time: + name: "Loop Time" + psram: + name: "PSRAM Free" + - platform: vbus model: custom command: 0x100 diff --git a/tests/test8.yaml b/tests/test8.yaml index 8d031b033f..28c6e78b87 100644 --- a/tests/test8.yaml +++ b/tests/test8.yaml @@ -14,6 +14,10 @@ esphome: logger: +debug: + +psram: + light: - platform: neopixelbus type: GRB @@ -50,3 +54,14 @@ binary_sensor: - platform: tt21100 name: Home Button index: 1 + +sensor: + - platform: debug + free: + name: "Heap Free" + block: + name: "Max Block Free" + loop_time: + name: "Loop Time" + psram: + name: "PSRAM Free" From b11824b0587fd23317959bfbf02e3f1eb445cfc0 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 5 Sep 2023 14:33:42 +1200 Subject: [PATCH 063/111] libretiny: fix uart_port framework config (#5343) --- esphome/components/libretiny/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/libretiny/__init__.py b/esphome/components/libretiny/__init__.py index c6c63b48c8..ac294d3f65 100644 --- a/esphome/components/libretiny/__init__.py +++ b/esphome/components/libretiny/__init__.py @@ -218,7 +218,7 @@ FRAMEWORK_SCHEMA = cv.All( cv.Optional(CONF_SDK_SILENT, default="all"): ( cv.one_of("all", "auto", "none", lower=True) ), - cv.Optional(CONF_UART_PORT, default=None): cv.one_of(0, 1, 2, int=True), + cv.Optional(CONF_UART_PORT): cv.one_of(0, 1, 2, int=True), cv.Optional(CONF_GPIO_RECOVER, default=True): cv.boolean, cv.Optional(CONF_OPTIONS, default={}): { cv.string_strict: cv.string, @@ -309,8 +309,8 @@ async def component_to_code(config): lt_options["LT_UART_SILENT_ENABLED"] = 0 lt_options["LT_UART_SILENT_ALL"] = 0 # set default UART port - if framework[CONF_UART_PORT] is not None: - lt_options["LT_UART_DEFAULT_PORT"] = framework[CONF_UART_PORT] + if uart_port := framework.get(CONF_UART_PORT, None) is not None: + lt_options["LT_UART_DEFAULT_PORT"] = uart_port # add custom options lt_options.update(framework[CONF_OPTIONS]) From 343278b29108a1f4e331fda4116244dc9f5a35a9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Sep 2023 18:11:46 +1200 Subject: [PATCH 064/111] Bump actions/checkout from 3 to 4 (#5341) Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci-docker.yml | 2 +- .github/workflows/ci.yml | 22 +++++++++++----------- .github/workflows/release.yml | 6 +++--- .github/workflows/sync-device-classes.yml | 4 ++-- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/.github/workflows/ci-docker.yml b/.github/workflows/ci-docker.yml index eb3a5a945c..394379d675 100644 --- a/.github/workflows/ci-docker.yml +++ b/.github/workflows/ci-docker.yml @@ -40,7 +40,7 @@ jobs: arch: [amd64, armv7, aarch64] build_type: ["ha-addon", "docker", "lint"] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v4 with: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ba46936952..1de5822960 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,7 +30,7 @@ jobs: cache-key: ${{ steps.cache-key.outputs.key }} steps: - name: Check out code from GitHub - uses: actions/checkout@v3.5.2 + uses: actions/checkout@v4 - name: Generate cache-key id: cache-key run: echo key="${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}" >> $GITHUB_OUTPUT @@ -60,7 +60,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out code from GitHub - uses: actions/checkout@v3.5.2 + uses: actions/checkout@v4 - name: Run yamllint uses: frenck/action-yamllint@v1.4.1 @@ -71,7 +71,7 @@ jobs: - common steps: - name: Check out code from GitHub - uses: actions/checkout@v3.5.2 + uses: actions/checkout@v4 - name: Restore Python uses: ./.github/actions/restore-python with: @@ -92,7 +92,7 @@ jobs: - common steps: - name: Check out code from GitHub - uses: actions/checkout@v3.5.2 + uses: actions/checkout@v4 - name: Restore Python uses: ./.github/actions/restore-python with: @@ -113,7 +113,7 @@ jobs: - common steps: - name: Check out code from GitHub - uses: actions/checkout@v3.5.2 + uses: actions/checkout@v4 - name: Restore Python uses: ./.github/actions/restore-python with: @@ -134,7 +134,7 @@ jobs: - common steps: - name: Check out code from GitHub - uses: actions/checkout@v3.5.2 + uses: actions/checkout@v4 - name: Restore Python uses: ./.github/actions/restore-python with: @@ -155,7 +155,7 @@ jobs: - common steps: - name: Check out code from GitHub - uses: actions/checkout@v3.5.2 + uses: actions/checkout@v4 - name: Restore Python uses: ./.github/actions/restore-python with: @@ -176,7 +176,7 @@ jobs: - common steps: - name: Check out code from GitHub - uses: actions/checkout@v3.5.2 + uses: actions/checkout@v4 - name: Restore Python uses: ./.github/actions/restore-python with: @@ -196,7 +196,7 @@ jobs: - common steps: - name: Check out code from GitHub - uses: actions/checkout@v3.5.2 + uses: actions/checkout@v4 - name: Restore Python uses: ./.github/actions/restore-python with: @@ -235,7 +235,7 @@ jobs: file: [1, 2, 3, 3.1, 4, 5, 6, 7, 8] steps: - name: Check out code from GitHub - uses: actions/checkout@v3.5.2 + uses: actions/checkout@v4 - name: Restore Python uses: ./.github/actions/restore-python with: @@ -291,7 +291,7 @@ jobs: steps: - name: Check out code from GitHub - uses: actions/checkout@v3.5.2 + uses: actions/checkout@v4 - name: Restore Python uses: ./.github/actions/restore-python with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 74ff4d87f4..71a0cd2c78 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -19,7 +19,7 @@ jobs: outputs: tag: ${{ steps.tag.outputs.tag }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Get tag id: tag # yamllint disable rule:line-length @@ -43,7 +43,7 @@ jobs: if: github.repository == 'esphome/esphome' && github.event_name == 'release' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v4 with: @@ -88,7 +88,7 @@ jobs: target: "lint" baseimg: "docker" steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v4 with: diff --git a/.github/workflows/sync-device-classes.yml b/.github/workflows/sync-device-classes.yml index 7067300826..1759db962c 100644 --- a/.github/workflows/sync-device-classes.yml +++ b/.github/workflows/sync-device-classes.yml @@ -14,10 +14,10 @@ jobs: if: github.repository == 'esphome/esphome' steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Checkout Home Assistant - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: home-assistant/core path: lib/home-assistant From 32b24726ed5bc9dfa18c8a8d3bb41c2f25d65144 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Tue, 5 Sep 2023 17:01:28 +1000 Subject: [PATCH 065/111] Add Lilygo T-Embed to st7789v display config. (#5337) * Add Lilygo T-Embed to st7789v display config. * Move all configuration into the Python code. Add presets for TTGO. All preset configuration can be overridden. * Add Adafruit S2 pin presets * Add test * Add funhouse pins. Co-authored-by: Keith Burzinski * Keep ordering of options consistent * Remove unused declarations --------- Co-authored-by: Keith Burzinski --- esphome/components/st7789v/display.py | 133 ++++++++++++++++++------- esphome/components/st7789v/st7789v.cpp | 62 ++---------- esphome/components/st7789v/st7789v.h | 13 +-- tests/test2.yaml | 8 ++ 4 files changed, 112 insertions(+), 104 deletions(-) diff --git a/esphome/components/st7789v/display.py b/esphome/components/st7789v/display.py index 16c1e790bd..ad152bf356 100644 --- a/esphome/components/st7789v/display.py +++ b/esphome/components/st7789v/display.py @@ -12,6 +12,8 @@ from esphome.const import ( CONF_RESET_PIN, CONF_WIDTH, CONF_POWER_SUPPLY, + CONF_ROTATION, + CONF_CS_PIN, ) from . import st7789v_ns @@ -26,48 +28,106 @@ DEPENDENCIES = ["spi"] ST7789V = st7789v_ns.class_( "ST7789V", cg.PollingComponent, spi.SPIDevice, display.DisplayBuffer ) -ST7789VRef = ST7789V.operator("ref") -ST7789VModel = st7789v_ns.enum("ST7789VModel") + +MODEL_PRESETS = "model_presets" +REQUIRE_PS = "require_ps" + + +def model_spec(require_ps=False, presets=None): + if presets is None: + presets = {} + return {MODEL_PRESETS: presets, REQUIRE_PS: require_ps} + MODELS = { - "TTGO_TDISPLAY_135X240": ST7789VModel.ST7789V_MODEL_TTGO_TDISPLAY_135_240, - "ADAFRUIT_FUNHOUSE_240X240": ST7789VModel.ST7789V_MODEL_ADAFRUIT_FUNHOUSE_240_240, - "ADAFRUIT_RR_280X240": ST7789VModel.ST7789V_MODEL_ADAFRUIT_RR_280_240, - "ADAFRUIT_S2_TFT_FEATHER_240X135": ST7789VModel.ST7789V_MODEL_ADAFRUIT_S2_TFT_FEATHER_240_135, - "CUSTOM": ST7789VModel.ST7789V_MODEL_CUSTOM, + "TTGO_TDISPLAY_135X240": model_spec( + presets={ + CONF_HEIGHT: 240, + CONF_WIDTH: 135, + CONF_OFFSET_HEIGHT: 52, + CONF_OFFSET_WIDTH: 40, + CONF_CS_PIN: "GPIO5", + CONF_DC_PIN: "GPIO16", + CONF_RESET_PIN: "GPIO23", + CONF_BACKLIGHT_PIN: "GPIO4", + } + ), + "ADAFRUIT_FUNHOUSE_240X240": model_spec( + presets={ + CONF_HEIGHT: 240, + CONF_WIDTH: 240, + CONF_OFFSET_HEIGHT: 0, + CONF_OFFSET_WIDTH: 0, + CONF_CS_PIN: "GPIO40", + CONF_DC_PIN: "GPIO39", + CONF_RESET_PIN: "GPIO41", + } + ), + "ADAFRUIT_RR_280X240": model_spec( + presets={ + CONF_HEIGHT: 280, + CONF_WIDTH: 240, + CONF_OFFSET_HEIGHT: 0, + CONF_OFFSET_WIDTH: 20, + } + ), + "ADAFRUIT_S2_TFT_FEATHER_240X135": model_spec( + require_ps=True, + presets={ + CONF_HEIGHT: 240, + CONF_WIDTH: 135, + CONF_OFFSET_HEIGHT: 52, + CONF_OFFSET_WIDTH: 40, + CONF_CS_PIN: "GPIO7", + CONF_DC_PIN: "GPIO39", + CONF_RESET_PIN: "GPIO40", + CONF_BACKLIGHT_PIN: "GPIO45", + }, + ), + "LILYGO_T-EMBED_170X320": model_spec( + presets={ + CONF_HEIGHT: 320, + CONF_WIDTH: 170, + CONF_OFFSET_HEIGHT: 35, + CONF_OFFSET_WIDTH: 0, + CONF_ROTATION: 270, + CONF_CS_PIN: "GPIO10", + CONF_DC_PIN: "GPIO13", + CONF_RESET_PIN: "GPIO9", + CONF_BACKLIGHT_PIN: "GPIO15", + } + ), + "CUSTOM": model_spec(), } -ST7789V_MODEL = cv.enum(MODELS, upper=True, space="_") - def validate_st7789v(config): - if config[CONF_MODEL].upper() == "CUSTOM" and ( - CONF_HEIGHT not in config - or CONF_WIDTH not in config - or CONF_OFFSET_HEIGHT not in config - or CONF_OFFSET_WIDTH not in config - ): - raise cv.Invalid( - f'{CONF_HEIGHT}, {CONF_WIDTH}, {CONF_OFFSET_HEIGHT} and {CONF_OFFSET_WIDTH} must be specified when {CONF_MODEL} is "CUSTOM"' - ) + model_data = MODELS[config[CONF_MODEL]] + presets = model_data[MODEL_PRESETS] + for key, value in presets.items(): + if key not in config: + if key.endswith("pin"): + # All pins are output. + value = pins.gpio_output_pin_schema(value) + config[key] = value - if config[CONF_MODEL].upper() != "CUSTOM" and ( - CONF_HEIGHT in config - or CONF_WIDTH in config - or CONF_OFFSET_HEIGHT in config - or CONF_OFFSET_WIDTH in config - ): + if model_data[REQUIRE_PS] and CONF_POWER_SUPPLY not in config: raise cv.Invalid( - f'Do not specify {CONF_HEIGHT}, {CONF_WIDTH}, {CONF_OFFSET_HEIGHT} or {CONF_OFFSET_WIDTH} when using {CONF_MODEL} that is not "CUSTOM"' + f'{CONF_POWER_SUPPLY} must be specified when {CONF_MODEL} is {config[CONF_MODEL]}"' ) if ( - config[CONF_MODEL].upper() == "ADAFRUIT_S2_TFT_FEATHER_240X135" - and CONF_POWER_SUPPLY not in config + CONF_OFFSET_WIDTH not in config + or CONF_OFFSET_HEIGHT not in config + or CONF_HEIGHT not in config + or CONF_WIDTH not in config ): raise cv.Invalid( - f'{CONF_POWER_SUPPLY} must be specified when {CONF_MODEL} is "ADAFRUIT_S2_TFT_FEATHER_240X135"' + f"{CONF_HEIGHT}, {CONF_WIDTH}, {CONF_OFFSET_HEIGHT} and {CONF_OFFSET_WIDTH} must all be specified" ) + if CONF_DC_PIN not in config or CONF_RESET_PIN not in config: + raise cv.Invalid(f"both {CONF_DC_PIN} and {CONF_RESET_PIN} must be specified") + return config @@ -75,9 +135,9 @@ CONFIG_SCHEMA = cv.All( display.FULL_DISPLAY_SCHEMA.extend( { cv.GenerateID(): cv.declare_id(ST7789V), - cv.Required(CONF_MODEL): ST7789V_MODEL, - cv.Required(CONF_RESET_PIN): pins.gpio_output_pin_schema, - cv.Required(CONF_DC_PIN): pins.gpio_output_pin_schema, + cv.Required(CONF_MODEL): cv.one_of(*MODELS.keys(), upper=True, space="_"), + cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_DC_PIN): pins.gpio_output_pin_schema, cv.Optional(CONF_BACKLIGHT_PIN): pins.gpio_output_pin_schema, cv.Optional(CONF_POWER_SUPPLY): cv.use_id(power_supply.PowerSupply), cv.Optional(CONF_EIGHTBITCOLOR, default=False): cv.boolean, @@ -99,13 +159,12 @@ async def to_code(config): await display.register_display(var, config) await spi.register_spi_device(var, config) - cg.add(var.set_model(config[CONF_MODEL])) + cg.add(var.set_model_str(config[CONF_MODEL])) - if config[CONF_MODEL].upper() == "CUSTOM": - cg.add(var.set_height(config[CONF_HEIGHT])) - cg.add(var.set_width(config[CONF_WIDTH])) - cg.add(var.set_offset_height(config[CONF_OFFSET_HEIGHT])) - cg.add(var.set_offset_width(config[CONF_OFFSET_WIDTH])) + cg.add(var.set_height(config[CONF_HEIGHT])) + cg.add(var.set_width(config[CONF_WIDTH])) + cg.add(var.set_offset_height(config[CONF_OFFSET_HEIGHT])) + cg.add(var.set_offset_width(config[CONF_OFFSET_WIDTH])) cg.add(var.set_eightbitcolor(config[CONF_EIGHTBITCOLOR])) diff --git a/esphome/components/st7789v/st7789v.cpp b/esphome/components/st7789v/st7789v.cpp index f29182e634..a181723546 100644 --- a/esphome/components/st7789v/st7789v.cpp +++ b/esphome/components/st7789v/st7789v.cpp @@ -122,11 +122,11 @@ void ST7789V::setup() { void ST7789V::dump_config() { LOG_DISPLAY("", "SPI ST7789V", this); - ESP_LOGCONFIG(TAG, " Model: %s", this->model_str_()); - if (this->model_ == ST7789V_MODEL_CUSTOM) { - ESP_LOGCONFIG(TAG, " Height Offset: %u", this->offset_height_); - ESP_LOGCONFIG(TAG, " Width Offset: %u", this->offset_width_); - } + ESP_LOGCONFIG(TAG, " Model: %s", this->model_str_); + ESP_LOGCONFIG(TAG, " Height: %u", this->height_); + ESP_LOGCONFIG(TAG, " Width: %u", this->width_); + ESP_LOGCONFIG(TAG, " Height Offset: %u", this->offset_height_); + ESP_LOGCONFIG(TAG, " Width Offset: %u", this->offset_width_); ESP_LOGCONFIG(TAG, " 8-bit color mode: %s", YESNO(this->eightbitcolor_)); LOG_PIN(" CS Pin: ", this->cs_); LOG_PIN(" DC Pin: ", this->dc_pin_); @@ -145,42 +145,7 @@ void ST7789V::update() { this->write_display_data(); } -void ST7789V::set_model(ST7789VModel model) { - this->model_ = model; - - switch (this->model_) { - case ST7789V_MODEL_TTGO_TDISPLAY_135_240: - this->height_ = 240; - this->width_ = 135; - this->offset_height_ = 52; - this->offset_width_ = 40; - break; - - case ST7789V_MODEL_ADAFRUIT_FUNHOUSE_240_240: - this->height_ = 240; - this->width_ = 240; - this->offset_height_ = 0; - this->offset_width_ = 0; - break; - - case ST7789V_MODEL_ADAFRUIT_RR_280_240: - this->height_ = 280; - this->width_ = 240; - this->offset_height_ = 0; - this->offset_width_ = 20; - break; - - case ST7789V_MODEL_ADAFRUIT_S2_TFT_FEATHER_240_135: - this->height_ = 240; - this->width_ = 135; - this->offset_height_ = 52; - this->offset_width_ = 40; - break; - - default: - break; - } -} +void ST7789V::set_model_str(const char *model_str) { this->model_str_ = model_str; } void ST7789V::write_display_data() { uint16_t x1 = this->offset_height_; @@ -339,20 +304,5 @@ void HOT ST7789V::draw_absolute_pixel_internal(int x, int y, Color color) { } } -const char *ST7789V::model_str_() { - switch (this->model_) { - case ST7789V_MODEL_TTGO_TDISPLAY_135_240: - return "TTGO T-Display 135x240"; - case ST7789V_MODEL_ADAFRUIT_FUNHOUSE_240_240: - return "Adafruit Funhouse 240x240"; - case ST7789V_MODEL_ADAFRUIT_RR_280_240: - return "Adafruit Round-Rectangular 280x240"; - case ST7789V_MODEL_ADAFRUIT_S2_TFT_FEATHER_240_135: - return "Adafruit ESP32-S2 TFT Feather"; - default: - return "Custom"; - } -} - } // namespace st7789v } // namespace esphome diff --git a/esphome/components/st7789v/st7789v.h b/esphome/components/st7789v/st7789v.h index 56132e8ea2..22093301e2 100644 --- a/esphome/components/st7789v/st7789v.h +++ b/esphome/components/st7789v/st7789v.h @@ -10,14 +10,6 @@ namespace esphome { namespace st7789v { -enum ST7789VModel { - ST7789V_MODEL_TTGO_TDISPLAY_135_240, - ST7789V_MODEL_ADAFRUIT_FUNHOUSE_240_240, - ST7789V_MODEL_ADAFRUIT_RR_280_240, - ST7789V_MODEL_ADAFRUIT_S2_TFT_FEATHER_240_135, - ST7789V_MODEL_CUSTOM -}; - static const uint8_t ST7789_NOP = 0x00; // No Operation static const uint8_t ST7789_SWRESET = 0x01; // Software Reset static const uint8_t ST7789_RDDID = 0x04; // Read Display ID @@ -120,7 +112,7 @@ class ST7789V : public PollingComponent, public spi::SPIDevice { public: - void set_model(ST7789VModel model); + void set_model_str(const char *model_str); void set_dc_pin(GPIOPin *dc_pin) { this->dc_pin_ = dc_pin; } void set_reset_pin(GPIOPin *reset_pin) { this->reset_pin_ = reset_pin; } void set_backlight_pin(GPIOPin *backlight_pin) { this->backlight_pin_ = backlight_pin; } @@ -146,7 +138,6 @@ class ST7789V : public PollingComponent, display::DisplayType get_display_type() override { return display::DisplayType::DISPLAY_TYPE_COLOR; } protected: - ST7789VModel model_{ST7789V_MODEL_TTGO_TDISPLAY_135_240}; GPIOPin *dc_pin_{nullptr}; GPIOPin *reset_pin_{nullptr}; GPIOPin *backlight_pin_{nullptr}; @@ -175,7 +166,7 @@ class ST7789V : public PollingComponent, void draw_absolute_pixel_internal(int x, int y, Color color) override; - const char *model_str_(); + const char *model_str_; }; } // namespace st7789v diff --git a/tests/test2.yaml b/tests/test2.yaml index d1508632b3..adc57f4f7e 100644 --- a/tests/test2.yaml +++ b/tests/test2.yaml @@ -710,6 +710,14 @@ interval: - logger.log: Interval Run display: + - platform: st7789v + model: LILYGO_T-EMBED_170X320 + height: 320 + width: 170 + offset_height: 35 + offset_width: 0 + dc_pin: GPIO13 + reset_pin: GPIO9 image: - id: binary_image From 97dcbe84da0c8ff9d6044e7d2d059c0161b5170a Mon Sep 17 00:00:00 2001 From: Jimmy Hedman Date: Tue, 5 Sep 2023 09:56:17 +0200 Subject: [PATCH 066/111] Disable IPv6 when config explicitly says false (#5310) --- .../ethernet/ethernet_component.cpp | 20 +++++++++---------- esphome/components/network/__init__.py | 1 + .../wifi/wifi_component_esp32_arduino.cpp | 8 ++++---- .../wifi/wifi_component_esp_idf.cpp | 14 ++++++------- 4 files changed, 22 insertions(+), 21 deletions(-) diff --git a/esphome/components/ethernet/ethernet_component.cpp b/esphome/components/ethernet/ethernet_component.cpp index d9004a913b..59d2e4c4d6 100644 --- a/esphome/components/ethernet/ethernet_component.cpp +++ b/esphome/components/ethernet/ethernet_component.cpp @@ -118,10 +118,10 @@ void EthernetComponent::setup() { ESPHL_ERROR_CHECK(err, "ETH event handler register error"); err = esp_event_handler_register(IP_EVENT, IP_EVENT_ETH_GOT_IP, &EthernetComponent::got_ip_event_handler, nullptr); ESPHL_ERROR_CHECK(err, "GOT IP event handler register error"); -#if LWIP_IPV6 +#if ENABLE_IPV6 err = esp_event_handler_register(IP_EVENT, IP_EVENT_GOT_IP6, &EthernetComponent::got_ip6_event_handler, nullptr); ESPHL_ERROR_CHECK(err, "GOT IP6 event handler register error"); -#endif /* LWIP_IPV6 */ +#endif /* ENABLE_IPV6 */ /* start Ethernet driver state machine */ err = esp_eth_start(this->eth_handle_); @@ -164,7 +164,7 @@ void EthernetComponent::loop() { this->state_ = EthernetComponentState::CONNECTING; this->start_connect_(); } -#if LWIP_IPV6 +#if ENABLE_IPV6 else if (this->got_ipv6_) { esp_ip6_addr_t ip6_addr; if (esp_netif_get_ip6_global(this->eth_netif_, &ip6_addr) == 0 && @@ -177,7 +177,7 @@ void EthernetComponent::loop() { this->got_ipv6_ = false; } -#endif /* LWIP_IPV6 */ +#endif /* ENABLE_IPV6 */ break; } } @@ -272,14 +272,14 @@ void EthernetComponent::got_ip_event_handler(void *arg, esp_event_base_t event_b ESP_LOGV(TAG, "[Ethernet event] ETH Got IP (num=%" PRId32 ")", event_id); } -#if LWIP_IPV6 +#if ENABLE_IPV6 void EthernetComponent::got_ip6_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) { ESP_LOGV(TAG, "[Ethernet event] ETH Got IP6 (num=%d)", event_id); global_eth_component->got_ipv6_ = true; global_eth_component->ipv6_count_ += 1; } -#endif /* LWIP_IPV6 */ +#endif /* ENABLE_IPV6 */ void EthernetComponent::start_connect_() { this->connect_begin_ = millis(); @@ -343,12 +343,12 @@ void EthernetComponent::start_connect_() { if (err != ESP_ERR_ESP_NETIF_DHCP_ALREADY_STARTED) { ESPHL_ERROR_CHECK(err, "DHCPC start error"); } -#if LWIP_IPV6 +#if ENABLE_IPV6 err = esp_netif_create_ip6_linklocal(this->eth_netif_); if (err != ESP_OK) { ESPHL_ERROR_CHECK(err, "IPv6 local failed"); } -#endif /* LWIP_IPV6 */ +#endif /* ENABLE_IPV6 */ } this->connect_begin_ = millis(); @@ -376,7 +376,7 @@ void EthernetComponent::dump_connect_params_() { ESP_LOGCONFIG(TAG, " DNS2: %s", network::IPAddress(dns_ip2->addr).str().c_str()); #endif -#if LWIP_IPV6 +#if ENABLE_IPV6 if (this->ipv6_count_ > 0) { esp_ip6_addr_t ip6_addr; esp_netif_get_ip6_linklocal(this->eth_netif_, &ip6_addr); @@ -387,7 +387,7 @@ void EthernetComponent::dump_connect_params_() { ESP_LOGCONFIG(TAG, "IPv6 Addr (Global): " IPV6STR, IPV62STR(ip6_addr)); } } -#endif /* LWIP_IPV6 */ +#endif /* ENABLE_IPV6 */ esp_err_t err; diff --git a/esphome/components/network/__init__.py b/esphome/components/network/__init__.py index cd29734f42..83778e0bf4 100644 --- a/esphome/components/network/__init__.py +++ b/esphome/components/network/__init__.py @@ -24,6 +24,7 @@ CONFIG_SCHEMA = cv.Schema( async def to_code(config): if CONF_ENABLE_IPV6 in config: + cg.add_define("ENABLE_IPV6", config[CONF_ENABLE_IPV6]) if CORE.using_esp_idf: add_idf_sdkconfig_option("CONFIG_LWIP_IPV6", config[CONF_ENABLE_IPV6]) add_idf_sdkconfig_option( diff --git a/esphome/components/wifi/wifi_component_esp32_arduino.cpp b/esphome/components/wifi/wifi_component_esp32_arduino.cpp index 95f4e2ce92..5b147b20c6 100644 --- a/esphome/components/wifi/wifi_component_esp32_arduino.cpp +++ b/esphome/components/wifi/wifi_component_esp32_arduino.cpp @@ -447,9 +447,9 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ buf[it.ssid_len] = '\0'; ESP_LOGV(TAG, "Event: Connected ssid='%s' bssid=" LOG_SECRET("%s") " channel=%u, authmode=%s", buf, format_mac_addr(it.bssid).c_str(), it.channel, get_auth_mode_str(it.authmode)); -#if LWIP_IPV6 +#if ENABLE_IPV6 this->set_timeout(100, [] { WiFi.enableIpV6(); }); -#endif /* LWIP_IPV6 */ +#endif /* ENABLE_IPV6 */ break; } @@ -504,13 +504,13 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ s_sta_connecting = false; break; } -#if LWIP_IPV6 +#if ENABLE_IPV6 case ESPHOME_EVENT_ID_WIFI_STA_GOT_IP6: { auto it = info.got_ip6.ip6_info; ESP_LOGV(TAG, "Got IPv6 address=" IPV6STR, IPV62STR(it.ip)); break; } -#endif /* LWIP_IPV6 */ +#endif /* ENABLE_IPV6 */ case ESPHOME_EVENT_ID_WIFI_STA_LOST_IP: { ESP_LOGV(TAG, "Event: Lost IP"); break; diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp index 9041679ccf..0ff9e932b2 100644 --- a/esphome/components/wifi/wifi_component_esp_idf.cpp +++ b/esphome/components/wifi/wifi_component_esp_idf.cpp @@ -58,9 +58,9 @@ struct IDFWiFiEvent { wifi_event_ap_probe_req_rx_t ap_probe_req_rx; wifi_event_bss_rssi_low_t bss_rssi_low; ip_event_got_ip_t ip_got_ip; -#if LWIP_IPV6 +#if ENABLE_IPV6 ip_event_got_ip6_t ip_got_ip6; -#endif +#endif /* ENABLE_IPV6 */ ip_event_ap_staipassigned_t ip_ap_staipassigned; } data; }; @@ -84,7 +84,7 @@ void event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, voi memcpy(&event.data.sta_disconnected, event_data, sizeof(wifi_event_sta_disconnected_t)); } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) { memcpy(&event.data.ip_got_ip, event_data, sizeof(ip_event_got_ip_t)); -#if LWIP_IPV6 +#if ENABLE_IPV6 } else if (event_base == IP_EVENT && event_id == IP_EVENT_GOT_IP6) { memcpy(&event.data.ip_got_ip6, event_data, sizeof(ip_event_got_ip6_t)); #endif @@ -645,18 +645,18 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) { } else if (data->event_base == IP_EVENT && data->event_id == IP_EVENT_STA_GOT_IP) { const auto &it = data->data.ip_got_ip; -#if LWIP_IPV6_AUTOCONFIG +#if ENABLE_IPV6 esp_netif_create_ip6_linklocal(s_sta_netif); -#endif +#endif /* ENABLE_IPV6 */ ESP_LOGV(TAG, "Event: Got IP static_ip=%s gateway=%s", format_ip4_addr(it.ip_info.ip).c_str(), format_ip4_addr(it.ip_info.gw).c_str()); s_sta_got_ip = true; -#if LWIP_IPV6 +#if ENABLE_IPV6 } else if (data->event_base == IP_EVENT && data->event_id == IP_EVENT_GOT_IP6) { const auto &it = data->data.ip_got_ip6; ESP_LOGV(TAG, "Event: Got IPv6 address=%s", format_ip6_addr(it.ip6_info.ip).c_str()); -#endif +#endif /* ENABLE_IPV6 */ } else if (data->event_base == IP_EVENT && data->event_id == IP_EVENT_STA_LOST_IP) { ESP_LOGV(TAG, "Event: Lost IP"); From b7a16d5a5968122c9ec9e2937da826b4eb93b274 Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Tue, 5 Sep 2023 05:35:20 -0500 Subject: [PATCH 067/111] Add defines.h to ethernet_component.h for ENABLE_IPV6 (#5344) --- esphome/components/ethernet/ethernet_component.h | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/ethernet/ethernet_component.h b/esphome/components/ethernet/ethernet_component.h index 1bd4786b44..11f50af966 100644 --- a/esphome/components/ethernet/ethernet_component.h +++ b/esphome/components/ethernet/ethernet_component.h @@ -1,6 +1,7 @@ #pragma once #include "esphome/core/component.h" +#include "esphome/core/defines.h" #include "esphome/core/hal.h" #include "esphome/components/network/ip_address.h" From e2d784a5b5dfbf8d6be7f09d3f38ba172272349c Mon Sep 17 00:00:00 2001 From: esphomebot Date: Wed, 6 Sep 2023 07:29:41 +1200 Subject: [PATCH 068/111] Synchronise Device Classes from Home Assistant (#5328) --- esphome/const.py | 1 - 1 file changed, 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index d0575a6ebd..067fd23946 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -980,7 +980,6 @@ DEVICE_CLASS_DURATION = "duration" DEVICE_CLASS_EMPTY = "" DEVICE_CLASS_ENERGY = "energy" DEVICE_CLASS_ENERGY_STORAGE = "energy_storage" -DEVICE_CLASS_ENUM = "enum" DEVICE_CLASS_FREQUENCY = "frequency" DEVICE_CLASS_GARAGE = "garage" DEVICE_CLASS_GARAGE_DOOR = "garage_door" From 35b5dadb99f1a22b9a8d3c5ff8603537cb7292e7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 6 Sep 2023 07:32:00 +1200 Subject: [PATCH 069/111] Bump pytest from 7.4.0 to 7.4.1 (#5342) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 7ab6742b02..e160c3e9e3 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -5,7 +5,7 @@ pyupgrade==3.10.1 # also change in .pre-commit-config.yaml when updating pre-commit # Unit tests -pytest==7.4.0 +pytest==7.4.1 pytest-cov==4.1.0 pytest-mock==3.11.1 pytest-asyncio==0.21.1 From 47735d1dae8d2e66eb8b755afc0de3d68d3020af Mon Sep 17 00:00:00 2001 From: Pavlo Dudnytskyi Date: Tue, 5 Sep 2023 21:37:01 +0200 Subject: [PATCH 070/111] Fixed default temperature step values for haier climate (#5330) --- esphome/components/haier/climate.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/esphome/components/haier/climate.py b/esphome/components/haier/climate.py index acb079c822..d796f13581 100644 --- a/esphome/components/haier/climate.py +++ b/esphome/components/haier/climate.py @@ -136,12 +136,10 @@ def validate_visual(config): f"Configured visual temperature step {temp_step} is wrong, it should be a multiple of 0.5" ) else: - config[CONF_VISUAL][CONF_TEMPERATURE_STEP] = ( - { - CONF_TARGET_TEMPERATURE: PROTOCOL_TARGET_TEMPERATURE_STEP, - CONF_CURRENT_TEMPERATURE: PROTOCOL_CURRENT_TEMPERATURE_STEP, - }, - ) + config[CONF_VISUAL][CONF_TEMPERATURE_STEP] = { + CONF_TARGET_TEMPERATURE: PROTOCOL_TARGET_TEMPERATURE_STEP, + CONF_CURRENT_TEMPERATURE: PROTOCOL_CURRENT_TEMPERATURE_STEP, + } else: config[CONF_VISUAL] = { CONF_MIN_TEMPERATURE: PROTOCOL_MIN_TEMPERATURE, From ac5c6ec2883115e68cf31d51118f36cbdf8de011 Mon Sep 17 00:00:00 2001 From: Jimmy Hedman Date: Tue, 5 Sep 2023 21:38:58 +0200 Subject: [PATCH 071/111] Add debug component to all tests (#5333) --- tests/test2.yaml | 2 ++ tests/test3.1.yaml | 2 ++ tests/test3.yaml | 2 ++ tests/test4.yaml | 2 ++ tests/test6.yaml | 2 ++ tests/test7.yaml | 2 ++ 6 files changed, 12 insertions(+) diff --git a/tests/test2.yaml b/tests/test2.yaml index adc57f4f7e..4928b8b877 100644 --- a/tests/test2.yaml +++ b/tests/test2.yaml @@ -55,6 +55,8 @@ ota: logger: level: DEBUG +debug: + deep_sleep: run_duration: default: 20s diff --git a/tests/test3.1.yaml b/tests/test3.1.yaml index ea8dc337be..16f31409d8 100644 --- a/tests/test3.1.yaml +++ b/tests/test3.1.yaml @@ -39,6 +39,8 @@ ota: logger: +debug: + sensor: - platform: apds9960 type: proximity diff --git a/tests/test3.yaml b/tests/test3.yaml index 471b7d97b6..abfd133c99 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -287,6 +287,8 @@ logger: level: DEBUG esp8266_store_log_strings_in_flash: true +debug: + improv_serial: next_url: https://esphome.io/?name={{device_name}}&version={{esphome_version}}&ip={{ip_address}} diff --git a/tests/test4.yaml b/tests/test4.yaml index 54caebf1fe..1175bb207c 100644 --- a/tests/test4.yaml +++ b/tests/test4.yaml @@ -50,6 +50,8 @@ ota: logger: level: DEBUG +debug: + web_server: ota: false auth: diff --git a/tests/test6.yaml b/tests/test6.yaml index 6224563a77..f048a4fa14 100644 --- a/tests/test6.yaml +++ b/tests/test6.yaml @@ -22,6 +22,8 @@ ota: logger: +debug: + binary_sensor: - platform: gpio pin: GPIO5 diff --git a/tests/test7.yaml b/tests/test7.yaml index 8d48c9a601..2355dd6feb 100644 --- a/tests/test7.yaml +++ b/tests/test7.yaml @@ -28,6 +28,8 @@ esphome: logger: +debug: + http_request: useragent: esphome/tagreader timeout: 10s From 82c1988a2d925f78e47036b8c002a2c78d9503ff Mon Sep 17 00:00:00 2001 From: JJ Date: Tue, 5 Sep 2023 14:59:23 -0700 Subject: [PATCH 072/111] Support MaxBotix XL in addition to HRXL (#4510) --- .../hrxl_maxsonar_wr/hrxl_maxsonar_wr.cpp | 27 +++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/esphome/components/hrxl_maxsonar_wr/hrxl_maxsonar_wr.cpp b/esphome/components/hrxl_maxsonar_wr/hrxl_maxsonar_wr.cpp index bd1c82c96b..b56e96badc 100644 --- a/esphome/components/hrxl_maxsonar_wr/hrxl_maxsonar_wr.cpp +++ b/esphome/components/hrxl_maxsonar_wr/hrxl_maxsonar_wr.cpp @@ -1,9 +1,10 @@ // Official Datasheet: -// https://www.maxbotix.com/documents/HRXL-MaxSonar-WR_Datasheet.pdf +// HRXL: https://www.maxbotix.com/documents/HRXL-MaxSonar-WR_Datasheet.pdf +// XL: https://www.maxbotix.com/documents/XL-MaxSonar-WR_Datasheet.pdf // // This implementation is designed to work with the TTL Versions of the -// MaxBotix HRXL MaxSonar WR sensor series. The sensor's TTL Pin (5) should be -// wired to one of the ESP's input pins and configured as uart rx_pin. +// MaxBotix HRXL and XL MaxSonar WR sensor series. The sensor's TTL Pin (5) +// should be wired to one of the ESP's input pins and configured as uart rx_pin. #include "hrxl_maxsonar_wr.h" #include "esphome/core/log.h" @@ -17,8 +18,10 @@ static const uint8_t ASCII_NBSP = 0xFF; static const int MAX_DATA_LENGTH_BYTES = 6; /** - * The sensor outputs something like "R1234\r" at a fixed rate of 6 Hz. Where - * 1234 means a distance of 1,234 m. + * HRXL sensors output the format "R1234\r" at 6Hz + * The 1234 means 1234mm + * XL sensors output the format "R123\r" at 5 to 10Hz + * The 123 means 123cm */ void HrxlMaxsonarWrComponent::loop() { uint8_t data; @@ -42,9 +45,17 @@ void HrxlMaxsonarWrComponent::check_buffer_() { if (this->buffer_.back() == static_cast(ASCII_CR) || this->buffer_.length() >= MAX_DATA_LENGTH_BYTES) { ESP_LOGV(TAG, "Read from serial: %s", this->buffer_.c_str()); - if (this->buffer_.length() == MAX_DATA_LENGTH_BYTES && this->buffer_[0] == 'R' && - this->buffer_.back() == static_cast(ASCII_CR)) { - int millimeters = parse_number(this->buffer_.substr(1, MAX_DATA_LENGTH_BYTES - 2)).value_or(0); + size_t rpos = this->buffer_.find(static_cast(ASCII_CR)); + + if (this->buffer_.length() <= MAX_DATA_LENGTH_BYTES && this->buffer_[0] == 'R' && rpos != std::string::npos) { + std::string distance = this->buffer_.substr(1, rpos - 1); + int millimeters = parse_number(distance).value_or(0); + + // XL reports in cm instead of mm and reports 3 digits instead of 4 + if (distance.length() == 3) { + millimeters = millimeters * 10; + } + float meters = float(millimeters) / 1000.0; ESP_LOGV(TAG, "Distance from sensor: %d mm, %f m", millimeters, meters); this->publish_state(meters); From 74ab940aff7289b2183fe315cd5665e4ba1d36f1 Mon Sep 17 00:00:00 2001 From: JJ Date: Tue, 5 Sep 2023 15:09:22 -0700 Subject: [PATCH 073/111] Adding DFRobot Ozone Sensor Support (sen0321) (#4782) --- CODEOWNERS | 1 + esphome/components/sen0321/__init__.py | 1 + esphome/components/sen0321/sen0321.cpp | 36 ++++++++++++++++++++++++++ esphome/components/sen0321/sen0321.h | 35 +++++++++++++++++++++++++ esphome/components/sen0321/sensor.py | 34 ++++++++++++++++++++++++ tests/test1.yaml | 5 ++++ 6 files changed, 112 insertions(+) create mode 100644 esphome/components/sen0321/__init__.py create mode 100644 esphome/components/sen0321/sen0321.cpp create mode 100644 esphome/components/sen0321/sen0321.h create mode 100644 esphome/components/sen0321/sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index 1455643550..384db8098f 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -246,6 +246,7 @@ esphome/components/sdm_meter/* @jesserockz @polyfaces esphome/components/sdp3x/* @Azimath esphome/components/selec_meter/* @sourabhjaiswal esphome/components/select/* @esphome/core +esphome/components/sen0321/* @notjj esphome/components/sen21231/* @shreyaskarnik esphome/components/sen5x/* @martgras esphome/components/sensirion_common/* @martgras diff --git a/esphome/components/sen0321/__init__.py b/esphome/components/sen0321/__init__.py new file mode 100644 index 0000000000..458ffa67f9 --- /dev/null +++ b/esphome/components/sen0321/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@notjj"] diff --git a/esphome/components/sen0321/sen0321.cpp b/esphome/components/sen0321/sen0321.cpp new file mode 100644 index 0000000000..7801c8c389 --- /dev/null +++ b/esphome/components/sen0321/sen0321.cpp @@ -0,0 +1,36 @@ +#include "sen0321.h" +#include "esphome/core/log.h" +#include "esphome/core/hal.h" + +namespace esphome { +namespace sen0321_sensor { + +static const char *const TAG = "sen0321_sensor.sensor"; + +void Sen0321Sensor::setup() { + ESP_LOGCONFIG(TAG, "Setting up sen0321..."); + if (!this->write_byte(SENSOR_MODE_REGISTER, SENSOR_MODE_AUTO)) { + ESP_LOGW(TAG, "Error setting measurement mode."); + this->mark_failed(); + }; +} + +void Sen0321Sensor::update() { this->read_data_(); } + +void Sen0321Sensor::dump_config() { + ESP_LOGCONFIG(TAG, "DF Robot Ozone Sensor sen0321:"); + LOG_I2C_DEVICE(this); + if (this->is_failed()) { + ESP_LOGE(TAG, "Communication with sen0321 failed!"); + } + LOG_UPDATE_INTERVAL(this); +} + +void Sen0321Sensor::read_data_() { + uint8_t result[2]; + this->read_bytes(SENSOR_AUTO_READ_REG, result, (uint8_t) 2); + this->publish_state(((uint16_t) (result[0] << 8) + result[1])); +} + +} // namespace sen0321_sensor +} // namespace esphome diff --git a/esphome/components/sen0321/sen0321.h b/esphome/components/sen0321/sen0321.h new file mode 100644 index 0000000000..3bb3d5b015 --- /dev/null +++ b/esphome/components/sen0321/sen0321.h @@ -0,0 +1,35 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/i2c/i2c.h" + +// ref: +// https://github.com/DFRobot/DFRobot_OzoneSensor + +namespace esphome { +namespace sen0321_sensor { +// Sensor Mode +// While passive is supposedly supported, it does not appear to work reliably. +static const uint8_t SENSOR_MODE_REGISTER = 0x03; +static const uint8_t SENSOR_MODE_AUTO = 0x00; +static const uint8_t SENSOR_MODE_PASSIVE = 0x01; +static const uint8_t SET_REGISTER = 0x04; + +// Each register is 2 wide, so 0x07-0x08 for passive, or 0x09-0x0A for auto +// First register is high bits, next low. +static const uint8_t SENSOR_PASS_READ_REG = 0x07; +static const uint8_t SENSOR_AUTO_READ_REG = 0x09; + +class Sen0321Sensor : public sensor::Sensor, public PollingComponent, public i2c::I2CDevice { + public: + void update() override; + void dump_config() override; + void setup() override; + + protected: + void read_data_(); +}; + +} // namespace sen0321_sensor +} // namespace esphome diff --git a/esphome/components/sen0321/sensor.py b/esphome/components/sen0321/sensor.py new file mode 100644 index 0000000000..ee613dc440 --- /dev/null +++ b/esphome/components/sen0321/sensor.py @@ -0,0 +1,34 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c, sensor +from esphome.const import ( + ICON_CHEMICAL_WEAPON, + UNIT_PARTS_PER_BILLION, + STATE_CLASS_MEASUREMENT, +) + +CODEOWNERS = ["@notjj"] +DEPENDENCIES = ["i2c"] + +sen0321_sensor_ns = cg.esphome_ns.namespace("sen0321_sensor") +Sen0321Sensor = sen0321_sensor_ns.class_( + "Sen0321Sensor", cg.PollingComponent, i2c.I2CDevice +) + +CONFIG_SCHEMA = ( + sensor.sensor_schema( + Sen0321Sensor, + unit_of_measurement=UNIT_PARTS_PER_BILLION, + icon=ICON_CHEMICAL_WEAPON, + accuracy_decimals=1, + state_class=STATE_CLASS_MEASUREMENT, + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x73)) +) + + +async def to_code(config): + var = await sensor.new_sensor(config) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) diff --git a/tests/test1.yaml b/tests/test1.yaml index b78407069d..33782dbf53 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -1081,6 +1081,11 @@ sensor: ambient_pressure_compensation: 961mBar temperature_offset: 4.2C i2c_id: i2c_bus + - platform: sen0321 + name: Workshop Ozone Sensor + id: sen0321_ozone + update_interval: 10s + i2c_id: i2c_bus - platform: sgp30 eco2: name: Workshop eCO2 From feba9ffdc453f978ce6530fa63dcae3ee6d82b19 Mon Sep 17 00:00:00 2001 From: Stijn Tintel Date: Wed, 6 Sep 2023 01:11:07 +0300 Subject: [PATCH 074/111] mdns: bump IDF mdns component to 1.2.0 (#5217) --- esphome/components/mdns/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/mdns/__init__.py b/esphome/components/mdns/__init__.py index e7d700d149..fbe1e1a719 100644 --- a/esphome/components/mdns/__init__.py +++ b/esphome/components/mdns/__init__.py @@ -88,7 +88,7 @@ async def to_code(config): add_idf_component( name="mdns", repo="https://github.com/espressif/esp-protocols.git", - ref="mdns-v1.0.9", + ref="mdns-v1.2.0", path="components/mdns", ) From 76ebbfefd2d6f69d58f3ceaf0f2c0cc0b3705d8b Mon Sep 17 00:00:00 2001 From: Christian Date: Tue, 5 Sep 2023 23:33:49 +0100 Subject: [PATCH 075/111] Integration LightwaveRF switches (#4812) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/lightwaverf/LwRx.cpp | 427 ++++++++++++++++++ esphome/components/lightwaverf/LwRx.h | 142 ++++++ esphome/components/lightwaverf/LwTx.cpp | 208 +++++++++ esphome/components/lightwaverf/LwTx.h | 92 ++++ esphome/components/lightwaverf/__init__.py | 83 ++++ .../components/lightwaverf/lightwaverf.cpp | 67 +++ esphome/components/lightwaverf/lightwaverf.h | 70 +++ tests/test3.yaml | 4 + 9 files changed, 1094 insertions(+) create mode 100644 esphome/components/lightwaverf/LwRx.cpp create mode 100644 esphome/components/lightwaverf/LwRx.h create mode 100644 esphome/components/lightwaverf/LwTx.cpp create mode 100644 esphome/components/lightwaverf/LwTx.h create mode 100644 esphome/components/lightwaverf/__init__.py create mode 100644 esphome/components/lightwaverf/lightwaverf.cpp create mode 100644 esphome/components/lightwaverf/lightwaverf.h diff --git a/CODEOWNERS b/CODEOWNERS index 384db8098f..498cfcac01 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -150,6 +150,7 @@ esphome/components/ledc/* @OttoWinter esphome/components/libretiny/* @kuba2k2 esphome/components/libretiny_pwm/* @kuba2k2 esphome/components/light/* @esphome/core +esphome/components/lightwaverf/* @max246 esphome/components/lilygo_t5_47/touchscreen/* @jesserockz esphome/components/lock/* @esphome/core esphome/components/logger/* @esphome/core diff --git a/esphome/components/lightwaverf/LwRx.cpp b/esphome/components/lightwaverf/LwRx.cpp new file mode 100644 index 0000000000..2b1ad5e870 --- /dev/null +++ b/esphome/components/lightwaverf/LwRx.cpp @@ -0,0 +1,427 @@ +// LwRx.cpp +// +// LightwaveRF 434MHz receiver interface for Arduino +// +// Author: Bob Tidey (robert@tideys.net) + +#ifdef USE_ESP8266 + +#include "LwRx.h" +#include + +namespace esphome { +namespace lightwaverf { + +/** + Pin change interrupt routine that identifies 1 and 0 LightwaveRF bits + and constructs a message when a valid packet of data is received. +**/ + +void IRAM_ATTR LwRx::rx_process_bits(LwRx *args) { + uint8_t event = args->rx_pin_isr_.digital_read(); // start setting event to the current value + uint32_t curr = micros(); // the current time in microseconds + + uint16_t dur = (curr - args->rx_prev); // unsigned int + args->rx_prev = curr; + // set event based on input and duration of previous pulse + if (dur < 120) { // 120 very short + } else if (dur < 500) { // normal short pulse + event += 2; + } else if (dur < 2000) { // normal long pulse + event += 4; + } else if (dur > 5000) { // gap between messages + event += 6; + } else { // 2000 > 5000 + event = 8; // illegal gap + } + // state machine transitions + switch (args->rx_state) { + case RX_STATE_IDLE: + switch (event) { + case 7: // 1 after a message gap + args->rx_state = RX_STATE_MSGSTARTFOUND; + break; + } + break; + case RX_STATE_MSGSTARTFOUND: + switch (event) { + case 2: // 0 160->500 + // nothing to do wait for next positive edge + break; + case 3: // 1 160->500 + args->rx_num_bytes = 0; + args->rx_state = RX_STATE_BYTESTARTFOUND; + break; + default: + // not good start again + args->rx_state = RX_STATE_IDLE; + break; + } + break; + case RX_STATE_BYTESTARTFOUND: + switch (event) { + case 2: // 0 160->500 + // nothing to do wait for next positive edge + break; + case 3: // 1 160->500 + args->rx_state = RX_STATE_GETBYTE; + args->rx_num_bits = 0; + break; + case 5: // 0 500->1500 + args->rx_state = RX_STATE_GETBYTE; + // Starts with 0 so put this into uint8_t + args->rx_num_bits = 1; + args->rx_buf[args->rx_num_bytes] = 0; + break; + default: + // not good start again + args->rx_state = RX_STATE_IDLE; + break; + } + break; + case RX_STATE_GETBYTE: + switch (event) { + case 2: // 0 160->500 + // nothing to do wait for next positive edge but do stats + if (args->lwrx_stats_enable) { + args->lwrx_stats[RX_STAT_HIGH_MAX] = std::max(args->lwrx_stats[RX_STAT_HIGH_MAX], dur); + args->lwrx_stats[RX_STAT_HIGH_MIN] = std::min(args->lwrx_stats[RX_STAT_HIGH_MIN], dur); + args->lwrx_stats[RX_STAT_HIGH_AVE] = + args->lwrx_stats[RX_STAT_HIGH_AVE] - (args->lwrx_stats[RX_STAT_HIGH_AVE] >> 4) + dur; + } + break; + case 3: // 1 160->500 + // a single 1 + args->rx_buf[args->rx_num_bytes] = args->rx_buf[args->rx_num_bytes] << 1 | 1; + args->rx_num_bits++; + if (args->lwrx_stats_enable) { + args->lwrx_stats[RX_STAT_LOW1_MAX] = std::max(args->lwrx_stats[RX_STAT_LOW1_MAX], dur); + args->lwrx_stats[RX_STAT_LOW1_MIN] = std::min(args->lwrx_stats[RX_STAT_LOW1_MIN], dur); + args->lwrx_stats[RX_STAT_LOW1_AVE] = + args->lwrx_stats[RX_STAT_LOW1_AVE] - (args->lwrx_stats[RX_STAT_LOW1_AVE] >> 4) + dur; + } + break; + case 5: // 1 500->1500 + // a 1 followed by a 0 + args->rx_buf[args->rx_num_bytes] = args->rx_buf[args->rx_num_bytes] << 2 | 2; + args->rx_num_bits++; + args->rx_num_bits++; + if (args->lwrx_stats_enable) { + args->lwrx_stats[RX_STAT_LOW0_MAX] = std::max(args->lwrx_stats[RX_STAT_LOW0_MAX], dur); + args->lwrx_stats[RX_STAT_LOW0_MIN] = std::min(args->lwrx_stats[RX_STAT_LOW0_MIN], dur); + args->lwrx_stats[RX_STAT_LOW0_AVE] = + args->lwrx_stats[RX_STAT_LOW0_AVE] - (args->lwrx_stats[RX_STAT_LOW0_AVE] >> 4) + dur; + } + break; + default: + // not good start again + args->rx_state = RX_STATE_IDLE; + break; + } + if (args->rx_num_bits >= 8) { + args->rx_num_bytes++; + args->rx_num_bits = 0; + if (args->rx_num_bytes >= RX_MSGLEN) { + uint32_t curr_millis = millis(); + if (args->rx_repeats > 0) { + if ((curr_millis - args->rx_prevpkttime) / 100 > args->rx_timeout) { + args->rx_repeatcount = 1; + } else { + // Test message same as last one + int16_t i = RX_MSGLEN; // int + do { + i--; + } while ((i >= 0) && (args->rx_msg[i] == args->rx_buf[i])); + if (i < 0) { + args->rx_repeatcount++; + } else { + args->rx_repeatcount = 1; + } + } + } else { + args->rx_repeatcount = 0; + } + args->rx_prevpkttime = curr_millis; + // If last message hasn't been read it gets overwritten + memcpy(args->rx_msg, args->rx_buf, RX_MSGLEN); + if (args->rx_repeats == 0 || args->rx_repeatcount == args->rx_repeats) { + if (args->rx_pairtimeout != 0) { + if ((curr_millis - args->rx_pairstarttime) / 100 <= args->rx_pairtimeout) { + if (args->rx_msg[3] == RX_CMD_ON) { + args->rx_addpairfrommsg_(); + } else if (args->rx_msg[3] == RX_CMD_OFF) { + args->rx_remove_pair_(&args->rx_msg[2]); + } + } + } + if (args->rx_report_message_()) { + args->rx_msgcomplete = true; + } + args->rx_pairtimeout = 0; + } + // And cycle round for next one + args->rx_state = RX_STATE_IDLE; + } else { + args->rx_state = RX_STATE_BYTESTARTFOUND; + } + } + break; + } +} + +/** + Test if a message has arrived +**/ +bool LwRx::lwrx_message() { return (this->rx_msgcomplete); } + +/** + Set translate mode +**/ +void LwRx::lwrx_settranslate(bool rxtranslate) { this->rx_translate = rxtranslate; } +/** + Transfer a message to user buffer +**/ +bool LwRx::lwrx_getmessage(uint8_t *buf, uint8_t len) { + bool ret = true; + int16_t j = 0; // int + if (this->rx_msgcomplete && len <= RX_MSGLEN) { + for (uint8_t i = 0; ret && i < RX_MSGLEN; i++) { + if (this->rx_translate || (len != RX_MSGLEN)) { + j = this->rx_find_nibble_(this->rx_msg[i]); + if (j < 0) + ret = false; + } else { + j = this->rx_msg[i]; + } + switch (len) { + case 4: + if (i == 9) + buf[2] = j; + if (i == 2) + buf[3] = j; + case 2: + if (i == 3) + buf[0] = j; + if (i == 0) + buf[1] = j << 4; + if (i == 1) + buf[1] += j; + break; + case 10: + buf[i] = j; + break; + } + } + this->rx_msgcomplete = false; + } else { + ret = false; + } + return ret; +} + +/** + Return time in milliseconds since last packet received +**/ +uint32_t LwRx::lwrx_packetinterval() { return millis() - this->rx_prevpkttime; } + +/** + Set up repeat filtering of received messages +**/ +void LwRx::lwrx_setfilter(uint8_t repeats, uint8_t timeout) { + this->rx_repeats = repeats; + this->rx_timeout = timeout; +} + +/** + Add a pair to filter received messages + pairdata is device,dummy,5*addr,room + pairdata is held in translated form to make comparisons quicker +**/ +uint8_t LwRx::lwrx_addpair(const uint8_t *pairdata) { + if (this->rx_paircount < RX_MAXPAIRS) { + for (uint8_t i = 0; i < 8; i++) { + this->rx_pairs[rx_paircount][i] = RX_NIBBLE[pairdata[i]]; + } + this->rx_paircommit_(); + } + return this->rx_paircount; +} + +/** + Make a pair from next message successfully received +**/ +void LwRx::lwrx_makepair(uint8_t timeout) { + this->rx_pairtimeout = timeout; + this->rx_pairstarttime = millis(); +} + +/** + Get pair data (translated back to nibble form +**/ +uint8_t LwRx::lwrx_getpair(uint8_t *pairdata, uint8_t pairnumber) { + if (pairnumber < this->rx_paircount) { + int16_t j; // int + for (uint8_t i = 0; i < 8; i++) { + j = this->rx_find_nibble_(this->rx_pairs[pairnumber][i]); + if (j >= 0) + pairdata[i] = j; + } + } + return this->rx_paircount; +} + +/** + Clear all pairing +**/ +void LwRx::lwrx_clearpairing_() { rx_paircount = 0; } + +/** + Return stats on high and low pulses +**/ +bool LwRx::lwrx_getstats_(uint16_t *stats) { // unsigned int + if (this->lwrx_stats_enable) { + memcpy(stats, this->lwrx_stats, 2 * RX_STAT_COUNT); + return true; + } else { + return false; + } +} + +/** + Set stats mode +**/ +void LwRx::lwrx_setstatsenable_(bool rx_stats_enable) { + this->lwrx_stats_enable = rx_stats_enable; + if (!this->lwrx_stats_enable) { + // clear down stats when disabling + memcpy(this->lwrx_stats, LWRX_STATSDFLT, sizeof(LWRX_STATSDFLT)); + } +} +/** + Set pairs behaviour +**/ +void LwRx::lwrx_set_pair_mode(bool pair_enforce, bool pair_base_only) { + this->rx_pairEnforce = pair_enforce; + this->rx_pairBaseOnly = pair_base_only; +} + +/** + Set things up to receive LightWaveRF 434Mhz messages + pin must be 2 or 3 to trigger interrupts + !!! For Spark, any pin will work +**/ +void LwRx::lwrx_setup(InternalGPIOPin *pin) { + // rx_pin = pin; + pin->setup(); + this->rx_pin_isr_ = pin->to_isr(); + pin->attach_interrupt(&LwRx::rx_process_bits, this, gpio::INTERRUPT_ANY_EDGE); + + memcpy(this->lwrx_stats, LWRX_STATSDFLT, sizeof(LWRX_STATSDFLT)); +} + +/** + Check a message to see if it should be reported under pairing / mood / all off rules + returns -1 if none found +**/ +bool LwRx::rx_report_message_() { + if (this->rx_pairEnforce && this->rx_paircount == 0) { + return false; + } else { + bool all_devices; + // True if mood to device 15 or Off cmd with Allof paramater + all_devices = ((this->rx_msg[3] == RX_CMD_MOOD && this->rx_msg[2] == RX_DEV_15) || + (this->rx_msg[3] == RX_CMD_OFF && this->rx_msg[0] == RX_PAR0_ALLOFF)); + return (rx_check_pairs_(&this->rx_msg[2], all_devices) != -1); + } +} +/** + Find nibble from byte + returns -1 if none found +**/ +int16_t LwRx::rx_find_nibble_(uint8_t data) { // int + int16_t i = 15; // int + do { + if (RX_NIBBLE[i] == data) + break; + i--; + } while (i >= 0); + return i; +} + +/** + add pair from message buffer +**/ +void LwRx::rx_addpairfrommsg_() { + if (this->rx_paircount < RX_MAXPAIRS) { + memcpy(this->rx_pairs[this->rx_paircount], &this->rx_msg[2], 8); + this->rx_paircommit_(); + } +} + +/** + check and commit pair +**/ +void LwRx::rx_paircommit_() { + if (this->rx_paircount == 0 || this->rx_check_pairs_(this->rx_pairs[this->rx_paircount], false) < 0) { + this->rx_paircount++; + } +} + +/** + Check to see if message matches one of the pairs + if mode is pairBase only then ignore device and room + if allDevices is true then ignore the device number + Returns matching pair number, -1 if not found, -2 if no pairs defined +**/ +int16_t LwRx::rx_check_pairs_(const uint8_t *buf, bool all_devices) { // int + if (this->rx_paircount == 0) { + return -2; + } else { + int16_t pair = this->rx_paircount; // int + int16_t j = -1; // int + int16_t jstart, jend; // int + if (this->rx_pairBaseOnly) { + // skip room(8) and dev/cmd (0,1) + jstart = 7; + jend = 2; + } else { + // include room in comparison + jstart = 8; + // skip device comparison if allDevices true + jend = (all_devices) ? 2 : 0; + } + while (pair > 0 && j < 0) { + pair--; + j = jstart; + while (j > jend) { + j--; + if (j != 1) { + if (this->rx_pairs[pair][j] != buf[j]) { + j = -1; + } + } + } + } + return (j >= 0) ? pair : -1; + } +} + +/** + Remove an existing pair matching the buffer +**/ +void LwRx::rx_remove_pair_(uint8_t *buf) { + int16_t pair = this->rx_check_pairs_(buf, false); // int + if (pair >= 0) { + while (pair < this->rx_paircount - 1) { + for (uint8_t j = 0; j < 8; j++) { + this->rx_pairs[pair][j] = this->rx_pairs[pair + 1][j]; + } + pair++; + } + this->rx_paircount--; + } +} + +} // namespace lightwaverf +} // namespace esphome +#endif diff --git a/esphome/components/lightwaverf/LwRx.h b/esphome/components/lightwaverf/LwRx.h new file mode 100644 index 0000000000..7200f9a51c --- /dev/null +++ b/esphome/components/lightwaverf/LwRx.h @@ -0,0 +1,142 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/hal.h" + +namespace esphome { +namespace lightwaverf { + +// LwRx.h +// +// LightwaveRF 434MHz receiver for Arduino +// +// Author: Bob Tidey (robert@tideys.net) + +static const uint8_t RX_STAT_HIGH_AVE = 0; +static const uint8_t RX_STAT_HIGH_MAX = 1; +static const uint8_t RX_STAT_HIGH_MIN = 2; +static const uint8_t RX_STAT_LOW0_AVE = 3; +static const uint8_t RX_STAT_LOW0_MAX = 4; +static const uint8_t RX_STAT_LOW0_MIN = 5; +static const uint8_t RX_STAT_LOW1_AVE = 6; +static const uint8_t RX_STAT_LOW1_MAX = 7; +static const uint8_t RX_STAT_LOW1_MIN = 8; +static const uint8_t RX_STAT_COUNT = 9; + +// sets maximum number of pairings which can be held +static const uint8_t RX_MAXPAIRS = 10; + +static const uint8_t RX_NIBBLE[] = {0xF6, 0xEE, 0xED, 0xEB, 0xDE, 0xDD, 0xDB, 0xBE, + 0xBD, 0xBB, 0xB7, 0x7E, 0x7D, 0x7B, 0x77, 0x6F}; +static const uint8_t RX_CMD_OFF = 0xF6; // raw 0 +static const uint8_t RX_CMD_ON = 0xEE; // raw 1 +static const uint8_t RX_CMD_MOOD = 0xED; // raw 2 +static const uint8_t RX_PAR0_ALLOFF = 0x7D; // param 192-255 all off (12 in msb) +static const uint8_t RX_DEV_15 = 0x6F; // device 15 + +static const uint8_t RX_MSGLEN = 10; // expected length of rx message + +static const uint8_t RX_STATE_IDLE = 0; +static const uint8_t RX_STATE_MSGSTARTFOUND = 1; +static const uint8_t RX_STATE_BYTESTARTFOUND = 2; +static const uint8_t RX_STATE_GETBYTE = 3; + +// Gather stats for pulse widths (ave is x 16) +static const uint16_t LWRX_STATSDFLT[RX_STAT_COUNT] = {5000, 0, 5000, 20000, 0, 2500, 4000, 0, 500}; // usigned int + +class LwRx { + public: + // Seup must be called once, set up pin used to receive data + void lwrx_setup(InternalGPIOPin *pin); + + // Set translate to determine whether translating from nibbles to bytes in message + // Translate off only applies to 10char message returns + void lwrx_settranslate(bool translate); + + // Check to see whether message available + bool lwrx_message(); + + // Get a message, len controls format (2 cmd+param, 4 cmd+param+room+device),10 full message + bool lwrx_getmessage(uint8_t *buf, uint8_t len); + + // Setup repeat filter + void lwrx_setfilter(uint8_t repeats, uint8_t timeout); + + // Add pair, if no pairing set then all messages are received, returns number of pairs + uint8_t lwrx_addpair(const uint8_t *pairdata); + + // Get pair data into buffer for the pairnumber. Returns current paircount + // Use pairnumber 255 to just get current paircount + uint8_t lwrx_getpair(uint8_t *pairdata, uint8_t pairnumber); + + // Make a pair from next message received within timeout 100mSec + // This call returns immediately whilst message checking continues + void lwrx_makepair(uint8_t timeout); + + // Set pair mode controls + void lwrx_set_pair_mode(bool pair_enforce, bool pair_base_only); + + // Returns time from last packet received in msec + // Can be used to determine if Rx may be still receiving repeats + uint32_t lwrx_packetinterval(); + + static void rx_process_bits(LwRx *arg); + + // Pairing data + uint8_t rx_paircount = 0; + uint8_t rx_pairs[RX_MAXPAIRS][8]; + // set false to responds to all messages if no pairs set up + bool rx_pairEnforce = false; + // set false to use Address, Room and Device in pairs, true just the Address part + bool rx_pairBaseOnly = false; + + uint8_t rx_pairtimeout = 0; // 100msec units + + // Repeat filters + uint8_t rx_repeats = 2; // msg must be repeated at least this number of times + uint8_t rx_repeatcount = 0; + uint8_t rx_timeout = 20; // reset repeat window after this in 100mSecs + uint32_t rx_prevpkttime = 0; // last packet time in milliseconds + uint32_t rx_pairstarttime = 0; // last msg time in milliseconds + + // Receive mode constants and variables + uint8_t rx_msg[RX_MSGLEN]; // raw message received + uint8_t rx_buf[RX_MSGLEN]; // message buffer during reception + + uint32_t rx_prev; // time of previous interrupt in microseconds + + bool rx_msgcomplete = false; // set high when message available + bool rx_translate = true; // Set false to get raw data + + uint8_t rx_state = 0; + + uint8_t rx_num_bits = 0; // number of bits in the current uint8_t + uint8_t rx_num_bytes = 0; // number of bytes received + + uint16_t lwrx_stats[RX_STAT_COUNT]; // unsigned int + + bool lwrx_stats_enable = true; + + protected: + void lwrx_clearpairing_(); + + // Return stats on pulse timings + bool lwrx_getstats_(uint16_t *stats); + + // Enable collection of stats on pulse timings + void lwrx_setstatsenable_(bool rx_stats_enable); + + // internal support functions + bool rx_report_message_(); + int16_t rx_find_nibble_(uint8_t data); // int + void rx_addpairfrommsg_(); + void rx_paircommit_(); + void rx_remove_pair_(uint8_t *buf); + int16_t rx_check_pairs_(const uint8_t *buf, bool all_devices); // int + + ISRInternalGPIOPin rx_pin_isr_; + InternalGPIOPin *rx_pin_; +}; + +} // namespace lightwaverf +} // namespace esphome diff --git a/esphome/components/lightwaverf/LwTx.cpp b/esphome/components/lightwaverf/LwTx.cpp new file mode 100644 index 0000000000..2f46b04b2d --- /dev/null +++ b/esphome/components/lightwaverf/LwTx.cpp @@ -0,0 +1,208 @@ +// LwTx.cpp +// +// LightwaveRF 434MHz tx interface for Arduino +// +// Author: Bob Tidey (robert@tideys.net) +#ifdef USE_ESP8266 + +#include "LwTx.h" +#include +#include +#include "esphome/core/log.h" +#include "esphome/core/helpers.h" + +namespace esphome { +namespace lightwaverf { + +static const uint8_t TX_NIBBLE[] = {0xF6, 0xEE, 0xED, 0xEB, 0xDE, 0xDD, 0xDB, 0xBE, + 0xBD, 0xBB, 0xB7, 0x7E, 0x7D, 0x7B, 0x77, 0x6F}; + +static const uint8_t TX_STATE_IDLE = 0; +static const uint8_t TX_STATE_MSGSTART = 1; +static const uint8_t TX_STATE_BYTESTART = 2; +static const uint8_t TX_STATE_SENDBYTE = 3; +static const uint8_t TX_STATE_MSGEND = 4; +static const uint8_t TX_STATE_GAPSTART = 5; +static const uint8_t TX_STATE_GAPEND = 6; +/** + Set translate mode +**/ +void LwTx::lwtx_settranslate(bool txtranslate) { tx_translate = txtranslate; } + +static void IRAM_ATTR isr_t_xtimer(LwTx *arg) { + // Set low after toggle count interrupts + arg->tx_toggle_count--; + if (arg->tx_toggle_count == arg->tx_trail_count) { + // ESP_LOGD("lightwaverf.sensor", "timer") + arg->tx_pin->digital_write(arg->txoff); + } else if (arg->tx_toggle_count == 0) { + arg->tx_toggle_count = arg->tx_high_count; // default high pulse duration + switch (arg->tx_state) { + case TX_STATE_IDLE: + if (arg->tx_msg_active) { + arg->tx_repeat = 0; + arg->tx_state = TX_STATE_MSGSTART; + } + break; + case TX_STATE_MSGSTART: + arg->tx_pin->digital_write(arg->txon); + arg->tx_num_bytes = 0; + arg->tx_state = TX_STATE_BYTESTART; + break; + case TX_STATE_BYTESTART: + arg->tx_pin->digital_write(arg->txon); + arg->tx_bit_mask = 0x80; + arg->tx_state = TX_STATE_SENDBYTE; + break; + case TX_STATE_SENDBYTE: + if (arg->tx_buf[arg->tx_num_bytes] & arg->tx_bit_mask) { + arg->tx_pin->digital_write(arg->txon); + } else { + // toggle count for the 0 pulse + arg->tx_toggle_count = arg->tx_low_count; + } + arg->tx_bit_mask >>= 1; + if (arg->tx_bit_mask == 0) { + arg->tx_num_bytes++; + if (arg->tx_num_bytes >= esphome::lightwaverf::LwTx::TX_MSGLEN) { + arg->tx_state = TX_STATE_MSGEND; + } else { + arg->tx_state = TX_STATE_BYTESTART; + } + } + break; + case TX_STATE_MSGEND: + arg->tx_pin->digital_write(arg->txon); + arg->tx_state = TX_STATE_GAPSTART; + arg->tx_gap_repeat = arg->tx_gap_multiplier; + break; + case TX_STATE_GAPSTART: + arg->tx_toggle_count = arg->tx_gap_count; + if (arg->tx_gap_repeat == 0) { + arg->tx_state = TX_STATE_GAPEND; + } else { + arg->tx_gap_repeat--; + } + break; + case TX_STATE_GAPEND: + arg->tx_repeat++; + if (arg->tx_repeat >= arg->tx_repeats) { + // disable timer nterrupt + arg->lw_timer_stop(); + arg->tx_msg_active = false; + arg->tx_state = TX_STATE_IDLE; + } else { + arg->tx_state = TX_STATE_MSGSTART; + } + break; + } + } +} + +/** + Check for send free +**/ +bool LwTx::lwtx_free() { return !this->tx_msg_active; } + +/** + Send a LightwaveRF message (10 nibbles in bytes) +**/ +void LwTx::lwtx_send(const std::vector &msg) { + if (this->tx_translate) { + for (uint8_t i = 0; i < TX_MSGLEN; i++) { + this->tx_buf[i] = TX_NIBBLE[msg[i] & 0xF]; + ESP_LOGD("lightwaverf.sensor", "%x ", msg[i]); + } + } else { + // memcpy(tx_buf, msg, tx_msglen); + } + this->lw_timer_start(); + this->tx_msg_active = true; +} + +/** + Set 5 char address for future messages +**/ +void LwTx::lwtx_setaddr(const uint8_t *addr) { + for (uint8_t i = 0; i < 5; i++) { + this->tx_buf[i + 4] = TX_NIBBLE[addr[i] & 0xF]; + } +} + +/** + Send a LightwaveRF message (10 nibbles in bytes) +**/ +void LwTx::lwtx_cmd(uint8_t command, uint8_t parameter, uint8_t room, uint8_t device) { + // enable timer 2 interrupts + this->tx_buf[0] = TX_NIBBLE[parameter >> 4]; + this->tx_buf[1] = TX_NIBBLE[parameter & 0xF]; + this->tx_buf[2] = TX_NIBBLE[device & 0xF]; + this->tx_buf[3] = TX_NIBBLE[command & 0xF]; + this->tx_buf[9] = TX_NIBBLE[room & 0xF]; + this->lw_timer_start(); + this->tx_msg_active = true; +} + +/** + Set things up to transmit LightWaveRF 434Mhz messages +**/ +void LwTx::lwtx_setup(InternalGPIOPin *pin, uint8_t repeats, bool inverted, int u_sec) { + pin->setup(); + tx_pin = pin; + + tx_pin->pin_mode(gpio::FLAG_OUTPUT); + tx_pin->digital_write(txoff); + + if (repeats > 0 && repeats < 40) { + this->tx_repeats = repeats; + } + if (inverted) { + this->txon = 0; + this->txoff = 1; + } else { + this->txon = 1; + this->txoff = 0; + } + + int period1 = 330; + /* + if (period > 32 && period < 1000) { + period1 = period; + } else { + // default 330 uSec + period1 = 330; + }*/ + this->espPeriod = 5 * period1; + timer1_isr_init(); +} + +void LwTx::lwtx_set_tick_counts(uint8_t low_count, uint8_t high_count, uint8_t trail_count, uint8_t gap_count) { + this->tx_low_count = low_count; + this->tx_high_count = high_count; + this->tx_trail_count = trail_count; + this->tx_gap_count = gap_count; +} + +void LwTx::lwtx_set_gap_multiplier(uint8_t gap_multiplier) { this->tx_gap_multiplier = gap_multiplier; } + +void LwTx::lw_timer_start() { + { + InterruptLock lock; + static LwTx *arg = this; // NOLINT + timer1_attachInterrupt([] { isr_t_xtimer(arg); }); + timer1_enable(TIM_DIV16, TIM_EDGE, TIM_LOOP); + timer1_write(this->espPeriod); + } +} + +void LwTx::lw_timer_stop() { + { + InterruptLock lock; + timer1_disable(); + timer1_detachInterrupt(); + } +} + +} // namespace lightwaverf +} // namespace esphome +#endif diff --git a/esphome/components/lightwaverf/LwTx.h b/esphome/components/lightwaverf/LwTx.h new file mode 100644 index 0000000000..719826640e --- /dev/null +++ b/esphome/components/lightwaverf/LwTx.h @@ -0,0 +1,92 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/hal.h" + +namespace esphome { +namespace lightwaverf { + +// LxTx.h +// +// LightwaveRF 434MHz tx interface for Arduino +// +// Author: Bob Tidey (robert@tideys.net) + +// Include basic library header and set default TX pin +static const uint8_t TX_PIN_DEFAULT = 13; + +class LwTx { + public: + // Sets up basic parameters must be called at least once + void lwtx_setup(InternalGPIOPin *pin, uint8_t repeats, bool inverted, int u_sec); + + // Allows changing basic tick counts from their defaults + void lwtx_set_tick_counts(uint8_t low_count, uint8_t high_count, uint8_t trail_count, uint8_t gap_count); + + // Allws multiplying the gap period for creating very large gaps + void lwtx_set_gap_multiplier(uint8_t gap_multiplier); + + // determines whether incoming data or should be translated from nibble data + void lwtx_settranslate(bool txtranslate); + + // Checks whether tx is free to accept a new message + bool lwtx_free(); + + // Basic send of new 10 char message, not normally needed if setaddr and cmd are used. + void lwtx_send(const std::vector &msg); + + // Sets up 5 char address which will be used to form messages for lwtx_cmd + void lwtx_setaddr(const uint8_t *addr); + + // Send Command + void lwtx_cmd(uint8_t command, uint8_t parameter, uint8_t room, uint8_t device); + + // Allows changing basic tick counts from their defaults + void lw_timer_start(); + + // Allws multiplying the gap period for creating very large gaps + void lw_timer_stop(); + + // These set the pulse durationlws in ticks. ESP uses 330uSec base tick, else use 140uSec + uint8_t tx_low_count = 3; // total number of ticks in a low (990 uSec) + uint8_t tx_high_count = 2; // total number of ticks in a high (660 uSec) + uint8_t tx_trail_count = 1; // tick count to set line low (330 uSec) + + uint8_t tx_toggle_count = 3; + + static const uint8_t TX_MSGLEN = 10; // the expected length of the message + + // Transmit mode constants and variables + uint8_t tx_repeats = 12; // Number of repeats of message sent + uint8_t txon = 1; + uint8_t txoff = 0; + bool tx_msg_active = false; // set true to activate message sending + bool tx_translate = true; // Set false to send raw data + + uint8_t tx_buf[TX_MSGLEN]; // the message buffer during reception + uint8_t tx_repeat = 0; // counter for repeats + uint8_t tx_state = 0; + uint16_t tx_gap_repeat = 0; // unsigned int + + // Use with low repeat counts + uint8_t tx_gap_count = 33; // Inter-message gap count (10.9 msec) + uint32_t espPeriod = 0; // Holds interrupt timer0 period + uint32_t espNext = 0; // Holds interrupt next count + + // Gap multiplier byte is used to multiply gap if longer periods are needed for experimentation + // If gap is 255 (35msec) then this to give a max of 9 seconds + // Used with low repeat counts to find if device times out + uint8_t tx_gap_multiplier = 0; // Gap extension byte + + uint8_t tx_bit_mask = 0; // bit mask in current byte + uint8_t tx_num_bytes = 0; // number of bytes sent + + InternalGPIOPin *tx_pin; + + protected: + uint32_t duty_on_; + uint32_t duty_off_; +}; + +} // namespace lightwaverf +} // namespace esphome diff --git a/esphome/components/lightwaverf/__init__.py b/esphome/components/lightwaverf/__init__.py new file mode 100644 index 0000000000..4e96dda663 --- /dev/null +++ b/esphome/components/lightwaverf/__init__.py @@ -0,0 +1,83 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome import pins +from esphome import automation + +from esphome.const import ( + CONF_READ_PIN, + CONF_ID, + CONF_NAME, + CONF_WRITE_PIN, + CONF_REPEAT, + CONF_INVERTED, + CONF_PULSE_LENGTH, + CONF_CODE, +) +from esphome.cpp_helpers import gpio_pin_expression + +CODEOWNERS = ["@max246"] + +lightwaverf_ns = cg.esphome_ns.namespace("lightwaverf") + + +LIGHTWAVERFComponent = lightwaverf_ns.class_( + "LightWaveRF", cg.Component, cg.PollingComponent +) +LightwaveRawAction = lightwaverf_ns.class_("SendRawAction", automation.Action) + + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(LIGHTWAVERFComponent), + cv.Optional(CONF_READ_PIN, default=13): pins.internal_gpio_input_pin_schema, + cv.Optional(CONF_WRITE_PIN, default=14): pins.internal_gpio_input_pin_schema, + } +).extend(cv.polling_component_schema("1s")) + + +LIGHTWAVE_SEND_SCHEMA = cv.Any( + cv.int_range(min=1), + cv.Schema( + { + cv.GenerateID(): cv.use_id(LIGHTWAVERFComponent), + cv.Required(CONF_NAME): cv.string, + cv.Required(CONF_CODE): cv.All( + [cv.Any(cv.hex_uint8_t)], + cv.Length(min=10), + ), + cv.Optional(CONF_REPEAT, default=10): cv.int_, + cv.Optional(CONF_INVERTED, default=False): cv.boolean, + cv.Optional(CONF_PULSE_LENGTH, default=330): cv.int_, + } + ), +) + + +@automation.register_action( + "lightwaverf.send_raw", + LightwaveRawAction, + LIGHTWAVE_SEND_SCHEMA, +) +async def send_raw_to_code(config, action_id, template_arg, args): + paren = await cg.get_variable(config[CONF_ID]) + var = cg.new_Pvariable(action_id, template_arg, paren) + + repeats = await cg.templatable(config[CONF_REPEAT], args, int) + inverted = await cg.templatable(config[CONF_INVERTED], args, bool) + pulse_length = await cg.templatable(config[CONF_PULSE_LENGTH], args, int) + code = config[CONF_CODE] + + cg.add(var.set_repeats(repeats)) + cg.add(var.set_inverted(inverted)) + cg.add(var.set_pulse_length(pulse_length)) + cg.add(var.set_data(code)) + return var + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + + pin_read = await gpio_pin_expression(config[CONF_READ_PIN]) + pin_write = await gpio_pin_expression(config[CONF_WRITE_PIN]) + cg.add(var.set_pin(pin_write, pin_read)) diff --git a/esphome/components/lightwaverf/lightwaverf.cpp b/esphome/components/lightwaverf/lightwaverf.cpp new file mode 100644 index 0000000000..89cbdae6e1 --- /dev/null +++ b/esphome/components/lightwaverf/lightwaverf.cpp @@ -0,0 +1,67 @@ +#include "esphome/core/log.h" + +#ifdef USE_ESP8266 + +#include "lightwaverf.h" + +namespace esphome { +namespace lightwaverf { + +static const char *const TAG = "lightwaverf.sensor"; + +static const uint8_t DEFAULT_REPEAT = 10; +static const bool DEFAULT_INVERT = false; +static const uint32_t DEFAULT_TICK = 330; + +void LightWaveRF::setup() { + ESP_LOGCONFIG(TAG, "Setting up Lightwave RF..."); + + this->lwtx_.lwtx_setup(pin_tx_, DEFAULT_REPEAT, DEFAULT_INVERT, DEFAULT_TICK); + this->lwrx_.lwrx_setup(pin_rx_); +} + +void LightWaveRF::update() { this->read_tx(); } + +void LightWaveRF::read_tx() { + if (this->lwrx_.lwrx_message()) { + this->lwrx_.lwrx_getmessage(msg_, msglen_); + print_msg_(msg_, msglen_); + } +} + +void LightWaveRF::send_rx(const std::vector &msg, uint8_t repeats, bool inverted, int u_sec) { + this->lwtx_.lwtx_setup(pin_tx_, repeats, inverted, u_sec); + + uint32_t timeout = 0; + if (this->lwtx_.lwtx_free()) { + this->lwtx_.lwtx_send(msg); + timeout = millis(); + ESP_LOGD(TAG, "[%i] msg start", timeout); + } + while (!this->lwtx_.lwtx_free() && millis() < (timeout + 1000)) { + delay(10); + } + timeout = millis() - timeout; + ESP_LOGD(TAG, "[%u] msg sent: %i", millis(), timeout); +} + +void LightWaveRF::print_msg_(uint8_t *msg, uint8_t len) { + char buffer[65]; + ESP_LOGD(TAG, " Received code (len:%i): ", len); + + for (int i = 0; i < len; i++) { + sprintf(&buffer[i * 6], "0x%02x, ", msg[i]); + } + ESP_LOGD(TAG, "[%s]", buffer); +} + +void LightWaveRF::dump_config() { + ESP_LOGCONFIG(TAG, "Lightwave RF:"); + LOG_PIN(" Pin TX: ", this->pin_tx_); + LOG_PIN(" Pin RX: ", this->pin_rx_); + LOG_UPDATE_INTERVAL(this); +} +} // namespace lightwaverf +} // namespace esphome + +#endif diff --git a/esphome/components/lightwaverf/lightwaverf.h b/esphome/components/lightwaverf/lightwaverf.h new file mode 100644 index 0000000000..b9f2abfcb3 --- /dev/null +++ b/esphome/components/lightwaverf/lightwaverf.h @@ -0,0 +1,70 @@ +#pragma once + +#ifdef USE_ESP8266 + +#include "esphome/core/component.h" +#include "esphome/core/hal.h" +#include "esphome/core/automation.h" + +#include + +#include "LwRx.h" +#include "LwTx.h" + +namespace esphome { +namespace lightwaverf { + +#ifdef USE_ESP8266 + +class LightWaveRF : public PollingComponent { + public: + void set_pin(InternalGPIOPin *pin_tx, InternalGPIOPin *pin_rx) { + pin_tx_ = pin_tx; + pin_rx_ = pin_rx; + } + void update() override; + void setup() override; + void dump_config() override; + void read_tx(); + void send_rx(const std::vector &msg, uint8_t repeats, bool inverted, int u_sec); + + protected: + void print_msg_(uint8_t *msg, uint8_t len); + uint8_t msg_[10]; + uint8_t msglen_ = 10; + InternalGPIOPin *pin_tx_; + InternalGPIOPin *pin_rx_; + LwRx lwrx_; + LwTx lwtx_; +}; + +template class SendRawAction : public Action { + public: + SendRawAction(LightWaveRF *parent) : parent_(parent){}; + TEMPLATABLE_VALUE(int, repeat); + TEMPLATABLE_VALUE(int, inverted); + TEMPLATABLE_VALUE(int, pulse_length); + TEMPLATABLE_VALUE(std::vector, code); + + void set_repeats(const int &data) { repeat_ = data; } + void set_inverted(const int &data) { inverted_ = data; } + void set_pulse_length(const int &data) { pulse_length_ = data; } + void set_data(const std::vector &data) { code_ = data; } + + void play(Ts... x) { + int repeats = this->repeat_.value(x...); + int inverted = this->inverted_.value(x...); + int pulse_length = this->pulse_length_.value(x...); + std::vector msg = this->code_.value(x...); + + this->parent_->send_rx(msg, repeats, inverted, pulse_length); + } + + protected: + LightWaveRF *parent_; +}; + +#endif +} // namespace lightwaverf +} // namespace esphome +#endif diff --git a/tests/test3.yaml b/tests/test3.yaml index abfd133c99..5d30e415fb 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -1188,6 +1188,10 @@ qr_code: - id: homepage_qr value: https://esphome.io/index.html +lightwaverf: + read_pin: 13 + write_pin: 14 + alarm_control_panel: - platform: template id: alarmcontrolpanel1 From eff76d578b3391c568674cd41f820634c37c7a3e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 6 Sep 2023 10:44:47 +1200 Subject: [PATCH 076/111] Bump flake8 from 6.0.0 to 6.1.0 (#5171) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index e160c3e9e3..2d46d3dccd 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,5 +1,5 @@ pylint==2.17.5 -flake8==6.0.0 # also change in .pre-commit-config.yaml when updating +flake8==6.1.0 # also change in .pre-commit-config.yaml when updating black==23.7.0 # also change in .pre-commit-config.yaml when updating pyupgrade==3.10.1 # also change in .pre-commit-config.yaml when updating pre-commit From e89c6494a6621a46685112fd658153611b9f72b2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 6 Sep 2023 10:45:17 +1200 Subject: [PATCH 077/111] Bump tornado from 6.3.2 to 6.3.3 (#5236) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index c52b8e1d8e..9ec3a9c8f9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ voluptuous==0.13.1 PyYAML==6.0.1 paho-mqtt==1.6.1 colorama==0.4.6 -tornado==6.3.2 +tornado==6.3.3 tzlocal==5.0.1 # from time tzdata>=2021.1 # from time pyserial==3.5 From 6b0fb3dd065a7b443ea17ddef095e5ac3785a62d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Sep 2023 23:31:21 +0000 Subject: [PATCH 078/111] Bump platformio from 6.1.10 to 6.1.11 (#5323) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- docker/Dockerfile | 2 +- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index a590445de9..bf64897af7 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -61,7 +61,7 @@ RUN \ # Ubuntu python3-pip is missing wheel pip3 install --no-cache-dir \ wheel==0.37.1 \ - platformio==6.1.10 \ + platformio==6.1.11 \ # Change some platformio settings && platformio settings set enable_telemetry No \ && platformio settings set check_platformio_interval 1000000 \ diff --git a/requirements.txt b/requirements.txt index 9ec3a9c8f9..dcb7420d3f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,7 @@ tornado==6.3.3 tzlocal==5.0.1 # from time tzdata>=2021.1 # from time pyserial==3.5 -platformio==6.1.10 # When updating platformio, also update Dockerfile +platformio==6.1.11 # When updating platformio, also update Dockerfile esptool==4.6.2 click==8.1.7 esphome-dashboard==20230904.0 From 8cac5ca90c59acb62283d3ae2a488c798069e2ee Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 6 Sep 2023 15:32:14 +1200 Subject: [PATCH 079/111] Only run ci-docker when ci-docker workflow changes (#5347) --- .github/workflows/ci-docker.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-docker.yml b/.github/workflows/ci-docker.yml index 394379d675..dbd0d573da 100644 --- a/.github/workflows/ci-docker.yml +++ b/.github/workflows/ci-docker.yml @@ -8,7 +8,7 @@ on: branches: [dev, beta, release] paths: - "docker/**" - - ".github/workflows/**" + - ".github/workflows/ci-docker.yml" - "requirements*.txt" - "platformio.ini" - "script/platformio_install_deps.py" @@ -16,7 +16,7 @@ on: pull_request: paths: - "docker/**" - - ".github/workflows/**" + - ".github/workflows/ci-docker.yml" - "requirements*.txt" - "platformio.ini" - "script/platformio_install_deps.py" From f2a6f1855376bbea403409f885dfd747d75ec6fd Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 6 Sep 2023 17:02:21 +1200 Subject: [PATCH 080/111] esp32: Extra build customization (#5322) --- esphome/components/esp32/__init__.py | 46 +++++++++++++++++++++++----- esphome/components/esp32/const.py | 1 + 2 files changed, 40 insertions(+), 7 deletions(-) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index ee18315518..0b067dc78f 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -22,6 +22,7 @@ from esphome.const import ( CONF_IGNORE_EFUSE_MAC_CRC, KEY_CORE, KEY_FRAMEWORK_VERSION, + KEY_NAME, KEY_TARGET_FRAMEWORK, KEY_TARGET_PLATFORM, TYPE_GIT, @@ -37,6 +38,7 @@ from .const import ( # noqa KEY_BOARD, KEY_COMPONENTS, KEY_ESP32, + KEY_EXTRA_BUILD_FILES, KEY_PATH, KEY_REF, KEY_REFRESH, @@ -73,6 +75,8 @@ def set_core_data(config): ) CORE.data[KEY_ESP32][KEY_BOARD] = config[CONF_BOARD] CORE.data[KEY_ESP32][KEY_VARIANT] = config[CONF_VARIANT] + CORE.data[KEY_ESP32][KEY_EXTRA_BUILD_FILES] = {} + return config @@ -166,6 +170,24 @@ def add_idf_component( } +def add_extra_script(stage: str, filename: str, path: str): + """Add an extra script to the project.""" + key = f"{stage}:{filename}" + if add_extra_build_file(filename, path): + cg.add_platformio_option("extra_scripts", [key]) + + +def add_extra_build_file(filename: str, path: str) -> bool: + """Add an extra build file to the project.""" + if filename not in CORE.data[KEY_ESP32][KEY_EXTRA_BUILD_FILES]: + CORE.data[KEY_ESP32][KEY_EXTRA_BUILD_FILES][filename] = { + KEY_NAME: filename, + KEY_PATH: path, + } + return True + return False + + def _format_framework_arduino_version(ver: cv.Version) -> str: # format the given arduino (https://github.com/espressif/arduino-esp32/releases) version to # a PIO platformio/framework-arduinoespressif32 value @@ -390,7 +412,11 @@ async def to_code(config): conf = config[CONF_FRAMEWORK] cg.add_platformio_option("platform", conf[CONF_PLATFORM_VERSION]) - cg.add_platformio_option("extra_scripts", ["post:post_build.py"]) + add_extra_script( + "post", + "post_build2.py", + os.path.join(os.path.dirname(__file__), "post_build.py.script"), + ) if conf[CONF_TYPE] == FRAMEWORK_ESP_IDF: cg.add_platformio_option("framework", "espidf") @@ -604,9 +630,15 @@ def copy_files(): ignore_dangling_symlinks=True, ) - dir = os.path.dirname(__file__) - post_build_file = os.path.join(dir, "post_build.py.script") - copy_file_if_changed( - post_build_file, - CORE.relative_build_path("post_build.py"), - ) + for _, file in CORE.data[KEY_ESP32][KEY_EXTRA_BUILD_FILES].items(): + if file[KEY_PATH].startswith("http"): + import requests + + mkdir_p(CORE.relative_build_path(os.path.dirname(file[KEY_NAME]))) + with open(CORE.relative_build_path(file[KEY_NAME]), "wb") as f: + f.write(requests.get(file[KEY_PATH], timeout=30).content) + else: + copy_file_if_changed( + file[KEY_PATH], + CORE.relative_build_path(file[KEY_NAME]), + ) diff --git a/esphome/components/esp32/const.py b/esphome/components/esp32/const.py index 9e997bdeb5..a86713e857 100644 --- a/esphome/components/esp32/const.py +++ b/esphome/components/esp32/const.py @@ -10,6 +10,7 @@ KEY_REF = "ref" KEY_REFRESH = "refresh" KEY_PATH = "path" KEY_SUBMODULES = "submodules" +KEY_EXTRA_BUILD_FILES = "extra_build_files" VARIANT_ESP32 = "ESP32" VARIANT_ESP32S2 = "ESP32S2" From 72f29b1283b925da3177bacd4b60ec0446291e81 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 7 Sep 2023 10:15:54 +1200 Subject: [PATCH 081/111] Allow upload command to flash file via serial (#5274) --- esphome/__main__.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/esphome/__main__.py b/esphome/__main__.py index 9b208c2280..9fac8cd605 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -218,14 +218,16 @@ def compile_program(args, config): return 0 if idedata is not None else 1 -def upload_using_esptool(config, port): +def upload_using_esptool(config, port, file): from esphome import platformio_api first_baudrate = config[CONF_ESPHOME][CONF_PLATFORMIO_OPTIONS].get( "upload_speed", 460800 ) - def run_esptool(baud_rate): + if file is not None: + flash_images = [platformio_api.FlashImage(path=file, offset="0x0")] + else: idedata = platformio_api.get_idedata(config) firmware_offset = "0x10000" if CORE.is_esp32 else "0x0" @@ -236,12 +238,13 @@ def upload_using_esptool(config, port): *idedata.extra_flash_images, ] - mcu = "esp8266" - if CORE.is_esp32: - from esphome.components.esp32 import get_esp32_variant + mcu = "esp8266" + if CORE.is_esp32: + from esphome.components.esp32 import get_esp32_variant - mcu = get_esp32_variant().lower() + mcu = get_esp32_variant().lower() + def run_esptool(baud_rate): cmd = [ "esptool.py", "--before", @@ -292,7 +295,8 @@ def upload_using_platformio(config, port): def upload_program(config, args, host): if get_port_type(host) == "SERIAL": if CORE.target_platform in (PLATFORM_ESP32, PLATFORM_ESP8266): - return upload_using_esptool(config, host) + file = getattr(args, "file", None) + return upload_using_esptool(config, host, file) if CORE.target_platform in (PLATFORM_RP2040): return upload_using_platformio(config, args.device) From 87395d259ee53c8e50a2bbc29525bdeff0a97493 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 7 Sep 2023 13:22:39 +1200 Subject: [PATCH 082/111] Allow "--device SERIAL" on cli to flash only via serial (#5351) --- esphome/__main__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/esphome/__main__.py b/esphome/__main__.py index 9fac8cd605..cf540f58ba 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -85,6 +85,8 @@ def choose_upload_log_host( options = [] for port in get_serial_ports(): options.append((f"{port.path} ({port.description})", port.path)) + if default == "SERIAL": + return choose_prompt(options, purpose=purpose) if (show_ota and "ota" in CORE.config) or (show_api and "api" in CORE.config): options.append((f"Over The Air ({CORE.address})", CORE.address)) if default == "OTA": From ab872b075a402d0180ab5087199b7e251b577f64 Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Thu, 7 Sep 2023 04:48:44 -0500 Subject: [PATCH 083/111] Fix PN532 for IDF 5 and ultralight enhancements (#5352) --- esphome/components/nfc/nfc.cpp | 2 +- esphome/components/pn532/pn532.h | 8 +- .../pn532/pn532_mifare_ultralight.cpp | 119 ++++++++++-------- 3 files changed, 71 insertions(+), 58 deletions(-) diff --git a/esphome/components/nfc/nfc.cpp b/esphome/components/nfc/nfc.cpp index b7c7215028..7225e373b3 100644 --- a/esphome/components/nfc/nfc.cpp +++ b/esphome/components/nfc/nfc.cpp @@ -54,7 +54,7 @@ uint8_t get_mifare_classic_ndef_start_index(std::vector &data) { bool decode_mifare_classic_tlv(std::vector &data, uint32_t &message_length, uint8_t &message_start_index) { uint8_t i = get_mifare_classic_ndef_start_index(data); - if (i < 0 || data[i] != 0x03) { + if (data[i] != 0x03) { ESP_LOGE(TAG, "Error, Can't decode message length."); return false; } diff --git a/esphome/components/pn532/pn532.h b/esphome/components/pn532/pn532.h index 73b349e328..8ae215dfd9 100644 --- a/esphome/components/pn532/pn532.h +++ b/esphome/components/pn532/pn532.h @@ -7,6 +7,7 @@ #include "esphome/components/nfc/nfc.h" #include "esphome/components/nfc/automation.h" +#include #include namespace esphome { @@ -74,10 +75,11 @@ class PN532 : public PollingComponent { bool write_mifare_classic_tag_(std::vector &uid, nfc::NdefMessage *message); std::unique_ptr read_mifare_ultralight_tag_(std::vector &uid); - bool read_mifare_ultralight_page_(uint8_t page_num, std::vector &data); - bool is_mifare_ultralight_formatted_(); + bool read_mifare_ultralight_bytes_(uint8_t start_page, uint16_t num_bytes, std::vector &data); + bool is_mifare_ultralight_formatted_(const std::vector &page_3_to_6); uint16_t read_mifare_ultralight_capacity_(); - bool find_mifare_ultralight_ndef_(uint8_t &message_length, uint8_t &message_start_index); + bool find_mifare_ultralight_ndef_(const std::vector &page_3_to_6, uint8_t &message_length, + uint8_t &message_start_index); bool write_mifare_ultralight_page_(uint8_t page_num, std::vector &write_data); bool write_mifare_ultralight_tag_(std::vector &uid, nfc::NdefMessage *message); bool clean_mifare_ultralight_(); diff --git a/esphome/components/pn532/pn532_mifare_ultralight.cpp b/esphome/components/pn532/pn532_mifare_ultralight.cpp index 1b91ae919e..b08a7336c7 100644 --- a/esphome/components/pn532/pn532_mifare_ultralight.cpp +++ b/esphome/components/pn532/pn532_mifare_ultralight.cpp @@ -9,93 +9,104 @@ namespace pn532 { static const char *const TAG = "pn532.mifare_ultralight"; std::unique_ptr PN532::read_mifare_ultralight_tag_(std::vector &uid) { - if (!this->is_mifare_ultralight_formatted_()) { - ESP_LOGD(TAG, "Not NDEF formatted"); + std::vector data; + // pages 3 to 6 contain various info we are interested in -- do one read to grab it all + if (!this->read_mifare_ultralight_bytes_(3, nfc::MIFARE_ULTRALIGHT_PAGE_SIZE * nfc::MIFARE_ULTRALIGHT_READ_SIZE, + data)) { + return make_unique(uid, nfc::NFC_FORUM_TYPE_2); + } + + if (!this->is_mifare_ultralight_formatted_(data)) { + ESP_LOGW(TAG, "Not NDEF formatted"); return make_unique(uid, nfc::NFC_FORUM_TYPE_2); } uint8_t message_length; uint8_t message_start_index; - if (!this->find_mifare_ultralight_ndef_(message_length, message_start_index)) { + if (!this->find_mifare_ultralight_ndef_(data, message_length, message_start_index)) { + ESP_LOGW(TAG, "Couldn't find NDEF message"); return make_unique(uid, nfc::NFC_FORUM_TYPE_2); } - ESP_LOGVV(TAG, "message length: %d, start: %d", message_length, message_start_index); + ESP_LOGVV(TAG, "NDEF message length: %u, start: %u", message_length, message_start_index); if (message_length == 0) { return make_unique(uid, nfc::NFC_FORUM_TYPE_2); } - std::vector data; - for (uint8_t page = nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE; page < nfc::MIFARE_ULTRALIGHT_MAX_PAGE; page++) { - std::vector page_data; - if (!this->read_mifare_ultralight_page_(page, page_data)) { - ESP_LOGE(TAG, "Error reading page %d", page); + // we already read pages 3-6 earlier -- pick up where we left off so we're not re-reading pages + const uint8_t read_length = message_length + message_start_index > 12 ? message_length + message_start_index - 12 : 0; + if (read_length) { + if (!read_mifare_ultralight_bytes_(nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE + 3, read_length, data)) { + ESP_LOGE(TAG, "Error reading tag data"); return make_unique(uid, nfc::NFC_FORUM_TYPE_2); } - data.insert(data.end(), page_data.begin(), page_data.end()); - - if (data.size() >= (message_length + message_start_index)) - break; } - - data.erase(data.begin(), data.begin() + message_start_index); - data.erase(data.begin() + message_length, data.end()); + // we need to trim off page 3 as well as any bytes ahead of message_start_index + data.erase(data.begin(), data.begin() + message_start_index + nfc::MIFARE_ULTRALIGHT_PAGE_SIZE); return make_unique(uid, nfc::NFC_FORUM_TYPE_2, data); } -bool PN532::read_mifare_ultralight_page_(uint8_t page_num, std::vector &data) { - if (!this->write_command_({ - PN532_COMMAND_INDATAEXCHANGE, - 0x01, // One card - nfc::MIFARE_CMD_READ, - page_num, - })) { - return false; +bool PN532::read_mifare_ultralight_bytes_(uint8_t start_page, uint16_t num_bytes, std::vector &data) { + const uint8_t read_increment = nfc::MIFARE_ULTRALIGHT_READ_SIZE * nfc::MIFARE_ULTRALIGHT_PAGE_SIZE; + std::vector response; + + for (uint8_t i = 0; i * read_increment < num_bytes; i++) { + if (!this->write_command_({ + PN532_COMMAND_INDATAEXCHANGE, + 0x01, // One card + nfc::MIFARE_CMD_READ, + uint8_t(i * nfc::MIFARE_ULTRALIGHT_READ_SIZE + start_page), + })) { + return false; + } + + if (!this->read_response(PN532_COMMAND_INDATAEXCHANGE, response) || response[0] != 0x00) { + return false; + } + uint16_t bytes_offset = (i + 1) * read_increment; + auto pages_in_end_itr = bytes_offset <= num_bytes ? response.end() : response.end() - (bytes_offset - num_bytes); + + if ((pages_in_end_itr > response.begin()) && (pages_in_end_itr <= response.end())) { + data.insert(data.end(), response.begin() + 1, pages_in_end_itr); + } } - if (!this->read_response(PN532_COMMAND_INDATAEXCHANGE, data) || data[0] != 0x00) { - return false; - } - data.erase(data.begin()); - // We only want 1 page of data but the PN532 returns 4 at once. - data.erase(data.begin() + 4, data.end()); - - ESP_LOGVV(TAG, "Pages %d-%d: %s", page_num, page_num + 4, nfc::format_bytes(data).c_str()); + ESP_LOGVV(TAG, "Data read: %s", nfc::format_bytes(data).c_str()); return true; } -bool PN532::is_mifare_ultralight_formatted_() { - std::vector data; - if (this->read_mifare_ultralight_page_(4, data)) { - return !(data[0] == 0xFF && data[1] == 0xFF && data[2] == 0xFF && data[3] == 0xFF); - } - return true; +bool PN532::is_mifare_ultralight_formatted_(const std::vector &page_3_to_6) { + const uint8_t p4_offset = nfc::MIFARE_ULTRALIGHT_PAGE_SIZE; // page 4 will begin 4 bytes into the vector + + return (page_3_to_6.size() > p4_offset + 3) && + !((page_3_to_6[p4_offset + 0] == 0xFF) && (page_3_to_6[p4_offset + 1] == 0xFF) && + (page_3_to_6[p4_offset + 2] == 0xFF) && (page_3_to_6[p4_offset + 3] == 0xFF)); } uint16_t PN532::read_mifare_ultralight_capacity_() { std::vector data; - if (this->read_mifare_ultralight_page_(3, data)) { + if (this->read_mifare_ultralight_bytes_(3, nfc::MIFARE_ULTRALIGHT_PAGE_SIZE, data)) { + ESP_LOGV(TAG, "Tag capacity is %u bytes", data[2] * 8U); return data[2] * 8U; } return 0; } -bool PN532::find_mifare_ultralight_ndef_(uint8_t &message_length, uint8_t &message_start_index) { - std::vector data; - for (int page = 4; page < 6; page++) { - std::vector page_data; - if (!this->read_mifare_ultralight_page_(page, page_data)) { - return false; - } - data.insert(data.end(), page_data.begin(), page_data.end()); +bool PN532::find_mifare_ultralight_ndef_(const std::vector &page_3_to_6, uint8_t &message_length, + uint8_t &message_start_index) { + const uint8_t p4_offset = nfc::MIFARE_ULTRALIGHT_PAGE_SIZE; // page 4 will begin 4 bytes into the vector + + if (!(page_3_to_6.size() > p4_offset + 5)) { + return false; } - if (data[0] == 0x03) { - message_length = data[1]; + + if (page_3_to_6[p4_offset + 0] == 0x03) { + message_length = page_3_to_6[p4_offset + 1]; message_start_index = 2; return true; - } else if (data[5] == 0x03) { - message_length = data[6]; + } else if (page_3_to_6[p4_offset + 5] == 0x03) { + message_length = page_3_to_6[p4_offset + 6]; message_start_index = 7; return true; } @@ -111,7 +122,7 @@ bool PN532::write_mifare_ultralight_tag_(std::vector &uid, nfc::NdefMes uint32_t buffer_length = nfc::get_mifare_ultralight_buffer_size(message_length); if (buffer_length > capacity) { - ESP_LOGE(TAG, "Message length exceeds tag capacity %d > %d", buffer_length, capacity); + ESP_LOGE(TAG, "Message length exceeds tag capacity %" PRIu32 " > %" PRIu32, buffer_length, capacity); return false; } @@ -164,13 +175,13 @@ bool PN532::write_mifare_ultralight_page_(uint8_t page_num, std::vector }); data.insert(data.end(), write_data.begin(), write_data.end()); if (!this->write_command_(data)) { - ESP_LOGE(TAG, "Error writing page %d", page_num); + ESP_LOGE(TAG, "Error writing page %u", page_num); return false; } std::vector response; if (!this->read_response(PN532_COMMAND_INDATAEXCHANGE, response)) { - ESP_LOGE(TAG, "Error writing page %d", page_num); + ESP_LOGE(TAG, "Error writing page %u", page_num); return false; } From ce171f5c002c229cd207c0858881d5a79fd12689 Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Thu, 7 Sep 2023 04:49:12 -0500 Subject: [PATCH 084/111] Fix cpu_ll_get_cycle_count() deprecated warning (#5353) --- esphome/components/esp32/core.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/esphome/components/esp32/core.cpp b/esphome/components/esp32/core.cpp index 16aa93c232..48c8b2b04d 100644 --- a/esphome/components/esp32/core.cpp +++ b/esphome/components/esp32/core.cpp @@ -53,7 +53,11 @@ void arch_init() { void IRAM_ATTR HOT arch_feed_wdt() { esp_task_wdt_reset(); } uint8_t progmem_read_byte(const uint8_t *addr) { return *addr; } +#if ESP_IDF_VERSION_MAJOR >= 5 +uint32_t arch_get_cpu_cycle_count() { return esp_cpu_get_cycle_count(); } +#else uint32_t arch_get_cpu_cycle_count() { return cpu_hal_get_cycle_count(); } +#endif uint32_t arch_get_cpu_freq_hz() { return rtc_clk_apb_freq_get(); } #ifdef USE_ESP_IDF From 5c26f95a4be6948adb4fc35252f297a6b17af4a8 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Fri, 8 Sep 2023 17:27:19 +1000 Subject: [PATCH 085/111] Refactor SPI code; Add ESP-IDF hardware support (#5311) * Checkpoint * Checkpoint * Checkpoint * Revert hal change * Checkpoint * Checkpoint * Checkpoint * Checkpoint * ESP-IDF working * clang-format * use bus_list * Add spi_device; fix 16 bit transfer. * Enable multi_conf; Fix LSB 16 bit transactions * Formatting fixes * Clang-format, codeowners * Add test * Formatting * clang tidy * clang-format * clang-tidy * clang-format * Checkpoint * Checkpoint * Checkpoint * Revert hal change * Checkpoint * Checkpoint * Checkpoint * Checkpoint * ESP-IDF working * clang-format * use bus_list * Add spi_device; fix 16 bit transfer. * Enable multi_conf; Fix LSB 16 bit transactions * Formatting fixes * Clang-format, codeowners * Add test * Formatting * clang tidy * clang-format * clang-tidy * clang-format * Clang-tidy * Clang-format * clang-tidy * clang-tidy * Fix ESP8266 * RP2040 * RP2040 * Avoid use of spi1 as id * Refactor SPI code. Add support for ESP-IDF hardware SPI * Force SW only for RP2040 * Break up large transfers * Add interface: option for spi. validate pins in python. * Can't use match/case with Python 3.9. Check for inverted pins. * Work around target_platform issue with * Remove debug code * Optimize write_array16 * Show errors in hex * Only one spi on ESP32Cx variants * Ensure bus is claimed before asserting /CS. * Check on init/deinit * Allow maximum rate write only SPI on GPIO MUXed pins. * Clang-format * Clang-tidy * Fix issue with reads. * Finger trouble... * Make comment about missing SPI on Cx variants * Pacify CI clang-format. Did not complain locally?? * Restore 8266 to its former SPI glory * Fix per clang-format * Move validation and choice of SPI into Python code. * Add test for interface: config * Fix issues found on self-review. --------- Co-authored-by: Keith Burzinski --- CODEOWNERS | 1 + esphome/components/spi/__init__.py | 200 ++++++- esphome/components/spi/spi.cpp | 296 +++------- esphome/components/spi/spi.h | 567 +++++++++++-------- esphome/components/spi/spi_arduino.cpp | 89 +++ esphome/components/spi/spi_esp_idf.cpp | 163 ++++++ esphome/components/spi_device/__init__.py | 49 ++ esphome/components/spi_device/spi_device.cpp | 30 + esphome/components/spi_device/spi_device.h | 22 + tests/test4.yaml | 1 + tests/test8.yaml | 9 + 11 files changed, 954 insertions(+), 473 deletions(-) create mode 100644 esphome/components/spi/spi_arduino.cpp create mode 100644 esphome/components/spi/spi_esp_idf.cpp create mode 100644 esphome/components/spi_device/__init__.py create mode 100644 esphome/components/spi_device/spi_device.cpp create mode 100644 esphome/components/spi_device/spi_device.h diff --git a/CODEOWNERS b/CODEOWNERS index 498cfcac01..ab4c5011f6 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -270,6 +270,7 @@ esphome/components/socket/* @esphome/core esphome/components/sonoff_d1/* @anatoly-savchenkov esphome/components/speaker/* @jesserockz esphome/components/spi/* @esphome/core +esphome/components/spi_device/* @clydebarrow esphome/components/sprinkler/* @kbx81 esphome/components/sps30/* @martgras esphome/components/ssd1322_base/* @kbx81 diff --git a/esphome/components/spi/__init__.py b/esphome/components/spi/__init__.py index a2ef956200..79e7a5b034 100644 --- a/esphome/components/spi/__init__.py +++ b/esphome/components/spi/__init__.py @@ -1,6 +1,17 @@ +import re + import esphome.codegen as cg import esphome.config_validation as cv import esphome.final_validate as fv +from esphome.components.esp32.const import ( + KEY_ESP32, + VARIANT_ESP32S2, + VARIANT_ESP32S3, + VARIANT_ESP32C2, + VARIANT_ESP32C3, + VARIANT_ESP32C6, + VARIANT_ESP32H2, +) from esphome import pins from esphome.const import ( CONF_CLK_PIN, @@ -9,6 +20,11 @@ from esphome.const import ( CONF_MOSI_PIN, CONF_SPI_ID, CONF_CS_PIN, + CONF_NUMBER, + CONF_INVERTED, + KEY_CORE, + KEY_TARGET_PLATFORM, + KEY_VARIANT, ) from esphome.core import coroutine_with_priority, CORE @@ -34,10 +50,147 @@ SPI_DATA_RATE_OPTIONS = { } SPI_DATA_RATE_SCHEMA = cv.All(cv.frequency, cv.enum(SPI_DATA_RATE_OPTIONS)) -MULTI_CONF = True CONF_FORCE_SW = "force_sw" +CONF_INTERFACE = "interface" +CONF_INTERFACE_INDEX = "interface_index" -CONFIG_SCHEMA = cv.All( + +def get_target_platform(): + return ( + CORE.data[KEY_CORE][KEY_TARGET_PLATFORM] + if KEY_TARGET_PLATFORM in CORE.data[KEY_CORE] + else "" + ) + + +def get_target_variant(): + return ( + CORE.data[KEY_ESP32][KEY_VARIANT] if KEY_VARIANT in CORE.data[KEY_ESP32] else "" + ) + + +# Get a list of available hardware interfaces based on target and variant. +# The returned value is a list of lists of names +def get_hw_interface_list(): + target_platform = get_target_platform() + if target_platform == "esp8266": + return [["spi", "hspi"]] + if target_platform == "esp32": + if get_target_variant() in [ + VARIANT_ESP32C2, + VARIANT_ESP32C3, + VARIANT_ESP32C6, + VARIANT_ESP32H2, + ]: + return [["spi", "spi2"]] + return [["spi", "spi2"], ["spi3"]] + if target_platform == "rp2040": + return [["spi"]] + return [] + + +# Given an SPI name, return the index of it in the available list +def get_spi_index(name): + for i, ilist in enumerate(get_hw_interface_list()): + if name in ilist: + return i + # Should never get to here. + raise cv.Invalid(f"{name} is not an available SPI") + + +# Check that pins are suitable for HW spi +# TODO verify that the pins are internal +def validate_hw_pins(spi): + clk_pin = spi[CONF_CLK_PIN] + if clk_pin[CONF_INVERTED]: + return False + clk_pin_no = clk_pin[CONF_NUMBER] + sdo_pin_no = -1 + sdi_pin_no = -1 + if CONF_MOSI_PIN in spi: + sdo_pin = spi[CONF_MOSI_PIN] + if sdo_pin[CONF_INVERTED]: + return False + sdo_pin_no = sdo_pin[CONF_NUMBER] + if CONF_MISO_PIN in spi: + sdi_pin = spi[CONF_MISO_PIN] + if sdi_pin[CONF_INVERTED]: + return False + sdi_pin_no = sdi_pin[CONF_NUMBER] + + target_platform = get_target_platform() + if target_platform == "esp8266": + if clk_pin_no == 6: + return sdo_pin_no in (-1, 8) and sdi_pin_no in (-1, 7) + if clk_pin_no == 14: + return sdo_pin_no in (-1, 13) and sdi_pin_no in (-1, 12) + return False + + if target_platform == "esp32": + return clk_pin_no >= 0 + + return False + + +def validate_spi_config(config): + available = list(range(len(get_hw_interface_list()))) + for spi in config: + interface = spi[CONF_INTERFACE] + if spi[CONF_FORCE_SW]: + if interface == "any": + spi[CONF_INTERFACE] = interface = "software" + elif interface != "software": + raise cv.Invalid("force_sw is deprecated - use interface: software") + if interface == "software": + pass + elif interface == "any": + if not validate_hw_pins(spi): + spi[CONF_INTERFACE] = "software" + elif interface == "hardware": + if len(available) == 0: + raise cv.Invalid("No hardware interface available") + index = spi[CONF_INTERFACE_INDEX] = available[0] + available.remove(index) + else: + # Must be a specific name + index = spi[CONF_INTERFACE_INDEX] = get_spi_index(interface) + if index not in available: + raise cv.Invalid( + f"interface '{interface}' not available here (may be already assigned)" + ) + available.remove(index) + + # Second time around: + # Any specific names and any 'hardware' requests will have already been filled, + # so just need to assign remaining hardware to 'any' requests. + for spi in config: + if spi[CONF_INTERFACE] == "any" and len(available) != 0: + index = available[0] + spi[CONF_INTERFACE_INDEX] = index + available.remove(index) + if CONF_INTERFACE_INDEX in spi and not validate_hw_pins(spi): + raise cv.Invalid("Invalid pin selections for hardware SPI interface") + + return config + + +# Given an SPI index, convert to a string that represents the C++ object for it. +def get_spi_interface(index): + if CORE.using_esp_idf: + return ["SPI2_HOST", "SPI3_HOST"][index] + # Arduino code follows + platform = get_target_platform() + if platform == "rp2040": + return "&spi1" + if index == 0: + return "&SPI" + # Following code can't apply to C2, H2 or 8266 since they have only one SPI + if get_target_variant() in (VARIANT_ESP32S3, VARIANT_ESP32S2): + return "new SPIClass(FSPI)" + return "return new SPIClass(HSPI)" + + +SPI_SCHEMA = cv.All( cv.Schema( { cv.GenerateID(): cv.declare_id(SPIComponent), @@ -45,28 +198,47 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_MISO_PIN): pins.gpio_input_pin_schema, cv.Optional(CONF_MOSI_PIN): pins.gpio_output_pin_schema, cv.Optional(CONF_FORCE_SW, default=False): cv.boolean, + cv.Optional(CONF_INTERFACE, default="any"): cv.one_of( + *sum(get_hw_interface_list(), ["software", "hardware", "any"]), + lower=True, + ), } ), cv.has_at_least_one_key(CONF_MISO_PIN, CONF_MOSI_PIN), cv.only_on(["esp32", "esp8266", "rp2040"]), ) +CONFIG_SCHEMA = cv.All( + cv.ensure_list(SPI_SCHEMA), + validate_spi_config, +) + @coroutine_with_priority(1.0) -async def to_code(config): +async def to_code(configs): cg.add_global(spi_ns.using) - var = cg.new_Pvariable(config[CONF_ID]) - await cg.register_component(var, config) + for spi in configs: + var = cg.new_Pvariable(spi[CONF_ID]) + await cg.register_component(var, spi) - clk = await cg.gpio_pin_expression(config[CONF_CLK_PIN]) - cg.add(var.set_clk(clk)) - cg.add(var.set_force_sw(config[CONF_FORCE_SW])) - if CONF_MISO_PIN in config: - miso = await cg.gpio_pin_expression(config[CONF_MISO_PIN]) - cg.add(var.set_miso(miso)) - if CONF_MOSI_PIN in config: - mosi = await cg.gpio_pin_expression(config[CONF_MOSI_PIN]) - cg.add(var.set_mosi(mosi)) + clk = await cg.gpio_pin_expression(spi[CONF_CLK_PIN]) + cg.add(var.set_clk(clk)) + if CONF_MISO_PIN in spi: + miso = await cg.gpio_pin_expression(spi[CONF_MISO_PIN]) + cg.add(var.set_miso(miso)) + if CONF_MOSI_PIN in spi: + mosi = await cg.gpio_pin_expression(spi[CONF_MOSI_PIN]) + cg.add(var.set_mosi(mosi)) + if CONF_INTERFACE_INDEX in spi: + index = spi[CONF_INTERFACE_INDEX] + cg.add(var.set_interface(cg.RawExpression(get_spi_interface(index)))) + cg.add( + var.set_interface_name( + re.sub( + r"\W", "", get_spi_interface(index).replace("new SPIClass", "") + ) + ) + ) if CORE.using_arduino: cg.add_library("SPI", None) diff --git a/esphome/components/spi/spi.cpp b/esphome/components/spi/spi.cpp index 33630897f6..935399500f 100644 --- a/esphome/components/spi/spi.cpp +++ b/esphome/components/spi/spi.cpp @@ -1,268 +1,116 @@ #include "spi.h" #include "esphome/core/log.h" -#include "esphome/core/helpers.h" #include "esphome/core/application.h" namespace esphome { namespace spi { -static const char *const TAG = "spi"; +const char *const TAG = "spi"; -void IRAM_ATTR HOT SPIComponent::disable() { -#ifdef USE_SPI_ARDUINO_BACKEND - if (this->hw_spi_ != nullptr) { - this->hw_spi_->endTransaction(); - } -#endif // USE_SPI_ARDUINO_BACKEND - if (this->active_cs_) { - this->active_cs_->digital_write(true); - this->active_cs_ = nullptr; +SPIDelegate *const SPIDelegate::NULL_DELEGATE = // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + new SPIDelegateDummy(); +// https://bugs.llvm.org/show_bug.cgi?id=48040 + +bool SPIDelegate::is_ready() { return true; } + +GPIOPin *const NullPin::NULL_PIN = new NullPin(); // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + +SPIDelegate *SPIComponent::register_device(SPIClient *device, SPIMode mode, SPIBitOrder bit_order, uint32_t data_rate, + GPIOPin *cs_pin) { + if (this->devices_.count(device) != 0) { + ESP_LOGE(TAG, "SPI device already registered"); + return this->devices_[device]; } + SPIDelegate *delegate = this->spi_bus_->get_delegate(data_rate, bit_order, mode, cs_pin); // NOLINT + this->devices_[device] = delegate; + return delegate; } + +void SPIComponent::unregister_device(SPIClient *device) { + if (this->devices_.count(device) == 0) { + esph_log_e(TAG, "SPI device not registered"); + return; + } + delete this->devices_[device]; // NOLINT + this->devices_.erase(device); +} + void SPIComponent::setup() { - ESP_LOGCONFIG(TAG, "Setting up SPI bus..."); - this->clk_->setup(); - this->clk_->digital_write(true); + ESP_LOGD(TAG, "Setting up SPI bus..."); -#ifdef USE_SPI_ARDUINO_BACKEND - bool use_hw_spi = !this->force_sw_; - const bool has_miso = this->miso_ != nullptr; - const bool has_mosi = this->mosi_ != nullptr; - int8_t clk_pin = -1, miso_pin = -1, mosi_pin = -1; - - if (!this->clk_->is_internal()) - use_hw_spi = false; - if (has_miso && !miso_->is_internal()) - use_hw_spi = false; - if (has_mosi && !mosi_->is_internal()) - use_hw_spi = false; - if (use_hw_spi) { - auto *clk_internal = (InternalGPIOPin *) clk_; - auto *miso_internal = (InternalGPIOPin *) miso_; - auto *mosi_internal = (InternalGPIOPin *) mosi_; - - if (clk_internal->is_inverted()) - use_hw_spi = false; - if (has_miso && miso_internal->is_inverted()) - use_hw_spi = false; - if (has_mosi && mosi_internal->is_inverted()) - use_hw_spi = false; - - if (use_hw_spi) { - clk_pin = clk_internal->get_pin(); - miso_pin = has_miso ? miso_internal->get_pin() : -1; - mosi_pin = has_mosi ? mosi_internal->get_pin() : -1; - } - } -#ifdef USE_ESP8266 - if (!(clk_pin == 6 && miso_pin == 7 && mosi_pin == 8) && - !(clk_pin == 14 && (!has_miso || miso_pin == 12) && (!has_mosi || mosi_pin == 13))) - use_hw_spi = false; - - if (use_hw_spi) { - this->hw_spi_ = &SPI; - this->hw_spi_->pins(clk_pin, miso_pin, mosi_pin, 0); - this->hw_spi_->begin(); + if (this->sdo_pin_ == nullptr) + this->sdo_pin_ = NullPin::NULL_PIN; + if (this->sdi_pin_ == nullptr) + this->sdi_pin_ = NullPin::NULL_PIN; + if (this->clk_pin_ == nullptr) { + ESP_LOGE(TAG, "No clock pin for SPI"); + this->mark_failed(); return; } -#endif // USE_ESP8266 -#ifdef USE_ESP32 - static uint8_t spi_bus_num = 0; - if (spi_bus_num >= 2) { - use_hw_spi = false; - } - if (use_hw_spi) { - if (spi_bus_num == 0) { - this->hw_spi_ = &SPI; - } else { -#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || \ - defined(USE_ESP32_VARIANT_ESP32C2) || defined(USE_ESP32_VARIANT_ESP32C6) - this->hw_spi_ = new SPIClass(FSPI); // NOLINT(cppcoreguidelines-owning-memory) -#else - this->hw_spi_ = new SPIClass(HSPI); // NOLINT(cppcoreguidelines-owning-memory) -#endif // USE_ESP32_VARIANT + if (this->using_hw_) { + this->spi_bus_ = SPIComponent::get_bus(this->interface_, this->clk_pin_, this->sdo_pin_, this->sdi_pin_); + if (this->spi_bus_ == nullptr) { + ESP_LOGE(TAG, "Unable to allocate SPI interface"); + this->mark_failed(); } - spi_bus_num++; - this->hw_spi_->begin(clk_pin, miso_pin, mosi_pin); - return; - } -#endif // USE_ESP32 -#ifdef USE_RP2040 - static uint8_t spi_bus_num = 0; - if (spi_bus_num >= 2) { - use_hw_spi = false; - } - if (use_hw_spi) { - SPIClassRP2040 *spi; - if (spi_bus_num == 0) { - spi = &SPI; - } else { - spi = &SPI1; - } - spi_bus_num++; - - if (miso_pin != -1) - spi->setRX(miso_pin); - if (mosi_pin != -1) - spi->setTX(mosi_pin); - spi->setSCK(clk_pin); - this->hw_spi_ = spi; - this->hw_spi_->begin(); - return; - } -#endif // USE_RP2040 -#endif // USE_SPI_ARDUINO_BACKEND - - if (this->miso_ != nullptr) { - this->miso_->setup(); - } - if (this->mosi_ != nullptr) { - this->mosi_->setup(); - this->mosi_->digital_write(false); + } else { + this->spi_bus_ = new SPIBus(this->clk_pin_, this->sdo_pin_, this->sdi_pin_); // NOLINT + this->clk_pin_->setup(); + this->clk_pin_->digital_write(true); + this->sdo_pin_->setup(); + this->sdi_pin_->setup(); } } + void SPIComponent::dump_config() { ESP_LOGCONFIG(TAG, "SPI bus:"); - LOG_PIN(" CLK Pin: ", this->clk_); - LOG_PIN(" MISO Pin: ", this->miso_); - LOG_PIN(" MOSI Pin: ", this->mosi_); -#ifdef USE_SPI_ARDUINO_BACKEND - ESP_LOGCONFIG(TAG, " Using HW SPI: %s", YESNO(this->hw_spi_ != nullptr)); -#endif // USE_SPI_ARDUINO_BACKEND -} -float SPIComponent::get_setup_priority() const { return setup_priority::BUS; } - -void SPIComponent::cycle_clock_(bool value) { - uint32_t start = arch_get_cpu_cycle_count(); - while (start - arch_get_cpu_cycle_count() < this->wait_cycle_) - ; - this->clk_->digital_write(value); - start += this->wait_cycle_; - while (start - arch_get_cpu_cycle_count() < this->wait_cycle_) - ; + LOG_PIN(" CLK Pin: ", this->clk_pin_) + LOG_PIN(" SDI Pin: ", this->sdi_pin_) + LOG_PIN(" SDO Pin: ", this->sdo_pin_) + if (this->spi_bus_->is_hw()) { + ESP_LOGCONFIG(TAG, " Using HW SPI: %s", this->interface_name_); + } else { + ESP_LOGCONFIG(TAG, " Using software SPI"); + } } -// NOLINTNEXTLINE -#ifndef CLANG_TIDY -#pragma GCC optimize("unroll-loops") -// NOLINTNEXTLINE -#pragma GCC optimize("O2") -#endif // CLANG_TIDY +void SPIDelegateDummy::begin_transaction() { ESP_LOGE(TAG, "SPIDevice not initialised - did you call spi_setup()?"); } -template -uint8_t HOT SPIComponent::transfer_(uint8_t data) { +uint8_t SPIDelegateBitBash::transfer(uint8_t data) { // Clock starts out at idle level - this->clk_->digital_write(CLOCK_POLARITY); + this->clk_pin_->digital_write(clock_polarity_); uint8_t out_data = 0; for (uint8_t i = 0; i < 8; i++) { uint8_t shift; - if (BIT_ORDER == BIT_ORDER_MSB_FIRST) { + if (bit_order_ == BIT_ORDER_MSB_FIRST) { shift = 7 - i; } else { shift = i; } - if (CLOCK_PHASE == CLOCK_PHASE_LEADING) { + if (clock_phase_ == CLOCK_PHASE_LEADING) { // sampling on leading edge - if (WRITE) { - this->mosi_->digital_write(data & (1 << shift)); - } - - // SAMPLE! - this->cycle_clock_(!CLOCK_POLARITY); - - if (READ) { - out_data |= uint8_t(this->miso_->digital_read()) << shift; - } - - this->cycle_clock_(CLOCK_POLARITY); + this->sdo_pin_->digital_write(data & (1 << shift)); + this->cycle_clock_(); + out_data |= uint8_t(this->sdi_pin_->digital_read()) << shift; + this->clk_pin_->digital_write(!this->clock_polarity_); + this->cycle_clock_(); + this->clk_pin_->digital_write(this->clock_polarity_); } else { // sampling on trailing edge - this->cycle_clock_(!CLOCK_POLARITY); - - if (WRITE) { - this->mosi_->digital_write(data & (1 << shift)); - } - - // SAMPLE! - this->cycle_clock_(CLOCK_POLARITY); - - if (READ) { - out_data |= uint8_t(this->miso_->digital_read()) << shift; - } + this->cycle_clock_(); + this->clk_pin_->digital_write(!this->clock_polarity_); + this->sdo_pin_->digital_write(data & (1 << shift)); + this->cycle_clock_(); + out_data |= uint8_t(this->sdi_pin_->digital_read()) << shift; + this->clk_pin_->digital_write(this->clock_polarity_); } } - App.feed_wdt(); - return out_data; } -// Generate with (py3): -// -// from itertools import product -// bit_orders = ['BIT_ORDER_LSB_FIRST', 'BIT_ORDER_MSB_FIRST'] -// clock_pols = ['CLOCK_POLARITY_LOW', 'CLOCK_POLARITY_HIGH'] -// clock_phases = ['CLOCK_PHASE_LEADING', 'CLOCK_PHASE_TRAILING'] -// reads = [False, True] -// writes = [False, True] -// cpp_bool = {False: 'false', True: 'true'} -// for b, cpol, cph, r, w in product(bit_orders, clock_pols, clock_phases, reads, writes): -// if not r and not w: -// continue -// print(f"template uint8_t SPIComponent::transfer_<{b}, {cpol}, {cph}, {cpp_bool[r]}, {cpp_bool[w]}>(uint8_t -// data);") - -template uint8_t SPIComponent::transfer_( - uint8_t data); -template uint8_t SPIComponent::transfer_( - uint8_t data); -template uint8_t SPIComponent::transfer_( - uint8_t data); -template uint8_t SPIComponent::transfer_( - uint8_t data); -template uint8_t SPIComponent::transfer_( - uint8_t data); -template uint8_t SPIComponent::transfer_( - uint8_t data); -template uint8_t SPIComponent::transfer_( - uint8_t data); -template uint8_t SPIComponent::transfer_( - uint8_t data); -template uint8_t SPIComponent::transfer_( - uint8_t data); -template uint8_t SPIComponent::transfer_( - uint8_t data); -template uint8_t SPIComponent::transfer_( - uint8_t data); -template uint8_t SPIComponent::transfer_( - uint8_t data); -template uint8_t SPIComponent::transfer_( - uint8_t data); -template uint8_t SPIComponent::transfer_( - uint8_t data); -template uint8_t SPIComponent::transfer_( - uint8_t data); -template uint8_t SPIComponent::transfer_( - uint8_t data); -template uint8_t SPIComponent::transfer_( - uint8_t data); -template uint8_t SPIComponent::transfer_( - uint8_t data); -template uint8_t SPIComponent::transfer_( - uint8_t data); -template uint8_t SPIComponent::transfer_( - uint8_t data); -template uint8_t SPIComponent::transfer_( - uint8_t data); -template uint8_t SPIComponent::transfer_( - uint8_t data); -template uint8_t SPIComponent::transfer_( - uint8_t data); -template uint8_t SPIComponent::transfer_( - uint8_t data); - } // namespace spi } // namespace esphome diff --git a/esphome/components/spi/spi.h b/esphome/components/spi/spi.h index 159d117533..2761c2d604 100644 --- a/esphome/components/spi/spi.h +++ b/esphome/components/spi/spi.h @@ -2,16 +2,34 @@ #include "esphome/core/component.h" #include "esphome/core/hal.h" +#include "esphome/core/log.h" +#include "esphome/core/application.h" #include +#include #ifdef USE_ARDUINO -#define USE_SPI_ARDUINO_BACKEND -#endif -#ifdef USE_SPI_ARDUINO_BACKEND #include + +#ifdef USE_RP2040 +using SPIInterface = SPIClassRP2040 *; +#else +using SPIInterface = SPIClass *; #endif +#endif + +#ifdef USE_ESP_IDF + +#include "driver/spi_master.h" + +using SPIInterface = spi_host_device_t; + +#endif // USE_ESP_IDF + +/** + * Implementation of SPI Controller mode. + */ namespace esphome { namespace spi { @@ -48,10 +66,19 @@ enum SPIClockPhase { /// The data is sampled on a trailing clock edge. (CPHA=1) CLOCK_PHASE_TRAILING, }; -/** The SPI clock signal data rate. This defines for what duration the clock signal is HIGH/LOW. - * So effectively the rate of bytes can be calculated using + +/** + * Modes mapping to clock phase and polarity. * - * effective_byte_rate = spi_data_rate / 16 + */ + +enum SPIMode { + MODE0 = 0, + MODE1 = 1, + MODE2 = 2, + MODE3 = 3, +}; +/** The SPI clock signal frequency, which determines the transfer bit rate/second. * * Implementations can use the pre-defined constants here, or use an integer in the template definition * to manually use a specific data rate. @@ -71,270 +98,340 @@ enum SPIDataRate : uint32_t { DATA_RATE_80MHZ = 80000000, }; -class SPIComponent : public Component { +/** + * A pin to replace those that don't exist. + */ +class NullPin : public GPIOPin { + friend class SPIComponent; + + friend class SPIDelegate; + + friend class Utility; + public: - void set_clk(GPIOPin *clk) { clk_ = clk; } - void set_miso(GPIOPin *miso) { miso_ = miso; } - void set_mosi(GPIOPin *mosi) { mosi_ = mosi; } - void set_force_sw(bool force_sw) { force_sw_ = force_sw; } + void setup() override {} - void setup() override; + void pin_mode(gpio::Flags flags) override {} - void dump_config() override; + bool digital_read() override { return false; } - template uint8_t read_byte() { -#ifdef USE_SPI_ARDUINO_BACKEND - if (this->hw_spi_ != nullptr) { - return this->hw_spi_->transfer(0x00); - } -#endif // USE_SPI_ARDUINO_BACKEND - return this->transfer_(0x00); - } + void digital_write(bool value) override {} - template - void read_array(uint8_t *data, size_t length) { -#ifdef USE_SPI_ARDUINO_BACKEND - if (this->hw_spi_ != nullptr) { - this->hw_spi_->transfer(data, length); - return; - } -#endif // USE_SPI_ARDUINO_BACKEND - for (size_t i = 0; i < length; i++) { - data[i] = this->read_byte(); - } - } - - template - void write_byte(uint8_t data) { -#ifdef USE_SPI_ARDUINO_BACKEND - if (this->hw_spi_ != nullptr) { -#ifdef USE_RP2040 - this->hw_spi_->transfer(data); -#else - this->hw_spi_->write(data); -#endif - return; - } -#endif // USE_SPI_ARDUINO_BACKEND - this->transfer_(data); - } - - template - void write_byte16(const uint16_t data) { -#ifdef USE_SPI_ARDUINO_BACKEND - if (this->hw_spi_ != nullptr) { -#ifdef USE_RP2040 - this->hw_spi_->transfer16(data); -#else - this->hw_spi_->write16(data); -#endif - return; - } -#endif // USE_SPI_ARDUINO_BACKEND - - this->write_byte(data >> 8); - this->write_byte(data); - } - - template - void write_array16(const uint16_t *data, size_t length) { -#ifdef USE_SPI_ARDUINO_BACKEND - if (this->hw_spi_ != nullptr) { - for (size_t i = 0; i < length; i++) { -#ifdef USE_RP2040 - this->hw_spi_->transfer16(data[i]); -#else - this->hw_spi_->write16(data[i]); -#endif - } - return; - } -#endif // USE_SPI_ARDUINO_BACKEND - for (size_t i = 0; i < length; i++) { - this->write_byte16(data[i]); - } - } - - template - void write_array(const uint8_t *data, size_t length) { -#ifdef USE_SPI_ARDUINO_BACKEND - if (this->hw_spi_ != nullptr) { - auto *data_c = const_cast(data); -#ifdef USE_RP2040 - this->hw_spi_->transfer(data_c, length); -#else - this->hw_spi_->writeBytes(data_c, length); -#endif - return; - } -#endif // USE_SPI_ARDUINO_BACKEND - for (size_t i = 0; i < length; i++) { - this->write_byte(data[i]); - } - } - - template - uint8_t transfer_byte(uint8_t data) { - if (this->miso_ != nullptr) { -#ifdef USE_SPI_ARDUINO_BACKEND - if (this->hw_spi_ != nullptr) { - return this->hw_spi_->transfer(data); - } else { -#endif // USE_SPI_ARDUINO_BACKEND - return this->transfer_(data); -#ifdef USE_SPI_ARDUINO_BACKEND - } -#endif // USE_SPI_ARDUINO_BACKEND - } - this->write_byte(data); - return 0; - } - - template - void transfer_array(uint8_t *data, size_t length) { -#ifdef USE_SPI_ARDUINO_BACKEND - if (this->hw_spi_ != nullptr) { - if (this->miso_ != nullptr) { - this->hw_spi_->transfer(data, length); - } else { -#ifdef USE_RP2040 - this->hw_spi_->transfer(data, length); -#else - this->hw_spi_->writeBytes(data, length); -#endif - } - return; - } -#endif // USE_SPI_ARDUINO_BACKEND - - if (this->miso_ != nullptr) { - for (size_t i = 0; i < length; i++) { - data[i] = this->transfer_byte(data[i]); - } - } else { - this->write_array(data, length); - } - } - - template - void enable(GPIOPin *cs) { -#ifdef USE_SPI_ARDUINO_BACKEND - if (this->hw_spi_ != nullptr) { - uint8_t data_mode = SPI_MODE0; - if (!CLOCK_POLARITY && CLOCK_PHASE) { - data_mode = SPI_MODE1; - } else if (CLOCK_POLARITY && !CLOCK_PHASE) { - data_mode = SPI_MODE2; - } else if (CLOCK_POLARITY && CLOCK_PHASE) { - data_mode = SPI_MODE3; - } -#ifdef USE_RP2040 - SPISettings settings(DATA_RATE, static_cast(BIT_ORDER), data_mode); -#else - SPISettings settings(DATA_RATE, BIT_ORDER, data_mode); -#endif - this->hw_spi_->beginTransaction(settings); - } else { -#endif // USE_SPI_ARDUINO_BACKEND - this->clk_->digital_write(CLOCK_POLARITY); - uint32_t cpu_freq_hz = arch_get_cpu_freq_hz(); - this->wait_cycle_ = uint32_t(cpu_freq_hz) / DATA_RATE / 2ULL; -#ifdef USE_SPI_ARDUINO_BACKEND - } -#endif // USE_SPI_ARDUINO_BACKEND - - if (cs != nullptr) { - this->active_cs_ = cs; - this->active_cs_->digital_write(false); - } - } - - void disable(); - - float get_setup_priority() const override; + std::string dump_summary() const override { return std::string(); } protected: - inline void cycle_clock_(bool value); - - template - uint8_t transfer_(uint8_t data); - - GPIOPin *clk_; - GPIOPin *miso_{nullptr}; - GPIOPin *mosi_{nullptr}; - GPIOPin *active_cs_{nullptr}; - bool force_sw_{false}; -#ifdef USE_SPI_ARDUINO_BACKEND - SPIClass *hw_spi_{nullptr}; -#endif // USE_SPI_ARDUINO_BACKEND - uint32_t wait_cycle_; + static GPIOPin *const NULL_PIN; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + // https://bugs.llvm.org/show_bug.cgi?id=48040 }; -template -class SPIDevice { +class Utility { public: - SPIDevice() = default; - SPIDevice(SPIComponent *parent, GPIOPin *cs) : parent_(parent), cs_(cs) {} + static int get_pin_no(GPIOPin *pin) { + if (pin == nullptr || !pin->is_internal()) + return -1; + if (((InternalGPIOPin *) pin)->is_inverted()) + return -1; + return ((InternalGPIOPin *) pin)->get_pin(); + } - void set_spi_parent(SPIComponent *parent) { parent_ = parent; } - void set_cs_pin(GPIOPin *cs) { cs_ = cs; } + static SPIMode get_mode(SPIClockPolarity polarity, SPIClockPhase phase) { + if (polarity == CLOCK_POLARITY_HIGH) { + return phase == CLOCK_PHASE_LEADING ? MODE2 : MODE3; + } + return phase == CLOCK_PHASE_LEADING ? MODE0 : MODE1; + } - void spi_setup() { - if (this->cs_) { - this->cs_->setup(); - this->cs_->digital_write(true); + static SPIClockPhase get_phase(SPIMode mode) { + switch (mode) { + case MODE0: + case MODE2: + return CLOCK_PHASE_LEADING; + default: + return CLOCK_PHASE_TRAILING; } } - void enable() { this->parent_->template enable(this->cs_); } + static SPIClockPolarity get_polarity(SPIMode mode) { + switch (mode) { + case MODE0: + case MODE1: + return CLOCK_POLARITY_LOW; + default: + return CLOCK_POLARITY_HIGH; + } + } +}; - void disable() { this->parent_->disable(); } +class SPIDelegateDummy; - uint8_t read_byte() { return this->parent_->template read_byte(); } +// represents a device attached to an SPI bus, with a defined clock rate, mode and bit order. On Arduino this is +// a thin wrapper over SPIClass. +class SPIDelegate { + friend class SPIClient; - void read_array(uint8_t *data, size_t length) { - return this->parent_->template read_array(data, length); + public: + SPIDelegate() = default; + + SPIDelegate(uint32_t data_rate, SPIBitOrder bit_order, SPIMode mode, GPIOPin *cs_pin) + : bit_order_(bit_order), data_rate_(data_rate), mode_(mode), cs_pin_(cs_pin) { + if (this->cs_pin_ == nullptr) + this->cs_pin_ = NullPin::NULL_PIN; + this->cs_pin_->setup(); + this->cs_pin_->digital_write(true); } - template std::array read_array() { - std::array data; - this->read_array(data.data(), N); - return data; + virtual ~SPIDelegate(){}; + + // enable CS if configured. + virtual void begin_transaction() { this->cs_pin_->digital_write(false); } + + // end the transaction + virtual void end_transaction() { this->cs_pin_->digital_write(true); } + + // transfer one byte, return the byte that was read. + virtual uint8_t transfer(uint8_t data) = 0; + + // transfer a buffer, replace the contents with read data + virtual void transfer(uint8_t *ptr, size_t length) { this->transfer(ptr, ptr, length); } + + virtual void transfer(const uint8_t *txbuf, uint8_t *rxbuf, size_t length) { + for (size_t i = 0; i != length; i++) + rxbuf[i] = this->transfer(txbuf[i]); } - void write_byte(uint8_t data) { - return this->parent_->template write_byte(data); + // write 16 bits + virtual void write16(uint16_t data) { + if (this->bit_order_ == BIT_ORDER_MSB_FIRST) { + uint16_t buffer; + buffer = (data >> 8) | (data << 8); + this->write_array(reinterpret_cast(&buffer), 2); + } else { + this->write_array(reinterpret_cast(&data), 2); + } } - void write_byte16(uint16_t data) { - return this->parent_->template write_byte16(data); + virtual void write_array16(const uint16_t *data, size_t length) { + for (size_t i = 0; i != length; i++) { + this->write16(data[i]); + } } - void write_array16(const uint16_t *data, size_t length) { - this->parent_->template write_array16(data, length); + // write the contents of a buffer, ignore read data (buffer is unchanged.) + virtual void write_array(const uint8_t *ptr, size_t length) { + for (size_t i = 0; i != length; i++) + this->transfer(ptr[i]); } - void write_array(const uint8_t *data, size_t length) { - this->parent_->template write_array(data, length); + // read into a buffer, write nulls + virtual void read_array(uint8_t *ptr, size_t length) { + for (size_t i = 0; i != length; i++) + ptr[i] = this->transfer(0); } + // check if device is ready + virtual bool is_ready(); + + protected: + SPIBitOrder bit_order_{BIT_ORDER_MSB_FIRST}; + uint32_t data_rate_{1000000}; + SPIMode mode_{MODE0}; + GPIOPin *cs_pin_{NullPin::NULL_PIN}; + static SPIDelegate *const NULL_DELEGATE; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +}; + +/** + * A dummy SPIDelegate that complains if it's used. + */ + +class SPIDelegateDummy : public SPIDelegate { + public: + SPIDelegateDummy() = default; + + uint8_t transfer(uint8_t data) override { return 0; } + + void begin_transaction() override; +}; + +/** + * An implementation of SPI that relies only on software toggling of pins. + * + */ +class SPIDelegateBitBash : public SPIDelegate { + public: + SPIDelegateBitBash(uint32_t clock, SPIBitOrder bit_order, SPIMode mode, GPIOPin *cs_pin, GPIOPin *clk_pin, + GPIOPin *sdo_pin, GPIOPin *sdi_pin) + : SPIDelegate(clock, bit_order, mode, cs_pin), clk_pin_(clk_pin), sdo_pin_(sdo_pin), sdi_pin_(sdi_pin) { + // this calculation is pretty meaningless except at very low bit rates. + this->wait_cycle_ = uint32_t(arch_get_cpu_freq_hz()) / this->data_rate_ / 2ULL; + this->clock_polarity_ = Utility::get_polarity(this->mode_); + this->clock_phase_ = Utility::get_phase(this->mode_); + } + + uint8_t transfer(uint8_t data) override; + + protected: + GPIOPin *clk_pin_; + GPIOPin *sdo_pin_; + GPIOPin *sdi_pin_; + uint32_t last_transition_{0}; + uint32_t wait_cycle_; + SPIClockPolarity clock_polarity_; + SPIClockPhase clock_phase_; + + void HOT cycle_clock_() { + while (this->last_transition_ - arch_get_cpu_cycle_count() < this->wait_cycle_) + continue; + this->last_transition_ += this->wait_cycle_; + } +}; + +class SPIBus { + public: + SPIBus() = default; + + SPIBus(GPIOPin *clk, GPIOPin *sdo, GPIOPin *sdi) : clk_pin_(clk), sdo_pin_(sdo), sdi_pin_(sdi) {} + + virtual SPIDelegate *get_delegate(uint32_t data_rate, SPIBitOrder bit_order, SPIMode mode, GPIOPin *cs_pin) { + return new SPIDelegateBitBash(data_rate, bit_order, mode, cs_pin, this->clk_pin_, this->sdo_pin_, this->sdi_pin_); + } + + virtual bool is_hw() { return false; } + + protected: + GPIOPin *clk_pin_{}; + GPIOPin *sdo_pin_{}; + GPIOPin *sdi_pin_{}; +}; + +class SPIClient; + +class SPIComponent : public Component { + public: + SPIDelegate *register_device(SPIClient *device, SPIMode mode, SPIBitOrder bit_order, uint32_t data_rate, + GPIOPin *cs_pin); + void unregister_device(SPIClient *device); + + void set_clk(GPIOPin *clk) { this->clk_pin_ = clk; } + + void set_miso(GPIOPin *sdi) { this->sdi_pin_ = sdi; } + + void set_mosi(GPIOPin *sdo) { this->sdo_pin_ = sdo; } + + void set_interface(SPIInterface interface) { + this->interface_ = interface; + this->using_hw_ = true; + } + + void set_interface_name(const char *name) { this->interface_name_ = name; } + + float get_setup_priority() const override { return setup_priority::BUS; } + + void setup() override; + void dump_config() override; + + protected: + GPIOPin *clk_pin_{nullptr}; + GPIOPin *sdi_pin_{nullptr}; + GPIOPin *sdo_pin_{nullptr}; + SPIInterface interface_{}; + bool using_hw_{false}; + const char *interface_name_{nullptr}; + SPIBus *spi_bus_{}; + std::map devices_; + + static SPIBus *get_bus(SPIInterface interface, GPIOPin *clk, GPIOPin *sdo, GPIOPin *sdi); +}; + +/** + * Base class for SPIDevice, un-templated. + */ +class SPIClient { + public: + SPIClient(SPIBitOrder bit_order, SPIMode mode, uint32_t data_rate) + : bit_order_(bit_order), mode_(mode), data_rate_(data_rate) {} + + virtual void spi_setup() { + this->delegate_ = this->parent_->register_device(this, this->mode_, this->bit_order_, this->data_rate_, this->cs_); + } + + virtual void spi_teardown() { + this->parent_->unregister_device(this); + this->delegate_ = SPIDelegate::NULL_DELEGATE; + } + + bool spi_is_ready() { return this->delegate_->is_ready(); } + + protected: + SPIBitOrder bit_order_{BIT_ORDER_MSB_FIRST}; + SPIMode mode_{MODE0}; + uint32_t data_rate_{1000000}; + SPIComponent *parent_{nullptr}; + GPIOPin *cs_{nullptr}; + SPIDelegate *delegate_{SPIDelegate::NULL_DELEGATE}; +}; + +/** + * The SPIDevice is what components using the SPI will create. + * + * @tparam BIT_ORDER + * @tparam CLOCK_POLARITY + * @tparam CLOCK_PHASE + * @tparam DATA_RATE + */ +template +class SPIDevice : public SPIClient { + public: + SPIDevice() : SPIClient(BIT_ORDER, Utility::get_mode(CLOCK_POLARITY, CLOCK_PHASE), DATA_RATE) {} + + SPIDevice(SPIComponent *parent, GPIOPin *cs_pin) { + this->set_spi_parent(parent); + this->set_cs_pin(cs_pin); + } + + void spi_setup() override { SPIClient::spi_setup(); } + + void spi_teardown() override { SPIClient::spi_teardown(); } + + void set_spi_parent(SPIComponent *parent) { this->parent_ = parent; } + + void set_cs_pin(GPIOPin *cs) { this->cs_ = cs; } + + void set_data_rate(uint32_t data_rate) { this->data_rate_ = data_rate; } + + void set_bit_order(SPIBitOrder order) { + this->bit_order_ = order; + esph_log_d("spi.h", "bit order set to %d", order); + } + + void set_mode(SPIMode mode) { this->mode_ = mode; } + + uint8_t read_byte() { return this->delegate_->transfer(0); } + + void read_array(uint8_t *data, size_t length) { return this->delegate_->read_array(data, length); } + + void write_byte(uint8_t data) { this->delegate_->write_array(&data, 1); } + + void transfer_array(uint8_t *data, size_t length) { this->delegate_->transfer(data, length); } + + uint8_t transfer_byte(uint8_t data) { return this->delegate_->transfer(data); } + + // the driver will byte-swap if required. + void write_byte16(uint16_t data) { this->delegate_->write16(data); } + + // avoid use of this if possible. It's inefficient and ugly. + void write_array16(const uint16_t *data, size_t length) { this->delegate_->write_array16(data, length); } + + void enable() { this->delegate_->begin_transaction(); } + + void disable() { this->delegate_->end_transaction(); } + + void write_array(const uint8_t *data, size_t length) { this->delegate_->write_array(data, length); } + template void write_array(const std::array &data) { this->write_array(data.data(), N); } void write_array(const std::vector &data) { this->write_array(data.data(), data.size()); } - uint8_t transfer_byte(uint8_t data) { - return this->parent_->template transfer_byte(data); - } - - void transfer_array(uint8_t *data, size_t length) { - this->parent_->template transfer_array(data, length); - } - template void transfer_array(std::array &data) { this->transfer_array(data.data(), N); } - - protected: - SPIComponent *parent_{nullptr}; - GPIOPin *cs_{nullptr}; }; } // namespace spi diff --git a/esphome/components/spi/spi_arduino.cpp b/esphome/components/spi/spi_arduino.cpp new file mode 100644 index 0000000000..40ed9e6062 --- /dev/null +++ b/esphome/components/spi/spi_arduino.cpp @@ -0,0 +1,89 @@ +#include "spi.h" +#include + +namespace esphome { +namespace spi { + +#ifdef USE_ARDUINO + +static const char *const TAG = "spi-esp-arduino"; +class SPIDelegateHw : public SPIDelegate { + public: + SPIDelegateHw(SPIInterface channel, uint32_t data_rate, SPIBitOrder bit_order, SPIMode mode, GPIOPin *cs_pin) + : SPIDelegate(data_rate, bit_order, mode, cs_pin), channel_(channel) {} + + void begin_transaction() override { +#ifdef USE_RP2040 + SPISettings const settings(this->data_rate_, static_cast(this->bit_order_), this->mode_); +#else + SPISettings const settings(this->data_rate_, this->bit_order_, this->mode_); +#endif + this->channel_->beginTransaction(settings); + SPIDelegate::begin_transaction(); + } + + void transfer(uint8_t *ptr, size_t length) override { this->channel_->transfer(ptr, length); } + + void end_transaction() override { + this->channel_->endTransaction(); + SPIDelegate::end_transaction(); + } + + uint8_t transfer(uint8_t data) override { return this->channel_->transfer(data); } + + void write16(uint16_t data) override { this->channel_->transfer16(data); } + +#ifdef USE_RP2040 + void write_array(const uint8_t *ptr, size_t length) override { + // avoid overwriting the supplied buffer + uint8_t *rxbuf = new uint8_t[length]; // NOLINT(cppcoreguidelines-owning-memory) + memcpy(rxbuf, ptr, length); + this->channel_->transfer((void *) rxbuf, length); + delete[] rxbuf; // NOLINT(cppcoreguidelines-owning-memory) + } +#else + void write_array(const uint8_t *ptr, size_t length) override { this->channel_->writeBytes(ptr, length); } +#endif + + void read_array(uint8_t *ptr, size_t length) override { this->channel_->transfer(ptr, length); } + + protected: + SPIInterface channel_{}; +}; + +class SPIBusHw : public SPIBus { + public: + SPIBusHw(GPIOPin *clk, GPIOPin *sdo, GPIOPin *sdi, SPIInterface channel) : SPIBus(clk, sdo, sdi), channel_(channel) { +#ifdef USE_ESP8266 + channel->pins(Utility::get_pin_no(clk), Utility::get_pin_no(sdi), Utility::get_pin_no(sdo), -1); + channel->begin(); +#endif // USE_ESP8266 +#ifdef USE_ESP32 + channel->begin(Utility::get_pin_no(clk), Utility::get_pin_no(sdi), Utility::get_pin_no(sdo), -1); +#endif +#ifdef USE_RP2040 + if (Utility::get_pin_no(sdi) != -1) + channel->setRX(Utility::get_pin_no(sdi)); + if (Utility::get_pin_no(sdo) != -1) + channel->setTX(Utility::get_pin_no(sdo)); + channel->setSCK(Utility::get_pin_no(clk)); + channel->begin(); +#endif + } + + SPIDelegate *get_delegate(uint32_t data_rate, SPIBitOrder bit_order, SPIMode mode, GPIOPin *cs_pin) override { + return new SPIDelegateHw(this->channel_, data_rate, bit_order, mode, cs_pin); + } + + protected: + SPIInterface channel_{}; + bool is_hw() override { return true; } +}; + +SPIBus *SPIComponent::get_bus(SPIInterface interface, GPIOPin *clk, GPIOPin *sdo, GPIOPin *sdi) { + return new SPIBusHw(clk, sdo, sdi, interface); +} + +#endif // USE_ARDUINO +} // namespace spi +} // namespace esphome diff --git a/esphome/components/spi/spi_esp_idf.cpp b/esphome/components/spi/spi_esp_idf.cpp new file mode 100644 index 0000000000..f9e4bfcca6 --- /dev/null +++ b/esphome/components/spi/spi_esp_idf.cpp @@ -0,0 +1,163 @@ +#include "spi.h" +#include + +namespace esphome { +namespace spi { + +#ifdef USE_ESP_IDF +static const char *const TAG = "spi-esp-idf"; +static const size_t MAX_TRANSFER_SIZE = 4092; // dictated by ESP-IDF API. + +class SPIDelegateHw : public SPIDelegate { + public: + SPIDelegateHw(SPIInterface channel, uint32_t data_rate, SPIBitOrder bit_order, SPIMode mode, GPIOPin *cs_pin, + bool write_only) + : SPIDelegate(data_rate, bit_order, mode, cs_pin), channel_(channel), write_only_(write_only) { + spi_device_interface_config_t config = {}; + config.mode = static_cast(mode); + config.clock_speed_hz = static_cast(data_rate); + config.spics_io_num = -1; + config.flags = 0; + config.queue_size = 1; + config.pre_cb = nullptr; + config.post_cb = nullptr; + if (bit_order == BIT_ORDER_LSB_FIRST) + config.flags |= SPI_DEVICE_BIT_LSBFIRST; + if (write_only) + config.flags |= SPI_DEVICE_HALFDUPLEX | SPI_DEVICE_NO_DUMMY; + esp_err_t const err = spi_bus_add_device(channel, &config, &this->handle_); + if (err != ESP_OK) + ESP_LOGE(TAG, "Add device failed - err %X", err); + } + + bool is_ready() override { return this->handle_ != nullptr; } + + void begin_transaction() override { + if (this->is_ready()) { + if (spi_device_acquire_bus(this->handle_, portMAX_DELAY) != ESP_OK) + ESP_LOGE(TAG, "Failed to acquire SPI bus"); + SPIDelegate::begin_transaction(); + } else { + ESP_LOGW(TAG, "spi_setup called before initialisation"); + } + } + + void end_transaction() override { + if (this->is_ready()) { + SPIDelegate::end_transaction(); + spi_device_release_bus(this->handle_); + } + } + + ~SPIDelegateHw() override { + esp_err_t const err = spi_bus_remove_device(this->handle_); + if (err != ESP_OK) + ESP_LOGE(TAG, "Remove device failed - err %X", err); + } + + // do a transfer. either txbuf or rxbuf (but not both) may be null. + // transfers above the maximum size will be split. + // TODO - make use of the queue for interrupt transfers to provide a (short) pipeline of blocks + // when splitting is required. + void transfer(const uint8_t *txbuf, uint8_t *rxbuf, size_t length) override { + if (rxbuf != nullptr && this->write_only_) { + ESP_LOGE(TAG, "Attempted read from write-only channel"); + return; + } + spi_transaction_t desc = {}; + desc.flags = 0; + while (length != 0) { + size_t const partial = std::min(length, MAX_TRANSFER_SIZE); + desc.length = partial * 8; + desc.rxlength = this->write_only_ ? 0 : partial * 8; + desc.tx_buffer = txbuf; + desc.rx_buffer = rxbuf; + esp_err_t const err = spi_device_transmit(this->handle_, &desc); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Transmit failed - err %X", err); + break; + } + length -= partial; + if (txbuf != nullptr) + txbuf += partial; + if (rxbuf != nullptr) + rxbuf += partial; + } + } + + void transfer(uint8_t *ptr, size_t length) override { this->transfer(ptr, ptr, length); } + + uint8_t transfer(uint8_t data) override { + uint8_t rxbuf; + this->transfer(&data, &rxbuf, 1); + return rxbuf; + } + + void write16(uint16_t data) override { + if (this->bit_order_ == BIT_ORDER_MSB_FIRST) { + uint16_t txbuf = SPI_SWAP_DATA_TX(data, 16); + this->transfer((uint8_t *) &txbuf, nullptr, 2); + } else { + this->transfer((uint8_t *) &data, nullptr, 2); + } + } + + void write_array(const uint8_t *ptr, size_t length) override { this->transfer(ptr, nullptr, length); } + + void write_array16(const uint16_t *data, size_t length) override { + if (this->bit_order_ == BIT_ORDER_LSB_FIRST) { + this->write_array((uint8_t *) data, length * 2); + } else { + uint16_t buffer[MAX_TRANSFER_SIZE / 2]; + while (length != 0) { + size_t const partial = std::min(length, MAX_TRANSFER_SIZE / 2); + for (size_t i = 0; i != partial; i++) { + buffer[i] = SPI_SWAP_DATA_TX(*data++, 16); + } + this->write_array((const uint8_t *) buffer, partial * 2); + length -= partial; + } + } + } + + void read_array(uint8_t *ptr, size_t length) override { this->transfer(nullptr, ptr, length); } + + protected: + SPIInterface channel_{}; + spi_device_handle_t handle_{}; + bool write_only_{false}; +}; + +class SPIBusHw : public SPIBus { + public: + SPIBusHw(GPIOPin *clk, GPIOPin *sdo, GPIOPin *sdi, SPIInterface channel) : SPIBus(clk, sdo, sdi), channel_(channel) { + spi_bus_config_t buscfg = {}; + buscfg.mosi_io_num = Utility::get_pin_no(sdo); + buscfg.miso_io_num = Utility::get_pin_no(sdi); + buscfg.sclk_io_num = Utility::get_pin_no(clk); + buscfg.quadwp_io_num = -1; + buscfg.quadhd_io_num = -1; + buscfg.max_transfer_sz = MAX_TRANSFER_SIZE; + auto err = spi_bus_initialize(channel, &buscfg, SPI_DMA_CH_AUTO); + if (err != ESP_OK) + ESP_LOGE(TAG, "Bus init failed - err %X", err); + } + + SPIDelegate *get_delegate(uint32_t data_rate, SPIBitOrder bit_order, SPIMode mode, GPIOPin *cs_pin) override { + return new SPIDelegateHw(this->channel_, data_rate, bit_order, mode, cs_pin, + Utility::get_pin_no(this->sdi_pin_) == -1); + } + + protected: + SPIInterface channel_{}; + + bool is_hw() override { return true; } +}; + +SPIBus *SPIComponent::get_bus(SPIInterface interface, GPIOPin *clk, GPIOPin *sdo, GPIOPin *sdi) { + return new SPIBusHw(clk, sdo, sdi, interface); +} + +#endif +} // namespace spi +} // namespace esphome diff --git a/esphome/components/spi_device/__init__.py b/esphome/components/spi_device/__init__.py new file mode 100644 index 0000000000..428b5bfbda --- /dev/null +++ b/esphome/components/spi_device/__init__.py @@ -0,0 +1,49 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import spi +from esphome.const import CONF_ID, CONF_DATA_RATE, CONF_MODE + +DEPENDENCIES = ["spi"] +CODEOWNERS = ["@clydebarrow"] + +MULTI_CONF = True +spi_device_ns = cg.esphome_ns.namespace("spi_device") + +spi_device = spi_device_ns.class_("SPIDeviceComponent", cg.Component, spi.SPIDevice) + +Mode = spi.spi_ns.enum("SPIMode") +MODES = { + "0": Mode.MODE0, + "1": Mode.MODE1, + "2": Mode.MODE2, + "3": Mode.MODE3, + "MODE0": Mode.MODE0, + "MODE1": Mode.MODE1, + "MODE2": Mode.MODE2, + "MODE3": Mode.MODE3, +} + +BitOrder = spi.spi_ns.enum("SPIBitOrder") +ORDERS = { + "msb_first": BitOrder.BIT_ORDER_MSB_FIRST, + "lsb_first": BitOrder.BIT_ORDER_LSB_FIRST, +} +CONF_BIT_ORDER = "bit_order" + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_ID): cv.declare_id(spi_device), + cv.Optional(CONF_DATA_RATE, default="1MHz"): spi.SPI_DATA_RATE_SCHEMA, + cv.Optional(CONF_BIT_ORDER, default="msb_first"): cv.enum(ORDERS, lower=True), + cv.Optional(CONF_MODE, default="0"): cv.enum(MODES, upper=True), + } +).extend(spi.spi_device_schema(False)) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + cg.add(var.set_data_rate(config[CONF_DATA_RATE])) + cg.add(var.set_mode(config[CONF_MODE])) + cg.add(var.set_bit_order(config[CONF_BIT_ORDER])) + await spi.register_spi_device(var, config) diff --git a/esphome/components/spi_device/spi_device.cpp b/esphome/components/spi_device/spi_device.cpp new file mode 100644 index 0000000000..4e0b72ae60 --- /dev/null +++ b/esphome/components/spi_device/spi_device.cpp @@ -0,0 +1,30 @@ +#include "spi_device.h" +#include "esphome/core/log.h" +#include "esphome/core/hal.h" + +namespace esphome { +namespace spi_device { + +static const char *const TAG = "spi_device"; + +void SPIDeviceComponent::setup() { + ESP_LOGD(TAG, "Setting up SPIDevice..."); + this->spi_setup(); + ESP_LOGCONFIG(TAG, "SPIDevice started!"); +} + +void SPIDeviceComponent::dump_config() { + ESP_LOGCONFIG(TAG, "SPIDevice"); + LOG_PIN(" CS pin: ", this->cs_); + ESP_LOGCONFIG(TAG, " Mode: %d", this->mode_); + if (this->data_rate_ < 1000000) { + ESP_LOGCONFIG(TAG, " Data rate: %dkHz", this->data_rate_ / 1000); + } else { + ESP_LOGCONFIG(TAG, " Data rate: %dMHz", this->data_rate_ / 1000000); + } +} + +float SPIDeviceComponent::get_setup_priority() const { return setup_priority::DATA; } + +} // namespace spi_device +} // namespace esphome diff --git a/esphome/components/spi_device/spi_device.h b/esphome/components/spi_device/spi_device.h new file mode 100644 index 0000000000..d8aef440a7 --- /dev/null +++ b/esphome/components/spi_device/spi_device.h @@ -0,0 +1,22 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/spi/spi.h" + +namespace esphome { +namespace spi_device { + +class SPIDeviceComponent : public Component, + public spi::SPIDevice { + public: + void setup() override; + void dump_config() override; + + float get_setup_priority() const override; + + protected: +}; + +} // namespace spi_device +} // namespace esphome diff --git a/tests/test4.yaml b/tests/test4.yaml index 1175bb207c..341e613785 100644 --- a/tests/test4.yaml +++ b/tests/test4.yaml @@ -32,6 +32,7 @@ spi: clk_pin: GPIO21 mosi_pin: GPIO22 miso_pin: GPIO23 + interface: hardware uart: - id: uart115200 diff --git a/tests/test8.yaml b/tests/test8.yaml index 28c6e78b87..498da94483 100644 --- a/tests/test8.yaml +++ b/tests/test8.yaml @@ -31,8 +31,17 @@ light: restore_mode: ALWAYS_OFF spi: + id: spi_id_1 clk_pin: GPIO7 mosi_pin: GPIO6 + interface: any + +spi_device: + id: spidev + data_rate: 2MHz + spi_id: spi_id_1 + mode: 3 + bit_order: lsb_first display: - platform: ili9xxx From b19a7e006efda8c40010ccd381976577b3f00c0c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 9 Sep 2023 08:57:10 +1200 Subject: [PATCH 086/111] Bump actions/cache from 3.3.1 to 3.3.2 (#5367) Bumps [actions/cache](https://github.com/actions/cache) from 3.3.1 to 3.3.2. - [Release notes](https://github.com/actions/cache/releases) - [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md) - [Commits](https://github.com/actions/cache/compare/v3.3.1...v3.3.2) --- updated-dependencies: - dependency-name: actions/cache dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1de5822960..4214bc2c5d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,7 +41,7 @@ jobs: python-version: ${{ env.DEFAULT_PYTHON }} - name: Restore Python virtual environment id: cache-venv - uses: actions/cache@v3.3.1 + uses: actions/cache@v3.3.2 with: path: venv # yamllint disable-line rule:line-length @@ -298,7 +298,7 @@ jobs: python-version: ${{ env.DEFAULT_PYTHON }} cache-key: ${{ needs.common.outputs.cache-key }} - name: Cache platformio - uses: actions/cache@v3.3.1 + uses: actions/cache@v3.3.2 with: path: ~/.platformio # yamllint disable-line rule:line-length From 2fd6942de4bef6d2bc2fa556ec942ef558201379 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 9 Sep 2023 08:57:35 +1200 Subject: [PATCH 087/111] Bump zeroconf from 0.88.0 to 0.102.0 (#5368) Bumps [zeroconf](https://github.com/python-zeroconf/python-zeroconf) from 0.88.0 to 0.102.0. - [Release notes](https://github.com/python-zeroconf/python-zeroconf/releases) - [Changelog](https://github.com/python-zeroconf/python-zeroconf/blob/master/CHANGELOG.md) - [Commits](https://github.com/python-zeroconf/python-zeroconf/compare/0.88.0...0.102.0) --- updated-dependencies: - dependency-name: zeroconf dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index dcb7420d3f..9bce4a309d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,7 +11,7 @@ esptool==4.6.2 click==8.1.7 esphome-dashboard==20230904.0 aioesphomeapi==15.0.0 -zeroconf==0.88.0 +zeroconf==0.102.0 # esp-idf requires this, but doesn't bundle it by default # https://github.com/espressif/esp-idf/blob/220590d599e134d7a5e7f1e683cc4550349ffbf8/requirements.txt#L24 From d9523a0cbf556c3483d546ce42da808345af01f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20S=C3=A1rk=C3=B6zi?= Date: Fri, 8 Sep 2023 23:10:20 +0200 Subject: [PATCH 088/111] Fix repeat.count = 0 case (#5364) * Only play first action if count is non-zero * Add test to yaml * Update test5.yaml --- esphome/core/base_automation.h | 6 +++++- tests/test5.yaml | 7 +++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/esphome/core/base_automation.h b/esphome/core/base_automation.h index daa09b912e..a17b6a6f85 100644 --- a/esphome/core/base_automation.h +++ b/esphome/core/base_automation.h @@ -249,7 +249,11 @@ template class RepeatAction : public Action { void play_complex(Ts... x) override { this->num_running_++; this->var_ = std::make_tuple(x...); - this->then_.play(0, x...); + if (this->count_.value(x...) > 0) { + this->then_.play(0, x...); + } else { + this->play_next_tuple_(this->var_); + } } void play(Ts... x) override { /* ignore - see play_complex */ diff --git a/tests/test5.yaml b/tests/test5.yaml index a1cc3103d7..417f3bfecd 100644 --- a/tests/test5.yaml +++ b/tests/test5.yaml @@ -563,6 +563,13 @@ script: then: - logger.log: looping! + - id: zero_repeat_test + then: + - repeat: + count: !lambda "return 0;" + then: + - logger.log: shouldn't see mee! + switch: - platform: modbus_controller modbus_controller_id: modbus_controller_test From 9cf115a7526a51b8e951586f8b4488784afcf41c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Fri, 8 Sep 2023 23:20:26 +0200 Subject: [PATCH 089/111] Fix dashboard download for ESP32 variants (#5355) --- esphome/dashboard/dashboard.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index 0d6ec8dc13..cacd5e2db0 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -540,7 +540,10 @@ class DownloadListRequestHandler(BaseHandler): self.send_error(404) return - from esphome.components.esp32 import get_download_types as esp32_types + from esphome.components.esp32 import ( + get_download_types as esp32_types, + VARIANTS as ESP32_VARIANTS, + ) from esphome.components.esp8266 import get_download_types as esp8266_types from esphome.components.rp2040 import get_download_types as rp2040_types from esphome.components.libretiny import get_download_types as libretiny_types @@ -551,7 +554,7 @@ class DownloadListRequestHandler(BaseHandler): downloads = rp2040_types(storage_json) elif platform == const.PLATFORM_ESP8266: downloads = esp8266_types(storage_json) - elif platform == const.PLATFORM_ESP32: + elif platform.upper() in ESP32_VARIANTS: downloads = esp32_types(storage_json) elif platform == const.PLATFORM_BK72XX: downloads = libretiny_types(storage_json) From ccc30116ba5c31af3e2e71e1338c579e66433005 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 9 Sep 2023 09:20:54 +1200 Subject: [PATCH 090/111] Bump pytest from 7.4.1 to 7.4.2 (#5357) Bumps [pytest](https://github.com/pytest-dev/pytest) from 7.4.1 to 7.4.2. - [Release notes](https://github.com/pytest-dev/pytest/releases) - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest/compare/7.4.1...7.4.2) --- updated-dependencies: - dependency-name: pytest dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 2d46d3dccd..f17ccd220d 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -5,7 +5,7 @@ pyupgrade==3.10.1 # also change in .pre-commit-config.yaml when updating pre-commit # Unit tests -pytest==7.4.1 +pytest==7.4.2 pytest-cov==4.1.0 pytest-mock==3.11.1 pytest-asyncio==0.21.1 From 7bb67ae94b0399467a4f9752b5111b1c613f2643 Mon Sep 17 00:00:00 2001 From: Ilia Sotnikov Date: Sat, 9 Sep 2023 12:00:45 +0300 Subject: [PATCH 091/111] [ADC] Support measuring VCC on Raspberry Pico (W) (#5335) * [ADC] Support measuring VCC on Raspberry Pico (W) Added support for measuring VCC on Raspberry Pico (W) with ADC. GPIO pin is provided as `VCC`, same as with ESP8266. VSYS is the voltage being actually processed, and might have an offset from actual power supply voltage (e.g. USB on VBUS) due to voltage drop on Schottky diode between VSYS and VBUS on Rasberry Pico. The offset has experimentally been found to be ~0.25V on Pico W and ~0.1 on Pico, presumably due to different power consumption. Example usage: sensor: - platform: adc pin: VCC name: "VSYS" * + Added tests for VCC measuring on `rpipicow` board --- esphome/components/adc/__init__.py | 6 +++- esphome/components/adc/adc_sensor.cpp | 40 +++++++++++++++++++++++++-- tests/test6.yaml | 3 ++ 3 files changed, 45 insertions(+), 4 deletions(-) diff --git a/esphome/components/adc/__init__.py b/esphome/components/adc/__init__.py index ba72951777..0b6ee145f2 100644 --- a/esphome/components/adc/__init__.py +++ b/esphome/components/adc/__init__.py @@ -5,6 +5,10 @@ from esphome.const import CONF_ANALOG, CONF_INPUT from esphome.core import CORE from esphome.components.esp32 import get_esp32_variant +from esphome.const import ( + PLATFORM_ESP8266, + PLATFORM_RP2040, +) from esphome.components.esp32.const import ( VARIANT_ESP32, VARIANT_ESP32C2, @@ -143,7 +147,7 @@ ESP32_VARIANT_ADC2_PIN_TO_CHANNEL = { def validate_adc_pin(value): if str(value).upper() == "VCC": - return cv.only_on_esp8266("VCC") + return cv.only_on([PLATFORM_ESP8266, PLATFORM_RP2040])("VCC") if str(value).upper() == "TEMPERATURE": return cv.only_on_rp2040("TEMPERATURE") diff --git a/esphome/components/adc/adc_sensor.cpp b/esphome/components/adc/adc_sensor.cpp index 0642cd7f3f..e69e6b9313 100644 --- a/esphome/components/adc/adc_sensor.cpp +++ b/esphome/components/adc/adc_sensor.cpp @@ -12,6 +12,9 @@ ADC_MODE(ADC_VCC) #endif #ifdef USE_RP2040 +#ifdef CYW43_USES_VSYS_PIN +#include "pico/cyw43_arch.h" +#endif #include #endif @@ -123,13 +126,19 @@ void ADCSensor::dump_config() { } } #endif // USE_ESP32 + #ifdef USE_RP2040 if (this->is_temperature_) { ESP_LOGCONFIG(TAG, " Pin: Temperature"); } else { +#ifdef USE_ADC_SENSOR_VCC + ESP_LOGCONFIG(TAG, " Pin: VCC"); +#else LOG_PIN(" Pin: ", pin_); +#endif // USE_ADC_SENSOR_VCC } -#endif +#endif // USE_RP2040 + LOG_UPDATE_INTERVAL(this); } @@ -238,7 +247,20 @@ float ADCSensor::sample() { delay(1); adc_select_input(4); } else { - uint8_t pin = this->pin_->get_pin(); + uint8_t pin; +#ifdef USE_ADC_SENSOR_VCC +#ifdef CYW43_USES_VSYS_PIN + // Measuring VSYS on Raspberry Pico W needs to be wrapped with + // `cyw43_thread_enter()`/`cyw43_thread_exit()` as discussed in + // https://github.com/raspberrypi/pico-sdk/issues/1222, since Wifi chip and + // VSYS ADC both share GPIO29 + cyw43_thread_enter(); +#endif // CYW43_USES_VSYS_PIN + pin = PICO_VSYS_PIN; +#else + pin = this->pin_->get_pin(); +#endif // USE_ADC_SENSOR_VCC + adc_gpio_init(pin); adc_select_input(pin - 26); } @@ -246,11 +268,23 @@ float ADCSensor::sample() { int32_t raw = adc_read(); if (this->is_temperature_) { adc_set_temp_sensor_enabled(false); + } else { +#ifdef USE_ADC_SENSOR_VCC +#ifdef CYW43_USES_VSYS_PIN + cyw43_thread_exit(); +#endif // CYW43_USES_VSYS_PIN +#endif // USE_ADC_SENSOR_VCC } + if (output_raw_) { return raw; } - return raw * 3.3f / 4096.0f; + float coeff = 1.0; +#ifdef USE_ADC_SENSOR_VCC + // As per Raspberry Pico (W) datasheet (section 2.1) the VSYS/3 is measured + coeff = 3.0; +#endif // USE_ADC_SENSOR_VCC + return raw * 3.3f / 4096.0f * coeff; } #endif diff --git a/tests/test6.yaml b/tests/test6.yaml index f048a4fa14..3d6a1ceb1f 100644 --- a/tests/test6.yaml +++ b/tests/test6.yaml @@ -62,3 +62,6 @@ switch: sensor: - platform: internal_temperature name: Internal Temperature + - platform: adc + pin: VCC + name: VSYS From 0c84224ca265c483db4de784061062f7e80d3852 Mon Sep 17 00:00:00 2001 From: "Kevin P. Fleming" Date: Sat, 9 Sep 2023 19:19:54 -0400 Subject: [PATCH 092/111] Move CONF_PHASE_A/B/C constants to const.py. (#5304) --- esphome/components/atm90e32/sensor.py | 7 +++---- esphome/components/growatt_solar/sensor.py | 7 +++---- esphome/components/havells_solar/sensor.py | 6 +++--- esphome/const.py | 3 +++ 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/esphome/components/atm90e32/sensor.py b/esphome/components/atm90e32/sensor.py index 6cc0f6ac3e..af4d2ef412 100644 --- a/esphome/components/atm90e32/sensor.py +++ b/esphome/components/atm90e32/sensor.py @@ -6,6 +6,9 @@ from esphome.const import ( CONF_REACTIVE_POWER, CONF_VOLTAGE, CONF_CURRENT, + CONF_PHASE_A, + CONF_PHASE_B, + CONF_PHASE_C, CONF_POWER, CONF_POWER_FACTOR, CONF_FREQUENCY, @@ -31,10 +34,6 @@ from esphome.const import ( UNIT_WATT_HOURS, ) -CONF_PHASE_A = "phase_a" -CONF_PHASE_B = "phase_b" -CONF_PHASE_C = "phase_c" - CONF_LINE_FREQUENCY = "line_frequency" CONF_CHIP_TEMPERATURE = "chip_temperature" CONF_GAIN_PGA = "gain_pga" diff --git a/esphome/components/growatt_solar/sensor.py b/esphome/components/growatt_solar/sensor.py index f95d679c3e..0db15ae53e 100644 --- a/esphome/components/growatt_solar/sensor.py +++ b/esphome/components/growatt_solar/sensor.py @@ -6,6 +6,9 @@ from esphome.const import ( CONF_CURRENT, CONF_FREQUENCY, CONF_ID, + CONF_PHASE_A, + CONF_PHASE_B, + CONF_PHASE_C, CONF_VOLTAGE, DEVICE_CLASS_CURRENT, DEVICE_CLASS_ENERGY, @@ -21,10 +24,6 @@ from esphome.const import ( UNIT_WATT, ) -CONF_PHASE_A = "phase_a" -CONF_PHASE_B = "phase_b" -CONF_PHASE_C = "phase_c" - CONF_ENERGY_PRODUCTION_DAY = "energy_production_day" CONF_TOTAL_ENERGY_PRODUCTION = "total_energy_production" CONF_TOTAL_GENERATION_TIME = "total_generation_time" diff --git a/esphome/components/havells_solar/sensor.py b/esphome/components/havells_solar/sensor.py index d7c8d544f9..66b72f9e3e 100644 --- a/esphome/components/havells_solar/sensor.py +++ b/esphome/components/havells_solar/sensor.py @@ -6,6 +6,9 @@ from esphome.const import ( CONF_CURRENT, CONF_FREQUENCY, CONF_ID, + CONF_PHASE_A, + CONF_PHASE_B, + CONF_PHASE_C, CONF_REACTIVE_POWER, CONF_VOLTAGE, DEVICE_CLASS_CURRENT, @@ -24,9 +27,6 @@ from esphome.const import ( UNIT_WATT, ) -CONF_PHASE_A = "phase_a" -CONF_PHASE_B = "phase_b" -CONF_PHASE_C = "phase_c" CONF_ENERGY_PRODUCTION_DAY = "energy_production_day" CONF_TOTAL_ENERGY_PRODUCTION = "total_energy_production" CONF_TOTAL_GENERATION_TIME = "total_generation_time" diff --git a/esphome/const.py b/esphome/const.py index 067fd23946..0e4e880c19 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -538,8 +538,11 @@ CONF_PAYLOAD_AVAILABLE = "payload_available" CONF_PAYLOAD_NOT_AVAILABLE = "payload_not_available" CONF_PERIOD = "period" CONF_PH = "ph" +CONF_PHASE_A = "phase_a" CONF_PHASE_ANGLE = "phase_angle" +CONF_PHASE_B = "phase_b" CONF_PHASE_BALANCER = "phase_balancer" +CONF_PHASE_C = "phase_c" CONF_PIN = "pin" CONF_PIN_A = "pin_a" CONF_PIN_B = "pin_b" From e66047e07212aa214542949da68049c914901d6a Mon Sep 17 00:00:00 2001 From: Flaviu Tamas Date: Sat, 9 Sep 2023 22:25:09 -0400 Subject: [PATCH 093/111] Add BMI160 support (#5143) * Add BMI160 support * BMI160: use set_timeout for delay * Add support for old compilers Fix "warning: missing terminating ' character" * Increase power-on delay to be more conservative * Add helper for reading little-endian data over i2c * Replace configuration names with globals Note: for testing with external components, you will need to comment out the import & define your own CONF_GYROSCOPE_X, etc, in this file * Improve icons * Fix tests & lint --- CODEOWNERS | 1 + esphome/components/bmi160/__init__.py | 1 + esphome/components/bmi160/bmi160.cpp | 270 ++++++++++++++++++++++++++ esphome/components/bmi160/bmi160.h | 44 +++++ esphome/components/bmi160/sensor.py | 102 ++++++++++ esphome/const.py | 6 + tests/test1.yaml | 17 ++ 7 files changed, 441 insertions(+) create mode 100644 esphome/components/bmi160/__init__.py create mode 100644 esphome/components/bmi160/bmi160.cpp create mode 100644 esphome/components/bmi160/bmi160.h create mode 100644 esphome/components/bmi160/sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index ab4c5011f6..2658aebdc7 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -49,6 +49,7 @@ esphome/components/bl0942/* @dbuezas esphome/components/ble_client/* @buxtronix esphome/components/bluetooth_proxy/* @jesserockz esphome/components/bme680_bsec/* @trvrnrth +esphome/components/bmi160/* @flaviut esphome/components/bmp3xx/* @martgras esphome/components/bmp581/* @kahrendt esphome/components/bp1658cj/* @Cossid diff --git a/esphome/components/bmi160/__init__.py b/esphome/components/bmi160/__init__.py new file mode 100644 index 0000000000..49b6d0252a --- /dev/null +++ b/esphome/components/bmi160/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@flaviut"] diff --git a/esphome/components/bmi160/bmi160.cpp b/esphome/components/bmi160/bmi160.cpp new file mode 100644 index 0000000000..69b4694345 --- /dev/null +++ b/esphome/components/bmi160/bmi160.cpp @@ -0,0 +1,270 @@ +#include "bmi160.h" +#include "esphome/core/hal.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace bmi160 { + +static const char *const TAG = "bmi160"; + +const uint8_t BMI160_REGISTER_CHIPID = 0x00; + +const uint8_t BMI160_REGISTER_CMD = 0x7E; +enum class Cmd : uint8_t { + START_FOC = 0x03, + ACCL_SET_PMU_MODE = 0b00010000, // last 2 bits are mode + GYRO_SET_PMU_MODE = 0b00010100, // last 2 bits are mode + MAG_SET_PMU_MODE = 0b00011000, // last 2 bits are mode + PROG_NVM = 0xA0, + FIFO_FLUSH = 0xB0, + INT_RESET = 0xB1, + SOFT_RESET = 0xB6, + STEP_CNT_CLR = 0xB2, +}; +enum class GyroPmuMode : uint8_t { + SUSPEND = 0b00, + NORMAL = 0b01, + LOW_POWER = 0b10, +}; +enum class AcclPmuMode : uint8_t { + SUSPEND = 0b00, + NORMAL = 0b01, + FAST_STARTUP = 0b11, +}; +enum class MagPmuMode : uint8_t { + SUSPEND = 0b00, + NORMAL = 0b01, + LOW_POWER = 0b10, +}; + +const uint8_t BMI160_REGISTER_ACCEL_CONFIG = 0x40; +enum class AcclFilterMode : uint8_t { + POWER_SAVING = 0b00000000, + PERF = 0b10000000, +}; +enum class AcclBandwidth : uint8_t { + OSR4_AVG1 = 0b00000000, + OSR2_AVG2 = 0b00010000, + NORMAL_AVG4 = 0b00100000, + RES_AVG8 = 0b00110000, + RES_AVG16 = 0b01000000, + RES_AVG32 = 0b01010000, + RES_AVG64 = 0b01100000, + RES_AVG128 = 0b01110000, +}; +enum class AccelOutputDataRate : uint8_t { + HZ_25_32 = 0b0001, // 25/32 Hz + HZ_25_16 = 0b0010, // 25/16 Hz + HZ_25_8 = 0b0011, // 25/8 Hz + HZ_25_4 = 0b0100, // 25/4 Hz + HZ_25_2 = 0b0101, // 25/2 Hz + HZ_25 = 0b0110, // 25 Hz + HZ_50 = 0b0111, // 50 Hz + HZ_100 = 0b1000, // 100 Hz + HZ_200 = 0b1001, // 200 Hz + HZ_400 = 0b1010, // 400 Hz + HZ_800 = 0b1011, // 800 Hz + HZ_1600 = 0b1100, // 1600 Hz +}; +const uint8_t BMI160_REGISTER_ACCEL_RANGE = 0x41; +enum class AccelRange : uint8_t { + RANGE_2G = 0b0011, + RANGE_4G = 0b0101, + RANGE_8G = 0b1000, + RANGE_16G = 0b1100, +}; + +const uint8_t BMI160_REGISTER_GYRO_CONFIG = 0x42; +enum class GyroBandwidth : uint8_t { + OSR4 = 0x00, + OSR2 = 0x10, + NORMAL = 0x20, +}; +enum class GyroOuputDataRate : uint8_t { + HZ_25 = 0x06, + HZ_50 = 0x07, + HZ_100 = 0x08, + HZ_200 = 0x09, + HZ_400 = 0x0A, + HZ_800 = 0x0B, + HZ_1600 = 0x0C, + HZ_3200 = 0x0D, +}; +const uint8_t BMI160_REGISTER_GYRO_RANGE = 0x43; +enum class GyroRange : uint8_t { + RANGE_2000_DPS = 0x0, // ±2000 °/s + RANGE_1000_DPS = 0x1, + RANGE_500_DPS = 0x2, + RANGE_250_DPS = 0x3, + RANGE_125_DPS = 0x4, +}; + +const uint8_t BMI160_REGISTER_DATA_GYRO_X_LSB = 0x0C; +const uint8_t BMI160_REGISTER_DATA_GYRO_X_MSB = 0x0D; +const uint8_t BMI160_REGISTER_DATA_GYRO_Y_LSB = 0x0E; +const uint8_t BMI160_REGISTER_DATA_GYRO_Y_MSB = 0x0F; +const uint8_t BMI160_REGISTER_DATA_GYRO_Z_LSB = 0x10; +const uint8_t BMI160_REGISTER_DATA_GYRO_Z_MSB = 0x11; +const uint8_t BMI160_REGISTER_DATA_ACCEL_X_LSB = 0x12; +const uint8_t BMI160_REGISTER_DATA_ACCEL_X_MSB = 0x13; +const uint8_t BMI160_REGISTER_DATA_ACCEL_Y_LSB = 0x14; +const uint8_t BMI160_REGISTER_DATA_ACCEL_Y_MSB = 0x15; +const uint8_t BMI160_REGISTER_DATA_ACCEL_Z_LSB = 0x16; +const uint8_t BMI160_REGISTER_DATA_ACCEL_Z_MSB = 0x17; +const uint8_t BMI160_REGISTER_DATA_TEMP_LSB = 0x20; +const uint8_t BMI160_REGISTER_DATA_TEMP_MSB = 0x21; + +const float GRAVITY_EARTH = 9.80665f; + +void BMI160Component::internal_setup_(int stage) { + switch (stage) { + case 0: + ESP_LOGCONFIG(TAG, "Setting up BMI160..."); + uint8_t chipid; + if (!this->read_byte(BMI160_REGISTER_CHIPID, &chipid) || (chipid != 0b11010001)) { + this->mark_failed(); + return; + } + + ESP_LOGV(TAG, " Bringing accelerometer out of sleep..."); + if (!this->write_byte(BMI160_REGISTER_CMD, (uint8_t) Cmd::ACCL_SET_PMU_MODE | (uint8_t) AcclPmuMode::NORMAL)) { + this->mark_failed(); + return; + } + ESP_LOGV(TAG, " Waiting for accelerometer to wake up..."); + // need to wait (max delay in datasheet) because we can't send commands while another is in progress + // min 5ms, 10ms + this->set_timeout(10, [this]() { this->internal_setup_(1); }); + break; + + case 1: + ESP_LOGV(TAG, " Bringing gyroscope out of sleep..."); + if (!this->write_byte(BMI160_REGISTER_CMD, (uint8_t) Cmd::GYRO_SET_PMU_MODE | (uint8_t) GyroPmuMode::NORMAL)) { + this->mark_failed(); + return; + } + ESP_LOGV(TAG, " Waiting for gyroscope to wake up..."); + // wait between 51 & 81ms, doing 100 to be safe + this->set_timeout(10, [this]() { this->internal_setup_(2); }); + break; + + case 2: + ESP_LOGV(TAG, " Setting up Gyro Config..."); + uint8_t gyro_config = (uint8_t) GyroBandwidth::OSR4 | (uint8_t) GyroOuputDataRate::HZ_25; + ESP_LOGV(TAG, " Output gyro_config: 0b" BYTE_TO_BINARY_PATTERN, BYTE_TO_BINARY(gyro_config)); + if (!this->write_byte(BMI160_REGISTER_GYRO_CONFIG, gyro_config)) { + this->mark_failed(); + return; + } + ESP_LOGV(TAG, " Setting up Gyro Range..."); + uint8_t gyro_range = (uint8_t) GyroRange::RANGE_2000_DPS; + ESP_LOGV(TAG, " Output gyro_range: 0b" BYTE_TO_BINARY_PATTERN, BYTE_TO_BINARY(gyro_range)); + if (!this->write_byte(BMI160_REGISTER_GYRO_RANGE, gyro_range)) { + this->mark_failed(); + return; + } + + ESP_LOGV(TAG, " Setting up Accel Config..."); + uint8_t accel_config = + (uint8_t) AcclFilterMode::PERF | (uint8_t) AcclBandwidth::RES_AVG16 | (uint8_t) AccelOutputDataRate::HZ_25; + ESP_LOGV(TAG, " Output accel_config: 0b" BYTE_TO_BINARY_PATTERN, BYTE_TO_BINARY(accel_config)); + if (!this->write_byte(BMI160_REGISTER_ACCEL_CONFIG, accel_config)) { + this->mark_failed(); + return; + } + ESP_LOGV(TAG, " Setting up Accel Range..."); + uint8_t accel_range = (uint8_t) AccelRange::RANGE_16G; + ESP_LOGV(TAG, " Output accel_range: 0b" BYTE_TO_BINARY_PATTERN, BYTE_TO_BINARY(accel_range)); + if (!this->write_byte(BMI160_REGISTER_ACCEL_RANGE, accel_range)) { + this->mark_failed(); + return; + } + + this->setup_complete_ = true; + } +} + +void BMI160Component::setup() { this->internal_setup_(0); } +void BMI160Component::dump_config() { + ESP_LOGCONFIG(TAG, "BMI160:"); + LOG_I2C_DEVICE(this); + if (this->is_failed()) { + ESP_LOGE(TAG, "Communication with BMI160 failed!"); + } + LOG_UPDATE_INTERVAL(this); + LOG_SENSOR(" ", "Acceleration X", this->accel_x_sensor_); + LOG_SENSOR(" ", "Acceleration Y", this->accel_y_sensor_); + LOG_SENSOR(" ", "Acceleration Z", this->accel_z_sensor_); + LOG_SENSOR(" ", "Gyro X", this->gyro_x_sensor_); + LOG_SENSOR(" ", "Gyro Y", this->gyro_y_sensor_); + LOG_SENSOR(" ", "Gyro Z", this->gyro_z_sensor_); + LOG_SENSOR(" ", "Temperature", this->temperature_sensor_); +} + +i2c::ErrorCode BMI160Component::read_le_int16_(uint8_t reg, int16_t *value, uint8_t len) { + uint8_t raw_data[len * 2]; + // read using read_register because we have little-endian data, and read_bytes_16 will swap it + i2c::ErrorCode err = this->read_register(reg, raw_data, len * 2, true); + if (err != i2c::ERROR_OK) { + return err; + } + for (int i = 0; i < len; i++) { + value[i] = (int16_t) ((uint16_t) raw_data[i * 2] | ((uint16_t) raw_data[i * 2 + 1] << 8)); + } + return err; +} + +void BMI160Component::update() { + if (!this->setup_complete_) { + return; + } + + ESP_LOGV(TAG, " Updating BMI160..."); + int16_t data[6]; + if (this->read_le_int16_(BMI160_REGISTER_DATA_GYRO_X_LSB, data, 6) != i2c::ERROR_OK) { + this->status_set_warning(); + return; + } + + float gyro_x = (float) data[0] / (float) INT16_MAX * 2000.f; + float gyro_y = (float) data[1] / (float) INT16_MAX * 2000.f; + float gyro_z = (float) data[2] / (float) INT16_MAX * 2000.f; + float accel_x = (float) data[3] / (float) INT16_MAX * 16 * GRAVITY_EARTH; + float accel_y = (float) data[4] / (float) INT16_MAX * 16 * GRAVITY_EARTH; + float accel_z = (float) data[5] / (float) INT16_MAX * 16 * GRAVITY_EARTH; + + int16_t raw_temperature; + if (this->read_le_int16_(BMI160_REGISTER_DATA_TEMP_LSB, &raw_temperature, 1) != i2c::ERROR_OK) { + this->status_set_warning(); + return; + } + float temperature = (float) raw_temperature / (float) INT16_MAX * 64.5f + 23.f; + + ESP_LOGD(TAG, + "Got accel={x=%.3f m/s², y=%.3f m/s², z=%.3f m/s²}, " + "gyro={x=%.3f °/s, y=%.3f °/s, z=%.3f °/s}, temp=%.3f°C", + accel_x, accel_y, accel_z, gyro_x, gyro_y, gyro_z, temperature); + + if (this->accel_x_sensor_ != nullptr) + this->accel_x_sensor_->publish_state(accel_x); + if (this->accel_y_sensor_ != nullptr) + this->accel_y_sensor_->publish_state(accel_y); + if (this->accel_z_sensor_ != nullptr) + this->accel_z_sensor_->publish_state(accel_z); + + if (this->temperature_sensor_ != nullptr) + this->temperature_sensor_->publish_state(temperature); + + if (this->gyro_x_sensor_ != nullptr) + this->gyro_x_sensor_->publish_state(gyro_x); + if (this->gyro_y_sensor_ != nullptr) + this->gyro_y_sensor_->publish_state(gyro_y); + if (this->gyro_z_sensor_ != nullptr) + this->gyro_z_sensor_->publish_state(gyro_z); + + this->status_clear_warning(); +} +float BMI160Component::get_setup_priority() const { return setup_priority::DATA; } + +} // namespace bmi160 +} // namespace esphome diff --git a/esphome/components/bmi160/bmi160.h b/esphome/components/bmi160/bmi160.h new file mode 100644 index 0000000000..47691a4de9 --- /dev/null +++ b/esphome/components/bmi160/bmi160.h @@ -0,0 +1,44 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/i2c/i2c.h" + +namespace esphome { +namespace bmi160 { + +class BMI160Component : public PollingComponent, public i2c::I2CDevice { + public: + void setup() override; + void dump_config() override; + + void update() override; + + float get_setup_priority() const override; + + void set_accel_x_sensor(sensor::Sensor *accel_x_sensor) { accel_x_sensor_ = accel_x_sensor; } + void set_accel_y_sensor(sensor::Sensor *accel_y_sensor) { accel_y_sensor_ = accel_y_sensor; } + void set_accel_z_sensor(sensor::Sensor *accel_z_sensor) { accel_z_sensor_ = accel_z_sensor; } + void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; } + void set_gyro_x_sensor(sensor::Sensor *gyro_x_sensor) { gyro_x_sensor_ = gyro_x_sensor; } + void set_gyro_y_sensor(sensor::Sensor *gyro_y_sensor) { gyro_y_sensor_ = gyro_y_sensor; } + void set_gyro_z_sensor(sensor::Sensor *gyro_z_sensor) { gyro_z_sensor_ = gyro_z_sensor; } + + protected: + sensor::Sensor *accel_x_sensor_{nullptr}; + sensor::Sensor *accel_y_sensor_{nullptr}; + sensor::Sensor *accel_z_sensor_{nullptr}; + sensor::Sensor *temperature_sensor_{nullptr}; + sensor::Sensor *gyro_x_sensor_{nullptr}; + sensor::Sensor *gyro_y_sensor_{nullptr}; + sensor::Sensor *gyro_z_sensor_{nullptr}; + + void internal_setup_(int stage); + bool setup_complete_{false}; + + /** reads `len` 16-bit little-endian integers from the given i2c register */ + i2c::ErrorCode read_le_int16_(uint8_t reg, int16_t *value, uint8_t len); +}; + +} // namespace bmi160 +} // namespace esphome diff --git a/esphome/components/bmi160/sensor.py b/esphome/components/bmi160/sensor.py new file mode 100644 index 0000000000..baf185f95a --- /dev/null +++ b/esphome/components/bmi160/sensor.py @@ -0,0 +1,102 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c, sensor +from esphome.const import ( + CONF_ID, + CONF_TEMPERATURE, + CONF_ACCELERATION_X, + CONF_ACCELERATION_Y, + CONF_ACCELERATION_Z, + CONF_GYROSCOPE_X, + CONF_GYROSCOPE_Y, + CONF_GYROSCOPE_Z, + DEVICE_CLASS_TEMPERATURE, + STATE_CLASS_MEASUREMENT, + UNIT_METER_PER_SECOND_SQUARED, + ICON_ACCELERATION_X, + ICON_ACCELERATION_Y, + ICON_ACCELERATION_Z, + ICON_GYROSCOPE_X, + ICON_GYROSCOPE_Y, + ICON_GYROSCOPE_Z, + UNIT_DEGREE_PER_SECOND, + UNIT_CELSIUS, +) + +DEPENDENCIES = ["i2c"] + +bmi160_ns = cg.esphome_ns.namespace("bmi160") +BMI160Component = bmi160_ns.class_( + "BMI160Component", cg.PollingComponent, i2c.I2CDevice +) + +accel_schema = { + "unit_of_measurement": UNIT_METER_PER_SECOND_SQUARED, + "accuracy_decimals": 2, + "state_class": STATE_CLASS_MEASUREMENT, +} +gyro_schema = { + "unit_of_measurement": UNIT_DEGREE_PER_SECOND, + "accuracy_decimals": 2, + "state_class": STATE_CLASS_MEASUREMENT, +} + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(BMI160Component), + cv.Optional(CONF_ACCELERATION_X): sensor.sensor_schema( + icon=ICON_ACCELERATION_X, + **accel_schema, + ), + cv.Optional(CONF_ACCELERATION_Y): sensor.sensor_schema( + icon=ICON_ACCELERATION_Y, + **accel_schema, + ), + cv.Optional(CONF_ACCELERATION_Z): sensor.sensor_schema( + icon=ICON_ACCELERATION_Z, + **accel_schema, + ), + cv.Optional(CONF_GYROSCOPE_X): sensor.sensor_schema( + icon=ICON_GYROSCOPE_X, + **gyro_schema, + ), + cv.Optional(CONF_GYROSCOPE_Y): sensor.sensor_schema( + icon=ICON_GYROSCOPE_Y, + **gyro_schema, + ), + cv.Optional(CONF_GYROSCOPE_Z): sensor.sensor_schema( + icon=ICON_GYROSCOPE_Z, + **gyro_schema, + ), + cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=0, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x68)) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) + + for d in ["x", "y", "z"]: + accel_key = f"acceleration_{d}" + if accel_key in config: + sens = await sensor.new_sensor(config[accel_key]) + cg.add(getattr(var, f"set_accel_{d}_sensor")(sens)) + accel_key = f"gyroscope_{d}" + if accel_key in config: + sens = await sensor.new_sensor(config[accel_key]) + cg.add(getattr(var, f"set_gyro_{d}_sensor")(sens)) + + if CONF_TEMPERATURE in config: + sens = await sensor.new_sensor(config[CONF_TEMPERATURE]) + cg.add(var.set_temperature_sensor(sens)) diff --git a/esphome/const.py b/esphome/const.py index 0e4e880c19..bbc6e71885 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -298,6 +298,9 @@ CONF_GLYPHS = "glyphs" CONF_GPIO = "gpio" CONF_GREEN = "green" CONF_GROUP = "group" +CONF_GYROSCOPE_X = "gyroscope_x" +CONF_GYROSCOPE_Y = "gyroscope_y" +CONF_GYROSCOPE_Z = "gyroscope_z" CONF_HARDWARE_UART = "hardware_uart" CONF_HEAD = "head" CONF_HEARTBEAT = "heartbeat" @@ -867,6 +870,9 @@ ICON_FLOWER = "mdi:flower" ICON_GAS_CYLINDER = "mdi:gas-cylinder" ICON_GAUGE = "mdi:gauge" ICON_GRAIN = "mdi:grain" +ICON_GYROSCOPE_X = "mdi:axis-x-rotate-clockwise" +ICON_GYROSCOPE_Y = "mdi:axis-y-rotate-clockwise" +ICON_GYROSCOPE_Z = "mdi:axis-z-rotate-clockwise" ICON_HEATING_COIL = "mdi:heating-coil" ICON_KEY_PLUS = "mdi:key-plus" ICON_LIGHTBULB = "mdi:lightbulb" diff --git a/tests/test1.yaml b/tests/test1.yaml index 33782dbf53..fe983cf421 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -915,6 +915,23 @@ sensor: temperature: name: MPU6886 Temperature i2c_id: i2c_bus + - platform: bmi160 + address: 0x68 + acceleration_x: + name: BMI160 Accel X + acceleration_y: + name: BMI160 Accel Y + acceleration_z: + name: BMI160 Accel z + gyroscope_x: + name: BMI160 Gyro X + gyroscope_y: + name: BMI160 Gyro Y + gyroscope_z: + name: BMI160 Gyro z + temperature: + name: BMI160 Temperature + i2c_id: i2c_bus - platform: mmc5603 address: 0x30 field_strength_x: From 32b103eb1d67ea2275d61531ffc51b90f59870fa Mon Sep 17 00:00:00 2001 From: "Kevin P. Fleming" Date: Sun, 10 Sep 2023 15:42:42 -0400 Subject: [PATCH 094/111] Use black-pre-commit-mirror to speed up pre-commit runs. (#5372) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index cf86d354b7..0bbb2fee61 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,7 +2,7 @@ # See https://pre-commit.com for more information # See https://pre-commit.com/hooks.html for more hooks repos: - - repo: https://github.com/psf/black + - repo: https://github.com/psf/black-pre-commit-mirror rev: 23.7.0 hooks: - id: black From d2648657fb13e9bc576c1f8436ca7ab9a9d3748e Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Mon, 11 Sep 2023 12:20:06 +1000 Subject: [PATCH 095/111] Native SPI RGB LED component (#5288) * Add testing branch to workflow * Add workflow * Checkpoint * Align SPI data rates in c++ code with Python code. * Checkpoint * CI fixes * Update codeowners * Workflow cleanup * Rename to spi_rgb_led * Rename header file * Clang tidy * Disable spi after transfer. * Move enable() to where it belongs * Call spi_setup before enable * Clang tidy * Add test * Rename to spi_led_strip * Include 'defines.h' * Fix CODEOWNERS * Migrate data rate to new style setting. * Remove defines.h * Fix class name * Fix name in .py * And more more name tidy up. --------- Co-authored-by: Keith Burzinski --- CODEOWNERS | 1 + esphome/components/spi_led_strip/__init__.py | 2 + esphome/components/spi_led_strip/light.py | 27 ++++++ .../components/spi_led_strip/spi_led_strip.h | 91 +++++++++++++++++++ tests/test8.yaml | 6 ++ 5 files changed, 127 insertions(+) create mode 100644 esphome/components/spi_led_strip/__init__.py create mode 100644 esphome/components/spi_led_strip/light.py create mode 100644 esphome/components/spi_led_strip/spi_led_strip.h diff --git a/CODEOWNERS b/CODEOWNERS index 2658aebdc7..8e0911f2a9 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -272,6 +272,7 @@ esphome/components/sonoff_d1/* @anatoly-savchenkov esphome/components/speaker/* @jesserockz esphome/components/spi/* @esphome/core esphome/components/spi_device/* @clydebarrow +esphome/components/spi_led_strip/* @clydebarrow esphome/components/sprinkler/* @kbx81 esphome/components/sps30/* @martgras esphome/components/ssd1322_base/* @kbx81 diff --git a/esphome/components/spi_led_strip/__init__.py b/esphome/components/spi_led_strip/__init__.py new file mode 100644 index 0000000000..850a1f6e02 --- /dev/null +++ b/esphome/components/spi_led_strip/__init__.py @@ -0,0 +1,2 @@ +CODEOWNERS = ["@clydebarrow"] +DEPENDENCIES = ["spi"] diff --git a/esphome/components/spi_led_strip/light.py b/esphome/components/spi_led_strip/light.py new file mode 100644 index 0000000000..7420b0c929 --- /dev/null +++ b/esphome/components/spi_led_strip/light.py @@ -0,0 +1,27 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import light +from esphome.components import spi +from esphome.const import CONF_OUTPUT_ID, CONF_NUM_LEDS, CONF_DATA_RATE + +spi_led_strip_ns = cg.esphome_ns.namespace("spi_led_strip") +SpiLedStrip = spi_led_strip_ns.class_( + "SpiLedStrip", light.AddressableLight, spi.SPIDevice +) + +CONFIG_SCHEMA = light.ADDRESSABLE_LIGHT_SCHEMA.extend( + { + cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(SpiLedStrip), + cv.Optional(CONF_NUM_LEDS, default=1): cv.positive_not_null_int, + cv.Optional(CONF_DATA_RATE, default="1MHz"): spi.SPI_DATA_RATE_SCHEMA, + } +).extend(spi.spi_device_schema(False)) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_OUTPUT_ID]) + cg.add(var.set_data_rate(spi.SPI_DATA_RATE_OPTIONS[config[CONF_DATA_RATE]])) + cg.add(var.set_num_leds(config[CONF_NUM_LEDS])) + await light.register_light(var, config) + await spi.register_spi_device(var, config) + await cg.register_component(var, config) diff --git a/esphome/components/spi_led_strip/spi_led_strip.h b/esphome/components/spi_led_strip/spi_led_strip.h new file mode 100644 index 0000000000..0d8c1c1e1c --- /dev/null +++ b/esphome/components/spi_led_strip/spi_led_strip.h @@ -0,0 +1,91 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/log.h" +#include "esphome/components/light/addressable_light.h" +#include "esphome/components/spi/spi.h" + +namespace esphome { +namespace spi_led_strip { + +static const char *const TAG = "spi_led_strip"; +class SpiLedStrip : public light::AddressableLight, + public spi::SPIDevice { + public: + void setup() { this->spi_setup(); } + + int32_t size() const override { return this->num_leds_; } + + light::LightTraits get_traits() override { + auto traits = light::LightTraits(); + traits.set_supported_color_modes({light::ColorMode::RGB}); + return traits; + } + void set_num_leds(uint16_t num_leds) { + this->num_leds_ = num_leds; + ExternalRAMAllocator allocator(ExternalRAMAllocator::ALLOW_FAILURE); + this->buffer_size_ = num_leds * 4 + 8; + this->buf_ = allocator.allocate(this->buffer_size_); + if (this->buf_ == nullptr) { + esph_log_e(TAG, "Failed to allocate buffer of size %u", this->buffer_size_); + this->mark_failed(); + return; + } + + this->effect_data_ = allocator.allocate(num_leds); + if (this->effect_data_ == nullptr) { + esph_log_e(TAG, "Failed to allocate effect data of size %u", num_leds); + this->mark_failed(); + return; + } + memset(this->buf_, 0xFF, this->buffer_size_); + memset(this->buf_, 0, 4); + } + + void dump_config() { + esph_log_config(TAG, "SPI LED Strip:"); + esph_log_config(TAG, " LEDs: %d", this->num_leds_); + if (this->data_rate_ >= spi::DATA_RATE_1MHZ) + esph_log_config(TAG, " Data rate: %uMHz", (unsigned) (this->data_rate_ / 1000000)); + else + esph_log_config(TAG, " Data rate: %ukHz", (unsigned) (this->data_rate_ / 1000)); + } + + void write_state(light::LightState *state) override { + if (this->is_failed()) + return; + if (ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE) { + char strbuf[49]; + size_t len = std::min(this->buffer_size_, (size_t) (sizeof(strbuf) - 1) / 3); + memset(strbuf, 0, sizeof(strbuf)); + for (size_t i = 0; i != len; i++) { + sprintf(strbuf + i * 3, "%02X ", this->buf_[i]); + } + esph_log_v(TAG, "write_state: buf = %s", strbuf); + } + this->enable(); + this->write_array(this->buf_, this->buffer_size_); + this->disable(); + } + + void clear_effect_data() override { + for (int i = 0; i < this->size(); i++) + this->effect_data_[i] = 0; + } + + protected: + light::ESPColorView get_view_internal(int32_t index) const override { + size_t pos = index * 4 + 5; + return {this->buf_ + pos + 2, this->buf_ + pos + 1, this->buf_ + pos + 0, nullptr, + this->effect_data_ + index, &this->correction_}; + } + + size_t buffer_size_{}; + uint8_t *effect_data_{nullptr}; + uint8_t *buf_{nullptr}; + uint16_t num_leds_; +}; + +} // namespace spi_led_strip +} // namespace esphome diff --git a/tests/test8.yaml b/tests/test8.yaml index 498da94483..01d12ea330 100644 --- a/tests/test8.yaml +++ b/tests/test8.yaml @@ -29,6 +29,12 @@ light: name: neopixel-enable internal: false restore_mode: ALWAYS_OFF + - platform: spi_led_strip + num_leds: 4 + color_correct: [80%, 60%, 100%] + id: rgb_led + name: "RGB LED" + data_rate: 8MHz spi: id: spi_id_1 From b107948c476e0d587b41399fdc5a7bea90753c68 Mon Sep 17 00:00:00 2001 From: Lubos Horacek Date: Mon, 11 Sep 2023 21:13:24 +0200 Subject: [PATCH 096/111] Wireguard component (#4256) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Co-authored-by: Simone Rossetto Co-authored-by: Thomas Bernard --- .github/workflows/ci.yml | 2 +- .gitignore | 6 + CODEOWNERS | 1 + esphome/components/wireguard/__init__.py | 113 +++++++ esphome/components/wireguard/binary_sensor.py | 28 ++ esphome/components/wireguard/sensor.py | 30 ++ esphome/components/wireguard/wireguard.cpp | 296 ++++++++++++++++++ esphome/components/wireguard/wireguard.h | 122 ++++++++ platformio.ini | 2 + tests/README.md | 1 + tests/test10.yaml | 48 +++ 11 files changed, 648 insertions(+), 1 deletion(-) create mode 100644 esphome/components/wireguard/__init__.py create mode 100644 esphome/components/wireguard/binary_sensor.py create mode 100644 esphome/components/wireguard/sensor.py create mode 100644 esphome/components/wireguard/wireguard.cpp create mode 100644 esphome/components/wireguard/wireguard.h create mode 100644 tests/test10.yaml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4214bc2c5d..79ebe8782e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -232,7 +232,7 @@ jobs: fail-fast: false max-parallel: 2 matrix: - file: [1, 2, 3, 3.1, 4, 5, 6, 7, 8] + file: [1, 2, 3, 3.1, 4, 5, 6, 7, 8, 10] steps: - name: Check out code from GitHub uses: actions/checkout@v4 diff --git a/.gitignore b/.gitignore index d180b58259..0c9a878400 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,12 @@ __pycache__/ # Intellij Idea .idea +# Eclipse +.project +.cproject +.pydevproject +.settings/ + # Vim *.swp diff --git a/CODEOWNERS b/CODEOWNERS index 8e0911f2a9..22e46aa2f0 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -333,6 +333,7 @@ esphome/components/web_server_idf/* @dentra esphome/components/whirlpool/* @glmnet esphome/components/whynter/* @aeonsablaze esphome/components/wiegand/* @ssieb +esphome/components/wireguard/* @droscy @lhoracek @thomas0bernard esphome/components/wl_134/* @hobbypunk90 esphome/components/x9c/* @EtienneMD esphome/components/xiaomi_lywsd03mmc/* @ahpohl diff --git a/esphome/components/wireguard/__init__.py b/esphome/components/wireguard/__init__.py new file mode 100644 index 0000000000..717fe50d2c --- /dev/null +++ b/esphome/components/wireguard/__init__.py @@ -0,0 +1,113 @@ +import re +import ipaddress +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.const import ( + CONF_ID, + CONF_TIME_ID, + CONF_ADDRESS, + CONF_REBOOT_TIMEOUT, +) +from esphome.components import time + +CONF_NETMASK = "netmask" +CONF_PRIVATE_KEY = "private_key" +CONF_PEER_ENDPOINT = "peer_endpoint" +CONF_PEER_PUBLIC_KEY = "peer_public_key" +CONF_PEER_PORT = "peer_port" +CONF_PEER_PRESHARED_KEY = "peer_preshared_key" +CONF_PEER_ALLOWED_IPS = "peer_allowed_ips" +CONF_PEER_PERSISTENT_KEEPALIVE = "peer_persistent_keepalive" +CONF_REQUIRE_CONNECTION_TO_PROCEED = "require_connection_to_proceed" + +DEPENDENCIES = ["time", "esp32"] +CODEOWNERS = ["@lhoracek", "@droscy", "@thomas0bernard"] + +# The key validation regex has been described by Jason Donenfeld himself +# url: https://lists.zx2c4.com/pipermail/wireguard/2020-December/006222.html +_WG_KEY_REGEX = re.compile(r"^[A-Za-z0-9+/]{42}[AEIMQUYcgkosw480]=$") + +wireguard_ns = cg.esphome_ns.namespace("wireguard") +Wireguard = wireguard_ns.class_("Wireguard", cg.Component, cg.PollingComponent) + + +def _wireguard_key(value): + if _WG_KEY_REGEX.match(cv.string(value)) is not None: + return value + raise cv.Invalid(f"Invalid WireGuard key: {value}") + + +def _cidr_network(value): + try: + ipaddress.ip_network(value, strict=False) + except ValueError as err: + raise cv.Invalid(f"Invalid network in CIDR notation: {err}") + return value + + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(Wireguard), + cv.GenerateID(CONF_TIME_ID): cv.use_id(time.RealTimeClock), + cv.Required(CONF_ADDRESS): cv.ipv4, + cv.Optional(CONF_NETMASK, default="255.255.255.255"): cv.ipv4, + cv.Required(CONF_PRIVATE_KEY): _wireguard_key, + cv.Required(CONF_PEER_ENDPOINT): cv.string, + cv.Required(CONF_PEER_PUBLIC_KEY): _wireguard_key, + cv.Optional(CONF_PEER_PORT, default=51820): cv.port, + cv.Optional(CONF_PEER_PRESHARED_KEY): _wireguard_key, + cv.Optional(CONF_PEER_ALLOWED_IPS, default=["0.0.0.0/0"]): cv.ensure_list( + _cidr_network + ), + cv.Optional(CONF_PEER_PERSISTENT_KEEPALIVE, default=0): cv.Any( + cv.positive_time_period_seconds, + cv.uint16_t, + ), + cv.Optional( + CONF_REBOOT_TIMEOUT, default="15min" + ): cv.positive_time_period_milliseconds, + cv.Optional(CONF_REQUIRE_CONNECTION_TO_PROCEED, default=False): cv.boolean, + } +).extend(cv.polling_component_schema("10s")) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + + cg.add(var.set_address(str(config[CONF_ADDRESS]))) + cg.add(var.set_netmask(str(config[CONF_NETMASK]))) + cg.add(var.set_private_key(config[CONF_PRIVATE_KEY])) + cg.add(var.set_peer_endpoint(config[CONF_PEER_ENDPOINT])) + cg.add(var.set_peer_public_key(config[CONF_PEER_PUBLIC_KEY])) + cg.add(var.set_peer_port(config[CONF_PEER_PORT])) + cg.add(var.set_keepalive(config[CONF_PEER_PERSISTENT_KEEPALIVE])) + cg.add(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT])) + + if CONF_PEER_PRESHARED_KEY in config: + cg.add(var.set_preshared_key(config[CONF_PEER_PRESHARED_KEY])) + + allowed_ips = list( + ipaddress.collapse_addresses( + [ + ipaddress.ip_network(ip, strict=False) + for ip in config[CONF_PEER_ALLOWED_IPS] + ] + ) + ) + + for ip in allowed_ips: + cg.add(var.add_allowed_ip(str(ip.network_address), str(ip.netmask))) + + cg.add(var.set_srctime(await cg.get_variable(config[CONF_TIME_ID]))) + + if config[CONF_REQUIRE_CONNECTION_TO_PROCEED]: + cg.add(var.disable_auto_proceed()) + + # This flag is added here because the esp_wireguard library statically + # set the size of its allowed_ips list at compile time using this value; + # the '+1' modifier is relative to the device's own address that will + # be automatically added to the provided list. + cg.add_build_flag(f"-DCONFIG_WIREGUARD_MAX_SRC_IPS={len(allowed_ips) + 1}") + cg.add_library("droscy/esp_wireguard", "0.3.2") + + await cg.register_component(var, config) diff --git a/esphome/components/wireguard/binary_sensor.py b/esphome/components/wireguard/binary_sensor.py new file mode 100644 index 0000000000..14ff2b0159 --- /dev/null +++ b/esphome/components/wireguard/binary_sensor.py @@ -0,0 +1,28 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import binary_sensor +from esphome.const import ( + CONF_STATUS, + DEVICE_CLASS_CONNECTIVITY, +) + +from . import Wireguard + +CONF_WIREGUARD_ID = "wireguard_id" + +DEPENDENCIES = ["wireguard"] + +CONFIG_SCHEMA = { + cv.GenerateID(CONF_WIREGUARD_ID): cv.use_id(Wireguard), + cv.Optional(CONF_STATUS): binary_sensor.binary_sensor_schema( + device_class=DEVICE_CLASS_CONNECTIVITY, + ), +} + + +async def to_code(config): + parent = await cg.get_variable(config[CONF_WIREGUARD_ID]) + + if status_config := config.get(CONF_STATUS): + sens = await binary_sensor.new_binary_sensor(status_config) + cg.add(parent.set_status_sensor(sens)) diff --git a/esphome/components/wireguard/sensor.py b/esphome/components/wireguard/sensor.py new file mode 100644 index 0000000000..78cb619701 --- /dev/null +++ b/esphome/components/wireguard/sensor.py @@ -0,0 +1,30 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor +from esphome.const import ( + DEVICE_CLASS_TIMESTAMP, + ENTITY_CATEGORY_DIAGNOSTIC, +) + +from . import Wireguard + +CONF_WIREGUARD_ID = "wireguard_id" +CONF_LATEST_HANDSHAKE = "latest_handshake" + +DEPENDENCIES = ["wireguard"] + +CONFIG_SCHEMA = { + cv.GenerateID(CONF_WIREGUARD_ID): cv.use_id(Wireguard), + cv.Optional(CONF_LATEST_HANDSHAKE): sensor.sensor_schema( + device_class=DEVICE_CLASS_TIMESTAMP, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), +} + + +async def to_code(config): + parent = await cg.get_variable(config[CONF_WIREGUARD_ID]) + + if latest_handshake_config := config.get(CONF_LATEST_HANDSHAKE): + sens = await sensor.new_sensor(latest_handshake_config) + cg.add(parent.set_handshake_sensor(sens)) diff --git a/esphome/components/wireguard/wireguard.cpp b/esphome/components/wireguard/wireguard.cpp new file mode 100644 index 0000000000..1b361cc1cc --- /dev/null +++ b/esphome/components/wireguard/wireguard.cpp @@ -0,0 +1,296 @@ +#include "wireguard.h" + +#ifdef USE_ESP32 + +#include +#include + +#include "esphome/core/application.h" +#include "esphome/core/log.h" +#include "esphome/core/time.h" +#include "esphome/components/network/util.h" + +#include + +#include + +// includes for resume/suspend wdt +#if defined(USE_ESP_IDF) +#include +#if ESP_IDF_VERSION_MAJOR >= 5 +#include +#endif +#elif defined(USE_ARDUINO) +#include +#endif + +namespace esphome { +namespace wireguard { + +static const char *const TAG = "wireguard"; + +static const char *const LOGMSG_PEER_STATUS = "WireGuard remote peer is %s (latest handshake %s)"; +static const char *const LOGMSG_ONLINE = "online"; +static const char *const LOGMSG_OFFLINE = "offline"; + +void Wireguard::setup() { + ESP_LOGD(TAG, "initializing WireGuard..."); + + this->wg_config_.address = this->address_.c_str(); + this->wg_config_.private_key = this->private_key_.c_str(); + this->wg_config_.endpoint = this->peer_endpoint_.c_str(); + this->wg_config_.public_key = this->peer_public_key_.c_str(); + this->wg_config_.port = this->peer_port_; + this->wg_config_.netmask = this->netmask_.c_str(); + this->wg_config_.persistent_keepalive = this->keepalive_; + + if (this->preshared_key_.length() > 0) + this->wg_config_.preshared_key = this->preshared_key_.c_str(); + + this->wg_initialized_ = esp_wireguard_init(&(this->wg_config_), &(this->wg_ctx_)); + + if (this->wg_initialized_ == ESP_OK) { + ESP_LOGI(TAG, "WireGuard initialized"); + this->wg_peer_offline_time_ = millis(); + this->srctime_->add_on_time_sync_callback(std::bind(&Wireguard::start_connection_, this)); + this->defer(std::bind(&Wireguard::start_connection_, this)); // defer to avoid blocking setup + } else { + ESP_LOGE(TAG, "cannot initialize WireGuard, error code %d", this->wg_initialized_); + this->mark_failed(); + } +} + +void Wireguard::loop() { + if ((this->wg_initialized_ == ESP_OK) && (this->wg_connected_ == ESP_OK) && (!network::is_connected())) { + ESP_LOGV(TAG, "local network connection has been lost, stopping WireGuard..."); + this->stop_connection_(); + } +} + +void Wireguard::update() { + bool peer_up = this->is_peer_up(); + time_t lhs = this->get_latest_handshake(); + bool lhs_updated = (lhs > this->latest_saved_handshake_); + + ESP_LOGV(TAG, "handshake: latest=%.0f, saved=%.0f, updated=%d", (double) lhs, (double) this->latest_saved_handshake_, + (int) lhs_updated); + + if (lhs_updated) { + this->latest_saved_handshake_ = lhs; + } + + std::string latest_handshake = + (this->latest_saved_handshake_ > 0) + ? ESPTime::from_epoch_local(this->latest_saved_handshake_).strftime("%Y-%m-%d %H:%M:%S %Z") + : "timestamp not available"; + + if (peer_up) { + if (this->wg_peer_offline_time_ != 0) { + ESP_LOGI(TAG, LOGMSG_PEER_STATUS, LOGMSG_ONLINE, latest_handshake.c_str()); + this->wg_peer_offline_time_ = 0; + } else { + ESP_LOGD(TAG, LOGMSG_PEER_STATUS, LOGMSG_ONLINE, latest_handshake.c_str()); + } + } else { + if (this->wg_peer_offline_time_ == 0) { + ESP_LOGW(TAG, LOGMSG_PEER_STATUS, LOGMSG_OFFLINE, latest_handshake.c_str()); + this->wg_peer_offline_time_ = millis(); + } else { + ESP_LOGD(TAG, LOGMSG_PEER_STATUS, LOGMSG_OFFLINE, latest_handshake.c_str()); + this->start_connection_(); + } + + // check reboot timeout every time the peer is down + if (this->reboot_timeout_ > 0) { + if (millis() - this->wg_peer_offline_time_ > this->reboot_timeout_) { + ESP_LOGE(TAG, "WireGuard remote peer is unreachable, rebooting..."); + App.reboot(); + } + } + } + +#ifdef USE_BINARY_SENSOR + if (this->status_sensor_ != nullptr) { + this->status_sensor_->publish_state(peer_up); + } +#endif + +#ifdef USE_SENSOR + if (this->handshake_sensor_ != nullptr && lhs_updated) { + this->handshake_sensor_->publish_state((double) this->latest_saved_handshake_); + } +#endif +} + +void Wireguard::dump_config() { + ESP_LOGCONFIG(TAG, "WireGuard:"); + ESP_LOGCONFIG(TAG, " Address: %s", this->address_.c_str()); + ESP_LOGCONFIG(TAG, " Netmask: %s", this->netmask_.c_str()); + ESP_LOGCONFIG(TAG, " Private Key: " LOG_SECRET("%s"), mask_key(this->private_key_).c_str()); + ESP_LOGCONFIG(TAG, " Peer Endpoint: " LOG_SECRET("%s"), this->peer_endpoint_.c_str()); + ESP_LOGCONFIG(TAG, " Peer Port: " LOG_SECRET("%d"), this->peer_port_); + ESP_LOGCONFIG(TAG, " Peer Public Key: " LOG_SECRET("%s"), this->peer_public_key_.c_str()); + ESP_LOGCONFIG(TAG, " Peer Pre-shared Key: " LOG_SECRET("%s"), + (this->preshared_key_.length() > 0 ? mask_key(this->preshared_key_).c_str() : "NOT IN USE")); + ESP_LOGCONFIG(TAG, " Peer Allowed IPs:"); + for (auto &allowed_ip : this->allowed_ips_) { + ESP_LOGCONFIG(TAG, " - %s/%s", std::get<0>(allowed_ip).c_str(), std::get<1>(allowed_ip).c_str()); + } + ESP_LOGCONFIG(TAG, " Peer Persistent Keepalive: %d%s", this->keepalive_, + (this->keepalive_ > 0 ? "s" : " (DISABLED)")); + ESP_LOGCONFIG(TAG, " Reboot Timeout: %d%s", (this->reboot_timeout_ / 1000), + (this->reboot_timeout_ != 0 ? "s" : " (DISABLED)")); + // be careful: if proceed_allowed_ is true, require connection is false + ESP_LOGCONFIG(TAG, " Require Connection to Proceed: %s", (this->proceed_allowed_ ? "NO" : "YES")); + LOG_UPDATE_INTERVAL(this); +} + +void Wireguard::on_shutdown() { this->stop_connection_(); } + +bool Wireguard::can_proceed() { return (this->proceed_allowed_ || this->is_peer_up()); } + +bool Wireguard::is_peer_up() const { + return (this->wg_initialized_ == ESP_OK) && (this->wg_connected_ == ESP_OK) && + (esp_wireguardif_peer_is_up(&(this->wg_ctx_)) == ESP_OK); +} + +time_t Wireguard::get_latest_handshake() const { + time_t result; + if (esp_wireguard_latest_handshake(&(this->wg_ctx_), &result) != ESP_OK) { + result = 0; + } + return result; +} + +void Wireguard::set_address(const std::string &address) { this->address_ = address; } +void Wireguard::set_netmask(const std::string &netmask) { this->netmask_ = netmask; } +void Wireguard::set_private_key(const std::string &key) { this->private_key_ = key; } +void Wireguard::set_peer_endpoint(const std::string &endpoint) { this->peer_endpoint_ = endpoint; } +void Wireguard::set_peer_public_key(const std::string &key) { this->peer_public_key_ = key; } +void Wireguard::set_peer_port(const uint16_t port) { this->peer_port_ = port; } +void Wireguard::set_preshared_key(const std::string &key) { this->preshared_key_ = key; } + +void Wireguard::add_allowed_ip(const std::string &ip, const std::string &netmask) { + this->allowed_ips_.emplace_back(ip, netmask); +} + +void Wireguard::set_keepalive(const uint16_t seconds) { this->keepalive_ = seconds; } +void Wireguard::set_reboot_timeout(const uint32_t seconds) { this->reboot_timeout_ = seconds; } +void Wireguard::set_srctime(time::RealTimeClock *srctime) { this->srctime_ = srctime; } + +#ifdef USE_BINARY_SENSOR +void Wireguard::set_status_sensor(binary_sensor::BinarySensor *sensor) { this->status_sensor_ = sensor; } +#endif + +#ifdef USE_SENSOR +void Wireguard::set_handshake_sensor(sensor::Sensor *sensor) { this->handshake_sensor_ = sensor; } +#endif + +void Wireguard::disable_auto_proceed() { this->proceed_allowed_ = false; } + +void Wireguard::start_connection_() { + if (this->wg_initialized_ != ESP_OK) { + ESP_LOGE(TAG, "cannot start WireGuard, initialization in error with code %d", this->wg_initialized_); + return; + } + + if (!network::is_connected()) { + ESP_LOGD(TAG, "WireGuard is waiting for local network connection to be available"); + return; + } + + if (!this->srctime_->now().is_valid()) { + ESP_LOGD(TAG, "WireGuard is waiting for system time to be synchronized"); + return; + } + + if (this->wg_connected_ == ESP_OK) { + ESP_LOGV(TAG, "WireGuard connection already started"); + return; + } + + ESP_LOGD(TAG, "starting WireGuard connection..."); + + /* + * The function esp_wireguard_connect() contains a DNS resolution + * that could trigger the watchdog, so before it we suspend (or + * increase the time, it depends on the platform) the wdt and + * then we resume the normal timeout. + */ + suspend_wdt(); + ESP_LOGV(TAG, "executing esp_wireguard_connect"); + this->wg_connected_ = esp_wireguard_connect(&(this->wg_ctx_)); + resume_wdt(); + + if (this->wg_connected_ == ESP_OK) { + ESP_LOGI(TAG, "WireGuard connection started"); + } else { + ESP_LOGW(TAG, "cannot start WireGuard connection, error code %d", this->wg_connected_); + return; + } + + ESP_LOGD(TAG, "configuring WireGuard allowed IPs list..."); + bool allowed_ips_ok = true; + for (std::tuple ip : this->allowed_ips_) { + allowed_ips_ok &= + (esp_wireguard_add_allowed_ip(&(this->wg_ctx_), std::get<0>(ip).c_str(), std::get<1>(ip).c_str()) == ESP_OK); + } + + if (allowed_ips_ok) { + ESP_LOGD(TAG, "allowed IPs list configured correctly"); + } else { + ESP_LOGE(TAG, "cannot configure WireGuard allowed IPs list, aborting..."); + this->stop_connection_(); + this->mark_failed(); + } +} + +void Wireguard::stop_connection_() { + if (this->wg_initialized_ == ESP_OK && this->wg_connected_ == ESP_OK) { + ESP_LOGD(TAG, "stopping WireGuard connection..."); + esp_wireguard_disconnect(&(this->wg_ctx_)); + this->wg_connected_ = ESP_FAIL; + } +} + +void suspend_wdt() { +#if defined(USE_ESP_IDF) +#if ESP_IDF_VERSION_MAJOR >= 5 + ESP_LOGV(TAG, "temporarily increasing wdt timeout to 15000 ms"); + esp_task_wdt_config_t wdtc; + wdtc.timeout_ms = 15000; + wdtc.idle_core_mask = 0; + wdtc.trigger_panic = false; + esp_task_wdt_reconfigure(&wdtc); +#else + ESP_LOGV(TAG, "temporarily increasing wdt timeout to 15 seconds"); + esp_task_wdt_init(15, false); +#endif +#elif defined(USE_ARDUINO) + ESP_LOGV(TAG, "temporarily disabling the wdt"); + disableLoopWDT(); +#endif +} + +void resume_wdt() { +#if defined(USE_ESP_IDF) +#if ESP_IDF_VERSION_MAJOR >= 5 + wdtc.timeout_ms = CONFIG_ESP_TASK_WDT_TIMEOUT_S * 1000; + esp_task_wdt_reconfigure(&wdtc); + ESP_LOGV(TAG, "wdt resumed with %d ms timeout", wdtc.timeout_ms); +#else + esp_task_wdt_init(CONFIG_ESP_TASK_WDT_TIMEOUT_S, false); + ESP_LOGV(TAG, "wdt resumed with %d seconds timeout", CONFIG_ESP_TASK_WDT_TIMEOUT_S); +#endif +#elif defined(USE_ARDUINO) + enableLoopWDT(); + ESP_LOGV(TAG, "wdt resumed"); +#endif +} + +std::string mask_key(const std::string &key) { return (key.substr(0, 5) + "[...]="); } + +} // namespace wireguard +} // namespace esphome + +#endif // USE_ESP32 diff --git a/esphome/components/wireguard/wireguard.h b/esphome/components/wireguard/wireguard.h new file mode 100644 index 0000000000..cfc5fa1a27 --- /dev/null +++ b/esphome/components/wireguard/wireguard.h @@ -0,0 +1,122 @@ +#pragma once + +#ifdef USE_ESP32 + +#include +#include +#include + +#include "esphome/core/component.h" +#include "esphome/components/time/real_time_clock.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 + +namespace esphome { +namespace wireguard { + +class Wireguard : public PollingComponent { + public: + void setup() override; + void loop() override; + void update() override; + void dump_config() override; + void on_shutdown() override; + bool can_proceed() override; + + float get_setup_priority() const override { return esphome::setup_priority::BEFORE_CONNECTION; } + + void set_address(const std::string &address); + void set_netmask(const std::string &netmask); + void set_private_key(const std::string &key); + void set_peer_endpoint(const std::string &endpoint); + void set_peer_public_key(const std::string &key); + void set_peer_port(uint16_t port); + void set_preshared_key(const std::string &key); + + void add_allowed_ip(const std::string &ip, const std::string &netmask); + + void set_keepalive(uint16_t seconds); + void set_reboot_timeout(uint32_t seconds); + void set_srctime(time::RealTimeClock *srctime); + +#ifdef USE_BINARY_SENSOR + void set_status_sensor(binary_sensor::BinarySensor *sensor); +#endif + +#ifdef USE_SENSOR + void set_handshake_sensor(sensor::Sensor *sensor); +#endif + + /// Block the setup step until peer is connected. + void disable_auto_proceed(); + + bool is_peer_up() const; + time_t get_latest_handshake() const; + + protected: + std::string address_; + std::string netmask_; + std::string private_key_; + std::string peer_endpoint_; + std::string peer_public_key_; + std::string preshared_key_; + + std::vector> allowed_ips_; + + uint16_t peer_port_; + uint16_t keepalive_; + uint32_t reboot_timeout_; + + time::RealTimeClock *srctime_; + +#ifdef USE_BINARY_SENSOR + binary_sensor::BinarySensor *status_sensor_ = nullptr; +#endif + +#ifdef USE_SENSOR + sensor::Sensor *handshake_sensor_ = nullptr; +#endif + + /// Set to false to block the setup step until peer is connected. + bool proceed_allowed_ = true; + + wireguard_config_t wg_config_ = ESP_WIREGUARD_CONFIG_DEFAULT(); + wireguard_ctx_t wg_ctx_ = ESP_WIREGUARD_CONTEXT_DEFAULT(); + + esp_err_t wg_initialized_ = ESP_FAIL; + esp_err_t wg_connected_ = ESP_FAIL; + + /// The last time the remote peer become offline. + uint32_t wg_peer_offline_time_ = 0; + + /** \brief The latest saved handshake. + * + * This is used to save (and log) the latest completed handshake even + * after a full refresh of the wireguard keys (for example after a + * stop/start connection cycle). + */ + time_t latest_saved_handshake_ = 0; + + void start_connection_(); + void stop_connection_(); +}; + +// These are used for possibly long DNS resolution to temporarily suspend the watchdog +void suspend_wdt(); +void resume_wdt(); + +/// Strip most part of the key only for secure printing +std::string mask_key(const std::string &key); + +} // namespace wireguard +} // namespace esphome + +#endif // USE_ESP32 diff --git a/platformio.ini b/platformio.ini index ab9584d9b8..aea164353d 100644 --- a/platformio.ini +++ b/platformio.ini @@ -123,6 +123,7 @@ lib_deps = DNSServer ; captive_portal (Arduino built-in) esphome/ESP32-audioI2S@2.0.7 ; i2s_audio crankyoldgit/IRremoteESP8266@2.7.12 ; heatpumpir + droscy/esp_wireguard@0.3.2 ; wireguard build_flags = ${common:arduino.build_flags} -DUSE_ESP32 @@ -141,6 +142,7 @@ framework = espidf lib_deps = ${common:idf.lib_deps} espressif/esp32-camera@1.0.0 ; esp32_camera + droscy/esp_wireguard@0.3.2 ; wireguard build_flags = ${common:idf.build_flags} -Wno-nonnull-compare diff --git a/tests/README.md b/tests/README.md index 6d83fc6886..5b312d00de 100644 --- a/tests/README.md +++ b/tests/README.md @@ -27,3 +27,4 @@ Current test_.yaml file contents. | test6.yaml | RP2040 | wifi | N/A | test7.yaml | ESP32-C3 | wifi | N/A | test8.yaml | ESP32-S3 | wifi | None +| test10.yaml | ESP32 | wifi | None diff --git a/tests/test10.yaml b/tests/test10.yaml new file mode 100644 index 0000000000..0470e37e6c --- /dev/null +++ b/tests/test10.yaml @@ -0,0 +1,48 @@ +--- +esphome: + name: test10 + build_path: build/test10 + +esp32: + board: esp32doit-devkit-v1 + framework: + type: arduino + +wifi: + ssid: "MySSID1" + password: "password1" + reboot_timeout: 3min + power_save_mode: high + +logger: + level: VERBOSE + +api: + reboot_timeout: 10min + +time: + - platform: sntp + +wireguard: + id: vpn + address: 172.16.34.100 + netmask: 255.255.255.0 + # NEVER use the following keys for your vpn, they are now public! + private_key: wPBMxtNYH3mChicrbpsRpZIasIdPq3yZuthn23FbGG8= + peer_public_key: Hs2JfikvYU03/Kv3YoAs1hrUIPPTEkpsZKSPUljE9yc= + peer_preshared_key: 20fjM5GRnSolGPC5SRj9ljgIUyQfruv0B0bvLl3Yt60= + peer_endpoint: wg.server.example + peer_persistent_keepalive: 25s + peer_allowed_ips: + - 172.16.34.0/24 + - 192.168.4.0/24 + +binary_sensor: + - platform: wireguard + status: + name: 'WireGuard Status' + +sensor: + - platform: wireguard + latest_handshake: + name: 'WireGuard Latest Handshake' From 892d2ce34fb25332c2013ca3349f2558935734dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Mon, 11 Sep 2023 21:15:24 +0200 Subject: [PATCH 097/111] Bump LibreTiny version to 1.4.0 (#5375) --- esphome/components/libretiny/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/libretiny/__init__.py b/esphome/components/libretiny/__init__.py index ac294d3f65..3c1c0ac3f0 100644 --- a/esphome/components/libretiny/__init__.py +++ b/esphome/components/libretiny/__init__.py @@ -168,9 +168,9 @@ def _notify_old_style(config): # NOTE: Keep this in mind when updating the recommended version: # * For all constants below, update platformio.ini (in this repo) ARDUINO_VERSIONS = { - "dev": (cv.Version(0, 0, 0), "https://github.com/kuba2k2/libretiny.git"), + "dev": (cv.Version(0, 0, 0), "https://github.com/libretiny-eu/libretiny.git"), "latest": (cv.Version(0, 0, 0), None), - "recommended": (cv.Version(1, 3, 0), None), + "recommended": (cv.Version(1, 4, 0), None), } From deb34c947314b27c9db4f49cd8771fa5c76eaf00 Mon Sep 17 00:00:00 2001 From: "Kevin P. Fleming" Date: Mon, 11 Sep 2023 16:02:07 -0400 Subject: [PATCH 098/111] time: Make std::string version of strftime() avoid runaway memory allocations (#5348) --- esphome/core/time.cpp | 5 +++++ esphome/core/time.h | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/esphome/core/time.cpp b/esphome/core/time.cpp index bc5bfa173e..751b2a2703 100644 --- a/esphome/core/time.cpp +++ b/esphome/core/time.cpp @@ -49,6 +49,11 @@ std::string ESPTime::strftime(const std::string &format) { struct tm c_tm = this->to_c_tm(); size_t len = ::strftime(×tr[0], timestr.size(), format.c_str(), &c_tm); while (len == 0) { + if (timestr.size() >= 128) { + // strftime has failed for reasons unrelated to the size of the buffer + // so return a formatting error + return "ERROR"; + } timestr.resize(timestr.size() * 2); len = ::strftime(×tr[0], timestr.size(), format.c_str(), &c_tm); } diff --git a/esphome/core/time.h b/esphome/core/time.h index e16e449f0b..14c36311e0 100644 --- a/esphome/core/time.h +++ b/esphome/core/time.h @@ -45,6 +45,10 @@ struct ESPTime { * * @warning This method uses dynamically allocated strings which can cause heap fragmentation with some * microcontrollers. + * + * @warning This method can return "ERROR" when the underlying strftime() call fails, e.g. when the + * format string contains unsupported specifiers or when the format string doesn't produce any + * output. */ std::string strftime(const std::string &format); From d3196e0e34584bebc63b7a3a23d6403f4ceffdea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20S=C3=A1rk=C3=B6zi?= Date: Mon, 11 Sep 2023 22:12:56 +0200 Subject: [PATCH 099/111] Fix disabled wifi crash on boot (#5370) --- esphome/components/wifi/wifi_component.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index ff621291f0..2cb36fe8ea 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -40,6 +40,9 @@ void WiFiComponent::setup() { if (this->enable_on_boot_) { this->start(); } else { +#ifdef USE_ESP32 + esp_netif_init(); +#endif this->state_ = WIFI_COMPONENT_STATE_DISABLED; } } From c930c86cfa80ca67be2f8d98ec9b1f010a7dd90a Mon Sep 17 00:00:00 2001 From: Stijn Tintel Date: Mon, 11 Sep 2023 23:19:26 +0300 Subject: [PATCH 100/111] debug: add ESP32-C6 support (#5354) --- esphome/components/debug/debug_component.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/esphome/components/debug/debug_component.cpp b/esphome/components/debug/debug_component.cpp index 67b07237b7..fe66220ead 100644 --- a/esphome/components/debug/debug_component.cpp +++ b/esphome/components/debug/debug_component.cpp @@ -17,6 +17,8 @@ #include #elif defined(USE_ESP32_VARIANT_ESP32C3) #include +#elif defined(USE_ESP32_VARIANT_ESP32C6) +#include #elif defined(USE_ESP32_VARIANT_ESP32S2) #include #elif defined(USE_ESP32_VARIANT_ESP32S3) @@ -119,6 +121,8 @@ void DebugComponent::dump_config() { model = "ESP32"; #elif defined(USE_ESP32_VARIANT_ESP32C3) model = "ESP32-C3"; +#elif defined(USE_ESP32_VARIANT_ESP32C6) + model = "ESP32-C6"; #elif defined(USE_ESP32_VARIANT_ESP32S2) model = "ESP32-S2"; #elif defined(USE_ESP32_VARIANT_ESP32S3) @@ -202,9 +206,11 @@ void DebugComponent::dump_config() { case RTCWDT_SYS_RESET: reset_reason = "RTC Watch Dog Reset Digital Core"; break; +#if !defined(USE_ESP32_VARIANT_ESP32C6) case INTRUSION_RESET: reset_reason = "Intrusion Reset CPU"; break; +#endif #if defined(USE_ESP32_VARIANT_ESP32) case TGWDT_CPU_RESET: reset_reason = "Timer Group Reset CPU"; From 10eee47b6bd4f4032226aaf009b7339ec642d0fa Mon Sep 17 00:00:00 2001 From: Daniel Dunn Date: Mon, 11 Sep 2023 15:26:00 -0600 Subject: [PATCH 101/111] Make string globals persist-able using fixed size allocations (#5296) Co-authored-by: Daniel Dunn --- esphome/components/globals/__init__.py | 20 ++++++- .../components/globals/globals_component.h | 59 +++++++++++++++++++ esphome/cpp_generator.py | 7 ++- tests/test2.yaml | 7 +++ 4 files changed, 89 insertions(+), 4 deletions(-) diff --git a/esphome/components/globals/__init__.py b/esphome/components/globals/__init__.py index 97a7ba3d54..8defa4ac24 100644 --- a/esphome/components/globals/__init__.py +++ b/esphome/components/globals/__init__.py @@ -15,8 +15,14 @@ CODEOWNERS = ["@esphome/core"] globals_ns = cg.esphome_ns.namespace("globals") GlobalsComponent = globals_ns.class_("GlobalsComponent", cg.Component) RestoringGlobalsComponent = globals_ns.class_("RestoringGlobalsComponent", cg.Component) +RestoringGlobalStringComponent = globals_ns.class_( + "RestoringGlobalStringComponent", cg.Component +) GlobalVarSetAction = globals_ns.class_("GlobalVarSetAction", automation.Action) +CONF_MAX_RESTORE_DATA_LENGTH = "max_restore_data_length" + + MULTI_CONF = True CONFIG_SCHEMA = cv.Schema( { @@ -24,6 +30,7 @@ CONFIG_SCHEMA = cv.Schema( cv.Required(CONF_TYPE): cv.string_strict, cv.Optional(CONF_INITIAL_VALUE): cv.string_strict, cv.Optional(CONF_RESTORE_VALUE, default=False): cv.boolean, + cv.Optional(CONF_MAX_RESTORE_DATA_LENGTH): cv.int_range(0, 254), } ).extend(cv.COMPONENT_SCHEMA) @@ -32,12 +39,19 @@ CONFIG_SCHEMA = cv.Schema( @coroutine_with_priority(-100.0) async def to_code(config): type_ = cg.RawExpression(config[CONF_TYPE]) - template_args = cg.TemplateArguments(type_) restore = config[CONF_RESTORE_VALUE] - type = RestoringGlobalsComponent if restore else GlobalsComponent - res_type = type.template(template_args) + # Special casing the strings to their own class with a different save/restore mechanism + if str(type_) == "std::string" and restore: + template_args = cg.TemplateArguments( + type_, config.get(CONF_MAX_RESTORE_DATA_LENGTH, 63) + 1 + ) + type = RestoringGlobalStringComponent + else: + template_args = cg.TemplateArguments(type_) + type = RestoringGlobalsComponent if restore else GlobalsComponent + res_type = type.template(template_args) initial_value = None if CONF_INITIAL_VALUE in config: initial_value = cg.RawExpression(config[CONF_INITIAL_VALUE]) diff --git a/esphome/components/globals/globals_component.h b/esphome/components/globals/globals_component.h index 101adeb311..78808436af 100644 --- a/esphome/components/globals/globals_component.h +++ b/esphome/components/globals/globals_component.h @@ -65,6 +65,64 @@ template class RestoringGlobalsComponent : public Component { ESPPreferenceObject rtc_; }; +// Use with string or subclasses of strings +template class RestoringGlobalStringComponent : public Component { + public: + using value_type = T; + explicit RestoringGlobalStringComponent() = default; + explicit RestoringGlobalStringComponent(T initial_value) { this->value_ = initial_value; } + explicit RestoringGlobalStringComponent( + std::array::type, std::extent::value> initial_value) { + memcpy(this->value_, initial_value.data(), sizeof(T)); + } + + T &value() { return this->value_; } + + void setup() override { + char temp[SZ]; + this->rtc_ = global_preferences->make_preference(1944399030U ^ this->name_hash_); + bool hasdata = this->rtc_.load(&temp); + if (hasdata) { + this->value_.assign(temp + 1, temp[0]); + } + this->prev_value_.assign(this->value_); + } + + float get_setup_priority() const override { return setup_priority::HARDWARE; } + + void loop() override { store_value_(); } + + void on_shutdown() override { store_value_(); } + + void set_name_hash(uint32_t name_hash) { this->name_hash_ = name_hash; } + + protected: + void store_value_() { + int diff = this->value_.compare(this->prev_value_); + if (diff != 0) { + // Make it into a length prefixed thing + unsigned char temp[SZ]; + + // If string is bigger than the allocation, do not save it. + // We don't need to waste ram setting prev_value either. + int size = this->value_.size(); + // Less than, not less than or equal, SZ includes the length byte. + if (size < SZ) { + memcpy(temp + 1, this->value_.c_str(), size); + // SZ should be pre checked at the schema level, it can't go past the char range. + temp[0] = ((unsigned char) size); + this->rtc_.save(&temp); + this->prev_value_.assign(this->value_); + } + } + } + + T value_{}; + T prev_value_{}; + uint32_t name_hash_{}; + ESPPreferenceObject rtc_; +}; + template class GlobalVarSetAction : public Action { public: explicit GlobalVarSetAction(C *parent) : parent_(parent) {} @@ -81,6 +139,7 @@ template class GlobalVarSetAction : public Action T &id(GlobalsComponent *value) { return value->value(); } template T &id(RestoringGlobalsComponent *value) { return value->value(); } +template T &id(RestoringGlobalStringComponent *value) { return value->value(); } } // namespace globals } // namespace esphome diff --git a/esphome/cpp_generator.py b/esphome/cpp_generator.py index 789bd58e5c..2841be1546 100644 --- a/esphome/cpp_generator.py +++ b/esphome/cpp_generator.py @@ -663,7 +663,11 @@ async def process_lambda( :param return_type: The return type of the lambda. :return: The generated lambda expression. """ - from esphome.components.globals import GlobalsComponent, RestoringGlobalsComponent + from esphome.components.globals import ( + GlobalsComponent, + RestoringGlobalsComponent, + RestoringGlobalStringComponent, + ) if value is None: return @@ -676,6 +680,7 @@ async def process_lambda( and ( full_id.type.inherits_from(GlobalsComponent) or full_id.type.inherits_from(RestoringGlobalsComponent) + or full_id.type.inherits_from(RestoringGlobalStringComponent) ) ): parts[i * 3 + 1] = var.value() diff --git a/tests/test2.yaml b/tests/test2.yaml index 4928b8b877..c04e6726b1 100644 --- a/tests/test2.yaml +++ b/tests/test2.yaml @@ -5,6 +5,13 @@ esphome: board: nodemcu-32s build_path: build/test2 +globals: + - id: my_global_string + type: std::string + restore_value: yes + max_restore_data_length: 70 + initial_value: '"DefaultValue"' + substitutions: devicename: test2 From fe81bcc00343ba8d99a437762087717d45a20946 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 12 Sep 2023 09:26:48 +1200 Subject: [PATCH 102/111] Use /data directory for .esphome folder when running as HA add-on (#5374) --- .../etc/s6-overlay/s6-rc.d/esphome/run | 7 ++++++- esphome/components/font/__init__.py | 3 +-- esphome/components/image/__init__.py | 2 +- esphome/components/shelly_dimmer/light.py | 7 +------ esphome/core/__init__.py | 12 +++++++----- esphome/core/config.py | 4 ++-- esphome/dashboard/dashboard.py | 19 +++++++++---------- esphome/git.py | 2 +- esphome/storage_json.py | 14 +++++++------- esphome/wizard.py | 19 +++++++++++-------- tests/unit_tests/test_wizard.py | 7 +++++++ 11 files changed, 53 insertions(+), 43 deletions(-) diff --git a/docker/ha-addon-rootfs/etc/s6-overlay/s6-rc.d/esphome/run b/docker/ha-addon-rootfs/etc/s6-overlay/s6-rc.d/esphome/run index 277f26ea49..775c2fa0d6 100755 --- a/docker/ha-addon-rootfs/etc/s6-overlay/s6-rc.d/esphome/run +++ b/docker/ha-addon-rootfs/etc/s6-overlay/s6-rc.d/esphome/run @@ -35,11 +35,16 @@ if bashio::config.has_value 'default_compile_process_limit'; then export ESPHOME_DEFAULT_COMPILE_PROCESS_LIMIT=$(bashio::config 'default_compile_process_limit') else if grep -q 'Raspberry Pi 3' /proc/cpuinfo; then - export ESPHOME_DEFAULT_COMPILE_PROCESS_LIMIT=1; + export ESPHOME_DEFAULT_COMPILE_PROCESS_LIMIT=1 fi fi mkdir -p "${pio_cache_base}" +if bashio::fs.directory_exists '/config/esphome/.esphome'; then + bashio::log.info "Removing old .esphome directory..." + rm -rf /config/esphome/.esphome +fi + bashio::log.info "Starting ESPHome dashboard..." exec esphome dashboard /config/esphome --socket /var/run/esphome.sock --ha-addon diff --git a/esphome/components/font/__init__.py b/esphome/components/font/__init__.py index 52f877d986..e6244d8d44 100644 --- a/esphome/components/font/__init__.py +++ b/esphome/components/font/__init__.py @@ -98,10 +98,9 @@ def validate_truetype_file(value): def _compute_local_font_dir(name) -> Path: - base_dir = Path(CORE.config_dir) / ".esphome" / DOMAIN h = hashlib.new("sha256") h.update(name.encode()) - return base_dir / h.hexdigest()[:8] + return Path(CORE.data_dir) / DOMAIN / h.hexdigest()[:8] def _compute_gfonts_local_path(value) -> Path: diff --git a/esphome/components/image/__init__.py b/esphome/components/image/__init__.py index 392efb18a2..aa402ee329 100644 --- a/esphome/components/image/__init__.py +++ b/esphome/components/image/__init__.py @@ -52,7 +52,7 @@ Image_ = image_ns.class_("Image") def _compute_local_icon_path(value) -> Path: - base_dir = Path(CORE.config_dir) / ".esphome" / DOMAIN / "mdi" + base_dir = Path(CORE.data_dir) / DOMAIN / "mdi" return base_dir / f"{value[CONF_ICON]}.svg" diff --git a/esphome/components/shelly_dimmer/light.py b/esphome/components/shelly_dimmer/light.py index c49193d135..467a3c3531 100644 --- a/esphome/components/shelly_dimmer/light.py +++ b/esphome/components/shelly_dimmer/light.py @@ -87,12 +87,7 @@ def get_firmware(value): url = value[CONF_URL] if CONF_SHA256 in value: # we have a hash, enable caching - path = ( - Path(CORE.config_dir) - / ".esphome" - / DOMAIN - / (value[CONF_SHA256] + "_fw_stm.bin") - ) + path = Path(CORE.data_dir) / DOMAIN / (value[CONF_SHA256] + "_fw_stm.bin") if not path.is_file(): firmware_data, dl_hash = dl(url) diff --git a/esphome/core/__init__.py b/esphome/core/__init__.py index d9b1603894..cca758e3c1 100644 --- a/esphome/core/__init__.py +++ b/esphome/core/__init__.py @@ -554,6 +554,12 @@ class EsphomeCore: def config_dir(self): return os.path.dirname(self.config_path) + @property + def data_dir(self): + if is_ha_addon(): + return os.path.join("/data") + return self.relative_config_path(".esphome") + @property def config_filename(self): return os.path.basename(self.config_path) @@ -563,7 +569,7 @@ class EsphomeCore: return os.path.join(self.config_dir, path_) def relative_internal_path(self, *path: str) -> str: - return self.relative_config_path(".esphome", *path) + return os.path.join(self.data_dir, *path) def relative_build_path(self, *path): path_ = os.path.expanduser(os.path.join(*path)) @@ -573,13 +579,9 @@ class EsphomeCore: return self.relative_build_path("src", *path) def relative_pioenvs_path(self, *path): - if is_ha_addon(): - return os.path.join("/data", self.name, ".pioenvs", *path) return self.relative_build_path(".pioenvs", *path) def relative_piolibdeps_path(self, *path): - if is_ha_addon(): - return os.path.join("/data", self.name, ".piolibdeps", *path) return self.relative_build_path(".piolibdeps", *path) @property diff --git a/esphome/core/config.py b/esphome/core/config.py index a09252e4b4..1625644092 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -198,8 +198,8 @@ def preload_core_config(config, result): CORE.data[KEY_CORE] = {} if CONF_BUILD_PATH not in conf: - conf[CONF_BUILD_PATH] = f".esphome/build/{CORE.name}" - CORE.build_path = CORE.relative_config_path(conf[CONF_BUILD_PATH]) + conf[CONF_BUILD_PATH] = f"build/{CORE.name}" + CORE.build_path = CORE.relative_internal_path(conf[CONF_BUILD_PATH]) has_oldstyle = CONF_PLATFORM in conf newstyle_found = [key for key in TARGET_PLATFORMS if key in config] diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index cacd5e2db0..8049fb7f4c 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -32,6 +32,7 @@ import yaml from tornado.log import access_log from esphome import const, platformio_api, util, yaml_util +from esphome.core import CORE from esphome.helpers import get_bool_env, mkdir_p, run_system_command from esphome.storage_json import ( EsphomeStorageJSON, @@ -70,6 +71,7 @@ class DashboardSettings: self.password_hash = password_hash(password) self.config_dir = args.configuration self.absolute_config_dir = Path(self.config_dir).resolve() + CORE.config_path = os.path.join(self.config_dir, ".") @property def relative_url(self): @@ -534,7 +536,7 @@ class DownloadListRequestHandler(BaseHandler): @authenticated @bind_config def get(self, configuration=None): - storage_path = ext_storage_path(settings.config_dir, configuration) + storage_path = ext_storage_path(configuration) storage_json = StorageJSON.load(storage_path) if storage_json is None: self.send_error(404) @@ -577,7 +579,7 @@ class DownloadBinaryRequestHandler(BaseHandler): def get(self, configuration=None): compressed = self.get_argument("compressed", "0") == "1" - storage_path = ext_storage_path(settings.config_dir, configuration) + storage_path = ext_storage_path(configuration) storage_json = StorageJSON.load(storage_path) if storage_json is None: self.send_error(404) @@ -666,9 +668,7 @@ class DashboardEntry: @property def storage(self) -> Optional[StorageJSON]: if not self._loaded_storage: - self._storage = StorageJSON.load( - ext_storage_path(settings.config_dir, self.filename) - ) + self._storage = StorageJSON.load(ext_storage_path(self.filename)) self._loaded_storage = True return self._storage @@ -1044,9 +1044,9 @@ class DeleteRequestHandler(BaseHandler): @bind_config def post(self, configuration=None): config_file = settings.rel_path(configuration) - storage_path = ext_storage_path(settings.config_dir, configuration) + storage_path = ext_storage_path(configuration) - trash_path = trash_storage_path(settings.config_dir) + trash_path = trash_storage_path() mkdir_p(trash_path) shutil.move(config_file, os.path.join(trash_path, configuration)) @@ -1067,7 +1067,7 @@ class UndoDeleteRequestHandler(BaseHandler): @bind_config def post(self, configuration=None): config_file = settings.rel_path(configuration) - trash_path = trash_storage_path(settings.config_dir) + trash_path = trash_storage_path() shutil.move(os.path.join(trash_path, configuration), config_file) @@ -1325,10 +1325,9 @@ def make_app(debug=get_bool_env(ENV_DEV)): def start_web_server(args): settings.parse_args(args) - mkdir_p(settings.rel_path(".esphome")) if settings.using_auth: - path = esphome_storage_path(settings.config_dir) + path = esphome_storage_path() storage = EsphomeStorageJSON.load(path) if storage is None: storage = EsphomeStorageJSON.get_default() diff --git a/esphome/git.py b/esphome/git.py index dcc3e4d0c8..4f0911233e 100644 --- a/esphome/git.py +++ b/esphome/git.py @@ -35,7 +35,7 @@ def run_git_command(cmd, cwd=None) -> str: def _compute_destination_path(key: str, domain: str) -> Path: - base_dir = Path(CORE.config_dir) / ".esphome" / domain + base_dir = Path(CORE.data_dir) / domain h = hashlib.new("sha256") h.update(key.encode()) return base_dir / h.hexdigest()[:8] diff --git a/esphome/storage_json.py b/esphome/storage_json.py index acf525203d..a2619cb536 100644 --- a/esphome/storage_json.py +++ b/esphome/storage_json.py @@ -22,19 +22,19 @@ _LOGGER = logging.getLogger(__name__) def storage_path() -> str: - return CORE.relative_internal_path(f"{CORE.config_filename}.json") + return os.path.join(CORE.data_dir, "storage", f"{CORE.config_filename}.json") -def ext_storage_path(base_path: str, config_filename: str) -> str: - return os.path.join(base_path, ".esphome", f"{config_filename}.json") +def ext_storage_path(config_filename: str) -> str: + return os.path.join(CORE.data_dir, "storage", f"{config_filename}.json") -def esphome_storage_path(base_path: str) -> str: - return os.path.join(base_path, ".esphome", "esphome.json") +def esphome_storage_path() -> str: + return os.path.join(CORE.data_dir, "esphome.json") -def trash_storage_path(base_path: str) -> str: - return os.path.join(base_path, ".esphome", "trash") +def trash_storage_path() -> str: + return CORE.relative_config_path("trash") class StorageJSON: diff --git a/esphome/wizard.py b/esphome/wizard.py index 17a0882e1c..aa05e513a7 100644 --- a/esphome/wizard.py +++ b/esphome/wizard.py @@ -6,12 +6,12 @@ import unicodedata import voluptuous as vol import esphome.config_validation as cv +from esphome.const import ALLOWED_NAME_CHARS, ENV_QUICKWIZARD +from esphome.core import CORE from esphome.helpers import get_bool_env, write_file -from esphome.log import color, Fore - +from esphome.log import Fore, color from esphome.storage_json import StorageJSON, ext_storage_path from esphome.util import safe_print -from esphome.const import ALLOWED_NAME_CHARS, ENV_QUICKWIZARD CORE_BIG = r""" _____ ____ _____ ______ / ____/ __ \| __ \| ____| @@ -193,10 +193,10 @@ captive_portal: def wizard_write(path, **kwargs): - from esphome.components.esp8266 import boards as esp8266_boards - from esphome.components.esp32 import boards as esp32_boards - from esphome.components.rp2040 import boards as rp2040_boards from esphome.components.bk72xx import boards as bk72xx_boards + from esphome.components.esp32 import boards as esp32_boards + from esphome.components.esp8266 import boards as esp8266_boards + from esphome.components.rp2040 import boards as rp2040_boards from esphome.components.rtl87xx import boards as rtl87xx_boards name = kwargs["name"] @@ -225,7 +225,7 @@ def wizard_write(path, **kwargs): write_file(path, wizard_file(**kwargs)) storage = StorageJSON.from_wizard(name, name, f"{name}.local", hardware) - storage_path = ext_storage_path(os.path.dirname(path), os.path.basename(path)) + storage_path = ext_storage_path(os.path.basename(path)) storage.save(storage_path) return True @@ -265,9 +265,9 @@ def strip_accents(value): def wizard(path): + from esphome.components.bk72xx import boards as bk72xx_boards from esphome.components.esp32 import boards as esp32_boards from esphome.components.esp8266 import boards as esp8266_boards - from esphome.components.bk72xx import boards as bk72xx_boards from esphome.components.rtl87xx import boards as rtl87xx_boards if not path.endswith(".yaml") and not path.endswith(".yml"): @@ -280,6 +280,9 @@ def wizard(path): f"Uh oh, it seems like {color(Fore.CYAN, path)} already exists, please delete that file first or chose another configuration file." ) return 2 + + CORE.config_path = path + safe_print("Hi there!") sleep(1.5) safe_print("I'm the wizard of ESPHome :)") diff --git a/tests/unit_tests/test_wizard.py b/tests/unit_tests/test_wizard.py index d94624d1e4..8bbce08ae5 100644 --- a/tests/unit_tests/test_wizard.py +++ b/tests/unit_tests/test_wizard.py @@ -1,7 +1,9 @@ """Tests for the wizard.py file.""" +import os import esphome.wizard as wz import pytest +from esphome.core import CORE from esphome.components.esp8266.boards import ESP8266_BOARD_PINS from esphome.components.esp32.boards import ESP32_BOARD_PINS from esphome.components.bk72xx.boards import BK72XX_BOARD_PINS @@ -110,6 +112,7 @@ def test_wizard_write_sets_platform(default_config, tmp_path, monkeypatch): # Given del default_config["platform"] monkeypatch.setattr(wz, "write_file", MagicMock()) + monkeypatch.setattr(CORE, "config_path", os.path.dirname(tmp_path)) # When wz.wizard_write(tmp_path, **default_config) @@ -130,6 +133,7 @@ def test_wizard_write_defaults_platform_from_board_esp8266( default_config["board"] = [*ESP8266_BOARD_PINS][0] monkeypatch.setattr(wz, "write_file", MagicMock()) + monkeypatch.setattr(CORE, "config_path", os.path.dirname(tmp_path)) # When wz.wizard_write(tmp_path, **default_config) @@ -150,6 +154,7 @@ def test_wizard_write_defaults_platform_from_board_esp32( default_config["board"] = [*ESP32_BOARD_PINS][0] monkeypatch.setattr(wz, "write_file", MagicMock()) + monkeypatch.setattr(CORE, "config_path", os.path.dirname(tmp_path)) # When wz.wizard_write(tmp_path, **default_config) @@ -170,6 +175,7 @@ def test_wizard_write_defaults_platform_from_board_bk72xx( default_config["board"] = [*BK72XX_BOARD_PINS][0] monkeypatch.setattr(wz, "write_file", MagicMock()) + monkeypatch.setattr(CORE, "config_path", os.path.dirname(tmp_path)) # When wz.wizard_write(tmp_path, **default_config) @@ -190,6 +196,7 @@ def test_wizard_write_defaults_platform_from_board_rtl87xx( default_config["board"] = [*RTL87XX_BOARD_PINS][0] monkeypatch.setattr(wz, "write_file", MagicMock()) + monkeypatch.setattr(CORE, "config_path", os.path.dirname(tmp_path)) # When wz.wizard_write(tmp_path, **default_config) From e6da2313e69a1da4d1673ea28e5441fe189862fa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Sep 2023 10:02:13 +1200 Subject: [PATCH 103/111] Bump zeroconf from 0.102.0 to 0.108.0 (#5376) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 9bce4a309d..19c05bf8f7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,7 +11,7 @@ esptool==4.6.2 click==8.1.7 esphome-dashboard==20230904.0 aioesphomeapi==15.0.0 -zeroconf==0.102.0 +zeroconf==0.108.0 # esp-idf requires this, but doesn't bundle it by default # https://github.com/espressif/esp-idf/blob/220590d599e134d7a5e7f1e683cc4550349ffbf8/requirements.txt#L24 From 47b1b458284dd1d3e7bbf2a8bd196a5b2f4ac64a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Sep 2023 22:38:58 +0000 Subject: [PATCH 104/111] Bump black from 23.7.0 to 23.9.1 (#5377) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- requirements_test.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0bbb2fee61..6e7261ebc3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,7 +3,7 @@ # See https://pre-commit.com/hooks.html for more hooks repos: - repo: https://github.com/psf/black-pre-commit-mirror - rev: 23.7.0 + rev: 23.9.1 hooks: - id: black args: diff --git a/requirements_test.txt b/requirements_test.txt index f17ccd220d..a07815df54 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,6 +1,6 @@ pylint==2.17.5 flake8==6.1.0 # also change in .pre-commit-config.yaml when updating -black==23.7.0 # also change in .pre-commit-config.yaml when updating +black==23.9.1 # also change in .pre-commit-config.yaml when updating pyupgrade==3.10.1 # also change in .pre-commit-config.yaml when updating pre-commit From fc354eec0e6377d4faf0dcb7c160da5cc4e76228 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 12 Sep 2023 14:14:10 +1200 Subject: [PATCH 105/111] Attempt to fix rp2040 adc with vcc (#5378) --- esphome/components/adc/__init__.py | 9 ++--- esphome/components/adc/adc_sensor.cpp | 55 +++++++++++++-------------- 2 files changed, 30 insertions(+), 34 deletions(-) diff --git a/esphome/components/adc/__init__.py b/esphome/components/adc/__init__.py index 0b6ee145f2..bad5cf74ef 100644 --- a/esphome/components/adc/__init__.py +++ b/esphome/components/adc/__init__.py @@ -5,10 +5,7 @@ from esphome.const import CONF_ANALOG, CONF_INPUT from esphome.core import CORE from esphome.components.esp32 import get_esp32_variant -from esphome.const import ( - PLATFORM_ESP8266, - PLATFORM_RP2040, -) +from esphome.const import PLATFORM_ESP8266 from esphome.components.esp32.const import ( VARIANT_ESP32, VARIANT_ESP32C2, @@ -147,7 +144,9 @@ ESP32_VARIANT_ADC2_PIN_TO_CHANNEL = { def validate_adc_pin(value): if str(value).upper() == "VCC": - return cv.only_on([PLATFORM_ESP8266, PLATFORM_RP2040])("VCC") + if CORE.is_rp2040: + return pins.internal_gpio_input_pin_schema(29) + return cv.only_on([PLATFORM_ESP8266])("VCC") if str(value).upper() == "TEMPERATURE": return cv.only_on_rp2040("TEMPERATURE") diff --git a/esphome/components/adc/adc_sensor.cpp b/esphome/components/adc/adc_sensor.cpp index e69e6b9313..a9ac5a5cfe 100644 --- a/esphome/components/adc/adc_sensor.cpp +++ b/esphome/components/adc/adc_sensor.cpp @@ -1,6 +1,6 @@ #include "adc_sensor.h" -#include "esphome/core/log.h" #include "esphome/core/helpers.h" +#include "esphome/core/log.h" #ifdef USE_ESP8266 #ifdef USE_ADC_SENSOR_VCC @@ -246,45 +246,42 @@ float ADCSensor::sample() { adc_set_temp_sensor_enabled(true); delay(1); adc_select_input(4); + + int32_t raw = adc_read(); + adc_set_temp_sensor_enabled(false); + if (this->output_raw_) { + return raw; + } + return raw * 3.3f / 4096.0f; } else { - uint8_t pin; -#ifdef USE_ADC_SENSOR_VCC + uint8_t pin = this->pin_->get_pin(); #ifdef CYW43_USES_VSYS_PIN - // Measuring VSYS on Raspberry Pico W needs to be wrapped with - // `cyw43_thread_enter()`/`cyw43_thread_exit()` as discussed in - // https://github.com/raspberrypi/pico-sdk/issues/1222, since Wifi chip and - // VSYS ADC both share GPIO29 - cyw43_thread_enter(); + if (pin == PICO_VSYS_PIN) { + // Measuring VSYS on Raspberry Pico W needs to be wrapped with + // `cyw43_thread_enter()`/`cyw43_thread_exit()` as discussed in + // https://github.com/raspberrypi/pico-sdk/issues/1222, since Wifi chip and + // VSYS ADC both share GPIO29 + cyw43_thread_enter(); + } #endif // CYW43_USES_VSYS_PIN - pin = PICO_VSYS_PIN; -#else - pin = this->pin_->get_pin(); -#endif // USE_ADC_SENSOR_VCC adc_gpio_init(pin); adc_select_input(pin - 26); - } - int32_t raw = adc_read(); - if (this->is_temperature_) { - adc_set_temp_sensor_enabled(false); - } else { -#ifdef USE_ADC_SENSOR_VCC + int32_t raw = adc_read(); + #ifdef CYW43_USES_VSYS_PIN - cyw43_thread_exit(); + if (pin == PICO_VSYS_PIN) { + cyw43_thread_exit(); + } #endif // CYW43_USES_VSYS_PIN -#endif // USE_ADC_SENSOR_VCC - } - if (output_raw_) { - return raw; + if (output_raw_) { + return raw; + } + float coeff = pin == PICO_VSYS_PIN ? 3.0 : 1.0; + return raw * 3.3f / 4096.0f * coeff; } - float coeff = 1.0; -#ifdef USE_ADC_SENSOR_VCC - // As per Raspberry Pico (W) datasheet (section 2.1) the VSYS/3 is measured - coeff = 3.0; -#endif // USE_ADC_SENSOR_VCC - return raw * 3.3f / 4096.0f * coeff; } #endif From dadbc1aefa267d1868aa189da6a3ee16bb318674 Mon Sep 17 00:00:00 2001 From: Jimmy Hedman Date: Tue, 12 Sep 2023 22:05:02 +0200 Subject: [PATCH 106/111] Enable IPv6 for ESP8266 and Raspberry pi pico w (RP2040) (#4759) --- esphome/components/async_tcp/__init__.py | 2 +- esphome/components/mdns/mdns_esp8266.cpp | 3 +- esphome/components/mqtt/__init__.py | 4 +-- esphome/components/mqtt/mqtt_client.cpp | 10 ------- esphome/components/network/__init__.py | 29 ++++++++++--------- esphome/components/socket/socket.cpp | 2 +- .../wifi/wifi_component_esp8266.cpp | 17 +++++++++-- .../components/wifi/wifi_component_pico_w.cpp | 4 +++ platformio.ini | 4 +-- 9 files changed, 41 insertions(+), 34 deletions(-) diff --git a/esphome/components/async_tcp/__init__.py b/esphome/components/async_tcp/__init__.py index 1677d4b9a8..fa74f7103f 100644 --- a/esphome/components/async_tcp/__init__.py +++ b/esphome/components/async_tcp/__init__.py @@ -19,4 +19,4 @@ async def to_code(config): cg.add_library("esphome/AsyncTCP-esphome", "2.0.1") elif CORE.is_esp8266: # https://github.com/esphome/ESPAsyncTCP - cg.add_library("esphome/ESPAsyncTCP-esphome", "1.2.3") + cg.add_library("esphome/ESPAsyncTCP-esphome", "2.0.0") diff --git a/esphome/components/mdns/mdns_esp8266.cpp b/esphome/components/mdns/mdns_esp8266.cpp index 4ccfe42baa..5ff1b86341 100644 --- a/esphome/components/mdns/mdns_esp8266.cpp +++ b/esphome/components/mdns/mdns_esp8266.cpp @@ -13,8 +13,7 @@ namespace mdns { void MDNSComponent::setup() { this->compile_records_(); - network::IPAddress addr = network::get_ip_address(); - MDNS.begin(this->hostname_.c_str(), (uint32_t) addr); + MDNS.begin(this->hostname_.c_str()); for (const auto &service : this->services_) { // Strip the leading underscore from the proto and service_type. While it is diff --git a/esphome/components/mqtt/__init__.py b/esphome/components/mqtt/__init__.py index 102c070eb6..9df2067832 100644 --- a/esphome/components/mqtt/__init__.py +++ b/esphome/components/mqtt/__init__.py @@ -273,8 +273,8 @@ async def to_code(config): await cg.register_component(var, config) # Add required libraries for ESP8266 if CORE.is_esp8266: - # https://github.com/OttoWinter/async-mqtt-client/blob/master/library.json - cg.add_library("ottowinter/AsyncMqttClient-esphome", "0.8.6") + # https://github.com/heman/async-mqtt-client/blob/master/library.json + cg.add_library("heman/AsyncMqttClient-esphome", "1.0.0") cg.add_define("USE_MQTT") cg.add_global(mqtt_ns.using) diff --git a/esphome/components/mqtt/mqtt_client.cpp b/esphome/components/mqtt/mqtt_client.cpp index 1d804170f6..0c6da42328 100644 --- a/esphome/components/mqtt/mqtt_client.cpp +++ b/esphome/components/mqtt/mqtt_client.cpp @@ -168,15 +168,10 @@ void MQTTClientComponent::start_dnslookup_() { case ERR_OK: { // Got IP immediately this->dns_resolved_ = true; -#ifdef USE_ESP32 #if LWIP_IPV6 this->ip_ = addr.u_addr.ip4.addr; #else this->ip_ = addr.addr; -#endif -#endif -#ifdef USE_ESP8266 - this->ip_ = addr.addr; #endif this->start_connect_(); return; @@ -228,15 +223,10 @@ void MQTTClientComponent::dns_found_callback(const char *name, const ip_addr_t * if (ipaddr == nullptr) { a_this->dns_resolve_error_ = true; } else { -#ifdef USE_ESP32 #if LWIP_IPV6 a_this->ip_ = ipaddr->u_addr.ip4.addr; #else a_this->ip_ = ipaddr->addr; -#endif -#endif // USE_ESP32 -#ifdef USE_ESP8266 - a_this->ip_ = ipaddr->addr; #endif a_this->dns_resolved_ = true; } diff --git a/esphome/components/network/__init__.py b/esphome/components/network/__init__.py index 83778e0bf4..dd1353f86f 100644 --- a/esphome/components/network/__init__.py +++ b/esphome/components/network/__init__.py @@ -15,22 +15,23 @@ IPAddress = network_ns.class_("IPAddress") CONFIG_SCHEMA = cv.Schema( { - cv.SplitDefault(CONF_ENABLE_IPV6, esp32=False): cv.All( - cv.only_on_esp32, cv.boolean - ), + cv.Optional(CONF_ENABLE_IPV6, default=False): cv.boolean, } ) async def to_code(config): - if CONF_ENABLE_IPV6 in config: - cg.add_define("ENABLE_IPV6", config[CONF_ENABLE_IPV6]) - if CORE.using_esp_idf: - add_idf_sdkconfig_option("CONFIG_LWIP_IPV6", config[CONF_ENABLE_IPV6]) - add_idf_sdkconfig_option( - "CONFIG_LWIP_IPV6_AUTOCONFIG", config[CONF_ENABLE_IPV6] - ) - else: - if config[CONF_ENABLE_IPV6]: - cg.add_build_flag("-DCONFIG_LWIP_IPV6") - cg.add_build_flag("-DCONFIG_LWIP_IPV6_AUTOCONFIG") + cg.add_define("ENABLE_IPV6", config[CONF_ENABLE_IPV6]) + if CORE.using_esp_idf: + add_idf_sdkconfig_option("CONFIG_LWIP_IPV6", config[CONF_ENABLE_IPV6]) + add_idf_sdkconfig_option( + "CONFIG_LWIP_IPV6_AUTOCONFIG", config[CONF_ENABLE_IPV6] + ) + else: + if config[CONF_ENABLE_IPV6]: + cg.add_build_flag("-DCONFIG_LWIP_IPV6") + cg.add_build_flag("-DCONFIG_LWIP_IPV6_AUTOCONFIG") + if CORE.is_rp2040: + cg.add_build_flag("-DPIO_FRAMEWORK_ARDUINO_ENABLE_IPV6") + if CORE.is_esp8266: + cg.add_build_flag("-DPIO_FRAMEWORK_ARDUINO_LWIP2_IPV6_LOW_MEMORY") diff --git a/esphome/components/socket/socket.cpp b/esphome/components/socket/socket.cpp index 4c78397873..824e04150b 100644 --- a/esphome/components/socket/socket.cpp +++ b/esphome/components/socket/socket.cpp @@ -60,7 +60,7 @@ socklen_t set_sockaddr_any(struct sockaddr *addr, socklen_t addrlen, uint16_t po memset(server, 0, sizeof(sockaddr_in6)); server->sin6_family = AF_INET6; server->sin6_port = htons(port); - server->sin6_addr = in6addr_any; + server->sin6_addr = IN6ADDR_ANY_INIT; return sizeof(sockaddr_in6); #else if (addrlen < sizeof(sockaddr_in)) { diff --git a/esphome/components/wifi/wifi_component_esp8266.cpp b/esphome/components/wifi/wifi_component_esp8266.cpp index a28aa8b858..1afa439567 100644 --- a/esphome/components/wifi/wifi_component_esp8266.cpp +++ b/esphome/components/wifi/wifi_component_esp8266.cpp @@ -19,6 +19,7 @@ extern "C" { #include "lwip/apps/sntp.h" #if LWIP_IPV6 #include "lwip/netif.h" // struct netif +#include #endif #if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(3, 0, 0) #include "LwipDhcpServer.h" @@ -164,11 +165,11 @@ bool WiFiComponent::wifi_sta_ip_config_(optional manual_ip) { ip_addr_t dns; if (uint32_t(manual_ip->dns1) != 0) { - dns.addr = static_cast(manual_ip->dns1); + ip_addr_set_ip4_u32_val(dns, static_cast(manual_ip->dns1)); dns_setserver(0, &dns); } if (uint32_t(manual_ip->dns2) != 0) { - dns.addr = static_cast(manual_ip->dns2); + ip_addr_set_ip4_u32_val(dns, static_cast(manual_ip->dns2)); dns_setserver(1, &dns); } @@ -325,6 +326,18 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { return false; } +#if ENABLE_IPV6 + for (bool configured = false; !configured;) { + for (auto addr : addrList) { + ESP_LOGV(TAG, "Address %s", addr.toString().c_str()); + if ((configured = !addr.isLocal() && addr.isV6())) { + break; + } + } + delay(500); // NOLINT + } +#endif + if (ap.get_channel().has_value()) { ret = wifi_set_channel(*ap.get_channel()); if (!ret) { diff --git a/esphome/components/wifi/wifi_component_pico_w.cpp b/esphome/components/wifi/wifi_component_pico_w.cpp index 489ebc3699..149ca61cd5 100644 --- a/esphome/components/wifi/wifi_component_pico_w.cpp +++ b/esphome/components/wifi/wifi_component_pico_w.cpp @@ -175,7 +175,11 @@ network::IPAddress WiFiComponent::wifi_subnet_mask_() { return {WiFi.subnetMask( network::IPAddress WiFiComponent::wifi_gateway_ip_() { return {WiFi.gatewayIP()}; } network::IPAddress WiFiComponent::wifi_dns_ip_(int num) { const ip_addr_t *dns_ip = dns_getserver(num); +#ifdef PIO_FRAMEWORK_ARDUINO_ENABLE_IPV6 + return {dns_ip->u_addr.ip4.addr}; +#else return {dns_ip->addr}; +#endif } void WiFiComponent::wifi_loop_() { diff --git a/platformio.ini b/platformio.ini index aea164353d..73cd7c65c8 100644 --- a/platformio.ini +++ b/platformio.ini @@ -57,6 +57,7 @@ lib_deps = ${common.lib_deps} SPI ; spi (Arduino built-in) Wire ; i2c (Arduino built-int) + heman/AsyncMqttClient-esphome@1.0.0 ; mqtt esphome/ESPAsyncWebServer-esphome@2.1.0 ; web_server_base fastled/FastLED@3.3.2 ; fastled_base mikalhart/TinyGPSPlus@1.0.2 ; gps @@ -88,8 +89,7 @@ lib_deps = ${common:arduino.lib_deps} ESP8266WiFi ; wifi (Arduino built-in) Update ; ota (Arduino built-in) - ottowinter/AsyncMqttClient-esphome@0.8.6 ; mqtt - esphome/ESPAsyncTCP-esphome@1.2.3 ; async_tcp + esphome/ESPAsyncTCP-esphome@2.0.0 ; async_tcp ESP8266HTTPClient ; http_request (Arduino built-in) ESP8266mDNS ; mdns (Arduino built-in) DNSServer ; captive_portal (Arduino built-in) From bff74af882d52e515798284b70ea4b5e3d9b16b4 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 13 Sep 2023 10:06:32 +1200 Subject: [PATCH 107/111] Workflow updates (#5384) --- .github/actions/restore-python/action.yml | 4 +-- .github/workflows/ci-docker.yml | 8 ++--- .github/workflows/ci.yml | 38 +++++++++-------------- .github/workflows/lock.yml | 2 +- .github/workflows/release.yml | 22 ++++++------- .github/workflows/stale.yml | 4 +-- .github/workflows/sync-device-classes.yml | 11 +++---- .github/workflows/yaml-lint.yml | 22 +++++++++++++ 8 files changed, 62 insertions(+), 49 deletions(-) create mode 100644 .github/workflows/yaml-lint.yml diff --git a/.github/actions/restore-python/action.yml b/.github/actions/restore-python/action.yml index c6e9ca4153..aa8dd6d894 100644 --- a/.github/actions/restore-python/action.yml +++ b/.github/actions/restore-python/action.yml @@ -17,12 +17,12 @@ runs: steps: - name: Set up Python ${{ inputs.python-version }} id: python - uses: actions/setup-python@v4.6.0 + uses: actions/setup-python@v4.7.0 with: python-version: ${{ inputs.python-version }} - name: Restore Python virtual environment id: cache-venv - uses: actions/cache/restore@v3.3.1 + uses: actions/cache/restore@v3.3.2 with: path: venv # yamllint disable-line rule:line-length diff --git a/.github/workflows/ci-docker.yml b/.github/workflows/ci-docker.yml index dbd0d573da..b53eaf8e1a 100644 --- a/.github/workflows/ci-docker.yml +++ b/.github/workflows/ci-docker.yml @@ -40,15 +40,15 @@ jobs: arch: [amd64, armv7, aarch64] build_type: ["ha-addon", "docker", "lint"] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v4.0.0 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v4.7.0 with: python-version: "3.9" - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@v3.0.0 - name: Set up QEMU - uses: docker/setup-qemu-action@v2 + uses: docker/setup-qemu-action@v3.0.0 - name: Set TAG run: | diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 79ebe8782e..0786e1b9a2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,6 +7,10 @@ on: branches: [dev, beta, release] pull_request: + paths: + - "**" + - "!.github/workflows/*.yml" + - ".github/workflows/ci.yml" merge_group: permissions: @@ -30,13 +34,13 @@ jobs: cache-key: ${{ steps.cache-key.outputs.key }} steps: - name: Check out code from GitHub - uses: actions/checkout@v4 + uses: actions/checkout@v4.0.0 - name: Generate cache-key id: cache-key run: echo key="${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}" >> $GITHUB_OUTPUT - name: Set up Python ${{ env.DEFAULT_PYTHON }} id: python - uses: actions/setup-python@v4.6.0 + uses: actions/setup-python@v4.7.0 with: python-version: ${{ env.DEFAULT_PYTHON }} - name: Restore Python virtual environment @@ -55,15 +59,6 @@ jobs: pip install -r requirements.txt -r requirements_optional.txt -r requirements_test.txt pip install -e . - yamllint: - name: yamllint - runs-on: ubuntu-latest - steps: - - name: Check out code from GitHub - uses: actions/checkout@v4 - - name: Run yamllint - uses: frenck/action-yamllint@v1.4.1 - black: name: Check black runs-on: ubuntu-latest @@ -71,7 +66,7 @@ jobs: - common steps: - name: Check out code from GitHub - uses: actions/checkout@v4 + uses: actions/checkout@v4.0.0 - name: Restore Python uses: ./.github/actions/restore-python with: @@ -92,7 +87,7 @@ jobs: - common steps: - name: Check out code from GitHub - uses: actions/checkout@v4 + uses: actions/checkout@v4.0.0 - name: Restore Python uses: ./.github/actions/restore-python with: @@ -113,7 +108,7 @@ jobs: - common steps: - name: Check out code from GitHub - uses: actions/checkout@v4 + uses: actions/checkout@v4.0.0 - name: Restore Python uses: ./.github/actions/restore-python with: @@ -134,7 +129,7 @@ jobs: - common steps: - name: Check out code from GitHub - uses: actions/checkout@v4 + uses: actions/checkout@v4.0.0 - name: Restore Python uses: ./.github/actions/restore-python with: @@ -155,7 +150,7 @@ jobs: - common steps: - name: Check out code from GitHub - uses: actions/checkout@v4 + uses: actions/checkout@v4.0.0 - name: Restore Python uses: ./.github/actions/restore-python with: @@ -176,7 +171,7 @@ jobs: - common steps: - name: Check out code from GitHub - uses: actions/checkout@v4 + uses: actions/checkout@v4.0.0 - name: Restore Python uses: ./.github/actions/restore-python with: @@ -196,7 +191,7 @@ jobs: - common steps: - name: Check out code from GitHub - uses: actions/checkout@v4 + uses: actions/checkout@v4.0.0 - name: Restore Python uses: ./.github/actions/restore-python with: @@ -227,7 +222,6 @@ jobs: - pylint - pytest - pyupgrade - - yamllint strategy: fail-fast: false max-parallel: 2 @@ -235,7 +229,7 @@ jobs: file: [1, 2, 3, 3.1, 4, 5, 6, 7, 8, 10] steps: - name: Check out code from GitHub - uses: actions/checkout@v4 + uses: actions/checkout@v4.0.0 - name: Restore Python uses: ./.github/actions/restore-python with: @@ -258,7 +252,6 @@ jobs: - pylint - pytest - pyupgrade - - yamllint strategy: fail-fast: false max-parallel: 2 @@ -291,7 +284,7 @@ jobs: steps: - name: Check out code from GitHub - uses: actions/checkout@v4 + uses: actions/checkout@v4.0.0 - name: Restore Python uses: ./.github/actions/restore-python with: @@ -337,7 +330,6 @@ jobs: - pylint - pytest - pyupgrade - - yamllint - compile-tests - clang-tidy if: always() diff --git a/.github/workflows/lock.yml b/.github/workflows/lock.yml index e762512ff6..b455e3f4ea 100644 --- a/.github/workflows/lock.yml +++ b/.github/workflows/lock.yml @@ -18,7 +18,7 @@ jobs: lock: runs-on: ubuntu-latest steps: - - uses: dessant/lock-threads@v4 + - uses: dessant/lock-threads@v4.0.1 with: pr-inactive-days: "1" pr-lock-reason: "" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 71a0cd2c78..99d1594f03 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -19,7 +19,7 @@ jobs: outputs: tag: ${{ steps.tag.outputs.tag }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v4.0.0 - name: Get tag id: tag # yamllint disable rule:line-length @@ -43,9 +43,9 @@ jobs: if: github.repository == 'esphome/esphome' && github.event_name == 'release' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v4.0.0 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v4.7.0 with: python-version: "3.x" - name: Set up python environment @@ -88,24 +88,24 @@ jobs: target: "lint" baseimg: "docker" steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v4.0.0 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v4.7.0 with: python-version: "3.9" - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@v3.0.0 - name: Set up QEMU - uses: docker/setup-qemu-action@v2 + uses: docker/setup-qemu-action@v3.0.0 - name: Log in to docker hub - uses: docker/login-action@v2 + uses: docker/login-action@v3.0.0 with: username: ${{ secrets.DOCKER_USER }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Log in to the GitHub container registry - uses: docker/login-action@v2 + uses: docker/login-action@v3.0.0 with: registry: ghcr.io username: ${{ github.actor }} @@ -119,7 +119,7 @@ jobs: --suffix "${{ matrix.image.suffix }}" - name: Build and push - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v5.0.0 with: context: . file: ./docker/Dockerfile @@ -141,7 +141,7 @@ jobs: needs: [deploy-docker] steps: - name: Trigger Workflow - uses: actions/github-script@v6 + uses: actions/github-script@v6.4.1 with: github-token: ${{ secrets.DEPLOY_HA_ADDON_REPO_TOKEN }} script: | diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 3a3e390eef..a2d3f2f77d 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -18,7 +18,7 @@ jobs: stale: runs-on: ubuntu-latest steps: - - uses: actions/stale@v8 + - uses: actions/stale@v8.0.0 with: days-before-pr-stale: 90 days-before-pr-close: 7 @@ -38,7 +38,7 @@ jobs: close-issues: runs-on: ubuntu-latest steps: - - uses: actions/stale@v8 + - uses: actions/stale@v8.0.0 with: days-before-pr-stale: -1 days-before-pr-close: -1 diff --git a/.github/workflows/sync-device-classes.yml b/.github/workflows/sync-device-classes.yml index 1759db962c..943e93a0b7 100644 --- a/.github/workflows/sync-device-classes.yml +++ b/.github/workflows/sync-device-classes.yml @@ -4,8 +4,7 @@ name: Synchronise Device Classes from Home Assistant on: workflow_dispatch: schedule: - - cron: '45 6 * * *' - + - cron: "45 6 * * *" jobs: sync: @@ -14,16 +13,16 @@ jobs: if: github.repository == 'esphome/esphome' steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v4.0.0 - name: Checkout Home Assistant - uses: actions/checkout@v4 + uses: actions/checkout@v4.0.0 with: repository: home-assistant/core path: lib/home-assistant - name: Setup Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v4.7.0 with: python-version: 3.11 @@ -37,7 +36,7 @@ jobs: python ./script/sync-device_class.py - name: Commit changes - uses: peter-evans/create-pull-request@v5 + uses: peter-evans/create-pull-request@v5.0.2 with: commit-message: "Synchronise Device Classes from Home Assistant" committer: esphomebot diff --git a/.github/workflows/yaml-lint.yml b/.github/workflows/yaml-lint.yml new file mode 100644 index 0000000000..77b3c1dcb5 --- /dev/null +++ b/.github/workflows/yaml-lint.yml @@ -0,0 +1,22 @@ +name: YAML lint + +on: + push: + branches: [dev, beta, release] + paths: + - "**.yaml" + - "**.yml" + pull_request: + paths: + - "**.yaml" + - "**.yml" + +jobs: + yamllint: + name: yamllint + runs-on: ubuntu-latest + steps: + - name: Check out code from GitHub + uses: actions/checkout@v4.0.0 + - name: Run yamllint + uses: frenck/action-yamllint@v1.4.1 From bf5352b44ee84f6880935fcc011be4df7edf4863 Mon Sep 17 00:00:00 2001 From: Tercio Filho Date: Tue, 12 Sep 2023 19:15:01 -0300 Subject: [PATCH 108/111] Modbus Controller added some features (#5318) --- .../components/modbus_controller/__init__.py | 5 +++- esphome/components/modbus_controller/const.py | 1 + .../modbus_controller/modbus_controller.cpp | 23 +++++++++++++++++++ .../modbus_controller/modbus_controller.h | 13 ++++++++--- 4 files changed, 38 insertions(+), 4 deletions(-) diff --git a/esphome/components/modbus_controller/__init__.py b/esphome/components/modbus_controller/__init__.py index 46bb2c4233..8703771c3a 100644 --- a/esphome/components/modbus_controller/__init__.py +++ b/esphome/components/modbus_controller/__init__.py @@ -8,6 +8,7 @@ from .const import ( CONF_BITMASK, CONF_BYTE_OFFSET, CONF_COMMAND_THROTTLE, + CONF_OFFLINE_SKIP_UPDATES, CONF_CUSTOM_COMMAND, CONF_FORCE_NEW_RANGE, CONF_MODBUS_CONTROLLER_ID, @@ -104,6 +105,7 @@ CONFIG_SCHEMA = cv.All( cv.Optional( CONF_COMMAND_THROTTLE, default="0ms" ): cv.positive_time_period_milliseconds, + cv.Optional(CONF_OFFLINE_SKIP_UPDATES, default=0): cv.positive_int, } ) .extend(cv.polling_component_schema("60s")) @@ -206,8 +208,9 @@ async def add_modbus_base_properties( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID], config[CONF_COMMAND_THROTTLE]) + var = cg.new_Pvariable(config[CONF_ID]) cg.add(var.set_command_throttle(config[CONF_COMMAND_THROTTLE])) + cg.add(var.set_offline_skip_updates(config[CONF_OFFLINE_SKIP_UPDATES])) await register_modbus_device(var, config) diff --git a/esphome/components/modbus_controller/const.py b/esphome/components/modbus_controller/const.py index baf72efb94..1a23640e17 100644 --- a/esphome/components/modbus_controller/const.py +++ b/esphome/components/modbus_controller/const.py @@ -1,6 +1,7 @@ CONF_BITMASK = "bitmask" CONF_BYTE_OFFSET = "byte_offset" CONF_COMMAND_THROTTLE = "command_throttle" +CONF_OFFLINE_SKIP_UPDATES = "offline_skip_updates" CONF_CUSTOM_COMMAND = "custom_command" CONF_FORCE_NEW_RANGE = "force_new_range" CONF_MODBUS_CONTROLLER_ID = "modbus_controller_id" diff --git a/esphome/components/modbus_controller/modbus_controller.cpp b/esphome/components/modbus_controller/modbus_controller.cpp index 79c13e3f68..7565dc5e1b 100644 --- a/esphome/components/modbus_controller/modbus_controller.cpp +++ b/esphome/components/modbus_controller/modbus_controller.cpp @@ -26,6 +26,17 @@ bool ModbusController::send_next_command_() { // remove from queue if command was sent too often if (command->send_countdown < 1) { + if (!this->module_offline_) { + ESP_LOGW(TAG, "Modbus device=%d set offline", this->address_); + + if (this->offline_skip_updates_ > 0) { + // Update skip_updates_counter to stop flooding channel with timeouts + for (auto &r : this->register_ranges_) { + r.skip_updates_counter = this->offline_skip_updates_; + } + } + } + this->module_offline_ = true; ESP_LOGD( TAG, "Modbus command to device=%d register=0x%02X countdown=%d no response received - removed from send queue", @@ -49,6 +60,18 @@ bool ModbusController::send_next_command_() { void ModbusController::on_modbus_data(const std::vector &data) { auto ¤t_command = this->command_queue_.front(); if (current_command != nullptr) { + if (this->module_offline_) { + ESP_LOGW(TAG, "Modbus device=%d back online", this->address_); + + if (this->offline_skip_updates_ > 0) { + // Restore skip_updates_counter to restore commands updates + for (auto &r : this->register_ranges_) { + r.skip_updates_counter = 0; + } + } + } + this->module_offline_ = false; + // Move the commandItem to the response queue current_command->payload = data; this->incoming_queue_.push(std::move(current_command)); diff --git a/esphome/components/modbus_controller/modbus_controller.h b/esphome/components/modbus_controller/modbus_controller.h index ccb0edf9c6..a389375523 100644 --- a/esphome/components/modbus_controller/modbus_controller.h +++ b/esphome/components/modbus_controller/modbus_controller.h @@ -409,7 +409,6 @@ class ModbusCommandItem { class ModbusController : public PollingComponent, public modbus::ModbusDevice { public: - ModbusController(uint16_t throttle = 0) : command_throttle_(throttle){}; void dump_config() override; void loop() override; void setup() override; @@ -431,6 +430,12 @@ class ModbusController : public PollingComponent, public modbus::ModbusDevice { const std::vector &data); /// called by esphome generated code to set the command_throttle period void set_command_throttle(uint16_t command_throttle) { this->command_throttle_ = command_throttle; } + /// called by esphome generated code to set the offline_skip_updates + void set_offline_skip_updates(uint16_t offline_skip_updates) { this->offline_skip_updates_ = offline_skip_updates; } + /// get the number of queued modbus commands (should be mostly empty) + size_t get_command_queue_length() { return command_queue_.size(); } + /// get if the module is offline, didn't respond the last command + bool get_module_offline() { return module_offline_; } protected: /// parse sensormap_ and create range of sequential addresses @@ -443,8 +448,6 @@ class ModbusController : public PollingComponent, public modbus::ModbusDevice { void process_modbus_data_(const ModbusCommandItem *response); /// send the next modbus command from the send queue bool send_next_command_(); - /// get the number of queued modbus commands (should be mostly empty) - size_t get_command_queue_length_() { return command_queue_.size(); } /// dump the parsed sensormap for diagnostics void dump_sensors_(); /// Collection of all sensors for this component @@ -459,6 +462,10 @@ class ModbusController : public PollingComponent, public modbus::ModbusDevice { uint32_t last_command_timestamp_; /// min time in ms between sending modbus commands uint16_t command_throttle_; + /// if module didn't respond the last command + bool module_offline_; + /// how many updates to skip if module is offline + uint16_t offline_skip_updates_; }; /** Convert vector response payload to float. From b8fa737bc956577e7b2635af7f60b43633613a58 Mon Sep 17 00:00:00 2001 From: rufuswilson Date: Wed, 13 Sep 2023 00:20:00 +0200 Subject: [PATCH 109/111] Force heater off on setup (#5161) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/sht3xd/sensor.py | 7 ++++++- esphome/components/sht3xd/sht3xd.cpp | 4 ++++ esphome/components/sht3xd/sht3xd.h | 2 ++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/esphome/components/sht3xd/sensor.py b/esphome/components/sht3xd/sensor.py index 8e1ef426ad..5c73a63f1a 100644 --- a/esphome/components/sht3xd/sensor.py +++ b/esphome/components/sht3xd/sensor.py @@ -12,6 +12,8 @@ from esphome.const import ( UNIT_PERCENT, ) +CONF_HEATER_ENABLED = "heater_enabled" + DEPENDENCIES = ["i2c"] AUTO_LOAD = ["sensirion_common"] @@ -36,7 +38,8 @@ CONFIG_SCHEMA = ( device_class=DEVICE_CLASS_HUMIDITY, state_class=STATE_CLASS_MEASUREMENT, ), - } + cv.Optional(CONF_HEATER_ENABLED, default=True): cv.boolean, + }, ) .extend(cv.polling_component_schema("60s")) .extend(i2c.i2c_device_schema(0x44)) @@ -48,6 +51,8 @@ async def to_code(config): await cg.register_component(var, config) await i2c.register_i2c_device(var, config) + cg.add(var.set_heater_enabled(config[CONF_HEATER_ENABLED])) + if CONF_TEMPERATURE in config: sens = await sensor.new_sensor(config[CONF_TEMPERATURE]) cg.add(var.set_temperature_sensor(sens)) diff --git a/esphome/components/sht3xd/sht3xd.cpp b/esphome/components/sht3xd/sht3xd.cpp index 4e1c9742bc..f4bd2da271 100644 --- a/esphome/components/sht3xd/sht3xd.cpp +++ b/esphome/components/sht3xd/sht3xd.cpp @@ -22,6 +22,10 @@ void SHT3XDComponent::setup() { this->mark_failed(); return; } + if (!this->write_command(heater_enabled_ ? SHT3XD_COMMAND_HEATER_ENABLE : SHT3XD_COMMAND_HEATER_DISABLE)) { + this->mark_failed(); + return; + } uint32_t serial_number = (uint32_t(raw_serial_number[0]) << 16) | uint32_t(raw_serial_number[1]); ESP_LOGV(TAG, " Serial Number: 0x%08X", serial_number); } diff --git a/esphome/components/sht3xd/sht3xd.h b/esphome/components/sht3xd/sht3xd.h index 41ca3c5d6e..04023c8a46 100644 --- a/esphome/components/sht3xd/sht3xd.h +++ b/esphome/components/sht3xd/sht3xd.h @@ -17,10 +17,12 @@ class SHT3XDComponent : public PollingComponent, public sensirion_common::Sensir void dump_config() override; float get_setup_priority() const override; void update() override; + void set_heater_enabled(bool heater_enabled) { heater_enabled_ = heater_enabled; } protected: sensor::Sensor *temperature_sensor_{nullptr}; sensor::Sensor *humidity_sensor_{nullptr}; + bool heater_enabled_{true}; }; } // namespace sht3xd From 736dbfac135e368f8c6ee0be2701597ec59ad3ef Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Tue, 12 Sep 2023 18:36:17 -0500 Subject: [PATCH 110/111] Add IDF 5 test yaml, add adc to IDF tests, fix adc for IDF 5 (#5379) --- .github/workflows/ci.yml | 2 +- esphome/components/adc/adc_sensor.h | 4 + tests/test11.5.yaml | 697 ++++++++++++++++++++++++++++ tests/test5.yaml | 6 + 4 files changed, 708 insertions(+), 1 deletion(-) create mode 100644 tests/test11.5.yaml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0786e1b9a2..4e4e6c0ece 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -226,7 +226,7 @@ jobs: fail-fast: false max-parallel: 2 matrix: - file: [1, 2, 3, 3.1, 4, 5, 6, 7, 8, 10] + file: [1, 2, 3, 3.1, 4, 5, 6, 7, 8, 10, 11.5] steps: - name: Check out code from GitHub uses: actions/checkout@v4.0.0 diff --git a/esphome/components/adc/adc_sensor.h b/esphome/components/adc/adc_sensor.h index 7d9c8959da..b1fdcd5d29 100644 --- a/esphome/components/adc/adc_sensor.h +++ b/esphome/components/adc/adc_sensor.h @@ -62,8 +62,12 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage adc1_channel_t channel1_{ADC1_CHANNEL_MAX}; adc2_channel_t channel2_{ADC2_CHANNEL_MAX}; bool autorange_{false}; +#if ESP_IDF_VERSION_MAJOR >= 5 + esp_adc_cal_characteristics_t cal_characteristics_[SOC_ADC_ATTEN_NUM] = {}; +#else esp_adc_cal_characteristics_t cal_characteristics_[ADC_ATTEN_MAX] = {}; #endif +#endif }; } // namespace adc diff --git a/tests/test11.5.yaml b/tests/test11.5.yaml new file mode 100644 index 0000000000..544dc10930 --- /dev/null +++ b/tests/test11.5.yaml @@ -0,0 +1,697 @@ +--- +# copy of test5.yaml configured to build on IDF 5 +esphome: + name: test11-5 + build_path: build/test11.5 + project: + name: esphome.test11_5_project + version: "1.0.0" + +esp32: + board: nodemcu-32s + framework: + type: esp-idf + version: 5.0.2 + platform_version: 6.3.2 + advanced: + ignore_efuse_mac_crc: true + +wifi: + networks: + - ssid: "MySSID" + password: "password1" + manual_ip: + static_ip: 192.168.1.23 + gateway: 192.168.1.1 + subnet: 255.255.255.0 + +api: + +ota: + +logger: + +debug: + +psram: + +uart: + - id: uart_1 + tx_pin: 1 + rx_pin: 3 + baud_rate: 9600 + - id: uart_2 + tx_pin: 17 + rx_pin: 16 + baud_rate: 19200 + +i2c: + frequency: 100khz + +modbus: + uart_id: uart_1 + flow_control_pin: 5 + id: mod_bus1 + +modbus_controller: + - id: modbus_controller_test + address: 0x2 + modbus_id: mod_bus1 + +mqtt: + broker: test.mosquitto.org + port: 1883 + discovery: true + discovery_prefix: homeassistant + idf_send_async: false + on_message: + topic: testing/sensor/testing_sensor/state + qos: 0 + then: + # yamllint disable rule:line-length + - lambda: |- + ESP_LOGD("Mqtt Test", "testing/sensor/testing_sensor/state=[%s]", x.c_str()); + # yamllint enable rule:line-length + +vbus: + - uart_id: uart_2 + +binary_sensor: + - platform: gpio + pin: GPIO0 + id: io0_button + icon: mdi:gesture-tap-button + + - platform: modbus_controller + modbus_controller_id: modbus_controller_test + id: modbus_binsensortest + register_type: read + address: 0x3200 + bitmask: 0x80 # (bit 8) + lambda: "return x;" + + - platform: tm1638 + id: Button0 + key: 0 + filters: + - delayed_on: 10ms + on_press: + then: + - switch.turn_on: Led0 + on_release: + then: + - switch.turn_off: Led0 + + - platform: tm1638 + id: Button1 + key: 1 + on_press: + then: + - switch.turn_on: Led1 + on_release: + then: + - switch.turn_off: Led1 + + - platform: tm1638 + id: Button2 + key: 2 + on_press: + then: + - switch.turn_on: Led2 + on_release: + then: + - switch.turn_off: Led2 + + - platform: tm1638 + id: Button3 + key: 3 + on_press: + then: + - switch.turn_on: Led3 + on_release: + then: + - switch.turn_off: Led3 + + - platform: tm1638 + id: Button4 + key: 4 + on_press: + then: + - output.turn_on: Led4 + on_release: + then: + - output.turn_off: Led4 + + - platform: tm1638 + id: Button5 + key: 5 + on_press: + then: + - output.turn_on: Led5 + on_release: + then: + - output.turn_off: Led5 + + - platform: tm1638 + id: Button6 + key: 6 + on_press: + then: + - output.turn_on: Led6 + on_release: + then: + - output.turn_off: Led6 + + - platform: tm1638 + id: Button7 + key: 7 + on_press: + then: + - output.turn_on: Led7 + on_release: + then: + - output.turn_off: Led7 + + - platform: gpio + id: sn74hc165_pin_0 + pin: + sn74hc165: sn74hc165_hub + number: 0 + + - platform: ezo_pmp + pump_state: + name: "Pump State" + is_paused: + name: "Is Paused" + + - platform: matrix_keypad + keypad_id: keypad + id: key4 + row: 1 + col: 1 + - platform: matrix_keypad + id: key1 + key: 1 + + - platform: vbus + model: deltasol_bs_plus + relay2: + name: Relay 2 On + sensor1_error: + name: Sensor 1 Error + + - platform: vbus + model: custom + command: 0x100 + source: 0x1234 + dest: 0x10 + binary_sensors: + - id: vcustom_b + name: VBus Custom Binary Sensor + lambda: return x[0] & 1; + +tlc5947: + data_pin: GPIO12 + clock_pin: GPIO14 + lat_pin: GPIO15 + +gp8403: + - id: gp8403_5v + voltage: 5V + - id: gp8403_10v + voltage: 10V + +output: + - platform: gpio + pin: GPIO2 + id: built_in_led + + - platform: tlc5947 + id: output_red + channel: 0 + max_power: 0.8 + + - platform: mcp47a1 + id: output_mcp47a1 + + - platform: modbus_controller + modbus_controller_id: modbus_controller_test + id: modbus_output_test + lambda: |- + return x * 1.0 ; + address: 0x9001 + value_type: U_WORD + + - platform: tm1638 + id: Led4 + led: 4 + + - platform: tm1638 + id: Led5 + led: 5 + + - platform: tm1638 + id: Led6 + led: 6 + + - platform: tm1638 + id: Led7 + led: 7 + + - platform: gp8403 + id: gp8403_output_0 + gp8403_id: gp8403_5v + channel: 0 + - platform: gp8403 + gp8403_id: gp8403_10v + id: gp8403_output_1 + channel: 1 + +demo: + +esp32_ble: + +esp32_ble_server: + manufacturer: ESPHome + model: Test11 + +esp32_improv: + authorizer: io0_button + authorized_duration: 1min + status_indicator: built_in_led + +ezo_pmp: + id: hcl_pump + update_interval: 1s + +number: + - platform: template + name: My template number + id: template_number_id + optimistic: true + max_value: 100 + min_value: 0 + step: 5 + unit_of_measurement: "%" + mode: slider + device_class: humidity + on_value: + - logger.log: + format: Number changed to %f + args: [x] + set_action: + - logger.log: + format: Template Number set to %f + args: [x] + - number.set: + id: template_number_id + value: 50 + - number.to_min: template_number_id + - number.to_min: + id: template_number_id + - number.to_max: template_number_id + - number.to_max: + id: template_number_id + - number.increment: template_number_id + - number.increment: + id: template_number_id + cycle: false + - number.decrement: template_number_id + - number.decrement: + id: template_number_id + cycle: false + - number.operation: + id: template_number_id + operation: Increment + cycle: false + - number.operation: + id: template_number_id + operation: !lambda "return NUMBER_OP_INCREMENT;" + cycle: !lambda "return false;" + + - id: modbus_numbertest + platform: modbus_controller + modbus_controller_id: modbus_controller_test + name: ModbusNumber + address: 0x9002 + value_type: U_WORD + lambda: "return x * 1.0;" + write_lambda: |- + return x * 1.0 ; + multiply: 1.0 + +select: + - platform: template + name: My template select + id: template_select_id + optimistic: true + initial_option: two + restore_value: true + on_value: + - logger.log: + format: Select changed to %s (index %d)" + args: ["x.c_str()", "i"] + set_action: + - logger.log: + format: Template Select set to %s + args: ["x.c_str()"] + - select.set: + id: template_select_id + option: two + - select.first: template_select_id + - select.last: + id: template_select_id + - select.previous: template_select_id + - select.next: + id: template_select_id + cycle: false + - select.operation: + id: template_select_id + operation: Previous + cycle: false + - select.operation: + id: template_select_id + operation: !lambda "return SELECT_OP_PREVIOUS;" + cycle: !lambda "return true;" + - select.set_index: + id: template_select_id + index: 1 + - select.set_index: + id: template_select_id + index: !lambda "return 1 + 1;" + options: + - one + - two + - three + + - platform: modbus_controller + name: Modbus Select Register 1000 + address: 1000 + value_type: U_WORD + optionsmap: + "Zero": 0 + "One": 1 + "Two": 2 + "Three": 3 + +sensor: + - platform: adc + id: adc_sensor_p32 + name: ADC pin 32 + pin: 32 + attenuation: 11db + update_interval: 1s + - platform: internal_temperature + name: Internal Temperature + - platform: selec_meter + total_active_energy: + name: SelecEM2M Total Active Energy + import_active_energy: + name: SelecEM2M Import Active Energy + export_active_energy: + name: SelecEM2M Export Active Energy + total_reactive_energy: + name: SelecEM2M Total Reactive Energy + import_reactive_energy: + name: SelecEM2M Import Reactive Energy + export_reactive_energy: + name: SelecEM2M Export Reactive Energy + apparent_energy: + name: SelecEM2M Apparent Energy + active_power: + name: SelecEM2M Active Power + reactive_power: + name: SelecEM2M Reactive Power + apparent_power: + name: SelecEM2M Apparent Power + voltage: + name: SelecEM2M Voltage + current: + name: SelecEM2M Current + power_factor: + name: SelecEM2M Power Factor + frequency: + name: SelecEM2M Frequency + maximum_demand_active_power: + name: SelecEM2M Maximum Demand Active Power + disabled_by_default: true + maximum_demand_reactive_power: + name: SelecEM2M Maximum Demand Reactive Power + disabled_by_default: true + maximum_demand_apparent_power: + name: SelecEM2M Maximum Demand Apparent Power + disabled_by_default: true + + - id: modbus_sensortest + platform: modbus_controller + modbus_controller_id: modbus_controller_test + address: 0x331A + register_type: read + value_type: U_WORD + + - platform: t6615 + uart_id: uart_2 + co2: + name: CO2 Sensor + + - platform: bmp3xx + temperature: + name: BMP Temperature + oversampling: 16x + pressure: + name: BMP Pressure + address: 0x77 + iir_filter: 2X + + - platform: sen5x + id: sen54 + temperature: + name: Temperature + accuracy_decimals: 1 + humidity: + name: Humidity + accuracy_decimals: 0 + pm_1_0: + name: PM <1µm Weight concentration + id: pm_1_0 + accuracy_decimals: 1 + pm_2_5: + name: PM <2.5µm Weight concentration + id: pm_2_5 + accuracy_decimals: 1 + pm_4_0: + name: PM <4µm Weight concentration + id: pm_4_0 + accuracy_decimals: 1 + pm_10_0: + name: PM <10µm Weight concentration + id: pm_10_0 + accuracy_decimals: 1 + nox: + name: NOx + voc: + name: VOC + algorithm_tuning: + index_offset: 100 + learning_time_offset_hours: 12 + learning_time_gain_hours: 12 + gating_max_duration_minutes: 180 + std_initial: 50 + gain_factor: 230 + temperature_compensation: + offset: 0 + normalized_offset_slope: 0 + time_constant: 0 + auto_cleaning_interval: 604800s + acceleration_mode: low + store_baseline: true + address: 0x69 + - platform: mcp9600 + thermocouple_type: K + hot_junction: + name: Thermocouple Temperature + cold_junction: + name: Ambient Temperature + + - platform: ezo_pmp + current_volume_dosed: + name: Current Volume Dosed + total_volume_dosed: + name: Total Volume Dosed + absolute_total_volume_dosed: + name: Absolute Total Volume Dosed + pump_voltage: + name: Pump Voltage + last_volume_requested: + name: Last Volume Requested + max_flow_rate: + name: Max Flow Rate + + - platform: vbus + model: deltasol c + temperature_3: + name: Temperature 3 + operating_hours_1: + name: Operating Hours 1 + heat_quantity: + name: Heat Quantity + time: + name: System Time + + - platform: debug + free: + name: "Heap Free" + block: + name: "Heap Max Block" + loop_time: + name: "Loop Time" + psram: + name: "PSRAM Free" + + - platform: vbus + model: custom + command: 0x100 + source: 0x1234 + dest: 0x10 + sensors: + - id: vcustom + name: VBus Custom Sensor + lambda: return x[0] / 10.0; + + - platform: kuntze + ph: + name: Kuntze pH + temperature: + name: Kuntze temperature + +script: + - id: automation_test + then: + - repeat: + count: 5 + then: + - logger.log: looping! + + - id: zero_repeat_test + then: + - repeat: + count: !lambda "return 0;" + then: + - logger.log: shouldn't see mee! + +switch: + - platform: modbus_controller + modbus_controller_id: modbus_controller_test + id: modbus_switch_test + register_type: coil + address: 2 + bitmask: 1 + + - platform: tm1638 + id: Led0 + led: 0 + name: TM1638Led0 + + - platform: tm1638 + id: Led1 + led: 1 + name: TM1638Led1 + + - platform: tm1638 + id: Led2 + led: 2 + name: TM1638Led2 + + - platform: tm1638 + id: Led3 + led: 3 + name: TM1638Led3 + +display: + - platform: tm1638 + id: primarydisplay + stb_pin: 5 #TM1638 STB + clk_pin: 18 #TM1638 CLK + dio_pin: 23 #TM1638 DIO + update_interval: 5s + intensity: 5 + lambda: |- + it.print("81818181"); + +time: + - platform: pcf85063 + - platform: pcf8563 + +text_sensor: + - platform: ezo_pmp + dosing_mode: + name: Dosing Mode + calibration_status: + name: Calibration Status + on_value: + - ezo_pmp.dose_volume: + id: hcl_pump + volume: 10 + - ezo_pmp.dose_volume_over_time: + id: hcl_pump + volume: 10 + duration: 2 + - ezo_pmp.dose_with_constant_flow_rate: + id: hcl_pump + volume_per_minute: 10 + duration: 2 + - ezo_pmp.set_calibration_volume: + id: hcl_pump + volume: 10 + - ezo_pmp.find: hcl_pump + - ezo_pmp.dose_continuously: hcl_pump + - ezo_pmp.clear_total_volume_dosed: hcl_pump + - ezo_pmp.clear_calibration: hcl_pump + - ezo_pmp.pause_dosing: hcl_pump + - ezo_pmp.stop_dosing: hcl_pump + - ezo_pmp.arbitrary_command: + id: hcl_pump + command: D,? + +sn74hc165: + id: sn74hc165_hub + data_pin: GPIO12 + clock_pin: GPIO14 + load_pin: GPIO27 + clock_inhibit_pin: GPIO26 + sr_count: 4 + +matrix_keypad: + id: keypad + rows: + - pin: 21 + - pin: 19 + columns: + - pin: 17 + - pin: 16 + keys: "1234" + +key_collector: + - id: reader + source_id: keypad + min_length: 4 + max_length: 4 + +light: + - platform: esp32_rmt_led_strip + id: led_strip + pin: 13 + num_leds: 60 + rmt_channel: 6 + rgb_order: GRB + chipset: ws2812 + - platform: esp32_rmt_led_strip + id: led_strip2 + pin: 15 + num_leds: 60 + rmt_channel: 2 + rgb_order: RGB + bit0_high: 100us + bit0_low: 100us + bit1_high: 100us + bit1_low: 100us diff --git a/tests/test5.yaml b/tests/test5.yaml index 417f3bfecd..274570aad6 100644 --- a/tests/test5.yaml +++ b/tests/test5.yaml @@ -392,6 +392,12 @@ select: "Three": 3 sensor: + - platform: adc + id: adc_sensor_p32 + name: ADC pin 32 + pin: 32 + attenuation: 11db + update_interval: 1s - platform: internal_temperature name: Internal Temperature - platform: selec_meter From 68a2c45edf01e426f26350693d91669696481ab1 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 13 Sep 2023 13:05:06 +1200 Subject: [PATCH 111/111] Bump version to 2023.9.0b1 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index bbc6e71885..cbb31e0ec6 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2023.9.0-dev" +__version__ = "2023.9.0b1" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = (