Skip to content

Commit

Permalink
Improve multi vehicle support
Browse files Browse the repository at this point in the history
  • Loading branch information
dan-r committed Nov 11, 2024
1 parent 664b4e8 commit 166749a
Show file tree
Hide file tree
Showing 6 changed files with 46 additions and 19 deletions.
2 changes: 1 addition & 1 deletion custom_components/nissan_connect/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ async def async_setup_entry(hass, entry):

_LOGGER.debug("Finding vehicles")
for vehicle in await hass.async_add_executor_job(kamereon_session.fetch_vehicles):
await hass.async_add_executor_job(vehicle.refresh)
await hass.async_add_executor_job(vehicle.fetch_all)
if vehicle.vin not in data[DATA_VEHICLES]:
data[DATA_VEHICLES][vehicle.vin] = vehicle

Expand Down
12 changes: 8 additions & 4 deletions custom_components/nissan_connect/button.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,26 @@
"""Support for Kamereon cars."""
import logging
import asyncio

from homeassistant.components.button import ButtonEntity

from .base import KamereonEntity
from .kamereon import ChargingStatus, PluggedStatus, Feature
from .const import DOMAIN, DATA_VEHICLES, DATA_COORDINATOR_POLL, DATA_COORDINATOR_STATISTICS
from .const import DOMAIN, DATA_VEHICLES, DATA_COORDINATOR_POLL, DATA_COORDINATOR_FETCH, DATA_COORDINATOR_STATISTICS

_LOGGER = logging.getLogger(__name__)


async def async_setup_entry(hass, config, async_add_entities):
data = hass.data[DOMAIN][DATA_VEHICLES]
coordinator = hass.data[DOMAIN][DATA_COORDINATOR_POLL]
coordinator_fetch = hass.data[DOMAIN][DATA_COORDINATOR_FETCH]
stats_coordinator = hass.data[DOMAIN][DATA_COORDINATOR_STATISTICS]

entities = []

for vehicle in data:
entities.append(ForceUpdateButton(coordinator, data[vehicle], hass, stats_coordinator))
entities.append(ForceUpdateButton(coordinator_fetch, data[vehicle], hass, stats_coordinator))
if Feature.HORN_AND_LIGHTS in data[vehicle].features:
entities += [
HornLightsButtons(coordinator, data[vehicle], "flash_lights", "mdi:car-light-high", "lights"),
Expand All @@ -44,9 +46,11 @@ def icon(self):
return 'mdi:update'

async def async_press(self):
await self.coordinator.async_refresh()
await self.coordinator_statistics.async_refresh()
loop = asyncio.get_running_loop()

await loop.run_in_executor(None, self.vehicle.refresh)
await self.coordinator.async_refresh()

class HornLightsButtons(KamereonEntity, ButtonEntity):
def __init__(self, coordinator, vehicle, translation_key, icon, action):
self._attr_translation_key = translation_key
Expand Down
6 changes: 5 additions & 1 deletion custom_components/nissan_connect/climate.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,12 @@ async def _async_fetch_loop(self, target_state):

_LOGGER.debug("Beginning HVAC fetch loop")
self._loop_mutex = True

loop = asyncio.get_running_loop()

for _ in range(10):
await self._hass.data[DOMAIN][DATA_COORDINATOR_POLL].async_refresh()
await loop.run_in_executor(None, self.vehicle.refresh)
await self._hass.data[DOMAIN][DATA_COORDINATOR_FETCH].async_refresh()

# We have our update, break out
if target_state == self.vehicle.hvac_status:
Expand Down
40 changes: 31 additions & 9 deletions custom_components/nissan_connect/coordinator.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import logging

from datetime import timedelta
from time import time
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from .const import DOMAIN, DATA_VEHICLES, DEFAULT_INTERVAL_POLL, DEFAULT_INTERVAL_CHARGING, DEFAULT_INTERVAL_STATISTICS, DEFAULT_INTERVAL_FETCH, DATA_COORDINATOR_FETCH, DATA_COORDINATOR_POLL
from .kamereon import Feature, PluggedStatus, ChargingStatus, Period
Expand All @@ -25,7 +26,6 @@ async def _async_update_data(self):
try:
for vehicle in self._vehicles:
await self._hass.async_add_executor_job(self._vehicles[vehicle].fetch_all)

except BaseException:
_LOGGER.warning("Error communicating with API")
return False
Expand All @@ -49,7 +49,11 @@ def __init__(self, hass, config):
self._hass = hass
self._vehicles = hass.data[DOMAIN][DATA_VEHICLES]
self._config = config

self._pluggednotcharging = {key: 0 for key in self._vehicles}
self._intervals = {key: 0 for key in self._vehicles}
self._last_updated = {key: 0 for key in self._vehicles}
self._force_update = {key: False for key in self._vehicles}

def set_next_interval(self):
"""Calculate the next update interval."""
Expand All @@ -58,6 +62,9 @@ def set_next_interval(self):

# Get the shortest interval from all vehicles
for vehicle in self._vehicles:
# Initially set interval to default
new_interval = interval

# EV, decide which time to use based on whether we are plugged in or not
if Feature.BATTERY_STATUS in self._vehicles[vehicle].features and self._vehicles[vehicle].plugged_in == PluggedStatus.PLUGGED:
# If we are plugged in but not charging, increment a counter
Expand All @@ -68,15 +75,25 @@ def set_next_interval(self):

# If we haven't hit the counter limit, use the shorter interval
if self._pluggednotcharging[vehicle] < 5:
interval = interval_charging if interval_charging < interval else interval
new_interval = interval_charging

# Update every minute if HVAC on
if self._vehicles[vehicle].hvac_status:
interval = 1
new_interval = 1

# If the interval has changed, force next update
if new_interval != self._intervals[vehicle]:
_LOGGER.debug(f"Changing #{vehicle[-3:]} update interval to {new_interval} minutes")
self._force_update[vehicle] = True

self._intervals[vehicle] = new_interval

if interval != (self.update_interval.seconds / 60):
_LOGGER.debug(f"Changing next update interval to {interval} minutes")
self.update_interval = timedelta(minutes=interval)
# Set the coordinator to update at the shortest interval
shortest_interval = min(self._intervals.values())

if shortest_interval != (self.update_interval.seconds / 60):
_LOGGER.debug(f"Changing coordinator update interval to {shortest_interval} minutes")
self.update_interval = timedelta(minutes=shortest_interval)
self._async_unsub_refresh()
if self._listeners:
self._schedule_refresh()
Expand All @@ -85,9 +102,14 @@ async def _async_update_data(self):
"""Fetch data from API."""
try:
for vehicle in self._vehicles:
await self._hass.async_add_executor_job(self._vehicles[vehicle].refresh_location)
await self._hass.async_add_executor_job(self._vehicles[vehicle].refresh_battery_status)

time_since_updated = round((time() - self._last_updated[vehicle]) / 60)
if self._force_update[vehicle] or time_since_updated >= self._intervals[vehicle]:
_LOGGER.debug("Polling #%s as %d mins have elapsed (interval %d)", vehicle[-3:], time_since_updated, self._intervals[vehicle])
self._last_updated[vehicle] = int(time())
self._force_update[vehicle] = False
await self._hass.async_add_executor_job(self._vehicles[vehicle].refresh)
else:
_LOGGER.debug("NOT polling #%s as %d mins have elapsed (interval %d)", vehicle[-3:], time_since_updated, self._intervals[vehicle])
except BaseException:
_LOGGER.warning("Error communicating with API")
return False
Expand Down
1 change: 0 additions & 1 deletion custom_components/nissan_connect/kamereon/kamereon.py
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,6 @@ def _post(self, url, data=None, headers=None):
def refresh(self):
self.refresh_location()
self.refresh_battery_status()
self.fetch_all()

def fetch_all(self):
self.fetch_cockpit()
Expand Down
4 changes: 1 addition & 3 deletions custom_components/nissan_connect/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,9 +177,7 @@ def _handle_coordinator_update(self) -> None:
new_state = getattr(self.vehicle, "total_mileage")

# This sometimes goes backwards? So only accept a positive odometer delta
if new_state is not None and new_state > (self._state or 0):
_LOGGER.debug(f"Updating odometer state")

if new_state is not None and new_state > (self._state or 0):
self._state = new_state
self.async_write_ha_state()

Expand Down

0 comments on commit 166749a

Please sign in to comment.