Argot. Building a language for Cubes.

Freetronics Cube Serial Tutorial

freetronics-cube-serial-argot-1.0.zip

The Freetronics Cube is a great small Arduino kit that allows anyone to build a 4x4x4 cube that is fully programmable. I decided that this will make a perfect playground to build and test various aspects of Argot. This tutorial provides a more complete demo of building a more complex language to communicate with a device. If you have a cube and just looking for something to play with, this is also a bit of fun. It allows you to create small Cube programs which can be sent in a single message to the cube. I've also provided a small Processing example showing driving the cube from a simple user interface. The interface could easily be expanded to allow people to create their own small programs and send them to the cube. If anyone does this please let me know.

Just show me the demo

If you're just interested in playing with your cube and don't want to read through the full tutorial, you need to do the following

  1. Download and unzip the archive.
  2. Copy the Cube4 library into the Arduino Libraries directory. This is a stripped down version of the full library provided by Freetronics. This is required as the Freetronics library has an inbuilt serial library.
  3. Open the ArgotCubed.ino file and upload it to the cube. Once loaded the cube should turn fully white.
  4. Open the ProcessingCubed.pde file in the Processing application.

If you've successfully loaded the program onto the cube and started the Processing application, a colour picker and heart will appear. Click the mouse on the colour picker and the cube will change to that colour. Selecting the heart will send a short series of commands to the cube which mimic the heart demo that comes with the Freetronics cube library.

Here's a small clip I took with showing the finished product. Excuse the low quality of the video.

Argot Cubed Tutorial

Following the rest of this tutorial, you should get an understanding of how this small demo was put together and how Argot assists in the development of the Cube protocol. To fully run through the tutorial, you require:

  1. To have read over the Argot quick start example.
  2. A Freetronics cube. If you don't have a cube, the tutorial still provides a good example of building a protocol using Argot.
  3. Have the Rxtx serial library installed. For OSX I used this native library.
  4. Have Eclipse or other Java IDE to run the software.

Follow the instructions above to get started with the project in Arduino and Processing. To setup the project in eclipse, do the following:

  1. In Eclipse select File->Import.
  2. Select "General->Existing Projects into Workspace".
  3. Select "Select root directory" and browse to the directory in the JavaCubed folder.
  4. Select finish.

To run the example in Java, run the ArgotCubed as a Java Application. This demonstrates using the Cube API direct from Java.

The Argot Cube Protocol

The Cube protocol is designed to allow either single command or multiple commands to be sent in a single message. Currently, the commands consist of instructions to set the cube to a colour, set a single light to a colour, set a plane in the x,y or z direction and loop through a set of commands. With the addition of the command to delay, it provides enough of a language to send small programs. Here's the Argot definition file. Most of the definitions should be self explanatory.

/* All definitions are in the cube cluster */
cluster cube;

/* Using the cube.command before it is defined.  Reserve is used when there are cyclical references */

!reserve cube.command;


/* cube.colour is red,green and blue. */

definition cube.colour 1.0:
{
	@red #uint8;
	@green #uint8;
	@blue #uint8;	
};


/* set colour sets an individual LED on the cube */

definition cube.set_colour 1.0:
{
	@x		#uint8;
	@y		#uint8;
	@z		#uint8;
	@colour #cube.colour;
};


/* set all, sets the whole cube to a single colour */

definition cube.set_all 1.0:
{
	@colour #cube.colour;
};

/* set column sets a single vertical column to a specific colour */

definition cube.set_column 1.0:
{
	@x		#uint8;
	@y		#uint8;
	@colour #cube.colour;
};

/* set z plane sets a horizontal layer on the cube to a specific colour */

definition cube.set_z_plane 1.0:
{
	@position #uint8;
	@colour #cube.colour;
};

/* set x plane sets a vertical layer on the cube to a specific colour */

definition cube.set_x_plane 1.0:
{
	@position #uint8;
	@colour #cube.colour;
};

/* set y plane sets a vertical layer on the cube to a specific colour */

definition cube.set_y_plane 1.0:
{
	@position #uint8;
	@colour #cube.colour;
};

/* delays the next change for a specific time */

definition cube.delay 1.0:
{
	@milliseconds #uint16;
};

/* A list of up to 255 commands in a single message */

definition cube.command_list 1.0:
(meta.sequence [
	(meta.array
		(meta.reference #uint8)
		(meta.reference #cube.command))]);

/* Repeat a list of commands up to 255 times */

definition cube.loop 1.0:
{
	@start #uint8;
	@commands #cube.command_list;
};

definition cube.command 1.0:
	(meta.abstract[
		(meta.abstract_map #cube.set_colour)
		(meta.abstract_map #cube.set_all)
		(meta.abstract_map #cube.set_x_plane)
		(meta.abstract_map #cube.set_y_plane)
		(meta.abstract_map #cube.set_z_plane)
		(meta.abstract_map #cube.set_column)
		(meta.abstract_map #cube.delay)
		(meta.abstract_map #cube.loop)
	]);

/* For serial communications wrap the command in a simple packet structure */

definition cube.packet 1.0:
{
	@start #uint8;
	@length #uint8;
	@command #cube.command;
};

Most of the type formats are using the Argot simple definition format. This format is available for the most commonly used sequence definitions. However, the base Argot format is more like a S-Expression style format. For instance the cube.packet definition directly above could be defined with the following format:

definition cube.packet 1.0:
	(meta.sequence[
		(meta.tag u8utf8:"start" (meta.reference #uint8))
		(meta.tag u8utf8:"length" (meta.reference #uint8))
		(meta.tag u8utf8:"command" (meta.reference #cube.command))
	]);

The Java Host

Each of the defined types above are mirrored in Java with individual classes. All the classes have the same basic structure. In fact, it would not be difficult to extend Argot to generate the classes automatically to speed development.

@ArgotMarshaller(TypeAnnotationMarshaller.class)
public class SetAll 
extends Command
{
	public static final String TYPENAME = "cube.set_all";
	
	@ArgotTag("colour")
	public Colour colour;
	
	public SetAll(Colour colour)
	{
		this.colour = colour;
	}
	
	public SetAll()
	{
		// Empty constructor required for annotation marshaller.
	}
}

As you can see, these classes are using the TypeAnnotationMarshaller to bind the class to the Argot definition above. This marshaller uses the ArgotTag to match each of the names associated with the sequence elements.

Load and bind the library

In Argot, all data types are stored in a type library. With the data types defined and the objects created in Java, the next step is to compile and load the type library. In this case I've created a loader class to encapsulate the compiling of the cubed.argot and bind each of the classes to the Argot definitions.

public class CubeArgotLoader 
extends ArgotCompilerLoader 
{
	private static final String NAME = "cubed.argot";

	public CubeArgotLoader() 
	{
		super(NAME);
	}

	@Override
	public void bind( TypeLibrary library )
	throws TypeException
	{
		library.bind( library.getTypeId(Command.LIST_TYPENAME,"1.0"), 
		new TypeArrayMarshaller(), new TypeArrayMarshaller(), Command[].class);	
		library.bind( library.getTypeId(Colour.TYPENAME, "1.0"), Colour.class );
		library.bind( library.getTypeId(Delay.TYPENAME, "1.0"), Delay.class );
		library.bind( library.getTypeId(Loop.TYPENAME,"1.0"),	Loop.class );
		library.bind( library.getTypeId(SetAll.TYPENAME,"1.0"),	SetAll.class );
		library.bind( library.getTypeId(SetColour.TYPENAME, "1.0"), SetColour.class );
		library.bind( library.getTypeId(SetColumn.TYPENAME, "1.0"), SetColumn.class );
		library.bind( library.getTypeId(SetXPlane.TYPENAME,"1.0"), SetXPlane.class );
		library.bind( library.getTypeId(SetYPlane.TYPENAME,"1.0"), SetYPlane.class );
		library.bind( library.getTypeId(SetZPlane.TYPENAME,"1.0"), SetZPlane.class );	
		library.bind( library.getTypeId(Command.COMMAND_TYPENAME, "1.0"), Cube.class );
		library.bind( library.getTypeId(Packet.TYPENAME, "1.0"), Packet.class );
	}

	@Override
	public String getName() 
	{
		return NAME;
	}

}

With this in place, setting up the type library is simply a matter of creating and loading the library.

	// Create the type library and compile/bind the switch data types.
	typeLibrary = new TypeLibrary( );
	typeLibrary.loadLibrary(new CubeArgotLoader());

In this demo, the definitions are static between the host and cube. For this reason a static type map is loaded from the compiled cubed.dictionary file (which is generated when the cubed.argot is compiled on startup of the program).

		
	// Load the typeMap again so we have the right map to write messages.
	cubeMap = Dictionary.readDictionary(typeLibrary, new FileInputStream( new File("cubed.dictionary")));

Sending the commands

In this implementation all the commands are sent via a serial output stream. The SerialCube provides the wrapper for all communication to the cube. The writeCommand prepares the object and sends the packet to the stream. To write the data packet, the length of the command needs to be known. To capture the length, a CountOutputStream is used. This class simply counts the bytes written without actually recording the data anywhere. Once the command length is known the Packet object is created and sent to the cube.

	// Write out the command.
	ByteArrayOutputStream out = new ByteArrayOutputStream();
	TypeOutputStream typeOut = new TypeOutputStream( out, cubeMap );
	typeOut.writeObject( Command.COMMAND_TYPENAME, command );
	out.close();
	
	// To write out a packet requires knowledge of the length.
	CountOutputStream countOut = new CountOutputStream();
	TypeOutputStream sizeOut = new TypeOutputStream( countOut, cubeMap);
	sizeOut.writeObject(Command.COMMAND_TYPENAME, command);
	countOut.close();
	
	// Create the Packet
	Packet packet = new Packet(countOut.length(), command);
	
	// Send the packet on the serial link.
	TypeOutputStream serialOut = new TypeOutputStream( serial, cubeMap );
	serialOut.writeObject( Packet.TYPENAME, packet );

Argot uses the meta data provided and the bound classes to perform all the encoding automatically.

The Arduino Code

In this example the focus is on the implementation of writing binary packets on the Java host. On the Arduino side, a hand crafted simple Serial reader has been implemented. The main command handling is handled by the processCommand function.

// Process the command after receiving the complete packet.
void processCommand()
{
  //Serial.println("Arduino: Process command message");
  uint8_t command = read_u8();
 
  switch(command) {
  case CUBE_SET_COLOUR:
    processSetColour();
    break;
  case CUBE_SET_ALL:
    processSetAll();
    break;
  case CUBE_SET_COLUMN:
    processSetColumn();
    break;
  case CUBE_SET_Z_PLANE:
    processZPlane();
    break;
  case CUBE_SET_X_PLANE:
    processXPlane();
    break;
  case CUBE_SET_Y_PLANE:
    processYPlane();
    break;
  case CUBE_DELAY:
    processDelay();
    break;
  case CUBE_LOOP:
    processLoop();
    break;
  default:
    Serial.print("Arduino: Unknown command: " + command);
  }
}

Once again, although in this instance the code has been hand written, the structure could easily be automatically generated using the meta data from the argot dictionary. The example includes a cubed_dictionary.h and cubed_dictionary.c which are generated by running the CreateDictionary class in java. In this case the cubed_dictionary.h is used to define the identifiers of each of the commands. The cubed_dictionary.c is not currently used, but will be used in future examples.

The Processing UI

Processing provides a fun way to create simple graphical user interfaces. In this instance, a simple user interface allows the colour of the cube to be set. In addition, clicking the heart image causes the following set of commands to be sent:

    return new Loop(2, new Command[] {
        new Delay(1000),
        new Loop(2, new Command[] {
          new SetAll( new Colour(0,0,0)),
          new SetZPlane( 1, new Colour(100,0,0)),
          new Delay(50),
          new SetZPlane( 2, new Colour(80,0,0)),
          new Delay(50),
          new SetZPlane( 3, new Colour(60,0,0)),
          new Delay(50),
          new SetZPlane( 4, new Colour(40,0,0)),
          new Delay(100),
          new SetZPlane( 4, new Colour(0,0,0)),
          new Delay(50),
          new SetZPlane( 3, new Colour(0,0,0)),
          new Delay(50),
          new SetZPlane( 2, new Colour(0,0,0)),
          new Delay(50),
          new SetZPlane( 1, new Colour(0,0,0)),
          new Delay(200),
        })
    });

This set of commands is encoded to only 77 bytes. It would not be difficult to extend this to send other simple programs to the cube based on user feedback. It's also a great way to teach kids the basics of programming. The cube language is a little like a modern day turtle logo.

Conclusion

This example provides the basic building blocks to do more advanced applications and protocols with Argot. Be sure to check back on the web-site regularly to see new examples. If you have any issues or queries please contact david using the email below.