Skip to content
Merged
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
76 changes: 42 additions & 34 deletions custom_components/opendisplay/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,15 @@
)


def _format_ble_protocol_label(protocol_type: str) -> str:
"""Return a user-facing label for a BLE protocol."""
if protocol_type == "open_display":
return "OpenDisplay (OD)"
if protocol_type == "atc":
return "OEPL / ATC"
return protocol_type


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

Expand All @@ -59,6 +68,34 @@ def __init__(self) -> None:
self._discovered_device: dict[str, Any] | None = {}
self._dhcp_discovery_info: DhcpServiceInfo | None = None

def _bluetooth_description_placeholders(
self,
error: str | None = None,
) -> dict[str, str]:
"""Build placeholders for the Bluetooth confirmation dialog."""
device = self._discovered_device
advertised_details = ""
if device["protocol_type"] == "atc":
battery = f"{device['battery_mv']/1000:.2f}V" if device["battery_mv"] > 0 else "Unknown"
fw_version = str(device["fw_version"]) if device["fw_version"] > 0 else "Unknown"
config_version = str(device["version"]) if device["version"] > 0 else "Unknown"
advertised_details = (
f"\n- Battery: {battery}"
f"\n- Firmware: {fw_version}"
f"\n- Config Version: {config_version}"
)

placeholders = {
"name": device["name"],
"device_type": device["protocol_display"],
"address": device["address"],
"rssi": str(device["rssi"]),
"advertised_details": advertised_details,
}
if error is not None:
placeholders["error"] = error
return placeholders

async def _validate_input(self, host: str) -> tuple[dict[str, str], str | None]:
"""Validate the user input allows us to connect.

Expand Down Expand Up @@ -226,6 +263,7 @@ async def async_step_bluetooth(
"fw_version": advertising_data.fw_version,
"version": advertising_data.version,
"protocol_type": protocol.protocol_name, # Store protocol type
"protocol_display": _format_ble_protocol_label(protocol.protocol_name),
}
_LOGGER.debug("Discovered device info: %s", self._discovered_device)

Expand Down Expand Up @@ -391,57 +429,27 @@ async def async_step_bluetooth_confirm(
return self.async_show_form(
step_id="bluetooth_confirm",
errors={"base": "invalid_device_config"},
description_placeholders={
"name": self._discovered_device["name"],
"address": self._discovered_device["address"],
"rssi": str(self._discovered_device["rssi"]),
"battery": f"{self._discovered_device['battery_mv']/1000:.2f}V" if self._discovered_device["battery_mv"] > 0 else "Unknown",
"fw_version": str(self._discovered_device["fw_version"]) if self._discovered_device["fw_version"] > 0 else "Unknown",
"config_version": str(self._discovered_device["version"]) if self._discovered_device["version"] > 0 else "Unknown",
},
description_placeholders=self._bluetooth_description_placeholders(),
)

except (BLEConnectionError, BLEProtocolError) as e:
_LOGGER.error("Error during device interrogation: %s", e)
return self.async_show_form(
step_id="bluetooth_confirm",
errors={"base": "interrogation_failed"},
description_placeholders={
"name": self._discovered_device["name"],
"address": self._discovered_device["address"],
"rssi": str(self._discovered_device["rssi"]),
"battery": f"{self._discovered_device['battery_mv']/1000:.2f}V" if self._discovered_device["battery_mv"] > 0 else "Unknown",
"fw_version": str(self._discovered_device["fw_version"]) if self._discovered_device["fw_version"] > 0 else "Unknown",
"config_version": str(self._discovered_device["version"]) if self._discovered_device["version"] > 0 else "Unknown",
"error": str(e),
},
description_placeholders=self._bluetooth_description_placeholders(str(e)),
)

except Exception as e:
_LOGGER.error("Unexpected error during device interrogation: %s", e)
return self.async_show_form(
step_id="bluetooth_confirm",
errors={"base": "interrogation_failed"},
description_placeholders={
"name": self._discovered_device["name"],
"address": self._discovered_device["address"],
"rssi": str(self._discovered_device["rssi"]),
"battery": f"{self._discovered_device['battery_mv']/1000:.2f}V" if self._discovered_device["battery_mv"] > 0 else "Unknown",
"fw_version": str(self._discovered_device["fw_version"]) if self._discovered_device["fw_version"] > 0 else "Unknown",
"config_version": str(self._discovered_device["version"]) if self._discovered_device["version"] > 0 else "Unknown",
"error": str(e),
},
description_placeholders=self._bluetooth_description_placeholders(str(e)),
)

# Build description placeholders from advertising data
description_placeholders = {
"name": self._discovered_device["name"],
"address": self._discovered_device["address"],
"rssi": str(self._discovered_device["rssi"]),
"battery": f"{self._discovered_device['battery_mv']/1000:.2f}V" if self._discovered_device["battery_mv"] > 0 else "Unknown",
"fw_version": str(self._discovered_device["fw_version"]) if self._discovered_device["fw_version"] > 0 else "Unknown",
"config_version": str(self._discovered_device["version"]) if self._discovered_device["version"] > 0 else "Unknown",
}
description_placeholders = self._bluetooth_description_placeholders()

return self.async_show_form(
step_id="bluetooth_confirm",
Expand Down
4 changes: 1 addition & 3 deletions custom_components/opendisplay/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import websockets
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
from homeassistant.core import HomeAssistant, CALLBACK_TYPE, callback
from homeassistant.core import HomeAssistant, CALLBACK_TYPE
from homeassistant.exceptions import ConfigEntryNotReady, HomeAssistantError
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.dispatcher import async_dispatcher_send
Expand Down Expand Up @@ -458,7 +458,6 @@ async def delayed_reconnect():
except Exception as err:
_LOGGER.exception("Error handling message: %s", err)

@callback
async def _handle_system_message(self, sys_data: dict) -> None:
"""Process a system message from the AP.

Expand Down Expand Up @@ -504,7 +503,6 @@ async def _handle_system_message(self, sys_data: dict) -> None:

async_dispatcher_send(self.hass, SIGNAL_AP_UPDATE)

@callback
async def _handle_tag_message(self, tag_data: dict) -> None:
"""Process a tag update message from the AP.

Expand Down
16 changes: 14 additions & 2 deletions custom_components/opendisplay/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,18 @@
from .runtime_data import OpenDisplayConfigEntry


def _format_tag_firmware_version(firmware_version) -> str:
"""Format AP tag firmware versions without raising on malformed data."""
if firmware_version in (None, ""):
return "Unknown"

firmware_version_str = str(firmware_version)
try:
return f"0x{int(firmware_version_str, 16):X}"
except ValueError:
return firmware_version_str


class OpenDisplayAPEntity(Entity):
"""
Base entity for AP-level entities (switch, select, text, AP sensors).
Expand Down Expand Up @@ -103,15 +115,15 @@ def device_info(self) -> DeviceInfo:
hw_type = tag_data.get("hw_type", 0)
hw_string = get_hw_string(hw_type)
width, height = get_hw_dimensions(hw_type)
firmware_version = str(tag_data.get("version", ""))
firmware_version = tag_data.get("version")

return DeviceInfo(
identifiers={(DOMAIN, self._tag_mac)},
name=tag_name,
manufacturer="OpenEPaperLink",
model=hw_string,
via_device=(DOMAIN, "ap"),
sw_version=f"0x{int(firmware_version, 16):X}" if firmware_version else "Unknown",
sw_version=_format_tag_firmware_version(firmware_version),
serial_number=self._tag_mac,
hw_version=f"{width}x{height}",
)
Expand Down
2 changes: 0 additions & 2 deletions custom_components/opendisplay/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,10 +236,8 @@ def _handle_tag_image_update(self, data) -> None:
if isinstance(data, bytes):
self._cached_image = data
self._last_updated = datetime.now()
self.async_write_ha_state()
elif data:
self.hass.async_create_task(self._refresh_image())
self.async_write_ha_state()
self.async_write_ha_state()


Expand Down
14 changes: 5 additions & 9 deletions custom_components/opendisplay/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,11 @@ async def get_entity_id_from_device_id(device_id: str) -> str:
translation_placeholders={"device_id": device_id},
)

domain_mac = next(iter(device.identifiers))
if domain_mac[0] != DOMAIN:
domain_mac = next(
(identifier for identifier in device.identifiers if identifier[0] == DOMAIN),
None,
)
if domain_mac is None:
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="device_not_opendisplay",
Expand Down Expand Up @@ -267,13 +270,6 @@ async def drawcustom_service(service: ServiceCall, entity_id: str) -> None:
translation_placeholders={"errors": errors_str},
)

if device_errors:
_LOGGER.warning(
"Completed with warnings for device %s:\n%s",
entity_id,
"\n".join(device_errors)
)

# Handle dry-run mode
if service.data.get("dry-run", False):
_LOGGER.info("Dry run completed for %s", entity_id)
Expand Down
4 changes: 2 additions & 2 deletions custom_components/opendisplay/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
"config": {
"step": {
"bluetooth_confirm": {
"title": "Set up BLE Tag",
"description": "Set up **{name}**?\n\n**Details:**\n- Address: {address}\n- Signal: {rssi} dBm\n- Battery: {battery}\n- Firmware: {fw_version}\n- Config Version: {config_version}"
"title": "Set up OpenDisplay Device",
"description": "Set up **{name}**?\n\n**Details:**\n- Device Type: {device_type}\n- Address: {address}\n- Signal: {rssi} dBm{advertised_details}"
},
"dhcp_confirm": {
"title": "Set up {hostname} ({ip})",
Expand Down
4 changes: 2 additions & 2 deletions custom_components/opendisplay/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
"config": {
"step": {
"bluetooth_confirm": {
"title": "Set up BLE Tag",
"description": "Set up **{name}**?\n\n**Details:**\n- Address: {address}\n- Signal: {rssi} dBm\n- Battery: {battery}\n- Firmware: {fw_version}\n- Config Version: {config_version}"
"title": "Set up OpenDisplay Device",
"description": "Set up **{name}**?\n\n**Details:**\n- Device Type: {device_type}\n- Address: {address}\n- Signal: {rssi} dBm{advertised_details}"
},
"dhcp_confirm": {
"title": "Set up {hostname} ({ip})",
Expand Down
Loading