Merge pull request #34178 from home-assistant/rc

0.108.4
This commit is contained in:
Paulus Schoutsen 2020-04-13 18:17:03 -07:00 committed by GitHub
commit 615a346a39
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 205 additions and 109 deletions

View File

@ -1,17 +1,11 @@
"""Support for Daikin AC sensors."""
import logging
from homeassistant.const import CONF_ICON, CONF_NAME, CONF_TYPE
from homeassistant.const import CONF_ICON, CONF_NAME, TEMP_CELSIUS
from homeassistant.helpers.entity import Entity
from homeassistant.util.unit_system import UnitSystem
from . import DOMAIN as DAIKIN_DOMAIN
from .const import (
ATTR_INSIDE_TEMPERATURE,
ATTR_OUTSIDE_TEMPERATURE,
SENSOR_TYPE_TEMPERATURE,
SENSOR_TYPES,
)
from .const import ATTR_INSIDE_TEMPERATURE, ATTR_OUTSIDE_TEMPERATURE, SENSOR_TYPES
_LOGGER = logging.getLogger(__name__)
@ -31,30 +25,19 @@ async def async_setup_entry(hass, entry, async_add_entities):
sensors = [ATTR_INSIDE_TEMPERATURE]
if daikin_api.device.support_outside_temperature:
sensors.append(ATTR_OUTSIDE_TEMPERATURE)
async_add_entities(
[
DaikinClimateSensor(daikin_api, sensor, hass.config.units)
for sensor in sensors
]
)
async_add_entities([DaikinClimateSensor(daikin_api, sensor) for sensor in sensors])
class DaikinClimateSensor(Entity):
"""Representation of a Sensor."""
def __init__(self, api, monitored_state, units: UnitSystem, name=None) -> None:
def __init__(self, api, monitored_state) -> None:
"""Initialize the sensor."""
self._api = api
self._sensor = SENSOR_TYPES.get(monitored_state)
if name is None:
name = f"{self._sensor[CONF_NAME]} {api.name}"
self._name = f"{name} {monitored_state.replace('_', ' ')}"
self._sensor = SENSOR_TYPES[monitored_state]
self._name = f"{api.name} {self._sensor[CONF_NAME]}"
self._device_attribute = monitored_state
if self._sensor[CONF_TYPE] == SENSOR_TYPE_TEMPERATURE:
self._unit_of_measurement = units.temperature_unit
@property
def unique_id(self):
"""Return a unique ID."""
@ -82,7 +65,7 @@ class DaikinClimateSensor(Entity):
@property
def unit_of_measurement(self):
"""Return the unit of measurement."""
return self._unit_of_measurement
return TEMP_CELSIUS
async def async_update(self):
"""Retrieve latest state."""

View File

@ -39,7 +39,7 @@ from .const import (
ELK_ELEMENTS,
)
SYNC_TIMEOUT = 55
SYNC_TIMEOUT = 120
_LOGGER = logging.getLogger(__name__)
@ -215,7 +215,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
if not await async_wait_for_elk_to_sync(elk, SYNC_TIMEOUT):
_LOGGER.error(
"Timed out after %d seconds while trying to sync with ElkM1", SYNC_TIMEOUT,
"Timed out after %d seconds while trying to sync with ElkM1 at %s",
SYNC_TIMEOUT,
conf[CONF_HOST],
)
elk.disconnect()
raise ConfigEntryNotReady

View File

@ -64,8 +64,9 @@ async def validate_input(data):
timed_out = False
if not await async_wait_for_elk_to_sync(elk, VALIDATE_TIMEOUT):
_LOGGER.error(
"Timed out after %d seconds while trying to sync with elkm1",
"Timed out after %d seconds while trying to sync with ElkM1 at %s",
VALIDATE_TIMEOUT,
url,
)
timed_out = True

View File

@ -424,6 +424,9 @@ class HERETravelTimeData:
if departure is not None:
departure = convert_time_to_isodate(departure)
if departure is None and arrival is None:
departure = "now"
_LOGGER.debug(
"Requesting route for origin: %s, destination: %s, route_mode: %s, mode: %s, traffic_mode: %s, arrival: %s, departure: %s",
origin,

View File

@ -4,5 +4,5 @@
"documentation": "https://www.home-assistant.io/integrations/intesishome",
"dependencies": [],
"codeowners": ["@jnimmo"],
"requirements": ["pyintesishome==1.7.1"]
"requirements": ["pyintesishome==1.7.3"]
}

View File

@ -15,7 +15,7 @@ _LOGGER = logging.getLogger(__name__)
def to_lutron_level(level):
"""Convert the given Home Assistant light level (0-255) to Lutron (0-100)."""
return int((level * 100) // 255)
return int(round((level * 100) / 255))
def to_hass_level(level):

View File

@ -2,7 +2,6 @@
import logging
from nexia.const import (
FAN_MODES,
OPERATION_MODE_AUTO,
OPERATION_MODE_COOL,
OPERATION_MODE_HEAT,
@ -192,7 +191,7 @@ class NexiaZone(NexiaThermostatZoneEntity, ClimateDevice):
@property
def fan_modes(self):
"""Return the list of available fan modes."""
return FAN_MODES
return self._thermostat.get_fan_modes()
@property
def min_temp(self):

View File

@ -1,7 +1,7 @@
{
"domain": "nexia",
"name": "Nexia",
"requirements": ["nexia==0.8.2"],
"requirements": ["nexia==0.9.1"],
"codeowners": ["@ryannazaretian", "@bdraco"],
"documentation": "https://www.home-assistant.io/integrations/nexia",
"config_flow": true

View File

@ -109,7 +109,7 @@ def _firmware_from_status(status):
def _serial_from_status(status):
"""Find the best serialvalue from the status."""
serial = status.get("device.serial") or status.get("ups.serial")
if serial and serial == "unknown":
if serial and (serial.lower() == "unknown" or serial.count("0") == len(serial)):
return None
return serial

View File

@ -17,6 +17,7 @@ from homeassistant.exceptions import ConfigEntryNotReady
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.event import async_track_time_interval
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from .const import (
ACTIVE_UPDATE_RATE,
@ -27,6 +28,7 @@ from .const import (
SENSE_DEVICES_DATA,
SENSE_DISCOVERED_DEVICES_DATA,
SENSE_TIMEOUT_EXCEPTIONS,
SENSE_TRENDS_COORDINATOR,
)
_LOGGER = logging.getLogger(__name__)
@ -111,9 +113,23 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
except SENSE_TIMEOUT_EXCEPTIONS:
raise ConfigEntryNotReady
trends_coordinator = DataUpdateCoordinator(
hass,
_LOGGER,
name=f"Sense Trends {email}",
update_method=gateway.update_trend_data,
update_interval=timedelta(seconds=300),
)
# This can take longer than 60s and we already know
# sense is online since get_discovered_device_data was
# successful so we do it later.
hass.loop.create_task(trends_coordinator.async_request_refresh())
hass.data[DOMAIN][entry.entry_id] = {
SENSE_DATA: gateway,
SENSE_DEVICES_DATA: sense_devices_data,
SENSE_TRENDS_COORDINATOR: trends_coordinator,
SENSE_DISCOVERED_DEVICES_DATA: sense_discovered_devices,
}
@ -122,7 +138,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
hass.config_entries.async_forward_entry_setup(entry, component)
)
async def async_sense_update(now):
async def async_sense_update(_):
"""Retrieve latest state."""
try:
await gateway.update_realtime()

View File

@ -71,7 +71,6 @@ class SenseDevice(BinarySensorDevice):
self._unique_id = f"{sense_monitor_id}-{self._id}"
self._icon = sense_to_mdi(device["icon"])
self._sense_devices_data = sense_devices_data
self._undo_dispatch_subscription = None
self._state = None
self._available = False
@ -117,17 +116,14 @@ class SenseDevice(BinarySensorDevice):
async def async_added_to_hass(self):
"""Register callbacks."""
self._undo_dispatch_subscription = async_dispatcher_connect(
self.hass,
f"{SENSE_DEVICE_UPDATE}-{self._sense_monitor_id}",
self._async_update_from_data,
self.async_on_remove(
async_dispatcher_connect(
self.hass,
f"{SENSE_DEVICE_UPDATE}-{self._sense_monitor_id}",
self._async_update_from_data,
)
)
async def async_will_remove_from_hass(self):
"""Undo subscription."""
if self._undo_dispatch_subscription:
self._undo_dispatch_subscription()
@callback
def _async_update_from_data(self):
"""Get the latest data, update state. Must not do I/O."""

View File

@ -12,6 +12,7 @@ SENSE_DATA = "sense_data"
SENSE_DEVICE_UPDATE = "sense_devices_update"
SENSE_DEVICES_DATA = "sense_devices_data"
SENSE_DISCOVERED_DEVICES_DATA = "sense_discovered_devices"
SENSE_TRENDS_COORDINATOR = "sense_trends_coorindator"
ACTIVE_NAME = "Energy"
ACTIVE_TYPE = "active"

View File

@ -1,12 +1,10 @@
"""Support for monitoring a Sense energy sensor."""
from datetime import timedelta
import logging
from homeassistant.const import DEVICE_CLASS_POWER, ENERGY_KILO_WATT_HOUR, POWER_WATT
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
from .const import (
ACTIVE_NAME,
@ -22,12 +20,9 @@ from .const import (
SENSE_DEVICE_UPDATE,
SENSE_DEVICES_DATA,
SENSE_DISCOVERED_DEVICES_DATA,
SENSE_TIMEOUT_EXCEPTIONS,
SENSE_TRENDS_COORDINATOR,
)
MIN_TIME_BETWEEN_DAILY_UPDATES = timedelta(seconds=300)
_LOGGER = logging.getLogger(__name__)
@ -64,17 +59,18 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up the Sense sensor."""
data = hass.data[DOMAIN][config_entry.entry_id][SENSE_DATA]
sense_devices_data = hass.data[DOMAIN][config_entry.entry_id][SENSE_DEVICES_DATA]
trends_coordinator = hass.data[DOMAIN][config_entry.entry_id][
SENSE_TRENDS_COORDINATOR
]
@Throttle(MIN_TIME_BETWEEN_DAILY_UPDATES)
async def update_trends():
"""Update the daily power usage."""
await data.update_trend_data()
# Request only in case it takes longer
# than 60s
await trends_coordinator.async_request_refresh()
sense_monitor_id = data.sense_monitor_id
sense_devices = hass.data[DOMAIN][config_entry.entry_id][
SENSE_DISCOVERED_DEVICES_DATA
]
await data.update_trend_data()
devices = [
SenseEnergyDevice(sense_devices_data, device, sense_monitor_id)
@ -108,8 +104,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
name,
sensor_type,
is_production,
update_trends,
var,
trends_coordinator,
unique_id,
)
)
@ -140,7 +135,6 @@ class SenseActiveSensor(Entity):
self._sensor_type = sensor_type
self._is_production = is_production
self._state = None
self._undo_dispatch_subscription = None
@property
def name(self):
@ -179,17 +173,14 @@ class SenseActiveSensor(Entity):
async def async_added_to_hass(self):
"""Register callbacks."""
self._undo_dispatch_subscription = async_dispatcher_connect(
self.hass,
f"{SENSE_DEVICE_UPDATE}-{self._sense_monitor_id}",
self._async_update_from_data,
self.async_on_remove(
async_dispatcher_connect(
self.hass,
f"{SENSE_DEVICE_UPDATE}-{self._sense_monitor_id}",
self._async_update_from_data,
)
)
async def async_will_remove_from_hass(self):
"""Undo subscription."""
if self._undo_dispatch_subscription:
self._undo_dispatch_subscription()
@callback
def _async_update_from_data(self):
"""Update the sensor from the data. Must not do I/O."""
@ -206,7 +197,7 @@ class SenseTrendsSensor(Entity):
"""Implementation of a Sense energy sensor."""
def __init__(
self, data, name, sensor_type, is_production, update_call, sensor_id, unique_id
self, data, name, sensor_type, is_production, trends_coordinator, unique_id,
):
"""Initialize the Sense sensor."""
name_type = PRODUCTION_NAME if is_production else CONSUMPTION_NAME
@ -215,10 +206,11 @@ class SenseTrendsSensor(Entity):
self._available = False
self._data = data
self._sensor_type = sensor_type
self.update_sensor = update_call
self._coordinator = trends_coordinator
self._is_production = is_production
self._state = None
self._unit_of_measurement = ENERGY_KILO_WATT_HOUR
self._had_any_update = False
@property
def name(self):
@ -228,12 +220,12 @@ class SenseTrendsSensor(Entity):
@property
def state(self):
"""Return the state of the sensor."""
return self._state
return round(self._data.get_trend(self._sensor_type, self._is_production), 1)
@property
def available(self):
"""Return the availability of the sensor."""
return self._available
"""Return if entity is available."""
return self._had_any_update and self._coordinator.last_update_success
@property
def unit_of_measurement(self):
@ -250,18 +242,27 @@ class SenseTrendsSensor(Entity):
"""Return the unique id."""
return self._unique_id
@property
def should_poll(self):
"""No need to poll. Coordinator notifies entity of updates."""
return False
@callback
def _async_update(self):
"""Track if we had an update so we do not report zero data."""
self._had_any_update = True
self.async_write_ha_state()
async def async_update(self):
"""Get the latest data, update state."""
"""Update the entity.
try:
await self.update_sensor()
except SENSE_TIMEOUT_EXCEPTIONS:
_LOGGER.error("Timeout retrieving data")
return
Only used by the generic entity update service.
"""
await self._coordinator.async_request_refresh()
state = self._data.get_trend(self._sensor_type, self._is_production)
self._state = round(state, 1)
self._available = True
async def async_added_to_hass(self):
"""When entity is added to hass."""
self.async_on_remove(self._coordinator.async_add_listener(self._async_update))
class SenseEnergyDevice(Entity):
@ -276,7 +277,6 @@ class SenseEnergyDevice(Entity):
self._unique_id = f"{sense_monitor_id}-{self._id}-{CONSUMPTION_ID}"
self._icon = sense_to_mdi(device["icon"])
self._sense_devices_data = sense_devices_data
self._undo_dispatch_subscription = None
self._state = None
@property
@ -321,17 +321,14 @@ class SenseEnergyDevice(Entity):
async def async_added_to_hass(self):
"""Register callbacks."""
self._undo_dispatch_subscription = async_dispatcher_connect(
self.hass,
f"{SENSE_DEVICE_UPDATE}-{self._sense_monitor_id}",
self._async_update_from_data,
self.async_on_remove(
async_dispatcher_connect(
self.hass,
f"{SENSE_DEVICE_UPDATE}-{self._sense_monitor_id}",
self._async_update_from_data,
)
)
async def async_will_remove_from_hass(self):
"""Undo subscription."""
if self._undo_dispatch_subscription:
self._undo_dispatch_subscription()
@callback
def _async_update_from_data(self):
"""Get the latest data, update state. Must not do I/O."""

View File

@ -74,11 +74,7 @@ class SlackNotificationService(BaseNotificationService):
self._default_channel = default_channel
self._hass = hass
self._icon = icon
if username or self._icon:
self._as_user = False
else:
self._as_user = True
self._username = username
async def _async_send_local_file_message(self, path, targets, message, title):
"""Upload a local file (with message) to Slack."""
@ -108,11 +104,11 @@ class SlackNotificationService(BaseNotificationService):
target: self._client.chat_postMessage(
channel=target,
text=message,
as_user=self._as_user,
attachments=attachments,
blocks=blocks,
icon_emoji=self._icon,
link_names=True,
username=self._username,
)
for target in targets
}

View File

@ -2,7 +2,7 @@
import logging
from homeassistant.components.unifi.config_flow import get_controller_from_config_entry
from homeassistant.const import DATA_BYTES
from homeassistant.const import DATA_MEGABYTES
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
@ -116,7 +116,7 @@ class UniFiRxBandwidthSensor(UniFiClient):
@property
def unit_of_measurement(self):
"""Return the unit of measurement of this entity."""
return DATA_BYTES
return DATA_MEGABYTES
class UniFiTxBandwidthSensor(UniFiRxBandwidthSensor):

View File

@ -104,7 +104,7 @@ def byte_to_zwave_brightness(value):
`value` -- (int) Brightness byte value from 0-255.
"""
if value > 0:
return max(1, int((value / 255) * 99))
return max(1, round((value / 255) * 99))
return 0

View File

@ -1,7 +1,7 @@
"""Constants used by Home Assistant components."""
MAJOR_VERSION = 0
MINOR_VERSION = 108
PATCH_VERSION = "3"
PATCH_VERSION = "4"
__short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}"
__version__ = f"{__short_version__}.{PATCH_VERSION}"
REQUIRED_PYTHON_VER = (3, 7, 0)

View File

@ -922,7 +922,7 @@ netdisco==2.6.0
neurio==0.3.1
# homeassistant.components.nexia
nexia==0.8.2
nexia==0.9.1
# homeassistant.components.nextcloud
nextcloudmonitor==1.1.0
@ -1330,7 +1330,7 @@ pyialarm==0.3
pyicloud==0.9.6.1
# homeassistant.components.intesishome
pyintesishome==1.7.1
pyintesishome==1.7.3
# homeassistant.components.ipma
pyipma==2.0.5

View File

@ -357,7 +357,7 @@ nessclient==0.9.15
netdisco==2.6.0
# homeassistant.components.nexia
nexia==0.8.2
nexia==0.9.1
# homeassistant.components.nsw_fuel_station
nsw-fuel-api-client==1.0.10

View File

@ -80,6 +80,8 @@ def _build_mock_url(origin, destination, modes, api_key, departure=None, arrival
parameters["arrival"] = arrival
if departure is not None:
parameters["departure"] = departure
if departure is None and arrival is None:
parameters["departure"] = "now"
url = base_url + urllib.parse.urlencode(parameters)
print(url)
return url

View File

@ -18,8 +18,8 @@ async def test_climate_zones(hass):
"current_temperature": 22.8,
"dehumidify_setpoint": 45.0,
"dehumidify_supported": True,
"fan_mode": "auto",
"fan_modes": ["auto", "on", "circulate"],
"fan_mode": "Auto",
"fan_modes": ["Auto", "On", "Circulate"],
"friendly_name": "Nick Office",
"humidify_supported": False,
"humidity": 45.0,
@ -53,8 +53,8 @@ async def test_climate_zones(hass):
"current_temperature": 25.0,
"dehumidify_setpoint": 50.0,
"dehumidify_supported": True,
"fan_mode": "auto",
"fan_modes": ["auto", "on", "circulate"],
"fan_mode": "Auto",
"fan_modes": ["Auto", "On", "Circulate"],
"friendly_name": "Kitchen",
"humidify_supported": False,
"humidity": 50.0,

View File

@ -100,13 +100,23 @@ def test_dimmer_turn_on(mock_openzwave):
node.reset_mock()
device.turn_on(**{ATTR_BRIGHTNESS: 224})
assert node.set_dimmer.called
value_id, brightness = node.set_dimmer.mock_calls[0][1]
assert value_id == value.value_id
assert brightness == 87 # round(224 / 255 * 99)
node.reset_mock()
device.turn_on(**{ATTR_BRIGHTNESS: 120})
assert node.set_dimmer.called
value_id, brightness = node.set_dimmer.mock_calls[0][1]
assert value_id == value.value_id
assert brightness == 46 # int(120 / 255 * 99)
assert brightness == 47 # round(120 / 255 * 99)
with patch.object(light, "_LOGGER", MagicMock()) as mock_logger:
device.turn_on(**{ATTR_TRANSITION: 35})

47
tests/fixtures/nut/BACKUPSES600M1.json vendored Normal file
View File

@ -0,0 +1,47 @@
{
"ups.realpower.nominal" : "330",
"input.voltage" : "123.0",
"ups.mfr" : "American Power Conversion",
"driver.version" : "2.7.4",
"ups.test.result" : "No test initiated",
"input.voltage.nominal" : "120",
"input.transfer.low" : "92",
"driver.parameter.pollinterval" : "15",
"driver.version.data" : "APC HID 0.96",
"driver.parameter.pollfreq" : "30",
"battery.mfr.date" : "2017/04/01",
"ups.beeper.status" : "enabled",
"battery.date" : "2001/09/25",
"driver.name" : "usbhid-ups",
"battery.charge" : "100",
"ups.status" : "OL",
"ups.model" : "Back-UPS ES 600M1",
"battery.runtime.low" : "120",
"ups.firmware" : "928.a5 .D",
"ups.delay.shutdown" : "20",
"device.model" : "Back-UPS ES 600M1",
"device.serial" : "4B1713P32195 ",
"input.sensitivity" : "medium",
"ups.firmware.aux" : "a5 ",
"input.transfer.reason" : "input voltage out of range",
"ups.timer.reboot" : "0",
"battery.voltage.nominal" : "12.0",
"ups.vendorid" : "051d",
"input.transfer.high" : "139",
"battery.voltage" : "13.7",
"battery.charge.low" : "10",
"battery.type" : "PbAc",
"ups.mfr.date" : "2017/04/01",
"ups.timer.shutdown" : "-1",
"device.mfr" : "American Power Conversion",
"driver.parameter.port" : "auto",
"battery.charge.warning" : "50",
"device.type" : "ups",
"driver.parameter.vendorid" : "051d",
"ups.serial" : "4B1713P32195 ",
"ups.load" : "22",
"driver.version.internal" : "0.41",
"battery.runtime" : "1968",
"driver.parameter.synchronous" : "no",
"ups.productid" : "0002"
}

43
tests/fixtures/nut/CP1500PFCLCD.json vendored Normal file
View File

@ -0,0 +1,43 @@
{
"battery.runtime.low" : "300",
"driver.parameter.port" : "auto",
"ups.delay.shutdown" : "20",
"driver.parameter.pollfreq" : "30",
"ups.beeper.status" : "disabled",
"input.voltage.nominal" : "120",
"device.serial" : "000000000000",
"ups.timer.shutdown" : "-60",
"input.voltage" : "122.0",
"ups.status" : "OL",
"ups.model" : "CP1500PFCLCD",
"device.mfr" : "CPS",
"device.model" : "CP1500PFCLCD",
"input.transfer.low" : "88",
"battery.mfr.date" : "CPS",
"driver.version" : "2.7.4",
"driver.version.data" : "CyberPower HID 0.4",
"driver.parameter.synchronous" : "no",
"ups.realpower.nominal" : "900",
"ups.productid" : "0501",
"ups.mfr" : "CPS",
"ups.vendorid" : "0764",
"driver.version.internal" : "0.41",
"output.voltage" : "138.0",
"battery.runtime" : "10530",
"device.type" : "ups",
"battery.charge.low" : "10",
"ups.timer.start" : "-60",
"driver.parameter.pollinterval" : "15",
"ups.load" : "0",
"ups.serial" : "000000000000",
"input.transfer.high" : "139",
"battery.charge.warning" : "20",
"battery.voltage.nominal" : "24",
"driver.parameter.vendorid" : "0764",
"driver.name" : "usbhid-ups",
"battery.type" : "PbAcid",
"ups.delay.start" : "30",
"battery.voltage" : "24.0",
"battery.charge" : "100",
"ups.test.result" : "No test initiated"
}