Files
home-information/docs/dev
Tony C d2449ef0fc Issue #319: EntityStateRole for semantic role per EntityState (#322)
* Introduce EntityStateRole for semantic role per EntityState

Adds a first-class semantic-role concept to EntityState so the data
model can express what each state means within its enclosing entity.
Resolves the multi-of-same-type case (a thermostat's four
TEMPERATURE substates; a fan's two DISCRETE substates) and replaces
the legacy EntityStateType-priority pre-filter that bucketed icon
display by type rather than per-entity primary state.

Schema and model
- New EntityStateRole(LabeledEnum) in hi/apps/entity/enums.py with
  two tiers: type-default roles (one per EntityStateType, name-
  matched) and domain-prefixed refinements (THERMOSTAT_*, FAN_*,
  LIGHT_*, HVAC_*).
- New EntityStateType.default_role() method.
- New EntityState.role_str field + entity_state_role property /
  setter. Save() defaults role_str to the type's default when not
  set explicitly, so direct EntityState.objects.create() paths get
  a role automatically.
- Migration 0016 backfills existing rows with their type-default
  role; behavior pre-refinement is unchanged.

Factory and HA integration
- HiModelHelper.create_sensor / create_controller gain an optional
  entity_state_role parameter; threaded through to EntityState
  creation when provided.
- HassConverter._SubstateSpec renamed _StateSpec; new role field on
  the spec. Climate / fan / light substate spec lists assign
  domain-prefixed roles for the multi-of-same-type cases.

Modal listing order
- New EntityStateRoleOrdering class + module-level
  ENTITY_STATUS_VIEW_ORDERING instance in
  hi/apps/entity/entity_state_role_order.py with per-EntityType
  override maps.
- EntityStatusData.to_template_context() emits state_status_data_list
  (sorted by role priority); the underlying field stays order-
  neutral for non-modal consumers.

LocationView icon primary-state selection
- ENTITY_PRIMARY_STATE_ORDERING (separate instance) selects the
  primary state for entity-level visual representation.
- LocationViewData._get_latest_entity_state_status_data_map switched
  from timestamp-sort to role-priority sort.
- StatusDisplayManager.get_entity_to_entity_state_status_data_list
  no longer pre-filters by EntityStateType priority; returns all
  states (including delegations). Shared
  _all_entity_states_including_delegations helper introduced.
- LocationViewData._get_css_class_map now emits exactly one
  hi-entity-state-* class per entity (the primary state's), so
  per-state polling updates don't clobber the entity's status
  attribute via shared CSS classes.

EntityStatusView template dispatch
- entity_status.html outer wrapper dispatches via include_with_fallback
  to per-EntityType templates; entity_status_default.html carries
  the existing flat-list body. Placeholder thermostat / ceiling_fan
  templates establish the customization pattern.

Decision-point markers
- LocationViewType and OneClickControlService._find_controller carry
  comments noting the legacy entity_state_type_priority_list is
  retained for one-click, pending a parallel
  ENTITY_CONTROL_STATE_ORDERING follow-up.

Docs
- integration-guidelines.md gains "EntityStateType vs.
  EntityStateRole" section explaining the two axes and the
  integration's role-declaration responsibility.

Closes #319 (with manual verification of thermostat / fan / icon
status behavior). Audit of EntityStateType vs role conflation
(MOVEMENT vs PRESENCE, POWER_LEVEL vs LIGHT_DIMMER) and one-click
ordering consolidation are out of scope and tracked as
follow-ups.

* Migrate OneClickControlService to role-based selection; drop legacy type-priority filter

Completes the role-mechanism conversion: one-click control no
longer consults LocationViewType.entity_state_type_priority_list
and instead walks ENTITY_CONTROL_STATE_ORDERING for the entity's
EntityType. The legacy type-priority pre-filter on
LocationViewType is removed entirely along with its dead
consumers in StatusDisplayManager.

OneClickControlService
- execute_one_click_control and _find_controller no longer take
  a location_view_type kwarg. The view-level "only AUTOMATION
  invokes one-click" gate remains at LocationItemStatusView.
- _find_controller walks ENTITY_CONTROL_STATE_ORDERING.order_for
  strictly: only states whose role is listed are eligible. Unlisted
  roles (e.g., a thermostat's THERMOSTAT_TARGET_TEMPERATURE) are
  not one-click targets even when controllable.
- _is_toggle_eligible helper extracted to keep the picker honest
  (only states with toggle_values within ONE_CLICK_CHOICE_LIMIT
  qualify).

ENTITY_CONTROL_STATE_ORDERING
- New EntityStateRoleOrdering instance with a curated default list
  of universally safe-to-toggle roles: ON_OFF, OPEN_CLOSE,
  OPEN_CLOSE_POSITION, POWER_LEVEL, LIGHT_DIMMER. Binary roles
  come first so they win when both binary and continuous variants
  exist.
- Per-EntityType overrides for LIGHT (LIGHT_ON_OFF + LIGHT_BRIGHTNESS),
  CEILING_FAN / EXHAUST_FAN (FAN_SPEED), GARAGE_DOOR_OPENER (OPEN_CLOSE).
  Switches / outlets / locks rely on the default ON_OFF fallback.

Legacy removals
- LocationViewType.entity_state_type_priority_list and its __init__
  override are gone; the enum collapses to plain DEFAULT / SECURITY
  / AUTOMATION members carrying just label and description.
- StatusDisplayManager.get_entity_state_list_for_status and
  get_entity_state_type_for_status removed (their last consumer
  was one-click; the icon path stopped using them earlier in #319).

Tests
- New test_one_click_control_service.py covers: default fallback
  for unrecognized EntityTypes; LIGHT override preferring
  LIGHT_ON_OFF; LIGHT_BRIGHTNESS fallback for fully-modeled color
  bulbs; speed-only fan via POWER_LEVEL default; cover with
  OPEN_CLOSE_POSITION via default; sensor-only and thermostat
  cases correctly NotSupported; toggle-value-limit guard.

* Fix collection card sensor polling updates and apply role ordering to collection cards

Audit of CollectionView rendering surfaced two issues that #319's
work touched indirectly:

Sensor values not updating from polling
- Collection card sensor renders did not wrap the value template in
  the per-EntityState CSS class, so the entity_state_status.js
  dispatcher had no DOM target for `.hi-entity-state-{id}` polling
  updates. Controllers worked because controller_data.html carries
  the class itself; sensor-only states (thermometer, humidity, etc.)
  silently never updated.
- entity_state_row.html now wraps the value include in a div with
  sensor_response.css_class, matching the modal's pattern in
  sensor_response_status_row.html.

State ordering inconsistency between modal and collection cards
- EntityStatusData.state_status_data_list promoted from a local in
  to_template_context() to a public property. The modal continues
  to consume it via the template context; collection card templates
  now access it directly on the dataclass instead of reading the
  unordered entity_state_status_data_list field. Single source of
  truth for "display order" across both surfaces.
- entity_card_list.html and entity_card_grid.html updated to pass
  entity_status_data.state_status_data_list into the partial.

* New urlib3 version with security patch

* Rename "wire value" to "EntityState value" in canonical-value docstrings

The term "wire value" is appropriate for integration-boundary code where
values genuinely travel over a wire (HA REST/WS payloads). Internally, once
an EntityState has stored its value, it is the canonical EntityState value,
not a wire-format string. Updating docstrings, parameter names, and local
variable names in EntityStateValue.to_display_label, EntityState.choices,
value_label tag, and associated tests to match.

* Promote delegations helper, dedup result, prefetch hot path, add review-cycle test

Review-cycle follow-ups for the EntityStateRole work:

- Rename ``StatusDisplayManager._all_entity_states_including_delegations``
  to ``all_entity_states_including_delegations`` (public). It is called
  from OneClickControlService and reaching across module boundaries to
  a private helper is an antipattern.

- Deduplicate the helper's return list to guard against entities that
  delegate one of their own states or that have two delegations
  resolving to the same EntityState.

- Add a one-shot ``prefetch_related_objects`` in
  ``get_entity_to_entity_state_status_data_list`` covering the entity
  states, delegations, sensors, and controllers walked downstream. On
  a LocationView with 20 entities of ~3 states each this collapses
  from ~160 queries per render to ~6. Drop the now-defeated
  ``select_related('entity_state')`` inside the helper so prefetched
  callers actually use the cache; the FK fetch cost for non-prefetched
  callers is bounded.

- Add comments documenting two intentional choices: EntityStateRole's
  label collisions between bare and domain-refined roles, and the
  intentional absence of sensor-only roles from
  ``DEFAULT_CONTROL_STATE_ROLE_ORDER``.

- Add ``execute_one_click_control`` end-to-end tests pinning the full
  pipeline (find_controller → get_current_state_value →
  determine_control_value → ControllerManager.do_control) for both
  the "toggle from known state" and "no sensor history" branches.
2026-05-12 18:04:44 -05:00
..
2026-04-20 10:05:04 -05:00
2025-09-25 18:31:38 -05:00
2026-04-20 10:05:04 -05:00

Home Information Logo

Developer Documentation

See the markdown files in this directory for various developer-related documentation.

The code is the best documentation for lower-level details, but there are some higher-level concepts that are useful to help orient developers. This is the place for that high-level, developer-specific documentation.