Advanced: Understanding the Ardulink-2 Module Architecture

This post covers the internal architecture of Ardulink-2 — the module system, link discovery mechanism, and how to extend the framework.

Maven Multi-Module Structure

Ardulink-2 is built as a Maven multi-module project. Each module has a specific role:

ardulink-2/
├── ardulink-core-base          Core: Link interface, Pin model, events
├── ardulink-core-beans         Bean-based configuration utilities
├── ardulink-core-util          Shared utility classes
├── ardulink-core-firmata-proto Firmata protocol implementation
│
├── ardulink-link-serial-jssc   Serial transport (JSSC)
├── ardulink-link-serial-nrrxtx Serial transport (NRSerialRxTx)
├── ardulink-link-serial-rxtx   Serial transport (Rxtx)
├── ardulink-link-bluetooth     Bluetooth transport
├── ardulink-link-mqtt          MQTT transport
├── ardulink-link-nodemcu       NodeMCU/ESP8266 transport
├── ardulink-link-raspberry     Raspberry Pi GPIO
├── ardulink-link-digispark     Digispark transport
├── ardulink-link-proxy         Network proxy transport
├── ardulink-link-virtual       Virtual (testing) transport
│
├── ardulink-mqtt               Standalone MQTT bridge
├── ardulink-rest               REST API server
├── ardulink-camel              Apache Camel component
├── ardulink-swing              Swing GUI components
├── ardulink-mail               Email notifications
└── ardulink-console            Interactive console

The core abstraction is org.ardulink.core.Link:

public interface Link extends Closeable {
    Link addListener(EventListener listener) throws IOException;
    Link removeListener(EventListener listener) throws IOException;
    Link addRplyListener(RplyListener listener) throws IOException;
    Link removeRplyListener(RplyListener listener) throws IOException;
    Link addCustomListener(CustomListener listener) throws IOException;
    Link removeCustomListener(CustomListener listener) throws IOException;
    long startListening(Pin pin) throws IOException;
    long stopListening(Pin pin) throws IOException;
    long switchAnalogPin(AnalogPin analogPin, int value) throws IOException;
    long switchDigitalPin(DigitalPin digitalPin, boolean value) throws IOException;
    long sendKeyPressEvent(char keychar, int keycode, int keylocation,
            int keymodifiers, int keymodifiersex) throws IOException;
    long sendTone(Tone tone) throws IOException;
    long sendNoTone(AnalogPin analogPin) throws IOException;
    long sendCustomMessage(String... messages) throws IOException;
}

Every link adapter implements this interface. The return values are message IDs used for correlation.

Ardulink uses a convenience layer for link creation:

Links.getDefault();                           // auto-detect first link
Links.getLink("ardulink://serial?port=...");  // specific URI

How it works:

  1. Links uses LinkManager to scan the classpath for available link adapters
  2. Each link adapter registers its URI scheme (e.g., ardulink://serial, ardulink://bluetooth)
  3. getDefault() returns the first available link (serial preferred)
  4. getLink(uri) creates a link for the specific connection string

Connection URL Format

ardulink://<transport>?<param1>=<value1>&<param2>=<value2>

Examples:

  • ardulink://serial?port=/dev/ttyACM0&baudrate=115200
  • ardulink://bluetooth?address=00:11:22:33:44:55
  • ardulink://mqtt?broker=tcp://localhost:1883&topic=arduino1
  • ardulink://virtual-console
  • ardulink://virtual-random

Event System

The event system supports three listener types:

Interface Event Type Description
EventListener PinChangedEvent Pin state changes
RplyListener RplyEvent Reply/acknowledgment messages
CustomListener CustomEvent Custom string messages

Events flow from Arduino → Link adapter → Java listeners.

The ConnectionBasedLink class extends Link with connection lifecycle management:

public abstract class ConnectionBasedLink implements Link {
    // Connection management
    void addConnectionListener(ConnectionListener listener);
    void removeConnectionListener(ConnectionListener listener);

    // Connection state
    boolean isConnected();
    void disconnect();
}

Connection listeners receive reconnected() and connectionLost() callbacks.

To create a custom transport:

  1. Implement Link (or extend ConnectionBasedLink)
  2. Implement the transport-specific connection logic
  3. Register as a LinkFactory implementation
  4. Package as a Maven module
public class MyCustomLink extends ConnectionBasedLink {
    @Override
    public long switchDigitalPin(DigitalPin pin, boolean value) throws IOException {
        // Send command over your transport
        byte[] message = encodeCommand(pin, value);
        transport.send(message);
        return messageId;
    }

    // ... implement other Link methods
}

Firmata Protocol Support

Version 2.2.0 introduces experimental Firmata protocol See Firmata Protocol Support for details. Version 2.3.0 added much more Firmata protocol support

Future Direction

The 2.4.0 release notes indicate:

  • Next version will require Java 17 (dependencies like Apache Camel otherwise gets outdated)
  • Network proxy server may be removed (MQTT covers the same use case)

Next Steps