/* * Please read the LICENSE file that is included with the source * code. */ package se.nicklasgavelin.sphero.command; import java.util.Date; /** * * @author Nicklas Gavelin, nicklas.gavelin@gmail.com, LuleƄ University of * Technology */ public class CommandMessage { /* Static values */ private static int nSeq = 0; /* Static indicies */ private static final byte COMMAND_PREFIX = -1; private static final int CHECKSUM_LENGTH = 1, INDEX_START_1 = 0, INDEX_START_2 = 1, INDEX_DEVICE_ID = 2, INDEX_COMMAND = 3, INDEX_COMMAND_SEQUENCE_NO = 4, INDEX_COMMAND_DATA_LENGTH = 5, COMMAND_HEADER_LENGTH = 6; /* Internal storage */ private Date timestamp; private boolean seqSet = false; private int seqNum; private COMMAND_MESSAGE_TYPE command; private byte[] packet; public CommandMessage( COMMAND_MESSAGE_TYPE _command ) { this.command = _command; } /** * Returns the internal sequence number * Will also set the sequence number if it's not set * * @return The internal sequence number */ public int getSequenceNumber() { if ( !this.seqSet ) { this.seqNum = CommandMessage.nSeq++; this.seqSet = true; } return this.seqNum; } /** * Returns the command type * * @return The command type */ public COMMAND_MESSAGE_TYPE getCommand() { return this.command; } /** * Returns the packet data. * WILL SET THE SEQUENCE NUMBER DURING THE FIRST CALL! * * @return The packet data */ public byte[] getPacket() { if ( this.packet == null ) this.packet = packetize(); return this.packet; } /** * Returns the length of the command * * @param dataLength The length of the data * * @return The length of the command */ public byte getCommandLength( int dataLength ) { return ( byte ) (dataLength + 1); } /** * Returns the packet data * * @return Packet data */ protected byte[] getPacketData() { return null; } /** * Returns the complete packet length including header, checksum and data * length * * @return The packet length */ public int getPacketLength() { return (this.getPacket().length); } /** * Create the packet content * * @return The packet content */ protected byte[] packetize() { byte[] data = getPacketData(); int data_length = data != null ? data.length : 0; int packet_length = data_length + COMMAND_HEADER_LENGTH + CHECKSUM_LENGTH; byte[] buffer = new byte[ packet_length ]; byte checksum = 0; buffer[ INDEX_START_1] = COMMAND_PREFIX; buffer[ INDEX_START_2] = COMMAND_PREFIX; byte device_id = this.command.getDeviceId(); checksum = ( byte ) (checksum + device_id); buffer[ INDEX_DEVICE_ID] = device_id; byte cmd = this.command.getCommandId(); checksum = ( byte ) (checksum + cmd); buffer[ INDEX_COMMAND] = cmd; int sequenceNumber = this.getSequenceNumber(); checksum = ( byte ) (checksum + sequenceNumber); buffer[ INDEX_COMMAND_SEQUENCE_NO] = ( byte ) (sequenceNumber); byte response_length = getCommandLength( data_length ); checksum = ( byte ) (checksum + response_length); buffer[ INDEX_COMMAND_DATA_LENGTH] = response_length; // Check if we need to calculate the checksum for the data we have added if ( data != null ) { // Calculate the checksum for the data (also add the data to the array) for ( int i = 0; i < data_length; i++ ) { buffer[(i + COMMAND_HEADER_LENGTH)] = data[i]; checksum = ( byte ) (checksum + data[i]); } } buffer[(packet_length - CHECKSUM_LENGTH)] = ( byte ) (checksum ^ 0xFFFFFFFF); return buffer; } /** * Returns the timestamp when the message was made into a packet * * @return The timestamp when the message was packetized */ public Date getTimestamp() { return this.timestamp; } /* ***************************** * INTERNAL CLASSES ***************************** */ /** * Different command types */ public static enum COMMAND_MESSAGE_TYPE { /* Core commands */ PING( 0, 0 ), VERSIONING( 2, 0 ), SET_BLUETOOTH_NAME( 16, 0 ), GET_BLUETOOTH_INFO( 17, 0 ), GO_TO_SLEEP( 34, 0 ), JUMP_TO_BOOTLOADER( 48, 0 ), LEVEL_1_DIAGNOSTICS( 64, 0 ), /* Bootloader command */ JUMP_TO_MAIN( 4, 1 ), /* Sphero command */ CALIBRATE( 1, 2 ), STABILIZATION( 2, 2 ), ROTATION_RATE( 3, 2 ), CONFIGURE_COLLISION_DETECTION( 18, 2 ), RGB_LED_OUTPUT( 32, 2 ), FRONT_LED_OUTPUT( 33, 2 ), ROLL( 48, 2 ), BOOST( 49, 2 ), RAW_MOTOR( 51, 2 ), GET_CONFIGURATION_BLOCK( 64, 2 ), RUN_MACRO( 80, 2 ), MACRO( 81, 2 ), SAVE_MACRO( 82, 2 ), ABORT_MACRO( 85, 2 ), SET_DATA_STREAMING( 17, 2 ), SPIN_LEFT( RAW_MOTOR.getCommandId(), RAW_MOTOR.getDeviceId() ), SPIN_RIGHT( RAW_MOTOR.getCommandId(), RAW_MOTOR.getDeviceId() ), CUSTOM_PING( FRONT_LED_OUTPUT.getCommandId(), FRONT_LED_OUTPUT.getDeviceId() ); /* Internal storage */ private static int idCount = 0; private byte commandId; private byte deviceId; private int id; /** * Create device command with a device id and command id * * @param commandId The command id * @param deviceId The device id */ private COMMAND_MESSAGE_TYPE( int commandId, int deviceId ) { this.commandId = ( byte ) commandId; this.deviceId = ( byte ) deviceId; this.setId(); } /** * Set the id of the command */ private void setId() { this.id = COMMAND_MESSAGE_TYPE.idCount++; } /** * Returns the device id * * @return The device id */ public byte getDeviceId() { return this.deviceId; } /** * Returns the command id * * @return The command id */ public byte getCommandId() { return this.commandId; } /** * Returns the unique id for the command * * @return The unique id */ public int getId() { return this.id; } /** * Returns the device command that corresponds to the given command and * device id. * * @param uniqueId The unique command id * * @return The device command or null if no command could be represented */ public static COMMAND_MESSAGE_TYPE valueOf( int uniqueId ) // int commandId, int deviceId ) { COMMAND_MESSAGE_TYPE[] cmds = COMMAND_MESSAGE_TYPE.values(); for ( COMMAND_MESSAGE_TYPE dc : cmds ) if ( dc.getId() == uniqueId ) return dc; return null; } } }