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,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)
|