esphome 2024.11.3__py3-none-any.whl → 2024.12.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- esphome/components/adc/adc_sensor.h +7 -8
- esphome/components/adc/adc_sensor_common.cpp +24 -0
- esphome/components/adc/{adc_sensor.cpp → adc_sensor_esp32.cpp} +10 -179
- esphome/components/adc/adc_sensor_esp8266.cpp +58 -0
- esphome/components/adc/adc_sensor_libretiny.cpp +48 -0
- esphome/components/adc/adc_sensor_rp2040.cpp +93 -0
- esphome/components/alarm_control_panel/alarm_control_panel_call.cpp +3 -4
- esphome/components/animation/__init__.py +1 -2
- esphome/components/apds9306/apds9306.cpp +2 -1
- esphome/components/audio/audio.h +1 -1
- esphome/components/bk72xx/__init__.py +1 -1
- esphome/components/cse7766/cse7766.cpp +1 -1
- esphome/components/deep_sleep/deep_sleep_esp32.cpp +2 -2
- esphome/components/dht/dht.cpp +2 -1
- esphome/components/display/display.cpp +10 -6
- esphome/components/display/display.h +14 -0
- esphome/components/display_menu_base/__init__.py +0 -2
- esphome/components/display_menu_base/display_menu_base.cpp +1 -1
- esphome/components/dsmr/dsmr.cpp +1 -1
- esphome/components/esp32/__init__.py +94 -12
- esphome/components/esp32/boards.py +222 -14
- esphome/components/esp32_ble/__init__.py +22 -2
- esphome/components/esp32_ble/ble.cpp +39 -12
- esphome/components/esp32_ble/ble.h +2 -0
- esphome/components/esp32_ble/ble_advertising.cpp +1 -1
- esphome/components/esp32_ble/ble_uuid.cpp +9 -10
- esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +4 -1
- esphome/components/esp32_camera_web_server/camera_web_server.h +1 -1
- esphome/components/esp32_rmt_led_strip/light.py +3 -3
- esphome/components/esp8266/__init__.py +5 -7
- esphome/components/ezo/ezo.cpp +14 -26
- esphome/components/font/__init__.py +10 -25
- esphome/components/font/font.cpp +5 -3
- esphome/components/graphical_display_menu/__init__.py +2 -0
- esphome/components/haier/hon_climate.cpp +79 -80
- esphome/components/hbridge/switch/__init__.py +44 -0
- esphome/components/hbridge/switch/hbridge_switch.cpp +95 -0
- esphome/components/hbridge/switch/hbridge_switch.h +50 -0
- esphome/components/hitachi_ac344/hitachi_ac344.cpp +4 -2
- esphome/components/hitachi_ac424/hitachi_ac424.cpp +4 -2
- esphome/components/homeassistant/number/homeassistant_number.cpp +3 -0
- esphome/components/hx711/hx711.cpp +1 -1
- esphome/components/hx711/hx711.h +1 -1
- esphome/components/i2c/i2c_bus_esp_idf.cpp +2 -2
- esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp +61 -59
- esphome/components/i2s_audio/speaker/i2s_audio_speaker.h +8 -17
- esphome/components/ili9xxx/display.py +1 -2
- esphome/components/ili9xxx/ili9xxx_display.cpp +3 -2
- esphome/components/image/__init__.py +1 -2
- esphome/components/logger/logger.cpp +1 -1
- esphome/components/ltr501/ltr501.cpp +1 -1
- esphome/components/lvgl/defines.py +8 -1
- esphome/components/lvgl/lv_validation.py +8 -3
- esphome/components/lvgl/lvgl_esphome.cpp +1 -1
- esphome/components/lvgl/lvgl_esphome.h +16 -0
- esphome/components/lvgl/widgets/animimg.py +12 -17
- esphome/components/lvgl/widgets/img.py +1 -3
- esphome/components/matrix_keypad/__init__.py +15 -3
- esphome/components/matrix_keypad/matrix_keypad.cpp +4 -0
- esphome/components/matrix_keypad/matrix_keypad.h +5 -0
- esphome/components/max31865/max31865.cpp +4 -2
- esphome/components/modbus_controller/modbus_controller.cpp +24 -24
- esphome/components/modbus_controller/modbus_controller.h +22 -22
- esphome/components/modbus_controller/number/modbus_number.cpp +8 -8
- esphome/components/modbus_controller/number/modbus_number.h +4 -4
- esphome/components/modbus_controller/output/modbus_output.cpp +7 -6
- esphome/components/modbus_controller/output/modbus_output.h +5 -5
- esphome/components/modbus_controller/select/modbus_select.cpp +4 -3
- esphome/components/modbus_controller/select/modbus_select.h +4 -4
- esphome/components/modbus_controller/switch/modbus_switch.cpp +5 -5
- esphome/components/modbus_controller/switch/modbus_switch.h +2 -2
- esphome/components/mqtt/__init__.py +4 -0
- esphome/components/mqtt/mqtt_alarm_control_panel.cpp +2 -5
- esphome/components/mqtt/mqtt_backend_esp32.cpp +3 -3
- esphome/components/mqtt/mqtt_client.cpp +4 -0
- esphome/components/mqtt/mqtt_client.h +6 -0
- esphome/components/mqtt/mqtt_climate.cpp +13 -3
- esphome/components/mqtt/mqtt_sensor.cpp +2 -0
- esphome/components/network/ip_address.h +1 -1
- esphome/components/nextion/__init__.py +2 -0
- esphome/components/nextion/automation.h +76 -0
- esphome/components/nextion/base_component.py +1 -0
- esphome/components/nextion/binary_sensor/__init__.py +43 -2
- esphome/components/nextion/display.py +15 -0
- esphome/components/nextion/nextion.cpp +8 -5
- esphome/components/nextion/nextion.h +7 -0
- esphome/components/nextion/nextion_upload_idf.cpp +2 -2
- esphome/components/nextion/sensor/__init__.py +38 -5
- esphome/components/nextion/switch/__init__.py +38 -2
- esphome/components/nextion/text_sensor/__init__.py +37 -2
- esphome/components/nfc/ndef_record.cpp +3 -3
- esphome/components/online_image/__init__.py +1 -0
- esphome/components/opentherm/opentherm.cpp +3 -3
- esphome/components/opentherm/opentherm.h +1 -1
- esphome/components/ota/automation.h +1 -1
- esphome/components/output/float_output.cpp +1 -1
- esphome/components/pca6416a/pca6416a.cpp +5 -3
- esphome/components/pca9554/pca9554.cpp +4 -4
- esphome/components/pipsolar/pipsolar.cpp +2 -2
- esphome/components/pipsolar/switch/pipsolar_switch.cpp +2 -2
- esphome/components/pn532/pn532_mifare_ultralight.cpp +2 -2
- esphome/components/pn7150/pn7150_mifare_ultralight.cpp +2 -2
- esphome/components/pn7160/pn7160_mifare_ultralight.cpp +2 -2
- esphome/components/qmc5883l/qmc5883l.cpp +45 -19
- esphome/components/qmc5883l/qmc5883l.h +1 -1
- esphome/components/qspi_dbi/qspi_dbi.cpp +2 -1
- esphome/components/remote_base/raw_protocol.cpp +1 -1
- esphome/components/remote_receiver/__init__.py +5 -6
- esphome/components/rotary_encoder/rotary_encoder.cpp +3 -1
- esphome/components/rp2040/__init__.py +1 -1
- esphome/components/rtl87xx/__init__.py +1 -1
- esphome/components/safe_mode/automation.h +1 -1
- esphome/components/seeed_mr60bha2/__init__.py +41 -0
- esphome/components/seeed_mr60bha2/seeed_mr60bha2.cpp +173 -0
- esphome/components/seeed_mr60bha2/seeed_mr60bha2.h +61 -0
- esphome/components/seeed_mr60bha2/sensor.py +57 -0
- esphome/components/seeed_mr60fda2/__init__.py +41 -0
- esphome/components/seeed_mr60fda2/binary_sensor.py +33 -0
- esphome/components/seeed_mr60fda2/button/__init__.py +45 -0
- esphome/components/seeed_mr60fda2/button/get_radar_parameters_button.cpp +9 -0
- esphome/components/seeed_mr60fda2/button/get_radar_parameters_button.h +18 -0
- esphome/components/seeed_mr60fda2/button/reset_radar_button.cpp +9 -0
- esphome/components/seeed_mr60fda2/button/reset_radar_button.h +18 -0
- esphome/components/seeed_mr60fda2/seeed_mr60fda2.cpp +368 -0
- esphome/components/seeed_mr60fda2/seeed_mr60fda2.h +101 -0
- esphome/components/seeed_mr60fda2/select/__init__.py +59 -0
- esphome/components/seeed_mr60fda2/select/height_threshold_select.cpp +15 -0
- esphome/components/seeed_mr60fda2/select/height_threshold_select.h +18 -0
- esphome/components/seeed_mr60fda2/select/install_height_select.cpp +15 -0
- esphome/components/seeed_mr60fda2/select/install_height_select.h +18 -0
- esphome/components/seeed_mr60fda2/select/sensitivity_select.cpp +15 -0
- esphome/components/seeed_mr60fda2/select/sensitivity_select.h +18 -0
- esphome/components/sen5x/sensor.py +5 -6
- esphome/components/sgp30/sensor.py +8 -9
- esphome/components/sgp30/sgp30.cpp +2 -6
- esphome/components/shelly_dimmer/shelly_dimmer.cpp +1 -1
- esphome/components/sim800l/sim800l.cpp +1 -1
- esphome/components/sntp/sntp_component.cpp +14 -20
- esphome/components/sntp/sntp_component.h +6 -9
- esphome/components/sntp/time.py +4 -7
- esphome/components/sprinkler/sprinkler.cpp +2 -2
- esphome/components/st7735/st7735.cpp +1 -1
- esphome/components/st7789v/st7789v.cpp +1 -1
- esphome/components/stepper/stepper.h +0 -1
- esphome/components/sun_gtil2/sun_gtil2.cpp +1 -1
- esphome/components/switch/binary_sensor/__init__.py +31 -0
- esphome/components/switch/binary_sensor/switch_binary_sensor.cpp +17 -0
- esphome/components/switch/binary_sensor/switch_binary_sensor.h +22 -0
- esphome/components/sx1509/sx1509_gpio_pin.cpp +2 -1
- esphome/components/sx1509/sx1509_gpio_pin.h +5 -5
- esphome/components/uart/uart.h +1 -1
- esphome/components/udp/udp_component.cpp +32 -16
- esphome/components/ufire_ec/sensor.py +4 -4
- esphome/components/uln2003/uln2003.cpp +4 -1
- esphome/components/waveshare_epaper/display.py +8 -0
- esphome/components/waveshare_epaper/waveshare_epaper.cpp +191 -0
- esphome/components/waveshare_epaper/waveshare_epaper.h +56 -0
- esphome/components/wiegand/__init__.py +3 -4
- esphome/components/wifi/__init__.py +42 -0
- esphome/components/wifi/wifi_component.cpp +2 -2
- esphome/components/wifi/wifi_component.h +82 -1
- esphome/components/wifi/wifi_component_esp32_arduino.cpp +1 -1
- esphome/components/wifi/wifi_component_esp8266.cpp +1 -1
- esphome/components/wifi/wifi_component_esp_idf.cpp +1 -1
- esphome/components/wifi/wifi_component_libretiny.cpp +1 -1
- esphome/components/wifi/wifi_component_pico_w.cpp +1 -1
- esphome/components/wireguard/wireguard.cpp +2 -2
- esphome/components/xiaomi_ble/xiaomi_ble.cpp +1 -1
- esphome/config_validation.py +15 -11
- esphome/const.py +11 -1
- esphome/core/component.cpp +1 -1
- esphome/core/config.py +1 -2
- esphome/core/defines.h +3 -1
- esphome/core/helpers.cpp +8 -2
- esphome/core/helpers.h +2 -1
- esphome/core/optional.h +2 -2
- esphome/dashboard/web_server.py +6 -0
- {esphome-2024.11.3.dist-info → esphome-2024.12.0.dist-info}/METADATA +4 -4
- {esphome-2024.11.3.dist-info → esphome-2024.12.0.dist-info}/RECORD +183 -153
- {esphome-2024.11.3.dist-info → esphome-2024.12.0.dist-info}/LICENSE +0 -0
- {esphome-2024.11.3.dist-info → esphome-2024.12.0.dist-info}/WHEEL +0 -0
- {esphome-2024.11.3.dist-info → esphome-2024.12.0.dist-info}/entry_points.txt +0 -0
- {esphome-2024.11.3.dist-info → esphome-2024.12.0.dist-info}/top_level.txt +0 -0
@@ -81,8 +81,8 @@ bool PN7160::is_mifare_ultralight_formatted_(const std::vector<uint8_t> &page_3_
|
|
81
81
|
const uint8_t p4_offset = nfc::MIFARE_ULTRALIGHT_PAGE_SIZE; // page 4 will begin 4 bytes into the vector
|
82
82
|
|
83
83
|
return (page_3_to_6.size() > p4_offset + 3) &&
|
84
|
-
|
85
|
-
|
84
|
+
((page_3_to_6[p4_offset + 0] != 0xFF) || (page_3_to_6[p4_offset + 1] != 0xFF) ||
|
85
|
+
(page_3_to_6[p4_offset + 2] != 0xFF) || (page_3_to_6[p4_offset + 3] != 0xFF));
|
86
86
|
}
|
87
87
|
|
88
88
|
uint16_t PN7160::read_mifare_ultralight_capacity_() {
|
@@ -81,16 +81,39 @@ void QMC5883LComponent::dump_config() {
|
|
81
81
|
}
|
82
82
|
float QMC5883LComponent::get_setup_priority() const { return setup_priority::DATA; }
|
83
83
|
void QMC5883LComponent::update() {
|
84
|
+
i2c::ErrorCode err;
|
84
85
|
uint8_t status = false;
|
85
|
-
|
86
|
-
|
87
|
-
//
|
88
|
-
//
|
89
|
-
|
90
|
-
if (
|
91
|
-
|
92
|
-
|
93
|
-
|
86
|
+
// Status byte gets cleared when data is read, so we have to read this first.
|
87
|
+
// If status and two axes are desired, it's possible to save one byte of traffic by enabling
|
88
|
+
// ROL_PNT in setup and reading 7 bytes starting at the status register.
|
89
|
+
// If status and all three axes are desired, using ROL_PNT saves you 3 bytes.
|
90
|
+
// But simply not reading status saves you 4 bytes always and is much simpler.
|
91
|
+
if (ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG) {
|
92
|
+
err = this->read_register(QMC5883L_REGISTER_STATUS, &status, 1);
|
93
|
+
if (err != i2c::ERROR_OK) {
|
94
|
+
this->status_set_warning(str_sprintf("status read failed (%d)", err).c_str());
|
95
|
+
return;
|
96
|
+
}
|
97
|
+
}
|
98
|
+
|
99
|
+
uint16_t raw[3] = {0};
|
100
|
+
// Z must always be requested, otherwise the data registers will remain locked against updates.
|
101
|
+
// Skipping the Y axis if X and Z are needed actually requires an additional byte of comms.
|
102
|
+
// Starting partway through the axes does save you traffic.
|
103
|
+
uint8_t start, dest;
|
104
|
+
if (this->heading_sensor_ != nullptr || this->x_sensor_ != nullptr) {
|
105
|
+
start = QMC5883L_REGISTER_DATA_X_LSB;
|
106
|
+
dest = 0;
|
107
|
+
} else if (this->y_sensor_ != nullptr) {
|
108
|
+
start = QMC5883L_REGISTER_DATA_Y_LSB;
|
109
|
+
dest = 1;
|
110
|
+
} else {
|
111
|
+
start = QMC5883L_REGISTER_DATA_Z_LSB;
|
112
|
+
dest = 2;
|
113
|
+
}
|
114
|
+
err = this->read_bytes_16_le_(start, &raw[dest], 3 - dest);
|
115
|
+
if (err != i2c::ERROR_OK) {
|
116
|
+
this->status_set_warning(str_sprintf("mag read failed (%d)", err).c_str());
|
94
117
|
return;
|
95
118
|
}
|
96
119
|
|
@@ -107,17 +130,18 @@ void QMC5883LComponent::update() {
|
|
107
130
|
}
|
108
131
|
|
109
132
|
// in µT
|
110
|
-
const float x = int16_t(
|
111
|
-
const float y = int16_t(
|
112
|
-
const float z = int16_t(
|
133
|
+
const float x = int16_t(raw[0]) * mg_per_bit * 0.1f;
|
134
|
+
const float y = int16_t(raw[1]) * mg_per_bit * 0.1f;
|
135
|
+
const float z = int16_t(raw[2]) * mg_per_bit * 0.1f;
|
113
136
|
|
114
137
|
float heading = atan2f(0.0f - x, y) * 180.0f / M_PI;
|
115
138
|
|
116
139
|
float temp = NAN;
|
117
140
|
if (this->temperature_sensor_ != nullptr) {
|
118
141
|
uint16_t raw_temp;
|
119
|
-
|
120
|
-
|
142
|
+
err = this->read_bytes_16_le_(QMC5883L_REGISTER_TEMPERATURE_LSB, &raw_temp);
|
143
|
+
if (err != i2c::ERROR_OK) {
|
144
|
+
this->status_set_warning(str_sprintf("temp read failed (%d)", err).c_str());
|
121
145
|
return;
|
122
146
|
}
|
123
147
|
temp = int16_t(raw_temp) * 0.01f;
|
@@ -138,11 +162,13 @@ void QMC5883LComponent::update() {
|
|
138
162
|
this->temperature_sensor_->publish_state(temp);
|
139
163
|
}
|
140
164
|
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
165
|
+
i2c::ErrorCode QMC5883LComponent::read_bytes_16_le_(uint8_t a_register, uint16_t *data, uint8_t len) {
|
166
|
+
i2c::ErrorCode err = this->read_register(a_register, reinterpret_cast<uint8_t *>(data), len * 2);
|
167
|
+
if (err != i2c::ERROR_OK)
|
168
|
+
return err;
|
169
|
+
for (size_t i = 0; i < len; i++)
|
170
|
+
data[i] = convert_little_endian(data[i]);
|
171
|
+
return err;
|
146
172
|
}
|
147
173
|
|
148
174
|
} // namespace qmc5883l
|
@@ -55,7 +55,7 @@ class QMC5883LComponent : public PollingComponent, public i2c::I2CDevice {
|
|
55
55
|
NONE = 0,
|
56
56
|
COMMUNICATION_FAILED,
|
57
57
|
} error_code_;
|
58
|
-
|
58
|
+
i2c::ErrorCode read_bytes_16_le_(uint8_t a_register, uint16_t *data, uint8_t len = 1);
|
59
59
|
HighFrequencyLoopRequester high_freq_;
|
60
60
|
};
|
61
61
|
|
@@ -146,7 +146,8 @@ void QspiDbi::draw_pixels_at(int x_start, int y_start, int w, int h, const uint8
|
|
146
146
|
return;
|
147
147
|
if (bitness != display::COLOR_BITNESS_565 || order != this->color_mode_ ||
|
148
148
|
big_endian != (this->bit_order_ == spi::BIT_ORDER_MSB_FIRST)) {
|
149
|
-
|
149
|
+
Display::draw_pixels_at(x_start, y_start, w, h, ptr, order, bitness, big_endian, x_offset, y_offset, x_pad);
|
150
|
+
return;
|
150
151
|
} else if (this->draw_from_origin_) {
|
151
152
|
auto stride = x_offset + w + x_pad;
|
152
153
|
for (int y = 0; y != h; y++) {
|
@@ -28,7 +28,7 @@ bool RawDumper::dump(RemoteReceiveData src) {
|
|
28
28
|
ESP_LOGI(TAG, "%s", buffer);
|
29
29
|
buffer_offset = 0;
|
30
30
|
written = sprintf(buffer, " ");
|
31
|
-
if (i + 1 < src.size()) {
|
31
|
+
if (i + 1 < src.size() - 1) {
|
32
32
|
written += sprintf(buffer + written, "%" PRId32 ", ", value);
|
33
33
|
} else {
|
34
34
|
written += sprintf(buffer + written, "%" PRId32, value);
|
@@ -1,24 +1,23 @@
|
|
1
|
+
from esphome import pins
|
1
2
|
import esphome.codegen as cg
|
3
|
+
from esphome.components import esp32_rmt, remote_base
|
2
4
|
import esphome.config_validation as cv
|
3
|
-
from esphome import pins
|
4
|
-
from esphome.components import remote_base, esp32_rmt
|
5
5
|
from esphome.const import (
|
6
6
|
CONF_BUFFER_SIZE,
|
7
|
+
CONF_CLOCK_DIVIDER,
|
7
8
|
CONF_DUMP,
|
8
9
|
CONF_FILTER,
|
9
10
|
CONF_ID,
|
10
11
|
CONF_IDLE,
|
12
|
+
CONF_MEMORY_BLOCKS,
|
11
13
|
CONF_PIN,
|
14
|
+
CONF_RMT_CHANNEL,
|
12
15
|
CONF_TOLERANCE,
|
13
16
|
CONF_TYPE,
|
14
|
-
CONF_MEMORY_BLOCKS,
|
15
|
-
CONF_RMT_CHANNEL,
|
16
17
|
CONF_VALUE,
|
17
18
|
)
|
18
19
|
from esphome.core import CORE, TimePeriod
|
19
20
|
|
20
|
-
CONF_CLOCK_DIVIDER = "clock_divider"
|
21
|
-
|
22
21
|
AUTO_LOAD = ["remote_base"]
|
23
22
|
remote_receiver_ns = cg.esphome_ns.namespace("remote_receiver")
|
24
23
|
remote_base_ns = cg.esphome_ns.namespace("remote_base")
|
@@ -162,7 +162,7 @@ void RotaryEncoderSensor::dump_config() {
|
|
162
162
|
LOG_PIN(" Pin B: ", this->pin_b_);
|
163
163
|
LOG_PIN(" Pin I: ", this->pin_i_);
|
164
164
|
|
165
|
-
const LogString *restore_mode
|
165
|
+
const LogString *restore_mode;
|
166
166
|
switch (this->restore_mode_) {
|
167
167
|
case ROTARY_ENCODER_RESTORE_DEFAULT_ZERO:
|
168
168
|
restore_mode = LOG_STR("Restore (Defaults to zero)");
|
@@ -170,6 +170,8 @@ void RotaryEncoderSensor::dump_config() {
|
|
170
170
|
case ROTARY_ENCODER_ALWAYS_ZERO:
|
171
171
|
restore_mode = LOG_STR("Always zero");
|
172
172
|
break;
|
173
|
+
default:
|
174
|
+
restore_mode = LOG_STR("");
|
173
175
|
}
|
174
176
|
ESP_LOGCONFIG(TAG, " Restore Mode: %s", LOG_STR_ARG(restore_mode));
|
175
177
|
|
@@ -17,7 +17,7 @@ from esphome.const import (
|
|
17
17
|
PLATFORM_RP2040,
|
18
18
|
)
|
19
19
|
from esphome.core import CORE, EsphomeError, coroutine_with_priority
|
20
|
-
from esphome.helpers import copy_file_if_changed, mkdir_p,
|
20
|
+
from esphome.helpers import copy_file_if_changed, mkdir_p, read_file, write_file
|
21
21
|
|
22
22
|
from .const import KEY_BOARD, KEY_PIO_FILES, KEY_RP2040, rp2040_ns
|
23
23
|
|
@@ -15,7 +15,7 @@ from esphome.components.libretiny.const import (
|
|
15
15
|
)
|
16
16
|
from esphome.core import CORE
|
17
17
|
|
18
|
-
from .boards import
|
18
|
+
from .boards import RTL87XX_BOARD_PINS, RTL87XX_BOARDS
|
19
19
|
|
20
20
|
CODEOWNERS = ["@kuba2k2"]
|
21
21
|
AUTO_LOAD = ["libretiny"]
|
@@ -9,7 +9,7 @@ namespace safe_mode {
|
|
9
9
|
class SafeModeTrigger : public Trigger<> {
|
10
10
|
public:
|
11
11
|
explicit SafeModeTrigger(SafeModeComponent *parent) {
|
12
|
-
parent->add_on_safe_mode_callback([this
|
12
|
+
parent->add_on_safe_mode_callback([this]() { trigger(); });
|
13
13
|
}
|
14
14
|
};
|
15
15
|
|
@@ -0,0 +1,41 @@
|
|
1
|
+
import esphome.codegen as cg
|
2
|
+
from esphome.components import uart
|
3
|
+
import esphome.config_validation as cv
|
4
|
+
from esphome.const import CONF_ID
|
5
|
+
|
6
|
+
CODEOWNERS = ["@limengdu"]
|
7
|
+
DEPENDENCIES = ["uart"]
|
8
|
+
MULTI_CONF = True
|
9
|
+
|
10
|
+
mr60bha2_ns = cg.esphome_ns.namespace("seeed_mr60bha2")
|
11
|
+
|
12
|
+
MR60BHA2Component = mr60bha2_ns.class_(
|
13
|
+
"MR60BHA2Component", cg.Component, uart.UARTDevice
|
14
|
+
)
|
15
|
+
|
16
|
+
CONF_MR60BHA2_ID = "mr60bha2_id"
|
17
|
+
|
18
|
+
CONFIG_SCHEMA = (
|
19
|
+
cv.Schema(
|
20
|
+
{
|
21
|
+
cv.GenerateID(): cv.declare_id(MR60BHA2Component),
|
22
|
+
}
|
23
|
+
)
|
24
|
+
.extend(uart.UART_DEVICE_SCHEMA)
|
25
|
+
.extend(cv.COMPONENT_SCHEMA)
|
26
|
+
)
|
27
|
+
|
28
|
+
FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
|
29
|
+
"seeed_mr60bha2",
|
30
|
+
require_tx=True,
|
31
|
+
require_rx=True,
|
32
|
+
baud_rate=115200,
|
33
|
+
parity="NONE",
|
34
|
+
stop_bits=1,
|
35
|
+
)
|
36
|
+
|
37
|
+
|
38
|
+
async def to_code(config):
|
39
|
+
var = cg.new_Pvariable(config[CONF_ID])
|
40
|
+
await cg.register_component(var, config)
|
41
|
+
await uart.register_uart_device(var, config)
|
@@ -0,0 +1,173 @@
|
|
1
|
+
#include "seeed_mr60bha2.h"
|
2
|
+
#include "esphome/core/log.h"
|
3
|
+
|
4
|
+
#include <utility>
|
5
|
+
|
6
|
+
namespace esphome {
|
7
|
+
namespace seeed_mr60bha2 {
|
8
|
+
|
9
|
+
static const char *const TAG = "seeed_mr60bha2";
|
10
|
+
|
11
|
+
// Prints the component's configuration data. dump_config() prints all of the component's configuration
|
12
|
+
// items in an easy-to-read format, including the configuration key-value pairs.
|
13
|
+
void MR60BHA2Component::dump_config() {
|
14
|
+
ESP_LOGCONFIG(TAG, "MR60BHA2:");
|
15
|
+
#ifdef USE_SENSOR
|
16
|
+
LOG_SENSOR(" ", "Breath Rate Sensor", this->breath_rate_sensor_);
|
17
|
+
LOG_SENSOR(" ", "Heart Rate Sensor", this->heart_rate_sensor_);
|
18
|
+
LOG_SENSOR(" ", "Distance Sensor", this->distance_sensor_);
|
19
|
+
#endif
|
20
|
+
}
|
21
|
+
|
22
|
+
// main loop
|
23
|
+
void MR60BHA2Component::loop() {
|
24
|
+
uint8_t byte;
|
25
|
+
|
26
|
+
// Is there data on the serial port
|
27
|
+
while (this->available()) {
|
28
|
+
this->read_byte(&byte);
|
29
|
+
this->rx_message_.push_back(byte);
|
30
|
+
if (!this->validate_message_()) {
|
31
|
+
this->rx_message_.clear();
|
32
|
+
}
|
33
|
+
}
|
34
|
+
}
|
35
|
+
|
36
|
+
/**
|
37
|
+
* @brief Calculate the checksum for a byte array.
|
38
|
+
*
|
39
|
+
* This function calculates the checksum for the provided byte array using an
|
40
|
+
* XOR-based checksum algorithm.
|
41
|
+
*
|
42
|
+
* @param data The byte array to calculate the checksum for.
|
43
|
+
* @param len The length of the byte array.
|
44
|
+
* @return The calculated checksum.
|
45
|
+
*/
|
46
|
+
static uint8_t calculate_checksum(const uint8_t *data, size_t len) {
|
47
|
+
uint8_t checksum = 0;
|
48
|
+
for (size_t i = 0; i < len; i++) {
|
49
|
+
checksum ^= data[i];
|
50
|
+
}
|
51
|
+
checksum = ~checksum;
|
52
|
+
return checksum;
|
53
|
+
}
|
54
|
+
|
55
|
+
/**
|
56
|
+
* @brief Validate the checksum of a byte array.
|
57
|
+
*
|
58
|
+
* This function validates the checksum of the provided byte array by comparing
|
59
|
+
* it to the expected checksum.
|
60
|
+
*
|
61
|
+
* @param data The byte array to validate.
|
62
|
+
* @param len The length of the byte array.
|
63
|
+
* @param expected_checksum The expected checksum.
|
64
|
+
* @return True if the checksum is valid, false otherwise.
|
65
|
+
*/
|
66
|
+
static bool validate_checksum(const uint8_t *data, size_t len, uint8_t expected_checksum) {
|
67
|
+
return calculate_checksum(data, len) == expected_checksum;
|
68
|
+
}
|
69
|
+
|
70
|
+
bool MR60BHA2Component::validate_message_() {
|
71
|
+
size_t at = this->rx_message_.size() - 1;
|
72
|
+
auto *data = &this->rx_message_[0];
|
73
|
+
uint8_t new_byte = data[at];
|
74
|
+
|
75
|
+
if (at == 0) {
|
76
|
+
return new_byte == FRAME_HEADER_BUFFER;
|
77
|
+
}
|
78
|
+
|
79
|
+
if (at <= 2) {
|
80
|
+
return true;
|
81
|
+
}
|
82
|
+
uint16_t frame_id = encode_uint16(data[1], data[2]);
|
83
|
+
|
84
|
+
if (at <= 4) {
|
85
|
+
return true;
|
86
|
+
}
|
87
|
+
|
88
|
+
uint16_t length = encode_uint16(data[3], data[4]);
|
89
|
+
|
90
|
+
if (at <= 6) {
|
91
|
+
return true;
|
92
|
+
}
|
93
|
+
|
94
|
+
uint16_t frame_type = encode_uint16(data[5], data[6]);
|
95
|
+
|
96
|
+
if (frame_type != BREATH_RATE_TYPE_BUFFER && frame_type != HEART_RATE_TYPE_BUFFER &&
|
97
|
+
frame_type != DISTANCE_TYPE_BUFFER) {
|
98
|
+
return false;
|
99
|
+
}
|
100
|
+
|
101
|
+
uint8_t header_checksum = new_byte;
|
102
|
+
|
103
|
+
if (at == 7) {
|
104
|
+
if (!validate_checksum(data, 7, header_checksum)) {
|
105
|
+
ESP_LOGE(TAG, "HEAD_CKSUM_FRAME ERROR: 0x%02x", header_checksum);
|
106
|
+
ESP_LOGV(TAG, "GET FRAME: %s", format_hex_pretty(data, 8).c_str());
|
107
|
+
return false;
|
108
|
+
}
|
109
|
+
return true;
|
110
|
+
}
|
111
|
+
|
112
|
+
// Wait until all data is read
|
113
|
+
if (at - 8 < length) {
|
114
|
+
return true;
|
115
|
+
}
|
116
|
+
|
117
|
+
uint8_t data_checksum = new_byte;
|
118
|
+
if (at == 8 + length) {
|
119
|
+
if (!validate_checksum(data + 8, length, data_checksum)) {
|
120
|
+
ESP_LOGE(TAG, "DATA_CKSUM_FRAME ERROR: 0x%02x", data_checksum);
|
121
|
+
ESP_LOGV(TAG, "GET FRAME: %s", format_hex_pretty(data, 8 + length).c_str());
|
122
|
+
return false;
|
123
|
+
}
|
124
|
+
}
|
125
|
+
|
126
|
+
const uint8_t *frame_data = data + 8;
|
127
|
+
ESP_LOGV(TAG, "Received Frame: ID: 0x%04x, Type: 0x%04x, Data: [%s] Raw Data: [%s]", frame_id, frame_type,
|
128
|
+
format_hex_pretty(frame_data, length).c_str(), format_hex_pretty(this->rx_message_).c_str());
|
129
|
+
this->process_frame_(frame_id, frame_type, data + 8, length);
|
130
|
+
|
131
|
+
// Return false to reset rx buffer
|
132
|
+
return false;
|
133
|
+
}
|
134
|
+
|
135
|
+
void MR60BHA2Component::process_frame_(uint16_t frame_id, uint16_t frame_type, const uint8_t *data, size_t length) {
|
136
|
+
switch (frame_type) {
|
137
|
+
case BREATH_RATE_TYPE_BUFFER:
|
138
|
+
if (this->breath_rate_sensor_ != nullptr && length >= 4) {
|
139
|
+
uint32_t current_breath_rate_int = encode_uint32(data[3], data[2], data[1], data[0]);
|
140
|
+
if (current_breath_rate_int != 0) {
|
141
|
+
float breath_rate_float;
|
142
|
+
memcpy(&breath_rate_float, ¤t_breath_rate_int, sizeof(float));
|
143
|
+
this->breath_rate_sensor_->publish_state(breath_rate_float);
|
144
|
+
}
|
145
|
+
}
|
146
|
+
break;
|
147
|
+
case HEART_RATE_TYPE_BUFFER:
|
148
|
+
if (this->heart_rate_sensor_ != nullptr && length >= 4) {
|
149
|
+
uint32_t current_heart_rate_int = encode_uint32(data[3], data[2], data[1], data[0]);
|
150
|
+
if (current_heart_rate_int != 0) {
|
151
|
+
float heart_rate_float;
|
152
|
+
memcpy(&heart_rate_float, ¤t_heart_rate_int, sizeof(float));
|
153
|
+
this->heart_rate_sensor_->publish_state(heart_rate_float);
|
154
|
+
}
|
155
|
+
}
|
156
|
+
break;
|
157
|
+
case DISTANCE_TYPE_BUFFER:
|
158
|
+
if (!data[0]) {
|
159
|
+
if (this->distance_sensor_ != nullptr && length >= 8) {
|
160
|
+
uint32_t current_distance_int = encode_uint32(data[7], data[6], data[5], data[4]);
|
161
|
+
float distance_float;
|
162
|
+
memcpy(&distance_float, ¤t_distance_int, sizeof(float));
|
163
|
+
this->distance_sensor_->publish_state(distance_float);
|
164
|
+
}
|
165
|
+
}
|
166
|
+
break;
|
167
|
+
default:
|
168
|
+
break;
|
169
|
+
}
|
170
|
+
}
|
171
|
+
|
172
|
+
} // namespace seeed_mr60bha2
|
173
|
+
} // namespace esphome
|
@@ -0,0 +1,61 @@
|
|
1
|
+
#pragma once
|
2
|
+
#include "esphome/core/component.h"
|
3
|
+
#include "esphome/core/defines.h"
|
4
|
+
#ifdef USE_SENSOR
|
5
|
+
#include "esphome/components/sensor/sensor.h"
|
6
|
+
#endif
|
7
|
+
#include "esphome/components/uart/uart.h"
|
8
|
+
#include "esphome/core/automation.h"
|
9
|
+
#include "esphome/core/helpers.h"
|
10
|
+
|
11
|
+
#include <map>
|
12
|
+
|
13
|
+
namespace esphome {
|
14
|
+
namespace seeed_mr60bha2 {
|
15
|
+
|
16
|
+
static const uint8_t DATA_BUF_MAX_SIZE = 12;
|
17
|
+
static const uint8_t FRAME_BUF_MAX_SIZE = 21;
|
18
|
+
static const uint8_t LEN_TO_HEAD_CKSUM = 8;
|
19
|
+
static const uint8_t LEN_TO_DATA_FRAME = 9;
|
20
|
+
|
21
|
+
static const uint8_t FRAME_HEADER_BUFFER = 0x01;
|
22
|
+
static const uint16_t BREATH_RATE_TYPE_BUFFER = 0x0A14;
|
23
|
+
static const uint16_t HEART_RATE_TYPE_BUFFER = 0x0A15;
|
24
|
+
static const uint16_t DISTANCE_TYPE_BUFFER = 0x0A16;
|
25
|
+
|
26
|
+
enum FrameLocation {
|
27
|
+
LOCATE_FRAME_HEADER,
|
28
|
+
LOCATE_ID_FRAME1,
|
29
|
+
LOCATE_ID_FRAME2,
|
30
|
+
LOCATE_LENGTH_FRAME_H,
|
31
|
+
LOCATE_LENGTH_FRAME_L,
|
32
|
+
LOCATE_TYPE_FRAME1,
|
33
|
+
LOCATE_TYPE_FRAME2,
|
34
|
+
LOCATE_HEAD_CKSUM_FRAME, // Header checksum: [from the first byte to the previous byte of the HEAD_CKSUM bit]
|
35
|
+
LOCATE_DATA_FRAME,
|
36
|
+
LOCATE_DATA_CKSUM_FRAME, // Data checksum: [from the first to the previous byte of the DATA_CKSUM bit]
|
37
|
+
LOCATE_PROCESS_FRAME,
|
38
|
+
};
|
39
|
+
|
40
|
+
class MR60BHA2Component : public Component,
|
41
|
+
public uart::UARTDevice { // The class name must be the name defined by text_sensor.py
|
42
|
+
#ifdef USE_SENSOR
|
43
|
+
SUB_SENSOR(breath_rate);
|
44
|
+
SUB_SENSOR(heart_rate);
|
45
|
+
SUB_SENSOR(distance);
|
46
|
+
#endif
|
47
|
+
|
48
|
+
public:
|
49
|
+
float get_setup_priority() const override { return esphome::setup_priority::LATE; }
|
50
|
+
void dump_config() override;
|
51
|
+
void loop() override;
|
52
|
+
|
53
|
+
protected:
|
54
|
+
bool validate_message_();
|
55
|
+
void process_frame_(uint16_t frame_id, uint16_t frame_type, const uint8_t *data, size_t length);
|
56
|
+
|
57
|
+
std::vector<uint8_t> rx_message_;
|
58
|
+
};
|
59
|
+
|
60
|
+
} // namespace seeed_mr60bha2
|
61
|
+
} // namespace esphome
|
@@ -0,0 +1,57 @@
|
|
1
|
+
import esphome.codegen as cg
|
2
|
+
from esphome.components import sensor
|
3
|
+
import esphome.config_validation as cv
|
4
|
+
from esphome.const import (
|
5
|
+
CONF_DISTANCE,
|
6
|
+
DEVICE_CLASS_DISTANCE,
|
7
|
+
ICON_HEART_PULSE,
|
8
|
+
ICON_PULSE,
|
9
|
+
ICON_SIGNAL,
|
10
|
+
STATE_CLASS_MEASUREMENT,
|
11
|
+
UNIT_BEATS_PER_MINUTE,
|
12
|
+
UNIT_CENTIMETER,
|
13
|
+
)
|
14
|
+
|
15
|
+
from . import CONF_MR60BHA2_ID, MR60BHA2Component
|
16
|
+
|
17
|
+
DEPENDENCIES = ["seeed_mr60bha2"]
|
18
|
+
|
19
|
+
CONF_BREATH_RATE = "breath_rate"
|
20
|
+
CONF_HEART_RATE = "heart_rate"
|
21
|
+
|
22
|
+
CONFIG_SCHEMA = cv.Schema(
|
23
|
+
{
|
24
|
+
cv.GenerateID(CONF_MR60BHA2_ID): cv.use_id(MR60BHA2Component),
|
25
|
+
cv.Optional(CONF_BREATH_RATE): sensor.sensor_schema(
|
26
|
+
accuracy_decimals=2,
|
27
|
+
state_class=STATE_CLASS_MEASUREMENT,
|
28
|
+
icon=ICON_PULSE,
|
29
|
+
),
|
30
|
+
cv.Optional(CONF_HEART_RATE): sensor.sensor_schema(
|
31
|
+
accuracy_decimals=0,
|
32
|
+
icon=ICON_HEART_PULSE,
|
33
|
+
state_class=STATE_CLASS_MEASUREMENT,
|
34
|
+
unit_of_measurement=UNIT_BEATS_PER_MINUTE,
|
35
|
+
),
|
36
|
+
cv.Optional(CONF_DISTANCE): sensor.sensor_schema(
|
37
|
+
device_class=DEVICE_CLASS_DISTANCE,
|
38
|
+
state_class=STATE_CLASS_MEASUREMENT,
|
39
|
+
unit_of_measurement=UNIT_CENTIMETER,
|
40
|
+
accuracy_decimals=2,
|
41
|
+
icon=ICON_SIGNAL,
|
42
|
+
),
|
43
|
+
}
|
44
|
+
)
|
45
|
+
|
46
|
+
|
47
|
+
async def to_code(config):
|
48
|
+
mr60bha2_component = await cg.get_variable(config[CONF_MR60BHA2_ID])
|
49
|
+
if breath_rate_config := config.get(CONF_BREATH_RATE):
|
50
|
+
sens = await sensor.new_sensor(breath_rate_config)
|
51
|
+
cg.add(mr60bha2_component.set_breath_rate_sensor(sens))
|
52
|
+
if heart_rate_config := config.get(CONF_HEART_RATE):
|
53
|
+
sens = await sensor.new_sensor(heart_rate_config)
|
54
|
+
cg.add(mr60bha2_component.set_heart_rate_sensor(sens))
|
55
|
+
if distance_config := config.get(CONF_DISTANCE):
|
56
|
+
sens = await sensor.new_sensor(distance_config)
|
57
|
+
cg.add(mr60bha2_component.set_distance_sensor(sens))
|
@@ -0,0 +1,41 @@
|
|
1
|
+
import esphome.codegen as cg
|
2
|
+
from esphome.components import uart
|
3
|
+
import esphome.config_validation as cv
|
4
|
+
from esphome.const import CONF_ID
|
5
|
+
|
6
|
+
CODEOWNERS = ["@limengdu"]
|
7
|
+
DEPENDENCIES = ["uart"]
|
8
|
+
MULTI_CONF = True
|
9
|
+
|
10
|
+
mr60fda2_ns = cg.esphome_ns.namespace("seeed_mr60fda2")
|
11
|
+
|
12
|
+
MR60FDA2Component = mr60fda2_ns.class_(
|
13
|
+
"MR60FDA2Component", cg.Component, uart.UARTDevice
|
14
|
+
)
|
15
|
+
|
16
|
+
CONF_MR60FDA2_ID = "mr60fda2_id"
|
17
|
+
|
18
|
+
CONFIG_SCHEMA = (
|
19
|
+
cv.Schema(
|
20
|
+
{
|
21
|
+
cv.GenerateID(): cv.declare_id(MR60FDA2Component),
|
22
|
+
}
|
23
|
+
)
|
24
|
+
.extend(uart.UART_DEVICE_SCHEMA)
|
25
|
+
.extend(cv.COMPONENT_SCHEMA)
|
26
|
+
)
|
27
|
+
|
28
|
+
FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
|
29
|
+
"seeed_mr60fda2",
|
30
|
+
require_tx=True,
|
31
|
+
require_rx=True,
|
32
|
+
baud_rate=115200,
|
33
|
+
parity="NONE",
|
34
|
+
stop_bits=1,
|
35
|
+
)
|
36
|
+
|
37
|
+
|
38
|
+
async def to_code(config):
|
39
|
+
var = cg.new_Pvariable(config[CONF_ID])
|
40
|
+
await cg.register_component(var, config)
|
41
|
+
await uart.register_uart_device(var, config)
|
@@ -0,0 +1,33 @@
|
|
1
|
+
import esphome.codegen as cg
|
2
|
+
from esphome.components import binary_sensor
|
3
|
+
import esphome.config_validation as cv
|
4
|
+
from esphome.const import DEVICE_CLASS_OCCUPANCY, DEVICE_CLASS_SAFETY
|
5
|
+
|
6
|
+
from . import CONF_MR60FDA2_ID, MR60FDA2Component
|
7
|
+
|
8
|
+
DEPENDENCIES = ["seeed_mr60fda2"]
|
9
|
+
|
10
|
+
CONF_PEOPLE_EXIST = "people_exist"
|
11
|
+
CONF_FALL_DETECTED = "fall_detected"
|
12
|
+
|
13
|
+
CONFIG_SCHEMA = {
|
14
|
+
cv.GenerateID(CONF_MR60FDA2_ID): cv.use_id(MR60FDA2Component),
|
15
|
+
cv.Optional(CONF_PEOPLE_EXIST): binary_sensor.binary_sensor_schema(
|
16
|
+
device_class=DEVICE_CLASS_OCCUPANCY, icon="mdi:motion-sensor"
|
17
|
+
),
|
18
|
+
cv.Optional(CONF_FALL_DETECTED): binary_sensor.binary_sensor_schema(
|
19
|
+
device_class=DEVICE_CLASS_SAFETY, icon="mdi:emergency"
|
20
|
+
),
|
21
|
+
}
|
22
|
+
|
23
|
+
|
24
|
+
async def to_code(config):
|
25
|
+
mr60fda2_component = await cg.get_variable(config[CONF_MR60FDA2_ID])
|
26
|
+
|
27
|
+
if people_exist_config := config.get(CONF_PEOPLE_EXIST):
|
28
|
+
sens = await binary_sensor.new_binary_sensor(people_exist_config)
|
29
|
+
cg.add(mr60fda2_component.set_people_exist_binary_sensor(sens))
|
30
|
+
|
31
|
+
if is_fall_config := config.get(CONF_FALL_DETECTED):
|
32
|
+
sens = await binary_sensor.new_binary_sensor(is_fall_config)
|
33
|
+
cg.add(mr60fda2_component.set_fall_detected_binary_sensor(sens))
|
@@ -0,0 +1,45 @@
|
|
1
|
+
import esphome.codegen as cg
|
2
|
+
from esphome.components import button
|
3
|
+
import esphome.config_validation as cv
|
4
|
+
from esphome.const import (
|
5
|
+
DEVICE_CLASS_RESTART,
|
6
|
+
DEVICE_CLASS_UPDATE,
|
7
|
+
ENTITY_CATEGORY_DIAGNOSTIC,
|
8
|
+
ENTITY_CATEGORY_NONE,
|
9
|
+
CONF_FACTORY_RESET,
|
10
|
+
)
|
11
|
+
|
12
|
+
from .. import CONF_MR60FDA2_ID, MR60FDA2Component, mr60fda2_ns
|
13
|
+
|
14
|
+
DEPENDENCIES = ["seeed_mr60fda2"]
|
15
|
+
|
16
|
+
GetRadarParametersButton = mr60fda2_ns.class_("GetRadarParametersButton", button.Button)
|
17
|
+
ResetRadarButton = mr60fda2_ns.class_("ResetRadarButton", button.Button)
|
18
|
+
|
19
|
+
CONF_GET_RADAR_PARAMETERS = "get_radar_parameters"
|
20
|
+
|
21
|
+
CONFIG_SCHEMA = {
|
22
|
+
cv.GenerateID(CONF_MR60FDA2_ID): cv.use_id(MR60FDA2Component),
|
23
|
+
cv.Optional(CONF_GET_RADAR_PARAMETERS): button.button_schema(
|
24
|
+
GetRadarParametersButton,
|
25
|
+
device_class=DEVICE_CLASS_UPDATE,
|
26
|
+
entity_category=ENTITY_CATEGORY_NONE,
|
27
|
+
),
|
28
|
+
cv.Optional(CONF_FACTORY_RESET): button.button_schema(
|
29
|
+
ResetRadarButton,
|
30
|
+
device_class=DEVICE_CLASS_RESTART,
|
31
|
+
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
32
|
+
),
|
33
|
+
}
|
34
|
+
|
35
|
+
|
36
|
+
async def to_code(config):
|
37
|
+
mr60fda2_component = await cg.get_variable(config[CONF_MR60FDA2_ID])
|
38
|
+
if get_radar_parameters_config := config.get(CONF_GET_RADAR_PARAMETERS):
|
39
|
+
b = await button.new_button(get_radar_parameters_config)
|
40
|
+
await cg.register_parented(b, config[CONF_MR60FDA2_ID])
|
41
|
+
cg.add(mr60fda2_component.set_get_radar_parameters_button(b))
|
42
|
+
if factory_reset_config := config.get(CONF_FACTORY_RESET):
|
43
|
+
b = await button.new_button(factory_reset_config)
|
44
|
+
await cg.register_parented(b, config[CONF_MR60FDA2_ID])
|
45
|
+
cg.add(mr60fda2_component.set_factory_reset_button(b))
|