1 """Support for Motionblinds using their WLAN API."""
3 from __future__
import annotations
8 from motionblinds
import BlindType
9 import voluptuous
as vol
25 ATTR_ABSOLUTE_POSITION,
31 SERVICE_SET_ABSOLUTE_POSITION,
34 from .entity
import MotionCoordinatorEntity
36 _LOGGER = logging.getLogger(__name__)
39 POSITION_DEVICE_MAP = {
40 BlindType.RollerBlind: CoverDeviceClass.SHADE,
41 BlindType.RomanBlind: CoverDeviceClass.SHADE,
42 BlindType.HoneycombBlind: CoverDeviceClass.SHADE,
43 BlindType.DimmingBlind: CoverDeviceClass.SHADE,
44 BlindType.DayNightBlind: CoverDeviceClass.SHADE,
45 BlindType.RollerShutter: CoverDeviceClass.SHUTTER,
46 BlindType.Switch: CoverDeviceClass.SHUTTER,
47 BlindType.RollerGate: CoverDeviceClass.GATE,
48 BlindType.Awning: CoverDeviceClass.AWNING,
49 BlindType.Curtain: CoverDeviceClass.CURTAIN,
50 BlindType.CurtainLeft: CoverDeviceClass.CURTAIN,
51 BlindType.CurtainRight: CoverDeviceClass.CURTAIN,
52 BlindType.SkylightBlind: CoverDeviceClass.SHADE,
53 BlindType.InsectScreen: CoverDeviceClass.SHADE,
57 BlindType.VenetianBlind: CoverDeviceClass.BLIND,
58 BlindType.ShangriLaBlind: CoverDeviceClass.BLIND,
59 BlindType.DoubleRoller: CoverDeviceClass.SHADE,
60 BlindType.DualShade: CoverDeviceClass.SHADE,
61 BlindType.VerticalBlind: CoverDeviceClass.BLIND,
62 BlindType.VerticalBlindLeft: CoverDeviceClass.BLIND,
63 BlindType.VerticalBlindRight: CoverDeviceClass.BLIND,
66 TILT_ONLY_DEVICE_MAP = {
67 BlindType.WoodShutter: CoverDeviceClass.BLIND,
71 BlindType.TopDownBottomUp: CoverDeviceClass.SHADE,
72 BlindType.TriangleBlind: CoverDeviceClass.BLIND,
76 SET_ABSOLUTE_POSITION_SCHEMA: VolDictType = {
77 vol.Required(ATTR_ABSOLUTE_POSITION): vol.All(cv.positive_int, vol.Range(max=100)),
78 vol.Optional(ATTR_TILT_POSITION): vol.All(cv.positive_int, vol.Range(max=100)),
79 vol.Optional(ATTR_WIDTH): vol.All(cv.positive_int, vol.Range(max=100)),
85 config_entry: ConfigEntry,
86 async_add_entities: AddEntitiesCallback,
88 """Set up the Motion Blind from a config entry."""
89 entities: list[MotionBaseDevice] = []
90 motion_gateway = hass.data[DOMAIN][config_entry.entry_id][KEY_GATEWAY]
91 coordinator = hass.data[DOMAIN][config_entry.entry_id][KEY_COORDINATOR]
93 for blind
in motion_gateway.device_list.values():
94 if blind.type
in POSITION_DEVICE_MAP:
99 POSITION_DEVICE_MAP[blind.type],
103 elif blind.type
in TILT_DEVICE_MAP:
108 TILT_DEVICE_MAP[blind.type],
112 elif blind.type
in TILT_ONLY_DEVICE_MAP:
117 TILT_ONLY_DEVICE_MAP[blind.type],
121 elif blind.type
in TDBU_DEVICE_MAP:
126 TDBU_DEVICE_MAP[blind.type],
134 TDBU_DEVICE_MAP[blind.type],
142 TDBU_DEVICE_MAP[blind.type],
149 "Blind type '%s' not yet supported, assuming RollerBlind",
156 POSITION_DEVICE_MAP[BlindType.RollerBlind],
162 platform = entity_platform.async_get_current_platform()
163 platform.async_register_entity_service(
164 SERVICE_SET_ABSOLUTE_POSITION,
165 SET_ABSOLUTE_POSITION_SCHEMA,
166 "async_set_absolute_position",
171 """Representation of a Motionblinds Device."""
173 _restore_tilt =
False
175 def __init__(self, coordinator, blind, device_class):
176 """Initialize the blind."""
177 super().
__init__(coordinator, blind)
184 """Return True if entity is available."""
185 if self.coordinator.data
is None:
188 if not self.coordinator.data[KEY_GATEWAY][ATTR_AVAILABLE]:
191 return self.coordinator.data[self.
_blind_blind.mac][ATTR_AVAILABLE]
195 """Return current position of cover.
197 None is unknown, 0 is open, 100 is closed.
199 if self.
_blind_blind.position
is None:
201 return 100 - self.
_blind_blind.position
205 """Return if the cover is closed or not."""
206 if self.
_blind_blind.position
is None:
208 return self.
_blind_blind.position == 100
211 """Open the cover."""
213 await self.
hasshasshass.async_add_executor_job(self.
_blind_blind.Open)
219 await self.
hasshasshass.async_add_executor_job(self.
_blind_blind.Close)
223 """Move the cover to a specific position."""
224 position = kwargs[ATTR_POSITION]
226 await self.
hasshasshass.async_add_executor_job(
227 self.
_blind_blind.Set_position,
235 """Move the cover to a specific absolute position (see TDBU)."""
236 position = kwargs[ATTR_ABSOLUTE_POSITION]
237 angle = kwargs.get(ATTR_TILT_POSITION)
238 if angle
is not None:
239 angle = angle * 180 / 100
241 await self.
hasshasshass.async_add_executor_job(
242 self.
_blind_blind.Set_position,
250 """Stop the cover."""
252 await self.
hasshasshass.async_add_executor_job(self.
_blind_blind.Stop)
258 """Representation of a Motion Blind Device."""
264 """Representation of a Motionblinds Device."""
270 """Return current angle of cover.
272 None is unknown, 0 is closed/minimum tilt, 100 is fully open/maximum tilt.
274 if self.
_blind_blind.angle
is None:
276 return self.
_blind_blind.angle * 100 / 180
280 """Return if the cover is closed or not."""
281 if self.
_blind_blind.position
is None:
283 return self.
_blind_blind.position >= 95
286 """Open the cover tilt."""
288 await self.
hasshasshass.async_add_executor_job(self.
_blind_blind.Set_angle, 180)
291 """Close the cover tilt."""
293 await self.
hasshasshass.async_add_executor_job(self.
_blind_blind.Set_angle, 0)
296 """Move the cover tilt to a specific position."""
297 angle = kwargs[ATTR_TILT_POSITION] * 180 / 100
299 await self.
hasshasshass.async_add_executor_job(self.
_blind_blind.Set_angle, angle)
302 """Stop the cover."""
304 await self.
hasshasshass.async_add_executor_job(self.
_blind_blind.Stop)
310 """Representation of a Motionblinds Device."""
312 _restore_tilt =
False
316 """Flag supported features."""
317 supported_features = (
318 CoverEntityFeature.OPEN_TILT
319 | CoverEntityFeature.CLOSE_TILT
320 | CoverEntityFeature.STOP_TILT
324 supported_features |= CoverEntityFeature.SET_TILT_POSITION
326 return supported_features
330 """Return current position of cover."""
335 """Return current angle of cover.
337 None is unknown, 0 is closed/minimum tilt, 100 is fully open/maximum tilt.
339 if self.
_blind_blind.position
is None:
340 if self.
_blind_blind.angle
is None:
342 return self.
_blind_blind.angle * 100 / 180
344 return self.
_blind_blind.position
348 """Return if the cover is closed or not."""
349 if self.
_blind_blind.position
is None:
350 if self.
_blind_blind.angle
is None:
352 return self.
_blind_blind.angle == 0
354 return self.
_blind_blind.position == 0
357 """Open the cover tilt."""
359 await self.
hasshasshass.async_add_executor_job(self.
_blind_blind.Open)
362 """Close the cover tilt."""
364 await self.
hasshasshass.async_add_executor_job(self.
_blind_blind.Close)
367 """Move the cover tilt to a specific position."""
368 angle = kwargs[ATTR_TILT_POSITION]
369 if self.
_blind_blind.position
is None:
370 angle = angle * 180 / 100
372 await self.
hasshasshass.async_add_executor_job(self.
_blind_blind.Set_angle, angle)
375 await self.
hasshasshass.async_add_executor_job(self.
_blind_blind.Set_position, angle)
378 """Move the cover to a specific absolute position (see TDBU)."""
379 angle = kwargs.get(ATTR_TILT_POSITION)
383 if self.
_blind_blind.position
is None:
384 angle = angle * 180 / 100
386 await self.
hasshasshass.async_add_executor_job(self.
_blind_blind.Set_angle, angle)
389 await self.
hasshasshass.async_add_executor_job(self.
_blind_blind.Set_position, angle)
393 """Representation of a Motion Top Down Bottom Up blind Device."""
395 def __init__(self, coordinator, blind, device_class, motor):
396 """Initialize the blind."""
397 super().
__init__(coordinator, blind, device_class)
403 if self.
_motor_motor
not in [
"Bottom",
"Top",
"Combined"]:
404 _LOGGER.error(
"Unknown motor '%s'", self.
_motor_motor)
408 """Return current position of cover.
410 None is unknown, 0 is open, 100 is closed.
412 if self.
_blind_blind.scaled_position
is None:
419 """Return if the cover is closed or not."""
420 if self.
_blind_blind.position
is None:
423 if self.
_motor_motor ==
"Combined":
424 return self.
_blind_blind.width == 100
430 """Return device specific state attributes."""
432 if self.
_blind_blind.position
is not None:
433 attributes[ATTR_ABSOLUTE_POSITION] = (
436 if self.
_blind_blind.width
is not None:
437 attributes[ATTR_WIDTH] = self.
_blind_blind.width
441 """Open the cover."""
453 """Move the cover to a specific scaled position."""
454 position = kwargs[ATTR_POSITION]
456 await self.
hasshasshass.async_add_executor_job(
457 self.
_blind_blind.Set_scaled_position, 100 - position, self.
_motor_key_motor_key
462 """Move the cover to a specific absolute position."""
463 position = kwargs[ATTR_ABSOLUTE_POSITION]
464 target_width = kwargs.get(ATTR_WIDTH)
467 await self.
hasshasshass.async_add_executor_job(
468 self.
_blind_blind.Set_position, 100 - position, self.
_motor_key_motor_key, target_width
474 """Stop the cover."""
int|None current_cover_tilt_position(self)
current_cover_tilt_position
None async_close_cover(self, **Any kwargs)
None async_set_cover_position(self, **Any kwargs)
bool|None is_closed(self)
def __init__(self, coordinator, blind, device_class)
None async_stop_cover(self, **Any kwargs)
def async_set_absolute_position(self, **kwargs)
None async_open_cover(self, **Any kwargs)
None async_stop_cover(self, **Any kwargs)
def __init__(self, coordinator, blind, device_class, motor)
dict[str, Any] extra_state_attributes(self)
None async_set_cover_position(self, **Any kwargs)
None async_close_cover(self, **Any kwargs)
def async_set_absolute_position(self, **kwargs)
bool|None is_closed(self)
None async_open_cover(self, **Any kwargs)
int|None current_cover_tilt_position(self)
None async_stop_cover_tilt(self, **Any kwargs)
None async_set_cover_tilt_position(self, **Any kwargs)
None async_close_cover_tilt(self, **Any kwargs)
None async_open_cover_tilt(self, **Any kwargs)
bool|None is_closed(self)
bool|None is_closed(self)
int|None current_cover_tilt_position(self)
def async_set_absolute_position(self, **kwargs)
None async_close_cover_tilt(self, **Any kwargs)
None async_set_cover_tilt_position(self, **Any kwargs)
CoverEntityFeature supported_features(self)
None async_open_cover_tilt(self, **Any kwargs)
None async_request_position_till_stop(self, int|None delay=None)
None async_setup_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities)