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.
211 lines
6.9 KiB
Markdown
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
|