Framework-LED-Matrix 0.1.1__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.
- cli.py +669 -0
- framework_led_matrix/__init__.py +0 -0
- framework_led_matrix/apps/__init__.py +0 -0
- framework_led_matrix/apps/background_runner.py +125 -0
- framework_led_matrix/apps/runtime.py +185 -0
- framework_led_matrix/core/__init__.py +0 -0
- framework_led_matrix/core/led_commands.py +406 -0
- framework_led_matrix/core/math_engine.py +294 -0
- framework_led_matrix/simulations/BihamMiddletonLevineTrafficModel.py +238 -0
- framework_led_matrix/simulations/HardyPomeauPazzis.py +241 -0
- framework_led_matrix/simulations/__init__.py +0 -0
- framework_led_matrix/simulations/inner_totalistic.py +47 -0
- framework_led_matrix/simulations/outer_totalistic.py +112 -0
- framework_led_matrix/utils/__init__.py +0 -0
- framework_led_matrix/utils/anagrams.py +39 -0
- framework_led_matrix/utils/text_rendering.py +281 -0
- framework_led_matrix-0.1.1.dist-info/METADATA +159 -0
- framework_led_matrix-0.1.1.dist-info/RECORD +22 -0
- framework_led_matrix-0.1.1.dist-info/WHEEL +5 -0
- framework_led_matrix-0.1.1.dist-info/entry_points.txt +2 -0
- framework_led_matrix-0.1.1.dist-info/licenses/LICENSE +674 -0
- framework_led_matrix-0.1.1.dist-info/top_level.txt +2 -0
cli.py
ADDED
|
@@ -0,0 +1,669 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
helper = """
|
|
4
|
+
Master Command-Line Interface (CLI) for the LED Matrix project.
|
|
5
|
+
|
|
6
|
+
This script provides a centralized way to run various functions
|
|
7
|
+
from the different modules of the project.
|
|
8
|
+
|
|
9
|
+
USAGE:
|
|
10
|
+
python3 cli.py [COMMAND] [SUBCOMMAND] [OPTIONS]
|
|
11
|
+
./cli.py [COMMAND] [SUBCOMMAND] [OPTIONS] (if executable)
|
|
12
|
+
|
|
13
|
+
--- EXAMPLES BY COMMAND ---
|
|
14
|
+
|
|
15
|
+
[1] led: Basic hardware commands
|
|
16
|
+
# Get firmware version from modules
|
|
17
|
+
./cli.py led version
|
|
18
|
+
|
|
19
|
+
# Clear both displays (all LEDs OFF)
|
|
20
|
+
./cli.py led clear
|
|
21
|
+
|
|
22
|
+
# Fill both displays and hold for 3 seconds
|
|
23
|
+
./cli.py led fill --hold 3
|
|
24
|
+
|
|
25
|
+
# Start hardware fade/etc animation
|
|
26
|
+
./cli.py led start-anim
|
|
27
|
+
|
|
28
|
+
# Stop hardware animation
|
|
29
|
+
./cli.py led stop-anim
|
|
30
|
+
|
|
31
|
+
# Clear display and stop animation
|
|
32
|
+
./cli.py led reset
|
|
33
|
+
|
|
34
|
+
# Show available serial ports (for debugging)
|
|
35
|
+
./cli.py led ports
|
|
36
|
+
|
|
37
|
+
[2] text: Render text on the matrix
|
|
38
|
+
# Draw text vertically and hold for 5 seconds
|
|
39
|
+
./cli.py text vertical "HELLO" --hold 5
|
|
40
|
+
|
|
41
|
+
# Draw text horizontally with a specific font size and offset
|
|
42
|
+
./cli.py text horizontal "Scrolling" --font-size 8 --x-offset 10
|
|
43
|
+
|
|
44
|
+
[3] anagram: Run anagram-related functions
|
|
45
|
+
# Find and draw all anagrams for the word "post"
|
|
46
|
+
./cli.py anagram draw post
|
|
47
|
+
|
|
48
|
+
# Do the same, but disable the animation between words
|
|
49
|
+
./cli.py anagram draw post --no-animate
|
|
50
|
+
|
|
51
|
+
[4] sim: Run complex cellular automata and simulations
|
|
52
|
+
# Run Conway's Game of Life (B3/S23) from a random start
|
|
53
|
+
./cli.py sim gof
|
|
54
|
+
|
|
55
|
+
# Run Game of Life with a specific pattern (glider)
|
|
56
|
+
./cli.py sim gof --board glider --steps 300 --delay 0.05
|
|
57
|
+
|
|
58
|
+
# Run a CUSTOM Outer-Totalistic CA (e.g., "HighLife" B3,6/S2,3)
|
|
59
|
+
# NOTE: Rules are now comma-separated.
|
|
60
|
+
./cli.py sim outer --b-rule "3,6" --s-rule "2,3"
|
|
61
|
+
|
|
62
|
+
# Run a CUSTOM Inner-Totalistic CA (e.t., rule 777)
|
|
63
|
+
# This takes a single number, so double/triple digits are fine.
|
|
64
|
+
./cli.py sim inner 777 --steps 100
|
|
65
|
+
./cli.py sim inner 1024 --steps 100
|
|
66
|
+
|
|
67
|
+
# Run the BML traffic simulation
|
|
68
|
+
./cli.py sim bml --density 0.4 --steps 1000 --delay 0.02
|
|
69
|
+
|
|
70
|
+
# Run the BML simulation LOCALLY (in a Matplotlib window)
|
|
71
|
+
./cli.py sim bml-local --density 0.4 --steps 1000
|
|
72
|
+
|
|
73
|
+
# Run the HPP Lattice Gas simulation
|
|
74
|
+
./cli.py sim hpp --density 0.5 --steps 500
|
|
75
|
+
|
|
76
|
+
# Run HPP, but seed the board with math function graphs
|
|
77
|
+
./cli.py sim hpp-math --graphs 3
|
|
78
|
+
|
|
79
|
+
# Show random greyscale noise for 15 seconds
|
|
80
|
+
./cli.py sim random-grey --duration 15
|
|
81
|
+
|
|
82
|
+
# Draw anagrams for 3 words, then run GoL on each
|
|
83
|
+
./cli.py sim anagram-gof --words 3
|
|
84
|
+
|
|
85
|
+
# Show 10 random math graphs, pausing 3 sec each, and hold the last one
|
|
86
|
+
./cli.py sim math-graphs --num 10 --delay 3 --hold 5
|
|
87
|
+
|
|
88
|
+
[5] math: Run local math visualization tools
|
|
89
|
+
# Plot a predefined function LOCALLY and on the MATRIX
|
|
90
|
+
./cli.py math plot sin
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
import argparse
|
|
94
|
+
import sys
|
|
95
|
+
import time
|
|
96
|
+
import numpy as np
|
|
97
|
+
from typing import List, Optional
|
|
98
|
+
|
|
99
|
+
# --- Import All Necessary Functions ---
|
|
100
|
+
# We import from the new package structure.
|
|
101
|
+
|
|
102
|
+
try:
|
|
103
|
+
# LED Matrix Core Commands
|
|
104
|
+
from framework_led_matrix.core.led_commands import (
|
|
105
|
+
log,
|
|
106
|
+
reset_modules,
|
|
107
|
+
get_firmware_version,
|
|
108
|
+
clear_graph,
|
|
109
|
+
fill_graph,
|
|
110
|
+
start_animation,
|
|
111
|
+
stop_animation,
|
|
112
|
+
output_ports,
|
|
113
|
+
WIDTH,
|
|
114
|
+
HEIGHT,
|
|
115
|
+
coordinates_to_matrix,
|
|
116
|
+
draw_matrix_on_board
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
# Text Rendering
|
|
120
|
+
from framework_led_matrix.utils.text_rendering import draw_text_vertical, draw_text_horizontal
|
|
121
|
+
|
|
122
|
+
# Anagrams
|
|
123
|
+
from framework_led_matrix.utils.anagrams import draw_anagram_on_matrix
|
|
124
|
+
|
|
125
|
+
# Math Functions
|
|
126
|
+
from framework_led_matrix.core.math_engine import (
|
|
127
|
+
plot_function,
|
|
128
|
+
MATH_OPERATIONS,
|
|
129
|
+
pick_largest_graph,
|
|
130
|
+
REGULAR_OPERATIONS
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
# Simulation Models
|
|
134
|
+
from framework_led_matrix.simulations.BihamMiddletonLevineTrafficModel import run_bml, show_bml_local_animation
|
|
135
|
+
from framework_led_matrix.simulations.HardyPomeauPazzis import run_hpp_simulation, create_hpp_board_np
|
|
136
|
+
from framework_led_matrix.simulations.outer_totalistic import STARTING_STATES_GOF, game_of_life_rules
|
|
137
|
+
|
|
138
|
+
# Runtime Simulation Functions
|
|
139
|
+
from framework_led_matrix.apps.runtime import (
|
|
140
|
+
run_hpp_with_math,
|
|
141
|
+
run_outer_totalistic_simulation,
|
|
142
|
+
game_of_life_totalistic_sim,
|
|
143
|
+
run_bml_simulation,
|
|
144
|
+
run_inner_totalistic_simulation,
|
|
145
|
+
random_greyscale_animation,
|
|
146
|
+
run_anagrams_game_of_life,
|
|
147
|
+
run_draw_anagram_on_matrix,
|
|
148
|
+
run_math_funs_game_of_life,
|
|
149
|
+
show_random_graphs
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
# Background Runner
|
|
153
|
+
from framework_led_matrix.apps.background_runner import run_background_mode
|
|
154
|
+
|
|
155
|
+
except ImportError as e:
|
|
156
|
+
print(f"Error: Failed to import a necessary module: {e}", file=sys.stderr)
|
|
157
|
+
print("Please ensure you are running cli.py from the root directory", file=sys.stderr)
|
|
158
|
+
print("of the graph_functions_on_matrix project.", file=sys.stderr)
|
|
159
|
+
sys.exit(1)
|
|
160
|
+
except Exception as e:
|
|
161
|
+
print(f"An unexpected error occurred during import: {e}", file=sys.stderr)
|
|
162
|
+
sys.exit(1)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
# --- Type-Parsing Helper Functions ---
|
|
166
|
+
|
|
167
|
+
def parse_int_list(list_str: str) -> List[int]:
|
|
168
|
+
"""Parses a comma-separated list of ints (e.g., '3,6,7') into a list."""
|
|
169
|
+
if not list_str:
|
|
170
|
+
return []
|
|
171
|
+
try:
|
|
172
|
+
# Allow empty strings to return empty lists
|
|
173
|
+
if not list_str.strip():
|
|
174
|
+
return []
|
|
175
|
+
return [int(x.strip()) for x in list_str.split(',')]
|
|
176
|
+
except Exception as e:
|
|
177
|
+
raise argparse.ArgumentTypeError(f"Invalid integer list format: '{list_str}'. Must be comma-separated (e.g., '3,6,7'). Error: {e}")
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
# --- CLI Definition ---
|
|
181
|
+
|
|
182
|
+
def build_cli():
|
|
183
|
+
"""
|
|
184
|
+
Builds the entire argparse CLI structure.
|
|
185
|
+
"""
|
|
186
|
+
parser = argparse.ArgumentParser(
|
|
187
|
+
description="Master CLI for LED Matrix controllers and simulations.",
|
|
188
|
+
formatter_class=argparse.RawTextHelpFormatter,
|
|
189
|
+
# Updated main epilog
|
|
190
|
+
epilog="Run a command with -h for more specific help (e.g., ./cli.py sim gof -h)"
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
# --- ADDED: Top-level verbose flag ---
|
|
194
|
+
parser.add_argument(
|
|
195
|
+
'-v', '--verbose',
|
|
196
|
+
action='store_true',
|
|
197
|
+
help='Enable detailed verbose logging for all commands.'
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
subparsers = parser.add_subparsers(dest='command', required=True, help='Main command category')
|
|
201
|
+
|
|
202
|
+
# --- 0. Background Sub-parser ---
|
|
203
|
+
subparsers.add_parser(
|
|
204
|
+
'background',
|
|
205
|
+
aliases=['bg'],
|
|
206
|
+
help='Run the background runner (screensaver mode).',
|
|
207
|
+
epilog="Example: ./cli.py background"
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
# --- 1. LED Sub-parser ---
|
|
211
|
+
led_parser = subparsers.add_parser(
|
|
212
|
+
'led',
|
|
213
|
+
help='Basic hardware commands.',
|
|
214
|
+
epilog="Example: ./cli.py led clear"
|
|
215
|
+
)
|
|
216
|
+
led_subparsers = led_parser.add_subparsers(dest='led_command', required=True, help='Specific LED command')
|
|
217
|
+
|
|
218
|
+
led_subparsers.add_parser('version', help='Get firmware version from modules.', epilog="Example: ./cli.py led version")
|
|
219
|
+
led_subparsers.add_parser('clear', help='Clear both displays (all LEDs OFF).', epilog="Example: ./cli.py led clear")
|
|
220
|
+
|
|
221
|
+
# Add 'fill' command with hold argument
|
|
222
|
+
led_fill = led_subparsers.add_parser(
|
|
223
|
+
'fill',
|
|
224
|
+
help='Fill both displays (all LEDs ON).',
|
|
225
|
+
epilog="Example: ./cli.py led fill --hold 2"
|
|
226
|
+
)
|
|
227
|
+
led_fill.add_argument('--hold', type=float, default=0, help='Seconds to hold the filled screen (default: 0).')
|
|
228
|
+
|
|
229
|
+
led_subparsers.add_parser('start-anim', help='Start hardware animation (e.g., fades).', epilog="Example: ./cli.py led start-anim")
|
|
230
|
+
led_subparsers.add_parser('stop-anim', help='Stop hardware animation.', epilog="Example: ./cli.py led stop-anim")
|
|
231
|
+
led_subparsers.add_parser('reset', help='Clear display and stop animation.', epilog="Example: ./cli.py led reset")
|
|
232
|
+
led_subparsers.add_parser('ports', help='Show available serial ports (for debugging).', epilog="Example: ./cli.py led ports")
|
|
233
|
+
|
|
234
|
+
# --- 2. Text Sub-parser ---
|
|
235
|
+
text_parser = subparsers.add_parser(
|
|
236
|
+
'text',
|
|
237
|
+
help='Render text on the matrix.',
|
|
238
|
+
epilog="Example: ./cli.py text vertical \"HELLO\" --hold 3"
|
|
239
|
+
)
|
|
240
|
+
text_subparsers = text_parser.add_subparsers(dest='text_command', required=True, help='Specific text command')
|
|
241
|
+
|
|
242
|
+
# 'text vertical' command
|
|
243
|
+
text_vert = text_subparsers.add_parser(
|
|
244
|
+
'vertical',
|
|
245
|
+
help='Draw text vertically.',
|
|
246
|
+
epilog="Example: ./cli.py text vertical \"MY TEXT\" --font-size 10 --hold 3"
|
|
247
|
+
)
|
|
248
|
+
text_vert.add_argument('text', type=str, help='The text string to render.')
|
|
249
|
+
text_vert.add_argument('--font-size', type=int, default=None, help='Force a specific font size (default: auto).')
|
|
250
|
+
text_vert.add_argument('--which', type=str, default='both', choices=['left', 'right', 'both'], help='Which module to draw on.')
|
|
251
|
+
text_vert.add_argument('--row-offset', type=int, default=0, help='Row offset from the top (default: 0).')
|
|
252
|
+
text_vert.add_argument('--hold', type=float, default=0, help='Seconds to hold the text on screen after drawing (default: 0).')
|
|
253
|
+
|
|
254
|
+
# 'text horizontal' command
|
|
255
|
+
text_horiz = text_subparsers.add_parser(
|
|
256
|
+
'horizontal',
|
|
257
|
+
help='Draw text horizontally.',
|
|
258
|
+
epilog="Example: ./cli.py text horizontal \"SCROLL\" --x-offset 5 --hold 3"
|
|
259
|
+
)
|
|
260
|
+
text_horiz.add_argument('text', type=str, help='The text string to render.')
|
|
261
|
+
text_horiz.add_argument('--font-size', type=int, default=None, help='Force a specific font size (default: auto).')
|
|
262
|
+
text_horiz.add_argument('--which', type=str, default='both', choices=['left', 'right', 'both'], help='Which module to draw on.')
|
|
263
|
+
text_horiz.add_argument('--x-offset', type=int, default=0, help='Horizontal scroll offset (default: 0).')
|
|
264
|
+
text_horiz.add_argument('--y-offset', type=int, default=0, help='Vertical position offset (default: centered).')
|
|
265
|
+
text_horiz.add_argument('--hold', type=float, default=0, help='Seconds to hold the text on screen after drawing (default: 0).')
|
|
266
|
+
|
|
267
|
+
# --- 3. Anagram Sub-parser ---
|
|
268
|
+
anagram_parser = subparsers.add_parser(
|
|
269
|
+
'anagram',
|
|
270
|
+
help='Run anagram-related functions.',
|
|
271
|
+
epilog="Example: ./cli.py anagram draw post"
|
|
272
|
+
)
|
|
273
|
+
anagram_subparsers = anagram_parser.add_subparsers(dest='anagram_command', required=True, help='Specific anagram command')
|
|
274
|
+
|
|
275
|
+
# 'anagram draw' command
|
|
276
|
+
ana_draw = anagram_subparsers.add_parser(
|
|
277
|
+
'draw',
|
|
278
|
+
help='Find and draw anagrams for a word.',
|
|
279
|
+
epilog="Example: ./cli.py anagram draw listen"
|
|
280
|
+
)
|
|
281
|
+
ana_draw.add_argument('word', type=str, help='The word to find anagrams for.')
|
|
282
|
+
ana_draw.add_argument('--which', type=str, default='both', choices=['left', 'right', 'both'], help='Which module to draw on.')
|
|
283
|
+
ana_draw.add_argument('--no-animate', action='store_false', dest='animate', help='Disable animation between words.')
|
|
284
|
+
|
|
285
|
+
# --- 4. Simulation Sub-parser ---
|
|
286
|
+
sim_parser = subparsers.add_parser(
|
|
287
|
+
'sim',
|
|
288
|
+
help='Run complex simulations.',
|
|
289
|
+
epilog="Example: ./cli.py sim gof --board glider --steps 100"
|
|
290
|
+
)
|
|
291
|
+
sim_subparsers = sim_parser.add_subparsers(dest='sim_command', required=True, help='Specific simulation to run')
|
|
292
|
+
|
|
293
|
+
# 'sim bml' command
|
|
294
|
+
sim_bml = sim_subparsers.add_parser(
|
|
295
|
+
'bml',
|
|
296
|
+
help='Run BML traffic simulation on LED matrix.',
|
|
297
|
+
epilog="Example: ./cli.py sim bml --density 0.4 --steps 1000 --delay 0.02"
|
|
298
|
+
)
|
|
299
|
+
sim_bml.add_argument('--density', type=float, default=0.35, help='Car density (0.0 to 1.0).')
|
|
300
|
+
sim_bml.add_argument('--steps', type=int, default=500, help='Total half-steps to simulate.')
|
|
301
|
+
sim_bml.add_argument('--delay', type=float, default=0.05, help='Delay in seconds between frames.')
|
|
302
|
+
|
|
303
|
+
# 'sim bml-local' command
|
|
304
|
+
sim_bml_local = sim_subparsers.add_parser(
|
|
305
|
+
'bml-local',
|
|
306
|
+
help='Run BML sim locally in a Matplotlib window.',
|
|
307
|
+
epilog="Example: ./cli.py sim bml-local --density 0.35 --steps 500"
|
|
308
|
+
)
|
|
309
|
+
sim_bml_local.add_argument('--density', type=float, default=0.35, help='Car density (0.0 to 1.0).')
|
|
310
|
+
sim_bml_local.add_argument('--steps', type=int, default=500, help='Total half-steps to simulate.')
|
|
311
|
+
|
|
312
|
+
# 'sim hpp' command
|
|
313
|
+
sim_hpp = sim_subparsers.add_parser(
|
|
314
|
+
'hpp',
|
|
315
|
+
help='Run HPP Lattice Gas simulation on LED matrix.',
|
|
316
|
+
epilog="Example: ./cli.py sim hpp --density 0.5 --steps 500"
|
|
317
|
+
)
|
|
318
|
+
sim_hpp.add_argument('--density', type=float, default=0.5, help='Particle density (0.0 to 1.0).')
|
|
319
|
+
sim_hpp.add_argument('--steps', type=int, default=500, help='Simulation steps.')
|
|
320
|
+
sim_hpp.add_argument('--delay', type=float, default=0.05, help='Delay in seconds between frames.')
|
|
321
|
+
sim_hpp.add_argument('--which', type=str, default='both', choices=['left', 'right', 'both'], help='Which module to draw on.')
|
|
322
|
+
|
|
323
|
+
# 'sim hpp-math' command
|
|
324
|
+
sim_hpp_math = sim_subparsers.add_parser(
|
|
325
|
+
'hpp-math',
|
|
326
|
+
help='Run HPP sim, seeded with math graphs.',
|
|
327
|
+
epilog="Example: ./cli.py sim hpp-math --graphs 3 --steps 200"
|
|
328
|
+
)
|
|
329
|
+
sim_hpp_math.add_argument('--density', type=float, default=0.3, help='Particle density (0.0 to 1.0).')
|
|
330
|
+
sim_hpp_math.add_argument('--steps', type=int, default=500, help='Simulation steps per graph.')
|
|
331
|
+
sim_hpp_math.add_argument('--delay', type=float, default=0.05, help='Delay in seconds between frames.')
|
|
332
|
+
sim_hpp_math.add_argument('--graphs', type=int, default=5, help='Number of math graphs to run.')
|
|
333
|
+
|
|
334
|
+
# 'sim outer' command
|
|
335
|
+
sim_outer = sim_subparsers.add_parser(
|
|
336
|
+
'outer',
|
|
337
|
+
help="Run a generic Outer-Totalistic (B/S) CA.",
|
|
338
|
+
epilog="Example (HighLife rule): ./cli.py sim outer --b-rule \"3,6\" --s-rule \"2,3\""
|
|
339
|
+
)
|
|
340
|
+
sim_outer.add_argument('--b-rule', type=parse_int_list, default=[3], help="Birth rule, comma-separated (e.g., '3,6').")
|
|
341
|
+
sim_outer.add_argument('--s-rule', type=parse_int_list, default=[2, 3], help="Survival rule, comma-separated (e.g., '2,3').")
|
|
342
|
+
sim_outer.add_argument('--steps', type=int, default=200, help='Simulation steps.')
|
|
343
|
+
sim_outer.add_argument('--delay', type=float, default=0.1, help='Delay in seconds between frames.')
|
|
344
|
+
sim_outer.add_argument('--oscil-max', type=int, default=20, help='Steps to detect oscillation.')
|
|
345
|
+
sim_outer.add_argument('--still-max', type=int, default=10, help='Steps to detect still life.')
|
|
346
|
+
sim_outer.add_argument('--empty-max', type=int, default=5, help='Steps to detect empty board.')
|
|
347
|
+
|
|
348
|
+
# 'sim gof' command
|
|
349
|
+
sim_gof = sim_subparsers.add_parser(
|
|
350
|
+
'gof',
|
|
351
|
+
help="Run Conway's Game of Life (B3/S23).",
|
|
352
|
+
epilog="Example: ./cli.py sim gof --board glider --steps 300 --delay 0.05"
|
|
353
|
+
)
|
|
354
|
+
sim_gof.add_argument('--board', type=str, default='random', choices=['random'] + list(STARTING_STATES_GOF.keys()), help='Initial board state (default: random).')
|
|
355
|
+
sim_gof.add_argument('--steps', type=int, default=200, help='Simulation steps.')
|
|
356
|
+
sim_gof.add_argument('--delay', type=float, default=0.1, help='Delay in seconds between frames.')
|
|
357
|
+
sim_gof.add_argument('--which', type=str, default='both', choices=['left', 'right', 'both'], help='Which module to draw on.')
|
|
358
|
+
|
|
359
|
+
# 'sim inner' command
|
|
360
|
+
sim_inner = sim_subparsers.add_parser(
|
|
361
|
+
'inner',
|
|
362
|
+
help="Run an Inner-Totalistic (NKS) CA.",
|
|
363
|
+
epilog="Example: ./cli.py sim inner 777 --steps 100"
|
|
364
|
+
)
|
|
365
|
+
sim_inner.add_argument('rule', type=int, help="The NKS-style rule number (e.g., 777). This is a single integer.")
|
|
366
|
+
sim_inner.add_argument('--steps', type=int, default=200, help='Simulation steps.')
|
|
367
|
+
sim_inner.add_argument('--delay', type=float, default=0.1, help='Delay in seconds between frames.')
|
|
368
|
+
|
|
369
|
+
# 'sim random-grey' command
|
|
370
|
+
sim_grey = sim_subparsers.add_parser(
|
|
371
|
+
'random-grey',
|
|
372
|
+
help='Display random greyscale noise.',
|
|
373
|
+
epilog="Example: ./cli.py sim random-grey --duration 15"
|
|
374
|
+
)
|
|
375
|
+
sim_grey.add_argument('--duration', type=int, default=10, help='Duration in seconds.')
|
|
376
|
+
sim_grey.add_argument('--no-animate', action='store_false', dest='animate', help='Disable hardware animation (for pure noise).')
|
|
377
|
+
|
|
378
|
+
# 'sim anagram-gof' command
|
|
379
|
+
sim_ana_gof = sim_subparsers.add_parser(
|
|
380
|
+
'anagram-gof',
|
|
381
|
+
help='Draw anagrams, then run GoL on them.',
|
|
382
|
+
epilog="Example: ./cli.py sim anagram-gof --words 3 --steps 50"
|
|
383
|
+
)
|
|
384
|
+
sim_ana_gof.add_argument('--words', type=int, default=4, help='Number of random words to use.')
|
|
385
|
+
sim_ana_gof.add_argument('--steps', type=int, default=100, help='GoL steps per anagram.')
|
|
386
|
+
sim_ana_gof.add_argument('--delay', type=float, default=0.1, help='Delay in seconds between GoL frames.')
|
|
387
|
+
sim_ana_gof.add_argument('--which', type=str, default='both', choices=['left', 'right', 'both'], help='Which module to draw on.')
|
|
388
|
+
|
|
389
|
+
# 'sim anagram-draw' command
|
|
390
|
+
sim_ana_draw = sim_subparsers.add_parser(
|
|
391
|
+
'anagram-draw',
|
|
392
|
+
help='Find and draw anagrams for random words.',
|
|
393
|
+
epilog="Example: ./cli.py sim anagram-draw --words 2"
|
|
394
|
+
)
|
|
395
|
+
sim_ana_draw.add_argument('--words', type=int, default=3, help='Number of random words to use.')
|
|
396
|
+
sim_ana_draw.add_argument('--which', type=str, default='both', choices=['left', 'right', 'both'], help='Which module to draw on.')
|
|
397
|
+
|
|
398
|
+
# 'sim math-gof' command
|
|
399
|
+
sim_math_gof = sim_subparsers.add_parser(
|
|
400
|
+
'math-gof',
|
|
401
|
+
help='Draw math graphs, then run GoL on them.',
|
|
402
|
+
epilog="Example: ./cli.py sim math-gof --steps 50"
|
|
403
|
+
)
|
|
404
|
+
sim_math_gof.add_argument('--steps', type=int, default=100, help='GoL steps per graph.')
|
|
405
|
+
sim_math_gof.add_argument('--delay', type=float, default=0.1, help='Delay in seconds between GoL frames.')
|
|
406
|
+
|
|
407
|
+
# 'sim math-graphs' command
|
|
408
|
+
sim_math_graphs = sim_subparsers.add_parser(
|
|
409
|
+
'math-graphs',
|
|
410
|
+
help='Show a sequence of random math graphs.',
|
|
411
|
+
epilog="Example: ./cli.py sim math-graphs --num 5 --delay 3 --hold 5"
|
|
412
|
+
)
|
|
413
|
+
sim_math_graphs.add_argument('--num', type=int, default=5, help='Number of graphs to show.')
|
|
414
|
+
sim_math_graphs.add_argument('--delay', type=float, default=2.0, help='Delay in seconds between graphs.')
|
|
415
|
+
sim_math_graphs.add_argument('--which', type=str, default='both', choices=['left', 'right', 'both'], help='Which module to draw on.')
|
|
416
|
+
sim_math_graphs.add_argument('--hold', type=float, default=0, help='Seconds to hold the *final* graph on screen (default: 0).')
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
# --- 5. Math Sub-parser (User's modified version) ---
|
|
420
|
+
math_parser = subparsers.add_parser(
|
|
421
|
+
'math',
|
|
422
|
+
help='Run local math visualization tools.',
|
|
423
|
+
epilog="Example: ./cli.py math plot sin"
|
|
424
|
+
)
|
|
425
|
+
math_subparsers = math_parser.add_subparsers(dest='math_command', required=True, help='Specific math command')
|
|
426
|
+
|
|
427
|
+
# 'math plot' command
|
|
428
|
+
math_plot = math_subparsers.add_parser(
|
|
429
|
+
'plot',
|
|
430
|
+
help='Plot a predefined function locally and on the matrix.',
|
|
431
|
+
epilog="""
|
|
432
|
+
Examples:
|
|
433
|
+
./cli.py math plot sin
|
|
434
|
+
./cli.py math plot tanh
|
|
435
|
+
"""
|
|
436
|
+
)
|
|
437
|
+
# Create the list of available function names for the help message
|
|
438
|
+
try:
|
|
439
|
+
func_names = list(REGULAR_OPERATIONS.keys())
|
|
440
|
+
choices_help = f"Picks from this predefined list: {', '.join(func_names)}"
|
|
441
|
+
except NameError:
|
|
442
|
+
# Fallback if REGULAR_OPERATIONS isn't loaded yet (shouldn't happen)
|
|
443
|
+
func_names = ['sin', 'cos', 'tan', 'exp', 'log', 'tanh', 'sqrt', 'arcsin', 'arccos', 'arctanh']
|
|
444
|
+
choices_help = "Picks from a predefined list (e.g., 'sin', 'cos')."
|
|
445
|
+
|
|
446
|
+
math_plot.add_argument(
|
|
447
|
+
'function_string',
|
|
448
|
+
type=str,
|
|
449
|
+
choices=func_names + [""], # Add "" to handle empty list during build
|
|
450
|
+
help=choices_help
|
|
451
|
+
)
|
|
452
|
+
math_plot.add_argument('--points', type=int, default=500, help='Number of points to plot. Determines resolution (default: 500).')
|
|
453
|
+
|
|
454
|
+
return parser
|
|
455
|
+
|
|
456
|
+
# --- Main Execution ---
|
|
457
|
+
|
|
458
|
+
def main():
|
|
459
|
+
"""
|
|
460
|
+
Main function to parse arguments and dispatch commands.
|
|
461
|
+
Includes a try...finally block to reset modules on exit.
|
|
462
|
+
"""
|
|
463
|
+
# Note: parser.parse_args() reads directly from sys.argv
|
|
464
|
+
parser = build_cli()
|
|
465
|
+
|
|
466
|
+
# If no commands are given, print main help
|
|
467
|
+
if len(sys.argv) == 1:
|
|
468
|
+
print(helper)
|
|
469
|
+
return
|
|
470
|
+
|
|
471
|
+
args = parser.parse_args()
|
|
472
|
+
|
|
473
|
+
# --- Set verbose flag from new top-level argument ---
|
|
474
|
+
log.verbose = args.verbose
|
|
475
|
+
# --- Removed hardcoded log.verbose = True ---
|
|
476
|
+
|
|
477
|
+
try:
|
|
478
|
+
# --- 0. Background Command Dispatch ---
|
|
479
|
+
if args.command in ['background', 'bg']:
|
|
480
|
+
run_background_mode()
|
|
481
|
+
|
|
482
|
+
# --- 1. LED Command Dispatch ---
|
|
483
|
+
elif args.command == 'led':
|
|
484
|
+
if args.led_command == 'version':
|
|
485
|
+
get_firmware_version()
|
|
486
|
+
elif args.led_command == 'clear':
|
|
487
|
+
clear_graph()
|
|
488
|
+
elif args.led_command == 'fill':
|
|
489
|
+
fill_graph()
|
|
490
|
+
if args.hold > 0:
|
|
491
|
+
log(f"Holding fill for {args.hold} seconds...")
|
|
492
|
+
time.sleep(args.hold)
|
|
493
|
+
elif args.led_command == 'start-anim':
|
|
494
|
+
start_animation()
|
|
495
|
+
elif args.led_command == 'stop-anim':
|
|
496
|
+
stop_animation()
|
|
497
|
+
elif args.led_command == 'reset':
|
|
498
|
+
reset_modules()
|
|
499
|
+
elif args.led_command == 'ports':
|
|
500
|
+
output_ports()
|
|
501
|
+
|
|
502
|
+
# --- 2. Text Command Dispatch ---
|
|
503
|
+
elif args.command == 'text':
|
|
504
|
+
if args.text_command == 'vertical':
|
|
505
|
+
draw_text_vertical(args.text, args.font_size, args.which, args.row_offset)
|
|
506
|
+
if args.hold > 0:
|
|
507
|
+
log(f"Holding text for {args.hold} seconds...")
|
|
508
|
+
time.sleep(args.hold)
|
|
509
|
+
elif args.text_command == 'horizontal':
|
|
510
|
+
draw_text_horizontal(args.text, args.font_size, args.which, args.x_offset, args.y_offset)
|
|
511
|
+
if args.hold > 0:
|
|
512
|
+
log(f"Holding text for {args.hold} seconds...")
|
|
513
|
+
time.sleep(args.hold)
|
|
514
|
+
|
|
515
|
+
# --- 3. Anagram Command Dispatch ---
|
|
516
|
+
elif args.command == 'anagram':
|
|
517
|
+
if args.anagram_command == 'draw':
|
|
518
|
+
draw_anagram_on_matrix(args.word, args.which, args.animate)
|
|
519
|
+
# Note: This function has its own internal delays.
|
|
520
|
+
# We don't add --hold here as it would be ambiguous.
|
|
521
|
+
|
|
522
|
+
# --- 4. Simulation Command Dispatch ---
|
|
523
|
+
elif args.command == 'sim':
|
|
524
|
+
if args.sim_command == 'bml':
|
|
525
|
+
run_bml_simulation(args.density, args.steps, args.delay)
|
|
526
|
+
elif args.sim_command == 'bml-local':
|
|
527
|
+
print(f"Starting local BML simulation (density={args.density}, steps={args.steps})...")
|
|
528
|
+
print("Close the Matplotlib window to exit.")
|
|
529
|
+
show_bml_local_animation(args.density, args.steps)
|
|
530
|
+
elif args.sim_command == 'hpp':
|
|
531
|
+
run_hpp_simulation(None, args.density, args.steps, args.delay, args.which)
|
|
532
|
+
elif args.sim_command == 'hpp-math':
|
|
533
|
+
run_hpp_with_math(args.density, args.steps, args.delay, args.graphs)
|
|
534
|
+
elif args.sim_command == 'outer':
|
|
535
|
+
run_outer_totalistic_simulation(
|
|
536
|
+
initial_state=None,
|
|
537
|
+
b_rule=args.b_rule,
|
|
538
|
+
s_rule=args.s_rule,
|
|
539
|
+
timesteps=args.steps,
|
|
540
|
+
delay_sec=args.delay,
|
|
541
|
+
oscilation_max_steps=args.oscil_max,
|
|
542
|
+
still_board_max_steps=args.still_max,
|
|
543
|
+
empty_board_max_steps=args.empty_max
|
|
544
|
+
)
|
|
545
|
+
elif args.sim_command == 'gof':
|
|
546
|
+
initial_board = None
|
|
547
|
+
if args.board != 'random':
|
|
548
|
+
initial_board = STARTING_STATES_GOF[args.board]
|
|
549
|
+
game_of_life_totalistic_sim(initial_board, args.steps, args.delay, args.which)
|
|
550
|
+
elif args.sim_command == 'inner':
|
|
551
|
+
# 'rule' is already an int thanks to type=int in add_argument
|
|
552
|
+
run_inner_totalistic_simulation(None, args.rule, args.steps, args.delay)
|
|
553
|
+
elif args.sim_command == 'random-grey':
|
|
554
|
+
random_greyscale_animation(args.animate, args.duration)
|
|
555
|
+
elif args.sim_command == 'anagram-gof':
|
|
556
|
+
run_anagrams_game_of_life(args.words, args.steps, args.delay, args.which)
|
|
557
|
+
elif args.sim_command == 'anagram-draw':
|
|
558
|
+
run_draw_anagram_on_matrix(args.words, args.which)
|
|
559
|
+
elif args.sim_command == 'math-gof':
|
|
560
|
+
run_math_funs_game_of_life(args.steps, args.delay)
|
|
561
|
+
elif args.sim_command == 'math-graphs':
|
|
562
|
+
show_random_graphs(args.num, args.delay, args.which)
|
|
563
|
+
if args.hold > 0:
|
|
564
|
+
time.sleep(args.hold)
|
|
565
|
+
|
|
566
|
+
# --- 5. Math Command Dispatch (User's modified version) ---
|
|
567
|
+
elif args.command == 'math':
|
|
568
|
+
if args.math_command == 'plot':
|
|
569
|
+
try:
|
|
570
|
+
op = args.function_string.strip()
|
|
571
|
+
if op in REGULAR_OPERATIONS:
|
|
572
|
+
func = REGULAR_OPERATIONS[op]
|
|
573
|
+
log(f"Plotting predefined function: '{op}'")
|
|
574
|
+
else:
|
|
575
|
+
print(f"Error: '{op}' not found in REGULAR_OPERATIONS.", file=sys.stderr)
|
|
576
|
+
print("Available functions are:", list(REGULAR_OPERATIONS.keys()), file=sys.stderr)
|
|
577
|
+
return
|
|
578
|
+
|
|
579
|
+
log("Generating graph for matrix...")
|
|
580
|
+
graph_matrix = pick_largest_graph(func)
|
|
581
|
+
draw_matrix_on_board(graph_matrix, which='both')
|
|
582
|
+
|
|
583
|
+
log("Generating local plot window...")
|
|
584
|
+
plot_function(func, -40, 40, args.points, f'Plot of {op}', label=op) # type: ignore
|
|
585
|
+
|
|
586
|
+
except Exception as e:
|
|
587
|
+
print(f"Error during math plot: {e}", file=sys.stderr)
|
|
588
|
+
sys.exit(1)
|
|
589
|
+
|
|
590
|
+
except KeyboardInterrupt:
|
|
591
|
+
print("\nCaught KeyboardInterrupt. Exiting and resetting modules.")
|
|
592
|
+
except Exception as e:
|
|
593
|
+
print(f"\nAn unhandled error occurred: {e}", file=sys.stderr)
|
|
594
|
+
import traceback
|
|
595
|
+
traceback.print_exc(file=sys.stderr)
|
|
596
|
+
finally:
|
|
597
|
+
# Check if 'args' exists, as it wouldn't if no args were given
|
|
598
|
+
if 'args' in locals():
|
|
599
|
+
# Don't reset if the command *was* reset
|
|
600
|
+
if not (args.command == 'led' and args.led_command == 'reset'):
|
|
601
|
+
log("Cleaning up... resetting modules.")
|
|
602
|
+
reset_modules()
|
|
603
|
+
|
|
604
|
+
# Always print done
|
|
605
|
+
print("Done.")
|
|
606
|
+
|
|
607
|
+
# --- Built-in Test Suite ---
|
|
608
|
+
|
|
609
|
+
def run_tests():
|
|
610
|
+
"""
|
|
611
|
+
Runs a built-in test suite by simulating command-line arguments.
|
|
612
|
+
This is triggered by running: ./cli.py --test
|
|
613
|
+
"""
|
|
614
|
+
original_argv = list(sys.argv)
|
|
615
|
+
print("--- STARTING BUILT-IN TEST SUITE ---")
|
|
616
|
+
|
|
617
|
+
# Find a valid function name from REGULAR_OPERATIONS for the test
|
|
618
|
+
# Default to 'sin' if the import failed for some reason
|
|
619
|
+
try:
|
|
620
|
+
math_test_func = list(REGULAR_OPERATIONS.keys())[0]
|
|
621
|
+
except (NameError, IndexError):
|
|
622
|
+
math_test_func = 'sin' # Fallback
|
|
623
|
+
|
|
624
|
+
# Define test commands (as lists of strings, just like sys.argv)
|
|
625
|
+
test_commands = [
|
|
626
|
+
['cli.py', 'led', 'version'],
|
|
627
|
+
['cli.py', '-v', 'text', 'vertical', 'TEST', '--hold', '1.5'], # Test verbose flag
|
|
628
|
+
['cli.py', 'sim', 'gof', '--board', 'blinker', '--steps', '25', '--delay', '0.05'],
|
|
629
|
+
['cli.py', 'sim', 'outer', '--b-rule', '3,6', '--s-rule', '2,3', '--steps', '25', '--delay', '0.05'],
|
|
630
|
+
['cli.py', 'sim', 'inner', '777', '--steps', '25', '--delay', '0.05'],
|
|
631
|
+
['cli.py', 'sim', 'bml-local', '--steps', '50'], # Will pause for user
|
|
632
|
+
# --- FIXED TEST CASE ---
|
|
633
|
+
['cli.py', 'math', 'plot', math_test_func], # Will pause for user
|
|
634
|
+
]
|
|
635
|
+
|
|
636
|
+
try:
|
|
637
|
+
for i, cmd in enumerate(test_commands):
|
|
638
|
+
print(f"\n--- TEST {i+1}/{len(test_commands)}: {' '.join(cmd)} ---")
|
|
639
|
+
sys.argv = cmd # Overwrite sys.argv
|
|
640
|
+
|
|
641
|
+
try:
|
|
642
|
+
main() # Run the main function with the new sys.argv
|
|
643
|
+
except SystemExit:
|
|
644
|
+
print(f"Test {i+1} completed with SystemExit (expected for -h or plot).")
|
|
645
|
+
except Exception as e:
|
|
646
|
+
print(f"--- TEST {i+1} FAILED: {e} ---")
|
|
647
|
+
|
|
648
|
+
# Pause briefly for visual tests on the matrix
|
|
649
|
+
# (The --hold test will pause itself)
|
|
650
|
+
if (cmd[1] == 'sim' and 'local' not in cmd[2]):
|
|
651
|
+
print("Pausing 2 seconds for visual check...")
|
|
652
|
+
time.sleep(2)
|
|
653
|
+
|
|
654
|
+
except Exception as e:
|
|
655
|
+
print(f"\n--- TEST SUITE ABORTED: {e} ---")
|
|
656
|
+
finally:
|
|
657
|
+
print("\n--- TEST SUITE FINISHED ---")
|
|
658
|
+
sys.argv = original_argv # Restore original sys.argv
|
|
659
|
+
reset_modules()
|
|
660
|
+
print("Original argv restored. Modules reset.")
|
|
661
|
+
|
|
662
|
+
|
|
663
|
+
if __name__ == "__main__":
|
|
664
|
+
# Check for the special --test flag
|
|
665
|
+
if len(sys.argv) == 2 and sys.argv[1] == '--test':
|
|
666
|
+
run_tests()
|
|
667
|
+
else:
|
|
668
|
+
# Run normal operation
|
|
669
|
+
main()
|
|
File without changes
|
|
File without changes
|