esphome 2025.4.0b1__py3-none-any.whl → 2025.4.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 +5 -3
- esphome/components/am2315c/am2315c.cpp +4 -4
- esphome/components/api/api_frame_helper.cpp +4 -0
- esphome/components/axs15231/touchscreen/axs15231_touchscreen.cpp +7 -3
- esphome/components/lvgl/automation.py +14 -2
- esphome/components/lvgl/encoders.py +3 -2
- esphome/components/lvgl/lvcode.py +2 -1
- esphome/components/lvgl/lvgl_esphome.h +2 -0
- esphome/components/lvgl/number/__init__.py +23 -20
- esphome/components/lvgl/number/lvgl_number.h +27 -14
- esphome/components/lvgl/schemas.py +3 -1
- esphome/components/lvgl/select/__init__.py +11 -13
- esphome/components/lvgl/select/lvgl_select.h +25 -18
- esphome/components/lvgl/widgets/buttonmatrix.py +2 -2
- esphome/components/lvgl/widgets/canvas.py +7 -3
- esphome/components/lvgl/widgets/meter.py +3 -1
- esphome/components/sml/sml.cpp +8 -6
- esphome/components/sml/sml.h +1 -1
- esphome/components/sml/sml_parser.cpp +16 -18
- esphome/components/sml/sml_parser.h +42 -12
- esphome/components/speaker/media_player/audio_pipeline.cpp +3 -2
- esphome/config_validation.py +0 -42
- esphome/const.py +1 -1
- esphome/core/__init__.py +0 -1
- esphome/vscode.py +27 -8
- esphome/yaml_util.py +75 -39
- {esphome-2025.4.0b1.dist-info → esphome-2025.4.0b3.dist-info}/METADATA +4 -4
- {esphome-2025.4.0b1.dist-info → esphome-2025.4.0b3.dist-info}/RECORD +32 -32
- {esphome-2025.4.0b1.dist-info → esphome-2025.4.0b3.dist-info}/WHEEL +0 -0
- {esphome-2025.4.0b1.dist-info → esphome-2025.4.0b3.dist-info}/entry_points.txt +0 -0
- {esphome-2025.4.0b1.dist-info → esphome-2025.4.0b3.dist-info}/licenses/LICENSE +0 -0
- {esphome-2025.4.0b1.dist-info → esphome-2025.4.0b3.dist-info}/top_level.txt +0 -0
@@ -5,17 +5,17 @@
|
|
5
5
|
namespace esphome {
|
6
6
|
namespace sml {
|
7
7
|
|
8
|
-
SmlFile::SmlFile(
|
8
|
+
SmlFile::SmlFile(const BytesView &buffer) : buffer_(buffer) {
|
9
9
|
// extract messages
|
10
10
|
this->pos_ = 0;
|
11
11
|
while (this->pos_ < this->buffer_.size()) {
|
12
12
|
if (this->buffer_[this->pos_] == 0x00)
|
13
13
|
break; // EndOfSmlMsg
|
14
14
|
|
15
|
-
SmlNode message
|
15
|
+
SmlNode message;
|
16
16
|
if (!this->setup_node(&message))
|
17
17
|
break;
|
18
|
-
this->messages.emplace_back(message);
|
18
|
+
this->messages.emplace_back(std::move(message));
|
19
19
|
}
|
20
20
|
}
|
21
21
|
|
@@ -62,22 +62,20 @@ bool SmlFile::setup_node(SmlNode *node) {
|
|
62
62
|
return false;
|
63
63
|
|
64
64
|
node->type = type;
|
65
|
-
node->nodes.clear();
|
66
|
-
node->value_bytes.clear();
|
67
65
|
|
68
66
|
if (type == SML_LIST) {
|
69
67
|
node->nodes.reserve(length);
|
70
68
|
for (size_t i = 0; i != length; i++) {
|
71
|
-
SmlNode child_node
|
69
|
+
SmlNode child_node;
|
72
70
|
if (!this->setup_node(&child_node))
|
73
71
|
return false;
|
74
|
-
node->nodes.emplace_back(child_node);
|
72
|
+
node->nodes.emplace_back(std::move(child_node));
|
75
73
|
}
|
76
74
|
} else {
|
77
75
|
// Value starts at the current position
|
78
76
|
// Value ends "length" bytes later,
|
79
77
|
// (since the TL field is counted but already subtracted from length)
|
80
|
-
node->value_bytes =
|
78
|
+
node->value_bytes = buffer_.subview(this->pos_, length);
|
81
79
|
// Increment the pointer past all consumed bytes
|
82
80
|
this->pos_ += length;
|
83
81
|
}
|
@@ -87,14 +85,14 @@ bool SmlFile::setup_node(SmlNode *node) {
|
|
87
85
|
std::vector<ObisInfo> SmlFile::get_obis_info() {
|
88
86
|
std::vector<ObisInfo> obis_info;
|
89
87
|
for (auto const &message : messages) {
|
90
|
-
|
88
|
+
const auto &message_body = message.nodes[3];
|
91
89
|
uint16_t message_type = bytes_to_uint(message_body.nodes[0].value_bytes);
|
92
90
|
if (message_type != SML_GET_LIST_RES)
|
93
91
|
continue;
|
94
92
|
|
95
|
-
|
96
|
-
|
97
|
-
|
93
|
+
const auto &get_list_response = message_body.nodes[1];
|
94
|
+
const auto &server_id = get_list_response.nodes[1].value_bytes;
|
95
|
+
const auto &val_list = get_list_response.nodes[4];
|
98
96
|
|
99
97
|
for (auto const &val_list_entry : val_list.nodes) {
|
100
98
|
obis_info.emplace_back(server_id, val_list_entry);
|
@@ -103,7 +101,7 @@ std::vector<ObisInfo> SmlFile::get_obis_info() {
|
|
103
101
|
return obis_info;
|
104
102
|
}
|
105
103
|
|
106
|
-
std::string bytes_repr(const
|
104
|
+
std::string bytes_repr(const BytesView &buffer) {
|
107
105
|
std::string repr;
|
108
106
|
for (auto const value : buffer) {
|
109
107
|
repr += str_sprintf("%02x", value & 0xff);
|
@@ -111,7 +109,7 @@ std::string bytes_repr(const bytes &buffer) {
|
|
111
109
|
return repr;
|
112
110
|
}
|
113
111
|
|
114
|
-
uint64_t bytes_to_uint(const
|
112
|
+
uint64_t bytes_to_uint(const BytesView &buffer) {
|
115
113
|
uint64_t val = 0;
|
116
114
|
for (auto const value : buffer) {
|
117
115
|
val = (val << 8) + value;
|
@@ -119,7 +117,7 @@ uint64_t bytes_to_uint(const bytes &buffer) {
|
|
119
117
|
return val;
|
120
118
|
}
|
121
119
|
|
122
|
-
int64_t bytes_to_int(const
|
120
|
+
int64_t bytes_to_int(const BytesView &buffer) {
|
123
121
|
uint64_t tmp = bytes_to_uint(buffer);
|
124
122
|
int64_t val;
|
125
123
|
|
@@ -135,14 +133,14 @@ int64_t bytes_to_int(const bytes &buffer) {
|
|
135
133
|
return val;
|
136
134
|
}
|
137
135
|
|
138
|
-
std::string bytes_to_string(const
|
136
|
+
std::string bytes_to_string(const BytesView &buffer) { return std::string(buffer.begin(), buffer.end()); }
|
139
137
|
|
140
|
-
ObisInfo::ObisInfo(
|
138
|
+
ObisInfo::ObisInfo(const BytesView &server_id, const SmlNode &val_list_entry) : server_id(server_id) {
|
141
139
|
this->code = val_list_entry.nodes[0].value_bytes;
|
142
140
|
this->status = val_list_entry.nodes[1].value_bytes;
|
143
141
|
this->unit = bytes_to_uint(val_list_entry.nodes[3].value_bytes);
|
144
142
|
this->scaler = bytes_to_int(val_list_entry.nodes[4].value_bytes);
|
145
|
-
|
143
|
+
const auto &value_node = val_list_entry.nodes[5];
|
146
144
|
this->value = value_node.value_bytes;
|
147
145
|
this->value_type = value_node.type;
|
148
146
|
}
|
@@ -1,5 +1,6 @@
|
|
1
1
|
#pragma once
|
2
2
|
|
3
|
+
#include <cassert>
|
3
4
|
#include <cstdint>
|
4
5
|
#include <cstdio>
|
5
6
|
#include <string>
|
@@ -11,44 +12,73 @@ namespace sml {
|
|
11
12
|
|
12
13
|
using bytes = std::vector<uint8_t>;
|
13
14
|
|
15
|
+
class BytesView {
|
16
|
+
public:
|
17
|
+
BytesView() noexcept = default;
|
18
|
+
|
19
|
+
explicit BytesView(const uint8_t *first, size_t count) noexcept : data_{first}, count_{count} {}
|
20
|
+
|
21
|
+
explicit BytesView(const bytes &bytes) noexcept : data_{bytes.data()}, count_{bytes.size()} {}
|
22
|
+
|
23
|
+
size_t size() const noexcept { return count_; }
|
24
|
+
|
25
|
+
uint8_t operator[](size_t index) const noexcept {
|
26
|
+
assert(index < count_);
|
27
|
+
return data_[index];
|
28
|
+
}
|
29
|
+
|
30
|
+
BytesView subview(size_t offset, size_t count) const noexcept {
|
31
|
+
assert(offset + count <= count_);
|
32
|
+
return BytesView{data_ + offset, count};
|
33
|
+
}
|
34
|
+
|
35
|
+
const uint8_t *begin() const noexcept { return data_; }
|
36
|
+
|
37
|
+
const uint8_t *end() const noexcept { return data_ + count_; }
|
38
|
+
|
39
|
+
private:
|
40
|
+
const uint8_t *data_ = nullptr;
|
41
|
+
size_t count_ = 0;
|
42
|
+
};
|
43
|
+
|
14
44
|
class SmlNode {
|
15
45
|
public:
|
16
46
|
uint8_t type;
|
17
|
-
|
47
|
+
BytesView value_bytes;
|
18
48
|
std::vector<SmlNode> nodes;
|
19
49
|
};
|
20
50
|
|
21
51
|
class ObisInfo {
|
22
52
|
public:
|
23
|
-
ObisInfo(
|
24
|
-
|
25
|
-
|
26
|
-
|
53
|
+
ObisInfo(const BytesView &server_id, const SmlNode &val_list_entry);
|
54
|
+
BytesView server_id;
|
55
|
+
BytesView code;
|
56
|
+
BytesView status;
|
27
57
|
char unit;
|
28
58
|
char scaler;
|
29
|
-
|
59
|
+
BytesView value;
|
30
60
|
uint16_t value_type;
|
31
61
|
std::string code_repr() const;
|
32
62
|
};
|
33
63
|
|
34
64
|
class SmlFile {
|
35
65
|
public:
|
36
|
-
SmlFile(
|
66
|
+
SmlFile(const BytesView &buffer);
|
37
67
|
bool setup_node(SmlNode *node);
|
38
68
|
std::vector<SmlNode> messages;
|
39
69
|
std::vector<ObisInfo> get_obis_info();
|
40
70
|
|
41
71
|
protected:
|
42
|
-
const
|
72
|
+
const BytesView buffer_;
|
43
73
|
size_t pos_;
|
44
74
|
};
|
45
75
|
|
46
|
-
std::string bytes_repr(const
|
76
|
+
std::string bytes_repr(const BytesView &buffer);
|
47
77
|
|
48
|
-
uint64_t bytes_to_uint(const
|
78
|
+
uint64_t bytes_to_uint(const BytesView &buffer);
|
49
79
|
|
50
|
-
int64_t bytes_to_int(const
|
80
|
+
int64_t bytes_to_int(const BytesView &buffer);
|
51
81
|
|
52
|
-
std::string bytes_to_string(const
|
82
|
+
std::string bytes_to_string(const BytesView &buffer);
|
53
83
|
} // namespace sml
|
54
84
|
} // namespace esphome
|
@@ -441,9 +441,10 @@ void AudioPipeline::decode_task(void *params) {
|
|
441
441
|
pdFALSE, // Wait for all the bits,
|
442
442
|
portMAX_DELAY); // Block indefinitely until bit is set
|
443
443
|
|
444
|
+
xEventGroupClearBits(this_pipeline->event_group_,
|
445
|
+
EventGroupBits::DECODER_MESSAGE_FINISHED | EventGroupBits::READER_MESSAGE_LOADED_MEDIA_TYPE);
|
446
|
+
|
444
447
|
if (!(event_bits & EventGroupBits::PIPELINE_COMMAND_STOP)) {
|
445
|
-
xEventGroupClearBits(this_pipeline->event_group_,
|
446
|
-
EventGroupBits::DECODER_MESSAGE_FINISHED | EventGroupBits::READER_MESSAGE_LOADED_MEDIA_TYPE);
|
447
448
|
InfoErrorEvent event;
|
448
449
|
event.source = InfoErrorSource::DECODER;
|
449
450
|
|
esphome/config_validation.py
CHANGED
@@ -1499,30 +1499,9 @@ def dimensions(value):
|
|
1499
1499
|
|
1500
1500
|
|
1501
1501
|
def directory(value):
|
1502
|
-
import json
|
1503
|
-
|
1504
1502
|
value = string(value)
|
1505
1503
|
path = CORE.relative_config_path(value)
|
1506
1504
|
|
1507
|
-
if CORE.vscode and (
|
1508
|
-
not CORE.ace or os.path.abspath(path) == os.path.abspath(CORE.config_path)
|
1509
|
-
):
|
1510
|
-
print(
|
1511
|
-
json.dumps(
|
1512
|
-
{
|
1513
|
-
"type": "check_directory_exists",
|
1514
|
-
"path": path,
|
1515
|
-
}
|
1516
|
-
)
|
1517
|
-
)
|
1518
|
-
data = json.loads(input())
|
1519
|
-
assert data["type"] == "directory_exists_response"
|
1520
|
-
if data["content"]:
|
1521
|
-
return value
|
1522
|
-
raise Invalid(
|
1523
|
-
f"Could not find directory '{path}'. Please make sure it exists (full path: {os.path.abspath(path)})."
|
1524
|
-
)
|
1525
|
-
|
1526
1505
|
if not os.path.exists(path):
|
1527
1506
|
raise Invalid(
|
1528
1507
|
f"Could not find directory '{path}'. Please make sure it exists (full path: {os.path.abspath(path)})."
|
@@ -1535,30 +1514,9 @@ def directory(value):
|
|
1535
1514
|
|
1536
1515
|
|
1537
1516
|
def file_(value):
|
1538
|
-
import json
|
1539
|
-
|
1540
1517
|
value = string(value)
|
1541
1518
|
path = CORE.relative_config_path(value)
|
1542
1519
|
|
1543
|
-
if CORE.vscode and (
|
1544
|
-
not CORE.ace or os.path.abspath(path) == os.path.abspath(CORE.config_path)
|
1545
|
-
):
|
1546
|
-
print(
|
1547
|
-
json.dumps(
|
1548
|
-
{
|
1549
|
-
"type": "check_file_exists",
|
1550
|
-
"path": path,
|
1551
|
-
}
|
1552
|
-
)
|
1553
|
-
)
|
1554
|
-
data = json.loads(input())
|
1555
|
-
assert data["type"] == "file_exists_response"
|
1556
|
-
if data["content"]:
|
1557
|
-
return value
|
1558
|
-
raise Invalid(
|
1559
|
-
f"Could not find file '{path}'. Please make sure it exists (full path: {os.path.abspath(path)})."
|
1560
|
-
)
|
1561
|
-
|
1562
1520
|
if not os.path.exists(path):
|
1563
1521
|
raise Invalid(
|
1564
1522
|
f"Could not find file '{path}'. Please make sure it exists (full path: {os.path.abspath(path)})."
|
esphome/const.py
CHANGED
esphome/core/__init__.py
CHANGED
esphome/vscode.py
CHANGED
@@ -78,28 +78,47 @@ def _print_file_read_event(path: str) -> None:
|
|
78
78
|
)
|
79
79
|
|
80
80
|
|
81
|
+
def _request_and_get_stream_on_stdin(fname: str) -> StringIO:
|
82
|
+
_print_file_read_event(fname)
|
83
|
+
raw_yaml_stream = StringIO(_read_file_content_from_json_on_stdin())
|
84
|
+
return raw_yaml_stream
|
85
|
+
|
86
|
+
|
87
|
+
def _vscode_loader(fname: str) -> dict[str, Any]:
|
88
|
+
raw_yaml_stream = _request_and_get_stream_on_stdin(fname)
|
89
|
+
# it is required to set the name on StringIO so document on start_mark
|
90
|
+
# is set properly. Otherwise it is initialized with "<file>"
|
91
|
+
raw_yaml_stream.name = fname
|
92
|
+
return parse_yaml(fname, raw_yaml_stream, _vscode_loader)
|
93
|
+
|
94
|
+
|
95
|
+
def _ace_loader(fname: str) -> dict[str, Any]:
|
96
|
+
raw_yaml_stream = _request_and_get_stream_on_stdin(fname)
|
97
|
+
return parse_yaml(fname, raw_yaml_stream)
|
98
|
+
|
99
|
+
|
81
100
|
def read_config(args):
|
82
101
|
while True:
|
83
102
|
CORE.reset()
|
84
103
|
data = json.loads(input())
|
85
|
-
assert data["type"] == "validate"
|
104
|
+
assert data["type"] == "validate" or data["type"] == "exit"
|
105
|
+
if data["type"] == "exit":
|
106
|
+
return
|
86
107
|
CORE.vscode = True
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
CORE.config_path = os.path.join(args.configuration, f)
|
108
|
+
if args.ace: # Running from ESPHome Compiler dashboard, not vscode
|
109
|
+
CORE.config_path = os.path.join(args.configuration, data["file"])
|
110
|
+
loader = _ace_loader
|
91
111
|
else:
|
92
112
|
CORE.config_path = data["file"]
|
113
|
+
loader = _vscode_loader
|
93
114
|
|
94
115
|
file_name = CORE.config_path
|
95
|
-
_print_file_read_event(file_name)
|
96
|
-
raw_yaml = _read_file_content_from_json_on_stdin()
|
97
116
|
command_line_substitutions: dict[str, Any] = (
|
98
117
|
dict(args.substitution) if args.substitution else {}
|
99
118
|
)
|
100
119
|
vs = VSCodeResult()
|
101
120
|
try:
|
102
|
-
config =
|
121
|
+
config = loader(file_name)
|
103
122
|
res = validate_config(config, command_line_substitutions)
|
104
123
|
except Exception as err: # pylint: disable=broad-except
|
105
124
|
vs.add_yaml_error(str(err))
|
esphome/yaml_util.py
CHANGED
@@ -3,12 +3,12 @@ from __future__ import annotations
|
|
3
3
|
import fnmatch
|
4
4
|
import functools
|
5
5
|
import inspect
|
6
|
-
from io import TextIOWrapper
|
6
|
+
from io import BytesIO, TextIOBase, TextIOWrapper
|
7
7
|
from ipaddress import _BaseAddress
|
8
8
|
import logging
|
9
9
|
import math
|
10
10
|
import os
|
11
|
-
from typing import Any
|
11
|
+
from typing import Any, Callable
|
12
12
|
import uuid
|
13
13
|
|
14
14
|
import yaml
|
@@ -69,7 +69,10 @@ class ESPForceValue:
|
|
69
69
|
pass
|
70
70
|
|
71
71
|
|
72
|
-
def make_data_base(
|
72
|
+
def make_data_base(
|
73
|
+
value, from_database: ESPHomeDataBase = None
|
74
|
+
) -> ESPHomeDataBase | Any:
|
75
|
+
"""Wrap a value in a ESPHomeDataBase object."""
|
73
76
|
try:
|
74
77
|
value = add_class_to_obj(value, ESPHomeDataBase)
|
75
78
|
if from_database is not None:
|
@@ -102,6 +105,11 @@ def _add_data_ref(fn):
|
|
102
105
|
class ESPHomeLoaderMixin:
|
103
106
|
"""Loader class that keeps track of line numbers."""
|
104
107
|
|
108
|
+
def __init__(self, name: str, yaml_loader: Callable[[str], dict[str, Any]]) -> None:
|
109
|
+
"""Initialize the loader."""
|
110
|
+
self.name = name
|
111
|
+
self.yaml_loader = yaml_loader
|
112
|
+
|
105
113
|
@_add_data_ref
|
106
114
|
def construct_yaml_int(self, node):
|
107
115
|
return super().construct_yaml_int(node)
|
@@ -127,7 +135,7 @@ class ESPHomeLoaderMixin:
|
|
127
135
|
return super().construct_yaml_seq(node)
|
128
136
|
|
129
137
|
@_add_data_ref
|
130
|
-
def construct_yaml_map(self, node):
|
138
|
+
def construct_yaml_map(self, node: yaml.MappingNode) -> OrderedDict[str, Any]:
|
131
139
|
"""Traverses the given mapping node and returns a list of constructed key-value pairs."""
|
132
140
|
assert isinstance(node, yaml.MappingNode)
|
133
141
|
# A list of key-value pairs we find in the current mapping
|
@@ -231,7 +239,7 @@ class ESPHomeLoaderMixin:
|
|
231
239
|
return OrderedDict(pairs)
|
232
240
|
|
233
241
|
@_add_data_ref
|
234
|
-
def construct_env_var(self, node):
|
242
|
+
def construct_env_var(self, node: yaml.Node) -> str:
|
235
243
|
args = node.value.split()
|
236
244
|
# Check for a default value
|
237
245
|
if len(args) > 1:
|
@@ -243,23 +251,23 @@ class ESPHomeLoaderMixin:
|
|
243
251
|
)
|
244
252
|
|
245
253
|
@property
|
246
|
-
def _directory(self):
|
254
|
+
def _directory(self) -> str:
|
247
255
|
return os.path.dirname(self.name)
|
248
256
|
|
249
|
-
def _rel_path(self, *args):
|
257
|
+
def _rel_path(self, *args: str) -> str:
|
250
258
|
return os.path.join(self._directory, *args)
|
251
259
|
|
252
260
|
@_add_data_ref
|
253
|
-
def construct_secret(self, node):
|
261
|
+
def construct_secret(self, node: yaml.Node) -> str:
|
254
262
|
try:
|
255
|
-
secrets =
|
263
|
+
secrets = self.yaml_loader(self._rel_path(SECRET_YAML))
|
256
264
|
except EsphomeError as e:
|
257
265
|
if self.name == CORE.config_path:
|
258
266
|
raise e
|
259
267
|
try:
|
260
268
|
main_config_dir = os.path.dirname(CORE.config_path)
|
261
269
|
main_secret_yml = os.path.join(main_config_dir, SECRET_YAML)
|
262
|
-
secrets =
|
270
|
+
secrets = self.yaml_loader(main_secret_yml)
|
263
271
|
except EsphomeError as er:
|
264
272
|
raise EsphomeError(f"{e}\n{er}") from er
|
265
273
|
|
@@ -272,7 +280,9 @@ class ESPHomeLoaderMixin:
|
|
272
280
|
return val
|
273
281
|
|
274
282
|
@_add_data_ref
|
275
|
-
def construct_include(
|
283
|
+
def construct_include(
|
284
|
+
self, node: yaml.Node
|
285
|
+
) -> dict[str, Any] | OrderedDict[str, Any]:
|
276
286
|
from esphome.const import CONF_VARS
|
277
287
|
|
278
288
|
def extract_file_vars(node):
|
@@ -290,71 +300,93 @@ class ESPHomeLoaderMixin:
|
|
290
300
|
else:
|
291
301
|
file, vars = node.value, None
|
292
302
|
|
293
|
-
result =
|
303
|
+
result = self.yaml_loader(self._rel_path(file))
|
294
304
|
if not vars:
|
295
305
|
vars = {}
|
296
306
|
result = substitute_vars(result, vars)
|
297
307
|
return result
|
298
308
|
|
299
309
|
@_add_data_ref
|
300
|
-
def construct_include_dir_list(self, node):
|
310
|
+
def construct_include_dir_list(self, node: yaml.Node) -> list[dict[str, Any]]:
|
301
311
|
files = filter_yaml_files(_find_files(self._rel_path(node.value), "*.yaml"))
|
302
|
-
return [
|
312
|
+
return [self.yaml_loader(f) for f in files]
|
303
313
|
|
304
314
|
@_add_data_ref
|
305
|
-
def construct_include_dir_merge_list(self, node):
|
315
|
+
def construct_include_dir_merge_list(self, node: yaml.Node) -> list[dict[str, Any]]:
|
306
316
|
files = filter_yaml_files(_find_files(self._rel_path(node.value), "*.yaml"))
|
307
317
|
merged_list = []
|
308
318
|
for fname in files:
|
309
|
-
loaded_yaml =
|
319
|
+
loaded_yaml = self.yaml_loader(fname)
|
310
320
|
if isinstance(loaded_yaml, list):
|
311
321
|
merged_list.extend(loaded_yaml)
|
312
322
|
return merged_list
|
313
323
|
|
314
324
|
@_add_data_ref
|
315
|
-
def construct_include_dir_named(
|
325
|
+
def construct_include_dir_named(
|
326
|
+
self, node: yaml.Node
|
327
|
+
) -> OrderedDict[str, dict[str, Any]]:
|
316
328
|
files = filter_yaml_files(_find_files(self._rel_path(node.value), "*.yaml"))
|
317
329
|
mapping = OrderedDict()
|
318
330
|
for fname in files:
|
319
331
|
filename = os.path.splitext(os.path.basename(fname))[0]
|
320
|
-
mapping[filename] =
|
332
|
+
mapping[filename] = self.yaml_loader(fname)
|
321
333
|
return mapping
|
322
334
|
|
323
335
|
@_add_data_ref
|
324
|
-
def construct_include_dir_merge_named(
|
336
|
+
def construct_include_dir_merge_named(
|
337
|
+
self, node: yaml.Node
|
338
|
+
) -> OrderedDict[str, dict[str, Any]]:
|
325
339
|
files = filter_yaml_files(_find_files(self._rel_path(node.value), "*.yaml"))
|
326
340
|
mapping = OrderedDict()
|
327
341
|
for fname in files:
|
328
|
-
loaded_yaml =
|
342
|
+
loaded_yaml = self.yaml_loader(fname)
|
329
343
|
if isinstance(loaded_yaml, dict):
|
330
344
|
mapping.update(loaded_yaml)
|
331
345
|
return mapping
|
332
346
|
|
333
347
|
@_add_data_ref
|
334
|
-
def construct_lambda(self, node):
|
348
|
+
def construct_lambda(self, node: yaml.Node) -> Lambda:
|
335
349
|
return Lambda(str(node.value))
|
336
350
|
|
337
351
|
@_add_data_ref
|
338
|
-
def construct_force(self, node):
|
352
|
+
def construct_force(self, node: yaml.Node) -> ESPForceValue:
|
339
353
|
obj = self.construct_scalar(node)
|
340
354
|
return add_class_to_obj(obj, ESPForceValue)
|
341
355
|
|
342
356
|
@_add_data_ref
|
343
|
-
def construct_extend(self, node):
|
357
|
+
def construct_extend(self, node: yaml.Node) -> Extend:
|
344
358
|
return Extend(str(node.value))
|
345
359
|
|
346
360
|
@_add_data_ref
|
347
|
-
def construct_remove(self, node):
|
361
|
+
def construct_remove(self, node: yaml.Node) -> Remove:
|
348
362
|
return Remove(str(node.value))
|
349
363
|
|
350
364
|
|
351
365
|
class ESPHomeLoader(ESPHomeLoaderMixin, FastestAvailableSafeLoader):
|
352
366
|
"""Loader class that keeps track of line numbers."""
|
353
367
|
|
368
|
+
def __init__(
|
369
|
+
self,
|
370
|
+
stream: TextIOBase | BytesIO,
|
371
|
+
name: str,
|
372
|
+
yaml_loader: Callable[[str], dict[str, Any]],
|
373
|
+
) -> None:
|
374
|
+
FastestAvailableSafeLoader.__init__(self, stream)
|
375
|
+
ESPHomeLoaderMixin.__init__(self, name, yaml_loader)
|
376
|
+
|
354
377
|
|
355
378
|
class ESPHomePurePythonLoader(ESPHomeLoaderMixin, PurePythonLoader):
|
356
379
|
"""Loader class that keeps track of line numbers."""
|
357
380
|
|
381
|
+
def __init__(
|
382
|
+
self,
|
383
|
+
stream: TextIOBase | BytesIO,
|
384
|
+
name: str,
|
385
|
+
yaml_loader: Callable[[str], dict[str, Any]],
|
386
|
+
) -> None:
|
387
|
+
PurePythonLoader.__init__(self, stream)
|
388
|
+
ESPHomeLoaderMixin.__init__(self, name, yaml_loader)
|
389
|
+
|
358
390
|
|
359
391
|
for _loader in (ESPHomeLoader, ESPHomePurePythonLoader):
|
360
392
|
_loader.add_constructor("tag:yaml.org,2002:int", _loader.construct_yaml_int)
|
@@ -388,17 +420,30 @@ def load_yaml(fname: str, clear_secrets: bool = True) -> Any:
|
|
388
420
|
return _load_yaml_internal(fname)
|
389
421
|
|
390
422
|
|
391
|
-
def
|
423
|
+
def _load_yaml_internal(fname: str) -> Any:
|
424
|
+
"""Load a YAML file."""
|
425
|
+
try:
|
426
|
+
with open(fname, encoding="utf-8") as f_handle:
|
427
|
+
return parse_yaml(fname, f_handle)
|
428
|
+
except (UnicodeDecodeError, OSError) as err:
|
429
|
+
raise EsphomeError(f"Error reading file {fname}: {err}") from err
|
430
|
+
|
431
|
+
|
432
|
+
def parse_yaml(
|
433
|
+
file_name: str, file_handle: TextIOWrapper, yaml_loader=_load_yaml_internal
|
434
|
+
) -> Any:
|
392
435
|
"""Parse a YAML file."""
|
393
436
|
try:
|
394
|
-
return _load_yaml_internal_with_type(
|
437
|
+
return _load_yaml_internal_with_type(
|
438
|
+
ESPHomeLoader, file_name, file_handle, yaml_loader
|
439
|
+
)
|
395
440
|
except EsphomeError:
|
396
441
|
# Loading failed, so we now load with the Python loader which has more
|
397
442
|
# readable exceptions
|
398
443
|
# Rewind the stream so we can try again
|
399
444
|
file_handle.seek(0, 0)
|
400
445
|
return _load_yaml_internal_with_type(
|
401
|
-
ESPHomePurePythonLoader, file_name, file_handle
|
446
|
+
ESPHomePurePythonLoader, file_name, file_handle, yaml_loader
|
402
447
|
)
|
403
448
|
|
404
449
|
|
@@ -435,23 +480,14 @@ def substitute_vars(config, vars):
|
|
435
480
|
return result
|
436
481
|
|
437
482
|
|
438
|
-
def _load_yaml_internal(fname: str) -> Any:
|
439
|
-
"""Load a YAML file."""
|
440
|
-
try:
|
441
|
-
with open(fname, encoding="utf-8") as f_handle:
|
442
|
-
return parse_yaml(fname, f_handle)
|
443
|
-
except (UnicodeDecodeError, OSError) as err:
|
444
|
-
raise EsphomeError(f"Error reading file {fname}: {err}") from err
|
445
|
-
|
446
|
-
|
447
483
|
def _load_yaml_internal_with_type(
|
448
484
|
loader_type: type[ESPHomeLoader] | type[ESPHomePurePythonLoader],
|
449
485
|
fname: str,
|
450
486
|
content: TextIOWrapper,
|
487
|
+
yaml_loader: Any,
|
451
488
|
) -> Any:
|
452
489
|
"""Load a YAML file."""
|
453
|
-
loader = loader_type(content)
|
454
|
-
loader.name = fname
|
490
|
+
loader = loader_type(content, fname, yaml_loader)
|
455
491
|
try:
|
456
492
|
return loader.get_single_data() or OrderedDict()
|
457
493
|
except yaml.YAMLError as exc:
|
@@ -470,7 +506,7 @@ def dump(dict_, show_secrets=False):
|
|
470
506
|
)
|
471
507
|
|
472
508
|
|
473
|
-
def _is_file_valid(name):
|
509
|
+
def _is_file_valid(name: str) -> bool:
|
474
510
|
"""Decide if a file is valid."""
|
475
511
|
return not name.startswith(".")
|
476
512
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: esphome
|
3
|
-
Version: 2025.4.
|
3
|
+
Version: 2025.4.0b3
|
4
4
|
Summary: ESPHome is a system to configure your microcontrollers by simple yet powerful configuration files and control them remotely through Home Automation systems.
|
5
5
|
Author-email: The ESPHome Authors <esphome@openhomefoundation.org>
|
6
6
|
License: MIT
|
@@ -37,9 +37,9 @@ Requires-Dist: pyserial==3.5
|
|
37
37
|
Requires-Dist: platformio==6.1.18
|
38
38
|
Requires-Dist: esptool==4.8.1
|
39
39
|
Requires-Dist: click==8.1.7
|
40
|
-
Requires-Dist: esphome-dashboard==
|
41
|
-
Requires-Dist: aioesphomeapi==29.
|
42
|
-
Requires-Dist: zeroconf==0.146.
|
40
|
+
Requires-Dist: esphome-dashboard==20250415.0
|
41
|
+
Requires-Dist: aioesphomeapi==29.10.0
|
42
|
+
Requires-Dist: zeroconf==0.146.5
|
43
43
|
Requires-Dist: puremagic==1.28
|
44
44
|
Requires-Dist: ruamel.yaml==0.18.10
|
45
45
|
Requires-Dist: esphome-glyphsets==0.2.0
|