Skip to content

Commit

Permalink
doc: 5.3.0
Browse files Browse the repository at this point in the history
  • Loading branch information
fuatakgun committed Dec 30, 2022
1 parent a790080 commit 059b5ef
Show file tree
Hide file tree
Showing 7 changed files with 256 additions and 88 deletions.
126 changes: 116 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
Welcome to Alpha release of Eufy Security Integration for Home Assistant. Congragulations on being a brave heart and trying this version.

# Gratitude #
# Gratitude
- @bropat for building docker image (/~https://github.com/bropat/eufy-security-ws) so I can wrap it as Home Assistant Add-on. You can also thank him over here: https://ko-fi.com/bropat
- @cdnninja for educating me on github actions and many other good practices
- Home assistant community (https://community.home-assistant.io/t/eufy-security-integration/318353)

# How is this working ? #
# How is this working ?
- @bropat built `eufy-security-ws` using `eufy-security-client` to imitate mobile app and web portal functionalities and I had wrapped `eufy-security-ws` as `eufy_security_addon` so we can use it as Home Assistant Add-on.
- Add-on requires email address, password, country code, event duration in seconds and trusted device name.
- Every time add-on is started, it forces all other sessions to log off, so you must create a secondary account and share your home/devices with secondary account including admin rights and you must use secondary account credentials on add-on page. Please login once to eufy mobile app with this secondary account to be sure that devices are available.
- Country code is very crucial to connect to correct regional servers. If your main account has setup in US and if you are trying to login into your secondary account in DE country, your device data would not be found in EU servers. So please pay attention to put correct country code. (Source: Alpha 2 country code https://en.wikipedia.org/wiki/ISO_3166-1#Officially_assigned_code_elements)
- Event duration in seconds correspond to how long (in seconds) entities in home assistant would stay in active. As an example, when camera notices a person, add-on would receive a push notification from eufy and home assistant integration will active person detected sensor and person detected sensor will stay on state for `event duration in seconds` long.
- 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 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.**
- **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. **

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

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

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.
Expand All @@ -28,9 +28,11 @@ 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 probabaly 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 automations on camera states (idle, preparing, streaming).

So, let's start.

## 1. Installing Eufy Security Add-On ##
## 1. Installing Eufy Security Add-On
1- Add 'Eufy Security 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/eufy_security_addon)

2- Search 'Eufy Security' on Add-on Store (https://your-instance.duckdns.org/hassio/store)
Expand All @@ -48,7 +50,7 @@ So, let's start.
2022-12-27 20:09:26.601 INFO Connected to station T8410PXXX on host 87.240.219.YYY and port 18969
```

## 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
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- Search `RTSP Simple Server` on `Add-on Store` (https://your-instance.duckdns.org/hassio/store)
Expand All @@ -65,7 +67,7 @@ So, let's start.
2022/12/27 23:53:09 I [0/0] [HLS] listener opened on :8888
```

## 3. Installing Eufy Security Integration ##
## 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
Expand All @@ -82,7 +84,7 @@ So, let's start.

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

## Setting up your dashboard ##
## Setting up your dashboard
Please replace `camera.entrance` with your camera entity name. You also need to install WebRTC integration from HACS for faster (almost instant) streaming.

```
Expand Down Expand Up @@ -127,8 +129,112 @@ cards:
- type: custom:webrtc-camera
entity: camera.entrance
```
# Features
- There are many sensors available out there, if you need an additional one, raise a request and share your `Debug (device)` and `Debug (station)` sensor attributes so I can extract these sensors. If these sensors cannot be extracted from state of device, please mention it explicitly.
- There are many `button`, `switch` and `select` entities, please use them.
- Integration Services;
- Force Sync - Integration will get latest changes from cloud. Some features are not updated via push notifications, you can call this if you want and integration will call this regularly.
- Camera Services;
- `turn_on` and `turn_off` - Integration will check if your device supports RTSP and fallback P2P based streaming
- `start_rtsp_livestream` and `stop_rtsp_livestream` - Stream will be started using RTSP if your device supports it
- `start_p2p_livestream` and `stop_p2p_livestream` - Stream will be started using P2P, all devices work here
- `generate_image` - This will generate a thumbnail for Home Assistant if camera is already streaming
- `ptz_up`, `ptz_down`, `ptz_right`, `ptz_left`, `ptz_360` - Pan and Tilt commands
- `trigger_camera_alarm_with_duration` - Trigger alarm on camera for a given duration
- `quick_response` - Send a quick response message for doorbell, you can get `voice_id` information from `Debug (device)` sensor attributes of device.
- `snooze` - Snooze ongoing notification for a given duration.
- Alarm Panel Services;
- There is an select entity called Guard Mode, it is one to one mapping of Eufy Security state.
- `trigger_base_alarm_with_duration` - Trigger alarm on station for a given duration
- `reset_alarm` - Reset ongoing alarm for a given duration
- `snooze` - Snooze ongoing notification for a given duration.
- `arm_home` - Switch to Home state
- `arm_away` - Switch to Away state
- `disarm` - Disarm the panel
- `alarm_arm_custom1` - Switch to custom 1
- `alarm_arm_custom2` - Switch to custom 2
- `alarm_arm_custom3` - Switch to custom 3
- `geofence` - Switch to geofencing, this might not impact the state of panel given that it will chage its state based on geolocation via eufy app
- `schedule` - Switch to custom 3, this might not impact the state of panel given that it will chage its state based on schedule via eufy app

# Examples
## Start streaming on camera, when there is a motion, this would generate a new thumbnail on Home Assistant
- Replace `camera.entrance` with your own entity name.
```
alias: Capture Image on Trigger, Send Mobile Notification with Actions, Snooze or Alarm via Actions
description: ""
trigger:
- platform: state
entity_id:
- binary_sensor.entrance_motion_detected
- binary_sensor.entrance_person_detected
to: "on"
id: sensor
- platform: event
event_type: mobile_app_notification_action
id: snooze
event_data:
action: SNOOZE
- platform: event
event_type: mobile_app_notification_action
id: alarm
event_data:
action: ALARM
condition: []
action:
- choose:
- conditions:
- condition: trigger
id: sensor
sequence:
- service: eufy_security.start_p2p_livestream
data: {}
target:
entity_id: camera.entrance
- service: eufy_security.generate_image
data: {}
target:
entity_id: camera.entrance
- service: eufy_security.stop_p2p_livestream
data: {}
target:
entity_id: camera.entrance
- service: notify.mobile_app_fuatx3pro
data:
message: Motion detected
data:
image: /api/camera_proxy/camera.entrance
actions:
- action: ALARM
title: Alarm
- action: SNOOZE
title: Snooze
- conditions:
- condition: trigger
id: snooze
sequence:
- service: eufy_security.snooze
data:
snooze_time: 10
snooze_chime: false
snooze_motion: true
snooze_homebase: false
target:
entity_id: camera.entrance
- conditions:
- condition: trigger
id: alarm
sequence:
- service: eufy_security.trigger_camera_alarm_with_duration
data:
duration: 1
target:
entity_id: camera.entrance
mode: single
```

# Debugging Issues #
# Debugging Issues
I am more than happy to debug individual issues as long as you follow setup instructions.

I need you to share your problematic cameras with me so that I can use my own machine to debug the issue.
Expand Down
12 changes: 12 additions & 0 deletions custom_components/eufy_security/alarm_control_panel.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,11 @@ class CurrentModeToState(Enum):
NONE = -1
AWAY = 0
HOME = 1
SCHEDULE = 2
CUSTOM_BYPASS = 3
NIGHT = 4
VACATION = 5
GEOFENCE = 47
DISARMED = 63


Expand Down Expand Up @@ -72,6 +74,8 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, asyn
platform.async_register_entity_service("alarm_arm_custom1", {}, "async_alarm_arm_custom_bypass")
platform.async_register_entity_service("alarm_arm_custom2", {}, "async_alarm_arm_night")
platform.async_register_entity_service("alarm_arm_custom3", {}, "async_alarm_arm_vacation")
platform.async_register_entity_service("geofence", {}, "geofence")
platform.async_register_entity_service("schedule", {}, "schedule")


class EufySecurityAlarmControlPanel(AlarmControlPanelEntity, EufySecurityEntity):
Expand Down Expand Up @@ -129,6 +133,14 @@ async def async_reset_alarm(self) -> None:
"""reset ongoing alarm but there is no change in current mode"""
await self.product.reset_alarm()

async def geofence(self) -> None:
"""switch to geofence mode"""
await self._set_guard_mode(CurrentModeToState.GEOFENCE)

async def schedule(self) -> None:
"""switch to schedule mode"""
await self._set_guard_mode(CurrentModeToState.SCHEDULE)

@property
def state(self):
alarm_delayed = get_child_value(self.product.properties, "alarmDelay", 0)
Expand Down
40 changes: 32 additions & 8 deletions custom_components/eufy_security/camera.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,12 @@
from .const import COORDINATOR, DOMAIN, Schema
from .coordinator import EufySecurityDataUpdateCoordinator
from .entity import EufySecurityEntity
from .eufy_security_api.camera import StreamProvider, StreamStatus
from .eufy_security_api.camera import (
STREAM_SLEEP_SECONDS,
STREAM_TIMEOUT_SECONDS,
StreamProvider,
StreamStatus,
)
from .eufy_security_api.metadata import Metadata
from .eufy_security_api.util import wait_for_value_to_equal

Expand All @@ -38,6 +43,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, asyn

# register entity level services
platform = entity_platform.async_get_current_platform()
platform.async_register_entity_service("generate_image", {}, "_generate_image")
platform.async_register_entity_service("start_p2p_livestream", {}, "_start_livestream")
platform.async_register_entity_service("stop_p2p_livestream", {}, "_stop_livestream")
platform.async_register_entity_service("start_rtsp_livestream", {}, "_start_rtsp_livestream")
Expand Down Expand Up @@ -101,9 +107,9 @@ async def async_create_stream(self):
async def _start_hass_streaming(self):
await wait_for_value_to_equal(self.product.__dict__, "stream_status", StreamStatus.STREAMING)
await self._stop_hass_streaming()
if await self.async_create_stream() is None:
return
await self.stream.start()
await self.async_create_stream()
if self.stream is not None:
await self.stream.start()
await self.async_camera_image()

async def _stop_hass_streaming(self):
Expand All @@ -120,20 +126,35 @@ async def _get_image_from_hass_stream(self, width, height):
while True:
result = await self.stream.async_get_image(width, height)
if result is not None:
_LOGGER.debug(f"_get_image_from_hass_stream - received {len(result)}")
return result
_LOGGER.debug(f"_get_image_from_hass_stream - is_empty {result is None}")
await asyncio.sleep(STREAM_SLEEP_SECONDS)

async def _get_image_from_stream_url(self, width, height):
while True:
result = await ffmpeg.async_get_image(self.hass, await self.stream_source(), width=width, height=height)
if result is not None:
_LOGGER.debug(f"_get_image_from_stream_url - received {len(result)}")
return result
_LOGGER.debug(f"_get_image_from_stream_url - is_empty {result is None}")
await asyncio.sleep(STREAM_SLEEP_SECONDS)

async def async_camera_image(self, width: int | None = None, height: int | None = None) -> bytes | None:
_LOGGER.debug(f"image 1 - {self.is_streaming} - {self.stream}")
if self.is_streaming is True:
if self.stream is not None:
with contextlib.suppress(asyncio.TimeoutError):
self._last_image = await asyncio.wait_for(self._get_image_from_hass_stream(width, height), 5)
_LOGGER.debug(f"image 3.1 - {self.is_streaming} - is_empty {self._last_image is None} - {self.stream.available}")
self._last_image = await asyncio.wait_for(self._get_image_from_stream_url(width, height), STREAM_TIMEOUT_SECONDS)
# self._last_image = await asyncio.wait_for(self._get_image_from_hass_stream(width, height), STREAM_TIMEOUT_SECONDS)
_LOGGER.debug(f"image 2 with hass stream - is_empty {self._last_image is None}")
else:
self._last_image = await ffmpeg.async_get_image(self.hass, await self.stream_source(), width=width, height=height)
_LOGGER.debug(f"image 3.2 - {self.is_streaming} - is_empty {self._last_image is None}")
with contextlib.suppress(asyncio.TimeoutError):
self._last_image = await asyncio.wait_for(self._get_image_from_stream_url(width, height), STREAM_TIMEOUT_SECONDS)
_LOGGER.debug(f"image 2 without hass stream - is_empty {self._last_image is None}")
self._last_url = None

# until encryption is fixed on thumbnails and notifications this section is turned off
# else:
# current_url = get_child_value(self.product.properties, MessageField.PICTURE_URL.value)
# if current_url != self._last_url and current_url.startswith("https"):
Expand Down Expand Up @@ -209,6 +230,9 @@ async def _async_ptz_right(self) -> None:
async def _async_ptz_360(self) -> None:
await self.product.ptz_360()

async def _generate_image(self) -> None:
await self.async_camera_image()

async def _async_quick_response(self, voice_id: int) -> None:
await self.product.quick_response(voice_id)

Expand Down
Loading

0 comments on commit 059b5ef

Please sign in to comment.