From simulator to real glasses
Move a Meta Ray-Ban app from the browser simulator to real glasses — Meta identity, credentials, DAT resolution, the transport switch, and hardware checks.
You built your app against the browser simulator 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.
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.
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 |
| 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.
-
Create an app in the Meta Wearables Developer Center — the App ID and Client Token are minted there (Manage projects).
-
Copy the App ID and Client Token from the DAT configuration.
-
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.)
-
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.
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.xmlwith themeta_application_idandmeta_client_tokenstring resources, referenced by two<meta-data>elements (com.meta.wearable.mwdat.APPLICATION_ID/…CLIENT_TOKEN) the MWDAT SDK reads at init. (Values go through@stringrefs because a bare numeric App ID is typed as an int by the resource compiler.) - iOS — fills the
Info.plistMWDATdict (MetaAppID,ClientToken, plus the iOS-onlyTeamIDand 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)
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.
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:
-
The repository, in
settings.gradle.kts(insidedependencyResolutionManagement.repositories).generateConnectionModuleemits this block for you — it's the only repository you add by hand, because the Extentos library itself resolves from its normal Maven coordinates: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") } } -
A GitHub personal access token with the
read:packagesscope, provided either as aGITHUB_TOKENenvironment variable or asgithub_tokenin yourlocal.properties(which is in the default Android Studio.gitignore— keep it that way). The username can be the literaltoken.
Validate the whole chain before your first build — token, scope, repo block, and live reachability — with the bundled diagnostic, run from your project root:
npx -p @extentos/mcp-server extentos-mcp setupIt 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_URLnull (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.RealMetaexplicitly inExtentosConfig.
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
- A physical Android 12+ device (the library's
minSdkis 31). No SIM needed. - 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.
adb installyour 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 returnsREGISTERED, the session opens.
Run the verification pass
Assert the transport first, once, before you judge anything else:
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. 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")onassistant.session_startedcovers 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_REGISTRYAnything 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.
3. Did Meta registration complete?
registration: app not registered — starting handoff
registration: registeredIf 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 |
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. The assistant-runtime walkthrough on this page is written against the Android library.
Related
- Transport vs app simulation — 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 — the MCP flow that scaffolds the project and bakes your credentials
- Vendors: Meta Ray-Ban — the capability matrix and what DAT supports on real hardware
- Glasses won't connect — the connection-failure decision tree
Related
Transport vs app simulation
Meta's Mock Device Kit simulates the transport layer; Extentos simulates the app layer — voice, photo capture, and the wearing experience. Both matter.
Quickstart with an AI agent
Install the Extentos MCP server and let your AI agent scaffold Meta Ray-Ban smart-glasses capabilities into a native iOS or Android app. Free to start.
Meta smart glasses (Meta DAT)
Meta smart glasses developer guide: Wearables Device Access Toolkit (DAT 0.8.0) capabilities, supported models (Ray-Ban Meta, Oakley Meta, Ray-Ban Display), 2026 distribution state, and how Extentos abstracts the toolkit.
Glasses won't connect
Fix common first-connection failures on Meta Ray-Ban — no pairing dialog, session won't start, the DAT auth callback never fires, or an immediate disconnect.
Handle disconnects
Observe glasses.connection.state, branch on the typed Disconnected.cause to tell a user-pulled-the-glasses-off from a Bluetooth drop, and reconnect cleanly. The connection-state spine for any Meta Ray-Ban app.
Migrating from raw Meta DAT
Move an app you already built against Meta's Device Access Toolkit onto the Extentos SDK — a call-site swap, not a rewrite, because Extentos calls the same DAT underneath.