uiprotect 7.0.2__py3-none-any.whl → 7.2.0__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.

Potentially problematic release.


This version of uiprotect might be problematic. Click here for more details.

uiprotect/api.py CHANGED
@@ -57,7 +57,7 @@ from .data import (
57
57
  create_from_unifi_dict,
58
58
  )
59
59
  from .data.base import ProtectModelWithId
60
- from .data.devices import Chime
60
+ from .data.devices import AiPort, Chime
61
61
  from .data.types import IteratorCallback, ProgressCallback
62
62
  from .exceptions import BadRequest, NotAuthorized, NvrError
63
63
  from .utils import (
@@ -1268,6 +1268,14 @@ class ProtectApiClient(BaseApiClient):
1268
1268
  """
1269
1269
  return cast(list[Chime], await self.get_devices(ModelType.CHIME, Chime))
1270
1270
 
1271
+ async def get_aiports(self) -> list[AiPort]:
1272
+ """
1273
+ Gets the list of aiports straight from the NVR.
1274
+
1275
+ The websocket is connected and running, you likely just want to use `self.bootstrap.aiports`
1276
+ """
1277
+ return cast(list[AiPort], await self.get_devices(ModelType.AIPORT, AiPort))
1278
+
1271
1279
  async def get_viewers(self) -> list[Viewer]:
1272
1280
  """
1273
1281
  Gets the list of viewers straight from the NVR.
@@ -1386,6 +1394,14 @@ class ProtectApiClient(BaseApiClient):
1386
1394
  """
1387
1395
  return cast(Chime, await self.get_device(ModelType.CHIME, device_id, Chime))
1388
1396
 
1397
+ async def get_aiport(self, device_id: str) -> AiPort:
1398
+ """
1399
+ Gets a AiPort straight from the NVR.
1400
+
1401
+ The websocket is connected and running, you likely just want to use `self.bootstrap.aiport[device_id]`
1402
+ """
1403
+ return cast(AiPort, await self.get_device(ModelType.AIPORT, device_id, AiPort))
1404
+
1389
1405
  async def get_viewer(self, device_id: str) -> Viewer:
1390
1406
  """
1391
1407
  Gets a viewer straight from the NVR.
@@ -1790,10 +1806,12 @@ class ProtectApiClient(BaseApiClient):
1790
1806
  *,
1791
1807
  volume: int | None = None,
1792
1808
  repeat_times: int | None = None,
1809
+ ringtone_id: str | None = None,
1810
+ track_no: int | None = None,
1793
1811
  ) -> None:
1794
1812
  """Plays chime tones on a chime"""
1795
1813
  data: dict[str, Any] | None = None
1796
- if volume or repeat_times:
1814
+ if volume or repeat_times or ringtone_id or track_no:
1797
1815
  chime = self.bootstrap.chimes.get(device_id)
1798
1816
  if chime is None:
1799
1817
  raise BadRequest("Invalid chime ID %s", device_id)
@@ -1801,8 +1819,11 @@ class ProtectApiClient(BaseApiClient):
1801
1819
  data = {
1802
1820
  "volume": volume or chime.volume,
1803
1821
  "repeatTimes": repeat_times or chime.repeat_times,
1804
- "trackNo": chime.track_no,
1822
+ "trackNo": track_no or chime.track_no,
1805
1823
  }
1824
+ if ringtone_id:
1825
+ data["ringtoneId"] = ringtone_id
1826
+ data.pop("trackNo", None)
1806
1827
 
1807
1828
  await self.api_request(
1808
1829
  f"chimes/{device_id}/play-speaker",
@@ -1814,6 +1835,16 @@ class ProtectApiClient(BaseApiClient):
1814
1835
  """Plays chime tones on a chime"""
1815
1836
  await self.api_request(f"chimes/{device_id}/play-buzzer", method="post")
1816
1837
 
1838
+ async def set_light_is_led_force_on(
1839
+ self, device_id: str, is_led_force_on: bool
1840
+ ) -> None:
1841
+ """Sets isLedForceOn for light.""" # workaround because forceOn doesnt work via websocket
1842
+ await self.api_request(
1843
+ f"lights/{device_id}",
1844
+ method="patch",
1845
+ json={"lightOnSettings": {"isLedForceOn": is_led_force_on}},
1846
+ )
1847
+
1817
1848
  async def clear_tamper_sensor(self, device_id: str) -> None:
1818
1849
  """Clears tamper status for sensor"""
1819
1850
  await self.api_request(f"sensors/{device_id}/clear-tamper-flag", method="post")
uiprotect/cli/__init__.py CHANGED
@@ -17,6 +17,7 @@ from ..data import Version, WSPacket
17
17
  from ..test_util import SampleDataGenerator
18
18
  from ..utils import RELEASE_CACHE, get_local_timezone, run_async
19
19
  from ..utils import profile_ws as profile_ws_job
20
+ from .aiports import app as aiports_app
20
21
  from .base import CliContext, OutputFormatEnum
21
22
  from .cameras import app as camera_app
22
23
  from .chimes import app as chime_app
@@ -128,6 +129,7 @@ app.add_typer(doorlock_app, name="doorlocks")
128
129
  app.add_typer(light_app, name="lights")
129
130
  app.add_typer(sensor_app, name="sensors")
130
131
  app.add_typer(viewer_app, name="viewers")
132
+ app.add_typer(aiports_app, name="aiports")
131
133
 
132
134
  if backup_app is not None:
133
135
  app.add_typer(backup_app, name="backup")
@@ -0,0 +1,59 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Optional
5
+
6
+ import typer
7
+
8
+ from ..api import ProtectApiClient
9
+ from ..cli import base
10
+ from ..data import AiPort
11
+
12
+ app = typer.Typer(rich_markup_mode="rich")
13
+
14
+ ARG_DEVICE_ID = typer.Argument(
15
+ None, help="ID of AiPort device to select for subcommands"
16
+ )
17
+
18
+
19
+ @dataclass
20
+ class AiPortContext(base.CliContext):
21
+ devices: dict[str, AiPort]
22
+ device: AiPort | None = None
23
+
24
+
25
+ ALL_COMMANDS, DEVICE_COMMANDS = base.init_common_commands(app)
26
+
27
+
28
+ @app.callback(invoke_without_command=True)
29
+ def main(ctx: typer.Context, device_id: Optional[str] = ARG_DEVICE_ID) -> None:
30
+ """
31
+ AiPort device CLI.
32
+
33
+ Returns full list of AiPorts without any arguments passed.
34
+ """
35
+ protect: ProtectApiClient = ctx.obj.protect
36
+ context = AiPortContext(
37
+ protect=ctx.obj.protect,
38
+ device=None,
39
+ devices=protect.bootstrap.aiports,
40
+ output_format=ctx.obj.output_format,
41
+ )
42
+ ctx.obj = context
43
+
44
+ if device_id is not None and device_id not in ALL_COMMANDS:
45
+ if (device := protect.bootstrap.aiports.get(device_id)) is None:
46
+ typer.secho("Invalid aiport ID", fg="red")
47
+ raise typer.Exit(1)
48
+ ctx.obj.device = device
49
+
50
+ if not ctx.invoked_subcommand:
51
+ if device_id in ALL_COMMANDS:
52
+ ctx.invoke(ALL_COMMANDS[device_id], ctx)
53
+ return
54
+
55
+ if ctx.obj.device is not None:
56
+ base.print_unifi_obj(ctx.obj.device, ctx.obj.output_format)
57
+ return
58
+
59
+ base.print_unifi_dict(ctx.obj.devices)
@@ -10,6 +10,7 @@ from .base import (
10
10
  from .bootstrap import Bootstrap
11
11
  from .convert import create_from_unifi_dict
12
12
  from .devices import (
13
+ AiPort,
13
14
  Bridge,
14
15
  Camera,
15
16
  CameraChannel,
@@ -85,6 +86,7 @@ __all__ = [
85
86
  "DEFAULT_TYPE",
86
87
  "NVR",
87
88
  "WS_HEADER_SIZE",
89
+ "AiPort",
88
90
  "AnalyticsOption",
89
91
  "AudioStyle",
90
92
  "Bootstrap",
@@ -23,12 +23,14 @@ from .base import (
23
23
  )
24
24
  from .convert import MODEL_TO_CLASS, create_from_unifi_dict
25
25
  from .devices import (
26
+ AiPort,
26
27
  Bridge,
27
28
  Camera,
28
29
  Chime,
29
30
  Doorlock,
30
31
  Light,
31
32
  ProtectAdoptableDeviceModel,
33
+ Ringtone,
32
34
  Sensor,
33
35
  Viewer,
34
36
  )
@@ -181,6 +183,8 @@ class Bootstrap(ProtectBaseObject):
181
183
  sensors: dict[str, Sensor]
182
184
  doorlocks: dict[str, Doorlock]
183
185
  chimes: dict[str, Chime]
186
+ aiports: dict[str, AiPort]
187
+ ringtones: list[Ringtone]
184
188
  last_update_id: str
185
189
 
186
190
  # TODO:
uiprotect/data/convert.py CHANGED
@@ -8,6 +8,7 @@ from uiprotect.data.base import ProtectModelWithId
8
8
 
9
9
  from ..exceptions import DataDecodeError
10
10
  from .devices import (
11
+ AiPort,
11
12
  Bridge,
12
13
  Camera,
13
14
  Chime,
@@ -40,6 +41,7 @@ MODEL_TO_CLASS: dict[str, type[ProtectModel]] = {
40
41
  ModelType.SENSOR: Sensor,
41
42
  ModelType.DOORLOCK: Doorlock,
42
43
  ModelType.CHIME: Chime,
44
+ ModelType.AIPORT: AiPort,
43
45
  ModelType.KEYRING: Keyring,
44
46
  ModelType.ULP_USER: UlpUser,
45
47
  }
uiprotect/data/devices.py CHANGED
@@ -976,6 +976,8 @@ class Camera(ProtectMotionDeviceModel):
976
976
  audio_settings: CameraAudioSettings | None = None
977
977
  # requires 5.0.33+
978
978
  is_third_party_camera: bool | None = None
979
+ # requires 5.1.78+
980
+ is_paired_with_ai_port: bool | None = None
979
981
  # TODO: used for adopting
980
982
  # apMac read only
981
983
  # apRssi read only
@@ -3382,3 +3384,16 @@ class Chime(ProtectAdoptableDeviceModel):
3382
3384
  raise BadRequest("Camera %s is not paired with chime", camera.id)
3383
3385
 
3384
3386
  await self.queue_update(callback)
3387
+
3388
+
3389
+ class AiPort(Camera):
3390
+ paired_cameras: list[str]
3391
+
3392
+
3393
+ class Ringtone(ProtectBaseObject):
3394
+ id: str
3395
+ name: str
3396
+ size: int
3397
+ is_default: bool
3398
+ nvr_mac: str
3399
+ model_key: str
uiprotect/data/types.py CHANGED
@@ -124,9 +124,11 @@ class ModelType(str, UnknownValuesEnumMixin, enum.Enum):
124
124
  DOORLOCK = "doorlock"
125
125
  SCHEDULE = "schedule"
126
126
  CHIME = "chime"
127
+ AIPORT = "aiport"
127
128
  DEVICE_GROUP = "deviceGroup"
128
129
  RECORDING_SCHEDULE = "recordingSchedule"
129
130
  ULP_USER = "ulpUser"
131
+ RINGTONE = "ringtone"
130
132
  KEYRING = "keyring"
131
133
  UNKNOWN = "unknown"
132
134
 
@@ -173,6 +175,7 @@ class ModelType(str, UnknownValuesEnumMixin, enum.Enum):
173
175
  ModelType.SENSOR,
174
176
  ModelType.DOORLOCK,
175
177
  ModelType.CHIME,
178
+ ModelType.AIPORT,
176
179
  )
177
180
 
178
181
  @classmethod
@@ -131,6 +131,7 @@ class SampleDataGenerator:
131
131
  "sensor": len(bootstrap["sensors"]),
132
132
  "doorlock": len(bootstrap["doorlocks"]),
133
133
  "chime": len(bootstrap["chimes"]),
134
+ "aiport": len(bootstrap["aiports"]),
134
135
  }
135
136
 
136
137
  self.log("Generating event data...")
@@ -283,6 +284,7 @@ class SampleDataGenerator:
283
284
  self.generate_sensor_data(),
284
285
  self.generate_lock_data(),
285
286
  self.generate_chime_data(),
287
+ self.generate_aiport_data(),
286
288
  self.generate_bridge_data(),
287
289
  self.generate_liveview_data(),
288
290
  )
@@ -469,6 +471,21 @@ class SampleDataGenerator:
469
471
  obj = await self.client.api_request_obj(f"chimes/{device_id}")
470
472
  await self.write_json_file("sample_chime", obj)
471
473
 
474
+ async def generate_aiport_data(self) -> None:
475
+ objs = await self.client.api_request_list("aiports")
476
+ device_id: str | None = None
477
+ for obj_dict in objs:
478
+ device_id = obj_dict["id"]
479
+ if is_online(obj_dict):
480
+ break
481
+
482
+ if device_id is None:
483
+ self.log("No aiport found. Skipping aiport endpoints...")
484
+ return
485
+
486
+ obj = await self.client.api_request_obj(f"aiports/{device_id}")
487
+ await self.write_json_file("sample_aiport", obj)
488
+
472
489
  async def generate_bridge_data(self) -> None:
473
490
  objs = await self.client.api_request_list("bridges")
474
491
  device_id: str | None = None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: uiprotect
3
- Version: 7.0.2
3
+ Version: 7.2.0
4
4
  Summary: Python API for Unifi Protect (Unofficial)
5
5
  Home-page: https://github.com/uilibs/uiprotect
6
6
  Author: UI Protect Maintainers
@@ -1,8 +1,9 @@
1
1
  uiprotect/__init__.py,sha256=Oz6i1tonIz4QWVnEPkbielJDJ3WQdwZVgYtjY4IwGAQ,636
2
2
  uiprotect/__main__.py,sha256=C_bHCOkv5qj6WMy-6ELoY3Y6HDhLxOa1a30CzmbZhsg,462
3
3
  uiprotect/_compat.py,sha256=HThmb1zQZCEssCxYYbQzFhJq8zYYlVaSnIEZabKc-6U,302
4
- uiprotect/api.py,sha256=qj6qEKLY3j25kNTL7Jlo-wwreOdmFzcV_MZxkNo7Oeg,69038
5
- uiprotect/cli/__init__.py,sha256=1MO8rJmjjAsfVx2x01gn5DJo8B64xdPGo6gRVJbWd18,8868
4
+ uiprotect/api.py,sha256=5oWPmVqMqNeug4eZFbyF0X7tq2pXseSLmKDeH3zrGso,70311
5
+ uiprotect/cli/__init__.py,sha256=gHo9G2WusvrWgnHhecT8Q2NLRXG0VS-rc9TKy4V5Kw8,8951
6
+ uiprotect/cli/aiports.py,sha256=wpEr2w_hY18CGpFiQM2Yc0FiVwG_1l2CzZhZLGNigvI,1576
6
7
  uiprotect/cli/backup.py,sha256=ZiS7RZnJGKI8TJKLW2cOUzkRM8nyTvE5Ov_jZZGtvSM,36708
7
8
  uiprotect/cli/base.py,sha256=GVHQMrI3thQ-4ixJlunTCfEMF90xCnt-bvRPMDupDss,7588
8
9
  uiprotect/cli/cameras.py,sha256=YvvMccQEYG3Wih0Ix8tan1R1vfaJ6cogg6YKWLzMUV8,16973
@@ -14,25 +15,25 @@ uiprotect/cli/liveviews.py,sha256=GU5z-ZLRBXHyspDKiJpiv-kbaBcvxK_-K70rPoqx2Ms,18
14
15
  uiprotect/cli/nvr.py,sha256=TwxEg2XT8jXAbOqv6gc7KFXELKadeItEDYweSL4_-e8,4260
15
16
  uiprotect/cli/sensors.py,sha256=fQtcDJCVxs4VbAqcavgBy2ABiVxAW3GXtna6_XFBp2k,8153
16
17
  uiprotect/cli/viewers.py,sha256=2cyrp104ffIvgT0wYGIO0G35QMkEbFe7fSVqLwDXQYQ,2171
17
- uiprotect/data/__init__.py,sha256=OcfuJl2qXfHcj_mdnrHhzZ5tEIZrw8auziX5IE7dn-I,2938
18
+ uiprotect/data/__init__.py,sha256=audwJBjxRiYdNPeYlP6iofFIOq3gyQzh6VpDsOCM2dQ,2964
18
19
  uiprotect/data/base.py,sha256=LRqmK60PKeDFgP2k5qpVj93AxEvdfC6kLk0Cqvt1W5k,35481
19
- uiprotect/data/bootstrap.py,sha256=oWjjWsLg8f_OERG_9h__5exB3qCmwI6KX4khBGZEzoc,23251
20
- uiprotect/data/convert.py,sha256=CDPkSMxSEhvDigmzmLFKpjrz0oa5FOvOdkNIHZrOZ4Q,2586
21
- uiprotect/data/devices.py,sha256=Brj9bT9oJPb5jqAY5vc2JhFG6m7DJt8u58qVe-oTw6M,113803
20
+ uiprotect/data/bootstrap.py,sha256=Umija3z2SI4RPJmbzZNolm3jw-GSP4a2d9Fd0xcmf9c,23338
21
+ uiprotect/data/convert.py,sha256=xEN878_hm0HZZCVYGwJSxcSp2as9zpkvsemVIibReOA,2628
22
+ uiprotect/data/devices.py,sha256=ls-paEN3RITVASL0Nezfevpt5ROvxnhbBGYg8dPfAyM,114061
22
23
  uiprotect/data/nvr.py,sha256=E18DgE0nXl9VZ_ULotTPcXSi3M1u3mWQsuZbY1gIajs,47490
23
- uiprotect/data/types.py,sha256=YCRqY5aMSFNX-X-3eqsHnUO5s1ZbPjKc5o2-eH12E34,19086
24
+ uiprotect/data/types.py,sha256=GVY6E88M41-_HjkIwVI7_EIObJSwH4c9BiunmEeo11Y,19164
24
25
  uiprotect/data/user.py,sha256=Del5LUmt5uCfAQMI9-kl_GaKm085oTLjxmcCrlEKXxc,10526
25
26
  uiprotect/data/websocket.py,sha256=m4EV1Qfh08eKOihy70ycViYgEQpeNSGZQJWdtGIYJDA,6791
26
27
  uiprotect/exceptions.py,sha256=kgn0cRM6lTtgLza09SDa3ZiX6ue1QqHCOogQ4qu6KTQ,965
27
28
  uiprotect/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
28
29
  uiprotect/release_cache.json,sha256=NamnSFy78hOWY0DPO87J9ELFCAN6NnVquv8gQO75ZG4,386
29
30
  uiprotect/stream.py,sha256=MWiTRFIhUfFLPA_csSrKl5-SkUbPZ2VhDu0XW2oVr-U,4800
30
- uiprotect/test_util/__init__.py,sha256=Ky8mTL61nhp5II2mxTKBAsSGvNqK8U_CfKC5AGwToAI,18704
31
+ uiprotect/test_util/__init__.py,sha256=HlQBgIgdtrvT-gQ5OWP92LbgVr_YzsD5NFImLRonUZk,19320
31
32
  uiprotect/test_util/anonymize.py,sha256=f-8ijU-_y9r-uAbhIPn0f0I6hzJpAkvJzc8UpWihObI,8478
32
33
  uiprotect/utils.py,sha256=q2YYQfxr0b3QEWMSP7SdpcJZbB4huEhpo8PbtnTbEFI,20481
33
34
  uiprotect/websocket.py,sha256=tEyenqblNXHcjWYuf4oRP1E7buNwx6zoECMwpBr-jig,8191
34
- uiprotect-7.0.2.dist-info/LICENSE,sha256=INx18jhdbVXMEiiBANeKEbrbz57ckgzxk5uutmmcxGk,1111
35
- uiprotect-7.0.2.dist-info/METADATA,sha256=rtp7lvceFYj6ZjMcu6s0RtFwTCZQ4KJrRgbfRr9aUNE,11142
36
- uiprotect-7.0.2.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
37
- uiprotect-7.0.2.dist-info/entry_points.txt,sha256=J78AUTPrTTxgI3s7SVgrmGqDP7piX2wuuEORzhDdVRA,47
38
- uiprotect-7.0.2.dist-info/RECORD,,
35
+ uiprotect-7.2.0.dist-info/LICENSE,sha256=INx18jhdbVXMEiiBANeKEbrbz57ckgzxk5uutmmcxGk,1111
36
+ uiprotect-7.2.0.dist-info/METADATA,sha256=_N35r5x3l7De3JMKKJv6kImajl786yjdJBMWHs8i_60,11142
37
+ uiprotect-7.2.0.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
38
+ uiprotect-7.2.0.dist-info/entry_points.txt,sha256=J78AUTPrTTxgI3s7SVgrmGqDP7piX2wuuEORzhDdVRA,47
39
+ uiprotect-7.2.0.dist-info/RECORD,,