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

View File

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

View File

@ -5,9 +5,11 @@
"dependencies": ["usb"], "dependencies": ["usb"],
"quality_scale": "internal", "quality_scale": "internal",
"requirements": [ "requirements": [
"bleak==0.16.0", "bleak==0.17.0",
"bleak-retry-connector==1.15.1",
"bluetooth-adapters==0.4.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"], "codeowners": ["@bdraco"],
"config_flow": true, "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.bluezdbus.scanner import BlueZScannerArgs
from bleak.backends.device import BLEDevice from bleak.backends.device import BLEDevice
from bleak.backends.scanner import AdvertisementData 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.const import EVENT_HOMEASSISTANT_STOP
from homeassistant.core import ( from homeassistant.core import (

View File

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

View File

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

View File

@ -2,7 +2,7 @@
"domain": "dhcp", "domain": "dhcp",
"name": "DHCP Discovery", "name": "DHCP Discovery",
"documentation": "https://www.home-assistant.io/integrations/dhcp", "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"], "codeowners": ["@bdraco"],
"quality_scale": "internal", "quality_scale": "internal",
"iot_class": "local_push", "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: async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry.""" """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): if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
hass.data[DOMAIN].pop(entry.entry_id) hass.data[DOMAIN].pop(entry.entry_id)

View File

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

View File

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

View File

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

View File

@ -3,7 +3,7 @@
"name": "HomeKit Controller", "name": "HomeKit Controller",
"config_flow": true, "config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/homekit_controller", "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."], "zeroconf": ["_hap._tcp.local.", "_hap._udp.local."],
"bluetooth": [{ "manufacturer_id": 76, "manufacturer_data_start": [6] }], "bluetooth": [{ "manufacturer_id": 76, "manufacturer_data_start": [6] }],
"dependencies": ["bluetooth", "zeroconf"], "dependencies": ["bluetooth", "zeroconf"],

View File

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

View File

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

View File

@ -65,7 +65,7 @@ def _cv_input_number(cfg):
return cfg return cfg
CREATE_FIELDS = { STORAGE_FIELDS = {
vol.Required(CONF_NAME): vol.All(str, vol.Length(min=1)), vol.Required(CONF_NAME): vol.All(str, vol.Length(min=1)),
vol.Required(CONF_MIN): vol.Coerce(float), vol.Required(CONF_MIN): vol.Coerce(float),
vol.Required(CONF_MAX): 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]), 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( CONFIG_SCHEMA = vol.Schema(
{ {
DOMAIN: cv.schema_with_slug_keys( DOMAIN: cv.schema_with_slug_keys(
@ -148,7 +137,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
await storage_collection.async_load() await storage_collection.async_load()
collection.StorageCollectionWebsocket( collection.StorageCollectionWebsocket(
storage_collection, DOMAIN, DOMAIN, CREATE_FIELDS, UPDATE_FIELDS storage_collection, DOMAIN, DOMAIN, STORAGE_FIELDS, STORAGE_FIELDS
).async_setup(hass) ).async_setup(hass)
async def reload_service_handler(service_call: ServiceCall) -> None: 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): class NumberStorageCollection(collection.StorageCollection):
"""Input storage based collection.""" """Input storage based collection."""
CREATE_SCHEMA = vol.Schema(vol.All(CREATE_FIELDS, _cv_input_number)) SCHEMA = vol.Schema(vol.All(STORAGE_FIELDS, _cv_input_number))
UPDATE_SCHEMA = vol.Schema(UPDATE_FIELDS)
async def _process_create_data(self, data: dict) -> dict: async def _process_create_data(self, data: dict) -> dict:
"""Validate the config is valid.""" """Validate the config is valid."""
return self.CREATE_SCHEMA(data) return self.SCHEMA(data)
@callback @callback
def _get_suggested_id(self, info: dict) -> str: def _get_suggested_id(self, info: dict) -> str:
"""Suggest an ID based on the config.""" """Suggest an ID based on the config."""
return info[CONF_NAME] 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: async def _update_data(self, data: dict, update_data: dict) -> dict:
"""Return a new updated data object.""" """Return a new updated data object."""
update_data = self.UPDATE_SCHEMA(update_data) update_data = self.SCHEMA(update_data)
return _cv_input_number({**data, **update_data}) return {CONF_ID: data[CONF_ID]} | update_data
class InputNumber(RestoreEntity): class InputNumber(RestoreEntity):

View File

@ -56,7 +56,7 @@ def _unique(options: Any) -> Any:
raise HomeAssistantError("Duplicate options are not allowed") from exc 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_NAME): vol.All(str, vol.Length(min=1)),
vol.Required(CONF_OPTIONS): vol.All( vol.Required(CONF_OPTIONS): vol.All(
cv.ensure_list, vol.Length(min=1), _unique, [cv.string] 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_INITIAL): cv.string,
vol.Optional(CONF_ICON): cv.icon, 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]: 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() await storage_collection.async_load()
collection.StorageCollectionWebsocket( collection.StorageCollectionWebsocket(
storage_collection, DOMAIN, DOMAIN, CREATE_FIELDS, UPDATE_FIELDS storage_collection, DOMAIN, DOMAIN, STORAGE_FIELDS, STORAGE_FIELDS
).async_setup(hass) ).async_setup(hass)
async def reload_service_handler(service_call: ServiceCall) -> None: 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): class InputSelectStorageCollection(collection.StorageCollection):
"""Input storage based collection.""" """Input storage based collection."""
CREATE_SCHEMA = vol.Schema(vol.All(CREATE_FIELDS, _cv_input_select)) CREATE_UPDATE_SCHEMA = vol.Schema(vol.All(STORAGE_FIELDS, _cv_input_select))
UPDATE_SCHEMA = vol.Schema(UPDATE_FIELDS)
async def _process_create_data(self, data: dict[str, Any]) -> dict[str, Any]: async def _process_create_data(self, data: dict[str, Any]) -> dict[str, Any]:
"""Validate the config is valid.""" """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 @callback
def _get_suggested_id(self, info: dict[str, Any]) -> str: 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] self, data: dict[str, Any], update_data: dict[str, Any]
) -> dict[str, Any]: ) -> dict[str, Any]:
"""Return a new updated data object.""" """Return a new updated data object."""
update_data = self.UPDATE_SCHEMA(update_data) update_data = self.CREATE_UPDATE_SCHEMA(update_data)
return _cv_input_select({**data, **update_data}) return {CONF_ID: data[CONF_ID]} | update_data
class InputSelect(SelectEntity, RestoreEntity): class InputSelect(SelectEntity, RestoreEntity):

View File

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

View File

@ -1,19 +1,61 @@
"""Component for the Portuguese weather service - IPMA.""" """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.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.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 .config_flow import IpmaFlowHandler # noqa: F401
from .const import DOMAIN # noqa: F401 from .const import DATA_API, DATA_LOCATION, DOMAIN
DEFAULT_NAME = "ipma" DEFAULT_NAME = "ipma"
PLATFORMS = [Platform.WEATHER] 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.""" """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 return True

View File

@ -6,3 +6,6 @@ DOMAIN = "ipma"
HOME_LOCATION_NAME = "Home" HOME_LOCATION_NAME = "Home"
ENTITY_ID_SENSOR_FORMAT_HOME = f"{WEATHER_DOMAIN}.ipma_{HOME_LOCATION_NAME}" 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)", "name": "Instituto Portugu\u00eas do Mar e Atmosfera (IPMA)",
"config_flow": true, "config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/ipma", "documentation": "https://www.home-assistant.io/integrations/ipma",
"requirements": ["pyipma==3.0.2"], "requirements": ["pyipma==3.0.4"],
"codeowners": ["@dgomes", "@abmantis"], "codeowners": ["@dgomes", "@abmantis"],
"iot_class": "cloud_polling", "iot_class": "cloud_polling",
"loggers": ["geopy", "pyipma"] "loggers": ["geopy", "pyipma"]

View File

@ -48,11 +48,12 @@ from homeassistant.const import (
) )
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import config_validation as cv, entity_registry 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.entity_platform import AddEntitiesCallback
from homeassistant.helpers.sun import is_up from homeassistant.helpers.sun import is_up
from homeassistant.util import Throttle from homeassistant.util import Throttle
from .const import DATA_API, DATA_LOCATION, DOMAIN
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
ATTRIBUTION = "Instituto Português do Mar e Atmosfera" ATTRIBUTION = "Instituto Português do Mar e Atmosfera"
@ -95,13 +96,10 @@ async def async_setup_entry(
async_add_entities: AddEntitiesCallback, async_add_entities: AddEntitiesCallback,
) -> None: ) -> None:
"""Add a weather entity from a config_entry.""" """Add a weather entity from a config_entry."""
latitude = config_entry.data[CONF_LATITUDE] api = hass.data[DOMAIN][config_entry.entry_id][DATA_API]
longitude = config_entry.data[CONF_LONGITUDE] location = hass.data[DOMAIN][config_entry.entry_id][DATA_LOCATION]
mode = config_entry.data[CONF_MODE] 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 # Migrate old unique_id
@callback @callback
def _async_migrator(entity_entry: entity_registry.RegistryEntry): 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_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): class IPMAWeather(WeatherEntity):
"""Representation of a weather condition.""" """Representation of a weather condition."""

View File

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

View File

@ -99,4 +99,11 @@ class RainMachineUpdateEntity(RainMachineEntity, UpdateEntity):
UpdateStates.UPGRADING, UpdateStates.UPGRADING,
UpdateStates.REBOOT, 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", "domain": "switchbot",
"name": "SwitchBot", "name": "SwitchBot",
"documentation": "https://www.home-assistant.io/integrations/switchbot", "documentation": "https://www.home-assistant.io/integrations/switchbot",
"requirements": ["PySwitchbot==0.19.5"], "requirements": ["PySwitchbot==0.19.8"],
"config_flow": true, "config_flow": true,
"dependencies": ["bluetooth"], "dependencies": ["bluetooth"],
"codeowners": [ "codeowners": [

View File

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

View File

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

View File

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

View File

@ -3,10 +3,15 @@
"name": "Yale Access Bluetooth", "name": "Yale Access Bluetooth",
"config_flow": true, "config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/yalexs_ble", "documentation": "https://www.home-assistant.io/integrations/yalexs_ble",
"requirements": ["yalexs-ble==1.8.1"], "requirements": ["yalexs-ble==1.9.0"],
"dependencies": ["bluetooth"], "dependencies": ["bluetooth"],
"codeowners": ["@bdraco"], "codeowners": ["@bdraco"],
"bluetooth": [{ "manufacturer_id": 465 }], "bluetooth": [
{
"manufacturer_id": 465,
"service_uuid": "0000fe24-0000-1000-8000-00805f9b34fb"
}
],
"iot_class": "local_push", "iot_class": "local_push",
"supported_brands": { "supported_brands": {
"august_ble": "August Bluetooth" "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_STATE_CHANGED = "zha_light_group_state_changed"
SIGNAL_LIGHT_GROUP_TRANSITION_START = "zha_light_group_transition_start" SIGNAL_LIGHT_GROUP_TRANSITION_START = "zha_light_group_transition_start"
SIGNAL_LIGHT_GROUP_TRANSITION_FINISHED = "zha_light_group_transition_finished" 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} COLOR_MODES_GROUP_LIGHT = {ColorMode.COLOR_TEMP, ColorMode.XY}
SUPPORT_GROUP_LIGHT = ( SUPPORT_GROUP_LIGHT = (

View File

@ -7,7 +7,7 @@ from .backports.enum import StrEnum
MAJOR_VERSION: Final = 2022 MAJOR_VERSION: Final = 2022
MINOR_VERSION: Final = 9 MINOR_VERSION: Final = 9
PATCH_VERSION: Final = "2" PATCH_VERSION: Final = "3"
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
__version__: Final = f"{__short_version__}.{PATCH_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}"
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 9, 0) 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", "service_uuid": "00008351-0000-1000-8000-00805f9b34fb",
"connectable": False "connectable": False
}, },
{
"domain": "govee_ble",
"manufacturer_id": 57391,
"service_uuid": "00008351-0000-1000-8000-00805f9b34fb",
"connectable": False
},
{ {
"domain": "govee_ble", "domain": "govee_ble",
"manufacturer_id": 18994, "manufacturer_id": 18994,
@ -281,6 +287,7 @@ BLUETOOTH: list[dict[str, bool | str | int | list[int]]] = [
}, },
{ {
"domain": "yalexs_ble", "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 PyJWT==2.4.0
PyNaCl==1.5.0 PyNaCl==1.5.0
aiodiscover==1.4.11 aiodiscover==1.4.13
aiohttp==3.8.1 aiohttp==3.8.1
aiohttp_cors==0.7.0 aiohttp_cors==0.7.0
astral==2.2 astral==2.2
@ -10,16 +10,18 @@ atomicwrites-homeassistant==1.4.1
attrs==21.2.0 attrs==21.2.0
awesomeversion==22.8.0 awesomeversion==22.8.0
bcrypt==3.1.7 bcrypt==3.1.7
bleak==0.16.0 bleak-retry-connector==1.15.1
bleak==0.17.0
bluetooth-adapters==0.4.1 bluetooth-adapters==0.4.1
bluetooth-auto-recovery==0.3.2 bluetooth-auto-recovery==0.3.3
certifi>=2021.5.30 certifi>=2021.5.30
ciso8601==2.2.0 ciso8601==2.2.0
cryptography==37.0.4 cryptography==37.0.4
dbus-fast==1.4.0
fnvhash==0.1.0 fnvhash==0.1.0
hass-nabucasa==0.55.0 hass-nabucasa==0.55.0
home-assistant-bluetooth==1.3.0 home-assistant-bluetooth==1.3.0
home-assistant-frontend==20220907.0 home-assistant-frontend==20220907.1
httpx==0.23.0 httpx==0.23.0
ifaddr==0.1.7 ifaddr==0.1.7
jinja2==3.1.2 jinja2==3.1.2

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -20,6 +20,7 @@ from homeassistant.const import (
ENERGY_MEGA_WATT_HOUR, ENERGY_MEGA_WATT_HOUR,
ENERGY_WATT_HOUR, ENERGY_WATT_HOUR,
STATE_UNKNOWN, STATE_UNKNOWN,
VOLUME_CUBIC_FEET,
VOLUME_CUBIC_METERS, VOLUME_CUBIC_METERS,
) )
from homeassistant.helpers import entity_registry as er 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" 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.""" """Test gas cost price from sensor entity."""
energy_attributes = { energy_attributes = {
ATTR_UNIT_OF_MEASUREMENT: VOLUME_CUBIC_METERS, ATTR_UNIT_OF_MEASUREMENT: unit,
ATTR_STATE_CLASS: SensorStateClass.TOTAL_INCREASING, ATTR_STATE_CLASS: SensorStateClass.TOTAL_INCREASING,
} }
energy_data = data.EnergyManager.default_preferences() 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"}]}, "data": {"items": [{"id": "from_storage", "name": "from storage"}]},
} }
else: else:
hass_storage[DOMAIN] = items hass_storage[DOMAIN] = {
"key": DOMAIN,
"version": 1,
"data": {"items": items},
}
if config is None: if config is None:
config = {DOMAIN: {}} config = {DOMAIN: {}}
return await async_setup_component(hass, DOMAIN, config) 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 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): async def test_setup_no_config(hass, hass_admin_user):
"""Test component setup with no config.""" """Test component setup with no config."""
count_start = len(hass.states.async_entity_ids()) 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() resp = await client.receive_json()
assert resp["success"] assert resp["success"]
assert resp["result"] == {"id": "new", "name": "newer"}
state = hass.states.get(f"{DOMAIN}.new") state = hass.states.get(f"{DOMAIN}.new")
assert state is not None 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) 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( await client.send_json(
{ {
"id": 6, "id": 6,
"type": f"{DOMAIN}/update", "type": f"{DOMAIN}/update",
f"{DOMAIN}_id": f"{input_id}", f"{DOMAIN}_id": f"{input_id}",
ATTR_NAME: "even newer name", **updated_settings,
CONF_HAS_DATE: False,
} }
) )
resp = await client.receive_json() resp = await client.receive_json()
assert resp["success"] assert resp["success"]
assert resp["result"] == {"id": "from_storage"} | updated_settings
state = hass.states.get(input_entity_id) state = hass.states.get(input_entity_id)
assert state.state == INITIAL_TIME 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.""" """Test set up from storage."""
assert await storage_setup() assert await storage_setup()
state = hass.states.get(f"{DOMAIN}.from_storage") 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_FRIENDLY_NAME) == "from storage"
assert state.attributes.get(ATTR_EDITABLE) 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") 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_FRIENDLY_NAME) == "from storage"
assert state.attributes.get(ATTR_EDITABLE) 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): async def test_update_min_max(hass, hass_ws_client, storage_setup):
"""Test updating min/max updates the state.""" """Test updating min/max updates the state."""
items = [ settings = {
{ "name": "from storage",
"id": "from_storage", "max": 100,
"name": "from storage", "min": 0,
"max": 100, "step": 1,
"min": 0, "mode": "slider",
"step": 1, }
"mode": "slider", items = [{"id": "from_storage"} | settings]
}
]
assert await storage_setup(items) assert await storage_setup(items)
input_id = "from_storage" 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) client = await hass_ws_client(hass)
updated_settings = settings | {"min": 9}
await client.send_json( 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() resp = await client.receive_json()
assert resp["success"] assert resp["success"]
assert resp["result"] == {"id": "from_storage"} | updated_settings
state = hass.states.get(input_entity_id) state = hass.states.get(input_entity_id)
assert float(state.state) == 9 assert float(state.state) == 9
updated_settings = settings | {"max": 5}
await client.send_json( await client.send_json(
{ {
"id": 7, "id": 7,
"type": f"{DOMAIN}/update", "type": f"{DOMAIN}/update",
f"{DOMAIN}_id": f"{input_id}", f"{DOMAIN}_id": f"{input_id}",
"max": 5, **updated_settings,
"min": 0,
} }
) )
resp = await client.receive_json() resp = await client.receive_json()
assert resp["success"] assert resp["success"]
assert resp["result"] == {"id": "from_storage"} | updated_settings
state = hass.states.get(input_entity_id) state = hass.states.get(input_entity_id)
assert float(state.state) == 5 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): async def test_update(hass, hass_ws_client, storage_setup):
"""Test updating options updates the state.""" """Test updating options updates the state."""
items = [ settings = {
{ "name": "from storage",
"id": "from_storage", "options": ["yaml update 1", "yaml update 2"],
"name": "from storage", }
"options": ["yaml update 1", "yaml update 2"], items = [{"id": "from_storage"} | settings]
}
]
assert await storage_setup(items) assert await storage_setup(items)
input_id = "from_storage" input_id = "from_storage"
@ -647,28 +645,36 @@ async def test_update(hass, hass_ws_client, storage_setup):
client = await hass_ws_client(hass) client = await hass_ws_client(hass)
updated_settings = settings | {
"options": ["new option", "newer option"],
CONF_INITIAL: "newer option",
}
await client.send_json( await client.send_json(
{ {
"id": 6, "id": 6,
"type": f"{DOMAIN}/update", "type": f"{DOMAIN}/update",
f"{DOMAIN}_id": f"{input_id}", f"{DOMAIN}_id": f"{input_id}",
"options": ["new option", "newer option"], **updated_settings,
CONF_INITIAL: "newer option",
} }
) )
resp = await client.receive_json() resp = await client.receive_json()
assert resp["success"] assert resp["success"]
assert resp["result"] == {"id": "from_storage"} | updated_settings
state = hass.states.get(input_entity_id) state = hass.states.get(input_entity_id)
assert state.attributes[ATTR_OPTIONS] == ["new option", "newer option"] assert state.attributes[ATTR_OPTIONS] == ["new option", "newer option"]
# Should fail because the initial state is now invalid # 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( await client.send_json(
{ {
"id": 7, "id": 7,
"type": f"{DOMAIN}/update", "type": f"{DOMAIN}/update",
f"{DOMAIN}_id": f"{input_id}", f"{DOMAIN}_id": f"{input_id}",
"options": ["new option", "no newer option"], **updated_settings,
} }
) )
resp = await client.receive_json() 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): async def test_update_duplicates(hass, hass_ws_client, storage_setup, caplog):
"""Test updating options updates the state.""" """Test updating options updates the state."""
items = [ settings = {
{ "name": "from storage",
"id": "from_storage", "options": ["yaml update 1", "yaml update 2"],
"name": "from storage", }
"options": ["yaml update 1", "yaml update 2"], items = [{"id": "from_storage"} | settings]
}
]
assert await storage_setup(items) assert await storage_setup(items)
input_id = "from_storage" 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) client = await hass_ws_client(hass)
updated_settings = settings | {
"options": ["new option", "newer option", "newer option"],
CONF_INITIAL: "newer option",
}
await client.send_json( await client.send_json(
{ {
"id": 6, "id": 6,
"type": f"{DOMAIN}/update", "type": f"{DOMAIN}/update",
f"{DOMAIN}_id": f"{input_id}", f"{DOMAIN}_id": f"{input_id}",
"options": ["new option", "newer option", "newer option"], **updated_settings,
CONF_INITIAL: "newer option",
} }
) )
resp = await client.receive_json() 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) 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( await client.send_json(
{ {
"id": 6, "id": 6,
"type": f"{DOMAIN}/update", "type": f"{DOMAIN}/update",
f"{DOMAIN}_id": f"{input_id}", f"{DOMAIN}_id": f"{input_id}",
ATTR_NAME: "even newer name", **updated_settings,
CONF_INITIAL: "newer option",
ATTR_MIN: 6,
ATTR_MODE: "password",
} }
) )
resp = await client.receive_json() resp = await client.receive_json()
assert resp["success"] assert resp["success"]
assert resp["result"] == {"id": "from_storage"} | updated_settings
state = hass.states.get(input_entity_id) state = hass.states.get(input_entity_id)
assert state.state == "loaded from storage" assert state.state == "loaded from storage"

View File

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

View File

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

View File

@ -41,7 +41,7 @@ def find_log(logs, level):
if not isinstance(level, tuple): if not isinstance(level, tuple):
level = (level,) level = (level,)
log = next( 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, None,
) )
assert log is not 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) client = await hass_ws_client(hass)
updated_settings = {
CONF_NAME: "timer from storage",
CONF_DURATION: 33,
CONF_RESTORE: True,
}
await client.send_json( await client.send_json(
{ {
"id": 6, "id": 6,
"type": f"{DOMAIN}/update", "type": f"{DOMAIN}/update",
f"{DOMAIN}_id": f"{timer_id}", f"{DOMAIN}_id": f"{timer_id}",
CONF_DURATION: 33, **updated_settings,
CONF_RESTORE: True,
} }
) )
resp = await client.receive_json() resp = await client.receive_json()
assert resp["success"] 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) state = hass.states.get(timer_entity_id)
assert state.attributes[ATTR_DURATION] == _format_timedelta(cv.time_period(33)) 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() await hass.async_block_till_done()
assert len(hass.states.async_all()) == 1 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 smoke_sensor_attribtes = smoke_sensor.attributes
assert smoke_sensor.state == "on" 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) assert await hass.config_entries.async_unload(entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()
@ -90,10 +90,10 @@ async def test_moisture(hass):
await hass.async_block_till_done() await hass.async_block_till_done()
assert len(hass.states.async_all()) == 1 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 sensor_attr = sensor.attributes
assert sensor.state == "on" 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) assert await hass.config_entries.async_unload(entry.entry_id)
await hass.async_block_till_done() 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={} result["flow_id"], user_input={}
) )
assert result2["type"] == FlowResultType.CREATE_ENTRY 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["data"] == {}
assert result2["result"].unique_id == "00:81:F9:DD:6F:C1" 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={} result["flow_id"], user_input={}
) )
assert result2["type"] == FlowResultType.CREATE_ENTRY 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["data"] == {}
assert result2["result"].unique_id == "A4:C1:38:56:53:84" 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["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["data"] == {}
assert result["result"].unique_id == "00:81:F9:DD:6F:C1" assert result["result"].unique_id == "00:81:F9:DD:6F:C1"
assert len(mock_setup_entry.mock_calls) == 1 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"}, user_input={"bindkey": "b853075158487ca39a5b5ea9"},
) )
assert result2["type"] == FlowResultType.CREATE_ENTRY 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["data"] == {"bindkey": "b853075158487ca39a5b5ea9"}
assert result2["result"].unique_id == "F8:24:41:C5:98:8B" 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"}, user_input={"bindkey": "b853075158487ca39a5b5ea9"},
) )
assert result2["type"] == FlowResultType.CREATE_ENTRY 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["data"] == {"bindkey": "b853075158487ca39a5b5ea9"}
assert result2["result"].unique_id == "F8:24:41:C5:98:8B" 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"}, user_input={"bindkey": "b853075158487ca39a5b5ea9"},
) )
assert result2["type"] == FlowResultType.CREATE_ENTRY 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["data"] == {"bindkey": "b853075158487ca39a5b5ea9"}
assert result2["result"].unique_id == "F8:24:41:C5:98:8B" 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["type"] == FlowResultType.CREATE_ENTRY
assert result2["title"] == "Thermometer E39CBC (JTYJGD03MI)" assert result2["title"] == "Thermometer 9CBC (JTYJGD03MI)"
assert result2["data"] == {"bindkey": "5b51a7c91cde6707c9ef18dfda143a58"} assert result2["data"] == {"bindkey": "5b51a7c91cde6707c9ef18dfda143a58"}
assert result2["result"].unique_id == "54:EF:44:E3:9C:BC" 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["type"] == FlowResultType.CREATE_ENTRY
assert result2["title"] == "Thermometer E39CBC (JTYJGD03MI)" assert result2["title"] == "Thermometer 9CBC (JTYJGD03MI)"
assert result2["data"] == {"bindkey": "5b51a7c91cde6707c9ef18dfda143a58"} assert result2["data"] == {"bindkey": "5b51a7c91cde6707c9ef18dfda143a58"}
assert result2["result"].unique_id == "54:EF:44:E3:9C:BC" 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["type"] == FlowResultType.CREATE_ENTRY
assert result2["title"] == "Thermometer E39CBC (JTYJGD03MI)" assert result2["title"] == "Thermometer 9CBC (JTYJGD03MI)"
assert result2["data"] == {"bindkey": "5b51a7c91cde6707c9ef18dfda143a58"} assert result2["data"] == {"bindkey": "5b51a7c91cde6707c9ef18dfda143a58"}
assert result2["result"].unique_id == "54:EF:44:E3:9C:BC" 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"}, user_input={"address": "58:2D:34:35:93:21"},
) )
assert result2["type"] == FlowResultType.CREATE_ENTRY 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["data"] == {}
assert result2["result"].unique_id == "58:2D:34:35:93:21" 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={} result["flow_id"], user_input={}
) )
assert result3["type"] == FlowResultType.CREATE_ENTRY 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["data"] == {}
assert result3["result"].unique_id == "A4:C1:38:56:53:84" 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["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"} 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["type"] == FlowResultType.CREATE_ENTRY
assert result2["title"] == "Thermometer E39CBC (JTYJGD03MI)" assert result2["title"] == "Thermometer 9CBC (JTYJGD03MI)"
assert result2["data"] == {"bindkey": "5b51a7c91cde6707c9ef18dfda143a58"} assert result2["data"] == {"bindkey": "5b51a7c91cde6707c9ef18dfda143a58"}
assert result2["result"].unique_id == "54:EF:44:E3:9C:BC" 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["type"] == FlowResultType.CREATE_ENTRY
assert result2["title"] == "Thermometer E39CBC (JTYJGD03MI)" assert result2["title"] == "Thermometer 9CBC (JTYJGD03MI)"
assert result2["data"] == {"bindkey": "5b51a7c91cde6707c9ef18dfda143a58"} assert result2["data"] == {"bindkey": "5b51a7c91cde6707c9ef18dfda143a58"}
assert result2["result"].unique_id == "54:EF:44:E3:9C:BC" 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["type"] == FlowResultType.CREATE_ENTRY
assert result2["title"] == "Thermometer E39CBC (JTYJGD03MI)" assert result2["title"] == "Thermometer 9CBC (JTYJGD03MI)"
assert result2["data"] == {"bindkey": "5b51a7c91cde6707c9ef18dfda143a58"} assert result2["data"] == {"bindkey": "5b51a7c91cde6707c9ef18dfda143a58"}
assert result2["result"].unique_id == "54:EF:44:E3:9C:BC" 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"}, user_input={"bindkey": "b853075158487ca39a5b5ea9"},
) )
assert result2["type"] == FlowResultType.CREATE_ENTRY 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["data"] == {"bindkey": "b853075158487ca39a5b5ea9"}
assert result2["result"].unique_id == "F8:24:41:C5:98:8B" 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"}, user_input={"bindkey": "b853075158487ca39a5b5ea9"},
) )
assert result2["type"] == FlowResultType.CREATE_ENTRY 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["data"] == {"bindkey": "b853075158487ca39a5b5ea9"}
assert result2["result"].unique_id == "F8:24:41:C5:98:8B" 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"}, user_input={"bindkey": "b853075158487ca39a5b5ea9"},
) )
assert result2["type"] == FlowResultType.CREATE_ENTRY 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["data"] == {"bindkey": "b853075158487ca39a5b5ea9"}
assert result2["result"].unique_id == "F8:24:41:C5:98:8B" 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"}, user_input={"address": "00:81:F9:DD:6F:C1"},
) )
assert result2["type"] == FlowResultType.CREATE_ENTRY 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["data"] == {}
assert result2["result"].unique_id == "00:81:F9:DD:6F:C1" 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() await hass.async_block_till_done()
assert len(hass.states.async_all()) == 2 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 temp_sensor_attribtes = temp_sensor.attributes
assert temp_sensor.state == "36.8719980616822" assert temp_sensor.state == "36.8719980616822"
assert ( assert (
temp_sensor_attribtes[ATTR_FRIENDLY_NAME] temp_sensor_attribtes[ATTR_FRIENDLY_NAME] == "Baby Thermometer 6FC1 Temperature"
== "Baby Thermometer DD6FC1 Temperature"
) )
assert temp_sensor_attribtes[ATTR_UNIT_OF_MEASUREMENT] == "°C" assert temp_sensor_attribtes[ATTR_UNIT_OF_MEASUREMENT] == "°C"
assert temp_sensor_attribtes[ATTR_STATE_CLASS] == "measurement" assert temp_sensor_attribtes[ATTR_STATE_CLASS] == "measurement"
@ -92,10 +91,10 @@ async def test_xiaomi_formaldeyhde(hass):
await hass.async_block_till_done() await hass.async_block_till_done()
assert len(hass.states.async_all()) == 1 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 sensor_attr = sensor.attributes
assert sensor.state == "2.44" 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_UNIT_OF_MEASUREMENT] == "mg/m³"
assert sensor_attr[ATTR_STATE_CLASS] == "measurement" assert sensor_attr[ATTR_STATE_CLASS] == "measurement"
@ -139,10 +138,10 @@ async def test_xiaomi_consumable(hass):
await hass.async_block_till_done() await hass.async_block_till_done()
assert len(hass.states.async_all()) == 1 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 sensor_attr = sensor.attributes
assert sensor.state == "96" 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_UNIT_OF_MEASUREMENT] == "%"
assert sensor_attr[ATTR_STATE_CLASS] == "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() await hass.async_block_till_done()
assert len(hass.states.async_all()) == 2 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 volt_sensor_attr = volt_sensor.attributes
assert volt_sensor.state == "3.1" 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_UNIT_OF_MEASUREMENT] == "V"
assert volt_sensor_attr[ATTR_STATE_CLASS] == "measurement" 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 bat_sensor_attr = bat_sensor.attributes
assert bat_sensor.state == "100" 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_UNIT_OF_MEASUREMENT] == "%"
assert bat_sensor_attr[ATTR_STATE_CLASS] == "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() await hass.async_block_till_done()
assert len(hass.states.async_all()) == 5 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 illum_sensor_attr = illum_sensor.attributes
assert illum_sensor.state == "0" 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_UNIT_OF_MEASUREMENT] == "lx"
assert illum_sensor_attr[ATTR_STATE_CLASS] == "measurement" 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 cond_sensor_attribtes = cond_sensor.attributes
assert cond_sensor.state == "599" assert cond_sensor.state == "599"
assert ( assert cond_sensor_attribtes[ATTR_FRIENDLY_NAME] == "Plant Sensor 3E7A Conductivity"
cond_sensor_attribtes[ATTR_FRIENDLY_NAME] == "Plant Sensor 6A3E7A Conductivity"
)
assert cond_sensor_attribtes[ATTR_UNIT_OF_MEASUREMENT] == "µS/cm" assert cond_sensor_attribtes[ATTR_UNIT_OF_MEASUREMENT] == "µS/cm"
assert cond_sensor_attribtes[ATTR_STATE_CLASS] == "measurement" 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 moist_sensor_attribtes = moist_sensor.attributes
assert moist_sensor.state == "64" 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_UNIT_OF_MEASUREMENT] == "%"
assert moist_sensor_attribtes[ATTR_STATE_CLASS] == "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 temp_sensor_attribtes = temp_sensor.attributes
assert temp_sensor.state == "24.4" assert temp_sensor.state == "24.4"
assert ( assert temp_sensor_attribtes[ATTR_FRIENDLY_NAME] == "Plant Sensor 3E7A Temperature"
temp_sensor_attribtes[ATTR_FRIENDLY_NAME] == "Plant Sensor 6A3E7A Temperature"
)
assert temp_sensor_attribtes[ATTR_UNIT_OF_MEASUREMENT] == "°C" assert temp_sensor_attribtes[ATTR_UNIT_OF_MEASUREMENT] == "°C"
assert temp_sensor_attribtes[ATTR_STATE_CLASS] == "measurement" 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 batt_sensor_attribtes = batt_sensor.attributes
assert batt_sensor.state == "5" 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_UNIT_OF_MEASUREMENT] == "%"
assert batt_sensor_attribtes[ATTR_STATE_CLASS] == "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() await hass.async_block_till_done()
assert len(hass.states.async_all()) == 4 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 illum_sensor_attr = illum_sensor.attributes
assert illum_sensor.state == "0" 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_UNIT_OF_MEASUREMENT] == "lx"
assert illum_sensor_attr[ATTR_STATE_CLASS] == "measurement" 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 cond_sensor_attribtes = cond_sensor.attributes
assert cond_sensor.state == "599" assert cond_sensor.state == "599"
assert ( assert cond_sensor_attribtes[ATTR_FRIENDLY_NAME] == "Plant Sensor 3E7A Conductivity"
cond_sensor_attribtes[ATTR_FRIENDLY_NAME] == "Plant Sensor 6A3E7A Conductivity"
)
assert cond_sensor_attribtes[ATTR_UNIT_OF_MEASUREMENT] == "µS/cm" assert cond_sensor_attribtes[ATTR_UNIT_OF_MEASUREMENT] == "µS/cm"
assert cond_sensor_attribtes[ATTR_STATE_CLASS] == "measurement" 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 moist_sensor_attribtes = moist_sensor.attributes
assert moist_sensor.state == "64" 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_UNIT_OF_MEASUREMENT] == "%"
assert moist_sensor_attribtes[ATTR_STATE_CLASS] == "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 temp_sensor_attribtes = temp_sensor.attributes
assert temp_sensor.state == "24.4" assert temp_sensor.state == "24.4"
assert ( assert temp_sensor_attribtes[ATTR_FRIENDLY_NAME] == "Plant Sensor 3E7A Temperature"
temp_sensor_attribtes[ATTR_FRIENDLY_NAME] == "Plant Sensor 6A3E7A Temperature"
)
assert temp_sensor_attribtes[ATTR_UNIT_OF_MEASUREMENT] == "°C" assert temp_sensor_attribtes[ATTR_UNIT_OF_MEASUREMENT] == "°C"
assert temp_sensor_attribtes[ATTR_STATE_CLASS] == "measurement" 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() await hass.async_block_till_done()
assert len(hass.states.async_all()) == 5 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 illum_sensor_attr = illum_sensor.attributes
assert illum_sensor.state == "0" 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_UNIT_OF_MEASUREMENT] == "lx"
assert illum_sensor_attr[ATTR_STATE_CLASS] == "measurement" 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 cond_sensor_attribtes = cond_sensor.attributes
assert cond_sensor.state == "599" assert cond_sensor.state == "599"
assert ( assert cond_sensor_attribtes[ATTR_FRIENDLY_NAME] == "Plant Sensor 3E7A Conductivity"
cond_sensor_attribtes[ATTR_FRIENDLY_NAME] == "Plant Sensor 6A3E7A Conductivity"
)
assert cond_sensor_attribtes[ATTR_UNIT_OF_MEASUREMENT] == "µS/cm" assert cond_sensor_attribtes[ATTR_UNIT_OF_MEASUREMENT] == "µS/cm"
assert cond_sensor_attribtes[ATTR_STATE_CLASS] == "measurement" 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 moist_sensor_attribtes = moist_sensor.attributes
assert moist_sensor.state == "64" 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_UNIT_OF_MEASUREMENT] == "%"
assert moist_sensor_attribtes[ATTR_STATE_CLASS] == "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 temp_sensor_attribtes = temp_sensor.attributes
assert temp_sensor.state == "24.4" assert temp_sensor.state == "24.4"
assert ( assert temp_sensor_attribtes[ATTR_FRIENDLY_NAME] == "Plant Sensor 3E7A Temperature"
temp_sensor_attribtes[ATTR_FRIENDLY_NAME] == "Plant Sensor 6A3E7A Temperature"
)
assert temp_sensor_attribtes[ATTR_UNIT_OF_MEASUREMENT] == "°C" assert temp_sensor_attribtes[ATTR_UNIT_OF_MEASUREMENT] == "°C"
assert temp_sensor_attribtes[ATTR_STATE_CLASS] == "measurement" 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 batt_sensor_attribtes = batt_sensor.attributes
assert batt_sensor.state == "5" 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_UNIT_OF_MEASUREMENT] == "%"
assert batt_sensor_attribtes[ATTR_STATE_CLASS] == "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() await hass.async_block_till_done()
assert len(hass.states.async_all()) == 1 assert len(hass.states.async_all()) == 1
temp_sensor = hass.states.get( temp_sensor = hass.states.get("sensor.temperature_humidity_sensor_2089_temperature")
"sensor.temperature_humidity_sensor_122089_temperature"
)
temp_sensor_attribtes = temp_sensor.attributes temp_sensor_attribtes = temp_sensor.attributes
assert temp_sensor.state == "22.6" assert temp_sensor.state == "22.6"
assert ( assert (
temp_sensor_attribtes[ATTR_FRIENDLY_NAME] 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_UNIT_OF_MEASUREMENT] == "°C"
assert temp_sensor_attribtes[ATTR_STATE_CLASS] == "measurement" 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, ieee=IEEE_GROUPABLE_DEVICE2,
manufacturer="Sengled", manufacturer="sengled",
nwk=0xC79E, nwk=0xC79E,
) )
color_cluster = zigpy_device.endpoints[1].light_color color_cluster = zigpy_device.endpoints[1].light_color

View File

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