Explorar el Código

Add set_slot_enabled service and toggle button in card

New service calls node.access_control.set_user() with SetUserOptions(active=)
to enable or disable a slot without touching its PIN. The slot status icon
in the card is now a button that calls the service to toggle state.
ZiB hace 4 meses
padre
commit
150d93c49b

+ 10 - 6
custom_components/zlock_slots/frontend/zlock-slots-card.js

@@ -68,9 +68,9 @@ class ZLockSlotsCard extends HTMLElement {
         th { text-align: left; padding: 4px 8px; opacity: 0.6; font-weight: 500; }
         th { text-align: left; padding: 4px 8px; opacity: 0.6; font-weight: 500; }
         td { padding: 4px 8px; vertical-align: middle; }
         td { padding: 4px 8px; vertical-align: middle; }
         tr:not(:last-child) td { border-bottom: 1px solid var(--divider-color, #e0e0e0); }
         tr:not(:last-child) td { border-bottom: 1px solid var(--divider-color, #e0e0e0); }
-        .status-icon ha-icon { --mdc-icon-size: 20px; }
-        .status-icon.enabled ha-icon { color: var(--success-color, #4caf50); }
-        .status-icon.disabled ha-icon { color: var(--warning-color, #ff9800); }
+        .status-icon { --mdc-icon-size: 20px; }
+        .status-icon.enabled { color: var(--success-color, #4caf50); }
+        .status-icon.disabled { color: var(--warning-color, #ff9800); }
         .name-cell { display: flex; align-items: center; gap: 6px; }
         .name-cell { display: flex; align-items: center; gap: 6px; }
         .icon-btn { background: none; border: none; cursor: pointer; padding: 2px; opacity: 0.5; display: flex; align-items: center; }
         .icon-btn { background: none; border: none; cursor: pointer; padding: 2px; opacity: 0.5; display: flex; align-items: center; }
         .icon-btn:hover { opacity: 1; }
         .icon-btn:hover { opacity: 1; }
@@ -111,9 +111,9 @@ class ZLockSlotsCard extends HTMLElement {
                     <tr data-slot="${s.slot_num}">
                     <tr data-slot="${s.slot_num}">
                       <td>${s.slot_num}</td>
                       <td>${s.slot_num}</td>
                       <td>
                       <td>
-                        <span class="status-icon ${s.status}">
+                        <button class="icon-btn status-icon ${s.status}" data-action="toggle-enabled" data-slot="${s.slot_num}" data-enabled="${s.status === "enabled"}" title="${s.status === "enabled" ? "Disable slot" : "Enable slot"}">
                           <ha-icon icon="${s.status === "enabled" ? "mdi:account" : "mdi:account-off"}"></ha-icon>
                           <ha-icon icon="${s.status === "enabled" ? "mdi:account" : "mdi:account-off"}"></ha-icon>
-                        </span>
+                        </button>
                       </td>
                       </td>
                       <td>
                       <td>
                         <span class="name-cell" id="name-display-${s.slot_num}">
                         <span class="name-cell" id="name-display-${s.slot_num}">
@@ -174,7 +174,11 @@ class ZLockSlotsCard extends HTMLElement {
         const action = el.dataset.action;
         const action = el.dataset.action;
         const slot = parseInt(el.dataset.slot, 10);
         const slot = parseInt(el.dataset.slot, 10);
 
 
-        if (action === "clear") {
+        if (action === "toggle-enabled") {
+          const currentlyEnabled = el.dataset.enabled === "true";
+          await this._callService("set_slot_enabled", { lock_entity_id: lock, slot, enabled: !currentlyEnabled });
+
+        } else if (action === "clear") {
           if (!confirm(`Clear code slot ${slot}?`)) return;
           if (!confirm(`Clear code slot ${slot}?`)) return;
           await this._callService("clear_code", { lock_entity_id: lock, slot });
           await this._callService("clear_code", { lock_entity_id: lock, slot });
 
 

+ 22 - 1
custom_components/zlock_slots/services.py

@@ -16,6 +16,7 @@ _LOGGER = logging.getLogger(__name__)
 SERVICE_SET_CODE = "set_code"
 SERVICE_SET_CODE = "set_code"
 SERVICE_CLEAR_CODE = "clear_code"
 SERVICE_CLEAR_CODE = "clear_code"
 SERVICE_SET_NAME = "set_name"
 SERVICE_SET_NAME = "set_name"
+SERVICE_SET_SLOT_ENABLED = "set_slot_enabled"
 
 
 SET_CODE_SCHEMA = vol.Schema({
 SET_CODE_SCHEMA = vol.Schema({
   vol.Required("lock_entity_id"): cv.entity_id,
   vol.Required("lock_entity_id"): cv.entity_id,
@@ -35,6 +36,12 @@ SET_NAME_SCHEMA = vol.Schema({
   vol.Required("name"): cv.string,
   vol.Required("name"): cv.string,
 })
 })
 
 
+SET_SLOT_ENABLED_SCHEMA = vol.Schema({
+  vol.Required("lock_entity_id"): cv.entity_id,
+  vol.Required("slot"): vol.All(int, vol.Range(min=1)),
+  vol.Required("enabled"): cv.boolean,
+})
+
 
 
 def _get_coordinator(hass: HomeAssistant, lock_entity_id: str) -> ZLockCoordinator:
 def _get_coordinator(hass: HomeAssistant, lock_entity_id: str) -> ZLockCoordinator:
   for entry_data in hass.data.get(DOMAIN, {}).values():
   for entry_data in hass.data.get(DOMAIN, {}).values():
@@ -88,12 +95,26 @@ def register_services(hass: HomeAssistant) -> None:
     coordinator = _get_coordinator(hass, lock_entity_id)
     coordinator = _get_coordinator(hass, lock_entity_id)
     await coordinator.async_set_name(lock_entity_id, slot, name)
     await coordinator.async_set_name(lock_entity_id, slot, name)
 
 
+  async def handle_set_slot_enabled(call: ServiceCall) -> None:
+    from homeassistant.components.zwave_js.helpers import async_get_node_from_entity_id
+    from zwave_js_server.model.access_control import SetUserOptions
+
+    lock_entity_id = call.data["lock_entity_id"]
+    slot = call.data["slot"]
+    enabled = call.data["enabled"]
+
+    coordinator = _get_coordinator(hass, lock_entity_id)
+    node = async_get_node_from_entity_id(hass, lock_entity_id)
+    await node.access_control.set_user(slot, SetUserOptions(active=enabled))
+    await coordinator.async_refresh_lock(lock_entity_id)
+
   hass.services.async_register(DOMAIN, SERVICE_SET_CODE, handle_set_code, SET_CODE_SCHEMA)
   hass.services.async_register(DOMAIN, SERVICE_SET_CODE, handle_set_code, SET_CODE_SCHEMA)
   hass.services.async_register(DOMAIN, SERVICE_CLEAR_CODE, handle_clear_code, CLEAR_CODE_SCHEMA)
   hass.services.async_register(DOMAIN, SERVICE_CLEAR_CODE, handle_clear_code, CLEAR_CODE_SCHEMA)
   hass.services.async_register(DOMAIN, SERVICE_SET_NAME, handle_set_name, SET_NAME_SCHEMA)
   hass.services.async_register(DOMAIN, SERVICE_SET_NAME, handle_set_name, SET_NAME_SCHEMA)
+  hass.services.async_register(DOMAIN, SERVICE_SET_SLOT_ENABLED, handle_set_slot_enabled, SET_SLOT_ENABLED_SCHEMA)
 
 
 
 
 def unregister_services(hass: HomeAssistant) -> None:
 def unregister_services(hass: HomeAssistant) -> None:
-  for service in (SERVICE_SET_CODE, SERVICE_CLEAR_CODE, SERVICE_SET_NAME):
+  for service in (SERVICE_SET_CODE, SERVICE_CLEAR_CODE, SERVICE_SET_NAME, SERVICE_SET_SLOT_ENABLED):
     if hass.services.has_service(DOMAIN, service):
     if hass.services.has_service(DOMAIN, service):
       hass.services.async_remove(DOMAIN, service)
       hass.services.async_remove(DOMAIN, service)

+ 28 - 0
custom_components/zlock_slots/services.yaml

@@ -82,3 +82,31 @@ set_name:
       required: true
       required: true
       selector:
       selector:
         text:
         text:
+
+set_slot_enabled:
+  name: Set Slot Enabled
+  description: Enable or disable a lock code slot without changing its PIN.
+  fields:
+    lock_entity_id:
+      name: Lock entity
+      description: The lock entity the slot belongs to.
+      required: true
+      selector:
+        entity:
+          domain: lock
+          integration: zwave_js
+    slot:
+      name: Slot number
+      description: Code slot number to enable or disable.
+      required: true
+      selector:
+        number:
+          min: 1
+          max: 250
+          mode: box
+    enabled:
+      name: Enabled
+      description: True to enable the slot, false to disable it.
+      required: true
+      selector:
+        boolean: