cybertqdm 0.2.1__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.
- cybertqdm/.DS_Store +0 -0
- cybertqdm/__init__.py +5 -0
- cybertqdm/cybertqdm.py +120 -0
- cybertqdm/device/epd.py +993 -0
- cybertqdm/device/main.py +57 -0
- cybertqdm/diagnostics.py +110 -0
- cybertqdm/py.typed +0 -0
- cybertqdm-0.2.1.dist-info/METADATA +118 -0
- cybertqdm-0.2.1.dist-info/RECORD +11 -0
- cybertqdm-0.2.1.dist-info/WHEEL +4 -0
- cybertqdm-0.2.1.dist-info/licenses/LICENSE +15 -0
cybertqdm/.DS_Store
ADDED
|
Binary file
|
cybertqdm/__init__.py
ADDED
cybertqdm/cybertqdm.py
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import glob
|
|
2
|
+
import time
|
|
3
|
+
import warnings
|
|
4
|
+
|
|
5
|
+
import serial
|
|
6
|
+
from serial.tools import list_ports
|
|
7
|
+
from tqdm import tqdm
|
|
8
|
+
|
|
9
|
+
__all__ = ["CyberTqdm", "cybertqdm"]
|
|
10
|
+
|
|
11
|
+
PICO_VID = (
|
|
12
|
+
0x2E8A # Raspberry Pi vendor ID; used to detect the device in macOS and Linux
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def find_pico_ports():
|
|
17
|
+
"""Check serial ports of connected Pico devices, should work on any OS."""
|
|
18
|
+
return sorted(p.device for p in list_ports.comports() if p.vid == PICO_VID)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class CyberTqdm(tqdm):
|
|
22
|
+
"""A tqdm wrapper that also prints a progres bar onto a e-ink display.
|
|
23
|
+
|
|
24
|
+
To prevent wearing down the e-ink display too fast, console and e-ink update interval differs.
|
|
25
|
+
|
|
26
|
+
- console: normal tqdm update interval
|
|
27
|
+
- e-ink display: updates at start, then every `epaper_interval` seconds with a final update when the observed task ends.
|
|
28
|
+
- If no Pico 2 is detected or the connection is lost, a warning is output. Console operation remains unaffected.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
_ser = None # common connection for progress bars
|
|
32
|
+
_unavailable = False # in case of errors do not try to reconnect again and again...
|
|
33
|
+
|
|
34
|
+
def __init__(self, *args, port=None, epaper_interval=30, **kwargs):
|
|
35
|
+
self._epaper_interval = epaper_interval
|
|
36
|
+
self._last_send = 0.0
|
|
37
|
+
self._epaper_done = False
|
|
38
|
+
self._open_port(port)
|
|
39
|
+
super().__init__(*args, **kwargs)
|
|
40
|
+
|
|
41
|
+
# --- connection ---------------------------------------------------------
|
|
42
|
+
@staticmethod
|
|
43
|
+
def _open_port(port):
|
|
44
|
+
cls = CyberTqdm
|
|
45
|
+
if (cls._ser is not None and cls._ser.is_open) or cls._unavailable:
|
|
46
|
+
return
|
|
47
|
+
if port is None:
|
|
48
|
+
# ports = sorted(glob.glob("/dev/cu.usbmodem*")) # works only on macOS
|
|
49
|
+
ports = find_pico_ports()
|
|
50
|
+
if not ports:
|
|
51
|
+
cls._unavailable = True
|
|
52
|
+
warnings.warn(
|
|
53
|
+
"E-ink display unavailable – falling back to console output."
|
|
54
|
+
)
|
|
55
|
+
return
|
|
56
|
+
port = ports[0]
|
|
57
|
+
try:
|
|
58
|
+
cls._ser = serial.Serial(port, 115200, timeout=0, write_timeout=2)
|
|
59
|
+
except (serial.SerialException, OSError) as e:
|
|
60
|
+
cls._unavailable = True
|
|
61
|
+
warnings.warn(
|
|
62
|
+
f"E-ink display unavailable ({e}) – falling back to console output."
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
@staticmethod
|
|
66
|
+
def _drop_port(err):
|
|
67
|
+
cls = CyberTqdm
|
|
68
|
+
warnings.warn(
|
|
69
|
+
f"Lost connection to e-ink display ({err}) – falling back to console output."
|
|
70
|
+
)
|
|
71
|
+
try:
|
|
72
|
+
cls._ser.close()
|
|
73
|
+
except Exception:
|
|
74
|
+
pass
|
|
75
|
+
cls._ser = None
|
|
76
|
+
cls._unavailable = True
|
|
77
|
+
|
|
78
|
+
# --- send data -------------------------------------------------------------
|
|
79
|
+
def _send(self):
|
|
80
|
+
ser = CyberTqdm._ser
|
|
81
|
+
if ser is None or self.disable:
|
|
82
|
+
return
|
|
83
|
+
try:
|
|
84
|
+
if ser.in_waiting: # drop OK/busy messages from the Pico 2
|
|
85
|
+
ser.read(ser.in_waiting)
|
|
86
|
+
d = self.format_dict
|
|
87
|
+
desc = (
|
|
88
|
+
(self.desc or "").replace("|", "/").replace("\n", " ").strip(": ")[:30]
|
|
89
|
+
)
|
|
90
|
+
n = int(d["n"])
|
|
91
|
+
total = int(d["total"] or 0)
|
|
92
|
+
rate = d["rate"] or 0.0
|
|
93
|
+
ser.write(f"{desc}|{n}|{total}|{rate:.2f}\n".encode())
|
|
94
|
+
ser.flush()
|
|
95
|
+
except (serial.SerialException, OSError) as e:
|
|
96
|
+
self._drop_port(e)
|
|
97
|
+
self._last_send = time.time()
|
|
98
|
+
|
|
99
|
+
# --- tqdm hooks ---------------------------------------------------------
|
|
100
|
+
def display(self, msg=None, pos=None):
|
|
101
|
+
if time.time() - self._last_send >= self._epaper_interval:
|
|
102
|
+
self._send()
|
|
103
|
+
return super().display(msg, pos)
|
|
104
|
+
|
|
105
|
+
def close(self):
|
|
106
|
+
if not getattr(self, "_epaper_done", True):
|
|
107
|
+
self._epaper_done = True
|
|
108
|
+
self._send() # always display final state of the progress bar
|
|
109
|
+
super().close()
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def cybertqdm(*args, **kwargs):
|
|
113
|
+
"""Shortcut for CyberTqdm(range(*args), **kwargs)."""
|
|
114
|
+
return CyberTqdm(range(*args), **kwargs)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
if __name__ == "__main__":
|
|
118
|
+
# quick test: 60 steps, each sleeping for 0.5 seconds, e-ink display update every 10 seconds
|
|
119
|
+
for _ in cybertqdm(60, desc="Loosing faith in humanity...", epaper_interval=10):
|
|
120
|
+
time.sleep(0.5)
|