diff --git a/custom_components/gtfs2/__init__.py b/custom_components/gtfs2/__init__.py index 4e71276..fa688c5 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 +from .coordinator import GTFSUpdateCoordinator, GTFSRealtimeUpdateCoordinator import voluptuous as vol from .gtfs_helper import get_gtfs @@ -20,25 +20,12 @@ async def async_migrate_entry(hass, config_entry: ConfigEntry) -> bool: if config_entry.version == 1: - 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 + new = {**config_entry.data} + new['extract_from'] = 'url' + new.pop('refresh_interval') - config_entry.version = 3 - hass.config_entries.async_update_entry(config_entry, options=new_options) + config_entry.version = 2 + hass.config_entries.async_update_entry(config_entry, data=new) _LOGGER.debug("Migration to version %s successful", config_entry.version) @@ -53,9 +40,6 @@ 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, } @@ -90,6 +74,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=1) + hass.data[DOMAIN][entry.entry_id]['coordinator'].update_interval = timedelta(minutes=entry.options.get("refresh_interval", DEFAULT_REFRESH_INTERVAL)) 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 1d56101..aeef210 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 = 3 + VERSION = 2 def __init__(self) -> None: """Init ConfigFlow.""" @@ -246,6 +246,7 @@ 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}") @@ -260,7 +261,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 a9fbb1c..c04f29a 100644 --- a/custom_components/gtfs2/coordinator.py +++ b/custom_components/gtfs2/coordinator.py @@ -4,11 +4,9 @@ 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 @@ -28,7 +26,7 @@ def __init__(self, hass: HomeAssistant, entry: ConfigEntry) -> None: hass=hass, logger=_LOGGER, name=entry.entry_id, - update_interval=timedelta(minutes=1), + update_interval=timedelta(minutes=entry.options.get("refresh_interval", DEFAULT_REFRESH_INTERVAL)), ) self.config_entry = entry self.hass = hass @@ -37,78 +35,87 @@ def __init__(self, hass: HomeAssistant, entry: ConfigEntry) -> None: self._data: dict[str, str] = {} async def _async_update_data(self) -> dict[str, str]: - """Get the latest data from GTFS and GTFS relatime, depending refresh interval""" + """Update.""" data = self.config_entry.data options = self.config_entry.options self._pygtfs = get_gtfs( self.hass, DEFAULT_PATH, data, False ) - previous_data = None if self.data is None else self.data.copy() - _LOGGER.debug("Previous data: %s", previous_data) + 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"], + } - 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"], - } + 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 - 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"]) - +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 + if "real_time" in options: - 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 = 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) - 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.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 + """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) + else: + _LOGGER.error("GTFS RT: Issue with entity options") + return "---" + return self._get_next_service diff --git a/custom_components/gtfs2/gtfs_helper.py b/custom_components/gtfs2/gtfs_helper.py index 331ecd1..e2b0a67 100644 --- a/custom_components/gtfs2/gtfs_helper.py +++ b/custom_components/gtfs2/gtfs_helper.py @@ -298,7 +298,6 @@ 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 5d9940d..b8b83b0 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._get_rt_route_statuses + self.data = self._data self._stop = self._stop_id self._route = self._route_id self._direction = self._direction diff --git a/custom_components/gtfs2/manifest.json b/custom_components/gtfs2/manifest.json index 8dbe4c3..9c94526 100644 --- a/custom_components/gtfs2/manifest.json +++ b/custom_components/gtfs2/manifest.json @@ -6,6 +6,6 @@ "documentation": "https://github.com/vingerha/gtfs2", "iot_class": "local_polling", "issue_tracker": "https://github.com/vingerha/gtfs2/issues", - "requirements": ["pygtfs==0.1.9","gtfs-realtime-bindings==1.0.0"], + "requirements": ["pygtfs==0.1.9"], "version": "0.1.5" } diff --git a/custom_components/gtfs2/sensor.py b/custom_components/gtfs2/sensor.py index 102120c..30800fd 100644 --- a/custom_components/gtfs2/sensor.py +++ b/custom_components/gtfs2/sensor.py @@ -12,6 +12,8 @@ from homeassistant.util import slugify import homeassistant.util.dt as dt_util +from .coordinator import GTFSRealtimeUpdateCoordinator + from .const import ( ATTR_ARRIVAL, ATTR_BICYCLE, @@ -379,18 +381,8 @@ def _update_attrs(self): # noqa: C901 PLR0911 self._attributes["next_departures_headsign"] = self._departure[ "next_departures_headsign" ][:10] - - # 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._attributes["updated_at"] = dt_util.now().replace(tzinfo=None) self._attr_extra_state_attributes = self._attributes return self._attr_extra_state_attributes @@ -420,3 +412,36 @@ 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