Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
c0c31d1
Bump infrared-protocols to 8.1.0 (#176625)
emontnemery Jul 21, 2026
350f308
Add support for R1700BT (2017 model) to Edifier Infrared (#176721)
emontnemery Jul 21, 2026
6d8c190
Exempt certain protocol integrations from entity limit (#176973)
emontnemery Jul 21, 2026
d33c90e
Remove confusing set intersection from device registry restore (#176964)
emontnemery Jul 21, 2026
ea70780
Remove redundant device lookups (#176909)
emontnemery Jul 21, 2026
2bbf975
Refactor Imou device callback registration (#176690)
joostlek Jul 21, 2026
af1391a
Developer Tools -> Tools: Redirect URLs to /config/tools (#175281)
timmo001 Jul 21, 2026
8c9c53a
Bump yoto-api to 4.3.2 (#176987)
piitaya Jul 21, 2026
ebcdf5a
Bump earn-e-p1 to 0.2.0 (#176990)
Miggets7 Jul 21, 2026
c553bce
Fix Tado reusing an expired device code when retrying after login tim…
niklasR Jul 21, 2026
d0e872f
Add button platform to Mikrotik (#175599)
chemelli74 Jul 21, 2026
72b68cb
Teach the search integration about composite devices (#176978)
emontnemery Jul 21, 2026
955a156
Bump pydaikin version to 2.18.3 (#176974)
valerioformato Jul 21, 2026
06693f0
Update knx-frontend to 2026.7.17.104339 (#176002)
farmio Jul 21, 2026
ca4fd1d
Migrate NextDNS integration to use subentries (#175067)
bieniu Jul 21, 2026
d58f280
Add the Open Home Foundation AI Policy (#176917)
frenck Jul 21, 2026
09aa2af
Adapt zha to new device registry API (#176958)
emontnemery Jul 21, 2026
ff9db47
Bump monzopy to 1.6.0 (#176979)
JakeMartin-ICL Jul 21, 2026
fe092ac
Move http server code into it's own module (#176968)
edenhaus Jul 21, 2026
3317a20
Sort pylint plugin README rule table by code (#176971)
Markus98 Jul 21, 2026
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
3 changes: 2 additions & 1 deletion .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@
for before merging your code.

AI tools are welcome, but contributors are responsible for *fully*
understanding the code before submitting a PR.
understanding the code before submitting a PR. Please follow our AI policy:
https://developers.home-assistant.io/docs/ai_policy
-->

- [ ] I understand the code I am submitting and can explain how it works.
Expand Down
11 changes: 10 additions & 1 deletion .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ The PR description must follow this template (from `.github/PULL_REQUEST_TEMPLAT
for before merging your code.

AI tools are welcome, but contributors are responsible for *fully*
understanding the code before submitting a PR.
understanding the code before submitting a PR. Please follow our AI policy:
https://developers.home-assistant.io/docs/ai_policy
-->

- [ ] I understand the code I am submitting and can explain how it works.
Expand Down Expand Up @@ -181,3 +182,11 @@ This repository contains the core of Home Assistant, a Python 3 based home autom
- Do not add comments that just restate the code on the following line(s) (e.g. `# Check if initialized` above `if self.initialized:`). Comments should only explain why (non-obvious constraints, surprising behavior, or workarounds), never what. Never add comments that justify a change by referencing what the code looked like before.
- Do not add section or divider comments (e.g. `# --- XYZ Triggers ---`) inside or outside of functions, since those can easily become stale and be misleading.
- When catching exceptions, try-clauses should be as small as possible, i.e. avoid wrapping large blocks of code in a try-clause, and avoid catching exceptions from functions that are not expected to raise them.

## AI policy

This project follows the [Open Home Foundation AI Policy](AI_POLICY.md).
Autonomous contributions are not accepted: a human must review, understand,
and be able to explain every change before it is submitted. Do not open
issues or pull requests autonomously, and do not post comments on behalf of
a user without their review.
8 changes: 8 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,11 @@ This repository contains the core of Home Assistant, a Python 3 based home autom
- Do not add comments that just restate the code on the following line(s) (e.g. `# Check if initialized` above `if self.initialized:`). Comments should only explain why (non-obvious constraints, surprising behavior, or workarounds), never what. Never add comments that justify a change by referencing what the code looked like before.
- Do not add section or divider comments (e.g. `# --- XYZ Triggers ---`) inside or outside of functions, since those can easily become stale and be misleading.
- When catching exceptions, try-clauses should be as small as possible, i.e. avoid wrapping large blocks of code in a try-clause, and avoid catching exceptions from functions that are not expected to raise them.

## AI policy

This project follows the [Open Home Foundation AI Policy](AI_POLICY.md).
Autonomous contributions are not accepted: a human must review, understand,
and be able to explain every change before it is submitted. Do not open
issues or pull requests autonomously, and do not post comments on behalf of
a user without their review.
45 changes: 45 additions & 0 deletions AI_POLICY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Open Home Foundation - AI Policy

We support using AI (i.e., LLMs) as tools when contributing to Open Home Foundation projects. However, you are responsible for any contributions you submit, and we are responsible for any contributions we merge and release. We hold a high bar for all contributions to our projects.

Our maintainers dedicate their time and expertise to reviewing contributions. Submitting AI-generated content that you have not personally reviewed and understood wastes that time and will not be accepted.

## Autonomous agents

**We do not allow autonomous agents to be used for contributing to our projects.** We will close any pull requests or issues that we believe were created autonomously, and may mark automated comments as spam. This includes contributions that bypass the provided issue or pull request templates.

## Communication on issues, pull requests, and code reviews

We don't mind if you use AI tools to help you write. However, do not have tools post unreviewed content on your behalf. Keep responses to the minimum needed to communicate your intent. We may hide any comments that we believe are unreviewed AI output.

If you are opening a pull request, we expect you to be able to explain the proposed changes in your own words. This includes the pull request description and responses to questions. If you use AI to help generate the pull request summary, you must review it for technical accuracy.

**Do not use AI to generate answers to questions from maintainers.** You should understand and be able to explain your own work. Using AI to improve grammar or clarity is fine, but the substance of your responses must be your own.

If you wish to include context from an interaction with AI in your comments, it must be in a quote block (e.g., using `>`) and disclosed as such. It must be accompanied by your own commentary explaining the relevance and implications of the context. Do not share long snippets.

## Non-native English speakers

We understand that AI is useful when communicating as a non-native English speaker. Using AI to improve the grammar or clarity of text you have written yourself is fine. If you are using AI to translate your comments, please ensure the translation accurately reflects your intent. Including your original text in a details block shows the effort behind your contribution, helps maintainers verify the translation if needed, and keeps the conversation readable.

## Code and documentation contributions

AI can be a helpful tool for writing code and documentation. However, due to the foundational open source nature of our projects, we require a human in the loop who understands the work produced by AI.

All contributions must be reviewed and understood by the contributor before submission. You should be able to explain every change in a pull request you submit. Pull requests that appear to be unreviewed AI output will be closed without review.

## Our use of AI

Some of our projects use AI tools to assist with code reviews, issue triaging, reporting, and other project management tasks. These tools may leave comments on pull requests or issues. As with any automated tooling, these comments are not always correct.

If an AI tool leaves a comment on your contribution, treat it as you would any other review comment. If you believe it is incorrect, say so; a brief explanation is sufficient. Maintainers always have the final say. If in doubt, ask a maintainer.

## Enforcement

Contributions that do not follow this policy will be closed. Repeated violations may result in being blocked from contributing to OHF projects. If you believe your contribution was closed in error, you are welcome to reach out to a maintainer to discuss.

---

The canonical version of this policy is published at
<https://developers.home-assistant.io/docs/ai_policy>. In case of differences,
the published version applies.
7 changes: 7 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,10 @@ If you want to suggest a new feature for Home Assistant (e.g. new integrations),
## Issue Tracker

If you want to report an issue, please [create an issue](https://github.com/home-assistant/core/issues) on GitHub.

## AI policy

This project follows the [Open Home Foundation AI Policy](AI_POLICY.md). In
short: AI tools are welcome as an aid, but you must fully understand and be
able to explain every change you submit. Contributions made by autonomous
agents are not accepted.
12 changes: 2 additions & 10 deletions homeassistant/components/acmeda/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,8 @@ async def async_remove_and_unregister(self) -> None:
if self.entity_id in ent_registry.entities:
ent_registry.async_remove(self.entity_id)

dev_registry = dr.async_get(self.hass)
device = dev_registry.async_get_device(identifiers={(DOMAIN, self.unique_id)})
if (
device is not None
and self.registry_entry is not None
and self.registry_entry.config_entry_id is not None
):
dev_registry.async_update_device(
device.id, remove_config_entry_id=self.registry_entry.config_entry_id
)
if self.device_entry:
dr.async_get(self.hass).async_remove_device(self.device_entry.id)

await self.async_remove(force_remove=True)

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 @@ -7,6 +7,6 @@
"integration_type": "device",
"iot_class": "local_polling",
"loggers": ["pydaikin"],
"requirements": ["pydaikin==2.18.2"],
"requirements": ["pydaikin==2.18.3"],
"zeroconf": ["_dkapi._tcp.local."]
}
12 changes: 2 additions & 10 deletions homeassistant/components/devolo_home_control/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,16 +103,8 @@ def _generic_message(self, message: tuple) -> None:
].name,
)
self._attr_available = state
elif message[1] == "del" and self.platform.config_entry:
device_registry = dr.async_get(self.hass)
device = device_registry.async_get_device(
identifiers={(DOMAIN, self._device_instance.uid)}
)
if device:
device_registry.async_update_device(
device.id,
remove_config_entry_id=self.platform.config_entry.entry_id,
)
elif message[1] == "del" and self.device_entry:
dr.async_get(self.hass).async_remove_device(self.device_entry.id)
else:
_LOGGER.debug("No valid message received: %s", message)

Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/earn_e_p1/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@
"integration_type": "device",
"iot_class": "local_push",
"quality_scale": "bronze",
"requirements": ["earn-e-p1==0.1.0"]
"requirements": ["earn-e-p1==0.2.0"]
}
30 changes: 29 additions & 1 deletion homeassistant/components/edifier_infrared/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
"""Edifier infrared integration for Home Assistant."""

from infrared_protocols.codes.edifier.models import MODEL_TO_COMMAND_SET, EdifierModel

from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.const import CONF_MODEL, Platform
from homeassistant.core import HomeAssistant

from .const import CONF_COMMAND_SET, CONF_INFRARED_ENTITY_ID

PLATFORMS = [Platform.BUTTON, Platform.MEDIA_PLAYER]


Expand All @@ -13,6 +17,30 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
return True


async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Migrate old config entries."""
if entry.version > 2:
return False

if entry.version == 1:
data = {**entry.data}
# The R1700BT model was renamed to R1700BT (pre-2017), and its
# command set was split from the one shared with the R1700BTs
# family, which it was incorrectly grouped with.
if data[CONF_MODEL] == "R1700BT":
data[CONF_MODEL] = EdifierModel.R1700BT_PRE_2017.value
command_set = MODEL_TO_COMMAND_SET[EdifierModel(data[CONF_MODEL])]
data[CONF_COMMAND_SET] = command_set.value
hass.config_entries.async_update_entry(
entry,
data=data,
unique_id=f"{command_set.value}_{data[CONF_INFRARED_ENTITY_ID]}",
version=2,
)

return True


async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload an Edifier IR config entry."""
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
40 changes: 33 additions & 7 deletions homeassistant/components/edifier_infrared/button.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@

from infrared_protocols.codes.edifier.models import EdifierCommandSet, EdifierModel
from infrared_protocols.codes.edifier.r1280db import EdifierR1280DBCode
from infrared_protocols.codes.edifier.r1700bt import EdifierR1700BTCode
from infrared_protocols.codes.edifier.r1700bt_2017 import EdifierR1700BT2017Code
from infrared_protocols.codes.edifier.r1700bt_pre_2017 import EdifierR1700BTPre2017Code
from infrared_protocols.codes.edifier.r1700bts import EdifierR1700BTsCode
from infrared_protocols.codes.edifier.rc20g import EdifierRC20GCode
from infrared_protocols.codes.edifier.s360db import EdifierS360DBCode
from infrared_protocols.codes.edifier.s3000pro import EdifierS3000ProCode
Expand Down Expand Up @@ -34,31 +36,55 @@ class EdifierIrButtonEntityDescription(ButtonEntityDescription):
EdifierCommandSet,
tuple[EdifierIrButtonEntityDescription, ...],
] = {
EdifierCommandSet.R1700BT: (
EdifierCommandSet.R1700BT_PRE_2017: (
EdifierIrButtonEntityDescription(
key="bluetooth",
translation_key="bluetooth",
command_code=EdifierR1700BTCode.BLUETOOTH,
command_code=EdifierR1700BTPre2017Code.BLUETOOTH,
),
EdifierIrButtonEntityDescription(
key="line",
translation_key="line",
command_code=EdifierR1700BTPre2017Code.LINE,
),
),
EdifierCommandSet.R1700BT_2017: (
EdifierIrButtonEntityDescription(
key="bluetooth",
translation_key="bluetooth",
command_code=EdifierR1700BT2017Code.BLUETOOTH,
),
EdifierIrButtonEntityDescription(
key="line",
translation_key="line",
command_code=EdifierR1700BT2017Code.LINE,
),
),
EdifierCommandSet.R1700BTS: (
EdifierIrButtonEntityDescription(
key="bluetooth",
translation_key="bluetooth",
command_code=EdifierR1700BTsCode.BLUETOOTH,
),
EdifierIrButtonEntityDescription(
key="line_1",
translation_key="line_1",
command_code=EdifierR1700BTCode.LINE_1,
command_code=EdifierR1700BTsCode.LINE_1,
),
EdifierIrButtonEntityDescription(
key="line_2",
translation_key="line_2",
command_code=EdifierR1700BTCode.LINE_2,
command_code=EdifierR1700BTsCode.LINE_2,
),
EdifierIrButtonEntityDescription(
key="fx_on",
translation_key="fx_on",
command_code=EdifierR1700BTCode.FX_ON,
command_code=EdifierR1700BTsCode.FX_ON,
),
EdifierIrButtonEntityDescription(
key="fx_off",
translation_key="fx_off",
command_code=EdifierR1700BTCode.FX_OFF,
command_code=EdifierR1700BTsCode.FX_OFF,
),
),
EdifierCommandSet.R1280DB: (
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/edifier_infrared/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
class EdifierIrConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle config flow for Edifier IR."""

VERSION = 1
VERSION = 2
MINOR_VERSION = 1

@override
Expand Down
8 changes: 6 additions & 2 deletions homeassistant/components/edifier_infrared/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

from infrared_protocols.codes.edifier.r1280db import EdifierR1280DBCode
from infrared_protocols.codes.edifier.r1280t import EdifierR1280TCode
from infrared_protocols.codes.edifier.r1700bt import EdifierR1700BTCode
from infrared_protocols.codes.edifier.r1700bt_2017 import EdifierR1700BT2017Code
from infrared_protocols.codes.edifier.r1700bt_pre_2017 import EdifierR1700BTPre2017Code
from infrared_protocols.codes.edifier.r1700bts import EdifierR1700BTsCode
from infrared_protocols.codes.edifier.rc20g import EdifierRC20GCode
from infrared_protocols.codes.edifier.s360db import EdifierS360DBCode
from infrared_protocols.codes.edifier.s3000pro import EdifierS3000ProCode
Expand All @@ -12,7 +14,9 @@
CONF_COMMAND_SET = "command_set"

type EdifierCode = (
EdifierR1700BTCode
EdifierR1700BTPre2017Code
| EdifierR1700BT2017Code
| EdifierR1700BTsCode
| EdifierR1280DBCode
| EdifierR1280TCode
| EdifierS360DBCode
Expand Down
40 changes: 29 additions & 11 deletions homeassistant/components/edifier_infrared/media_player.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
from infrared_protocols.codes.edifier.models import EdifierCommandSet, EdifierModel
from infrared_protocols.codes.edifier.r1280db import EdifierR1280DBCode
from infrared_protocols.codes.edifier.r1280t import EdifierR1280TCode
from infrared_protocols.codes.edifier.r1700bt import EdifierR1700BTCode
from infrared_protocols.codes.edifier.r1700bt_2017 import EdifierR1700BT2017Code
from infrared_protocols.codes.edifier.r1700bt_pre_2017 import EdifierR1700BTPre2017Code
from infrared_protocols.codes.edifier.r1700bts import EdifierR1700BTsCode
from infrared_protocols.codes.edifier.rc20g import EdifierRC20GCode
from infrared_protocols.codes.edifier.s360db import EdifierS360DBCode
from infrared_protocols.codes.edifier.s3000pro import EdifierS3000ProCode
Expand Down Expand Up @@ -35,18 +37,34 @@
tuple[EdifierCode | tuple[EdifierCode, ...], ...],
],
] = {
EdifierCommandSet.R1700BT: {
MediaPlayerEntityFeature.TURN_ON: (EdifierR1700BTCode.POWER,),
MediaPlayerEntityFeature.TURN_OFF: (EdifierR1700BTCode.POWER,),
EdifierCommandSet.R1700BT_PRE_2017: {
MediaPlayerEntityFeature.VOLUME_STEP: (
(EdifierR1700BTCode.VOLUME_UP,),
(EdifierR1700BTCode.VOLUME_DOWN,),
(EdifierR1700BTPre2017Code.VOLUME_UP,),
(EdifierR1700BTPre2017Code.VOLUME_DOWN,),
),
MediaPlayerEntityFeature.VOLUME_MUTE: (EdifierR1700BTCode.MUTE,),
MediaPlayerEntityFeature.PLAY: (EdifierR1700BTCode.PLAY_PAUSE,),
MediaPlayerEntityFeature.PAUSE: (EdifierR1700BTCode.PLAY_PAUSE,),
MediaPlayerEntityFeature.NEXT_TRACK: (EdifierR1700BTCode.FORWARD,),
MediaPlayerEntityFeature.PREVIOUS_TRACK: (EdifierR1700BTCode.BACK,),
MediaPlayerEntityFeature.VOLUME_MUTE: (EdifierR1700BTPre2017Code.MUTE,),
},
EdifierCommandSet.R1700BT_2017: {
MediaPlayerEntityFeature.TURN_ON: (EdifierR1700BT2017Code.POWER,),
MediaPlayerEntityFeature.TURN_OFF: (EdifierR1700BT2017Code.POWER,),
MediaPlayerEntityFeature.VOLUME_STEP: (
(EdifierR1700BT2017Code.VOLUME_UP,),
(EdifierR1700BT2017Code.VOLUME_DOWN,),
),
MediaPlayerEntityFeature.VOLUME_MUTE: (EdifierR1700BT2017Code.MUTE,),
},
EdifierCommandSet.R1700BTS: {
MediaPlayerEntityFeature.TURN_ON: (EdifierR1700BTsCode.POWER,),
MediaPlayerEntityFeature.TURN_OFF: (EdifierR1700BTsCode.POWER,),
MediaPlayerEntityFeature.VOLUME_STEP: (
(EdifierR1700BTsCode.VOLUME_UP,),
(EdifierR1700BTsCode.VOLUME_DOWN,),
),
MediaPlayerEntityFeature.VOLUME_MUTE: (EdifierR1700BTsCode.MUTE,),
MediaPlayerEntityFeature.PLAY: (EdifierR1700BTsCode.PLAY_PAUSE,),
MediaPlayerEntityFeature.PAUSE: (EdifierR1700BTsCode.PLAY_PAUSE,),
MediaPlayerEntityFeature.NEXT_TRACK: (EdifierR1700BTsCode.FORWARD,),
MediaPlayerEntityFeature.PREVIOUS_TRACK: (EdifierR1700BTsCode.BACK,),
},
EdifierCommandSet.R1280DB: {
MediaPlayerEntityFeature.TURN_ON: (EdifierR1280DBCode.POWER,),
Expand Down
3 changes: 3 additions & 0 deletions homeassistant/components/edifier_infrared/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@
"fx_on": {
"name": "FX on"
},
"line": {
"name": "Line"
},
"line_1": {
"name": "Line 1"
},
Expand Down
10 changes: 3 additions & 7 deletions homeassistant/components/eq3btsmart/climate.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.device_registry import CONNECTION_BLUETOOTH
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
import homeassistant.util.dt as dt_util

Expand Down Expand Up @@ -96,12 +95,9 @@ def _async_on_status_updated(self, data: Any) -> None:
def _async_on_device_updated(self, data: Any) -> None:
"""Handle updated device data from the thermostat."""

device_registry = dr.async_get(self.hass)
if device := device_registry.async_get_device(
connections={(CONNECTION_BLUETOOTH, self._eq3_config.mac_address)},
):
device_registry.async_update_device(
device.id,
if self.device_entry:
dr.async_get(self.hass).async_update_device(
self.device_entry.id,
sw_version=str(self._thermostat.device_data.firmware_version),
serial_number=self._thermostat.device_data.device_serial,
)
Expand Down
Loading
Loading