Skip to content

API get_camera_rtsps_streams fails for camera not on primary NVR in stacked scenario #685

@gazoodle

Description

@gazoodle

Describe the bug

I have a stacked UNVR setup. The API camera.get_rtsps_streams() fails with uiprotect.exceptions.BadRequest when called against a camera that is hosted on the secondary UNVR.

To Reproduce

Set up a stacked UNVR environment.
Ensure cameras are on both devices.

Run this code.

import asyncio
from uiprotect import ProtectApiClient

host = "<IPADDRESS>"						# Primary stacked UNVR
port = 443									# SSL
user = "<USER>"								# User
pwd = "<PASSWORD>"				# Password
key = "<API Key>"	# API Key

async def do_it():
	protect = ProtectApiClient(host, port, user, pwd, api_key=key, verify_ssl=False)

	await protect.update()

	host1 = None
	host2 = None
	interested = []

	# First time through, print al the cameras out and where they are hosted
	for camera in protect.bootstrap.cameras.values():
		if host1 is None:
			host1 = camera.connection_host
			interested.append(camera.id)
		elif host2 is None:
			if camera.connection_host is not host1:
				host2 = camera.connection_host
				interested.append(camera.id)

		print(f"{camera.name} : {camera.id} [{camera.host} on {camera.connection_host}]")

	print("*** Now we have a couple of cameras to be interested in, lets get the RTSPS streams ...")
	for camera in protect.bootstrap.cameras.values():
		if camera.id in interested:
			print(f"{camera.name} : {camera.id} [{camera.host} on {camera.connection_host}]")
			for stm in await camera.get_rtsps_streams():
				print(f"   {stm}")

if __name__ == "__main__":
	asyncio.run(do_it())

This will print out all the cameras and which host they are on. It will also pick the first camera on each host and then attempt to get the RTSPS streams. It will work fine on the first camera (which in my case is the same host as the main UI), but will fail on the second host with an exception.

In my case, the redacted output is:

Lane : a3f9c7e2b1d48a0c9f12de34 [192.168.103.44 on 192.168.23.221]
Workshop Cam : b7d1e4c9a02f88de671c53ab [192.168.77.206 on 192.168.23.221]
Back Patio : 9c4a12de88f0b7e1d653aa90 [192.168.189.139 on 192.168.23.221]
Front Doorbell : e1d0c6b487a9f2038c45de91 [192.168.131.9 on 192.168.23.221]
Drive & Gate : 7f91b2e4d8c03a5e619ad044 [192.168.149.24 on 192.168.23.221]
Garage Wide : d204fbe198a73c5e6a90d211 [192.168.134.108 on 192.168.23.221]
Nola Cam : 81c7f2e04db93a56eaa19c88 [192.168.168.112 on 192.168.23.221]
Hedgehog Cam : 5e3f9a1c0db842e7a661c9d2 [192.168.210.85 on 192.168.23.221]
Study Cam : f9c2d3b1a48e77c05d10e9aa [192.168.147.185 on 192.168.23.221]
Front Porch : 0a9d5c8e71f3b2e44d6a19cf [192.168.122.161 on 192.168.23.221]
Outside Gates : c71e3a20d9bf458aa6e1209d [192.168.195.32 on 192.168.23.221]
Hall Cam : 2b8f09d41c5ea773a90de621 [192.168.27.7 on 192.168.23.221]
Gate Doorbell : a0e98b6c42f7d3a11e5c90db [192.168.131.15 on 192.168.23.221]
By Family Room : 6a4d90b2ce1f3885e7a921dd [192.168.88.167 on 192.168.23.221]
Front Drive : 8d1b2e6f0c4a9e735aa19f02 [192.168.16.102 on 192.168.15.239]
Sentry : 3e9c02b71fa8d45a6c11e8b0 [192.168.153.103 on 192.168.15.239]
Bootroom : f1b93e27a0d845c69e4a110b [192.168.181.83 on 192.168.15.239]
Verandah : 4c7a0de218b9f35e61d02aac [192.168.122.75 on 192.168.15.239]
Garage Bays : 90e6bfa21d4837c5a119de02 [192.168.2.125 on 192.168.15.239]
Apple Tree : 1e8c6b5d9a2037f4eaa120bc [192.168.61.110 on 192.168.15.239]
Side Bit : a4c7e92b1f6d03859e11a0d9 [192.168.61.4 on 192.168.15.239]
Pool : 6b0d8a5e29c471f31aa9e204 [192.168.184.205 on 192.168.15.239]

*** Now we have a couple of cameras to be interested in, lets get the RTSPS streams …

Lane : a3f9c7e2b1d48a0c9f12de34 [192.168.103.44 on 192.168.23.221]
(‘high’, ‘rtsps://192.168.23.221:7441/Fa92cE1bD0e4A7c9?enableSrtp’)
(‘medium’, None)
(‘low’, ‘rtsps://192.168.23.221:7441/9dC0Abe27F14cE8a?enableSrtp’)
(‘package’, None)

Front Drive : 8d1b2e6f0c4a9e735aa19f02 [192.168.16.102 on 192.168.15.239]
Traceback (most recent call last):
  File "/media/psf/Home/Documents/esphome/protect.py", line 39, in <module>
    asyncio.run(do_it())
  File "/usr/lib/python3.12/asyncio/runners.py", line 194, in run
    return runner.run(main)
           ^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/asyncio/runners.py", line 118, in run
    return self._loop.run_until_complete(task)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/asyncio/base_events.py", line 687, in run_until_complete
    return future.result()
           ^^^^^^^^^^^^^^^
  File "/media/psf/Home/Documents/esphome/protect.py", line 35, in do_it
    for stm in await camera.get_rtsps_streams():
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/media/psf/Home/Documents/esphome/esphome-venv/lib/python3.12/site-packages/uiprotect/data/devices.py", line 2182, in get_rtsps_streams
    return await self._api.get_camera_rtsps_streams(self.id)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/media/psf/Home/Documents/esphome/esphome-venv/lib/python3.12/site-packages/uiprotect/api.py", line 2118, in get_camera_rtsps_streams
    response = await self.api_request_raw(
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/media/psf/Home/Documents/esphome/esphome-venv/lib/python3.12/site-packages/uiprotect/api.py", line 571, in api_request_raw
    await self._raise_for_status(response, raise_exception)
  File "/media/psf/Home/Documents/esphome/esphome-venv/lib/python3.12/site-packages/uiprotect/api.py", line 610, in _raise_for_status
    raise BadRequest(msg % (url, status, reason))
uiprotect.exceptions.BadRequest: Request failed: https://192.168.23.221/proxy/protect/integration/v1/cameras/8d1b2e6f0c4a9e735aa19f02/rtsps-stream - Status: 404 - Reason: Entity 'camera' not found

Additional context

I am trying to get my stacked UNVR cameras working in Home Assistant. I raised an issue there (home-assistant/core#154433) and some work was done on it but it wasn't quite fixed so I thought I'd try going a bit lower with the protect api, but I can't quite get there yet, I suspect it is related.

Version

7.33.2

Platform

macOS 15.6.1

Code of Conduct

  • I agree to follow this project's Code of Conduct.

No Duplicate

  • I have checked existing issues to avoid duplicates.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions