Feature: Centralized ‘rgbmatrix’ command-line interface (CLI)
Hello there, I have some extra free time over the holidays and I’m looking to develop a feature that I think this project needs. I make heavy use of this architecture in my own personal project, but I’m looking to abstract away the generalized pattern into the rgbmatrix library. This can make it universally available for all users, as well as to promote the use of a single CLI layer that hides internal implementation details.
Thankfully I’ve already done what I consider to be a good majority of this work, but I’m looking to collect requirements before submitting a polished version. What I want to avoid personally is submitting my pull-request of a final version, but receiving a lot of critique in the PR stage. Then having to continually refactor and getting caught up in back-and-forth dialogue.
So in the spirit of efficiency and time-preservation, I’d like to submit an overall high-level idea of this feature and collect feedback from other developers to help unify the vision of a CLI that works for everybody.
But before I do so, I’d like to first address potential concerns beforehand:
Maintenance Burden:
Having something like this it can be argued it is extra fluff that can become a maintenance burden on the few existing developers/maintainers of this project. While I commit to be responsible for this feature of the codebase, I’d like to mention that a lot of repetitive and common issues submitted by newbies & stuck users looking for help can be reduced with a simplified and automated command interface.
If someone is stuck on an error in their terminal, this interface should provide tools to output the state of their environment/system and potential corrective actions. It should scan and handle these potential error-points with clear messaging that can shift the burden of help back onto the user themselves into a form of self-sufficiency. It should gracefully handle and guide them simply what is wrong and what command to run next as opposed to an uncontrolled massive dump of errors that prompt them to outreach for somebody to untangle. This potentially comes from missing system packages, bash scripts, libraries and dependencies which can be verbose and overwhelming. By separating things into domains of concern, we can add automated repeatable commands and guardrails that address them.
Feature Creep:
While I accept this library is just a driver for led matrices at its core, the users of this project typically use it in the context of a wider personal LED project they are working on with a Raspberry Pi IoT device. It would be beneficial to bundle common generalized tooling that serves the majority of the users. This can expand adoption while also reducing repetitive outreach to project maintainers; with everyone having the ultimate goal of an LED display in working order.
I’ll now describe the vision for the implementation below:
Concept of Views
A view described below is a display program. It’s written in C++, C# or Python. In the context of using this command interface, all views will be referenced by filename only. There is no concept of directories. The CLI is intelligent enough to scan directories to find the executable view. It searches the library folders (utils, examples, bindings) and optional directories provided in an environment variable (RGBMATRIX_DIR_VIEW=/home/pi/led-project/view). That allows you to plugin your own view executables so the CLI can find them.
Command Architecture
rgbmatrix is the top-level namespace / bash executable. Each of the children below it are domains. They house commands related to their domain of concern.
rgbmatrix
view
assert
build
cache
cpu
debug
depend
env
flag
gpu
help
install
os
perm
python
service
sound
status
test
uninstall
update
vendor
__cleanup # cleanup handler
__dispatch # command-dispatcher
__main # entry-point
Usage Examples:
Note: If you prefer to skip right to the pseudo-implementation details instead of CLI usage, scroll past this.
# view
rgbmatrix view assert <view> # assert a view is in workable condition before execution
rgbmatrix view exists <view> # scan and check a view exists
rgbmatrix view build <view> # build (compile/link) an individual view
rgbmatrix view hook <view> # run external bash script hooks on event lifecycle (boot, build, start, stop)
rgbmatrix view start <view> # start a view
rgbmatrix view stop # kill all running rgbmatrix/view processes in daemon or interactive mode
# assertions
rgbmatrix assert root-user
rgbmatrix assert env-dev
# build (compile/link stages)
rgbmatrix build all
rgbmatrix build led-image-viewer
rgbmatrix build my-custom-view # in .env file, define RGBMATRIX_DIR_VIEW=/home/pi/my-led-project/view
# data/file caching for view programs
rgbmatrix cache set-ram # pipe data to /tmp folder (RAM store cleared on OS reboot)
rgbmatrix cache set-fs # pipe data to cache/ folder (Permanent filesystem store)
# cpu (perform rpi-rgb-led-matrix tweaks relating to the CPU)
rgbmatrix cpu isolate # set isolcpus=3 in /boot/firmware/cmdline.txt
# debugging info
rgbmatrix debug # output debugging info
# environment variables and setup
rgbmatrix env init # export .env environment variables for view programs
# flag (build common repetitive flags for view programs from a central config)
# Ex: --led-cols=64 --led-rows=32
rgbmatrix flag <view> # outputs full command-line flags to append to view program
# gpu (perform actions on the Broadcom VideoCore GPU)
rgbmatrix gpu
# help
rgbmatrix help
# install
rgbmatrix install # perform project-wide installation and conditionally python/c# installs
# os
rgbmatrix os # Run related commands on the underlying OS required by the rpi-rgb-led-matrix library
# perm
rgbmatrix perm # handle common permission-based issues (chmod/chown)
# python
rgbmatrix python # run common python actions
rgbmatrix python -v # get current python version
rgbmatrix python venv # handle setup of python virtual environment
# will be nice to convert this project from os-based python to python virtual environment.
# service (custom OS systemd services - you want to run your views as an automated service)
rgbmatrix service start
rgbmatrix service stop
rgbmatrix service restart
rgbmatrix service reload
# sound
rgbmatrix sound onboard-disable # disable onboard 'snd_bcm2835' module
rgbmatrix sound onboard-enable # enable onboard 'snd_bcm2835' module
rgbmatrix sound onboard-check # output debug info on dtparam=audio status
# status
rgbmatrix status # Output name/status of current running view process in daemon or interactive mode
# test
rgbmatrix test # run all bash test suites in test/ directory
rgbmatrix test unit # run all bash unit tests in test/ directory
rgbmatrix test e2e # run all bash e2e tests in test/ directory
# uninstall
rgbmatrix uninstall # run system-wide uninstall and return OS to pre-installation state
# update
rgbmatrix update # update resources
# vendor
rgbmatrix vendor install # download and extract all external vendor packages to a project folder
Pseudo-code Implementation (Rough draft template):
# Domain functions
rgbmatrix::view() {
case "$1"
assert)
# Run and confirm assumptions for a view before attempting to execute it
;;
exists)
# Scan all known directories and output successful exit code if view exists
;;
build)
# Compile & link an individual view program (C++/C# only, not needed for Python)
;;
hook)
# Run any individual bash hook scripts before executing the main view program
# Useful for preparing content for your view before running it
# This seperates the domain of data-fetching from the primary rendering responsibility of the view
;;
start)
# Execution path in this routine:
# - assert # Confirm assumptions (root-user, permissions, directories exist)
# - stop # Stop all existing processes
# - env init # Init environment variables for view program
# - cache init # Init cache directories for writing
# - build <view> # Build the individual view program
# - flag all # Generate all command-line arguments for the view program
# - *determine execution pathway* (C++, C#, Python?)
# - view hook # Run all bash hooks for the program
# - execute view program by its "filename.ext" identifier (no directories)
# - spawn child process for the view program
# - scan all known directories:
# - utils/
# - examples-api-use/
# - bindings/c#/examples
# - bindings/python/samples
# - User-supplied .env ENV variable (Ex: RGBMATRIX_DIR_VIEW=/home/pi/my-led-project/views)
# - *monitor process* # Monitor state of process/PID and restart if failed or exited
;;
stop)
# Identify a view program by its name or process ID (PID) and kill it
# Normally in daemon mode, or if you have a leaky process that accidentally went chaotic.
;;
esac
}
rgbmatrix::assert() {
case "$1" in
root-dir)
# Confirm the current working directory is from the rpi-rgb-led-matrix root folder
;;
root-user)
# Confirm whether we are in root permissions
# Some commands can be ran without root, so we don't want to force it
# But for running view programs we want to conditionally assert for root user
;;
env-dev)
# Assert whether we are operating in `RGBMATRIX_ENV=dev` mode
;;
env-prod)
# Assert whether we are operating in `RGBMATRIX_ENV=prod` mode
;;
exists)
view_name="$2"
# Scan all known directories where view programs live and check whether a view exists there
;;
esac
}
rgbmatrix::build() {
case "$1" in
all)
make all
;;
dev)
make dev
;;
prod)
make prod
;;
view)
view_name="$2"
view_dir="..." # Scan known directories for view programs and run their Makefiles if C++
make -C "$view-dir"
;;
esac
}
rgbmatrix::cache() {
case "$1" in
init)
mkdir -p "/tmp/rgbmatrix.cache"
;;
set-ram)
# Write temporary cache file to /tmp system folder
# Stored in RAM, gets cleared on OS reboot
;;
set-fs)
# Write permanent file to hard disk / SD card
;;
get)
# Fetch cache item by "filename.ext" ID
# Scan known cache directories and output file contents
;;
clear)
# Wipe caches clean
;;
esac
}
rgbmatrix::cpu() {
case "$1" in
isolate)
sed -i -e "s/ isolcpus=[^ ]*//g" -e "s/\$/ isolcpus=3/" "/boot/firmware/cmdline.txt"
;;
esac
}
rgbmatrix::debug() {
# Perform debugging actions
# Scan/enable logging
}
rgbmatrix::depend() {
case "$1" in
install)
# Install base OS package dependencies
# Conditionally install Python package dependencies
# Conditionally install C# dependencies
;;
esac
}
rgbmatrix::env() {
# Parse ".env" file in project root directory
# export environment variables for use in view programs
# EX: "export RGBMATRIX_DIR_FONT=fonts RGBMATRIX_DIR_CACHE=/tmp RGBMATRIX_DEBUG=1"
# - Use RGBMATRIX_DIR_FONT instead of hardcoded references to directories
# - Use RGBMATRIX_DIR_CACHE in view programs to locate where to write cache files
# - Use RGBMATRIX_DEBUG to conditionally log debug statements
}
rgbmatrix::flag() {
# Read config file "manifest.yaml"
# Assemble command-line string of flags for the given view program
# EX (when running): rgbmatrix.sh view led-image-viewer
# This function will output the repetitive runtime flags "--led-gpio-mapping=regular --led-cols=64 --led-rows-32"
# As well as sensible/common default flags for the requested view program
# EX: -C -f rpi-rgb-led-matrix/fonts (font dirs)
# The common defaults for each view program can be mapped in manifest.yaml for what you prefer
# The goal here is to eliminate repetition having to type out command flags, you store the common ones in a config
# And each view program has a central config to list its default params
# You can also add/override input params at the end of your CLI command and it will be appended with the generated ones
}
rgbmatrix::gpu() {
# Perform actions on the Broadcom VideoCore GPU
}
rgbmatrix::help() {
# Output command-line usage and help
}
rgbmatrix::install() {
# Run system-wide uninstallation and return OS to state it was in prior to rgbmatrix install
}
rgbmatrix::os() {
# Run related commands on the underlying OS required by the rpi-rgb-led-matrix library
}
rgbmatrix::perm() {
# Handle common permission-based issues (chmod/chown)
}
rgbmatrix::python() {
# Run common python actions including
# - virtual environment
# - install
# - build
# - version-check
# - package/dependency installation
}
rgbmatrix::service() {
# Run commands relating to OS systemd services (install, start, stop, restart, reload)
}
rgbmatrix::sound() {
case "$1" in
onboard-disable)
printf "blacklist snd_bcm2835" >> "/etc/modprobe.d/blacklist-rgbmatrix.conf"
;;
onboard-enable)
if [[ -f "/etc/modprobe.d/blacklist-rgbmatrix.conf" ]]; then
sed -i "s/blacklist snd_bcm2835//g" "/etc/modprobe.d/blacklist-rgbmatrix.conf"
fi
;;
onboard-check)
# Output debug info on dtparam=audio status
;;
sound-check)
# Probably not needed. But can pulse-check test audio through external or onboard sound device
;;
esac
}
rgbmatrix::status() {
# Output name/status of the current running view if it is in daemon mode
}
rgbmatrix::test() {
case "$1" in
unit)
# run unit test in test/ folder
;;
e2e)
# Spawn a process of a view program in a subshell and verify that its stdout content is working as expected
# This way utils, demos & user-generated view programs can be tested programattically without human intervention
;;
esac
}
rgbmatrix::uninstall() {
# Run system-wide uninstallation and return OS to state it was in prior to rgbmatrix install
}
rgbmatrix::update() {
# Run git pull, or other update routines from a remote server
}
rgbmatrix::vendor() {
# If you have any external vendor libraries or external sources you want to include
# You can supply a "vendor" key-value mapping in the manifest.yaml file that downloads and extracts them there
}
# Cleanup handler
rgbmatrix::__cleanup() {
# When the main script terminates by interupt or script error
# Append a log of the line_number, command, exit_code to error.log in project directory
}
# Dispatcher (command router)
rgbmatrix::__dispatch() {
domain="$1"
cmd_args=""${@:2}"
domain_list="assert build cpu debug depend env flag gpu help install os perm python service sound test uninstall update view"
# Redirect bad domain calls to help domain
if [[ -z "$domain" ]]; then rgbmatrix::help; return; fi
# Route command
if contains_word "$domain_list" "$domain"; then
domain_sig="rgbmatrix::$domain"
$domain_sig $cmd_args
fi
printf "Unknown command: $domain"
rgbmatrix::help
}
# Main (entry point)
rgbmatrix::__main() {
# Entry point
# Includes
# Run assertions
rgbmatrix::__dispatch "@"
}
rgbmatrix::__main() "$@"
Thanks for your time and patience reading this and I welcome any suggestions and feedback!