13
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

現在地付近の東京の電車・バスをスマートグラスに「スカウター」として映す — Even G2 plugin を作った話

13
Last updated at Posted at 2026-05-12

東京の電車・バスをスマートグラスに「スカウター」として映す — Even G2 plugin の開発記

Even Realities G2 (スマートグラス) に、東京の電車・バス・駅・運行情報をリアルタイムで映すアプリ「東京スカウター」を作りました。
576×288 px / 16 階調モノクロ・1 文字描画ですら制約だらけのハードウェアと戦う過程と、Even Hub 公式審査で 2 回 reject を喰らって学んだ作法を中心にまとめます。

glasses_20260514133510.png


TL;DR

  • 作ったもの: 東京の公共交通リアルタイム情報を Even G2 に投影する Web プラグイン (単一ソナー画面)
  • データ: 公共交通オープンデータセンター (ODPT) の REST + GTFS-RT。徒歩ルート案内は OSRM
  • 技術: TypeScript + Vite + @evenrealities/even_hub_sdk + Leaflet (companion 側) + fflate (GTFS Static 解凍) + @mapbox/vector-tile
  • インフラ: AWS CloudFront + S3 + CloudFront Functions (acl:consumerKey を edge で注入、path rewrite)、Terraform 管理
  • 見どころ:
    • G2 ならではのハードウェア制約 (4-bit greyscale, 200×100 image container, LVGL 固定フォント, グリフ収録範囲) との戦い
    • Even Hub 審査で 2 回 reject を喰らった内容と対処
  • ソースコード: 公開後にここへ GitHub リンクを貼る予定

1. Even Realities G2 とは

Even Realities が販売しているスマートグラス。眼鏡の両レンズに micro-LED で 片眼ずつ 576×288 px / 16 階調モノクロ (緑のみ) の表示を投影できる (SDK API では両眼に同じ内容が描かれる)。カメラもスピーカーもなく、プライバシーフォーカスの設計。

仕様
表示 576 × 288 px / 片眼
色深度 4-bit grayscale (緑 16 階調)
通信 Bluetooth 5.2
入力 テンプル touchpad (Press / Double press / Swipe up / Swipe down)
音声 4 マイクアレイ
カメラ なし

アプリ本体は スマホ上の WebView で動かして、表示だけを BLE で G2 に投げる構造。これによって普通の Web 技術 (HTML/CSS/JS) で書ける = 開発が圧倒的に楽。

詳細は Even Hub の公式ドキュメント を参照。


2. 東京スカウターって何?

戦闘力を測るscouterではないですが(え)、視界に「今、この方向に走ってる電車・バス」「最寄りの駅・バス停」「運行情報」が浮かぶイメージ。スマホを取り出さずに、目線だけで街の交通状況を把握できる。

2.1 G2 側: 単一ソナーページ

canvas 576 × 288:
┌──────────────────────────────┬───────────────┐
│ 時刻  表示範囲 ↑北            │   sonar (top) │
│                              │  200×100      │
│ 次のバス  都営バス 都02      │               │
│           600m 北東 3分      ├───────────────┤
│                              │   sonar (bot) │
│ 次の電車  銀座線 浅草行       │  200×100      │
│           1.2km 北 5分       │  (縦2枚で     │
│                              │   実質 200×200)│
│ 最寄駅  東京駅 200m 西        │               │
│ バス停  12件 < 200m          │               │
│                              │               │
│ 運行情報 平常運転             │               │
└──────────────────────────────┴───────────────┘

左側がテキスト情報、右側がソナー (中央=自分、放射状に entity の方向)。1 画面に全部入れたのがポイント (理由は後述「Hub 審査 reject 体験記」)。

2.2 タッチ操作 (極小)

  • シングルタップ: 何もしない (no-op) — 画面遷移は廃止
  • ダブルタップ: bridge.shutDownPageContainer(1) でシステムの終了確認ダイアログ (Hub 審査必須要件)
  • Swipe up / down: 表示半径を循環 500 m → 1 km → 5 km → 10 km

glasses_20260514133510.png

2.3 スマホ companion 側: タップでルート案内

G2 で「あの方向にバスがあるな」と気付いたら、ペアリングしたスマホの WebView 画面で マップをタップ → 徒歩ルートが見れる:

  • 背景: CartoDB Positron (白ベース・低彩度) の OpenStreetMap タイル — 街路と地名がはっきり読める
  • マーカー: 駅 (青)、バス停 (グレー)、走行中バス (緑)、電車 (オレンジ)、自分 (赤)
  • タップ動作: 駅 / バス停 / バス / 電車を tap → 黄色 polyline でその地点までの徒歩ルート + 距離 + 推定所要時間を表示
  • ルート取得: フリーの OSRM (Open Source Routing Machine) 公開デモサーバー を CloudFront proxy 経由で叩く

image.png


3. アーキテクチャ全景

ポイント

  1. アプリ本体 (TS + Vite で書いた SPA) は S3 + CloudFront で配信
  2. すべての外部 API は CloudFront 経由でプロキシ — Even Hub plugin 権限 (network.whitelist) を CloudFront 1 ドメインに集約でき、レビューが格段にクリーン
  3. CloudFront Functions で:
    • /odpt/* のクエリに acl:consumerKey を edge 注入 (API key をクライアントに露出させない)
    • /odpt-public/* / /osm-tiles/* / /osrm/* のパスを各オリジン用に rewrite

4. G2 特有のハードウェア制約との戦い

ここがいちばん面白いパート。普通の Web 開発の感覚で挑むと、ことごとく潰される。

4.1 解像度 576×288 / 16 階調モノクロ

「お、Retina の今どきまあいけるやろ」と思って何でも詰めようとすると、情報密度設計のうまさが問われるハードルになる。文字 1 個ぶんが画面の数%を占める世界。色がない (緑だけ) ので、形状でしか entity を識別できない:

  • 駅: 中空角丸スクエア
  • バス停: 1×2 ピクセルのドット
  • バス: 円
  • 電車: 三角形

4.2 画像コンテナの上限 200×100 px / 1 ページ 4 個まで

「地図を 400×200 で描こう」と思ったら NG。1 個の image container は 200×100 が最大。仕方ないので 4 個並べる or 縦に 2 個積んで実質 200×200 の sonar を作るなど工夫が要る:

canvas 576 × 288:
┌──────────────────┬───────────────┐
│                  │ sonar_top     │ 200×100  ← image container
│ left panel       ├───────────────┤
│ (text)           │ sonar_bot     │ 200×100  ← image container
│                  │ (合計で       │
│                  │  200×200相当) │
└──────────────────┴───────────────┘

4.3 LVGL 固定フォント — サイズが変えられない

G2 のテキスト描画は LVGL という組み込み GUI ライブラリの上に乗っていて、フォントは 1 種類・1 サイズ固定。プラットフォーム側から差し替え不可。これは長文の情報密度を上げたいときに辛い。

回避策: OffscreenCanvas で 9–10 px の小さい文字を fillText で焼き込み、それを PNG にして image container に流す。canvas 描画なので任意サイズの文字が出る:

function drawText(g, text, x, y, sizePx, fillStyle) {
  g.fillStyle = fillStyle
  g.font = `${sizePx}px "Hiragino Sans", sans-serif`
  g.fillText(text, x, y)
}

4.4 LVGL の文字グリフ収録範囲が狭い

これがいちばんハマる罠。LVGL のフォントは 基本 ASCII + JIS X 0208 + ごく一部の記号 しか収録していない。よく使う以下は NG:

Unicode 文字 用途 (使いたかった) LVGL 結果
U+25A2 駅マーカー 謎の □ プレースホルダ
U+26A0 運行情報 warning 謎の □ プレースホルダ + warn 多発
U+1F68C 🚌 バスアイコン 同上
U+1F686 🚆 電車アイコン 同上

LVGL は描画時に glyph dsc. not found for U+26A0 のような warning をログに大量に出してくる。**「絵文字や記号類は使わない」**が安全策。利用可能な代替:

  • (U+25A1) → OK
  • (U+25CF) → OK
  • (U+25B2) → OK
  • (U+30FB) → OK
  • ↑→←↓ → OK

4.5 BLE 帯域

PNG エンコードして送るので、毎フレーム再送すると詰まる。フレームハッシュで前フレームと同じなら BLE 送信を skip することで diff だけ送る運用に。

private async sendHalf(half, id, name, buf) {
  const h = hash32(buf)
  if (h === this.lastHash[half]) return  // ← 前回と同じならスキップ
  await this.bridge.updateImageRawData(...)
  this.lastHash[half] = h
}

4.6 SDK のクセ

  • createStartUpPageContainer()起動時 1 回しか呼べない。以降は rebuildPageContainer()
  • ページ切替時に古い container は teardown される — その瞬間に描画送ると container N not found warn
  • simulator は単発タップを {sysEvent: {eventSource: 1, eventType: undefined}} で送ってくる (実機は eventType: CLICK_EVENT) → 自前 wrapper で workaround

5. 開発スタック

中核ライブラリ

{
  "dependencies": {
    "@evenrealities/even_hub_sdk": "^0.0.10",
    "@mapbox/vector-tile": "^2.0.4",
    "fflate": "^0.8.2",
    "gtfs-realtime-bindings": "^1.1.1",
    "leaflet": "^1.9.0",
    "pbf": "^4.0.1",
    "rbush": "^4.0.1"
  }
}

ポイント:

  • fflate — GTFS Static zip 展開用。jszip から置き換えた (理由は後述「Hub 審査 reject 体験記」 — new Function() をバンドルに含めるため reject されたため)
  • leaflet — スマホ companion 側の地図 + ルートポリラインの描画。タップ判定もネイティブ機能
  • @mapbox/vector-tile + pbf — PLATEAU 建物 MVT のパース用 (G2 描画速度の都合で現在は描画停止中、依存だけ残)

6. データソース — ODPT

ODPT は MLIT などが後援する公共交通オープンデータ協議会が運営するデータセンター。東京メトロ / 都営 / JR 東 / つくばエクスプレス (運営: MIR) / りんかい線 (運営: TWR) / 関東バス / 関東鉄道などの駅・路線・時刻表・列車位置・運行情報を REST + GTFS-RT で提供している。

7 事業者 × 17 種類のマトリクス全体は docs/data-catalog.md 参照。「電車のリアルタイム位置」は都営しか GTFS-RT で取れないので、メトロ / JR / MIR (TX) / TWR は odpt:Train (区間 + 方向) を駅座標に近似プロットしている。


7. AWS CloudFront を edge proxy として使う

acl:consumerKey を edge で注入する CF Function

function handler(event) {
  var req = event.request;
  // /odpt/api/v4/... → /api/v4/... (origin 側の path に合わせる)
  req.uri = req.uri.replace(/^\/odpt\//, '/');
  // consumerKey を edge で注入
  var qs = req.querystring;
  qs['acl:consumerKey'] = { value: '${var.odpt_consumer_key}' };  // Terraform interpolation
  req.querystring = qs;
  return req;
}
  • ODPT key をクライアントに渡さない
  • Even Hub plugin の network.whitelistCloudFront 1 ドメインに集約
  • 5 つの外部 API (ODPT 公開、ODPT 認証、CartoDB 地図タイル、OSRM ルーティング) すべて 1 ドメイン経由

キャッシュ戦略

パス TTL 理由
/ (HTML) no-cache 新版を即座に反映
/assets/*-[hash].js 1 year, immutable ハッシュ付きなので別 URL
/odpt-public/*, /odpt/* 5–15s リアルタイム性優先
/osm-tiles/* (CartoDB) 1 week (max 30 days) タイルはほぼ不変
/osrm/* 1 hour 同一 OD は同じ結果

8. Hub 審査リジェクト体験記 ⭐️

書き終わってから Even Hub に submit した後、2 回 reject されて学んだ作法を共有します。これから submit する人は事前にチェックしてほしい。

第 1 回 reject の指摘 3 件

1. We couldn't find bridge.shutDownPageContainer().
   Please add bridge.shutDownPageContainer(1) to the double-tap handler
   to enable the exit confirmation dialog.

2. Screenshot does not meet the standard.
   Please re-capture using the latest simulator.

3. new Function() poses security risks.
   Please refactor to remove it.

#1 — shutDownPageContainer(1) がない

Submission Guidelines の必須要件:

Root-page double-tap calls bridge.shutDownPageContainer(1) — the system exit confirmation dialog.
Mode 0 (immediate exit) is not acceptable on the root page.
A custom in-app exit confirmation UI is not acceptable on the root page either.

ところが当時の私の実装では、double-tap は **「詳細ページに遷移」**になっていた。明らかにこの指針違反。

修正: シングルページ設計に切り替え (sonar の 1 画面に全情報を統合)、double-tap は問答無用で bridge.shutDownPageContainer(1) を呼ぶように:

case 'doubleClick': {
  await bridge.shutDownPageContainer(1)
  break
}

ついでに「map / details ページへの遷移」自体を廃止。シングルページにしたことで遷移バグ (container 10 not found 等) も同時解消した。

#2 — スクリーンショットの背景が真っ黒

最初は「G2 ってモノクロ緑なんで黒地で当然じゃ」と思って混乱したが、よく見るとアップロードした画像が 完全に空の真っ黒 だった。原因は shutDownPageContainer(1) を呼んだ直後の真っ黒フレームで撮ってしまったこと。simulator ではこの mode 1 のダイアログ UI が実装されてなくて、G2 画面が空になる。

ちなみに real device では確認ダイアログが描画される設計なので、real device で撮影できればここまでハマらなかった気もする。

修正: simulator を再起動し、Sonar が完全に表示された状態を目視確認してから撮影:

# シェル A
npm run dev

# シェル B
evenhub-simulator http://localhost:5173

# Browser ウィンドウで Debug log を確認
# "📺 Page: SONAR" が出てから
# simulator 右下のカメラアイコンをクリック

#3 — new Function() security risk

これがいちばん意外な reject。我々のソースには new Function() なんて書いてない。

調査したら、jszip パッケージの中にあった:

// node_modules/jszip/dist/jszip.js:11404 (= bundled setImmediate polyfill)
function setImmediate(callback) {
  if (typeof callback !== "function") {
    callback = new Function("" + callback)  // ← これ
  }
  ...
}

実際には setImmediate(function) の形でしか呼ばれないので実行されないデッドコードだが、静的解析でバンドル内の new Function( を grep するレビュー方式だと引っかかる。

修正: jszipfflate (modern, eval-free, より軽量) に置き換え:

// 旧
import JSZip from 'jszip'
const zip = await JSZip.loadAsync(buf)
const routesCsv = await zip.file('routes.txt').async('string')

// 新
import { unzip, strFromU8 } from 'fflate'
const files = await new Promise((resolve, reject) => {
  unzip(buf, {
    filter: (f) => f.name === 'routes.txt' || f.name === 'trips.txt',
  }, (err, out) => err ? reject(err) : resolve(out))
})
const routesCsv = strFromU8(files['routes.txt'])

unzipfilter必要なファイルだけ展開できるのも fflate の利点 (GTFS Static は ~70MB に展開されるが、routes.txttrips.txt の 2 ファイルだけで足りる)。

npx vite build 後の bundle を grep で確認:

$ grep -c "new Function" dist/assets/index-*.js
0   # ← 0 件、これで通る

第 2 回 reject の指摘 2 件

3 件直して再 submit したら、また reject:

1. Unlisted URLs found in network.whitelist
   — add if used, ignore if unused.

2. Screenshot failed standard validation.
   Please retake using the latest simulator.
   Could you check why the screenshot background is black?

#1 — whitelist に未使用 URL

私の app.jsonnetwork.whitelist には先回り的に dev / stg / prd の 3 つの CloudFront URL を入れていたが、現実には dev しか build していない:

//   stg / prd を含んでいたが両方未構築なので reject
"whitelist": [
  "https://oec-tokyo-scouter.dev.tailstone.net",
  "https://oec-tokyo-scouter.stg.tailstone.net",    //  未構築
  "https://oec-tokyo-scouter.tailstone.net",         //  未構築
  "https://dataodpt.blob.core.windows.net"
]

//   実際に使ってる 2 つだけ
"whitelist": [
  "https://oec-tokyo-scouter.dev.tailstone.net",
  "https://dataodpt.blob.core.windows.net"
]

dataodpt.blob.core.windows.net は ODPT の /files/* エンドポイントが 302 で Azure Blob Storage の SAS 署名つき URL にリダイレクトする ため、whitelist に追加が必要なケース。desc フィールドにこれを書いておくと審査がスムーズ。

#2 — また screenshot が黒

simulator の振る舞いをもう一度整理:

  • 起動直後 → containers 未生成 → 黒
  • installInitial() 完了 → sonar 表示 → コンテンツあり
  • double-tap → shutDownPageContainer(1) → 黒 (simulator は dialog UI を実装してない)

install 完了後の sonar 表示中 に撮影するのが鉄則。Debug log で 📺 Page: SONAR が出てから 5 秒くらい待って、左パネルテキスト + 右ソナー両方が見える状態で撮影。

教訓

  1. 公式の Submission Guidelines は事前に隅々まで読む
  2. bundle 内の new Function / eval は依存ライブラリ起因も含めて 0 にする (grep -c "new Function" dist/...)
  3. whitelist は実際に fetch する URL だけにする (将来用に先回り NG)
  4. screenshot は install 完了状態の sonar を撮る (黒フレーム NG)

9. 開発フロー全体像

具体的なコマンドは次章以降。最後に Makefile で 1 行化する話 があるので、好みで使い分けて。


10. シミュレータが超便利

Even Realities が用意してくれた evenhub-simulator (公式 npm package) が本当に良くできている。実機を出さなくても 90% の見た目検証ができるので、CI 的に回せる。

セットアップ

npm install -g @evenrealities/evenhub-simulator @evenrealities/evenhub-cli

起動 + sideload

# シェル A: Vite dev server
npm run dev

# シェル B: シミュレータ
evenhub-simulator http://localhost:5173
# → 576×288 の G2 mock window が開く

実機テストは公式 CLI の QR sideload:

evenhub qr --url http://$(ipconfig getifaddr en0):5173
# ターミナル ASCII QR → Even App でスキャン → G2 投影 (HMR 対応)

screenshot の撮り方 (審査用)

1) Browser window で Debug log を確認:
   - "✅ Bridge connected"
   - "📍 Location: ..."
   - "📡 Static: 498 駅 / 3691 バス停"
   - "📺 Page: SONAR"
   が全部出てから 5 秒待つ

2) "Glasses Display" window 右下のカメラアイコンをクリック
   → ~/Downloads/evenhub-screenshot-*.png に 576×288 PNG が保存

3) Finder で PNG を開いて、左パネルの text と右の sonar circle 両方が見えることを確認
   → 全黒なら NG (撮るタイミング失敗)

11. 性能チューニング — 実機で動かして初めて分かること

実機の G2 はスマホ WebView の CPU を経由して BLE で画像を送る。要因が多重なので、何が遅いか切り分けるのが大変だった。

ボトルネック 対策
建物ポリゴン g.fill() × 数百個 建物描画を削除 (一旦)
鉄道線の自作 1px ループ g.beginPath() + g.stroke() の native API に
fillText の駅名ラベル 削除 (情報密度より速度)
1Hz periodic re-render data-change-driven に変更
PNG エンコード × 4 タイル hash 一致で BLE 送信を skip
ソナーの回転 sweep バー 撤去 (背景静止 → hash skip が効きやすく)

data-change-driven render

ポーラーが新データを fire したタイミングだけ描画:

const renderTick = async () => {
  if (renderInFlight || transitioning) { renderScheduled = true; return }
  // ... heavy work ...
}

const requestRender = () => {
  const since = Date.now() - lastRender
  if (since < MIN_RENDER_INTERVAL_MS) {
    setTimeout(() => requestRender(), MIN_RENDER_INTERVAL_MS - since)
    return
  }
  void renderTick()
}

busPoller.subscribe(() => requestRender())
trainGtfsRt.subscribe(() => requestRender())
// 安全網: 3 秒に 1 度は無条件で再描画 (時計表示等のため)
setInterval(() => void renderTick(), 3000)

ページ遷移時の container N not found 対策

rebuildPageContainer() を await している間、古い container はもう無く、新しい container はまだ無い「真空状態」が生まれる。そこで render が走ると warn 連発。

transitioning フラグで install 中の render を skip:

let transitioning = false

const renderTick = async () => {
  if (renderInFlight || transitioning) { renderScheduled = true; return }
  // ...
}

// page install
transitioning = true
try {
  await sonarPage.install(state)
} finally {
  transitioning = false
  requestRender()
}

12. デプロイ — AWS CLI + Terraform で全部やる

初回: インフラを Terraform で立てる

# 1. tfstate 用の S3 + DynamoDB lock
terraform -chdir=terraform/bootstrap/dev init
terraform -chdir=terraform/bootstrap/dev apply

# 2. ODPT consumer key を Secrets Manager に保管
aws secretsmanager create-secret \
  --name oec-tokyo-scouter/odpt-consumer-key \
  --region ap-northeast-1 \
  --secret-string "<your-odpt-key>"

# 3. Terraform に key を渡して apply
export TF_VAR_odpt_consumer_key=$(aws secretsmanager get-secret-value \
  --secret-id oec-tokyo-scouter/odpt-consumer-key \
  --region ap-northeast-1 --query SecretString --output text)

terraform -chdir=terraform/environments/dev init
terraform -chdir=terraform/environments/dev apply
# → CloudFront + S3 + ACM + Route53 + CF Functions が 15-20 分で立つ

通常のデプロイ

# 1. ビルド
npm run build

# 2. S3 へ sync (assets は immutable, index.html は no-cache)
BUCKET=$(terraform -chdir=terraform/environments/dev output -raw plugin_s3_bucket)
aws s3 sync dist/ s3://$BUCKET/ --delete \
  --cache-control "public, max-age=31536000, immutable" \
  --exclude "*.html"
aws s3 cp dist/index.html s3://$BUCKET/index.html \
  --cache-control "no-cache, no-store, must-revalidate"

# 3. CloudFront invalidate
DIST=$(terraform -chdir=terraform/environments/dev output -raw cloudfront_distribution_id)
aws cloudfront create-invalidation --distribution-id $DIST --paths '/*'

Hub portal に登録するときは .ehpk パッケージ

evenhub pack app.json dist -o oec-tokyo-scouter-0.1.6.ehpk
# → Hub developer portal にアップロード → レビュー申請

13. 学び・気づき

制約があるほうがクリエイティブ

「16 色しか使えない」「200×100 が最大」「絵文字使えない」というハードコアな制約があると、情報の取捨選択 が研ぎ澄まされる。「これは本当に表示すべき情報か?」と毎ピクセル考えるのは普段の Web 開発と全然違う頭の使い方で楽しい。

Edge function は強い

CloudFront Functions で 5 行の JS が consumerKey 注入 + path rewrite を edge で完結させる。**「key を front に出さない」「whitelist を 1 ドメインに集約する」**という Hub 審査クリアの観点でも有効。

Hub 審査の作法は事前に読む

reject の経験から強く言える: Submission Guidelines を最初に読み込むだけで 2 回分の往復が省ける。とくに:

  • ルートページ double-tap = shutDownPageContainer(1) 必須
  • new Function / eval をバンドルから完全除去 (依存ライブラリ起因も含む)
  • network.whitelist は実際に fetch する URL だけ
  • screenshot は app が完全描画された状態で撮る

Open data + edge function + smart glasses = 楽しい組み合わせ

ODPT のような公共データを edge proxy 経由で smart glasses に流すパイプライン、技術的にも面白くて応用範囲も広い。位置情報・方角・少しの情報で価値が出るドメインなら何でも smart glasses は強い。


14. Makefile にまとめた — 開発体験の統一

ここまで CLI 直叩きで紹介してきたコマンドは、毎回手で打つには長いので、リポジトリの Makefile1 行のエイリアス として整理してある:

ENV         ?= dev
PROJECT     := oec-tokyo-scouter
APP_VERSION := $(shell node -p "require('./app.json').version")
EHPK_NAME   := $(PROJECT)-$(APP_VERSION).ehpk

install:     ; npm install && npm i -g @evenrealities/evenhub-simulator @evenrealities/evenhub-cli
dev:         ; npm run dev
simulator:   ; evenhub-simulator http://localhost:5173
qr:          ; evenhub qr --url http://$$(ipconfig getifaddr en0):5173
build:       ; npm run build
pack: build  ; evenhub pack app.json dist -o $(EHPK_NAME)

bootstrap:   ; cd terraform/bootstrap/$(ENV) && terraform init && terraform apply
tf-apply:    ; cd terraform/environments/$(ENV) && terraform apply
get-key:     ; @echo "export TF_VAR_odpt_consumer_key=$$(aws secretsmanager \
                 get-secret-value --secret-id $(PROJECT)/odpt-consumer-key \
                 --query SecretString --output text)"

deploy: build upload invalidate

結果として開発フローはこれだけ

# 初回
make install
echo "ODPT_CONSUMER_KEY=<key>" > .env.local
make bootstrap ENV=dev
eval "$(make get-key)"
make tf-apply ENV=dev

# 日常
make dev          # シェル A
make simulator    # シェル B
make qr           # 実機 sideload 用 QR

# デプロイ
make deploy ENV=dev

# Hub 提出
make pack         # → oec-tokyo-scouter-0.1.6.ehpk

重要: make X は公式機能ではなく自作 wrappermake qr を「Even の機能」と勘違いされないように、各 target が実際に呼んでいるコマンドは npm / evenhub CLI / aws CLI / terraform の組み合わせです。


15. これから

  • 本番リリース (ENV=prd) — まずは Hub 審査を通すこと
  • PLATEAU の建物再導入 — 描画コストを抑える形で (LOD1 シルエットのみ等)
  • AR 的な表示 — 「次のバスはこっちの方向」のような方角インジケータ
  • 多言語化app.json に既に ["ja", "en"] 宣言済み、UI 全体を翻訳

まとめ

スマートグラスは「制約だらけのコンピューター」だけど、その制約こそが設計を研ぎ澄ます。Web 開発の延長で組めるし、AWS / Open data / Edge function という現代的なスタックがちょうど噛み合う面白い領域だと思う。

Hub の審査要件は最初に読み込むだけで rejection サイクルがガッと減るので、これから submit する人は 第 8 章のチェックリスト を参考にしてください。

公共交通系に限らず、位置と方角と少しの情報で価値が出るドメインなら何でも smart glasses は強い。皆さんもぜひ Even G2 で何か作ってみてください。


参考リンク

Even Realities 公式

Even コミュニティ

公共交通オープンデータ (ODPT)

地図 / ルーティング

今回使った npm パッケージ

AWS / インフラ

13
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?