// -*- mode: java; c-basic-offset: 2; -*-
// Copyright 2011-2012 MIT, All rights reserved
// Released under the Apache License, Version 2.0
// http://www.apache.org/licenses/LICENSE-2.0
package com.google.appinventor.components.runtime;
import com.google.appinventor.components.annotations.DesignerProperty;
import com.google.appinventor.components.annotations.PropertyCategory;
import com.google.appinventor.components.annotations.SimpleObject;
import com.google.appinventor.components.annotations.SimpleProperty;
import com.google.appinventor.components.common.PropertyTypeConstants;
import com.google.appinventor.components.runtime.util.ErrorMessages;
import com.google.appinventor.components.runtime.util.Ev3BinaryParser;
import java.util.Collections;
/**
* The base class for EV3 components.
*
* @author jerry73204@gmail.com (jerry73204)
* @author spaded06543@gmail.com (Alvin Chang)
*/
@SimpleObject
public class LegoMindstormsEv3Base extends AndroidNonvisibleComponent
implements BluetoothConnectionListener, Component, Deleteable {
private static final int TOY_ROBOT = 0x0804;
protected int commandCount;
protected final String logTag;
protected BluetoothClient bluetooth;
protected LegoMindstormsEv3Base(ComponentContainer container, String logTag) {
super(container.$form());
this.logTag = logTag;
}
protected LegoMindstormsEv3Base() {
super(null);
logTag = null;
}
@SimpleProperty(description = "The BluetoothClient component that should be used for communication.",
category = PropertyCategory.BEHAVIOR)
public BluetoothClient BluetoothClient() {
return bluetooth;
}
@DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_BLUETOOTHCLIENT,
defaultValue = "")
@SimpleProperty
public void BluetoothClient(BluetoothClient bluetoothClient) {
if (bluetooth != null) {
bluetooth.removeBluetoothConnectionListener(this);
bluetooth.detachComponent(this);
bluetooth = null;
}
if (bluetoothClient != null) {
bluetooth = bluetoothClient;
bluetooth.attachComponent(this, Collections.singleton(TOY_ROBOT));
bluetooth.addBluetoothConnectionListener(this);
if (bluetooth.IsConnected()) {
// We missed the real afterConnect event.
afterConnect(bluetooth);
}
}
}
protected final boolean isBluetoothConnected(String functionName) {
if (bluetooth == null) {
form.dispatchErrorOccurredEvent(this, functionName, ErrorMessages.ERROR_EV3_BLUETOOTH_NOT_SET);
return false;
}
if (!bluetooth.IsConnected()) {
form.dispatchErrorOccurredEvent(this, functionName, ErrorMessages.ERROR_EV3_NOT_CONNECTED_TO_ROBOT);
return false;
}
return true;
}
protected final byte[] sendCommand(String functionName, byte[] command, boolean doReceiveReply) {
// check connecttivity
if (!isBluetoothConnected(functionName))
return null;
// prepend header and send payload
byte[] header = Ev3BinaryParser.pack("hh", (short) (command.length + 2), (short) commandCount);
commandCount++;
bluetooth.write(functionName, header);
bluetooth.write(functionName, command);
// receive reply if required
if (doReceiveReply) {
header = bluetooth.read(functionName, 4);
if (header.length == 4) {
Object[] decodedHeader = Ev3BinaryParser.unpack("hh", header);
int replySize = (int) ((Short) decodedHeader[0]) - 2;
int replyCount = (int) ((Short) decodedHeader[1]);
byte[] reply = bluetooth.read(functionName, replySize);
if (reply.length == replySize)
return reply;
else
{
form.dispatchErrorOccurredEvent(this, functionName, ErrorMessages.ERROR_EV3_INVALID_REPLY);
return null;
}
}
// handle errors
form.dispatchErrorOccurredEvent(this, functionName, ErrorMessages.ERROR_EV3_INVALID_REPLY);
return null;
} else {
return null;
}
}
protected final int sensorPortLetterToPortNumber(String letter) {
if (letter.length() != 1)
throw new IllegalArgumentException("String \"" + letter + "\" is not a valid sensor port letter");
int portNumber = letter.charAt(0) - '1';
if (portNumber < 0 || portNumber > 3)
throw new IllegalArgumentException("String \"" + letter + "\" is not a valid sensor port letter");
return portNumber;
}
protected final String portNumberToSensorPortLetter(int portNumber) {
if (portNumber < 0 || portNumber > 3)
throw new IllegalArgumentException(portNumber + " is not a valid port number");
return "" + ('1' + portNumber);
}
protected final int motorPortLettersToBitField(String letters) {
if (letters.length() > 4)
throw new IllegalArgumentException("Malformed motor port letters \"" + letters + "\"");
int portABit = 0;
int portBBit = 0;
int portCBit = 0;
int portDBit = 0;
for (int i = 0; i < letters.length(); i++)
{
switch (letters.charAt(i))
{
case 'A':
if (portABit != 0)
throw new IllegalArgumentException("Malformed motor port letters \"" + letters + "\"");
portABit = 1;
break;
case 'B':
if (portBBit != 0)
throw new IllegalArgumentException("Malformed motor port letters \"" + letters + "\"");
portBBit = 2;
break;
case 'C':
if (portCBit != 0)
throw new IllegalArgumentException("Malformed motor port letters \"" + letters + "\"");
portCBit = 4;
break;
case 'D':
if (portDBit != 0)
throw new IllegalArgumentException("Malformed motor port letters \"" + letters + "\"");
portDBit = 8;
break;
default:
throw new IllegalArgumentException("Malformed motor port letters \"" + letters + "\"");
}
}
return portABit | portBBit | portCBit | portDBit;
}
protected final String bitFieldToMotorPortLetters(int bitField) {
if (bitField < 0 || bitField > 15)
throw new IllegalArgumentException("Invalid bit field number " + bitField);
String portLetters = "";
if ((bitField & 1) != 0)
portLetters += "A";
if ((bitField & 2) != 0)
portLetters += "B";
if ((bitField & 4) != 0)
portLetters += "C";
if ((bitField & 8) != 0)
portLetters += "D";
return portLetters;
}
@Override
public void afterConnect(BluetoothConnectionBase bluetoothConnection) {
// Subclasses may wish to do something.
}
@Override
public void beforeDisconnect(BluetoothConnectionBase bluetoothConnection) {
// Subclasses may wish to do something.
}
// interface Deleteable implementation
@Override
public void onDelete() {
if (bluetooth != null) {
bluetooth.removeBluetoothConnectionListener(this);
bluetooth.detachComponent(this);
bluetooth = null;
}
}
}