mirror of
https://github.com/home-assistant/core.git
synced 2026-09-04 18:15:04 -05:00
2025.12.3 (#158811)
This commit is contained in:
@@ -7,5 +7,5 @@
|
||||
"integration_type": "hub",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["aioasuswrt", "asusrouter", "asyncssh"],
|
||||
"requirements": ["aioasuswrt==1.5.1", "asusrouter==1.21.1"]
|
||||
"requirements": ["aioasuswrt==1.5.2", "asusrouter==1.21.3"]
|
||||
}
|
||||
|
||||
@@ -64,6 +64,12 @@ async def async_migrate_entry(hass: HomeAssistant, entry: BlinkConfigEntry) -> b
|
||||
if entry.version == 2:
|
||||
await _reauth_flow_wrapper(hass, entry, data)
|
||||
return False
|
||||
if entry.version == 3:
|
||||
# Migrate device_id to hardware_id for blinkpy 0.25.x OAuth2 compatibility
|
||||
if "device_id" in data:
|
||||
data["hardware_id"] = data.pop("device_id")
|
||||
hass.config_entries.async_update_entry(entry, data=data, version=4)
|
||||
return True
|
||||
return True
|
||||
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ from homeassistant.core import callback
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
|
||||
from .const import DEVICE_ID, DOMAIN
|
||||
from .const import DOMAIN, HARDWARE_ID
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -43,7 +43,7 @@ async def _send_blink_2fa_pin(blink: Blink, pin: str | None) -> bool:
|
||||
class BlinkConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
"""Handle a Blink config flow."""
|
||||
|
||||
VERSION = 3
|
||||
VERSION = 4
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Initialize the blink flow."""
|
||||
@@ -53,7 +53,7 @@ class BlinkConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
async def _handle_user_input(self, user_input: dict[str, Any]):
|
||||
"""Handle user input."""
|
||||
self.auth = Auth(
|
||||
{**user_input, "device_id": DEVICE_ID},
|
||||
{**user_input, "hardware_id": HARDWARE_ID},
|
||||
no_prompt=True,
|
||||
session=async_get_clientsession(self.hass),
|
||||
)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
from homeassistant.const import Platform
|
||||
|
||||
DOMAIN = "blink"
|
||||
DEVICE_ID = "Home Assistant"
|
||||
HARDWARE_ID = "Home Assistant"
|
||||
|
||||
CONF_MIGRATE = "migrate"
|
||||
CONF_CAMERA = "camera"
|
||||
|
||||
@@ -20,5 +20,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/blink",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["blinkpy"],
|
||||
"requirements": ["blinkpy==0.24.1"]
|
||||
"requirements": ["blinkpy==0.25.1"]
|
||||
}
|
||||
|
||||
@@ -8,5 +8,5 @@
|
||||
"integration_type": "service",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["googleapiclient"],
|
||||
"requirements": ["gcal-sync==8.0.0", "oauth2client==4.1.3", "ical==11.1.0"]
|
||||
"requirements": ["gcal-sync==8.0.0", "oauth2client==4.1.3", "ical==12.1.1"]
|
||||
}
|
||||
|
||||
@@ -8,5 +8,5 @@
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["google_air_quality_api"],
|
||||
"quality_scale": "bronze",
|
||||
"requirements": ["google_air_quality_api==2.0.0"]
|
||||
"requirements": ["google_air_quality_api==2.0.2"]
|
||||
}
|
||||
|
||||
@@ -88,16 +88,16 @@
|
||||
"1b_good_air_quality": "1B - Good air quality",
|
||||
"2_cyan": "2 - Cyan",
|
||||
"2_light_green": "2 - Light green",
|
||||
"2_orange": "4 - Orange",
|
||||
"2_red": "5 - Red",
|
||||
"2_yellow": "3 - Yellow",
|
||||
"2a_acceptable_air_quality": "2A - Acceptable air quality",
|
||||
"2b_acceptable_air_quality": "2B - Acceptable air quality",
|
||||
"3_green": "3 - Green",
|
||||
"3_yellow": "3 - Yellow",
|
||||
"3a_aggravated_air_quality": "3A - Aggravated air quality",
|
||||
"3b_bad_air_quality": "3B - Bad air quality",
|
||||
"4_orange": "4 - Orange",
|
||||
"4_yellow_watch": "4 - Yellow/Watch",
|
||||
"5_orange_alert": "5 - Orange/Alert",
|
||||
"5_red": "5 - Red",
|
||||
"6_red_alert": "6 - Red/Alert+",
|
||||
"10_33": "10-33% of guideline",
|
||||
"33_66": "33-66% of guideline",
|
||||
|
||||
@@ -27,6 +27,7 @@ MIX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
||||
api_key="eBatChargeToday",
|
||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
device_class=SensorDeviceClass.ENERGY,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
),
|
||||
GrowattSensorEntityDescription(
|
||||
key="mix_battery_charge_lifetime",
|
||||
@@ -42,6 +43,7 @@ MIX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
||||
api_key="eBatDisChargeToday",
|
||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
device_class=SensorDeviceClass.ENERGY,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
),
|
||||
GrowattSensorEntityDescription(
|
||||
key="mix_battery_discharge_lifetime",
|
||||
@@ -57,6 +59,7 @@ MIX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
||||
api_key="epvToday",
|
||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
device_class=SensorDeviceClass.ENERGY,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
),
|
||||
GrowattSensorEntityDescription(
|
||||
key="mix_solar_generation_lifetime",
|
||||
@@ -72,6 +75,7 @@ MIX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
||||
api_key="pDischarge1",
|
||||
native_unit_of_measurement=UnitOfPower.WATT,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
GrowattSensorEntityDescription(
|
||||
key="mix_battery_voltage",
|
||||
@@ -101,6 +105,7 @@ MIX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
||||
api_key="elocalLoadToday",
|
||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
device_class=SensorDeviceClass.ENERGY,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
),
|
||||
GrowattSensorEntityDescription(
|
||||
key="mix_load_consumption_lifetime",
|
||||
@@ -116,6 +121,7 @@ MIX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
||||
api_key="etoGridToday",
|
||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
device_class=SensorDeviceClass.ENERGY,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
),
|
||||
GrowattSensorEntityDescription(
|
||||
key="mix_export_to_grid_lifetime",
|
||||
@@ -132,6 +138,7 @@ MIX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
||||
api_key="chargePower",
|
||||
native_unit_of_measurement=UnitOfPower.KILO_WATT,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
GrowattSensorEntityDescription(
|
||||
key="mix_load_consumption",
|
||||
@@ -139,6 +146,7 @@ MIX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
||||
api_key="pLocalLoad",
|
||||
native_unit_of_measurement=UnitOfPower.KILO_WATT,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
GrowattSensorEntityDescription(
|
||||
key="mix_wattage_pv_1",
|
||||
@@ -146,6 +154,7 @@ MIX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
||||
api_key="pPv1",
|
||||
native_unit_of_measurement=UnitOfPower.KILO_WATT,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
GrowattSensorEntityDescription(
|
||||
key="mix_wattage_pv_2",
|
||||
@@ -153,6 +162,7 @@ MIX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
||||
api_key="pPv2",
|
||||
native_unit_of_measurement=UnitOfPower.KILO_WATT,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
GrowattSensorEntityDescription(
|
||||
key="mix_wattage_pv_all",
|
||||
@@ -160,6 +170,7 @@ MIX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
||||
api_key="ppv",
|
||||
native_unit_of_measurement=UnitOfPower.KILO_WATT,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
GrowattSensorEntityDescription(
|
||||
key="mix_export_to_grid",
|
||||
@@ -167,6 +178,7 @@ MIX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
||||
api_key="pactogrid",
|
||||
native_unit_of_measurement=UnitOfPower.KILO_WATT,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
GrowattSensorEntityDescription(
|
||||
key="mix_import_from_grid",
|
||||
@@ -174,6 +186,7 @@ MIX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
||||
api_key="pactouser",
|
||||
native_unit_of_measurement=UnitOfPower.KILO_WATT,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
GrowattSensorEntityDescription(
|
||||
key="mix_battery_discharge_kw",
|
||||
@@ -181,6 +194,7 @@ MIX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
||||
api_key="pdisCharge1",
|
||||
native_unit_of_measurement=UnitOfPower.KILO_WATT,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
GrowattSensorEntityDescription(
|
||||
key="mix_grid_voltage",
|
||||
@@ -196,6 +210,7 @@ MIX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
||||
api_key="eCharge",
|
||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
device_class=SensorDeviceClass.ENERGY,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
),
|
||||
GrowattSensorEntityDescription(
|
||||
key="mix_load_consumption_solar_today",
|
||||
@@ -203,6 +218,7 @@ MIX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
||||
api_key="eChargeToday",
|
||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
device_class=SensorDeviceClass.ENERGY,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
),
|
||||
GrowattSensorEntityDescription(
|
||||
key="mix_self_consumption_today",
|
||||
@@ -210,6 +226,7 @@ MIX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
||||
api_key="eChargeToday1",
|
||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
device_class=SensorDeviceClass.ENERGY,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
),
|
||||
GrowattSensorEntityDescription(
|
||||
key="mix_load_consumption_battery_today",
|
||||
@@ -217,6 +234,7 @@ MIX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
||||
api_key="echarge1",
|
||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
device_class=SensorDeviceClass.ENERGY,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
),
|
||||
GrowattSensorEntityDescription(
|
||||
key="mix_import_from_grid_today",
|
||||
@@ -224,6 +242,7 @@ MIX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
||||
api_key="etouser",
|
||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
device_class=SensorDeviceClass.ENERGY,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
),
|
||||
# This sensor is manually created using the most recent X-Axis value from the chartData
|
||||
GrowattSensorEntityDescription(
|
||||
|
||||
@@ -79,6 +79,7 @@ TLX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
||||
api_key="ppv1",
|
||||
native_unit_of_measurement=UnitOfPower.WATT,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
precision=1,
|
||||
),
|
||||
GrowattSensorEntityDescription(
|
||||
@@ -122,6 +123,7 @@ TLX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
||||
api_key="ppv2",
|
||||
native_unit_of_measurement=UnitOfPower.WATT,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
precision=1,
|
||||
),
|
||||
GrowattSensorEntityDescription(
|
||||
@@ -165,6 +167,7 @@ TLX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
||||
api_key="ppv3",
|
||||
native_unit_of_measurement=UnitOfPower.WATT,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
precision=1,
|
||||
),
|
||||
GrowattSensorEntityDescription(
|
||||
@@ -208,6 +211,7 @@ TLX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
||||
api_key="ppv4",
|
||||
native_unit_of_measurement=UnitOfPower.WATT,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
precision=1,
|
||||
),
|
||||
GrowattSensorEntityDescription(
|
||||
@@ -234,6 +238,7 @@ TLX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
||||
api_key="ppv",
|
||||
native_unit_of_measurement=UnitOfPower.WATT,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
precision=1,
|
||||
),
|
||||
GrowattSensorEntityDescription(
|
||||
@@ -258,6 +263,7 @@ TLX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
||||
api_key="pac",
|
||||
native_unit_of_measurement=UnitOfPower.WATT,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
precision=1,
|
||||
),
|
||||
GrowattSensorEntityDescription(
|
||||
@@ -323,6 +329,7 @@ TLX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
||||
api_key="bdc1DischargePower",
|
||||
native_unit_of_measurement=UnitOfPower.WATT,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
GrowattSensorEntityDescription(
|
||||
key="tlx_battery_1_discharge_total",
|
||||
@@ -339,6 +346,7 @@ TLX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
||||
api_key="bdc2DischargePower",
|
||||
native_unit_of_measurement=UnitOfPower.WATT,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
GrowattSensorEntityDescription(
|
||||
key="tlx_battery_2_discharge_total",
|
||||
@@ -372,6 +380,7 @@ TLX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
||||
api_key="bdc1ChargePower",
|
||||
native_unit_of_measurement=UnitOfPower.WATT,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
GrowattSensorEntityDescription(
|
||||
key="tlx_battery_1_charge_total",
|
||||
@@ -388,6 +397,7 @@ TLX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
||||
api_key="bdc2ChargePower",
|
||||
native_unit_of_measurement=UnitOfPower.WATT,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
GrowattSensorEntityDescription(
|
||||
key="tlx_battery_2_charge_total",
|
||||
@@ -445,6 +455,7 @@ TLX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
||||
api_key="pacToLocalLoad",
|
||||
native_unit_of_measurement=UnitOfPower.WATT,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
precision=1,
|
||||
),
|
||||
GrowattSensorEntityDescription(
|
||||
@@ -453,6 +464,7 @@ TLX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
||||
api_key="pacToUserTotal",
|
||||
native_unit_of_measurement=UnitOfPower.WATT,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
precision=1,
|
||||
),
|
||||
GrowattSensorEntityDescription(
|
||||
@@ -461,6 +473,7 @@ TLX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
||||
api_key="pacToGridTotal",
|
||||
native_unit_of_measurement=UnitOfPower.WATT,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
precision=1,
|
||||
),
|
||||
GrowattSensorEntityDescription(
|
||||
@@ -545,6 +558,7 @@ TLX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
||||
api_key="psystem",
|
||||
native_unit_of_measurement=UnitOfPower.WATT,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
precision=1,
|
||||
),
|
||||
GrowattSensorEntityDescription(
|
||||
@@ -553,6 +567,7 @@ TLX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
||||
api_key="pself",
|
||||
native_unit_of_measurement=UnitOfPower.WATT,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
precision=1,
|
||||
),
|
||||
)
|
||||
|
||||
@@ -50,5 +50,6 @@ TOTAL_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
||||
api_key="nominalPower",
|
||||
native_unit_of_measurement=UnitOfPower.WATT,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
)
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/hanna",
|
||||
"iot_class": "cloud_polling",
|
||||
"quality_scale": "bronze",
|
||||
"requirements": ["hanna-cloud==0.0.6"]
|
||||
"requirements": ["hanna-cloud==0.0.7"]
|
||||
}
|
||||
|
||||
@@ -39,6 +39,10 @@ if TYPE_CHECKING:
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
_DESCRIPTION_PLACEHOLDERS = {
|
||||
"sensor_value_types_url": "https://www.home-assistant.io/integrations/knx/#value-types"
|
||||
}
|
||||
|
||||
|
||||
@callback
|
||||
def async_setup_services(hass: HomeAssistant) -> None:
|
||||
@@ -48,6 +52,7 @@ def async_setup_services(hass: HomeAssistant) -> None:
|
||||
SERVICE_KNX_SEND,
|
||||
service_send_to_knx_bus,
|
||||
schema=SERVICE_KNX_SEND_SCHEMA,
|
||||
description_placeholders=_DESCRIPTION_PLACEHOLDERS,
|
||||
)
|
||||
|
||||
hass.services.async_register(
|
||||
@@ -63,6 +68,7 @@ def async_setup_services(hass: HomeAssistant) -> None:
|
||||
SERVICE_KNX_EVENT_REGISTER,
|
||||
service_event_register_modify,
|
||||
schema=SERVICE_KNX_EVENT_REGISTER_SCHEMA,
|
||||
description_placeholders=_DESCRIPTION_PLACEHOLDERS,
|
||||
)
|
||||
|
||||
async_register_admin_service(
|
||||
@@ -71,6 +77,7 @@ def async_setup_services(hass: HomeAssistant) -> None:
|
||||
SERVICE_KNX_EXPOSURE_REGISTER,
|
||||
service_exposure_register_modify,
|
||||
schema=SERVICE_KNX_EXPOSURE_REGISTER_SCHEMA,
|
||||
description_placeholders=_DESCRIPTION_PLACEHOLDERS,
|
||||
)
|
||||
|
||||
async_register_admin_service(
|
||||
|
||||
@@ -674,7 +674,7 @@
|
||||
"name": "Remove event registration"
|
||||
},
|
||||
"type": {
|
||||
"description": "If set, the payload will be decoded as given DPT in the event data `value` key. KNX sensor types are valid values (see https://www.home-assistant.io/integrations/knx/#value-types).",
|
||||
"description": "If set, the payload will be decoded as given DPT in the event data `value` key. KNX sensor types are valid values (see {sensor_value_types_url}).",
|
||||
"name": "Value type"
|
||||
}
|
||||
},
|
||||
@@ -704,7 +704,7 @@
|
||||
"name": "Remove exposure"
|
||||
},
|
||||
"type": {
|
||||
"description": "Telegrams will be encoded as given DPT. 'binary' and all KNX sensor types are valid values (see https://www.home-assistant.io/integrations/knx/#value-types).",
|
||||
"description": "Telegrams will be encoded as given DPT. 'binary' and all KNX sensor types are valid values (see {sensor_value_types_url}).",
|
||||
"name": "Value type"
|
||||
}
|
||||
},
|
||||
@@ -740,7 +740,7 @@
|
||||
"name": "Send as Response"
|
||||
},
|
||||
"type": {
|
||||
"description": "If set, the payload will not be sent as raw bytes, but encoded as given DPT. KNX sensor types are valid values (see https://www.home-assistant.io/integrations/knx/#value-types).",
|
||||
"description": "If set, the payload will not be sent as raw bytes, but encoded as given DPT. KNX sensor types are valid values (see {sensor_value_types_url}).",
|
||||
"name": "Value type"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -37,5 +37,5 @@
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["pylamarzocco"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["pylamarzocco==2.2.2"]
|
||||
"requirements": ["pylamarzocco==2.2.4"]
|
||||
}
|
||||
|
||||
@@ -9,5 +9,5 @@
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["pypck"],
|
||||
"quality_scale": "silver",
|
||||
"requirements": ["pypck==0.9.5", "lcn-frontend==0.2.7"]
|
||||
"requirements": ["pypck==0.9.7", "lcn-frontend==0.2.7"]
|
||||
}
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/local_calendar",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["ical"],
|
||||
"requirements": ["ical==11.1.0"]
|
||||
"requirements": ["ical==12.1.1"]
|
||||
}
|
||||
|
||||
@@ -5,5 +5,5 @@
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/local_todo",
|
||||
"iot_class": "local_polling",
|
||||
"requirements": ["ical==11.1.0"]
|
||||
"requirements": ["ical==12.1.1"]
|
||||
}
|
||||
|
||||
@@ -183,6 +183,48 @@ class MatterModeSelectEntity(MatterAttributeSelectEntity):
|
||||
self._attr_name = desc
|
||||
|
||||
|
||||
class MatterDoorLockOperatingModeSelectEntity(MatterAttributeSelectEntity):
|
||||
"""Representation of a Door Lock Operating Mode select entity.
|
||||
|
||||
This entity dynamically filters available operating modes based on the device's
|
||||
`SupportedOperatingModes` bitmap attribute. In this bitmap, bit=0 indicates a
|
||||
supported mode and bit=1 indicates unsupported (inverted from typical bitmap conventions).
|
||||
If the bitmap is unavailable, only mandatory modes are included. The mapping from
|
||||
bitmap bits to operating mode values is defined by the Matter specification.
|
||||
"""
|
||||
|
||||
entity_description: MatterMapSelectEntityDescription
|
||||
|
||||
@callback
|
||||
def _update_from_device(self) -> None:
|
||||
"""Update from device."""
|
||||
# Get the bitmap of supported operating modes
|
||||
supported_modes_bitmap = self.get_matter_attribute_value(
|
||||
self.entity_description.list_attribute
|
||||
)
|
||||
|
||||
# Convert bitmap to list of supported mode values
|
||||
# NOTE: The Matter spec inverts the usual meaning: bit=0 means supported,
|
||||
# bit=1 means not supported, undefined bits must be 1. Mandatory modes are
|
||||
# bits 0 (Normal) and 3 (NoRemoteLockUnlock).
|
||||
num_mode_bits = supported_modes_bitmap.bit_length()
|
||||
supported_mode_values = [
|
||||
bit_position
|
||||
for bit_position in range(num_mode_bits)
|
||||
if not supported_modes_bitmap & (1 << bit_position)
|
||||
]
|
||||
|
||||
# Map supported mode values to their string representations
|
||||
self._attr_options = [
|
||||
mapped_value
|
||||
for mode_value in supported_mode_values
|
||||
if (mapped_value := self.entity_description.device_to_ha(mode_value))
|
||||
]
|
||||
|
||||
# Use base implementation to set the current option
|
||||
super()._update_from_device()
|
||||
|
||||
|
||||
class MatterListSelectEntity(MatterEntity, SelectEntity):
|
||||
"""Representation of a select entity from Matter list and selected item Cluster attribute(s)."""
|
||||
|
||||
@@ -594,15 +636,18 @@ DISCOVERY_SCHEMAS = [
|
||||
),
|
||||
MatterDiscoverySchema(
|
||||
platform=Platform.SELECT,
|
||||
entity_description=MatterSelectEntityDescription(
|
||||
entity_description=MatterMapSelectEntityDescription(
|
||||
key="DoorLockOperatingMode",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
translation_key="door_lock_operating_mode",
|
||||
options=list(DOOR_LOCK_OPERATING_MODE_MAP.values()),
|
||||
list_attribute=clusters.DoorLock.Attributes.SupportedOperatingModes,
|
||||
device_to_ha=DOOR_LOCK_OPERATING_MODE_MAP.get,
|
||||
ha_to_device=DOOR_LOCK_OPERATING_MODE_MAP_REVERSE.get,
|
||||
),
|
||||
entity_class=MatterAttributeSelectEntity,
|
||||
required_attributes=(clusters.DoorLock.Attributes.OperatingMode,),
|
||||
entity_class=MatterDoorLockOperatingModeSelectEntity,
|
||||
required_attributes=(
|
||||
clusters.DoorLock.Attributes.OperatingMode,
|
||||
clusters.DoorLock.Attributes.SupportedOperatingModes,
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
"ws_path": "WebSocket path"
|
||||
},
|
||||
"data_description": {
|
||||
"advanced_options": "Enable and select **Next** to set advanced options.",
|
||||
"advanced_options": "Enable and select **Submit** to set advanced options.",
|
||||
"broker": "The hostname or IP address of your MQTT broker.",
|
||||
"certificate": "The custom CA certificate file to validate your MQTT brokers certificate.",
|
||||
"client_cert": "The client certificate to authenticate against your MQTT broker.",
|
||||
|
||||
@@ -49,6 +49,7 @@ SENSORS = [
|
||||
key="current",
|
||||
device_class=SensorDeviceClass.CURRENT,
|
||||
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value_fn=lambda client: client.power.amps,
|
||||
),
|
||||
OhmeSensorDescription(
|
||||
@@ -57,6 +58,7 @@ SENSORS = [
|
||||
native_unit_of_measurement=UnitOfPower.WATT,
|
||||
suggested_unit_of_measurement=UnitOfPower.KILO_WATT,
|
||||
suggested_display_precision=1,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value_fn=lambda client: client.power.watts,
|
||||
),
|
||||
OhmeSensorDescription(
|
||||
@@ -81,6 +83,7 @@ SENSORS = [
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
device_class=SensorDeviceClass.BATTERY,
|
||||
suggested_display_precision=0,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value_fn=lambda client: client.battery,
|
||||
),
|
||||
OhmeSensorDescription(
|
||||
|
||||
@@ -8,5 +8,5 @@
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["ical"],
|
||||
"quality_scale": "silver",
|
||||
"requirements": ["ical==11.1.0"]
|
||||
"requirements": ["ical==12.1.1"]
|
||||
}
|
||||
|
||||
@@ -394,7 +394,14 @@ class RoborockWashingMachineUpdateCoordinator(
|
||||
async def _async_update_data(
|
||||
self,
|
||||
) -> dict[RoborockZeoProtocol, StateType]:
|
||||
return await self.api.query_values(self.request_protocols)
|
||||
try:
|
||||
return await self.api.query_values(self.request_protocols)
|
||||
except RoborockException as ex:
|
||||
_LOGGER.debug("Failed to update washing machine data: %s", ex)
|
||||
raise UpdateFailed(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="update_data_fail",
|
||||
) from ex
|
||||
|
||||
|
||||
class RoborockWetDryVacUpdateCoordinator(
|
||||
@@ -425,4 +432,11 @@ class RoborockWetDryVacUpdateCoordinator(
|
||||
async def _async_update_data(
|
||||
self,
|
||||
) -> dict[RoborockDyadDataProtocol, StateType]:
|
||||
return await self.api.query_values(self.request_protocols)
|
||||
try:
|
||||
return await self.api.query_values(self.request_protocols)
|
||||
except RoborockException as ex:
|
||||
_LOGGER.debug("Failed to update wet dry vac data: %s", ex)
|
||||
raise UpdateFailed(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="update_data_fail",
|
||||
) from ex
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
"loggers": ["roborock"],
|
||||
"quality_scale": "silver",
|
||||
"requirements": [
|
||||
"python-roborock==3.10.10",
|
||||
"python-roborock==3.12.2",
|
||||
"vacuum-map-parser-roborock==0.1.4"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ from datetime import time
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from roborock.data import DnDTimer
|
||||
from roborock.data import DnDTimer, ValleyElectricityTimer
|
||||
from roborock.exceptions import RoborockException
|
||||
|
||||
from homeassistant.components.time import TimeEntity, TimeEntityDescription
|
||||
@@ -80,13 +80,14 @@ TIME_DESCRIPTIONS: list[RoborockTimeDescription] = [
|
||||
key="off_peak_start",
|
||||
translation_key="off_peak_start",
|
||||
trait=lambda api: api.valley_electricity_timer,
|
||||
update_value=lambda trait, desired_time: trait.update_value(
|
||||
[
|
||||
desired_time.hour,
|
||||
desired_time.minute,
|
||||
trait.end_hour,
|
||||
trait.end_minute,
|
||||
]
|
||||
update_value=lambda trait, desired_time: trait.set_timer(
|
||||
ValleyElectricityTimer(
|
||||
enabled=trait.enabled,
|
||||
start_hour=desired_time.hour,
|
||||
start_minute=desired_time.minute,
|
||||
end_hour=trait.end_hour,
|
||||
end_minute=trait.end_minute,
|
||||
)
|
||||
),
|
||||
get_value=lambda trait: datetime.time(
|
||||
hour=trait.start_hour, minute=trait.start_minute
|
||||
@@ -98,13 +99,14 @@ TIME_DESCRIPTIONS: list[RoborockTimeDescription] = [
|
||||
key="off_peak_end",
|
||||
translation_key="off_peak_end",
|
||||
trait=lambda api: api.valley_electricity_timer,
|
||||
update_value=lambda trait, desired_time: trait.update_value(
|
||||
[
|
||||
trait.start_hour,
|
||||
trait.start_minute,
|
||||
desired_time.hour,
|
||||
desired_time.minute,
|
||||
]
|
||||
update_value=lambda trait, desired_time: trait.set_timer(
|
||||
ValleyElectricityTimer(
|
||||
enabled=trait.enabled,
|
||||
start_hour=trait.start_hour,
|
||||
start_minute=trait.start_minute,
|
||||
end_hour=desired_time.hour,
|
||||
end_minute=desired_time.minute,
|
||||
)
|
||||
),
|
||||
get_value=lambda trait: datetime.time(
|
||||
hour=trait.end_hour, minute=trait.end_minute
|
||||
|
||||
@@ -31,5 +31,5 @@
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["pysmartthings"],
|
||||
"quality_scale": "bronze",
|
||||
"requirements": ["pysmartthings==3.5.0"]
|
||||
"requirements": ["pysmartthings==3.5.1"]
|
||||
}
|
||||
|
||||
@@ -147,16 +147,16 @@ class SwitchBotCloudBinarySensor(SwitchBotCloudEntity, BinarySensorEntity):
|
||||
self.entity_description = description
|
||||
self._attr_unique_id = f"{device.device_id}_{description.key}"
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool | None:
|
||||
def _set_attributes(self) -> None:
|
||||
"""Set attributes from coordinator data."""
|
||||
if not self.coordinator.data:
|
||||
return None
|
||||
return
|
||||
|
||||
if self.entity_description.value_fn:
|
||||
return self.entity_description.value_fn(self.coordinator.data)
|
||||
self._attr_is_on = self.entity_description.value_fn(self.coordinator.data)
|
||||
return
|
||||
|
||||
return (
|
||||
self._attr_is_on = (
|
||||
self.coordinator.data.get(self.entity_description.key)
|
||||
== self.entity_description.on_value
|
||||
)
|
||||
|
||||
@@ -433,23 +433,23 @@ class TuyaClimateEntity(TuyaEntity, ClimateEntity):
|
||||
return self._read_wrapper(self._target_humidity_wrapper)
|
||||
|
||||
@property
|
||||
def hvac_mode(self) -> HVACMode:
|
||||
def hvac_mode(self) -> HVACMode | None:
|
||||
"""Return hvac mode."""
|
||||
# If the switch is off, hvac mode is off as well.
|
||||
# Unless the switch doesn't exists of course...
|
||||
# If the switch is off, hvac mode is off.
|
||||
switch_status: bool | None
|
||||
if (switch_status := self._read_wrapper(self._switch_wrapper)) is False:
|
||||
return HVACMode.OFF
|
||||
|
||||
# If the mode is known and maps to an HVAC mode, return it.
|
||||
if (mode := self._read_wrapper(self._hvac_mode_wrapper)) and (
|
||||
hvac_mode := TUYA_HVAC_TO_HA.get(mode)
|
||||
):
|
||||
return hvac_mode
|
||||
# If we don't have a mode wrapper, return switch only mode.
|
||||
if self._hvac_mode_wrapper is None:
|
||||
if switch_status is True:
|
||||
return self.entity_description.switch_only_hvac_mode
|
||||
return None
|
||||
|
||||
# If hvac_mode is unknown, return the switch only mode.
|
||||
if switch_status:
|
||||
return self.entity_description.switch_only_hvac_mode
|
||||
return HVACMode.OFF
|
||||
# If we do have a mode wrapper, check if the mode maps to an HVAC mode.
|
||||
if (hvac_status := self._read_wrapper(self._hvac_mode_wrapper)) is None:
|
||||
return None
|
||||
return TUYA_HVAC_TO_HA.get(hvac_status)
|
||||
|
||||
@property
|
||||
def preset_mode(self) -> str | None:
|
||||
|
||||
@@ -98,6 +98,7 @@ def _async_device_as_dict(
|
||||
"home_assistant": {},
|
||||
"set_up": device.set_up,
|
||||
"support_local": device.support_local,
|
||||
"local_strategy": device.local_strategy,
|
||||
"warnings": DEVICE_WARNINGS.get(device.id),
|
||||
}
|
||||
|
||||
|
||||
@@ -127,12 +127,12 @@ class BitmapTypeInformation(TypeInformation):
|
||||
@classmethod
|
||||
def from_json(cls, dpcode: str, type_data: str) -> Self | None:
|
||||
"""Load JSON string and return a BitmapTypeInformation object."""
|
||||
if not (parsed := json_loads_object(type_data)):
|
||||
if not (parsed := cast(dict[str, Any] | None, json_loads_object(type_data))):
|
||||
return None
|
||||
return cls(
|
||||
dpcode=dpcode,
|
||||
type_data=type_data,
|
||||
**cast(dict[str, list[str]], parsed),
|
||||
label=parsed["label"],
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.template import Template
|
||||
from homeassistant.helpers.trigger import TriggerActionType, TriggerInfo
|
||||
from homeassistant.helpers.typing import ConfigType, TemplateVarsType
|
||||
from homeassistant.util.json import json_loads
|
||||
|
||||
from . import (
|
||||
DEFAULT_METHODS,
|
||||
@@ -62,7 +63,9 @@ async def _handle_webhook(
|
||||
base_result: dict[str, Any] = {"platform": "webhook", "webhook_id": webhook_id}
|
||||
|
||||
if "json" in request.headers.get(hdrs.CONTENT_TYPE, ""):
|
||||
base_result["json"] = await request.json()
|
||||
# Always attempt to read the body; request.text() returns "" if empty
|
||||
text = await request.text()
|
||||
base_result["json"] = json_loads(text) if text else {}
|
||||
else:
|
||||
base_result["data"] = await request.post()
|
||||
|
||||
|
||||
@@ -81,10 +81,11 @@ class XboxUpdateCoordinator(DataUpdateCoordinator[XboxData]):
|
||||
_LOGGER,
|
||||
config_entry=config_entry,
|
||||
name=DOMAIN,
|
||||
update_interval=timedelta(seconds=10),
|
||||
update_interval=timedelta(seconds=15),
|
||||
)
|
||||
self.data = XboxData()
|
||||
self.current_friends: set[str] = set()
|
||||
self.title_data: dict[str, Title] = {}
|
||||
|
||||
async def _async_setup(self) -> None:
|
||||
"""Set up coordinator."""
|
||||
@@ -217,7 +218,6 @@ class XboxUpdateCoordinator(DataUpdateCoordinator[XboxData]):
|
||||
)
|
||||
|
||||
# retrieve title details
|
||||
title_data: dict[str, Title] = {}
|
||||
for person in presence_data.values():
|
||||
if presence_detail := next(
|
||||
(
|
||||
@@ -227,6 +227,12 @@ class XboxUpdateCoordinator(DataUpdateCoordinator[XboxData]):
|
||||
),
|
||||
None,
|
||||
):
|
||||
if (
|
||||
person.xuid in self.title_data
|
||||
and presence_detail.title_id
|
||||
== self.title_data[person.xuid].title_id
|
||||
):
|
||||
continue
|
||||
try:
|
||||
title = await self.client.titlehub.get_title_info(
|
||||
presence_detail.title_id
|
||||
@@ -250,7 +256,9 @@ class XboxUpdateCoordinator(DataUpdateCoordinator[XboxData]):
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="request_exception",
|
||||
) from e
|
||||
title_data[person.xuid] = title.titles[0]
|
||||
self.title_data[person.xuid] = title.titles[0]
|
||||
else:
|
||||
self.title_data.pop(person.xuid, None)
|
||||
person.last_seen_date_time_utc = self.last_seen_timestamp(person)
|
||||
if (
|
||||
self.current_friends - (new_friends := set(presence_data))
|
||||
@@ -259,7 +267,7 @@ class XboxUpdateCoordinator(DataUpdateCoordinator[XboxData]):
|
||||
self.remove_stale_devices(new_friends)
|
||||
self.current_friends = new_friends
|
||||
|
||||
return XboxData(new_console_data, presence_data, title_data)
|
||||
return XboxData(new_console_data, presence_data, self.title_data)
|
||||
|
||||
def last_seen_timestamp(self, person: Person) -> datetime | None:
|
||||
"""Returns the most recent of two timestamps."""
|
||||
|
||||
@@ -17,7 +17,7 @@ if TYPE_CHECKING:
|
||||
APPLICATION_NAME: Final = "HomeAssistant"
|
||||
MAJOR_VERSION: Final = 2025
|
||||
MINOR_VERSION: Final = 12
|
||||
PATCH_VERSION: Final = "2"
|
||||
PATCH_VERSION: Final = "3"
|
||||
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
||||
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
||||
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 13, 2)
|
||||
|
||||
@@ -223,3 +223,6 @@ gql<4.0.0
|
||||
|
||||
# Pin pytest-rerunfailures to prevent accidental breaks
|
||||
pytest-rerunfailures==16.0.1
|
||||
|
||||
# pycares 5.x is not yet compatible with aiodns
|
||||
pycares==4.11.0
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "homeassistant"
|
||||
version = "2025.12.2"
|
||||
version = "2025.12.3"
|
||||
license = "Apache-2.0"
|
||||
license-files = ["LICENSE*", "homeassistant/backports/LICENSE*"]
|
||||
description = "Open-source home automation platform running on Python 3."
|
||||
|
||||
Generated
+10
-10
@@ -206,7 +206,7 @@ aioaquacell==0.2.0
|
||||
aioaseko==1.0.0
|
||||
|
||||
# homeassistant.components.asuswrt
|
||||
aioasuswrt==1.5.1
|
||||
aioasuswrt==1.5.2
|
||||
|
||||
# homeassistant.components.husqvarna_automower
|
||||
aioautomower==2.7.1
|
||||
@@ -537,7 +537,7 @@ arris-tg2492lg==2.2.0
|
||||
asmog==0.0.6
|
||||
|
||||
# homeassistant.components.asuswrt
|
||||
asusrouter==1.21.1
|
||||
asusrouter==1.21.3
|
||||
|
||||
# homeassistant.components.dlna_dmr
|
||||
# homeassistant.components.dlna_dms
|
||||
@@ -648,7 +648,7 @@ bleak==1.0.1
|
||||
blebox-uniapi==2.5.0
|
||||
|
||||
# homeassistant.components.blink
|
||||
blinkpy==0.24.1
|
||||
blinkpy==0.25.1
|
||||
|
||||
# homeassistant.components.bitcoin
|
||||
blockchain==1.4.4
|
||||
@@ -1090,7 +1090,7 @@ google-nest-sdm==9.1.2
|
||||
google-photos-library-api==0.12.1
|
||||
|
||||
# homeassistant.components.google_air_quality
|
||||
google_air_quality_api==2.0.0
|
||||
google_air_quality_api==2.0.2
|
||||
|
||||
# homeassistant.components.slide
|
||||
# homeassistant.components.slide_local
|
||||
@@ -1154,7 +1154,7 @@ habiticalib==0.4.6
|
||||
habluetooth==5.7.0
|
||||
|
||||
# homeassistant.components.hanna
|
||||
hanna-cloud==0.0.6
|
||||
hanna-cloud==0.0.7
|
||||
|
||||
# homeassistant.components.cloud
|
||||
hass-nabucasa==1.7.0
|
||||
@@ -1234,7 +1234,7 @@ ibeacon-ble==1.2.0
|
||||
# homeassistant.components.local_calendar
|
||||
# homeassistant.components.local_todo
|
||||
# homeassistant.components.remote_calendar
|
||||
ical==11.1.0
|
||||
ical==12.1.1
|
||||
|
||||
# homeassistant.components.caldav
|
||||
icalendar==6.3.1
|
||||
@@ -2145,7 +2145,7 @@ pykwb==0.0.8
|
||||
pylacrosse==0.4
|
||||
|
||||
# homeassistant.components.lamarzocco
|
||||
pylamarzocco==2.2.2
|
||||
pylamarzocco==2.2.4
|
||||
|
||||
# homeassistant.components.lastfm
|
||||
pylast==5.1.0
|
||||
@@ -2285,7 +2285,7 @@ pypaperless==4.1.1
|
||||
pypca==0.0.7
|
||||
|
||||
# homeassistant.components.lcn
|
||||
pypck==0.9.5
|
||||
pypck==0.9.7
|
||||
|
||||
# homeassistant.components.pglab
|
||||
pypglab==0.0.5
|
||||
@@ -2400,7 +2400,7 @@ pysmappee==0.2.29
|
||||
pysmarlaapi==0.9.2
|
||||
|
||||
# homeassistant.components.smartthings
|
||||
pysmartthings==3.5.0
|
||||
pysmartthings==3.5.1
|
||||
|
||||
# homeassistant.components.smarty
|
||||
pysmarty2==0.10.3
|
||||
@@ -2557,7 +2557,7 @@ python-rabbitair==0.0.8
|
||||
python-ripple-api==0.0.3
|
||||
|
||||
# homeassistant.components.roborock
|
||||
python-roborock==3.10.10
|
||||
python-roborock==3.12.2
|
||||
|
||||
# homeassistant.components.smarttub
|
||||
python-smarttub==0.0.45
|
||||
|
||||
Generated
+10
-10
@@ -197,7 +197,7 @@ aioaquacell==0.2.0
|
||||
aioaseko==1.0.0
|
||||
|
||||
# homeassistant.components.asuswrt
|
||||
aioasuswrt==1.5.1
|
||||
aioasuswrt==1.5.2
|
||||
|
||||
# homeassistant.components.husqvarna_automower
|
||||
aioautomower==2.7.1
|
||||
@@ -504,7 +504,7 @@ aranet4==2.5.1
|
||||
arcam-fmj==1.8.2
|
||||
|
||||
# homeassistant.components.asuswrt
|
||||
asusrouter==1.21.1
|
||||
asusrouter==1.21.3
|
||||
|
||||
# homeassistant.components.dlna_dmr
|
||||
# homeassistant.components.dlna_dms
|
||||
@@ -585,7 +585,7 @@ bleak==1.0.1
|
||||
blebox-uniapi==2.5.0
|
||||
|
||||
# homeassistant.components.blink
|
||||
blinkpy==0.24.1
|
||||
blinkpy==0.25.1
|
||||
|
||||
# homeassistant.components.blue_current
|
||||
bluecurrent-api==1.3.2
|
||||
@@ -966,7 +966,7 @@ google-nest-sdm==9.1.2
|
||||
google-photos-library-api==0.12.1
|
||||
|
||||
# homeassistant.components.google_air_quality
|
||||
google_air_quality_api==2.0.0
|
||||
google_air_quality_api==2.0.2
|
||||
|
||||
# homeassistant.components.slide
|
||||
# homeassistant.components.slide_local
|
||||
@@ -1024,7 +1024,7 @@ habiticalib==0.4.6
|
||||
habluetooth==5.7.0
|
||||
|
||||
# homeassistant.components.hanna
|
||||
hanna-cloud==0.0.6
|
||||
hanna-cloud==0.0.7
|
||||
|
||||
# homeassistant.components.cloud
|
||||
hass-nabucasa==1.7.0
|
||||
@@ -1086,7 +1086,7 @@ ibeacon-ble==1.2.0
|
||||
# homeassistant.components.local_calendar
|
||||
# homeassistant.components.local_todo
|
||||
# homeassistant.components.remote_calendar
|
||||
ical==11.1.0
|
||||
ical==12.1.1
|
||||
|
||||
# homeassistant.components.caldav
|
||||
icalendar==6.3.1
|
||||
@@ -1801,7 +1801,7 @@ pykrakenapi==0.1.8
|
||||
pykulersky==0.5.8
|
||||
|
||||
# homeassistant.components.lamarzocco
|
||||
pylamarzocco==2.2.2
|
||||
pylamarzocco==2.2.4
|
||||
|
||||
# homeassistant.components.lastfm
|
||||
pylast==5.1.0
|
||||
@@ -1920,7 +1920,7 @@ pypalazzetti==0.1.20
|
||||
pypaperless==4.1.1
|
||||
|
||||
# homeassistant.components.lcn
|
||||
pypck==0.9.5
|
||||
pypck==0.9.7
|
||||
|
||||
# homeassistant.components.pglab
|
||||
pypglab==0.0.5
|
||||
@@ -2014,7 +2014,7 @@ pysmappee==0.2.29
|
||||
pysmarlaapi==0.9.2
|
||||
|
||||
# homeassistant.components.smartthings
|
||||
pysmartthings==3.5.0
|
||||
pysmartthings==3.5.1
|
||||
|
||||
# homeassistant.components.smarty
|
||||
pysmarty2==0.10.3
|
||||
@@ -2135,7 +2135,7 @@ python-pooldose==0.7.8
|
||||
python-rabbitair==0.0.8
|
||||
|
||||
# homeassistant.components.roborock
|
||||
python-roborock==3.10.10
|
||||
python-roborock==3.12.2
|
||||
|
||||
# homeassistant.components.smarttub
|
||||
python-smarttub==0.0.45
|
||||
|
||||
@@ -214,6 +214,9 @@ gql<4.0.0
|
||||
|
||||
# Pin pytest-rerunfailures to prevent accidental breaks
|
||||
pytest-rerunfailures==16.0.1
|
||||
|
||||
# pycares 5.x is not yet compatible with aiodns
|
||||
pycares==4.11.0
|
||||
"""
|
||||
|
||||
GENERATED_MESSAGE = (
|
||||
|
||||
@@ -85,7 +85,7 @@ def mock_config_fixture():
|
||||
data={
|
||||
CONF_USERNAME: "test_user",
|
||||
CONF_PASSWORD: "Password",
|
||||
"device_id": "Home Assistant",
|
||||
"hardware_id": "Home Assistant",
|
||||
"uid": "BlinkCamera_e1233333e2-0909-09cd-777a-123456789012",
|
||||
"token": "A_token",
|
||||
"unique_id": "an_email@email.com",
|
||||
@@ -95,5 +95,5 @@ def mock_config_fixture():
|
||||
"account_id": 654321,
|
||||
},
|
||||
entry_id=str(uuid4()),
|
||||
version=3,
|
||||
version=4,
|
||||
)
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
'data': dict({
|
||||
'account_id': 654321,
|
||||
'client_id': 123456,
|
||||
'device_id': 'Home Assistant',
|
||||
'hardware_id': 'Home Assistant',
|
||||
'host': 'u034.immedia-semi.com',
|
||||
'password': '**REDACTED**',
|
||||
'region_id': 'u034',
|
||||
@@ -52,7 +52,7 @@
|
||||
]),
|
||||
'title': 'Mock Title',
|
||||
'unique_id': None,
|
||||
'version': 3,
|
||||
'version': 4,
|
||||
}),
|
||||
})
|
||||
# ---
|
||||
|
||||
@@ -113,3 +113,32 @@ async def test_migrate(
|
||||
await hass.async_block_till_done()
|
||||
entry = hass.config_entries.async_get_entry(mock_config_entry.entry_id)
|
||||
assert entry.state is ConfigEntryState.MIGRATION_ERROR
|
||||
|
||||
|
||||
async def test_migrate_v3_to_v4(
|
||||
hass: HomeAssistant,
|
||||
mock_blink_api: MagicMock,
|
||||
mock_blink_auth_api: MagicMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test migration from version 3 to 4 (device_id to hardware_id)."""
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
|
||||
# Set up v3 config entry with device_id
|
||||
data = {**mock_config_entry.data}
|
||||
data.pop("hardware_id", None)
|
||||
data["device_id"] = "Home Assistant"
|
||||
hass.config_entries.async_update_entry(
|
||||
mock_config_entry,
|
||||
version=3,
|
||||
data=data,
|
||||
)
|
||||
|
||||
assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
entry = hass.config_entries.async_get_entry(mock_config_entry.entry_id)
|
||||
assert entry.state is ConfigEntryState.LOADED
|
||||
assert entry.version == 4
|
||||
assert "hardware_id" in entry.data
|
||||
assert "device_id" not in entry.data
|
||||
assert entry.data["hardware_id"] == "Home Assistant"
|
||||
|
||||
@@ -229,7 +229,9 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
@@ -268,6 +270,7 @@
|
||||
'device_class': 'power',
|
||||
'friendly_name': 'MIN123456 Battery 1 charging W',
|
||||
'icon': 'mdi:solar-power',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -283,7 +286,9 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
@@ -322,6 +327,7 @@
|
||||
'device_class': 'power',
|
||||
'friendly_name': 'MIN123456 Battery 1 discharging W',
|
||||
'icon': 'mdi:solar-power',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -337,7 +343,9 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
@@ -376,6 +384,7 @@
|
||||
'device_class': 'power',
|
||||
'friendly_name': 'MIN123456 Battery 2 charging W',
|
||||
'icon': 'mdi:solar-power',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -391,7 +400,9 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
@@ -430,6 +441,7 @@
|
||||
'device_class': 'power',
|
||||
'friendly_name': 'MIN123456 Battery 2 discharging W',
|
||||
'icon': 'mdi:solar-power',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -730,7 +742,9 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
@@ -769,6 +783,7 @@
|
||||
'device_class': 'power',
|
||||
'friendly_name': 'MIN123456 Export power',
|
||||
'icon': 'mdi:solar-power',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -898,7 +913,9 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
@@ -937,6 +954,7 @@
|
||||
'device_class': 'power',
|
||||
'friendly_name': 'MIN123456 Import power',
|
||||
'icon': 'mdi:solar-power',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -1060,7 +1078,9 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
@@ -1099,6 +1119,7 @@
|
||||
'device_class': 'power',
|
||||
'friendly_name': 'MIN123456 Input 1 wattage',
|
||||
'icon': 'mdi:solar-power',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -1222,7 +1243,9 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
@@ -1261,6 +1284,7 @@
|
||||
'device_class': 'power',
|
||||
'friendly_name': 'MIN123456 Input 2 wattage',
|
||||
'icon': 'mdi:solar-power',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -1384,7 +1408,9 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
@@ -1423,6 +1449,7 @@
|
||||
'device_class': 'power',
|
||||
'friendly_name': 'MIN123456 Input 3 wattage',
|
||||
'icon': 'mdi:solar-power',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -1546,7 +1573,9 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
@@ -1585,6 +1614,7 @@
|
||||
'device_class': 'power',
|
||||
'friendly_name': 'MIN123456 Input 4 wattage',
|
||||
'icon': 'mdi:solar-power',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -1600,7 +1630,9 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
@@ -1639,6 +1671,7 @@
|
||||
'device_class': 'power',
|
||||
'friendly_name': 'MIN123456 Internal wattage',
|
||||
'icon': 'mdi:solar-power',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -2737,7 +2770,9 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
@@ -2776,6 +2811,7 @@
|
||||
'device_class': 'power',
|
||||
'friendly_name': 'MIN123456 Local load power',
|
||||
'icon': 'mdi:solar-power',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -2791,7 +2827,9 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
@@ -2830,6 +2868,7 @@
|
||||
'device_class': 'power',
|
||||
'friendly_name': 'MIN123456 Output power',
|
||||
'icon': 'mdi:solar-power',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -2956,7 +2995,9 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
@@ -2995,6 +3036,7 @@
|
||||
'device_class': 'power',
|
||||
'friendly_name': 'MIN123456 Self power',
|
||||
'icon': 'mdi:solar-power',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -3118,7 +3160,9 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
@@ -3157,6 +3201,7 @@
|
||||
'device_class': 'power',
|
||||
'friendly_name': 'MIN123456 System power',
|
||||
'icon': 'mdi:solar-power',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -3613,7 +3658,9 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
@@ -3652,6 +3699,7 @@
|
||||
'device_class': 'power',
|
||||
'friendly_name': 'Test Plant Total Maximum power',
|
||||
'icon': 'mdi:solar-power',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -3936,7 +3984,9 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
@@ -3975,6 +4025,7 @@
|
||||
'device_class': 'power',
|
||||
'friendly_name': 'Test Plant Total Maximum power',
|
||||
'icon': 'mdi:solar-power',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -4372,7 +4423,9 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
@@ -4411,6 +4464,7 @@
|
||||
'device_class': 'power',
|
||||
'friendly_name': 'TLX123456 Battery 1 charging W',
|
||||
'icon': 'mdi:solar-power',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -4426,7 +4480,9 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
@@ -4465,6 +4521,7 @@
|
||||
'device_class': 'power',
|
||||
'friendly_name': 'TLX123456 Battery 1 discharging W',
|
||||
'icon': 'mdi:solar-power',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -4480,7 +4537,9 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
@@ -4519,6 +4578,7 @@
|
||||
'device_class': 'power',
|
||||
'friendly_name': 'TLX123456 Battery 2 charging W',
|
||||
'icon': 'mdi:solar-power',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -4534,7 +4594,9 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
@@ -4573,6 +4635,7 @@
|
||||
'device_class': 'power',
|
||||
'friendly_name': 'TLX123456 Battery 2 discharging W',
|
||||
'icon': 'mdi:solar-power',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -4873,7 +4936,9 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
@@ -4912,6 +4977,7 @@
|
||||
'device_class': 'power',
|
||||
'friendly_name': 'TLX123456 Export power',
|
||||
'icon': 'mdi:solar-power',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -5041,7 +5107,9 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
@@ -5080,6 +5148,7 @@
|
||||
'device_class': 'power',
|
||||
'friendly_name': 'TLX123456 Import power',
|
||||
'icon': 'mdi:solar-power',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -5203,7 +5272,9 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
@@ -5242,6 +5313,7 @@
|
||||
'device_class': 'power',
|
||||
'friendly_name': 'TLX123456 Input 1 wattage',
|
||||
'icon': 'mdi:solar-power',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -5365,7 +5437,9 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
@@ -5404,6 +5478,7 @@
|
||||
'device_class': 'power',
|
||||
'friendly_name': 'TLX123456 Input 2 wattage',
|
||||
'icon': 'mdi:solar-power',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -5527,7 +5602,9 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
@@ -5566,6 +5643,7 @@
|
||||
'device_class': 'power',
|
||||
'friendly_name': 'TLX123456 Input 3 wattage',
|
||||
'icon': 'mdi:solar-power',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -5689,7 +5767,9 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
@@ -5728,6 +5808,7 @@
|
||||
'device_class': 'power',
|
||||
'friendly_name': 'TLX123456 Input 4 wattage',
|
||||
'icon': 'mdi:solar-power',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -5743,7 +5824,9 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
@@ -5782,6 +5865,7 @@
|
||||
'device_class': 'power',
|
||||
'friendly_name': 'TLX123456 Internal wattage',
|
||||
'icon': 'mdi:solar-power',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -6880,7 +6964,9 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
@@ -6919,6 +7005,7 @@
|
||||
'device_class': 'power',
|
||||
'friendly_name': 'TLX123456 Local load power',
|
||||
'icon': 'mdi:solar-power',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -6934,7 +7021,9 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
@@ -6973,6 +7062,7 @@
|
||||
'device_class': 'power',
|
||||
'friendly_name': 'TLX123456 Output power',
|
||||
'icon': 'mdi:solar-power',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -7099,7 +7189,9 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
@@ -7138,6 +7230,7 @@
|
||||
'device_class': 'power',
|
||||
'friendly_name': 'TLX123456 Self power',
|
||||
'icon': 'mdi:solar-power',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -7261,7 +7354,9 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
@@ -7300,6 +7395,7 @@
|
||||
'device_class': 'power',
|
||||
'friendly_name': 'TLX123456 System power',
|
||||
'icon': 'mdi:solar-power',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
|
||||
@@ -241,10 +241,7 @@
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
'normal',
|
||||
'vacation',
|
||||
'privacy',
|
||||
'no_remote_lock_unlock',
|
||||
'passage',
|
||||
]),
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
@@ -282,10 +279,7 @@
|
||||
'friendly_name': 'Aqara Smart Lock U200 Operating mode',
|
||||
'options': list([
|
||||
'normal',
|
||||
'vacation',
|
||||
'privacy',
|
||||
'no_remote_lock_unlock',
|
||||
'passage',
|
||||
]),
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -684,10 +678,7 @@
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
'normal',
|
||||
'vacation',
|
||||
'privacy',
|
||||
'no_remote_lock_unlock',
|
||||
'passage',
|
||||
]),
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
@@ -725,10 +716,7 @@
|
||||
'friendly_name': 'Mock Door Lock Operating mode',
|
||||
'options': list([
|
||||
'normal',
|
||||
'vacation',
|
||||
'privacy',
|
||||
'no_remote_lock_unlock',
|
||||
'passage',
|
||||
]),
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -869,10 +857,7 @@
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
'normal',
|
||||
'vacation',
|
||||
'privacy',
|
||||
'no_remote_lock_unlock',
|
||||
'passage',
|
||||
]),
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
@@ -910,10 +895,7 @@
|
||||
'friendly_name': 'Mock Door Lock with unbolt Operating mode',
|
||||
'options': list([
|
||||
'normal',
|
||||
'vacation',
|
||||
'privacy',
|
||||
'no_remote_lock_unlock',
|
||||
'passage',
|
||||
]),
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -2454,10 +2436,7 @@
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
'normal',
|
||||
'vacation',
|
||||
'privacy',
|
||||
'no_remote_lock_unlock',
|
||||
'passage',
|
||||
]),
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
@@ -2495,10 +2474,7 @@
|
||||
'friendly_name': 'Mock Lock Operating mode',
|
||||
'options': list([
|
||||
'normal',
|
||||
'vacation',
|
||||
'privacy',
|
||||
'no_remote_lock_unlock',
|
||||
'passage',
|
||||
]),
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -3657,10 +3633,8 @@
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
'normal',
|
||||
'vacation',
|
||||
'privacy',
|
||||
'no_remote_lock_unlock',
|
||||
'passage',
|
||||
]),
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
@@ -3698,10 +3672,8 @@
|
||||
'friendly_name': 'Secuyou Smart Lock Operating mode',
|
||||
'options': list([
|
||||
'normal',
|
||||
'vacation',
|
||||
'privacy',
|
||||
'no_remote_lock_unlock',
|
||||
'passage',
|
||||
]),
|
||||
}),
|
||||
'context': <ANY>,
|
||||
|
||||
@@ -8,7 +8,6 @@ from matter_server.common.helpers.util import create_attribute_path_from_attribu
|
||||
import pytest
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
|
||||
from homeassistant.components.matter.select import DOOR_LOCK_OPERATING_MODE_MAP
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
@@ -314,22 +313,30 @@ async def test_door_lock_operating_mode_select(
|
||||
"""Test Door Lock Operating Mode select entity discovery and interaction.
|
||||
|
||||
Verifies:
|
||||
- Options match mapping in DOOR_LOCK_OPERATING_MODE_MAP
|
||||
- Options are filtered based on SupportedOperatingModes bitmap
|
||||
- Attribute updates reflect current option
|
||||
- Selecting an option writes correct enum value
|
||||
"""
|
||||
entity_id = "select.secuyou_smart_lock_operating_mode"
|
||||
state = hass.states.get(entity_id)
|
||||
assert state, "Missing operating mode select entity"
|
||||
assert state.attributes["options"] == list(DOOR_LOCK_OPERATING_MODE_MAP.values())
|
||||
# Initial state should be one of the allowed options
|
||||
# According to the spec, bit=0 means supported and bit=1 means not supported.
|
||||
# The fixture bitmap clears bits 0, 2, and 3, so the supported modes are
|
||||
# Normal, Privacy, and NoRemoteLockUnlock; the other bits are set (not
|
||||
# supported).
|
||||
assert set(state.attributes["options"]) == {
|
||||
"normal",
|
||||
"privacy",
|
||||
"no_remote_lock_unlock",
|
||||
}
|
||||
# Verify that the initial state is part of the allowed options
|
||||
assert state.state in state.attributes["options"]
|
||||
|
||||
# Dynamically obtain ids instead of hardcoding
|
||||
door_lock_cluster_id = clusters.DoorLock.Attributes.OperatingMode.cluster_id
|
||||
operating_mode_attr_id = clusters.DoorLock.Attributes.OperatingMode.attribute_id
|
||||
|
||||
# Change OperatingMode attribute on the node to 'privacy'
|
||||
# Change OperatingMode attribute on the node to a supported mode ('privacy')
|
||||
set_node_attribute(
|
||||
matter_node,
|
||||
1,
|
||||
@@ -341,12 +348,12 @@ async def test_door_lock_operating_mode_select(
|
||||
state = hass.states.get(entity_id)
|
||||
assert state.state == "privacy"
|
||||
|
||||
# Select another option (vacation) via service to validate mapping
|
||||
# Select another supported option (NoRemoteLockUnlock) via service to validate mapping
|
||||
matter_client.write_attribute.reset_mock()
|
||||
await hass.services.async_call(
|
||||
"select",
|
||||
"select_option",
|
||||
{"entity_id": entity_id, "option": "vacation"},
|
||||
{"entity_id": entity_id, "option": "no_remote_lock_unlock"},
|
||||
blocking=True,
|
||||
)
|
||||
assert matter_client.write_attribute.call_count == 1
|
||||
@@ -356,5 +363,5 @@ async def test_door_lock_operating_mode_select(
|
||||
endpoint_id=1,
|
||||
attribute=clusters.DoorLock.Attributes.OperatingMode,
|
||||
),
|
||||
value=clusters.DoorLock.Enums.OperatingModeEnum.kVacation,
|
||||
value=clusters.DoorLock.Enums.OperatingModeEnum.kNoRemoteLockUnlock,
|
||||
)
|
||||
|
||||
@@ -52,7 +52,9 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
@@ -90,6 +92,7 @@
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'current',
|
||||
'friendly_name': 'Ohme Home Pro Current',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': <UnitOfElectricCurrent.AMPERE: 'A'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -164,7 +167,9 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
@@ -205,6 +210,7 @@
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'power',
|
||||
'friendly_name': 'Ohme Home Pro Power',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': <UnitOfPower.KILO_WATT: 'kW'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -286,7 +292,9 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
@@ -324,6 +332,7 @@
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'battery',
|
||||
'friendly_name': 'Ohme Home Pro Vehicle battery',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': '%',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
|
||||
@@ -21,6 +21,7 @@ from roborock.data import (
|
||||
NetworkInfo,
|
||||
RoborockBase,
|
||||
RoborockDyadStateCode,
|
||||
ValleyElectricityTimer,
|
||||
ZeoError,
|
||||
ZeoState,
|
||||
)
|
||||
@@ -40,6 +41,9 @@ from roborock.devices.traits.v1.network_info import NetworkInfoTrait
|
||||
from roborock.devices.traits.v1.routines import RoutinesTrait
|
||||
from roborock.devices.traits.v1.smart_wash_params import SmartWashParamsTrait
|
||||
from roborock.devices.traits.v1.status import StatusTrait
|
||||
from roborock.devices.traits.v1.valley_electricity_timer import (
|
||||
ValleyElectricityTimerTrait,
|
||||
)
|
||||
from roborock.devices.traits.v1.volume import SoundVolumeTrait
|
||||
from roborock.devices.traits.v1.wash_towel_mode import WashTowelModeTrait
|
||||
from roborock.roborock_message import RoborockDyadDataProtocol, RoborockZeoProtocol
|
||||
@@ -68,6 +72,7 @@ from .mock_data import (
|
||||
STATUS,
|
||||
USER_DATA,
|
||||
USER_EMAIL,
|
||||
VALLEY_ELECTRICITY_TIMER,
|
||||
)
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
@@ -188,6 +193,25 @@ def make_dnd_timer(dataclass_template: RoborockBase) -> AsyncMock:
|
||||
return dnd_trait
|
||||
|
||||
|
||||
def make_valley_electric_timer(dataclass_template: RoborockBase) -> AsyncMock:
|
||||
"""Make a function for the fake timer trait that emulates the real behavior."""
|
||||
valley_electric_timer_trait = make_mock_switch(
|
||||
trait_spec=ValleyElectricityTimerTrait,
|
||||
dataclass_template=dataclass_template,
|
||||
)
|
||||
|
||||
async def set_timer(timer: ValleyElectricityTimer) -> None:
|
||||
setattr(valley_electric_timer_trait, "start_hour", timer.start_hour)
|
||||
setattr(valley_electric_timer_trait, "start_minute", timer.start_minute)
|
||||
setattr(valley_electric_timer_trait, "end_hour", timer.end_hour)
|
||||
setattr(valley_electric_timer_trait, "end_minute", timer.end_minute)
|
||||
setattr(valley_electric_timer_trait, "enabled", timer.enabled)
|
||||
|
||||
valley_electric_timer_trait.set_timer = AsyncMock()
|
||||
valley_electric_timer_trait.set_timer.side_effect = set_timer
|
||||
return valley_electric_timer_trait
|
||||
|
||||
|
||||
def make_home_trait(
|
||||
map_info: list[MultiMapsListMapInfo],
|
||||
current_map: int | None,
|
||||
@@ -257,7 +281,9 @@ def create_v1_properties(network_info: NetworkInfo) -> AsyncMock:
|
||||
v1_properties.child_lock = make_mock_switch()
|
||||
v1_properties.led_status = make_mock_switch()
|
||||
v1_properties.flow_led_status = make_mock_switch()
|
||||
v1_properties.valley_electricity_timer = make_mock_switch()
|
||||
v1_properties.valley_electricity_timer = make_valley_electric_timer(
|
||||
dataclass_template=VALLEY_ELECTRICITY_TIMER,
|
||||
)
|
||||
v1_properties.dust_collection_mode = make_mock_trait(
|
||||
trait_spec=DustCollectionModeTrait
|
||||
)
|
||||
|
||||
@@ -14,6 +14,7 @@ from roborock.data import (
|
||||
NetworkInfo,
|
||||
S7Status,
|
||||
UserData,
|
||||
ValleyElectricityTimer,
|
||||
)
|
||||
from vacuum_map_parser_base.config.image_config import ImageConfig
|
||||
from vacuum_map_parser_base.map_data import ImageData
|
||||
@@ -1072,6 +1073,16 @@ DND_TIMER = DnDTimer.from_dict(
|
||||
}
|
||||
)
|
||||
|
||||
VALLEY_ELECTRICITY_TIMER = ValleyElectricityTimer.from_dict(
|
||||
{
|
||||
"start_hour": 23,
|
||||
"start_minute": 0,
|
||||
"end_hour": 7,
|
||||
"end_minute": 0,
|
||||
"enabled": 1,
|
||||
}
|
||||
)
|
||||
|
||||
STATUS = S7Status.from_dict(
|
||||
{
|
||||
"msg_ver": 2,
|
||||
|
||||
@@ -10,6 +10,7 @@ from roborock import (
|
||||
RoborockInvalidUserAgreement,
|
||||
RoborockNoUserAgreement,
|
||||
)
|
||||
from roborock.exceptions import RoborockException
|
||||
|
||||
from homeassistant.components.roborock.const import DOMAIN
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
@@ -330,3 +331,71 @@ async def test_cloud_api_repair(
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(issue_registry.issues) == 0
|
||||
|
||||
|
||||
@pytest.mark.parametrize("platforms", [[Platform.SENSOR]])
|
||||
async def test_zeo_device_fails_setup(
|
||||
hass: HomeAssistant,
|
||||
mock_roborock_entry: MockConfigEntry,
|
||||
device_registry: DeviceRegistry,
|
||||
fake_devices: list[FakeDevice],
|
||||
) -> None:
|
||||
"""Simulate an error while setting up a zeo device."""
|
||||
# We have a single zeo device in the test setup. Find it then set it to fail.
|
||||
zeo_device = next(
|
||||
(device for device in fake_devices if device.zeo is not None),
|
||||
None,
|
||||
)
|
||||
assert zeo_device is not None
|
||||
zeo_device.zeo.query_values.side_effect = RoborockException("Simulated Zeo failure")
|
||||
|
||||
await hass.config_entries.async_setup(mock_roborock_entry.entry_id)
|
||||
assert mock_roborock_entry.state is ConfigEntryState.LOADED
|
||||
|
||||
# The current behavior is that we do not add the Zeo device if it fails to setup
|
||||
found_devices = device_registry.devices.get_devices_for_config_entry_id(
|
||||
mock_roborock_entry.entry_id
|
||||
)
|
||||
assert {device.name for device in found_devices} == {
|
||||
"Roborock S7 MaxV",
|
||||
"Roborock S7 MaxV Dock",
|
||||
"Roborock S7 2",
|
||||
"Roborock S7 2 Dock",
|
||||
"Dyad Pro",
|
||||
# Zeo device is missing
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("platforms", [[Platform.SENSOR]])
|
||||
async def test_dyad_device_fails_setup(
|
||||
hass: HomeAssistant,
|
||||
mock_roborock_entry: MockConfigEntry,
|
||||
device_registry: DeviceRegistry,
|
||||
fake_devices: list[FakeDevice],
|
||||
) -> None:
|
||||
"""Simulate an error while setting up a dyad device."""
|
||||
# We have a single dyad device in the test setup. Find it then set it to fail.
|
||||
dyad_device = next(
|
||||
(device for device in fake_devices if device.dyad is not None),
|
||||
None,
|
||||
)
|
||||
assert dyad_device is not None
|
||||
dyad_device.dyad.query_values.side_effect = RoborockException(
|
||||
"Simulated Dyad failure"
|
||||
)
|
||||
|
||||
await hass.config_entries.async_setup(mock_roborock_entry.entry_id)
|
||||
assert mock_roborock_entry.state is ConfigEntryState.LOADED
|
||||
|
||||
# The current behavior is that we do not add the Dyad device if it fails to setup
|
||||
found_devices = device_registry.devices.get_devices_for_config_entry_id(
|
||||
mock_roborock_entry.entry_id
|
||||
)
|
||||
assert {device.name for device in found_devices} == {
|
||||
"Roborock S7 MaxV",
|
||||
"Roborock S7 MaxV Dock",
|
||||
"Roborock S7 2",
|
||||
"Roborock S7 2 Dock",
|
||||
# Dyad device is missing
|
||||
"Zeo One",
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
"""Test Roborock Time platform."""
|
||||
|
||||
from collections.abc import Callable
|
||||
from datetime import time
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
import roborock
|
||||
from roborock.data import DnDTimer
|
||||
from roborock.data import DnDTimer, RoborockBaseTimer, ValleyElectricityTimer
|
||||
|
||||
from homeassistant.components.time import SERVICE_SET_VALUE
|
||||
from homeassistant.const import Platform
|
||||
@@ -22,23 +24,44 @@ def platforms() -> list[Platform]:
|
||||
return [Platform.TIME]
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
|
||||
@pytest.mark.parametrize(
|
||||
("entity_id", "start_state", "expected_args", "end_state"),
|
||||
("entity_id", "start_state", "expected_call", "expected_args", "end_state"),
|
||||
[
|
||||
(
|
||||
"time.roborock_s7_maxv_do_not_disturb_begin",
|
||||
"22:00:00",
|
||||
lambda x: x.v1_properties.dnd.set_dnd_timer,
|
||||
DnDTimer(start_hour=1, start_minute=1, end_hour=7, end_minute=0, enabled=1),
|
||||
"01:01:00",
|
||||
),
|
||||
(
|
||||
"time.roborock_s7_maxv_do_not_disturb_end",
|
||||
"07:00:00",
|
||||
lambda x: x.v1_properties.dnd.set_dnd_timer,
|
||||
DnDTimer(
|
||||
start_hour=22, start_minute=0, end_hour=1, end_minute=1, enabled=1
|
||||
),
|
||||
"01:01:00",
|
||||
),
|
||||
(
|
||||
"time.roborock_s7_maxv_off_peak_start",
|
||||
"23:00:00",
|
||||
lambda x: x.v1_properties.valley_electricity_timer.set_timer,
|
||||
ValleyElectricityTimer(
|
||||
start_hour=1, start_minute=1, end_hour=7, end_minute=0, enabled=1
|
||||
),
|
||||
"01:01:00",
|
||||
),
|
||||
(
|
||||
"time.roborock_s7_maxv_off_peak_end",
|
||||
"07:00:00",
|
||||
lambda x: x.v1_properties.valley_electricity_timer.set_timer,
|
||||
ValleyElectricityTimer(
|
||||
start_hour=23, start_minute=0, end_hour=1, end_minute=1, enabled=1
|
||||
),
|
||||
"01:01:00",
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_update_success(
|
||||
@@ -48,7 +71,8 @@ async def test_update_success(
|
||||
entity_id: str,
|
||||
start_state: str,
|
||||
end_state: str,
|
||||
expected_args: DnDTimer,
|
||||
expected_call: Callable[[FakeDevice], Any],
|
||||
expected_args: RoborockBaseTimer,
|
||||
) -> None:
|
||||
"""Test turning switch entities on and off."""
|
||||
# Ensure that the entity exist, as these test can pass even if there is no entity.
|
||||
@@ -64,10 +88,11 @@ async def test_update_success(
|
||||
target={"entity_id": entity_id},
|
||||
)
|
||||
|
||||
assert fake_vacuum.v1_properties.dnd.set_dnd_timer.call_count == 1
|
||||
call = expected_call(fake_vacuum)
|
||||
assert call.call_count == 1
|
||||
# Since we update the begin or end time separately: Verify that the args are built properly
|
||||
# by reading the existing value and only updating the relevant fields.
|
||||
assert fake_vacuum.v1_properties.dnd.set_dnd_timer.call_args == ((expected_args,),)
|
||||
assert call.call_args == ((expected_args,),)
|
||||
|
||||
state = hass.states.get(entity_id)
|
||||
assert state is not None
|
||||
|
||||
@@ -176,6 +176,7 @@ async def _create_device(hass: HomeAssistant, mock_device_code: str) -> Customer
|
||||
if device.update_time:
|
||||
device.update_time = int(dt_util.as_timestamp(device.update_time))
|
||||
device.support_local = details.get("support_local")
|
||||
device.local_strategy = details.get("local_strategy")
|
||||
device.mqtt_connected = details.get("mqtt_connected")
|
||||
|
||||
device.function = {
|
||||
|
||||
@@ -651,7 +651,7 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'off',
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_platform_setup_and_discovery[climate.itc_308_wifi_thermostat-entry]
|
||||
@@ -713,7 +713,7 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'off',
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_platform_setup_and_discovery[climate.kabinet-entry]
|
||||
@@ -1007,7 +1007,7 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'off',
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_platform_setup_and_discovery[climate.polotentsosushitel-entry]
|
||||
|
||||
@@ -198,6 +198,7 @@
|
||||
'name_by_user': None,
|
||||
}),
|
||||
'id': '2pxfek1jjrtctiyglam',
|
||||
'local_strategy': None,
|
||||
'mqtt_connected': True,
|
||||
'name': 'Multifunction alarm',
|
||||
'online': True,
|
||||
@@ -369,6 +370,7 @@
|
||||
'name_by_user': None,
|
||||
}),
|
||||
'id': 'cwwk68dyfsh2eqi4jbqr',
|
||||
'local_strategy': None,
|
||||
'mqtt_connected': True,
|
||||
'name': 'Gas sensor',
|
||||
'online': True,
|
||||
@@ -512,6 +514,7 @@
|
||||
'name_by_user': None,
|
||||
}),
|
||||
'id': 'vrhdtr5fawoiyth9qdt',
|
||||
'local_strategy': None,
|
||||
'mqtt_connected': True,
|
||||
'name': 'Framboisiers',
|
||||
'online': True,
|
||||
@@ -644,6 +647,7 @@
|
||||
'name_by_user': None,
|
||||
}),
|
||||
'id': 'cwwk68dyfsh2eqi4jbqr',
|
||||
'local_strategy': None,
|
||||
'name': 'Gas sensor',
|
||||
'online': True,
|
||||
'product_id': '4iqe2hsfyd86kwwc',
|
||||
|
||||
@@ -8,6 +8,7 @@ import pytest
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from tests.common import async_capture_events
|
||||
from tests.typing import ClientSessionGenerator
|
||||
|
||||
|
||||
@@ -377,3 +378,46 @@ async def test_webhook_template(
|
||||
|
||||
assert len(events) == 1
|
||||
assert events[0].data["hello"] == "yo world"
|
||||
|
||||
|
||||
async def test_webhook_query_json_header_no_payload(
|
||||
hass: HomeAssistant, hass_client_no_auth: ClientSessionGenerator
|
||||
) -> None:
|
||||
"""Test requests with application/json header but no payload."""
|
||||
events = async_capture_events(hass, "test_success")
|
||||
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
"automation",
|
||||
{
|
||||
"automation": {
|
||||
"trigger": {
|
||||
"platform": "webhook",
|
||||
"webhook_id": "no_payload_webhook",
|
||||
"local_only": True,
|
||||
"allowed_methods": ["GET", "POST"],
|
||||
},
|
||||
"action": {
|
||||
"event": "test_success",
|
||||
},
|
||||
}
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
client = await hass_client_no_auth()
|
||||
|
||||
# GET
|
||||
response = await client.get(
|
||||
"/api/webhook/no_payload_webhook", headers={"Content-Type": "application/json"}
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert response.status == 200
|
||||
|
||||
# POST
|
||||
response = await client.post(
|
||||
"/api/webhook/no_payload_webhook", headers={"Content-Type": "application/json"}
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert response.status == 200
|
||||
|
||||
assert len(events) == 2
|
||||
|
||||
@@ -115,12 +115,12 @@ async def test_load_image_from_url(
|
||||
"rgWHJigthrlsHCxEOMG9UGNdojCYasYt6MJHBjmxmtuAHJeo.sOkUiPmg4JHXvOS82c3UOrvdJTDaCKwCwHPJ0t0Plha8oHFC1i_o-&format=png"
|
||||
).respond(status_code=HTTPStatus.OK, content_type="image/png", content=b"Test2")
|
||||
|
||||
freezer.tick(timedelta(seconds=10))
|
||||
freezer.tick(timedelta(seconds=15))
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert (state := hass.states.get("image.gsr_ae_gamerpic"))
|
||||
assert state.state == "2025-06-16T00:00:10+00:00"
|
||||
assert state.state == "2025-06-16T00:00:15+00:00"
|
||||
|
||||
access_token = state.attributes["access_token"]
|
||||
assert (
|
||||
|
||||
Reference in New Issue
Block a user