IS-Matrix-Forge 1.0.0.dev1__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.
- is_matrix_forge/__init__.py +8 -0
- is_matrix_forge/assets/__init__.py +8 -0
- is_matrix_forge/assets/audio/__init__.py +36 -0
- is_matrix_forge/assets/audio/files/__init__.py +53 -0
- is_matrix_forge/assets/audio/files/plugged.wav +0 -0
- is_matrix_forge/assets/audio/files/unplugged.wav +0 -0
- is_matrix_forge/assets/char_map.json +2150 -0
- is_matrix_forge/assets/font_map.py +119 -0
- is_matrix_forge/common/__init__.py +0 -0
- is_matrix_forge/common/decorators/__init__.py +152 -0
- is_matrix_forge/common/dirs.py +20 -0
- is_matrix_forge/common/helpers/__init__.py +132 -0
- is_matrix_forge/common/helpers/github_api.py +67 -0
- is_matrix_forge/designer_gui/__init__.py +8 -0
- is_matrix_forge/designer_gui/main_window/__init__.py +252 -0
- is_matrix_forge/designer_gui/main_window/layout.py +117 -0
- is_matrix_forge/dev_tools/__init__.py +8 -0
- is_matrix_forge/dev_tools/debug.py +82 -0
- is_matrix_forge/dev_tools/presets.py +47 -0
- is_matrix_forge/inputmodule/__init__.py +164 -0
- is_matrix_forge/inputmodule/b1display.py +160 -0
- is_matrix_forge/inputmodule/c1minimal.py +35 -0
- is_matrix_forge/inputmodule/font.py +2166 -0
- is_matrix_forge/inputmodule/helpers/__init__.py +16 -0
- is_matrix_forge/inputmodule/ledmatrix.py +516 -0
- is_matrix_forge/led_matrix/Scripts/__init__.py +4 -0
- is_matrix_forge/led_matrix/Scripts/battery_monitor.py +152 -0
- is_matrix_forge/led_matrix/Scripts/install_presets/__init__.py +2 -0
- is_matrix_forge/led_matrix/Scripts/install_presets/arguments.py +84 -0
- is_matrix_forge/led_matrix/Scripts/install_presets/main.py +192 -0
- is_matrix_forge/led_matrix/__init__.py +7 -0
- is_matrix_forge/led_matrix/commands/__init__.py +84 -0
- is_matrix_forge/led_matrix/commands/map.py +46 -0
- is_matrix_forge/led_matrix/constants.py +82 -0
- is_matrix_forge/led_matrix/controller/__init__.py +26 -0
- is_matrix_forge/led_matrix/controller/controller.py +635 -0
- is_matrix_forge/led_matrix/controller/helpers/__init__.py +7 -0
- is_matrix_forge/led_matrix/controller/helpers/threading.py +41 -0
- is_matrix_forge/led_matrix/controller/multiton.py +32 -0
- is_matrix_forge/led_matrix/controller/threaded.py +72 -0
- is_matrix_forge/led_matrix/display/__init__.py +13 -0
- is_matrix_forge/led_matrix/display/animations/__init__.py +29 -0
- is_matrix_forge/led_matrix/display/animations/animation.py +593 -0
- is_matrix_forge/led_matrix/display/animations/effects/__init__.py +17 -0
- is_matrix_forge/led_matrix/display/animations/errors.py +23 -0
- is_matrix_forge/led_matrix/display/animations/font/__init__.py +17 -0
- is_matrix_forge/led_matrix/display/animations/font/font.py +4 -0
- is_matrix_forge/led_matrix/display/animations/font/map_builder.py +123 -0
- is_matrix_forge/led_matrix/display/animations/frame/__init__.py +47 -0
- is_matrix_forge/led_matrix/display/animations/frame/base.py +245 -0
- is_matrix_forge/led_matrix/display/animations/frame/helpers.py +67 -0
- is_matrix_forge/led_matrix/display/animations/keep_awake.py +1 -0
- is_matrix_forge/led_matrix/display/animations/plugged.py +0 -0
- is_matrix_forge/led_matrix/display/animations/text_scroller.py +104 -0
- is_matrix_forge/led_matrix/display/effects/__init__.py +17 -0
- is_matrix_forge/led_matrix/display/effects/breather.py +206 -0
- is_matrix_forge/led_matrix/display/effects/fade.py +0 -0
- is_matrix_forge/led_matrix/display/effects/scroller/__init__.py +92 -0
- is_matrix_forge/led_matrix/display/effects/scroller/base.py +76 -0
- is_matrix_forge/led_matrix/display/effects/scroller/helpers.py +62 -0
- is_matrix_forge/led_matrix/display/grid/__init__.py +9 -0
- is_matrix_forge/led_matrix/display/grid/grid.py +208 -0
- is_matrix_forge/led_matrix/display/grid/helpers.py +130 -0
- is_matrix_forge/led_matrix/display/grid/presets/__init__.py +64 -0
- is_matrix_forge/led_matrix/display/grid/presets/manifest.py +172 -0
- is_matrix_forge/led_matrix/display/helpers/__init__.py +110 -0
- is_matrix_forge/led_matrix/display/helpers/columns.py +33 -0
- is_matrix_forge/led_matrix/display/media.py +199 -0
- is_matrix_forge/led_matrix/display/patterns/__init__.py +59 -0
- is_matrix_forge/led_matrix/display/patterns/built_in/__init__.py +91 -0
- is_matrix_forge/led_matrix/display/patterns/built_in/commands.py +3 -0
- is_matrix_forge/led_matrix/display/patterns/built_in/constants.py +29 -0
- is_matrix_forge/led_matrix/display/patterns/built_in/stencils.py +70 -0
- is_matrix_forge/led_matrix/display/patterns/utils.py +2 -0
- is_matrix_forge/led_matrix/display/text/__init__.py +66 -0
- is_matrix_forge/led_matrix/display/text/scroller.py +136 -0
- is_matrix_forge/led_matrix/errors/__init__.py +4 -0
- is_matrix_forge/led_matrix/errors/base.py +14 -0
- is_matrix_forge/led_matrix/errors/grid.py +37 -0
- is_matrix_forge/led_matrix/errors/matrix.py +68 -0
- is_matrix_forge/led_matrix/errors/misc.py +36 -0
- is_matrix_forge/led_matrix/fonts.py +20 -0
- is_matrix_forge/led_matrix/hardware.py +172 -0
- is_matrix_forge/led_matrix/helpers/__init__.py +165 -0
- is_matrix_forge/led_matrix/helpers/core/__init__.py +17 -0
- is_matrix_forge/led_matrix/helpers/core/lazy_mod_load.py +84 -0
- is_matrix_forge/led_matrix/helpers/device.py +118 -0
- is_matrix_forge/led_matrix/helpers/status_handler.py +14 -0
- is_matrix_forge/led_matrix/helpers/stream/__init__.py +70 -0
- is_matrix_forge/led_matrix/helpers/stream/errors.py +17 -0
- is_matrix_forge/led_matrix/led_matrix.py +25 -0
- is_matrix_forge/led_matrix/status.py +8 -0
- is_matrix_forge/led_matrix/tools/__init__.py +8 -0
- is_matrix_forge/led_matrix/tools/identify.py +27 -0
- is_matrix_forge/led_matrix/utils/__init__.py +41 -0
- is_matrix_forge/log_engine.py +25 -0
- is_matrix_forge/monitor/__init__.py +68 -0
- is_matrix_forge/monitor/errors.py +35 -0
- is_matrix_forge/monitor/events.py +78 -0
- is_matrix_forge/monitor/gui/__init__.py +0 -0
- is_matrix_forge/monitor/gui/errors.py +35 -0
- is_matrix_forge/monitor/gui/event/__init__.py +8 -0
- is_matrix_forge/monitor/gui/event/collection.py +153 -0
- is_matrix_forge/monitor/gui/event/errors.py +58 -0
- is_matrix_forge/monitor/gui/event/event.py +226 -0
- is_matrix_forge/monitor/gui/layout/__init__.py +8 -0
- is_matrix_forge/monitor/gui/layout/base.py +147 -0
- is_matrix_forge/monitor/gui/metaclasses/__init__.py +20 -0
- is_matrix_forge/monitor/gui/windows/__init__.py +0 -0
- is_matrix_forge/monitor/gui/windows/base.py +325 -0
- is_matrix_forge/monitor/gui/windows/main/__init__.py +77 -0
- is_matrix_forge/monitor/gui/windows/main/layout.py +21 -0
- is_matrix_forge/monitor/helpers.py +55 -0
- is_matrix_forge/monitor/monitor.py +431 -0
- is_matrix_forge/notify/__init__.py +0 -0
- is_matrix_forge/notify/sounds/__init__.py +6 -0
- is_matrix_forge/notify/sounds/base.py +106 -0
- is_matrix_forge/progress.py +99 -0
- is_matrix_forge-1.0.0.dev1.dist-info/LICENSE.md +19 -0
- is_matrix_forge-1.0.0.dev1.dist-info/METADATA +190 -0
- is_matrix_forge-1.0.0.dev1.dist-info/RECORD +123 -0
- is_matrix_forge-1.0.0.dev1.dist-info/WHEEL +4 -0
- is_matrix_forge-1.0.0.dev1.dist-info/entry_points.txt +5 -0
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from ..layout.base import Layout
|
|
3
|
+
from ..errors import MalformedWindowError
|
|
4
|
+
from ..event import EventCollection
|
|
5
|
+
from ..metaclasses import SingletonABCMeta
|
|
6
|
+
import PySimpleGUI as psg
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class WindowBase(metaclass=SingletonABCMeta):
|
|
10
|
+
"""
|
|
11
|
+
Base class for all windows in the monitor control panel GUI.
|
|
12
|
+
"""
|
|
13
|
+
DEFAULT_TITLE = 'LED Matrix Battery Monitor'
|
|
14
|
+
|
|
15
|
+
@property
|
|
16
|
+
def EVENT_COLLECTION(self):
|
|
17
|
+
if self._event_collection is None:
|
|
18
|
+
self._event_collection = EventCollection(self)
|
|
19
|
+
return self._event_collection
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
@abstractmethod
|
|
23
|
+
def LAYOUT(self) -> Layout:
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
def __init__(
|
|
27
|
+
self,
|
|
28
|
+
title: str,
|
|
29
|
+
skip_auto_build: bool = False,
|
|
30
|
+
skip_auto_run: bool = False
|
|
31
|
+
) -> None:
|
|
32
|
+
self._auto_build = None
|
|
33
|
+
self._auto_run = None
|
|
34
|
+
self._event_collection = None
|
|
35
|
+
self.__initialized = False
|
|
36
|
+
self._layout = None
|
|
37
|
+
self._title = None
|
|
38
|
+
self.__running = False
|
|
39
|
+
self.__window = None
|
|
40
|
+
|
|
41
|
+
# Set the values for instance variables/properties
|
|
42
|
+
self.title = title
|
|
43
|
+
self.auto_build = not skip_auto_build
|
|
44
|
+
self.auto_run = not skip_auto_run
|
|
45
|
+
|
|
46
|
+
self.__post_init__()
|
|
47
|
+
|
|
48
|
+
def __post_init__(self):
|
|
49
|
+
if self.auto_build:
|
|
50
|
+
self.build()
|
|
51
|
+
if self.auto_run:
|
|
52
|
+
self.start()
|
|
53
|
+
|
|
54
|
+
def _set_window_title(self, new_title: str, do_not_change_capitalization: bool = False) -> None:
|
|
55
|
+
if not isinstance(new_title, str):
|
|
56
|
+
raise TypeError('Window title must be a string.')
|
|
57
|
+
|
|
58
|
+
if self.window is None:
|
|
59
|
+
raise RuntimeError('Window not built yet.')
|
|
60
|
+
|
|
61
|
+
self._title = new_title if do_not_change_capitalization else new_title.title()
|
|
62
|
+
self.window.set_title(self._title)
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def auto_build(self):
|
|
66
|
+
return bool(self._auto_build)
|
|
67
|
+
|
|
68
|
+
@auto_build.setter
|
|
69
|
+
def auto_build(self, new: bool) -> None:
|
|
70
|
+
if self.initialized:
|
|
71
|
+
raise RuntimeError('Cannot change auto_build after initialization.')
|
|
72
|
+
|
|
73
|
+
if not isinstance(new, bool):
|
|
74
|
+
raise TypeError(f'new must be of type `bool`, not {type(new)}')
|
|
75
|
+
|
|
76
|
+
self._auto_build = new
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def auto_run(self):
|
|
80
|
+
return bool(self._auto_run)
|
|
81
|
+
|
|
82
|
+
@auto_run.setter
|
|
83
|
+
def auto_run(self, new: bool) -> None:
|
|
84
|
+
if self.initialized:
|
|
85
|
+
raise RuntimeError('Cannot change auto_run after initialization.')
|
|
86
|
+
|
|
87
|
+
if not isinstance(new, bool):
|
|
88
|
+
raise TypeError(f'new must be of type `bool`, not {type(new)}')
|
|
89
|
+
|
|
90
|
+
self._auto_run = new
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
def built(self):
|
|
94
|
+
try:
|
|
95
|
+
return bool(self.layout) and bool(self.window)
|
|
96
|
+
except RuntimeError:
|
|
97
|
+
return False
|
|
98
|
+
|
|
99
|
+
@property
|
|
100
|
+
def initialized(self):
|
|
101
|
+
"""
|
|
102
|
+
Whether the window has been initialized.
|
|
103
|
+
|
|
104
|
+
The window is considered initialized if `WindowBase.__init__` has
|
|
105
|
+
been called and finished successfully.
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
bool:
|
|
109
|
+
Whether the window has been initialized.
|
|
110
|
+
"""
|
|
111
|
+
return bool(self.__initialized)
|
|
112
|
+
|
|
113
|
+
@property
|
|
114
|
+
def layout(self):
|
|
115
|
+
"""
|
|
116
|
+
The `PySimpleGUI` layout for this window.
|
|
117
|
+
|
|
118
|
+
The layout is constructed from the `WindowBase.layout_class` using the
|
|
119
|
+
`WindowBase.layout_kwargs` constructor arguments.
|
|
120
|
+
|
|
121
|
+
The layout is built automatically if `WindowBase.auto_build` is
|
|
122
|
+
`True` (the default).
|
|
123
|
+
|
|
124
|
+
Raises:
|
|
125
|
+
RuntimeError:
|
|
126
|
+
If the layout has not been built yet.
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
List[List[sg.Element]]:
|
|
130
|
+
A 2D list of `PySimpleGUI` elements representing the layout.
|
|
131
|
+
"""
|
|
132
|
+
if self._layout is None:
|
|
133
|
+
raise RuntimeError('Layout not built yet.')
|
|
134
|
+
|
|
135
|
+
return self._layout
|
|
136
|
+
|
|
137
|
+
@property
|
|
138
|
+
def running(self):
|
|
139
|
+
"""
|
|
140
|
+
Whether the window is currently running.
|
|
141
|
+
|
|
142
|
+
A window is considered running if it has been started
|
|
143
|
+
(i.e. `WindowBase.start` has been called) and has not been
|
|
144
|
+
stopped (i.e. `WindowBase.stop` has been called). If this
|
|
145
|
+
returns `True`, the window main loop is most-likely running.
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
bool:
|
|
149
|
+
Whether the window is currently running.
|
|
150
|
+
"""
|
|
151
|
+
return bool(self.__running)
|
|
152
|
+
|
|
153
|
+
@running.setter
|
|
154
|
+
def running(self, new: bool) -> None:
|
|
155
|
+
"""
|
|
156
|
+
Set the running status of the window.
|
|
157
|
+
|
|
158
|
+
This method sets the `running` status of the window. It raises an error if
|
|
159
|
+
attempting to start the window by setting `running` to True directly, instead
|
|
160
|
+
of using the `start` method. It also ensures that the value assigned is a boolean.
|
|
161
|
+
|
|
162
|
+
Parameters:
|
|
163
|
+
new (bool):
|
|
164
|
+
The new running status.
|
|
165
|
+
|
|
166
|
+
Raises:
|
|
167
|
+
RuntimeError:
|
|
168
|
+
If attempting to start the window by setting `running` to True.
|
|
169
|
+
|
|
170
|
+
TypeError:
|
|
171
|
+
If the new value is not a boolean.
|
|
172
|
+
"""
|
|
173
|
+
if not self.__running and new:
|
|
174
|
+
raise RuntimeError('Cannot start window by setting running to True. Use "start" method instead.')
|
|
175
|
+
|
|
176
|
+
if not isinstance(new, bool):
|
|
177
|
+
raise TypeError(f'new must be of type `bool`, not {type(new)}')
|
|
178
|
+
|
|
179
|
+
self.__running = new
|
|
180
|
+
if not self.__running and new:
|
|
181
|
+
raise RuntimeError('Cannot start window by setting running to True. Use "start" method instead.')
|
|
182
|
+
|
|
183
|
+
self.__running = new
|
|
184
|
+
|
|
185
|
+
@property
|
|
186
|
+
def title(self):
|
|
187
|
+
"""
|
|
188
|
+
The title of the window.
|
|
189
|
+
|
|
190
|
+
This property returns the current title of the window. The title is used
|
|
191
|
+
to identify the window and can be displayed on the window's title bar.
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
str:
|
|
195
|
+
The current title of the window.
|
|
196
|
+
"""
|
|
197
|
+
return self._title
|
|
198
|
+
|
|
199
|
+
@title.setter
|
|
200
|
+
def title(self, new):
|
|
201
|
+
"""
|
|
202
|
+
Set the title of the window.
|
|
203
|
+
|
|
204
|
+
This method sets the title of the window, which is displayed on the window's title bar.
|
|
205
|
+
The title is also used to identify the window in the taskbar or other window management
|
|
206
|
+
interfaces.
|
|
207
|
+
|
|
208
|
+
If the window is currently running, the title is updated immediately.
|
|
209
|
+
|
|
210
|
+
Parameters:
|
|
211
|
+
new (str):
|
|
212
|
+
The new title of the window.
|
|
213
|
+
|
|
214
|
+
Raises:
|
|
215
|
+
TypeError:
|
|
216
|
+
If the new title is not a string.
|
|
217
|
+
"""
|
|
218
|
+
if not isinstance(new, str):
|
|
219
|
+
raise TypeError(f'Window title must be a string, not {type(new)}')
|
|
220
|
+
if new != self.title:
|
|
221
|
+
self._title = new
|
|
222
|
+
|
|
223
|
+
if self.running:
|
|
224
|
+
self._set_window_title(new)
|
|
225
|
+
|
|
226
|
+
@property
|
|
227
|
+
def window(self):
|
|
228
|
+
"""
|
|
229
|
+
The window object.
|
|
230
|
+
|
|
231
|
+
This property returns the underlying window object, which is an instance of
|
|
232
|
+
`PySimpleGUI.Window`. This object can be used to manipulate the window,
|
|
233
|
+
e.g. to add or remove widgets, set the window's title, or to close the
|
|
234
|
+
window.
|
|
235
|
+
|
|
236
|
+
Returns:
|
|
237
|
+
PySimpleGUI.Window:
|
|
238
|
+
The underlying window object.
|
|
239
|
+
"""
|
|
240
|
+
return self.__window
|
|
241
|
+
|
|
242
|
+
@window.deleter
|
|
243
|
+
def window(self):
|
|
244
|
+
"""
|
|
245
|
+
Deletes the window object and stops the window if it is running.
|
|
246
|
+
|
|
247
|
+
This deleter will also stop the window if it is running, and rebuild the
|
|
248
|
+
layout if it exists.
|
|
249
|
+
|
|
250
|
+
This is a convenience method to ensure that the window is properly shut
|
|
251
|
+
down when it is no longer needed. It is called automatically when the
|
|
252
|
+
window is garbage collected.
|
|
253
|
+
|
|
254
|
+
Note that this method does not raise an error if the window is not
|
|
255
|
+
running or if the layout does not exist. This is a no-op in those cases.
|
|
256
|
+
"""
|
|
257
|
+
if self.running:
|
|
258
|
+
self.stop()
|
|
259
|
+
|
|
260
|
+
if self.layout:
|
|
261
|
+
self.layout.rebuild()
|
|
262
|
+
|
|
263
|
+
def build(self):
|
|
264
|
+
if self.built:
|
|
265
|
+
raise RuntimeError('Window already built.')
|
|
266
|
+
self.LAYOUT.build()
|
|
267
|
+
self._layout = self.LAYOUT.layout
|
|
268
|
+
self.__window = psg.Window(self.title, layout=self.layout, finalize=True)
|
|
269
|
+
if getattr(self, '__post_build__', None):
|
|
270
|
+
print('post_build')
|
|
271
|
+
def build_event_handlers(self):
|
|
272
|
+
if not self.EVENT_COLLECTION.events:
|
|
273
|
+
raise RuntimeError('No event handlers found to build!')
|
|
274
|
+
|
|
275
|
+
def close(self):
|
|
276
|
+
if self.window and self.running:
|
|
277
|
+
self.window.close()
|
|
278
|
+
elif not self.window:
|
|
279
|
+
raise RuntimeError('Window not built yet, nothing to stop.')
|
|
280
|
+
else:
|
|
281
|
+
raise RuntimeError('Window not running, nothing to stop.')
|
|
282
|
+
|
|
283
|
+
self.window.close()
|
|
284
|
+
|
|
285
|
+
def handle_event(self, event, values):
|
|
286
|
+
for handler in self.EVENT_COLLECTION:
|
|
287
|
+
if handler.key == event:
|
|
288
|
+
|
|
289
|
+
handler.callback
|
|
290
|
+
|
|
291
|
+
def run(self):
|
|
292
|
+
if not self.built:
|
|
293
|
+
raise RuntimeError('Window not built yet.')
|
|
294
|
+
|
|
295
|
+
if not self.running:
|
|
296
|
+
raise RuntimeError('Window is not running. Start it first.')
|
|
297
|
+
|
|
298
|
+
while self.running:
|
|
299
|
+
event, values = self.window.read(timeout=100)
|
|
300
|
+
|
|
301
|
+
if event is None:
|
|
302
|
+
break
|
|
303
|
+
|
|
304
|
+
self.handle_event(event, values)
|
|
305
|
+
|
|
306
|
+
self.stop()
|
|
307
|
+
|
|
308
|
+
def start(self):
|
|
309
|
+
if self.running:
|
|
310
|
+
raise RuntimeError('One cannot start a window that is already running.')
|
|
311
|
+
|
|
312
|
+
if not self.window:
|
|
313
|
+
raise RuntimeError('Window not built yet.')
|
|
314
|
+
|
|
315
|
+
self.__running = True
|
|
316
|
+
self.run()
|
|
317
|
+
self.stop()
|
|
318
|
+
|
|
319
|
+
def stop(self):
|
|
320
|
+
if not self.running:
|
|
321
|
+
raise RuntimeError('One cannot stop a window that is not running.')
|
|
322
|
+
|
|
323
|
+
self.close()
|
|
324
|
+
if self.running:
|
|
325
|
+
self.running = False
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
from .layout import Layout as MainWindowLayout
|
|
2
|
+
from ..base import WindowBase
|
|
3
|
+
from is_matrix_forge.led_matrix.hardware import brightness
|
|
4
|
+
from is_matrix_forge.led_matrix.helpers.device import DEVICES
|
|
5
|
+
from is_matrix_forge.monitor.monitor import PowerMonitor
|
|
6
|
+
import time
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
POWER_MONITOR = PowerMonitor(DEVICES[0])
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class MainWindow(WindowBase):
|
|
13
|
+
"""
|
|
14
|
+
Main window for the LED Matrix Battery Monitor GUI.
|
|
15
|
+
"""
|
|
16
|
+
DEFAULT_TITLE = 'LED Matrix Battery Monitor'
|
|
17
|
+
LAYOUT = MainWindowLayout()
|
|
18
|
+
|
|
19
|
+
def __init__(
|
|
20
|
+
self,
|
|
21
|
+
*args,
|
|
22
|
+
**kwargs
|
|
23
|
+
):
|
|
24
|
+
if not args and 'title' not in kwargs:
|
|
25
|
+
args = (MainWindow.DEFAULT_TITLE, *args)
|
|
26
|
+
super().__init__(*args, **kwargs)
|
|
27
|
+
|
|
28
|
+
def __post_build__(self):
|
|
29
|
+
slider_elem = self.window.find_element('BRIGHTNESS_SLIDER')
|
|
30
|
+
slider_elem.bind(
|
|
31
|
+
'<ButtonRelease-1>',
|
|
32
|
+
lambda event, s=slider_elem: self.window.write_event_value('BRIGHTNESS_SLIDER_DONE', s.get())
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
def build_event_handlers(self):
|
|
36
|
+
self.EVENT_COLLECTION.create_event(None, self.stop)
|
|
37
|
+
|
|
38
|
+
def handle_event(self, event, *args, **kwargs):
|
|
39
|
+
values = args[0] if args else {}
|
|
40
|
+
|
|
41
|
+
if event in (None, 'EXIT_BTTN'):
|
|
42
|
+
self.stop()
|
|
43
|
+
return
|
|
44
|
+
|
|
45
|
+
if event == 'BRIGHTNESS_SLIDER':
|
|
46
|
+
brightness(DEVICES[0], int(values.get('BRIGHTNESS_SLIDER', 0)))
|
|
47
|
+
return
|
|
48
|
+
|
|
49
|
+
if event == 'BRIGHTNESS_SLIDER_DONE':
|
|
50
|
+
brightness(DEVICES[0], int(values.get('BRIGHTNESS_SLIDER_DONE', 0)))
|
|
51
|
+
|
|
52
|
+
def run(self):
|
|
53
|
+
if not self.built:
|
|
54
|
+
raise RuntimeError('Window is not built. Call build() first.')
|
|
55
|
+
|
|
56
|
+
if not self.running:
|
|
57
|
+
raise RuntimeError('Window is not running. Call start() first.')
|
|
58
|
+
|
|
59
|
+
DEBOUNCE_DELAY = 0.3
|
|
60
|
+
last_change = 0
|
|
61
|
+
pending_value = None
|
|
62
|
+
|
|
63
|
+
while self.running:
|
|
64
|
+
event, values = self.window.read(timeout=100)
|
|
65
|
+
|
|
66
|
+
if event == 'BRIGHTNESS_SLIDER':
|
|
67
|
+
pending_value = int(values['BRIGHTNESS_SLIDER'])
|
|
68
|
+
last_change = time.time()
|
|
69
|
+
|
|
70
|
+
if pending_value is not None and time.time() - last_change > DEBOUNCE_DELAY:
|
|
71
|
+
brightness(DEVICES[0], pending_value)
|
|
72
|
+
pending_value = None
|
|
73
|
+
|
|
74
|
+
self.handle_event(event, values)
|
|
75
|
+
|
|
76
|
+
if event is None:
|
|
77
|
+
break
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import PySimpleGUI as psg
|
|
2
|
+
from is_matrix_forge.monitor.gui.layout.base import Layout as BaseLayout
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
from is_matrix_forge.log_engine import ROOT_LOGGER as PARENT_LOGGER
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
MOD_LOGGER = PARENT_LOGGER.get_child('monitor.gui.windows.main.layout')
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Layout(BaseLayout):
|
|
12
|
+
@property
|
|
13
|
+
def BLUEPRINT(self):
|
|
14
|
+
return [
|
|
15
|
+
|
|
16
|
+
[psg.Text('Brightness'), psg.Slider((0, 255), key='BRIGHTNESS_SLIDER', enable_events=True)],
|
|
17
|
+
[psg.Button('Animate'), psg.Checkbox('Animated', key='ANIMATED_CHKBOX'),
|
|
18
|
+
psg.Button('Exit', key='EXIT_BTTN')],
|
|
19
|
+
|
|
20
|
+
].copy()
|
|
21
|
+
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import psutil
|
|
2
|
+
from psutil import sensors_battery as get_battery_info
|
|
3
|
+
from .errors import BatteryStateUnknownError
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def get_plugged_status(battery_info: Optional[psutil._common.sbattery] = None) -> bool:
|
|
8
|
+
"""
|
|
9
|
+
Retrieves the plugged-in status of the system.
|
|
10
|
+
|
|
11
|
+
Args:
|
|
12
|
+
battery_info (Optional[psutil._common.sbattery]):
|
|
13
|
+
The battery information to get the plugged-in status from. If not provided, it will be retrieved
|
|
14
|
+
with :func:`psutil.sensors_battery`.
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
bool:
|
|
18
|
+
Whether the system is currently plugged into power.
|
|
19
|
+
|
|
20
|
+
Raises:
|
|
21
|
+
TypeError:
|
|
22
|
+
If the provided `battery_info` is not a `psutil._common.sbattery` instance.
|
|
23
|
+
|
|
24
|
+
BatteryStateUnknownError:
|
|
25
|
+
If an error occurs while retrieving the plugged-in status.
|
|
26
|
+
|
|
27
|
+
Examples:
|
|
28
|
+
>>> get_plugged_status() # returns True if the system is plugged in, False otherwise
|
|
29
|
+
|
|
30
|
+
See Also:
|
|
31
|
+
- :func:`psutil.sensors_battery`
|
|
32
|
+
- :exc:`BatteryStateUnknownError`
|
|
33
|
+
"""
|
|
34
|
+
if battery_info is not None:
|
|
35
|
+
if not isinstance(battery_info, psutil._common.sbattery):
|
|
36
|
+
raise TypeError('Expected a `psutil._common.sbattery` instance')
|
|
37
|
+
else:
|
|
38
|
+
from psutil import sensors_battery
|
|
39
|
+
battery_info = sensors_battery()
|
|
40
|
+
if not isinstance(battery_info, psutil._common.sbattery):
|
|
41
|
+
raise TypeError('Expected a `psutil._common.sbattery` instance')
|
|
42
|
+
|
|
43
|
+
try:
|
|
44
|
+
battery_info = battery_info or get_battery_info()
|
|
45
|
+
except Exception as e:
|
|
46
|
+
raise BatteryStateUnknownError('Could not determine whether the system is plugged in or not due to an ' \
|
|
47
|
+
f'error: {e}') from e
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def check_plugged_in():
|
|
51
|
+
return get_battery_info().power_plugged
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def get_battery_percentage():
|
|
55
|
+
return get_battery_info().percent
|