Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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: 1 addition & 1 deletion homeassistant/components/acer_projector/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/acer_projector",
"iot_class": "local_polling",
"quality_scale": "legacy",
"requirements": ["serialx==1.4.1"]
"requirements": ["serialx==1.7.0"]
}
2 changes: 1 addition & 1 deletion homeassistant/components/elkm1/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,5 @@
"integration_type": "hub",
"iot_class": "local_push",
"loggers": ["elkm1_lib"],
"requirements": ["elkm1-lib==2.2.13"]
"requirements": ["elkm1-lib==2.2.15"]
}
4 changes: 3 additions & 1 deletion homeassistant/components/elkm1/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,9 @@ class ElkSetting(ElkSensor):
_element: Setting

def _element_changed(self, element: Element, changeset: dict[str, Any]) -> None:
self._attr_native_value = self._element.value
self._attr_native_value = (
None if self._element.value is None else str(self._element.value)
)

@property
def extra_state_attributes(self) -> dict[str, Any]:
Expand Down
1 change: 0 additions & 1 deletion homeassistant/components/openai_conversation/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@
]

UNSUPPORTED_WEB_SEARCH_MODELS: list[str] = [
"gpt-5-nano",
"gpt-3.5",
"gpt-4-turbo",
"gpt-4.1-nano",
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/serial/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@
"codeowners": ["@fabaff"],
"documentation": "https://www.home-assistant.io/integrations/serial",
"iot_class": "local_polling",
"requirements": ["serialx==1.4.1"]
"requirements": ["serialx==1.7.0"]
}
1 change: 1 addition & 0 deletions homeassistant/components/switchbot/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
SupportedModels.HYGROMETER.value: [Platform.SENSOR],
SupportedModels.HYGROMETER_CO2.value: [
Platform.BUTTON,
Platform.NUMBER,
Platform.SENSOR,
Platform.SELECT,
],
Expand Down
77 changes: 77 additions & 0 deletions homeassistant/components/switchbot/number.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
"""Number platform for SwitchBot devices."""

from datetime import timedelta
import logging

import switchbot
from switchbot import SwitchbotOperationError
from switchbot.devices.meter_pro import MAX_TIME_OFFSET

from homeassistant.components.number import NumberDeviceClass, NumberEntity
from homeassistant.const import EntityCategory, UnitOfTime
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback

from .coordinator import SwitchbotConfigEntry, SwitchbotDataUpdateCoordinator
from .entity import SwitchbotEntity, exception_handler

PARALLEL_UPDATES = 0
SCAN_INTERVAL = timedelta(days=7)
_LOGGER = logging.getLogger(__name__)
_SECONDS_IN_MINUTE = 60
_MAX_TIME_OFFSET_MINUTES = MAX_TIME_OFFSET // _SECONDS_IN_MINUTE


async def async_setup_entry(
hass: HomeAssistant,
entry: SwitchbotConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up SwitchBot number platform."""
coordinator = entry.runtime_data

if isinstance(coordinator.device, switchbot.SwitchbotMeterProCO2):
async_add_entities(
[SwitchBotMeterProCO2DisplayTimeOffsetNumber(coordinator)], True
)


class SwitchBotMeterProCO2DisplayTimeOffsetNumber(SwitchbotEntity, NumberEntity):
"""Number entity to set the time offset for Meter Pro CO2 devices."""

_device: switchbot.SwitchbotMeterProCO2
_attr_device_class = NumberDeviceClass.DURATION
_attr_entity_category = EntityCategory.CONFIG
_attr_translation_key = "display_time_offset"
_attr_native_min_value = -_MAX_TIME_OFFSET_MINUTES
_attr_native_max_value = _MAX_TIME_OFFSET_MINUTES
_attr_native_step = 1.0
_attr_native_unit_of_measurement = UnitOfTime.MINUTES
_attr_should_poll = True
_attr_entity_registry_enabled_default = False

def __init__(self, coordinator: SwitchbotDataUpdateCoordinator) -> None:
"""Initialize the number entity."""
super().__init__(coordinator)
self._attr_unique_id = f"{coordinator.base_unique_id}_display_time_offset"

@exception_handler
async def async_set_native_value(self, value: float) -> None:
"""Set the time offset."""
_LOGGER.debug("Setting time offset to %s minutes for %s", value, self._address)
offset_minutes = round(value)
offset_seconds = offset_minutes * _SECONDS_IN_MINUTE
await self._device.set_time_offset(offset_seconds)
self._attr_native_value = offset_minutes
self.async_write_ha_state()

async def async_update(self) -> None:
"""Fetch the latest time offset from the device."""
try:
offset_seconds = await self._device.get_time_offset()
except SwitchbotOperationError:
_LOGGER.debug(
"Failed to update time offset for %s", self._address, exc_info=True
)
return
self._attr_native_value = round(offset_seconds / _SECONDS_IN_MINUTE)
5 changes: 5 additions & 0 deletions homeassistant/components/switchbot/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,11 @@
}
}
},
"number": {
"display_time_offset": {
"name": "Display time offset"
}
},
"select": {
"time_format": {
"name": "Time format",
Expand Down
49 changes: 16 additions & 33 deletions homeassistant/components/template/weather.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,12 @@
ATTR_CONDITION_WINDY_VARIANT,
DOMAIN as WEATHER_DOMAIN,
ENTITY_ID_FORMAT,
PLATFORM_SCHEMA as WEATHER_PLATFORM_SCHEMA,
Forecast,
WeatherEntity,
WeatherEntityFeature,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
CONF_NAME,
CONF_TEMPERATURE_UNIT,
CONF_UNIQUE_ID,
STATE_UNAVAILABLE,
STATE_UNKNOWN,
)
from homeassistant.const import CONF_TEMPERATURE_UNIT, STATE_UNAVAILABLE, STATE_UNKNOWN
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.entity_platform import (
Expand Down Expand Up @@ -230,18 +223,6 @@
make_template_entity_common_modern_schema(WEATHER_DOMAIN, DEFAULT_NAME).schema
)

PLATFORM_SCHEMA = (
vol.Schema(
{
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.template,
vol.Optional(CONF_UNIQUE_ID): cv.string,
}
)
.extend(WEATHER_COMMON_LEGACY_SCHEMA.schema)
.extend(WEATHER_PLATFORM_SCHEMA.schema)
)


WEATHER_CONFIG_ENTRY_SCHEMA = WEATHER_COMMON_MODERN_SCHEMA.extend(
TEMPLATE_ENTITY_COMMON_CONFIG_ENTRY_SCHEMA.schema
)
Expand All @@ -257,21 +238,23 @@ async def async_setup_platform(

# Rewrite the configuration options to modern keys.
if discovery_info is None:
# Legacy
config = rewrite_legacy_to_modern_config(hass, config, LEGACY_FIELDS)
else:
# Modern and Trigger
entity_configs: list[ConfigType] = discovery_info["entities"]
modified_entity_configs = []
for entity_config in entity_configs:
entity_config = rewrite_legacy_to_modern_config(
hass, entity_config, LEGACY_FIELDS
)
_LOGGER.warning(
"Template weather entities can only be configured under template:"
)
return

# Modern and Trigger
entity_configs: list[ConfigType] = discovery_info["entities"]
modified_entity_configs = []
for entity_config in entity_configs:
entity_config = rewrite_legacy_to_modern_config(
hass, entity_config, LEGACY_FIELDS
)

modified_entity_configs.append(entity_config)
modified_entity_configs.append(entity_config)

if modified_entity_configs:
discovery_info["entities"] = modified_entity_configs
if modified_entity_configs:
discovery_info["entities"] = modified_entity_configs

await async_setup_template_platform(
hass,
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/tuya/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
"iot_class": "cloud_push",
"loggers": ["tuya_sharing"],
"requirements": [
"tuya-device-handlers==0.0.18",
"tuya-device-handlers==0.0.19",
"tuya-device-sharing-sdk==0.2.8"
]
}
2 changes: 1 addition & 1 deletion homeassistant/components/usb/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@
"integration_type": "system",
"iot_class": "local_push",
"quality_scale": "internal",
"requirements": ["aiousbwatcher==1.1.2", "serialx==1.4.1"]
"requirements": ["aiousbwatcher==1.1.2", "serialx==1.7.0"]
}
1 change: 1 addition & 0 deletions homeassistant/components/v2c/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

PLATFORMS: list[Platform] = [
Platform.BINARY_SENSOR,
Platform.LIGHT,
Platform.NUMBER,
Platform.SENSOR,
Platform.SWITCH,
Expand Down
8 changes: 8 additions & 0 deletions homeassistant/components/v2c/icons.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
{
"entity": {
"light": {
"light_led": {
"default": "mdi:led-on"
},
"logo_led": {
"default": "mdi:led-on"
}
},
"sensor": {
"battery_power": {
"default": "mdi:home-battery"
Expand Down
126 changes: 126 additions & 0 deletions homeassistant/components/v2c/light.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
"""Light platform for V2C EVSE LEDs."""

from collections.abc import Callable, Coroutine
from dataclasses import dataclass
from typing import Any

from pytrydan import Trydan, TrydanData

from homeassistant.components.light import (
ATTR_BRIGHTNESS,
ColorMode,
LightEntity,
LightEntityDescription,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.util.color import brightness_to_value, value_to_brightness

from .coordinator import V2CConfigEntry, V2CUpdateCoordinator
from .entity import V2CBaseEntity

LED_ON_VALUE = 100
LED_OFF_VALUE = 0
BRIGHTNESS_SCALE = (LED_OFF_VALUE, LED_ON_VALUE)


@dataclass(frozen=True, kw_only=True)
class V2CLightEntityDescription(LightEntityDescription):
"""Describes V2C EVSE light entity."""

supports_brightness: bool = False
value_fn: Callable[[TrydanData], int | None]
update_fn: Callable[[Trydan, int], Coroutine[Any, Any, None]]


TRYDAN_LIGHTS = (
V2CLightEntityDescription(
key="light_led",
translation_key="light_led",
entity_registry_enabled_default=False,
value_fn=lambda evse_data: evse_data.light_led,
update_fn=lambda evse, value: evse.light_led(value),
),
V2CLightEntityDescription(
key="logo_led",
translation_key="logo_led",
supports_brightness=True,
value_fn=lambda evse_data: evse_data.logo_led,
update_fn=lambda evse, value: evse.logo_led(value),
),
)


async def async_setup_entry(
hass: HomeAssistant,
config_entry: V2CConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up V2C Trydan light platform."""
coordinator = config_entry.runtime_data
data = coordinator.data
assert data is not None

async_add_entities(
V2CLightEntity(
coordinator,
description,
config_entry.entry_id,
)
for description in TRYDAN_LIGHTS
if description.value_fn(data) is not None
)


class V2CLightEntity(V2CBaseEntity, LightEntity):
"""Representation of V2C EVSE LED light entity."""

entity_description: V2CLightEntityDescription

def __init__(
self,
coordinator: V2CUpdateCoordinator,
description: V2CLightEntityDescription,
entry_id: str,
) -> None:
"""Initialize the V2C light entity."""
super().__init__(coordinator, description)
self._attr_unique_id = f"{entry_id}_{description.key}"
self._attr_color_mode = (
ColorMode.BRIGHTNESS if description.supports_brightness else ColorMode.ONOFF
)
self._attr_supported_color_modes = {self._attr_color_mode}

@property
def brightness(self) -> int | None:
"""Return the light brightness."""
if not self.entity_description.supports_brightness:
return None
value = self.entity_description.value_fn(self.data)
if value is None:
return None
return value_to_brightness(BRIGHTNESS_SCALE, value)

@property
def is_on(self) -> bool | None:
"""Return true if the light is on."""
value = self.entity_description.value_fn(self.data)
if value is None:
return None
return value > 0

async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn on the LED."""
value = LED_ON_VALUE
if self.entity_description.supports_brightness:
brightness = kwargs.get(ATTR_BRIGHTNESS, 255)
value = round(brightness_to_value(BRIGHTNESS_SCALE, brightness))
if brightness:
value = max(value, 1)
await self.entity_description.update_fn(self.coordinator.evse, value)
await self.coordinator.async_request_refresh()

async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn off the LED."""
await self.entity_description.update_fn(self.coordinator.evse, LED_OFF_VALUE)
await self.coordinator.async_request_refresh()
8 changes: 8 additions & 0 deletions homeassistant/components/v2c/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@
"name": "Ready"
}
},
"light": {
"light_led": {
"name": "Light LED"
},
"logo_led": {
"name": "Logo LED"
}
},
"number": {
"intensity": {
"name": "Intensity"
Expand Down
Loading
Loading