diff --git a/.coveragerc b/.coveragerc
index 3529e7413ca..7287dcb143f 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -145,6 +145,9 @@ omit =
homeassistant/components/maxcube.py
homeassistant/components/*/maxcube.py
+ homeassistant/components/mercedesme.py
+ homeassistant/components/*/mercedesme.py
+
homeassistant/components/mochad.py
homeassistant/components/*/mochad.py
diff --git a/homeassistant/components/binary_sensor/mercedesme.py b/homeassistant/components/binary_sensor/mercedesme.py
new file mode 100755
index 00000000000..dbbe679e852
--- /dev/null
+++ b/homeassistant/components/binary_sensor/mercedesme.py
@@ -0,0 +1,100 @@
+"""
+Support for Mercedes cars with Mercedes ME.
+
+For more details about this component, please refer to the documentation at
+https://home-assistant.io/components/mercedesme/
+"""
+import logging
+import datetime
+
+from homeassistant.components.binary_sensor import (BinarySensorDevice)
+from homeassistant.components.mercedesme import (
+ DATA_MME, MercedesMeEntity, BINARY_SENSORS)
+
+DEPENDENCIES = ['mercedesme']
+
+_LOGGER = logging.getLogger(__name__)
+
+
+def setup_platform(hass, config, add_devices, discovery_info=None):
+ """Setup the sensor platform."""
+ data = hass.data[DATA_MME].data
+
+ if not data.cars:
+ _LOGGER.error("setup_platform data.cars is none")
+ return
+
+ devices = []
+ for car in data.cars:
+ for dev in BINARY_SENSORS:
+ devices.append(MercedesMEBinarySensor(
+ data, dev, dev, car["vin"], None))
+
+ add_devices(devices, True)
+
+
+class MercedesMEBinarySensor(MercedesMeEntity, BinarySensorDevice):
+ """Representation of a Sensor."""
+
+ @property
+ def is_on(self):
+ """Return the state of the binary sensor."""
+ return self._state == "On"
+
+ @property
+ def device_state_attributes(self):
+ """Return the state attributes."""
+ if self._name == "windowsClosed":
+ return {
+ "windowStatusFrontLeft": self._car["windowStatusFrontLeft"],
+ "windowStatusFrontRight": self._car["windowStatusFrontRight"],
+ "windowStatusRearLeft": self._car["windowStatusRearLeft"],
+ "windowStatusRearRight": self._car["windowStatusRearRight"],
+ "originalValue": self._car[self._name],
+ "lastUpdate": datetime.datetime.fromtimestamp(
+ self._car["lastUpdate"]).strftime('%Y-%m-%d %H:%M:%S'),
+ "car": self._car["license"]
+ }
+ elif self._name == "tireWarningLight":
+ return {
+ "frontRightTirePressureKpa":
+ self._car["frontRightTirePressureKpa"],
+ "frontLeftTirePressureKpa":
+ self._car["frontLeftTirePressureKpa"],
+ "rearRightTirePressureKpa":
+ self._car["rearRightTirePressureKpa"],
+ "rearLeftTirePressureKpa":
+ self._car["rearLeftTirePressureKpa"],
+ "originalValue": self._car[self._name],
+ "lastUpdate": datetime.datetime.fromtimestamp(
+ self._car["lastUpdate"]
+ ).strftime('%Y-%m-%d %H:%M:%S'),
+ "car": self._car["license"],
+ }
+ return {
+ "originalValue": self._car[self._name],
+ "lastUpdate": datetime.datetime.fromtimestamp(
+ self._car["lastUpdate"]).strftime('%Y-%m-%d %H:%M:%S'),
+ "car": self._car["license"]
+ }
+
+ def update(self):
+ """Fetch new state data for the sensor."""
+ _LOGGER.debug("Updating %s", self._name)
+
+ self._car = next(
+ car for car in self._data.cars if car["vin"] == self._vin)
+
+ result = False
+
+ if self._name == "windowsClosed":
+ result = bool(self._car[self._name] == "CLOSED")
+ elif self._name == "tireWarningLight":
+ result = bool(self._car[self._name] != "INACTIVE")
+ else:
+ result = self._car[self._name] is True
+
+ self._state = "On" if result else "Off"
+
+ _LOGGER.debug("Updated %s Value: %s IsOn: %s",
+ self._name, self._state, self.is_on)
diff --git a/homeassistant/components/device_tracker/mercedesme.py b/homeassistant/components/device_tracker/mercedesme.py
new file mode 100755
index 00000000000..c33cc239412
--- /dev/null
+++ b/homeassistant/components/device_tracker/mercedesme.py
@@ -0,0 +1,72 @@
+"""
+Support for Mercedes cars with Mercedes ME.
+
+For more details about this component, please refer to the documentation at
+https://home-assistant.io/components/mercedesme/
+"""
+import logging
+from datetime import timedelta
+
+from homeassistant.components.mercedesme import DATA_MME
+from homeassistant.helpers.event import track_time_interval
+from homeassistant.util import Throttle
+
+_LOGGER = logging.getLogger(__name__)
+
+DEPENDENCIES = ['mercedesme']
+
+MIN_TIME_BETWEEN_SCANS = timedelta(seconds=30)
+
+
+def setup_scanner(hass, config, see, discovery_info=None):
+ """Set up the Mercedes ME tracker."""
+ if discovery_info is None:
+ return False
+
+ data = hass.data[DATA_MME].data
+
+ if not data.cars:
+ return False
+
+ MercedesMEDeviceTracker(hass, config, see, data)
+
+ return True
+
+
+class MercedesMEDeviceTracker(object):
+ """A class representing a Mercedes ME device tracker."""
+
+ def __init__(self, hass, config, see, data):
+ """Initialize the Mercedes ME device tracker."""
+ self.hass = hass
+ self.see = see
+ self.data = data
+ self.update_info()
+
+ track_time_interval(
+ self.hass, self.update_info, MIN_TIME_BETWEEN_SCANS)
+
+ @Throttle(MIN_TIME_BETWEEN_SCANS)
+ def update_info(self, now=None):
+ """Update the device info."""
+ for device in self.data.cars:
+ _LOGGER.debug("Updating %s", device["vin"])
+ location = self.data.get_location(device["vin"])
+ if location is None:
+ return False
+ dev_id = device["vin"]
+ name = device["license"]
+
+ lat = location['positionLat']['value']
+ lon = location['positionLong']['value']
+ attrs = {
+ 'trackr_id': dev_id,
+ 'id': dev_id,
+ 'name': name
+ }
+ self.see(
+ dev_id=dev_id, host_name=name,
+ gps=(lat, lon), attributes=attrs
+ )
+
+ return True
diff --git a/homeassistant/components/mercedesme.py b/homeassistant/components/mercedesme.py
new file mode 100755
index 00000000000..0a5825bb16d
--- /dev/null
+++ b/homeassistant/components/mercedesme.py
@@ -0,0 +1,144 @@
+"""
+Support for MercedesME System.
+
+For more details about this component, please refer to the documentation at
+https://home-assistant.io/components/mercedesme/
+"""
+import asyncio
+import logging
+from datetime import timedelta
+
+import voluptuous as vol
+import homeassistant.helpers.config_validation as cv
+
+from homeassistant.const import (
+ CONF_USERNAME, CONF_PASSWORD, CONF_SCAN_INTERVAL)
+from homeassistant.helpers import discovery
+from homeassistant.helpers.dispatcher import (
+ async_dispatcher_connect, dispatcher_send)
+from homeassistant.helpers.entity import Entity
+from homeassistant.helpers.event import track_time_interval
+
+REQUIREMENTS = ['mercedesmejsonpy==0.1.2']
+
+_LOGGER = logging.getLogger(__name__)
+
+BINARY_SENSORS = [
+ 'doorsClosed',
+ 'windowsClosed',
+ 'locked',
+ 'tireWarningLight'
+]
+
+DATA_MME = 'mercedesme'
+DOMAIN = 'mercedesme'
+
+NOTIFICATION_ID = 'mercedesme_integration_notification'
+NOTIFICATION_TITLE = 'Mercedes me integration setup'
+
+SIGNAL_UPDATE_MERCEDESME = "mercedesme_update"
+
+
+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=30):
+ vol.All(cv.positive_int, vol.Clamp(min=10))
+ })
+}, extra=vol.ALLOW_EXTRA)
+
+
+def setup(hass, config):
+ """Set up MercedesMe System."""
+ from mercedesmejsonpy.controller import Controller
+ from mercedesmejsonpy import Exceptions
+
+ conf = config[DOMAIN]
+ username = conf.get(CONF_USERNAME)
+ password = conf.get(CONF_PASSWORD)
+ scan_interval = conf.get(CONF_SCAN_INTERVAL)
+
+ try:
+ mercedesme_api = Controller(username, password, scan_interval)
+ if not mercedesme_api.is_valid_session:
+ raise Exceptions.MercedesMeException(500)
+ hass.data[DATA_MME] = MercedesMeHub(mercedesme_api)
+ except Exceptions.MercedesMeException as ex:
+ if ex.code == 401:
+ hass.components.persistent_notification.create(
+ "Error:
Please check username and password."
+ "You will need to restart Home Assistant after fixing.",
+ title=NOTIFICATION_TITLE,
+ notification_id=NOTIFICATION_ID)
+ else:
+ hass.components.persistent_notification.create(
+ "Error:
Can't communicate with Mercedes me API.
"
+ "Error code: {} Reason: {}"
+ "You will need to restart Home Assistant after fixing."
+ "".format(ex.code, ex.message),
+ title=NOTIFICATION_TITLE,
+ notification_id=NOTIFICATION_ID)
+
+ _LOGGER.error("Unable to communicate with Mercedes me API: %s",
+ ex.message)
+ return False
+
+ discovery.load_platform(hass, 'sensor', DOMAIN, {}, config)
+ discovery.load_platform(hass, 'device_tracker', DOMAIN, {}, config)
+ discovery.load_platform(hass, 'binary_sensor', DOMAIN, {}, config)
+
+ def hub_refresh(event_time):
+ """Call Mercedes me API to refresh information."""
+ _LOGGER.info("Updating Mercedes me component.")
+ hass.data[DATA_MME].data.update()
+ dispatcher_send(hass, SIGNAL_UPDATE_MERCEDESME)
+
+ track_time_interval(
+ hass,
+ hub_refresh,
+ timedelta(seconds=scan_interval))
+
+ return True
+
+
+class MercedesMeHub(object):
+ """Representation of a base MercedesMe device."""
+
+ def __init__(self, data):
+ """Initialize the entity."""
+ self.data = data
+
+
+class MercedesMeEntity(Entity):
+ """Entity class for MercedesMe devices."""
+
+ def __init__(self, data, internal_name, sensor_name, vin, unit):
+ """Initialize the MercedesMe entity."""
+ self._car = None
+ self._data = data
+ self._state = False
+ self._name = sensor_name
+ self._internal_name = internal_name
+ self._unit = unit
+ self._vin = vin
+
+ @property
+ def name(self):
+ """Return the name of the sensor."""
+ return self._name
+
+ @asyncio.coroutine
+ def async_added_to_hass(self):
+ """Register callbacks."""
+ async_dispatcher_connect(
+ self.hass, SIGNAL_UPDATE_MERCEDESME, self._update_callback)
+
+ def _update_callback(self):
+ """Callback update method."""
+ self.schedule_update_ha_state(True)
+
+ @property
+ def unit_of_measurement(self):
+ """Return the unit of measurement."""
+ return self._unit
diff --git a/homeassistant/components/sensor/mercedesme.py b/homeassistant/components/sensor/mercedesme.py
new file mode 100755
index 00000000000..08183a01ba8
--- /dev/null
+++ b/homeassistant/components/sensor/mercedesme.py
@@ -0,0 +1,92 @@
+"""
+Support for Mercedes cars with Mercedes ME.
+
+For more details about this component, please refer to the documentation at
+https://home-assistant.io/components/mercedesme/
+"""
+import logging
+import datetime
+
+from homeassistant.const import LENGTH_KILOMETERS
+from homeassistant.components.mercedesme import DATA_MME, MercedesMeEntity
+
+
+DEPENDENCIES = ['mercedesme']
+
+_LOGGER = logging.getLogger(__name__)
+
+SENSOR_TYPES = {
+ 'fuelLevelPercent': ['Fuel Level', '%'],
+ 'fuelRangeKm': ['Fuel Range', LENGTH_KILOMETERS],
+ 'latestTrip': ['Latest Trip', None],
+ 'odometerKm': ['Odometer', LENGTH_KILOMETERS],
+ 'serviceIntervalDays': ['Next Service', 'days'],
+ 'doorsClosed': ['doorsClosed', None],
+}
+
+
+def setup_platform(hass, config, add_devices, discovery_info=None):
+ """Setup the sensor platform."""
+ if discovery_info is None:
+ return
+
+ data = hass.data[DATA_MME].data
+
+ if not data.cars:
+ return
+
+ devices = []
+ for car in data.cars:
+ for key, value in sorted(SENSOR_TYPES.items()):
+ devices.append(
+ MercedesMESensor(data, key, value[0], car["vin"], value[1]))
+
+ add_devices(devices, True)
+
+
+class MercedesMESensor(MercedesMeEntity):
+ """Representation of a Sensor."""
+
+ @property
+ def state(self):
+ """Return the state of the sensor."""
+ return self._state
+
+ def update(self):
+ """Get the latest data and updates the states."""
+ _LOGGER.debug("Updating %s", self._internal_name)
+
+ self._car = next(
+ car for car in self._data.cars if car["vin"] == self._vin)
+
+ if self._internal_name == "latestTrip":
+ self._state = self._car["latestTrip"]["id"]
+ else:
+ self._state = self._car[self._internal_name]
+
+ @property
+ def device_state_attributes(self):
+ """Return the state attributes."""
+ if self._internal_name == "latestTrip":
+ return {
+ "durationSeconds":
+ self._car["latestTrip"]["durationSeconds"],
+ "distanceTraveledKm":
+ self._car["latestTrip"]["distanceTraveledKm"],
+ "startedAt": datetime.datetime.fromtimestamp(
+ self._car["latestTrip"]["startedAt"]
+ ).strftime('%Y-%m-%d %H:%M:%S'),
+ "averageSpeedKmPerHr":
+ self._car["latestTrip"]["averageSpeedKmPerHr"],
+ "finished": self._car["latestTrip"]["finished"],
+ "lastUpdate": datetime.datetime.fromtimestamp(
+ self._car["lastUpdate"]
+ ).strftime('%Y-%m-%d %H:%M:%S'),
+ "car": self._car["license"]
+ }
+
+ return {
+ "lastUpdate": datetime.datetime.fromtimestamp(
+ self._car["lastUpdate"]).strftime('%Y-%m-%d %H:%M:%S'),
+ "car": self._car["license"]
+ }
diff --git a/requirements_all.txt b/requirements_all.txt
index 30aeda2b35d..20dc82cb95a 100644
--- a/requirements_all.txt
+++ b/requirements_all.txt
@@ -477,6 +477,9 @@ matrix-client==0.0.6
# homeassistant.components.maxcube
maxcube-api==0.1.0
+# homeassistant.components.mercedesme
+mercedesmejsonpy==0.1.2
+
# homeassistant.components.notify.message_bird
messagebird==1.2.0