v0.2.6: fix kiosk-code scan resolution + add zone QR asset
All checks were successful
build-apk / build (push) Successful in 30m18s

ScanScreen.lookupCode tried getMetersByScannerCode first and let its thrown
error-envelope abort the whole lookup, so the getMetersByZoneName fallback never
ran; it also sent ZoneName in the scanned case (the API is case-sensitive — the
official app lowercases). Confirmed via live mitmproxy capture of the official
app: a scanned code resolves through ScannerCode, and ZoneName=dl (lowercase)
returns the zone.

Now tries ScannerCode -> ZoneName -> zonename(lowercased) -> TerminalSerNo, each
independent and non-fatal; only reports "Lookup failed" if every attempt errored.

Adds docs/zone-qr/ with the DL-Sandpoint QR (encodes ScannerCode "DL", works in
both BigBrainParking and the official app) and a how-to.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-07-16 19:08:49 +00:00
parent deb78bf952
commit 2bdb59b49a
4 changed files with 71 additions and 17 deletions

View file

@ -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",

View file

@ -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<Awaited<ReturnType<typeof ps.getMetersByZoneName>>['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],

Binary file not shown.

After

Width:  |  Height:  |  Size: 431 B

36
docs/zone-qr/README.md Normal file
View file

@ -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 "<ScannerCode>"
```
`<ScannerCode>` 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.