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,199 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Media display module for LED Matrix.
|
|
3
|
+
|
|
4
|
+
This module provides functions for displaying images and videos on the LED matrix.
|
|
5
|
+
It includes functions for rendering images, playing videos, and capturing from a camera.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import serial
|
|
9
|
+
import time
|
|
10
|
+
import cv2
|
|
11
|
+
from PIL import Image
|
|
12
|
+
|
|
13
|
+
from ..constants import WIDTH, HEIGHT
|
|
14
|
+
from ..hardware import send_serial
|
|
15
|
+
from ..commands.map import CommandVals
|
|
16
|
+
from ..commands import send_command
|
|
17
|
+
from . import send_col, commit_cols
|
|
18
|
+
from is_matrix_forge.led_matrix.helpers.status_handler import get_status, set_status
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def image(dev, image_file):
|
|
22
|
+
"""Display a black/white image
|
|
23
|
+
Send everything in a single command"""
|
|
24
|
+
# Initialize a byte array to hold the binary representation of the image
|
|
25
|
+
# 39 bytes = 312 bits, which is enough for 9x34 = 306 pixels
|
|
26
|
+
vals = [0x00 for _ in range(39)]
|
|
27
|
+
|
|
28
|
+
# Open and convert the image to RGB format
|
|
29
|
+
im = Image.open(image_file).convert("RGB")
|
|
30
|
+
width, height = im.size
|
|
31
|
+
|
|
32
|
+
# Verify image dimensions match the LED matrix size
|
|
33
|
+
assert width == 9
|
|
34
|
+
assert height == 34
|
|
35
|
+
|
|
36
|
+
# Get all pixel values as a flat list
|
|
37
|
+
pixel_values = list(im.getdata())
|
|
38
|
+
|
|
39
|
+
# Process each pixel
|
|
40
|
+
for i, pixel in enumerate(pixel_values):
|
|
41
|
+
# Calculate average brightness of RGB components
|
|
42
|
+
brightness = sum(pixel) / 3
|
|
43
|
+
|
|
44
|
+
# If brightness is above half of max (127.5), consider it "on"
|
|
45
|
+
# This creates a binary black/white effect
|
|
46
|
+
if brightness > 0xFF / 2:
|
|
47
|
+
# Calculate which byte in vals array this pixel belongs to (i/8)
|
|
48
|
+
# Then set the appropriate bit within that byte (i%8)
|
|
49
|
+
# This packs 8 pixels into each byte of the vals array
|
|
50
|
+
vals[int(i / 8)] |= 1 << i % 8
|
|
51
|
+
|
|
52
|
+
# Send the packed binary data to the device
|
|
53
|
+
send_command(dev, CommandVals.Draw, vals)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def pixel_to_brightness(pixel):
|
|
57
|
+
"""Calculate pixel brightness from an RGB triple"""
|
|
58
|
+
assert len(pixel) == 3
|
|
59
|
+
brightness = sum(pixel) / len(pixel)
|
|
60
|
+
|
|
61
|
+
# Poor man's scaling to make the greyscale pop better.
|
|
62
|
+
# Should find a good function.
|
|
63
|
+
if brightness > 200:
|
|
64
|
+
brightness = brightness
|
|
65
|
+
elif brightness > 150:
|
|
66
|
+
brightness = brightness * 0.8
|
|
67
|
+
elif brightness > 100:
|
|
68
|
+
brightness = brightness * 0.5
|
|
69
|
+
elif brightness > 50:
|
|
70
|
+
brightness = brightness
|
|
71
|
+
else:
|
|
72
|
+
brightness = brightness * 2
|
|
73
|
+
|
|
74
|
+
return int(brightness)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def image_greyscale(dev, image_file):
|
|
78
|
+
"""Display an image in greyscale
|
|
79
|
+
Sends each 1x34 column and then commits => 10 commands
|
|
80
|
+
"""
|
|
81
|
+
with serial.Serial(dev.device, 115200) as s:
|
|
82
|
+
from PIL import Image
|
|
83
|
+
|
|
84
|
+
im = Image.open(image_file).convert("RGB")
|
|
85
|
+
width, height = im.size
|
|
86
|
+
assert width == 9
|
|
87
|
+
assert height == 34
|
|
88
|
+
pixel_values = list(im.getdata())
|
|
89
|
+
for x in range(0, WIDTH):
|
|
90
|
+
vals = [0 for _ in range(HEIGHT)]
|
|
91
|
+
|
|
92
|
+
for y in range(HEIGHT):
|
|
93
|
+
vals[y] = pixel_to_brightness(pixel_values[x + y * WIDTH])
|
|
94
|
+
|
|
95
|
+
send_col(dev, s, x, vals)
|
|
96
|
+
commit_cols(dev, s)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def camera(dev):
|
|
100
|
+
"""Play a live view from the webcam, for fun"""
|
|
101
|
+
set_status('camera')
|
|
102
|
+
with serial.Serial(dev.device, 115200) as s:
|
|
103
|
+
import cv2
|
|
104
|
+
|
|
105
|
+
capture = cv2.VideoCapture(1)
|
|
106
|
+
ret, frame = capture.read()
|
|
107
|
+
|
|
108
|
+
scale_y = HEIGHT / frame.shape[0]
|
|
109
|
+
|
|
110
|
+
# Scale the video to 34 pixels height
|
|
111
|
+
dim = (HEIGHT, int(round(frame.shape[1] * scale_y)))
|
|
112
|
+
# Find the starting position to crop the width to be centered
|
|
113
|
+
# For very narrow videos, make sure to stay in bounds
|
|
114
|
+
start_x = max(0, int(round(dim[1] / 2 - WIDTH / 2)))
|
|
115
|
+
end_x = min(dim[1], start_x + WIDTH)
|
|
116
|
+
|
|
117
|
+
# Pre-process the video into resized, cropped, grayscale frames
|
|
118
|
+
while get_status() == 'camera':
|
|
119
|
+
ret, frame = capture.read()
|
|
120
|
+
if not ret:
|
|
121
|
+
print("Failed to capture video frames")
|
|
122
|
+
break
|
|
123
|
+
|
|
124
|
+
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
|
|
125
|
+
|
|
126
|
+
resized = cv2.resize(gray, (dim[1], dim[0]))
|
|
127
|
+
cropped = resized[0:HEIGHT, start_x:end_x]
|
|
128
|
+
|
|
129
|
+
for x in range(0, cropped.shape[1]):
|
|
130
|
+
vals = [0 for _ in range(HEIGHT)]
|
|
131
|
+
|
|
132
|
+
for y in range(0, HEIGHT):
|
|
133
|
+
vals[y] = cropped[y, x]
|
|
134
|
+
|
|
135
|
+
send_col(dev, s, x, vals)
|
|
136
|
+
commit_cols(dev, s)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def video(dev, video_file):
|
|
140
|
+
"""Resize and play back a video"""
|
|
141
|
+
set_status('video')
|
|
142
|
+
with serial.Serial(dev.device, 115200) as s:
|
|
143
|
+
import cv2
|
|
144
|
+
|
|
145
|
+
capture = cv2.VideoCapture(video_file)
|
|
146
|
+
ret, frame = capture.read()
|
|
147
|
+
|
|
148
|
+
scale_y = HEIGHT / frame.shape[0]
|
|
149
|
+
|
|
150
|
+
# Scale the video to 34 pixels height
|
|
151
|
+
dim = (HEIGHT, int(round(frame.shape[1] * scale_y)))
|
|
152
|
+
# Find the starting position to crop the width to be centered
|
|
153
|
+
# For very narrow videos, make sure to stay in bounds
|
|
154
|
+
start_x = max(0, int(round(dim[1] / 2 - WIDTH / 2)))
|
|
155
|
+
end_x = min(dim[1], start_x + WIDTH)
|
|
156
|
+
|
|
157
|
+
processed = []
|
|
158
|
+
|
|
159
|
+
# Pre-process the video into resized, cropped, grayscale frames
|
|
160
|
+
while get_status() == 'video':
|
|
161
|
+
ret, frame = capture.read()
|
|
162
|
+
if not ret:
|
|
163
|
+
print("Failed to read video frames")
|
|
164
|
+
break
|
|
165
|
+
|
|
166
|
+
gray = cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY)
|
|
167
|
+
|
|
168
|
+
resized = cv2.resize(gray, (dim[1], dim[0]))
|
|
169
|
+
cropped = resized[0:HEIGHT, start_x:end_x]
|
|
170
|
+
|
|
171
|
+
processed.append(cropped)
|
|
172
|
+
|
|
173
|
+
# Determine frame delay based on the video's FPS. Fall back to 30 FPS
|
|
174
|
+
# if the FPS value cannot be obtained.
|
|
175
|
+
fps = capture.get(cv2.CAP_PROP_FPS)
|
|
176
|
+
try:
|
|
177
|
+
fps = float(fps)
|
|
178
|
+
if fps <= 0 or fps != fps:
|
|
179
|
+
raise ValueError
|
|
180
|
+
except Exception:
|
|
181
|
+
fps = 30.0
|
|
182
|
+
frame_delay = 1.0 / fps
|
|
183
|
+
|
|
184
|
+
# Write it out to the module one frame at a time while respecting the
|
|
185
|
+
# original frame rate.
|
|
186
|
+
for frame in processed:
|
|
187
|
+
start = time.time()
|
|
188
|
+
for x in range(0, cropped.shape[1]):
|
|
189
|
+
vals = [0 for _ in range(HEIGHT)]
|
|
190
|
+
|
|
191
|
+
for y in range(0, HEIGHT):
|
|
192
|
+
vals[y] = frame[y, x]
|
|
193
|
+
|
|
194
|
+
send_col(dev, s, x, vals)
|
|
195
|
+
commit_cols(dev, s)
|
|
196
|
+
|
|
197
|
+
elapsed = time.time() - start
|
|
198
|
+
if frame_delay > elapsed:
|
|
199
|
+
time.sleep(frame_delay - elapsed)
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Pattern display module for LED Matrix.
|
|
3
|
+
|
|
4
|
+
This module provides functions for displaying various patterns on the LED matrix.
|
|
5
|
+
It includes functions for displaying checkerboards, gradients, and other visual patterns.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import time
|
|
9
|
+
|
|
10
|
+
from .built_in.stencils import every_nth_row, every_nth_col
|
|
11
|
+
from ..helpers import render_matrix
|
|
12
|
+
from is_matrix_forge.led_matrix.display.patterns.built_in.stencils import every_nth_row, every_nth_col
|
|
13
|
+
from is_matrix_forge.led_matrix.display.helpers.columns import send_col
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def breathing(dev):
|
|
17
|
+
"""Animate breathing brightness.
|
|
18
|
+
Keeps currently displayed grid"""
|
|
19
|
+
set_status('breathing')
|
|
20
|
+
# Bright ranges appear similar, so we have to go through those faster
|
|
21
|
+
while get_status() == 'breathing':
|
|
22
|
+
# Go quickly from 250 to 50
|
|
23
|
+
for i in range(10):
|
|
24
|
+
time.sleep(0.03)
|
|
25
|
+
brightness(dev, 250 - i * 20)
|
|
26
|
+
|
|
27
|
+
# Go slowly from 50 to 0
|
|
28
|
+
for i in range(10):
|
|
29
|
+
time.sleep(0.06)
|
|
30
|
+
brightness(dev, 50 - i * 5)
|
|
31
|
+
|
|
32
|
+
# Go slowly from 0 to 50
|
|
33
|
+
for i in range(10):
|
|
34
|
+
time.sleep(0.06)
|
|
35
|
+
brightness(dev, i * 5)
|
|
36
|
+
|
|
37
|
+
# Go quickly from 50 to 250
|
|
38
|
+
for i in range(10):
|
|
39
|
+
time.sleep(0.03)
|
|
40
|
+
brightness(dev, 50 + i * 20)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def eq(dev, vals):
|
|
44
|
+
"""Display 9 values in equalizer diagram starting from the middle, going up and down"""
|
|
45
|
+
matrix = [[0 for _ in range(34)] for _ in range(9)]
|
|
46
|
+
|
|
47
|
+
for col, val in enumerate(vals[:9]):
|
|
48
|
+
row = int(34 / 2)
|
|
49
|
+
above = int(val / 2)
|
|
50
|
+
below = val - above
|
|
51
|
+
|
|
52
|
+
for i in range(above):
|
|
53
|
+
matrix[col][row + i] = 0xFF
|
|
54
|
+
for i in range(below):
|
|
55
|
+
matrix[col][row - 1 - i] = 0xFF
|
|
56
|
+
|
|
57
|
+
render_matrix(dev, matrix)
|
|
58
|
+
|
|
59
|
+
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import difflib
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from .constants import PATTERN_MAP
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class BuiltInPatterns:
|
|
8
|
+
"""
|
|
9
|
+
Interface for rendering built-in LED matrix patterns on a device.
|
|
10
|
+
|
|
11
|
+
Parameters
|
|
12
|
+
----------
|
|
13
|
+
dev : Any
|
|
14
|
+
The device instance to send commands to.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def __init__(self, dev: Any):
|
|
18
|
+
from serial.tools.list_ports_common import ListPortInfo
|
|
19
|
+
from is_matrix_forge.led_matrix.controller.controller import LEDMatrixController
|
|
20
|
+
|
|
21
|
+
if dev is None:
|
|
22
|
+
from is_matrix_forge.led_matrix.controller.helpers import get_controllers
|
|
23
|
+
|
|
24
|
+
if not isinstance(dev, ListPortInfo) and not isinstance(dev, LEDMatrixController):
|
|
25
|
+
raise TypeError(f'dev must be of type `ListPortInfo` or `LEDMatrixController`, not {type(dev)}')
|
|
26
|
+
elif isinstance(dev, LEDMatrixController):
|
|
27
|
+
dev = dev.device
|
|
28
|
+
|
|
29
|
+
self.dev = dev
|
|
30
|
+
|
|
31
|
+
def render(self, pattern_name: str) -> None:
|
|
32
|
+
"""
|
|
33
|
+
Render the specified pattern on the device.
|
|
34
|
+
|
|
35
|
+
Parameters
|
|
36
|
+
----------
|
|
37
|
+
pattern_name : str
|
|
38
|
+
The name of the pattern to render.
|
|
39
|
+
"""
|
|
40
|
+
normalized = self._normalize(pattern_name)
|
|
41
|
+
action = PATTERN_MAP.get(normalized)
|
|
42
|
+
|
|
43
|
+
if action:
|
|
44
|
+
action(self.dev)
|
|
45
|
+
else:
|
|
46
|
+
print(f"❌ Invalid pattern: '{pattern_name}'")
|
|
47
|
+
suggestions = self.suggest(pattern_name)
|
|
48
|
+
if suggestions:
|
|
49
|
+
print("💡 Did you mean:")
|
|
50
|
+
for s in suggestions:
|
|
51
|
+
print(f" - {s}")
|
|
52
|
+
else:
|
|
53
|
+
print("ℹ️ Available patterns:")
|
|
54
|
+
for key in self.list():
|
|
55
|
+
print(f" - {key}")
|
|
56
|
+
|
|
57
|
+
def suggest(self, pattern_name: str, n: int = 3) -> list[str]:
|
|
58
|
+
"""
|
|
59
|
+
Suggest similar pattern names.
|
|
60
|
+
|
|
61
|
+
Parameters
|
|
62
|
+
----------
|
|
63
|
+
pattern_name : str
|
|
64
|
+
The name to suggest alternatives for.
|
|
65
|
+
|
|
66
|
+
n : int, optional
|
|
67
|
+
Maximum number of suggestions to return.
|
|
68
|
+
|
|
69
|
+
Returns
|
|
70
|
+
-------
|
|
71
|
+
list[str]
|
|
72
|
+
List of suggested pattern names.
|
|
73
|
+
"""
|
|
74
|
+
return difflib.get_close_matches(pattern_name, self.list(), n=n)
|
|
75
|
+
|
|
76
|
+
@staticmethod
|
|
77
|
+
def list() -> list[str]:
|
|
78
|
+
"""
|
|
79
|
+
List all available pattern names.
|
|
80
|
+
|
|
81
|
+
Returns
|
|
82
|
+
-------
|
|
83
|
+
list[str]
|
|
84
|
+
Names of all patterns available.
|
|
85
|
+
"""
|
|
86
|
+
return list(PATTERN_MAP.keys())
|
|
87
|
+
|
|
88
|
+
@staticmethod
|
|
89
|
+
def _normalize(name: str) -> str:
|
|
90
|
+
return name.strip()
|
|
91
|
+
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from .commands import PatternVals
|
|
2
|
+
from is_matrix_forge.led_matrix.commands.map import CommandVals
|
|
3
|
+
from is_matrix_forge.led_matrix.commands import send_command
|
|
4
|
+
from .stencils import all_brightnesses, every_nth_row, every_nth_col, checkerboard
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
PATTERN_MAP = {
|
|
8
|
+
"All LEDs on": lambda dev: send_command(dev, CommandVals.Pattern, [PatternVals.FullBrightness]),
|
|
9
|
+
"Gradient (0-13% Brightness)": lambda dev: send_command(dev, CommandVals.Pattern, [PatternVals.Gradient]),
|
|
10
|
+
"Double Gradient (0-7-0% Brightness)": lambda dev: send_command(dev, CommandVals.Pattern, [PatternVals.DoubleGradient]),
|
|
11
|
+
"\"LOTUS\" sideways": lambda dev: send_command(dev, CommandVals.Pattern, [PatternVals.DisplayLotus]),
|
|
12
|
+
"Zigzag": lambda dev: send_command(dev, CommandVals.Pattern, [PatternVals.ZigZag]),
|
|
13
|
+
"\"PANIC\"": lambda dev: send_command(dev, CommandVals.Pattern, [PatternVals.DisplayPanic]),
|
|
14
|
+
"\"LOTUS\" Top Down": lambda dev: send_command(dev, CommandVals.Pattern, [PatternVals.DisplayLotus2]),
|
|
15
|
+
"All brightness levels (1 LED each)": lambda dev: all_brightnesses(dev),
|
|
16
|
+
"Every Second Row": lambda dev: every_nth_row(dev, 2),
|
|
17
|
+
"Every Third Row": lambda dev: every_nth_row(dev, 3),
|
|
18
|
+
"Every Fourth Row": lambda dev: every_nth_row(dev, 4),
|
|
19
|
+
"Every Fifth Row": lambda dev: every_nth_row(dev, 5),
|
|
20
|
+
"Every Sixth Row": lambda dev: every_nth_row(dev, 6),
|
|
21
|
+
"Every Second Col": lambda dev: every_nth_col(dev, 2),
|
|
22
|
+
"Every Third Col": lambda dev: every_nth_col(dev, 3),
|
|
23
|
+
"Every Fourth Col": lambda dev: every_nth_col(dev, 4),
|
|
24
|
+
"Every Fifth Col": lambda dev: every_nth_col(dev, 5),
|
|
25
|
+
"Checkerboard": lambda dev: checkerboard(dev, 1),
|
|
26
|
+
"Double Checkerboard": lambda dev: checkerboard(dev, 2),
|
|
27
|
+
"Triple Checkerboard": lambda dev: checkerboard(dev, 3),
|
|
28
|
+
"Quad Checkerboard": lambda dev: checkerboard(dev, 4),
|
|
29
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
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
|
+
import serial
|
|
19
|
+
|
|
20
|
+
from is_matrix_forge.led_matrix.constants import WIDTH, HEIGHT
|
|
21
|
+
from is_matrix_forge.led_matrix.display.helpers.columns import send_col, commit_cols
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def all_brightnesses(dev):
|
|
25
|
+
"""Increase the brightness with each pixel.
|
|
26
|
+
Only 0-255 available, so it can't fill all 306 LEDs"""
|
|
27
|
+
with serial.Serial(dev.device, 115200) as s:
|
|
28
|
+
for x in range(0, WIDTH):
|
|
29
|
+
vals = [0 for _ in range(HEIGHT)]
|
|
30
|
+
|
|
31
|
+
for y in range(HEIGHT):
|
|
32
|
+
brightness = x + WIDTH * y
|
|
33
|
+
if brightness > 255:
|
|
34
|
+
vals[y] = 0
|
|
35
|
+
else:
|
|
36
|
+
vals[y] = brightness
|
|
37
|
+
|
|
38
|
+
send_col(dev, s, x, vals)
|
|
39
|
+
commit_cols(dev, s)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def every_nth_row(dev, n):
|
|
44
|
+
with serial.Serial(dev.device, 115200) as s:
|
|
45
|
+
for x in range(0, WIDTH):
|
|
46
|
+
vals = [(0xFF if y % n == 0 else 0) for y in range(HEIGHT)]
|
|
47
|
+
|
|
48
|
+
send_col(dev, s, x, vals)
|
|
49
|
+
commit_cols(dev, s)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def every_nth_col(dev, n):
|
|
53
|
+
with serial.Serial(dev.device, 115200) as s:
|
|
54
|
+
for x in range(0, WIDTH):
|
|
55
|
+
vals = [(0xFF if x % n == 0 else 0) for _ in range(HEIGHT)]
|
|
56
|
+
|
|
57
|
+
send_col(dev, s, x, vals)
|
|
58
|
+
commit_cols(dev, s)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def checkerboard(dev, n):
|
|
62
|
+
with serial.Serial(dev.device, 115200) as s:
|
|
63
|
+
for x in range(0, WIDTH):
|
|
64
|
+
vals = (([0xFF] * n) + ([0x00] * n)) * int(HEIGHT / 2)
|
|
65
|
+
if x % (n * 2) < n:
|
|
66
|
+
# Rotate once
|
|
67
|
+
vals = vals[n:] + vals[:n]
|
|
68
|
+
|
|
69
|
+
send_col(dev, s, x, vals)
|
|
70
|
+
commit_cols(dev, s)
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
from .scroller import TextScroller
|
|
2
|
+
"""
|
|
3
|
+
Text and symbol rendering module for LED Matrix.
|
|
4
|
+
|
|
5
|
+
This module provides functions for displaying text and symbols on the LED matrix.
|
|
6
|
+
It includes functions for rendering strings, fonts, and special symbols.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from is_matrix_forge.led_matrix import fonts as font
|
|
10
|
+
from is_matrix_forge.led_matrix.commands import send_command
|
|
11
|
+
from ...commands.map import CommandVals
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def show_string(dev, s):
|
|
15
|
+
"""Render a string with up to five letters"""
|
|
16
|
+
show_font(dev, [font.convert_font(letter) for letter in str(s)[:5]])
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def show_font(dev, font_items):
|
|
20
|
+
"""Render up to five 5x6 pixel font items"""
|
|
21
|
+
# Initialize a byte array with all pixels off
|
|
22
|
+
vals = [0x00 for _ in range(39)]
|
|
23
|
+
|
|
24
|
+
# Process each font item (character/symbol)
|
|
25
|
+
for digit_i, digit_pixels in enumerate(font_items):
|
|
26
|
+
# Calculate vertical offset for this character
|
|
27
|
+
# Each character is placed 7 pixels apart vertically
|
|
28
|
+
offset = digit_i * 7
|
|
29
|
+
|
|
30
|
+
# Process each pixel in the 5x6 character grid
|
|
31
|
+
for pixel_x in range(5):
|
|
32
|
+
for pixel_y in range(6):
|
|
33
|
+
# Convert 2D coordinates to 1D index in the font data
|
|
34
|
+
# Font data is stored as a 1D array in row-major order
|
|
35
|
+
pixel_value = digit_pixels[pixel_x + pixel_y * 5]
|
|
36
|
+
|
|
37
|
+
# Calculate the position in the LED matrix
|
|
38
|
+
# Characters start at x=2 (to center them) and are stacked vertically
|
|
39
|
+
# with the calculated offset
|
|
40
|
+
i = (2 + pixel_x) + (9 * (pixel_y + offset))
|
|
41
|
+
|
|
42
|
+
# If this pixel should be on
|
|
43
|
+
if pixel_value:
|
|
44
|
+
# Calculate which byte and bit to set
|
|
45
|
+
byte_index = int(i / 8)
|
|
46
|
+
bit_position = i % 8
|
|
47
|
+
|
|
48
|
+
# Set the bit in the appropriate byte
|
|
49
|
+
vals[byte_index] = vals[byte_index] | (1 << bit_position)
|
|
50
|
+
|
|
51
|
+
# Send the command to display the characters
|
|
52
|
+
send_command(dev, CommandVals.Draw, vals)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def show_symbols(dev, symbols):
|
|
56
|
+
"""Render a list of up to five symbols
|
|
57
|
+
Can use letters/numbers or symbol names, like 'sun', ':)'"""
|
|
58
|
+
font_items = []
|
|
59
|
+
for symbol in symbols:
|
|
60
|
+
s = font.convert_symbol(symbol)
|
|
61
|
+
if not s:
|
|
62
|
+
s = font.convert_font(symbol)
|
|
63
|
+
font_items.append(s)
|
|
64
|
+
|
|
65
|
+
show_font(dev, font_items)
|
|
66
|
+
|
|
@@ -0,0 +1,136 @@
|
|
|
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 typing import List
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class TextScroller:
|
|
23
|
+
"""
|
|
24
|
+
Generate Frame objects for scrolling text bottom-to-top on an LED matrix.
|
|
25
|
+
|
|
26
|
+
Frames are returned as a list of "columns", each column is a list of bits down the rows.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
font,
|
|
32
|
+
rows: int = 34,
|
|
33
|
+
cols: int = 9,
|
|
34
|
+
delay: float = 0.1,
|
|
35
|
+
letter_width: int = 5,
|
|
36
|
+
letter_height: int = 6,
|
|
37
|
+
spacing: int = 1,
|
|
38
|
+
col_offset: int = 2,
|
|
39
|
+
):
|
|
40
|
+
"""
|
|
41
|
+
Initialize a TextScroller.
|
|
42
|
+
|
|
43
|
+
Parameters:
|
|
44
|
+
font:
|
|
45
|
+
A font object with convert_font(char) -> glyph callable.
|
|
46
|
+
rows:
|
|
47
|
+
Number of rows in the matrix.
|
|
48
|
+
cols:
|
|
49
|
+
Number of columns in the matrix.
|
|
50
|
+
delay:
|
|
51
|
+
Duration for each generated Frame.
|
|
52
|
+
letter_width:
|
|
53
|
+
Width of each character in pixels.
|
|
54
|
+
letter_height:
|
|
55
|
+
Height of each character in pixels.
|
|
56
|
+
spacing:
|
|
57
|
+
Vertical spacing between characters.
|
|
58
|
+
col_offset:
|
|
59
|
+
Horizontal offset for character placement.
|
|
60
|
+
"""
|
|
61
|
+
self.font = font
|
|
62
|
+
self.rows = rows
|
|
63
|
+
self.cols = cols
|
|
64
|
+
self.delay = delay
|
|
65
|
+
self.letter_width = letter_width
|
|
66
|
+
self.letter_height = letter_height
|
|
67
|
+
self.spacing = spacing
|
|
68
|
+
self.col_offset = col_offset
|
|
69
|
+
|
|
70
|
+
def scroll(self, text: str, skip_end_space: bool = False) -> List['Frame']:
|
|
71
|
+
"""
|
|
72
|
+
Build and return a list of Frame objects representing the text scroll.
|
|
73
|
+
|
|
74
|
+
Parameters:
|
|
75
|
+
text:
|
|
76
|
+
The text string to scroll.
|
|
77
|
+
skip_end_space:
|
|
78
|
+
If True, omit trailing blank frames after text.
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
A list of Frame instances, each covering the full matrix as columns×rows.
|
|
82
|
+
"""
|
|
83
|
+
from is_matrix_forge.led_matrix.display.animations.frame import Frame
|
|
84
|
+
|
|
85
|
+
grid = self._build_big_grid(text)
|
|
86
|
+
total_height = len(grid)
|
|
87
|
+
end_limit = total_height if skip_end_space else total_height + self.rows
|
|
88
|
+
frames: List[Frame] = []
|
|
89
|
+
|
|
90
|
+
for top in range(-self.rows, end_limit):
|
|
91
|
+
# extract a rows×cols slice (list of rows)
|
|
92
|
+
slice_rows = [
|
|
93
|
+
grid[r] if 0 <= r < total_height else [0] * self.cols
|
|
94
|
+
for r in range(top, top + self.rows)
|
|
95
|
+
]
|
|
96
|
+
# reorient to columns×rows
|
|
97
|
+
cols_grid = self._orient_frame(slice_rows)
|
|
98
|
+
frames.append(Frame(cols_grid, duration=self.delay))
|
|
99
|
+
|
|
100
|
+
return frames
|
|
101
|
+
|
|
102
|
+
def _build_big_grid(self, text: str) -> List[List[int]]:
|
|
103
|
+
"""
|
|
104
|
+
Build a concatenated vertical grid of all glyphs in text as list of rows.
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
A 2D list of bits [total_height][cols].
|
|
108
|
+
"""
|
|
109
|
+
font_items = [self.font.convert_font(c) for c in text]
|
|
110
|
+
total_height = len(font_items) * (self.letter_height + self.spacing) - self.spacing
|
|
111
|
+
grid = [[0] * self.cols for _ in range(total_height)]
|
|
112
|
+
|
|
113
|
+
for idx, glyph in enumerate(font_items):
|
|
114
|
+
base = idx * (self.letter_height + self.spacing)
|
|
115
|
+
for x in range(self.letter_width):
|
|
116
|
+
for y in range(self.letter_height):
|
|
117
|
+
if glyph[x + y * self.letter_width]:
|
|
118
|
+
grid[base + y][self.col_offset + x] = 1
|
|
119
|
+
|
|
120
|
+
return grid
|
|
121
|
+
|
|
122
|
+
def _orient_frame(self, rows_grid: List[List[int]]) -> List[List[int]]:
|
|
123
|
+
"""
|
|
124
|
+
Transpose a rows×cols grid into cols×rows orientation.
|
|
125
|
+
|
|
126
|
+
Parameters:
|
|
127
|
+
rows_grid:
|
|
128
|
+
A list of self.rows lists, each length self.cols.
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
A list of self.cols lists, each length self.rows.
|
|
132
|
+
"""
|
|
133
|
+
return [
|
|
134
|
+
[rows_grid[r][c] for r in range(self.rows)]
|
|
135
|
+
for c in range(self.cols)
|
|
136
|
+
]
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from inspyre_toolbox.exceptional import CustomRootException
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class LEDMatrixControllerError(CustomRootException):
|
|
5
|
+
"""
|
|
6
|
+
Base class for all LED matrix controller errors.
|
|
7
|
+
|
|
8
|
+
Class Attributes:
|
|
9
|
+
default_message (str): Read-Only. The default message for the error.
|
|
10
|
+
"""
|
|
11
|
+
default_message = 'An error occurred with the LED matrix controller library!'
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
__all__ = ['LEDMatrixControllerError']
|