Meta Questを使ってMR - 現実と仮想を融合する デバイス連動編 -

3.3K Views

August 31, 24

スライド概要

Meta Questを使い現実世界と仮想世界を融合させる。
現実世界のデバイスの操作や情報の表示を、MRデバイスを使い仮想のインターフェース上で実現する方法を解説します。
・現実世界の扇風機をMRのインターフェースから操作(コンセントのオンオフ)
・現実世界の温度/湿度センサーの情報をMRで表示

profile-image

XR(VR/AR)のエバンジェリスト。XR技術で世界を変えていきましょう。 https://majimajiwaroze.connpass.com/

シェア

またはPlayer版

埋め込む »CMSなどでJSが使えない場合

関連スライド

各ページのテキスト
1.

MRで現実と仮想の境界をなくす - デバイス連動編 - 2024.8.31

2.

大久保 聡 Mail [email protected] Twitter @followapp

3.

それほど遠くない未来

4.

インターフェース がMRデバイスに統合される 1 Human Interface 2 Sensor Device Life Log 新しいデータが蓄積され 3 Spatial Map +Data データが現実の位置情報と 連動できる

5.

必要な情報が 必要な時に 必要な場所で利用でき 現実のものに 仮想のIFを通じて働きかけることができる

6.

仮想というIFを通じて現実を操作 現実 IoT機器 家電 スマホ 車 etc. デジタル の 情報 仮想 という IF 現実の操作 情報の利用

7.

仮想を通じて現実を操作する。

8.

META QUESTを用い、現実の操作・可視化を行う  Meta Questから、直接現実にアクセスさせることも可能。今回はPythonのライブラリが充実してい ることもあり、現実とのIF部分はPythonを用い、Meta QuestからはWeb APIを経由してアクセスさ えることとする。

9.

共通の環境構築  Python仮想環境の作成し、仮想環境に切替ます。 python3 -m venv .venv source .venv/bin/activate  Webサーバーをインストールします。 pip install --upgrade fastapi  Pythonのプログラム開発用に、Visual Studio Codeをインストールします。 sudo apt update sudo apt install code

10.

VS CODEの設定  Pythonの実行環境のエクステンションをインストールします。

11.

仮想を介して現実のデバイスにアクセスする 現実 IoT機器 家電 スマホ 車 etc. デジタル の 情報 仮想 という IF 現実の操作 情報の利用

12.

スマートコンセント https://www.tp-link.com/jp/smart-home/tapo/tapo-p105/  ネットワークからオン・オフできるスマートコンセント「tp-lin Tapo P105」を使って家電を操作す る。  1個1000-1500円くらい。  スマホのアプリ、音声アシスタント、IFTTTからコンセントのオン・オフができる。  Wifi(ローカルエリアネットワーク)で直接操作もできる。。 Tapo P105

13.

システム構成  コンセントオンオフで制御できる家電ということで、扇風機をP105 経由で操作します。複雑な操作 が必要な場合は、IFTTTや赤外線リモコンを経由して操作すれば良いと思います。  Webサーバー 兼 家電とのIFはRaspberryPiを利用します。 Web Server (fastapi) Client API (tapo) Python 3 Tapo P105 Quest3 RaspberryPI 5

14.

準備、TAPOのIPアドレスの確認  TAPOのスマホアプリを起動し、デバイス一覧からTapo P105を選択します。  詳細ボタンを押し、割り当てられているIPアドレスを確認します。

15.

環境構築  Tp-Link のスマートプラグ操作用のライブラリインストールします。 pip install --upgrade tapo

16.
[beta]
TAPOへのアクセスとWEB API
 onplug/{name}とoffplug/{name}というIFを作成し実装します。
 Tapoの利用時に作成したユーザーのIDとPWが接続時に必要となります。
 調べたIPアドレスとnameを対応させ、それぞれにアクセスできるようなIFにします。
Import uvicorn
From fastapi import FastAPI, HTTPException, Request
From starlette.exceptions import HTTPException as StarletteHTTPException
Import asyncio
Import os
From tapo import ApiClient
From fastapi.responses import JSONResponse
Import struct

@app.get("/offplug/{name}")
async def off(
name: str
):
ip = plugs[name]
device = await client.p105(ip)
await device.off()
return {"Result": "OK"}

app = FastAPI()
Client = ApiClient(“id", “password")
Plugs = {'Plug1':'192.168.11.10', 'Plug2':'192.168.11.11’}

if __name__ == "__main__":
uvicorn.run(app, host="192.168.11.9", port=8000)

@app.get("/onplug/{name}")
async def on(
name: str
):
ip = plugs[name]
device = await client.p105(ip)
await device.on()
return {"Result": "OK"}

17.
[beta]
QUESTからWEB APIを叩く
 トグルボタンのオン/オフでコンセントと、仮想空間内の風のオン/オフを切り替えます。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.UI;
using Cysharp.Threading.Tasks;
using System.Threading;
public class DeviceControll : MonoBehaviour
{
private Toggle toggle;
[SerializeField]
WindZone windZone;

public void OnToggleChanged()
{
if(toggle.isOn)
{
SendWebRequest("onplug", this.GetCancellationTokenOnDestroy()).Forget();
windZone.windTurbulence = 0.5f;
}
else
{
SendWebRequest("offplug", this.GetCancellationTokenOnDestroy()).Forget();
windZone.windTurbulence = 0;
}
}

[SerializeField]
string url = "http://192.168.11.9:8000";

async UniTask SendWebRequest(string command, CancellationToken ct = default)
{
var uriBuilder = new System.UriBuilder(url + "/" + command + "/" + deviceName);
var request = UnityWebRequest.Get(uriBuilder.Uri);

[SerializeField]
string deviceName = "Plug1";

await request.SendWebRequest().ToUniTask(cancellationToken: ct);
// Start is called before the first frame update
void Start()
{
toggle = GetComponent<Toggle>();

}

}

}

18.

動作イメージ  Youtube動画をご覧ください。 https://youtu.be/kfgXEEcWHXQ

19.

仮想を通じてみえない情報を見る

20.

みえないデジタル情報を仮想のIFを介して見る 現実 IoT機器 家電 スマホ 車 etc. デジタル の 情報 仮想 という IF 現実の操作 情報の利用

21.

BLE温度・湿度計 https://inkbird.com/collections/thermometers-and-hygrometers  Bluetooth Low Energy(無線)で温度・湿度が取得できる「INK BIRD IBS-TH1 MINI 」を使って、 環境情報を可視化する。  IBS-TH2が2000円くらい。  スマホアプリ、Bluetooth接続で値の取得ができる。 IBS-TH2 IBS-TH1 MINI

22.

システム構成  Webサーバー 兼 家電とのIFはRaspberryPiを利用します。 Web Server (fastapi) BLE Library (bluepy3) Python 3 Quest3 IBS-TH1 MINI RaspberryPI 5

23.

準備、IBS-TH1 MINI のMACアドレス確認  MACアドレスを調べる Bluetoothデバイスの一覧を表示し、デバイス名がspsのものを確認する。 $ sudo hcitool lescan LE Scan ... DE:FF:EF:47:C8:C8 (unknown) B0:7E:11:EE:71:51 sps 67:AA:3D:83:1D:34 (unknown) 67:AA:3D:83:1D:34 (unknown) A8:10:87:50:F7:96 (unknown) A8:10:87:50:F7:96 sps 45:11:89:9C:19:89 (unknown) CF:9B:A7:81:69:62 (unknown)

24.

環境構築  BLEアクセス用のライブラリをインストール pip install bs4 lxml requests sudo apt-get install libglib2.0-dev libbluetooth-dev pip install --upgrade bluepy3  bluepy3-helperに権限付与 sudo setcap cap_net_raw,cap_net_admin+ep .venv/lib/python3.11/sitepackages/bluepy3/bluepy3-helper

25.
[beta]
BLEへのアクセスとWEB API
 gettemp/{name}というIFを作成し実装します。
 調べたMACアドレスとnameを対応させ、それぞれにアクセスできるようなIFにします。
import uvicorn
from fastapi import FastAPI, HTTPException, Request
from starlette.exceptions import HTTPException as StarletteHTTPException
import asyncio
import os
from fastapi.responses import JSONResponse
from bluepy3 import btle
import struct
app = FastAPI()
Thermometers
= {'Thermometer1':'B0:7E:11:EE:71:51', 'Thermometer2':'A8:10:87:50:F7:96'}

@app.get("/gettemp/{name}")
async def off(
name: str
):
mac = Thermometers[name]
sensorValue = await get_ibsth1_mini_data(mac)
return {
"result": "OK",
"temp": sensorValue['Temperature’],
"humidity": sensorValue['Humidity’]
}

if __name__ == "__main__":
async def get_ibsth1_mini_data(macaddr):
uvicorn.run(app, host="192.168.11.9", port=8000)
peripheral = btle.Peripheral(macaddr)
characteristic = peripheral.readCharacteristic(0x002d)
(temp, humid, unknown1, unknown2, unknown3) = struct.unpack('<hhBBB', characteristic)
sensorValue = {
'Temperature': temp / 100,
'Humidity': humid / 100,
'unknown1': unknown1,
'unknown2': unknown2,
'unknown3': unknown3,
}
peripheral.disconnect()
return sensorValue

26.
[beta]
QUESTからWEB APIを叩く
 一定間隔ごとに、温度と湿度を取得しTextの文字を更新します。
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.UI;
using Cysharp.Threading.Tasks;
using System.Threading;
using TMPro;
[System.Serializable]
public class Result
{
public string result;
public string temp;
public string humidity;
}
public class GetTemp : MonoBehaviour
{
[SerializeField]
string url = "http://192.168.11.9:8000";
[SerializeField]
string deviceName = "Thermometer1";

[SerializeField]
TextMeshProUGUI temperatureText;

[SerializeField]
TextMeshProUGUI humidityText;

[SerializeField]
Image image;
private async void Start()
{
while (true)
{
// n秒ごとに処理を行う
await UniTask.Delay(5000);
SendWebRequest("gettemp", this.GetCancellationTokenOnDestroy()).Forget();
}
}

27.
[beta]
QUESTからWEB APIを叩く
async UniTask SendWebRequest(string command, CancellationToken ct = default)
{
var uriBuilder = new System.UriBuilder(url + "/" + command + "/" + deviceName);
var request = UnityWebRequest.Get(uriBuilder.Uri);
await request.SendWebRequest().ToUniTask(cancellationToken: ct);

// Result:{"Result":"OK","Temp":27.25,"Humidity":62.66}
Result result = JsonUtility.FromJson<Result>(request.downloadHandler.text);
if (double.TryParse(result.temp, out double temp))
{
temperatureText.text = temp.ToString("F1") + "℃";
}
else
{
temperatureText.text = "- ℃";
}

if (double.TryParse(result.humidity, out double humidity))
{
humidityText.text = humidity.ToString("F1") + "%";
}
else
{
humidityText.text = "- %";
}
}
}

28.

動作イメージ  Youtube動画をご覧ください。https://youtu.be/XnPf5r7HTsM