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.
Files changed (123) hide show
  1. is_matrix_forge/__init__.py +8 -0
  2. is_matrix_forge/assets/__init__.py +8 -0
  3. is_matrix_forge/assets/audio/__init__.py +36 -0
  4. is_matrix_forge/assets/audio/files/__init__.py +53 -0
  5. is_matrix_forge/assets/audio/files/plugged.wav +0 -0
  6. is_matrix_forge/assets/audio/files/unplugged.wav +0 -0
  7. is_matrix_forge/assets/char_map.json +2150 -0
  8. is_matrix_forge/assets/font_map.py +119 -0
  9. is_matrix_forge/common/__init__.py +0 -0
  10. is_matrix_forge/common/decorators/__init__.py +152 -0
  11. is_matrix_forge/common/dirs.py +20 -0
  12. is_matrix_forge/common/helpers/__init__.py +132 -0
  13. is_matrix_forge/common/helpers/github_api.py +67 -0
  14. is_matrix_forge/designer_gui/__init__.py +8 -0
  15. is_matrix_forge/designer_gui/main_window/__init__.py +252 -0
  16. is_matrix_forge/designer_gui/main_window/layout.py +117 -0
  17. is_matrix_forge/dev_tools/__init__.py +8 -0
  18. is_matrix_forge/dev_tools/debug.py +82 -0
  19. is_matrix_forge/dev_tools/presets.py +47 -0
  20. is_matrix_forge/inputmodule/__init__.py +164 -0
  21. is_matrix_forge/inputmodule/b1display.py +160 -0
  22. is_matrix_forge/inputmodule/c1minimal.py +35 -0
  23. is_matrix_forge/inputmodule/font.py +2166 -0
  24. is_matrix_forge/inputmodule/helpers/__init__.py +16 -0
  25. is_matrix_forge/inputmodule/ledmatrix.py +516 -0
  26. is_matrix_forge/led_matrix/Scripts/__init__.py +4 -0
  27. is_matrix_forge/led_matrix/Scripts/battery_monitor.py +152 -0
  28. is_matrix_forge/led_matrix/Scripts/install_presets/__init__.py +2 -0
  29. is_matrix_forge/led_matrix/Scripts/install_presets/arguments.py +84 -0
  30. is_matrix_forge/led_matrix/Scripts/install_presets/main.py +192 -0
  31. is_matrix_forge/led_matrix/__init__.py +7 -0
  32. is_matrix_forge/led_matrix/commands/__init__.py +84 -0
  33. is_matrix_forge/led_matrix/commands/map.py +46 -0
  34. is_matrix_forge/led_matrix/constants.py +82 -0
  35. is_matrix_forge/led_matrix/controller/__init__.py +26 -0
  36. is_matrix_forge/led_matrix/controller/controller.py +635 -0
  37. is_matrix_forge/led_matrix/controller/helpers/__init__.py +7 -0
  38. is_matrix_forge/led_matrix/controller/helpers/threading.py +41 -0
  39. is_matrix_forge/led_matrix/controller/multiton.py +32 -0
  40. is_matrix_forge/led_matrix/controller/threaded.py +72 -0
  41. is_matrix_forge/led_matrix/display/__init__.py +13 -0
  42. is_matrix_forge/led_matrix/display/animations/__init__.py +29 -0
  43. is_matrix_forge/led_matrix/display/animations/animation.py +593 -0
  44. is_matrix_forge/led_matrix/display/animations/effects/__init__.py +17 -0
  45. is_matrix_forge/led_matrix/display/animations/errors.py +23 -0
  46. is_matrix_forge/led_matrix/display/animations/font/__init__.py +17 -0
  47. is_matrix_forge/led_matrix/display/animations/font/font.py +4 -0
  48. is_matrix_forge/led_matrix/display/animations/font/map_builder.py +123 -0
  49. is_matrix_forge/led_matrix/display/animations/frame/__init__.py +47 -0
  50. is_matrix_forge/led_matrix/display/animations/frame/base.py +245 -0
  51. is_matrix_forge/led_matrix/display/animations/frame/helpers.py +67 -0
  52. is_matrix_forge/led_matrix/display/animations/keep_awake.py +1 -0
  53. is_matrix_forge/led_matrix/display/animations/plugged.py +0 -0
  54. is_matrix_forge/led_matrix/display/animations/text_scroller.py +104 -0
  55. is_matrix_forge/led_matrix/display/effects/__init__.py +17 -0
  56. is_matrix_forge/led_matrix/display/effects/breather.py +206 -0
  57. is_matrix_forge/led_matrix/display/effects/fade.py +0 -0
  58. is_matrix_forge/led_matrix/display/effects/scroller/__init__.py +92 -0
  59. is_matrix_forge/led_matrix/display/effects/scroller/base.py +76 -0
  60. is_matrix_forge/led_matrix/display/effects/scroller/helpers.py +62 -0
  61. is_matrix_forge/led_matrix/display/grid/__init__.py +9 -0
  62. is_matrix_forge/led_matrix/display/grid/grid.py +208 -0
  63. is_matrix_forge/led_matrix/display/grid/helpers.py +130 -0
  64. is_matrix_forge/led_matrix/display/grid/presets/__init__.py +64 -0
  65. is_matrix_forge/led_matrix/display/grid/presets/manifest.py +172 -0
  66. is_matrix_forge/led_matrix/display/helpers/__init__.py +110 -0
  67. is_matrix_forge/led_matrix/display/helpers/columns.py +33 -0
  68. is_matrix_forge/led_matrix/display/media.py +199 -0
  69. is_matrix_forge/led_matrix/display/patterns/__init__.py +59 -0
  70. is_matrix_forge/led_matrix/display/patterns/built_in/__init__.py +91 -0
  71. is_matrix_forge/led_matrix/display/patterns/built_in/commands.py +3 -0
  72. is_matrix_forge/led_matrix/display/patterns/built_in/constants.py +29 -0
  73. is_matrix_forge/led_matrix/display/patterns/built_in/stencils.py +70 -0
  74. is_matrix_forge/led_matrix/display/patterns/utils.py +2 -0
  75. is_matrix_forge/led_matrix/display/text/__init__.py +66 -0
  76. is_matrix_forge/led_matrix/display/text/scroller.py +136 -0
  77. is_matrix_forge/led_matrix/errors/__init__.py +4 -0
  78. is_matrix_forge/led_matrix/errors/base.py +14 -0
  79. is_matrix_forge/led_matrix/errors/grid.py +37 -0
  80. is_matrix_forge/led_matrix/errors/matrix.py +68 -0
  81. is_matrix_forge/led_matrix/errors/misc.py +36 -0
  82. is_matrix_forge/led_matrix/fonts.py +20 -0
  83. is_matrix_forge/led_matrix/hardware.py +172 -0
  84. is_matrix_forge/led_matrix/helpers/__init__.py +165 -0
  85. is_matrix_forge/led_matrix/helpers/core/__init__.py +17 -0
  86. is_matrix_forge/led_matrix/helpers/core/lazy_mod_load.py +84 -0
  87. is_matrix_forge/led_matrix/helpers/device.py +118 -0
  88. is_matrix_forge/led_matrix/helpers/status_handler.py +14 -0
  89. is_matrix_forge/led_matrix/helpers/stream/__init__.py +70 -0
  90. is_matrix_forge/led_matrix/helpers/stream/errors.py +17 -0
  91. is_matrix_forge/led_matrix/led_matrix.py +25 -0
  92. is_matrix_forge/led_matrix/status.py +8 -0
  93. is_matrix_forge/led_matrix/tools/__init__.py +8 -0
  94. is_matrix_forge/led_matrix/tools/identify.py +27 -0
  95. is_matrix_forge/led_matrix/utils/__init__.py +41 -0
  96. is_matrix_forge/log_engine.py +25 -0
  97. is_matrix_forge/monitor/__init__.py +68 -0
  98. is_matrix_forge/monitor/errors.py +35 -0
  99. is_matrix_forge/monitor/events.py +78 -0
  100. is_matrix_forge/monitor/gui/__init__.py +0 -0
  101. is_matrix_forge/monitor/gui/errors.py +35 -0
  102. is_matrix_forge/monitor/gui/event/__init__.py +8 -0
  103. is_matrix_forge/monitor/gui/event/collection.py +153 -0
  104. is_matrix_forge/monitor/gui/event/errors.py +58 -0
  105. is_matrix_forge/monitor/gui/event/event.py +226 -0
  106. is_matrix_forge/monitor/gui/layout/__init__.py +8 -0
  107. is_matrix_forge/monitor/gui/layout/base.py +147 -0
  108. is_matrix_forge/monitor/gui/metaclasses/__init__.py +20 -0
  109. is_matrix_forge/monitor/gui/windows/__init__.py +0 -0
  110. is_matrix_forge/monitor/gui/windows/base.py +325 -0
  111. is_matrix_forge/monitor/gui/windows/main/__init__.py +77 -0
  112. is_matrix_forge/monitor/gui/windows/main/layout.py +21 -0
  113. is_matrix_forge/monitor/helpers.py +55 -0
  114. is_matrix_forge/monitor/monitor.py +431 -0
  115. is_matrix_forge/notify/__init__.py +0 -0
  116. is_matrix_forge/notify/sounds/__init__.py +6 -0
  117. is_matrix_forge/notify/sounds/base.py +106 -0
  118. is_matrix_forge/progress.py +99 -0
  119. is_matrix_forge-1.0.0.dev1.dist-info/LICENSE.md +19 -0
  120. is_matrix_forge-1.0.0.dev1.dist-info/METADATA +190 -0
  121. is_matrix_forge-1.0.0.dev1.dist-info/RECORD +123 -0
  122. is_matrix_forge-1.0.0.dev1.dist-info/WHEEL +4 -0
  123. is_matrix_forge-1.0.0.dev1.dist-info/entry_points.txt +5 -0
@@ -0,0 +1,252 @@
1
+ import json
2
+ from typing import List, Tuple, Any, Dict
3
+ import PySimpleGUI as sg
4
+ import copy
5
+ import os
6
+
7
+ from ...led_matrix.helpers.device import DEVICES
8
+ from is_matrix_forge.led_matrix.display.animations.frame.base import Frame
9
+ from is_matrix_forge.led_matrix.display.grid.helpers import is_valid_grid
10
+ from is_matrix_forge.led_matrix.display.animations.frame.helpers import is_valid_frames
11
+ from .layout import PixelGridLayout
12
+ from is_matrix_forge.led_matrix import render_matrix
13
+
14
+
15
+ def _normalize(key: Any) -> str:
16
+ s = str(key)
17
+ for ch in " <>,'()":
18
+ s = s.replace(ch, '_')
19
+ return s.strip('_')
20
+
21
+
22
+ class PixelGrid:
23
+ """
24
+ LED matrix pixel grid UI with frame-by-frame animation.
25
+ """
26
+ def __init__(self, width: int = 9, height: int = 34):
27
+ self.__width = None
28
+ self.__height = None
29
+ self.__frames = None
30
+ self.__current_frame = None
31
+ self.__grid = None
32
+ self.__preferred_device = None
33
+
34
+ self.width = width
35
+ self.height = height
36
+
37
+ base = [[0] * self.height for _ in range(self.width)]
38
+ Frame.__width = self.width
39
+ Frame.__height = self.height
40
+ self.frames = [Frame(copy.deepcopy(base))]
41
+ self.current_frame = 0
42
+ self.grid = self.frames[0].grid
43
+ self.preferred_device = None
44
+
45
+ layout = PixelGridLayout(self.width, self.height).build()
46
+ self.window = sg.Window('Pixel Grid', layout, finalize=True)
47
+ self._init_button_colors()
48
+ self._update_frame_indicator()
49
+
50
+ @property
51
+ def width(self) -> int:
52
+ return self.__width
53
+
54
+ @width.setter
55
+ def width(self, value: Any) -> None:
56
+ if not isinstance(value, int) or value <= 0:
57
+ raise TypeError("Width must be a positive integer")
58
+ self.__width = value
59
+
60
+ @property
61
+ def height(self) -> int:
62
+ return self.__height
63
+
64
+ @height.setter
65
+ def height(self, value: Any) -> None:
66
+ if not isinstance(value, int) or value <= 0:
67
+ raise TypeError("Height must be a positive integer")
68
+ self.__height = value
69
+
70
+ @property
71
+ def frames(self) -> List[Frame]:
72
+ return self.__frames
73
+
74
+ @frames.setter
75
+ def frames(self, value: Any) -> None:
76
+ if not isinstance(value, list) or len(value) == 0 or not all(isinstance(f, Frame) for f in value):
77
+ raise TypeError("frames must be a non-empty list of Frame objects")
78
+ self.__frames = value
79
+
80
+ @property
81
+ def current_frame(self) -> int:
82
+ return self.__current_frame
83
+
84
+ @current_frame.setter
85
+ def current_frame(self, value: Any) -> None:
86
+ if not isinstance(value, int) or not (0 <= value < len(self.frames)):
87
+ raise ValueError("current_frame must be an index into frames list")
88
+ self.__current_frame = value
89
+
90
+ @property
91
+ def grid(self) -> List[List[int]]:
92
+ return self.__grid
93
+
94
+ @grid.setter
95
+ def grid(self, value: Any) -> None:
96
+ if not is_valid_grid(value, self.width, self.height):
97
+ raise ValueError("Grid must match width/height and be 0/1 values")
98
+ self.__grid = value
99
+ # update frame's grid as well
100
+ if hasattr(self, 'frames') and self.frames:
101
+ self.frames[self.current_frame].grid = value
102
+
103
+ @property
104
+ def preferred_device(self):
105
+ return self.__preferred_device
106
+
107
+ @preferred_device.setter
108
+ def preferred_device(self, value: Any) -> None:
109
+ # no strict type check, but could enforce presence in DEVICES
110
+ self.__preferred_device = value
111
+
112
+ def _init_button_colors(self) -> None:
113
+ for col in range(self.width):
114
+ for row in range(self.height):
115
+ state = self.grid[col][row]
116
+ color = 'green' if state else 'lightgrey'
117
+ self.window[(col, row)].update(button_color=('black', color))
118
+
119
+ def _update_frame_indicator(self) -> None:
120
+ total = len(self.frames)
121
+ idx = self.current_frame + 1
122
+ dur = self.frames[self.current_frame].duration
123
+ self.window['FRAME_INDICATOR'].update(
124
+ f'Frame {idx}/{total} (Duration: {dur}s)'
125
+ )
126
+
127
+ def run(self) -> None:
128
+ while True:
129
+ event, _ = self.window.read()
130
+ if event in (sg.WINDOW_CLOSED, 'Exit'):
131
+ break
132
+ handler = getattr(self, f'_handle_{_normalize(event)}', None)
133
+ if callable(handler):
134
+ handler(event)
135
+ elif isinstance(event, tuple):
136
+ self._toggle_pixel(event)
137
+ self.window.close()
138
+
139
+ def _toggle_pixel(self, key: Tuple[int, int]) -> None:
140
+ col, row = key
141
+ new = 1 - self.grid[col][row]
142
+ self.grid = [[new if (c==col and r==row) else self.grid[c][r]
143
+ for r in range(self.height)]
144
+ for c in range(self.width)]
145
+ self.window[key].update(
146
+ button_color=('black', 'green' if new else 'lightgrey')
147
+ )
148
+
149
+ # Frame management
150
+ def add_frame(self) -> None:
151
+ new_grid = copy.deepcopy(self.grid)
152
+ frm = Frame(new_grid)
153
+ self.frames = self.frames + [frm]
154
+ self.current_frame = len(self.frames) - 1
155
+ self.grid = frm.grid
156
+ self._init_button_colors()
157
+ self._update_frame_indicator()
158
+
159
+ def prev_frame(self) -> None:
160
+ if self.current_frame > 0:
161
+ self.current_frame -= 1
162
+ self._load_frame()
163
+
164
+ def next_frame(self) -> None:
165
+ if self.current_frame < len(self.frames) - 1:
166
+ self.current_frame += 1
167
+ self._load_frame()
168
+
169
+ def _load_frame(self) -> None:
170
+ frm = self.frames[self.current_frame]
171
+ self.grid = frm.grid
172
+ self._init_button_colors()
173
+ self._update_frame_indicator()
174
+
175
+ # Export handlers
176
+ def _handle_Export(self, _event) -> None:
177
+ data = [f.grid for f in self.frames] if len(self.frames) > 1 else self.frames[0].grid
178
+ print(data)
179
+
180
+ def _handle_Export_to_File(self, _event) -> None:
181
+ path = sg.popup_get_file(
182
+ 'Save Grid As', save_as=True, no_window=True,
183
+ file_types=(('JSON Files', '*.json'),), default_extension='.json'
184
+ )
185
+ if not path:
186
+ return
187
+ data = [f.grid for f in self.frames] if len(self.frames) > 1 else self.frames[0].grid
188
+ try:
189
+ with open(path, 'w') as f:
190
+ json.dump(data, f)
191
+ sg.popup('Success', f'Saved to {os.path.basename(path)}')
192
+ except Exception as e:
193
+ sg.popup_error('Error', str(e))
194
+
195
+ def _handle_Load_from_File(self, _event) -> None:
196
+ path = sg.popup_get_file(
197
+ 'Load Grid From', no_window=True,
198
+ file_types=(('JSON Files', '*.json'),)
199
+ )
200
+ if not path:
201
+ return
202
+ try:
203
+ raw = json.load(open(path))
204
+ if is_valid_grid(raw, self.width, self.height):
205
+ grids = [raw]
206
+ elif is_valid_frames(raw, self.width, self.height):
207
+ grids = raw
208
+ else:
209
+ sg.popup_error('Invalid format')
210
+ return
211
+ self.frames = [Frame(copy.deepcopy(g)) for g in grids]
212
+ self.current_frame = 0
213
+ self._load_frame()
214
+ sg.popup('Success', f'Loaded {len(self.frames)} frame(s)')
215
+ except Exception as e:
216
+ sg.popup_error('Error', str(e))
217
+
218
+ # Send to matrix
219
+ def _handle_Send_to_Matrix(self, _event) -> None:
220
+ devices = DEVICES
221
+ if not devices:
222
+ sg.popup_error('No devices found.')
223
+ return
224
+ if self.preferred_device in devices:
225
+ dev = self.preferred_device
226
+ else:
227
+ descs = [f"{d.device} - {d.description}" for d in devices]
228
+ win = sg.Window('Select Device', [
229
+ [sg.Text('Select device:')],
230
+ [sg.Listbox(descs, size=(40, len(descs)), key='DEV')],
231
+ [sg.Checkbox('Always choose', key='REM')],
232
+ [sg.OK(), sg.Cancel()]
233
+ ], modal=True)
234
+ ev, vals = win.read(); win.close()
235
+ if ev != 'OK' or not vals['DEV']:
236
+ return
237
+ dev = devices[descs.index(vals['DEV'][0])]
238
+ if vals['REM']:
239
+ self.preferred_device = dev
240
+ try:
241
+ render_matrix(dev, self.grid)
242
+ sg.popup('Success', f'Sent to {dev.device}')
243
+ except Exception as e:
244
+ sg.popup_error('Error sending:', str(e))
245
+
246
+ # Button-mapped handlers
247
+ def _handle_Add_Frame(self, e): self.add_frame()
248
+ def _handle_Prev(self, e): self.prev_frame()
249
+ def _handle_Next(self, e): self.next_frame()
250
+
251
+ def _validate(self, grid: Any) -> bool:
252
+ return is_valid_grid(grid, self.width, self.height)
@@ -0,0 +1,117 @@
1
+ import PySimpleGUI as sg
2
+ from typing import List, Tuple, Any
3
+
4
+
5
+ class PixelGridLayout:
6
+ """
7
+ Builds the PySimpleGUI layout for the pixel grid, control panel,
8
+ and frame animation controls.
9
+ """
10
+ def __init__(
11
+ self,
12
+ width: int = 9,
13
+ height: int = 34,
14
+ button_list: List[str] = None,
15
+ pad: Tuple[int, int] = (0, 0),
16
+ button_size: Tuple[int, int] = (2, 1)
17
+ ):
18
+ # initialize backing fields
19
+ self.__width = None
20
+ self.__height = None
21
+ self.__pad = None
22
+ self.__button_size = None
23
+ self.__button_list = None
24
+ # assign via properties
25
+ self.width = width
26
+ self.height = height
27
+ self.pad = pad
28
+ self.button_size = button_size
29
+ self.button_list = button_list or [
30
+ 'Export', 'Export to File', 'Load from File',
31
+ 'Send to Matrix', 'Add Frame', 'Exit'
32
+ ]
33
+
34
+ @property
35
+ def width(self) -> int:
36
+ return self.__width
37
+
38
+ @width.setter
39
+ def width(self, value: Any) -> None:
40
+ if not isinstance(value, int) or value <= 0:
41
+ raise TypeError("Width must be a positive integer")
42
+ self.__width = value
43
+
44
+ @property
45
+ def height(self) -> int:
46
+ return self.__height
47
+
48
+ @height.setter
49
+ def height(self, value: Any) -> None:
50
+ if not isinstance(value, int) or value <= 0:
51
+ raise TypeError("Height must be a positive integer")
52
+ self.__height = value
53
+
54
+ @property
55
+ def pad(self) -> Tuple[int, int]:
56
+ return self.__pad
57
+
58
+ @pad.setter
59
+ def pad(self, value: Any) -> None:
60
+ if (
61
+ not isinstance(value, tuple)
62
+ or len(value) != 2
63
+ or not all(isinstance(v, int) for v in value)
64
+ ):
65
+ raise TypeError("pad must be a tuple of two integers")
66
+ self.__pad = value
67
+
68
+ @property
69
+ def button_size(self) -> Tuple[int, int]:
70
+ return self.__button_size
71
+
72
+ @button_size.setter
73
+ def button_size(self, value: Any) -> None:
74
+ if (
75
+ not isinstance(value, tuple)
76
+ or len(value) != 2
77
+ or not all(isinstance(v, int) for v in value)
78
+ ):
79
+ raise TypeError("button_size must be a tuple of two integers")
80
+ self.__button_size = value
81
+
82
+ @property
83
+ def button_list(self) -> List[str]:
84
+ return self.__button_list
85
+
86
+ @button_list.setter
87
+ def button_list(self, value: Any) -> None:
88
+ if (
89
+ not isinstance(value, list)
90
+ or not all(isinstance(v, str) for v in value)
91
+ ):
92
+ raise TypeError("button_list must be a list of strings")
93
+ self.__button_list = value
94
+
95
+ def build(self) -> List[List[Any]]:
96
+ grid = [
97
+ [sg.Button(' ', size=self.button_size, key=(col, row), pad=self.pad)
98
+ for col in range(self.width)]
99
+ for row in range(self.height)
100
+ ]
101
+ grid_column = sg.Column(grid)
102
+
103
+ controls = [[sg.Button(btn)] for btn in self.button_list]
104
+ control_column = sg.Column(controls, vertical_alignment='top')
105
+
106
+ frame_controls = [
107
+ sg.Button('< Prev', key='<Prev>'),
108
+ sg.Text('', key='FRAME_INDICATOR'),
109
+ sg.Button('Next >', key='Next>')
110
+ ]
111
+
112
+ layout = [
113
+ [grid_column, sg.VerticalSeparator(), control_column],
114
+ [sg.HorizontalSeparator(pad=(0, 5))],
115
+ [sg.Push(), *frame_controls, sg.Push()]
116
+ ]
117
+ return layout
@@ -0,0 +1,8 @@
1
+ """
2
+ Author: Taylor B. tayjaybabee@gmail.com
3
+ Date: 2025-05-05 15:02:58
4
+ LastEditors: Taylor B. tayjaybabee@gmail.com
5
+ LastEditTime: 2025-05-05 15:24:08
6
+ FilePath: is_matrix_forge/dev_tools/__init__.py
7
+ Description: 这是默认设置,可以在设置》工具》File Description中进行配置
8
+ """
@@ -0,0 +1,82 @@
1
+ from pathlib import Path
2
+ from platformdirs import PlatformDirs
3
+ from easy_exit_calls import ExitCallHandler
4
+ from is_matrix_forge.log_engine import ROOT_LOGGER as PARENT_LOGGER
5
+
6
+ MOD_LOGGER = PARENT_LOGGER.get_child('monitor.gui.debug')
7
+
8
+ DEBUG_MODE_KEYFILE_NAME = 'marauder.map'
9
+
10
+ ECH: ExitCallHandler
11
+
12
+ PLATFORM_DIRS = PlatformDirs('LEDMatrixPowerMonitor', 'Inspyre-Softworks')
13
+
14
+
15
+ def get_debug_mode_keyfile_path():
16
+ return PLATFORM_DIRS.user_data_path
17
+
18
+
19
+ MARAUDER_FILE_PATH = get_debug_mode_keyfile_path()
20
+
21
+
22
+ def create_marauders_map(mm_path=MARAUDER_FILE_PATH):
23
+ log = MOD_LOGGER.get_child('create_marauders_map')
24
+ mm_path = Path(mm_path).expanduser().resolve()
25
+ log.debug(f'Creating Marauder\'s Map at {mm_path}')
26
+
27
+ if not mm_path.parent.exists():
28
+ log.debug(f'Creating parent directory for Marauder\'s Map: {mm_path.parent}')
29
+ mm_path.parent.mkdir(parents=True, exist_ok=True)
30
+
31
+ with open(mm_path, 'w') as f:
32
+ f.write('Mooney, Wormtail, Padfoot, and Prongs are proud to present the Marauder\'s Map.')
33
+
34
+ log.debug(f'Marauder\'s Map created at {mm_path}')
35
+
36
+
37
+ def disable_debug_mode(mm_file_path=MARAUDER_FILE_PATH):
38
+ log = MOD_LOGGER.get_child('disable_debug_mode')
39
+ mm_file_path = Path(mm_file_path).expanduser().resolve()
40
+ log.debug(f'Writing magic words to Marauder\'s Map at {mm_file_path}')
41
+
42
+ if not mm_file_path.exists():
43
+ log.warning(f'Marauder\'s Map file does not exist: {mm_file_path}. (No debug mode to disable)')
44
+ raise RuntimeError(f'Marauder\'s Map file does not exist: {mm_file_path}. (No debug mode to disable)')
45
+
46
+ with open(MARAUDER_FILE_PATH, 'w') as f:
47
+ f.write('Mischief Managed.')
48
+ log.debug(f'Magic words written to Marauder\'s Map at {mm_file_path}')
49
+
50
+ if 'ECH' in globals() and globals()['ECH'].function_registered(disable_debug_mode):
51
+ log.debug(f'Found ExitCallHandler instance with registered function: {disable_debug_mode}. Unregistering it.')
52
+ globals()['ECH'].unregister_handler(disable_debug_mode)
53
+ log.debug('Unregistered ExitCallHandler instance with registered function: {disable_debug_mode}.')
54
+
55
+
56
+ def enable_debug_mode(
57
+ mm_file_path=MARAUDER_FILE_PATH,
58
+ disable_on_exit=False,
59
+ ):
60
+ mm_file_path = Path(mm_file_path).expanduser().resolve()
61
+
62
+ if not mm_file_path.exists():
63
+ create_marauders_map(mm_file_path)
64
+
65
+ with open(MARAUDER_FILE_PATH, 'w') as f:
66
+ f.write('I solemnly swear that I am up to no good.')
67
+
68
+ if disable_on_exit:
69
+ from easy_exit_calls import ExitCallHandler
70
+ global ECH
71
+ ech = ExitCallHandler()
72
+ ech.register_handler(disable_debug_mode, mm_file_path)
73
+
74
+
75
+ def is_debug_mode(portkey_file_path=MARAUDER_FILE_PATH):
76
+ try:
77
+ with open(portkey_file_path, 'r') as f:
78
+ # Check if the file contains the correct content
79
+ content = f.read()
80
+ return content.strip() == 'I solemnly swear that I am up to no good.'
81
+ except FileNotFoundError:
82
+ return False
@@ -0,0 +1,47 @@
1
+ """
2
+
3
+
4
+ Author:
5
+ Inspyre Softworks
6
+
7
+ Project:
8
+ led-matrix-battery
9
+
10
+ File:
11
+ is_matrix_forge/dev_tools/presets.py
12
+
13
+
14
+ Description:
15
+
16
+
17
+ """
18
+ from pathlib import Path
19
+ from typing import Union, Optional
20
+ from is_matrix_forge.common.dirs import PRESETS_DIR
21
+ from is_matrix_forge.led_matrix.constants import PROJECT_URLS
22
+ import requests
23
+
24
+
25
+ MANIFEST_FILE_NAME = 'manifest.json'
26
+ REMOTE_MANIFEST_URL = PROJECT_URLS['github_api'] + '/' + MANIFEST_FILE_NAME
27
+
28
+
29
+ def get_remote_manifest():
30
+ """
31
+ Download the remote preset manifest file.
32
+
33
+ Returns:
34
+ dict:
35
+ The JSON content of the remote manifest file.
36
+
37
+ Raises:
38
+ requests.RequestException:
39
+ If there is an error during the HTTP request.
40
+ """
41
+ res = requests.get(REMOTE_MANIFEST_URL)
42
+ res.raise_for_status()
43
+
44
+ data = res.json()
45
+
46
+ return data
47
+
@@ -0,0 +1,164 @@
1
+ from enum import IntEnum
2
+ import serial
3
+
4
+ DISCONNECTED_DEVS = []
5
+ FWK_MAGIC = [0x32, 0xAC]
6
+ FWK_VID = 0x32AC
7
+ LED_MATRIX_PID = 0x20
8
+ QTPY_PID = 0x001F
9
+ INPUTMODULE_PIDS = [LED_MATRIX_PID, QTPY_PID]
10
+
11
+
12
+ def disconnect_dev(dev):
13
+ """
14
+ Disconnect the device from the system.
15
+
16
+ Parameters:
17
+ dev (str):
18
+ The device to disconnect.
19
+ """
20
+ global DISCONNECTED_DEVS
21
+ if dev in DISCONNECTED_DEVS:
22
+ return
23
+ DISCONNECTED_DEVS.append(dev)
24
+
25
+
26
+ class CommandVals(IntEnum):
27
+ Brightness = 0x00
28
+ Pattern = 0x01
29
+ BootloaderReset = 0x02
30
+ Sleep = 0x03
31
+ Animate = 0x04
32
+ Panic = 0x05
33
+ Draw = 0x06
34
+ StageGreyCol = 0x07
35
+ DrawGreyColBuffer = 0x08
36
+ SetText = 0x09
37
+ StartGame = 0x10
38
+ GameControl = 0x11
39
+ GameStatus = 0x12
40
+ SetColor = 0x13
41
+ DisplayOn = 0x14
42
+ InvertScreen = 0x15
43
+ SetPixelColumn = 0x16
44
+ FlushFramebuffer = 0x17
45
+ ClearRam = 0x18
46
+ ScreenSaver = 0x19
47
+ SetFps = 0x1A
48
+ SetPowerMode = 0x1B
49
+ PwmFreq = 0x1E
50
+ DebugMode = 0x1F
51
+ Version = 0x20
52
+
53
+
54
+ class Game(IntEnum):
55
+ Snake = 0x00
56
+ Pong = 0x01
57
+ Tetris = 0x02
58
+ GameOfLife = 0x03
59
+
60
+
61
+ class PatternVals(IntEnum):
62
+ Percentage = 0x00
63
+ Gradient = 0x01
64
+ DoubleGradient = 0x02
65
+ DisplayLotus = 0x03
66
+ ZigZag = 0x04
67
+ FullBrightness = 0x05
68
+ DisplayPanic = 0x06
69
+ DisplayLotus2 = 0x07
70
+
71
+
72
+ class GameOfLifeStartParam(IntEnum):
73
+ Currentmatrix = 0x00
74
+ Pattern1 = 0x01
75
+ Blinker = 0x02
76
+ Toad = 0x03
77
+ Beacon = 0x04
78
+ Glider = 0x05
79
+
80
+ def __str__(self):
81
+ return self.name.lower()
82
+
83
+ def __repr__(self):
84
+ return str(self)
85
+
86
+ @staticmethod
87
+ def argparse(s):
88
+ try:
89
+ return GameOfLifeStartParam[s.lower().capitalize()]
90
+ except KeyError:
91
+ return s
92
+
93
+
94
+ class GameControlVal(IntEnum):
95
+ Up = 0
96
+ Down = 1
97
+ Left = 2
98
+ Right = 3
99
+ Quit = 4
100
+
101
+
102
+ RESPONSE_SIZE = 32
103
+
104
+
105
+ def bootloader_jump(dev):
106
+ """Reboot into the bootloader to flash new firmware"""
107
+ send_command(dev, CommandVals.BootloaderReset, [0x00])
108
+
109
+
110
+ def brightness(dev, b: int):
111
+ """Adjust the brightness scaling of the entire screen."""
112
+ send_command(dev, CommandVals.Brightness, [b])
113
+
114
+
115
+ def get_brightness(dev):
116
+ """Adjust the brightness scaling of the entire screen."""
117
+ res = send_command(dev, CommandVals.Brightness, with_response=True)
118
+ return int(res[0])
119
+
120
+
121
+ def get_version(dev):
122
+ """Get the device's firmware version"""
123
+ res = send_command(dev, CommandVals.Version, with_response=True)
124
+ if not res:
125
+ return 'Unknown'
126
+ major = res[0]
127
+ minor = (res[1] & 0xF0) >> 4
128
+ patch = res[1] & 0xF
129
+ pre_release = res[2]
130
+
131
+ version = f"{major}.{minor}.{patch}"
132
+ if pre_release:
133
+ version += " (Pre-release)"
134
+ return version
135
+
136
+
137
+ def send_command(dev, command, parameters=[], with_response=False):
138
+ return send_command_raw(dev, FWK_MAGIC + [command] + parameters, with_response)
139
+
140
+
141
+ def send_command_raw(dev, command, with_response=False):
142
+ """Send a command to the device.
143
+ Opens new serial connection every time"""
144
+ # print(f"Sending command: {command}")
145
+ try:
146
+ with serial.Serial(dev.device, 115200) as s:
147
+ s.write(command)
148
+
149
+ if with_response:
150
+ res = s.read(RESPONSE_SIZE)
151
+ # print(f"Received: {res}")
152
+ return res
153
+ except (IOError, OSError) as _ex:
154
+ disconnect_dev(dev.device)
155
+ # print("Error: ", ex)
156
+
157
+
158
+ def send_serial(dev, s, command):
159
+ """Send serial command by using existing serial connection"""
160
+ try:
161
+ s.write(command)
162
+ except (IOError, OSError) as _ex:
163
+ disconnect_dev(dev.device)
164
+ # print("Error: ", ex)