Hardware Extensions SKDs


Right now, there is already an extensive list of hardware devices that you can add to Ruby to extend the functionality, as seen in the side picture (read more about it on the Hardware section).

Still, this might not satisfy the needs of any project or customization. As such, a hardware SDK is provided in order to add new types of hardware peripherals to Ruby.

Optional peripherals that can be attached to Ruby hardware
The way it works is as follows:
  • Create your own hardware as needed, using microcontrollers (Arduino, ESP, others) or other type of hardware;
  • Write the software you need on your microcontroller and use this hardware SDK to communicate with Ruby. You can download the full I2C protocol definition from here.
  • Your hardware will connect with Ruby using I2C interface and (optionally, for high data transfers bandwidth) SPI interface. Those interfaces where choosen because they are standard and easy to use and you can have multiple devices on the same phisical wires, with no need for multiple connections.
  • Use the hardware SDKs to communicate between Ruby and your hardware.


Download the hardware SDK and a sample plugin from here.

Exposed Interfaces/Commands/Data Messages:

The SDK provides access to a set of standard commands using I2C, commands that are exchanged between Ruby and your hardware in order to provide the desired functionalities.
The list of commands and what data you can exchange can be further customised if you use them in conjunction with the OSD plugins and Streams plugins.

Each time Ruby boots, it uses the I2C bus to query for hardware extensions and then gets the exposed capabilities of each hardware device it founds.
Then, the user interface and Ruby capabilities change based on the detected hardware extensions and the capabilities they provide.

The runtime behaviour is as follows:
Ruby (I2C master device) sends commands to your hardware device (I2C slave device);
Your device needs to respond according to the received commands.

Note: All commands and responses have 1 extra byte at the end as the CRC for the exchanged data;
Note2: Always check the CRC value when you get something. Ignore the commands with bad CRC. There can always be elextrical or RF noise or bad I2C devices on the I2C bus.

I2C commands, flags and data structures:

Here is the full description of the I2C interface and capabilities. Ruby will always act as I2C master and send commands to your hardware in order to configure it or to request data from it; your hardware device must act as a I2C slave device and only respond to the commands and data requests it receives.

All commands sent by Ruby are in this format:
1 byte: start command header, always 0xFF;
2 byte: command id, see below;
N bytes: command data, size depending on command;
1 byte: CRC;

All responses sent by your device are in this format:
N bytes: response data, size depending on the command;
1 byte: CRC;

There is a sample CRC function in the hardware SDK. You should use it to make sure you compute the CRC in the same way Ruby does.

List of commands and responses:

I2C_COMMAND_ID_GET_FLAGS (0x01)

Get flags: master asks the slave for the capabilities it supports;
Master sends: 1 byte: command id (I2C_COMMAND_ID_GET_FLAGS)
Slave responds: 2 bytes: capabilities (flags as OR-ed bits) supported by the I2C device;

Here is the list of supported capabilites. Your device needs to respond only with those it does support:
I2C_CAPABILITY_FLAG_SPI ((u16)(((u16)0x01)<<1)) // Set if the slave device does support SPI communication with the Ruby controller (if not, only I2C is used)
I2C_CAPABILITY_FLAG_BUTTONS ((u16)(((u16)0x01)<<2)) // Set if the slave device has buttons (for UI navigation in Ruby);
I2C_CAPABILITY_FLAG_ROTARY ((u16)(((u16)0x01)<<3)) // Set if the slave device has rotary encoder (for UI navigation/Camera control);
I2C_CAPABILITY_FLAG_ROTARY2 ((u16)(((u16)0x01)<<4)) // Set if the slave device has the secondary rotary encoder (for UI navigation/Camera control);
I2C_CAPABILITY_FLAG_LEDS ((u16)(((u16)0x01)<<5)) // Set if the slave device has LEDs to be controlled by the Ruby controller;
I2C_CAPABILITY_FLAG_RC_INPUT ((u16)(((u16)0x01)<<6)) // Set if the slave device has RC input hardware;
I2C_CAPABILITY_FLAG_RC_OUTPUT ((u16)(((u16)0x01)<<7)) // Set if the slave device should output RC frames to the FC;
I2C_CAPABILITY_FLAG_FLIGHT_CONTROL ((u16)(((u16)0x01)<<8)) // Set if the slave device wants to send flight commands to the vehicle;
I2C_CAPABILITY_FLAG_CAMERA_CONTROL ((u16)(((u16)0x01)<<9)) // Set if the slave device wants to send camera commands (brightness, contrast, etc);
I2C_CAPABILITY_FLAG_SOUNDS ((u16)(((u16)0x01)<<10)) // Set if the slave device can play sounds (alarms);

I2C_COMMAND_ID_GET_VERSION (0x02)

Get flags: master asks the slave for the version;
Master sends: 1 byte: command id (I2C_COMMAND_ID_GET_VERSION);
Slave responds: 1 byte: version of the software on the I2C slave device;

I2C_COMMAND_ID_GET_NAME (0x03)

Get name: master asks slave for the device name, to be used in the user interface;
Master sends: 1 byte: command id;
Slave responds: I2C_PROTOCOL_STRING_LENGTH bytes: null terminated string, padded with 0 up to I2C_PROTOCOL_STRING_LENGTH bytes;

I2C_COMMAND_ID_SET_ADDRESS (0x04)

Set address: master asks slave to set it's address to a custom one, to avoid conflicts
Master sends: 2 bytes: command id and the new I2C address to be used by slave device;
Slave responds: 1 byte: 0 - ok, 1 - error

I2C_COMMAND_ID_GET_BUTTONS_EVENTS (0x10)

Get buttons: master asks slave if any buttons where pressed;
Master sends: 1 byte: command id;
Slave responds: 4 bytes:
first 2 bytes: each bit represents 1 if a button was pressed, for a possible of 16 buttons on the device;
last 2 bytes: each bit represents 1 if a button was long pressed, for a possible of 16 buttons on the device;
bit 0 - Menu/Ok button
bit 1 - Cancel button
bit 2 - Plus button
bit 3 - Minus button
bit 4 - QA1 button
bit 5 - QA2 button
bit 6 - QA3 button
bit 7 - Action Plus button
bit 8 - Action Minus button
bit 9... - future use

I2C_COMMAND_ID_GET_ROTARY_EVENTS (0x11)
I2C_COMMAND_ID_GET_ROTARY_EVENTS2 (0x12)

Get rotary encoder events (for main and secondary rotary encoders, if present);
Master asks slave if any rotary encoder events (first or secondary rotary encoder) took place.
Master sends: 1 byte: command id;
Slave responds: 1 byte: each bit represents:
bit 0: rotary encoder was pressed;
bit 1: rotary encoder was long pressed;
bit 2: rotary encoder was rotated CCW;
bit 3: rotary encoder was rotated CW;
bit 4: rotary encoder was rotated fast CCW;
bit 5: rotary encoder was rotated fast CW;

I2C_COMMAND_ID_SET_RC_INPUT_FLAGS (0x20)

Set RC Input flags: master tells the slave what RC protocol to read and if the RC input UART should be inverted or not (SBUS should be inverted).
Master sends: 2 bytes: command id and the RC input flags:
bit 0-4: RC protocol: 1 - parse SBUS RC input; 2 - parse IBUS RC input; 4 - parse PPM RC input;
bit 4: 0 - non inverted UART, 1 - invert UART;
Slave responds: 1 byte: 0 - ok, 1 - error

I2C_COMMAND_ID_RC_GET_CHANNELS (0x21)

Gets the current RC channels values from the slave device;
Master sends: 1 byte: command id;
Slave responds: 26 bytes: 1 byte flags, 1 byte frame number, 24 bytes for RC channels values (16 channels, 12 bits per channel, for 0...4095 posible values).
byte 0: flags: bit 0 is set if RC input failsafe event occured on the slave device.
byte 1: frame number: monotonically increasing on each received RC frame
byte 1-16 - LSBits (8 bits) of each channel, from ch 1 to ch 16;
byte 17-24 - MSBits (4 bits) of each channel, from ch 1 to ch 16;
Unused channels should be set to zero.

I2C_COMMAND_ID_SET_RC_OUTPUT_FLAGS (0x30)

Set RC Output flags: master tells the slave what RC protocol to generate and if the RC output UART should be inverted or not (SBUS should be inverted).
Master sends: 2 bytes: command id and the RC output flags (same bits as RC input flags):
bit 0-4: RC protocol: 1 - SBUS RC output; 2 - IBUS RC output; 4 - PPM RC output;
bit 4: 0 - non inverted UART, 1 - invert UART;
Slave responds: 1 byte: 0 - ok, 1 - error

I2C_COMMAND_ID_RC_SET_CHANNELS (0x31)

Sets the current RC channels values to the slave device;
Master sends: 26 bytes: 1 byte command id, 1 byte flags, 24 bytes: RC channels values (16 channels, 12 bits per channel, for 0...4095 posible values).
byte 0: command id (this one);
byte 1: flags: bit 0: set if failsafe should be signaled by the slave device; not set otherways;
byte 2-17 - LSBits (8 bits) of each channel, from ch 1 to ch 16;
byte 18-25 - MSBits (4 bits) of each channel, from ch 1 to ch 16;
Unused channels should be set to zero.

I2C_COMMAND_ID_FLIGHT_CTRL_QUERY_ARM (0x40)

Ask the slave device if the vehicle should receive the arm or disarm command
Master sends: 1 byte: command id;
Slave responds: 1 byte:
bit 0: 0 - no change; 1 - has new arm state
bit 1: 0 - disarm; 1 - arm

I2C_COMMAND_ID_FLIGHT_CTRL_QUERY_MODE (0x41)

Ask the slave device if the vehicle should receive a new flight mode
Master sends: 1 byte: command id;
Slave responds: 1 byte:
bit 0: 0 - no change; 1 - has new flight mode;
bit 1..7: new flight mode as defined by ArduPilot;

I2C_COMMAND_ID_CAMERA_CTRL_QUERY (0x50)

Ask the slave device if they have any pending camera params changes (or wants to change something);
Master sends: 1 byte: command id;
Slave responds: 1 byte:
bit 0: 0 - no change; 1 - wants to do some changes

I2C_COMMAND_ID_CAMERA_CTRL_GET_PARAMS (0x51)

Ask the slave device for the new camera params;
Master sends: 1 byte: command id;
Slave responds: 4 bytes:
byte 0: brightness (0..100)
byte 1: contrast (0..100)
byte 2: saturation (0..100)
byte 3: sharpness (0..100)

I2C_COMMAND_ID_PLAY_SOUND_ALARM (0x60)

Ask the slave to play a particular sound;
Master sends: 2 bytes: command id; sound id:
1 - battery alarm;
2 - arm alarm;
3 - disarm alarm;
Slave responds: 1 byte:
bit 0: 0 - failed; 1 - succeeded;

Note: Right now the SPI interface is not used, it's for future use, for devices that do not have a I2C interface or for devices that need faster data transfers or for devices that require higher data throughput.

You can download a sample of a working hardware plugin here. It's the default Ruby Pico addon provided with Ruby.