Skip to content

Commit

Permalink
Fix for login #17 and range sensors #15
Browse files Browse the repository at this point in the history
  • Loading branch information
Farfar committed Jan 31, 2022
1 parent c74bcfa commit 6a09d76
Show file tree
Hide file tree
Showing 10 changed files with 132 additions and 87 deletions.
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

# Seat Connect - A Home Assistant custom component for Seat Connect

# v1.1.1
## This is fork of [lendy007/homeassistant-skodaconnect](https://github.com/lendy007/homeassistant-skodaconnect) modified to support Seat
This integration for Home Assistant will fetch data from VAG connect servers related to your Seat Connect enabled car.
Seat Connect never fetch data directly from car, the car sends updated data to VAG servers on specific events such as lock/unlock, charging events, climatisation events and when vehicle is parked. The integration will then fetch this data from the servers.
Expand Down
100 changes: 68 additions & 32 deletions custom_components/seatconnect/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@
CONF_NAME,
CONF_PASSWORD,
CONF_RESOURCES,
CONF_SCAN_INTERVAL,
CONF_USERNAME, EVENT_HOMEASSISTANT_STOP,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers import config_validation as cv, device_registry
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.dispatcher import async_dispatcher_connect
Expand Down Expand Up @@ -48,13 +49,12 @@
CONF_SCANDINAVIAN_MILES,
CONF_SPIN,
CONF_VEHICLE,
CONF_UPDATE_INTERVAL,
CONF_INSTRUMENTS,
DATA,
DATA_KEY,
DEFAULT_UPDATE_INTERVAL,
MIN_SCAN_INTERVAL,
DEFAULT_SCAN_INTERVAL,
DOMAIN,
MIN_UPDATE_INTERVAL,
SIGNAL_STATE_UPDATED,
UNDO_UPDATE_LISTENER, UPDATE_CALLBACK, CONF_DEBUG, DEFAULT_DEBUG, CONF_CONVERT, CONF_NO_CONVERSION,
CONF_IMPERIAL_UNITS,
Expand Down Expand Up @@ -127,23 +127,29 @@

async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
"""Setup Seat Connect component from a config entry."""
_LOGGER.debug(f'Init async_setup_entry')
hass.data.setdefault(DOMAIN, {})

if entry.options.get(CONF_UPDATE_INTERVAL):
update_interval = timedelta(minutes=entry.options[CONF_UPDATE_INTERVAL])
if entry.options.get(CONF_SCAN_INTERVAL):
update_interval = timedelta(seconds=entry.options[CONF_SCAN_INTERVAL])
else:
update_interval = timedelta(minutes=DEFAULT_UPDATE_INTERVAL)
update_interval = timedelta(seconds=DEFAULT_SCAN_INTERVAL)
if update_interval < timedelta(seconds=MIN_SCAN_INTERVAL):
update_interval = timedelta(seconds=MIN_SCAN_INTERVAL)

coordinator = SeatCoordinator(hass, entry, update_interval)

if not await coordinator.async_login():
await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_REAUTH},
data=entry,
)
return False
try:
if not await coordinator.async_login():
await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_REAUTH},
data=entry,
)
return False
except (SeatAuthenticationException, SeatAccountLockedException, SeatLoginFailedException) as e:
raise ConfigEntryAuthFailed(e) from e
except Exception as e:
raise ConfigEntryNotReady(e) from e

entry.async_on_unload(
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, coordinator.async_logout)
Expand Down Expand Up @@ -325,8 +331,8 @@ async def set_schedule(service_call=None):
if service_call.data.get("charge_current", None) is not None:
schedule["chargeMaxCurrent"] = service_call.data.get("charge_current")
# Global optional options
if service_call.data.get("target_temp", None) is not None:
schedule["targetTemp"] = service_call.data.get("target_temp")
if service_call.data.get("temp", None) is not None:
schedule["targetTemp"] = service_call.data.get("temp")

# Find the correct car and execute service call
car = await get_car(service_call)
Expand Down Expand Up @@ -529,6 +535,28 @@ def get_convert_conf(entry: ConfigEntry):
)
) else CONF_NO_CONVERSION

async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry):
"""Migrate configuration from old version to new."""
_LOGGER.debug(f'Migrating from version {entry.version}')

# Migrate data from version 1
if entry.version == 1:
# Make a copy of old config
new = {**entry.data}

# Convert from minutes to seconds for poll interval
minutes = entry.options.get("update_interval", 1)
seconds = minutes*60
new.pop("update_interval", None)
new[CONF_SCAN_INTERVAL] = seconds

# Save "new" config
entry.data = {**new}

entry.version = 2

_LOGGER.info("Migration to version %s successful", entry.version)
return True

class SeatData:
"""Hold component state."""
Expand Down Expand Up @@ -634,8 +662,7 @@ def icon(self):
return icon_for_battery_level(
battery_level=self.instrument.state, charging=self.vehicle.charging
)
else:
return self.instrument.icon
return self.instrument.icon

@property
def vehicle(self):
Expand Down Expand Up @@ -675,8 +702,11 @@ def device_state_attributes(self):

# Return model image as picture attribute for position entity
if "position" in self.attribute:
if self.vehicle.is_model_image_supported:
attributes["entity_picture"] = self.vehicle.model_image
# Try to use small thumbnail first hand, else fallback to fullsize
if self.vehicle.is_model_image_small_supported:
attributes["entity_picture"] = self.vehicle.model_image_small
elif self.vehicle.is_model_image_large_supported:
attributes["entity_picture"] = self.vehicle.model_image_large

return attributes

Expand Down Expand Up @@ -762,14 +792,20 @@ async def async_logout(self, event=None):
async def async_login(self):
"""Login to Seat Connect"""
# Check if we can login
if await self.connection.doLogin() is False:
_LOGGER.warning(
"Could not login to Seat Connect, please check your credentials and verify that the service is working"
)
return False
# Get associated vehicles before we continue
await self.connection.get_vehicles()
return True
try:
if await self.connection.doLogin() is False:
_LOGGER.warning(
"Could not login to Seat Connect, please check your credentials and verify that the service is working"
)
return False
# Get associated vehicles before we continue
await self.connection.get_vehicles()
return True
except (SeatAccountLockedException, SeatAuthenticationException) as e:
# Raise auth failed error in config flow
raise ConfigEntryAuthFailed(e) from e
except:
raise

async def update(self) -> Union[bool, Vehicle]:
"""Update status from Seat Connect"""
Expand All @@ -779,11 +815,11 @@ async def update(self) -> Union[bool, Vehicle]:
try:
# Get Vehicle object matching VIN number
vehicle = self.connection.vehicle(self.vin)
if not await vehicle.update():
if await vehicle.update():
return vehicle
else:
_LOGGER.warning("Could not query update from Seat Connect")
return False
else:
return vehicle
except Exception as error:
_LOGGER.warning(f"An error occured while requesting update from Seat Connect: {error}")
return False
44 changes: 27 additions & 17 deletions custom_components/seatconnect/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
CONF_PASSWORD,
CONF_RESOURCES,
CONF_USERNAME,
CONF_SCAN_INTERVAL
)
from homeassistant.core import callback
from homeassistant.helpers.aiohttp_client import async_get_clientsession
Expand All @@ -21,19 +22,19 @@
CONF_DEBUG,
CONVERT_DICT,
CONF_MUTABLE,
CONF_UPDATE_INTERVAL,
CONF_SPIN,
CONF_VEHICLE,
CONF_INSTRUMENTS,
DEFAULT_UPDATE_INTERVAL,
MIN_SCAN_INTERVAL,
DEFAULT_SCAN_INTERVAL,
DOMAIN,
DEFAULT_DEBUG
)

_LOGGER = logging.getLogger(__name__)

class SeatConnectConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
VERSION = 1
VERSION = 2
task_login = None
task_finish = None
task_get_vehicles = None
Expand Down Expand Up @@ -65,7 +66,7 @@ async def async_step_user(self, user_input=None):
self._options = {
CONF_CONVERT: CONF_NO_CONVERSION,
CONF_MUTABLE: True,
CONF_UPDATE_INTERVAL: 5,
CONF_SCAN_INTERVAL: DEFAULT_SCAN_INTERVAL,
CONF_DEBUG: False,
CONF_SPIN: None,
CONF_RESOURCES: []
Expand Down Expand Up @@ -142,7 +143,7 @@ async def async_step_monitoring(self, user_input=None):
if user_input is not None:
self._options[CONF_RESOURCES] = user_input[CONF_RESOURCES]
self._options[CONF_CONVERT] = user_input[CONF_CONVERT]
self._options[CONF_UPDATE_INTERVAL] = user_input[CONF_UPDATE_INTERVAL]
self._options[CONF_SCAN_INTERVAL] = user_input[CONF_SCAN_INTERVAL]
self._options[CONF_DEBUG] = user_input[CONF_DEBUG]

await self.async_set_unique_id(self._data[CONF_VEHICLE])
Expand Down Expand Up @@ -172,8 +173,11 @@ async def async_step_monitoring(self, user_input=None):
CONF_CONVERT, default=CONF_NO_CONVERSION
): vol.In(CONVERT_DICT),
vol.Required(
CONF_UPDATE_INTERVAL, default=1
): cv.positive_int,
CONF_SCAN_INTERVAL, default=DEFAULT_SCAN_INTERVAL
): vol.All(
vol.Coerce(int),
vol.Range(min=MIN_SCAN_INTERVAL, max=900)
),
vol.Required(
CONF_DEBUG, default=False
): cv.boolean
Expand Down Expand Up @@ -253,9 +257,7 @@ async def async_step_reauth_confirm(self, user_input: dict = None) -> dict:

# noinspection PyBroadException
try:
await self._connection.doLogin()

if not await self._connection.validate_login:
if not await self._connection.doLogin():
_LOGGER.debug("Unable to login to Seat Connect. Need to accept a new EULA/T&C? Try logging in to the portal: https://my.seat/portal/")
errors["base"] = "cannot_connect"
else:
Expand Down Expand Up @@ -300,7 +302,7 @@ async def async_step_import(self, yaml):
self._options = {
CONF_CONVERT: CONF_NO_CONVERSION,
CONF_MUTABLE: True,
CONF_UPDATE_INTERVAL: 5,
CONF_SCAN_INTERVAL: DEFAULT_SCAN_INTERVAL,
CONF_DEBUG: False,
CONF_SPIN: None,
CONF_RESOURCES: []
Expand All @@ -323,8 +325,13 @@ async def async_step_import(self, yaml):
if yaml["scandinavian_miles"]:
self._options[CONF_CONVERT] = "scandinavian_miles"
if "scan_interval" in yaml:
seconds = 60
minutes = 0
if "seconds" in yaml["scan_interval"]:
seconds = int(yaml["scan_interval"]["seconds"])
if "minutes" in yaml["scan_interval"]:
self._options[CONF_UPDATE_INTERVAL] = int(yaml["scan_interval"]["minutes"])
minutes = int(yaml["scan_interval"]["minutes"])
self._options[CONF_SCAN_INTERVAL] = seconds+(minutes*60)
if "name" in yaml:
vin = next(iter(yaml["name"]))
self._data[CONF_VEHICLE] = vin.upper()
Expand Down Expand Up @@ -410,7 +417,7 @@ async def async_step_user(self, user_input=None):
self.hass.config_entries.async_update_entry(self._config_entry, data={**data})

options = self._config_entry.options.copy()
options[CONF_UPDATE_INTERVAL] = user_input.get(CONF_UPDATE_INTERVAL, 1)
options[CONF_SCAN_INTERVAL] = user_input.get(CONF_SCAN_INTERVAL, 1)
options[CONF_SPIN] = user_input.get(CONF_SPIN, None)
options[CONF_MUTABLE] = user_input.get(CONF_MUTABLE, True)
options[CONF_DEBUG] = user_input.get(CONF_DEBUG, False)
Expand Down Expand Up @@ -445,11 +452,14 @@ async def async_step_user(self, user_input=None):
data_schema=vol.Schema(
{
vol.Optional(
CONF_UPDATE_INTERVAL,
default=self._config_entry.options.get(CONF_UPDATE_INTERVAL,
self._config_entry.data.get(CONF_UPDATE_INTERVAL, 5)
CONF_SCAN_INTERVAL,
default=self._config_entry.options.get(CONF_SCAN_INTERVAL,
self._config_entry.data.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL)
)
): cv.positive_int,
): vol.All(
vol.Coerce(int),
vol.Range(min=MIN_SCAN_INTERVAL, max=900)
),
vol.Optional(
CONF_SPIN,
default=self._config_entry.options.get(CONF_SPIN,
Expand Down
5 changes: 2 additions & 3 deletions custom_components/seatconnect/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
CONF_CONVERT = "convert"
CONF_VEHICLE = "vehicle"
CONF_INSTRUMENTS = "instruments"
CONF_UPDATE_INTERVAL = "update_interval"
CONF_DEBUG = "debug"

# Service definitions
Expand All @@ -29,8 +28,8 @@

SIGNAL_STATE_UPDATED = f"{DOMAIN}.updated"

MIN_UPDATE_INTERVAL = timedelta(minutes=1)
DEFAULT_UPDATE_INTERVAL = 5
MIN_SCAN_INTERVAL = 10
DEFAULT_SCAN_INTERVAL = 60

CONVERT_DICT = {
CONF_NO_CONVERSION: "No conversion",
Expand Down
5 changes: 5 additions & 0 deletions custom_components/seatconnect/device_tracker.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@ def source_type(self):
"""Return the source type, eg gps or router, of the device."""
return SOURCE_TYPE_GPS

@property
def force_update(self):
"""All updates do not need to be written to the state machine."""
return False

@property
def icon(self):
"""Return the icon."""
Expand Down
6 changes: 3 additions & 3 deletions custom_components/seatconnect/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
"config_flow": true,
"codeowners": ["@Farfar"],
"requirements": [
"seatconnect>=1.1.3",
"homeassistant>=2021.06.0"
"seatconnect>=1.1.4",
"homeassistant>=2021.12.0"
],
"version": "v1.1.1",
"version": "v1.1.2",
"iot_class": "cloud_polling"
}
20 changes: 20 additions & 0 deletions custom_components/seatconnect/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from . import DATA_KEY, DOMAIN, SeatEntity
from .const import DATA
from homeassistant.components.sensor import DEVICE_CLASSES, SensorEntity
from homeassistant.const import CONF_RESOURCES

_LOGGER = logging.getLogger(__name__)
Expand Down Expand Up @@ -52,3 +53,22 @@ def state(self):
def unit_of_measurement(self):
"""Return the unit of measurement."""
return self.instrument.unit

@property
def device_class(self):
"""Return the class of this sensor, from DEVICE_CLASSES."""
if self.instrument.device_class in DEVICE_CLASSES:
return self.instrument.device_class
return None

@property
def state_class(self):
"""Return the state_class for the sensor, to enable statistics"""
state_class = None
if self.instrument.attr in [
'battery_level', 'adblue_level', 'fuel_level', 'charging_time_left', 'charging_power', 'charge_rate',
'electric_range', 'combustion_range', 'combined_range', 'outside_temperature'
]:
state_class = "measurement"
return state_class

Loading

0 comments on commit 6a09d76

Please sign in to comment.