Skip to content

Commit a9d644a

Browse files
authored
Merge pull request #202 from natekspencer/dev
Add user data to system
2 parents 3b01344 + 3fea32e commit a9d644a

File tree

4 files changed

+123
-2
lines changed

4 files changed

+123
-2
lines changed

vivintpy/const.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,21 @@ class AuthUserAttribute:
3232
class UserAttribute:
3333
"""User attributes."""
3434

35+
ADMIN = "ad"
3536
DOCUMENT_SEQUENCE = "DocumentSequence"
3637
EMAIL = "e"
3738
GHOME = "ghome"
3839
GROUP_IDS = "grpid"
3940
ID = "_id"
41+
HAS_LOCK_PIN = "hasLockPin"
42+
HAS_PANEL_PIN = "hasPanelPin"
43+
HAS_PINS = "hasPins"
44+
LOCK_IDS = "lids"
4045
MESSAGE_BROADCAST_CHANNEL = "mbc"
4146
NAME = "n"
4247
PING_ID = "pngid"
48+
REGISTERED = "reg"
49+
REMOTE_ACCESS = "ra"
4350
RESTRICTED_SYSTEM = "rsystem"
4451
SMART_HOME_SYSTEM = "smarthomesystem"
4552
SETTINGS = "stg"
@@ -72,6 +79,7 @@ class SystemAttribute:
7279
PARTITION_ID = "parid"
7380
SYSTEM = "system"
7481
SYSTEM_NICKNAME = "sn"
82+
USERS = "u"
7583

7684

7785
class PubNubMessageAttribute:
@@ -149,6 +157,12 @@ class CameraAttribute(VivintDeviceAttribute):
149157
WIRELESS_SIGNAL_STRENGTH = "wiss"
150158

151159

160+
class LockAttribute(VivintDeviceAttribute):
161+
"""Lock attributes."""
162+
163+
USER_CODE_LIST = "ucl"
164+
165+
152166
class SwitchAttribute(VivintDeviceAttribute):
153167
"""Switch attributes."""
154168

vivintpy/devices/door_lock.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
from __future__ import annotations
44

5+
from typing import cast
6+
7+
from ..const import LockAttribute
58
from ..const import ZWaveDeviceAttribute as Attribute
69
from ..utils import send_deprecation_warning
710
from . import BypassTamperDevice
@@ -26,6 +29,11 @@ def node_online(self) -> bool:
2629
send_deprecation_warning("node_online", "is_online")
2730
return self.is_online
2831

32+
@property
33+
def user_code_list(self) -> list[int]:
34+
"""Return the user code list."""
35+
return cast(list[int], self.data.get(LockAttribute.USER_CODE_LIST, []))
36+
2937
async def set_state(self, locked: bool) -> None:
3038
"""Set door lock's state."""
3139
assert self.alarm_panel

vivintpy/system.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from .const import SystemAttribute as Attribute
99
from .devices.alarm_panel import AlarmPanel
1010
from .entity import Entity
11+
from .user import User
1112
from .utils import first_or_none, send_deprecation_warning
1213
from .vivintskyapi import VivintSkyApi
1314

@@ -27,6 +28,10 @@ def __init__(self, data: dict, api: VivintSkyApi, *, name: str, is_admin: bool):
2728
AlarmPanel(panel_data, self)
2829
for panel_data in self.data[Attribute.SYSTEM][Attribute.PARTITION]
2930
]
31+
self.users = [
32+
User(user_data, self)
33+
for user_data in self.data[Attribute.SYSTEM][Attribute.USERS]
34+
]
3035

3136
@property
3237
def api(self) -> VivintSkyApi:
@@ -70,17 +75,29 @@ async def refresh(self) -> None:
7075
else:
7176
self.alarm_panels.append(AlarmPanel(panel_data, self))
7277

78+
def update_user_data(self, data: list[dict]) -> None:
79+
"""Update user data."""
80+
for d in data:
81+
user = first_or_none(self.users, lambda user: user.id == d["_id"])
82+
if not user:
83+
_LOGGER.debug("User not found for system %s: %s", self.id, d)
84+
return
85+
user.handle_pubnub_message(d)
86+
7387
def handle_pubnub_message(self, message: dict) -> None:
7488
"""Handle a pubnub message."""
75-
if message[PubNubMessageAttribute.TYPE] == "account_system":
89+
if (message_type := message[PubNubMessageAttribute.TYPE]) == "account_system":
7690
# this is a system message
7791
operation = message.get(PubNubMessageAttribute.OPERATION)
7892
data = message.get(PubNubMessageAttribute.DATA)
7993

8094
if data and operation == "u":
95+
if Attribute.USERS in data:
96+
self.update_user_data(data[Attribute.USERS])
97+
del data[Attribute.USERS]
8198
self.update_data(data)
8299

83-
elif message[PubNubMessageAttribute.TYPE] == "account_partition":
100+
elif message_type == "account_partition":
84101
# this is a message for one of the devices attached to this system
85102
partition_id = message.get(PubNubMessageAttribute.PARTITION_ID)
86103
if not partition_id:
@@ -106,3 +123,7 @@ def handle_pubnub_message(self, message: dict) -> None:
106123
return
107124

108125
alarm_panel.handle_pubnub_message(message)
126+
else:
127+
_LOGGER.warning(
128+
"Unknown message received by system %s: %s", self.id, message
129+
)

vivintpy/user.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
"""Module that implements the User class."""
2+
3+
from __future__ import annotations
4+
5+
from typing import TYPE_CHECKING, cast
6+
7+
from .const import UserAttribute as Attribute
8+
from .entity import Entity
9+
10+
if TYPE_CHECKING:
11+
from .system import System
12+
13+
ADD_LOCK = f"{Attribute.LOCK_IDS}.1"
14+
15+
16+
class User(Entity):
17+
"""Describe a Vivint user."""
18+
19+
def __init__(self, data: dict, system: System):
20+
"""Initialize a user."""
21+
super().__init__(data)
22+
self._system = system
23+
24+
def __repr__(self) -> str:
25+
"""Return custom __repr__ of user."""
26+
return f"<{self.__class__.__name__} {self.id}, {self.name}{' (admin)' if self.is_admin else ''}>"
27+
28+
@property
29+
def has_lock_pin(self) -> bool:
30+
"""Return True if the user has pins."""
31+
return bool(self.data[Attribute.HAS_LOCK_PIN])
32+
33+
@property
34+
def has_panel_pin(self) -> bool:
35+
"""Return True if the user has pins."""
36+
return bool(self.data[Attribute.HAS_PANEL_PIN])
37+
38+
@property
39+
def has_pins(self) -> bool:
40+
"""Return True if the user has pins."""
41+
return bool(self.data[Attribute.HAS_PINS])
42+
43+
@property
44+
def has_remote_access(self) -> bool:
45+
"""Return True if the user has remote access."""
46+
return bool(self.data[Attribute.REMOTE_ACCESS])
47+
48+
@property
49+
def id(self) -> int: # pylint: disable=invalid-name
50+
"""User's id."""
51+
return int(self.data[Attribute.ID])
52+
53+
@property
54+
def is_admin(self) -> bool:
55+
"""Return True if the user is an admin."""
56+
return bool(self.data[Attribute.ADMIN])
57+
58+
@property
59+
def is_registered(self) -> bool:
60+
"""Return True if the user is registered."""
61+
return bool(self.data[Attribute.REGISTERED])
62+
63+
@property
64+
def lock_ids(self) -> list[int]:
65+
"""User's lock ids."""
66+
return cast(list[int], self.data.get(Attribute.LOCK_IDS, []))
67+
68+
@property
69+
def name(self) -> str:
70+
"""User's name."""
71+
return str(self.data[Attribute.NAME])
72+
73+
def handle_pubnub_message(self, message: dict) -> None:
74+
"""Handle a pubnub message addressed to this user."""
75+
if ADD_LOCK in message:
76+
message[Attribute.LOCK_IDS] = self.lock_ids + [message[ADD_LOCK]]
77+
del message[ADD_LOCK]
78+
super().handle_pubnub_message(message)

0 commit comments

Comments
 (0)