1
1
"""Support for MyQ-Enabled Garage Doors."""
2
2
import logging
3
+ import time
3
4
4
5
import voluptuous as vol
5
6
6
7
from homeassistant .components .cover import (
8
+ DEVICE_CLASS_GARAGE ,
9
+ DEVICE_CLASS_GATE ,
7
10
PLATFORM_SCHEMA ,
8
11
SUPPORT_CLOSE ,
9
12
SUPPORT_OPEN ,
18
21
STATE_CLOSING ,
19
22
STATE_OPENING ,
20
23
)
24
+ from homeassistant .core import callback
21
25
from homeassistant .helpers import config_validation as cv
22
-
23
- from .const import DOMAIN , MYQ_DEVICE_STATE , MYQ_DEVICE_STATE_ONLINE , MYQ_TO_HASS
26
+ from homeassistant .helpers .event import async_call_later
27
+
28
+ from .const import (
29
+ DOMAIN ,
30
+ MYQ_COORDINATOR ,
31
+ MYQ_DEVICE_STATE ,
32
+ MYQ_DEVICE_STATE_ONLINE ,
33
+ MYQ_DEVICE_TYPE ,
34
+ MYQ_DEVICE_TYPE_GATE ,
35
+ MYQ_GATEWAY ,
36
+ MYQ_TO_HASS ,
37
+ TRANSITION_COMPLETE_DURATION ,
38
+ TRANSITION_START_DURATION ,
39
+ )
24
40
25
41
_LOGGER = logging .getLogger (__name__ )
26
42
@@ -53,21 +69,32 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
53
69
54
70
async def async_setup_entry (hass , config_entry , async_add_entities ):
55
71
"""Set up mysq covers."""
56
- myq = hass .data [DOMAIN ][config_entry .entry_id ]
57
- async_add_entities ([MyQDevice (device ) for device in myq .covers .values ()], True )
72
+ data = hass .data [DOMAIN ][config_entry .entry_id ]
73
+ myq = data [MYQ_GATEWAY ]
74
+ coordinator = data [MYQ_COORDINATOR ]
75
+
76
+ async_add_entities (
77
+ [MyQDevice (coordinator , device ) for device in myq .covers .values ()], True
78
+ )
58
79
59
80
60
81
class MyQDevice (CoverDevice ):
61
82
"""Representation of a MyQ cover."""
62
83
63
- def __init__ (self , device ):
84
+ def __init__ (self , coordinator , device ):
64
85
"""Initialize with API object, device id."""
86
+ self ._coordinator = coordinator
65
87
self ._device = device
88
+ self ._last_action_timestamp = 0
89
+ self ._scheduled_transition_update = None
66
90
67
91
@property
68
92
def device_class (self ):
69
93
"""Define this cover as a garage door."""
70
- return "garage"
94
+ device_type = self ._device .device_json .get (MYQ_DEVICE_TYPE )
95
+ if device_type is not None and device_type == MYQ_DEVICE_TYPE_GATE :
96
+ return DEVICE_CLASS_GATE
97
+ return DEVICE_CLASS_GARAGE
71
98
72
99
@property
73
100
def name (self ):
@@ -77,6 +104,9 @@ def name(self):
77
104
@property
78
105
def available (self ):
79
106
"""Return if the device is online."""
107
+ if not self ._coordinator .last_update_success :
108
+ return False
109
+
80
110
# Not all devices report online so assume True if its missing
81
111
return self ._device .device_json [MYQ_DEVICE_STATE ].get (
82
112
MYQ_DEVICE_STATE_ONLINE , True
@@ -109,19 +139,41 @@ def unique_id(self):
109
139
110
140
async def async_close_cover (self , ** kwargs ):
111
141
"""Issue close command to cover."""
142
+ self ._last_action_timestamp = time .time ()
112
143
await self ._device .close ()
113
- # Writes closing state
114
- self .async_write_ha_state ()
144
+ self ._async_schedule_update_for_transition ()
115
145
116
146
async def async_open_cover (self , ** kwargs ):
117
147
"""Issue open command to cover."""
148
+ self ._last_action_timestamp = time .time ()
118
149
await self ._device .open ()
119
- # Writes opening state
150
+ self ._async_schedule_update_for_transition ()
151
+
152
+ @callback
153
+ def _async_schedule_update_for_transition (self ):
120
154
self .async_write_ha_state ()
121
155
156
+ # Cancel any previous updates
157
+ if self ._scheduled_transition_update :
158
+ self ._scheduled_transition_update ()
159
+
160
+ # Schedule an update for when we expect the transition
161
+ # to be completed so the garage door or gate does not
162
+ # seem like its closing or opening for a long time
163
+ self ._scheduled_transition_update = async_call_later (
164
+ self .hass ,
165
+ TRANSITION_COMPLETE_DURATION ,
166
+ self ._async_complete_schedule_update ,
167
+ )
168
+
169
+ async def _async_complete_schedule_update (self , _ ):
170
+ """Update status of the cover via coordinator."""
171
+ self ._scheduled_transition_update = None
172
+ await self ._coordinator .async_request_refresh ()
173
+
122
174
async def async_update (self ):
123
175
"""Update status of cover."""
124
- await self ._device . update ()
176
+ await self ._coordinator . async_request_refresh ()
125
177
126
178
@property
127
179
def device_info (self ):
@@ -135,3 +187,27 @@ def device_info(self):
135
187
if self ._device .parent_device_id :
136
188
device_info ["via_device" ] = (DOMAIN , self ._device .parent_device_id )
137
189
return device_info
190
+
191
+ @callback
192
+ def _async_consume_update (self ):
193
+ if time .time () - self ._last_action_timestamp <= TRANSITION_START_DURATION :
194
+ # If we just started a transition we need
195
+ # to prevent a bouncy state
196
+ return
197
+
198
+ self .async_write_ha_state ()
199
+
200
+ @property
201
+ def should_poll (self ):
202
+ """Return False, updates are controlled via coordinator."""
203
+ return False
204
+
205
+ async def async_added_to_hass (self ):
206
+ """Subscribe to updates."""
207
+ self ._coordinator .async_add_listener (self ._async_consume_update )
208
+
209
+ async def async_will_remove_from_hass (self ):
210
+ """Undo subscription."""
211
+ self ._coordinator .async_remove_listener (self ._async_consume_update )
212
+ if self ._scheduled_transition_update :
213
+ self ._scheduled_transition_update ()
0 commit comments