Skip to content

Commit

Permalink
Config flow for entity association
Browse files Browse the repository at this point in the history
  • Loading branch information
andrew-codechimp committed May 5, 2024
1 parent 37614c4 commit 69e1f09
Show file tree
Hide file tree
Showing 25 changed files with 521 additions and 11 deletions.
3 changes: 3 additions & 0 deletions custom_components/battery_notes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
DATA,
DATA_LIBRARY_UPDATER,
CONF_SHOW_ALL_DEVICES,
CONF_SHOW_ALL_ENTITIES,
CONF_ENABLE_REPLACED,
CONF_DEFAULT_BATTERY_LOW_THRESHOLD,
CONF_BATTERY_INCREASE_THRESHOLD,
Expand Down Expand Up @@ -88,6 +89,7 @@
vol.Optional(CONF_ENABLE_AUTODISCOVERY, default=True): cv.boolean,
vol.Optional(CONF_USER_LIBRARY, default=""): cv.string,
vol.Optional(CONF_SHOW_ALL_DEVICES, default=False): cv.boolean,
vol.Optional(CONF_SHOW_ALL_ENTITIES, default=False): cv.boolean,
vol.Optional(CONF_ENABLE_REPLACED, default=True): cv.boolean,
vol.Optional(CONF_HIDE_BATTERY, default=False): cv.boolean,
vol.Optional(CONF_ROUND_BATTERY, default=False): cv.boolean,
Expand Down Expand Up @@ -130,6 +132,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
domain_config: ConfigType = config.get(DOMAIN) or {
CONF_ENABLE_AUTODISCOVERY: True,
CONF_SHOW_ALL_DEVICES: False,
CONF_SHOW_ALL_ENTITIES: False,
CONF_ENABLE_REPLACED: True,
CONF_HIDE_BATTERY: False,
CONF_ROUND_BATTERY: False,
Expand Down
4 changes: 2 additions & 2 deletions custom_components/battery_notes/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@
ATTR_BATTERY_LOW_THRESHOLD,
)

from .common import isfloat
from .common import validate_is_float

from .device import BatteryNotesDevice
from .coordinator import BatteryNotesCoordinator
Expand Down Expand Up @@ -536,7 +536,7 @@ def _handle_coordinator_update(self) -> None:
STATE_UNAVAILABLE,
STATE_UNKNOWN,
]
or not isfloat(wrapped_battery_state.state)
or not validate_is_float(wrapped_battery_state.state)
):
self._attr_is_on = None
self._attr_available = False
Expand Down
79 changes: 77 additions & 2 deletions custom_components/battery_notes/common.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,83 @@
"""Common functions for battery_notes."""

import re
from typing import NamedTuple

def isfloat(num):
"""Is the value a float."""
import homeassistant.helpers.device_registry as dr
import homeassistant.helpers.entity_registry as er
import voluptuous as vol
from homeassistant.const import CONF_ENTITY_ID, CONF_NAME, CONF_UNIQUE_ID
from homeassistant.core import HomeAssistant, split_entity_id

class SourceEntity(NamedTuple):
object_id: str
entity_id: str
domain: str
unique_id: str | None = None
name: str | None = None
entity_entry: er.RegistryEntry | None = None
device_entry: dr.DeviceEntry | None = None

async def create_source_entity(entity_id: str, hass: HomeAssistant) -> SourceEntity:
"""Create object containing all information about the source entity."""

source_entity_domain, source_object_id = split_entity_id(entity_id)

entity_registry = er.async_get(hass)
entity_entry = entity_registry.async_get(entity_id)

device_registry = dr.async_get(hass)
device_entry = (
device_registry.async_get(entity_entry.device_id)
if entity_entry and entity_entry.device_id
else None
)

unique_id = None

if entity_entry:
source_entity_domain = entity_entry.domain
unique_id = entity_entry.unique_id

return SourceEntity(
source_object_id,
entity_id,
source_entity_domain,
unique_id,
get_wrapped_entity_name(
hass,
entity_id,
source_object_id,
entity_entry,
device_entry,
),
entity_entry,
device_entry,
)


def get_wrapped_entity_name(
hass: HomeAssistant,
entity_id: str,
object_id: str,
entity_entry: er.RegistryEntry | None,
device_entry: dr.DeviceEntry | None,
) -> str:
"""Construct entity name based on the wrapped entity"""
if entity_entry:
if entity_entry.name is None and entity_entry.has_entity_name and device_entry:
return device_entry.name_by_user or device_entry.name or object_id

return entity_entry.name or entity_entry.original_name or object_id

entity_state = hass.states.get(entity_id)
if entity_state:
return str(entity_state.name)

return object_id

def validate_is_float(num):
"""Validate value is a float."""
if num:
try:
float(num)
Expand Down
110 changes: 108 additions & 2 deletions custom_components/battery_notes/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from homeassistant.const import (
CONF_NAME,
CONF_DEVICE_ID,
CONF_ENTITY_ID,
)

from .library import Library, ModelInfo
Expand All @@ -37,13 +38,22 @@
DATA_LIBRARY_UPDATER,
DOMAIN_CONFIG,
CONF_SHOW_ALL_DEVICES,
CONF_SHOW_ALL_ENTITIES,
CONF_BATTERY_LOW_TEMPLATE,
)

_LOGGER = logging.getLogger(__name__)

CONFIG_VERSION = 2

MENU_OPTION_DEVICE = "device"
MENU_OPTION_ENTITY = "entity"

ASSOCIATION_TYPE_MENU = {
MENU_OPTION_DEVICE: "Device (recommended)",
MENU_OPTION_ENTITY: "Entity (advanced)",
}

DEVICE_SCHEMA_ALL = vol.Schema(
{
vol.Required(CONF_DEVICE_ID): selector.DeviceSelector(
Expand Down Expand Up @@ -77,6 +87,31 @@
}
)

ENTITY_SCHEMA_ALL = vol.Schema(
{
vol.Required(CONF_ENTITY_ID): selector.EntitySelector(
config=selector.EntityFilterSelectorConfig()
),
vol.Optional(CONF_NAME): selector.TextSelector(
selector.TextSelectorConfig(type=selector.TextSelectorType.TEXT),
)
}
)

ENTITY_SCHEMA = vol.Schema(
{
vol.Required(CONF_ENTITY_ID): selector.EntitySelector(
selector.EntityFilterSelectorConfig(
domain=[Platform.SENSOR, Platform.BINARY_SENSOR],
device_class=SensorDeviceClass.BATTERY,
)
),
vol.Optional(CONF_NAME): selector.TextSelector(
selector.TextSelectorConfig(type=selector.TextSelectorType.TEXT),
)
}
)


class BatteryNotesFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
"""Config flow for BatteryNotes."""
Expand Down Expand Up @@ -108,13 +143,21 @@ async def async_step_integration_discovery(
"model": discovery_info[CONF_MODEL],
}

return await self.async_step_user(discovery_info)
return await self.async_step_menu_device(discovery_info)

async def async_step_user(
self,
user_input: dict | None = None,
) -> config_entries.FlowResult:
"""Handle a flow initialized by the user."""

return self.async_show_menu(step_id="user", menu_options=ASSOCIATION_TYPE_MENU)

async def async_step_device(
self,
user_input: dict | None = None,
) -> config_entries.FlowResult:
"""Handle a flow for a device or discovery."""
_errors = {}
if user_input is not None:
self.data = user_input
Expand Down Expand Up @@ -168,7 +211,70 @@ async def async_step_user(
schema = DEVICE_SCHEMA_ALL

return self.async_show_form(
step_id="user",
step_id="device",
data_schema=schema,
errors=_errors,
last_step=False,
)

async def async_step_entity(
self,
user_input: dict | None = None,
) -> config_entries.FlowResult:
"""Handle a flow for a device or discovery."""
_errors = {}
if user_input is not None:
self.data = user_input

# device_id = user_input[CONF_DEVICE_ID]

# if (
# DOMAIN in self.hass.data
# and DATA_LIBRARY_UPDATER in self.hass.data[DOMAIN]
# ):
# library_updater: LibraryUpdater = self.hass.data[DOMAIN][
# DATA_LIBRARY_UPDATER
# ]
# await library_updater.get_library_updates(dt_util.utcnow())

# device_registry = dr.async_get(self.hass)
# device_entry = device_registry.async_get(device_id)

# _LOGGER.debug(
# "Looking up device %s %s %s", device_entry.manufacturer, device_entry.model, device_entry.hw_version
# )

# model_info = ModelInfo(device_entry.manufacturer, device_entry.model, device_entry.hw_version)

# library = Library.factory(self.hass)

self.data[CONF_BATTERY_QUANTITY] = 1

# device_battery_details = await library.get_device_battery_details(
# model_info
# )

# if device_battery_details and not device_battery_details.is_manual:
# _LOGGER.debug(
# "Found device %s %s %s", device_entry.manufacturer, device_entry.model, device_entry.hw_version
# )
# self.data[CONF_BATTERY_TYPE] = device_battery_details.battery_type

# self.data[
# CONF_BATTERY_QUANTITY
# ] = device_battery_details.battery_quantity

return await self.async_step_battery()

schema = ENTITY_SCHEMA
# If show_all_entities = is specified and true, don't filter
if DOMAIN in self.hass.data and DOMAIN_CONFIG in self.hass.data[DOMAIN]:
domain_config: dict = self.hass.data[DOMAIN][DOMAIN_CONFIG]
if domain_config.get(CONF_SHOW_ALL_ENTITIES, False):
schema = ENTITY_SCHEMA_ALL

return self.async_show_form(
step_id="entity",
data_schema=schema,
errors=_errors,
last_step=False,
Expand Down
1 change: 1 addition & 0 deletions custom_components/battery_notes/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
CONF_DEVICE_NAME = "device_name"
CONF_LIBRARY_URL = "https://raw.githubusercontent.com/andrew-codechimp/HA-Battery-Notes/main/custom_components/battery_notes/data/library.json" # pylint: disable=line-too-long
CONF_SHOW_ALL_DEVICES = "show_all_devices"
CONF_SHOW_ALL_ENTITIES = "show_all_entities"
CONF_ENABLE_REPLACED = "enable_replaced"
CONF_DEFAULT_BATTERY_LOW_THRESHOLD = "default_battery_low_threshold"
CONF_BATTERY_INCREASE_THRESHOLD = "battery_increase_threshold"
Expand Down
6 changes: 3 additions & 3 deletions custom_components/battery_notes/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
)


from .common import isfloat
from .common import validate_is_float
from .store import BatteryNotesStorage

from .const import (
Expand Down Expand Up @@ -257,7 +257,7 @@ def battery_low(self) -> bool:
if self.battery_low_template:
return self.battery_low_template_state
else:
if isfloat(self.current_battery_level):
if validate_is_float(self.current_battery_level):
return bool(
float(self.current_battery_level) < self.battery_low_threshold
)
Expand All @@ -276,7 +276,7 @@ def rounded_previous_battery_level(self) -> float:

def _rounded_level(self, value) -> float:
"""Round the level, if preferred."""
if isfloat(value):
if validate_is_float(value):
return round(float(value), None if self._round_battery else 1)
else:
return value
Expand Down
4 changes: 2 additions & 2 deletions custom_components/battery_notes/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@
ATTR_DEVICE_NAME,
)

from .common import isfloat
from .common import validate_is_float
from .device import BatteryNotesDevice
from .coordinator import BatteryNotesCoordinator

Expand Down Expand Up @@ -329,7 +329,7 @@ async def async_state_changed_listener(
STATE_UNAVAILABLE,
STATE_UNKNOWN,
]
or not isfloat(wrapped_battery_state.state)
or not validate_is_float(wrapped_battery_state.state)
):
self._attr_native_value = None
self._attr_available = False
Expand Down
19 changes: 19 additions & 0 deletions custom_components/battery_notes/translations/ca.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,16 @@
"step": {
"user": {
"description": "Si necessiteu ajuda amb la configuració, mireu aquí: https://andrew-codechimp.github.io/HA-Battery-Notes/",
"data": {
"association_type": "Association type"
},
"menu_options": {
"device": "Device (recommended)",
"entity": "Entity (advanced)"
},
"title": "Choose your association type"
},
"device": {
"data": {
"device_id": "Dispositiu",
"name": "Nom"
Expand All @@ -11,6 +21,15 @@
"name": "Si ho deixes en blanc, agafarà el nom del dispositiu d'origen"
}
},
"entity": {
"data": {
"entity_id": "Entity",
"name": "Nom"
},
"data_description": {
"name": "Leaving blank will take the name from the source entity"
}
},
"battery": {
"data": {
"battery_type": "Tipus de bateria",
Expand Down
Loading

0 comments on commit 69e1f09

Please sign in to comment.