What GPIO does
GPIO (general-purpose input/output) lines are the pins on a Player's board that an application can read and drive one at a time. A line is an input that reads high or low, or an output that the application sets high or low.
Inputs hear the room:
- A push button, such as a "call for service" or "next page" button.
- A door or window contact, a reed switch or a limit switch.
- Any sensor with a dry contact, such as a flow switch or a relay output on a motion detector.
Outputs act on it:
- A relay that releases a door strike, switches a stack light or sounds a buzzer.
- An LED that lights a button.
The application on the screen does the work through screenrig.device.gpio and tells your agent what happened with emit, so "service requested" appears in the project event log. Your agent builds the application, uploads it and places it on the screen like any other. The hardware page shows what else an application can reach.
Which Players have GPIO
| Player | GPIO | Input pull | Output idle level | Pin numbers |
|---|---|---|---|---|
| Raspberry Pi 3, 4 and 5 (Linux Player) | Yes, on the 40-pin header. The Player finds the header chip by itself | "up", "down" or "none" | Yes | The BCM numbers |
| Other Linux boards | Yes, with SCREENRIG_GPIO_CHIP | "up", "down" or "none" | Yes | The line numbers of that chip |
| Android and Amazon Signage Stick | Where the device exposes a writable sysfs GPIO chip | No: add a resistor | Yes | The line numbers of that chip |
| BrightSign | Yes: pins 0 to 7 on the GPIO connector, or a BrightSign USB-to-GPIO adapter | Built-in pull-ups | Yes | Pins 0 to 7, and the ranges in the BrightSign section |
| macOS, Windows, the Player web app, Samsung Smart Signage (Tizen), LG webOS Signage | No | No | No | No |
The flag goes to the page's controller, or to the application that is alone on its page. An application sees capabilities["device.gpio"] as true only on a Player and a device that has GPIO, so one build also runs on a screen without it.
Set up the Player
Raspberry Pi
A Raspberry Pi needs no setup. The Linux Player opens the header chip of a Pi 3, 4 or 5 by itself, and a pin is its BCM number. The Player's user needs access to /dev/gpiochip*. On Raspberry Pi OS the default user belongs to the gpio group, which gives it. A --kiosk install adds its account to the video and i2c groups only, so give a different --kiosk-user account the gpio group and restart the Player:
sudo usermod -aG gpio KIOSK_USEROther Linux boards
On another board, name the chip that carries the lines with SCREENRIG_GPIO_CHIP, in the Player's launch environment:
SCREENRIG_GPIO_CHIP=/dev/gpiochip4For a --kiosk install, add it to the service with sudo systemctl edit screenrig-player-kiosk.service:
[Service]
Environment=SCREENRIG_GPIO_CHIP=/dev/gpiochip4A pin is then a line number of that chip. The Player's user needs access to the chip's device file.
Android
Android has GPIO where the device exposes a writable sysfs GPIO chip, such as a signage board or a custom image. A pin is the chip's global line number. A standard TV box has no such chip, and the application sees device.gpio as false. The Player holds a line only while the application uses it. Android cannot set an input's pull: wire a resistor, as in Wire a button.
Choose a pin
pin is an integer from 0 to 255: the line number of the chip. On a Raspberry Pi it is the BCM number, the GPIO number the Pi's documentation names, and not the position of the pin on the header. GPIO17 is physical pin 11. Common lines:
BCM number (the pin) | Physical pin | Note |
|---|---|---|
| 17 | 11 | A good first line |
| 27 | 13 | |
| 22 | 15 | |
| 23 | 16 | |
| 24 | 18 | |
| 25 | 22 | |
| 5 | 29 | |
| 6 | 31 | |
| 12 | 32 | |
| 13 | 33 | |
| 16 | 36 | |
| 26 | 37 | |
| 20 | 38 | |
| 21 | 40 | |
| 2 and 3 | 3 and 5 | I2C lines with fixed pull-ups; leave them for I2C |
| 14 and 15 | 8 and 10 | The serial console's lines |
| 0 and 1 | 27 and 28 | Reserved for HAT identification |
Ground is physical pins 6, 9, 14, 20, 25, 30, 34 and 39, and 3.3 V is pins 1 and 17. The Raspberry Pi documentation lists the whole header. A line another program already holds rejects pin_busy, and a line the chip does not have rejects pin_not_found.
Drive a relay
Drive a relay through a relay board, and never straight from a pin. A pin supplies a few milliamps at 3.3 V, which cannot switch a relay coil, a door strike, a stack light or a buzzer, and a coil's voltage spike can damage the board. A relay board has the transistor and the coil's protection on it.
5V (pin 2) -------------- VCC |
GND (pin 6) ------------- GND | relay board
GPIO27 (pin 13) --------- IN1 |
| COM ---- supply for the load
| NO ---- the load (door strike, light, buzzer)Power the load from its own supply through the relay's contacts. Choose a board whose input triggers at 3.3 V.
const sr = window.screenrig;
await sr.device.gpio.write(27, true); // relay on
await sr.device.gpio.write(27, false); // relay offMost boards energise on high. Many cheap boards are active low: the relay energises when the input is pulled low. Pass idle: true for an active-low board so a page that leaves releases the relay as off. See Idle level.
await sr.device.gpio.write(27, false, { idle: true }); // active-low board: relay on
await sr.device.gpio.write(27, true, { idle: true }); // relay offAn output on a BrightSign is 3.3 V through 100 Ω, so a BrightSign drives a relay board in the same way.
3.3 V only
Every GPIO line is 3.3 V logic. A Raspberry Pi's pins are not 5 V tolerant, and 5 V on a pin can damage the board. Connect dry contacts only: a button, a switch or a relay output that carries no voltage of its own. A sensor that outputs 5 V, 12 V, 24 V or mains voltage needs a level shifter, an optocoupler or a relay between it and the pin.
Debounce
A mechanical contact bounces: for a few milliseconds a press flickers between high and low. The Player filters contact bounce, so the handler hears a press as one change and the release as one more. A Linux or Raspberry Pi line ignores changes that settle within about 20 ms. No code of yours is needed, and a pulse shorter than that is not a use for GPIO input.
Hear a line
onChange(pin, handler, options) watches a line. The handler receives the level as a boolean, true for high. The first call carries the level the line has when the Player starts watching, and each later call is a change. The call returns a function that stops watching.
const stop = window.screenrig.device.gpio.onChange(17, function (high) {
doorOpen = high;
}, { pull: "up" });At most 16 lines are watched at once, and a 17th throws invalid_request. Two handlers on one line share its pull: a second handler with a different pull throws invalid_request at once.
Idle level
write(pin, value, options) takes an idle option, the level the Player drives the line to when the page releases it. The default is low. The page releases a line when it leaves the screen, when its context ends and when it calls screenrig.destroy(). The Player then releases the line. The latest write for a pin sets its idle level.
This is why a relay does not stay energised after the page goes. Set idle to the level that means "off" for your board: low for most boards, high for an active-low board.
When the page leaves
What a page holds ends with the page. When it leaves the screen, its inputs stop and every output goes to its idle level and is released. When the Player stops or the device loses power, the lines go with it. Plan the load for that: choose a door strike that locks or unlocks the way the building needs when the power fails.
Limits and errors
- A pin is an integer from 0 to 255.
- At most 16 lines are watched at once.
writerejectsinvalid_requestfor a badpin,valueoridle,pin_not_foundfor a line the chip does not have,pin_busyfor a line another process holds andgpio_unavailablewhen the Player can open no GPIO chip.onChangethrowsinvalid_requestfor a 17th line or a conflictingpull. A refused watch is logged, and the handler never runs.- A call checks
inert,not_supportedandcapability_deniedbefore it sends anything. The SDK reference lists the order. Awritewithidlerejectsnot_supportedon a Player that cannot set it.
Example: call for service
A button on GPIO17, wired to ground, tells your agent that a customer wants help, and an LED or buzzer relay on GPIO27 answers for five seconds. The first reading is the level the line already has, so the handler acts only on a change from high to low.
const sr = window.screenrig;
function active() {
return sr.readyState === "active";
}
async function start() {
if (!sr) return;
await sr.waitUntilActive();
await sr.ready();
if (!sr.supports("device.gpio") || !sr.capabilities["device.gpio"]) return;
let wasHigh = null;
sr.device.gpio.onChange(17, function (high) {
const pressed = wasHigh === true && high === false;
wasHigh = high;
if (!pressed || !active()) return;
sr.emit("service.requested");
document.getElementById("message").hidden = false;
sr.device.gpio.write(27, true).catch(function () {});
setTimeout(function () {
document.getElementById("message").hidden = true;
if (active()) sr.device.gpio.write(27, false).catch(function () {});
}, 5000);
}, { pull: "up" });
}
start();The page leaving the screen releases line 27 at its idle level, low unless you pass idle, so a button pressed just before the page goes does not leave the relay on.
Your agent reads each press from the event log:
screenrig events list --type application.event --screen-id scr_SCREENExample: door-open input
A reed switch on GPIO17, wired to ground, closes when the door is shut, so the line reads low with the door closed and high with it open. The page shows a notice while the door is open and tells your agent.
const sr = window.screenrig;
const notice = document.getElementById("door-notice");
function show(open) {
notice.hidden = !open;
document.body.classList.toggle("door-open", open);
}
async function start() {
show(false);
if (!sr) return;
await sr.waitUntilActive();
await sr.ready();
if (!sr.supports("device.gpio") || !sr.capabilities["device.gpio"]) return;
sr.device.gpio.onChange(17, function (high) {
show(high);
if (sr.readyState === "active") sr.emit(high ? "door.opened" : "door.closed");
}, { pull: "up" });
}
start();BrightSign
A BrightSign player runs the same API. Pins 0 to 7 are the GPIO connector's lines, the connector on the XT, XD and HD models (a DA-15 socket), pin 0 is the connector's line 0 and pin 7 its line 7. Its inputs have fixed pull-ups, so pull is not offered and a button wired to ground works as it is. idle works as on every Player. Outputs are 3.3 V through 100 Ω: drive a relay through a relay board.
A BrightSign adds two more banks of lines through USB:
| Pins | What they are |
|---|---|
| 0 to 7 | The GPIO connector |
| 8 to 39 | USB GPIO expanders, 8 pins each, up to 4: pins 8 to 15 are the first expander, 16 to 23 the second, and so on |
| 100 to 110 | A BP200 or BP900 button board, number 0: pin 100+k is button k as an input, and its LED as an output |
| 120 to 130, 140 to 150 and 160 to 170 | Button boards 1, 2 and 3, the same way |
A write to pin 100+k lights the LED of button k, and a watch on pin 100+k hears the button. A board or an expander that is not connected makes its pins reject pin_not_found.
Try it in app dev
screenrig app dev simulates GPIO. The harness shows a GPIO chip switch and three simulated lines: 17 (low), 27 (high) and 22, which another process holds and so answers pin_busy. The examples above use 17 and 27. Click a line's level to change it, and your application hears the change when it watches the line. A write from the application moves the line, and the panel shows the idle level it set. The harness accepts pull and idle, so a build runs there before it reaches a screen. Develop locally covers the controls.
Reference
screenrig.device.gpio.write()andscreenrig.device.gpio.onChange()in the SDK reference.- Hardware an application can use: every Player and every device capability.
- Raspberry Pi digital signage: the Pi as a screen.
- Linux and Raspberry Pi: install the Player.
Questions
- Can a screen drive a relay?
- Yes, through a relay board. An application sets a line high or low, and the board switches the door strike, stack light or buzzer. Never drive a relay straight from a pin.
- Does a button need a resistor?
- On a Raspberry Pi or another Linux board, no: ask for the pull-up and wire the button to ground. On Android, add a resistor of about 10 kΩ to 3.3 V. A BrightSign has pull-ups built in.
- Which number do I pass for a pin?
- On a Raspberry Pi, the BCM number: GPIO17 is 17 and sits on physical pin 11. On another board, the line number of its GPIO chip. On a BrightSign, the pin ranges in the BrightSign section.
- What happens to a relay when the page changes?
- The Player drives the line to its idle level, low unless the application chose another, and releases it. Choose high for an active-low relay board so the relay is off.
- Does the application need to run all the time?
- The application listens while its page is on the screen. When the page leaves, the inputs stop and the outputs go to their idle level.
- What does it cost?
- screenRIG is free. The board, the button and the relay are your cost.