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
parent
b7153ca207
commit
9ba3abd1b7
|
@ -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")
|
||||
|
|
|
@ -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'
|
||||
|
|
Loading…
Reference in New Issue