// -*- mode: java; c-basic-offset: 2; -*- // Copyright 2009-2011 Google, All Rights reserved // 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.DesignerComponent; import com.google.appinventor.components.annotations.SimpleFunction; import com.google.appinventor.components.annotations.SimpleObject; import com.google.appinventor.components.annotations.UsesPermissions; import com.google.appinventor.components.common.ComponentCategory; import com.google.appinventor.components.common.YaVersion; import com.google.appinventor.components.runtime.util.ErrorMessages; import com.google.appinventor.components.runtime.util.MediaUtil; import com.google.appinventor.components.runtime.util.YailList; import android.util.Log; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.List; /** * A component that provides a low-level interface to a LEGO MINDSTORMS NXT * robot, with functions to send NXT Direct Commands. * * @author lizlooney@google.com (Liz Looney) */ @DesignerComponent(version = YaVersion.NXT_DIRECT_COMMANDS_COMPONENT_VERSION, description = "A component that provides a low-level interface to a LEGO MINDSTORMS NXT " + "robot, with functions to send NXT Direct Commands.", category = ComponentCategory.LEGOMINDSTORMS, nonVisible = true, iconName = "images/legoMindstormsNxt.png") @SimpleObject @UsesPermissions(permissionNames = "android.permission.INTERNET," + "android.permission.WRITE_EXTERNAL_STORAGE," + "android.permission.READ_EXTERNAL_STORAGE") public class NxtDirectCommands extends LegoMindstormsNxtBase { /** * Creates a new NxtDirectCommands component. */ public NxtDirectCommands(ComponentContainer container) { super(container, "NxtDirectCommands"); } // TODO(user, lizlooney) - Add a property for a "helper program", like MotorControl21.rxe. If // set, then the Connect method would automatically take care of checking for, downloading (if // necessary) and starting the helper program. This would minimize the programming blocks for a // classroom project. @SimpleFunction(description = "Start execution of a previously downloaded program on " + "the robot.") public void StartProgram(String programName) { String functionName = "StartProgram"; if (!checkBluetooth(functionName)) { return; } if (programName.length() == 0) { form.dispatchErrorOccurredEvent(this, functionName, ErrorMessages.ERROR_NXT_INVALID_PROGRAM_NAME); return; } if (programName.indexOf(".") == -1) { programName += ".rxe"; } byte[] command = new byte[22]; command[0] = (byte) 0x80; // Direct command telegram, no response command[1] = (byte) 0x00; // STARTPROGRAM command copyStringValueToBytes(programName, command, 2, 19); sendCommand(functionName, command); } @SimpleFunction(description = "Stop execution of the currently running program on " + "the robot.") public void StopProgram() { String functionName = "StopProgram"; if (!checkBluetooth(functionName)) { return; } byte[] command = new byte[2]; command[0] = (byte) 0x80; // Direct command telegram, no response command[1] = (byte) 0x01; // STOPPROGRAM command sendCommand(functionName, command); } @SimpleFunction(description = "Play a sound file on the robot.") public void PlaySoundFile(String fileName) { String functionName = "PlaySoundFile"; if (!checkBluetooth(functionName)) { return; } if (fileName.length() == 0) { form.dispatchErrorOccurredEvent(this, functionName, ErrorMessages.ERROR_NXT_INVALID_FILE_NAME); return; } if (fileName.indexOf(".") == -1) { fileName += ".rso"; } byte[] command = new byte[23]; command[0] = (byte) 0x80; // Direct command telegram, no response command[1] = (byte) 0x02; // PLAYSOUNDFILE command copyBooleanValueToBytes(false, command, 2); // play file once only copyStringValueToBytes(fileName, command, 3, 19); sendCommand(functionName, command); } @SimpleFunction(description = "Make the robot play a tone.") public void PlayTone(int frequencyHz, int durationMs) { String functionName = "PlayTone"; if (!checkBluetooth(functionName)) { return; } if (frequencyHz < 200) { Log.w(logTag, "frequencyHz " + frequencyHz + " is invalid, using 200."); frequencyHz = 200; } if (frequencyHz > 14000) { Log.w(logTag, "frequencyHz " + frequencyHz + " is invalid, using 14000."); frequencyHz = 14000; } byte[] command = new byte[6]; command[0] = (byte) 0x80; // Direct command telegram, no response command[1] = (byte) 0x03; // PLAYTONE command copyUWORDValueToBytes(frequencyHz, command, 2); // 2-3: frequency, Hz (UWORD) copyUWORDValueToBytes(durationMs, command, 4); // 4-5: Duration, ms (UWORD) sendCommand(functionName, command); } @SimpleFunction(description = "Sets the output state of a motor on the robot.") public void SetOutputState(String motorPortLetter, int power, int mode, int regulationMode, int turnRatio, int runState, long tachoLimit) { String functionName = "SetOutputState"; if (!checkBluetooth(functionName)) { return; } int port; try { port = convertMotorPortLetterToNumber(motorPortLetter); } catch (IllegalArgumentException e) { form.dispatchErrorOccurredEvent(this, functionName, ErrorMessages.ERROR_NXT_INVALID_MOTOR_PORT, motorPortLetter); return; } setOutputState(functionName, port, power, mode, regulationMode, sanitizeTurnRatio(turnRatio), runState, tachoLimit); } @SimpleFunction(description = "Configure an input sensor on the robot.") public void SetInputMode(String sensorPortLetter, int sensorType, int sensorMode) { String functionName = "SetInputMode"; if (!checkBluetooth(functionName)) { return; } int port; try { port = convertSensorPortLetterToNumber(sensorPortLetter); } catch (IllegalArgumentException e) { form.dispatchErrorOccurredEvent(this, functionName, ErrorMessages.ERROR_NXT_INVALID_SENSOR_PORT, sensorPortLetter); return; } setInputMode(functionName, port, sensorType, sensorMode); } @SimpleFunction(description = "Reads the output state of a motor on the robot.") public List<Number> GetOutputState(String motorPortLetter) { String functionName = "GetOutputState"; if (!checkBluetooth(functionName)) { return new ArrayList<Number>(); } int port; try { port = convertMotorPortLetterToNumber(motorPortLetter); } catch (IllegalArgumentException e) { form.dispatchErrorOccurredEvent(this, functionName, ErrorMessages.ERROR_NXT_INVALID_MOTOR_PORT, motorPortLetter); return new ArrayList<Number>(); } byte[] returnPackage = getOutputState(functionName, port); if (returnPackage != null) { List<Number> outputState = new ArrayList<Number>(); outputState.add(getSBYTEValueFromBytes(returnPackage, 4)); // Power (SBYTE -100 to 100) outputState.add(getUBYTEValueFromBytes(returnPackage, 5)); // Mode (UBYTE) outputState.add(getUBYTEValueFromBytes(returnPackage, 6)); // Regulation mode (UBYTE) outputState.add(getSBYTEValueFromBytes(returnPackage, 7)); // TurnRatio (SBYTE -100 to 100) outputState.add(getUBYTEValueFromBytes(returnPackage, 8)); // RunState (UBYTE) outputState.add(getULONGValueFromBytes(returnPackage, 9)); // TachoLimit (ULONG) outputState.add(getSLONGValueFromBytes(returnPackage, 13)); // TachoCount (SLONG) outputState.add(getSLONGValueFromBytes(returnPackage, 17)); // BlockTachoCount (SLONG) outputState.add(getSLONGValueFromBytes(returnPackage, 21)); // RotationCount (SLONG) return outputState; } // invalid response return new ArrayList<Number>(); } private byte[] getOutputState(String functionName, int port) { byte[] command = new byte[3]; command[0] = (byte) 0x00; // Direct command telegram, response required command[1] = (byte) 0x06; // GETOUTPUTSTATE command copyUBYTEValueToBytes(port, command, 2); byte[] returnPackage = sendCommandAndReceiveReturnPackage(functionName, command); if (evaluateStatus(functionName, returnPackage, command[1])) { if (returnPackage.length == 25) { return returnPackage; } else { Log.w(logTag, functionName + ": unexpected return package length " + returnPackage.length + " (expected 25)"); } } return null; } @SimpleFunction(description = "Reads the values of an input sensor on the robot. " + "Assumes sensor type has been configured via SetInputMode.") public List<Object> GetInputValues(String sensorPortLetter) { String functionName = "GetInputValues"; if (!checkBluetooth(functionName)) { return new ArrayList<Object>(); } int port; try { port = convertSensorPortLetterToNumber(sensorPortLetter); } catch (IllegalArgumentException e) { form.dispatchErrorOccurredEvent(this, functionName, ErrorMessages.ERROR_NXT_INVALID_SENSOR_PORT, sensorPortLetter); return new ArrayList<Object>(); } byte[] returnPackage = getInputValues(functionName, port); if (returnPackage != null) { List<Object> inputValues = new ArrayList<Object>(); inputValues.add(getBooleanValueFromBytes(returnPackage, 4)); // Valid inputValues.add(getBooleanValueFromBytes(returnPackage, 5)); // Calibrated inputValues.add(getUBYTEValueFromBytes(returnPackage, 6)); // Sensor type inputValues.add(getUBYTEValueFromBytes(returnPackage, 7)); // Sensor mode inputValues.add(getUWORDValueFromBytes(returnPackage, 8)); // Raw A/D value (UWORD) inputValues.add(getUWORDValueFromBytes(returnPackage, 10)); // Normalized A/D value (UWORD) inputValues.add(getSWORDValueFromBytes(returnPackage, 12)); // Scaled value (SWORD) inputValues.add(getSWORDValueFromBytes(returnPackage, 14)); // Calibrated value (SWORD) return inputValues; } // invalid response return new ArrayList<Object>(); } @SimpleFunction(description = "Reset the scaled value of an input sensor on the robot.") public void ResetInputScaledValue(String sensorPortLetter) { String functionName = "ResetInputScaledValue"; if (!checkBluetooth(functionName)) { return; } int port; try { port = convertSensorPortLetterToNumber(sensorPortLetter); } catch (IllegalArgumentException e) { form.dispatchErrorOccurredEvent(this, functionName, ErrorMessages.ERROR_NXT_INVALID_SENSOR_PORT, sensorPortLetter); return; } resetInputScaledValue(functionName, port); byte[] command = new byte[3]; command[0] = (byte) 0x80; // Direct command telegram, no response command[1] = (byte) 0x08; // RESETINPUTSCALEDVALUE command copyUBYTEValueToBytes(port, command, 2); sendCommand(functionName, command); } @SimpleFunction(description = "Write a message to a mailbox (1-10) on the robot.") public void MessageWrite(int mailbox, String message) { String functionName = "MessageWrite"; if (!checkBluetooth(functionName)) { return; } // Note from Paul Gyugyi during code review: we are only supporting mailboxes 1-10, but NXT can // use mailboxes above 10 as relays to other NXTs. We've never needed it, but if you ever see // a feature request or bug report, all that might be required is just raising our upper limit // on the range. if (mailbox < 1 || mailbox > 10) { form.dispatchErrorOccurredEvent(this, functionName, ErrorMessages.ERROR_NXT_INVALID_MAILBOX, mailbox); return; } int messageLength = message.length(); if (messageLength > 58) { form.dispatchErrorOccurredEvent(this, functionName, ErrorMessages.ERROR_NXT_MESSAGE_TOO_LONG); return; } mailbox--; // send 0-based mailbox to NXT byte[] command = new byte[4 + messageLength + 1]; command[0] = (byte) 0x80; // Direct command telegram, no response command[1] = (byte) 0x09; // MESSAGEWRITE command copyUBYTEValueToBytes(mailbox, command, 2); // message length includes null termination byte copyUBYTEValueToBytes(messageLength + 1, command, 3); copyStringValueToBytes(message, command, 4, messageLength); // The command array is already filled with zeros. No need to actually set the last byte to 0. sendCommand(functionName, command); } @SimpleFunction(description = "Reset motor position.") public void ResetMotorPosition(String motorPortLetter, boolean relative) { String functionName = "ResetMotorPosition"; if (!checkBluetooth(functionName)) { return; } int port; try { port = convertMotorPortLetterToNumber(motorPortLetter); } catch (IllegalArgumentException e) { form.dispatchErrorOccurredEvent(this, functionName, ErrorMessages.ERROR_NXT_INVALID_MOTOR_PORT, motorPortLetter); return; } byte[] command = new byte[4]; command[0] = (byte) 0x80; // Direct command telegram, no response command[1] = (byte) 0x0A; // RESETMOTORPOSITION command copyUBYTEValueToBytes(port, command, 2); copyBooleanValueToBytes(relative, command, 3); sendCommand(functionName, command); } @SimpleFunction(description = "Get the battery level for the robot. " + "Returns the voltage in millivolts.") public int GetBatteryLevel() { String functionName = "GetBatteryLevel"; if (!checkBluetooth(functionName)) { return 0; } byte[] command = new byte[2]; command[0] = (byte) 0x00; // Direct command telegram, response required command[1] = (byte) 0x0B; // GETBATTERYLEVEL command byte[] returnPackage = sendCommandAndReceiveReturnPackage(functionName, command); if (evaluateStatus(functionName, returnPackage, command[1])) { if (returnPackage.length == 5) { return getUWORDValueFromBytes(returnPackage, 3); } else { Log.w(logTag, "GetBatteryLevel: unexpected return package length " + returnPackage.length + " (expected 5)"); } } return 0; } @SimpleFunction(description = "Stop sound playback.") public void StopSoundPlayback() { String functionName = "StopSoundPlayback"; if (!checkBluetooth(functionName)) { return; } byte[] command = new byte[2]; command[0] = (byte) 0x80; // Direct command telegram, no response command[1] = (byte) 0x0C; // STOPSOUNDPLAYBACK command sendCommand(functionName, command); } @SimpleFunction(description = "Keep Alive. " + "Returns the current sleep time limit in milliseconds.") public long KeepAlive() { String functionName = "KeepAlive"; if (!checkBluetooth(functionName)) { return 0; } byte[] command = new byte[2]; command[0] = (byte) 0x00; // Direct command telegram, response required command[1] = (byte) 0x0D; // KEEPALIVE command byte[] returnPackage = sendCommandAndReceiveReturnPackage(functionName, command); if (evaluateStatus(functionName, returnPackage, command[1])) { if (returnPackage.length == 7) { return getULONGValueFromBytes(returnPackage, 3); } else { Log.w(logTag, "KeepAlive: unexpected return package length " + returnPackage.length + " (expected 7)"); } } return 0; } @SimpleFunction(description = "Returns the count of available bytes to read.") public int LsGetStatus(String sensorPortLetter) { String functionName = "LsGetStatus"; if (!checkBluetooth(functionName)) { return 0; } int port; try { port = convertSensorPortLetterToNumber(sensorPortLetter); } catch (IllegalArgumentException e) { form.dispatchErrorOccurredEvent(this, functionName, ErrorMessages.ERROR_NXT_INVALID_SENSOR_PORT, sensorPortLetter); return 0; } return lsGetStatus(functionName, port); } @SimpleFunction(description = "Writes low speed data to an input sensor on the robot. " + "Assumes sensor type has been configured via SetInputMode.") public void LsWrite(String sensorPortLetter, YailList list, int rxDataLength) { String functionName = "LsWrite"; if (!checkBluetooth(functionName)) { return; } int port; try { port = convertSensorPortLetterToNumber(sensorPortLetter); } catch (IllegalArgumentException e) { form.dispatchErrorOccurredEvent(this, functionName, ErrorMessages.ERROR_NXT_INVALID_SENSOR_PORT, sensorPortLetter); return; } if (list.size() > 16) { form.dispatchErrorOccurredEvent(this, functionName, ErrorMessages.ERROR_NXT_DATA_TOO_LARGE); return; } Object[] array = list.toArray(); byte[] bytes = new byte[array.length]; for (int i = 0; i < array.length; i++) { // We use Object.toString here because the element might be a String or it might be some // numeric class. Object element = array[i]; String s = element.toString(); int n; try { n = Integer.decode(s); } catch (NumberFormatException e) { form.dispatchErrorOccurredEvent(this, functionName, ErrorMessages.ERROR_NXT_COULD_NOT_DECODE_ELEMENT, i + 1); return; } bytes[i] = (byte) (n & 0xFF); n = n >> 8; if (n != 0 && n != -1) { form.dispatchErrorOccurredEvent(this, functionName, ErrorMessages.ERROR_NXT_COULD_NOT_FIT_ELEMENT_IN_BYTE, i + 1); return; } } lsWrite(functionName, port, bytes, rxDataLength); } @SimpleFunction(description = "Reads unsigned low speed data from an input sensor on the " + "robot. Assumes sensor type has been configured via SetInputMode.") public List<Integer> LsRead(String sensorPortLetter) { String functionName = "LsRead"; if (!checkBluetooth(functionName)) { return new ArrayList<Integer>(); } int port; try { port = convertSensorPortLetterToNumber(sensorPortLetter); } catch (IllegalArgumentException e) { form.dispatchErrorOccurredEvent(this, functionName, ErrorMessages.ERROR_NXT_INVALID_SENSOR_PORT, sensorPortLetter); return new ArrayList<Integer>(); } byte[] returnPackage = lsRead(functionName, port); if (returnPackage != null) { List<Integer> list = new ArrayList<Integer>(); int count = getUBYTEValueFromBytes(returnPackage, 3); for (int i = 0; i < count; i++) { int n = returnPackage[4 + i] & 0xFF; // unsigned list.add(n); } return list; } // invalid response return new ArrayList<Integer>(); } @SimpleFunction(description = "Get the name of currently running program on " + "the robot.") public String GetCurrentProgramName() { String functionName = "GetCurrentProgramName"; if (!checkBluetooth(functionName)) { return ""; } byte[] command = new byte[2]; command[0] = (byte) 0x00; // Direct command telegram, response required command[1] = (byte) 0x11; // GETCURRRENTPROGRAMNAME command byte[] returnPackage = sendCommandAndReceiveReturnPackage(functionName, command); int status = getStatus(functionName, returnPackage, command[1]); if (status == 0) { // Success return getStringValueFromBytes(returnPackage, 3); } if (status == 0xEC) { // No active program. We don't treat this as an error. return ""; } // Some other error code. evaluateStatus(functionName, returnPackage, command[1]); return ""; } @SimpleFunction(description = "Read a message from a mailbox (1-10) on the robot.") public String MessageRead(int mailbox) { String functionName = "MessageRead"; if (!checkBluetooth(functionName)) { return ""; } // Note from Paul Gyugyi during code review: we are only supporting mailboxes 1-10, but NXT can // use mailboxes above 10 as relays to other NXTs. We've never needed it, but if you ever see // a feature request or bug report, all that might be required is just raising our upper limit // on the range. if (mailbox < 1 || mailbox > 10) { form.dispatchErrorOccurredEvent(this, functionName, ErrorMessages.ERROR_NXT_INVALID_MAILBOX, mailbox); return ""; } mailbox--; // send 0-based mailbox to NXT byte[] command = new byte[5]; command[0] = (byte) 0x00; // Direct command telegram, response required command[1] = (byte) 0x13; // MESSAGEREAD command copyUBYTEValueToBytes(0, command, 2); // no remote mailbox copyUBYTEValueToBytes(mailbox, command, 3); copyBooleanValueToBytes(true, command, 4); // remove message from mailbox byte[] returnPackage = sendCommandAndReceiveReturnPackage(functionName, command); if (evaluateStatus(functionName, returnPackage, command[1])) { if (returnPackage.length == 64) { int mailboxEcho = getUBYTEValueFromBytes(returnPackage, 3); if (mailboxEcho != mailbox) { Log.w(logTag, "MessageRead: unexpected return mailbox: " + mailboxEcho + " (expected " + mailbox + ")"); } int messageLength = getUBYTEValueFromBytes(returnPackage, 4) - 1; return getStringValueFromBytes(returnPackage, 5, messageLength); } else { Log.w(logTag, "MessageRead: unexpected return package length " + returnPackage.length + " (expected 64)"); } } return ""; } /** * Download a file to the robot. * * <p/>See {@link MediaUtil#determineMediaSource} for information about what * a path can be. * * @param source the path of the file to download * @param destination the name of the file on the robot */ @SimpleFunction(description = "Download a file to the robot.") public void DownloadFile(String source, String destination) { String functionName = "DownloadFile"; if (!checkBluetooth(functionName)) { return; } if (source.length() == 0) { form.dispatchErrorOccurredEvent(this, functionName, ErrorMessages.ERROR_NXT_INVALID_SOURCE_ARGUMENT); return; } if (destination.length() == 0) { form.dispatchErrorOccurredEvent(this, functionName, ErrorMessages.ERROR_NXT_INVALID_DESTINATION_ARGUMENT); return; } try { File tempFile = MediaUtil.copyMediaToTempFile(form, source); try { InputStream in = new BufferedInputStream(new FileInputStream(tempFile), 1024); try { long fileSize = tempFile.length(); Integer handle = (destination.endsWith(".rxe") || destination.endsWith(".ric")) ? openWriteLinear(functionName, destination, fileSize) : openWrite(functionName, destination, fileSize); if (handle == null) { return; } try { // Send data to NXT 32 bytes at a time. byte[] buffer = new byte[32]; long sentLength = 0; while (sentLength < fileSize) { int chunkLength = (int) Math.min(32, fileSize - sentLength); in.read(buffer, 0, chunkLength); int writtenLength = writeChunk(functionName, handle, buffer, chunkLength); sentLength += writtenLength; } } finally { closeHandle(functionName, handle); } } finally { in.close(); } } finally { tempFile.delete(); } } catch (IOException e) { form.dispatchErrorOccurredEvent(this, functionName, ErrorMessages.ERROR_NXT_UNABLE_TO_DOWNLOAD_FILE, e.getMessage()); return; } } private Integer openWrite(String functionName, String fileName, long fileSize) { byte[] command = new byte[26]; command[0] = (byte) 0x01; // System command telegram, response required command[1] = (byte) 0x81; // OPEN WRITE command copyStringValueToBytes(fileName, command, 2, 19); copyULONGValueToBytes(fileSize, command, 22); byte[] returnPackage = sendCommandAndReceiveReturnPackage(functionName, command); if (evaluateStatus(functionName, returnPackage, command[1])) { if (returnPackage.length == 4) { return getUBYTEValueFromBytes(returnPackage, 3); } else { Log.w(logTag, functionName + ": unexpected return package length " + returnPackage.length + " (expected 4)"); } } return null; } private int writeChunk(String functionName, int handle, byte[] buffer, int length) throws IOException { if (length > 32) { throw new IllegalArgumentException("length must be <= 32"); } byte[] command = new byte[3 + length]; command[0] = (byte) 0x01; // System command telegram, response required command[1] = (byte) 0x83; // WRITE command copyUBYTEValueToBytes(handle, command, 2); System.arraycopy(buffer, 0, command, 3, length); byte[] returnPackage = sendCommandAndReceiveReturnPackage(functionName, command); if (evaluateStatus(functionName, returnPackage, command[1])) { if (returnPackage.length == 6) { int writtenLength = getUWORDValueFromBytes(returnPackage, 4); if (writtenLength != length) { Log.e(logTag, functionName + ": only " + writtenLength + " bytes were written " + "(expected " + length + ")"); throw new IOException("Unable to write file on robot"); } return writtenLength; } else { Log.w(logTag, functionName + ": unexpected return package length " + returnPackage.length + " (expected 6)"); } } return 0; } private void closeHandle(String functionName, int handle) { byte[] command = new byte[3]; command[0] = (byte) 0x01; // System command telegram, response required command[1] = (byte) 0x84; // CLOSE command copyUBYTEValueToBytes(handle, command, 2); byte[] returnPackage = sendCommandAndReceiveReturnPackage(functionName, command); evaluateStatus(functionName, returnPackage, command[1]); } @SimpleFunction(description = "Delete a file on the robot.") public void DeleteFile(String fileName) { String functionName = "DeleteFile"; if (!checkBluetooth(functionName)) { return; } if (fileName.length() == 0) { form.dispatchErrorOccurredEvent(this, functionName, ErrorMessages.ERROR_NXT_INVALID_FILE_NAME); return; } byte[] command = new byte[22]; command[0] = (byte) 0x01; // System command telegram, response required command[1] = (byte) 0x85; // DELETE command copyStringValueToBytes(fileName, command, 2, 19); byte[] returnPackage = sendCommandAndReceiveReturnPackage(functionName, command); evaluateStatus(functionName, returnPackage, command[1]); } @SimpleFunction(description = "Returns a list containing the names of matching files found on " + "the robot.") public List<String> ListFiles(String wildcard) { String functionName = "ListFiles"; if (!checkBluetooth(functionName)) { return new ArrayList<String>(); } List<String> fileNames = new ArrayList<String>(); if (wildcard.length() == 0) { wildcard = "*.*"; } byte[] command = new byte[22]; command[0] = (byte) 0x01; // System command telegram, response required command[1] = (byte) 0x86; // FIND FIRST command copyStringValueToBytes(wildcard, command, 2, 19); byte[] returnPackage = sendCommandAndReceiveReturnPackage(functionName, command); int status = getStatus(functionName, returnPackage, command[1]); while (status == 0) { int handle = getUBYTEValueFromBytes(returnPackage, 3); String fileName = getStringValueFromBytes(returnPackage, 4); fileNames.add(fileName); command = new byte[3]; command[0] = (byte) 0x01; // System command telegram, response required command[1] = (byte) 0x87; // FIND NEXT command copyUBYTEValueToBytes(handle, command, 2); returnPackage = sendCommandAndReceiveReturnPackage(functionName, command); status = getStatus(functionName, returnPackage, command[1]); } return fileNames; } @SimpleFunction(description = "Get the firmware and protocol version numbers for the robot as" + " a list where the first element is the firmware version number and the second element is" + " the protocol version number.") public List<String> GetFirmwareVersion() { String functionName = "GetFirmwareVersion"; if (!checkBluetooth(functionName)) { return new ArrayList<String>(); } byte[] command = new byte[2]; command[0] = (byte) 0x01; // System command telegram, response required command[1] = (byte) 0x88; // GET FIRMWARE VERSION command byte[] returnPackage = sendCommandAndReceiveReturnPackage(functionName, command); if (evaluateStatus(functionName, returnPackage, command[1])) { List<String> versions = new ArrayList<String>(); versions.add(returnPackage[6] + "." + returnPackage[5]); // firmware versions.add(returnPackage[4] + "." + returnPackage[3]); // protocol return versions; } return new ArrayList<String>(); } private Integer openWriteLinear(String functionName, String fileName, long fileSize) { byte[] command = new byte[26]; command[0] = (byte) 0x01; // System command telegram, response required command[1] = (byte) 0x89; // OPEN WRITE LINEAR command copyStringValueToBytes(fileName, command, 2, 19); copyULONGValueToBytes(fileSize, command, 22); byte[] returnPackage = sendCommandAndReceiveReturnPackage(functionName, command); if (evaluateStatus(functionName, returnPackage, command[1])) { if (returnPackage.length == 4) { return getUBYTEValueFromBytes(returnPackage, 3); } else { Log.w(logTag, functionName + ": unexpected return package length " + returnPackage.length + " (expected 4)"); } } return null; } // SetBrickName will change the name of the NXT. // The new name will appear on the NXT LCD immediately, // but the AddressesAndNames property does not update until // the app is restarted. @SimpleFunction(description = "Set the brick name of the robot.") public void SetBrickName(String name) { String functionName = "SetBrickName"; if (!checkBluetooth(functionName)) { return; } byte[] command = new byte[18]; command[0] = (byte) 0x01; // System command telegram, response required command[1] = (byte) 0x98; // SET BRICK NAME command copyStringValueToBytes(name, command, 2, 15); byte[] returnPackage = sendCommandAndReceiveReturnPackage(functionName, command); evaluateStatus(functionName, returnPackage, command[1]); } @SimpleFunction(description = "Get the brick name of the robot.") public String GetBrickName() { String functionName = "GetBrickName"; if (!checkBluetooth(functionName)) { return ""; } byte[] command = new byte[2]; command[0] = (byte) 0x01; // System command telegram, response required command[1] = (byte) 0x9B; // GET DEVICE INFO command byte[] returnPackage = sendCommandAndReceiveReturnPackage(functionName, command); if (evaluateStatus(functionName, returnPackage, command[1])) { return getStringValueFromBytes(returnPackage, 3); } return ""; } }