文档

GPIO 上的按钮和继电器

把按钮、门触点或继电器板连接到屏幕的 GPIO 引脚,屏幕上的应用就能获知按钮、驱动继电器,并向你的智能体报告。

GPIO 的作用

GPIO(通用输入/输出)线路是播放器主板上的引脚,应用可以逐条读取和驱动。线路可以是读取高电平或低电平的输入,也可以是由应用设为高电平或低电平的输出。

输入感知房间:

  • 按钮,例如“呼叫服务”或“下一页”按钮。
  • 门或窗触点、干簧管开关或限位开关。
  • 任何带干触点的传感器,例如流量开关或移动探测器上的继电器输出。

输出据此动作:

  • 释放电控门锁、切换信号灯塔或让蜂鸣器鸣响的继电器。
  • 点亮按钮的 LED。

屏幕上的应用通过 screenrig.device.gpio 完成工作,并用 emit 告知你的智能体发生了什么,因此“已请求服务”会进入项目事件日志。你的智能体像处理其他应用一样构建应用、上传并放置到屏幕上。硬件页面展示应用还能访问什么。

哪些播放器有 GPIO

播放器GPIO输入上下拉输出空闲电平引脚编号
Raspberry Pi 3、4 和 5(Linux 播放器)是,在 40 针排针上。播放器会自动找到排针芯片"up","down" 或 "none"是BCM 编号
其他 Linux 板子是,使用 SCREENRIG_GPIO_CHIP"up","down" 或 "none"是该芯片的线路编号
Android 和 Amazon Signage Stick设备开放可写 sysfs GPIO 芯片的情况否:请加电阻是该芯片的线路编号
BrightSign是:GPIO 接口上的引脚 0 到 7,或 BrightSign USB 转 GPIO 适配器内置上拉是引脚 0 到 7,以及 BrightSign 一节中的范围
macOS、Windows、播放器 Web 应用、Samsung Smart Signage (Tizen)、LG webOS Signage否否否否

标志交给页面的控制者,或交给独占其页面的应用。只有在具备 GPIO 的播放器和设备上,应用才会看到 capabilities["device.gpio"] 为 true,因此同一个构建也能在没有 GPIO 的屏幕上运行。

设置播放器

Raspberry Pi

Raspberry Pi 无需设置。Linux 播放器会自行打开 Pi 3、4 或 5 的排针芯片,引脚即其 BCM 编号。播放器的用户需要有权访问 /dev/gpiochip*。在 Raspberry Pi OS 上,默认用户属于 gpio 组,已具备该权限。--kiosk 安装只会把其账户加入 video 和 i2c 组,因此请为不同的 --kiosk-user 账户加入 gpio 组,然后重启播放器:

sudo usermod -aG gpio KIOSK_USER

其他 Linux 板子

在其他板子上,请在播放器的启动环境中用 SCREENRIG_GPIO_CHIP 指定承载这些线路的芯片:

SCREENRIG_GPIO_CHIP=/dev/gpiochip4

对于 --kiosk 安装,请用 sudo systemctl edit screenrig-player-kiosk.service 将其添加到服务中:

[Service]
Environment=SCREENRIG_GPIO_CHIP=/dev/gpiochip4

此时引脚就是该芯片的线路编号。播放器的用户需要有权访问芯片的设备文件。

Android

当设备开放可写的 sysfs GPIO 芯片时(例如标牌主板或自定义镜像),Android 才有 GPIO。引脚是芯片的全局线路编号。标准电视盒没有这类芯片,应用会看到 device.gpio 为 false。播放器仅在应用使用期间持有线路。Android 无法设置输入的上下拉:请接电阻,如连接按钮所述。

选择引脚

pin 是 0 到 255 的整数:芯片的线路编号。在 Raspberry Pi 上它是 BCM 编号,即 Pi 文档中所用的 GPIO 编号,而不是引脚在排针上的位置。GPIO17 是物理引脚 11。常用线路:

BCM 编号(pin)物理引脚备注
1711合适的首选线路
2713
2215
2316
2418
2522
529
631
1232
1333
1636
2637
2038
2140
2 和 33 和 5带固定上拉的 I2C 线路;请保留给 I2C 使用
14 和 158 和 10串口控制台的线路
0 和 127 和 28保留用于 HAT 识别

接地是物理引脚 6、9、14、20、25、30、34 和 39,3.3 V 是引脚 1 和 17。Raspberry Pi 文档列出了完整的排针。已被其他程序占用的线路会以 pin_busy 拒绝,芯片没有的线路会以 pin_not_found 拒绝。

连接按钮

把按钮接在 GPIO 线路与地之间,并开启该线路的上拉。按钮按下之前线路读为高电平,按下会把它拉到地,因此按下读为低电平。

 GPIO17 (pin 11) -----+
                      |
                   [ button ]
                      |
 GND (pin 9) ---------+

在 Raspberry Pi 或其他 Linux 板子上,用 pull: "up" 向播放器请求上拉:

window.screenrig.device.gpio.onChange(17, handlePress, { pull: "up" });

pull: "down" 适用于接到 3.3 V 的按钮,按下时读为高电平。"none" 是默认值:播放器不设置偏置,因此未接任何东西的输入处于悬空状态,不带电阻的按钮会读到随机变化。

Android 不设置上下拉,因此对于接地的按钮,请在线路与 3.3 V 之间加一个约 10 kΩ 的电阻(上拉)。BrightSign 的输入内置上拉,接地的按钮可直接使用。

在无法设置上下拉的播放器上请求 "up" 或 "down" 不会使处理程序停止:SDK 会在不设上下拉的情况下监视该线路,并记录一条代码为 gpio_pull_unsupported 的 warn 条目。

驱动继电器

请通过继电器板驱动继电器,绝不要直接从引脚驱动。引脚在 3.3 V 下只能提供几毫安,无法切换继电器线圈、电控门锁、信号灯塔或蜂鸣器,而且线圈的电压尖峰可能损坏主板。继电器板上带有晶体管和线圈保护。

 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)

让负载通过继电器触点由其自身电源供电。请选择输入在 3.3 V 即可触发的板子。

const sr = window.screenrig;
await sr.device.gpio.write(27, true);   // relay on
await sr.device.gpio.write(27, false);  // relay off

大多数板子在高电平时吸合。许多廉价板子是低电平有效:输入被拉低时继电器吸合。对低电平有效的板子请传入 idle: true,这样页面离开时会让继电器释放为关闭。参见空闲电平。

await sr.device.gpio.write(27, false, { idle: true }); // active-low board: relay on
await sr.device.gpio.write(27, true, { idle: true });  // relay off

BrightSign 的输出是经 100 Ω 的 3.3 V,因此 BrightSign 驱动继电器板的方式相同。

仅限 3.3 V

每条 GPIO 线路都是 3.3 V 逻辑。Raspberry Pi 的引脚不耐受 5 V,引脚上接 5 V 可能损坏主板。只连接干触点:按钮、开关或自身不带电压的继电器输出。输出 5 V、12 V、24 V 或市电电压的传感器,需要在它与引脚之间接电平转换器、光耦或继电器。

消抖

机械触点会抖动:按下时会在高低电平之间闪烁几毫秒。播放器会过滤触点抖动,因此处理程序把按下听作一次变化,把释放听作另一次变化。Linux 或 Raspberry Pi 线路会忽略约 20 ms 内稳定下来的变化。你无需编写任何代码,而且比这更短的脉冲不适合用作 GPIO 输入。

监听线路

onChange(pin, handler, options) 监视一条线路。处理程序以布尔值接收电平,高电平为 true。首次调用携带播放器开始监视时线路的电平,之后每次调用都是一次变化。该调用返回一个停止监视的函数。

const stop = window.screenrig.device.gpio.onChange(17, function (high) {
  doorOpen = high;
}, { pull: "up" });

同时最多监视 16 条线路,第 17 条会抛出 invalid_request。同一线路上的两个处理程序共用其上下拉:第二个处理程序若使用不同的 pull,会立即抛出 invalid_request。

空闲电平

write(pin, value, options) 接受 idle 选项,即页面释放线路时播放器将线路驱动到的电平。默认为低电平。页面离开屏幕、其上下文结束以及调用 screenrig.destroy() 时,会释放线路。随后播放器释放该线路。某个引脚最近一次写入决定其空闲电平。

这就是页面离开后继电器不会保持吸合的原因。请把 idle 设为对你的板子表示“关闭”的电平:大多数板子为低电平,低电平有效的板子为高电平。

页面离开时

页面持有的资源随页面结束。页面离开屏幕时,其输入停止,每个输出变为其空闲电平并被释放。播放器停止或设备断电时,线路也随之消失。请据此规划负载:选择一种在断电时按建筑需要上锁或解锁的电控门锁。

限制和错误

  • 引脚是 0 到 255 的整数。
  • 同时最多监视 16 条线路。
  • write 会以 invalid_request 拒绝(pin、value 或 idle 无效),以 pin_not_found 拒绝(芯片没有该线路),以 pin_busy 拒绝(线路被其他进程占用),以 gpio_unavailable 拒绝(播放器打不开任何 GPIO 芯片)。
  • onChange 会针对第 17 条线路或冲突的 pull 抛出 invalid_request。被拒绝的监视会记入日志,处理程序永不运行。
  • 调用在发送任何内容之前会检查 inert、not_supported 和 capability_denied。SDK 参考列出了顺序。带有 idle 的 write 在无法设置它的播放器上会以 not_supported 拒绝。

示例:呼叫服务

接地的 GPIO17 上的按钮会告知你的智能体有顾客需要帮助,GPIO27 上的 LED 或蜂鸣器继电器随即响应五秒。第一次读数是线路已有的电平,因此处理程序只在电平由高变低时才动作。

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();

页面离开屏幕时会把线路 27 释放到其空闲电平(除非你传入 idle,否则为低电平),因此页面离开前刚按下的按钮不会让继电器保持开启。

你的智能体从事件日志中读取每一次按下:

screenrig events list --type application.event --screen-id scr_SCREEN

示例:门开输入

接地的 GPIO17 上的干簧管开关在门关上时闭合,因此门关时线路读为低电平,门开时读为高电平。门开着时页面显示提示,并告知你的智能体。

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

BrightSign 播放器运行同一套 API。引脚 0 到 7 是 GPIO 接口的线路,该接口位于 XT、XD 和 HD 型号上(DA-15 插座),引脚 0 是接口的线路 0,引脚 7 是其线路 7。其输入带有固定上拉,因此不提供 pull,接地的按钮可直接使用。idle 与在每个播放器上一样工作。输出为经 100 Ω 的 3.3 V:请通过继电器板驱动继电器。

BrightSign 可通过 USB 再增加两组线路:

引脚它们是什么
0 到 7GPIO 接口
8 到 39USB GPIO 扩展器,每个 8 个引脚,最多 4 个:引脚 8 到 15 是第一个扩展器,16 到 23 是第二个,依此类推
100 到 110BP200 或 BP900 按钮板,编号 0:引脚 100+k 作为输入是按钮 k,作为输出是其 LED
120 到 130、140 到 150 和 160 到 170按钮板 1、2 和 3,方式相同

向引脚 100+k 写入会点亮按钮 k 的 LED,监视引脚 100+k 可获知该按钮。未连接的板或扩展器,其引脚会以 pin_not_found 拒绝。

在 app dev 中试用

screenrig app dev 模拟 GPIO。测试台显示一个 GPIO 芯片开关和三条模拟线路:17(低电平)、27(高电平)以及 22,22 被其他进程占用,因此返回 pin_busy。上面的示例使用 17 和 27。点击某条线路的电平即可更改,应用在监视该线路时会收到变化。应用的写入会改变线路,面板显示它设置的空闲电平。测试台接受 pull 和 idle,因此构建可在到达屏幕之前先在此运行。在本地开发介绍各个控件。

参考

常见问题

屏幕能驱动继电器吗?
可以,通过继电器板。应用把线路设为高电平或低电平,继电器板切换电控门锁、信号灯塔或蜂鸣器。绝不要直接从引脚驱动继电器。
按钮需要电阻吗?
在 Raspberry Pi 或其他 Linux 板子上不需要:请求上拉,并把按钮接地。在 Android 上,请在线路与 3.3 V 之间加一个约 10 kΩ 的电阻。BrightSign 内置上拉。
引脚应传哪个编号?
在 Raspberry Pi 上传 BCM 编号:GPIO17 就是 17,位于物理引脚 11。在其他板子上传其 GPIO 芯片的线路编号。在 BrightSign 上传 BrightSign 一节中的引脚范围。
页面切换时继电器会怎样?
播放器把线路驱动到其空闲电平(除非应用另选,否则为低电平),然后释放它。对低电平有效的继电器板请选高电平,这样继电器处于关闭状态。
应用需要一直运行吗?
应用在其页面显示在屏幕上时进行监听。页面离开后,输入停止,输出变为其空闲电平。
费用是多少?
screenRIG 免费。主板、按钮和继电器的费用由你承担。