Skip to content

Commit

Permalink
Fix platform not found error #2
Browse files Browse the repository at this point in the history
  • Loading branch information
fermartv committed Dec 4, 2021
0 parents commit 1de700a
Show file tree
Hide file tree
Showing 8 changed files with 350 additions and 0 deletions.
16 changes: 16 additions & 0 deletions .gitignore
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
21 changes: 21 additions & 0 deletions LICENSE
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.
99 changes: 99 additions & 0 deletions README.md
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') }}"
```
1 change: 1 addition & 0 deletions custom_components/emt_madrid/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Madrid EMT bus sensor."""
8 changes: 8 additions & 0 deletions custom_components/emt_madrid/manifest.json
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"
}
199 changes: 199 additions & 0 deletions custom_components/emt_madrid/sensor.py
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

Binary file added example.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions hacs.json
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"]
}

0 comments on commit 1de700a

Please sign in to comment.