-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 1de700a
Showing
8 changed files
with
350 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
/.HA_VERSION | ||
/.cloud | ||
/.storage | ||
/.vscode | ||
/automations.yaml | ||
/configuration.yaml | ||
/deps | ||
/groups.yaml | ||
/home-assistant.log | ||
/home-assistant_v2.db | ||
/scenes.yaml | ||
/scripts.yaml | ||
/secrets.yaml | ||
/tts | ||
/custom_components/emt_madrid/__pycache__ | ||
*.pyc |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
MIT License | ||
|
||
Copyright (c) 2020 Fernando Martínez | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
_Please :star: this repo if you find it useful_ | ||
|
||
# EMT Madrid bus platform for Home Assistant | ||
|
||
This is a custom sensor for Home Assistant that allows you tu have the waiting time for a specific Madrid-EMT bus stop. Each sensor will provide the arrival time for the next 2 buses of the line specified in the configuration. | ||
|
||
Thanks to [EMT Madrid MobilityLabs](https://mobilitylabs.emtmadrid.es/) for providing the data and [documentation](https://apidocs.emtmadrid.es/). | ||
|
||
![Example](example.png) | ||
|
||
## Prerequisites | ||
|
||
To use the EMT Mobilitylabs API you need to register in their [website](https://mobilitylabs.emtmadrid.es/). You have to provide a valid email account and a password that will be used to configure the sensor. Once you are registered you will receive a confirmation email to activate your account. It will not work until you have completed all the steps. | ||
|
||
## Manual Installation | ||
|
||
1. Using the tool of choice open the directory (folder) for your HA configuration (where you find `configuration.yaml`). | ||
1. If you do not have a `custom_components` directory (folder) there, you need to create it. | ||
1. In the `custom_components` directory (folder) create a new folder called `emt_madrid`. | ||
1. Download _all_ the files from the `custom_components/emt_madrid/` directory (folder) in this repository. | ||
1. Place the files you downloaded in the new directory (folder) you created. | ||
1. Restart Home Assistant | ||
1. Add `emt_madrid` sensor to your `configuration.yaml` file: | ||
|
||
```yaml | ||
# Example configuration.yaml entry | ||
sensor: | ||
- platform: emt_madrid | ||
email: !secret EMT_EMAIL | ||
password: !secret EMT_PASSWORD | ||
stop: "72" | ||
line: "27" | ||
name: "Bus 27 en Cibeles" | ||
icon: "mdi:fountain" | ||
``` | ||
### Configuration Variables | ||
**email**:\ | ||
_(string) (Required)_\ | ||
Email account used to register in the EMT Madrid API. | ||
**password**:\ | ||
_(string) (Required)_\ | ||
Password used to register in the EMT Madrid API. | ||
**stop**:\ | ||
_(string) (Required)_\ | ||
Bus stop ID. | ||
**line**:\ | ||
_(string) (Required)_\ | ||
Bus line that stops at the previous bus stop. | ||
**name**:\ | ||
_(string) (Optional)_\ | ||
Name to use in the frontend. | ||
_Default value: "Bus <bus_line> at <bus_stop>"_ | ||
**icon**:\ | ||
_(string) (Optional)_\ | ||
Icon to use in the frontend. | ||
_Default value: "mdi:bus"_ | ||
## Sensor status and attributes | ||
Once you have you sensor up and running it will update the data automatically every 30 seconds and you should have the following data: | ||
**state**:\ | ||
_(int)_\ | ||
Arrival time in minutes for the next bus. It will show "-" when there are no more buses coming and 30 when the arrival time is over 30 minutes. | ||
### Attributes | ||
**later_bus**:\ | ||
_(int)_\ | ||
Arrival time in minutes for the second bus. It will show "-" when there are no more buses coming and 30 when the arrival time is over 30 minutes. | ||
**bus_stop_id**:\ | ||
_(int)_\ | ||
Bus stop id given in the configuration. | ||
**bus_line**:\ | ||
_(int)_\ | ||
Bus line given in the configuration. | ||
### Second bus sensor | ||
If you want to have a specific sensor to show the arrival time for the second bus, you can add the following lines to your `configuration.yaml` file below the `emt_madrid` bus sensor. See the official Home Assistant [template sensor](https://www.home-assistant.io/integrations/template/) for more information. | ||
|
||
```yaml | ||
# Example configuration.yaml entry | ||
- platform: template | ||
sensors: | ||
siguiente_27: | ||
friendly_name: "Siguiente bus 27" | ||
unit_of_measurement: "min" | ||
value_template: "{{ state_attr('sensor.bus_27_en_cibeles', 'later_bus') }}" | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
"""Madrid EMT bus sensor.""" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
{ | ||
"domain": "emt_madrid", | ||
"name": "EMT Madrid", | ||
"documentation": "https://github.com/fermartv/EMT-Madrid/", | ||
"issue_tracker": "https://github.com/fermartv/EMT-Madrid/issues", | ||
"codeowners": ["@FerMartV"], | ||
"version": "1.0.1" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,199 @@ | ||
import requests | ||
import json | ||
import math | ||
|
||
import logging | ||
|
||
import voluptuous as vol | ||
|
||
"""Platform for sensor integration.""" | ||
from homeassistant.components.sensor import PLATFORM_SCHEMA | ||
from homeassistant.const import ( | ||
ATTR_ATTRIBUTION, | ||
CONF_EMAIL, | ||
CONF_ICON, | ||
CONF_NAME, | ||
CONF_PASSWORD, | ||
TIME_MINUTES, | ||
) | ||
|
||
import homeassistant.helpers.config_validation as cv | ||
from homeassistant.helpers.entity import Entity | ||
|
||
ATTRIBUTION = "Data provided by EMT Madrid MobilityLabs" | ||
|
||
CONF_STOP = "stop" | ||
CONF_LINE = "line" | ||
|
||
DEFAULT_NAME = "EMT Madrid" | ||
DEFAULT_ICON = "mdi:bus" | ||
|
||
ATTR_NEXT_UP = "later_bus" | ||
ATTR_BUS_STOP = "bus_stop_id" | ||
ATTR_BUS_LINE = "bus_line" | ||
|
||
BASE_URL = "https://openapi.emtmadrid.es/" | ||
ENDPOINT_LOGIN = "v1/mobilitylabs/user/login/" | ||
ENDPOINT_ARRIVAL_TIME = "v2/transport/busemtmad/stops/" | ||
|
||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( | ||
{ | ||
vol.Required(CONF_EMAIL): cv.string, | ||
vol.Required(CONF_PASSWORD): cv.string, | ||
vol.Required(CONF_STOP): cv.string, | ||
vol.Required(CONF_LINE): cv.string, | ||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, | ||
vol.Optional(CONF_ICON, default=DEFAULT_ICON): cv.string, | ||
} | ||
) | ||
|
||
_LOGGER = logging.getLogger(__name__) | ||
|
||
|
||
def setup_platform(hass, config, add_entities, discovery_info=None): | ||
"""Set up the sensor platform.""" | ||
email = config.get(CONF_EMAIL) | ||
password = config.get(CONF_PASSWORD) | ||
bus_stop = config.get(CONF_STOP) | ||
line = config.get(CONF_LINE) | ||
name = config.get(CONF_NAME) | ||
icon = config.get(CONF_ICON) | ||
api_emt = APIEMT(email, password) | ||
api_emt.update(bus_stop, line) | ||
add_entities([BusStopSensor(api_emt, bus_stop, line, name, icon)]) | ||
|
||
|
||
class BusStopSensor(Entity): | ||
"""Implementation of an EMT-Madrid bus stop sensor.""" | ||
|
||
def __init__(self, api_emt, bus_stop, line, name, icon): | ||
"""Initialize the sensor.""" | ||
self._state = None | ||
self._api_emt = api_emt | ||
self._bus_stop = bus_stop | ||
self._bus_line = line | ||
self._icon = icon | ||
self._name = name | ||
if self._name == DEFAULT_NAME: | ||
self._name = "Next {} at {}".format(self._bus_line, self._bus_stop) | ||
|
||
@property | ||
def name(self): | ||
"""Return the name of the sensor.""" | ||
return self._name | ||
|
||
@property | ||
def state(self): | ||
"""Return the state of the sensor.""" | ||
return self._api_emt.get_stop_data(self._bus_line, "arrival") | ||
|
||
@property | ||
def unit_of_measurement(self): | ||
"""Return the unit of measurement.""" | ||
return TIME_MINUTES | ||
|
||
@property | ||
def icon(self): | ||
"""Return sensor specific icon.""" | ||
return self._icon | ||
|
||
@property | ||
def extra_state_attributes(self): | ||
"""Return the device state attributes.""" | ||
return { | ||
ATTR_NEXT_UP: self._api_emt.get_stop_data(self._bus_line, "next_arrival"), | ||
ATTR_BUS_STOP: self._bus_stop, | ||
ATTR_BUS_LINE: self._bus_line, | ||
ATTR_ATTRIBUTION: ATTRIBUTION, | ||
} | ||
|
||
def update(self): | ||
"""Fetch new state data for the sensor.""" | ||
self._api_emt.update(self._bus_stop, self._bus_line) | ||
|
||
|
||
class APIEMT: | ||
""" | ||
Interface for the EMT REST API. Docs at https://apidocs.emtmadrid.es/ | ||
""" | ||
|
||
def __init__(self, user, password): | ||
""" Initialize the class and retrieve the token from the API. """ | ||
self._user = user | ||
self._password = password | ||
self.token = self.update_token() | ||
|
||
def update(self, stop, line): | ||
""" Update the arrival times data from the API. """ | ||
url = "{}{}{}/arrives/{}/".format(BASE_URL, ENDPOINT_ARRIVAL_TIME, stop, line) | ||
headers = {"accessToken": self.token} | ||
data = {"stopId": stop, "lineArrive": line, "Text_EstimationsRequired_YN": "Y"} | ||
response = self.api_call(url, headers, data) | ||
try: | ||
self.set_stop_data(response, line) | ||
except: | ||
_LOGGER.error("Invalid stop ID") | ||
raise Exception("Unable to get the arrival times from the API") | ||
|
||
def set_stop_data(self, data, target_line): | ||
""" Create a dictionary to store the arrival time data in the following format: | ||
{ | ||
line:{ | ||
'arrival': arrival_time, | ||
'next_arrival': arrival_time | ||
} | ||
} | ||
""" | ||
arrival_data = {} | ||
|
||
for bus in data["data"][0]["Arrive"]: | ||
estimated_time = math.trunc(bus["estimateArrive"] / 60) | ||
if estimated_time > 30: | ||
estimated_time = 30 | ||
line = bus["line"] | ||
if line not in arrival_data: | ||
arrival_data[line] = {"arrival": estimated_time} | ||
elif "next_arrival" not in arrival_data[line]: | ||
arrival_data[line]["next_arrival"] = estimated_time | ||
|
||
if target_line not in arrival_data: | ||
arrival_data[target_line] = {"arrival": "-"} | ||
if "next_arrival" not in arrival_data[target_line]: | ||
arrival_data[target_line]["next_arrival"] = "-" | ||
self._arrival_time = arrival_data | ||
|
||
def get_stop_data(self, line, bus): | ||
""" Get the data of the next bus ("arrival") or the one after that ("next_arrival") """ | ||
return self._arrival_time[str(line)][bus] | ||
|
||
def api_call(self, url, headers, payload=None, method="POST"): | ||
""" Request data from the API. """ | ||
if method == "POST": | ||
payload = json.dumps(payload) | ||
response = requests.post(url, data=payload, headers=headers) | ||
|
||
elif method == "GET": | ||
response = requests.get(url, headers=headers) | ||
|
||
else: | ||
raise Exception("Invalid HTTP method: " + method) | ||
|
||
if response.status_code != 200: | ||
_LOGGER.error("Invalid response: %s", response.status_code) | ||
return response.json() | ||
|
||
def update_token(self): | ||
""" Update or create the token when the sensor is initialized. """ | ||
headers = {"email": self._user, "password": self._password} | ||
url = BASE_URL + ENDPOINT_LOGIN | ||
response = self.api_call(url, headers, None, "GET") | ||
try: | ||
self.token = response["data"][0]["accessToken"] | ||
except: | ||
if response["code"] == "80": | ||
_LOGGER.error("Invalid credentials") | ||
else: | ||
_LOGGER.error("Unable to retrieve the token from the API") | ||
raise Exception("Unable to connect to the API") | ||
return self.token | ||
|
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
{ | ||
"name": "EMT-Madrid bus", | ||
"render_readme": true, | ||
"country": "ES", | ||
"domains": ["sensor"] | ||
} |