戻る

ブラウザ上のRaspberry Pi 4と5 — 基本モードとLinuxモードを順を追って解説

Velxio上のRaspberry Piとは、キャンバス上に置かれたボードであり、その周囲に配線した回路に対してPythonスクリプトを実行するものです。コンパイルするものは何もありません。スクリプトを書いてRunを押すと、2つのエンジンのうちの1つがそれを実行します。

この記事では、モードごとに1つずつ、2つのプロジェクトを構築し、その過程のすべてのクリックを紹介します。1つ目はRaspberry Pi 5上のGroveモジュールで作る防犯ステーションです。入力側にモーションセンサー、振動スイッチ、DHT11、出力側にブザー、2つのLED、ファン付きリレーを備えます。2つ目は3つのI2Cデバイスを接続したRaspberry Pi 4上のセンサー装置です。どちらのスクリプトもRaspberry Pi OSに同梱されているライブラリを使うので、実機のボードでもそのまま動作します。

始める前に、アクセスに関する2つの点を説明します。誰でもキャンバス上にRaspberry Piを配置し、その周囲に回路を配線できます。実行にはサインインしたアカウントが必要です。無料アカウントにはすべて、Raspberry Piファミリーでどちらのモードでも使える15分のトライアルセッションが3回付与され、有料プランならこの制限がなくなります。詳細は最後の方にある料金についてのセクションにあります。

同じスクリプトを実行する2つの方法

基本モードLinuxモード
スクリプトの実行場所ブラウザタブ内のPythonインタプリタVelxioのサーバー上でQEMU配下のAlpine Linuxゲスト
起動時間数秒約1分
GPIORPi.GPIO、gpiozero、Adafruit Blinka同じものに加え、/dev/gpiochip0とlibgpiodツール
I2C、SPI、UART、1-Wireエミュレートされたデバイスファイル経由で本物のsmbus2、spidev、pyserial、w1thermsensorカーネルの本物のデバイスノード経由で同じライブラリ
シェルとファイルシステムなしあり、Serial Monitor内で
サードパーティパッケージrequirements.txtからrequirements.txtから、ゲスト起動前にオフラインで解決
最適な用途ほぼすべてシェルツール、サブプロセス、ファイル、そしてチュートリアルがターミナルから行うようなあらゆる操作

エンジンは自動的に選択されます。subprocessのようにLinuxでしかできないことを要求するスクリプトはLinuxモードに送られ、ツールバーのチップがその理由を教えてくれます。自分でLinuxモードを強制することもでき、2つ目のプロジェクトではそうしています。

プロジェクト1 — Raspberry Pi 5上のGrove防犯ステーション

GroveモジュールはPiにセンサーを載せる最速の方法です。どのモジュールも同じ4ピンソケットを備えており、VelxioではそれぞれがSIG、VCC、GNDの3本のワイヤでヘッダーに接続されます。このステーションはPIR人感センサーと振動スイッチで部屋を監視し、DHT11で空気を読み取り、ブザー、「異常なし」を示す緑LED、「警報」を示す青LED、そして部屋が暖かくなるとファンを回すリレーで応答します。

ステップ1 — Raspberry Pi 5でプロジェクトを開始する

velxio.dev/editorを開きます。Start a new projectダイアログが自動的に開きます。Raspberry Piセクションまでスクロールし、Raspberry Pi 5カードをクリックします。Raspberry Pi 4Bカードも同じように動作します。この2つのボードは40ピンヘッダーとライブラリを共有しているため、以下の内容はどちらにも当てはまります。

Raspberry Piセクションが表示されたStart a new projectダイアログ。Raspberry Pi 5カードがハイライトされている

ステップ2 — 部品を追加する

ツールバー右端の青いAddボタンをクリックします。部品カタログが開きます。

Addボタンがハイライトされたツールバー

検索フィールドに名前を入力し、カードをクリックするとキャンバス上に部品が配置されます。GroveモジュールはすべてSeeedの名前で登録されているので、「Grove PIR」や「Grove relay」で見つかります。以下の8つの部品を1つずつ追加してください。

  1. Grove - PIR Motion Sensor
  2. Grove - Vibration Sensor (SW-420)
  3. Grove - Temperature & Humidity Sensor (DHT11)
  4. Grove - Piezo Buzzer
  5. Grove - Green LED
  6. Grove - Blue LED
  7. Grove - Relay
  8. DC Motor / Fan

検索フィールドにGrove PIRと入力され、PIR人感センサーカードがハイライトされたAdd Componentダイアログ

部品はキャンバス上のどこにでもドラッグできます。Piを下に、モジュールをヘッダーの上に配置すると、ワイヤを短く保てます。

ステップ3 — 配線する

ワイヤを引くには、ピンをクリックしてから反対側のピンをクリックします。部品にカーソルを合わせるとピン名が表示されます。どのGroveモジュールも同じ3本のワイヤで接続します。SIGはGPIOへ、VCCは3V3へ、GNDはGNDへ。電源側の例外はリレーで、そのコイルは5Vを必要とします。

モジュールSIGVCCGND
PIR人感センサーGPIO173V3GND
振動センサー (SW-420)GPIO273V3GND
DHT11GPIO43V3GND
ブザーGPIO243V3GND
緑LEDGPIO53V3GND
青LEDGPIO63V3GND
リレーGPIO235VGND

ファンには専用の信号線はありません。ベンチ上の負荷と同じように、リレーのスイッチ接点に接続します。電源はCOMに入り、コイルが通電している間だけNOから出てきます。

接続元接続先
Raspberry Pi 5Vリレー COM
リレー NOファン +
ファン -Raspberry Pi GND

配線済みのステーション。下部にRaspberry Pi 5、そのヘッダーの上にPIR、振動センサー、DHT11、ブザーが一列に並び、その下に2つのLEDとファン付きリレーがあり、各モジュールに注釈が付いている

ステップ4 — スクリプト

左側のファイルエクスプローラーには、短い点滅スクリプトを含むscript.pyというファイルがすでにあります。この名前をそのまま使ってください。基本モードは最初に見つけた.pyファイルを実行しますが、Linuxモードは特にscript.pyを実行するため、メインファイルがこの名前を保っているプロジェクトは両方のモードで動作します。

ファイルエクスプローラーで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)

このスクリプトには注目すべき点が3つあります。いずれも実機と同じ挙動です。

ステップ5 — 実行する

ツールバーの緑の三角形をクリックしてプロジェクトを実行します。下部にSerial Monitorが開き、エンジンが何をしているかを報告します。Pythonランタイムをロードし、GPIOライブラリをインストールし、[Velxio] ready in 6068 msのような行に続いて実行中のスクリプト名を出力します。それ以降、スクリプト内のすべてのprint()がそこに表示されます。

Runボタンがハイライトされたツールバー

スクリプトはStation armedを出力し、その後2秒ごとに温度と湿度の行を出力します。キャンバス上のDHT11は31 Cから始まるので、リレーはすぐに閉じてファンが回り始めます。モーターには方向矢印とrpm単位の速度が表示され、リレー自身のインジケーターも点灯します。次にセンサーを操作してみましょう。

基本モードで動作中のステーション。リレーの後ろでファンが回り、PIRドームをクリックした後に青LEDが点灯し、Serial Monitorに読み取り値とALARM行が表示されている

Runボタンの隣の赤い四角がスクリプトを停止します。これが基本モードのすべてです。スクリプトはタブ内で実行され、ピンはキャンバスに直接届き、Runから回路が反応するまでのループ全体が数秒で完了します。

プロジェクト2 — Raspberry Pi 4上のI2Cセンサーリグ、そしてLinuxモード

2つ目のプロジェクトでは、ヘッダーバスに3つのI2Cデバイスを接続し、本物のsmbus2を使ってそれらと通信します。2つのポテンショメーターを備えたGrove ADS1115アナログ-デジタルコンバーター、Grove SHT31温度・湿度センサー、そしてSSD1306 OLEDです。さらに、通常のGPIOに接続したボタンとLEDで完成します。同じ回路は、配線もコードも一行も変えることなくRaspberry Pi 5でも動作します。ヘッダーは同じだからです。

ADS1115が登場するのには、多くの人が引っかかる理由があります。Raspberry Piにはアナログ入力がありません。ポテンショメーターをGPIOに直接配線しても、実機でもVelxioでもハイかローしか読み取れないため、つまみはADCを経由します。

回路

Raspberry Pi 4Bカードで新しいプロジェクトを開始し、カタログから以下の部品を追加します。

次に配線します。3つのI2CデバイスはすべてGPIO2 (SDA) とGPIO3 (SCL) を共有します。

接続元 (Raspberry Pi 4)接続先
GPIO2 (SDA)ADS1115、SHT31、OLEDのSDA
GPIO3 (SCL)ADS1115、SHT31、OLEDのSCL
3V3ADS1115、SHT31、OLED、両ポテンショメーターのVCC
GNDADS1115、SHT31、OLED、両ポテンショメーターのGND
GPIO27LEDアノード、LEDカソードから220 Ωを経てGND
GPIO22プッシュボタン、ボタンのもう一方をGNDへ
接続元接続先
ポテンショメーター1 SIGADS1115 A0
ポテンショメーター2 SIGADS1115 A1

配線済みのセンサーリグ — 下部にRaspberry Pi 4、その上に2つのポテンショメーターを備えたADS1115、OLED、SHT31、右側にLEDとボタンがあり、I2Cピンと各デバイスに注釈が付いている

スクリプト

これをscript.pyとして保存します。まず3つのアドレスそれぞれに応答があるか問い合わせ、その後ループします。2つの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)

注目に値する詳細が3つあります。ADS1115は、データシートがコンフィグレジスタを定義する方法に従ってフィールドごとに設定されているため、各定数をそのドキュメントの表8と照合できます。SHT31はi2c_rdwrで読み取られます。その測定コマンドはレジスタアドレスのない2バイトであり、通常のSMBus呼び出しでは表現できないからです。そしてluma.oledは、Raspberry Piのチュートリアルがpipでインストールするのと同じディスプレイライブラリです。ここではsmbus2やgpiozeroと同様、プリインストールされています。

まず基本モードで実行する

Runを押します。プローブが3つのチップを見つけ、ループが測定値を出力し、OLEDがそれを表示します。ポテンショメーターを回すとA0またはA1の電圧が追従し、SHT31をクリックすると報告される温度と湿度を設定でき、プッシュボタンを押し続けるとLEDが点灯します。

基本モードで動作するセンサーリグ — OLEDは2つの電圧、温度と湿度、ボタン状態を表示し、シリアルモニターは検出された3つのデバイスと測定値を表示している

ここまではすべてブラウザ上で実行されました。次のステップでは、まったく同じプロジェクトをLinuxマシン上で実行します。

Linuxモードに切り替える

  1. シリアルモニターが開いていない場合は、ツールバーのSerialをクリックします。
  2. シリアルモニターのタイトルバー右側にあるLinux terminalをクリックします。

Linux terminalボタンがハイライトされたシリアルモニターのタイトルバー

  1. Advanced Linux modeというダイアログが、これから起こることを説明します。プロジェクトが再起動し、起動には約1分かかり、すべてがブラウザよりも遅く動作します。Start Linux modeをクリックします。

Start Linux modeボタンがハイライトされたAdvanced Linux modeダイアログ

ゲストはシリアルモニター内で起動します。シリアルモニターは本物のターミナルになっています。起動中、ツールバーのボードセレクターの隣にあるチップはLinuxと表示され、そのツールチップにはこのプロジェクトでLinuxモードが有効であることが示されます。

Linuxと表示されたエンジンチップがハイライトされたツールバー

プロンプトが表示されると、Velxioはscript.pyをゲストのホームディレクトリにコピーして起動します。静かなサーバーでは、プロンプトは約20秒後に表示され、最初の測定値はボタンをクリックしてから約1分後に現れます。出力は以前と同じで、OLEDは同じluma.oled呼び出しによって駆動されますが、今度はカーネルの/dev/i2c-1を通じてゲスト内部からです。

Linuxモードのセンサーリグ — ターミナルは検出された3つのデバイスと測定値を表示し、OLEDは同じ値を表示している

シェルを使う

これが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

2行目はヘッダーバスをデバイスノードとして一覧表示します。/dev/i2c-0、/dev/i2c-1、/dev/spidev0.0、/dev/spidev0.1、そして/dev/gpiochip0です。これらはRaspberry Pi上と同じシステムコールに応答するため、それらを自ら開くライブラリ、Cプログラム、あるいは独自のioctlコードがキャンバス上の部品と通信できます。

3行目は、Raspberry Pi OS Bookwormが同梱するlibgpiodツールを使い、シェルからピンを駆動する現代的な方法です。Enterを押した瞬間、キャンバス上のGPIO27のLEDが点灯します。ゲストはlibgpiodバージョン1を搭載しているため、上記のようにチップは位置引数であり、--chipを使うバージョン2の記法は理解されません。

スクリプトを中断した後のLinuxシェル — gpiosetコマンドがハイライトされ、キャンバス上のLEDが点灯している

スクリプトを手動で再度実行するには、python3 script.pyと入力します。ブラウザエンジンに戻るには、同じタイトルバーのExit Linux terminalをクリックします。この選択はプロジェクトとともに保存されません。明日開き直してRunを押すと、基本モードに戻ります。

Linuxモードのシリアルモニターのタイトルバーで、Exit Linux terminalボタンがハイライトされている

各モードで動作するもの

以下の表は、ドキュメントのRaspberry Piページの要約版であり、実際の製品に対してモジュールごとに検証したものです。

モジュール基本モードLinuxモード
RPi.GPIO、gpiozeroプリインストール済みプリインストール済み
smbus2、spidevプリインストール済み、本物のライブラリプリインストール済み、カーネルノード経由の本物のライブラリ
serial(pyserial)Piの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ホイールがある場合

試用セッションを無駄にしないために、どちらのモードでもできないことを先に挙げておきます。

2つの習慣が時間を節約します。メインファイルをscript.pyと名付ければ、両モードが同じファイルを実行します。そしてサードパーティのパッケージを隣のrequirements.txtに書いておきましょう。スクリプトが不足しているパッケージをインポートすると、実行コンソールがその行を提示し、ボタンがそれを書き込んでくれます。

コストについて

アカウントなしでRaspberry Piの回路を配線することは誰でもできます。実行には制限があります。

サインアウトした訪問者に見えるツールバー。Free trial、sign inチップがハイライトされている

試用セッションは両エンジンで同じなので、基本モードだけで済むスクリプトはそこに留めるべきです。1分ではなく数秒で起動し、LinuxゲストはLinuxにしかできないことのためにあります。

次に進む先

まとめ

2つのプロジェクト、2つのエンジン。GroveアラームステーションはRaspberry Pi 5の基本モードで、空のキャンバスから数分で反応する回路へと至り、Raspberry Piのチュートリアルで使われるようなgpiozeroとAdafruit DHTのコードを使用しました。センサー装置はまずRaspberry Pi 4の基本モードで動作し、次にLinuxモードで、同じスクリプトがカーネルの/dev/i2c-1を通じてADS1115、SHT31、OLEDと通信し、プロンプトで入力したgpiosetコマンドがキャンバス上のLEDを点灯させました。基本モードはRaspberry Piのチュートリアルが求めるもののほとんどをカバーし、Linuxモードはターミナル、ファイル、システムツールのために存在し、その代償は1分の起動時間です。


共有:
David Montero

執筆者

David Montero

Creator of Velxio, the open-source circuit and Arduino simulator.

GitHub velxio.dev

関連記事


次の記事
Castlevania: Symphony of the Night が ESP32-S3 上でネイティブ動作