"""Total Connect Client constants.""" import urllib.parse from enum import Enum from typing import Any, Final from .exceptions import BadResultCodeError class ArmType(Enum): """Represent ArmingType.""" AWAY = 0 STAY = 1 STAY_INSTANT = 2 AWAY_INSTANT = 3 STAY_NIGHT = 4 class ArmingState(Enum): """Represent ArmingState.""" DISARMED = 10200 DISARMED_BYPASS = 10211 DISARMED_ZONE_FAULTED = 10214 # seems to apply to location, not to partition. See issue #144 ARMED_AWAY = 10201 ARMED_AWAY_BYPASS = 10202 ARMED_STAY = 10203 ARMED_STAY_OTHER = 10226 ARMED_STAY_PROA7 = 10230 # issue #173 ARMED_STAY_BYPASS_PROA7 = 10231 # issue #177 ARMED_STAY_INSTANT_PROA7 = 10232 # issue #177 ARMED_STAY_INSTANT_BYPASS_PROA7 = 10233 # issue #177 ARMED_STAY_BYPASS = 10204 ARMED_AWAY_INSTANT = 10205 ARMED_AWAY_INSTANT_BYPASS = 10206 ARMED_STAY_INSTANT = 10209 ARMED_STAY_INSTANT_BYPASS = 10210 ARMED_STAY_NIGHT = 10218 ARMED_STAY_NIGHT_BYPASS_PROA7 = 10219 # issue #177 ARMED_STAY_NIGHT_INSTANT_PROA7 = 10220 # issue #177 ARMED_STAY_NIGHT_INSTANT_BYPASS_PROA7 = 10221 # issue #177 ARMED_CUSTOM_BYPASS = 10223 ALARMING = 10207 ALARMING_FIRE_SMOKE = 10212 ALARMING_CARBON_MONOXIDE = 10213 ALARMING_CARBON_MONOXIDE_PROA7 = 10215 ARMING = 10307 DISARMING = 10308 UNKNOWN = 0 def is_arming(self) -> bool: """Return true if the system is in the process of arming.""" return self == ArmingState.ARMING def is_disarming(self) -> bool: """Return true if the system is in the process of disarming.""" return self == ArmingState.DISARMING def is_pending(self) -> bool: """Return true if the system is pending an action.""" return self.is_disarming() or self.is_arming() def is_disarmed(self) -> bool: """Return True if the system is disarmed.""" return self in ( ArmingState.DISARMED, ArmingState.DISARMED_BYPASS, ArmingState.DISARMED_ZONE_FAULTED, ) def is_armed_away(self) -> bool: """Return True if the system is armed away in any way.""" return self in ( ArmingState.ARMED_AWAY, ArmingState.ARMED_AWAY_BYPASS, ArmingState.ARMED_AWAY_INSTANT, ArmingState.ARMED_AWAY_INSTANT_BYPASS, ) def is_armed_custom_bypass(self) -> bool: """Return True if the system is armed custom bypass in any way.""" return self == ArmingState.ARMED_CUSTOM_BYPASS def is_armed_home(self) -> bool: """Return True if the system is armed home/stay in any way.""" return self in ( ArmingState.ARMED_STAY, ArmingState.ARMED_STAY_PROA7, ArmingState.ARMED_STAY_BYPASS, ArmingState.ARMED_STAY_BYPASS_PROA7, ArmingState.ARMED_STAY_INSTANT, ArmingState.ARMED_STAY_INSTANT_PROA7, ArmingState.ARMED_STAY_INSTANT_BYPASS, ArmingState.ARMED_STAY_INSTANT_BYPASS_PROA7, ArmingState.ARMED_STAY_NIGHT, ArmingState.ARMED_STAY_NIGHT_BYPASS_PROA7, ArmingState.ARMED_STAY_NIGHT_INSTANT_PROA7, ArmingState.ARMED_STAY_NIGHT_INSTANT_BYPASS_PROA7, ArmingState.ARMED_STAY_OTHER, ) def is_armed_night(self) -> bool: """Return True if the system is armed night in any way.""" return self in ( ArmingState.ARMED_STAY_NIGHT, ArmingState.ARMED_STAY_NIGHT_BYPASS_PROA7, ArmingState.ARMED_STAY_NIGHT_INSTANT_PROA7, ArmingState.ARMED_STAY_NIGHT_INSTANT_BYPASS_PROA7, # per #240 STAY_INSTANT and STAY_INSTANT_BYPASS are actually Night ArmingState.ARMED_STAY_INSTANT, ArmingState.ARMED_STAY_INSTANT_BYPASS, ) def is_armed(self) -> bool: """Return True if the system is armed in any way.""" return ( self.is_armed_away() or self.is_armed_home() or self.is_armed_night() # noqa: W504 or self.is_armed_custom_bypass() ) def is_triggered_police(self) -> bool: """Return True if the system is triggered for police or medical.""" return self == ArmingState.ALARMING def is_triggered_fire(self) -> bool: """Return True if the system is triggered for fire or smoke.""" return self == ArmingState.ALARMING_FIRE_SMOKE def is_triggered_gas(self) -> bool: """Return True if the system is triggered for carbon monoxide.""" return self in ( ArmingState.ALARMING_CARBON_MONOXIDE, ArmingState.ALARMING_CARBON_MONOXIDE_PROA7, ) def is_triggered(self) -> bool: """Return True if the system is triggered in any way.""" return self.is_triggered_fire() or self.is_triggered_gas() or self.is_triggered_police() class _ResultCode(Enum): """Represent ResultCode. As suggested by the leading underscore, this class is not used by callers of the API. """ @staticmethod def from_response(response_dict: dict[str, Any]) -> "_ResultCode": """Create a _ResultCode from an API response dictionary. Args: response_dict: Dictionary containing API response with ResultCode or error field Returns: _ResultCode enum value corresponding to the response Raises: BadResultCodeError: If the result code is unknown """ value = response_dict.get("ResultCode") or response_dict.get("error") if not value: return _ResultCode.SUCCESS try: return _ResultCode(int(value)) except ValueError: raise BadResultCodeError(f"unknown result code {value}", response_dict) from None SUCCESS = 0 ARM_SUCCESS = 4500 DISARM_SUCCESS = 4500 SESSION_INITIATED = 4500 BAD_USER_OR_PASSWORD = -50004 INVALID_SESSIONID = -30002 FAILED_TO_BYPASS_ZONE = -4504 COMMAND_FAILED = -4502 USER_CODE_UNAVAILABLE = -4114 CANNOT_CONNECT = -4108 # see #262 USER_CODE_INVALID = -4106 FAILED_TO_CONNECT = -4104 # Invalid Parameter returned when requesting SyncPanelStatus # using non-existant JobID. INVALID_PARAMETER = -501 BAD_OBJECT_REFERENCE = -400 FEATURE_NOT_SUPPORTED = -120 INVALID_SESSION = -102 AUTHENTICATION_FAILED = -100 CONNECTION_ERROR = 4101 ACCOUNT_LOCKED = -123 PROJECT_URL: Final[str] = "https://github.com/craigjmidwinter/total-connect-client" STATUS_URL: Final[str] = "https://status.resideo.com/" AUTH_CONFIG_ENDPOINT: Final[str] = "https://totalconnect2.com/application.config.json" AUTH_TOKEN_ENDPOINT: Final[str] = "https://rs.alarmnet.com/TC2API.Auth/token" HTTP_API_ENDPOINT_BASE: Final[str] = "https://rs.alarmnet.com/TC2API.TCResource/" def make_http_endpoint(path: str) -> str: """Create a full HTTP endpoint URL by joining the base URL with the given path.""" return urllib.parse.urljoin(HTTP_API_ENDPOINT_BASE, path) HTTP_API_SESSION_DETAILS_ENDPOINT: Final[str] = make_http_endpoint( "api/v3/authentication/sessiondetails" ) HTTP_API_LOGOUT: Final[str] = make_http_endpoint("api/v3/authentication/logout")