-
Notifications
You must be signed in to change notification settings - Fork 11
Remote Protocol Reference
This document describes the stream protocol used by the remote hardware clients in:
ArduinoCore-Linux/cores/arduino/RemoteI2C.hArduinoCore-Linux/cores/arduino/RemoteSPI.hArduinoCore-Linux/cores/arduino/RemoteGPIO.hArduinoCore-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.
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: 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
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
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.
| 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 |
-
onReceive()andonRequest()exist in the enum but are not used byRemoteI2C. -
requestFrom()returns an 8-bit reply even though the local API type issize_t. -
read()andpeek()consume a 16-bit reply, not an 8-bit reply.
| 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 |
- For
SpiTransfer, the peer must return exactlycountbytes. - The buffer transfer is in-place from the caller’s perspective: transmitted bytes are replaced by the response bytes.
| 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 |
-
pinMode()sends both pin and mode as signed 8-bit values. -
digitalRead()expects a 1-byte reply. -
pulseIn()andpulseInLong()expect an 8-byte reply.
RemoteSerialClass multiplexes multiple serial ports by prepending a port number to most commands.
| 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 |
-
SerialPeekcurrently does not send the port number even though the other Serial commands do. -
begin(baudrate, config)ignoresconfigand sends the same payload asbegin(baudrate). -
read()is implemented on top ofreadBytes()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 whenflush()or bulkwrite()runs.
HardwareSetupRemote adds an optional setup handshake when used with a stream or the default UDP transport.
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:
- captures the sender IP and port from the first packet
- sets that endpoint as the UDP target
- sends
OKas a 2-byte payload
The current implementation uses:
if (strncmp(buffer, "Arduino-Emulator", 16))
That condition is true when the strings are different, not when they match. So the current code treats a non-matching prefix as success and a matching prefix as unknown command.
Because this document reflects the implementation, a compatible peer may need to account for that bug if it depends on the built-in handshake path.
Request bytes:
- command
GpioDigitalRead=28encoded as little-endianuint16 - pin
13encoded asuint8
Reply bytes:
- one
uint8containing the resultingPinStatus
Request bytes:
- command
SpiTransfer=15as little-endianuint16 - count
4as little-endianuint32 - 4 raw payload bytes
Reply bytes:
- 4 raw response bytes
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
- handles the handshake quirks if you use
HardwareSetupRemote
The protocol definition comes directly from these files:
ArduinoCore-Linux/cores/arduino/HardwareService.hArduinoCore-Linux/cores/arduino/RemoteI2C.hArduinoCore-Linux/cores/arduino/RemoteSPI.hArduinoCore-Linux/cores/arduino/RemoteGPIO.hArduinoCore-Linux/cores/arduino/RemoteSerial.hArduinoCore-Linux/cores/arduino/HardwareSetupRemote.h