1
0
mirror of https://github.com/esphome/esphome.git synced 2026-02-11 02:02:09 +00:00

Compare commits

..

6 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
c4f265796c Fix preprocessor guards and update test to follow repo patterns
Co-authored-by: jesserockz <3060199+jesserockz@users.noreply.github.com>
2025-11-27 01:38:44 +00:00
pre-commit-ci-lite[bot]
2c16d6eb9a [pre-commit.ci lite] apply automatic fixes 2025-11-27 01:12:06 +00:00
Jesse Hills
f32ecb73db Merge branch 'dev' into copilot/fix-10045 2025-11-27 14:10:33 +13:00
copilot-swe-agent[bot]
484087f780 Fix seeed_mr24hpc1 compilation by adding comprehensive entity guards and renaming test file
Co-authored-by: jesserockz <3060199+jesserockz@users.noreply.github.com>
2025-08-04 06:57:56 +00:00
copilot-swe-agent[bot]
d91eed1411 Fix seeed_mr24hpc1 compilation on ESP8266 by adding USE_SELECT guards
Co-authored-by: jesserockz <3060199+jesserockz@users.noreply.github.com>
2025-08-04 06:06:31 +00:00
copilot-swe-agent[bot]
47714a1745 Initial plan 2025-08-04 05:53:18 +00:00
7 changed files with 253 additions and 249 deletions

View File

@@ -37,7 +37,6 @@ from esphome.const import (
__version__,
)
from esphome.core import CORE, HexInt, TimePeriod
from esphome.coroutine import CoroPriority, coroutine_with_priority
import esphome.final_validate as fv
from esphome.helpers import copy_file_if_changed, write_file_if_changed
from esphome.types import ConfigType
@@ -263,32 +262,15 @@ def add_idf_component(
"deprecated and will be removed in ESPHome 2026.1. If you are seeing this, report "
"an issue to the external_component author and ask them to update it."
)
components_registry = CORE.data[KEY_ESP32][KEY_COMPONENTS]
if components:
for comp in components:
existing = components_registry.get(comp)
if existing and existing.get(KEY_REF) != ref:
_LOGGER.warning(
"IDF component %s version conflict %s replaced by %s",
comp,
existing.get(KEY_REF),
ref,
)
components_registry[comp] = {
CORE.data[KEY_ESP32][KEY_COMPONENTS][comp] = {
KEY_REPO: repo,
KEY_REF: ref,
KEY_PATH: f"{path}/{comp}" if path else comp,
}
else:
existing = components_registry.get(name)
if existing and existing.get(KEY_REF) != ref:
_LOGGER.warning(
"IDF component %s version conflict %s replaced by %s",
name,
existing.get(KEY_REF),
ref,
)
components_registry[name] = {
CORE.data[KEY_ESP32][KEY_COMPONENTS][name] = {
KEY_REPO: repo,
KEY_REF: ref,
KEY_PATH: path,
@@ -610,14 +592,6 @@ def require_vfs_dir() -> None:
CORE.data[KEY_VFS_DIR_REQUIRED] = True
def _parse_idf_component(value: str) -> ConfigType:
"""Parse IDF component shorthand syntax like 'owner/component^version'"""
if "^" not in value:
raise cv.Invalid(f"Invalid IDF component shorthand '{value}'")
name, ref = value.split("^", 1)
return {CONF_NAME: name, CONF_REF: ref}
def _validate_idf_component(config: ConfigType) -> ConfigType:
"""Validate IDF component config and warn about deprecated options."""
if CONF_REFRESH in config:
@@ -685,19 +659,14 @@ FRAMEWORK_SCHEMA = cv.Schema(
),
cv.Optional(CONF_COMPONENTS, default=[]): cv.ensure_list(
cv.All(
cv.Any(
cv.All(cv.string_strict, _parse_idf_component),
cv.Schema(
{
cv.Required(CONF_NAME): cv.string_strict,
cv.Optional(CONF_SOURCE): cv.git_ref,
cv.Optional(CONF_REF): cv.string,
cv.Optional(CONF_PATH): cv.string,
cv.Optional(CONF_REFRESH): cv.All(
cv.string, cv.source_refresh
),
}
),
cv.Schema(
{
cv.Required(CONF_NAME): cv.string_strict,
cv.Optional(CONF_SOURCE): cv.git_ref,
cv.Optional(CONF_REF): cv.string,
cv.Optional(CONF_PATH): cv.string,
cv.Optional(CONF_REFRESH): cv.All(cv.string, cv.source_refresh),
}
),
_validate_idf_component,
)
@@ -882,18 +851,6 @@ def _configure_lwip_max_sockets(conf: dict) -> None:
add_idf_sdkconfig_option("CONFIG_LWIP_MAX_SOCKETS", max_sockets)
@coroutine_with_priority(CoroPriority.FINAL)
async def _add_yaml_idf_components(components: list[ConfigType]):
"""Add IDF components from YAML config with final priority to override code-added components."""
for component in components:
add_idf_component(
name=component[CONF_NAME],
repo=component.get(CONF_SOURCE),
ref=component.get(CONF_REF),
path=component.get(CONF_PATH),
)
async def to_code(config):
cg.add_platformio_option("board", config[CONF_BOARD])
cg.add_platformio_option("board_upload.flash_size", config[CONF_FLASH_SIZE])
@@ -1140,10 +1097,13 @@ async def to_code(config):
for name, value in conf[CONF_SDKCONFIG_OPTIONS].items():
add_idf_sdkconfig_option(name, RawSdkconfigValue(value))
# Components from YAML are added in a separate coroutine with FINAL priority
# Schedule it to run after all other components
if conf[CONF_COMPONENTS]:
CORE.add_job(_add_yaml_idf_components, conf[CONF_COMPONENTS])
for component in conf[CONF_COMPONENTS]:
add_idf_component(
name=component[CONF_NAME],
repo=component.get(CONF_SOURCE),
ref=component.get(CONF_REF),
path=component.get(CONF_PATH),
)
APP_PARTITION_SIZES = {

View File

@@ -64,15 +64,21 @@ void MR24HPC1Component::dump_config() {
void MR24HPC1Component::setup() {
this->check_uart_settings(115200);
#ifdef USE_NUMBER
if (this->custom_mode_number_ != nullptr) {
this->custom_mode_number_->publish_state(0); // Zero out the custom mode
}
#endif
#ifdef USE_SENSOR
if (this->custom_mode_num_sensor_ != nullptr) {
this->custom_mode_num_sensor_->publish_state(0);
}
#endif
#ifdef USE_TEXT_SENSOR
if (this->custom_mode_end_text_sensor_ != nullptr) {
this->custom_mode_end_text_sensor_->publish_state("Not in custom mode");
}
#endif
this->set_custom_end_mode();
this->poll_time_base_func_check_ = true;
this->check_dev_inf_sign_ = true;
@@ -352,6 +358,7 @@ void MR24HPC1Component::r24_split_data_frame_(uint8_t value) {
}
// Parses data frames related to product information
#ifdef USE_TEXT_SENSOR
void MR24HPC1Component::r24_frame_parse_product_information_(uint8_t *data) {
uint16_t product_len = encode_uint16(data[FRAME_COMMAND_WORD_INDEX + 1], data[FRAME_COMMAND_WORD_INDEX + 2]);
if (data[FRAME_COMMAND_WORD_INDEX] == COMMAND_PRODUCT_MODE) {
@@ -389,20 +396,29 @@ void MR24HPC1Component::r24_frame_parse_product_information_(uint8_t *data) {
}
}
}
#endif
// Parsing the underlying open parameters
void MR24HPC1Component::r24_frame_parse_open_underlying_information_(uint8_t *data) {
if (data[FRAME_COMMAND_WORD_INDEX] == 0x00) {
uint8_t cmd = data[FRAME_COMMAND_WORD_INDEX];
if (cmd == 0x00) {
#ifdef USE_SWITCH
if (this->underlying_open_function_switch_ != nullptr) {
this->underlying_open_function_switch_->publish_state(
data[FRAME_DATA_INDEX]); // Underlying Open Parameter Switch Status Updates
}
#endif
if (data[FRAME_DATA_INDEX]) {
this->s_output_info_switch_flag_ = OUTPUT_SWITCH_ON;
} else {
this->s_output_info_switch_flag_ = OUTPUT_SWTICH_OFF;
}
} else if (data[FRAME_COMMAND_WORD_INDEX] == 0x01) {
return;
}
if (cmd == 0x01) {
#ifdef USE_SENSOR
if (this->custom_spatial_static_value_sensor_ != nullptr) {
this->custom_spatial_static_value_sensor_->publish_state(data[FRAME_DATA_INDEX]);
}
@@ -418,72 +434,125 @@ void MR24HPC1Component::r24_frame_parse_open_underlying_information_(uint8_t *da
if (this->custom_motion_speed_sensor_ != nullptr) {
this->custom_motion_speed_sensor_->publish_state((data[FRAME_DATA_INDEX + 4] - 10) * 0.5f);
}
} else if ((data[FRAME_COMMAND_WORD_INDEX] == 0x06) || (data[FRAME_COMMAND_WORD_INDEX] == 0x86)) {
#endif
return;
}
if (cmd == 0x06 || cmd == 0x86) {
// none:0x00 close_to:0x01 far_away:0x02
#ifdef USE_TEXT_SENSOR
if ((this->keep_away_text_sensor_ != nullptr) && (data[FRAME_DATA_INDEX] < 3)) {
this->keep_away_text_sensor_->publish_state(S_KEEP_AWAY_STR[data[FRAME_DATA_INDEX]]);
}
} else if ((this->movement_signs_sensor_ != nullptr) &&
((data[FRAME_COMMAND_WORD_INDEX] == 0x07) || (data[FRAME_COMMAND_WORD_INDEX] == 0x87))) {
#endif
return;
}
#ifdef USE_SENSOR
if ((cmd == 0x07 || cmd == 0x87) && this->movement_signs_sensor_ != nullptr) {
this->movement_signs_sensor_->publish_state(data[FRAME_DATA_INDEX]);
} else if ((this->existence_threshold_number_ != nullptr) &&
((data[FRAME_COMMAND_WORD_INDEX] == 0x08) || (data[FRAME_COMMAND_WORD_INDEX] == 0x88))) {
return;
}
#endif
#ifdef USE_NUMBER
if ((cmd == 0x08 || cmd == 0x88) && this->existence_threshold_number_ != nullptr) {
this->existence_threshold_number_->publish_state(data[FRAME_DATA_INDEX]);
} else if ((this->motion_threshold_number_ != nullptr) &&
((data[FRAME_COMMAND_WORD_INDEX] == 0x09) || (data[FRAME_COMMAND_WORD_INDEX] == 0x89))) {
return;
}
if ((cmd == 0x09 || cmd == 0x89) && this->motion_threshold_number_ != nullptr) {
this->motion_threshold_number_->publish_state(data[FRAME_DATA_INDEX]);
} else if ((this->existence_boundary_select_ != nullptr) &&
((data[FRAME_COMMAND_WORD_INDEX] == 0x0a) || (data[FRAME_COMMAND_WORD_INDEX] == 0x8a))) {
return;
}
#endif
#ifdef USE_SELECT
if ((cmd == 0x0a || cmd == 0x8a) && this->existence_boundary_select_ != nullptr) {
if (this->existence_boundary_select_->has_index(data[FRAME_DATA_INDEX] - 1)) {
this->existence_boundary_select_->publish_state(data[FRAME_DATA_INDEX] - 1);
}
} else if ((this->motion_boundary_select_ != nullptr) &&
((data[FRAME_COMMAND_WORD_INDEX] == 0x0b) || (data[FRAME_COMMAND_WORD_INDEX] == 0x8b))) {
return;
}
if ((cmd == 0x0b || cmd == 0x8b) && this->motion_boundary_select_ != nullptr) {
if (this->motion_boundary_select_->has_index(data[FRAME_DATA_INDEX] - 1)) {
this->motion_boundary_select_->publish_state(data[FRAME_DATA_INDEX] - 1);
}
} else if ((this->motion_trigger_number_ != nullptr) &&
((data[FRAME_COMMAND_WORD_INDEX] == 0x0c) || (data[FRAME_COMMAND_WORD_INDEX] == 0x8c))) {
return;
}
#endif
#ifdef USE_NUMBER
if ((cmd == 0x0c || cmd == 0x8c) && this->motion_trigger_number_ != nullptr) {
uint32_t motion_trigger_time = encode_uint32(data[FRAME_DATA_INDEX], data[FRAME_DATA_INDEX + 1],
data[FRAME_DATA_INDEX + 2], data[FRAME_DATA_INDEX + 3]);
this->motion_trigger_number_->publish_state(motion_trigger_time);
} else if ((this->motion_to_rest_number_ != nullptr) &&
((data[FRAME_COMMAND_WORD_INDEX] == 0x0d) || (data[FRAME_COMMAND_WORD_INDEX] == 0x8d))) {
return;
}
if ((cmd == 0x0d || cmd == 0x8d) && this->motion_to_rest_number_ != nullptr) {
uint32_t move_to_rest_time = encode_uint32(data[FRAME_DATA_INDEX], data[FRAME_DATA_INDEX + 1],
data[FRAME_DATA_INDEX + 2], data[FRAME_DATA_INDEX + 3]);
this->motion_to_rest_number_->publish_state(move_to_rest_time);
} else if ((this->custom_unman_time_number_ != nullptr) &&
((data[FRAME_COMMAND_WORD_INDEX] == 0x0e) || (data[FRAME_COMMAND_WORD_INDEX] == 0x8e))) {
return;
}
if ((cmd == 0x0e || cmd == 0x8e) && this->custom_unman_time_number_ != nullptr) {
uint32_t enter_unmanned_time = encode_uint32(data[FRAME_DATA_INDEX], data[FRAME_DATA_INDEX + 1],
data[FRAME_DATA_INDEX + 2], data[FRAME_DATA_INDEX + 3]);
float custom_unmanned_time = enter_unmanned_time / 1000.0;
this->custom_unman_time_number_->publish_state(custom_unmanned_time);
} else if (data[FRAME_COMMAND_WORD_INDEX] == 0x80) {
return;
}
#endif
if (cmd == 0x80) {
if (data[FRAME_DATA_INDEX]) {
this->s_output_info_switch_flag_ = OUTPUT_SWITCH_ON;
} else {
this->s_output_info_switch_flag_ = OUTPUT_SWTICH_OFF;
}
#ifdef USE_SWITCH
if (this->underlying_open_function_switch_ != nullptr) {
this->underlying_open_function_switch_->publish_state(data[FRAME_DATA_INDEX]);
}
} else if ((this->custom_spatial_static_value_sensor_ != nullptr) && (data[FRAME_COMMAND_WORD_INDEX] == 0x81)) {
this->custom_spatial_static_value_sensor_->publish_state(data[FRAME_DATA_INDEX]);
} else if ((this->custom_spatial_motion_value_sensor_ != nullptr) && (data[FRAME_COMMAND_WORD_INDEX] == 0x82)) {
this->custom_spatial_motion_value_sensor_->publish_state(data[FRAME_DATA_INDEX]);
} else if ((this->custom_presence_of_detection_sensor_ != nullptr) && (data[FRAME_COMMAND_WORD_INDEX] == 0x83)) {
this->custom_presence_of_detection_sensor_->publish_state(
S_PRESENCE_OF_DETECTION_RANGE_STR[data[FRAME_DATA_INDEX]]);
} else if ((this->custom_motion_distance_sensor_ != nullptr) && (data[FRAME_COMMAND_WORD_INDEX] == 0x84)) {
this->custom_motion_distance_sensor_->publish_state(data[FRAME_DATA_INDEX] * 0.5f);
} else if ((this->custom_motion_speed_sensor_ != nullptr) && (data[FRAME_COMMAND_WORD_INDEX] == 0x85)) {
this->custom_motion_speed_sensor_->publish_state((data[FRAME_DATA_INDEX] - 10) * 0.5f);
#endif
return;
}
#ifdef USE_SENSOR
if (cmd == 0x81 && this->custom_spatial_static_value_sensor_ != nullptr) {
this->custom_spatial_static_value_sensor_->publish_state(data[FRAME_DATA_INDEX]);
return;
}
if (cmd == 0x82 && this->custom_spatial_motion_value_sensor_ != nullptr) {
this->custom_spatial_motion_value_sensor_->publish_state(data[FRAME_DATA_INDEX]);
return;
}
if (cmd == 0x83 && this->custom_presence_of_detection_sensor_ != nullptr) {
this->custom_presence_of_detection_sensor_->publish_state(S_PRESENCE_OF_DETECTION_RANGE_STR[data[FRAME_DATA_INDEX]]);
return;
}
if (cmd == 0x84 && this->custom_motion_distance_sensor_ != nullptr) {
this->custom_motion_distance_sensor_->publish_state(data[FRAME_DATA_INDEX] * 0.5f);
return;
}
if (cmd == 0x85 && this->custom_motion_speed_sensor_ != nullptr) {
this->custom_motion_speed_sensor_->publish_state((data[FRAME_DATA_INDEX] - 10) * 0.5f);
return;
}
#endif
}
void MR24HPC1Component::r24_parse_data_frame_(uint8_t *data, uint8_t len) {
switch (data[FRAME_CONTROL_WORD_INDEX]) {
case 0x01: {
#ifdef USE_TEXT_SENSOR
if ((this->heartbeat_state_text_sensor_ != nullptr) && (data[FRAME_COMMAND_WORD_INDEX] == 0x01)) {
this->heartbeat_state_text_sensor_->publish_state("Equipment Normal");
} else if (data[FRAME_COMMAND_WORD_INDEX] == 0x02) {
@@ -491,9 +560,16 @@ void MR24HPC1Component::r24_parse_data_frame_(uint8_t *data, uint8_t len) {
} else if (this->heartbeat_state_text_sensor_ != nullptr) {
this->heartbeat_state_text_sensor_->publish_state("Equipment Abnormal");
}
#else
if (data[FRAME_COMMAND_WORD_INDEX] == 0x02) {
ESP_LOGD(TAG, "Reply: query restart packet");
}
#endif
} break;
case 0x02: {
#ifdef USE_TEXT_SENSOR
this->r24_frame_parse_product_information_(data);
#endif
} break;
case 0x05: {
this->r24_frame_parse_work_status_(data);
@@ -511,87 +587,152 @@ void MR24HPC1Component::r24_parse_data_frame_(uint8_t *data, uint8_t len) {
}
void MR24HPC1Component::r24_frame_parse_work_status_(uint8_t *data) {
if (data[FRAME_COMMAND_WORD_INDEX] == 0x01) {
uint8_t cmd = data[FRAME_COMMAND_WORD_INDEX];
if (cmd == 0x01) {
ESP_LOGD(TAG, "Reply: get radar init status 0x%02X", data[FRAME_DATA_INDEX]);
} else if (data[FRAME_COMMAND_WORD_INDEX] == 0x07) {
return;
}
if (cmd == 0x07) {
#ifdef USE_SELECT
if ((this->scene_mode_select_ != nullptr) && (this->scene_mode_select_->has_index(data[FRAME_DATA_INDEX]))) {
this->scene_mode_select_->publish_state(data[FRAME_DATA_INDEX]);
} else {
ESP_LOGD(TAG, "Select has index offset %d Error", data[FRAME_DATA_INDEX]);
}
} else if ((this->sensitivity_number_ != nullptr) &&
((data[FRAME_COMMAND_WORD_INDEX] == 0x08) || (data[FRAME_COMMAND_WORD_INDEX] == 0x88))) {
#endif
return;
}
#ifdef USE_NUMBER
if ((cmd == 0x08 || cmd == 0x88) && this->sensitivity_number_ != nullptr) {
// 1-3
this->sensitivity_number_->publish_state(data[FRAME_DATA_INDEX]);
} else if (data[FRAME_COMMAND_WORD_INDEX] == 0x09) {
return;
}
#endif
if (cmd == 0x09) {
// 1-4
#ifdef USE_SENSOR
if (this->custom_mode_num_sensor_ != nullptr) {
this->custom_mode_num_sensor_->publish_state(data[FRAME_DATA_INDEX]);
}
#endif
#ifdef USE_NUMBER
if (this->custom_mode_number_ != nullptr) {
this->custom_mode_number_->publish_state(0);
}
#endif
#ifdef USE_TEXT_SENSOR
if (this->custom_mode_end_text_sensor_ != nullptr) {
this->custom_mode_end_text_sensor_->publish_state("Setup in progress");
}
} else if (data[FRAME_COMMAND_WORD_INDEX] == 0x81) {
#endif
return;
}
if (cmd == 0x81) {
ESP_LOGD(TAG, "Reply: get radar init status 0x%02X", data[FRAME_DATA_INDEX]);
} else if (data[FRAME_COMMAND_WORD_INDEX] == 0x87) {
return;
}
if (cmd == 0x87) {
#ifdef USE_SELECT
if ((this->scene_mode_select_ != nullptr) && (this->scene_mode_select_->has_index(data[FRAME_DATA_INDEX]))) {
this->scene_mode_select_->publish_state(data[FRAME_DATA_INDEX]);
} else {
ESP_LOGD(TAG, "Select has index offset %d Error", data[FRAME_DATA_INDEX]);
}
} else if ((this->custom_mode_end_text_sensor_ != nullptr) && (data[FRAME_COMMAND_WORD_INDEX] == 0x0A)) {
#endif
return;
}
#ifdef USE_TEXT_SENSOR
if (cmd == 0x0A && this->custom_mode_end_text_sensor_ != nullptr) {
this->custom_mode_end_text_sensor_->publish_state("Set Success!");
} else if (data[FRAME_COMMAND_WORD_INDEX] == 0x89) {
return;
}
#endif
if (cmd == 0x89) {
if (data[FRAME_DATA_INDEX] == 0) {
#ifdef USE_TEXT_SENSOR
if (this->custom_mode_end_text_sensor_ != nullptr) {
this->custom_mode_end_text_sensor_->publish_state("Not in custom mode");
}
#endif
#ifdef USE_NUMBER
if (this->custom_mode_number_ != nullptr) {
this->custom_mode_number_->publish_state(0);
}
#endif
#ifdef USE_SENSOR
if (this->custom_mode_num_sensor_ != nullptr) {
this->custom_mode_num_sensor_->publish_state(data[FRAME_DATA_INDEX]);
}
#endif
} else {
#ifdef USE_SENSOR
if (this->custom_mode_num_sensor_ != nullptr) {
this->custom_mode_num_sensor_->publish_state(data[FRAME_DATA_INDEX]);
}
#endif
}
} else {
ESP_LOGD(TAG, "[%s] No found COMMAND_WORD(%02X) in Frame", __FUNCTION__, data[FRAME_COMMAND_WORD_INDEX]);
return;
}
ESP_LOGD(TAG, "[%s] No found COMMAND_WORD(%02X) in Frame", __FUNCTION__, cmd);
}
void MR24HPC1Component::r24_frame_parse_human_information_(uint8_t *data) {
if ((this->has_target_binary_sensor_ != nullptr) &&
((data[FRAME_COMMAND_WORD_INDEX] == 0x01) || (data[FRAME_COMMAND_WORD_INDEX] == 0x81))) {
uint8_t cmd = data[FRAME_COMMAND_WORD_INDEX];
#ifdef USE_BINARY_SENSOR
if ((cmd == 0x01 || cmd == 0x81) && this->has_target_binary_sensor_ != nullptr) {
this->has_target_binary_sensor_->publish_state(S_SOMEONE_EXISTS_STR[data[FRAME_DATA_INDEX]]);
} else if ((this->motion_status_text_sensor_ != nullptr) &&
((data[FRAME_COMMAND_WORD_INDEX] == 0x02) || (data[FRAME_COMMAND_WORD_INDEX] == 0x82))) {
return;
}
#endif
#ifdef USE_TEXT_SENSOR
if ((cmd == 0x02 || cmd == 0x82) && this->motion_status_text_sensor_ != nullptr) {
if (data[FRAME_DATA_INDEX] < 3) {
this->motion_status_text_sensor_->publish_state(S_MOTION_STATUS_STR[data[FRAME_DATA_INDEX]]);
}
} else if ((this->movement_signs_sensor_ != nullptr) &&
((data[FRAME_COMMAND_WORD_INDEX] == 0x03) || (data[FRAME_COMMAND_WORD_INDEX] == 0x83))) {
return;
}
#endif
#ifdef USE_SENSOR
if ((cmd == 0x03 || cmd == 0x83) && this->movement_signs_sensor_ != nullptr) {
this->movement_signs_sensor_->publish_state(data[FRAME_DATA_INDEX]);
} else if ((this->unman_time_select_ != nullptr) &&
((data[FRAME_COMMAND_WORD_INDEX] == 0x0A) || (data[FRAME_COMMAND_WORD_INDEX] == 0x8A))) {
return;
}
#endif
#ifdef USE_SELECT
if ((cmd == 0x0A || cmd == 0x8A) && this->unman_time_select_ != nullptr) {
// none:0x00 1s:0x01 30s:0x02 1min:0x03 2min:0x04 5min:0x05 10min:0x06 30min:0x07 1hour:0x08
if (data[FRAME_DATA_INDEX] < 9) {
this->unman_time_select_->publish_state(data[FRAME_DATA_INDEX]);
}
} else if ((this->keep_away_text_sensor_ != nullptr) &&
((data[FRAME_COMMAND_WORD_INDEX] == 0x0B) || (data[FRAME_COMMAND_WORD_INDEX] == 0x8B))) {
return;
}
#endif
#ifdef USE_TEXT_SENSOR
if ((cmd == 0x0B || cmd == 0x8B) && this->keep_away_text_sensor_ != nullptr) {
// none:0x00 close_to:0x01 far_away:0x02
if (data[FRAME_DATA_INDEX] < 3) {
this->keep_away_text_sensor_->publish_state(S_KEEP_AWAY_STR[data[FRAME_DATA_INDEX]]);
}
} else {
ESP_LOGD(TAG, "[%s] No found COMMAND_WORD(%02X) in Frame", __FUNCTION__, data[FRAME_COMMAND_WORD_INDEX]);
return;
}
#endif
ESP_LOGD(TAG, "[%s] No found COMMAND_WORD(%02X) in Frame", __FUNCTION__, cmd);
}
// Sending data frames
@@ -695,12 +836,15 @@ void MR24HPC1Component::set_underlying_open_function(bool enable) {
} else {
this->send_query_(UNDERLYING_SWITCH_OFF, sizeof(UNDERLYING_SWITCH_OFF));
}
#ifdef USE_TEXT_SENSOR
if (this->keep_away_text_sensor_ != nullptr) {
this->keep_away_text_sensor_->publish_state("");
}
if (this->motion_status_text_sensor_ != nullptr) {
this->motion_status_text_sensor_->publish_state("");
}
#endif
#ifdef USE_SENSOR
if (this->custom_spatial_static_value_sensor_ != nullptr) {
this->custom_spatial_static_value_sensor_->publish_state(NAN);
}
@@ -716,6 +860,7 @@ void MR24HPC1Component::set_underlying_open_function(bool enable) {
if (this->custom_motion_speed_sensor_ != nullptr) {
this->custom_motion_speed_sensor_->publish_state(NAN);
}
#endif
}
void MR24HPC1Component::set_scene_mode(uint8_t value) {
@@ -723,12 +868,16 @@ void MR24HPC1Component::set_scene_mode(uint8_t value) {
uint8_t send_data[10] = {0x53, 0x59, 0x05, 0x07, 0x00, 0x01, value, 0x00, 0x54, 0x43};
send_data[7] = get_frame_crc_sum(send_data, send_data_len);
this->send_query_(send_data, send_data_len);
#ifdef USE_NUMBER
if (this->custom_mode_number_ != nullptr) {
this->custom_mode_number_->publish_state(0);
}
#endif
#ifdef USE_SENSOR
if (this->custom_mode_num_sensor_ != nullptr) {
this->custom_mode_num_sensor_->publish_state(0);
}
#endif
this->get_scene_mode();
this->get_sensitivity();
this->get_custom_mode();
@@ -768,9 +917,11 @@ void MR24HPC1Component::set_unman_time(uint8_t value) {
void MR24HPC1Component::set_custom_mode(uint8_t mode) {
if (mode == 0) {
this->set_custom_end_mode(); // Equivalent to end setting
#ifdef USE_NUMBER
if (this->custom_mode_number_ != nullptr) {
this->custom_mode_number_->publish_state(0);
}
#endif
return;
}
uint8_t send_data_len = 10;
@@ -793,9 +944,11 @@ void MR24HPC1Component::set_custom_end_mode() {
uint8_t send_data_len = 10;
uint8_t send_data[10] = {0x53, 0x59, 0x05, 0x0a, 0x00, 0x01, 0x0F, 0xCB, 0x54, 0x43};
this->send_query_(send_data, send_data_len);
#ifdef USE_NUMBER
if (this->custom_mode_number_ != nullptr) {
this->custom_mode_number_->publish_state(0); // Clear setpoints
}
#endif
this->get_existence_boundary();
this->get_motion_boundary();
this->get_existence_threshold();
@@ -809,8 +962,10 @@ void MR24HPC1Component::set_custom_end_mode() {
}
void MR24HPC1Component::set_existence_boundary(uint8_t value) {
#ifdef USE_SENSOR
if ((this->custom_mode_num_sensor_ != nullptr) && (this->custom_mode_num_sensor_->state == 0))
return; // You'll have to check that you're in custom mode to set it up
#endif
uint8_t send_data_len = 10;
uint8_t send_data[10] = {0x53, 0x59, 0x08, 0x0A, 0x00, 0x01, (uint8_t) (value + 1), 0x00, 0x54, 0x43};
send_data[7] = get_frame_crc_sum(send_data, send_data_len);
@@ -819,8 +974,10 @@ void MR24HPC1Component::set_existence_boundary(uint8_t value) {
}
void MR24HPC1Component::set_motion_boundary(uint8_t value) {
#ifdef USE_SENSOR
if ((this->custom_mode_num_sensor_ != nullptr) && (this->custom_mode_num_sensor_->state == 0))
return; // You'll have to check that you're in custom mode to set it up
#endif
uint8_t send_data_len = 10;
uint8_t send_data[10] = {0x53, 0x59, 0x08, 0x0B, 0x00, 0x01, (uint8_t) (value + 1), 0x00, 0x54, 0x43};
send_data[7] = get_frame_crc_sum(send_data, send_data_len);
@@ -829,8 +986,10 @@ void MR24HPC1Component::set_motion_boundary(uint8_t value) {
}
void MR24HPC1Component::set_existence_threshold(uint8_t value) {
#ifdef USE_SENSOR
if ((this->custom_mode_num_sensor_ != nullptr) && (this->custom_mode_num_sensor_->state == 0))
return; // You'll have to check that you're in custom mode to set it up
#endif
uint8_t send_data_len = 10;
uint8_t send_data[10] = {0x53, 0x59, 0x08, 0x08, 0x00, 0x01, value, 0x00, 0x54, 0x43};
send_data[7] = get_frame_crc_sum(send_data, send_data_len);
@@ -839,8 +998,10 @@ void MR24HPC1Component::set_existence_threshold(uint8_t value) {
}
void MR24HPC1Component::set_motion_threshold(uint8_t value) {
#ifdef USE_SENSOR
if ((this->custom_mode_num_sensor_ != nullptr) && (this->custom_mode_num_sensor_->state == 0))
return; // You'll have to check that you're in custom mode to set it up
#endif
uint8_t send_data_len = 10;
uint8_t send_data[10] = {0x53, 0x59, 0x08, 0x09, 0x00, 0x01, value, 0x00, 0x54, 0x43};
send_data[7] = get_frame_crc_sum(send_data, send_data_len);
@@ -849,8 +1010,10 @@ void MR24HPC1Component::set_motion_threshold(uint8_t value) {
}
void MR24HPC1Component::set_motion_trigger_time(uint8_t value) {
#ifdef USE_SENSOR
if ((this->custom_mode_num_sensor_ != nullptr) && (this->custom_mode_num_sensor_->state == 0))
return; // You'll have to check that you're in custom mode to set it up
#endif
uint8_t send_data_len = 13;
uint8_t send_data[13] = {0x53, 0x59, 0x08, 0x0C, 0x00, 0x04, 0x00, 0x00, 0x00, value, 0x00, 0x54, 0x43};
send_data[10] = get_frame_crc_sum(send_data, send_data_len);
@@ -859,8 +1022,10 @@ void MR24HPC1Component::set_motion_trigger_time(uint8_t value) {
}
void MR24HPC1Component::set_motion_to_rest_time(uint16_t value) {
#ifdef USE_SENSOR
if ((this->custom_mode_num_sensor_ != nullptr) && (this->custom_mode_num_sensor_->state == 0))
return; // You'll have to check that you're in custom mode to set it up
#endif
uint8_t h8_num = (value >> 8) & 0xff;
uint8_t l8_num = value & 0xff;
uint8_t send_data_len = 13;
@@ -871,8 +1036,10 @@ void MR24HPC1Component::set_motion_to_rest_time(uint16_t value) {
}
void MR24HPC1Component::set_custom_unman_time(uint16_t value) {
#ifdef USE_SENSOR
if ((this->custom_mode_num_sensor_ != nullptr) && (this->custom_mode_num_sensor_->state == 0))
return; // You'll have to check that you're in custom mode to set it up
#endif
uint32_t value_ms = value * 1000;
uint8_t h24_num = (value_ms >> 24) & 0xff;
uint8_t h16_num = (value_ms >> 16) & 0xff;

View File

@@ -160,7 +160,9 @@ class MR24HPC1Component : public Component,
void r24_parse_data_frame_(uint8_t *data, uint8_t len);
void r24_frame_parse_open_underlying_information_(uint8_t *data);
void r24_frame_parse_work_status_(uint8_t *data);
#ifdef USE_TEXT_SENSOR
void r24_frame_parse_product_information_(uint8_t *data);
#endif
void r24_frame_parse_human_information_(uint8_t *data);
void send_query_(const uint8_t *query, size_t string_length);

View File

@@ -1,12 +1,8 @@
from collections.abc import Callable
import importlib
import logging
import os
from pathlib import Path
import re
import shutil
import stat
from types import TracebackType
from esphome import loader
from esphome.config import iter_component_configs, iter_components
@@ -305,24 +301,9 @@ def clean_cmake_cache():
pioenvs_cmake_path.unlink()
def _rmtree_error_handler(
func: Callable[[str], object],
path: str,
exc_info: tuple[type[BaseException], BaseException, TracebackType | None],
) -> None:
"""Error handler for shutil.rmtree to handle read-only files on Windows.
On Windows, git pack files and other files may be marked read-only,
causing shutil.rmtree to fail with "Access is denied". This handler
removes the read-only flag and retries the deletion.
"""
if os.access(path, os.W_OK):
raise exc_info[1].with_traceback(exc_info[2])
os.chmod(path, stat.S_IWUSR | stat.S_IRUSR)
func(path)
def clean_build(clear_pio_cache: bool = True):
import shutil
# Allow skipping cache cleaning for integration tests
if os.environ.get("ESPHOME_SKIP_CLEAN_BUILD"):
_LOGGER.warning("Skipping build cleaning (ESPHOME_SKIP_CLEAN_BUILD set)")
@@ -331,11 +312,11 @@ def clean_build(clear_pio_cache: bool = True):
pioenvs = CORE.relative_pioenvs_path()
if pioenvs.is_dir():
_LOGGER.info("Deleting %s", pioenvs)
shutil.rmtree(pioenvs, onerror=_rmtree_error_handler)
shutil.rmtree(pioenvs)
piolibdeps = CORE.relative_piolibdeps_path()
if piolibdeps.is_dir():
_LOGGER.info("Deleting %s", piolibdeps)
shutil.rmtree(piolibdeps, onerror=_rmtree_error_handler)
shutil.rmtree(piolibdeps)
dependencies_lock = CORE.relative_build_path("dependencies.lock")
if dependencies_lock.is_file():
_LOGGER.info("Deleting %s", dependencies_lock)
@@ -356,10 +337,12 @@ def clean_build(clear_pio_cache: bool = True):
cache_dir = Path(config.get("platformio", "cache_dir"))
if cache_dir.is_dir():
_LOGGER.info("Deleting PlatformIO cache %s", cache_dir)
shutil.rmtree(cache_dir, onerror=_rmtree_error_handler)
shutil.rmtree(cache_dir)
def clean_all(configuration: list[str]):
import shutil
data_dirs = []
for config in configuration:
item = Path(config)
@@ -381,7 +364,7 @@ def clean_all(configuration: list[str]):
if item.is_file() and not item.name.endswith(".json"):
item.unlink()
elif item.is_dir() and item.name != "storage":
shutil.rmtree(item, onerror=_rmtree_error_handler)
shutil.rmtree(item)
# Clean PlatformIO project files
try:
@@ -395,7 +378,7 @@ def clean_all(configuration: list[str]):
path = Path(config.get("platformio", pio_dir))
if path.is_dir():
_LOGGER.info("Deleting PlatformIO %s %s", pio_dir, path)
shutil.rmtree(path, onerror=_rmtree_error_handler)
shutil.rmtree(path)
GITIGNORE_CONTENT = """# Gitignore settings for ESPHome

View File

@@ -4,10 +4,6 @@ esp32:
cpu_frequency: 400MHz
framework:
type: esp-idf
components:
- espressif/mdns^1.8.2
- name: espressif/esp_hosted
ref: 2.6.6
advanced:
enable_idf_experimental_features: yes

View File

@@ -0,0 +1,4 @@
packages:
uart: !include ../../test_build_components/common/uart/esp8266-ard.yaml
<<: !include common.yaml

View File

@@ -1,9 +1,7 @@
"""Test writer module functionality."""
from collections.abc import Callable
import os
from pathlib import Path
import stat
from typing import Any
from unittest.mock import MagicMock, patch
@@ -1064,109 +1062,3 @@ def test_clean_all_preserves_json_files(
# Verify logging mentions cleaning
assert "Cleaning" in caplog.text
assert str(build_dir) in caplog.text
@patch("esphome.writer.CORE")
def test_clean_build_handles_readonly_files(
mock_core: MagicMock,
tmp_path: Path,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test clean_build handles read-only files (e.g., git pack files on Windows)."""
# Create directory structure with read-only files
pioenvs_dir = tmp_path / ".pioenvs"
pioenvs_dir.mkdir()
git_dir = pioenvs_dir / ".git" / "objects" / "pack"
git_dir.mkdir(parents=True)
# Create a read-only file (simulating git pack files on Windows)
readonly_file = git_dir / "pack-abc123.pack"
readonly_file.write_text("pack data")
os.chmod(readonly_file, stat.S_IRUSR) # Read-only
# Setup mocks
mock_core.relative_pioenvs_path.return_value = pioenvs_dir
mock_core.relative_piolibdeps_path.return_value = tmp_path / ".piolibdeps"
mock_core.relative_build_path.return_value = tmp_path / "dependencies.lock"
# Verify file is read-only
assert not os.access(readonly_file, os.W_OK)
# Call the function - should not crash
with caplog.at_level("INFO"):
clean_build()
# Verify directory was removed despite read-only files
assert not pioenvs_dir.exists()
@patch("esphome.writer.CORE")
def test_clean_all_handles_readonly_files(
mock_core: MagicMock,
tmp_path: Path,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test clean_all handles read-only files."""
from esphome.writer import clean_all
# Create config directory
config_dir = tmp_path / "config"
config_dir.mkdir()
build_dir = config_dir / ".esphome"
build_dir.mkdir()
# Create a subdirectory with read-only files
subdir = build_dir / "subdir"
subdir.mkdir()
readonly_file = subdir / "readonly.txt"
readonly_file.write_text("content")
os.chmod(readonly_file, stat.S_IRUSR) # Read-only
# Verify file is read-only
assert not os.access(readonly_file, os.W_OK)
# Call the function - should not crash
with caplog.at_level("INFO"):
clean_all([str(config_dir)])
# Verify directory was removed despite read-only files
assert not subdir.exists()
assert build_dir.exists() # .esphome dir itself is preserved
@patch("esphome.writer.CORE")
def test_clean_build_reraises_for_other_errors(
mock_core: MagicMock,
tmp_path: Path,
) -> None:
"""Test clean_build re-raises errors that are not read-only permission issues."""
# Create directory structure with a read-only subdirectory
# This prevents file deletion and triggers the error handler
pioenvs_dir = tmp_path / ".pioenvs"
pioenvs_dir.mkdir()
subdir = pioenvs_dir / "subdir"
subdir.mkdir()
test_file = subdir / "test.txt"
test_file.write_text("content")
# Make subdir read-only so files inside can't be deleted
os.chmod(subdir, stat.S_IRUSR | stat.S_IXUSR)
# Setup mocks
mock_core.relative_pioenvs_path.return_value = pioenvs_dir
mock_core.relative_piolibdeps_path.return_value = tmp_path / ".piolibdeps"
mock_core.relative_build_path.return_value = tmp_path / "dependencies.lock"
try:
# Mock os.access in writer module to return True (writable)
# This simulates a case where the error is NOT due to read-only permissions
# so the error handler should re-raise instead of trying to fix permissions
with (
patch("esphome.writer.os.access", return_value=True),
pytest.raises(PermissionError),
):
clean_build()
finally:
# Cleanup - restore write permission so tmp_path cleanup works
os.chmod(subdir, stat.S_IRWXU)