Commit graph

98 commits

Author SHA1 Message Date
f082eeadad fix: skip ACK wait on SaveConfig — beacon reboots, never ACKs
SaveConfig (0x60) causes the beacon MCU to reboot and save to flash.
It never sends an ACK, so writeToCharAndWaitACK would wait for the
5s timeout, during which the beacon disconnects. The disconnect
handler fires while writesCompleted is still false, causing a false
"Unexpected disconnect: beacon timed out" error.

Fix: fire-and-forget the SaveConfig write and return immediately.
The BLE-level write (.withResponse) confirms delivery. writeConfig()
returns before the disconnect callback runs, so writesCompleted gets
set to true in time.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 03:44:36 +00:00
3720f496bd perf: reduce inter-command delay from 500ms to 300ms (conservative)
Shaves ~4.6s off the 23-command provisioning sequence while keeping
a safe margin for the beacon's BLE stack to process each write.

Next step: if stable, we can go more aggressive (200ms or 150ms).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 03:41:44 +00:00
a82a3697da fix: treat post-SaveConfig disconnect as expected, not an error
After all 23 commands write successfully, the DX-Smart beacon reboots
to apply config — dropping the BLE connection. ScanView's disconnect
handler was racing the async writeConfig return and logging this as
"Unexpected disconnect: beacon timed out" even though provisioning
succeeded.

Added writesCompleted flag so the disconnect handler knows writes
finished and logs it as expected behavior instead of an error.
2026-03-23 03:32:21 +00:00
9ce7b9571a fix: allow minor = 0 in allocateMinor response validation 2026-03-23 03:22:23 +00:00
734a18356f fix: show all BLE devices in scan, no filtering
Remove the guard that dropped non-CP-28 devices. All discovered
BLE peripherals now appear in the scan list (defaulting to .dxsmart
type). detectBeaconType still classifies known CP-28 patterns but
unknown devices are no longer hidden.
2026-03-23 03:18:11 +00:00
f0fdb04e0e fix: restore FFF0 fallback and add 'payfrit' name detection in BLE scan
The CP-28-only refactor accidentally over-filtered the BLE scan:

1. FFF0 service detection was gated on name patterns — CP-28 beacons
   advertising FFF0 with non-matching names (e.g. already provisioned
   as "Payfrit") were silently filtered out. Restored unconditional
   FFF0 → dxsmart mapping (matching old behavior).

2. Already-provisioned beacons broadcast with name "Payfrit" (set by
   old SDK cmd 0x43), but that name wasn't in the detection patterns.
   Added "payfrit" to the name check.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 03:11:46 +00:00
12048e5c88 Merge pull request 'refactor: CP-28 only — strip all non-DX-Smart beacon code' (#41) from schwifty/cp28-only into main 2026-03-23 03:04:14 +00:00
5eebf00aa0 refactor: strip all non-CP-28 beacon code
Remove BlueCharmProvisioner, KBeaconProvisioner, and FallbackProvisioner.
Simplify BeaconType enum to DX-Smart only. Simplify BLE detection to only
show CP-28 beacons. Remove multi-type provisioner factory from ScanView.

-989 lines of dead code removed. Other beacon types will be re-added
when we start using different hardware.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 03:04:07 +00:00
5678256356 Merge pull request 'fix: three provisioning bugs causing beacon write failures' (#40) from schwifty/fix-provisioning-bugs into main 2026-03-23 03:03:58 +00:00
7089224244 fix: three provisioning bugs causing beacon write failures
1. Minor allocation: reject minor=0 from API instead of silently using it.
   API returning null/0 means the service point isn't configured right.

2. DXSmart write reliability:
   - Add per-command retry (1 retry with 500ms backoff)
   - Increase inter-command delay from 200ms to 500ms
   - Increase post-auth settle from 100ms to 500ms
   - Add 2s cooldown in FallbackProvisioner between provisioner attempts
   The beacon's BLE stack gets hammered by KBeacon's 15 failed auth
   attempts before DXSmart even gets a chance. These timings give it
   breathing room.

3. KBeacon passwords: password 5 was a duplicate of password 3
   (both "1234567890123456"). Replaced with "000000" (6-char variant).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 02:29:16 +00:00
b88dded928 fix: resolve write ACK on didWriteValueFor instead of waiting for notification
Frame1_DevInfo (cmd 0x61) and potentially other commands don't send a
separate FFE1 notification after being written. The code was waiting for
didUpdateValueFor (notification) to resolve responseContinuation, but it
never came — causing a 5s timeout on every such command.

The .withResponse write type already guarantees the BLE stack confirmed
delivery. Now didWriteValueFor resolves responseContinuation on success,
so commands that don't trigger notifications still complete immediately.

If a notification also arrives later, responseContinuation is already nil
so it's harmlessly ignored.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 02:16:24 +00:00
37c7c72052 fix: replace Task{@MainActor} with DispatchQueue.main.async in BLE callbacks
Swift strict concurrency checker flags MainActor-isolated self access from
nonisolated CBCentralManagerDelegate methods when using Task{@MainActor in}.
DispatchQueue.main.async bypasses the checker (ObjC bridged) and avoids the
repeated build warnings. Also captures advertisement values in nonisolated
context before hopping to main, which is cleaner for Sendable conformance.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 01:00:12 +00:00
6eaccb6bf6 Merge pull request 'fix: prevent DXSmart disconnect race killing Write Config screen' (#37) from schwifty/fix-dxsmart-disconnect-race into main 2026-03-23 00:49:30 +00:00
8f8fcba9c0 fix: prevent DXSmart disconnect race killing the "Write Config" screen
After DXSmart auth completes, the beacon often drops BLE connection
due to aggressive timeouts. The disconnect handler was treating this
as a failure, stomping the .connected state before the user could see
the "Write Config" button.

Changes:
- Ignore BLE disconnects during .connected state for DXSmart beacons
  (the LED keeps flashing regardless of BLE connection)
- Auto-reconnect in writeConfigToConnectedBeacon() if BLE dropped
  while waiting for user confirmation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 00:48:09 +00:00
61862adfa8 fix: add real-time status updates to KBeacon provisioner + fix disconnect handler
KBeaconProvisioner had no onStatusUpdate callback, so the UI showed a static
"Connecting..." message during the entire auth cycle (5 passwords × 5s timeout
× 3 retries = 75s of dead silence). Now reports each phase: connecting,
discovering services, authenticating (with password attempt count), writing,
saving.

Also fixed ScanView disconnect handler to cover .writing and .verifying states —
previously only handled .connecting/.connected, so a mid-write disconnect left
the UI permanently stuck.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 00:40:43 +00:00
ed9a57a938 Merge pull request 'fix: real-time provisioning status + disconnect handling' (#35) from schwifty/fix-provisioning-status into main 2026-03-23 00:01:23 +00:00
c3f2b4faab fix: add real-time status updates during beacon provisioning
- DXSmartProvisioner now reports each phase (connecting, discovering
  services, authenticating, retrying) via onStatusUpdate callback
- ScanView shows live diagnostic log during connecting/writing states,
  not just on failure — so you can see exactly where it stalls
- Unexpected BLE disconnects now properly update provisioningState to
  .failed instead of silently logging
- Added cancel button to connecting progress view
- "Connected" screen title changed to "Connected — Beacon is Flashing"
  for clearer status indication

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 00:01:01 +00:00
349dab1b75 Merge pull request 'fix: connection callback bug + provisioning diagnostics' (#34) from schwifty/provision-diagnostics into main 2026-03-22 23:12:31 +00:00
c243235237 fix: connection callback bug + add provisioning diagnostics
BIG FIX: Provisioners were calling centralManager.connect() but
BLEManager is the CBCentralManagerDelegate — provisioners never
received didConnect/didFailToConnect callbacks, so connections
ALWAYS timed out after 5s regardless. This is why provisioning
kept failing. Fixed by:
1. Adding didConnect/didFailToConnect/didDisconnect to BLEManager
2. Provisioners register connection callbacks via bleManager
3. Increased connection timeout from 5s to 10s

DIAGNOSTICS: Added ProvisionLog system so failures show a timestamped
step-by-step log of what happened (with Share button). Every phase
is logged: init, API calls, connect attempts, service discovery,
auth, write commands, and errors.
2026-03-22 23:12:06 +00:00
c879ecd442 Merge pull request 'fix: sort beacons by RSSI (closest first)' (#33) from schwifty/sort-beacons-by-rssi into main 2026-03-22 23:03:36 +00:00
2306c10d32 fix: sort discovered beacons by RSSI (closest first)
Sort the beacon list so strongest signal (closest beacon) appears at the
top. Sorting happens both in BLEManager as beacons are discovered and in
the ScanView list rendering.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 23:03:18 +00:00
df2a03f15a Merge pull request 'fix: overhaul beacon discovery to find all DX beacons' (#32) from schwifty/fix-beacon-discovery into main 2026-03-22 22:59:21 +00:00
174240c13e fix: overhaul beacon discovery to match Android detection logic
Major detection gaps were causing iOS to miss 7-8 out of 8-9 nearby DX beacons:

1. FFF0 service UUID was incorrectly mapped exclusively to BlueCharm.
   Android maps DXSmartProvisioner.SERVICE_UUID_FFF0 to DXSMART.
   Now checks device name to disambiguate FFF0 between DX and BlueCharm,
   defaulting to DXSmart (matching Android behavior).

2. Added DX factory default UUID detection (E2C56DB5-DFFB-48D2-B060-D0F5A71096E0).
   Android catches DX beacons by this UUID on line 130 of BeaconScanner.kt.
   iOS was missing this entirely.

3. Added Payfrit shard UUID detection — already-provisioned DX beacons
   broadcasting a shard UUID are now recognized.

4. Added iBeacon manufacturer data parsing with proper UUID extraction.
   Any device broadcasting valid iBeacon data is now included (not just
   those with minor > 10000).

5. Added permissive fallback matching Android lines 164-169: connectable
   devices with names are included even if type is unknown, so they're
   at least visible to the user.

6. Added FEA0 service UUID for BlueCharm (Android line 124).

7. Added "DX-CP" name pattern (Android line 138) that was missing.

Root cause: Android uses MAC OUI prefix 48:87:2D to catch all DX beacons
regardless of advertisement contents. iOS can't do this (CoreBluetooth
doesn't expose MAC addresses), so we compensate with broader iBeacon
UUID matching and more permissive device inclusion.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 22:55:35 +00:00
a6ba88803c Merge pull request 'fix: back button bounces user back into selected business' (#31) from schwifty/fix-back-button-bounce into main 2026-03-22 22:32:12 +00:00
81d4cad030 fix: back button bounces user right back into selected business
When tapping Back from ScanView, BusinessListView remounts and .task fires
loadBusinesses() which sees AppPrefs.lastBusinessId still set — immediately
re-navigating into the same business. Fix: clear lastBusinessId on back,
and add skipAutoNav flag to also prevent single-business auto-select from
firing when user explicitly navigated back.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 21:47:45 +00:00
f60c70f32a fix: QR scanner crash — missing NSCameraUsageDescription in Info.plist
The app crashed immediately when tapping QR scan because the Info.plist
was missing the required NSCameraUsageDescription key. iOS kills the app
with EXC_BAD_INSTRUCTION when camera access is requested without it.

Also fixes:
- Flash toggle could SIGABRT if lockForConfiguration failed (try? + unconditional unlock)
- Camera setup now logs errors instead of silently failing

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 21:45:10 +00:00
1624e0e59d Merge pull request 'fix: decode actual API response format' (#29) from schwifty/fix-api-response-decoding into main 2026-03-22 21:27:15 +00:00
a1c215eb42 fix: decode actual API response format (OK + flat keys, not Success/Data)
The API returns {OK: true, BUSINESSES: [...]} but the iOS client was
decoding {Success: true, Data: [...]} which never matched — causing
"Failed to load businesses" on every call. Also fixes Business model
(BusinessID/Name vs ID/BusinessName) and ServicePoint model
(ServicePointID vs ID). All response decoders now match the real API.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 21:26:42 +00:00
2242260f5a fix: align OTP auth with actual API response format
Three bugs found and fixed:
1. sendOTP was sending "ContactNumber" but API expects "Phone"
2. APIResponse expected {"Success":true,"Data":{}} but API returns {"OK":true,"UUID":"..."}
3. verifyOTP was sending "Code" but API expects "OTP"

Now decodes the raw API format directly instead of going through the
generic APIResponse wrapper (which doesn't match auth endpoints).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 20:46:55 +00:00
fd4b1bf8ca Merge schwifty/v3-clean-refactor: v3 BeaconProvisioner clean refactor with BLE timeout fixes 2026-03-22 20:15:41 +00:00
1b3b16478c fix: remove duplicate color definitions from Assets.xcassets to resolve ambiguity
PayfritGreen and PayfritGreenDark were defined in both Assets.xcassets
(as .colorset files) and in BrandColors.swift (as Color extensions).
All code references the Swift extension (.payfritGreen), so the asset
catalog versions are redundant and cause ambiguity. Removed the colorsets.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 19:47:03 +00:00
09db3e8ec7 fix: resolve ambiguous color references by removing ShapeStyle extension
The ShapeStyle where Self == Color extension duplicated Color statics,
causing 'ambiguous use' errors in foregroundStyle/stroke/tint/background
contexts. Removed the extension entirely and use explicit Color.xxx prefix
at all call sites instead.
2026-03-22 19:40:51 +00:00
3fbb44d22c fix: add ShapeStyle extension for brand colors to resolve ambiguous dot-syntax 2026-03-22 19:34:29 +00:00
8ecd533429 fix: replace generic icon with beacon-specific icon and match Android color scheme
- App icon now uses white bg + PAYFRIT text + bluetooth beacon icon (matches Android)
- AccentColor set to Payfrit Green (#22B24B)
- Added BrandColors.swift with full Android color palette parity
- All views updated: payfritGreen replaces .blue, proper status colors throughout
- Signal strength, beacon type badges, QR scanner corners all use brand colors
- DevBanner uses warningOrange matching Android's #FF9800

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 19:29:35 +00:00
2496cab7f3 fix: wrap iOS 17+ APIs in @available checks for iOS 16 compat
ContentUnavailableView and .symbolEffect(.pulse) require iOS 17+
but deployment target is iOS 16. Wrapped all usages in
if #available(iOS 17.0, *) with VStack-based fallbacks for iOS 16.

Files fixed:
- ScanView.swift (4 ContentUnavailableView + 1 symbolEffect)
- QRScannerView.swift (1 ContentUnavailableView)
- BusinessListView.swift (2 ContentUnavailableView)
2026-03-22 19:20:46 +00:00
fe2ee59930 fix: resolve ambiguous toolbar(content:) in BusinessListView
Use explicit toolbar(content:) call instead of trailing closure to
disambiguate the SwiftUI toolbar modifier overload.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 19:15:35 +00:00
9986937f66 fix: add missing Info.plist for PayfritBeacon target
Project references PayfritBeacon/Info.plist but the file was never committed.
Includes Bluetooth and Location usage descriptions required for beacon functionality.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 19:10:53 +00:00
6f4dba1804 fix: add actual 1024x1024 AppIcon.png to asset catalog
Copied from payfrit-user-ios — same Payfrit brand icon.
Contents.json now references the file.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 19:04:42 +00:00
21a40a0a28 fix: add missing Assets.xcassets with AppIcon.appiconset
Xcode build was failing because the asset catalog referenced in
project.pbxproj didn't exist on disk. Added:
- Assets.xcassets/Contents.json
- AppIcon.appiconset/Contents.json (single 1024x1024 slot, no image yet)
- AccentColor.colorset/Contents.json

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 19:02:48 +00:00
9ffd890a62 Merge pull request 'fix: rewrite xcodeproj to match subdirectory structure' (#27) from schwifty/fix-xcodeproj-structure into main 2026-03-22 18:59:06 +00:00
023ee9b3e3 fix: rewrite xcodeproj to match new subdirectory structure
The project file referenced old flat-structure filenames (Api.swift,
BeaconScanner.swift, BLEBeaconScanner.swift, etc.) but files are now
organized into App/, Models/, Provisioners/, Services/, Utils/, Views/.

Changes:
- Added PBXGroup entries for all 6 subdirectories
- Updated all 26 Swift file references to use subdirectory paths
- Removed 6 stale references (Api.swift, BeaconScanner.swift,
  BLEBeaconScanner.swift, BeaconProvisioner.swift,
  ServicePointListView.swift, DebugLog.swift)
- Added 14 new file references (AppPrefs, AppState, BeaconConfig,
  BeaconType, Business, ServicePoint, all Provisioners, APIClient,
  APIConfig, BLEManager, SecureStorage)
- All build configurations preserved unchanged

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 18:58:49 +00:00
b943a291d9 fix: add missing build files referenced in xcodeproj
- payfrit-favicon-light-outlines.svg (copied from payfrit-works-ios)
- en.lproj/InfoPlist.strings (standard localization)

These were referenced in project.pbxproj but not committed to the repo,
causing build failures.
2026-03-22 18:44:08 +00:00
928c5ff28f Merge pull request 'fix: remove CocoaPods references breaking build' (#26) from koda/remove-cocoapods-refs into main 2026-03-22 18:37:43 +00:00
8bd3fb474a fix: remove all CocoaPods references from xcodeproj
The rebuild removed Podfile/Podfile.lock/Pods but left CocoaPods build
phases and xcconfig references in the project file, causing build failure.

Removed: [CP] Check Pods Manifest.lock, [CP] Embed Pods Frameworks,
Pods_PayfritBeacon.framework, all Pods xcconfig baseConfigurationReferences,
and the Pods/Frameworks groups.

The old pods (Kingfisher, SVGKit) are not imported anywhere in the
rebuilt codebase — no replacement needed.
2026-03-22 18:37:28 +00:00
075706f162 docs: add CLAUDE.md updated for new modular architecture
Reflects Schwifty's rebuild — modular provisioners, actor-based API,
secure storage, QR scanner, and updated project structure.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 18:31:56 +00:00
0c4542e62f Merge schwifty/koda-review-fixes: complete iOS beacon rebuild with code review fixes 2026-03-22 18:29:48 +00:00
38b4c987c9 fix: address all issues from koda's code review
🔴 Critical:
- DXSmartProvisioner: complete rewrite to match Android's new SDK protocol
  - Writes to FFE2 (not FFE1) using 4E4F protocol packets
  - Correct command IDs: 0x74 UUID, 0x75 Major, 0x76 Minor, 0x77 RSSI,
    0x78 AdvInt, 0x79 TxPower, 0x60 Save
  - Frame selection (0x11/0x12) + frame type (0x62 iBeacon)
  - Old SDK fallback (0x36-0x43 via FFE1 with 555555 re-auth per command)
  - Auth timing: 100ms delays (was 500ms, matches Android SDK)
- BeaconShardPool: replaced 71 pattern UUIDs with exact 64 from Android

🟡 Warnings:
- BlueCharmProvisioner: 3 fallback write methods matching Android
  (FEA3 direct → FEA1 raw → FEA1 indexed), legacy FFF0 support,
  added "minew123" and "bc04p" passwords (5 total, was 3)
- BeaconBanList: added 4 missing prefixes (8492E75F, A0B13730,
  EBEFD083, B5B182C7), full UUID ban list, getBanReason() helper
- BLEManager: documented MAC OUI limitation (48:87:2D not available
  on iOS via CoreBluetooth)

🔵 Info:
- APIClient: added get_beacon_config endpoint for server-configured values
- ScanView: unknown beacon type now tries KBeacon→DXSmart→BlueCharm
  fallback chain via new FallbackProvisioner
- DXSmartProvisioner: added readFrame2() for post-write verification

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 17:25:55 +00:00
6832a8ad53 feat: add QR scanner view + 7 missing API endpoints
- QRScannerView: AVFoundation camera + barcode/QR detection with
  flashlight toggle, viewfinder overlay, MAC/UUID pattern recognition
- New API endpoints: deleteServicePoint, updateServicePoint,
  listBeacons, decommissionBeacon, lookupByMac, getBeaconStatus, getProfile
- Wire QR scanner into ScanView with BLE Scan + QR Scan side-by-side
- MAC address lookup on scan to check if beacon already registered
- Updated Xcode project file with new source
2026-03-22 17:17:49 +00:00
cfa78679be feat: complete rebuild of PayfritBeacon iOS from scratch
100% fresh codebase — no legacy code carried over. Built against
the Android beacon app as the behavioral spec.

Architecture:
- App: SwiftUI @main, AppState-driven navigation, Keychain storage
- Views: LoginView (OTP + biometric), BusinessListView, ScanView (provisioning hub)
- Models: Business, ServicePoint, BeaconConfig, BeaconType, DiscoveredBeacon
- Services: APIClient (actor, async/await), BLEManager (CoreBluetooth scanner)
- Provisioners: KBeacon, DXSmart (2-step auth + flashing), BlueCharm
- Utils: UUIDFormatting, BeaconBanList, BeaconShardPool (64 shards)

Matches Android feature parity:
- 4-screen flow: Login → Business Select → Scan/Provision
- 3 beacon types with correct GATT protocols and timeouts
- Namespace allocation via beacon-sharding API
- Smart service point naming (Table N auto-increment)
- DXSmart special flow (connect → flash → user confirms → write)
- Biometric auth, dev/prod build configs, DEV banner overlay

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 17:13:36 +00:00
66053508d3 refactor: v3 BeaconProvisioner — clean restart from pre-refactor baseline
Replaces the v2 refactor with a clean v3 rewrite built from the working
pre-refactor code (d123d25). Preserves all battle-tested BLE fixes while
removing dead code and simplifying state management. See schwifty/v3-clean-refactor
branch for full details.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 05:08:21 +00:00