Velxio 中的树莓派是画布上的一块开发板,它针对你围绕它搭建的电路运行 Python 脚本。无需编译。你编写脚本,按下运行,两个引擎之一便会执行它:
- 基础模式在浏览器标签页内的 Python 解释器上运行脚本。几秒钟即可启动,是你大多数时候会使用的模式。
- Linux 模式在 Velxio 的服务器上启动一个真正的 Linux 客户机,并将其连接到同一电路。你会获得一个 shell、文件系统以及内核自有的 I2C、SPI、UART 和 GPIO 设备文件。启动大约需要一分钟。
本文构建两个项目,每种模式各一个,并展示过程中的每一次点击。第一个是树莓派 5 上用 Grove 模块搭建的报警站:输入端有运动传感器、振动开关和 DHT11,输出端有蜂鸣器、两个 LED 以及一个带风扇的继电器。第二个是树莓派 4 上带有三个 I2C 设备的传感器装置。两个脚本都使用 Raspberry Pi OS 自带的库,因此无需修改即可在真实开发板上运行。
开始之前,关于访问权限有两点说明。任何人都可以在画布上放置树莓派并围绕它搭建电路。运行它则需要已登录的账户:每个免费账户在树莓派系列上可获得三次 15 分钟的试用会话,两种模式均可,任何付费方案都会取消该限制。详情见文末的费用说明部分。
运行同一脚本的两种方式
| 基础模式 | Linux 模式 | |
|---|---|---|
| 脚本运行位置 | 浏览器标签页内的 Python 解释器 | Velxio 服务器上 QEMU 下的 Alpine Linux 客户机 |
| 启动时间 | 几秒钟 | 约一分钟 |
| GPIO | RPi.GPIO、gpiozero、Adafruit Blinka | 相同,外加 /dev/gpiochip0 和 libgpiod 工具 |
| I2C、SPI、UART、1-Wire | 通过模拟设备文件使用真正的 smbus2、spidev、pyserial 和 w1thermsensor | 通过内核真实设备节点使用相同的库 |
| shell 和文件系统 | 无 | 有,位于串口监视器中 |
| 第三方包 | 来自 requirements.txt | 来自 requirements.txt,在客户机启动前离线解析 |
| 最适合 | 几乎所有场景 | shell 工具、子进程、文件,以及任何教程中从终端执行的操作 |
引擎会为你选择。如果脚本请求只有 Linux 才能做到的事情,例如 subprocess,它会被发送到 Linux 模式,工具栏中的标签会告诉你原因。你也可以自行强制使用 Linux 模式,第二个项目正是如此。
项目 1:树莓派 5 上的 Grove 报警站
Grove 模块是给树莓派接传感器最快的方式:每个模块都有相同的四针插座,在 Velxio 中每个模块都用三根线接到排针上——SIG、VCC 和 GND。这个报警站用 PIR 运动传感器和振动开关监视房间,用 DHT11 读取空气数据,并通过蜂鸣器、“一切正常”的绿色 LED、“报警”的蓝色 LED,以及一个在房间变热时驱动风扇的继电器来做出响应。
第 1 步:在树莓派 5 上启动项目
打开 velxio.dev/editor。Start a new project 对话框会自动弹出。滚动到 Raspberry Pi 部分,点击 Raspberry Pi 5 卡片。Raspberry Pi 4B 卡片的工作方式完全相同:两块板共享 40 针排针和相同的库,因此下面的一切对两者都适用。

第 2 步:添加元件
点击工具栏最右侧蓝色的 Add 按钮。它会打开元件目录。

在搜索框中输入名称,然后点击卡片将元件放到画布上。Grove 模块都以 Seeed 的名称列出,因此搜索 “Grove PIR” 或 “Grove relay” 就能找到它们。依次添加以下八个元件:
- Grove - PIR Motion Sensor
- Grove - Vibration Sensor (SW-420)
- Grove - Temperature & Humidity Sensor (DHT11)
- Grove - Piezo Buzzer
- Grove - Green LED
- Grove - Blue LED
- Grove - Relay
- DC Motor / Fan

元件可以拖到画布上的任意位置。把树莓派放在底部、模块放在其排针上方,可以让连线保持简短。
第 3 步:接线
要画一根线,先点击一个引脚,再点击另一端的引脚。悬停在元件上时会显示引脚名称。每个 Grove 模块都接相同的三根线:SIG 接到某个 GPIO,VCC 接到 3V3,GND 接到 GND。继电器在供电侧是个例外,它的线圈需要 5 V。
| 模块 | SIG | VCC | GND |
|---|---|---|---|
| PIR 运动传感器 | GPIO17 | 3V3 | GND |
| 振动传感器 (SW-420) | GPIO27 | 3V3 | GND |
| DHT11 | GPIO4 | 3V3 | GND |
| 蜂鸣器 | GPIO24 | 3V3 | GND |
| 绿色 LED | GPIO5 | 3V3 | GND |
| 蓝色 LED | GPIO6 | 3V3 | GND |
| 继电器 | GPIO23 | 5V | GND |
风扇本身没有信号线。它接在继电器的开关触点上,就像实验台上的负载一样:电源进入 COM,只有在继电器线圈通电时才会从 NO 输出。
| 从 | 到 |
|---|---|
| Raspberry Pi 5V | Relay COM |
| Relay NO | Fan + |
| Fan - | Raspberry Pi GND |

第 4 步:脚本
左侧的文件浏览器中已经有一个名为 script.py 的文件,里面是一段简短的闪烁脚本。保留这个名称。基础模式会运行它找到的第一个 .py 文件,但 Linux 模式会专门运行 script.py,因此主文件保持这个名称的项目在两种模式下都能工作。

将其内容替换为以下脚本:
"""Grove alarm station on a Raspberry Pi 5.
A PIR sensor and a vibration switch watch the room, a DHT11 reads the air,
and the actuators answer: a buzzer and a blue LED for the alarm, a green LED
while all is quiet, and a relay that runs a fan when the room gets warm.
Every module hangs off the header with a Grove cable, one signal pin each.
Wiring, BCM numbers (VCC and GND of each module to 3V3 and GND):
PIR motion sensor SIG GPIO17 HIGH while motion is seen
vibration SW-420 SIG GPIO27 LOW while shaking, HIGH at rest
DHT11 SIG GPIO4
buzzer SIG GPIO24
green LED GPIO5, blue LED GPIO6
relay SIG GPIO23, VCC on 5V; the contacts switch the fan:
5V to COM, NO to the fan's +, the fan's - to GND
"""
import time
import adafruit_dht
import board
from gpiozero import LED, Button, Buzzer, MotionSensor
FAN_ON_AT = 28 # degrees C
HOLD = 2.5 # seconds the alarm light stays on after the beeps
pir = MotionSensor(17)
shaker = Button(27, pull_up=True) # the SW-420 pulls SIG low while it vibrates
dht = adafruit_dht.DHT11(board.D4, use_pulseio=False)
buzzer = Buzzer(24)
quiet_led = LED(5)
alarm_led = LED(6)
fan = LED(23) # the relay coil: on() closes COM to NO
def alarm(reason, beeps, beep_time):
print(f"ALARM: {reason}")
quiet_led.off()
alarm_led.on()
buzzer.beep(on_time=beep_time, off_time=0.1, n=beeps, background=False)
time.sleep(HOLD)
alarm_led.off()
quiet_led.on()
quiet_led.on()
motion_seen = False
shaking = False
next_reading = 0
print("Station armed")
while True:
if pir.motion_detected and not motion_seen:
alarm("motion in the room", beeps=3, beep_time=0.15)
motion_seen = pir.motion_detected
if shaker.is_pressed and not shaking:
alarm("vibration on the door", beeps=1, beep_time=0.6)
shaking = shaker.is_pressed
if time.monotonic() >= next_reading:
next_reading = time.monotonic() + 2
try:
temperature, humidity = dht.temperature, dht.humidity
except RuntimeError as err: # a DHT11 misses a reading now and then
print(f"DHT11: {err}")
else:
fan.value = temperature >= FAN_ON_AT
print(f"{temperature:.0f} C {humidity:.0f} %RH fan {'on' if fan.value else 'off'}")
time.sleep(0.1)
其中有三个地方值得注意,因为它们在真实硬件上也是如此:
- SW-420 的接线方式与其数据手册描述一致:模块静止时 SIG 为 HIGH,晃动时变为 LOW。带下拉的 gpiozero
Button会在晃动期间将其读取为”按下”,这就是脚本不需要任何反转逻辑的原因。 - 继电器被当作
LED来驱动。gpiozero 没有继电器类;继电器线圈需要的只是一个开或关的输出设备。 adafruit_dht在读取失败时会抛出RuntimeError,而 DHT11 确实会时不时读取失败。try块不是防御性的装饰,而是这个库本来的用法。
第 5 步:运行
在工具栏上,点击绿色三角形来运行项目。底部的 Serial Monitor 会打开并报告引擎正在做什么:它加载 Python 运行时、安装 GPIO 库,并打印一行类似 [Velxio] ready in 6068 ms 的信息,后面跟着它正在运行的脚本名称。从那时起,脚本中的每个 print() 都会输出到那里。

脚本会打印 Station armed,然后每两秒打印一行温度和湿度。画布上的 DHT11 起始温度为 31 C,因此继电器会立即闭合,风扇开始旋转:电机会显示方向箭头和以 rpm 为单位的速度,继电器自身的指示灯也会亮起。现在与传感器交互:
- 点击 PIR 的半球罩,或在其传感器面板中按下 Motion。SIG 会保持 HIGH 五秒钟,脚本打印
ALARM: motion in the room,蓝色 LED 亮起,蜂鸣器响三声。 - 点击振动传感器,或在其面板中切换 Vibration。一声长鸣,以及
ALARM: vibration on the door。 - 将 DHT11 的温度滑块拖到 28 C 以下。在下一次读取时继电器断开,风扇滑行至停止。

Run 按钮旁边的红色方块会停止脚本。这就是基础模式的全部:脚本在你的标签页中运行,引脚直接连接到画布,从点击 Run 到电路做出反应的整个循环只需几秒钟。
项目 2:树莓派 4 上的 I2C 传感器装置,然后切换到 Linux 模式
第二个项目把三个 I2C 设备接到排针总线上,并使用真正的 smbus2 与它们通信:一个带两个电位器的 Grove ADS1115 模数转换器、一个 Grove SHT31 温湿度传感器,以及一个 SSD1306 OLED。再加上一个接在普通 GPIO 上的按钮和一个 LED 就完整了。同样的电路在树莓派 5 上无需改动任何一根线或一行代码即可运行:排针是一样的。
ADS1115 的存在有一个让很多人踩坑的原因:树莓派没有模拟输入。无论是真实板子还是 Velxio,直接把电位器接到 GPIO 上读到的永远只有高或低,所以旋钮要经过 ADC。
电路
在 Raspberry Pi 4B 卡片上新建一个项目,然后从元件目录中添加以下部件:
- Grove - 4 Channel 16-bit ADC (ADS1115)
- 电位器,两个
- Grove - Temperature&Humidity Sensor (SHT31)
- SSD1306 OLED (I2C, 4-pin)
- LED、220 Ω 电阻 和 按钮
然后接线。三个 I2C 设备共用 GPIO2(SDA)和 GPIO3(SCL):
| 从(树莓派 4) | 到 |
|---|---|
| GPIO2 (SDA) | ADS1115、SHT31 和 OLED 的 SDA |
| GPIO3 (SCL) | ADS1115、SHT31 和 OLED 的 SCL |
| 3V3 | ADS1115、SHT31、OLED 和两个电位器的 VCC |
| GND | ADS1115、SHT31、OLED 和两个电位器的 GND |
| GPIO27 | LED 阳极;LED 阴极接 220 Ω,再接 GND |
| GPIO22 | 按钮;按钮另一端接 GND |
| 从 | 到 |
|---|---|
| 电位器 1 SIG | ADS1115 A0 |
| 电位器 2 SIG | ADS1115 A1 |

脚本
将其保存为 script.py。它首先询问三个地址是否有设备应答,然后循环:两个 ADC 通道、温度和湿度、按钮状态控制 LED,以及通过 luma.oled 在 OLED 上显示读数。
"""Sensor rig check for a Raspberry Pi 4.
Probes the three I2C devices, then loops: two knobs through the ADS1115,
temperature and humidity from the SHT31, the readings on the OLED, and a
button that lights the LED. Everything here is what Raspberry Pi OS ships:
smbus2 for the bus, gpiozero for the LED and the button, luma.oled for
the display.
Wiring, on the 40-pin header:
I2C : ADS1115, SHT31 and OLED on GPIO2 (SDA) and GPIO3 (SCL), 3V3, GND
ADC : one potentiometer on ADS1115 A0, another on A1
GPIO : LED through 220 ohm on GPIO27, button between GPIO22 and GND
"""
import time
import smbus2
from gpiozero import LED, Button
from luma.core.interface.serial import i2c
from luma.core.render import canvas
from luma.oled.device import ssd1306
ADS1115, SHT31, OLED = 0x48, 0x44, 0x3C
NAMES = {ADS1115: "ADS1115", SHT31: "SHT31", OLED: "SSD1306 OLED"}
# ADS1115 config register, field by field (datasheet, table 8)
ADS_START = 0x8000 # OS: begin one conversion
ADS_MUX_SINGLE = 0x4000 # AINx against GND; the channel goes in bits 12 and 13
ADS_PGA_4V = 0x0200 # +/-4.096 V full scale
ADS_ONE_SHOT = 0x0100 # power down between conversions
ADS_128_SPS = 0x0080 # 128 samples per second
ADS_NO_COMP = 0x0003 # comparator off
ADS_LSB_VOLTS = 4.096 / 32768
bus = smbus2.SMBus(1)
led = LED(27)
button = Button(22, pull_up=True)
def present(address):
"""True when a device acknowledges its address on bus 1."""
try:
bus.write_quick(address)
return True
except OSError:
return False
def read_ads1115(channel):
"""One single-shot conversion on AIN0..AIN3, returned in volts."""
config = (ADS_START | ADS_MUX_SINGLE | (channel << 12)
| ADS_PGA_4V | ADS_ONE_SHOT | ADS_128_SPS | ADS_NO_COMP)
bus.write_i2c_block_data(ADS1115, 0x01, [config >> 8, config & 0xFF])
time.sleep(0.01)
hi, lo = bus.read_i2c_block_data(ADS1115, 0x00, 2)
raw = (hi << 8) | lo
if raw & 0x8000:
raw -= 0x10000
return raw * ADS_LSB_VOLTS
def read_sht31():
"""Single shot, high repeatability, no clock stretching (command 0x2400)."""
bus.i2c_rdwr(smbus2.i2c_msg.write(SHT31, [0x24, 0x00]))
time.sleep(0.02)
reply = smbus2.i2c_msg.read(SHT31, 6)
bus.i2c_rdwr(reply)
d = list(reply)
temperature = -45 + 175 * ((d[0] << 8) | d[1]) / 65535
humidity = 100 * ((d[3] << 8) | d[4]) / 65535
return temperature, humidity
print("I2C bus 1:")
for address, name in NAMES.items():
print(f" 0x{address:02X} {name}: {'found' if present(address) else 'missing'}")
print()
oled = ssd1306(i2c(port=1, address=OLED))
while True:
knob_a = read_ads1115(0)
knob_b = read_ads1115(1)
temperature, humidity = read_sht31()
pressed = button.is_pressed
led.value = pressed
print(f"A0 {knob_a:5.3f} V A1 {knob_b:5.3f} V {temperature:4.1f} C {humidity:3.0f} %RH"
+ (" button pressed" if pressed else ""))
with canvas(oled) as draw:
draw.text((0, 0), "Pi 4 sensor rig", fill="white")
draw.text((0, 16), f"A0 {knob_a:.2f}V A1 {knob_b:.2f}V", fill="white")
draw.text((0, 32), f"{temperature:.1f} C {humidity:.0f} %RH", fill="white")
draw.text((0, 48), "button " + ("down" if pressed else "up"), fill="white")
time.sleep(2)
有三个细节值得一看。ADS1115 是按数据手册中配置寄存器的字段逐一配置的,因此每个常量都可以对照该文档的表 8 进行核对。SHT31 使用 i2c_rdwr 读取,因为它的测量命令是两个字节且没有寄存器地址,普通的 SMBus 调用无法表达。而 luma.oled 正是树莓派教程中用 pip 安装的那个显示库;这里它已预装,就像 smbus2 和 gpiozero 一样。
先在基础模式下运行
点击 Run。探测会找到三个芯片,循环会打印读数,OLED 会显示它们。转动电位器,A0 或 A1 的电压会随之变化;点击 SHT31,你可以设置它报告的温度和湿度;按住按钮,LED 就会亮起。

到目前为止一切都在浏览器中运行。接下来的步骤会在 Linux 机器上运行完全相同的项目。
切换到 Linux 模式
- 如果串口监视器未打开,请点击工具栏上的 Serial。
- 在串口监视器标题栏的右侧,点击 Linux terminal。

- 一个名为 Advanced Linux mode 的对话框会说明即将发生的事情:项目会重启,启动大约需要一分钟,而且一切运行都比浏览器中慢。点击 Start Linux mode。

客户机在串口监视器中启动,此时它已变成一个真正的终端。启动期间,工具栏中板子选择器旁边的芯片显示 Linux,其工具提示说明此项目已开启 Linux 模式。

当提示符出现时,Velxio 会将 script.py 复制到客户机的主目录并启动它。在空闲的服务器上,提示符大约 20 秒后出现,首次读数在你点击按钮后约一分钟出现。输出与之前相同,OLED 由同样的 luma.oled 调用驱动,现在是从客户机内部通过内核的 /dev/i2c-1 进行。

使用 shell
这正是 Linux 模式的用途。在终端内点击,按 Ctrl+C 停止脚本,你就处于 Alpine Linux 系统的 root 提示符下。可以尝试以下几行:
cat /etc/os-release | head -2
ls /dev/i2c* /dev/spidev* /dev/gpiochip*
gpioset gpiochip0 27=1
ls -la /home/pi
第二行将排针总线列为设备节点:/dev/i2c-0、/dev/i2c-1、/dev/spidev0.0、/dev/spidev0.1 和 /dev/gpiochip0。它们响应与树莓派上相同的系统调用,因此自行打开它们的库、C 程序或你自己的 ioctl 代码都能与画布上的部件通信。
第三行是从 shell 驱动引脚的现代方式,使用 Raspberry Pi OS Bookworm 附带的 libgpiod 工具。你按下 Enter 的那一刻,画布上 GPIO27 的 LED 就会亮起。客户机使用的是 libgpiod 版本 1,因此芯片是位置参数,如上所写,而版本 2 中带 --chip 的写法无法识别。

要手动再次运行脚本,请输入 python3 script.py。要返回浏览器引擎,请点击同一标题栏中的 Exit Linux terminal。该选择不会随项目保存:明天重新打开它,Run 会回到基础模式。

各模式下可用的功能
下表是文档中树莓派页面的简要版本,该页面是逐模块对照实际产品测量得出的。
| 模块 | 基础模式 | Linux 模式 |
|---|---|---|
RPi.GPIO、gpiozero | 已预装 | 已预装 |
smbus2、spidev | 已预装,真实库 | 已预装,通过内核节点使用真实库 |
serial(pyserial) | 仅树莓派 UART 路径 | 真实 tty 上的真实 pyserial,/dev/serial0 |
adafruit_dht、Adafruit_DHT | 已预装 | 已预装 |
w1thermsensor、1-Wire sysfs 树 | 已预装 | 通过 requirements.txt;sysfs 树可用 |
luma.oled、RPLCD、ST7789、Adafruit Blinka | 已预装 | 已预装 |
gpiod 及 gpio* 工具 | 浏览器中无字符设备 | 已预装 |
cv2(OpenCV) | 支持 | 客户机无对应构建 |
picamera2 | 支持,通过你的网络摄像头 | 不支持;请使用 rpicam-jpeg 及其同类工具 |
requests、urllib | 通过带允许列表的出站代理 | 客户机内无网络 |
| PyPI 上的其他任何包 | 通过 requirements.txt | 通过 requirements.txt,前提是 PyPI 有 musl aarch64 的 wheel |
两种模式都做不到的事情,免得你花一次试用会话才发现:
- GPIO 上的模拟输入。 树莓派没有 ADC。请在传感器和开发板之间加一个 ADS1115 或 MCP3008,如第二个项目所示。
pigpio。 它通过/dev/gpiomem映射外设寄存器,而这里没有任何东西提供该映射。请使用 RPi.GPIO、gpiozero,或在 Linux 模式下使用 libgpiod。apt、raspi-config、桌面环境。 客户机是 Alpine Linux,不是 Raspberry Pi OS。Python 3 和 pip 都在;包管理器和树莓派固件工具则没有。- 客户机内部的网络。 客户机内的任何东西都无法访问互联网或 PyPI。
requirements.txt中列出的包会在启动前解析,并可在提示符下导入。 - 大型机器学习技术栈。 PyTorch 和 TensorFlow 会被拒绝并给出说明。
两个习惯能节省时间。将主文件命名为 script.py,这样两种模式运行的是同一个文件。并把第三方包放在旁边的 requirements.txt 中:当脚本导入一个缺失的包时,运行控制台会给出该行,并有一个按钮为你写入。
费用
任何人都可以无需账户就接线树莓派电路。运行则受限:
- 免费账户在树莓派系列上获得三次 15 分钟的试用会话。会话在你按下运行(任一模式)时开启,直到结束前的每次运行都免费。未登录时,工具栏会显示一个芯片,邀请你在首次运行前登录。
- 任何付费方案都会移除该限制。最便宜的 No AI 方案为每月 4 美元或每年 36 美元,包含树莓派和 STM32 开发板且运行时间不限;Maker 和 Pro 也包含它们。当前价格见定价页面。

两种引擎上的试用会话相同,因此只需基础模式的脚本应留在基础模式:它几秒即可启动,而不是一分钟,而 Linux 客户机是留给只有 Linux 才能做的事情的。
接下来去哪里
- 树莓派的图库示例:树莓派 4 上的按钮和 LED、树莓派 5 上的 PIR 运动报警、通过 smbus2 使用 MPU6050,以及树莓派 4 上同时使用所有总线。
- Grove 目录:Grove 模块文章介绍了模块在画布上的样子以及它是如何被模拟的。
- 文档:树莓派系列页面包含完整的兼容性表格,以及树莓派 4和树莓派 5的引脚定义。
总结
两个项目,两个引擎。Grove 报警站在树莓派 5 上以基础模式运行,从空白画布到电路做出反应只用了几分钟,使用的是树莓派教程中会用的 gpiozero 和 Adafruit DHT 代码。传感器装置先在树莓派 4 上以基础模式运行,然后切换到 Linux 模式,在那里同一个脚本通过内核的 /dev/i2c-1 与 ADS1115、SHT31 和 OLED 通信,而在提示符下输入的一条 gpioset 命令点亮了画布上的一个 LED。基础模式涵盖了树莓派教程所需的大部分内容;Linux 模式则为终端、文件和系统工具而存在,代价是一分钟的启动时间。