Skip to content

Commit

Permalink
Merge branch 'hzeller:master' into feature-remap-mapper
Browse files Browse the repository at this point in the history
  • Loading branch information
ledvinap committed Mar 27, 2023
2 parents 0c8b92a + 3a1c17a commit e48cf18
Show file tree
Hide file tree
Showing 11 changed files with 202 additions and 43 deletions.
23 changes: 19 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -639,11 +639,26 @@ switches on the hardware pulses feature for the Adafruit HAT/Bonnet.

### 64x64 with E-line on Adafruit HAT/Bonnet
There are LED panels that have 64x64 LEDs packed, but they need 5 address lines,
which is 1:32 multiplexing (they have an `E` address-line). The hardware of
the Adafruit HAT/Bonnet is not prepared for this, but it can be done with another
hardware mod.
which is 1:32 multiplexing (they have an `E` address-line). The first generation
of the Adafruit HAT/Bonnet was not prepared for this, but it can be done with another
hardware mod. Beginning October 2018, Adafruit began selling an updated version of
the HAT that supports 64x64 panels simply by bridging two pads on the PCB with solder.

It is a little more advanced hack, so is only really for people who are
You can identify which HAT you have by looking for the **Address E** pads, circled here:

<a href="https://cdn-learn.adafruit.com/assets/assets/000/063/005/original/led_matrices_addr-e-pad.jpg" target="_blank"><img src="https://cdn-learn.adafruit.com/assets/assets/000/063/005/original/led_matrices_addr-e-pad.jpg" height=80></a>

### New Adafruit RGB Matrix Hat (with Address E pads)

Look for the Address E pads located between the HUB75 connector and Pi camera cutout.

Melt a blob of solder between the center “E” pad the the “8” pad just above it
(for 64x64 matrices in the Adafruit shop)…*_or_* the “16” pad below (rare, for some
third-party 64x64 matrices…check datasheet).

### Old Adafruit HAT/Bonnet (without)

It is a little more advanced hack, so it is only really for people who are
comfortable with this kind of thing.
First, you have to figure out which is the input of the E-Line on your matrix
(they seem to be either on Pin 4 or Pin 8 of the IDC connector).
Expand Down
25 changes: 15 additions & 10 deletions include/led-matrix-c.h
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,16 @@ struct RGBLedMatrixOptions {
*/
int multiplexing;

/** The following boolean flags are off by default **/

/* Allow to use the hardware subsystem to create pulses. This won't do
* anything if output enable is not connected to GPIO 18.
* Corresponding flag: --led-hardware-pulse
*/
bool disable_hardware_pulsing; /* Flag: --led-hardware-pulse */
bool show_refresh_rate; /* Flag: --led-show-refresh */
bool inverse_colors; /* Flag: --led-inverse */

/* In case the internal sequence of mapping is not "RGB", this contains the
* real mapping. Some panels mix up these colors.
*/
Expand All @@ -138,16 +148,6 @@ struct RGBLedMatrixOptions {
*/
const char *panel_type; /* Corresponding flag: --led-panel-type */

/** The following are boolean flags, all off by default **/

/* Allow to use the hardware subsystem to create pulses. This won't do
* anything if output enable is not connected to GPIO 18.
* Corresponding flag: --led-hardware-pulse
*/
char disable_hardware_pulsing;
char show_refresh_rate; /* Corresponding flag: --led-show-refresh */
char inverse_colors; /* Corresponding flag: --led-inverse */

/* Limit refresh rate of LED panel. This will help on a loaded system
* to keep a constant refresh rate. <= 0 for no limit.
*/
Expand Down Expand Up @@ -189,6 +189,11 @@ struct RGBLedRuntimeOptions {
// e.g. you want to just create a stream output (see content-streamer.h),
// set this to false.
bool do_gpio_init;

// If drop privileges is enabled, this is the user/group we drop privileges
// to. Unless chosen otherwise, the default is "daemon" for user and group.
const char *drop_priv_user;
const char *drop_priv_group;
};

/**
Expand Down
19 changes: 13 additions & 6 deletions include/led-matrix.h
Original file line number Diff line number Diff line change
Expand Up @@ -395,36 +395,43 @@ class FrameCanvas : public Canvas {
struct RuntimeOptions {
RuntimeOptions();

int gpio_slowdown; // 0 = no slowdown. Flag: --led-slowdown-gpio
int gpio_slowdown; // 0 = no slowdown. Flag: --led-slowdown-gpio

// ----------
// If the following options are set to disabled with -1, they are not
// even offered via the command line flags.
// ----------

// Thre are three possible values here
// -1 : don't leave choise of becoming daemon to the command line parsing.
// If set to -1, the --led-daemon option is not offered.
// -1 : don't leave choise of becoming daemon to the command line
// parsing. If set to -1, the --led-daemon option is not offered.
// 0 : do not becoma a daemon, run in forgreound (default value)
// 1 : become a daemon, run in background.
//
// If daemon is disabled (= -1), the user has to call
// RGBMatrix::StartRefresh() manually once the matrix is created, to leave
// the decision to become a daemon
// after the call (which requires that no threads have been started yet).
// In the other cases (off or on), the choice is already made, so the thread
// is conveniently already started for you.
// In the other cases (off or on), the choice is already made, so the
// thread is conveniently already started for you.
int daemon; // -1 disabled. 0=off, 1=on. Flag: --led-daemon

// Drop privileges from 'root' to 'daemon' once the hardware is initialized.
// Drop privileges from 'root' to drop_priv_user/group once the hardware is
// initialized.
// This is usually a good idea unless you need to stay on elevated privs.
// -1, 0, 1 similar meaning to 'daemon' above.
int drop_privileges; // -1 disabled. 0=off, 1=on. flag: --led-drop-privs

// By default, the gpio is initialized for you, but if you run on a platform
// not the Raspberry Pi, this will fail. If you don't need to access GPIO
// e.g. you want to just create a stream output (see content-streamer.h),
// set this to false.
bool do_gpio_init;

// If drop privileges is enabled, this is the user/group we drop privileges
// to. Unless chosen otherwise, the default is "daemon" for user and group.
const char *drop_priv_user;
const char *drop_priv_group;
};

// Convenience utility functions to read standard rgb-matrix flags and create
Expand Down
6 changes: 6 additions & 0 deletions lib/bdf-font.cc
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,12 @@ int Font::DrawGlyph(Canvas *c, int x_pos, int y_pos,
if (g == NULL) g = FindGlyph(kUnicodeReplacementCodepoint);
if (g == NULL) return 0;
y_pos = y_pos - g->height - g->y_offset;

if (x_pos + g->device_width < 0 || x_pos > c->width() ||
y_pos + g->height < 0 || y_pos > c->height()) {
return g->device_width; // Outside canvas border. Bail out early.
}

for (int y = 0; y < g->height; ++y) {
const rowbitmap_t& row = g->bitmap[y];
for (int x = 0; x < g->device_width; ++x) {
Expand Down
4 changes: 4 additions & 0 deletions lib/led-matrix-c.cc
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ static struct RGBLedMatrix *led_matrix_create_from_options_optional_edit(
RT_OPT_COPY_IF_SET(daemon);
RT_OPT_COPY_IF_SET(drop_privileges);
RT_OPT_COPY_IF_SET(do_gpio_init);
RT_OPT_COPY_IF_SET(drop_priv_user);
RT_OPT_COPY_IF_SET(drop_priv_group);
#undef RT_OPT_COPY_IF_SET
}

Expand Down Expand Up @@ -138,6 +140,8 @@ static struct RGBLedMatrix *led_matrix_create_from_options_optional_edit(
ACTUAL_VALUE_BACK_TO_RT_OPT(daemon);
ACTUAL_VALUE_BACK_TO_RT_OPT(drop_privileges);
ACTUAL_VALUE_BACK_TO_RT_OPT(do_gpio_init);
ACTUAL_VALUE_BACK_TO_RT_OPT(drop_priv_user);
ACTUAL_VALUE_BACK_TO_RT_OPT(drop_priv_group);
#undef ACTUAL_VALUE_BACK_TO_RT_OPT
}

Expand Down
49 changes: 38 additions & 11 deletions lib/led-matrix.cc
Original file line number Diff line number Diff line change
Expand Up @@ -501,6 +501,20 @@ FrameCanvas *RGBMatrix::Impl::CreateFrameCanvas() {
result->framebuffer()->SetBrightness(params_.brightness);

created_frames_.push_back(result);

if (created_frames_.size() % 500 == 0) {
if (created_frames_.size() == 500) {
fprintf(stderr, "CreateFrameCanvas() called %d times; Usually you only want to call it once (or at most a few times) for double-buffering. These frames will not be freed until the end of the program.\n"
"Typical reasons: \n"
" * Accidentally called CreateFrameCanvas() inside your inner loop (move outside the loop. Create offscreen-canvas once, then re-use. See SwapOnVSync() examples).\n"
" * Used to pre-compute many frames (use led_matrix::StreamWriter instead for such use-case. See e.g. led-image-viewer)\n",
(int)created_frames_.size());
} else {
fprintf(stderr, "FYI: CreateFrameCanvas() now called %d times.\n",
(int)created_frames_.size());
}
}

return result;
}

Expand Down Expand Up @@ -619,21 +633,33 @@ static bool drop_privs(const char *priv_user, const char *priv_group) {
return true;
}

struct group *g = getgrnam(priv_group);
if (g == NULL) {
perror("group lookup.");
return false;
if (priv_user == nullptr || priv_user[0] == 0) priv_user = "daemon";
if (priv_group == nullptr || priv_group[0] == 0) priv_group = "daemon";

gid_t gid = atoi(priv_group); // Attempt to parse as GID first
if (gid == 0) {
struct group *g = getgrnam(priv_group);
if (g == NULL) {
perror("group lookup.");
return false;
}
gid = g->gr_gid;
}
if (setresgid(g->gr_gid, g->gr_gid, g->gr_gid) != 0) {
if (setresgid(gid, gid, gid) != 0) {
perror("setresgid()");
return false;
}
struct passwd *p = getpwnam(priv_user);
if (p == NULL) {
perror("user lookup.");
return false;

uid_t uid = atoi(priv_user); // Attempt to parse as UID first.
if (uid == 0) {
struct passwd *p = getpwnam(priv_user);
if (p == NULL) {
perror("user lookup.");
return false;
}
uid = p->pw_uid;
}
if (setresuid(p->pw_uid, p->pw_uid, p->pw_uid) != 0) {
if (setresuid(uid, uid, uid) != 0) {
perror("setresuid()");
return false;
}
Expand Down Expand Up @@ -678,7 +704,8 @@ RGBMatrix *RGBMatrix::CreateFromOptions(const RGBMatrix::Options &options,
// realtime thread that usually requires root to be established.
// Double check and document.
if (runtime_options.drop_privileges > 0) {
drop_privs("daemon", "daemon");
drop_privs(runtime_options.drop_priv_user,
runtime_options.drop_priv_group);
}

return new RGBMatrix(result);
Expand Down
19 changes: 18 additions & 1 deletion lib/options-initialize.cc
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ RuntimeOptions::RuntimeOptions() :
#endif
daemon(0), // Don't become a daemon by default.
drop_privileges(1), // Encourage good practice: drop privileges by default.
do_gpio_init(true)
do_gpio_init(true),
drop_priv_user("daemon"),
drop_priv_group("daemon")
{
// Nothing to see here.
}
Expand Down Expand Up @@ -226,6 +228,15 @@ static bool FlagInit(int &argc, char **&argv,
ropts->drop_privileges = bool_scratch ? 1 : 0;
continue;
}
if (ConsumeStringFlag("drop-priv-user", it, end,
&ropts->drop_priv_user, &err)) {
continue;
}
if (ConsumeStringFlag("drop-priv-group", it, end,
&ropts->drop_priv_group, &err)) {
continue;
}

if (strncmp(*it, OPTION_PREFIX, OPTION_PREFIX_LEN) == 0) {
fprintf(stderr, "Option %s starts with %s but it is unknown. Typo?\n",
*it, OPTION_PREFIX);
Expand Down Expand Up @@ -357,6 +368,12 @@ void PrintMatrixFlags(FILE *out, const RGBMatrix::Options &d,
"\t--led-%sdrop-privs : %srop privileges from 'root' "
"after initializing the hardware.\n",
on ? "no-" : "", on ? "Don't d" : "D");
fprintf(out, "\t--led-drop-priv-user : "
"Drop privileges to this username or UID (Default: '%s')\n",
r.drop_priv_user);
fprintf(out, "\t--led-drop-priv-group : "
"Drop privileges to this groupname or GID (Default: '%s')\n",
r.drop_priv_group);
}
}

Expand Down
21 changes: 19 additions & 2 deletions utils/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ the [toplevel readme](../README.md#changing-parameters-via-command-line-flags)
--led-slowdown-gpio=<0..4>: Slowdown GPIO. Needed for faster Pis/slower panels (Default: 1).
--led-daemon : Make the process run in the background as daemon.
--led-no-drop-privs : Don't drop privileges from 'root' after initializing the hardware.
--led-drop-priv-user : Drop privileges to this username or UID (Default: 'daemon')
--led-drop-priv-group : Drop privileges to this groupname or GID (Default: 'daemon')
```
</details>

Expand Down Expand Up @@ -156,6 +158,7 @@ usage: ./text-scroller [options] <text>
Takes text and scrolls it with speed -s
Options:
-f <font-file> : Path to *.bdf-font to be used.
-i <textfile> : Input from file.
-s <speed> : Approximate letters per second.
Positive: scroll right to left; Negative: scroll left to right
(Zero for no scrolling)
Expand All @@ -173,16 +176,30 @@ General LED matrix options:
<... all the --led- options>
```

You need to specify a font for the tool to use. We are using BDF-fonts, which are bitmap fonts
nicely suited for low-resolution displays such as ours. A few fonts you find in the
You need to specify a font for the tool to use. We are using BDF-fonts,
which are bitmap fonts nicely suited for low-resolution displays such as ours.
A few fonts you find in the
[../fonts](../fonts) directory. The [README.md](../fonts/README.md) there also describes
how to make your own.

The program directly takes the text found on the command line and scrolls
it over the screen.
Alternatively, with the `-i` option, a file is read with the text to be
scrolled. The file is watched, and if the content changes, the `text-scroller`
automatically updates the scroll text.

##### Examples

```bash
# (use your --led-rows, --led-chain and --led-parallel suited for your setup)

# Print simple 'Hello world'
sudo ./text-scroller -f ../fonts/9x18.bdf "Hello World ♥"

# Read input from text file (will pick up changes when file changes)
echo "Hello world" > input.txt
sudo ./text-scroller -f ../fonts/9x18.bdf -i input.txt

# Red (-C) text on a display with 4 chained displays. Notice you can use UTF-8 characters
# if they are supported by the font.
sudo ./text-scroller -f ../fonts/9x18.bdf -C255,0,0 --led-chain=4 "Hello World ♥"
Expand Down
9 changes: 7 additions & 2 deletions utils/led-image-viewer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ static void StoreInStream(const Magick::Image &img, int delay_time_us,
for (size_t y = 0; y < img.rows(); ++y) {
for (size_t x = 0; x < img.columns(); ++x) {
const Magick::Color &c = img.pixelColor(x, y);
if (c.alphaQuantum() < 256) {
if (c.alphaQuantum() < 255) {
scratch->SetPixel(x + x_offset, y + y_offset,
ScaleQuantumToChar(c.redQuantum()),
ScaleQuantumToChar(c.greenQuantum()),
Expand Down Expand Up @@ -251,6 +251,11 @@ int main(int argc, char *argv[]) {

RGBMatrix::Options matrix_options;
rgb_matrix::RuntimeOptions runtime_opt;
// If started with 'sudo': make sure to drop privileges to same user
// we started with, which is the most expected (and allows us to read
// files as that user).
runtime_opt.drop_priv_user = getenv("SUDO_UID");
runtime_opt.drop_priv_group = getenv("SUDO_GID");
if (!rgb_matrix::ParseOptionsFromFlags(&argc, &argv,
&matrix_options, &runtime_opt)) {
return usage(argv[0]);
Expand Down Expand Up @@ -421,7 +426,7 @@ int main(int argc, char *argv[]) {
CopyStream(&reader, global_stream_writer, offscreen_canvas);
}
} else {
err_msg = "Can't read as image or compatible stream";
err_msg += "; Can't read as image or compatible stream";
delete file_info->content_stream;
delete file_info;
file_info = NULL;
Expand Down
Loading

0 comments on commit e48cf18

Please sign in to comment.