mirror of
https://github.com/home-assistant/core.git
synced 2025-07-09 06:17:07 +00:00
Make rmvtransport async (#17225)
* Make rmvtransport async * Make rmv transport async * Make async tests * Update rmvtransport module version * Remove unnecessary import * Make rmvtransport async * Make rmv transport async * Make async tests * Update rmvtransport module version * Remove unnecessary import * Update requirements * Remove async loop * Fix wrong import * Fix stupidness * Remove unnecessary import * Bump upstream version * Don't store the session * Refactor tests * Add test for no data * Fix linter issues * Fix stale docstring * Fix stale docstring * Remove unnecessary test code * Remove unnecessary import * Add configurable timeout * Remove global variable
This commit is contained in:
parent
8310f4a1cf
commit
cfc175d71d
@ -5,15 +5,18 @@ For more details about this platform, please refer to the documentation at
|
|||||||
https://home-assistant.io/components/sensor.rmvtransport/
|
https://home-assistant.io/components/sensor.rmvtransport/
|
||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
from homeassistant.components.sensor import PLATFORM_SCHEMA
|
from homeassistant.components.sensor import PLATFORM_SCHEMA
|
||||||
from homeassistant.const import (CONF_NAME, ATTR_ATTRIBUTION)
|
from homeassistant.const import (CONF_NAME, ATTR_ATTRIBUTION)
|
||||||
|
from homeassistant.util import Throttle
|
||||||
|
|
||||||
REQUIREMENTS = ['PyRMVtransport==0.1']
|
REQUIREMENTS = ['PyRMVtransport==0.1.3']
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -26,6 +29,7 @@ CONF_LINES = 'lines'
|
|||||||
CONF_PRODUCTS = 'products'
|
CONF_PRODUCTS = 'products'
|
||||||
CONF_TIME_OFFSET = 'time_offset'
|
CONF_TIME_OFFSET = 'time_offset'
|
||||||
CONF_MAX_JOURNEYS = 'max_journeys'
|
CONF_MAX_JOURNEYS = 'max_journeys'
|
||||||
|
CONF_TIMEOUT = 'timeout'
|
||||||
|
|
||||||
DEFAULT_NAME = 'RMV Journey'
|
DEFAULT_NAME = 'RMV Journey'
|
||||||
|
|
||||||
@ -46,6 +50,8 @@ ICONS = {
|
|||||||
}
|
}
|
||||||
ATTRIBUTION = "Data provided by opendata.rmv.de"
|
ATTRIBUTION = "Data provided by opendata.rmv.de"
|
||||||
|
|
||||||
|
SCAN_INTERVAL = timedelta(seconds=60)
|
||||||
|
|
||||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||||
vol.Required(CONF_NEXT_DEPARTURE): [{
|
vol.Required(CONF_NEXT_DEPARTURE): [{
|
||||||
vol.Required(CONF_STATION): cv.string,
|
vol.Required(CONF_STATION): cv.string,
|
||||||
@ -59,16 +65,23 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
|||||||
vol.All(cv.ensure_list, [vol.In(VALID_PRODUCTS)]),
|
vol.All(cv.ensure_list, [vol.In(VALID_PRODUCTS)]),
|
||||||
vol.Optional(CONF_TIME_OFFSET, default=0): cv.positive_int,
|
vol.Optional(CONF_TIME_OFFSET, default=0): cv.positive_int,
|
||||||
vol.Optional(CONF_MAX_JOURNEYS, default=5): cv.positive_int,
|
vol.Optional(CONF_MAX_JOURNEYS, default=5): cv.positive_int,
|
||||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string}]
|
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string}],
|
||||||
|
vol.Optional(CONF_TIMEOUT, default=10): cv.positive_int
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
async def async_setup_platform(hass, config, async_add_entities,
|
||||||
|
discovery_info=None):
|
||||||
"""Set up the RMV departure sensor."""
|
"""Set up the RMV departure sensor."""
|
||||||
|
timeout = config.get(CONF_TIMEOUT)
|
||||||
|
|
||||||
|
session = async_get_clientsession(hass)
|
||||||
|
|
||||||
sensors = []
|
sensors = []
|
||||||
for next_departure in config.get(CONF_NEXT_DEPARTURE):
|
for next_departure in config.get(CONF_NEXT_DEPARTURE):
|
||||||
sensors.append(
|
sensors.append(
|
||||||
RMVDepartureSensor(
|
RMVDepartureSensor(
|
||||||
|
session,
|
||||||
next_departure[CONF_STATION],
|
next_departure[CONF_STATION],
|
||||||
next_departure.get(CONF_DESTINATIONS),
|
next_departure.get(CONF_DESTINATIONS),
|
||||||
next_departure.get(CONF_DIRECTIONS),
|
next_departure.get(CONF_DIRECTIONS),
|
||||||
@ -76,21 +89,23 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
|||||||
next_departure.get(CONF_PRODUCTS),
|
next_departure.get(CONF_PRODUCTS),
|
||||||
next_departure.get(CONF_TIME_OFFSET),
|
next_departure.get(CONF_TIME_OFFSET),
|
||||||
next_departure.get(CONF_MAX_JOURNEYS),
|
next_departure.get(CONF_MAX_JOURNEYS),
|
||||||
next_departure.get(CONF_NAME)))
|
next_departure.get(CONF_NAME),
|
||||||
add_entities(sensors, True)
|
timeout))
|
||||||
|
async_add_entities(sensors, True)
|
||||||
|
|
||||||
|
|
||||||
class RMVDepartureSensor(Entity):
|
class RMVDepartureSensor(Entity):
|
||||||
"""Implementation of an RMV departure sensor."""
|
"""Implementation of an RMV departure sensor."""
|
||||||
|
|
||||||
def __init__(self, station, destinations, directions,
|
def __init__(self, session, station, destinations, directions, lines,
|
||||||
lines, products, time_offset, max_journeys, name):
|
products, time_offset, max_journeys, name, timeout):
|
||||||
"""Initialize the sensor."""
|
"""Initialize the sensor."""
|
||||||
self._station = station
|
self._station = station
|
||||||
self._name = name
|
self._name = name
|
||||||
self._state = None
|
self._state = None
|
||||||
self.data = RMVDepartureData(station, destinations, directions, lines,
|
self.data = RMVDepartureData(session, station, destinations,
|
||||||
products, time_offset, max_journeys)
|
directions, lines, products, time_offset,
|
||||||
|
max_journeys, timeout)
|
||||||
self._icon = ICONS[None]
|
self._icon = ICONS[None]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -134,9 +149,10 @@ class RMVDepartureSensor(Entity):
|
|||||||
"""Return the unit this state is expressed in."""
|
"""Return the unit this state is expressed in."""
|
||||||
return "min"
|
return "min"
|
||||||
|
|
||||||
def update(self):
|
async def async_update(self):
|
||||||
"""Get the latest data and update the state."""
|
"""Get the latest data and update the state."""
|
||||||
self.data.update()
|
await self.data.async_update()
|
||||||
|
|
||||||
if not self.data.departures:
|
if not self.data.departures:
|
||||||
self._state = None
|
self._state = None
|
||||||
self._icon = ICONS[None]
|
self._icon = ICONS[None]
|
||||||
@ -151,10 +167,11 @@ class RMVDepartureSensor(Entity):
|
|||||||
class RMVDepartureData:
|
class RMVDepartureData:
|
||||||
"""Pull data from the opendata.rmv.de web page."""
|
"""Pull data from the opendata.rmv.de web page."""
|
||||||
|
|
||||||
def __init__(self, station_id, destinations, directions,
|
def __init__(self, session, station_id, destinations, directions, lines,
|
||||||
lines, products, time_offset, max_journeys):
|
products, time_offset, max_journeys, timeout):
|
||||||
"""Initialize the sensor."""
|
"""Initialize the sensor."""
|
||||||
import RMVtransport
|
from RMVtransport import RMVtransport
|
||||||
|
|
||||||
self.station = None
|
self.station = None
|
||||||
self._station_id = station_id
|
self._station_id = station_id
|
||||||
self._destinations = destinations
|
self._destinations = destinations
|
||||||
@ -163,15 +180,16 @@ class RMVDepartureData:
|
|||||||
self._products = products
|
self._products = products
|
||||||
self._time_offset = time_offset
|
self._time_offset = time_offset
|
||||||
self._max_journeys = max_journeys
|
self._max_journeys = max_journeys
|
||||||
self.rmv = RMVtransport.RMVtransport()
|
self.rmv = RMVtransport(session, timeout)
|
||||||
self.departures = []
|
self.departures = []
|
||||||
|
|
||||||
def update(self):
|
@Throttle(SCAN_INTERVAL)
|
||||||
|
async def async_update(self):
|
||||||
"""Update the connection data."""
|
"""Update the connection data."""
|
||||||
try:
|
try:
|
||||||
_data = self.rmv.get_departures(self._station_id,
|
_data = await self.rmv.get_departures(self._station_id,
|
||||||
products=self._products,
|
products=self._products,
|
||||||
maxJourneys=50)
|
maxJourneys=50)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
self.departures = []
|
self.departures = []
|
||||||
_LOGGER.warning("Returned data not understood")
|
_LOGGER.warning("Returned data not understood")
|
||||||
|
@ -52,7 +52,7 @@ PyMata==2.14
|
|||||||
PyQRCode==1.2.1
|
PyQRCode==1.2.1
|
||||||
|
|
||||||
# homeassistant.components.sensor.rmvtransport
|
# homeassistant.components.sensor.rmvtransport
|
||||||
PyRMVtransport==0.1
|
PyRMVtransport==0.1.3
|
||||||
|
|
||||||
# homeassistant.components.switch.switchbot
|
# homeassistant.components.switch.switchbot
|
||||||
PySwitchbot==0.3
|
PySwitchbot==0.3
|
||||||
|
@ -22,7 +22,7 @@ requests_mock==1.5.2
|
|||||||
HAP-python==2.2.2
|
HAP-python==2.2.2
|
||||||
|
|
||||||
# homeassistant.components.sensor.rmvtransport
|
# homeassistant.components.sensor.rmvtransport
|
||||||
PyRMVtransport==0.1
|
PyRMVtransport==0.1.3
|
||||||
|
|
||||||
# homeassistant.components.notify.yessssms
|
# homeassistant.components.notify.yessssms
|
||||||
YesssSMS==0.2.3
|
YesssSMS==0.2.3
|
||||||
|
@ -1,14 +1,17 @@
|
|||||||
"""The tests for the rmvtransport platform."""
|
"""The tests for the rmvtransport platform."""
|
||||||
import unittest
|
|
||||||
from unittest.mock import patch
|
|
||||||
import datetime
|
import datetime
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
from homeassistant.setup import setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
|
|
||||||
from tests.common import get_test_home_assistant
|
from tests.common import mock_coro
|
||||||
|
|
||||||
VALID_CONFIG_MINIMAL = {'sensor': {'platform': 'rmvtransport',
|
|
||||||
'next_departure': [{'station': '3000010'}]}}
|
VALID_CONFIG_MINIMAL = {'sensor': {
|
||||||
|
'platform': 'rmvtransport',
|
||||||
|
'next_departure': [
|
||||||
|
{'station': '3000010'}
|
||||||
|
]}}
|
||||||
|
|
||||||
VALID_CONFIG_NAME = {'sensor': {
|
VALID_CONFIG_NAME = {'sensor': {
|
||||||
'platform': 'rmvtransport',
|
'platform': 'rmvtransport',
|
||||||
@ -41,8 +44,7 @@ VALID_CONFIG_DEST = {'sensor': {
|
|||||||
]}}
|
]}}
|
||||||
|
|
||||||
|
|
||||||
def get_departuresMock(stationId, maxJourneys,
|
def get_departures_mock():
|
||||||
products): # pylint: disable=invalid-name
|
|
||||||
"""Mock rmvtransport departures loading."""
|
"""Mock rmvtransport departures loading."""
|
||||||
data = {'station': 'Frankfurt (Main) Hauptbahnhof',
|
data = {'station': 'Frankfurt (Main) Hauptbahnhof',
|
||||||
'stationId': '3000010', 'filter': '11111111111', 'journeys': [
|
'stationId': '3000010', 'filter': '11111111111', 'journeys': [
|
||||||
@ -97,77 +99,77 @@ def get_departuresMock(stationId, maxJourneys,
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
def get_errDeparturesMock(stationId, maxJourneys,
|
def get_no_departures_mock():
|
||||||
products): # pylint: disable=invalid-name
|
"""Mock no departures in results."""
|
||||||
"""Mock rmvtransport departures erroneous loading."""
|
data = {'station': 'Frankfurt (Main) Hauptbahnhof',
|
||||||
raise ValueError
|
'stationId': '3000010',
|
||||||
|
'filter': '11111111111',
|
||||||
|
'journeys': []}
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
class TestRMVtransportSensor(unittest.TestCase):
|
async def test_rmvtransport_min_config(hass):
|
||||||
"""Test the rmvtransport sensor."""
|
"""Test minimal rmvtransport configuration."""
|
||||||
|
with patch('RMVtransport.RMVtransport.get_departures',
|
||||||
|
return_value=mock_coro(get_departures_mock())):
|
||||||
|
assert await async_setup_component(hass, 'sensor',
|
||||||
|
VALID_CONFIG_MINIMAL) is True
|
||||||
|
|
||||||
def setUp(self):
|
state = hass.states.get('sensor.frankfurt_main_hauptbahnhof')
|
||||||
"""Set up things to run when tests begin."""
|
assert state.state == '7'
|
||||||
self.hass = get_test_home_assistant()
|
assert state.attributes['departure_time'] == \
|
||||||
self.config = VALID_CONFIG_MINIMAL
|
datetime.datetime(2018, 8, 6, 14, 21)
|
||||||
self.reference = {}
|
assert state.attributes['direction'] == \
|
||||||
self.entities = []
|
'Frankfurt (Main) Hugo-Junkers-Straße/Schleife'
|
||||||
|
assert state.attributes['product'] == 'Tram'
|
||||||
|
assert state.attributes['line'] == 12
|
||||||
|
assert state.attributes['icon'] == 'mdi:tram'
|
||||||
|
assert state.attributes['friendly_name'] == 'Frankfurt (Main) Hauptbahnhof'
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
"""Stop everything that was started."""
|
|
||||||
self.hass.stop()
|
|
||||||
|
|
||||||
@patch('RMVtransport.RMVtransport.get_departures',
|
async def test_rmvtransport_name_config(hass):
|
||||||
side_effect=get_departuresMock)
|
"""Test custom name configuration."""
|
||||||
def test_rmvtransport_min_config(self, mock_get_departures):
|
with patch('RMVtransport.RMVtransport.get_departures',
|
||||||
"""Test minimal rmvtransport configuration."""
|
return_value=mock_coro(get_departures_mock())):
|
||||||
assert setup_component(self.hass, 'sensor', VALID_CONFIG_MINIMAL)
|
assert await async_setup_component(hass, 'sensor', VALID_CONFIG_NAME)
|
||||||
state = self.hass.states.get('sensor.frankfurt_main_hauptbahnhof')
|
|
||||||
self.assertEqual(state.state, '7')
|
|
||||||
self.assertEqual(state.attributes['departure_time'],
|
|
||||||
datetime.datetime(2018, 8, 6, 14, 21))
|
|
||||||
self.assertEqual(state.attributes['direction'],
|
|
||||||
'Frankfurt (Main) Hugo-Junkers-Straße/Schleife')
|
|
||||||
self.assertEqual(state.attributes['product'], 'Tram')
|
|
||||||
self.assertEqual(state.attributes['line'], 12)
|
|
||||||
self.assertEqual(state.attributes['icon'], 'mdi:tram')
|
|
||||||
self.assertEqual(state.attributes['friendly_name'],
|
|
||||||
'Frankfurt (Main) Hauptbahnhof')
|
|
||||||
|
|
||||||
@patch('RMVtransport.RMVtransport.get_departures',
|
state = hass.states.get('sensor.my_station')
|
||||||
side_effect=get_departuresMock)
|
assert state.attributes['friendly_name'] == 'My Station'
|
||||||
def test_rmvtransport_name_config(self, mock_get_departures):
|
|
||||||
"""Test custom name configuration."""
|
|
||||||
assert setup_component(self.hass, 'sensor', VALID_CONFIG_NAME)
|
|
||||||
state = self.hass.states.get('sensor.my_station')
|
|
||||||
self.assertEqual(state.attributes['friendly_name'], 'My Station')
|
|
||||||
|
|
||||||
@patch('RMVtransport.RMVtransport.get_departures',
|
|
||||||
side_effect=get_errDeparturesMock)
|
|
||||||
def test_rmvtransport_err_config(self, mock_get_departures):
|
|
||||||
"""Test erroneous rmvtransport configuration."""
|
|
||||||
assert setup_component(self.hass, 'sensor', VALID_CONFIG_MINIMAL)
|
|
||||||
|
|
||||||
@patch('RMVtransport.RMVtransport.get_departures',
|
async def test_rmvtransport_misc_config(hass):
|
||||||
side_effect=get_departuresMock)
|
"""Test misc configuration."""
|
||||||
def test_rmvtransport_misc_config(self, mock_get_departures):
|
with patch('RMVtransport.RMVtransport.get_departures',
|
||||||
"""Test misc configuration."""
|
return_value=mock_coro(get_departures_mock())):
|
||||||
assert setup_component(self.hass, 'sensor', VALID_CONFIG_MISC)
|
assert await async_setup_component(hass, 'sensor', VALID_CONFIG_MISC)
|
||||||
state = self.hass.states.get('sensor.frankfurt_main_hauptbahnhof')
|
|
||||||
self.assertEqual(state.attributes['friendly_name'],
|
|
||||||
'Frankfurt (Main) Hauptbahnhof')
|
|
||||||
self.assertEqual(state.attributes['line'], 21)
|
|
||||||
|
|
||||||
@patch('RMVtransport.RMVtransport.get_departures',
|
state = hass.states.get('sensor.frankfurt_main_hauptbahnhof')
|
||||||
side_effect=get_departuresMock)
|
assert state.attributes['friendly_name'] == 'Frankfurt (Main) Hauptbahnhof'
|
||||||
def test_rmvtransport_dest_config(self, mock_get_departures):
|
assert state.attributes['line'] == 21
|
||||||
"""Test misc configuration."""
|
|
||||||
assert setup_component(self.hass, 'sensor', VALID_CONFIG_DEST)
|
|
||||||
state = self.hass.states.get('sensor.frankfurt_main_hauptbahnhof')
|
async def test_rmvtransport_dest_config(hass):
|
||||||
self.assertEqual(state.state, '11')
|
"""Test destination configuration."""
|
||||||
self.assertEqual(state.attributes['direction'],
|
with patch('RMVtransport.RMVtransport.get_departures',
|
||||||
'Frankfurt (Main) Hugo-Junkers-Straße/Schleife')
|
return_value=mock_coro(get_departures_mock())):
|
||||||
self.assertEqual(state.attributes['line'], 12)
|
assert await async_setup_component(hass, 'sensor', VALID_CONFIG_DEST)
|
||||||
self.assertEqual(state.attributes['minutes'], 11)
|
|
||||||
self.assertEqual(state.attributes['departure_time'],
|
state = hass.states.get('sensor.frankfurt_main_hauptbahnhof')
|
||||||
datetime.datetime(2018, 8, 6, 14, 25))
|
assert state.state == '11'
|
||||||
|
assert state.attributes['direction'] == \
|
||||||
|
'Frankfurt (Main) Hugo-Junkers-Straße/Schleife'
|
||||||
|
assert state.attributes['line'] == 12
|
||||||
|
assert state.attributes['minutes'] == 11
|
||||||
|
assert state.attributes['departure_time'] == \
|
||||||
|
datetime.datetime(2018, 8, 6, 14, 25)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_rmvtransport_no_departures(hass):
|
||||||
|
"""Test for no departures."""
|
||||||
|
with patch('RMVtransport.RMVtransport.get_departures',
|
||||||
|
return_value=mock_coro(get_no_departures_mock())):
|
||||||
|
assert await async_setup_component(hass, 'sensor',
|
||||||
|
VALID_CONFIG_MINIMAL)
|
||||||
|
|
||||||
|
state = hass.states.get('sensor.frankfurt_main_hauptbahnhof')
|
||||||
|
assert not state
|
||||||
|
Loading…
x
Reference in New Issue
Block a user