Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added API key support and fixed missing sensor attributes #25

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
1. Stop name is case in-sensitive, but stop code is not. E.g. töölöntori and Töölöntori are OK. H0209 is OK. h0209 is NOT OK.
2. Route takes precedence over destination, if specified. Both options are case in-sensitive.
3. In case, route and destination are not needed, leave the default values as "ALL" or "all".
4. Add the API-key generated from the Digitransit site.

<br/>

Expand Down Expand Up @@ -442,3 +443,6 @@ title: V1530 Next Lines

## Original Author
Anand Radhakrishnan [@anand-p-r](https://github.com/anand-p-r)

## Added API key support
Mathias Backman [@fimathias](https://github.com/fimathias)
59 changes: 34 additions & 25 deletions __init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
from python_graphql_client import GraphqlClient

from .const import (
BASE_URL, DESTINATION,
BASE_URL,
DESTINATION,
DOMAIN,
STOP_NAME,
STOP_GTFS,
Expand All @@ -31,13 +32,13 @@
VAR_LIMIT,
LIMIT,
SECS_IN_DAY,
_LOGGER
_LOGGER,
APIKEY,
)


PLATFORMS = ["sensor"]


graph_client = GraphqlClient(endpoint=BASE_URL)


Expand All @@ -62,9 +63,7 @@ async def async_setup_entry(hass, config_entry) -> bool:

websession = async_get_clientsession(hass)

coordinator = HSLHRTDataUpdateCoordinator(
hass, websession, config_entry
)
coordinator = HSLHRTDataUpdateCoordinator(hass, websession, config_entry)
await coordinator.async_refresh()

if not coordinator.last_update_success:
Expand Down Expand Up @@ -108,17 +107,20 @@ def __init__(self, hass, session, config_entry):
"""Initialize."""

##if config_entry.data.get(STOP_NAME, "None") is not None:
_LOGGER.debug("Using Name/Code: %s", config_entry.data.get(STOP_NAME, "None"))
_LOGGER.debug("Using Name/Code: %s", config_entry.data.get(STOP_NAME, "None"))

##if config_entry.data.get(ROUTE, "None") is not None:
_LOGGER.debug("Using Route: %s", config_entry.data.get(ROUTE, "None"))
_LOGGER.debug("Using Route: %s", config_entry.data.get(ROUTE, "None"))

##if config_entry.data.get(DESTINATION, "None") is not None:
_LOGGER.debug("Using Destination: %s", config_entry.data.get(DESTINATION, "None"))
_LOGGER.debug(
"Using Destination: %s", config_entry.data.get(DESTINATION, "None")
)

self.gtfs_id = config_entry.data.get(STOP_GTFS, "")
self.route = config_entry.data.get(ROUTE, "")
self.dest = config_entry.data.get(DESTINATION, "")
self.apikey = config_entry.data.get(APIKEY, "")

self.route_data = None
self._hass = hass
Expand All @@ -129,12 +131,10 @@ def __init__(self, hass, session, config_entry):
hass, _LOGGER, name=DOMAIN, update_interval=MIN_TIME_BETWEEN_UPDATES
)


async def _async_update_data(self):
"""Update data via HSl HRT Open API."""

def parse_data(data=None, line_from_user=None, dest_from_user=None):

parsed_data = {}
bus_lines = {}

Expand All @@ -144,7 +144,6 @@ def parse_data(data=None, line_from_user=None, dest_from_user=None):
hsl_stop_data = graph_data.get("stop", None)

if hsl_stop_data is not None:

parsed_data[STOP_NAME] = hsl_stop_data.get("name", "")
parsed_data[STOP_CODE] = hsl_stop_data.get("code", "")
parsed_data[STOP_GTFS] = hsl_stop_data.get("gtfsId", "")
Expand All @@ -153,7 +152,6 @@ def parse_data(data=None, line_from_user=None, dest_from_user=None):
route_data = hsl_stop_data.get("stoptimesWithoutPatterns", None)

if route_data is not None:

routes = []
for route in route_data:
route_dict = {}
Expand All @@ -164,14 +162,16 @@ def parse_data(data=None, line_from_user=None, dest_from_user=None):

## Arrival time is num of secs from midnight when the trip started.
## If the trip starts on this day and arrival time is next day (e.g late night trips)
## the arrival time shows the number of secs more than 24hrs ending up with a
## the arrival time shows the number of secs more than 24hrs ending up with a
## 1 day, hh:mm:ss on the displays. This corrects it.
if arrival >= SECS_IN_DAY:
arrival = arrival - SECS_IN_DAY

route_dict[DICT_KEY_ARRIVAL] = str(datetime.timedelta(seconds=arrival))
route_dict[DICT_KEY_ARRIVAL] = str(
datetime.timedelta(seconds=arrival)
)
route_dict[DICT_KEY_DEST] = route.get("headsign", "")

route_dict[DICT_KEY_ROUTE] = ""
if route_dict[DICT_KEY_DEST] != "":
for bus in bus_lines:
Expand All @@ -186,9 +186,14 @@ def parse_data(data=None, line_from_user=None, dest_from_user=None):
if trip != "":
trip_route = trip.get("route", "")
if trip_route != "":
trip_route_shortname = trip_route.get("shortName", "")
trip_route_shortname = trip_route.get(
"shortName", ""
)
if trip_route_shortname != "":
if trip_route_shortname.lower() == line.lower():
if (
trip_route_shortname.lower()
== line.lower()
):
route_dict[DICT_KEY_ROUTE] = line

routes.append(route_dict)
Expand Down Expand Up @@ -239,22 +244,26 @@ def parse_data(data=None, line_from_user=None, dest_from_user=None):

try:
async with timeout(10):

# Find all the trips for the day
current_epoch = int(time.time())
variables = {
VAR_ID: self.gtfs_id.upper(),
VAR_CURR_EPOCH: current_epoch,
VAR_LIMIT: LIMIT}
VAR_ID: self.gtfs_id.upper(),
VAR_CURR_EPOCH: current_epoch,
VAR_LIMIT: LIMIT,
}

graph_client.headers["digitransit-subscription-key"] = self.apikey

# Asynchronous request
data = await self._hass.async_add_executor_job(
graph_client.execute, ROUTE_QUERY_WITH_LIMIT, variables
)

self.route_data = parse_data(data=data, line_from_user=self.route, dest_from_user=self.dest)
self.route_data = parse_data(
data=data, line_from_user=self.route, dest_from_user=self.dest
)
_LOGGER.debug(f"DATA: {self.route_data}")

except Exception as error:
raise UpdateFailed(error) from error
return {}
return {}
71 changes: 43 additions & 28 deletions config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,12 @@
ALL,
VAR_NAME_CODE,
ROUTE,
DESTINATION
DESTINATION,
APIKEY,
)

api_key = None


async def validate_user_config(hass: core.HomeAssistant, data):
"""Validate input configuration for HSL HRT.
Expand All @@ -32,6 +35,7 @@ async def validate_user_config(hass: core.HomeAssistant, data):
name_code = data[NAME_CODE]
route = data[ROUTE]
dest = data[DESTINATION]
apikey = data[APIKEY]

errors = ""
stop_code = None
Expand All @@ -54,28 +58,36 @@ async def validate_user_config(hass: core.HomeAssistant, data):
# Option-1 --> As given by user
variables = {VAR_NAME_CODE: name_code}
valid_opt_count = 1
while (True):
hsl_data = await graph_client.execute_async(query=STOP_ID_QUERY, variables=variables)
while True:
graph_client.headers["digitransit-subscription-key"] = apikey
hsl_data = await graph_client.execute_async(
query=STOP_ID_QUERY, variables=variables
)

data_dict = hsl_data.get("data", None)
if data_dict is not None:

stops_data = data_dict.get("stops", None)
if stops_data is not None:

if len(stops_data) > 0:
## Reset errors
errors = ""
break
else:
_LOGGER.debug("no data in stops for %s", variables.get(VAR_NAME_CODE, "-NA-"))
errors = "invalid_name_code"
_LOGGER.debug(
"no data in stops for %s",
variables.get(VAR_NAME_CODE, "-NA-"),
)
errors = "invalid_name_code 1"
else:
_LOGGER.debug("no key stops for %s", variables.get(VAR_NAME_CODE, "-NA-"))
errors = "invalid_name_code"
_LOGGER.debug(
"no key stops for %s", variables.get(VAR_NAME_CODE, "-NA-")
)
errors = "invalid_name_code 2"
else:
_LOGGER.debug("no key data for %s", variables.get(VAR_NAME_CODE, "-NA-"))
errors = "invalid_name_code"
_LOGGER.debug(
"no key data for %s", variables.get(VAR_NAME_CODE, "-NA-")
)
errors = "invalid_name_code 3"

if valid_opt_count == 3:
return {
Expand All @@ -84,7 +96,8 @@ async def validate_user_config(hass: core.HomeAssistant, data):
STOP_GTFS: stop_gtfs,
ROUTE: ret_route,
DESTINATION: ret_dest,
ERROR: errors
ERROR: errors,
APIKEY: apikey,
}
elif valid_opt_count == 1:
# Option-2: --> Upper case of user value
Expand All @@ -95,20 +108,20 @@ async def validate_user_config(hass: core.HomeAssistant, data):
variables = {VAR_NAME_CODE: name_code.lower()}
valid_opt_count = 3


except Exception as err:
err_string = f"Client error with message {err.message}"
errors = "client_connect_error"
_LOGGER.error(err_string)

return {
STOP_CODE: None,
STOP_NAME: None,
STOP_GTFS: None,
STOP_CODE: None,
STOP_NAME: None,
STOP_GTFS: None,
ROUTE: None,
DESTINATION: None,
ERROR: errors
}
ERROR: errors,
APIKEY: apikey,
}

stop_data = stops_data[0]
stop_gtfs = stop_data.get("gtfsId", "")
Expand Down Expand Up @@ -137,10 +150,10 @@ async def validate_user_config(hass: core.HomeAssistant, data):
if dest.lower() in head_sign.lower():
ret_dest = head_sign
break_loop = True
break
break

if break_loop:
break
break
else:
ret_route = route

Expand All @@ -151,15 +164,16 @@ async def validate_user_config(hass: core.HomeAssistant, data):
errors = "invalid_destination"
else:
_LOGGER.error("Name or gtfs is blank")
errors = "invalid_name_code"
errors = "invalid_name_code 4"

return {
STOP_CODE: stop_code,
STOP_NAME: stop_name,
STOP_GTFS: stop_gtfs,
ROUTE: ret_route,
DESTINATION: ret_dest,
ERROR: errors
ERROR: errors,
APIKEY: apikey,
}


Expand All @@ -174,21 +188,21 @@ async def async_step_user(self, user_input=None):
# Display an option for the user to provide Stop Name/Code for the integration
errors = {}
valid = {}

if user_input is not None:

if user_input is not None:
valid = await validate_user_config(self.hass, user_input)
await self.async_set_unique_id(
base_unique_id(valid[STOP_GTFS], valid[ROUTE], valid[DESTINATION])
)
self._abort_if_unique_id_configured()
if valid.get(ERROR, "") == "":

title = ""
if valid[ROUTE] is not None:
title = f"{valid[STOP_NAME]}({valid[STOP_CODE]}) {valid[ROUTE]}"
elif valid[DESTINATION] is not None:
title = f"{valid[STOP_NAME]}({valid[STOP_CODE]}) {valid[DESTINATION]}"
title = (
f"{valid[STOP_NAME]}({valid[STOP_CODE]}) {valid[DESTINATION]}"
)
else:
title = f"{valid[STOP_NAME]}({valid[STOP_CODE]}) ALL"
return self.async_create_entry(title=title, data=valid)
Expand All @@ -201,12 +215,13 @@ async def async_step_user(self, user_input=None):
{
vol.Required(NAME_CODE, default=""): str,
vol.Required(ROUTE, default="ALL"): str,
vol.Required(DESTINATION, default="ALL"): str
vol.Required(DESTINATION, default="ALL"): str,
vol.Required(APIKEY, default=""): str,
}
)

return self.async_show_form(
step_id="user",
data_schema=data_schema,
errors=errors,
)
)
Loading