esphome 2025.5.0b2__py3-none-any.whl → 2025.5.0b3__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/__main__.py +16 -14
- esphome/components/api/api_connection.cpp +4 -3
- esphome/components/api/api_connection.h +8 -1
- esphome/components/api/api_frame_helper.cpp +136 -49
- esphome/components/api/api_frame_helper.h +49 -6
- esphome/components/api/proto.h +48 -8
- esphome/components/ballu/climate.py +1 -1
- esphome/components/bedjet/bedjet_hub.cpp +1 -0
- esphome/components/binary_sensor/binary_sensor.cpp +10 -6
- esphome/components/binary_sensor/binary_sensor.h +1 -1
- esphome/components/binary_sensor/filter.cpp +21 -21
- esphome/components/binary_sensor/filter.h +10 -10
- esphome/components/bluetooth_proxy/bluetooth_proxy.cpp +2 -1
- esphome/components/ccs811/sensor.py +9 -6
- esphome/components/climate_ir/__init__.py +3 -3
- esphome/components/climate_ir_lg/climate.py +1 -1
- esphome/components/coolix/climate.py +1 -1
- esphome/components/cse7766/cse7766.cpp +2 -1
- esphome/components/current_based/current_based_cover.cpp +2 -1
- esphome/components/daikin/climate.py +1 -1
- esphome/components/daikin_arc/climate.py +1 -1
- esphome/components/daikin_brc/climate.py +1 -1
- esphome/components/daly_bms/daly_bms.cpp +2 -1
- esphome/components/debug/debug_component.cpp +1 -1
- esphome/components/delonghi/climate.py +1 -1
- esphome/components/dps310/sensor.py +6 -6
- esphome/components/ee895/sensor.py +9 -9
- esphome/components/emmeti/climate.py +1 -1
- esphome/components/endstop/endstop_cover.cpp +2 -1
- esphome/components/ens160_base/__init__.py +12 -9
- esphome/components/esp32_ble/ble_advertising.cpp +2 -1
- esphome/components/esp32_camera/esp32_camera.cpp +2 -1
- esphome/components/esp32_camera/esp32_camera.h +1 -1
- esphome/components/esp32_improv/esp32_improv_component.cpp +1 -1
- esphome/components/esp32_touch/esp32_touch.cpp +1 -1
- esphome/components/ethernet/ethernet_component.cpp +1 -1
- esphome/components/feedback/feedback_cover.cpp +2 -1
- esphome/components/fujitsu_general/climate.py +1 -1
- esphome/components/gcja5/gcja5.cpp +2 -1
- esphome/components/gps/__init__.py +37 -16
- esphome/components/gps/gps.cpp +33 -17
- esphome/components/gps/gps.h +16 -15
- esphome/components/gree/climate.py +1 -1
- esphome/components/growatt_solar/growatt_solar.cpp +2 -1
- esphome/components/heatpumpir/climate.py +1 -1
- esphome/components/hitachi_ac344/climate.py +1 -1
- esphome/components/hitachi_ac424/climate.py +1 -1
- esphome/components/hte501/sensor.py +6 -6
- esphome/components/hyt271/sensor.py +6 -6
- esphome/components/kuntze/kuntze.cpp +2 -1
- esphome/components/logger/__init__.py +1 -0
- esphome/components/logger/logger.cpp +53 -32
- esphome/components/logger/logger.h +55 -5
- esphome/components/matrix_keypad/matrix_keypad.cpp +2 -1
- esphome/components/max7219digit/max7219digit.cpp +2 -1
- esphome/components/mhz19/sensor.py +11 -7
- esphome/components/midea_ir/climate.py +1 -1
- esphome/components/mitsubishi/climate.py +1 -1
- esphome/components/modbus/modbus.cpp +2 -1
- esphome/components/mqtt/mqtt_client.cpp +1 -1
- esphome/components/ms5611/sensor.py +6 -6
- esphome/components/ms8607/sensor.py +3 -3
- esphome/components/noblex/climate.py +1 -1
- esphome/components/pmsx003/pmsx003.cpp +2 -1
- esphome/components/pzem004t/pzem004t.cpp +2 -1
- esphome/components/rf_bridge/rf_bridge.cpp +2 -1
- esphome/components/sds011/sds011.cpp +2 -1
- esphome/components/sen5x/sen5x.cpp +55 -36
- esphome/components/senseair/sensor.py +3 -3
- esphome/components/sgp30/sensor.py +14 -16
- esphome/components/shtcx/sensor.py +6 -6
- esphome/components/slow_pwm/slow_pwm_output.cpp +2 -1
- esphome/components/sprinkler/sprinkler.cpp +6 -5
- esphome/components/t6615/sensor.py +3 -3
- esphome/components/t6615/t6615.cpp +2 -1
- esphome/components/tcl112/climate.py +1 -1
- esphome/components/time_based/time_based_cover.cpp +2 -1
- esphome/components/toshiba/climate.py +1 -1
- esphome/components/uart/switch/uart_switch.cpp +2 -1
- esphome/components/uponor_smatrix/climate/uponor_smatrix_climate.cpp +2 -1
- esphome/components/uponor_smatrix/uponor_smatrix.cpp +2 -1
- esphome/components/whirlpool/climate.py +1 -1
- esphome/components/whynter/climate.py +1 -1
- esphome/components/zhlt01/climate.py +1 -1
- esphome/config.py +13 -13
- esphome/const.py +1 -1
- esphome/core/application.cpp +22 -10
- esphome/core/application.h +5 -1
- esphome/core/component.cpp +10 -5
- esphome/core/component.h +5 -1
- esphome/core/scheduler.cpp +4 -1
- esphome/log.py +15 -19
- esphome/mqtt.py +2 -2
- esphome/voluptuous_schema.py +3 -1
- esphome/wizard.py +45 -35
- {esphome-2025.5.0b2.dist-info → esphome-2025.5.0b3.dist-info}/METADATA +1 -1
- {esphome-2025.5.0b2.dist-info → esphome-2025.5.0b3.dist-info}/RECORD +101 -101
- {esphome-2025.5.0b2.dist-info → esphome-2025.5.0b3.dist-info}/WHEEL +0 -0
- {esphome-2025.5.0b2.dist-info → esphome-2025.5.0b3.dist-info}/entry_points.txt +0 -0
- {esphome-2025.5.0b2.dist-info → esphome-2025.5.0b3.dist-info}/licenses/LICENSE +0 -0
- {esphome-2025.5.0b2.dist-info → esphome-2025.5.0b3.dist-info}/top_level.txt +0 -0
@@ -19,7 +19,7 @@ CONFIG_SCHEMA = (
|
|
19
19
|
cv.Schema(
|
20
20
|
{
|
21
21
|
cv.GenerateID(): cv.declare_id(T6615Component),
|
22
|
-
cv.
|
22
|
+
cv.Optional(CONF_CO2): sensor.sensor_schema(
|
23
23
|
unit_of_measurement=UNIT_PARTS_PER_MILLION,
|
24
24
|
accuracy_decimals=0,
|
25
25
|
device_class=DEVICE_CLASS_CARBON_DIOXIDE,
|
@@ -41,6 +41,6 @@ async def to_code(config):
|
|
41
41
|
await cg.register_component(var, config)
|
42
42
|
await uart.register_uart_device(var, config)
|
43
43
|
|
44
|
-
if
|
45
|
-
sens = await sensor.new_sensor(
|
44
|
+
if co2 := config.get(CONF_CO2):
|
45
|
+
sens = await sensor.new_sensor(co2)
|
46
46
|
cg.add(var.set_co2_sensor(sens))
|
@@ -63,7 +63,8 @@ void T6615Component::loop() {
|
|
63
63
|
case T6615Command::GET_PPM: {
|
64
64
|
const uint16_t ppm = encode_uint16(response_buffer[3], response_buffer[4]);
|
65
65
|
ESP_LOGD(TAG, "T6615 Received CO₂=%uppm", ppm);
|
66
|
-
this->co2_sensor_
|
66
|
+
if (this->co2_sensor_ != nullptr)
|
67
|
+
this->co2_sensor_->publish_state(ppm);
|
67
68
|
break;
|
68
69
|
}
|
69
70
|
default:
|
@@ -7,7 +7,7 @@ CODEOWNERS = ["@glmnet"]
|
|
7
7
|
tcl112_ns = cg.esphome_ns.namespace("tcl112")
|
8
8
|
Tcl112Climate = tcl112_ns.class_("Tcl112Climate", climate_ir.ClimateIR)
|
9
9
|
|
10
|
-
CONFIG_SCHEMA = climate_ir.
|
10
|
+
CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(Tcl112Climate)
|
11
11
|
|
12
12
|
|
13
13
|
async def to_code(config):
|
@@ -1,6 +1,7 @@
|
|
1
1
|
#include "time_based_cover.h"
|
2
2
|
#include "esphome/core/log.h"
|
3
3
|
#include "esphome/core/hal.h"
|
4
|
+
#include "esphome/core/application.h"
|
4
5
|
|
5
6
|
namespace esphome {
|
6
7
|
namespace time_based {
|
@@ -26,7 +27,7 @@ void TimeBasedCover::loop() {
|
|
26
27
|
if (this->current_operation == COVER_OPERATION_IDLE)
|
27
28
|
return;
|
28
29
|
|
29
|
-
const uint32_t now =
|
30
|
+
const uint32_t now = App.get_loop_component_start_time();
|
30
31
|
|
31
32
|
// Recompute position every loop cycle
|
32
33
|
this->recompute_position_();
|
@@ -16,7 +16,7 @@ MODELS = {
|
|
16
16
|
"RAC-PT1411HWRU-F": Model.MODEL_RAC_PT1411HWRU_F,
|
17
17
|
}
|
18
18
|
|
19
|
-
CONFIG_SCHEMA = climate_ir.
|
19
|
+
CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(ToshibaClimate).extend(
|
20
20
|
{
|
21
21
|
cv.Optional(CONF_MODEL, default="generic"): cv.enum(MODELS, upper=True),
|
22
22
|
}
|
@@ -1,5 +1,6 @@
|
|
1
1
|
#include "uart_switch.h"
|
2
2
|
#include "esphome/core/log.h"
|
3
|
+
#include "esphome/core/application.h"
|
3
4
|
|
4
5
|
namespace esphome {
|
5
6
|
namespace uart {
|
@@ -8,7 +9,7 @@ static const char *const TAG = "uart.switch";
|
|
8
9
|
|
9
10
|
void UARTSwitch::loop() {
|
10
11
|
if (this->send_every_) {
|
11
|
-
const uint32_t now =
|
12
|
+
const uint32_t now = App.get_loop_component_start_time();
|
12
13
|
if (now - this->last_transmission_ > this->send_every_) {
|
13
14
|
this->write_command_(this->state);
|
14
15
|
this->last_transmission_ = now;
|
@@ -1,6 +1,7 @@
|
|
1
1
|
#include "uponor_smatrix_climate.h"
|
2
2
|
#include "esphome/core/helpers.h"
|
3
3
|
#include "esphome/core/log.h"
|
4
|
+
#include "esphome/core/application.h"
|
4
5
|
|
5
6
|
namespace esphome {
|
6
7
|
namespace uponor_smatrix {
|
@@ -13,7 +14,7 @@ void UponorSmatrixClimate::dump_config() {
|
|
13
14
|
}
|
14
15
|
|
15
16
|
void UponorSmatrixClimate::loop() {
|
16
|
-
const uint32_t now =
|
17
|
+
const uint32_t now = App.get_loop_component_start_time();
|
17
18
|
|
18
19
|
// Publish state after all update packets are processed
|
19
20
|
if (this->last_data_ != 0 && (now - this->last_data_ > 100) && this->target_temperature_raw_ != 0) {
|
@@ -1,5 +1,6 @@
|
|
1
1
|
#include "uponor_smatrix.h"
|
2
2
|
#include "esphome/core/log.h"
|
3
|
+
#include "esphome/core/application.h"
|
3
4
|
|
4
5
|
namespace esphome {
|
5
6
|
namespace uponor_smatrix {
|
@@ -35,7 +36,7 @@ void UponorSmatrixComponent::dump_config() {
|
|
35
36
|
}
|
36
37
|
|
37
38
|
void UponorSmatrixComponent::loop() {
|
38
|
-
const uint32_t now =
|
39
|
+
const uint32_t now = App.get_loop_component_start_time();
|
39
40
|
|
40
41
|
// Discard stale data
|
41
42
|
if (!this->rx_buffer_.empty() && (now - this->last_rx_ > 50)) {
|
@@ -15,7 +15,7 @@ MODELS = {
|
|
15
15
|
"DG11J1-91": Model.MODEL_DG11J1_91,
|
16
16
|
}
|
17
17
|
|
18
|
-
CONFIG_SCHEMA = climate_ir.
|
18
|
+
CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(WhirlpoolClimate).extend(
|
19
19
|
{
|
20
20
|
cv.Optional(CONF_MODEL, default="DG11J1-3A"): cv.enum(MODELS, upper=True),
|
21
21
|
}
|
@@ -9,7 +9,7 @@ whynter_ns = cg.esphome_ns.namespace("whynter")
|
|
9
9
|
Whynter = whynter_ns.class_("Whynter", climate_ir.ClimateIR)
|
10
10
|
|
11
11
|
|
12
|
-
CONFIG_SCHEMA = climate_ir.
|
12
|
+
CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(Whynter).extend(
|
13
13
|
{
|
14
14
|
cv.Optional(CONF_USE_FAHRENHEIT, default=False): cv.boolean,
|
15
15
|
}
|
@@ -7,7 +7,7 @@ CODEOWNERS = ["@cfeenstra1024"]
|
|
7
7
|
zhlt01_ns = cg.esphome_ns.namespace("zhlt01")
|
8
8
|
ZHLT01Climate = zhlt01_ns.class_("ZHLT01Climate", climate_ir.ClimateIR)
|
9
9
|
|
10
|
-
CONFIG_SCHEMA = climate_ir.
|
10
|
+
CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(ZHLT01Climate)
|
11
11
|
|
12
12
|
|
13
13
|
async def to_code(config):
|
esphome/config.py
CHANGED
@@ -28,7 +28,7 @@ import esphome.core.config as core_config
|
|
28
28
|
import esphome.final_validate as fv
|
29
29
|
from esphome.helpers import indent
|
30
30
|
from esphome.loader import ComponentManifest, get_component, get_platform
|
31
|
-
from esphome.log import
|
31
|
+
from esphome.log import AnsiFore, color
|
32
32
|
from esphome.types import ConfigFragmentType, ConfigType
|
33
33
|
from esphome.util import OrderedDict, safe_print
|
34
34
|
from esphome.voluptuous_schema import ExtraKeysInvalid
|
@@ -959,7 +959,7 @@ def line_info(config, path, highlight=True):
|
|
959
959
|
if obj:
|
960
960
|
mark = obj.start_mark
|
961
961
|
source = f"[source {mark.document}:{mark.line + 1}]"
|
962
|
-
return color(
|
962
|
+
return color(AnsiFore.CYAN, source)
|
963
963
|
return "None"
|
964
964
|
|
965
965
|
|
@@ -983,7 +983,7 @@ def dump_dict(
|
|
983
983
|
if at_root:
|
984
984
|
error = config.get_error_for_path(path)
|
985
985
|
if error is not None:
|
986
|
-
ret += f"\n{color(
|
986
|
+
ret += f"\n{color(AnsiFore.BOLD_RED, _format_vol_invalid(error, config))}\n"
|
987
987
|
|
988
988
|
if isinstance(conf, (list, tuple)):
|
989
989
|
multiline = True
|
@@ -995,11 +995,11 @@ def dump_dict(
|
|
995
995
|
path_ = path + [i]
|
996
996
|
error = config.get_error_for_path(path_)
|
997
997
|
if error is not None:
|
998
|
-
ret += f"\n{color(
|
998
|
+
ret += f"\n{color(AnsiFore.BOLD_RED, _format_vol_invalid(error, config))}\n"
|
999
999
|
|
1000
1000
|
sep = "- "
|
1001
1001
|
if config.is_in_error_path(path_):
|
1002
|
-
sep = color(
|
1002
|
+
sep = color(AnsiFore.RED, sep)
|
1003
1003
|
msg, _ = dump_dict(config, path_, at_root=False)
|
1004
1004
|
msg = indent(msg)
|
1005
1005
|
inf = line_info(config, path_, highlight=config.is_in_error_path(path_))
|
@@ -1018,11 +1018,11 @@ def dump_dict(
|
|
1018
1018
|
path_ = path + [k]
|
1019
1019
|
error = config.get_error_for_path(path_)
|
1020
1020
|
if error is not None:
|
1021
|
-
ret += f"\n{color(
|
1021
|
+
ret += f"\n{color(AnsiFore.BOLD_RED, _format_vol_invalid(error, config))}\n"
|
1022
1022
|
|
1023
1023
|
st = f"{k}: "
|
1024
1024
|
if config.is_in_error_path(path_):
|
1025
|
-
st = color(
|
1025
|
+
st = color(AnsiFore.RED, st)
|
1026
1026
|
msg, m = dump_dict(config, path_, at_root=False)
|
1027
1027
|
|
1028
1028
|
inf = line_info(config, path_, highlight=config.is_in_error_path(path_))
|
@@ -1044,7 +1044,7 @@ def dump_dict(
|
|
1044
1044
|
if len(conf) > 80:
|
1045
1045
|
conf = f"|-\n{indent(conf)}"
|
1046
1046
|
error = config.get_error_for_path(path)
|
1047
|
-
col =
|
1047
|
+
col = AnsiFore.BOLD_RED if error else AnsiFore.KEEP
|
1048
1048
|
ret += color(col, str(conf))
|
1049
1049
|
elif isinstance(conf, core.Lambda):
|
1050
1050
|
if is_secret(conf):
|
@@ -1052,13 +1052,13 @@ def dump_dict(
|
|
1052
1052
|
|
1053
1053
|
conf = f"!lambda |-\n{indent(str(conf.value))}"
|
1054
1054
|
error = config.get_error_for_path(path)
|
1055
|
-
col =
|
1055
|
+
col = AnsiFore.BOLD_RED if error else AnsiFore.KEEP
|
1056
1056
|
ret += color(col, conf)
|
1057
1057
|
elif conf is None:
|
1058
1058
|
pass
|
1059
1059
|
else:
|
1060
1060
|
error = config.get_error_for_path(path)
|
1061
|
-
col =
|
1061
|
+
col = AnsiFore.BOLD_RED if error else AnsiFore.KEEP
|
1062
1062
|
ret += color(col, str(conf))
|
1063
1063
|
multiline = "\n" in ret
|
1064
1064
|
|
@@ -1100,13 +1100,13 @@ def read_config(command_line_substitutions):
|
|
1100
1100
|
if not CORE.verbose:
|
1101
1101
|
res = strip_default_ids(res)
|
1102
1102
|
|
1103
|
-
safe_print(color(
|
1103
|
+
safe_print(color(AnsiFore.BOLD_RED, "Failed config"))
|
1104
1104
|
safe_print("")
|
1105
1105
|
for path, domain in res.output_paths:
|
1106
1106
|
if not res.is_in_error_path(path):
|
1107
1107
|
continue
|
1108
1108
|
|
1109
|
-
errstr = color(
|
1109
|
+
errstr = color(AnsiFore.BOLD_RED, f"{domain}:")
|
1110
1110
|
errline = line_info(res, path)
|
1111
1111
|
if errline:
|
1112
1112
|
errstr += f" {errline}"
|
@@ -1121,7 +1121,7 @@ def read_config(command_line_substitutions):
|
|
1121
1121
|
safe_print(indent("\n".join(split_dump[:i])))
|
1122
1122
|
|
1123
1123
|
for err in res.errors:
|
1124
|
-
safe_print(color(
|
1124
|
+
safe_print(color(AnsiFore.BOLD_RED, err.msg))
|
1125
1125
|
safe_print("")
|
1126
1126
|
|
1127
1127
|
return None
|
esphome/const.py
CHANGED
esphome/core/application.cpp
CHANGED
@@ -67,22 +67,32 @@ void Application::loop() {
|
|
67
67
|
uint32_t new_app_state = 0;
|
68
68
|
|
69
69
|
this->scheduler.call();
|
70
|
-
|
70
|
+
|
71
|
+
// Get the initial loop time at the start
|
72
|
+
uint32_t last_op_end_time = millis();
|
73
|
+
|
74
|
+
// Feed WDT with time
|
75
|
+
this->feed_wdt(last_op_end_time);
|
76
|
+
|
71
77
|
for (Component *component : this->looping_components_) {
|
78
|
+
// Update the cached time before each component runs
|
79
|
+
this->loop_component_start_time_ = last_op_end_time;
|
80
|
+
|
72
81
|
{
|
73
82
|
this->set_current_component(component);
|
74
|
-
WarnIfComponentBlockingGuard guard{component};
|
83
|
+
WarnIfComponentBlockingGuard guard{component, last_op_end_time};
|
75
84
|
component->call();
|
85
|
+
// Use the finish method to get the current time as the end time
|
86
|
+
last_op_end_time = guard.finish();
|
76
87
|
}
|
77
88
|
new_app_state |= component->get_component_state();
|
78
89
|
this->app_state_ |= new_app_state;
|
79
|
-
this->feed_wdt();
|
90
|
+
this->feed_wdt(last_op_end_time);
|
80
91
|
}
|
81
92
|
this->app_state_ = new_app_state;
|
82
93
|
|
83
|
-
|
84
|
-
|
85
|
-
auto elapsed = now - this->last_loop_;
|
94
|
+
// Use the last component's end time instead of calling millis() again
|
95
|
+
auto elapsed = last_op_end_time - this->last_loop_;
|
86
96
|
if (elapsed >= this->loop_interval_ || HighFrequencyLoopRequester::is_high_frequency()) {
|
87
97
|
yield();
|
88
98
|
} else {
|
@@ -94,7 +104,7 @@ void Application::loop() {
|
|
94
104
|
delay_time = std::min(next_schedule, delay_time);
|
95
105
|
delay(delay_time);
|
96
106
|
}
|
97
|
-
this->last_loop_ =
|
107
|
+
this->last_loop_ = last_op_end_time;
|
98
108
|
|
99
109
|
if (this->dump_config_at_ < this->components_.size()) {
|
100
110
|
if (this->dump_config_at_ == 0) {
|
@@ -109,10 +119,12 @@ void Application::loop() {
|
|
109
119
|
}
|
110
120
|
}
|
111
121
|
|
112
|
-
void IRAM_ATTR HOT Application::feed_wdt() {
|
122
|
+
void IRAM_ATTR HOT Application::feed_wdt(uint32_t time) {
|
113
123
|
static uint32_t last_feed = 0;
|
114
|
-
|
115
|
-
|
124
|
+
// Use provided time if available, otherwise get current time
|
125
|
+
uint32_t now = time ? time : millis();
|
126
|
+
// Compare in milliseconds (3ms threshold)
|
127
|
+
if (now - last_feed > 3) {
|
116
128
|
arch_feed_wdt();
|
117
129
|
last_feed = now;
|
118
130
|
#ifdef USE_STATUS_LED
|
esphome/core/application.h
CHANGED
@@ -217,6 +217,9 @@ class Application {
|
|
217
217
|
|
218
218
|
std::string get_compilation_time() const { return this->compilation_time_; }
|
219
219
|
|
220
|
+
/// Get the cached time in milliseconds from when the current component started its loop execution
|
221
|
+
inline uint32_t IRAM_ATTR HOT get_loop_component_start_time() const { return this->loop_component_start_time_; }
|
222
|
+
|
220
223
|
/** Set the target interval with which to run the loop() calls.
|
221
224
|
* If the loop() method takes longer than the target interval, ESPHome won't
|
222
225
|
* sleep in loop(), but if the time spent in loop() is small than the target, ESPHome
|
@@ -236,7 +239,7 @@ class Application {
|
|
236
239
|
|
237
240
|
void schedule_dump_config() { this->dump_config_at_ = 0; }
|
238
241
|
|
239
|
-
void feed_wdt();
|
242
|
+
void feed_wdt(uint32_t time = 0);
|
240
243
|
|
241
244
|
void reboot();
|
242
245
|
|
@@ -551,6 +554,7 @@ class Application {
|
|
551
554
|
size_t dump_config_at_{SIZE_MAX};
|
552
555
|
uint32_t app_state_{0};
|
553
556
|
Component *current_component_{nullptr};
|
557
|
+
uint32_t loop_component_start_time_{0};
|
554
558
|
};
|
555
559
|
|
556
560
|
/// Global storage of Application pointer - only one Application can exist.
|
esphome/core/component.cpp
CHANGED
@@ -240,10 +240,12 @@ void PollingComponent::stop_poller() {
|
|
240
240
|
uint32_t PollingComponent::get_update_interval() const { return this->update_interval_; }
|
241
241
|
void PollingComponent::set_update_interval(uint32_t update_interval) { this->update_interval_ = update_interval; }
|
242
242
|
|
243
|
-
WarnIfComponentBlockingGuard::WarnIfComponentBlockingGuard(Component *component)
|
244
|
-
: started_(
|
245
|
-
WarnIfComponentBlockingGuard
|
246
|
-
uint32_t
|
243
|
+
WarnIfComponentBlockingGuard::WarnIfComponentBlockingGuard(Component *component, uint32_t start_time)
|
244
|
+
: started_(start_time), component_(component) {}
|
245
|
+
uint32_t WarnIfComponentBlockingGuard::finish() {
|
246
|
+
uint32_t curr_time = millis();
|
247
|
+
|
248
|
+
uint32_t blocking_time = curr_time - this->started_;
|
247
249
|
bool should_warn;
|
248
250
|
if (this->component_ != nullptr) {
|
249
251
|
should_warn = this->component_->should_warn_of_blocking(blocking_time);
|
@@ -254,8 +256,11 @@ WarnIfComponentBlockingGuard::~WarnIfComponentBlockingGuard() {
|
|
254
256
|
const char *src = component_ == nullptr ? "<null>" : component_->get_component_source();
|
255
257
|
ESP_LOGW(TAG, "Component %s took a long time for an operation (%" PRIu32 " ms).", src, blocking_time);
|
256
258
|
ESP_LOGW(TAG, "Components should block for at most 30 ms.");
|
257
|
-
;
|
258
259
|
}
|
260
|
+
|
261
|
+
return curr_time;
|
259
262
|
}
|
260
263
|
|
264
|
+
WarnIfComponentBlockingGuard::~WarnIfComponentBlockingGuard() {}
|
265
|
+
|
261
266
|
} // namespace esphome
|
esphome/core/component.h
CHANGED
@@ -339,7 +339,11 @@ class PollingComponent : public Component {
|
|
339
339
|
|
340
340
|
class WarnIfComponentBlockingGuard {
|
341
341
|
public:
|
342
|
-
WarnIfComponentBlockingGuard(Component *component);
|
342
|
+
WarnIfComponentBlockingGuard(Component *component, uint32_t start_time);
|
343
|
+
|
344
|
+
// Finish the timing operation and return the current time
|
345
|
+
uint32_t finish();
|
346
|
+
|
343
347
|
~WarnIfComponentBlockingGuard();
|
344
348
|
|
345
349
|
protected:
|
esphome/core/scheduler.cpp
CHANGED
@@ -229,8 +229,11 @@ void HOT Scheduler::call() {
|
|
229
229
|
// - timeouts/intervals get added, potentially invalidating vector pointers
|
230
230
|
// - timeouts/intervals get cancelled
|
231
231
|
{
|
232
|
-
|
232
|
+
uint32_t now_ms = millis();
|
233
|
+
WarnIfComponentBlockingGuard guard{item->component, now_ms};
|
233
234
|
item->callback();
|
235
|
+
// Call finish to ensure blocking time is properly calculated and reported
|
236
|
+
guard.finish();
|
234
237
|
}
|
235
238
|
}
|
236
239
|
|
esphome/log.py
CHANGED
@@ -1,9 +1,10 @@
|
|
1
|
+
from enum import Enum
|
1
2
|
import logging
|
2
3
|
|
3
4
|
from esphome.core import CORE
|
4
5
|
|
5
6
|
|
6
|
-
class AnsiFore:
|
7
|
+
class AnsiFore(Enum):
|
7
8
|
KEEP = ""
|
8
9
|
BLACK = "\033[30m"
|
9
10
|
RED = "\033[31m"
|
@@ -26,7 +27,7 @@ class AnsiFore:
|
|
26
27
|
BOLD_RESET = "\033[1;39m"
|
27
28
|
|
28
29
|
|
29
|
-
class AnsiStyle:
|
30
|
+
class AnsiStyle(Enum):
|
30
31
|
BRIGHT = "\033[1m"
|
31
32
|
BOLD = "\033[1m"
|
32
33
|
DIM = "\033[2m"
|
@@ -35,16 +36,10 @@ class AnsiStyle:
|
|
35
36
|
RESET_ALL = "\033[0m"
|
36
37
|
|
37
38
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
def color(col: str, msg: str, reset: bool = True) -> bool:
|
43
|
-
if col and not col.startswith("\033["):
|
44
|
-
raise ValueError("Color must be value from esphome.log.Fore")
|
45
|
-
s = str(col) + msg
|
39
|
+
def color(col: AnsiFore, msg: str, reset: bool = True) -> str:
|
40
|
+
s = col.value + msg
|
46
41
|
if reset and col:
|
47
|
-
s +=
|
42
|
+
s += AnsiStyle.RESET_ALL.value
|
48
43
|
return s
|
49
44
|
|
50
45
|
|
@@ -54,20 +49,21 @@ class ESPHomeLogFormatter(logging.Formatter):
|
|
54
49
|
fmt += "%(levelname)s %(message)s"
|
55
50
|
super().__init__(fmt=fmt, style="%")
|
56
51
|
|
57
|
-
|
52
|
+
# @override
|
53
|
+
def format(self, record: logging.LogRecord) -> str:
|
58
54
|
formatted = super().format(record)
|
59
55
|
prefix = {
|
60
|
-
"DEBUG":
|
61
|
-
"INFO":
|
62
|
-
"WARNING":
|
63
|
-
"ERROR":
|
64
|
-
"CRITICAL":
|
56
|
+
"DEBUG": AnsiFore.CYAN.value,
|
57
|
+
"INFO": AnsiFore.GREEN.value,
|
58
|
+
"WARNING": AnsiFore.YELLOW.value,
|
59
|
+
"ERROR": AnsiFore.RED.value,
|
60
|
+
"CRITICAL": AnsiFore.RED.value,
|
65
61
|
}.get(record.levelname, "")
|
66
|
-
return f"{prefix}{formatted}{
|
62
|
+
return f"{prefix}{formatted}{AnsiStyle.RESET_ALL.value}"
|
67
63
|
|
68
64
|
|
69
65
|
def setup_log(
|
70
|
-
log_level=logging.INFO,
|
66
|
+
log_level: int = logging.INFO,
|
71
67
|
include_timestamp: bool = False,
|
72
68
|
) -> None:
|
73
69
|
import colorama
|
esphome/mqtt.py
CHANGED
@@ -28,7 +28,7 @@ from esphome.const import (
|
|
28
28
|
)
|
29
29
|
from esphome.core import CORE, EsphomeError
|
30
30
|
from esphome.helpers import get_int_env, get_str_env
|
31
|
-
from esphome.log import
|
31
|
+
from esphome.log import AnsiFore, color
|
32
32
|
from esphome.util import safe_print
|
33
33
|
|
34
34
|
_LOGGER = logging.getLogger(__name__)
|
@@ -291,7 +291,7 @@ def get_fingerprint(config):
|
|
291
291
|
|
292
292
|
sha1 = hashlib.sha1(cert_der).hexdigest()
|
293
293
|
|
294
|
-
safe_print(f"SHA1 Fingerprint: {color(
|
294
|
+
safe_print(f"SHA1 Fingerprint: {color(AnsiFore.CYAN, sha1)}")
|
295
295
|
safe_print(
|
296
296
|
f"Copy the string above into mqtt.ssl_fingerprints section of {CORE.config_path}"
|
297
297
|
)
|
esphome/voluptuous_schema.py
CHANGED
@@ -15,7 +15,9 @@ class ExtraKeysInvalid(vol.Invalid):
|
|
15
15
|
def ensure_multiple_invalid(err):
|
16
16
|
if isinstance(err, vol.MultipleInvalid):
|
17
17
|
return err
|
18
|
-
|
18
|
+
if isinstance(err, list):
|
19
|
+
return vol.MultipleInvalid(err)
|
20
|
+
return vol.MultipleInvalid([err])
|
19
21
|
|
20
22
|
|
21
23
|
# pylint: disable=protected-access, unidiomatic-typecheck
|