core/homeassistant/components/mychevy/__init__.py

156 lines
4.5 KiB
Python
Raw Normal View History

"""Support for MyChevy."""
2018-01-15 20:50:56 +00:00
from datetime import timedelta
import logging
import threading
import time
2018-01-15 20:50:56 +00:00
import mychevy.mychevy as mc
2018-01-15 20:50:56 +00:00
import voluptuous as vol
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.helpers import config_validation as cv, discovery
2018-01-15 20:50:56 +00:00
from homeassistant.util import Throttle
2019-07-31 19:25:30 +00:00
DOMAIN = "mychevy"
2018-01-15 20:50:56 +00:00
UPDATE_TOPIC = DOMAIN
ERROR_TOPIC = DOMAIN + "_error"
MYCHEVY_SUCCESS = "success"
MYCHEVY_ERROR = "error"
2019-07-31 19:25:30 +00:00
NOTIFICATION_ID = "mychevy_website_notification"
NOTIFICATION_TITLE = "MyChevy website status"
2018-01-15 20:50:56 +00:00
_LOGGER = logging.getLogger(__name__)
MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=30)
ERROR_SLEEP_TIME = timedelta(minutes=30)
2019-07-31 19:25:30 +00:00
CONF_COUNTRY = "country"
DEFAULT_COUNTRY = "us"
CONFIG_SCHEMA = vol.Schema(
{
DOMAIN: vol.Schema(
{
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Optional(CONF_COUNTRY, default=DEFAULT_COUNTRY): vol.All(
cv.string, vol.In(["us", "ca"])
),
}
)
},
extra=vol.ALLOW_EXTRA,
)
2018-01-15 20:50:56 +00:00
class EVSensorConfig:
"""The EV sensor configuration."""
2018-01-15 20:50:56 +00:00
2019-07-31 19:25:30 +00:00
def __init__(
self, name, attr, unit_of_measurement=None, icon=None, extra_attrs=None
):
"""Create new sensor configuration."""
2018-01-15 20:50:56 +00:00
self.name = name
self.attr = attr
self.extra_attrs = extra_attrs or []
2018-01-15 20:50:56 +00:00
self.unit_of_measurement = unit_of_measurement
self.icon = icon
class EVBinarySensorConfig:
"""The EV binary sensor configuration."""
2018-01-15 20:50:56 +00:00
def __init__(self, name, attr, device_class=None):
"""Create new binary sensor configuration."""
2018-01-15 20:50:56 +00:00
self.name = name
self.attr = attr
self.device_class = device_class
def setup(hass, base_config):
"""Set up the mychevy component."""
2018-01-15 20:50:56 +00:00
config = base_config.get(DOMAIN)
email = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD)
country = config.get(CONF_COUNTRY)
2019-07-31 19:25:30 +00:00
hass.data[DOMAIN] = MyChevyHub(
mc.MyChevy(email, password, country), hass, base_config
)
2018-01-15 20:50:56 +00:00
hass.data[DOMAIN].start()
return True
class MyChevyHub(threading.Thread):
"""MyChevy Hub.
Connecting to the mychevy website is done through a selenium
webscraping process. That can only run synchronously. In order to
prevent blocking of other parts of Home Assistant the architecture
launches a polling loop in a thread.
When new data is received, sensors are updated, and hass is
signaled that there are updates. Sensors are not created until the
first update, which will be 60 - 120 seconds after the platform
starts.
"""
2018-10-29 14:49:57 +00:00
def __init__(self, client, hass, hass_config):
2018-01-15 20:50:56 +00:00
"""Initialize MyChevy Hub."""
super().__init__()
self._client = client
self.hass = hass
2018-10-29 14:49:57 +00:00
self.hass_config = hass_config
self.cars = []
2018-01-15 20:50:56 +00:00
self.status = None
self.ready = False
2018-01-15 20:50:56 +00:00
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
"""Update sensors from mychevy website.
This is a synchronous polling call that takes a very long time
(like 2 to 3 minutes long time)
"""
self._client.login()
self._client.get_cars()
self.cars = self._client.cars
if self.ready is not True:
2019-07-31 19:25:30 +00:00
discovery.load_platform(self.hass, "sensor", DOMAIN, {}, self.hass_config)
discovery.load_platform(
self.hass, "binary_sensor", DOMAIN, {}, self.hass_config
)
self.ready = True
self.cars = self._client.update_cars()
def get_car(self, vid):
"""Compatibility to work with one car."""
if self.cars:
for car in self.cars:
if car.vid == vid:
return car
return None
2018-01-15 20:50:56 +00:00
def run(self):
"""Thread run loop."""
# We add the status device first outside of the loop
# And then busy wait on threads
while True:
try:
_LOGGER.info("Starting mychevy loop")
self.update()
self.hass.helpers.dispatcher.dispatcher_send(UPDATE_TOPIC)
time.sleep(MIN_TIME_BETWEEN_UPDATES.seconds)
except Exception: # pylint: disable=broad-except
_LOGGER.exception(
"Error updating mychevy data. "
2019-07-31 19:25:30 +00:00
"This probably means the OnStar link is down again"
)
2018-01-15 20:50:56 +00:00
self.hass.helpers.dispatcher.dispatcher_send(ERROR_TOPIC)
time.sleep(ERROR_SLEEP_TIME.seconds)