Add support Slide cover (#25913)
* Add support GoSlide cover * Fixed Parameters differ from overridden Fixed Removed other pylint warnings * Renamed GoSlide to Slide, because of Innovation in Motion rebranding * Fixed codeowners file * Fixed requirements file * Removed pylint: disable=unused-argument Removed DOMAIN not exist check Changed if to min/max Changed 3rd party import to top of the module Removed timeout/retry parameters Removed unused constants Added check for discovery_info is none Changed pass slide object instead of full hass object Changed pass api object instead of full hass object Added unique_id functionality Removed entity_id/name properties Removed supported_features/state functions * Fixed unused variables * Changed Slide API uses snake names Changed Improved exception handling Changed Updated Slide API to 0.50.0 * Changed moved exceptions into goslide-api Changed retry setup into coroutine * Changed str(err) to err Changed invert if result to if not resultpull/26390/head
parent
7d1e3af701
commit
330ae0d885
homeassistant/components/slide
|
@ -564,6 +564,7 @@ omit =
|
|||
homeassistant/components/skybeacon/sensor.py
|
||||
homeassistant/components/skybell/*
|
||||
homeassistant/components/slack/notify.py
|
||||
homeassistant/components/slide/*
|
||||
homeassistant/components/sma/sensor.py
|
||||
homeassistant/components/smappee/*
|
||||
homeassistant/components/smarty/*
|
||||
|
|
|
@ -236,6 +236,7 @@ homeassistant/components/shell_command/* @home-assistant/core
|
|||
homeassistant/components/shiftr/* @fabaff
|
||||
homeassistant/components/shodan/* @fabaff
|
||||
homeassistant/components/simplisafe/* @bachya
|
||||
homeassistant/components/slide/* @ualex73
|
||||
homeassistant/components/sma/* @kellerza
|
||||
homeassistant/components/smarthab/* @outadoc
|
||||
homeassistant/components/smartthings/* @andrewsayre
|
||||
|
|
|
@ -0,0 +1,157 @@
|
|||
"""Component for the Go Slide API."""
|
||||
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
|
||||
import voluptuous as vol
|
||||
from goslideapi import GoSlideCloud, goslideapi
|
||||
|
||||
from homeassistant.const import (
|
||||
CONF_USERNAME,
|
||||
CONF_PASSWORD,
|
||||
CONF_SCAN_INTERVAL,
|
||||
STATE_OPEN,
|
||||
STATE_CLOSED,
|
||||
STATE_OPENING,
|
||||
STATE_CLOSING,
|
||||
)
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.discovery import async_load_platform
|
||||
from homeassistant.helpers.event import async_track_time_interval, async_call_later
|
||||
from .const import DOMAIN, SLIDES, API, COMPONENT, DEFAULT_RETRY
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_SCAN_INTERVAL = timedelta(seconds=30)
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema(
|
||||
{
|
||||
DOMAIN: vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_USERNAME): cv.string,
|
||||
vol.Required(CONF_PASSWORD): cv.string,
|
||||
vol.Optional(
|
||||
CONF_SCAN_INTERVAL, default=DEFAULT_SCAN_INTERVAL
|
||||
): cv.time_period,
|
||||
}
|
||||
)
|
||||
},
|
||||
extra=vol.ALLOW_EXTRA,
|
||||
)
|
||||
|
||||
|
||||
async def async_setup(hass, config):
|
||||
"""Set up the Slide platform."""
|
||||
|
||||
async def update_slides(now=None):
|
||||
"""Update slide information."""
|
||||
result = await hass.data[DOMAIN][API].slides_overview()
|
||||
|
||||
if result is None:
|
||||
_LOGGER.error("Slide API does not work or returned an error")
|
||||
return
|
||||
|
||||
if result:
|
||||
_LOGGER.debug("Slide API returned %d slide(s)", len(result))
|
||||
else:
|
||||
_LOGGER.warning("Slide API returned 0 slides")
|
||||
|
||||
for slide in result:
|
||||
if "device_id" not in slide:
|
||||
_LOGGER.error(
|
||||
"Found invalid Slide entry, device_id is " "missing. Entry=%s",
|
||||
slide,
|
||||
)
|
||||
continue
|
||||
|
||||
uid = slide["device_id"].replace("slide_", "")
|
||||
slidenew = hass.data[DOMAIN][SLIDES].setdefault(uid, {})
|
||||
slidenew["mac"] = uid
|
||||
slidenew["id"] = slide["id"]
|
||||
slidenew["name"] = slide["device_name"]
|
||||
slidenew["state"] = None
|
||||
oldpos = slidenew.get("pos")
|
||||
slidenew["pos"] = None
|
||||
slidenew["online"] = False
|
||||
|
||||
if "device_info" not in slide:
|
||||
_LOGGER.error(
|
||||
"Slide %s (%s) has no device_info Entry=%s",
|
||||
slide["id"],
|
||||
slidenew["mac"],
|
||||
slide,
|
||||
)
|
||||
continue
|
||||
|
||||
# Check if we have pos (OK) or code (NOK)
|
||||
if "pos" in slide["device_info"]:
|
||||
slidenew["online"] = True
|
||||
slidenew["pos"] = slide["device_info"]["pos"]
|
||||
slidenew["pos"] = max(0, min(1, slidenew["pos"]))
|
||||
|
||||
if oldpos is None or oldpos == slidenew["pos"]:
|
||||
slidenew["state"] = (
|
||||
STATE_CLOSED if slidenew["pos"] > 0.95 else STATE_OPEN
|
||||
)
|
||||
elif oldpos < slidenew["pos"]:
|
||||
slidenew["state"] = (
|
||||
STATE_CLOSED if slidenew["pos"] >= 0.95 else STATE_CLOSING
|
||||
)
|
||||
else:
|
||||
slidenew["state"] = (
|
||||
STATE_OPEN if slidenew["pos"] <= 0.05 else STATE_OPENING
|
||||
)
|
||||
elif "code" in slide["device_info"]:
|
||||
_LOGGER.warning(
|
||||
"Slide %s (%s) is offline with " "code=%s",
|
||||
slide["id"],
|
||||
slidenew["mac"],
|
||||
slide["device_info"]["code"],
|
||||
)
|
||||
else:
|
||||
_LOGGER.error(
|
||||
"Slide %s (%s) has invalid device_info %s",
|
||||
slide["id"],
|
||||
slidenew["mac"],
|
||||
slide["device_info"],
|
||||
)
|
||||
|
||||
_LOGGER.debug("Updated entry=%s", slidenew)
|
||||
|
||||
async def retry_setup(now):
|
||||
"""Retry setup if a connection/timeout happens on Slide API."""
|
||||
await async_setup(hass, config)
|
||||
|
||||
hass.data[DOMAIN] = {}
|
||||
hass.data[DOMAIN][SLIDES] = {}
|
||||
|
||||
username = config[DOMAIN][CONF_USERNAME]
|
||||
password = config[DOMAIN][CONF_PASSWORD]
|
||||
scaninterval = config[DOMAIN][CONF_SCAN_INTERVAL]
|
||||
|
||||
hass.data[DOMAIN][API] = GoSlideCloud(username, password)
|
||||
|
||||
try:
|
||||
result = await hass.data[DOMAIN][API].login()
|
||||
except (goslideapi.ClientConnectionError, goslideapi.ClientTimeoutError) as err:
|
||||
_LOGGER.error(
|
||||
"Error connecting to Slide Cloud: %s, going to retry in %s seconds",
|
||||
err,
|
||||
DEFAULT_RETRY,
|
||||
)
|
||||
async_call_later(hass, DEFAULT_RETRY, retry_setup)
|
||||
return True
|
||||
|
||||
if not result:
|
||||
_LOGGER.error("Slide API returned unknown error during authentication")
|
||||
return False
|
||||
|
||||
_LOGGER.debug("Slide API successfully authenticated")
|
||||
|
||||
await update_slides()
|
||||
|
||||
hass.async_create_task(async_load_platform(hass, COMPONENT, DOMAIN, {}, config))
|
||||
|
||||
async_track_time_interval(hass, update_slides, scaninterval)
|
||||
|
||||
return True
|
|
@ -0,0 +1,7 @@
|
|||
"""Define constants for the Go Slide component."""
|
||||
|
||||
API = "api"
|
||||
COMPONENT = "cover"
|
||||
DOMAIN = "slide"
|
||||
SLIDES = "slides"
|
||||
DEFAULT_RETRY = 120
|
|
@ -0,0 +1,124 @@
|
|||
"""Support for Go Slide slides."""
|
||||
|
||||
import logging
|
||||
|
||||
from homeassistant.const import ATTR_ID
|
||||
from homeassistant.components.cover import (
|
||||
ATTR_POSITION,
|
||||
STATE_CLOSED,
|
||||
STATE_OPENING,
|
||||
STATE_CLOSING,
|
||||
DEVICE_CLASS_CURTAIN,
|
||||
CoverDevice,
|
||||
)
|
||||
from .const import API, DOMAIN, SLIDES
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
||||
"""Set up cover(s) for Go Slide platform."""
|
||||
|
||||
if discovery_info is None:
|
||||
return
|
||||
|
||||
entities = []
|
||||
|
||||
for slide in hass.data[DOMAIN][SLIDES].values():
|
||||
_LOGGER.debug("Setting up Slide entity: %s", slide)
|
||||
entities.append(SlideCover(hass.data[DOMAIN][API], slide))
|
||||
|
||||
async_add_entities(entities)
|
||||
|
||||
|
||||
class SlideCover(CoverDevice):
|
||||
"""Representation of a Go Slide cover."""
|
||||
|
||||
def __init__(self, api, slide):
|
||||
"""Initialize the cover."""
|
||||
self._api = api
|
||||
self._slide = slide
|
||||
self._id = slide["id"]
|
||||
self._unique_id = slide["mac"]
|
||||
self._name = slide["name"]
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return the device unique id."""
|
||||
return self._unique_id
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the device name."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return device specific state attributes."""
|
||||
return {ATTR_ID: self._id}
|
||||
|
||||
@property
|
||||
def is_opening(self):
|
||||
"""Return if the cover is opening or not."""
|
||||
return self._slide["state"] == STATE_OPENING
|
||||
|
||||
@property
|
||||
def is_closing(self):
|
||||
"""Return if the cover is closing or not."""
|
||||
return self._slide["state"] == STATE_CLOSING
|
||||
|
||||
@property
|
||||
def is_closed(self):
|
||||
"""Return None if status is unknown, True if closed, else False."""
|
||||
if self._slide["state"] is None:
|
||||
return None
|
||||
return self._slide["state"] == STATE_CLOSED
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
"""Return False if state is not available."""
|
||||
return self._slide["online"]
|
||||
|
||||
@property
|
||||
def assumed_state(self):
|
||||
"""Let HA know the integration is assumed state."""
|
||||
return True
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
"""Return the device class of the cover."""
|
||||
return DEVICE_CLASS_CURTAIN
|
||||
|
||||
@property
|
||||
def current_cover_position(self):
|
||||
"""Return the current position of cover shutter."""
|
||||
pos = self._slide["pos"]
|
||||
if pos is not None:
|
||||
pos = int(pos * 100)
|
||||
return pos
|
||||
|
||||
async def async_open_cover(self, **kwargs):
|
||||
"""Open the cover."""
|
||||
self._slide["state"] = STATE_OPENING
|
||||
await self._api.slide_open(self._id)
|
||||
|
||||
async def async_close_cover(self, **kwargs):
|
||||
"""Close the cover."""
|
||||
self._slide["state"] = STATE_CLOSING
|
||||
await self._api.slide_close(self._id)
|
||||
|
||||
async def async_stop_cover(self, **kwargs):
|
||||
"""Stop the cover."""
|
||||
await self._api.slide_stop(self._id)
|
||||
|
||||
async def async_set_cover_position(self, **kwargs):
|
||||
"""Move the cover to a specific position."""
|
||||
position = kwargs[ATTR_POSITION] / 100
|
||||
|
||||
if self._slide["pos"] is not None:
|
||||
if position > self._slide["pos"]:
|
||||
self._slide["state"] = STATE_CLOSING
|
||||
else:
|
||||
self._slide["state"] = STATE_OPENING
|
||||
|
||||
await self._api.slide_set_position(self._id, position)
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"domain": "slide",
|
||||
"name": "Slide",
|
||||
"documentation": "https://www.home-assistant.io/components/slide",
|
||||
"requirements": [
|
||||
"goslide-api==0.5.1"
|
||||
],
|
||||
"dependencies": [],
|
||||
"codeowners": [
|
||||
"@ualex73"
|
||||
]
|
||||
}
|
|
@ -567,6 +567,9 @@ google-cloud-texttospeech==0.4.0
|
|||
# homeassistant.components.google_travel_time
|
||||
googlemaps==2.5.1
|
||||
|
||||
# homeassistant.components.slide
|
||||
goslide-api==0.5.1
|
||||
|
||||
# homeassistant.components.remote_rpi_gpio
|
||||
gpiozero==1.4.1
|
||||
|
||||
|
|
Loading…
Reference in New Issue