diff --git a/custom_components/myhome/binary_sensor.py b/custom_components/myhome/binary_sensor.py index 31f5aaa..969bedc 100644 --- a/custom_components/myhome/binary_sensor.py +++ b/custom_components/myhome/binary_sensor.py @@ -14,9 +14,12 @@ from homeassistant.helpers.restore_state import RestoreEntity from OWNd.message import ( + OWNEvent, OWNDryContactEvent, OWNDryContactCommand, OWNLightingCommand, + OWNHeatingCommand, + OWNEnergyCommand, MESSAGE_TYPE_MOTION, MESSAGE_TYPE_PIR_SENSITIVITY, MESSAGE_TYPE_MOTION_TIMEOUT, @@ -29,10 +32,14 @@ CONF_ENTITY_NAME, CONF_WHO, CONF_WHERE, + CONF_PHASE, CONF_MANUFACTURER, CONF_DEVICE_MODEL, CONF_DEVICE_CLASS, CONF_INVERTED, + CONF_BUS_INTERFACE, + CONF_ICON, + CONF_ICON_ON, DOMAIN, LOGGER, ) @@ -98,6 +105,63 @@ async def async_setup_entry(hass, config_entry, async_add_entities): gateway=hass.data[DOMAIN][config_entry.data[CONF_MAC]][CONF_ENTITY], ) _binary_sensors.append(_binary_sensor) + elif _who == 1 and _device_class == BinarySensorDeviceClass.POWER: + _binary_sensor = MyHOMEActuator( + hass=hass, + device_id=_binary_sensor, + who=_configured_binary_sensors[_binary_sensor][CONF_WHO], + where=_configured_binary_sensors[_binary_sensor][CONF_WHERE], + phase=_configured_binary_sensors[_binary_sensor][CONF_PHASE] if CONF_PHASE in _configured_binary_sensors[_binary_sensor] else None, + icon=_configured_binary_sensors[_binary_sensor][CONF_ICON], + icon_on=_configured_binary_sensors[_binary_sensor][CONF_ICON_ON], + name=_configured_binary_sensors[_binary_sensor][CONF_NAME], + entity_name=_configured_binary_sensors[_binary_sensor][CONF_ENTITY_NAME], + inverted=_configured_binary_sensors[_binary_sensor][CONF_INVERTED], + interface=_configured_binary_sensors[_binary_sensor][CONF_BUS_INTERFACE] if CONF_BUS_INTERFACE in _configured_binary_sensors[_binary_sensor] else None, + device_class=_device_class, + manufacturer=_configured_binary_sensors[_binary_sensor][CONF_MANUFACTURER], + model=_configured_binary_sensors[_binary_sensor][CONF_DEVICE_MODEL], + gateway=hass.data[DOMAIN][config_entry.data[CONF_MAC]][CONF_ENTITY], + ) + _binary_sensors.append(_binary_sensor) + elif _who == 4 and _device_class == BinarySensorDeviceClass.POWER: + _binary_sensor = MyHOMEActuator( + hass=hass, + device_id=_binary_sensor, + who=_configured_binary_sensors[_binary_sensor][CONF_WHO], + where=_configured_binary_sensors[_binary_sensor][CONF_WHERE], + phase=_configured_binary_sensors[_binary_sensor][CONF_PHASE] if CONF_PHASE in _configured_binary_sensors[_binary_sensor] else None, + icon=_configured_binary_sensors[_binary_sensor][CONF_ICON], + icon_on=_configured_binary_sensors[_binary_sensor][CONF_ICON_ON], + name=_configured_binary_sensors[_binary_sensor][CONF_NAME], + entity_name=_configured_binary_sensors[_binary_sensor][CONF_ENTITY_NAME], + inverted=_configured_binary_sensors[_binary_sensor][CONF_INVERTED], + interface=_configured_binary_sensors[_binary_sensor][CONF_BUS_INTERFACE] if CONF_BUS_INTERFACE in _configured_binary_sensors[_binary_sensor] else None, + device_class=_device_class, + manufacturer=_configured_binary_sensors[_binary_sensor][CONF_MANUFACTURER], + model=_configured_binary_sensors[_binary_sensor][CONF_DEVICE_MODEL], + gateway=hass.data[DOMAIN][config_entry.data[CONF_MAC]][CONF_ENTITY], + ) + _binary_sensors.append(_binary_sensor) + elif _who == 18 and _device_class == BinarySensorDeviceClass.POWER: + _binary_sensor = MyHOMEActuator( + hass=hass, + device_id=_binary_sensor, + who=_configured_binary_sensors[_binary_sensor][CONF_WHO], + where=_configured_binary_sensors[_binary_sensor][CONF_WHERE], + phase=_configured_binary_sensors[_binary_sensor][CONF_PHASE] if CONF_PHASE in _configured_binary_sensors[_binary_sensor] else None, + icon=_configured_binary_sensors[_binary_sensor][CONF_ICON], + icon_on=_configured_binary_sensors[_binary_sensor][CONF_ICON_ON], + name=_configured_binary_sensors[_binary_sensor][CONF_NAME], + entity_name=_configured_binary_sensors[_binary_sensor][CONF_ENTITY_NAME], + inverted=_configured_binary_sensors[_binary_sensor][CONF_INVERTED], + interface=_configured_binary_sensors[_binary_sensor][CONF_BUS_INTERFACE] if CONF_BUS_INTERFACE in _configured_binary_sensors[_binary_sensor] else None, + device_class=_device_class, + manufacturer=_configured_binary_sensors[_binary_sensor][CONF_MANUFACTURER], + model=_configured_binary_sensors[_binary_sensor][CONF_DEVICE_MODEL], + gateway=hass.data[DOMAIN][config_entry.data[CONF_MAC]][CONF_ENTITY], + ) + _binary_sensors.append(_binary_sensor) async_add_entities(_binary_sensors) @@ -334,3 +398,118 @@ def handle_event(self, message: OWNLightingEvent): self._attr_force_update = True self.async_write_ha_state() self._attr_force_update = False + + +class MyHOMEActuator(MyHOMEEntity, BinarySensorEntity): + def __init__( + self, + hass, + name: str, + entity_name: str, + icon: str, + icon_on: str, + device_id: str, + who: str, + where: str, + phase: str, + inverted: bool, + interface: str, + device_class: str, + manufacturer: str, + model: str, + gateway: MyHOMEGatewayHandler, + ): + super().__init__( + hass=hass, + name=name, + platform=PLATFORM, + device_id=device_id, + who=who, + where=where, + manufacturer=manufacturer, + model=model, + gateway=gateway, + ) + + self._inverted = inverted + self._phase = phase + + self._attr_device_class = device_class + self._attr_name = entity_name if entity_name else self._attr_device_class.replace("_", " ").capitalize() + + self._attr_unique_id = f"{gateway.mac}-{self._device_id}-{self._attr_device_class}" + + self._interface = interface + + if self._who == "1": + if self._interface is not None: + self._attr_extra_state_attributes["Int"] = self._interface + self._full_where = f"{self._where}#4#{self._interface}" + else: + self._full_where = self._where + self._attr_extra_state_attributes = { + "A": where[: len(where) // 2], + "PL": where[len(where) // 2 :], + } + elif self._who == "4": + if self._interface is not None: + raise ValueError("Interface cannot be set with WHO=4") + self._attr_extra_state_attributes = { + "Z": where + } + elif self._who == "18": + if self._interface is not None: + raise ValueError("Interface cannot be set with WHO=18") + self._attr_extra_state_attributes = { + "P": str(int(where) - 70) + } + + self._on_icon = icon_on + self._off_icon = icon + + if self._off_icon is not None: + self._attr_icon = self._off_icon + + self._attr_is_on = None + + async def async_added_to_hass(self): + """When entity is added to hass.""" + self._hass.data[DOMAIN][self._gateway_handler.mac][CONF_PLATFORMS][self._platform][self._device_id][CONF_ENTITIES][self._attr_device_class] = self + await self.async_update() + + async def async_will_remove_from_hass(self): + """When entity is removed from hass.""" + if self._attr_device_class in self._hass.data[DOMAIN][self._gateway_handler.mac][CONF_PLATFORMS][self._platform][self._device_id][CONF_ENTITIES]: + del self._hass.data[DOMAIN][self._gateway_handler.mac][CONF_PLATFORMS][self._platform][self._device_id][CONF_ENTITIES][self._attr_device_class] + + async def async_update(self): + """Update the entity. + + Only used by the generic entity update service. + """ + if self._who == "1": + await self._gateway_handler.send_status_request(OWNLightingCommand.status(self._full_where)) + elif self._who == "4": + await self._gateway_handler.send_status_request(OWNHeatingCommand(f"*#4*{self._where}*20##")) + elif self._who == "18": + await self._gateway_handler.send_status_request(OWNEnergyCommand(f"*#18*{self._where}#{self._phase}*71##")) + + def handle_event(self, message: OWNEvent): + """Handle an event message.""" + LOGGER.info( + "%s %s", + self._gateway_handler.log_id, + message.human_readable_log, + ) + + if self._who == "1": + self._attr_is_on = message.is_on != self._inverted + elif self._who == "4" and message.dimension == 20: + self._attr_is_on = message.is_active() != self._inverted + elif self._who == "18" and message.dimension == 71: + self._attr_is_on = bool(int(message._dimension_value[0])) != self._inverted + + if self._off_icon is not None and self._on_icon is not None: + self._attr_icon = self._on_icon if self._attr_is_on else self._off_icon + + self.async_schedule_update_ha_state() diff --git a/custom_components/myhome/climate.py b/custom_components/myhome/climate.py index c5a4839..1fa08b0 100644 --- a/custom_components/myhome/climate.py +++ b/custom_components/myhome/climate.py @@ -147,7 +147,7 @@ def __init__( self._attr_min_temp = 5 self._attr_max_temp = 40 - self._attr_supported_features = 0 + self._attr_supported_features = ClimateEntityFeature.TURN_ON | ClimateEntityFeature.TURN_OFF self._attr_hvac_modes = [HVACMode.OFF] self._heating = heating self._cooling = cooling @@ -191,7 +191,10 @@ def target_temperature(self) -> float: if self._local_target_temperature is not None: return self._local_target_temperature else: - return self._target_temperature + return ( + (self._target_temperature - self._local_offset) + if self._target_temperature is not None else None + ) async def async_set_hvac_mode(self, hvac_mode): """Set new target hvac mode.""" @@ -293,9 +296,6 @@ def handle_event(self, message: OWNHeatingEvent): message.human_readable_log, ) self._target_temperature = message.set_temperature - self._local_target_temperature = ( - self._target_temperature + self._local_offset - ) elif message.message_type == MESSAGE_TYPE_LOCAL_OFFSET: LOGGER.info( "%s %s", @@ -303,10 +303,6 @@ def handle_event(self, message: OWNHeatingEvent): message.human_readable_log, ) self._local_offset = message.local_offset - if self._target_temperature is not None: - self._local_target_temperature = ( - self._target_temperature + self._local_offset - ) elif message.message_type == MESSAGE_TYPE_LOCAL_TARGET_TEMPERATURE: LOGGER.info( "%s %s", @@ -314,9 +310,6 @@ def handle_event(self, message: OWNHeatingEvent): message.human_readable_log, ) self._local_target_temperature = message.local_set_temperature - self._target_temperature = ( - self._local_target_temperature - self._local_offset - ) elif message.message_type == MESSAGE_TYPE_MODE: if ( message.mode == CLIMATE_MODE_AUTO @@ -408,9 +401,6 @@ def handle_event(self, message: OWNHeatingEvent): self._attr_hvac_mode = HVACMode.OFF self._attr_hvac_action = HVACAction.OFF self._target_temperature = message.set_temperature - self._local_target_temperature = ( - self._target_temperature + self._local_offset - ) elif message.message_type == MESSAGE_TYPE_ACTION: LOGGER.info( "%s %s", @@ -418,14 +408,9 @@ def handle_event(self, message: OWNHeatingEvent): message.human_readable_log, ) if message.is_active(): - if self._heating and self._cooling: - if message.is_heating(): - self._attr_hvac_action = HVACAction.HEATING - elif message.is_cooling(): - self._attr_hvac_action = HVACAction.COOLING - elif self._heating: + if self._attr_hvac_mode == HVACMode.HEAT: self._attr_hvac_action = HVACAction.HEATING - elif self._cooling: + if self._attr_hvac_mode == HVACMode.COOL: self._attr_hvac_action = HVACAction.COOLING elif self._attr_hvac_mode == HVACMode.OFF: self._attr_hvac_action = HVACAction.OFF diff --git a/custom_components/myhome/const.py b/custom_components/myhome/const.py old mode 100644 new mode 100755 index 9c560b6..0205eeb --- a/custom_components/myhome/const.py +++ b/custom_components/myhome/const.py @@ -30,6 +30,7 @@ CONF_PARENT_ID = "parent_id" CONF_WHO = "who" CONF_WHERE = "where" +CONF_PHASE = "phase" CONF_BUS_INTERFACE = "interface" CONF_ZONE = "zone" CONF_DIMMABLE = "dimmable" @@ -46,3 +47,5 @@ CONF_SHORT_RELEASE = "pushbutton_short_release" CONF_LONG_PRESS = "pushbutton_long_press" CONF_LONG_RELEASE = "pushbutton_long_release" +CONF_SHUTTER_OPENING_TIME = "opening_time" +CONF_SHUTTER_CLOSING_TIME = "closing_time" \ No newline at end of file diff --git a/custom_components/myhome/cover.py b/custom_components/myhome/cover.py old mode 100644 new mode 100755 index 13c42e0..7253241 --- a/custom_components/myhome/cover.py +++ b/custom_components/myhome/cover.py @@ -29,10 +29,13 @@ CONF_ADVANCED_SHUTTER, DOMAIN, LOGGER, + CONF_SHUTTER_OPENING_TIME, + CONF_SHUTTER_CLOSING_TIME, ) from .myhome_device import MyHOMEEntity from .gateway import MyHOMEGatewayHandler - +from datetime import datetime +import asyncio async def async_setup_entry(hass, config_entry, async_add_entities): if PLATFORM not in hass.data[DOMAIN][config_entry.data[CONF_MAC]][CONF_PLATFORMS]: @@ -54,6 +57,8 @@ async def async_setup_entry(hass, config_entry, async_add_entities): manufacturer=_configured_covers[_cover][CONF_MANUFACTURER], model=_configured_covers[_cover][CONF_DEVICE_MODEL], gateway=hass.data[DOMAIN][config_entry.data[CONF_MAC]][CONF_ENTITY], + opening_time=_configured_covers[_cover][CONF_SHUTTER_OPENING_TIME], + closing_time=_configured_covers[_cover][CONF_SHUTTER_CLOSING_TIME], ) _covers.append(_cover) @@ -86,6 +91,8 @@ def __init__( manufacturer: str, model: str, gateway: MyHOMEGatewayHandler, + opening_time: int, + closing_time: int ): super().__init__( hass=hass, @@ -103,10 +110,15 @@ def __init__( self._interface = interface self._full_where = f"{self._where}#4#{self._interface}" if self._interface is not None else self._where + self._attr_opening_time = opening_time + self._attr_closing_time = closing_time + self._attr_advanced = advanced self._attr_supported_features = CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE | CoverEntityFeature.STOP if advanced: self._attr_supported_features |= CoverEntityFeature.SET_POSITION + elif opening_time > 0 and closing_time > 0: + self._attr_supported_features |= CoverEntityFeature.SET_POSITION self._gateway_handler = gateway self._attr_extra_state_attributes = { @@ -120,6 +132,7 @@ def __init__( self._attr_is_opening = None self._attr_is_closing = None self._attr_is_closed = None + self._attr_last_event = datetime.now() async def async_update(self): """Update the entity. @@ -140,7 +153,38 @@ async def async_set_cover_position(self, **kwargs): """Move the cover to a specific position.""" if ATTR_POSITION in kwargs: position = kwargs[ATTR_POSITION] - await self._gateway_handler.send(OWNAutomationCommand.set_shutter_level(self._full_where, position)) + if self._attr_advanced: + await self._gateway_handler.send(OWNAutomationCommand.set_shutter_level(self._full_where, position)) + elif self._attr_opening_time > 0 or self._attr_closing_time > 0: + + if self._attr_is_closing or self._attr_is_closing: + await self._gateway_handler.send(OWNAutomationCommand.stop_shutter(self._full_where)) + + if self._attr_current_cover_position is None: + return + + required_move = int(position - self._attr_current_cover_position) + if required_move > 0: + """ open """ + required_time = abs(self._attr_opening_time * required_move / 100) + LOGGER.debug( + "Open -> Required time %s", + required_time, + ) + await self._gateway_handler.send(OWNAutomationCommand.raise_shutter(self._full_where)) + await asyncio.sleep(required_time) + await self._gateway_handler.send(OWNAutomationCommand.stop_shutter(self._full_where)) + elif required_move < 0: + """ close """ + required_time = abs(self._attr_closing_time * required_move / 100) + LOGGER.debug( + "Close -> Required time %s", + required_time, + ) + await self._gateway_handler.send(OWNAutomationCommand.lower_shutter(self._full_where)) + await asyncio.sleep(required_time) + await self._gateway_handler.send(OWNAutomationCommand.stop_shutter(self._full_where)) + async def async_stop_cover(self, **kwargs): # pylint: disable=unused-argument """Stop the cover.""" @@ -153,11 +197,35 @@ def handle_event(self, message: OWNAutomationEvent): self._gateway_handler.log_id, message.human_readable_log, ) + + if message.current_position is not None: + self._attr_current_cover_position = message.current_position + elif self._attr_last_event is not None and self._attr_opening_time > 0 and self._attr_closing_time > 0: + elapsed_seconds = (datetime.now() - self._attr_last_event).total_seconds() + if elapsed_seconds > 0: + if self._attr_is_opening: + if self._attr_opening_time < elapsed_seconds: + self._attr_current_cover_position = 100 + elif (self._attr_current_cover_position is not None): + self._attr_current_cover_position = round(min(100, self._attr_current_cover_position + (100 * elapsed_seconds / self._attr_opening_time)), 0) + elif self._attr_is_closing: + if self._attr_closing_time < elapsed_seconds: + self._attr_current_cover_position = 0 + elif self._attr_current_cover_position is not None: + self._attr_current_cover_position = round(max(0, self._attr_current_cover_position - (100 * elapsed_seconds / self._attr_closing_time)), 0) + + LOGGER.debug( + "%s %s", + self._gateway_handler.log_id, + self._attr_current_cover_position, + ) + + self._attr_last_event = datetime.now() self._attr_is_opening = message.is_opening self._attr_is_closing = message.is_closing if message.is_closed is not None: self._attr_is_closed = message.is_closed - if message.current_position is not None: - self._attr_current_cover_position = message.current_position + elif self._attr_current_cover_position is not None: + self._attr_is_closed = self._attr_current_cover_position == 0 - self.async_schedule_update_ha_state() + self.async_schedule_update_ha_state() \ No newline at end of file diff --git a/custom_components/myhome/gateway.py b/custom_components/myhome/gateway.py index 821a20d..c0a093e 100644 --- a/custom_components/myhome/gateway.py +++ b/custom_components/myhome/gateway.py @@ -143,6 +143,11 @@ async def listening_loop(self): message = await _event_session.get_next() LOGGER.debug("%s Message received: `%s`", self.log_id, message) + # Workaround due to how the OWNd library creates the entity ID, + # replacing zone=0 with zone=where_param + if isinstance(message, OWNHeatingEvent) and message.where == "0": + message._zone = 0 + if self.generate_events: if isinstance(message, OWNMessage): _event_content = {"gateway": str(self.gateway.host)} @@ -164,7 +169,28 @@ async def listening_loop(self): self.hass.data[DOMAIN][self.mac][CONF_PLATFORMS][SENSOR][message.entity][CONF_ENTITIES][_entity], MyHOMEEntity, ): - self.hass.data[DOMAIN][self.mac][CONF_PLATFORMS][SENSOR][message.entity][CONF_ENTITIES][_entity].handle_event(message) + try: + self.hass.data[DOMAIN][self.mac][CONF_PLATFORMS][SENSOR][message.entity][CONF_ENTITIES][_entity].handle_event(message) + except: + LOGGER.error( + "%s Error handling sensor event `%s`", + self.log_id, + message, + ) + elif BINARY_SENSOR in self.hass.data[DOMAIN][self.mac][CONF_PLATFORMS] and message.entity in self.hass.data[DOMAIN][self.mac][CONF_PLATFORMS][BINARY_SENSOR]: + for _entity in self.hass.data[DOMAIN][self.mac][CONF_PLATFORMS][BINARY_SENSOR][message.entity][CONF_ENTITIES]: + if isinstance( + self.hass.data[DOMAIN][self.mac][CONF_PLATFORMS][BINARY_SENSOR][message.entity][CONF_ENTITIES][_entity], + MyHOMEEntity, + ): + try: + self.hass.data[DOMAIN][self.mac][CONF_PLATFORMS][BINARY_SENSOR][message.entity][CONF_ENTITIES][_entity].handle_event(message) + except: + LOGGER.error( + "%s Error handling binary sensor event `%s`", + self.log_id, + message, + ) else: continue elif ( @@ -257,7 +283,7 @@ async def listening_loop(self): ) if not is_event: if isinstance(message, OWNLightingEvent) and message.brightness_preset: - if isinstance( + if message.entity in self.hass.data[DOMAIN][self.mac][CONF_PLATFORMS][LIGHT] and isinstance( self.hass.data[DOMAIN][self.mac][CONF_PLATFORMS][LIGHT][message.entity][CONF_ENTITIES][LIGHT], MyHOMEEntity, ): @@ -280,7 +306,14 @@ async def listening_loop(self): EnableCommandButtonEntity, ) ): - self.hass.data[DOMAIN][self.mac][CONF_PLATFORMS][_platform][message.entity][CONF_ENTITIES][_entity].handle_event(message) + try: + self.hass.data[DOMAIN][self.mac][CONF_PLATFORMS][_platform][message.entity][CONF_ENTITIES][_entity].handle_event(message) + except: + LOGGER.error( + "%s Error handling event `%s`", + self.log_id, + message, + ) else: LOGGER.debug( @@ -378,7 +411,7 @@ async def sending_loop(self, worker_id: int): while not self._terminate_sender: task = await self.send_buffer.get() LOGGER.debug( - "%s Message `%s` was successfully unqueued by worker %s.", + "%s (%s) Message `%s` was successfully unqueued by worker %s.", self.name, self.gateway.host, task["message"], diff --git a/custom_components/myhome/light.py b/custom_components/myhome/light.py index 91ed3b2..db4fa6b 100644 --- a/custom_components/myhome/light.py +++ b/custom_components/myhome/light.py @@ -217,12 +217,16 @@ def handle_event(self, message: OWNLightingEvent): self._gateway_handler.log_id, message.human_readable_log, ) - self._attr_is_on = message.is_on - if ColorMode.BRIGHTNESS in self._attr_supported_color_modes and message.brightness is not None: - self._attr_brightness_pct = message.brightness - self._attr_brightness = percent_to_eight_bits(message.brightness) - - if self._off_icon is not None and self._on_icon is not None: - self._attr_icon = self._on_icon if self._attr_is_on else self._off_icon - - self.async_schedule_update_ha_state() + #This should be in try block as message.is_on can throw error for unsupported message types (like for some dimensions) + try: + self._attr_is_on = message.is_on + if ColorMode.BRIGHTNESS in self._attr_supported_color_modes and message.brightness is not None: + self._attr_brightness_pct = message.brightness + self._attr_brightness = percent_to_eight_bits(message.brightness) + + if self._off_icon is not None and self._on_icon is not None: + self._attr_icon = self._on_icon if self._attr_is_on else self._off_icon + + self.async_schedule_update_ha_state() + except TypeError: + pass \ No newline at end of file diff --git a/custom_components/myhome/manifest.json b/custom_components/myhome/manifest.json index b32a3e7..96da0a5 100644 --- a/custom_components/myhome/manifest.json +++ b/custom_components/myhome/manifest.json @@ -2,7 +2,7 @@ "domain": "myhome", "integration_type": "hub", "name": "MyHOME", - "version": "0.9.3", + "version": "0.9.4-michnovka", "config_flow": true, "documentation": "https://github.com/anotherjulien/MyHOME", "issue_tracker": "https://github.com/anotherjulien/MyHOME/issues", diff --git a/custom_components/myhome/validate.py b/custom_components/myhome/validate.py old mode 100644 new mode 100755 index a27b09c..d13eac5 --- a/custom_components/myhome/validate.py +++ b/custom_components/myhome/validate.py @@ -35,6 +35,7 @@ CONF_PLATFORMS, CONF_WHO, CONF_WHERE, + CONF_PHASE, CONF_BUS_INTERFACE, CONF_ENTITIES, CONF_ENTITY_NAME, @@ -52,6 +53,8 @@ CONF_COOLING_SUPPORT, CONF_STANDALONE, CONF_CENTRAL, + CONF_SHUTTER_OPENING_TIME, + CONF_SHUTTER_CLOSING_TIME, ) @@ -147,10 +150,10 @@ def __init__(self, msg=None): self.msg = msg def __call__(self, v): - if type(v) == str and v.isdigit(): + if type(v) == str and re.match(r"^[0-9#]+$", v): return v else: - raise Invalid(f"Invalid WHERE {v}, it must be a string of digits.") + raise Invalid(f"Invalid WHERE {v}, it must be a string of [0-9#]+.") def __repr__(self): return "Where(%s, msg=%r)" % ("String", self.msg) @@ -336,6 +339,8 @@ def __call__(self, data): Required(CONF_NAME): str, Optional(CONF_ENTITY_NAME): str, Optional(CONF_ADVANCED_SHUTTER, default=False): Boolean(), + Optional(CONF_SHUTTER_OPENING_TIME, default=0): int, + Optional(CONF_SHUTTER_CLOSING_TIME, default=0): int, Optional(CONF_MANUFACTURER, default="BTicino S.p.A."): str, Optional(CONF_DEVICE_MODEL): Coerce(str), } @@ -345,11 +350,14 @@ def __call__(self, data): binary_sensor_schema = MyHomeDeviceSchema( { Required(str): { - Optional(CONF_WHO, default="25"): In(["1", "9", "25"]), + Optional(CONF_WHO, default="25"): In(["1", "4", "9", "18", "25"]), Required(CONF_WHERE): All(Coerce(str), SpecialWhere()), + Optional(CONF_PHASE): str, Required(CONF_NAME): str, Optional(CONF_ENTITY_NAME): str, Optional(CONF_INVERTED, default=False): Boolean(), + Optional(CONF_ICON): str, + Optional(CONF_ICON_ON): str, Optional(CONF_DEVICE_CLASS): In( [ BinarySensorDeviceClass.BATTERY,