vsure 2.6.9__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- verisure/__init__.py +29 -0
- verisure/__main__.py +156 -0
- verisure/session.py +799 -0
- vsure-2.6.9.dist-info/METADATA +31 -0
- vsure-2.6.9.dist-info/RECORD +10 -0
- vsure-2.6.9.dist-info/WHEEL +5 -0
- vsure-2.6.9.dist-info/entry_points.txt +2 -0
- vsure-2.6.9.dist-info/licenses/LICENSE +22 -0
- vsure-2.6.9.dist-info/top_level.txt +1 -0
- vsure-2.6.9.dist-info/zip-safe +1 -0
verisure/__init__.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""
|
|
2
|
+
A python module for reading and changing status of verisure devices through
|
|
3
|
+
verisure app API.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
__all__ = [
|
|
7
|
+
'Error',
|
|
8
|
+
'LoginError',
|
|
9
|
+
'RequestError',
|
|
10
|
+
'ResponseError',
|
|
11
|
+
'Session',
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
from .session import ( # NOQA
|
|
15
|
+
Error,
|
|
16
|
+
LoginError,
|
|
17
|
+
RequestError,
|
|
18
|
+
VariableTypes,
|
|
19
|
+
ResponseError,
|
|
20
|
+
Session,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
ALARM_ARMED_HOME = 'ARMED_HOME'
|
|
24
|
+
ALARM_ARMED_AWAY = 'ARMED_AWAY'
|
|
25
|
+
ALARM_DISARMED = 'DISARMED'
|
|
26
|
+
LOCK_LOCKED = 'LOCKED'
|
|
27
|
+
LOCK_UNLOCKED = 'UNLOCKED'
|
|
28
|
+
SMARTPLUG_ON = 'on'
|
|
29
|
+
SMARTPLUG_OFF = 'off'
|
verisure/__main__.py
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
""" Command line interface for Verisure MyPages """
|
|
2
|
+
|
|
3
|
+
import inspect
|
|
4
|
+
import json
|
|
5
|
+
import re
|
|
6
|
+
import click
|
|
7
|
+
import logging
|
|
8
|
+
import getpass
|
|
9
|
+
from verisure import VariableTypes, Session, ResponseError, LoginError
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class DeviceLabel(click.ParamType):
|
|
13
|
+
"""Click param for device label"""
|
|
14
|
+
name = "DeviceLabel"
|
|
15
|
+
|
|
16
|
+
def convert(self, value, param, ctx):
|
|
17
|
+
if re.match(r"^([A-Z]|[0-9]){4} ([A-Z]|[0-9]){4}$", value):
|
|
18
|
+
return value
|
|
19
|
+
self.fail(f"{value!r} is not a device label", param, ctx)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ArmFutureState(click.ParamType):
|
|
23
|
+
"""Click param for arm future state"""
|
|
24
|
+
name = "FutureState"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class LockFutureState(click.ParamType):
|
|
28
|
+
"""Click param for lock future state"""
|
|
29
|
+
name = "FutureState"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class TransactionId(click.ParamType):
|
|
33
|
+
"""Click param for transaction id"""
|
|
34
|
+
name = "TransactionId"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class RequestId(click.ParamType):
|
|
38
|
+
"""Click param for request id"""
|
|
39
|
+
name = "RequestId"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class Code(click.ParamType):
|
|
43
|
+
"""Click param for code"""
|
|
44
|
+
name = "Code"
|
|
45
|
+
|
|
46
|
+
def convert(self, value, param, ctx):
|
|
47
|
+
if re.match(r"^[0-9]{4,6}$", value):
|
|
48
|
+
return value
|
|
49
|
+
self.fail(f"{value!r} is not a code", param, ctx)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
VariableTypeMap = {
|
|
53
|
+
VariableTypes.DeviceLabel: DeviceLabel(),
|
|
54
|
+
VariableTypes.ArmFutureState: ArmFutureState(),
|
|
55
|
+
VariableTypes.LockFutureState: LockFutureState(),
|
|
56
|
+
bool: click.BOOL,
|
|
57
|
+
VariableTypes.TransactionId: TransactionId(),
|
|
58
|
+
VariableTypes.RequestId: RequestId(),
|
|
59
|
+
VariableTypes.Code: Code(),
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def options_from_operator_list():
|
|
64
|
+
"""Get all query operations and build query cli"""
|
|
65
|
+
def decorator(f):
|
|
66
|
+
ops = inspect.getmembers(Session, predicate=inspect.isfunction)
|
|
67
|
+
for name, operation in reversed(ops):
|
|
68
|
+
if not hasattr(operation, 'is_query'):
|
|
69
|
+
continue
|
|
70
|
+
variables = list(operation.__annotations__.values())
|
|
71
|
+
# Remove Giid type from variables, not supported by CLI
|
|
72
|
+
if VariableTypes.Giid in variables:
|
|
73
|
+
variables.remove(VariableTypes.Giid)
|
|
74
|
+
dashed_name = name.replace('_', '-')
|
|
75
|
+
if len(variables) == 0:
|
|
76
|
+
click.option(
|
|
77
|
+
'--'+dashed_name,
|
|
78
|
+
is_flag=True,
|
|
79
|
+
help=operation.__doc__)(f)
|
|
80
|
+
elif len(variables) == 1:
|
|
81
|
+
click.option(
|
|
82
|
+
'--'+dashed_name,
|
|
83
|
+
type=VariableTypeMap[variables[0]],
|
|
84
|
+
help=operation.__doc__)(f)
|
|
85
|
+
else:
|
|
86
|
+
types = [VariableTypeMap[variable] for variable in variables]
|
|
87
|
+
click.option(
|
|
88
|
+
'--'+dashed_name,
|
|
89
|
+
type=click.Tuple(types),
|
|
90
|
+
help=operation.__doc__)(f)
|
|
91
|
+
return f
|
|
92
|
+
return decorator
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def make_query(session, name, arguments):
|
|
96
|
+
"""make query operation"""
|
|
97
|
+
if arguments is True:
|
|
98
|
+
return getattr(session, name)()
|
|
99
|
+
if isinstance(arguments, str):
|
|
100
|
+
return getattr(session, name)(arguments)
|
|
101
|
+
return getattr(session, name)(*arguments)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@click.command()
|
|
105
|
+
@click.argument('username')
|
|
106
|
+
@click.argument('password', required=False)
|
|
107
|
+
@click.option('-i', '--installation', 'installation', help='Installation number', type=int, default=0) # noqa: E501
|
|
108
|
+
@click.option('-c', '--cookie', 'cookie', help='File to store cookie in', default='~/.verisure-cookie') # noqa: E501
|
|
109
|
+
@click.option('--mfa', 'mfa', help='Login using MFA', default=False, is_flag=True) # noqa: E501
|
|
110
|
+
@click.option('--log-level', type=click.Choice(['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'], case_sensitive=False)) # noqa: E501
|
|
111
|
+
@options_from_operator_list()
|
|
112
|
+
def cli(username, password, installation, cookie, mfa, log_level, **kwargs):
|
|
113
|
+
"""
|
|
114
|
+
Read and change status of verisure devices through verisure app API\n
|
|
115
|
+
PASSWORD will be prompted without echoing if not provided as an argument
|
|
116
|
+
"""
|
|
117
|
+
|
|
118
|
+
if not password:
|
|
119
|
+
password = getpass.getpass(prompt='Password: ', stream=None)
|
|
120
|
+
if log_level:
|
|
121
|
+
logging.basicConfig(level=logging.getLevelName(log_level))
|
|
122
|
+
|
|
123
|
+
session = Session(username, password, cookie)
|
|
124
|
+
|
|
125
|
+
try:
|
|
126
|
+
# try using the cookie first
|
|
127
|
+
installations = session.login_cookie()
|
|
128
|
+
except LoginError:
|
|
129
|
+
installations = None
|
|
130
|
+
|
|
131
|
+
try:
|
|
132
|
+
if mfa and not installations:
|
|
133
|
+
session.request_mfa()
|
|
134
|
+
code = input("Enter verification code: ")
|
|
135
|
+
session.validate_mfa(code)
|
|
136
|
+
installations = session.login_cookie()
|
|
137
|
+
elif not installations:
|
|
138
|
+
installations = session.login()
|
|
139
|
+
|
|
140
|
+
session.set_giid(
|
|
141
|
+
installations['data']['account']
|
|
142
|
+
['installations'][installation]['giid'])
|
|
143
|
+
queries = [
|
|
144
|
+
make_query(session, name, arguments)
|
|
145
|
+
for name, arguments in kwargs.items()
|
|
146
|
+
if arguments]
|
|
147
|
+
result = session.request(*queries)
|
|
148
|
+
click.echo(json.dumps(result, indent=4, separators=(',', ': ')))
|
|
149
|
+
|
|
150
|
+
except ResponseError as ex:
|
|
151
|
+
click.echo(ex,err=True)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
if __name__ == "__main__":
|
|
155
|
+
# pylint: disable=no-value-for-parameter
|
|
156
|
+
cli()
|
verisure/session.py
ADDED
|
@@ -0,0 +1,799 @@
|
|
|
1
|
+
'''
|
|
2
|
+
Verisure session, using verisure app api
|
|
3
|
+
'''
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import logging
|
|
7
|
+
import os
|
|
8
|
+
import pickle
|
|
9
|
+
|
|
10
|
+
import requests
|
|
11
|
+
|
|
12
|
+
LOGGER = logging.getLogger(__package__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Error(Exception):
|
|
16
|
+
''' Verisure session error '''
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class RequestError(Error):
|
|
20
|
+
''' Request '''
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class LoginError(Error):
|
|
24
|
+
''' Login failed '''
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class LogoutError(Error):
|
|
28
|
+
''' Logout failed '''
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class ResponseError(Error):
|
|
32
|
+
''' Unexcpected response '''
|
|
33
|
+
def __init__(self, status_code, text):
|
|
34
|
+
super().__init__(
|
|
35
|
+
f'Invalid response, status code: {status_code} - Data: {text}')
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def query_func(f):
|
|
39
|
+
"""A wrapper that indicates that the function is a query (used by CLI)"""
|
|
40
|
+
f.is_query = True
|
|
41
|
+
return f
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class VariableTypes:
|
|
45
|
+
"""Types for query parameters"""
|
|
46
|
+
class DeviceLabel(str):
|
|
47
|
+
"""Device label"""
|
|
48
|
+
|
|
49
|
+
class TransactionId(str):
|
|
50
|
+
"""Transaction ID"""
|
|
51
|
+
|
|
52
|
+
class RequestId(str):
|
|
53
|
+
"""Request ID"""
|
|
54
|
+
|
|
55
|
+
class ArmFutureState(str):
|
|
56
|
+
"""Arm state"""
|
|
57
|
+
# ARMED_AWAY, DISARMED, ARMED_HOME
|
|
58
|
+
|
|
59
|
+
class LockFutureState(str):
|
|
60
|
+
"""Lock state"""
|
|
61
|
+
|
|
62
|
+
class Code(str):
|
|
63
|
+
"""Code"""
|
|
64
|
+
|
|
65
|
+
class Giid(str):
|
|
66
|
+
"""Giid"""
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class Session(object):
|
|
70
|
+
""" Verisure app session
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
username (str): Username used to login to verisure app
|
|
74
|
+
password (str): Password used to login to verisure app
|
|
75
|
+
cookie_file_name (str): path to cookie file
|
|
76
|
+
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
def __init__(self, username, password,
|
|
80
|
+
cookie_file_name='~/.verisure-cookie'):
|
|
81
|
+
LOGGER.info(f"Initialize Session ({username=}, {cookie_file_name=})")
|
|
82
|
+
self._username = username
|
|
83
|
+
self._password = password
|
|
84
|
+
self._cookies = None
|
|
85
|
+
self._cookie_file_name = os.path.expanduser(cookie_file_name)
|
|
86
|
+
self._trust_token = None
|
|
87
|
+
self._giid = None
|
|
88
|
+
self._base_url = None
|
|
89
|
+
self._base_urls = ['https://automation01.verisure.com',
|
|
90
|
+
'https://automation02.verisure.com']
|
|
91
|
+
self._post = self._wrap_request(requests.post)
|
|
92
|
+
self._delete = self._wrap_request(requests.delete)
|
|
93
|
+
self._get = self._wrap_request(requests.get)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _wrap_request(self, function):
|
|
97
|
+
"""
|
|
98
|
+
Used to wrap methods from the requests module to try both urls and remember
|
|
99
|
+
the last working one.
|
|
100
|
+
"""
|
|
101
|
+
|
|
102
|
+
def wrapper(url, *args, **kwargs):
|
|
103
|
+
last_exception = Error("Unknown error")
|
|
104
|
+
base_urls = self._base_urls.copy()
|
|
105
|
+
for base_url in base_urls:
|
|
106
|
+
try:
|
|
107
|
+
response = function(base_url+url, *args, **kwargs)
|
|
108
|
+
if response.status_code > 200 or "errors" in response.text:
|
|
109
|
+
LOGGER.debug(
|
|
110
|
+
f"{response.request.method} {response.request.url} "
|
|
111
|
+
f"{response.status_code} f{response.text}"
|
|
112
|
+
)
|
|
113
|
+
if response.status_code >= 500:
|
|
114
|
+
last_exception = ResponseError(response.status_code, response.text)
|
|
115
|
+
self._base_urls.reverse()
|
|
116
|
+
continue
|
|
117
|
+
if response.status_code >= 400:
|
|
118
|
+
last_exception = LoginError(response.text)
|
|
119
|
+
break
|
|
120
|
+
if response.status_code == 200:
|
|
121
|
+
if "SYS_00004" in response.text:
|
|
122
|
+
self._base_urls.reverse()
|
|
123
|
+
continue
|
|
124
|
+
return response
|
|
125
|
+
|
|
126
|
+
except requests.exceptions.RequestException as ex:
|
|
127
|
+
LOGGER.warning(f"Unexpected error on '{base_url}{url}' ({ex=})")
|
|
128
|
+
last_exception = RequestError(str(ex))
|
|
129
|
+
self._base_urls.reverse()
|
|
130
|
+
raise last_exception
|
|
131
|
+
return wrapper
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def login(self):
|
|
135
|
+
""" Login to verisure app api
|
|
136
|
+
Login before calling any read or write commands
|
|
137
|
+
Return installations
|
|
138
|
+
"""
|
|
139
|
+
|
|
140
|
+
response = self._post(
|
|
141
|
+
"/auth/login",
|
|
142
|
+
headers={'APPLICATION_ID': 'PS_PYTHON'},
|
|
143
|
+
auth=(self._username, self._password))
|
|
144
|
+
|
|
145
|
+
if "stepUpToken" in response.text:
|
|
146
|
+
raise LoginError("Multifactor authentication enabled, "
|
|
147
|
+
"disable or create MFA cookie")
|
|
148
|
+
|
|
149
|
+
self._cookies = response.cookies
|
|
150
|
+
with open(self._cookie_file_name, 'wb') as f:
|
|
151
|
+
pickle.dump(self._cookies, f)
|
|
152
|
+
|
|
153
|
+
installations = self.get_installations()
|
|
154
|
+
if 'errors' not in installations:
|
|
155
|
+
return installations
|
|
156
|
+
|
|
157
|
+
raise LoginError("Failed to log in")
|
|
158
|
+
|
|
159
|
+
def request_mfa(self):
|
|
160
|
+
""" Request MFA verification code """
|
|
161
|
+
|
|
162
|
+
response = self._post(
|
|
163
|
+
url="/auth/login",
|
|
164
|
+
headers={'APPLICATION_ID': 'PS_PYTHON'},
|
|
165
|
+
auth=(self._username, self._password))
|
|
166
|
+
|
|
167
|
+
if "stepUpToken" not in response.text:
|
|
168
|
+
raise LoginError("Multifactor authentication disabled, "
|
|
169
|
+
"use regular login instead")
|
|
170
|
+
|
|
171
|
+
self._cookies = response.cookies
|
|
172
|
+
for mfa_type in ['phone', 'email']:
|
|
173
|
+
try:
|
|
174
|
+
mfa_response = self._post(
|
|
175
|
+
url=f"/auth/mfa?type={mfa_type}",
|
|
176
|
+
headers={'APPLICATION_ID': 'PS_PYTHON'},
|
|
177
|
+
cookies=self._cookies)
|
|
178
|
+
if mfa_response.status_code == 200:
|
|
179
|
+
return
|
|
180
|
+
except Exception as ex:
|
|
181
|
+
raise LoginError("Failed to request MFA type") from ex
|
|
182
|
+
|
|
183
|
+
raise LoginError("Failed to log in")
|
|
184
|
+
|
|
185
|
+
def validate_mfa(self, code):
|
|
186
|
+
""" Validate mfa request
|
|
187
|
+
Return installations
|
|
188
|
+
"""
|
|
189
|
+
|
|
190
|
+
response = self._post(
|
|
191
|
+
url="/auth/mfa/validate",
|
|
192
|
+
headers={
|
|
193
|
+
'APPLICATION_ID': 'PS_PYTHON',
|
|
194
|
+
'Accept': 'application/json',
|
|
195
|
+
'Content-Type': 'application/json'},
|
|
196
|
+
cookies=self._cookies,
|
|
197
|
+
data=json.dumps({"token": code}))
|
|
198
|
+
self._cookies = response.cookies
|
|
199
|
+
|
|
200
|
+
trust_response = self._post(
|
|
201
|
+
url="/auth/trust",
|
|
202
|
+
headers={
|
|
203
|
+
'APPLICATION_ID': 'PS_PYTHON',
|
|
204
|
+
'Accept': 'application/json',
|
|
205
|
+
},
|
|
206
|
+
cookies=self._cookies)
|
|
207
|
+
self._cookies.update(trust_response.cookies)
|
|
208
|
+
with open(self._cookie_file_name, 'wb') as cookie_file:
|
|
209
|
+
pickle.dump(self._cookies, cookie_file)
|
|
210
|
+
self._trust_token = trust_response.json()
|
|
211
|
+
|
|
212
|
+
installations = self.get_installations()
|
|
213
|
+
if 'errors' not in installations:
|
|
214
|
+
return installations
|
|
215
|
+
|
|
216
|
+
raise LoginError("Failed to log in")
|
|
217
|
+
|
|
218
|
+
def _load_cookie_file_into_memory(self):
|
|
219
|
+
"""Populate ``_cookies`` from the persisted pickle (used by cookie login paths)."""
|
|
220
|
+
|
|
221
|
+
try:
|
|
222
|
+
with open(self._cookie_file_name, 'rb') as cookie_file:
|
|
223
|
+
self._cookies = pickle.load(cookie_file)
|
|
224
|
+
except OSError as ex:
|
|
225
|
+
raise LoginError("Failed to read cookie") from ex
|
|
226
|
+
except (EOFError, pickle.UnpicklingError, AttributeError, TypeError, ValueError) as ex:
|
|
227
|
+
raise LoginError("Failed to read cookie") from ex
|
|
228
|
+
|
|
229
|
+
def login_cookie(self):
|
|
230
|
+
""" Login using cookie
|
|
231
|
+
Return installations
|
|
232
|
+
"""
|
|
233
|
+
|
|
234
|
+
self._load_cookie_file_into_memory()
|
|
235
|
+
|
|
236
|
+
# Login
|
|
237
|
+
cookie_jar = requests.sessions.RequestsCookieJar()
|
|
238
|
+
for name, value in self._cookies.items():
|
|
239
|
+
if 'vs-trust' in name:
|
|
240
|
+
cookie_jar.set(name, value)
|
|
241
|
+
response = self._post(
|
|
242
|
+
url="/auth/login",
|
|
243
|
+
headers={'APPLICATION_ID': 'PS_PYTHON'},
|
|
244
|
+
auth=(self._username, self._password),
|
|
245
|
+
cookies=cookie_jar)
|
|
246
|
+
self._cookies.update(response.cookies)
|
|
247
|
+
with open(self._cookie_file_name, 'wb') as f:
|
|
248
|
+
pickle.dump(self._cookies, f)
|
|
249
|
+
|
|
250
|
+
installations = self.get_installations()
|
|
251
|
+
if 'errors' not in installations:
|
|
252
|
+
return installations
|
|
253
|
+
|
|
254
|
+
raise LoginError("Failed to log in")
|
|
255
|
+
|
|
256
|
+
def update_cookie(self):
|
|
257
|
+
""" Update expired cookie
|
|
258
|
+
Cookie can last 15 minutes before it needs to be updated.
|
|
259
|
+
|
|
260
|
+
Long-running callers may reset ``self._cookies`` while a valid pickle remains
|
|
261
|
+
on disk; hydrate from the file before calling ``/auth/token`` so the request
|
|
262
|
+
is not sent with an empty cookie jar.
|
|
263
|
+
"""
|
|
264
|
+
if self._cookies is None:
|
|
265
|
+
self._load_cookie_file_into_memory()
|
|
266
|
+
|
|
267
|
+
cookie_jar = requests.sessions.RequestsCookieJar()
|
|
268
|
+
if self._cookies is not None:
|
|
269
|
+
for name, value in self._cookies.items():
|
|
270
|
+
if name in ['vid', 'vs-refresh']:
|
|
271
|
+
cookie_jar[name] = value
|
|
272
|
+
response = self._get(
|
|
273
|
+
url="/auth/token",
|
|
274
|
+
headers={'APPLICATION_ID': 'PS_PYTHON'},
|
|
275
|
+
cookies=cookie_jar)
|
|
276
|
+
|
|
277
|
+
self._cookies.update(response.cookies)
|
|
278
|
+
with open(self._cookie_file_name, 'wb') as cookie_file:
|
|
279
|
+
pickle.dump(self._cookies, cookie_file)
|
|
280
|
+
LOGGER.debug(f"Saved cookies: {[cookie for cookie in self._cookies.keys()]}")
|
|
281
|
+
|
|
282
|
+
def logout(self):
|
|
283
|
+
""" Log out from the verisure app api """
|
|
284
|
+
try:
|
|
285
|
+
if self._trust_token is not None:
|
|
286
|
+
token = self._trust_token['trustTokenValue']
|
|
287
|
+
self._delete(
|
|
288
|
+
url=f"/auth/trust/{token}",
|
|
289
|
+
headers={
|
|
290
|
+
'APPLICATION_ID': 'PS_PYTHON',
|
|
291
|
+
'Accept': 'application/json',
|
|
292
|
+
},
|
|
293
|
+
cookies=self._cookies)
|
|
294
|
+
self._delete(
|
|
295
|
+
url="/auth/logout",
|
|
296
|
+
headers={'APPLICATION_ID': 'PS_PYTHON'},
|
|
297
|
+
cookies=self._cookies)
|
|
298
|
+
finally:
|
|
299
|
+
self._base_url = None
|
|
300
|
+
self._giid = None
|
|
301
|
+
self._cookies = None
|
|
302
|
+
self._trust_token = None
|
|
303
|
+
if os.path.exists(self._cookie_file_name):
|
|
304
|
+
os.remove(self._cookie_file_name)
|
|
305
|
+
|
|
306
|
+
def request(self, *operations):
|
|
307
|
+
"""Request operations"""
|
|
308
|
+
if not operations:
|
|
309
|
+
# Return empty json if no operations were requested
|
|
310
|
+
return json.loads("{}")
|
|
311
|
+
response = self._post(
|
|
312
|
+
'/graphql',
|
|
313
|
+
headers={
|
|
314
|
+
'APPLICATION_ID': 'PS_PYTHON',
|
|
315
|
+
'Accept': 'application/json'},
|
|
316
|
+
cookies=self._cookies,
|
|
317
|
+
data=json.dumps(list(operations)))
|
|
318
|
+
return json.loads(response.text)
|
|
319
|
+
|
|
320
|
+
def get_installations(self):
|
|
321
|
+
""" Get information about installations """
|
|
322
|
+
return self.request(self.fetch_all_installations())
|
|
323
|
+
|
|
324
|
+
def set_giid(self, giid):
|
|
325
|
+
""" Set installation giid
|
|
326
|
+
|
|
327
|
+
Args:
|
|
328
|
+
giid (str): Installation identifier
|
|
329
|
+
"""
|
|
330
|
+
self._giid = giid
|
|
331
|
+
LOGGER.info(f"Installation identifier set ({giid=})")
|
|
332
|
+
|
|
333
|
+
@query_func
|
|
334
|
+
def arm_away(self,
|
|
335
|
+
code: VariableTypes.Code,
|
|
336
|
+
giid: VariableTypes.Giid=None):
|
|
337
|
+
"""Set arm status away"""
|
|
338
|
+
assert giid or self._giid, "Set default giid or pass explicit"
|
|
339
|
+
return {
|
|
340
|
+
"operationName": "armAway",
|
|
341
|
+
"variables": {
|
|
342
|
+
"giid": giid or self._giid,
|
|
343
|
+
"code": code},
|
|
344
|
+
"query": "mutation armAway($giid: String!, $code: String!) {\n armStateArmAway(giid: $giid, code: $code)\n}\n", # noqa: E501
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
@query_func
|
|
348
|
+
def arm_home(self,
|
|
349
|
+
code: VariableTypes.Code,
|
|
350
|
+
giid: VariableTypes.Giid=None):
|
|
351
|
+
"""Set arm state home"""
|
|
352
|
+
assert giid or self._giid, "Set default giid or pass explicit"
|
|
353
|
+
return {
|
|
354
|
+
"operationName": "armHome",
|
|
355
|
+
"variables": {
|
|
356
|
+
"giid": giid or self._giid,
|
|
357
|
+
"code": code},
|
|
358
|
+
"query": "mutation armHome($giid: String!, $code: String!) {\n armStateArmHome(giid: $giid, code: $code)\n}\n", # noqa: E501
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
@query_func
|
|
362
|
+
def arm_state(self,
|
|
363
|
+
giid: VariableTypes.Giid=None):
|
|
364
|
+
"""Read arm state"""
|
|
365
|
+
assert giid or self._giid, "Set default giid or pass explicit"
|
|
366
|
+
return {
|
|
367
|
+
"operationName": "ArmState",
|
|
368
|
+
"variables": {
|
|
369
|
+
"giid": giid or self._giid},
|
|
370
|
+
"query": "query ArmState($giid: String!) {\n installation(giid: $giid) {\n armState {\n type\n statusType\n date\n name\n changedVia\n __typename\n }\n __typename\n }\n}\n", # noqa: E501
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
@query_func
|
|
374
|
+
def broadband(self,
|
|
375
|
+
giid: VariableTypes.Giid=None):
|
|
376
|
+
"""Get broadband status"""
|
|
377
|
+
assert giid or self._giid, "Set default giid or pass explicit"
|
|
378
|
+
return {
|
|
379
|
+
"operationName": "Broadband",
|
|
380
|
+
"variables": {
|
|
381
|
+
"giid": giid or self._giid},
|
|
382
|
+
"query": "query Broadband($giid: String!) {\n installation(giid: $giid) {\n broadband {\n testDate\n isBroadbandConnected\n __typename\n }\n __typename\n }\n}\n", # noqa: E501
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
@query_func
|
|
386
|
+
def capability(self,
|
|
387
|
+
giid: VariableTypes.Giid=None):
|
|
388
|
+
"""Get capability"""
|
|
389
|
+
assert giid or self._giid, "Set default giid or pass explicit"
|
|
390
|
+
return {
|
|
391
|
+
"operationName": "Capability",
|
|
392
|
+
"variables": {
|
|
393
|
+
"giid": giid or self._giid},
|
|
394
|
+
"query": "query Capability($giid: String!) {\n installation(giid: $giid) {\n capability {\n current\n gained {\n capability\n __typename\n }\n __typename\n }\n __typename\n }\n}\n", # noqa: E501
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
@query_func
|
|
398
|
+
def charge_sms(self,
|
|
399
|
+
giid: VariableTypes.Giid=None):
|
|
400
|
+
"""Charge SMS"""
|
|
401
|
+
assert giid or self._giid, "Set default giid or pass explicit"
|
|
402
|
+
return {
|
|
403
|
+
"operationName": "ChargeSms",
|
|
404
|
+
"variables": {
|
|
405
|
+
"giid": giid or self._giid},
|
|
406
|
+
"query": "query ChargeSms($giid: String!) {\n installation(giid: $giid) {\n chargeSms {\n chargeSmartPlugOnOff\n chargeLockUnlock\n chargeArmDisarm\n chargeNotifications\n __typename\n }\n __typename\n }\n}\n", # noqa: E501
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
@query_func
|
|
410
|
+
def climate(self,
|
|
411
|
+
giid: VariableTypes.Giid=None):
|
|
412
|
+
"""Get climate"""
|
|
413
|
+
assert giid or self._giid, "Set default giid or pass explicit"
|
|
414
|
+
return {
|
|
415
|
+
"operationName": "Climate",
|
|
416
|
+
"variables": {
|
|
417
|
+
"giid": giid or self._giid},
|
|
418
|
+
"query": "query Climate($giid: String!) {\n installation(giid: $giid) {\n climates {\n device {\n deviceLabel\n area\n gui {\n label\n __typename\n }\n __typename\n }\n humidityEnabled\n humidityTimestamp\n humidityValue\n temperatureTimestamp\n temperatureValue\n thresholds {\n aboveMaxAlert\n belowMinAlert\n sensorType\n __typename\n }\n __typename\n }\n __typename\n }\n}\n", # noqa: E501
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
@query_func
|
|
422
|
+
def disarm(self,
|
|
423
|
+
code: VariableTypes.Code,
|
|
424
|
+
giid: VariableTypes.Giid=None):
|
|
425
|
+
"""Disarm alarm"""
|
|
426
|
+
assert giid or self._giid, "Set default giid or pass explicit"
|
|
427
|
+
return {
|
|
428
|
+
"operationName": "disarm",
|
|
429
|
+
"variables": {
|
|
430
|
+
"giid": giid or self._giid,
|
|
431
|
+
"code": code},
|
|
432
|
+
"query": "mutation disarm($giid: String!, $code: String!) {\n armStateDisarm(giid: $giid, code: $code)\n}\n", # noqa: E501
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
@query_func
|
|
436
|
+
def door_lock(self,
|
|
437
|
+
device_label: VariableTypes.DeviceLabel,
|
|
438
|
+
code: VariableTypes.Code,
|
|
439
|
+
giid: VariableTypes.Giid=None):
|
|
440
|
+
"""Lock door"""
|
|
441
|
+
assert giid or self._giid, "Set default giid or pass explicit"
|
|
442
|
+
return {
|
|
443
|
+
"operationName": "DoorLock",
|
|
444
|
+
"variables": {
|
|
445
|
+
"giid": giid or self._giid,
|
|
446
|
+
"deviceLabel": device_label,
|
|
447
|
+
"input": {
|
|
448
|
+
"code": code,
|
|
449
|
+
},
|
|
450
|
+
},
|
|
451
|
+
"query": "mutation DoorLock($giid: String!, $deviceLabel: String!, $input: LockDoorInput!) {\n DoorLock(giid: $giid, deviceLabel: $deviceLabel, input: $input)\n}\n", # noqa: E501
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
@query_func
|
|
455
|
+
def door_lock_configuration(self,
|
|
456
|
+
device_label: VariableTypes.DeviceLabel,
|
|
457
|
+
giid: VariableTypes.Giid=None):
|
|
458
|
+
"""Get door lock configuration"""
|
|
459
|
+
assert giid or self._giid, "Set default giid or pass explicit"
|
|
460
|
+
return {
|
|
461
|
+
"operationName": "DoorLockConfiguration",
|
|
462
|
+
"variables": {
|
|
463
|
+
"giid": giid or self._giid,
|
|
464
|
+
"deviceLabel": device_label},
|
|
465
|
+
"query": "query DoorLockConfiguration($giid: String!, $deviceLabel: String!) {\n installation(giid: $giid) {\n smartLocks(filter: {deviceLabels: [$deviceLabel]}) {\n device {\n area\n deviceLabel\n __typename\n }\n configuration {\n ... on YaleLockConfiguration {\n autoLockEnabled\n voiceLevel\n volume\n __typename\n }\n ... on DanaLockConfiguration {\n holdBackLatchDuration\n twistAssistEnabled\n __typename\n }\n __typename\n }\n __typename\n }\n __typename\n }\n}\n", # noqa: E501
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
@query_func
|
|
469
|
+
def set_autolock_enabled(self,
|
|
470
|
+
device_label: VariableTypes.DeviceLabel,
|
|
471
|
+
auto_lock_enabled: bool,
|
|
472
|
+
giid: VariableTypes.Giid=None):
|
|
473
|
+
"""Enable or disable autolock"""
|
|
474
|
+
assert giid or self._giid, "Set default giid or pass explicit"
|
|
475
|
+
return {
|
|
476
|
+
"operationName": "DoorLockUpdateConfig",
|
|
477
|
+
"variables": {
|
|
478
|
+
"giid": giid or self._giid,
|
|
479
|
+
"deviceLabel": device_label,
|
|
480
|
+
"input": {
|
|
481
|
+
"autoLockEnabled": auto_lock_enabled
|
|
482
|
+
}
|
|
483
|
+
},
|
|
484
|
+
"query": "mutation DoorLockUpdateConfig($giid: String!, $deviceLabel: String!, $input: DoorLockUpdateConfigInput!) {\n DoorLockUpdateConfig(giid: $giid, deviceLabel: $deviceLabel, input: $input)\n}\n", # noqa: E501
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
@query_func
|
|
488
|
+
def door_unlock(self,
|
|
489
|
+
device_label: VariableTypes.DeviceLabel,
|
|
490
|
+
code: VariableTypes.Code,
|
|
491
|
+
giid: VariableTypes.Giid=None):
|
|
492
|
+
"""Unlock door"""
|
|
493
|
+
assert giid or self._giid, "Set default giid or pass explicit"
|
|
494
|
+
return {
|
|
495
|
+
"operationName": "DoorUnlock",
|
|
496
|
+
"variables": {
|
|
497
|
+
"giid": giid or self._giid,
|
|
498
|
+
"deviceLabel": device_label,
|
|
499
|
+
"input": {
|
|
500
|
+
"code": code,
|
|
501
|
+
},
|
|
502
|
+
},
|
|
503
|
+
"query": "mutation DoorUnlock($giid: String!, $deviceLabel: String!, $input: LockDoorInput!) {\n DoorUnlock(giid: $giid, deviceLabel: $deviceLabel, input: $input)\n}\n", # noqa: E501
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
@query_func
|
|
507
|
+
def door_window(self,
|
|
508
|
+
giid: VariableTypes.Giid=None):
|
|
509
|
+
"""Read status of door and window sensors"""
|
|
510
|
+
assert giid or self._giid, "Set default giid or pass explicit"
|
|
511
|
+
return {
|
|
512
|
+
"operationName": "DoorWindow",
|
|
513
|
+
"variables": {
|
|
514
|
+
"giid": giid or self._giid},
|
|
515
|
+
"query": "query DoorWindow($giid: String!) {\n installation(giid: $giid) {\n doorWindows {\n device {\n deviceLabel\n __typename\n }\n type\n area\n state\n wired\n reportTime\n __typename\n }\n __typename\n }\n}\n", # noqa: E501
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
@query_func
|
|
519
|
+
def event_log(self,
|
|
520
|
+
giid: VariableTypes.Giid=None):
|
|
521
|
+
"""Read event log"""
|
|
522
|
+
assert giid or self._giid, "Set default giid or pass explicit"
|
|
523
|
+
return {
|
|
524
|
+
"operationName": "EventLog",
|
|
525
|
+
"variables": {
|
|
526
|
+
"giid": giid or self._giid,
|
|
527
|
+
"offset": 0,
|
|
528
|
+
"pagesize": 15,
|
|
529
|
+
"eventCategories": ["INTRUSION", "FIRE", "SOS", "WATER", "ANIMAL", "TECHNICAL", "WARNING", "ARM", "DISARM", "LOCK", "UNLOCK", "PICTURE", "CLIMATE", "CAMERA_SETTINGS"], # noqa: E501
|
|
530
|
+
"eventContactIds": [],
|
|
531
|
+
"eventDeviceLabels": [],
|
|
532
|
+
"fromDate": None,
|
|
533
|
+
"toDate": None
|
|
534
|
+
},
|
|
535
|
+
"query": "query EventLog($giid: String!, $offset: Int!, $pagesize: Int!, $eventCategories: [String], $fromDate: String, $toDate: String, $eventContactIds: [String], $eventDeviceLabels: [String]) {\n installation(giid: $giid) {\n eventLog(offset: $offset, pagesize: $pagesize, eventCategories: $eventCategories, eventContactIds: $eventContactIds, eventDeviceLabels: $eventDeviceLabels, fromDate: $fromDate, toDate: $toDate) {\n moreDataAvailable\n pagedList {\n device {\n deviceLabel\n area\n gui {\n label\n __typename\n }\n __typename\n }\n arloDevice {\n name\n __typename\n }\n gatewayArea\n eventType\n eventCategory\n eventSource\n eventId\n eventTime\n userName\n armState\n userType\n climateValue\n sensorType\n eventCount\n __typename\n }\n __typename\n }\n __typename\n }\n}\n", # noqa: E501
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
@query_func
|
|
539
|
+
def fetch_all_installations(self):
|
|
540
|
+
"""Fetch installations"""
|
|
541
|
+
return {
|
|
542
|
+
"operationName": "fetchAllInstallations",
|
|
543
|
+
"variables": {
|
|
544
|
+
"email": self._username},
|
|
545
|
+
"query": "query fetchAllInstallations($email: String!){\n account(email: $email) {\n installations {\n giid\n alias\n customerType\n dealerId\n subsidiary\n pinCodeLength\n locale\n address {\n street\n city\n postalNumber\n __typename\n }\n __typename\n }\n __typename\n }\n}\n", # noqa: E501
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
@query_func
|
|
549
|
+
def firmware(self,
|
|
550
|
+
giid: VariableTypes.Giid=None):
|
|
551
|
+
"""Get firmware information"""
|
|
552
|
+
assert giid or self._giid, "Set default giid or pass explicit"
|
|
553
|
+
return {
|
|
554
|
+
"operationName": "Firmware",
|
|
555
|
+
"variables": {
|
|
556
|
+
"giid": giid or self._giid
|
|
557
|
+
},
|
|
558
|
+
"query": "query Firmware($giid: String!) {\n installation(giid: $giid) {\n firmware {\n status {\n latestFirmware\n requestedFirmware\n upgradeable\n status\n gateways {\n reportedRunningFirmware\n deviceLabel\n status\n __typename\n }\n __typename\n }\n __typename\n }\n __typename\n }\n}\n" # noqa: E501
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
@query_func
|
|
562
|
+
def guardian_sos(self):
|
|
563
|
+
"""Guardian SOS"""
|
|
564
|
+
return {
|
|
565
|
+
"operationName": "GuardianSos",
|
|
566
|
+
"variables": {},
|
|
567
|
+
"query": "query GuardianSos {\n guardianSos {\n serverTime\n sos {\n fullName\n phone\n deviceId\n deviceName\n giid\n type\n username\n expireDate\n warnBeforeExpireDate\n contactId\n __typename\n }\n __typename\n }\n}\n", # noqa: E501
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
@query_func
|
|
571
|
+
def is_guardian_activated(self,
|
|
572
|
+
giid: VariableTypes.Giid=None):
|
|
573
|
+
"""Is guardian activated"""
|
|
574
|
+
assert giid or self._giid, "Set default giid or pass explicit"
|
|
575
|
+
return {
|
|
576
|
+
"operationName": "IsGuardianActivated",
|
|
577
|
+
"variables": {
|
|
578
|
+
"giid": giid or self._giid,
|
|
579
|
+
"featureName": "GUARDIAN"},
|
|
580
|
+
"query": "query IsGuardianActivated($giid: String!, $featureName: String!) {\n installation(giid: $giid) {\n activatedFeature {\n isFeatureActivated(featureName: $featureName)\n __typename\n }\n __typename\n }\n}\n", # noqa: E501
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
@query_func
|
|
584
|
+
def permissions(self,
|
|
585
|
+
giid: VariableTypes.Giid=None):
|
|
586
|
+
"""Permissions"""
|
|
587
|
+
assert giid or self._giid, "Set default giid or pass explicit"
|
|
588
|
+
return {
|
|
589
|
+
"operationName": "Permissions",
|
|
590
|
+
"variables": {
|
|
591
|
+
"giid": giid or self._giid,
|
|
592
|
+
"email": self._username},
|
|
593
|
+
"query": "query Permissions($giid: String!, $email: String!) {\n permissions(giid: $giid, email: $email) {\n accountPermissionsHash\n name\n __typename\n }\n}\n", # noqa: E501
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
@query_func
|
|
597
|
+
def poll_arm_state(self,
|
|
598
|
+
transaction_id: VariableTypes.TransactionId,
|
|
599
|
+
future_state: VariableTypes.ArmFutureState,
|
|
600
|
+
giid: VariableTypes.Giid=None):
|
|
601
|
+
"""Poll arm state"""
|
|
602
|
+
assert giid or self._giid, "Set default giid or pass explicit"
|
|
603
|
+
return {
|
|
604
|
+
"operationName": "pollArmState",
|
|
605
|
+
"variables": {
|
|
606
|
+
"giid": giid or self._giid,
|
|
607
|
+
"transactionId": transaction_id,
|
|
608
|
+
"futureState": future_state},
|
|
609
|
+
"query": "query pollArmState($giid: String!, $transactionId: String, $futureState: ArmStateStatusTypes!) {\n installation(giid: $giid) {\n armStateChangePollResult(transactionId: $transactionId, futureState: $futureState) {\n result\n createTime\n __typename\n }\n __typename\n }\n}\n", # noqa: E501
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
@query_func
|
|
613
|
+
def poll_lock_state(self,
|
|
614
|
+
transaction_id: VariableTypes.TransactionId,
|
|
615
|
+
device_label: VariableTypes.DeviceLabel,
|
|
616
|
+
future_state: VariableTypes.LockFutureState,
|
|
617
|
+
giid: VariableTypes.Giid=None):
|
|
618
|
+
"""Poll lock state"""
|
|
619
|
+
assert giid or self._giid, "Set default giid or pass explicit"
|
|
620
|
+
return {
|
|
621
|
+
"operationName": "pollLockState",
|
|
622
|
+
"variables": {
|
|
623
|
+
"giid": giid or self._giid,
|
|
624
|
+
"transactionId": transaction_id,
|
|
625
|
+
"deviceLabel": device_label,
|
|
626
|
+
"futureState": future_state},
|
|
627
|
+
"query": "query pollLockState($giid: String!, $transactionId: String, $deviceLabel: String!, $futureState: DoorLockState!) {\n installation(giid: $giid) {\n doorLockStateChangePollResult(transactionId: $transactionId, deviceLabel: $deviceLabel, futureState: $futureState) {\n result\n createTime\n __typename\n }\n __typename\n }\n}\n", # noqa: E501
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
@query_func
|
|
631
|
+
def remaining_sms(self,
|
|
632
|
+
giid: VariableTypes.Giid=None):
|
|
633
|
+
"""Get remaing number of SMS"""
|
|
634
|
+
assert giid or self._giid, "Set default giid or pass explicit"
|
|
635
|
+
return {
|
|
636
|
+
"operationName": "RemainingSms",
|
|
637
|
+
"variables": {
|
|
638
|
+
"giid": giid or self._giid},
|
|
639
|
+
"query": "query RemainingSms($giid: String!) {\n installation(giid: $giid) {\n remainingSms\n __typename\n }\n}\n", # noqa: E501
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
@query_func
|
|
643
|
+
def smart_button(self,
|
|
644
|
+
giid: VariableTypes.Giid=None):
|
|
645
|
+
"""Get smart button state"""
|
|
646
|
+
assert giid or self._giid, "Set default giid or pass explicit"
|
|
647
|
+
return {
|
|
648
|
+
"operationName": "SmartButton",
|
|
649
|
+
"variables": {
|
|
650
|
+
"giid": giid or self._giid},
|
|
651
|
+
"query": "query SmartButton($giid: String!) {\n installation(giid: $giid) {\n smartButton {\n entries {\n smartButtonId\n icon\n label\n color\n active\n action {\n actionType\n expectedState\n target {\n ... on Installation {\n alias\n __typename\n }\n ... on Device {\n deviceLabel\n area\n gui {\n label\n __typename\n }\n featureStatuses(type: \"SmartPlug\") {\n device {\n deviceLabel\n __typename\n }\n ... on SmartPlug {\n icon\n isHazardous\n __typename\n }\n __typename\n }\n __typename\n }\n __typename\n }\n __typename\n }\n __typename\n }\n __typename\n }\n __typename\n }\n}\n", # noqa: E501
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
@query_func
|
|
655
|
+
def smart_lock(self,
|
|
656
|
+
giid: VariableTypes.Giid=None):
|
|
657
|
+
"""Get smart lock state"""
|
|
658
|
+
assert giid or self._giid, "Set default giid or pass explicit"
|
|
659
|
+
return {
|
|
660
|
+
"operationName": "SmartLock",
|
|
661
|
+
"variables": {
|
|
662
|
+
"giid": giid or self._giid},
|
|
663
|
+
"query": "query SmartLock($giid: String!) {\n installation(giid: $giid) {\n smartLocks {\n lockStatus\n doorState\n lockMethod\n eventTime\n doorLockType\n secureMode\n device {\n deviceLabel\n area\n __typename\n }\n user {\n name\n __typename\n }\n __typename\n }\n __typename\n }\n}\n", # noqa: E501
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
@query_func
|
|
667
|
+
def set_smartplug(self,
|
|
668
|
+
device_label: VariableTypes.DeviceLabel,
|
|
669
|
+
state: bool,
|
|
670
|
+
giid: VariableTypes.Giid=None):
|
|
671
|
+
"""Set state of smart plug"""
|
|
672
|
+
assert giid or self._giid, "Set default giid or pass explicit"
|
|
673
|
+
return {
|
|
674
|
+
"operationName": "UpdateState",
|
|
675
|
+
"variables": {
|
|
676
|
+
"giid": giid or self._giid,
|
|
677
|
+
"deviceLabel": device_label,
|
|
678
|
+
"state": state},
|
|
679
|
+
"query": "mutation UpdateState($giid: String!, $deviceLabel: String!, $state: Boolean!) {\n SmartPlugSetState(giid: $giid, input: [{deviceLabel: $deviceLabel, state: $state}])}", # noqa: E501
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
@query_func
|
|
683
|
+
def smartplug(self,
|
|
684
|
+
device_label: VariableTypes.DeviceLabel,
|
|
685
|
+
giid: VariableTypes.Giid=None):
|
|
686
|
+
"""Read status of a single smart plug"""
|
|
687
|
+
assert giid or self._giid, "Set default giid or pass explicit"
|
|
688
|
+
return {
|
|
689
|
+
"operationName": "SmartPlug",
|
|
690
|
+
"variables": {
|
|
691
|
+
"giid": giid or self._giid,
|
|
692
|
+
"deviceLabel": device_label},
|
|
693
|
+
"query": "query SmartPlug($giid: String!, $deviceLabel: String!) {\n installation(giid: $giid) {\n smartplugs(filter: {deviceLabels: [$deviceLabel]}) {\n device {\n deviceLabel\n area\n __typename\n }\n currentState\n icon\n isHazardous\n __typename\n }\n __typename\n }\n}\n", # noqa: E501
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
@query_func
|
|
697
|
+
def smartplugs(self,
|
|
698
|
+
giid: VariableTypes.Giid=None):
|
|
699
|
+
"""Read status of all smart plugs"""
|
|
700
|
+
assert giid or self._giid, "Set default giid or pass explicit"
|
|
701
|
+
return {
|
|
702
|
+
"operationName": "SmartPlug",
|
|
703
|
+
"variables": {
|
|
704
|
+
"giid": giid or self._giid},
|
|
705
|
+
"query": "query SmartPlug($giid: String!) {\n installation(giid: $giid) {\n smartplugs {\n device {\n deviceLabel\n area\n __typename\n }\n currentState\n icon\n isHazardous\n __typename\n }\n __typename\n }\n}\n", # noqa: E501
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
@query_func
|
|
709
|
+
def user_trackings(self,
|
|
710
|
+
giid: VariableTypes.Giid=None):
|
|
711
|
+
"""Read user tracking status"""
|
|
712
|
+
assert giid or self._giid, "Set default giid or pass explicit"
|
|
713
|
+
return {
|
|
714
|
+
"operationName": "userTrackings",
|
|
715
|
+
"variables": {
|
|
716
|
+
"giid": giid or self._giid},
|
|
717
|
+
"query": "query userTrackings($giid: String!) {\n installation(giid: $giid) {\n userTrackings {\n isCallingUser\n webAccount\n status\n xbnContactId\n currentLocationName\n deviceId\n name\n initials\n currentLocationTimestamp\n deviceName\n currentLocationId\n __typename\n }\n __typename\n }\n}\n", # noqa: E501
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
@query_func
|
|
721
|
+
def cameras(self,
|
|
722
|
+
giid: VariableTypes.Giid=None):
|
|
723
|
+
"""Get cameras state"""
|
|
724
|
+
assert giid or self._giid, "Set default giid or pass explicit"
|
|
725
|
+
return {
|
|
726
|
+
"operationName": "Camera",
|
|
727
|
+
"variables": {
|
|
728
|
+
"all": True,
|
|
729
|
+
"giid": giid or self._giid},
|
|
730
|
+
"query": "query Camera($giid: String!, $all: Boolean!) {\n installation(giid: $giid) {\n cameras(allCameras: $all) {\n visibleOnCard\n initiallyConfigured\n imageCaptureAllowed\n imageCaptureAllowedByArmstate\n device {\n deviceLabel\n area\n __typename\n }\n latestCameraSeries {\n image {\n imageId\n imageStatus\n captureTime\n url\n }\n }\n }\n }\n}", # noqa: E501
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
@query_func
|
|
734
|
+
def cameras_last_image(self,
|
|
735
|
+
giid: VariableTypes.Giid=None):
|
|
736
|
+
"""Get cameras last image"""
|
|
737
|
+
assert giid or self._giid, "Set default giid or pass explicit"
|
|
738
|
+
return {
|
|
739
|
+
"variables": {
|
|
740
|
+
"giid": giid or self._giid},
|
|
741
|
+
"query": "query queryCaptureImageRequestStatus($giid: String!) {\n installation(giid: $giid) {\n cameraContentProvider {\n latestImage {\n deviceLabel\n mediaId\n contentType\n contentUrl\n timestamp\n duration\n thumbnailUrl\n bitRate\n width\n height\n codec\n }\n }\n }\n}", # noqa: E501
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
@query_func
|
|
745
|
+
def cameras_image_series(self,
|
|
746
|
+
limit=50,
|
|
747
|
+
offset=0,
|
|
748
|
+
giid: VariableTypes.Giid=None):
|
|
749
|
+
"""Get the cameras image series"""
|
|
750
|
+
assert giid or self._giid, "Set default giid or pass explicit"
|
|
751
|
+
return {
|
|
752
|
+
"operationName": "GQL_CCCP_SearchMedia",
|
|
753
|
+
"variables": {
|
|
754
|
+
"giid": giid or self._giid,
|
|
755
|
+
"limit": limit,
|
|
756
|
+
"offset": offset},
|
|
757
|
+
"query": "mutation GQL_CCCP_SearchMedia(\n $giid: BigInt!\n $offset: Int\n $limit: Int\n $fromDate: Date\n $toDate: Date) {\n\n ContentProviderMediaSearch(\n giid: $giid\n offset: $offset\n limit: $limit\n fromDate: $fromDate\n toDate: $toDate\n ) {\n totalNumberOfMediaSeries\n mediaSeriesList {\n seriesId\n storageType\n viewed\n timestamp\n deviceMediaList {\n contentUrl\n mediaAvailable\n deviceLabel\n mediaId\n contentType\n timestamp\n requestTimestamp\n duration\n expiryDate\n viewed\n thumbnailUrl\n bitRate\n width\n height\n codec\n }\n }\n }\n}", # noqa: E501}
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
@query_func
|
|
761
|
+
def camera_get_request_id(self,
|
|
762
|
+
device_label: VariableTypes.DeviceLabel,
|
|
763
|
+
giid: VariableTypes.Giid=None):
|
|
764
|
+
"""Get requestId for camera_capture"""
|
|
765
|
+
assert giid or self._giid, "Set default giid or pass explicit"
|
|
766
|
+
return {
|
|
767
|
+
"variables": {
|
|
768
|
+
"deviceIdentifier": "RandomString",
|
|
769
|
+
"deviceLabel": device_label,
|
|
770
|
+
"giid": giid or self._giid,
|
|
771
|
+
"resolution": "high"},
|
|
772
|
+
"query": "mutation cccp($giid: String!, $deviceLabel: String!, $resolution: String!, $deviceIdentifier: String) {\n ContentProviderCaptureImageRequest(giid: $giid, deviceLabel: $deviceLabel, resolution: $resolution, deviceIdentifier: $deviceIdentifier) {\n requestId\n }\n}", # noqa: E501
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
@query_func
|
|
776
|
+
def camera_capture(self,
|
|
777
|
+
device_label: VariableTypes.DeviceLabel,
|
|
778
|
+
request_id: VariableTypes.RequestId,
|
|
779
|
+
giid: VariableTypes.Giid=None):
|
|
780
|
+
"""Capture a new image from a camera"""
|
|
781
|
+
assert giid or self._giid, "Set default giid or pass explicit"
|
|
782
|
+
return {
|
|
783
|
+
"variables": {
|
|
784
|
+
"deviceLabel": device_label,
|
|
785
|
+
"giid": giid or self._giid,
|
|
786
|
+
"requestId": request_id},
|
|
787
|
+
"query": "query queryCaptureImageRequestStatus($giid: String!, $deviceLabel: String!, $requestId: BigInt!) {\n installation(giid: $giid) {\n cameraContentProvider {\n captureImageRequestStatus(deviceLabel: $deviceLabel, requestId: $requestId) {\n mediaRequestStatus\n }\n }\n }\n}", # noqa: E501
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
def download_image(self, image_url, file_name):
|
|
791
|
+
"""Download image from url"""
|
|
792
|
+
try:
|
|
793
|
+
response = requests.get(image_url, stream=True)
|
|
794
|
+
except requests.exceptions.RequestException as ex:
|
|
795
|
+
raise RequestError("Failed to get image") from ex
|
|
796
|
+
with open(file_name, 'wb') as image_file:
|
|
797
|
+
for chunk in response.iter_content(chunk_size=1024):
|
|
798
|
+
if chunk:
|
|
799
|
+
image_file.write(chunk)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: vsure
|
|
3
|
+
Version: 2.6.9
|
|
4
|
+
Summary: Read and change status of verisure devices through mypages.
|
|
5
|
+
Home-page: http://github.com/persandstrom/python-verisure
|
|
6
|
+
Author: Per Sandstrom
|
|
7
|
+
Author-email: per.j.sandstrom@gmail.com
|
|
8
|
+
License: MIT
|
|
9
|
+
Keywords: home automation verisure
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Topic :: Home Automation
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.6
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Requires-Dist: requests>=2.25.1
|
|
19
|
+
Requires-Dist: click>=8.0.0a1
|
|
20
|
+
Dynamic: author
|
|
21
|
+
Dynamic: author-email
|
|
22
|
+
Dynamic: classifier
|
|
23
|
+
Dynamic: description
|
|
24
|
+
Dynamic: home-page
|
|
25
|
+
Dynamic: keywords
|
|
26
|
+
Dynamic: license
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
Dynamic: requires-dist
|
|
29
|
+
Dynamic: summary
|
|
30
|
+
|
|
31
|
+
A python3 module for reading and changing status of verisure devices through mypages.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
verisure/__init__.py,sha256=PuC4CPVYgYz4aeowwyR56uIv9-_0QWqho122TVs-JCI,518
|
|
2
|
+
verisure/__main__.py,sha256=yErcsdJP6LRsmk6ymBpyFJ7a1BFKWG5ca-anZ5Mx_ZY,5096
|
|
3
|
+
verisure/session.py,sha256=MGJiWEfbZUE74rlBVo4iSaWKvhuWHPkxRQ4-_3IJTpQ,37673
|
|
4
|
+
vsure-2.6.9.dist-info/licenses/LICENSE,sha256=vNvDUDugNXOM-bsuwrS_GvKmKEhdSJ08PY9jZT5tCWA,1082
|
|
5
|
+
vsure-2.6.9.dist-info/METADATA,sha256=zFk6PSuNjF_zkvTTzIybc2EZubyHhjn-B-W95o2x_XM,977
|
|
6
|
+
vsure-2.6.9.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
7
|
+
vsure-2.6.9.dist-info/entry_points.txt,sha256=qlRnwjOscU3jpgC8rgPMyQ_OtUacPXz6RguLBTfmoSo,48
|
|
8
|
+
vsure-2.6.9.dist-info/top_level.txt,sha256=-oNvWzMpVusfSzs0C1_43wC26DI9Bi7W6j-dGF65YGM,9
|
|
9
|
+
vsure-2.6.9.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
10
|
+
vsure-2.6.9.dist-info/RECORD,,
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2015 Per Sandström
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
verisure
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|