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,37 @@
|
|
|
1
|
+
from .base import LEDMatrixControllerError
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class GridDefinitionError(LEDMatrixControllerError, ValueError):
|
|
5
|
+
"""
|
|
6
|
+
Base class for all grid definition errors.
|
|
7
|
+
|
|
8
|
+
A "grid definition error" is an error that occurs while defining a grid for the LED matrix, either in translation
|
|
9
|
+
from data to the LED matrix or in the grid itself. This includes errors such as malformed grids, invalid grid
|
|
10
|
+
sizes, etc.
|
|
11
|
+
|
|
12
|
+
Provided Subclasses:
|
|
13
|
+
- MalformedGridError:
|
|
14
|
+
Raised when the grid structure does not match expectations. This can include issues such as incorrect
|
|
15
|
+
dimensions, missing elements, or other structural problems.
|
|
16
|
+
"""
|
|
17
|
+
default_message = 'An error has occurred within the grid definition!'
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class MalformedGridError(GridDefinitionError):
|
|
21
|
+
"""
|
|
22
|
+
Raised when the grid is malformed.
|
|
23
|
+
|
|
24
|
+
Class Attributes:
|
|
25
|
+
default_message (str):
|
|
26
|
+
**Read-Only**. The default message for the error.
|
|
27
|
+
"""
|
|
28
|
+
default_message = 'The grid is malformed!'
|
|
29
|
+
|
|
30
|
+
def __init__(self, more_info: str = None, **kwargs) -> None:
|
|
31
|
+
if more_info is not None:
|
|
32
|
+
self.default_message = f"{self.default_message}\n\n Additional information from caller:\n {more_info}"
|
|
33
|
+
|
|
34
|
+
super().__init__(message=self.default_message, **kwargs)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
__all__ = ['GridDefinitionError', 'MalformedGridError']
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
from inspyre_toolbox.exceptional import CustomRootException
|
|
2
|
+
from .base import LEDMatrixControllerError
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class MatrixError(LEDMatrixControllerError):
|
|
6
|
+
"""
|
|
7
|
+
Base class for all LED matrix errors.
|
|
8
|
+
|
|
9
|
+
Subclasses of this exception are used to indicate specific errors that can occur while interacting with the LED
|
|
10
|
+
matrix hardware.
|
|
11
|
+
|
|
12
|
+
Provided Subclasses:
|
|
13
|
+
- MatrixConnectionError:
|
|
14
|
+
Raised when the LED matrix cannot be connected to.
|
|
15
|
+
|
|
16
|
+
- MatrixInUseError:
|
|
17
|
+
Raised when the LED matrix is already in use by another process.
|
|
18
|
+
"""
|
|
19
|
+
default_message = 'An error occurred while interacting with the LED matrix!'
|
|
20
|
+
|
|
21
|
+
def __init__(self, message: str = None, device_name: str = None) -> None:
|
|
22
|
+
full_msg = self.default_message
|
|
23
|
+
|
|
24
|
+
if device_name is not None:
|
|
25
|
+
full_msg += f' (Device: {device_name})'
|
|
26
|
+
|
|
27
|
+
if message is not None:
|
|
28
|
+
full_msg = f'{full_msg}\n\n Additional Info:\n{" " * 4}{message}'
|
|
29
|
+
|
|
30
|
+
super().__init__(full_msg)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class MatrixConnectionError(MatrixError):
|
|
34
|
+
"""
|
|
35
|
+
Raised when the LED matrix cannot be connected to.
|
|
36
|
+
"""
|
|
37
|
+
default_message = 'Unable to connect to the LED matrix!'
|
|
38
|
+
|
|
39
|
+
def __init__(self, message: str = None) -> None:
|
|
40
|
+
if message is None:
|
|
41
|
+
message = self.default_message
|
|
42
|
+
else:
|
|
43
|
+
message = f'{self.default_message}\n\n Additional Info:\n{" " * 4}{message}'
|
|
44
|
+
|
|
45
|
+
super().__init__(message)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class MatrixInUseError(MatrixConnectionError):
|
|
49
|
+
"""
|
|
50
|
+
Raised when the LED matrix is already in use by another process.
|
|
51
|
+
"""
|
|
52
|
+
message = 'The LED matrix is already in use, and cannot be used by this program! Please close the other ' \
|
|
53
|
+
'instance/process and try again!'
|
|
54
|
+
|
|
55
|
+
def __init__(self):
|
|
56
|
+
super().__init__(self.message)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class InvalidBrightnessError(MatrixError):
|
|
60
|
+
"""
|
|
61
|
+
Raised when the brightness value provided is invalid.
|
|
62
|
+
"""
|
|
63
|
+
default_message = 'The brightness value provided is invalid! Value must be an integer between 0 and 255!'
|
|
64
|
+
|
|
65
|
+
def __init__(self, brightness: int):
|
|
66
|
+
message = f'{self.default_message}\n\n Brightness: {brightness}'
|
|
67
|
+
|
|
68
|
+
super().__init__(message)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from .base import CustomRootException
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class MiscError(CustomRootException):
|
|
5
|
+
pass
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ImplicitNameDerivationError(MiscError, ValueError):
|
|
9
|
+
"""
|
|
10
|
+
Raised by `freeze_setter` when it's used to decorate a property setter
|
|
11
|
+
whose name begins with a single underscore (e.g., `_my_prop`) without an
|
|
12
|
+
explicit `attr_name` being provided.
|
|
13
|
+
|
|
14
|
+
This error occurs because the default behavior of `freeze_setter` is to
|
|
15
|
+
derive the underlying private attribute name by prepending an underscore
|
|
16
|
+
to the setter's name. If the setter is `_my_prop`, this would implicitly
|
|
17
|
+
attempt to create `__my_prop`. Attributes starting with double underscores
|
|
18
|
+
trigger Python's name mangling, which can lead to unexpected behavior and
|
|
19
|
+
make the underlying attribute name less predictable if not explicitly intended.
|
|
20
|
+
|
|
21
|
+
To prevent this implicit and potentially confusing name derivation,
|
|
22
|
+
`freeze_setter` requires that you explicitly specify the `attr_name`
|
|
23
|
+
when decorating setters that start with a single underscore.
|
|
24
|
+
|
|
25
|
+
TL;DR: When using `freeze_setter` on a property setter named `_my_prop`,
|
|
26
|
+
you must explicitly specify the `attr_name` as "__my_prop" to avoid confusion
|
|
27
|
+
about how the underlying attribute will be named, and to confirm your intent.
|
|
28
|
+
"""
|
|
29
|
+
default_message = (
|
|
30
|
+
"Implicitly derived attribute names are not allowed for properties "
|
|
31
|
+
"whose names begin with a single underscore. Please provide an "
|
|
32
|
+
"`attr_name` argument."
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
def __init__(self, **kwargs):
|
|
36
|
+
super().__init__(message=self.default_message, **kwargs)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
import json
|
|
3
|
+
import importlib.resources
|
|
4
|
+
|
|
5
|
+
from is_matrix_forge.assets.font_map import FontMap
|
|
6
|
+
|
|
7
|
+
# Load the default font map shipped with the package using importlib.resources
|
|
8
|
+
with importlib.resources.open_text("is_matrix_forge.assets", "char_map.json", encoding="utf-8") as f:
|
|
9
|
+
_FONT_DATA = json.load(f)
|
|
10
|
+
FONT_MAP = FontMap(font_map=_FONT_DATA)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def convert_font(ch: str) -> List[int]:
|
|
14
|
+
"""Return the 5x6 glyph list for a single character."""
|
|
15
|
+
return FONT_MAP.lookup(ch, kind="character")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def convert_symbol(symbol: str) -> List[int]:
|
|
19
|
+
"""Return the glyph list for a named symbol."""
|
|
20
|
+
return FONT_MAP.lookup(symbol, kind="symbol")
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Hardware communication module for LED Matrix.
|
|
3
|
+
|
|
4
|
+
This module provides low-level functions for communicating with LED matrix hardware.
|
|
5
|
+
It includes functions for sending commands, controlling brightness, and other basic operations.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from is_matrix_forge.led_matrix.commands import send_command
|
|
9
|
+
from is_matrix_forge.led_matrix.commands.map import CommandVals
|
|
10
|
+
from is_matrix_forge.led_matrix.helpers.core.lazy_mod_load import LazyModuleLoader
|
|
11
|
+
from enum import IntEnum
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# Import the necessary functions from the controller module
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class PatternVals(IntEnum):
|
|
18
|
+
Percentage = 0x00
|
|
19
|
+
Gradient = 0x01
|
|
20
|
+
DoubleGradient = 0x02
|
|
21
|
+
DisplayLotus = 0x03
|
|
22
|
+
ZigZag = 0x04
|
|
23
|
+
FullBrightness = 0x05
|
|
24
|
+
DisplayPanic = 0x06
|
|
25
|
+
DisplayLotus2 = 0x07
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class Game(IntEnum):
|
|
29
|
+
Snake = 0x00
|
|
30
|
+
Pong = 0x01
|
|
31
|
+
Tetris = 0x02
|
|
32
|
+
GameOfLife = 0x03
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class GameOfLifeStartParam(IntEnum):
|
|
36
|
+
Currentmatrix = 0x00
|
|
37
|
+
Pattern1 = 0x01
|
|
38
|
+
Blinker = 0x02
|
|
39
|
+
Toad = 0x03
|
|
40
|
+
Beacon = 0x04
|
|
41
|
+
Glider = 0x05
|
|
42
|
+
|
|
43
|
+
def __str__(self):
|
|
44
|
+
return self.name.lower()
|
|
45
|
+
|
|
46
|
+
def __repr__(self):
|
|
47
|
+
return str(self)
|
|
48
|
+
|
|
49
|
+
@staticmethod
|
|
50
|
+
def argparse(s):
|
|
51
|
+
try:
|
|
52
|
+
return GameOfLifeStartParam[s.lower().capitalize()]
|
|
53
|
+
except KeyError:
|
|
54
|
+
return s
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class GameControlVal(IntEnum):
|
|
58
|
+
Up = 0
|
|
59
|
+
Down = 1
|
|
60
|
+
Left = 2
|
|
61
|
+
Right = 3
|
|
62
|
+
Quit = 4
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def disconnect_dev(dev):
|
|
66
|
+
"""
|
|
67
|
+
Disconnect the device from the system.
|
|
68
|
+
|
|
69
|
+
Parameters:
|
|
70
|
+
dev (str):
|
|
71
|
+
The device to disconnect.
|
|
72
|
+
"""
|
|
73
|
+
global DISCONNECTED_DEVS
|
|
74
|
+
if dev in DISCONNECTED_DEVS:
|
|
75
|
+
return
|
|
76
|
+
DISCONNECTED_DEVS.append(dev)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def bootloader_jump(dev):
|
|
80
|
+
"""Reboot into the bootloader to flash new firmware"""
|
|
81
|
+
send_command(dev, CommandVals.BootloaderReset, [0x00])
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def brightness(dev, b: int):
|
|
85
|
+
"""Adjust the brightness scaling of the entire screen."""
|
|
86
|
+
send_command(dev, CommandVals.Brightness, [b])
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def get_brightness(dev):
|
|
90
|
+
"""Adjust the brightness scaling of the entire screen."""
|
|
91
|
+
res = send_command(dev, CommandVals.Brightness, with_response=True)
|
|
92
|
+
return int(res[0])
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def get_version(dev):
|
|
96
|
+
"""Get the device's firmware version"""
|
|
97
|
+
res = send_command(dev, CommandVals.Version, with_response=True)
|
|
98
|
+
if not res:
|
|
99
|
+
return 'Unknown'
|
|
100
|
+
major = res[0]
|
|
101
|
+
minor = (res[1] & 0xF0) >> 4
|
|
102
|
+
patch = res[1] & 0xF
|
|
103
|
+
pre_release = res[2]
|
|
104
|
+
|
|
105
|
+
version = f"{major}.{minor}.{patch}"
|
|
106
|
+
if pre_release:
|
|
107
|
+
version += " (Pre-release)"
|
|
108
|
+
return version
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def send_serial(dev, s, command):
|
|
112
|
+
"""Send serial command by using existing serial connection"""
|
|
113
|
+
try:
|
|
114
|
+
s.write(command)
|
|
115
|
+
except (IOError, OSError) as _ex:
|
|
116
|
+
disconnect_dev(dev.device)
|
|
117
|
+
# print("Error: ", ex)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def animate(dev, b: bool):
|
|
121
|
+
"""Enable/disable animation"""
|
|
122
|
+
send_command(dev, CommandVals.Animate, [0x01 if b else 0x00])
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def get_animate(dev):
|
|
126
|
+
"""Check if animation is enabled"""
|
|
127
|
+
res = send_command(dev, CommandVals.Animate, with_response=True)
|
|
128
|
+
return bool(res[0])
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def get_pwm_freq(dev):
|
|
132
|
+
"""Adjust the brightness scaling of the entire screen."""
|
|
133
|
+
res = send_command(dev, CommandVals.PwmFreq, with_response=True)
|
|
134
|
+
freq = int(res[0])
|
|
135
|
+
if freq == 0:
|
|
136
|
+
return 29000
|
|
137
|
+
elif freq == 1:
|
|
138
|
+
return 3600
|
|
139
|
+
elif freq == 2:
|
|
140
|
+
return 1800
|
|
141
|
+
elif freq == 3:
|
|
142
|
+
return 900
|
|
143
|
+
else:
|
|
144
|
+
return None
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def pwm_freq(dev, freq):
|
|
148
|
+
"""Set the PWM frequency"""
|
|
149
|
+
if freq == "29kHz":
|
|
150
|
+
send_command(dev, CommandVals.PwmFreq, [0])
|
|
151
|
+
elif freq == "3.6kHz":
|
|
152
|
+
send_command(dev, CommandVals.PwmFreq, [1])
|
|
153
|
+
elif freq == "1.8kHz":
|
|
154
|
+
send_command(dev, CommandVals.PwmFreq, [2])
|
|
155
|
+
elif freq == "900Hz":
|
|
156
|
+
send_command(dev, CommandVals.PwmFreq, [3])
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def percentage(dev, p):
|
|
160
|
+
"""Fill a percentage of the screen. Bottom to top"""
|
|
161
|
+
send_command(dev, CommandVals.Pattern, [PatternVals.Percentage, p])
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def define_controllers(threaded=False):
|
|
165
|
+
from is_matrix_forge.led_matrix.controller.helpers import get_controllers
|
|
166
|
+
return get_controllers(threaded)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
# CONTROLLERS = define_controllers()
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import serial
|
|
2
|
+
from inspyre_toolbox.chrono.sleep import NegateSwitch
|
|
3
|
+
from inspyre_toolbox.path_man import provision_path
|
|
4
|
+
|
|
5
|
+
from serial.tools.list_ports_common import ListPortInfo
|
|
6
|
+
from threading import Thread
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Union, Optional, List, Any, ByteString
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def get_json_from_file(path: Union[str, Path]) -> Any:
|
|
14
|
+
"""
|
|
15
|
+
Load and parse a JSON file.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
path (Union[str, Path]): The path to the JSON file to load.
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
Any: The parsed JSON data.
|
|
22
|
+
|
|
23
|
+
Raises:
|
|
24
|
+
FileNotFoundError:
|
|
25
|
+
If the file does not exist.
|
|
26
|
+
|
|
27
|
+
IsADirectoryError:
|
|
28
|
+
If the path points to a directory.
|
|
29
|
+
|
|
30
|
+
json.JSONDecodeError:
|
|
31
|
+
If the file contains invalid JSON.
|
|
32
|
+
"""
|
|
33
|
+
path = provision_path(path)
|
|
34
|
+
if not path.exists():
|
|
35
|
+
raise FileNotFoundError(f'Preset file not found: {path}')
|
|
36
|
+
|
|
37
|
+
if not path.is_file():
|
|
38
|
+
raise IsADirectoryError(f'Preset file is a directory: {path}')
|
|
39
|
+
|
|
40
|
+
with open(path, 'r') as f:
|
|
41
|
+
return json.load(f)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def load_from_file(
|
|
45
|
+
path: Union[str, Path],
|
|
46
|
+
expected_width: Optional[int] = None,
|
|
47
|
+
expected_height: Optional[int] = None,
|
|
48
|
+
fallback_duration: Optional[Union[int, float]] = None
|
|
49
|
+
) -> List['Frame']:
|
|
50
|
+
"""
|
|
51
|
+
Load a list of frames from a file.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
path (Union[str, Path]): The path to the file to load.
|
|
55
|
+
expected_width (Optional[int]): The expected width of the frames in the file. Defaults to 9.
|
|
56
|
+
expected_height (Optional[int]): The expected height of the frames in the file. Defaults to 34.
|
|
57
|
+
fallback_duration (Optional[Union[int, float]]): The duration of the frames in the file.
|
|
58
|
+
Defaults to 0.33 (1/3 of a second).
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
List[Frame]: A list of frames loaded from the file.
|
|
62
|
+
|
|
63
|
+
Raises:
|
|
64
|
+
FileNotFoundError: If the file does not exist.
|
|
65
|
+
IsADirectoryError: If the path points to a directory.
|
|
66
|
+
ValueError: If the file does not contain a valid JSON array.
|
|
67
|
+
"""
|
|
68
|
+
from is_matrix_forge.led_matrix.display.animations.frame.helpers import migrate_frame
|
|
69
|
+
from is_matrix_forge.led_matrix.display.grid.helpers import is_valid_grid
|
|
70
|
+
|
|
71
|
+
width = expected_width or 9
|
|
72
|
+
height = expected_height or 34
|
|
73
|
+
|
|
74
|
+
data = get_json_from_file(path)
|
|
75
|
+
|
|
76
|
+
if not isinstance(data, list):
|
|
77
|
+
raise ValueError(f"File {path} does not contain a valid JSON array")
|
|
78
|
+
|
|
79
|
+
frames = []
|
|
80
|
+
for entry in data:
|
|
81
|
+
|
|
82
|
+
if not isinstance(entry, dict) and isinstance(entry, list) and is_valid_grid(entry, width, height):
|
|
83
|
+
entry = migrate_frame(entry, fallback_duration)
|
|
84
|
+
entry['duration'] = fallback_duration or .33
|
|
85
|
+
|
|
86
|
+
frames.append(entry)
|
|
87
|
+
|
|
88
|
+
return data
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def disconnect_dev(dev: str) -> None:
|
|
92
|
+
"""
|
|
93
|
+
Disconnect the device from the system.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
dev (str): The device to disconnect.
|
|
97
|
+
|
|
98
|
+
Note:
|
|
99
|
+
This function adds the device to a global list of disconnected devices
|
|
100
|
+
to prevent further attempts to communicate with it.
|
|
101
|
+
"""
|
|
102
|
+
global DISCONNECTED_DEVS
|
|
103
|
+
if dev in DISCONNECTED_DEVS:
|
|
104
|
+
return
|
|
105
|
+
|
|
106
|
+
DISCONNECTED_DEVS.append(dev)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def send_serial(dev: ListPortInfo, s: 'serial.Serial', command: ByteString) -> None:
|
|
110
|
+
"""
|
|
111
|
+
Send serial command by using an existing serial connection.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
dev (ListPortInfo): The device to send the command to.
|
|
115
|
+
s (serial.Serial): The serial connection to use.
|
|
116
|
+
command (ByteString): The command to send.
|
|
117
|
+
|
|
118
|
+
Raises:
|
|
119
|
+
IOError, OSError: If there is an error communicating with the device.
|
|
120
|
+
"""
|
|
121
|
+
try:
|
|
122
|
+
s.write(command)
|
|
123
|
+
except (IOError, OSError) as _ex:
|
|
124
|
+
disconnect_dev(dev.device)
|
|
125
|
+
# print("Error: ", ex)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def identify_devices(devices: Optional[List[ListPortInfo]] = None) -> None:
|
|
129
|
+
"""
|
|
130
|
+
Identify LED matrix devices by flashing an identification message on each
|
|
131
|
+
connected matrix. If no specific devices are provided, all detected devices
|
|
132
|
+
are used.
|
|
133
|
+
|
|
134
|
+
Parameters:
|
|
135
|
+
devices (Optional[List[ListPortInfo]], optional):
|
|
136
|
+
List of devices to identify. Defaults to ``None`` which will use all
|
|
137
|
+
detected devices.
|
|
138
|
+
"""
|
|
139
|
+
from is_matrix_forge.led_matrix.controller.controller import LEDMatrixController
|
|
140
|
+
|
|
141
|
+
if devices is None:
|
|
142
|
+
from is_matrix_forge.led_matrix.helpers.device import DEVICES
|
|
143
|
+
devices = DEVICES
|
|
144
|
+
|
|
145
|
+
import logging
|
|
146
|
+
|
|
147
|
+
controllers = [LEDMatrixController(dev, 100) for dev in devices]
|
|
148
|
+
|
|
149
|
+
def safe_identify(controller):
|
|
150
|
+
try:
|
|
151
|
+
controller.identify()
|
|
152
|
+
except Exception as e:
|
|
153
|
+
logging.exception(f"Exception in identify thread for controller {controller}: {e}")
|
|
154
|
+
|
|
155
|
+
threads = []
|
|
156
|
+
for controller in controllers:
|
|
157
|
+
t = Thread(target=safe_identify, args=(controller,))
|
|
158
|
+
threads.append(t)
|
|
159
|
+
t.start()
|
|
160
|
+
|
|
161
|
+
for t in threads:
|
|
162
|
+
t.join()
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
running = NegateSwitch(False)
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import types
|
|
3
|
+
import logging
|
|
4
|
+
import pkgutil
|
|
5
|
+
from importlib import import_module
|
|
6
|
+
from typing import Set, Optional, Union
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class LazyModuleLoader:
|
|
10
|
+
"""
|
|
11
|
+
A utility to lazily load submodules and avoid circular imports.
|
|
12
|
+
|
|
13
|
+
Parameters
|
|
14
|
+
----------
|
|
15
|
+
parent_module : str
|
|
16
|
+
The fully qualified name of the module (e.g., 'is_matrix_forge.led_matrix').
|
|
17
|
+
|
|
18
|
+
submodules : Optional[Set[str]]
|
|
19
|
+
A set of submodules to lazily load. If not provided, it will be auto-discovered.
|
|
20
|
+
|
|
21
|
+
fallback_module : Optional[str]
|
|
22
|
+
Optional fallback module to delegate attribute lookups to.
|
|
23
|
+
|
|
24
|
+
enable_logging : bool
|
|
25
|
+
Enable logging for import errors.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __init__(
|
|
29
|
+
self,
|
|
30
|
+
parent_module: str,
|
|
31
|
+
submodules: Optional[Set[str]] = None,
|
|
32
|
+
fallback_module: Optional[str] = None,
|
|
33
|
+
enable_logging: bool = True
|
|
34
|
+
):
|
|
35
|
+
self.module_name = parent_module
|
|
36
|
+
self.submodules = submodules or self._discover_submodules()
|
|
37
|
+
self.fallback_module = fallback_module
|
|
38
|
+
self.enable_logging = enable_logging
|
|
39
|
+
self.logger = logging.getLogger(__name__)
|
|
40
|
+
self._install()
|
|
41
|
+
|
|
42
|
+
def _discover_submodules(self) -> Set[str]:
|
|
43
|
+
try:
|
|
44
|
+
module = import_module(self.module_name)
|
|
45
|
+
path = module.__path__
|
|
46
|
+
return {name for _, name, is_pkg in pkgutil.iter_modules(path)}
|
|
47
|
+
except Exception as e:
|
|
48
|
+
if self.enable_logging:
|
|
49
|
+
self.logger.error(f"Error discovering submodules for '{self.module_name}': {e}")
|
|
50
|
+
return set()
|
|
51
|
+
|
|
52
|
+
def _install(self):
|
|
53
|
+
mod = sys.modules[self.module_name]
|
|
54
|
+
mod.__getattr__ = self.__getattr__
|
|
55
|
+
mod.__dir__ = self.__dir__
|
|
56
|
+
mod.__all__ = sorted(self.submodules)
|
|
57
|
+
|
|
58
|
+
def __getattr__(self, name):
|
|
59
|
+
if name in self.submodules:
|
|
60
|
+
try:
|
|
61
|
+
full_name = f"{self.module_name}.{name}"
|
|
62
|
+
module = import_module(full_name)
|
|
63
|
+
sys.modules[full_name] = module
|
|
64
|
+
return module
|
|
65
|
+
except ImportError as e:
|
|
66
|
+
if self.enable_logging:
|
|
67
|
+
self.logger.error(f"Failed to import submodule '{name}': {e}")
|
|
68
|
+
raise
|
|
69
|
+
elif self.fallback_module:
|
|
70
|
+
base = import_module(self.fallback_module)
|
|
71
|
+
try:
|
|
72
|
+
return getattr(base, name)
|
|
73
|
+
except AttributeError as e:
|
|
74
|
+
if self.enable_logging:
|
|
75
|
+
self.logger.error(
|
|
76
|
+
f"Fallback '{self.fallback_module}' has no attribute '{name}': {e}"
|
|
77
|
+
)
|
|
78
|
+
raise
|
|
79
|
+
else:
|
|
80
|
+
raise AttributeError(f"Module '{self.module_name}' has no attribute '{name}'")
|
|
81
|
+
|
|
82
|
+
def __dir__(self):
|
|
83
|
+
return sorted(set(sys.modules[self.module_name].__dict__) | self.submodules)
|
|
84
|
+
|