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
136 changes: 133 additions & 3 deletions homeassistant/components/besen/sensor.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
"""Sensor platform for Besen."""

from collections.abc import Callable
from collections.abc import Callable, Mapping
from dataclasses import dataclass
from typing import override
from typing import Final, override

from besen.models import BesenData

Expand Down Expand Up @@ -30,16 +30,146 @@

PARALLEL_UPDATES = 0

ERROR_STATES: Final = {
"Relay Stick Error": "relay_stick_error",
"OFFLINE": "offline",
"CC Error": "cc_error",
"CP Error": "cp_error",
"Emergency Stop": "emergency_stop",
"Over Temperature": "over_temperature",
"Leakage Protection": "leakage_protection",
"Short Circuit": "short_circuit",
"Over Current": "over_current",
"Ungrounded": "ungrounded",
"Over Voltage": "over_voltage",
"Low Voltage": "low_voltage",
"Input Power Error": "input_power_error",
"DLB Over Current - Mains overload": "dlb_over_current",
"Diode Short Circuit": "diode_short_circuit",
"RTC Failure": "rtc_failure",
"Flash Memory Failure": "flash_memory_failure",
"EEPROM Failure": "eeprom_failure",
"Metering Module Failure": "metering_module_failure",
"No Error": "no_error",
}

CHARGING_STATES: Final = {
"Start": "start",
"Finish Charging": "finish_charging",
"Waiting": "waiting",
"Finished": "finished",
"Cancel": "canceled",
"Connect": "connect",
"Fault": "fault",
}

CHARGING_MESSAGES: Final = {
"EV is connected, please press start": "ev_connected_press_start",
"Charging": "charging",
"Charging has started, waiting for EV.": "waiting_for_ev",
"Charging completed": "charging_completed",
"Charging reservation.": "charging_reservation",
"The plug is not connected, please start charging after connecting.": (
"plug_not_connected"
),
"See Error State": "see_error_state",
"Wait for the swipe to start": "waiting_for_swipe",
"Wait for the button to activate": "waiting_for_button",
}

PLUG_STATES: Final = {
"Disconnected": "disconnected",
"Connected Unlocked": "connected_unlocked",
"Connected Locked": "connected_locked",
}

OUTPUT_STATES: Final = {
"Charging": "charging",
"Idle": "idle",
}

CURRENT_STATES: Final = {
"Fault": "fault",
"Charging Fault 1": "charging_fault_1",
"Charging Fault 2": "charging_fault_2",
"Waiting for swipe": "waiting_for_swipe",
"Waiting for button": "waiting_for_button",
"Not Connected": "not_connected",
"Ready to charge": "ready_to_charge",
"Charging": "charging",
"Completed": "completed",
"Completed Full Charge": "completed_full_charge",
"Charging Reservation": "charging_reservation",
}


def _enum_state(value: str | None, states: Mapping[str, str]) -> str | None:
"""Return the stable Home Assistant value for a charger state."""

return states.get(value) if value is not None else None


@dataclass(frozen=True, kw_only=True)
class BesenSensorEntityDescription(SensorEntityDescription):
"""Describe a Besen sensor entity."""

value_fn: Callable[[BesenData], float | int | None]
value_fn: Callable[[BesenData], StateType]
three_phase_only: bool = False


SENSOR_DESCRIPTIONS: tuple[BesenSensorEntityDescription, ...] = (
BesenSensorEntityDescription(
key="charging_status",
translation_key="charging_status",
device_class=SensorDeviceClass.ENUM,
options=list(CHARGING_STATES.values()),
value_fn=lambda data: _enum_state(data.charge.charging_status, CHARGING_STATES),
),
BesenSensorEntityDescription(
key="charging_message",
translation_key="charging_message",
device_class=SensorDeviceClass.ENUM,
options=list(CHARGING_MESSAGES.values()),
value_fn=lambda data: _enum_state(
data.charge.charging_status_description, CHARGING_MESSAGES
),
),
BesenSensorEntityDescription(
key="error_state",
translation_key="error_state",
device_class=SensorDeviceClass.ENUM,
entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False,
options=list(ERROR_STATES.values()),
value_fn=lambda data: _enum_state(data.charge.error_details, ERROR_STATES),
),
BesenSensorEntityDescription(
key="plug_state",
translation_key="plug_state",
device_class=SensorDeviceClass.ENUM,
entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False,
options=list(PLUG_STATES.values()),
value_fn=lambda data: _enum_state(data.charge.plug_state, PLUG_STATES),
),
BesenSensorEntityDescription(
key="output_state",
translation_key="output_state",
device_class=SensorDeviceClass.ENUM,
entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False,
options=list(OUTPUT_STATES.values()),
value_fn=lambda data: _enum_state(data.charge.output_state, OUTPUT_STATES),
),
BesenSensorEntityDescription(
key="current_state",
translation_key="current_state",
device_class=SensorDeviceClass.ENUM,
entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False,
options=list(CURRENT_STATES.values()),
value_fn=lambda data: _enum_state(data.charge.current_state, CURRENT_STATES),
),
BesenSensorEntityDescription(
key="charging_power",
translation_key="charging_power",
Expand Down
82 changes: 82 additions & 0 deletions homeassistant/components/besen/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,74 @@
"charging_current": { "name": "Charging current" }
},
"sensor": {
"charging_message": {
"name": "Charging message",
"state": {
"charging": "[%key:common::state::charging%]",
"charging_completed": "Charging completed",
"charging_reservation": "Charging reservation",
"ev_connected_press_start": "EV is connected, press start",
"plug_not_connected": "The plug is not connected. Connect it before starting charging.",
"see_error_state": "See error state",
"waiting_for_button": "Waiting for the button to activate",
"waiting_for_ev": "Charging started, waiting for the EV",
"waiting_for_swipe": "Waiting for a card swipe"
}
},
"charging_power": { "name": "Charging power" },
"charging_status": {
"name": "Charging status",
"state": {
"canceled": "Canceled",
"connect": "Connect",
"fault": "[%key:common::state::fault%]",
"finish_charging": "Finish charging",
"finished": "Finished",
"start": "Start",
"waiting": "Waiting"
}
},
"current_state": {
"name": "Current state",
"state": {
"charging": "[%key:common::state::charging%]",
"charging_fault_1": "Charging fault 1",
"charging_fault_2": "Charging fault 2",
"charging_reservation": "Charging reservation",
"completed": "Completed",
"completed_full_charge": "Completed, fully charged",
"fault": "[%key:common::state::fault%]",
"not_connected": "Not connected",
"ready_to_charge": "Ready to charge",
"waiting_for_button": "Waiting for button",
"waiting_for_swipe": "Waiting for card swipe"
}
},
"error_state": {
"name": "Error state",
"state": {
"cc_error": "CC error",
"cp_error": "CP error",
"diode_short_circuit": "Diode short circuit",
"dlb_over_current": "DLB overcurrent, mains overload",
"eeprom_failure": "EEPROM failure",
"emergency_stop": "Emergency stop",
"flash_memory_failure": "Flash memory failure",
"input_power_error": "Input power error",
"leakage_protection": "Leakage protection",
"low_voltage": "Low voltage",
"metering_module_failure": "Metering module failure",
"no_error": "No error",
"offline": "Offline",
"over_current": "Overcurrent",
"over_temperature": "Overtemperature",
"over_voltage": "Overvoltage",
"relay_stick_error": "Relay stick error",
"rtc_failure": "RTC failure",
"short_circuit": "Short circuit",
"ungrounded": "Ungrounded"
}
},
"external_temperature": { "name": "External temperature" },
"internal_temperature": { "name": "Internal temperature" },
"l1_current": { "name": "L1 current" },
Expand All @@ -51,6 +118,21 @@
"l2_voltage": { "name": "L2 voltage" },
"l3_current": { "name": "L3 current" },
"l3_voltage": { "name": "L3 voltage" },
"output_state": {
"name": "Output state",
"state": {
"charging": "[%key:common::state::charging%]",
"idle": "[%key:common::state::idle%]"
}
},
"plug_state": {
"name": "Plug state",
"state": {
"connected_locked": "Connected, locked",
"connected_unlocked": "Connected, unlocked",
"disconnected": "[%key:common::state::disconnected%]"
}
},
"session_energy": { "name": "Session energy" },
"total_energy": { "name": "Total energy" }
},
Expand Down
54 changes: 38 additions & 16 deletions homeassistant/components/collection_image/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,21 @@

import voluptuous as vol

from homeassistant.components.image import DOMAIN as IMAGE_DOMAIN
from homeassistant.components.media_player import BrowseError, MediaClass
from homeassistant.components.media_source import async_browse_media
from homeassistant.components.media_source import URI_SCHEME, async_browse_media
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.helpers.selector import MediaSelector

from .const import CONF_MEDIA, DOMAIN

IMAGE_MEDIA_URI = f"{URI_SCHEME}{IMAGE_DOMAIN}"

STEP_USER_DATA_SCHEMA = vol.Schema(
{
vol.Required(CONF_MEDIA): MediaSelector({"accept": ["directory"]}),
vol.Required(CONF_MEDIA): MediaSelector(
{"accept": ["directory"], "multiple": True}
),
}
)

Expand All @@ -28,24 +33,41 @@ async def async_step_user(
"""Handle the initial step."""
errors: dict[str, str] = {}
placeholders: dict[str, str] = {}
found_pictures = False
title = "Unnamed collection"
if user_input is not None:
user_media = user_input[CONF_MEDIA]
try:
browse = await async_browse_media(
self.hass, user_media["media_content_id"]
)
except BrowseError as err:
errors["media"] = "failed_browse"
placeholders["error"] = str(err)
else:
if browse.children and any(
item.media_class == MediaClass.IMAGE for item in browse.children
):
user_media_list = user_input[CONF_MEDIA]
for user_media in user_media_list:
if user_media["media_content_id"] == IMAGE_MEDIA_URI:
errors["media"] = "invalid_selection"
placeholders["error"] = IMAGE_MEDIA_URI
break
try:
browse = await async_browse_media(
self.hass, user_media["media_content_id"]
)
except BrowseError as err:
errors["media"] = "failed_browse"
placeholders["error"] = str(err)
break
else:
if (
not found_pictures
and browse.children
and any(
item.media_class == MediaClass.IMAGE
for item in browse.children
)
):
found_pictures = True
if browse.title:
title = f"{browse.title} collection"
if "media" not in errors:
if found_pictures:
return self.async_create_entry(
title=f"{browse.title or 'Unnamed'} collection",
title=title,
data=user_input,
)

errors["media"] = "selected_media_no_images"

return self.async_show_form(
Expand Down
Loading
Loading