Skip to content

Add simavr-based automated test suite - #148

Open
GlassOnTin wants to merge 8 commits into
ToyKeeper:trunkfrom
GlassOnTin:simavr-test-suite
Open

GlassOnTin wants to merge 8 commits into
ToyKeeper:trunkfrom
GlassOnTin:simavr-test-suite

Conversation

@GlassOnTin

@GlassOnTin GlassOnTin commented Dec 10, 2025 •

Copy link
Copy Markdown

Adds a test suite that runs the actual compiled firmware hex in simavr, a cycle-accurate AVR emulator — integration tests against the real binary rather than mocked units.

The harness (sim/tests/anduril-test.[hc]) drives the firmware the way a user does: anduril_click() / anduril_multi_click() / anduril_button_set() for input, anduril_run_ticks() to advance WDT time, and anduril_get_pwm() to read what the LEDs would do. 18 tests in three suites cover the basic UI (on/off, hold, 2C turbo, 4C lockout/unlock), ramping (up/down, floor and ceiling limits, memory), and channel modes (3C switching, persistence, cycling). make test in sim/ runs everything; per-suite and verbose targets exist, and sim/README.md has the full setup and API documentation.

All 18 pass against current trunk firmware, and the assertions are negative-controlled: deliberately broken firmware (and a build with channel switching pinned to a single mode) makes the relevant tests fail rather than staying green.

Refreshed 2026-08 while rebasing onto current trunk, which caught two defects in the tests themselves, both fixed on this branch:

  • 4click_unlock sent an extra click before asserting. 4C from lockout exits straight to ON ("exit and turn on" in lockout-mode.c), so that click was turning the light back off. It now asserts the documented behaviour.
  • The channel-mode tests drove 3H expecting a mode change, but Anduril switches modes on 3C — 3H adjusts the current mode's tint/arg. Their exact-equality PWM comparisons were then passing on delta-sigma dither rather than behaviour. They now drive 3C and compare patterns against a dither-robust distance threshold. The suite passes 18/18 against trunk and against the CM_CCT (Add CCT ramp mode for D4K-3ch with colorimetric accuracy #147) and CM_CHAOS (Add chaotic pendulum color animation mode for D4K-3ch #150) branches.

External dependency: simavr with an ATtiny1634 core. Everything this suite previously needed from my fork is now merged in upstream simavr: the ATtiny1634 core (buserror/simavr#568) and the intel-hex loader use-after-free fixes (buserror/simavr#569, follow-up in buserror/simavr#574). A stock clone of https://github.com/buserror/simavr as a sibling of sim/ is all the setup required. The vendored core copy has been removed from this branch. Verified against upstream simavr master (9985961): 18/18 tests pass. CI integration is the natural follow-up now that no fork is needed.

@GlassOnTin
GlassOnTin force-pushed the simavr-test-suite branch 2 times, most recently from f8cc989 to ec77ed3 Compare December 10, 2025 15:05
@ToyKeeper

Copy link
Copy Markdown
Owner

Hi, it looks like you've been doing some really cool stuff with Anduril. Thanks!

I'm looking forward to checking it out in detail, and hopefully merging it. It might take me a while though, since I've got a lot going on and have a lot to catch up on. Just wanted to let you know, for now, that I see it and it is not being ignored. I'm just slow.

The simavr stuff in particular seems really nice. I briefly tried to simulate things once for testing purposes, and didn't get very far... so it's awesome to see that concept actually implemented. I also set up an automated hardware testing thing at one point, but it didn't end up being very practical for this purpose because button-press and measurement latency was too high, so it never got past an early proof of concept. So I've just been testing manually all this time.

Automated tests will be particularly relevant for some big upcoming changes I'm hoping to do. Specifically, I'd like to expand the simple/advanced mode concept so the user can instead choose from several entirely different interfaces. But that'll greatly expand the amount of testing needed, so automation is very welcome.

@GlassOnTin

Copy link
Copy Markdown
Author

I'd be happy to write a few tests or discuss them with you when you're looking to implement your new features.

Test Driven Development is a helpful strategy at times when pushing the feature boundaries!

@SiteRelEnby

Copy link
Copy Markdown
Contributor

Taking a look into this PR, this has been vaguely on my mind for ages but no real experience with simavr, got a bit of time at the moment so might take a dive into it.

@SiteRelEnby

Copy link
Copy Markdown
Contributor

@GlassOnTin really nice work, managed to build some interesting stuff on top of this already. Did run into a use-after-free that caused a few heisenbugs on our part, opened a PR against simavr here: buserror/simavr#569 - would appreciate it if you could test with your toolchain to see if it works with that fix as a sanity check as this is way outside my usual context of dev work 😅

GlassOnTin and others added 7 commits August 19, 2026 00:20
Adds a cycle-accurate AVR emulator test infrastructure using simavr
with ATtiny1634 support. Tests run actual compiled firmware hex files,
providing true integration testing of the firmware.

## Test Framework (sim/tests/)

- anduril-test.h/c: Test API with button simulation, PWM reading, timing
- Macros: TEST_BEGIN, TEST_PASS, TEST_FAIL, ASSERT
- Helpers: anduril_click(), anduril_multi_click(), anduril_get_pwm()

## Test Suites (18 tests total)

- test_basic_ui.c (8 tests): on/off, hold, turbo, lockout
- test_ramping.c (6 tests): ramp up/down, floor/ceiling, memory
- test_channel_modes.c (4 tests): 3C switching, persistence, cycling

## ATtiny1634 Core (sim/simavr-core/)

Custom simavr core for ATtiny1634 MCU used in Anduril flashlights:
- 16KB Flash, 1KB SRAM, 256B EEPROM
- Timer0 (8-bit), Timer1 (16-bit) with PWM
- GPIO ports A, B, C

## Makefile Targets

- make test: Run all 18 tests
- make test-basic/ramping/channel: Run individual suites
- make test-verbose: Debug output with button/interrupt traces

## Dependencies

Requires simavr with ATtiny1634 core support:
- Fork: https://github.com/GlassOnTin/simavr
- Upstream PR: buserror/simavr#568
This commit adds a protocol-based simulator interface for external UIs and
fixes a critical bug where the ADC was reading 0V, causing spurious low
voltage protection (LVP) stepdowns.

New Features:
- sim-interface: Protocol-based interface exposing Anduril simulator over
  stdin/stdout for integration with external UIs (Python, web, etc.)
- PROTOCOL.md: Complete documentation of the text-based protocol
- Temperature simulation: SETTEMP/GETTEMP commands with proper sensor emulation
- EEPROM access: GETEEPROM, DUMPEEPROM, SETEEPROM for state inspection
- Debug mode: DEBUG=1 environment variable enables detailed ADC/PWM logging
- Voltage control: SETVOLTAGE command for battery voltage simulation

Critical Bug Fixes:
- Fixed ADC reading 0V which caused firmware to think battery was dead,
  triggering constant LVP stepdowns from turbo
- Root cause: ADC values must be injected BEFORE simavr samples them, not
  in response to ADC_IRQ_OUT_TRIGGER
- Discovered simavr requires 3x voltage scaling for correct ADC conversions
  (e.g., 730mV input requires 2190mV to get correct 678 ADC result)
- Fixed ADC register addresses for ATtiny1634:
  ADCL=0x20, ADCH=0x21, ADCSRA=0x23, ADMUX=0x24

Technical Details:
- ADC now properly reads battery voltage (e.g., 3.7V → 730mV after divider
  → 678 ADC counts with 1.1V ref)
- Temperature sensor emulation uses linear mapping: 25°C = 300mV, ±10mV/°C
- Protocol supports both simple commands (CLICK, RESET) and parameterized
  commands (RUN:50, SETVOLTAGE:3700)
- Responses: OK, ERROR:msg, PWM:main2,led3,led4, EEPROM:hexdata, BYE

Protocol Command Summary:
  INIT:hexfile - Initialize simulator with firmware
  RESET - Reset AVR to power-on state
  CLICK - Single button click (~80ms press + ~128ms gap)
  MULTI:N - Multi-click sequence (2C, 3C, etc.)
  HOLD:N - Press and hold for N WDT ticks (~16ms each)
  PRESS/RELEASE - Manual button control
  RUN:N - Advance simulation by N ticks
  GETPWM - Read current PWM values
  SETVOLTAGE:mV - Set battery voltage (2000-4500mV)
  SETTEMP:C - Set temperature (-40 to 85°C)
  GETTEMP - Read current temperature
  GETEEPROM:offset,len - Read EEPROM bytes
  DUMPEEPROM - Read all 256 EEPROM bytes
  SETEEPROM:offset,value - Write EEPROM byte
  QUIT - Shutdown simulator

Files Changed:
- sim/Makefile: Added sim-interface build target
- sim/sim-interface.c: New protocol handler (479 lines)
- sim/PROTOCOL.md: Complete protocol documentation (711 lines)
- sim/tests/anduril-test.c: Fixed ADC injection, added temp/EEPROM support
- sim/tests/anduril-test.h: Added new API functions

Testing:
  make sim-interface
  echo -e "INIT:../hex/anduril.hex\nRESET\nSETVOLTAGE:3700\nRUN:50\nGETPWM\nQUIT" | ./sim-interface

This enables building external UIs (Python/Tkinter, web-based, etc.) that
communicate with the simulator via a simple text protocol over stdin/stdout.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
  - Move last_adcsra variable declaration outside #if TEST_VERBOSE block
    to fix undeclared variable error with stricter GCC 14 compiler
  - Use dynamic platform detection for simavr obj directory instead of
    hardcoded x86_64-linux-gnu (fixes Void Linux and other distros)"
The CLICK_GAP_TICKS delay made UI clicks feel unresponsive and prevented
rapid clicks from registering as multi-clicks (2C, 3C, etc).

Now anduril_click() only simulates the button press duration, letting
the UI/user control click timing naturally. Multi-click functions still
use gaps for automated testing.
Extend simulator to read and report RGB aux LED states (PA5/PA4/PA3).
Each aux LED can be: off (0), low via pullup (1), or high (2).

Changes:
- Extended pwm_state_t to include aux_r, aux_g, aux_b fields
- Updated anduril_get_pwm() to read DDRA and PUEA registers
- Extended GETPWM protocol to output 6 values instead of 3
- Detects off/low/high states by checking DDR and pullup registers

This enables UI visualization of aux LED states including low-mode
operation using the MCU's internal pullup resistors.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
lockout-mode.c exits 4C straight to steady mode at the memorized level
("4 clicks: exit and turn on"), so the extra click the test sent before
asserting was turning the light back off. Assert the documented
behaviour instead: on after unlock, off after one more click.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The channel tests sent 3H to change modes, but Anduril switches modes
on 3C -- 3H adjusts the current mode's tint/arg (or falls through to
momentary turbo in argless modes), so no mode change ever happened.
The exact-equality PWM comparisons then passed or failed on delta-sigma
dither (one high-byte step of jitter in a steady state) rather than on
behaviour.

Use 3C via a next_channel_mode() helper, and compare patterns with a
weighted channel distance and a threshold safely above the dither.
Verified against stock trunk firmware and both the CCT and chaos
channel-mode branches: 18/18 in all three cases, and the channel tests
now fail if the 3C handler is disabled.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The ATtiny1634 core (buserror/simavr#568) and both ihex loader
use-after-free fixes (#569, #574) are merged in upstream simavr,
so a stock clone of buserror/simavr now works. Also drop -lelf
from the test link flags: the tests load .hex firmware and
libsimavr.so carries its own dependencies, so the extra flag only
forced an unneeded libelf-dev install.
@GlassOnTin

Copy link
Copy Markdown
Author

@SiteRelEnby Tested with upstream simavr master (9985961). This version includes your use-after-free fix from buserror/simavr#569, the later loader fix in buserror/simavr#574, and the ATtiny1634 core from buserror/simavr#568. With my toolchain (gcc 14.3, avr-gcc 14.3, x86_64 Linux), all 18 tests pass. These consist of 8 basic UI tests, 6 ramping tests, and 4 channel mode tests.

Since a stock clone of upstream now works, I have updated this branch to remove the vendored tiny1634 core. The setup documentation now points to buserror/simavr instead of my fork. The branch is also rebased onto current trunk. Sorry for the slow reply. I am glad the suite was useful to build on.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants