mcpField
D
device-features

device-features

github.com/even-realities開発79138 回インストール1 週間前に更新
リポジトリ

device-features について

Even Hub G2 のハードウェアとアプリ連携を実装するためのリファレンススキルです。音声、IMU、位置情報、写真、デバイス・ユーザー情報、永続ストレージを解説します。

主な機能

  1. 01グラスまたはスマートフォンのマイク音声を取得
  2. 02IMU レポートを制御しモーションデータを取得
  3. 03スマートフォンの位置情報を単発または継続取得
  4. 04アルバム画像の選択やスマートフォンでの撮影
  5. 05デバイス情報とユーザー情報の取得
  6. 06SDK ストレージによるアプリ状態の永続化

活用シーン

  1. 01Even Hub アプリにマイク入力を追加
  2. 02G2 の IMU データに応じた処理
  3. 03位置情報の更新表示やスマートフォン写真の取り込み
  4. 04アプリ再起動後も設定やコンテンツを保持

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/even-realities/everything-evenhub --skill device-features

インストール後、Claude Code で/device-featuresを入力して読み込まれたか確認します。

詳細

You are implementing G2 hardware feature integration for an Even Hub app. Use the reference below to implement exactly what $ARGUMENTS describes.

前提条件

createStartUpPageContainer の呼び出しが成功してから、imuControl または audioControl(true, AudioInputSource.Glasses) を呼び出す必要があります。どちらも、先にメガネ側のスタートアップページを確立しておく必要があります。スマートフォンのマイク音声、位置情報 API、pickImageFromAlbum、captureImageFromCamera にはスタートアップページは不要です。これらはメガネではなく、スマートフォンを経由します。


音声キャプチャ

await bridge.audioControl(true, source) でマイクを開始し、await bridge.audioControl(false) で停止します。source 引数で、音声をキャプチャするマイクを指定します。

  • AudioInputSource.Glasses — G2 の4マイクアレイ(デフォルト。先に createStartUpPageContainer が必要です)
  • AudioInputSource.Phone — スマートフォンのマイク(スタートアップページは不要です)

2つ目の引数を省略すると、SDK はデフォルトで AudioInputSource.Glasses を使用します(0.0.11 より前のコードとの後方互換性があります)。

音声データは onEvenHubEvent リスナーを通じて届きます。event.audioEvent からアクセスできます。このオブジェクトには、source(バッファの取得元であるマイク)と audioPcm(Uint8Array)が含まれます。

両方の音源のフォーマット: PCM、サンプリングレート 16 kHz、符号付き 16 ビット・リトルエンディアン、モノラルチャンネル。

import { AudioInputSource } from '@evenrealities/even_hub_sdk'

// Glasses MIC — requires createStartUpPageContainer to have succeeded
await bridge.audioControl(true, AudioInputSource.Glasses)

// Phone MIC — no startup-page requirement
// await bridge.audioControl(true, AudioInputSource.Phone)

const unsubscribe = bridge.onEvenHubEvent(event => {
  const audio = event.audioEvent
  if (!audio) return
  // audio.source: AudioInputSource.Glasses | AudioInputSource.Phone
  // audio.audioPcm: Uint8Array
  // Process PCM (Web Audio API, speech recognition, etc.)
})

// Stop and clean up
await bridge.audioControl(false)
unsubscribe()

権限: app.json で、AudioInputSource.Glasses には g2-microphone を、AudioInputSource.Phone には phone-microphone を宣言してください。アプリで実際に使用する音源の権限のみを宣言してください。


IMU 制御

await bridge.imuControl(true, ImuReportPace.P500) で IMU のレポートを開始し、await bridge.imuControl(false) で停止します。

ImuReportPace の列挙値: P100、P200、P300、P400、P500、P600、P700、P800、P900、P1000。これらはプロトコルのペーシングコードであり、実際の Hz 値ではありません。

IMU データは onEvenHubEvent を通じて届きます。event.sysEvent.imuData からアクセスできます。形状は { x: float, y: float, z: float } です。sys.eventType === OsEventTypeList.IMU_DATA_REPORT を確認してイベントをフィルタリングしてください。

import { ImuReportPace, OsEventTypeList } from '@evenrealities/even_hub_sdk'

await bridge.imuControl(true, ImuReportPace.P500)

const unsubscribe = bridge.onEvenHubEvent(event => {
  const sys = event.sysEvent
  if (!sys?.imuData) return
  if (sys.eventType !== OsEventTypeList.IMU_DATA_REPORT) return
  const { x, y, z } = sys.imuData
  console.log('IMU:', x, y, z)
})

await bridge.imuControl(false)
unsubscribe()

位置情報

1回限りの読み取りと継続的な更新の2つのモードがあります。どちらもスマートフォンの位置情報サービスから取得します。呼び出す前に、app.json の権限に location を宣言してください。createStartUpPageContainer は不要です。

1回限り — getAppLocation(options?)
import { AppLocationAccuracy } from '@evenrealities/even_hub_sdk'

const fix = await bridge.getAppLocation({
  accuracy: AppLocationAccuracy.High,
  timeoutMs: 5000,
})

if (fix) {
  console.log(fix.latitude, fix.longitude)
} else {
  // No fix in time, permission denied, or invalid coords. Handle gracefully.
}

AppLocation | null を返します。必ず null チェックを行ってください。ホストは、制限時間内に位置情報を取得できなかった場合、ユーザーが権限を拒否した場合、または座標が無効な場合に null を返します。

継続的な更新 — startAppLocationUpdates(options?) + onAppLocationChanged(cb)
await bridge.startAppLocationUpdates({
  accuracy: AppLocationAccuracy.Medium,
  intervalMs: 1000,
  distanceFilter: 5,  // meters — host skips pushes smaller than this
})

const unsubscribeLocation = bridge.onAppLocationChanged(loc => {
  console.log(loc.latitude, loc.longitude, loc.speed)
})

// Stop and clean up
await bridge.stopAppLocationUpdates()
unsubscribeLocation()
AppLocationAccuracy の値
  • AppLocationAccuracy.Low — 市街地レベル。最も低コストで、バッテリー消費も最小です
  • AppLocationAccuracy.Medium — 街区レベル。バランスの取れたデフォルトです
  • AppLocationAccuracy.High — 利用可能な中で最も高精度な測位。バッテリー消費は最大です
AppLocation の形状
  • latitude — 数値、度
  • longitude — 数値、度
  • accuracy? — 数値、水平方向の精度(メートル)
  • altitude? — 数値、海抜高度(メートル)
  • speed? — 数値、速度(メートル毎秒)
  • heading? — 数値、真北からの方位角(度)
  • timestamp? — 数値、Unix 時刻(ミリ秒)
AppLocationOptions のフィールド(すべて任意)
  • accuracy — AppLocationAccuracy
  • timeoutMs — 1回限りの取得のみ。測位を待つ最大時間
  • intervalMs — 継続的な更新のみ。要求する通知間隔
  • distanceFilter — 継続的な更新のみ。距離(メートル)
バックグラウンドでの動作

Android では、WebView が一時停止されると startAppLocationUpdates は停止します(background-state を参照)。フォアグラウンドに戻ったら再度有効にしてください。待機中の表示には、bridge.setLocalStorage でキャッシュした測位結果を使用できます。


写真

スマートフォンからアプリに画像を取り込む方法は2つあります。アルバムから選択する方法と、スマートフォンのカメラで撮影する方法です。どちらも1回限りの操作で、AppImageAsset を1つ返し、スマートフォンのハードウェアを使用します。G2 にはメガネ上のカメラはありません。

const fromAlbum = await bridge.pickImageFromAlbum()      // requires `album` permission
const fromCamera = await bridge.captureImageFromCamera() // requires `camera` permission

if (fromAlbum) {
  // fromAlbum.base64 is ready to drop into an <img src="data:...">
}

どちらも、ユーザーがピッカー/カメラをキャンセルした場合、または権限を拒否した場合は null を返します。アルバムピッカーは単一選択のみで、複数の画像をインポートすることはできません。

AppImageAsset の形式
  • path — 文字列。ホスト側のパスです(WebView からは不透明です)
  • name — 文字列。元のファイル名です
  • mimeType — 文字列。例:image/jpeg、image/png
  • size — 数値。バイト数です
  • base64 — 文字列。<img> での表示や、さらなる処理にそのまま使えるインラインデータです
画像をグラスに送信する

base64 は WebView ですぐに表示できますが、12 メガピクセルの JPEG を updateImageRawData 経由でグラスのコンテナーに送る処理は、SDK 内部の LZ4 圧縮(0.0.12+)を使っても負荷が高いままです。圧縮によって転送量は減りますが、ピクセル数は変わりません。グラスにピクセルを送信する前に、コンテナーの寸法に合わせて縮小し、4 ビットのグレースケールに変換してください。 画像コンテナーのパターンについては、glasses-ui を参照してください。


デバイス情報

await bridge.getDeviceInfo() は、DeviceInfo オブジェクトまたは null を返します。

DeviceInfo のフィールド:

  • model — DeviceModel 列挙型(DeviceModel.G1 = "g1"、DeviceModel.G2 = "g2"、DeviceModel.Ring1 = "ring1")
  • sn — シリアル番号の文字列
  • status — DeviceStatus オブジェクト

DeviceStatus インターフェースのフィールド:

  • sn — シリアル番号
  • connectType — DeviceConnectType 列挙型の値
  • isWearing? — ブール値。ユーザーがデバイスを装着しているかどうか
  • batteryLevel? — 整数。0~100
  • isCharging? — ブール値
  • isInCase? — ブール値

DeviceConnectType の列挙値: None、Connecting、Connected、Disconnected、ConnectionFailed

DeviceStatus のヘルパーメソッド: isNone()、isConnected()、isConnecting()、isDisconnected()、isConnectionFailed()

リアルタイムのステータス更新を受け取るには、bridge.onDeviceStatusChanged を使って登録します:

const unsubscribe = bridge.onDeviceStatusChanged(status => {
  console.log('Battery:', status.batteryLevel)
  console.log('Connected:', status.isConnected())
})

リスニングを停止するには、unsubscribe() を呼び出します。


ユーザー情報

await bridge.getUserInfo() は、UserInfo オブジェクトを返します。

UserInfo のフィールド:

  • uid — 数値。一意のユーザー識別子
  • name — 文字列。表示名
  • avatar — 文字列。ユーザーのアバター画像の URL
  • country — 文字列。ユーザーの国コード

ローカルストレージ

データを Even Realities App に保存します(アプリを再起動しても保持されます):

  • await bridge.setLocalStorage(key, value) — 文字列値を保存し、成功したかどうかを示す boolean を返します
  • await bridge.getLocalStorage(key) — 保存された文字列を取得します。キーが存在しない場合は空の文字列を返します
SDK の localStorage だけが信頼できる永続化手段です

Even App の WebView は Flutter WebView です。この環境では、ブラウザーの IndexedDB と localStorage は、アプリを再起動しても確実に保持されません。ユーザーがアプリを閉じて再度開いたときに、保存したデータが失われることがあります。

設定、進捗、ブックマーク、好み、キャッシュ済みコンテンツなど、すべてのユーザー状態には bridge.setLocalStorage / bridge.getLocalStorage を使用してください。大きなコンテンツ(例:電子書籍のテキスト)は、複数のキーに分割して保存します:

const CHUNK_SIZE = 50_000  // chars per key
const PREFIX = 'myapp.content_'

async function saveContent(bridge: EvenAppBridge, id: string, text: string) {
  const chunks = Math.ceil(text.length / CHUNK_SIZE)
  await bridge.setLocalStorage(`${PREFIX}${id}_n`, String(chunks))
  for (let i = 0; i < chunks; i++) {
    await bridge.setLocalStorage(
      `${PREFIX}${id}_${i}`,
      text.slice(i * CHUNK_SIZE, (i + 1) * CHUNK_SIZE),
    )
  }
}

ブリッジへの書き込みをデバウンスして直列化する方法については、glasses-ui → Best Practices を参照してください。


終了時のクリーンアップ

アプリの終了時または破棄時には、必ずハードウェア機能を停止し、イベントリスナーの登録を解除してください。これを怠ると、グラスのハードウェア上でマイクや IMU が動作し続ける可能性があります。

// Store all unsubscribe functions and stop hardware on exit
window.addEventListener('beforeunload', () => {
  bridge.audioControl(false)
  bridge.imuControl(false)
  bridge.stopAppLocationUpdates()
  unsubscribe() // from onEvenHubEvent
  unsubscribeLocation() // from onAppLocationChanged
})

明示的な停止/開始制御が必要なハードウェア機能(audioControl、imuControl、startAppLocationUpdates)にも、同じパターンを適用してください。


SDK で利用できない機能

以下の機能は Even Hub SDK では利用できません:

  • Bluetooth への直接アクセス
  • 任意のピクセル描画
  • 音声出力(スピーカーはありません)
  • テキストの配置制御
  • フォント制御
  • 背景色
  • リスト項目ごとのスタイル設定
  • プログラムによるスクロール位置の制御
  • アニメーション
  • グラス側のカメラ(G2 には搭載されていません。上記の「写真」を参照してください。captureImageFromCamera 経由でスマートフォンのカメラを利用できます)
  • 画像はグレースケールのみ

タスク

$ARGUMENTS

コメント