Phil Cole 656d39e3ec Nissan Leaf Integration (Carwings / NissanConnect EV) (#19786)
* Added work so far.

* Change interval so nobody drains their battery when I put this online

* Added the warning notice.

* Async setup

* Still broken, but we're getting there.

* Back to synchronous, moved refresh stuff into DataStore

* Functional sensors!

* Added working switches, tweaked intervals a bit

* Fixed turn off result

* Moved plug status to binary_sensor, added smart intervals

* Documentation and car nickname stuff

* Syntax fixes and coveragerc additions

* Style fixes

* Fixing the final line length

* Fixed an issue with newer models and bad climate data

* Forgot to check my line endings.

* New icons for most of the components

* Hotfix for handling Nissan's awful servers

* Merge in fixes made by Phil Cole

Remove invalid FIXMEs and update TODOs
Fixes for pylint and test for CarwingsError exception rather than Exception
Flake8 fixes
Add pycarwings2 to requirements_all.txt
Add extra configuration documentation.
Use pycarwings2 from pip. Check server dates between requests.
Add sensor device class for battery.
Async conversion fixes
flake8 fixes and docstrings
Non-async charging is OK
Handle multiple cars in the configuration
Convert to async. Better imports for platforms
Fix scanning interval & prevent extra refreshes.  async switchover
Check discovery_info to prevent load of platforms
Ensure update frequency is always above a minimum interval (1 min).
Platforms don't have return values
Use values() instead of items() when not using key
Use snake_case (LeafCore becomes leaf_core)

commit 418b6bbcc49cf2909aac85869440435410abf3fd

* Add pycarwings2 to requirements_all.txt

* Make stopping charge error an 'info'. Remove TODO.

* Request update from car after sending start charging command.

* Delay initial (slow) update for 15 seconds and make async

* Flake8 line length fixes

* Try to fix D401 'imperative mood' git diff tox errors

* Try to fix more D401 'imperative mood' tox errors

* Default interval of an hour in code, to match comments.

* Update to pycarwings2 2.3

* Update to pycarwings2 2.3 in requirements_all.txt

* Remove documentation, instead refering to home-assistant.io

* Remove unneeded dispatcher_send()

* Remove unneeded requirements comments

* Combine excess debugging.

* Remove single line method signal_components()

* Bump to version 2.4 of pycarwings2

* Remove unused dispatcher_send

* Simplify logging of LeafEntity registration

* Update requirements_all.txt

* Multiple changes

Increase timeout to 30 seconds
Only consider battery_status
Fix plugged in status
Better attempts at try/exception handling

* Fix line length

* Use pycarwings 2.5

* Remove pointless 'is True'

* Remove unnecessary 'is True/False'

* Remove unnecessary 'is True/False'

* Use LENGTH_MILES and LENGTH_KILOMETERS

* Remove excess logging in setup_platform()

* Remove unnecessary 'is True'

* Use pycarwings2 version 2.6

* Require pycarwings2 version 2.7.

* Increase sleep delay for climate and location reponses.

* Remove unnecessary 'is True'

* Increase frequent polling warning to _LOGGER.warning()

* Use DEVICE_CLASS_BATTERY

* Remove extraneous 'is True'.

* Move icon strings to constants.

* Remove unneeded key.

* LeafRangeSensor ac_on property is internal.

* Flake8 missing line

* Remove homebridge attributes.

* Remove round battery % and range to whole numbers

* Use pycarwings2 2.8

* Move to embedded component model

* Reduce maximum attempts to 10 (5 mins)

* Include attempt count in 'waiting' log message

* Use await instead of yield. Remove @asyncio.coroutine decorators.

* Add @filcole as nissan_leaf codeowner

* Fix checking for if not data returned from vehicle. Don't double send signal on location update.

* Exposed updated_on, update_in_progress and next_update attributes.

* Add nissan_leaf.update service that triggers an update.

* Flake8 line fixes

* Remove excess and double logging.

* Add updated_on attribute for device tracker.

* Fix crash if pycarwings2 doesn't provide cruising ranges.

* Minor changes

* Minor changes

* Minor changes

* Minor changes

* Minor changes
2019-02-15 14:35:25 +01:00

114 lines
3.3 KiB
Python

"""Battery Charge and Range Support for the Nissan Leaf."""
import logging
from homeassistant.components.nissan_leaf import (
DATA_BATTERY, DATA_CHARGING, DATA_LEAF, DATA_RANGE_AC, DATA_RANGE_AC_OFF,
LeafEntity)
from homeassistant.const import DEVICE_CLASS_BATTERY
from homeassistant.helpers.icon import icon_for_battery_level
from homeassistant.util.distance import LENGTH_KILOMETERS, LENGTH_MILES
from homeassistant.util.unit_system import IMPERIAL_SYSTEM, METRIC_SYSTEM
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['nissan_leaf']
ICON_RANGE = 'mdi:speedometer'
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Sensors setup."""
_LOGGER.debug("setup_platform nissan_leaf sensors, discovery_info=%s",
discovery_info)
devices = []
for key, value in hass.data[DATA_LEAF].items():
_LOGGER.debug("adding sensor for item key=%s, value=%s", key, value)
devices.append(LeafBatterySensor(value))
devices.append(LeafRangeSensor(value, True))
devices.append(LeafRangeSensor(value, False))
add_devices(devices, True)
class LeafBatterySensor(LeafEntity):
"""Nissan Leaf Battery Sensor."""
@property
def name(self):
"""Sensor Name."""
return self.car.leaf.nickname + " Charge"
@property
def device_class(self):
"""Return the device class of the sensor."""
return DEVICE_CLASS_BATTERY
@property
def state(self):
"""Battery state percentage."""
return round(self.car.data[DATA_BATTERY])
@property
def unit_of_measurement(self):
"""Battery state measured in percentage."""
return '%'
@property
def icon(self):
"""Battery state icon handling."""
chargestate = self.car.data[DATA_CHARGING]
return icon_for_battery_level(
battery_level=self.state,
charging=chargestate
)
class LeafRangeSensor(LeafEntity):
"""Nissan Leaf Range Sensor."""
def __init__(self, car, ac_on):
"""Set-up range sensor. Store if AC on."""
self._ac_on = ac_on
super().__init__(car)
@property
def name(self):
"""Update sensor name depending on AC."""
if self._ac_on is True:
return self.car.leaf.nickname + " Range (AC)"
return self.car.leaf.nickname + " Range"
def log_registration(self):
"""Log registration."""
_LOGGER.debug(
"Registered LeafRangeSensor component with HASS for VIN %s",
self.car.leaf.vin)
@property
def state(self):
"""Battery range in miles or kms."""
if self._ac_on:
ret = self.car.data[DATA_RANGE_AC]
else:
ret = self.car.data[DATA_RANGE_AC_OFF]
if (not self.car.hass.config.units.is_metric or
self.car.force_miles):
ret = IMPERIAL_SYSTEM.length(ret, METRIC_SYSTEM.length_unit)
return round(ret)
@property
def unit_of_measurement(self):
"""Battery range unit."""
if (not self.car.hass.config.units.is_metric or
self.car.force_miles):
return LENGTH_MILES
return LENGTH_KILOMETERS
@property
def icon(self):
"""Nice icon for range."""
return ICON_RANGE