python-aidot-cameras 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.
- aidot/__init__.py +31 -0
- aidot/aes_utils.py +46 -0
- aidot/client.py +405 -0
- aidot/const.py +241 -0
- aidot/credentials.py +158 -0
- aidot/device_client.py +12209 -0
- aidot/discover.py +229 -0
- aidot/exceptions.py +58 -0
- aidot/g711.py +54 -0
- aidot/login_const.py +13 -0
- python_aidot_cameras-0.1.0.dist-info/METADATA +61 -0
- python_aidot_cameras-0.1.0.dist-info/RECORD +15 -0
- python_aidot_cameras-0.1.0.dist-info/WHEEL +5 -0
- python_aidot_cameras-0.1.0.dist-info/licenses/LICENSE +21 -0
- python_aidot_cameras-0.1.0.dist-info/top_level.txt +1 -0
aidot/discover.py
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import socket
|
|
3
|
+
import json
|
|
4
|
+
import time
|
|
5
|
+
import logging
|
|
6
|
+
import asyncio
|
|
7
|
+
import subprocess
|
|
8
|
+
import sys
|
|
9
|
+
from typing import Any, List, Tuple
|
|
10
|
+
|
|
11
|
+
from .aes_utils import aes_encrypt, aes_decrypt
|
|
12
|
+
from .const import CONF_ID, CONF_IPADDRESS
|
|
13
|
+
from .exceptions import AidotOSError
|
|
14
|
+
|
|
15
|
+
_LOGGER = logging.getLogger(__name__)
|
|
16
|
+
_DISCOVER_TIME = 5
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _get_broadcast_candidates() -> List[Tuple[str, str]]:
|
|
20
|
+
"""Return (bind_ip, broadcast_ip) pairs for every active IPv4 interface.
|
|
21
|
+
|
|
22
|
+
Sends a separate broadcast per interface so cameras are reachable
|
|
23
|
+
regardless of which interface the OS default route prefers.
|
|
24
|
+
Falls back to a single ("0.0.0.0", "255.255.255.255") entry if
|
|
25
|
+
interface enumeration is unavailable.
|
|
26
|
+
"""
|
|
27
|
+
results: List[Tuple[str, str]] = []
|
|
28
|
+
try:
|
|
29
|
+
if sys.platform == "darwin":
|
|
30
|
+
# macOS ifconfig: "inet 192.168.1.175 netmask 0xffffff00 broadcast 192.168.1.255"
|
|
31
|
+
out = subprocess.check_output(
|
|
32
|
+
["/sbin/ifconfig"], text=True, stderr=subprocess.DEVNULL
|
|
33
|
+
)
|
|
34
|
+
for m in re.finditer(
|
|
35
|
+
r"\binet\s+"
|
|
36
|
+
r"((?!127\.|169\.254\.)\d+\.\d+\.\d+\.\d+)"
|
|
37
|
+
r"\s+netmask\s+\S+"
|
|
38
|
+
r"\s+broadcast\s+"
|
|
39
|
+
r"(\d+\.\d+\.\d+\.\d+)",
|
|
40
|
+
out,
|
|
41
|
+
):
|
|
42
|
+
results.append((m.group(1), m.group(2)))
|
|
43
|
+
else:
|
|
44
|
+
# Linux: "inet 192.168.1.x/24 brd 192.168.1.255"
|
|
45
|
+
out = subprocess.check_output(
|
|
46
|
+
["ip", "addr", "show"], text=True, stderr=subprocess.DEVNULL
|
|
47
|
+
)
|
|
48
|
+
for m in re.finditer(
|
|
49
|
+
r"\binet\s+"
|
|
50
|
+
r"((?!127\.|169\.254\.)\d+\.\d+\.\d+\.\d+)/\d+"
|
|
51
|
+
r"\s+brd\s+"
|
|
52
|
+
r"(\d+\.\d+\.\d+\.\d+)",
|
|
53
|
+
out,
|
|
54
|
+
):
|
|
55
|
+
results.append((m.group(1), m.group(2)))
|
|
56
|
+
except Exception as exc:
|
|
57
|
+
_LOGGER.debug("_get_broadcast_candidates: interface enumeration failed: %s", exc)
|
|
58
|
+
|
|
59
|
+
if not results:
|
|
60
|
+
# Fallback: let the OS pick the outgoing interface
|
|
61
|
+
results = [("0.0.0.0", "255.255.255.255")]
|
|
62
|
+
|
|
63
|
+
_LOGGER.debug("_get_broadcast_candidates: %s", results)
|
|
64
|
+
return results
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class BroadcastProtocol:
|
|
68
|
+
_is_closed = False
|
|
69
|
+
|
|
70
|
+
def __init__(self, callback, user_id, broadcast_addr: str = "255.255.255.255") -> None:
|
|
71
|
+
self.aes_key = bytearray(32)
|
|
72
|
+
key_string = "T54uednca587"
|
|
73
|
+
key_bytes = key_string.encode()
|
|
74
|
+
self.aes_key[: len(key_bytes)] = key_bytes
|
|
75
|
+
|
|
76
|
+
self._discover_cb = callback
|
|
77
|
+
self.user_id = user_id
|
|
78
|
+
self._broadcast_addr = broadcast_addr
|
|
79
|
+
|
|
80
|
+
def connection_made(self, transport) -> None:
|
|
81
|
+
self.transport = transport
|
|
82
|
+
sock = transport.get_extra_info("socket")
|
|
83
|
+
sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
|
|
84
|
+
|
|
85
|
+
def send_broadcast(self) -> None:
|
|
86
|
+
if self._is_closed:
|
|
87
|
+
_LOGGER.error("%s: connection is closed", self.user_id)
|
|
88
|
+
return
|
|
89
|
+
current_timestamp_milliseconds = int(time.time() * 1000)
|
|
90
|
+
seq = str(current_timestamp_milliseconds + 1)[-9:]
|
|
91
|
+
message = {
|
|
92
|
+
"protocolVer": "2.0.0",
|
|
93
|
+
"service": "device",
|
|
94
|
+
"method": "devDiscoveryReq",
|
|
95
|
+
"seq": seq,
|
|
96
|
+
"srcAddr": f"0.{self.user_id}]",
|
|
97
|
+
"tst": current_timestamp_milliseconds,
|
|
98
|
+
"payload": {
|
|
99
|
+
"extends": {},
|
|
100
|
+
"localCtrFlag": 1,
|
|
101
|
+
"timestamp": str(current_timestamp_milliseconds),
|
|
102
|
+
},
|
|
103
|
+
}
|
|
104
|
+
send_data = aes_encrypt(json.dumps(message).encode(), self.aes_key)
|
|
105
|
+
try:
|
|
106
|
+
sock = self.transport.get_extra_info("socket")
|
|
107
|
+
local_addr = sock.getsockname() if sock else ("?", 0)
|
|
108
|
+
self.transport.sendto(send_data, (self._broadcast_addr, 6666))
|
|
109
|
+
_LOGGER.info(
|
|
110
|
+
"discovery broadcast sent: %s:%s → %s:6666",
|
|
111
|
+
local_addr[0], local_addr[1], self._broadcast_addr,
|
|
112
|
+
)
|
|
113
|
+
except Exception as error:
|
|
114
|
+
_LOGGER.error("%s: send failed: %s", self.user_id, error)
|
|
115
|
+
|
|
116
|
+
def datagram_received(self, data, addr) -> None:
|
|
117
|
+
try:
|
|
118
|
+
data_str = aes_decrypt(data, self.aes_key)
|
|
119
|
+
data_json = json.loads(data_str)
|
|
120
|
+
except Exception as exc:
|
|
121
|
+
_LOGGER.debug("discovery: ignored undecodable packet from %s: %s", addr, exc)
|
|
122
|
+
return
|
|
123
|
+
if "payload" in data_json and "mac" in data_json["payload"]:
|
|
124
|
+
devId = data_json["payload"]["devId"]
|
|
125
|
+
if self._discover_cb:
|
|
126
|
+
self._discover_cb(devId, {CONF_IPADDRESS: addr[0]})
|
|
127
|
+
|
|
128
|
+
def error_received(self, exc) -> None:
|
|
129
|
+
_LOGGER.error("%s: error occurred: %s", self.user_id, exc)
|
|
130
|
+
|
|
131
|
+
def close(self) -> None:
|
|
132
|
+
try:
|
|
133
|
+
self.transport.close()
|
|
134
|
+
except Exception as error:
|
|
135
|
+
_LOGGER.error("close error: %s", error)
|
|
136
|
+
|
|
137
|
+
def connection_lost(self, exc) -> None:
|
|
138
|
+
self._is_closed = True
|
|
139
|
+
if exc:
|
|
140
|
+
_LOGGER.error("%s: connection lost: %s", self.user_id, exc)
|
|
141
|
+
else:
|
|
142
|
+
_LOGGER.info("%s: connection closed", self.user_id)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class Discover:
|
|
146
|
+
_login_info: dict[str, Any] = None
|
|
147
|
+
discovered_device: dict[str, str]
|
|
148
|
+
_is_close: bool = False
|
|
149
|
+
|
|
150
|
+
def __init__(self, login_info, callback):
|
|
151
|
+
self.discovered_device = {}
|
|
152
|
+
self._login_info = login_info
|
|
153
|
+
self._callback = callback
|
|
154
|
+
self._protocols: List[BroadcastProtocol] = []
|
|
155
|
+
|
|
156
|
+
async def _ensure_sockets(self) -> None:
|
|
157
|
+
"""Create one datagram endpoint per active interface (idempotent)."""
|
|
158
|
+
if self._protocols:
|
|
159
|
+
return
|
|
160
|
+
|
|
161
|
+
candidates = _get_broadcast_candidates()
|
|
162
|
+
user_id = self._login_info[CONF_ID]
|
|
163
|
+
|
|
164
|
+
for bind_ip, broadcast_ip in candidates:
|
|
165
|
+
protocol = BroadcastProtocol(
|
|
166
|
+
self._discover_callback, user_id, broadcast_addr=broadcast_ip
|
|
167
|
+
)
|
|
168
|
+
try:
|
|
169
|
+
await asyncio.get_event_loop().create_datagram_endpoint(
|
|
170
|
+
lambda p=protocol: p,
|
|
171
|
+
local_addr=(bind_ip, 0),
|
|
172
|
+
)
|
|
173
|
+
self._protocols.append(protocol)
|
|
174
|
+
_LOGGER.debug(
|
|
175
|
+
"discovery socket: bind=%s broadcast=%s", bind_ip, broadcast_ip
|
|
176
|
+
)
|
|
177
|
+
except OSError as exc:
|
|
178
|
+
_LOGGER.debug(
|
|
179
|
+
"discovery socket bind %s failed: %s", bind_ip, exc
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
if not self._protocols:
|
|
183
|
+
# Last-resort fallback
|
|
184
|
+
protocol = BroadcastProtocol(self._discover_callback, user_id)
|
|
185
|
+
try:
|
|
186
|
+
await asyncio.get_event_loop().create_datagram_endpoint(
|
|
187
|
+
lambda: protocol,
|
|
188
|
+
local_addr=("0.0.0.0", 0),
|
|
189
|
+
)
|
|
190
|
+
self._protocols.append(protocol)
|
|
191
|
+
except OSError:
|
|
192
|
+
raise AidotOSError
|
|
193
|
+
|
|
194
|
+
# ---------------------------------------------------------------------- #
|
|
195
|
+
# Public API (kept compatible with existing callers)
|
|
196
|
+
# ---------------------------------------------------------------------- #
|
|
197
|
+
|
|
198
|
+
async def try_create_broadcast(self) -> None:
|
|
199
|
+
await self._ensure_sockets()
|
|
200
|
+
|
|
201
|
+
async def send_broadcast(self) -> None:
|
|
202
|
+
await self._ensure_sockets()
|
|
203
|
+
for proto in self._protocols:
|
|
204
|
+
proto.send_broadcast()
|
|
205
|
+
|
|
206
|
+
async def repeat_broadcast(self) -> None:
|
|
207
|
+
self._is_close = False
|
|
208
|
+
while True:
|
|
209
|
+
await self.send_broadcast()
|
|
210
|
+
for _ in range(_DISCOVER_TIME):
|
|
211
|
+
await asyncio.sleep(1)
|
|
212
|
+
if self._is_close:
|
|
213
|
+
return
|
|
214
|
+
|
|
215
|
+
async def fetch_devices_info(self) -> dict[str, str]:
|
|
216
|
+
await self.send_broadcast()
|
|
217
|
+
await asyncio.sleep(2)
|
|
218
|
+
return self.discovered_device
|
|
219
|
+
|
|
220
|
+
def _discover_callback(self, dev_id, event: dict[str, str]) -> None:
|
|
221
|
+
self.discovered_device[dev_id] = event[CONF_IPADDRESS]
|
|
222
|
+
if self._callback:
|
|
223
|
+
self._callback(dev_id, event)
|
|
224
|
+
|
|
225
|
+
def close(self) -> None:
|
|
226
|
+
self._is_close = True
|
|
227
|
+
for proto in self._protocols:
|
|
228
|
+
proto.close()
|
|
229
|
+
self._protocols.clear()
|
aidot/exceptions.py
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""AiDot exception hierarchy."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class AidotError(Exception):
|
|
5
|
+
"""Base exception for all AiDot errors."""
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class InvalidURL(AidotError):
|
|
9
|
+
"""Invalid URL."""
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class HTTPError(AidotError):
|
|
13
|
+
"""HTTP request failed."""
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class InvalidHost(AidotError):
|
|
17
|
+
"""Invalid host."""
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class AidotAuthTokenExpired(AidotError):
|
|
21
|
+
"""Auth token is invalid or expired."""
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class AidotAuthFailed(AidotError):
|
|
25
|
+
"""Authentication failed."""
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class AidotNotLogin(AidotError):
|
|
29
|
+
"""Client is not logged in."""
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class AidotUserOrPassIncorrect(AidotError):
|
|
33
|
+
"""Username or password is incorrect."""
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class AidotOSError(AidotError):
|
|
37
|
+
"""OS-level error from the AiDot library."""
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class AidotCameraBusy(AidotError):
|
|
41
|
+
"""Camera refused the live stream with a TERMINAL ack code - retrying is futile.
|
|
42
|
+
|
|
43
|
+
Raised when a ``webrtcResp`` carries ``ack.code`` in the terminal set:
|
|
44
|
+
-50002 WEBRTC_ERROR_EN_RTC_ERR_CODE_SESSION_EXCEED (max concurrent streams)
|
|
45
|
+
-50015 LIVE_SD_MAX_CONNECT_ERROR (SD-card / connection cap)
|
|
46
|
+
|
|
47
|
+
The official app treats both as terminal (shows an error, does NOT retry -
|
|
48
|
+
decompiled LiveCameraView.java:765). Callers should surface the error rather
|
|
49
|
+
than burning their retry budget hammering a camera that already said no.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
def __init__(self, code: int, desc: str = "") -> None:
|
|
53
|
+
self.code = code
|
|
54
|
+
self.desc = desc
|
|
55
|
+
msg = f"camera refused stream: ack code {code}"
|
|
56
|
+
if desc:
|
|
57
|
+
msg += f" ({desc})"
|
|
58
|
+
super().__init__(msg)
|
aidot/g711.py
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""Pure-Python G.711 A-law encoder for two-way-audio (talk).
|
|
2
|
+
|
|
3
|
+
No stdlib ``audioop`` dependency - that module was removed in Python 3.13 and
|
|
4
|
+
Home Assistant runs on 3.12/3.13. A-law encode follows the ITU-T G.711
|
|
5
|
+
reference (Sun ``g711.c`` ``linear2alaw``); verified byte-identical to
|
|
6
|
+
``audioop.lin2alaw`` over all 65536 samples on Python <=3.12.
|
|
7
|
+
|
|
8
|
+
The SDES talk pump (device_client) feeds 20 ms (320-byte) frames of s16le PCM
|
|
9
|
+
@ 8 kHz mono through :func:`pcm_to_alaw` to produce 160-byte PCMA (PT=8) RTP
|
|
10
|
+
payloads.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import struct
|
|
16
|
+
|
|
17
|
+
_SEG_AEND = (0x1F, 0x3F, 0x7F, 0xFF, 0x1FF, 0x3FF, 0x7FF, 0xFFF)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _search(value: int) -> int:
|
|
21
|
+
for i, bound in enumerate(_SEG_AEND):
|
|
22
|
+
if value <= bound:
|
|
23
|
+
return i
|
|
24
|
+
return 8
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def linear2alaw(pcm_val: int) -> int:
|
|
28
|
+
"""Encode one signed 16-bit PCM sample to an 8-bit A-law byte."""
|
|
29
|
+
pcm_val = pcm_val >> 3 # 16-bit -> 13-bit
|
|
30
|
+
if pcm_val >= 0:
|
|
31
|
+
mask = 0xD5
|
|
32
|
+
else:
|
|
33
|
+
mask = 0x55
|
|
34
|
+
pcm_val = -pcm_val - 1
|
|
35
|
+
if pcm_val < 0:
|
|
36
|
+
pcm_val = 0
|
|
37
|
+
seg = _search(pcm_val)
|
|
38
|
+
if seg >= 8:
|
|
39
|
+
return 0x7F ^ mask
|
|
40
|
+
aval = seg << 4
|
|
41
|
+
if seg < 2:
|
|
42
|
+
aval |= (pcm_val >> 1) & 0x0F
|
|
43
|
+
else:
|
|
44
|
+
aval |= (pcm_val >> seg) & 0x0F
|
|
45
|
+
return aval ^ mask
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def pcm_to_alaw(pcm: bytes) -> bytes:
|
|
49
|
+
"""Encode little-endian signed 16-bit PCM bytes to A-law bytes."""
|
|
50
|
+
n = len(pcm) // 2
|
|
51
|
+
if n == 0:
|
|
52
|
+
return b""
|
|
53
|
+
samples = struct.unpack(f"<{n}h", pcm[: n * 2])
|
|
54
|
+
return bytes(linear2alaw(s) for s in samples)
|
aidot/login_const.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Constants for the aidot integration."""
|
|
2
|
+
|
|
3
|
+
APP_ID = "1383974540041977857"
|
|
4
|
+
BASE_URL = "https://prod-us-api.arnoo.com/v17"
|
|
5
|
+
|
|
6
|
+
PUBLIC_KEY_PEM = b"""
|
|
7
|
+
-----BEGIN PUBLIC KEY-----
|
|
8
|
+
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCtQAnPCi8ksPnS1Du6z96PsKfN
|
|
9
|
+
p2Gp/f/bHwlrAdplbX3p7/TnGpnbJGkLq8uRxf6cw+vOthTsZjkPCF7CatRvRnTj
|
|
10
|
+
c9fcy7yE0oXa5TloYyXD6GkxgftBbN/movkJJGQCc7gFavuYoAdTRBOyQoXBtm0m
|
|
11
|
+
kXMSjXOldI/290b9BQIDAQAB
|
|
12
|
+
-----END PUBLIC KEY-----
|
|
13
|
+
"""
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: python-aidot-cameras
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Adds camera support to AiDot-Development-Team's python-AiDot library
|
|
5
|
+
Home-page: https://github.com/cbrightly/python-AiDot
|
|
6
|
+
Author: aidotdev2024
|
|
7
|
+
Author-email: Chris Brightly <chris.brightly@gmail.com>
|
|
8
|
+
Project-URL: Homepage, https://github.com
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Requires-Python: >=3.8
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Dynamic: author
|
|
16
|
+
Dynamic: home-page
|
|
17
|
+
Dynamic: license-file
|
|
18
|
+
|
|
19
|
+
# python-aidot
|
|
20
|
+
|
|
21
|
+
Control AIDOT WiFi lights **and cameras** from Python and Home Assistant.
|
|
22
|
+
|
|
23
|
+
This is a camera-capable fork of the upstream lights-only
|
|
24
|
+
[`python-aidot`](https://github.com/Aidot-Development-Team/python-aidot). It adds
|
|
25
|
+
live WebRTC video streaming (DTLS and SDES-SRTP paths), snapshots, PTZ, camera
|
|
26
|
+
controls, cloud recordings/thumbnails, and two-way (push-to-talk) audio, plus a
|
|
27
|
+
Home Assistant custom component that exposes all of it.
|
|
28
|
+
|
|
29
|
+
## Library install
|
|
30
|
+
|
|
31
|
+
The camera support is **not published to PyPI** (PyPI only has the upstream
|
|
32
|
+
lights-only releases). Install this fork's library directly from the repo:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
# lights + camera cloud/control only:
|
|
36
|
+
pip install "git+https://github.com/cbrightly/python-AiDot"
|
|
37
|
+
# add live WebRTC streaming, snapshots, and two-way audio:
|
|
38
|
+
pip install "python-aidot[webrtc] @ git+https://github.com/cbrightly/python-AiDot"
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Home Assistant component
|
|
42
|
+
|
|
43
|
+
The custom component lives in `custom_components/aidot/`. Its `manifest.json`
|
|
44
|
+
lists the third-party Python dependencies (aiortc, av, paho-mqtt, …) so Home
|
|
45
|
+
Assistant installs those automatically, **but the `aidot` library itself is not
|
|
46
|
+
on PyPI** - install it into Home Assistant's Python environment first:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
# inside the HA venv / container
|
|
50
|
+
pip install "python-aidot[webrtc] @ git+https://github.com/cbrightly/python-AiDot"
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Then copy `custom_components/aidot/` into your HA `config/custom_components/`
|
|
54
|
+
folder (or add this repo to HACS as a custom repository) and restart Home
|
|
55
|
+
Assistant.
|
|
56
|
+
|
|
57
|
+
## CLI
|
|
58
|
+
|
|
59
|
+
`test_camera.py` exercises the camera features directly - discovery, LAN probe,
|
|
60
|
+
WebRTC streaming, snapshots, recordings, attribute get/set, and two-way audio
|
|
61
|
+
(`--talk`). Run `python test_camera.py --help` for the full list.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
aidot/__init__.py,sha256=tIiGDn9iiYOeFRXbUkZdcCtc6-DvC9J1_sQZjPfPE4I,617
|
|
2
|
+
aidot/aes_utils.py,sha256=luqUzsTTJzZs2O861jGlAJLFZtH_6UBha4H-rvEXGtM,2175
|
|
3
|
+
aidot/client.py,sha256=MUKrq6U-bR9ZwJ1k_MHFJrQNixTeZDrGTyogfhpBE6Y,16967
|
|
4
|
+
aidot/const.py,sha256=ZL4E5jygope729oNt0ddEpaMNmPgrINHjf966gh6hzE,12811
|
|
5
|
+
aidot/credentials.py,sha256=OrusdjH_29Wi6Yq4IeMM1uA9swtf7EwX0-eIUBIUTl0,5784
|
|
6
|
+
aidot/device_client.py,sha256=zeuO1opwcw3ZyCtX1A18RVb3SA1LqWBh0HoAY-z8Oho,598731
|
|
7
|
+
aidot/discover.py,sha256=FBm8L18HS06VXMCtXB0OqxKN-PCEHpISIEeqWpELE0s,8165
|
|
8
|
+
aidot/exceptions.py,sha256=3XmliOOGVtTj96fdv87PkXqMgr7F8-e3tdPIWx1aFuU,1525
|
|
9
|
+
aidot/g711.py,sha256=Ls5BzApKzV0bOKrWiAw8iLqeHx4uUIoo3RJfxYMyPuA,1563
|
|
10
|
+
aidot/login_const.py,sha256=1Gg-hGav5AQ_DPJPIi21clXlx76EwFvhWrg1OQLdbY4,434
|
|
11
|
+
python_aidot_cameras-0.1.0.dist-info/licenses/LICENSE,sha256=5k6Ccd7T5u39Y8730J0qr1ahoaEkd1cahwEccuUZ2Dc,1079
|
|
12
|
+
python_aidot_cameras-0.1.0.dist-info/METADATA,sha256=_MNnaC8r2WOpHsq3wjSabi3xVF0HUM1p_2G9El7PG-M,2339
|
|
13
|
+
python_aidot_cameras-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
14
|
+
python_aidot_cameras-0.1.0.dist-info/top_level.txt,sha256=_doNL2OOnXeinm1X72eH2wz26wAkZwbM47KhNB6_QzI,6
|
|
15
|
+
python_aidot_cameras-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Aidot Development Team
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
aidot
|