Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
533 changes: 489 additions & 44 deletions custom_components/opendisplay/__init__.py

Large diffs are not rendered by default.

60 changes: 60 additions & 0 deletions custom_components/opendisplay/binary_sensor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
"""Binary sensor platform for OpenDisplay diagnostic entities."""

from dataclasses import dataclass

from homeassistant.components.binary_sensor import (
BinarySensorEntity,
BinarySensorEntityDescription,
)
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback

from . import OpenDisplayConfigEntry
from .entity import OpenDisplayEntity

PARALLEL_UPDATES = 0


@dataclass(frozen=True, kw_only=True)
class OpenDisplayBinarySensorEntityDescription(BinarySensorEntityDescription):
"""Describes an OpenDisplay binary sensor entity."""


_PENDING_UPLOAD_DESCRIPTION = OpenDisplayBinarySensorEntityDescription(
key="pending_upload",
translation_key="pending_upload",
entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False,
)


async def async_setup_entry(
hass: HomeAssistant,
entry: OpenDisplayConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up OpenDisplay binary sensor entities."""
coordinator = entry.runtime_data.coordinator
async_add_entities(
[
OpenDisplayPendingUploadBinarySensorEntity(
coordinator,
_PENDING_UPLOAD_DESCRIPTION,
)
]
)


class OpenDisplayPendingUploadBinarySensorEntity(
OpenDisplayEntity[OpenDisplayBinarySensorEntityDescription],
BinarySensorEntity,
):
"""Binary sensor representing pending upload queue state."""

entity_description: OpenDisplayBinarySensorEntityDescription

@property
def is_on(self) -> bool:
"""Return True when there is a pending upload to be sent."""
return self.coordinator.pending_upload
88 changes: 82 additions & 6 deletions custom_components/opendisplay/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,62 @@
from homeassistant.components.bluetooth import (
BluetoothServiceInfoBleak,
async_ble_device_from_address,
async_clear_advertisement_history,
async_discovered_service_info,
)
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.config_entries import (
ConfigEntry,
ConfigFlow,
ConfigFlowResult,
OptionsFlowWithReload,
)
from homeassistant.const import CONF_ADDRESS
from homeassistant.core import callback

from .const import CONF_ENCRYPTION_KEY, DOMAIN
from .const import (
CONF_DEEP_SLEEP_TIMEOUT_MARGIN_MINUTES,
CONF_ENCRYPTION_KEY,
DOMAIN,
MAX_DEEP_SLEEP_TIMEOUT_MARGIN_MINUTES,
MIN_DEEP_SLEEP_TIMEOUT_MARGIN_MINUTES,
)
from .deep_sleep import deep_sleep_timeout_margin_minutes

_LOGGER = logging.getLogger(__name__)


_ENCRYPTION_KEY_VALIDATOR = vol.All(str.strip, str.lower, vol.Match(r"^[0-9a-f]{32}$"))
_DEEP_SLEEP_TIMEOUT_MARGIN_VALIDATOR = vol.All(
vol.Coerce(int),
vol.Range(
min=MIN_DEEP_SLEEP_TIMEOUT_MARGIN_MINUTES,
max=MAX_DEEP_SLEEP_TIMEOUT_MARGIN_MINUTES,
),
)


def _options_schema(config_entry: ConfigEntry) -> vol.Schema:
"""Return the options form schema."""
current_margin = deep_sleep_timeout_margin_minutes(config_entry.options)
return vol.Schema(
{
vol.Required(
CONF_DEEP_SLEEP_TIMEOUT_MARGIN_MINUTES,
default=current_margin,
): _DEEP_SLEEP_TIMEOUT_MARGIN_VALIDATOR
}
)


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

@staticmethod
@callback
def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlowWithReload:
"""Return the options flow."""
return OpenDisplayOptionsFlow()

def __init__(self) -> None:
"""Initialize the config flow."""
self._discovery_info: BluetoothServiceInfoBleak | None = None
Expand All @@ -46,10 +86,15 @@ async def _async_test_connection(
if ble_device is None:
raise BLEConnectionError(f"Could not find connectable device for {address}")

async with OpenDisplayDevice(
mac_address=address, ble_device=ble_device, encryption_key=encryption_key
) as device:
await device.read_firmware_version()
try:
async with OpenDisplayDevice(
mac_address=address,
ble_device=ble_device,
encryption_key=encryption_key,
) as device:
await device.read_firmware_version()
finally:
async_clear_advertisement_history(self.hass, address)

async def async_step_bluetooth(
self, discovery_info: BluetoothServiceInfoBleak
Expand Down Expand Up @@ -241,3 +286,34 @@ async def async_step_reauth_confirm(
description_placeholders={"name": reauth_entry.title},
errors=errors,
)


class OpenDisplayOptionsFlow(OptionsFlowWithReload):
"""Handle OpenDisplay options."""

async def async_step_init(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Manage OpenDisplay options."""
errors: dict[str, str] = {}
config_entry = self.config_entry

if user_input is not None:
try:
timeout_margin = _DEEP_SLEEP_TIMEOUT_MARGIN_VALIDATOR(
user_input[CONF_DEEP_SLEEP_TIMEOUT_MARGIN_MINUTES]
)
except vol.Invalid:
errors[CONF_DEEP_SLEEP_TIMEOUT_MARGIN_MINUTES] = (
"invalid_timeout_margin"
)
else:
options = dict(config_entry.options)
options[CONF_DEEP_SLEEP_TIMEOUT_MARGIN_MINUTES] = timeout_margin
return self.async_create_entry(title="", data=options)

return self.async_show_form(
step_id="init",
data_schema=_options_schema(config_entry),
errors=errors,
)
11 changes: 11 additions & 0 deletions custom_components/opendisplay/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,15 @@

DOMAIN = "opendisplay"
CONF_ENCRYPTION_KEY = "encryption_key"
CONF_CACHED_DEVICE_CONFIG = "cached_device_config"
CONF_CACHED_FIRMWARE = "cached_firmware"
CONF_CACHED_IS_FLEX = "cached_is_flex"
CONF_CACHED_LAST_SEEN = "cached_last_seen"
CONF_DEEP_SLEEP_TIMEOUT_MARGIN_MINUTES = "deep_sleep_timeout_margin_minutes"
SIGNAL_IMAGE_UPDATED = f"{DOMAIN}_image_updated"
SIGNAL_DEVICE_SEEN = f"{DOMAIN}_device_seen"
SIGNAL_PENDING_UPLOAD = f"{DOMAIN}_pending_upload"

DEFAULT_DEEP_SLEEP_TIMEOUT_MARGIN_MINUTES = 7
MIN_DEEP_SLEEP_TIMEOUT_MARGIN_MINUTES = 0
MAX_DEEP_SLEEP_TIMEOUT_MARGIN_MINUTES = 24 * 60
Loading
Loading