Skip to content

Commit

Permalink
fix: better handling of reconnections
Browse files Browse the repository at this point in the history
  • Loading branch information
fuatakgun committed Nov 9, 2023
1 parent 8fd0603 commit 91682b4
Show file tree
Hide file tree
Showing 15 changed files with 75 additions and 22 deletions.
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ Welcome to Alpha release of Eufy Security Integration for Home Assistant. Congra
- [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)
- [Example Automation](#example-automation)
- [Start streaming on camera, when there is a motion, this would generate a new thumbnail on Home Assistant](#start-streaming-on-camera-when-there-is-a-motion-this-would-generate-a-new-thumbnail-on-home-assistant)
- [Example Automations](#example-automations)
- [Send notification with thumbnail from home assistant](#send-notification-with-thumbnail-from-home-assistant)
- [Alternative trigger condition](#alternative-trigger-condition)
- [Unlock safe with code](#unlock-safe-with-code)
- [Debugging Issues](#debugging-issues)
- [Show Off](#show-off)
Expand Down Expand Up @@ -342,6 +343,7 @@ target:
# Debugging Issues

First, check all issues (open or close) to find out if there was any similar question rather than duplicating it.
Focus on enabling push notification settings, lowering camera streaming/recording quality and removing any network level isoloation/restriction. Most of the issues could be eliminated via these.
Later on, if you find a similar issue, please just put +1 on it, sharing same logs over and over does not help at all.
Lastly, create your issue following the template. I will probably ask follow up questions later on.

Expand Down
6 changes: 4 additions & 2 deletions custom_components/eufy_security/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,11 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry):
hass.async_add_job(hass.config_entries.async_forward_entry_setup(config_entry, platform.value))

async def update(event_time_utc):
await coordinator.async_refresh()
local_coordinator = hass.data[DOMAIN][COORDINATOR]
await local_coordinator.async_refresh()

config_entry.add_update_listener(async_reload_entry)
async_track_time_interval(hass, update, timedelta(seconds=coordinator.config.sync_interval))
# async_track_time_interval(hass, update, timedelta(seconds=coordinator.config.sync_interval))

return True

Expand Down
4 changes: 4 additions & 0 deletions custom_components/eufy_security/camera.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,10 @@ def is_streaming(self) -> bool:
def available(self) -> bool:
return True

@property
def extra_state_attributes(self):
return {"stream_debug": self.product.stream_debug}

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)
Expand Down
19 changes: 16 additions & 3 deletions custom_components/eufy_security/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from homeassistant.core import callback
from homeassistant.helpers import aiohttp_client
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.event import async_call_later

from .const import COORDINATOR, DOMAIN
from .eufy_security_api.api_client import ApiClient
Expand Down Expand Up @@ -60,7 +61,7 @@ def async_get_options_flow(config_entry: ConfigEntry):
_LOGGER.debug(f"{DOMAIN} EufySecurityOptionFlowHandler - {config_entry.data}")
return EufySecurityOptionFlowHandler(config_entry)

def __init__(self):
def __init__(self) -> None:
self._errors = {}

async def async_step_user(self, user_input=None):
Expand All @@ -79,8 +80,20 @@ async def async_step_user(self, user_input=None):
coordinator.config.captcha_img = None
await coordinator.set_captcha_and_connect(captcha_id, captcha_input)

if self._async_current_entries():
await self.hass.config_entries.async_reload(self.context["entry_id"])
config_entry_id = None
for entry in self._async_current_entries():
config_entry_id = entry.entry_id

async def try_reloading(_now):
_LOGGER.debug(f"{DOMAIN} try_reloading start after captcha/mfa")
await coordinator.disconnect()
self.hass.data[DOMAIN] = {}
await self.hass.config_entries.async_reload(config_entry_id)
_LOGGER.debug(f"{DOMAIN} try_reloading finish after captcha/mfa")

async_call_later(self.hass, 3, try_reloading)
return self.async_abort(reason="reauth_successful")


if self._async_current_entries():
return self.async_abort(reason="single_instance_allowed")
Expand Down
4 changes: 4 additions & 0 deletions custom_components/eufy_security/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,14 +83,18 @@ async def set_log_level(self, log_level: str) -> None:

async def _update_local(self):
try:
_LOGGER.debug(f"coordinator - start update_local")
await self._api.poll_refresh()
_LOGGER.debug(f"coordinator - complete update_local")
return self.data
except WebSocketConnectionException as exc:
raise UpdateFailed(f"Error communicating with Add-on: {exc}") from exc

async def disconnect(self):
"""disconnect from api"""
await self._api.disconnect()
self._api = None
await self.async_shutdown()

async def _async_reload(self, _):
await asyncio.sleep(5)
Expand Down
10 changes: 5 additions & 5 deletions custom_components/eufy_security/eufy_security_api/api_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,14 +130,14 @@ async def _get_products(self, product_type: ProductType, products: list) -> dict
async def set_captcha_and_connect(self, captcha_id: str, captcha_input: str):
"""Set captcha set products"""
await self._set_captcha(captcha_id, captcha_input)
await asyncio.sleep(10)
await self._set_products()
await asyncio.sleep(30)
# await self._set_products()

async def set_mfa_and_connect(self, mfa_input: str):
"""Set mfa code set products"""
await self._set_mfa_code(mfa_input)
await asyncio.sleep(10)
await self._set_products()
await asyncio.sleep(30)
# await self._set_products()

# server level commands
async def _start_listening(self):
Expand Down Expand Up @@ -256,7 +256,7 @@ async def reboot(self, product_type: ProductType, serial_no: str) -> None:
await self._send_message_get_response(OutgoingMessage(OutgoingMessageType.reboot, serial_no=serial_no))

async def _on_message(self, message: dict) -> None:
message_str = str(message)[0:5000]
message_str = str(message)[0:1000]
if "livestream video data" not in message_str and "livestream audio data" not in message_str:
_LOGGER.debug(f"_on_message - {message_str}")
if message[MessageField.TYPE.value] == IncomingMessageType.result.name:
Expand Down
21 changes: 21 additions & 0 deletions custom_components/eufy_security/eufy_security_api/camera.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ def __init__(self, api, serial_no: str, properties: dict, metadata: dict, comman
self.p2p_started_event = asyncio.Event()
self.rtsp_started_event = asyncio.Event()

self.stream_debug = None

@property
def is_streaming(self) -> bool:
"""Is Camera in Streaming Status"""
Expand Down Expand Up @@ -133,25 +135,36 @@ async def start_livestream(self) -> bool:
"""Process start p2p livestream call"""
self.set_stream_prodiver(StreamProvider.P2P)
self.stream_status = StreamStatus.PREPARING
self.stream_debug = "info - send command to add-on"
await self.api.start_livestream(self.product_type, self.serial_no)
self.stream_debug = "info - command was done, open a local tcp port"
self.p2p_stream_thread = threading.Thread(target=self.p2p_stream_handler.setup, daemon=True)
self.p2p_stream_thread.start()
await wait_for_value(self.p2p_stream_handler.__dict__, "port", None)
self.stream_debug = "info - local tcp was setup, checking for codec"

if self.codec is not None:
self.stream_debug = "info - codec is known, start ffmpeg consuming tcp port and forwarding to rtsp add-on"
await self._start_ffmpeg()
self.stream_debug = "info - ffmpeg was started"

with contextlib.suppress(asyncio.TimeoutError):
self.stream_debug = "info - wait for bytes to arrive from add-on, they will be written to tcp port"
await asyncio.wait_for(self.p2p_started_event.wait(), STREAM_TIMEOUT_SECONDS)

if self.p2p_started_event.is_set() is False:
self.stream_debug = "error - ffmpeg pocess could not connect"
return False

try:
self.stream_debug = "info - check if rtsp url is a valid stream"
await asyncio.wait_for(self._is_stream_url_ready(), STREAM_TIMEOUT_SECONDS)
except asyncio.TimeoutError:
self.stream_debug = "error - rtsp url was not a valid stream"
return False

self.stream_status = StreamStatus.STREAMING
self.stream_debug = "info - streaming"
return True

async def stop_livestream(self):
Expand All @@ -164,22 +177,30 @@ async def start_rtsp_livestream(self):
"""Process start rtsp livestream call"""
self.set_stream_prodiver(StreamProvider.RTSP)
self.stream_status = StreamStatus.PREPARING
self.stream_debug = "info - send command to add-on"
await self.api.start_rtsp_livestream(self.product_type, self.serial_no)

try:
await asyncio.wait_for(self.rtsp_started_event.wait(), 5)
self.stream_debug = "info - command was done"
except asyncio.TimeoutError:
self.stream_debug = "error - command was failed"
return False

try:
self.stream_status = StreamStatus.STREAMING
self.stream_debug = "info - check if rtsp url is a valid stream"
await asyncio.wait_for(self._is_stream_url_ready(), 5)
_LOGGER.debug(f"start_rtsp_livestream - 2 - try success - {self.stream_status}")
return True
except asyncio.TimeoutError:
self.stream_debug = "error - rtsp url was not a valid stream"
_LOGGER.debug("start_rtsp_livestream - 2 - try timeout")
return False

self.stream_debug = "info - streaming"
return True

async def stop_rtsp_livestream(self):
"""Process stop rtsp livestream call"""
await self.api.stop_rtsp_livestream(self.product_type, self.serial_no)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,12 @@ async def connect(self):

async def disconnect(self):
"""Close web socket connection"""
if self.socket is not None:
await self.socket.close()
self.socket = None
if self.task is not None:
self.task.cancel()
self.task = None
if self.socket is not None:
await self.socket.close()
self.socket = None

async def _on_open(self) -> None:
if self.open_callback is not None:
Expand Down
3 changes: 2 additions & 1 deletion custom_components/eufy_security/translations/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
"auth": "Host/Port ist falsch."
},
"abort": {
"single_instance_allowed": "Es ist nur eine einzige Instanz zulässig."
"single_instance_allowed": "Es ist nur eine einzige Instanz zulässig.",
"reauth_successful": "Code is received, reloading the integration in background automatically!"
}
},
"options": {
Expand Down
3 changes: 2 additions & 1 deletion custom_components/eufy_security/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
"auth": "Host/Port is wrong."
},
"abort": {
"single_instance_allowed": "Only a single instance is allowed."
"single_instance_allowed": "Only a single instance is allowed.",
"reauth_successful": "Code is received, reloading the integration in background automatically!"
}
},
"options": {
Expand Down
3 changes: 2 additions & 1 deletion custom_components/eufy_security/translations/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
"auth": "L'hôte/le port est incorrect."
},
"abort": {
"single_instance_allowed": "Une seule instance est autorisée."
"single_instance_allowed": "Une seule instance est autorisée.",
"reauth_successful": "Code is received, reloading the integration in background automatically!"
}
},
"options": {
Expand Down
3 changes: 2 additions & 1 deletion custom_components/eufy_security/translations/it.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
"auth": "L'host/porta è sbagliato."
},
"abort": {
"single_instance_allowed": "È consentita una sola istanza."
"single_instance_allowed": "È consentita una sola istanza.",
"reauth_successful": "Code is received, reloading the integration in background automatically!"
}
},
"options": {
Expand Down
3 changes: 2 additions & 1 deletion custom_components/eufy_security/translations/nl.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
"auth": "Host/Poort klopt niet."
},
"abort": {
"single_instance_allowed": "Slechts één exemplaar is toegestaan."
"single_instance_allowed": "Slechts één exemplaar is toegestaan.",
"reauth_successful": "Code is received, reloading the integration in background automatically!"
}
},
"options": {
Expand Down
3 changes: 2 additions & 1 deletion custom_components/eufy_security/translations/pl.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
"auth": "Host lub Port jest nieprawidłowy."
},
"abort": {
"single_instance_allowed": "Dozwolona jest tylko jedna instancja."
"single_instance_allowed": "Dozwolona jest tylko jedna instancja.",
"reauth_successful": "Code is received, reloading the integration in background automatically!"
}
},
"options": {
Expand Down
3 changes: 2 additions & 1 deletion custom_components/eufy_security/translations/pt-BR.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
"auth": "O host/porta está errado."
},
"abort": {
"single_instance_allowed": "Apenas uma única instância é permitida."
"single_instance_allowed": "Apenas uma única instância é permitida.",
"reauth_successful": "Code is received, reloading the integration in background automatically!"
}
},
"options": {
Expand Down

0 comments on commit 91682b4

Please sign in to comment.