Mopar split (#21526)
* Split out mopar into a component and sensor platform * Add the mopar switch platform * Add the mopar lock platform * Clean up and bump version * Update per review * Re-add service to trigger horn * Clean up again * Don't call async from sync context * Lint * Implement changes from review * Lint * A little more clean up
This commit is contained in:
parent
f11f5255ae
commit
c4eab21736
7 changed files with 317 additions and 115 deletions
|
@ -328,6 +328,7 @@ omit =
|
|||
homeassistant/components/mobile_app/*
|
||||
homeassistant/components/mochad/*
|
||||
homeassistant/components/modbus/*
|
||||
homeassistant/components/mopar/*
|
||||
homeassistant/components/mychevy/*
|
||||
homeassistant/components/mycroft/*
|
||||
homeassistant/components/mysensors/*
|
||||
|
@ -499,7 +500,7 @@ omit =
|
|||
homeassistant/components/miflora/sensor.py
|
||||
homeassistant/components/mitemp_bt/sensor.py
|
||||
homeassistant/components/modem_callerid/sensor.py
|
||||
homeassistant/components/mopar/sensor.py
|
||||
homeassistant/components/mopar/*
|
||||
homeassistant/components/mqtt_room/sensor.py
|
||||
homeassistant/components/mvglive/sensor.py
|
||||
homeassistant/components/nederlandse_spoorwegen/sensor.py
|
||||
|
|
|
@ -1 +1,157 @@
|
|||
"""The mopar component."""
|
||||
"""Support for Mopar vehicles."""
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.lock import DOMAIN as LOCK
|
||||
from homeassistant.components.sensor import DOMAIN as SENSOR
|
||||
from homeassistant.components.switch import DOMAIN as SWITCH
|
||||
from homeassistant.const import (
|
||||
CONF_USERNAME,
|
||||
CONF_PASSWORD,
|
||||
CONF_PIN,
|
||||
CONF_SCAN_INTERVAL
|
||||
)
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.discovery import load_platform
|
||||
from homeassistant.helpers.dispatcher import dispatcher_send
|
||||
from homeassistant.helpers.event import track_time_interval
|
||||
|
||||
REQUIREMENTS = ['motorparts==1.1.0']
|
||||
|
||||
DOMAIN = 'mopar'
|
||||
DATA_UPDATED = '{}_data_updated'.format(DOMAIN)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
COOKIE_FILE = 'mopar_cookies.pickle'
|
||||
SUCCESS_RESPONSE = 'completed'
|
||||
|
||||
SUPPORTED_PLATFORMS = [LOCK, SENSOR, SWITCH]
|
||||
|
||||
DEFAULT_INTERVAL = timedelta(days=7)
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({
|
||||
DOMAIN: vol.Schema({
|
||||
vol.Required(CONF_USERNAME): cv.string,
|
||||
vol.Required(CONF_PASSWORD): cv.string,
|
||||
vol.Required(CONF_PIN): cv.positive_int,
|
||||
vol.Optional(CONF_SCAN_INTERVAL, default=DEFAULT_INTERVAL):
|
||||
vol.All(cv.time_period, cv.positive_timedelta),
|
||||
})
|
||||
}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
SERVICE_HORN = 'sound_horn'
|
||||
ATTR_VEHICLE_INDEX = 'vehicle_index'
|
||||
SERVICE_HORN_SCHEMA = vol.Schema({
|
||||
vol.Required(ATTR_VEHICLE_INDEX): cv.positive_int
|
||||
})
|
||||
|
||||
|
||||
def setup(hass, config):
|
||||
"""Set up the Mopar component."""
|
||||
import motorparts
|
||||
|
||||
cookie = hass.config.path(COOKIE_FILE)
|
||||
try:
|
||||
session = motorparts.get_session(
|
||||
config[CONF_USERNAME],
|
||||
config[CONF_PASSWORD],
|
||||
config[CONF_PIN],
|
||||
cookie_path=cookie
|
||||
)
|
||||
except motorparts.MoparError:
|
||||
_LOGGER.error("Failed to login")
|
||||
return False
|
||||
|
||||
data = hass.data[DOMAIN] = MoparData(hass, session)
|
||||
data.update(now=None)
|
||||
|
||||
track_time_interval(
|
||||
hass, data.update, config[CONF_SCAN_INTERVAL]
|
||||
)
|
||||
|
||||
def handle_horn(call):
|
||||
"""Enable the horn on the Mopar vehicle."""
|
||||
data.actuate('horn', call.data[ATTR_VEHICLE_INDEX])
|
||||
|
||||
hass.services.register(
|
||||
DOMAIN,
|
||||
SERVICE_HORN,
|
||||
handle_horn,
|
||||
schema=SERVICE_HORN_SCHEMA
|
||||
)
|
||||
|
||||
for platform in SUPPORTED_PLATFORMS:
|
||||
load_platform(hass, platform, DOMAIN, {}, config)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class MoparData:
|
||||
"""
|
||||
Container for Mopar vehicle data.
|
||||
|
||||
Prevents session expiry re-login race condition.
|
||||
"""
|
||||
|
||||
def __init__(self, hass, session):
|
||||
"""Initialize data."""
|
||||
self._hass = hass
|
||||
self._session = session
|
||||
self.vehicles = []
|
||||
self.vhrs = {}
|
||||
self.tow_guides = {}
|
||||
|
||||
def update(self, now, **kwargs):
|
||||
"""Update data."""
|
||||
import motorparts
|
||||
|
||||
_LOGGER.debug("Updating vehicle data")
|
||||
try:
|
||||
self.vehicles = motorparts.get_summary(self._session)['vehicles']
|
||||
except motorparts.MoparError:
|
||||
_LOGGER.exception("Failed to get summary")
|
||||
return
|
||||
|
||||
for index, _ in enumerate(self.vehicles):
|
||||
try:
|
||||
self.vhrs[index] = motorparts.get_report(self._session, index)
|
||||
self.tow_guides[index] = motorparts.get_tow_guide(
|
||||
self._session, index)
|
||||
except motorparts.MoparError:
|
||||
_LOGGER.warning("Failed to update for vehicle index %s", index)
|
||||
return
|
||||
|
||||
dispatcher_send(self._hass, DATA_UPDATED)
|
||||
|
||||
@property
|
||||
def attribution(self):
|
||||
"""Get the attribution string from Mopar."""
|
||||
import motorparts
|
||||
|
||||
return motorparts.ATTRIBUTION
|
||||
|
||||
def get_vehicle_name(self, index):
|
||||
"""Get the name corresponding with this vehicle."""
|
||||
vehicle = self.vehicles[index]
|
||||
if not vehicle:
|
||||
return None
|
||||
return '{} {} {}'.format(
|
||||
vehicle['year'],
|
||||
vehicle['make'],
|
||||
vehicle['model']
|
||||
)
|
||||
|
||||
def actuate(self, command, index):
|
||||
"""Run a command on the specified Mopar vehicle."""
|
||||
import motorparts
|
||||
|
||||
try:
|
||||
response = getattr(motorparts, command)(self._session, index)
|
||||
except motorparts.MoparError as error:
|
||||
_LOGGER.error(error)
|
||||
return False
|
||||
|
||||
return response == SUCCESS_RESPONSE
|
||||
|
|
55
homeassistant/components/mopar/lock.py
Normal file
55
homeassistant/components/mopar/lock.py
Normal file
|
@ -0,0 +1,55 @@
|
|||
"""Support for the Mopar vehicle lock."""
|
||||
import logging
|
||||
|
||||
from homeassistant.components.lock import LockDevice
|
||||
from homeassistant.components.mopar import (
|
||||
DOMAIN as MOPAR_DOMAIN
|
||||
)
|
||||
from homeassistant.const import STATE_LOCKED, STATE_UNLOCKED
|
||||
|
||||
DEPENDENCIES = ['mopar']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
"""Set up the Mopar lock platform."""
|
||||
data = hass.data[MOPAR_DOMAIN]
|
||||
add_entities([MoparLock(data, index)
|
||||
for index, _ in enumerate(data.vehicles)], True)
|
||||
|
||||
|
||||
class MoparLock(LockDevice):
|
||||
"""Representation of a Mopar vehicle lock."""
|
||||
|
||||
def __init__(self, data, index):
|
||||
"""Initialize the Mopar lock."""
|
||||
self._index = index
|
||||
self._name = '{} Lock'.format(data.get_vehicle_name(self._index))
|
||||
self._actuate = data.actuate
|
||||
self._state = None
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the lock."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def is_locked(self):
|
||||
"""Return true if vehicle is locked."""
|
||||
return self._state == STATE_LOCKED
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""Return the polling requirement for this lock."""
|
||||
return False
|
||||
|
||||
def lock(self, **kwargs):
|
||||
"""Lock the vehicle."""
|
||||
if self._actuate('lock', self._index):
|
||||
self._state = STATE_LOCKED
|
||||
|
||||
def unlock(self, **kwargs):
|
||||
"""Unlock the vehicle."""
|
||||
if self._actuate('unlock', self._index):
|
||||
self._state = STATE_UNLOCKED
|
|
@ -1,108 +1,27 @@
|
|||
"""
|
||||
Sensor for Mopar vehicles.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/sensor.mopar/
|
||||
"""
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.sensor import DOMAIN, PLATFORM_SCHEMA
|
||||
"""Support for the Mopar vehicle sensor platform."""
|
||||
from homeassistant.components.mopar import (
|
||||
DOMAIN as MOPAR_DOMAIN,
|
||||
DATA_UPDATED,
|
||||
ATTR_VEHICLE_INDEX
|
||||
)
|
||||
from homeassistant.const import (
|
||||
ATTR_ATTRIBUTION, ATTR_COMMAND, CONF_PASSWORD, CONF_PIN, CONF_USERNAME,
|
||||
LENGTH_KILOMETERS)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
ATTR_ATTRIBUTION, LENGTH_KILOMETERS)
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.util import Throttle
|
||||
|
||||
REQUIREMENTS = ['motorparts==1.1.0']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
ATTR_VEHICLE_INDEX = 'vehicle_index'
|
||||
|
||||
COOKIE_FILE = 'mopar_cookies.pickle'
|
||||
|
||||
MIN_TIME_BETWEEN_UPDATES = timedelta(days=7)
|
||||
|
||||
SERVICE_REMOTE_COMMAND = 'mopar_remote_command'
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_USERNAME): cv.string,
|
||||
vol.Required(CONF_PASSWORD): cv.string,
|
||||
vol.Required(CONF_PIN): cv.positive_int,
|
||||
})
|
||||
|
||||
REMOTE_COMMAND_SCHEMA = vol.Schema({
|
||||
vol.Required(ATTR_COMMAND): cv.string,
|
||||
vol.Required(ATTR_VEHICLE_INDEX): cv.positive_int
|
||||
})
|
||||
DEPENDENCIES = ['mopar']
|
||||
ICON = 'mdi:car'
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
async def async_setup_platform(hass, config, add_entities,
|
||||
discovery_info=None):
|
||||
"""Set up the Mopar platform."""
|
||||
import motorparts
|
||||
cookie = hass.config.path(COOKIE_FILE)
|
||||
try:
|
||||
session = motorparts.get_session(
|
||||
config.get(CONF_USERNAME), config.get(CONF_PASSWORD),
|
||||
config.get(CONF_PIN), cookie_path=cookie)
|
||||
except motorparts.MoparError:
|
||||
_LOGGER.error("Failed to login")
|
||||
return
|
||||
|
||||
def _handle_service(service):
|
||||
"""Handle service call."""
|
||||
index = service.data.get(ATTR_VEHICLE_INDEX)
|
||||
command = service.data.get(ATTR_COMMAND)
|
||||
try:
|
||||
motorparts.remote_command(session, command, index)
|
||||
except motorparts.MoparError as error:
|
||||
_LOGGER.error(str(error))
|
||||
|
||||
hass.services.register(DOMAIN, SERVICE_REMOTE_COMMAND, _handle_service,
|
||||
schema=REMOTE_COMMAND_SCHEMA)
|
||||
|
||||
data = MoparData(session)
|
||||
data = hass.data[MOPAR_DOMAIN]
|
||||
add_entities([MoparSensor(data, index)
|
||||
for index, _ in enumerate(data.vehicles)], True)
|
||||
|
||||
|
||||
class MoparData:
|
||||
"""Container for Mopar vehicle data.
|
||||
|
||||
Prevents session expiry re-login race condition.
|
||||
"""
|
||||
|
||||
def __init__(self, session):
|
||||
"""Initialize data."""
|
||||
self._session = session
|
||||
self.vehicles = []
|
||||
self.vhrs = {}
|
||||
self.tow_guides = {}
|
||||
self.update()
|
||||
|
||||
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
||||
def update(self, **kwargs):
|
||||
"""Update data."""
|
||||
import motorparts
|
||||
_LOGGER.info("Updating vehicle data")
|
||||
try:
|
||||
self.vehicles = motorparts.get_summary(self._session)['vehicles']
|
||||
except motorparts.MoparError:
|
||||
_LOGGER.exception("Failed to get summary")
|
||||
return
|
||||
for index, _ in enumerate(self.vehicles):
|
||||
try:
|
||||
self.vhrs[index] = motorparts.get_report(self._session, index)
|
||||
self.tow_guides[index] = motorparts.get_tow_guide(
|
||||
self._session, index)
|
||||
except motorparts.MoparError:
|
||||
_LOGGER.warning("Failed to update for vehicle index %s", index)
|
||||
|
||||
|
||||
class MoparSensor(Entity):
|
||||
"""Mopar vehicle sensor."""
|
||||
|
||||
|
@ -114,24 +33,12 @@ class MoparSensor(Entity):
|
|||
self._tow_guide = {}
|
||||
self._odometer = None
|
||||
self._data = data
|
||||
|
||||
def update(self):
|
||||
"""Update device state."""
|
||||
self._data.update()
|
||||
self._vehicle = self._data.vehicles[self._index]
|
||||
self._vhr = self._data.vhrs.get(self._index, {})
|
||||
self._tow_guide = self._data.tow_guides.get(self._index, {})
|
||||
if 'odometer' in self._vhr:
|
||||
odo = float(self._vhr['odometer'])
|
||||
self._odometer = int(self.hass.config.units.length(
|
||||
odo, LENGTH_KILOMETERS))
|
||||
self._name = self._data.get_vehicle_name(self._index)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the sensor."""
|
||||
return '{} {} {}'.format(
|
||||
self._vehicle['year'], self._vehicle['make'],
|
||||
self._vehicle['model'])
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
|
@ -141,10 +48,9 @@ class MoparSensor(Entity):
|
|||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return the state attributes."""
|
||||
import motorparts
|
||||
attributes = {
|
||||
ATTR_VEHICLE_INDEX: self._index,
|
||||
ATTR_ATTRIBUTION: motorparts.ATTRIBUTION
|
||||
ATTR_ATTRIBUTION: self._data.attribution
|
||||
}
|
||||
attributes.update(self._vehicle)
|
||||
attributes.update(self._vhr)
|
||||
|
@ -159,4 +65,29 @@ class MoparSensor(Entity):
|
|||
@property
|
||||
def icon(self):
|
||||
"""Return the icon."""
|
||||
return 'mdi:car'
|
||||
return ICON
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""Return the polling requirement for this sensor."""
|
||||
return False
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
"""Handle entity which will be added."""
|
||||
async_dispatcher_connect(
|
||||
self.hass, DATA_UPDATED, self._schedule_immediate_update
|
||||
)
|
||||
|
||||
def update(self):
|
||||
"""Update device state."""
|
||||
self._vehicle = self._data.vehicles[self._index]
|
||||
self._vhr = self._data.vhrs.get(self._index, {})
|
||||
self._tow_guide = self._data.tow_guides.get(self._index, {})
|
||||
if 'odometer' in self._vhr:
|
||||
odo = float(self._vhr['odometer'])
|
||||
self._odometer = int(self.hass.config.units.length(
|
||||
odo, LENGTH_KILOMETERS))
|
||||
|
||||
@callback
|
||||
def _schedule_immediate_update(self):
|
||||
self.async_schedule_update_ha_state(True)
|
||||
|
|
6
homeassistant/components/mopar/services.yaml
Normal file
6
homeassistant/components/mopar/services.yaml
Normal file
|
@ -0,0 +1,6 @@
|
|||
sound_horn:
|
||||
description: Trigger the vehicle's horn
|
||||
fields:
|
||||
vehicle_index:
|
||||
description: The index of the vehicle to trigger. This is exposed in the sensor's device attributes.
|
||||
example: 1
|
53
homeassistant/components/mopar/switch.py
Normal file
53
homeassistant/components/mopar/switch.py
Normal file
|
@ -0,0 +1,53 @@
|
|||
"""Support for the Mopar vehicle switch."""
|
||||
import logging
|
||||
|
||||
from homeassistant.components.mopar import DOMAIN as MOPAR_DOMAIN
|
||||
from homeassistant.components.switch import SwitchDevice
|
||||
from homeassistant.const import STATE_ON, STATE_OFF
|
||||
|
||||
DEPENDENCIES = ['mopar']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
"""Set up the Mopar Switch platform."""
|
||||
data = hass.data[MOPAR_DOMAIN]
|
||||
add_entities([MoparSwitch(data, index)
|
||||
for index, _ in enumerate(data.vehicles)], True)
|
||||
|
||||
|
||||
class MoparSwitch(SwitchDevice):
|
||||
"""Representation of a Mopar switch."""
|
||||
|
||||
def __init__(self, data, index):
|
||||
"""Initialize the Switch."""
|
||||
self._index = index
|
||||
self._name = '{} Switch'.format(data.get_vehicle_name(self._index))
|
||||
self._actuate = data.actuate
|
||||
self._state = None
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the switch."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return True if the entity is on."""
|
||||
return self._state == STATE_ON
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""Return the polling requirement for this switch."""
|
||||
return False
|
||||
|
||||
def turn_on(self, **kwargs):
|
||||
"""Turn on the Mopar Vehicle."""
|
||||
if self._actuate('engine_on', self._index):
|
||||
self._state = STATE_ON
|
||||
|
||||
def turn_off(self, **kwargs):
|
||||
"""Turn off the Mopar Vehicle."""
|
||||
if self._actuate('engine_off', self._index):
|
||||
self._state = STATE_OFF
|
|
@ -709,7 +709,7 @@ millheater==0.3.4
|
|||
# homeassistant.components.mitemp_bt.sensor
|
||||
mitemp_bt==0.0.1
|
||||
|
||||
# homeassistant.components.mopar.sensor
|
||||
# homeassistant.components.mopar
|
||||
motorparts==1.1.0
|
||||
|
||||
# homeassistant.components.tts
|
||||
|
|
Loading…
Add table
Reference in a new issue