diff --git a/app/app.json b/app/app.json index d7f9dba..4c83119 100644 --- a/app/app.json +++ b/app/app.json @@ -3,14 +3,14 @@ "name": "BigBrainParking", "slug": "bigbrainparking", "scheme": "bigbrainparking", - "version": "0.2.5", + "version": "0.2.6", "orientation": "portrait", "userInterfaceStyle": "automatic", "newArchEnabled": true, "icon": "./assets/icon.png", "android": { "package": "top.mowden.bigbrainparking", - "versionCode": 15, + "versionCode": 16, "edgeToEdgeEnabled": true, "adaptiveIcon": { "foregroundImage": "./assets/adaptive-icon.png", diff --git a/app/src/screens/ScanScreen.tsx b/app/src/screens/ScanScreen.tsx index 1901a4e..25a602e 100644 --- a/app/src/screens/ScanScreen.tsx +++ b/app/src/screens/ScanScreen.tsx @@ -66,23 +66,41 @@ export function ScanScreen() { const lookupCode = useCallback( async (code: string) => { setBusy(true); - try { - let res = await ps.getMetersByScannerCode(code); - let zone = res.Zones?.[0]; - if (!zone) { - res = await ps.getMetersByZoneName(code); + // A scanned/typed code can resolve by terminal serial, scanner code, or + // zone name. Unmatched lookups return an error envelope (which the client + // throws), and ZoneName is case-sensitive server-side — the official app + // queries it lowercased. So try each strategy independently and don't let + // one failing attempt abort the others. + // ScannerCode is what kiosk QRs encode and what the official app honors; + // zone name (case-sensitive) and terminal serial are extra fallbacks. + const attempts = [ + () => ps.getMetersByScannerCode(code), + () => ps.getMetersByZoneName(code), + () => ps.getMetersByZoneName(code.toLowerCase()), + () => ps.getMetersBySerialNumber(code), + ]; + let zone: + | NonNullable>['Zones']>[number] + | undefined; + let anyResponded = false; + for (const attempt of attempts) { + try { + const res = await attempt(); + anyResponded = true; zone = res.Zones?.[0]; + if (zone) break; + } catch { + // strategy didn't apply (e.g. error envelope) — try the next one } - if (zone) { - setManualOpen(false); - navigation.navigate('MeterDetail', { zone }); - } else { - Alert.alert('Not found', `No meter matches "${code}".`); - } - } catch (e: any) { - Alert.alert('Lookup failed', e?.serverMessage ?? e?.message ?? 'error'); - } finally { - setBusy(false); + } + setBusy(false); + if (zone) { + setManualOpen(false); + navigation.navigate('MeterDetail', { zone }); + } else if (anyResponded) { + Alert.alert('Not found', `No meter matches "${code}".`); + } else { + Alert.alert('Lookup failed', 'Could not reach ParkSmarter. Check your connection.'); } }, [navigation], diff --git a/docs/zone-qr/DL-sandpoint.png b/docs/zone-qr/DL-sandpoint.png new file mode 100644 index 0000000..74afee5 Binary files /dev/null and b/docs/zone-qr/DL-sandpoint.png differ diff --git a/docs/zone-qr/README.md b/docs/zone-qr/README.md new file mode 100644 index 0000000..aed65dc --- /dev/null +++ b/docs/zone-qr/README.md @@ -0,0 +1,36 @@ +# Zone QR codes + +A parking-zone QR code just encodes the zone's **`ScannerCode`** as plain text. +BigBrainParking's scanner (and the official ParkSmarter app) resolve that code to +the zone. No URL, no wrapper — the raw scanner code is enough. + +## Make one + +```bash +qrencode -o my-zone.png -s 14 -m 4 "" +``` + +`` is the zone's `ScannerCode` field (visible on the meter detail +screen, or in a `/api/Meter` response). For the DL zone in Sandpoint it's `DL`: + +```bash +qrencode -o DL-sandpoint.png -s 14 -m 4 "DL" +``` + +![DL zone, Sandpoint](DL-sandpoint.png) + +## Which field to encode + +Use **`ScannerCode`** — it's what physical kiosk QR stickers encode and the only +field the official ParkSmarter app accepts when scanning. + +- `ScannerCode` — ✅ works in BigBrainParking **and** the official app. +- `ZoneName` (e.g. `dl`) — works as a fallback in BigBrainParking (case-sensitive + server-side; the app tries the lowercased form too). +- `TerminalSerNo` (e.g. `30004246`) — uniquely identifies the terminal and works + in BigBrainParking, but the **official app rejects a bare serial** ("not + recognized as a ParkSmarter zone"), so don't use it for cross-app QR codes. + +When BigBrainParking scans a code it tries, in order: `ScannerCode → ZoneName → +zonename(lowercased) → TerminalSerNo`, each independently, so any of these +resolve.