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
77 changes: 77 additions & 0 deletions .claude/agents/quality-scale-rule-verifier.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
---
name: quality-scale-rule-verifier
description: |
Use this agent when you need to verify that a Home Assistant integration follows a specific quality scale rule. This includes checking if the integration implements required patterns, configurations, or code structures defined by the quality scale system.

<example>
Context: The user wants to verify if an integration follows a specific quality scale rule.
user: "Check if the peblar integration follows the config-flow rule"
assistant: "I'll use the quality scale rule verifier to check if the peblar integration properly implements the config-flow rule."
<commentary>
Since the user is asking to verify a quality scale rule implementation, use the quality-scale-rule-verifier agent.
</commentary>
</example>

<example>
Context: The user is reviewing if an integration reaches a specific quality scale level.
user: "Verify that this integration reaches the bronze quality scale"
assistant: "Let me use the quality scale rule verifier to check the bronze quality scale implementation."
<commentary>
The user wants to verify the integration has reached a certain quality level, so use multiple quality-scale-rule-verifier agents to verify each bronze rule.
</commentary>
</example>
model: inherit
color: yellow
tools: Read, Bash, Grep, Glob, WebFetch
---

You are an expert Home Assistant integration quality scale auditor specializing in verifying compliance with specific quality scale rules. You have deep knowledge of Home Assistant's architecture, best practices, and the quality scale system that ensures integration consistency and reliability.

You will verify if an integration follows a specific quality scale rule by:

1. **Fetching Rule Documentation**: Retrieve the official rule documentation from:
`https://raw.githubusercontent.com/home-assistant/developers.home-assistant/refs/heads/master/docs/core/integration-quality-scale/rules/{rule_name}.md`
where `{rule_name}` is the rule identifier (e.g., 'config-flow', 'entity-unique-id', 'parallel-updates')

2. **Understanding Rule Requirements**: Parse the rule documentation to identify:
- Core requirements and mandatory implementations
- Specific code patterns or configurations required
- Common violations and anti-patterns
- Exemption criteria (when a rule might not apply)
- The quality tier this rule belongs to (Bronze, Silver, Gold, Platinum)

3. **Analyzing Integration Code**: Examine the integration's codebase at `homeassistant/components/<integration domain>` focusing on:
- `manifest.json` for quality scale declaration and configuration
- `quality_scale.yaml` for rule status (done, todo, exempt)
- Relevant Python modules based on the rule requirements
- Configuration files and service definitions as needed

4. **Verification Process**:
- Check if the rule is marked as 'done', 'todo', or 'exempt' in quality_scale.yaml
- If marked 'exempt', verify the exemption reason is valid
- If marked 'done', verify the actual implementation matches requirements
- Identify specific files and code sections that demonstrate compliance or violations
- Consider the integration's declared quality tier when applying rules
- To fetch the integration docs, use WebFetch to fetch from `https://raw.githubusercontent.com/home-assistant/home-assistant.io/refs/heads/current/source/_integrations/<integration domain>.markdown`
- To fetch information about a PyPI package, use the URL `https://pypi.org/pypi/<package>/json`

5. **Reporting Findings**: Provide a comprehensive verification report that includes:
- **Rule Summary**: Brief description of what the rule requires
- **Compliance Status**: Clear pass/fail/exempt determination
- **Evidence**: Specific code examples showing compliance or violations
- **Issues Found**: Detailed list of any non-compliance issues with file locations
- **Recommendations**: Actionable steps to achieve compliance if needed
- **Exemption Analysis**: If applicable, whether the exemption is justified

When examining code, you will:
- Look for exact implementation patterns specified in the rule
- Verify all required components are present and properly configured
- Check for common mistakes and anti-patterns
- Consider edge cases and error handling requirements
- Validate that implementations follow Home Assistant conventions

You will be thorough but focused, examining only the aspects relevant to the specific rule being verified. You will provide clear, actionable feedback that helps developers understand both what needs to be fixed and why it matters for integration quality.

If you cannot access the rule documentation or find the integration code, clearly state what information is missing and what you would need to complete the verification.

Remember that quality scale rules are cumulative - Bronze rules apply to all integrations with a quality scale, Silver rules apply to Silver+ integrations, and so on. Always consider the integration's target quality level when determining which rules should be enforced.
5 changes: 5 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,15 +55,20 @@
creating the PR. If you're unsure about any of them, don't hesitate to ask.
We're here to help! This is simply a reminder of what we are going to look
for before merging your code.

AI tools are welcome, but contributors are responsible for *fully*
understanding the code before submitting a PR.
-->

- [ ] I understand the code I am submitting and can explain how it works.
- [ ] The code change is tested and works locally.
- [ ] Local tests pass. **Your PR cannot be merged unless tests pass**
- [ ] There is no commented out code in this PR.
- [ ] I have followed the [development checklist][dev-checklist]
- [ ] I have followed the [perfect PR recommendations][perfect-pr]
- [ ] The code has been formatted using Ruff (`ruff format homeassistant tests`)
- [ ] Tests have been added to verify that the new code works.
- [ ] Any generated code has been carefully reviewed for correctness and compliance with project standards.

If user exposed functionality or configuration variables are added/changed:

Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -140,5 +140,5 @@ tmp_cache
pytest_buckets.txt

# AI tooling
.claude
.claude/settings.local.json

2 changes: 1 addition & 1 deletion homeassistant/components/bthome/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,5 @@
"dependencies": ["bluetooth_adapters"],
"documentation": "https://www.home-assistant.io/integrations/bthome",
"iot_class": "local_push",
"requirements": ["bthome-ble==3.13.1"]
"requirements": ["bthome-ble==3.14.2"]
}
10 changes: 10 additions & 0 deletions homeassistant/components/bthome/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
DEGREE,
LIGHT_LUX,
PERCENTAGE,
REVOLUTIONS_PER_MINUTE,
SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
EntityCategory,
UnitOfConductivity,
Expand Down Expand Up @@ -269,6 +270,15 @@
native_unit_of_measurement=DEGREE,
state_class=SensorStateClass.MEASUREMENT,
),
# Rotational speed (rpm)
(
BTHomeExtendedSensorDeviceClass.ROTATIONAL_SPEED,
Units.REVOLUTIONS_PER_MINUTE,
): SensorEntityDescription(
key=f"{BTHomeExtendedSensorDeviceClass.ROTATIONAL_SPEED}_{Units.REVOLUTIONS_PER_MINUTE}",
native_unit_of_measurement=REVOLUTIONS_PER_MINUTE,
state_class=SensorStateClass.MEASUREMENT,
),
# Signal Strength (RSSI) (dB)
(
BTHomeSensorDeviceClass.SIGNAL_STRENGTH,
Expand Down
88 changes: 85 additions & 3 deletions homeassistant/components/elkm1/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,14 @@ def _make_url_from_data(data: dict[str, str]) -> str:
return f"{protocol}{address}"


def _get_protocol_from_url(url: str) -> str:
"""Get protocol from URL. Returns the configured protocol from URL or the default secure protocol."""
return next(
(k for k, v in PROTOCOL_MAP.items() if url.startswith(v)),
DEFAULT_SECURE_PROTOCOL,
)


def _placeholders_from_device(device: ElkSystem) -> dict[str, str]:
return {
"mac_address": _short_mac(device.mac_address),
Expand Down Expand Up @@ -205,6 +213,78 @@ async def async_step_discovery_confirm(
)
return await self.async_step_discovered_connection()

async def async_step_reconfigure(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle reconfiguration of the integration."""
errors: dict[str, str] = {}
reconfigure_entry = self._get_reconfigure_entry()
existing_data = reconfigure_entry.data

if user_input is not None:
validate_input_data = dict(user_input)
validate_input_data[CONF_PREFIX] = existing_data.get(CONF_PREFIX, "")

try:
info = await validate_input(
validate_input_data, reconfigure_entry.unique_id
)
except TimeoutError:
errors["base"] = "cannot_connect"
except InvalidAuth:
errors[CONF_PASSWORD] = "invalid_auth"
except Exception:
_LOGGER.exception("Unexpected exception during reconfiguration")
errors["base"] = "unknown"
else:
# Discover the device at the provided address to obtain its MAC (unique_id)
device = await async_discover_device(
self.hass, validate_input_data[CONF_ADDRESS]
)
if device is not None and device.mac_address:
await self.async_set_unique_id(dr.format_mac(device.mac_address))
self._abort_if_unique_id_mismatch() # aborts if user tried to switch devices
else:
# If we cannot confirm identity, keep existing behavior (don't block reconfigure)
await self.async_set_unique_id(reconfigure_entry.unique_id)

return self.async_update_reload_and_abort(
reconfigure_entry,
data_updates={
**reconfigure_entry.data,
CONF_HOST: info[CONF_HOST],
CONF_USERNAME: validate_input_data[CONF_USERNAME],
CONF_PASSWORD: validate_input_data[CONF_PASSWORD],
CONF_PREFIX: info[CONF_PREFIX],
},
reason="reconfigure_successful",
)

return self.async_show_form(
step_id="reconfigure",
data_schema=vol.Schema(
{
vol.Optional(
CONF_USERNAME,
default=existing_data.get(CONF_USERNAME, ""),
): str,
vol.Optional(
CONF_PASSWORD,
default="",
): str,
vol.Required(
CONF_ADDRESS,
default=hostname_from_url(existing_data[CONF_HOST]),
): str,
vol.Required(
CONF_PROTOCOL,
default=_get_protocol_from_url(existing_data[CONF_HOST]),
): vol.In(ALL_PROTOCOLS),
}
),
errors=errors,
)

async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
Expand Down Expand Up @@ -249,12 +329,14 @@ async def _async_create_or_error(

try:
info = await validate_input(user_input, self.unique_id)
except TimeoutError:
except TimeoutError as ex:
_LOGGER.debug("Connection timed out: %s", ex)
return {"base": "cannot_connect"}, None
except InvalidAuth:
except InvalidAuth as ex:
_LOGGER.debug("Invalid auth for %s: %s", user_input.get(CONF_HOST), ex)
return {CONF_PASSWORD: "invalid_auth"}, None
except Exception:
_LOGGER.exception("Unexpected exception")
_LOGGER.exception("Unexpected error validating input")
return {"base": "unknown"}, None

if importing:
Expand Down
32 changes: 22 additions & 10 deletions homeassistant/components/elkm1/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
"address": "The IP address or domain or serial port if connecting via serial.",
"username": "[%key:common::config_flow::data::username%]",
"password": "[%key:common::config_flow::data::password%]",
"prefix": "A unique prefix (leave blank if you only have one ElkM1).",
"temperature_unit": "The temperature unit ElkM1 uses."
"prefix": "A unique prefix (leave blank if you only have one Elk-M1).",
"temperature_unit": "The temperature unit Elk-M1 uses."
}
},
"discovered_connection": {
Expand All @@ -30,6 +30,16 @@
"password": "[%key:common::config_flow::data::password%]",
"temperature_unit": "[%key:component::elkm1::config::step::manual_connection::data::temperature_unit%]"
}
},
"reconfigure": {
"title": "Reconfigure Elk-M1 Control",
"description": "[%key:component::elkm1::config::step::manual_connection::description%]",
"data": {
"protocol": "[%key:component::elkm1::config::step::manual_connection::data::protocol%]",
"address": "[%key:component::elkm1::config::step::manual_connection::data::address%]",
"username": "[%key:common::config_flow::data::username%]",
"password": "[%key:common::config_flow::data::password%]"
}
}
},
"error": {
Expand All @@ -42,8 +52,10 @@
"unknown": "[%key:common::config_flow::error::unknown%]",
"already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]",
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"already_configured": "An ElkM1 with this prefix is already configured",
"address_already_configured": "An ElkM1 with this address is already configured"
"already_configured": "An Elk-M1 with this prefix is already configured",
"address_already_configured": "An Elk-M1 with this address is already configured",
"reconfigure_successful": "Successfully reconfigured Elk-M1 integration",
"unique_id_mismatch": "Reconfigure should be used for the same device not a new one"
}
},
"services": {
Expand All @@ -69,7 +81,7 @@
},
"alarm_arm_home_instant": {
"name": "Alarm arm home instant",
"description": "Arms the ElkM1 in home instant mode.",
"description": "Arms the Elk-M1 in home instant mode.",
"fields": {
"code": {
"name": "Code",
Expand All @@ -79,7 +91,7 @@
},
"alarm_arm_night_instant": {
"name": "Alarm arm night instant",
"description": "Arms the ElkM1 in night instant mode.",
"description": "Arms the Elk-M1 in night instant mode.",
"fields": {
"code": {
"name": "Code",
Expand All @@ -89,7 +101,7 @@
},
"alarm_arm_vacation": {
"name": "Alarm arm vacation",
"description": "Arms the ElkM1 in vacation mode.",
"description": "Arms the Elk-M1 in vacation mode.",
"fields": {
"code": {
"name": "Code",
Expand All @@ -99,7 +111,7 @@
},
"alarm_display_message": {
"name": "Alarm display message",
"description": "Displays a message on all of the ElkM1 keypads for an area.",
"description": "Displays a message on all of the Elk-M1 keypads for an area.",
"fields": {
"clear": {
"name": "Clear",
Expand Down Expand Up @@ -135,7 +147,7 @@
},
"speak_phrase": {
"name": "Speak phrase",
"description": "Speaks a phrase. See list of phrases in ElkM1 ASCII Protocol documentation.",
"description": "Speaks a phrase. See list of phrases in Elk-M1 ASCII Protocol documentation.",
"fields": {
"number": {
"name": "Phrase number",
Expand All @@ -149,7 +161,7 @@
},
"speak_word": {
"name": "Speak word",
"description": "Speaks a word. See list of words in ElkM1 ASCII Protocol documentation.",
"description": "Speaks a word. See list of words in Elk-M1 ASCII Protocol documentation.",
"fields": {
"number": {
"name": "Word number",
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/eq3btsmart/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,5 @@
"integration_type": "device",
"iot_class": "local_polling",
"loggers": ["eq3btsmart"],
"requirements": ["eq3btsmart==2.2.0"]
"requirements": ["eq3btsmart==2.3.0"]
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"documentation": "https://www.home-assistant.io/integrations/homeassistant_hardware",
"integration_type": "system",
"requirements": [
"universal-silabs-flasher==0.0.31",
"universal-silabs-flasher==0.0.32",
"ha-silabs-firmware-client==0.2.0"
]
}
2 changes: 1 addition & 1 deletion homeassistant/components/improv_ble/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
"characteristic_missing": "The device is either already connected to Wi-Fi, or no longer able to connect to Wi-Fi. If you want to connect it to another network, try factory resetting it first.",
"no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]",
"provision_successful": "The device has successfully connected to the Wi-Fi network.",
"provision_successful_url": "The device has successfully connected to the Wi-Fi network.\n\nPlease visit {url} to finish setup.",
"provision_successful_url": "The device has successfully connected to the Wi-Fi network.\n\nPlease [click here]({url}) to finish setup.",
"unknown": "[%key:common::config_flow::error::unknown%]"
}
}
Expand Down
5 changes: 0 additions & 5 deletions homeassistant/components/matter/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,3 @@
ID_TYPE_SERIAL = "serial"

FEATUREMAP_ATTRIBUTE_ID = 65532

# vacuum entity service actions
SERVICE_GET_AREAS = "get_areas" # get SupportedAreas and SupportedMaps
SERVICE_SELECT_AREAS = "select_areas" # call SelectAreas Matter command
SERVICE_CLEAN_AREAS = "clean_areas" # call SelectAreas Matter command and start RVC
11 changes: 0 additions & 11 deletions homeassistant/components/matter/icons.json
Original file line number Diff line number Diff line change
Expand Up @@ -150,16 +150,5 @@
"default": "mdi:ev-station"
}
}
},
"services": {
"clean_areas": {
"service": "mdi:robot-vacuum"
},
"get_areas": {
"service": "mdi:map"
},
"select_areas": {
"service": "mdi:map"
}
}
}
Loading
Loading