Skip to content

Commit

Permalink
feat: update doc and complete p2p streaming
Browse files Browse the repository at this point in the history
  • Loading branch information
fuatakgun committed Dec 6, 2023
1 parent 6a7aa03 commit 0a7ae61
Show file tree
Hide file tree
Showing 3 changed files with 43 additions and 81 deletions.
67 changes: 10 additions & 57 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Welcome to Alpha release of Eufy Security Integration for Home Assistant. Congra
- [Installation](#installation)
- [Important: You must set Streaming Quality to LOW and Streaming Codec to LOW in all possible places, otherwise Home Assistant will not be able to handle video generation and/or playing.](#important-you-must-set-streaming-quality-to-low-and-streaming-codec-to-low-in-all-possible-places-otherwise-home-assistant-will-not-be-able-to-handle-video-generation-andor-playing)
- [1. Installing Eufy Security Add-On](#1-installing-eufy-security-add-on)
- [2. Install RTSP Simple Server Add-on - Required for P2P Based Video Streaming - Not Required for RTSP Based Video Streaming](#2-install-rtsp-simple-server-add-on---required-for-p2p-based-video-streaming---not-required-for-rtsp-based-video-streaming)
- [2. Install go2rtc Add-on](#2-install-go2rtc-add-on)
- [3. Installing Eufy Security Integration](#3-installing-eufy-security-integration)
- [4. Setting up your dashboard for camera](#4-setting-up-your-dashboard-for-camera)
- [Features](#features)
Expand All @@ -33,45 +33,13 @@ Welcome to Alpha release of Eufy Security Integration for Home Assistant. Congra
- Trusted device name is a required identifier for Eufy systems to record this add-on as mobile client, so you can differentiate the connection from this add-on in multi factor authentication (two factor authentication) page.
- As we already called out earlier, add-on heavily relies on push notifications, so you must enable all kind of push notifications (motion detected, person detected, lock events, alarm events etc) in your mobile app. These notifications are not user based but device based so after enabling all these notifications, your main account will probably bloated with many push notifications. In android, there is a setting to disable specific notifications, please use it.

RTSP Based Streaming - When your devices support RTSP

```mermaid
sequenceDiagram
Home Assistant ->> Eufy-Security-WS: Call Start RTSP Stream
Eufy-Security-WS ->> Device/Station: Call Start RTSP Stream
Device/Station ->> Eufy-Security-WS: Return RTSP Stream URL
Eufy-Security-WS ->> Home Assistant: Return RTSP Stream URL
Home Assistant ->> Device/Station: (Native) Watch Video
WebRTC (go2rtc) ->> Device/Station: Get RTSP Stream Bytes
WebRTC (go2rtc) ->> WebRTC (go2rtc): Generate WebRTC Video
Home Assistant ->> WebRTC (go2rtc): (WebRTC) Watch Video
```

P2P Based Streaming - When we have to generate the stream in hard way

```mermaid
sequenceDiagram
Home Assistant ->> Eufy-Security-WS: Call Start Live Stream
Eufy-Security-WS ->> Device/Station: Call Start Live Stream
Device/Station ->> Eufy-Security-WS: Return data bytes from stream
Eufy-Security-WS ->> Home Assistant: Return data bytes from stream
Home Assistant ->> Random TCP Port: Write data bytes from stream
Random TCP Port ->> FFMPEG Process : Write individiual bytes of stream
FFMPEG Process ->> RTSP Simple Server: Generate Video Stream via codec
RTSP Simple Server ->> RTSP Simple Server: Generate RTSP Stream (rtsp://127.0.0.1:9554/SERIALNO)
Home Assistant ->> RTSP Simple Server: (Native) Watch Video
WebRTC (go2rtc) ->> RTSP Simple Server: Get RTSP Stream Bytes
WebRTC (go2rtc) ->> WebRTC (go2rtc): Generate WebRTC Video
Home Assistant ->> WebRTC (go2rtc): (WebRTC) Watch Video
```

# Supported or Known Working devices

Please check here: /~https://github.com/bropat/eufy-security-client#known-working-devices

# Installation

In upcoming steps, you are going to install at least one add-on and one integration.
In upcoming steps, you are going to install at least one add-on and two integrations.

In Home Assistant eco-system, if you are using Supervised or HASS OS based setup, you can use `Add-ons` page of Home Assistant to install these. If you are running Core or you don't have `Add-ons` option in your setup, you need to install the docker and run these containers yourself. You will see respective commands in respective steps. If you are interested in composing of your docker container, please check the end section

Expand All @@ -81,7 +49,7 @@ If you are intending to use this integration for video streaming purposes and if

If you are intending to use this integration for video streaming purposes and if your camera supports RTSP, you will probably enjoy reliable stream because generating RTSP stream is responsibility of hardware and it is very much reliable than P2P based streaming. There is no need to convert incoming P2P bytes into RTSP stream. There are some modified version of Android apk of Eufy Security out there which could enable RTSP stream for unsupported devices but I have not tried it. Moreover, I do not own personally a P2P required device, that is because, many times, I cannot replicate your issues locally and we need to work together to debug these issues.

Lastly, your camera would not start streaming magically by itself, you have to call `turn_on` or `turn_off` services of respective camera entities. So, when you first install everything, you would not have any video until you call these functions. Moreover, P2P streaming might stop randomly because of low level issues, you can restart it again with `turn_off` and `turn_on`. You can trigger your automation's on camera states (idle, preparing, streaming).
Lastly, your camera would not start streaming magically by itself, you have to call `turn_on` or `turn_off` services of respective camera entities. So, when you first install everything, you would not have any video until you call these functions. Moreover, P2P streaming might stop randomly because of low level technical issues, you can restart it again with `turn_off` and `turn_on`. You can trigger your automation's on camera states (idle, preparing, streaming).

### Important: You must set Streaming Quality to LOW and Streaming Codec to LOW in all possible places, otherwise Home Assistant will not be able to handle video generation and/or playing. ###

Expand All @@ -91,42 +59,27 @@ So, let's start.

Please follow the guideline from here: /~https://github.com/bropat/hassio-eufy-security-ws

## 2. Install RTSP Simple Server Add-on - Required for P2P Based Video Streaming - Not Required for RTSP Based Video Streaming

If you use your own docker service, please run it like this `docker run -it RTSP_PROTOCOLS=tcp -p 8554:8554 -p 1935:1935 bluenviron/mediamtx:latest` and jump into Step 5 (Note that the source project was renamed to `MediaMTX`).

1- Add `RTSP Simple Server Add-on` Repository to `Add-On Store`. Please follow steps located here (https://www.home-assistant.io/common-tasks/os#installing-third-party-add-ons) and use this repository URL (/~https://github.com/fuatakgun/rtsp_simple_server/)
## 2. Install go2rtc Add-on

2- Search `RTSP Simple Server` on `Add-on Store` (https://your-instance.duckdns.org/hassio/store)
This is a must for P2P streaming and nice to have for RTSP streaming. P2P streaming will use go2rtc to generate stream with a specific RTSP address. RTSP streaming will use this for faster streaming.

3- Install `RTSP Simple Server`

4- Hit `Start` and wait for it to be started.

5- Check Logs, you have to see something like this;

```
2022/12/27 23:53:09 I [0/0] rtsp-simple-server v0.17.6
2022/12/27 23:53:09 I [0/0] [RTSP] TCP listener opened on :8554
2022/12/27 23:53:09 I [0/0] [RTMP] listener opened on :1935
2022/12/27 23:53:09 I [0/0] [HLS] listener opened on :8888
```
There are two ways of doing this, either installing add-on itself or installing Webrtc custom integration. I suggest you to install Webrtc custom integration, which includes go2rtc and respective front-end card for faster streaming. Installing go2rtc with or without webrtc can be done following this link: /~https://github.com/AlexxIT/go2rtc/#go2rtc-home-assistant-add-on

## 3. Installing Eufy Security Integration

1- If you have not already installed, install `HACS` following this guide: https://hacs.xyz/docs/setup/download

2- When `HACS` is ready, search for `Eufy Security` inside `HACS` Integrations
2- When `HACS` is ready, search for `Eufy Security` inside `HACS` Integrations.

3- Install `Eufy Security` integration, restart your Home Assistant instance.
3- Install `Eufy Security` integration through HACS, restart your Home Assistant instance.

4- Install `Eufy Security` service from the previous step by navigating to `Settings -> Devices & Services` page (https://your-instance.duckdns.org/config/integrations). Click on `Add Integration` and search for `Eufy Security`. If you do not see it, first validate that it is installed via HACS and you had restarted it, later try with another browser. Integrations list might be already cached in your browser.
4- Navigate to `Settings -> Devices & Services` page of Home Assistant(https://your-instance.duckdns.org/config/integrations). Click on `Add Integration` and search for `Eufy Security` (not `Eufy`, it is `Eufy Security` exactly). If you do not see it, first validate that it is installed via HACS and you had restarted it, later try with another browser. Integrations list might be already cached in your browser.

5- Put `Eufy Security Add-on IP Address` (127.0.0.1 for Supervised installation) and `configured port` (default 3000) and click Submit.

6- You might receive Captcha or Multi Factor Authentications (MFA) warnings, please Reconfigure the integration. Captcha code will be visible on Reconfigure page and MFA Code will be emailed or texted to you. Please enter these values. After this, you might need to restart your Home Assistant instance.

7- If you have installed `RTSP Simple Server Add-On`, please put its `IP Address` and `Port` into Integration Configuration page.
7- If you have installed `RTSP Simple Server Add-On`, please put its `IP Address` into Integration Configuration page. You can put `127.0.0.1` for Supervised installation.

8- You can also configure `Cloud Scan Interval`, Video Analyze Duration, `Custom Name 1`, `Custom Name 2` and `Custom Name 3`

Expand Down
25 changes: 15 additions & 10 deletions custom_components/eufy_security/eufy_security_api/camera.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ def __init__(self, api, serial_no: str, properties: dict, metadata: dict, comman
self.image_last_updated = None

self.stream_future = None
self.stream_checker = None

self.p2p_streamer = P2PStreamer(self)

Expand Down Expand Up @@ -105,8 +106,8 @@ async def _handle_livestream_video_data_received(self, event: Event):
self.video_queue.append(bytearray(event.data["buffer"]["data"]))

async def _handle_livestream_audio_data_received(self, event: Event):
#pass
self.audio_queue.append(bytearray(event.data["buffer"]["data"]))
pass
#self.audio_queue.append(bytearray(event.data["buffer"]["data"]))

async def _initiate_start_stream(self, stream_type) -> bool:
self.set_stream_prodiver(stream_type)
Expand Down Expand Up @@ -135,17 +136,16 @@ async def _initiate_start_stream(self, stream_type) -> bool:
_LOGGER.debug(f"_initiate_start_stream - {self.stream_debug}")
return False

async def check_live_stream(self):
async def _check_live_stream(self):
while self.p2p_streamer.retry is None:
await asyncio.sleep(1)
await asyncio.sleep(0.5)

_LOGGER.debug(f"async_restart_livestream - start - {self.p2p_streamer.retry}")
if self.stream_status != StreamStatus.IDLE:
await self.stop_livestream()
await self.stop_livestream(is_internal=True)

if self.p2p_streamer.retry is True:
_LOGGER.debug(f"async_restart_livestream - sleep - {self.p2p_streamer.retry}")
await asyncio.sleep(1)
_LOGGER.debug(f"async_restart_livestream - start live stream finish - {self.p2p_streamer.retry}")
_LOGGER.debug(f"async_restart_livestream - start live stream start - {self.p2p_streamer.retry}")
await self.start_livestream()
_LOGGER.debug(f"async_restart_livestream - start live stream end - {self.p2p_streamer.retry}")

Expand All @@ -154,12 +154,17 @@ async def start_livestream(self) -> bool:
if await self._initiate_start_stream(StreamProvider.P2P) is False:
return False
self.stream_future = asyncio.create_task(self.p2p_streamer.start())
self.stream_checker = asyncio.create_task(self.check_live_stream())
self.stream_checker = asyncio.create_task(self._check_live_stream())
self.stream_status = StreamStatus.STREAMING
return True

async def stop_livestream(self):
async def stop_livestream(self, is_internal=False):
"""Process stop p2p livestream call"""
if is_internal is True:
# called from another function, so respect retry value
pass
else:
self.p2p_streamer.retry = False
await self.api.stop_livestream(self.product_type, self.serial_no)

async def start_rtsp_livestream(self) -> bool:
Expand Down
32 changes: 18 additions & 14 deletions custom_components/eufy_security/eufy_security_api/p2p_streamer.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,24 +43,28 @@ async def write_bytes(self, queue, queue_name):
async with aiohttp.ClientSession() as session:
resp = await session.post(url, data = self.chunk_generator(queue, queue_name), timeout=aiohttp.ClientTimeout(total=None, connect=5))
_LOGGER.debug(f"write_bytes {queue_name} - post response - {resp.status} - {await resp.text()}")
_LOGGER.debug("write_bytes - post ended - no retry")
self.retry = False
if resp is not None and resp.status is not None:
if resp.status == 500:
self.retry = self.retry or True
self.retry = self.retry or False

_LOGGER.debug("write_bytes - post ended - {self.retry}")
except (asyncio.exceptions.TimeoutError, asyncio.exceptions.CancelledError) as ex:
# live stream probabaly stopped, handle peacefully
_LOGGER.debug(f"write_bytes {queue_name} timeout/cancelled no retry exception {ex} - traceback: {traceback.format_exc()}")
self.retry = False
_LOGGER.debug(f"write_bytes {queue_name} timeout/cancelled NO RETRY exception {ex} - traceback: {traceback.format_exc()}")
self.retry = self.retry or False
except aiohttp.client_exceptions.ServerDisconnectedError as ex:
# connection to go2rtc server is broken, try again``
_LOGGER.debug(f"write_bytes {queue_name} server_disconnected retry exception {ex} - traceback: {traceback.format_exc()}")
self.retry = True
_LOGGER.debug(f"write_bytes {queue_name} server_disconnected RETRY exception {ex} - traceback: {traceback.format_exc()}")
self.retry = self.retry or True
except Exception as ex: # pylint: disable=broad-except
# other exceptions, log the error
_LOGGER.debug(f"write_bytes {queue_name} general exception no retry {ex} - traceback: {traceback.format_exc()}")
self.retry = False
_LOGGER.debug(f"write_bytes {queue_name} general exception NO RETRY {ex} - traceback: {traceback.format_exc()}")
self.retry = self.retry or False

_LOGGER.debug(f"write_bytes {queue_name} - ended")
_LOGGER.debug(f"write_bytes {queue_name} - ended with {self.retry}")

async def create_stream_on_go2rtc(self):
async def _create_stream_on_go2rtc(self):
parameters = {"name": str(self.camera.serial_no)}
url = GO2RTC_API_URL.format(self.camera.config.rtsp_server_address, GO2RTC_API_PORT)
url = f"{url}s"
Expand All @@ -77,15 +81,15 @@ async def create_stream_on_go2rtc(self):
result = response.status, await response.text()
_LOGGER.debug(f"create_stream_on_go2rtc - put stream response {result}")

def run(self, queue, name):
def _run(self, queue, name):
asyncio.run(self.write_bytes(queue, name))

async def start(self):
"""start streaming thread"""
# send API command to go2rtc to create a new stream
self.retry = None
await self.create_stream_on_go2rtc()
await self._create_stream_on_go2rtc()
await asyncio.gather(
asyncio.to_thread(self.run, self.camera.audio_queue, "audio"),
asyncio.to_thread(self.run, self.camera.video_queue, "video")
#asyncio.to_thread(self._run, self.camera.audio_queue, "audio"),
asyncio.to_thread(self._run, self.camera.video_queue, "video")
)

0 comments on commit 0a7ae61

Please sign in to comment.