This commit is contained in:
Paulus Schoutsen 2022-09-13 21:50:29 -04:00 committed by GitHub
commit 896955e4df
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
54 changed files with 518 additions and 376 deletions

View File

@ -169,7 +169,6 @@ jobs:
uses: actions/setup-python@v4.1.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
cache: "pip"
- name: Restore base Python virtual environment
id: cache-venv
uses: actions/cache@v3.0.8
@ -484,7 +483,7 @@ jobs:
with:
path: venv
key: >-
${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
${{ runner.os }}-${{ matrix.python-version }}-${{
needs.info.outputs.python_cache_key }}
- name: Restore pip wheel cache
if: steps.cache-venv.outputs.cache-hit != 'true'
@ -492,10 +491,10 @@ jobs:
with:
path: ${{ env.PIP_CACHE }}
key: >-
${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
${{ runner.os }}-${{ matrix.python-version }}-${{
steps.generate-pip-key.outputs.key }}
restore-keys: |
${{ runner.os }}-${{ steps.python.outputs.python-version }}-pip-${{ env.PIP_CACHE_VERSION }}-${{ env.HA_SHORT_VERSION }}-
${{ runner.os }}-${{ matrix.python-version }}-pip-${{ env.PIP_CACHE_VERSION }}-${{ env.HA_SHORT_VERSION }}-
- name: Install additional OS dependencies
if: steps.cache-venv.outputs.cache-hit != 'true'
run: |
@ -542,7 +541,7 @@ jobs:
with:
path: venv
key: >-
${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
${{ runner.os }}-${{ env.DEFAULT_PYTHON }}-${{
needs.info.outputs.python_cache_key }}
- name: Fail job if Python cache restore failed
if: steps.cache-venv.outputs.cache-hit != 'true'
@ -574,7 +573,7 @@ jobs:
with:
path: venv
key: >-
${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
${{ runner.os }}-${{ env.DEFAULT_PYTHON }}-${{
needs.info.outputs.python_cache_key }}
- name: Fail job if Python cache restore failed
if: steps.cache-venv.outputs.cache-hit != 'true'
@ -607,7 +606,7 @@ jobs:
with:
path: venv
key: >-
${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
${{ runner.os }}-${{ env.DEFAULT_PYTHON }}-${{
needs.info.outputs.python_cache_key }}
- name: Fail job if Python cache restore failed
if: steps.cache-venv.outputs.cache-hit != 'true'
@ -651,7 +650,7 @@ jobs:
with:
path: venv
key: >-
${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
${{ runner.os }}-${{ env.DEFAULT_PYTHON }}-${{
needs.info.outputs.python_cache_key }}
- name: Fail job if Python cache restore failed
if: steps.cache-venv.outputs.cache-hit != 'true'
@ -699,7 +698,7 @@ jobs:
with:
path: venv
key: >-
${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
${{ runner.os }}-${{ matrix.python-version }}-${{
needs.info.outputs.python_cache_key }}
- name: Fail job if Python cache restore failed
if: steps.cache-venv.outputs.cache-hit != 'true'
@ -752,7 +751,7 @@ jobs:
uses: actions/cache@v3.0.8
with:
path: venv
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
key: ${{ runner.os }}-${{ matrix.python-version }}-${{
needs.info.outputs.python_cache_key }}
- name: Fail job if Python cache restore failed
if: steps.cache-venv.outputs.cache-hit != 'true'

View File

@ -2,7 +2,7 @@
"domain": "blink",
"name": "Blink",
"documentation": "https://www.home-assistant.io/integrations/blink",
"requirements": ["blinkpy==0.19.0"],
"requirements": ["blinkpy==0.19.2"],
"codeowners": ["@fronzbot"],
"dhcp": [
{

View File

@ -5,9 +5,11 @@
"dependencies": ["usb"],
"quality_scale": "internal",
"requirements": [
"bleak==0.16.0",
"bleak==0.17.0",
"bleak-retry-connector==1.15.1",
"bluetooth-adapters==0.4.1",
"bluetooth-auto-recovery==0.3.2"
"bluetooth-auto-recovery==0.3.3",
"dbus-fast==1.4.0"
],
"codeowners": ["@bdraco"],
"config_flow": true,

View File

@ -17,7 +17,7 @@ from bleak.backends.bluezdbus.advertisement_monitor import OrPattern
from bleak.backends.bluezdbus.scanner import BlueZScannerArgs
from bleak.backends.device import BLEDevice
from bleak.backends.scanner import AdvertisementData
from dbus_next import InvalidMessageError
from dbus_fast import InvalidMessageError
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
from homeassistant.core import (

View File

@ -47,7 +47,7 @@ SERVICE_CONFIGURE = "configure"
STORAGE_KEY = DOMAIN
STORAGE_VERSION = 1
CREATE_FIELDS = {
STORAGE_FIELDS = {
vol.Optional(CONF_ICON): cv.icon,
vol.Optional(CONF_INITIAL, default=DEFAULT_INITIAL): cv.positive_int,
vol.Required(CONF_NAME): vol.All(cv.string, vol.Length(min=1)),
@ -57,16 +57,6 @@ CREATE_FIELDS = {
vol.Optional(CONF_STEP, default=DEFAULT_STEP): cv.positive_int,
}
UPDATE_FIELDS = {
vol.Optional(CONF_ICON): cv.icon,
vol.Optional(CONF_INITIAL): cv.positive_int,
vol.Optional(CONF_NAME): cv.string,
vol.Optional(CONF_MAXIMUM): vol.Any(None, vol.Coerce(int)),
vol.Optional(CONF_MINIMUM): vol.Any(None, vol.Coerce(int)),
vol.Optional(CONF_RESTORE): cv.boolean,
vol.Optional(CONF_STEP): cv.positive_int,
}
def _none_to_empty_dict(value):
if value is None:
@ -128,7 +118,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
await storage_collection.async_load()
collection.StorageCollectionWebsocket(
storage_collection, DOMAIN, DOMAIN, CREATE_FIELDS, UPDATE_FIELDS
storage_collection, DOMAIN, DOMAIN, STORAGE_FIELDS, STORAGE_FIELDS
).async_setup(hass)
component.async_register_entity_service(SERVICE_INCREMENT, {}, "async_increment")
@ -152,12 +142,11 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
class CounterStorageCollection(collection.StorageCollection):
"""Input storage based collection."""
CREATE_SCHEMA = vol.Schema(CREATE_FIELDS)
UPDATE_SCHEMA = vol.Schema(UPDATE_FIELDS)
CREATE_UPDATE_SCHEMA = vol.Schema(STORAGE_FIELDS)
async def _process_create_data(self, data: dict) -> dict:
"""Validate the config is valid."""
return self.CREATE_SCHEMA(data)
return self.CREATE_UPDATE_SCHEMA(data)
@callback
def _get_suggested_id(self, info: dict) -> str:
@ -166,8 +155,8 @@ class CounterStorageCollection(collection.StorageCollection):
async def _update_data(self, data: dict, update_data: dict) -> dict:
"""Return a new updated data object."""
update_data = self.UPDATE_SCHEMA(update_data)
return {**data, **update_data}
update_data = self.CREATE_UPDATE_SCHEMA(update_data)
return {CONF_ID: data[CONF_ID]} | update_data
class Counter(RestoreEntity):

View File

@ -43,6 +43,9 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
except AuthenticationRequired as err:
raise ConfigEntryAuthFailed from err
if not hass.data[DOMAIN]:
async_setup_services(hass)
gateway = hass.data[DOMAIN][config_entry.entry_id] = DeconzGateway(
hass, config_entry, api
)
@ -53,9 +56,6 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
await async_setup_events(gateway)
await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS)
if len(hass.data[DOMAIN]) == 1:
async_setup_services(hass)
api.start()
config_entry.async_on_unload(

View File

@ -2,7 +2,7 @@
"domain": "dhcp",
"name": "DHCP Discovery",
"documentation": "https://www.home-assistant.io/integrations/dhcp",
"requirements": ["scapy==2.4.5", "aiodiscover==1.4.11"],
"requirements": ["scapy==2.4.5", "aiodiscover==1.4.13"],
"codeowners": ["@bdraco"],
"quality_scale": "internal",
"iot_class": "local_push",

View File

@ -44,6 +44,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
webhook.async_unregister(hass, entry.data[CONF_WEBHOOK_ID])
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
hass.data[DOMAIN].pop(entry.entry_id)

View File

@ -20,6 +20,7 @@ from homeassistant.const import (
ENERGY_KILO_WATT_HOUR,
ENERGY_MEGA_WATT_HOUR,
ENERGY_WATT_HOUR,
VOLUME_CUBIC_FEET,
VOLUME_CUBIC_METERS,
)
from homeassistant.core import (
@ -44,7 +45,7 @@ SUPPORTED_STATE_CLASSES = [
SensorStateClass.TOTAL_INCREASING,
]
VALID_ENERGY_UNITS = [ENERGY_WATT_HOUR, ENERGY_KILO_WATT_HOUR, ENERGY_MEGA_WATT_HOUR]
VALID_ENERGY_UNITS_GAS = [VOLUME_CUBIC_METERS] + VALID_ENERGY_UNITS
VALID_ENERGY_UNITS_GAS = [VOLUME_CUBIC_FEET, VOLUME_CUBIC_METERS] + VALID_ENERGY_UNITS
_LOGGER = logging.getLogger(__name__)

View File

@ -2,7 +2,7 @@
"domain": "frontend",
"name": "Home Assistant Frontend",
"documentation": "https://www.home-assistant.io/integrations/frontend",
"requirements": ["home-assistant-frontend==20220907.0"],
"requirements": ["home-assistant-frontend==20220907.1"],
"dependencies": [
"api",
"auth",

View File

@ -17,6 +17,11 @@
"service_uuid": "00008351-0000-1000-8000-00805f9b34fb",
"connectable": false
},
{
"manufacturer_id": 57391,
"service_uuid": "00008351-0000-1000-8000-00805f9b34fb",
"connectable": false
},
{
"manufacturer_id": 18994,
"service_uuid": "00008551-0000-1000-8000-00805f9b34fb",
@ -53,7 +58,7 @@
"connectable": false
}
],
"requirements": ["govee-ble==0.17.2"],
"requirements": ["govee-ble==0.17.3"],
"dependencies": ["bluetooth"],
"codeowners": ["@bdraco"],
"iot_class": "local_push"

View File

@ -3,7 +3,7 @@
"name": "HomeKit Controller",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/homekit_controller",
"requirements": ["aiohomekit==1.5.6"],
"requirements": ["aiohomekit==1.5.7"],
"zeroconf": ["_hap._tcp.local.", "_hap._udp.local."],
"bluetooth": [{ "manufacturer_id": 76, "manufacturer_data_start": [6] }],
"dependencies": ["bluetooth", "zeroconf"],

View File

@ -37,20 +37,25 @@ _LOGGER = logging.getLogger(__name__)
CONF_INITIAL = "initial"
CREATE_FIELDS = {
STORAGE_FIELDS = {
vol.Required(CONF_NAME): vol.All(str, vol.Length(min=1)),
vol.Optional(CONF_INITIAL): cv.boolean,
vol.Optional(CONF_ICON): cv.icon,
}
UPDATE_FIELDS = {
CONFIG_SCHEMA = vol.Schema(
{
DOMAIN: cv.schema_with_slug_keys(
vol.Any(
{
vol.Optional(CONF_NAME): cv.string,
vol.Optional(CONF_INITIAL): cv.boolean,
vol.Optional(CONF_ICON): cv.icon,
}
CONFIG_SCHEMA = vol.Schema(
{DOMAIN: cv.schema_with_slug_keys(vol.Any(UPDATE_FIELDS, None))},
},
None,
)
)
},
extra=vol.ALLOW_EXTRA,
)
@ -62,12 +67,11 @@ STORAGE_VERSION = 1
class InputBooleanStorageCollection(collection.StorageCollection):
"""Input boolean collection stored in storage."""
CREATE_SCHEMA = vol.Schema(CREATE_FIELDS)
UPDATE_SCHEMA = vol.Schema(UPDATE_FIELDS)
CREATE_UPDATE_SCHEMA = vol.Schema(STORAGE_FIELDS)
async def _process_create_data(self, data: dict) -> dict:
"""Validate the config is valid."""
return self.CREATE_SCHEMA(data)
return self.CREATE_UPDATE_SCHEMA(data)
@callback
def _get_suggested_id(self, info: dict) -> str:
@ -76,8 +80,8 @@ class InputBooleanStorageCollection(collection.StorageCollection):
async def _update_data(self, data: dict, update_data: dict) -> dict:
"""Return a new updated data object."""
update_data = self.UPDATE_SCHEMA(update_data)
return {**data, **update_data}
update_data = self.CREATE_UPDATE_SCHEMA(update_data)
return {CONF_ID: data[CONF_ID]} | update_data
@bind_hass
@ -118,7 +122,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
await storage_collection.async_load()
collection.StorageCollectionWebsocket(
storage_collection, DOMAIN, DOMAIN, CREATE_FIELDS, UPDATE_FIELDS
storage_collection, DOMAIN, DOMAIN, STORAGE_FIELDS, STORAGE_FIELDS
).async_setup(hass)
async def reload_service_handler(service_call: ServiceCall) -> None:

View File

@ -30,18 +30,23 @@ DOMAIN = "input_button"
_LOGGER = logging.getLogger(__name__)
CREATE_FIELDS = {
STORAGE_FIELDS = {
vol.Required(CONF_NAME): vol.All(str, vol.Length(min=1)),
vol.Optional(CONF_ICON): cv.icon,
}
UPDATE_FIELDS = {
CONFIG_SCHEMA = vol.Schema(
{
DOMAIN: cv.schema_with_slug_keys(
vol.Any(
{
vol.Optional(CONF_NAME): cv.string,
vol.Optional(CONF_ICON): cv.icon,
}
CONFIG_SCHEMA = vol.Schema(
{DOMAIN: cv.schema_with_slug_keys(vol.Any(UPDATE_FIELDS, None))},
},
None,
)
)
},
extra=vol.ALLOW_EXTRA,
)
@ -53,12 +58,11 @@ STORAGE_VERSION = 1
class InputButtonStorageCollection(collection.StorageCollection):
"""Input button collection stored in storage."""
CREATE_SCHEMA = vol.Schema(CREATE_FIELDS)
UPDATE_SCHEMA = vol.Schema(UPDATE_FIELDS)
CREATE_UPDATE_SCHEMA = vol.Schema(STORAGE_FIELDS)
async def _process_create_data(self, data: dict) -> vol.Schema:
"""Validate the config is valid."""
return self.CREATE_SCHEMA(data)
return self.CREATE_UPDATE_SCHEMA(data)
@callback
def _get_suggested_id(self, info: dict) -> str:
@ -67,8 +71,8 @@ class InputButtonStorageCollection(collection.StorageCollection):
async def _update_data(self, data: dict, update_data: dict) -> dict:
"""Return a new updated data object."""
update_data = self.UPDATE_SCHEMA(update_data)
return {**data, **update_data}
update_data = self.CREATE_UPDATE_SCHEMA(update_data)
return {CONF_ID: data[CONF_ID]} | update_data
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
@ -103,7 +107,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
await storage_collection.async_load()
collection.StorageCollectionWebsocket(
storage_collection, DOMAIN, DOMAIN, CREATE_FIELDS, UPDATE_FIELDS
storage_collection, DOMAIN, DOMAIN, STORAGE_FIELDS, STORAGE_FIELDS
).async_setup(hass)
async def reload_service_handler(service_call: ServiceCall) -> None:

View File

@ -61,20 +61,13 @@ def validate_set_datetime_attrs(config):
STORAGE_KEY = DOMAIN
STORAGE_VERSION = 1
CREATE_FIELDS = {
STORAGE_FIELDS = {
vol.Required(CONF_NAME): vol.All(str, vol.Length(min=1)),
vol.Optional(CONF_HAS_DATE, default=False): cv.boolean,
vol.Optional(CONF_HAS_TIME, default=False): cv.boolean,
vol.Optional(CONF_ICON): cv.icon,
vol.Optional(CONF_INITIAL): cv.string,
}
UPDATE_FIELDS = {
vol.Optional(CONF_NAME): cv.string,
vol.Optional(CONF_HAS_DATE): cv.boolean,
vol.Optional(CONF_HAS_TIME): cv.boolean,
vol.Optional(CONF_ICON): cv.icon,
vol.Optional(CONF_INITIAL): cv.string,
}
def has_date_or_time(conf):
@ -167,7 +160,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
await storage_collection.async_load()
collection.StorageCollectionWebsocket(
storage_collection, DOMAIN, DOMAIN, CREATE_FIELDS, UPDATE_FIELDS
storage_collection, DOMAIN, DOMAIN, STORAGE_FIELDS, STORAGE_FIELDS
).async_setup(hass)
async def reload_service_handler(service_call: ServiceCall) -> None:
@ -213,12 +206,11 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
class DateTimeStorageCollection(collection.StorageCollection):
"""Input storage based collection."""
CREATE_SCHEMA = vol.Schema(vol.All(CREATE_FIELDS, has_date_or_time))
UPDATE_SCHEMA = vol.Schema(UPDATE_FIELDS)
CREATE_UPDATE_SCHEMA = vol.Schema(vol.All(STORAGE_FIELDS, has_date_or_time))
async def _process_create_data(self, data: dict) -> dict:
"""Validate the config is valid."""
return self.CREATE_SCHEMA(data)
return self.CREATE_UPDATE_SCHEMA(data)
@callback
def _get_suggested_id(self, info: dict) -> str:
@ -227,8 +219,8 @@ class DateTimeStorageCollection(collection.StorageCollection):
async def _update_data(self, data: dict, update_data: dict) -> dict:
"""Return a new updated data object."""
update_data = self.UPDATE_SCHEMA(update_data)
return has_date_or_time({**data, **update_data})
update_data = self.CREATE_UPDATE_SCHEMA(update_data)
return {CONF_ID: data[CONF_ID]} | update_data
class InputDatetime(RestoreEntity):

View File

@ -65,7 +65,7 @@ def _cv_input_number(cfg):
return cfg
CREATE_FIELDS = {
STORAGE_FIELDS = {
vol.Required(CONF_NAME): vol.All(str, vol.Length(min=1)),
vol.Required(CONF_MIN): vol.Coerce(float),
vol.Required(CONF_MAX): vol.Coerce(float),
@ -76,17 +76,6 @@ CREATE_FIELDS = {
vol.Optional(CONF_MODE, default=MODE_SLIDER): vol.In([MODE_BOX, MODE_SLIDER]),
}
UPDATE_FIELDS = {
vol.Optional(CONF_NAME): cv.string,
vol.Optional(CONF_MIN): vol.Coerce(float),
vol.Optional(CONF_MAX): vol.Coerce(float),
vol.Optional(CONF_INITIAL): vol.Coerce(float),
vol.Optional(CONF_STEP): vol.All(vol.Coerce(float), vol.Range(min=1e-9)),
vol.Optional(CONF_ICON): cv.icon,
vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string,
vol.Optional(CONF_MODE): vol.In([MODE_BOX, MODE_SLIDER]),
}
CONFIG_SCHEMA = vol.Schema(
{
DOMAIN: cv.schema_with_slug_keys(
@ -148,7 +137,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
await storage_collection.async_load()
collection.StorageCollectionWebsocket(
storage_collection, DOMAIN, DOMAIN, CREATE_FIELDS, UPDATE_FIELDS
storage_collection, DOMAIN, DOMAIN, STORAGE_FIELDS, STORAGE_FIELDS
).async_setup(hass)
async def reload_service_handler(service_call: ServiceCall) -> None:
@ -184,22 +173,37 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
class NumberStorageCollection(collection.StorageCollection):
"""Input storage based collection."""
CREATE_SCHEMA = vol.Schema(vol.All(CREATE_FIELDS, _cv_input_number))
UPDATE_SCHEMA = vol.Schema(UPDATE_FIELDS)
SCHEMA = vol.Schema(vol.All(STORAGE_FIELDS, _cv_input_number))
async def _process_create_data(self, data: dict) -> dict:
"""Validate the config is valid."""
return self.CREATE_SCHEMA(data)
return self.SCHEMA(data)
@callback
def _get_suggested_id(self, info: dict) -> str:
"""Suggest an ID based on the config."""
return info[CONF_NAME]
async def _async_load_data(self) -> dict | None:
"""Load the data.
A past bug caused frontend to add initial value to all input numbers.
This drops that.
"""
data = await super()._async_load_data()
if data is None:
return data
for number in data["items"]:
number.pop(CONF_INITIAL, None)
return data
async def _update_data(self, data: dict, update_data: dict) -> dict:
"""Return a new updated data object."""
update_data = self.UPDATE_SCHEMA(update_data)
return _cv_input_number({**data, **update_data})
update_data = self.SCHEMA(update_data)
return {CONF_ID: data[CONF_ID]} | update_data
class InputNumber(RestoreEntity):

View File

@ -56,7 +56,7 @@ def _unique(options: Any) -> Any:
raise HomeAssistantError("Duplicate options are not allowed") from exc
CREATE_FIELDS = {
STORAGE_FIELDS = {
vol.Required(CONF_NAME): vol.All(str, vol.Length(min=1)),
vol.Required(CONF_OPTIONS): vol.All(
cv.ensure_list, vol.Length(min=1), _unique, [cv.string]
@ -64,14 +64,6 @@ CREATE_FIELDS = {
vol.Optional(CONF_INITIAL): cv.string,
vol.Optional(CONF_ICON): cv.icon,
}
UPDATE_FIELDS = {
vol.Optional(CONF_NAME): cv.string,
vol.Optional(CONF_OPTIONS): vol.All(
cv.ensure_list, vol.Length(min=1), _unique, [cv.string]
),
vol.Optional(CONF_INITIAL): cv.string,
vol.Optional(CONF_ICON): cv.icon,
}
def _remove_duplicates(options: list[str], name: str | None) -> list[str]:
@ -172,7 +164,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
await storage_collection.async_load()
collection.StorageCollectionWebsocket(
storage_collection, DOMAIN, DOMAIN, CREATE_FIELDS, UPDATE_FIELDS
storage_collection, DOMAIN, DOMAIN, STORAGE_FIELDS, STORAGE_FIELDS
).async_setup(hass)
async def reload_service_handler(service_call: ServiceCall) -> None:
@ -238,12 +230,11 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
class InputSelectStorageCollection(collection.StorageCollection):
"""Input storage based collection."""
CREATE_SCHEMA = vol.Schema(vol.All(CREATE_FIELDS, _cv_input_select))
UPDATE_SCHEMA = vol.Schema(UPDATE_FIELDS)
CREATE_UPDATE_SCHEMA = vol.Schema(vol.All(STORAGE_FIELDS, _cv_input_select))
async def _process_create_data(self, data: dict[str, Any]) -> dict[str, Any]:
"""Validate the config is valid."""
return cast(dict[str, Any], self.CREATE_SCHEMA(data))
return cast(dict[str, Any], self.CREATE_UPDATE_SCHEMA(data))
@callback
def _get_suggested_id(self, info: dict[str, Any]) -> str:
@ -254,8 +245,8 @@ class InputSelectStorageCollection(collection.StorageCollection):
self, data: dict[str, Any], update_data: dict[str, Any]
) -> dict[str, Any]:
"""Return a new updated data object."""
update_data = self.UPDATE_SCHEMA(update_data)
return _cv_input_select({**data, **update_data})
update_data = self.CREATE_UPDATE_SCHEMA(update_data)
return {CONF_ID: data[CONF_ID]} | update_data
class InputSelect(SelectEntity, RestoreEntity):

View File

@ -51,7 +51,7 @@ SERVICE_SET_VALUE = "set_value"
STORAGE_KEY = DOMAIN
STORAGE_VERSION = 1
CREATE_FIELDS = {
STORAGE_FIELDS = {
vol.Required(CONF_NAME): vol.All(str, vol.Length(min=1)),
vol.Optional(CONF_MIN, default=CONF_MIN_VALUE): vol.Coerce(int),
vol.Optional(CONF_MAX, default=CONF_MAX_VALUE): vol.Coerce(int),
@ -61,16 +61,6 @@ CREATE_FIELDS = {
vol.Optional(CONF_PATTERN): cv.string,
vol.Optional(CONF_MODE, default=MODE_TEXT): vol.In([MODE_TEXT, MODE_PASSWORD]),
}
UPDATE_FIELDS = {
vol.Optional(CONF_NAME): cv.string,
vol.Optional(CONF_MIN): vol.Coerce(int),
vol.Optional(CONF_MAX): vol.Coerce(int),
vol.Optional(CONF_INITIAL): cv.string,
vol.Optional(CONF_ICON): cv.icon,
vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string,
vol.Optional(CONF_PATTERN): cv.string,
vol.Optional(CONF_MODE): vol.In([MODE_TEXT, MODE_PASSWORD]),
}
def _cv_input_text(cfg):
@ -147,7 +137,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
await storage_collection.async_load()
collection.StorageCollectionWebsocket(
storage_collection, DOMAIN, DOMAIN, CREATE_FIELDS, UPDATE_FIELDS
storage_collection, DOMAIN, DOMAIN, STORAGE_FIELDS, STORAGE_FIELDS
).async_setup(hass)
async def reload_service_handler(service_call: ServiceCall) -> None:
@ -177,12 +167,11 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
class InputTextStorageCollection(collection.StorageCollection):
"""Input storage based collection."""
CREATE_SCHEMA = vol.Schema(vol.All(CREATE_FIELDS, _cv_input_text))
UPDATE_SCHEMA = vol.Schema(UPDATE_FIELDS)
CREATE_UPDATE_SCHEMA = vol.Schema(vol.All(STORAGE_FIELDS, _cv_input_text))
async def _process_create_data(self, data: dict) -> dict:
"""Validate the config is valid."""
return self.CREATE_SCHEMA(data)
return self.CREATE_UPDATE_SCHEMA(data)
@callback
def _get_suggested_id(self, info: dict) -> str:
@ -191,8 +180,8 @@ class InputTextStorageCollection(collection.StorageCollection):
async def _update_data(self, data: dict, update_data: dict) -> dict:
"""Return a new updated data object."""
update_data = self.UPDATE_SCHEMA(update_data)
return _cv_input_text({**data, **update_data})
update_data = self.CREATE_UPDATE_SCHEMA(update_data)
return {CONF_ID: data[CONF_ID]} | update_data
class InputText(RestoreEntity):

View File

@ -1,19 +1,61 @@
"""Component for the Portuguese weather service - IPMA."""
import logging
import async_timeout
from pyipma import IPMAException
from pyipma.api import IPMA_API
from pyipma.location import Location
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .config_flow import IpmaFlowHandler # noqa: F401
from .const import DOMAIN # noqa: F401
from .const import DATA_API, DATA_LOCATION, DOMAIN
DEFAULT_NAME = "ipma"
PLATFORMS = [Platform.WEATHER]
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_get_api(hass):
"""Get the pyipma api object."""
websession = async_get_clientsession(hass)
return IPMA_API(websession)
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
"""Set up IPMA station as config entry."""
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
latitude = config_entry.data[CONF_LATITUDE]
longitude = config_entry.data[CONF_LONGITUDE]
api = await async_get_api(hass)
try:
async with async_timeout.timeout(30):
location = await Location.get(api, float(latitude), float(longitude))
_LOGGER.debug(
"Initializing for coordinates %s, %s -> station %s (%d, %d)",
latitude,
longitude,
location.station,
location.id_station,
location.global_id_local,
)
except IPMAException as err:
raise ConfigEntryNotReady(
f"Could not get location for ({latitude},{longitude})"
) from err
hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN][config_entry.entry_id] = {DATA_API: api, DATA_LOCATION: location}
await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS)
return True

View File

@ -6,3 +6,6 @@ DOMAIN = "ipma"
HOME_LOCATION_NAME = "Home"
ENTITY_ID_SENSOR_FORMAT_HOME = f"{WEATHER_DOMAIN}.ipma_{HOME_LOCATION_NAME}"
DATA_LOCATION = "location"
DATA_API = "api"

View File

@ -3,7 +3,7 @@
"name": "Instituto Portugu\u00eas do Mar e Atmosfera (IPMA)",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/ipma",
"requirements": ["pyipma==3.0.2"],
"requirements": ["pyipma==3.0.4"],
"codeowners": ["@dgomes", "@abmantis"],
"iot_class": "cloud_polling",
"loggers": ["geopy", "pyipma"]

View File

@ -48,11 +48,12 @@ from homeassistant.const import (
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import config_validation as cv, entity_registry
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.sun import is_up
from homeassistant.util import Throttle
from .const import DATA_API, DATA_LOCATION, DOMAIN
_LOGGER = logging.getLogger(__name__)
ATTRIBUTION = "Instituto Português do Mar e Atmosfera"
@ -95,13 +96,10 @@ async def async_setup_entry(
async_add_entities: AddEntitiesCallback,
) -> None:
"""Add a weather entity from a config_entry."""
latitude = config_entry.data[CONF_LATITUDE]
longitude = config_entry.data[CONF_LONGITUDE]
api = hass.data[DOMAIN][config_entry.entry_id][DATA_API]
location = hass.data[DOMAIN][config_entry.entry_id][DATA_LOCATION]
mode = config_entry.data[CONF_MODE]
api = await async_get_api(hass)
location = await async_get_location(hass, api, latitude, longitude)
# Migrate old unique_id
@callback
def _async_migrator(entity_entry: entity_registry.RegistryEntry):
@ -127,29 +125,6 @@ async def async_setup_entry(
async_add_entities([IPMAWeather(location, api, config_entry.data)], True)
async def async_get_api(hass):
"""Get the pyipma api object."""
websession = async_get_clientsession(hass)
return IPMA_API(websession)
async def async_get_location(hass, api, latitude, longitude):
"""Retrieve pyipma location, location name to be used as the entity name."""
async with async_timeout.timeout(30):
location = await Location.get(api, float(latitude), float(longitude))
_LOGGER.debug(
"Initializing for coordinates %s, %s -> station %s (%d, %d)",
latitude,
longitude,
location.station,
location.id_station,
location.global_id_local,
)
return location
class IPMAWeather(WeatherEntity):
"""Representation of a weather condition."""

View File

@ -3,7 +3,7 @@
"name": "LED BLE",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/ble_ble",
"requirements": ["led-ble==0.9.1"],
"requirements": ["led-ble==0.10.0"],
"dependencies": ["bluetooth"],
"codeowners": ["@bdraco"],
"bluetooth": [

View File

@ -99,4 +99,11 @@ class RainMachineUpdateEntity(RainMachineEntity, UpdateEntity):
UpdateStates.UPGRADING,
UpdateStates.REBOOT,
)
self._attr_latest_version = data["packageDetails"]["newVersion"]
# The RainMachine API docs say that multiple "packages" can be updated, but
# don't give details on what types exist (which makes it impossible to have
# update entities per update type); so, we use the first one (with the idea that
# after it succeeds, the entity will show the next update):
package_details = data["packageDetails"][0]
self._attr_latest_version = package_details["newVersion"]
self._attr_title = package_details["packageName"]

View File

@ -2,7 +2,7 @@
"domain": "switchbot",
"name": "SwitchBot",
"documentation": "https://www.home-assistant.io/integrations/switchbot",
"requirements": ["PySwitchbot==0.19.5"],
"requirements": ["PySwitchbot==0.19.8"],
"config_flow": true,
"dependencies": ["bluetooth"],
"codeowners": [

View File

@ -61,18 +61,12 @@ SERVICE_FINISH = "finish"
STORAGE_KEY = DOMAIN
STORAGE_VERSION = 1
CREATE_FIELDS = {
STORAGE_FIELDS = {
vol.Required(CONF_NAME): cv.string,
vol.Optional(CONF_ICON): cv.icon,
vol.Optional(CONF_DURATION, default=DEFAULT_DURATION): cv.time_period,
vol.Optional(CONF_RESTORE, default=DEFAULT_RESTORE): cv.boolean,
}
UPDATE_FIELDS = {
vol.Optional(CONF_NAME): cv.string,
vol.Optional(CONF_ICON): cv.icon,
vol.Optional(CONF_DURATION): cv.time_period,
vol.Optional(CONF_RESTORE): cv.boolean,
}
def _format_timedelta(delta: timedelta):
@ -137,7 +131,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
await storage_collection.async_load()
collection.StorageCollectionWebsocket(
storage_collection, DOMAIN, DOMAIN, CREATE_FIELDS, UPDATE_FIELDS
storage_collection, DOMAIN, DOMAIN, STORAGE_FIELDS, STORAGE_FIELDS
).async_setup(hass)
async def reload_service_handler(service_call: ServiceCall) -> None:
@ -171,12 +165,11 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
class TimerStorageCollection(collection.StorageCollection):
"""Timer storage based collection."""
CREATE_SCHEMA = vol.Schema(CREATE_FIELDS)
UPDATE_SCHEMA = vol.Schema(UPDATE_FIELDS)
CREATE_UPDATE_SCHEMA = vol.Schema(STORAGE_FIELDS)
async def _process_create_data(self, data: dict) -> dict:
"""Validate the config is valid."""
data = self.CREATE_SCHEMA(data)
data = self.CREATE_UPDATE_SCHEMA(data)
# make duration JSON serializeable
data[CONF_DURATION] = _format_timedelta(data[CONF_DURATION])
return data
@ -188,7 +181,7 @@ class TimerStorageCollection(collection.StorageCollection):
async def _update_data(self, data: dict, update_data: dict) -> dict:
"""Return a new updated data object."""
data = {**data, **self.UPDATE_SCHEMA(update_data)}
data = {CONF_ID: data[CONF_ID]} | self.CREATE_UPDATE_SCHEMA(update_data)
# make duration JSON serializeable
if CONF_DURATION in update_data:
data[CONF_DURATION] = _format_timedelta(data[CONF_DURATION])

View File

@ -3,7 +3,7 @@
"name": "Viessmann ViCare",
"documentation": "https://www.home-assistant.io/integrations/vicare",
"codeowners": ["@oischinger"],
"requirements": ["PyViCare==2.16.2"],
"requirements": ["PyViCare==2.17.0"],
"iot_class": "cloud_polling",
"config_flow": true,
"dhcp": [

View File

@ -9,7 +9,7 @@
"service_data_uuid": "0000fe95-0000-1000-8000-00805f9b34fb"
}
],
"requirements": ["xiaomi-ble==0.9.0"],
"requirements": ["xiaomi-ble==0.10.0"],
"dependencies": ["bluetooth"],
"codeowners": ["@Jc2k", "@Ernst79"],
"iot_class": "local_push"

View File

@ -3,10 +3,15 @@
"name": "Yale Access Bluetooth",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/yalexs_ble",
"requirements": ["yalexs-ble==1.8.1"],
"requirements": ["yalexs-ble==1.9.0"],
"dependencies": ["bluetooth"],
"codeowners": ["@bdraco"],
"bluetooth": [{ "manufacturer_id": 465 }],
"bluetooth": [
{
"manufacturer_id": 465,
"service_uuid": "0000fe24-0000-1000-8000-00805f9b34fb"
}
],
"iot_class": "local_push",
"supported_brands": {
"august_ble": "August Bluetooth"

View File

@ -84,7 +84,7 @@ PARALLEL_UPDATES = 0
SIGNAL_LIGHT_GROUP_STATE_CHANGED = "zha_light_group_state_changed"
SIGNAL_LIGHT_GROUP_TRANSITION_START = "zha_light_group_transition_start"
SIGNAL_LIGHT_GROUP_TRANSITION_FINISHED = "zha_light_group_transition_finished"
DEFAULT_MIN_TRANSITION_MANUFACTURERS = {"Sengled"}
DEFAULT_MIN_TRANSITION_MANUFACTURERS = {"sengled"}
COLOR_MODES_GROUP_LIGHT = {ColorMode.COLOR_TEMP, ColorMode.XY}
SUPPORT_GROUP_LIGHT = (

View File

@ -7,7 +7,7 @@ from .backports.enum import StrEnum
MAJOR_VERSION: Final = 2022
MINOR_VERSION: Final = 9
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, 9, 0)

View File

@ -62,6 +62,12 @@ BLUETOOTH: list[dict[str, bool | str | int | list[int]]] = [
"service_uuid": "00008351-0000-1000-8000-00805f9b34fb",
"connectable": False
},
{
"domain": "govee_ble",
"manufacturer_id": 57391,
"service_uuid": "00008351-0000-1000-8000-00805f9b34fb",
"connectable": False
},
{
"domain": "govee_ble",
"manufacturer_id": 18994,
@ -281,6 +287,7 @@ BLUETOOTH: list[dict[str, bool | str | int | list[int]]] = [
},
{
"domain": "yalexs_ble",
"manufacturer_id": 465
"manufacturer_id": 465,
"service_uuid": "0000fe24-0000-1000-8000-00805f9b34fb"
}
]

View File

@ -1,6 +1,6 @@
PyJWT==2.4.0
PyNaCl==1.5.0
aiodiscover==1.4.11
aiodiscover==1.4.13
aiohttp==3.8.1
aiohttp_cors==0.7.0
astral==2.2
@ -10,16 +10,18 @@ atomicwrites-homeassistant==1.4.1
attrs==21.2.0
awesomeversion==22.8.0
bcrypt==3.1.7
bleak==0.16.0
bleak-retry-connector==1.15.1
bleak==0.17.0
bluetooth-adapters==0.4.1
bluetooth-auto-recovery==0.3.2
bluetooth-auto-recovery==0.3.3
certifi>=2021.5.30
ciso8601==2.2.0
cryptography==37.0.4
dbus-fast==1.4.0
fnvhash==0.1.0
hass-nabucasa==0.55.0
home-assistant-bluetooth==1.3.0
home-assistant-frontend==20220907.0
home-assistant-frontend==20220907.1
httpx==0.23.0
ifaddr==0.1.7
jinja2==3.1.2

View File

@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "homeassistant"
version = "2022.9.2"
version = "2022.9.3"
license = {text = "Apache-2.0"}
description = "Open-source home automation platform running on Python 3."
readme = "README.rst"

View File

@ -37,7 +37,7 @@ PyRMVtransport==0.3.3
PySocks==1.7.1
# homeassistant.components.switchbot
PySwitchbot==0.19.5
PySwitchbot==0.19.8
# homeassistant.components.transport_nsw
PyTransportNSW==0.1.1
@ -47,7 +47,7 @@ PyTransportNSW==0.1.1
PyTurboJPEG==1.6.7
# homeassistant.components.vicare
PyViCare==2.16.2
PyViCare==2.17.0
# homeassistant.components.xiaomi_aqara
PyXiaomiGateway==0.13.4
@ -134,7 +134,7 @@ aiobafi6==0.7.2
aiobotocore==2.1.0
# homeassistant.components.dhcp
aiodiscover==1.4.11
aiodiscover==1.4.13
# homeassistant.components.dnsip
# homeassistant.components.minecraft_server
@ -171,7 +171,7 @@ aioguardian==2022.07.0
aioharmony==0.2.9
# homeassistant.components.homekit_controller
aiohomekit==1.5.6
aiohomekit==1.5.7
# homeassistant.components.emulated_hue
# homeassistant.components.http
@ -408,13 +408,16 @@ bimmer_connected==0.10.2
bizkaibus==0.1.1
# homeassistant.components.bluetooth
bleak==0.16.0
bleak-retry-connector==1.15.1
# homeassistant.components.bluetooth
bleak==0.17.0
# homeassistant.components.blebox
blebox_uniapi==2.0.2
# homeassistant.components.blink
blinkpy==0.19.0
blinkpy==0.19.2
# homeassistant.components.blinksticklight
blinkstick==1.2.0
@ -433,7 +436,7 @@ bluemaestro-ble==0.2.0
bluetooth-adapters==0.4.1
# homeassistant.components.bluetooth
bluetooth-auto-recovery==0.3.2
bluetooth-auto-recovery==0.3.3
# homeassistant.components.bond
bond-async==0.1.22
@ -534,6 +537,9 @@ datadog==0.15.0
# homeassistant.components.metoffice
datapoint==0.9.8
# homeassistant.components.bluetooth
dbus-fast==1.4.0
# homeassistant.components.debugpy
debugpy==1.6.3
@ -772,7 +778,7 @@ googlemaps==2.5.1
goslide-api==0.5.1
# homeassistant.components.govee_ble
govee-ble==0.17.2
govee-ble==0.17.3
# homeassistant.components.remote_rpi_gpio
gpiozero==1.6.2
@ -851,7 +857,7 @@ hole==0.7.0
holidays==0.14.2
# homeassistant.components.frontend
home-assistant-frontend==20220907.0
home-assistant-frontend==20220907.1
# homeassistant.components.home_connect
homeconnect==0.7.2
@ -968,7 +974,7 @@ lakeside==0.12
laundrify_aio==1.1.2
# homeassistant.components.led_ble
led-ble==0.9.1
led-ble==0.10.0
# homeassistant.components.foscam
libpyfoscam==1.0
@ -1608,7 +1614,7 @@ pyinsteon==1.2.0
pyintesishome==1.8.0
# homeassistant.components.ipma
pyipma==3.0.2
pyipma==3.0.4
# homeassistant.components.ipp
pyipp==0.11.0
@ -2522,7 +2528,7 @@ xbox-webapi==2.0.11
xboxapi==2.0.1
# homeassistant.components.xiaomi_ble
xiaomi-ble==0.9.0
xiaomi-ble==0.10.0
# homeassistant.components.knx
xknx==1.0.2
@ -2542,7 +2548,7 @@ xs1-api-client==3.0.0
yalesmartalarmclient==0.3.9
# homeassistant.components.yalexs_ble
yalexs-ble==1.8.1
yalexs-ble==1.9.0
# homeassistant.components.august
yalexs==1.2.1

View File

@ -33,7 +33,7 @@ PyRMVtransport==0.3.3
PySocks==1.7.1
# homeassistant.components.switchbot
PySwitchbot==0.19.5
PySwitchbot==0.19.8
# homeassistant.components.transport_nsw
PyTransportNSW==0.1.1
@ -43,7 +43,7 @@ PyTransportNSW==0.1.1
PyTurboJPEG==1.6.7
# homeassistant.components.vicare
PyViCare==2.16.2
PyViCare==2.17.0
# homeassistant.components.xiaomi_aqara
PyXiaomiGateway==0.13.4
@ -121,7 +121,7 @@ aiobafi6==0.7.2
aiobotocore==2.1.0
# homeassistant.components.dhcp
aiodiscover==1.4.11
aiodiscover==1.4.13
# homeassistant.components.dnsip
# homeassistant.components.minecraft_server
@ -155,7 +155,7 @@ aioguardian==2022.07.0
aioharmony==0.2.9
# homeassistant.components.homekit_controller
aiohomekit==1.5.6
aiohomekit==1.5.7
# homeassistant.components.emulated_hue
# homeassistant.components.http
@ -329,13 +329,16 @@ bellows==0.33.1
bimmer_connected==0.10.2
# homeassistant.components.bluetooth
bleak==0.16.0
bleak-retry-connector==1.15.1
# homeassistant.components.bluetooth
bleak==0.17.0
# homeassistant.components.blebox
blebox_uniapi==2.0.2
# homeassistant.components.blink
blinkpy==0.19.0
blinkpy==0.19.2
# homeassistant.components.bluemaestro
bluemaestro-ble==0.2.0
@ -344,7 +347,7 @@ bluemaestro-ble==0.2.0
bluetooth-adapters==0.4.1
# homeassistant.components.bluetooth
bluetooth-auto-recovery==0.3.2
bluetooth-auto-recovery==0.3.3
# homeassistant.components.bond
bond-async==0.1.22
@ -411,6 +414,9 @@ datadog==0.15.0
# homeassistant.components.metoffice
datapoint==0.9.8
# homeassistant.components.bluetooth
dbus-fast==1.4.0
# homeassistant.components.debugpy
debugpy==1.6.3
@ -573,7 +579,7 @@ google-nest-sdm==2.0.0
googlemaps==2.5.1
# homeassistant.components.govee_ble
govee-ble==0.17.2
govee-ble==0.17.3
# homeassistant.components.gree
greeclimate==1.3.0
@ -628,7 +634,7 @@ hole==0.7.0
holidays==0.14.2
# homeassistant.components.frontend
home-assistant-frontend==20220907.0
home-assistant-frontend==20220907.1
# homeassistant.components.home_connect
homeconnect==0.7.2
@ -706,7 +712,7 @@ lacrosse-view==0.0.9
laundrify_aio==1.1.2
# homeassistant.components.led_ble
led-ble==0.9.1
led-ble==0.10.0
# homeassistant.components.foscam
libpyfoscam==1.0
@ -1121,7 +1127,7 @@ pyicloud==1.0.0
pyinsteon==1.2.0
# homeassistant.components.ipma
pyipma==3.0.2
pyipma==3.0.4
# homeassistant.components.ipp
pyipp==0.11.0
@ -1729,7 +1735,7 @@ wolf_smartset==0.1.11
xbox-webapi==2.0.11
# homeassistant.components.xiaomi_ble
xiaomi-ble==0.9.0
xiaomi-ble==0.10.0
# homeassistant.components.knx
xknx==1.0.2
@ -1746,7 +1752,7 @@ xmltodict==0.13.0
yalesmartalarmclient==0.3.9
# homeassistant.components.yalexs_ble
yalexs-ble==1.8.1
yalexs-ble==1.9.0
# homeassistant.components.august
yalexs==1.2.1

View File

@ -7,7 +7,7 @@ from bleak.backends.scanner import (
AdvertisementDataCallback,
BLEDevice,
)
from dbus_next import InvalidMessageError
from dbus_fast import InvalidMessageError
import pytest
from homeassistant.components import bluetooth

View File

@ -591,9 +591,7 @@ async def test_ws_delete(hass, hass_ws_client, storage_setup):
async def test_update_min_max(hass, hass_ws_client, storage_setup):
"""Test updating min/max updates the state."""
items = [
{
"id": "from_storage",
settings = {
"initial": 15,
"name": "from storage",
"maximum": 100,
@ -601,7 +599,7 @@ async def test_update_min_max(hass, hass_ws_client, storage_setup):
"step": 3,
"restore": True,
}
]
items = [{"id": "from_storage"} | settings]
assert await storage_setup(items)
input_id = "from_storage"
@ -618,16 +616,18 @@ async def test_update_min_max(hass, hass_ws_client, storage_setup):
client = await hass_ws_client(hass)
updated_settings = settings | {"minimum": 19}
await client.send_json(
{
"id": 6,
"type": f"{DOMAIN}/update",
f"{DOMAIN}_id": f"{input_id}",
"minimum": 19,
**updated_settings,
}
)
resp = await client.receive_json()
assert resp["success"]
assert resp["result"] == {"id": "from_storage"} | updated_settings
state = hass.states.get(input_entity_id)
assert int(state.state) == 19
@ -635,18 +635,18 @@ async def test_update_min_max(hass, hass_ws_client, storage_setup):
assert state.attributes[ATTR_MAXIMUM] == 100
assert state.attributes[ATTR_STEP] == 3
updated_settings = settings | {"maximum": 5, "minimum": 2, "step": 5}
await client.send_json(
{
"id": 7,
"type": f"{DOMAIN}/update",
f"{DOMAIN}_id": f"{input_id}",
"maximum": 5,
"minimum": 2,
"step": 5,
**updated_settings,
}
)
resp = await client.receive_json()
assert resp["success"]
assert resp["result"] == {"id": "from_storage"} | updated_settings
state = hass.states.get(input_entity_id)
assert int(state.state) == 5
@ -654,18 +654,18 @@ async def test_update_min_max(hass, hass_ws_client, storage_setup):
assert state.attributes[ATTR_MAXIMUM] == 5
assert state.attributes[ATTR_STEP] == 5
updated_settings = settings | {"maximum": None, "minimum": None, "step": 6}
await client.send_json(
{
"id": 8,
"type": f"{DOMAIN}/update",
f"{DOMAIN}_id": f"{input_id}",
"maximum": None,
"minimum": None,
"step": 6,
**updated_settings,
}
)
resp = await client.receive_json()
assert resp["success"]
assert resp["result"] == {"id": "from_storage"} | updated_settings
state = hass.states.get(input_entity_id)
assert int(state.state) == 5

View File

@ -20,6 +20,7 @@ from homeassistant.const import (
ENERGY_MEGA_WATT_HOUR,
ENERGY_WATT_HOUR,
STATE_UNKNOWN,
VOLUME_CUBIC_FEET,
VOLUME_CUBIC_METERS,
)
from homeassistant.helpers import entity_registry as er
@ -841,10 +842,13 @@ async def test_cost_sensor_handle_price_units(
assert state.state == "20.0"
async def test_cost_sensor_handle_gas(hass, hass_storage, setup_integration) -> None:
@pytest.mark.parametrize("unit", (VOLUME_CUBIC_FEET, VOLUME_CUBIC_METERS))
async def test_cost_sensor_handle_gas(
hass, hass_storage, setup_integration, unit
) -> None:
"""Test gas cost price from sensor entity."""
energy_attributes = {
ATTR_UNIT_OF_MEASUREMENT: VOLUME_CUBIC_METERS,
ATTR_UNIT_OF_MEASUREMENT: unit,
ATTR_STATE_CLASS: SensorStateClass.TOTAL_INCREASING,
}
energy_data = data.EnergyManager.default_preferences()

View File

@ -40,7 +40,11 @@ def storage_setup(hass, hass_storage):
"data": {"items": [{"id": "from_storage", "name": "from storage"}]},
}
else:
hass_storage[DOMAIN] = items
hass_storage[DOMAIN] = {
"key": DOMAIN,
"version": 1,
"data": {"items": items},
}
if config is None:
config = {DOMAIN: {}}
return await async_setup_component(hass, DOMAIN, config)
@ -332,6 +336,89 @@ async def test_ws_delete(hass, hass_ws_client, storage_setup):
assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, input_id) is None
async def test_ws_update(hass, hass_ws_client, storage_setup):
"""Test update WS."""
settings = {
"name": "from storage",
}
items = [{"id": "from_storage"} | settings]
assert await storage_setup(items)
input_id = "from_storage"
input_entity_id = f"{DOMAIN}.{input_id}"
ent_reg = er.async_get(hass)
state = hass.states.get(input_entity_id)
assert state is not None
assert state.state
assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, input_id) is not None
client = await hass_ws_client(hass)
updated_settings = settings | {"name": "new_name", "icon": "mdi:blah"}
await client.send_json(
{
"id": 6,
"type": f"{DOMAIN}/update",
f"{DOMAIN}_id": f"{input_id}",
**updated_settings,
}
)
resp = await client.receive_json()
assert resp["success"]
assert resp["result"] == {"id": "from_storage"} | updated_settings
state = hass.states.get(input_entity_id)
assert state.attributes["icon"] == "mdi:blah"
assert state.attributes["friendly_name"] == "new_name"
updated_settings = settings | {"name": "new_name_2"}
await client.send_json(
{
"id": 7,
"type": f"{DOMAIN}/update",
f"{DOMAIN}_id": f"{input_id}",
**updated_settings,
}
)
resp = await client.receive_json()
assert resp["success"]
assert resp["result"] == {"id": "from_storage"} | updated_settings
state = hass.states.get(input_entity_id)
assert "icon" not in state.attributes
assert state.attributes["friendly_name"] == "new_name_2"
async def test_ws_create(hass, hass_ws_client, storage_setup):
"""Test create WS."""
assert await storage_setup(items=[])
input_id = "new_input"
input_entity_id = f"{DOMAIN}.{input_id}"
ent_reg = er.async_get(hass)
state = hass.states.get(input_entity_id)
assert state is None
assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, input_id) is None
client = await hass_ws_client(hass)
await client.send_json(
{
"id": 6,
"type": f"{DOMAIN}/create",
"name": "New Input",
}
)
resp = await client.receive_json()
assert resp["success"]
state = hass.states.get(input_entity_id)
assert state.state
async def test_setup_no_config(hass, hass_admin_user):
"""Test component setup with no config."""
count_start = len(hass.states.async_entity_ids())

View File

@ -305,6 +305,7 @@ async def test_ws_create_update(hass, hass_ws_client, storage_setup):
)
resp = await client.receive_json()
assert resp["success"]
assert resp["result"] == {"id": "new", "name": "newer"}
state = hass.states.get(f"{DOMAIN}.new")
assert state is not None

View File

@ -583,17 +583,23 @@ async def test_update(hass, hass_ws_client, storage_setup):
client = await hass_ws_client(hass)
updated_settings = {
CONF_NAME: "even newer name",
CONF_HAS_DATE: False,
CONF_HAS_TIME: True,
CONF_INITIAL: INITIAL_DATETIME,
}
await client.send_json(
{
"id": 6,
"type": f"{DOMAIN}/update",
f"{DOMAIN}_id": f"{input_id}",
ATTR_NAME: "even newer name",
CONF_HAS_DATE: False,
**updated_settings,
}
)
resp = await client.receive_json()
assert resp["success"]
assert resp["result"] == {"id": "from_storage"} | updated_settings
state = hass.states.get(input_entity_id)
assert state.state == INITIAL_TIME

View File

@ -416,7 +416,7 @@ async def test_load_from_storage(hass, storage_setup):
"""Test set up from storage."""
assert await storage_setup()
state = hass.states.get(f"{DOMAIN}.from_storage")
assert float(state.state) == 10
assert float(state.state) == 0 # initial is not supported when loading from storage
assert state.attributes.get(ATTR_FRIENDLY_NAME) == "from storage"
assert state.attributes.get(ATTR_EDITABLE)
@ -438,7 +438,7 @@ async def test_editable_state_attribute(hass, storage_setup):
)
state = hass.states.get(f"{DOMAIN}.from_storage")
assert float(state.state) == 10
assert float(state.state) == 0
assert state.attributes.get(ATTR_FRIENDLY_NAME) == "from storage"
assert state.attributes.get(ATTR_EDITABLE)
@ -507,16 +507,14 @@ async def test_ws_delete(hass, hass_ws_client, storage_setup):
async def test_update_min_max(hass, hass_ws_client, storage_setup):
"""Test updating min/max updates the state."""
items = [
{
"id": "from_storage",
settings = {
"name": "from storage",
"max": 100,
"min": 0,
"step": 1,
"mode": "slider",
}
]
items = [{"id": "from_storage"} | settings]
assert await storage_setup(items)
input_id = "from_storage"
@ -530,26 +528,34 @@ async def test_update_min_max(hass, hass_ws_client, storage_setup):
client = await hass_ws_client(hass)
updated_settings = settings | {"min": 9}
await client.send_json(
{"id": 6, "type": f"{DOMAIN}/update", f"{DOMAIN}_id": f"{input_id}", "min": 9}
{
"id": 6,
"type": f"{DOMAIN}/update",
f"{DOMAIN}_id": f"{input_id}",
**updated_settings,
}
)
resp = await client.receive_json()
assert resp["success"]
assert resp["result"] == {"id": "from_storage"} | updated_settings
state = hass.states.get(input_entity_id)
assert float(state.state) == 9
updated_settings = settings | {"max": 5}
await client.send_json(
{
"id": 7,
"type": f"{DOMAIN}/update",
f"{DOMAIN}_id": f"{input_id}",
"max": 5,
"min": 0,
**updated_settings,
}
)
resp = await client.receive_json()
assert resp["success"]
assert resp["result"] == {"id": "from_storage"} | updated_settings
state = hass.states.get(input_entity_id)
assert float(state.state) == 5

View File

@ -628,13 +628,11 @@ async def test_ws_delete(hass, hass_ws_client, storage_setup):
async def test_update(hass, hass_ws_client, storage_setup):
"""Test updating options updates the state."""
items = [
{
"id": "from_storage",
settings = {
"name": "from storage",
"options": ["yaml update 1", "yaml update 2"],
}
]
items = [{"id": "from_storage"} | settings]
assert await storage_setup(items)
input_id = "from_storage"
@ -647,28 +645,36 @@ async def test_update(hass, hass_ws_client, storage_setup):
client = await hass_ws_client(hass)
updated_settings = settings | {
"options": ["new option", "newer option"],
CONF_INITIAL: "newer option",
}
await client.send_json(
{
"id": 6,
"type": f"{DOMAIN}/update",
f"{DOMAIN}_id": f"{input_id}",
"options": ["new option", "newer option"],
CONF_INITIAL: "newer option",
**updated_settings,
}
)
resp = await client.receive_json()
assert resp["success"]
assert resp["result"] == {"id": "from_storage"} | updated_settings
state = hass.states.get(input_entity_id)
assert state.attributes[ATTR_OPTIONS] == ["new option", "newer option"]
# Should fail because the initial state is now invalid
updated_settings = settings | {
"options": ["new option", "no newer option"],
CONF_INITIAL: "newer option",
}
await client.send_json(
{
"id": 7,
"type": f"{DOMAIN}/update",
f"{DOMAIN}_id": f"{input_id}",
"options": ["new option", "no newer option"],
**updated_settings,
}
)
resp = await client.receive_json()
@ -678,13 +684,11 @@ async def test_update(hass, hass_ws_client, storage_setup):
async def test_update_duplicates(hass, hass_ws_client, storage_setup, caplog):
"""Test updating options updates the state."""
items = [
{
"id": "from_storage",
settings = {
"name": "from storage",
"options": ["yaml update 1", "yaml update 2"],
}
]
items = [{"id": "from_storage"} | settings]
assert await storage_setup(items)
input_id = "from_storage"
@ -697,13 +701,16 @@ async def test_update_duplicates(hass, hass_ws_client, storage_setup, caplog):
client = await hass_ws_client(hass)
updated_settings = settings | {
"options": ["new option", "newer option", "newer option"],
CONF_INITIAL: "newer option",
}
await client.send_json(
{
"id": 6,
"type": f"{DOMAIN}/update",
f"{DOMAIN}_id": f"{input_id}",
"options": ["new option", "newer option", "newer option"],
CONF_INITIAL: "newer option",
**updated_settings,
}
)
resp = await client.receive_json()

View File

@ -432,19 +432,24 @@ async def test_update(hass, hass_ws_client, storage_setup):
client = await hass_ws_client(hass)
updated_settings = {
ATTR_NAME: "even newer name",
CONF_INITIAL: "newer option",
ATTR_MAX: TEST_VAL_MAX,
ATTR_MIN: 6,
ATTR_MODE: "password",
}
await client.send_json(
{
"id": 6,
"type": f"{DOMAIN}/update",
f"{DOMAIN}_id": f"{input_id}",
ATTR_NAME: "even newer name",
CONF_INITIAL: "newer option",
ATTR_MIN: 6,
ATTR_MODE: "password",
**updated_settings,
}
)
resp = await client.receive_json()
assert resp["success"]
assert resp["result"] == {"id": "from_storage"} | updated_settings
state = hass.states.get(input_entity_id)
assert state.state == "loaded from storage"

View File

@ -168,7 +168,7 @@ async def test_config_entry_migration(hass):
)
with patch(
"homeassistant.components.ipma.weather.async_get_location",
"pyipma.location.Location.get",
return_value=MockLocation(),
):
assert await async_setup_component(hass, DOMAIN, {})

View File

@ -19,7 +19,6 @@ from homeassistant.components.weather import (
ATTR_WEATHER_TEMPERATURE,
ATTR_WEATHER_WIND_BEARING,
ATTR_WEATHER_WIND_SPEED,
DOMAIN as WEATHER_DOMAIN,
)
from homeassistant.const import STATE_UNKNOWN
@ -181,7 +180,8 @@ async def test_setup_config_flow(hass):
return_value=MockLocation(),
):
entry = MockConfigEntry(domain="ipma", data=TEST_CONFIG)
await hass.config_entries.async_forward_entry_setup(entry, WEATHER_DOMAIN)
entry.add_to_hass(hass)
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
state = hass.states.get("weather.hometown")
@ -203,7 +203,8 @@ async def test_daily_forecast(hass):
return_value=MockLocation(),
):
entry = MockConfigEntry(domain="ipma", data=TEST_CONFIG)
await hass.config_entries.async_forward_entry_setup(entry, WEATHER_DOMAIN)
entry.add_to_hass(hass)
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
state = hass.states.get("weather.hometown")
@ -227,7 +228,8 @@ async def test_hourly_forecast(hass):
return_value=MockLocation(),
):
entry = MockConfigEntry(domain="ipma", data=TEST_CONFIG_HOURLY)
await hass.config_entries.async_forward_entry_setup(entry, WEATHER_DOMAIN)
entry.add_to_hass(hass)
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
state = hass.states.get("weather.hometown")
@ -248,7 +250,8 @@ async def test_failed_get_observation_forecast(hass):
return_value=MockBadLocation(),
):
entry = MockConfigEntry(domain="ipma", data=TEST_CONFIG)
await hass.config_entries.async_forward_entry_setup(entry, WEATHER_DOMAIN)
entry.add_to_hass(hass)
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
state = hass.states.get("weather.hometown")

View File

@ -41,7 +41,7 @@ def find_log(logs, level):
if not isinstance(level, tuple):
level = (level,)
log = next(
(log for log in logs if log["level"] in level),
(log for log in logs if log["level"] in level and log["name"] != "asyncio"),
None,
)
assert log is not None

View File

@ -585,17 +585,27 @@ async def test_update(hass, hass_ws_client, storage_setup):
client = await hass_ws_client(hass)
updated_settings = {
CONF_NAME: "timer from storage",
CONF_DURATION: 33,
CONF_RESTORE: True,
}
await client.send_json(
{
"id": 6,
"type": f"{DOMAIN}/update",
f"{DOMAIN}_id": f"{timer_id}",
CONF_DURATION: 33,
CONF_RESTORE: True,
**updated_settings,
}
)
resp = await client.receive_json()
assert resp["success"]
assert resp["result"] == {
"id": "from_storage",
CONF_DURATION: "0:00:33",
CONF_NAME: "timer from storage",
CONF_RESTORE: True,
}
state = hass.states.get(timer_entity_id)
assert state.attributes[ATTR_DURATION] == _format_timedelta(cv.time_period(33))

View File

@ -45,10 +45,10 @@ async def test_smoke_sensor(hass):
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 1
smoke_sensor = hass.states.get("binary_sensor.thermometer_e39cbc_smoke")
smoke_sensor = hass.states.get("binary_sensor.thermometer_9cbc_smoke")
smoke_sensor_attribtes = smoke_sensor.attributes
assert smoke_sensor.state == "on"
assert smoke_sensor_attribtes[ATTR_FRIENDLY_NAME] == "Thermometer E39CBC Smoke"
assert smoke_sensor_attribtes[ATTR_FRIENDLY_NAME] == "Thermometer 9CBC Smoke"
assert await hass.config_entries.async_unload(entry.entry_id)
await hass.async_block_till_done()
@ -90,10 +90,10 @@ async def test_moisture(hass):
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 1
sensor = hass.states.get("binary_sensor.smart_flower_pot_6a3e7a_moisture")
sensor = hass.states.get("binary_sensor.smart_flower_pot_3e7a_moisture")
sensor_attr = sensor.attributes
assert sensor.state == "on"
assert sensor_attr[ATTR_FRIENDLY_NAME] == "Smart Flower Pot 6A3E7A Moisture"
assert sensor_attr[ATTR_FRIENDLY_NAME] == "Smart Flower Pot 3E7A Moisture"
assert await hass.config_entries.async_unload(entry.entry_id)
await hass.async_block_till_done()

View File

@ -39,7 +39,7 @@ async def test_async_step_bluetooth_valid_device(hass):
result["flow_id"], user_input={}
)
assert result2["type"] == FlowResultType.CREATE_ENTRY
assert result2["title"] == "Baby Thermometer DD6FC1 (MMC-T201-1)"
assert result2["title"] == "Baby Thermometer 6FC1 (MMC-T201-1)"
assert result2["data"] == {}
assert result2["result"].unique_id == "00:81:F9:DD:6F:C1"
@ -65,7 +65,7 @@ async def test_async_step_bluetooth_valid_device_but_missing_payload(hass):
result["flow_id"], user_input={}
)
assert result2["type"] == FlowResultType.CREATE_ENTRY
assert result2["title"] == "Temperature/Humidity Sensor 565384 (LYWSD03MMC)"
assert result2["title"] == "Temperature/Humidity Sensor 5384 (LYWSD03MMC)"
assert result2["data"] == {}
assert result2["result"].unique_id == "A4:C1:38:56:53:84"
@ -123,7 +123,7 @@ async def test_async_step_bluetooth_during_onboarding(hass):
)
assert result["type"] == FlowResultType.CREATE_ENTRY
assert result["title"] == "Baby Thermometer DD6FC1 (MMC-T201-1)"
assert result["title"] == "Baby Thermometer 6FC1 (MMC-T201-1)"
assert result["data"] == {}
assert result["result"].unique_id == "00:81:F9:DD:6F:C1"
assert len(mock_setup_entry.mock_calls) == 1
@ -148,7 +148,7 @@ async def test_async_step_bluetooth_valid_device_legacy_encryption(hass):
user_input={"bindkey": "b853075158487ca39a5b5ea9"},
)
assert result2["type"] == FlowResultType.CREATE_ENTRY
assert result2["title"] == "Dimmer Switch C5988B (YLKG07YL/YLKG08YL)"
assert result2["title"] == "Dimmer Switch 988B (YLKG07YL/YLKG08YL)"
assert result2["data"] == {"bindkey": "b853075158487ca39a5b5ea9"}
assert result2["result"].unique_id == "F8:24:41:C5:98:8B"
@ -180,7 +180,7 @@ async def test_async_step_bluetooth_valid_device_legacy_encryption_wrong_key(has
user_input={"bindkey": "b853075158487ca39a5b5ea9"},
)
assert result2["type"] == FlowResultType.CREATE_ENTRY
assert result2["title"] == "Dimmer Switch C5988B (YLKG07YL/YLKG08YL)"
assert result2["title"] == "Dimmer Switch 988B (YLKG07YL/YLKG08YL)"
assert result2["data"] == {"bindkey": "b853075158487ca39a5b5ea9"}
assert result2["result"].unique_id == "F8:24:41:C5:98:8B"
@ -214,7 +214,7 @@ async def test_async_step_bluetooth_valid_device_legacy_encryption_wrong_key_len
user_input={"bindkey": "b853075158487ca39a5b5ea9"},
)
assert result2["type"] == FlowResultType.CREATE_ENTRY
assert result2["title"] == "Dimmer Switch C5988B (YLKG07YL/YLKG08YL)"
assert result2["title"] == "Dimmer Switch 988B (YLKG07YL/YLKG08YL)"
assert result2["data"] == {"bindkey": "b853075158487ca39a5b5ea9"}
assert result2["result"].unique_id == "F8:24:41:C5:98:8B"
@ -238,7 +238,7 @@ async def test_async_step_bluetooth_valid_device_v4_encryption(hass):
)
assert result2["type"] == FlowResultType.CREATE_ENTRY
assert result2["title"] == "Thermometer E39CBC (JTYJGD03MI)"
assert result2["title"] == "Thermometer 9CBC (JTYJGD03MI)"
assert result2["data"] == {"bindkey": "5b51a7c91cde6707c9ef18dfda143a58"}
assert result2["result"].unique_id == "54:EF:44:E3:9C:BC"
@ -272,7 +272,7 @@ async def test_async_step_bluetooth_valid_device_v4_encryption_wrong_key(hass):
)
assert result2["type"] == FlowResultType.CREATE_ENTRY
assert result2["title"] == "Thermometer E39CBC (JTYJGD03MI)"
assert result2["title"] == "Thermometer 9CBC (JTYJGD03MI)"
assert result2["data"] == {"bindkey": "5b51a7c91cde6707c9ef18dfda143a58"}
assert result2["result"].unique_id == "54:EF:44:E3:9C:BC"
@ -306,7 +306,7 @@ async def test_async_step_bluetooth_valid_device_v4_encryption_wrong_key_length(
)
assert result2["type"] == FlowResultType.CREATE_ENTRY
assert result2["title"] == "Thermometer E39CBC (JTYJGD03MI)"
assert result2["title"] == "Thermometer 9CBC (JTYJGD03MI)"
assert result2["data"] == {"bindkey": "5b51a7c91cde6707c9ef18dfda143a58"}
assert result2["result"].unique_id == "54:EF:44:E3:9C:BC"
@ -370,7 +370,7 @@ async def test_async_step_user_with_found_devices(hass):
user_input={"address": "58:2D:34:35:93:21"},
)
assert result2["type"] == FlowResultType.CREATE_ENTRY
assert result2["title"] == "Temperature/Humidity Sensor 359321 (LYWSDCGQ)"
assert result2["title"] == "Temperature/Humidity Sensor 9321 (LYWSDCGQ)"
assert result2["data"] == {}
assert result2["result"].unique_id == "58:2D:34:35:93:21"
@ -405,7 +405,7 @@ async def test_async_step_user_short_payload(hass):
result["flow_id"], user_input={}
)
assert result3["type"] == FlowResultType.CREATE_ENTRY
assert result3["title"] == "Temperature/Humidity Sensor 565384 (LYWSD03MMC)"
assert result3["title"] == "Temperature/Humidity Sensor 5384 (LYWSD03MMC)"
assert result3["data"] == {}
assert result3["result"].unique_id == "A4:C1:38:56:53:84"
@ -453,7 +453,7 @@ async def test_async_step_user_short_payload_then_full(hass):
)
assert result2["type"] == FlowResultType.CREATE_ENTRY
assert result2["title"] == "Temperature/Humidity Sensor 565384 (LYWSD03MMC)"
assert result2["title"] == "Temperature/Humidity Sensor 5384 (LYWSD03MMC)"
assert result2["data"] == {"bindkey": "a115210eed7a88e50ad52662e732a9fb"}
@ -486,7 +486,7 @@ async def test_async_step_user_with_found_devices_v4_encryption(hass):
)
assert result2["type"] == FlowResultType.CREATE_ENTRY
assert result2["title"] == "Thermometer E39CBC (JTYJGD03MI)"
assert result2["title"] == "Thermometer 9CBC (JTYJGD03MI)"
assert result2["data"] == {"bindkey": "5b51a7c91cde6707c9ef18dfda143a58"}
assert result2["result"].unique_id == "54:EF:44:E3:9C:BC"
@ -532,7 +532,7 @@ async def test_async_step_user_with_found_devices_v4_encryption_wrong_key(hass):
)
assert result2["type"] == FlowResultType.CREATE_ENTRY
assert result2["title"] == "Thermometer E39CBC (JTYJGD03MI)"
assert result2["title"] == "Thermometer 9CBC (JTYJGD03MI)"
assert result2["data"] == {"bindkey": "5b51a7c91cde6707c9ef18dfda143a58"}
assert result2["result"].unique_id == "54:EF:44:E3:9C:BC"
@ -580,7 +580,7 @@ async def test_async_step_user_with_found_devices_v4_encryption_wrong_key_length
)
assert result2["type"] == FlowResultType.CREATE_ENTRY
assert result2["title"] == "Thermometer E39CBC (JTYJGD03MI)"
assert result2["title"] == "Thermometer 9CBC (JTYJGD03MI)"
assert result2["data"] == {"bindkey": "5b51a7c91cde6707c9ef18dfda143a58"}
assert result2["result"].unique_id == "54:EF:44:E3:9C:BC"
@ -613,7 +613,7 @@ async def test_async_step_user_with_found_devices_legacy_encryption(hass):
user_input={"bindkey": "b853075158487ca39a5b5ea9"},
)
assert result2["type"] == FlowResultType.CREATE_ENTRY
assert result2["title"] == "Dimmer Switch C5988B (YLKG07YL/YLKG08YL)"
assert result2["title"] == "Dimmer Switch 988B (YLKG07YL/YLKG08YL)"
assert result2["data"] == {"bindkey": "b853075158487ca39a5b5ea9"}
assert result2["result"].unique_id == "F8:24:41:C5:98:8B"
@ -658,7 +658,7 @@ async def test_async_step_user_with_found_devices_legacy_encryption_wrong_key(
user_input={"bindkey": "b853075158487ca39a5b5ea9"},
)
assert result2["type"] == FlowResultType.CREATE_ENTRY
assert result2["title"] == "Dimmer Switch C5988B (YLKG07YL/YLKG08YL)"
assert result2["title"] == "Dimmer Switch 988B (YLKG07YL/YLKG08YL)"
assert result2["data"] == {"bindkey": "b853075158487ca39a5b5ea9"}
assert result2["result"].unique_id == "F8:24:41:C5:98:8B"
@ -703,7 +703,7 @@ async def test_async_step_user_with_found_devices_legacy_encryption_wrong_key_le
user_input={"bindkey": "b853075158487ca39a5b5ea9"},
)
assert result2["type"] == FlowResultType.CREATE_ENTRY
assert result2["title"] == "Dimmer Switch C5988B (YLKG07YL/YLKG08YL)"
assert result2["title"] == "Dimmer Switch 988B (YLKG07YL/YLKG08YL)"
assert result2["data"] == {"bindkey": "b853075158487ca39a5b5ea9"}
assert result2["result"].unique_id == "F8:24:41:C5:98:8B"
@ -822,7 +822,7 @@ async def test_async_step_user_takes_precedence_over_discovery(hass):
user_input={"address": "00:81:F9:DD:6F:C1"},
)
assert result2["type"] == FlowResultType.CREATE_ENTRY
assert result2["title"] == "Baby Thermometer DD6FC1 (MMC-T201-1)"
assert result2["title"] == "Baby Thermometer 6FC1 (MMC-T201-1)"
assert result2["data"] == {}
assert result2["result"].unique_id == "00:81:F9:DD:6F:C1"

View File

@ -42,12 +42,11 @@ async def test_sensors(hass):
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 2
temp_sensor = hass.states.get("sensor.baby_thermometer_dd6fc1_temperature")
temp_sensor = hass.states.get("sensor.baby_thermometer_6fc1_temperature")
temp_sensor_attribtes = temp_sensor.attributes
assert temp_sensor.state == "36.8719980616822"
assert (
temp_sensor_attribtes[ATTR_FRIENDLY_NAME]
== "Baby Thermometer DD6FC1 Temperature"
temp_sensor_attribtes[ATTR_FRIENDLY_NAME] == "Baby Thermometer 6FC1 Temperature"
)
assert temp_sensor_attribtes[ATTR_UNIT_OF_MEASUREMENT] == "°C"
assert temp_sensor_attribtes[ATTR_STATE_CLASS] == "measurement"
@ -92,10 +91,10 @@ async def test_xiaomi_formaldeyhde(hass):
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 1
sensor = hass.states.get("sensor.smart_flower_pot_6a3e7a_formaldehyde")
sensor = hass.states.get("sensor.smart_flower_pot_3e7a_formaldehyde")
sensor_attr = sensor.attributes
assert sensor.state == "2.44"
assert sensor_attr[ATTR_FRIENDLY_NAME] == "Smart Flower Pot 6A3E7A Formaldehyde"
assert sensor_attr[ATTR_FRIENDLY_NAME] == "Smart Flower Pot 3E7A Formaldehyde"
assert sensor_attr[ATTR_UNIT_OF_MEASUREMENT] == "mg/m³"
assert sensor_attr[ATTR_STATE_CLASS] == "measurement"
@ -139,10 +138,10 @@ async def test_xiaomi_consumable(hass):
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 1
sensor = hass.states.get("sensor.smart_flower_pot_6a3e7a_consumable")
sensor = hass.states.get("sensor.smart_flower_pot_3e7a_consumable")
sensor_attr = sensor.attributes
assert sensor.state == "96"
assert sensor_attr[ATTR_FRIENDLY_NAME] == "Smart Flower Pot 6A3E7A Consumable"
assert sensor_attr[ATTR_FRIENDLY_NAME] == "Smart Flower Pot 3E7A Consumable"
assert sensor_attr[ATTR_UNIT_OF_MEASUREMENT] == "%"
assert sensor_attr[ATTR_STATE_CLASS] == "measurement"
@ -186,17 +185,17 @@ async def test_xiaomi_battery_voltage(hass):
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 2
volt_sensor = hass.states.get("sensor.smart_flower_pot_6a3e7a_voltage")
volt_sensor = hass.states.get("sensor.smart_flower_pot_3e7a_voltage")
volt_sensor_attr = volt_sensor.attributes
assert volt_sensor.state == "3.1"
assert volt_sensor_attr[ATTR_FRIENDLY_NAME] == "Smart Flower Pot 6A3E7A Voltage"
assert volt_sensor_attr[ATTR_FRIENDLY_NAME] == "Smart Flower Pot 3E7A Voltage"
assert volt_sensor_attr[ATTR_UNIT_OF_MEASUREMENT] == "V"
assert volt_sensor_attr[ATTR_STATE_CLASS] == "measurement"
bat_sensor = hass.states.get("sensor.smart_flower_pot_6a3e7a_battery")
bat_sensor = hass.states.get("sensor.smart_flower_pot_3e7a_battery")
bat_sensor_attr = bat_sensor.attributes
assert bat_sensor.state == "100"
assert bat_sensor_attr[ATTR_FRIENDLY_NAME] == "Smart Flower Pot 6A3E7A Battery"
assert bat_sensor_attr[ATTR_FRIENDLY_NAME] == "Smart Flower Pot 3E7A Battery"
assert bat_sensor_attr[ATTR_UNIT_OF_MEASUREMENT] == "%"
assert bat_sensor_attr[ATTR_STATE_CLASS] == "measurement"
@ -254,42 +253,38 @@ async def test_xiaomi_HHCCJCY01(hass):
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 5
illum_sensor = hass.states.get("sensor.plant_sensor_6a3e7a_illuminance")
illum_sensor = hass.states.get("sensor.plant_sensor_3e7a_illuminance")
illum_sensor_attr = illum_sensor.attributes
assert illum_sensor.state == "0"
assert illum_sensor_attr[ATTR_FRIENDLY_NAME] == "Plant Sensor 6A3E7A Illuminance"
assert illum_sensor_attr[ATTR_FRIENDLY_NAME] == "Plant Sensor 3E7A Illuminance"
assert illum_sensor_attr[ATTR_UNIT_OF_MEASUREMENT] == "lx"
assert illum_sensor_attr[ATTR_STATE_CLASS] == "measurement"
cond_sensor = hass.states.get("sensor.plant_sensor_6a3e7a_conductivity")
cond_sensor = hass.states.get("sensor.plant_sensor_3e7a_conductivity")
cond_sensor_attribtes = cond_sensor.attributes
assert cond_sensor.state == "599"
assert (
cond_sensor_attribtes[ATTR_FRIENDLY_NAME] == "Plant Sensor 6A3E7A Conductivity"
)
assert cond_sensor_attribtes[ATTR_FRIENDLY_NAME] == "Plant Sensor 3E7A Conductivity"
assert cond_sensor_attribtes[ATTR_UNIT_OF_MEASUREMENT] == "µS/cm"
assert cond_sensor_attribtes[ATTR_STATE_CLASS] == "measurement"
moist_sensor = hass.states.get("sensor.plant_sensor_6a3e7a_moisture")
moist_sensor = hass.states.get("sensor.plant_sensor_3e7a_moisture")
moist_sensor_attribtes = moist_sensor.attributes
assert moist_sensor.state == "64"
assert moist_sensor_attribtes[ATTR_FRIENDLY_NAME] == "Plant Sensor 6A3E7A Moisture"
assert moist_sensor_attribtes[ATTR_FRIENDLY_NAME] == "Plant Sensor 3E7A Moisture"
assert moist_sensor_attribtes[ATTR_UNIT_OF_MEASUREMENT] == "%"
assert moist_sensor_attribtes[ATTR_STATE_CLASS] == "measurement"
temp_sensor = hass.states.get("sensor.plant_sensor_6a3e7a_temperature")
temp_sensor = hass.states.get("sensor.plant_sensor_3e7a_temperature")
temp_sensor_attribtes = temp_sensor.attributes
assert temp_sensor.state == "24.4"
assert (
temp_sensor_attribtes[ATTR_FRIENDLY_NAME] == "Plant Sensor 6A3E7A Temperature"
)
assert temp_sensor_attribtes[ATTR_FRIENDLY_NAME] == "Plant Sensor 3E7A Temperature"
assert temp_sensor_attribtes[ATTR_UNIT_OF_MEASUREMENT] == "°C"
assert temp_sensor_attribtes[ATTR_STATE_CLASS] == "measurement"
batt_sensor = hass.states.get("sensor.plant_sensor_6a3e7a_battery")
batt_sensor = hass.states.get("sensor.plant_sensor_3e7a_battery")
batt_sensor_attribtes = batt_sensor.attributes
assert batt_sensor.state == "5"
assert batt_sensor_attribtes[ATTR_FRIENDLY_NAME] == "Plant Sensor 6A3E7A Battery"
assert batt_sensor_attribtes[ATTR_FRIENDLY_NAME] == "Plant Sensor 3E7A Battery"
assert batt_sensor_attribtes[ATTR_UNIT_OF_MEASUREMENT] == "%"
assert batt_sensor_attribtes[ATTR_STATE_CLASS] == "measurement"
@ -355,35 +350,31 @@ async def test_xiaomi_HHCCJCY01_not_connectable(hass):
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 4
illum_sensor = hass.states.get("sensor.plant_sensor_6a3e7a_illuminance")
illum_sensor = hass.states.get("sensor.plant_sensor_3e7a_illuminance")
illum_sensor_attr = illum_sensor.attributes
assert illum_sensor.state == "0"
assert illum_sensor_attr[ATTR_FRIENDLY_NAME] == "Plant Sensor 6A3E7A Illuminance"
assert illum_sensor_attr[ATTR_FRIENDLY_NAME] == "Plant Sensor 3E7A Illuminance"
assert illum_sensor_attr[ATTR_UNIT_OF_MEASUREMENT] == "lx"
assert illum_sensor_attr[ATTR_STATE_CLASS] == "measurement"
cond_sensor = hass.states.get("sensor.plant_sensor_6a3e7a_conductivity")
cond_sensor = hass.states.get("sensor.plant_sensor_3e7a_conductivity")
cond_sensor_attribtes = cond_sensor.attributes
assert cond_sensor.state == "599"
assert (
cond_sensor_attribtes[ATTR_FRIENDLY_NAME] == "Plant Sensor 6A3E7A Conductivity"
)
assert cond_sensor_attribtes[ATTR_FRIENDLY_NAME] == "Plant Sensor 3E7A Conductivity"
assert cond_sensor_attribtes[ATTR_UNIT_OF_MEASUREMENT] == "µS/cm"
assert cond_sensor_attribtes[ATTR_STATE_CLASS] == "measurement"
moist_sensor = hass.states.get("sensor.plant_sensor_6a3e7a_moisture")
moist_sensor = hass.states.get("sensor.plant_sensor_3e7a_moisture")
moist_sensor_attribtes = moist_sensor.attributes
assert moist_sensor.state == "64"
assert moist_sensor_attribtes[ATTR_FRIENDLY_NAME] == "Plant Sensor 6A3E7A Moisture"
assert moist_sensor_attribtes[ATTR_FRIENDLY_NAME] == "Plant Sensor 3E7A Moisture"
assert moist_sensor_attribtes[ATTR_UNIT_OF_MEASUREMENT] == "%"
assert moist_sensor_attribtes[ATTR_STATE_CLASS] == "measurement"
temp_sensor = hass.states.get("sensor.plant_sensor_6a3e7a_temperature")
temp_sensor = hass.states.get("sensor.plant_sensor_3e7a_temperature")
temp_sensor_attribtes = temp_sensor.attributes
assert temp_sensor.state == "24.4"
assert (
temp_sensor_attribtes[ATTR_FRIENDLY_NAME] == "Plant Sensor 6A3E7A Temperature"
)
assert temp_sensor_attribtes[ATTR_FRIENDLY_NAME] == "Plant Sensor 3E7A Temperature"
assert temp_sensor_attribtes[ATTR_UNIT_OF_MEASUREMENT] == "°C"
assert temp_sensor_attribtes[ATTR_STATE_CLASS] == "measurement"
@ -438,42 +429,38 @@ async def test_xiaomi_HHCCJCY01_only_some_sources_connectable(hass):
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 5
illum_sensor = hass.states.get("sensor.plant_sensor_6a3e7a_illuminance")
illum_sensor = hass.states.get("sensor.plant_sensor_3e7a_illuminance")
illum_sensor_attr = illum_sensor.attributes
assert illum_sensor.state == "0"
assert illum_sensor_attr[ATTR_FRIENDLY_NAME] == "Plant Sensor 6A3E7A Illuminance"
assert illum_sensor_attr[ATTR_FRIENDLY_NAME] == "Plant Sensor 3E7A Illuminance"
assert illum_sensor_attr[ATTR_UNIT_OF_MEASUREMENT] == "lx"
assert illum_sensor_attr[ATTR_STATE_CLASS] == "measurement"
cond_sensor = hass.states.get("sensor.plant_sensor_6a3e7a_conductivity")
cond_sensor = hass.states.get("sensor.plant_sensor_3e7a_conductivity")
cond_sensor_attribtes = cond_sensor.attributes
assert cond_sensor.state == "599"
assert (
cond_sensor_attribtes[ATTR_FRIENDLY_NAME] == "Plant Sensor 6A3E7A Conductivity"
)
assert cond_sensor_attribtes[ATTR_FRIENDLY_NAME] == "Plant Sensor 3E7A Conductivity"
assert cond_sensor_attribtes[ATTR_UNIT_OF_MEASUREMENT] == "µS/cm"
assert cond_sensor_attribtes[ATTR_STATE_CLASS] == "measurement"
moist_sensor = hass.states.get("sensor.plant_sensor_6a3e7a_moisture")
moist_sensor = hass.states.get("sensor.plant_sensor_3e7a_moisture")
moist_sensor_attribtes = moist_sensor.attributes
assert moist_sensor.state == "64"
assert moist_sensor_attribtes[ATTR_FRIENDLY_NAME] == "Plant Sensor 6A3E7A Moisture"
assert moist_sensor_attribtes[ATTR_FRIENDLY_NAME] == "Plant Sensor 3E7A Moisture"
assert moist_sensor_attribtes[ATTR_UNIT_OF_MEASUREMENT] == "%"
assert moist_sensor_attribtes[ATTR_STATE_CLASS] == "measurement"
temp_sensor = hass.states.get("sensor.plant_sensor_6a3e7a_temperature")
temp_sensor = hass.states.get("sensor.plant_sensor_3e7a_temperature")
temp_sensor_attribtes = temp_sensor.attributes
assert temp_sensor.state == "24.4"
assert (
temp_sensor_attribtes[ATTR_FRIENDLY_NAME] == "Plant Sensor 6A3E7A Temperature"
)
assert temp_sensor_attribtes[ATTR_FRIENDLY_NAME] == "Plant Sensor 3E7A Temperature"
assert temp_sensor_attribtes[ATTR_UNIT_OF_MEASUREMENT] == "°C"
assert temp_sensor_attribtes[ATTR_STATE_CLASS] == "measurement"
batt_sensor = hass.states.get("sensor.plant_sensor_6a3e7a_battery")
batt_sensor = hass.states.get("sensor.plant_sensor_3e7a_battery")
batt_sensor_attribtes = batt_sensor.attributes
assert batt_sensor.state == "5"
assert batt_sensor_attribtes[ATTR_FRIENDLY_NAME] == "Plant Sensor 6A3E7A Battery"
assert batt_sensor_attribtes[ATTR_FRIENDLY_NAME] == "Plant Sensor 3E7A Battery"
assert batt_sensor_attribtes[ATTR_UNIT_OF_MEASUREMENT] == "%"
assert batt_sensor_attribtes[ATTR_STATE_CLASS] == "measurement"
@ -515,14 +502,12 @@ async def test_xiaomi_CGDK2(hass):
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 1
temp_sensor = hass.states.get(
"sensor.temperature_humidity_sensor_122089_temperature"
)
temp_sensor = hass.states.get("sensor.temperature_humidity_sensor_2089_temperature")
temp_sensor_attribtes = temp_sensor.attributes
assert temp_sensor.state == "22.6"
assert (
temp_sensor_attribtes[ATTR_FRIENDLY_NAME]
== "Temperature/Humidity Sensor 122089 Temperature"
== "Temperature/Humidity Sensor 2089 Temperature"
)
assert temp_sensor_attribtes[ATTR_UNIT_OF_MEASUREMENT] == "°C"
assert temp_sensor_attribtes[ATTR_STATE_CLASS] == "measurement"

View File

@ -178,7 +178,7 @@ async def device_light_2(hass, zigpy_device_mock, zha_device_joined):
}
},
ieee=IEEE_GROUPABLE_DEVICE2,
manufacturer="Sengled",
manufacturer="sengled",
nwk=0xC79E,
)
color_cluster = zigpy_device.endpoints[1].light_color

View File

@ -5493,7 +5493,7 @@ DEVICES = [
DEV_SIG_EVT_CHANNELS: ["1:0x0019"],
DEV_SIG_ENTITIES: [
"button.sengled_e11_g13_identifybutton",
"light.sengled_e11_g13_light",
"light.sengled_e11_g13_mintransitionlight",
"sensor.sengled_e11_g13_smartenergymetering",
"sensor.sengled_e11_g13_smartenergysummation",
"sensor.sengled_e11_g13_rssi",
@ -5502,8 +5502,8 @@ DEVICES = [
DEV_SIG_ENT_MAP: {
("light", "00:11:22:33:44:55:66:77-1"): {
DEV_SIG_CHANNELS: ["on_off", "level"],
DEV_SIG_ENT_MAP_CLASS: "Light",
DEV_SIG_ENT_MAP_ID: "light.sengled_e11_g13_light",
DEV_SIG_ENT_MAP_CLASS: "MinTransitionLight",
DEV_SIG_ENT_MAP_ID: "light.sengled_e11_g13_mintransitionlight",
},
("button", "00:11:22:33:44:55:66:77-1-3"): {
DEV_SIG_CHANNELS: ["identify"],
@ -5549,7 +5549,7 @@ DEVICES = [
DEV_SIG_EVT_CHANNELS: ["1:0x0019"],
DEV_SIG_ENTITIES: [
"button.sengled_e12_n14_identifybutton",
"light.sengled_e12_n14_light",
"light.sengled_e12_n14_mintransitionlight",
"sensor.sengled_e12_n14_smartenergymetering",
"sensor.sengled_e12_n14_smartenergysummation",
"sensor.sengled_e12_n14_rssi",
@ -5558,8 +5558,8 @@ DEVICES = [
DEV_SIG_ENT_MAP: {
("light", "00:11:22:33:44:55:66:77-1"): {
DEV_SIG_CHANNELS: ["on_off", "level"],
DEV_SIG_ENT_MAP_CLASS: "Light",
DEV_SIG_ENT_MAP_ID: "light.sengled_e12_n14_light",
DEV_SIG_ENT_MAP_CLASS: "MinTransitionLight",
DEV_SIG_ENT_MAP_ID: "light.sengled_e12_n14_mintransitionlight",
},
("button", "00:11:22:33:44:55:66:77-1-3"): {
DEV_SIG_CHANNELS: ["identify"],
@ -5605,7 +5605,7 @@ DEVICES = [
DEV_SIG_EVT_CHANNELS: ["1:0x0019"],
DEV_SIG_ENTITIES: [
"button.sengled_z01_a19nae26_identifybutton",
"light.sengled_z01_a19nae26_light",
"light.sengled_z01_a19nae26_mintransitionlight",
"sensor.sengled_z01_a19nae26_smartenergymetering",
"sensor.sengled_z01_a19nae26_smartenergysummation",
"sensor.sengled_z01_a19nae26_rssi",
@ -5614,8 +5614,8 @@ DEVICES = [
DEV_SIG_ENT_MAP: {
("light", "00:11:22:33:44:55:66:77-1"): {
DEV_SIG_CHANNELS: ["on_off", "level", "light_color"],
DEV_SIG_ENT_MAP_CLASS: "Light",
DEV_SIG_ENT_MAP_ID: "light.sengled_z01_a19nae26_light",
DEV_SIG_ENT_MAP_CLASS: "MinTransitionLight",
DEV_SIG_ENT_MAP_ID: "light.sengled_z01_a19nae26_mintransitionlight",
},
("button", "00:11:22:33:44:55:66:77-1-3"): {
DEV_SIG_CHANNELS: ["identify"],