difonlib 0.4.3__tar.gz → 0.4.4__tar.gz
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.
- {difonlib-0.4.3 → difonlib-0.4.4}/PKG-INFO +1 -1
- {difonlib-0.4.3 → difonlib-0.4.4}/pyproject.toml +1 -1
- {difonlib-0.4.3 → difonlib-0.4.4}/src/difonlib/ng_lib.py +110 -14
- {difonlib-0.4.3 → difonlib-0.4.4}/src/difonlib/ng_lib_test3.py +46 -2
- {difonlib-0.4.3 → difonlib-0.4.4}/src/difonlib/tuya_devs.py +7 -1
- {difonlib-0.4.3 → difonlib-0.4.4}/src/difonlib.egg-info/PKG-INFO +1 -1
- {difonlib-0.4.3 → difonlib-0.4.4}/README.md +0 -0
- {difonlib-0.4.3 → difonlib-0.4.4}/setup.cfg +0 -0
- {difonlib-0.4.3 → difonlib-0.4.4}/src/difonlib/__init__.py +0 -0
- {difonlib-0.4.3 → difonlib-0.4.4}/src/difonlib/bt_scanner.py +0 -0
- {difonlib-0.4.3 → difonlib-0.4.4}/src/difonlib/bt_utils.py +0 -0
- {difonlib-0.4.3 → difonlib-0.4.4}/src/difonlib/conn_kbd_devs.py +0 -0
- {difonlib-0.4.3 → difonlib-0.4.4}/src/difonlib/docker-android/get_tuya_preferences.py +0 -0
- {difonlib-0.4.3 → difonlib-0.4.4}/src/difonlib/input_devs.py +0 -0
- {difonlib-0.4.3 → difonlib-0.4.4}/src/difonlib/py.typed +0 -0
- {difonlib-0.4.3 → difonlib-0.4.4}/src/difonlib/remctrl.py +0 -0
- {difonlib-0.4.3 → difonlib-0.4.4}/src/difonlib/utils.py +0 -0
- {difonlib-0.4.3 → difonlib-0.4.4}/src/difonlib.egg-info/SOURCES.txt +0 -0
- {difonlib-0.4.3 → difonlib-0.4.4}/src/difonlib.egg-info/dependency_links.txt +0 -0
- {difonlib-0.4.3 → difonlib-0.4.4}/src/difonlib.egg-info/requires.txt +0 -0
- {difonlib-0.4.3 → difonlib-0.4.4}/src/difonlib.egg-info/top_level.txt +0 -0
- {difonlib-0.4.3 → difonlib-0.4.4}/tests/test_bt_utils.py +0 -0
- {difonlib-0.4.3 → difonlib-0.4.4}/tests/test_utils.py +0 -0
|
@@ -2,7 +2,7 @@ from nicegui import ui
|
|
|
2
2
|
import inspect
|
|
3
3
|
import asyncio
|
|
4
4
|
from typing import Any, Callable, Awaitable, Literal
|
|
5
|
-
|
|
5
|
+
import threading
|
|
6
6
|
from difonlib.utils import logdbg
|
|
7
7
|
|
|
8
8
|
dbg = logdbg
|
|
@@ -28,6 +28,103 @@ ui.add_css(
|
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
class DialogBox:
|
|
31
|
+
|
|
32
|
+
async def dialog_processing(
|
|
33
|
+
self,
|
|
34
|
+
proc: Callable,
|
|
35
|
+
proc_cancel_event: bool = False,
|
|
36
|
+
label_txt: str = "Processing...",
|
|
37
|
+
label_txt_color: str = "green",
|
|
38
|
+
timeout: int | None = None,
|
|
39
|
+
) -> tuple[Literal["Canceled", "Timeout"] | None, Any | None]:
|
|
40
|
+
"""
|
|
41
|
+
See examples in ng_lib_test3.py
|
|
42
|
+
def proc_dummy2(cancel_event: threading.Event) -> None:
|
|
43
|
+
for i in range(10, 0, -1):
|
|
44
|
+
if cancel_event.is_set():
|
|
45
|
+
print(f"Exit")
|
|
46
|
+
return
|
|
47
|
+
print(f"Remaining: {i}")
|
|
48
|
+
time.sleep(1)
|
|
49
|
+
async def aproc_dummy(cnt: int = 20):
|
|
50
|
+
for i in range(cnt, 0, -1):
|
|
51
|
+
print(f"aRemaining: {i}")
|
|
52
|
+
await asyncio.sleep(1)
|
|
53
|
+
return 125
|
|
54
|
+
|
|
55
|
+
async def proc_dialog():
|
|
56
|
+
err, result = await dialog_box.dialog_processing(
|
|
57
|
+
functools.partial(aproc_dummy, cnt=4), timeout=5
|
|
58
|
+
)
|
|
59
|
+
dbg(f"ERROR: {err}; Result: {result}") # //Dima
|
|
60
|
+
ui.button(
|
|
61
|
+
"Test processing dialog",
|
|
62
|
+
on_click=proc_dialog,
|
|
63
|
+
)
|
|
64
|
+
"""
|
|
65
|
+
task_proc: asyncio.Task | None = None
|
|
66
|
+
cancel_event = None
|
|
67
|
+
if proc_cancel_event:
|
|
68
|
+
cancel_event = threading.Event()
|
|
69
|
+
|
|
70
|
+
async def countdown(timeout: int, label: ui.label) -> None:
|
|
71
|
+
for counter in range(timeout, 0, -1):
|
|
72
|
+
label.text = f"{label_txt}{counter}"
|
|
73
|
+
await asyncio.sleep(1)
|
|
74
|
+
|
|
75
|
+
canceled = False
|
|
76
|
+
ret_value: Any | None = None
|
|
77
|
+
err: Literal["Canceled", "Timeout"] | None = None
|
|
78
|
+
|
|
79
|
+
async def on_cancel() -> None:
|
|
80
|
+
nonlocal canceled
|
|
81
|
+
canceled = True
|
|
82
|
+
if cancel_event:
|
|
83
|
+
cancel_event.set()
|
|
84
|
+
if task_proc:
|
|
85
|
+
task_proc.cancel()
|
|
86
|
+
dialog.close()
|
|
87
|
+
|
|
88
|
+
text_color = f"text-{label_txt_color}-500"
|
|
89
|
+
with (
|
|
90
|
+
ui.dialog().props("persistent") as dialog,
|
|
91
|
+
ui.card().classes("p-4 items-center justify-center"),
|
|
92
|
+
):
|
|
93
|
+
with ui.row().classes("items-center gap-3"):
|
|
94
|
+
ui.spinner(size="md")
|
|
95
|
+
label = ui.label(f"{label_txt}").classes(f"{text_color} text-2xl")
|
|
96
|
+
ui.button("Cancel", on_click=on_cancel)
|
|
97
|
+
dialog.open()
|
|
98
|
+
task_count: asyncio.Task | None = None
|
|
99
|
+
if timeout:
|
|
100
|
+
task_count = asyncio.create_task(countdown(timeout=timeout, label=label))
|
|
101
|
+
|
|
102
|
+
if proc_cancel_event:
|
|
103
|
+
if inspect.iscoroutinefunction(getattr(proc, "func", proc)):
|
|
104
|
+
task_proc = asyncio.create_task(proc(cancel_event))
|
|
105
|
+
else:
|
|
106
|
+
task_proc = asyncio.create_task(asyncio.to_thread(proc, cancel_event))
|
|
107
|
+
else:
|
|
108
|
+
if inspect.iscoroutinefunction(getattr(proc, "func", proc)):
|
|
109
|
+
task_proc = asyncio.create_task(proc())
|
|
110
|
+
else:
|
|
111
|
+
task_proc = asyncio.create_task(asyncio.to_thread(proc))
|
|
112
|
+
|
|
113
|
+
try:
|
|
114
|
+
ret_value = await asyncio.wait_for(task_proc, timeout=timeout)
|
|
115
|
+
except (asyncio.TimeoutError, asyncio.CancelledError):
|
|
116
|
+
if cancel_event:
|
|
117
|
+
cancel_event.set()
|
|
118
|
+
err = "Canceled" if canceled else "Timeout"
|
|
119
|
+
ui.notify(err)
|
|
120
|
+
|
|
121
|
+
finally:
|
|
122
|
+
if task_count and not task_count.cancelled():
|
|
123
|
+
task_count.cancel()
|
|
124
|
+
dialog.close()
|
|
125
|
+
|
|
126
|
+
return (err, ret_value)
|
|
127
|
+
|
|
31
128
|
async def dialog_ok(self, text: str = "Hello!)") -> None:
|
|
32
129
|
with ui.dialog().props("persistent") as dialog, ui.card():
|
|
33
130
|
ui.label(text=text)
|
|
@@ -155,33 +252,32 @@ class CardTable:
|
|
|
155
252
|
"""Add field 'name', needed for Quasar GUI"""
|
|
156
253
|
return [{**col, "name": col.get("name", col["field"])} for col in columns]
|
|
157
254
|
|
|
158
|
-
def set_row_display_filter(self,
|
|
255
|
+
def set_row_display_filter(self, **field: str | bool) -> None:
|
|
159
256
|
"""
|
|
160
|
-
card_table.set_row_display_filter(
|
|
161
|
-
|
|
162
|
-
card_table.row_display_filter(state_on=True)
|
|
163
|
-
Show all rows:
|
|
164
|
-
card_table.row_display_filter()
|
|
257
|
+
card_table.set_row_display_filter(id="123456")
|
|
258
|
+
card_table.set_row_display_filter(_marked=True)
|
|
165
259
|
"""
|
|
260
|
+
assert len(field) == 1, "❌ Exactly one field expected"
|
|
261
|
+
field_name, field_value = next(iter(field.items()))
|
|
166
262
|
if isinstance(field_value, bool):
|
|
167
|
-
self.filter_value = field_value
|
|
263
|
+
self.filter_value = str(field_value).lower() # "true" / "false" для JS
|
|
168
264
|
else:
|
|
169
265
|
self.filter_value = f"\\'{field_value}\\'"
|
|
170
|
-
|
|
171
266
|
self.table.props(rf"""
|
|
172
267
|
:filter-method="(rows, terms) => !terms ? rows : rows.filter(r => r.{field_name} === terms)"
|
|
173
268
|
""")
|
|
174
269
|
|
|
175
270
|
def row_display_filter(self, state_on: bool = False) -> None:
|
|
176
271
|
"""
|
|
177
|
-
|
|
272
|
+
Show rows with field id='123456' :
|
|
273
|
+
card_table.set_row_display_filter(id="123456")
|
|
178
274
|
card_table.row_display_filter(state_on=True)
|
|
179
|
-
|
|
180
|
-
card_table.set_row_display_filter(
|
|
275
|
+
Show rows with field _marked=True :
|
|
276
|
+
card_table.set_row_display_filter(_marked=True)
|
|
181
277
|
card_table.row_display_filter(True)
|
|
182
|
-
|
|
278
|
+
Show all rows:
|
|
279
|
+
card_table.row_display_filter()
|
|
183
280
|
"""
|
|
184
|
-
dbg(f"self.filter_value: {self.filter_value}") # //Dima
|
|
185
281
|
if state_on:
|
|
186
282
|
self.table.props(f':filter="{self.filter_value}"')
|
|
187
283
|
else:
|
|
@@ -1,12 +1,37 @@
|
|
|
1
|
+
import asyncio
|
|
1
2
|
from nicegui import ui
|
|
2
3
|
from ng_lib import CardTable, DialogBox
|
|
3
4
|
import functools
|
|
5
|
+
import threading
|
|
6
|
+
import time
|
|
4
7
|
|
|
5
8
|
from difonlib.bt_utils import logdbg
|
|
6
9
|
|
|
7
10
|
dbg = logdbg
|
|
8
11
|
|
|
9
12
|
|
|
13
|
+
def proc_dummy2(cancel_event: threading.Event) -> None:
|
|
14
|
+
for i in range(10, 0, -1):
|
|
15
|
+
if cancel_event.is_set():
|
|
16
|
+
print("Exit")
|
|
17
|
+
return
|
|
18
|
+
print(f"Remaining: {i}")
|
|
19
|
+
time.sleep(1)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def proc_dummy(cnt: int = 20) -> None:
|
|
23
|
+
for i in range(cnt, 0, -1):
|
|
24
|
+
print(f"Remaining: {i}")
|
|
25
|
+
time.sleep(1)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
async def aproc_dummy(cnt: int = 20) -> int:
|
|
29
|
+
for i in range(cnt, 0, -1):
|
|
30
|
+
print(f"aRemaining: {i}")
|
|
31
|
+
await asyncio.sleep(1)
|
|
32
|
+
return 125
|
|
33
|
+
|
|
34
|
+
|
|
10
35
|
@ui.page("/", dark=True)
|
|
11
36
|
def main() -> None:
|
|
12
37
|
|
|
@@ -33,8 +58,8 @@ def main() -> None:
|
|
|
33
58
|
found = card_table.mark_row(id="129956")
|
|
34
59
|
print(f" ==> found: {found}") # //Dima
|
|
35
60
|
|
|
36
|
-
card_table.set_row_display_filter(
|
|
37
|
-
# card_table.set_row_display_filter(
|
|
61
|
+
card_table.set_row_display_filter(_marked=True)
|
|
62
|
+
# card_table.set_row_display_filter(id="123456")
|
|
38
63
|
|
|
39
64
|
# self.table.props(':filter="\'online\'"')
|
|
40
65
|
# self.table.update()
|
|
@@ -100,5 +125,24 @@ def main() -> None:
|
|
|
100
125
|
on_click=dialog_input,
|
|
101
126
|
)
|
|
102
127
|
|
|
128
|
+
async def proc_dialog() -> None:
|
|
129
|
+
err, result = await dialog_box.dialog_processing(
|
|
130
|
+
functools.partial(aproc_dummy, cnt=4), timeout=5
|
|
131
|
+
)
|
|
132
|
+
dbg(f"ERROR: {err}; Result: {result}") # //Dima
|
|
133
|
+
ui.notify(f"ERROR: {err}; Result: {result}")
|
|
134
|
+
|
|
135
|
+
ui.button(
|
|
136
|
+
"Test processing dialog",
|
|
137
|
+
on_click=proc_dialog,
|
|
138
|
+
# on_click=lambda: dialog_box.dialog_processing(proc_dummy, timeout=4),
|
|
139
|
+
# on_click=lambda: dialog_box.dialog_processing(
|
|
140
|
+
# functools.partial(aproc_dummy, cnt=5), timeout=6
|
|
141
|
+
# ),
|
|
142
|
+
# on_click=lambda: dialog_box.dialog_processing(
|
|
143
|
+
# proc_dummy2, proc_cancel_event=True, timeout=4
|
|
144
|
+
# ),
|
|
145
|
+
)
|
|
146
|
+
|
|
103
147
|
|
|
104
148
|
ui.run(port=1111)
|
|
@@ -126,6 +126,11 @@ class TuyaDevs:
|
|
|
126
126
|
# return con_devs
|
|
127
127
|
|
|
128
128
|
def dev_is_connected(self, dev_id: str, timeout: int = 8, rescan: bool = False) -> bool:
|
|
129
|
+
"""
|
|
130
|
+
if rescan:
|
|
131
|
+
self.scan(timeout=timeout)
|
|
132
|
+
Check if dev_id in last scan list (self.devs_online)
|
|
133
|
+
"""
|
|
129
134
|
if rescan:
|
|
130
135
|
# update self.devs_online
|
|
131
136
|
self.scan(timeout=timeout)
|
|
@@ -178,9 +183,10 @@ class TuyaDevs:
|
|
|
178
183
|
return dev
|
|
179
184
|
|
|
180
185
|
def outlet_dev(self, dev_id: str, local_key: str) -> Optional[OutletDevice]:
|
|
181
|
-
"""TurnOn/Off:
|
|
186
|
+
"""TurnOn/Off & toggle:
|
|
182
187
|
odev.turn_on()
|
|
183
188
|
odev.turn_off()
|
|
189
|
+
odev.toggle()
|
|
184
190
|
"""
|
|
185
191
|
try:
|
|
186
192
|
dev = OutletDeviceM(
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|