- Fix BeaconShardPool: replace all 64 UUIDs to match Android + migration.sql (iOS had a completely different set of UUIDs — would cause shard resolution failures) - Add frames 3-6 disable to write sequence (16 → 24 steps, matches Android's DXSmartProvisioner.writeBeaconConfig() exactly) - Add CLAUDE.md with full project docs, API endpoints, protocol reference Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
150 lines
6 KiB
Markdown
150 lines
6 KiB
Markdown
# Payfrit Beacon iOS
|
|
|
|
Native Swift iOS app for provisioning iBeacon hardware at restaurant tables. Repo: `payfrit-beacon-ios` on Forgejo.
|
|
|
|
## Purpose
|
|
|
|
Utility app for restaurant setup staff to:
|
|
1. Scan for nearby BLE iBeacons
|
|
2. Check UUID against known bulk manufacturer defaults (ban list)
|
|
3. Assign table names to beacons (smart-incremented)
|
|
4. Save beacon + auto-create service point records via API
|
|
|
|
## Environment
|
|
|
|
- **Server**: dev.payfrit.com (always dev — this is a setup tool)
|
|
- **API Base**: `https://dev.payfrit.com/api`
|
|
- **IS_DEV**: Driven by `DEV` compiler flag (orange DEV banner on all screens)
|
|
- **OTP**: Dev server uses magic OTP (no Twilio)
|
|
|
|
## Database (dev schema — migrated Jan 2026)
|
|
|
|
Connected via API, not direct SQL. **Dev DB uses clean, unprefixed names.** PKs are always `ID`. FKs reference their parent table.
|
|
|
|
| Table | PK | Key Columns |
|
|
|-------|----|-------------|
|
|
| Beacons | `ID` | `UUID`, `Name`, `BusinessID`, `IsActive` |
|
|
| ServicePoints | `ID` | `BusinessID`, `Name`, `TypeID`, `Code`, `SortOrder`, `IsActive`, `BeaconID` |
|
|
| Businesses | `ID` | `Name`, `ParentBusinessID` |
|
|
|
|
**Note**: `POST /api/beacons/save.php` auto-creates a ServicePoint when saving a beacon.
|
|
|
|
## API Endpoints Used
|
|
|
|
| Method | Endpoint | Auth | Purpose |
|
|
|--------|----------|------|---------|
|
|
| POST | /auth/loginOTP.php | No | Send OTP to phone |
|
|
| POST | /auth/verifyLoginOTP.php | No | Verify OTP, get token |
|
|
| POST | /businesses/list.php | Yes | List user's businesses |
|
|
| POST | /businesses/get.php | Yes | Get single business |
|
|
| POST | /servicepoints/list.php | Yes | List service points for business |
|
|
| POST | /servicepoints/save.php | Yes | Create/update service point |
|
|
| POST | /beacon-sharding/allocate_business_namespace.php | Yes | Allocate UUID+Major shard |
|
|
| POST | /beacon-sharding/get_beacon_config.php | Yes | Get complete beacon config |
|
|
| POST | /beacon-sharding/register_beacon_hardware.php | Yes | Register provisioned device |
|
|
| POST | /beacon-sharding/verify_beacon_broadcast.php | Yes | Verify beacon is broadcasting |
|
|
| POST | /beacon-sharding/resolve_business.php | Yes | Resolve business by UUID+Major |
|
|
| POST | /beacon-sharding/allocate_servicepoint_minor.php | Yes | Auto-assign minor value |
|
|
| GET | /beacons/list_all.php | No | All active beacons (UUID→ID map) |
|
|
| POST | /beacons/lookup.php | No | Lookup beacon assignments by UUID |
|
|
| POST | /beacons/lookupByMac.php | No | Lookup beacon by MAC address |
|
|
| POST | /beacons/list.php | Yes | List beacons for a business |
|
|
| POST | /beacons/save.php | Yes | Create/update beacon + auto-create service point |
|
|
| POST | /beacons/wipe.php | Yes | Wipe/deactivate a beacon |
|
|
|
|
## Build & Deploy
|
|
|
|
Build in Xcode targeting iOS 17+. No local testing — deploy to device for BLE testing.
|
|
|
|
## Project Structure
|
|
|
|
```
|
|
PayfritBeacon/
|
|
├── Api.swift REST client, all API calls, auth token mgmt
|
|
├── BeaconBanList.swift Known bad UUID prefixes (factory defaults)
|
|
├── BeaconProvisioner.swift DX-Smart CP28 GATT provisioner (24-step write sequence)
|
|
├── BeaconScanner.swift iBeacon CLLocationManager scanner
|
|
├── BeaconShardPool.swift 64 Payfrit shard UUIDs (matches Android + migration.sql)
|
|
├── BLEBeaconScanner.swift CoreBluetooth BLE scanner + BeaconType enum
|
|
├── BusinessListView.swift Business selection screen
|
|
├── DebugLog.swift Shared logging singleton
|
|
├── DevBanner.swift Orange "DEV" banner overlay
|
|
├── LoginView.swift OTP login screen
|
|
├── PayfritBeaconApp.swift App entry point
|
|
├── RootView.swift Root navigation
|
|
├── ScanView.swift Main provisioning hub
|
|
├── ServicePointListView.swift Service point list + beacon assignment
|
|
└── UUIDFormatting.swift UUID string formatting extensions
|
|
```
|
|
|
|
## DX-Smart CP28 Provisioning Protocol
|
|
|
|
### BLE Service & Characteristics
|
|
|
|
- **Service**: `0000FFE0-0000-1000-8000-00805F9B34FB`
|
|
- **FFE1** (Notify): Response notifications (RX)
|
|
- **FFE2** (Write): Command TX
|
|
- **FFE3** (Write): Password authentication
|
|
|
|
### Packet Format
|
|
|
|
`[4E][4F][CMD][LEN][DATA...][XOR_CHECKSUM]`
|
|
|
|
Header: `4E 4F` (fixed). Checksum: XOR of CMD ⊕ LEN ⊕ DATA bytes.
|
|
|
|
### Command Codes
|
|
|
|
| Code | Purpose |
|
|
|------|---------|
|
|
| 0x11-0x16 | Select frame 1-6 |
|
|
| 0x60 | Save config to flash |
|
|
| 0x61 | Set frame as device info |
|
|
| 0x62 | Set frame as iBeacon |
|
|
| 0x71 | Write device name (max 20 ASCII) |
|
|
| 0x74 | Write UUID (16 bytes BE) |
|
|
| 0x75 | Write Major (2 bytes BE) |
|
|
| 0x76 | Write Minor (2 bytes BE) |
|
|
| 0x77 | Write RSSI@1m (1 byte signed) |
|
|
| 0x78 | Write adv interval (1 byte, x100ms) |
|
|
| 0x79 | Write TX power (1 byte, 0-7 index) |
|
|
| 0xA0 | Trigger off |
|
|
| 0xFF | Disable frame (no data) |
|
|
|
|
### 24-Step Write Sequence
|
|
|
|
1. DeviceName (0x71) → Frame1 select (0x11) → Frame1 type (0x61) → Frame1 RSSI/AdvInt/TxPow
|
|
2. Frame2 select (0x12) → Frame2 iBeacon (0x62) → UUID/Major/Minor/RSSI/AdvInt/TxPow
|
|
3. TriggerOff (0xA0)
|
|
4. Disable frames 3-6 (select 0x13-0x16 + 0xFF for each)
|
|
5. SaveConfig (0x60)
|
|
|
|
### Authentication
|
|
|
|
Passwords tried in order: `555555`, `dx1234`, `000000`. Written to FFE3 with `.withResponse`.
|
|
|
|
## Ban List UUIDs
|
|
|
|
Hardcoded in `BeaconBanList.swift`. Known factory-default prefixes:
|
|
- `E2C56DB5` — Apple AirLocate sample
|
|
- `B9407F30` — Estimote default
|
|
- `FDA50693` — Generic Chinese bulk default
|
|
- `F7826DA6` — Kontakt.io default
|
|
- `74278BDA` — Generic bulk default
|
|
- `00000000` / `FFFFFFFF` — Unconfigured hardware
|
|
|
|
## Dependencies
|
|
|
|
- SwiftUI + Combine
|
|
- CoreBluetooth (BLE GATT operations)
|
|
- CoreLocation (CLBeaconRegion for iBeacon ranging)
|
|
- Security (Keychain for auth token storage)
|
|
|
|
## App Flow
|
|
|
|
1. **Login** → OTP (Keychain-saved token for re-auth)
|
|
2. **Select Business** → Auto-select if one, list if multiple
|
|
3. **Select Service Point** → Table assignment target
|
|
4. **Scan** → BLE scan → show results with status badges
|
|
5. **Provision** → Connect → Auth → Write 24-step config → Save
|
|
6. **Register** → API call to register hardware
|
|
7. **Repeat** or **Done**
|