Skip to content

Remote Protocol Reference

Phil Schatzmann edited this page May 8, 2026 · 3 revisions

This document describes the stream protocol used by the remote hardware clients in:

  • ArduinoCore-Linux/cores/arduino/RemoteI2C.h
  • ArduinoCore-Linux/cores/arduino/RemoteSPI.h
  • ArduinoCore-Linux/cores/arduino/RemoteGPIO.h
  • ArduinoCore-Linux/cores/arduino/RemoteSerial.h

It documents the protocol exactly as it is currently implemented in the codebase, including a few quirks that are important when building a compatible peer.

Overview

The remote API tunnels hardware operations over an arbitrary Arduino Stream.

Each request starts with a 16-bit command ID from HWCalls, followed by zero or more binary arguments. Some commands are fire-and-forget, while others are request/response operations that expect data back from the remote peer.

There is no framing header, checksum, version byte, or length prefix around the full message. Message boundaries are implied entirely by the command ID and the fixed/known argument layout for that command.

Transport assumptions

  • Transport: any bidirectional Stream
  • Typical transport: UDP via WiFiUDPStream
  • Byte order: little-endian on the wire for 16/32-bit values
  • Blocking reads: up to 1000 ms in HardwareService::blockingRead()
  • Reliability: not provided by the protocol itself

Primitive encoding

HardwareService serializes values in this order:

  • HWCalls: 16-bit unsigned integer
  • uint8_t: 1 byte
  • bool: sizeof(bool) bytes on the current platform
  • uint16_t: 2 bytes, little-endian
  • uint32_t: 4 bytes, little-endian
  • uint64_t: 8 bytes, little-endian
  • raw buffers: written as-is, no extra length field unless the command defines one

Command table

The numeric command IDs are the ordinal values of enum HWCalls in declaration order.

ID Command Area
0 I2cBegin0 I2C
1 I2cBegin1 I2C
2 I2cEnd I2C
3 I2cSetClock I2C
4 I2cBeginTransmission I2C
5 I2cEndTransmission1 I2C
6 I2cEndTransmission I2C
7 I2cRequestFrom3 I2C
8 I2cRequestFrom2 I2C
9 I2cOnReceive I2C
10 I2cOnRequest I2C
11 I2cWrite I2C
12 I2cAvailable I2C
13 I2cRead I2C
14 I2cPeek I2C
15 SpiTransfer SPI
16 SpiTransfer8 SPI
17 SpiTransfer16 SPI
18 SpiUsingInterrupt SPI
19 SpiNotUsingInterrupt SPI
20 SpiBeginTransaction SPI
21 SpiEndTransaction SPI
22 SpiAttachInterrupt SPI
23 SpiDetachInterrupt SPI
24 SpiBegin SPI
25 SpiEnd SPI
26 GpioPinMode GPIO
27 GpioDigitalWrite GPIO
28 GpioDigitalRead GPIO
29 GpioAnalogRead GPIO
30 GpioAnalogReference GPIO
31 GpioAnalogWrite GPIO
32 GpioTone GPIO
33 GpioNoTone GPIO
34 GpioPulseIn GPIO
35 GpioPulseInLong GPIO
36 GpioAnalogWriteFrequency GPIO
37 GpioAnalogWriteResolution GPIO
38 SerialBegin Serial
39 SerialEnd Serial
40 SerialWrite Serial
41 SerialRead Serial
42 SerialAvailable Serial
43 SerialPeek Serial
44 SerialFlush Serial
45 I2sSetup I2S
46 I2sBegin3 I2S
47 I2sBegin2 I2S
48 I2sEnd I2S
49 I2sAvailable I2S
50 I2sRead I2S
51 I2sPeek I2S
52 I2sFlush I2S
53 I2sWrite I2S
54 I2sAvailableForWrite I2S
55 I2sSetBufferSize I2S

Only I2C, SPI, GPIO, and Serial are used by the current Remote*.h clients.

I2C protocol

Requests

Command Request payload Reply
I2cBegin0 none none
I2cBegin1 uint8 address none
I2cEnd none none
I2cSetClock uint32 freq none
I2cBeginTransmission uint8 address none
I2cEndTransmission1 bool stopBit uint8 status
I2cEndTransmission none uint8 status
I2cRequestFrom3 uint8 address, uint64 len, bool stopBit uint8 count
I2cRequestFrom2 uint8 address, uint64 len uint8 count
I2cWrite uint8 value uint16 bytesWritten
I2cAvailable none uint16 available
I2cRead none uint16 value
I2cPeek none uint16 value

Notes

  • onReceive() and onRequest() exist in the enum but are not used by RemoteI2C.
  • requestFrom() returns an 8-bit reply even though the local API type is size_t.
  • read() and peek() consume a 16-bit reply, not an 8-bit reply.

SPI protocol

Requests

Command Request payload Reply
SpiTransfer8 uint8 data uint8 result
SpiTransfer16 uint16 data uint16 result
SpiTransfer uint32 count, count raw bytes count raw bytes
SpiUsingInterrupt int32 interruptNumber none
SpiNotUsingInterrupt int32 interruptNumber none
SpiBeginTransaction uint32 clock, uint8 bitOrder, uint8 dataMode none
SpiEndTransaction none none
SpiAttachInterrupt none none
SpiDetachInterrupt none none
SpiBegin none none
SpiEnd none none

Notes

  • For SpiTransfer, the peer must return exactly count bytes.
  • The buffer transfer is in-place from the caller’s perspective: transmitted bytes are replaced by the response bytes.

GPIO protocol

Requests

Command Request payload Reply
GpioPinMode int8 pin, int8 mode none
GpioDigitalWrite uint8 pin, uint8 status none
GpioDigitalRead uint8 pin uint8 status
GpioAnalogRead uint8 pin uint16 value
GpioAnalogReference uint8 mode none
GpioAnalogWrite uint8 pin, int32 value none
GpioTone uint8 pin, uint32 frequency, uint64 duration none
GpioNoTone uint8 pin none
GpioPulseIn uint8 pin, uint8 state, uint64 timeout uint64 pulseLength
GpioPulseInLong uint8 pin, uint8 state, uint64 timeout uint64 pulseLength
GpioAnalogWriteFrequency uint8 pin, uint32 freq none
GpioAnalogWriteResolution uint8 bits none

Notes

  • pinMode() sends both pin and mode as signed 8-bit values.
  • digitalRead() expects a 1-byte reply.
  • pulseIn() and pulseInLong() expect an 8-byte reply.

Serial protocol

RemoteSerialClass multiplexes multiple serial ports by prepending a port number to most commands.

Requests

Command Request payload Reply
SerialBegin uint8 portNo, uint64 baudrate none
SerialEnd uint8 portNo none
SerialWrite uint8 portNo, uint64 len, len raw bytes uint16 bytesWritten
SerialRead uint8 portNo, uint64 len raw bytes, up to requested length
SerialAvailable uint8 portNo uint16 available
SerialPeek none uint16 value
SerialFlush uint8 portNo none

Notes

  • SerialPeek currently does not send the port number even though the other Serial commands do.
  • begin(baudrate, config) ignores config and sends the same payload as begin(baudrate).
  • read() is implemented on top of readBytes() and local buffering.
  • readBytes() does not receive a length prefix; it simply reads as many bytes as become available, up to the requested count and subject to the read timeout.
  • write(uint8_t) only appends to a local buffer; the actual protocol write happens when flush() or bulk write() runs.

Handshake behavior

HardwareSetupRemote adds an optional setup handshake when used with a stream or the default UDP transport.

Host-side behavior

When begin() performs a handshake, the host waits until at least 16 bytes are available and then reads 18 bytes from the stream.

The code comment says it expects the device to send Arduino-Emulator.

After UDP auto-discovery succeeds, the host:

  1. captures the sender IP and port from the first packet
  2. sets that endpoint as the UDP target
  3. sends OK as a 2-byte payload

Minimal examples

Example: digital read on pin 13

Request bytes:

  1. command GpioDigitalRead = 28 encoded as little-endian uint16
  2. pin 13 encoded as uint8

Reply bytes:

  1. one uint8 containing the resulting PinStatus

Example: SPI transfer of 4 bytes

Request bytes:

  1. command SpiTransfer = 15 as little-endian uint16
  2. count 4 as little-endian uint32
  3. 4 raw payload bytes

Reply bytes:

  1. 4 raw response bytes

Interoperability checklist

If you implement the peer side, make sure it:

  • reads the first 2 bytes as a little-endian command ID
  • knows the exact payload shape for each command
  • returns replies only for commands that expect them
  • returns the exact byte count expected by the caller

Source of truth

The protocol definition comes directly from these files:

  • ArduinoCore-Linux/cores/arduino/HardwareService.h
  • ArduinoCore-Linux/cores/arduino/RemoteI2C.h
  • ArduinoCore-Linux/cores/arduino/RemoteSPI.h
  • ArduinoCore-Linux/cores/arduino/RemoteGPIO.h
  • ArduinoCore-Linux/cores/arduino/RemoteSerial.h
  • ArduinoCore-Linux/cores/arduino/HardwareSetupRemote.h

Clone this wiki locally