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
108 changes: 94 additions & 14 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,107 @@

All notable changes to UnoSim. Versions follow [Semantic Versioning](https://semver.org/).

## Unreleased (integration branch `feature/browser-emulation`)
## v2.0.0-beta.1 – 2026-10-24

First pre-release of version 2 from the integration branch `feature/browser-emulation`
for a pilot instance next to the V1 instance (plan 9.2). Version 2 runs the compiled sketch in the browser instead of on the server and
adds the Arduino Nano, the Arduino Mega 2560 and the ATtiny85 as preview
boards. Contracts: REST API `1.1.0` (additive, `1.0.0` clients stay
supported), WebSocket `1.0.0` (unchanged; serial messages may carry an optional
`port`), embedding API (`postMessage`) `1.5.0` (additive). The decisions are
drafted in ADR 0009 and ADR 0010 (`Proposed`).

### Behaviour changes for teaching

Check slides and exercises against these points; the user guide section
"Grenzen der Simulation" describes the model, "Legacy" keeps the former
behaviour for the Uno.

- **Data types are those of the board:** `int` has 16 bit (`32767 + 1` is
−32768, formerly 32768), `long` 32 bit, `double` is the 32-bit `float`,
`sizeof(int)` is 2.
- **`Serial.print(2.5)` prints `2.50`** (two decimals without a precision
argument), as on the board; formerly `2.5`.
- **`Serial.parseInt()` without input waits one second** (the stream timeout)
and then returns 0; formerly it returned at once.
- **Speed is the board's:** `loop()` runs as often per second as on a 16 MHz
board (ATtiny85: 8 MHz); counters in `loop()` without `delay()` reach other
values than before.
- **Memory is the board's:** 2 KB SRAM on the Uno and Nano; a large array can
crash the sketch as on the board. UnoSim reports a stack that grows into the
globals or the heap, but not every memory error.
- **`random()` gives the Uno's sequence.**
- **Pause halts `millis()`.**
- **An open input (`INPUT`) reads LOW**, `INPUT_PULLUP` reads HIGH until a
level is set at the board.
- **Serial input sent before `Serial.begin()` is lost** as on the board; a
system line reports it.
- **The run time counts wall-clock time:** on a device slower than real time
(real-time factor below 1 in the header) a 30-second run ends after less
simulated time.
- **EEPROM content stays per board while the tab is open**; a reload starts
with an erased EEPROM.

### Upgrade notes for operators

- **Three images** from the same commit: `unosim-server`, `unosim-sandbox`
(host mock) and the new `unosim-compile-sandbox`; `npm run build:sandbox`
builds both sandbox images. In the Docker profile every compile runs
`arduino-cli` in its own container without network; the backend image no
longer contains `arduino-cli` or `g++` (1.62 GB → 726 MB). The backend
checks the compile sandbox image at startup.
- **New variables** (Compose passes them from `.env`; empty means default):
`UNOSIM_RUNTIME_BACKENDS` (`browser`, `host-mock`), `UNOSIM_DEFAULT_RUNTIME`,
`UNOSIM_BOARDS`, `UNOSIM_DEFAULT_BOARD`. `ARDUINO_FQBN` remains as an alias
for the default board and is deprecated.
- **Docker profile default stays the host mock** until the release is accepted;
a pilot instance sets `UNOSIM_DEFAULT_RUNTIME=browser` (INSTALL_SERVER.md,
"Browser-Emulation im Docker-Profil (Pilot)"). Without the browser backend only the Uno is offered.
- **Return to V1** works with the unchanged `.env` and `storage/`
(INSTALL_SERVER.md, "Update und Rollback").
- **Local installations** need ATTinyCore 1.5.2 for the ATtiny85:
`sh scripts/install-attinycore.sh` (INSTALL_LOCAL.md).
- `npm run capacity:calibrate` measures compile capacity as well
(CAPACITY_MODEL.md, "Compile capacity").

### Added

- **Tools → Board** with the Arduino Nano (A6/A7 as analog-only inputs), the
Arduino Mega 2560 (pins 0–69, four serial ports with a port bar in the
Serial Monitor) and the ATtiny85 (ATTinyCore 1.5.2, 8 MHz, `Serial` as a
half-duplex software UART), each with its own board view and with warnings
for its pins. The choice is kept per browser; a change stops the run.
- **Examples can name their board**; loading one for another board asks first.
- **Diagnostics** in the Serial Monitor: stack collision, program counter
outside the program, watchdog reset, `sleep` with interrupts disabled.
- **Debug bar:** engine (`BROWSER` or `LEGACY`) and real-time factor.
- **I/O Registry** keeps the static entries during a run and marks what the
run confirmed ("+ runtime").
- **Tutor** questions name the board; the Tutor knows its data type sizes.
- **Embedding API `1.5.0`:** `GET_STATE` with board, runtime and serial ports;
pins are checked against the selected board; `SERIAL_INPUT` and serial
events carry the port.

### Changed

- **The Arduino Uno now runs in the browser by default** (local profile). The
compiled sketch is executed by an emulator of the ATmega328P in a Web Worker;
`int` is 16 bit, `32767 + 1` is −32768, `sizeof`, `millis()` and `delay()`
behave like the board, and a running sketch keeps going when the connection to
the server is lost. Pause halts the simulated time. File → Settings →
"Simulation engine" switches to "Legacy" (the former server-side
simulation). The user guide section "Grenzen der Simulation" describes the
model.
- Serial input that arrives before `Serial.begin()` is discarded like on the
board and reported with a system line.
- REST API `1.1.0` (additive: `board` and `artifact` in `POST /api/compile`);
`1.0.0` clients stay supported. WebSocket `1.0.0` and the embedding API
`1.4.0` are unchanged.
- **Sketches run in the browser by default** (local profile): the compiled
machine code runs in an emulator of the board's microcontroller (avr8js in a
Web Worker). A started run keeps going when the connection to the server is
lost. File → Settings → "Simulation engine" switches to "Legacy" (the former
server-side simulation, Uno only).
- `POST /api/compile` returns the flash image as `artifact` (REST `1.1.0`);
the HEX file never leaves the server.

### Fixed

- The serial input field could not be typed into while it was empty.
- A sketch printing without pause (`Serial.println(millis())`) froze the
browser; the Serial Monitor keeps the last 40 000 characters.
- The editor keeps the indentation on Enter, indents after `{` and closes
brackets and quotes.
- On phones the panel navigation covered pins of the board view.
- The ATtiny85 drives the PWM signal of timer 1 on PB1 and PB4.
- A hidden browser tab no longer ends a run as "stopped responding".

## v1.0.1 – 2026-10-18

Expand Down
28 changes: 18 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ and an interactive Arduino Preview for sketches.

- **Code Editor**: Monaco editor integration for writing Arduino sketches with syntax highlighting
- **Compilation**: Compile Arduino code through the UnoSim backend
- **Browser emulation of the Arduino Uno**: the ATmega328P runs in your browser (avr8js in a Web Worker) and executes the machine code of the compiled sketch, so data types, overflow and timing behave like the board; the server only compiles. "Legacy" (File → Settings → Simulation engine) keeps the server-side simulation
- **Browser emulation of the Arduino Uno**: the ATmega328P runs in your browser (avr8js in a Web Worker) and executes the machine code of the compiled sketch, so data types, overflow and timing behave like the board; the server only compiles. "Legacy" (File → Settings → Simulation engine) keeps the server-side simulation for the Uno
- **More boards (preview)**: Arduino Nano, Arduino Mega 2560 (four serial ports, A0–A15) and ATtiny85 (ATTinyCore, `Serial` as software UART) under Tools → Board, each with its own board view
- **Serial Monitor**: Real-time output display from the simulated Arduino
- **Pause/Resume Simulation**: Pause running sketches to inspect state, change pin values, and resume execution
- **Arduino Preview**: A preview of analog/digital inputs and outputs directly in the Arduino SVG
Expand All @@ -51,7 +52,7 @@ and an interactive Arduino Preview for sketches.

- **Frontend**: React, TypeScript, Vite, TailwindCSS, Radix UI
- **Backend**: Node.js (TypeScript), Express, WebSocket support
- **Emulation**: avr8js (ATmega328P) in a Web Worker
- **Emulation**: avr8js (ATmega328P, ATmega2560, ATtiny85) in a Web Worker, packaged as the workspace package `@unosim/avr-emulator`
- **Editor**: Monaco Editor
- **Testing**: Vitest with React Testing Library
- **Build Tools**: Vite, esbuild
Expand All @@ -64,7 +65,7 @@ UnoSim runs on Linux and macOS. Production always runs in Docker; see
### Prerequisites

- Node.js 24.20.0 LTS (see `.nvmrc`) and npm
- [Arduino CLI](https://arduino.github.io/arduino-cli/) with the `arduino:avr` core
- [Arduino CLI](https://arduino.github.io/arduino-cli/) with the `arduino:avr` core, and ATTinyCore for the ATtiny85 (`sh scripts/install-attinycore.sh`, see [docs/INSTALL_LOCAL.md](docs/INSTALL_LOCAL.md))

Docker is not needed for development.

Expand All @@ -76,13 +77,15 @@ cd UnoSim
npm ci
arduino-cli core update-index
arduino-cli core install arduino:avr
sh scripts/install-attinycore.sh # optional: only for the ATtiny85
npm run dev:full
```

Open `http://localhost:3001`. `npm run dev:full` starts the backend
(`127.0.0.1:3000`) and the Vite dev server with hot reload; `npm run dev`
starts only the backend. The development profile is single-user, compiles and
runs sketches directly on your machine and accepts only loopback requests.
starts only the backend. The development profile is single-user, compiles
sketches directly on your machine, runs them in the browser emulation (the
"Legacy" host mock as a local process) and accepts only loopback requests.

Examples load from the public GitHub repository
`ttbombadil/unosim-examples` at `main` by default. Each browser can choose a
Expand All @@ -102,25 +105,30 @@ changing the server default; see
| `npm run check` | TypeScript type-check (`tsc --noEmit`) |
| `npm run test:fast` | Run deterministic unit tests |
| `npm run test:integration` | Run Arduino/toolchain integration tests |
| `npm run test:docker` | Run Docker sandbox integration tests |
| `npm run test:docker` | Run Docker sandbox integration tests, including the compile sandbox contract |
| `npm run build:sandbox` | Build the simulation sandbox and the compile sandbox image |
| `npm run emulate -- <hex> --board <id>` | Run a HEX file headless in the emulator and measure its speed |
| `npm run check:bundle` | Check the initial client bundle budget and that avr8js stays in the worker chunk |
| `npm run test:e2e` | Run Playwright browser tests |
| `npm run capacity:calibrate` | Measure host/Docker simulation capacity and generate review-only Capacity recommendations |
| `npm run capacity:calibrate` | Measure host/Docker compile and simulation capacity and generate review-only Capacity recommendations |
| `npm run test:all` | Run unit, integration and Docker gates |
| `./run-tests.sh` | Full pipeline: lint, unit tests, Docker build, integration tests, E2E |
| `RUN_HEAVY_TESTS=1 ./run-tests.sh` | Full pipeline including the slow parallel-instance stress test |

For host-specific Capacity sizing, use `npm run capacity:calibrate` and follow
the review workflow in [`docs/CAPACITY_MODEL.md`](docs/CAPACITY_MODEL.md).
Calibration needs a built application (`npm run build`) and the sandbox image
(`npm run build:sandbox`); it aborts early with a hint if the build is missing.
and the compile sandbox (`npm run build:sandbox` builds both); it aborts early with a hint if the build is missing.

### Architecture Overview

The full picture is in [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md).

- **Sandbox Runner Pool** — Manages runner leases for sketch execution. In the documented production path, each simulation runs in a short-lived isolated Docker sandbox with stdout/stderr capture for serial output and pin state reporting.
- **Browser emulation** — The client runs the compiled flash image in a Web Worker (`packages/avr-emulator`, avr8js). `POST /api/compile` returns the image as `artifact`; the run itself needs no server and survives a lost connection.
- **Compile sandbox** — In the Docker profile every compile runs `arduino-cli` in its own short-lived container without network (`Dockerfile.compile-sandbox`).
- **Sandbox Runner Pool** — Manages runner leases for the host mock ("Legacy"). In the documented production path, each host-mock simulation runs in a short-lived isolated Docker sandbox with stdout/stderr capture for serial output and pin state reporting.
- **Compilation Worker Pool** — In production mode, the configured Node.js Worker Thread pool handles compilations in parallel via `CompilerWithFallback`. The Compose reference uses 8 workers (the pool runs at most 8); a full pool answers `503 SYSTEM_BUSY`. Current capacity guidance is in [`docs/CAPACITY_MODEL.md`](docs/CAPACITY_MODEL.md); [`docs/CAPACITY_HISTORICAL_PHASE_3_4.md`](docs/CAPACITY_HISTORICAL_PHASE_3_4.md) keeps the historical measurement.
- **WebSocket Layer** — Real-time communication between client and server for serial output, pin state batches, and simulation control (start/stop/pause/resume).
- **WebSocket Layer** — Real-time communication between client and server for host-mock runs (serial output, pin state batches, simulation control) and status.
- **SonarQube Integration** — Optional SonarQube scans are wired into the pre-push hook and `./run-tests.sh` when `SONAR_TOKEN` and a reachable SonarQube service are available. Release blocking is controlled by `REQUIRE_RELEASE_GATE=1`.

## Notes for running tests (optional)
Expand Down
Loading
Loading