quilt-hp-python 0.1.1__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.
Files changed (53) hide show
  1. quilt_hp/__init__.py +22 -0
  2. quilt_hp/_paths.py +26 -0
  3. quilt_hp/_proto/__init__.py +0 -0
  4. quilt_hp/_proto/quilt_device_pairing_pb2.py +56 -0
  5. quilt_hp/_proto/quilt_device_pairing_pb2.pyi +317 -0
  6. quilt_hp/_proto/quilt_device_pairing_pb2_grpc.py +24 -0
  7. quilt_hp/_proto/quilt_hds_pb2.py +292 -0
  8. quilt_hp/_proto/quilt_hds_pb2.pyi +3947 -0
  9. quilt_hp/_proto/quilt_hds_pb2_grpc.py +1732 -0
  10. quilt_hp/_proto/quilt_notifier_pb2.py +55 -0
  11. quilt_hp/_proto/quilt_notifier_pb2.pyi +258 -0
  12. quilt_hp/_proto/quilt_notifier_pb2_grpc.py +97 -0
  13. quilt_hp/_proto/quilt_services_pb2.py +171 -0
  14. quilt_hp/_proto/quilt_services_pb2.pyi +1320 -0
  15. quilt_hp/_proto/quilt_services_pb2_grpc.py +1188 -0
  16. quilt_hp/_proto/quilt_system_pb2.py +53 -0
  17. quilt_hp/_proto/quilt_system_pb2.pyi +164 -0
  18. quilt_hp/_proto/quilt_system_pb2_grpc.py +270 -0
  19. quilt_hp/auth.py +244 -0
  20. quilt_hp/cli/__init__.py +1 -0
  21. quilt_hp/cli/main.py +770 -0
  22. quilt_hp/cli/settings.py +123 -0
  23. quilt_hp/cli/store.py +105 -0
  24. quilt_hp/cli/tui.py +2677 -0
  25. quilt_hp/client.py +616 -0
  26. quilt_hp/const.py +57 -0
  27. quilt_hp/exceptions.py +23 -0
  28. quilt_hp/models/__init__.py +85 -0
  29. quilt_hp/models/comfort.py +47 -0
  30. quilt_hp/models/controller.py +135 -0
  31. quilt_hp/models/energy.py +31 -0
  32. quilt_hp/models/enums.py +298 -0
  33. quilt_hp/models/indoor_unit.py +412 -0
  34. quilt_hp/models/outdoor_unit.py +71 -0
  35. quilt_hp/models/qsm.py +105 -0
  36. quilt_hp/models/schedule.py +98 -0
  37. quilt_hp/models/sensor.py +92 -0
  38. quilt_hp/models/software_update.py +74 -0
  39. quilt_hp/models/space.py +177 -0
  40. quilt_hp/models/system.py +451 -0
  41. quilt_hp/py.typed +1 -0
  42. quilt_hp/services/__init__.py +1 -0
  43. quilt_hp/services/hds.py +480 -0
  44. quilt_hp/services/streaming.py +561 -0
  45. quilt_hp/services/system.py +95 -0
  46. quilt_hp/services/user.py +143 -0
  47. quilt_hp/tokens.py +119 -0
  48. quilt_hp/transport.py +192 -0
  49. quilt_hp_python-0.1.1.dist-info/METADATA +172 -0
  50. quilt_hp_python-0.1.1.dist-info/RECORD +53 -0
  51. quilt_hp_python-0.1.1.dist-info/WHEEL +4 -0
  52. quilt_hp_python-0.1.1.dist-info/entry_points.txt +2 -0
  53. quilt_hp_python-0.1.1.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,480 @@
1
+ """HomeDatastoreService.
2
+
3
+ Async CRUD for spaces, IDUs, comfort settings, and schedules.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import time
9
+ from collections.abc import Callable, Sequence
10
+ from typing import Protocol, cast
11
+
12
+ import grpc.aio
13
+ from google.protobuf.timestamp_pb2 import Timestamp
14
+
15
+ from quilt_hp._proto import quilt_hds_pb2 as hds
16
+ from quilt_hp._proto import quilt_hds_pb2_grpc as hds_grpc
17
+ from quilt_hp.exceptions import QuiltError, QuiltNotFoundError
18
+ from quilt_hp.models.comfort import ComfortSetting
19
+ from quilt_hp.models.enums import FanSpeed, HVACMode, LouverMode
20
+ from quilt_hp.models.indoor_unit import IndoorUnit
21
+ from quilt_hp.models.schedule import ScheduleDay, ScheduleEvent, ScheduleWeek, ScheduleWeekDay
22
+ from quilt_hp.models.space import Space
23
+ from quilt_hp.models.system import SystemSnapshot
24
+
25
+
26
+ class _HomeDatastoreServiceStub(Protocol):
27
+ async def GetHomeDatastoreSystem(
28
+ self, request: hds.GetHomeDatastoreSystemRequest
29
+ ) -> object: ...
30
+ async def UpdateSpace(self, request: hds.UpdateSpaceRequest) -> object: ...
31
+ async def UpdateIndoorUnit(self, request: hds.UpdateIndoorUnitRequest) -> object: ...
32
+ async def UpdateComfortSetting(self, request: hds.UpdateComfortSettingRequest) -> object: ...
33
+ async def CreateScheduleDay(self, request: hds.CreateScheduleDayRequest) -> object: ...
34
+ async def CreateScheduleWeek(self, request: hds.CreateScheduleWeekRequest) -> object: ...
35
+ async def UpdateScheduleWeek(self, request: hds.UpdateScheduleWeekRequest) -> object: ...
36
+ async def DeleteScheduleDay(self, request: hds.DeleteScheduleDayRequest) -> object: ...
37
+ async def UpdateScheduleDay(self, request: hds.UpdateScheduleDayRequest) -> object: ...
38
+ async def DeleteScheduleWeek(self, request: hds.DeleteScheduleWeekRequest) -> object: ...
39
+ async def UpdateLocation(self, request: hds.UpdateLocationRequest) -> object: ...
40
+
41
+
42
+ def _now_ts() -> Timestamp:
43
+ ts = Timestamp()
44
+ ts.FromSeconds(int(time.time()))
45
+ return ts
46
+
47
+
48
+ def _to_wire_schedule_event(event: ScheduleEvent | hds.ScheduleEvent) -> hds.ScheduleEvent:
49
+ if isinstance(event, hds.ScheduleEvent):
50
+ return event
51
+ return hds.ScheduleEvent(
52
+ start_s=event.start_s,
53
+ comfort_setting_id=event.comfort_setting_id,
54
+ hvac_mode=cast("hds.HVACMode.ValueType", event.hvac_mode),
55
+ heating_temperature_setpoint_c=event.heating_setpoint_c,
56
+ cooling_temperature_setpoint_c=event.cooling_setpoint_c,
57
+ precondition=event.precondition,
58
+ )
59
+
60
+
61
+ def _to_wire_schedule_week_day(
62
+ day: ScheduleWeekDay | hds.ScheduleWeekDay,
63
+ ) -> hds.ScheduleWeekDay:
64
+ if isinstance(day, hds.ScheduleWeekDay):
65
+ return day
66
+ return hds.ScheduleWeekDay(
67
+ weekday=cast("hds.Weekday.ValueType", day.weekday),
68
+ day_id=day.day_id,
69
+ )
70
+
71
+
72
+ class HomeDatastoreService:
73
+ """Async wrapper for HomeDatastoreService gRPC methods."""
74
+
75
+ def __init__(self, channel: grpc.aio.Channel) -> None:
76
+ factory = cast(
77
+ "Callable[[grpc.aio.Channel], _HomeDatastoreServiceStub]",
78
+ hds_grpc.HomeDatastoreServiceStub,
79
+ )
80
+ self._stub: _HomeDatastoreServiceStub = factory(channel)
81
+
82
+ async def get_system(self, system_id: str) -> SystemSnapshot:
83
+ """Fetch a full system snapshot."""
84
+ try:
85
+ snap = await self._stub.GetHomeDatastoreSystem(
86
+ hds.GetHomeDatastoreSystemRequest(system_id=system_id)
87
+ )
88
+ except grpc.aio.AioRpcError as exc:
89
+ if exc.code() == grpc.StatusCode.NOT_FOUND:
90
+ raise QuiltNotFoundError(f"System {system_id} not found") from exc
91
+ raise QuiltError(f"GetHomeDatastoreSystem failed: {exc.details()}") from exc
92
+ return SystemSnapshot.from_proto(snap)
93
+
94
+ async def update_space(
95
+ self,
96
+ snapshot_space: Space,
97
+ *,
98
+ mode: HVACMode | None = None,
99
+ heat_setpoint_c: float | None = None,
100
+ cool_setpoint_c: float | None = None,
101
+ ) -> Space:
102
+ """Update a space's HVAC mode and/or setpoints.
103
+
104
+ Follows the app's setpoint routing rules:
105
+ - temperature_setpoint_c = mode-relevant setpoint
106
+ - Both heating/cooling setpoints are always sent
107
+
108
+ STANDBY semantics:
109
+ When mode=STANDBY is explicitly requested the comfort-setting association
110
+ is cleared (empty id, override=NONE). This produces plain OFF —
111
+ the room stays off regardless of occupancy. Sending the existing
112
+ comfort_setting_id when going to STANDBY would preserve an AWAY
113
+ setting and the room would still reactivate on occupancy.
114
+ """
115
+ c = snapshot_space.controls
116
+ mode_enum = mode if mode is not None else c.hvac_mode
117
+ mode_val = mode_enum.value
118
+ heat = heat_setpoint_c if heat_setpoint_c is not None else c.heating_setpoint_c
119
+ cool = cool_setpoint_c if cool_setpoint_c is not None else c.cooling_setpoint_c
120
+
121
+ # Mode-relevant setpoint routing (from KX.java)
122
+ temp_setpoint = heat if mode_enum == HVACMode.HEAT else cool
123
+
124
+ # AUTO mode: enforce cool - heat >= 2.5°C
125
+ if mode_enum == HVACMode.AUTO and cool - heat < 2.5:
126
+ cool = heat + 2.5
127
+
128
+ # Setting to STANDBY explicitly means "turn off" — clear the comfort
129
+ # setting so occupancy cannot reactivate the room (i.e. not AWAY mode).
130
+ if mode_enum == HVACMode.STANDBY:
131
+ cs_id = ""
132
+ cs_override = hds.COMFORT_SETTING_OVERRIDE_NONE
133
+ else:
134
+ cs_id = c.comfort_setting_id
135
+ cs_override = hds.COMFORT_SETTING_OVERRIDE_UNTIL_NEXT_SCHEDULE
136
+
137
+ diff = hds.Space(
138
+ header=hds.EntityMetadata(
139
+ object_id=snapshot_space.id,
140
+ system_id=snapshot_space.system_id,
141
+ ),
142
+ controls=hds.SpaceControls(
143
+ hvac_mode=cast("hds.HVACMode.ValueType", mode_val),
144
+ temperature_setpoint_c=temp_setpoint,
145
+ heating_temperature_setpoint_c=heat,
146
+ cooling_temperature_setpoint_c=cool,
147
+ updated_ts=_now_ts(),
148
+ comfort_setting_override=cs_override,
149
+ comfort_setting_id_string=cs_id,
150
+ ),
151
+ )
152
+ try:
153
+ result = await self._stub.UpdateSpace(hds.UpdateSpaceRequest(diff=diff))
154
+ except grpc.aio.AioRpcError as exc:
155
+ raise QuiltError(f"UpdateSpace failed: {exc.details()}") from exc
156
+ return Space.from_proto(result)
157
+
158
+ async def update_space_settings(
159
+ self,
160
+ snapshot_space: Space,
161
+ *,
162
+ unoccupied_timeout_s: float | None = None,
163
+ occupied_timeout_s: float | None = None,
164
+ ) -> Space:
165
+ """Update a space's auto-away / auto-return timeouts.
166
+
167
+ Sends a sparse UpdateSpace diff containing only the ``settings`` block.
168
+ All current settings fields are echoed back to avoid the server clearing
169
+ fields that are absent from a partial proto (name, timezone, etc.).
170
+ """
171
+ s = snapshot_space.settings
172
+ diff = hds.Space(
173
+ header=hds.EntityMetadata(
174
+ object_id=snapshot_space.id,
175
+ system_id=snapshot_space.system_id,
176
+ ),
177
+ settings=hds.SpaceSettings(
178
+ name=s.name,
179
+ timezone=s.timezone,
180
+ occupied_timeout_s=(
181
+ occupied_timeout_s if occupied_timeout_s is not None else s.occupied_timeout_s
182
+ ),
183
+ unoccupied_timeout_s=(
184
+ unoccupied_timeout_s
185
+ if unoccupied_timeout_s is not None
186
+ else s.unoccupied_timeout_s
187
+ ),
188
+ occupancy=cast("hds.OccupancyMode.ValueType", s.occupancy_mode.value),
189
+ safety_heating=cast("hds.SafetyHeatingMode.ValueType", s.safety_heating.value),
190
+ updated_ts=_now_ts(),
191
+ ),
192
+ )
193
+ try:
194
+ result = await self._stub.UpdateSpace(hds.UpdateSpaceRequest(diff=diff))
195
+ except grpc.aio.AioRpcError as exc:
196
+ raise QuiltError(f"UpdateSpace settings failed: {exc.details()}") from exc
197
+ return Space.from_proto(result)
198
+
199
+ async def update_indoor_unit(
200
+ self,
201
+ idu: IndoorUnit,
202
+ *,
203
+ fan_speed: FanSpeed | None = None,
204
+ louver_mode: LouverMode | None = None,
205
+ louver_position: float | None = None,
206
+ led_color_code: int | None = None,
207
+ led_brightness: float | None = None,
208
+ led_animation: int | None = None,
209
+ ) -> IndoorUnit:
210
+ """Update indoor unit controls (fan, louver, LED)."""
211
+ c = idu.controls
212
+ fan_mode_val, fan_pct = (
213
+ fan_speed.to_wire() if fan_speed is not None else c.fan_speed.to_wire()
214
+ )
215
+ diff = hds.IndoorUnit(
216
+ header=hds.EntityMetadata(
217
+ object_id=idu.id,
218
+ system_id=idu.system_id,
219
+ ),
220
+ controls=hds.IndoorUnitControls(
221
+ updated_ts=_now_ts(),
222
+ fan_speed_mode=cast("hds.FanSpeedMode.ValueType", fan_mode_val),
223
+ fan_speed_percent=fan_pct,
224
+ louver_mode=cast(
225
+ "hds.IndoorUnitLouverMode.ValueType",
226
+ louver_mode.value if louver_mode is not None else c.louver_mode.value,
227
+ ),
228
+ louver_fixed_position=(
229
+ louver_position if louver_position is not None else c.louver_fixed_position
230
+ ),
231
+ led_color_code=led_color_code if led_color_code is not None else c.led_color_code,
232
+ led_color_brightness_percent=(
233
+ led_brightness if led_brightness is not None else c.led_brightness
234
+ ),
235
+ led_animation=cast(
236
+ "hds.LightAnimation.ValueType",
237
+ led_animation if led_animation is not None else c.led_animation,
238
+ ),
239
+ ),
240
+ )
241
+ try:
242
+ result = await self._stub.UpdateIndoorUnit(hds.UpdateIndoorUnitRequest(diff=diff))
243
+ except grpc.aio.AioRpcError as exc:
244
+ raise QuiltError(f"UpdateIndoorUnit failed: {exc.details()}") from exc
245
+ return IndoorUnit.from_proto(result)
246
+
247
+ async def update_indoor_unit_settings(
248
+ self,
249
+ idu: IndoorUnit,
250
+ *,
251
+ fence_left_m: float | None = None,
252
+ fence_right_m: float | None = None,
253
+ fence_forward_m: float | None = None,
254
+ radar_height_m: float | None = None,
255
+ light_brightness_default: float | None = None,
256
+ ) -> IndoorUnit:
257
+ """Update indoor unit settings.
258
+
259
+ Includes presence fence geometry and default brightness.
260
+ """
261
+ st = idu.settings
262
+ diff = hds.IndoorUnit(
263
+ header=hds.EntityMetadata(
264
+ object_id=idu.id,
265
+ system_id=idu.system_id,
266
+ ),
267
+ settings=hds.IndoorUnitSettings(
268
+ updated_ts=_now_ts(),
269
+ name=st.name,
270
+ description=st.description,
271
+ light_brightness_default_percent=(
272
+ light_brightness_default
273
+ if light_brightness_default is not None
274
+ else st.light_brightness_default_percent
275
+ ),
276
+ presence_fence_left_m=(
277
+ fence_left_m if fence_left_m is not None else st.presence_fence_left_m
278
+ ),
279
+ presence_fence_right_m=(
280
+ fence_right_m if fence_right_m is not None else st.presence_fence_right_m
281
+ ),
282
+ presence_fence_forward_m=(
283
+ fence_forward_m if fence_forward_m is not None else st.presence_fence_forward_m
284
+ ),
285
+ radar_sensor_distance_from_floor_m=(
286
+ radar_height_m
287
+ if radar_height_m is not None
288
+ else st.radar_sensor_distance_from_floor_m
289
+ ),
290
+ ),
291
+ )
292
+ try:
293
+ result = await self._stub.UpdateIndoorUnit(hds.UpdateIndoorUnitRequest(diff=diff))
294
+ except grpc.aio.AioRpcError as exc:
295
+ raise QuiltError(f"UpdateIndoorUnit settings failed: {exc.details()}") from exc
296
+ return IndoorUnit.from_proto(result)
297
+
298
+ async def update_comfort_setting(
299
+ self,
300
+ setting: ComfortSetting,
301
+ *,
302
+ name: str | None = None,
303
+ hvac_mode: HVACMode | None = None,
304
+ heat_setpoint_c: float | None = None,
305
+ cool_setpoint_c: float | None = None,
306
+ fan_speed: FanSpeed | None = None,
307
+ ) -> ComfortSetting:
308
+ """Update a comfort setting preset."""
309
+ fan_mode_val, fan_pct = (
310
+ fan_speed.to_wire() if fan_speed is not None else setting.fan_speed.to_wire()
311
+ )
312
+ diff = hds.ComfortSetting(
313
+ header=hds.EntityMetadata(
314
+ object_id=setting.id,
315
+ system_id=setting.system_id,
316
+ ),
317
+ attributes=hds.ComfortSettingAttributes(
318
+ updated_ts=_now_ts(),
319
+ name=name if name is not None else setting.name,
320
+ heating_temperature_setpoint_c=(
321
+ heat_setpoint_c if heat_setpoint_c is not None else setting.heating_setpoint_c
322
+ ),
323
+ cooling_temperature_setpoint_c=(
324
+ cool_setpoint_c if cool_setpoint_c is not None else setting.cooling_setpoint_c
325
+ ),
326
+ hvac_mode=cast(
327
+ "hds.HVACMode.ValueType",
328
+ hvac_mode.value if hvac_mode is not None else setting.hvac_mode.value,
329
+ ),
330
+ fan_speed_mode=cast("hds.FanSpeedMode.ValueType", fan_mode_val),
331
+ fan_speed_percent=fan_pct,
332
+ type=cast("hds.ComfortSettingType.ValueType", setting.type.value),
333
+ ),
334
+ )
335
+ try:
336
+ result = await self._stub.UpdateComfortSetting(
337
+ hds.UpdateComfortSettingRequest(comfort_setting=diff)
338
+ )
339
+ except grpc.aio.AioRpcError as exc:
340
+ raise QuiltError(f"UpdateComfortSetting failed: {exc.details()}") from exc
341
+ return ComfortSetting.from_proto(result)
342
+
343
+ async def create_schedule_day(
344
+ self,
345
+ system_id: str,
346
+ space_id: str,
347
+ name: str,
348
+ events: Sequence[ScheduleEvent | hds.ScheduleEvent],
349
+ ) -> ScheduleDay:
350
+ """Create a new schedule day program for a space."""
351
+ wire_events = [_to_wire_schedule_event(event) for event in events]
352
+ diff = hds.ScheduleDay(
353
+ header=hds.EntityMetadata(system_id=system_id),
354
+ attributes=hds.ScheduleDayAttributes(name=name),
355
+ relationships=hds.ScheduleDayRelationships(space_id=space_id),
356
+ events=wire_events,
357
+ )
358
+ try:
359
+ result = await self._stub.CreateScheduleDay(
360
+ hds.CreateScheduleDayRequest(schedule_day=diff)
361
+ )
362
+ except grpc.aio.AioRpcError as exc:
363
+ raise QuiltError(f"CreateScheduleDay failed: {exc.details()}") from exc
364
+ return ScheduleDay.from_proto(result)
365
+
366
+ async def create_schedule_week(
367
+ self,
368
+ system_id: str,
369
+ space_id: str,
370
+ days: Sequence[ScheduleWeekDay | hds.ScheduleWeekDay] | None = None,
371
+ ) -> ScheduleWeek:
372
+ """Create a new schedule week for a space."""
373
+ wire_days = [_to_wire_schedule_week_day(day) for day in (days or [])]
374
+ diff = hds.ScheduleWeek(
375
+ header=hds.EntityMetadata(system_id=system_id),
376
+ relationships=hds.ScheduleWeekRelationships(space_id=space_id),
377
+ days=wire_days,
378
+ )
379
+ try:
380
+ result = await self._stub.CreateScheduleWeek(
381
+ hds.CreateScheduleWeekRequest(schedule_week=diff)
382
+ )
383
+ except grpc.aio.AioRpcError as exc:
384
+ raise QuiltError(f"CreateScheduleWeek failed: {exc.details()}") from exc
385
+ return ScheduleWeek.from_proto(result)
386
+
387
+ async def update_schedule_week(
388
+ self,
389
+ schedule_week_id: str,
390
+ system_id: str,
391
+ space_id: str,
392
+ days: Sequence[ScheduleWeekDay | hds.ScheduleWeekDay],
393
+ ) -> ScheduleWeek:
394
+ """Update an existing schedule week."""
395
+ wire_days = [_to_wire_schedule_week_day(day) for day in days]
396
+ diff = hds.ScheduleWeek(
397
+ header=hds.EntityMetadata(
398
+ object_id=schedule_week_id,
399
+ system_id=system_id,
400
+ ),
401
+ relationships=hds.ScheduleWeekRelationships(space_id=space_id),
402
+ days=wire_days,
403
+ )
404
+ try:
405
+ result = await self._stub.UpdateScheduleWeek(
406
+ hds.UpdateScheduleWeekRequest(schedule_week=diff)
407
+ )
408
+ except grpc.aio.AioRpcError as exc:
409
+ raise QuiltError(f"UpdateScheduleWeek failed: {exc.details()}") from exc
410
+ return ScheduleWeek.from_proto(result)
411
+
412
+ async def delete_schedule_day(self, schedule_day_id: str) -> None:
413
+ """Delete a schedule day program."""
414
+ try:
415
+ await self._stub.DeleteScheduleDay(
416
+ hds.DeleteScheduleDayRequest(schedule_day_id=schedule_day_id)
417
+ )
418
+ except grpc.aio.AioRpcError as exc:
419
+ raise QuiltError(f"DeleteScheduleDay failed: {exc.details()}") from exc
420
+
421
+ async def update_schedule_day(
422
+ self,
423
+ schedule_day_id: str,
424
+ system_id: str,
425
+ space_id: str,
426
+ name: str | None = None,
427
+ events: Sequence[ScheduleEvent | hds.ScheduleEvent] | None = None,
428
+ ) -> ScheduleDay:
429
+ """Update an existing schedule day (name and/or events)."""
430
+ diff = hds.ScheduleDay(
431
+ header=hds.EntityMetadata(
432
+ object_id=schedule_day_id,
433
+ system_id=system_id,
434
+ ),
435
+ relationships=hds.ScheduleDayRelationships(space_id=space_id),
436
+ )
437
+ if name is not None:
438
+ diff.attributes.CopyFrom(hds.ScheduleDayAttributes(name=name))
439
+ if events is not None:
440
+ diff.events.extend(_to_wire_schedule_event(event) for event in events)
441
+ try:
442
+ result = await self._stub.UpdateScheduleDay(
443
+ hds.UpdateScheduleDayRequest(schedule_day=diff)
444
+ )
445
+ except grpc.aio.AioRpcError as exc:
446
+ raise QuiltError(f"UpdateScheduleDay failed: {exc.details()}") from exc
447
+ return ScheduleDay.from_proto(result)
448
+
449
+ async def delete_schedule_week(self, schedule_week_id: str) -> None:
450
+ """Delete a schedule week."""
451
+ try:
452
+ await self._stub.DeleteScheduleWeek(
453
+ hds.DeleteScheduleWeekRequest(schedule_week_id=schedule_week_id)
454
+ )
455
+ except grpc.aio.AioRpcError as exc:
456
+ raise QuiltError(f"DeleteScheduleWeek failed: {exc.details()}") from exc
457
+
458
+ async def update_location_schedule_execution(
459
+ self,
460
+ location_id: str,
461
+ system_id: str,
462
+ paused: bool,
463
+ ) -> None:
464
+ """Pause or resume all schedules for a location (global switch).
465
+
466
+ Args:
467
+ paused: True to pause all schedules, False to resume.
468
+ """
469
+ execution = hds.SCHEDULE_EXECUTION_PAUSED if paused else hds.SCHEDULE_EXECUTION_RUNNING
470
+ diff = hds.Location(
471
+ header=hds.EntityMetadata(
472
+ object_id=location_id,
473
+ system_id=system_id,
474
+ ),
475
+ controls=hds.LocationControls(schedule_execution=execution),
476
+ )
477
+ try:
478
+ await self._stub.UpdateLocation(hds.UpdateLocationRequest(location=diff))
479
+ except grpc.aio.AioRpcError as exc:
480
+ raise QuiltError(f"UpdateLocation failed: {exc.details()}") from exc