An open, AirTag-style Bluetooth LE finder tag built on the Vicharak ShrikeFi (ESP32-S3 + Renesas ForgeFPGA).
Lose your bag, keys or bike? Clip a ShrikeFi to it. The board continuously advertises over Bluetooth Low Energy; your phone sees it, shows how strong the signal is ("hotter / colder") and can make the tag blink (and beep) so you can find it β just like the "Play Sound" / proximity part of an AirTag.
Important
This is not an Apple AirTag and it does not work with Apple's Find My app or network. It is a local BLE beacon: you can find it only when your own phone is within Bluetooth range (roughly 10β30 m indoors, more outdoors). There is no crowd-sourced, worldwide location. See Limitations.
Built for the Vicharak Fellowship 2026 challenge "can we make an AirTag using ShrikeFi?" β with complete documentation so anyone with a ShrikeFi can reproduce it.
- Features
- Hardware
- Quick start
- Find the tag with your phone
- Last-seen map
- Onboard GPS (roadmap)
- How it works
- Configuration
- Limitations
- Roadmap
- Repository layout
- Credits and license
| v0.1 (this release) | |
|---|---|
| π‘ iBeacon advertising | Your own 128-bit UUID + Major/Minor + calibrated 1 m power, readable by any beacon scanner app |
| π "Find Me" alert | Standard Bluetooth SIG Immediate Alert Service (0x1802) β write 1 (mild) or 2 (high) from a phone and the tag blinks fast / beeps; 0 stops it; auto-stops after 15 s |
| π± Works on Android and iPhone | Tested flow with nRF Connect and LightBlue (free apps) β no custom app needed |
| π‘ Status LED (GPIO21) | heartbeat blink = advertising Β· solid = phone connected Β· fast blink = alert |
| π Optional buzzer | any 3.3 V active buzzer on a spare GPIO |
| π» Laptop finder | tools/find_tag.py β live RSSI bar + distance estimate + alert trigger (Windows / Linux / macOS) |
| πΊοΈ Last-seen map (phone) | companion/ PWA β Web Bluetooth + phone GPS pins on a Leaflet map (Android Chrome; not Find My / no onboard GPS) |
| π οΈ Beginner-friendly build | Arduino IDE or PlatformIO, one config file |
You need only: a ShrikeFi board and a USB-C data cable (charge-only cables will not flash). Full list in docs/BOM.md.
| ShrikeFi feature | Used for |
|---|---|
| ESP32-S3 (dual-core LX7, 240 MHz) | runs the firmware |
| Bluetooth LE 5.0 radio (on-chip) | beacon advertising + GATT alert service |
| MCU user LED β GPIO21, active high | status / alert indicator |
| Dual USB-C (CH9102 USB-UART + native USB) | power, flashing, serial log |
| Renesas SLG47910 ForgeFPGA | not used in v0.1 β see Roadmap |
| Optional BMS / LiPo charger (DNP on base board) | not required β power from USB-C or a power bank |
Board docs: Hardware overview Β· Pinouts Β· Getting started
Full step-by-step guide with screenshots-to-take and troubleshooting: docs/HOWTO.md.
- Install Arduino IDE 2.x.
- File β Preferences β Additional boards manager URLs, add (same as Vicharak's guide):
https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json - Tools β Board β Boards Manager β install "esp32" by Espressif Systems (3.x).
- Open
firmware/ShrikeFiBleTag/ShrikeFiBleTag.ino. Editconfig.h(at least generate your ownIBEACON_UUID). - Tools menu:
Setting Value Board ESP32S3 Dev Module (Vicharak's guide calls it "Generic ESP32-S3") Port the ShrikeFi's COM / tty port USB Mode Hardware CDC and JTAG USB CDC On Boot Disabled if plugged into the CH9102 USB-UART port Β· Enabled if plugged into the native ESP32-S3 USB port Flash Size 4MB (safe on every ShrikeFi; firmware is ~0.6 MB) β 8MB as in Vicharak's guide also works if your board has 8 MB Partition Scheme Default Upload Speed 460800 (drop to 115200 if uploads fail) - Click Upload. First time only: if upload fails with "Failed to connect", hold BOOT while plugging in the cable (or hold BOOT, tap RESET, release BOOT), then upload again.
- Open Serial Monitor at 115200 and press RESET β you should see the tag's name, MAC, UUID; the blue LED blinks once a second.
pip install platformio # or install the PlatformIO IDE extension in VS Code
cd firmware
pio run -e shrikefi -t upload # CH9102 USB-UART port
pio device monitor # 115200 baud
# native USB port instead: pio run -e shrikefi_native_usb -t uploadInstall a free BLE scanner β nRF Connect for Mobile (Nordic Semiconductor; Android & iOS) or LightBlue (Punch Through; iOS & Android).
| Android (tested target: OnePlus 11 5G) | iPhone (tested target: iPhone 14 / 15) | |
|---|---|---|
Sees ShrikeFi-Tag by name |
β | β |
| Live RSSI ("hotter / colder") | β nRF Connect RSSI + graph | β RSSI in device list / LightBlue signal view |
| Sees the iBeacon frame (UUID, Major, Minor) | β nRF Connect decodes it | β iOS hides iBeacon packets from Bluetooth apps by design (only Core Location beacon apps that know your UUID can range it) |
| Make the tag blink / beep | β
connect β Immediate Alert β write 01/02 |
β
connect β service 1802 β Alert Level β write 01/02 |
- Power the tag (USB-C to laptop or power bank). LED blinks once per second.
- Open the app β Scan β find ShrikeFi-Tag (use the name filter).
- Find by signal: walk around and watch RSSI. β β50 dBm = right next to you, β70 = same room, β90 = far / behind walls.
- Make it blink: tap Connect β Immediate Alert service (
0x1802) β Alert Level (0x2A06) β write02(high, fast blink) or01(mild) β write00to stop.
Detailed per-app taps, Android permissions (Nearby devices / Location) and calibration: docs/HOWTO.md.
A single-page companion under companion/ records where your phone was when it last saw the tag (Web Bluetooth + geolocation), and shows pins + a short trail on an offline-friendly Leaflet / OpenStreetMap map.
Not Apple Find My. The ShrikeFi has no GPS. Pins are phone-GPS βlast sightingsβ while you are in BLE range β not a crowd-sourced world map.
| Best on | Android Chrome (OnePlus and similar) |
| iPhone | Safari usually lacks Web Bluetooth β keep using LightBlue / nRF Connect for find-me; the map companion is Android-primary |
| How to open | GitHub Pages: geneticscrol.github.io/shrikefi-ble-tag/companion/ Β· or local python -m http.server + adb reverse β see companion/README.md |
| Usage | Connect to tag β auto pins while connected Β· Mark last seen for a manual pin Β· trail stored in the browser (localStorage) |
Step-by-step, screenshots and limitations: docs/HOWTO.md Β§8.
A u-blox NEO-M8N GNSS module will be wired to the ShrikeFi over UART so the tag can store its own lat/lng outdoors (no phone required for the fix). Until that lands:
- Location in the repo today is still phone last-seen via the companion (Web Bluetooth + phone GPS).
- NEO-M8N is outdoor-oriented (needs sky view); indoor find-me stays BLE RSSI + Find Me alert.
- Firmware will read NMEA / u-blox over UART; exact pins and power notes will land with the hardware hookup.
flowchart LR
subgraph ShrikeFi["ShrikeFi (ESP32-S3)"]
CFG[config.h<br/>name, UUID, major/minor] --> FW[Firmware loop]
FW -->|every 1 s swap| A1[Frame 1: iBeacon<br/>non-connectable]
FW -->|every 1 s swap| A2[Frame 2: Find Me<br/>connectable, name + 0x1802]
GATT[GATT server<br/>Immediate Alert 0x1802<br/>Alert Level 0x2A06] --> FW
FW --> LED[GPIO21 LED]
FW -.optional.-> BZ[Active buzzer]
end
A1 -. BLE adv .-> AND[Android<br/>nRF Connect]
A2 -. BLE adv .-> AND
A2 -. BLE adv .-> IOS[iPhone<br/>LightBlue / nRF Connect]
A2 -. BLE adv .-> PC[Laptop<br/>tools/find_tag.py]
AND -- connect + write 01/02 --> GATT
IOS -- connect + write 01/02 --> GATT
PC -- connect + write 01/02 --> GATT
- Two advertising frames, alternated every second.
- iBeacon frame (non-connectable): Apple's iBeacon manufacturer-data layout (
4C 00 02 15+ UUID + Major + Minor + measured power). Beacon apps use it to identify your tag and estimate distance. - Find Me frame (connectable): flags + 16-bit service UUID
0x1802+ complete local name. This is the frame iPhones can see, and the one a phone connects to. Why two? iOS/macOS strip any advertisement containing iBeacon data from normal Bluetooth (CoreBluetooth) apps. Splitting them means every phone sees something.
- iBeacon frame (non-connectable): Apple's iBeacon manufacturer-data layout (
- Distance from RSSI. Phones measure received signal strength (RSSI, dBm). With the 1 m reference (
IBEACON_MEASURED_POWER, default β59 dBm) a rough distance isd β 10^((Pββ β RSSI) / (10Β·n)),n β 2(open air) β¦ 3 (indoors). Treat it as warmer/colder, not metres. - Find Me alert. The firmware hosts the Bluetooth SIG Immediate Alert Service. When a phone writes Alert Level
1/2, the LED (and optional buzzer) switch to a fast pattern for 15 s or until0is written. While a phone is connected the tag keeps advertising the iBeacon frame. - Portable code. Builds on Arduino-ESP32 3.x (NimBLE host) and 2.0.x (Bluedroid); the few API differences are handled in the sketch.
Data flow, packet bytes and design decisions are explained in more depth in docs/HOWTO.md.
Everything lives in firmware/ShrikeFiBleTag/config.h:
| Setting | Default | Meaning |
|---|---|---|
TAG_NAME |
ShrikeFi-Tag |
name shown in scanner apps (β€ 12 chars) |
IBEACON_UUID |
example UUID | replace with your own (uuidgen / python -c "import uuid;print(uuid.uuid4())") |
IBEACON_MAJOR / IBEACON_MINOR |
1 / 1 | tell multiple tags apart |
IBEACON_MEASURED_POWER |
β59 | RSSI at 1 m, calibrate for better distance |
ENABLE_IBEACON_FRAME / ENABLE_FINDME_FRAME |
1 / 1 | turn either frame off |
FRAME_SWITCH_MS |
1000 | how long each frame is advertised |
ADV_INTERVAL_MS |
200 | lower = faster RSSI updates, higher = less power |
LED_PIN |
21 | ShrikeFi MCU LED |
BUZZER_PIN |
β1 | GPIO of an optional active buzzer |
ALERT_DURATION_MS |
15000 | alert auto-stop |
Being honest about what this is (and isn't):
- No Apple Find My / Google Find My Device network. Those networks rely on manufacturer-certified accessories (Apple MFi / Google's Find Hub spec), rotating cryptographic keys and millions of phones relaying encrypted locations. This project does none of that. Community reverse-engineering projects (e.g. OpenHaystack) exist, but they are unofficial, fragile, not first-class on ESP32-S3 and not used or claimed here.
- Range-limited. You can only find the tag when your phone is within BLE range. The tag has no onboard GPS; the
companion/last-seen map uses your phoneβs GPS when the tag is nearby (Android Chrome). There is still no crowd-sourced worldwide location. - RSSI is noisy. Bodies, walls, board orientation and the breadboard (the ShrikeFi antenna is sensitive to nearby metal β discussion) easily swing Β±5β10 dB.
- iPhone can't see the iBeacon frame in LightBlue / nRF Connect (iOS policy). The Find Me frame works fine.
- Privacy. The tag uses a fixed Bluetooth address and fixed UUID, so anyone with a scanner can recognise it. Real AirTags rotate identifiers and include anti-stalking alerts. Only attach this to your own belongings β never use it to track people.
- Power. The base ShrikeFi has the battery charger/BMS not populated, and v0.1 does not use deep sleep, so expect power-bank/USB power rather than coin-cell life.
- Trademarks. "AirTag", "iBeacon" and "Find My" are trademarks of Apple Inc. This project is not affiliated with or endorsed by Apple. The iBeacon packet layout is used here for hobby/educational interoperability.
- FPGA status LED β drive the ForgeFPGA user LED (FPGA GPIO16) with a hardware pattern generator, triggered by the MCU over the FPGAβMCU link (shows why ShrikeFi instead of a bare ESP32).
- Low-power mode β longer advertising interval + light sleep; measure current; LiPo via the optional BMS.
- Link-loss / "left behind" alert (Bluetooth Link Loss Service 0x1803).
- Last-seen map companion (PWA) β
companion/Web Bluetooth + phone GPS pins (Android Chrome) Β· Pages. - NEO-M8N onboard GPS β UART lat/lng outdoors; phone last-seen remains until then (see Onboard GPS).
- Richer companion (Flutter / Capacitor) β UUID filter, hot/cold gauge, richer history UI.
- Eddystone-UID frame option (Google's open beacon format).
- Button on a GPIO to toggle beacon / pairing mode.
shrikefi-ble-tag/
βββ firmware/
β βββ platformio.ini # PlatformIO envs (CH9102 port, native USB, core 2.x)
β βββ ShrikeFiBleTag/
β βββ ShrikeFiBleTag.ino # firmware (open this in Arduino IDE)
β βββ config.h # β your settings
βββ companion/ # last-seen map PWA (Android Chrome)
β βββ index.html / app.js / styles.css
β βββ manifest.webmanifest / sw.js
β βββ README.md # how to open on OnePlus
βββ tools/
β βββ find_tag.py # laptop finder (Python + bleak)
βββ docs/
β βββ HOWTO.md # build, flash, phone test, calibration, troubleshooting
β βββ BOM.md # bill of materials
β βββ screenshots/ # privacy-scrubbed nRF + companion captures
β βββ images/ # photos & screenshots for docs/blog
βββ LICENSE # MIT
- Hardware: Vicharak ShrikeFi β docs at https://vicharak-in.github.io/shrike/, community on Discord.
- Arduino core: espressif/arduino-esp32. Laptop scanner: bleak.
- Author: Saksham Sud (@Geneticscrol), Vicharak Campus Fellow 2026.
Released under the MIT License.