Home

Awesome

EpoxyDuino

AUnit Tests

This project contains a small (but often effective) implementation of the Arduino programming framework for Linux, MacOS, FreeBSD (experimental) and potentially other POSIX-like systems. Originally, it was created to allow AUnit unit tests to be compiled and run on a desktop class machine, instead of running on the embedded microcontroller. As more Arduino functionality was added, I found it useful for doing certain types of application development on my Linux laptop, especially the parts that were more algorithmic instead of hardware dependent. EpoxyDuino can be effectively used in Continuous Integration (CI) pipeline (like GitHub Actions) for automatically validating that a library or application compiles without errors.

The build process uses GNU Make. A simple Makefile needs to be created inside the sketch folder. For example, if the sketch is SampleTest/SampleTest.ino, then the makefile should be SampleTest/Makefile. The sketch is compiled with just a make command. It produces an executable with a .out extension, for example, SampleTest.out.

The Serial port object sends the output to the STDOUT and reads from the STDIN of the Unix host environment. Most other hardware dependent features (e.g. I2C, SPI, GPIO) are stubbed out (defined but don't do anything) to allow the Arduino programs to compile. Mock versions of various libraries are also provided:

These mock libraries may be sufficient for a CI pipeline.

For actual application development, I have started to build a set of libraries within EpoxyDuino which emulate the versions that run the actual hardware:

If your program has limited hardware dependencies so that it is conceptually portable to a vanilla Unix environment, EpoxyDuino may work well for you.

Running an Arduino program natively on a desktop-class machine has some advantages:

The disadvantages are:

Version: 1.6.0 (2024-07-25)

Changelog: See CHANGELOG.md

Table of Contents

<a name="Installation"></a>

Installation

You need to grab the sources directly from GitHub. This project is not an Arduino library so it is not available through the Arduino Library Manager in the Arduino IDE.

The location of the EpoxyDuino directory can be arbitrary, but a convenient location might be the same ./libraries/ directory used by the Arduino IDE to store other Arduino libraries:

$ cd {sketchbook_directory}/libraries
$ git clone https://github.com/bxparks/EpoxyDuino.git

This will create a directory called {sketchbook_directory}/libraries/EpoxyDuino, and put you on the default develop branch.

You can be slightly conservative and use the latest stable release on the master branch:

$ cd {sketchbook_directory}/libraries/EpoxyDuino
$ git checkout master

You can go to a specific release by checking out the corresponding tag, for example v1.2.0:

$ git checkout v1.2.0

Dependencies

The core of EpoxyDuino depends on:

These are normally installed on the host OS by default.

The example and test code under ./tests/, ./examples/, ./libraries/*/tests/, and ./libraries/*/examples/ depend on:

<a name="Usage"></a>

Usage

<a name="Makefile"></a>

Makefile

The minimal Makefile has 3 lines:

APP_NAME := {name of project}
ARDUINO_LIBS := {list of dependent Arduino libraries}
include {path/to/EpoxyDuino.mk}

For example, the examples/BlinkSOS project contains this Makefile:

APP_NAME := BlinkSOS
ARDUINO_LIBS :=
include ../../../EpoxyDuino/EpoxyDuino.mk

To build the program, just run make:

$ cd examples/BlinkSOS
$ make clean
$ make

The executable will be created with a .out extension. To run it, type one of the following:

$ ./BlinkSOS.out
$ make run

If you run :make run command inside the vim editor, it knows how to parse the file and line number contained in the assertion messages generated by AUnit. The vim editor will jump directly to the file and line where the assertion failure occurred. See https://vimhelp.org/quickfix.txt.html for information on the quickfix feature and the errorformat format that parses the GNU Make output.

The output that would normally be printed on the Serial on an Arduino board will be sent to the STDOUT of the Linux, MacOS, or FreeBSD terminal. The output should be identical to what would be shown on the serial port of the Arduino controller.

<a name="AdditionalLibraries"></a>

Additional Arduino Libraries

If the Arduino program depends on additional Arduino libraries, they must be specified in the Makefile using the ARDUINO_LIBS parameter. For example, the following includes the AUnit, AceButton, and AceTime libraries if they are installed at the same directory level as EpoxyDuino:

APP_NAME := SampleTest
ARDUINO_LIBS := AUnit AceButton AceTime
include ../../EpoxyDuino/EpoxyDuino.mk

The libraries are referred to using their base directory name (e.g. AceButton, or AceTime) not their full path. By default, the EpoxyDuino.mk file will look for these additional libraries at the following locations:

Version v1.5 supports compiling and linking plain C files in the third-party libraries (defined by ARDUINO_LIBS) or in the application directory itself. The C files are assumed to be written in C11 and are compiled using the cc compiler. It may be possible to override the compiler flags by adding -std=c99 into the EXTRA_CFLAGS in the Makefile (which clobbers the default -std=c11) but this has not been tested.

<a name="AdditionalLibraryLocations"></a>

Additional Arduino Library Locations

As explained above, EpoxyDuino normally assumes that the additional libraries are siblings to theEpoxyDuino/ directory or under the EpoxyDuino/libraries/ directory. If you need to import additional Arduino libraries, you need to tell EpoxyDuino where they are because Arduino libraries tend to be scattered among many different locations. These additional locations can be specified using the ARDUINO_LIB_DIRS variable. For example,

APP_NAME := SampleTest
arduino_ide_dir := ../../arduino-1.8.9
ARDUINO_LIBS := AUnit AceButton AceTime
ARDUINO_LIB_DIRS := \
	$(arduino_ide_dir)/portable/packages/arduino/hardware/avr/1.8.2/libraries \
	$(arduino_ide_dir)/libraries \
	$(arduino_ide_dir)/hardware/arduino/avr/libraries
include ../../EpoxyDuino/EpoxyDuino.mk

Each of the AUnit, AceButton and AceTime libraries will be searched in each of the 3 directories given in the ARDUINO_LIB_DIRS. (The arduino_ide_dir is a convenience temporary variable. It has no significance to EpoxyDuino.mk)

<a name="DifferenceFromArduinoIDE"></a>

Difference from Arduino IDE

There are a number of differences compared to the programming environment provided by the Arduino IDE:

Fortunately, the changes required to make an ino file compatible with EpoxyDuino are backwards compatible with the Arduino IDE. In other words, a program that compiles with EpoxyDuino will also compile under Ardunio IDE.

There are other substantial differences. The Arduino IDE supports multiple microcontroller board types, each using its own set of compiler tools and library locations. There is a complicated set of files and rules that determine how to find and use those tools and libraries. The EpoxyDuino tool does not use any of the configuration files used by the Arduino IDE. Sometimes, you can use the ARDUINO_LIB_DIRS to get around this limitations. However, when you start using ARDUINO_LIB_DIRS, you will often run into third party libraries using features which are not supported by the EpoxyDuino framework emulation layer.

<a name="ConditionalCode"></a>

Conditional Code

If you want to add code that takes effect only on EpoxyDuino, you can use the following macro:

#if defined(EPOXY_DUINO)
  ...
#endif

If you need to target a particular desktop OS, you can use the following:

Linux:

#if defined(__linux__)
  ...
#endif

MacOS:

#if defined(__APPLE__)
  ...
#endif

FreeBSD:

#if defined(__FreeBSD__)
  ...
#endif

<a name="ManagingMultipleMakefiles"></a>

Managing Multiple Makefiles

It is often convenient to create a parent Makefile that runs multiple targets in the Makefiles under the subdirectories. For example, most of my libraries have the following directory structure:

FooLibrary
|-- LICENSE
|-- README.md
|-- examples
|   |-- ExampleA
|   |   |-- ExampleA.ino
|   |   `-- Makefile
|   |-- ExampleB
|   |   |-- ExampleB.ino
|   |   `-- Makefile
|   |-- ...
|   |-- ExampleN
|   |   |-- ExampleN.ino
|   |   `-- Makefile
|   `-- Makefile
|-- library.properties
|-- src
|   |-- FooLibrary.h
|   `-- foolib
|       |-- file.h
|       `-- file.cpp
`-- tests
    |-- AxxTest
    |   |-- AxxTest.ino
    |   `-- Makefile
    |-- BxxTest
    |   |-- BxxTest.ino
    |   `-- Makefile
    |-- ...
    |-- MxxTest
    |   |-- MxxTest.ino
    |   `-- Makefile
    `-- Makefile

I often want to compile all of the examples , and run all the unit tests with a single command. There are multiple ways to do this, but the technique that I use is to create a parent Makefile in the examples/ and tests/ directories that recursively runs the targets of the subdirectories. In examples/Makefile, I create the following:

all:
	set -e; \
	for i in */Makefile; do \
		echo '==== Making:' $$(dirname $$i); \
		$(MAKE) -C $$(dirname $$i); \
	done

clean:
	set -e; \
	for i in */Makefile; do \
		echo '==== Cleaning:' $$(dirname $$i); \
		$(MAKE) -C $$(dirname $$i) clean; \
	done

In tests/Makefile, I create the following:

tests:
	set -e; \
	for i in *Test/Makefile; do \
		echo '==== Making:' $$(dirname $$i); \
		$(MAKE) -C $$(dirname $$i); \
	done

runtests:
	set -e; \
	for i in *Test/Makefile; do \
		echo '==== Running:' $$(dirname $$i); \
		$(MAKE) -C $$(dirname $$i) run; \
	done

clean:
	set -e; \
	for i in *Test/Makefile; do \
		echo '==== Cleaning:' $$(dirname $$i); \
		$(MAKE) -C $$(dirname $$i) clean; \
	done

To compile and run all the examples and unit tests, I do the following:

$ make -C examples clean
$ make -C tests clean

$ make -C examples all
$ make -C tests tests
$ make -C tests runtests | grep failed

If you run :make runtests inside the vim editor, it knows how to parse the diagnostic outputs generated by GNU Make (as it changes directories through the -C flag), and it knows how to parse the file and line number contained in the assertion messages generated by AUnit. The vim editor will jump directly to the file and line where the assertion failure occurred. See https://vimhelp.org/quickfix.txt.html for information on the quickfix feature and the errorformat format that parses the GNU Make output.

These parent Makefiles can also be used in Continuous Integration, as shown below.

<a name="ContinuousIntegration"></a>

Continuous Integration

You can use EpoxyDuino to run continuous integration tests or validations on the GitHub Actions infrastructure. The basic ubuntu-18.04 or ubuntu-20.04 docker image already contains the C++ compiler and make binary. You don't need to install the Arduino IDE or the Arduino CLI. You need:

Take a look at some of my GitHub Actions YAML config files:

<a name="AdvancedUsage"></a>

Advanced Usage

<a name="AlternateCompiler"></a>

Alternate C++ Compiler

Normally the C++ compiler on Linux is g++. If you have clang++ installed you can use that instead by specifying the CXX makefile variable:

$ make CXX=clang++

This tells make to set the CXX variable to clang++ within the context of EpoxyDuino.mk which causes clang++ to be used over the default g++.

If you have C files in your library or application, you can use to following to compile the C files using clang instead of cc (which is the same as gcc on Linux):

$ make CXX=clang++ CC=clang

One reason to use clang++ instead of g++ is that the clang++ compiler will sometimes catch a different set of programming errors.

<a name="AdditionalCppFlags"></a>

Additional Cpp Flags

You can pass additional flags to the C preprocessor through the EXTRA_CPPFLAGS variable, like this:

$ make EXTRA_CPPFLAGS='-D DEBUG=2'

<a name="AdditionalCompilerFlags"></a>

Additional Compiler Flags

You can pass additional flags to the C++ compiler through the EXTRA_CXXFLAGS variable, like this:

$ make EXTRA_CXXFLAGS='-g'

or

$ make EXTRA_CXXFLAGS='-g' EXTRA_CFLAGS='-g'

<a name="AdditionalCleanUp"></a>

Additional Clean Up

The make clean rule is predefined to remove all of the intermediate *.o files and GENERATED files that the EpoxyDuino.mk file knows about. Sometimes, we want to do additional clean up. For example, the EEPROM emulation libraries (EpoxyEepromAvr or EpoxyEepromEsp) will create a file in the current directory named epoxyeepromdata which stores the content of the emulated EEPROM. To remove such extra files, we can create a new Makefile target that performs the clean up, and add the name of the target to MORE_CLEAN.

For example, we create a target named more_clean to perform the extra clean up, and tell the clean target to depend on more_clean target using the MORE_CLEAN variable:

MORE_CLEAN := more_clean
APP_NAME := {name of project}
ARDUINO_LIBS := {list of dependent Arduino libraries}
include {path/to/EpoxyDuino.mk}

more_clean:
    rm -f epoxyeepromdata

<a name="AdditionalDependencies"></a>

Additional Dependencies

Sometimes the *.ino file depends on additional header files within the same directory. When these header files are modified, the *.ino file must be recompiled. These additional header files can be listed in the DEPS variable:

DEPS := header1.h header2.h
...
include {path/to/EpoxyDuino.mk}

<a name="GeneratedSourceCode"></a>

Generated Source Code

If a source file is generated dynamically through a code generation script, and the source file is not checked into the repository because it is too dynamic, then you can include the generated files using the GENERATED and the OBJS variables.

First add the list of generated files *.cpp or *.c to the GENERATED variable. Then add the corresponding *.o files to the OBJS variable, like this:

GENERATED := foo.cpp bar.cpp
OBJS := foo.o bar.o
APP_NAME := {name of project}
ARDUINO_LIBS := {list of dependent Arduino libraries}
include {path/to/EpoxyDuino.mk}

foo.cpp: foo.h generate_foo.sh
    ./generate_foo.sh # creates 'foo.cpp'

bar.cpp: bar.h generate_bar.sh
    ./generate_bar.sh # creates 'bar.cpp'

...

The *.o files in OJBS are passed to the linker when the app.out binary file is created.

The GENERATED is not strictly required, since the default rules already know how to compile the *.o files from the *.cpp or *.c files. The primary effect of GENERATED currently is to cause the generated files to be removed when make clean is called.

<a name="AlternateArduinoCore"></a>

Alternate Arduino Core

This is very advanced. The Arduino ecosystem supports different hardware processors, architectures, and platforms. The software environment for a specific hardware environment is called a "Core". By default, the environment provided by EpoxyDuino resembles the AVR Core most closely because a lot of the API emulation code was borrowed from the AVR Core.

There may be situations where an Arduino program is specifically meant to run under a hardware platform other than an AVR processor, for example, the ESP8266 Core. EpoxyDuino provides the ability substitute a different Arduino API Core through 2 Makefile variables:

EPOXY_CORE

The EPOXY_CORE Makefile variable defines the C-preprocessor macro which will be defined through the -D flag through -D $(EPOXY_CORE).

There are currently 2 valid options for this Makefile variable:

For example, setting the following in the Makefile:

EPOXY_CORE := EPOXY_CORE_ESP8266

causes the make command to pass the -D EPOXY_CORE_ESP8266 flag to the compiler, which will activate any code that is guarded by:

#if defined(EPOXY_CORE_ESP8266)
  ...
#endif

Note that:

This is because EpoxyDuino cannot provide perfect emulation of all the classes and APIs of the AVR, the ESP8266, or any other third party cores. So defining these macros would break too much code. The developer must carefully evaluate whether a #if defined(EXPOXY_CORE_ESP8266) should be enabled for code that is guarded by #if defined(ESP8266).

EPOXY_CORE_PATH

If the EPOXY_CORE make variable is insufficient (e.g. because the appropriate changes have not been incorporated into $(EPOXY_DUINO_DIR)/cores/epoxy/), then the EPOXY_CORE_PATH provides an even bigger hammer. It defines the full-path to the Arduino Core API files.

By default, this is set to $(EPOXY_DUINO_DIR)/cores/epoxy. You can create your own set of Arduino API files in a directory of your choosing, and set this make variable to point to these custom files:

EPOXY_CORE_PATH := {my_own_directory}/cores/mycore

<a name="PlatformIO"></a>

PlatformIO

The library.json file supports PlaformIO in Native mode. It was added in PR#31 (thanks @lopsided98). However, this functionality is unsupported. If it becomes broken in the future, please send me a PR to fix it.

<a name="CommandLineFlagsAndArguments"></a>

Command Line Flags and Arguments

The standard Arduino environment does not provide command line arguments, since a microcontroller does not normally provide a command line environment. When an Arduino application is compiled Using EpoxyDuino, the Unix command line parameters (argc and argv) become available through 2 global variables:

The examples/CommandLine program contains a basic command line parser which can be copied and customized for different applications:

$ ./CommandLine.out --help
Usage: ./CommandLine.out [--help|-h] [-s] [--include word] [--] [words ...]

$ ./CommandLine.out one two
arg: one
arg: two

$ ./CommandLine.out -s
flag: -s

$ ./CommandLine.out --include inc one two
flag: --include inc
arg: one
arg: two

$ ./CommandLine.out --include inc -- -one two
flag: --include inc
arg: -one
arg: two

$ ./CommandLine.out -a
Unknonwn flag '-a'
Usage: ./CommandLine.out [--help|-h] [-s] [--include word] [--] [words ...]

A more advanced example can be seen in AUnit/TestRunner.cpp.

<a name="Debugging"></a>

Debugging

A huge benefit of compiling Arduino programs using EpoxyDuino is that all the debugging tools in a Unix environment become automatically available. For example:

I am not an expert on any of these sanitizers, and I have not enabled them by default in the EpoxyDuino.mk file. But you have the capability to add them to your Makefile through the EXTRA_CXXFLAGS variable.

Below are some things that I have found useful in my own limited experience.

<a name="Valgrind"></a>

Valgrind

I have found the Valgrind tool quite helpful in tracking down Segmentation Fault crashes. Here is a quick start:

  1. Compile your program using the -g flag.
    • This is not strictly necessary but this will allow Valgrind to print line numbers to the source code in the stack trace.
    • Two ways:
      • Pass the pass through the command line: $ make EXTRA_CXXFLAGS=-g
      • Edit the Makefile and add a EXTRA_CXXFLAGS = -g directive near the top of the file.
  2. Run the program under the valgrind program.
    • Valgrind has tons of options and flags. Here are the flags that I use (don't remember where I got them):
      • $ alias val='valgrind --tool=memcheck --leak-check=yes --show-reachable=yes --num-callers=20 --track-fds=yes'
      • $ val ./MyProgram.out

When the program crashes because of a nullptr dereference, Valgrind will show exactly where that happened in the source code.

<a name="MockDigitalReadDigitalWrite"></a>

Mock digitalRead() digitalWrite()

EpoxyDuino is not meant to simulate the actual hardware. By default, the digitalRead() and digitalWrite() functions are just stubs which don't do anything. However for testing purposes, it is sometimes useful to be able to control the values returned by digitalRead(), or to read back the value written by digitalWrite(). Two functions have been added to EpoxyDuino to allow this mocking.

<a name="DigitalReadValue"></a>

digitalReadValue()

The digitalReadValue(pin, val) function sets the value that will be returned by the next digitalRead(pin). Here is an example of how this can be used:

#include <Arduino.h>

...
const uint8_t PIN = 8;

void something() {
  uint8_t val = digitalRead(PIN); // val == 0

#if defined(EPOXY_DUINO)
  digitalReadValue(PIN, 1);
#endif
  val = digitalRead(PIN); // val == 1

#if defined(EPOXY_DUINO)
  digitalReadValue(PIN, 0);
#endif
  val = digitalRead(PIN); // val == 0
}

The #if defined(EPOXY_DUINO) is recommended because digitalReadValue() is not a standard Arduino function. It is defined only in EpoxyDuino.

The pin parameter should satisfy 0 <= pin < 32. If pin >= 32, then digitalReadValue() is a no-op and the corresponding digitalRead(pin) will always return 0.

<a name="DigitalWriteValue"></a>

digitalWriteValue()

The digitalWriteValue(pin) function returns the value that was written by the most recent digitalWrite(pin, val). Here is an example of how this can be used:

#include <Arduino.h>

...
const uint8_t PIN = 9;

void something() {
  digitalWrite(PIN, 0);

#if defined(EPOXY_DUINO)
  uint8_t val = digitalWriteValue(PIN);
  // val should be 0
#endif

  digitalWrite(PIN, 1);

#if defined(EPOXY_DUINO)
  uint8_t val = digitalWriteValue(PIN);
  // val should be 1
#endif
}

The #if defined(EPOXY_DUINO) is recommended because digitalWriteValue() is not a standard Arduino function. It is defined only in EpoxyDuino.

The pin parameter should satisfy 0 <= pin < 32. If pin >= 32, then digitalWriteValue() always return 0.

<a name="SupportedArduinoFeatures"></a>

Supported Arduino Features

<a name="ArduinoMacros"></a>

Arduino Macros

The following C-preprocessor macros are defined:

The following are not defined. Many third party libraries tend to break with those defined because EpoxyDuino does not emulate those environments perfectly:

<a name="ArduinoFunctions"></a>

Arduino Functions

The following is an incomplete list of Arduino functions and features which are implemented:

See Arduino.h for the latest list. Most of the header files included by this Arduino.h file were copied and modified from the arduino:avr core, v1.8.2 or v1.8.3. A number of tweaks have been made to support slight variations in the API of other platforms, particularly the ESP8266 v2.7.4 and ESP32 v1.0.6 cores.

The Print::printf() function is an extension to the Print class that is provided by many Arduino-compatible microcontrollers (but not the AVR controllers). It is implemented here for convenience. The size of the internal buffer is 250 characters, which can be changed by changing the PRINTF_BUFFER_SIZE parameter if needed.

<a name="SerialPortEmulation"></a>

Serial Port Emulation

The Serial object is an instance of the StdioSerial class which emulates the Serial port using the STDIN and STDOUT of the Unix system. Serial.print() sends the output to the STDOUT and Serial.read() reads from the STDIN.

The interaction with the Unix tty device is complicated, and I am not entirely sure that I have implemented things properly. See Entering raw mode for in-depth details. The following is a quick summary of how this is implemented under EpoxyDuino.

The STDOUT remains mostly in normal mode. In particular, ONLCR mode is enabled, which translates \n (NL) to \r\n (CR-NL). This allows the program to print a line of string terminating in just \n (e.g. in a printf() function) and the Unix tty device will automatically add the \r (CR) to start the next line at the left. (Interestingly, the Print.println() method prints \r\n, which gets translated into \r\r\n by the terminal, which still does the correct thing. The extra \r does not do any harm.)

The STDIN is put into "raw" mode to avoid blocking the loop() function while waiting for input from the keyboard. It also allows ICRNL and INLCR which flips the mapping of \r and \n from the keyboard. That's because normally, the "Enter" or "Return" key transmits a \r, but internally, most string processing code wants to see a line terminated by \n instead. This is convenient because when the \n is printed back to the screen, it becomes translated into \r\n, which is what most people expect is the correct behavior.

The ISIG option on the tty device is enabled. This allows the usual Unix signals to be active, such as Ctrl-C to quit the program, or Ctrl-Z to suspend the program. But this convenience means that the Arduino program running under EpoxyDuino will never receive a control character through the Serial.read() function. The advantages of having normal Unix signals seemed worth the trade-off.

<a name="UnixLineMode"></a>

Unix Line Mode

The Print class in the Arduino API implements the Print::println() function by printing the DOS line terminator characters \r\n. This decision make sense when the serial port of the microcontroller is connected to a serial terminal, which requires a \r\n at the end of each line to render the text properly.

But when the Arduino application is executed on Linux machine, and the output is redirected into a file, the \r\n is not consistent with the Unix convention of using only a single \n to terminate each line. This causes the file to be interpreted as a DOS-formatted file. Usually the DOS formatted file can be processed without problems by other Linux programs and scripts, but sometimes the extra \r\n causes problems, especially when mixed with a Serial.printf() function using a single \n.

EpoxyDuino provides a mechanism to configure the line termination convention for a given application by providing 2 additional methods to its Print class:

class Print {
  public:
    // Use DOS line termination. This is the default.
    void setLineModeNormal();

    // Use Unix line termination.
    void setLineModeUnix();

    ...
};

When an Arduino application is executed on a Linux machine using EpoxyDuino, you can configure the Serial object in the *.ino file to use the Unix convention like this:

void setup() {
#if ! defined(EPOXY_DUINO)
  delay(1000); // wait to prevent garbage on Serial
#endif

  Serial.begin(115200);
  while (!Serial); // Leonardo/Micro

#if defined(EPOXY_DUINO)
  Serial.setLineModeUnix();
#endif
}

Why isn't setLineModeUnix() simply made to be the default on EpoxyDuino? Because people write AUnit unit tests which they expect will pass on both the microcontroller and on EpoxyDuino:

#include <Arduino.h>
#include <AUnit.h>
#include <AceCommon.h> // PrintStr<N>
...

static void sayHello(Print& printer) {
  printer.println("hello");
}

test(myTest) {
  PrintStr<200> observed;
  sayHello(observed);
  assertEqual(observed.cstr(), "hello\r\n");
}

<a name="EnableTerminalEcho"></a>

Enable Terminal Echno

By default, the stdin of the terminal is set to NOECHO mode for consistency with the actual serial port of an Arduino microcontroller. However when running a command line utility on a Unix terminal emulator using EpoxyDuino, it is often useful to enable echoing so that the characters being typed are visible.

To enable echoing, call the enableTerminalEcho() function from the global setup():

void setup() {
  ...

#if defined(EPOXY_DUINO)
  enableTerminalEcho();
#endif

  ...
}

<a name="LibrariesAndMocks"></a>

Libraries and Mocks

The Arduino ecosystem provides thousands of libraries that can be used to extend the functionality of an Arduino application. Some of these libraries will work perfectly fine with EpoxyDuino, some will not. It is difficult to categorize these libraries in a sensible way in the context of EpoxyDuino, but here is my current attempt:

These 3 types are described in more detail below.

<a name="InherentlyCompatibleLibraries"></a>

Inherently Compatible Libraries

Almost all libraries that I write will be inherently compatible with EpoxyDuino because EpoxyDuino is what I use to develop and test my libraries. For example, the following should compile using EpoxyDuino:

There are probably many other 3rd party libraries which are inherently compatible with EpoxyDuino but we won't know until we try to compile them under EpoxyDuino. If there are compile-time problems, it may be possible that only a small set of tweaks are required to make it work. Often, the fixes are similar to the changes needed to make the library cross-compatible with other Arduino platforms.

<a name="EmulationLibraries"></a>

Emulation Libraries

These libraries are designed partially or fully emulate the functionality a particular Arduino library in the Unix-like desktop environment using EpoxyDuino. I have provided 3 such libraries within the EpoxyDuino project:

Since the desktop environment already has a working network stack, I hope to make create additional network libraries (HTTP client, HTTP Server, MQTT client, etc) for EpoxyDuino, so that even more of the Arduino development can be done on the Linux/MacOS host.

<a name="MockLibraries"></a>

Mock Libraries

Mock libraries are designed to run under EpoxyDuino and provide non-working API stubs of the target library. These libraries are useful to verify that a program compiles, but they do not allow us to actually verify that the library works as intended. This limitation may be sufficient for Continuous Integration purposes.

<a name="SystemRequirements"></a>

System Requirements

Tier 1: Fully Supported

The following environments are tested on each release of EpoxyDuino.

Tier 2: Best Effort

The following environments are supported on a best-effort basis because I don't test them as often.

Tier 3: Should Work

The following environments are older OS environments which worked with previous versions of EpoxyDuino. But I am not able to validate them against the latest EpoxyDuino release because I no longer use these older environments.

<a name="License"></a>

License

MIT License

<a name="BugsAndLimitations"></a>

Bugs and Limitations

<a name="FeedbackAndSupport"></a>

Feedback and Support

If you have any questions, comments, or feature requests for this library, please use the GitHub Discussions for this project. If you have bug reports, please file a ticket in GitHub Issues. Feature requests should go into Discussions first because they often have alternative solutions which are useful to remain visible, instead of disappearing from the default view of the Issue tracker after the ticket is closed.

Please refrain from emailing me directly unless the content is sensitive. The problem with email is that I cannot reference the email conversation when other people ask similar questions later.

<a name="Authors"></a>

Authors