/virtualavr

Run Arduino/AVR Code on your PC (inside a docker container) including support for serial communication. State of the simulated microcontroller can be seen and controlled via websockets

Primary LanguageJavaScript

Publish Docker image Docker Image CI Docker Image Size Docker Image Version Docker Pulls

virtualavr

An AVR/Arduino Simulator based on avr8js with focus on automated tests.

  • You want to test your microcontroller program on an integration level without flashing a real microprocessor every time?
  • You want to test some code that interacts with a microprocessor but you want to test without having real hardware connected and flashed (e.g. on a ci server)?

This is where virtualavr comes into play

virtualavr comes as a Docker image that provides a virtual AVR including a virtual serial device which you can connect to just like to real hardware.

Start the container (will load the included blink sketch) docker run -v /dev:/dev -d pfichtner/virtualavr

Connect to virtual serial device minicom -D /dev/virtualavr0

Full example, you can pass the devicename as well the code that gets compiled and the executed on the virtual AVR docker run -e VIRTUALDEVICE=/dev/ttyUSB0 -e FILENAME=myArduinoSketch.ino -v /dev:/dev -v /path/of/the/sketch:/sketch -d pfichtner/virtualavr

Environment variables supported

  • VIRTUALDEVICE the full path of the virtual device that socat creates (defaults to /dev/virtualavr0)
  • DEVICEUSER user the VIRTUALDEVICE belongs to (default root)
  • DEVICEGROUP group the VIRTUALDEVICE belongs to (default dialout)
  • DEVICEMODE file mode of the VIRTUALDEVICE (default 660)
  • FILENAME the name of the ino/hex/zip file (defaults to sketch.ino). Zipfile content is wokwi structure (sketch.ino, libraries.txt). If the filename ends with '.hex' it gets passed to virtualavr directly
  • BAUDRATE baudrate to use (defaults to 9600). Hint: If haven't seen problems when baudrate differs from the really used one
  • VERBOSITY verbosity args for socat e.g. "-d -d -v" see man socat for more infos. That way you can see what is "copied" by socat from serial line to avr8js/node and vice versa
  • PUBLISH_MILLIS analog values gets published each millis on change (default 250)
  • MIN_DIFF_TO_PUBLISH only publish analog values if they differ more than this value (default 0)

Screencast of usage

The screencast is not uptodate!!!

  • The prefered way of setting pin states is no more "fakePinState" but "pinState"
  • You now have to enable the reporting of pin states by sending a websocket message { "type": "pinMode", "pin": "D13", "mode": "digital" }

Websocket messages

Sent by virtualavr

  • Changes when listening for digital pin state changes { 'type': 'pinState', 'pin': 'D13', 'state': true }
  • Changes when listening for analog pin state changes { 'type': 'pinState', 'pin': 'A0', 'state': 42 }
  • When data is received via serial line and serial debug is enabled { 'type': 'serialDebug', 'direction': 'RX', 'bytes': (bytes received) }
  • When data is send via serial line and serial debug is enabled { 'type': 'serialDebug', 'direction': 'TX', 'bytes': (bytes send) }

Accepted by virtualavr

  • Set the mode for which pin what messages should be send: { "type": "pinMode", "pin": "D12", "mode": "analog" } (supported modes: analog (or alternative pwm), digital, any other value means off)
  • Set a pin to the passed state/value { "type": "pinState", "pin": "D12", "state": true }
  • Set a (PWM) pin to the passed state/value { "type": "pinState", "pin": "D12", "state": 42 }
  • Enable/disable serial debug { "type": "serialDebug", "state": true|false }

Testing your sketch within your prefered programming language

Because virtualavr offers a websocket server to interact with you can write your tests with any language that supports websocket communication (there shouldn't be many language without). So here's an example of a Java (JUnit5) Test

private static final String INTERNAL_LED = "D13";

@Container
VirtualAvrContainer<?> virtualavr = new VirtualAvrContainer<>() //
					.withSketchFile(new File("blink.ino"));

@Test
void awaitHasBlinkedAtLeastThreeTimes() {
  VirtualAvrConnection virtualAvr = virtualavr.avr();
  virtualAvr.pinReportMode(INTERNAL_LED, DIGITAL);
  await().until(() -> count(virtualAvr.pinStates(), PinState.on(INTERNAL_LED)) >= 3
		  && count(virtualAvr.pinStates(), PinState.off(INTERNAL_LED)) >= 3);

}

What's inside? How does it work?

  • The heart is avr8js
  • virtualavr.js runs inside a node process, and links nodejs' stdin/stdout to avr8js' virtual serial port
  • socat creates a virtual serial port on the local machine (better said inside the docker container) and links this virtual serial port to nodejs' stdin/stdout. That way you get a virtual serial port which is connected to the serial port of the simulator (avr8js)
  • Due to the whole thing is packaged inside a docker container the serial port is inside that docker container, too (and only). So you have to do volume mounts (-v /dev:/dev) so that you get access to the "in-docker-device" on your local computer
  • Virtualavr starts a websocket server you can connect to. Using that websocket connection you can control the states of the analog/digital pins as well cou get informed about things hapening on the virtual AVR e.g. state changes of the pins

virtualavr.png

Todos

  • Add support for running simulator without VIRTUALDEVICE (VIRTUALDEVICE="")
  • Add time/cpu-cycles to ws messages
  • Provide Java Bindings as maven artefacts
  • Can we connect other handles than stdin/stdout so that we still ca write to stdout from within nodejs/virtualavr.js?
  • Compile local instead of cloud service, using https://arduino.github.io/arduino-cli/0.22/installation/ and https://www.npmjs.com/package/arduino-cli
  • Add an example (jest?): How to test firmware, e.g. firmware reading DHT22 values and writing infos/warnings to console/SSD1306
  • We could use WS to interact with the simulator: "loadFirmware", "start", "stop", ...
  • Possibility to define component layout, e.g. add a DHT22
  • JS Callbacks for pin states/Components, e.g. DHT22
  • Java-Bindings for pin states/Components, e.g. DHT22 (IPC, using websockets?)
  • Make websockets port configurable
  • Watch support: Recompile/reload firmware when changed on filesystem
  • Could we implement upload? So that you can upload the compiled firmware to runniner container / /dev/virtualdevice? Could we use arduino firmware? https://github.com/arduino/ArduinoCore-avr/tree/master/bootloaders/atmega : If this works? Do we have to upload elf binaries?