Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
899f0e0
Add Tuya test fixtures (#150835)
epenet Aug 19, 2025
69757be
Support for YoLink YS4102 YS4103 (#150464)
matrixd2 Aug 19, 2025
c18dc9b
Fix icloud service calls (#150881)
epenet Aug 19, 2025
c62c52e
Bump mastodon.py to 2.1.1 (#150876)
andrew-codechimp Aug 19, 2025
69dbcb0
Add sound switch to Tuya fan light (#150879)
epenet Aug 19, 2025
e6a158b
Add temperature sensor to Tuya solar inverters (#150878)
epenet Aug 19, 2025
4c1788e
Add sensors to Imeon inverter integration (#146437)
Imeon-Energy Aug 19, 2025
319e373
Migrate Emoncms_history to external async library (#149824)
alexandrecuer Aug 19, 2025
c46618c
Bump renault-api to 0.4.0 (#150624)
epenet Aug 19, 2025
e8409e7
Bump to zcc-helper==3.6 (#150608)
markhannon Aug 19, 2025
a08be4f
Add event entity to Togrill (#150812)
elupus Aug 19, 2025
785c9eb
Modbus: Retry primary connect. (#150853)
janiversen Aug 19, 2025
89abe65
Add air purifier for switchbot cloud integration (#147001)
zerzhang Aug 19, 2025
76f3397
Bump pyDaikin to 2.16.0 (#150867)
pwarren Aug 19, 2025
b52a806
Show charging power as 0 when not charging for the Volvo integration …
thomasddn Aug 19, 2025
f440051
Fix PWA theme color to match darker blue color scheme in 2025.8 (#150…
balloob Aug 19, 2025
63640af
Update voluptuous-serialize to 2.7.0 (#150822)
farmio Aug 19, 2025
2290940
Modbus: Remove unused variable. (#150894)
janiversen Aug 19, 2025
08fc2ab
Add missing unsupported reasons to list (#150866)
agners Aug 19, 2025
65696f9
Bump aiohasupervisor from version 0.3.1 to version 0.3.2b0 (#150893)
agners Aug 19, 2025
10fe479
Add new attributes to Met Éireann (#150653)
rossfoss Aug 19, 2025
cded163
Update contributing guide links (#150159)
lukeheckman Aug 19, 2025
7ecf323
Modbus: Avoid duplicate updates. (#150895)
janiversen Aug 19, 2025
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: 2 additions & 0 deletions CODEOWNERS

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 5 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,8 @@ Still interested? Then you should take a peek at the [developer documentation](h

## Feature suggestions

If you want to suggest a new feature for Home Assistant (e.g., new integrations), please open a thread in our [Community Forum: Feature Requests](https://community.home-assistant.io/c/feature-requests).
We use [GitHub for tracking issues](https://github.com/home-assistant/core/issues), not for tracking feature requests.
If you want to suggest a new feature for Home Assistant (e.g. new integrations), please [start a discussion](https://github.com/orgs/home-assistant/discussions) on GitHub.

## Issue Tracker

If you want to report an issue, please [create an issue](https://github.com/home-assistant/core/issues) on GitHub.
22 changes: 9 additions & 13 deletions homeassistant/components/auth/login_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,23 +199,19 @@ async def get(self, request: web.Request) -> web.Response:
)


def _prepare_result_json(
result: AuthFlowResult,
) -> AuthFlowResult:
"""Convert result to JSON."""
def _prepare_result_json(result: AuthFlowResult) -> dict[str, Any]:
"""Convert result to JSON serializable dict."""
if result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY:
data = result.copy()
data.pop("result")
data.pop("data")
return data
return {
key: val for key, val in result.items() if key not in ("result", "data")
}

if result["type"] != data_entry_flow.FlowResultType.FORM:
return result
return result # type: ignore[return-value]

data = result.copy()

if (schema := data["data_schema"]) is None:
data["data_schema"] = [] # type: ignore[typeddict-item] # json result type
data = dict(result)
if (schema := result["data_schema"]) is None:
data["data_schema"] = []
else:
data["data_schema"] = voluptuous_serialize.convert(schema)

Expand Down
18 changes: 7 additions & 11 deletions homeassistant/components/auth/mfa_setup_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,20 +149,16 @@ async def async_depose(msg: dict[str, Any]) -> None:
hass.async_create_task(async_depose(msg))


def _prepare_result_json(
result: data_entry_flow.FlowResult,
) -> data_entry_flow.FlowResult:
"""Convert result to JSON."""
def _prepare_result_json(result: data_entry_flow.FlowResult) -> dict[str, Any]:
"""Convert result to JSON serializable dict."""
if result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY:
return result.copy()

return dict(result)
if result["type"] != data_entry_flow.FlowResultType.FORM:
return result

data = result.copy()
return result # type: ignore[return-value]

if (schema := data["data_schema"]) is None:
data["data_schema"] = [] # type: ignore[typeddict-item] # json result type
data = dict(result)
if (schema := result["data_schema"]) is None:
data["data_schema"] = []
else:
data["data_schema"] = voluptuous_serialize.convert(schema)

Expand Down
22 changes: 9 additions & 13 deletions homeassistant/components/config/config_entries.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,20 +137,16 @@ async def post(self, request: web.Request, entry_id: str) -> web.Response:

def _prepare_config_flow_result_json(
result: data_entry_flow.FlowResult,
prepare_result_json: Callable[
[data_entry_flow.FlowResult], data_entry_flow.FlowResult
],
) -> data_entry_flow.FlowResult:
prepare_result_json: Callable[[data_entry_flow.FlowResult], dict[str, Any]],
) -> dict[str, Any]:
"""Convert result to JSON."""
if result["type"] != data_entry_flow.FlowResultType.CREATE_ENTRY:
return prepare_result_json(result)

data = result.copy()
entry: config_entries.ConfigEntry = data["result"] # type: ignore[typeddict-item]
data = {key: val for key, val in result.items() if key not in ("data", "context")}
entry: config_entries.ConfigEntry = result["result"] # type: ignore[typeddict-item]
# We overwrite the ConfigEntry object with its json representation.
data["result"] = entry.as_json_fragment # type: ignore[typeddict-unknown-key]
data.pop("data")
data.pop("context")
data["result"] = entry.as_json_fragment
return data


Expand Down Expand Up @@ -204,8 +200,8 @@ def get_context(self, data: dict[str, Any]) -> dict[str, Any]:

def _prepare_result_json(
self, result: data_entry_flow.FlowResult
) -> data_entry_flow.FlowResult:
"""Convert result to JSON."""
) -> dict[str, Any]:
"""Convert result to JSON serializable dict."""
return _prepare_config_flow_result_json(result, super()._prepare_result_json)


Expand All @@ -229,8 +225,8 @@ async def post(self, request: web.Request, flow_id: str) -> web.Response:

def _prepare_result_json(
self, result: data_entry_flow.FlowResult
) -> data_entry_flow.FlowResult:
"""Convert result to JSON."""
) -> dict[str, Any]:
"""Convert result to JSON serializable dict."""
return _prepare_config_flow_result_json(result, super()._prepare_result_json)


Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/daikin/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@
"documentation": "https://www.home-assistant.io/integrations/daikin",
"iot_class": "local_polling",
"loggers": ["pydaikin"],
"requirements": ["pydaikin==2.15.0"],
"requirements": ["pydaikin==2.16.0"],
"zeroconf": ["_dkapi._tcp.local."]
}
103 changes: 47 additions & 56 deletions homeassistant/components/emoncms_history/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
"""Support for sending data to Emoncms."""

from datetime import timedelta
from http import HTTPStatus
from datetime import datetime, timedelta
from functools import partial
import logging

import requests
import aiohttp
from pyemoncms import EmoncmsClient
import voluptuous as vol

from homeassistant.const import (
Expand All @@ -17,9 +18,9 @@
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv, state as state_helper
from homeassistant.helpers.event import track_point_in_time
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.event import async_track_time_interval
from homeassistant.helpers.typing import ConfigType
from homeassistant.util import dt as dt_util

_LOGGER = logging.getLogger(__name__)

Expand All @@ -42,61 +43,51 @@
)


def setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the Emoncms history component."""
conf = config[DOMAIN]
whitelist = conf.get(CONF_WHITELIST)

def send_data(url, apikey, node, payload):
"""Send payload data to Emoncms."""
async def async_send_to_emoncms(
hass: HomeAssistant,
emoncms_client: EmoncmsClient,
whitelist: list[str],
node: str | int,
_: datetime,
) -> None:
"""Send data to Emoncms."""
payload_dict = {}

for entity_id in whitelist:
state = hass.states.get(entity_id)
if state is None or state.state in (STATE_UNKNOWN, "", STATE_UNAVAILABLE):
continue
try:
fullurl = f"{url}/input/post.json"
data = {"apikey": apikey, "data": payload}
parameters = {"node": node}
req = requests.post(
fullurl, params=parameters, data=data, allow_redirects=True, timeout=5
)

except requests.exceptions.RequestException:
_LOGGER.error("Error saving data '%s' to '%s'", payload, fullurl)
payload_dict[entity_id] = state_helper.state_as_number(state)
except ValueError:
continue

if payload_dict:
try:
await emoncms_client.async_input_post(data=payload_dict, node=node)
except (aiohttp.ClientError, TimeoutError) as err:
_LOGGER.warning("Network error when sending data to Emoncms: %s", err)
except ValueError as err:
_LOGGER.warning("Value error when preparing data for Emoncms: %s", err)
else:
if req.status_code != HTTPStatus.OK:
_LOGGER.error(
"Error saving data %s to %s (http status code = %d)",
payload,
fullurl,
req.status_code,
)

def update_emoncms(time):
"""Send whitelisted entities states regularly to Emoncms."""
payload_dict = {}

for entity_id in whitelist:
state = hass.states.get(entity_id)
_LOGGER.debug("Sent data to Emoncms: %s", payload_dict)

if state is None or state.state in (STATE_UNKNOWN, "", STATE_UNAVAILABLE):
continue

try:
payload_dict[entity_id] = state_helper.state_as_number(state)
except ValueError:
continue

if payload_dict:
payload = ",".join(f"{key}:{val}" for key, val in payload_dict.items())

send_data(
conf.get(CONF_URL),
conf.get(CONF_API_KEY),
str(conf.get(CONF_INPUTNODE)),
f"{{{payload}}}",
)

track_point_in_time(
hass, update_emoncms, time + timedelta(seconds=conf.get(CONF_SCAN_INTERVAL))
)
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the Emoncms history component."""
conf = config[DOMAIN]
whitelist = conf.get(CONF_WHITELIST)
input_node = str(conf.get(CONF_INPUTNODE))

emoncms_client = EmoncmsClient(
url=conf.get(CONF_URL),
api_key=conf.get(CONF_API_KEY),
session=async_get_clientsession(hass),
)
async_track_time_interval(
hass,
partial(async_send_to_emoncms, hass, emoncms_client, whitelist, input_node),
timedelta(seconds=conf.get(CONF_SCAN_INTERVAL)),
)

update_emoncms(dt_util.utcnow())
return True
5 changes: 3 additions & 2 deletions homeassistant/components/emoncms_history/manifest.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
{
"domain": "emoncms_history",
"name": "Emoncms History",
"codeowners": [],
"codeowners": ["@alexandrecuer"],
"documentation": "https://www.home-assistant.io/integrations/emoncms_history",
"iot_class": "local_polling",
"quality_scale": "legacy"
"quality_scale": "legacy",
"requirements": ["pyemoncms==0.1.2"]
}
2 changes: 1 addition & 1 deletion homeassistant/components/frontend/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
CONF_FRONTEND_REPO = "development_repo"
CONF_JS_VERSION = "javascript_version"

DEFAULT_THEME_COLOR = "#03A9F4"
DEFAULT_THEME_COLOR = "#2980b9"


DATA_PANELS: HassKey[dict[str, Panel]] = HassKey("frontend_panels")
Expand Down
4 changes: 3 additions & 1 deletion homeassistant/components/hassio/issues.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,25 +61,27 @@

UNSUPPORTED_REASONS = {
"apparmor",
"cgroup_version",
"connectivity_check",
"content_trust",
"dbus",
"dns_server",
"docker_configuration",
"docker_version",
"cgroup_version",
"job_conditions",
"lxc",
"network_manager",
"os",
"os_agent",
"os_version",
"restart_policy",
"software",
"source_mods",
"supervisor_version",
"systemd",
"systemd_journal",
"systemd_resolved",
"virtualization_image",
}
# Some unsupported reasons also mark the system as unhealthy. If the unsupported reason
# provides no additional information beyond the unhealthy one then skip that repair.
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/hassio/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@
"documentation": "https://www.home-assistant.io/integrations/hassio",
"iot_class": "local_polling",
"quality_scale": "internal",
"requirements": ["aiohasupervisor==0.3.1"],
"requirements": ["aiohasupervisor==0.3.2b0"],
"single_config_entry": true
}
25 changes: 11 additions & 14 deletions homeassistant/components/icloud/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from homeassistant.helpers import config_validation as cv
from homeassistant.util import slugify

from .account import IcloudAccount
from .account import IcloudAccount, IcloudConfigEntry
from .const import (
ATTR_ACCOUNT,
ATTR_DEVICE_NAME,
Expand Down Expand Up @@ -92,8 +92,10 @@ def lost_device(service: ServiceCall) -> None:
def update_account(service: ServiceCall) -> None:
"""Call the update function of an iCloud account."""
if (account := service.data.get(ATTR_ACCOUNT)) is None:
for account in service.hass.data[DOMAIN].values():
account.keep_alive()
# Update all accounts when no specific account is provided
entry: IcloudConfigEntry
for entry in service.hass.config_entries.async_loaded_entries(DOMAIN):
entry.runtime_data.keep_alive()
else:
_get_account(service.hass, account).keep_alive()

Expand All @@ -102,17 +104,12 @@ def _get_account(hass: HomeAssistant, account_identifier: str) -> IcloudAccount:
if account_identifier is None:
return None

icloud_account: IcloudAccount | None = hass.data[DOMAIN].get(account_identifier)
if icloud_account is None:
for account in hass.data[DOMAIN].values():
if account.username == account_identifier:
icloud_account = account

if icloud_account is None:
raise ValueError(
f"No iCloud account with username or name {account_identifier}"
)
return icloud_account
entry: IcloudConfigEntry
for entry in hass.config_entries.async_loaded_entries(DOMAIN):
if entry.runtime_data.username == account_identifier:
return entry.runtime_data

raise ValueError(f"No iCloud account with username or name {account_identifier}")


@callback
Expand Down
23 changes: 23 additions & 0 deletions homeassistant/components/imeon_inverter/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,26 @@
PLATFORMS = [
Platform.SENSOR,
]
ATTR_BATTERY_STATUS = ["charging", "discharging", "charged"]
ATTR_INVERTER_STATE = [
"unsynchronized",
"grid_consumption",
"grid_injection",
"grid_synchronised_but_not_used",
]
ATTR_TIMELINE_STATUS = [
"com_lost",
"warning_grid",
"warning_pv",
"warning_bat",
"error_ond",
"error_soft",
"error_pv",
"error_grid",
"error_bat",
"good_1",
"info_soft",
"info_ond",
"info_bat",
"info_smartlo",
]
Loading
Loading