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.
@@ -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
+
@@ -0,0 +1,10 @@
1
+ include CHANGELOG.rst
2
+ include LICEN[CS]E*
3
+ include pyproject.toml
4
+ include README.rst
5
+ include requirements*.txt
6
+
7
+ global-exclude *.py[co]
8
+ global-exclude __pycache__
9
+ global-exclude *~
10
+ global-exclude *.ipynb_checkpoints/*
@@ -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
+ ]