Skip to content
Merged
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
1 change: 1 addition & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -726,6 +726,7 @@ omit =
homeassistant/components/yale_smart_alarm/alarm_control_panel.py
homeassistant/components/yamaha/media_player.py
homeassistant/components/yamaha_musiccast/media_player.py
homeassistant/components/yandex_transport/*
homeassistant/components/yeelight/*
homeassistant/components/yeelightsunflower/light.py
homeassistant/components/yi/camera.py
Expand Down
1 change: 1 addition & 0 deletions CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,7 @@ homeassistant/components/xiaomi_miio/* @rytilahti @syssi
homeassistant/components/xiaomi_tv/* @simse
homeassistant/components/xmpp/* @fabaff @flowolf
homeassistant/components/yamaha_musiccast/* @jalmeroth
homeassistant/components/yandex_transport/* @rishatik92
homeassistant/components/yeelight/* @rytilahti @zewelor
homeassistant/components/yeelightsunflower/* @lindsaymarkward
homeassistant/components/yessssms/* @flowolf
Expand Down
1 change: 1 addition & 0 deletions homeassistant/components/yandex_transport/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Service for obtaining information about closer bus from Transport Yandex Service."""
12 changes: 12 additions & 0 deletions homeassistant/components/yandex_transport/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"domain": "yandex_transport",
"name": "Yandex Transport",
"documentation": "https://www.home-assistant.io/components/yandex_transport",
"requirements": [
"ya_ma==0.3.4"
],
"dependencies": [],
"codeowners": [
"@rishatik92"
]
}
128 changes: 128 additions & 0 deletions homeassistant/components/yandex_transport/sensor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
# -*- coding: utf-8 -*-
"""Service for obtaining information about closer bus from Transport Yandex Service."""

import logging
from datetime import timedelta

import voluptuous as vol
from ya_ma import YandexMapsRequester

import homeassistant.helpers.config_validation as cv
import homeassistant.util.dt as dt_util
from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import CONF_NAME, ATTR_ATTRIBUTION, DEVICE_CLASS_TIMESTAMP
from homeassistant.helpers.entity import Entity

_LOGGER = logging.getLogger(__name__)

STOP_NAME = "stop_name"
USER_AGENT = "Home Assistant"
ATTRIBUTION = "Data provided by maps.yandex.ru"

CONF_STOP_ID = "stop_id"
CONF_ROUTE = "routes"

DEFAULT_NAME = "Yandex Transport"
ICON = "mdi:bus"

SCAN_INTERVAL = timedelta(minutes=1)

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{
vol.Required(CONF_STOP_ID): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_ROUTE, default=[]): vol.All(cv.ensure_list, [cv.string]),
}
)


def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Yandex transport sensor."""
stop_id = config[CONF_STOP_ID]
name = config[CONF_NAME]
routes = config[CONF_ROUTE]

data = YandexMapsRequester(user_agent=USER_AGENT)
add_entities([DiscoverMoscowYandexTransport(data, stop_id, routes, name)], True)


class DiscoverMoscowYandexTransport(Entity):
"""Implementation of yandex_transport sensor."""

def __init__(self, requester, stop_id, routes, name):
"""Initialize sensor."""
self.requester = requester
self._stop_id = stop_id
self._routes = []
self._routes = routes
self._state = None
self._name = name
self._attrs = None

def update(self):
"""Get the latest data from maps.yandex.ru and update the states."""
attrs = {}
closer_time = None
try:
yandex_reply = self.requester.get_stop_info(self._stop_id)
data = yandex_reply["data"]
stop_metadata = data["properties"]["StopMetaData"]
except KeyError as key_error:
_LOGGER.warning(
"Exception KeyError was captured, missing key is %s. Yandex returned: %s",
key_error,
yandex_reply,
)
self.requester.set_new_session()
data = self.requester.get_stop_info(self._stop_id)["data"]
stop_metadata = data["properties"]["StopMetaData"]
stop_name = data["properties"]["name"]
transport_list = stop_metadata["Transport"]
for transport in transport_list:
route = transport["name"]
if self._routes and route not in self._routes:
# skip unnecessary route info
continue
if "Events" in transport["BriefSchedule"]:
for event in transport["BriefSchedule"]["Events"]:
if "Estimated" in event:
posix_time_next = int(event["Estimated"]["value"])
if closer_time is None or closer_time > posix_time_next:
closer_time = posix_time_next
if route not in attrs:
attrs[route] = []
attrs[route].append(event["Estimated"]["text"])
attrs[STOP_NAME] = stop_name
attrs[ATTR_ATTRIBUTION] = ATTRIBUTION
if closer_time is None:
self._state = None
else:
self._state = dt_util.utc_from_timestamp(closer_time).isoformat(
timespec="seconds"
)
self._attrs = attrs

@property
def state(self):
"""Return the state of the sensor."""
return self._state

@property
def device_class(self):
"""Return the device class."""
return DEVICE_CLASS_TIMESTAMP

@property
def name(self):
"""Return the name of the sensor."""
return self._name

@property
def device_state_attributes(self):
"""Return the state attributes."""
return self._attrs

@property
def icon(self):
"""Icon to use in the frontend, if any."""
return ICON
3 changes: 3 additions & 0 deletions requirements_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1955,6 +1955,9 @@ xmltodict==0.12.0
# homeassistant.components.xs1
xs1-api-client==2.3.5

# homeassistant.components.yandex_transport
ya_ma==0.3.4

# homeassistant.components.yweather
yahooweather==0.10

Expand Down
1 change: 1 addition & 0 deletions tests/components/yandex_transport/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Tests for the yandex transport platform."""
88 changes: 88 additions & 0 deletions tests/components/yandex_transport/test_yandex_transport_sensor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
"""Tests for the yandex transport platform."""

import json
import pytest

import homeassistant.components.sensor as sensor
import homeassistant.util.dt as dt_util
from homeassistant.const import CONF_NAME
from tests.common import (
assert_setup_component,
async_setup_component,
MockDependency,
load_fixture,
)

REPLY = json.loads(load_fixture("yandex_transport_reply.json"))


@pytest.fixture
def mock_requester():
"""Create a mock ya_ma module and YandexMapsRequester."""
with MockDependency("ya_ma") as ya_ma:
instance = ya_ma.YandexMapsRequester.return_value
instance.get_stop_info.return_value = REPLY
yield instance


STOP_ID = 9639579
ROUTES = ["194", "т36", "т47", "м10"]
NAME = "test_name"
TEST_CONFIG = {
"sensor": {
"platform": "yandex_transport",
"stop_id": 9639579,
"routes": ROUTES,
"name": NAME,
}
}

FILTERED_ATTRS = {
"т36": ["21:43", "21:47", "22:02"],
"т47": ["21:40", "22:01"],
"м10": ["21:48", "22:00"],
"stop_name": "7-й автобусный парк",
"attribution": "Data provided by maps.yandex.ru",
}

RESULT_STATE = dt_util.utc_from_timestamp(1568659253).isoformat(timespec="seconds")


async def assert_setup_sensor(hass, config, count=1):
"""Set up the sensor and assert it's been created."""
with assert_setup_component(count):
assert await async_setup_component(hass, sensor.DOMAIN, config)


async def test_setup_platform_valid_config(hass, mock_requester):
"""Test that sensor is set up properly with valid config."""
await assert_setup_sensor(hass, TEST_CONFIG)


async def test_setup_platform_invalid_config(hass, mock_requester):
"""Check an invalid configuration."""
await assert_setup_sensor(
hass, {"sensor": {"platform": "yandex_transport", "stopid": 1234}}, count=0
)


async def test_name(hass, mock_requester):
"""Return the name if set in the configuration."""
await assert_setup_sensor(hass, TEST_CONFIG)
state = hass.states.get("sensor.test_name")
assert state.name == TEST_CONFIG["sensor"][CONF_NAME]


async def test_state(hass, mock_requester):
"""Return the contents of _state."""
await assert_setup_sensor(hass, TEST_CONFIG)
state = hass.states.get("sensor.test_name")
assert state.state == RESULT_STATE


async def test_filtered_attributes(hass, mock_requester):
"""Return the contents of attributes."""
await assert_setup_sensor(hass, TEST_CONFIG)
state = hass.states.get("sensor.test_name")
state_attrs = {key: state.attributes[key] for key in FILTERED_ATTRS}
assert state_attrs == FILTERED_ATTRS
Loading