mirror of
https://github.com/home-assistant/core.git
synced 2025-04-24 09:17:53 +00:00
Add sensor.nsw_fuel_station component (#14757)
* Add sensor.nsw_fuel_station component * bump dependency * PR Changes * flake8 * Use MockPrice * Fix requirements * Fix tests * line length * wip * Handle errors and show persistent notification * update tests * Address @MartinHjelmare's comments * Fetch station name from API * Update tests * Update requirements * Address comments
This commit is contained in:
parent
cccd0deb65
commit
cdd111df49
@ -655,6 +655,7 @@ omit =
|
||||
homeassistant/components/sensor/nederlandse_spoorwegen.py
|
||||
homeassistant/components/sensor/netdata.py
|
||||
homeassistant/components/sensor/neurio_energy.py
|
||||
homeassistant/components/sensor/nsw_fuel_station.py
|
||||
homeassistant/components/sensor/nut.py
|
||||
homeassistant/components/sensor/nzbget.py
|
||||
homeassistant/components/sensor/ohmconnect.py
|
||||
|
@ -70,6 +70,7 @@ homeassistant/components/sensor/filter.py @dgomes
|
||||
homeassistant/components/sensor/gearbest.py @HerrHofrat
|
||||
homeassistant/components/sensor/irish_rail_transport.py @ttroy50
|
||||
homeassistant/components/sensor/miflora.py @danielhiversen @ChristianKuehnel
|
||||
homeassistant/components/sensor/nsw_fuel_station.py @nickw444
|
||||
homeassistant/components/sensor/pollen.py @bachya
|
||||
homeassistant/components/sensor/qnap.py @colinodell
|
||||
homeassistant/components/sensor/sma.py @kellerza
|
||||
|
174
homeassistant/components/sensor/nsw_fuel_station.py
Normal file
174
homeassistant/components/sensor/nsw_fuel_station.py
Normal file
@ -0,0 +1,174 @@
|
||||
"""
|
||||
Sensor platform to display the current fuel prices at a NSW fuel station.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/sensor.nsw_fuel_station/
|
||||
"""
|
||||
import datetime
|
||||
import logging
|
||||
from typing import Optional
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.components.light import PLATFORM_SCHEMA
|
||||
from homeassistant.const import ATTR_ATTRIBUTION
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.util import Throttle
|
||||
|
||||
REQUIREMENTS = ['nsw-fuel-api-client==1.0.10']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
ATTR_STATION_ID = 'station_id'
|
||||
ATTR_STATION_NAME = 'station_name'
|
||||
|
||||
CONF_STATION_ID = 'station_id'
|
||||
CONF_FUEL_TYPES = 'fuel_types'
|
||||
CONF_ALLOWED_FUEL_TYPES = ["E10", "U91", "E85", "P95", "P98", "DL",
|
||||
"PDL", "B20", "LPG", "CNG", "EV"]
|
||||
CONF_DEFAULT_FUEL_TYPES = ["E10", "U91"]
|
||||
CONF_ATTRIBUTION = "Data provided by NSW Government FuelCheck"
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_STATION_ID): cv.positive_int,
|
||||
vol.Optional(CONF_FUEL_TYPES, default=CONF_DEFAULT_FUEL_TYPES):
|
||||
vol.All(cv.ensure_list, [vol.In(CONF_ALLOWED_FUEL_TYPES)]),
|
||||
})
|
||||
|
||||
MIN_TIME_BETWEEN_UPDATES = datetime.timedelta(hours=1)
|
||||
|
||||
NOTIFICATION_ID = 'nsw_fuel_station_notification'
|
||||
NOTIFICATION_TITLE = 'NSW Fuel Station Sensor Setup'
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Set up the NSW Fuel Station sensor."""
|
||||
from nsw_fuel import FuelCheckClient
|
||||
|
||||
station_id = config[CONF_STATION_ID]
|
||||
fuel_types = config[CONF_FUEL_TYPES]
|
||||
|
||||
client = FuelCheckClient()
|
||||
station_data = StationPriceData(client, station_id)
|
||||
station_data.update()
|
||||
|
||||
if station_data.error is not None:
|
||||
message = (
|
||||
'Error: {}. Check the logs for additional information.'
|
||||
).format(station_data.error)
|
||||
|
||||
hass.components.persistent_notification.create(
|
||||
message,
|
||||
title=NOTIFICATION_TITLE,
|
||||
notification_id=NOTIFICATION_ID)
|
||||
return
|
||||
|
||||
available_fuel_types = station_data.get_available_fuel_types()
|
||||
|
||||
add_devices([
|
||||
StationPriceSensor(station_data, fuel_type)
|
||||
for fuel_type in fuel_types
|
||||
if fuel_type in available_fuel_types
|
||||
])
|
||||
|
||||
|
||||
class StationPriceData(object):
|
||||
"""An object to store and fetch the latest data for a given station."""
|
||||
|
||||
def __init__(self, client, station_id: int) -> None:
|
||||
"""Initialize the sensor."""
|
||||
self.station_id = station_id
|
||||
self._client = client
|
||||
self._data = None
|
||||
self._reference_data = None
|
||||
self.error = None
|
||||
self._station_name = None
|
||||
|
||||
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
||||
def update(self):
|
||||
"""Update the internal data using the API client."""
|
||||
from nsw_fuel import FuelCheckError
|
||||
|
||||
if self._reference_data is None:
|
||||
try:
|
||||
self._reference_data = self._client.get_reference_data()
|
||||
except FuelCheckError as exc:
|
||||
self.error = str(exc)
|
||||
_LOGGER.error(
|
||||
'Failed to fetch NSW Fuel station reference data. %s', exc)
|
||||
return
|
||||
|
||||
try:
|
||||
self._data = self._client.get_fuel_prices_for_station(
|
||||
self.station_id)
|
||||
except FuelCheckError as exc:
|
||||
self.error = str(exc)
|
||||
_LOGGER.error(
|
||||
'Failed to fetch NSW Fuel station price data. %s', exc)
|
||||
|
||||
def for_fuel_type(self, fuel_type: str):
|
||||
"""Return the price of the given fuel type."""
|
||||
if self._data is None:
|
||||
return None
|
||||
return next((price for price
|
||||
in self._data if price.fuel_type == fuel_type), None)
|
||||
|
||||
def get_available_fuel_types(self):
|
||||
"""Return the available fuel types for the station."""
|
||||
return [price.fuel_type for price in self._data]
|
||||
|
||||
def get_station_name(self) -> str:
|
||||
"""Return the name of the station."""
|
||||
if self._station_name is None:
|
||||
name = None
|
||||
if self._reference_data is not None:
|
||||
name = next((station.name for station
|
||||
in self._reference_data.stations
|
||||
if station.code == self.station_id), None)
|
||||
|
||||
self._station_name = name or 'station {}'.format(self.station_id)
|
||||
|
||||
return self._station_name
|
||||
|
||||
|
||||
class StationPriceSensor(Entity):
|
||||
"""Implementation of a sensor that reports the fuel price for a station."""
|
||||
|
||||
def __init__(self, station_data: StationPriceData, fuel_type: str):
|
||||
"""Initialize the sensor."""
|
||||
self._station_data = station_data
|
||||
self._fuel_type = fuel_type
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
"""Return the name of the sensor."""
|
||||
return '{} {}'.format(
|
||||
self._station_data.get_station_name(), self._fuel_type)
|
||||
|
||||
@property
|
||||
def state(self) -> Optional[float]:
|
||||
"""Return the state of the sensor."""
|
||||
price_info = self._station_data.for_fuel_type(self._fuel_type)
|
||||
if price_info:
|
||||
return price_info.price
|
||||
|
||||
return None
|
||||
|
||||
@property
|
||||
def device_state_attributes(self) -> dict:
|
||||
"""Return the state attributes of the device."""
|
||||
return {
|
||||
ATTR_STATION_ID: self._station_data.station_id,
|
||||
ATTR_STATION_NAME: self._station_data.get_station_name(),
|
||||
ATTR_ATTRIBUTION: CONF_ATTRIBUTION
|
||||
}
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self) -> str:
|
||||
"""Return the units of measurement."""
|
||||
return '¢/L'
|
||||
|
||||
def update(self):
|
||||
"""Update current conditions."""
|
||||
self._station_data.update()
|
@ -597,6 +597,9 @@ neurio==0.3.1
|
||||
# homeassistant.components.sensor.nederlandse_spoorwegen
|
||||
nsapi==2.7.4
|
||||
|
||||
# homeassistant.components.sensor.nsw_fuel_station
|
||||
nsw-fuel-api-client==1.0.10
|
||||
|
||||
# homeassistant.components.nuheat
|
||||
nuheat==0.3.0
|
||||
|
||||
|
117
tests/components/sensor/test_nsw_fuel_station.py
Normal file
117
tests/components/sensor/test_nsw_fuel_station.py
Normal file
@ -0,0 +1,117 @@
|
||||
"""The tests for the NSW Fuel Station sensor platform."""
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
from homeassistant.components import sensor
|
||||
from homeassistant.setup import setup_component
|
||||
from tests.common import (
|
||||
get_test_home_assistant, assert_setup_component, MockDependency)
|
||||
|
||||
VALID_CONFIG = {
|
||||
'platform': 'nsw_fuel_station',
|
||||
'station_id': 350,
|
||||
'fuel_types': ['E10', 'P95'],
|
||||
}
|
||||
|
||||
|
||||
class MockPrice():
|
||||
"""Mock Price implementation."""
|
||||
|
||||
def __init__(self, price, fuel_type, last_updated,
|
||||
price_unit, station_code):
|
||||
"""Initialize a mock price instance."""
|
||||
self.price = price
|
||||
self.fuel_type = fuel_type
|
||||
self.last_updated = last_updated
|
||||
self.price_unit = price_unit
|
||||
self.station_code = station_code
|
||||
|
||||
|
||||
class MockStation():
|
||||
"""Mock Station implementation."""
|
||||
|
||||
def __init__(self, name, code):
|
||||
"""Initialize a mock Station instance."""
|
||||
self.name = name
|
||||
self.code = code
|
||||
|
||||
|
||||
class MockGetReferenceDataResponse():
|
||||
"""Mock GetReferenceDataResponse implementation."""
|
||||
|
||||
def __init__(self, stations):
|
||||
"""Initialize a mock GetReferenceDataResponse instance."""
|
||||
self.stations = stations
|
||||
|
||||
|
||||
class FuelCheckClientMock():
|
||||
"""Mock FuelCheckClient implementation."""
|
||||
|
||||
def get_fuel_prices_for_station(self, station):
|
||||
"""Return a fake fuel prices response."""
|
||||
return [
|
||||
MockPrice(
|
||||
price=150.0,
|
||||
fuel_type='P95',
|
||||
last_updated=None,
|
||||
price_unit=None,
|
||||
station_code=350
|
||||
),
|
||||
MockPrice(
|
||||
price=140.0,
|
||||
fuel_type='E10',
|
||||
last_updated=None,
|
||||
price_unit=None,
|
||||
station_code=350
|
||||
)
|
||||
]
|
||||
|
||||
def get_reference_data(self):
|
||||
"""Return a fake reference data response."""
|
||||
return MockGetReferenceDataResponse(
|
||||
stations=[
|
||||
MockStation(code=350, name="My Fake Station")
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
class TestNSWFuelStation(unittest.TestCase):
|
||||
"""Test the NSW Fuel Station sensor platform."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up things to be run when tests are started."""
|
||||
self.hass = get_test_home_assistant()
|
||||
self.config = VALID_CONFIG
|
||||
|
||||
def tearDown(self):
|
||||
"""Stop everything that was started."""
|
||||
self.hass.stop()
|
||||
|
||||
@MockDependency('nsw_fuel')
|
||||
@patch('nsw_fuel.FuelCheckClient', new=FuelCheckClientMock)
|
||||
def test_setup(self, mock_nsw_fuel):
|
||||
"""Test the setup with custom settings."""
|
||||
with assert_setup_component(1, sensor.DOMAIN):
|
||||
self.assertTrue(setup_component(self.hass, sensor.DOMAIN, {
|
||||
'sensor': VALID_CONFIG}))
|
||||
|
||||
fake_entities = [
|
||||
'my_fake_station_p95',
|
||||
'my_fake_station_e10'
|
||||
]
|
||||
|
||||
for entity_id in fake_entities:
|
||||
state = self.hass.states.get('sensor.{}'.format(entity_id))
|
||||
self.assertIsNotNone(state)
|
||||
|
||||
@MockDependency('nsw_fuel')
|
||||
@patch('nsw_fuel.FuelCheckClient', new=FuelCheckClientMock)
|
||||
def test_sensor_values(self, mock_nsw_fuel):
|
||||
"""Test retrieval of sensor values."""
|
||||
self.assertTrue(setup_component(
|
||||
self.hass, sensor.DOMAIN, {'sensor': VALID_CONFIG}))
|
||||
|
||||
self.assertEqual('140.0', self.hass.states.get(
|
||||
'sensor.my_fake_station_e10').state)
|
||||
self.assertEqual('150.0', self.hass.states.get(
|
||||
'sensor.my_fake_station_p95').state)
|
Loading…
x
Reference in New Issue
Block a user