変更履歴 — Hapbeat Arduino
Hapbeat Arduino ライブラリ(hapbeat-arduino)の主要な変更点をまとめます。
0.2.0 — 複数デバイス追跡とコマンドのユニキャスト化
Section titled “0.2.0 — 複数デバイス追跡とコマンドのユニキャスト化”変更(既定の挙動)
Section titled “変更(既定の挙動)”- PLAY / STOP / STOP_ALL も、PONG を返した既知デバイスへユニキャスト送信する
ようになりました(従来はブロードキャスト。ストリームだけが unicast でした)。
Wi-Fi AP は、同じ AP に省電力状態の端末が 1 台でもいるとブロードキャストフレームを
次の DTIM ビーコン(100〜300 ms 周期)まで保留するため、その 1 発が最大 300 ms
遅れていました。ユニキャストはこの保留を受けません。
- 既知デバイスが 0 台のときはブロードキャストにフォールバックします。
- ユニキャストとブロードキャストの二重送信はしません。
- PING はデバイス検出のためブロードキャストのままです。
- デバイステーブルを複数台対応にしました(従来は
_deviceIp1 台のみ)。最大 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 のアドレス解決も維持されます。
追加(続き)
Section titled “追加(続き)”deviceIps(IPAddress* out, uint8_t maxOut)— 現在のユニキャスト宛先一覧。
注意(0.1.0 の記述の訂正)
Section titled “注意(0.1.0 の記述の訂正)”"group_<N>"単独のターゲットは機能しません。 照合は位置ベースで、group_2は player スロットと比較されるため一致しません。group だけを指定したい場合は"*/*/group_2"のように前のスロットを*で埋めてください。- 部分ワイルドカードは使えません。
*はセグメント全体が*のときだけ有効です (player_1/pos_*は不一致、player_1/*は一致)。 - デバイスのアドレスは常に
player_<N>/<position>/group_<M>の正規形で、既定はplayer_1/group_1です。
0.1.0 — 初の公開リリース
Section titled “0.1.0 — 初の公開リリース”Hapbeat 触覚デバイスを Wi-Fi UDP で駆動する level-1 の Arduino / ESP32 / M5Stack ライブラリ。依存ゼロです(サンプルのみ M5Unified を使用)。
command(fire)モード
Section titled “command(fire)モード”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()呼び出しごとに上限を設けます。
検出とアドレッシング
Section titled “検出とアドレッシング”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参照)。
未対応(今後の予定)
Section titled “未対応(今後の予定)”- ESP-NOW transport(ルーター不要)、保存 WAV / clip ストリーミング、EventMap による 調整レイヤー、より高度な合成。