見出し画像

フルカラーLEDテープ(WS2812B)+Arduinoで、ネコちゃんと遊ぶ


開発目的:ネコちゃんのために

ネコちゃんと暮らし初めて、とにかくご機嫌に遊んで過ごしていただきたいと考えるようになりました。毎日誠心誠意ねこじゃらしを振るのに加え、ご自身でも遊んでいただけるものを捧げたくなり、フルカラーLEDテープを使って、センサーで感知すると光がネズミのように走り回るオモチャを作りました。

完成品紹介

テレビ横の人感(ネコ感?)センサーに近づくと、動作スタートします。色は折り返し地点でランダムに変化し、早く移動したり少しゆっくりになったり、5秒静止したりします。これによって、生き物っぽさを演出しています。
ESP32を使ってWi-Fi接続し、Alexaからの音声起動や(別途Alexaアプリ開発)、定期実行(別途Raspberry PiでNore-RED使用)もできるようにしています。

詳しい開発の話

フルカラーLEDテープ(WS2812B)って?

フルカラーLEDが数珠つなぎになっていて、Arduino等からそれぞれ好きな色に光らせることができます。詳細な使い方については、こちらのページが非常に参考になりました。

大分価格も安くなってきて、Amazonでは1m 60LEDで1000円ぐらいで購入できます。私の買った5m 300LEDのものは、AliExpressで1700円でした。

この製品の素晴らしい点は、1本の汎用ピンがあれば、何個のLEDがつながっていようと関係なく制御できてしまうことです。各LEDにコントローラが入っており、自身の色設定が終わるまで次のLEDに信号を伝えない仕組みとなっているため、コントローラからはn個分の信号を繰り返し送るだけで、全部のLEDを自由に光らせることができます。

ArduinoにはAdafruit_NeoPixelというライブラリがあり、これを使って簡単に光らせることができます。(*)
(*) ESP32で使用したところ、2024/11/24現在 公式ライブラリに不具合があるようで、75個を超えるLEDの制御ができませんでした。ここで説明されているように、公式のライブラリをアンインストールして、有志の修正版のライブラリをフォルダに入れることで正常動作させることができました。

回路構成

回路構成図は以下のような感じです。今回は同時に5つのLEDしか光らせないため、フルカラーLEDテープの電源はArduinoボードから供給しています。(もっと沢山光らせたければ、外部5V電源に接続してください)
なお、フルカラーLEDテープの両面テープを使うとベタベタになって後が大変なので、ガード的な意味合いも兼ねて100均のマスキングテープで上から張り付けています。

回路構成図

① Arduinoボード(今回はESP32ボードを採用)

② フルカラーLEDテープ(WS2812B×n) 

③ 赤外線 人感センサ(SR501)


プログラムコード

ArduinoのESP32用のソースコードです。Wi-Fi経由でブラウザから IPアドレス/start とアクセスできるようにしています。Wi-Fi処理の部分を削除すれば、普通のArduinoでも動くと思います。
(ChatGPT 4oに丸投げしてソースコードを書いてもらいました)

// WS2812B 5m 60 IP65

#include <Adafruit_NeoPixel.h>
#include <WiFi.h>
#include <WebServer.h>
#include "esp_task_wdt.h" // ハードウェアWDT用

#define LED_PIN    27   // LEDテープのデータピン
#define NUM_LEDS   300  // 5mのテープ(1mあたり60ピクセルの場合は300)
#define BRIGHTNESS 32   // 全体の明るさを調整(0-255)
#define TRIGGER_PIN 26  // 人感センサー トリガーピン

const char* ssid = "your_SSID";      // WiFi SSID
const char* password = "your_PASSWORD";  // WiFi パスワード

WebServer server(80);

Adafruit_NeoPixel strip = Adafruit_NeoPixel(NUM_LEDS, LED_PIN, NEO_GRB + NEO_KHZ800);

bool startAnimation = false;
int roundTripCount;
int position;
int direction;  // 1: 行く, -1: 戻る
uint32_t color; // 色
int stepCount;  // ステップカウント
int currentDelay; // 現在の遅延時間

// アニメーションの初期化関数
void initializeAnimation() {
  roundTripCount = 0;
  position = 100;
  direction = 1;
  color = strip.Color(random(0, 256), random(0, 256), random(0, 256));
  stepCount = 0;
  currentDelay = 5;
}

void handleStart() {
  if( startAnimation == false ) {
    startAnimation = true;
    initializeAnimation();
  }
  server.send(200, "text/plain", "Animation started");
}

void setup() {
  Serial.begin(115200);

  strip.begin();
  strip.setBrightness(BRIGHTNESS); // 最大輝度を制限して消費電力を抑える
  strip.show(); // 初期化
  delay(100); // 初期化後の安定化待ち
  randomSeed(analogRead(0)); // ランダムシードを設定

  pinMode(TRIGGER_PIN, INPUT); // トリガーピンの設定

  // WiFi接続とサーバールートの設定
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("\nWiFi connected");
  Serial.println(WiFi.localIP());

  server.on("/start", handleStart);
  server.begin();
  Serial.println("HTTP server started");

  // ハードウェアWDTの初期化
  esp_task_wdt_config_t wdt_config = {
    .timeout_ms = 180000, // 180秒
    .idle_core_mask = 0, // コアID 0で有効
    .trigger_panic = true // WDTがタイムアウトした場合にパニック発生
  };
  esp_task_wdt_init(&wdt_config);
}

void loop() {
  server.handleClient();

  // トリガーピンがHIGHになったときにアニメーションを開始
  if (digitalRead(TRIGGER_PIN) == HIGH) {
    if( startAnimation == false ) {
      startAnimation = true;
      initializeAnimation();
    }
  }

  if (startAnimation) {
    strip.clear();

    // 中心を最も明るく、その周りを徐々に暗くする
    for (int i = -2; i <= 2; i++) {
      int ledIndex = position + i;
      if (ledIndex >= 0 && ledIndex < NUM_LEDS) {
        float brightnessFactor = 1.0 - abs(i) * 0.3; // 中心から離れるごとに明るさを減少
        uint32_t dimmedColor = dimColor(color, brightnessFactor);
        strip.setPixelColor(ledIndex, dimmedColor);
      }
    }

    strip.show();
    delay(currentDelay); // 移動速度の調整

    // WDTをリフレッシュ
    esp_task_wdt_reset();

    // 次の位置を計算
    position += direction;
    stepCount++;

    // 50個進むごとに遅延時間をランダムに変更, 5%の確率で5秒停止
    if (stepCount >= 50) {
      currentDelay = random(1, 11); // 1~10のランダムな遅延時間に設定
      if (random(0, 100) < 5) { // 5%の確率
        delay(5000); // 5秒間停止
      }
      stepCount = 0;
    }

    if (position >= NUM_LEDS - 1 || position <= 0) {
      direction *= -1; // 端に到達したら方向を変える
      roundTripCount++; // 往復カウントを増加
      // 折り返すときに色をランダムに変更
      color = strip.Color(random(0, 256), random(0, 256), random(0, 256));
    }

    // 5往復したら停止して全てのLEDを消灯
    if (roundTripCount >= 10) {
      startAnimation = false;
      strip.clear();
      strip.show();
    }
  }
}

// 色を暗くするための関数
uint32_t dimColor(uint32_t color, float factor) {
  uint8_t r = (uint8_t)((color >> 16) & 0xFF);
  uint8_t g = (uint8_t)((color >> 8) & 0xFF);
  uint8_t b = (uint8_t)(color & 0xFF);
  return strip.Color((uint8_t)(r * factor), (uint8_t)(g * factor), (uint8_t)(b * factor));
}

使用感

ネコちゃんにもよるかもしれませんが、うちの子は遊ぶのが大好きなので、センサー感知して動き出すと(遊びモードの時は)必死で追いかけてくれます。
ちょっと台所で手が離せない時や、危ないものを触っていて近寄ってほしくない時など、このLEDを光らせておくことで距離をとる&時間を確保するといった使い方もできて役立っています。


いいなと思ったら応援しよう!