All guides

Other

Google Smart Tap on the gate (Pi + Wallet Mate II)

Android / Google Wallet taps at the door use Google Smart Tap 2.x on the access-engine reader. The Pi decrypts the pass and checks the redemption value against the local member cache (and /api/access/verify as fallback).

This is separate from Apple VAS (transparent session + OSE.VAS.01 GET DATA). Both can be enabled on the same reader; the engine runs Smart Tap first, then Apple, so Android HCE is not broken by Apple’s session.

Code: access-engine/smart_tap/ (protocol ported from Google’s smart-tap-sample-app).


What “success” looks like in logs

SmartTap: redemption value: <pass-uuid-or-redemption-string>
NFC: Wallet tap detected — verifying token: <same>
Main: Access GRANTED (Wallet Local-First) …

Not success (wrong path — phone treated as a fob):

NFC: Card detected (UID: 08FA396C)
Main: Access DENIED for 08FA396C (Not found or inactive in local DB)

If wallet mode is on and the tap is a phone (ATR like 3B80800101), the engine skips UID fallback and logs:

NFC: Phone tap — wallet read did not return a pass token; skipping UID fallback (not a fob).

Prerequisites (Google + Pass Ninja + portal)

LayerRequirement
Google Pay & Wallet ConsoleIssuer / redemption setup; Collector ID; EC private key uploaded; key version noted
Pass classredemptionIssuers includes your issuer; pass objects have smartTapRedemptionValue set (Pass Ninja: usually passes.id via nfc.message)
Pass Ninja (if used)Google template + PASS_NINJA_API_FIELD_NFC=nfc.message on web-portal; member adds pass to Google Wallet (not only a barcode)
PortalActive membership; pass synced to Pi (Sync … N active tokens cached)
HardwareACS Wallet Mate II (PICC), pcscd, member-access.service

Collector ID on the terminal must match a collector ID on the pass in Google Wallet. Wrong ID → negotiate 9500 or get-data 9001 (no pass transmitted).


Pi configuration (device.env)

Copy from access-engine/device.env.example. Wallet-related vars:

# Required for Google Wallet taps
GOOGLE_SMART_TAP_COLLECTOR_ID=20180608          # numeric, from Google console
GOOGLE_SMART_TAP_KEY_VERSION=1                  # matches uploaded key version
GOOGLE_SMART_TAP_PRIVATE_KEY_PATH=/home/porta/member-access-os/access-engine/google_smart_tap.pem

# Also required for online fallback + Apple (shared reader config)
ACCESS_VERIFY_URL=https://pass.qrtick.com/api/access/verify
ACCESS_POINT_ID=<uuid-from-portal-access-points>
APPLE_PASS_TYPE_IDENTIFIER=pass.com.example.memberaccess   # Apple only
VAS_PRIVATE_KEY_PATH=/home/porta/member-access-os/access-engine/vas_private.pem

Optional: inline PEM instead of file:

GOOGLE_SMART_TAP_PRIVATE_KEY_PEM="-----BEGIN EC PRIVATE KEY-----\n..."

On boot you should see:

NFC: Wallet enabled (Apple VAS & Google Smart Tap).

If Google is missing: only collector ID set, or PEM path wrong → Wallet enabled may list Apple only or UID-only mode.


Deploy from your laptop

Pre-built device.env + keys on the Pi path; then sync code and restart:

# From repo root — adjust PI_USER / PI_HOST (Tailscale name or LAN)
PI_USER=porta PI_HOST=tyegym-gate-001 ./deploy.sh access-engine/device.env

Follow logs:

ssh porta@tyegym-gate-001 'sudo journalctl -u member-access -n 100 -f'

Or set DEPLOY_FOLLOW_LOGS=1 on deploy.

Lab provisioning only on the device: sudo ./access-engine/deploy.sh ./device.env (see on-site install runbook).


Member tap procedure (on-site)

  1. Member opens Google Wallet and displays the gym pass (not just the lock screen).
  2. Hold phone flat on the Wallet Mate 2–3 seconds.
  3. If multiple passes match, Google may show a carousel — member selects the gym pass and taps again.

Apple users: open pass in Apple Wallet if ECP did not auto-present (VAS: ECP not confirmed in logs).


Troubleshooting

SymptomLikely causeWhat to check
SmartTap: unable to authenticate (9500)Collector ID / private key / key version mismatch vs Google consoleRe-export PEM; confirm GOOGLE_SMART_TAP_* on Pi; same key registered for that collector
SmartTap: no matching pass on device (9001)Pass not in wallet, wrong collector on pass, or pass not openedPass Ninja Google install URL; redemptionIssuers; member has pass on screen
Phone tap — … skipping UID fallbackSmart Tap ran but no tokenAbove + Pass Ninja NFC field on Google template
ST2 not listed in OSEInformationalEngine still tries SELECT Smart Tap 2
SmartTap: OSE label … (not AndroidPay)Device is not presenting Google Wallet (iPhone / empty field)Expected on iPhone; use Apple VAS path
Access DENIED with UID hexOld code or wallet not configuredDeploy latest access-engine/; confirm wallet enabled in logs
Access GRANTED online but not localSync lagWait for Sync [Poll]: Auth sync complete; check facility slug / access point
Gate never opens, remote pulse worksDoor / Shelly, not NFCShelly env; staff Release gate in portal

Verify token shape: Redemption value should match passes.id (UUID) when using PASS_NINJA_API_FIELD_NFC=nfc.message. Pi cache uses that same id as serial_number during sync.


Protocol reference (for engineers)

StepAPDUNotes
1SELECT OSE.VAS.01Detect AndroidPay; list Smart Tap 2 AID
2SELECT A0000000476D0000111Smart Tap 2 (v1 AID …0101 is deprecated)
390 53 NEGOTIATECollector signature + ECDH session keys
490 50 GET DATAService types tried: generic 0x12, loyalty 0x03, all 0x00
590 C0 GET MOREIf status 9100

Implementation: access-engine/smart_tap/protocol.pywallet_smarttap.try_smart_tap_token()nfc_drivermain.handle_vas_token().