Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Axion Lighting integration #125039

Open
wants to merge 18 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,8 @@ build.json @home-assistant/supervisor
/homeassistant/components/avea/ @pattyland
/homeassistant/components/awair/ @ahayworth @danielsjf
/tests/components/awair/ @ahayworth @danielsjf
/homeassistant/components/axion_dmx/ @Vrncanac
/tests/components/axion_dmx/ @Vrncanac
/homeassistant/components/axis/ @Kane610
/tests/components/axis/ @Kane610
/homeassistant/components/azure_data_explorer/ @kaareseras
Expand Down
54 changes: 54 additions & 0 deletions homeassistant/components/axion_dmx/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
"""The Axion Lighting integration."""

from __future__ import annotations

from dataclasses import dataclass

from libaxion_dmx import AxionDmxApi

from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant

from .const import CONF_CHANNEL, CONF_HOST, CONF_PASSWORD
from .coordinator import AxionDataUpdateCoordinator

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


@dataclass
class AxionData:
"""Represent a config for Axion Light."""

coordinator: AxionDataUpdateCoordinator
api: AxionDmxApi


type AxionConfigEntry = ConfigEntry[AxionData]


async def async_setup_entry(hass: HomeAssistant, entry: AxionConfigEntry) -> bool:
"""Set up Axion Lighting from a config entry."""

# Create API instance
api = AxionDmxApi(entry.data[CONF_HOST], entry.data[CONF_PASSWORD])

# Validate the API connection (and authentication)
if not await api.authenticate():
return False

# Create coordinator instance
coordinator = AxionDataUpdateCoordinator(hass, api, entry.data[CONF_CHANNEL])
await coordinator.async_config_entry_first_refresh()

# Store coordinator and API objects in runtime_data
entry.runtime_data = AxionData(coordinator=coordinator, api=api)

await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)

return True


async def async_unload_entry(hass: HomeAssistant, entry: AxionConfigEntry) -> bool:
"""Unload a config entry."""
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
82 changes: 82 additions & 0 deletions homeassistant/components/axion_dmx/config_flow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
"""Config flow for Axion Lighting integration."""

from __future__ import annotations

from typing import Any

from libaxion_dmx import AxionDmxApi
import voluptuous as vol

from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_HOST, CONF_PASSWORD
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import selector

from .const import _LOGGER, CONF_CHANNEL, CONF_LIGHT_TYPE, DOMAIN

STEP_USER_DATA_SCHEMA = vol.Schema(
{
vol.Required(CONF_HOST): str,
vol.Required(CONF_PASSWORD): str,
vol.Required(CONF_CHANNEL): vol.All(vol.Coerce(int), vol.Range(min=1)),
Vrncanac marked this conversation as resolved.
Show resolved Hide resolved
vol.Required(CONF_LIGHT_TYPE): selector.SelectSelector(
selector.SelectSelectorConfig(
options=[
selector.SelectOptionDict(value="White", label="White"),
selector.SelectOptionDict(
value="Tunable White", label="Tunable White"
),
selector.SelectOptionDict(value="RGB", label="RGB"),
selector.SelectOptionDict(value="RGBW", label="RGBW"),
selector.SelectOptionDict(value="RGBWW", label="RGBWW"),
Vrncanac marked this conversation as resolved.
Show resolved Hide resolved
],
translation_key="light_type",
multiple=False,
mode=selector.SelectSelectorMode.DROPDOWN,
)
),
}
)


class AxionConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Axion Lighting."""

VERSION = 1

async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle the initial step."""
errors: dict[str, str] = {}
if user_input is not None:
api = AxionDmxApi(user_input[CONF_HOST], user_input[CONF_PASSWORD])
# Validate the user input and authenticate
try:
if not await api.authenticate():
raise InvalidAuth # noqa: TRY301
except CannotConnect:
errors["base"] = "cannot_connect"
except InvalidAuth:
errors["base"] = "invalid_auth"
Vrncanac marked this conversation as resolved.
Show resolved Hide resolved
except Exception as e: # noqa: BLE001
_LOGGER.exception("Unexpected exception: %s", e)
errors["base"] = "unknown"
else:
# Return the validated entry data
return self.async_create_entry(
title=f"Axion DMX Light - Channel {user_input[CONF_CHANNEL]}",
data=user_input,
)

return self.async_show_form(
step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors
)


class CannotConnect(HomeAssistantError):
"""Error to indicate we cannot connect."""


class InvalidAuth(HomeAssistantError):
"""Error to indicate there is invalid auth."""
14 changes: 14 additions & 0 deletions homeassistant/components/axion_dmx/const.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
"""Constants for the Axion Lighting integration."""

import logging

_LOGGER = logging.getLogger(__name__)

DOMAIN = "axion_dmx"
CONF_CHANNEL = "channel"
CONF_LIGHT_TYPE = "light_type"
CONF_HOST = "host"
CONF_PASSWORD = "password"

AXION_MANUFACTURER_NAME = "Axion"
AXION_MODEL_NAME = "Axion DMX Controller"
37 changes: 37 additions & 0 deletions homeassistant/components/axion_dmx/coordinator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"""Coordinator for Axion Lighting integration."""

from datetime import timedelta
from typing import Any

from libaxion_dmx import AxionDmxApi
from requests.exceptions import RequestException

from homeassistant.core import HomeAssistant
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed

from .const import _LOGGER


class AxionDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
"""Custom coordinator for Axion Lighting integration."""

def __init__(self, hass: HomeAssistant, api: AxionDmxApi, channel: int) -> None:
"""Initialize the Axion data coordinator."""
self.api = api
self.channel = channel
super().__init__(
hass,
_LOGGER,
name="Axion Light",
update_interval=timedelta(seconds=5),
)

async def _async_update_data(self) -> dict[str, Any]:
"""Fetch data from the API endpoint."""
try:
# Return data in dictionary form
return {
Vrncanac marked this conversation as resolved.
Show resolved Hide resolved
"updated_value": await self.api.get_level(self.channel),
}
except RequestException as err:
raise UpdateFailed("Error communicating with API") from err
Loading