Parcourir la source

Add Guest Entered button for per-entry automations

Fires every time the door opens, checked in or not, so automations
can trigger on every building entry instead of only at check-in. The
button's own last-pressed state is the automation trigger; the first
press of a stay also sets Arrived if it isn't set yet.
ZiB il y a 1 mois
Parent
commit
fcc53c4dd6

+ 29 - 0
custom_components/room_cal_ha/button.py

@@ -29,6 +29,7 @@ async def async_setup_entry(
     OverrideEmptyButton(coordinator, entry, device_name),
     OverrideEmptyButton(coordinator, entry, device_name),
     GuestArrivedButton(coordinator, entry, device_name),
     GuestArrivedButton(coordinator, entry, device_name),
     GuestDepartedButton(coordinator, entry, device_name),
     GuestDepartedButton(coordinator, entry, device_name),
+    GuestEnteredButton(coordinator, entry, device_name),
   ])
   ])
 
 
 
 
@@ -160,3 +161,31 @@ class GuestDepartedButton(ButtonEntity):
 
 
   async def async_press(self) -> None:
   async def async_press(self) -> None:
     await self._coordinator.async_set_departed(True)
     await self._coordinator.async_set_departed(True)
+
+
+class GuestEnteredButton(ButtonEntity):
+  """Pressed every time the door is opened, checked in or not. The button's own
+  state (last-pressed timestamp) is what automations for "on every building
+  entry" should trigger on. The first press for a stay also flips Arrived, if
+  it isn't already set — subsequent presses leave Arrived untouched."""
+
+  _attr_has_entity_name = True
+  _attr_translation_key = "guest_entered"
+  _attr_icon = "mdi:door-open"
+
+  def __init__(
+    self,
+    coordinator: RoomCalCoordinator,
+    entry: ConfigEntry,
+    device_name: str,
+  ) -> None:
+    self._coordinator = coordinator
+    self._attr_unique_id = f"{entry.entry_id}_guest_entered"
+    self._attr_device_info = DeviceInfo(
+      identifiers={(DOMAIN, f"{entry.entry_id}_room_state")},
+      name=device_name,
+    )
+
+  async def async_press(self) -> None:
+    if not self._coordinator.data.active_arrived:
+      await self._coordinator.async_set_arrived(True)

+ 2 - 1
custom_components/room_cal_ha/strings.json

@@ -122,7 +122,8 @@
       "override_next": { "name": "Override to Next" },
       "override_next": { "name": "Override to Next" },
       "override_empty": { "name": "Override to Empty" },
       "override_empty": { "name": "Override to Empty" },
       "guest_arrived": { "name": "Guest Arrived" },
       "guest_arrived": { "name": "Guest Arrived" },
-      "guest_departed": { "name": "Guest Departed" }
+      "guest_departed": { "name": "Guest Departed" },
+      "guest_entered": { "name": "Guest Entered" }
     }
     }
   },
   },
   "services": {
   "services": {

+ 2 - 1
custom_components/room_cal_ha/translations/en.json

@@ -122,7 +122,8 @@
       "override_next": { "name": "Override to Next" },
       "override_next": { "name": "Override to Next" },
       "override_empty": { "name": "Override to Empty" },
       "override_empty": { "name": "Override to Empty" },
       "guest_arrived": { "name": "Guest Arrived" },
       "guest_arrived": { "name": "Guest Arrived" },
-      "guest_departed": { "name": "Guest Departed" }
+      "guest_departed": { "name": "Guest Departed" },
+      "guest_entered": { "name": "Guest Entered" }
     }
     }
   },
   },
   "services": {
   "services": {

+ 29 - 1
tests/test_arrived_departed.py

@@ -11,7 +11,11 @@ from homeassistant.core import HomeAssistant
 from pytest_homeassistant_custom_component.common import MockConfigEntry
 from pytest_homeassistant_custom_component.common import MockConfigEntry
 
 
 from custom_components.room_cal_ha.binary_sensor import ArrivedSensor, DepartedSensor
 from custom_components.room_cal_ha.binary_sensor import ArrivedSensor, DepartedSensor
-from custom_components.room_cal_ha.button import GuestArrivedButton, GuestDepartedButton
+from custom_components.room_cal_ha.button import (
+  GuestArrivedButton,
+  GuestDepartedButton,
+  GuestEnteredButton,
+)
 from custom_components.room_cal_ha.const import (
 from custom_components.room_cal_ha.const import (
   CONF_SYNC_MODE,
   CONF_SYNC_MODE,
   DOMAIN,
   DOMAIN,
@@ -125,6 +129,30 @@ async def test_departed_button_calls_set_departed_true():
   btn._coordinator.async_set_departed.assert_awaited_once_with(True)
   btn._coordinator.async_set_departed.assert_awaited_once_with(True)
 
 
 
 
+# ---------------------------------------------------------------------------
+# GuestEnteredButton unit tests
+# ---------------------------------------------------------------------------
+
+def _entered_button(arrived=False) -> GuestEnteredButton:
+  btn = object.__new__(GuestEnteredButton)
+  btn._coordinator = MagicMock()
+  btn._coordinator.data = _data(arrived=arrived)
+  btn._coordinator.async_set_arrived = AsyncMock()
+  return btn
+
+
+async def test_entered_button_sets_arrived_when_not_yet_arrived():
+  btn = _entered_button(arrived=False)
+  await btn.async_press()
+  btn._coordinator.async_set_arrived.assert_awaited_once_with(True)
+
+
+async def test_entered_button_does_not_re_set_arrived_when_already_arrived():
+  btn = _entered_button(arrived=True)
+  await btn.async_press()
+  btn._coordinator.async_set_arrived.assert_not_awaited()
+
+
 # ---------------------------------------------------------------------------
 # ---------------------------------------------------------------------------
 # Coordinator: async_set_arrived / async_set_departed
 # Coordinator: async_set_arrived / async_set_departed
 # ---------------------------------------------------------------------------
 # ---------------------------------------------------------------------------