Skip to content

Latest commit

Β 

History

17 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Entity Device Mapper (ent2dev)

Entity Device Mapper (ent2dev) is a custom Home Assistant (HASS) integration that builds a high-performance mapping of entities to their corresponding devices.

It provides a WebSocket API (ent2dev/map) for frontend cards and scripts to access the device map efficiently without bloating the Home Assistant state machine or database.

πŸ› οΈ Features

  • Efficient Architecture:

    • Zero Database Impact: Data is stored in memory and served on-demand via WebSocket. No state attributes are written to the recorder.
    • Non-Blocking: Map building is non-blocking to prevent system freezes during startup or large registry updates.
    • Debounced Updates: Registry changes are aggregated (default 5s delay) to prevent "thundering herd" updates.
  • Entity-to-Device Mapping:
    Maps each entity_id to its associated device_id along with key device attributes.

  • On-Demand Services:
    Exposes ent2dev.get_devices and ent2dev.get_entities for precise querying in templates and automations.

  • Advanced Filtering:
    Filter by IDs, names (substring), states (e.g. unavailable), labels, or integrations on demand.

  • Universal Negation:
    Supports !value syntax to exclude specific items (e.g., labels=['!ephemeral'], integrations=['!tuya']).

πŸ“ File Structure

ent2dev/
β”œβ”€β”€ custom_components/
β”‚   └── ent2dev/
β”‚       β”œβ”€β”€ __init__.py    # Main logic (API, Debouncer)
β”‚       β”œβ”€β”€ const.py       # Configuration
β”‚       β”œβ”€β”€ config_flow.py # Config Entry setup
β”‚       └── manifest.json
β”œβ”€β”€ README.md
β”œβ”€β”€ LICENSE
└── .gitignore

Installation

Step 1: Install

Copy the ent2dev folder into your custom_components directory.

Step 2: Configure

No YAML configuration is required. Add the integration via the UI: Settings > Devices & Services > Add Integration > Search for "Entity Device Mapper"

πŸ“‘ Usage

Services (Recommended)

This integration exposes services to query data on demand. This is the preferred method for automations and templates.

1. ent2dev.get_devices

Returns a list of unique devices that match the criteria. Useful for "Missing Devices" dashboards.

Parameters:

  • device_ids (list, optional): Filter by specific Device IDs.
  • device_names (list, optional): Filter by device name (substring match, case-insensitive).
  • entity_ids (list, optional): Find devices that contain any of these entities.
  • entity_names (list, optional): Find devices containing entities matching these name substrings.
  • entity_states (list, optional): Only match devices if they contain entities in these states (e.g. unavailable, unknown).
  • match_every_entity_state (boolean, optional): If true, include device ONLY if ALL valid entities match filter_states. Useful for avoiding devices that are only partially unavailable (e.g. appliances in standby). Prioritisation: if the entities are in multiple states, the matched_state in the output will reflect the state that appears first in your filter_states list. Example: filter_states: ["unknown", "unavailable"] will report unknown for mixed devices. filter_states: ["unavailable", "unknown"] will report unavailable. If false, the first entity that matches any of filter_states will be used to determine the matched_state.
  • include_entities (boolean, optional): If true (default), the output includes a dictionary of all entities belonging to the device. Set to false to exclude this and reduce response size.
  • include_attributes (list, optional): Additional device attributes to include (e.g. sw_version, serial_number). If an attribute is missing on a device, it returns null.
  • include_disabled (boolean, optional): Include disabled devices and entities (default false).
  • disabled_by (list, optional): Filter by who disabled the device (e.g. user, !integration).
  • labels (list, optional): Filter by device labels (e.g. kitchen, !ephemeral).
  • integrations (list, optional): Filter by integration domain (e.g. tuya, !hacs).
  • entry_types (list, optional): Filter by entry type (e.g. service, !service).
  • device_models (list, optional): Filter by device model (substring match, e.g. !Chromecast, Hue).

Output Schema:

devices:
  - device_id: "..."
    name: "Kitchen Light"
    model: "LWB010"
    manufacturer: "Philips"
    area_name: "Kitchen"
    floor_name: "Ground Floor"
    matched_state: "unavailable"  # The state of the first entity that triggered the match
    matched_entity_states:        # All entities belonging to this device
      light.kitchen_1:
         state: "unavailable"
         name: "Kitchen Light 1"
         state: "unavailable"
         name: "Kitchen Light 1"
    disabled_by: null             # Standard field: null if enabled
    labels: ["my_label"]          # List of device labels
    entry_type: "service"         # "service" or null (physical)
    sw_version: "1.2.3"           # Optional attribute

2. ent2dev.get_entities

Returns a list of entities matching the criteria, enriched with device context.

Parameters:

  • entity_ids (list, optional): Filter by specific Entity IDs.
  • entity_names (list, optional): Filter by entity name (substring match).
  • device_ids (list, optional): Find entities belonging to these devices.
  • device_names (list, optional): Find entities belonging to devices matching these name substrings.
  • entity_states (list, optional): Filter by entity state.
  • include_attributes (list, optional): Additional entity state attributes to include (e.g. unit_of_measurement).
  • include_disabled (boolean, optional): Include disabled entities (default false).
  • disabled_by (list, optional): Filter by who disabled the entity (e.g. user, !integration).
  • labels (list, optional): Filter by device labels.
  • integrations (list, optional): Filter by device integration domain.

Output Schema:

entities:
  - entity_id: "sensor.kitchen_temp"
    name: "Kitchen Temperature"
    state: "21.5"
    unit_of_measurement: "Β°C"    # Optional attribute (flattened)
    disabled_by: null            # Standard field
    device:                      # Nested to disambiguate from entity attributes
      device_id: "..."
      name: "Kitchen Sensor"
      area_name: "Kitchen"
      floor_name: "Ground Floor"

WebSocket API (Frontend)

You can call the services directly from the frontend using the standard call_service command. This is preferred over the legacy map command as it supports filtering.

Example: getting offline devices in a custom card:

hass.callWS({
    type: "call_service",
    domain: "ent2dev",
    service: "get_devices",
    service_data: {
        entity_states: ["unavailable", "unknown"]
    },
    return_response: true
}).then((response) => {
    // response = { devices: [ ... ] }
    console.log("Offline Devices:", response.devices);
});

Uninstallation

  1. Remove the integration from Settings > Devices & Services.
  2. Delete the ent2dev folder from custom_components.
  3. Restart Home Assistant.

🧠 Rationale: Why use ent2dev?

The ent2dev integration effectively functions as a specialized "Database View" or "Stored Procedure" for Home Assistant's internal registries. While native tools can technically achieve similar results, ent2dev offers significant performance and usability advantages for specific "cross-domain" queries.

1. Functional Overlap

There is high functional overlap, but low architectural overlap. You can technically replicate almost everything ent2dev does using Jinja2 templates, but the implementation differs drastically in efficiency.

Use Case HA Native (Jinja2) ent2dev Service
Get Device Entities device_entities(device_id) get_entities(device_ids=[...])
Get Device Area area_name(device_attr(dev_id, 'area_id')) Included automatically (area_name)
Filter by State `states selectattr('state', 'eq', 'X')`
Get Offline Devices Complex Loop (Iterate States -> Get Entity -> Get Device -> Deduplicate) Single call: get_devices(entity_states=['unavailable'])

2. Performance Characteristics

The "Join" Problem

This is where ent2dev shines. Home Assistant's data model is hierarchical but disjointed:

  • Entities belong to Devices.
  • Devices belong to Areas.
  • Areas belong to Floors.

To get a flat list of "Offline Devices with their Floor Name" natively, you must traverse this hierarchy for every single item in a loop.

  • Native Jinja: O(N) Iteration + 3 lookups per item (Device->Area->Floor). In Jinja, this is computationally expensive and slow (states object can have thousands of items).
  • ent2dev:
    • Iterates the Entity Registry in compiled Python (much faster than Jinja interpretation).
    • Performs O(1) dictionary lookups for Device, Area, and Floor registries.
    • Returns a pre-joined, flattened object.

Filtering Efficiency

  • ent2dev.get_entities: If you provide device_ids, it uses er.async_entries_for_device (an internal O(1) index). It skips scanning the entire entity registry.
  • Native device_entities: Also uses internal indices, so performance here is comparable.
  • Native selectattr: Iterates the entire list. ent2dev also iterates candidates, but Python execution is significantly faster than Jinja template rendering for large sets.

3. Unique Advantages of ent2dev

  1. "Inverse" Querying (Entity State -> Device)

    • Native: There is no direct way to ask "Give me all devices that have an unavailable entity". You have to find the entities first, then map them to devices, then deduplicate the device list.
    • ent2dev: Handles the deduction and deduplication logic internally, returning a clean list of unique devices.
  2. Arbitrary List Querying & Batch Processing

    • Advantage: You can pass a list of 50 disparate device_ids (e.g., from a group or user selection) and get a rich return object for all of them in one go.
    • Native: You would need to write a complex Jinja loop to iterate your list, perform lookups for each, and manually construct a JSON-like string structure if you wanted to pass it to a frontend card.
  3. Structured Data Objects

    • Advantage: ent2dev returns true Python/JSON objects (Dictionaries/Lists).
    • Native: Jinja templates primarily output Strings. While modern HA supports native types in variables, passing complex nested structures (Device -> Area -> Floor) out of a template sensor usually requires serializing to JSON strings and re-parsing, which is brittle and slow.
  4. Implicit Context Resolution (Flattening)

    • It treats the Device -> Area -> Floor hierarchy as a single flat record. This allows you to say "Group by floor_name" immediately in your frontend card, whereas natively you'd have to perform those lookups inside the card's JavaScript or Jinja loop.

Summary

  • Use Native Templates if you are looking up simple info for a single known entity (e.g. is_state(...)) or iterating a small list.
  • Use ent2dev if you need to:
    • Query "up the tree" (find devices based on entity states).
    • Process large lists (100+ items).
    • Get joined/enriched data (Device+Area+Floor) for a collection of items.
    • Return complex data structures to the frontend without string parsing.

About

Home Assistant Integration to map Entities to Devices

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages