目次

目次

【Maker Faire Tokyo 2023】おしゃべりぱくちゃんず作成までの道のり(4) ~通信機・まとめ編~

アバター画像
深沢雛子
アバター画像
深沢雛子
最終更新日2025/11/26 投稿日2023/12/27

はじめに

NX開発推進部Android開発グループ所属の深沢と申します。今回は、Maker Faire Tokyo 2023に参加し、出展物作成までの過程を振り返りたいと思います。Maker Faireとは何かについては公式サイトをご参照ください。

全4回投稿予定です。本記事は第4回になります。


前回までの記事の内容でぱくちゃん本体が完成しました。今回はまだ作成していないぱくちゃんに信号を送る通信機とMaker Faire Tokyo 2023当日までのお話について書いていこうと思います。

通信機の制作

初期の検証ではM5StickCPlusを通信機として使用していましたが、トランシーバーのように持てる方が良いという考えから、マイクがついていて画面の大きいM5GOに変更しました。

カバー作成

M5GOがすっぽり入り、トランシーバーのように見えるカバーをモデリングし、印刷しました。カム機構で作成は慣れていたのでかなりスムーズに作成することができました。カバーは箱部分と蓋部分を作成し、中にM5GOを入れられるようにしています。

蓋には穴をあけ、アンテナに見立てた棒を作成して差し込むようにしました。 印刷したところ、少し蓋がカパカパしてしまいすぐに外れてしまいそうだったため間に差し込める板のようなものを急遽追加で作成しました。また、通信機で話しかけたところ少しマイクが反応しづらかったため、穴を開けて音が拾いやすいように調整しました。

通信機裏側写真+穴開けた蓋の写真追加

通信機の裏側。下に挟まっているの部品を追加しました。

マイクが通りやすいように穴を開けました。

通信機を置く台は2枚の板を組み合わせるタイプのスマホスタンドを参考に作成しました。

スタンドのイメージ図。板同士が支え合って自立するようにしている

すべてを印刷し、組み合わせたところ以下のような通信機が完成しました。

作成した通信機

通信機のプログラム

通信機(M5GO)は音声を認識したらぱくちゃん(ATOM Echo)に対してESP-NOWを使ってデータを送信し、ぱくちゃんの制御を行なっています。 以下に通信機のプログラムを一部抜粋して記載します。

1. ESP-NOWでぱくちゃんを探す

SSIDがpakuから始まるデバイスを送信対象としてマークするようにしています。

#include <esp_now.h>
#include <WiFi.h>

void setup() {
  // ESP-NOW初期化
  espNowInit();

  // レシーバをペアリング
  ScanForPaku();
  if (pakuCnt > 0) {
    managePaku();
  }
}

void espNowInit() {
  WiFi.mode(WIFI_STA);
  WiFi.disconnect();
  if (esp_now_init() != ESP_OK) {
    ESP.restart();
  }
}

void ScanForPaku() {
  int8_t scanResults = WiFi.scanNetworks();
  pakuCnt = 0;
  if (scanResults != 0) {
    for (int i = 0; i < scanResults; ++i) {
      String SSID = WiFi.SSID(i);
      int32_t RSSI = WiFi.RSSI(i);
      String BSSIDstr = WiFi.BSSIDstr(i);

      delay(10);
      if (SSID.indexOf("paku") == 0) {
        int mac[6];
        if (6 == sscanf(BSSIDstr.c_str(), "%x:%x:%x:%x:%x:%x", &mac[0], &mac[1], &mac[2], &mac[3], &mac[4], &mac[5])) {
          for (int ii = 0; ii < 6; ++ii) {
            pakus[pakuCnt].peer_addr[ii] = (uint8_t)mac[ii];
          }
        }
        pakus[pakuCnt].channel = WIFI_CHANNEL; 
        pakus[pakuCnt].encrypt = 0;
        pakuCnt++;
      }
    }
  }

  // clean up ram
  WiFi.scanDelete();
}

void managePaku() {
  for (int i = 0; i < pakuCnt; i++) {
    bool exists = esp_now_is_peer_exist(pakus[i].peer_addr);
    if (!exists) {
      esp_err_t addStatus = esp_now_add_peer(&pakus[i]);
      if (addStatus == ESP_OK) {
        Serial.println("Pair success");
      } else if (addStatus == ESP_ERR_ESPNOW_NOT_INIT) {
        Serial.println("ESPNOW Not Init");
      } else if (addStatus == ESP_ERR_ESPNOW_ARG) {
        Serial.println("Add Peer - Invalid Argument");
      } else if (addStatus == ESP_ERR_ESPNOW_FULL) {
        Serial.println("Peer list full");
      } else if (addStatus == ESP_ERR_ESPNOW_NO_MEM) {
        Serial.println("Out of memory");
      } else if (addStatus == ESP_ERR_ESPNOW_EXIST) {
        Serial.println("Peer Exists");
      } else {
        Serial.println("Not sure what happened");
      }
      delay(100);
    }
  }
}

2. マイクが音を拾ったらぱくちゃんにデータを送る

マイクから拾った音声をFFTで周波数ごとに分解して送信データを作成しています。

#include <M5Unified.h>
#include <driver/i2s.h>
#include <arduinoFFT.h>
#define REC_INTERVAL_MS 100
#define SAMPLING_FREQUENCY 44100

static constexpr const size_t record_length = 200;
static constexpr const size_t record_samplerate = SAMPLING_FREQUENCY;
static size_t rec_record_idx = 2;
static int16_t *rec_data;

int16_t *buffer = NULL;

unsigned int sampling_period_us;

const uint16_t FFTsamples = 256;
double vReal[FFTsamples];
double vImag[FFTsamples];

int targetBand[] = { 3, 4, 5, 6, 7, 8, 9 };
arduinoFFT FFT = arduinoFFT(vReal, vImag, FFTsamples, SAMPLING_FREQUENCY);

void setup() {
  //マイク初期化
  M5.Mic.begin();

  // マイク待ち受け開始
  xTaskCreate(mic_record_task, "mic_record_task", 4096, NULL, 1, NULL);
}

void mic_record_task(void *arg) {
  if (!M5.Mic.isEnabled()) {
    Serial.println("Mic is Disabled");
    return;
  }
  size_t bytesread;
  while (1) {
    if (!ready) {
      vTaskDelay(REC_INTERVAL_MS / portTICK_RATE_MS);
      continue;
    }
    auto data = &rec_data[rec_record_idx * record_length];
    if (M5.Mic.record(data, record_length, record_samplerate)) {
      data = &rec_data[rec_record_idx * record_length];
      buffer = (int16_t *)data;
      fft();
      analyze();
      vTaskDelay(REC_INTERVAL_MS / portTICK_RATE_MS);
    }
  }
}

void fft() {
  for (int i = 0; i < FFTsamples; i++) {
    unsigned long t = micros();
    vReal[i] = buffer[i];
    vImag[i] = 0;
    while ((micros() - t) < sampling_period_us)
      ;
  }

  // 高速フーリエ変換
  FFT.Windowing(FFT_WIN_TYP_HAMMING, FFT_FORWARD);
  FFT.Compute(FFT_FORWARD);
  FFT.ComplexToMagnitude();
}

void analyze() {
  bool overTreshold = false;
  int nsamples = FFTsamples / 2;
  int targetSize = sizeof(targetBand) / sizeof(*targetBand);
  uint8_t data[sizeof(targetBand)];

  for (int i = 0; i < targetSize; i++) {
    for (int band = 0; band < FFTsamples / 2; band++) {
      if (band > targetBand[i]) break;
      if (band != targetBand[i]) continue;
      data[i] = convertoToHex(vReal[targetBand[i]]);
    }
  }

  sendData(data);
}

void sendData(uint8_t data[]) {

  int volume = *std::max_element(data, data + sizeof(data));

  // 音の大きさによって送信数を決める
  int sendCount = calcSendCount(paku, volume);

  for(int i = 0; i < pakuCnt; i++) {
    if (i > sendCount - 1) {
      break;
    }

    const uint8_t *peer_addr = pakus[i].peer_addr;
    esp_err_t result = esp_now_send(peer_addr, data, sizeof(data));
    delay(100);
  }
}

これで通信機側の実装が完了し、喋りかけるとぱくちゃんにデータを送信できるようになりました。

Maker Faire Tokyo 2023当日に向けて

上記までの対応でおしゃべりぱくちゃんずの実装のすべてが完了しました。当日は10個ほどぱくちゃんを並べますのでぱくちゃんの印刷や組み立て、塗装を行いました。 1個体作るのに大体以下のような工程と時間がかかりました。

  • 印刷:合計5時間半
  • 回路作成+ATOM Echoに書き込み:30分
  • 塗装:30分+乾かす時間
  • 組み立てなどその他:30分

量産途中のぱくちゃん達

また、展示に向けてロゴとポップも作成しました。

ロゴ

ポップ

すべての準備を整えてMaker Faire Tokyo 2023の当日に挑みました。

Maker Faire Tokyo 2023開催

全ての準備を経て、ついにMaker Faire Tokyo 2023の開催当日を迎えました。

並べるとカラフルで可愛らしくいい感じです。そして、Maker Faire Tokyo 2023が開始しました。

トラブル発生

開始後、間もなく予想外の事態が発生しました。

通信機に話しかけていないのに、突然ぱくちゃんたちが勝手に動き始めてしまいました。これは当日の現場で初めて起こった現象で、少しパニックになりました。

原因調査したところ、会場内でESP-NOWを用いた通信が無数に飛び交っており、意図しないデータを受け取ってぱくちゃんが暴走してしまっていました。 ESP-NOWは最大250バイトまでのメッセージを一方的にデータを送りつけることができる非常に簡易的な通信方式です。会場内で大多数が送ってくるという考慮が不足していました。

原因がわかったため、送信元を識別できるデータを送り、受信側でチェックすることで解決できました。

void onDataRecv(const uint8_t *mac_addr, const uint8_t *data, int data_len) {
    if (data[0] != 89) {
      // 最初のデータが意図しないものは捨てる
      return;
    }
    // 省略 
}

この初日のトラブルを乗り越え、その後は大きなトラブルもなく、無事2日間の展示を終えることができました。2日間で500人以上の方におしゃべりぱくちゃんずを体験していただくことができ、多くの方から「面白い」「かわいい」などのお言葉をいただき、とても嬉しかったです。

まとめ

以上がおしゃべりぱくちゃんず作成までのお話でした。企画から当日まで初めての事だらけで本当に大変でしたが、無事完成して多くの方に体験していただくことができ、大変ながらも充実した数ヶ月でした。 今回はできませんでしたが、通信をWi-Fiに変更して入力された音声に関する出力をしたり、カム機構の動作音を更に小さくしておしゃべりの音声を聞こえやすくするなどこうすればもっと良かったのではないかという内容がいくつか出てきました。

今回身につけた3Dプリントなどの技術を用いて、また色んなものを作成していけたらと思います。

最後までご覧いただきありがとうございました。

アバター画像

深沢雛子

目次