Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion backends/advanced/Docs/plugin-development-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ OMI Device (BLE)
friend-lite-sdk (extras/friend-lite-sdk/)
→ parse_button_event() converts payload → ButtonState IntEnum
BLE Client (extras/local-omi-bt/ or mobile app)
BLE Client (extras/local-wearable-client/ or mobile app)
→ Formats as Wyoming protocol: {"type": "button-event", "data": {"state": "SINGLE_TAP"}}
→ Sends over WebSocket
Expand Down
7 changes: 6 additions & 1 deletion extras/friend-lite-sdk/friend_lite/__init__.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
from .bluetooth import OmiConnection, listen_to_omi, print_devices
from .bluetooth import OmiConnection, WearableConnection, listen_to_omi, print_devices
from .button import ButtonState, parse_button_event
from .neo1 import Neo1Connection
from .uuids import (
NEO1_CTRL_CHAR_UUID,
OMI_AUDIO_CHAR_UUID,
OMI_BUTTON_CHAR_UUID,
OMI_BUTTON_SERVICE_UUID,
)

__all__ = [
"ButtonState",
"NEO1_CTRL_CHAR_UUID",
"Neo1Connection",
"OMI_AUDIO_CHAR_UUID",
"OMI_BUTTON_CHAR_UUID",
"OMI_BUTTON_SERVICE_UUID",
"OmiConnection",
"WearableConnection",
"listen_to_omi",
"parse_button_event",
"print_devices",
Expand Down
22 changes: 16 additions & 6 deletions extras/friend-lite-sdk/friend_lite/bluetooth.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,19 @@ def print_devices() -> None:
print(f"{i}. {d.name} [{d.address}]")


class OmiConnection:
class WearableConnection:
"""Base class for BLE wearable device connections.

Provides connect/disconnect lifecycle, audio subscription, and
disconnect-wait primitives shared by all wearable devices.
"""

def __init__(self, mac_address: str) -> None:
self._mac_address = mac_address
self._client: Optional[BleakClient] = None
self._disconnected = asyncio.Event()

async def __aenter__(self) -> "OmiConnection":
async def __aenter__(self) -> "WearableConnection":
await self.connect()
return self

Expand Down Expand Up @@ -48,12 +54,9 @@ async def disconnect(self) -> None:
async def subscribe_audio(self, callback: Callable[[int, bytearray], None]) -> None:
await self.subscribe(OMI_AUDIO_CHAR_UUID, callback)

async def subscribe_button(self, callback: Callable[[int, bytearray], None]) -> None:
await self.subscribe(OMI_BUTTON_CHAR_UUID, callback)

async def subscribe(self, uuid: str, callback: Callable[[int, bytearray], None]) -> None:
if self._client is None:
raise RuntimeError("Not connected to OMI device")
raise RuntimeError("Not connected to device")
await self._client.start_notify(uuid, callback)

async def wait_until_disconnected(self, timeout: float | None = None) -> None:
Expand All @@ -63,6 +66,13 @@ async def wait_until_disconnected(self, timeout: float | None = None) -> None:
await asyncio.wait_for(self._disconnected.wait(), timeout=timeout)


class OmiConnection(WearableConnection):
"""OMI device with button support."""

async def subscribe_button(self, callback: Callable[[int, bytearray], None]) -> None:
await self.subscribe(OMI_BUTTON_CHAR_UUID, callback)


async def listen_to_omi(mac_address: str, char_uuid: str, data_handler) -> None:
"""Backward-compatible wrapper for older consumers."""
async with OmiConnection(mac_address) as conn:
Expand Down
16 changes: 16 additions & 0 deletions extras/friend-lite-sdk/friend_lite/neo1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from .bluetooth import WearableConnection
from .uuids import NEO1_CTRL_CHAR_UUID


class Neo1Connection(WearableConnection):
"""Neo1 device with sleep/wake control (no buttons)."""

async def sleep(self) -> None:
if self._client is None:
raise RuntimeError("Not connected to device")
await self._client.write_gatt_char(NEO1_CTRL_CHAR_UUID, b"\x00", response=True)

async def wake(self) -> None:
if self._client is None:
raise RuntimeError("Not connected to device")
await self._client.write_gatt_char(NEO1_CTRL_CHAR_UUID, b"\x01", response=True)
9 changes: 6 additions & 3 deletions extras/friend-lite-sdk/friend_lite/uuids.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
"""UUID constants for OMI BLE services and characteristics."""
"""UUID constants for wearable BLE services and characteristics."""

# Standard Omi audio characteristic UUID
# Shared audio characteristic UUID (all devices)
OMI_AUDIO_CHAR_UUID = "19B10001-E8F2-537E-4F6C-D104768A1214"

# Omi button service + characteristic UUIDs
# OMI button service + characteristic UUIDs
OMI_BUTTON_SERVICE_UUID = "23BA7924-0000-1000-7450-346EAC492E92"
OMI_BUTTON_CHAR_UUID = "23BA7925-0000-1000-7450-346EAC492E92"

# Neo1 control characteristic UUID (sleep/wake)
NEO1_CTRL_CHAR_UUID = "19B10003-E8F2-537E-4F6C-D104768A1214"
4 changes: 2 additions & 2 deletions extras/friend-lite-sdk/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[project]
name = "friend-lite-sdk"
version = "0.2.0"
description = "Python SDK for OMI/Friend Lite BLE devices — audio streaming, button events, and transcription"
version = "0.3.0"
description = "Python SDK for OMI/Neo1 BLE wearable devices — audio streaming, button events, and device control"
requires-python = ">= 3.10"
license = "MIT"
dependencies = [
Expand Down
Loading