Skip to content

components: drive pin-field UX with hardware-capability requirements (pin_features) and pin_mode #1012

@bdraco

Description

@bdraco

Background

While reviewing PR #1011 (mashumaro from_dict refactor), we noticed that two pieces of ConfigEntry metadata are wired up end-to-end but never carry any actual data, so the corresponding UX gates are no-ops today across all 317 PIN-type fields in the catalog:

Field What it would drive Actual content today
entry.pin_features: list[PinFeature] Filter the pin dropdown to board pins whose features list includes every required capability (required.every(f => pin.features.includes(f)) at config-entry-pin-renderer.ts:125-128) Empty list for every component
`entry.pin_mode: PinMode None` Disable input-only pins for fields needing output (line 64-68 of the same file)

Background trail: PR #1011 surfaced this when filtering _resolve_pin_features in script/sync_components.py against the PinFeature enum dropped 100% of catalog pin_features entries (the schema was emitting GPIO mode strings like input / output / pullup that don't belong on a hardware-capability enum; the runtime was already silently dropping them via _safe_enum before they reached the wire). The data shape is now honest, but the metadata pipe runs dry.

The frontend filter wiring, the model shape, and the board manifests are all already in place — board manifests list real hardware features (adc, dac, i2c_sda, spi_clk, uart_tx, pwm, touch, strapping, input_only, …). The missing piece is just the per-field requirement.

What this issue covers

  1. Hand-curated _PIN_FEATURE_OVERRIDES table in script/sync_components.py. Map (component_id, field_key) → list[PinFeature] for high-value entries. ~20-30 rows covers most of the user-visible benefit:

    Component / field Required feature
    sensor.adc.pin ADC
    dac.gpio.pin DAC
    i2c.sda / i2c.scl I2C_SDA / I2C_SCL
    spi.clk_pin / mosi_pin / miso_pin / cs_pin SPI_CLK / SPI_MOSI / SPI_MISO / SPI_CS
    uart.tx_pin / rx_pin UART_TX / UART_RX
    esp32_touch.pin TOUCH
    output.ledc.pin, light-PWM outputs PWM

    Mirrors the existing _FIELD_OVERRIDES and INTERNAL_COMPONENT_IDS hand-curation pattern. Applied during emission so the build-time roundtrip guard (components: replace hand-rolled body loader with mashumaro from_dict, tighten sync-script roundtrip #1006 / _emit_split_catalog) catches typos at sync time.

  2. Same exercise for pin_mode. Whether each PIN field expects input or output:

    • output.gpio.pin, stepper dir_pin / step_pin / sleep_pin, switch.gpio.pin (when driving a load) → OUTPUT
    • binary_sensor.gpio.pin, button GPIO inputs → INPUT
    • Most can probably be inferred from the schema's pin-block default mode: setting — the sync script already walks the inner schema and could lift that to a field-level pin_mode.
  3. Soften the frontend filter. The current required.every(f => pin.features.includes(f)) hides non-matching pins. That's harsh for unusual boards and "I know what I'm doing" workflows. Render-time options:

    • Show matching pins normally; flag non-matching pins with a warning icon + tooltip (e.g., "This pin doesn't expose ADC; the component may not work as expected.")
    • Group: matching pins on top, "Other pins" collapsed below
    • Keep selection always allowed; only the visual affordance changes

    Companion frontend change in config-entry-pin-renderer.ts.

Why not push it upstream into the ESPHome schema bundle?

Cleaner long-term, but it requires an upstream PR + schema version coordination + waiting for a bundle release. The hand-curated table gives us the user-visible benefit immediately while leaving the door open to a schema-driven source of truth later — same way INTERNAL_COMPONENT_IDS was hand-maintained even though the auto-load chain notionally lives in ESPHome's component metadata.

Acceptance

  • _PIN_FEATURE_OVERRIDES populated for the components listed above (and any others the maintainers want covered).
  • pin_mode populated for OUTPUT-driving fields where the schema's default mode makes it unambiguous.
  • Frontend renders non-matching pins with a soft affordance (warning icon / grouped section) rather than hiding them; pin selection stays unrestricted.
  • A test pinning at least one mapping per category (sensor.adc.pin requires ADC, etc.) so a regen-without-the-table regression is caught in CI.

Out of scope

  • Adding new PinFeature enum values — the current set covers everything board manifests carry.
  • Restructuring the board manifest pin schema.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions