mirror of
https://github.com/home-assistant/core.git
synced 2025-12-21 03:20:01 +00:00
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Joostlek <joostlek@outlook.com>
194 lines
5.9 KiB
Python
194 lines
5.9 KiB
Python
"""Support for Vivotek IP Cameras."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from typing import TYPE_CHECKING
|
|
|
|
from libpyvivotek.vivotek import VivotekCamera
|
|
import voluptuous as vol
|
|
|
|
from homeassistant.components.camera import (
|
|
PLATFORM_SCHEMA as CAMERA_PLATFORM_SCHEMA,
|
|
Camera,
|
|
CameraEntityFeature,
|
|
)
|
|
from homeassistant.config_entries import SOURCE_IMPORT
|
|
from homeassistant.const import (
|
|
CONF_AUTHENTICATION,
|
|
CONF_IP_ADDRESS,
|
|
CONF_NAME,
|
|
CONF_PASSWORD,
|
|
CONF_SSL,
|
|
CONF_USERNAME,
|
|
CONF_VERIFY_SSL,
|
|
HTTP_BASIC_AUTHENTICATION,
|
|
HTTP_DIGEST_AUTHENTICATION,
|
|
)
|
|
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant
|
|
from homeassistant.data_entry_flow import FlowResultType
|
|
from homeassistant.helpers import config_validation as cv, issue_registry as ir
|
|
from homeassistant.helpers.entity_platform import (
|
|
AddConfigEntryEntitiesCallback,
|
|
AddEntitiesCallback,
|
|
)
|
|
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
|
|
|
from . import VivotekConfigEntry
|
|
from .const import (
|
|
CONF_FRAMERATE,
|
|
CONF_SECURITY_LEVEL,
|
|
CONF_STREAM_PATH,
|
|
DOMAIN,
|
|
INTEGRATION_TITLE,
|
|
)
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
DEFAULT_CAMERA_BRAND = "VIVOTEK"
|
|
DEFAULT_NAME = "VIVOTEK Camera"
|
|
DEFAULT_EVENT_0_KEY = "event_i0_enable"
|
|
DEFAULT_FRAMERATE = 2
|
|
DEFAULT_SECURITY_LEVEL = "admin"
|
|
DEFAULT_STREAM_SOURCE = "live.sdp"
|
|
|
|
PLATFORM_SCHEMA = CAMERA_PLATFORM_SCHEMA.extend(
|
|
{
|
|
vol.Required(CONF_IP_ADDRESS): cv.string,
|
|
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
|
vol.Required(CONF_PASSWORD): cv.string,
|
|
vol.Required(CONF_USERNAME): cv.string,
|
|
vol.Optional(CONF_AUTHENTICATION, default=HTTP_BASIC_AUTHENTICATION): vol.In(
|
|
[HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION]
|
|
),
|
|
vol.Optional(CONF_SSL, default=False): cv.boolean,
|
|
vol.Optional(CONF_VERIFY_SSL, default=True): cv.boolean,
|
|
vol.Optional(CONF_FRAMERATE, default=DEFAULT_FRAMERATE): cv.positive_int,
|
|
vol.Optional(CONF_SECURITY_LEVEL, default=DEFAULT_SECURITY_LEVEL): cv.string,
|
|
vol.Optional(CONF_STREAM_PATH, default=DEFAULT_STREAM_SOURCE): cv.string,
|
|
}
|
|
)
|
|
|
|
|
|
async def async_setup_platform(
|
|
hass: HomeAssistant,
|
|
config: ConfigType,
|
|
async_add_entities: AddEntitiesCallback,
|
|
discovery_info: DiscoveryInfoType | None = None,
|
|
) -> None:
|
|
"""Set up the Vivotek camera platform."""
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN,
|
|
context={"source": SOURCE_IMPORT},
|
|
data=config,
|
|
)
|
|
if (
|
|
result.get("type") is FlowResultType.ABORT
|
|
and result.get("reason") != "already_configured"
|
|
):
|
|
ir.async_create_issue(
|
|
hass,
|
|
DOMAIN,
|
|
f"deprecated_yaml_import_issue_{result.get('reason')}",
|
|
breaks_in_ha_version="2026.6.0",
|
|
is_fixable=False,
|
|
issue_domain=DOMAIN,
|
|
severity=ir.IssueSeverity.WARNING,
|
|
translation_key=f"deprecated_yaml_import_issue_{result.get('reason')}",
|
|
translation_placeholders={
|
|
"domain": DOMAIN,
|
|
"integration_title": INTEGRATION_TITLE,
|
|
},
|
|
)
|
|
return
|
|
|
|
ir.async_create_issue(
|
|
hass,
|
|
HOMEASSISTANT_DOMAIN,
|
|
"deprecated_yaml",
|
|
breaks_in_ha_version="2026.6.0",
|
|
is_fixable=False,
|
|
issue_domain=DOMAIN,
|
|
severity=ir.IssueSeverity.WARNING,
|
|
translation_key="deprecated_yaml",
|
|
translation_placeholders={
|
|
"domain": DOMAIN,
|
|
"integration_title": INTEGRATION_TITLE,
|
|
},
|
|
)
|
|
|
|
|
|
async def async_setup_entry(
|
|
hass: HomeAssistant,
|
|
entry: VivotekConfigEntry,
|
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
|
) -> None:
|
|
"""Set up the component from a config entry."""
|
|
config = entry.data
|
|
creds = f"{config[CONF_USERNAME]}:{config[CONF_PASSWORD]}"
|
|
stream_source = (
|
|
f"rtsp://{creds}@{config[CONF_IP_ADDRESS]}:554/{config[CONF_STREAM_PATH]}"
|
|
)
|
|
cam_client = entry.runtime_data
|
|
if TYPE_CHECKING:
|
|
assert entry.unique_id is not None
|
|
async_add_entities(
|
|
[
|
|
VivotekCam(
|
|
cam_client,
|
|
stream_source,
|
|
entry.unique_id,
|
|
entry.options[CONF_FRAMERATE],
|
|
entry.title,
|
|
)
|
|
]
|
|
)
|
|
|
|
|
|
class VivotekCam(Camera):
|
|
"""A Vivotek IP camera."""
|
|
|
|
_attr_brand = DEFAULT_CAMERA_BRAND
|
|
_attr_supported_features = CameraEntityFeature.STREAM
|
|
|
|
def __init__(
|
|
self,
|
|
cam_client: VivotekCamera,
|
|
stream_source: str,
|
|
unique_id: str,
|
|
framerate: int,
|
|
name: str,
|
|
) -> None:
|
|
"""Initialize a Vivotek camera."""
|
|
super().__init__()
|
|
self._cam = cam_client
|
|
self._attr_frame_interval = 1 / framerate
|
|
self._attr_unique_id = unique_id
|
|
self._attr_name = name
|
|
self._stream_source = stream_source
|
|
|
|
def camera_image(
|
|
self, width: int | None = None, height: int | None = None
|
|
) -> bytes | None:
|
|
"""Return bytes of camera image."""
|
|
return self._cam.snapshot()
|
|
|
|
async def stream_source(self) -> str:
|
|
"""Return the source of the stream."""
|
|
return self._stream_source
|
|
|
|
def disable_motion_detection(self) -> None:
|
|
"""Disable motion detection in camera."""
|
|
response = self._cam.set_param(DEFAULT_EVENT_0_KEY, 0)
|
|
self._attr_motion_detection_enabled = int(response) == 1
|
|
|
|
def enable_motion_detection(self) -> None:
|
|
"""Enable motion detection in camera."""
|
|
response = self._cam.set_param(DEFAULT_EVENT_0_KEY, 1)
|
|
self._attr_motion_detection_enabled = int(response) == 1
|
|
|
|
def update(self) -> None:
|
|
"""Update entity status."""
|
|
self._attr_model = self._cam.model_name
|
|
self._attr_available = self._attr_model is not None
|