Home IO Control
ESPHome add-on for IO-Homecontrol devices
Loading...
Searching...
No Matches
tuning.py
Go to the documentation of this file.
1## @file
2## @brief ESPHome tuning schema and code generation for Home IO Control.
3## @ingroup hioc_codegen
4##
5## Provides the YAML ``tuning:`` block under ``home_io_control:`` and optionally
6## generates Home Assistant ``number`` and ``select`` entities so users can adjust
7## pairing and radio parameters at runtime. Tuned values are volatile and reset on
8## every boot.
9
10import esphome.codegen as cg
11import esphome.config_validation as cv
12from esphome.components import number, select
13from esphome.const import CONF_ID, CONF_NAME, ENTITY_CATEGORY_CONFIG
14from esphome.core import ID
15
16home_io_control_ns = cg.esphome_ns.namespace("home_io_control")
17
18CONF_TUNING = "tuning"
19CONF_UI_CONTROLS = "ui_controls"
20
21# Fixed ID prefix for the generated tuning companion entities (see _inject_tuning_companion_ids).
22_COMPANION_ID_BASE = "home_io_control"
23
24# --- Radio / physical layer ---
25CONF_SX1262_RX_BANDWIDTH = "sx1262_rx_bandwidth"
26CONF_SX1262_RESPONSE_PREAMBLE = "sx1262_response_preamble"
27CONF_SX1262_POST_TX_SETTLE_US = "sx1262_post_tx_settle_us"
28CONF_SX1276_RX_BANDWIDTH = "sx1276_rx_bandwidth"
29CONF_SX1276_RESPONSE_PREAMBLE = "sx1276_response_preamble"
30CONF_SX1276_DISCOVERY_HOP_SLICE_MS = "sx1276_discovery_hop_slice_ms"
31CONF_SX1262_DISCOVERY_HOP_SLICE_MS = "sx1262_discovery_hop_slice_ms"
32CONF_LR1121_RX_BANDWIDTH = "lr1121_rx_bandwidth"
33CONF_LR1121_RESPONSE_PREAMBLE = "lr1121_response_preamble"
34CONF_LR1121_POST_TX_SETTLE_US = "lr1121_post_tx_settle_us"
35CONF_LR1121_DISCOVERY_HOP_SLICE_MS = "lr1121_discovery_hop_slice_ms"
36CONF_LBT_MAX_RETRIES = "lbt_max_retries"
37CONF_LBT_RSSI_THRESHOLD_DBM = "lbt_rssi_threshold_dbm"
38CONF_EXCHANGE_START_RESPONSE_WAIT_MS = "exchange_start_response_wait_ms"
39CONF_EXCHANGE_RESPONSE_WAIT_MS = "exchange_response_wait_ms"
40
41# --- Pairing protocol ---
42CONF_PAIRING_DISCOVERY_COMMANDS = "pairing_discovery_commands"
43CONF_PAIRING_DISCOVERY_DESTINATION = "pairing_discovery_destination"
44CONF_PAIRING_DISCOVERY_PAYLOAD = "pairing_discovery_payload"
45CONF_PAIRING_DISCOVERY_LOW_POWER = "pairing_discovery_low_power"
46CONF_PAIRING_DISCOVERY_WAIT_MS = "pairing_discovery_wait_ms"
47CONF_PAIRING_DISCOVERY_INITIAL_DWELL_MS = "pairing_discovery_initial_dwell_ms"
48CONF_PAIRING_KEY_EXCHANGE_RETRIES = "pairing_key_exchange_retries"
49
50# C++ type references
51TuningConfig = home_io_control_ns.class_("TuningConfig")
52IOHomeTuningNumber = home_io_control_ns.class_(
53 "IOHomeTuningNumber", number.Number, cg.Component
54)
55IOHomeTuningSelect = home_io_control_ns.class_(
56 "IOHomeTuningSelect", select.Select, cg.Component
57)
58
59# C++ enums. These stringify to their fully-qualified C++ names (e.g.
60# esphome::home_io_control::SX1262RxBandwidth::BW_117_3_KHZ) so generated code compiles in
61# the global-namespace setup() function.
62SX1262RxBandwidth = home_io_control_ns.enum("SX1262RxBandwidth", is_class=True)
63SX1276RxBandwidth = home_io_control_ns.enum("SX1276RxBandwidth", is_class=True)
64LR1121RxBandwidth = home_io_control_ns.enum("LR1121RxBandwidth", is_class=True)
65DiscoveryCommand = home_io_control_ns.enum("DiscoveryCommand", is_class=True)
66
67# Map each YAML option string to its C++ enum value. Options are bare kHz numbers (the "kHz"
68# unit lives in the entity name) for uniformity with the numeric parameters.
69SX1262_BANDWIDTH_OPTIONS = {
70 "58.6": SX1262RxBandwidth.BW_58_6_KHZ,
71 "78.2": SX1262RxBandwidth.BW_78_2_KHZ,
72 "117.3": SX1262RxBandwidth.BW_117_3_KHZ,
73 "156.2": SX1262RxBandwidth.BW_156_2_KHZ,
74 "187.2": SX1262RxBandwidth.BW_187_2_KHZ,
75}
76
77SX1276_BANDWIDTH_OPTIONS = {
78 "20.8": SX1276RxBandwidth.BW_20_8_KHZ,
79 "41.7": SX1276RxBandwidth.BW_41_7_KHZ,
80 "62.5": SX1276RxBandwidth.BW_62_5_KHZ,
81 "83.3": SX1276RxBandwidth.BW_83_3_KHZ,
82 "125.0": SX1276RxBandwidth.BW_125_0_KHZ,
83}
84
85# LR1121 has its own register encoding, distinct from SX1262's (see tuning_config.h
86# LR1121RxBandwidth — two of the five values borrowed from SX1262 turned out wrong for this
87# chip). Includes two narrower options (39.0/46.9kHz) not offered for SX1262, added to get
88# closer to SX1276's real-hardware-validated 41.7kHz default.
89LR1121_BANDWIDTH_OPTIONS = {
90 "39.0": LR1121RxBandwidth.BW_39_0_KHZ,
91 "46.9": LR1121RxBandwidth.BW_46_9_KHZ,
92 "58.6": LR1121RxBandwidth.BW_58_6_KHZ,
93 "78.2": LR1121RxBandwidth.BW_78_2_KHZ,
94 "117.3": LR1121RxBandwidth.BW_117_3_KHZ,
95 "156.2": LR1121RxBandwidth.BW_156_2_KHZ,
96 "187.2": LR1121RxBandwidth.BW_187_2_KHZ,
97}
98
99DISCOVERY_COMMAND_OPTIONS = {
100 "0x28": DiscoveryCommand.DISCOVER,
101 "0x2E": DiscoveryCommand.DISCOVER_ALT,
102}
103# 0x2A (CMD_DISCOVER_SPE_REQ) is deliberately excluded: it is a roll-call answered only by devices
104# that already hold the controller's system key, so a device in learning mode never answers it.
105# Offering it here could only add replies from already-paired devices to a pairing attempt, never
106# help reach the unpaired one. The command byte itself stays valid — see
107# DiscoveryCommand::DISCOVER_SPE in tuning_config.h.
108
109# Home Assistant `select` entities are single-choice, but the discovery phase can send an
110# ordered list of commands. These comma-separated presets expose the useful combinations as
111# selectable options; the C++ dispatch (update_tuning_select) parses them back into the vector.
112DISCOVERY_COMMAND_PRESETS = [
113 "0x28", # default 2W discovery
114 "0x2E", # alternate discovery (the most common thing to try for a stuck device)
115 "0x28,0x2E", # both broadcasts
116]
117
118DISCOVERY_DESTINATION_OPTIONS = ["auto", "0x00003B", "0x00003F"]
119
120PAIRING_DISCOVERY_PAYLOAD_OPTIONS = ["none", "0x00"]
121
122def _id_key(param_key):
123 """Config-dict key under which a parameter's injected companion entity ID is stored."""
124 return f"_{param_key}_id"
125
126
127# UI entity names. Radio params are prefixed "Radio" and pairing params "Pairing" so that,
128# under Home Assistant's Configuration section (entity_category=config), the two logical groups
129# cluster together alphabetically.
130UI_NAMES = {
131 CONF_SX1262_RX_BANDWIDTH: "Radio SX1262 RX Bandwidth (kHz)",
132 CONF_SX1262_RESPONSE_PREAMBLE: "Radio SX1262 Response Preamble",
133 CONF_SX1262_POST_TX_SETTLE_US: "Radio SX1262 Post-TX Settle",
134 CONF_SX1276_RX_BANDWIDTH: "Radio SX1276 RX Bandwidth (kHz)",
135 CONF_SX1276_RESPONSE_PREAMBLE: "Radio SX1276 Response Preamble",
136 CONF_SX1276_DISCOVERY_HOP_SLICE_MS: "Radio SX1276 Discovery Hop Slice",
137 CONF_SX1262_DISCOVERY_HOP_SLICE_MS: "Radio SX1262 Discovery Hop Slice",
138 CONF_LR1121_RX_BANDWIDTH: "Radio LR1121 RX Bandwidth (kHz)",
139 CONF_LR1121_RESPONSE_PREAMBLE: "Radio LR1121 Response Preamble",
140 CONF_LR1121_POST_TX_SETTLE_US: "Radio LR1121 Post-TX Settle",
141 CONF_LR1121_DISCOVERY_HOP_SLICE_MS: "Radio LR1121 Discovery Hop Slice",
142 CONF_LBT_MAX_RETRIES: "Radio LBT Max Retries",
143 CONF_LBT_RSSI_THRESHOLD_DBM: "Radio LBT RSSI Threshold",
144 CONF_EXCHANGE_START_RESPONSE_WAIT_MS: "Exchange Start Response Wait",
145 CONF_EXCHANGE_RESPONSE_WAIT_MS: "Exchange Response Wait",
146 CONF_PAIRING_DISCOVERY_COMMANDS: "Pairing Discovery Commands",
147 CONF_PAIRING_DISCOVERY_DESTINATION: "Pairing Discovery Destination",
148 CONF_PAIRING_DISCOVERY_PAYLOAD: "Pairing Discovery Payload",
149 CONF_PAIRING_DISCOVERY_LOW_POWER: "Pairing Discovery Low Power",
150 CONF_PAIRING_DISCOVERY_WAIT_MS: "Pairing Discovery Wait",
151 CONF_PAIRING_DISCOVERY_INITIAL_DWELL_MS: "Pairing Discovery Initial Dwell",
152 CONF_PAIRING_KEY_EXCHANGE_RETRIES: "Pairing Key Exchange Retries",
153}
154
155# Numeric parameters: key -> (min, max, step, unit). Single source of truth for both the
156# YAML validation range and the Home Assistant `number` entity bounds, so the two cannot drift.
157_NUMBER_PARAMS = {
158 # Floor is 8, not an arbitrary round number: it's SHORT_PREAMBLE, the protocol's own nominal
159 # preamble length and the lowest value that makes sense to transmit (below it there isn't
160 # enough preamble left for the peer's detector to lock on at all). Same floor as
161 # sx1276_response_preamble below.
162 CONF_SX1262_RESPONSE_PREAMBLE: (8, 256, 1, "B"),
163 CONF_SX1262_POST_TX_SETTLE_US: (0, 2000, 10, "µs"),
164 CONF_SX1276_RESPONSE_PREAMBLE: (8, 256, 1, "B"),
165 CONF_SX1276_DISCOVERY_HOP_SLICE_MS: (5, 200, 1, "ms"),
166 CONF_SX1262_DISCOVERY_HOP_SLICE_MS: (50, 500, 1, "ms"),
167 # LR1121 numeric ranges reuse the SX1262 bounds — same chip-family physical constraints
168 # (design doc §3.2: "seed every timing/tuning default from the validated SX1262 values").
169 CONF_LR1121_RESPONSE_PREAMBLE: (8, 256, 1, "B"),
170 CONF_LR1121_POST_TX_SETTLE_US: (0, 2000, 10, "µs"),
171 CONF_LR1121_DISCOVERY_HOP_SLICE_MS: (50, 500, 1, "ms"),
172 CONF_LBT_MAX_RETRIES: (0, 10, 1, ""),
173 CONF_LBT_RSSI_THRESHOLD_DBM: (-95, -70, 1, "dBm"),
174 # Ceilings are generous because the right value is a property of the target device, not of
175 # the radio: measured RS100 solar reply latencies reach ~3 s (see RESPONSE_START_WAIT_MS).
176 # Every millisecond here is loop-blocking time on a *failed* exchange only (ADR 0013).
177 CONF_EXCHANGE_START_RESPONSE_WAIT_MS: (200, 4000, 50, "ms"),
178 CONF_EXCHANGE_RESPONSE_WAIT_MS: (200, 4000, 50, "ms"),
179 CONF_PAIRING_DISCOVERY_WAIT_MS: (500, 5000, 50, "ms"),
180 CONF_PAIRING_DISCOVERY_INITIAL_DWELL_MS: (0, 500, 10, "ms"),
181 CONF_PAIRING_KEY_EXCHANGE_RETRIES: (1, 5, 1, ""),
182}
183
184# Select parameters: key -> ordered option list. Single source of truth for both the
185# companion-ID injection and the entity creation.
186_SELECT_OPTIONS = {
187 CONF_PAIRING_DISCOVERY_COMMANDS: DISCOVERY_COMMAND_PRESETS,
188 CONF_PAIRING_DISCOVERY_DESTINATION: DISCOVERY_DESTINATION_OPTIONS,
189 CONF_PAIRING_DISCOVERY_PAYLOAD: PAIRING_DISCOVERY_PAYLOAD_OPTIONS,
190 CONF_PAIRING_DISCOVERY_LOW_POWER: ["Off", "On"],
191 CONF_SX1262_RX_BANDWIDTH: list(SX1262_BANDWIDTH_OPTIONS),
192 CONF_SX1276_RX_BANDWIDTH: list(SX1276_BANDWIDTH_OPTIONS),
193 CONF_LR1121_RX_BANDWIDTH: list(LR1121_BANDWIDTH_OPTIONS),
194}
195
196
197def _one_of_string(param_name, options, coerce_number=False):
198 """Build a validator that requires a string value present in `options`.
199
200 Works for both list and dict `options` (dict membership tests the keys), so a single
201 factory replaces a hand-written validator per option set while keeping a per-parameter
202 error message. With `coerce_number`, a numeric YAML value (e.g. ``117.3``) is accepted and
203 normalized to its string form, so bare-number options do not force the user to quote them.
204 """
205
206 def validate(value):
207 if coerce_number and isinstance(value, (int, float)) and not isinstance(value, bool):
208 value = str(value)
209 value = cv.string_strict(value)
210 if value not in options:
211 raise cv.Invalid(f"{param_name} must be one of {list(options)}")
212 return value
213
214 return validate
215
216
217_validate_bandwidth = _one_of_string(
218 CONF_SX1262_RX_BANDWIDTH, SX1262_BANDWIDTH_OPTIONS, coerce_number=True
219)
220_validate_sx1276_bandwidth = _one_of_string(
221 CONF_SX1276_RX_BANDWIDTH, SX1276_BANDWIDTH_OPTIONS, coerce_number=True
222)
223_validate_lr1121_bandwidth = _one_of_string(
224 CONF_LR1121_RX_BANDWIDTH, LR1121_BANDWIDTH_OPTIONS, coerce_number=True
225)
226_validate_discovery_command = _one_of_string(
227 f"{CONF_PAIRING_DISCOVERY_COMMANDS} entries", DISCOVERY_COMMAND_OPTIONS
228)
229_validate_discovery_destination = _one_of_string(
230 CONF_PAIRING_DISCOVERY_DESTINATION, DISCOVERY_DESTINATION_OPTIONS
231)
232_validate_discovery_payload = _one_of_string(
233 CONF_PAIRING_DISCOVERY_PAYLOAD, PAIRING_DISCOVERY_PAYLOAD_OPTIONS
234)
235
236
238 """Convert an explicit destination string like '0x00003B' to a list of 3 byte values."""
239 digits = value[2:] # strip the '0x' prefix
240 return [int(digits[i : i + 2], 16) for i in range(0, 6, 2)]
241
242
243def _parse_payload(value):
244 """Convert a payload option string like '0x00' to its numeric byte value."""
245 return int(value, 16)
246
247
248# Tuning sub-schema (imported and extended by the hub __init__.py).
249#
250# Only `ui_controls` carries a Python default (it is a feature toggle, not a tunable). Every
251# tunable is a plain cv.Optional with NO default: when a key is omitted, the C++ TuningConfig
252# field keeps its canonical default from proto_frame.h, which is the single source of truth.
253# to_code() only overrides fields that are actually present in the validated config.
254TUNING_SCHEMA = cv.Schema(
255 {
256 cv.Optional(CONF_UI_CONTROLS, default=False): cv.boolean,
257 cv.Optional(CONF_SX1262_RX_BANDWIDTH): _validate_bandwidth,
258 cv.Optional(CONF_SX1276_RX_BANDWIDTH): _validate_sx1276_bandwidth,
259 cv.Optional(CONF_LR1121_RX_BANDWIDTH): _validate_lr1121_bandwidth,
260 cv.Optional(CONF_PAIRING_DISCOVERY_COMMANDS): cv.All(
261 cv.ensure_list(_validate_discovery_command),
262 cv.Length(min=1),
263 ),
264 cv.Optional(CONF_PAIRING_DISCOVERY_DESTINATION): _validate_discovery_destination,
265 cv.Optional(CONF_PAIRING_DISCOVERY_PAYLOAD): _validate_discovery_payload,
266 cv.Optional(CONF_PAIRING_DISCOVERY_LOW_POWER): cv.boolean,
267 # Numeric parameters share their range with the number-entity bounds via _NUMBER_PARAMS.
268 **{
269 cv.Optional(key): cv.int_range(min=lo, max=hi)
270 for key, (lo, hi, _step, _unit) in _NUMBER_PARAMS.items()
271 },
272 }
273)
274
275
277 """Declare companion entity IDs for tuning UI controls.
278
279 ESPHome 2026.x sizes the runtime component vector from the number of IDs
280 declared during validation. If UI entities were created only in to_code(),
281 they could be silently dropped. This post-validator injects declared IDs
282 before the vector is sized.
283 """
284 if not config[CONF_UI_CONTROLS]:
285 return config
286
287 # The tuning block is a sub-schema of home_io_control and does not carry the parent's
288 # CONF_ID, so the companion entity IDs use a fixed prefix. It only needs to be unique
289 # among the generated tuning entities.
290 base = _COMPANION_ID_BASE
291
292 for key in _SELECT_OPTIONS:
293 config[_id_key(key)] = ID(f"{base}_{key}", is_declaration=True, type=IOHomeTuningSelect)
294 for key in _NUMBER_PARAMS:
295 config[_id_key(key)] = ID(f"{base}_{key}", is_declaration=True, type=IOHomeTuningNumber)
296
297 return config
298
299
300TUNING_CONFIG_SCHEMA = cv.All(TUNING_SCHEMA, _inject_tuning_companion_ids)
301
302
303def _cpp_bool(value):
304 """Format a Python bool as a C++ boolean literal."""
305 return "true" if value else "false"
306
307
308def _assign(struct, field, value):
309 """Emit a `struct.field = value;` C++ assignment statement at codegen time.
310
311 ESPHome's MockObj does not support Python attribute assignment, so struct fields
312 are populated with raw assignment statements. `value` must already be valid C++
313 (an int, or a string such as an enum value or brace-init list).
314 """
315 cg.add(cg.RawExpression(f"{struct}.{field} = {value}"))
316
317
318def _apply_tuning_config(config, var):
319 """Generate C++ code that builds a TuningConfig from the validated YAML.
320
321 Only keys the user actually provided are emitted; every omitted key keeps the
322 default baked into the C++ TuningConfig struct (sourced from proto_frame.h), so
323 the defaults are never restated here.
324 """
325 tuning_id = cv.declare_id(TuningConfig)("tuning_config")
326 tuning = cg.new_variable(tuning_id, cg.RawExpression(f"{TuningConfig}()"))
327
328 # Presence of the block is what activates the override layer.
329 _assign(tuning, "active", "true")
330
331 # --- Parameters needing custom handling (enum/list/byte/bool) ---
332 if CONF_SX1262_RX_BANDWIDTH in config:
333 _assign(
334 tuning,
335 "sx1262_rx_bandwidth",
336 SX1262_BANDWIDTH_OPTIONS[config[CONF_SX1262_RX_BANDWIDTH]],
337 )
338
339 if CONF_SX1276_RX_BANDWIDTH in config:
340 _assign(
341 tuning,
342 "sx1276_rx_bandwidth",
343 SX1276_BANDWIDTH_OPTIONS[config[CONF_SX1276_RX_BANDWIDTH]],
344 )
345
346 if CONF_LR1121_RX_BANDWIDTH in config:
347 _assign(
348 tuning,
349 "lr1121_rx_bandwidth",
350 LR1121_BANDWIDTH_OPTIONS[config[CONF_LR1121_RX_BANDWIDTH]],
351 )
352
353 # Ordered discovery commands. Clear the struct default before appending so a
354 # user-provided list replaces it rather than extending it.
355 if CONF_PAIRING_DISCOVERY_COMMANDS in config:
356 cg.add(tuning.pairing_discovery_commands.clear())
357 for cmd in config[CONF_PAIRING_DISCOVERY_COMMANDS]:
358 cg.add(
359 tuning.pairing_discovery_commands.push_back(
360 DISCOVERY_COMMAND_OPTIONS[cmd]
361 )
362 )
363
364 if CONF_PAIRING_DISCOVERY_DESTINATION in config:
365 dest = config[CONF_PAIRING_DISCOVERY_DESTINATION]
366 if dest == "auto":
367 _assign(tuning, "pairing_discovery_destination_auto", "true")
368 else:
369 _assign(tuning, "pairing_discovery_destination_auto", "false")
371 _assign(
372 tuning,
373 "pairing_discovery_destination",
374 f"{{0x{b[0]:02X}, 0x{b[1]:02X}, 0x{b[2]:02X}}}",
375 )
376
377 if CONF_PAIRING_DISCOVERY_PAYLOAD in config:
378 payload = config[CONF_PAIRING_DISCOVERY_PAYLOAD]
379 if payload == "none":
380 _assign(tuning, "pairing_discovery_payload_enabled", "false")
381 else:
382 _assign(tuning, "pairing_discovery_payload_enabled", "true")
383 _assign(tuning, "pairing_discovery_payload", f"0x{_parse_payload(payload):02X}")
384
385 if CONF_PAIRING_DISCOVERY_LOW_POWER in config:
386 _assign(
387 tuning,
388 "pairing_discovery_low_power",
389 _cpp_bool(config[CONF_PAIRING_DISCOVERY_LOW_POWER]),
390 )
391
392 # --- Plain integer parameters: the C++ struct field name equals the YAML key. ---
393 for key in _NUMBER_PARAMS:
394 if key in config:
395 _assign(tuning, key, config[key])
396
397 cg.add(var.set_tuning_config(tuning))
398
399
400async def _create_tuning_entities(config, var):
401 """Generate number/select entities for the tuning parameters when enabled."""
402 if not config[CONF_UI_CONTROLS]:
403 return
404
405 # --- Select entities (options sourced from _SELECT_OPTIONS) ---
406 for key, options in _SELECT_OPTIONS.items():
407 await _create_select(config, var, key, options)
408
409 # --- Number entities (bounds sourced from _NUMBER_PARAMS) ---
410 for key, (min_value, max_value, step, unit) in _NUMBER_PARAMS.items():
411 await _create_number(
412 config, var, key, min_value=min_value, max_value=max_value, step=step, unit=unit
413 )
414
415
417 config, var, key, min_value, max_value, step, unit=""
418):
419 """Create a single IOHomeTuningNumber entity.
420
421 The bare {id, name} dict is normalized through number_schema()+COMPONENT_SCHEMA so it
422 carries the entity/component defaults (disabled_by_default, setup_priority, ...) that
423 register_number()/register_component() require.
424 """
425 schema = number.number_schema(
426 IOHomeTuningNumber,
427 unit_of_measurement=unit,
428 entity_category=ENTITY_CATEGORY_CONFIG,
429 )
430 entity_config = schema.extend(cv.COMPONENT_SCHEMA)(
431 {
432 CONF_ID: config[_id_key(key)],
433 CONF_NAME: UI_NAMES[key],
434 }
435 )
436 entity = await number.new_number(
437 entity_config,
438 var,
439 key,
440 min_value=min_value,
441 max_value=max_value,
442 step=step,
443 )
444 await cg.register_component(entity, entity_config)
445
446
447async def _create_select(config, var, key, options):
448 """Create a single IOHomeTuningSelect entity.
449
450 The bare {id, name} dict is normalized through select_schema()+COMPONENT_SCHEMA so it
451 carries the entity/component defaults that register_select()/register_component() require.
452 """
453 entity_config = select.select_schema(
454 IOHomeTuningSelect, entity_category=ENTITY_CATEGORY_CONFIG
455 ).extend(cv.COMPONENT_SCHEMA)(
456 {
457 CONF_ID: config[_id_key(key)],
458 CONF_NAME: UI_NAMES[key],
459 }
460 )
461 entity = await select.new_select(
462 entity_config,
463 var,
464 key,
465 options=options,
466 )
467 await cg.register_component(entity, entity_config)
468
469
470async def to_code(config, var):
471 """Generate code for the tuning block."""
472 _apply_tuning_config(config, var)
473 await _create_tuning_entities(config, var)
_inject_tuning_companion_ids(config)
Definition tuning.py:276
_one_of_string(param_name, options, coerce_number=False)
Definition tuning.py:197
_create_select(config, var, key, options)
Definition tuning.py:447
_create_tuning_entities(config, var)
Definition tuning.py:400
_apply_tuning_config(config, var)
Definition tuning.py:318
_parse_destination_to_bytes(value)
Definition tuning.py:237
_id_key(param_key)
Definition tuning.py:122
to_code(config, var)
Definition tuning.py:470
_create_number(config, var, key, min_value, max_value, step, unit="")
Definition tuning.py:418
_assign(struct, field, value)
Definition tuning.py:308