Compare commits

...

17 Commits

Author SHA1 Message Date
Willem-Jan van Rootselaar 486385e308 Bump python-bsblan to version 6.1.4 (#174974) 2026-06-27 18:02:38 +03:00
Raj Laud 11113ce1f4 Add Orion XS and Inverter support to Victron BLE (#169834)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-27 16:48:19 +02:00
Øyvind Matheson Wergeland 31dc1f9519 Drop nobo_hub custom select device class (#170135)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2026-06-27 16:44:57 +02:00
On Freund 926900d0ca Bump pymonoprice to 0.6.1 (#174951)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-27 09:49:03 -04:00
OzGav 06b429d0b7 Update music-assistant-client to 1.3.6 (#174968) 2026-06-27 15:31:17 +02:00
Åke Strandberg 9fb2d14fdb Handle SSE updates of single miele devices (#174955) 2026-06-27 15:26:57 +02:00
Manu 0e6279e273 Remove Watson TTS integration (#174967) 2026-06-27 15:17:41 +02:00
TimL 02ddd4c3f4 Bump Pysmlight to v0.5.0 (#174947) 2026-06-27 15:58:27 +03:00
Michael c147321aba Migrate demo to UnitOfRatio enums (#174966) 2026-06-27 15:56:34 +03:00
Michael f0e75ebb83 Bump aioimmich to 0.15.1 (#174962) 2026-06-27 15:55:18 +03:00
Manu 7ae85d8d97 Remove BlinkStick (#174961) 2026-06-27 14:29:30 +02:00
g4bri3lDev 43802961e9 Fix swallowed exception in opendisplay upload_image action handler (#170660) 2026-06-27 14:19:47 +02:00
Øyvind Matheson Wergeland 97844d7ee0 Filter already-configured hubs from nobo_hub config flow (#169593)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-27 14:18:53 +02:00
renovate[bot] e8e5eedd7e Update pytest-github-actions-annotate-failures to 0.4.2 (#174941) 2026-06-27 10:41:27 +02:00
Erwin Douna bbf7a60a9e ProxmoxVE refactor to use text selectors (#174949) 2026-06-27 10:19:38 +02:00
Erwin Douna fac6e8feb6 MELCloud Home add current password config flow (#174950) 2026-06-27 09:26:20 +02:00
Arie Catsman 86d282ac2a remove pyenphase library deprecated data fields from enphase_envoy fixtures (#174928) 2026-06-27 08:30:48 +02:00
48 changed files with 5258 additions and 1016 deletions
Generated
-1
View File
@@ -1986,7 +1986,6 @@ CLAUDE.md @home-assistant/core
/tests/components/waterfurnace/ @sdague @masterkoppa
/homeassistant/components/watergate/ @adam-the-hero
/tests/components/watergate/ @adam-the-hero
/homeassistant/components/watson_tts/ @rutkai
/homeassistant/components/watts/ @theobld-ww @devender-verma-ww @ssi-spyro
/tests/components/watts/ @theobld-ww @devender-verma-ww @ssi-spyro
/homeassistant/components/watttime/ @bachya
@@ -1 +0,0 @@
"""The BlinkStick integration."""
@@ -1,87 +0,0 @@
"""Support for BlinkStick lights."""
# mypy: ignore-errors
from typing import Any
# from blinkstick import blinkstick
import voluptuous as vol
from homeassistant.components.light import (
ATTR_BRIGHTNESS,
ATTR_HS_COLOR,
PLATFORM_SCHEMA as LIGHT_PLATFORM_SCHEMA,
ColorMode,
LightEntity,
)
from homeassistant.const import CONF_NAME
from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from homeassistant.util import color as color_util
CONF_SERIAL = "serial"
DEFAULT_NAME = "Blinkstick"
PLATFORM_SCHEMA = LIGHT_PLATFORM_SCHEMA.extend(
{
vol.Required(CONF_SERIAL): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
}
)
def setup_platform(
hass: HomeAssistant,
config: ConfigType,
add_entities: AddEntitiesCallback,
discovery_info: DiscoveryInfoType | None = None,
) -> None:
"""Set up BlinkStick device specified by serial number."""
name = config[CONF_NAME]
serial = config[CONF_SERIAL]
stick = blinkstick.find_by_serial(serial)
add_entities([BlinkStickLight(stick, name)], True)
class BlinkStickLight(LightEntity):
"""Representation of a BlinkStick light."""
_attr_color_mode = ColorMode.HS
_attr_supported_color_modes = {ColorMode.HS}
def __init__(self, stick, name):
"""Initialize the light."""
self._stick = stick
self._attr_name = name
def update(self) -> None:
"""Read back the device state."""
rgb_color = self._stick.get_color()
hsv = color_util.color_RGB_to_hsv(*rgb_color)
self._attr_hs_color = hsv[:2]
self._attr_brightness = int(hsv[2])
self._attr_is_on = self.brightness is not None and self.brightness > 0
def turn_on(self, **kwargs: Any) -> None:
"""Turn the device on."""
if ATTR_HS_COLOR in kwargs:
self._attr_hs_color = kwargs[ATTR_HS_COLOR]
brightness: int = kwargs.get(ATTR_BRIGHTNESS, 255)
self._attr_brightness = brightness
self._attr_is_on = bool(brightness)
assert self.hs_color
rgb_color = color_util.color_hsv_to_RGB(
self.hs_color[0], self.hs_color[1], brightness / 255 * 100
)
self._stick.set_color(red=rgb_color[0], green=rgb_color[1], blue=rgb_color[2])
def turn_off(self, **kwargs: Any) -> None:
"""Turn the device off."""
self._stick.turn_off()
@@ -1,11 +0,0 @@
{
"domain": "blinksticklight",
"name": "BlinkStick",
"codeowners": [],
"disabled": "This integration is disabled because it uses non-open source code to operate.",
"documentation": "https://www.home-assistant.io/integrations/blinksticklight",
"iot_class": "local_polling",
"loggers": ["blinkstick"],
"quality_scale": "legacy",
"requirements": ["BlinkStick==1.2.0"]
}
@@ -1,5 +0,0 @@
extend = "../../../pyproject.toml"
lint.extend-ignore = [
"F821"
]
@@ -8,7 +8,7 @@
"iot_class": "local_polling",
"loggers": ["bsblan"],
"quality_scale": "silver",
"requirements": ["python-bsblan==6.1.3"],
"requirements": ["python-bsblan==6.1.4"],
"zeroconf": [
{
"name": "bsb-lan*",
+6 -7
View File
@@ -12,11 +12,10 @@ from homeassistant.components.sensor import (
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
CONCENTRATION_PARTS_PER_MILLION,
PERCENTAGE,
EntityCategory,
UnitOfEnergy,
UnitOfPower,
UnitOfRatio,
UnitOfTemperature,
UnitOfVolume,
)
@@ -52,7 +51,7 @@ async def async_setup_entry(
12,
SensorDeviceClass.BATTERY,
SensorStateClass.MEASUREMENT,
PERCENTAGE,
UnitOfRatio.PERCENTAGE,
entity_category=EntityCategory.DIAGNOSTIC,
entity_name="Battery",
),
@@ -63,7 +62,7 @@ async def async_setup_entry(
54,
SensorDeviceClass.HUMIDITY,
SensorStateClass.MEASUREMENT,
PERCENTAGE,
UnitOfRatio.PERCENTAGE,
),
DemoSensor(
"sensor_3",
@@ -72,7 +71,7 @@ async def async_setup_entry(
54,
SensorDeviceClass.CO,
SensorStateClass.MEASUREMENT,
CONCENTRATION_PARTS_PER_MILLION,
UnitOfRatio.PARTS_PER_MILLION,
),
DemoSensor(
"sensor_4",
@@ -81,7 +80,7 @@ async def async_setup_entry(
54,
SensorDeviceClass.CO2,
SensorStateClass.MEASUREMENT,
CONCENTRATION_PARTS_PER_MILLION,
UnitOfRatio.PARTS_PER_MILLION,
),
DemoSensor(
"battery_4",
@@ -90,7 +89,7 @@ async def async_setup_entry(
99,
SensorDeviceClass.BATTERY,
SensorStateClass.MEASUREMENT,
PERCENTAGE,
UnitOfRatio.PERCENTAGE,
entity_category=EntityCategory.DIAGNOSTIC,
entity_name="Battery",
),
@@ -9,5 +9,5 @@
"iot_class": "local_polling",
"loggers": ["aioimmich"],
"quality_scale": "platinum",
"requirements": ["aioimmich==0.15.0"]
"requirements": ["aioimmich==0.15.1"]
}
@@ -31,7 +31,9 @@ STEP_USER_DATA_SCHEMA = vol.Schema(
TextSelectorConfig(type=TextSelectorType.EMAIL, autocomplete="username")
),
vol.Required(CONF_PASSWORD): TextSelector(
TextSelectorConfig(type=TextSelectorType.PASSWORD)
TextSelectorConfig(
type=TextSelectorType.PASSWORD, autocomplete="current-password"
)
),
}
)
+10 -4
View File
@@ -114,20 +114,26 @@ class MieleDataUpdateCoordinator(DataUpdateCoordinator[MieleCoordinatorData]):
async def callback_update_data(self, devices_json: dict[str, dict]) -> None:
"""Handle data update from the API."""
devices = {
updated_devices = {
device_id: MieleDevice(device) for device_id, device in devices_json.items()
}
self.async_set_updated_data(
MieleCoordinatorData(devices=devices, actions=self.data.actions)
MieleCoordinatorData(
devices={**self.data.devices, **updated_devices},
actions=self.data.actions,
)
)
async def callback_update_actions(self, actions_json: dict[str, dict]) -> None:
"""Handle data update from the API."""
actions = {
updated_actions = {
device_id: MieleAction(action) for device_id, action in actions_json.items()
}
self.async_set_updated_data(
MieleCoordinatorData(devices=self.data.devices, actions=actions)
MieleCoordinatorData(
devices=self.data.devices,
actions={**self.data.actions, **updated_actions},
)
)
@@ -4,7 +4,7 @@ from dataclasses import dataclass
import logging
from pymonoprice import Monoprice, get_monoprice
from serial import SerialException
from serialx import SerialException
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_PORT, Platform
@@ -4,7 +4,7 @@ import logging
from typing import Any, override
from pymonoprice import get_monoprice
from serial import SerialException
from serialx import SerialException
import voluptuous as vol
from homeassistant.config_entries import (
@@ -7,5 +7,5 @@
"integration_type": "hub",
"iot_class": "local_polling",
"loggers": ["pymonoprice"],
"requirements": ["pymonoprice==0.5"]
"requirements": ["pymonoprice==0.6.1"]
}
@@ -3,7 +3,7 @@
import logging
from typing import override
from serial import SerialException
from serialx import SerialException
from homeassistant import core
from homeassistant.components.media_player import (
@@ -25,6 +25,8 @@ from .const import (
DOMAIN,
OVERRIDE_TYPE_CONSTANT,
OVERRIDE_TYPE_NOW,
SERIAL_LENGTH,
SERIAL_PREFIX_LENGTH,
)
DATA_NOBO_HUB_IMPL = "nobo_hub_flow_implementation"
@@ -49,7 +51,20 @@ class NoboHubConfigFlow(ConfigFlow, domain=DOMAIN):
) -> ConfigFlowResult:
"""Handle the initial step."""
if self._discovered_hubs is None:
self._discovered_hubs = dict(await nobo.async_discover_hubs())
# Wait 5s — real-world gaps up to ~4s have been observed.
discovered = dict(await nobo.async_discover_hubs(autodiscover_wait=5.0))
# Hide hubs that already have a config entry. Include matching on IP
# as serial prefix is not unique.
configured = {
(entry.data[CONF_IP_ADDRESS], entry.unique_id[:SERIAL_PREFIX_LENGTH])
for entry in self._async_current_entries(include_ignore=False)
if entry.unique_id
}
self._discovered_hubs = {
ip: prefix
for ip, prefix in discovered.items()
if (ip, prefix) not in configured
}
if not self._discovered_hubs:
# No hubs auto discovered
@@ -227,7 +242,7 @@ class NoboHubConfigFlow(ConfigFlow, domain=DOMAIN):
)
async def _test_connection(self, serial: str, ip_address: str) -> str:
if not len(serial) == 12 or not serial.isdigit():
if len(serial) != SERIAL_LENGTH or not serial.isdigit():
raise NoboHubConnectError("invalid_serial")
try:
socket.inet_aton(ip_address)
@@ -9,6 +9,11 @@ CONF_OVERRIDE_TYPE = "override_type"
OVERRIDE_TYPE_CONSTANT = "constant"
OVERRIDE_TYPE_NOW = "now"
# Hub serial: 9-digit batch prefix + 3-digit per-hub suffix. Discovery
# broadcasts only the prefix; the user supplies the suffix.
SERIAL_PREFIX_LENGTH = 9
SERIAL_LENGTH = SERIAL_PREFIX_LENGTH + 3
NOBO_MANUFACTURER = "Glen Dimplex Nordic AS"
ATTR_HARDWARE_VERSION: Final = "hardware_version"
ATTR_SOFTWARE_VERSION: Final = "software_version"
@@ -64,11 +64,7 @@ rules:
docs-use-cases: todo
dynamic-devices: todo
entity-category: todo
entity-device-class:
status: todo
comment: >
Custom device class on global override select being dropped in
PR #170135.
entity-device-class: done
entity-disabled-by-default: todo
entity-translations: todo
exception-translations: todo
@@ -53,7 +53,6 @@ class NoboGlobalSelector(NoboBaseEntity, SelectEntity):
"""Global override selector for Nobø Ecohub."""
_attr_translation_key = "global_override"
_attr_device_class = "nobo_hub__override"
_modes = {
nobo.API.OVERRIDE_MODE_NORMAL: "none",
nobo.API.OVERRIDE_MODE_AWAY: "away",
@@ -2,7 +2,6 @@
import asyncio
from collections.abc import Callable
import contextlib
from datetime import timedelta
from enum import IntEnum
import io
@@ -188,9 +187,7 @@ async def _async_upload_image(call: ServiceCall) -> None:
current = asyncio.current_task()
if (prev := entry.runtime_data.upload_task) is not None and not prev.done():
prev.cancel()
# pylint: disable-next=home-assistant-action-swallowed-exception
with contextlib.suppress(asyncio.CancelledError):
await prev
await asyncio.wait({prev})
entry.runtime_data.upload_task = current
try:
@@ -25,6 +25,9 @@ from homeassistant.helpers.selector import (
SelectSelector,
SelectSelectorConfig,
SelectSelectorMode,
TextSelector,
TextSelectorConfig,
TextSelectorType,
)
from .common import sanitize_config_entry
@@ -58,7 +61,9 @@ BASE_SCHEMA = vol.Schema(
)
),
vol.Required(CONF_HOST): cv.string,
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_USERNAME): TextSelector(
TextSelectorConfig(type=TextSelectorType.TEXT, autocomplete="username")
),
vol.Required(CONF_PORT, default=DEFAULT_PORT): cv.port,
vol.Required(CONF_TOKEN, default=False): cv.boolean,
vol.Optional(CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL): cv.boolean,
@@ -67,7 +72,12 @@ BASE_SCHEMA = vol.Schema(
PASSWORD_SCHEMA = vol.Schema(
{
vol.Required(CONF_PASSWORD): cv.string,
vol.Required(CONF_PASSWORD): TextSelector(
TextSelectorConfig(
type=TextSelectorType.PASSWORD,
autocomplete="current-password",
)
),
}
)
TOKEN_SCHEMA = vol.Schema(
@@ -12,7 +12,7 @@
"integration_type": "device",
"iot_class": "local_push",
"quality_scale": "silver",
"requirements": ["pysmlight==0.4.0"],
"requirements": ["pysmlight==0.5.0"],
"zeroconf": [
{
"type": "_slzb-06._tcp.local."
@@ -23,6 +23,7 @@ from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
PERCENTAGE,
SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
UnitOfApparentPower,
UnitOfElectricCurrent,
UnitOfElectricPotential,
UnitOfEnergy,
@@ -348,6 +349,13 @@ SENSOR_DESCRIPTIONS = {
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
state_class=SensorStateClass.MEASUREMENT,
),
Keys.OUTPUT_CURRENT: VictronBLESensorEntityDescription(
key=Keys.OUTPUT_CURRENT,
translation_key=Keys.OUTPUT_CURRENT,
device_class=SensorDeviceClass.CURRENT,
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
state_class=SensorStateClass.MEASUREMENT,
),
Keys.REMAINING_MINUTES: VictronBLESensorEntityDescription(
key=Keys.REMAINING_MINUTES,
translation_key=Keys.REMAINING_MINUTES,
@@ -412,6 +420,27 @@ SENSOR_DESCRIPTIONS = {
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
state_class=SensorStateClass.MEASUREMENT,
),
Keys.AC_APPARENT_POWER: VictronBLESensorEntityDescription(
key=Keys.AC_APPARENT_POWER,
translation_key=Keys.AC_APPARENT_POWER,
device_class=SensorDeviceClass.APPARENT_POWER,
native_unit_of_measurement=UnitOfApparentPower.VOLT_AMPERE,
state_class=SensorStateClass.MEASUREMENT,
),
Keys.AC_VOLTAGE: VictronBLESensorEntityDescription(
key=Keys.AC_VOLTAGE,
translation_key=Keys.AC_VOLTAGE,
device_class=SensorDeviceClass.VOLTAGE,
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
state_class=SensorStateClass.MEASUREMENT,
),
Keys.INPUT_CURRENT: VictronBLESensorEntityDescription(
key=Keys.INPUT_CURRENT,
translation_key=Keys.INPUT_CURRENT,
device_class=SensorDeviceClass.CURRENT,
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
state_class=SensorStateClass.MEASUREMENT,
),
Keys.OUTPUT_VOLTAGE_1: VictronBLESensorEntityDescription(
key=Keys.OUTPUT_VOLTAGE_1,
translation_key="output_phase_voltage",
@@ -50,6 +50,9 @@
},
"entity": {
"sensor": {
"ac_apparent_power": {
"name": "AC apparent power"
},
"ac_current": {
"name": "AC current"
},
@@ -67,6 +70,9 @@
"ac_out_power": {
"name": "AC-out power"
},
"ac_voltage": {
"name": "AC voltage"
},
"alarm": {
"name": "Alarm",
"state": {
@@ -210,6 +216,9 @@
"external_device_load": {
"name": "External device load"
},
"input_current": {
"name": "Input current"
},
"input_voltage": {
"name": "Input voltage"
},
@@ -254,6 +263,9 @@
"switched_off_switch": "Switched off by switch"
}
},
"output_current": {
"name": "Output current"
},
"output_phase_current": {
"name": "Output phase {phase} current"
},
@@ -1 +0,0 @@
"""Support for IBM Watson TTS integration."""
@@ -1,11 +0,0 @@
{
"domain": "watson_tts",
"name": "IBM Watson TTS",
"codeowners": ["@rutkai"],
"disabled": "Dependencies not compatible with the new pip resolver",
"documentation": "https://www.home-assistant.io/integrations/watson_tts",
"iot_class": "cloud_push",
"loggers": ["ibm_cloud_sdk_core", "ibm_watson"],
"quality_scale": "legacy",
"requirements": ["ibm-watson==5.2.2"]
}
-200
View File
@@ -1,200 +0,0 @@
"""Support for IBM Watson TTS integration."""
import logging
from typing import Any, override
from ibm_cloud_sdk_core.authenticators import IAMAuthenticator
from ibm_watson import TextToSpeechV1
import voluptuous as vol
from homeassistant.components.tts import (
PLATFORM_SCHEMA as TTS_PLATFORM_SCHEMA,
Provider,
TtsAudioType,
)
from homeassistant.helpers import config_validation as cv
_LOGGER = logging.getLogger(__name__)
CONF_URL = "watson_url"
CONF_APIKEY = "watson_apikey"
DEFAULT_URL = "https://api.us-south.text-to-speech.watson.cloud.ibm.com"
CONF_VOICE = "voice"
CONF_OUTPUT_FORMAT = "output_format"
CONF_TEXT_TYPE = "text"
# List from https://tinyurl.com/watson-tts-docs
SUPPORTED_VOICES = [
"de-DE_BirgitV2Voice",
"de-DE_BirgitV3Voice",
"de-DE_BirgitVoice",
"de-DE_DieterV2Voice",
"de-DE_DieterV3Voice",
"de-DE_DieterVoice",
"de-DE_ErikaV3Voice",
"en-AU_HeidiExpressive",
"en-AU_JackExpressive",
"en-GB_CharlotteV3Voice",
"en-GB_JamesV3Voice",
"en-GB_KateV3Voice",
"en-GB_KateVoice",
"en-US_AllisonExpressive",
"en-US_AllisonV2Voice",
"en-US_AllisonV3Voice",
"en-US_AllisonVoice",
"en-US_EmilyV3Voice",
"en-US_EmmaExpressive",
"en-US_HenryV3Voice",
"en-US_KevinV3Voice",
"en-US_LisaExpressive",
"en-US_LisaV2Voice",
"en-US_LisaV3Voice",
"en-US_LisaVoice",
"en-US_MichaelExpressive",
"en-US_MichaelV2Voice",
"en-US_MichaelV3Voice",
"en-US_MichaelVoice",
"en-US_OliviaV3Voice",
"es-ES_EnriqueV3Voice",
"es-ES_EnriqueVoice",
"es-ES_LauraV3Voice",
"es-ES_LauraVoice",
"es-LA_SofiaV3Voice",
"es-LA_SofiaVoice",
"es-US_SofiaV3Voice",
"es-US_SofiaVoice",
"fr-CA_LouiseV3Voice",
"fr-FR_NicolasV3Voice",
"fr-FR_ReneeV3Voice",
"fr-FR_ReneeVoice",
"it-IT_FrancescaV2Voice",
"it-IT_FrancescaV3Voice",
"it-IT_FrancescaVoice",
"ja-JP_EmiV3Voice",
"ja-JP_EmiVoice",
"ko-KR_JinV3Voice",
"nl-NL_MerelV3Voice",
"pt-BR_IsabelaV3Voice",
"pt-BR_IsabelaVoice",
]
DEPRECATED_VOICES = [
"de-DE_BirgitVoice",
"de-DE_DieterVoice",
"en-US_AllisonVoice",
"en-US_LisaVoice",
"en-US_MichaelVoice",
"es-ES_EnriqueVoice",
"es-ES_LauraVoice",
"es-LA_SofiaVoice",
"es-US_SofiaVoice",
"fr-FR_ReneeVoice",
"it-IT_FrancescaVoice",
"ja-JP_EmiVoice",
"pt-BR_IsabelaVoice",
]
SUPPORTED_OUTPUT_FORMATS = [
"audio/flac",
"audio/mp3",
"audio/mpeg",
"audio/ogg",
"audio/ogg;codecs=opus",
"audio/ogg;codecs=vorbis",
"audio/wav",
]
CONTENT_TYPE_EXTENSIONS = {
"audio/flac": "flac",
"audio/mp3": "mp3",
"audio/mpeg": "mp3",
"audio/ogg": "ogg",
"audio/ogg;codecs=opus": "ogg",
"audio/ogg;codecs=vorbis": "ogg",
"audio/wav": "wav",
}
DEFAULT_VOICE = "en-US_AllisonV3Voice"
DEFAULT_OUTPUT_FORMAT = "audio/mp3"
PLATFORM_SCHEMA = TTS_PLATFORM_SCHEMA.extend(
{
vol.Optional(CONF_URL, default=DEFAULT_URL): cv.string,
vol.Required(CONF_APIKEY): cv.string,
vol.Optional(CONF_VOICE, default=DEFAULT_VOICE): vol.In(SUPPORTED_VOICES),
vol.Optional(CONF_OUTPUT_FORMAT, default=DEFAULT_OUTPUT_FORMAT): vol.In(
SUPPORTED_OUTPUT_FORMATS
),
}
)
def get_engine(hass, config, discovery_info=None):
"""Set up IBM Watson TTS component."""
authenticator = IAMAuthenticator(config[CONF_APIKEY])
service = TextToSpeechV1(authenticator)
service.set_service_url(config[CONF_URL])
supported_languages = list({s[:5] for s in SUPPORTED_VOICES})
default_voice = config[CONF_VOICE]
output_format = config[CONF_OUTPUT_FORMAT]
service.set_default_headers({"x-watson-learning-opt-out": "true"})
if default_voice in DEPRECATED_VOICES:
_LOGGER.warning(
"Watson TTS voice %s is deprecated, it may be removed in the future",
default_voice,
)
return WatsonTTSProvider(service, supported_languages, default_voice, output_format)
class WatsonTTSProvider(Provider):
"""IBM Watson TTS api provider."""
def __init__(self, service, supported_languages, default_voice, output_format):
"""Initialize Watson TTS provider."""
self.service = service
self.supported_langs = supported_languages
self.default_lang = default_voice[:5]
self.default_voice = default_voice
self.output_format = output_format
self.name = "Watson TTS"
@property
@override
def supported_languages(self) -> list[str]:
"""Return a list of supported languages."""
return self.supported_langs
@property
@override
def default_language(self) -> str:
"""Return the default language."""
return self.default_lang
@property
@override
def default_options(self) -> dict[str, Any]:
"""Return dict include default options."""
return {CONF_VOICE: self.default_voice}
@property
@override
def supported_options(self) -> list[str]:
"""Return a list of supported options."""
return [CONF_VOICE]
@override
def get_tts_audio(
self, message: str, language: str, options: dict[str, Any]
) -> TtsAudioType:
"""Request TTS file from Watson TTS."""
response = self.service.synthesize(
text=message, accept=self.output_format, voice=options[CONF_VOICE]
).get_result()
return (CONTENT_TYPE_EXTENSIONS[self.output_format], response.content)
-12
View File
@@ -764,12 +764,6 @@
"config_flow": true,
"iot_class": "cloud_polling"
},
"blinksticklight": {
"name": "BlinkStick",
"integration_type": "hub",
"config_flow": false,
"iot_class": "local_polling"
},
"bliss_automation": {
"name": "Bliss Automation",
"integration_type": "virtual",
@@ -7868,12 +7862,6 @@
"config_flow": true,
"iot_class": "local_push"
},
"watson_tts": {
"name": "IBM Watson TTS",
"integration_type": "hub",
"config_flow": false,
"iot_class": "cloud_push"
},
"watts": {
"name": "Watts Vision +",
"integration_type": "hub",
+4 -4
View File
@@ -303,7 +303,7 @@ aiohue==4.8.1
aioimaplib==2.0.1
# homeassistant.components.immich
aioimmich==0.15.0
aioimmich==0.15.1
# homeassistant.components.apache_kafka
aiokafka==0.10.0
@@ -2358,7 +2358,7 @@ pymochad==0.2.0
pymodbus==3.13.1
# homeassistant.components.monoprice
pymonoprice==0.5
pymonoprice==0.6.1
# homeassistant.components.mysensors
pymysensors==0.26.0
@@ -2564,7 +2564,7 @@ pysmhi==2.0.0
pysml==0.1.8
# homeassistant.components.smlight
pysmlight==0.4.0
pysmlight==0.5.0
# homeassistant.components.snmp
pysnmp==7.1.27
@@ -2615,7 +2615,7 @@ python-awair==0.2.5
python-blockchain-api==0.0.2
# homeassistant.components.bsblan
python-bsblan==6.1.3
python-bsblan==6.1.4
# homeassistant.components.citybikes
python-citybikes==0.3.3
+1 -1
View File
@@ -27,7 +27,7 @@ pytest-asyncio==1.4.0
pytest-aiohttp==1.1.1
pytest-cov==7.1.0
pytest-freezer==0.4.9
pytest-github-actions-annotate-failures==0.4.1
pytest-github-actions-annotate-failures==0.4.2
pytest-socket==0.8.0
pytest-sugar==1.1.1
pytest-timeout==2.4.0
-4
View File
@@ -189,7 +189,6 @@ INTEGRATIONS_WITHOUT_QUALITY_SCALE_FILE = [
"blackbird",
"blebox",
"blink",
"blinksticklight",
"blockchain",
"blue_current",
"bluemaestro",
@@ -986,7 +985,6 @@ INTEGRATIONS_WITHOUT_QUALITY_SCALE_FILE = [
"wake_on_lan",
"wallbox",
"waqi",
"watson_tts",
"watttime",
"waze_travel_time",
"weatherflow_cloud",
@@ -1133,7 +1131,6 @@ INTEGRATIONS_WITHOUT_SCALE = [
"blackbird",
"blebox",
"blink",
"blinksticklight",
"blockchain",
"blue_current",
"bluemaestro",
@@ -1966,7 +1963,6 @@ INTEGRATIONS_WITHOUT_SCALE = [
"wake_on_lan",
"wallbox",
"waqi",
"watson_tts",
"watttime",
"waze_travel_time",
"weatherflow",
@@ -29,12 +29,6 @@
"system_production_phases": null,
"ctmeters": {},
"ctmeters_phases": {},
"ctmeter_production": null,
"ctmeter_consumption": null,
"ctmeter_storage": null,
"ctmeter_production_phases": null,
"ctmeter_consumption_phases": null,
"ctmeter_storage_phases": null,
"dry_contact_status": {},
"dry_contact_settings": {},
"inverters": {
@@ -70,40 +70,6 @@
}
},
"ctmeters_phases": {},
"ctmeter_production": {
"eid": "100000010",
"timestamp": 1708006110,
"energy_delivered": 11234,
"energy_received": 12345,
"active_power": 100,
"power_factor": 0.11,
"voltage": 111,
"current": 0.2,
"frequency": 50.1,
"state": "enabled",
"measurement_type": "production",
"metering_status": "normal",
"status_flags": ["production-imbalance", "power-on-unused-phase"]
},
"ctmeter_consumption": {
"eid": "100000020",
"timestamp": 1708006120,
"energy_delivered": 21234,
"energy_received": 22345,
"active_power": 101,
"power_factor": 0.21,
"voltage": 112,
"current": 0.3,
"frequency": 50.2,
"state": "enabled",
"measurement_type": "net-consumption",
"metering_status": "normal",
"status_flags": []
},
"ctmeter_storage": null,
"ctmeter_production_phases": null,
"ctmeter_consumption_phases": null,
"ctmeter_storage_phases": null,
"dry_contact_status": {},
"dry_contact_settings": {},
"inverters": {
@@ -215,132 +215,6 @@
}
}
},
"ctmeter_production": {
"eid": "100000010",
"timestamp": 1708006110,
"energy_delivered": 11234,
"energy_received": 12345,
"active_power": 100,
"power_factor": 0.11,
"voltage": 111,
"current": 0.2,
"frequency": 50.1,
"state": "enabled",
"measurement_type": "production",
"metering_status": "normal",
"status_flags": ["production-imbalance", "power-on-unused-phase"]
},
"ctmeter_consumption": {
"eid": "100000020",
"timestamp": 1708006120,
"energy_delivered": 21234,
"energy_received": 22345,
"active_power": 101,
"power_factor": 0.21,
"voltage": 112,
"current": 0.3,
"frequency": 50.2,
"state": "enabled",
"measurement_type": "net-consumption",
"metering_status": "normal",
"status_flags": []
},
"ctmeter_storage": null,
"ctmeter_production_phases": {
"L1": {
"eid": "100000011",
"timestamp": 1708006111,
"energy_delivered": 112341,
"energy_received": 123451,
"active_power": 20,
"power_factor": 0.12,
"voltage": 111,
"current": 0.2,
"frequency": 50.1,
"state": "enabled",
"measurement_type": "production",
"metering_status": "normal",
"status_flags": ["production-imbalance"]
},
"L2": {
"eid": "100000012",
"timestamp": 1708006112,
"energy_delivered": 112342,
"energy_received": 123452,
"active_power": 30,
"power_factor": 0.13,
"voltage": 111,
"current": 0.2,
"frequency": 50.1,
"state": "enabled",
"measurement_type": "production",
"metering_status": "normal",
"status_flags": ["power-on-unused-phase"]
},
"L3": {
"eid": "100000013",
"timestamp": 1708006113,
"energy_delivered": 112343,
"energy_received": 123453,
"active_power": 50,
"power_factor": 0.14,
"voltage": 111,
"current": 0.2,
"frequency": 50.1,
"state": "enabled",
"measurement_type": "production",
"metering_status": "normal",
"status_flags": []
}
},
"ctmeter_consumption_phases": {
"L1": {
"eid": "100000021",
"timestamp": 1708006121,
"energy_delivered": 212341,
"energy_received": 223451,
"active_power": 21,
"power_factor": 0.22,
"voltage": 112,
"current": 0.3,
"frequency": 50.2,
"state": "enabled",
"measurement_type": "net-consumption",
"metering_status": "normal",
"status_flags": []
},
"L2": {
"eid": "100000022",
"timestamp": 1708006122,
"energy_delivered": 212342,
"energy_received": 223452,
"active_power": 31,
"power_factor": 0.23,
"voltage": 112,
"current": 0.3,
"frequency": 50.2,
"state": "enabled",
"measurement_type": "net-consumption",
"metering_status": "normal",
"status_flags": []
},
"L3": {
"eid": "100000023",
"timestamp": 1708006123,
"energy_delivered": 212343,
"energy_received": 223453,
"active_power": 51,
"power_factor": 0.24,
"voltage": 112,
"current": 0.3,
"frequency": 50.2,
"state": "enabled",
"measurement_type": "net-consumption",
"metering_status": "normal",
"status_flags": []
}
},
"ctmeter_storage_phases": null,
"dry_contact_status": {},
"dry_contact_settings": {},
"inverters": {
@@ -203,132 +203,6 @@
}
}
},
"ctmeter_production": {
"eid": "100000010",
"timestamp": 1708006110,
"energy_delivered": 11234,
"energy_received": 12345,
"active_power": 100,
"power_factor": 0.11,
"voltage": 111,
"current": 0.2,
"frequency": 50.1,
"state": "enabled",
"measurement_type": "production",
"metering_status": "normal",
"status_flags": ["production-imbalance", "power-on-unused-phase"]
},
"ctmeter_consumption": {
"eid": "100000020",
"timestamp": 1708006120,
"energy_delivered": 21234,
"energy_received": 22345,
"active_power": 101,
"power_factor": 0.21,
"voltage": 112,
"current": 0.3,
"frequency": 50.2,
"state": "enabled",
"measurement_type": "net-consumption",
"metering_status": "normal",
"status_flags": []
},
"ctmeter_storage": null,
"ctmeter_production_phases": {
"L1": {
"eid": "100000011",
"timestamp": 1708006111,
"energy_delivered": 112341,
"energy_received": 123451,
"active_power": 20,
"power_factor": 0.12,
"voltage": 111,
"current": 0.2,
"frequency": 50.1,
"state": "enabled",
"measurement_type": "production",
"metering_status": "normal",
"status_flags": ["production-imbalance"]
},
"L2": {
"eid": "100000012",
"timestamp": 1708006112,
"energy_delivered": 112342,
"energy_received": 123452,
"active_power": 30,
"power_factor": 0.13,
"voltage": 111,
"current": 0.2,
"frequency": 50.1,
"state": "enabled",
"measurement_type": "production",
"metering_status": "normal",
"status_flags": ["power-on-unused-phase"]
},
"L3": {
"eid": "100000013",
"timestamp": 1708006113,
"energy_delivered": 112343,
"energy_received": 123453,
"active_power": 50,
"power_factor": 0.14,
"voltage": 111,
"current": 0.2,
"frequency": 50.1,
"state": "enabled",
"measurement_type": "production",
"metering_status": "normal",
"status_flags": []
}
},
"ctmeter_consumption_phases": {
"L1": {
"eid": "100000021",
"timestamp": 1708006121,
"energy_delivered": 212341,
"energy_received": 223451,
"active_power": 21,
"power_factor": 0.22,
"voltage": 112,
"current": 0.3,
"frequency": 50.2,
"state": "enabled",
"measurement_type": "net-consumption",
"metering_status": "normal",
"status_flags": []
},
"L2": {
"eid": "100000022",
"timestamp": 1708006122,
"energy_delivered": 212342,
"energy_received": 223452,
"active_power": 31,
"power_factor": 0.23,
"voltage": 112,
"current": 0.3,
"frequency": 50.2,
"state": "enabled",
"measurement_type": "net-consumption",
"metering_status": "normal",
"status_flags": []
},
"L3": {
"eid": "100000023",
"timestamp": 1708006123,
"energy_delivered": 212343,
"energy_received": 223453,
"active_power": 51,
"power_factor": 0.24,
"voltage": 112,
"current": 0.3,
"frequency": 50.2,
"state": "enabled",
"measurement_type": "net-consumption",
"metering_status": "normal",
"status_flags": []
}
},
"ctmeter_storage_phases": null,
"dry_contact_status": {},
"dry_contact_settings": {},
"inverters": {
@@ -589,192 +589,6 @@
}
}
},
"ctmeter_production": {
"eid": "100000010",
"timestamp": 1708006110,
"energy_delivered": 11234,
"energy_received": 12345,
"active_power": 100,
"power_factor": 0.11,
"voltage": 111,
"current": 0.2,
"frequency": 50.1,
"state": "enabled",
"measurement_type": "production",
"metering_status": "normal",
"status_flags": ["production-imbalance", "power-on-unused-phase"]
},
"ctmeter_consumption": {
"eid": "100000020",
"timestamp": 1708006120,
"energy_delivered": 21234,
"energy_received": 22345,
"active_power": 101,
"power_factor": 0.21,
"voltage": 112,
"current": 0.3,
"frequency": 50.2,
"state": "enabled",
"measurement_type": "net-consumption",
"metering_status": "normal",
"status_flags": []
},
"ctmeter_storage": {
"eid": "100000030",
"timestamp": 1708006120,
"energy_delivered": 31234,
"energy_received": 32345,
"active_power": 103,
"power_factor": 0.23,
"voltage": 113,
"current": 0.4,
"frequency": 50.3,
"state": "enabled",
"measurement_type": "storage",
"metering_status": "normal",
"status_flags": []
},
"ctmeter_production_phases": {
"L1": {
"eid": "100000011",
"timestamp": 1708006111,
"energy_delivered": 112341,
"energy_received": 123451,
"active_power": 20,
"power_factor": 0.12,
"voltage": 111,
"current": 0.2,
"frequency": 50.1,
"state": "enabled",
"measurement_type": "production",
"metering_status": "normal",
"status_flags": ["production-imbalance"]
},
"L2": {
"eid": "100000012",
"timestamp": 1708006112,
"energy_delivered": 112342,
"energy_received": 123452,
"active_power": 30,
"power_factor": 0.13,
"voltage": 111,
"current": 0.2,
"frequency": 50.1,
"state": "enabled",
"measurement_type": "production",
"metering_status": "normal",
"status_flags": ["power-on-unused-phase"]
},
"L3": {
"eid": "100000013",
"timestamp": 1708006113,
"energy_delivered": 112343,
"energy_received": 123453,
"active_power": 50,
"power_factor": 0.14,
"voltage": 111,
"current": 0.2,
"frequency": 50.1,
"state": "enabled",
"measurement_type": "production",
"metering_status": "normal",
"status_flags": []
}
},
"ctmeter_consumption_phases": {
"L1": {
"eid": "100000021",
"timestamp": 1708006121,
"energy_delivered": 212341,
"energy_received": 223451,
"active_power": 21,
"power_factor": 0.22,
"voltage": 112,
"current": 0.3,
"frequency": 50.2,
"state": "enabled",
"measurement_type": "net-consumption",
"metering_status": "normal",
"status_flags": []
},
"L2": {
"eid": "100000022",
"timestamp": 1708006122,
"energy_delivered": 212342,
"energy_received": 223452,
"active_power": 31,
"power_factor": 0.23,
"voltage": 112,
"current": 0.3,
"frequency": 50.2,
"state": "enabled",
"measurement_type": "net-consumption",
"metering_status": "normal",
"status_flags": []
},
"L3": {
"eid": "100000023",
"timestamp": 1708006123,
"energy_delivered": 212343,
"energy_received": 223453,
"active_power": 51,
"power_factor": 0.24,
"voltage": 112,
"current": 0.3,
"frequency": 50.2,
"state": "enabled",
"measurement_type": "net-consumption",
"metering_status": "normal",
"status_flags": []
}
},
"ctmeter_storage_phases": {
"L1": {
"eid": "100000031",
"timestamp": 1708006121,
"energy_delivered": 312341,
"energy_received": 323451,
"active_power": 22,
"power_factor": 0.32,
"voltage": 113,
"current": 0.4,
"frequency": 50.3,
"state": "enabled",
"measurement_type": "storage",
"metering_status": "normal",
"status_flags": []
},
"L2": {
"eid": "100000032",
"timestamp": 1708006122,
"energy_delivered": 312342,
"energy_received": 323452,
"active_power": 33,
"power_factor": 0.23,
"voltage": 112,
"current": 0.3,
"frequency": 50.2,
"state": "enabled",
"measurement_type": "storage",
"metering_status": "normal",
"status_flags": []
},
"L3": {
"eid": "100000033",
"timestamp": 1708006123,
"energy_delivered": 312343,
"energy_received": 323453,
"active_power": 53,
"power_factor": 0.24,
"voltage": 112,
"current": 0.3,
"frequency": 50.2,
"state": "enabled",
"measurement_type": "storage",
"metering_status": "normal",
"status_flags": []
}
},
"dry_contact_status": {
"NC1": {
"id": "NC1",
@@ -222,132 +222,6 @@
}
}
},
"ctmeter_production": {
"eid": "100000010",
"timestamp": 1708006110,
"energy_delivered": 11234,
"energy_received": 12345,
"active_power": 100,
"power_factor": 0.11,
"voltage": 111,
"current": 0.2,
"frequency": 50.1,
"state": "enabled",
"measurement_type": "production",
"metering_status": "normal",
"status_flags": ["production-imbalance", "power-on-unused-phase"]
},
"ctmeter_consumption": {
"eid": "100000020",
"timestamp": 1708006120,
"energy_delivered": 21234,
"energy_received": 22345,
"active_power": 101,
"power_factor": 0.21,
"voltage": 112,
"current": 0.3,
"frequency": 50.2,
"state": "enabled",
"measurement_type": "net-consumption",
"metering_status": "normal",
"status_flags": []
},
"ctmeter_storage": null,
"ctmeter_production_phases": {
"L1": {
"eid": "100000011",
"timestamp": 1708006111,
"energy_delivered": 112341,
"energy_received": 123451,
"active_power": 20,
"power_factor": 0.12,
"voltage": 111,
"current": 0.2,
"frequency": 50.1,
"state": "enabled",
"measurement_type": "production",
"metering_status": "normal",
"status_flags": ["production-imbalance"]
},
"L2": {
"eid": "100000012",
"timestamp": 1708006112,
"energy_delivered": 112342,
"energy_received": 123452,
"active_power": 30,
"power_factor": 0.13,
"voltage": 111,
"current": 0.2,
"frequency": 50.1,
"state": "enabled",
"measurement_type": "production",
"metering_status": "normal",
"status_flags": ["power-on-unused-phase"]
},
"L3": {
"eid": "100000013",
"timestamp": 1708006113,
"energy_delivered": 112343,
"energy_received": 123453,
"active_power": 50,
"power_factor": 0.14,
"voltage": 111,
"current": 0.2,
"frequency": 50.1,
"state": "enabled",
"measurement_type": "production",
"metering_status": "normal",
"status_flags": []
}
},
"ctmeter_consumption_phases": {
"L1": {
"eid": "100000021",
"timestamp": 1708006121,
"energy_delivered": 212341,
"energy_received": 223451,
"active_power": 21,
"power_factor": 0.22,
"voltage": 112,
"current": 0.3,
"frequency": 50.2,
"state": "enabled",
"measurement_type": "net-consumption",
"metering_status": "normal",
"status_flags": []
},
"L2": {
"eid": "100000022",
"timestamp": 1708006122,
"energy_delivered": 212342,
"energy_received": 223452,
"active_power": 31,
"power_factor": 0.23,
"voltage": 112,
"current": 0.3,
"frequency": 50.2,
"state": "enabled",
"measurement_type": "net-consumption",
"metering_status": "normal",
"status_flags": []
},
"L3": {
"eid": "100000023",
"timestamp": 1708006123,
"energy_delivered": 212343,
"energy_received": 223453,
"active_power": 51,
"power_factor": 0.24,
"voltage": 112,
"current": 0.3,
"frequency": 50.2,
"state": "enabled",
"measurement_type": "net-consumption",
"metering_status": "normal",
"status_flags": []
}
},
"ctmeter_storage_phases": null,
"dry_contact_status": {},
"dry_contact_settings": {},
"inverters": {
@@ -65,40 +65,6 @@
}
},
"ctmeters_phases": {},
"ctmeter_production": {
"eid": "100000010",
"timestamp": 1708006110,
"energy_delivered": 11234,
"energy_received": 12345,
"active_power": 100,
"power_factor": 0.11,
"voltage": 111,
"current": 0.2,
"frequency": 50.1,
"state": "enabled",
"measurement_type": "production",
"metering_status": "normal",
"status_flags": ["production-imbalance", "power-on-unused-phase"]
},
"ctmeter_consumption": {
"eid": "100000020",
"timestamp": 1708006120,
"energy_delivered": 21234,
"energy_received": 22345,
"active_power": 101,
"power_factor": 0.21,
"voltage": 112,
"current": 0.3,
"frequency": 50.2,
"state": "enabled",
"measurement_type": "total-consumption",
"metering_status": "normal",
"status_flags": []
},
"ctmeter_storage": null,
"ctmeter_production_phases": null,
"ctmeter_consumption_phases": null,
"ctmeter_storage_phases": null,
"dry_contact_status": {},
"dry_contact_settings": {},
"inverters": {
@@ -0,0 +1,114 @@
{
"Dummy_Appliance_1": {
"ident": {
"type": {
"key_localized": "Device type",
"value_raw": 20,
"value_localized": "Freezer"
},
"deviceName": "",
"protocolVersion": 201,
"deviceIdentLabel": {
"fabNumber": "Dummy_Appliance_1",
"fabIndex": "21",
"techType": "FNS 28463 E ed/",
"matNumber": "10805070",
"swids": ["4497"]
},
"xkmIdentLabel": {
"techType": "EK042",
"releaseVersion": "31.17"
}
},
"state": {
"ProgramID": {
"value_raw": 0,
"value_localized": "",
"key_localized": "Program name"
},
"status": {
"value_raw": 5,
"value_localized": "In use",
"key_localized": "status"
},
"programType": {
"value_raw": 0,
"value_localized": "",
"key_localized": "Program type"
},
"programPhase": {
"value_raw": 0,
"value_localized": "",
"key_localized": "Program phase"
},
"remainingTime": [0, 0],
"startTime": [0, 0],
"targetTemperature": [
{
"value_raw": -1800,
"value_localized": -18,
"unit": "Celsius"
},
{
"value_raw": -32768,
"value_localized": null,
"unit": "Celsius"
},
{
"value_raw": -32768,
"value_localized": null,
"unit": "Celsius"
}
],
"coreTargetTemperature": [],
"temperature": [
{
"value_raw": -1900,
"value_localized": -19,
"unit": "Celsius"
},
{
"value_raw": -32768,
"value_localized": null,
"unit": "Celsius"
},
{
"value_raw": -32768,
"value_localized": null,
"unit": "Celsius"
}
],
"coreTemperature": [],
"signalInfo": false,
"signalFailure": false,
"signalDoor": false,
"remoteEnable": {
"fullRemoteControl": true,
"smartGrid": false,
"mobileStart": false
},
"ambientLight": null,
"light": null,
"elapsedTime": [],
"spinningSpeed": {
"unit": "rpm",
"value_raw": null,
"value_localized": null,
"key_localized": "Spin speed"
},
"dryingStep": {
"value_raw": null,
"value_localized": "",
"key_localized": "Drying level"
},
"ventilationStep": {
"value_raw": null,
"value_localized": "",
"key_localized": "Fan level"
},
"plateStep": [],
"ecoFeedback": null,
"batteryLevel": null
}
}
}
File diff suppressed because it is too large Load Diff
+25
View File
@@ -15,6 +15,8 @@ from homeassistant.const import STATE_UNAVAILABLE, STATE_UNKNOWN
from homeassistant.core import HomeAssistant, State
from homeassistant.helpers import entity_registry as er
from . import get_data_callback
from tests.common import (
MockConfigEntry,
async_fire_time_changed,
@@ -55,6 +57,29 @@ async def test_sensor_states_api_push(
await snapshot_platform(hass, entity_registry, snapshot, setup_platform.entry_id)
@pytest.mark.freeze_time("2025-05-31 12:30:00+00:00")
@pytest.mark.parametrize("platforms", [(SENSOR_DOMAIN,)])
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
async def test_sensor_states_api_push_one_device(
hass: HomeAssistant,
mock_miele_client: MagicMock,
snapshot: SnapshotAssertion,
entity_registry: er.EntityRegistry,
setup_platform: MockConfigEntry,
push_data_and_actions: None,
) -> None:
"""Test sensor state when the API pushes data for one device only via SSE."""
dev_file = await async_load_json_object_fixture(hass, "1_device.json", DOMAIN)
data_callback = get_data_callback(mock_miele_client)
await data_callback(dev_file)
await hass.async_block_till_done()
assert hass.states.get("sensor.refrigerator_temperature").state != "unavailable"
assert hass.states.get("sensor.freezer_temperature").state == "-19.0"
await snapshot_platform(hass, entity_registry, snapshot, setup_platform.entry_id)
@pytest.mark.parametrize("load_device_file", ["hob.json"])
@pytest.mark.parametrize("platforms", [(SENSOR_DOMAIN,)])
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
@@ -2,7 +2,7 @@
from unittest.mock import patch
from serial import SerialException
from serialx import SerialException
from homeassistant import config_entries
from homeassistant.components.monoprice.const import (
@@ -4,7 +4,7 @@ from collections import defaultdict
from typing import Any
from unittest.mock import patch
from serial import SerialException
from serialx import SerialException
from homeassistant.components.media_player import (
ATTR_INPUT_SOURCE,
@@ -88,7 +88,7 @@
'object_id_base': 'Global override',
'options': dict({
}),
'original_device_class': 'nobo_hub__override',
'original_device_class': None,
'original_icon': None,
'original_name': 'Global override',
'platform': 'nobo_hub',
@@ -103,7 +103,6 @@
# name: test_select_entities[select.my_eco_hub_global_override-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'nobo_hub__override',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'My Eco Hub Global override',
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
'none',
+102 -1
View File
@@ -32,12 +32,13 @@ async def test_configure_with_discover(
with patch(
"homeassistant.components.nobo_hub.config_flow.nobo.async_discover_hubs",
return_value=[("1.1.1.1", "123456789")],
):
) as mock_discover:
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "user"
mock_discover.assert_awaited_once_with(autodiscover_wait=5.0)
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
@@ -76,6 +77,106 @@ async def test_configure_with_discover(
mock_setup_entry.assert_awaited_once()
@pytest.mark.parametrize(
("discovered", "expected_devices", "selected_device"),
[
# Same IP+prefix hidden; sibling with same prefix at a different IP shown.
(
[("1.1.1.1", "111111111"), ("2.2.2.2", "111111111")],
{"2.2.2.2", "manual"},
"2.2.2.2",
),
# Same IP, different prefix → different hub (e.g. replacement), shown.
([("1.1.1.1", "222222222")], {"1.1.1.1", "manual"}, "1.1.1.1"),
],
ids=["sibling_different_ip", "replaced_hub"],
)
async def test_configure_filters_configured_hubs(
hass: HomeAssistant,
mock_setup_entry: AsyncMock,
discovered: list[tuple[str, str]],
expected_devices: set[str],
selected_device: str,
) -> None:
"""Configured (IP, prefix) pairs are hidden; the user can pick a remaining one."""
MockConfigEntry(
domain=DOMAIN,
unique_id="111111111012",
data={CONF_SERIAL: "111111111012", CONF_IP_ADDRESS: "1.1.1.1"},
).add_to_hass(hass)
with patch("pynobo.nobo.async_discover_hubs", return_value=discovered):
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "user"
assert set(result["data_schema"].schema["device"].container) == expected_devices
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{"device": selected_device},
)
assert result2["type"] is FlowResultType.FORM
assert result2["step_id"] == "selected"
with (
patch("pynobo.nobo.async_connect_hub", return_value=True),
patch(
"pynobo.nobo.hub_info",
new_callable=PropertyMock,
create=True,
return_value={"name": "My Nobø Ecohub"},
),
):
result3 = await hass.config_entries.flow.async_configure(
result2["flow_id"],
{"serial_suffix": "999"},
)
assert result3["type"] is FlowResultType.CREATE_ENTRY
async def test_configure_skips_user_step_when_all_configured(
hass: HomeAssistant,
mock_setup_entry: AsyncMock,
) -> None:
"""Flow falls through to manual when every discovered hub matches a configured pair."""
MockConfigEntry(
domain=DOMAIN,
unique_id="111111111012",
data={CONF_SERIAL: "111111111012", CONF_IP_ADDRESS: "1.1.1.1"},
).add_to_hass(hass)
with patch(
"pynobo.nobo.async_discover_hubs",
return_value=[("1.1.1.1", "111111111")],
):
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "manual"
with (
patch("pynobo.nobo.async_connect_hub", return_value=True),
patch(
"pynobo.nobo.hub_info",
new_callable=PropertyMock,
create=True,
return_value={"name": "My Nobø Ecohub"},
),
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{"serial": "999999999999", "ip_address": "9.9.9.9"},
)
assert result2["type"] is FlowResultType.CREATE_ENTRY
async def test_configure_manual(
hass: HomeAssistant,
mock_setup_entry: AsyncMock,
@@ -5,6 +5,7 @@
'MAC': 'AA:BB:CC:DD:EE:FF',
'addons': dict({
}),
'ble': None,
'coord_mode': 0,
'device_ip': '192.168.1.161',
'fs_total': 3456,
+19 -2
View File
@@ -72,6 +72,22 @@ VICTRON_DC_DC_CONVERTER_SERVICE_INFO = BluetoothServiceInfo(
VICTRON_DC_DC_CONVERTER_TOKEN = "64ba49f1a8562e45197a8e1fe50d7658"
# Orion XS
VICTRON_ORION_XS_SERVICE_INFO = BluetoothServiceInfo(
name="Orion XS",
address="01:02:03:04:05:15",
rssi=-60,
manufacturer_data={
0x02E1: bytes.fromhex("1000f0a30f3412aa471c3ac637f436dd184693849c9c081a"),
},
service_data={},
service_uuids=[],
source="local",
)
VICTRON_ORION_XS_TOKEN = "aabbccdd11223344aabbccdd11223344"
# DC energy meter
VICTRON_DC_ENERGY_METER_SERVICE_INFO = BluetoothServiceInfo(
@@ -105,12 +121,13 @@ VICTRON_INVERTER_SERVICE_INFO = BluetoothServiceInfo(
address="01:02:03:04:05:10",
rssi=-60,
manufacturer_data={
0x02E1: bytes.fromhex("1003a2a2031252dad26f0b8eb39162074d140df410"),
}, # not a valid advertisement, but model id mangled to match inverter
0x02E1: bytes.fromhex("100064a2033412aa4d1c7c1e0100570c5f4d938199990f1d"),
},
service_data={},
service_uuids=[],
source="local",
)
VICTRON_INVERTER_TOKEN = "aabbccdd11223344aabbccdd11223344"
VICTRON_INVERTER_RS_SERVICE_INFO = BluetoothServiceInfo(
name="Inverter RS",
File diff suppressed because it is too large Load Diff
@@ -32,6 +32,10 @@ from .fixtures import (
VICTRON_DC_DC_CONVERTER_UNKNOWN_OFF_REASON_SERVICE_INFO,
VICTRON_DC_ENERGY_METER_SERVICE_INFO,
VICTRON_DC_ENERGY_METER_TOKEN,
VICTRON_INVERTER_SERVICE_INFO,
VICTRON_INVERTER_TOKEN,
VICTRON_ORION_XS_SERVICE_INFO,
VICTRON_ORION_XS_TOKEN,
VICTRON_SMART_BATTERY_PROTECT_SERVICE_INFO,
VICTRON_SMART_BATTERY_PROTECT_TOKEN,
VICTRON_SMART_LITHIUM_SERVICE_INFO,
@@ -110,6 +114,8 @@ def test_sensor_descriptions_are_json_serializable() -> None:
(VICTRON_BATTERY_SENSE_SERVICE_INFO, VICTRON_BATTERY_SENSE_TOKEN),
(VICTRON_DC_DC_CONVERTER_SERVICE_INFO, VICTRON_DC_DC_CONVERTER_TOKEN),
(VICTRON_DC_ENERGY_METER_SERVICE_INFO, VICTRON_DC_ENERGY_METER_TOKEN),
(VICTRON_INVERTER_SERVICE_INFO, VICTRON_INVERTER_TOKEN),
(VICTRON_ORION_XS_SERVICE_INFO, VICTRON_ORION_XS_TOKEN),
(
VICTRON_SMART_BATTERY_PROTECT_SERVICE_INFO,
VICTRON_SMART_BATTERY_PROTECT_TOKEN,
@@ -124,6 +130,8 @@ def test_sensor_descriptions_are_json_serializable() -> None:
"battery_sense",
"dc_dc_converter",
"dc_energy_meter",
"inverter",
"orion_xs",
"smart_battery_protect",
"smart_lithium",
"solar_charger",