# Firmware — Blinky Node (TinyGo / ATmega328P)

`firmware.go` runs on the Blinky node. It is compiled only with TinyGo
(`//go:build tinygo`) for an AVR ATmega328P; the build tag keeps it out of the
standard Go toolchain, so the standard repo toolchain (`go build`, `go test`)
never compiles it.

## Build & flash via hil-cli

```powershell
# Check what tools are available
hil-cli firmware info

# Build firmware for Arduino Uno
hil-cli firmware build --board uno

# Flash firmware to Arduino Uno
hil-cli firmware flash --board uno --port COM3

# Build for Arduino Nano
hil-cli firmware build --board nano
hil-cli firmware flash --board nano --port COM3
```

## Build & flash directly (manual)

```powershell
# Requires TinyGo (not part of the standard toolchain check)
tinygo build -target=arduino-nano -o firmware.hex firmware.go
tinygo flash -target=arduino-nano firmware.go

# For Arduino Uno
tinygo build -target=arduino -o firmware.hex firmware.go
tinygo flash -target=arduino -port COM3 firmware.go
```

## Supported boards

| Board | Chip | TinyGo target | Flash | SRAM |
|-------|------|---------------|-------|------|
| Arduino Uno | ATmega328P | `arduino` | 32KB | 2KB |
| Arduino Nano | ATmega328P | `arduino-nano` | 32KB | 2KB |
| Arduino Mega | ATmega2560 | `arduino-mega` | 256KB | 8KB |

## Requirements

- **TinyGo** — https://tinygo.org/getting-started/ (recommended)
- **Arduino IDE** — https://www.arduino.cc/en/software (alternative, bundles avr-gcc + avrdude)
- **avrdude** — standalone flash tool (if not using TinyGo)

## Command parsing

A background `scan` loop reads UART bytes into a 128-byte buffer until `\r`.
Each frame `ACTION TYPE PIN VAL1 VAL2|CRC\r` is parsed with `extractInt` (which
handles multi-digit negatives and is what makes the autopilot DELAY encoding
work) and dispatched to `executeHardware(action, typ, pin, val1, val2)`.

`exec` matches actions case-insensitively:

| Action | Behaviour |
|--------|-----------|
| `W` | `typ D` → `digitalWrite` (val1 > 0 = HIGH); `typ A`/`P` → PWM via `setPWM` (0..255, clamped) |
| `R` | `typ D` → `digitalRead` → `D:pin:val`; `typ A` → `getAnalogPin` ADC → `A:pin:val` |
| `T` | Tone on a pin |
| `S` | Pulse: `val1` toggles at `val2` µs half-period |
| `E` | Arm digital event (`CHANGE`/`FALLING`/`RISING`) |
| `C` | Arm analog rate-of-change threshold with EMA smoothing |
| `I` | I2C write/read (`I:addr:val` replies) |
| `A` | Autopilot: clear (`C`), record (`W` + `D`), commit (`E`), boot-arm (`S`) |
| `N` | NeoPixel set/show |
| `P` | Pulse-train (legacy) |
| `H` | DHT / LM35 / ultrasonic sensor reads |
| `L` | LCD (I2C/parallel) |
| `V` | Servo sweep/target |
| `M` | MP3 player |
| `X` | Attention input |
| `Z` | PID channel config and closed/open loop |
| `F` | SD-card log |

Every accepted command replies `ACK`; reads reply with their value frames.
Frame validation rejects wrong CRC (and bad frames) without crashing the node.

## Autopilot

`AutoCmd` is an 8-byte packed record:

```
val1 int16  val2 int16  pin int8  action byte  typ byte  _ byte
```

The autopilot buffer holds 64 records (512 bytes RAM). Host `AUTOPILOT-BEGIN`
clears it; every following hardware op is recorded; `AUTOPILOT-END` commits it.
At boot, if `bootAutopilot` is armed, `runAutopilot` replays the recorded
sequence from RAM with no host tether. `runAutopilot` handles `'D'` records by
sleeping `val1` milliseconds — this supports delays up to 32767 ms and is the
fix for the earlier 127 ms ceiling (delays are recorded via `A W 68 0 0` then
`A D <ms> 0 0`).

## PWM

`setPWM` uses the TinyGo `machine` PWM, with 8-bit duty normalization and
bounds guards, so `WRITE A`/`WRITE P` values outside 0..255 are clamped on the
firmware too (the host clamps as well).

## Events

The main loop polls armed watches each cycle and emits
`EVT:D:pin:val` / `EVT_A:A:pin:val` frames when conditions fire. Watch state is
bit-packed (`watchState` uint16, `attentionMask`, `servoActive`, `pidActive`)
to keep RAM small.

## Peripheral state

PID gains are stored scaled by 100 as int32 to avoid float code. Servo targets,
analog thresholds and EMA-smoothed readings are kept in compact arrays.

> Note: `firmware.go` cannot be compiled in this workspace (TinyGo is not
> installed); it is validated with `gofmt -e`. The wire protocol it implements
> is fully covered by the monitor's Go tests.
