# Vending Almost Anything

> Operate, deploy, configure, and modify the open-source XIAO / Wio Terminal vending machine in xiao-vending-machine-full-code-system. The system is a CSV-backed Node.js backend (the "brain", normally on a PC) plus two Wio Terminal firmwares: a frontend reader that dispenses and a backend RFID writer that encodes cards. Use when running or hosting the backend (local PC, Render, Codespaces, or USB-writer mode), flashing/configuring the Wio Terminals (WiFi, backend URL, device keys), choosing a deployment topology (MCU-only offline, PC-on-LAN, cloud, or one-box USB writer), adding/removing products or changing prices/servo mapping/stock/capacity, recalibrating servos, debugging the card verify / checkout / dispense flow, sourcing parts and assembling the hardware, or adapting the mechanical design to vend a different product.

- Skill: `seeed-studio/vending-almost-anything` (Agent Skill, multi-file: 4 files)
- Install (CLI): `npx skillmds@latest add seeed-studio/vending-almost-anything`
- Raw SKILL.md: https://api.skillmd.com/api/skills/seeed-studio/vending-almost-anything/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: Seeed-Studio (https://skillmd.com/u/seeed-studio)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/seeed-studio/vending-almost-anything

---


# Vending Almost Anything

The reference system that lets a Fab Lab "vend almost anything". It is a small
set of parts you can rearrange for different deployments; this skill is the map
for running it, deploying it in any topology, and changing what it sells.

## Where the code lives

Everything is under `xiao-vending-machine-full-code-system/` (the "code system"):

```text
backend-full/                              CSV-backed Node.js backend + operator dashboard (the brain)
  src/server.mjs                             the official API + static dashboard, port 3000
  data/*.csv                                 products, cards, orders, inventory, devices (the state)
  public/                                    Operate / Inventory / Config dashboard pages
  wio-rfid-writer/wio_rfid_writer/           firmware for the backend RFID WRITER Wio
  scripts/card_io.py                         USB serial card read/write (LOCAL_DEVICE_TOOLS mode)
frontend-vending-machine/
  official_frontend_wio_terminal/            firmware for the customer-facing READER Wio (dispenses)
  testing_phase/                             bring-up sketches + the offline MCU-only sketch (phase 2)
scripts/start_backend.sh                     install deps + start the backend from anywhere
render.yaml / CODESPACES_SETUP.md            one-click cloud hosting recipes
```

## The three parts

| Part | Where it usually runs | Job |
|------|----------------------|-----|
| **Backend** (`backend-full`) | a PC (or cloud) | Holds all state in `data/*.csv`; decides every dispense, refill, balance. |
| **Frontend reader** (`official_frontend_wio_terminal`) | Wio Terminal in the machine | Reads a card, asks the backend for permission, drives the 4 servos, reports back. |
| **Backend writer** (`wio-rfid-writer`) | Wio Terminal at the operator desk | Polls the backend for write jobs and encodes RFID cards. |

Two **card types** (the `type` is written onto the card by the writer):

- **direct** — an operator pre-picks the exact product(s) + quantity; the machine
  dispenses after backend approval. `{"type":"direct","user_name":..,"order_number":..,"v":1}`
- **selecting** — a stored-value balance card; the customer picks products on the
  Wio screen. Balance lives in `cards.csv`, not on the card.
  `{"type":"selecting","user_name":..,"v":1}`

## The hardware (parts + build)

The code drives a physical machine whose build lives in the sibling folder
`xiao-vending-machine-assemble-steps/`:

- **Bill of materials — what to buy:** the "What you'll buy" section of
  `xiao-vending-machine-assemble-steps/README.md` (2× Wio Terminal, 4× Feetech
  ST3215 bus servo, an RFID reader + M1 tags, a 12 V 10 A adapter + a 12 V→5 V
  buck converter, hinge / lock / M3 heat-set nuts / M3×20 + M4×20 screws, 4× PVC
  column, a PC front board). Every link there is an example — any equivalent works.
- **Assembly:** ten photographed steps plus the Wio Terminal wiring diagram
  (servo UART on header pins 8/10; RFID on the Grove I2C pins 27/28).
- **Design files:** editable STL (print) and STEP (case) under
  `xiao-vending-machine-assemble-steps/hardware-preparatory/stl-files/`.

**Power:** one **12 V** input drives the four servos; a **12 V→5 V buck converter**
supplies the 5 V USB that powers the Wio Terminal, so a single adapter runs both.

## Step 1 — pick a deployment topology

The same parts snap together four ways. Choose one, then follow its column.
Full walkthroughs are in [reference-deployment.md](reference-deployment.md).

| Topology | Backend | MCUs | Network | Tracks stock/balance? | Use when |
|----------|---------|------|---------|----------------------|----------|
| **A. MCU-only (offline)** | none | 1 reader Wio | none | No (card carries the recipe) | Demo, no PC, no WiFi, simplest possible |
| **B. PC-on-LAN (default)** | booth PC :3000 | reader + writer Wio | same WiFi | Yes | Real booth with a laptop present |
| **C. One-box USB writer** | booth PC :3000 (`LOCAL_DEVICE_TOOLS=1`) | reader Wio only | reader on WiFi; cards written over USB | Yes | Operator desk writes cards without a second Wio |
| **D. Cloud backend** | Render / Codespaces | reader + writer Wio | internet | Yes, but state is ephemeral | Remote/multi-site; hosted dashboard |

> The **official firmware requires a backend** (B/C/D). Topology **A** is a
> different, self-contained sketch (`testing_phase/2-servo-rfid-testing`) that
> dispenses a fixed recipe with no network — see reference-deployment.md.

## Step 2 — run the backend (topologies B / C / D)

From the code-system root (copy `backend-full`, `frontend-vending-machine`, and
`scripts` together, then):

```bash
bash scripts/start_backend.sh          # installs deps, serves http://localhost:3000
# or:
cd backend-full && npm install && npm start
```

Open `http://localhost:3000` for the three dashboard pages: **Operate**,
**Inventory**, **Config**. Health check: `GET /api/health`.

Common environment switches (details in [reference-backend.md](reference-backend.md)):

| Variable | Effect |
|----------|--------|
| `PORT` | Serve on a port other than 3000 (also update each Wio's backend URL). |
| `LOCAL_DEVICE_TOOLS=1` | Enable USB card writing/flashing from the dashboard (topology C). |

## Step 3 — configure and flash the Wio Terminals

Both Wios must reach the backend and present matching credentials. Two ways:

**From the dashboard (Config page, easiest).** With `arduino-cli` installed and a
Wio on USB, the Config page detects the PC's LAN IP, writes WiFi + backend URL
into the sketch, flags a network mismatch, and flashes with one click.
(`POST /api/wio/wifi`, `POST /api/wio/flash`; `target` is `frontend` or `writer`.)

**By hand,** edit the constants at the top of each sketch, then compile/upload
with `arduino-cli` (`FQBN Seeeduino:samd:seeed_wio_terminal`). The config points:

| Sketch | Constants to set |
|--------|------------------|
| `official_frontend_wio_terminal.ino` (reader) | `WIFI_SSID`, `WIFI_PASSWORD`, `BACKEND_BASE_URL` (no trailing slash), `DEVICE_ID="frontend-1"`, `API_KEY="FRONTEND_1_SECRET"` |
| `wio_rfid_writer.ino` (writer) | `WIFI_SSID`, `WIFI_PASSWORD`, `API_BASE` (note: not `BACKEND_BASE_URL`), `DEVICE_ID="wio-rfid-writer"`, `API_KEY="WIO_WRITER_SECRET"` |

Each Wio's `DEVICE_ID`/`API_KEY` must match an `enabled=true` row in
`backend-full/data/devices.csv`, or every request is rejected with `401`. Build,
flash, and calibration commands are in [reference-firmware.md](reference-firmware.md).

## Change what it sells (products)

Products map **1 product → 1 servo → 1 physical column**, servo ids `1..4` (four
columns). Two ways to change them:

- **Dashboard (runtime):** *Inventory* sets exact per-column counts and refills
  (cap 10); *Operate* writes order/balance cards and shows alerts. No restart.
- **CSV (definition):** edit `backend-full/data/` and restart the backend.
  `inventory.csv` is canonical; `product_meta.csv` holds the extra fields:

```text
# inventory.csv  (canonical: what & where & price)
product_id,name,servo_id,stock,price
P001,XIAO ESP32-S3 Sense,1,0,15.99

# product_meta.csv  (display + rules, joined on product_id)
product_id,slot_id,low_stock_threshold,max_capacity,active,tag,description,feature_1,feature_2,feature_3,image_path,product_url
P001,A1,3,10,true,Vision + Voice AI,...,...,...,...,/assets/products/xiao-esp32-s3-sense.jpg,https://...
```

**Keep these in sync (the invariants that break dispensing if ignored):**

- `servo_id` must be a unique value in **1..4**. The on-screen *selecting* menu
  and the dispense plan are indexed by servo id 1..4 — a product on servo 5, or
  two products sharing a servo, will not appear/behave correctly.
- Physical column order must match `servo_id` (product on servo 2 sits in the
  column driven by servo id 2).
- `max_capacity` (default 10) is the mechanical column limit; refills are capped
  to it. `low_stock_threshold` drives the refill/`needs_refill` alerts.
- `active=false` hides a product from selling without deleting its history.

Full CSV schema for all files (orders, cards, ledger, devices, logs) is in
[reference-backend.md](reference-backend.md).

### Vend something else (make it your own)

"Vend almost anything" means adapting this reference, not rebuilding from zero.
The `1 product → 1 servo → 1 column` model is deliberately generic:

- **Different product:** print `dispenser-specific.stl` instead of the common
  `dispenser.stl`, and/or resize the printed `dispenser` / `dispenser arm` and
  the PVC column for the new item (editable STL/STEP in the assembly guide),
  then recapture servo travel with `testing_phase/1-a` + `1-b`.
- **Everything else is unchanged:** the backend, card types, dashboard, and REST
  API do not care what a column holds — only the `product_meta.csv` labels/prices
  and the servo plan change.

Reuse the architecture; swap the physical front end.

## Change the parameters

| Parameter | Where | Notes |
|-----------|-------|-------|
| Servo travel (home/open) | `ZERO_POS[4]` / `MAX_POS[4]` in the reader `.ino` | Re-capture with `testing_phase/1-a`+`1-b`; one dispense = ZERO→MAX→ZERO. |
| Servo count / ids | `SERVO_NUM`, `ID[]` in the reader `.ino` | Bound to 4 columns; changing this ripples into the backend's 1..4 arrays. |
| WiFi / backend URL | sketch constants or Config page | URL has **no trailing slash**; `localhost` on a Wio means the Wio itself. |
| Device credentials | `devices.csv` + sketch `DEVICE_ID`/`API_KEY` | Must match; `enabled=true`. |
| Capacity / low-stock | `max_capacity` / `low_stock_threshold` in `product_meta.csv` | |
| Prices | `price` in `inventory.csv` (or dashboard) | Used for selecting-card checkout math. |
| RFID key / data blocks | `DATA_BLOCKS {4,5,6,8,9,10}`, key `FF*6` in **both** sketches | Writer and reader must agree, or reads fail. |
| Backend port | `PORT` env | Update every Wio's backend URL to match. |

## Smoke test the backend (no hardware)

```bash
curl -s http://localhost:3000/api/health
curl -s http://localhost:3000/api/products
# frontend device call needs the matching headers:
curl -s -X POST http://localhost:3000/api/frontend/verify-card \
  -H 'Content-Type: application/json' \
  -H 'x-device-id: frontend-1' -H 'x-api-key: FRONTEND_1_SECRET' \
  -d '{"type":"selecting","user_name":"Alice"}'
```

More scripted checks and the full request/response contract are in
[reference-backend.md](reference-backend.md).

## Reference files

- [reference-backend.md](reference-backend.md) — run/env, complete REST API, every
  CSV schema, the order + stock/balance lifecycle, and hosting (LAN / Render /
  Codespaces / USB) with the persistence caveat.
- [reference-firmware.md](reference-firmware.md) — the two Wio firmwares in detail,
  servo calibration workflow + numbers, RFID payload/blocks/key, build & flash,
  controls, and hardware troubleshooting.
- [reference-deployment.md](reference-deployment.md) — the four topologies A–D end
  to end, including the offline MCU-only sketch, and how changing products /
  parameters differs per topology.

The physical build — bill of materials, Wio Terminal wiring diagram, ten assembly
steps, dispense test videos, and editable STL/STEP design files — lives in the
sibling folder `xiao-vending-machine-assemble-steps/` (see its `README.md`).
