2121ADT_ALARM_UNKNOWN = "unknown"
2222ADT_ALARM_ARMING = "arming"
2323ADT_ALARM_DISARMING = "disarming"
24+ ADT_ALARM_NIGHT = "night"
2425
2526ALARM_STATUSES = (
2627 ADT_ALARM_AWAY ,
2930 ADT_ALARM_UNKNOWN ,
3031 ADT_ALARM_ARMING ,
3132 ADT_ALARM_DISARMING ,
33+ ADT_ALARM_NIGHT ,
3234)
3335
36+ ALARM_POSSIBLE_STATUS_MAP = {
37+ "Disarmed" : (ADT_ALARM_OFF , ADT_ALARM_ARMING ),
38+ "Armed Away" : (ADT_ALARM_AWAY , ADT_ALARM_DISARMING ),
39+ "Armed Stay" : (ADT_ALARM_HOME , ADT_ALARM_DISARMING ),
40+ "Armed Night" : (ADT_ALARM_NIGHT , ADT_ALARM_DISARMING ),
41+ }
42+
3443ADT_ARM_DISARM_TIMEOUT : float = 20
3544
3645
@@ -129,6 +138,16 @@ def is_disarming(self) -> bool:
129138 with self ._state_lock :
130139 return self ._status == ADT_ALARM_DISARMING
131140
141+ @property
142+ def is_armed_night (self ) -> bool :
143+ """Return if system is in night mode.
144+
145+ Returns:
146+ bool: True if system is in night mode
147+ """
148+ with self ._state_lock :
149+ return self ._status == ADT_ALARM_NIGHT
150+
132151 @property
133152 def last_update (self ) -> float :
134153 """Return last update time.
@@ -198,7 +217,7 @@ async def _arm(
198217 if arm_result is not None :
199218 error_block = arm_result .find (".//div" )
200219 if error_block is not None :
201- error_text = arm_result .text_contents ().replace (
220+ error_text = arm_result .text_content ().replace (
202221 "Arm AnywayCancel\n \n " , ""
203222 )
204223 LOG .warning (
@@ -240,6 +259,18 @@ def arm_away(self, connection: PulseConnection, force_arm: bool = False) -> bool
240259 """
241260 return self ._sync_set_alarm_mode (connection , ADT_ALARM_AWAY , force_arm )
242261
262+ @typechecked
263+ def arm_night (self , connection : PulseConnection , force_arm : bool = False ) -> bool :
264+ """Arm the alarm in Night mode.
265+
266+ Args:
267+ force_arm (bool, Optional): force system to arm
268+
269+ Returns:
270+ bool: True if arm succeeded
271+ """
272+ return self ._sync_set_alarm_mode (connection , ADT_ALARM_NIGHT , force_arm )
273+
243274 @typechecked
244275 def arm_home (self , connection : PulseConnection , force_arm : bool = False ) -> bool :
245276 """Arm the alarm in Home mode.
@@ -288,6 +319,19 @@ async def async_arm_home(
288319 """
289320 return await self ._arm (connection , ADT_ALARM_HOME , force_arm )
290321
322+ @typechecked
323+ async def async_arm_night (
324+ self , connection : PulseConnection , force_arm : bool = False
325+ ) -> bool :
326+ """Arm alarm night async.
327+
328+ Args:
329+ force_arm (bool, Optional): force system to arm
330+ Returns:
331+ bool: True if arm succeeded
332+ """
333+ return await self ._arm (connection , ADT_ALARM_NIGHT , force_arm )
334+
291335 @typechecked
292336 async def async_disarm (self , connection : PulseConnection ) -> bool :
293337 """Disarm alarm async.
@@ -313,51 +357,44 @@ def update_alarm_from_etree(self, summary_html_etree: html.HtmlElement) -> None:
313357 value = summary_html_etree .find (".//span[@class='p_boldNormalTextLarge']" )
314358 sat_location = "security_button_0"
315359 with self ._state_lock :
360+ status_found = False
361+ last_updated = int (time ())
316362 if value is not None :
317363 text = value .text_content ().lstrip ().splitlines ()[0 ]
318- last_updated = int (time ())
319-
320- if text .startswith ("Disarmed" ):
321- if (
322- self ._status != ADT_ALARM_ARMING
323- or last_updated - self ._last_arm_disarm > ADT_ARM_DISARM_TIMEOUT
324- ):
325- self ._status = ADT_ALARM_OFF
326- self ._last_arm_disarm = last_updated
327- elif text .startswith ("Armed Away" ):
328- if (
329- self ._status != ADT_ALARM_DISARMING
330- or last_updated - self ._last_arm_disarm > ADT_ARM_DISARM_TIMEOUT
331- ):
332- self ._status = ADT_ALARM_AWAY
333- self ._last_arm_disarm = last_updated
334- elif text .startswith ("Armed Stay" ):
335- if (
336- self ._status != ADT_ALARM_DISARMING
337- or last_updated - self ._last_arm_disarm > ADT_ARM_DISARM_TIMEOUT
338- ):
339- self ._status = ADT_ALARM_HOME
340- self ._last_arm_disarm = last_updated
341- else :
364+
365+ for (
366+ current_status ,
367+ possible_statuses ,
368+ ) in ALARM_POSSIBLE_STATUS_MAP .items ():
369+ if text .startswith (current_status ):
370+ status_found = True
371+ if (
372+ self ._status != possible_statuses [1 ]
373+ or last_updated - self ._last_arm_disarm
374+ > ADT_ARM_DISARM_TIMEOUT
375+ ):
376+ self ._status = possible_statuses [0 ]
377+ self ._last_arm_disarm = last_updated
378+ break
379+
380+ if value is None or not status_found :
381+ if not text .startswith ("Status Unavailable" ):
342382 LOG .warning ("Failed to get alarm status from '%s'" , text )
343- self ._status = ADT_ALARM_UNKNOWN
344- self ._last_arm_disarm = last_updated
345- return
346- LOG .debug ("Alarm status = %s" , self ._status )
383+ self ._status = ADT_ALARM_UNKNOWN
384+ self ._last_arm_disarm = last_updated
385+ return
386+ LOG .debug ("Alarm status = %s" , self ._status )
347387 sat_string = f'.//input[@id="{ sat_location } "]'
348388 sat_button = summary_html_etree .find (sat_string )
349389 if sat_button is not None and "onclick" in sat_button .attrib :
350390 on_click = sat_button .attrib ["onclick" ]
351391 match = re .search (r"sat=([a-z0-9\-]+)" , on_click )
352392 if match :
353393 self ._sat = match .group (1 )
354- elif len (self ._sat ) == 0 :
355- LOG .warning ("No sat recorded and was unable extract sat." )
356-
357- if len (self ._sat ) > 0 :
358- LOG .debug ("Extracted sat = %s" , self ._sat )
394+ if not self ._sat :
395+ LOG .warning ("No sat recorded and was unable to extract sat." )
359396 else :
360- LOG .warning ( "Unable to extract sat" )
397+ LOG .debug ( "Extracted sat = %s" , self . _sat )
361398
362399 @typechecked
363400 def set_alarm_attributes (self , alarm_attributes : dict [str , str ]) -> None :
0 commit comments