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
2 changes: 2 additions & 0 deletions homeassistant/auth/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,8 @@ async def async_link_user(

async def async_remove_user(self, user: models.User) -> None:
"""Remove a user."""
if user.is_owner:
raise ValueError("Unable to remove the owner")
tasks = [
self.async_remove_credentials(credentials)
for credentials in user.credentials
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/besen/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,5 @@
"integration_type": "device",
"iot_class": "local_push",
"quality_scale": "bronze",
"requirements": ["besen==0.4.2"]
"requirements": ["besen==0.4.5"]
}
18 changes: 18 additions & 0 deletions homeassistant/components/config/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,24 @@ async def websocket_delete(
)
return

if user.system_generated:
connection.send_message(
websocket_api.error_message(
msg["id"],
"cannot_modify_system_generated",
"Unable to delete system generated users.",
)
)
return

if user.is_owner:
connection.send_message(
websocket_api.error_message(
msg["id"], "cannot_delete_owner", "Unable to delete the owner"
)
)
return

await hass.auth.async_remove_user(user)

connection.send_message(websocket_api.result_message(msg["id"]))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,15 @@ async def websocket_delete(
# if not new, an existing credential exists.
# Removing the credential will also remove the auth.
if not credentials.is_new:
user = await hass.auth.async_get_user_by_credentials(credentials)
if user is not None and user.is_owner:
connection.send_error(
msg["id"],
"cannot_delete_owner_credentials",
"Unable to delete the credentials of the owner",
)
return

await hass.auth.async_remove_credentials(credentials)

connection.send_result(msg["id"])
Expand Down
9 changes: 7 additions & 2 deletions homeassistant/components/glances/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,9 @@
ConfigEntryNotReady,
HomeAssistantError,
)
from homeassistant.helpers.httpx_client import get_async_client
from homeassistant.helpers.httpx_client import create_async_httpx_client

from .const import DEFAULT_TIMEOUT
from .coordinator import GlancesConfigEntry, GlancesDataUpdateCoordinator

PLATFORMS = [Platform.SENSOR]
Expand Down Expand Up @@ -65,7 +66,11 @@ async def async_unload_entry(hass: HomeAssistant, entry: GlancesConfigEntry) ->

async def get_api(hass: HomeAssistant, entry_data: dict[str, Any]) -> Glances:
"""Return the api from glances_api."""
httpx_client = get_async_client(hass, verify_ssl=entry_data[CONF_VERIFY_SSL])
# The shared httpx client cannot be used because its 5-second timeout
# is too short for slow Glances hosts.
httpx_client = create_async_httpx_client(
hass, verify_ssl=entry_data[CONF_VERIFY_SSL], timeout=DEFAULT_TIMEOUT
)
for version in (4, 3):
api = Glances(
host=entry_data[CONF_HOST],
Expand Down
1 change: 1 addition & 0 deletions homeassistant/components/glances/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@
DEFAULT_HOST = "localhost"
DEFAULT_PORT = 61208
DEFAULT_SCAN_INTERVAL = timedelta(seconds=60)
DEFAULT_TIMEOUT = 30

CPU_ICON = f"mdi:cpu-{64 if sys.maxsize > 2**32 else 32}-bit"
2 changes: 1 addition & 1 deletion homeassistant/components/glances/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ async def _async_update_data(self) -> dict[str, Any]:
except exceptions.GlancesApiAuthorizationError as err:
raise ConfigEntryAuthFailed from err
except exceptions.GlancesApiError as err:
raise UpdateFailed from err
raise UpdateFailed(str(err)) from err
# Update computed values
uptime: datetime | None = None
up_duration: timedelta | None = None
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/homematicip_cloud/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@
"integration_type": "hub",
"iot_class": "cloud_push",
"loggers": ["homematicip"],
"requirements": ["homematicip==2.16.0"]
"requirements": ["homematicip==2.17.0"]
}
15 changes: 15 additions & 0 deletions homeassistant/components/ipp/icons.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,24 @@
{
"entity": {
"sensor": {
"impressions_completed": {
"default": "mdi:file-document-outline"
},
"impressions_completed_full_color": {
"default": "mdi:file-document-outline"
},
"impressions_completed_monochrome": {
"default": "mdi:file-document-outline"
},
"marker": {
"default": "mdi:water"
},
"media_sheets_completed": {
"default": "mdi:file-document-outline"
},
"pages_completed": {
"default": "mdi:file-document-outline"
},
"printer": {
"default": "mdi:printer"
},
Expand Down
60 changes: 60 additions & 0 deletions homeassistant/components/ipp/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ class IPPSensorEntityDescription(SensorEntityDescription):

value_fn: Callable[[Printer], StateType | datetime]
attributes_fn: Callable[[Printer], dict[Any, StateType]] = lambda _: {}
exists_fn: Callable[[Printer], bool] = lambda _: True


def _get_marker_attributes_fn(
Expand Down Expand Up @@ -81,6 +82,59 @@ def _get_marker_value_fn(
),
)

PAGE_COUNT_SENSORS: tuple[IPPSensorEntityDescription, ...] = (
IPPSensorEntityDescription(
key="pages_completed",
translation_key="pages_completed",
state_class=SensorStateClass.TOTAL_INCREASING,
entity_category=EntityCategory.DIAGNOSTIC,
exists_fn=lambda printer: "pages_completed" in printer.counters.supported,
value_fn=lambda printer: printer.counters.pages_completed,
),
IPPSensorEntityDescription(
key="impressions_completed",
translation_key="impressions_completed",
state_class=SensorStateClass.TOTAL_INCREASING,
entity_category=EntityCategory.DIAGNOSTIC,
exists_fn=lambda printer: "impressions_completed" in printer.counters.supported,
value_fn=lambda printer: printer.counters.impressions_completed,
),
IPPSensorEntityDescription(
key="media_sheets_completed",
translation_key="media_sheets_completed",
state_class=SensorStateClass.TOTAL_INCREASING,
entity_category=EntityCategory.DIAGNOSTIC,
exists_fn=lambda printer: (
"media_sheets_completed" in printer.counters.supported
),
value_fn=lambda printer: printer.counters.media_sheets_completed,
),
IPPSensorEntityDescription(
key="impressions_completed_monochrome",
translation_key="impressions_completed_monochrome",
state_class=SensorStateClass.TOTAL_INCREASING,
entity_category=EntityCategory.DIAGNOSTIC,
exists_fn=lambda printer: (
"monochrome" in printer.counters.impressions_completed_col
),
value_fn=lambda printer: printer.counters.impressions_completed_col.get(
"monochrome"
),
),
IPPSensorEntityDescription(
key="impressions_completed_full_color",
translation_key="impressions_completed_full_color",
state_class=SensorStateClass.TOTAL_INCREASING,
entity_category=EntityCategory.DIAGNOSTIC,
exists_fn=lambda printer: (
"full-color" in printer.counters.impressions_completed_col
),
value_fn=lambda printer: printer.counters.impressions_completed_col.get(
"full-color"
),
),
)


async def async_setup_entry(
hass: HomeAssistant,
Expand Down Expand Up @@ -123,6 +177,12 @@ async def async_setup_entry(
)
)

sensors.extend(
IPPSensor(coordinator, description)
for description in PAGE_COUNT_SENSORS
if description.exists_fn(coordinator.data)
)

async_add_entities(sensors, True)


Expand Down
20 changes: 20 additions & 0 deletions homeassistant/components/ipp/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,26 @@
},
"entity": {
"sensor": {
"impressions_completed": {
"name": "Impressions completed",
"unit_of_measurement": "impressions"
},
"impressions_completed_full_color": {
"name": "Color impressions completed",
"unit_of_measurement": "[%key:component::ipp::entity::sensor::impressions_completed::unit_of_measurement%]"
},
"impressions_completed_monochrome": {
"name": "Monochrome impressions completed",
"unit_of_measurement": "[%key:component::ipp::entity::sensor::impressions_completed::unit_of_measurement%]"
},
"media_sheets_completed": {
"name": "Media sheets completed",
"unit_of_measurement": "sheets"
},
"pages_completed": {
"name": "Pages completed",
"unit_of_measurement": "pages"
},
"printer": {
"state": {
"idle": "[%key:common::state::idle%]",
Expand Down
48 changes: 48 additions & 0 deletions homeassistant/components/isy994/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@
KEY_ACTIONS = "actions"
KEY_STATUS = "status"

EVENT_ISY994_CONTROL = "isy994_control"

NODE_PLATFORMS = [
Platform.BINARY_SENSOR,
Platform.CLIMATE,
Expand All @@ -106,10 +108,15 @@
ROOT_NODE_PLATFORMS = [Platform.BUTTON]
VARIABLE_PLATFORMS = [Platform.NUMBER, Platform.SENSOR]

# Platforms that classify in parallel with NODE_PLATFORMS — a node placed in
# one of these still falls through to its primary platform classification.
NODE_PARALLEL_PLATFORMS = [Platform.EVENT]

# Set of all platforms used by integration
PLATFORMS = {
*NODE_PLATFORMS,
*NODE_AUX_PROP_PLATFORMS,
*NODE_PARALLEL_PLATFORMS,
*PROGRAM_PLATFORMS,
*ROOT_NODE_PLATFORMS,
*VARIABLE_PLATFORMS,
Expand Down Expand Up @@ -315,6 +322,47 @@
FILTER_INSTEON_TYPE: ["4.8", TYPE_CATEGORY_CLIMATE],
FILTER_ZWAVE_CAT: ["140"],
},
# Additive: a node matching here still gets its primary classification.
Platform.EVENT: {
FILTER_UOM: [],
FILTER_STATES: [],
FILTER_NODE_DEF_ID: [
"BallastRelayLampSwitch",
"BallastRelayLampSwitch_ADV",
"DimmerLampSwitch",
"DimmerLampSwitch_ADV",
"DimmerSwitchOnly",
"DimmerSwitchOnly_ADV",
"KeypadButton",
"KeypadButton_ADV",
"KeypadDimmer",
"KeypadDimmer_ADV",
"KeypadRelay",
"KeypadRelay_ADV",
"RelayLampOnly",
"RelayLampOnly_ADV",
"RelayLampSwitch",
"RelayLampSwitch_ADV",
"RelaySwitchOnlyPlusQuery",
"RelaySwitchOnlyPlusQuery_ADV",
],
# Type prefixes derived from observed eisy node families
# (SwitchLinc / KeypadLinc / InLineLinc / BallastLinc) — catches
# legacy non-_ADV firmware variants of the same hardware.
FILTER_INSTEON_TYPE: [
"1.14.",
"1.32.",
"1.45.",
"1.65.",
"1.66.",
"2.42.",
"2.44.",
"2.55.",
"2.57.",
"3.32.",
],
FILTER_ZWAVE_CAT: [],
},
}
NODE_AUX_FILTERS: dict[str, Platform] = {
PROP_ON_LEVEL: Platform.NUMBER,
Expand Down
4 changes: 2 additions & 2 deletions homeassistant/components/isy994/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity import Entity, EntityDescription

from .const import DOMAIN
from .const import DOMAIN, EVENT_ISY994_CONTROL


class ISYEntity(Entity):
Expand Down Expand Up @@ -82,7 +82,7 @@ def async_on_control(self, event: NodeProperty) -> None:
# New state attributes may be available, update the state.
self.async_write_ha_state()

self.hass.bus.async_fire("isy994_control", event_data)
self.hass.bus.async_fire(EVENT_ISY994_CONTROL, event_data)


class ISYNodeEntity(ISYEntity):
Expand Down
Loading
Loading