Skip to content
Open
Show file tree
Hide file tree
Changes from 38 commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
65542a1
Kiosker
Claeysson Jun 19, 2025
1181ddf
Kiosker
Claeysson Jun 20, 2025
a2b457c
Kiosker
Claeysson Jun 29, 2025
ccd8fdf
Kiosker
Claeysson Jul 23, 2025
7f8ee6d
Kiosker
Claeysson Jul 23, 2025
a6752b2
Kiosker
Claeysson Jul 29, 2025
b188faa
Kiosker
Claeysson Aug 1, 2025
8f844a5
Kiosker
Claeysson Aug 1, 2025
1b04aba
Kiosker
Claeysson Aug 9, 2025
6a49e95
Kiosker
Claeysson Aug 18, 2025
9cf95a8
Remove .claude folder from tracking
Claeysson Aug 18, 2025
5730542
Updated claude.md
Claeysson Aug 18, 2025
80d7eab
bf rebase
Claeysson Oct 24, 2025
dc6428a
tests
Claeysson Oct 24, 2025
0f51804
Claude changes
Claeysson Oct 24, 2025
43c1e8b
Update
Claeysson Nov 4, 2025
5d70644
Adds the Kiosker integration
Claeysson Nov 17, 2025
b40654f
Reduced to sensor only
Claeysson Dec 4, 2025
5964bcb
Review changes
Claeysson Feb 25, 2026
c2de064
Review changes
Claeysson Feb 25, 2026
1f281f9
Enhance Kiosker integration with improved error handling and updated …
Claeysson Feb 26, 2026
0c793dd
Removed agent
Claeysson Mar 1, 2026
092f6c0
Update homeassistant/components/kiosker/config_flow.py
Claeysson Mar 1, 2026
fa0e4bf
Update homeassistant/components/kiosker/.gitignore
Claeysson Mar 1, 2026
de78917
Update homeassistant/components/kiosker/coordinator.py
Claeysson Mar 1, 2026
4e631cc
Update homeassistant/components/kiosker/entity.py
Claeysson Mar 1, 2026
20e4b35
Update homeassistant/components/kiosker/entity.py
Claeysson Mar 1, 2026
6efcecc
Updated tests for sensor
Claeysson Mar 1, 2026
b1f1619
Updated translations
Claeysson Mar 1, 2026
1ec92e3
Updated tests
Claeysson Mar 1, 2026
b9b0054
Copilot Review
Claeysson Mar 2, 2026
735344d
Copilot Review
Claeysson Mar 2, 2026
e776c5e
Remove .gitigonre in component
Claeysson Mar 3, 2026
dcc0589
Copilot Review
Claeysson Mar 3, 2026
184244b
joostlek review
Claeysson Mar 4, 2026
f63ecac
Remove binary sensor
Claeysson Mar 4, 2026
8aaa161
Remove binary sensor
Claeysson Mar 4, 2026
ee12318
Remove lastUpdate sensor
Claeysson Mar 4, 2026
f9652f9
Changed error handling in config_flow.py
Claeysson Mar 4, 2026
91aaced
Added none handling to hw_version
Claeysson Mar 4, 2026
13b2f74
Removed unused transations
Claeysson Mar 4, 2026
effcd11
Changed transaltions removed PARALLEL_UPDATES
Claeysson Mar 4, 2026
a83ea23
Updated quality_scale
Claeysson Mar 4, 2026
4330dfd
added error handling for entity ID
Claeysson Mar 4, 2026
8ad2978
Review changes
Claeysson Mar 4, 2026
cbe3c47
Copilot Review changes
Claeysson Mar 4, 2026
13abab9
Copilot Review changes
Claeysson Mar 4, 2026
2ac64ad
Copilot Review changes
Claeysson Mar 4, 2026
0279c90
Removed PORT in config
Claeysson Mar 5, 2026
3b34b09
Syntax error and redundant null-check
Claeysson Mar 5, 2026
8b912a5
Review Changes
Claeysson Mar 26, 2026
fae397a
Test review
Claeysson Apr 2, 2026
6bb8727
Test review
Claeysson Apr 7, 2026
acb62db
Update homeassistant/components/kiosker/entity.py
Claeysson Apr 10, 2026
1c89cfc
Update homeassistant/components/kiosker/entity.py
Claeysson Apr 10, 2026
4e89cf3
Update homeassistant/components/kiosker/sensor.py
Claeysson Apr 10, 2026
6bbd57a
Update homeassistant/components/kiosker/sensor.py
Claeysson Apr 10, 2026
fc1c38a
Update homeassistant/components/kiosker/strings.json
Claeysson Apr 10, 2026
1612e16
Update homeassistant/components/kiosker/icons.json
Claeysson Apr 10, 2026
daafdd6
Update tests/components/kiosker/test_config_flow.py
Claeysson Apr 10, 2026
f88ed56
Update tests/components/kiosker/test_config_flow.py
Claeysson Apr 10, 2026
a22def9
Review
Claeysson Apr 10, 2026
bc1e5fd
Removed model and manufacturer
Claeysson Apr 10, 2026
eefec9d
Merge branch 'dev' into kiosker
joostlek Apr 10, 2026
195a89a
Test snapshots
Claeysson Apr 10, 2026
498ece4
Changed ZeroConf title
Claeysson Apr 11, 2026
540bf40
Fixed failing test
Claeysson Apr 11, 2026
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

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

29 changes: 29 additions & 0 deletions homeassistant/components/kiosker/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
"""The Kiosker integration."""

from __future__ import annotations

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

from .coordinator import KioskerConfigEntry, KioskerDataUpdateCoordinator

_PLATFORMS: list[Platform] = [Platform.SENSOR]


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

coordinator = KioskerDataUpdateCoordinator(hass, entry)

await coordinator.async_config_entry_first_refresh()

entry.runtime_data = coordinator

await hass.config_entries.async_forward_entry_setups(entry, _PLATFORMS)

return True


async def async_unload_entry(hass: HomeAssistant, entry: KioskerConfigEntry) -> bool:
"""Unload a config entry."""
return await hass.config_entries.async_unload_platforms(entry, _PLATFORMS)
235 changes: 235 additions & 0 deletions homeassistant/components/kiosker/config_flow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
"""Config flow for the Kiosker integration."""

from __future__ import annotations

import logging
from typing import Any

from kiosker import (
AuthenticationError,
BadRequestError,
ConnectionError,
IPAuthenticationError,
KioskerAPI,
PingError,
TLSVerificationError,
)
import voluptuous as vol

from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_HOST, CONF_PORT, CONF_SSL, CONF_VERIFY_SSL
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.service_info.zeroconf import ZeroconfServiceInfo

from .const import CONF_API_TOKEN, DEFAULT_PORT, DEFAULT_SSL, DEFAULT_SSL_VERIFY, DOMAIN

_LOGGER = logging.getLogger(__name__)

STEP_USER_DATA_SCHEMA = vol.Schema(
{
vol.Required(CONF_HOST): str,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): int,
vol.Required(CONF_API_TOKEN): str,
vol.Optional(CONF_SSL, default=DEFAULT_SSL): bool,
vol.Optional(CONF_VERIFY_SSL, default=DEFAULT_SSL_VERIFY): bool,
}
)
STEP_ZEROCONF_CONFIRM_DATA_SCHEMA = vol.Schema(
{
vol.Required(CONF_API_TOKEN): str,
vol.Optional(CONF_VERIFY_SSL, default=DEFAULT_SSL_VERIFY): bool,
}
)


async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, Any]:
"""Validate the user input allows us to connect.

Data has the keys from STEP_USER_DATA_SCHEMA with values provided by the user.
Returns title and device_id for config entry setup.
"""
api = KioskerAPI(
host=data[CONF_HOST],
port=data[CONF_PORT],
token=data[CONF_API_TOKEN],
ssl=data[CONF_SSL],
verify=data[CONF_VERIFY_SSL],
)

try:
# Test connection by getting status
status = await hass.async_add_executor_job(api.status)
except ConnectionError as exc:
raise CannotConnect from exc
except (AuthenticationError, IPAuthenticationError) as exc:
raise InvalidAuth from exc
except TLSVerificationError as exc:
raise TLSError from exc
except BadRequestError as exc:
raise BadRequest from exc
except PingError as exc:
raise CannotConnect from exc
except Exception as exc:
_LOGGER.exception("Unexpected exception while connecting to Kiosker")
raise CannotConnect from exc

# Ensure we have a device_id from the status response
if not hasattr(status, "device_id") or not status.device_id:
_LOGGER.error("Device did not return a valid device_id")
raise CannotConnect
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why wouldn't we have one?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should not happened but the UUID from the status is optional because the underlaying "identifierForVendor" in the iOS framework is optional. Never seen this return null, and can't tell you when it might be null.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean more like, why do we use hasattr? I'd assume when it's null, that we return None, not that the whole field stops existing on the object

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed "hasattr" check.


device_id = status.device_id
# Use first 8 characters of device_id for consistency with entity naming
display_id = device_id[:8] if len(device_id) > 8 else device_id
return {"title": f"Kiosker {display_id}", "device_id": device_id}


class KioskerConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Kiosker."""

VERSION = 1
MINOR_VERSION = 1

def __init__(self) -> None:
"""Initialize the config flow."""

self._discovered_host: str | None = None
self._discovered_port: int | None = None
self._discovered_uuid: str | None = None
self._discovered_version: str | None = None
self._discovered_ssl: bool | None = None

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:
try:
info = await validate_input(self.hass, user_input)
except CannotConnect:
errors["base"] = "cannot_connect"
except InvalidAuth:
errors["base"] = "invalid_auth"
except TLSError:
errors["base"] = "tls_error"
except BadRequest:
errors["base"] = "bad_request"
except Exception:
_LOGGER.exception("Unexpected exception during validation")
errors["base"] = "unknown"
else:
# Use device ID as unique identifier
await self.async_set_unique_id(
info["device_id"], raise_on_progress=False
)
self._abort_if_unique_id_configured()

return self.async_create_entry(title=info["title"], data=user_input)

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

async def async_step_zeroconf(
self, discovery_info: ZeroconfServiceInfo
) -> ConfigFlowResult:
"""Handle zeroconf discovery."""
host = discovery_info.host
port = discovery_info.port or DEFAULT_PORT

# Extract device information from zeroconf properties
properties = discovery_info.properties
uuid = properties.get("uuid")
app_name = properties.get("app", "Kiosker")
version = properties.get("version", "")
ssl = properties.get("ssl", "false").lower() == "true"

# Use UUID from zeroconf
if uuid:
device_name = f"{app_name} ({uuid[:8].upper()})"
unique_id = uuid
else:
_LOGGER.debug("Zeroconf properties did not include a valid device_id")
return self.async_abort(reason="cannot_connect")

# Set unique ID and check for duplicates
await self.async_set_unique_id(unique_id)
self._abort_if_unique_id_configured()

# Store discovery info for confirmation step
self.context["title_placeholders"] = {
"name": device_name,
"host": host,
"port": str(port),
"ssl": ssl,
}

# Store discovered information for later use
self._discovered_host = host
self._discovered_port = port
self._discovered_uuid = uuid
self._discovered_version = version
self._discovered_ssl = ssl

# Show confirmation dialog
return await self.async_step_zeroconf_confirm()

async def async_step_zeroconf_confirm(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle zeroconf confirmation."""
errors: dict[str, str] = {}

if user_input is not None and CONF_API_TOKEN in user_input:
# Use stored discovery info and user-provided token
host = self._discovered_host
port = self._discovered_port
ssl = self._discovered_ssl

# Create config with discovered host/port and user-provided token
config_data = {
CONF_HOST: host,
CONF_PORT: port,
CONF_API_TOKEN: user_input[CONF_API_TOKEN],
CONF_SSL: ssl,
CONF_VERIFY_SSL: user_input.get(CONF_VERIFY_SSL, DEFAULT_SSL_VERIFY),
}

try:
info = await validate_input(self.hass, config_data)
except CannotConnect:
errors[CONF_API_TOKEN] = "cannot_connect"
except InvalidAuth:
errors[CONF_API_TOKEN] = "invalid_auth"
except TLSError:
errors["base"] = "tls_error"
except BadRequest:
errors["base"] = "bad_request"
else:
return self.async_create_entry(title=info["title"], data=config_data)

# Show form to get API token for discovered device
return self.async_show_form(
step_id="zeroconf_confirm",
data_schema=STEP_ZEROCONF_CONFIRM_DATA_SCHEMA,
description_placeholders=self.context["title_placeholders"],
errors=errors,
)


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


class InvalidAuth(HomeAssistantError):
"""Error to indicate there is invalid auth."""


class TLSError(HomeAssistantError):
"""Error to indicate TLS verification failed."""


class BadRequest(HomeAssistantError):
"""Error to indicate bad request."""
12 changes: 12 additions & 0 deletions homeassistant/components/kiosker/const.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
"""Constants for the Kiosker integration."""

DOMAIN = "kiosker"

# Configuration keys
CONF_API_TOKEN = "api_token"

# Default values
DEFAULT_PORT = 8081
POLL_INTERVAL = 15
DEFAULT_SSL = False
DEFAULT_SSL_VERIFY = False
99 changes: 99 additions & 0 deletions homeassistant/components/kiosker/coordinator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
"""DataUpdateCoordinator for Kiosker."""

from __future__ import annotations

from dataclasses import dataclass
from datetime import timedelta
import logging

from kiosker import (
AuthenticationError,
BadRequestError,
Blackout,
ConnectionError,
IPAuthenticationError,
KioskerAPI,
PingError,
ScreensaverState,
Status,
TLSVerificationError,
)

from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST, CONF_PORT, CONF_SSL, CONF_VERIFY_SSL
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed

from .const import CONF_API_TOKEN, DOMAIN, POLL_INTERVAL

_LOGGER = logging.getLogger(__name__)

type KioskerConfigEntry = ConfigEntry[KioskerDataUpdateCoordinator]


@dataclass
class KioskerData:
"""Data structure for Kiosker integration."""

status: Status
blackout: Blackout | None
screensaver: ScreensaverState | None


class KioskerDataUpdateCoordinator(DataUpdateCoordinator[KioskerData]):
"""Class to manage fetching data from the Kiosker API."""

def __init__(
self,
hass: HomeAssistant,
config_entry: KioskerConfigEntry,
) -> None:
"""Initialize."""
self.api = KioskerAPI(
host=config_entry.data[CONF_HOST],
port=config_entry.data[CONF_PORT],
token=config_entry.data[CONF_API_TOKEN],
ssl=config_entry.data.get(CONF_SSL, False),
verify=config_entry.data.get(CONF_VERIFY_SSL, False),
)
super().__init__(
hass,
_LOGGER,
name=DOMAIN,
update_interval=timedelta(seconds=POLL_INTERVAL),
config_entry=config_entry,
)

def _fetch_all_data(self) -> tuple[Status, Blackout, ScreensaverState]:
"""Fetch all data from the API in a single executor job."""
status = self.api.status()
blackout = self.api.blackout_get()
screensaver = self.api.screensaver_get_state()
return status, blackout, screensaver

async def _async_update_data(self) -> KioskerData:
"""Update data via library."""
try:
status, blackout, screensaver = await self.hass.async_add_executor_job(
self._fetch_all_data
)
except (AuthenticationError, IPAuthenticationError) as exc:
raise ConfigEntryAuthFailed("Authentication failed") from exc
except (ConnectionError, PingError) as exc:
raise UpdateFailed(f"Connection failed: {exc}") from exc
except TLSVerificationError as exc:
raise UpdateFailed(f"TLS verification failed: {exc}") from exc
except BadRequestError as exc:
raise UpdateFailed(f"Bad request: {exc}") from exc
except (OSError, TimeoutError) as exc:
raise UpdateFailed(f"Connection timeout: {exc}") from exc
except Exception as exc:
_LOGGER.debug("Unexpected error updating Kiosker data")
raise UpdateFailed(f"Unexpected error: {exc}") from exc

return KioskerData(
status=status,
blackout=blackout,
screensaver=screensaver,
)
Loading
Loading