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,130 @@
1
+ from __future__ import annotations
2
+
3
+ import threading
4
+ from typing import Any, List
5
+
6
+ from inspyre_toolbox.chrono import sleep as ist_sleep
7
+ from serial.tools.list_ports_common import ListPortInfo
8
+
9
+ from is_matrix_forge.common.helpers import coerce_to_int
10
+ from is_matrix_forge.led_matrix.helpers.device import get_devices
11
+ from is_matrix_forge.led_matrix.constants import HEIGHT, WIDTH
12
+ from is_matrix_forge.led_matrix.display.helpers import render_matrix
13
+ from is_matrix_forge.led_matrix.errors import MalformedGridError
14
+
15
+
16
+ def get_grid_spec_from_animation_file(path: str, frame_number: int) -> List[List[int]]:
17
+ pass
18
+
19
+
20
+ def generate_blank_grid(
21
+ width: int = WIDTH,
22
+ height: int = HEIGHT,
23
+ fill_value: int = 0
24
+ ) -> List[List[int]]:
25
+ """
26
+ Generate a blank grid of the specified width and height.
27
+
28
+ A blank grid is a list of lists of 0s, where each inner list represents a row of the grid.
29
+
30
+ Parameters:
31
+ width (int):
32
+ The width (in pixels) of the grid. Default is 9.
33
+
34
+ height (int):
35
+ The height (in pixels) of the grid. Default is 34.
36
+
37
+ fill_value (int):
38
+ The value to fill the grid with:
39
+
40
+ - Default is 0.
41
+ - Accepts 0 or 1:
42
+ - 0 for no LEDs on.
43
+ - 1 for all LEDs on.
44
+
45
+ Returns:
46
+ List[List[int]]:
47
+ A blank grid of the specified width and height.
48
+
49
+ Raises:
50
+ TypeError:
51
+ If width or height is not an int.
52
+ """
53
+ if not isinstance(width, int):
54
+ width = coerce_to_int(width)
55
+ if width is None:
56
+ raise TypeError(f"width must be an int, not {type(width)}")
57
+
58
+ if not isinstance(height, int):
59
+ height = coerce_to_int(height)
60
+ if height is None:
61
+ raise TypeError(f"height must be an int, not {type(height)}")
62
+
63
+ if not isinstance(fill_value, int):
64
+ fill_value = coerce_to_int(fill_value)
65
+ if fill_value is None:
66
+ raise TypeError(f"fill_value must be an int, not {type(fill_value)}")
67
+
68
+ if fill_value not in (0, 1):
69
+ raise ValueError(f"fill_value must be 0 or 1, not {fill_value}")
70
+
71
+ return [
72
+ [fill_value for _ in range(height)]
73
+ for _ in range(width)
74
+ ]
75
+
76
+
77
+ def is_valid_grid(grid, width, height):
78
+
79
+ # column-major: width columns of height rows each
80
+ return (
81
+ isinstance(grid, list)
82
+ and len(grid) == width
83
+ and all(
84
+ isinstance(col, list)
85
+ and len(col) == height
86
+ and all(cell in (0,1) for cell in col)
87
+ for col in grid
88
+ )
89
+ )
90
+
91
+
92
+
93
+
94
+ def hold_pattern(dev, grid: List[List[int]], reapply_interval: float = 55.00) -> None:
95
+ """
96
+ Hold the state of the LED matrix indefinitely, only updating the display every `reapply_interval` seconds.
97
+
98
+ This is useful for maintaining a static display on the LED matrix without having a constant refresh/computation
99
+ load.
100
+
101
+ Parameters:
102
+ dev (ListPortInfo):
103
+ The serial com device/port to use.
104
+
105
+ grid (List[List[int]])::
106
+ The grid to display on the LED matrix.
107
+
108
+ reapply_interval (Union[float, int]):
109
+ The interval in seconds at which to reapply the grid state to the LED matrix. Default is 55 seconds,
110
+ maximum is 60 (the number of seconds before the matrix turns all the LEDs off unless a new state is
111
+ applied)
112
+
113
+ Returns:
114
+ None
115
+ """
116
+ if not isinstance(dev, ListPortInfo):
117
+ raise TypeError(f"dev must be a ListPortInfo object, not {type(dev)}")
118
+
119
+ if not isinstance(grid, list) or not is_valid_grid(grid, 9, 34):
120
+ raise MalformedGridError(f"grid must be a 9x34 list of 0/1, not {type(grid)}")
121
+
122
+ def worker(interval):
123
+ global running
124
+ running.state = True
125
+ while running:
126
+ ist_sleep(interval)
127
+ render_matrix(dev, grid)
128
+
129
+ thread = threading.Thread(target=worker, args=(reapply_interval,), daemon=True)
130
+ thread.start()
@@ -0,0 +1,64 @@
1
+ """
2
+ Grid presets module for LED Matrix display.
3
+
4
+ Author:
5
+ Inspyre Softworks
6
+
7
+ Project:
8
+ led-matrix-battery
9
+
10
+ File:
11
+ is_matrix_forge/led_matrix/display/grid/presets/__init__.py
12
+
13
+ Description:
14
+ This module provides functionality for managing grid presets for the LED matrix display.
15
+ It includes a manifest of available presets and validation functions.
16
+ """
17
+ from pathlib import Path
18
+ from typing import Optional
19
+
20
+ from is_matrix_forge.common.helpers import verify_checksum
21
+ from is_matrix_forge.led_matrix.constants import PRESETS_DIR
22
+ from is_matrix_forge.led_matrix.display.grid.presets.manifest import GridPresetManifest
23
+
24
+
25
+ PRESETS_MANIFEST = GridPresetManifest(PRESETS_DIR / 'manifest.json')
26
+
27
+
28
+ def validate_preset_files(preset_dir_path: Optional[Path] = None, manifest: Optional[GridPresetManifest] = None) -> bool:
29
+ """
30
+ Validate that all preset files in the manifest exist and are valid.
31
+
32
+ Args:
33
+ preset_dir_path (Optional[Path], optional):
34
+ The directory containing preset files. Defaults to PRESETS_DIR if None.
35
+ manifest (Optional[GridPresetManifest], optional):
36
+ The manifest to validate against. Defaults to PRESETS_MANIFEST if None.
37
+
38
+ Returns:
39
+ bool: True if all preset files are valid, False otherwise.
40
+
41
+ Note:
42
+ Validation checks that each file listed in the manifest exists in the
43
+ preset directory and that its checksum matches the value stored in the
44
+ manifest.
45
+ """
46
+
47
+ preset_dir_path = preset_dir_path or PRESETS_DIR
48
+ manifest = manifest or PRESETS_MANIFEST
49
+
50
+ if not isinstance(preset_dir_path, Path):
51
+ preset_dir_path = Path(preset_dir_path)
52
+
53
+ all_valid = True
54
+
55
+ for filename, checksum in manifest.as_dict().items():
56
+ file_path = preset_dir_path / filename
57
+ if not file_path.exists():
58
+ all_valid = False
59
+ break
60
+ if not verify_checksum(checksum, file_path):
61
+ all_valid = False
62
+ break
63
+
64
+ return all_valid
@@ -0,0 +1,172 @@
1
+ """
2
+ Author:
3
+ Inspyre Softworks
4
+
5
+ Project:
6
+ led-matrix-battery
7
+
8
+ File:
9
+ is_matrix_forge/led_matrix/display/grid/presets/manifest.py
10
+
11
+ Description:
12
+ Manages a JSON-based manifest mapping filenames to checksums,
13
+ wrapped with metadata such as build version and save date.
14
+ """
15
+
16
+ import json
17
+ import hashlib
18
+ from pathlib import Path
19
+ from typing import Optional, Dict, Union
20
+ from datetime import datetime
21
+
22
+ from easy_exit_calls import ExitCallHandler
23
+ from is_matrix_forge.dev_tools.debug import is_debug_mode
24
+
25
+ ECH = ExitCallHandler()
26
+
27
+
28
+ def calculate_checksum(file_path: Union[str, Path]) -> str:
29
+ """
30
+ Calculates a SHA256 checksum for a file.
31
+
32
+ Parameters:
33
+ file_path (Union[str, Path]): Path to the file.
34
+
35
+ Returns:
36
+ str: Hex digest of the file's SHA256 checksum.
37
+ """
38
+ hash_sha256 = hashlib.sha256()
39
+ with open(file_path, 'rb') as f:
40
+ for chunk in iter(lambda: f.read(4096), b''):
41
+ hash_sha256.update(chunk)
42
+ return hash_sha256.hexdigest()
43
+
44
+
45
+ class GridPresetManifest:
46
+ """
47
+ Represents a manifest that maps preset filenames to their known checksums,
48
+ along with versioning and date metadata.
49
+
50
+ Automatically saves the manifest to disk when the program exits (in debug mode).
51
+ """
52
+
53
+ DEFAULT_VERSION = "dev"
54
+
55
+ def __init__(self, manifest_path: Union[str, Path]):
56
+ self.manifest_path = Path(manifest_path)
57
+ self._manifest_dict: Dict[str, str] = {}
58
+ self._meta: Dict[str, str] = {
59
+ 'version': self.DEFAULT_VERSION,
60
+ 'date': datetime.now().isoformat()
61
+ }
62
+
63
+ self._load()
64
+
65
+ if is_debug_mode():
66
+ ECH.register_handler(self._save)
67
+
68
+ def _load(self):
69
+ """
70
+ Loads the manifest and metadata from disk if it exists.
71
+ """
72
+ if self.manifest_path.exists():
73
+ try:
74
+ with open(self.manifest_path, 'r', encoding='utf-8') as f:
75
+ data = json.load(f)
76
+
77
+ self._meta = data.get('meta', self._meta)
78
+ manifest = data.get('manifest', [])
79
+
80
+ self._manifest_dict = {
81
+ list(item.keys())[0]: list(item.values())[0]
82
+ for item in manifest
83
+ }
84
+
85
+ except Exception as e:
86
+ print(f"[GridPresetManifest] Failed to load manifest: {e}")
87
+ else:
88
+ self._manifest_dict = {}
89
+
90
+ def _save(self):
91
+ """
92
+ Saves the manifest and metadata to disk in JSON format.
93
+ """
94
+ try:
95
+ self._meta['date'] = datetime.now().isoformat()
96
+ data = {
97
+ 'meta': self._meta,
98
+ 'manifest': [{k: v} for k, v in self._manifest_dict.items()]
99
+ }
100
+
101
+ with open(self.manifest_path, 'w', encoding='utf-8') as f:
102
+ json.dump(data, f, indent=2)
103
+ except Exception as e:
104
+ print(f"[GridPresetManifest] Failed to save manifest: {e}")
105
+
106
+ def get_checksum(self, filename: str) -> Optional[str]:
107
+ """
108
+ Returns the stored checksum for a given file.
109
+
110
+ Parameters:
111
+ filename (str): Name of the file to query.
112
+
113
+ Returns:
114
+ Optional[str]: Checksum string or None.
115
+ """
116
+ return self._manifest_dict.get(filename)
117
+
118
+ def add(self, filename: str, checksum: str):
119
+ """
120
+ Adds or updates a checksum entry in the manifest.
121
+
122
+ Parameters:
123
+ filename (str): File name.
124
+ checksum (str): Checksum string.
125
+ """
126
+ self._manifest_dict[filename] = checksum
127
+
128
+ def scan(self, dir_path: Union[str, Path]):
129
+ """
130
+ Scans a directory for files and adds their checksums to the manifest.
131
+
132
+ Parameters:
133
+ dir_path (Union[str, Path]): Directory path.
134
+ """
135
+ dir_path = Path(dir_path)
136
+ if not dir_path.is_dir():
137
+ raise ValueError(f"{dir_path} is not a valid directory")
138
+
139
+ for file in dir_path.iterdir():
140
+ if file.is_file():
141
+ checksum = calculate_checksum(file)
142
+ self.add(file.name, checksum)
143
+
144
+ def __getitem__(self, filename: str) -> Optional[str]:
145
+ return self.get_checksum(filename)
146
+
147
+ def __setitem__(self, filename: str, checksum: str):
148
+ self.add(filename, checksum)
149
+
150
+ def __contains__(self, filename: str) -> bool:
151
+ return filename in self._manifest_dict
152
+
153
+ def as_dict(self) -> Dict[str, str]:
154
+ """
155
+ Returns a copy of the manifest dictionary.
156
+ """
157
+ return self._manifest_dict.copy()
158
+
159
+ def get_meta(self) -> Dict[str, str]:
160
+ """
161
+ Returns a copy of the metadata dictionary.
162
+ """
163
+ return self._meta.copy()
164
+
165
+ def set_version(self, version: str):
166
+ """
167
+ Sets the version in metadata.
168
+
169
+ Parameters:
170
+ version (str): Version string.
171
+ """
172
+ self._meta['version'] = version
@@ -0,0 +1,110 @@
1
+ import time
2
+ import math
3
+ from is_matrix_forge.led_matrix.commands.map import CommandVals
4
+ from is_matrix_forge.led_matrix.commands import send_command
5
+
6
+
7
+ def keep_image(
8
+ controller,
9
+ breathe: bool = False,
10
+ breathe_fps: float = 30,
11
+ interval: float = 0.1,
12
+ time_between_refreshes: float = 50
13
+ ):
14
+ """
15
+ Continuously hold the current image on-screen, optionally breathing the brightness.
16
+
17
+ Args:
18
+ controller: an LEDMatrixController-like object with:
19
+ - .brightness (0-100)
20
+ - .keep_image (bool flag you can set to False to exit)
21
+ - .draw_grid(grid) or .render_current() to re-send the image
22
+ - .set_brightness(value)
23
+ breathe: if True, oscillate brightness in a sinusoidal “breathing” pattern
24
+ breathe_fps: how many brightness steps per second when breathing
25
+ interval: seconds between each brightness step (if breathing)
26
+ time_between_refreshes: seconds to sleep after one full breath cycle
27
+ (or between redraws if not breathing)
28
+ """
29
+ base_brightness = controller.brightness
30
+ t = 0.0
31
+ period = 1.0 # one full sine cycle = 1 second
32
+ step = 1.0 / breathe_fps
33
+
34
+ while controller.keep_image:
35
+ if breathe:
36
+ # Compute a smooth sine-based oscillation from 0→1→0
37
+ phase = (t % period) / period # 0.0→1.0
38
+ fade = 0.5 * (1 - math.cos(2 * math.pi * phase))
39
+ bri = int(round(base_brightness * fade))
40
+ controller.set_brightness(bri)
41
+ time.sleep(interval)
42
+ t += interval
43
+ else:
44
+ # just hold at base brightness
45
+ controller.set_brightness(base_brightness)
46
+ time.sleep(time_between_refreshes)
47
+
48
+ # re-draw the current image (whatever the controller is holding)
49
+ # assume controller.draw_current() or similar:
50
+ try:
51
+ controller.render_current()
52
+ except AttributeError:
53
+ # fallback to a generic draw call
54
+ controller.draw()
55
+
56
+
57
+ def light_leds(dev, leds):
58
+ """Light a specific number of LEDs"""
59
+ # Initialize a byte array with all LEDs off
60
+ vals = [0x00 for _ in range(39)]
61
+
62
+ # Calculate how many complete bytes we need to fill (each byte = 8 LEDs)
63
+ complete_bytes = int(leds / 8)
64
+
65
+ # Set all complete bytes to 0xFF (all 8 bits on)
66
+ for byte in range(complete_bytes):
67
+ vals[byte] = 0xFF
68
+
69
+ # Handle the remaining LEDs (less than 8) in the last partial byte
70
+ remaining_leds = leds % 8
71
+
72
+ # For each remaining LED, set the corresponding bit in the last byte
73
+ # This creates a binary pattern like 00011111 for 5 remaining LEDs
74
+ for i in range(remaining_leds):
75
+ vals[complete_bytes] += 1 << i
76
+
77
+ # Send the command to the device to display the pattern
78
+ send_command(dev, CommandVals.Draw, vals)
79
+
80
+
81
+ def render_matrix(dev, matrix):
82
+ """Show a black/white matrix
83
+ Send everything in a single command"""
84
+ # Initialize a byte array to hold the binary representation of the matrix
85
+ # 39 bytes = 312 bits, which is enough for 9x34 = 306 pixels
86
+ vals = [0x00 for _ in range(39)]
87
+
88
+ # Iterate through each position in the 9x34 matrix
89
+ for x in range(9):
90
+ for y in range(34):
91
+ # Convert 2D coordinates to a linear index
92
+ # The matrix is stored in column-major order (y changes faster than x)
93
+ i = x + 9 * y
94
+
95
+ # If the pixel at this position is "on" (non-zero)
96
+ if matrix[x][y]:
97
+ # Calculate which byte in the vals array this pixel belongs to
98
+ byte_index = int(i / 8)
99
+
100
+ # Calculate which bit within that byte represents this pixel
101
+ bit_position = i % 8
102
+
103
+ # Set the corresponding bit in the appropriate byte
104
+ # This efficiently packs 8 pixels into each byte
105
+ vals[byte_index] = vals[byte_index] | (1 << bit_position)
106
+
107
+ # Send the packed binary data to the device
108
+ send_command(dev, CommandVals.Draw, vals)
109
+
110
+
@@ -0,0 +1,33 @@
1
+ """
2
+
3
+
4
+ Author:
5
+ Inspyre Softworks
6
+
7
+ Project:
8
+ led-matrix-battery
9
+
10
+ File:
11
+ ${DIR_PATH}/${FILE_NAME}
12
+
13
+
14
+ Description:
15
+ $DESCRIPTION
16
+
17
+ """
18
+ from is_matrix_forge.led_matrix.commands.map import CommandVals
19
+ from is_matrix_forge.led_matrix.constants import FWK_MAGIC
20
+
21
+
22
+ def send_col(dev, s, x, vals):
23
+ """Stage greyscale values for a single column. Must be committed with commit_cols()"""
24
+ command = FWK_MAGIC + [CommandVals.StageGreyCol, x] + vals
25
+ send_serial(dev, s, command)
26
+
27
+
28
+ def commit_cols(dev, s):
29
+ """Commit the changes from sending individual cols with send_col(), displaying the matrix.
30
+ This makes sure that the matrix isn't partially updated."""
31
+ from ..hardware import FWK_MAGIC
32
+ command = FWK_MAGIC + [CommandVals.DrawGreyColBuffer, 0x00]
33
+ send_serial(dev, s, command)