266 lines
8.4 KiB
Python
266 lines
8.4 KiB
Python
"""Platform for the Garadget cover component."""
|
|
import logging
|
|
|
|
import requests
|
|
import voluptuous as vol
|
|
|
|
from homeassistant.components.cover import PLATFORM_SCHEMA, CoverEntity
|
|
from homeassistant.const import (
|
|
CONF_ACCESS_TOKEN,
|
|
CONF_COVERS,
|
|
CONF_DEVICE,
|
|
CONF_NAME,
|
|
CONF_PASSWORD,
|
|
CONF_USERNAME,
|
|
STATE_CLOSED,
|
|
STATE_OPEN,
|
|
)
|
|
import homeassistant.helpers.config_validation as cv
|
|
from homeassistant.helpers.event import track_utc_time_change
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
ATTR_AVAILABLE = "available"
|
|
ATTR_SENSOR_STRENGTH = "sensor_reflection_rate"
|
|
ATTR_SIGNAL_STRENGTH = "wifi_signal_strength"
|
|
ATTR_TIME_IN_STATE = "time_in_state"
|
|
|
|
DEFAULT_NAME = "Garadget"
|
|
|
|
STATE_CLOSING = "closing"
|
|
STATE_OFFLINE = "offline"
|
|
STATE_OPENING = "opening"
|
|
STATE_STOPPED = "stopped"
|
|
|
|
STATES_MAP = {
|
|
"open": STATE_OPEN,
|
|
"opening": STATE_OPENING,
|
|
"closed": STATE_CLOSED,
|
|
"closing": STATE_CLOSING,
|
|
"stopped": STATE_STOPPED,
|
|
}
|
|
|
|
COVER_SCHEMA = vol.Schema(
|
|
{
|
|
vol.Optional(CONF_ACCESS_TOKEN): cv.string,
|
|
vol.Optional(CONF_DEVICE): cv.string,
|
|
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
|
vol.Optional(CONF_PASSWORD): cv.string,
|
|
vol.Optional(CONF_USERNAME): cv.string,
|
|
}
|
|
)
|
|
|
|
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
|
{vol.Required(CONF_COVERS): cv.schema_with_slug_keys(COVER_SCHEMA)}
|
|
)
|
|
|
|
|
|
def setup_platform(hass, config, add_entities, discovery_info=None):
|
|
"""Set up the Garadget covers."""
|
|
covers = []
|
|
devices = config.get(CONF_COVERS)
|
|
|
|
for device_id, device_config in devices.items():
|
|
args = {
|
|
"name": device_config.get(CONF_NAME),
|
|
"device_id": device_config.get(CONF_DEVICE, device_id),
|
|
"username": device_config.get(CONF_USERNAME),
|
|
"password": device_config.get(CONF_PASSWORD),
|
|
"access_token": device_config.get(CONF_ACCESS_TOKEN),
|
|
}
|
|
|
|
covers.append(GaradgetCover(hass, args))
|
|
|
|
add_entities(covers)
|
|
|
|
|
|
class GaradgetCover(CoverEntity):
|
|
"""Representation of a Garadget cover."""
|
|
|
|
def __init__(self, hass, args):
|
|
"""Initialize the cover."""
|
|
self.particle_url = "https://api.particle.io"
|
|
self.hass = hass
|
|
self._name = args["name"]
|
|
self.device_id = args["device_id"]
|
|
self.access_token = args["access_token"]
|
|
self.obtained_token = False
|
|
self._username = args["username"]
|
|
self._password = args["password"]
|
|
self._state = None
|
|
self.time_in_state = None
|
|
self.signal = None
|
|
self.sensor = None
|
|
self._unsub_listener_cover = None
|
|
self._available = True
|
|
|
|
if self.access_token is None:
|
|
self.access_token = self.get_token()
|
|
self._obtained_token = True
|
|
|
|
try:
|
|
if self._name is None:
|
|
doorconfig = self._get_variable("doorConfig")
|
|
if doorconfig["nme"] is not None:
|
|
self._name = doorconfig["nme"]
|
|
self.update()
|
|
except requests.exceptions.ConnectionError as ex:
|
|
_LOGGER.error("Unable to connect to server: %(reason)s", {"reason": ex})
|
|
self._state = STATE_OFFLINE
|
|
self._available = False
|
|
self._name = DEFAULT_NAME
|
|
except KeyError:
|
|
_LOGGER.warning(
|
|
"Garadget device %(device)s seems to be offline",
|
|
{"device": self.device_id},
|
|
)
|
|
self._name = DEFAULT_NAME
|
|
self._state = STATE_OFFLINE
|
|
self._available = False
|
|
|
|
def __del__(self):
|
|
"""Try to remove token."""
|
|
if self._obtained_token is True and self.access_token is not None:
|
|
self.remove_token()
|
|
|
|
@property
|
|
def name(self):
|
|
"""Return the name of the cover."""
|
|
return self._name
|
|
|
|
@property
|
|
def available(self):
|
|
"""Return True if entity is available."""
|
|
return self._available
|
|
|
|
@property
|
|
def extra_state_attributes(self):
|
|
"""Return the device state attributes."""
|
|
data = {}
|
|
|
|
if self.signal is not None:
|
|
data[ATTR_SIGNAL_STRENGTH] = self.signal
|
|
|
|
if self.time_in_state is not None:
|
|
data[ATTR_TIME_IN_STATE] = self.time_in_state
|
|
|
|
if self.sensor is not None:
|
|
data[ATTR_SENSOR_STRENGTH] = self.sensor
|
|
|
|
if self.access_token is not None:
|
|
data[CONF_ACCESS_TOKEN] = self.access_token
|
|
|
|
return data
|
|
|
|
@property
|
|
def is_closed(self):
|
|
"""Return if the cover is closed."""
|
|
if self._state is None:
|
|
return None
|
|
return self._state == STATE_CLOSED
|
|
|
|
@property
|
|
def device_class(self):
|
|
"""Return the class of this device, from component DEVICE_CLASSES."""
|
|
return "garage"
|
|
|
|
def get_token(self):
|
|
"""Get new token for usage during this session."""
|
|
args = {
|
|
"grant_type": "password",
|
|
"username": self._username,
|
|
"password": self._password,
|
|
}
|
|
url = f"{self.particle_url}/oauth/token"
|
|
ret = requests.post(url, auth=("particle", "particle"), data=args, timeout=10)
|
|
|
|
try:
|
|
return ret.json()["access_token"]
|
|
except KeyError:
|
|
_LOGGER.error("Unable to retrieve access token")
|
|
|
|
def remove_token(self):
|
|
"""Remove authorization token from API."""
|
|
url = f"{self.particle_url}/v1/access_tokens/{self.access_token}"
|
|
ret = requests.delete(url, auth=(self._username, self._password), timeout=10)
|
|
return ret.text
|
|
|
|
def _start_watcher(self, command):
|
|
"""Start watcher."""
|
|
_LOGGER.debug("Starting Watcher for command: %s ", command)
|
|
if self._unsub_listener_cover is None:
|
|
self._unsub_listener_cover = track_utc_time_change(
|
|
self.hass, self._check_state
|
|
)
|
|
|
|
def _check_state(self, now):
|
|
"""Check the state of the service during an operation."""
|
|
self.schedule_update_ha_state(True)
|
|
|
|
def close_cover(self, **kwargs):
|
|
"""Close the cover."""
|
|
if self._state not in ["close", "closing"]:
|
|
ret = self._put_command("setState", "close")
|
|
self._start_watcher("close")
|
|
return ret.get("return_value") == 1
|
|
|
|
def open_cover(self, **kwargs):
|
|
"""Open the cover."""
|
|
if self._state not in ["open", "opening"]:
|
|
ret = self._put_command("setState", "open")
|
|
self._start_watcher("open")
|
|
return ret.get("return_value") == 1
|
|
|
|
def stop_cover(self, **kwargs):
|
|
"""Stop the door where it is."""
|
|
if self._state not in ["stopped"]:
|
|
ret = self._put_command("setState", "stop")
|
|
self._start_watcher("stop")
|
|
return ret["return_value"] == 1
|
|
|
|
def update(self):
|
|
"""Get updated status from API."""
|
|
try:
|
|
status = self._get_variable("doorStatus")
|
|
_LOGGER.debug("Current Status: %s", status["status"])
|
|
self._state = STATES_MAP.get(status["status"])
|
|
self.time_in_state = status["time"]
|
|
self.signal = status["signal"]
|
|
self.sensor = status["sensor"]
|
|
self._available = True
|
|
except requests.exceptions.ConnectionError as ex:
|
|
_LOGGER.error("Unable to connect to server: %(reason)s", {"reason": ex})
|
|
self._state = STATE_OFFLINE
|
|
except KeyError:
|
|
_LOGGER.warning(
|
|
"Garadget device %(device)s seems to be offline",
|
|
{"device": self.device_id},
|
|
)
|
|
self._state = STATE_OFFLINE
|
|
|
|
if (
|
|
self._state not in [STATE_CLOSING, STATE_OPENING]
|
|
and self._unsub_listener_cover is not None
|
|
):
|
|
self._unsub_listener_cover()
|
|
self._unsub_listener_cover = None
|
|
|
|
def _get_variable(self, var):
|
|
"""Get latest status."""
|
|
url = f"{self.particle_url}/v1/devices/{self.device_id}/{var}?access_token={self.access_token}"
|
|
ret = requests.get(url, timeout=10)
|
|
result = {}
|
|
for pairs in ret.json()["result"].split("|"):
|
|
key = pairs.split("=")
|
|
result[key[0]] = key[1]
|
|
return result
|
|
|
|
def _put_command(self, func, arg=None):
|
|
"""Send commands to API."""
|
|
params = {"access_token": self.access_token}
|
|
if arg:
|
|
params["command"] = arg
|
|
url = f"{self.particle_url}/v1/devices/{self.device_id}/{func}"
|
|
ret = requests.post(url, data=params, timeout=10)
|
|
return ret.json()
|