esphome 2025.4.0__py3-none-any.whl → 2025.4.0b2__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 CHANGED
@@ -375,12 +375,10 @@ def upload_program(config, args, host):
375
375
  password = ota_conf.get(CONF_PASSWORD, "")
376
376
 
377
377
  if (
378
- CONF_MQTT in config # pylint: disable=too-many-boolean-expressions
378
+ not is_ip_address(CORE.address) # pylint: disable=too-many-boolean-expressions
379
+ and (get_port_type(host) == "MQTT" or config[CONF_MDNS][CONF_DISABLED])
380
+ and CONF_MQTT in config
379
381
  and (not args.device or args.device in ("MQTT", "OTA"))
380
- and (
381
- ((config[CONF_MDNS][CONF_DISABLED]) and not is_ip_address(CORE.address))
382
- or get_port_type(host) == "MQTT"
383
- )
384
382
  ):
385
383
  from esphome import mqtt
386
384
 
@@ -128,7 +128,7 @@ void AM2315C::update() {
128
128
  data[2] = 0x00;
129
129
  if (this->write(data, 3) != i2c::ERROR_OK) {
130
130
  ESP_LOGE(TAG, "Write failed!");
131
- this->status_set_warning();
131
+ this->mark_failed();
132
132
  return;
133
133
  }
134
134
 
@@ -138,12 +138,12 @@ void AM2315C::update() {
138
138
  uint8_t status = 0;
139
139
  if (this->read(&status, 1) != i2c::ERROR_OK) {
140
140
  ESP_LOGE(TAG, "Read failed!");
141
- this->status_set_warning();
141
+ this->mark_failed();
142
142
  return;
143
143
  }
144
144
  if ((status & 0x80) == 0x80) {
145
145
  ESP_LOGE(TAG, "HW still busy!");
146
- this->status_set_warning();
146
+ this->mark_failed();
147
147
  return;
148
148
  }
149
149
 
@@ -151,7 +151,7 @@ void AM2315C::update() {
151
151
  uint8_t data[7];
152
152
  if (this->read(data, 7) != i2c::ERROR_OK) {
153
153
  ESP_LOGE(TAG, "Read failed!");
154
- this->status_set_warning();
154
+ this->mark_failed();
155
155
  return;
156
156
  }
157
157
 
@@ -52,8 +52,9 @@ void Sml::loop() {
52
52
  break;
53
53
 
54
54
  // remove start/end sequence
55
- this->process_sml_file_(
56
- BytesView(this->sml_data_).subview(START_SEQ.size(), this->sml_data_.size() - START_SEQ.size() - 8));
55
+ this->sml_data_.erase(this->sml_data_.begin(), this->sml_data_.begin() + START_SEQ.size());
56
+ this->sml_data_.resize(this->sml_data_.size() - 8);
57
+ this->process_sml_file_(this->sml_data_);
57
58
  }
58
59
  break;
59
60
  };
@@ -65,8 +66,8 @@ void Sml::add_on_data_callback(std::function<void(std::vector<uint8_t>, bool)> &
65
66
  this->data_callbacks_.add(std::move(callback));
66
67
  }
67
68
 
68
- void Sml::process_sml_file_(const BytesView &sml_data) {
69
- SmlFile sml_file(sml_data);
69
+ void Sml::process_sml_file_(const bytes &sml_data) {
70
+ SmlFile sml_file = SmlFile(sml_data);
70
71
  std::vector<ObisInfo> obis_info = sml_file.get_obis_info();
71
72
  this->publish_obis_info_(obis_info);
72
73
 
@@ -74,7 +75,6 @@ void Sml::process_sml_file_(const BytesView &sml_data) {
74
75
  }
75
76
 
76
77
  void Sml::log_obis_info_(const std::vector<ObisInfo> &obis_info_vec) {
77
- #ifdef ESPHOME_LOG_HAS_DEBUG
78
78
  ESP_LOGD(TAG, "OBIS info:");
79
79
  for (auto const &obis_info : obis_info_vec) {
80
80
  std::string info;
@@ -83,7 +83,6 @@ void Sml::log_obis_info_(const std::vector<ObisInfo> &obis_info_vec) {
83
83
  info += " [0x" + bytes_repr(obis_info.value) + "]";
84
84
  ESP_LOGD(TAG, "%s", info.c_str());
85
85
  }
86
- #endif
87
86
  }
88
87
 
89
88
  void Sml::publish_obis_info_(const std::vector<ObisInfo> &obis_info_vec) {
@@ -93,11 +92,10 @@ void Sml::publish_obis_info_(const std::vector<ObisInfo> &obis_info_vec) {
93
92
  }
94
93
 
95
94
  void Sml::publish_value_(const ObisInfo &obis_info) {
96
- const auto obis_code = obis_info.code_repr();
97
95
  for (auto const &sml_listener : sml_listeners_) {
98
96
  if ((!sml_listener->server_id.empty()) && (bytes_repr(obis_info.server_id) != sml_listener->server_id))
99
97
  continue;
100
- if (obis_code != sml_listener->obis_code)
98
+ if (obis_info.code_repr() != sml_listener->obis_code)
101
99
  continue;
102
100
  sml_listener->publish_val(obis_info);
103
101
  }
@@ -27,7 +27,7 @@ class Sml : public Component, public uart::UARTDevice {
27
27
  void add_on_data_callback(std::function<void(std::vector<uint8_t>, bool)> &&callback);
28
28
 
29
29
  protected:
30
- void process_sml_file_(const BytesView &sml_data);
30
+ void process_sml_file_(const bytes &sml_data);
31
31
  void log_obis_info_(const std::vector<ObisInfo> &obis_info_vec);
32
32
  void publish_obis_info_(const std::vector<ObisInfo> &obis_info_vec);
33
33
  char check_start_end_bytes_(uint8_t byte);
@@ -5,17 +5,17 @@
5
5
  namespace esphome {
6
6
  namespace sml {
7
7
 
8
- SmlFile::SmlFile(const BytesView &buffer) : buffer_(buffer) {
8
+ SmlFile::SmlFile(bytes buffer) : buffer_(std::move(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 = SmlNode();
16
16
  if (!this->setup_node(&message))
17
17
  break;
18
- this->messages.emplace_back(std::move(message));
18
+ this->messages.emplace_back(message);
19
19
  }
20
20
  }
21
21
 
@@ -62,20 +62,22 @@ 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();
65
67
 
66
68
  if (type == SML_LIST) {
67
69
  node->nodes.reserve(length);
68
70
  for (size_t i = 0; i != length; i++) {
69
- SmlNode child_node;
71
+ SmlNode child_node = SmlNode();
70
72
  if (!this->setup_node(&child_node))
71
73
  return false;
72
- node->nodes.emplace_back(std::move(child_node));
74
+ node->nodes.emplace_back(child_node);
73
75
  }
74
76
  } else {
75
77
  // Value starts at the current position
76
78
  // Value ends "length" bytes later,
77
79
  // (since the TL field is counted but already subtracted from length)
78
- node->value_bytes = buffer_.subview(this->pos_, length);
80
+ node->value_bytes = bytes(this->buffer_.begin() + this->pos_, this->buffer_.begin() + this->pos_ + length);
79
81
  // Increment the pointer past all consumed bytes
80
82
  this->pos_ += length;
81
83
  }
@@ -85,14 +87,14 @@ bool SmlFile::setup_node(SmlNode *node) {
85
87
  std::vector<ObisInfo> SmlFile::get_obis_info() {
86
88
  std::vector<ObisInfo> obis_info;
87
89
  for (auto const &message : messages) {
88
- const auto &message_body = message.nodes[3];
90
+ SmlNode message_body = message.nodes[3];
89
91
  uint16_t message_type = bytes_to_uint(message_body.nodes[0].value_bytes);
90
92
  if (message_type != SML_GET_LIST_RES)
91
93
  continue;
92
94
 
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];
95
+ SmlNode get_list_response = message_body.nodes[1];
96
+ bytes server_id = get_list_response.nodes[1].value_bytes;
97
+ SmlNode val_list = get_list_response.nodes[4];
96
98
 
97
99
  for (auto const &val_list_entry : val_list.nodes) {
98
100
  obis_info.emplace_back(server_id, val_list_entry);
@@ -101,7 +103,7 @@ std::vector<ObisInfo> SmlFile::get_obis_info() {
101
103
  return obis_info;
102
104
  }
103
105
 
104
- std::string bytes_repr(const BytesView &buffer) {
106
+ std::string bytes_repr(const bytes &buffer) {
105
107
  std::string repr;
106
108
  for (auto const value : buffer) {
107
109
  repr += str_sprintf("%02x", value & 0xff);
@@ -109,7 +111,7 @@ std::string bytes_repr(const BytesView &buffer) {
109
111
  return repr;
110
112
  }
111
113
 
112
- uint64_t bytes_to_uint(const BytesView &buffer) {
114
+ uint64_t bytes_to_uint(const bytes &buffer) {
113
115
  uint64_t val = 0;
114
116
  for (auto const value : buffer) {
115
117
  val = (val << 8) + value;
@@ -117,7 +119,7 @@ uint64_t bytes_to_uint(const BytesView &buffer) {
117
119
  return val;
118
120
  }
119
121
 
120
- int64_t bytes_to_int(const BytesView &buffer) {
122
+ int64_t bytes_to_int(const bytes &buffer) {
121
123
  uint64_t tmp = bytes_to_uint(buffer);
122
124
  int64_t val;
123
125
 
@@ -133,14 +135,14 @@ int64_t bytes_to_int(const BytesView &buffer) {
133
135
  return val;
134
136
  }
135
137
 
136
- std::string bytes_to_string(const BytesView &buffer) { return std::string(buffer.begin(), buffer.end()); }
138
+ std::string bytes_to_string(const bytes &buffer) { return std::string(buffer.begin(), buffer.end()); }
137
139
 
138
- ObisInfo::ObisInfo(const BytesView &server_id, const SmlNode &val_list_entry) : server_id(server_id) {
140
+ ObisInfo::ObisInfo(bytes server_id, SmlNode val_list_entry) : server_id(std::move(server_id)) {
139
141
  this->code = val_list_entry.nodes[0].value_bytes;
140
142
  this->status = val_list_entry.nodes[1].value_bytes;
141
143
  this->unit = bytes_to_uint(val_list_entry.nodes[3].value_bytes);
142
144
  this->scaler = bytes_to_int(val_list_entry.nodes[4].value_bytes);
143
- const auto &value_node = val_list_entry.nodes[5];
145
+ SmlNode value_node = val_list_entry.nodes[5];
144
146
  this->value = value_node.value_bytes;
145
147
  this->value_type = value_node.type;
146
148
  }
@@ -1,6 +1,5 @@
1
1
  #pragma once
2
2
 
3
- #include <cassert>
4
3
  #include <cstdint>
5
4
  #include <cstdio>
6
5
  #include <string>
@@ -12,73 +11,44 @@ namespace sml {
12
11
 
13
12
  using bytes = std::vector<uint8_t>;
14
13
 
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
-
44
14
  class SmlNode {
45
15
  public:
46
16
  uint8_t type;
47
- BytesView value_bytes;
17
+ bytes value_bytes;
48
18
  std::vector<SmlNode> nodes;
49
19
  };
50
20
 
51
21
  class ObisInfo {
52
22
  public:
53
- ObisInfo(const BytesView &server_id, const SmlNode &val_list_entry);
54
- BytesView server_id;
55
- BytesView code;
56
- BytesView status;
23
+ ObisInfo(bytes server_id, SmlNode val_list_entry);
24
+ bytes server_id;
25
+ bytes code;
26
+ bytes status;
57
27
  char unit;
58
28
  char scaler;
59
- BytesView value;
29
+ bytes value;
60
30
  uint16_t value_type;
61
31
  std::string code_repr() const;
62
32
  };
63
33
 
64
34
  class SmlFile {
65
35
  public:
66
- SmlFile(const BytesView &buffer);
36
+ SmlFile(bytes buffer);
67
37
  bool setup_node(SmlNode *node);
68
38
  std::vector<SmlNode> messages;
69
39
  std::vector<ObisInfo> get_obis_info();
70
40
 
71
41
  protected:
72
- const BytesView buffer_;
42
+ const bytes buffer_;
73
43
  size_t pos_;
74
44
  };
75
45
 
76
- std::string bytes_repr(const BytesView &buffer);
46
+ std::string bytes_repr(const bytes &buffer);
77
47
 
78
- uint64_t bytes_to_uint(const BytesView &buffer);
48
+ uint64_t bytes_to_uint(const bytes &buffer);
79
49
 
80
- int64_t bytes_to_int(const BytesView &buffer);
50
+ int64_t bytes_to_int(const bytes &buffer);
81
51
 
82
- std::string bytes_to_string(const BytesView &buffer);
52
+ std::string bytes_to_string(const bytes &buffer);
83
53
  } // namespace sml
84
54
  } // namespace esphome
@@ -1499,9 +1499,30 @@ def dimensions(value):
1499
1499
 
1500
1500
 
1501
1501
  def directory(value):
1502
+ import json
1503
+
1502
1504
  value = string(value)
1503
1505
  path = CORE.relative_config_path(value)
1504
1506
 
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
+
1505
1526
  if not os.path.exists(path):
1506
1527
  raise Invalid(
1507
1528
  f"Could not find directory '{path}'. Please make sure it exists (full path: {os.path.abspath(path)})."
@@ -1514,9 +1535,30 @@ def directory(value):
1514
1535
 
1515
1536
 
1516
1537
  def file_(value):
1538
+ import json
1539
+
1517
1540
  value = string(value)
1518
1541
  path = CORE.relative_config_path(value)
1519
1542
 
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
+
1520
1562
  if not os.path.exists(path):
1521
1563
  raise Invalid(
1522
1564
  f"Could not find file '{path}'. Please make sure it exists (full path: {os.path.abspath(path)})."
esphome/const.py CHANGED
@@ -1,6 +1,6 @@
1
1
  """Constants used by esphome."""
2
2
 
3
- __version__ = "2025.4.0"
3
+ __version__ = "2025.4.0b2"
4
4
 
5
5
  ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
6
6
  VALID_SUBSTITUTIONS_CHARACTERS = (
esphome/core/__init__.py CHANGED
@@ -475,6 +475,7 @@ class EsphomeCore:
475
475
  self.dashboard = False
476
476
  # True if command is run from vscode api
477
477
  self.vscode = False
478
+ self.ace = False
478
479
  # The name of the node
479
480
  self.name: Optional[str] = None
480
481
  # The friendly name of the node
esphome/vscode.py CHANGED
@@ -78,47 +78,28 @@ 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
-
100
81
  def read_config(args):
101
82
  while True:
102
83
  CORE.reset()
103
84
  data = json.loads(input())
104
- assert data["type"] == "validate" or data["type"] == "exit"
105
- if data["type"] == "exit":
106
- return
85
+ assert data["type"] == "validate"
107
86
  CORE.vscode = True
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
87
+ CORE.ace = args.ace
88
+ f = data["file"]
89
+ if CORE.ace:
90
+ CORE.config_path = os.path.join(args.configuration, f)
111
91
  else:
112
92
  CORE.config_path = data["file"]
113
- loader = _vscode_loader
114
93
 
115
94
  file_name = CORE.config_path
95
+ _print_file_read_event(file_name)
96
+ raw_yaml = _read_file_content_from_json_on_stdin()
116
97
  command_line_substitutions: dict[str, Any] = (
117
98
  dict(args.substitution) if args.substitution else {}
118
99
  )
119
100
  vs = VSCodeResult()
120
101
  try:
121
- config = loader(file_name)
102
+ config = parse_yaml(file_name, StringIO(raw_yaml))
122
103
  res = validate_config(config, command_line_substitutions)
123
104
  except Exception as err: # pylint: disable=broad-except
124
105
  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 BytesIO, TextIOBase, TextIOWrapper
6
+ from io import TextIOWrapper
7
7
  from ipaddress import _BaseAddress
8
8
  import logging
9
9
  import math
10
10
  import os
11
- from typing import Any, Callable
11
+ from typing import Any
12
12
  import uuid
13
13
 
14
14
  import yaml
@@ -69,10 +69,7 @@ class ESPForceValue:
69
69
  pass
70
70
 
71
71
 
72
- def make_data_base(
73
- value, from_database: ESPHomeDataBase = None
74
- ) -> ESPHomeDataBase | Any:
75
- """Wrap a value in a ESPHomeDataBase object."""
72
+ def make_data_base(value, from_database: ESPHomeDataBase = None):
76
73
  try:
77
74
  value = add_class_to_obj(value, ESPHomeDataBase)
78
75
  if from_database is not None:
@@ -105,11 +102,6 @@ def _add_data_ref(fn):
105
102
  class ESPHomeLoaderMixin:
106
103
  """Loader class that keeps track of line numbers."""
107
104
 
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
-
113
105
  @_add_data_ref
114
106
  def construct_yaml_int(self, node):
115
107
  return super().construct_yaml_int(node)
@@ -135,7 +127,7 @@ class ESPHomeLoaderMixin:
135
127
  return super().construct_yaml_seq(node)
136
128
 
137
129
  @_add_data_ref
138
- def construct_yaml_map(self, node: yaml.MappingNode) -> OrderedDict[str, Any]:
130
+ def construct_yaml_map(self, node):
139
131
  """Traverses the given mapping node and returns a list of constructed key-value pairs."""
140
132
  assert isinstance(node, yaml.MappingNode)
141
133
  # A list of key-value pairs we find in the current mapping
@@ -239,7 +231,7 @@ class ESPHomeLoaderMixin:
239
231
  return OrderedDict(pairs)
240
232
 
241
233
  @_add_data_ref
242
- def construct_env_var(self, node: yaml.Node) -> str:
234
+ def construct_env_var(self, node):
243
235
  args = node.value.split()
244
236
  # Check for a default value
245
237
  if len(args) > 1:
@@ -251,23 +243,23 @@ class ESPHomeLoaderMixin:
251
243
  )
252
244
 
253
245
  @property
254
- def _directory(self) -> str:
246
+ def _directory(self):
255
247
  return os.path.dirname(self.name)
256
248
 
257
- def _rel_path(self, *args: str) -> str:
249
+ def _rel_path(self, *args):
258
250
  return os.path.join(self._directory, *args)
259
251
 
260
252
  @_add_data_ref
261
- def construct_secret(self, node: yaml.Node) -> str:
253
+ def construct_secret(self, node):
262
254
  try:
263
- secrets = self.yaml_loader(self._rel_path(SECRET_YAML))
255
+ secrets = _load_yaml_internal(self._rel_path(SECRET_YAML))
264
256
  except EsphomeError as e:
265
257
  if self.name == CORE.config_path:
266
258
  raise e
267
259
  try:
268
260
  main_config_dir = os.path.dirname(CORE.config_path)
269
261
  main_secret_yml = os.path.join(main_config_dir, SECRET_YAML)
270
- secrets = self.yaml_loader(main_secret_yml)
262
+ secrets = _load_yaml_internal(main_secret_yml)
271
263
  except EsphomeError as er:
272
264
  raise EsphomeError(f"{e}\n{er}") from er
273
265
 
@@ -280,9 +272,7 @@ class ESPHomeLoaderMixin:
280
272
  return val
281
273
 
282
274
  @_add_data_ref
283
- def construct_include(
284
- self, node: yaml.Node
285
- ) -> dict[str, Any] | OrderedDict[str, Any]:
275
+ def construct_include(self, node):
286
276
  from esphome.const import CONF_VARS
287
277
 
288
278
  def extract_file_vars(node):
@@ -300,93 +290,71 @@ class ESPHomeLoaderMixin:
300
290
  else:
301
291
  file, vars = node.value, None
302
292
 
303
- result = self.yaml_loader(self._rel_path(file))
293
+ result = _load_yaml_internal(self._rel_path(file))
304
294
  if not vars:
305
295
  vars = {}
306
296
  result = substitute_vars(result, vars)
307
297
  return result
308
298
 
309
299
  @_add_data_ref
310
- def construct_include_dir_list(self, node: yaml.Node) -> list[dict[str, Any]]:
300
+ def construct_include_dir_list(self, node):
311
301
  files = filter_yaml_files(_find_files(self._rel_path(node.value), "*.yaml"))
312
- return [self.yaml_loader(f) for f in files]
302
+ return [_load_yaml_internal(f) for f in files]
313
303
 
314
304
  @_add_data_ref
315
- def construct_include_dir_merge_list(self, node: yaml.Node) -> list[dict[str, Any]]:
305
+ def construct_include_dir_merge_list(self, node):
316
306
  files = filter_yaml_files(_find_files(self._rel_path(node.value), "*.yaml"))
317
307
  merged_list = []
318
308
  for fname in files:
319
- loaded_yaml = self.yaml_loader(fname)
309
+ loaded_yaml = _load_yaml_internal(fname)
320
310
  if isinstance(loaded_yaml, list):
321
311
  merged_list.extend(loaded_yaml)
322
312
  return merged_list
323
313
 
324
314
  @_add_data_ref
325
- def construct_include_dir_named(
326
- self, node: yaml.Node
327
- ) -> OrderedDict[str, dict[str, Any]]:
315
+ def construct_include_dir_named(self, node):
328
316
  files = filter_yaml_files(_find_files(self._rel_path(node.value), "*.yaml"))
329
317
  mapping = OrderedDict()
330
318
  for fname in files:
331
319
  filename = os.path.splitext(os.path.basename(fname))[0]
332
- mapping[filename] = self.yaml_loader(fname)
320
+ mapping[filename] = _load_yaml_internal(fname)
333
321
  return mapping
334
322
 
335
323
  @_add_data_ref
336
- def construct_include_dir_merge_named(
337
- self, node: yaml.Node
338
- ) -> OrderedDict[str, dict[str, Any]]:
324
+ def construct_include_dir_merge_named(self, node):
339
325
  files = filter_yaml_files(_find_files(self._rel_path(node.value), "*.yaml"))
340
326
  mapping = OrderedDict()
341
327
  for fname in files:
342
- loaded_yaml = self.yaml_loader(fname)
328
+ loaded_yaml = _load_yaml_internal(fname)
343
329
  if isinstance(loaded_yaml, dict):
344
330
  mapping.update(loaded_yaml)
345
331
  return mapping
346
332
 
347
333
  @_add_data_ref
348
- def construct_lambda(self, node: yaml.Node) -> Lambda:
334
+ def construct_lambda(self, node):
349
335
  return Lambda(str(node.value))
350
336
 
351
337
  @_add_data_ref
352
- def construct_force(self, node: yaml.Node) -> ESPForceValue:
338
+ def construct_force(self, node):
353
339
  obj = self.construct_scalar(node)
354
340
  return add_class_to_obj(obj, ESPForceValue)
355
341
 
356
342
  @_add_data_ref
357
- def construct_extend(self, node: yaml.Node) -> Extend:
343
+ def construct_extend(self, node):
358
344
  return Extend(str(node.value))
359
345
 
360
346
  @_add_data_ref
361
- def construct_remove(self, node: yaml.Node) -> Remove:
347
+ def construct_remove(self, node):
362
348
  return Remove(str(node.value))
363
349
 
364
350
 
365
351
  class ESPHomeLoader(ESPHomeLoaderMixin, FastestAvailableSafeLoader):
366
352
  """Loader class that keeps track of line numbers."""
367
353
 
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
-
377
354
 
378
355
  class ESPHomePurePythonLoader(ESPHomeLoaderMixin, PurePythonLoader):
379
356
  """Loader class that keeps track of line numbers."""
380
357
 
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
-
390
358
 
391
359
  for _loader in (ESPHomeLoader, ESPHomePurePythonLoader):
392
360
  _loader.add_constructor("tag:yaml.org,2002:int", _loader.construct_yaml_int)
@@ -420,30 +388,17 @@ def load_yaml(fname: str, clear_secrets: bool = True) -> Any:
420
388
  return _load_yaml_internal(fname)
421
389
 
422
390
 
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:
391
+ def parse_yaml(file_name: str, file_handle: TextIOWrapper) -> Any:
435
392
  """Parse a YAML file."""
436
393
  try:
437
- return _load_yaml_internal_with_type(
438
- ESPHomeLoader, file_name, file_handle, yaml_loader
439
- )
394
+ return _load_yaml_internal_with_type(ESPHomeLoader, file_name, file_handle)
440
395
  except EsphomeError:
441
396
  # Loading failed, so we now load with the Python loader which has more
442
397
  # readable exceptions
443
398
  # Rewind the stream so we can try again
444
399
  file_handle.seek(0, 0)
445
400
  return _load_yaml_internal_with_type(
446
- ESPHomePurePythonLoader, file_name, file_handle, yaml_loader
401
+ ESPHomePurePythonLoader, file_name, file_handle
447
402
  )
448
403
 
449
404
 
@@ -480,14 +435,23 @@ def substitute_vars(config, vars):
480
435
  return result
481
436
 
482
437
 
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
+
483
447
  def _load_yaml_internal_with_type(
484
448
  loader_type: type[ESPHomeLoader] | type[ESPHomePurePythonLoader],
485
449
  fname: str,
486
450
  content: TextIOWrapper,
487
- yaml_loader: Any,
488
451
  ) -> Any:
489
452
  """Load a YAML file."""
490
- loader = loader_type(content, fname, yaml_loader)
453
+ loader = loader_type(content)
454
+ loader.name = fname
491
455
  try:
492
456
  return loader.get_single_data() or OrderedDict()
493
457
  except yaml.YAMLError as exc:
@@ -506,7 +470,7 @@ def dump(dict_, show_secrets=False):
506
470
  )
507
471
 
508
472
 
509
- def _is_file_valid(name: str) -> bool:
473
+ def _is_file_valid(name):
510
474
  """Decide if a file is valid."""
511
475
  return not name.startswith(".")
512
476
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: esphome
3
- Version: 2025.4.0
3
+ Version: 2025.4.0b2
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
@@ -39,7 +39,7 @@ Requires-Dist: esptool==4.8.1
39
39
  Requires-Dist: click==8.1.7
40
40
  Requires-Dist: esphome-dashboard==20250415.0
41
41
  Requires-Dist: aioesphomeapi==29.10.0
42
- Requires-Dist: zeroconf==0.146.5
42
+ Requires-Dist: zeroconf==0.146.4
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
@@ -1,11 +1,11 @@
1
1
  esphome/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- esphome/__main__.py,sha256=u8FUFrXqmO17I_HIFz_t8N-O7Cma7QiHzCnW4ARp_VQ,32617
2
+ esphome/__main__.py,sha256=3PVf9SDEDgPHvXJdGE0dCboamTgRVEWhfmMcY8T3pLw,32587
3
3
  esphome/automation.py,sha256=9xmW3AmWDd2oKB7zF-UITYIiSci8ys8qiylK-rcU7Rg,15689
4
4
  esphome/codegen.py,sha256=GePHUM7xdXb_Pil59SHVsXg2F4VBPgkH-Fz2PDX8Z54,1873
5
5
  esphome/config.py,sha256=fFrDYbhWY1xn_onAl_0jwlg9D8NkK_FdKULTlnjZtxs,39832
6
6
  esphome/config_helpers.py,sha256=MKf_wzO35nn41FvigXE0iYKDslPgL2ruf8R-EPtTT2I,3256
7
- esphome/config_validation.py,sha256=cjOlphJs-ybsA2VlXXPY-lYq80Vm2uPJ8oIXtWhiPU4,62490
8
- esphome/const.py,sha256=j_x0WTM-Pa_FMcMHwoI23V2RXqeWj0suBDATPVBG9pg,40828
7
+ esphome/config_validation.py,sha256=9KOhLHQXmDbahg6zHynXfXDfAL2bciu35_SHxHrzZ2M,63705
8
+ esphome/const.py,sha256=U00foWVg7L2J3h7o4-5e48t8gl8y0deYApkPsFP3dFg,40830
9
9
  esphome/coroutine.py,sha256=j_14z8dIIzIBeuNO30D4c1RJvMMt1xZFZ58Evd-EvJA,9344
10
10
  esphome/cpp_generator.py,sha256=1g-y3fxWSrd5Kpbz6DrJXaQajjuwQiTIaTRIz9n7svI,31237
11
11
  esphome/cpp_helpers.py,sha256=6C2vNbOIhZKi43xRVlk5hp9GfshfBn-rc5D_ZFUEYaE,4801
@@ -25,10 +25,10 @@ esphome/storage_json.py,sha256=VyIQDo7hHTIYEeLJTO5cq6I_nOWTlvFVvxqvXd6cgg4,10124
25
25
  esphome/types.py,sha256=xJaCRRyYuJiRo32mns9v-YeYB6w12NAT8vMSk9ZmJl8,430
26
26
  esphome/util.py,sha256=sKW1kNMRle3bOGrw9Rga32ZfSDXYMbQeQeVKxFEumJs,9329
27
27
  esphome/voluptuous_schema.py,sha256=Z5nhm8lM4H4D-RB7hi4aCKgPiVLuIZ5EULkC0X4QUjY,9345
28
- esphome/vscode.py,sha256=NtCZkDwckoHrM9ZPmyMBE0q29nqd899VjvISkojsLMw,3991
28
+ esphome/vscode.py,sha256=612-o7AVyoh1Fmg-ZXYxKVWUc5fcoumsnunpMv7MQuo,3251
29
29
  esphome/wizard.py,sha256=BO89_Y9Z6T8kvC5ZrQGPZzT5Rl9vXpte9L2hqr6CeOk,15349
30
30
  esphome/writer.py,sha256=nJSeC5kZesf9EL4nbsWwhMRq7wPnZPjhP0gLZbVTkvI,10999
31
- esphome/yaml_util.py,sha256=pHw9fJzU0sr180ntmdLZh7k7HQ5giSHkCJzdIgTi_jg,22919
31
+ esphome/yaml_util.py,sha256=4lCUmBYsbdbxW4yjEAQGcYXhKI3KFkwSzT_N31UojFs,21523
32
32
  esphome/zeroconf.py,sha256=IuAKqtvDCi4uvLKCbLxrP-DFrXVm1I9icbryrCkenAA,6383
33
33
  esphome/components/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
34
34
  esphome/components/a01nyub/__init__.py,sha256=d_xNluCS0T63CxdXJGuw7raK7G9vAEwXNJpnA9GthzA,34
@@ -143,7 +143,7 @@ esphome/components/alpha3/alpha3.cpp,sha256=1zSC6RmxRxmLFFwRq-pY1BbUWqNiB6ZMOkzf
143
143
  esphome/components/alpha3/alpha3.h,sha256=tiCloEGqw9rHdIOocRskj50Z32DiWo6aVUjUk-tRAb8,3405
144
144
  esphome/components/alpha3/sensor.py,sha256=6qSx-_JzQ0wK8aml1LLHqm-u0L0ABX3o8dgqqwdpNvQ,2749
145
145
  esphome/components/am2315c/__init__.py,sha256=_LBsZwU4Hpwdgs3o5uioFVeClSE5fK8k_FZB2MWSADE,30
146
- esphome/components/am2315c/am2315c.cpp,sha256=azPE7NUg2saRKmzz5p7hpIu6eMcyglu4OqnV7BMLj4w,5529
146
+ esphome/components/am2315c/am2315c.cpp,sha256=_fQDCMiy6OfTmnWFEz8RG78oygM2SKSvShkp3FLgKaA,5501
147
147
  esphome/components/am2315c/am2315c.h,sha256=xJyLj_vUnDACJ0LehomCzXKWnqWYUmmr9OvjpfdrteQ,2047
148
148
  esphome/components/am2315c/sensor.py,sha256=Y3tt7aOi12IsrfbS9fAHec9BkV0hz3PfmviT-nDyfX0,1702
149
149
  esphome/components/am2320/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -2655,10 +2655,10 @@ esphome/components/sm300d2/sm300d2.h,sha256=YDd_1VQ0Bq0KhWgtvsJLmuXdIRG8purGyxmU
2655
2655
  esphome/components/sml/__init__.py,sha256=ibRZmFRQFxOAJ9-YC_00_FYUGQSn2UcFy_9mAKfEg3g,1795
2656
2656
  esphome/components/sml/automation.h,sha256=lY2a8F41GCJB03oPWj4fBJ_3VlXgSc7HKwkVzVmPLqc,411
2657
2657
  esphome/components/sml/constants.h,sha256=bBhl2KSbp7c44Ah4qcVxGwVGMq5na8JgSLwu-Zk-uUE,639
2658
- esphome/components/sml/sml.cpp,sha256=G0AYjyk8VvP1V_BEKgsXe_eH-P-MouyZENciorrUAHg,4210
2659
- esphome/components/sml/sml.h,sha256=5xTtlwcoKVfvBN3IXoB6gKIvni85edLL6z_865SqNaE,1330
2660
- esphome/components/sml/sml_parser.cpp,sha256=oNOcV2mPUzXNY0oYRfOxHM9i51ODw3vWNdxeyhH9N7M,5051
2661
- esphome/components/sml/sml_parser.h,sha256=bZk5KCQnIjlRUEjnIR2SfpSCe835BqJAlTVGvFkJ3i0,1812
2658
+ esphome/components/sml/sml.cpp,sha256=Th5Q5VbkGt8RsNOCWFDRFFmTbTZj0NH6JRH1A2tSlNE,4209
2659
+ esphome/components/sml/sml.h,sha256=UWA0WZEwiMk2NWgCm9nUCQyJa6DUSxJwhV_W9EE3l1E,1326
2660
+ esphome/components/sml/sml_parser.cpp,sha256=Xb7habP5AXdpFn7yJ1tnFAkPxMBTgcDoAI-0FoHD5Fw,5111
2661
+ esphome/components/sml/sml_parser.h,sha256=9HlTT3dcbn8jnnvwIARwzBC7wasi2UUZrCj6tTXN--E,955
2662
2662
  esphome/components/sml/sensor/__init__.py,sha256=jlZBTGgveDgAtiLdpJzyumU4DiKyZgvTOY6m3q09arg,910
2663
2663
  esphome/components/sml/sensor/sml_sensor.cpp,sha256=onWh6gi584Qy8TaYgnRi5K4nMUkS39P8_liCTTKW5JM,1106
2664
2664
  esphome/components/sml/sensor/sml_sensor.h,sha256=QypjAdqlNPIVLP77YuxASVRJVwjITCuoc5q0MvzUjNY,420
@@ -3423,7 +3423,7 @@ esphome/components/zyaura/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJW
3423
3423
  esphome/components/zyaura/sensor.py,sha256=cSmO4ozYdi4ZD7NK4lmYjswWVmJoDvEruU1HhGrZSE0,2476
3424
3424
  esphome/components/zyaura/zyaura.cpp,sha256=F7WM8XAZ5MYuCD3eERm2_IA-O7sP1i-A-yF5TV4PqX8,3679
3425
3425
  esphome/components/zyaura/zyaura.h,sha256=yWrcYj_CnLDyopkuIVySJy5dB-R5X9zwm5fWjBDTSts,2384
3426
- esphome/core/__init__.py,sha256=RCYQQQLlJxABwGyhSQJ9-kCwMOZL-uSVhBEnHUfEeOU,26735
3426
+ esphome/core/__init__.py,sha256=L4pbrSzT5rq3ba3Xm1N5xiRx4m5a4pKN5wBwvUwWZLU,26760
3427
3427
  esphome/core/application.cpp,sha256=DOhq0rTYu_dNcUCavHYVYASQZ5QzVOXMWVTaO7YTdnM,4680
3428
3428
  esphome/core/application.h,sha256=jepd6I9BBa48ByAXoLnUE7ddtlHiUS5uuKaMaJOD0x0,17721
3429
3429
  esphome/core/automation.h,sha256=UQQD9De3eiaopvzYQG89SxkBfnL5QaiR6bvkk2RxV8k,7332
@@ -3481,9 +3481,9 @@ esphome/dashboard/util/itertools.py,sha256=8eLrWEWmICLtXNxkKdYPQV0c_N4GEz8m9Npnb
3481
3481
  esphome/dashboard/util/password.py,sha256=cQz3b9B-ijTe7zS6BeCW0hc3pWv6JjC78jmnycYYAh8,321
3482
3482
  esphome/dashboard/util/subprocess.py,sha256=T8EW6dbU4LPd2DG1dRrdh8li71tt6J1isn411poMhkk,1022
3483
3483
  esphome/dashboard/util/text.py,sha256=ENDnfN4O0NdA3CKVJjQYabFbwbrsIhVKrAMQe53qYu4,534
3484
- esphome-2025.4.0.dist-info/licenses/LICENSE,sha256=HzEjkBInJe44L4WvAOPfhPJJDNj6YbnqFyvGWRzArGM,36664
3485
- esphome-2025.4.0.dist-info/METADATA,sha256=w3xkxBsaU1Vmq0YL683PtmEoiTNVYToB-b4MvLJauhQ,3671
3486
- esphome-2025.4.0.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
3487
- esphome-2025.4.0.dist-info/entry_points.txt,sha256=mIxVNuWtbYzeEcaWCl-AQ-97aBOWbnYBAK8nbF6P4M0,50
3488
- esphome-2025.4.0.dist-info/top_level.txt,sha256=0GSXEW3cnITpgG3qnsSMz0qoqJHAFyfw7Y8MVtEf1Yk,8
3489
- esphome-2025.4.0.dist-info/RECORD,,
3484
+ esphome-2025.4.0b2.dist-info/licenses/LICENSE,sha256=HzEjkBInJe44L4WvAOPfhPJJDNj6YbnqFyvGWRzArGM,36664
3485
+ esphome-2025.4.0b2.dist-info/METADATA,sha256=QDzQqlrluTWB6o0YzPZysWvsFSvlhigg7_zgG0ifL34,3673
3486
+ esphome-2025.4.0b2.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
3487
+ esphome-2025.4.0b2.dist-info/entry_points.txt,sha256=mIxVNuWtbYzeEcaWCl-AQ-97aBOWbnYBAK8nbF6P4M0,50
3488
+ esphome-2025.4.0b2.dist-info/top_level.txt,sha256=0GSXEW3cnITpgG3qnsSMz0qoqJHAFyfw7Y8MVtEf1Yk,8
3489
+ esphome-2025.4.0b2.dist-info/RECORD,,