Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 32 additions & 1 deletion camect/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ def log_to_console():
_LOGGER.addHandler(handler)

EventListener = Callable[[Dict[str, str]], None]
ConnectionListener = Callable[[bool], None]

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

Expand All @@ -57,7 +58,9 @@ def __init__(self, server_addr: str, user: str, password: str) -> None:
self._password = password
# Make sure it connects.
self.get_info()
self._ws_connected = False
self._evt_listeners_ = []
self._conn_listeners_ = []
self._evt_loop = asyncio.new_event_loop()
evt_thread = Thread(
target=self._evt_loop.run_until_complete, args=(self._event_handler(),))
Expand Down Expand Up @@ -143,7 +146,8 @@ def list_cameras(self) -> List[Dict[str, str]]:
json = resp.json()
if resp.status_code != 200:
raise Error("Failed to get home info: [%d](%s)" % (resp.status_code, json["err_msg"]))
return json["camera"]
# Hub may omit the key when there are no cameras
return json.get("camera") or []

def snapshot_camera(self, cam_id: str, width: int = 0, height: int = 0,
ts_ms: int = 0) -> bytes:
Expand Down Expand Up @@ -231,12 +235,33 @@ def ptz(self, cam_id: str, action: int):
_LOGGER.error(
"Failed to ptz camera %s: [%d](%s)", cam_id, resp.status_code, json["err_msg"])

def is_event_stream_connected(self) -> bool:
"""True while the event websocket is open."""
return self._ws_connected

def add_event_listener(self, cb: EventListener) -> None:
self._evt_loop.call_soon_threadsafe(self._evt_listeners_.append, cb)

def del_event_listener(self, cb: EventListener) -> None:
self._evt_loop.call_soon_threadsafe(self._evt_listeners_.remove, cb)

def add_connection_listener(self, cb: ConnectionListener) -> None:
"""Notify when the event websocket connects (True) or drops (False)."""
self._evt_loop.call_soon_threadsafe(self._conn_listeners_.append, cb)

def del_connection_listener(self, cb: ConnectionListener) -> None:
self._evt_loop.call_soon_threadsafe(self._conn_listeners_.remove, cb)

def _set_ws_connected(self, connected: bool) -> None:
if self._ws_connected == connected:
return
self._ws_connected = connected
for cb in list(self._conn_listeners_):
try:
cb(connected)
except Exception:
_LOGGER.warning("Connection listener failed", exc_info=True)

def _authorization(self) -> str:
return base64.b64encode(f"{self._user}:{self._password}".encode()).decode()

Expand All @@ -249,6 +274,7 @@ async def _event_handler(self):
_LOGGER.info("Connecting to Camect hub at '%s' ...", self._ws_uri)
websocket = await websockets.connect(self._ws_uri, ssl=context,
additional_headers={"Authorization": authorization})
self._set_ws_connected(True)
try:
async for msg in websocket:
_LOGGER.debug("Received event: %s", msg)
Expand All @@ -260,20 +286,25 @@ async def _event_handler(self):
_LOGGER.error("Invalid JSON '%s': %s", msg, err)
except (websockets.exceptions.ConnectionClosed, OSError):
_LOGGER.warning("Websocket to Camect hub was closed.")
self._set_ws_connected(False)
await asyncio.sleep(5)
except (ConnectionRefusedError, ConnectionError):
_LOGGER.warning("Cannot connect Camect hub.")
self._set_ws_connected(False)
await asyncio.sleep(10)
except:
e = sys.exc_info()[0]
_LOGGER.warning("Unexpected exception: %s", e, exc_info=True)
self._set_ws_connected(False)
await asyncio.sleep(10)
except (OSError, ConnectionError):
_LOGGER.warning("Cannot connect Camect hub.")
self._set_ws_connected(False)
await asyncio.sleep(10)
except:
e = sys.exc_info()[0]
_LOGGER.warning("Unexpected exception: %s", e, exc_info=True)
self._set_ws_connected(False)
await asyncio.sleep(10)

Home = Hub
5 changes: 3 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@

setuptools.setup(
name="camect-py",
version="0.2.0",
version="0.2.2",
author="Chao Liu",
author_email="chao@camect.com",
description="A client library to talk to Camect.",
license="MIT License",
long_description=open('README.md').read(),
long_description_content_type="text/markdown",
url="https://github.com/camect/camect-py",
url="https://github.com/jimboca/camect-py",
packages=setuptools.find_packages(),
classifiers=[
"Programming Language :: Python :: 3",
Expand All @@ -18,6 +18,7 @@
],
install_requires=[
"websockets>=14.0",
"requests",
],
python_requires='>=3.6',
)