# Tybe Zephyr/NCS BLE + MPSL timeslot bridge

Next-generation bridge firmware for Seeed XIAO nRF52840 Sense.

Goal:

```text
Mac/iPhone BLE client
  -> Tybe GATT Text Input write
  -> Zephyr/NCS BLE peripheral stays connected
  -> frame queue
  -> MPSL radio timeslot
  -> raw nRF RADIO packet to existing Tybe dongle
  -> optional dongle ACK in same timeslot
  -> Tybe Status notify
```

This is the new live BLE bridge target. It replaces the Arduino/Bluefruit
timeslot bridge for the BLE live keyboard path. Bridge stays connected,
forwards Tybe frames over the 2.4 GHz radio inside MPSL timeslots, and notifies
the Tybe Status characteristic per frame.

Current status:

- BLE peripheral up; advertises as `Tybe Zephyr Dev`.
- Frame write -> queue -> MPSL timeslot -> raw radio TX -> Status notify works.
- TX completion is treated as success (`WAIT_FOR_DONGLE_ACK = 0`); bridge-side
  ACK RX inside the same timeslot is unreliable on this stack.
- Tested with `tybe-send --name "Tybe Zephyr Dev"` from `clients/swift-shared`.

## Status LED

The XIAO nRF52840 onboard RGB LED reports bridge state at a glance. One
color shows at a time:

| LED | Meaning |
|---|---|
| Slow blue blink (~1 Hz) | Powered on, advertising, no BLE client connected |
| Solid blue | BLE client connected, dongle not linked yet |
| Solid green | BLE client connected **and** dongle linked — ready |
| Fast red blink | A frame error occurred in the last ~0.7 s |

On a solid color, each forwarded text frame produces a brief dark blip so
typing activity is visible. Heartbeat pings do not blip. "Dongle linked"
follows the same sticky window as the Tybe Status `dongle linked` flag
(`DONGLE_LINK_TIMEOUT_MS`), so green tracks the client-side Bridge pill.

## Prereqs

NCS was initialized locally at:

```text
~/ncs/v2.7.0
```

If setting up on a fresh Mac:

```sh
python3 -m pip install --user west
mkdir -p ~/ncs
cd ~/ncs
west init -m https://github.com/nrfconnect/sdk-nrf --mr v2.7.0 v2.7.0
cd ~/ncs/v2.7.0
west update --narrow -o=--depth=1
west zephyr-export
python3 -m pip install --user -r zephyr/scripts/requirements-base.txt -r zephyr/scripts/requirements-build-test.txt -r nrf/scripts/requirements-build.txt
brew install arm-none-eabi-gcc
```

## Build

```sh
firmware/bridge-zephyr/build.sh
```

Default board:

```text
xiao_ble/nrf52840/sense
```

Override with:

```sh
BOARD=xiao_ble/nrf52840 firmware/bridge-zephyr/build.sh
```

## Flash

The build produces:

```text
firmware/bridge-zephyr/build/zephyr/zephyr.uf2
```

The XIAO nRF52840 ships with a UF2 bootloader. To flash:

1. Double-tap the reset button on the XIAO. A volume named `XIAO-SENSE`
   appears.
2. Copy the UF2 onto it:

   ```sh
   cp firmware/bridge-zephyr/build/zephyr/zephyr.uf2 /Volumes/XIAO-SENSE/
   ```

   The board reboots and starts running the new firmware. It then advertises
   over BLE as `Tybe Zephyr Dev`.

If flashing via SWD probe instead:

```sh
cd ~/ncs/v2.7.0
west flash -d /Users/dongookson/work/tybe/firmware/bridge-zephyr/build
```

## Test

From the repo root:

```sh
cd clients/swift-shared
swift run tybe-send --name "Tybe Zephyr Dev" --timeout 5 "hello"
```

Expected:

- dongle types `hello` over USB HID,
- bridge serial logs `forward seq=... err=0 total=...`,
- client prints `status: flags=0x03 seq=0x... error=OK`.

## Protocol compatibility

GATT UUIDs match `docs/protocol.md`:

```text
Service:    74796265-0000-0001-8000-00805F9B34FB
TextInput:  74796265-0010-0001-8000-00805F9B34FB
Status:     74796265-0020-0001-8000-00805F9B34FB
Config:     74796265-0030-0001-8000-00805F9B34FB
Control:    74796265-0040-0001-8000-00805F9B34FB
```

Raw radio settings match the current Arduino dongle:

```text
mode:    Nordic proprietary 2 Mbps
channel: 76
address: E7 E7 E7 E7 E7
CRC:     16-bit, poly 0x11021, init 0xFFFF
packet:  8-bit length + payload
```
