Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial typing and some linter fixes #235

Merged
merged 6 commits into from
Dec 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta:__legacy__"

[project]
name="total_connect_client"
version="2024.12"
version="2024.12.1"
authors = [
{ name="Craig J. Midwinter", email="craig.j.midwinter@gmail.com" },
]
Expand Down
8 changes: 4 additions & 4 deletions total_connect_client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ def _send_one_request(self, operation_name, args):
API_APP_ID = "14588"
API_APP_VERSION = "1.0.34"

def request(self, operation_name, args, attempts_remaining=5):
def request(self, operation_name, args, attempts_remaining: int=5):
"""Send a SOAP request. args is a list or tuple defining the
parameters to the operation.
"""
Expand Down Expand Up @@ -313,7 +313,7 @@ def authenticate(self):
LOGGER.info(f"{self.username} authenticated: {len(self._locations)} locations")
self.times["authenticate()"] = time.time() - start_time

def validate_usercode(self, device_id, usercode):
def validate_usercode(self, device_id, usercode:str)-> bool:
"""Return True if the usercode is valid for the device."""
response = self.request(
"ValidateUserCode", (self.token, device_id, str(usercode))
Expand All @@ -328,7 +328,7 @@ def validate_usercode(self, device_id, usercode):
return False
return True

def is_logged_in(self):
def is_logged_in(self)->bool:
"""Return true if the client is logged into the Total Connect service
with valid credentials.
"""
Expand All @@ -344,7 +344,7 @@ def log_out(self):
LOGGER.info("Logout Successful")
self.token = None

def get_number_locations(self):
def get_number_locations(self)->int:
"""Return the number of locations. Home Assistant needs a way
to force the locations to load inside a callable function.
"""
Expand Down
20 changes: 15 additions & 5 deletions total_connect_client/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@


class ArmType(Enum):
"""Represent ArmingType."""

AWAY = 0
STAY = 1
STAY_INSTANT = 2
Expand All @@ -14,6 +16,8 @@ class ArmType(Enum):


class ArmingState(Enum):
"""Represent ArmingState."""

DISARMED = 10200
DISARMED_BYPASS = 10211
DISARMED_ZONE_FAULTED = (
Expand Down Expand Up @@ -141,7 +145,9 @@ def is_triggered(self):


class _ResultCode(Enum):
"""As suggested by the leading underscore, this class is not used by
"""Represent ResultCode.

As suggested by the leading underscore, this class is not used by
callers of the API.
"""

Expand All @@ -150,8 +156,11 @@ def from_response(response_dict):
try:
return _ResultCode(response_dict["ResultCode"])
except TypeError:
# sometimes when there are server issues, it returns empty responses - see issue #228
raise ServiceUnavailable(f"Server returned empty response, check server status at {STATUS_URL}") from None
# sometimes when there are server issues,
# it returns empty responses - see issue #228
raise ServiceUnavailable(
f"Server returned empty response, check server status at {STATUS_URL}"
) from None
except ValueError:
raise BadResultCodeError(
f"unknown result code {response_dict['ResultCode']}", response_dict
Expand All @@ -170,7 +179,8 @@ def from_response(response_dict):
USER_CODE_INVALID = -4106
FAILED_TO_CONNECT = -4104

# Invalid Parameter returned when requesting SyncPanelStatus using non-existant JobID.
# Invalid Parameter returned when requesting SyncPanelStatus
# using non-existant JobID.
INVALID_PARAMETER = -501

BAD_OBJECT_REFERENCE = -400
Expand All @@ -183,4 +193,4 @@ def from_response(response_dict):

PROJECT_URL = "https://github.com/craigjmidwinter/total-connect-client"

STATUS_URL = "https://status.resideo.com/"
STATUS_URL = "https://status.resideo.com/"
4 changes: 2 additions & 2 deletions total_connect_client/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
class TotalConnectDevice:
"""Device class for Total Connect."""

def __init__(self, info):
def __init__(self, info: dict):
"""Initialize device based on DeviceInfoBasic."""
self.deviceid = info.get("DeviceID")
self.name = info.get("DeviceName")
Expand Down Expand Up @@ -61,7 +61,7 @@ def doorbell_info(self, data):
if data:
self._doorbell_info = data

def is_doorbell(self):
def is_doorbell(self) -> bool:
"""Return true if a doorbell."""
if self._doorbell_info and self._doorbell_info["IsExistingDoorBellUser"] == 1:
return True
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
import logging
import sys

from .const import ArmType
from .client import TotalConnectClient
from ..const import ArmType
from ..client import TotalConnectClient

logging.basicConfig(filename="test.log", level=logging.DEBUG)

Expand Down
File renamed without changes.
File renamed without changes.
2 changes: 1 addition & 1 deletion total_connect_client/location.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ def _build_partition_list(self, partition_id=None):
)
return {"int": [partition_id]}

def arm(self, arm_type, partition_id=None):
def arm(self, arm_type: int, partition_id=None):
"""Arm the given partition. If no partition is given, arm all partitions."""
# see https://rs.alarmnet.com/TC21api/tc2.asmx?op=ArmSecuritySystemPartitionsV1
assert isinstance(arm_type, ArmType)
Expand Down
4 changes: 2 additions & 2 deletions total_connect_client/partition.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,15 @@ def __str__(self):

return data

def arm(self, arm_type):
def arm(self, arm_type: int):
"""Arm the partition."""
self.parent.arm(arm_type, self.partitionid)

def disarm(self):
"""Disarm the partition."""
self.parent.disarm(self.partitionid)

def _update(self, info):
def _update(self, info: dict):
"""Update partition based on PartitionInfo."""
astate = (info or {}).get("ArmingState")
if astate is None:
Expand Down
2 changes: 1 addition & 1 deletion total_connect_client/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def __init__(self, user_info):
"with minimal permissions."
)

def security_problem(self):
def security_problem(self)-> bool:
"""Run security checks. Return true if problem."""
problem = False

Expand Down
61 changes: 37 additions & 24 deletions total_connect_client/zone.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,26 @@


class ZoneStatus(IntFlag):
"""Class to represent ZoneStatus."""

NORMAL = 0
BYPASSED = 1
FAULT = 2
TROUBLE = 8 # is also Tampered
TAMPER = 16 # Tamper for ProA7, see #176
COMMUNICATION_FAILURE = 32 # see #191
TAMPER = 16 # Tamper for ProA7, see #176
COMMUNICATION_FAILURE = 32 # see #191
LOW_BATTERY = 64
TRIGGERED = 256
KNOWN = NORMAL | BYPASSED | FAULT | TROUBLE | TAMPER | COMMUNICATION_FAILURE | LOW_BATTERY | TRIGGERED
KNOWN = (
NORMAL
| BYPASSED
| FAULT
| TROUBLE
| TAMPER
| COMMUNICATION_FAILURE
| LOW_BATTERY
| TRIGGERED
)


class ZoneType(Enum):
Expand Down Expand Up @@ -57,8 +68,8 @@ class ZoneType(Enum):
AAV_MONITOR = 81 # per Vista20P docs
LYRIC_LOCAL_ALARM = 89

# According to the VISTA docs, these can be programmed via downloader software
# or from a keypad using data fields *182-*185
# According to the VISTA docs, these can be programmed via downloader
# software or from a keypad using data fields *182-*185

VISTA_CONFIGURABLE_90 = 90
VISTA_CONFIGURABLE_91 = 91
Expand Down Expand Up @@ -108,34 +119,36 @@ def __str__(self):
f"Device Type: {self.device_type}\n\n"
)

def is_bypassed(self):
def is_bypassed(self) -> bool:
"""Return true if the zone is bypassed."""
return self.status & ZoneStatus.BYPASSED > 0

def is_faulted(self):
def is_faulted(self) -> bool:
"""Return true if the zone is faulted."""
return self.status & ZoneStatus.FAULT > 0

def is_tampered(self):
def is_tampered(self) -> bool:
"""Return true if zone is tampered."""
return (self.status & ZoneStatus.TROUBLE > 0) or (self.status & ZoneStatus.TAMPER > 0)
return (self.status & ZoneStatus.TROUBLE > 0) or (
self.status & ZoneStatus.TAMPER > 0
)

def is_low_battery(self):
def is_low_battery(self) -> bool:
"""Return true if low battery."""
return self.status & ZoneStatus.LOW_BATTERY > 0

def is_troubled(self):
def is_troubled(self) -> bool:
"""Return true if zone is troubled."""
return self.status & ZoneStatus.TROUBLE > 0

def is_triggered(self):
def is_triggered(self) -> bool:
"""Return true if zone is triggered."""
return self.status & ZoneStatus.TRIGGERED > 0

def is_type_button(self):
def is_type_button(self) -> bool:
"""Return true if zone is a button."""

# as seen so far, any security zone that cannot be bypassed is a button on a panel
# as seen so far, any security zone that cannot be bypassed
# is a button on a panel
if self.is_type_security() and not self.can_be_bypassed:
return True

Expand All @@ -151,9 +164,8 @@ def is_type_button(self):

return False

def is_type_security(self):
def is_type_security(self) -> bool:
"""Return true if zone type is security."""

return self.zone_type_id in (
ZoneType.SECURITY,
ZoneType.ENTRY_EXIT1,
Expand All @@ -168,27 +180,27 @@ def is_type_security(self):
ZoneType.PROA7_GARAGE_MONITOR,
)

def is_type_motion(self):
def is_type_motion(self) -> bool:
"""Return true if zone type is motion."""
return self.zone_type_id == ZoneType.INTERIOR_FOLLOWER

def is_type_fire(self):
def is_type_fire(self) -> bool:
"""Return true if zone type is fire or smoke."""
return self.zone_type_id == ZoneType.FIRE_SMOKE

def is_type_temperature(self):
def is_type_temperature(self) -> bool:
"""Return true if zone monitors the temperature."""
return self.zone_type_id == ZoneType.MONITOR

def is_type_carbon_monoxide(self):
def is_type_carbon_monoxide(self) -> bool:
"""Return true if zone type is carbon monoxide."""
return self.zone_type_id == ZoneType.CARBON_MONOXIDE

def is_type_medical(self):
def is_type_medical(self) -> bool:
"""Return true if zone type is medical."""
return self.zone_type_id == ZoneType.PROA7_MEDICAL

def is_type_keypad(self):
def is_type_keypad(self) -> bool:
"""Return true if zone type is keypad."""
return self.zone_type_id == ZoneType.LYRIC_KEYPAD

Expand All @@ -199,7 +211,8 @@ def _update(self, zone):
assert self.zoneid == zid, (self.zoneid, zid)

self.description = zone.get("ZoneDescription")
# ZoneInfo gives 'PartitionID' but ZoneStatusInfoWithPartitionId gives 'PartitionId'
# ZoneInfo gives 'PartitionID' but
# ZoneStatusInfoWithPartitionId gives 'PartitionId'
if "PartitionId" in zone:
# ...and PartitionId gives an int instead of a string
self.partition = str(zone["PartitionId"])
Expand Down
Loading