Skip to content

Commit

Permalink
Add watch_timeout
Browse files Browse the repository at this point in the history
  • Loading branch information
kaffetorsk committed Oct 26, 2024
1 parent b192af4 commit 0a19ee5
Show file tree
Hide file tree
Showing 3 changed files with 30 additions and 11 deletions.
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ This allow arlo cameras to be used in the NVR of your choosing. (e.g. [Frigate](
The streams will provide an "idle" picture when the camera is not actively streaming.
Motion will trigger an active stream, replacing the "idle" picture with the actual camera stream.

External streams (typically live view in arlo app) will trigger a watch mode. Due to limitations in pyaarlo this kind of stream will have to be periodically taken down and restablished to see if the stream is still beeing viewed elsewhere.


**Note:** For ideal operation, the arlo cameras should not be set to record on motion in the arlo app. This slows down stream setup significantly, leading to loss of valuable frames at the start of the event. A common approach is to choose push notification only, then disable notifications for the arlo app.

## Usage
Expand All @@ -25,7 +28,7 @@ FFMPEG_OUT: out-string for ffmpeg. (e.g. -c:v copy -c:a copy -f flv rtmp://127.0
```
### Optional
```
MOTION_TIMEOUT: How long to provide active stream after motion (in seconds) (default: 60)
MOTION_TIMEOUT: How long to provide active stream after motion (in seconds). Also used as refresh interval for external streams (default: 60)
MQTT_BROKER: If specified, will be used to publish snapshots and status, and control the camera (see MQTT).
MQTT_PORT: broker port (default: 1883)
MQTT_USER: broker username. Not setting this will result in an anonymous connection (default: None)
Expand All @@ -36,6 +39,7 @@ MQTT_TOPIC_CONTROL: control will be read on this topic. (default: arlo/control/{
MQTT_TOPIC_MOTION: motion events will be published to this topic. (default: arlo/motion/{name})
MQTT_RECONNECT_INTERVAL: Wait this amount before retrying connection to broker (in seconds) (default: 5)
STATUS_INTERVAL: Time between published status messages (in seconds) (default: 120)
WATCH_REFRESH_TIME: Downtime to check if remote stream is still active (in seconds) (default: 2)
DEBUG: True enables full debug (default: False)
PYAARLO_BACKEND: Pyaarlo backend. (default determined by pyaarlo). Options are `mqtt` and `sse`.
PYAARLO_REFRESH_DEVICES: Pyaarlo backend device refresh interval (in hours) (default: never)
Expand Down
32 changes: 23 additions & 9 deletions camera.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,18 @@ class Camera(Device):
# Possible states
STATES = [
'idle',
'streaming', # arlo-streamer initiated the stream
'watching', # someone else initiated the stream
'streaming', # arlo-streamer initiated the stream
'watching', # someone else initiated the stream
]

def __init__(self, arlo_camera, ffmpeg_out,
motion_timeout, status_interval, last_image_idle,
default_resolution):
default_resolution, watch_refresh_time):
super().__init__(arlo_camera, status_interval)
self.ffmpeg_out = shlex.split(ffmpeg_out.format(name=self.name))
self.timeout = motion_timeout
self.last_image_idle = last_image_idle
self.watch_refresh_time = watch_refresh_time
self._timeout_task = None
self.motion = False
self._state = None
Expand Down Expand Up @@ -123,7 +124,7 @@ async def on_arlo_state(self, state):

case 'watching':
await self.set_state('idle')

elif state == 'userStreamActive' and self.get_state() != 'streaming':
await self.set_state('watching')

Expand All @@ -147,9 +148,12 @@ async def _on_state_change(self, new_state):

case 'streaming':
await self._start_stream()

case 'watching':
await self._start_stream()
await self._start_stream(self._arlo.get_stream_url)
self._timeout_task = asyncio.create_task(
self._watch_timeout()
)

async def _start_proxy_stream(self):
"""
Expand Down Expand Up @@ -235,13 +239,16 @@ async def _start_idle_stream(self):
)
await asyncio.sleep(3)

async def _start_stream(self):
async def _start_stream(self, stream_cmd=None):
"""
Request stream, grab it, kill idle stream and start new ffmpeg instance
writing to proxy.
"""
stream = await self.event_loop.run_in_executor(None,
self._arlo.get_stream)
if stream_cmd is None:
stream_cmd = self._arlo.get_stream

stream = await self.event_loop.run_in_executor(None, stream_cmd)

if stream:
self.stop_stream()

Expand Down Expand Up @@ -279,6 +286,13 @@ async def _stream_timeout(self):
await asyncio.sleep(self.timeout)
await self.set_state('idle')

async def _watch_timeout(self):
await asyncio.sleep(self.timeout)
await self.set_state('idle')
await asyncio.sleep(self.watch_refresh_time)
if self._arlo.is_streaming:
await self.set_state('watching')

def stop_stream(self):
"""
Stop live or idle stream (not proxy stream)
Expand Down
3 changes: 2 additions & 1 deletion main.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
MOTION_TIMEOUT = config('MOTION_TIMEOUT', default=60, cast=int)
STATUS_INTERVAL = config('STATUS_INTERVAL', default=120, cast=int)
LAST_IMAGE_IDLE = config('LAST_IMAGE_IDLE', default=False, cast=bool)
WATCH_REFRESH_TIME = config('WATCH_REFRESH_TIME', default=2, cast=int)
DEBUG = config('DEBUG', default=False, cast=bool)
PYAARLO_BACKEND = config('PYAARLO_BACKEND', default=None)
PYAARLO_REFRESH_DEVICES = config('PYAARLO_REFRESH_DEVICES', default=0, cast=int)
Expand Down Expand Up @@ -73,7 +74,7 @@ async def main():
# Initialize cameras
cameras = [Camera(
c, FFMPEG_OUT, MOTION_TIMEOUT, STATUS_INTERVAL, LAST_IMAGE_IDLE,
DEFAULT_RESOLUTION
DEFAULT_RESOLUTION, WATCH_REFRESH_TIME
) for c in arlo.cameras]

# Start both
Expand Down

0 comments on commit 0a19ee5

Please sign in to comment.