camera-ui-sdk 1.4.2__tar.gz → 1.4.4__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.
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/PKG-INFO +4 -4
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/camera_ui_sdk/camera/device.py +16 -1
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/camera_ui_sdk/internal/__init__.py +1 -9
- camera_ui_sdk-1.4.4/camera_ui_sdk/internal/camera_config_internal.py +29 -0
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/camera_ui_sdk/internal/camera_enums.py +0 -2
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/camera_ui_sdk/internal/camera_wire.py +0 -4
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/camera_ui_sdk/internal/event_emitter.py +0 -24
- camera_ui_sdk-1.4.4/camera_ui_sdk/internal/manager_rpc.py +15 -0
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/camera_ui_sdk/internal/sensor_rpc.py +2 -31
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/camera_ui_sdk/internal/shared_utils.py +0 -15
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/camera_ui_sdk/internal/streaming_internal.py +0 -5
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/camera_ui_sdk/manager/__init__.py +2 -0
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/camera_ui_sdk/observable/__init__.py +0 -1
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/camera_ui_sdk/sensor/base.py +1 -14
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/camera_ui_sdk/sensor/classifier.py +1 -2
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/camera_ui_sdk/sensor/face.py +1 -2
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/camera_ui_sdk/sensor/license_plate.py +1 -2
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/camera_ui_sdk.egg-info/PKG-INFO +4 -4
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/camera_ui_sdk.egg-info/requires.txt +3 -3
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/pyproject.toml +5 -5
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/tests/test_event_emitter.py +2 -26
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/tests/test_observable.py +0 -25
- camera_ui_sdk-1.4.2/camera_ui_sdk/internal/camera_config_internal.py +0 -50
- camera_ui_sdk-1.4.2/camera_ui_sdk/internal/manager_rpc.py +0 -24
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/LICENSE.md +0 -0
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/README.md +0 -0
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/camera_ui_sdk/__init__.py +0 -0
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/camera_ui_sdk/camera/__init__.py +0 -0
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/camera_ui_sdk/camera/config.py +0 -0
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/camera_ui_sdk/camera/detection.py +0 -0
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/camera_ui_sdk/camera/enums.py +0 -0
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/camera_ui_sdk/camera/events.py +0 -0
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/camera_ui_sdk/camera/frames.py +0 -0
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/camera_ui_sdk/camera/notification.py +0 -0
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/camera_ui_sdk/camera/recording.py +0 -0
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/camera_ui_sdk/camera/streaming.py +0 -0
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/camera_ui_sdk/plugin/__init__.py +0 -0
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/camera_ui_sdk/plugin/api.py +0 -0
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/camera_ui_sdk/plugin/assistant.py +0 -0
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/camera_ui_sdk/plugin/assistant_models.py +0 -0
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/camera_ui_sdk/plugin/contract.py +0 -0
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/camera_ui_sdk/plugin/helper.py +0 -0
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/camera_ui_sdk/plugin/interfaces.py +0 -0
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/camera_ui_sdk/plugin/notifier.py +0 -0
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/camera_ui_sdk/plugin/oauth.py +0 -0
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/camera_ui_sdk/py.typed +0 -0
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/camera_ui_sdk/sensor/__init__.py +0 -0
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/camera_ui_sdk/sensor/audio.py +0 -0
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/camera_ui_sdk/sensor/battery.py +0 -0
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/camera_ui_sdk/sensor/carbon_dioxide.py +0 -0
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/camera_ui_sdk/sensor/carbon_monoxide.py +0 -0
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/camera_ui_sdk/sensor/clip.py +0 -0
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/camera_ui_sdk/sensor/cold.py +0 -0
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/camera_ui_sdk/sensor/contact.py +0 -0
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/camera_ui_sdk/sensor/detection.py +0 -0
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/camera_ui_sdk/sensor/doorbell.py +0 -0
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/camera_ui_sdk/sensor/face_embedder.py +0 -0
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/camera_ui_sdk/sensor/garage.py +0 -0
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/camera_ui_sdk/sensor/gas.py +0 -0
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/camera_ui_sdk/sensor/heat.py +0 -0
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/camera_ui_sdk/sensor/humidity.py +0 -0
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/camera_ui_sdk/sensor/illuminance.py +0 -0
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/camera_ui_sdk/sensor/leak.py +0 -0
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/camera_ui_sdk/sensor/light.py +0 -0
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/camera_ui_sdk/sensor/lock.py +0 -0
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/camera_ui_sdk/sensor/motion.py +0 -0
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/camera_ui_sdk/sensor/object.py +0 -0
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/camera_ui_sdk/sensor/occupancy.py +0 -0
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/camera_ui_sdk/sensor/person_embedder.py +0 -0
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/camera_ui_sdk/sensor/power.py +0 -0
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/camera_ui_sdk/sensor/problem.py +0 -0
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/camera_ui_sdk/sensor/ptz.py +0 -0
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/camera_ui_sdk/sensor/security_system.py +0 -0
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/camera_ui_sdk/sensor/segmenter.py +0 -0
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/camera_ui_sdk/sensor/siren.py +0 -0
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/camera_ui_sdk/sensor/smoke.py +0 -0
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/camera_ui_sdk/sensor/spec.py +0 -0
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/camera_ui_sdk/sensor/switch.py +0 -0
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/camera_ui_sdk/sensor/tamper.py +0 -0
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/camera_ui_sdk/sensor/temperature.py +0 -0
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/camera_ui_sdk/sensor/vibration.py +0 -0
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/camera_ui_sdk/storage/__init__.py +0 -0
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/camera_ui_sdk/types.py +0 -0
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/camera_ui_sdk.egg-info/SOURCES.txt +0 -0
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/camera_ui_sdk.egg-info/dependency_links.txt +0 -0
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/camera_ui_sdk.egg-info/top_level.txt +0 -0
- {camera_ui_sdk-1.4.2 → camera_ui_sdk-1.4.4}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: camera-ui-sdk
|
|
3
|
-
Version: 1.4.
|
|
3
|
+
Version: 1.4.4
|
|
4
4
|
Summary: camera.ui sdk
|
|
5
5
|
Author-email: seydx <hi@seydx.dev>
|
|
6
6
|
Maintainer-email: seydx <hi@seydx.dev>
|
|
@@ -28,10 +28,10 @@ Provides-Extra: dev
|
|
|
28
28
|
Requires-Dist: build>=1.6.1; extra == "dev"
|
|
29
29
|
Requires-Dist: twine>=7.0.0; extra == "dev"
|
|
30
30
|
Requires-Dist: mypy>=2.4.0; extra == "dev"
|
|
31
|
-
Requires-Dist: ruff>=0.17.
|
|
31
|
+
Requires-Dist: ruff>=0.17.2; extra == "dev"
|
|
32
32
|
Provides-Extra: docs
|
|
33
|
-
Requires-Dist: mkdocs-material>=9.
|
|
34
|
-
Requires-Dist: mkdocstrings[python]>=1.0.
|
|
33
|
+
Requires-Dist: mkdocs-material>=9.8.0; extra == "docs"
|
|
34
|
+
Requires-Dist: mkdocstrings[python]>=1.0.8; extra == "docs"
|
|
35
35
|
Requires-Dist: mkdocs>=1.6.1; extra == "docs"
|
|
36
36
|
Requires-Dist: mkdocs-exclude>=1.0.2; extra == "docs"
|
|
37
37
|
Dynamic: license-file
|
|
@@ -245,7 +245,7 @@ class CameraDevice(Protocol):
|
|
|
245
245
|
|
|
246
246
|
@property
|
|
247
247
|
def snapshotSource(self) -> CameraSource | None:
|
|
248
|
-
"""
|
|
248
|
+
"""Source that getSnapshot uses: the snapshot source or the one marked for snapshots, otherwise the low, mid or high resolution source."""
|
|
249
249
|
...
|
|
250
250
|
|
|
251
251
|
@property
|
|
@@ -296,6 +296,21 @@ class CameraDevice(Protocol):
|
|
|
296
296
|
"""
|
|
297
297
|
...
|
|
298
298
|
|
|
299
|
+
async def getSnapshot(self, forceNew: bool = False) -> bytes | None:
|
|
300
|
+
"""
|
|
301
|
+
Get a snapshot from the snapshot source, see ``snapshotSource``.
|
|
302
|
+
|
|
303
|
+
Args:
|
|
304
|
+
forceNew: Force fresh snapshot (ignore cache).
|
|
305
|
+
|
|
306
|
+
Returns:
|
|
307
|
+
JPEG image data, or None if the camera has no source or no picture.
|
|
308
|
+
|
|
309
|
+
Example:
|
|
310
|
+
jpeg = await camera.getSnapshot()
|
|
311
|
+
"""
|
|
312
|
+
...
|
|
313
|
+
|
|
299
314
|
def createStorage(self, schemas: list[JsonSchema]) -> DeviceStorage:
|
|
300
315
|
"""
|
|
301
316
|
Create storage for plugin-specific camera configuration.
|
|
@@ -20,33 +20,25 @@ if TYPE_CHECKING:
|
|
|
20
20
|
from .streaming_internal import IceServer
|
|
21
21
|
|
|
22
22
|
__all__ = [
|
|
23
|
-
# Events
|
|
24
23
|
"AsyncEventEmitter",
|
|
25
|
-
# Manager
|
|
26
24
|
"ConnectionStatus",
|
|
27
25
|
"DiscoveredCameraWithState",
|
|
28
|
-
# Sensor wire-format / RPC
|
|
29
26
|
"SensorJSON",
|
|
30
27
|
"PropertyChangedEvent",
|
|
31
28
|
"PropertyUpdateFn",
|
|
32
29
|
"CapabilityUpdateFn",
|
|
33
30
|
"SensorSourcePatch",
|
|
34
31
|
"SourceUpdateFn",
|
|
35
|
-
# Camera wire-format
|
|
36
32
|
"DetectionEventMessage",
|
|
37
|
-
# Streaming
|
|
38
33
|
"IceServer",
|
|
39
|
-
# Camera enums
|
|
40
34
|
"FrameType",
|
|
41
35
|
"CameraFrameWorkerDecoder",
|
|
42
|
-
# Camera config
|
|
43
36
|
"CameraInputSettings",
|
|
44
37
|
"CameraConfigPartial",
|
|
45
|
-
# Utils
|
|
46
38
|
"is_equal",
|
|
47
39
|
]
|
|
48
40
|
|
|
49
|
-
#
|
|
41
|
+
# resolved lazily to avoid circular imports during SDK bootstrap
|
|
50
42
|
_LAZY: dict[str, tuple[str, str]] = {
|
|
51
43
|
"AsyncEventEmitter": (".event_emitter", "AsyncEventEmitter"),
|
|
52
44
|
"ConnectionStatus": (".manager_rpc", "ConnectionStatus"),
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import NotRequired, TypedDict
|
|
4
|
+
|
|
5
|
+
from ..camera.config import CameraConfigInputSettings, CameraInformation
|
|
6
|
+
from ..camera.enums import CameraRole, StreamingRole
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class CameraInputSettings(TypedDict):
|
|
10
|
+
_id: str
|
|
11
|
+
name: str
|
|
12
|
+
role: CameraRole
|
|
13
|
+
useForSnapshot: bool
|
|
14
|
+
hotMode: bool
|
|
15
|
+
preload: bool
|
|
16
|
+
muted: NotRequired[bool]
|
|
17
|
+
urls: list[str]
|
|
18
|
+
childSourceId: NotRequired[str]
|
|
19
|
+
childCameraId: NotRequired[str]
|
|
20
|
+
childCameraRole: NotRequired[StreamingRole]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class CameraConfigPartial(TypedDict, total=False):
|
|
24
|
+
name: str
|
|
25
|
+
nativeId: str
|
|
26
|
+
isCloud: bool
|
|
27
|
+
disabled: bool
|
|
28
|
+
info: CameraInformation
|
|
29
|
+
sources: list[CameraConfigInputSettings]
|
|
@@ -7,18 +7,11 @@ from typing import Any
|
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
class AsyncEventEmitter:
|
|
10
|
-
"""Event-listener registry that accepts sync and async handlers.
|
|
11
|
-
|
|
12
|
-
Listeners are keyed by event name and invoked in registration order. Async
|
|
13
|
-
handlers are scheduled fire-and-forget and tracked so :meth:`cancel` can stop them.
|
|
14
|
-
"""
|
|
15
|
-
|
|
16
10
|
def __init__(self) -> None:
|
|
17
11
|
self._events: dict[str, OrderedDict[Callable[..., Any], Callable[..., Any]]] = {}
|
|
18
12
|
self._waiting: set[asyncio.Future[Any]] = set()
|
|
19
13
|
|
|
20
14
|
def on(self, event: str, f: Callable[..., Any]) -> Callable[..., Any]:
|
|
21
|
-
"""Register a listener (sync or async) that is invoked for every emission of *event*."""
|
|
22
15
|
event = self._normalize_event(event)
|
|
23
16
|
listeners = self._events.get(event)
|
|
24
17
|
if listeners is None:
|
|
@@ -28,7 +21,6 @@ class AsyncEventEmitter:
|
|
|
28
21
|
return f
|
|
29
22
|
|
|
30
23
|
def once(self, event: str, f: Callable[..., Any]) -> Callable[..., Any]:
|
|
31
|
-
"""Register a one-shot listener that runs on the next emission of *event*, then removes itself."""
|
|
32
24
|
event = self._normalize_event(event)
|
|
33
25
|
|
|
34
26
|
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
@@ -43,11 +35,6 @@ class AsyncEventEmitter:
|
|
|
43
35
|
return f
|
|
44
36
|
|
|
45
37
|
def emit(self, event: str, *args: Any, **kwargs: Any) -> bool:
|
|
46
|
-
"""Invoke every listener registered for *event* with the given arguments.
|
|
47
|
-
|
|
48
|
-
Returns ``True`` if *event* had listeners, ``False`` otherwise.
|
|
49
|
-
Async handlers are scheduled as fire-and-forget tasks.
|
|
50
|
-
"""
|
|
51
38
|
event = self._normalize_event(event)
|
|
52
39
|
listeners = self._events.get(event)
|
|
53
40
|
if not listeners:
|
|
@@ -70,13 +57,6 @@ class AsyncEventEmitter:
|
|
|
70
57
|
on_error: Callable[[BaseException], None] | None = None,
|
|
71
58
|
**kwargs: Any,
|
|
72
59
|
) -> bool:
|
|
73
|
-
"""Invoke every listener registered for *event* and wait for async handlers to settle.
|
|
74
|
-
|
|
75
|
-
Handler exceptions go to *on_error* instead of propagating, so one failing
|
|
76
|
-
listener never blocks the others. Returns ``False`` if handlers were still
|
|
77
|
-
pending after *timeout* seconds, they keep running and stay cancellable
|
|
78
|
-
via :meth:`cancel`.
|
|
79
|
-
"""
|
|
80
60
|
event = self._normalize_event(event)
|
|
81
61
|
listeners = self._events.get(event)
|
|
82
62
|
|
|
@@ -107,7 +87,6 @@ class AsyncEventEmitter:
|
|
|
107
87
|
return not still_pending
|
|
108
88
|
|
|
109
89
|
def remove_listener(self, event: str, f: Callable[..., Any]) -> None:
|
|
110
|
-
"""Remove a previously registered listener *f* from *event*. No-op if it is not registered."""
|
|
111
90
|
event = self._normalize_event(event)
|
|
112
91
|
listeners = self._events.get(event)
|
|
113
92
|
if listeners is not None:
|
|
@@ -116,19 +95,16 @@ class AsyncEventEmitter:
|
|
|
116
95
|
del self._events[event]
|
|
117
96
|
|
|
118
97
|
def remove_all_listeners(self, event: str | None = None) -> None:
|
|
119
|
-
"""Remove every listener for *event*, or every listener for every event when *event* is ``None``."""
|
|
120
98
|
if event is not None:
|
|
121
99
|
self._events.pop(self._normalize_event(event), None)
|
|
122
100
|
else:
|
|
123
101
|
self._events.clear()
|
|
124
102
|
|
|
125
103
|
def cancel(self) -> None:
|
|
126
|
-
"""Cancel all pending async tasks."""
|
|
127
104
|
for future in self._waiting:
|
|
128
105
|
future.cancel()
|
|
129
106
|
self._waiting.clear()
|
|
130
107
|
|
|
131
108
|
@staticmethod
|
|
132
109
|
def _normalize_event(event: Any) -> str:
|
|
133
|
-
"""Accept both plain event names and enum members."""
|
|
134
110
|
return event.value if hasattr(event, "value") else str(event)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Literal, NotRequired
|
|
4
|
+
|
|
5
|
+
from ..manager import DiscoveredCamera
|
|
6
|
+
|
|
7
|
+
ConnectionStatus = Literal["idle", "connecting", "connected", "error"]
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class DiscoveredCameraWithState(DiscoveredCamera):
|
|
11
|
+
provider: str
|
|
12
|
+
|
|
13
|
+
connectionStatus: ConnectionStatus
|
|
14
|
+
|
|
15
|
+
errorMessage: NotRequired[str]
|
|
@@ -54,68 +54,39 @@ if TYPE_CHECKING:
|
|
|
54
54
|
|
|
55
55
|
|
|
56
56
|
class PropertyChangedEvent(TypedDict):
|
|
57
|
-
"""Emitted when a sensor property value changes."""
|
|
58
|
-
|
|
59
57
|
sensorId: str
|
|
60
|
-
"""Sensor that changed."""
|
|
61
58
|
sensorType: SensorType
|
|
62
|
-
"""Type of the sensor that changed."""
|
|
63
59
|
property: str
|
|
64
|
-
"""Property key that changed."""
|
|
65
60
|
value: object
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
"""Not sent by the server, keep the last value yourself if you need it."""
|
|
69
|
-
timestamp: int
|
|
70
|
-
"""Change time in epoch milliseconds."""
|
|
61
|
+
previousValue: NotRequired[object] # never sent by the server
|
|
62
|
+
timestamp: int # epoch ms
|
|
71
63
|
|
|
72
64
|
|
|
73
65
|
PropertyUpdateFn = Callable[[dict[str, Any]], None]
|
|
74
|
-
"""Receives a partial-state delta, one call per ``_write_state``."""
|
|
75
66
|
|
|
76
67
|
CapabilityUpdateFn = Callable[[list[str]], None]
|
|
77
|
-
"""Receives the full capability list whenever it changes."""
|
|
78
68
|
|
|
79
69
|
|
|
80
70
|
class SensorSourcePatch(TypedDict):
|
|
81
|
-
"""Source fields a sensor reports to the registry, only the changed ones."""
|
|
82
|
-
|
|
83
71
|
sourceState: NotRequired[SensorSourceState]
|
|
84
72
|
address: NotRequired[str]
|
|
85
73
|
|
|
86
74
|
|
|
87
75
|
SourceUpdateFn = Callable[[SensorSourcePatch], None]
|
|
88
|
-
"""Receives a source patch, one call per ``setSourceState`` / ``setAddress``."""
|
|
89
76
|
|
|
90
77
|
|
|
91
78
|
class SensorJSON(TypedDict):
|
|
92
|
-
"""JSON-serializable representation of a sensor for RPC transport."""
|
|
93
|
-
|
|
94
79
|
id: str
|
|
95
|
-
"""Sensor ID."""
|
|
96
80
|
type: SensorType
|
|
97
|
-
"""Sensor type."""
|
|
98
81
|
name: str
|
|
99
|
-
"""Internal name."""
|
|
100
82
|
displayName: str
|
|
101
|
-
"""Name shown in the UI."""
|
|
102
83
|
category: SensorCategory
|
|
103
|
-
"""Category the sensor belongs to."""
|
|
104
84
|
nativeId: NotRequired[str]
|
|
105
|
-
"""Device ID assigned by the plugin."""
|
|
106
85
|
origin: NotRequired[str]
|
|
107
|
-
"""Source system the sensor was imported from, e.g. ``'homeassistant'``."""
|
|
108
86
|
pluginId: NotRequired[str]
|
|
109
|
-
"""Plugin that owns the sensor."""
|
|
110
87
|
properties: dict[str, Any]
|
|
111
|
-
"""Current property values."""
|
|
112
88
|
capabilities: NotRequired[list[str]]
|
|
113
|
-
"""Capability keys the sensor reports."""
|
|
114
89
|
requiresFrames: NotRequired[bool]
|
|
115
|
-
"""Sensor needs a frame feed to work."""
|
|
116
90
|
modelSpec: NotRequired[ModelSpec]
|
|
117
|
-
"""Model the sensor runs, for ML-backed sensors."""
|
|
118
91
|
sourceState: NotRequired[SensorSourceState]
|
|
119
|
-
"""The plugin's view of the source behind an adopted sensor."""
|
|
120
92
|
address: NotRequired[str]
|
|
121
|
-
"""Current address at the source, e.g. a Home Assistant entity id."""
|
|
@@ -4,21 +4,6 @@ from typing import Any
|
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
def is_equal(first: Any, second: Any, ignore_order: bool = False) -> bool:
|
|
7
|
-
"""Deep equality for primitives, lists, and dicts. Dict key order never matters.
|
|
8
|
-
|
|
9
|
-
Args:
|
|
10
|
-
first: First value to compare.
|
|
11
|
-
second: Second value to compare.
|
|
12
|
-
ignore_order: Compare lists as multisets instead of position by position.
|
|
13
|
-
|
|
14
|
-
Returns:
|
|
15
|
-
``True`` if the values are deeply equal.
|
|
16
|
-
|
|
17
|
-
Example:
|
|
18
|
-
```python
|
|
19
|
-
is_equal({"a": 1, "b": 2}, {"b": 2, "a": 1}) # True
|
|
20
|
-
```
|
|
21
|
-
"""
|
|
22
7
|
if first is second:
|
|
23
8
|
return True
|
|
24
9
|
|
|
@@ -6,11 +6,6 @@ from typing_extensions import TypedDict
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
class IceServer(TypedDict):
|
|
9
|
-
"""WebRTC ICE server configuration."""
|
|
10
|
-
|
|
11
9
|
urls: list[str]
|
|
12
|
-
"""STUN/TURN server URLs."""
|
|
13
10
|
username: NotRequired[str]
|
|
14
|
-
"""Authentication username."""
|
|
15
11
|
credential: NotRequired[str]
|
|
16
|
-
"""Authentication credential."""
|
|
@@ -48,6 +48,8 @@ class AssistantAskRequest(TypedDict, total=False):
|
|
|
48
48
|
images: list[AssistantAskImage]
|
|
49
49
|
outputSchema: dict[str, Any]
|
|
50
50
|
timeoutMs: int
|
|
51
|
+
modelId: str
|
|
52
|
+
"""Id of a configured model to answer with instead of the assigned one. The plugin still needs access, an unknown id falls back to the assigned model."""
|
|
51
53
|
|
|
52
54
|
|
|
53
55
|
class AssistantAskResult(TypedDict, total=False):
|
|
@@ -269,7 +269,6 @@ class Subject(Generic[T]):
|
|
|
269
269
|
return Observable(lambda cb: self.subscribe(cb))
|
|
270
270
|
|
|
271
271
|
def _on_complete(self, handler: Callable[[], None]) -> Disposable:
|
|
272
|
-
"""Register a handler run once on completion, immediately if already completed."""
|
|
273
272
|
if self._completed:
|
|
274
273
|
handler()
|
|
275
274
|
return Disposable(lambda: None)
|
|
@@ -583,10 +583,6 @@ class Sensor(ABC, Generic[TProperties, TStorage, TCapability]):
|
|
|
583
583
|
self._write_state({"modelSpec": spec})
|
|
584
584
|
|
|
585
585
|
def _write_state(self, partial: Mapping[str, Any]) -> None:
|
|
586
|
-
"""Write changed properties, fire one batched RPC update and notify listeners.
|
|
587
|
-
|
|
588
|
-
Used by the semantic helpers on each sensor type, not by plugin code.
|
|
589
|
-
"""
|
|
590
586
|
delta: dict[str, Any] = {}
|
|
591
587
|
changes: list[tuple[str, Any, Any]] = []
|
|
592
588
|
|
|
@@ -616,18 +612,10 @@ class Sensor(ABC, Generic[TProperties, TStorage, TCapability]):
|
|
|
616
612
|
fallback_label: str,
|
|
617
613
|
fallback_extra: dict[str, Any] | None = None,
|
|
618
614
|
) -> list[dict[str, Any]]:
|
|
619
|
-
"""Normalize the arguments of a `reportDetections(detected, detections)` call.
|
|
620
|
-
|
|
621
|
-
- `detected` is False: returns `[]` (clear).
|
|
622
|
-
- `detected` is True with detections: returns them, substituting a full-frame box where missing.
|
|
623
|
-
- `detected` is True without detections: returns one synthesized full-frame
|
|
624
|
-
detection carrying `fallback_label` and `fallback_extra`.
|
|
625
|
-
"""
|
|
626
615
|
if not detected:
|
|
627
616
|
return []
|
|
628
617
|
if detections:
|
|
629
|
-
# smart-camera plugins report labels without coordinates,
|
|
630
|
-
# consumers (coordinator, zone matching) require a box on every detection
|
|
618
|
+
# smart-camera plugins report labels without coordinates, consumers need a box on every detection
|
|
631
619
|
return [
|
|
632
620
|
detection
|
|
633
621
|
if detection.get("box")
|
|
@@ -647,7 +635,6 @@ class Sensor(ABC, Generic[TProperties, TStorage, TCapability]):
|
|
|
647
635
|
self._storage = storage
|
|
648
636
|
|
|
649
637
|
def _fire_lifecycle(self, active: bool) -> None:
|
|
650
|
-
"""Invoke the lifecycle hook and schedule it if the override is async."""
|
|
651
638
|
try:
|
|
652
639
|
result = self.on_start() if active else self.on_stop()
|
|
653
640
|
except Exception: # noqa: BLE001 - lifecycle errors must not break bookkeeping
|
|
@@ -152,8 +152,7 @@ class ClassifierSensor(Sensor[ClassifierSensorProperties, TStorage, str], Generi
|
|
|
152
152
|
"motion",
|
|
153
153
|
{"attribute": "", "subAttribute": ""},
|
|
154
154
|
)
|
|
155
|
-
# the specific answer
|
|
156
|
-
# accumulate while the classifier keeps firing, so automations don't flap
|
|
155
|
+
# the specific answer (subAttribute) wins, labels accumulate while firing so automations don't flap
|
|
157
156
|
recognized = [
|
|
158
157
|
label for d in list_ if (label := d.get("subAttribute") or d.get("attribute") or d.get("label"))
|
|
159
158
|
]
|
|
@@ -150,8 +150,7 @@ class FaceSensor(Sensor[FaceSensorProperties, TStorage, str], Generic[TStorage])
|
|
|
150
150
|
"person",
|
|
151
151
|
{"attribute": "face"},
|
|
152
152
|
)
|
|
153
|
-
#
|
|
154
|
-
# don't flap when a face is momentarily unrecognized mid-presence
|
|
153
|
+
# names accumulate while faces stay visible, so automations don't flap on a missed recognition
|
|
155
154
|
recognized = [d["identity"] for d in list_ if d.get("identity")]
|
|
156
155
|
state: dict[str, Any] = {
|
|
157
156
|
FaceProperty.Detected.value: detected,
|
|
@@ -156,8 +156,7 @@ class LicensePlateSensor(Sensor[LicensePlateSensorProperties, TStorage, str], Ge
|
|
|
156
156
|
"vehicle",
|
|
157
157
|
{"attribute": "license_plate", "plateText": ""},
|
|
158
158
|
)
|
|
159
|
-
#
|
|
160
|
-
# don't flap when the OCR misses a frame mid-presence
|
|
159
|
+
# plates accumulate while visible, so automations don't flap when the OCR misses a frame
|
|
161
160
|
recognized = [d["plateText"] for d in list_ if d.get("plateText")]
|
|
162
161
|
state: dict[str, Any] = {
|
|
163
162
|
LicensePlateProperty.Detected.value: detected,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: camera-ui-sdk
|
|
3
|
-
Version: 1.4.
|
|
3
|
+
Version: 1.4.4
|
|
4
4
|
Summary: camera.ui sdk
|
|
5
5
|
Author-email: seydx <hi@seydx.dev>
|
|
6
6
|
Maintainer-email: seydx <hi@seydx.dev>
|
|
@@ -28,10 +28,10 @@ Provides-Extra: dev
|
|
|
28
28
|
Requires-Dist: build>=1.6.1; extra == "dev"
|
|
29
29
|
Requires-Dist: twine>=7.0.0; extra == "dev"
|
|
30
30
|
Requires-Dist: mypy>=2.4.0; extra == "dev"
|
|
31
|
-
Requires-Dist: ruff>=0.17.
|
|
31
|
+
Requires-Dist: ruff>=0.17.2; extra == "dev"
|
|
32
32
|
Provides-Extra: docs
|
|
33
|
-
Requires-Dist: mkdocs-material>=9.
|
|
34
|
-
Requires-Dist: mkdocstrings[python]>=1.0.
|
|
33
|
+
Requires-Dist: mkdocs-material>=9.8.0; extra == "docs"
|
|
34
|
+
Requires-Dist: mkdocstrings[python]>=1.0.8; extra == "docs"
|
|
35
35
|
Requires-Dist: mkdocs>=1.6.1; extra == "docs"
|
|
36
36
|
Requires-Dist: mkdocs-exclude>=1.0.2; extra == "docs"
|
|
37
37
|
Dynamic: license-file
|
|
@@ -4,11 +4,11 @@ typing_extensions>=4.16.0
|
|
|
4
4
|
build>=1.6.1
|
|
5
5
|
twine>=7.0.0
|
|
6
6
|
mypy>=2.4.0
|
|
7
|
-
ruff>=0.17.
|
|
7
|
+
ruff>=0.17.2
|
|
8
8
|
|
|
9
9
|
[docs]
|
|
10
|
-
mkdocs-material>=9.
|
|
11
|
-
mkdocstrings[python]>=1.0.
|
|
10
|
+
mkdocs-material>=9.8.0
|
|
11
|
+
mkdocstrings[python]>=1.0.8
|
|
12
12
|
mkdocs>=1.6.1
|
|
13
13
|
mkdocs-exclude>=1.0.2
|
|
14
14
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
[build-system]
|
|
2
|
-
requires = ["setuptools>=84.
|
|
2
|
+
requires = ["setuptools>=84.2.1"]
|
|
3
3
|
build-backend = "setuptools.build_meta"
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "camera-ui-sdk"
|
|
7
|
-
version = "1.4.
|
|
7
|
+
version = "1.4.4"
|
|
8
8
|
description = "camera.ui sdk"
|
|
9
9
|
keywords = ["camera.ui", "python", "sdk"]
|
|
10
10
|
readme = "README.md"
|
|
@@ -32,11 +32,11 @@ dev = [
|
|
|
32
32
|
"build>=1.6.1",
|
|
33
33
|
"twine>=7.0.0",
|
|
34
34
|
"mypy>=2.4.0",
|
|
35
|
-
"ruff>=0.17.
|
|
35
|
+
"ruff>=0.17.2",
|
|
36
36
|
]
|
|
37
37
|
docs = [
|
|
38
|
-
"mkdocs-material>=9.
|
|
39
|
-
"mkdocstrings[python]>=1.0.
|
|
38
|
+
"mkdocs-material>=9.8.0",
|
|
39
|
+
"mkdocstrings[python]>=1.0.8",
|
|
40
40
|
"mkdocs>=1.6.1",
|
|
41
41
|
"mkdocs-exclude>=1.0.2",
|
|
42
42
|
]
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
"""Tests for AsyncEventEmitter."""
|
|
2
|
-
|
|
3
1
|
from __future__ import annotations
|
|
4
2
|
|
|
5
3
|
import asyncio
|
|
@@ -8,8 +6,6 @@ import pytest
|
|
|
8
6
|
|
|
9
7
|
from camera_ui_sdk.internal import AsyncEventEmitter
|
|
10
8
|
|
|
11
|
-
# ── on + emit (sync) ─────────────────────────────────────────────────
|
|
12
|
-
|
|
13
9
|
|
|
14
10
|
class TestOnEmitSync:
|
|
15
11
|
def test_sync_handler_called(self) -> None:
|
|
@@ -30,9 +26,6 @@ class TestOnEmitSync:
|
|
|
30
26
|
assert results == [1, 2]
|
|
31
27
|
|
|
32
28
|
|
|
33
|
-
# ── on + emit (async) ────────────────────────────────────────────────
|
|
34
|
-
|
|
35
|
-
|
|
36
29
|
class TestOnEmitAsync:
|
|
37
30
|
@pytest.mark.asyncio
|
|
38
31
|
async def test_async_handler_fire_and_forget(self) -> None:
|
|
@@ -45,14 +38,10 @@ class TestOnEmitAsync:
|
|
|
45
38
|
ee.on("test", handler)
|
|
46
39
|
ee.emit("test", "async_val")
|
|
47
40
|
|
|
48
|
-
# Give the scheduled coroutine a chance to run
|
|
49
41
|
await asyncio.sleep(0)
|
|
50
42
|
assert results == ["async_val"]
|
|
51
43
|
|
|
52
44
|
|
|
53
|
-
# ── once ──────────────────────────────────────────────────────────────
|
|
54
|
-
|
|
55
|
-
|
|
56
45
|
class TestOnce:
|
|
57
46
|
def test_fires_only_once(self) -> None:
|
|
58
47
|
ee = AsyncEventEmitter()
|
|
@@ -80,9 +69,6 @@ class TestOnce:
|
|
|
80
69
|
assert results == ["once", "on", "on"]
|
|
81
70
|
|
|
82
71
|
|
|
83
|
-
# ── remove_listener ──────────────────────────────────────────────────
|
|
84
|
-
|
|
85
|
-
|
|
86
72
|
class TestRemoveListener:
|
|
87
73
|
def test_removes_specific_handler(self) -> None:
|
|
88
74
|
ee = AsyncEventEmitter()
|
|
@@ -103,7 +89,7 @@ class TestRemoveListener:
|
|
|
103
89
|
|
|
104
90
|
def test_remove_nonexistent_is_noop(self) -> None:
|
|
105
91
|
ee = AsyncEventEmitter()
|
|
106
|
-
ee.remove_listener("no_such_event", lambda: None)
|
|
92
|
+
ee.remove_listener("no_such_event", lambda: None)
|
|
107
93
|
|
|
108
94
|
def test_remove_once_listener(self) -> None:
|
|
109
95
|
ee = AsyncEventEmitter()
|
|
@@ -120,9 +106,6 @@ class TestRemoveListener:
|
|
|
120
106
|
assert count == 0
|
|
121
107
|
|
|
122
108
|
|
|
123
|
-
# ── remove_all_listeners ─────────────────────────────────────────────
|
|
124
|
-
|
|
125
|
-
|
|
126
109
|
class TestRemoveAllListeners:
|
|
127
110
|
def test_removes_for_specific_event(self) -> None:
|
|
128
111
|
ee = AsyncEventEmitter()
|
|
@@ -153,9 +136,6 @@ class TestRemoveAllListeners:
|
|
|
153
136
|
assert results == []
|
|
154
137
|
|
|
155
138
|
|
|
156
|
-
# ── emit return value ────────────────────────────────────────────────
|
|
157
|
-
|
|
158
|
-
|
|
159
139
|
class TestEmitReturn:
|
|
160
140
|
def test_returns_true_with_listeners(self) -> None:
|
|
161
141
|
ee = AsyncEventEmitter()
|
|
@@ -167,9 +147,6 @@ class TestEmitReturn:
|
|
|
167
147
|
assert ee.emit("evt") is False
|
|
168
148
|
|
|
169
149
|
|
|
170
|
-
# ── cancel ────────────────────────────────────────────────────────────
|
|
171
|
-
|
|
172
|
-
|
|
173
150
|
class TestCancel:
|
|
174
151
|
@pytest.mark.asyncio
|
|
175
152
|
async def test_cancels_pending_tasks(self) -> None:
|
|
@@ -184,7 +161,6 @@ class TestCancel:
|
|
|
184
161
|
ee.on("evt", long_handler)
|
|
185
162
|
ee.emit("evt")
|
|
186
163
|
|
|
187
|
-
# Let the task start
|
|
188
164
|
await asyncio.sleep(0)
|
|
189
165
|
assert started is True
|
|
190
166
|
assert len(ee._waiting) == 1 # pyright: ignore[reportPrivateUsage]
|
|
@@ -202,7 +178,7 @@ class TestCancel:
|
|
|
202
178
|
ee.on("evt", quick_handler)
|
|
203
179
|
ee.emit("evt")
|
|
204
180
|
|
|
205
|
-
#
|
|
181
|
+
# one tick for the task, one for its done callback
|
|
206
182
|
await asyncio.sleep(0)
|
|
207
183
|
await asyncio.sleep(0)
|
|
208
184
|
assert len(ee._waiting) == 0 # pyright: ignore[reportPrivateUsage]
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
"""Tests for lightweight reactive primitives."""
|
|
2
|
-
|
|
3
1
|
from __future__ import annotations
|
|
4
2
|
|
|
5
3
|
import asyncio
|
|
@@ -21,8 +19,6 @@ from camera_ui_sdk.observable import (
|
|
|
21
19
|
share,
|
|
22
20
|
)
|
|
23
21
|
|
|
24
|
-
# ── Disposable ────────────────────────────────────────────────────────
|
|
25
|
-
|
|
26
22
|
|
|
27
23
|
class TestDisposable:
|
|
28
24
|
def test_calls_teardown_once(self) -> None:
|
|
@@ -55,9 +51,6 @@ class TestDisposable:
|
|
|
55
51
|
assert d.closed is True
|
|
56
52
|
|
|
57
53
|
|
|
58
|
-
# ── Subject ───────────────────────────────────────────────────────────
|
|
59
|
-
|
|
60
|
-
|
|
61
54
|
class TestSubject:
|
|
62
55
|
def test_emits_values(self) -> None:
|
|
63
56
|
subject: Subject[int] = Subject()
|
|
@@ -126,9 +119,6 @@ class TestSubject:
|
|
|
126
119
|
assert isinstance(obs, Observable)
|
|
127
120
|
|
|
128
121
|
|
|
129
|
-
# ── BehaviorSubject ───────────────────────────────────────────────────
|
|
130
|
-
|
|
131
|
-
|
|
132
122
|
class TestBehaviorSubject:
|
|
133
123
|
def test_emits_current_value_on_subscribe(self) -> None:
|
|
134
124
|
subject = BehaviorSubject(42)
|
|
@@ -176,9 +166,6 @@ class TestBehaviorSubject:
|
|
|
176
166
|
assert values == []
|
|
177
167
|
|
|
178
168
|
|
|
179
|
-
# ── ReplaySubject ─────────────────────────────────────────────────────
|
|
180
|
-
|
|
181
|
-
|
|
182
169
|
class TestReplaySubject:
|
|
183
170
|
def test_replays_buffered_values(self) -> None:
|
|
184
171
|
subject: ReplaySubject[int] = ReplaySubject(2)
|
|
@@ -224,9 +211,6 @@ class TestReplaySubject:
|
|
|
224
211
|
assert values == [1]
|
|
225
212
|
|
|
226
213
|
|
|
227
|
-
# ── Observable ────────────────────────────────────────────────────────
|
|
228
|
-
|
|
229
|
-
|
|
230
214
|
class TestObservable:
|
|
231
215
|
def test_subscribe_calls_factory(self) -> None:
|
|
232
216
|
called = False
|
|
@@ -252,9 +236,6 @@ class TestObservable:
|
|
|
252
236
|
assert values == [1]
|
|
253
237
|
|
|
254
238
|
|
|
255
|
-
# ── Operators ─────────────────────────────────────────────────────────
|
|
256
|
-
|
|
257
|
-
|
|
258
239
|
class TestFilterOp:
|
|
259
240
|
def test_filters_values(self) -> None:
|
|
260
241
|
subject: Subject[int] = Subject()
|
|
@@ -428,9 +409,6 @@ class TestShare:
|
|
|
428
409
|
assert subscribe_count == 2
|
|
429
410
|
|
|
430
411
|
|
|
431
|
-
# ── Pipe chaining ─────────────────────────────────────────────────────
|
|
432
|
-
|
|
433
|
-
|
|
434
412
|
class TestPipeChaining:
|
|
435
413
|
def test_chains_multiple_operators(self) -> None:
|
|
436
414
|
subject: Subject[int] = Subject()
|
|
@@ -465,9 +443,6 @@ class TestPipeChaining:
|
|
|
465
443
|
assert values == [0, 1, 2]
|
|
466
444
|
|
|
467
445
|
|
|
468
|
-
# ── firstValueFrom ───────────────────────────────────────────────────
|
|
469
|
-
|
|
470
|
-
|
|
471
446
|
class TestFirstValueFrom:
|
|
472
447
|
@pytest.mark.asyncio
|
|
473
448
|
async def test_resolves_with_first_value(self) -> None:
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from typing import NotRequired, TypedDict
|
|
4
|
-
|
|
5
|
-
from ..camera.config import CameraConfigInputSettings, CameraInformation
|
|
6
|
-
from ..camera.enums import CameraRole, StreamingRole
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class CameraInputSettings(TypedDict):
|
|
10
|
-
"""Camera input settings (user configuration)."""
|
|
11
|
-
|
|
12
|
-
_id: str
|
|
13
|
-
"""Unique source ID."""
|
|
14
|
-
name: str
|
|
15
|
-
"""Source display name."""
|
|
16
|
-
role: CameraRole
|
|
17
|
-
"""Resolution role."""
|
|
18
|
-
useForSnapshot: bool
|
|
19
|
-
"""Use this source for snapshots."""
|
|
20
|
-
hotMode: bool
|
|
21
|
-
"""Keep connection always active."""
|
|
22
|
-
preload: bool
|
|
23
|
-
"""Keep a keyframe cache for this source, so the view opens faster. Use ``hotMode`` to keep the stream connected."""
|
|
24
|
-
muted: NotRequired[bool]
|
|
25
|
-
"""Strip the audio track from this source (defaults to False)."""
|
|
26
|
-
urls: list[str]
|
|
27
|
-
"""User-provided stream URLs."""
|
|
28
|
-
childSourceId: NotRequired[str]
|
|
29
|
-
"""Child source ID (for snapshot fallback)."""
|
|
30
|
-
childCameraId: NotRequired[str]
|
|
31
|
-
"""Camera whose stream shows in the picture-in-picture overlay, instead of a source of this camera."""
|
|
32
|
-
childCameraRole: NotRequired[StreamingRole]
|
|
33
|
-
"""Role of the ``childCameraId`` stream shown in the overlay."""
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
class CameraConfigPartial(TypedDict, total=False):
|
|
37
|
-
"""Camera configuration subset for partial updates."""
|
|
38
|
-
|
|
39
|
-
name: str
|
|
40
|
-
"""Camera display name."""
|
|
41
|
-
nativeId: str
|
|
42
|
-
"""Native device ID from plugin."""
|
|
43
|
-
isCloud: bool
|
|
44
|
-
"""Whether camera streams from cloud."""
|
|
45
|
-
disabled: bool
|
|
46
|
-
"""Disable this camera."""
|
|
47
|
-
info: CameraInformation
|
|
48
|
-
"""Camera hardware information."""
|
|
49
|
-
sources: list[CameraConfigInputSettings]
|
|
50
|
-
"""Video input sources."""
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from typing import Literal, NotRequired
|
|
4
|
-
|
|
5
|
-
from ..manager import DiscoveredCamera
|
|
6
|
-
|
|
7
|
-
ConnectionStatus = Literal["idle", "connecting", "connected", "error"]
|
|
8
|
-
"""Connection status for discovered cameras."""
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class DiscoveredCameraWithState(DiscoveredCamera):
|
|
12
|
-
"""Discovered camera plus the provider that found it and its live connection state.
|
|
13
|
-
|
|
14
|
-
Used by the UI to render the adoption list.
|
|
15
|
-
"""
|
|
16
|
-
|
|
17
|
-
provider: str
|
|
18
|
-
"""Name of the provider plugin that discovered this camera."""
|
|
19
|
-
|
|
20
|
-
connectionStatus: ConnectionStatus
|
|
21
|
-
"""Current connection status reported by the provider."""
|
|
22
|
-
|
|
23
|
-
errorMessage: NotRequired[str]
|
|
24
|
-
"""Last error message when ``connectionStatus`` is ``error``."""
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|