Skip to content

Commit

Permalink
Merge pull request #24 from JurajNyiri/2.1
Browse files Browse the repository at this point in the history
Version 2.1 🏃
  • Loading branch information
JurajNyiri authored Oct 8, 2020
2 parents 859133e + 105a78b commit 344a681
Show file tree
Hide file tree
Showing 7 changed files with 263 additions and 62 deletions.
19 changes: 18 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,12 @@ To add multiple cameras, add integration multiple times.

## Services

This custom component creates tapo_control.* entities in your Home Assistant. Use these entity_id(s) in following service calls.
This custom component creates:
- tapo_control.* services to control a camera
- 2 camera entities, one for HD and one for SD stream
- 1 binary sensor for motion after the motion is detected for the first time

Use these services in following service calls.

<details>
<summary>tapo_control.ptz</summary>
Expand Down Expand Up @@ -139,6 +144,18 @@ This custom component creates tapo_control.* entities in your Home Assistant. Us
- **preset** Required: PTZ preset ID or a Name. See possible presets in entity attributes
</details>

## Troubleshooting

<details>
<summary>Binary sensor for motion doesn't show up or work</summary>

Motion sensor is added only after a motion is detected for the first time.

- Make sure the camera has motion detection turned on
- Try walking in front of the camera
- If above didn't work, restart the camera and try again
</details>

## Have a comment or a suggestion?

Please [open a new issue](https://github.com/JurajNyiri/HomeAssistant-Tapo-Control/issues/new/choose), or discuss on [Home Assistant: Community Forum](https://community.home-assistant.io/t/tapo-cameras-control/231795).
Expand Down
75 changes: 71 additions & 4 deletions custom_components/tapo_control/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
from homeassistant.const import (CONF_IP_ADDRESS, CONF_USERNAME, CONF_PASSWORD)
import logging
import asyncio
from homeassistant.const import (CONF_IP_ADDRESS, CONF_USERNAME, CONF_PASSWORD, EVENT_HOMEASSISTANT_STOP)
from homeassistant.core import HomeAssistant
from homeassistant.config_entries import ConfigEntry
from homeassistant.exceptions import ConfigEntryNotReady
import logging
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from homeassistant.components.onvif.event import EventManager
from .const import *
from .utils import registerController
from .utils import registerController, getCamData, initOnvifEvents

_LOGGER = logging.getLogger(__name__)

Expand All @@ -13,22 +16,86 @@ async def async_setup(hass: HomeAssistant, config: dict):
return True


async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
if(hass.data[DOMAIN][entry.entry_id]['events']):
await hass.data[DOMAIN][entry.entry_id]['events'].async_stop()

async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
"""Set up the Tapo: Cameras Control component from a config entry."""
hass.data.setdefault(DOMAIN, {})

host = entry.data.get(CONF_IP_ADDRESS)
username = entry.data.get(CONF_USERNAME)
password = entry.data.get(CONF_PASSWORD)

try:
tapoController = await hass.async_add_executor_job(registerController, host, username, password)

hass.data[DOMAIN][entry.entry_id] = tapoController
async def setupOnvif():
hass.data[DOMAIN][entry.entry_id]['eventsDevice'] = await initOnvifEvents(hass, host, username, password)

if(hass.data[DOMAIN][entry.entry_id]['eventsDevice']):
hass.data[DOMAIN][entry.entry_id]['events'] = EventManager(
hass, hass.data[DOMAIN][entry.entry_id]['eventsDevice'], f"{entry.entry_id}_tapo_events"
)

hass.data[DOMAIN][entry.entry_id]['eventsSetup'] = await setupEvents()

async def setupEvents():
if(not hass.data[DOMAIN][entry.entry_id]['events'].started):
events = hass.data[DOMAIN][entry.entry_id]['events']
if(await events.async_start()):
hass.async_create_task(
hass.config_entries.async_forward_entry_setup(entry, "binary_sensor")
)
return True
else:
return False

async def async_update_data():
# motion detection retries
if not hass.data[DOMAIN][entry.entry_id]['eventsDevice']:
# retry if connection to onvif failed
await setupOnvif()
elif not hass.data[DOMAIN][entry.entry_id]['eventsSetup']:
# retry if subscription to events failed
hass.data[DOMAIN][entry.entry_id]['eventsSetup'] = await setupEvents()

# cameras state
camData = await getCamData(hass, tapoController)
for entity in hass.data[DOMAIN][entry.entry_id]['entities']:
entity.updateCam(camData)
entity.async_schedule_update_ha_state(True)


tapoCoordinator = DataUpdateCoordinator(
hass,
_LOGGER,
name="Tapo resource status",
update_method=async_update_data
)

camData = await getCamData(hass, tapoController)

hass.data[DOMAIN][entry.entry_id] = {
"controller": tapoController,
"coordinator": tapoCoordinator,
"initialData": camData,
"name": camData['basic_info']['device_alias']
}

await setupOnvif()

hass.async_create_task(
hass.config_entries.async_forward_entry_setup(entry, "camera")
)

async def unsubscribe(event):
if(hass.data[DOMAIN][entry.entry_id]['events']):
await hass.data[DOMAIN][entry.entry_id]['events'].async_stop()

hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, unsubscribe)

except Exception as e:
_LOGGER.error("Unable to connect to Tapo: Cameras Control controller: %s", str(e))
raise ConfigEntryNotReady
Expand Down
65 changes: 65 additions & 0 deletions custom_components/tapo_control/binary_sensor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
from typing import Optional
from homeassistant.components.binary_sensor import BinarySensorEntity
from homeassistant.core import callback
from .const import DOMAIN

async def async_setup_entry(hass, entry, async_add_entities):
events = hass.data[DOMAIN][entry.entry_id]['events']
name = hass.data[DOMAIN][entry.entry_id]['name']
entities = {
event.uid: TapoBinarySensor(event.uid, events, name)
for event in events.get_platform("binary_sensor")
}

async_add_entities(entities.values())

@callback
def async_check_entities():
new_entities = []
for event in events.get_platform("binary_sensor"):
if event.uid not in entities:
entities[event.uid] = TapoBinarySensor(event.uid, events, name)
new_entities.append(entities[event.uid])
async_add_entities(new_entities)

events.async_add_listener(async_check_entities)

return True

class TapoBinarySensor(BinarySensorEntity):

def __init__(self, uid, events, name):
self._name = name
BinarySensorEntity.__init__(self)

self.uid = uid
self.events = events

@property
def is_on(self) -> bool:
return self.events.get_uid(self.uid).value

@property
def name(self) -> str:
return f"{self._name} - Motion"

@property
def device_class(self) -> Optional[str]:
return self.events.get_uid(self.uid).device_class

@property
def unique_id(self) -> str:
return self.uid

@property
def entity_registry_enabled_default(self) -> bool:
return self.events.get_uid(self.uid).entity_enabled

@property
def should_poll(self) -> bool:
return False

async def async_added_to_hass(self):
self.async_on_remove(
self.events.async_add_listener(self.async_write_ha_state)
)
Loading

0 comments on commit 344a681

Please sign in to comment.