Skip to content

Commit

Permalink
Add Switch Entities
Browse files Browse the repository at this point in the history
  • Loading branch information
Snuffy2 committed Nov 16, 2024
1 parent 4ffd828 commit ee2e81a
Show file tree
Hide file tree
Showing 7 changed files with 378 additions and 133 deletions.
8 changes: 7 additions & 1 deletion custom_components/keymaster/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,13 @@
DOMAIN = "keymaster"
VERSION = "v0.0.0" # this will be automatically updated as part of the release workflow
ISSUE_URL = "https://github.com/FutureTense/keymaster"
PLATFORMS = [Platform.BINARY_SENSOR, Platform.SENSOR, Platform.TEXT, Platform.NUMBER]
PLATFORMS: list = [
Platform.BINARY_SENSOR,
Platform.SENSOR,
Platform.TEXT,
Platform.NUMBER,
Platform.SWITCH,
]
# INTEGRATION = "zwave_integration"

# hass.data attributes
Expand Down
22 changes: 11 additions & 11 deletions custom_components/keymaster/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ async def add_lock(self, kmlock: KeymasterLock) -> bool:
self.kmlocks[kmlock.keymaster_config_entry_id] = kmlock
await self._rebuild_lock_relationships()
await self._update_listeners(kmlock)
await self._async_update_data()
await self.async_refresh()
return True

async def update_lock(self, kmlock: KeymasterLock) -> bool:
Expand All @@ -178,7 +178,7 @@ async def update_lock(self, kmlock: KeymasterLock) -> bool:
self.kmlocks.update({kmlock.keymaster_config_entry_id: kmlock})
await self._rebuild_lock_relationships()
await self._update_listeners(self.kmlocks[kmlock.keymaster_config_entry_id])
await self._async_update_data()
await self.async_refresh()
return True

async def update_lock_by_config_entry_id(
Expand All @@ -191,7 +191,7 @@ async def update_lock_by_config_entry_id(
setattr(self.kmlocks[config_entry_id], attr, value)
await self._rebuild_lock_relationships()
await self._update_listeners(self.kmlocks[config_entry_id])
await self._async_update_data()
await self.async_refresh()
return True

async def delete_lock(self, kmlock: KeymasterLock) -> bool:
Expand All @@ -202,7 +202,7 @@ async def delete_lock(self, kmlock: KeymasterLock) -> bool:
)
self.kmlocks.pop(kmlock.keymaster_config_entry_id, None)
await self._rebuild_lock_relationships()
await self._async_update_data()
await self.async_refresh()
return True

async def delete_lock_by_config_entry_id(self, config_entry_id: str) -> bool:
Expand All @@ -211,7 +211,7 @@ async def delete_lock_by_config_entry_id(self, config_entry_id: str) -> bool:
await self._unsubscribe_listeners(self.kmlocks[config_entry_id])
self.kmlocks.pop(config_entry_id, None)
await self._rebuild_lock_relationships()
await self._async_update_data()
await self.async_refresh()
return True

async def get_lock_by_name(self, lock_name: str) -> KeymasterLock | None:
Expand Down Expand Up @@ -289,7 +289,7 @@ async def set_pin_on_lock(
self.hass, ZWAVE_JS_DOMAIN, SERVICE_SET_LOCK_USERCODE, servicedata
)
if update_after:
await self._async_update_data()
await self.async_refresh()
return True

else:
Expand Down Expand Up @@ -342,7 +342,7 @@ async def clear_pin_from_lock(
self.hass, ZWAVE_JS_DOMAIN, SERVICE_CLEAR_LOCK_USERCODE, servicedata
)
if update_after:
await self._async_update_data()
await self.async_refresh()
return True

else:
Expand All @@ -353,8 +353,8 @@ async def _is_slot_active(self, slot: KeymasterCodeSlot) -> bool:
if not isinstance(slot, KeymasterCodeSlot) or not slot.enabled:
return False

if not slot.accesslimit:
return True
# if not slot.accesslimit:
# return True

# TODO: Build the rest of the access limit logic
return True
Expand Down Expand Up @@ -551,8 +551,8 @@ async def _async_update_data(self) -> Mapping[str, Any]:
child_kmlock.code_slots[
num
].accesslimit_day_of_week[dow_num],
attr,
getattr(dow_slot, attr),
dow_attr,
getattr(dow_slot, dow_attr),
)

_LOGGER.debug(
Expand Down
7 changes: 5 additions & 2 deletions custom_components/keymaster/lock.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
class KeymasterCodeSlotDayOfWeek:
day_of_week_num: int
day_of_week_name: str
enabled: bool = False
dow_enabled: bool = False
include_exclude: bool = True
time_start: time | None = None
time_end: time | None = None
Expand All @@ -25,7 +25,7 @@ class KeymasterCodeSlot:
pin: str | None = None
active: bool = True
override_parent: bool = False
accesslimit: bool = False
notifications: bool = False
accesslimit_count_enabled: bool = False
accesslimit_count: int | None = None
accesslimit_date_range_enabled: bool = False
Expand All @@ -52,9 +52,12 @@ class KeymasterLock:
number_of_code_slots: int | None = None
starting_code_slot: int = 1
code_slots: Mapping[int, KeymasterCodeSlot] | None = None
lock_notifications: bool = False
door_notifications: bool = False
autolock_enabled: bool = False
autolock_min_day: int | None = None
autolock_min_night: int | None = None
retry_lock: bool = False
parent_name: str | None = None
parent_config_entry_id: str | None = None
child_config_entry_ids: list = field(default_factory=list)
Expand Down
24 changes: 21 additions & 3 deletions custom_components/keymaster/number.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ async def async_setup_entry(
KeymasterNumber(
entity_description=KeymasterNumberEntityDescription(
key=f"number.code_slots:{x}.accesslimit_count",
name=f"Unlock Events {x}",
name=f"Code Slot {x}: Uses Remaining",
mode=NumberMode.BOX,
native_min_value=0,
native_max_value=100,
Expand Down Expand Up @@ -115,6 +115,15 @@ def _handle_coordinator_update(self) -> None:
self.async_write_ha_state()
return

if (
"code_slots" in self._property
and self._kmlock.parent_name is not None
and not self._kmlock.code_slots[self._code_slot].override_parent
):
self._attr_available = False
self.async_write_ha_state()
return

if "code_slots" in self._property and (
self._code_slot not in self._kmlock.code_slots
or not self._kmlock.code_slots[self._code_slot].enabled
Expand All @@ -123,11 +132,19 @@ def _handle_coordinator_update(self) -> None:
self.async_write_ha_state()
return

if (
self._property.endswith(".accesslimit_count")
and not self._kmlock.code_slots[self._code_slot].accesslimit_count_enabled
):
self._attr_available = False
self.async_write_ha_state()
return

self._attr_available = True
self._attr_native_value = self._get_property_value()
self.async_write_ha_state()

async def async_set_value(self, value: str) -> None:
async def async_set_native_value(self, value: float) -> None:
_LOGGER.debug(
f"[Number async_set_value] {self.name}: config_entry_id: {self._config_entry.entry_id}, value: {value}"
)
Expand All @@ -138,8 +155,9 @@ async def async_set_value(self, value: str) -> None:
and not self._kmlock.code_slots[self._code_slot].override_parent
):
_LOGGER.debug(
f"[async_set_value] {self._kmlock.lock_name}: Child lock and code slot {self._code_slot} not set to override parent. Ignoring change"
f"[Number async_set_value] {self._kmlock.lock_name}: Child lock and code slot {self._code_slot} not set to override parent. Ignoring change"
)
return
if self._set_property_value(value):
self._attr_native_value = value
await self.coordinator.async_refresh()
160 changes: 78 additions & 82 deletions custom_components/keymaster/sensor.py
Original file line number Diff line number Diff line change
@@ -1,110 +1,107 @@
"""Sensor for keymaster."""

from dataclasses import dataclass
from functools import partial
import logging

from homeassistant.components.sensor import SensorEntity, SensorEntityDescription
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import entity_platform
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_registry import (
EntityRegistry,
async_get as async_get_entity_registry,
)
from homeassistant.util import slugify

from .const import (
ATTR_CODE_SLOT,
CONF_LOCK_NAME,
CONF_SLOTS,
CONF_START,
COORDINATOR,
DOMAIN,
)

from .const import COORDINATOR, DOMAIN
from .entity import KeymasterEntity, KeymasterEntityDescription
from .lock import KeymasterLock

_LOGGER: logging.Logger = logging.getLogger(__name__)


async def async_setup_entry(
hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities
):
"""Setup config entry."""
# Add entities for all defined slots
# TODO: Remove these? Doesn't need to be sensor, already in text.

coordinator = hass.data[DOMAIN][COORDINATOR]
async_add_entities(
[
kmlock: KeymasterLock = await coordinator.get_lock_by_config_entry_id(
config_entry.entry_id
)
entities: list = []

entities.append(
KeymasterSensor(
entity_description=KeymasterSensorEntityDescription(
key="sensor.lock_name",
name="Lock Name",
entity_registry_enabled_default=True,
hass=hass,
config_entry=config_entry,
coordinator=coordinator,
),
)
)

if hasattr(kmlock, "parent_name") and kmlock.parent_name is not None:
entities.append(
KeymasterSensor(
entity_description=KeymasterSensorEntityDescription(
key=f"sensor.code_slots:{x}.pin",
name=f"Code Slot {x}",
icon="mdi:lock-smart",
key="sensor.parent_name",
name="Parent Lock",
entity_registry_enabled_default=True,
hass=hass,
config_entry=config_entry,
coordinator=coordinator,
)
),
)
for x in range(
config_entry.data[CONF_START],
config_entry.data[CONF_START] + config_entry.data[CONF_SLOTS],
)
],
True,
)

async def code_slots_changed(
ent_reg: EntityRegistry,
platform: entity_platform.EntityPlatform,
config_entry: ConfigEntry,
old_slots: list[int],
new_slots: list[int],
):
"""Handle code slots changed."""
# TODO: Update/Confirm this works
slots_to_add = list(set(new_slots) - set(old_slots))
slots_to_remove = list(set(old_slots) - set(new_slots))
for slot in slots_to_remove:
sensor_name = slugify(
f"{config_entry.data[CONF_LOCK_NAME]}_code_slot_{slot}"
)
entity_id = f"sensor.{sensor_name}"
if ent_reg.async_get(entity_id):
await platform.async_remove_entity(entity_id)
ent_reg.async_remove(entity_id)
coordinator = hass.data[DOMAIN][COORDINATOR]

async_add_entities(
[
KeymasterSensor(
entity_description=KeymasterSensorEntityDescription(
key=f"sensor.code_slots:{x}.pin",
name=f"Code Slot {x}",
icon="mdi:lock-smart",
entity_registry_enabled_default=True,
hass=hass,
config_entry=config_entry,
coordinator=coordinator,
)
)
for x in slots_to_add
],
True,
)

async_dispatcher_connect(
hass,
f"{DOMAIN}_{config_entry.entry_id}_code_slots_changed",
partial(
code_slots_changed,
async_get_entity_registry(hass),
entity_platform.current_platform.get(),
config_entry,
),
)
async_add_entities(entities, True)

# async def code_slots_changed(
# ent_reg: EntityRegistry,
# platform: entity_platform.EntityPlatform,
# config_entry: ConfigEntry,
# old_slots: list[int],
# new_slots: list[int],
# ):
# """Handle code slots changed."""
# # TODO: Update/Confirm this works
# slots_to_add = list(set(new_slots) - set(old_slots))
# slots_to_remove = list(set(old_slots) - set(new_slots))
# for slot in slots_to_remove:
# sensor_name = slugify(
# f"{config_entry.data[CONF_LOCK_NAME]}_code_slot_{slot}"
# )
# entity_id = f"sensor.{sensor_name}"
# if ent_reg.async_get(entity_id):
# await platform.async_remove_entity(entity_id)
# ent_reg.async_remove(entity_id)
# coordinator = hass.data[DOMAIN][COORDINATOR]

# async_add_entities(
# [
# KeymasterSensor(
# entity_description=KeymasterSensorEntityDescription(
# key=f"sensor.code_slots:{x}.pin",
# name=f"Code Slot {x}",
# icon="mdi:lock-smart",
# entity_registry_enabled_default=True,
# hass=hass,
# config_entry=config_entry,
# coordinator=coordinator,
# )
# )
# for x in slots_to_add
# ],
# True,
# )

# async_dispatcher_connect(
# hass,
# f"{DOMAIN}_{config_entry.entry_id}_code_slots_changed",
# partial(
# code_slots_changed,
# async_get_entity_registry(hass),
# entity_platform.current_platform.get(),
# config_entry,
# ),
# )

return True

Expand All @@ -126,7 +123,6 @@ def __init__(
super().__init__(
entity_description=entity_description,
)
self._attr_extra_state_attributes = {ATTR_CODE_SLOT: self._code_slot}
self._attr_native_value = None

@callback
Expand Down
Loading

0 comments on commit ee2e81a

Please sign in to comment.