boneIO 0.11.0.dev2__py3-none-any.whl → 0.11.0.dev4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- boneio/const.py +3 -0
- boneio/cover/__init__.py +6 -0
- boneio/cover/cover.py +289 -0
- boneio/{cover.py → cover/previous.py} +16 -7
- boneio/cover/time_based.py +118 -0
- boneio/cover/venetian.py +275 -0
- boneio/helper/__init__.py +10 -4
- boneio/helper/exceptions.py +2 -0
- boneio/helper/ha_discovery.py +31 -0
- boneio/helper/loader.py +59 -17
- boneio/manager.py +93 -53
- boneio/modbus/liquid-sensor.json +5 -5
- boneio/models/__init__.py +8 -1
- boneio/schema/actions.yaml +18 -1
- boneio/schema/schema.yaml +27 -0
- boneio/sensor/gpio_new.py +2 -0
- boneio/version.py +1 -1
- boneio/webui/app.py +25 -2
- boneio/webui/frontend-dist/assets/{cssMode-CGPtA-VR.js → cssMode-C5afUc0K.js} +1 -1
- boneio/webui/frontend-dist/assets/{editor.worker-D5ngByhL.js → editor.worker-oRlJJsnX.js} +6 -6
- boneio/webui/frontend-dist/assets/{freemarker2-D1CirOL5.js → freemarker2-emRcpycN.js} +1 -1
- boneio/webui/frontend-dist/assets/{handlebars-IqYH5zJa.js → handlebars-mMSD-c9g.js} +1 -1
- boneio/webui/frontend-dist/assets/{html-JnBHoSjR.js → html-Clq5L_-r.js} +1 -1
- boneio/webui/frontend-dist/assets/{htmlMode-XHuWx37o.js → htmlMode-CYqwdxDX.js} +1 -1
- boneio/webui/frontend-dist/assets/index-BcJKL9Rz.js +829 -0
- boneio/webui/frontend-dist/assets/index-DzcnWIxb.css +1 -0
- boneio/webui/frontend-dist/assets/{javascript-M7D_Xsxc.js → javascript-BR7Nbw1G.js} +1 -1
- boneio/webui/frontend-dist/assets/{json.worker-F0ZGA5Zk.js → json.worker-BnUULff4.js} +21 -21
- boneio/webui/frontend-dist/assets/{jsonMode-BTyMC1Tb.js → jsonMode-aJvzz7xf.js} +3 -3
- boneio/webui/frontend-dist/assets/{liquid-B62I3JmO.js → liquid-BNyvmcbu.js} +1 -1
- boneio/webui/frontend-dist/assets/{mdx-F7-QsS7b.js → mdx-pWhQfMuK.js} +1 -1
- boneio/webui/frontend-dist/assets/{python-BTlOxdWb.js → python-CVtAQrrC.js} +1 -1
- boneio/webui/frontend-dist/assets/{razor-Bscm8KjN.js → razor-BBiV-C6e.js} +1 -1
- boneio/webui/frontend-dist/assets/{tsMode-CrG5AngL.js → tsMode-COZIvtMx.js} +1 -1
- boneio/webui/frontend-dist/assets/{typescript-I2vsHIYp.js → typescript-DKdGc2VX.js} +1 -1
- boneio/webui/frontend-dist/assets/{xml-xRg1CRI5.js → xml-CTK9ybeu.js} +1 -1
- boneio/webui/frontend-dist/assets/{yaml-BASa6xGy.js → yaml-ggTgJ1wa.js} +1 -1
- boneio/webui/frontend-dist/assets/yaml.worker-DSHdx_mx.js +411 -0
- boneio/webui/frontend-dist/index.html +2 -2
- boneio/webui/schema/binary_sensor.schema.json +62 -2
- boneio/webui/schema/config.schema.json +190 -5
- boneio/webui/schema/cover.schema.json +35 -0
- boneio/webui/schema/event.schema.json +93 -3
- {boneio-0.11.0.dev2.dist-info → boneio-0.11.0.dev4.dist-info}/METADATA +1 -1
- {boneio-0.11.0.dev2.dist-info → boneio-0.11.0.dev4.dist-info}/RECORD +48 -44
- boneio/webui/frontend-dist/assets/index-DBEImLlU.css +0 -1
- boneio/webui/frontend-dist/assets/index-DSSYjJ1h.js +0 -825
- boneio/webui/frontend-dist/assets/yaml.worker-C2uymlqn.js +0 -476
- {boneio-0.11.0.dev2.dist-info → boneio-0.11.0.dev4.dist-info}/WHEEL +0 -0
- {boneio-0.11.0.dev2.dist-info → boneio-0.11.0.dev4.dist-info}/entry_points.txt +0 -0
- {boneio-0.11.0.dev2.dist-info → boneio-0.11.0.dev4.dist-info}/licenses/LICENSE +0 -0
boneio/const.py
CHANGED
boneio/cover/__init__.py
ADDED
boneio/cover/cover.py
ADDED
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import logging
|
|
5
|
+
import threading
|
|
6
|
+
import time
|
|
7
|
+
from abc import ABC, abstractmethod
|
|
8
|
+
from typing import Callable
|
|
9
|
+
|
|
10
|
+
from boneio.const import (
|
|
11
|
+
CLOSED,
|
|
12
|
+
CLOSING,
|
|
13
|
+
COVER,
|
|
14
|
+
IDLE,
|
|
15
|
+
OPEN,
|
|
16
|
+
OPENING,
|
|
17
|
+
)
|
|
18
|
+
from boneio.helper.events import EventBus
|
|
19
|
+
from boneio.helper.mqtt import BasicMqtt
|
|
20
|
+
from boneio.helper.timeperiod import TimePeriod
|
|
21
|
+
from boneio.models import CoverState, PositionDict
|
|
22
|
+
from boneio.relay import MCPRelay
|
|
23
|
+
|
|
24
|
+
_LOGGER = logging.getLogger(__name__)
|
|
25
|
+
|
|
26
|
+
class BaseCoverABC(ABC):
|
|
27
|
+
"""Base cover class."""
|
|
28
|
+
|
|
29
|
+
@abstractmethod
|
|
30
|
+
def __init__(self, id: str,
|
|
31
|
+
open_relay: MCPRelay,
|
|
32
|
+
close_relay: MCPRelay,
|
|
33
|
+
state_save: Callable,
|
|
34
|
+
open_time: TimePeriod,
|
|
35
|
+
close_time: TimePeriod,
|
|
36
|
+
event_bus: EventBus,
|
|
37
|
+
position: int = 100,
|
|
38
|
+
**kwargs,
|
|
39
|
+
) -> None:
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@abstractmethod
|
|
44
|
+
async def stop(self) -> None:
|
|
45
|
+
"""Stop cover."""
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
@abstractmethod
|
|
49
|
+
async def open(self) -> None:
|
|
50
|
+
"""Open cover."""
|
|
51
|
+
pass
|
|
52
|
+
|
|
53
|
+
@abstractmethod
|
|
54
|
+
async def close(self) -> None:
|
|
55
|
+
"""Close cover."""
|
|
56
|
+
pass
|
|
57
|
+
|
|
58
|
+
@abstractmethod
|
|
59
|
+
async def toggle(self) -> None:
|
|
60
|
+
"""Toggle cover to open or close."""
|
|
61
|
+
pass
|
|
62
|
+
|
|
63
|
+
@abstractmethod
|
|
64
|
+
async def toggle_open(self) -> None:
|
|
65
|
+
"""Toggle cover to open or stop."""
|
|
66
|
+
pass
|
|
67
|
+
|
|
68
|
+
@abstractmethod
|
|
69
|
+
async def toggle_close(self) -> None:
|
|
70
|
+
"""Toggle cover to close or stop."""
|
|
71
|
+
pass
|
|
72
|
+
|
|
73
|
+
@abstractmethod
|
|
74
|
+
async def set_cover_position(self, position: int) -> None:
|
|
75
|
+
"""Set cover position."""
|
|
76
|
+
pass
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@property
|
|
80
|
+
@abstractmethod
|
|
81
|
+
def state(self) -> str:
|
|
82
|
+
pass
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
@abstractmethod
|
|
86
|
+
def position(self) -> int:
|
|
87
|
+
pass
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
@abstractmethod
|
|
91
|
+
def current_operation(self) -> str:
|
|
92
|
+
pass
|
|
93
|
+
|
|
94
|
+
@property
|
|
95
|
+
@abstractmethod
|
|
96
|
+
def last_timestamp(self) -> float:
|
|
97
|
+
pass
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@abstractmethod
|
|
101
|
+
async def run_cover(self, current_operation: str, target_position: float | None = None, target_tilt: float | None = None) -> None:
|
|
102
|
+
"""This function is called to run cover after calling open, close, toggle, toggle_open, toggle_close, set_cover_position"""
|
|
103
|
+
pass
|
|
104
|
+
|
|
105
|
+
@abstractmethod
|
|
106
|
+
async def send_state(self, state: str, position: float) -> None:
|
|
107
|
+
pass
|
|
108
|
+
|
|
109
|
+
class BaseVenetianCoverABC:
|
|
110
|
+
@property
|
|
111
|
+
@abstractmethod
|
|
112
|
+
def tilt_position(self) -> int:
|
|
113
|
+
pass
|
|
114
|
+
|
|
115
|
+
@property
|
|
116
|
+
@abstractmethod
|
|
117
|
+
def tilt_current_operation(self) -> str:
|
|
118
|
+
pass
|
|
119
|
+
|
|
120
|
+
@property
|
|
121
|
+
@abstractmethod
|
|
122
|
+
def last_tilt_timestamp(self) -> float:
|
|
123
|
+
pass
|
|
124
|
+
|
|
125
|
+
@abstractmethod
|
|
126
|
+
async def set_cover_tilt_position(self, position: int) -> None:
|
|
127
|
+
"""Set cover tilt position."""
|
|
128
|
+
pass
|
|
129
|
+
|
|
130
|
+
@abstractmethod
|
|
131
|
+
async def tilt_open(self) -> None:
|
|
132
|
+
"""Open cover tilt."""
|
|
133
|
+
pass
|
|
134
|
+
|
|
135
|
+
@abstractmethod
|
|
136
|
+
async def tilt_close(self) -> None:
|
|
137
|
+
"""Close cover tilt."""
|
|
138
|
+
pass
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class BaseCover(BaseCoverABC, BasicMqtt):
|
|
142
|
+
def __init__(self, id: str,
|
|
143
|
+
open_relay: MCPRelay,
|
|
144
|
+
close_relay: MCPRelay,
|
|
145
|
+
state_save: Callable,
|
|
146
|
+
open_time: TimePeriod,
|
|
147
|
+
close_time: TimePeriod,
|
|
148
|
+
event_bus: EventBus,
|
|
149
|
+
position: int = 100,
|
|
150
|
+
**kwargs,
|
|
151
|
+
) -> None:
|
|
152
|
+
BasicMqtt.__init__(self, id=id, name=id, topic_type=COVER, **kwargs)
|
|
153
|
+
self._loop = asyncio.get_event_loop()
|
|
154
|
+
self._id = id
|
|
155
|
+
self._open_relay = open_relay
|
|
156
|
+
self._close_relay = close_relay
|
|
157
|
+
self._state_save = state_save
|
|
158
|
+
self._event_bus = event_bus
|
|
159
|
+
self._open_time = open_time.total_milliseconds
|
|
160
|
+
self._close_time = close_time.total_milliseconds
|
|
161
|
+
self._position = position
|
|
162
|
+
self._initial_position = None
|
|
163
|
+
self._current_operation = IDLE
|
|
164
|
+
|
|
165
|
+
self._last_timestamp = time.monotonic()
|
|
166
|
+
|
|
167
|
+
self._last_update_time = 0
|
|
168
|
+
self._closed = position <= 0
|
|
169
|
+
|
|
170
|
+
self._movement_thread = None
|
|
171
|
+
self._stop_event = threading.Event()
|
|
172
|
+
|
|
173
|
+
self._event_bus.add_sigterm_listener(self.on_exit)
|
|
174
|
+
|
|
175
|
+
def _schedule_send_state_task(state_to_send, json_pos_to_send):
|
|
176
|
+
asyncio.ensure_future(self.send_state(state_to_send, json_pos_to_send), loop=self._loop)
|
|
177
|
+
|
|
178
|
+
self._loop.call_soon_threadsafe(
|
|
179
|
+
self._loop.call_later,
|
|
180
|
+
0.5,
|
|
181
|
+
_schedule_send_state_task,
|
|
182
|
+
self.state,
|
|
183
|
+
self.json_position
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
async def on_exit(self) -> None:
|
|
187
|
+
"""Stop on exit."""
|
|
188
|
+
await self.stop(on_exit=True)
|
|
189
|
+
|
|
190
|
+
async def stop(self, on_exit=False) -> None:
|
|
191
|
+
if self._movement_thread and self._movement_thread.is_alive():
|
|
192
|
+
self._stop_event.set()
|
|
193
|
+
self._movement_thread.join(timeout=0.5)
|
|
194
|
+
self._open_relay.turn_off()
|
|
195
|
+
self._close_relay.turn_off()
|
|
196
|
+
self._current_operation = IDLE
|
|
197
|
+
if not on_exit:
|
|
198
|
+
await self.send_state(self.state, self.json_position)
|
|
199
|
+
|
|
200
|
+
async def open(self) -> None:
|
|
201
|
+
if self._position >= 100:
|
|
202
|
+
return
|
|
203
|
+
_LOGGER.info("Opening cover %s.", self._id)
|
|
204
|
+
await self.run_cover(current_operation=OPENING)
|
|
205
|
+
self._send_message(topic=f"{self._send_topic}/state", payload=OPENING)
|
|
206
|
+
|
|
207
|
+
async def close(self) -> None:
|
|
208
|
+
if self._position <= 0:
|
|
209
|
+
return
|
|
210
|
+
_LOGGER.info("Closing cover %s.", self._id)
|
|
211
|
+
await self.run_cover(current_operation=CLOSING)
|
|
212
|
+
self._send_message(topic=f"{self._send_topic}/state", payload=CLOSING)
|
|
213
|
+
|
|
214
|
+
async def set_cover_position(self, position: int) -> None:
|
|
215
|
+
if not 0 <= position <= 100:
|
|
216
|
+
raise ValueError("Pozycja musi być w zakresie od 0 do 100.")
|
|
217
|
+
|
|
218
|
+
if abs(self._position - position) < 1:
|
|
219
|
+
return
|
|
220
|
+
|
|
221
|
+
if position > self._position:
|
|
222
|
+
await self.run_cover(current_operation=OPENING, target_position=position)
|
|
223
|
+
elif position < self._position:
|
|
224
|
+
await self.run_cover(current_operation=CLOSING, target_position=position)
|
|
225
|
+
|
|
226
|
+
async def toggle(self) -> None:
|
|
227
|
+
_LOGGER.debug("Toggle cover %s from input.", self._id)
|
|
228
|
+
if self._position > 50:
|
|
229
|
+
await self.close()
|
|
230
|
+
else:
|
|
231
|
+
await self.open()
|
|
232
|
+
|
|
233
|
+
async def toggle_open(self) -> None:
|
|
234
|
+
_LOGGER.debug("Toggle open cover %s from input.", self._id)
|
|
235
|
+
if self._current_operation != IDLE:
|
|
236
|
+
await self.stop()
|
|
237
|
+
else:
|
|
238
|
+
await self.open()
|
|
239
|
+
|
|
240
|
+
async def toggle_close(self) -> None:
|
|
241
|
+
_LOGGER.debug("Toggle close cover %s from input.", self._id)
|
|
242
|
+
if self._current_operation != IDLE:
|
|
243
|
+
await self.stop()
|
|
244
|
+
else:
|
|
245
|
+
await self.close()
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
@property
|
|
249
|
+
def state(self) -> str:
|
|
250
|
+
if self._current_operation == OPENING:
|
|
251
|
+
return OPENING
|
|
252
|
+
elif self._current_operation == CLOSING:
|
|
253
|
+
return CLOSING
|
|
254
|
+
else:
|
|
255
|
+
return CLOSED if self._position == 0 else OPEN
|
|
256
|
+
|
|
257
|
+
@property
|
|
258
|
+
def position(self) -> int:
|
|
259
|
+
return round(self._position, 0)
|
|
260
|
+
|
|
261
|
+
@property
|
|
262
|
+
def json_position(self) -> PositionDict:
|
|
263
|
+
return {"position": self.position}
|
|
264
|
+
|
|
265
|
+
@property
|
|
266
|
+
def current_operation(self) -> str:
|
|
267
|
+
return self._current_operation
|
|
268
|
+
|
|
269
|
+
@property
|
|
270
|
+
def last_timestamp(self) -> float:
|
|
271
|
+
return self._last_timestamp
|
|
272
|
+
|
|
273
|
+
async def send_state(self, state: str, json_position: PositionDict = None) -> None:
|
|
274
|
+
event = CoverState(
|
|
275
|
+
id=self.id,
|
|
276
|
+
name=self.name,
|
|
277
|
+
state=state,
|
|
278
|
+
kind=self.kind,
|
|
279
|
+
timestamp=self._last_timestamp,
|
|
280
|
+
current_operation=self._current_operation,
|
|
281
|
+
**json_position
|
|
282
|
+
)
|
|
283
|
+
await self._event_bus.async_trigger_event(event_type="cover", entity_id=self.id, event=event)
|
|
284
|
+
self._send_message(topic=f"{self._send_topic}/state", payload=state)
|
|
285
|
+
self._send_message(topic=f"{self._send_topic}/pos", payload=json_position)
|
|
286
|
+
|
|
287
|
+
async def send_state_and_save(self, json_position: PositionDict):
|
|
288
|
+
await self.send_state(self.state, json_position)
|
|
289
|
+
self._state_save(json_position)
|
|
@@ -24,7 +24,7 @@ from boneio.models import CoverState
|
|
|
24
24
|
from boneio.relay import MCPRelay
|
|
25
25
|
|
|
26
26
|
_LOGGER = logging.getLogger(__name__)
|
|
27
|
-
|
|
27
|
+
DEFAULT_RESTORED_STATE = {"position": 100}
|
|
28
28
|
COVER_COMMANDS = {
|
|
29
29
|
OPEN: "open_cover",
|
|
30
30
|
CLOSE: "close_cover",
|
|
@@ -53,7 +53,7 @@ class RelayHelper:
|
|
|
53
53
|
return self._steps
|
|
54
54
|
|
|
55
55
|
|
|
56
|
-
class
|
|
56
|
+
class PreviousCover(BasicMqtt):
|
|
57
57
|
"""Cover class of boneIO"""
|
|
58
58
|
|
|
59
59
|
def __init__(
|
|
@@ -65,7 +65,7 @@ class Cover(BasicMqtt):
|
|
|
65
65
|
open_time: TimePeriod,
|
|
66
66
|
close_time: TimePeriod,
|
|
67
67
|
event_bus: EventBus,
|
|
68
|
-
restored_state:
|
|
68
|
+
restored_state: dict = DEFAULT_RESTORED_STATE,
|
|
69
69
|
**kwargs,
|
|
70
70
|
) -> None:
|
|
71
71
|
"""Initialize cover class."""
|
|
@@ -78,7 +78,7 @@ class Cover(BasicMqtt):
|
|
|
78
78
|
self._close = RelayHelper(relay=close_relay, time=close_time)
|
|
79
79
|
self._set_position = None
|
|
80
80
|
self._current_operation = IDLE
|
|
81
|
-
self._position = restored_state
|
|
81
|
+
self._position = float(restored_state.get("position", DEFAULT_RESTORED_STATE["position"]))
|
|
82
82
|
self._requested_closing = True
|
|
83
83
|
self._event_bus = event_bus
|
|
84
84
|
self._timer_handle = None
|
|
@@ -142,6 +142,7 @@ class Cover(BasicMqtt):
|
|
|
142
142
|
name=self.name,
|
|
143
143
|
state=self.state,
|
|
144
144
|
position=round(self._position, 0),
|
|
145
|
+
kind=self.kind,
|
|
145
146
|
timestamp=self.last_timestamp,
|
|
146
147
|
current_operation=self._current_operation,
|
|
147
148
|
)
|
|
@@ -153,8 +154,8 @@ class Cover(BasicMqtt):
|
|
|
153
154
|
topic=f"{self._send_topic}/state", payload=self.state
|
|
154
155
|
)
|
|
155
156
|
pos = round(self._position, 0)
|
|
156
|
-
self._send_message(topic=f"{self._send_topic}/pos", payload=str(pos))
|
|
157
|
-
self._state_save(position
|
|
157
|
+
self._send_message(topic=f"{self._send_topic}/pos", payload={ "position": str(pos) })
|
|
158
|
+
self._state_save(value={"position": pos})
|
|
158
159
|
|
|
159
160
|
def _stop_cover(self, on_exit=False) -> None:
|
|
160
161
|
"""Stop cover."""
|
|
@@ -200,7 +201,7 @@ class Cover(BasicMqtt):
|
|
|
200
201
|
rounded_pos = 100
|
|
201
202
|
elif rounded_pos < 0:
|
|
202
203
|
rounded_pos = 0
|
|
203
|
-
self._send_message(topic=f"{self._send_topic}/pos", payload=rounded_pos)
|
|
204
|
+
self._send_message(topic=f"{self._send_topic}/pos", payload={"position": str(rounded_pos)})
|
|
204
205
|
asyncio.create_task(self.async_send_state())
|
|
205
206
|
if rounded_pos == self._set_position or (
|
|
206
207
|
self._set_position is None
|
|
@@ -307,3 +308,11 @@ class Cover(BasicMqtt):
|
|
|
307
308
|
@property
|
|
308
309
|
def current_operation(self) -> str:
|
|
309
310
|
return self._current_operation
|
|
311
|
+
|
|
312
|
+
@property
|
|
313
|
+
def kind(self) -> str:
|
|
314
|
+
return "previous"
|
|
315
|
+
|
|
316
|
+
def update_config_times(self, config: dict) -> None:
|
|
317
|
+
self._open_duration = config.get("open_duration", self._open_duration)
|
|
318
|
+
self._close_duration = config.get("close_duration", self._close_duration)
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import logging
|
|
5
|
+
import threading
|
|
6
|
+
import time
|
|
7
|
+
from typing import Callable, Optional
|
|
8
|
+
|
|
9
|
+
from boneio.const import CLOSE, CLOSING, IDLE, OPEN, OPENING, STOP
|
|
10
|
+
from boneio.cover.cover import BaseCover
|
|
11
|
+
from boneio.helper.events import EventBus
|
|
12
|
+
from boneio.helper.timeperiod import TimePeriod
|
|
13
|
+
from boneio.relay import MCPRelay
|
|
14
|
+
|
|
15
|
+
_LOGGER = logging.getLogger(__name__)
|
|
16
|
+
DEFAULT_RESTORED_STATE = {"position": 100}
|
|
17
|
+
|
|
18
|
+
class TimeBasedCover(BaseCover):
|
|
19
|
+
"""Time-based cover algorithm similar to ESPHome."""
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
open_relay: MCPRelay,
|
|
23
|
+
close_relay: MCPRelay,
|
|
24
|
+
state_save: Callable,
|
|
25
|
+
open_time: TimePeriod,
|
|
26
|
+
close_time: TimePeriod,
|
|
27
|
+
event_bus: EventBus,
|
|
28
|
+
restored_state: dict = DEFAULT_RESTORED_STATE,
|
|
29
|
+
**kwargs,
|
|
30
|
+
) -> None:
|
|
31
|
+
position = int(restored_state.get("position", DEFAULT_RESTORED_STATE["position"]))
|
|
32
|
+
super().__init__(
|
|
33
|
+
open_relay=open_relay,
|
|
34
|
+
close_relay=close_relay,
|
|
35
|
+
state_save=state_save,
|
|
36
|
+
open_time=open_time,
|
|
37
|
+
close_time=close_time,
|
|
38
|
+
event_bus=event_bus,
|
|
39
|
+
position=position,
|
|
40
|
+
**kwargs,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _move_cover(self, direction: str, duration: float, target_position: Optional[int] = None):
|
|
45
|
+
"""Metoda uruchamiana w oddzielnym wątku do fizycznego ruchu rolety."""
|
|
46
|
+
if direction == OPEN:
|
|
47
|
+
relay = self._open_relay
|
|
48
|
+
total_steps = 100 - self._position
|
|
49
|
+
elif direction == CLOSE:
|
|
50
|
+
relay = self._close_relay
|
|
51
|
+
total_steps = self._position
|
|
52
|
+
else:
|
|
53
|
+
return
|
|
54
|
+
|
|
55
|
+
if total_steps == 0 or duration == 0:
|
|
56
|
+
self._current_operation = IDLE
|
|
57
|
+
self._loop.call_soon_threadsafe(asyncio.run_coroutine_threadsafe, self.send_state(self.state, self.json_position), self._loop)
|
|
58
|
+
return
|
|
59
|
+
|
|
60
|
+
relay.turn_on()
|
|
61
|
+
start_time = time.monotonic()
|
|
62
|
+
|
|
63
|
+
while not self._stop_event.is_set():
|
|
64
|
+
current_time = time.monotonic() # Pobierz aktualny czas tylko raz na iterację
|
|
65
|
+
elapsed_time = (current_time - start_time) * 1000 # Konwersja na milisekundy
|
|
66
|
+
progress = elapsed_time / duration
|
|
67
|
+
|
|
68
|
+
if direction == OPEN:
|
|
69
|
+
self._position = min(100.0, self._initial_position + progress * 100)
|
|
70
|
+
elif direction == CLOSE:
|
|
71
|
+
self._position = max(0.0, self._initial_position - progress * 100)
|
|
72
|
+
|
|
73
|
+
self._last_timestamp = current_time # Użyj pobranego czasu
|
|
74
|
+
if current_time - self._last_update_time >= 1:
|
|
75
|
+
self._loop.call_soon_threadsafe(asyncio.run_coroutine_threadsafe, self.send_state(self.state, self.json_position), self._loop)
|
|
76
|
+
self._last_update_time = current_time
|
|
77
|
+
|
|
78
|
+
if target_position is not None:
|
|
79
|
+
if (direction == OPEN and self._position >= target_position) or \
|
|
80
|
+
(direction == CLOSE and self._position <= target_position):
|
|
81
|
+
break
|
|
82
|
+
|
|
83
|
+
if progress >= 1.0:
|
|
84
|
+
break
|
|
85
|
+
|
|
86
|
+
time.sleep(0.05) # Małe opóźnienie, aby nie blokować CPU
|
|
87
|
+
relay.turn_off()
|
|
88
|
+
self._current_operation = IDLE
|
|
89
|
+
self._loop.call_soon_threadsafe(asyncio.run_coroutine_threadsafe, self.async_state_save(self.json_position), self._loop)
|
|
90
|
+
self._loop.call_soon_threadsafe(asyncio.run_coroutine_threadsafe, self.send_state(self.state, self.json_position), self._loop)
|
|
91
|
+
self._last_update_time = time.monotonic() # Upewnij się, że aktualizacja jest wysłana na końcu ruchu
|
|
92
|
+
|
|
93
|
+
async def run_cover(self, current_operation: str, target_position: Optional[int] = None) -> None:
|
|
94
|
+
if self._movement_thread and self._movement_thread.is_alive() or current_operation == STOP:
|
|
95
|
+
_LOGGER.warning("Ruch rolety już trwa. Najpierw zatrzymaj.")
|
|
96
|
+
await self.stop()
|
|
97
|
+
|
|
98
|
+
self._current_operation = current_operation
|
|
99
|
+
self._initial_position = self._position
|
|
100
|
+
self._stop_event.clear()
|
|
101
|
+
self._last_update_time = time.monotonic() - 1 # Inicjalizacja czasu ostatniej aktualizacji
|
|
102
|
+
|
|
103
|
+
if current_operation == OPENING:
|
|
104
|
+
self._movement_thread = threading.Thread(target=self._move_cover, args=("open", self._open_time, target_position))
|
|
105
|
+
self._movement_thread.start()
|
|
106
|
+
elif current_operation == CLOSING:
|
|
107
|
+
self._movement_thread = threading.Thread(target=self._move_cover, args=("close", self._close_time, target_position))
|
|
108
|
+
self._movement_thread.start()
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@property
|
|
112
|
+
def kind(self) -> str:
|
|
113
|
+
return "time"
|
|
114
|
+
|
|
115
|
+
def update_config_times(self, config: dict) -> None:
|
|
116
|
+
self._open_duration = config.get("open_duration", self._open_duration)
|
|
117
|
+
self._close_duration = config.get("close_duration", self._close_duration)
|
|
118
|
+
|