---
title: From simulator to real glasses
description: Move a Meta Ray-Ban app from the browser simulator to real glasses — Meta identity, credentials, DAT resolution, the transport switch, and hardware checks.
type: guide
platform: all
vendor: meta
related:
  - /docs/concepts/transport-vs-app
  - /docs/getting-started/with-agent
  - /docs/vendors/meta
  - /docs/troubleshooting/glasses-wont-connect
---

You built your app against the [browser simulator](/docs/concepts/transport-vs-app) and the agent-driven test loop closes: you inject an utterance, the right tool fires, the event log shows the expected sequence. That gives you **high confidence the protocol, tool wiring, dispatch, and event reporting are correct** — because those bytes are identical to what real glasses send and receive. It does **not** tell you whether the app will sound clear on a noisy street, recover from a dropped Bluetooth link, or survive a 30-minute session on the glasses' battery. Real hardware is the only thing that proves those.

This page is the transition between the two. The simulator needs zero credentials and no Meta involvement — that's the point of it. Moving to real glasses is where your app first acquires a **production identity** and flips off the simulated transport. There are four moves, all of them one-time setup, and an emulator can't do any of it (no real Bluetooth radio, no Meta companion app), so you need a physical Android device.

<Callout type="info">
  **Voice-only app? This whole page is optional.** Moves 1–3 exist to get you a
  Meta production identity, and a voice app has no vendor relationship to
  establish — no Meta Developer Center account, no App ID, no GitHub token, no
  DAT pairing. Your transition is: build a release variant with no baked session
  URL and install it. On Android, confirm `glasses.transportChosen` reads
  `SYSTEM_AUDIO`. iOS has no such property — check that the assistant answers
  and that `glasses.capabilities.camera` is `false`, which is what the audio
  baseline looks like there.
  That's it. See [choose your path](/docs/getting-started/choose-your-path).
</Callout>

## The four moves

| Move | What you do | Where |
|---|---|---|
| **1 — Identity** | Create a Meta app, enable Wearables DAT, get an App ID + Client Token, register your package name + release SHA-256 signature, and enroll your own test account in a release channel | [Meta Wearables Developer Center](https://wearables.developer.meta.com/) |
| **2 — Store** | Paste the App ID + Client Token into your project's Credentials section; `generateConnectionModule` bakes them into the build | Extentos dashboard |
| **3 — Resolve** | Give Gradle a `read:packages` GitHub token so it can pull Meta's DAT SDK from GitHub Packages | Your shell or `local.properties` |
| **4 — Flip & verify** | Build a release variant with no baked simulator URL, pair glasses to an Android device, install, and run the verification pass | Your machine + a paired Ray-Ban Meta |

The rest of this page walks each one, then covers what the simulator already proved (so you know what *not* to re-test) and what only hardware can confirm.

## What the simulator already proved — and what it didn't

Don't re-test on hardware what the simulator already guarantees, and don't assume the simulator covered what it can't. The split is sharp.

**Byte-identical with real glasses** (proven in sim — trust it):

| Aspect | Why it's identical |
|---|---|
| Audio input bytes (16 kHz mono i16 PCM, app → assistant) | Same format the glasses' wideband HFP microphone produces |
| Audio output bytes (`tts_audio_chunk`, assistant → speaker) | Same path that drives the glasses' speaker |
| The WebSocket to the assistant gateway | The sim opens the *same* connection real hardware does — both go through the Extentos managed gateway, so there is no sim-vs-hardware difference in this path |
| Tool dispatch + the `assistant.*` event log | SDK-side, not transport-side — behaves identically everywhere |

**Only real hardware can confirm** (the simulator does not model these):

| Bucket | What hardware adds |
|---|---|
| **Audio character** | The glasses' beamforming HFP mic is narrower-band and noisier than a laptop mic. A model that picks tools reliably from clean sim audio may stumble on real capture, especially outdoors. |
| **Hardware timing** | The first utterance after the assistant opens is partially clipped while Bluetooth switches A2DP→HFP (~200–500 ms). Music playing over A2DP collides with the assistant and drops to mono. |
| **Hardware failure modes** | Bluetooth range loss, an incoming call pausing the session, ~30 min of continuous HFP draining the battery, sustained camera + assistant throttling the SoC. |

If your app passes the agent loop in sim, the logic is correct. The verification pass below is about the second table, not the first.

## Move 1 — Get your Meta production identity

Real-hardware traffic is gated by Meta. The simulator's placeholder credentials only ever route to the simulator; a real session needs your own Meta registration.

1. Create an app in the [Meta Wearables Developer Center](https://wearables.developer.meta.com/) — the App ID and Client Token are minted there (Manage projects).
2. Copy the **App ID** and **Client Token** from the DAT configuration.
3. Register your app's identity: its **package name** and its **release SHA-256 signing signature**. Meta binds the DAT entitlement to that exact pair — your app will not connect to real glasses until it's registered. (On iOS the pair is bundle ID + Team ID instead.)

4. **Enroll yourself as a tester.** Meta gates registration on release-channel membership: create a version, assign it to a channel, and invite the Meta account that owns the glasses you're about to test with — **including your own**. Until that invite is accepted, registration fails even with correct credentials and a correctly signed build. Requires glasses firmware v125+. See [release channels](/docs/vendors/meta).

Because the entitlement is bound to *your* package and signature, this registration is per-app and can't be shared — Extentos can deliver credentials into your build, but it can't substitute its identity into your signed app.

## Move 2 — Store credentials in your dashboard

Open your project's **Credentials** section and paste in the **App ID** and **Client Token**. These are *build-time vendor identity* — they ship inside your binary and are extractable, so they're build identity, not a server secret. (That's a different role from your managed-gateway AI key, which stays server-side and never ships. The section separates the two.)

You don't hand-edit any files. The next time you run `generateConnectionModule`, it reads your stored credentials over an owner-authenticated request and bakes them in:

- **Android** — writes `app/src/main/res/values/extentos_meta_credentials.xml` with the `meta_application_id` and `meta_client_token` string resources, referenced by two `<meta-data>` elements (`com.meta.wearable.mwdat.APPLICATION_ID` / `…CLIENT_TOKEN`) the MWDAT SDK reads at init. (Values go through `@string` refs because a bare numeric App ID is typed as an int by the resource compiler.)
- **iOS** — fills the `Info.plist` `MWDAT` dict (`MetaAppID`, `ClientToken`, plus the iOS-only `TeamID` and URL scheme).

If you haven't stored them yet, the scaffold leaves `REPLACE_ME` placeholders you can fill by hand. Either way, **the simulator and emulator dev need none of this** — credentials matter only for real hardware and release builds.

## Move 3 — Resolve the Meta DAT SDK (the GitHub token)

<Callout type="info">
  **Voice-only apps skip this move entirely.** If your app doesn't use the camera
  or the display, it has no vendor dependency: no Meta DAT artifacts, no GitHub
  token, and nothing to add to `settings.gradle.kts`. Jump to Move 4.
</Callout>

This is the one step that surprises people, so it's worth understanding *why* it exists. Camera and display come from `com.extentos:glasses-meta`, which depends on Meta's DAT SDK (`com.meta.wearable:mwdat-*`) — and **Meta publishes that SDK only through GitHub Packages**, not Maven Central, not Google's Maven repository. `com.extentos:glasses` itself has no vendor dependency and resolves from Maven Central like any normal artifact.

Concretely you need two things in your project:

1. **The repository**, in `settings.gradle.kts` (inside `dependencyResolutionManagement.repositories`). `generateConnectionModule` emits this block for you — it's the only repository you add by hand, because the Extentos library itself resolves from its normal Maven coordinates:

   ```kotlin
   maven {
     url = uri("https://maven.pkg.github.com/facebook/meta-wearables-dat-android")
     credentials {
       val localProps = java.util.Properties().apply {
         val f = File(rootDir, "local.properties")
         if (f.exists()) f.inputStream().use { load(it) }
       }
       username = localProps.getProperty("github_username") ?: System.getenv("GITHUB_USERNAME") ?: "token"
       password = System.getenv("GITHUB_TOKEN") ?: localProps.getProperty("github_token")
     }
   }
   ```

2. **A GitHub personal access token** with the `read:packages` scope, provided either as a `GITHUB_TOKEN` environment variable or as `github_token` in your `local.properties` (which is in the default Android Studio `.gitignore` — keep it that way). The username can be the literal `token`.

Validate the whole chain before your first build — token, scope, repo block, and live reachability — with the bundled diagnostic, run from your project root:

```bash
npx -p @extentos/mcp-server extentos-mcp setup
```

It does a ground-truth fetch against the actual mwdat artifact using the same auth scheme Gradle uses, so a green result genuinely predicts a successful build. Running it first saves the 1–3 minute build cycle that otherwise fails late at `:app:checkDebugAarMetadata` with a bare `401 Unauthorized` that never mentions Extentos or Meta. If you skipped it and you're staring at exactly that 401, this is the cause.

## Move 4 — Flip the transport, pair, and run

### Flip off the simulator

During development the library auto-selects the browser simulator when a session URL is baked in (`BuildConfig.EXTENTOS_SESSION_URL` on Android, `extentos.session.plist` on iOS). The resolver checks that baked URL **before** it checks for bonded glasses — so an APK built with a simulator URL stays in simulator mode even on a phone with glasses paired. For your hardware build:

- Build a **release variant** that leaves `EXTENTOS_SESSION_URL` null (and doesn't set the env var), so the resolver falls through to real Meta DAT when glasses are bonded. A release build also omits the debug-only pairing path.
- Or, for a quick spike, pass `TransportChoice.RealMeta` explicitly in `ExtentosConfig`.

`validateIntegration` will flag a release build that still has a simulator URL baked, and a missing GitHub Packages repo block — run it before you build.

### Pair and install

1. A physical **Android 12+** device (the library's `minSdk` is 31). No SIM needed.
2. Install the **Meta AI** companion app from the Play Store, sign in, and pair your Ray-Ban Meta. Take a photo from the glasses to confirm the pairing works outside your app.
3. `adb install` your release-variant APK, grant the runtime permissions (microphone, camera, Bluetooth) on first launch, and let the DAT registration flow hand off to the Meta AI app for approval. Once it returns `REGISTERED`, the session opens.

### Run the verification pass

Assert the transport first, once, before you judge anything else:

```kotlin
Log.i("Extentos", "transport=${glasses.transportChosen} source=${glasses.selectionSource} camera=${glasses.capabilities.camera}")
```

`REAL_META` is what you're after. `SYSTEM_AUDIO` means `Auto` fell through to the vendorless audio baseline — voice will work, capture won't, and nothing will throw. The usual causes are a missing `com.extentos:glasses-meta` dependency, glasses that aren't bonded, or `BLUETOOTH_CONNECT` not granted before `create(...)`. `BROWSER_SIM` means the simulator flip in the previous step didn't take.

If the assistant fails to start with `AssistantError.NoApiKey`, that's the gateway, not the glasses: a sideloaded development build can't attest, so it needs either a simulator-session binding or the dev-tier project key. See [gateway identity](/docs/concepts/ai-gateway#identity-how-the-gateway-knows-whos-calling). A beta or production build attests on its own.

Then walk 5–10 real voice interactions covering every tool you registered, and watch `getEventLog(filter: "voice")`. You're confirming two things: that the `assistant.*` sequence matches what the simulator showed (`session_started → user_spoke → tool_called → tool_result → assistant_spoke`), and how the app behaves on the three things the simulator couldn't model:

- **Audio character** — do your tools still fire reliably with real HFP audio, including outdoors? If a tool that was solid in sim gets flaky on hardware, the fix is almost always a clearer tool description, not a code change.
- **Hardware timing** — does the first utterance feel right, or is it clipped while Bluetooth switches profiles? Playing an earcon or a short `say("ready")` on `assistant.session_started` covers the gap. Check music coexistence if your app needs it.
- **Failure modes** — does the app recover from walking out of Bluetooth range, from an incoming call pausing the session, and does it stay usable within the ~30-minute battery ceiling of continuous use?

Record what you find in your app's release notes — these are customer-facing UX expectations, not library bugs. When the pass is clean, `getProductionChecklist` covers the remaining ship gates (permissions audit, foreground service, store listing).

## First hardware run — telling the four failures apart

Four different things block a first real-glasses run, and three of them can look
identical from the app's side. Run `adb logcat -s Extentos:*` while you launch,
and read in this order — each line is emitted by the SDK and is greppable.

**1. Did you reach the vendor transport at all?**

```
transport=REAL_META source=VENDOR_REGISTRY
```

Anything else and you never got to Meta. `SYSTEM_AUDIO` means the vendor arm
didn't claim — missing `com.extentos:glasses-meta`, no bonded glasses, or
`BLUETOOTH_CONNECT` not granted before `create(...)`. Fix that first; nothing
below can succeed until this line reads `REAL_META`.

**2. Did the assistant open?**

`AssistantError.NoApiKey` thrown from `assistant.start(...)` is the **project
key**, not the glasses. A sideloaded build can't attest, so it needs the baked
`EXTENTOS_PROJECT_KEY` or a simulator-session binding. This failure happens
*before* any camera work and is easy to mistake for a hardware problem — it
isn't. See [install § the project key](/docs/sdk/android/install).

**3. Did Meta registration complete?**

```
registration: app not registered — starting handoff
registration: registered
```

If you see the first line and never the second, the handoff started but didn't
land. The SDK also logs, after a stall:

```
registration: still waiting — are the glasses awake and connected in the Meta AI app?
```

Three causes, in the order worth checking:

| What to check | How you know |
|---|---|
| Glasses awake and connected **in the Meta AI app** | Open Meta AI; if the glasses show as disconnected there, Extentos can't reach them either |
| Your package + **release** SHA-256 registered with Meta | A debug-signed build never matches the registered signature — reinstall the release variant |
| Your own Meta account **enrolled in one of your release channels** | The consent prompt never appears at all. This is the one with no distinct log line, so treat it as the answer when the other two check out |

**4. Did the device session start?**

```
device session: STARTED (start gated)
```

If instead you see `device session: did NOT reach STARTED in 12s`, or:

```
device session: createSession failed (dat=NO_ELIGIBLE_DEVICE)
```

…the glasses are registered but the session didn't open. A `createSession
failed` line carries a hint when the SDK can identify the cause — for example
` — camera permission not granted; grant Camera access to capture photo/video`.
`NO_ELIGIBLE_DEVICE` with no hint usually means the glasses went to sleep
between steps; wake them and retry.

Only after line 4 does `capturePhoto()` have a session to run on. A capture
before that returns `CaptureError.NotConnected`, which is a symptom of one of
the four above and never a problem in your capture code.

## Troubleshooting

| Symptom | Cause | Fix |
|---|---|---|
| Build fails at `:app:checkDebugAarMetadata` with `401` | No `read:packages` token, or the GitHub Packages repo block is missing | Run `extentos-mcp setup`; see Move 3 |
| App runs on the phone but behaves like the simulator | A simulator session URL is baked into the build | Build a release variant with `EXTENTOS_SESSION_URL` null (Move 4) |
| Glasses never reach `REGISTERED` | Meta AI companion app missing, glasses not bonded, or DAT registration not approved | Pair and take a photo in the Meta AI app first; see [Glasses won't connect](/docs/troubleshooting/glasses-wont-connect) |
| `Wearables` init fails on a tablet | Some tablets can't install the Meta AI companion app from Play | Use an Android 12+ phone, or verify the companion app installs on your tablet before relying on it |

## Frequently asked questions

### Can I just keep using the simulator?

For building and iterating, yes — that's the primary surface, and the agent-driven loop closes there without humans or hardware. But before you put the app on a customer's head, at least one real-hardware pass is required: the simulator is byte-accurate for protocol and logic, and deliberately silent on audio character, Bluetooth timing, and failure modes.

### Can I test on an Android emulator instead of a real phone?

No. Emulators have only a virtual Bluetooth stack, so the glasses can't bond, Bluetooth SCO audio can't start, and the DAT registration has no companion app to hand off to. Cloud device farms are real phones but they're in a datacenter — they can't reach the glasses on your desk. Real-hardware testing needs a physical device with the glasses paired to it.

### Do I need to create a GitHub personal access token?

You need a token with the `read:packages` scope so Gradle can pull Meta's DAT SDK from GitHub Packages — see Move 3. It can be a brand-new token used only for this; it never goes into your app binary or your git history. If you've already built the library locally, you likely have one set already (`extentos-mcp setup` will tell you).

### Why does Meta's SDK need a token at all?

Because Meta distributes the Device Access Toolkit exclusively through GitHub Packages, and GitHub Packages requires authentication even for public packages. There's no unauthenticated mirror today. The token authenticates *to GitHub*, not to Meta or Extentos.

### My build worked all through development and suddenly 401s — what changed?

Nothing about your code. The most common trigger is building on a fresh machine or CI runner where the `read:packages` token isn't configured, or a `settings.gradle.kts` where the GitHub Packages repo block was dropped. Run `extentos-mcp setup` to pinpoint which. (Note this applies only to apps that depend on `com.extentos:glasses-meta` for camera or display — those artifacts are transitive through it, so a missing token bites simulator builds too. A voice-only app never pulls them.)

### Is the transition different on iOS?

The shape is the same — get a Meta identity, store credentials, flip the transport, pair and verify — but the concrete wiring uses Swift Package Manager and the `Info.plist` `MWDAT` dict instead of Gradle and resource files. See the [iOS quickstart](/docs/getting-started/ios). The assistant-runtime walkthrough on this page is written against the Android library.

## Related

- **[Transport vs app simulation](/docs/concepts/transport-vs-app)** — what the browser simulator covers versus Meta's Mock Device Kit, and why the same `glasses.*` code runs against both the simulator and real hardware
- **[Quickstart with an AI agent](/docs/getting-started/with-agent)** — the MCP flow that scaffolds the project and bakes your credentials
- **[Vendors: Meta Ray-Ban](/docs/vendors/meta)** — the capability matrix and what DAT supports on real hardware
- **[Glasses won't connect](/docs/troubleshooting/glasses-wont-connect)** — the connection-failure decision tree
