diff --git a/homeassistant/components/aftership/sensor.py b/homeassistant/components/aftership/sensor.py index 07267c1185d..2d9021f8009 100644 --- a/homeassistant/components/aftership/sensor.py +++ b/homeassistant/components/aftership/sensor.py @@ -181,8 +181,8 @@ class AfterShipSensor(Entity): track["tracking_number"] if track["title"] is None else track["title"] ) last_checkpoint = ( - "Shipment pending" - if track["tag"] == "Pending" + f"Shipment {track['tag'].lower()}" + if not track["checkpoints"] else track["checkpoints"][-1] ) status_counts[status] = status_counts.get(status, 0) + 1 diff --git a/homeassistant/components/bmw_connected_drive/manifest.json b/homeassistant/components/bmw_connected_drive/manifest.json index 5bce904e1cd..c1d90f713f4 100644 --- a/homeassistant/components/bmw_connected_drive/manifest.json +++ b/homeassistant/components/bmw_connected_drive/manifest.json @@ -2,7 +2,7 @@ "domain": "bmw_connected_drive", "name": "BMW Connected Drive", "documentation": "https://www.home-assistant.io/integrations/bmw_connected_drive", - "requirements": ["bimmer_connected==0.7.13"], + "requirements": ["bimmer_connected==0.7.14"], "codeowners": ["@gerard33", "@rikroe"], "config_flow": true } diff --git a/homeassistant/components/myq/manifest.json b/homeassistant/components/myq/manifest.json index 653a2229296..aba2f24b5bd 100644 --- a/homeassistant/components/myq/manifest.json +++ b/homeassistant/components/myq/manifest.json @@ -2,7 +2,7 @@ "domain": "myq", "name": "MyQ", "documentation": "https://www.home-assistant.io/integrations/myq", - "requirements": ["pymyq==2.0.13"], + "requirements": ["pymyq==2.0.14"], "codeowners": ["@bdraco"], "config_flow": true, "homekit": { diff --git a/homeassistant/components/neato/sensor.py b/homeassistant/components/neato/sensor.py index b083ec1d7df..50af42e7007 100644 --- a/homeassistant/components/neato/sensor.py +++ b/homeassistant/components/neato/sensor.py @@ -37,7 +37,7 @@ class NeatoSensor(Entity): def __init__(self, neato, robot): """Initialize Neato sensor.""" self.robot = robot - self._available = neato is not None + self._available = False self._robot_name = f"{self.robot.name} {BATTERY}" self._robot_serial = self.robot.serial self._state = None diff --git a/homeassistant/components/neato/switch.py b/homeassistant/components/neato/switch.py index 204adb108a8..a3cc51b82c6 100644 --- a/homeassistant/components/neato/switch.py +++ b/homeassistant/components/neato/switch.py @@ -40,7 +40,7 @@ class NeatoConnectedSwitch(ToggleEntity): """Initialize the Neato Connected switches.""" self.type = switch_type self.robot = robot - self._available = neato is not None + self._available = False self._robot_name = f"{self.robot.name} {SWITCH_TYPES[self.type][0]}" self._state = None self._schedule_state = None diff --git a/homeassistant/components/openweathermap/sensor.py b/homeassistant/components/openweathermap/sensor.py index 39c50c3b941..b1ba4ab7625 100644 --- a/homeassistant/components/openweathermap/sensor.py +++ b/homeassistant/components/openweathermap/sensor.py @@ -1,7 +1,10 @@ """Support for the OpenWeatherMap (OWM) service.""" +import datetime + from .abstract_owm_sensor import AbstractOpenWeatherMapSensor from .const import ( ATTR_API_FORECAST, + DEVICE_CLASS_TIMESTAMP, DOMAIN, ENTRY_NAME, ENTRY_WEATHER_COORDINATOR, @@ -95,5 +98,10 @@ class OpenWeatherMapForecastSensor(AbstractOpenWeatherMapSensor): """Return the state of the device.""" forecasts = self._weather_coordinator.data.get(ATTR_API_FORECAST) if forecasts is not None and len(forecasts) > 0: - return forecasts[0].get(self._sensor_type, None) + value = forecasts[0].get(self._sensor_type, None) + if self._device_class is DEVICE_CLASS_TIMESTAMP: + value = datetime.datetime.fromtimestamp( + value, datetime.timezone.utc + ).isoformat() + return value return None diff --git a/homeassistant/components/openweathermap/weather_update_coordinator.py b/homeassistant/components/openweathermap/weather_update_coordinator.py index b4ddb40c046..605e6f9edc1 100644 --- a/homeassistant/components/openweathermap/weather_update_coordinator.py +++ b/homeassistant/components/openweathermap/weather_update_coordinator.py @@ -138,7 +138,7 @@ class WeatherUpdateCoordinator(DataUpdateCoordinator): def _convert_forecast(self, entry): forecast = { - ATTR_FORECAST_TIME: entry.reference_time("unix") * 1000, + ATTR_FORECAST_TIME: entry.reference_time("unix"), ATTR_FORECAST_PRECIPITATION: self._calc_precipitation( entry.rain, entry.snow ), diff --git a/homeassistant/components/utility_meter/sensor.py b/homeassistant/components/utility_meter/sensor.py index 6b25ec7d123..e8d551ba280 100644 --- a/homeassistant/components/utility_meter/sensor.py +++ b/homeassistant/components/utility_meter/sensor.py @@ -169,7 +169,11 @@ class UtilityMeterSensor(RestoreEntity): new_state = event.data.get("new_state") if new_state is None: return - if self._tariff == new_state.state: + + self._change_status(new_state.state) + + def _change_status(self, tariff): + if self._tariff == tariff: self._collecting = async_track_state_change_event( self.hass, [self._sensor_source_id], self.async_reading ) @@ -271,25 +275,26 @@ class UtilityMeterSensor(RestoreEntity): self._last_reset = dt_util.parse_datetime( state.attributes.get(ATTR_LAST_RESET) ) - self.async_write_ha_state() - if state.attributes.get(ATTR_STATUS) == PAUSED: - # Fake cancellation function to init the meter paused + if state.attributes.get(ATTR_STATUS) == COLLECTING: + # Fake cancellation function to init the meter in similar state self._collecting = lambda: None @callback def async_source_tracking(event): """Wait for source to be ready, then start meter.""" if self._tariff_entity is not None: - _LOGGER.debug("Track %s", self._tariff_entity) + _LOGGER.debug( + "<%s> tracks utility meter %s", self.name, self._tariff_entity + ) async_track_state_change_event( self.hass, [self._tariff_entity], self.async_tariff_change ) tariff_entity_state = self.hass.states.get(self._tariff_entity) - if self._tariff != tariff_entity_state.state: - return + self._change_status(tariff_entity_state.state) + return - _LOGGER.debug("tracking source: %s", self._sensor_source_id) + _LOGGER.debug("<%s> collecting from %s", self.name, self._sensor_source_id) self._collecting = async_track_state_change_event( self.hass, [self._sensor_source_id], self.async_reading ) diff --git a/homeassistant/const.py b/homeassistant/const.py index 13ed75196d7..582a5260fb5 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 2021 MINOR_VERSION = 1 -PATCH_VERSION = "1" +PATCH_VERSION = "2" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 7, 1) diff --git a/requirements_all.txt b/requirements_all.txt index 47fd57aeef4..eaca17a952e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -339,7 +339,7 @@ beautifulsoup4==4.9.1 bellows==0.21.0 # homeassistant.components.bmw_connected_drive -bimmer_connected==0.7.13 +bimmer_connected==0.7.14 # homeassistant.components.bizkaibus bizkaibus==0.1.1 @@ -1536,7 +1536,7 @@ pymsteams==0.1.12 pymusiccast==0.1.6 # homeassistant.components.myq -pymyq==2.0.13 +pymyq==2.0.14 # homeassistant.components.mysensors pymysensors==0.18.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 0be9f1a1d96..a8d21dbaa42 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -189,7 +189,7 @@ base36==0.1.1 bellows==0.21.0 # homeassistant.components.bmw_connected_drive -bimmer_connected==0.7.13 +bimmer_connected==0.7.14 # homeassistant.components.blebox blebox_uniapi==1.3.2 @@ -779,7 +779,7 @@ pymodbus==2.3.0 pymonoprice==0.3 # homeassistant.components.myq -pymyq==2.0.13 +pymyq==2.0.14 # homeassistant.components.nut pynut2==2.1.2 diff --git a/tests/components/utility_meter/test_sensor.py b/tests/components/utility_meter/test_sensor.py index 2856a63b4f5..17459912175 100644 --- a/tests/components/utility_meter/test_sensor.py +++ b/tests/components/utility_meter/test_sensor.py @@ -10,17 +10,24 @@ from homeassistant.components.utility_meter.const import ( SERVICE_CALIBRATE_METER, SERVICE_SELECT_TARIFF, ) +from homeassistant.components.utility_meter.sensor import ( + ATTR_LAST_RESET, + ATTR_STATUS, + COLLECTING, + PAUSED, +) from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_UNIT_OF_MEASUREMENT, ENERGY_KILO_WATT_HOUR, EVENT_HOMEASSISTANT_START, ) +from homeassistant.core import State from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util from tests.async_mock import patch -from tests.common import async_fire_time_changed +from tests.common import async_fire_time_changed, mock_restore_cache @contextmanager @@ -55,6 +62,21 @@ async def test_state(hass): ) await hass.async_block_till_done() + state = hass.states.get("sensor.energy_bill_onpeak") + assert state is not None + assert state.state == "0" + assert state.attributes.get("status") == COLLECTING + + state = hass.states.get("sensor.energy_bill_midpeak") + assert state is not None + assert state.state == "0" + assert state.attributes.get("status") == PAUSED + + state = hass.states.get("sensor.energy_bill_offpeak") + assert state is not None + assert state.state == "0" + assert state.attributes.get("status") == PAUSED + now = dt_util.utcnow() + timedelta(seconds=10) with patch("homeassistant.util.dt.utcnow", return_value=now): hass.states.async_set( @@ -68,14 +90,17 @@ async def test_state(hass): state = hass.states.get("sensor.energy_bill_onpeak") assert state is not None assert state.state == "1" + assert state.attributes.get("status") == COLLECTING state = hass.states.get("sensor.energy_bill_midpeak") assert state is not None assert state.state == "0" + assert state.attributes.get("status") == PAUSED state = hass.states.get("sensor.energy_bill_offpeak") assert state is not None assert state.state == "0" + assert state.attributes.get("status") == PAUSED await hass.services.async_call( DOMAIN, @@ -99,14 +124,17 @@ async def test_state(hass): state = hass.states.get("sensor.energy_bill_onpeak") assert state is not None assert state.state == "1" + assert state.attributes.get("status") == PAUSED state = hass.states.get("sensor.energy_bill_midpeak") assert state is not None assert state.state == "0" + assert state.attributes.get("status") == PAUSED state = hass.states.get("sensor.energy_bill_offpeak") assert state is not None assert state.state == "3" + assert state.attributes.get("status") == COLLECTING await hass.services.async_call( DOMAIN, @@ -131,6 +159,65 @@ async def test_state(hass): assert state.state == "0.123" +async def test_restore_state(hass): + """Test utility sensor restore state.""" + config = { + "utility_meter": { + "energy_bill": { + "source": "sensor.energy", + "tariffs": ["onpeak", "midpeak", "offpeak"], + } + } + } + mock_restore_cache( + hass, + [ + State( + "sensor.energy_bill_onpeak", + "3", + attributes={ + ATTR_STATUS: PAUSED, + ATTR_LAST_RESET: "2020-12-21T00:00:00.013073+00:00", + }, + ), + State( + "sensor.energy_bill_offpeak", + "6", + attributes={ + ATTR_STATUS: COLLECTING, + ATTR_LAST_RESET: "2020-12-21T00:00:00.013073+00:00", + }, + ), + ], + ) + + assert await async_setup_component(hass, DOMAIN, config) + assert await async_setup_component(hass, SENSOR_DOMAIN, config) + await hass.async_block_till_done() + + # restore from cache + state = hass.states.get("sensor.energy_bill_onpeak") + assert state.state == "3" + assert state.attributes.get("status") == PAUSED + + state = hass.states.get("sensor.energy_bill_offpeak") + assert state.state == "6" + assert state.attributes.get("status") == COLLECTING + + # utility_meter is loaded, now set sensors according to utility_meter: + hass.bus.async_fire(EVENT_HOMEASSISTANT_START) + await hass.async_block_till_done() + + state = hass.states.get("utility_meter.energy_bill") + assert state.state == "onpeak" + + state = hass.states.get("sensor.energy_bill_onpeak") + assert state.attributes.get("status") == COLLECTING + + state = hass.states.get("sensor.energy_bill_offpeak") + assert state.attributes.get("status") == PAUSED + + async def test_net_consumption(hass): """Test utility sensor state.""" config = {