From de8bd5b1ea5fa07043d5139f57a5a68da8ab07ff Mon Sep 17 00:00:00 2001 From: Andrew Grimberg Date: Wed, 23 Mar 2022 09:03:25 -0700 Subject: [PATCH] Feat: Link calendar and sensors to device Generate a virtual device and link the calendar and sensor entities to the device. At the same time, mark the entities as DIAGNOSTIC so that they don't get auto-added to the auto-dashboards Signed-off-by: Andrew Grimberg --- custom_components/rental_control/__init__.py | 47 +++++++++++++-- custom_components/rental_control/calendar.py | 29 ++++++--- custom_components/rental_control/sensor.py | 62 ++++++++++++-------- 3 files changed, 102 insertions(+), 36 deletions(-) diff --git a/custom_components/rental_control/__init__.py b/custom_components/rental_control/__init__.py index 760cf84..392b0e3 100644 --- a/custom_components/rental_control/__init__.py +++ b/custom_components/rental_control/__init__.py @@ -27,6 +27,7 @@ from homeassistant.const import CONF_VERIFY_SSL from homeassistant.core import HomeAssistant from homeassistant.helpers.aiohttp_client import async_get_clientsession +from homeassistant.helpers import device_registry as dr from homeassistant.util import dt from .const import CONF_CHECKIN @@ -44,6 +45,7 @@ from .const import DOMAIN from .const import PLATFORMS from .const import REQUEST_TIMEOUT +from .const import VERSION from .util import gen_uuid _LOGGER = logging.getLogger(__name__) @@ -69,7 +71,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: # hass.data[DOMAIN][entry.entry_id] = MyApi(...) if DOMAIN not in hass.data: hass.data[DOMAIN] = {} - hass.data[DOMAIN][entry.unique_id] = ICalEvents(hass=hass, config=config) + hass.data[DOMAIN][entry.unique_id] = ICalEvents( + hass=hass, config=config, unique_id=entry.unique_id + ) for component in PLATFORMS: hass.async_create_task( @@ -151,10 +155,11 @@ class ICalEvents: # pylint: disable=too-many-instance-attributes - def __init__(self, hass, config): + def __init__(self, hass, config, unique_id): """Set up a calendar object.""" self.hass = hass - self.name = config.get(CONF_NAME) + self._name = config.get(CONF_NAME) + self._unique_id = unique_id self.event_prefix = config.get(CONF_EVENT_PREFIX) self.url = config.get(CONF_URL) # Early versions did not have these variables, as such it may not be @@ -182,6 +187,40 @@ def __init__(self, hass, config): self.event = None self.all_day = False self.created = config.get(CONF_CREATION_DATETIME, str(dt.now())) + self._version = VERSION + + # setup device + device_registry = dr.async_get(hass) + device_registry.async_get_or_create( + config_entry_id=self.unique_id, + identifiers={(DOMAIN, self.unique_id)}, + name=self.name, + sw_version=self.version, + ) + + @property + def device_info(self): + """Return the device info block.""" + return { + "identifiers": {(DOMAIN, self.unique_id)}, + "name": self.name, + "sw_version": self.version, + } + + @property + def name(self): + """Return the name.""" + return self._name + + @property + def unique_id(self): + """Return the unique id.""" + return self._unique_id + + @property + def version(self): + """Return the version.""" + return self._version async def async_get_events( self, hass, start_date, end_date @@ -227,7 +266,7 @@ async def update(self): def update_config(self, config): """Update config entries.""" - self.name = config.get(CONF_NAME) + self._name = config.get(CONF_NAME) self.url = config.get(CONF_URL) # Early versions did not have these variables, as such it may not be # set, this should guard against issues until we're certain diff --git a/custom_components/rental_control/calendar.py b/custom_components/rental_control/calendar.py index f1371fb..29aea6f 100644 --- a/custom_components/rental_control/calendar.py +++ b/custom_components/rental_control/calendar.py @@ -4,12 +4,13 @@ from homeassistant.components.calendar import calculate_offset from homeassistant.components.calendar import CalendarEventDevice -from homeassistant.components.calendar import ENTITY_ID_FORMAT from homeassistant.components.calendar import is_offset_reached from homeassistant.const import CONF_NAME -from homeassistant.helpers.entity import generate_entity_id +from homeassistant.helpers.entity import EntityCategory from .const import DOMAIN +from .const import NAME +from .util import gen_uuid _LOGGER = logging.getLogger(__name__) OFFSET = "!!" @@ -22,11 +23,9 @@ async def async_setup_entry(hass, config_entry, async_add_entities): _LOGGER.debug("Conf: %s", config) name = config.get(CONF_NAME) - entity_id = generate_entity_id(ENTITY_ID_FORMAT, DOMAIN + " " + name, hass=hass) - rental_control_events = hass.data[DOMAIN][config_entry.unique_id] - calendar = ICalCalendarEventDevice(hass, name, entity_id, rental_control_events) + calendar = ICalCalendarEventDevice(hass, f"{NAME} {name}", rental_control_events) async_add_entities([calendar], True) @@ -35,14 +34,25 @@ class ICalCalendarEventDevice(CalendarEventDevice): """A device for getting the next Task from a WebDav Calendar.""" def __init__( - self, hass, name, entity_id, rental_control_events + self, hass, name, rental_control_events ): # pylint: disable=unused-argument """Create the iCal Calendar Event Device.""" - self.entity_id = entity_id + self._entity_category = EntityCategory.DIAGNOSTIC self._event = None self._name = name self._offset_reached = False self.rental_control_events = rental_control_events + self._unique_id = gen_uuid(f"{self.rental_control_events.unique_id} calendar") + + @property + def device_info(self): + """Return the device info block.""" + return self.rental_control_events.device_info + + @property + def entity_category(self): + """Return the category.""" + return self._entity_category @property def extra_state_attributes(self): @@ -59,6 +69,11 @@ def name(self): """Return the name of the entity.""" return self._name + @property + def unique_id(self): + """Return the unique_id.""" + return self._unique_id + async def async_get_events(self, hass, start_date, end_date): """Get all events in a specific time frame.""" _LOGGER.debug("Running ICalCalendarEventDevice async get events") diff --git a/custom_components/rental_control/sensor.py b/custom_components/rental_control/sensor.py index 2d72229..0cf74cd 100644 --- a/custom_components/rental_control/sensor.py +++ b/custom_components/rental_control/sensor.py @@ -7,11 +7,13 @@ from homeassistant.const import CONF_NAME from homeassistant.helpers.entity import Entity -from homeassistant.helpers.entity import generate_entity_id +from homeassistant.helpers.entity import EntityCategory from .const import CONF_MAX_EVENTS from .const import DOMAIN from .const import ICON +from .const import NAME +from .util import gen_uuid _LOGGER = logging.getLogger(__name__) @@ -41,7 +43,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ICalSensor( hass, rental_control_events, - DOMAIN + " " + name, + f"{NAME} {name}", eventnumber, ) ) @@ -65,18 +67,13 @@ def __init__(self, hass, rental_control_events, sensor_name, event_number): sensor_name is typically the name of the calendar. eventnumber indicates which upcoming event this is, starting at zero """ - self._event_number = event_number - self._hass = hass self.rental_control_events = rental_control_events - self._entity_id = generate_entity_id( - "sensor.{}", - f"{sensor_name} event {self._event_number}", - hass=self._hass, - ) if rental_control_events.event_prefix: summary = f"{rental_control_events.event_prefix} No reservation" else: summary = "No reservation" + self._code_generator = rental_control_events.code_generator + self._entity_category = EntityCategory.DIAGNOSTIC self._event_attributes = { "summary": summary, "description": None, @@ -87,9 +84,14 @@ def __init__(self, hass, rental_control_events, sensor_name, event_number): "slot_name": None, "slot_code": None, } - self._state = summary + self._event_number = event_number + self._hass = hass self._is_available = None - self._code_generator = rental_control_events.code_generator + self._name = f"{sensor_name} Event {self._event_number}" + self._state = summary + self._unique_id = gen_uuid( + f"{self.rental_control_events.unique_id} sensor {self._event_number}" + ) def _generate_door_code(self) -> str: """Generate a door code based upon the selected type.""" @@ -175,34 +177,44 @@ def _get_slot_name(self) -> str: return p.findall(summary)[0] @property - def entity_id(self): - """Return the entity_id of the sensor.""" - return self._entity_id + def available(self): + """Return True if ZoneMinder is available.""" + return self._event_attributes["start"] is not None + + @property + def device_info(self): + """Return the device info block.""" + return self.rental_control_events.device_info @property - def name(self): - """Return the name of the sensor.""" - return self._event_attributes["summary"] + def entity_category(self): + """Return the entity category.""" + return self._entity_category + + @property + def extra_state_attributes(self): + """Return the attributes of the event.""" + return self._event_attributes @property def icon(self): """Return the icon for the frontend.""" return ICON + @property + def name(self): + """Return the name of the sensor.""" + return self._name + @property def state(self): """Return the date of the next event.""" return self._state @property - def extra_state_attributes(self): - """Return the attributes of the event.""" - return self._event_attributes - - @property - def available(self): - """Return True if ZoneMinder is available.""" - return self._event_attributes["start"] is not None + def unique_id(self): + """Return the unique_id.""" + return self._unique_id async def async_update(self): """Update the sensor."""