指の曲げ伸ばしを、そのまま入力に使えるコントローラです。
M5StickC Plus を人差し指と中指で挟み、指を「くいっ」と曲げると、その角度が -1(伸ばしきり)から +1(曲げきり)の連続値として届きます。 ボタンのようなオン・オフではなく、途中の位置も自由に取れるので、 スライダーやアナログスティックのように使えます。
┌────────┐
親指 ─┤ M5StickC+ ├─ 中指 指を伸ばす … -1.0
└────────┘ まっすぐ … 0.0
│ 指を曲げる … +1.0
くいっ、くいっ
手のひらの中に収まり、机も平らな場所も要りません。 指一本の小さな動きで、狙った値をぴたりと出せます。
実際に動かしている様子は、こちらの投稿でご覧いただけます。
- リズムゲーム / 音楽アプリ … 拍に合わせて指を動かす。振動で拍を返せます
- 楽器・エフェクター … 曲げ具合をピッチやフィルターに割り当てる
- プレゼンや映像のスクラブ … 指先で再生位置を行き来する
- アクセシビリティ用の入力 … 指一本、小さな可動域で連続値を出せます
- ロボットやサーボの手元操作 … 曲げた角度をそのまま関節角度へ
受け手は Web ブラウザ(JavaScript) でも Python でも書けます。
どちらも動くサンプルが receiver/ に入っています。
セットアップは 2 回の姿勢登録だけ。 「伸ばした姿勢」と「曲げた姿勢」でボタンを押すと、そこから どの軸をどちら向きに使うかを自動で判定します。軸の選択も感度調整も 反転設定もありません。治具の向きを変えたら、登録し直すだけで済みます。
速い動きでも値が暴れません。
加速度センサーだけで傾きを測ると、素早く振ったときに動きの加速度が
混ざって値が乱れます。ジャイロと組み合わせた相補フィルタで姿勢を推定し、
速い操作でも追従します(実装は receiver/web/protocol.js の GravityFilter)。
無線でも有線でも同じように扱えます。 BLE(Web Bluetooth)と USB シリアル(Web Serial)の 2 経路があり、 流れるデータは完全に同じ 50Hz のストリームです。普段は BLE、 開発中はペアリング不要で遅延の小さい USB、と使い分けられます。
手に振動を返せます。 別売の Vibration HAT を挿すと、受け手から振動を送れます。強さと長さを 指定する単発の振動のほか、テンポを渡してデバイス側で自走させる モードがあり、無線の遅延ゆらぎに影響されない正確な拍を刻めます。
| M5StickC Plus | 必須。無印 M5StickC は画面の仕様が異なるため動きません。Plus2 は未検証です |
| Vibration HAT | 任意。振動フィードバックを使う場合のみ(M5Stack 公式、HAT ヘッダに挿すだけ) |
| 指で挟む治具 | hardware/v3.stl を 3D プリントしてください |
治具を自作する場合、次の 3 点を満たす必要があります。
- 指を曲げると、本体が重力に対して傾くこと。 指を曲げる回転軸が地面と垂直に近い持ち方だと、本体の傾きが変わらないため 原理的に検出できません(登録時に自動で検出して知らせます)
- 伸ばした姿勢と曲げた姿勢で 15 度以上傾くこと。 同梱の治具では約 106 度の差があり、十分な余裕があります
- 振動を使うなら、本体が指に密着すること
PlatformIO が必要です。
このリポジトリを clone して、ディレクトリ内で次を実行します。
pio run -t uploadブラウザで試す(Chrome または Edge)
cd receiver/web
serve.cmd # Windows。Python / Node / PowerShell のどれかがあれば動きますhttp://localhost:8000 を開き、「BLE 接続」→ 一覧から M5Finger を選択 →
「登録を始める」で 2 姿勢を登録すると、バーが指の動きに追従します。
生のセンサー値、受信レート、振動テストもこの画面から確認できます。
Web Bluetooth と Web Serial はセキュアな文脈でのみ動作するため、 HTML ファイルを直接開く(
file://)と動きません。localhostか HTTPS で配信してください。Safari と Firefox は未対応です。
Python で試す(USB 接続)
pip install pyserial
python receiver/python/read_serial.py COM3画面の指示に従って 2 姿勢を登録すると、ターミナルにバーが表示されます。
JavaScript なら、必要なのは実質これだけです。
import { LinkHub, LaneMapper } from './protocol.js';
import { BleLink } from './ble.js';
import { SerialLink } from './serial.js';
const link = new LinkHub([new BleLink(), new SerialLink()]);
const mapper = new LaneMapper();
link.addEventListener('sample', (e) => mapper.update(e.detail));
// ボタンなどのユーザー操作から呼ぶ(ブラウザの制約で、接続は要ユーザー操作)
await link.get('ble').connect();
mapper.captureAt('extended'); // 指を伸ばした姿勢で
mapper.captureAt('bent'); // 指を曲げた姿勢で
// 以降 mapper.out が指の位置(-1.0 〜 +1.0)。50Hz で更新されますデバイスへ送る側の例:
link.sendCmd(CMD.HAPTIC, 220, 20); // 強さ 220 で 80ms 振動
link.startBeat(120, 4, BEAT_FLAG.OFFBEAT); // 120BPM の裏拍でデバイスが自走
link.syncBeat(0); // 「今が拍の頭」と伝えるPython での実装例は receiver/python/read_serial.py にあります。
フレームの解釈からキャリブレーションまで約 200 行なので、
他の言語へ移植する際の下敷きにも使えます。
デバイスは センサーの生の値を 50Hz で流すことに専念します。 「傾きを指の位置に変換する」処理はすべて受け手側にあります。
この分担のおかげで、操作感の調整(平滑化の強さ、キャリブレーションのやり方、 軸の扱い)を変えるのにファームウェアを書き込み直す必要がありません。 受け手のコードを書き換えるだけで済みます。
送られてくるデータ(16 バイト / 50Hz):
| 内容 | 型 |
|---|---|
| シーケンス番号(取りこぼし検出用) | uint8 |
| ボタン A / B の状態 | uint8(ビットフラグ) |
| 加速度 X / Y / Z | int16 ×3 [mG] |
| 角速度 X / Y / Z | int16 ×3 [0.1dps] |
| 電池残量 | uint8 [%] |
バイト単位の詳細、コマンド一覧、シリアルのフレーム形式、
ビートクロックの同期方法は docs/protocol.md にまとめてあります。
| 操作 | 動作 |
|---|---|
| A ボタン | キャリブレーションを 1 ステップ進める(受け手アプリと連動) |
| B ボタン 短押し | 画面の上下を反転(治具の向きに合わせる。設定は保存されます) |
| B ボタン 長押し(2 秒) | 電源オフ |
pio device monitor で接続すると 1 秒ごとに状態が出力されます。
加速度の大きさが 1.00G 付近なら、センサーは正常に動作しています。
src/main.cpp ファームウェア
platformio.ini ビルド設定
receiver/web/ ブラウザ用サンプル(BLE / USB 両対応)
receiver/python/ Python 用サンプル(USB)
docs/protocol.md 通信仕様
hardware/v3.stl 治具の 3D データ
MIT License です。ファームウェア、サンプルコード、治具の 3D データを すべて含みます。商用利用も改変も自由です。