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
16home_io_control_ns = cg.esphome_ns.namespace(
"home_io_control")
19CONF_UI_CONTROLS =
"ui_controls"
22_COMPANION_ID_BASE =
"home_io_control"
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_COLD_BROADCAST_REPLY_PREAMBLE =
"cold_broadcast_reply_preamble"
37CONF_NORMAL_START_PREAMBLE =
"normal_start_preamble"
38CONF_LBT_MAX_RETRIES =
"lbt_max_retries"
39CONF_LBT_RSSI_THRESHOLD_DBM =
"lbt_rssi_threshold_dbm"
40CONF_LOW_POWER_WAKE_BELIEF =
"low_power_wake_belief"
41CONF_LOW_POWER_WAKE_PREAMBLE =
"low_power_wake_preamble"
42CONF_EXCHANGE_START_RESPONSE_WAIT_MS =
"exchange_start_response_wait_ms"
43CONF_EXCHANGE_EARLY_RESPONSE_WAIT_MS =
"exchange_early_response_wait_ms"
44CONF_EXCHANGE_RESPONSE_WAIT_MS =
"exchange_response_wait_ms"
45CONF_EXCHANGE_TOTAL_BUDGET_MS =
"exchange_total_budget_ms"
48CONF_REMOTE_ACTIVITY_POLL_DELAY_MS =
"remote_activity_poll_delay_ms"
49CONF_REMOTE_ACTIVITY_CATCHUP_POLL_MS =
"remote_activity_catchup_poll_ms"
52CONF_PAIRING_DISCOVERY_COMMANDS =
"pairing_discovery_commands"
53CONF_PAIRING_DISCOVERY_DESTINATION =
"pairing_discovery_destination"
54CONF_PAIRING_DISCOVERY_PAYLOAD =
"pairing_discovery_payload"
55CONF_PAIRING_DISCOVERY_LOW_POWER =
"pairing_discovery_low_power"
56CONF_PAIRING_DISCOVERY_ACK_CAPABLE =
"pairing_discovery_ack_capable"
57CONF_PAIRING_DISCOVERY_PREAMBLE =
"pairing_discovery_preamble"
58CONF_PAIRING_DISCOVERY_WAIT_MS =
"pairing_discovery_wait_ms"
59CONF_PAIRING_DISCOVERY_INITIAL_DWELL_MS =
"pairing_discovery_initial_dwell_ms"
60CONF_PAIRING_KEY_EXCHANGE_RETRIES =
"pairing_key_exchange_retries"
61CONF_SCAN_POWER_CLASSES =
"scan_power_classes"
62CONF_PAIRING_DISCOVER_CONFIRM =
"pairing_discover_confirm"
63CONF_PAIRING_DISCOVERY_LISTEN_CHANNELS =
"pairing_discovery_listen_channels"
64CONF_PAIRING_KEY_INIT_DELAY_MS =
"pairing_key_init_delay_ms"
65CONF_PAIRING_KEY_TRANSFER =
"pairing_key_transfer"
66CONF_KEY_EXTRACTION_TYPE_WORD =
"key_extraction_type_word"
67CONF_KEY_EXTRACTION_MULTI_INFO =
"key_extraction_multi_info"
70TuningConfig = home_io_control_ns.class_(
"TuningConfig")
71IOHomeTuningNumber = home_io_control_ns.class_(
72 "IOHomeTuningNumber", number.Number, cg.Component
74IOHomeTuningSelect = home_io_control_ns.class_(
75 "IOHomeTuningSelect", select.Select, cg.Component
81SX1262RxBandwidth = home_io_control_ns.enum(
"SX1262RxBandwidth", is_class=
True)
82SX1276RxBandwidth = home_io_control_ns.enum(
"SX1276RxBandwidth", is_class=
True)
83LR1121RxBandwidth = home_io_control_ns.enum(
"LR1121RxBandwidth", is_class=
True)
84DiscoveryCommand = home_io_control_ns.enum(
"DiscoveryCommand", is_class=
True)
85ScanPowerClasses = home_io_control_ns.enum(
"ScanPowerClasses", is_class=
True)
86DiscoverConfirmMode = home_io_control_ns.enum(
"DiscoverConfirmMode", is_class=
True)
87DiscoveryListenChannels = home_io_control_ns.enum(
"DiscoveryListenChannels", is_class=
True)
88KeyTransferMode = home_io_control_ns.enum(
"KeyTransferMode", is_class=
True)
92SX1262_BANDWIDTH_OPTIONS = {
93 "39.0": SX1262RxBandwidth.BW_39_0_KHZ,
94 "46.9": SX1262RxBandwidth.BW_46_9_KHZ,
95 "58.6": SX1262RxBandwidth.BW_58_6_KHZ,
96 "78.2": SX1262RxBandwidth.BW_78_2_KHZ,
97 "117.3": SX1262RxBandwidth.BW_117_3_KHZ,
98 "156.2": SX1262RxBandwidth.BW_156_2_KHZ,
99 "187.2": SX1262RxBandwidth.BW_187_2_KHZ,
102SX1276_BANDWIDTH_OPTIONS = {
103 "20.8": SX1276RxBandwidth.BW_20_8_KHZ,
104 "41.7": SX1276RxBandwidth.BW_41_7_KHZ,
105 "62.5": SX1276RxBandwidth.BW_62_5_KHZ,
106 "83.3": SX1276RxBandwidth.BW_83_3_KHZ,
107 "125.0": SX1276RxBandwidth.BW_125_0_KHZ,
117LR1121_BANDWIDTH_OPTIONS = {
118 "39.0": LR1121RxBandwidth.BW_39_0_KHZ,
119 "46.9": LR1121RxBandwidth.BW_46_9_KHZ,
120 "58.6": LR1121RxBandwidth.BW_58_6_KHZ,
121 "78.2": LR1121RxBandwidth.BW_78_2_KHZ,
122 "117.3": LR1121RxBandwidth.BW_117_3_KHZ,
123 "156.2": LR1121RxBandwidth.BW_156_2_KHZ,
124 "187.2": LR1121RxBandwidth.BW_187_2_KHZ,
127DISCOVERY_COMMAND_OPTIONS = {
128 "0x28": DiscoveryCommand.DISCOVER,
129 "0x2E": DiscoveryCommand.DISCOVER_ALT,
140DISCOVERY_COMMAND_PRESETS = [
163DISCOVERY_DESTINATION_OPTIONS = [
172PAIRING_DISCOVERY_PAYLOAD_OPTIONS = [
"none",
"0x00"]
179SCAN_POWER_CLASSES_OPTIONS = {
180 "both": ScanPowerClasses.BOTH,
181 "always_alive": ScanPowerClasses.ALWAYS_ALIVE,
182 "low_power": ScanPowerClasses.LOW_POWER,
190DISCOVER_CONFIRM_MODE_OPTIONS = {
191 "skip": DiscoverConfirmMode.SKIP,
192 "send": DiscoverConfirmMode.SEND,
193 "send_with_ack": DiscoverConfirmMode.SEND_WITH_ACK,
200DISCOVERY_LISTEN_CHANNELS_OPTIONS = {
201 "skip_request": DiscoveryListenChannels.SKIP_REQUEST,
202 "all": DiscoveryListenChannels.ALL,
209KEY_TRANSFER_MODE_OPTIONS = {
210 "auto": KeyTransferMode.AUTO,
211 "push": KeyTransferMode.PUSH,
212 "pull": KeyTransferMode.PULL,
217 """Config-dict key under which a parameter's injected companion entity ID is stored."""
218 return f
"_{param_key}_id"
225 CONF_SX1262_RX_BANDWIDTH:
"Radio SX1262 RX Bandwidth (kHz)",
226 CONF_SX1262_RESPONSE_PREAMBLE:
"Radio SX1262 Response Preamble",
227 CONF_SX1262_POST_TX_SETTLE_US:
"Radio SX1262 Post-TX Settle",
228 CONF_SX1276_RX_BANDWIDTH:
"Radio SX1276 RX Bandwidth (kHz)",
229 CONF_SX1276_RESPONSE_PREAMBLE:
"Radio SX1276 Response Preamble",
230 CONF_SX1276_DISCOVERY_HOP_SLICE_MS:
"Radio SX1276 Discovery Hop Slice",
231 CONF_SX1262_DISCOVERY_HOP_SLICE_MS:
"Radio SX1262 Discovery Hop Slice",
232 CONF_LR1121_RX_BANDWIDTH:
"Radio LR1121 RX Bandwidth (kHz)",
233 CONF_LR1121_RESPONSE_PREAMBLE:
"Radio LR1121 Response Preamble",
234 CONF_LR1121_POST_TX_SETTLE_US:
"Radio LR1121 Post-TX Settle",
235 CONF_LR1121_DISCOVERY_HOP_SLICE_MS:
"Radio LR1121 Discovery Hop Slice",
236 CONF_COLD_BROADCAST_REPLY_PREAMBLE:
"Radio Cold Broadcast Reply Preamble",
237 CONF_NORMAL_START_PREAMBLE:
"Radio Normal Start Preamble",
238 CONF_LBT_MAX_RETRIES:
"Radio LBT Max Retries",
239 CONF_LBT_RSSI_THRESHOLD_DBM:
"Radio LBT RSSI Threshold",
240 CONF_LOW_POWER_WAKE_BELIEF:
"Radio Low Power Wake Belief",
241 CONF_LOW_POWER_WAKE_PREAMBLE:
"Radio Low Power Wake Preamble",
242 CONF_EXCHANGE_START_RESPONSE_WAIT_MS:
"Exchange Start Response Wait",
243 CONF_EXCHANGE_EARLY_RESPONSE_WAIT_MS:
"Exchange Early Response Wait",
244 CONF_EXCHANGE_RESPONSE_WAIT_MS:
"Exchange Response Wait",
245 CONF_EXCHANGE_TOTAL_BUDGET_MS:
"Exchange Total Budget",
246 CONF_REMOTE_ACTIVITY_POLL_DELAY_MS:
"Remote Activity Poll Delay",
247 CONF_REMOTE_ACTIVITY_CATCHUP_POLL_MS:
"Remote Activity Catch-up Poll",
248 CONF_PAIRING_DISCOVERY_COMMANDS:
"Pairing Discovery Commands",
249 CONF_PAIRING_DISCOVERY_DESTINATION:
"Pairing Discovery Destination",
250 CONF_PAIRING_DISCOVERY_PAYLOAD:
"Pairing Discovery Payload",
251 CONF_PAIRING_DISCOVERY_LOW_POWER:
"Pairing Discovery Low Power",
252 CONF_PAIRING_DISCOVERY_ACK_CAPABLE:
"Pairing Discovery ACK Capable",
253 CONF_PAIRING_DISCOVERY_PREAMBLE:
"Pairing Discovery Preamble",
254 CONF_PAIRING_DISCOVERY_WAIT_MS:
"Pairing Discovery Wait",
255 CONF_PAIRING_DISCOVERY_INITIAL_DWELL_MS:
"Pairing Discovery Initial Dwell",
256 CONF_PAIRING_KEY_EXCHANGE_RETRIES:
"Pairing Key Exchange Retries",
257 CONF_SCAN_POWER_CLASSES:
"Pairing Scan Power Classes",
258 CONF_PAIRING_DISCOVER_CONFIRM:
"Pairing Discover Confirm",
259 CONF_PAIRING_KEY_INIT_DELAY_MS:
"Pairing Key Init Delay",
260 CONF_PAIRING_DISCOVERY_LISTEN_CHANNELS:
"Pairing Discovery Listen Channels",
261 CONF_PAIRING_KEY_TRANSFER:
"Pairing Key Transfer",
262 CONF_KEY_EXTRACTION_TYPE_WORD:
"Key Extraction Type Word",
263 CONF_KEY_EXTRACTION_MULTI_INFO:
"Key Extraction Multi Info",
273 CONF_SX1262_RESPONSE_PREAMBLE: (8, 256, 1,
"B"),
274 CONF_SX1262_POST_TX_SETTLE_US: (0, 2000, 10,
"µs"),
275 CONF_SX1276_RESPONSE_PREAMBLE: (8, 256, 1,
"B"),
276 CONF_SX1276_DISCOVERY_HOP_SLICE_MS: (5, 200, 1,
"ms"),
282 CONF_SX1262_DISCOVERY_HOP_SLICE_MS: (0, 500, 1,
"ms"),
285 CONF_LR1121_RESPONSE_PREAMBLE: (8, 256, 1,
"B"),
286 CONF_LR1121_POST_TX_SETTLE_US: (0, 2000, 10,
"µs"),
287 CONF_LR1121_DISCOVERY_HOP_SLICE_MS: (0, 500, 1,
"ms"),
294 CONF_COLD_BROADCAST_REPLY_PREAMBLE: (8, 256, 1,
"B"),
300 CONF_NORMAL_START_PREAMBLE: (8, 256, 1,
"B"),
306 CONF_PAIRING_DISCOVERY_PREAMBLE: (8, 1024, 1,
"B"),
312 CONF_LOW_POWER_WAKE_PREAMBLE: (1024, 4096, 64,
"B"),
313 CONF_LBT_MAX_RETRIES: (0, 10, 1,
""),
314 CONF_LBT_RSSI_THRESHOLD_DBM: (-95, -70, 1,
"dBm"),
318 CONF_EXCHANGE_START_RESPONSE_WAIT_MS: (200, 4000, 50,
"ms"),
324 CONF_EXCHANGE_EARLY_RESPONSE_WAIT_MS: (150, 1000, 50,
"ms"),
325 CONF_EXCHANGE_RESPONSE_WAIT_MS: (200, 4000, 50,
"ms"),
328 CONF_EXCHANGE_TOTAL_BUDGET_MS: (500, 12000, 100,
"ms"),
331 CONF_REMOTE_ACTIVITY_POLL_DELAY_MS: (0, 120000, 100,
"ms"),
333 CONF_REMOTE_ACTIVITY_CATCHUP_POLL_MS: (0, 600000, 1000,
"ms"),
334 CONF_PAIRING_DISCOVERY_WAIT_MS: (500, 5000, 50,
"ms"),
335 CONF_PAIRING_DISCOVERY_INITIAL_DWELL_MS: (0, 500, 10,
"ms"),
336 CONF_PAIRING_KEY_EXCHANGE_RETRIES: (1, 5, 1,
""),
339 CONF_PAIRING_KEY_INIT_DELAY_MS: (0, 10000, 100,
"ms"),
343 CONF_KEY_EXTRACTION_TYPE_WORD: (0, 0xFFFF, 1,
""),
344 CONF_KEY_EXTRACTION_MULTI_INFO: (0, 0xFF, 1,
""),
357_BOOL_PARAMS = (CONF_PAIRING_DISCOVERY_LOW_POWER, CONF_PAIRING_DISCOVERY_ACK_CAPABLE, CONF_LOW_POWER_WAKE_BELIEF)
358_BOOL_SELECT_OPTIONS = [
"Off",
"On"]
363 CONF_PAIRING_DISCOVERY_COMMANDS: DISCOVERY_COMMAND_PRESETS,
364 CONF_PAIRING_DISCOVERY_DESTINATION: DISCOVERY_DESTINATION_OPTIONS,
365 CONF_PAIRING_DISCOVERY_PAYLOAD: PAIRING_DISCOVERY_PAYLOAD_OPTIONS,
369 CONF_PAIRING_DISCOVERY_LOW_POWER: _BOOL_SELECT_OPTIONS,
370 CONF_PAIRING_DISCOVERY_ACK_CAPABLE: _BOOL_SELECT_OPTIONS,
371 CONF_SX1262_RX_BANDWIDTH: list(SX1262_BANDWIDTH_OPTIONS),
372 CONF_SX1276_RX_BANDWIDTH: list(SX1276_BANDWIDTH_OPTIONS),
373 CONF_LR1121_RX_BANDWIDTH: list(LR1121_BANDWIDTH_OPTIONS),
374 CONF_LOW_POWER_WAKE_BELIEF: _BOOL_SELECT_OPTIONS,
375 CONF_SCAN_POWER_CLASSES: list(SCAN_POWER_CLASSES_OPTIONS),
376 CONF_PAIRING_DISCOVER_CONFIRM: list(DISCOVER_CONFIRM_MODE_OPTIONS),
377 CONF_PAIRING_DISCOVERY_LISTEN_CHANNELS: list(DISCOVERY_LISTEN_CHANNELS_OPTIONS),
378 CONF_PAIRING_KEY_TRANSFER: list(KEY_TRANSFER_MODE_OPTIONS),
383 """Build a validator that requires a string value present in `options`.
385 Works for both list and dict `options` (dict membership tests the keys), so a single
386 factory replaces a hand-written validator per option set while keeping a per-parameter
387 error message. With `coerce_number`, a numeric YAML value (e.g. ``117.3``) is accepted and
388 normalized to its string form, so bare-number options do not force the user to quote them.
392 if coerce_number
and isinstance(value, (int, float))
and not isinstance(value, bool):
394 value = cv.string_strict(value)
395 if value
not in options:
396 raise cv.Invalid(f
"{param_name} must be one of {list(options)}")
403 CONF_SX1262_RX_BANDWIDTH, SX1262_BANDWIDTH_OPTIONS, coerce_number=
True
406 CONF_SX1276_RX_BANDWIDTH, SX1276_BANDWIDTH_OPTIONS, coerce_number=
True
409 CONF_LR1121_RX_BANDWIDTH, LR1121_BANDWIDTH_OPTIONS, coerce_number=
True
412 f
"{CONF_PAIRING_DISCOVERY_COMMANDS} entries", DISCOVERY_COMMAND_OPTIONS
415 CONF_PAIRING_DISCOVERY_DESTINATION, DISCOVERY_DESTINATION_OPTIONS
418 CONF_PAIRING_DISCOVERY_PAYLOAD, PAIRING_DISCOVERY_PAYLOAD_OPTIONS
421 CONF_SCAN_POWER_CLASSES, SCAN_POWER_CLASSES_OPTIONS
424 CONF_PAIRING_DISCOVER_CONFIRM, DISCOVER_CONFIRM_MODE_OPTIONS
427 CONF_PAIRING_DISCOVERY_LISTEN_CHANNELS, DISCOVERY_LISTEN_CHANNELS_OPTIONS
430 CONF_PAIRING_KEY_TRANSFER, KEY_TRANSFER_MODE_OPTIONS
436 CONF_KEY_EXTRACTION_TYPE_WORD: 4,
437 CONF_KEY_EXTRACTION_MULTI_INFO: 2,
442 """Build a validator for a protocol byte or word: an int (YAML ``0xDC``) or a hex string.
444 Accepts ``0xDC``, ``"0xDC"`` and ``"DC"`` (spaces allowed, so ``"01 80"`` copied from a frame
445 dump works), and rejects anything wider than `digits` hex digits.
447 max_value = (1 << (4 * digits)) - 1
450 if isinstance(value, bool):
451 raise cv.Invalid(f
"{param_name} must be a hex value such as 0x{'0' * (digits - 2)}DC")
452 if isinstance(value, str):
453 text = value.replace(
" ",
"").lower()
454 text = text.removeprefix(
"0x")
456 value = int(text, 16)
457 except ValueError
as err:
458 raise cv.Invalid(f
"{param_name} must be a hex value, got '{value}'")
from err
459 if not isinstance(value, int)
or not 0 <= value <= max_value:
460 raise cv.Invalid(f
"{param_name} must be between 0x00 and 0x{max_value:0{digits}X}")
467 """Convert an explicit destination string like '0x00003B' to a list of 3 byte values."""
469 return [int(digits[i : i + 2], 16)
for i
in range(0, 6, 2)]
473 """Convert a payload option string like '0x00' to its numeric byte value."""
474 return int(value, 16)
483TUNING_SCHEMA = cv.Schema(
485 cv.Optional(CONF_UI_CONTROLS, default=
False): cv.boolean,
486 cv.Optional(CONF_SX1262_RX_BANDWIDTH): _validate_bandwidth,
487 cv.Optional(CONF_SX1276_RX_BANDWIDTH): _validate_sx1276_bandwidth,
488 cv.Optional(CONF_LR1121_RX_BANDWIDTH): _validate_lr1121_bandwidth,
489 cv.Optional(CONF_PAIRING_DISCOVERY_COMMANDS): cv.All(
490 cv.ensure_list(_validate_discovery_command),
493 cv.Optional(CONF_PAIRING_DISCOVERY_DESTINATION): _validate_discovery_destination,
494 cv.Optional(CONF_PAIRING_DISCOVERY_PAYLOAD): _validate_discovery_payload,
495 cv.Optional(CONF_SCAN_POWER_CLASSES): _validate_scan_power_classes,
496 cv.Optional(CONF_PAIRING_DISCOVER_CONFIRM): _validate_discover_confirm_mode,
498 CONF_PAIRING_DISCOVERY_LISTEN_CHANNELS
499 ): _validate_discovery_listen_channels,
500 cv.Optional(CONF_PAIRING_KEY_TRANSFER): _validate_key_transfer_mode,
501 **{cv.Optional(key): cv.boolean
for key
in _BOOL_PARAMS},
504 cv.Optional(key): cv.int_range(min=lo, max=hi)
505 for key, (lo, hi, _step, _unit)
in _NUMBER_PARAMS.items()
506 if key
not in _HEX_PARAMS
508 **{cv.Optional(key):
_hex_value(key, digits)
for key, digits
in _HEX_PARAMS.items()},
514 """Declare companion entity IDs for tuning UI controls.
516 ESPHome 2026.x sizes the runtime component vector from the number of IDs
517 declared during validation. If UI entities were created only in to_code(),
518 they could be silently dropped. This post-validator injects declared IDs
519 before the vector is sized.
521 if not config[CONF_UI_CONTROLS]:
527 base = _COMPANION_ID_BASE
529 for key
in _SELECT_OPTIONS:
530 config[
_id_key(key)] = ID(f
"{base}_{key}", is_declaration=
True, type=IOHomeTuningSelect)
531 for key
in _NUMBER_PARAMS:
532 config[
_id_key(key)] = ID(f
"{base}_{key}", is_declaration=
True, type=IOHomeTuningNumber)
537TUNING_CONFIG_SCHEMA = cv.All(TUNING_SCHEMA, _inject_tuning_companion_ids)
541 """Format a Python bool as a C++ boolean literal."""
542 return "true" if value
else "false"
546 """Emit a `struct.field = value;` C++ assignment statement at codegen time.
548 ESPHome's MockObj does not support Python attribute assignment, so struct fields
549 are populated with raw assignment statements. `value` must already be valid C++
550 (an int, or a string such as an enum value or brace-init list).
552 cg.add(cg.RawExpression(f
"{struct}.{field} = {value}"))
556 """Generate C++ code that builds a TuningConfig from the validated YAML.
558 Only keys the user actually provided are emitted; every omitted key keeps the
559 default baked into the C++ TuningConfig struct (sourced from proto_frame.h), so
560 the defaults are never restated here.
562 tuning_id = cv.declare_id(TuningConfig)(
"tuning_config")
563 tuning = cg.new_variable(tuning_id, cg.RawExpression(f
"{TuningConfig}()"))
566 _assign(tuning,
"active",
"true")
569 if CONF_SX1262_RX_BANDWIDTH
in config:
572 "sx1262_rx_bandwidth",
573 SX1262_BANDWIDTH_OPTIONS[config[CONF_SX1262_RX_BANDWIDTH]],
576 if CONF_SX1276_RX_BANDWIDTH
in config:
579 "sx1276_rx_bandwidth",
580 SX1276_BANDWIDTH_OPTIONS[config[CONF_SX1276_RX_BANDWIDTH]],
583 if CONF_LR1121_RX_BANDWIDTH
in config:
586 "lr1121_rx_bandwidth",
587 LR1121_BANDWIDTH_OPTIONS[config[CONF_LR1121_RX_BANDWIDTH]],
590 if CONF_SCAN_POWER_CLASSES
in config:
593 "scan_power_classes",
594 SCAN_POWER_CLASSES_OPTIONS[config[CONF_SCAN_POWER_CLASSES]],
597 if CONF_PAIRING_DISCOVER_CONFIRM
in config:
600 "pairing_discover_confirm",
601 DISCOVER_CONFIRM_MODE_OPTIONS[config[CONF_PAIRING_DISCOVER_CONFIRM]],
604 if CONF_PAIRING_DISCOVERY_LISTEN_CHANNELS
in config:
607 "pairing_discovery_listen_channels",
608 DISCOVERY_LISTEN_CHANNELS_OPTIONS[
609 config[CONF_PAIRING_DISCOVERY_LISTEN_CHANNELS]
613 if CONF_PAIRING_KEY_TRANSFER
in config:
616 "pairing_key_transfer",
617 KEY_TRANSFER_MODE_OPTIONS[config[CONF_PAIRING_KEY_TRANSFER]],
622 if CONF_PAIRING_DISCOVERY_COMMANDS
in config:
623 cg.add(tuning.pairing_discovery_commands.clear())
624 for cmd
in config[CONF_PAIRING_DISCOVERY_COMMANDS]:
626 tuning.pairing_discovery_commands.push_back(
627 DISCOVERY_COMMAND_OPTIONS[cmd]
631 if CONF_PAIRING_DISCOVERY_DESTINATION
in config:
632 dest = config[CONF_PAIRING_DISCOVERY_DESTINATION]
634 _assign(tuning,
"pairing_discovery_destination_auto",
"true")
636 _assign(tuning,
"pairing_discovery_destination_auto",
"false")
640 "pairing_discovery_destination",
641 f
"{{0x{b[0]:02X}, 0x{b[1]:02X}, 0x{b[2]:02X}}}",
644 if CONF_PAIRING_DISCOVERY_PAYLOAD
in config:
645 payload = config[CONF_PAIRING_DISCOVERY_PAYLOAD]
646 if payload ==
"none":
647 _assign(tuning,
"pairing_discovery_payload_enabled",
"false")
649 _assign(tuning,
"pairing_discovery_payload_enabled",
"true")
650 _assign(tuning,
"pairing_discovery_payload", f
"0x{_parse_payload(payload):02X}")
653 for key
in _BOOL_PARAMS:
658 for key
in _NUMBER_PARAMS:
660 _assign(tuning, key, config[key])
666 if CONF_NORMAL_START_PREAMBLE
in config:
667 _assign(tuning,
"normal_start_preamble_from_yaml",
"true")
671 if CONF_PAIRING_DISCOVERY_PREAMBLE
in config:
672 _assign(tuning,
"pairing_discovery_preamble_set",
"true")
674 cg.add(var.set_tuning_config(tuning))
678 """Generate number/select entities for the tuning parameters when enabled."""
679 if not config[CONF_UI_CONTROLS]:
683 for key, options
in _SELECT_OPTIONS.items():
687 for key, (min_value, max_value, step, unit)
in _NUMBER_PARAMS.items():
689 config, var, key, min_value=min_value, max_value=max_value, step=step, unit=unit
694 config, var, key, min_value, max_value, step, unit=""
696 """Create a single IOHomeTuningNumber entity.
698 The bare {id, name} dict is normalized through number_schema()+COMPONENT_SCHEMA so it
699 carries the entity/component defaults (disabled_by_default, setup_priority, ...) that
700 register_number()/register_component() require.
702 schema = number.number_schema(
704 unit_of_measurement=unit,
705 entity_category=ENTITY_CATEGORY_CONFIG,
707 entity_config = schema.extend(cv.COMPONENT_SCHEMA)(
710 CONF_NAME: UI_NAMES[key],
713 entity = await number.new_number(
721 await cg.register_component(entity, entity_config)
725 """Create a single IOHomeTuningSelect entity.
727 The bare {id, name} dict is normalized through select_schema()+COMPONENT_SCHEMA so it
728 carries the entity/component defaults that register_select()/register_component() require.
730 entity_config = select.select_schema(
731 IOHomeTuningSelect, entity_category=ENTITY_CATEGORY_CONFIG
732 ).extend(cv.COMPONENT_SCHEMA)(
735 CONF_NAME: UI_NAMES[key],
738 entity = await select.new_select(
744 await cg.register_component(entity, entity_config)
748 """Generate code for the tuning block."""
_create_tuning_entities(config, var)
_apply_tuning_config(config, var)
_parse_destination_to_bytes(value)
_inject_tuning_companion_ids(config)
_hex_value(param_name, digits)
_create_select(config, var, key, options)
_assign(struct, field, value)
_one_of_string(param_name, options, coerce_number=False)
_create_number(config, var, key, min_value, max_value, step, unit="")