MR60BHA2(ミリ波センサーとESP32C6)を使ってトイレの照明ON/OFFをしたかった

目的

今までは,Panasonic製のかってにスイッチシリーズのWTK1811WKを使ってトイレの照明のON/OFF(および換気扇のON/OFF)を行っていた.このスイッチは壁に設置できるのでメンテナンスフリーであるのだが,静止検知はできないためにトイレが長い場合*1に照明がOFFになってしまう場合がある.
また,昨年度の異動で引越が発生し,トイレの外にトイレの照明スイッチがあるので,かってにスイッチが使えなくなった.そのため,静止検知もできるトイレ体験ができるモノをゆるりと探していたが,せっかくなので自作してみようと思い立ってほぼ完成形のところまでは進めたので記載しておく.

ポイントは,Passive Infrared Ray(PIR,受動赤外線センサー)ではなくてミリ波センサー*2を使うことである.ミリ波センサーであれば静止検知ができる.

試作を試みたセット

名称 規格 製造会社 備考
ボット CR2電池タイプ,Bluetooth Low Energy SwitchBot メルカリにて3,300JPYで購入
ミリ波センサーキット MR60BHA2(XIAO ESP32C6内蔵) Seeed Studio AliExpressにて5,211JPYで購入
指ロボット用取付台 コスモシリーズワイド21用,土台B+マウントA にゃん工房 Amazon.co.jpにて1,830JPYで購入
統合開発環境 Arduino IDE,1.8.19 Arduino 新規インストールならば2.x系がよいと思う
ESP32ボード設定 esp32,3.3.11 Espressif Systems
BLE制御ライブラリー NimBLE-Arduino,2.5.1 Ryan Powell
RGB LED制御ライブラリー Adafruit NeoPixel,1.15.5 Adafruit
Androidスマートフォン AQUOS Sense9(SH-M29),Android 16 SHARP
無線通信デバイスの開発・テスト・検証支援アプリ nRF Connect,4.29.1 Nordic Semiconductor Android版
SwitchBot制御アプリ SwitchBot,10.0.1 SwitchBot(wonderlabs) Android版
開発用PC Ryzen 7 3700X,RAM 32[GB],Windows 10など ---

想定読者

  • XIAO ESP32C6を初めて使う方
  • SwitchBotボットをBLEで「押したい」方
  • 動体検知だけでなく静止検知もしたい方
  • トイレの照明を自動化したい方

結論

試作のソフトがほぼ完成してきたところで,重大な問題を忘れていた.
MR60BHA2のミリ波センサー部分には,技適こと技術基準適合証明がない*3! つまり,日本国内で自由に使えないものであったということ.

試作するところまでの過程をもったいないので記載する.
おもに在不在判定ループと照明消灯用の消灯待ちタイマー,状態表示灯で構成される.

在不在判定ループ
ミリ波センサーから約120[ms]ごとに出力されるUARTデータから在不在を読み取る.0F 09のデータ受信の後ろの1バイトを飛ばして,01が来ていたら在室,00が来ていたら不在となる*4

  1. 在室判定:01 ?? ?? 00 02 0F 09 ?? 01 00 FE
  2. 不在判定:01 ?? ?? 00 02 0F 09 ?? 00 00 FF

上記の在不在データ受信により,以下の動作をする:

  1. 在室判定のとき
    • 不在カウンターを0にする
    • 初回は即時照明スイッチを押下
    • 2回目以降は消灯待ちタイマー動作中(直前にいったん不在判定に入っていた場合)には照明スイッチを押さずに消灯待ちタイマーをキャンセルして停止する
  2. 不在判定のとき
    • 不在カウンターを+1する
    • 不在カウンターが16に達したら不在確定とし,消灯待ちタイマーを開始する

消灯待ちタイマー
トイレの運用上,退室して戻ることは少ない.場合によっては,忘れ物(スマホなど)を取りに来たり,掃除のために出たり入ったりすることが考えられる.そのため,消灯確定時に即時照明を消すのではなく,消灯するまで待つタイマーを設けた.消灯待ちタイマーは不在確定でカウントスタート,在室確定でカウントリセットおよび停止する.

状態表示灯
以下のパターンでRGB LEDの色を切り替える.白または赤の点滅動作中は色を重ねない*5

表示色 点灯パターン 割り当てた状態 備考
点灯 在室判定中
点灯 在不在切替中 この動きはしなかった気がする
点灯 不在判定中 基本はこの状態
点滅 指ロボット動作要求送信中
点滅 指ロボット動作要求送信失敗

最終形「仮」のフローチャートおよびソースコードは以下のとおりである.

フローチャート

/*
 * トイレ照明自動化+RGB LED状態表示
 *
 * ベースボードのRGB LED(D1)で状態表示を追加。
 *
 * LED色の割り当て:
 *   在室               → 青
 *   不在切替の判定中     → 黄(不在フレームが連続カウント中)
 *   不在確定           → 緑
 *   SwitchBot送信中     → 白点滅
 *   送信失敗           → 赤点滅
 *
 * 必要なライブラリ:
 *   NimBLE-Arduino
 *   Adafruit NeoPixel
 */

#include <NimBLEDevice.h>
#include <Adafruit_NeoPixel.h>

// ================= 設定 =================

// --- レーダー ---
#define RADAR_RX_PIN 17
#define RADAR_TX_PIN 16
#define RADAR_BAUD   115200

// --- RGB LED ---
#define LED_PIN      D1
// 0〜255の範囲.まあぶしいので12くらいで十分
#define LED_BRIGHTNESS 12

// --- SwitchBot ---
// BOT_MACの後半3バイトは個体ごとに異なる(調べ方は後述)
static const char* BOT_MAC      = "f4:a3:99:**:**:**";
static const char* SERVICE_UUID = "cba20d00-224d-11e6-9fb8-0002a5d5c51b";
static const char* WRITE_UUID   = "cba20002-224d-11e6-9fb8-0002a5d5c51b";
static const uint8_t PRESS_CMD[3] = { 0x57, 0x01, 0x01 };

// --- 判定パラメータ ---
// 不在確定までの連続フレーム数
// (約120msごとにデータ受信するため16回の連続判定で約2秒)
static const uint8_t  ABSENT_CONFIRM_COUNT = 16;
// 退室確定から消灯までを制御する消灯待ちタイマータイムアウト値(10秒)
static const uint32_t OFF_DELAY_MS = 10000;
static const uint8_t  MAX_RETRY = 3;

// ================= 状態 =================

HardwareSerial RadarSerial(1);
Adafruit_NeoPixel led(1, LED_PIN, NEO_GRB + NEO_KHZ800);

bool     presence        = false;
uint8_t  absentStreak    = 0;
bool     offTimerRunning = false;
uint32_t offTimerStart   = 0;

uint8_t frameBuf[32];
uint8_t frameIdx = 0;
bool    inFrame  = false;

uint32_t pressCount   = 0;
uint32_t successCount = 0;

// LEDの現在状態を表す種別
enum LedState { LED_PRESENT, LED_JUDGING, LED_ABSENT, LED_SENDING, LED_FAILED };
LedState ledState = LED_ABSENT;

// ================= LED =================

void setLed(uint8_t r, uint8_t g, uint8_t b) {
  led.setPixelColor(0, led.Color(r, g, b));
  led.show();
}

// 通常状態(在室/判定中/不在)を色で表示する
void updateLedNormal() {
  if (presence) {
    ledState = LED_PRESENT;
    setLed(0, 0, 255);          // 青
  } else if (absentStreak > 0) {
    ledState = LED_JUDGING;
    setLed(255, 180, 0);      // 黄
  } else {
    ledState = LED_ABSENT;
    setLed(0, 255, 0);          // 緑
  }
}

// 白点滅(送信中)
void blinkWhite(uint8_t times) {
  for (uint8_t i = 0; i < times; i++) {
    setLed(255, 255, 255);
    delay(120);
    setLed(0, 0, 0);
    delay(120);
  }
}

// 赤点滅(失敗)
void blinkRed(uint8_t times) {
  for (uint8_t i = 0; i < times; i++) {
    setLed(255, 0, 0);
    delay(200);
    setLed(0, 0, 0);
    delay(200);
  }
}

// ================= ユーティリティ =================

void printTime() {
  Serial.printf("[%7.1fs] ", millis() / 1000.0);
}

// ================= BLE =================

bool pressOnce() {
  NimBLEClient* client = nullptr;

  if (NimBLEDevice::getCreatedClientCount() > 0) {
    client = NimBLEDevice::getClientByPeerAddress(NimBLEAddress(BOT_MAC, BLE_ADDR_RANDOM));
    if (client == nullptr) {
      client = NimBLEDevice::getDisconnectedClient();
    }
  }
  if (client == nullptr) {
    client = NimBLEDevice::createClient();
    client->setConnectTimeout(5 * 1000);
  }

  NimBLEAddress addr(BOT_MAC, BLE_ADDR_RANDOM);
  if (!client->connect(addr)) {
    return false;
  }

  NimBLERemoteService* service = client->getService(SERVICE_UUID);
  if (service == nullptr) {
    client->disconnect();
    return false;
  }

  NimBLERemoteCharacteristic* chr = service->getCharacteristic(WRITE_UUID);
  if (chr == nullptr) {
    client->disconnect();
    return false;
  }

  bool ok = chr->writeValue(PRESS_CMD, sizeof(PRESS_CMD), true);

  delay(200);
  client->disconnect();
  return ok;
}

void pressSwitchBot(const char* reason) {
  pressCount++;
  printTime();
  Serial.printf("SwitchBot を押します(%s\n", reason);

  // 送信中は白点滅
  ledState = LED_SENDING;

  bool ok = false;
  for (uint8_t retry = 0; retry < MAX_RETRY && !ok; retry++) {
    if (retry > 0) {
      printTime();
      Serial.printf("    リトライ %d/%d\n", retry, MAX_RETRY - 1);
    }
    blinkWhite(2);        // 送信の合図(白点滅)
    ok = pressOnce();
  }

  printTime();
  if (ok) {
    successCount++;
    Serial.printf("    ★ 成功(通算 %lu/%lu\n", successCount, pressCount);
  } else {
    Serial.printf("    × 失敗(通算 %lu/%lu\n", successCount, pressCount);
    blinkRed(3);          // 失敗は赤点滅
  }

  // 送信が終わったら通常表示に戻す
  updateLedNormal();
}

// ================= 在室判定 =================

void handleRawPresence(bool raw) {
  if (raw) {
    absentStreak = 0;

    if (!presence) {
      presence = true;
      printTime();
      Serial.println(">>> 入室を検知");

      if (offTimerRunning) {
        offTimerRunning = false;
        printTime();
        Serial.println("    消灯予定をキャンセル(照明は点いたまま)");
        updateLedNormal();
      } else {
        pressSwitchBot("点灯");
      }
    } else {
      updateLedNormal();   // 在室継続(青を維持)
    }
  } else {
    if (presence) {
      absentStreak++;
      updateLedNormal();   // 判定中(黄)を表示

      if (absentStreak >= ABSENT_CONFIRM_COUNT) {
        presence     = false;
        absentStreak = 0;

        printTime();
        Serial.printf(">>> 退室を検知(%lu秒後に消灯)\n", OFF_DELAY_MS / 1000);

        offTimerRunning = true;
        offTimerStart   = millis();
        updateLedNormal();  // 不在確定(緑)
      }
    }
  }
}

// ================= メイン =================

void setup() {
  Serial.begin(115200);
  delay(2000);
  Serial.println();
  Serial.println("=================================================");
  Serial.println(" トイレ照明自動化 + RGB LED表示");
  Serial.println("=================================================");
  Serial.println("色:在室=青 / 判定中=黄 / 不在=緑 / 送信中=白点滅 / 失敗=赤点滅");
  Serial.println();

  // LED
  led.begin();
  led.setBrightness(LED_BRIGHTNESS);
  led.clear();
  led.show();

  // レーダーUART
  RadarSerial.begin(RADAR_BAUD, SERIAL_8N1, RADAR_RX_PIN, RADAR_TX_PIN);

  // BLE
  NimBLEDevice::init("");
  NimBLEDevice::setPower(ESP_PWR_LVL_P9);

  updateLedNormal();      // 起動時は不在(緑)
  Serial.println("起動完了。待機中...");
}

void loop() {
  while (RadarSerial.available()) {
    uint8_t b = RadarSerial.read();

    if (!inFrame) {
      if (b == 0x01) {
        inFrame  = true;
        frameIdx = 0;
        frameBuf[frameIdx++] = b;
      }
    } else {
      if (frameIdx < sizeof(frameBuf)) {
        frameBuf[frameIdx++] = b;
      }

      if (frameIdx == 7) {
        uint16_t type = ((uint16_t)frameBuf[5] << 8) | frameBuf[6];
        if (type != 0x0F09) {
          inFrame = false;
        }
      }

      if (inFrame && frameIdx >= 9) {
        handleRawPresence(frameBuf[8] == 0x01);
        inFrame = false;
      }
    }
  }

  if (offTimerRunning && (millis() - offTimerStart >= OFF_DELAY_MS)) {
    offTimerRunning = false;
    pressSwitchBot("消灯");
  }
}

詳細

[Arduino IDE]XIAO ESP32C6ボードのArduino登録

以下の手順でESP32シリーズのボード登録を有効にして,XIAO ESP32C6を登録する:

  1. [ファイル]→[環境設定...]を選択する
    環境設定を開く
  2. 環境設定ウィンドウの追加のボードマネージャのURL:欄にhttps://espressif.github.io/arduino-esp32/package_esp32_index.jsonを入力して,[OK]を押す
    追加のボードマネージャにESP32のURLを記載
  3. [ツール]→[ボード:]→[ボードマネージャ...]を選択する
    ボードマネージャを開く
  4. 入力欄にesp32と入力して,esp32 by Espressif Systemsのほうの[インストール]を押す.インストールが完了したら[閉じる]を押す
    esp32 by Espressif Systemsのインストール
  5. 再度,[ツール]→[ボード:]の順にたどると[ESP32 Arduino]が増えているので,XIAO_ESP32C6を探して選択する
    XIAO_ESP32C6の選択
  6. 再度,[ツール]を開くとXIAO_ESP32C6のメニューが増える.特に変更は不要だが,[ポート]の項目にてXIAO ESP32C6が接続されたCOMポートを選択する(ここではCOM3)
    XIAO_ESP32C6の接続ポート選択

[Arduino IDE]ライブラリーの登録

今回のソフトでは,以下のライブラリーを利用する:

  • NimBLE-Arduino:Bluetooth Low Energyの通信制御(SwitchBotボットの通信用)
  • Adafruit NeoPixel:RGB LEDの制御

たとえば,NimBLE-Arduinoライブラリーの場合の追加手順は以下のとおりである:

  1. [ツール]→[ライブラリを管理...]を選択する
    ライブラリマネージャを開く
  2. ライブラリマネージャウィンドウの入力欄にnimble-arduinoと入力して,読み込みを待つ.読み込み完了後,リストからNimBLE-Arduinoを探して,[インストール]を押す.インストールが完了したら[閉じる]を押す
    NimBLE-Arduinoのインストール

[Arduino IDE][XIAO ESP32C6][MR60BHA2]ミリ波センサー在不在検出方法

仕様が不明であったため(調べてないだけ),ミリ波センサーの生の通信データを受信して,部屋に人がいるときといないときのデータを解析した(Claudeが).その結果,上述のとおりの在不在判定にたどり着いた.在不在のみを判別するソースコードは以下のとおり:

/*
 * ステップ1: mmWaveレーダー(MR60BHA2)のUART生データ確認
 *
 * 目的: 在室フレームと不在フレームが正しく届いているかを目視確認する
 *
 * ハードウェア: XIAO ESP32C6 + MR60BHA2 (Sensor Kit 内蔵接続)
 *   UART: 115200bps, RX=GPIO17, TX=GPIO16
 *
 * 期待する出力:
 *   在室: 01 XX XX 00 02 0F 09 CS 01 00 FE
 *   不在: 01 XX XX 00 02 0F 09 CS 00 00 FF
 *                              ^^ ^^
 *                      チェックサム 在室フラグ(01=在室 / 00=不在)
 */

#define RADAR_RX_PIN 17
#define RADAR_TX_PIN 16
#define RADAR_BAUD   115200

// レーダー用のシリアルポート(UART1を使う)
HardwareSerial RadarSerial(1);

// フレーム組み立て用
uint8_t frameBuf[32];
uint8_t frameIdx = 0;
bool    inFrame  = false;

void setup() {
  // USB経由のデバッグ出力
  Serial.begin(115200);
  delay(2000);              // シリアルモニタが開くのを待つ
  Serial.println();
  Serial.println("=================================================");
  Serial.println(" ステップ1: レーダーUART生データ確認");
  Serial.println("=================================================");
  Serial.println("在室フレーム(0F09)だけを抽出して表示します。");
  Serial.println("人が居る時と居ない時で 9バイト目 が変わるはずです。");
  Serial.println();

  // レーダーとのUARTを開始
  RadarSerial.begin(RADAR_BAUD, SERIAL_8N1, RADAR_RX_PIN, RADAR_TX_PIN);
}

void loop() {
  while (RadarSerial.available()) {
    uint8_t b = RadarSerial.read();

    if (!inFrame) {
      // フレーム先頭(0x01)を探す
      if (b == 0x01) {
        inFrame  = true;
        frameIdx = 0;
        frameBuf[frameIdx++] = b;
      }
    } else {
      if (frameIdx < sizeof(frameBuf)) {
        frameBuf[frameIdx++] = b;
      }

      // ヘッダー(1)+ID(2)+LEN(2)+TYPE(2) = 7バイトで種別を判定
      if (frameIdx == 7) {
        uint16_t type = ((uint16_t)frameBuf[5] << 8) | frameBuf[6];
        if (type != 0x0F09) {
          inFrame = false;      // 在室フレーム以外は捨てる
        }
      }

      // 在室フレームなら11バイト集めて表示
      if (inFrame && frameIdx >= 11) {
        Serial.print("0F09: ");
        for (uint8_t i = 0; i < 11; i++) {
          if (frameBuf[i] < 0x10) Serial.print('0');
          Serial.print(frameBuf[i], HEX);
          Serial.print(' ');
        }
        // 在室フラグ(インデックス8)を人間向けに表示
        Serial.print("  => ");
        Serial.println(frameBuf[8] == 0x01 ? "在室" : "不在");

        inFrame = false;
      }
    }
  }
}

また,実行時のログは以下のように表示される:

在不在の通信データ確認

[Arduino IDE][XIAO ESP32C6][SwitchBotボット]ESP32C6でのSwitchBotボット操作

本記事,ここで初めてスマホ,Android OSのAQUOS*6が登場する.

以下の方法にてSwitchBotボットのMACアドレスを確認した.nRF Connectアプリはバックグラウンドで起動しておけば,BLEのデバイスと通信したときに各種情報が確認できる!

  1. nRF Connectアプリ,SwitchBotアプリの両方をインストールする
    nRF ConnectアプリとSwitchBotアプリのインストール
  2. SwitchBotボットを操作する前にBLE通信を観測用のnRF Connectを起動させて,バックグラウンドで起動させたままにしておく
    nRF Connect起動後の画面(特にまだ操作しない)
  3. SwitchBotのアカウントを作成して,SwitchBotアプリのトップ画面を表示後,中央の[+デバイスを追加]をタップする
    デバイスを追加をタップ
  4. SwitchBotアプリが自動で周辺のSwitchBotデバイスを検出するため,[ボット]をタップする
    ボットをタップ
  5. SwitchBotアプリの画面にて,用意したボットとそのMACアドレスの最下位バイトが表示される.ボットの枠の電源ボタンイラストのボタンをタップすると,その後にnRF Connectのバナー(WoHand has connected)が表示されるので[Yes and open]にタップする
    ボットが表示されるので操作する
  6. nRF Connectのアプリに表示切替後,右上に表示されているMACアドレスが制御対象のSwitchBotボットのMACアドレスである
    SwitchBotボットのMACアドレス

このMACアドレスを元に,SwitchBotボットの操作を10秒ごとに行うソースコードは以下のとおり.ただし,指押しの押下の処理であり,指は押してはいないものの出たままの状態になる.

/*
 * ステップ2: BLEでSwitchBot Botを動かす確認
 *
 * 目的: 10秒ごとに press コマンドを送り、繰り返し動作するかを確認する
 *
 * 必要なライブラリ:
 *   NimBLE-Arduino(ライブラリマネージャからインストール)
 *   ※ESP32標準のBLEライブラリより軽量・安定で、
 *     接続の再確立まわりの挙動が素直なため採用
 *
 * SwitchBot Bot のパラメータ:
 *   MAC             : F4:A3:99:**:**:**
 *   Service UUID    : cba20d00-224d-11e6-9fb8-0002a5d5c51b
 *   Write Char UUID : cba20002-224d-11e6-9fb8-0002a5d5c51b
 *   Press コマンド  : 57 01 01
 */

#include <NimBLEDevice.h>

// ----- SwitchBot の設定 -----
// MACアドレスは実際のアドレスを記載する
static const char* BOT_MAC      = "f4:a3:99:**:**:**";   // 小文字で指定
static const char* SERVICE_UUID = "cba20d00-224d-11e6-9fb8-0002a5d5c51b";
static const char* WRITE_UUID   = "cba20002-224d-11e6-9fb8-0002a5d5c51b";
static const uint8_t PRESS_CMD[3] = { 0x57, 0x01, 0x01 };

// ----- 動作設定 -----
static const uint32_t PRESS_INTERVAL_MS = 10000;   // 10秒ごとに押す
static const uint8_t  MAX_RETRY         = 3;       // 接続リトライ回数

unsigned long lastPressTime = 0;
uint32_t pressCount = 0;
uint32_t successCount = 0;

/*
 * SwitchBot に press コマンドを1回送る
 * 戻り値: true=成功 / false=失敗
 */
bool pressSwitchBot() {
  NimBLEClient* client = nullptr;

  // 既存のクライアントがあれば再利用、なければ新規作成
  if (NimBLEDevice::getCreatedClientCount() > 0) {
    client = NimBLEDevice::getClientByPeerAddress(NimBLEAddress(BOT_MAC, BLE_ADDR_RANDOM));
    if (client == nullptr) {
      client = NimBLEDevice::getDisconnectedClient();
    }
  }
  if (client == nullptr) {
    client = NimBLEDevice::createClient();
    client->setConnectTimeout(5 * 1000);    // 接続タイムアウト5秒
  }

  // --- 接続 ---
  Serial.println("  [BLE] 接続を試みます...");
  // SwitchBotはランダムアドレスを使うため BLE_ADDR_RANDOM を指定する
  NimBLEAddress addr(BOT_MAC, BLE_ADDR_RANDOM);

  if (!client->connect(addr)) {
    Serial.println("  [BLE] 接続失敗");
    return false;
  }
  Serial.println("  [BLE] 接続成功");

  // --- サービスとキャラクタリスティックを取得 ---
  NimBLERemoteService* service = client->getService(SERVICE_UUID);
  if (service == nullptr) {
    Serial.println("  [BLE] サービスが見つかりません");
    client->disconnect();
    return false;
  }

  NimBLERemoteCharacteristic* chr = service->getCharacteristic(WRITE_UUID);
  if (chr == nullptr) {
    Serial.println("  [BLE] キャラクタリスティックが見つかりません");
    client->disconnect();
    return false;
  }

  // --- コマンド書き込み ---
  bool ok = chr->writeValue(PRESS_CMD, sizeof(PRESS_CMD), true);  // true=応答待ち
  if (ok) {
    Serial.println("  [BLE] ★ press コマンド送信成功!");
  } else {
    Serial.println("  [BLE] 書き込み失敗");
  }

  // --- 切断 ---
  // SwitchBotは繋ぎっぱなしにすると約15秒で自分から切るため、
  // 用が済んだらこちらから明示的に切る
  delay(200);
  client->disconnect();
  Serial.println("  [BLE] 切断しました");

  return ok;
}

void setup() {
  Serial.begin(115200);
  delay(2000);
  Serial.println();
  Serial.println("=================================================");
  Serial.println(" ステップ2: SwitchBot BLE動作確認");
  Serial.println("=================================================");
  Serial.printf("10秒ごとに press コマンドを送信します。\n");
  Serial.printf("対象: %s\n", BOT_MAC);
  Serial.println("Botのアームが繰り返し動けば成功です。");
  Serial.println();

  // BLE初期化
  NimBLEDevice::init("");
  NimBLEDevice::setPower(ESP_PWR_LVL_P9);   // 送信出力を最大にする

  lastPressTime = millis() - PRESS_INTERVAL_MS;   // 起動直後に1回実行
}

void loop() {
  if (millis() - lastPressTime >= PRESS_INTERVAL_MS) {
    lastPressTime = millis();
    pressCount++;

    Serial.printf("\n--- %lu回目の press (経過 %lu 秒) ---\n",
                  pressCount, millis() / 1000);

    // 失敗したらリトライする
    bool ok = false;
    for (uint8_t retry = 0; retry < MAX_RETRY && !ok; retry++) {
      if (retry > 0) {
        Serial.printf("  リトライ %d/%d\n", retry, MAX_RETRY - 1);
        delay(500);
      }
      ok = pressSwitchBot();
    }

    if (ok) successCount++;
    Serial.printf("成績: %lu / %lu 成功\n", successCount, pressCount);
  }

  delay(10);
}

[Arduino IDE][XIAO ESP32C6]LED制御

RGB LEDのLチカ動作チェックとして2秒ごとに赤,緑,青,白,消灯を繰り返すソースコードは以下のとおり:

/*
 * LEDテスト: ベースボードの WS2812 RGB LED 確認
 *
 * 判明したこと(Seeed公式サンプルより):
 *   RGB LED はベースボード上にあり、XIAOの D1 ピンに接続されている
 *   制御には Adafruit_NeoPixel ライブラリを使う
 *
 * 事前準備:
 *   Sketch > ライブラリを管理 で「Adafruit NeoPixel」をインストール
 *
 * 期待する動作:
 *   赤 → 緑 → 青 → 白 → 消灯 を2秒ずつ繰り返す
 */

#include <Adafruit_NeoPixel.h>

const int pixelPin = D1;    // ベースボードのRGB LED
Adafruit_NeoPixel pixels(1, pixelPin, NEO_GRB + NEO_KHZ800);

void showColor(const char* name, uint8_t r, uint8_t g, uint8_t b) {
  Serial.printf("色: %s (R=%d G=%d B=%d)\n", name, r, g, b);
  pixels.setPixelColor(0, pixels.Color(r, g, b));
  pixels.show();
}

void setup() {
  Serial.begin(115200);
  delay(2000);
  Serial.println();
  Serial.println("=================================================");
  Serial.println(" LEDテスト3: ベースボードRGB LED (D1)");
  Serial.println("=================================================");
  Serial.println("赤→緑→青→白→消灯 を繰り返します。");
  Serial.println();

  pixels.begin();
  pixels.clear();
  pixels.show();
}

void loop() {
  showColor("赤",   64,  0,  0);
  delay(2000);

  showColor("緑",    0, 64,  0);
  delay(2000);

  showColor("青",    0,  0, 64);
  delay(2000);

  showColor("白",   64, 64, 64);
  delay(2000);

  showColor("消灯",  0,  0,  0);
  delay(2000);

  Serial.println("--- 一巡しました ---");
}

その他

当初はESP Homeをベースに実装する手はずで進んでいたが,2日間の夜3時間程度のパワーでは解決できなかったので諦め.その後,何とか動いたんじゃないか,これ? と思った時点でふと冷静になったら技術基準適合証明通ってないから180日の評価を除き使えないことに気づいた悲痛な私のツイッター(の始まりのツイート):

改訂履歴

# 日付 内容
1 2026/08/12 ステップ2: BLEでSwitchBot Botを動かす確認のソースコードのMACアドレス末尾に:が付いていたのを削除

参考サイト

  1. [壁取付]かってにスイッチ(2線式) | 商品ラインアップ | コスモシリーズワイド21 | スイッチ・コンセント(配線器具) | Panasonic(2026/08/11現在)
  2. MR60BHA2 入門ガイド | Seeed Studio Wiki(2026/08/11現在)

※本記事におけるソフトウェア作成にはClaudeを利用している

*1:下痢か二日酔いが該当.

*2:ミリ波レーダー,mmWaveとも呼ばれる.

*3:XIAO ESP32C6はOK☞202-JCE066にて情報あり

*4:0F 09のデータ受信の後ろの1バイトはチェックサムであり,毎回値が変化する.また,先頭の01の後ろの2バイトは固定値ではなさそうである.

*5:たとえば,在室判定中に指ロボット動作要求送信中に青点灯に重ねて白点滅はせず,白点滅単独の動作が有効になる.

*6:Appleの端末でも同等の操作ができると思われるが,融通が利きそうなAndroid端末を選択した.

2026/05/05時点:NHKネットラジオらじる★らじるをラズベリーパイで自動録音する

☞2021年11月11日時点はこちら:NHKネットラジオらじる★らじるをラズベリーパイで自動録音する - so2akt’s technical diary

目的

NHKネットラジオことらじる★らじる(以下,NHKラジオと記載)をRaspberry Pi(以下,ラズパイと記載)にてシェルスクリプトおよびcronで自動録音を2021年に一時期実施していたが,5年経過するとURL変更されて機能しなくなっていた.

この2026年4月~5月のゴールデンウィークをかけて,Volumioのアップデートとらじる★らじるの録音をアップデートしたので記録しておく.

もともとらじる★らじる録音のきっかけは,語学をNHKラジオで学習するにあたり

  1. 言語によっては日中の業務時間中しか放送しておらず聴取できない*1
  2. その日の予定によっては朝または夜の放送時間に聴取できないことがある

という理由から,番組ごとに自動録音させるものを作りたかったことによる.

名称 規格 製造会社 備考
シングルボードコンピューター Raspberry Pi Model B Rev 2(SoC:BCM2835 ARM11,RAM:512[MB]など) ラズベリーパイ財団 cat /proc/cpuinfo | grep Modelにて確認
OS Volumio 3.912 Volumio SRL cat /etc/os-release | grep VOLUMIO_VERSIONにて確認
NAS NASNE(CECH-ZNR2J01,HDD 1[TB]) ソニー・インタラクティブエンタテインメント
コーデックライブラリー ffmpeg version 4.1.11-0+deb10u1+rpt1 the FFmpeg developers ffmpeg -versionで確認

また,GitHubのリポジトリーは以下: github.com

想定読者

  • Raspberry Pi等でらじる★らじるを録音したい方

結論

それぞれ第1とFMの放送URLは

にあるとおりで,東京の場合は以下のURLが該当する:

放送局 URL 備考
第1 https://simul.drdi.st.nhk/live/3/joined/master.m3u8 東京放送局
第2 https://simul.drdi.st.nhk/live/4/joined/master.m3u8 2026年3月29日の放送で閉局
FM https://simul.drdi.st.nhk/live/5/joined/master.m3u8 東京放送局

スクリプトnhk-radio-recorder.shとしては以下のとおりで,FMを15分録音して/mnt/NAS/GOGAKU/へ録音データを保存する場合は

./nhk-radio-recorder.sh fm 15 /tmp/test /mnt/NAS/GOGAKU/

とする.ここで/tmp/testは,一時的にローカルで録音データを保存する場所を意味する.

また,/mnt/NAS/GOGAKU/のマウント設定のオプションにはvers=1.0,rw,uid=volumio,gid=volumio,file_mode=0666,dir_mode=0777,nopermをWebUIで以下のとおり指定している*2

ネットワークドライブのマウントオプション

定期実行としては,crontabがあるが,Volumioにはデフォルトで入ってなかったので

sudo apt install cron -y
sudo service cron start
sudo systemctl enable cron
crontab -e

にて新規作成されたcronに

50 23 * * 1-5 /home/volumio/nhk-radio-recorder/nhk-radio-recorder.sh fm 11 /tmp /mnt/NAS/GOGAKU/english/timetrial > /home/volumio/cron.log 2>&1

を追記した.これは,月~金の23:50から10分番組の英会話タイムトライアルの登録である.シェルの第1引数を10ではなく11としているのは,録音のラグを考慮して少し多めに録音するためである.ログとしては,今回実行分だけ残せばヨシとして> /home/volumio/cron.log 2>&1としている.

詳細

2021/11/11時点の記事のシェルスクリプトを少し手を入れて再運用仕様としたが

  1. 新しいらじる★らじるのURLに対して,従来使っていたavconvでは対応できなかった
  2. Volumio 2.882ではffmpegをインストールできなかった

ことから,Volumioを3.x系に新しくしたところffmpegがインストールできた*3ので,修正に至っている.

その他

NHKラジオもいろいろ仕様を変えているようで,まさか過去のURLが動かないなんて思っていなかった.それに加えて,今回デバッグ中にNHKラジオ第2がまったく録音できずに必ず400 Bad Requestとなることに15分くらい悩まされたが,調べてみるとそもそも閉局していた始末であった.

参考サイト

  1. らじるらじるをRPiで録音しています。(2026年01月20日現在) #RaspberryPi - Qiita(2026/05/05現在)

ほかは,ChatGPTに聞いて解決.

nasne 1TBモデル (CECH-ZNR2J01)【メーカー生産終了】

nasne 1TBモデル (CECH-ZNR2J01)【メーカー生産終了】

  • ソニー・インタラクティブエンタテインメント
Amazon

*1:土日にまとめてその週の再放送,または見逃し配信にて1週間前のものは聴取できる

*2:これを設定しないと,ファイルコピー時にpreserving timesおよびpreserving permissionsがOperation not permittedと表示される

*3:正確にはapt installで指定しなくてもデフォルトでインストールされていた

初代ラズパイで使えるVolumioのバージョンは3.x系

目的

かつて2021年くらいに

  1. volumio2でapt-get upgradeに失敗する - so2akt’s technical diary
  2. NHKネットラジオらじる★らじるをラズベリーパイで自動録音する - so2akt’s technical diary

でVolumioをいじっていて,システムをVolumio 2.xから最新の4.xに更新したが,正常に起動しなかった(VolumioのWebUIからのアップデートができなかったので,SDカード書き替え).

Volumio4.119は起動するがIPアドレスが振られない

そもそも今の時代にラズパイの初代を使っていることが原因なのは明白だが,IrBerryDACを載せている以上は,まだこのまま初代を使いたい.初代ラズパイで使える最新のVolumioのバージョンを確認したので記載しておく.

名称 規格 製造会社 備考
シングルボードコンピューター Raspberry Pi Model B Rev 2(SoC:BCM2835 ARM11,RAM:512[MB]など) ラズベリーパイ財団 cat /proc/cpuinfo | grep Modelにて確認
OS書込ソフト Raspberry Pi Imager,2.0.0 ラズベリーパイ財団

想定読者

  • 初代ラズパイで動作できるVolumioのバージョンを確認したい方

結論

私が見つけられたものでは書込イメージは3.887が初代ラズパイで使えるVolumioの最新バージョンで,リンク先は以下のとおり:

3.887で起動後,VolumioのWebUIからシステムアップデート行うと,3.912にできる.

詳細

2026/05/05現在で,Volumioの最新バージョンは4.119である.通常はそのイメージを書き込んだあと,ボードを見る限りではLANのインジケーター含めて正常に起動しているように見えていた.どうしてもアクセスできないので,HDMIケーブルを挿してテレビに映したが,IPアドレスがローカルアドレスしか割り振られていなかった:

ラズパイ起動後のHDMI表示画面

volumioにログインして,状況を確認したところ以下の表示だった*1

dhcpd.serviceがうまく起動していないもよう

少し情報を追加で追ってみたが,結局のところ,これ以上はわからず断念:

詳細を追ってみたがわからず

最終的に,VolumioのForumを探したところ,同じように初代ラズパイでVolumioを使いたいユーザーの投稿を見つけて,Volumio 3.887に至る:

その他

悲痛な私のツイッター:

参考サイト

  1. Volumio 3.x download links? - Help and Support - Volumio Community(2026/05/05現在)
  2. Volumio-3.887-2025-12-19-pi.zip(2026/05/05現在)
  3. Get Started - Volumio(2026/05/05現在)

*1:SerialNumber:とMACアドレスはGeminiを使ってマスクしている

Volumio 3.xでネットワークドライブを名前でマウントできない問題を解決する

目的

かつて2021年くらいに

  1. volumio2でapt-get upgradeに失敗する - so2akt’s technical diary
  2. NHKネットラジオらじる★らじるをラズベリーパイで自動録音する - so2akt’s technical diary

でVolumioをいじっていて,システムをVolumio 2.xから3.xに更新した(VolumioのWebUIからのアップデートができなかったが).

そのため,SDカードの書き替えから実施したら,NASNEのマウントがIPアドレス指定でないとできなくなった問題に当たったので,その解決策を記載する.名前でマウントして失敗時はError in moutingとか: could not resolve addres forとかUnknown errorとか出る.

マウント失敗

名称 規格 製造会社 備考
シングルボードコンピューター Raspberry Pi Model B Rev 2(SoC:BCM2835 ARM11,RAM:512[MB]など) ラズベリーパイ財団 cat /proc/cpuinfo | grep Modelにて確認
OS Volumio 3.912 Volumio SRL cat /etc/os-release | grep VOLUMIO_VERSIONにて確認
NAS NASNE(CECH-ZNR2J01,HDD 1[TB]) ソニー・インタラクティブエンタテインメント

想定読者

  • Volumioでネットワークドライブが割り当てたい方でエラーが出る方

結論

libnss-winbindパッケージのインストールのみで解決できた:

sudo apt install libnss-winbind -y

マウント成功

詳細

マウントのための記述そのものは以下のとおりで,これは間違っていなかった:

設定は正しいが,IPアドレス入力以外はマウント失敗していた

SSHでVolumioに入ってから,pingを打つとシステムエラーで,Geminiにavahi-daemonのリスタートで解決するかもしれないと言われたが解決できず:

volumio@volumio:~$ sudo systemctl restart avahi-daemon
volumio@volumio:~$ ping -c 1 NASNE
ping: NASNE: System error

さらにGeminiに聞いて,libnss-winbindをインストールしたらpingも疎通し,NASNEのマウントができるようになった:

volumio@volumio:~$ sudo apt install libnss-winbind -y

Reading package lists... 0%

Reading package lists... 100%

Reading package lists... Done


Building dependency tree... 0%

Building dependency tree... 0%

Building dependency tree... 50%

Building dependency tree... 50%

Building dependency tree... 58%

Building dependency tree... 77%

Building dependency tree... 96%

Building dependency tree


Reading state information... 0%

Reading state information... 0%

Reading state information... Done

The following package was automatically installed and is no longer required:
  libdw1
Use 'sudo apt autoremove' to remove it.
Suggested packages:
  libpam-winbind
The following NEW packages will be installed:
  libnss-winbind
0 upgraded, 1 newly installed, 0 to remove and 1 not upgraded.
Need to get 114 kB of archives.
After this operation, 145 kB of additional disk space will be used.

0% [Working]
0% [Working]
0% [Connected to apt2.volumio.org (31.14.96.151)]

0% [Waiting for headers]

Get:1 https://apt2.volumio.org/raspbian buster/main armhf libnss-winbind armhf 2:4.9.5+dfsg-5+deb10u1+rpi1 [114 kB]

23% [1 libnss-winbind 32.5 kB/114 kB 29%]

100% [Working]

Fetched 114 kB in 3s (41.5 kB/s)
debconf: delaying package configuration, since apt-utils is not installed
Selecting previously unselected package libnss-winbind:armhf.
(Reading database ...
(Reading database ... 5%
(Reading database ... 10%
(Reading database ... 15%
(Reading database ... 20%
(Reading database ... 25%
(Reading database ... 30%
(Reading database ... 35%
(Reading database ... 40%
(Reading database ... 45%
(Reading database ... 50%
(Reading database ... 55%
(Reading database ... 60%
(Reading database ... 65%
(Reading database ... 70%
(Reading database ... 75%
(Reading database ... 80%
(Reading database ... 85%
(Reading database ... 90%
(Reading database ... 95%
(Reading database ... 100%
(Reading database ... 35002 files and directories currently installed.)
Preparing to unpack .../libnss-winbind_2%3a4.9.5+dfsg-5+deb10u1+rpi1_armhf.deb ...
Unpacking libnss-winbind:armhf (2:4.9.5+dfsg-5+deb10u1+rpi1) ...
Setting up libnss-winbind:armhf (2:4.9.5+dfsg-5+deb10u1+rpi1) ...
Processing triggers for libc-bin (2.28-10+rpt2+rpi1+deb10u2) ...
volumio@volumio:~$ ping -c 1 NASNE
PING NASNE (172.16.???.???) 56(84) bytes of data.
64 bytes from 172.16.???.??? (172.16.???.???): icmp_seq=1 ttl=64 time=1.90 ms

--- NASNE ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 1.904/1.904/1.904/0.000 ms

その結果,無事にマウント成功した:

マウント成功

参考サイト

  1. Volumioのインストールと設定方法を日本語で解説。(2026/05/05現在)

ほかは,Geminiに聞いて解決.

Raspberry Pi 3の低電圧警告が出ない電源アダプターを選ぶ

目的

まだラズパイ3なんて使っているの? といわれそうだが,ずいぶん前から発生していたラズパイ3の電力不足表示が出ないアダプターを選定したので,その過程を記載する.

画面上部の低電圧警告アイコンとメッセージ

きっかけは,業務でのちょっとしたことを余っているラズパイにやらせようとして,数年ぶりに再稼働して低電圧警告を再確認.まぁこのままでもよいかと思っていたが,コンソールで何かを実行中に他の操作をするとラズパイ3がリセットする現象が不定期に発生したことによる.

名称 規格 製造会社 備考
シングルボードコンピュータ SC0073,Raspberry Pi 3 Model B+ Rev 1.3,Cortex-A53 1.4[GHz],DDR2 SDRAM 1[GB],要求電源5[V],2.5[A] ラズベリーパイ財団 cat /proc/cpuinfo | grep Modelにて確認
OS Debian GNU/Linux 12 (bookworm) Debian cat /etc/os-releaseにて確認
タッチパネルディスプレイ SC1227,7インチ,画面解像度800x480 ラズベリーパイ財団 RSコンポーネンツにてRS品番:899-7466
ケース ASM-1900035-21,公式タッチパネル用ケース DesignSpark RSコンポーネンツにてRS品番:906-4665
アクティブヒートシンク アルミニウム・銅製,電源供給用ピンヘッダーあり GeeekPi Amazon.co.jpにてASIN:B07Q2RQZJJ,1079JPY
電源アダプターA
FX18P-050300U1,5[V],3[A]出力 Vesonn Amazon.co.jpにてASIN:B0875S9F66,1260JPY,USB microB出力,スイッチ付き
電源アダプターB
AD-K50P300,5[V],3[A]出力 厦门友盟电子 秋月電子にて販売コード:112001,1100JPY,USB microB出力
電源アダプターC
GPE024L-050400-6,5[V],4[A]出力,ジャック外径 5.5mm 内径 2.1mm,センタープラス コズムワン Amazon.co.jpにてASIN:B0C33B6DG6,1260JPY,DCジャック出力
電源コネクター変換ケーブル
ジャック外径5.5mm 内径2.1mm,センタープラスが適合 オーディオファン Amazon.co.jpにてASIN:B0CS358757,680JPY,DCジャック→USB microB変換
Y分岐ケーブル
(長いほうはデータと電源で短いほうは電源のみ) ModMyPi 旧ModMyPi時代に購入SKU:104268

想定読者

結論

後述の検証方法詳細で一番よい成績の電源アダプターB(AD-K50P300,秋月電子にて販売コード:112001)が比較的,低電圧警告表示なく安定して動作する.「比較的」と表現したのは,今回の検証で100%表示されなかったわけではなかったため.

検証方法詳細

低電圧警告には,電源の出力電流だけではなく出力電圧が安定するかどうかも関連するようで,ここでは,電源アダプターを3種類(上記,表参照),接続方法を3種類の合計9パターンを検証した.

接続方法は,以下の3パターンで,検証時はラズパイ3のFirefoxにてYouTubeの同一の動画(https://www.youtube.com/watch?v=ZLJtDkB9TEM)を数分再生して低電圧警告が表示されるかどうかを3回ずつ観察した.

接続方法# 詳細
接続方法1 タッチパネルディスプレイ基板にUSB microB電源供給,タッチパネルディスプレイからラズパイ3へピンヘッダーで電源供給
タッチパネルディスプレイ側に供給
5Vのピンヘッダーの接続あり
接続方法2 ラズパイ3基板にUSB microB電源供給,ラズパイ3からタッチパネルディスプレイへピンヘッダーで電源供給
ラズパイ3側に供給
5Vのピンヘッダーの接続あり
接続方法3 Y分岐ケーブルにてタッチパネルディスプレイの基板,ラズパイ3の基板それぞれにUSB microB電源供給
それぞれの基板に供給
5Vのピンヘッダーの接続なし

検証時のラズパイ3の画面スクリーンショットは以下のとおり.同じ構成でも低電圧警告が表示されるときと表示されないときがある.

電源アダプターA 接続方法1 接続方法2 接続方法3
1回目
OK
NG
NG
2回目
NG
OK
OK
3回目
OK
NG
NG
電源アダプターB 接続方法1 接続方法2 接続方法3
1回目
NG
OK
OK
2回目
NG
OK
OK
3回目
NG
OK
OK
電源アダプターC 接続方法1 接続方法2 接続方法3
1日目
NG
OK
NG
2回目
NG
OK
NG
3回目
NG
OK
NG

その他

同じ電源容量の電源アダプターでも,電源アダプターBのようにアダプター直結かつスイッチなしのほうが損失が少ないようで安定するようである.また,電源容量としては電源アダプターCが一番大きいのだが,安定しない結果となった.DCジャックからの変換などで損失が発生している可能性があるかもしれない.

正確には,温度条件による違いがあるかもしれないが,そこは未検証である.

参考サイト

  1. Raspberry Piに電力不足マークを出さずに7インチタッチパネルを接続するには | 日記というほどでも(2025/12/29現在)

Windows 10を1年延命するESU(拡張セキュリティ更新プログラム)を登録する

目的

完全にスルーしていたが,Windows 10は2025年10月14日(明日だ!)に終了してしまう.

たまたまtwitterを見ていて,Extended Security Updates(ESU)という拡張セキュリティ更新プログラムと呼ばれるものが個人向けにも適用できるという情報があったので,登録してみた.

ESUの有効化

名称 規格 製造会社 備考
オペレーティングシステム Windows 10 Pro 64ビット,22H2 マイクロソフト インターナショナル版

想定読者

  • Windows 10のサポートが切れるが,更新に不安がある方
  • Windows 10のサポートが切れるが,Windows 11にするのがめんどくさい方
  • Windows 10のサポートが切れるかつ,OSが英語版の方

結論

詳細は,以下のPCメモさんのYouTube動画がわかりやすいのでご参照ください! 先人の知恵

英語版でのスクリーンショットだが,以下のとおり

  1. PCメモさんの動画を元に,ESU適用に必要なことを済ませる
    1. マイクロソフトアカウントでログインしていること
    2. Windows 10のバージョンが22H2であること(winverコマンド実行で確認するのが楽)
    3. Windowsバックアップを有効にしていること
  2. Windowsメニュー,設定を押して設定画面を開いて,更新とセキュリティをクリック
    設定画面
  3. Windows 10 のサポートは 2025 年 10 月 に終了します 拡張セキュリティ更新プログラムに登録して、デバイスのセキュリティを維持しましょう。」の表示を探し,今すぐ登録をクリック
    拡張セキュリティ更新プログラムに,今すぐ登録
  4. 案内文が出るので,次へ→登録→完了をクリック
    拡張セキュリティ更新プログラムの案内文,次へ
    拡張セキュリティ更新プログラムの案内文,登録
    拡張セキュリティ更新プログラムの案内文,完了
  5. 完了後,表示が登録されている状態に変わる
    拡張セキュリティ更新プログラムの適用完了

参考サイト

  1. 【Windows10】サポート延長のやり方!ESUの登録で期限を延ばす方法(2025/10/13現在)
  2. タダで「Windows 10」を1年間延命できる「ESU」の登録が開始されたので試してみました - やじうまの杜 - 窓の杜(2025/10/13現在)

OBSを32.0.1にアップデートしたらobs-multi-rtmpが動作しなくなった現象の解消

目的

1週間ぶりくらいにゲーム配信しようかと思ってOBS(31.1.2)を立ち上げたところ,32.0.1のアップデートが来ていた.アップデートしてOBSを再起動したら,同時配信プラグインがロードできず,確かに画面にMultiple outputドックが表示されていない現象が発生していたが,解決したので記録しておく*1

obs-multi-rtmpのPlugin load errorウィンドウ

OBS 31.1.2起動直後の画面 OBS 32.0.1起動直後の画面
Multiple outputのドックが左側に表示
ドック表示なし,かつエラーウィンドウ
名称 規格 製造会社 備考
オペレーティングシステム Windows 10 Pro 64ビット,22H2 マイクロソフト インターナショナル版
録画・配信ソフトウェア(旧) OBS Studio,31.1.2 OBS 64ビット版
録画・配信ソフトウェア(新) OBS Studio,32.0.1 OBS 64ビット版
同時配信プラグイン(旧) obs-multi-rtmp,0.5.0.1 SoraYuki ポータブル版
同時配信プラグイン(新) obs-multi-rtmp,0.7.3.1 for OBS 32 SoraYuki ポータブル版,インストーラー版

想定読者

  • OBS 32.0.1にアップデートして,エラーウィンドウが出続ける方
  • 手順書どおりに実施しているつもりでも,エラーウィンドウが出続ける方
  • obs-multi-rtmpを導入してしばらく経過して,エラーウインドウが出続けている方(私は直近,2023/10/30に更新)

結論

obs-multi-rtmpのバージョンアップをする際に,もともとポータブル版を使用していたにもかかわらず,インストーラー版を使用してアップデート完了した気になっていたため.このケースでは,インストーラー版(ここでは0.7.3.1)とポータブル版(0.5.0.1)が共存している状態のため,ポータブル版がうまくロードできないことによりPlugin load errorウィンドウがOBS 32.0.1を起動するたびに表示される.

通常アップデートするのであれば

と合わせたほうがよい.
ここでは,ポータブル版→ポータブル版,ポータブル版→インストーラー版の更新方法を記載する.

ポータブル版→ポータブル版の更新

更新対象は具体的には以下の3つが該当する.

名称 対象ファイルまたはディレクトリー 備考
DLL C:\Program Files\obs-studio\obs-plugins\64bit\obs-multi-rtmp.dll
データベース C:\Program Files\obs-studio\obs-plugins\64bit\obs-multi-rtmp.pdb
各言語翻訳ファイル群 C:\Program Files\obs-studio\data\obs-plugins\obs-multi-rtmp ディレクトリーが対象

更新方法はシンプルで

  1. OBSを終了しておく
  2. obs-multi-rtmp 0.7.3.1 for OBS 32のページからobs-multi-rtmp-0.7.3.0-windows-x64.zipをダウンロードして解凍
  3. 解凍してできたobs-pluginsdataC:\Program Files\obs-studio\ディレクトリーの中にドロップ
  4. 次回OBS起動時より正常に動作する

obs-multi-rtmp-0.7.3.0-windows-x64.zipをダウンロード

ポータブル版→インストーラー版の更新(ポータブル版の削除)

念のため,無事にMultiple outputのドックが表示されることを確認してからポータブル版を削除する.手順は

  1. OBSを終了しておく(この時点ではMultiple outputのドックが表示されていない)
  2. obs-multi-rtmp 0.7.3.1 for OBS 32のページからobs-multi-rtmp-0.7.3.0-windows-x64-Installer.exeをダウンロードして,インストールを実行
    obs-multi-rtmp-0.7.3.0-windows-x64-Installer.exeのダウンロード
  3. OBSを再起動して従来どおりの位置にMultiple outputのドックが表示されることを確認(Plugin load errorウィンドウは,OBS起動時に表示されるまま)
  4. 以下の3つを削除して,次回OBS起動時より正常に動作する
名称 対象ファイルまたはディレクトリー 備考
DLL C:\Program Files\obs-studio\obs-plugins\64bit\obs-multi-rtmp.dll
データベース C:\Program Files\obs-studio\obs-plugins\64bit\obs-multi-rtmp.pdb
各言語翻訳ファイル群 C:\Program Files\obs-studio\data\obs-plugins\obs-multi-rtmp ディレクトリーが対象

その他

twitterで作者に聞いてしまった笑

ポータブル版,インストーラー版での格納先

格納先ディレクトリー 備考
ポータブル版 C:\Program Files\obs-studio\ 左記ディレクトリー内にdll,pdbを手動で格納する
インストーラー版 C:\ProgramData\obs-studio\plugins\obs-multi-rtmp 左記ディレクトリーが作成されてdll,pdbが自動で格納される

改訂履歴

# 日付 内容
1 2025/10/04 ポータブル版,インストーラ版でのOBSプラグイン格納先を追記

参考サイト

  1. obs-multi-rtmp | OBS複数サイト同時配信プラグイン(2025/09/29現在)
  2. obs-multi-rtmp 0.7.3.1 for OBS 32(2025/09/29現在)

*1:Plugin load errorウィンドウThe following OBS plugins failed to load: obs-multi-rtmp Please update or remove these plugins.が表示されていた