From aaa00ace6fa74018cc9116f66f485e3fb2df72b7 Mon Sep 17 00:00:00 2001 From: Arjan <44190435+vingerha@users.noreply.github.com> Date: Sat, 11 Nov 2023 08:05:38 +0100 Subject: [PATCH] Align for realtime --- custom_components/gtfs2/__init__.py | 30 +++-- custom_components/gtfs2/config_flow.py | 5 +- custom_components/gtfs2/coordinator.py | 141 ++++++++++------------ custom_components/gtfs2/gtfs_helper.py | 1 + custom_components/gtfs2/gtfs_rt_helper.py | 2 +- custom_components/gtfs2/sensor.py | 49 ++------ 6 files changed, 106 insertions(+), 122 deletions(-) diff --git a/custom_components/gtfs2/__init__.py b/custom_components/gtfs2/__init__.py index fa688c5..4e71276 100644 --- a/custom_components/gtfs2/__init__.py +++ b/custom_components/gtfs2/__init__.py @@ -8,7 +8,7 @@ from datetime import timedelta from .const import DOMAIN, PLATFORMS, DEFAULT_PATH, DEFAULT_REFRESH_INTERVAL -from .coordinator import GTFSUpdateCoordinator, GTFSRealtimeUpdateCoordinator +from .coordinator import GTFSUpdateCoordinator import voluptuous as vol from .gtfs_helper import get_gtfs @@ -20,12 +20,25 @@ async def async_migrate_entry(hass, config_entry: ConfigEntry) -> bool: if config_entry.version == 1: - new = {**config_entry.data} - new['extract_from'] = 'url' - new.pop('refresh_interval') - - config_entry.version = 2 + new_data = {**config_entry.data} + new_data['extract_from'] = 'url' + new_data.pop('refresh_interval') + + new_options = {**config_entry.options} + new_options['real_time'] = False + new_options['refresh_interval'] = 15 + + config_entry.version = 3 hass.config_entries.async_update_entry(config_entry, data=new) + hass.config_entries.async_update_entry(config_entry, options=new_options) + + if config_entry.version == 2: + + new_options = {**config_entry.options} + new_options['real_time'] = False + + config_entry.version = 3 + hass.config_entries.async_update_entry(config_entry, options=new_options) _LOGGER.debug("Migration to version %s successful", config_entry.version) @@ -40,6 +53,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: #await coordinator.async_config_entry_first_refresh() + if not coordinator.last_update_success: + raise ConfigEntryNotReady + hass.data[DOMAIN][entry.entry_id] = { "coordinator": coordinator, } @@ -74,6 +90,6 @@ def update_gtfs(call): async def update_listener(hass: HomeAssistant, entry: ConfigEntry): """Handle options update.""" - hass.data[DOMAIN][entry.entry_id]['coordinator'].update_interval = timedelta(minutes=entry.options.get("refresh_interval", DEFAULT_REFRESH_INTERVAL)) + hass.data[DOMAIN][entry.entry_id]['coordinator'].update_interval = timedelta(minutes=1) return True \ No newline at end of file diff --git a/custom_components/gtfs2/config_flow.py b/custom_components/gtfs2/config_flow.py index aeef210..1d56101 100644 --- a/custom_components/gtfs2/config_flow.py +++ b/custom_components/gtfs2/config_flow.py @@ -34,7 +34,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): """Handle a config flow for GTFS.""" - VERSION = 2 + VERSION = 3 def __init__(self) -> None: """Init ConfigFlow.""" @@ -246,7 +246,6 @@ async def async_step_init( ) -> FlowResult: """Manage the options.""" if user_input is not None: - user_input['real_time'] = False if user_input['real_time']: self._user_inputs.update(user_input) _LOGGER.debug(f"GTFS Options with realtime: {self._user_inputs}") @@ -261,7 +260,7 @@ async def async_step_init( data_schema=vol.Schema( { vol.Optional("refresh_interval", default=self.config_entry.options.get("refresh_interval", DEFAULT_REFRESH_INTERVAL)): int, -# vol.Required("real_time"): vol.In({False: "No", True: "Yes"}), + vol.Required("real_time"): vol.In({False: "No", True: "Yes"}), } ), ) diff --git a/custom_components/gtfs2/coordinator.py b/custom_components/gtfs2/coordinator.py index c04f29a..8614b10 100644 --- a/custom_components/gtfs2/coordinator.py +++ b/custom_components/gtfs2/coordinator.py @@ -4,9 +4,11 @@ from datetime import timedelta import logging + from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.update_coordinator import DataUpdateCoordinator +import homeassistant.util.dt as dt_util from .const import DEFAULT_PATH, DEFAULT_REFRESH_INTERVAL from .gtfs_helper import get_gtfs, get_next_departure, check_datasource_index @@ -26,7 +28,7 @@ def __init__(self, hass: HomeAssistant, entry: ConfigEntry) -> None: hass=hass, logger=_LOGGER, name=entry.entry_id, - update_interval=timedelta(minutes=entry.options.get("refresh_interval", DEFAULT_REFRESH_INTERVAL)), + update_interval=timedelta(minutes=1), ) self.config_entry = entry self.hass = hass @@ -35,87 +37,78 @@ def __init__(self, hass: HomeAssistant, entry: ConfigEntry) -> None: self._data: dict[str, str] = {} async def _async_update_data(self) -> dict[str, str]: - """Update.""" + """Get the latest data from GTFS and GTFS relatime, depending refresh interval""" data = self.config_entry.data options = self.config_entry.options self._pygtfs = get_gtfs( self.hass, DEFAULT_PATH, data, False ) - self._data = { - "schedule": self._pygtfs, - "origin": data["origin"].split(": ")[0], - "destination": data["destination"].split(": ")[0], - "offset": data["offset"], - "include_tomorrow": data["include_tomorrow"], - "gtfs_dir": DEFAULT_PATH, - "name": data["name"], - } + previous_data = None if self.data is None else self.data.copy() + _LOGGER.debug("Previous data: %s", previous_data) - check_index = await self.hass.async_add_executor_job( - check_datasource_index, self._pygtfs - ) - - try: - self._data["next_departure"] = await self.hass.async_add_executor_job( - get_next_departure, self - ) - except Exception as ex: # pylint: disable=broad-except - _LOGGER.error("Error getting gtfs data from generic helper: %s", ex) - _LOGGER.debug("GTFS coordinator data from helper: %s", self._data["next_departure"]) - return self._data + if previous_data is not None and (previous_data["next_departure"]["gtfs_updated_at"] + timedelta(minutes=options.get("refresh_interval", DEFAULT_REFRESH_INTERVAL))) > dt_util.now().replace(tzinfo=None): + _LOGGER.debug("Do nothing") + self._data = previous_data + + if previous_data is None or (previous_data["next_departure"]["gtfs_updated_at"] + timedelta(minutes=options.get("refresh_interval", DEFAULT_REFRESH_INTERVAL))) < dt_util.now().replace(tzinfo=None): + self._data = { + "schedule": self._pygtfs, + "origin": data["origin"].split(": ")[0], + "destination": data["destination"].split(": ")[0], + "offset": data["offset"], + "include_tomorrow": data["include_tomorrow"], + "gtfs_dir": DEFAULT_PATH, + "name": data["name"], + } -class GTFSRealtimeUpdateCoordinator(DataUpdateCoordinator): - """Data update coordinator for the GTFSRT integration.""" - - config_entry: ConfigEntry - - - def __init__(self, hass: HomeAssistant, entry: ConfigEntry) -> None: - """Initialize the coordinator.""" - _LOGGER.debug("GTFS RT: coordinator init") - super().__init__( - hass=hass, - logger=_LOGGER, - name=entry.entry_id, - update_interval=timedelta(minutes=entry.options.get("refresh_interval_rt", DEFAULT_REFRESH_INTERVAL_RT)), - ) - self.config_entry = entry - self.hass = hass - self._data: dict[str, str] = {} - - async def _async_update_data(self) -> dict[str, str]: - """Update.""" - data = self.config_entry.data - options = self.config_entry.options - _LOGGER.debug("GTFS RT: coordinator async_update_data: %s", data) - _LOGGER.debug("GTFS RT: coordinator async_update_data options: %s", options) - #add real_time if setup - + check_index = await self.hass.async_add_executor_job( + check_datasource_index, self._pygtfs + ) + + try: + self._data["next_departure"] = await self.hass.async_add_executor_job( + get_next_departure, self + ) + except Exception as ex: # pylint: disable=broad-except + _LOGGER.error("Error getting gtfs data from generic helper: %s", ex) + return None + _LOGGER.debug("GTFS coordinator data from helper: %s", self._data["next_departure"]) + if "real_time" in options: - - """Initialize the info object.""" - self._trip_update_url = options["trip_update_url"] - self._vehicle_position_url = options["vehicle_position_url"] - self._route_delimiter = "-" -# if options["CONF_API_KEY"] is not None: -# self._headers = {"Authorization": options["CONF_API_KEY"]} -# elif options["CONF_X_API_KEY"] is not None: -# self._headers = {"x-api-key": options["CONF_X_API_KEY"]} -# else: -# self._headers = None - self._headers = None - self.info = {} - self._route_id = data["route"].split(": ")[0] - self._stop_id = data["origin"].split(": ")[0] - self._direction = data["direction"] - self._relative = False - #_LOGGER.debug("GTFS RT: Realtime data: %s", self._data) - self._data = await self.hass.async_add_executor_job(get_rt_route_statuses, self) - self._get_next_service = await self.hass.async_add_executor_job(get_next_services, self) - _LOGGER.debug("GTFS RT: Realtime next service: %s", self._get_next_service) + if options["real_time"]: + + """Initialize the info object.""" + self._trip_update_url = options["trip_update_url"] + self._vehicle_position_url = options["vehicle_position_url"] + self._route_delimiter = "-" + # if options["CONF_API_KEY"] is not None: + # self._headers = {"Authorization": options["CONF_API_KEY"]} + # elif options["CONF_X_API_KEY"] is not None: + # self._headers = {"x-api-key": options["CONF_X_API_KEY"]} + # else: + # self._headers = None + self._headers = None + self.info = {} + self._route_id = self._data["next_departure"]["route_id"] + self._stop_id = data["origin"].split(": ")[0] + self._direction = data["direction"] + self._relative = False + #_LOGGER.debug("GTFS RT: Realtime data: %s", self._data) + try: + self._get_rt_route_statuses = await self.hass.async_add_executor_job(get_rt_route_statuses, self) + self._get_next_service = await self.hass.async_add_executor_job(get_next_services, self) + except Exception as ex: # pylint: disable=broad-except + _LOGGER.error("Error getting gtfs realtime data: %s", ex) + self._get_next_service = "error" + else: + _LOGGER.info("GTFS RT: RealTime = false, selected in entity options") + self._get_next_service = "n.a." else: - _LOGGER.error("GTFS RT: Issue with entity options") - return "---" + _LOGGER.debug("GTFS RT: RealTime not selected in entity options") + self._get_next_service = "n.a." + self._data["next_departure"]["next_departure_realtime"] = self._get_next_service + self._data["next_departure"]["gtfs_rt_updated_at"] = dt_util.now().replace(tzinfo=None) + + return self._data - return self._get_next_service diff --git a/custom_components/gtfs2/gtfs_helper.py b/custom_components/gtfs2/gtfs_helper.py index e2b0a67..331ecd1 100644 --- a/custom_components/gtfs2/gtfs_helper.py +++ b/custom_components/gtfs2/gtfs_helper.py @@ -298,6 +298,7 @@ def get_next_departure(self): "next_departures": timetable_remaining, "next_departures_lines": timetable_remaining_line, "next_departures_headsign": timetable_remaining_headsign, + "gtfs_updated_at": dt_util.now().replace(tzinfo=None), } diff --git a/custom_components/gtfs2/gtfs_rt_helper.py b/custom_components/gtfs2/gtfs_rt_helper.py index b8b83b0..5d9940d 100644 --- a/custom_components/gtfs2/gtfs_rt_helper.py +++ b/custom_components/gtfs2/gtfs_rt_helper.py @@ -122,7 +122,7 @@ def get_gtfs_feed_entities(url: str, headers, label: str): ## reworked for gtfs2 def get_next_services(self): - self.data = self._data + self.data = self._get_rt_route_statuses self._stop = self._stop_id self._route = self._route_id self._direction = self._direction diff --git a/custom_components/gtfs2/sensor.py b/custom_components/gtfs2/sensor.py index 30800fd..102120c 100644 --- a/custom_components/gtfs2/sensor.py +++ b/custom_components/gtfs2/sensor.py @@ -12,8 +12,6 @@ from homeassistant.util import slugify import homeassistant.util.dt as dt_util -from .coordinator import GTFSRealtimeUpdateCoordinator - from .const import ( ATTR_ARRIVAL, ATTR_BICYCLE, @@ -381,8 +379,18 @@ def _update_attrs(self): # noqa: C901 PLR0911 self._attributes["next_departures_headsign"] = self._departure[ "next_departures_headsign" ][:10] - - self._attributes["updated_at"] = dt_util.now().replace(tzinfo=None) + + # Add next departure realtime + self._attributes["next_departure_realtime"] = self._departure[ + "next_departure_realtime" + ] + self._attributes["gtfs_rt_updated_at"] = self._departure[ + "gtfs_rt_updated_at" + ] + + self._attributes["gtfs_updated_at"] = self._departure[ + "gtfs_updated_at" + ] self._attr_extra_state_attributes = self._attributes return self._attr_extra_state_attributes @@ -412,36 +420,3 @@ def remove_keys(self, prefix: str) -> None: } -class GTFSRealtimeDepartureSensor(CoordinatorEntity): - """Implementation of a GTFS departure sensor.""" - - def __init__(self, coordinator: GTFSRealtimeUpdateCoordinator) -> None: - """Initialize the GTFSsensor.""" - super().__init__(coordinator) - self._name = coordinator.data["name"] + "_rt" - self._attributes: dict[str, Any] = {} - - self._attr_unique_id = f"gtfs-{self._name}_rt" - self._attr_device_info = DeviceInfo( - name=f"GTFS - {self._name}", - entry_type=DeviceEntryType.SERVICE, - identifiers={(DOMAIN, f"GTFS - {self._name}_rt")}, - manufacturer="GTFS", - model=self._name, - ) - _LOGGER.debug("GTFS RT Sensor: coordinator data: %s", coordinator.data ) - self._coordinator = coordinator - self._attributes = self._update_attrs_rt() - self._attr_extra_state_attributes = self._attributes - - @callback - def _handle_coordinator_update(self) -> None: - """Handle updated data from the coordinator.""" - self._update_attrs_rt() - super()._handle_coordinator_update() - - def _update_attrs_rt(self): # noqa: C901 PLR0911 - _LOGGER.debug(f"GTFS RT Sensor update attr DATA: {self._coordinator.data}") - self._attr_native_value = coordinator.data - self._attributes["next_departure_realtime"] = self._coordinator.data - return self._attributes \ No newline at end of file