HoloLensをオフラインでコントロールする
状況は変わりつつありますが、諸事情でHoloLensアプリをUSB接続のみでコントロールしたいというニーズがあります。
ただHoloLensのUSBはDevicePortalへのアクセスと充電程度にしか使うことができず、通信用途には使えません。
しかし、DevicePortalのFileExprolerにはファイル転送機能があるので、この機能を工夫すればUSBでアクションを送れるのではないかと考えました。
つまりファイルをUSB経由でアップロードし、HoloLens側はファイルのアップロードを検知して何らかのアクションを実行します。
ファイル内にコマンドを書いておいて、それを解釈するようにすればなんでもできるというわけです。
ファイルの変更検知
HoloLensというかUWPアプリではファイルの変更検知を行うことができます。
下記コードで検知をおこなえます。
Controller.cs : メインスクリプト
using UnityEngine; #if NETFX_CORE using System.Threading; using System.Threading.Tasks; #endif public class Controller : MonoBehaviour { public bool isChanged = true; public GameObject cube; #if NETFX_CORE private CommandReceiver CommandReveicer { get; set; } #endif void Start() { #if NETFX_CORE this.CommandReveicer = new CommandReceiver(); this.CommandReveicer.OnCommandReceived += (s, o) => { Debug.Log(string.Format("RECEIVE: {0}", o.Command)); this.isChanged = !this.isChanged; }; this.CommandReveicer.Start(); #endif } void Update() { this.cube.SetActive(this.isChanged); } }
HoloLens実機で動作するときのみCommandReceiverクラスのインスタンスを生成し、実行します。
ファイル検知があった場合、Cubeの表示をオンオフして、それを知らせるようにしています。
CommandReceiverは下記のとおりです。
#if NETFX_CORE using System; using System.Collections.Generic; using System.IO; using System.Diagnostics; using System.Threading.Tasks; using Windows.Storage; using Windows.Storage.Search; public class CommandReceiver { public delegate void CommandReceivedEventHandler(object sender, CommandReceivedEventArgs e); public event CommandReceivedEventHandler OnCommandReceived; private StorageFileQueryResult FileQuery { get; set; } public void Start() { List<string> fileTypeFilter = new List<string>() { ".txt", }; var options = new QueryOptions(CommonFileQuery.OrderByName, fileTypeFilter); this.FileQuery = ApplicationData.Current.LocalFolder.CreateFileQueryWithOptions(options); this.FileQuery.ContentsChanged += this.Query_ContentsChanged; Task.Run(() => { var files = this.FileQuery.GetFilesAsync(); }); } public void Stop() { this.FileQuery.ContentsChanged -= this.Query_ContentsChanged; } private async void Query_ContentsChanged(IStorageQueryResultBase sender, object args) { try { var file = await sender.Folder.GetFileAsync("command.txt"); var json = await FileIO.ReadTextAsync(file); this.OnCommandReceived?.Invoke(this, new CommandReceivedEventArgs(json)); } catch (FileNotFoundException e) { Debug.WriteLine(e.ToString()); } } } #endif
command.txtというファイルの変更を検出するとイベントを発生させます。
CommandReceivedEventArgsクラスは下記のようなシンプルなものです。
public class CommandReceivedEventArgs { public string Command { get; private set; } public CommandReceivedEventArgs(string command) { this.Command = command; } }
このアプリをUSB接続したHoloLensで起動し、DevicePortalのFileExplorerからcommand.txtをアップロードするとCubeの表示がオンオフされ、USB経由でアクションが伝わっていることが確認できます。
HoloLensのREST API
HoloLensのDevicePortal機能はREST APIで公開されています。
https://developer.microsoft.com/ja-jp/windows/holographic/device_portal_api_reference
しかしこの中にはFileExplorerの機能は明記されていません。
ではちょっとChromeであればF12キーでNetworkの様子を調べてみましょう。
これはファイルリストを取得したもの
こちらはファイルをアップロードしたもの
それぞれ下記のようなAPIとなっていました。
フォルダ名、アプリパッケージ名、フォルダからのパスを指定します。
ファイルアップロードはMultipart形式でファイルを添付してPOSTリクエストを行えばOKです。
なおDevicePortalはBASIC認証がかかっているので、リクエストする際にはBASIC認証のヘッダをつける必要があります。
using RestSharp; using RestSharp.Authenticators; using System; using UnityEngine; using UnityEngine.UI; public class Controller : MonoBehaviour { public InputField idText; public InputField passwordText; public InputField baseUrlText; public InputField knownFolderIdText; public InputField packageFullNameText; public InputField pathText; public Text logText; void Start () { } void Update () { } public void GetFiles() { var url = this.baseUrlText.text; Debug.Log("GET URL=" + url); var client = new RestClient(); client.BaseUrl = new Uri(url); client.Authenticator = new HttpBasicAuthenticator(this.idText.text, this.passwordText.text); var request = new RestRequest("api/filesystem/apps/files", Method.GET); request.AddParameter("knownfolderid", this.knownFolderIdText.text); request.AddParameter("packagefullname", this.packageFullNameText.text); request.AddParameter("path", this.pathText.text); var response = client.Execute(request); var log = string.Empty; if (response.ErrorException != null) { log = "ERROR: " + response.StatusCode + " " + response.ErrorException.ToString(); } else { log = "RESPONSE: " + response.StatusCode + " " + response.Content; } this.logText.text = log; Debug.Log(log); } public void PostFile() { var url = this.baseUrlText.text; Debug.Log("POST URL=" + url); var client = new RestClient(); client.BaseUrl = new Uri(url); client.Authenticator = new HttpBasicAuthenticator(this.idText.text, this.passwordText.text); var resource = "api/filesystem/apps/file" + string.Format("?knownfolderid={0}&packagefullname={1}&path={2}", this.knownFolderIdText.text, this.packageFullNameText.text, WWW.EscapeURL(this.pathText.text)); var request = new RestRequest(resource, Method.POST) { AlwaysMultipartFormData = true }; request.AddHeader("Content-Type", "multipart/form-data"); var filePath = Application.streamingAssetsPath + "/command.txt"; Debug.Log("FILE PATH=" + filePath); request.AddFile("file", filePath); var response = client.Execute(request); var log = string.Empty; if (response.ErrorException != null) { log = "ERROR: " + response.StatusCode + " " + response.ErrorException.ToString(); } else { log = "RESPONSE: " + response.StatusCode + " " + response.Content; } this.logText.text = log; Debug.Log(log); } }
画面にはIDやパスワードなどの入力項目を設け、入力値を上記クラスから参照するかたちです。
上記を実装すれば、PCのプログラムからUSB接続のHoloLensアプリを自在にコントロールできるようになります。
わかりにくい動画ですが、PCでボタンを押すとホログラムのCUBEの表示がオンオフします。