# tinyble — tiny-tech's Bluetooth helper (macOS)

Spike C2 of the BLE-MESH lane (spec: tinyai-id `docs/ble-mesh/SPEC.md`). A ~280 KB Swift binary because CoreBluetooth is the only stable BLE stack on macOS (noble = central only, bleno dead on macOS ≥ 12); a bare binary inherits the TCC Bluetooth grant of the terminal that launches it.

```
swiftc -O -o tinyble main.swift TinyBeaconV2.swift        # ~60 s; tiny-tech's postinstall does this when Xcode CLT exist
tinyble scan [--seconds N] [--all] [--dupes]              # JSONL: tiny beacons (v2 + v1) or everything with --all
tinyble advertise --name "My Mac" [--social] [--handle h] [--device-id id] [--tools tools.json]
tinyble catalog <peripheral-uuid>                         # connect (no pairing) → identity, tools, endpoint as one JSON
tinyble version
```

`TinyBeaconV2.swift` is a source mirror of the iOS codec (`./sync-check.sh` diffs it against `~/tinyai-id/ios/Tiny/Sources/TinyBeaconV2.swift`).

## Proof log (2026-09-21, Mac mini M-series, macOS 26.4.1)
- `scan --seconds 6 --all` → 169 sightings / 6 s, `state 5 poweredOn` immediately, no prompt (terminal already granted).
- `advertise --name "Çağatay's Mac mini" --social --handle cagataycali --tools mac-tools.json` →
  `{"event":"advertising","name":"tn2.EAVnDlEIeknF.Dnox.Çağat","session":18810,"mine":"Dnox","tools":8}` then `{"event":"on_air"}` — the display tail is truncated to the 29-byte budget by design (full name is served by the Catalog).
- Reception is verified from the iPhone (Bluetooth.scanSummary lists `tn2.` peers as "a tiny Mac [darwin-arm64 · 8 tools · near]") — see the lane's OWNER-BRIEF for the on-device run.

## Linux (BlueZ) — `linux/tiny_beacon.py`

Zero-dependency Python for Reachy Mini, Raspberry Pi, Arduino UNO Q, Thor (Jetson) — any node running tiny-tech.
Since 0.2.0 it is **advertiser + Tiny Catalog GATT server** (SPEC §3) over BlueZ's D-Bus API, as a plain user:

```
python3 tiny_beacon.py --kind linux --tools-from daemon --fetch-secret      # Thor / Reachy / Pi: adv + Catalog
python3 tiny_beacon.py --kind q-brain --tools 8 --via bluetoothctl          # UNO Q: advertising only (v0.1 path)
python3 tiny_beacon.py --encode --kind linux --tools 34 --catalog           # AD bytes, no radio
```

- `--via dbus` (auto when `python3-dbus` + `python3-gi` import): `LEAdvertisingManager1` carries the FULL mfg AD
  (`0xFFFF 'TN' + header + mine tail`, flags.catalog set) and `GattManager1` serves identity / tools (paged ≤500 B,
  write 1 byte = page) / hello (32-entry ring, `hello_first` gate only when `--social`) / endpoint.
- `--tools-from daemon` = the running tiny-tech daemon's roster from its mesh file registry
  (`$TMPDIR/tiny/mesh_registry.json`, freshest live row) — real names and count, re-read every 5 min.
- `identity.name` = the enrolled device name from `~/.tiny/device.json` (never the hostname — Thor's is the owner's
  first name); the adapter Alias (GAP Device Name) is aligned to it unless `--keep-alias`. `handle` only with `--social`.
- **BlueZ facts (measured on Thor, Ubuntu 24.04, bluetoothd 5.72, btmon):** `MinInterval`/`MaxInterval` are only
  honoured with `Experimental = true` in `/etc/bluetooth/main.conf` (else the kernel's 1280 ms — ~1 sighting/10 s on a
  Mac; with it 250–400 ms ≈ 7 sightings/10 s); scan-response properties (`ScanResponseServiceUUIDs`) exist from 5.80,
  so on 5.72 the Catalog UUID is **not** on air and the FULL mfg + flags.catalog is the whole witness (Mac/phone connect
  by peripheral id; iOS background scan-by-UUID cannot find such a node). The `advertising` event reports both honestly.
- Install: `linux/tiny-beacon.service` (user unit, `%h/.tiny/bin/tiny_beacon.py`). Tests: `python3 linux/test_tiny_beacon.py`
  (fixture parity incl. `linux_catalog_mine_tail` = Thor's on-air bytes) + `test/ble.test.mjs` (a real Thor sighting).
- Real-radio proof from a Mac: `~/.tiny/bin/tinyble scan --seconds 10 --dupes` → `tinyble catalog <id> --hello '{…}'`.
