difonlib 0.4.2__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.
Files changed (24) hide show
  1. {difonlib-0.4.2 → difonlib-0.4.4}/PKG-INFO +1 -1
  2. {difonlib-0.4.2 → difonlib-0.4.4}/pyproject.toml +1 -1
  3. {difonlib-0.4.2 → difonlib-0.4.4}/src/difonlib/ng_lib.py +137 -5
  4. difonlib-0.4.4/src/difonlib/ng_lib_test3.py +148 -0
  5. {difonlib-0.4.2 → difonlib-0.4.4}/src/difonlib/tuya_devs.py +7 -1
  6. {difonlib-0.4.2 → difonlib-0.4.4}/src/difonlib.egg-info/PKG-INFO +1 -1
  7. difonlib-0.4.2/src/difonlib/ng_lib_test3.py +0 -77
  8. {difonlib-0.4.2 → difonlib-0.4.4}/README.md +0 -0
  9. {difonlib-0.4.2 → difonlib-0.4.4}/setup.cfg +0 -0
  10. {difonlib-0.4.2 → difonlib-0.4.4}/src/difonlib/__init__.py +0 -0
  11. {difonlib-0.4.2 → difonlib-0.4.4}/src/difonlib/bt_scanner.py +0 -0
  12. {difonlib-0.4.2 → difonlib-0.4.4}/src/difonlib/bt_utils.py +0 -0
  13. {difonlib-0.4.2 → difonlib-0.4.4}/src/difonlib/conn_kbd_devs.py +0 -0
  14. {difonlib-0.4.2 → difonlib-0.4.4}/src/difonlib/docker-android/get_tuya_preferences.py +0 -0
  15. {difonlib-0.4.2 → difonlib-0.4.4}/src/difonlib/input_devs.py +0 -0
  16. {difonlib-0.4.2 → difonlib-0.4.4}/src/difonlib/py.typed +0 -0
  17. {difonlib-0.4.2 → difonlib-0.4.4}/src/difonlib/remctrl.py +0 -0
  18. {difonlib-0.4.2 → difonlib-0.4.4}/src/difonlib/utils.py +0 -0
  19. {difonlib-0.4.2 → difonlib-0.4.4}/src/difonlib.egg-info/SOURCES.txt +0 -0
  20. {difonlib-0.4.2 → difonlib-0.4.4}/src/difonlib.egg-info/dependency_links.txt +0 -0
  21. {difonlib-0.4.2 → difonlib-0.4.4}/src/difonlib.egg-info/requires.txt +0 -0
  22. {difonlib-0.4.2 → difonlib-0.4.4}/src/difonlib.egg-info/top_level.txt +0 -0
  23. {difonlib-0.4.2 → difonlib-0.4.4}/tests/test_bt_utils.py +0 -0
  24. {difonlib-0.4.2 → difonlib-0.4.4}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: difonlib
3
- Version: 0.4.2
3
+ Version: 0.4.4
4
4
  Summary: python libraries
5
5
  Requires-Python: >=3.12
6
6
  Description-Content-Type: text/markdown
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "difonlib"
3
- version = "0.4.2"
3
+ version = "0.4.4"
4
4
  description = "python libraries"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -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)
@@ -80,7 +177,7 @@ class DialogBox:
80
177
 
81
178
 
82
179
  class CardTable:
83
- _current_yes_handler: Callable[[], None | Awaitable[None]] | None = None
180
+ # _current_yes_handler: Callable[[], None | Awaitable[None]] | None = None
84
181
 
85
182
  def __init__(
86
183
  self,
@@ -96,6 +193,8 @@ class CardTable:
96
193
  self.buttons_on_row_select_changed: list = []
97
194
  self.marked_text_color = marked_text_color
98
195
  self.marked_field = marked_field
196
+ self.filter_value: str | bool = ""
197
+ self._current_yes_handler: Callable[[], None | Awaitable[None]] | None = None
99
198
 
100
199
  with ui.card().classes("p-4 shadow-lg") as self.card:
101
200
  with ui.row().classes("items-center justify-between w-full mb-2"):
@@ -153,10 +252,42 @@ class CardTable:
153
252
  """Add field 'name', needed for Quasar GUI"""
154
253
  return [{**col, "name": col.get("name", col["field"])} for col in columns]
155
254
 
255
+ def set_row_display_filter(self, **field: str | bool) -> None:
256
+ """
257
+ card_table.set_row_display_filter(id="123456")
258
+ card_table.set_row_display_filter(_marked=True)
259
+ """
260
+ assert len(field) == 1, "❌ Exactly one field expected"
261
+ field_name, field_value = next(iter(field.items()))
262
+ if isinstance(field_value, bool):
263
+ self.filter_value = str(field_value).lower() # "true" / "false" для JS
264
+ else:
265
+ self.filter_value = f"\\'{field_value}\\'"
266
+ self.table.props(rf"""
267
+ :filter-method="(rows, terms) => !terms ? rows : rows.filter(r => r.{field_name} === terms)"
268
+ """)
269
+
270
+ def row_display_filter(self, state_on: bool = False) -> None:
271
+ """
272
+ Show rows with field id='123456' :
273
+ card_table.set_row_display_filter(id="123456")
274
+ card_table.row_display_filter(state_on=True)
275
+ Show rows with field _marked=True :
276
+ card_table.set_row_display_filter(_marked=True)
277
+ card_table.row_display_filter(True)
278
+ Show all rows:
279
+ card_table.row_display_filter()
280
+ """
281
+ if state_on:
282
+ self.table.props(f':filter="{self.filter_value}"')
283
+ else:
284
+ self.table.props(':filter=""')
285
+ self.table.update()
286
+
156
287
  def mark_row(self, mark: bool = True, **kwrds: Any) -> list[dict[str, Any]]:
157
288
  """
158
- card_table.mark_row(id="123456", name="ANNNNN")
159
- card_table.mark_row(ip="192.168.0.18", mark=False)
289
+ card_table.mark_row(id="123456", name="IRDevice.Lounge1")
290
+ card_table.mark_row(ip="192.168.0.18", mark=False) #Unmark
160
291
  """
161
292
  found = []
162
293
  for row in self.table.rows:
@@ -224,7 +355,8 @@ class CardTable:
224
355
  else:
225
356
  on_change(e.sender.value)
226
357
 
227
- chbox.on("click", handle_change)
358
+ # chbox.on("click", handle_change)
359
+ chbox.on("update:model-value", handle_change)
228
360
  setattr(self, f"chbox{chbox_txt.replace(' ', '_')}", chbox)
229
361
  return chbox
230
362
 
@@ -0,0 +1,148 @@
1
+ import asyncio
2
+ from nicegui import ui
3
+ from ng_lib import CardTable, DialogBox
4
+ import functools
5
+ import threading
6
+ import time
7
+
8
+ from difonlib.bt_utils import logdbg
9
+
10
+ dbg = logdbg
11
+
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
+
35
+ @ui.page("/", dark=True)
36
+ def main() -> None:
37
+
38
+ card_table = CardTable(
39
+ title="Connected Devices",
40
+ columns=[
41
+ {"name": "id", "label": "id", "field": "id"},
42
+ {"name": "name", "label": "Name", "field": "name"},
43
+ {"name": "ip", "label": "IP Address", "field": "ip"},
44
+ ],
45
+ selection="single",
46
+ rows=[
47
+ {"id": "123456", "name": "ANNNNN", "ip": "192.168.0.18"},
48
+ {"id": "987654", "name": "VNNNNN", "ip": "192.168.0.118"},
49
+ {"id": "123456", "name": "BNNNNN", "ip": "192.168.0.12"},
50
+ {"id": "003456", "name": "SNNNNN", "ip": "192.168.0.144"},
51
+ {"id": "129956", "name": "QNNNNN", "ip": "192.168.0.49"},
52
+ ],
53
+ )
54
+
55
+ found = card_table.mark_row(id="123456", mark=True)
56
+ print(f"card_table.table.rows:{card_table.table.rows}") # //Dima
57
+ found = card_table.mark_row(id="123456", name="ANNNNN", mark=False)
58
+ found = card_table.mark_row(id="129956")
59
+ print(f" ==> found: {found}") # //Dima
60
+
61
+ card_table.set_row_display_filter(_marked=True)
62
+ # card_table.set_row_display_filter(id="123456")
63
+
64
+ # self.table.props(':filter="\'online\'"')
65
+ # self.table.update()
66
+
67
+ # def only_marked(mark: bool = False, def_value="\\'\\'") -> None:
68
+ # print(f"table rows: {card_table.table.rows}")
69
+ # field_value = def_value
70
+ # vv = "123456"
71
+ # vv = mark
72
+ # # vv = "true"
73
+ # if mark:
74
+ # dbg(f" === MARK ===") # //Dima
75
+ # if isinstance(vv, str):
76
+ # field_value = f"\\'{vv}\\'"
77
+ # else:
78
+ # field_value = mark
79
+ # else:
80
+ # dbg(f" === NOT MARK ===") # //Dima
81
+ # dbg(f" * field_value: {field_value}") # //Dima
82
+ # card_table.table.props(f':filter="{field_value}"')
83
+ # card_table.table.update()
84
+
85
+ # card_table.add_checkbox(chbox_txt="Used Only", on_change=lambda v: ui.notify(v))
86
+ # card_table.add_checkbox(chbox_txt="Used Only", on_change=lambda v: only_marked(v))
87
+ card_table.add_checkbox(
88
+ chbox_txt="Used Only",
89
+ on_change=lambda v: card_table.row_display_filter(v),
90
+ )
91
+
92
+ dialog_box = DialogBox()
93
+
94
+ def btnok() -> None:
95
+ ui.notify("You press OK")
96
+
97
+ def btncancel() -> None:
98
+ ui.notify("You press Cancel")
99
+
100
+ card_table.add_button(
101
+ "test_ok_cancel",
102
+ on_click=functools.partial(
103
+ dialog_box.dialog_ok_cancel,
104
+ text="OK or Cancel?",
105
+ on_click_ok=btnok,
106
+ on_click_cancel=btncancel,
107
+ ),
108
+ )
109
+
110
+ async def confirm() -> None:
111
+ result = await dialog_box.dialog_confirm()
112
+ ui.notify(f"You chose {result}")
113
+
114
+ card_table.add_button(
115
+ "test_confirm",
116
+ on_click=confirm,
117
+ )
118
+
119
+ async def dialog_input() -> None:
120
+ inp = await dialog_box.dialog_input("Input IR_KEY_NAME", input_prefix="IR_KEY_")
121
+ ui.notify(f"Your input: {inp}")
122
+
123
+ card_table.add_button(
124
+ "test_input",
125
+ on_click=dialog_input,
126
+ )
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
+
147
+
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(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: difonlib
3
- Version: 0.4.2
3
+ Version: 0.4.4
4
4
  Summary: python libraries
5
5
  Requires-Python: >=3.12
6
6
  Description-Content-Type: text/markdown
@@ -1,77 +0,0 @@
1
- from nicegui import ui
2
- from ng_lib import CardTable, DialogBox
3
- import functools
4
-
5
- from difonlib.bt_utils import logdbg
6
-
7
- dbg = logdbg
8
-
9
-
10
- @ui.page("/", dark=True)
11
- def main() -> None:
12
-
13
- card_table = CardTable(
14
- title="Connected Devices",
15
- columns=[
16
- {"name": "id", "label": "id", "field": "id"},
17
- {"name": "name", "label": "Name", "field": "name"},
18
- {"name": "ip", "label": "IP Address", "field": "ip"},
19
- ],
20
- selection="single",
21
- rows=[
22
- {"id": "123456", "name": "ANNNNN", "ip": "192.168.0.18"},
23
- {"id": "987654", "name": "VNNNNN", "ip": "192.168.0.118"},
24
- {"id": "123456", "name": "BNNNNN", "ip": "192.168.0.12"},
25
- {"id": "003456", "name": "SNNNNN", "ip": "192.168.0.144"},
26
- {"id": "129956", "name": "QNNNNN", "ip": "192.168.0.49"},
27
- ],
28
- )
29
-
30
- found = card_table.mark_row(id="123456", mark=True)
31
- print(f"card_table.table.rows:{card_table.table.rows}") # //Dima
32
- found = card_table.mark_row(id="123456", name="ANNNNN", mark=False)
33
- found = card_table.mark_row(id="129956")
34
- print(f" ==> found: {found}") # //Dima
35
-
36
- print(f"card_table.table.rows: {card_table.table.rows}")
37
-
38
- card_table.add_checkbox(chbox_txt="Used Only", on_change=lambda v: ui.notify(v))
39
-
40
- dialog_box = DialogBox()
41
-
42
- def btnok() -> None:
43
- ui.notify("You press OK")
44
-
45
- def btncancel() -> None:
46
- ui.notify("You press Cancel")
47
-
48
- card_table.add_button(
49
- "test_ok_cancel",
50
- on_click=functools.partial(
51
- dialog_box.dialog_ok_cancel,
52
- text="OK or Cancel?",
53
- on_click_ok=btnok,
54
- on_click_cancel=btncancel,
55
- ),
56
- )
57
-
58
- async def confirm() -> None:
59
- result = await dialog_box.dialog_confirm()
60
- ui.notify(f"You chose {result}")
61
-
62
- card_table.add_button(
63
- "test_confirm",
64
- on_click=confirm,
65
- )
66
-
67
- async def dialog_input() -> None:
68
- inp = await dialog_box.dialog_input("Input IR_KEY_NAME", input_prefix="IR_KEY_")
69
- ui.notify(f"Your input: {inp}")
70
-
71
- card_table.add_button(
72
- "test_input",
73
- on_click=dialog_input,
74
- )
75
-
76
-
77
- ui.run(port=1111)
File without changes
File without changes
File without changes
File without changes
File without changes