From eca6bc6a73258cd9f0d56385a7d0c2ab6a8920b7 Mon Sep 17 00:00:00 2001 From: Nikolay Vasilchuk Date: Wed, 13 Jan 2021 22:44:24 +0300 Subject: [PATCH] Starline OBD information (#37608) * Starline OBD data * Small fix * Review (comments) * Review (service description) * Review (service method) * starline updated to 0.1.5 * Small typo fix --- homeassistant/components/starline/__init__.py | 35 ++++++++++++++++- homeassistant/components/starline/account.py | 38 +++++++++++++++++-- homeassistant/components/starline/const.py | 3 ++ .../components/starline/manifest.json | 2 +- homeassistant/components/starline/sensor.py | 25 +++++++++++- .../components/starline/services.yaml | 7 ++++ requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 8 files changed, 105 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/starline/__init__.py b/homeassistant/components/starline/__init__.py index 303507b1491..392dbff9e03 100644 --- a/homeassistant/components/starline/__init__.py +++ b/homeassistant/components/starline/__init__.py @@ -8,10 +8,13 @@ from homeassistant.exceptions import ConfigEntryNotReady from .account import StarlineAccount from .const import ( CONF_SCAN_INTERVAL, + CONF_SCAN_OBD_INTERVAL, DEFAULT_SCAN_INTERVAL, + DEFAULT_SCAN_OBD_INTERVAL, DOMAIN, PLATFORMS, SERVICE_SET_SCAN_INTERVAL, + SERVICE_SET_SCAN_OBD_INTERVAL, SERVICE_UPDATE_STATE, ) @@ -25,6 +28,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b """Set up the StarLine device from a config entry.""" account = StarlineAccount(hass, config_entry) await account.update() + await account.update_obd() if not account.api.available: raise ConfigEntryNotReady @@ -44,12 +48,23 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b ) async def async_set_scan_interval(call): - """Service for set scan interval.""" + """Set scan interval.""" options = dict(config_entry.options) options[CONF_SCAN_INTERVAL] = call.data[CONF_SCAN_INTERVAL] hass.config_entries.async_update_entry(entry=config_entry, options=options) - hass.services.async_register(DOMAIN, SERVICE_UPDATE_STATE, account.update) + async def async_set_scan_obd_interval(call): + """Set OBD info scan interval.""" + options = dict(config_entry.options) + options[CONF_SCAN_OBD_INTERVAL] = call.data[CONF_SCAN_INTERVAL] + hass.config_entries.async_update_entry(entry=config_entry, options=options) + + async def async_update(call=None): + """Update all data.""" + await account.update() + await account.update_obd() + + hass.services.async_register(DOMAIN, SERVICE_UPDATE_STATE, async_update) hass.services.async_register( DOMAIN, SERVICE_SET_SCAN_INTERVAL, @@ -62,6 +77,18 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b } ), ) + hass.services.async_register( + DOMAIN, + SERVICE_SET_SCAN_OBD_INTERVAL, + async_set_scan_obd_interval, + schema=vol.Schema( + { + vol.Required(CONF_SCAN_INTERVAL): vol.All( + vol.Coerce(int), vol.Range(min=180) + ) + } + ), + ) config_entry.add_update_listener(async_options_updated) await async_options_updated(hass, config_entry) @@ -83,4 +110,8 @@ async def async_options_updated(hass: HomeAssistant, config_entry: ConfigEntry) """Triggered by config entry options updates.""" account: StarlineAccount = hass.data[DOMAIN][config_entry.entry_id] scan_interval = config_entry.options.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL) + scan_obd_interval = config_entry.options.get( + CONF_SCAN_OBD_INTERVAL, DEFAULT_SCAN_OBD_INTERVAL + ) account.set_update_interval(scan_interval) + account.set_update_obd_interval(scan_obd_interval) diff --git a/homeassistant/components/starline/account.py b/homeassistant/components/starline/account.py index 1cedcef8e84..7452253019b 100644 --- a/homeassistant/components/starline/account.py +++ b/homeassistant/components/starline/account.py @@ -15,6 +15,7 @@ from .const import ( DATA_SLNET_TOKEN, DATA_USER_ID, DEFAULT_SCAN_INTERVAL, + DEFAULT_SCAN_OBD_INTERVAL, DOMAIN, ) @@ -27,17 +28,19 @@ class StarlineAccount: self._hass: HomeAssistant = hass self._config_entry: ConfigEntry = config_entry self._update_interval: int = DEFAULT_SCAN_INTERVAL + self._update_obd_interval: int = DEFAULT_SCAN_OBD_INTERVAL self._unsubscribe_auto_updater: Optional[Callable] = None + self._unsubscribe_auto_obd_updater: Optional[Callable] = None self._api: StarlineApi = StarlineApi( config_entry.data[DATA_USER_ID], config_entry.data[DATA_SLNET_TOKEN] ) - def _check_slnet_token(self) -> None: + def _check_slnet_token(self, interval: int) -> None: """Check SLNet token expiration and update if needed.""" now = datetime.now().timestamp() slnet_token_expires = self._config_entry.data[DATA_EXPIRES] - if now + self._update_interval > slnet_token_expires: + if now + interval > slnet_token_expires: self._update_slnet_token() def _update_slnet_token(self) -> None: @@ -64,9 +67,14 @@ class StarlineAccount: def _update_data(self): """Update StarLine data.""" - self._check_slnet_token() + self._check_slnet_token(self._update_interval) self._api.update() + def _update_obd_data(self): + """Update StarLine OBD data.""" + self._check_slnet_token(self._update_obd_interval) + self._api.update_obd() + @property def api(self) -> StarlineApi: """Return the instance of the API.""" @@ -76,6 +84,10 @@ class StarlineAccount: """Update StarLine data.""" await self._hass.async_add_executor_job(self._update_data) + async def update_obd(self, unused=None): + """Update StarLine OBD data.""" + await self._hass.async_add_executor_job(self._update_obd_data) + def set_update_interval(self, interval: int) -> None: """Set StarLine API update interval.""" _LOGGER.debug("Setting update interval: %ds", interval) @@ -88,12 +100,27 @@ class StarlineAccount: self._hass, self.update, delta ) + def set_update_obd_interval(self, interval: int) -> None: + """Set StarLine API OBD update interval.""" + _LOGGER.debug("Setting OBD update interval: %ds", interval) + self._update_obd_interval = interval + if self._unsubscribe_auto_obd_updater is not None: + self._unsubscribe_auto_obd_updater() + + delta = timedelta(seconds=interval) + self._unsubscribe_auto_obd_updater = async_track_time_interval( + self._hass, self.update_obd, delta + ) + def unload(self): """Unload StarLine API.""" _LOGGER.debug("Unloading StarLine API.") if self._unsubscribe_auto_updater is not None: self._unsubscribe_auto_updater() self._unsubscribe_auto_updater = None + if self._unsubscribe_auto_obd_updater is not None: + self._unsubscribe_auto_obd_updater() + self._unsubscribe_auto_obd_updater = None @staticmethod def device_info(device: StarlineDevice) -> Dict[str, Any]: @@ -140,3 +167,8 @@ class StarlineAccount: "autostart": device.car_state.get("r_start"), "ignition": device.car_state.get("run"), } + + @staticmethod + def errors_attrs(device: StarlineDevice) -> Dict[str, Any]: + """Attributes for errors sensor.""" + return {"errors": device.errors.get("errors")} diff --git a/homeassistant/components/starline/const.py b/homeassistant/components/starline/const.py index 64ba8fc3d2d..89ea0873aa1 100644 --- a/homeassistant/components/starline/const.py +++ b/homeassistant/components/starline/const.py @@ -13,6 +13,8 @@ CONF_CAPTCHA_CODE = "captcha_code" CONF_SCAN_INTERVAL = "scan_interval" DEFAULT_SCAN_INTERVAL = 180 # in seconds +CONF_SCAN_OBD_INTERVAL = "scan_obd_interval" +DEFAULT_SCAN_OBD_INTERVAL = 10800 # 3 hours in seconds ERROR_AUTH_APP = "error_auth_app" ERROR_AUTH_USER = "error_auth_user" @@ -25,3 +27,4 @@ DATA_EXPIRES = "expires" SERVICE_UPDATE_STATE = "update_state" SERVICE_SET_SCAN_INTERVAL = "set_scan_interval" +SERVICE_SET_SCAN_OBD_INTERVAL = "set_scan_obd_interval" diff --git a/homeassistant/components/starline/manifest.json b/homeassistant/components/starline/manifest.json index d0cba029787..79b163ee115 100644 --- a/homeassistant/components/starline/manifest.json +++ b/homeassistant/components/starline/manifest.json @@ -3,6 +3,6 @@ "name": "StarLine", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/starline", - "requirements": ["starline==0.1.3"], + "requirements": ["starline==0.1.5"], "codeowners": ["@anonym-tsk"] } diff --git a/homeassistant/components/starline/sensor.py b/homeassistant/components/starline/sensor.py index 2de4647aa94..8aba1b54269 100644 --- a/homeassistant/components/starline/sensor.py +++ b/homeassistant/components/starline/sensor.py @@ -1,6 +1,12 @@ """Reads vehicle status from StarLine API.""" from homeassistant.components.sensor import DEVICE_CLASS_TEMPERATURE -from homeassistant.const import PERCENTAGE, TEMP_CELSIUS, VOLT +from homeassistant.const import ( + LENGTH_KILOMETERS, + PERCENTAGE, + TEMP_CELSIUS, + VOLT, + VOLUME_LITERS, +) from homeassistant.helpers.entity import Entity from homeassistant.helpers.icon import icon_for_battery_level, icon_for_signal_level @@ -14,6 +20,9 @@ SENSOR_TYPES = { "ctemp": ["Interior Temperature", DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS, None], "etemp": ["Engine Temperature", DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS, None], "gsm_lvl": ["GSM Signal", None, PERCENTAGE, None], + "fuel": ["Fuel Volume", None, None, "mdi:fuel"], + "errors": ["OBD Errors", None, None, "mdi:alert-octagon"], + "mileage": ["Mileage", None, LENGTH_KILOMETERS, "mdi:counter"], } @@ -73,6 +82,12 @@ class StarlineSensor(StarlineEntity, Entity): return self._device.temp_engine if self._key == "gsm_lvl": return self._device.gsm_level_percent + if self._key == "fuel" and self._device.fuel: + return self._device.fuel.get("val") + if self._key == "errors" and self._device.errors: + return self._device.errors.get("val") + if self._key == "mileage" and self._device.mileage: + return self._device.mileage.get("val") return None @property @@ -80,6 +95,12 @@ class StarlineSensor(StarlineEntity, Entity): """Get the unit of measurement.""" if self._key == "balance": return self._device.balance.get("currency") or "₽" + if self._key == "fuel": + type_value = self._device.fuel.get("type") + if type_value == "percents": + return PERCENTAGE + if type_value == "litres": + return VOLUME_LITERS return self._unit @property @@ -94,4 +115,6 @@ class StarlineSensor(StarlineEntity, Entity): return self._account.balance_attrs(self._device) if self._key == "gsm_lvl": return self._account.gsm_attrs(self._device) + if self._key == "errors": + return self._account.errors_attrs(self._device) return None diff --git a/homeassistant/components/starline/services.yaml b/homeassistant/components/starline/services.yaml index bef3a16803e..4eab51b94d7 100644 --- a/homeassistant/components/starline/services.yaml +++ b/homeassistant/components/starline/services.yaml @@ -8,3 +8,10 @@ set_scan_interval: scan_interval: description: Update frequency (in seconds). example: 180 +set_scan_obd_interval: + description: > + Set OBD info update frequency. + fields: + scan_interval: + description: Update frequency (in seconds). + example: 10800 diff --git a/requirements_all.txt b/requirements_all.txt index eebeccf5e08..6125b094d4d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2100,7 +2100,7 @@ sqlalchemy==1.3.22 srpenergy==1.3.2 # homeassistant.components.starline -starline==0.1.3 +starline==0.1.5 # homeassistant.components.starlingbank starlingbank==3.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 792b0342b21..6eb22edb1ab 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1043,7 +1043,7 @@ sqlalchemy==1.3.22 srpenergy==1.3.2 # homeassistant.components.starline -starline==0.1.3 +starline==0.1.5 # homeassistant.components.statsd statsd==3.2.1