-
Notifications
You must be signed in to change notification settings - Fork 21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add camera support #336
base: master
Are you sure you want to change the base?
Add camera support #336
Conversation
It uses the mjpeg stream over the http server to access the camara feed. This stream implementation is based on the mjpeg integration.
This needs an update in the tasmota integration of the home assistant to work end-to-end. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good, just questions about configurability
Please fix the lint errors 👍 |
There are still lint errors. |
I had defined them to support the flip/resolution/brightness change operations. However, it is easier to support these operations via automation/blueprints, since these are just commands over mqtt. Dropping them from the code to avoid the linter warnings.
You need to add aiohttp to |
My bad. |
I had originally added the additional constants to support resolution/brightness/contrast configuration. I implemented it via home assistant blueprints instead: tasmota/blueprints#4 |
Hmm, I'm not sure what you mean now. Can you elaborate a bit? |
Linters are still failing.. I'll explain how to set up the linters locally then.
After that, activate the local development environment for hatasmota:
Run the linters:
Note that flake8 will complain about f-strings in existing code if run on Python 3.12 or newer, please ignore that |
The mypy error is genuine, I think it would make sense to modify the code like this: diff --git a/hatasmota/camera.py b/hatasmota/camera.py
index 25fa14b..2cb4840 100644
--- a/hatasmota/camera.py
+++ b/hatasmota/camera.py
@@ -5,7 +5,7 @@ from __future__ import annotations
from dataclasses import dataclass
import logging
from typing import Any
-from aiohttp import ClientSession, web
+from aiohttp import ClientResponse, ClientSession
from .const import (
CONF_IP,
@@ -122,12 +122,12 @@ class TasmotaCamera(TasmotaAvailability, TasmotaEntity):
"""Unsubscribe to all MQTT topics."""
self._sub_state = await self._mqtt_client.unsubscribe(self._sub_state)
- def get_still_image_stream(self, websession: ClientSession) -> web.StreamResponse | None:
+ async def get_still_image_stream(self, websession: ClientSession) -> ClientResponse:
"""Get the io stream to read the static image."""
still_image_url = f"http://{self._cfg.ip_address}/snapshot.jpg"
- return websession.get(still_image_url)
+ return await websession.get(still_image_url)
- def get_mjpeg_stream(self, websession: ClientSession) -> web.StreamResponse | None:
+ async def get_mjpeg_stream(self, websession: ClientSession) -> ClientResponse:
"""Get the io stream to read the mjpeg stream."""
mjpeg_url = f"http://{self._cfg.ip_address}:81/cam.mjpeg"
- return websession.get(mjpeg_url)
+ return await websession.get(mjpeg_url) You could also skip the await, and type the return values as diff --git a/hatasmota/camera.py b/hatasmota/camera.py
index 25fa14b..a78a571 100644
--- a/hatasmota/camera.py
+++ b/hatasmota/camera.py
@@ -2,10 +2,11 @@
from __future__ import annotations
+from collections.abc import Awaitable
from dataclasses import dataclass
import logging
from typing import Any
-from aiohttp import ClientSession, web
+from aiohttp import ClientResponse, ClientSession
from .const import (
CONF_IP,
@@ -122,12 +123,12 @@ class TasmotaCamera(TasmotaAvailability, TasmotaEntity):
"""Unsubscribe to all MQTT topics."""
self._sub_state = await self._mqtt_client.unsubscribe(self._sub_state)
- def get_still_image_stream(self, websession: ClientSession) -> web.StreamResponse | None:
+ def get_still_image_stream(self, websession: ClientSession) -> Awaitable[ClientResponse]:
"""Get the io stream to read the static image."""
still_image_url = f"http://{self._cfg.ip_address}/snapshot.jpg"
return websession.get(still_image_url)
- def get_mjpeg_stream(self, websession: ClientSession) -> web.StreamResponse | None:
+ def get_mjpeg_stream(self, websession: ClientSession) -> Awaitable[ClientResponse]:
"""Get the io stream to read the mjpeg stream."""
mjpeg_url = f"http://{self._cfg.ip_address}:81/cam.mjpeg"
return websession.get(mjpeg_url) |
This commit also rearranges the code as per the linter suggestions.
Oh.. I had only used the pylint linter locally. About my blueprint-related comment: |
hatasmota/discovery.py
Outdated
def get_camera_entities( | ||
discovery_msg: dict, | ||
) -> list[tuple[TasmotaCameraConfig | None, DiscoveryHashType]]: | ||
"""Generate camera configuration.""" | ||
camera_entities: list[tuple[TasmotaCameraConfig | None, DiscoveryHashType]] = [] | ||
|
||
entity = None | ||
discovery_hash = (discovery_msg[CONF_MAC], "cam", "cam", 0) | ||
if CONF_CAM in discovery_msg and discovery_msg[CONF_CAM]: | ||
entity = TasmotaCameraConfig.from_discovery_message(discovery_msg, "camera") | ||
camera_entities.append((entity, discovery_hash)) | ||
|
||
return camera_entities | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please move this to before get_cover_entities
to keep the functions mostly sorted alphabetically
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
hatasmota/discovery.py
Outdated
elif platform == "camera": | ||
entities.extend(get_camera_entities(discovery_msg)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please move this before cover to keep the alphabetical sorting
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
hatasmota/discovery.py
Outdated
if platform == "camera": | ||
return TasmotaCamera(config=config, mqtt_client=mqtt_client) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please move this before cover to keep the alphabetical sorting
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
hatasmota/camera.py
Outdated
class TasmotaCameraConfig(TasmotaAvailabilityConfig, TasmotaEntityConfig): | ||
"""Tasmota camera configuation.""" | ||
|
||
idx: int |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does tasmota support more than a single camera? If not, can we skip this, it seems to be hardcoded to 0 below?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed.
hatasmota/camera.py
Outdated
|
||
idx: int | ||
|
||
command_topic: str |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't seem to be used
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was added earlier to support the configuring the camera parameters. But since it is not used, I have removed it.
The configuration is done easily via the blueprint mentioned earlier.
hatasmota/camera.py
Outdated
async def subscribe_topics(self) -> None: | ||
"""Subscribe to topics.""" | ||
|
||
def state_message_received(msg: ReceiveMessage) -> None: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is this used for? The core PR doesn't seem to use it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was part of the original version of the core PR. It was removed during the review.
I have updated this PR to reflect that.
Earlier the sensors like fps and failures were used to set the camera state. But this logic was removed from the tasmota component of the home-assistant. Removing it from the hatasmota module as well. Remove related now-unused imports from camera.py. Minor changes: It also rearranges the functions in discovery.py so that they are alphabetical.
It uses the mjpeg stream over the http server to access the camara feed.
The stream implementation is based on the mjpeg integration in home assistant.