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
7 changes: 6 additions & 1 deletion homeassistant/components/labs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@
from homeassistant.loader import async_get_custom_components

from .const import DOMAIN, LABS_DATA, STORAGE_KEY, STORAGE_VERSION
from .helpers import async_is_preview_feature_enabled, async_listen
from .helpers import (
async_is_preview_feature_enabled,
async_listen,
async_update_preview_feature,
)
from .models import (
EventLabsUpdatedData,
LabPreviewFeature,
Expand All @@ -37,6 +41,7 @@
"EventLabsUpdatedData",
"async_is_preview_feature_enabled",
"async_listen",
"async_update_preview_feature",
]


Expand Down
29 changes: 29 additions & 0 deletions homeassistant/components/labs/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,32 @@ def _async_feature_updated(event: Event[EventLabsUpdatedData]) -> None:
listener()

return hass.bus.async_listen(EVENT_LABS_UPDATED, _async_feature_updated)


async def async_update_preview_feature(
hass: HomeAssistant,
domain: str,
preview_feature: str,
enabled: bool,
) -> None:
"""Update a lab preview feature state."""
labs_data = hass.data[LABS_DATA]

preview_feature_id = f"{domain}.{preview_feature}"

if preview_feature_id not in labs_data.preview_features:
raise ValueError(f"Preview feature {preview_feature_id} not found")

if enabled:
labs_data.data.preview_feature_status.add((domain, preview_feature))
else:
labs_data.data.preview_feature_status.discard((domain, preview_feature))

await labs_data.store.async_save(labs_data.data.to_store_format())

event_data: EventLabsUpdatedData = {
"domain": domain,
"preview_feature": preview_feature,
"enabled": enabled,
}
hass.bus.async_fire(EVENT_LABS_UPDATED, event_data)
22 changes: 6 additions & 16 deletions homeassistant/components/labs/websocket_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@

from homeassistant.components import websocket_api
from homeassistant.components.backup import async_get_manager
from homeassistant.const import EVENT_LABS_UPDATED
from homeassistant.core import HomeAssistant, callback

from .const import LABS_DATA
from .helpers import async_is_preview_feature_enabled, async_listen
from .models import EventLabsUpdatedData
from .helpers import (
async_is_preview_feature_enabled,
async_listen,
async_update_preview_feature,
)


@callback
Expand Down Expand Up @@ -95,19 +97,7 @@ async def websocket_update_preview_feature(
)
return

if enabled:
labs_data.data.preview_feature_status.add((domain, preview_feature))
else:
labs_data.data.preview_feature_status.discard((domain, preview_feature))

await labs_data.store.async_save(labs_data.data.to_store_format())

event_data: EventLabsUpdatedData = {
"domain": domain,
"preview_feature": preview_feature,
"enabled": enabled,
}
hass.bus.async_fire(EVENT_LABS_UPDATED, event_data)
await async_update_preview_feature(hass, domain, preview_feature, enabled)

connection.send_result(msg["id"])

Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/mastodon/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
from .services import async_setup_services
from .utils import construct_mastodon_username, create_mastodon_client

PLATFORMS: list[Platform] = [Platform.SENSOR]
PLATFORMS: list[Platform] = [Platform.BINARY_SENSOR, Platform.SENSOR]

CONFIG_SCHEMA = cv.empty_config_schema(DOMAIN)

Expand Down
128 changes: 128 additions & 0 deletions homeassistant/components/mastodon/binary_sensor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
"""Binary sensor platform for the Mastodon integration."""

from __future__ import annotations

from collections.abc import Callable
from dataclasses import dataclass
from enum import StrEnum

from mastodon.Mastodon import Account

from homeassistant.components.binary_sensor import (
BinarySensorEntity,
BinarySensorEntityDescription,
)
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback

from .coordinator import MastodonConfigEntry
from .entity import MastodonEntity

# Coordinator is used to centralize the data updates
PARALLEL_UPDATES = 0


class MastodonBinarySensor(StrEnum):
"""Mastodon binary sensors."""

BOT = "bot"
SUSPENDED = "suspended"
DISCOVERABLE = "discoverable"
LOCKED = "locked"
INDEXABLE = "indexable"
LIMITED = "limited"
MEMORIAL = "memorial"
MOVED = "moved"


@dataclass(frozen=True, kw_only=True)
class MastodonBinarySensorEntityDescription(BinarySensorEntityDescription):
"""Mastodon binary sensor description."""

is_on_fn: Callable[[Account], bool | None]


ENTITY_DESCRIPTIONS: tuple[MastodonBinarySensorEntityDescription, ...] = (
MastodonBinarySensorEntityDescription(
key=MastodonBinarySensor.BOT,
translation_key=MastodonBinarySensor.BOT,
is_on_fn=lambda account: account.bot,
entity_category=EntityCategory.DIAGNOSTIC,
),
MastodonBinarySensorEntityDescription(
key=MastodonBinarySensor.DISCOVERABLE,
translation_key=MastodonBinarySensor.DISCOVERABLE,
is_on_fn=lambda account: account.discoverable,
entity_category=EntityCategory.DIAGNOSTIC,
),
MastodonBinarySensorEntityDescription(
key=MastodonBinarySensor.LOCKED,
translation_key=MastodonBinarySensor.LOCKED,
is_on_fn=lambda account: account.locked,
entity_category=EntityCategory.DIAGNOSTIC,
),
MastodonBinarySensorEntityDescription(
key=MastodonBinarySensor.MOVED,
translation_key=MastodonBinarySensor.MOVED,
is_on_fn=lambda account: account.moved is not None,
entity_category=EntityCategory.DIAGNOSTIC,
),
MastodonBinarySensorEntityDescription(
key=MastodonBinarySensor.INDEXABLE,
translation_key=MastodonBinarySensor.INDEXABLE,
is_on_fn=lambda account: account.indexable,
entity_registry_enabled_default=False,
entity_category=EntityCategory.DIAGNOSTIC,
),
MastodonBinarySensorEntityDescription(
key=MastodonBinarySensor.LIMITED,
translation_key=MastodonBinarySensor.LIMITED,
is_on_fn=lambda account: account.limited is True,
entity_registry_enabled_default=False,
entity_category=EntityCategory.DIAGNOSTIC,
),
MastodonBinarySensorEntityDescription(
key=MastodonBinarySensor.MEMORIAL,
translation_key=MastodonBinarySensor.MEMORIAL,
is_on_fn=lambda account: account.memorial is True,
entity_registry_enabled_default=False,
entity_category=EntityCategory.DIAGNOSTIC,
),
MastodonBinarySensorEntityDescription(
key=MastodonBinarySensor.SUSPENDED,
translation_key=MastodonBinarySensor.SUSPENDED,
is_on_fn=lambda account: account.suspended is True,
entity_registry_enabled_default=False,
entity_category=EntityCategory.DIAGNOSTIC,
),
)


async def async_setup_entry(
hass: HomeAssistant,
entry: MastodonConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the binary sensor platform."""
coordinator = entry.runtime_data.coordinator

async_add_entities(
MastodonBinarySensorEntity(
coordinator=coordinator,
entity_description=entity_description,
data=entry,
)
for entity_description in ENTITY_DESCRIPTIONS
)


class MastodonBinarySensorEntity(MastodonEntity, BinarySensorEntity):
"""Mastodon binary sensor entity."""

entity_description: MastodonBinarySensorEntityDescription

@property
def is_on(self) -> bool | None:
"""Return true if the binary sensor is on."""
return self.entity_description.is_on_fn(self.coordinator.data)
13 changes: 13 additions & 0 deletions homeassistant/components/mastodon/icons.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
{
"entity": {
"binary_sensor": {
"bot": { "default": "mdi:robot" },
"discoverable": { "default": "mdi:magnify-scan" },
"indexable": { "default": "mdi:search-web" },
"limited": { "default": "mdi:volume-mute" },
"locked": {
"default": "mdi:account-lock",
"state": { "off": "mdi:account-lock-open" }
},
"memorial": { "default": "mdi:candle" },
"moved": { "default": "mdi:truck-delivery" },
"suspended": { "default": "mdi:account-off" }
},
"sensor": {
"followers": {
"default": "mdi:account-multiple"
Expand Down
10 changes: 10 additions & 0 deletions homeassistant/components/mastodon/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,16 @@
}
},
"entity": {
"binary_sensor": {
"bot": { "name": "Bot" },
"discoverable": { "name": "Discoverable" },
"indexable": { "name": "Indexable" },
"limited": { "name": "Muted" },
"locked": { "name": "Locked" },
"memorial": { "name": "Memorial" },
"moved": { "name": "Moved" },
"suspended": { "name": "Suspended" }
},
"sensor": {
"followers": {
"name": "Followers",
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/mealie/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@
"integration_type": "service",
"iot_class": "local_polling",
"quality_scale": "platinum",
"requirements": ["aiomealie==1.1.1"]
"requirements": ["aiomealie==1.2.0"]
}
4 changes: 2 additions & 2 deletions homeassistant/components/openai_conversation/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: OpenAIConfigEntry) -> bo
return True


async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_unload_entry(hass: HomeAssistant, entry: OpenAIConfigEntry) -> bool:
"""Unload OpenAI."""
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)

Expand All @@ -280,7 +280,7 @@ async def async_migrate_integration(hass: HomeAssistant) -> None:
if not any(entry.version == 1 for entry in entries):
return

api_keys_entries: dict[str, tuple[ConfigEntry, bool]] = {}
api_keys_entries: dict[str, tuple[OpenAIConfigEntry, bool]] = {}
entity_registry = er.async_get(hass)
device_registry = dr.async_get(hass)

Expand Down
3 changes: 1 addition & 2 deletions homeassistant/components/openai_conversation/ai_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
from openai.types.responses.response_output_item import ImageGenerationCall

from homeassistant.components import ai_task, conversation
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
Expand All @@ -35,7 +34,7 @@

async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: OpenAIConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up AI Task entities."""
Expand Down
9 changes: 8 additions & 1 deletion homeassistant/components/sma/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@
"data": {
"group": "[%key:component::sma::config::step::user::data::group%]",
"host": "[%key:common::config_flow::data::host%]",
"password": "[%key:common::config_flow::data::password%]",
"ssl": "[%key:common::config_flow::data::ssl%]",
"verify_ssl": "[%key:common::config_flow::data::verify_ssl%]"
},
Expand All @@ -57,5 +56,13 @@
"title": "Set up SMA Solar"
}
}
},
"selector": {
"group": {
"options": {
"installer": "Installer",
"user": "User"
}
}
}
}
33 changes: 20 additions & 13 deletions homeassistant/components/zwave_js/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -840,19 +840,26 @@ async def async_on_node_ready(self, node: ZwaveNode) -> None:
# After ensuring the node is set up in HA, we should check if the node's
# device config has changed, and if so, issue a repair registry entry for a
# possible reinterview
if not node.is_controller_node and await node.async_has_device_config_changed():
device_name = device.name_by_user or device.name or "Unnamed device"
async_create_issue(
self.hass,
DOMAIN,
f"device_config_file_changed.{device.id}",
data={"device_id": device.id, "device_name": device_name},
is_fixable=True,
is_persistent=False,
translation_key="device_config_file_changed",
translation_placeholders={"device_name": device_name},
severity=IssueSeverity.WARNING,
)
if not node.is_controller_node:
issue_id = f"device_config_file_changed.{device.id}"
if await node.async_has_device_config_changed():
device_name = device.name_by_user or device.name or "Unnamed device"
async_create_issue(
self.hass,
DOMAIN,
issue_id,
data={"device_id": device.id, "device_name": device_name},
is_fixable=True,
is_persistent=False,
translation_key="device_config_file_changed",
translation_placeholders={"device_name": device_name},
severity=IssueSeverity.WARNING,
)
else:
# Clear any existing repair issue if the device config is not considered
# changed. This can happen when the original issue was created by
# an upstream bug, or the change has been reverted.
async_delete_issue(self.hass, DOMAIN, issue_id)

async def async_handle_discovery_info(
self,
Expand Down
2 changes: 1 addition & 1 deletion requirements_all.txt

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

2 changes: 1 addition & 1 deletion requirements_test_all.txt

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

Loading
Loading