uiprotect 0.1.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/__init__.py +13 -0
- uiprotect/__main__.py +24 -0
- uiprotect/api.py +1936 -0
- uiprotect/cli/__init__.py +314 -0
- uiprotect/cli/backup.py +1103 -0
- uiprotect/cli/base.py +238 -0
- uiprotect/cli/cameras.py +574 -0
- uiprotect/cli/chimes.py +180 -0
- uiprotect/cli/doorlocks.py +125 -0
- uiprotect/cli/events.py +258 -0
- uiprotect/cli/lights.py +119 -0
- uiprotect/cli/liveviews.py +65 -0
- uiprotect/cli/nvr.py +154 -0
- uiprotect/cli/sensors.py +278 -0
- uiprotect/cli/viewers.py +76 -0
- uiprotect/data/__init__.py +157 -0
- uiprotect/data/base.py +1116 -0
- uiprotect/data/bootstrap.py +634 -0
- uiprotect/data/convert.py +77 -0
- uiprotect/data/devices.py +3384 -0
- uiprotect/data/nvr.py +1520 -0
- uiprotect/data/types.py +630 -0
- uiprotect/data/user.py +236 -0
- uiprotect/data/websocket.py +236 -0
- uiprotect/exceptions.py +41 -0
- uiprotect/py.typed +0 -0
- uiprotect/release_cache.json +1 -0
- uiprotect/stream.py +166 -0
- uiprotect/test_util/__init__.py +531 -0
- uiprotect/test_util/anonymize.py +257 -0
- uiprotect/utils.py +610 -0
- uiprotect/websocket.py +225 -0
- uiprotect-0.1.0.dist-info/LICENSE +23 -0
- uiprotect-0.1.0.dist-info/METADATA +245 -0
- uiprotect-0.1.0.dist-info/RECORD +37 -0
- uiprotect-0.1.0.dist-info/WHEEL +4 -0
- uiprotect-0.1.0.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
|
|
8
|
+
from uiprotect.api import ProtectApiClient
|
|
9
|
+
from uiprotect.cli import base
|
|
10
|
+
from uiprotect.data import Liveview
|
|
11
|
+
|
|
12
|
+
app = typer.Typer(rich_markup_mode="rich")
|
|
13
|
+
|
|
14
|
+
ARG_DEVICE_ID = typer.Argument(None, help="ID of liveview to select for subcommands")
|
|
15
|
+
ALL_COMMANDS = {"list-ids": app.command(name="list-ids")(base.list_ids)}
|
|
16
|
+
|
|
17
|
+
app.command(name="protect-url")(base.protect_url)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class LiveviewContext(base.CliContext):
|
|
22
|
+
devices: dict[str, Liveview]
|
|
23
|
+
device: Liveview | None = None
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@app.callback(invoke_without_command=True)
|
|
27
|
+
def main(ctx: typer.Context, device_id: Optional[str] = ARG_DEVICE_ID) -> None:
|
|
28
|
+
"""
|
|
29
|
+
Liveviews CLI.
|
|
30
|
+
|
|
31
|
+
Returns full list of Liveviews without any arguments passed.
|
|
32
|
+
"""
|
|
33
|
+
protect: ProtectApiClient = ctx.obj.protect
|
|
34
|
+
context = LiveviewContext(
|
|
35
|
+
protect=ctx.obj.protect,
|
|
36
|
+
device=None,
|
|
37
|
+
devices=protect.bootstrap.liveviews,
|
|
38
|
+
output_format=ctx.obj.output_format,
|
|
39
|
+
)
|
|
40
|
+
ctx.obj = context
|
|
41
|
+
|
|
42
|
+
if device_id is not None and device_id not in ALL_COMMANDS:
|
|
43
|
+
if (device := protect.bootstrap.liveviews.get(device_id)) is None:
|
|
44
|
+
typer.secho("Invalid liveview ID", fg="red")
|
|
45
|
+
raise typer.Exit(1)
|
|
46
|
+
ctx.obj.device = device
|
|
47
|
+
|
|
48
|
+
if not ctx.invoked_subcommand:
|
|
49
|
+
if device_id in ALL_COMMANDS:
|
|
50
|
+
ctx.invoke(ALL_COMMANDS[device_id], ctx)
|
|
51
|
+
return
|
|
52
|
+
|
|
53
|
+
if ctx.obj.device is not None:
|
|
54
|
+
base.print_unifi_obj(ctx.obj.device, ctx.obj.output_format)
|
|
55
|
+
return
|
|
56
|
+
|
|
57
|
+
base.print_unifi_dict(ctx.obj.devices)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@app.command()
|
|
61
|
+
def owner(ctx: typer.Context) -> None:
|
|
62
|
+
"""Gets the owner for the liveview."""
|
|
63
|
+
base.require_device_id(ctx)
|
|
64
|
+
obj: Liveview = ctx.obj.device
|
|
65
|
+
base.print_unifi_obj(obj.owner, ctx.obj.output_format)
|
uiprotect/cli/nvr.py
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from datetime import timedelta
|
|
5
|
+
|
|
6
|
+
import orjson
|
|
7
|
+
import typer
|
|
8
|
+
|
|
9
|
+
from uiprotect.cli import base
|
|
10
|
+
from uiprotect.data import NVR, AnalyticsOption
|
|
11
|
+
|
|
12
|
+
app = typer.Typer(rich_markup_mode="rich")
|
|
13
|
+
|
|
14
|
+
ARG_TIMEOUT = typer.Argument(..., help="Timeout (in seconds)")
|
|
15
|
+
ARG_DOORBELL_MESSAGE = typer.Argument(..., help="ASCII only. Max length 30")
|
|
16
|
+
OPTION_ENABLE_SMART = typer.Option(
|
|
17
|
+
False,
|
|
18
|
+
"--enable-smart",
|
|
19
|
+
help="Automatically enable smart detections",
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class NVRContext(base.CliContext):
|
|
25
|
+
device: NVR
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@app.callback(invoke_without_command=True)
|
|
29
|
+
def main(ctx: typer.Context) -> None:
|
|
30
|
+
"""
|
|
31
|
+
NVR device CLI.
|
|
32
|
+
|
|
33
|
+
Return NVR object without any arguments passed.
|
|
34
|
+
"""
|
|
35
|
+
context = NVRContext(
|
|
36
|
+
protect=ctx.obj.protect,
|
|
37
|
+
device=ctx.obj.protect.bootstrap.nvr,
|
|
38
|
+
output_format=ctx.obj.output_format,
|
|
39
|
+
)
|
|
40
|
+
ctx.obj = context
|
|
41
|
+
|
|
42
|
+
if not ctx.invoked_subcommand:
|
|
43
|
+
base.print_unifi_obj(context.device, ctx.obj.output_format)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
app.command(name="protect-url")(base.protect_url)
|
|
47
|
+
app.command(name="reboot")(base.reboot)
|
|
48
|
+
app.command(name="set-name")(base.set_name)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@app.command()
|
|
52
|
+
def set_analytics(ctx: typer.Context, value: AnalyticsOption) -> None:
|
|
53
|
+
"""Sets analytics collection for NVR."""
|
|
54
|
+
nvr: NVR = ctx.obj.device
|
|
55
|
+
base.run(ctx, nvr.set_analytics(value))
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@app.command()
|
|
59
|
+
def set_default_reset_timeout(ctx: typer.Context, timeout: int = ARG_TIMEOUT) -> None:
|
|
60
|
+
"""
|
|
61
|
+
Sets default message reset timeout.
|
|
62
|
+
|
|
63
|
+
This is how long until a custom message is reset back to the default message if no
|
|
64
|
+
timeout is passed in when the custom message is set.
|
|
65
|
+
"""
|
|
66
|
+
nvr: NVR = ctx.obj.device
|
|
67
|
+
base.run(ctx, nvr.set_default_reset_timeout(timedelta(seconds=timeout)))
|
|
68
|
+
base.print_unifi_obj(nvr.doorbell_settings, ctx.obj.output_format)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@app.command()
|
|
72
|
+
def set_default_doorbell_message(
|
|
73
|
+
ctx: typer.Context,
|
|
74
|
+
msg: str = ARG_DOORBELL_MESSAGE,
|
|
75
|
+
) -> None:
|
|
76
|
+
"""
|
|
77
|
+
Sets default message for doorbell.
|
|
78
|
+
|
|
79
|
+
This is the message that is set when a custom doorbell message times out or an empty
|
|
80
|
+
one is set.
|
|
81
|
+
"""
|
|
82
|
+
nvr: NVR = ctx.obj.device
|
|
83
|
+
base.run(ctx, nvr.set_default_doorbell_message(msg))
|
|
84
|
+
base.print_unifi_obj(nvr.doorbell_settings, ctx.obj.output_format)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@app.command()
|
|
88
|
+
def add_custom_doorbell_message(
|
|
89
|
+
ctx: typer.Context,
|
|
90
|
+
msg: str = ARG_DOORBELL_MESSAGE,
|
|
91
|
+
) -> None:
|
|
92
|
+
"""Adds a custom doorbell message."""
|
|
93
|
+
nvr: NVR = ctx.obj.device
|
|
94
|
+
base.run(ctx, nvr.add_custom_doorbell_message(msg))
|
|
95
|
+
base.print_unifi_obj(nvr.doorbell_settings, ctx.obj.output_format)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@app.command()
|
|
99
|
+
def remove_custom_doorbell_message(
|
|
100
|
+
ctx: typer.Context,
|
|
101
|
+
msg: str = ARG_DOORBELL_MESSAGE,
|
|
102
|
+
) -> None:
|
|
103
|
+
"""Removes a custom doorbell message."""
|
|
104
|
+
nvr: NVR = ctx.obj.device
|
|
105
|
+
base.run(ctx, nvr.remove_custom_doorbell_message(msg))
|
|
106
|
+
base.print_unifi_obj(nvr.doorbell_settings, ctx.obj.output_format)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@app.command()
|
|
110
|
+
def update(ctx: typer.Context, data: str) -> None:
|
|
111
|
+
"""Updates the NVR."""
|
|
112
|
+
nvr: NVR = ctx.obj.device
|
|
113
|
+
base.run(ctx, nvr.api.update_nvr(orjson.loads(data)))
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@app.command()
|
|
117
|
+
def set_smart_detections(ctx: typer.Context, value: bool) -> None:
|
|
118
|
+
"""Set if smart detections are globally enabled or not."""
|
|
119
|
+
nvr: NVR = ctx.obj.device
|
|
120
|
+
base.run(ctx, nvr.set_smart_detections(value))
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@app.command()
|
|
124
|
+
def set_face_recognition(
|
|
125
|
+
ctx: typer.Context,
|
|
126
|
+
value: bool,
|
|
127
|
+
enable_smart: bool = OPTION_ENABLE_SMART,
|
|
128
|
+
) -> None:
|
|
129
|
+
"""Set if face detections is enabled. Requires smart detections to be enabled."""
|
|
130
|
+
nvr: NVR = ctx.obj.device
|
|
131
|
+
|
|
132
|
+
async def callback() -> None:
|
|
133
|
+
if enable_smart:
|
|
134
|
+
await nvr.set_smart_detections(True)
|
|
135
|
+
await nvr.set_face_recognition(value)
|
|
136
|
+
|
|
137
|
+
base.run(ctx, callback())
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
@app.command()
|
|
141
|
+
def set_license_plate_recognition(
|
|
142
|
+
ctx: typer.Context,
|
|
143
|
+
value: bool,
|
|
144
|
+
enable_smart: bool = OPTION_ENABLE_SMART,
|
|
145
|
+
) -> None:
|
|
146
|
+
"""Set if license plate detections is enabled. Requires smart detections to be enabled."""
|
|
147
|
+
nvr: NVR = ctx.obj.device
|
|
148
|
+
|
|
149
|
+
async def callback() -> None:
|
|
150
|
+
if enable_smart:
|
|
151
|
+
await nvr.set_smart_detections(True)
|
|
152
|
+
await nvr.set_license_plate_recognition(value)
|
|
153
|
+
|
|
154
|
+
base.run(ctx, callback())
|
uiprotect/cli/sensors.py
ADDED
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
|
|
8
|
+
from uiprotect.api import ProtectApiClient
|
|
9
|
+
from uiprotect.cli import base
|
|
10
|
+
from uiprotect.data import MountType, Sensor
|
|
11
|
+
|
|
12
|
+
app = typer.Typer(rich_markup_mode="rich")
|
|
13
|
+
|
|
14
|
+
ARG_DEVICE_ID = typer.Argument(None, help="ID of sensor to select for subcommands")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class SensorContext(base.CliContext):
|
|
19
|
+
devices: dict[str, Sensor]
|
|
20
|
+
device: Sensor | None = None
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
ALL_COMMANDS, DEVICE_COMMANDS = base.init_common_commands(app)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@app.callback(invoke_without_command=True)
|
|
27
|
+
def main(ctx: typer.Context, device_id: Optional[str] = ARG_DEVICE_ID) -> None:
|
|
28
|
+
"""
|
|
29
|
+
Sensors device CLI.
|
|
30
|
+
|
|
31
|
+
Returns full list of Sensors without any arguments passed.
|
|
32
|
+
"""
|
|
33
|
+
protect: ProtectApiClient = ctx.obj.protect
|
|
34
|
+
context = SensorContext(
|
|
35
|
+
protect=ctx.obj.protect,
|
|
36
|
+
device=None,
|
|
37
|
+
devices=protect.bootstrap.sensors,
|
|
38
|
+
output_format=ctx.obj.output_format,
|
|
39
|
+
)
|
|
40
|
+
ctx.obj = context
|
|
41
|
+
|
|
42
|
+
if device_id is not None and device_id not in ALL_COMMANDS:
|
|
43
|
+
if (device := protect.bootstrap.sensors.get(device_id)) is None:
|
|
44
|
+
typer.secho("Invalid sensor ID", fg="red")
|
|
45
|
+
raise typer.Exit(1)
|
|
46
|
+
ctx.obj.device = device
|
|
47
|
+
|
|
48
|
+
if not ctx.invoked_subcommand:
|
|
49
|
+
if device_id in ALL_COMMANDS:
|
|
50
|
+
ctx.invoke(ALL_COMMANDS[device_id], ctx)
|
|
51
|
+
return
|
|
52
|
+
|
|
53
|
+
if ctx.obj.device is not None:
|
|
54
|
+
base.print_unifi_obj(ctx.obj.device, ctx.obj.output_format)
|
|
55
|
+
return
|
|
56
|
+
|
|
57
|
+
base.print_unifi_dict(ctx.obj.devices)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@app.command()
|
|
61
|
+
def camera(ctx: typer.Context, camera_id: Optional[str] = typer.Argument(None)) -> None:
|
|
62
|
+
"""Returns or sets tha paired camera for a sensor."""
|
|
63
|
+
base.require_device_id(ctx)
|
|
64
|
+
obj: Sensor = ctx.obj.device
|
|
65
|
+
|
|
66
|
+
if camera_id is None:
|
|
67
|
+
base.print_unifi_obj(obj.camera, ctx.obj.output_format)
|
|
68
|
+
else:
|
|
69
|
+
protect: ProtectApiClient = ctx.obj.protect
|
|
70
|
+
if (camera_obj := protect.bootstrap.cameras.get(camera_id)) is None:
|
|
71
|
+
typer.secho("Invalid camera ID")
|
|
72
|
+
raise typer.Exit(1)
|
|
73
|
+
base.run(ctx, obj.set_paired_camera(camera_obj))
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@app.command()
|
|
77
|
+
def is_tampering_detected(ctx: typer.Context) -> None:
|
|
78
|
+
"""Returns if tampering is detected for sensor"""
|
|
79
|
+
base.require_device_id(ctx)
|
|
80
|
+
obj: Sensor = ctx.obj.device
|
|
81
|
+
base.json_output(obj.is_tampering_detected)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@app.command()
|
|
85
|
+
def is_alarm_detected(ctx: typer.Context) -> None:
|
|
86
|
+
"""Returns if alarm is detected for sensor"""
|
|
87
|
+
base.require_device_id(ctx)
|
|
88
|
+
obj: Sensor = ctx.obj.device
|
|
89
|
+
base.json_output(obj.is_alarm_detected)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@app.command()
|
|
93
|
+
def is_contact_enabled(ctx: typer.Context) -> None:
|
|
94
|
+
"""Returns if contact sensor is enabled for sensor"""
|
|
95
|
+
base.require_device_id(ctx)
|
|
96
|
+
obj: Sensor = ctx.obj.device
|
|
97
|
+
base.json_output(obj.is_contact_sensor_enabled)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@app.command()
|
|
101
|
+
def is_motion_enabled(ctx: typer.Context) -> None:
|
|
102
|
+
"""Returns if motion sensor is enabled for sensor"""
|
|
103
|
+
base.require_device_id(ctx)
|
|
104
|
+
obj: Sensor = ctx.obj.device
|
|
105
|
+
base.json_output(obj.is_contact_sensor_enabled)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@app.command()
|
|
109
|
+
def is_alarm_enabled(ctx: typer.Context) -> None:
|
|
110
|
+
"""Returns if alarm sensor is enabled for sensor"""
|
|
111
|
+
base.require_device_id(ctx)
|
|
112
|
+
obj: Sensor = ctx.obj.device
|
|
113
|
+
base.json_output(obj.is_alarm_sensor_enabled)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@app.command()
|
|
117
|
+
def is_light_enabled(ctx: typer.Context) -> None:
|
|
118
|
+
"""Returns if light sensor is enabled for sensor"""
|
|
119
|
+
base.require_device_id(ctx)
|
|
120
|
+
obj: Sensor = ctx.obj.device
|
|
121
|
+
base.json_output(obj.is_light_sensor_enabled)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@app.command()
|
|
125
|
+
def is_temperature_enabled(ctx: typer.Context) -> None:
|
|
126
|
+
"""Returns if temperature sensor is enabled for sensor"""
|
|
127
|
+
base.require_device_id(ctx)
|
|
128
|
+
obj: Sensor = ctx.obj.device
|
|
129
|
+
base.json_output(obj.is_temperature_sensor_enabled)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
@app.command()
|
|
133
|
+
def is_humidity_enabled(ctx: typer.Context) -> None:
|
|
134
|
+
"""Returns if humidity sensor is enabled for sensor"""
|
|
135
|
+
base.require_device_id(ctx)
|
|
136
|
+
obj: Sensor = ctx.obj.device
|
|
137
|
+
base.json_output(obj.is_humidity_sensor_enabled)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
@app.command()
|
|
141
|
+
def set_status_light(ctx: typer.Context, enabled: bool) -> None:
|
|
142
|
+
"""Sets status light for sensor device."""
|
|
143
|
+
base.require_device_id(ctx)
|
|
144
|
+
obj: Sensor = ctx.obj.device
|
|
145
|
+
|
|
146
|
+
base.run(ctx, obj.set_status_light(enabled))
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
@app.command()
|
|
150
|
+
def set_mount_type(ctx: typer.Context, mount_type: MountType) -> None:
|
|
151
|
+
"""Sets mount type for sensor device."""
|
|
152
|
+
base.require_device_id(ctx)
|
|
153
|
+
obj: Sensor = ctx.obj.device
|
|
154
|
+
|
|
155
|
+
base.run(ctx, obj.set_mount_type(mount_type))
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
@app.command()
|
|
159
|
+
def set_motion(ctx: typer.Context, enabled: bool) -> None:
|
|
160
|
+
"""Sets motion sensor status for sensor device."""
|
|
161
|
+
base.require_device_id(ctx)
|
|
162
|
+
obj: Sensor = ctx.obj.device
|
|
163
|
+
|
|
164
|
+
base.run(ctx, obj.set_motion_status(enabled))
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
@app.command()
|
|
168
|
+
def set_temperature(ctx: typer.Context, enabled: bool) -> None:
|
|
169
|
+
"""Sets temperature sensor status for sensor device."""
|
|
170
|
+
base.require_device_id(ctx)
|
|
171
|
+
obj: Sensor = ctx.obj.device
|
|
172
|
+
|
|
173
|
+
base.run(ctx, obj.set_temperature_status(enabled))
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
@app.command()
|
|
177
|
+
def set_humidity(ctx: typer.Context, enabled: bool) -> None:
|
|
178
|
+
"""Sets humidity sensor status for sensor device."""
|
|
179
|
+
base.require_device_id(ctx)
|
|
180
|
+
obj: Sensor = ctx.obj.device
|
|
181
|
+
|
|
182
|
+
base.run(ctx, obj.set_humidity_status(enabled))
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
@app.command()
|
|
186
|
+
def set_light(ctx: typer.Context, enabled: bool) -> None:
|
|
187
|
+
"""Sets light sensor status for sensor device."""
|
|
188
|
+
base.require_device_id(ctx)
|
|
189
|
+
obj: Sensor = ctx.obj.device
|
|
190
|
+
|
|
191
|
+
base.run(ctx, obj.set_light_status(enabled))
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
@app.command()
|
|
195
|
+
def set_alarm(ctx: typer.Context, enabled: bool) -> None:
|
|
196
|
+
"""Sets alarm sensor status for sensor device."""
|
|
197
|
+
base.require_device_id(ctx)
|
|
198
|
+
obj: Sensor = ctx.obj.device
|
|
199
|
+
|
|
200
|
+
base.run(ctx, obj.set_alarm_status(enabled))
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
@app.command()
|
|
204
|
+
def set_motion_sensitivity(
|
|
205
|
+
ctx: typer.Context,
|
|
206
|
+
sensitivity: int = typer.Argument(..., min=0, max=100),
|
|
207
|
+
) -> None:
|
|
208
|
+
"""Sets motion sensitivity for the sensor."""
|
|
209
|
+
base.require_device_id(ctx)
|
|
210
|
+
obj: Sensor = ctx.obj.device
|
|
211
|
+
|
|
212
|
+
base.run(ctx, obj.set_motion_sensitivity(sensitivity))
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
@app.command()
|
|
216
|
+
def set_temperature_range(
|
|
217
|
+
ctx: typer.Context,
|
|
218
|
+
low: float = typer.Argument(..., min=0, max=44),
|
|
219
|
+
high: float = typer.Argument(..., min=1, max=45),
|
|
220
|
+
) -> None:
|
|
221
|
+
"""Sets temperature safe range (in °C). Anything out side of range will trigger event."""
|
|
222
|
+
base.require_device_id(ctx)
|
|
223
|
+
obj: Sensor = ctx.obj.device
|
|
224
|
+
|
|
225
|
+
base.run(ctx, obj.set_temperature_safe_range(low, high))
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
@app.command()
|
|
229
|
+
def set_humidity_range(
|
|
230
|
+
ctx: typer.Context,
|
|
231
|
+
low: float = typer.Argument(..., min=1, max=98),
|
|
232
|
+
high: float = typer.Argument(..., min=2, max=99),
|
|
233
|
+
) -> None:
|
|
234
|
+
"""Sets humidity safe range (in relative % humidity). Anything out side of range will trigger event."""
|
|
235
|
+
base.require_device_id(ctx)
|
|
236
|
+
obj: Sensor = ctx.obj.device
|
|
237
|
+
|
|
238
|
+
base.run(ctx, obj.set_humidity_safe_range(low, high))
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
@app.command()
|
|
242
|
+
def set_light_range(
|
|
243
|
+
ctx: typer.Context,
|
|
244
|
+
low: float = typer.Argument(..., min=1, max=999),
|
|
245
|
+
high: float = typer.Argument(..., min=2, max=1000),
|
|
246
|
+
) -> None:
|
|
247
|
+
"""Sets light safe range (in lux). Anything out side of range will trigger event."""
|
|
248
|
+
base.require_device_id(ctx)
|
|
249
|
+
obj: Sensor = ctx.obj.device
|
|
250
|
+
|
|
251
|
+
base.run(ctx, obj.set_light_safe_range(low, high))
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
@app.command()
|
|
255
|
+
def remove_temperature_range(ctx: typer.Context) -> None:
|
|
256
|
+
"""Removes temperature safe ranges so events will no longer fire."""
|
|
257
|
+
base.require_device_id(ctx)
|
|
258
|
+
obj: Sensor = ctx.obj.device
|
|
259
|
+
|
|
260
|
+
base.run(ctx, obj.remove_temperature_safe_range())
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
@app.command()
|
|
264
|
+
def remove_humidity_range(ctx: typer.Context) -> None:
|
|
265
|
+
"""Removes humidity safe ranges so events will no longer fire."""
|
|
266
|
+
base.require_device_id(ctx)
|
|
267
|
+
obj: Sensor = ctx.obj.device
|
|
268
|
+
|
|
269
|
+
base.run(ctx, obj.remove_humidity_safe_range())
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
@app.command()
|
|
273
|
+
def remove_light_range(ctx: typer.Context) -> None:
|
|
274
|
+
"""Removes light safe ranges so events will no longer fire."""
|
|
275
|
+
base.require_device_id(ctx)
|
|
276
|
+
obj: Sensor = ctx.obj.device
|
|
277
|
+
|
|
278
|
+
base.run(ctx, obj.remove_light_safe_range())
|
uiprotect/cli/viewers.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
|
|
8
|
+
from uiprotect.api import ProtectApiClient
|
|
9
|
+
from uiprotect.cli import base
|
|
10
|
+
from uiprotect.data import Viewer
|
|
11
|
+
|
|
12
|
+
app = typer.Typer(rich_markup_mode="rich")
|
|
13
|
+
|
|
14
|
+
ARG_DEVICE_ID = typer.Argument(None, help="ID of viewer to select for subcommands")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class ViewerContext(base.CliContext):
|
|
19
|
+
devices: dict[str, Viewer]
|
|
20
|
+
device: Viewer | None = None
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
ALL_COMMANDS, DEVICE_COMMANDS = base.init_common_commands(app)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@app.callback(invoke_without_command=True)
|
|
27
|
+
def main(ctx: typer.Context, device_id: Optional[str] = ARG_DEVICE_ID) -> None:
|
|
28
|
+
"""
|
|
29
|
+
Viewers device CLI.
|
|
30
|
+
|
|
31
|
+
Returns full list of Viewers without any arguments passed.
|
|
32
|
+
"""
|
|
33
|
+
protect: ProtectApiClient = ctx.obj.protect
|
|
34
|
+
context = ViewerContext(
|
|
35
|
+
protect=ctx.obj.protect,
|
|
36
|
+
device=None,
|
|
37
|
+
devices=protect.bootstrap.viewers,
|
|
38
|
+
output_format=ctx.obj.output_format,
|
|
39
|
+
)
|
|
40
|
+
ctx.obj = context
|
|
41
|
+
|
|
42
|
+
if device_id is not None and device_id not in ALL_COMMANDS:
|
|
43
|
+
if (device := protect.bootstrap.viewers.get(device_id)) is None:
|
|
44
|
+
typer.secho("Invalid viewer ID", fg="red")
|
|
45
|
+
raise typer.Exit(1)
|
|
46
|
+
ctx.obj.device = device
|
|
47
|
+
|
|
48
|
+
if not ctx.invoked_subcommand:
|
|
49
|
+
if device_id in ALL_COMMANDS:
|
|
50
|
+
ctx.invoke(ALL_COMMANDS[device_id], ctx)
|
|
51
|
+
return
|
|
52
|
+
|
|
53
|
+
if ctx.obj.device is not None:
|
|
54
|
+
base.print_unifi_obj(ctx.obj.device, ctx.obj.output_format)
|
|
55
|
+
return
|
|
56
|
+
|
|
57
|
+
base.print_unifi_dict(ctx.obj.devices)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@app.command()
|
|
61
|
+
def liveview(
|
|
62
|
+
ctx: typer.Context,
|
|
63
|
+
liveview_id: Optional[str] = typer.Argument(None),
|
|
64
|
+
) -> None:
|
|
65
|
+
"""Returns or sets the current liveview."""
|
|
66
|
+
base.require_device_id(ctx)
|
|
67
|
+
obj: Viewer = ctx.obj.device
|
|
68
|
+
|
|
69
|
+
if liveview_id is None:
|
|
70
|
+
base.print_unifi_obj(obj.liveview, ctx.obj.output_format)
|
|
71
|
+
else:
|
|
72
|
+
protect: ProtectApiClient = ctx.obj.protect
|
|
73
|
+
if (liveview_obj := protect.bootstrap.liveviews.get(liveview_id)) is None:
|
|
74
|
+
typer.secho("Invalid liveview ID")
|
|
75
|
+
raise typer.Exit(1)
|
|
76
|
+
base.run(ctx, obj.set_liveview(liveview_obj))
|