From 08dc21287a336978bfa26c5dfcbb4e26895abcdc Mon Sep 17 00:00:00 2001 From: karlowiczpl Date: Mon, 3 Feb 2025 09:08:39 +0100 Subject: [PATCH 01/12] Initialise commit --- CODEOWNERS | 2 + .../components/gryfsmart/__init__.py | 83 ++++ .../components/gryfsmart/config_flow.py | 382 ++++++++++++++++++ homeassistant/components/gryfsmart/const.py | 78 ++++ homeassistant/components/gryfsmart/entity.py | 36 ++ homeassistant/components/gryfsmart/light.py | 113 ++++++ .../components/gryfsmart/manifest.json | 13 + .../components/gryfsmart/quality_scale.yaml | 60 +++ homeassistant/components/gryfsmart/schema.py | 27 ++ .../components/gryfsmart/strings.json | 13 + homeassistant/generated/config_flows.py | 1 + homeassistant/generated/integrations.json | 6 + requirements_all.txt | 3 + requirements_test_all.txt | 3 + tests/components/gryfsmart/__init__.py | 1 + .../components/gryfsmart/test_config_flow.py | 120 ++++++ 16 files changed, 941 insertions(+) create mode 100644 homeassistant/components/gryfsmart/__init__.py create mode 100644 homeassistant/components/gryfsmart/config_flow.py create mode 100755 homeassistant/components/gryfsmart/const.py create mode 100755 homeassistant/components/gryfsmart/entity.py create mode 100755 homeassistant/components/gryfsmart/light.py create mode 100644 homeassistant/components/gryfsmart/manifest.json create mode 100644 homeassistant/components/gryfsmart/quality_scale.yaml create mode 100644 homeassistant/components/gryfsmart/schema.py create mode 100644 homeassistant/components/gryfsmart/strings.json create mode 100644 tests/components/gryfsmart/__init__.py create mode 100644 tests/components/gryfsmart/test_config_flow.py diff --git a/CODEOWNERS b/CODEOWNERS index e510eec6dfa046..67c9fbcc4e32b2 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -592,6 +592,8 @@ build.json @home-assistant/supervisor /tests/components/greeneye_monitor/ @jkeljo /homeassistant/components/group/ @home-assistant/core /tests/components/group/ @home-assistant/core +/homeassistant/components/gryfsmart/ @karlowiczpl +/tests/components/gryfsmart/ @karlowiczpl /homeassistant/components/guardian/ @bachya /tests/components/guardian/ @bachya /homeassistant/components/habitica/ @tr4nt0r diff --git a/homeassistant/components/gryfsmart/__init__.py b/homeassistant/components/gryfsmart/__init__.py new file mode 100644 index 00000000000000..ec400d08dab276 --- /dev/null +++ b/homeassistant/components/gryfsmart/__init__.py @@ -0,0 +1,83 @@ +"""The Gryf Smart integration.""" + +from __future__ import annotations + +import logging + +from pygryfsmart.api import GryfApi + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import Platform +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.helpers.discovery import async_load_platform +from homeassistant.helpers.typing import ConfigType + +from .const import CONF_API, CONF_COMMUNICATION, CONF_PORT, DOMAIN +from .schema import CONFIG_SCHEMA as SCHEMA + +CONFIG_SCHEMA = SCHEMA + +_PLATFORMS: list[Platform] = [ + Platform.LIGHT, + # Platform.BINARY_SENSOR, + # Platform.SENSOR + # Platform.CLIMATE, + # Platform.COVER, + # Platform.SWITCH, + # Platform.LOCK +] + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup( + hass: HomeAssistant, + config: ConfigType, +) -> bool: + """Set up the Gryf Smart Integration.""" + + hass.data[DOMAIN] = config.get(DOMAIN) + + if config.get(DOMAIN) is None: + return True + + _LOGGER.debug("%s", config.get(DOMAIN)) + + try: + api = GryfApi(config[DOMAIN][CONF_COMMUNICATION][CONF_PORT]) + await api.start_connection() + except ConnectionError: + raise ConfigEntryNotReady("Unable to connect with device") from ConnectionError + + hass.data[DOMAIN][CONF_API] = api + + lights_config = config.get(Platform.LIGHT, {}) + + await async_load_platform(hass, Platform.LIGHT, DOMAIN, lights_config, config) + + return True + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Config flow for Gryf Smart Integration.""" + try: + api = GryfApi(entry.data[CONF_COMMUNICATION][CONF_PORT]) + await api.start_connection() + except ConnectionError: + _LOGGER.error("Unable to connect: %s", ConnectionError) + return False + + hass.data[DOMAIN] = {} + hass.data[DOMAIN][CONF_API] = api + entry.runtime_data = api + + await hass.config_entries.async_forward_entry_setups(entry, _PLATFORMS) + + hass.data[DOMAIN][entry.entry_id] = entry.data + return True + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Unload a config entry.""" + return await hass.config_entries.async_unload_platforms(entry, _PLATFORMS) diff --git a/homeassistant/components/gryfsmart/config_flow.py b/homeassistant/components/gryfsmart/config_flow.py new file mode 100644 index 00000000000000..a7d315bb7aa763 --- /dev/null +++ b/homeassistant/components/gryfsmart/config_flow.py @@ -0,0 +1,382 @@ +"""Handle the configuration flow for the Gryf Smart integration.""" + +import logging +from types import MappingProxyType +from typing import Any +import uuid + +from pygryfsmart.rs232 import RS232Handler +from serial import SerialException +import voluptuous as vol + +from homeassistant import config_entries +from homeassistant.core import callback +from homeassistant.helpers import selector + +from .const import ( + CONF_COMMUNICATION, + CONF_DEVICES, + CONF_EXTRA, + CONF_ID, + CONF_MODULE_COUNT, + CONF_NAME, + CONF_PORT, + CONF_TYPE, + CONFIG_FLOW_MENU_OPTIONS, + DEFAULT_PORT, + DEVICE_TYPES, + DOMAIN, +) + +_LOGGER = logging.getLogger(__name__) + + +async def ping_connection(port) -> bool: + """Test connection.""" + writer = RS232Handler(port, 115200) + try: + await writer.open_connection() + await writer.close_connection() + return True + except SerialException as e: + _LOGGER.error("%s", e) + return False + else: + return True + + +class GryfSmartConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + """Gryf Smart ConfigFlow.""" + + VERSION = 1 + CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_PUSH + + def __init__(self) -> None: + """Initialize Gryf Smart ConfigFlow.""" + super().__init__() + self._config_data: dict[str, Any] = {} + self._config_data[CONF_DEVICES] = [] + self._current_device: dict[str, Any] + self._edit_index: int | None = None + + async def async_step_user( + self, user_input: dict[str, Any] | None = None + ) -> config_entries.ConfigFlowResult: + """First config flow step, selecting communication parameters.""" + + errors = {} + + if user_input: + if not await ping_connection(user_input.get(CONF_PORT)): + errors[CONF_PORT] = "Unable to connect" + elif not user_input.get(CONF_PORT, "").startswith("/dev/"): + errors[CONF_PORT] = "invalid_port" + else: + self._config_data = { + CONF_COMMUNICATION: {}, + CONF_DEVICES: [], + } + + self._config_data[CONF_COMMUNICATION][CONF_PORT] = user_input[CONF_PORT] + self._config_data[CONF_COMMUNICATION][CONF_MODULE_COUNT] = user_input[ + CONF_MODULE_COUNT + ] + + return await self.async_step_device_menu() + + await self.async_set_unique_id(str(uuid.uuid4())) + self._abort_if_unique_id_configured() + + return self.async_show_form( + step_id="user", + data_schema=vol.Schema( + { + vol.Required(CONF_PORT, default=DEFAULT_PORT): str, + vol.Required(CONF_MODULE_COUNT, default=1): int, + } + ), + errors=errors, + ) + + async def async_step_device_menu( + self, user_input: dict[str, Any] | None = None + ) -> config_entries.ConfigFlowResult: + """Show menu step.""" + + return self.async_show_menu( + step_id="device_menu", + menu_options=CONFIG_FLOW_MENU_OPTIONS, + ) + + async def async_step_add_device( + self, user_input: dict[str, Any] | None = None + ) -> config_entries.ConfigFlowResult: + """Add new device.""" + if user_input: + new_device = { + CONF_TYPE: user_input[CONF_TYPE], + CONF_NAME: user_input[CONF_NAME], + CONF_ID: user_input[CONF_ID], + CONF_EXTRA: user_input.get(CONF_EXTRA), + } + self._config_data[CONF_DEVICES].append(new_device) + return await self.async_step_device_menu() + + return self.async_show_form( + step_id="add_device", + data_schema=vol.Schema( + { + vol.Required(CONF_TYPE): vol.In(DEVICE_TYPES), + vol.Required(CONF_NAME): str, + vol.Required(CONF_ID): int, + vol.Optional(CONF_EXTRA): int, + } + ), + ) + + async def async_step_edit_device( + self, user_input: dict[str, Any] | None = None + ) -> config_entries.ConfigFlowResult: + """Select device to edit.""" + if not self._config_data[CONF_DEVICES]: + return await self.async_step_device_menu() + + if user_input: + self._edit_index = int(user_input["device_index"]) + self._current_device = self._config_data[CONF_DEVICES][ + self._edit_index + ].copy() + return await self.async_step_edit_device_details() + + devices = [ + selector.SelectOptionDict( + value=str(idx), label=f"{dev[CONF_NAME]} (ID: {dev[CONF_ID]})" + ) + for idx, dev in enumerate(self._config_data[CONF_DEVICES]) + ] + + return self.async_show_form( + step_id="edit_device", + data_schema=vol.Schema( + { + vol.Required("device_index"): selector.SelectSelector( + selector.SelectSelectorConfig(options=devices) + ) + } + ), + ) + + async def async_step_edit_device_details( + self, user_input: dict[str, Any] | None = None + ) -> config_entries.ConfigFlowResult: + """Edit device parameters.""" + if user_input: + self._config_data[CONF_DEVICES][self._edit_index] = user_input + self._edit_index = None + return await self.async_step_device_menu() + + return self.async_show_form( + step_id="edit_device_details", + data_schema=vol.Schema( + { + vol.Required( + CONF_TYPE, default=self._current_device[CONF_TYPE] + ): vol.In(DEVICE_TYPES), + vol.Required( + CONF_NAME, default=self._current_device[CONF_NAME] + ): str, + vol.Required(CONF_ID, default=self._current_device[CONF_ID]): int, + vol.Optional( + CONF_EXTRA, default=self._current_device.get(CONF_EXTRA) + ): int, + } + ), + ) + + async def async_step_finish( + self, user_input: dict[str, Any] | None = None + ) -> config_entries.ConfigFlowResult: + """Finish the config flow.""" + return self.async_create_entry( + title=f"GryfSmart: {self._config_data[CONF_COMMUNICATION][CONF_PORT]}", + data=self._config_data, + ) + + @staticmethod + @callback + def async_get_options_flow( + config_entry: config_entries.ConfigEntry, + ) -> config_entries.OptionsFlow: + """Get the options flow for this handler.""" + + return GryfSmartOptionsFlow() + + +class GryfSmartOptionsFlow(config_entries.OptionsFlow): + """Handle the options flow for the Gryf Smart Integration.""" + + _edit_index: int + data: MappingProxyType[str, Any] + _current_device: dict + + def __init__(self) -> None: + """Initialize OptionsFlow.""" + self._edit_index = 0 + self._current_device = {} + + async def async_step_init( + self, user_input: dict[str, Any] | None = None + ) -> config_entries.ConfigFlowResult: + """Initialize the Gryf Smart options flow.""" + self.data = self.config_entry.data + _LOGGER.debug("%s", self.data) + return await self.async_step_main_menu() + + async def async_step_main_menu( + self, user_input: dict[str, Any] | None = None + ) -> config_entries.ConfigFlowResult: + """Show main options menu.""" + return self.async_show_menu( + step_id="main_menu", menu_options=CONFIG_FLOW_MENU_OPTIONS + ) + + async def async_step_add_device( + self, user_input: dict[str, Any] | None = None + ) -> config_entries.ConfigFlowResult: + """Add new device.""" + if user_input: + new_device = { + CONF_TYPE: user_input[CONF_TYPE], + CONF_NAME: user_input[CONF_NAME], + CONF_ID: user_input[CONF_ID], + CONF_EXTRA: user_input.get(CONF_EXTRA), + } + self.data["devices"].append(new_device) + return await self.async_step_main_menu() + + return self.async_show_form( + step_id="add_device", + data_schema=vol.Schema( + { + vol.Required(CONF_TYPE): vol.In(DEVICE_TYPES), + vol.Required(CONF_NAME): str, + vol.Required(CONF_ID): int, + vol.Optional(CONF_EXTRA): int, + } + ), + ) + + async def async_step_edit_device( + self, user_input: dict[str, Any] | None = None + ) -> config_entries.ConfigFlowResult: + """Select device to edit.""" + if not self.data[CONF_DEVICES]: + return await self.async_step_main_menu() + + if user_input: + self._edit_index = int(user_input["device_index"]) + self._current_device = self.data[CONF_DEVICES][self._edit_index].copy() + return await self.async_step_edit_device_details() + + devices = [ + selector.SelectOptionDict( + value=str(idx), label=f"{dev[CONF_NAME]} (ID: {dev[CONF_ID]})" + ) + for idx, dev in enumerate(self.data[CONF_DEVICES]) + ] + + return self.async_show_form( + step_id="edit_device", + data_schema=vol.Schema( + { + vol.Required("device_index"): selector.SelectSelector( + selector.SelectSelectorConfig(options=devices) + ) + } + ), + ) + + async def async_step_edit_device_details( + self, user_input: dict[str, Any] | None = None + ) -> config_entries.ConfigFlowResult: + """Edit device parameters.""" + if user_input: + self.data[CONF_DEVICES][self._edit_index] = user_input + self._edit_index = 0 + return await self.async_step_main_menu() + + if self._current_device.get(CONF_EXTRA) is not None: + return self.async_show_form( + step_id="edit_device_details", + data_schema=vol.Schema( + { + vol.Required( + CONF_TYPE, default=self._current_device[CONF_TYPE] + ): vol.In(DEVICE_TYPES), + vol.Required( + CONF_NAME, default=self._current_device[CONF_NAME] + ): str, + vol.Required( + CONF_ID, default=self._current_device[CONF_ID] + ): int, + vol.Optional( + CONF_EXTRA, default=self._current_device.get(CONF_EXTRA) + ): int, + } + ), + ) + return self.async_show_form( + step_id="edit_device_details", + data_schema=vol.Schema( + { + vol.Required( + CONF_TYPE, default=self._current_device[CONF_TYPE] + ): vol.In(DEVICE_TYPES), + vol.Required( + CONF_NAME, default=self._current_device[CONF_NAME] + ): str, + vol.Required(CONF_ID, default=self._current_device[CONF_ID]): int, + vol.Optional(CONF_EXTRA): int, + } + ), + ) + + async def async_step_finish( + self, user_input: dict[str, Any] | None = None + ) -> config_entries.ConfigFlowResult: + """Finish config flow.""" + return self.async_create_entry(data=self.data) + + async def async_step_communication( + self, user_input: dict[str, Any] | None = None + ) -> config_entries.ConfigFlowResult: + """Show communication form.""" + errors = {} + if user_input: + if not await ping_connection(user_input.get(CONF_PORT)): + errors[CONF_PORT] = "Unable to connect" + elif not user_input.get(CONF_PORT, "").startswith("/dev/"): + errors[CONF_PORT] = "invalid_port" + else: + self.data[CONF_COMMUNICATION][CONF_PORT] = user_input[CONF_PORT] + self.data[CONF_COMMUNICATION][CONF_MODULE_COUNT] = user_input[ + CONF_MODULE_COUNT + ] + + return await self.async_step_main_menu() + + return self.async_show_form( + step_id="communication", + data_schema=vol.Schema( + { + vol.Required( + CONF_PORT, default=self.data[CONF_COMMUNICATION][CONF_PORT] + ): str, + vol.Required( + CONF_MODULE_COUNT, + default=self.data[CONF_COMMUNICATION][CONF_MODULE_COUNT], + ): int, + } + ), + errors=errors, + ) diff --git a/homeassistant/components/gryfsmart/const.py b/homeassistant/components/gryfsmart/const.py new file mode 100755 index 00000000000000..ff50c8a1815928 --- /dev/null +++ b/homeassistant/components/gryfsmart/const.py @@ -0,0 +1,78 @@ +"""Define constants used throughout the Gryf Smart integration.""" + +from enum import Enum + +from homeassistant.const import Platform + +DOMAIN = "gryfsmart" + +CONF_MODULE_COUNT = "module_count" +CONF_PORT = "port" +CONF_TYPE = "type" +CONF_ID = "id" +CONF_PIN = "pin" +CONF_NAME = "name" +CONF_EXTRA = "extra parameters" +CONF_DEVICES = "devices" +CONF_RECONFIGURE = "reconfigure" +CONF_COMMUNICATION = "communication" +CONF_API = "api" + +PLATFORM_PWM = "pwm" +PLATFORM_TEMPERATURE = "temperature" + +DEFAULT_PORT = "/dev/ttyUSB0" + +CONFIG_FLOW_MENU_OPTIONS = { + "add_device": "Add Device", + "edit_device": "Edit Device", + "communication": "Setup Communication", + "finish": "Finish", +} + +DEVICE_TYPES = { + Platform.COVER: "Shutter", + Platform.LIGHT: "Lights", + Platform.SWITCH: "Outputs", + Platform.SENSOR: "Input", + Platform.BINARY_SENSOR: "Binary input", + Platform.LOCK: "Lock", + Platform.CLIMATE: "Thermostat", + PLATFORM_PWM: "PWM", + PLATFORM_TEMPERATURE: "Termometr", +} + +CONF_TILT = "tilt" +CONF_LIGHTS = "lights" +CONF_BUTTON = "buttons" +CONF_SERIAL = "port" +CONF_DOORS = "doors" +CONF_WINDOW = "windows" +CONF_TEMPERATURE = "temperature" +CONF_COVER = "covers" +CONF_TIME = "time" +CONF_LOCK = "lock" +CONF_PWM = "pwm" +CONF_CLIMATE = "climate" +CONF_HARMONOGRAM = "harmonogram" +CONF_T_ID = "t_id" +CONF_O_ID = "o_id" +CONF_T_PIN = "t_pin" +CONF_O_PIN = "o_pin" +CONF_ID_COUNT = "id" +CONF_GATE = "gate" +CONF_P_COVER = "p_covers" +CONF_IP = "ip" +CONF_STATES_UPDATE = "states_update" +CONF_HARMONOGRAM = "harmonogram" +CONF_ON = "on" +CONF_OFF = "off" +CONF_ALL = "all" + + +class OUTPUT_STATES(Enum): + """Output states enum.""" + + OFF = "2" + ON = "1" + TOGGLE = "3" diff --git a/homeassistant/components/gryfsmart/entity.py b/homeassistant/components/gryfsmart/entity.py new file mode 100755 index 00000000000000..48fb2e06782508 --- /dev/null +++ b/homeassistant/components/gryfsmart/entity.py @@ -0,0 +1,36 @@ +"""Define the base entity for the Gryf Smart integration.""" + +from __future__ import annotations + +import logging + +from pygryfsmart.api import GryfApi +from pygryfsmart.device import _GryfDevice + +from homeassistant.helpers.entity import Entity + +_LOGGER = logging.getLogger(__name__) + + +class _GryfSmartEntityBase(Entity): + """Base Entity for Gryf Smart.""" + + _attr_should_poll = False + _attr_has_entity_name = True + _api: GryfApi + _device: _GryfDevice + _attr_unique_id: str | None + + def __init__(self): + self._attr_unique_id = self._device.name + self._attr_name = self._device.name + + @property + def name(self) -> str: + return self._device.name + + # @property + # def available(self) -> bool: + # return self._device.available + + # async def async_added_to_hass(self) -> None: diff --git a/homeassistant/components/gryfsmart/light.py b/homeassistant/components/gryfsmart/light.py new file mode 100755 index 00000000000000..8f1b166124f3bc --- /dev/null +++ b/homeassistant/components/gryfsmart/light.py @@ -0,0 +1,113 @@ +"""Handle the Gryf Smart light platform functionality.""" + +import logging + +from pygryfsmart.device import _GryfDevice, _GryfOutput + +from homeassistant.components.light import LightEntity +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_TYPE, Platform +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType + +from .const import CONF_API, CONF_DEVICES, CONF_ID, CONF_NAME, DOMAIN +from .entity import _GryfSmartEntityBase + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_platform( + hass: HomeAssistant, + config: ConfigType, + async_add_entities: AddEntitiesCallback, + discovery_info: DiscoveryInfoType | None, +) -> None: + """Set up the Light platform.""" + lights = [] + + for conf in hass.data[DOMAIN].get("lights", {}): + device = _GryfOutput( + conf.get(CONF_NAME), + conf.get(CONF_ID) // 10, + conf.get(CONF_ID) % 10, + hass.data[DOMAIN]["api"], + ) + lights.append(GryfLight(device, hass)) + + async_add_entities(lights) + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Config flow for Light platform.""" + lights = [] + light_config = config_entry.data[CONF_DEVICES] + + for conf in light_config: + if conf.get(CONF_TYPE) == Platform.LIGHT: + device = _GryfOutput( + conf.get(CONF_NAME), + conf.get(CONF_ID) // 10, + conf.get(CONF_ID) % 10, + hass.data[DOMAIN][CONF_API], + ) + lights.append(GryfLight(device, hass)) + + async_add_entities(lights) + + +class GryfLightBase(LightEntity): + """Gryf Smart light base.""" + + _is_on = False + _device: _GryfDevice + _hass: HomeAssistant + + async def update(self, state): + """Update state function.""" + self._is_on = state + self.async_write_ha_state() + + def __init__(self) -> None: + """Initialize Gryf Smart Light base.""" + self._device.subscribe(self.update) + + @property + def is_on(self): + """Return state.""" + return self._is_on + + async def async_turn_on(self, **kwargs): + """Turn light on.""" + await self._device.turn_on() + + async def async_turn_off(self, **kwargs): + """Turn light off.""" + await self._device.turn_off() + + async def async_added_to_hass(self) -> None: + """Add to hass.""" + await super().async_added_to_hass() + + async def async_removed_from_hass(self) -> None: + """Remove from hass.""" + await super().async_will_remove_from_hass() + + +class GryfLight(_GryfSmartEntityBase, GryfLightBase): + """Gryf Smart Light class.""" + + def __init__( + self, + device: _GryfDevice, + hass: HomeAssistant, + ) -> None: + """Init the Gryf Light.""" + self._device = device + self._hass = hass + GryfLightBase.__init__(self) + _GryfSmartEntityBase.__init__(self) diff --git a/homeassistant/components/gryfsmart/manifest.json b/homeassistant/components/gryfsmart/manifest.json new file mode 100644 index 00000000000000..c753d78255b23d --- /dev/null +++ b/homeassistant/components/gryfsmart/manifest.json @@ -0,0 +1,13 @@ +{ + "domain": "gryfsmart", + "name": "Gryf Smart", + "codeowners": ["@karlowiczpl"], + "config_flow": true, + "documentation": "https://www.home-assistant.io/integrations/gryfsmart", + "homekit": {}, + "iot_class": "local_push", + "quality_scale": "bronze", + "requirements": ["pygryfsmart==0.1.6"], + "ssdp": [], + "zeroconf": [] +} diff --git a/homeassistant/components/gryfsmart/quality_scale.yaml b/homeassistant/components/gryfsmart/quality_scale.yaml new file mode 100644 index 00000000000000..76b8d347408bf3 --- /dev/null +++ b/homeassistant/components/gryfsmart/quality_scale.yaml @@ -0,0 +1,60 @@ +rules: + # Bronze + action-setup: done + appropriate-polling: done + brands: done + common-modules: done + config-flow-test-coverage: done + config-flow: done + dependency-transparency: done + docs-actions: done + docs-high-level-description: done + docs-installation-instructions: done + docs-removal-instructions: done + entity-event-setup: done + entity-unique-id: done + has-entity-name: done + runtime-data: done + test-before-configure: done + test-before-setup: done + unique-config-entry: done + + # Silver + action-exceptions: todo + config-entry-unloading: todo + docs-configuration-parameters: todo + docs-installation-parameters: todo + entity-unavailable: todo + integration-owner: todo + log-when-unavailable: todo + parallel-updates: todo + reauthentication-flow: todo + test-coverage: todo + + # Gold + devices: todo + diagnostics: todo + discovery-update-info: todo + discovery: todo + docs-data-update: todo + docs-examples: todo + docs-known-limitations: todo + docs-supported-devices: todo + docs-supported-functions: todo + docs-troubleshooting: todo + docs-use-cases: todo + dynamic-devices: todo + entity-category: todo + entity-device-class: todo + entity-disabled-by-default: todo + entity-translations: todo + exception-translations: todo + icon-translations: todo + reconfiguration-flow: todo + repair-issues: todo + stale-devices: todo + + # Platinum + async-dependency: todo + inject-websession: todo + strict-typing: todo diff --git a/homeassistant/components/gryfsmart/schema.py b/homeassistant/components/gryfsmart/schema.py new file mode 100644 index 00000000000000..de9ff69812ddfd --- /dev/null +++ b/homeassistant/components/gryfsmart/schema.py @@ -0,0 +1,27 @@ +"""Config schema for Gryf Smart Integration.""" + +import voluptuous as vol + +from homeassistant.helpers import config_validation as cv + +from .const import CONF_ID, CONF_MODULE_COUNT, CONF_NAME, CONF_PORT, DOMAIN + +STANDARD_SCHEMA = vol.Schema( + { + vol.Required(CONF_NAME): cv.string, + vol.Required(CONF_ID): cv.positive_int, + } +) + +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Optional("light"): vol.All(cv.ensure_list, [STANDARD_SCHEMA]), + vol.Required(CONF_PORT): cv.string, + vol.Optional(CONF_MODULE_COUNT): cv.positive_int, + } + ) + }, + extra=vol.ALLOW_EXTRA, +) diff --git a/homeassistant/components/gryfsmart/strings.json b/homeassistant/components/gryfsmart/strings.json new file mode 100644 index 00000000000000..ad8f0f41ae7b29 --- /dev/null +++ b/homeassistant/components/gryfsmart/strings.json @@ -0,0 +1,13 @@ +{ + "config": { + "step": { + "confirm": { + "description": "[%key:common::config_flow::description::confirm_setup%]" + } + }, + "abort": { + "single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]", + "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]" + } + } +} diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index d0a8e821f8d2f6..569039eadcd581 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -245,6 +245,7 @@ "gpslogger", "gree", "growatt_server", + "gryfsmart", "guardian", "habitica", "harmony", diff --git a/homeassistant/generated/integrations.json b/homeassistant/generated/integrations.json index 026eab30f8fdb1..a1d97f8e5fcc35 100644 --- a/homeassistant/generated/integrations.json +++ b/homeassistant/generated/integrations.json @@ -2450,6 +2450,12 @@ "config_flow": true, "iot_class": "cloud_polling" }, + "gryfsmart": { + "name": "Gryf Smart", + "integration_type": "hub", + "config_flow": true, + "iot_class": "local_push" + }, "gstreamer": { "name": "GStreamer", "integration_type": "hub", diff --git a/requirements_all.txt b/requirements_all.txt index fe7ea7fc5cef40..da175e8d0b0a85 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1980,6 +1980,9 @@ pyfttt==0.3 # homeassistant.components.skybeacon pygatt[GATTTOOL]==4.0.5 +# homeassistant.components.gryfsmart +pygryfsmart==0.1.6 + # homeassistant.components.gtfs pygtfs==0.1.9 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index a686bbd06330d1..33bd6b340f2c91 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1612,6 +1612,9 @@ pyfritzhome==0.6.14 # homeassistant.components.ifttt pyfttt==0.3 +# homeassistant.components.gryfsmart +pygryfsmart==0.1.6 + # homeassistant.components.hvv_departures pygti==0.9.4 diff --git a/tests/components/gryfsmart/__init__.py b/tests/components/gryfsmart/__init__.py new file mode 100644 index 00000000000000..4d856af146099c --- /dev/null +++ b/tests/components/gryfsmart/__init__.py @@ -0,0 +1 @@ +"""Tests for the Gryf Smart integration.""" diff --git a/tests/components/gryfsmart/test_config_flow.py b/tests/components/gryfsmart/test_config_flow.py new file mode 100644 index 00000000000000..bbbd30922f520c --- /dev/null +++ b/tests/components/gryfsmart/test_config_flow.py @@ -0,0 +1,120 @@ +"""Test for GryfSmartConfigFlow.""" + +from unittest.mock import MagicMock, patch + +import pytest + +from homeassistant.components.gryfsmart.config_flow import ( + GryfSmartConfigFlow, + ping_connection, +) +from homeassistant.core import HomeAssistant +from homeassistant.setup import async_setup_component + + +@pytest.fixture +def mock_ping_connection_success(): + """Mock the ping_connection to simulate successful connection.""" + with patch( + "homeassistant.components.gryfsmart.config_flow.ping_connection", + return_value=True, + ): + yield + + +@pytest.fixture +def mock_ping_connection_failure(): + """Mock the ping_connection to simulate connection failure.""" + with patch( + "homeassistant.components.gryfsmart.config_flow.ping_connection", + return_value=False, + ): + yield + + +@pytest.fixture +def mock_create_entry(): + """Mock creating a config entry.""" + with patch( + "homeassistant.components.gryfsmart.config_flow.GryfSmartConfigFlow.async_create_entry" + ) as mock: + mock.return_value = {"type": "form"} + yield mock + + +@pytest.fixture +async def config_flow(hass: HomeAssistant): + """Fixture to create an instance of the config flow.""" + + await async_setup_component(hass, "config", {}) + + config_flow = GryfSmartConfigFlow() + config_flow._edit_index = 0 + + hass.config_entries = MagicMock() + + return config_flow + + +async def test_step_user_success(config_flow, mock_ping_connection_success) -> None: + """Test the user input step for a valid configuration.""" + user_input = {"port": "/dev/ttyUSB0", "module_count": 2} + + result = await config_flow.async_step_user(user_input) + + assert result["type"] == "menu" + assert result["step_id"] == "device_menu" + + +async def test_step_add_device(config_flow) -> None: + """Test adding a new device.""" + user_input = {"type": "sensor", "name": "Sensor 1", "id": 1234, "extra": 10} + + result = await config_flow.async_step_add_device(user_input) + + assert len(config_flow._config_data["devices"]) == 1 + assert config_flow._config_data["devices"][0]["name"] == "Sensor 1" + assert result["type"] == "menu" + + +async def test_step_edit_device(config_flow) -> None: + """Test editing an existing device.""" + user_input = {"type": "sensor", "name": "Sensor 1", "id": 1234} + await config_flow.async_step_add_device(user_input) + + user_input_edit = { + "type": "sensor", + "name": "Updated Sensor 1", + "id": 5678, + "extra": 20, + } + + result = await config_flow.async_step_edit_device_details(user_input_edit) + + assert config_flow._config_data["devices"][0]["name"] == "Updated Sensor 1" + assert result["type"] == "menu" + + +async def test_step_finish(config_flow, mock_create_entry) -> None: + """Test finishing the config flow.""" + + user_input = {"port": "/dev/ttyUSB0", "module_count": 2} + await config_flow.async_step_user(user_input) + + result = await config_flow.async_step_finish() + + mock_create_entry.assert_called_once() + + assert result["type"] == "form" + + +async def test_ping_connection_success(mock_ping_connection_success) -> None: + """Test the ping_connection function for success.""" + result = await ping_connection("/dev/ttyUSB0") + assert result is True + + +async def test_ping_connection_failure(mock_ping_connection_failure) -> None: + """Test the ping_connection function for failure.""" + result = await ping_connection("/dev/invalid") + assert result is False From 5b1f6a9e613f2a89a545015fb41d580c5fd86409 Mon Sep 17 00:00:00 2001 From: karlowiczpl Date: Mon, 3 Feb 2025 18:09:47 +0100 Subject: [PATCH 02/12] Initialise commit --- .../components/gryfsmart/__init__.py | 41 ++++++----- .../components/gryfsmart/config_flow.py | 2 +- homeassistant/components/gryfsmart/const.py | 1 + homeassistant/components/gryfsmart/entity.py | 61 +++++++++++++--- homeassistant/components/gryfsmart/light.py | 73 ++++++++++--------- 5 files changed, 113 insertions(+), 65 deletions(-) diff --git a/homeassistant/components/gryfsmart/__init__.py b/homeassistant/components/gryfsmart/__init__.py index ec400d08dab276..46be0074d4c6fc 100644 --- a/homeassistant/components/gryfsmart/__init__.py +++ b/homeassistant/components/gryfsmart/__init__.py @@ -13,7 +13,7 @@ from homeassistant.helpers.discovery import async_load_platform from homeassistant.helpers.typing import ConfigType -from .const import CONF_API, CONF_COMMUNICATION, CONF_PORT, DOMAIN +from .const import CONF_API, CONF_COMMUNICATION, CONF_DEVICE_DATA, CONF_PORT, DOMAIN from .schema import CONFIG_SCHEMA as SCHEMA CONFIG_SCHEMA = SCHEMA @@ -37,47 +37,54 @@ async def async_setup( ) -> bool: """Set up the Gryf Smart Integration.""" - hass.data[DOMAIN] = config.get(DOMAIN) - if config.get(DOMAIN) is None: return True - _LOGGER.debug("%s", config.get(DOMAIN)) - try: - api = GryfApi(config[DOMAIN][CONF_COMMUNICATION][CONF_PORT]) + api = GryfApi(config[DOMAIN][CONF_PORT]) await api.start_connection() except ConnectionError: - raise ConfigEntryNotReady("Unable to connect with device") from ConnectionError + _LOGGER.error("Unable to connect: %s", ConnectionError) + return False + hass.data[DOMAIN] = config.get(DOMAIN) hass.data[DOMAIN][CONF_API] = api - lights_config = config.get(Platform.LIGHT, {}) - - await async_load_platform(hass, Platform.LIGHT, DOMAIN, lights_config, config) + await async_load_platform(hass, Platform.LIGHT, DOMAIN, None, config) return True -async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: +async def async_setup_entry( + hass: HomeAssistant, + entry: ConfigEntry, +) -> bool: """Config flow for Gryf Smart Integration.""" + try: api = GryfApi(entry.data[CONF_COMMUNICATION][CONF_PORT]) await api.start_connection() except ConnectionError: - _LOGGER.error("Unable to connect: %s", ConnectionError) - return False + raise ConfigEntryNotReady("Unable to connect with device") from ConnectionError - hass.data[DOMAIN] = {} - hass.data[DOMAIN][CONF_API] = api - entry.runtime_data = api + entry.runtime_data = {} + entry.runtime_data[CONF_API] = api + entry.runtime_data[CONF_DEVICE_DATA] = { + "identifiers": {(DOMAIN, "Gryf Smart", entry.unique_id)}, + "name": f"Gryf Smart {entry.unique_id}", + "manufacturer": "Gryf Smart", + "model": "serial", + "sw_version": "1.0.0", + "hw_version": "1.0.0", + } await hass.config_entries.async_forward_entry_setups(entry, _PLATFORMS) - hass.data[DOMAIN][entry.entry_id] = entry.data + hass.data[DOMAIN][entry.entry_id] = entry return True async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Unload a config entry.""" + return await hass.config_entries.async_unload_platforms(entry, _PLATFORMS) diff --git a/homeassistant/components/gryfsmart/config_flow.py b/homeassistant/components/gryfsmart/config_flow.py index a7d315bb7aa763..da8485b3bf854f 100644 --- a/homeassistant/components/gryfsmart/config_flow.py +++ b/homeassistant/components/gryfsmart/config_flow.py @@ -84,7 +84,7 @@ async def async_step_user( return await self.async_step_device_menu() - await self.async_set_unique_id(str(uuid.uuid4())) + await self.async_set_unique_id(str(uuid.uuid4())[:8]) self._abort_if_unique_id_configured() return self.async_show_form( diff --git a/homeassistant/components/gryfsmart/const.py b/homeassistant/components/gryfsmart/const.py index ff50c8a1815928..a7ec8a2ee4ff03 100755 --- a/homeassistant/components/gryfsmart/const.py +++ b/homeassistant/components/gryfsmart/const.py @@ -17,6 +17,7 @@ CONF_RECONFIGURE = "reconfigure" CONF_COMMUNICATION = "communication" CONF_API = "api" +CONF_DEVICE_DATA = "device_data" PLATFORM_PWM = "pwm" PLATFORM_TEMPERATURE = "temperature" diff --git a/homeassistant/components/gryfsmart/entity.py b/homeassistant/components/gryfsmart/entity.py index 48fb2e06782508..d45ccb0de3da50 100755 --- a/homeassistant/components/gryfsmart/entity.py +++ b/homeassistant/components/gryfsmart/entity.py @@ -2,35 +2,72 @@ from __future__ import annotations -import logging - from pygryfsmart.api import GryfApi from pygryfsmart.device import _GryfDevice +from homeassistant.config_entries import ConfigEntry +from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.entity import Entity -_LOGGER = logging.getLogger(__name__) +from .const import CONF_DEVICE_DATA class _GryfSmartEntityBase(Entity): """Base Entity for Gryf Smart.""" _attr_should_poll = False - _attr_has_entity_name = True + _attr_entity_registry_enabled_default = True + _api: GryfApi _device: _GryfDevice _attr_unique_id: str | None - def __init__(self): - self._attr_unique_id = self._device.name - self._attr_name = self._device.name - @property def name(self) -> str: return self._device.name - # @property - # def available(self) -> bool: - # return self._device.available - # async def async_added_to_hass(self) -> None: +class GryfConfigFlowEntity(_GryfSmartEntityBase): + """Gryf Config flow entity class.""" + + _attr_has_entity_name = True + _device: _GryfDevice + _config_entry: ConfigEntry + + def __init__( + self, + config_entry: ConfigEntry, + device: _GryfDevice, + ) -> None: + """Init Gryf config flow entity.""" + + self._device = device + self._config_entry = config_entry + super().__init__() + + @property + def device_info(self) -> DeviceInfo | None: + """Return device info.""" + return self._config_entry.runtime_data[CONF_DEVICE_DATA] + + @property + def unique_id(self) -> str | None: + """Return unique_id.""" + return f"{self._device.name} {self._config_entry.unique_id}" + + +class GryfYamlEntity(_GryfSmartEntityBase): + """Gryf yaml entity class.""" + + _attr_has_entity_name = True + _device: _GryfDevice + + def __init__(self, device: _GryfDevice) -> None: + """Init Gryf yaml entity.""" + super().__init__() + self._device = device + + @property + def unique_id(self) -> str | None: + """Return unique id.""" + return self._device.name diff --git a/homeassistant/components/gryfsmart/light.py b/homeassistant/components/gryfsmart/light.py index 8f1b166124f3bc..dba20aee0660c0 100755 --- a/homeassistant/components/gryfsmart/light.py +++ b/homeassistant/components/gryfsmart/light.py @@ -1,6 +1,7 @@ """Handle the Gryf Smart light platform functionality.""" import logging +from typing import Any from pygryfsmart.device import _GryfDevice, _GryfOutput @@ -12,7 +13,7 @@ from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from .const import CONF_API, CONF_DEVICES, CONF_ID, CONF_NAME, DOMAIN -from .entity import _GryfSmartEntityBase +from .entity import GryfConfigFlowEntity, GryfYamlEntity _LOGGER = logging.getLogger(__name__) @@ -24,16 +25,17 @@ async def async_setup_platform( discovery_info: DiscoveryInfoType | None, ) -> None: """Set up the Light platform.""" + lights = [] - for conf in hass.data[DOMAIN].get("lights", {}): + for conf in hass.data[DOMAIN].get(Platform.LIGHT, {}): device = _GryfOutput( conf.get(CONF_NAME), conf.get(CONF_ID) // 10, conf.get(CONF_ID) % 10, - hass.data[DOMAIN]["api"], + hass.data[DOMAIN][CONF_API], ) - lights.append(GryfLight(device, hass)) + lights.append(GryfYamlLight(device)) async_add_entities(lights) @@ -53,61 +55,62 @@ async def async_setup_entry( conf.get(CONF_NAME), conf.get(CONF_ID) // 10, conf.get(CONF_ID) % 10, - hass.data[DOMAIN][CONF_API], + config_entry.runtime_data[CONF_API], ) - lights.append(GryfLight(device, hass)) + lights.append(GryfConfigFlowLight(device, config_entry)) async_add_entities(lights) class GryfLightBase(LightEntity): - """Gryf Smart light base.""" + """Gryf Light entity base.""" _is_on = False _device: _GryfDevice - _hass: HomeAssistant - - async def update(self, state): - """Update state function.""" - self._is_on = state - self.async_write_ha_state() - - def __init__(self) -> None: - """Initialize Gryf Smart Light base.""" - self._device.subscribe(self.update) @property def is_on(self): - """Return state.""" + """Return is on.""" + return self._is_on - async def async_turn_on(self, **kwargs): + async def async_update(self, is_on): + """Update state.""" + + self._is_on = is_on + self.async_write_ha_state() + + async def async_turn_on(self, **kwargs: Any) -> None: """Turn light on.""" + await self._device.turn_on() - async def async_turn_off(self, **kwargs): + async def async_turn_off(self, **kwargs: Any) -> None: """Turn light off.""" - await self._device.turn_off() - - async def async_added_to_hass(self) -> None: - """Add to hass.""" - await super().async_added_to_hass() - async def async_removed_from_hass(self) -> None: - """Remove from hass.""" - await super().async_will_remove_from_hass() + await self._device.turn_off() -class GryfLight(_GryfSmartEntityBase, GryfLightBase): - """Gryf Smart Light class.""" +class GryfConfigFlowLight(GryfConfigFlowEntity, GryfLightBase): + """Gryf Smart config flow Light class.""" def __init__( self, device: _GryfDevice, - hass: HomeAssistant, + config_entry: ConfigEntry, ) -> None: """Init the Gryf Light.""" - self._device = device - self._hass = hass - GryfLightBase.__init__(self) - _GryfSmartEntityBase.__init__(self) + + self._config_entry = config_entry + super().__init__(config_entry, device) + self._device.subscribe(self.async_update) + + +class GryfYamlLight(GryfYamlEntity, GryfLightBase): + """Gryf Smart Yaml Light class.""" + + def __init__(self, device: _GryfDevice) -> None: + """Init the Gryf Light.""" + + super().__init__(device) + device.subscribe(self.async_update) From 068d302d2f25423059eaeaa1170717fd8bf09c57 Mon Sep 17 00:00:00 2001 From: karlowiczpl Date: Mon, 3 Feb 2025 19:38:57 +0100 Subject: [PATCH 03/12] Initialise commit --- homeassistant/components/gryfsmart/light.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/gryfsmart/light.py b/homeassistant/components/gryfsmart/light.py index dba20aee0660c0..a6b4f5bcb97a4c 100755 --- a/homeassistant/components/gryfsmart/light.py +++ b/homeassistant/components/gryfsmart/light.py @@ -5,7 +5,7 @@ from pygryfsmart.device import _GryfDevice, _GryfOutput -from homeassistant.components.light import LightEntity +from homeassistant.components.light import ColorMode, LightEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_TYPE, Platform from homeassistant.core import HomeAssistant @@ -67,6 +67,7 @@ class GryfLightBase(LightEntity): _is_on = False _device: _GryfDevice + _attr_supported_color_modes = {ColorMode.ONOFF} @property def is_on(self): From c611933d150cf3a78f8e36a35f36a59ef755b99b Mon Sep 17 00:00:00 2001 From: karlowiczpl Date: Mon, 3 Feb 2025 21:34:52 +0100 Subject: [PATCH 04/12] Initialise commit --- .../components/gryfsmart/__init__.py | 1 - homeassistant/components/gryfsmart/light.py | 93 ++++++++++++++++++- .../components/gryfsmart/manifest.json | 2 +- homeassistant/components/gryfsmart/schema.py | 3 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 6 files changed, 96 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/gryfsmart/__init__.py b/homeassistant/components/gryfsmart/__init__.py index 46be0074d4c6fc..08535803ae9a6e 100644 --- a/homeassistant/components/gryfsmart/__init__.py +++ b/homeassistant/components/gryfsmart/__init__.py @@ -80,7 +80,6 @@ async def async_setup_entry( await hass.config_entries.async_forward_entry_setups(entry, _PLATFORMS) - hass.data[DOMAIN][entry.entry_id] = entry return True diff --git a/homeassistant/components/gryfsmart/light.py b/homeassistant/components/gryfsmart/light.py index a6b4f5bcb97a4c..eeaf03acc3139a 100755 --- a/homeassistant/components/gryfsmart/light.py +++ b/homeassistant/components/gryfsmart/light.py @@ -3,7 +3,7 @@ import logging from typing import Any -from pygryfsmart.device import _GryfDevice, _GryfOutput +from pygryfsmart.device import _GryfDevice, _GryfOutput, _GryfPwm from homeassistant.components.light import ColorMode, LightEntity from homeassistant.config_entries import ConfigEntry @@ -12,7 +12,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType -from .const import CONF_API, CONF_DEVICES, CONF_ID, CONF_NAME, DOMAIN +from .const import CONF_API, CONF_DEVICES, CONF_ID, CONF_NAME, CONF_PWM, DOMAIN from .entity import GryfConfigFlowEntity, GryfYamlEntity _LOGGER = logging.getLogger(__name__) @@ -27,6 +27,7 @@ async def async_setup_platform( """Set up the Light platform.""" lights = [] + pwm = [] for conf in hass.data[DOMAIN].get(Platform.LIGHT, {}): device = _GryfOutput( @@ -37,7 +38,17 @@ async def async_setup_platform( ) lights.append(GryfYamlLight(device)) + for conf in hass.data[DOMAIN].get(CONF_PWM, {}): + device = _GryfPwm( + conf.get(CONF_NAME), + conf.get(CONF_ID) // 10, + conf.get(CONF_ID) % 10, + hass.data[DOMAIN][CONF_API], + ) + pwm.append(GryfYamlPwm(device)) + async_add_entities(lights) + async_add_entities(pwm) async def async_setup_entry( @@ -47,6 +58,7 @@ async def async_setup_entry( ) -> None: """Config flow for Light platform.""" lights = [] + pwm = [] light_config = config_entry.data[CONF_DEVICES] for conf in light_config: @@ -58,8 +70,17 @@ async def async_setup_entry( config_entry.runtime_data[CONF_API], ) lights.append(GryfConfigFlowLight(device, config_entry)) + elif conf.get(CONF_TYPE) == CONF_PWM: + device = _GryfPwm( + conf.get(CONF_NAME), + conf.get(CONF_ID) // 10, + conf.get(CONF_ID) % 10, + config_entry.runtime_data[CONF_API], + ) + pwm.append(GryfConfigFlowPwm(device, config_entry)) async_add_entities(lights) + async_add_entities(pwm) class GryfLightBase(LightEntity): @@ -67,6 +88,7 @@ class GryfLightBase(LightEntity): _is_on = False _device: _GryfDevice + _attr_color_mode = ColorMode.ONOFF _attr_supported_color_modes = {ColorMode.ONOFF} @property @@ -115,3 +137,70 @@ def __init__(self, device: _GryfDevice) -> None: super().__init__(device) device.subscribe(self.async_update) + + +class GryfPwmBase(LightEntity): + """Gryf Pwm entity base.""" + + _is_on = False + _brightness = 0 + _device: _GryfDevice + _attr_color_mode = ColorMode.BRIGHTNESS + _attr_supported_color_modes = {ColorMode.BRIGHTNESS} + + @property + def brightness(self) -> int | None: + """The brightness property.""" + return self._brightness + + @property + def is_on(self) -> bool: + """The is_on property.""" + + return self._is_on + + async def async_update(self, brightness): + """Update state.""" + + self._is_on = bool(brightness) + self._brightness = brightness + self.async_write_ha_state() + + async def async_turn_on(self, **kwargs: Any) -> None: + """Turn pwm on.""" + brightness = kwargs.get("brightness") + if brightness is not None: + percentage_brightness = int((brightness / 255) * 100) + await self._device.set_level(percentage_brightness) + + await self._device.turn_on() + + async def async_turn_off(self, **kwargs: Any) -> None: + """Turn pwm off.""" + + await self._device.turn_off() + + +class GryfConfigFlowPwm(GryfConfigFlowEntity, GryfPwmBase): + """Gryf Smart config flow Light class.""" + + def __init__( + self, + device: _GryfDevice, + config_entry: ConfigEntry, + ) -> None: + """Init the Gryf Light.""" + + self._config_entry = config_entry + super().__init__(config_entry, device) + self._device.subscribe(self.async_update) + + +class GryfYamlPwm(GryfYamlEntity, GryfPwmBase): + """Gryf Smart Yaml Light class.""" + + def __init__(self, device: _GryfDevice) -> None: + """Init the Gryf Light.""" + + super().__init__(device) + device.subscribe(self.async_update) diff --git a/homeassistant/components/gryfsmart/manifest.json b/homeassistant/components/gryfsmart/manifest.json index c753d78255b23d..696dac1e5dc99f 100644 --- a/homeassistant/components/gryfsmart/manifest.json +++ b/homeassistant/components/gryfsmart/manifest.json @@ -7,7 +7,7 @@ "homekit": {}, "iot_class": "local_push", "quality_scale": "bronze", - "requirements": ["pygryfsmart==0.1.6"], + "requirements": ["pygryfsmart==0.1.7"], "ssdp": [], "zeroconf": [] } diff --git a/homeassistant/components/gryfsmart/schema.py b/homeassistant/components/gryfsmart/schema.py index de9ff69812ddfd..78b0b3e361deaf 100644 --- a/homeassistant/components/gryfsmart/schema.py +++ b/homeassistant/components/gryfsmart/schema.py @@ -4,7 +4,7 @@ from homeassistant.helpers import config_validation as cv -from .const import CONF_ID, CONF_MODULE_COUNT, CONF_NAME, CONF_PORT, DOMAIN +from .const import CONF_ID, CONF_MODULE_COUNT, CONF_NAME, CONF_PORT, CONF_PWM, DOMAIN STANDARD_SCHEMA = vol.Schema( { @@ -20,6 +20,7 @@ vol.Optional("light"): vol.All(cv.ensure_list, [STANDARD_SCHEMA]), vol.Required(CONF_PORT): cv.string, vol.Optional(CONF_MODULE_COUNT): cv.positive_int, + vol.Optional(CONF_PWM): vol.All(cv.ensure_list, [STANDARD_SCHEMA]), } ) }, diff --git a/requirements_all.txt b/requirements_all.txt index da175e8d0b0a85..7df77a06de4515 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1981,7 +1981,7 @@ pyfttt==0.3 pygatt[GATTTOOL]==4.0.5 # homeassistant.components.gryfsmart -pygryfsmart==0.1.6 +pygryfsmart==0.1.7 # homeassistant.components.gtfs pygtfs==0.1.9 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 33bd6b340f2c91..1c12fb7ae1dd63 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1613,7 +1613,7 @@ pyfritzhome==0.6.14 pyfttt==0.3 # homeassistant.components.gryfsmart -pygryfsmart==0.1.6 +pygryfsmart==0.1.7 # homeassistant.components.hvv_departures pygti==0.9.4 From ed4011e7443cd69f9937fb95d61438339ea7b986 Mon Sep 17 00:00:00 2001 From: karlowiczpl Date: Mon, 3 Feb 2025 22:53:53 +0100 Subject: [PATCH 05/12] add sensor platform --- .../components/gryfsmart/__init__.py | 3 +- homeassistant/components/gryfsmart/const.py | 2 + homeassistant/components/gryfsmart/light.py | 6 +- homeassistant/components/gryfsmart/sensor.py | 229 ++++++++++++++++++ 4 files changed, 234 insertions(+), 6 deletions(-) create mode 100644 homeassistant/components/gryfsmart/sensor.py diff --git a/homeassistant/components/gryfsmart/__init__.py b/homeassistant/components/gryfsmart/__init__.py index 08535803ae9a6e..e3f7f28dc43dbf 100644 --- a/homeassistant/components/gryfsmart/__init__.py +++ b/homeassistant/components/gryfsmart/__init__.py @@ -21,7 +21,7 @@ _PLATFORMS: list[Platform] = [ Platform.LIGHT, # Platform.BINARY_SENSOR, - # Platform.SENSOR + Platform.SENSOR, # Platform.CLIMATE, # Platform.COVER, # Platform.SWITCH, @@ -51,6 +51,7 @@ async def async_setup( hass.data[DOMAIN][CONF_API] = api await async_load_platform(hass, Platform.LIGHT, DOMAIN, None, config) + await async_load_platform(hass, Platform.SENSOR, DOMAIN, None, config) return True diff --git a/homeassistant/components/gryfsmart/const.py b/homeassistant/components/gryfsmart/const.py index a7ec8a2ee4ff03..5f05cd4ff96538 100755 --- a/homeassistant/components/gryfsmart/const.py +++ b/homeassistant/components/gryfsmart/const.py @@ -18,6 +18,7 @@ CONF_COMMUNICATION = "communication" CONF_API = "api" CONF_DEVICE_DATA = "device_data" +CONF_INPUTS = "input" PLATFORM_PWM = "pwm" PLATFORM_TEMPERATURE = "temperature" @@ -41,6 +42,7 @@ Platform.CLIMATE: "Thermostat", PLATFORM_PWM: "PWM", PLATFORM_TEMPERATURE: "Termometr", + CONF_INPUTS: "Input", } CONF_TILT = "tilt" diff --git a/homeassistant/components/gryfsmart/light.py b/homeassistant/components/gryfsmart/light.py index eeaf03acc3139a..2ff377cb0c6fef 100755 --- a/homeassistant/components/gryfsmart/light.py +++ b/homeassistant/components/gryfsmart/light.py @@ -1,6 +1,5 @@ """Handle the Gryf Smart light platform functionality.""" -import logging from typing import Any from pygryfsmart.device import _GryfDevice, _GryfOutput, _GryfPwm @@ -15,8 +14,6 @@ from .const import CONF_API, CONF_DEVICES, CONF_ID, CONF_NAME, CONF_PWM, DOMAIN from .entity import GryfConfigFlowEntity, GryfYamlEntity -_LOGGER = logging.getLogger(__name__) - async def async_setup_platform( hass: HomeAssistant, @@ -59,9 +56,8 @@ async def async_setup_entry( """Config flow for Light platform.""" lights = [] pwm = [] - light_config = config_entry.data[CONF_DEVICES] - for conf in light_config: + for conf in config_entry.data[CONF_DEVICES]: if conf.get(CONF_TYPE) == Platform.LIGHT: device = _GryfOutput( conf.get(CONF_NAME), diff --git a/homeassistant/components/gryfsmart/sensor.py b/homeassistant/components/gryfsmart/sensor.py new file mode 100644 index 00000000000000..dae25a5e7f28ac --- /dev/null +++ b/homeassistant/components/gryfsmart/sensor.py @@ -0,0 +1,229 @@ +"""Handle the Gryf Smart light platform functionality.""" + +from pygryfsmart.device import _GryfDevice, _GryfInput, _GryfInputLine, _GryfOutputLine + +from homeassistant.components.sensor import SensorDeviceClass, SensorEntity +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType + +from .const import CONF_API, CONF_DEVICES, CONF_ID, CONF_NAME, CONF_TYPE, DOMAIN +from .entity import GryfConfigFlowEntity, GryfYamlEntity + + +async def async_setup_platform( + hass: HomeAssistant, + config: ConfigType, + async_add_entities: AddEntitiesCallback, + discovery_info: DiscoveryInfoType | None, +) -> None: + """Set up the Sensor platform.""" + + async_add_entities( + [ + GryfYamlLine( + _GryfInputLine( + "Gryf IN", + hass.data[DOMAIN][CONF_API], + ), + True, + ) + ] + ) + async_add_entities( + [ + GryfYamlLine( + _GryfOutputLine( + "Gryf OUT", + hass.data[DOMAIN][CONF_API], + ), + False, + ) + ] + ) + + inputs = [] + + for conf in hass.data[DOMAIN].get("input", {}): + device = _GryfInput( + conf.get(CONF_NAME), + conf.get(CONF_ID) // 10, + conf.get(CONF_ID) % 10, + hass.data[DOMAIN][CONF_API], + ) + inputs.append(GryfYamlInput(device)) + + async_add_entities(inputs) + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Config flow for Sensor platform.""" + async_add_entities( + [ + GryfConfigFlowLine( + _GryfInputLine( + "Gryf IN", + config_entry.runtime_data[CONF_API], + ), + config_entry, + True, + ) + ] + ) + async_add_entities( + [ + GryfConfigFlowLine( + _GryfOutputLine( + "Gryf OUT", + config_entry.runtime_data[CONF_API], + ), + config_entry, + False, + ) + ] + ) + + inputs = [] + + for conf in config_entry.data[CONF_DEVICES]: + if conf.get(CONF_TYPE) == "input": + device = _GryfInput( + conf.get(CONF_NAME), + conf.get(CONF_ID) // 10, + conf.get(CONF_ID) % 10, + config_entry.runtime_data[CONF_API], + ) + inputs.append(GryfConfigFlowInput(device, config_entry)) + + async_add_entities(inputs) + + +class _GryfLineSensorBase(SensorEntity): + """Gryf line sensor base.""" + + _state = "" + _last_icon = False + _attr_icon = "mdi:message-arrow-right-outline" + _input: bool + + @property + def native_value(self) -> str: + """Return state.""" + return self._state + + async def async_update(self, state): + """Update state.""" + + self._state = state + self._last_icon = not self._last_icon + self.async_write_ha_state() + + @property + def icon(self) -> str: + """Property icon.""" + + if self._input: + return ( + "mdi:message-arrow-right-outline" + if self._last_icon + else "mdi:message-arrow-right" + ) + return ( + "mdi:message-arrow-left-outline" + if self._last_icon + else "mdi:message-arrow-left" + ) + + +class GryfConfigFlowLine(GryfConfigFlowEntity, _GryfLineSensorBase): + """Gryf Smart config flow input line class.""" + + def __init__( + self, + device: _GryfDevice, + config_entry: ConfigEntry, + input: bool, + ) -> None: + """Init the gryf input line.""" + + self._input = input + super().__init__(config_entry, device) + device.subscribe(self.async_update) + + +class GryfYamlLine(GryfYamlEntity, _GryfLineSensorBase): + """Gryf Smart yaml input line class.""" + + def __init__(self, device: _GryfDevice, input: bool) -> None: + """Init the gryf input line.""" + + self._input = input + super().__init__(device) + device.subscribe(self.async_update) + + +class _GryfInputSensorBase(SensorEntity): + """Gryf smart input sensor Base.""" + + _state = 0 + _device: _GryfDevice + _attr_icon = "mdi:light-switch-off" + _attr_device_class = SensorDeviceClass.ENUM + _attr_options = ["0", "1", "2", "3"] + + async def async_update(self, data): + """Update state.""" + + self._state = data + self.async_write_ha_state() + + @property + def native_value(self) -> int: + """Property state.""" + + return self._state + + @property + def icon(self) -> str: + """Property icon.""" + + icon_mapper = { + 0: "mdi:light-switch-off", + 1: "mdi:light-switch", + 2: "mdi:gesture-tap", + 3: "mdi:gesture-tap-hold", + } + + return icon_mapper[self._state] + + +class GryfConfigFlowInput(GryfConfigFlowEntity, _GryfInputSensorBase): + """Gryf Smart config flow input line class.""" + + def __init__( + self, + device: _GryfDevice, + config_entry: ConfigEntry, + ) -> None: + """Init the gryf input line.""" + + super().__init__(config_entry, device) + device.subscribe(self.async_update) + + +class GryfYamlInput(GryfYamlEntity, _GryfInputSensorBase): + """Gryf Smart config flow input line class.""" + + def __init__( + self, + device: _GryfDevice, + ) -> None: + """Init the gryf input line.""" + + super().__init__(device) + device.subscribe(self.async_update) From 4409267a98926cd777ead9bf7afceaeb50157d73 Mon Sep 17 00:00:00 2001 From: karlowiczpl Date: Wed, 5 Feb 2025 10:01:40 +0100 Subject: [PATCH 06/12] add binary sensor platform --- .../components/gryfsmart/__init__.py | 2 +- .../components/gryfsmart/binary_sensor.py | 108 +++++++++++++ .../components/gryfsmart/config_flow.py | 92 +++++++++-- homeassistant/components/gryfsmart/const.py | 68 ++++---- homeassistant/components/gryfsmart/light.py | 6 +- homeassistant/components/gryfsmart/schema.py | 16 +- homeassistant/components/gryfsmart/sensor.py | 147 +++++++++++++----- 7 files changed, 339 insertions(+), 100 deletions(-) create mode 100644 homeassistant/components/gryfsmart/binary_sensor.py diff --git a/homeassistant/components/gryfsmart/__init__.py b/homeassistant/components/gryfsmart/__init__.py index e3f7f28dc43dbf..b57777906802ac 100644 --- a/homeassistant/components/gryfsmart/__init__.py +++ b/homeassistant/components/gryfsmart/__init__.py @@ -20,7 +20,7 @@ _PLATFORMS: list[Platform] = [ Platform.LIGHT, - # Platform.BINARY_SENSOR, + Platform.BINARY_SENSOR, Platform.SENSOR, # Platform.CLIMATE, # Platform.COVER, diff --git a/homeassistant/components/gryfsmart/binary_sensor.py b/homeassistant/components/gryfsmart/binary_sensor.py new file mode 100644 index 00000000000000..3140614abfa8e7 --- /dev/null +++ b/homeassistant/components/gryfsmart/binary_sensor.py @@ -0,0 +1,108 @@ +"""Handle the Gryf Smart binary sensor platform functionality.""" + +from pygryfsmart.device import _GryfDevice, _GryfInput + +from homeassistant.components.binary_sensor import ( + BinarySensorDeviceClass, + BinarySensorEntity, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import Platform +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType + +from .const import ( + CONF_API, + CONF_DEVICES, + CONF_EXTRA, + CONF_ID, + CONF_NAME, + CONF_TYPE, + DOMAIN, +) +from .entity import GryfConfigFlowEntity + + +async def async_setup_platform( + hass: HomeAssistant, + config: ConfigType, + async_add_entities: AddEntitiesCallback, + discoveryInfo: DiscoveryInfoType | None, +) -> None: + """Set up the binary sensor platform.""" + + binary_sensors = [] + + for conf in hass.data[DOMAIN].get(Platform.BINARY_SENSOR, {}): + device = _GryfInput( + conf.get(CONF_NAME), + conf.get(CONF_ID) // 10, + conf.get(CONF_ID) % 10, + hass.data[DOMAIN][CONF_API], + ) + binary_sensors.append(device) + + async_add_entities(binary_sensors) + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Config flow for binary sensor platform.""" + + binary_sensors = [] + + for conf in config_entry.data[CONF_DEVICES]: + if conf.get(CONF_TYPE) == Platform.BINARY_SENSOR: + device = _GryfInput( + conf.get(CONF_NAME), + conf.get(CONF_ID) // 10, + conf.get(CONF_ID) % 10, + config_entry.runtime_data[CONF_API], + ) + binary_sensors.append( + GryfConfigFlowBinarySensor( + device, + config_entry, + conf.get(CONF_EXTRA), + ) + ) + + async_add_entities(binary_sensors) + + +class _GryfBinarySensorBase(BinarySensorEntity): + """Gryf Binary Sensor base.""" + + _is_on = False + _attr_device_class = BinarySensorDeviceClass.OPENING + + @property + def is_on(self) -> bool: + return not self._is_on + + async def async_update(self, state): + if state in [0, 1]: + self._is_on = state + self.async_write_ha_state() + + +class GryfConfigFlowBinarySensor(GryfConfigFlowEntity, _GryfBinarySensorBase): + """Gryf Smart config flow binary sensor class.""" + + def __init__( + self, + device: _GryfDevice, + config_entry: ConfigEntry, + device_class: BinarySensorDeviceClass | None, + ) -> None: + """Init the gryf binary sensor.""" + + super().__init__(config_entry, device) + device.subscribe(self.async_update) + + if device_class: + self._attr_device_class = device_class diff --git a/homeassistant/components/gryfsmart/config_flow.py b/homeassistant/components/gryfsmart/config_flow.py index da8485b3bf854f..cb69b552364021 100644 --- a/homeassistant/components/gryfsmart/config_flow.py +++ b/homeassistant/components/gryfsmart/config_flow.py @@ -10,10 +10,12 @@ import voluptuous as vol from homeassistant import config_entries +from homeassistant.const import Platform from homeassistant.core import callback from homeassistant.helpers import selector from .const import ( + BINARY_SENSOR_DEVICE_CLASS, CONF_COMMUNICATION, CONF_DEVICES, CONF_EXTRA, @@ -112,6 +114,8 @@ async def async_step_add_device( self, user_input: dict[str, Any] | None = None ) -> config_entries.ConfigFlowResult: """Add new device.""" + + errors = {} if user_input: new_device = { CONF_TYPE: user_input[CONF_TYPE], @@ -120,7 +124,15 @@ async def async_step_add_device( CONF_EXTRA: user_input.get(CONF_EXTRA), } self._config_data[CONF_DEVICES].append(new_device) - return await self.async_step_device_menu() + + if ( + check_extra_parameter( + user_input.get(CONF_EXTRA), user_input.get(CONF_TYPE) + ) + is None + ): + return await self.async_step_device_menu() + errors[CONF_TYPE] = "Bad binary sensor extra parameter!" return self.async_show_form( step_id="add_device", @@ -129,9 +141,10 @@ async def async_step_add_device( vol.Required(CONF_TYPE): vol.In(DEVICE_TYPES), vol.Required(CONF_NAME): str, vol.Required(CONF_ID): int, - vol.Optional(CONF_EXTRA): int, + vol.Optional(CONF_EXTRA): str, } ), + errors=errors, ) async def async_step_edit_device( @@ -170,10 +183,23 @@ async def async_step_edit_device_details( self, user_input: dict[str, Any] | None = None ) -> config_entries.ConfigFlowResult: """Edit device parameters.""" + + errors = {} + if user_input: - self._config_data[CONF_DEVICES][self._edit_index] = user_input - self._edit_index = None - return await self.async_step_device_menu() + if ( + check_extra_parameter( + user_input.get(CONF_EXTRA), user_input.get(CONF_TYPE) + ) + is None + ): + self._config_data[CONF_DEVICES][self._edit_index] = user_input + self._config_data[CONF_DEVICES][self._edit_index][CONF_EXTRA] = ( + user_input.get(CONF_EXTRA) + ) + self._edit_index = None + return await self.async_step_device_menu() + errors[CONF_TYPE] = "Bad binary sensor extra parameter!" return self.async_show_form( step_id="edit_device_details", @@ -188,9 +214,10 @@ async def async_step_edit_device_details( vol.Required(CONF_ID, default=self._current_device[CONF_ID]): int, vol.Optional( CONF_EXTRA, default=self._current_device.get(CONF_EXTRA) - ): int, + ): str, } ), + errors=errors, ) async def async_step_finish( @@ -244,6 +271,9 @@ async def async_step_add_device( self, user_input: dict[str, Any] | None = None ) -> config_entries.ConfigFlowResult: """Add new device.""" + + errors = {} + if user_input: new_device = { CONF_TYPE: user_input[CONF_TYPE], @@ -251,8 +281,15 @@ async def async_step_add_device( CONF_ID: user_input[CONF_ID], CONF_EXTRA: user_input.get(CONF_EXTRA), } - self.data["devices"].append(new_device) - return await self.async_step_main_menu() + if ( + check_extra_parameter( + user_input.get(CONF_EXTRA), user_input.get(CONF_TYPE) + ) + is None + ): + self.data["devices"].append(new_device) + return await self.async_step_main_menu() + errors[CONF_TYPE] = "Bad binary sensor extra parameter!" return self.async_show_form( step_id="add_device", @@ -261,9 +298,10 @@ async def async_step_add_device( vol.Required(CONF_TYPE): vol.In(DEVICE_TYPES), vol.Required(CONF_NAME): str, vol.Required(CONF_ID): int, - vol.Optional(CONF_EXTRA): int, + vol.Optional(CONF_EXTRA): str, } ), + errors=errors, ) async def async_step_edit_device( @@ -300,10 +338,21 @@ async def async_step_edit_device_details( self, user_input: dict[str, Any] | None = None ) -> config_entries.ConfigFlowResult: """Edit device parameters.""" + + errors = {} + if user_input: - self.data[CONF_DEVICES][self._edit_index] = user_input - self._edit_index = 0 - return await self.async_step_main_menu() + if ( + check_extra_parameter( + user_input.get(CONF_EXTRA), user_input.get(CONF_TYPE) + ) + is None + ): + self.data[CONF_DEVICES][self._edit_index] = user_input + _LOGGER.debug("%s", user_input) + self._edit_index = 0 + return await self.async_step_main_menu() + errors[CONF_TYPE] = "Bad binary sensor extra parameter!" if self._current_device.get(CONF_EXTRA) is not None: return self.async_show_form( @@ -321,9 +370,10 @@ async def async_step_edit_device_details( ): int, vol.Optional( CONF_EXTRA, default=self._current_device.get(CONF_EXTRA) - ): int, + ): str, } ), + errors=errors, ) return self.async_show_form( step_id="edit_device_details", @@ -336,9 +386,10 @@ async def async_step_edit_device_details( CONF_NAME, default=self._current_device[CONF_NAME] ): str, vol.Required(CONF_ID, default=self._current_device[CONF_ID]): int, - vol.Optional(CONF_EXTRA): int, + vol.Optional(CONF_EXTRA): str, } ), + errors=errors, ) async def async_step_finish( @@ -380,3 +431,16 @@ async def async_step_communication( ), errors=errors, ) + + +def check_extra_parameter( + extra_parameter: Any | None, + device_type: Any | None, +) -> str | None: + """Check extra parameter.""" + + if device_type == Platform.BINARY_SENSOR: + if extra_parameter in BINARY_SENSOR_DEVICE_CLASS: + return None + return "Bad binary sensor extra parameter!" + return None diff --git a/homeassistant/components/gryfsmart/const.py b/homeassistant/components/gryfsmart/const.py index 5f05cd4ff96538..cd5a74d51e8bfb 100755 --- a/homeassistant/components/gryfsmart/const.py +++ b/homeassistant/components/gryfsmart/const.py @@ -1,7 +1,6 @@ """Define constants used throughout the Gryf Smart integration.""" -from enum import Enum - +from homeassistant.components.binary_sensor import BinarySensorDeviceClass from homeassistant.const import Platform DOMAIN = "gryfsmart" @@ -22,8 +21,12 @@ PLATFORM_PWM = "pwm" PLATFORM_TEMPERATURE = "temperature" +PLATFORM_INPUT = "input" +PLATFORM_LIGHT = "light" DEFAULT_PORT = "/dev/ttyUSB0" +GRYF_IN_NAME = "Gryf IN" +GRYF_OUT_NAME = "Gryf OUT" CONFIG_FLOW_MENU_OPTIONS = { "add_device": "Add Device", @@ -33,49 +36,32 @@ } DEVICE_TYPES = { - Platform.COVER: "Shutter", + # Platform.COVER: "Shutter", Platform.LIGHT: "Lights", - Platform.SWITCH: "Outputs", - Platform.SENSOR: "Input", + # Platform.SWITCH: "Outputs", + # Platform.SENSOR: "Input", Platform.BINARY_SENSOR: "Binary input", - Platform.LOCK: "Lock", - Platform.CLIMATE: "Thermostat", + # Platform.LOCK: "Lock", + # Platform.CLIMATE: "Thermostat", PLATFORM_PWM: "PWM", PLATFORM_TEMPERATURE: "Termometr", - CONF_INPUTS: "Input", + PLATFORM_INPUT: "Input", } -CONF_TILT = "tilt" -CONF_LIGHTS = "lights" -CONF_BUTTON = "buttons" -CONF_SERIAL = "port" -CONF_DOORS = "doors" -CONF_WINDOW = "windows" -CONF_TEMPERATURE = "temperature" -CONF_COVER = "covers" -CONF_TIME = "time" -CONF_LOCK = "lock" -CONF_PWM = "pwm" -CONF_CLIMATE = "climate" -CONF_HARMONOGRAM = "harmonogram" -CONF_T_ID = "t_id" -CONF_O_ID = "o_id" -CONF_T_PIN = "t_pin" -CONF_O_PIN = "o_pin" -CONF_ID_COUNT = "id" -CONF_GATE = "gate" -CONF_P_COVER = "p_covers" -CONF_IP = "ip" -CONF_STATES_UPDATE = "states_update" -CONF_HARMONOGRAM = "harmonogram" -CONF_ON = "on" -CONF_OFF = "off" -CONF_ALL = "all" - - -class OUTPUT_STATES(Enum): - """Output states enum.""" +CONF_LINE_SENSOR_ICONS = { + GRYF_IN_NAME: ["mdi:message-arrow-right-outline", "mdi:message-arrow-right"], + GRYF_OUT_NAME: ["mdi:message-arrow-left-outline", "mdi:message-arrow-left"], +} - OFF = "2" - ON = "1" - TOGGLE = "3" +BINARY_SENSOR_DEVICE_CLASS = { + "door": BinarySensorDeviceClass.DOOR, + "garage door": BinarySensorDeviceClass.GARAGE_DOOR, + "heat": BinarySensorDeviceClass.HEAT, + "light": BinarySensorDeviceClass.LIGHT, + "motion": BinarySensorDeviceClass.MOTION, + "window": BinarySensorDeviceClass.WINDOW, + "smoke": BinarySensorDeviceClass.SMOKE, + "sound": BinarySensorDeviceClass.SOUND, + "power": BinarySensorDeviceClass.POWER, + None: BinarySensorDeviceClass.OPENING, +} diff --git a/homeassistant/components/gryfsmart/light.py b/homeassistant/components/gryfsmart/light.py index 2ff377cb0c6fef..7569c8b662e1b4 100755 --- a/homeassistant/components/gryfsmart/light.py +++ b/homeassistant/components/gryfsmart/light.py @@ -11,7 +11,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType -from .const import CONF_API, CONF_DEVICES, CONF_ID, CONF_NAME, CONF_PWM, DOMAIN +from .const import CONF_API, CONF_DEVICES, CONF_ID, CONF_NAME, DOMAIN, PLATFORM_PWM from .entity import GryfConfigFlowEntity, GryfYamlEntity @@ -35,7 +35,7 @@ async def async_setup_platform( ) lights.append(GryfYamlLight(device)) - for conf in hass.data[DOMAIN].get(CONF_PWM, {}): + for conf in hass.data[DOMAIN].get(PLATFORM_PWM, {}): device = _GryfPwm( conf.get(CONF_NAME), conf.get(CONF_ID) // 10, @@ -66,7 +66,7 @@ async def async_setup_entry( config_entry.runtime_data[CONF_API], ) lights.append(GryfConfigFlowLight(device, config_entry)) - elif conf.get(CONF_TYPE) == CONF_PWM: + elif conf.get(CONF_TYPE) == PLATFORM_PWM: device = _GryfPwm( conf.get(CONF_NAME), conf.get(CONF_ID) // 10, diff --git a/homeassistant/components/gryfsmart/schema.py b/homeassistant/components/gryfsmart/schema.py index 78b0b3e361deaf..369607b4c820ad 100644 --- a/homeassistant/components/gryfsmart/schema.py +++ b/homeassistant/components/gryfsmart/schema.py @@ -4,7 +4,15 @@ from homeassistant.helpers import config_validation as cv -from .const import CONF_ID, CONF_MODULE_COUNT, CONF_NAME, CONF_PORT, CONF_PWM, DOMAIN +from .const import ( + CONF_ID, + CONF_MODULE_COUNT, + CONF_NAME, + CONF_PORT, + DOMAIN, + PLATFORM_LIGHT, + PLATFORM_PWM, +) STANDARD_SCHEMA = vol.Schema( { @@ -17,10 +25,12 @@ { DOMAIN: vol.Schema( { - vol.Optional("light"): vol.All(cv.ensure_list, [STANDARD_SCHEMA]), + vol.Optional(PLATFORM_LIGHT): vol.All( + cv.ensure_list, [STANDARD_SCHEMA] + ), vol.Required(CONF_PORT): cv.string, vol.Optional(CONF_MODULE_COUNT): cv.positive_int, - vol.Optional(CONF_PWM): vol.All(cv.ensure_list, [STANDARD_SCHEMA]), + vol.Optional(PLATFORM_PWM): vol.All(cv.ensure_list, [STANDARD_SCHEMA]), } ) }, diff --git a/homeassistant/components/gryfsmart/sensor.py b/homeassistant/components/gryfsmart/sensor.py index dae25a5e7f28ac..e148a175dfb568 100644 --- a/homeassistant/components/gryfsmart/sensor.py +++ b/homeassistant/components/gryfsmart/sensor.py @@ -1,6 +1,12 @@ -"""Handle the Gryf Smart light platform functionality.""" +"""Handle the Gryf Smart Sensor platform functionality.""" -from pygryfsmart.device import _GryfDevice, _GryfInput, _GryfInputLine, _GryfOutputLine +from pygryfsmart.device import ( + _GryfDevice, + _GryfInput, + _GryfInputLine, + _GryfOutputLine, + _GryfTemperature, +) from homeassistant.components.sensor import SensorDeviceClass, SensorEntity from homeassistant.config_entries import ConfigEntry @@ -8,7 +14,19 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType -from .const import CONF_API, CONF_DEVICES, CONF_ID, CONF_NAME, CONF_TYPE, DOMAIN +from .const import ( + CONF_API, + CONF_DEVICES, + CONF_ID, + CONF_LINE_SENSOR_ICONS, + CONF_NAME, + CONF_TYPE, + DOMAIN, + GRYF_IN_NAME, + GRYF_OUT_NAME, + PLATFORM_INPUT, + PLATFORM_TEMPERATURE, +) from .entity import GryfConfigFlowEntity, GryfYamlEntity @@ -24,10 +42,10 @@ async def async_setup_platform( [ GryfYamlLine( _GryfInputLine( - "Gryf IN", + GRYF_IN_NAME, hass.data[DOMAIN][CONF_API], ), - True, + GRYF_IN_NAME, ) ] ) @@ -35,17 +53,17 @@ async def async_setup_platform( [ GryfYamlLine( _GryfOutputLine( - "Gryf OUT", + GRYF_OUT_NAME, hass.data[DOMAIN][CONF_API], ), - False, + GRYF_OUT_NAME, ) ] ) inputs = [] - for conf in hass.data[DOMAIN].get("input", {}): + for conf in hass.data[DOMAIN].get(PLATFORM_INPUT, {}): device = _GryfInput( conf.get(CONF_NAME), conf.get(CONF_ID) // 10, @@ -67,11 +85,11 @@ async def async_setup_entry( [ GryfConfigFlowLine( _GryfInputLine( - "Gryf IN", + GRYF_IN_NAME, config_entry.runtime_data[CONF_API], ), config_entry, - True, + GRYF_IN_NAME, ) ] ) @@ -79,19 +97,20 @@ async def async_setup_entry( [ GryfConfigFlowLine( _GryfOutputLine( - "Gryf OUT", + GRYF_OUT_NAME, config_entry.runtime_data[CONF_API], ), config_entry, - False, + GRYF_OUT_NAME, ) ] ) inputs = [] + temperature = [] for conf in config_entry.data[CONF_DEVICES]: - if conf.get(CONF_TYPE) == "input": + if conf.get(CONF_TYPE) == PLATFORM_INPUT: device = _GryfInput( conf.get(CONF_NAME), conf.get(CONF_ID) // 10, @@ -99,8 +118,19 @@ async def async_setup_entry( config_entry.runtime_data[CONF_API], ) inputs.append(GryfConfigFlowInput(device, config_entry)) + if conf.get(CONF_TYPE) == PLATFORM_TEMPERATURE: + temperature_device = _GryfTemperature( + conf.get(CONF_NAME), + conf.get(CONF_ID) // 10, + conf.get(CONF_ID) % 10, + config_entry.runtime_data[CONF_API], + ) + temperature.append( + GryfConfigFlowTemperature(temperature_device, config_entry) + ) async_add_entities(inputs) + async_add_entities(temperature) class _GryfLineSensorBase(SensorEntity): @@ -108,8 +138,8 @@ class _GryfLineSensorBase(SensorEntity): _state = "" _last_icon = False - _attr_icon = "mdi:message-arrow-right-outline" - _input: bool + _attr_icon = CONF_LINE_SENSOR_ICONS[GRYF_IN_NAME][0] + _input: str @property def native_value(self) -> str: @@ -127,17 +157,7 @@ async def async_update(self, state): def icon(self) -> str: """Property icon.""" - if self._input: - return ( - "mdi:message-arrow-right-outline" - if self._last_icon - else "mdi:message-arrow-right" - ) - return ( - "mdi:message-arrow-left-outline" - if self._last_icon - else "mdi:message-arrow-left" - ) + return CONF_LINE_SENSOR_ICONS[self._input][self._last_icon] class GryfConfigFlowLine(GryfConfigFlowEntity, _GryfLineSensorBase): @@ -147,7 +167,7 @@ def __init__( self, device: _GryfDevice, config_entry: ConfigEntry, - input: bool, + input: str, ) -> None: """Init the gryf input line.""" @@ -159,7 +179,11 @@ def __init__( class GryfYamlLine(GryfYamlEntity, _GryfLineSensorBase): """Gryf Smart yaml input line class.""" - def __init__(self, device: _GryfDevice, input: bool) -> None: + def __init__( + self, + device: _GryfDevice, + input: str, + ) -> None: """Init the gryf input line.""" self._input = input @@ -170,7 +194,7 @@ def __init__(self, device: _GryfDevice, input: bool) -> None: class _GryfInputSensorBase(SensorEntity): """Gryf smart input sensor Base.""" - _state = 0 + _state = "0" _device: _GryfDevice _attr_icon = "mdi:light-switch-off" _attr_device_class = SensorDeviceClass.ENUM @@ -179,11 +203,11 @@ class _GryfInputSensorBase(SensorEntity): async def async_update(self, data): """Update state.""" - self._state = data + self._state = str(data) self.async_write_ha_state() @property - def native_value(self) -> int: + def native_value(self) -> str: """Property state.""" return self._state @@ -193,31 +217,78 @@ def icon(self) -> str: """Property icon.""" icon_mapper = { - 0: "mdi:light-switch-off", - 1: "mdi:light-switch", - 2: "mdi:gesture-tap", - 3: "mdi:gesture-tap-hold", + "0": "mdi:light-switch-off", + "1": "mdi:light-switch", + "2": "mdi:gesture-tap", + "3": "mdi:gesture-tap-hold", } return icon_mapper[self._state] class GryfConfigFlowInput(GryfConfigFlowEntity, _GryfInputSensorBase): - """Gryf Smart config flow input line class.""" + """Gryf Smart config flow input class.""" def __init__( self, device: _GryfDevice, config_entry: ConfigEntry, ) -> None: - """Init the gryf input line.""" + """Init the gryf input.""" super().__init__(config_entry, device) device.subscribe(self.async_update) class GryfYamlInput(GryfYamlEntity, _GryfInputSensorBase): - """Gryf Smart config flow input line class.""" + """Gryf Smart yaml input line class.""" + + def __init__( + self, + device: _GryfDevice, + ) -> None: + """Init the gryf input line.""" + + super().__init__(device) + device.subscribe(self.async_update) + + +class _GryfTemperatureSensorBase(SensorEntity): + """Gryf Smart temperature sensor base.""" + + _state = "0.0" + _device: _GryfDevice + _attr_device_class = SensorDeviceClass.TEMPERATURE + _attr_native_unit_of_measurement = "°C" + + async def async_update(self, data): + """Update state.""" + + self._state = data + self.async_write_ha_state() + + @property + def native_value(self) -> str: + """Property state.""" + + return self._state + + +class GryfConfigFlowTemperature(GryfConfigFlowEntity, _GryfTemperatureSensorBase): + """Gryf Smart config flow temperature class.""" + + def __init__( + self, + device: _GryfDevice, + config_entry: ConfigEntry, + ) -> None: + """Init the gryf temperature.""" + super().__init__(config_entry, device) + device.subscribe(self.async_update) + + +class GryfYamlTemperature(GryfYamlEntity, _GryfTemperatureSensorBase): + """Gryf Smart yaml input line class.""" def __init__( self, From a7db884896b4ef8cdc29a64a72c64e37d9d24167 Mon Sep 17 00:00:00 2001 From: karlowiczpl Date: Thu, 6 Feb 2025 11:24:41 +0100 Subject: [PATCH 07/12] 1.0 --- .../components/gryfsmart/__init__.py | 8 +- .../components/gryfsmart/binary_sensor.py | 25 +++- .../components/gryfsmart/config_flow.py | 10 +- homeassistant/components/gryfsmart/const.py | 12 +- .../components/gryfsmart/manifest.json | 2 +- homeassistant/components/gryfsmart/schema.py | 26 +++- .../components/gryfsmart/strings.json | 13 -- homeassistant/components/gryfsmart/switch.py | 134 ++++++++++++++++++ requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 10 files changed, 205 insertions(+), 29 deletions(-) delete mode 100644 homeassistant/components/gryfsmart/strings.json create mode 100644 homeassistant/components/gryfsmart/switch.py diff --git a/homeassistant/components/gryfsmart/__init__.py b/homeassistant/components/gryfsmart/__init__.py index b57777906802ac..03cd1af589d601 100644 --- a/homeassistant/components/gryfsmart/__init__.py +++ b/homeassistant/components/gryfsmart/__init__.py @@ -24,7 +24,7 @@ Platform.SENSOR, # Platform.CLIMATE, # Platform.COVER, - # Platform.SWITCH, + Platform.SWITCH, # Platform.LOCK ] @@ -43,6 +43,7 @@ async def async_setup( try: api = GryfApi(config[DOMAIN][CONF_PORT]) await api.start_connection() + api.start_update_interval(1) except ConnectionError: _LOGGER.error("Unable to connect: %s", ConnectionError) return False @@ -50,8 +51,8 @@ async def async_setup( hass.data[DOMAIN] = config.get(DOMAIN) hass.data[DOMAIN][CONF_API] = api - await async_load_platform(hass, Platform.LIGHT, DOMAIN, None, config) - await async_load_platform(hass, Platform.SENSOR, DOMAIN, None, config) + for PLATFORM in _PLATFORMS: + await async_load_platform(hass, PLATFORM, DOMAIN, None, config) return True @@ -65,6 +66,7 @@ async def async_setup_entry( try: api = GryfApi(entry.data[CONF_COMMUNICATION][CONF_PORT]) await api.start_connection() + api.start_update_interval(1) except ConnectionError: raise ConfigEntryNotReady("Unable to connect with device") from ConnectionError diff --git a/homeassistant/components/gryfsmart/binary_sensor.py b/homeassistant/components/gryfsmart/binary_sensor.py index 3140614abfa8e7..384b5f5f48f760 100644 --- a/homeassistant/components/gryfsmart/binary_sensor.py +++ b/homeassistant/components/gryfsmart/binary_sensor.py @@ -14,14 +14,16 @@ from .const import ( CONF_API, + CONF_DEVICE_CLASS, CONF_DEVICES, CONF_EXTRA, CONF_ID, CONF_NAME, CONF_TYPE, DOMAIN, + PLATFORM_BINARY_SENSOR, ) -from .entity import GryfConfigFlowEntity +from .entity import GryfConfigFlowEntity, GryfYamlEntity async def async_setup_platform( @@ -34,14 +36,14 @@ async def async_setup_platform( binary_sensors = [] - for conf in hass.data[DOMAIN].get(Platform.BINARY_SENSOR, {}): + for conf in hass.data[DOMAIN].get(PLATFORM_BINARY_SENSOR, {}): device = _GryfInput( conf.get(CONF_NAME), conf.get(CONF_ID) // 10, conf.get(CONF_ID) % 10, hass.data[DOMAIN][CONF_API], ) - binary_sensors.append(device) + binary_sensors.append(GryfYamlBinarySensor(device, conf.get(CONF_DEVICE_CLASS))) async_add_entities(binary_sensors) @@ -106,3 +108,20 @@ def __init__( if device_class: self._attr_device_class = device_class + + +class GryfYamlBinarySensor(GryfYamlEntity, _GryfBinarySensorBase): + """Gryf Smart yaml input line class.""" + + def __init__( + self, + device: _GryfDevice, + device_class: BinarySensorDeviceClass | None, + ) -> None: + """Init the gryf input line.""" + + super().__init__(device) + device.subscribe(self.async_update) + + if device_class: + self._attr_device_class = device_class diff --git a/homeassistant/components/gryfsmart/config_flow.py b/homeassistant/components/gryfsmart/config_flow.py index cb69b552364021..3dcf148b97f056 100644 --- a/homeassistant/components/gryfsmart/config_flow.py +++ b/homeassistant/components/gryfsmart/config_flow.py @@ -28,6 +28,7 @@ DEFAULT_PORT, DEVICE_TYPES, DOMAIN, + SWITCH_DEVICE_CLASS, ) _LOGGER = logging.getLogger(__name__) @@ -440,7 +441,10 @@ def check_extra_parameter( """Check extra parameter.""" if device_type == Platform.BINARY_SENSOR: - if extra_parameter in BINARY_SENSOR_DEVICE_CLASS: - return None - return "Bad binary sensor extra parameter!" + if extra_parameter not in BINARY_SENSOR_DEVICE_CLASS: + return "Bad binary sensor extra parameter!" + if device_type == Platform.SWITCH: + if extra_parameter not in SWITCH_DEVICE_CLASS: + return "Bad Output extra parameter!" + return None diff --git a/homeassistant/components/gryfsmart/const.py b/homeassistant/components/gryfsmart/const.py index cd5a74d51e8bfb..633d4027be24b4 100755 --- a/homeassistant/components/gryfsmart/const.py +++ b/homeassistant/components/gryfsmart/const.py @@ -1,6 +1,7 @@ """Define constants used throughout the Gryf Smart integration.""" from homeassistant.components.binary_sensor import BinarySensorDeviceClass +from homeassistant.components.switch import SwitchDeviceClass from homeassistant.const import Platform DOMAIN = "gryfsmart" @@ -18,11 +19,14 @@ CONF_API = "api" CONF_DEVICE_DATA = "device_data" CONF_INPUTS = "input" +CONF_DEVICE_CLASS = "device_class" PLATFORM_PWM = "pwm" PLATFORM_TEMPERATURE = "temperature" PLATFORM_INPUT = "input" PLATFORM_LIGHT = "light" +PLATFORM_BINARY_SENSOR = "binary_sensor" +PLATFORM_SWITCH = "output" DEFAULT_PORT = "/dev/ttyUSB0" GRYF_IN_NAME = "Gryf IN" @@ -38,7 +42,7 @@ DEVICE_TYPES = { # Platform.COVER: "Shutter", Platform.LIGHT: "Lights", - # Platform.SWITCH: "Outputs", + Platform.SWITCH: "Output", # Platform.SENSOR: "Input", Platform.BINARY_SENSOR: "Binary input", # Platform.LOCK: "Lock", @@ -65,3 +69,9 @@ "power": BinarySensorDeviceClass.POWER, None: BinarySensorDeviceClass.OPENING, } + +SWITCH_DEVICE_CLASS = { + None: SwitchDeviceClass.SWITCH, + "switch": SwitchDeviceClass.SWITCH, + "outlet": SwitchDeviceClass.OUTLET, +} diff --git a/homeassistant/components/gryfsmart/manifest.json b/homeassistant/components/gryfsmart/manifest.json index 696dac1e5dc99f..7f09c63d88f3ca 100644 --- a/homeassistant/components/gryfsmart/manifest.json +++ b/homeassistant/components/gryfsmart/manifest.json @@ -7,7 +7,7 @@ "homekit": {}, "iot_class": "local_push", "quality_scale": "bronze", - "requirements": ["pygryfsmart==0.1.7"], + "requirements": ["pygryfsmart==0.1.8"], "ssdp": [], "zeroconf": [] } diff --git a/homeassistant/components/gryfsmart/schema.py b/homeassistant/components/gryfsmart/schema.py index 369607b4c820ad..10fa6a008ae8b8 100644 --- a/homeassistant/components/gryfsmart/schema.py +++ b/homeassistant/components/gryfsmart/schema.py @@ -5,13 +5,17 @@ from homeassistant.helpers import config_validation as cv from .const import ( + CONF_DEVICE_CLASS, CONF_ID, CONF_MODULE_COUNT, CONF_NAME, CONF_PORT, DOMAIN, + PLATFORM_BINARY_SENSOR, + PLATFORM_INPUT, PLATFORM_LIGHT, PLATFORM_PWM, + PLATFORM_SWITCH, ) STANDARD_SCHEMA = vol.Schema( @@ -20,17 +24,33 @@ vol.Required(CONF_ID): cv.positive_int, } ) +DEVICE_CLASS_SCHEMA = vol.Schema( + { + vol.Required(CONF_NAME): cv.string, + vol.Required(CONF_ID): cv.positive_int, + vol.Optional(CONF_DEVICE_CLASS): cv.string, + } +) CONFIG_SCHEMA = vol.Schema( { DOMAIN: vol.Schema( { + vol.Required(CONF_PORT): cv.string, + vol.Required(CONF_MODULE_COUNT): cv.positive_int, + vol.Optional(PLATFORM_PWM): vol.All(cv.ensure_list, [STANDARD_SCHEMA]), vol.Optional(PLATFORM_LIGHT): vol.All( cv.ensure_list, [STANDARD_SCHEMA] ), - vol.Required(CONF_PORT): cv.string, - vol.Optional(CONF_MODULE_COUNT): cv.positive_int, - vol.Optional(PLATFORM_PWM): vol.All(cv.ensure_list, [STANDARD_SCHEMA]), + vol.Optional(PLATFORM_INPUT): vol.All( + cv.ensure_list, [STANDARD_SCHEMA] + ), + vol.Optional(PLATFORM_BINARY_SENSOR): vol.All( + cv.ensure_list, [DEVICE_CLASS_SCHEMA] + ), + vol.Optional(PLATFORM_SWITCH): vol.All( + cv.ensure_list, [DEVICE_CLASS_SCHEMA] + ), } ) }, diff --git a/homeassistant/components/gryfsmart/strings.json b/homeassistant/components/gryfsmart/strings.json deleted file mode 100644 index ad8f0f41ae7b29..00000000000000 --- a/homeassistant/components/gryfsmart/strings.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "config": { - "step": { - "confirm": { - "description": "[%key:common::config_flow::description::confirm_setup%]" - } - }, - "abort": { - "single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]", - "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]" - } - } -} diff --git a/homeassistant/components/gryfsmart/switch.py b/homeassistant/components/gryfsmart/switch.py new file mode 100644 index 00000000000000..445d7d9c37d6de --- /dev/null +++ b/homeassistant/components/gryfsmart/switch.py @@ -0,0 +1,134 @@ +"""Handle the Gryf Smart Switch platform functionality.""" + +from pygryfsmart.device import _GryfDevice, _GryfOutput + +from homeassistant.components.switch import SwitchDeviceClass, SwitchEntity +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_TYPE, Platform +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType + +from .const import ( + CONF_API, + CONF_DEVICE_CLASS, + CONF_DEVICES, + CONF_EXTRA, + CONF_ID, + CONF_NAME, + DOMAIN, + PLATFORM_SWITCH, + SWITCH_DEVICE_CLASS, +) +from .entity import GryfConfigFlowEntity, GryfYamlEntity + + +async def async_setup_platform( + hass: HomeAssistant, + config: ConfigType, + async_add_entities: AddEntitiesCallback, + discovery_info: DiscoveryInfoType | None, +) -> None: + """Set up the Switch platform.""" + + switches = [] + + for conf in hass.data[DOMAIN].get(PLATFORM_SWITCH): + device = _GryfOutput( + conf.get(CONF_NAME), + conf.get(CONF_ID) // 10, + conf.get(CONF_ID) % 10, + hass.data[DOMAIN][CONF_API], + ) + switches.append(GryfYamlSwitch(device, conf.get(CONF_DEVICE_CLASS))) + + async_add_entities(switches) + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Config flow for Switch platform.""" + + switches = [] + for conf in config_entry.data[CONF_DEVICES]: + if conf.get(CONF_TYPE) == Platform.SWITCH: + device = _GryfOutput( + conf.get(CONF_NAME), + conf.get(CONF_ID) // 10, + conf.get(CONF_ID) % 10, + config_entry.runtime_data[CONF_API], + ) + switches.append( + GryfConfigFlowSwitch(device, config_entry, conf.get(CONF_EXTRA, None)) + ) + + async_add_entities(switches) + + +class GryfSwitchBase(SwitchEntity): + """Gryf Switch entity base.""" + + _is_on = False + _device: _GryfDevice + _attr_device_class = SwitchDeviceClass.SWITCH + + @property + def is_on(self): + """Property is on.""" + + return self._is_on + + async def async_update(self, is_on): + """Update state.""" + + self._is_on = is_on + self.async_write_ha_state() + + async def async_turn_on(self, **kwargs): + """Turn on switch.""" + + await self._device.turn_on() + + async def async_turn_off(self, **kwargs): + """Turn off switch.""" + + await self._device.turn_off() + + async def async_toggle(self, **kwargs): + """Toggle switch.""" + + await self._device.toggle() + + +class GryfConfigFlowSwitch(GryfConfigFlowEntity, GryfSwitchBase): + """Gryf Smart config flow Switch class.""" + + def __init__( + self, device: _GryfDevice, config_entry: ConfigEntry, device_class: str + ) -> None: + """Init the Gryf Switch.""" + + self._config_entry = config_entry + super().__init__(config_entry, device) + self._device.subscribe(self.async_update) + + self._attr_device_class = SWITCH_DEVICE_CLASS[device_class] + + +class GryfYamlSwitch(GryfYamlEntity, GryfSwitchBase): + """Gryf Smart yaml Switch class.""" + + def __init__( + self, + device: _GryfDevice, + device_class: str, + ) -> None: + """Init the Gryf Switch.""" + + super().__init__(device) + self._device.subscribe(self.async_update) + + self._attr_device_class = SWITCH_DEVICE_CLASS[device_class] diff --git a/requirements_all.txt b/requirements_all.txt index 7df77a06de4515..768f30881a84e4 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1981,7 +1981,7 @@ pyfttt==0.3 pygatt[GATTTOOL]==4.0.5 # homeassistant.components.gryfsmart -pygryfsmart==0.1.7 +pygryfsmart==0.1.8 # homeassistant.components.gtfs pygtfs==0.1.9 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 1c12fb7ae1dd63..4e4b32b1ae33c4 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1613,7 +1613,7 @@ pyfritzhome==0.6.14 pyfttt==0.3 # homeassistant.components.gryfsmart -pygryfsmart==0.1.7 +pygryfsmart==0.1.8 # homeassistant.components.hvv_departures pygti==0.9.4 From fc9c7c6651d718762b3fef2343e51965be270bce Mon Sep 17 00:00:00 2001 From: karlowiczpl Date: Fri, 7 Feb 2025 22:17:48 +0100 Subject: [PATCH 08/12] added GryfSmart integration --- homeassistant/components/gryfsmart/README.md | 188 ++++++++++++++++++ .../components/gryfsmart/__init__.py | 2 +- homeassistant/components/gryfsmart/climate.py | 178 +++++++++++++++++ .../components/gryfsmart/config_flow.py | 10 +- homeassistant/components/gryfsmart/const.py | 7 +- .../components/gryfsmart/manifest.json | 2 +- homeassistant/components/gryfsmart/schema.py | 15 ++ requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 9 files changed, 399 insertions(+), 7 deletions(-) create mode 100644 homeassistant/components/gryfsmart/README.md create mode 100644 homeassistant/components/gryfsmart/climate.py diff --git a/homeassistant/components/gryfsmart/README.md b/homeassistant/components/gryfsmart/README.md new file mode 100644 index 00000000000000..7dc6227828238b --- /dev/null +++ b/homeassistant/components/gryfsmart/README.md @@ -0,0 +1,188 @@ + +--- + +# GRYF_SMART Integration for Home Assistant + +This document describes how to configure and use the GRYFSMART integration with Home Assistant. The integration supports both YAML configuration and Config Flow (UI-based setup). + +--- + +## 1. Important Integration Information + +### 1.1 How to Use the ID + +The ID is a combination of the driver ID and the cell number (for outputs, inputs, etc.). +``` + XY + - X is the driver ID + - Y is the cell number +``` + +### 1.2 Communication + +To connect GRYFSMART, you can use either a Physical RS-232 connection or a USB/RS-232 converter. You need to know the address of the device. Typically, it is "/dev/ttyS0" for a physical RS-232 port, or "/dev/ttyUSB0" for a converter. You can list all devices using the following commands: + +##### If you are using a converter: +```bash +ls /dev/ttyUSB* # if you are using a converter +``` + +##### If you are using a physical port: +```bash +ls /dev/ttyS* # if you are using a physical port +``` + +### 1.3 Module Count + +The module count is the number of modules in the network. + +### 1.4 Entities + +**Gryf SMART driver supports 5 types of functions:** + +- **Relay Output (O)** +- **Input (I)** +- **PWM** +- **Temperature Input (T)** +- **Cover Output** + +#### 1.4.1 Light + +- **Type of function:** Relay Output +- **Services:** turn_on, turn_off +- **Icon:** lightbulb +- **Entity type:** light +- **Configuration scheme:** classic +- **Device class:** None + +#### 1.4.2 Switch + +- **Type of function:** Relay Output +- **Services:** turn_on, turn_off, toggle +- **Icon:** switch, outlet +- **Entity type:** switch +- **Configuration scheme:** device class +- **Device class:** switch, outlet +- **Default device class:** switch + +#### 1.4.3 Thermostat + +- **Type of function:** Relay Output and Temperature Input +- **Services:** turn_on, turn_off, toggle, set_temperature +- **Entity type:** climate +- **Configuration scheme:** thermostat + ```yaml + thermostat: + - name: # Name of the entity + t_id: # Thermometer ID + o_id: # Output ID + ``` + In Config Flow, you enter the t_id into the "extra" parameter and the o_id into the "id" parameter. +- **Device class:** None + +#### 1.4.4 Binary Input + +- **Type of function:** Input +- **Services:** None +- **Icon:** Specific for the chosen device class +- **Entity type:** binary_sensor +- **Configuration scheme:** device class +- **Device class:** door, garage_door, heat, light, motion, window, smoke, sound, power +- **Default device class:** opening + +#### 1.4.5 PWM + +- **Type of function:** PWM +- **Services:** turn_on, turn_off, toggle, set_value +- **Icon:** lightbulb +- **Entity type:** light +- **Configuration scheme:** classic +- **Device class:** None + +#### 1.4.6 Thermometer + +- **Type of function:** Temperature Input +- **Services:** None +- **Icon:** thermometer +- **Entity type:** sensor +- **Configuration scheme:** classic +- **Device class:** None + +#### 1.4.7 Input + +- **Type of function:** Input +- **Services:** None +- **Icon:** switch +- **Entity type:** sensor +- **Configuration scheme:** classic +- **Device class:** None +- **Extra information:** + If the input is a short press and release, the sensor state is 2; if it is a long press, the state is 3. + +--- + +## 2. Configuring via YAML + +### Example Configuration Tree +```yaml +gryfsmart: + port: "/dev/ttyS0" # RS-232 port location + module_count: 10 # Number of modules in the network + states_update: True # Enable asynchronous state updates + lights: # Lights (relay output) elements + - name: "Living Room Lamp" + id: 11 # Combined ID: controller 1, pin 1 + - name: "Kitchen Lamp" + id: 28 # Combined ID: controller 2, pin 8 + buttons: # Buttons (inputs) + - name: "Living Room Panel" + id: 17 # Combined ID: controller 1, pin 7 + climate: # Regulator (climate) elements + - name: "Regulator" + o_id: 34 # Combined ID: controller 3, pin 4 (for example) + t_id: 21 # Combined ID: controller 2, pin 1 (for example) + binary_input: + - name: "binary" + id: 34 + device_class: door +``` + +### 2.1 Configuration Schemes + +The configuration for each type follows specific rules. Remember: +- Names may include uppercase letters, numbers, and spaces, but no special characters other than “_”. +- Parameters are provided without any brackets. +- **Syntax and spacing are critical.** +- The entire configuration is stored under the key `gryfsmart:` in your `configuration.yaml`. +- The key `gryfsmart:` should appear only once. + +#### Classic Scheme +```yaml +gryf_smart: + lights: + - name: "Example Lamp" # Example name + id: 11 # Combined ID: where 1 = controller ID and 1 = pin +``` + +#### Device Class Scheme +```yaml +gryf_smart: + p_cover: + - name: "Example Blind" # Example name + id: 12 # Combined ID: 1 for controller, 2 for pin + device_class: door # Optional device class +``` + +--- + +## 3. Configuration via Config Flow + +The "extra" parameter corresponds to the device_class if it exists. In the case of a thermostat, the "extra" parameter maps to **t_id** instead. Otherwise, this parameter is not required. Additionally, the integration supports editing of individual devices and configuration. Please note that after making changes, you must reload the integration for the changes to take effect. + +--- + +## 4. Helper Entities + +Additionally, the configuration automatically generates two entities—**gryf_in** and **gryf_out**. The **gryf_in** entity receives incoming messages, and the **gryf_out** entity handles outgoing messages. However, if you are not an experienced GRYF SMART installer, you may ignore these details. + +--- diff --git a/homeassistant/components/gryfsmart/__init__.py b/homeassistant/components/gryfsmart/__init__.py index 03cd1af589d601..bf671bd96441bf 100644 --- a/homeassistant/components/gryfsmart/__init__.py +++ b/homeassistant/components/gryfsmart/__init__.py @@ -22,7 +22,7 @@ Platform.LIGHT, Platform.BINARY_SENSOR, Platform.SENSOR, - # Platform.CLIMATE, + Platform.CLIMATE, # Platform.COVER, Platform.SWITCH, # Platform.LOCK diff --git a/homeassistant/components/gryfsmart/climate.py b/homeassistant/components/gryfsmart/climate.py new file mode 100644 index 00000000000000..4274a34a8ce980 --- /dev/null +++ b/homeassistant/components/gryfsmart/climate.py @@ -0,0 +1,178 @@ +"""Handle the Gryf Smart Climate platform.""" + +from typing import Any + +from pygryfsmart.device import _GryfDevice, _GryfThermostat + +from homeassistant.components.climate import ( + ClimateEntity, + ClimateEntityFeature, + HVACAction, + HVACMode, + UnitOfTemperature, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import Platform +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType + +from .const import ( + CONF_API, + CONF_DEVICES, + CONF_EXTRA, + CONF_HYSTERESIS, + CONF_ID, + CONF_NAME, + CONF_OUT_ID, + CONF_TEMP_ID, + CONF_TYPE, + DOMAIN, + PLATFORM_THERMOSTAT, +) +from .entity import GryfConfigFlowEntity, GryfYamlEntity + + +async def async_setup_platform( + hass: HomeAssistant, + config: ConfigType, + async_add_entities: AddEntitiesCallback, + discovery_info: DiscoveryInfoType | None, +) -> None: + """Set up the climate platform.""" + + climates = [] + + for conf in hass.data[DOMAIN].get(PLATFORM_THERMOSTAT): + device = _GryfThermostat( + conf.get(CONF_NAME), + conf.get(CONF_OUT_ID) // 10, + conf.get(CONF_OUT_ID) % 10, + conf.get(CONF_TEMP_ID) // 10, + conf.get(CONF_TEMP_ID) % 10, + conf.get(CONF_HYSTERESIS, 0), + hass.data[DOMAIN][CONF_API], + ) + climates.append(GryfYamlThermostate(device)) + + async_add_entities(climates) + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Config flow for Climate platform.""" + + climates = [] + + for conf in config_entry.data[CONF_DEVICES]: + if conf.get(CONF_TYPE) == Platform.CLIMATE: + device = _GryfThermostat( + conf.get(CONF_NAME), + conf.get(CONF_ID) // 10, + conf.get(CONF_ID) % 10, + int(conf.get(CONF_EXTRA)) // 10, + int(conf.get(CONF_EXTRA)) % 10, + 0, + config_entry.runtime_data[CONF_API], + ) + climates.append(GryfConfigFlowThermostat(device, config_entry)) + + async_add_entities(climates) + + +class _GryfClimateThermostatBase(ClimateEntity): + """Gryf thermostat climate base.""" + + _device: _GryfThermostat + _attr_hvac_mode = HVACMode.OFF + _attr_hvac_modes = [HVACMode.OFF, HVACMode.HEAT] + _attr_hvac_action = HVACAction.OFF + _attr_target_temperature_high = 40 + _attr_target_temperature_low = 5 + _attr_temperature_unit = UnitOfTemperature.CELSIUS + _attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE + + _temperature = 85.0 + _target_temperature = 0.0 + + async def async_update(self, state): + """Update state.""" + + self._attr_hvac_action = HVACAction.HEATING if state["out"] else HVACAction.OFF + self._temperature = state["temp"] + self.async_write_ha_state() + + @property + def current_temperature(self) -> float | None: + return self._temperature + + @property + def target_temperature(self) -> float: + return self._target_temperature + + async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: + """Set new target HVAC mode.""" + + if hvac_mode == HVACMode.OFF: + self._device.enable(False) + self._attr_hvac_mode = HVACMode.OFF + + elif hvac_mode == HVACMode.HEAT: + self._device.enable(True) + self._attr_hvac_mode = HVACMode.HEAT + + self.async_write_ha_state() + + def turn_on(self): + """Turn the entity on.""" + + self._device.enable(True) + self._attr_hvac_mode = HVACMode.HEAT + self.async_write_ha_state() + + def turn_off(self): + """Turn the entity off.""" + + self._device.enable(False) + self._attr_hvac_mode = HVACMode.OFF + self.async_write_ha_state() + + def toggle(self): + """Toggle the entity.""" + + if self._attr_hvac_mode == HVACMode.OFF: + self.turn_on() + else: + self.turn_off() + + async def async_set_temperature(self, **kwargs: Any) -> None: + """Set target temperature.""" + temperature = kwargs.get("temperature", 0.0) + + self._target_temperature = temperature + self._device.set_target_temperature(temperature) + + +class GryfConfigFlowThermostat(GryfConfigFlowEntity, _GryfClimateThermostatBase): + """Gryf Smart config flow thermostat class.""" + + def __init__( + self, + device: _GryfDevice, + config_entry: ConfigEntry, + ) -> None: + """Init the gryf thermostat.""" + super().__init__(config_entry, device) + device.subscribe(self.async_update) + + +class GryfYamlThermostate(GryfYamlEntity, _GryfClimateThermostatBase): + """Gryf smart yaml thermostat class.""" + + def __init__(self, device: _GryfDevice) -> None: + """Init the gryf thermostat.""" + super().__init__(device) + device.subscribe(self.async_update) diff --git a/homeassistant/components/gryfsmart/config_flow.py b/homeassistant/components/gryfsmart/config_flow.py index 3dcf148b97f056..1ef3f450416c23 100644 --- a/homeassistant/components/gryfsmart/config_flow.py +++ b/homeassistant/components/gryfsmart/config_flow.py @@ -435,7 +435,7 @@ async def async_step_communication( def check_extra_parameter( - extra_parameter: Any | None, + extra_parameter: Any, device_type: Any | None, ) -> str | None: """Check extra parameter.""" @@ -443,8 +443,14 @@ def check_extra_parameter( if device_type == Platform.BINARY_SENSOR: if extra_parameter not in BINARY_SENSOR_DEVICE_CLASS: return "Bad binary sensor extra parameter!" - if device_type == Platform.SWITCH: + elif device_type == Platform.SWITCH: if extra_parameter not in SWITCH_DEVICE_CLASS: return "Bad Output extra parameter!" + elif device_type == Platform.CLIMATE: + try: + if int(extra_parameter) > 10: + return None + except ValueError: + return "Bad Thermostate extra parameter!" return None diff --git a/homeassistant/components/gryfsmart/const.py b/homeassistant/components/gryfsmart/const.py index 633d4027be24b4..9cac40a856277c 100755 --- a/homeassistant/components/gryfsmart/const.py +++ b/homeassistant/components/gryfsmart/const.py @@ -20,13 +20,17 @@ CONF_DEVICE_DATA = "device_data" CONF_INPUTS = "input" CONF_DEVICE_CLASS = "device_class" +CONF_OUT_ID = "o_id" +CONF_TEMP_ID = "t_id" +CONF_HYSTERESIS = "hysteresis" PLATFORM_PWM = "pwm" PLATFORM_TEMPERATURE = "temperature" PLATFORM_INPUT = "input" PLATFORM_LIGHT = "light" -PLATFORM_BINARY_SENSOR = "binary_sensor" +PLATFORM_BINARY_SENSOR = "binary_input" PLATFORM_SWITCH = "output" +PLATFORM_THERMOSTAT = "thermostat" DEFAULT_PORT = "/dev/ttyUSB0" GRYF_IN_NAME = "Gryf IN" @@ -47,6 +51,7 @@ Platform.BINARY_SENSOR: "Binary input", # Platform.LOCK: "Lock", # Platform.CLIMATE: "Thermostat", + Platform.CLIMATE: "Thermostat", PLATFORM_PWM: "PWM", PLATFORM_TEMPERATURE: "Termometr", PLATFORM_INPUT: "Input", diff --git a/homeassistant/components/gryfsmart/manifest.json b/homeassistant/components/gryfsmart/manifest.json index 7f09c63d88f3ca..2fb9bb0d92d167 100644 --- a/homeassistant/components/gryfsmart/manifest.json +++ b/homeassistant/components/gryfsmart/manifest.json @@ -7,7 +7,7 @@ "homekit": {}, "iot_class": "local_push", "quality_scale": "bronze", - "requirements": ["pygryfsmart==0.1.8"], + "requirements": ["pygryfsmart==0.1.9"], "ssdp": [], "zeroconf": [] } diff --git a/homeassistant/components/gryfsmart/schema.py b/homeassistant/components/gryfsmart/schema.py index 10fa6a008ae8b8..fa25a6c7f900c5 100644 --- a/homeassistant/components/gryfsmart/schema.py +++ b/homeassistant/components/gryfsmart/schema.py @@ -6,16 +6,20 @@ from .const import ( CONF_DEVICE_CLASS, + CONF_HYSTERESIS, CONF_ID, CONF_MODULE_COUNT, CONF_NAME, + CONF_OUT_ID, CONF_PORT, + CONF_TEMP_ID, DOMAIN, PLATFORM_BINARY_SENSOR, PLATFORM_INPUT, PLATFORM_LIGHT, PLATFORM_PWM, PLATFORM_SWITCH, + PLATFORM_THERMOSTAT, ) STANDARD_SCHEMA = vol.Schema( @@ -31,6 +35,14 @@ vol.Optional(CONF_DEVICE_CLASS): cv.string, } ) +CLIMATE_SCHEMA = vol.Schema( + { + vol.Required(CONF_NAME): cv.string, + vol.Required(CONF_OUT_ID): cv.positive_int, + vol.Required(CONF_TEMP_ID): cv.positive_int, + vol.Optional(CONF_HYSTERESIS): cv.positive_int, + } +) CONFIG_SCHEMA = vol.Schema( { @@ -51,6 +63,9 @@ vol.Optional(PLATFORM_SWITCH): vol.All( cv.ensure_list, [DEVICE_CLASS_SCHEMA] ), + vol.Optional(PLATFORM_THERMOSTAT): vol.All( + cv.ensure_list, [CLIMATE_SCHEMA] + ), } ) }, diff --git a/requirements_all.txt b/requirements_all.txt index 768f30881a84e4..f6adcde08411fc 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1981,7 +1981,7 @@ pyfttt==0.3 pygatt[GATTTOOL]==4.0.5 # homeassistant.components.gryfsmart -pygryfsmart==0.1.8 +pygryfsmart==0.1.9 # homeassistant.components.gtfs pygtfs==0.1.9 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 4e4b32b1ae33c4..13736501c359eb 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1613,7 +1613,7 @@ pyfritzhome==0.6.14 pyfttt==0.3 # homeassistant.components.gryfsmart -pygryfsmart==0.1.8 +pygryfsmart==0.1.9 # homeassistant.components.hvv_departures pygti==0.9.4 From ab349974cb8c0e978e81824e4f95805da39720cc Mon Sep 17 00:00:00 2001 From: karlowiczpl Date: Fri, 7 Feb 2025 22:18:29 +0100 Subject: [PATCH 09/12] added GryfSmart integration --- homeassistant/components/gryfsmart/README.md | 188 ------------------- 1 file changed, 188 deletions(-) delete mode 100644 homeassistant/components/gryfsmart/README.md diff --git a/homeassistant/components/gryfsmart/README.md b/homeassistant/components/gryfsmart/README.md deleted file mode 100644 index 7dc6227828238b..00000000000000 --- a/homeassistant/components/gryfsmart/README.md +++ /dev/null @@ -1,188 +0,0 @@ - ---- - -# GRYF_SMART Integration for Home Assistant - -This document describes how to configure and use the GRYFSMART integration with Home Assistant. The integration supports both YAML configuration and Config Flow (UI-based setup). - ---- - -## 1. Important Integration Information - -### 1.1 How to Use the ID - -The ID is a combination of the driver ID and the cell number (for outputs, inputs, etc.). -``` - XY - - X is the driver ID - - Y is the cell number -``` - -### 1.2 Communication - -To connect GRYFSMART, you can use either a Physical RS-232 connection or a USB/RS-232 converter. You need to know the address of the device. Typically, it is "/dev/ttyS0" for a physical RS-232 port, or "/dev/ttyUSB0" for a converter. You can list all devices using the following commands: - -##### If you are using a converter: -```bash -ls /dev/ttyUSB* # if you are using a converter -``` - -##### If you are using a physical port: -```bash -ls /dev/ttyS* # if you are using a physical port -``` - -### 1.3 Module Count - -The module count is the number of modules in the network. - -### 1.4 Entities - -**Gryf SMART driver supports 5 types of functions:** - -- **Relay Output (O)** -- **Input (I)** -- **PWM** -- **Temperature Input (T)** -- **Cover Output** - -#### 1.4.1 Light - -- **Type of function:** Relay Output -- **Services:** turn_on, turn_off -- **Icon:** lightbulb -- **Entity type:** light -- **Configuration scheme:** classic -- **Device class:** None - -#### 1.4.2 Switch - -- **Type of function:** Relay Output -- **Services:** turn_on, turn_off, toggle -- **Icon:** switch, outlet -- **Entity type:** switch -- **Configuration scheme:** device class -- **Device class:** switch, outlet -- **Default device class:** switch - -#### 1.4.3 Thermostat - -- **Type of function:** Relay Output and Temperature Input -- **Services:** turn_on, turn_off, toggle, set_temperature -- **Entity type:** climate -- **Configuration scheme:** thermostat - ```yaml - thermostat: - - name: # Name of the entity - t_id: # Thermometer ID - o_id: # Output ID - ``` - In Config Flow, you enter the t_id into the "extra" parameter and the o_id into the "id" parameter. -- **Device class:** None - -#### 1.4.4 Binary Input - -- **Type of function:** Input -- **Services:** None -- **Icon:** Specific for the chosen device class -- **Entity type:** binary_sensor -- **Configuration scheme:** device class -- **Device class:** door, garage_door, heat, light, motion, window, smoke, sound, power -- **Default device class:** opening - -#### 1.4.5 PWM - -- **Type of function:** PWM -- **Services:** turn_on, turn_off, toggle, set_value -- **Icon:** lightbulb -- **Entity type:** light -- **Configuration scheme:** classic -- **Device class:** None - -#### 1.4.6 Thermometer - -- **Type of function:** Temperature Input -- **Services:** None -- **Icon:** thermometer -- **Entity type:** sensor -- **Configuration scheme:** classic -- **Device class:** None - -#### 1.4.7 Input - -- **Type of function:** Input -- **Services:** None -- **Icon:** switch -- **Entity type:** sensor -- **Configuration scheme:** classic -- **Device class:** None -- **Extra information:** - If the input is a short press and release, the sensor state is 2; if it is a long press, the state is 3. - ---- - -## 2. Configuring via YAML - -### Example Configuration Tree -```yaml -gryfsmart: - port: "/dev/ttyS0" # RS-232 port location - module_count: 10 # Number of modules in the network - states_update: True # Enable asynchronous state updates - lights: # Lights (relay output) elements - - name: "Living Room Lamp" - id: 11 # Combined ID: controller 1, pin 1 - - name: "Kitchen Lamp" - id: 28 # Combined ID: controller 2, pin 8 - buttons: # Buttons (inputs) - - name: "Living Room Panel" - id: 17 # Combined ID: controller 1, pin 7 - climate: # Regulator (climate) elements - - name: "Regulator" - o_id: 34 # Combined ID: controller 3, pin 4 (for example) - t_id: 21 # Combined ID: controller 2, pin 1 (for example) - binary_input: - - name: "binary" - id: 34 - device_class: door -``` - -### 2.1 Configuration Schemes - -The configuration for each type follows specific rules. Remember: -- Names may include uppercase letters, numbers, and spaces, but no special characters other than “_”. -- Parameters are provided without any brackets. -- **Syntax and spacing are critical.** -- The entire configuration is stored under the key `gryfsmart:` in your `configuration.yaml`. -- The key `gryfsmart:` should appear only once. - -#### Classic Scheme -```yaml -gryf_smart: - lights: - - name: "Example Lamp" # Example name - id: 11 # Combined ID: where 1 = controller ID and 1 = pin -``` - -#### Device Class Scheme -```yaml -gryf_smart: - p_cover: - - name: "Example Blind" # Example name - id: 12 # Combined ID: 1 for controller, 2 for pin - device_class: door # Optional device class -``` - ---- - -## 3. Configuration via Config Flow - -The "extra" parameter corresponds to the device_class if it exists. In the case of a thermostat, the "extra" parameter maps to **t_id** instead. Otherwise, this parameter is not required. Additionally, the integration supports editing of individual devices and configuration. Please note that after making changes, you must reload the integration for the changes to take effect. - ---- - -## 4. Helper Entities - -Additionally, the configuration automatically generates two entities—**gryf_in** and **gryf_out**. The **gryf_in** entity receives incoming messages, and the **gryf_out** entity handles outgoing messages. However, if you are not an experienced GRYF SMART installer, you may ignore these details. - ---- From 2c0fd8819e42507fa6c7426d3eb03d6cf441ec22 Mon Sep 17 00:00:00 2001 From: karlowiczpl Date: Fri, 7 Feb 2025 22:41:40 +0100 Subject: [PATCH 10/12] added lights platform to GryfSmart --- homeassistant/components/gryfsmart/const.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/gryfsmart/const.py b/homeassistant/components/gryfsmart/const.py index 9cac40a856277c..a73edba0337216 100755 --- a/homeassistant/components/gryfsmart/const.py +++ b/homeassistant/components/gryfsmart/const.py @@ -44,17 +44,13 @@ } DEVICE_TYPES = { - # Platform.COVER: "Shutter", Platform.LIGHT: "Lights", - Platform.SWITCH: "Output", - # Platform.SENSOR: "Input", - Platform.BINARY_SENSOR: "Binary input", - # Platform.LOCK: "Lock", + # Platform.SWITCH: "Output", + # Platform.BINARY_SENSOR: "Binary input", # Platform.CLIMATE: "Thermostat", - Platform.CLIMATE: "Thermostat", - PLATFORM_PWM: "PWM", - PLATFORM_TEMPERATURE: "Termometr", - PLATFORM_INPUT: "Input", + # PLATFORM_PWM: "PWM", + # PLATFORM_TEMPERATURE: "Termometr", + # PLATFORM_INPUT: "Input", } CONF_LINE_SENSOR_ICONS = { From ab8dbfe70481ef227720f39ec3114c5cf1b77e7a Mon Sep 17 00:00:00 2001 From: karlowiczpl Date: Fri, 7 Feb 2025 22:54:07 +0100 Subject: [PATCH 11/12] Added light platform to gryfSmart Integration --- .../components/gryfsmart/binary_sensor.py | 127 -------- homeassistant/components/gryfsmart/climate.py | 178 ----------- homeassistant/components/gryfsmart/sensor.py | 300 ------------------ homeassistant/components/gryfsmart/switch.py | 134 -------- 4 files changed, 739 deletions(-) delete mode 100644 homeassistant/components/gryfsmart/binary_sensor.py delete mode 100644 homeassistant/components/gryfsmart/climate.py delete mode 100644 homeassistant/components/gryfsmart/sensor.py delete mode 100644 homeassistant/components/gryfsmart/switch.py diff --git a/homeassistant/components/gryfsmart/binary_sensor.py b/homeassistant/components/gryfsmart/binary_sensor.py deleted file mode 100644 index 384b5f5f48f760..00000000000000 --- a/homeassistant/components/gryfsmart/binary_sensor.py +++ /dev/null @@ -1,127 +0,0 @@ -"""Handle the Gryf Smart binary sensor platform functionality.""" - -from pygryfsmart.device import _GryfDevice, _GryfInput - -from homeassistant.components.binary_sensor import ( - BinarySensorDeviceClass, - BinarySensorEntity, -) -from homeassistant.config_entries import ConfigEntry -from homeassistant.const import Platform -from homeassistant.core import HomeAssistant -from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType - -from .const import ( - CONF_API, - CONF_DEVICE_CLASS, - CONF_DEVICES, - CONF_EXTRA, - CONF_ID, - CONF_NAME, - CONF_TYPE, - DOMAIN, - PLATFORM_BINARY_SENSOR, -) -from .entity import GryfConfigFlowEntity, GryfYamlEntity - - -async def async_setup_platform( - hass: HomeAssistant, - config: ConfigType, - async_add_entities: AddEntitiesCallback, - discoveryInfo: DiscoveryInfoType | None, -) -> None: - """Set up the binary sensor platform.""" - - binary_sensors = [] - - for conf in hass.data[DOMAIN].get(PLATFORM_BINARY_SENSOR, {}): - device = _GryfInput( - conf.get(CONF_NAME), - conf.get(CONF_ID) // 10, - conf.get(CONF_ID) % 10, - hass.data[DOMAIN][CONF_API], - ) - binary_sensors.append(GryfYamlBinarySensor(device, conf.get(CONF_DEVICE_CLASS))) - - async_add_entities(binary_sensors) - - -async def async_setup_entry( - hass: HomeAssistant, - config_entry: ConfigEntry, - async_add_entities: AddEntitiesCallback, -) -> None: - """Config flow for binary sensor platform.""" - - binary_sensors = [] - - for conf in config_entry.data[CONF_DEVICES]: - if conf.get(CONF_TYPE) == Platform.BINARY_SENSOR: - device = _GryfInput( - conf.get(CONF_NAME), - conf.get(CONF_ID) // 10, - conf.get(CONF_ID) % 10, - config_entry.runtime_data[CONF_API], - ) - binary_sensors.append( - GryfConfigFlowBinarySensor( - device, - config_entry, - conf.get(CONF_EXTRA), - ) - ) - - async_add_entities(binary_sensors) - - -class _GryfBinarySensorBase(BinarySensorEntity): - """Gryf Binary Sensor base.""" - - _is_on = False - _attr_device_class = BinarySensorDeviceClass.OPENING - - @property - def is_on(self) -> bool: - return not self._is_on - - async def async_update(self, state): - if state in [0, 1]: - self._is_on = state - self.async_write_ha_state() - - -class GryfConfigFlowBinarySensor(GryfConfigFlowEntity, _GryfBinarySensorBase): - """Gryf Smart config flow binary sensor class.""" - - def __init__( - self, - device: _GryfDevice, - config_entry: ConfigEntry, - device_class: BinarySensorDeviceClass | None, - ) -> None: - """Init the gryf binary sensor.""" - - super().__init__(config_entry, device) - device.subscribe(self.async_update) - - if device_class: - self._attr_device_class = device_class - - -class GryfYamlBinarySensor(GryfYamlEntity, _GryfBinarySensorBase): - """Gryf Smart yaml input line class.""" - - def __init__( - self, - device: _GryfDevice, - device_class: BinarySensorDeviceClass | None, - ) -> None: - """Init the gryf input line.""" - - super().__init__(device) - device.subscribe(self.async_update) - - if device_class: - self._attr_device_class = device_class diff --git a/homeassistant/components/gryfsmart/climate.py b/homeassistant/components/gryfsmart/climate.py deleted file mode 100644 index 4274a34a8ce980..00000000000000 --- a/homeassistant/components/gryfsmart/climate.py +++ /dev/null @@ -1,178 +0,0 @@ -"""Handle the Gryf Smart Climate platform.""" - -from typing import Any - -from pygryfsmart.device import _GryfDevice, _GryfThermostat - -from homeassistant.components.climate import ( - ClimateEntity, - ClimateEntityFeature, - HVACAction, - HVACMode, - UnitOfTemperature, -) -from homeassistant.config_entries import ConfigEntry -from homeassistant.const import Platform -from homeassistant.core import HomeAssistant -from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType - -from .const import ( - CONF_API, - CONF_DEVICES, - CONF_EXTRA, - CONF_HYSTERESIS, - CONF_ID, - CONF_NAME, - CONF_OUT_ID, - CONF_TEMP_ID, - CONF_TYPE, - DOMAIN, - PLATFORM_THERMOSTAT, -) -from .entity import GryfConfigFlowEntity, GryfYamlEntity - - -async def async_setup_platform( - hass: HomeAssistant, - config: ConfigType, - async_add_entities: AddEntitiesCallback, - discovery_info: DiscoveryInfoType | None, -) -> None: - """Set up the climate platform.""" - - climates = [] - - for conf in hass.data[DOMAIN].get(PLATFORM_THERMOSTAT): - device = _GryfThermostat( - conf.get(CONF_NAME), - conf.get(CONF_OUT_ID) // 10, - conf.get(CONF_OUT_ID) % 10, - conf.get(CONF_TEMP_ID) // 10, - conf.get(CONF_TEMP_ID) % 10, - conf.get(CONF_HYSTERESIS, 0), - hass.data[DOMAIN][CONF_API], - ) - climates.append(GryfYamlThermostate(device)) - - async_add_entities(climates) - - -async def async_setup_entry( - hass: HomeAssistant, - config_entry: ConfigEntry, - async_add_entities: AddEntitiesCallback, -) -> None: - """Config flow for Climate platform.""" - - climates = [] - - for conf in config_entry.data[CONF_DEVICES]: - if conf.get(CONF_TYPE) == Platform.CLIMATE: - device = _GryfThermostat( - conf.get(CONF_NAME), - conf.get(CONF_ID) // 10, - conf.get(CONF_ID) % 10, - int(conf.get(CONF_EXTRA)) // 10, - int(conf.get(CONF_EXTRA)) % 10, - 0, - config_entry.runtime_data[CONF_API], - ) - climates.append(GryfConfigFlowThermostat(device, config_entry)) - - async_add_entities(climates) - - -class _GryfClimateThermostatBase(ClimateEntity): - """Gryf thermostat climate base.""" - - _device: _GryfThermostat - _attr_hvac_mode = HVACMode.OFF - _attr_hvac_modes = [HVACMode.OFF, HVACMode.HEAT] - _attr_hvac_action = HVACAction.OFF - _attr_target_temperature_high = 40 - _attr_target_temperature_low = 5 - _attr_temperature_unit = UnitOfTemperature.CELSIUS - _attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE - - _temperature = 85.0 - _target_temperature = 0.0 - - async def async_update(self, state): - """Update state.""" - - self._attr_hvac_action = HVACAction.HEATING if state["out"] else HVACAction.OFF - self._temperature = state["temp"] - self.async_write_ha_state() - - @property - def current_temperature(self) -> float | None: - return self._temperature - - @property - def target_temperature(self) -> float: - return self._target_temperature - - async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: - """Set new target HVAC mode.""" - - if hvac_mode == HVACMode.OFF: - self._device.enable(False) - self._attr_hvac_mode = HVACMode.OFF - - elif hvac_mode == HVACMode.HEAT: - self._device.enable(True) - self._attr_hvac_mode = HVACMode.HEAT - - self.async_write_ha_state() - - def turn_on(self): - """Turn the entity on.""" - - self._device.enable(True) - self._attr_hvac_mode = HVACMode.HEAT - self.async_write_ha_state() - - def turn_off(self): - """Turn the entity off.""" - - self._device.enable(False) - self._attr_hvac_mode = HVACMode.OFF - self.async_write_ha_state() - - def toggle(self): - """Toggle the entity.""" - - if self._attr_hvac_mode == HVACMode.OFF: - self.turn_on() - else: - self.turn_off() - - async def async_set_temperature(self, **kwargs: Any) -> None: - """Set target temperature.""" - temperature = kwargs.get("temperature", 0.0) - - self._target_temperature = temperature - self._device.set_target_temperature(temperature) - - -class GryfConfigFlowThermostat(GryfConfigFlowEntity, _GryfClimateThermostatBase): - """Gryf Smart config flow thermostat class.""" - - def __init__( - self, - device: _GryfDevice, - config_entry: ConfigEntry, - ) -> None: - """Init the gryf thermostat.""" - super().__init__(config_entry, device) - device.subscribe(self.async_update) - - -class GryfYamlThermostate(GryfYamlEntity, _GryfClimateThermostatBase): - """Gryf smart yaml thermostat class.""" - - def __init__(self, device: _GryfDevice) -> None: - """Init the gryf thermostat.""" - super().__init__(device) - device.subscribe(self.async_update) diff --git a/homeassistant/components/gryfsmart/sensor.py b/homeassistant/components/gryfsmart/sensor.py deleted file mode 100644 index e148a175dfb568..00000000000000 --- a/homeassistant/components/gryfsmart/sensor.py +++ /dev/null @@ -1,300 +0,0 @@ -"""Handle the Gryf Smart Sensor platform functionality.""" - -from pygryfsmart.device import ( - _GryfDevice, - _GryfInput, - _GryfInputLine, - _GryfOutputLine, - _GryfTemperature, -) - -from homeassistant.components.sensor import SensorDeviceClass, SensorEntity -from homeassistant.config_entries import ConfigEntry -from homeassistant.core import HomeAssistant -from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType - -from .const import ( - CONF_API, - CONF_DEVICES, - CONF_ID, - CONF_LINE_SENSOR_ICONS, - CONF_NAME, - CONF_TYPE, - DOMAIN, - GRYF_IN_NAME, - GRYF_OUT_NAME, - PLATFORM_INPUT, - PLATFORM_TEMPERATURE, -) -from .entity import GryfConfigFlowEntity, GryfYamlEntity - - -async def async_setup_platform( - hass: HomeAssistant, - config: ConfigType, - async_add_entities: AddEntitiesCallback, - discovery_info: DiscoveryInfoType | None, -) -> None: - """Set up the Sensor platform.""" - - async_add_entities( - [ - GryfYamlLine( - _GryfInputLine( - GRYF_IN_NAME, - hass.data[DOMAIN][CONF_API], - ), - GRYF_IN_NAME, - ) - ] - ) - async_add_entities( - [ - GryfYamlLine( - _GryfOutputLine( - GRYF_OUT_NAME, - hass.data[DOMAIN][CONF_API], - ), - GRYF_OUT_NAME, - ) - ] - ) - - inputs = [] - - for conf in hass.data[DOMAIN].get(PLATFORM_INPUT, {}): - device = _GryfInput( - conf.get(CONF_NAME), - conf.get(CONF_ID) // 10, - conf.get(CONF_ID) % 10, - hass.data[DOMAIN][CONF_API], - ) - inputs.append(GryfYamlInput(device)) - - async_add_entities(inputs) - - -async def async_setup_entry( - hass: HomeAssistant, - config_entry: ConfigEntry, - async_add_entities: AddEntitiesCallback, -) -> None: - """Config flow for Sensor platform.""" - async_add_entities( - [ - GryfConfigFlowLine( - _GryfInputLine( - GRYF_IN_NAME, - config_entry.runtime_data[CONF_API], - ), - config_entry, - GRYF_IN_NAME, - ) - ] - ) - async_add_entities( - [ - GryfConfigFlowLine( - _GryfOutputLine( - GRYF_OUT_NAME, - config_entry.runtime_data[CONF_API], - ), - config_entry, - GRYF_OUT_NAME, - ) - ] - ) - - inputs = [] - temperature = [] - - for conf in config_entry.data[CONF_DEVICES]: - if conf.get(CONF_TYPE) == PLATFORM_INPUT: - device = _GryfInput( - conf.get(CONF_NAME), - conf.get(CONF_ID) // 10, - conf.get(CONF_ID) % 10, - config_entry.runtime_data[CONF_API], - ) - inputs.append(GryfConfigFlowInput(device, config_entry)) - if conf.get(CONF_TYPE) == PLATFORM_TEMPERATURE: - temperature_device = _GryfTemperature( - conf.get(CONF_NAME), - conf.get(CONF_ID) // 10, - conf.get(CONF_ID) % 10, - config_entry.runtime_data[CONF_API], - ) - temperature.append( - GryfConfigFlowTemperature(temperature_device, config_entry) - ) - - async_add_entities(inputs) - async_add_entities(temperature) - - -class _GryfLineSensorBase(SensorEntity): - """Gryf line sensor base.""" - - _state = "" - _last_icon = False - _attr_icon = CONF_LINE_SENSOR_ICONS[GRYF_IN_NAME][0] - _input: str - - @property - def native_value(self) -> str: - """Return state.""" - return self._state - - async def async_update(self, state): - """Update state.""" - - self._state = state - self._last_icon = not self._last_icon - self.async_write_ha_state() - - @property - def icon(self) -> str: - """Property icon.""" - - return CONF_LINE_SENSOR_ICONS[self._input][self._last_icon] - - -class GryfConfigFlowLine(GryfConfigFlowEntity, _GryfLineSensorBase): - """Gryf Smart config flow input line class.""" - - def __init__( - self, - device: _GryfDevice, - config_entry: ConfigEntry, - input: str, - ) -> None: - """Init the gryf input line.""" - - self._input = input - super().__init__(config_entry, device) - device.subscribe(self.async_update) - - -class GryfYamlLine(GryfYamlEntity, _GryfLineSensorBase): - """Gryf Smart yaml input line class.""" - - def __init__( - self, - device: _GryfDevice, - input: str, - ) -> None: - """Init the gryf input line.""" - - self._input = input - super().__init__(device) - device.subscribe(self.async_update) - - -class _GryfInputSensorBase(SensorEntity): - """Gryf smart input sensor Base.""" - - _state = "0" - _device: _GryfDevice - _attr_icon = "mdi:light-switch-off" - _attr_device_class = SensorDeviceClass.ENUM - _attr_options = ["0", "1", "2", "3"] - - async def async_update(self, data): - """Update state.""" - - self._state = str(data) - self.async_write_ha_state() - - @property - def native_value(self) -> str: - """Property state.""" - - return self._state - - @property - def icon(self) -> str: - """Property icon.""" - - icon_mapper = { - "0": "mdi:light-switch-off", - "1": "mdi:light-switch", - "2": "mdi:gesture-tap", - "3": "mdi:gesture-tap-hold", - } - - return icon_mapper[self._state] - - -class GryfConfigFlowInput(GryfConfigFlowEntity, _GryfInputSensorBase): - """Gryf Smart config flow input class.""" - - def __init__( - self, - device: _GryfDevice, - config_entry: ConfigEntry, - ) -> None: - """Init the gryf input.""" - - super().__init__(config_entry, device) - device.subscribe(self.async_update) - - -class GryfYamlInput(GryfYamlEntity, _GryfInputSensorBase): - """Gryf Smart yaml input line class.""" - - def __init__( - self, - device: _GryfDevice, - ) -> None: - """Init the gryf input line.""" - - super().__init__(device) - device.subscribe(self.async_update) - - -class _GryfTemperatureSensorBase(SensorEntity): - """Gryf Smart temperature sensor base.""" - - _state = "0.0" - _device: _GryfDevice - _attr_device_class = SensorDeviceClass.TEMPERATURE - _attr_native_unit_of_measurement = "°C" - - async def async_update(self, data): - """Update state.""" - - self._state = data - self.async_write_ha_state() - - @property - def native_value(self) -> str: - """Property state.""" - - return self._state - - -class GryfConfigFlowTemperature(GryfConfigFlowEntity, _GryfTemperatureSensorBase): - """Gryf Smart config flow temperature class.""" - - def __init__( - self, - device: _GryfDevice, - config_entry: ConfigEntry, - ) -> None: - """Init the gryf temperature.""" - super().__init__(config_entry, device) - device.subscribe(self.async_update) - - -class GryfYamlTemperature(GryfYamlEntity, _GryfTemperatureSensorBase): - """Gryf Smart yaml input line class.""" - - def __init__( - self, - device: _GryfDevice, - ) -> None: - """Init the gryf input line.""" - - super().__init__(device) - device.subscribe(self.async_update) diff --git a/homeassistant/components/gryfsmart/switch.py b/homeassistant/components/gryfsmart/switch.py deleted file mode 100644 index 445d7d9c37d6de..00000000000000 --- a/homeassistant/components/gryfsmart/switch.py +++ /dev/null @@ -1,134 +0,0 @@ -"""Handle the Gryf Smart Switch platform functionality.""" - -from pygryfsmart.device import _GryfDevice, _GryfOutput - -from homeassistant.components.switch import SwitchDeviceClass, SwitchEntity -from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_TYPE, Platform -from homeassistant.core import HomeAssistant -from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType - -from .const import ( - CONF_API, - CONF_DEVICE_CLASS, - CONF_DEVICES, - CONF_EXTRA, - CONF_ID, - CONF_NAME, - DOMAIN, - PLATFORM_SWITCH, - SWITCH_DEVICE_CLASS, -) -from .entity import GryfConfigFlowEntity, GryfYamlEntity - - -async def async_setup_platform( - hass: HomeAssistant, - config: ConfigType, - async_add_entities: AddEntitiesCallback, - discovery_info: DiscoveryInfoType | None, -) -> None: - """Set up the Switch platform.""" - - switches = [] - - for conf in hass.data[DOMAIN].get(PLATFORM_SWITCH): - device = _GryfOutput( - conf.get(CONF_NAME), - conf.get(CONF_ID) // 10, - conf.get(CONF_ID) % 10, - hass.data[DOMAIN][CONF_API], - ) - switches.append(GryfYamlSwitch(device, conf.get(CONF_DEVICE_CLASS))) - - async_add_entities(switches) - - -async def async_setup_entry( - hass: HomeAssistant, - config_entry: ConfigEntry, - async_add_entities: AddEntitiesCallback, -) -> None: - """Config flow for Switch platform.""" - - switches = [] - for conf in config_entry.data[CONF_DEVICES]: - if conf.get(CONF_TYPE) == Platform.SWITCH: - device = _GryfOutput( - conf.get(CONF_NAME), - conf.get(CONF_ID) // 10, - conf.get(CONF_ID) % 10, - config_entry.runtime_data[CONF_API], - ) - switches.append( - GryfConfigFlowSwitch(device, config_entry, conf.get(CONF_EXTRA, None)) - ) - - async_add_entities(switches) - - -class GryfSwitchBase(SwitchEntity): - """Gryf Switch entity base.""" - - _is_on = False - _device: _GryfDevice - _attr_device_class = SwitchDeviceClass.SWITCH - - @property - def is_on(self): - """Property is on.""" - - return self._is_on - - async def async_update(self, is_on): - """Update state.""" - - self._is_on = is_on - self.async_write_ha_state() - - async def async_turn_on(self, **kwargs): - """Turn on switch.""" - - await self._device.turn_on() - - async def async_turn_off(self, **kwargs): - """Turn off switch.""" - - await self._device.turn_off() - - async def async_toggle(self, **kwargs): - """Toggle switch.""" - - await self._device.toggle() - - -class GryfConfigFlowSwitch(GryfConfigFlowEntity, GryfSwitchBase): - """Gryf Smart config flow Switch class.""" - - def __init__( - self, device: _GryfDevice, config_entry: ConfigEntry, device_class: str - ) -> None: - """Init the Gryf Switch.""" - - self._config_entry = config_entry - super().__init__(config_entry, device) - self._device.subscribe(self.async_update) - - self._attr_device_class = SWITCH_DEVICE_CLASS[device_class] - - -class GryfYamlSwitch(GryfYamlEntity, GryfSwitchBase): - """Gryf Smart yaml Switch class.""" - - def __init__( - self, - device: _GryfDevice, - device_class: str, - ) -> None: - """Init the Gryf Switch.""" - - super().__init__(device) - self._device.subscribe(self.async_update) - - self._attr_device_class = SWITCH_DEVICE_CLASS[device_class] From 7529bafff8000b5689093bbf8f8da31548545a30 Mon Sep 17 00:00:00 2001 From: karlowiczpl Date: Fri, 7 Feb 2025 22:58:05 +0100 Subject: [PATCH 12/12] Added light platform to gryfSmart Integration --- homeassistant/components/gryfsmart/__init__.py | 10 ++++------ homeassistant/components/gryfsmart/const.py | 2 +- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/gryfsmart/__init__.py b/homeassistant/components/gryfsmart/__init__.py index bf671bd96441bf..83094aa4a7ae9e 100644 --- a/homeassistant/components/gryfsmart/__init__.py +++ b/homeassistant/components/gryfsmart/__init__.py @@ -20,12 +20,10 @@ _PLATFORMS: list[Platform] = [ Platform.LIGHT, - Platform.BINARY_SENSOR, - Platform.SENSOR, - Platform.CLIMATE, - # Platform.COVER, - Platform.SWITCH, - # Platform.LOCK + # Platform.BINARY_SENSOR, + # Platform.SENSOR, + # Platform.CLIMATE, + # Platform.SWITCH, ] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/gryfsmart/const.py b/homeassistant/components/gryfsmart/const.py index a73edba0337216..bc93a826ff054c 100755 --- a/homeassistant/components/gryfsmart/const.py +++ b/homeassistant/components/gryfsmart/const.py @@ -50,7 +50,7 @@ # Platform.CLIMATE: "Thermostat", # PLATFORM_PWM: "PWM", # PLATFORM_TEMPERATURE: "Termometr", - # PLATFORM_INPUT: "Input", + PLATFORM_INPUT: "Input", } CONF_LINE_SENSOR_ICONS = {