From 99c1c9472a4448f12f7302bc1b290e4b47c5c1c3 Mon Sep 17 00:00:00 2001
From: happyleavesaoc <happyleaves.tfr@gmail.com>
Date: Thu, 31 Aug 2017 10:26:33 -0400
Subject: [PATCH] mopar sensor (#9136)

* mopar sensor

* fix doc url

* mopar review comments

* remove unneeded hass.data handling

* fix lint
---
 .coveragerc                              |   1 +
 homeassistant/components/sensor/mopar.py | 165 +++++++++++++++++++++++
 requirements_all.txt                     |   3 +
 3 files changed, 169 insertions(+)
 create mode 100644 homeassistant/components/sensor/mopar.py

diff --git a/.coveragerc b/.coveragerc
index b43688aa281..5e27aed0182 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -486,6 +486,7 @@ omit =
     homeassistant/components/sensor/metoffice.py
     homeassistant/components/sensor/miflora.py
     homeassistant/components/sensor/modem_callerid.py
+    homeassistant/components/sensor/mopar.py
     homeassistant/components/sensor/mqtt_room.py
     homeassistant/components/sensor/mvglive.py
     homeassistant/components/sensor/netdata.py
diff --git a/homeassistant/components/sensor/mopar.py b/homeassistant/components/sensor/mopar.py
new file mode 100644
index 00000000000..0184cb2afdf
--- /dev/null
+++ b/homeassistant/components/sensor/mopar.py
@@ -0,0 +1,165 @@
+"""
+Sensor for Mopar vehicles.
+
+For more details about this platform, please refer to the documentation at
+https://home-assistant.io/components/sensor.mopar/
+"""
+import logging
+from datetime import timedelta
+
+import voluptuous as vol
+
+from homeassistant.components.sensor import PLATFORM_SCHEMA
+from homeassistant.helpers.entity import Entity
+from homeassistant.const import (CONF_USERNAME, CONF_PASSWORD, CONF_PIN,
+                                 ATTR_ATTRIBUTION, ATTR_COMMAND,
+                                 LENGTH_KILOMETERS)
+from homeassistant.util import Throttle
+import homeassistant.helpers.config_validation as cv
+
+
+REQUIREMENTS = ['motorparts==1.0.0']
+
+_LOGGER = logging.getLogger(__name__)
+
+MIN_TIME_BETWEEN_UPDATES = timedelta(days=7)
+DOMAIN = 'mopar'
+ATTR_VEHICLE_INDEX = 'vehicle_index'
+SERVICE_REMOTE_COMMAND = 'remote_command'
+COOKIE_FILE = 'mopar_cookies.pickle'
+
+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
+})
+
+
+# pylint: disable=unused-argument
+def setup_platform(hass, config, add_devices, discovery_info=None):
+    """Setup 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 False
+
+    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)
+    add_devices([MoparSensor(data, index)
+                 for index, _ in enumerate(data.vehicles)],
+                True)
+    return True
+
+
+# pylint: disable=too-few-public-methods
+class MoparData(object):
+    """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 = {}
+
+    @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."""
+
+    def __init__(self, data, index):
+        """Initialize the sensor."""
+        self._index = index
+        self._vehicle = {}
+        self._vhr = {}
+        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))
+
+    @property
+    def name(self):
+        """Return the name of the sensor."""
+        return '{} {} {}'.format(self._vehicle['year'],
+                                 self._vehicle['make'],
+                                 self._vehicle['model'])
+
+    @property
+    def state(self):
+        """Return the state of the sensor."""
+        return self._odometer
+
+    @property
+    def device_state_attributes(self):
+        """Return the state attributes."""
+        import motorparts
+        attributes = {
+            ATTR_VEHICLE_INDEX: self._index,
+            ATTR_ATTRIBUTION: motorparts.ATTRIBUTION
+        }
+        attributes.update(self._vehicle)
+        attributes.update(self._vhr)
+        attributes.update(self._tow_guide)
+        return attributes
+
+    @property
+    def unit_of_measurement(self):
+        """Return the unit of measurement."""
+        return self.hass.config.units.length_unit
+
+    @property
+    def icon(self):
+        """Return the icon."""
+        return 'mdi:car'
diff --git a/requirements_all.txt b/requirements_all.txt
index c3722ec1307..2df6562cf58 100644
--- a/requirements_all.txt
+++ b/requirements_all.txt
@@ -415,6 +415,9 @@ miflora==0.1.16
 # homeassistant.components.upnp
 miniupnpc==1.9
 
+# homeassistant.components.sensor.mopar
+motorparts==1.0.0
+
 # homeassistant.components.tts
 mutagen==1.38
 
-- 
GitLab