home-assistant/homeassistant/components/android_ip_webcam.py
2018-09-15 14:38:18 +02:00

344 lines
10 KiB
Python

"""
Support for IP Webcam, an Android app that acts as a full-featured webcam.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/android_ip_webcam/
"""
import asyncio
import logging
from datetime import timedelta
import voluptuous as vol
from homeassistant.core import callback
from homeassistant.const import (
CONF_NAME,
CONF_HOST,
CONF_PORT,
CONF_USERNAME,
CONF_PASSWORD,
CONF_SENSORS,
CONF_SWITCHES,
CONF_TIMEOUT,
CONF_SCAN_INTERVAL,
CONF_PLATFORM,
)
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers import discovery
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.dispatcher import (
async_dispatcher_send,
async_dispatcher_connect,
)
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.event import async_track_point_in_utc_time
from homeassistant.util.dt import utcnow
from homeassistant.components.camera.mjpeg import CONF_MJPEG_URL, CONF_STILL_IMAGE_URL
REQUIREMENTS = ["pydroid-ipcam==0.8"]
_LOGGER = logging.getLogger(__name__)
ATTR_AUD_CONNS = "Audio Connections"
ATTR_HOST = "host"
ATTR_VID_CONNS = "Video Connections"
CONF_MOTION_SENSOR = "motion_sensor"
DATA_IP_WEBCAM = "android_ip_webcam"
DEFAULT_NAME = "IP Webcam"
DEFAULT_PORT = 8080
DEFAULT_TIMEOUT = 10
DOMAIN = "android_ip_webcam"
SCAN_INTERVAL = timedelta(seconds=10)
SIGNAL_UPDATE_DATA = "android_ip_webcam_update"
KEY_MAP = {
"audio_connections": "Audio Connections",
"adet_limit": "Audio Trigger Limit",
"antibanding": "Anti-banding",
"audio_only": "Audio Only",
"battery_level": "Battery Level",
"battery_temp": "Battery Temperature",
"battery_voltage": "Battery Voltage",
"coloreffect": "Color Effect",
"exposure": "Exposure Level",
"exposure_lock": "Exposure Lock",
"ffc": "Front-facing Camera",
"flashmode": "Flash Mode",
"focus": "Focus",
"focus_homing": "Focus Homing",
"focus_region": "Focus Region",
"focusmode": "Focus Mode",
"gps_active": "GPS Active",
"idle": "Idle",
"ip_address": "IPv4 Address",
"ipv6_address": "IPv6 Address",
"ivideon_streaming": "Ivideon Streaming",
"light": "Light Level",
"mirror_flip": "Mirror Flip",
"motion": "Motion",
"motion_active": "Motion Active",
"motion_detect": "Motion Detection",
"motion_event": "Motion Event",
"motion_limit": "Motion Limit",
"night_vision": "Night Vision",
"night_vision_average": "Night Vision Average",
"night_vision_gain": "Night Vision Gain",
"orientation": "Orientation",
"overlay": "Overlay",
"photo_size": "Photo Size",
"pressure": "Pressure",
"proximity": "Proximity",
"quality": "Quality",
"scenemode": "Scene Mode",
"sound": "Sound",
"sound_event": "Sound Event",
"sound_timeout": "Sound Timeout",
"torch": "Torch",
"video_connections": "Video Connections",
"video_chunk_len": "Video Chunk Length",
"video_recording": "Video Recording",
"video_size": "Video Size",
"whitebalance": "White Balance",
"whitebalance_lock": "White Balance Lock",
"zoom": "Zoom",
}
ICON_MAP = {
"audio_connections": "mdi:speaker",
"battery_level": "mdi:battery",
"battery_temp": "mdi:thermometer",
"battery_voltage": "mdi:battery-charging-100",
"exposure_lock": "mdi:camera",
"ffc": "mdi:camera-front-variant",
"focus": "mdi:image-filter-center-focus",
"gps_active": "mdi:crosshairs-gps",
"light": "mdi:flashlight",
"motion": "mdi:run",
"night_vision": "mdi:weather-night",
"overlay": "mdi:monitor",
"pressure": "mdi:gauge",
"proximity": "mdi:map-marker-radius",
"quality": "mdi:quality-high",
"sound": "mdi:speaker",
"sound_event": "mdi:speaker",
"sound_timeout": "mdi:speaker",
"torch": "mdi:white-balance-sunny",
"video_chunk_len": "mdi:video",
"video_connections": "mdi:eye",
"video_recording": "mdi:record-rec",
"whitebalance_lock": "mdi:white-balance-auto",
}
SWITCHES = [
"exposure_lock",
"ffc",
"focus",
"gps_active",
"night_vision",
"overlay",
"torch",
"whitebalance_lock",
"video_recording",
]
SENSORS = [
"audio_connections",
"battery_level",
"battery_temp",
"battery_voltage",
"light",
"motion",
"pressure",
"proximity",
"sound",
"video_connections",
]
CONFIG_SCHEMA = vol.Schema(
{
DOMAIN: vol.All(
cv.ensure_list,
[
vol.Schema(
{
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
vol.Optional(
CONF_TIMEOUT, default=DEFAULT_TIMEOUT
): cv.positive_int,
vol.Optional(
CONF_SCAN_INTERVAL, default=SCAN_INTERVAL
): cv.time_period,
vol.Inclusive(CONF_USERNAME, "authentication"): cv.string,
vol.Inclusive(CONF_PASSWORD, "authentication"): cv.string,
vol.Optional(CONF_SWITCHES): vol.All(
cv.ensure_list, [vol.In(SWITCHES)]
),
vol.Optional(CONF_SENSORS): vol.All(
cv.ensure_list, [vol.In(SENSORS)]
),
vol.Optional(CONF_MOTION_SENSOR): cv.boolean,
}
)
],
)
},
extra=vol.ALLOW_EXTRA,
)
@asyncio.coroutine
def async_setup(hass, config):
"""Set up the IP Webcam component."""
from pydroid_ipcam import PyDroidIPCam
webcams = hass.data[DATA_IP_WEBCAM] = {}
websession = async_get_clientsession(hass)
@asyncio.coroutine
def async_setup_ipcamera(cam_config):
"""Set up an IP camera."""
host = cam_config[CONF_HOST]
username = cam_config.get(CONF_USERNAME)
password = cam_config.get(CONF_PASSWORD)
name = cam_config[CONF_NAME]
interval = cam_config[CONF_SCAN_INTERVAL]
switches = cam_config.get(CONF_SWITCHES)
sensors = cam_config.get(CONF_SENSORS)
motion = cam_config.get(CONF_MOTION_SENSOR)
# Init ip webcam
cam = PyDroidIPCam(
hass.loop,
websession,
host,
cam_config[CONF_PORT],
username=username,
password=password,
timeout=cam_config[CONF_TIMEOUT],
)
if switches is None:
switches = [
setting for setting in cam.enabled_settings if setting in SWITCHES
]
if sensors is None:
sensors = [sensor for sensor in cam.enabled_sensors if sensor in SENSORS]
sensors.extend(["audio_connections", "video_connections"])
if motion is None:
motion = "motion_active" in cam.enabled_sensors
@asyncio.coroutine
def async_update_data(now):
"""Update data from IP camera in SCAN_INTERVAL."""
yield from cam.update()
async_dispatcher_send(hass, SIGNAL_UPDATE_DATA, host)
async_track_point_in_utc_time(hass, async_update_data, utcnow() + interval)
yield from async_update_data(None)
# Load platforms
webcams[host] = cam
mjpeg_camera = {
CONF_PLATFORM: "mjpeg",
CONF_MJPEG_URL: cam.mjpeg_url,
CONF_STILL_IMAGE_URL: cam.image_url,
CONF_NAME: name,
}
if username and password:
mjpeg_camera.update({CONF_USERNAME: username, CONF_PASSWORD: password})
hass.async_create_task(
discovery.async_load_platform(hass, "camera", "mjpeg", mjpeg_camera, config)
)
if sensors:
hass.async_create_task(
discovery.async_load_platform(
hass,
"sensor",
DOMAIN,
{CONF_NAME: name, CONF_HOST: host, CONF_SENSORS: sensors},
config,
)
)
if switches:
hass.async_create_task(
discovery.async_load_platform(
hass,
"switch",
DOMAIN,
{CONF_NAME: name, CONF_HOST: host, CONF_SWITCHES: switches},
config,
)
)
if motion:
hass.async_create_task(
discovery.async_load_platform(
hass,
"binary_sensor",
DOMAIN,
{CONF_HOST: host, CONF_NAME: name},
config,
)
)
tasks = [async_setup_ipcamera(conf) for conf in config[DOMAIN]]
if tasks:
yield from asyncio.wait(tasks, loop=hass.loop)
return True
class AndroidIPCamEntity(Entity):
"""The Android device running IP Webcam."""
def __init__(self, host, ipcam):
"""Initialize the data object."""
self._host = host
self._ipcam = ipcam
@asyncio.coroutine
def async_added_to_hass(self):
"""Register update dispatcher."""
@callback
def async_ipcam_update(host):
"""Update callback."""
if self._host != host:
return
self.async_schedule_update_ha_state(True)
async_dispatcher_connect(self.hass, SIGNAL_UPDATE_DATA, async_ipcam_update)
@property
def should_poll(self):
"""Return True if entity has to be polled for state."""
return False
@property
def available(self):
"""Return True if entity is available."""
return self._ipcam.available
@property
def device_state_attributes(self):
"""Return the state attributes."""
state_attr = {ATTR_HOST: self._host}
if self._ipcam.status_data is None:
return state_attr
state_attr[ATTR_VID_CONNS] = self._ipcam.status_data.get("video_connections")
state_attr[ATTR_AUD_CONNS] = self._ipcam.status_data.get("audio_connections")
return state_attr