diff --git a/README.md b/README.md index 310dff6..a925921 100644 --- a/README.md +++ b/README.md @@ -154,6 +154,7 @@ switch: - port: 11 name: "Fan Office" unique_id: "fan_office_switch_port_11" + persistent: true - port: 12 name: "Light Desk" unique_id: "light_desk_switch_port_12" @@ -168,6 +169,7 @@ switch: | `name` | yes | | string | The name for the switch entity | | `unique_id` | no | | string | An ID that uniquely identifies the switch. Set this to a unique value to allow customization through the UI | | `invert_logic` | no | `false` | boolean | If true, inverts the output logic to ACTIVE LOW | +| `persistent` | no | `false` | boolean | If true, the switch state will be persistent in HA and will be restored if HA restart / crash | For more details about the GPIO layout, visit the Wikipedia [article](https://en.wikipedia.org/wiki/Raspberry_Pi#General_purpose_input-output_(GPIO)_connector) about the Raspberry Pi. diff --git a/custom_components/rpi_gpio/switch.py b/custom_components/rpi_gpio/switch.py index 6a49872..c15d195 100644 --- a/custom_components/rpi_gpio/switch.py +++ b/custom_components/rpi_gpio/switch.py @@ -1,5 +1,6 @@ """Allows to configure a switch using RPi GPIO.""" from __future__ import annotations +from typing import Any import voluptuous as vol @@ -10,20 +11,24 @@ from homeassistant.const import ( CONF_SWITCHES, CONF_UNIQUE_ID, DEVICE_DEFAULT_NAME, + STATE_ON, ) from homeassistant.core import HomeAssistant import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.reload import setup_reload_service from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType +from homeassistant.helpers.restore_state import RestoreEntity from . import DOMAIN, PLATFORMS, setup_output, write_output CONF_PULL_MODE = "pull_mode" CONF_PORTS = "ports" CONF_INVERT_LOGIC = "invert_logic" +CONF_PERSISTENT = "persistent" DEFAULT_INVERT_LOGIC = False +DEFAULT_PERSISTENT = False _SWITCHES_LEGACY_SCHEMA = vol.Schema({cv.positive_int: cv.string}) @@ -32,6 +37,7 @@ _SWITCH_SCHEMA = vol.Schema( vol.Required(CONF_NAME): cv.string, vol.Required(CONF_PORT): cv.positive_int, vol.Optional(CONF_INVERT_LOGIC, default=DEFAULT_INVERT_LOGIC): cv.boolean, + vol.Optional(CONF_PERSISTENT, default=DEFAULT_PERSISTENT): cv.boolean, vol.Optional(CONF_UNIQUE_ID): cv.string, } ) @@ -45,6 +51,7 @@ PLATFORM_SCHEMA = vol.All( cv.ensure_list, [_SWITCH_SCHEMA] ), vol.Optional(CONF_INVERT_LOGIC, default=DEFAULT_INVERT_LOGIC): cv.boolean, + vol.Optional(CONF_PERSISTENT, default=DEFAULT_PERSISTENT): cv.boolean, }, ), cv.has_at_least_one_key(CONF_PORTS, CONF_SWITCHES), @@ -65,31 +72,45 @@ def setup_platform( switches_conf = config.get(CONF_SWITCHES) if switches_conf is not None: for switch in switches_conf: - switches.append( - RPiGPIOSwitch( - switch[CONF_NAME], - switch[CONF_PORT], - switch[CONF_INVERT_LOGIC], - switch.get(CONF_UNIQUE_ID), + if switch[CONF_PERSISTENT]: + switches.append( + PersistentRPiGPIOSwitch( + switch[CONF_NAME], + switch[CONF_PORT], + switch[CONF_INVERT_LOGIC], + switch.get(CONF_UNIQUE_ID), + ) + ) + else: + switches.append( + RPiGPIOSwitch( + switch[CONF_NAME], + switch[CONF_PORT], + switch[CONF_INVERT_LOGIC], + switch.get(CONF_UNIQUE_ID), + ) ) - ) add_entities(switches, True) return invert_logic = config[CONF_INVERT_LOGIC] + persistent = config[CONF_PERSISTENT] ports = config[CONF_PORTS] for port, name in ports.items(): - switches.append(RPiGPIOSwitch(name, port, invert_logic)) + if persistent: + switches.append(PersistentRPiGPIOSwitch(name, port, invert_logic)) + else: + switches.append(RPiGPIOSwitch(name, port, invert_logic)) add_entities(switches) class RPiGPIOSwitch(SwitchEntity): - """Representation of a Raspberry Pi GPIO.""" + """Representation of a Raspberry Pi GPIO.""" - def __init__(self, name, port, invert_logic, unique_id=None): + def __init__(self, name, port, invert_logic, unique_id=None, skip_reset=False): """Initialize the pin.""" self._attr_name = name or DEVICE_DEFAULT_NAME self._attr_unique_id = unique_id @@ -98,21 +119,42 @@ class RPiGPIOSwitch(SwitchEntity): self._invert_logic = invert_logic self._state = False setup_output(self._port) - write_output(self._port, 1 if self._invert_logic else 0) + if not skip_reset: + write_output(self._port, 1 if self._invert_logic else 0) @property - def is_on(self): + def is_on(self) -> bool | None: """Return true if device is on.""" return self._state - def turn_on(self, **kwargs): + async def async_turn_on(self, **kwargs: Any) -> None: """Turn the device on.""" write_output(self._port, 0 if self._invert_logic else 1) self._state = True - self.schedule_update_ha_state() + self.async_write_ha_state() - def turn_off(self, **kwargs): + async def async_turn_off(self, **kwargs: Any) -> None: """Turn the device off.""" write_output(self._port, 1 if self._invert_logic else 0) self._state = False - self.schedule_update_ha_state() + self.async_write_ha_state() + + +class PersistentRPiGPIOSwitch(RPiGPIOSwitch, RestoreEntity): + """Representation of a persistent Raspberry Pi GPIO.""" + + def __init__(self, name, port, invert_logic, unique_id=None): + """Initialize the pin.""" + super().__init__(name, port, invert_logic, unique_id, True) + + async def async_added_to_hass(self) -> None: + """Call when the switch is added to hass.""" + await super().async_added_to_hass() + state = await self.async_get_last_state() + if not state: + return + self._state = True if state.state == STATE_ON else False + if self._state: + await self.async_turn_on() + else: + await self.async_turn_off() \ No newline at end of file diff --git a/info.md b/info.md index c5cd124..20b3628 100644 --- a/info.md +++ b/info.md @@ -142,6 +142,7 @@ switch: - port: 11 name: "Fan Office" unique_id: "fan_office_switch_port_11" + persistent: true - port: 12 name: "Light Desk" unique_id: "light_desk_switch_port_12" @@ -156,6 +157,7 @@ switch: | `name` | yes | | string | The name for the switch entity | | `unique_id` | no | | string | An ID that uniquely identifies the switch. Set this to a unique value to allow customization through the UI | | `invert_logic` | no | `false` | boolean | If true, inverts the output logic to ACTIVE LOW | +| `persistent` | no | `false` | boolean | If true, the switch state will be persistent in HA and will be restored if HA restart / crash | For more details about the GPIO layout, visit the Wikipedia [article](https://en.wikipedia.org/wiki/Raspberry_Pi#General_purpose_input-output_(GPIO)_connector) about the Raspberry Pi.