Custom integration with devices and entities not showing up

Hi,
I’m new to the Home Assistant integration development but I’m working on a custom integration. Right now I’m kinda stuck with some things. The device I would like to integrate communicates with a Rest API and should make some lights accessible in HA.

My problem is that I cannot get the integration/device/entities to show up correctly in HA.

  • This is my integration page not showing any devices or entities:
    Screenshot 2024-07-31 155948

  • Eventhough there are some entities that are created and/or removed by the integration:
    Screenshot 2024-07-31 160110

  • Additionally the entities do have this property that should create a device connected to the entities as well.

@property
def device_info(self) -> DeviceInfo:
    ...

Can someone help me? What am I missing to have this devices getting created when using the integration?

You can find my integration code files attached:

__init__.py

“”“The Lunatone integration.”“”

from future import annotations

from dataclasses import dataclass
import logging

from lunatone_dali_api_client import Auth, Devices

from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_URL, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator

PLATFORMS: list[Platform] = [Platform.LIGHT]

_LOGGER = logging.getLogger(name)

@dataclass
class LunatoneDALIIoTData:
“”“Lunatone DALI IoT data class.”“”

coordinator: DataUpdateCoordinator
devices: Devices

type LunatoneDALIIoTConfigEntry = ConfigEntry[LunatoneDALIIoTData]

async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) → bool:
“”“Set up Lunatone from a config entry.”“”

auth = Auth(async_get_clientsession(hass), entry.data[CONF_URL])
devices = Devices(auth)

coordinator = DataUpdateCoordinator(
    hass,
    _LOGGER,
    name="Lunatone",
    update_method=devices.async_update,
)
await coordinator.async_config_entry_first_refresh()

entry.runtime_data = LunatoneDALIIoTData(coordinator, devices)

await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)

return True

async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) → bool:
“”“Unload a config entry.”“”
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)

config_flow.py

“”“Config flow for Lunatone.”“”

import logging
from typing import Any

import aiohttp
from lunatone_dali_api_client import APIClient, Auth
import voluptuous as vol

from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_URL
from homeassistant.helpers.aiohttp_client import async_get_clientsession

from .const import DOMAIN

_LOGGER = logging.getLogger(name)

class LunatoneDALIIoTConfigFlow(ConfigFlow, domain=DOMAIN):
“”“Lunatone DALI IoT config flow.”“”

VERSION = 0
MINOR_VERSION = 1

def __init__(self) -> None:
    """Initialize the config flow."""
    self.data: dict[str, Any] = {}

async def async_step_user(
    self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
    """Handle a flow initialized by the user."""
    errors: dict[str, str] = {}
    if user_input is not None:
        self._async_abort_entries_match({CONF_URL: user_input[CONF_URL]})
        auth = Auth(
            session=async_get_clientsession(self.hass),
            base_url=user_input[CONF_URL],
        )
        client = APIClient(auth)
        try:
            device_info = await client.async_get_info()
        except aiohttp.ClientConnectionError:
            _LOGGER.debug(
                (
                    "Failed to connect to device %s. Check the URL and if the "
                    "device is connected to power"
                ),
                user_input[CONF_URL],
            )
            errors["base"] = "cannot_connect"
        else:
            await self.async_set_unique_id(f"lunatone-{device_info.device.serial}")
            self._abort_if_unique_id_configured()
            return self.async_create_entry(
                title=f"DALI IoT Gateway {user_input[CONF_URL].split("//")[1]}",
                data={CONF_URL: user_input[CONF_URL]},
            )
    return self.async_show_form(
        step_id="user",
        data_schema=vol.Schema({vol.Required(CONF_URL): str}),
        errors=errors,
    )
light.py

“”“Platform for light integration.”“”

from future import annotations

from typing import Any

from lunatone_dali_api_client import Device
from lunatone_dali_api_client.models import ControlData

from homeassistant.components.light import ColorMode, LightEntity
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback

from . import LunatoneDALIIoTConfigEntry
from .const import DOMAIN

async def async_setup_entry(
hass: HomeAssistant,
config_entry: LunatoneDALIIoTConfigEntry,
async_add_entities: AddEntitiesCallback,
) → None:
“”“Set up the Lunatone Light platform.”“”
devices = config_entry.runtime_data.devices

# Add devices
async_add_entities(
    [LunatoneLight(device) for device in devices.devices], update_before_add=True
)

class LunatoneLight(LightEntity):
“”“Representation of a Lunatone Light.”“”

_attr_color_mode = ColorMode.ONOFF
_attr_supported_color_modes = {ColorMode.ONOFF}

def __init__(self, device: Device) -> None:
    """Initialize a LunatoneLight."""
    self._device = device
    self._state = None

@property
def name(self) -> str:
    """Return the display name of this light."""
    return self._device.data.name

@property
def is_on(self) -> bool | None:
    """Return true if light is on."""
    return self._state

@property
def device_info(self) -> DeviceInfo:
    """Return the device info."""
    return DeviceInfo(
        identifiers={
            (
                DOMAIN,
                f"lunatone-l{self._device.data.line}-a{self._device.data.address}",
            )
        },
        name=self.name,
        manufacturer="Lunatone",
    )

async def async_turn_on(self, **kwargs: Any) -> None:
    """Instruct the light to turn on."""
    await self._device.async_control(ControlData(switchable=True))

async def async_turn_off(self, **kwargs: Any) -> None:
    """Instruct the light to turn off."""
    await self._device.async_control(ControlData(switchable=False))

async def async_update(self) -> None:
    """Fetch new state data for this light.

    This is the only method that should fetch new data for Home Assistant.
    """
    await self._device.async_update()
    features = self._device.data.features
    self._state = features.switchable.status

Have you called your domain ‘light’ because you cant do that. Think of something unique.

Its also much easier to get help if people can see your full code in a readable format. Ie a link to github. If you post a link, i’ll have a proper look for you.

Thanks for trying to help!

No, my domain is not light but lunatone.

Here is the GitHub link:

Ok, so your code looks good. I think issues are

  1. You need to define unique id in your light.py for devices to show. I would not use the name as it has spaces and a hash (unless you slugify it). This is obviously unique for each entity.
  2. You do not specify your api requirement in manifest.json - may not be causing you and issue but should be there.
  3. I am unclear why these do not show under your integration and show integration light, but wondering if it is somehow data related. Few questions, do these entities actually work? Ie do they turn on/off. Are there any errors in your logs (run HA in debug or at least loader).

Let me know what happens with 1 and i will keep looking at 3.

EDIT: also just seen you are developing this on 2024.9dev0. Highly recommend not doing that as it can have issue on such an early dev release. Use 2024.7 as a known stable release.

Thanks for your reply!

Ok, that’s a good call. Is self._attr_unique_id="something" the best way to set the unique ID of an entity?

I’m pretty sure that this is not causing an issue, but I will add the requirements before I’ll do a pull request. Right now the python API library isn’t on PyPi yet. So I’m installing it locally with pip3 install -e ../my_lib_folder.

I made some random progress without changing any code. Maybe it could be related to a messed up config folder.


I’m working on a different PC than before and now the entities at least show as connected in the integration but not below the integration entries field.

They work like intended.

I didn’t notice any errors.
There is a log message that tells me that the loading of the data was successful.

[homeassistant.components.lunatone] Finished fetching Lunatone data in 0.241 seconds (success: True)

From time to time I get this warning from the asyncio module, but I’m not really sure what’s the issue there. And it seems to work fine even with the warning.

[asyncio] Executing <Task pending name='ssdp_info_desc_lookup_http://172.17.0.2:40000/device.xml' coro=<Scanner._ssdp_listener_process_callback_with_lookup() running at /workspaces/core/homeassistant/components/ssdp/__init__.py:499> wait_for=<Future pending cb=[BaseSelectorEventLoop._sock_write_done(27, handle=<Handle BaseS...events.py:317>)(), Task.task_wakeup()] created at /usr/local/lib/python3.12/asyncio/base_events.py:449> cb=[set.remove()] created at /workspaces/core/homeassistant/util/async_.py:37> took 0.180 seconds

I regularly rebase on the latest dev branch. I thought that’s how it’s supposed to be done because it is mentioned in the documentation (See Submit your work | Home Assistant Developer Docs).

@msp1974 Thank you very much!

Adding a unique ID to the entity solved the problem.

1 Like

Yes, before you submit, you should check it against the current dev version (when you know it works ok) but when you are developing it, I would use a known stable version so that you do not get caught up in any wierd issues. Just my view,

Glad you have it working!