Skip to content

Commit

Permalink
feat: onboard to go2rtc
Browse files Browse the repository at this point in the history
BREAKING CHANGE: This change relies on go2rtc configuration. Please wait until a new version is released and do not get from master
  • Loading branch information
fuatakgun committed Nov 23, 2023
1 parent 088ac61 commit c0d5480
Show file tree
Hide file tree
Showing 18 changed files with 85 additions and 201 deletions.
6 changes: 3 additions & 3 deletions custom_components/eufy_security/alarm_control_panel.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,8 +175,8 @@ def state(self):
return CurrentModeToStateValue.TRIGGERED.value

current_mode = get_child_value(self.product.properties, self.metadata.name, False)
if current_mode is False:
_LOGGER.debug(f"{self.product.name} current mode is missing, fallback to guardmode {self.guard_mode}")
#if current_mode is False:
#_LOGGER.debug(f"{self.product.name} current mode is missing, fallback to guardmode {self.guard_mode}")
current_mode = get_child_value(self.product.properties, self.metadata.name, CurrentModeToState(self.guard_mode))

if current_mode in CUSTOM_CODES:
Expand All @@ -190,6 +190,6 @@ def state(self):
try:
state = CurrentModeToStateValue[CurrentModeToState(current_mode).name].value
except KeyError:
_LOGGER.debug(f"{self.product.name} current mode is missing, fallback to Unknown with guard mode {self.guard_mode}")
#_LOGGER.debug(f"{self.product.name} current mode is missing, fallback to Unknown with guard mode {self.guard_mode}")
state = CurrentModeToStateValue.NONE.value
return state
3 changes: 0 additions & 3 deletions custom_components/eufy_security/camera.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,8 @@ def __init__(self, coordinator: EufySecurityDataUpdateCoordinator, metadata: Met

# ffmpeg entities
self.ffmpeg = self.coordinator.hass.data[DATA_FFMPEG]
self.product.set_ffmpeg(CameraMjpeg(self.ffmpeg.binary), ImageFrame(self.ffmpeg.binary))

async def stream_source(self) -> str:
#for line in traceback.format_stack():
# _LOGGER.debug(f"stream_source - {line.strip()}")
if self.is_streaming is False:
return None
return self.product.stream_url
Expand Down
13 changes: 10 additions & 3 deletions custom_components/eufy_security/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,13 @@ class PropertyToEntityDescription(Enum):
snoozeStartTime = EntityDescription(id=auto(), category=EntityCategory.DIAGNOSTIC)
snooze = EntityDescription(id=auto(), icon="mdi:alarm-snooze")
snoozeTime = EntityDescription(id=auto(), icon="mdi:alarm-snooze")
doorSensor1BatteryLevel = EntityDescription(id=auto(), state_class=SensorStateClass.MEASUREMENT, category=EntityCategory.DIAGNOSTIC)
doorSensor2BatteryLevel = EntityDescription(id=auto(), state_class=SensorStateClass.MEASUREMENT, category=EntityCategory.DIAGNOSTIC)


stream_provider = EntityDescription(id=auto(), category=EntityCategory.DIAGNOSTIC)
stream_url = EntityDescription(id=auto(), category=EntityCategory.DIAGNOSTIC)
stream_status = EntityDescription(id=auto(), category=EntityCategory.DIAGNOSTIC)
codec = EntityDescription(id=auto(), category=EntityCategory.DIAGNOSTIC)
video_queue_size = EntityDescription(id=auto(), category=EntityCategory.DIAGNOSTIC)

# device binary sensor
Expand Down Expand Up @@ -112,6 +114,9 @@ class PropertyToEntityDescription(Enum):
snoozeHomebase = EntityDescription(id=auto(), category=EntityCategory.DIAGNOSTIC)
snoozeMotion = EntityDescription(id=auto(), category=EntityCategory.DIAGNOSTIC)
snoozeChime = EntityDescription(id=auto(), category=EntityCategory.DIAGNOSTIC)
doorSensor1LowBattery = EntityDescription(id=auto(), device_class=BinarySensorDeviceClass.BATTERY, category=EntityCategory.DIAGNOSTIC)
doorSensor2LowBattery = EntityDescription(id=auto(), device_class=BinarySensorDeviceClass.BATTERY, category=EntityCategory.DIAGNOSTIC)
connected = EntityDescription(id=auto(), category=EntityCategory.DIAGNOSTIC)

# device switch
enabled = EntityDescription(id=auto())
Expand All @@ -135,6 +140,8 @@ class PropertyToEntityDescription(Enum):
motionDetectionTypeHuman = EntityDescription(id=auto(), category=EntityCategory.CONFIG)
motionDetectionTypeHumanRecognition = EntityDescription(id=auto(), category=EntityCategory.CONFIG)
motionDetectionTypeAllOtherMotions = EntityDescription(id=auto(), category=EntityCategory.CONFIG)
door1Open = EntityDescription(id=auto())
door2Open = EntityDescription(id=auto())

# device select
powerSource = EntityDescription(id=auto(), icon="mdi:power-plug", category=EntityCategory.DIAGNOSTIC)
Expand All @@ -153,8 +160,8 @@ class PropertyToEntityDescription(Enum):
lightSettingsScheduleDynamicLighting = EntityDescription(id=auto(), category=EntityCategory.CONFIG)

# station sensor
currentMode = EntityDescription(id=auto(), icon="mdi:security", category=EntityCategory.DIAGNOSTIC)
guardMode = EntityDescription(id=auto(), icon="mdi:security", category=EntityCategory.DIAGNOSTIC)
currentMode = EntityDescription(id=auto(), icon="mdi:security")
guardMode = EntityDescription(id=auto(), icon="mdi:security")

# station select
promptVolume = EntityDescription(id=auto(), icon="mdi:volume-medium", category=EntityCategory.CONFIG)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,8 @@ async def _get_products(self, product_type: ProductType, products: list) -> dict
else:
product = Device(self, serial_no, properties, metadata, commands)
else:
properties[MessageField.CONNECTED.value] = await self._get_is_connected(product_type, serial_no)
metadata[MessageField.CONNECTED.value] = {'key': MessageField.CONNECTED.value,'name': MessageField.CONNECTED.value,'label': 'Connected','readable': True,'writeable': False,'type': 'boolean'}
product = Station(self, serial_no, properties, metadata, commands)

response[serial_no] = product
Expand Down Expand Up @@ -213,6 +215,10 @@ async def _get_is_rtsp_streaming(self, product_type: ProductType, serial_no: str
result = await self._send_message_get_response(OutgoingMessage(OutgoingMessageType.is_rtsp_livestreaming, serial_no=serial_no))
return result[MessageField.LIVE_STREAMING.value]

async def _get_is_connected(self, product_type: ProductType, serial_no: str) -> bool:
result = await self._send_message_get_response(OutgoingMessage(OutgoingMessageType.is_connected, serial_no=serial_no))
return result[MessageField.CONNECTED.value]

async def start_livestream(self, product_type: ProductType, serial_no: str) -> None:
"""Process start p2p livestream call"""
await self._send_message_get_response(OutgoingMessage(OutgoingMessageType.start_livestream, serial_no=serial_no))
Expand Down Expand Up @@ -256,7 +262,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:15000]
if "livestream video data" not in message_str and "livestream audio data" not in message_str:
_LOGGER.debug(f"_on_message - {message_str}")
else:
Expand Down
54 changes: 8 additions & 46 deletions custom_components/eufy_security/eufy_security_api/camera.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import datetime
import traceback

from .const import MessageField, STREAM_TIMEOUT_SECONDS, STREAM_SLEEP_SECONDS
from .const import MessageField, STREAM_TIMEOUT_SECONDS, STREAM_SLEEP_SECONDS, GO2RTC_RTSP_PORT
from .event import Event
from .exceptions import CameraRTSPStreamNotEnabled, CameraRTSPStreamNotSupported
from .p2p_streamer import P2PStreamer
Expand Down Expand Up @@ -51,13 +51,10 @@ def __init__(self, api, serial_no: str, properties: dict, metadata: dict, comman
self.stream_status: StreamStatus = StreamStatus.IDLE
self.stream_provider: StreamProvider = None
self.stream_url: str = None
self.codec: str = None

self.video_queue = asyncio.Queue()
self.config = config
self.voices = voices
self.ffmpeg = None
self.imagempeg = None
self.image_last_updated = None

self.p2p_streamer = P2PStreamer(self)
Expand All @@ -77,11 +74,6 @@ def is_streaming(self) -> bool:
"""Is Camera in Streaming Status"""
return self.stream_status == StreamStatus.STREAMING

def set_ffmpeg(self, ffmpeg, imagempeg):
"""set ffmpeg binary"""
self.ffmpeg = ffmpeg
self.imagempeg = imagempeg

async def _handle_livestream_started(self, event: Event):
# automatically find this function for respective event
_LOGGER.debug(f"_handle_livestream_started - {event}")
Expand All @@ -105,38 +97,8 @@ async def _handle_rtsp_livestream_stopped(self, event: Event):

async def _handle_livestream_video_data_received(self, event: Event):
# automatically find this function for respective event
if self.codec is None:
self.codec = event.data["metadata"]["videoCodec"].lower()

await self.video_queue.put(event.data["buffer"]["data"])

async def _start_p2p_streamer(self):
self.stream_debug = "info - wait for codec value"
await wait_for_value(self.__dict__, "codec", None)
await self.p2p_streamer.start()

async def _is_stream_url_ready(self) -> bool:
_LOGGER.debug("_is_stream_url_ready - 1")
with contextlib.suppress(Exception):
while True:
if await self.imagempeg.get_image(self.stream_url, timeout=1) is not None:
return True
return False

async def _check_stream_url(self) -> bool:
try:
self.stream_debug = "info - check if stream url is a valid stream"
_LOGGER.debug(f"_check_stream_url - {self.stream_debug}")
await asyncio.wait_for(self._is_stream_url_ready(), STREAM_TIMEOUT_SECONDS)
self.stream_status = StreamStatus.STREAMING
self.stream_debug = "info - streaming"
_LOGGER.debug(f"_check_stream_url - {self.stream_debug}")
return True
except asyncio.TimeoutError:
self.stream_debug = "error - rtsp url was not a valid stream"
_LOGGER.debug(f"_check_stream_url - {self.stream_debug}")
return False

async def _initiate_start_stream(self, stream_type) -> bool:
self.set_stream_prodiver(stream_type)
self.stream_status = StreamStatus.PREPARING
Expand Down Expand Up @@ -169,11 +131,9 @@ async def start_livestream(self) -> bool:
if await self._initiate_start_stream(StreamProvider.P2P) is False:
return False

self.stream_debug = "info - start ffmpeg"
_LOGGER.debug(f"start_livestream - {self.stream_debug}")
await self._start_p2p_streamer()

return await self._check_stream_url()
await self.p2p_streamer.start()
self.stream_status = StreamStatus.STREAMING
return True

async def check_and_stop_livestream(self):
if self.stream_status != StreamStatus.IDLE:
Expand All @@ -188,7 +148,9 @@ async def start_rtsp_livestream(self) -> bool:
if await self._initiate_start_stream(StreamProvider.RTSP) is False:
return False

return await self._check_stream_url()
self.stream_status = StreamStatus.STREAMING
return True


async def stop_rtsp_livestream(self):
"""Process stop rtsp livestream call"""
Expand Down Expand Up @@ -267,7 +229,7 @@ def set_stream_prodiver(self, stream_provider: StreamProvider) -> None:
elif self.stream_provider == StreamProvider.P2P:
url = url.replace("{serial_no}", str(self.serial_no))
url = url.replace("{server_address}", str(self.config.rtsp_server_address))
url = url.replace("{server_port}", str(self.config.rtsp_server_port))
url = url.replace("{server_port}", str(GO2RTC_RTSP_PORT))
self.stream_url = url
_LOGGER.debug(f"url - {self.stream_provider} - {self.stream_url}")

Expand Down
5 changes: 5 additions & 0 deletions custom_components/eufy_security/eufy_security_api/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@

STREAM_TIMEOUT_SECONDS = 15
STREAM_SLEEP_SECONDS = 0.25
GO2RTC_RTSP_PORT = 8554
GO2RTC_API_PORT = 1984


class MessageField(Enum):
Expand Down Expand Up @@ -103,6 +105,9 @@ class EventNameToHandler(Enum):
livestream_video_data_received = "livestream video data"
livestream_audio_data_received = "livestream audio data"
pin_verified = "pin verified"
connected = "connected"
disconnected = "disconnected"
connection_error = "connection error"


class ProductType(Enum):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ class OutgoingMessageType(Enum):
# station level commands
chime = {MessageField.DUMMY: auto(), MessageField.DOMAIN: EventSourceType.station, MessageField.RINGTONE: None}
reboot = {MessageField.DUMMY: auto(), MessageField.DOMAIN: EventSourceType.station}
is_connected = {MessageField.DUMMY: auto(), MessageField.DOMAIN: EventSourceType.station}


class OutgoingMessage:
Expand Down
Loading

0 comments on commit c0d5480

Please sign in to comment.