Skip to main content
Omi Banner
Omi is the world’s most advanced open-source AI wearable platform. Our mission is to build an open ecosystem for seamless communication, and that includes enabling a wide range of hardware. This guide will walk you through integrating third-party wearable devices—like Plaud AI, Limitless, or your own custom hardware—into the Omi app. By doing so, you can leverage Omi’s powerful features, including high-quality transcription, conversation memory, and a growing app marketplace, for any device.

The Integration Workflow

Integrating a new device involves two main phases:

1. Reverse Engineering

Understanding how the device communicates. This typically means capturing and analyzing its Bluetooth Low Energy (BLE) traffic to decode its protocol for commands and data streaming.

2. Software Integration

Writing code within the Omi mobile app to manage the connection, communication, and data processing for the new device.
This guide focuses on devices that stream audio data, but the principles apply to other data types as well.

Prerequisites

Before you begin, ensure you have the following:

The Hardware

The third-party device you want to integrate

Android Phone

Highly recommended for superior BLE traffic capturing capabilities

Wireshark

Essential tool for analyzing captured network traffic

Omi App Codebase

A local development setup of the Omi app

Technical Knowledge Required

SkillLevelDescription
BLE ConceptsBasicServices, Characteristics, UUIDs
Dart/FlutterIntermediateFor app integration
PythonOptionalFor writing verification scripts

Part 1: Reverse Engineering the Device Protocol

Your first goal is to become a detective. You need to learn the device’s language, which for most wearables is spoken over Bluetooth Low Energy (BLE).

Step 1.1: Capture BLE Traffic

The most effective way to learn the protocol is to capture the communication between the device and its official app.

Step 1.2: Analyze Traffic in Wireshark

With your log file open, it’s time to find the important packets.

Filter by Device

Find your device’s address and apply a display filter to isolate its traffic

Look for Patterns

For audio streaming, look for large numbers of similar-sized packets sent rapidly

Inspect Packet Details

Look for GATT Service UUID, Characteristic UUID, and raw data payload

Map the Services

Create a “map” of Service UUIDs, Characteristic UUIDs, and Data Formats
Key Information to Find:
  • Service UUIDs: High-level containers (e.g., “Audio Service”, “Device Information Service”)
  • Characteristic UUIDs: Specific data endpoints (e.g., “Audio Stream Data”, “Battery Level”)
  • Data Format: Encoding of the payload (e.g., Opus, PCM, µ-law, AAC)

Step 1.3: Decode the Data Payload

The data payload is a hexadecimal string. Your task is to figure out its structure.
Let’s say you capture several 240-byte data packets. You notice the first byte is always b8, and this byte reappears every 40 bytes within the same packet.This is a strong clue! The Opus audio codec uses a Table of Contents (TOC) byte at the start of each frame. The repeating b8 byte suggests the packet contains six 40-byte Opus frames.
CodecDescription
OpusHigh-quality, low-latency (most common in modern devices)
PCMUncompressed audio (16-bit typical)
µ-lawCompressed 8-bit audio (telephony standard)
AACAdvanced Audio Coding (Apple devices)

Step 1.4: Verify Your Findings

Before integrating, write a small standalone script to confirm your assumptions.
# Example verification using Python and Bleak import asyncio from bleak import BleakClient  DEVICE_MAC = "XX:XX:XX:XX:XX:XX" AUDIO_CHAR_UUID = "your-characteristic-uuid"  async def main():  async with BleakClient(DEVICE_MAC) as client:  def callback(sender, data):  # Decode and verify audio data  print(f"Received {len(data)} bytes")   await client.start_notify(AUDIO_CHAR_UUID, callback)  await asyncio.sleep(10) # Record for 10 seconds  asyncio.run(main()) 
If you can decode and play back the audio as a .wav file, you’ve cracked the code!

Part 2: Integrating with the Omi App

Now, let’s integrate your device into the Omi app’s modular architecture.

Understanding Omi’s Device Architecture

ComponentLocationPurpose
DeviceConnection.../device_connection.dartAbstract class defining the standard interface for all devices
DeviceTransport.../transports/ble_transport.dartLow-level BLE communication handler
DeviceConnectionFactory.../device_connection.dartConstructs the correct connection object based on DeviceType

Implementation Steps

Add a New DeviceType

Open app/lib/backend/schema/bt_device/bt_device.dart and add your device:
enum DeviceType {  omi,  openglass,  frame,  appleWatch,  plaud,  xyz, // Add your new device type here } 
Also update getTypeOfBluetoothDevice function and create a helper (e.g., isXyzDevice) to identify your device during Bluetooth scans.

Create Your Device Connection Class

Create app/lib/services/devices/xyz_connection.dart:
import 'dart:async'; import 'package:omi/backend/schema/bt_device/bt_device.dart'; import 'package:omi/services/devices/device_connection.dart'; import 'package:omi/services/devices/models.dart';  // Define your device's specific UUIDs const String xyzAudioServiceUuid = '0000...'; const String xyzAudioStreamUuid = '0000...'; const String xyzButtonUuid = '0000...';  class XyzConnection extends DeviceConnection {  XyzConnection(super.device, super.transport);   @override  Future<BleAudioCodec> performGetAudioCodec() async {  // Return the audio codec your device uses  return BleAudioCodec.opus;  }   @override  Future<StreamSubscription?> performGetBleAudioBytesListener({  required void Function(List<int> p1) onAudioBytesReceived,  }) async {  // Subscribe to your device's audio characteristic  final stream = transport.getCharacteristicStream(  xyzAudioServiceUuid,  xyzAudioStreamUuid  );  return stream.listen(onAudioBytesReceived);  }   @override  Future<int> performRetrieveBatteryLevel() async {  // Most devices use standard BLE Battery Service  return super.performRetrieveBatteryLevel();  } } 

Register Your Device in the Factory

Open app/lib/services/devices/device_connection.dart and add a new case:
class DeviceConnectionFactory {  static DeviceConnection? create(BtDevice device) {  switch (device.type) {  // ... other cases  case DeviceType.appleWatch:  return AppleWatchDeviceConnection(device, transport);   // Add your new case  case DeviceType.xyz:  return XyzConnection(device, transport);  }  } } 
Refer to app/lib/services/devices/omi_connection.dart for a complete example of a complex device implementation.

Part 3: Testing and Contribution

Testing Your Integration

Test your integration thoroughly within the Omi app:
TestDescription
Discovery & ConnectionCan you successfully discover and connect to the device?
Live TranscriptionDoes real-time transcription work as expected?
Battery LevelIs the battery level displayed correctly?
StabilityIs the connection stable? Does it handle reconnection gracefully?

Troubleshooting Common Issues

  • Double-check your Service and Characteristic UUIDs
  • Ensure the device is not connected to its official app or another phone
  • Verify BLE permissions are granted in the app
  • Your BleAudioCodec in performGetAudioCodec is likely incorrect
  • Verify the codec and its parameters (sample rate, bit depth)
  • Check if there’s a header to strip from audio packets
  • Confirm you are subscribing to the correct characteristic for notifications
  • Check in Wireshark if the device is actually sending data after connection
  • Verify the characteristic supports notifications (check properties)

Contributing Your Work

Omi is built by the community. If you’ve integrated a new device, we strongly encourage you to contribute it back!
1

Check the Contribution Guide

Review our Contribution Guide for code standards and PR process.
2

Open a Pull Request

Submit your integration to the Omi GitHub repository.
3

Join the Community

Discuss your integration with the team and community on Discord.
We offer paid bounties for specific features and integrations. Check them out!