技術ドキュメント - 機能説明 - Cubeクラス
目次
1. 概説
 
Cube モジュール群は、Unity システム上で動くキューブ(以下シミュレータ) と 現実のキューブ(以下リアル) を同一のコードで動かす事が出来るマルチプラットフォーム対応モジュールです。
ディレクトリ構成は下図のようになります。
 
Cube  +-------------------------------+ キューブルートディレクトリ
├── CoreCube  +-----------------------+ コアキューブディレクトリ
│   ├── Real  +-----------------------+ リアル実装ディレクトリ
│   │   ├── Versions  +---------------+ リアル実装のバージョンディレクトリ
│   │   │   ├── CubeReal_ver2_0_0.cs  + 2.0.0リアル実装クラス
│   │   │   ├── CubeReal_ver2_1_0.cs  + 2.1.0リアル実装クラス
│   │   │   ├── CubeReal_ver2_2_0.cs  + 2.2.0リアル実装クラス
│   │   │   ├── CubeReal_ver2_3_0.cs  + 2.3.0リアル実装クラス
│   │   │   └── CubeReal_ver2_4_0.cs  + 2.4.0リアル実装クラス
│   │   └── CubeReal.cs  +------------+ リアル実装抽象クラス
│   ├── Sim    +----------------------+ シミュレータ実装ディレクトリ
│   │   └── CubeUnity.cs  +-----------+ シミュレータ実装クラス
│   ├── Cube.cs  +--------------------+ キューブ抽象クラス
│   └── CubeOrderBalancer.cs  +-------+ 命令送信制御クラス
├── CubeConnecter.cs  +---------------+ 接続クラス
├── CubeHandle.cs  +------------------+ 機能拡張クラス
├── CubeManager.cs  +-----------------+ コード簡略化クラス
└── CubeScanner.cs  +-----------------+ 検索クラス
2. Cube クラスの構造
 
Cube クラス
Cube を操作するためのインタフェースです。
全ての関数・プロパティの中身が未実装となっており、派生クラスに全ての処理を委ねています。
こうする事で実行環境やバージョン毎の内部実装の違いを吸収し、ポリモーフィズムによる抽象的なプログラミングを可能にします。
再利用性を保つために、このクラスにはtoio™コア キューブ 技術仕様(通信仕様)以外の機能が存在しません。
toio™コア キューブ 技術仕様(通信仕様)以外の機能を利用/拡張する場合は、CubeHandle, CubeNavigator等の拡張クラスを利用します。
実装コード:Cube.cs
CubeUnity
Unity エディタ実行時に動作するシミュレータ用 Cube クラスです。
BLE プロトコルバージョンの解決処理が無いため、1 つのバージョンのみが動作対象になります。
実装コード:CubeUnity.cs
CubeReal
現実のキューブとの BLE 通信を行う Cube クラスです。
最低限の共通処理を除いて、殆どの内部実装を派生クラスで行います。
実装コード:CubeReal.cs
ver2_0_0:
- 実装コード:CubeReal_ver2_0_0.cs
- 通信仕様:https://toio.github.io/toio-spec/docs/2.0.0/about
ver2_1_0:
- 実装コード:CubeReal_ver2_1_0.cs
- 通信仕様:https://toio.github.io/toio-spec/docs/2.1.0/about
ver2_2_0:
- 実装コード:CubeReal_ver2_2_0.cs
- 通信仕様:https://toio.github.io/toio-spec/docs/2.2.0/about
ver2_3_0:
- 実装コード:CubeReal_ver2_3_0.cs
- 通信仕様:https://toio.github.io/toio-spec/docs/2.3.0/about
ver2_4_0:
- 実装コード:CubeReal_ver2_4_0.cs
- 通信仕様:https://toio.github.io/toio-spec/docs/about
3. 接続の仕組み
例として、シンプルな Cube 移動コードを示します。
このコードを実行すると、Cube へ接続後に移動関数が呼ばれます。Cube はクルクルと回転します。
using UnityEngine;
using toio;
public class SimpleScene : MonoBehaviour
{
    float intervalTime = 0.05f;
    float elapsedTime = 0;
    Cube cube;
    // 非同期初期化
    // C#標準機能であるasync/awaitキーワードを使用する事で、検索・接続それぞれで終了待ちする
    // async: 非同期キーワード
    // await: 待機キーワード
    async void Start()
    {
      	// Bluetoothデバイスを検索
        var peripheral = await new CubeScanner().NearestScan();
       	// デバイスへ接続してCube変数を生成
        cube = await new CubeConnecter().Connect(peripheral);
    }
    void Update()
    {
        // Cube変数の生成が完了するまで早期リターン
        if (null == cube) { return; }
        // 経過時間を計測
        elapsedTime += Time.deltaTime;
      	// 前回の命令から50ミリ秒以上経過した場合
        if (intervalTime < elapsedTime)
        {
            elapsedTime = 0.0f;
            // 左モーター速度:50, 右モーター速度:-50, 制御時間:200ミリ秒
            cube.Move(50, -50, 200);
        }
    }
}
この章では、検索接続プログラム部分について解説します。
async void Start()
{
  // Bluetoothデバイスを検索 (3.1. 検索)
  var peripheral = await new CubeScanner().NearestScan();
  // デバイスへ接続してCube変数を生成 (3.2. 接続)
  cube = await new CubeConnecter().Connect(peripheral);
}
手早く概要を把握したい方のため、ひとまずこのプログラムの概要図を示します。
このプログラムはマルチプラットフォームで動作するため、
Scanner モジュール、Connecter モジュールのそれぞれに 2 つの内部実装(リアル実装/シミュレータ実装)が存在します。
検索接続プログラムの概要(リアル実装)
 
上の図のような仕組みで、検索・接続を行います。
- Scanner.Scan 関数を実行して、Peripheral 変数を取得
- Connecter.Connect 関数を実行して、Characteristic 配列変数を取得
- Connecter が持つバージョンテーブルを参照して、対応する Cube 変数を取得
検索接続プログラムの概要(シミュレータ実装)
 
上の図のような仕組みで、検索・接続のダミー処理を行います。
- Scanner.Scan 関数を実行して、UnityPeripheral 変数を取得
- Connecter.Connect 関数を実行して、UnityCube 変数を取得
3.1. 検索(Scanner)
 
toio SDK for Unity には Bluetooth デバイスの検索モジュールが 1 つあります。
CubeScanner クラス:
- NearestScan 関数:最も信号強度の高いデバイスを戻り値として同期的に返します。
- NearScan 関数:信号強度の高い順に指定された複数のデバイスを戻り値として同期的に返します。
- StartScan 関数:非同期的に継続的なスキャンを開始します。結果はコールバックで返します。
- StopScan 関数:StartScanで始まったスキャンを中断します。
CubeScanner
NearestScan 関数を呼ぶ事で、最も信号強度の高いデバイスを戻り値として同期的に返します。
async/await キーワードでスキャン終了待ちする事で、呼び出し側から見ると同期処理と同じになります。
NearScan 関数を呼ぶ事で、信号強度の高い順に指定された数(satisfiedNum)のデバイスを戻り値として同期的に返します。async/await キーワードでスキャン終了待ちする事で、呼び出し側から見ると同期処理と同じになります。
StartScan 関数は非同期関数で、await せずに呼ぶ事で、裏でスキャンを回すことができます。スキャンしたキューブのリストはコールバックの形で受け取って処理することになります。
StopScan 関数を呼ぶ事で、スキャンを中断させます。
内部実装はシミュレータ実装 と リアル実装の 2 つに分かれており、コンストラクタのパラメータによって接続方法を指定可能です。基本設定の場合はビルド対象に応じて内部実装が自動的に変わるため、プラットフォーム毎に別々のコードを書かなくても動作します。接続方法を明示的に指定したい場合は、Cubeの接続設定をご参照ください。
CubeManagerに拡張性を持たせる目的で、インタフェースを継承して実装されています。
シミュレータ実装:
- シミュレータのCubeプレハブから作成された GameObject を検索
リアル実装:
- Bluetooth デバイスを検索
インターフェイスのコード
public interface CubeScannerInterface
{
    bool isScanning { get; }
    UniTask<BLEPeripheralInterface> NearestScan(float waitSeconds = 0f);
    UniTask<BLEPeripheralInterface[]> NearScan(int satisfiedNum, float waitSeconds = 3.0f);
    UniTask StartScan(Action<BLEPeripheralInterface[]> onScanUpdate, Action onScanEnd = null, float waitSeconds = 10f);
    void StopScan();
}
3.2. 接続(Connecter)
 
CubeConnecter の役割は、BLE デバイスへの接続 と BLE プロトコルバージョンの適応(※リアル実装のみ)です。
内部実装はシミュレータ実装 と リアル実装で分かれており、ビルド対象に応じて内部実装が自動的に変わるため、プラットフォーム毎に別々のコードを書かなくても動作します。async/await キーワードで接続終了待ちする事で、呼び出し側から見ると同期処理と同じになります。
CubeManagerに拡張性を持たせる目的で、インタフェースを継承して実装されています。
Connect 関数を呼ぶ事でキューブに接続します。
Disconnect 関数を呼ぶ事で接続済みのキューブとの通信を切断します。
シミュレータ実装:
- UnityPeripheral(GameObject)から GameObject を取得
- GameObjet を引数に CubeUnity 変数を生成 (※シミュレータ実装版ではBLE プロトコルバージョン適応は実装されていません)
リアル実装:
- Peripheral(Bluetooth デバイス)へ接続して Characteristic(機能)配列を取得
- BLE プロトコルバージョンを取得
- 事前に追加しておいたバージョンテーブルを参照、ファームウェアに適応した Cube 変数(CubeReal_verX_X_X)を生成
概要コード
public interface CubeConnecterInterface
{
    Task<Cube> Connect(BLEPeripheralInterface peripheral);
    Task<Cube[]> Connect(BLEPeripheralInterface[] peripherals);
    Task ReConnect(Cube cube, BLEPeripheralInterface peripheral);
}
/// <summary>
/// CoreCubeのBLE プロトコルバージョンを参照し, バージョンに応じたCubeクラスを生成.
/// </summary>
public class CubeConnecter : CubeConnecterInterface
{
#if UNITY_EDITOR
    public async Task<Cube> Connect(BLEPeripheralInterface peripheral)
    {
        /* return CubeUnity */
    }
    public async Task<Cube[]> Connect(BLEPeripheralInterface[] peripherals)
    {
        /* return CubeUnity[] */
    }
    public Task ReConnect(Cube cube, BLEPeripheralInterface peripheral)
    {
        return null;
    }
#else
    private Dictionary<string, Cube> versionTable = new Dictionary<string, Cube>();
    public CubeConnecter()
    {/*
        versionTable.add("2.0.0", CubeReal_ver2_0_0)
        versionTable.add("2.1.0", CubeReal_ver2_1_0)
        versionTable.add("2.2.0", CubeReal_ver2_2_0)
    */}
    public async Task<Cube> Connect(BLEPeripheralInterface peripheral)
    {/*
        characteristics ← connect(peripheral)
        version ← get_version(characteristics)
        cube ← versionTable[version]
        return cube
    */}
    public async Task<Cube[]> Connect(BLEPeripheralInterface[] peripherals)
    {/*
    	cubes = []
        for i = 1 to peripherals.len do
            characteristics ← connect(peripherals[i])
            version ← get_version(characteristics)
            cube ← versionTable[version]
            cubes.add(cube)
  	    return cubes
    */}
    public async Task ReConnect(Cube cube, BLEPeripheralInterface peripheral)
    {
        /* connect(peripheral) */
    }
#endif
}
実装コード:
サンプルコード:
4. 命令送信
 
全ての Cube クラスへの関数呼び出しは、継承により内部実装が異なります。
シミュレータ用 Cube クラスである CubeUntiy は、CubeSimulator に対して命令を送ります。
BLE 通信用 Cube クラスである CubeReal 派生クラスは、BLE に対して byte 配列を送信するように命令を送ります。
CubeUnity / CubeReal 派生クラスで内部実装は異なっていますが、
共通して命令送信をCubeOrderBalancerクラスに委ねています。
大まかには以下の手順で命令送信します。
- CubeUnity / CubeReal 派生クラスからの命令を CubeOrderBalancer クラスの命令キューへ追加します。
- CubeOrderBalancer の Update 関数実行時に、命令キューから 1 つだけ命令を取り出して送信します。
命令送信を CubeOrderBalancer クラスに委ねる背景は以下の 2 つです。
- 
    短い間隔(約 45ms 以内)で複数の命令を送ると、2 つ目以降の命令が無視される。 
- 
    命令には【必ず実行してほしい命令】と【時々無視しても問題ない命令】の 2 種類がある。 
CubeOrderBalancer
そのため、このクラスは以下の 2 つの機能を提供します。
- 
    命令間隔の制御(45ms) このクラスは命令キューを持っており、AddOrder 関数で命令を追加出来ます。 
 Update 関数内において、それぞれの Cube の前回の命令から経過時間が 45ms 以上の場合に、命令キューの中から 1 つだけ命令を取り出して送信します。
- 
    命令の優先度設定(強 / 弱) 強い命令:キューに弱い命令と混在していた場合、弱い命令を破棄して優先的に送信されます。複数の強い命令がキューにある場合、先入れ先出し法(FIFO)で強い命令がキューから取り出されて送信されます。送信されなかった強い命令はキューに残り続け、命令間隔の制御により 45ms 以上の間隔を空けた後に送信されます。したがって、単発で重要な命令(音を鳴らす、LED を光らす等)を強い命令にする事で、命令無視される確率を下げる事が出来ます。 弱い命令:キューに強い命令と混在していた場合、弱い命令は送信されずに破棄されます。複数の弱い命令がキューにある場合、キューの最後の命令だけ送信して、その他の命令は全て破棄します。高頻度で送信する(ナビゲーションなどの)移動命令の場合、命令を多少無視しても問題無い場合があります。こういった多少無視しても問題ない命令を弱い命令にする事で、対照的に優先度の高い強い命令を安定して送信出来ます。 
実装コード:CubeOrderBalancer.cs
5. 機能拡張の方法
toio SDK for Unity の機能拡張は、次のような方法が考えられます。
5.1. Cube クラスに関数を追加するには
Cube クラスの構造で説明しましたが、Cube クラスには継承関係があります。そのためベースとなる Cube クラスに仮想関数を追加してから派生クラスで関数オーバーライドしていく事で、関数を追加出来ます。
5.2. BLE プロトコルバージョンを追加するには
- 新たに追加されたBLE プロトコルバージョンに対応する CubeReal 派生クラスを作成します。
- CubeConnecter クラスの versionTable メンバ変数に生成関数を登録します。
5.3. 通信プログラムを変更する場合
BLE インタフェースの内部実装を変更する事で、既存の toio™ プログラムに変更を加える事なく通信プログラムだけを変更する事が出来ます。