Add Netatmo camera services (#27970)

* Netatmo camera : Implement turn_on and turn_off methods.

* Netatmo camera : Implement turn_on and turn_off methods.

* Netatmo camera : Implement turn_on and turn_off methods.

* Netatmo camera : Implement turn_on and turn_off methods.

* Netatmo camera : Implement enable_motion_detection(), disable_motion_detection() operations.

* Netatmo camera : Implement enable_motion_detection(), disable_motion_detection() operations.

* Netatmo camera : Implement enable_motion_detection(), disable_motion_detection() operations.

* Netatmo camera : Implement enable_motion_detection(), disable_motion_detection() operations.

* Netatmo camera : Implement enable_motion_detection(), disable_motion_detection() operations.

* Netatmo camera : Implement enable_motion_detection(), disable_motion_detection() operations.

* Netatmo camera : Implement enable_motion_detection(), disable_motion_detection() operations.

* Netatmo camera : Implement enable_motion_detection(), disable_motion_detection() operations.

* Netatmo camera : Implement enable_motion_detection(), disable_motion_detection() operations.

* Netatmo camera : Implement enable_motion_detection(), disable_motion_detection() operations.

* Netatmo camera : Implement enable_motion_detection(), disable_motion_detection() operations.

* Netatmo camera : Implement enable_motion_detection(), disable_motion_detection() operations.

* Netatmo camera : Implement enable_motion_detection(), disable_motion_detection() operations.

* Netatmo camera : Implement enable_motion_detection(), disable_motion_detection() operations.

* Netatmo camera : Implement enable_motion_detection(), disable_motion_detection() operations.

* Netatmo camera : Implement enable_motion_detection(), disable_motion_detection() operations.

* Netatmo camera : Implement enable_motion_detection(), disable_motion_detection() operations.

* Netatmo camera : Implement enable_motion_detection(), disable_motion_detection() operations.

* Add Presence Netatmo Camera services (set_light_auto, set_light_on, set_light_off) to control its internal flood light status.

* Add Presence Netatmo Camera services (set_light_auto, set_light_on, set_light_off) to control its internal flood light status.

* Netatmo camera : Use new style string formatting.

* Make the file compliant with flake8 linter.

* Make the file compliant with flake8 linter.

* Make it compliant with black formatter.

* Make it compliant with black formatter.

* Bug fix : Flood light control was not working with VPN url.
pull/28591/head
ssenart 2019-11-06 13:52:59 +01:00 committed by Charles Garwood
parent b7153ca207
commit 9ba3abd1b7
2 changed files with 312 additions and 19 deletions

View File

@ -5,11 +5,20 @@ from pyatmo import NoDevice
import requests
import voluptuous as vol
from homeassistant.components.camera import PLATFORM_SCHEMA, Camera, SUPPORT_STREAM
from homeassistant.const import CONF_VERIFY_SSL
from homeassistant.components.camera import (
PLATFORM_SCHEMA,
Camera,
SUPPORT_STREAM,
CAMERA_SERVICE_SCHEMA,
)
from homeassistant.const import CONF_VERIFY_SSL, STATE_ON, STATE_OFF
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.dispatcher import (
async_dispatcher_send,
async_dispatcher_connect,
)
from .const import DATA_NETATMO_AUTH
from .const import DATA_NETATMO_AUTH, DOMAIN
from . import CameraData
_LOGGER = logging.getLogger(__name__)
@ -33,6 +42,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
}
)
_BOOL_TO_STATE = {True: STATE_ON, False: STATE_OFF}
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up access to Netatmo cameras."""
@ -63,6 +74,27 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
except NoDevice:
return None
async def async_service_handler(call):
"""Handle service call."""
_LOGGER.debug(
"Service handler invoked with service=%s and data=%s",
call.service,
call.data,
)
service = call.service
entity_id = call.data["entity_id"][0]
async_dispatcher_send(hass, f"{service}_{entity_id}")
hass.services.async_register(
DOMAIN, "set_light_auto", async_service_handler, CAMERA_SERVICE_SCHEMA
)
hass.services.async_register(
DOMAIN, "set_light_on", async_service_handler, CAMERA_SERVICE_SCHEMA
)
hass.services.async_register(
DOMAIN, "set_light_off", async_service_handler, CAMERA_SERVICE_SCHEMA
)
class NetatmoCamera(Camera):
"""Representation of the images published from a Netatmo camera."""
@ -72,16 +104,39 @@ class NetatmoCamera(Camera):
super().__init__()
self._data = data
self._camera_name = camera_name
self._verify_ssl = verify_ssl
self._quality = quality
self._home = home
if home:
self._name = home + " / " + camera_name
else:
self._name = camera_name
self._vpnurl, self._localurl = self._data.camera_data.cameraUrls(
camera=camera_name
)
self._cameratype = camera_type
self._verify_ssl = verify_ssl
self._quality = quality
# URLs.
self._vpnurl = None
self._localurl = None
# Identifier
self._id = None
# Monitoring status.
self._status = None
# SD Card status
self._sd_status = None
# Power status
self._alim_status = None
# Is local
self._is_local = None
# VPN URL
self._vpn_url = None
# Light mode status
self._light_mode_status = None
def camera_image(self):
"""Return a still image response from the camera."""
@ -112,16 +167,79 @@ class NetatmoCamera(Camera):
return None
return response.content
# Entity property overrides
@property
def should_poll(self) -> bool:
"""Return True if entity has to be polled for state.
False if entity pushes its state to HA.
"""
return True
@property
def name(self):
"""Return the name of this Netatmo camera device."""
return self._name
@property
def device_state_attributes(self):
"""Return the Netatmo-specific camera state attributes."""
_LOGGER.debug("Getting new attributes from camera netatmo '%s'", self._name)
attr = {}
attr["id"] = self._id
attr["status"] = self._status
attr["sd_status"] = self._sd_status
attr["alim_status"] = self._alim_status
attr["is_local"] = self._is_local
attr["vpn_url"] = self._vpn_url
if self.model == "Presence":
attr["light_mode_status"] = self._light_mode_status
_LOGGER.debug("Attributes of '%s' = %s", self._name, attr)
return attr
@property
def available(self):
"""Return True if entity is available."""
return bool(self._alim_status == "on")
@property
def supported_features(self):
"""Return supported features."""
return SUPPORT_STREAM
@property
def is_recording(self):
"""Return true if the device is recording."""
return bool(self._status == "on")
@property
def brand(self):
"""Return the camera brand."""
return "Netatmo"
@property
def motion_detection_enabled(self):
"""Return the camera motion detection status."""
return bool(self._status == "on")
@property
def is_on(self):
"""Return true if on."""
return self.is_streaming
async def stream_source(self):
"""Return the stream source."""
url = "{0}/live/files/{1}/index.m3u8"
if self._localurl:
return url.format(self._localurl, self._quality)
return url.format(self._vpnurl, self._quality)
@property
def model(self):
"""Return the camera model."""
@ -131,14 +249,167 @@ class NetatmoCamera(Camera):
return "Welcome"
return None
@property
def supported_features(self):
"""Return supported features."""
return SUPPORT_STREAM
# Other Entity method overrides
async def stream_source(self):
"""Return the stream source."""
url = "{0}/live/files/{1}/index.m3u8"
if self._localurl:
return url.format(self._localurl, self._quality)
return url.format(self._vpnurl, self._quality)
async def async_added_to_hass(self):
"""Subscribe to signals and add camera to list."""
_LOGGER.debug("Registering services for entity_id=%s", self.entity_id)
async_dispatcher_connect(
self.hass, f"set_light_auto_{self.entity_id}", self.set_light_auto
)
async_dispatcher_connect(
self.hass, f"set_light_on_{self.entity_id}", self.set_light_on
)
async_dispatcher_connect(
self.hass, f"set_light_off_{self.entity_id}", self.set_light_off
)
def update(self):
"""Update entity status."""
_LOGGER.debug("Updating camera netatmo '%s'", self._name)
# Refresh camera data.
self._data.update()
# URLs.
self._vpnurl, self._localurl = self._data.camera_data.cameraUrls(
camera=self._camera_name
)
# Identifier
self._id = self._data.camera_data.cameraByName(
camera=self._camera_name, home=self._home
)["id"]
# Monitoring status.
self._status = self._data.camera_data.cameraByName(
camera=self._camera_name, home=self._home
)["status"]
_LOGGER.debug("Status of '%s' = %s", self._name, self._status)
# SD Card status
self._sd_status = self._data.camera_data.cameraByName(
camera=self._camera_name, home=self._home
)["sd_status"]
# Power status
self._alim_status = self._data.camera_data.cameraByName(
camera=self._camera_name, home=self._home
)["alim_status"]
# Is local
self._is_local = self._data.camera_data.cameraByName(
camera=self._camera_name, home=self._home
)["is_local"]
# VPN URL
self._vpn_url = self._data.camera_data.cameraByName(
camera=self._camera_name, home=self._home
)["vpn_url"]
self.is_streaming = self._alim_status == "on"
if self.model == "Presence":
# Light mode status
self._light_mode_status = self._data.camera_data.cameraByName(
camera=self._camera_name, home=self._home
)["light_mode_status"]
# Camera method overrides
def enable_motion_detection(self):
"""Enable motion detection in the camera."""
_LOGGER.debug("Enable motion detection of the camera '%s'", self._name)
self._enable_motion_detection(True)
def disable_motion_detection(self):
"""Disable motion detection in camera."""
_LOGGER.debug("Disable motion detection of the camera '%s'", self._name)
self._enable_motion_detection(False)
def _enable_motion_detection(self, enable):
"""Enable or disable motion detection."""
try:
if self._localurl:
requests.get(
f"{self._localurl}/command/changestatus?status={_BOOL_TO_STATE.get(enable)}",
timeout=10,
)
elif self._vpnurl:
requests.get(
f"{self._vpnurl}/command/changestatus?status={_BOOL_TO_STATE.get(enable)}",
timeout=10,
verify=self._verify_ssl,
)
else:
_LOGGER.error("Welcome/Presence VPN URL is None")
self._data.update()
(self._vpnurl, self._localurl) = self._data.camera_data.cameraUrls(
camera=self._camera_name
)
return None
except requests.exceptions.RequestException as error:
_LOGGER.error("Welcome/Presence URL changed: %s", error)
self._data.update()
(self._vpnurl, self._localurl) = self._data.camera_data.cameraUrls(
camera=self._camera_name
)
return None
else:
self.async_schedule_update_ha_state(True)
# Netatmo Presence specific camera method.
def set_light_auto(self):
"""Set flood light in automatic mode."""
_LOGGER.debug(
"Set the flood light in automatic mode for the camera '%s'", self._name
)
self._set_light_mode("auto")
def set_light_on(self):
"""Set flood light on."""
_LOGGER.debug("Set the flood light on for the camera '%s'", self._name)
self._set_light_mode("on")
def set_light_off(self):
"""Set flood light off."""
_LOGGER.debug("Set the flood light off for the camera '%s'", self._name)
self._set_light_mode("off")
def _set_light_mode(self, mode):
"""Set light mode ('auto', 'on', 'off')."""
if self.model == "Presence":
try:
config = '{"mode":"' + mode + '"}'
if self._localurl:
requests.get(
f"{self._localurl}/command/floodlight_set_config?config={config}",
timeout=10,
)
elif self._vpnurl:
requests.get(
f"{self._vpnurl}/command/floodlight_set_config?config={config}",
timeout=10,
verify=self._verify_ssl,
)
else:
_LOGGER.error("Presence VPN URL is None")
self._data.update()
(self._vpnurl, self._localurl) = self._data.camera_data.cameraUrls(
camera=self._camera_name
)
return None
except requests.exceptions.RequestException as error:
_LOGGER.error("Presence URL changed: %s", error)
self._data.update()
(self._vpnurl, self._localurl) = self._data.camera_data.cameraUrls(
camera=self._camera_name
)
return None
else:
self.async_schedule_update_ha_state(True)
else:
_LOGGER.error("Unsupported camera model for light mode")

View File

@ -4,5 +4,27 @@ addwebhook:
url:
description: URL for which to add the webhook.
example: https://yourdomain.com:443/api/webhook/webhook_id
dropwebhook:
description: Drop active webhooks.
description: Drop active webhooks.
set_light_auto:
description: Set the camera (Presence only) light in automatic mode.
fields:
entity_id:
description: Entity id.
example: 'camera.living_room'
set_light_on:
description: Set the camera (Netatmo Presence only) light on.
fields:
entity_id:
description: Entity id.
example: 'camera.living_room'
set_light_off:
description: Set the camera (Netatmo Presence only) light off.
fields:
entity_id:
description: Entity id.
example: 'camera.living_room'