Skip to content

[pull] dev from home-assistant:dev #636

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Apr 28, 2025
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
6 changes: 3 additions & 3 deletions homeassistant/components/apcupsd/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@
"name": "Internal temperature"
},
"last_self_test": {
"name": "Last self test"
"name": "Last self-test"
},
"last_transfer": {
"name": "Last transfer"
Expand Down Expand Up @@ -177,7 +177,7 @@
"name": "Restore requirement"
},
"self_test_result": {
"name": "Self test result"
"name": "Self-test result"
},
"sensitivity": {
"name": "Sensitivity"
Expand All @@ -195,7 +195,7 @@
"name": "Status"
},
"self_test_interval": {
"name": "Self test interval"
"name": "Self-test interval"
},
"time_left": {
"name": "Time left"
Expand Down
8 changes: 8 additions & 0 deletions homeassistant/components/miele/icons.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@
"default": "mdi:pause"
}
},
"sensor": {
"core_temperature": {
"default": "mdi:thermometer-probe"
},
"core_target_temperature": {
"default": "mdi:thermometer-probe"
}
},
"switch": {
"power": {
"default": "mdi:power"
Expand Down
40 changes: 40 additions & 0 deletions homeassistant/components/miele/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,46 @@ class MieleSensorDefinition:
/ 100.0,
),
),
MieleSensorDefinition(
types=(
MieleAppliance.OVEN,
MieleAppliance.OVEN_MICROWAVE,
MieleAppliance.STEAM_OVEN_COMBI,
),
description=MieleSensorDescription(
key="state_core_temperature",
translation_key="core_temperature",
zone=1,
device_class=SensorDeviceClass.TEMPERATURE,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
state_class=SensorStateClass.MEASUREMENT,
value_fn=(
lambda value: cast(int, value.state_core_temperature[0].temperature)
/ 100.0
),
),
),
MieleSensorDefinition(
types=(
MieleAppliance.OVEN,
MieleAppliance.OVEN_MICROWAVE,
MieleAppliance.STEAM_OVEN_COMBI,
),
description=MieleSensorDescription(
key="state_core_target_temperature",
translation_key="core_target_temperature",
zone=1,
device_class=SensorDeviceClass.TEMPERATURE,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
state_class=SensorStateClass.MEASUREMENT,
value_fn=(
lambda value: cast(
int, value.state_core_target_temperature[0].temperature
)
/ 100.0
),
),
),
)


Expand Down
6 changes: 6 additions & 0 deletions homeassistant/components/miele/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,12 @@
"superheating": "Superheating",
"waiting_to_start": "Waiting to start"
}
},
"core_temperature": {
"name": "Core temperature"
},
"core_target_temperature": {
"name": "Core target temperature"
}
},
"switch": {
Expand Down
8 changes: 7 additions & 1 deletion homeassistant/components/mill/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from homeassistant.helpers.aiohttp_client import async_get_clientsession

from .const import CLOUD, CONNECTION_TYPE, DOMAIN, LOCAL
from .coordinator import MillDataUpdateCoordinator
from .coordinator import MillDataUpdateCoordinator, MillHistoricDataUpdateCoordinator

PLATFORMS = [Platform.CLIMATE, Platform.NUMBER, Platform.SENSOR]

Expand All @@ -41,6 +41,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
key = entry.data[CONF_USERNAME]
conn_type = CLOUD

historic_data_coordinator = MillHistoricDataUpdateCoordinator(
hass,
mill_data_connection=mill_data_connection,
)
historic_data_coordinator.async_add_listener(lambda: None)
await historic_data_coordinator.async_config_entry_first_refresh()
try:
if not await mill_data_connection.connect():
raise ConfigEntryNotReady
Expand Down
115 changes: 114 additions & 1 deletion homeassistant/components/mill/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,30 @@

from datetime import timedelta
import logging
from typing import cast

from mill import Mill
from mill import Heater, Mill
from mill_local import Mill as MillLocal

from homeassistant.components.recorder import get_instance
from homeassistant.components.recorder.models import StatisticData, StatisticMetaData
from homeassistant.components.recorder.statistics import (
async_add_external_statistics,
get_last_statistics,
statistics_during_period,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import UnitOfEnergy
from homeassistant.core import HomeAssistant
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from homeassistant.util import dt as dt_util, slugify

from .const import DOMAIN

_LOGGER = logging.getLogger(__name__)

TWO_YEARS = 2 * 365 * 24


class MillDataUpdateCoordinator(DataUpdateCoordinator):
"""Class to manage fetching Mill data."""
Expand All @@ -40,3 +52,104 @@ def __init__(
update_method=mill_data_connection.fetch_heater_and_sensor_data,
update_interval=update_interval,
)


class MillHistoricDataUpdateCoordinator(DataUpdateCoordinator):
"""Class to manage fetching Mill historic data."""

def __init__(
self,
hass: HomeAssistant,
*,
mill_data_connection: Mill,
) -> None:
"""Initialize global Mill data updater."""
self.mill_data_connection = mill_data_connection

super().__init__(
hass,
_LOGGER,
name="MillHistoricDataUpdateCoordinator",
)

async def _async_update_data(self):
"""Update historic data via API."""
now = dt_util.utcnow()
self.update_interval = (
timedelta(hours=1) + now.replace(minute=1, second=0) - now
)

recoder_instance = get_instance(self.hass)
for dev_id, heater in self.mill_data_connection.devices.items():
if not isinstance(heater, Heater):
continue
statistic_id = f"{DOMAIN}:energy_{slugify(dev_id)}"

last_stats = await recoder_instance.async_add_executor_job(
get_last_statistics, self.hass, 1, statistic_id, True, set()
)
if not last_stats or not last_stats.get(statistic_id):
hourly_data = (
await self.mill_data_connection.fetch_historic_energy_usage(
dev_id, n_days=TWO_YEARS
)
)
hourly_data = dict(sorted(hourly_data.items(), key=lambda x: x[0]))
_sum = 0.0
last_stats_time = None
else:
hourly_data = (
await self.mill_data_connection.fetch_historic_energy_usage(
dev_id,
n_days=(
now
- dt_util.utc_from_timestamp(
last_stats[statistic_id][0]["start"]
)
).days
+ 2,
)
)
if not hourly_data:
continue
hourly_data = dict(sorted(hourly_data.items(), key=lambda x: x[0]))
start_time = next(iter(hourly_data))
stats = await recoder_instance.async_add_executor_job(
statistics_during_period,
self.hass,
start_time,
None,
{statistic_id},
"hour",
None,
{"sum", "state"},
)
stat = stats[statistic_id][0]

_sum = cast(float, stat["sum"]) - cast(float, stat["state"])
last_stats_time = dt_util.utc_from_timestamp(stat["start"])

statistics = []

for start, state in hourly_data.items():
if state is None:
continue
if (last_stats_time and start < last_stats_time) or start > now:
continue
_sum += state
statistics.append(
StatisticData(
start=start,
state=state,
sum=_sum,
)
)
metadata = StatisticMetaData(
has_mean=False,
has_sum=True,
name=f"{heater.name}",
source=DOMAIN,
statistic_id=statistic_id,
unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
)
async_add_external_statistics(self.hass, metadata, statistics)
1 change: 1 addition & 0 deletions homeassistant/components/mill/manifest.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"domain": "mill",
"name": "Mill",
"after_dependencies": ["recorder"],
"codeowners": ["@danielhiversen"],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/mill",
Expand Down
35 changes: 23 additions & 12 deletions homeassistant/components/teslemetry/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -428,16 +428,27 @@ class TeslemetryBinarySensorEntityDescription(BinarySensorEntityDescription):
)


ENERGY_LIVE_DESCRIPTIONS: tuple[BinarySensorEntityDescription, ...] = (
BinarySensorEntityDescription(key="backup_capable"),
BinarySensorEntityDescription(key="grid_services_active"),
BinarySensorEntityDescription(key="storm_mode_active"),
ENERGY_LIVE_DESCRIPTIONS: tuple[TeslemetryBinarySensorEntityDescription, ...] = (
TeslemetryBinarySensorEntityDescription(
key="grid_status",
polling_value_fn=lambda x: x == "Active",
device_class=BinarySensorDeviceClass.POWER,
entity_category=EntityCategory.DIAGNOSTIC,
),
TeslemetryBinarySensorEntityDescription(
key="backup_capable", entity_category=EntityCategory.DIAGNOSTIC
),
TeslemetryBinarySensorEntityDescription(
key="grid_services_active", entity_category=EntityCategory.DIAGNOSTIC
),
TeslemetryBinarySensorEntityDescription(key="storm_mode_active"),
)


ENERGY_INFO_DESCRIPTIONS: tuple[BinarySensorEntityDescription, ...] = (
BinarySensorEntityDescription(
ENERGY_INFO_DESCRIPTIONS: tuple[TeslemetryBinarySensorEntityDescription, ...] = (
TeslemetryBinarySensorEntityDescription(
key="components_grid_services_enabled",
entity_category=EntityCategory.DIAGNOSTIC,
),
)

Expand Down Expand Up @@ -548,38 +559,38 @@ class TeslemetryEnergyLiveBinarySensorEntity(
):
"""Base class for Teslemetry energy live binary sensors."""

entity_description: BinarySensorEntityDescription
entity_description: TeslemetryBinarySensorEntityDescription

def __init__(
self,
data: TeslemetryEnergyData,
description: BinarySensorEntityDescription,
description: TeslemetryBinarySensorEntityDescription,
) -> None:
"""Initialize the binary sensor."""
self.entity_description = description
super().__init__(data, description.key)

def _async_update_attrs(self) -> None:
"""Update the attributes of the binary sensor."""
self._attr_is_on = self._value
self._attr_is_on = self.entity_description.polling_value_fn(self._value)


class TeslemetryEnergyInfoBinarySensorEntity(
TeslemetryEnergyInfoEntity, BinarySensorEntity
):
"""Base class for Teslemetry energy info binary sensors."""

entity_description: BinarySensorEntityDescription
entity_description: TeslemetryBinarySensorEntityDescription

def __init__(
self,
data: TeslemetryEnergyData,
description: BinarySensorEntityDescription,
description: TeslemetryBinarySensorEntityDescription,
) -> None:
"""Initialize the binary sensor."""
self.entity_description = description
super().__init__(data, description.key)

def _async_update_attrs(self) -> None:
"""Update the attributes of the binary sensor."""
self._attr_is_on = self._value
self._attr_is_on = self.entity_description.polling_value_fn(self._value)
24 changes: 24 additions & 0 deletions homeassistant/components/teslemetry/icons.json
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,30 @@
"off": "mdi:hvac-off",
"on": "mdi:hvac"
}
},
"backup_capable": {
"state": {
"off": "mdi:battery-off",
"on": "mdi:home-battery"
}
},
"grid_status": {
"state": {
"off": "mdi:transmission-tower-off",
"on": "mdi:transmission-tower"
}
},
"grid_services_active": {
"state": {
"on": "mdi:sine-wave",
"off": "mdi:transmission-tower-off"
}
},
"components_grid_services_enabled": {
"state": {
"on": "mdi:sine-wave",
"off": "mdi:transmission-tower-off"
}
}
},
"button": {
Expand Down
5 changes: 4 additions & 1 deletion homeassistant/components/teslemetry/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -522,7 +522,10 @@ class TeslemetrySensorEntityDescription(SensorEntityDescription):
device_class=SensorDeviceClass.BATTERY,
native_unit_of_measurement=PERCENTAGE,
),
SensorEntityDescription(key="version"),
SensorEntityDescription(
key="version",
entity_category=EntityCategory.DIAGNOSTIC,
),
)

ENERGY_HISTORY_DESCRIPTIONS: tuple[SensorEntityDescription, ...] = tuple(
Expand Down
Loading