コンテンツにスキップ
JA

変更履歴 — Hapbeat Arduino

Hapbeat Arduino ライブラリ(hapbeat-arduino)の主要な変更点をまとめます。

0.2.0 — 複数デバイス追跡とコマンドのユニキャスト化

Section titled “0.2.0 — 複数デバイス追跡とコマンドのユニキャスト化”
  • PLAY / STOP / STOP_ALL も、PONG を返した既知デバイスへユニキャスト送信する ようになりました(従来はブロードキャスト。ストリームだけが unicast でした)。 Wi-Fi AP は、同じ AP に省電力状態の端末が 1 台でもいるとブロードキャストフレームを 次の DTIM ビーコン(100〜300 ms 周期)まで保留するため、その 1 発が最大 300 ms 遅れていました。ユニキャストはこの保留を受けません。
    • 既知デバイスが 0 台のときはブロードキャストにフォールバックします。
    • ユニキャストとブロードキャストの二重送信はしません。
    • PING はデバイス検出のためブロードキャストのままです。
  • デバイステーブルを複数台対応にしました(従来は _deviceIp 1 台のみ)。最大 8 台を 固定長配列で保持します(MCU なので動的確保はしません)。これが無いままコマンドを ユニキャスト化すると、複数台構成で 1 台にしか届かなくなるため、セットでの変更です。 ストリーム送出も同じテーブルを使うようになり、従来「最初に見つかった 1 台」にしか 流れていなかったのが解消されました。
  • discover() はタイムアウトまで待つようになりました(従来は最初の 1 台で即 return)。 応答した全デバイスを宛先に登録するためです。戻り値の意味は変わりません (true = 1 台以上見つかった)。
  • 最後の PONG から一定時間(既定 15 秒)応答が無いデバイスは宛先から外れます。 外さないと電源 OFF の相手へ送り続け、かつ「生存 0 台でもブロードキャストに戻れない」 状態になるためです。loop() から数秒おきに ping() を呼ぶとユニキャストが維持されます。
  • poll() — 届いている PONG を非ブロッキングで取り込む(送信時にも自動で行うため任意)。
  • deviceCount() — 生存している既知デバイス数。
  • setDeviceTimeout(ms) — 生存判定の窓(既定 15000)。
  • setBroadcastOnly(bool) — 常にブロードキャストで送る。多数台を厳密に同時発火させたい 用途では、1 回の送信で全台に届くブロードキャストの方が有利です(ユニキャストは台数分を 順に送るため、20 台で 3〜8 ms 程度の時間差が出ます)。
  • setDeviceIp(ip) は「固定の宛先」を意味するようになりました(生存判定で消えません)。 0.0.0.0 を渡すと固定を解除します。

修正(実機検証で判明したもの)

Section titled “修正(実機検証で判明したもの)”
  • PONG の受信が最初の 1 通で永久に止まる問題を修正しました。ESP32 の WiFiUDP::parsePacket() は前のデータグラムを読み切っていないと次を一切読まない ため、ヘッダだけ読んで残りを捨てていた pollPongs() で受信が固まっていました。 0.1.0 では discover() が最初の応答で即 return し以後ポーリングしなかったため 表面化していませんでしたが、継続ポーリング前提の本版では「ユニキャストが 15 秒で 無言のうちにブロードキャストへ降格する」退行になります。
  • 経路を切り替えた直後にストリームが止まる問題を修正しました。endSine()STREAM_END は切替前の経路、直後の beginSine()STREAM_BEGIN は切替後の 経路で出ます。ブロードキャストの END は AP の DTIM バッファに最大 300 ms 保留 される一方ユニキャストの BEGIN は即着するため、デバイスには BEGIN → 遅れて END の順で届き、開始直後のストリームが停止していました。beginSine() は再アームを 許可したので、再開のために endSine()beginSine() を続けて呼ばないでください (経路を変えるだけならどちらも不要です)。
  • beginSine() が狙いのバッファまで満たしてから戻るようになりました。従来は 約 64 ms 分だけ送って戻っていたため、呼び出し側が直後に画面描画などで数十 ms ブロックするとデバイスのリングが尽き、開始直後に一瞬途切れていました。
  • end() が再生中でも STREAM_END を送らずに終了していたのを修正しました (デバイスが止められなくなる)。
  • stopAll() がローカルのストリームを止めていなかったのを修正しました。 Python / JS SDK の stop_all() / stopAll() と挙動を揃えています。
  • 到達しない宛先への送信が連続 5 回失敗したらテーブルから外すようにしました (PONG を受ければ入り直ります)。
  • ユニキャスト時は送出パケット数が宛先数だけ増えるため、1 回あたりのチャンク数を 宛先数で割るようにしました(2 台で lwIP の TX プールが枯渇していました)。
  • ping() がブロードキャストに加えて既知デバイスへユニキャストでも送るように なりました。生存確認が二重化され、その host のアドレス解決も維持されます。
  • deviceIps(IPAddress* out, uint8_t maxOut) — 現在のユニキャスト宛先一覧。
  • "group_<N>" 単独のターゲットは機能しません。 照合は位置ベースで、group_2 は player スロットと比較されるため一致しません。group だけを指定したい場合は "*/*/group_2" のように前のスロットを * で埋めてください。
  • 部分ワイルドカードは使えません。 * はセグメント全体が * のときだけ有効です (player_1/pos_* は不一致、player_1/* は一致)。
  • デバイスのアドレスは常に player_<N>/<position>/group_<M> の正規形で、既定は player_1 / group_1 です。

Hapbeat 触覚デバイスを Wi-Fi UDP で駆動する level-1 の Arduino / ESP32 / M5Stack ライブラリ。依存ゼロです(サンプルのみ M5Unified を使用)。

  • play(eventId, gain, target) / stop / stopAll / ping / setGroup
  • kit イベントを id(<kit-name>.<file-name>)で発火します。波形は Hapbeat Studio で デバイスに導入した kit 側にあり、MCU は約 30 バイトのトリガーを送るだけで何も保存しません。

合成サイン波(kit 不要・WAV 不要)

Section titled “合成サイン波(kit 不要・WAV 不要)”
  • playSine(freqHz, intensity, durationMs) — ワンショット。
  • beginSine / pumpSine / endSine / sineActive — 連続・オープンエンドな ストリーム。押している間の再生や、周波数 / 強度のライブ制御に使えます。
  • 16 kHz モノラル PCM16 を MCU 上で生成。約 160 ms の送信先行バッファが Wi-Fi の ジッターをデバイス側の約 256 ms リングに対して吸収します。
  • 信頼性: STREAM_BEGIN / STREAM_END は再送します(BEGIN を取りこぼすとストリーム 全体が無音になるため)。prime / catch-up バーストは pumpSine() 呼び出しごとに上限を設けます。
  • discover() / deviceIp() / setDeviceIp() — ブロードキャスト PING → PONG で デバイス IP を学習し、ストリーミングを unicast できます(Wi-Fi MAC の ACK + リトライで ブロードキャストよりはるかに滑らか)。デバイスが見つからない場合はブロードキャストに フォールバックします。
  • target アドレッシング: ""(ブロードキャスト)、"player_1/chest""*/chest""group_<N>"
  • ワイヤーフォーマットは hapbeat-contracts および Python / Unity / JS SDK と バイト互換です(docs/wire-format.md 参照)。
  • ESP-NOW transport(ルーター不要)、保存 WAV / clip ストリーミング、EventMap による 調整レイヤー、より高度な合成。