Spams Bluetooth connection attempts at Meta glasses until they accept one. Runs on a Raspberry Pi as a single binary with no runtime dependencies.
Built for glasses that are flaky to connect to. No backoff, no retry limit, no giving up. If the glasses are around, zucchini keeps trying until they answer.
- Scans for nearby Bluetooth devices.
- Matches them against known Meta glasses signatures. The match is by Bluetooth SIG company ID, which is stable across MAC randomization, so it survives firmware changes and covers new pairs without a code change.
- Once a device matches, fires connection attempts back-to-back from several workers at once. Failures do not slow the next attempt.
- If a connection holds, workers go idle and watch for it to drop.
- If it drops, hammering resumes immediately.
- If the signature stops broadcasting for a grace period, zucchini stops hammering and goes back to scanning. When the glasses return, so does the hammering.
- If BlueZ itself dies, zucchini exits and systemd restarts it. If the adapter loses its discovery session (power off, rfkill), zucchini re-arms discovery automatically and keeps hammering.
Multiple devices are handled at once. Each matched device gets its own set of workers.
Shipped in zucchini.json:
| Field | Value | Source |
|---|---|---|
| Company ID | 0x0D53 |
Luxottica (Ray-Ban Meta, Oakley Meta) |
| Company ID | 0x01AB |
Meta Platforms |
| Company ID | 0x058E |
Meta Platforms Technologies |
| Service UUID | 0xFD5F |
Oculus VR |
Name patterns (Ray-Ban, Oakley Meta, Meta Glasses) are a weak fallback.
Company ID is the reliable signal.
A device matches if it hits any configured field. To target a new device family,
add a row to signatures in the config. No rebuild needed.
Company IDs and service UUIDs are normalized before comparison: company IDs
take hex with or without the 0x prefix (0x0D53 or 0d53), and service
UUIDs accept 16-bit shorts (0xFD5F), 32-bit shorts, and full 128-bit forms,
dashed or not.
zucchini.json:
{
"adapter": "hci0",
"grace_seconds": 15,
"workers": 4,
"call_timeout_ms": 5000,
"attempt_gap_ms": 20,
"signatures": [
{
"name": "meta-glasses",
"company_ids": ["0x0D53", "0x01AB", "0x058E"],
"service_uuids": ["0xFD5F"],
"name_patterns": ["Ray-Ban", "Oakley Meta", "Meta Glasses"]
}
]
}| Setting | Default | Max | Meaning |
|---|---|---|---|
adapter |
hci0 |
- | Adapter basename, matched exactly and case-sensitively (e.g. hci0, not HCI0 or ci0). Empty or omitted uses the default hci0. |
grace_seconds |
15 |
3600 |
How long the signature can go unseen before zucchini stops hammering |
workers |
4 |
16 |
Concurrent connection attempts per device |
call_timeout_ms |
5000 |
60000 |
How long one connection attempt may block before giving up |
attempt_gap_ms |
0 |
60000 |
Delay between attempts. Zero means no pause. This is not a backoff and it never grows. |
Values above the max are refused at startup. Unknown keys and trailing data
after the JSON object are refused too — a typo like grace_second is a hard
error, not a silent default.
go build -o zucchini ./cmd/zucchiniCross-compile for a Pi from another machine:
GOOS=linux GOARCH=arm64 go build -o zucchini ./cmd/zucchini # Pi 3/4/5 (64-bit)
GOOS=linux GOARCH=arm GOARM=7 go build -o zucchini ./cmd/zucchini # 32-bitThe binary is statically linked Go. The Pi needs BlueZ installed and running, which any Raspberry Pi OS with Bluetooth has.
scp zucchini pi@your-pi:/usr/local/bin/zucchini
scp zucchini.json pi@your-pi:/etc/zucchini.json
scp zucchini.service pi@your-pi:/etc/systemd/system/
ssh pi@your-pi 'sudo systemctl enable --now zucchini'Follow the logs:
ssh pi@your-pi 'journalctl -u zucchini -f'| Message | Meaning |
|---|---|
config: ... |
Startup refused: bad config (unknown key, value over the max, or trailing data). zucchini exits. |
seed devices: ... |
Seeding already-known devices failed. Non-fatal; hammering continues on what the event stream reports. |
target acquired: ... |
A matching device was found and hammering began |
connect failed: ... |
One attempt failed. Normal while hammering. |
target released: ... |
Hammering stopped. Either the grace window expired or the device left. |
discovery stopped on <adapter>, rediscovering |
The adapter lost its discovery session (e.g. powered off). zucchini retries every tick. |
discovery restored on <adapter> |
Re-arming succeeded. Hammering continues. |
run: org.bluez disappeared from the system bus |
bluetoothd died. zucchini exits so systemd restarts it. |
restart discovery: ... |
A re-arm attempt failed. |
stop discovery: ... |
Stopping discovery on shutdown failed. Logged, best-effort; zucchini is exiting anyway. |
target acquired, target released, connect failed, and restart discovery
are each rate-limited to one line per second (connect failed per device) so a
burst of spoofed adverts cannot fill the journal.
If you see connect failed repeating and never target acquired followed by a
held connection, the glasses are refusing the Pi. See the risk note below.
go test ./...
go test ./... -raceThe retry loop is tested against a fake Bluetooth backend rather than a real radio, so the state machine is covered without hardware: starting and stopping hammering, idling while connected, resuming after a drop, releasing after the grace window, and issuing concurrent attempts. The BlueZ message parsing is tested against synthetic signals with no live bus involved.
It is not yet confirmed that Meta glasses accept a connection from a non-phone device. They are designed as a phone accessory, and the initial pairing may be gated by Meta's app. If pairing is refused, zucchini will hammer forever and connect nothing.
zucchini doubles as the test rig for that question. If it runs and the glasses eventually connect, the risk is cleared. If it does not, that is a hard answer about the hardware, not a bug in this tool.
Phase one: connection loop only. Audio playback is deliberately out of scope for now and may come later.
zucchini is Copyright (c) 2026 Blaine Motsinger under the MIT license.