bitspire/docs/device-configuration.md
Padreug bf2b9427aa refactor(config): rename VITE_LAMASSU_* machine-config env vars to VITE_BITSPIRE_*
MACHINE_MODEL, FIAT_CODE, VALIDATOR_DEVICE, DISPENSER_DEVICE and CASSETTES
carried the old brand in their names. Renamed everywhere they are read
(device.ts, electron/main.ts), written (flake.nix, mkAtmApp.nix, live.nix,
provision-atm.sh, factory-reset-atm.sh) and documented (.env.example,
docs/device-configuration.md). No compatibility fallback in code: the
machine reads VITE_BITSPIRE_* and nothing else.

The deployed .env files are the one place the old names persist — sintra's
/var/lib/bitspire/.env holds all three keys today — and the machine reads
MACHINE_MODEL / FIAT_CODE / CASSETTES from that file on every boot. Renaming
the keys in code alone would boot a live machine on preset defaults (wrong
bays, wrong fiat) at the next nightly pull. So configuration.nix gains an
activation script, beside the existing lamassu→bitspire user migration,
that rewrites VITE_LAMASSU_* → VITE_BITSPIRE_* in that file. Idempotent;
runs before bitspire.service starts.
2026-10-09 21:57:38 +02:00

211 lines
6.9 KiB
Markdown

# Device Configuration
This document describes how to configure the ATM hardware for different machine models.
## Overview
bitSpire ships built-in presets for the hardware platforms we've tested on. Configuration is handled through:
1. **Machine presets** — built-in defaults for known hardware (Sintra, tejo, douro, batm3)
2. **Environment variables** — override any setting at runtime
3. **Runtime overrides** — programmatic configuration
## Supported machine models
### Sintra
The Sintra (Aaeon UP Board-based, Intel Atom x5-Z8350) uses:
| Device | Protocol | Path | Underlying device |
|---|---|---|---|
| Bill Validator | ID003 | `/dev/ttyJ5` | FTDI USB-serial → `/dev/ttyUSB1` |
| Bill Dispenser | F56 | `/dev/ttyJ7` | SoC MMIO UART → `/dev/ttyS4` |
| Printer | Nippon | `/dev/ttyJ4` | FTDI USB-serial → `/dev/ttyUSB0` |
**Hardware:**
- Platform: Aaeon UP Board (Intel Atom x5-Z8350)
- Validator: JCM iVIZION
- Dispenser: Fujitsu F53/F56
**Sintra-specific gotcha:** the kernel allocates `ttyS0..ttyS3` as placeholder serial nodes that error on any I/O. Only `ttyS0` (legacy 8250 at I/O 0x3f8) and `ttyS4` (SoC MMIO 16550A at 0xa171b000) are real on this hardware. The `bitspire-atm` NixOS module's `upboard.nix` keeps the kernel console on `tty0` only (not `ttyS4`) so userspace can claim `ttyS4` for the F56 dispenser.
### tejo
The Tejo runs on the same Aaeon UP Board family as Sintra; same validator + dispenser layout. Uses the same `upboard.nix` hardware module.
### Douro
Runs on a Dell OptiPlex 9030 AIO (Intel i7-4790S, Intel HD 4600) — Lamassu's stock Douro motherboard. SATA SSD instead of eMMC, eGalax touchscreen, dispenser/validator on USB-attached FTDI bridges. See `deploy/nixos/hardware/douro.nix` for the specifics.
### BATM3
The "batm3" target in this repo is **not the stock GeneralBytes BATM3** — it's a custom modification where the original ARM/Android board has been physically replaced with a Dell OptiPlex 9030 AIO (same hardware family as the Douro). The chassis, cash-handling units (MEI SCR validator/recycler + Puloon F56 dispenser), and screen are stock BATM3; everything compute-side is grafted in. See `deploy/nixos/hardware/batm3.nix` for the boot configuration; functionally it shares the Dell-board layout with Douro but with different cash hardware on the inside.
## Environment Variables
All configuration can be overridden via environment variables. For Vite/Electron, prefix with `VITE_`:
| Variable | Description | Default |
| ------------------------------- | ---------------------------- | ------------- |
| `VITE_BITSPIRE_MACHINE_MODEL` | Machine preset | `sintra` |
| `VITE_BITSPIRE_FIAT_CODE` | Fiat currency (ISO 4217) | `USD` |
| `VITE_BITSPIRE_VALIDATOR_DEVICE` | Validator serial device path | (from preset) |
| `VITE_BITSPIRE_DISPENSER_DEVICE` | Dispenser serial device path | (from preset) |
| `VITE_BITSPIRE_CASSETTES` | JSON array of cassettes | (from preset) |
### Example: Custom Cassette Configuration
```bash
# Two cassettes: $20 bills (100 count) and $50 bills (50 count)
export VITE_BITSPIRE_CASSETTES='[{"denomination":20,"count":100},{"denomination":50,"count":50}]'
```
### Example: Custom Device Paths
```bash
export VITE_BITSPIRE_VALIDATOR_DEVICE="/dev/ttyUSB0"
export VITE_BITSPIRE_DISPENSER_DEVICE="/dev/ttyUSB1"
```
## Cassette Configuration
Each cassette is defined with:
```typescript
interface CassetteConfig {
denomination: number // Bill denomination (e.g., 20 for $20)
count?: number // Number of bills loaded (optional, for inventory tracking)
}
```
### Default Sintra Configuration
```json
[{ "denomination": 20, "count": 50 }]
```
This configures a single cassette with $20 bills, 50 bill capacity.
### Multi-Cassette Example
```json
[
{ "denomination": 20, "count": 100 },
{ "denomination": 50, "count": 50 },
{ "denomination": 100, "count": 25 }
]
```
## Programmatic Configuration
You can also configure devices programmatically:
```typescript
import { getDeviceConfig, toHalConfig } from '@/config'
// Use preset with custom fiat code
const config = getDeviceConfig('sintra', 'EUR')
// Or with overrides
const config = getDeviceConfig('sintra', 'USD', {
dispenser: {
type: 'f56',
device: '/dev/ttyUSB1',
cassettes: [
{ denomination: 20, count: 100 },
{ denomination: 50, count: 50 },
],
},
})
// Convert to HAL config format
const halConfig = toHalConfig(config)
```
## Initialization
### For Production (Recommended)
The simplest way to initialize with hardware:
```typescript
import { useAtmStore } from '@/stores/atm'
const atm = useAtmStore()
// Uses environment variables with Sintra defaults
await atm.initializeForProduction()
```
### With Custom Config
```typescript
import { useAtmStore } from '@/stores/atm'
import { getDeviceConfig, toHalConfig } from '@/config'
const atm = useAtmStore()
const config = getDeviceConfig('sintra', 'USD', {
dispenser: {
type: 'f56',
device: '/dev/ttyJ7',
cassettes: [{ denomination: 20, count: 100 }],
},
})
await atm.initializeWithHal(toHalConfig(config))
```
## Verifying Device Paths
On a Sintra running Linux, verify the serial devices exist:
```bash
ls -la /dev/ttyJ*
```
Expected output on a working Sintra:
```
lrwxrwxrwx 1 root root 7 May 13 09:22 /dev/ttyJ4 -> ttyUSB0
lrwxrwxrwx 1 root root 7 May 13 09:22 /dev/ttyJ5 -> ttyUSB1
lrwxrwxrwx 1 root root 5 May 13 09:22 /dev/ttyJ7 -> ttyS4
```
(`ttyUSB0`/`ttyUSB1` are the two FTDI USB-serial bridges — printer + validator. `ttyS4` is the SoC's on-carrier MMIO UART used for the F56 dispenser.)
If the symlinks don't exist, check the udev rules in `deploy/nixos/hardware/upboard.nix`, then run `mdev -s` (or `udevadm trigger` on a non-Alpine system) to repopulate `/dev`.
If you see a symlink pointing at `ttyS1`/`ttyS2`/`ttyS3` instead, those are kernel-allocated placeholder nodes that always error on I/O — the udev rule needs to map to `ttyS4` for Sintra. The current `upboard.nix` covers all the cases (`ttyS1`, `ttyS4`, `ttyS5`) so whichever real device shows up gets the `ttyJ7` alias.
## Troubleshooting
### Permission Denied on Serial Port
Add your user to the `dialout` group:
```bash
sudo usermod -a -G dialout $USER
# Log out and back in for changes to take effect
```
### Device Not Found
1. Check the device exists: `ls -la /dev/ttyJ*` or `ls -la /dev/ttyS*`
2. Check dmesg for hardware detection: `dmesg | grep tty`
3. Verify udev rules are loaded: `udevadm info /dev/ttyS5`
### Validator Not Responding
1. Verify baud rate: ID003 uses 9600 baud, 8 data bits, even parity, 1 stop bit
2. Check cable connections
3. Power cycle the validator
4. Check validator is in "online" mode (not standalone)
### Dispenser Not Dispensing
1. Verify cassette is properly seated
2. Check for bill jams
3. Verify dispenser firmware is compatible with F56 protocol
4. Check the dispenser is powered and initialized