Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions homeassistant/components/apcupsd/quality_scale.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ rules:
status: done
comment: |
Consider looking into making a `mock_setup_entry` fixture that just automatically do this.
`test_config_flow_cannot_connect`: Needs to end in CREATE_ENTRY to test that its able to recover.
`test_config_flow_duplicate`: this test should be split in 2, one for testing duplicate host/port and one for duplicate serial number.
config-flow: done
dependency-transparency: done
docs-actions:
Expand Down
7 changes: 6 additions & 1 deletion homeassistant/components/conversation/chat_log.py
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,12 @@ async def async_add_delta_content_stream(
name=f"llm_tool_{tool_call.id}",
)
if self.delta_listener:
self.delta_listener(self, delta) # type: ignore[arg-type]
if filtered_delta := {
k: v for k, v in delta.items() if k != "native"
}:
# We do not want to send the native content to the listener
# as it is not JSON serializable
self.delta_listener(self, filtered_delta)
continue

# Starting a new message
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/huum/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

DOMAIN = "huum"

PLATFORMS = [Platform.BINARY_SENSOR, Platform.CLIMATE, Platform.LIGHT]
PLATFORMS = [Platform.BINARY_SENSOR, Platform.CLIMATE, Platform.LIGHT, Platform.NUMBER]

CONFIG_STEAMER = 1
CONFIG_LIGHT = 2
Expand Down
13 changes: 13 additions & 0 deletions homeassistant/components/huum/icons.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"entity": {
"number": {
"humidity": {
"default": "mdi:water",
"range": {
"0": "mdi:water-off",
"1": "mdi:water"
}
}
}
}
}
64 changes: 64 additions & 0 deletions homeassistant/components/huum/number.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
"""Control for steamer."""

from __future__ import annotations

import logging

from huum.const import SaunaStatus

from homeassistant.components.number import NumberEntity
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback

from .const import CONFIG_STEAMER, CONFIG_STEAMER_AND_LIGHT
from .coordinator import HuumConfigEntry, HuumDataUpdateCoordinator
from .entity import HuumBaseEntity

_LOGGER = logging.getLogger(__name__)


async def async_setup_entry(
hass: HomeAssistant,
config_entry: HuumConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up steamer if applicable."""
coordinator = config_entry.runtime_data

# Light is configured for this sauna.
if coordinator.data.config in [CONFIG_STEAMER, CONFIG_STEAMER_AND_LIGHT]:
async_add_entities([HuumSteamer(coordinator)])


class HuumSteamer(HuumBaseEntity, NumberEntity):
"""Representation of a steamer."""

_attr_translation_key = "humidity"
_attr_native_max_value = 10
_attr_native_min_value = 0
_attr_native_step = 1

def __init__(self, coordinator: HuumDataUpdateCoordinator) -> None:
"""Initialize the steamer."""
super().__init__(coordinator)

self._attr_unique_id = coordinator.config_entry.entry_id

@property
def native_value(self) -> float:
"""Return the current value."""
return self.coordinator.data.humidity

async def async_set_native_value(self, value: float) -> None:
"""Update the current value."""
target_temperature = self.coordinator.data.target_temperature
if (
not target_temperature
or self.coordinator.data.status != SaunaStatus.ONLINE_HEATING
):
return

await self.coordinator.huum.turn_on(
temperature=target_temperature, humidity=int(value)
)
await self.coordinator.async_refresh()
5 changes: 5 additions & 0 deletions homeassistant/components/huum/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@
"light": {
"name": "[%key:component::light::title%]"
}
},
"number": {
"humidity": {
"name": "[%key:component::sensor::entity_component::humidity::name%]"
}
}
}
}
2 changes: 1 addition & 1 deletion homeassistant/components/modbus/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@
"documentation": "https://www.home-assistant.io/integrations/modbus",
"iot_class": "local_polling",
"loggers": ["pymodbus"],
"requirements": ["pymodbus==3.11.0"]
"requirements": ["pymodbus==3.11.1"]
}
2 changes: 1 addition & 1 deletion homeassistant/components/solarlog/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@
"iot_class": "local_polling",
"loggers": ["solarlog_cli"],
"quality_scale": "platinum",
"requirements": ["solarlog_cli==0.4.0"]
"requirements": ["solarlog_cli==0.5.0"]
}
56 changes: 55 additions & 1 deletion homeassistant/components/solarlog/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from dataclasses import dataclass
from datetime import datetime

from solarlog_cli.solarlog_models import InverterData, SolarlogData
from solarlog_cli.solarlog_models import BatteryData, InverterData, SolarlogData

from homeassistant.components.sensor import (
SensorDeviceClass,
Expand Down Expand Up @@ -35,6 +35,13 @@ class SolarLogCoordinatorSensorEntityDescription(SensorEntityDescription):
value_fn: Callable[[SolarlogData], StateType | datetime | None]


@dataclass(frozen=True, kw_only=True)
class SolarLogBatterySensorEntityDescription(SensorEntityDescription):
"""Describes Solarlog battery sensor entity."""

value_fn: Callable[[BatteryData], float | int | None]


@dataclass(frozen=True, kw_only=True)
class SolarLogInverterSensorEntityDescription(SensorEntityDescription):
"""Describes Solarlog inverter sensor entity."""
Expand Down Expand Up @@ -247,6 +254,33 @@ class SolarLogInverterSensorEntityDescription(SensorEntityDescription):
),
)

BATTERY_SENSOR_TYPES: tuple[SolarLogBatterySensorEntityDescription, ...] = (
SolarLogBatterySensorEntityDescription(
key="charging_power",
translation_key="charging_power",
native_unit_of_measurement=UnitOfPower.WATT,
device_class=SensorDeviceClass.POWER,
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda battery_data: battery_data.charge_power,
),
SolarLogBatterySensorEntityDescription(
key="discharging_power",
translation_key="discharging_power",
native_unit_of_measurement=UnitOfPower.WATT,
device_class=SensorDeviceClass.POWER,
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda battery_data: battery_data.discharge_power,
),
SolarLogBatterySensorEntityDescription(
key="charge_level",
translation_key="charge_level",
native_unit_of_measurement=PERCENTAGE,
device_class=SensorDeviceClass.BATTERY,
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda battery_data: battery_data.level,
),
)

INVERTER_SENSOR_TYPES: tuple[SolarLogInverterSensorEntityDescription, ...] = (
SolarLogInverterSensorEntityDescription(
key="current_power",
Expand Down Expand Up @@ -286,6 +320,13 @@ async def async_setup_entry(
for sensor in SOLARLOG_SENSOR_TYPES
]

# add battery sensors only if respective data is available (otherwise no battery attached to solarlog)
if coordinator.data.battery_data is not None:
entities.extend(
SolarLogBatterySensor(coordinator, sensor)
for sensor in BATTERY_SENSOR_TYPES
)

device_data = coordinator.data.inverter_data

if device_data:
Expand Down Expand Up @@ -318,6 +359,19 @@ def native_value(self) -> StateType | datetime:
return self.entity_description.value_fn(self.coordinator.data)


class SolarLogBatterySensor(SolarLogCoordinatorEntity, SensorEntity):
"""Represents a SolarLog battery sensor."""

entity_description: SolarLogBatterySensorEntityDescription

@property
def native_value(self) -> StateType:
"""Return the state for this sensor."""
if (battery_data := self.coordinator.data.battery_data) is None:
return None
return self.entity_description.value_fn(battery_data)


class SolarLogInverterSensor(SolarLogInverterEntity, SensorEntity):
"""Represents a SolarLog inverter sensor."""

Expand Down
9 changes: 9 additions & 0 deletions homeassistant/components/solarlog/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,15 @@
},
"entity": {
"sensor": {
"charge_level": {
"name": "Charge level"
},
"charging_power": {
"name": "Charging power"
},
"discharging_power": {
"name": "Discharging power"
},
"last_update": {
"name": "Last update"
},
Expand Down
4 changes: 2 additions & 2 deletions homeassistant/components/totalconnect/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,9 +172,9 @@ def __init__(
super().__init__(coordinator, zone, location_id, entity_description.key)
self.entity_description = entity_description
self._attr_extra_state_attributes = {
"zone_id": zone.zoneid,
"zone_id": str(zone.zoneid),
"location_id": location_id,
"partition": zone.partition,
"partition": str(zone.partition),
}

@property
Expand Down
6 changes: 1 addition & 5 deletions homeassistant/components/totalconnect/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,11 +105,7 @@ async def async_step_locations(
},
)
else:
# Force the loading of locations using I/O
number_locations = await self.hass.async_add_executor_job(
self.client.get_number_locations,
)
if number_locations < 1:
if self.client.get_number_locations() < 1:
return self.async_abort(reason="no_locations")
for location_id in self.client.locations:
self.usercodes[location_id] = None
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/totalconnect/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/totalconnect",
"iot_class": "cloud_polling",
"loggers": ["total_connect_client"],
"requirements": ["total-connect-client==2025.1.4"]
"requirements": ["total-connect-client==2025.5"]
}
13 changes: 11 additions & 2 deletions homeassistant/components/tuya/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -314,8 +314,8 @@ class TuyaBinarySensorEntityDescription(BinarySensorEntityDescription):
),
TAMPER_BINARY_SENSOR,
),
# Zigbee gateway
# Undocumented
# Gateway control
# https://developer.tuya.com/en/docs/iot/wg?id=Kbcdadk79ejok
"wg2": (
TuyaBinarySensorEntityDescription(
key=DPCode.MASTER_STATE,
Expand All @@ -324,6 +324,15 @@ class TuyaBinarySensorEntityDescription(BinarySensorEntityDescription):
on_value="alarm",
),
),
# Thermostat
# https://developer.tuya.com/en/docs/iot/f?id=K9gf45ld5l0t9
"wk": (
TuyaBinarySensorEntityDescription(
key=DPCode.VALVE_STATE,
translation_key="valve",
on_value="open",
),
),
# Thermostatic Radiator Valve
# Not documented
"wkf": (
Expand Down
2 changes: 2 additions & 0 deletions homeassistant/components/tuya/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ class DPCode(StrEnum):
FLOODLIGHT_LIGHTNESS = "floodlight_lightness"
FLOODLIGHT_SWITCH = "floodlight_switch"
FORWARD_ENERGY_TOTAL = "forward_energy_total"
FROST = "frost" # Frost protection
GAS_SENSOR_STATE = "gas_sensor_state"
GAS_SENSOR_STATUS = "gas_sensor_status"
GAS_SENSOR_VALUE = "gas_sensor_value"
Expand Down Expand Up @@ -408,6 +409,7 @@ class DPCode(StrEnum):
VA_BATTERY = "va_battery"
VA_HUMIDITY = "va_humidity"
VA_TEMPERATURE = "va_temperature"
VALVE_STATE = "valve_state"
VOC_STATE = "voc_state"
VOC_VALUE = "voc_value"
VOICE_SWITCH = "voice_switch"
Expand Down
3 changes: 3 additions & 0 deletions homeassistant/components/tuya/icons.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
},
"tilt": {
"default": "mdi:spirit-level"
},
"valve": {
"default": "mdi:pipe-valve"
}
},
"button": {
Expand Down
2 changes: 2 additions & 0 deletions homeassistant/components/tuya/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -1575,6 +1575,7 @@ def __init__(
self.unique_id,
)
self._attr_device_class = None
self._attr_suggested_unit_of_measurement = None
return

uoms = DEVICE_CLASS_UNITS[self.device_class]
Expand All @@ -1585,6 +1586,7 @@ def __init__(
# Unknown unit of measurement, device class should not be used.
if uom is None:
self._attr_device_class = None
self._attr_suggested_unit_of_measurement = None
return

# Found unit of measurement, use the standardized Unit
Expand Down
6 changes: 6 additions & 0 deletions homeassistant/components/tuya/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@
"defrost": {
"name": "Defrost"
},
"valve": {
"name": "Valve"
},
"wet": {
"name": "Wet"
}
Expand Down Expand Up @@ -892,6 +895,9 @@
},
"siren": {
"name": "Siren"
},
"frost_protection": {
"name": "Frost protection"
}
}
},
Expand Down
14 changes: 14 additions & 0 deletions homeassistant/components/tuya/switch.py
Original file line number Diff line number Diff line change
Expand Up @@ -785,6 +785,15 @@
entity_category=EntityCategory.CONFIG,
),
),
# Gateway control
# https://developer.tuya.com/en/docs/iot/wg?id=Kbcdadk79ejok
"wg2": (
SwitchEntityDescription(
key=DPCode.MUFFLING,
translation_key="mute",
entity_category=EntityCategory.CONFIG,
),
),
# Thermostat
# https://developer.tuya.com/en/docs/iot/f?id=K9gf45ld5l0t9
"wk": (
Expand All @@ -793,6 +802,11 @@
translation_key="child_lock",
entity_category=EntityCategory.CONFIG,
),
SwitchEntityDescription(
key=DPCode.FROST,
translation_key="frost_protection",
entity_category=EntityCategory.CONFIG,
),
),
# Two-way temperature and humidity switch
# "MOES Temperature and Humidity Smart Switch Module MS-103"
Expand Down
Loading
Loading