mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-06 19:25:22 +03:00
TP-link tapo C210 PTZ support
DO NOT MERGE!
This commit is contained in:
parent
9a4f970337
commit
c00f19bd33
@ -4,6 +4,13 @@ import logging
|
|||||||
import site
|
import site
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
subprocess.check_call([sys.executable, "-m", "pip", "install", "pytapo"])
|
||||||
|
from pytapo import Tapo
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
|
||||||
import numpy
|
import numpy
|
||||||
from onvif import ONVIFCamera, ONVIFError
|
from onvif import ONVIFCamera, ONVIFError
|
||||||
|
|
||||||
@ -13,6 +20,8 @@ from frigate.util.builtin import find_by_key
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
mover_thread = None
|
||||||
|
stop_mover_thread = False
|
||||||
|
|
||||||
class OnvifCommandEnum(str, Enum):
|
class OnvifCommandEnum(str, Enum):
|
||||||
"""Holds all possible move commands"""
|
"""Holds all possible move commands"""
|
||||||
@ -57,9 +66,13 @@ class OnvifController:
|
|||||||
"active": False,
|
"active": False,
|
||||||
"features": [],
|
"features": [],
|
||||||
"presets": {},
|
"presets": {},
|
||||||
|
"pytapo": Tapo(cam.onvif.host, "admin", "my-tp-link-cloud-password") #either admin/tp-link-cloud-password (used in app to set up device), or actual device username/password as configured in the app.
|
||||||
}
|
}
|
||||||
|
print("pytapo:",self.cams[cam_name]["pytapo"].getBasicInfo())
|
||||||
except ONVIFError as e:
|
except ONVIFError as e:
|
||||||
logger.error(f"Onvif connection to {cam.name} failed: {e}")
|
logger.error(f"Onvif connection to {cam.name} failed: {e}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Unknown error connecting to {cam.name}: {e}")
|
||||||
|
|
||||||
def _init_onvif(self, camera_name: str) -> bool:
|
def _init_onvif(self, camera_name: str) -> bool:
|
||||||
onvif: ONVIFCamera = self.cams[camera_name]["onvif"]
|
onvif: ONVIFCamera = self.cams[camera_name]["onvif"]
|
||||||
@ -98,6 +111,19 @@ class OnvifController:
|
|||||||
),
|
),
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# status request for autotracking and filling ptz-parameters
|
||||||
|
status_request = ptz.create_type("GetStatus")
|
||||||
|
status_request.ProfileToken = profile.token
|
||||||
|
self.cams[camera_name]["status_request"] = status_request
|
||||||
|
try:
|
||||||
|
status = ptz.GetStatus(status_request)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(
|
||||||
|
f"Unable to get status from camera: {camera_name}: {e}"
|
||||||
|
)
|
||||||
|
status = None
|
||||||
|
logger.debug(f"Onvif status config for {camera_name}: {status}")
|
||||||
|
|
||||||
# autoracking relative panning/tilting needs a relative zoom value set to 0
|
# autoracking relative panning/tilting needs a relative zoom value set to 0
|
||||||
# if camera supports relative movement
|
# if camera supports relative movement
|
||||||
@ -122,9 +148,7 @@ class OnvifController:
|
|||||||
move_request = ptz.create_type("RelativeMove")
|
move_request = ptz.create_type("RelativeMove")
|
||||||
move_request.ProfileToken = profile.token
|
move_request.ProfileToken = profile.token
|
||||||
if move_request.Translation is None and fov_space_id is not None:
|
if move_request.Translation is None and fov_space_id is not None:
|
||||||
move_request.Translation = ptz.GetStatus(
|
move_request.Translation = status.Position
|
||||||
{"ProfileToken": profile.token}
|
|
||||||
).Position
|
|
||||||
move_request.Translation.PanTilt.space = ptz_config["Spaces"][
|
move_request.Translation.PanTilt.space = ptz_config["Spaces"][
|
||||||
"RelativePanTiltTranslationSpace"
|
"RelativePanTiltTranslationSpace"
|
||||||
][fov_space_id]["URI"]
|
][fov_space_id]["URI"]
|
||||||
@ -146,7 +170,7 @@ class OnvifController:
|
|||||||
)
|
)
|
||||||
|
|
||||||
if move_request.Speed is None:
|
if move_request.Speed is None:
|
||||||
move_request.Speed = ptz.GetStatus({"ProfileToken": profile.token}).Position
|
move_request.Speed = status.Position if status else None
|
||||||
self.cams[camera_name]["relative_move_request"] = move_request
|
self.cams[camera_name]["relative_move_request"] = move_request
|
||||||
|
|
||||||
# setup absolute moving request for autotracking zooming
|
# setup absolute moving request for autotracking zooming
|
||||||
@ -154,13 +178,6 @@ class OnvifController:
|
|||||||
move_request.ProfileToken = profile.token
|
move_request.ProfileToken = profile.token
|
||||||
self.cams[camera_name]["absolute_move_request"] = move_request
|
self.cams[camera_name]["absolute_move_request"] = move_request
|
||||||
|
|
||||||
# status request for autotracking
|
|
||||||
status_request = ptz.create_type("GetStatus")
|
|
||||||
status_request.ProfileToken = profile.token
|
|
||||||
self.cams[camera_name]["status_request"] = status_request
|
|
||||||
status = ptz.GetStatus(status_request)
|
|
||||||
logger.debug(f"Onvif status config for {camera_name}: {status}")
|
|
||||||
|
|
||||||
# setup existing presets
|
# setup existing presets
|
||||||
try:
|
try:
|
||||||
presets: list[dict] = ptz.GetPresets({"ProfileToken": profile.token})
|
presets: list[dict] = ptz.GetPresets({"ProfileToken": profile.token})
|
||||||
@ -215,18 +232,29 @@ class OnvifController:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def _stop(self, camera_name: str) -> None:
|
def _stop(self, camera_name: str) -> None:
|
||||||
|
logger.info(f"Stopping {camera_name}")
|
||||||
onvif: ONVIFCamera = self.cams[camera_name]["onvif"]
|
onvif: ONVIFCamera = self.cams[camera_name]["onvif"]
|
||||||
move_request = self.cams[camera_name]["move_request"]
|
move_request = self.cams[camera_name]["move_request"]
|
||||||
onvif.get_service("ptz").Stop(
|
# onvif.get_service("ptz").Stop(
|
||||||
{
|
# {
|
||||||
"ProfileToken": move_request.ProfileToken,
|
# "ProfileToken": move_request.ProfileToken,
|
||||||
"PanTilt": True,
|
# "PanTilt": True,
|
||||||
"Zoom": True,
|
# "Zoom": True,
|
||||||
}
|
# }
|
||||||
)
|
# )
|
||||||
self.cams[camera_name]["active"] = False
|
self.cams[camera_name]["active"] = False
|
||||||
|
# Stop the mover thread
|
||||||
|
# global mover_thread, stop_mover_thread
|
||||||
|
# if mover_thread is not None:
|
||||||
|
# stop_mover_thread = True
|
||||||
|
# mover_thread.join()
|
||||||
|
# mover_thread = None
|
||||||
|
|
||||||
|
pytapo = self.cams[camera_name]["pytapo"]
|
||||||
|
pytapo.performRequest({"method": "do", "motor": {"stop":"null"}})
|
||||||
|
|
||||||
def _move(self, camera_name: str, command: OnvifCommandEnum) -> None:
|
def _move(self, camera_name: str, command: OnvifCommandEnum) -> None:
|
||||||
|
logger.info(f"Moving {camera_name} {command}")
|
||||||
if self.cams[camera_name]["active"]:
|
if self.cams[camera_name]["active"]:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
f"{camera_name} is already performing an action, stopping..."
|
f"{camera_name} is already performing an action, stopping..."
|
||||||
@ -234,6 +262,54 @@ class OnvifController:
|
|||||||
self._stop(camera_name)
|
self._stop(camera_name)
|
||||||
|
|
||||||
self.cams[camera_name]["active"] = True
|
self.cams[camera_name]["active"] = True
|
||||||
|
|
||||||
|
# Start the mover thread.
|
||||||
|
def mover_thread_func():
|
||||||
|
global stop_mover_thread
|
||||||
|
# call the pytapo library once per second. Functions are: moveMotorClockWise, moveMotorCounterClockWise, moveMotorVertical (for up), moveMotorHorizontal (for down).
|
||||||
|
try:
|
||||||
|
while not stop_mover_thread:
|
||||||
|
pytapo = self.cams[camera_name]["pytapo"]
|
||||||
|
if command == OnvifCommandEnum.move_left:
|
||||||
|
res = pytapo.moveMotorCounterClockWise()
|
||||||
|
elif command == OnvifCommandEnum.move_right:
|
||||||
|
res = pytapo.moveMotorClockWise()
|
||||||
|
elif command == OnvifCommandEnum.move_up:
|
||||||
|
res = pytapo.moveMotorVertical()
|
||||||
|
elif command == OnvifCommandEnum.move_down:
|
||||||
|
res = pytapo.moveMotorHorizontal()
|
||||||
|
logger.info(f"Pytapo move direction: {command} result: {res}")
|
||||||
|
time.sleep(1)
|
||||||
|
pytapo.performRequest({"method": "do", "motor": {"stop":"null"}})
|
||||||
|
except Exception as e:
|
||||||
|
# Probably at max pan/tilt
|
||||||
|
logger.info(f"Pytapo move direction: {command} result: {e}")
|
||||||
|
logger.info(f"Pytapo mover thread for {camera_name} stopped.")
|
||||||
|
|
||||||
|
logger.info(f"Starting mover thread for {camera_name} in direction {command}")
|
||||||
|
|
||||||
|
pytapo = self.cams[camera_name]["pytapo"]
|
||||||
|
try:
|
||||||
|
if command == OnvifCommandEnum.move_left:
|
||||||
|
res = pytapo.moveMotorCounterClockWise()
|
||||||
|
elif command == OnvifCommandEnum.move_right:
|
||||||
|
res = pytapo.moveMotorClockWise()
|
||||||
|
elif command == OnvifCommandEnum.move_up:
|
||||||
|
res = pytapo.moveMotorVertical()
|
||||||
|
elif command == OnvifCommandEnum.move_down:
|
||||||
|
res = pytapo.moveMotorHorizontal()
|
||||||
|
logger.info(f"Pytapo move direction: {command} result: {res}")
|
||||||
|
except Exception as e:
|
||||||
|
# Probably at max pan/tilt
|
||||||
|
logger.info(f"Pytapo move direction: {command} result: {e}")
|
||||||
|
return
|
||||||
|
|
||||||
|
global mover_thread, stop_mover_thread
|
||||||
|
mover_thread = threading.Thread(target=mover_thread_func)
|
||||||
|
stop_mover_thread = False
|
||||||
|
mover_thread.start()
|
||||||
|
return
|
||||||
|
|
||||||
onvif: ONVIFCamera = self.cams[camera_name]["onvif"]
|
onvif: ONVIFCamera = self.cams[camera_name]["onvif"]
|
||||||
move_request = self.cams[camera_name]["move_request"]
|
move_request = self.cams[camera_name]["move_request"]
|
||||||
|
|
||||||
@ -256,7 +332,7 @@ class OnvifController:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onvif.get_service("ptz").ContinuousMove(move_request)
|
reply = onvif.get_service("ptz").ContinuousMove(move_request)
|
||||||
|
|
||||||
def _move_relative(self, camera_name: str, pan, tilt, zoom, speed) -> None:
|
def _move_relative(self, camera_name: str, pan, tilt, zoom, speed) -> None:
|
||||||
if "pt-r-fov" not in self.cams[camera_name]["features"]:
|
if "pt-r-fov" not in self.cams[camera_name]["features"]:
|
||||||
@ -486,7 +562,10 @@ class OnvifController:
|
|||||||
|
|
||||||
onvif: ONVIFCamera = self.cams[camera_name]["onvif"]
|
onvif: ONVIFCamera = self.cams[camera_name]["onvif"]
|
||||||
status_request = self.cams[camera_name]["status_request"]
|
status_request = self.cams[camera_name]["status_request"]
|
||||||
status = onvif.get_service("ptz").GetStatus(status_request)
|
try:
|
||||||
|
status = onvif.get_service("ptz").GetStatus(status_request)
|
||||||
|
except Exception as e:
|
||||||
|
pass # We're unsupported, that'll be reported in the next check.
|
||||||
|
|
||||||
# there doesn't seem to be an onvif standard with this optional parameter
|
# there doesn't seem to be an onvif standard with this optional parameter
|
||||||
# some cameras can report MoveStatus with or without PanTilt or Zoom attributes
|
# some cameras can report MoveStatus with or without PanTilt or Zoom attributes
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user