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_EXCHANGE_START_RESPONSE_WAIT_MS =
"exchange_start_response_wait_ms"
42CONF_EXCHANGE_RESPONSE_WAIT_MS =
"exchange_response_wait_ms"
43CONF_EXCHANGE_TOTAL_BUDGET_MS =
"exchange_total_budget_ms"
46CONF_PAIRING_DISCOVERY_COMMANDS =
"pairing_discovery_commands"
47CONF_PAIRING_DISCOVERY_DESTINATION =
"pairing_discovery_destination"
48CONF_PAIRING_DISCOVERY_PAYLOAD =
"pairing_discovery_payload"
49CONF_PAIRING_DISCOVERY_LOW_POWER =
"pairing_discovery_low_power"
50CONF_PAIRING_DISCOVERY_ACK_CAPABLE =
"pairing_discovery_ack_capable"
51CONF_PAIRING_DISCOVERY_PREAMBLE =
"pairing_discovery_preamble"
52CONF_PAIRING_DISCOVERY_WAIT_MS =
"pairing_discovery_wait_ms"
53CONF_PAIRING_DISCOVERY_INITIAL_DWELL_MS =
"pairing_discovery_initial_dwell_ms"
54CONF_PAIRING_KEY_EXCHANGE_RETRIES =
"pairing_key_exchange_retries"
55CONF_SCAN_POWER_CLASSES =
"scan_power_classes"
56CONF_PAIRING_DISCOVER_CONFIRM =
"pairing_discover_confirm"
57CONF_PAIRING_DISCOVERY_LISTEN_CHANNELS =
"pairing_discovery_listen_channels"
58CONF_PAIRING_KEY_INIT_DELAY_MS =
"pairing_key_init_delay_ms"
61TuningConfig = home_io_control_ns.class_(
"TuningConfig")
62IOHomeTuningNumber = home_io_control_ns.class_(
63 "IOHomeTuningNumber", number.Number, cg.Component
65IOHomeTuningSelect = home_io_control_ns.class_(
66 "IOHomeTuningSelect", select.Select, cg.Component
72SX1262RxBandwidth = home_io_control_ns.enum(
"SX1262RxBandwidth", is_class=
True)
73SX1276RxBandwidth = home_io_control_ns.enum(
"SX1276RxBandwidth", is_class=
True)
74LR1121RxBandwidth = home_io_control_ns.enum(
"LR1121RxBandwidth", is_class=
True)
75DiscoveryCommand = home_io_control_ns.enum(
"DiscoveryCommand", is_class=
True)
76ScanPowerClasses = home_io_control_ns.enum(
"ScanPowerClasses", is_class=
True)
77DiscoverConfirmMode = home_io_control_ns.enum(
"DiscoverConfirmMode", is_class=
True)
78DiscoveryListenChannels = home_io_control_ns.enum(
"DiscoveryListenChannels", is_class=
True)
82SX1262_BANDWIDTH_OPTIONS = {
83 "39.0": SX1262RxBandwidth.BW_39_0_KHZ,
84 "46.9": SX1262RxBandwidth.BW_46_9_KHZ,
85 "58.6": SX1262RxBandwidth.BW_58_6_KHZ,
86 "78.2": SX1262RxBandwidth.BW_78_2_KHZ,
87 "117.3": SX1262RxBandwidth.BW_117_3_KHZ,
88 "156.2": SX1262RxBandwidth.BW_156_2_KHZ,
89 "187.2": SX1262RxBandwidth.BW_187_2_KHZ,
92SX1276_BANDWIDTH_OPTIONS = {
93 "20.8": SX1276RxBandwidth.BW_20_8_KHZ,
94 "41.7": SX1276RxBandwidth.BW_41_7_KHZ,
95 "62.5": SX1276RxBandwidth.BW_62_5_KHZ,
96 "83.3": SX1276RxBandwidth.BW_83_3_KHZ,
97 "125.0": SX1276RxBandwidth.BW_125_0_KHZ,
107LR1121_BANDWIDTH_OPTIONS = {
108 "39.0": LR1121RxBandwidth.BW_39_0_KHZ,
109 "46.9": LR1121RxBandwidth.BW_46_9_KHZ,
110 "58.6": LR1121RxBandwidth.BW_58_6_KHZ,
111 "78.2": LR1121RxBandwidth.BW_78_2_KHZ,
112 "117.3": LR1121RxBandwidth.BW_117_3_KHZ,
113 "156.2": LR1121RxBandwidth.BW_156_2_KHZ,
114 "187.2": LR1121RxBandwidth.BW_187_2_KHZ,
117DISCOVERY_COMMAND_OPTIONS = {
118 "0x28": DiscoveryCommand.DISCOVER,
119 "0x2E": DiscoveryCommand.DISCOVER_ALT,
130DISCOVERY_COMMAND_PRESETS = [
149DISCOVERY_DESTINATION_OPTIONS = [
157PAIRING_DISCOVERY_PAYLOAD_OPTIONS = [
"none",
"0x00"]
164SCAN_POWER_CLASSES_OPTIONS = {
165 "both": ScanPowerClasses.BOTH,
166 "always_alive": ScanPowerClasses.ALWAYS_ALIVE,
167 "low_power": ScanPowerClasses.LOW_POWER,
175DISCOVER_CONFIRM_MODE_OPTIONS = {
176 "skip": DiscoverConfirmMode.SKIP,
177 "send": DiscoverConfirmMode.SEND,
178 "send_with_ack": DiscoverConfirmMode.SEND_WITH_ACK,
185DISCOVERY_LISTEN_CHANNELS_OPTIONS = {
186 "skip_request": DiscoveryListenChannels.SKIP_REQUEST,
187 "all": DiscoveryListenChannels.ALL,
192 """Config-dict key under which a parameter's injected companion entity ID is stored."""
193 return f
"_{param_key}_id"
200 CONF_SX1262_RX_BANDWIDTH:
"Radio SX1262 RX Bandwidth (kHz)",
201 CONF_SX1262_RESPONSE_PREAMBLE:
"Radio SX1262 Response Preamble",
202 CONF_SX1262_POST_TX_SETTLE_US:
"Radio SX1262 Post-TX Settle",
203 CONF_SX1276_RX_BANDWIDTH:
"Radio SX1276 RX Bandwidth (kHz)",
204 CONF_SX1276_RESPONSE_PREAMBLE:
"Radio SX1276 Response Preamble",
205 CONF_SX1276_DISCOVERY_HOP_SLICE_MS:
"Radio SX1276 Discovery Hop Slice",
206 CONF_SX1262_DISCOVERY_HOP_SLICE_MS:
"Radio SX1262 Discovery Hop Slice",
207 CONF_LR1121_RX_BANDWIDTH:
"Radio LR1121 RX Bandwidth (kHz)",
208 CONF_LR1121_RESPONSE_PREAMBLE:
"Radio LR1121 Response Preamble",
209 CONF_LR1121_POST_TX_SETTLE_US:
"Radio LR1121 Post-TX Settle",
210 CONF_LR1121_DISCOVERY_HOP_SLICE_MS:
"Radio LR1121 Discovery Hop Slice",
211 CONF_COLD_BROADCAST_REPLY_PREAMBLE:
"Radio Cold Broadcast Reply Preamble",
212 CONF_NORMAL_START_PREAMBLE:
"Radio Normal Start Preamble",
213 CONF_LBT_MAX_RETRIES:
"Radio LBT Max Retries",
214 CONF_LBT_RSSI_THRESHOLD_DBM:
"Radio LBT RSSI Threshold",
215 CONF_LOW_POWER_WAKE_BELIEF:
"Radio Low Power Wake Belief",
216 CONF_EXCHANGE_START_RESPONSE_WAIT_MS:
"Exchange Start Response Wait",
217 CONF_EXCHANGE_RESPONSE_WAIT_MS:
"Exchange Response Wait",
218 CONF_EXCHANGE_TOTAL_BUDGET_MS:
"Exchange Total Budget",
219 CONF_PAIRING_DISCOVERY_COMMANDS:
"Pairing Discovery Commands",
220 CONF_PAIRING_DISCOVERY_DESTINATION:
"Pairing Discovery Destination",
221 CONF_PAIRING_DISCOVERY_PAYLOAD:
"Pairing Discovery Payload",
222 CONF_PAIRING_DISCOVERY_LOW_POWER:
"Pairing Discovery Low Power",
223 CONF_PAIRING_DISCOVERY_ACK_CAPABLE:
"Pairing Discovery ACK Capable",
224 CONF_PAIRING_DISCOVERY_PREAMBLE:
"Pairing Discovery Preamble",
225 CONF_PAIRING_DISCOVERY_WAIT_MS:
"Pairing Discovery Wait",
226 CONF_PAIRING_DISCOVERY_INITIAL_DWELL_MS:
"Pairing Discovery Initial Dwell",
227 CONF_PAIRING_KEY_EXCHANGE_RETRIES:
"Pairing Key Exchange Retries",
228 CONF_SCAN_POWER_CLASSES:
"Pairing Scan Power Classes",
229 CONF_PAIRING_DISCOVER_CONFIRM:
"Pairing Discover Confirm",
230 CONF_PAIRING_KEY_INIT_DELAY_MS:
"Pairing Key Init Delay",
231 CONF_PAIRING_DISCOVERY_LISTEN_CHANNELS:
"Pairing Discovery Listen Channels",
241 CONF_SX1262_RESPONSE_PREAMBLE: (8, 256, 1,
"B"),
242 CONF_SX1262_POST_TX_SETTLE_US: (0, 2000, 10,
"µs"),
243 CONF_SX1276_RESPONSE_PREAMBLE: (8, 256, 1,
"B"),
244 CONF_SX1276_DISCOVERY_HOP_SLICE_MS: (5, 200, 1,
"ms"),
250 CONF_SX1262_DISCOVERY_HOP_SLICE_MS: (0, 500, 1,
"ms"),
253 CONF_LR1121_RESPONSE_PREAMBLE: (8, 256, 1,
"B"),
254 CONF_LR1121_POST_TX_SETTLE_US: (0, 2000, 10,
"µs"),
255 CONF_LR1121_DISCOVERY_HOP_SLICE_MS: (0, 500, 1,
"ms"),
262 CONF_COLD_BROADCAST_REPLY_PREAMBLE: (8, 256, 1,
"B"),
268 CONF_NORMAL_START_PREAMBLE: (8, 256, 1,
"B"),
274 CONF_PAIRING_DISCOVERY_PREAMBLE: (8, 1024, 1,
"B"),
275 CONF_LBT_MAX_RETRIES: (0, 10, 1,
""),
276 CONF_LBT_RSSI_THRESHOLD_DBM: (-95, -70, 1,
"dBm"),
280 CONF_EXCHANGE_START_RESPONSE_WAIT_MS: (200, 4000, 50,
"ms"),
281 CONF_EXCHANGE_RESPONSE_WAIT_MS: (200, 4000, 50,
"ms"),
284 CONF_EXCHANGE_TOTAL_BUDGET_MS: (500, 12000, 100,
"ms"),
285 CONF_PAIRING_DISCOVERY_WAIT_MS: (500, 5000, 50,
"ms"),
286 CONF_PAIRING_DISCOVERY_INITIAL_DWELL_MS: (0, 500, 10,
"ms"),
287 CONF_PAIRING_KEY_EXCHANGE_RETRIES: (1, 5, 1,
""),
290 CONF_PAIRING_KEY_INIT_DELAY_MS: (0, 10000, 100,
"ms"),
303_BOOL_PARAMS = (CONF_PAIRING_DISCOVERY_LOW_POWER, CONF_PAIRING_DISCOVERY_ACK_CAPABLE, CONF_LOW_POWER_WAKE_BELIEF)
304_BOOL_SELECT_OPTIONS = [
"Off",
"On"]
309 CONF_PAIRING_DISCOVERY_COMMANDS: DISCOVERY_COMMAND_PRESETS,
310 CONF_PAIRING_DISCOVERY_DESTINATION: DISCOVERY_DESTINATION_OPTIONS,
311 CONF_PAIRING_DISCOVERY_PAYLOAD: PAIRING_DISCOVERY_PAYLOAD_OPTIONS,
315 CONF_PAIRING_DISCOVERY_LOW_POWER: _BOOL_SELECT_OPTIONS,
316 CONF_PAIRING_DISCOVERY_ACK_CAPABLE: _BOOL_SELECT_OPTIONS,
317 CONF_SX1262_RX_BANDWIDTH: list(SX1262_BANDWIDTH_OPTIONS),
318 CONF_SX1276_RX_BANDWIDTH: list(SX1276_BANDWIDTH_OPTIONS),
319 CONF_LR1121_RX_BANDWIDTH: list(LR1121_BANDWIDTH_OPTIONS),
320 CONF_LOW_POWER_WAKE_BELIEF: _BOOL_SELECT_OPTIONS,
321 CONF_SCAN_POWER_CLASSES: list(SCAN_POWER_CLASSES_OPTIONS),
322 CONF_PAIRING_DISCOVER_CONFIRM: list(DISCOVER_CONFIRM_MODE_OPTIONS),
323 CONF_PAIRING_DISCOVERY_LISTEN_CHANNELS: list(DISCOVERY_LISTEN_CHANNELS_OPTIONS),
328 """Build a validator that requires a string value present in `options`.
330 Works for both list and dict `options` (dict membership tests the keys), so a single
331 factory replaces a hand-written validator per option set while keeping a per-parameter
332 error message. With `coerce_number`, a numeric YAML value (e.g. ``117.3``) is accepted and
333 normalized to its string form, so bare-number options do not force the user to quote them.
337 if coerce_number
and isinstance(value, (int, float))
and not isinstance(value, bool):
339 value = cv.string_strict(value)
340 if value
not in options:
341 raise cv.Invalid(f
"{param_name} must be one of {list(options)}")
348 CONF_SX1262_RX_BANDWIDTH, SX1262_BANDWIDTH_OPTIONS, coerce_number=
True
351 CONF_SX1276_RX_BANDWIDTH, SX1276_BANDWIDTH_OPTIONS, coerce_number=
True
354 CONF_LR1121_RX_BANDWIDTH, LR1121_BANDWIDTH_OPTIONS, coerce_number=
True
357 f
"{CONF_PAIRING_DISCOVERY_COMMANDS} entries", DISCOVERY_COMMAND_OPTIONS
360 CONF_PAIRING_DISCOVERY_DESTINATION, DISCOVERY_DESTINATION_OPTIONS
363 CONF_PAIRING_DISCOVERY_PAYLOAD, PAIRING_DISCOVERY_PAYLOAD_OPTIONS
366 CONF_SCAN_POWER_CLASSES, SCAN_POWER_CLASSES_OPTIONS
369 CONF_PAIRING_DISCOVER_CONFIRM, DISCOVER_CONFIRM_MODE_OPTIONS
372 CONF_PAIRING_DISCOVERY_LISTEN_CHANNELS, DISCOVERY_LISTEN_CHANNELS_OPTIONS
377 """Convert an explicit destination string like '0x00003B' to a list of 3 byte values."""
379 return [int(digits[i : i + 2], 16)
for i
in range(0, 6, 2)]
383 """Convert a payload option string like '0x00' to its numeric byte value."""
384 return int(value, 16)
393TUNING_SCHEMA = cv.Schema(
395 cv.Optional(CONF_UI_CONTROLS, default=
False): cv.boolean,
396 cv.Optional(CONF_SX1262_RX_BANDWIDTH): _validate_bandwidth,
397 cv.Optional(CONF_SX1276_RX_BANDWIDTH): _validate_sx1276_bandwidth,
398 cv.Optional(CONF_LR1121_RX_BANDWIDTH): _validate_lr1121_bandwidth,
399 cv.Optional(CONF_PAIRING_DISCOVERY_COMMANDS): cv.All(
400 cv.ensure_list(_validate_discovery_command),
403 cv.Optional(CONF_PAIRING_DISCOVERY_DESTINATION): _validate_discovery_destination,
404 cv.Optional(CONF_PAIRING_DISCOVERY_PAYLOAD): _validate_discovery_payload,
405 cv.Optional(CONF_SCAN_POWER_CLASSES): _validate_scan_power_classes,
406 cv.Optional(CONF_PAIRING_DISCOVER_CONFIRM): _validate_discover_confirm_mode,
408 CONF_PAIRING_DISCOVERY_LISTEN_CHANNELS
409 ): _validate_discovery_listen_channels,
410 **{cv.Optional(key): cv.boolean
for key
in _BOOL_PARAMS},
413 cv.Optional(key): cv.int_range(min=lo, max=hi)
414 for key, (lo, hi, _step, _unit)
in _NUMBER_PARAMS.items()
421 """Declare companion entity IDs for tuning UI controls.
423 ESPHome 2026.x sizes the runtime component vector from the number of IDs
424 declared during validation. If UI entities were created only in to_code(),
425 they could be silently dropped. This post-validator injects declared IDs
426 before the vector is sized.
428 if not config[CONF_UI_CONTROLS]:
434 base = _COMPANION_ID_BASE
436 for key
in _SELECT_OPTIONS:
437 config[
_id_key(key)] = ID(f
"{base}_{key}", is_declaration=
True, type=IOHomeTuningSelect)
438 for key
in _NUMBER_PARAMS:
439 config[
_id_key(key)] = ID(f
"{base}_{key}", is_declaration=
True, type=IOHomeTuningNumber)
444TUNING_CONFIG_SCHEMA = cv.All(TUNING_SCHEMA, _inject_tuning_companion_ids)
448 """Format a Python bool as a C++ boolean literal."""
449 return "true" if value
else "false"
453 """Emit a `struct.field = value;` C++ assignment statement at codegen time.
455 ESPHome's MockObj does not support Python attribute assignment, so struct fields
456 are populated with raw assignment statements. `value` must already be valid C++
457 (an int, or a string such as an enum value or brace-init list).
459 cg.add(cg.RawExpression(f
"{struct}.{field} = {value}"))
463 """Generate C++ code that builds a TuningConfig from the validated YAML.
465 Only keys the user actually provided are emitted; every omitted key keeps the
466 default baked into the C++ TuningConfig struct (sourced from proto_frame.h), so
467 the defaults are never restated here.
469 tuning_id = cv.declare_id(TuningConfig)(
"tuning_config")
470 tuning = cg.new_variable(tuning_id, cg.RawExpression(f
"{TuningConfig}()"))
473 _assign(tuning,
"active",
"true")
476 if CONF_SX1262_RX_BANDWIDTH
in config:
479 "sx1262_rx_bandwidth",
480 SX1262_BANDWIDTH_OPTIONS[config[CONF_SX1262_RX_BANDWIDTH]],
483 if CONF_SX1276_RX_BANDWIDTH
in config:
486 "sx1276_rx_bandwidth",
487 SX1276_BANDWIDTH_OPTIONS[config[CONF_SX1276_RX_BANDWIDTH]],
490 if CONF_LR1121_RX_BANDWIDTH
in config:
493 "lr1121_rx_bandwidth",
494 LR1121_BANDWIDTH_OPTIONS[config[CONF_LR1121_RX_BANDWIDTH]],
497 if CONF_SCAN_POWER_CLASSES
in config:
500 "scan_power_classes",
501 SCAN_POWER_CLASSES_OPTIONS[config[CONF_SCAN_POWER_CLASSES]],
504 if CONF_PAIRING_DISCOVER_CONFIRM
in config:
507 "pairing_discover_confirm",
508 DISCOVER_CONFIRM_MODE_OPTIONS[config[CONF_PAIRING_DISCOVER_CONFIRM]],
511 if CONF_PAIRING_DISCOVERY_LISTEN_CHANNELS
in config:
514 "pairing_discovery_listen_channels",
515 DISCOVERY_LISTEN_CHANNELS_OPTIONS[
516 config[CONF_PAIRING_DISCOVERY_LISTEN_CHANNELS]
522 if CONF_PAIRING_DISCOVERY_COMMANDS
in config:
523 cg.add(tuning.pairing_discovery_commands.clear())
524 for cmd
in config[CONF_PAIRING_DISCOVERY_COMMANDS]:
526 tuning.pairing_discovery_commands.push_back(
527 DISCOVERY_COMMAND_OPTIONS[cmd]
531 if CONF_PAIRING_DISCOVERY_DESTINATION
in config:
532 dest = config[CONF_PAIRING_DISCOVERY_DESTINATION]
534 _assign(tuning,
"pairing_discovery_destination_auto",
"true")
536 _assign(tuning,
"pairing_discovery_destination_auto",
"false")
540 "pairing_discovery_destination",
541 f
"{{0x{b[0]:02X}, 0x{b[1]:02X}, 0x{b[2]:02X}}}",
544 if CONF_PAIRING_DISCOVERY_PAYLOAD
in config:
545 payload = config[CONF_PAIRING_DISCOVERY_PAYLOAD]
546 if payload ==
"none":
547 _assign(tuning,
"pairing_discovery_payload_enabled",
"false")
549 _assign(tuning,
"pairing_discovery_payload_enabled",
"true")
550 _assign(tuning,
"pairing_discovery_payload", f
"0x{_parse_payload(payload):02X}")
553 for key
in _BOOL_PARAMS:
558 for key
in _NUMBER_PARAMS:
560 _assign(tuning, key, config[key])
566 if CONF_NORMAL_START_PREAMBLE
in config:
567 _assign(tuning,
"normal_start_preamble_from_yaml",
"true")
569 cg.add(var.set_tuning_config(tuning))
573 """Generate number/select entities for the tuning parameters when enabled."""
574 if not config[CONF_UI_CONTROLS]:
578 for key, options
in _SELECT_OPTIONS.items():
582 for key, (min_value, max_value, step, unit)
in _NUMBER_PARAMS.items():
584 config, var, key, min_value=min_value, max_value=max_value, step=step, unit=unit
589 config, var, key, min_value, max_value, step, unit=""
591 """Create a single IOHomeTuningNumber entity.
593 The bare {id, name} dict is normalized through number_schema()+COMPONENT_SCHEMA so it
594 carries the entity/component defaults (disabled_by_default, setup_priority, ...) that
595 register_number()/register_component() require.
597 schema = number.number_schema(
599 unit_of_measurement=unit,
600 entity_category=ENTITY_CATEGORY_CONFIG,
602 entity_config = schema.extend(cv.COMPONENT_SCHEMA)(
605 CONF_NAME: UI_NAMES[key],
608 entity = await number.new_number(
616 await cg.register_component(entity, entity_config)
620 """Create a single IOHomeTuningSelect entity.
622 The bare {id, name} dict is normalized through select_schema()+COMPONENT_SCHEMA so it
623 carries the entity/component defaults that register_select()/register_component() require.
625 entity_config = select.select_schema(
626 IOHomeTuningSelect, entity_category=ENTITY_CATEGORY_CONFIG
627 ).extend(cv.COMPONENT_SCHEMA)(
630 CONF_NAME: UI_NAMES[key],
633 entity = await select.new_select(
639 await cg.register_component(entity, entity_config)
643 """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)
_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="")