pygaposa 0.1.0__tar.gz
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.
- pygaposa-0.1.0/CHANGELOG.rst +18 -0
- pygaposa-0.1.0/LICENSE +22 -0
- pygaposa-0.1.0/MANIFEST.in +10 -0
- pygaposa-0.1.0/PKG-INFO +52 -0
- pygaposa-0.1.0/README.rst +30 -0
- pygaposa-0.1.0/pygaposa/__init__.py +10 -0
- pygaposa-0.1.0/pygaposa/__meta__.py +12 -0
- pygaposa-0.1.0/pygaposa/api.py +252 -0
- pygaposa-0.1.0/pygaposa/api_types.py +287 -0
- pygaposa-0.1.0/pygaposa/client.py +94 -0
- pygaposa-0.1.0/pygaposa/device.py +145 -0
- pygaposa-0.1.0/pygaposa/devicebase.py +102 -0
- pygaposa-0.1.0/pygaposa/firebase.py +280 -0
- pygaposa-0.1.0/pygaposa/gaposa.py +139 -0
- pygaposa-0.1.0/pygaposa/geoapi.py +68 -0
- pygaposa-0.1.0/pygaposa/group.py +50 -0
- pygaposa-0.1.0/pygaposa/model.py +68 -0
- pygaposa-0.1.0/pygaposa/motor.py +42 -0
- pygaposa-0.1.0/pygaposa/poll_manager.py +118 -0
- pygaposa-0.1.0/pygaposa/room.py +19 -0
- pygaposa-0.1.0/pygaposa/schedule.py +167 -0
- pygaposa-0.1.0/pygaposa.egg-info/PKG-INFO +52 -0
- pygaposa-0.1.0/pygaposa.egg-info/SOURCES.txt +31 -0
- pygaposa-0.1.0/pygaposa.egg-info/dependency_links.txt +1 -0
- pygaposa-0.1.0/pygaposa.egg-info/requires.txt +58 -0
- pygaposa-0.1.0/pygaposa.egg-info/top_level.txt +1 -0
- pygaposa-0.1.0/pyproject.toml +12 -0
- pygaposa-0.1.0/requirements-dev.txt +6 -0
- pygaposa-0.1.0/requirements-docs.txt +7 -0
- pygaposa-0.1.0/requirements-test.txt +8 -0
- pygaposa-0.1.0/requirements.txt +3 -0
- pygaposa-0.1.0/setup.cfg +4 -0
- pygaposa-0.1.0/setup.py +174 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
Changelog
|
|
2
|
+
=========
|
|
3
|
+
|
|
4
|
+
All notable changes to package_name will be documented here.
|
|
5
|
+
|
|
6
|
+
The format is based on `Keep a Changelog`_, and this project adheres to `Semantic Versioning`_.
|
|
7
|
+
|
|
8
|
+
.. _Keep a Changelog: https://keepachangelog.com/en/1.0.0/
|
|
9
|
+
.. _Semantic Versioning: https://semver.org/spec/v2.0.0.html
|
|
10
|
+
|
|
11
|
+
Categories for changes are: Added, Changed, Deprecated, Removed, Fixed, Security.
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
Version `0.1.0 <https://github.com/your_organisation/package_name/tree/0.1.0>`__
|
|
15
|
+
--------------------------------------------------------------------------------
|
|
16
|
+
|
|
17
|
+
Release date: YYYY-MM-DD.
|
|
18
|
+
Initial release (to be released).
|
pygaposa-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 Mark Watson <markwatson@cantab.net>
|
|
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
|
+
|
pygaposa-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: pygaposa
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Unofficial module for access to Gaposa morotized shades cloud API
|
|
5
|
+
Home-page: https://github.com/mwatson2/pygaposa
|
|
6
|
+
Author: Mark Watson
|
|
7
|
+
Author-email: markwatson@cantab.net
|
|
8
|
+
License: MIT
|
|
9
|
+
Classifier: Natural Language :: English
|
|
10
|
+
Classifier: Programming Language :: Python
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Requires-Python: >=3.6
|
|
16
|
+
Description-Content-Type: text/x-rst
|
|
17
|
+
Provides-Extra: test
|
|
18
|
+
Provides-Extra: docs
|
|
19
|
+
Provides-Extra: dev
|
|
20
|
+
Provides-Extra: all
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
|
|
23
|
+
|GHA tests| |Codecov report| |pre-commit| |black|
|
|
24
|
+
|
|
25
|
+
Gaposa Motorized Shades API
|
|
26
|
+
===========================
|
|
27
|
+
|
|
28
|
+
This module provides unofficial access to the cloud API for Gaposa motorized shades as used by the RollApp mobile application
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
Contributing
|
|
32
|
+
------------
|
|
33
|
+
|
|
34
|
+
Contributions are welcome! If you can see a way to improve this module:
|
|
35
|
+
|
|
36
|
+
- Do click the fork button
|
|
37
|
+
- Make your changes and make a pull request.
|
|
38
|
+
|
|
39
|
+
Or to report a bug or request something new, make an issue.
|
|
40
|
+
|
|
41
|
+
.. |GHA tests| image:: https://github.com/mwatson2/pygaposa/workflows/tests/badge.svg
|
|
42
|
+
:target: https://github.com/mwatson2/pygaposa/actions?query=workflow%3Atests
|
|
43
|
+
:alt: GHA Status
|
|
44
|
+
.. |Codecov report| image:: https://codecov.io/github/mwatson2/pygaposa/coverage.svg?branch=master
|
|
45
|
+
:target: https://codecov.io/github/mwatson2/pygaposa?branch=master
|
|
46
|
+
:alt: Coverage
|
|
47
|
+
.. |pre-commit| image:: https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white
|
|
48
|
+
:target: https://github.com/pre-commit/pre-commit
|
|
49
|
+
:alt: pre-commit
|
|
50
|
+
.. |black| image:: https://img.shields.io/badge/code%20style-black-000000.svg
|
|
51
|
+
:target: https://github.com/psf/black
|
|
52
|
+
:alt: black
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
|GHA tests| |Codecov report| |pre-commit| |black|
|
|
2
|
+
|
|
3
|
+
Gaposa Motorized Shades API
|
|
4
|
+
===========================
|
|
5
|
+
|
|
6
|
+
This module provides unofficial access to the cloud API for Gaposa motorized shades as used by the RollApp mobile application
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
Contributing
|
|
10
|
+
------------
|
|
11
|
+
|
|
12
|
+
Contributions are welcome! If you can see a way to improve this module:
|
|
13
|
+
|
|
14
|
+
- Do click the fork button
|
|
15
|
+
- Make your changes and make a pull request.
|
|
16
|
+
|
|
17
|
+
Or to report a bug or request something new, make an issue.
|
|
18
|
+
|
|
19
|
+
.. |GHA tests| image:: https://github.com/mwatson2/pygaposa/workflows/tests/badge.svg
|
|
20
|
+
:target: https://github.com/mwatson2/pygaposa/actions?query=workflow%3Atests
|
|
21
|
+
:alt: GHA Status
|
|
22
|
+
.. |Codecov report| image:: https://codecov.io/github/mwatson2/pygaposa/coverage.svg?branch=master
|
|
23
|
+
:target: https://codecov.io/github/mwatson2/pygaposa?branch=master
|
|
24
|
+
:alt: Coverage
|
|
25
|
+
.. |pre-commit| image:: https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white
|
|
26
|
+
:target: https://github.com/pre-commit/pre-commit
|
|
27
|
+
:alt: pre-commit
|
|
28
|
+
.. |black| image:: https://img.shields.io/badge/code%20style-black-000000.svg
|
|
29
|
+
:target: https://github.com/psf/black
|
|
30
|
+
:alt: black
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from . import __meta__
|
|
2
|
+
from .client import Client
|
|
3
|
+
from .device import Device
|
|
4
|
+
from .gaposa import FirebaseAuthException, Gaposa, GaposaAuthException
|
|
5
|
+
from .group import Group
|
|
6
|
+
from .motor import Motor
|
|
7
|
+
from .room import Room
|
|
8
|
+
from .schedule import Schedule
|
|
9
|
+
|
|
10
|
+
__version__ = __meta__.version
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# `name` is the name of the package as used for `pip install package`
|
|
2
|
+
name = "pygaposa"
|
|
3
|
+
# `path` is the name of the package for `import package`
|
|
4
|
+
path = name.lower().replace("-", "_").replace(" ", "_")
|
|
5
|
+
# Your version number should follow https://python.org/dev/peps/pep-0440 and
|
|
6
|
+
# https://semver.org
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
author = "Mark Watson"
|
|
9
|
+
author_email = "markwatson@cantab.net"
|
|
10
|
+
description = "Unofficial module for access to Gaposa morotized shades cloud API"
|
|
11
|
+
url = "https://github.com/mwatson2/pygaposa"
|
|
12
|
+
license = "MIT"
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import logging
|
|
3
|
+
from typing import Awaitable, Callable, Literal, Optional, Union
|
|
4
|
+
|
|
5
|
+
from aiohttp import ClientSession
|
|
6
|
+
from typeguard import check_type
|
|
7
|
+
|
|
8
|
+
from pygaposa.api_types import (
|
|
9
|
+
ApiControlRequest,
|
|
10
|
+
ApiControlRequestChannel,
|
|
11
|
+
ApiControlResponse,
|
|
12
|
+
ApiLoginResponse,
|
|
13
|
+
ApiRequestPayload,
|
|
14
|
+
ApiScheduleEventRequest,
|
|
15
|
+
ApiScheduleEventResponse,
|
|
16
|
+
ApiScheduleRequest,
|
|
17
|
+
ApiScheduleResponse,
|
|
18
|
+
ApiUsersResponse,
|
|
19
|
+
Command,
|
|
20
|
+
ScheduleEventInfo,
|
|
21
|
+
ScheduleEventType,
|
|
22
|
+
ScheduleUpdate,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
logging.basicConfig(level=logging.DEBUG)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class GaposaApi:
|
|
29
|
+
"""A class for interacting with the GAPOSA API.
|
|
30
|
+
|
|
31
|
+
Arguments:
|
|
32
|
+
---------
|
|
33
|
+
websession: An aiohttp ClientSession to use for requests.
|
|
34
|
+
getToken: A callable that returns a valid Google Authentication token.
|
|
35
|
+
serverUrl: The URL of the GAPOSA server to use.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
# serverUrl: str = "https://20230124t120606-dot-gaposa-prod.ew.r.appspot.com"
|
|
39
|
+
# serverUrl: str = "https://gaposa-prod.ew.r.appspot.com"
|
|
40
|
+
serverUrl = "https://backend.rollapp.tech"
|
|
41
|
+
|
|
42
|
+
def __init__(
|
|
43
|
+
self,
|
|
44
|
+
websession: ClientSession,
|
|
45
|
+
getToken: Callable[[], Awaitable[str]],
|
|
46
|
+
serverUrl: Optional[str] = None,
|
|
47
|
+
):
|
|
48
|
+
self.serverUrl = serverUrl or GaposaApi.serverUrl
|
|
49
|
+
self.websession = websession
|
|
50
|
+
self.getToken = getToken
|
|
51
|
+
self.logger = logging.getLogger("gaposa")
|
|
52
|
+
|
|
53
|
+
def clone(self) -> "GaposaApi":
|
|
54
|
+
"""Create a new GaposaApi instance with the same configuration as this one.
|
|
55
|
+
|
|
56
|
+
This enables us to create instances of the API with different configurations,
|
|
57
|
+
for example for different clients and devices.
|
|
58
|
+
"""
|
|
59
|
+
result = GaposaApi(self.websession, self.getToken, self.serverUrl)
|
|
60
|
+
if hasattr(self, "client"):
|
|
61
|
+
result.setClientAndRole(self.client, self.role)
|
|
62
|
+
if hasattr(self, "serial"):
|
|
63
|
+
result.setSerial(self.serial)
|
|
64
|
+
return result
|
|
65
|
+
|
|
66
|
+
def setClientAndRole(self, client: str, role: int):
|
|
67
|
+
"""Set the client and role for this API instance."""
|
|
68
|
+
self.client = client
|
|
69
|
+
self.role = role
|
|
70
|
+
|
|
71
|
+
def setSerial(self, serial: str):
|
|
72
|
+
"""Set the serial number for this API instance."""
|
|
73
|
+
self.serial = serial
|
|
74
|
+
|
|
75
|
+
async def login(self) -> ApiLoginResponse:
|
|
76
|
+
"""Log in to the GAPOSA API."""
|
|
77
|
+
response = await self.request("/v1/login")
|
|
78
|
+
return check_type(response, ApiLoginResponse)
|
|
79
|
+
|
|
80
|
+
async def users(self) -> ApiUsersResponse:
|
|
81
|
+
"""Get the list of users for the client set on this API instance."""
|
|
82
|
+
assert hasattr(self, "client")
|
|
83
|
+
response = await self.request("/v1/users")
|
|
84
|
+
return check_type(response, ApiUsersResponse)
|
|
85
|
+
|
|
86
|
+
async def control(
|
|
87
|
+
self,
|
|
88
|
+
command: Command,
|
|
89
|
+
scope: Union[Literal["channel"], Literal["group"]],
|
|
90
|
+
id: str,
|
|
91
|
+
):
|
|
92
|
+
"""Send a control command to a channel or group.
|
|
93
|
+
|
|
94
|
+
This API instance must have been configured with client and device serial
|
|
95
|
+
before calling this method.
|
|
96
|
+
"""
|
|
97
|
+
assert hasattr(self, "client")
|
|
98
|
+
assert hasattr(self, "serial")
|
|
99
|
+
if scope == "channel":
|
|
100
|
+
payload: ApiControlRequest = {
|
|
101
|
+
"serial": self.serial,
|
|
102
|
+
"data": {"cmd": command.value, "bank": 0, "address": int(id)},
|
|
103
|
+
}
|
|
104
|
+
else:
|
|
105
|
+
payload = {
|
|
106
|
+
"serial": self.serial,
|
|
107
|
+
"group": id,
|
|
108
|
+
"data": {"cmd": command.value},
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
response = await self.request("/v1/control", "POST", payload)
|
|
112
|
+
return check_type(response, ApiControlResponse)
|
|
113
|
+
|
|
114
|
+
async def addSchedule(self, schedule: ScheduleUpdate) -> ApiScheduleResponse:
|
|
115
|
+
"""Add a new schedule. This is a convenience method for addOrUpdateSchedule.
|
|
116
|
+
|
|
117
|
+
This API instance must have been configured with client and device serial
|
|
118
|
+
before calling this method.
|
|
119
|
+
"""
|
|
120
|
+
assert "Id" not in schedule
|
|
121
|
+
return await self.addOrUpdateSchedule(schedule)
|
|
122
|
+
|
|
123
|
+
async def updateSchedule(self, schedule: ScheduleUpdate) -> ApiScheduleResponse:
|
|
124
|
+
"""Update an existing schedule.
|
|
125
|
+
|
|
126
|
+
This is a convenience method for addOrUpdateSchedule.
|
|
127
|
+
|
|
128
|
+
This API instance must have been configured with client and device serial
|
|
129
|
+
before calling this method.
|
|
130
|
+
"""
|
|
131
|
+
assert "Id" in schedule
|
|
132
|
+
return await self.addOrUpdateSchedule(schedule)
|
|
133
|
+
|
|
134
|
+
async def addOrUpdateSchedule(
|
|
135
|
+
self, schedule: ScheduleUpdate
|
|
136
|
+
) -> ApiScheduleResponse:
|
|
137
|
+
"""Add or update a schedule.
|
|
138
|
+
|
|
139
|
+
This API instance must have been configured with client and device serial
|
|
140
|
+
before calling this method.
|
|
141
|
+
"""
|
|
142
|
+
assert hasattr(self, "client")
|
|
143
|
+
assert hasattr(self, "serial")
|
|
144
|
+
method = "POST" if "Id" not in schedule else "PUT"
|
|
145
|
+
payload: ApiScheduleRequest = {"serial": self.serial, "schedule": schedule}
|
|
146
|
+
response = await self.request("/v1/schedules", method, payload)
|
|
147
|
+
return check_type(response, ApiScheduleResponse)
|
|
148
|
+
|
|
149
|
+
async def deleteSchedule(self, Id: str) -> ApiScheduleResponse:
|
|
150
|
+
"""Delete a schedule.
|
|
151
|
+
|
|
152
|
+
This API instance must have been configured with client and device serial
|
|
153
|
+
before calling this method.
|
|
154
|
+
"""
|
|
155
|
+
assert hasattr(self, "client")
|
|
156
|
+
assert hasattr(self, "serial")
|
|
157
|
+
payload: ApiScheduleRequest = {"serial": self.serial, "schedule": {"Id": Id}}
|
|
158
|
+
response = await self.request("/v1/schedules", "DELETE", payload)
|
|
159
|
+
return check_type(response, ApiScheduleResponse)
|
|
160
|
+
|
|
161
|
+
async def addScheduleEvent(
|
|
162
|
+
self, Id: str, Mode: ScheduleEventType, event: ScheduleEventInfo
|
|
163
|
+
) -> ApiScheduleEventResponse:
|
|
164
|
+
"""Add a new event to a schedule.
|
|
165
|
+
|
|
166
|
+
This is a convenience method for updateScheduleEvent.
|
|
167
|
+
|
|
168
|
+
Schedules have three events for the three possible operations,
|
|
169
|
+
UP, DONW and PRESET. This is specified in the Mode argument.
|
|
170
|
+
|
|
171
|
+
This API instance must have been configured with client and device serial
|
|
172
|
+
before calling this method.
|
|
173
|
+
"""
|
|
174
|
+
assert hasattr(self, "client")
|
|
175
|
+
assert hasattr(self, "serial")
|
|
176
|
+
payload: ApiScheduleEventRequest = {
|
|
177
|
+
"serial": self.serial,
|
|
178
|
+
"schedule": {"Id": Id, "Mode": Mode.value},
|
|
179
|
+
"event": event,
|
|
180
|
+
}
|
|
181
|
+
response = await self.request("/v1/schedules/event", "PUT", payload)
|
|
182
|
+
return check_type(response, ApiScheduleEventResponse)
|
|
183
|
+
|
|
184
|
+
async def updateScheduleEvent(
|
|
185
|
+
self, Id: str, Mode: ScheduleEventType, event: ScheduleEventInfo
|
|
186
|
+
) -> ApiScheduleEventResponse:
|
|
187
|
+
"""Update an existing event in a schedule.
|
|
188
|
+
|
|
189
|
+
This is a convenience method for addScheduleEvent.
|
|
190
|
+
|
|
191
|
+
Schedules have three events for the three possible operations,
|
|
192
|
+
UP, DONW and PRESET. This is specified in the Mode argument.
|
|
193
|
+
|
|
194
|
+
This API instance must have been configured with client and device serial
|
|
195
|
+
before calling this method.
|
|
196
|
+
"""
|
|
197
|
+
return await self.addScheduleEvent(Id, Mode, event)
|
|
198
|
+
|
|
199
|
+
async def deleteScheduleEvent(
|
|
200
|
+
self, Id: str, Mode: ScheduleEventType
|
|
201
|
+
) -> ApiScheduleEventResponse:
|
|
202
|
+
"""Delete an event from a schedule.
|
|
203
|
+
|
|
204
|
+
Schedules have three events for the three possible operations,
|
|
205
|
+
UP, DONW and PRESET. This is specified in the Mode argument.
|
|
206
|
+
|
|
207
|
+
This API instance must have been configured with client and device serial
|
|
208
|
+
before calling this method.
|
|
209
|
+
"""
|
|
210
|
+
assert hasattr(self, "client")
|
|
211
|
+
assert hasattr(self, "serial")
|
|
212
|
+
payload: ApiScheduleEventRequest = {
|
|
213
|
+
"serial": self.serial,
|
|
214
|
+
"schedule": {"Id": Id, "Mode": Mode},
|
|
215
|
+
}
|
|
216
|
+
response = await self.request("/v1/schedules/event", "DELETE", payload)
|
|
217
|
+
return check_type(response, ApiScheduleEventResponse)
|
|
218
|
+
|
|
219
|
+
async def request(
|
|
220
|
+
self,
|
|
221
|
+
endpoint: str,
|
|
222
|
+
method: str = "GET",
|
|
223
|
+
payload: Optional[ApiRequestPayload] = None,
|
|
224
|
+
):
|
|
225
|
+
idToken = await self.getToken()
|
|
226
|
+
headers = {
|
|
227
|
+
"Content-Type": "application/json",
|
|
228
|
+
"authorization": f"Bearer {idToken}",
|
|
229
|
+
}
|
|
230
|
+
if hasattr(self, "client"):
|
|
231
|
+
headers["auth"] = json.dumps({"role": self.role, "client": self.client})
|
|
232
|
+
|
|
233
|
+
data = json.dumps({"payload": payload}) if payload else None
|
|
234
|
+
|
|
235
|
+
response = await self.websession.request(
|
|
236
|
+
method,
|
|
237
|
+
self.serverUrl + endpoint,
|
|
238
|
+
headers=headers,
|
|
239
|
+
data=data,
|
|
240
|
+
raise_for_status=True,
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
self.logger.debug(f"Request: {method} {endpoint}")
|
|
244
|
+
self.logger.debug(f"Headers: {headers}")
|
|
245
|
+
self.logger.debug(f"Payload: {data}")
|
|
246
|
+
self.logger.debug(f"Response: {response}")
|
|
247
|
+
|
|
248
|
+
responseObject = await response.json()
|
|
249
|
+
|
|
250
|
+
self.logger.debug(f"Response object: {responseObject}")
|
|
251
|
+
|
|
252
|
+
return responseObject
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
from enum import Enum, IntEnum
|
|
2
|
+
from typing import Any, Dict, List, Literal, Optional, TypedDict, Union
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class DeviceInfo(TypedDict):
|
|
6
|
+
Name: str
|
|
7
|
+
Serial: str
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ClientInfo(TypedDict):
|
|
11
|
+
Role: int
|
|
12
|
+
Name: str
|
|
13
|
+
Devices: List[DeviceInfo]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class AuthResult(TypedDict):
|
|
17
|
+
TermsAgreed: bool
|
|
18
|
+
UserRole: int
|
|
19
|
+
Clients: Dict[str, ClientInfo]
|
|
20
|
+
DateFormat: Any
|
|
21
|
+
TimeFormat: Any
|
|
22
|
+
Theme: Any
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# Response for /v1/login
|
|
26
|
+
class ApiLoginResponse(TypedDict):
|
|
27
|
+
apiStatus: Literal["Success"]
|
|
28
|
+
msg: Literal["Auth"]
|
|
29
|
+
result: AuthResult
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class UserInfo(TypedDict):
|
|
33
|
+
CountryID: str
|
|
34
|
+
EmailAlert: bool
|
|
35
|
+
Email: str
|
|
36
|
+
Name: str
|
|
37
|
+
Role: int
|
|
38
|
+
Uid: str
|
|
39
|
+
Active: bool
|
|
40
|
+
CompoundLocation: str
|
|
41
|
+
Country: str
|
|
42
|
+
Joined: Dict[str, int]
|
|
43
|
+
TermsAgreed: bool
|
|
44
|
+
CountryCode: str
|
|
45
|
+
Mobile: str
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class UsersResult(TypedDict):
|
|
49
|
+
Info: UserInfo
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
# Response for /v1/users
|
|
53
|
+
class ApiUsersResponse(TypedDict):
|
|
54
|
+
apiStatus: Literal["success"]
|
|
55
|
+
msg: Literal["Return user"]
|
|
56
|
+
result: UsersResult
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class Command(str, Enum):
|
|
60
|
+
DOWN = "0xee"
|
|
61
|
+
UP = "0xdd"
|
|
62
|
+
STOP = "0xcc"
|
|
63
|
+
PRESET = "0xad"
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class EventDays(IntEnum):
|
|
67
|
+
MON = 0
|
|
68
|
+
TUE = 1
|
|
69
|
+
WED = 2
|
|
70
|
+
THU = 3
|
|
71
|
+
FRI = 4
|
|
72
|
+
SAT = 5
|
|
73
|
+
SUN = 6
|
|
74
|
+
ALL = 7
|
|
75
|
+
WEEKDAYS = 8
|
|
76
|
+
WEEKENDS = 9
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
NumericIdentifier = str
|
|
80
|
+
Location = TypedDict("Location", {"_latitude": float, "_longitude": float})
|
|
81
|
+
EventRepeat = tuple[bool, bool, bool, bool, bool, bool, bool]
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class State(TypedDict):
|
|
85
|
+
TimeStamp: str
|
|
86
|
+
OnLine: bool
|
|
87
|
+
LastCmd: str
|
|
88
|
+
Message: str
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class Info(TypedDict):
|
|
92
|
+
Name: str
|
|
93
|
+
ClientId: str
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class Assistant(TypedDict):
|
|
97
|
+
Alexa: bool
|
|
98
|
+
Home: bool
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class HeartBeat(TypedDict):
|
|
102
|
+
Subnet: str
|
|
103
|
+
Channels: int
|
|
104
|
+
Software: str
|
|
105
|
+
Signal: int
|
|
106
|
+
Mode: str
|
|
107
|
+
Frequency: int
|
|
108
|
+
Gateway: str
|
|
109
|
+
Ip: str
|
|
110
|
+
TxID: list[int]
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class Channel(TypedDict):
|
|
114
|
+
StatusCode: int
|
|
115
|
+
State: str
|
|
116
|
+
HomeRunning: bool
|
|
117
|
+
Location: str
|
|
118
|
+
HomePercent: int
|
|
119
|
+
Icon: str
|
|
120
|
+
Name: str
|
|
121
|
+
HomePaused: bool
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class RoomInfo(TypedDict):
|
|
125
|
+
Favourite: bool
|
|
126
|
+
Motors: list[int]
|
|
127
|
+
Name: str
|
|
128
|
+
Icon: str
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
class GroupInfo(TypedDict):
|
|
132
|
+
Favourite: bool
|
|
133
|
+
Icon: str
|
|
134
|
+
Name: str
|
|
135
|
+
Motors: list[int]
|
|
136
|
+
Rooms: list[str]
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class ScheduleInfo(TypedDict):
|
|
140
|
+
Name: str
|
|
141
|
+
Groups: list[int]
|
|
142
|
+
Location: Location
|
|
143
|
+
Motors: list[int]
|
|
144
|
+
Icon: str
|
|
145
|
+
Active: bool
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
class ScheduleUpdate(TypedDict, total=False):
|
|
149
|
+
Id: NumericIdentifier
|
|
150
|
+
Name: str
|
|
151
|
+
Groups: list[int]
|
|
152
|
+
Location: Location
|
|
153
|
+
Motors: list[int]
|
|
154
|
+
Icon: str
|
|
155
|
+
Active: bool
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class DeviceDocumentRequired(TypedDict):
|
|
159
|
+
State: State
|
|
160
|
+
Info: Info
|
|
161
|
+
Assistant: Assistant
|
|
162
|
+
Channels: Dict[str, Channel]
|
|
163
|
+
Rooms: Dict[str, RoomInfo] # key is room name
|
|
164
|
+
Groups: Dict[NumericIdentifier, GroupInfo]
|
|
165
|
+
HeartBeat: HeartBeat
|
|
166
|
+
DeletedChannels: List[int]
|
|
167
|
+
Pending: list
|
|
168
|
+
Uid: list[str]
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
class DeviceDocument(DeviceDocumentRequired, total=False):
|
|
172
|
+
Schedule: Dict[NumericIdentifier, ScheduleInfo]
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
class EventMode(TypedDict):
|
|
176
|
+
SunRise: bool
|
|
177
|
+
SunSet: bool
|
|
178
|
+
TimeDay: bool
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
class ScheduleEventInfo(TypedDict):
|
|
182
|
+
EventRepeat: EventRepeat
|
|
183
|
+
TimeZone: str
|
|
184
|
+
Active: bool
|
|
185
|
+
FutureEvent: bool
|
|
186
|
+
Submit: bool
|
|
187
|
+
EventEpoch: int
|
|
188
|
+
Location: Location
|
|
189
|
+
Motors: list[int]
|
|
190
|
+
EventMode: EventMode
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
class NamedItem(TypedDict):
|
|
194
|
+
Name: str
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
class ScheduleEventType(str, Enum):
|
|
198
|
+
UP = "UP"
|
|
199
|
+
DOWN = "DOWN"
|
|
200
|
+
PRESET = "PRESET"
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
class ApiControlResult(TypedDict):
|
|
204
|
+
Success: Literal["OK"]
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
class ApiGroupControlData(TypedDict):
|
|
208
|
+
cmd: str
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
class ApiChannelControlData(TypedDict):
|
|
212
|
+
cmd: str
|
|
213
|
+
bank: int
|
|
214
|
+
address: int
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
# Request for /control
|
|
218
|
+
class ApiControlRequestBase(TypedDict):
|
|
219
|
+
serial: str
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
class ApiControlRequestGroup(ApiControlRequestBase):
|
|
223
|
+
group: str
|
|
224
|
+
data: ApiGroupControlData
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
class ApiControlRequestChannel(ApiControlRequestBase):
|
|
228
|
+
data: ApiChannelControlData
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
ApiControlRequest = Union[ApiControlRequestChannel, ApiControlRequestGroup]
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
# Response for /control
|
|
235
|
+
class ApiControlResponse(TypedDict):
|
|
236
|
+
apiCommand: Literal["Success"]
|
|
237
|
+
msg: Literal["OK"]
|
|
238
|
+
result: ApiControlResult
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
# Request for /v1/schedules (in "payload" field)
|
|
242
|
+
class ApiScheduleRequest(TypedDict):
|
|
243
|
+
serial: str
|
|
244
|
+
schedule: ScheduleUpdate
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
class ApiScheduleDeleteResult(TypedDict):
|
|
248
|
+
Schedule: bool
|
|
249
|
+
Down: bool
|
|
250
|
+
Up: bool
|
|
251
|
+
Preset: bool
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
# Response for /v1/schedules
|
|
255
|
+
class ApiScheduleResponse(TypedDict):
|
|
256
|
+
apiStatus: Literal["success"]
|
|
257
|
+
msg: Union[
|
|
258
|
+
Literal["Schedule Update"], Literal["Schedule deleted"], Literal["Schedule Add"]
|
|
259
|
+
]
|
|
260
|
+
result: Union[Literal["ok"], ApiScheduleDeleteResult, str]
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
class ScheduleEventSelector(TypedDict):
|
|
264
|
+
Id: NumericIdentifier
|
|
265
|
+
Mode: str
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
# Request for /v1/schedules/event (in "payload" field)
|
|
269
|
+
class ApiScheduleEventRequestBase(TypedDict):
|
|
270
|
+
serial: str
|
|
271
|
+
schedule: ScheduleEventSelector
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
class ApiScheduleEventRequest(ApiScheduleEventRequestBase, total=False):
|
|
275
|
+
event: ScheduleEventInfo
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
# Response for /v1/schedules/event
|
|
279
|
+
class ApiScheduleEventResponse(TypedDict):
|
|
280
|
+
apiStatus: Literal["success"]
|
|
281
|
+
msg: Literal["Schedule Add"]
|
|
282
|
+
result: str
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
ApiRequestPayload = Union[
|
|
286
|
+
ApiScheduleRequest, ApiScheduleEventRequest, ApiControlRequest
|
|
287
|
+
]
|