by Intelliverse X

Vending manufacturers manual — Reyeah & ZHZN, end to end

The operator-facing manual for the two vending hardware families Kiosk-X supports: Reyeah (a.k.a. reyeah-jd / Ruiye "JD", com.ruiye.jd) and ZHZN (a.k.a. CSM, on the Huiwan T3568 board). It explains what actually differs between them, and walks an operator/installer from a boxed machine to selling on either one — registration, planogram, pricing, the customer purchase, the physical product fall, receipt/telemetry, revenue, and Nayax.

Everything here is grounded in the real codebase; each claim cites the file it rests on. Where the code cannot prove a hardware-only detail, the manual says so and marks it confirm with hardware.

The one-sentence version. The two manufacturers converge on a single operator experience and a single cloud, and diverge only at the device layer: Reyeah is an Android kiosk APK that drops product over a FF0055… serial protocol; ZHZN uses its own Android APK (ai.intelliverse.zhzn.kioskx) or a Python agent on Linux that drops product over the CSM AA…BB serial protocol. Neither is "pure install" for product fall — see Product-fall verdict.

Android first install: Installation and operator handover. Linux env-file, SoftAP and systemd procedures elsewhere in this manual do not apply to the Android APK.

Factory line (ZHZN, bilingual): /manual/factory-zhzn — photo print is optional; power-bank door QR and Nayax Device Number (not Machine ID) must be scanned if fitted; unsigned crates are refused at the USA warehouse. Chinese: 照片打印选配;充电宝与读卡器若安装必须扫码绑定。


1. The two manufacturers at a glance

Dimension Reyeah (reyeah-jd) ZHZN (CSM / T3568)
On-machine software Android APK com.ruiye.jd (the universal "Kiosk X" build) ZHZN Android APK with WebView storefront; Python agent for Linux hosts
Android host board Rockchip RK3288, Android 7.1.2 (API 25), 32-bit armeabi-v7a Rockchip RK3568 (Huiwan HW-T3568), Android/Linux, 6× COM
Dispense mechanism Spring/aisle motors driven by the VMC Y-lift (elevator/gantry) or spring board (SH_YAxis / SH)
Controller serial protocol FF 00 55 … <chk> (reply FF 00 AA …), sum-mod-256 checksum AA <len> … <xor> BB (Y-lift) / no-checksum spring frames
Serial port(s) / baud /dev/ttyS1 main (115200/9600), /dev/ttyS0 dispense (9600) /dev/ttyS4 dispenser (9600 8N1); a second COM for payment
Dispense command FF00554102 <aisle> <qty> <chk> AA 14 01 01 … <aisle> … <xor> BB (type 0x01 deliver)
Dispense OK / fault FF00AAE10101 OK · FF00AAE101xx fault flag 0x02 success · 0x03 + board error code
Cloud device protocol /apk/* on api.kiosk-x.ai (getEquipment/getGoods/getStyle/getAd/createOrder…) /zhzn/* gateway (register/config/goods/pending/report/heartbeat/upgrade)
Device auth serial-derived equipmentNo + persisted cookie; auto-provision on first contact header secret: <fleet secret> + equipmentNo (ZHZN_DEVICE_SECRET)
Remote dispense MQTT push type:"1" / "21" to a topic named after the serial leased queue GET /zhzn/pending polled by the agent
Real-time presence MQTT {equipmentNo,state} + Last-Will POST /zhzn/heartbeat (and /machine/heartbeat)
OTA /apk/getUpgradeVersion → signed APK on S3, HTTPS + sha256 verified before install Android: versionCode + SHA-256 checked APK, same signing key; Linux: signed agent archive
Payment (card) Nayax VPOS Touch over MDB to the VMC Nayax/MDB via an MDB-RS232 adapter on a dedicated COM
Lead time (by fulfillment, not brand) in-stock ~4 wk when shipped from US warehouse stock made-to-order ~11 wk for factory builds (both brands' factory runs use MTO timing)
Product-fall on install APK contains the drop logic, but needs serial wiring + provisioning ZHZN APK contains the CSM driver; requires wiring, enrolment and acceptance

Sources: reyeah-jd-vending-kiosk/README.md, reyeah-jd-vending-kiosk/emulator/WIRING_AND_DEPLOY.md; zhzn-vending-kiosk/docs/t3568-board.md, zhzn-vending-kiosk/docs/protocol-ylift.md, zhzn-vending-kiosk/agent/config.py; kiosk-x/app/routes/device.py, kiosk-x/app/routes/zhzn.py, kiosk-x/app/mqtt_bridge.py, kiosk-x/app/dfy_manufacturing.py.


2. Where they converge, where they diverge

graph TD subgraph OP["ONE operator experience (manufacturer-agnostic)"] C1["Operator Console / Android app"] C2["Partner API on api.kiosk-x.ai
machines · inventory · pricing · orders · payments · revenue"] end subgraph CLOUD["Kiosk-X cloud"] D1["/apk/* device protocol"] D2["/zhzn/* device gateway"] MQ["MQTT broker mq.kiosk-x.ai"] end subgraph REY["Reyeah machine"] R1["Kiosk X APK (com.ruiye.jd)"] R2["VMC control board
FF0055 over /dev/ttyS0,S1"] end subgraph ZH["ZHZN machine (T3568)"] Z1["ZHZN Android APK (or Linux agent)"] Z2["CSM control / Y-lift board
AA..BB over /dev/ttyS4"] end C1 --> C2 C2 --- D1 C2 --- D2 D1 <--> R1 MQ <--> R1 R1 --> R2 D2 <--> Z1 Z1 --> Z2

3. Differentiators in depth

3.1 Dispensing / product-fall mechanism

Reyeah drives spring/aisle motors through the VMC over UART. The APK builds a dispense frame FF00554102 <aisle> <qty> <chk> (checksum = sum of every byte after FF 00, mod 256), e.g. aisle 5 qty 1 → FF0055410205019E. The board answers FF00AAE10101 (OK) or FF00AAE101xx (fault). Ports: /dev/ttyS1 main control and /dev/ttyS0 dispensing, both 9600 for commands (port discovery also probes at 9600; 115200 is used only temporarily for VMC firmware updates). An optical "drop" sensor frame (FF0055050104) provides anti-cheat drop detection (reyeah-jd-vending-kiosk/emulator/WIRING_AND_DEPLOY.md:30-60, reyeah-jd-vending-kiosk/README.md:22-35).

ZHZN uses a Y-lift (elevator/gantry that travels to a cargo lane, opens a pickup door, and returns to the port) or a spring board, selected by ZHZN_MODULE (SH_YAxis | SH). The deliver frame is AA 14 01 01 <push_delay> 00 <door_hi> <door_lo> <aisle> …<xor> BB (type 0x01, XOR check over header→last data byte). Success is response flag 0x02; an error is flag 0x03 carrying a board code (0x07 = "no shipment detected", etc.). A spring board's "correct response with drop-eye = 0" is a soft success logged as not_detected (zhzn-vending-kiosk/docs/protocol-ylift.md:44-70,162-186, zhzn-vending-kiosk/agent/dispenser.py:36-79). Y-lift lanes must be step-calibrated (0x07 set-steps, saved with the 0xFF lane) before aisles land correctly (protocol-ylift.md:107-120).

3.2 How the machine talks to the controller

Reyeah: the APK opens raw Linux serial nodes via a JNI libserialport.so, so the APK itself is the controller driver. ZHZN: the ZHZN APK or Linux agent owns the serial port (SerialDispenser over pyserial, one lock per write-then-read transaction) and drives the documented AA…BB protocol directly — no vendor Android SDK is required for the agent path (zhzn-vending-kiosk/agent/dispenser.py:1-9,131-203). The Reyeah APR's FF0055… protocol and the ZHZN AA…BB protocol are mutually incompatible, which is why "the Reyeah app cannot drive ZHZN machines" (zhzn-vending-kiosk/docs/INTEGRATION.md:10-12).

3.3 MQTT / broker (Reyeah only)

The Reyeah firmware keeps a persistent MQTT session on the Intelliverse broker mq.kiosk-x.ai (no vendor infra). Device→cloud publishes {equipmentNo, state} for presence with a Last-Will marking it offline; cloud→device publishes commands to a topic named after the machine's equipmentNo: type:"1" remote dispense {orderNumber, aisleNo, buyNum}, type:"21" multi-line cart dispense, type:"2" push APK install, type:"3" push VMC firmware (the firmware's MqttUtils also handles log-upload, ad-expire, cache-clear, lock and reboot types) (kiosk-x/app/mqtt_bridge.py:1-18,125-159; reyeah-jd-vending-kiosk/decompiled … MqttUtils.java). The stock build points at the vendor broker; the Kiosk-X build is repointed to tcp://mq.kiosk-x.ai at repack time (scripts/build_vending_variants.sh). MQTT is best-effort and optional — vending runs over HTTP /apk/* and never depends on the broker being reachable (mqtt_bridge.py:14-18).

ZHZN uses no MQTT. Presence is POST /zhzn/heartbeat; remote dispatch is a leased pull-queue the agent polls (see next).

3.4 Queue mechanics — ZHZN's leased queue vs Reyeah's push

Reyeah remote dispense is a fire-and-push MQTT message (with a GET /commands/pending HTTP fallback so a drop is never lost if the broker is down — kiosk-x/app/routes/fulfill.py:74-80,150-156).

ZHZN remote dispense is a leased, exactly-once queue (kiosk-x/app/routes/zhzn.py:23-39,250-398):

The agent additionally supports kiosk-led dispatch (on-device UI POSTs the paid order to a loopback 127.0.0.1:8770/dispense, Bearer-token protected) and remote-led (poll the queue), or both (ZHZN_DISPATCH_MODE; zhzn-vending-kiosk/agent/config.py:105-133, zhzn-vending-kiosk/docs/INTEGRATION.md:34-49).

3.5 Provisioning / machine identity

Reyeah: on first boot the APK reads its board ID off the VMC (FF00553101AD → e.g. ASCII VM20240001) and that becomes the equipmentNo. Kiosk-X auto-provisions an unseen serial into the unclaimed pool (fully stocked, ten empty aisles) so the kiosk boots to a working grid instead of the vendor cloud's "Device non-existed"; an operator claims it later via POST /api/v1/machines/register (kiosk-x/app/routes/device.py:9-17,436-450; reyeah-jd-vending-kiosk/emulator/WIRING_AND_DEPLOY.md:126-141).

ZHZN: identity is the hardware serial → machine code, resolved by ZHZN_MACHINE_MAP at the gateway, with three onboarding paths (zhzn-vending-kiosk/docs/GO-LIVE.md:54-110):

ZHZN catalog seam (read this or the machine will not sell). kiosk-x registration gives the device identity + hardware config, but the product catalog, slots, and dispense reports all key on the Intelliverse machine record, which kiosk-x does not create. Map the hardware serial to the Intelliverse machine code in KIOSKX_ZHZN_MACHINE_MAP, then assign products to (row,column) slots — otherwise kiosk-products/by-machine returns nothing (GO-LIVE.md:77-89).

3.6 Planogram / aisle model (shared)

Both resolve to one planogram. Reyeah "goods" are 1:1 with aisles; a purchase of aisle N decrements aisle N (device.py:133-195). The ZHZN gateway serves the same store in the device's row/column dialect, deriving the aisle with aisle = (row-1)·columnsPerRow + column (columnsPerRow default 10; zhzn.py:200-247, config.py:155-181, INTEGRATION.md:88-98). So a Reyeah APK (/apk/getGoods) and a ZHZN device (/zhzn/goods) pointed at the same machine read an identical planogram. (Honesty note: the shipped ZHZN agent currently fetches its catalog from the Intelliverse Products API (kiosk-products/by-machine) rather than /zhzn/goods — the gateway surface exists so both dialects can resolve to the one store; zhzn-vending-kiosk/agent/backend.py, zhzn.py:229-247.)

3.7 Firmware / OTA

Reyeah OTA is an APK rollout: /apk/getUpgradeVersion returns a vendor-shaped descriptor with an HTTPS url (S3) and a sha256/checksum; the firmware verifies the artifact before install, then reports the new build via /apk/apkUp, which flips the machine to "upgraded" and auto-completes the rollout (device.py:1330-1372,1969-1982). Client-side integrity was hardened in the vending repo (release 1.2.1 / versionCode 121): a self-contained com.kioskx.OtaIntegrity class rejects non-HTTPS offers and streams the download through SHA-256, deleting the file and aborting on mismatch before install — offers with no digest (legacy backend) keep working with a logged warning so a backend rollback can't brick fleet OTA (reyeah-jd-vending-kiosk commit 8d5d0f3, scripts/build_vending_variants.sh:112-138, scripts/patch_smali.py).

ZHZN Android OTA is a signed APK offered through /zhzn/upgrade; it requires a higher versionCode, matching SHA-256 and the existing Android signing key. The following archive description applies only to the Linux Python agent: /zhzn/upgrade (or ZHZN_OTA_MANIFEST_URL) returns {version, url, sha256}; the agent verifies the sha256 over TLS, stages the new tree, swaps the install root (previous stays at <root>.old for rollback), and restarts under systemd (zhzn.py:409-418, config.py:71-78, GO-LIVE.md:191-219). Known gaps: manifests are sha256-verified but not signed, and the install-root swap is two renames (tiny non-atomic window) — both flagged for hardening before large fleets (GO-LIVE.md:210-219).

3.8 Lead times (provisioning economics)

The DFY manufacturing lead-time model keys on fulfillment mode, not brand: in-stock (US warehouse pick/config + LTL freight, ~27 days ≈ 4 weeks end to end) vs made-to-order (factory build + ocean freight from Shenzhen/Ningbo, ~80 days ≈ 11 weeks: qa 25d and delivered 35d dominate). The manufacturers enum is hotbox | reyeah | zhzn, and the code's comment attributes MTO timing to "Reyeah/ZHZN factory builds" — i.e. either brand's factory run takes the ~11-week path, and either can be in-stock if warehoused (kiosk-x/app/dfy_manufacturing.py:31-82, docs/autopilot-investments.md, docs/superadmin-money-ops-manual.md).


4. The universal "Kiosk X" APK

4.1 What makes it universal

The universal build is the rebuilt Reyeah APK (com.ruiye.jd), pointed at the Kiosk-X cloud. Its universality is cloud-driven, not hardcoded:

4.2 What the APK does on first boot (verified in the decompiled source)

  1. SplashActivity → permissions + cached identity restore; with no cached equipmentNo it scans serial ports (SerialPortFinder, probing at 9600 with the clear frame and accepting FF00AAA201014E as "this is a VMC").
  2. Reads the board ID (FF00553101AD… → ASCII, e.g. VM20240001) — that becomes the equipmentNo sent on every cloud call.
  3. Bootstraps over HTTP in order: getUpgradeVersiongetAdgetStylegetEquipmentapkUp, then connects MQTT and queries VMC firmware.
  4. After getEquipment it configures the optical drop-eye (config.info.isEye) and enters the product grid, which loads /apk/getGoods.

(decompiled … SplashFragment.java, MyIntentService.java; emulator/WIRING_AND_DEPLOY.md:119-141.)

Device-auth honesty note: the APK sends a baked-in secret header (UrlConfigString.secret) plus apkVersion/equipmentNo on every call. The vendor cloud requires that secret; the Kiosk-X /apk/* surface identifies the device by equipmentNo/cookie and does not validate the Reyeah secret header (kiosk-x/app/routes/device.py:58-96) — in contrast to the ZHZN gateway, which enforces its fleet secret (zhzn.py:135-141).

4.3 Product-fall verdict: the honest answer

"Does product fall work just by installing the APK?" — No, on neither manufacturer is it pure-install. Be precise about why:

Reyeah ZHZN
Does the universal APK contain the drop logic? Yes — the FF0055… dispense frames ship in the APK The Reyeah APK does not; the separate ZHZN APK implements AA…BB
What actually performs the fall the APK over /dev/ttyS0/S1 the ZHZN APK over its configured UART/USB port, or the Linux agent
Extra manual steps required serial wiring; raw-serial access (platform-sign or chmod); uninstall factory app first (different signing cert); armeabi-v7a build; a provisioned/claimed serial install ZHZN APK; device owner + HOME; wire/calibrate; enrol; assign slots; complete factory acceptance
Verdict install + serial wiring + provisioning ZHZN APK + wiring + provisioning + acceptance

Reyeah specifics: the emulator proved the full chain app → serial handshake → device id → cloud config → grid → purchase → dispense except the vendor-side device record; a raw /dev/ttySx needs root/platform signing, and Android refuses to update the factory-signed package with our release cert, so you uninstall the factory app first (reyeah-jd-vending-kiosk/README.md:86-102, emulator/WIRING_AND_DEPLOY.md:64-104).

ZHZN Android specifics: install the dedicated ZHZN APK with its embedded WebView storefront. Linux hosts instead run the headless Python agent. Follow Android install and handover for Android cabinets. Product fall is the agent driving AA…BB; "runs today, proven by simulation" (zhzn-vending-kiosk/docs/GO-LIVE.md:1-14, docs/INTEGRATION.md:100-111).

confirm with hardware: the RK3288/RK3568 /dev/ttySN ↔ physical COM mapping and the exact chmod/SELinux/platform-signing needed for raw serial access are board-specific and cannot be proven from this code (zhzn-vending-kiosk/docs/t3568-board.md:26-41, reyeah-jd-vending-kiosk/emulator/WIRING_AND_DEPLOY.md:64-77).


5. Cloud API surface for the kiosk

All device traffic is the envelope {code, data, msg} with code == 0 = success (distinct from the Partner API's 200). Base host api.kiosk-x.ai (device builds use kiosk-x.intelli-verse-x.ai).

5.1 Reyeah /apk/* (implemented in kiosk-x/app/routes/device.py)

Endpoint Purpose
/apk/getEquipment Config/theming; auto-provisions an unseen serial; pins currency/pay tiles/age gate (:436-450)
/apk/getGoods Planogram → grid (aisle 1:1 good), cutout product art (:488-504)
/apk/getStyle Full skin (grid + pay-sheet chrome URLs) (:567-578)
/apk/getAd Never-empty attract loop, player-safety gated (:1130-1327)
/apk/createOrder · /apk/createOrderCart (+ offline variants) Book a sale; payType 3=card(Nayax), 4=Scan&Pay QR; $0 promo path (:1947-1964)
/apk/orderStatus · /apk/payQr Scan & Pay poll + QR (Stripe webhook settles, then MQTT dispense) (:2028-2086)
/apk/ordersUpdate Post-payment dispense outcome; a failed vend marks needsRefund (:1999-2015)
/apk/getUpgradeVersion · /apk/apkUp OTA offer (HTTPS + sha256) + install ack (:1330-1372,1969-1982)
/apk/saveAisleGoods On-machine manager-menu planogram edit persists (:2121-2155)
/apk/submitFault · /apk/uploadLog · /apk/vmcUp Fault/telemetry (fault blocks the aisle) (:2164-2201)
/apk/saveAdRecord Ad proof-of-play → sponsor/SSP reporting (:2218-2254)
/apk/authPassword · /apk/validDiscountCode · /apk/ageVerify Manager gate · free-product/QuestX codes · age gate (:1382-1597)
/device/qr/machine/{no}.png Machine-number registration QR → operator-console deep link (:717-731)

5.2 ZHZN /zhzn/* (implemented in kiosk-x/app/routes/zhzn.py)

Endpoint Purpose
POST /zhzn/register First contact: serial → machine code + config; auto-provision (:181-191)
GET /zhzn/config getStyle-equivalent (module/grid/intervals) (:194-197)
GET /zhzn/goods Planogram as row/col slots — same store as /apk/getGoods (:229-247)
GET /zhzn/pending Leased remote-dispense queue (:250-265)
POST /zhzn/report Dispense outcome; exactly-once forward to trigger-fall / order-complete (:285-398)
POST /zhzn/heartbeat Presence (:401-406)
GET /zhzn/upgrade OTA manifest {version,url,sha256} (:409-418)
POST /zhzn/dispense Operator-facing: enqueue a remote-led dispense (Partner-API auth, ownership enforced) (:423-450)

5.3 Shared vs manufacturer-specific

OTA hardening (both): offers are HTTPS-only and carry a sha256 the client verifies before installing (Reyeah com.kioskx.OtaIntegrity; ZHZN agent tarball verify). Machine-number QR registration lets an operator scan a serial at the machine instead of typing it (device.py:703-731).


6. End-to-end vending for one operator

The same operator runs both kinds from one Operator X app / web console.

sequenceDiagram participant Cust as Customer participant Dev as Machine (APK or agent) participant Cloud as Kiosk-X cloud participant Nayax as Nayax cloud Note over Dev: idle / attract loop (getAd) Cust->>Dev: touch to shop → select product Cust->>Dev: tap Credit card (or scan pay QR) Dev->>Cloud: createOrder (Reyeah) / order (ZHZN) Cust->>Nayax: tap card Nayax-->>Dev: authorized (VMC HavePaid) Dev->>Dev: DISPENSE (FF0055 motor / AA..BB Y-lift) Dev->>Cloud: ordersUpdate (Reyeah) / report (ZHZN) Nayax->>Cloud: settlement webhook → orders + payments Cloud-->>Cloud: revenue ledger entry

Operator setup (identical for both): 1. Register/claimPOST /api/v1/machines/register with the serial (or scan the machine-number QR). If the board already auto-provisioned, this claims it (and any vends it already took) into your fleet (machines.py:181-219). 2. Planogram + pricing — assign products to aisles/slots with stock and price via PUT /api/v1/inventory/… (Reyeah) or slot assignment (ZHZN); both land in the one shared store. 3. Bind NayaxPOST /api/v1/machines/{no}/nayax with the reader's Device Number (see §7).

Customer purchase (converges in the cloud): a walk-up card sale is machine-led (the device commands its own motors and the cloud records the sale); a remote sale is api_drop (Reyeah MQTT push) or a queued /zhzn/dispense (ZHZN pull). Either way the order, dispense outcome, Nayax settlement, and the revenue-ledger entry flow through the same operator endpoints (fulfill.py:83-123, device.py:1999-2015, zhzn.py:285-398, docs/hybrid-fulfillment.md).


7. Nayax per manufacturer

Nayax acquiring is independent of the vending cloud and settles to the operator's Nayax merchant account, decided by the terminal→account binding inside Nayax Core — not by anything the kiosk sends (docs/payments-nayax.md:24-52,189-201).

Binding (identical for both): POST /api/v1/machines/{no}/nayax with the reader's Device Number (terminalId), mirrored on the machine record; the webhook resolves the machine by exact terminal match and force-routes settlement from that binding (payments-nayax.md:54-85, kiosk-x/app/routes/machines.py:235-275). The webhook POST /api/v1/payments/nayax/webhook fails closed: it requires X-Nayax-Signature (hex HMAC-SHA256 of the raw body with KIOSKX_NAYAX_WEBHOOK_SECRET); unsigned/mis-signed → 401, unknown terminal → 404 (payments-nayax.md:109-149).

Reyeah ZHZN
Reader Nayax VPOS Touch Nayax reader via MDB-RS232 adapter
Wiring to controller reader → MDB → VMC MDB peripheral → MDB-RS232 adapter → a dedicated COM on the T3568 (separate from the dispenser COM)
Card tile flow createOrder payType=3 → app arms reader over serial → VMC HavePaid → app drives motor → ordersUpdate (payments-nayax.md:87-107) adapter is the MDB master, pushes accept events (30…/08…); app authorizes then calls the dispenser; payment path is independent of the CSM SDK (docs/mdb-rs232.md:15-71)
Settlement record webhook → orders + payments APIs same webhook → same APIs (manufacturer-agnostic)

Adapter serial config: 9600 8N1, HEX; events are prefixed with the MDB device ID and have no checksum; resend a host command if no reply after 2s (mdb-rs232.md:8-28). On vend failure, refund via escrow-return (35 00) or coin payout (mdb-rs232.md:63-71).

confirm with hardware: the exact MDB-RS232 ↔ Nayax reader model compatibility and which T3568 COM carries it are per-build wiring choices, not determinable from code (mdb-rs232.md:63-66, t3568-board.md:26-32).


8. Manual setup procedures

Steps marked [same] are identical across both; [Reyeah] / [ZHZN] are manufacturer-specific.

8.1 Reyeah — boxed machine to selling

  1. [same] Power + network — 12V/mains per the machine; Ethernet or WiFi with outbound HTTPS to kiosk-x.intelli-verse-x.ai (and MQTT to mq.kiosk-x.ai, optional).
  2. [Reyeah] Wire the controller — UART TX/RX/GND from the Android board to the VMC; match voltage (TTL 3.3V or RS232 via level shifter); /dev/ttyS1 main (115200/9600), /dev/ttyS0 dispense (9600) (emulator/WIRING_AND_DEPLOY.md:64-77).
  3. [Reyeah] Install the APK — build the armeabi-v7a Kiosk-X variant, then on a board still running the factory app: adb uninstall com.ruiye.jdadb install -r dist/reyeah-vending-kioskx.apk → launch. Grant raw serial access (platform-sign or chmod 666 /dev/ttyS*); set it as the boot launcher (BOOT_COMPLETED autostart) (README.md:86-102, emulator/WIRING_AND_DEPLOY.md:87-104).
  4. [same] Register/claimPOST /api/v1/machines/register with the serial, or tap the on-screen machine number → scan the QR (device.py:717-731, machines.py:181-219).
  5. [same] Planogram + pricing — set aisle products/stock/price (PUT /api/v1/inventory/…).
  6. [same] Bind NayaxPOST /api/v1/machines/{no}/nayax with the Device Number.
  7. [Reyeah] Test vend — use the manager menu (long-press the invisible top-right hotspot → cloud password → ManagementActivity) test-dispense, or am start -n com.ruiye.jd/.ui.activity.TestActivity; confirm FF00AAE10101 (OK) (emulator/WIRING_AND_DEPLOY.md:87-104, reyeah-jd-vending-kiosk/docs/operator-view-feasibility.md:13-27).
  8. [same] OTA thereafter — create a rollout; devices pull the signed APK from /apk/getUpgradeVersion (HTTPS + sha256) and ack via /apk/apkUp.

8.2 ZHZN — boxed machine to selling

  1. [same] Power + network — 12V input; set the VDC-SEL jumper for auto-power-on after outages; outbound HTTPS (t3568-board.md:18-41).
  2. [ZHZN] Wire the board — RS232 GND/TXD/RXD, 9600 8N1, to the CSM dispenser COM (vendor demo uses /dev/ttyS4); reserve a second COM for the MDB-RS232 payment adapter (t3568-board.md:26-32, INTEGRATION.md:150-154).
  3. [ZHZN] Calibrate (Y-lift) — run step calibration (0x03/0x07, save with the 0xFF lane) so aisles land; set ZHZN_COLUMNS_PER_ROW to match the grid (protocol-ylift.md:107-120, INTEGRATION.md:88-98).
  4. [ZHZN] Install the agentcp -r zhzn-vending-kiosk /opt/…, pip install -r agent/requirements.txt, fill /etc/zhzn-agent.env (ZHZN_CLOUD=kioskx, ZHZN_DEVICE_SECRET=<fleet secret>, ZHZN_SERIAL_PORT, and for kiosk-led a ZHZN_LOCAL_TOKEN), enable the systemd unit (Restart=always) (GO-LIVE.md:112-153).
  5. [same] Register/claim + catalog seam — Path A auto-provisions the serial, then create/claim the Intelliverse machine record, map serial→machine code in KIOSKX_ZHZN_MACHINE_MAP, and assign products to (row,col) slots (GO-LIVE.md:54-110).
  6. [same] Bind NayaxPOST /api/v1/machines/{no}/nayax.
  7. [ZHZN] Smoke-test one aislepython3 agent.py --once, or SerialDispenser("/dev/ttyS4",9600).dispense(aisle=1) → expect state="success"; no_ack ⇒ check baud/TX-RX/board type (INTEGRATION.md:156-169).
  8. [same] Full transaction test — buy the cheapest item; confirm the physical drop and a fall_logs row with metadata.success=true (GO-LIVE.md:154-179).
  9. [same] OTA thereafter — publish {version,url,sha256}; the agent verifies and swaps the install root, keeping <root>.old for rollback.

8.3 Factory line (China) — the acceptance gate before a unit ships

Run the APK's factory test with the cabinet open, on the line, before the unit is crated. The signature at the end is not a formality: it is the last moment anyone has physical access, and two of the facts it captures are impossible to recover remotely.

The gate refuses a signature (POST /zhzn/factory-sign → 409, and the kiosk greys its own button) until all of the following hold:

Requirement Why it blocks the ship
camera, speaker, mic, touch, screen, board all pass A skip is an unverified subsystem. "Nothing failed" is not the bar — a report with five skips and one pass used to read as a perfect machine.
Vend-board port identified — some /dev/tty* actually ACKed The SBC's physical COM headers map to Linux device nodes per unit. If the line does not record which node answered, the field agent guesses.
Tray mechanism identifiedSH (spring/spiral) or SH_YAxis (Y-lift) The two send incompatible init frames. Configure the wrong one and a healthy board answers nothing on every port — indistinguishable from a severed cable. This cost days on a live cabinet.
Camera device identified The photobooth cannot be enabled for a machine whose camera was never proven.

keypadLock is the one deliberate exception: it is fitted after the line runs, is recorded as pending, appears on the certificate as an open item, and never blocks the ship.

Once green, the discovered port and tray variant are pinned as that machine's agent config, so it boots on proven wiring on its first power-on in the field rather than on a default that may be wrong. An operator who later corrects the wiring by hand outranks the factory record and is never overwritten.

Operator X shows the certificate (technician, employee ID, lot, date), the recorded port map, and flags any cabinet that shipped unsigned.


9. Troubleshooting (common failures)

Symptom Reyeah ZHZN
Device not registered Old cloud shows "Device non-existed" — point the build at Kiosk-X (auto-provision) and claim via /api/v1/machines/register (README.md:54-62, device.py:440-450) Serial not in KIOSKX_ZHZN_MACHINE_MAP, or no Intelliverse machine record / slots — fix the catalog seam (GO-LIVE.md:77-89)
Black screen / ad loop crash An image served as type:1 crashes the video player; getAd player-safety gating prevents it and guarantees a never-empty loop (device.py:331-399,1290-1327) N/A (web storefront); check the browser kiosk app
Dispense failure Watch faults FF00AAE101xx; a failed vend ordersUpdate marks needsRefund for reconciliation (WIRING_AND_DEPLOY.md:59-60, device.py:1999-2015) Board error flag 0x03 + code (e.g. 0x07 no shipment); spring not_detected soft-success; check calibration/wiring (protocol-ylift.md:162-186, dispenser.py:36-60)
no_ack from board serial not opened / wrong port / no raw access wrong baud (must be 9600), TX/RX swap, wrong ZHZN_MODULE (INTEGRATION.md:166-169)
MQTT not connecting Best-effort only — vending still runs over /apk/*; the api_drop HTTP fallback delivers commands (mqtt_bridge.py:14-18, fulfill.py:74-80) ZHZN uses no MQTT — check heartbeat + /zhzn/pending polling instead
Nayax webhook rejected 401 = missing/wrong signature (set the real KIOSKX_NAYAX_WEBHOOK_SECRET); 404 = terminal not bound (payments-nayax.md:109-149) same webhook, same fixes
OTA won't install Non-HTTPS offer or sha256 mismatch is refused by com.kioskx.OtaIntegrity (by design) — ensure the rollout URL is HTTPS and the digest matches (commit 8d5d0f3) sha256 mismatch aborts the swap; a requirements.txt change needs pip install -r on the box (GO-LIVE.md:215-219)

10. Claims to confirm with hardware

These are not determinable from code and are marked for hardware verification:


11. Source index

Area Files
Reyeah device protocol (cloud) kiosk-x/app/routes/device.py
Animated product art / motion on screen (which surfaces can, and what each costs) kiosk-x/docs/animated-product-art.md
Reyeah APK + serial + build reyeah-jd-vending-kiosk/README.md, emulator/WIRING_AND_DEPLOY.md, scripts/build_vending_variants.sh, scripts/patch_smali.py, scripts/skin_shop_grid.py, docs/operator-view-feasibility.md
MQTT push channel kiosk-x/app/mqtt_bridge.py
ZHZN gateway (cloud) kiosk-x/app/routes/zhzn.py, kiosk-x/app/config.py (ZHZN_* keys)
ZHZN agent + protocol zhzn-vending-kiosk/agent/{config,dispenser}.py, docs/{INTEGRATION,GO-LIVE,protocol-ylift,mdb-rs232,t3568-board}.md
Operator API kiosk-x/app/routes/{machines,inventory,orders,fulfill}.py
Nayax kiosk-x/docs/payments-nayax.md, kiosk-x/app/routes/machines.py
Lifecycle / lead times kiosk-x/app/{lifecycle,dfy_manufacturing}.py, docs/{machine-lifecycle,lifecycle-matrix,autopilot-investments}.md

Cross-references: Machine client · APK rollouts · Payments & Nayax · Hybrid fulfillment · Lifecycle feature matrix.