IS-Matrix-Forge 1.0.0.dev2__py3-none-any.whl → 1.0.0.dev5__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 (61) hide show
  1. is_matrix_forge/__init__.py +1 -1
  2. is_matrix_forge/assets/__init__.py +1 -1
  3. is_matrix_forge/common/helpers/__init__.py +32 -0
  4. is_matrix_forge/designer_gui/__init__.py +1 -1
  5. is_matrix_forge/designer_gui/main_window/__init__.py +11 -3
  6. is_matrix_forge/designer_gui/main_window/{layout.py → layout/__init__.py} +2 -6
  7. is_matrix_forge/designer_gui/main_window/layout/button_grid/__init__.py +0 -0
  8. is_matrix_forge/designer_gui/main_window/layout/button_grid/column.py +17 -0
  9. is_matrix_forge/designer_gui/main_window/layout/button_grid/grid.py +15 -0
  10. is_matrix_forge/designer_gui/main_window/streamlit_app.py +93 -0
  11. is_matrix_forge/dev_tools/__init__.py +1 -1
  12. is_matrix_forge/inputmodule/ledmatrix.py +0 -16
  13. is_matrix_forge/led_matrix/Scripts/__init__.py +1 -1
  14. is_matrix_forge/led_matrix/Scripts/identify_matrices.py +228 -0
  15. is_matrix_forge/led_matrix/__init__.py +55 -4
  16. is_matrix_forge/led_matrix/commands/__init__.py +1 -66
  17. is_matrix_forge/led_matrix/constants.py +1 -0
  18. is_matrix_forge/led_matrix/controller/__init__.py +2 -0
  19. is_matrix_forge/led_matrix/controller/controller.py +75 -17
  20. is_matrix_forge/led_matrix/display/animations/__init__.py +0 -1
  21. is_matrix_forge/led_matrix/display/animations/animation.py +14 -2
  22. is_matrix_forge/led_matrix/display/animations/effects/__init__.py +1 -1
  23. is_matrix_forge/led_matrix/display/animations/errors.py +10 -0
  24. is_matrix_forge/led_matrix/display/animations/font/__init__.py +1 -1
  25. is_matrix_forge/led_matrix/display/animations/frame/__init__.py +1 -1
  26. is_matrix_forge/led_matrix/display/animations/frame/base.py +1 -1
  27. is_matrix_forge/led_matrix/display/effects/__init__.py +1 -1
  28. is_matrix_forge/led_matrix/display/effects/breather.py +27 -11
  29. is_matrix_forge/led_matrix/display/grid/presets/__init__.py +1 -1
  30. is_matrix_forge/led_matrix/display/helpers/__init__.py +7 -1
  31. is_matrix_forge/led_matrix/display/helpers/columns.py +1 -1
  32. is_matrix_forge/led_matrix/display/media.py +1 -2
  33. is_matrix_forge/led_matrix/display/patterns/built_in/__init__.py +1 -1
  34. is_matrix_forge/led_matrix/display/patterns/built_in/commands.py +2 -2
  35. is_matrix_forge/led_matrix/display/patterns/built_in/constants.py +1 -27
  36. is_matrix_forge/led_matrix/display/patterns/built_in/stencils/res.py +64 -0
  37. is_matrix_forge/led_matrix/display/text/__init__.py +1 -1
  38. is_matrix_forge/led_matrix/display/text/scroller.py +22 -0
  39. is_matrix_forge/led_matrix/hardware.py +153 -54
  40. is_matrix_forge/led_matrix/helpers/__init__.py +5 -1
  41. is_matrix_forge/led_matrix/helpers/core/__init__.py +1 -1
  42. is_matrix_forge/led_matrix/tools/__init__.py +1 -1
  43. is_matrix_forge/led_matrix/utils/__init__.py +1 -1
  44. is_matrix_forge/monitor/gui/layout/__init__.py +1 -1
  45. is_matrix_forge/monitor/monitor.py +3 -2
  46. is_matrix_forge/scripts/__init__.py +1 -0
  47. is_matrix_forge/scripts/preset_installer.py +184 -0
  48. is_matrix_forge/serial_com/__init__.py +1 -0
  49. is_matrix_forge/serial_com/base/__init__.py +18 -0
  50. is_matrix_forge/serial_com/base/answer.py +12 -0
  51. is_matrix_forge/serial_com/base/message.py +13 -0
  52. is_matrix_forge/serial_com/base/request.py +8 -0
  53. is_matrix_forge/serial_com/exchange.py +47 -0
  54. {is_matrix_forge-1.0.0.dev2.dist-info → is_matrix_forge-1.0.0.dev5.dist-info}/METADATA +52 -52
  55. {is_matrix_forge-1.0.0.dev2.dist-info → is_matrix_forge-1.0.0.dev5.dist-info}/RECORD +59 -47
  56. is_matrix_forge/led_matrix/display/patterns/__init__.py +0 -59
  57. is_matrix_forge/led_matrix/display/patterns/utils.py +0 -2
  58. /is_matrix_forge/led_matrix/display/patterns/built_in/{stencils.py → stencils/__init__.py} +0 -0
  59. {is_matrix_forge-1.0.0.dev2.dist-info → is_matrix_forge-1.0.0.dev5.dist-info}/LICENSE.md +0 -0
  60. {is_matrix_forge-1.0.0.dev2.dist-info → is_matrix_forge-1.0.0.dev5.dist-info}/WHEEL +0 -0
  61. {is_matrix_forge-1.0.0.dev2.dist-info → is_matrix_forge-1.0.0.dev5.dist-info}/entry_points.txt +0 -0
@@ -3,6 +3,6 @@ Author: Taylor B. tayjaybabee@gmail.com
3
3
  Date: 2025-04-15 09:15:25
4
4
  LastEditors: Taylor B. tayjaybabee@gmail.com
5
5
  LastEditTime: 2025-04-15 09:24:40
6
- FilePath: is_matrix_forge/__init__.py
6
+ FilePath: is_matrix_forge/grid.py
7
7
  Description: 这是默认设置,可以在设置》工具》File Description中进行配置
8
8
  """
@@ -3,6 +3,6 @@ Author: Taylor B. tayjaybabee@gmail.com
3
3
  Date: 2025-04-15 09:15:37
4
4
  LastEditors: Taylor B. tayjaybabee@gmail.com
5
5
  LastEditTime: 2025-04-15 09:24:40
6
- FilePath: is_matrix_forge/assets/__init__.py
6
+ FilePath: is_matrix_forge/assets/grid.py
7
7
  Description: 这是默认设置,可以在设置》工具》File Description中进行配置
8
8
  """
@@ -3,6 +3,10 @@ from pathlib import Path
3
3
  from typing import Optional
4
4
  import requests
5
5
  from inspyre_toolbox.path_man import provision_path
6
+ from is_matrix_forge.log_engine import ROOT_LOGGER
7
+
8
+
9
+ MOD_LOGGER = ROOT_LOGGER.get_child(__name__)
6
10
 
7
11
 
8
12
  def download_presets():
@@ -24,9 +28,13 @@ def calculate_checksum(file_path):
24
28
  str:
25
29
  The SHA-256 checksum of the file.
26
30
  """
31
+ log = MOD_LOGGER.get_child('calculate_checksum')
32
+
27
33
  if not isinstance(file_path, Path):
28
34
  file_path = provision_path(file_path)
29
35
 
36
+ log.debug(f'Calculating checksum for file: {file_path}')
37
+
30
38
  sha256 = hashlib.sha256()
31
39
 
32
40
  with open(file_path, 'rb') as f:
@@ -78,6 +86,30 @@ def percentage_to_value(percent, max_value=255):
78
86
  return int(round(percent * max_value / 100))
79
87
 
80
88
 
89
+ def value_to_percentage(value, max_value=255):
90
+ """
91
+ Convert a value to its percentage representation relative to `max_value`.
92
+
93
+ Parameters:
94
+ value (int or float):
95
+ The value to convert.
96
+
97
+ max_value (int, optional):
98
+ The maximum possible value. Defaults to 255.
99
+
100
+ Returns:
101
+ float:
102
+ The percentage value between 0 and 100.
103
+ """
104
+ if not isinstance(value, (int, float)):
105
+ raise TypeError(f'value must be of type int or float, not {type(value)}')
106
+
107
+ if value < 0 or value > max_value:
108
+ raise ValueError(f'value must be between 0 and {max_value}, not {value}')
109
+
110
+ return (value / max_value) * 100
111
+
112
+
81
113
  def verify_checksum(remote_checksum, local_file_path):
82
114
  """
83
115
  Verify the checksum of a local file against a remote checksum.
@@ -3,6 +3,6 @@ Author: Taylor B. tayjaybabee@gmail.com
3
3
  Date: 2025-04-30 16:01:10
4
4
  LastEditors: Taylor B. tayjaybabee@gmail.com
5
5
  LastEditTime: 2025-04-30 16:01:49
6
- FilePath: is_matrix_forge/designer_gui/__init__.py
6
+ FilePath: is_matrix_forge/designer_gui/grid.py
7
7
  Description: 这是默认设置,可以在设置》工具》File Description中进行配置
8
8
  """
@@ -4,12 +4,15 @@ import PySimpleGUI as sg
4
4
  import copy
5
5
  import os
6
6
 
7
- from ...led_matrix.helpers.device import DEVICES
7
+ from is_matrix_forge.led_matrix.helpers.device import DEVICES
8
8
  from is_matrix_forge.led_matrix.display.animations.frame.base import Frame
9
9
  from is_matrix_forge.led_matrix.display.grid.helpers import is_valid_grid
10
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
11
+ from is_matrix_forge.designer_gui.main_window.layout import PixelGridLayout
12
+ from is_matrix_forge.led_matrix.display.helpers import render_matrix
13
+
14
+
15
+ sg.theme('DarkTeal2')
13
16
 
14
17
 
15
18
  def _normalize(key: Any) -> str:
@@ -139,6 +142,7 @@ class PixelGrid:
139
142
  def _toggle_pixel(self, key: Tuple[int, int]) -> None:
140
143
  col, row = key
141
144
  new = 1 - self.grid[col][row]
145
+ print(f'Pixel {col},{row} toggled from {self.grid[col][row]} to {new}')
142
146
  self.grid = [[new if (c==col and r==row) else self.grid[c][r]
143
147
  for r in range(self.height)]
144
148
  for c in range(self.width)]
@@ -250,3 +254,7 @@ class PixelGrid:
250
254
 
251
255
  def _validate(self, grid: Any) -> bool:
252
256
  return is_valid_grid(grid, self.width, self.height)
257
+
258
+
259
+ if __name__ == '__main__':
260
+ PixelGrid().run()
@@ -1,5 +1,6 @@
1
1
  import PySimpleGUI as sg
2
2
  from typing import List, Tuple, Any
3
+ from is_matrix_forge.designer_gui.main_window.layout.button_grid.column import build_grid_frame
3
4
 
4
5
 
5
6
  class PixelGridLayout:
@@ -93,12 +94,7 @@ class PixelGridLayout:
93
94
  self.__button_list = value
94
95
 
95
96
  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)
97
+ grid_column = build_grid_frame(self.width, self.height, button_size=self.button_size, pad=self.pad)
102
98
 
103
99
  controls = [[sg.Button(btn)] for btn in self.button_list]
104
100
  control_column = sg.Column(controls, vertical_alignment='top')
@@ -0,0 +1,17 @@
1
+ import PySimpleGUI as sg
2
+
3
+
4
+ def build_grid_frame(num_cols=9, num_rows=34, button_size=(2,1), pad=(1,1)):
5
+ """Return a layout of 9 vertical columns (of 34 buttons each), side by side."""
6
+ return [
7
+ [
8
+ sg.Column(
9
+ [
10
+ [sg.Button(' ', key=(col, row), size=button_size, pad=pad)]
11
+ for row in range(num_rows)
12
+ ],
13
+ pad=(0,0)
14
+ )
15
+ for col in range(num_cols)
16
+ ]
17
+ ]
@@ -0,0 +1,15 @@
1
+ import PySimpleGUI as sg
2
+
3
+
4
+ def build_button_grid(num_cols, num_rows, button_size=(1, 1), pad=(1, 1)):
5
+ """
6
+ Returns a list of `num_cols` lists, each containing `num_rows` buttons.
7
+ So: 9 columns of 34 rows each.
8
+ """
9
+ return [
10
+ [sg.Button(' ', key=(col, row), size=button_size, pad=pad)]
11
+ for row in range(num_rows)
12
+ ],
13
+ for col in range(num_cols)
14
+ ]
15
+
@@ -0,0 +1,93 @@
1
+ import streamlit as st
2
+ import json
3
+ import copy
4
+
5
+ class Frame:
6
+ def __init__(self, grid, duration=1.0):
7
+ self.grid = grid
8
+ self.duration = duration
9
+
10
+ class PixelGridWeb:
11
+ def __init__(self, width=9, height=34):
12
+ self.width = width
13
+ self.height = height
14
+ if "frames" not in st.session_state:
15
+ st.session_state.frames = [Frame(self._new_grid())]
16
+ if "current_frame" not in st.session_state:
17
+ st.session_state.current_frame = 0
18
+
19
+ def _new_grid(self):
20
+ return [[0 for _ in range(self.height)] for _ in range(self.width)]
21
+
22
+ @property
23
+ def frames(self):
24
+ return st.session_state.frames
25
+
26
+ @frames.setter
27
+ def frames(self, value):
28
+ st.session_state.frames = value
29
+
30
+ @property
31
+ def current_frame(self):
32
+ return st.session_state.current_frame
33
+
34
+ @current_frame.setter
35
+ def current_frame(self, value):
36
+ st.session_state.current_frame = value
37
+
38
+ @property
39
+ def grid(self):
40
+ return self.frames[self.current_frame].grid
41
+
42
+ def draw(self):
43
+ st.title("PixelGrid Animator")
44
+ st.write(f"Frame {self.current_frame+1}/{len(self.frames)}")
45
+ # Render grid
46
+ grid = self.grid
47
+ for row in range(self.height):
48
+ cols = st.columns(self.width)
49
+ for col in range(self.width):
50
+ key = f"pix_{col}_{row}_{self.current_frame}"
51
+ is_on = grid[col][row]
52
+ label = "🟩" if is_on else "⬛"
53
+ if cols[col].button(label, key=key, use_container_width=True):
54
+ grid[col][row] ^= 1 # Toggle
55
+
56
+ # Frame controls
57
+ c1, c2, c3, c4 = st.columns(4)
58
+ if c1.button("Prev", disabled=self.current_frame == 0):
59
+ self.current_frame -= 1
60
+ if c2.button("Next", disabled=self.current_frame == len(self.frames)-1):
61
+ self.current_frame += 1
62
+ if c3.button("Add Frame"):
63
+ self.frames.append(Frame(copy.deepcopy(self.grid)))
64
+ self.current_frame = len(self.frames) - 1
65
+ if c4.button("Delete Frame", disabled=len(self.frames) == 1):
66
+ self.frames.pop(self.current_frame)
67
+ self.current_frame = max(0, self.current_frame-1)
68
+ # Export/import
69
+ st.markdown("---")
70
+ exp1, exp2, exp3 = st.columns(3)
71
+ if exp1.button("Export JSON"):
72
+ st.download_button(
73
+ "Download JSON",
74
+ data=json.dumps([f.grid for f in self.frames]),
75
+ file_name="frames.json",
76
+ mime="application/json"
77
+ )
78
+ uploaded = exp2.file_uploader("Load JSON", type="json")
79
+ if uploaded:
80
+ data = json.load(uploaded)
81
+ if isinstance(data, list) and all(isinstance(frame, list) for frame in data):
82
+ self.frames = [Frame(copy.deepcopy(g)) for g in data]
83
+ self.current_frame = 0
84
+ # Placeholder for "Send to Matrix"
85
+ if exp3.button("Send to Matrix"):
86
+ st.info("This would send to your matrix (not implemented here)!")
87
+
88
+ def main():
89
+ pg = PixelGridWeb()
90
+ pg.draw()
91
+
92
+ if __name__ == "__main__":
93
+ main()
@@ -3,6 +3,6 @@ Author: Taylor B. tayjaybabee@gmail.com
3
3
  Date: 2025-05-05 15:02:58
4
4
  LastEditors: Taylor B. tayjaybabee@gmail.com
5
5
  LastEditTime: 2025-05-05 15:24:08
6
- FilePath: is_matrix_forge/dev_tools/__init__.py
6
+ FilePath: is_matrix_forge/dev_tools/grid.py
7
7
  Description: 这是默认设置,可以在设置》工具》File Description中进行配置
8
8
  """
@@ -40,22 +40,6 @@ PWM_FREQUENCIES = [
40
40
  ]
41
41
 
42
42
 
43
- def get_pwm_freq(dev):
44
- """Adjust the brightness scaling of the entire screen."""
45
- res = send_command(dev, CommandVals.PwmFreq, with_response=True)
46
- freq = int(res[0])
47
- if freq == 0:
48
- return 29000
49
- elif freq == 1:
50
- return 3600
51
- elif freq == 2:
52
- return 1800
53
- elif freq == 3:
54
- return 900
55
- else:
56
- return None
57
-
58
-
59
43
  def percentage(dev, p):
60
44
  """Fill a percentage of the screen. Bottom to top"""
61
45
  send_command(dev, CommandVals.Pattern, [PatternVals.Percentage, p])
@@ -1,4 +1,4 @@
1
1
  """
2
- Fake `__init__.py` to make the Scripts directory a sub-package.
2
+ Fake `grid.py` to make the Scripts directory a sub-package.
3
3
  """
4
4
 
@@ -0,0 +1,228 @@
1
+ """
2
+ The main entrypoint for the `led-matrix-identify` command.
3
+
4
+ Description:
5
+ This module contains the entrypoint for the `led-matrix-identify` command, which
6
+ is used to identify:
7
+
8
+ * Which LED matrix can be communicated with by which serial port
9
+ * The physical location of each LED matrix
10
+
11
+
12
+ See `main()` for more information.
13
+
14
+ Author:
15
+ Taylor B. <t.blackstone@inspyre.tech>
16
+
17
+ Since:
18
+ v1.0.0
19
+ """
20
+ import argparse
21
+ from argparse import ArgumentParser
22
+ from threading import Thread
23
+ from typing import List, Optional, Union
24
+
25
+ from is_matrix_forge.led_matrix.controller.controller import LEDMatrixController
26
+ from is_matrix_forge.led_matrix.constants import DEVICES
27
+
28
+
29
+ DEFAULT_RUNTIME = 30
30
+ DEFAULT_CYCLES = 3
31
+
32
+
33
+ def find_leftmost_matrix(matrices: List[LEDMatrixController]) -> Optional[LEDMatrixController]:
34
+ """
35
+ Determine the leftmost (physically) LED matrix.
36
+
37
+ Parameters:
38
+ matrices (List[LEDMatrixController]):
39
+ The controller objects for all LED matrices.
40
+
41
+ Returns:
42
+ Optional[LEDMatrixController]:
43
+ The controller object representing the determined leftmost matrix, `None`
44
+ if none found.
45
+ """
46
+ if left_devices := [m for m in matrices if m.side_of_keyboard == 'left']:
47
+ return min(left_devices, key=lambda m: m.slot)
48
+
49
+ # Fallback: use right-side devices if no left-side
50
+ right_devices = [m for m in matrices if m.side_of_keyboard == 'right']
51
+
52
+ return min(right_devices, key=lambda m: m.slot) if right_devices else None
53
+
54
+
55
+ def find_rightmost_matrix(matrices: List[LEDMatrixController]) -> Optional[LEDMatrixController]:
56
+ """
57
+ Determine the rightmost (physically) LED matrix.
58
+
59
+ Parameters:
60
+ matrices (List[LEDMatrixController]):
61
+ The controller objects for alll LED matrices.
62
+
63
+ Returns:
64
+ Optional[LEDMatrixController]:
65
+ The controller object representing the determined rightmost matrix, `None`
66
+ if none found.
67
+ """
68
+
69
+ if right_devices := [m for m in matrices if m.side_of_keyboard == 'right']:
70
+ return max(right_devices, key=lambda m: m.slot)
71
+
72
+ # Fallback: use left-side devices if no right-side
73
+ left_devices = [m for m in matrices if m.side_of_keyboard == 'left']
74
+
75
+ return max(left_devices, key=lambda m: m.slot) if left_devices else None
76
+
77
+
78
+ class Arguments(ArgumentParser):
79
+ def __init__(self):
80
+ super().__init__('IdentifyLEDMatrices')
81
+ self.__parsed = None
82
+
83
+ # Set up the arguments
84
+ self.add_argument(
85
+ '--runtime', '-t',
86
+ action='store',
87
+ default=DEFAULT_RUNTIME,
88
+ help='The total runtime'
89
+ )
90
+
91
+ self.add_argument(
92
+ '--skip-clear',
93
+ action='store_true',
94
+ default=False,
95
+ help='Skip clearing LEDs'
96
+ )
97
+
98
+ self.add_argument(
99
+ '-c', '--cycle-count',
100
+ action='store',
101
+ type=int,
102
+ default=DEFAULT_CYCLES,
103
+ help='The number of cycles to run per message, for each selected device.'
104
+ )
105
+
106
+ # Set up the mutually exclusive arguments for the left and right matrices
107
+ left_right = self.add_mutually_exclusive_group()
108
+
109
+ left_right.add_argument(
110
+ '-R', '--only-right',
111
+ action='store_true',
112
+ default=False,
113
+ help='Only display identifying information for/on the rightmost matrix.'
114
+ )
115
+
116
+ left_right.add_argument(
117
+ '-L', '--only-left',
118
+ action='store_true',
119
+ default=False,
120
+ help='Only display identifying information for/on the leftmost matrix.'
121
+ )
122
+
123
+ @property
124
+ def parsed(self) -> Optional[argparse.Namespace]:
125
+ """
126
+ The parsed arguments (if they've been parsed).
127
+
128
+ Returns:
129
+ argparse.Namespace:
130
+ The parsed arguments.
131
+
132
+ None:
133
+ If the arguments have not yet been parsed.
134
+ """
135
+ return self.__parsed
136
+
137
+ def parse_args(self, *args, **kwargs):
138
+ """
139
+ (Overrides the parent method, since we need to cache the parsed arguments. Does only that and then calls the
140
+ parent method.)
141
+
142
+ Parameters:
143
+ *args:
144
+ **kwargs:
145
+
146
+ Returns:
147
+ argparse.Namespace:
148
+ The parsed arguments
149
+ """
150
+ self.__parsed = super().parse_args(*args, **kwargs)
151
+ return self.__parsed
152
+
153
+
154
+ def main(
155
+ runtime: Optional[Union[int, float]] = None,
156
+ only_left: Optional[bool] = None,
157
+ only_right: Optional[bool] = None,
158
+ skip_clear: Optional[bool] = None,
159
+ cycle_count: Optional[int] = None
160
+ ):
161
+ """
162
+ The main function of the script.
163
+
164
+ Parameters:
165
+ runtime (Optional[Union[int, float]]):
166
+ The total runtime in seconds.
167
+
168
+ only_left (Optional[bool]):
169
+ Only display identifying information for/on the leftmost matrix.
170
+
171
+ only_right (Optional[bool]):
172
+ Only display identifying information for/on the rightmost matrix.
173
+
174
+ skip_clear (Optional[bool]):
175
+ Skip clearing LEDs.
176
+
177
+ cycle_count (Optional[int]):
178
+ The number of cycles to run per message, for each selected device.
179
+
180
+ Returns:
181
+ List[Thread]:
182
+ A list of threads, one for each LED matrix being identified.
183
+ """
184
+ selected = [LEDMatrixController(device, 100, thread_safe=True) for device in DEVICES]
185
+
186
+ if not skip_clear:
187
+ skip_clear = ARGS.parsed.skip_clear
188
+
189
+ if only_left is None:
190
+ only_left = ARGS.parsed.only_left
191
+
192
+ if only_right is None:
193
+ only_right = ARGS.parsed.only_right
194
+
195
+ if only_right:
196
+ selected = [find_rightmost_matrix(selected)]
197
+ elif only_left:
198
+ selected = [find_leftmost_matrix(selected)]
199
+
200
+ cycle_count = ARGS.parsed.cycle_count if cycle_count is None else cycle_count
201
+
202
+ if runtime is None:
203
+ runtime = ARGS.parsed.runtime
204
+
205
+ threads = []
206
+
207
+ threads.extend(
208
+ Thread(
209
+ target=device.identify,
210
+ kwargs={
211
+ 'skip_clear': skip_clear,
212
+ 'duration': runtime,
213
+ 'cycles': cycle_count
214
+ },
215
+ )
216
+ for device in selected
217
+ )
218
+ for t in threads:
219
+ t.start()
220
+
221
+ return threads
222
+
223
+
224
+ ARGS = Arguments()
225
+ PARSED = ARGS.parse_args()
226
+
227
+ if __name__ == '__main__':
228
+ threads = main()
@@ -1,7 +1,58 @@
1
+ import json
2
+ from is_matrix_forge.led_matrix.controller import LEDMatrixController, get_controllers
3
+ from is_matrix_forge.led_matrix.display.text.scroller import scroll_text_on_multiple_matrices
4
+ from platformdirs import PlatformDirs
1
5
 
2
6
 
3
- def get_controllers():
4
- from is_matrix_forge.led_matrix.controller.controller import LEDMatrixController
5
- from is_matrix_forge.led_matrix.constants import DEVICES
7
+ PLATFORM_DIRS = PlatformDirs("IS-Matrix-Forge", "Inspyre Softworks")
8
+
9
+
10
+ APP_DIR = PLATFORM_DIRS.user_data_path
11
+ MEMORY_FILE = APP_DIR / 'memory.ini'
12
+
13
+
14
+ MEMORY_FILE_TEMPLATE = {
15
+ 'first_run': True
16
+ }
17
+
18
+ first_run = False
19
+
20
+ APP_DIR.mkdir(parents=True, exist_ok=True)
21
+
22
+ if not MEMORY_FILE.exists():
23
+ MEMORY_FILE.write_text(json.dumps(MEMORY_FILE_TEMPLATE))
24
+
25
+
26
+ def get_first_run():
27
+ global first_run
28
+ with open(MEMORY_FILE, 'r') as f:
29
+ memory = json.load(f)
30
+ first_run = memory['first_run']
31
+ return first_run
32
+
33
+
34
+ def set_first_run():
35
+ global first_run
36
+ with open(MEMORY_FILE, 'r') as f:
37
+ memory = json.load(f)
38
+
39
+ memory['first_run'] = False
40
+
41
+ with open(MEMORY_FILE, 'w') as f:
42
+ json.dump(memory, f)
43
+
44
+
45
+ def process_first_run():
46
+ global first_run
47
+ fr = get_first_run()
48
+
49
+ if fr:
50
+ controllers = get_controllers(threaded=True)
51
+ scroll_text_on_multiple_matrices(controllers, 'Welcome!', threaded=True)
52
+
53
+ set_first_run()
54
+
55
+
56
+ process_first_run()
57
+
6
58
 
7
- return [LEDMatrixController(device, 100) for device in DEVICES]
@@ -8,77 +8,12 @@ Project:
8
8
  led-matrix-battery
9
9
 
10
10
  File:
11
- is_matrix_forge/led_matrix/commands/__init__.py
11
+ is_matrix_forge/led_matrix/commands/grid.py
12
12
 
13
13
 
14
14
  Description:
15
15
 
16
16
 
17
17
  """
18
- from typing import List, Optional, ByteString
19
18
 
20
- import serial
21
- from serial.tools.list_ports_common import ListPortInfo
22
19
 
23
- from is_matrix_forge.led_matrix.constants import RESPONSE_SIZE, FWK_MAGIC
24
- from is_matrix_forge.led_matrix.helpers import disconnect_dev
25
-
26
-
27
- def send_command_raw(dev: ListPortInfo, command: List[int], with_response: bool = False, response_size: Optional[int] = None) -> Optional[ByteString]:
28
- """
29
- Send a command to the device using a new serial connection.
30
-
31
- Args:
32
- dev (ListPortInfo): The device to send the command to.
33
- command (List[int]): The command to send.
34
- with_response (bool, optional): Whether to wait for a response from the device. Defaults to False.
35
- response_size (Optional[int], optional): The size of the response to expect. Defaults to None.
36
-
37
- Returns:
38
- Optional[ByteString]: The response from the device, if any, or None if no response or an error occurred.
39
-
40
- Raises:
41
- IOError, OSError: If there is an error communicating with the device.
42
- """
43
- # print(f"Sending command: {command}")
44
- res_size = response_size or RESPONSE_SIZE
45
- try:
46
- with serial.Serial(dev.device, 115200) as s:
47
- s.write(command)
48
-
49
- return s.read(res_size) if with_response else None
50
- except (IOError, OSError) as _ex:
51
- disconnect_dev(dev.device)
52
- return None
53
- # print("Error: ", ex)
54
-
55
-
56
- def send_command(
57
- dev: ListPortInfo,
58
- command: int,
59
- parameters: Optional[List[int]] = None,
60
- with_response: bool = False
61
- ) -> Optional[ByteString]:
62
- """
63
- Send a command to the device using a new serial connection.
64
-
65
- Parameters:
66
- dev (ListPortInfo):
67
- The device to send the command to.
68
-
69
- command (int):
70
- The command to send.
71
-
72
- parameters (Optional[List[int]], optional):
73
- The parameters to send with the command. Defaults to None.
74
-
75
- with_response (bool, optional):
76
- Whether to wait for a response from the device. Defaults to False.
77
-
78
- Returns:
79
- Optional[ByteString]:
80
- The response from the device, if any, or None if no response or an error occurred.
81
- """
82
- if parameters is None:
83
- parameters = []
84
- return send_command_raw(dev, FWK_MAGIC + [command] + parameters, with_response)
@@ -11,6 +11,7 @@ This module defines various constants used throughout the application, including
11
11
  """
12
12
  from is_matrix_forge.common.dirs import PRESETS_DIR
13
13
 
14
+
14
15
  # Hardware identifiers
15
16
  FWK_MAGIC = [0x32, 0xAC]
16
17
  PID = 0x20