Skip to main content
Subscribe to real-time events from glasses using session.events. Get voice transcription, button presses, sensor data, and more.

Quick Start

protected async onSession(session: AppSession, sessionId: string, userId: string) {
  // Subscribe to voice transcription
  session.events.onTranscription((data) => {
    session.logger.info('User said:', data.text);
  });

  // Subscribe to button presses
  session.events.onButtonPress((data) => {
    session.logger.info('Button pressed:', data.button);
  });
}

How Events Work

Events are real-time data streams from the glasses. When you subscribe, your callback receives data as it happens.

Common Events

Voice Transcription

Real-time speech-to-text:
session.events.onTranscription((data) => {
  if (data.isFinal) {
    // Final transcription - confident result
    session.layouts.showTextWall(`You said: ${data.text}`);
  } else {
    // Interim results - still processing
    session.layouts.showTextWall(`${data.text}...`);
  }
});
Transcription data:
FieldTypeDescription
textstringTranscribed text
isFinalbooleanTrue if transcription is complete
languagestringLanguage code (e.g., ‘en-US’)
confidencenumberConfidence score (0-1)
Common pattern - wait for final:
session.events.onTranscription((data) => {
  // Ignore interim results
  if (!data.isFinal) return;

  // Process final transcription
  const command = data.text.toLowerCase();
  if (command.includes('help')) {
    this.showHelp(session);
  }
});

Button Presses

Hardware button events:
session.events.onButtonPress((data) => {
  switch (data.button) {
    case 'forward':
      this.nextPage(session);
      break;
    case 'back':
      this.previousPage(session);
      break;
    case 'select':
      this.selectItem(session);
      break;
  }
});
Button data:
FieldTypeDescription
buttonstringButton name (‘forward’, ‘back’, ‘select’, etc.)
actionstring’press’, ‘release’, ‘long_press’
timestampnumberWhen button was pressed

Head Position

Detect when user looks up/down:
session.events.onHeadPosition((data) => {
  if (data.position === 'up') {
    // User looking up - show dashboard
    session.dashboard.content.writeToMain('Status: Active');
  } else if (data.position === 'down') {
    // User looking down
    session.dashboard.content.writeToMain('');
  }
});

Location Updates

GPS coordinates:
session.location.subscribeToStream({ accuracy: 'high' }, (data) => {
  session.logger.info('Location:', data.lat, data.lng);
  session.layouts.showTextWall(`Lat: ${data.lat}\nLng: ${data.lng}`);
});
Location data:
FieldTypeDescription
latnumberLatitude
lngnumberLongitude
accuracynumberAccuracy in meters
altitudenumberAltitude in meters (optional)
timestampnumberWhen location was captured

Phone Notifications

Notifications from user’s phone:
session.events.onPhoneNotification((data) => {
  session.logger.info('Notification:', data.app, data.title);

  // Filter important notifications
  if (data.app === 'Messages' || data.app === 'Slack') {
    session.dashboard.content.writeToMain(`${data.app}\n${data.title}`);
  }
});
Notification data:
FieldTypeDescription
appstringApp that sent notification
titlestringNotification title
textstringNotification body
timestampnumberWhen notification arrived

Battery Updates

Monitor battery levels:
session.events.onGlassesBatteryUpdate((data) => {
  if (data.percentage < 20) {
    session.dashboard.content.writeToMain(`⚠️ Battery: ${data.percentage}%`);
  }
});

Event Reference Table

EventRequires PermissionData Type
onTranscription()MICROPHONETranscriptionData
onTranslation()MICROPHONETranslationData
onButtonPress()NoneButtonPress
onHeadPosition()NoneHeadPosition
onPhoneNotification()READ_NOTIFICATIONSPhoneNotification
onNotificationDismissed()READ_NOTIFICATIONSNotificationDismissed
onGlassesBatteryUpdate()NoneGlassesBatteryUpdate
onPhoneBatteryUpdate()NonePhoneBatteryUpdate
onGlassesConnectionState()NoneGlassesConnectionState
onVAD()MICROPHONEVad
onAudioChunk()MICROPHONEArrayBuffer

Unsubscribing

All event methods return an unsubscribe function:
protected async onSession(session: AppSession, sessionId: string, userId: string) {
  // Subscribe and store unsubscribe function
  const unsubscribe = session.events.onTranscription((data) => {
    session.logger.info(data.text);
  });

  // Later, stop listening
  setTimeout(() => {
    unsubscribe();
    session.logger.info('Stopped listening to transcription');
  }, 60000); // After 1 minute
}
Conditional subscriptions:
protected async onSession(session: AppSession, sessionId: string, userId: string) {
  let transcriptionActive = true;
  const unsubscribe = session.events.onTranscription((data) => {
    if (!transcriptionActive) return;
    session.layouts.showTextWall(data.text);
  });

  // Disable on button press
  session.events.onButtonPress((data) => {
    if (data.button === 'select') {
      transcriptionActive = false;
      unsubscribe();
      session.layouts.showTextWall('Voice disabled');
    }
  });
}

Common Patterns

Command Processing

protected async onSession(session: AppSession, sessionId: string, userId: string) {
  session.events.onTranscription((data) => {
    if (!data.isFinal) return;

    const text = data.text.toLowerCase();

    if (text.includes('weather')) {
      this.showWeather(session);
    } else if (text.includes('time')) {
      this.showTime(session);
    } else if (text.includes('help')) {
      this.showHelp(session);
    } else {
      session.layouts.showTextWall(`Unknown command: ${data.text}`);
    }
  });
}

private async showWeather(session: AppSession) {
  session.layouts.showReferenceCard('Weather', 'Sunny, 72°F');
}

Voice + Button Navigation

class NavigationApp extends AppServer {
  private currentPage = 0;
  private pages = ['Home', 'Settings', 'About'];

  protected async onSession(session: AppSession, sessionId: string, userId: string) {
    this.showCurrentPage(session);

    // Navigate with buttons
    session.events.onButtonPress((data) => {
      if (data.button === 'forward') {
        this.currentPage = (this.currentPage + 1) % this.pages.length;
        this.showCurrentPage(session);
      } else if (data.button === 'back') {
        this.currentPage = (this.currentPage - 1 + this.pages.length) % this.pages.length;
        this.showCurrentPage(session);
      }
    });

    // Navigate with voice
    session.events.onTranscription((data) => {
      if (!data.isFinal) return;

      const text = data.text.toLowerCase();
      if (text.includes('next')) {
        this.currentPage = (this.currentPage + 1) % this.pages.length;
        this.showCurrentPage(session);
      }
    });
  }

  private showCurrentPage(session: AppSession) {
    session.layouts.showTextWall(this.pages[this.currentPage]);
  }
}

Real-Time Data Processing

protected async onSession(session: AppSession, sessionId: string, userId: string) {
  let wordCount = 0;
  let sentenceCount = 0;

  session.events.onTranscription((data) => {
    if (data.isFinal) {
      // Count words and sentences
      const words = data.text.split(/\s+/).length;
      const sentences = data.text.split(/[.!?]+/).length;

      wordCount += words;
      sentenceCount += sentences;

      // Show stats
      session.dashboard.content.writeToMain(
        `Words: ${wordCount} | Sentences: ${sentenceCount}`
      );
    }
  });
}

Notification Filtering

protected async onSession(session: AppSession, sessionId: string, userId: string) {
  const importantApps = ['Messages', 'Slack', 'Email'];

  session.events.onPhoneNotification((data) => {
    // Only show notifications from important apps
    if (importantApps.includes(data.app)) {
      session.layouts.showDoubleTextWall({
        topText: `📱 ${data.app}`,
        bottomText: data.title
      });

      // Play sound
      session.audio.playTTS(`New ${data.app} notification`);
    }
  });
}

Location-Based Features

protected async onSession(session: AppSession, sessionId: string, userId: string) {
  const targetLat = 37.7749;
  const targetLng = -122.4194;

  session.location.subscribeToStream({ accuracy: 'high' }, (data) => {
    // Calculate distance to target
    const distance = this.calculateDistance(
      data.lat, data.lng,
      targetLat, targetLng
    );

    // Update display
    session.layouts.showTextWall(`Distance: ${distance.toFixed(2)} km`);

    // Alert when close
    if (distance < 0.1) { // Within 100m
      session.audio.playTTS('You have arrived at your destination');
    }
  });
}

private calculateDistance(lat1: number, lng1: number, lat2: number, lng2: number): number {
  // Haversine formula
  const R = 6371; // Earth radius in km
  const dLat = (lat2 - lat1) * Math.PI / 180;
  const dLng = (lng2 - lng1) * Math.PI / 180;
  const a = Math.sin(dLat/2) * Math.sin(dLat/2) +
            Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) *
            Math.sin(dLng/2) * Math.sin(dLng/2);
  return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
}

Best Practices

Always check isFinal to avoid processing interim results:
// ✅ Good
session.events.onTranscription((data) => {
  if (!data.isFinal) return;
  processCommand(data.text);
});

// ❌ Avoid - processes every interim result
session.events.onTranscription((data) => {
  processCommand(data.text); // Called too often
});
Clean up subscriptions to avoid memory leaks:
// ✅ Good
const cleanup = session.events.onTranscription(() => {});
// Later: cleanup();

// ❌ Avoid - subscription never cleaned up
session.events.onTranscription(() => {}); // Leaks memory
Events requiring permissions won’t fire without them:
// ✅ Good - assume permissions are set in dev console
session.events.onTranscription((data) => {
  // This only fires if MICROPHONE permission granted
});

// No need to check permissions in code
// Set them in Developer Console instead
Some events fire very frequently:
// ✅ Good - throttle frequent events
let lastUpdate = 0;
session.events.onVAD((data) => {
  const now = Date.now();
  if (now - lastUpdate > 1000) { // Once per second max
    session.dashboard.content.writeToMain(data.isVoiceDetected ? '🎤' : '');
    lastUpdate = now;
  }
});

Permissions Required

Different events require different permissions:
PermissionEvents Unlocked
MICROPHONETranscription, Translation, VAD, Audio Chunks
LOCATIONLocation Updates
READ_NOTIFICATIONSPhone Notifications, Notification Dismissed
CAMERAPhoto Taken, Stream Status
NoneButton Press, Head Position, Battery, Connection State
Set permissions in the Developer Console. Users approve them when installing your app.

Next Steps