payfrit-beacon-ios/CLAUDE.md
Koda c193f0abd1 feat: full parity with Android beacon provisioner
- 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>
2026-03-22 17:40:43 +00:00

6 KiB

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