Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 10 additions & 14 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,25 +20,21 @@ jobs:
cache: "npm"
cache-dependency-path: frontend/package-lock.json
- run: npm ci
- run: npm test
- run: npm run build

firmware:
runs-on: ubuntu-latest
env:
FQBN: "esp8266:esp8266:d1_mini:xtal=80,vt=flash,exception=disabled,ssl=all,eesz=4M2M,ip=lm2f,dbg=Disabled,lvl=None____,wipe=none,baud=921600"
ESP8266_INDEX: "https://arduino.esp8266.com/stable/package_esp8266com_index.json"
steps:
- uses: actions/checkout@v7
- uses: arduino/setup-arduino-cli@v2
- name: Install ESP8266 core
run: |
arduino-cli core update-index --additional-urls "$ESP8266_INDEX"
arduino-cli core install esp8266:esp8266 --additional-urls "$ESP8266_INDEX"
- name: Install libraries
run: |
arduino-cli lib install ArduinoJson
arduino-cli lib install WebSockets
- uses: actions/setup-python@v5
with:
python-version: "3.x"
- name: Install PlatformIO
run: pip install -U platformio
- name: Create secrets stub
run: cp firmware/z-duino/arduino_secrets.h.example firmware/z-duino/arduino_secrets.h
run: cp secrets.ini.example secrets.ini
- name: Compile
run: arduino-cli compile --fqbn "$FQBN" firmware/z-duino
run: pio run
- name: Run native unit tests
run: pio test -e native
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
# Secrets
secrets.ini
arduino_secrets.h

# Build artifacts
build/
.pio/

# Temp / debug scripts
tmp/
Expand Down
13 changes: 11 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,26 @@ npm run dev:all # Vite dev server + mock WebSocket server
./build.sh # Interactive menu — option 7 builds + uploads everything
```

## Testing
```bash
pio test -e native # Firmware: hardware-free unit tests for lib/ (MotorLogic, LedTiming, Command)
cd frontend && npm test # Frontend: Vitest — useTrainController.ts + component smoke tests
```
Both run in CI. Code touching real hardware I/O (pins, WiFi/sockets) isn't unit-tested this way — verify manually against real hardware.

## Key Files
- `platformio.ini` — PlatformIO project config (board, ldscript, lib_deps, secrets, native test env)
- `secrets.ini` — WiFi creds & build-time config (not committed, copy from `secrets.ini.example`)
- `lib/MotorLogic/`, `lib/LedTiming/`, `lib/Command/` — hardware-independent logic, unit-tested via `pio test -e native`
- `firmware/z-duino/z-duino.ino` — Main sketch
- `firmware/z-duino/Motor.h/.cpp` — Motor abstraction
- `firmware/z-duino/StatusLED.h/.cpp` — Status LED abstraction
- `firmware/z-duino/arduino_secrets.h` — WiFi creds (not committed, copy from .example)
- `frontend/src/App.vue` — Root Vue component
- `frontend/src/composables/useTrainController.ts` — Core controller logic (WebSocket, ramping, state)
- `frontend/src/components/` — Vue SFCs (SpeedController, LedTestPanel, DebugModal, etc.)
- `frontend/mock-server.ts` — Mock WebSocket for local dev
- `docs/LITTLEFS.md` — LittleFS flash layout & mklittlefs parameters

## Dependencies
- Arduino: ESP8266 board package, ArduinoJson, WebSockets
- Firmware (PlatformIO, `espressif8266` platform): ArduinoJson, WebSockets — declared in `platformio.ini`, resolved automatically
- Node: Vue 3, Vite, Nuxt UI, Tailwind CSS v4, TypeScript
70 changes: 38 additions & 32 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,9 @@
### Prerequisites

- [Node.js](https://nodejs.org/) v18+
- [Arduino CLI](https://arduino.github.io/arduino-cli/) (for firmware work)
- ESP8266 board package: `arduino-cli core install esp8266:esp8266`
- Arduino libraries:
```bash
arduino-cli lib install ArduinoJson
arduino-cli lib install WebSockets
```
- [PlatformIO Core](https://docs.platformio.org/en/latest/core/installation/index.html) (for firmware work): `brew install platformio`

Firmware dependencies (`espressif8266` platform, `ArduinoJson`, `WebSockets`) are declared in `platformio.ini` and resolved automatically by PlatformIO on first build — no manual install step needed. See [docs/BUILD.md](docs/BUILD.md) for the full setup walkthrough.

### Frontend Development

Expand All @@ -33,9 +29,9 @@ Open `http://localhost:3000` in your browser. The throttle UI is fully functiona

1. Copy the secrets template:
```bash
cp firmware/z-duino/arduino_secrets.h.example firmware/z-duino/arduino_secrets.h
cp secrets.ini.example secrets.ini
```
2. Edit `firmware/z-duino/arduino_secrets.h` with your WiFi credentials.
2. Edit `secrets.ini` with your WiFi credentials. These are PlatformIO `build_flags` merged in via `extra_configs` — gitignored, never committed.

3. Compile:
```bash
Expand All @@ -47,15 +43,39 @@ Open `http://localhost:3000` in your browser. The throttle UI is fully functiona
./build.sh # select option 7 (Build + Upload All)
```

### Testing

**Firmware** — the hardware-independent logic (motor state decisions, LED timing math, WebSocket command parsing) lives in `lib/` and is unit-tested on PlatformIO's `native` platform, no device required:
```bash
pio test -e native
```
Code that touches actual hardware I/O (`Motor`, `StatusLED`'s pin writes, WiFi/sockets) isn't unit-tested this way — verify those manually against real hardware.

**Frontend** — `useTrainController.ts` (via a fake WebSocket) and a couple of component smoke tests, using Vitest:
```bash
cd frontend
npm test # single run
npm run test:watch # watch mode
```

### Project Structure

```
z-duino/
├── platformio.ini # PlatformIO project config (board, ldscript, lib_deps, native test env)
├── secrets.ini.example # WiFi creds template — copy to secrets.ini (gitignored)
├── lib/ # Hardware-independent logic, shared by firmware + native tests
│ ├── MotorLogic/ # Motor forward/reverse/stop decision
│ ├── LedTiming/ # StatusLED breathing/blink timing math
│ └── Command/ # WebSocket JSON command parsing
├── test/ # PlatformIO native (Unity) tests for lib/
│ ├── test_motor_logic/
│ ├── test_led_timing/
│ └── test_command/
├── firmware/z-duino/
│ ├── z-duino.ino # Main sketch (WiFi, mDNS, HTTP, WebSocket)
│ ├── Motor.h / Motor.cpp # TB6612FNG motor driver abstraction
│ ├── StatusLED.h / StatusLED.cpp # RGB status LED abstraction
│ └── arduino_secrets.h.example
│ └── StatusLED.h / StatusLED.cpp # RGB status LED abstraction
├── frontend/
│ ├── src/
│ │ ├── App.vue # Root component
Expand All @@ -64,12 +84,17 @@ z-duino/
│ │ ├── components/ # Vue SFCs (SpeedController, LedTestPanel, etc.)
│ │ └── composables/
│ │ └── useTrainController.ts # Core logic (WebSocket, ramping, state)
│ ├── test/ # Vitest suites (composable + component smoke tests)
│ ├── index.html
│ ├── mock-server.ts # Mock WebSocket server for local dev
│ ├── vite.config.ts # Builds to ../build/data/ for LittleFS
│ ├── vitest.config.ts
│ ├── tsconfig.json
│ └── package.json
├── docs/
│ ├── BUILD.md # Full software prerequisites & build script reference
│ ├── HARDWARE.md # Parts list & wiring diagram
│ ├── PROTOCOL.md # Full WebSocket protocol spec
│ ├── LITTLEFS.md # LittleFS flash layout & mklittlefs parameters
│ └── status-led-wiring.md # RGB LED wiring & resistor values
└── build.sh # Build + deploy script
Expand All @@ -84,33 +109,14 @@ z-duino/

### WebSocket Protocol

The device runs a WebSocket server on port 81 with the `arduino` subprotocol.

**Client → Device:**
| Command | Payload |
|---|---|
| Set speed | `{"cmd": "speed", "value": 0.0}` (0.0–1.0) |
| Set direction | `{"cmd": "direction", "value": true}` (true=fwd) |
| Stop | `{"cmd": "stop"}` |
| Keepalive | `{"cmd": "ping"}` |
| Direction invert | `{"cmd": "invert", "value": true}` |
| LED test mode | `{"cmd": "led", "r": 0, "g": 0, "b": 1000}` (0–1000) |
| Resume status LED | `{"cmd": "led_auto"}` |

**Device → Client:**
| Message | Payload |
|---|---|
| Status update | `{"type": "status", "name": "...", "speed": 0.0, "direction": true, "connected": true}` |
| Pong | `{"type": "pong"}` |

The mock server (`frontend/mock-server.ts`) implements the same protocol.
See **[docs/PROTOCOL.md](docs/PROTOCOL.md)** for the full spec. The mock server (`frontend/mock-server.ts`) implements the same protocol.

### Submitting Changes

1. Fork the repo
2. Create a feature branch (`git checkout -b my-feature`)
3. Make your changes
4. Test against the mock server (`npm run dev:all`)
4. Test against the mock server (`npm run dev:all`), and run `pio test -e native` / `npm test` if you touched anything covered above
5. Open a pull request

Keep PRs focused — one feature or fix per PR.
142 changes: 21 additions & 121 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
[![CI](https://github.com/yamanote1138/z-duino/actions/workflows/ci.yml/badge.svg)](https://github.com/yamanote1138/z-duino/actions/workflows/ci.yml)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](#license)
![ESP8266](https://img.shields.io/badge/ESP8266-Arduino-00979D?logo=arduino&logoColor=white)
![PlatformIO](https://img.shields.io/badge/PlatformIO-Core-FF7F00?logo=platformio&logoColor=white)
![Vue 3](https://img.shields.io/badge/Vue-3-4FC08D?logo=vuedotjs&logoColor=white)
![TypeScript](https://img.shields.io/badge/TypeScript-5-3178C6?logo=typescript&logoColor=white)
![Vite](https://img.shields.io/badge/Vite-6-646CFF?logo=vite&logoColor=white)
Expand Down Expand Up @@ -32,138 +33,37 @@ Discoverable via mDNS at `http://ztrain.local`.

Wemos D1 Mini (ESP8266) → TB6612FNG H-bridge → track, plus an RGB status LED. Full parts list, wiring diagram, and pinout live in **[docs/HARDWARE.md](docs/HARDWARE.md)**.

## Software Prerequisites
## Quickstart

### macOS Setup

1. **Arduino CLI** (via Homebrew):
```bash
brew install arduino-cli
```

2. **ESP8266 board package** — add the board manager URL, then install:
```bash
arduino-cli config init # creates ~/Library/Arduino15/arduino-cli.yaml
arduino-cli config add board_manager.additional_urls \
http://arduino.esp8266.com/stable/package_esp8266com_index.json
arduino-cli core update-index
arduino-cli core install esp8266:esp8266
```
This installs the board definitions, toolchain, `mklittlefs`, and `esptool` under:
```
~/Library/Arduino15/packages/esp8266/
```

3. **Arduino libraries:**
```bash
arduino-cli lib install ArduinoJson WebSockets
```
Libraries install to `~/Arduino/libraries/`.

4. **Node.js** (v18+) — via [nvm](https://github.com/nvm-sh/nvm) or [nodejs.org](https://nodejs.org/):
```bash
nvm install 20
```

### Verify Installation

```bash
arduino-cli version # should print version info
arduino-cli core list # should show esp8266:esp8266
arduino-cli lib list # should show ArduinoJson and WebSockets
node --version # v18+ required
```

`mklittlefs` and `esptool` don't need to be on your PATH — `build.sh` finds them automatically inside `~/Library/Arduino15/`.

## Setup

1. **Clone the repo:**
1. Install [PlatformIO Core](https://docs.platformio.org/en/latest/core/installation/index.html) (`brew install platformio`) and [Node.js](https://nodejs.org/) v18+.
2. Clone the repo and configure WiFi credentials:
```bash
git clone https://github.com/yamanote1138/z-duino.git
cd z-duino
cp secrets.ini.example secrets.ini
# edit secrets.ini with your WiFi SSID/password
```

2. **Configure WiFi credentials:**
```bash
cp firmware/z-duino/arduino_secrets.h.example firmware/z-duino/arduino_secrets.h
```
Edit `arduino_secrets.h` with your WiFi SSID and password. Optionally change the mDNS hostname (default: `ztrain`) and `MAX_PWM` to tune top speed (default: `500` — about half voltage to the track, which is plenty for Z-scale).

3. **Install frontend dependencies:**
3. Install frontend dependencies and build + flash everything:
```bash
cd frontend
npm install
cd ..
cd frontend && npm install && cd ..
./build.sh # select option 7 (Build + Upload All)
```

4. **Build and flash:**
```bash
./build.sh
```
Select option **7** (Build + Upload All). This will:
- Build the frontend with Vite
- Compile the firmware with arduino-cli
- Flash the firmware to the Wemos
- Pack and flash the frontend filesystem (LittleFS)
For the full prerequisites walkthrough and every `build.sh` option, see **[docs/BUILD.md](docs/BUILD.md)**.

## Usage

1. Power up the Wemos D1 Mini (USB or external 5V)
2. Connect your 12V supply to the TB6612FNG
3. Open a browser and navigate to **http://ztrain.local**
- If mDNS isn't working on your network, check the serial monitor (115200 baud) for the device's IP address
4. Use the throttle:
- **Speed segments 1–10** — click to ramp to that speed level. Click a lit segment to ramp back down.
- **FWD / REV** — toggle direction. If the train is moving, it will smoothly ramp down, switch, and ramp back up.
- **Brake** — smooth ramp to stop
- **E-Stop** — immediate stop

> **Z-scale note:** These locomotives are tiny and light. Don't be surprised if nothing happens at low power — it's normal for a Z-scale train to sit still until 40–50% throttle, then take off suddenly once it overcomes static friction. This isn't a bug; it's just how small motors behave at low voltage. You may want to start around segment 4 and work up from there.

## Development

For frontend development without hardware:

```bash
cd frontend
npm run dev:all
```

This starts the Vite dev server on `http://localhost:3000` and a mock WebSocket server on port 81 that simulates the ESP8266's responses.

## Build Script Options

| Option | Action |
|---|---|
| 1 | Build All (frontend + firmware) |
| 2 | Build Frontend only |
| 3 | Build Firmware only |
| 4 | Upload Firmware only |
| 5 | Upload LittleFS only |
| 6 | Upload All (firmware + filesystem) |
| 7 | Build + Upload All |

## WebSocket Protocol

The ESP8266 runs a WebSocket server on port 81 (subprotocol `arduino`).

**Client → Device:**
```json
{"cmd": "speed", "value": 0.0} // 0.0 to 1.0
{"cmd": "direction", "value": true} // true = forward
{"cmd": "stop"} // immediate stop
{"cmd": "ping"} // keepalive
{"cmd": "invert", "value": true} // motor direction invert
{"cmd": "led", "r": 0, "g": 0, "b": 1000} // LED test mode (0–1000 per channel)
{"cmd": "led_auto"} // resume status LED behaviour
```

**Device → Client:**
```json
{"type": "status", "name": "Z-Duino", "speed": 0.0, "direction": true, "connected": true}
{"type": "pong"}
```
Power up the Wemos, connect your 12V track supply, and open **http://ztrain.local** in a browser (check the serial monitor at 115200 baud for the IP if mDNS doesn't resolve). Speed segments 1–10 ramp to that throttle level, FWD/REV toggles direction (ramping down/up automatically if moving), Brake ramps to a stop, and E-Stop halts immediately.

> **Z-scale note:** These locomotives are tiny and light. Don't be surprised if nothing happens at low power — it's normal for a Z-scale train to sit still until 40–50% throttle, then take off suddenly once it overcomes static friction. Start around segment 4 and work up from there.

## Documentation

- **[docs/BUILD.md](docs/BUILD.md)** — full prerequisites, setup, and `build.sh` reference
- **[docs/HARDWARE.md](docs/HARDWARE.md)** — parts list & wiring diagram
- **[docs/PROTOCOL.md](docs/PROTOCOL.md)** — WebSocket protocol spec
- **[docs/LITTLEFS.md](docs/LITTLEFS.md)** — LittleFS flash layout & partition details
- **[CONTRIBUTING.md](CONTRIBUTING.md)** — local development setup & project structure

## License

Expand Down
Loading
Loading