Help with my fist custom integration

Hi there.

I needed a integration with my water supply company in France as they have the data available through REST APIs. I’ve reversed engineered what they have on their website and create a small library for it.

I then embarqued into creating my own custom integration to:

  • have it configurable (i.e asking for credentials on setup)
  • create sensors called sensor.agur_water_index_<contract_id> which contains the consumption index of my water usage.

Now, I have followed the getting started guide, the cookiecutter template and various different threads on this forum to end up with something that I can install and configure. It can fetch the data and the sensors are created as expected.

However, the integration page doesn’t display the sensors, like any other integration would and I’m not understanding why. I’ve check the sun and other simple integrations but I cannot find what I’m doing wrong. The code is available on github here: GitHub - tbouron/ha-agur: Home Assistant integration for Agur https://ael.agur.fr

Do you have any pointers?

Screenshot of the sensor to show it is created and populated as expected (apologies, I can only send one picture per post since I’m new here, hence the reply)

After more digging, looks like I need a device to associated the sensor with. I have found another thread about the same issue although the author didn’t post the exact solution :sob:

Anyway, trying to work this out on my own, I have tried to set the device_info on my sensor using both _attr_device_info and overriding the device_info. Based on the docs, this should auto-magically create the device and link the sensor to it but the device is not created

class WaterSensor(CoordinatorEntity[AgurDataUpdateCoordinator], SensorEntity):
    """Agur water Sensor class."""
    _attr_name = "Water index"
    _attr_icon = SENSOR_WATER_ICON
    _attr_state_class = SENSOR_WATER_STATE_CLASS_TOTAL_INCREASING
    _attr_device_class = SENSOR_WATER_DEVICE_CLASS
    _attr_native_unit_of_measurement = SENSOR_WATER_UNIT

    def __init__(self, coordinator: AgurDataUpdateCoordinator, contract_id: str) -> None:
        """Pass coordinator to CoordinatorEntity."""
        super().__init__(coordinator=coordinator)
        self.entity_id = f"{SENSOR_PLATFORM}.{DOMAIN}_water_index_{contract_id}"
        self._contract_id = contract_id
        self._attr_extra_state_attributes = {
            "contract_id": contract_id
        }
        self._attr_device_info = DeviceInfo(
            identifiers={(DOMAIN, contract_id)},
            manufacturer=DEFAULT_NAME,
            name=DEFAULT_NAME
        )
        await self.async_update_ha_state(True)

    @property
    def native_value(self) -> float:
        """Return the state of the sensor."""
        return self.coordinator.data[self._contract_id]

    @property
    def device_info(self) -> DeviceInfo | None:
        return DeviceInfo(
            identifiers={(DOMAIN, self._contract_id)},
            manufacturer=DEFAULT_NAME,
            name=DEFAULT_NAME
        )

So instead I’ve tried to manually create the device – which works – before creating adding the sensor but it is still not attached to it.

async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback):
    """Add Agur sensors from a config_entry."""
    dr = device_registry.async_get(hass)
    coordinator = hass.data[DOMAIN][config_entry.entry_id]
    entities = []
    for contract_id in coordinator.contract_ids:
        _LOGGER.debug(f"Add sensor for Agur contract {contract_id}")
        entities.append(WaterSensor(coordinator=coordinator, contract_id=contract_id))
        dr.async_get_or_create(
            config_entry_id=config_entry.entry_id,
            identifiers={(DOMAIN, contract_id)},
            manufacturer=DEFAULT_NAME,
            name=DEFAULT_NAME
        )
    async_add_entities(entities, True)

I really don’t get what I’m doing wrong here. All the example I can find do the device_info thing and it just works :frowning:

After even more digging, I have found the magic incantation! The only thing required to attach the sensor to the integration (or domain rather) was to set self._attr_unique_id = unique_id where unique_id = config_entry.entry_id like so

async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback):
    """Add Agur sensors from a config_entry."""
    coordinator = hass.data[DOMAIN][config_entry.entry_id]
    entities = []
    for contract_id in coordinator.contract_ids:
        _LOGGER.debug(f"Add sensor for Agur contract {contract_id}")
        entities.append(WaterSensor(coordinator=coordinator, contract_id=contract_id, unique_id=config_entry.entry_id))
    async_add_entities(entities, True)


class WaterSensor(CoordinatorEntity[AgurDataUpdateCoordinator], SensorEntity):
    """Agur water Sensor class."""
    _attr_name = "Water index"
    _attr_icon = "mdi:water"
    _attr_state_class = SensorStateClass.TOTAL_INCREASING
    _attr_device_class = SensorDeviceClass.WATER
    _attr_native_unit_of_measurement = UnitOfVolume.LITERS

    def __init__(self, coordinator: AgurDataUpdateCoordinator, unique_id: str, contract_id: str) -> None:
        """Pass coordinator to CoordinatorEntity."""
        super().__init__(coordinator=coordinator)
        self.entity_id = f"{SENSOR_PLATFORM}.{DOMAIN}_water_index_{contract_id}"
        self._attr_unique_id = unique_id
        self._contract_id = contract_id
        self._attr_extra_state_attributes = {
            "contract_id": contract_id
        }
        self._attr_device_info = DeviceInfo(
            identifiers={(DOMAIN, contract_id)},
            manufacturer=DEFAULT_NAME,
            name=DEFAULT_NAME
        )

    @property
    def native_value(self) -> float:
        """Return the state of the sensor."""
        return self.coordinator.data[self._contract_id]

Setting to another value doesn’t work BTW. I have absolutely no idea why it needs specifically to be set to config_entry.entry_id though so if you happen to know why, I’m interested :slight_smile:

Apologies, have now updated my previous post with the solution.

Here is the direct link as well: GitHub - evercape/hass-resol-KM2: Custom Home Assistant integration to log sensor information from Resol devices using KM2 communication module.

Rgds
Martin

1 Like