mirror of
https://github.com/home-assistant/core.git
synced 2025-07-25 06:07:17 +00:00
Merge pull request #54249 from home-assistant/rc
This commit is contained in:
commit
f3de8b9f28
@ -91,13 +91,13 @@ class AdsCover(AdsEntity, CoverEntity):
|
||||
):
|
||||
"""Initialize AdsCover entity."""
|
||||
super().__init__(ads_hub, name, ads_var_is_closed)
|
||||
if self._ads_var is None:
|
||||
if self._attr_unique_id is None:
|
||||
if ads_var_position is not None:
|
||||
self._unique_id = ads_var_position
|
||||
self._attr_unique_id = ads_var_position
|
||||
elif ads_var_pos_set is not None:
|
||||
self._unique_id = ads_var_pos_set
|
||||
self._attr_unique_id = ads_var_pos_set
|
||||
elif ads_var_open is not None:
|
||||
self._unique_id = ads_var_open
|
||||
self._attr_unique_id = ads_var_open
|
||||
|
||||
self._state_dict[STATE_KEY_POSITION] = None
|
||||
self._ads_var_position = ads_var_position
|
||||
|
@ -49,7 +49,10 @@ def setup_platform(
|
||||
try:
|
||||
if not acc.login():
|
||||
raise ValueError("Username or Password is incorrect")
|
||||
add_entities(AladdinDevice(acc, door) for door in acc.get_doors())
|
||||
add_entities(
|
||||
(AladdinDevice(acc, door) for door in acc.get_doors()),
|
||||
update_before_add=True,
|
||||
)
|
||||
except (TypeError, KeyError, NameError, ValueError) as ex:
|
||||
_LOGGER.error("%s", ex)
|
||||
hass.components.persistent_notification.create(
|
||||
|
@ -449,6 +449,11 @@ class ADBDevice(MediaPlayerEntity):
|
||||
ATTR_HDMI_INPUT: None,
|
||||
}
|
||||
|
||||
@property
|
||||
def media_image_hash(self):
|
||||
"""Hash value for media image."""
|
||||
return f"{datetime.now().timestamp()}" if self._screencap else None
|
||||
|
||||
@adb_decorator()
|
||||
async def _adb_screencap(self):
|
||||
"""Take a screen capture from the device."""
|
||||
@ -458,9 +463,6 @@ class ADBDevice(MediaPlayerEntity):
|
||||
"""Fetch current playing image."""
|
||||
if not self._screencap or self.state in [STATE_OFF, None] or not self.available:
|
||||
return None, None
|
||||
self._attr_media_image_hash = (
|
||||
f"{datetime.now().timestamp()}" if self._screencap else None
|
||||
)
|
||||
|
||||
media_data = await self._adb_screencap()
|
||||
if media_data:
|
||||
|
@ -223,7 +223,6 @@ SENSOR_TYPES = {
|
||||
None,
|
||||
4,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
None,
|
||||
],
|
||||
"Flame": ["Flame", None, "mdi:toggle-switch", 2, None],
|
||||
"PowerEnergyConsumptionHeatingCircuit": [
|
||||
|
@ -66,6 +66,7 @@ from .const import (
|
||||
CONF_INPUT_TYPE,
|
||||
CONF_MAX_TEMP,
|
||||
CONF_MIN_TEMP,
|
||||
CONF_MSG_WAIT,
|
||||
CONF_PARITY,
|
||||
CONF_PRECISION,
|
||||
CONF_RETRIES,
|
||||
@ -283,6 +284,7 @@ MODBUS_SCHEMA = vol.Schema(
|
||||
vol.Optional(CONF_DELAY, default=0): cv.positive_int,
|
||||
vol.Optional(CONF_RETRIES, default=3): cv.positive_int,
|
||||
vol.Optional(CONF_RETRY_ON_EMPTY, default=False): cv.boolean,
|
||||
vol.Optional(CONF_MSG_WAIT): cv.positive_int,
|
||||
vol.Optional(CONF_BINARY_SENSORS): vol.All(
|
||||
cv.ensure_list, [BINARY_SENSOR_SCHEMA]
|
||||
),
|
||||
|
@ -31,6 +31,7 @@ CONF_INPUTS = "inputs"
|
||||
CONF_INPUT_TYPE = "input_type"
|
||||
CONF_MAX_TEMP = "max_temp"
|
||||
CONF_MIN_TEMP = "min_temp"
|
||||
CONF_MSG_WAIT = "message_wait_milliseconds"
|
||||
CONF_PARITY = "parity"
|
||||
CONF_REGISTER = "register"
|
||||
CONF_REGISTER_TYPE = "register_type"
|
||||
|
@ -39,6 +39,7 @@ from .const import (
|
||||
CONF_BAUDRATE,
|
||||
CONF_BYTESIZE,
|
||||
CONF_CLOSE_COMM_ON_ERROR,
|
||||
CONF_MSG_WAIT,
|
||||
CONF_PARITY,
|
||||
CONF_RETRIES,
|
||||
CONF_RETRY_ON_EMPTY,
|
||||
@ -229,6 +230,12 @@ class ModbusHub:
|
||||
self._pb_params["framer"] = ModbusRtuFramer
|
||||
|
||||
Defaults.Timeout = client_config[CONF_TIMEOUT]
|
||||
if CONF_MSG_WAIT in client_config:
|
||||
self._msg_wait = client_config[CONF_MSG_WAIT] / 1000
|
||||
elif self._config_type == CONF_SERIAL:
|
||||
self._msg_wait = 30 / 1000
|
||||
else:
|
||||
self._msg_wait = 0
|
||||
|
||||
def _log_error(self, text: str, error_state=True):
|
||||
log_text = f"Pymodbus: {text}"
|
||||
@ -322,7 +329,7 @@ class ModbusHub:
|
||||
result = await self.hass.async_add_executor_job(
|
||||
self._pymodbus_call, unit, address, value, use_call
|
||||
)
|
||||
if self._config_type == "serial":
|
||||
if self._msg_wait:
|
||||
# small delay until next request/response
|
||||
await asyncio.sleep(30 / 1000)
|
||||
await asyncio.sleep(self._msg_wait)
|
||||
return result
|
||||
|
@ -72,6 +72,7 @@ def struct_validator(config):
|
||||
_LOGGER.warning(error)
|
||||
try:
|
||||
data_type = OLD_DATA_TYPES[data_type][config.get(CONF_COUNT, 1)]
|
||||
config[CONF_DATA_TYPE] = data_type
|
||||
except KeyError as exp:
|
||||
error = f"{name} cannot convert automatically {data_type}"
|
||||
raise vol.Invalid(error) from exp
|
||||
|
@ -53,7 +53,7 @@ from homeassistant.helpers.dispatcher import (
|
||||
async_dispatcher_send,
|
||||
)
|
||||
from homeassistant.helpers.entity import DeviceInfo, EntityDescription
|
||||
from homeassistant.helpers.network import get_url
|
||||
from homeassistant.helpers.network import NoURLAvailableError, get_url
|
||||
from homeassistant.helpers.update_coordinator import (
|
||||
CoordinatorEntity,
|
||||
DataUpdateCoordinator,
|
||||
@ -145,12 +145,21 @@ def listen_for_new_cameras(
|
||||
|
||||
|
||||
@callback
|
||||
def async_generate_motioneye_webhook(hass: HomeAssistant, webhook_id: str) -> str:
|
||||
def async_generate_motioneye_webhook(
|
||||
hass: HomeAssistant, webhook_id: str
|
||||
) -> str | None:
|
||||
"""Generate the full local URL for a webhook_id."""
|
||||
return "{}{}".format(
|
||||
get_url(hass, allow_cloud=False),
|
||||
async_generate_path(webhook_id),
|
||||
)
|
||||
try:
|
||||
return "{}{}".format(
|
||||
get_url(hass, allow_cloud=False),
|
||||
async_generate_path(webhook_id),
|
||||
)
|
||||
except NoURLAvailableError:
|
||||
_LOGGER.warning(
|
||||
"Unable to get Home Assistant URL. Have you set the internal and/or "
|
||||
"external URLs in Configuration -> General?"
|
||||
)
|
||||
return None
|
||||
|
||||
|
||||
@callback
|
||||
@ -228,28 +237,31 @@ def _add_camera(
|
||||
if entry.options.get(CONF_WEBHOOK_SET, DEFAULT_WEBHOOK_SET):
|
||||
url = async_generate_motioneye_webhook(hass, entry.data[CONF_WEBHOOK_ID])
|
||||
|
||||
if _set_webhook(
|
||||
_build_url(
|
||||
device,
|
||||
url,
|
||||
EVENT_MOTION_DETECTED,
|
||||
EVENT_MOTION_DETECTED_KEYS,
|
||||
),
|
||||
KEY_WEB_HOOK_NOTIFICATIONS_URL,
|
||||
KEY_WEB_HOOK_NOTIFICATIONS_HTTP_METHOD,
|
||||
KEY_WEB_HOOK_NOTIFICATIONS_ENABLED,
|
||||
camera,
|
||||
) | _set_webhook(
|
||||
_build_url(
|
||||
device,
|
||||
url,
|
||||
EVENT_FILE_STORED,
|
||||
EVENT_FILE_STORED_KEYS,
|
||||
),
|
||||
KEY_WEB_HOOK_STORAGE_URL,
|
||||
KEY_WEB_HOOK_STORAGE_HTTP_METHOD,
|
||||
KEY_WEB_HOOK_STORAGE_ENABLED,
|
||||
camera,
|
||||
if url and (
|
||||
_set_webhook(
|
||||
_build_url(
|
||||
device,
|
||||
url,
|
||||
EVENT_MOTION_DETECTED,
|
||||
EVENT_MOTION_DETECTED_KEYS,
|
||||
),
|
||||
KEY_WEB_HOOK_NOTIFICATIONS_URL,
|
||||
KEY_WEB_HOOK_NOTIFICATIONS_HTTP_METHOD,
|
||||
KEY_WEB_HOOK_NOTIFICATIONS_ENABLED,
|
||||
camera,
|
||||
)
|
||||
| _set_webhook(
|
||||
_build_url(
|
||||
device,
|
||||
url,
|
||||
EVENT_FILE_STORED,
|
||||
EVENT_FILE_STORED_KEYS,
|
||||
),
|
||||
KEY_WEB_HOOK_STORAGE_URL,
|
||||
KEY_WEB_HOOK_STORAGE_HTTP_METHOD,
|
||||
KEY_WEB_HOOK_STORAGE_ENABLED,
|
||||
camera,
|
||||
)
|
||||
):
|
||||
hass.async_create_task(client.async_set_camera(camera_id, camera))
|
||||
|
||||
|
@ -45,6 +45,8 @@ from .const import (
|
||||
DOMAIN,
|
||||
KEY_COORDINATOR,
|
||||
KEY_DEVICE,
|
||||
MODEL_AIRHUMIDIFIER_CA1,
|
||||
MODEL_AIRHUMIDIFIER_CB1,
|
||||
MODELS_HUMIDIFIER_MIOT,
|
||||
)
|
||||
from .device import XiaomiCoordinatedMiioEntity, XiaomiMiioEntity
|
||||
@ -63,16 +65,17 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
||||
}
|
||||
)
|
||||
|
||||
ATTR_POWER = "power"
|
||||
ATTR_ACTUAL_SPEED = "actual_speed"
|
||||
ATTR_CHARGING = "charging"
|
||||
ATTR_DISPLAY_CLOCK = "display_clock"
|
||||
ATTR_HUMIDITY = "humidity"
|
||||
ATTR_MOTOR_SPEED = "motor_speed"
|
||||
ATTR_NIGHT_MODE = "night_mode"
|
||||
ATTR_NIGHT_TIME_BEGIN = "night_time_begin"
|
||||
ATTR_NIGHT_TIME_END = "night_time_end"
|
||||
ATTR_POWER = "power"
|
||||
ATTR_SENSOR_STATE = "sensor_state"
|
||||
ATTR_WATER_LEVEL = "water_level"
|
||||
ATTR_HUMIDITY = "humidity"
|
||||
ATTR_ACTUAL_MOTOR_SPEED = "actual_speed"
|
||||
|
||||
|
||||
@dataclass
|
||||
@ -121,6 +124,13 @@ SENSOR_TYPES = {
|
||||
valid_min_value=200.0,
|
||||
valid_max_value=2000.0,
|
||||
),
|
||||
"motor_speed": SensorType(
|
||||
unit="rpm",
|
||||
icon="mdi:fast-forward",
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
valid_min_value=200.0,
|
||||
valid_max_value=2000.0,
|
||||
),
|
||||
}
|
||||
|
||||
HUMIDIFIER_SENSORS = {
|
||||
@ -128,11 +138,17 @@ HUMIDIFIER_SENSORS = {
|
||||
ATTR_TEMPERATURE: "temperature",
|
||||
}
|
||||
|
||||
HUMIDIFIER_CA1_CB1_SENSORS = {
|
||||
ATTR_HUMIDITY: "humidity",
|
||||
ATTR_TEMPERATURE: "temperature",
|
||||
ATTR_MOTOR_SPEED: "motor_speed",
|
||||
}
|
||||
|
||||
HUMIDIFIER_SENSORS_MIOT = {
|
||||
ATTR_HUMIDITY: "humidity",
|
||||
ATTR_TEMPERATURE: "temperature",
|
||||
ATTR_WATER_LEVEL: "water_level",
|
||||
ATTR_ACTUAL_MOTOR_SPEED: "actual_speed",
|
||||
ATTR_ACTUAL_SPEED: "actual_speed",
|
||||
}
|
||||
|
||||
|
||||
@ -191,11 +207,12 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
sensors = []
|
||||
if model in MODELS_HUMIDIFIER_MIOT:
|
||||
device = hass.data[DOMAIN][config_entry.entry_id][KEY_DEVICE]
|
||||
coordinator = hass.data[DOMAIN][config_entry.entry_id][KEY_COORDINATOR]
|
||||
sensors = HUMIDIFIER_SENSORS_MIOT
|
||||
elif model in (MODEL_AIRHUMIDIFIER_CA1, MODEL_AIRHUMIDIFIER_CB1):
|
||||
device = hass.data[DOMAIN][config_entry.entry_id][KEY_DEVICE]
|
||||
sensors = HUMIDIFIER_CA1_CB1_SENSORS
|
||||
elif model.startswith("zhimi.humidifier."):
|
||||
device = hass.data[DOMAIN][config_entry.entry_id][KEY_DEVICE]
|
||||
coordinator = hass.data[DOMAIN][config_entry.entry_id][KEY_COORDINATOR]
|
||||
sensors = HUMIDIFIER_SENSORS
|
||||
else:
|
||||
unique_id = config_entry.unique_id
|
||||
|
@ -5,7 +5,7 @@ from typing import Final
|
||||
|
||||
MAJOR_VERSION: Final = 2021
|
||||
MINOR_VERSION: Final = 8
|
||||
PATCH_VERSION: Final = "3"
|
||||
PATCH_VERSION: Final = "4"
|
||||
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
||||
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
||||
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 8, 0)
|
||||
|
@ -51,6 +51,11 @@ httplib2>=0.19.0
|
||||
# https://github.com/home-assistant/core/issues/40148
|
||||
grpcio==1.31.0
|
||||
|
||||
# Newer versions of cloud pubsub pin a higher version of grpcio. This can
|
||||
# be reverted when the grpcio pin is reverted, see:
|
||||
# https://github.com/home-assistant/core/issues/53427
|
||||
google-cloud-pubsub==2.1.0
|
||||
|
||||
# This is a old unmaintained library and is replaced with pycryptodome
|
||||
pycrypto==1000000000.0.0
|
||||
|
||||
|
@ -73,6 +73,11 @@ httplib2>=0.19.0
|
||||
# https://github.com/home-assistant/core/issues/40148
|
||||
grpcio==1.31.0
|
||||
|
||||
# Newer versions of cloud pubsub pin a higher version of grpcio. This can
|
||||
# be reverted when the grpcio pin is reverted, see:
|
||||
# https://github.com/home-assistant/core/issues/53427
|
||||
google-cloud-pubsub==2.1.0
|
||||
|
||||
# This is a old unmaintained library and is replaced with pycryptodome
|
||||
pycrypto==1000000000.0.0
|
||||
|
||||
|
@ -40,6 +40,7 @@ from homeassistant.components.modbus.const import (
|
||||
CONF_BYTESIZE,
|
||||
CONF_DATA_TYPE,
|
||||
CONF_INPUT_TYPE,
|
||||
CONF_MSG_WAIT,
|
||||
CONF_PARITY,
|
||||
CONF_STOPBITS,
|
||||
CONF_SWAP,
|
||||
@ -245,6 +246,7 @@ async def test_exception_struct_validator(do_config):
|
||||
CONF_PORT: "usb01",
|
||||
CONF_PARITY: "E",
|
||||
CONF_STOPBITS: 1,
|
||||
CONF_MSG_WAIT: 100,
|
||||
},
|
||||
{
|
||||
CONF_TYPE: "serial",
|
||||
|
@ -32,11 +32,13 @@ from homeassistant.const import (
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.helpers.network import NoURLAvailableError
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from . import (
|
||||
TEST_CAMERA,
|
||||
TEST_CAMERA_DEVICE_IDENTIFIER,
|
||||
TEST_CAMERA_ENTITY_ID,
|
||||
TEST_CAMERA_ID,
|
||||
TEST_CAMERA_NAME,
|
||||
TEST_CAMERAS,
|
||||
@ -251,6 +253,35 @@ async def test_setup_camera_with_correct_webhook(
|
||||
assert not client.async_set_camera.called
|
||||
|
||||
|
||||
async def test_setup_camera_with_no_home_assistant_urls(
|
||||
hass: HomeAssistant,
|
||||
caplog: Any,
|
||||
) -> None:
|
||||
"""Verify setup works without Home Assistant internal/external URLs."""
|
||||
|
||||
client = create_mock_motioneye_client()
|
||||
config_entry = create_mock_motioneye_config_entry(hass, data={CONF_URL: TEST_URL})
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.motioneye.get_url", side_effect=NoURLAvailableError
|
||||
):
|
||||
await setup_mock_motioneye_config_entry(
|
||||
hass,
|
||||
config_entry=config_entry,
|
||||
client=client,
|
||||
)
|
||||
|
||||
# Should log a warning ...
|
||||
assert "Unable to get Home Assistant URL" in caplog.text
|
||||
|
||||
# ... should not set callbacks in the camera ...
|
||||
assert not client.async_set_camera.called
|
||||
|
||||
# ... but camera should still be present.
|
||||
entity_state = hass.states.get(TEST_CAMERA_ENTITY_ID)
|
||||
assert entity_state
|
||||
|
||||
|
||||
async def test_good_query(hass: HomeAssistant, aiohttp_client: Any) -> None:
|
||||
"""Test good callbacks."""
|
||||
await async_setup_component(hass, "http", {"http": {}})
|
||||
|
Loading…
x
Reference in New Issue
Block a user