Arduino MrlComm Message Schema

Message Definition

Message Direction Method Name Parameters
<  from Arduino to PC
>  from PC to Arduino
This will be the fuinction name called
Parameters can be of a variety of types.
If the type is not specified then it is of type "byte" range is 0 to 255
example :


/deviceId/animation/red/green/blue/b16 speed

Schema Datatypes

Schema Arduino Java Range
  byte int 1 byte - 0 to 255
bool bool boolean 1 byte - 0 to 1
b16 int int 2 bytes -32,768 to 32,767
b32 long long 4 bytes -2,147,483,648 to 2,147,483, 647
bu32 unsigned long long 0 to 4,294,967,295
str byte[ ] String variable length
[ ] byte[ ] int [ ] variable length
Example 1.  Arduino --to--> MrlComm
Given this Schema entry - > i2cWrite/deviceId/deviceAddress/[ ] data
This method is an i2cWrite function which writes to an I2C device on MrlComm.  It requires a deviceId of the appropriate MrlComm device, and a device address on the I2C bus with the "data" to be written.

The following code is generated in the Msg.cpp class.  A case where the message is identified will cause the Msg class to call the MrlComm's "expected" function signature with the correct parameters.

case I2C_WRITE: { // i2cWrite
        mrlComm->i2cWrite(ioCmd[1], /*deviceId*/ ioCmd[2], /*deviceAddress*/ ioCmd[3] /*dataSize*/, (byte*)(ioCmd+4) /*data*/);
The header of Msg.h & MrlComm.h is also generated.
void i2cWrite(const byte& deviceId, const byte& deviceAddress, const byte& dataSize, const byte*data);
So a corresponding i2cWrite with an appropriate parameter signature "must" be implemented in MrlComm.cpp
Example 2.
In the other direction  < publishSensorData/deviceId/[] data
the following code is generated.
void Msg::publishSensorData(const byte deviceId, const byte* data, const byte dataSize) {
  write(1 + 1 + (1 + dataSize)); // size
  write(PUBLISH_SENSOR_DATA); // msgType = 28
  write((byte*)data, dataSize);
The generated code makes no intermediate objects, instead it maintains the Msg functions to appropriately serialize and de-serialize the messages
Here is example of some of the contents of the One File Which Rules Them All
< publishMRLCommError/str errorMsg
> getBoardInfo
< publishBoardInfo/version/boardType
> analogWrite/pin/value
> controllerAttach/serialPort
> customMsg/[] msg
> deviceAttach/deviceType/str name/[] config
> deviceDetach/deviceId
> digitalWrite/pin/value
> disableBoardStatus
> disablePin/pin
> disablePins
> enableBoardStatus
> enableHeartbeat/bool enabled

Benefits of code generation

  • Less time to develop - we only write the interesting parts of functions - not the boring mundane and error prone serialization/deserialization code
  • Less errors - parsers are always one of the most difficult to make and maintain correctly
  • Less code - less code = less bugs !
  • More structured - more structure means its easier to quickly understand 
  • Easier testing

This is the "exact" set of methods from PC->Arduino and all their method signatures which makes it easy to understand and test.

   void getBoardInfo();
    void controllerAttach(const byte& serialPort);
    void customMsg(const byte& msgSize, const byte*msg);
    void deviceAttach(const byte& deviceType, const byte& nameSize, const byte*name, const byte& configSize, const byte*config);
    void deviceDetach(const byte& deviceId);
    void disableBoardStatus();
    void disablePin(const byte& pin);
    void disablePins();
    void enableBoardStatus();
    void enableHeartbeat(const bool& enabled);
    void enablePin(const byte& pin);
    void heartbeat();
    void i2cRead(const byte& deviceId, const byte& deviceAddress, const byte& size);
    void i2cWrite(const byte& deviceId, const byte& deviceAddress, const byte& dataSize, const byte*data);
    void i2cWriteRead(const byte& deviceId, const byte& deviceAddress, const byte& readSize, const byte& writeValue);
    void neoPixelSetAnimation(const byte& size, const byte& animation, const byte& red, const byte& green, const byte& blue, const int& speed);
    void neoPixelWriteMatrix(const byte& deviceId, const byte& bufferSize, const byte*buffer);
    void sensorPollingStart(const byte& deviceId);
    void sensorPollingStop(const byte& deviceId);
    void servoDetach(const byte& deviceId);
    void servoSetMaxVelocity(const byte& deviceId, const int& maxVelocity);
    void servoSetVelocity(const byte& deviceId, const int& velocity);
    void servoSweepStart(const byte& deviceId, const byte& min, const byte& max, const byte& step);
    void servoSweepStop(const byte& deviceId);
    void servoWrite(const byte& deviceId, const byte& target);
    void servoWriteMicroseconds(const byte& deviceId, const int& ms);
    void setSerialRate(const long& rate);
    void softReset();


Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
Mats's picture



i2cReadWrite is simply write a single byte first, then read.

Some i2c devices has memory. To read from memory, you first write one byte that is the pointer to the memory address where you want to start reading. 

It's there to improve performance. But it's also possible to use i2cWrite to write one byte and then i2cRead to do the read. The result will be the same.


GroG's picture

Refactor mode now ... This

Refactor mode now ...
This crud is going away ...


public void deviceAttach(DeviceControl device, Object... conf) throws Exception {
// TODO Auto-generated method stub
public void deviceDetach(DeviceControl device) {
// TODO Auto-generated method stub
Mats's picture

Ultrasonic device

Hi GroG 

Nice rework. Excited to see the result from the standardization and rework. Markus asked about how to use the ultrasonic sensor for distance measuring, and I don't think it was reworked this summer and I don't see the methods for it in the "exact" set of methods.  Problably because all the code is an the MRLUltrasonic.h file and no MRLUltrasonic.cpp exists. 

I guess it should be it's own device with it's own config and that it should use publishSensorData like all sensors ?

Just writing this so that it doesn't get "lost in translation".




GroG's picture

Yes Mats, you are correct.  I

Yes Mats,

you are correct.  I was surprised to see its non-implementation too.  

It will need to be re-worked, but the new design I hope will make it trivial to make new (or re-work old) devices.

A SerialRelay device is also needed.  I want to support Calamity's excellent feature, but its implementation needs to follow the rest of the design

Another thing not to get lost :)

GroG's picture

So I've tested all "types"

So I've tested all "types" which was a pain .. but as far as I can tell there is no bugs doing rpc calls from 
Java --to-> Arduino C++  or from
Arduino C++ --to--> Java

Here are the types supported and tested ..

Schema Arduino Java Range
  byte int 1 byte - 0 to 255
bool bool boolean 1 byte - 0 to 1
b16 int int 2 bytes -32,768 to 32,767
b32 long long 4 bytes -2,147,483,648 to 2,147,483, 647
bu32 unsigned long long 0 to 4,294,967,295
str byte[ ] String variable length
[ ] byte[ ] int [ ] variable length

With i2c writes & reads you are using Java byte[ ] which is signed (-128 to 127) ..    Arduino's definition of byte == unsigned char   (0 to 255)

I can convert your incoming bytes to unsigned equivalents.. which I 'think' would be the appropriate thing.. 
I'll check but I'm wondering if you do a conversion as well .. (the fewer the better in my opinion)

GroG's picture

Hi Mats, I'm trying to get

Hi Mats,

I'm trying to get I2C to work in the new framework

This is curious

Its linking with a global ?

Mats's picture


It's the same construct as here:

I think it can be removed since the allocation of the deviceId has moved from MRLComm to Java.

------ in the MRLComm from master
#include "Device.h"
Device::Device(int deviceType) {
  type = deviceType;
void Device::attachDevice() {
  id = nextDeviceId;
int Device::nextDeviceId=1; // device 0 is Arduino
bool Device::deviceAttach(unsigned char[], int) {
  return false; 
------ in the MRLComm from master
It was used to get a new device id.
The deviceId is passed from Arduino to MRLComm in the different attach methods now.
Next thing is about the i2cBus
The code here needs to be changed to be like this so that it corresponse to what was made in the code before.
Integer deviceId = attachDevice(control, new Object[] { busAddress, deviceAddress });
msg.i2cAttach(deviceId, busAddress, getMrlDeviceType(i2cBus), deviceAddress);
Integer deviceId = attachDevice(i2cBus, new Object[] { busAddress });
msg.i2cBusAttach(deviceId, busAddress);
The two lines above will create the representaion of the i2cBus in MRLComm. So the signature also needs to be changed. It's important that the i2cBus object is owned by the Arduino service so that the callback to the i2cBus will be able to return the data to the Arduino service in i2cRead.
This is where the "magic" for i2cRead happens: The i2cBus object returns the sensordata to the Arduino service, so that it can be returned to the caller.
This is where the data from the i2cRead ends up in the PUBLISH_SENDOR_DATA and passed on to the i2cBus object.
Pehaps there is a better and more straight on way to retúrn the i2c data to the caller, but this is how it was working before the refactoring. If you think it's a bit confusing, I agree. It was not easy to figure out a way to make it work using PUBLISH_SENSOR_DATA. Perhaps it should be a separate function to return the data from the i2cRead. That would probably remove a large bit of the complexity and make it easier to understand the code.