diff --git a/.coveragerc b/.coveragerc index e45ee866613..4ad141168d8 100644 --- a/.coveragerc +++ b/.coveragerc @@ -98,9 +98,6 @@ omit = homeassistant/components/bme680/sensor.py homeassistant/components/bmp280/sensor.py homeassistant/components/bmw_connected_drive/* - homeassistant/components/bom/camera.py - homeassistant/components/bom/sensor.py - homeassistant/components/bom/weather.py homeassistant/components/braviatv/__init__.py homeassistant/components/braviatv/const.py homeassistant/components/braviatv/media_player.py diff --git a/CODEOWNERS b/CODEOWNERS index 34e92bc8959..91ae8131db8 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -60,7 +60,6 @@ homeassistant/components/blebox/* @gadgetmobile homeassistant/components/blink/* @fronzbot homeassistant/components/bmp280/* @belidzs homeassistant/components/bmw_connected_drive/* @gerard33 @rikroe -homeassistant/components/bom/* @maddenp homeassistant/components/bond/* @prystupa homeassistant/components/braviatv/* @bieniu homeassistant/components/broadlink/* @danielhiversen @felipediel diff --git a/homeassistant/components/bom/__init__.py b/homeassistant/components/bom/__init__.py deleted file mode 100644 index 7b83a5c981b..00000000000 --- a/homeassistant/components/bom/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""The bom component.""" diff --git a/homeassistant/components/bom/camera.py b/homeassistant/components/bom/camera.py deleted file mode 100644 index b99b12916c7..00000000000 --- a/homeassistant/components/bom/camera.py +++ /dev/null @@ -1,132 +0,0 @@ -"""Provide animated GIF loops of BOM radar imagery.""" -from bomradarloop import BOMRadarLoop -import voluptuous as vol - -from homeassistant.components.camera import PLATFORM_SCHEMA, Camera -from homeassistant.const import CONF_ID, CONF_NAME -from homeassistant.helpers import config_validation as cv - -CONF_DELTA = "delta" -CONF_FRAMES = "frames" -CONF_LOCATION = "location" -CONF_OUTFILE = "filename" - -LOCATIONS = [ - "Adelaide", - "Albany", - "AliceSprings", - "Bairnsdale", - "Bowen", - "Brisbane", - "Broome", - "Cairns", - "Canberra", - "Carnarvon", - "Ceduna", - "Dampier", - "Darwin", - "Emerald", - "Esperance", - "Geraldton", - "Giles", - "Gladstone", - "Gove", - "Grafton", - "Gympie", - "HallsCreek", - "Hobart", - "Kalgoorlie", - "Katherine", - "Learmonth", - "Longreach", - "Mackay", - "Marburg", - "Melbourne", - "Mildura", - "Moree", - "MorningtonIs", - "MountIsa", - "MtGambier", - "Namoi", - "Newcastle", - "Newdegate", - "NorfolkIs", - "NWTasmania", - "Perth", - "PortHedland", - "Rainbow", - "SellicksHill", - "SouthDoodlakine", - "Sydney", - "Townsville", - "WaggaWagga", - "Warrego", - "Warruwi", - "Watheroo", - "Weipa", - "WillisIs", - "Wollongong", - "Woomera", - "Wyndham", - "Yarrawonga", -] - - -def _validate_schema(config): - if config.get(CONF_LOCATION) is None: - if not all(config.get(x) for x in (CONF_ID, CONF_DELTA, CONF_FRAMES)): - raise vol.Invalid( - f"Specify '{CONF_ID}', '{CONF_DELTA}' and '{CONF_FRAMES}' when '{CONF_LOCATION}' is unspecified" - ) - return config - - -LOCATIONS_MSG = f"Set '{CONF_LOCATION}' to one of: {', '.join(sorted(LOCATIONS))}" -XOR_MSG = f"Specify exactly one of '{CONF_ID}' or '{CONF_LOCATION}'" - -PLATFORM_SCHEMA = vol.All( - PLATFORM_SCHEMA.extend( - { - vol.Exclusive(CONF_ID, "xor", msg=XOR_MSG): cv.string, - vol.Exclusive(CONF_LOCATION, "xor", msg=XOR_MSG): vol.In( - LOCATIONS, msg=LOCATIONS_MSG - ), - vol.Optional(CONF_DELTA): cv.positive_int, - vol.Optional(CONF_FRAMES): cv.positive_int, - vol.Optional(CONF_NAME): cv.string, - vol.Optional(CONF_OUTFILE): cv.string, - } - ), - _validate_schema, -) - - -def setup_platform(hass, config, add_entities, discovery_info=None): - """Set up BOM radar-loop camera component.""" - location = config.get(CONF_LOCATION) or f"ID {config.get(CONF_ID)}" - name = config.get(CONF_NAME) or f"BOM Radar Loop - {location}" - args = [ - config.get(x) - for x in (CONF_LOCATION, CONF_ID, CONF_DELTA, CONF_FRAMES, CONF_OUTFILE) - ] - add_entities([BOMRadarCam(name, *args)]) - - -class BOMRadarCam(Camera): - """A camera component producing animated BOM radar-imagery GIFs.""" - - def __init__(self, name, location, radar_id, delta, frames, outfile): - """Initialize the component.""" - - super().__init__() - self._name = name - self._cam = BOMRadarLoop(location, radar_id, delta, frames, outfile) - - def camera_image(self): - """Return the current BOM radar-loop image.""" - return self._cam.current - - @property - def name(self): - """Return the component name.""" - return self._name diff --git a/homeassistant/components/bom/manifest.json b/homeassistant/components/bom/manifest.json deleted file mode 100644 index a712c8fd080..00000000000 --- a/homeassistant/components/bom/manifest.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "domain": "bom", - "name": "Australian Bureau of Meteorology (BOM)", - "documentation": "https://www.home-assistant.io/integrations/bom", - "requirements": ["bomradarloop==0.1.5"], - "codeowners": ["@maddenp"] -} diff --git a/homeassistant/components/bom/sensor.py b/homeassistant/components/bom/sensor.py deleted file mode 100644 index 0470ea6e970..00000000000 --- a/homeassistant/components/bom/sensor.py +++ /dev/null @@ -1,355 +0,0 @@ -"""Support for Australian BOM (Bureau of Meteorology) weather service.""" -import datetime -import ftplib -import gzip -import io -import json -import logging -import os -import re -import zipfile - -import requests -import voluptuous as vol - -from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import ( - ATTR_ATTRIBUTION, - CONF_LATITUDE, - CONF_LONGITUDE, - CONF_MONITORED_CONDITIONS, - CONF_NAME, - LENGTH_KILOMETERS, - LENGTH_METERS, - LENGTH_MILLIMETERS, - PERCENTAGE, - PRESSURE_MBAR, - SPEED_KILOMETERS_PER_HOUR, - TEMP_CELSIUS, -) -import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity -from homeassistant.util import Throttle -import homeassistant.util.dt as dt_util - -_LOGGER = logging.getLogger(__name__) - -ATTR_LAST_UPDATE = "last_update" -ATTR_SENSOR_ID = "sensor_id" -ATTR_STATION_ID = "station_id" -ATTR_STATION_NAME = "station_name" -ATTR_ZONE_ID = "zone_id" - -ATTRIBUTION = "Data provided by the Australian Bureau of Meteorology" - -CONF_STATION = "station" -CONF_ZONE_ID = "zone_id" -CONF_WMO_ID = "wmo_id" - -MIN_TIME_BETWEEN_UPDATES = datetime.timedelta(seconds=60) - -SENSOR_TYPES = { - "wmo": ["wmo", None], - "name": ["Station Name", None], - "history_product": ["Zone", None], - "local_date_time": ["Local Time", None], - "local_date_time_full": ["Local Time Full", None], - "aifstime_utc": ["UTC Time Full", None], - "lat": ["Lat", None], - "lon": ["Long", None], - "apparent_t": ["Feels Like C", TEMP_CELSIUS], - "cloud": ["Cloud", None], - "cloud_base_m": ["Cloud Base", None], - "cloud_oktas": ["Cloud Oktas", None], - "cloud_type_id": ["Cloud Type ID", None], - "cloud_type": ["Cloud Type", None], - "delta_t": ["Delta Temp C", TEMP_CELSIUS], - "gust_kmh": ["Wind Gust kmh", SPEED_KILOMETERS_PER_HOUR], - "gust_kt": ["Wind Gust kt", "kt"], - "air_temp": ["Air Temp C", TEMP_CELSIUS], - "dewpt": ["Dew Point C", TEMP_CELSIUS], - "press": ["Pressure mb", PRESSURE_MBAR], - "press_qnh": ["Pressure qnh", "qnh"], - "press_msl": ["Pressure msl", "msl"], - "press_tend": ["Pressure Tend", None], - "rain_trace": ["Rain Today", LENGTH_MILLIMETERS], - "rel_hum": ["Relative Humidity", PERCENTAGE], - "sea_state": ["Sea State", None], - "swell_dir_worded": ["Swell Direction", None], - "swell_height": ["Swell Height", LENGTH_METERS], - "swell_period": ["Swell Period", None], - "vis_km": [f"Visability {LENGTH_KILOMETERS}", LENGTH_KILOMETERS], - "weather": ["Weather", None], - "wind_dir": ["Wind Direction", None], - "wind_spd_kmh": ["Wind Speed kmh", SPEED_KILOMETERS_PER_HOUR], - "wind_spd_kt": ["Wind Speed kt", "kt"], -} - - -def validate_station(station): - """Check that the station ID is well-formed.""" - if station is None: - return - station = station.replace(".shtml", "") - if not re.fullmatch(r"ID[A-Z]\d\d\d\d\d\.\d\d\d\d\d", station): - raise vol.error.Invalid("Malformed station ID") - return station - - -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( - { - vol.Inclusive(CONF_ZONE_ID, "Deprecated partial station ID"): cv.string, - vol.Inclusive(CONF_WMO_ID, "Deprecated partial station ID"): cv.string, - vol.Optional(CONF_NAME): cv.string, - vol.Optional(CONF_STATION): validate_station, - vol.Required(CONF_MONITORED_CONDITIONS, default=[]): vol.All( - cv.ensure_list, [vol.In(SENSOR_TYPES)] - ), - } -) - - -def setup_platform(hass, config, add_entities, discovery_info=None): - """Set up the BOM sensor.""" - station = config.get(CONF_STATION) - zone_id, wmo_id = config.get(CONF_ZONE_ID), config.get(CONF_WMO_ID) - - if station is not None: - if zone_id and wmo_id: - _LOGGER.warning( - "Using configuration %s, not %s and %s for BOM sensor", - CONF_STATION, - CONF_ZONE_ID, - CONF_WMO_ID, - ) - elif zone_id and wmo_id: - station = f"{zone_id}.{wmo_id}" - else: - station = closest_station( - config.get(CONF_LATITUDE), - config.get(CONF_LONGITUDE), - hass.config.config_dir, - ) - if station is None: - _LOGGER.error("Could not get BOM weather station from lat/lon") - return - - bom_data = BOMCurrentData(station) - - try: - bom_data.update() - except ValueError as err: - _LOGGER.error("Received error from BOM Current: %s", err) - return - - add_entities( - [ - BOMCurrentSensor(bom_data, variable, config.get(CONF_NAME)) - for variable in config[CONF_MONITORED_CONDITIONS] - ] - ) - - -class BOMCurrentSensor(Entity): - """Implementation of a BOM current sensor.""" - - def __init__(self, bom_data, condition, stationname): - """Initialize the sensor.""" - self.bom_data = bom_data - self._condition = condition - self.stationname = stationname - - @property - def name(self): - """Return the name of the sensor.""" - if self.stationname is None: - return f"BOM {SENSOR_TYPES[self._condition][0]}" - - return f"BOM {self.stationname} {SENSOR_TYPES[self._condition][0]}" - - @property - def state(self): - """Return the state of the sensor.""" - return self.bom_data.get_reading(self._condition) - - @property - def device_state_attributes(self): - """Return the state attributes of the device.""" - return { - ATTR_ATTRIBUTION: ATTRIBUTION, - ATTR_LAST_UPDATE: self.bom_data.last_updated, - ATTR_SENSOR_ID: self._condition, - ATTR_STATION_ID: self.bom_data.latest_data["wmo"], - ATTR_STATION_NAME: self.bom_data.latest_data["name"], - ATTR_ZONE_ID: self.bom_data.latest_data["history_product"], - } - - @property - def unit_of_measurement(self): - """Return the units of measurement.""" - return SENSOR_TYPES[self._condition][1] - - def update(self): - """Update current conditions.""" - self.bom_data.update() - - -class BOMCurrentData: - """Get data from BOM.""" - - def __init__(self, station_id): - """Initialize the data object.""" - self._zone_id, self._wmo_id = station_id.split(".") - self._data = None - self.last_updated = None - - def _build_url(self): - """Build the URL for the requests.""" - url = ( - f"http://www.bom.gov.au/fwo/{self._zone_id}" - f"/{self._zone_id}.{self._wmo_id}.json" - ) - _LOGGER.debug("BOM URL: %s", url) - return url - - @property - def latest_data(self): - """Return the latest data object.""" - if self._data: - return self._data[0] - return None - - def get_reading(self, condition): - """Return the value for the given condition. - - BOM weather publishes condition readings for weather (and a few other - conditions) at intervals throughout the day. To avoid a `-` value in - the frontend for these conditions, we traverse the historical data - for the latest value that is not `-`. - - Iterators are used in this method to avoid iterating needlessly - through the entire BOM provided dataset. - """ - condition_readings = (entry[condition] for entry in self._data) - reading = next((x for x in condition_readings if x != "-"), None) - - if isinstance(reading, (int, float)): - return round(reading, 2) - return reading - - def should_update(self): - """Determine whether an update should occur. - - BOM provides updated data every 30 minutes. We manually define - refreshing logic here rather than a throttle to keep updates - in lock-step with BOM. - - If 35 minutes has passed since the last BOM data update, then - an update should be done. - """ - if self.last_updated is None: - # Never updated before, therefore an update should occur. - return True - - now = dt_util.utcnow() - update_due_at = self.last_updated + datetime.timedelta(minutes=35) - return now > update_due_at - - @Throttle(MIN_TIME_BETWEEN_UPDATES) - def update(self): - """Get the latest data from BOM.""" - if not self.should_update(): - _LOGGER.debug( - "BOM was updated %s minutes ago, skipping update as" - " < 35 minutes, Now: %s, LastUpdate: %s", - (dt_util.utcnow() - self.last_updated), - dt_util.utcnow(), - self.last_updated, - ) - return - - try: - result = requests.get(self._build_url(), timeout=10).json() - self._data = result["observations"]["data"] - - # set lastupdate using self._data[0] as the first element in the - # array is the latest date in the json - self.last_updated = dt_util.as_utc( - datetime.datetime.strptime( - str(self._data[0]["local_date_time_full"]), "%Y%m%d%H%M%S" - ) - ) - return - - except ValueError as err: - _LOGGER.error("Check BOM %s", err.args) - self._data = None - raise - - -def _get_bom_stations(): - """Return {CONF_STATION: (lat, lon)} for all stations, for auto-config. - - This function does several MB of internet requests, so please use the - caching version to minimize latency and hit-count. - """ - latlon = {} - with io.BytesIO() as file_obj: - with ftplib.FTP("ftp.bom.gov.au") as ftp: - ftp.login() - ftp.cwd("anon2/home/ncc/metadata/sitelists") - ftp.retrbinary("RETR stations.zip", file_obj.write) - file_obj.seek(0) - with zipfile.ZipFile(file_obj) as zipped: - with zipped.open("stations.txt") as station_txt: - for _ in range(4): - station_txt.readline() # skip header - while True: - line = station_txt.readline().decode().strip() - if len(line) < 120: - break # end while loop, ignoring any footer text - wmo, lat, lon = ( - line[a:b].strip() for a, b in [(128, 134), (70, 78), (79, 88)] - ) - if wmo != "..": - latlon[wmo] = (float(lat), float(lon)) - zones = {} - pattern = ( - r'' - ) - for state in ("nsw", "vic", "qld", "wa", "tas", "nt"): - url = f"http://www.bom.gov.au/{state}/observations/{state}all.shtml" - for zone_id, wmo_id in re.findall(pattern, requests.get(url).text): - zones[wmo_id] = zone_id - return {f"{zones[k]}.{k}": latlon[k] for k in set(latlon) & set(zones)} - - -def bom_stations(cache_dir): - """Return {CONF_STATION: (lat, lon)} for all stations, for auto-config. - - Results from internet requests are cached as compressed JSON, making - subsequent calls very much faster. - """ - cache_file = os.path.join(cache_dir, ".bom-stations.json.gz") - if not os.path.isfile(cache_file): - stations = _get_bom_stations() - with gzip.open(cache_file, "wt") as cache: - json.dump(stations, cache, sort_keys=True) - return stations - with gzip.open(cache_file, "rt") as cache: - return {k: tuple(v) for k, v in json.load(cache).items()} - - -def closest_station(lat, lon, cache_dir): - """Return the ZONE_ID.WMO_ID of the closest station to our lat/lon.""" - if lat is None or lon is None or not os.path.isdir(cache_dir): - return - stations = bom_stations(cache_dir) - - def comparable_dist(wmo_id): - """Create a psudeo-distance from latitude/longitude.""" - station_lat, station_lon = stations[wmo_id] - return (lat - station_lat) ** 2 + (lon - station_lon) ** 2 - - return min(stations, key=comparable_dist) diff --git a/homeassistant/components/bom/weather.py b/homeassistant/components/bom/weather.py deleted file mode 100644 index 9229d0c11d4..00000000000 --- a/homeassistant/components/bom/weather.py +++ /dev/null @@ -1,115 +0,0 @@ -"""Support for Australian BOM (Bureau of Meteorology) weather service.""" -import logging - -import voluptuous as vol - -from homeassistant.components.weather import PLATFORM_SCHEMA, WeatherEntity -from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME, TEMP_CELSIUS -from homeassistant.helpers import config_validation as cv - -# Reuse data and API logic from the sensor implementation -from .sensor import CONF_STATION, BOMCurrentData, closest_station, validate_station - -_LOGGER = logging.getLogger(__name__) - -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( - {vol.Optional(CONF_NAME): cv.string, vol.Optional(CONF_STATION): validate_station} -) - - -def setup_platform(hass, config, add_entities, discovery_info=None): - """Set up the BOM weather platform.""" - station = config.get(CONF_STATION) or closest_station( - config.get(CONF_LATITUDE), config.get(CONF_LONGITUDE), hass.config.config_dir - ) - if station is None: - _LOGGER.error("Could not get BOM weather station from lat/lon") - return False - bom_data = BOMCurrentData(station) - try: - bom_data.update() - except ValueError as err: - _LOGGER.error("Received error from BOM_Current: %s", err) - return False - add_entities([BOMWeather(bom_data, config.get(CONF_NAME))], True) - - -class BOMWeather(WeatherEntity): - """Representation of a weather condition.""" - - def __init__(self, bom_data, stationname=None): - """Initialise the platform with a data instance and station name.""" - self.bom_data = bom_data - self.stationname = stationname or self.bom_data.latest_data.get("name") - - def update(self): - """Update current conditions.""" - self.bom_data.update() - - @property - def name(self): - """Return the name of the sensor.""" - return f"BOM {self.stationname or '(unknown station)'}" - - @property - def condition(self): - """Return the current condition.""" - return self.bom_data.get_reading("weather") or self.bom_data.get_reading( - "cloud" - ) - - # Now implement the WeatherEntity interface - - @property - def temperature(self): - """Return the platform temperature.""" - return self.bom_data.get_reading("air_temp") - - @property - def temperature_unit(self): - """Return the unit of measurement.""" - return TEMP_CELSIUS - - @property - def pressure(self): - """Return the mean sea-level pressure.""" - return self.bom_data.get_reading("press_msl") - - @property - def humidity(self): - """Return the relative humidity.""" - return self.bom_data.get_reading("rel_hum") - - @property - def wind_speed(self): - """Return the wind speed.""" - return self.bom_data.get_reading("wind_spd_kmh") - - @property - def wind_bearing(self): - """Return the wind bearing.""" - directions = [ - "N", - "NNE", - "NE", - "ENE", - "E", - "ESE", - "SE", - "SSE", - "S", - "SSW", - "SW", - "WSW", - "W", - "WNW", - "NW", - "NNW", - ] - wind = {name: idx * 360 / 16 for idx, name in enumerate(directions)} - return wind.get(self.bom_data.get_reading("wind_dir")) - - @property - def attribution(self): - """Return the attribution.""" - return "Data provided by the Australian Bureau of Meteorology" diff --git a/requirements_all.txt b/requirements_all.txt index 380b13d391f..fb800aed023 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -369,9 +369,6 @@ blockchain==1.4.4 # homeassistant.components.bme680 # bme680==1.0.5 -# homeassistant.components.bom -bomradarloop==0.1.5 - # homeassistant.components.bond bond-api==0.1.8 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index c63473b4464..1c661d78970 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -197,9 +197,6 @@ blebox_uniapi==1.3.2 # homeassistant.components.blink blinkpy==0.16.3 -# homeassistant.components.bom -bomradarloop==0.1.5 - # homeassistant.components.bond bond-api==0.1.8 diff --git a/tests/components/bom/__init__.py b/tests/components/bom/__init__.py deleted file mode 100644 index a129618ef2c..00000000000 --- a/tests/components/bom/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Tests for the bom component.""" diff --git a/tests/components/bom/test_sensor.py b/tests/components/bom/test_sensor.py deleted file mode 100644 index 49cdb3e6ee3..00000000000 --- a/tests/components/bom/test_sensor.py +++ /dev/null @@ -1,104 +0,0 @@ -"""The tests for the BOM Weather sensor platform.""" -import json -import re -import unittest -from urllib.parse import urlparse - -import requests - -from homeassistant.components import sensor -from homeassistant.components.bom.sensor import BOMCurrentData -from homeassistant.setup import setup_component - -from tests.async_mock import patch -from tests.common import assert_setup_component, get_test_home_assistant, load_fixture - -VALID_CONFIG = { - "platform": "bom", - "station": "IDN60901.94767", - "name": "Fake", - "monitored_conditions": ["apparent_t", "press", "weather"], -} - - -def mocked_requests(*args, **kwargs): - """Mock requests.get invocations.""" - - class MockResponse: - """Class to represent a mocked response.""" - - def __init__(self, json_data, status_code): - """Initialize the mock response class.""" - self.json_data = json_data - self.status_code = status_code - - def json(self): - """Return the json of the response.""" - return self.json_data - - @property - def content(self): - """Return the content of the response.""" - return self.json() - - def raise_for_status(self): - """Raise an HTTPError if status is not 200.""" - if self.status_code != 200: - raise requests.HTTPError(self.status_code) - - url = urlparse(args[0]) - if re.match(r"^/fwo/[\w]+/[\w.]+\.json", url.path): - return MockResponse(json.loads(load_fixture("bom_weather.json")), 200) - - raise NotImplementedError(f"Unknown route {url.path}") - - -class TestBOMWeatherSensor(unittest.TestCase): - """Test the BOM Weather sensor.""" - - def setUp(self): - """Set up things to be run when tests are started.""" - self.hass = get_test_home_assistant() - self.config = VALID_CONFIG - self.addCleanup(self.hass.stop) - - @patch("requests.get", side_effect=mocked_requests) - def test_setup(self, mock_get): - """Test the setup with custom settings.""" - with assert_setup_component(1, sensor.DOMAIN): - assert setup_component(self.hass, sensor.DOMAIN, {"sensor": VALID_CONFIG}) - self.hass.block_till_done() - - fake_entities = [ - "bom_fake_feels_like_c", - "bom_fake_pressure_mb", - "bom_fake_weather", - ] - - for entity_id in fake_entities: - state = self.hass.states.get(f"sensor.{entity_id}") - assert state is not None - - @patch("requests.get", side_effect=mocked_requests) - def test_sensor_values(self, mock_get): - """Test retrieval of sensor values.""" - assert setup_component(self.hass, sensor.DOMAIN, {"sensor": VALID_CONFIG}) - self.hass.block_till_done() - - weather = self.hass.states.get("sensor.bom_fake_weather").state - assert "Fine" == weather - - pressure = self.hass.states.get("sensor.bom_fake_pressure_mb").state - assert "1021.7" == pressure - - feels_like = self.hass.states.get("sensor.bom_fake_feels_like_c").state - assert "25.0" == feels_like - - -class TestBOMCurrentData(unittest.TestCase): - """Test the BOM data container.""" - - def test_should_update_initial(self): - """Test that the first update always occurs.""" - bom_data = BOMCurrentData("IDN60901.94767") - assert bom_data.should_update() is True