7 package com.google.appinventor.components.runtime;
20 import android.util.Log;
22 import java.io.BufferedInputStream;
24 import java.io.IOException;
25 import java.io.InputStream;
26 import java.util.ArrayList;
27 import java.util.List;
37 @DesignerComponent(version = YaVersion.NXT_DIRECT_COMMANDS_COMPONENT_VERSION,
38 description =
"A component that provides a low-level interface to a LEGO MINDSTORMS NXT " +
39 "robot, with functions to send NXT Direct Commands.",
40 category = ComponentCategory.LEGOMINDSTORMS,
42 iconName =
"images/legoMindstormsNxt.png")
44 @UsesPermissions(permissionNames =
"android.permission.INTERNET," +
45 "android.permission.WRITE_EXTERNAL_STORAGE," +
46 "android.permission.READ_EXTERNAL_STORAGE")
53 super(container,
"NxtDirectCommands");
61 @
SimpleFunction(description =
"Start execution of a previously downloaded program on " +
63 public
void StartProgram(String programName) {
64 String functionName =
"StartProgram";
65 if (!checkBluetooth(functionName)) {
68 if (programName.length() == 0) {
69 form.dispatchErrorOccurredEvent(
this, functionName,
73 if (programName.indexOf(
".") == -1) {
74 programName +=
".rxe";
77 byte[] command =
new byte[22];
78 command[0] = (byte) 0x80;
79 command[1] = (byte) 0x00;
80 copyStringValueToBytes(programName, command, 2, 19);
81 sendCommand(functionName, command);
84 @
SimpleFunction(description =
"Stop execution of the currently running program on " +
86 public
void StopProgram() {
87 String functionName =
"StopProgram";
88 if (!checkBluetooth(functionName)) {
92 byte[] command =
new byte[2];
93 command[0] = (byte) 0x80;
94 command[1] = (byte) 0x01;
95 sendCommand(functionName, command);
99 public
void PlaySoundFile(String fileName) {
100 String functionName =
"PlaySoundFile";
101 if (!checkBluetooth(functionName)) {
104 if (fileName.length() == 0) {
105 form.dispatchErrorOccurredEvent(
this, functionName,
109 if (fileName.indexOf(
".") == -1) {
113 byte[] command =
new byte[23];
114 command[0] = (byte) 0x80;
115 command[1] = (byte) 0x02;
116 copyBooleanValueToBytes(
false, command, 2);
117 copyStringValueToBytes(fileName, command, 3, 19);
118 sendCommand(functionName, command);
122 public
void PlayTone(
int frequencyHz,
int durationMs) {
123 String functionName =
"PlayTone";
124 if (!checkBluetooth(functionName)) {
128 if (frequencyHz < 200) {
129 Log.w(logTag,
"frequencyHz " + frequencyHz +
" is invalid, using 200.");
132 if (frequencyHz > 14000) {
133 Log.w(logTag,
"frequencyHz " + frequencyHz +
" is invalid, using 14000.");
136 byte[] command =
new byte[6];
137 command[0] = (byte) 0x80;
138 command[1] = (byte) 0x03;
139 copyUWORDValueToBytes(frequencyHz, command, 2);
140 copyUWORDValueToBytes(durationMs, command, 4);
141 sendCommand(functionName, command);
144 @
SimpleFunction(description =
"Sets the output state of a motor on the robot.")
145 public
void SetOutputState(String motorPortLetter,
int power,
int mode,
int regulationMode,
146 int turnRatio,
int runState,
long tachoLimit) {
147 String functionName =
"SetOutputState";
148 if (!checkBluetooth(functionName)) {
154 port = convertMotorPortLetterToNumber(motorPortLetter);
155 }
catch (IllegalArgumentException e) {
156 form.dispatchErrorOccurredEvent(
this, functionName,
161 setOutputState(functionName, port, power, mode,
162 regulationMode, sanitizeTurnRatio(turnRatio), runState, tachoLimit);
165 @
SimpleFunction(description =
"Configure an input sensor on the robot.")
166 public
void SetInputMode(String sensorPortLetter,
int sensorType,
int sensorMode) {
167 String functionName =
"SetInputMode";
168 if (!checkBluetooth(functionName)) {
174 port = convertSensorPortLetterToNumber(sensorPortLetter);
175 }
catch (IllegalArgumentException e) {
176 form.dispatchErrorOccurredEvent(
this, functionName,
181 setInputMode(functionName, port, sensorType, sensorMode);
184 @
SimpleFunction(description =
"Reads the output state of a motor on the robot.")
185 public List<Number> GetOutputState(String motorPortLetter) {
186 String functionName =
"GetOutputState";
187 if (!checkBluetooth(functionName)) {
188 return new ArrayList<Number>();
193 port = convertMotorPortLetterToNumber(motorPortLetter);
194 }
catch (IllegalArgumentException e) {
195 form.dispatchErrorOccurredEvent(
this, functionName,
197 return new ArrayList<Number>();
200 byte[] returnPackage = getOutputState(functionName, port);
201 if (returnPackage !=
null) {
202 List<Number> outputState =
new ArrayList<Number>();
203 outputState.add(getSBYTEValueFromBytes(returnPackage, 4));
204 outputState.add(getUBYTEValueFromBytes(returnPackage, 5));
205 outputState.add(getUBYTEValueFromBytes(returnPackage, 6));
206 outputState.add(getSBYTEValueFromBytes(returnPackage, 7));
207 outputState.add(getUBYTEValueFromBytes(returnPackage, 8));
208 outputState.add(getULONGValueFromBytes(returnPackage, 9));
209 outputState.add(getSLONGValueFromBytes(returnPackage, 13));
210 outputState.add(getSLONGValueFromBytes(returnPackage, 17));
211 outputState.add(getSLONGValueFromBytes(returnPackage, 21));
216 return new ArrayList<Number>();
219 private byte[] getOutputState(String functionName,
int port) {
220 byte[] command =
new byte[3];
221 command[0] = (byte) 0x00;
222 command[1] = (byte) 0x06;
223 copyUBYTEValueToBytes(port, command, 2);
224 byte[] returnPackage = sendCommandAndReceiveReturnPackage(functionName, command);
225 if (evaluateStatus(functionName, returnPackage, command[1])) {
226 if (returnPackage.length == 25) {
227 return returnPackage;
229 Log.w(logTag, functionName +
": unexpected return package length " +
230 returnPackage.length +
" (expected 25)");
236 @SimpleFunction(description =
"Reads the values of an input sensor on the robot. " +
237 "Assumes sensor type has been configured via SetInputMode.")
238 public List<Object> GetInputValues(String sensorPortLetter) {
239 String functionName =
"GetInputValues";
240 if (!checkBluetooth(functionName)) {
241 return new ArrayList<Object>();
246 port = convertSensorPortLetterToNumber(sensorPortLetter);
247 }
catch (IllegalArgumentException e) {
248 form.dispatchErrorOccurredEvent(
this, functionName,
250 return new ArrayList<Object>();
253 byte[] returnPackage = getInputValues(functionName, port);
254 if (returnPackage !=
null) {
255 List<Object> inputValues =
new ArrayList<Object>();
256 inputValues.add(getBooleanValueFromBytes(returnPackage, 4));
257 inputValues.add(getBooleanValueFromBytes(returnPackage, 5));
258 inputValues.add(getUBYTEValueFromBytes(returnPackage, 6));
259 inputValues.add(getUBYTEValueFromBytes(returnPackage, 7));
260 inputValues.add(getUWORDValueFromBytes(returnPackage, 8));
261 inputValues.add(getUWORDValueFromBytes(returnPackage, 10));
262 inputValues.add(getSWORDValueFromBytes(returnPackage, 12));
263 inputValues.add(getSWORDValueFromBytes(returnPackage, 14));
268 return new ArrayList<Object>();
271 @
SimpleFunction(description =
"Reset the scaled value of an input sensor on the robot.")
272 public
void ResetInputScaledValue(String sensorPortLetter) {
273 String functionName =
"ResetInputScaledValue";
274 if (!checkBluetooth(functionName)) {
280 port = convertSensorPortLetterToNumber(sensorPortLetter);
281 }
catch (IllegalArgumentException e) {
282 form.dispatchErrorOccurredEvent(
this, functionName,
287 resetInputScaledValue(functionName, port);
288 byte[] command =
new byte[3];
289 command[0] = (byte) 0x80;
290 command[1] = (byte) 0x08;
291 copyUBYTEValueToBytes(port, command, 2);
292 sendCommand(functionName, command);
295 @
SimpleFunction(description =
"Write a message to a mailbox (1-10) on the robot.")
296 public
void MessageWrite(
int mailbox, String message) {
297 String functionName =
"MessageWrite";
298 if (!checkBluetooth(functionName)) {
305 if (mailbox < 1 || mailbox > 10) {
306 form.dispatchErrorOccurredEvent(
this, functionName,
310 int messageLength = message.length();
311 if (messageLength > 58) {
312 form.dispatchErrorOccurredEvent(
this, functionName,
319 byte[] command =
new byte[4 + messageLength + 1];
320 command[0] = (byte) 0x80;
321 command[1] = (byte) 0x09;
322 copyUBYTEValueToBytes(mailbox, command, 2);
324 copyUBYTEValueToBytes(messageLength + 1, command, 3);
325 copyStringValueToBytes(message, command, 4, messageLength);
327 sendCommand(functionName, command);
331 public
void ResetMotorPosition(String motorPortLetter,
boolean relative) {
332 String functionName =
"ResetMotorPosition";
333 if (!checkBluetooth(functionName)) {
339 port = convertMotorPortLetterToNumber(motorPortLetter);
340 }
catch (IllegalArgumentException e) {
341 form.dispatchErrorOccurredEvent(
this, functionName,
346 byte[] command =
new byte[4];
347 command[0] = (byte) 0x80;
348 command[1] = (byte) 0x0A;
349 copyUBYTEValueToBytes(port, command, 2);
350 copyBooleanValueToBytes(relative, command, 3);
351 sendCommand(functionName, command);
354 @
SimpleFunction(description =
"Get the battery level for the robot. " +
355 "Returns the voltage in millivolts.")
356 public
int GetBatteryLevel() {
357 String functionName =
"GetBatteryLevel";
358 if (!checkBluetooth(functionName)) {
362 byte[] command =
new byte[2];
363 command[0] = (byte) 0x00;
364 command[1] = (byte) 0x0B;
365 byte[] returnPackage = sendCommandAndReceiveReturnPackage(functionName, command);
366 if (evaluateStatus(functionName, returnPackage, command[1])) {
367 if (returnPackage.length == 5) {
368 return getUWORDValueFromBytes(returnPackage, 3);
370 Log.w(logTag,
"GetBatteryLevel: unexpected return package length " +
371 returnPackage.length +
" (expected 5)");
378 public
void StopSoundPlayback() {
379 String functionName =
"StopSoundPlayback";
380 if (!checkBluetooth(functionName)) {
384 byte[] command =
new byte[2];
385 command[0] = (byte) 0x80;
386 command[1] = (byte) 0x0C;
387 sendCommand(functionName, command);
391 "Returns the current sleep time limit in milliseconds.")
392 public
long KeepAlive() {
393 String functionName =
"KeepAlive";
394 if (!checkBluetooth(functionName)) {
398 byte[] command =
new byte[2];
399 command[0] = (byte) 0x00;
400 command[1] = (byte) 0x0D;
401 byte[] returnPackage = sendCommandAndReceiveReturnPackage(functionName, command);
402 if (evaluateStatus(functionName, returnPackage, command[1])) {
403 if (returnPackage.length == 7) {
404 return getULONGValueFromBytes(returnPackage, 3);
406 Log.w(logTag,
"KeepAlive: unexpected return package length " +
407 returnPackage.length +
" (expected 7)");
413 @
SimpleFunction(description =
"Returns the count of available bytes to read.")
414 public
int LsGetStatus(String sensorPortLetter) {
415 String functionName =
"LsGetStatus";
416 if (!checkBluetooth(functionName)) {
422 port = convertSensorPortLetterToNumber(sensorPortLetter);
423 }
catch (IllegalArgumentException e) {
424 form.dispatchErrorOccurredEvent(
this, functionName,
429 return lsGetStatus(functionName, port);
432 @
SimpleFunction(description =
"Writes low speed data to an input sensor on the robot. " +
433 "Assumes sensor type has been configured via SetInputMode.")
434 public
void LsWrite(String sensorPortLetter,
YailList list,
int rxDataLength) {
435 String functionName =
"LsWrite";
436 if (!checkBluetooth(functionName)) {
442 port = convertSensorPortLetterToNumber(sensorPortLetter);
443 }
catch (IllegalArgumentException e) {
444 form.dispatchErrorOccurredEvent(
this, functionName,
449 if (list.size() > 16) {
450 form.dispatchErrorOccurredEvent(
this, functionName,
455 Object[] array = list.toArray();
456 byte[] bytes =
new byte[array.length];
457 for (
int i = 0; i < array.length; i++) {
460 Object element = array[i];
461 String s = element.toString();
464 n = Integer.decode(s);
465 }
catch (NumberFormatException e) {
466 form.dispatchErrorOccurredEvent(
this, functionName,
470 bytes[i] = (byte) (n & 0xFF);
472 if (n != 0 && n != -1) {
473 form.dispatchErrorOccurredEvent(
this, functionName,
478 lsWrite(functionName, port, bytes, rxDataLength);
482 @
SimpleFunction(description =
"Reads unsigned low speed data from an input sensor on the " +
483 "robot. Assumes sensor type has been configured via SetInputMode.")
484 public List<Integer> LsRead(String sensorPortLetter) {
485 String functionName =
"LsRead";
486 if (!checkBluetooth(functionName)) {
487 return new ArrayList<Integer>();
492 port = convertSensorPortLetterToNumber(sensorPortLetter);
493 }
catch (IllegalArgumentException e) {
494 form.dispatchErrorOccurredEvent(
this, functionName,
496 return new ArrayList<Integer>();
499 byte[] returnPackage = lsRead(functionName, port);
500 if (returnPackage !=
null) {
501 List<Integer> list =
new ArrayList<Integer>();
502 int count = getUBYTEValueFromBytes(returnPackage, 3);
503 for (
int i = 0; i < count; i++) {
504 int n = returnPackage[4 + i] & 0xFF;
511 return new ArrayList<Integer>();
514 @
SimpleFunction(description =
"Get the name of currently running program on " +
516 public String GetCurrentProgramName() {
517 String functionName =
"GetCurrentProgramName";
518 if (!checkBluetooth(functionName)) {
522 byte[] command =
new byte[2];
523 command[0] = (byte) 0x00;
524 command[1] = (byte) 0x11;
525 byte[] returnPackage = sendCommandAndReceiveReturnPackage(functionName, command);
526 int status = getStatus(functionName, returnPackage, command[1]);
529 return getStringValueFromBytes(returnPackage, 3);
531 if (status == 0xEC) {
536 evaluateStatus(functionName, returnPackage, command[1]);
540 @
SimpleFunction(description =
"Read a message from a mailbox (1-10) on the robot.")
541 public String MessageRead(
int mailbox) {
542 String functionName =
"MessageRead";
543 if (!checkBluetooth(functionName)) {
550 if (mailbox < 1 || mailbox > 10) {
551 form.dispatchErrorOccurredEvent(
this, functionName,
558 byte[] command =
new byte[5];
559 command[0] = (byte) 0x00;
560 command[1] = (byte) 0x13;
561 copyUBYTEValueToBytes(0, command, 2);
562 copyUBYTEValueToBytes(mailbox, command, 3);
563 copyBooleanValueToBytes(
true, command, 4);
564 byte[] returnPackage = sendCommandAndReceiveReturnPackage(functionName, command);
565 if (evaluateStatus(functionName, returnPackage, command[1])) {
566 if (returnPackage.length == 64) {
567 int mailboxEcho = getUBYTEValueFromBytes(returnPackage, 3);
568 if (mailboxEcho != mailbox) {
569 Log.w(logTag,
"MessageRead: unexpected return mailbox: " +
570 mailboxEcho +
" (expected " + mailbox +
")");
572 int messageLength = getUBYTEValueFromBytes(returnPackage, 4) - 1;
573 return getStringValueFromBytes(returnPackage, 5, messageLength);
575 Log.w(logTag,
"MessageRead: unexpected return package length " +
576 returnPackage.length +
" (expected 64)");
592 public
void DownloadFile(String source, String destination) {
593 String functionName =
"DownloadFile";
594 if (!checkBluetooth(functionName)) {
597 if (source.length() == 0) {
598 form.dispatchErrorOccurredEvent(
this, functionName,
602 if (destination.length() == 0) {
603 form.dispatchErrorOccurredEvent(
this, functionName,
611 InputStream in =
new BufferedInputStream(
FileUtil.
openFile(form, tempFile), 1024);
613 long fileSize = tempFile.length();
614 Integer handle = (destination.endsWith(
".rxe") || destination.endsWith(
".ric"))
615 ? openWriteLinear(functionName, destination, fileSize)
616 : openWrite(functionName, destination, fileSize);
617 if (handle ==
null) {
622 byte[] buffer =
new byte[32];
624 while (sentLength < fileSize) {
625 int chunkLength = (int) Math.min(32, fileSize - sentLength);
626 in.read(buffer, 0, chunkLength);
627 int writtenLength = writeChunk(functionName, handle, buffer, chunkLength);
628 sentLength += writtenLength;
631 closeHandle(functionName, handle);
639 }
catch (IOException e) {
640 form.dispatchErrorOccurredEvent(
this, functionName,
646 private Integer openWrite(String functionName, String fileName,
long fileSize) {
647 byte[] command =
new byte[26];
648 command[0] = (byte) 0x01;
649 command[1] = (byte) 0x81;
650 copyStringValueToBytes(fileName, command, 2, 19);
651 copyULONGValueToBytes(fileSize, command, 22);
652 byte[] returnPackage = sendCommandAndReceiveReturnPackage(functionName, command);
653 if (evaluateStatus(functionName, returnPackage, command[1])) {
654 if (returnPackage.length == 4) {
655 return getUBYTEValueFromBytes(returnPackage, 3);
657 Log.w(logTag, functionName +
": unexpected return package length " +
658 returnPackage.length +
" (expected 4)");
664 private int writeChunk(String functionName,
int handle,
byte[] buffer,
int length)
667 throw new IllegalArgumentException(
"length must be <= 32");
670 byte[] command =
new byte[3 + length];
671 command[0] = (byte) 0x01;
672 command[1] = (byte) 0x83;
673 copyUBYTEValueToBytes(handle, command, 2);
674 System.arraycopy(buffer, 0, command, 3, length);
675 byte[] returnPackage = sendCommandAndReceiveReturnPackage(functionName, command);
676 if (evaluateStatus(functionName, returnPackage, command[1])) {
677 if (returnPackage.length == 6) {
678 int writtenLength = getUWORDValueFromBytes(returnPackage, 4);
679 if (writtenLength != length) {
680 Log.e(logTag, functionName +
": only " + writtenLength +
" bytes were written " +
681 "(expected " + length +
")");
682 throw new IOException(
"Unable to write file on robot");
684 return writtenLength;
686 Log.w(logTag, functionName +
": unexpected return package length " +
687 returnPackage.length +
" (expected 6)");
693 private void closeHandle(String functionName,
int handle) {
694 byte[] command =
new byte[3];
695 command[0] = (byte) 0x01;
696 command[1] = (byte) 0x84;
697 copyUBYTEValueToBytes(handle, command, 2);
698 byte[] returnPackage = sendCommandAndReceiveReturnPackage(functionName, command);
699 evaluateStatus(functionName, returnPackage, command[1]);
702 @SimpleFunction(description =
"Delete a file on the robot.")
703 public
void DeleteFile(String fileName) {
704 String functionName =
"DeleteFile";
705 if (!checkBluetooth(functionName)) {
708 if (fileName.length() == 0) {
709 form.dispatchErrorOccurredEvent(
this, functionName,
714 byte[] command =
new byte[22];
715 command[0] = (byte) 0x01;
716 command[1] = (byte) 0x85;
717 copyStringValueToBytes(fileName, command, 2, 19);
718 byte[] returnPackage = sendCommandAndReceiveReturnPackage(functionName, command);
719 evaluateStatus(functionName, returnPackage, command[1]);
722 @
SimpleFunction(description =
"Returns a list containing the names of matching files found on " +
724 public List<String> ListFiles(String wildcard) {
725 String functionName =
"ListFiles";
726 if (!checkBluetooth(functionName)) {
727 return new ArrayList<String>();
730 List<String> fileNames =
new ArrayList<String>();
732 if (wildcard.length() == 0) {
736 byte[] command =
new byte[22];
737 command[0] = (byte) 0x01;
738 command[1] = (byte) 0x86;
739 copyStringValueToBytes(wildcard, command, 2, 19);
740 byte[] returnPackage = sendCommandAndReceiveReturnPackage(functionName, command);
741 int status = getStatus(functionName, returnPackage, command[1]);
742 while (status == 0) {
743 int handle = getUBYTEValueFromBytes(returnPackage, 3);
744 String fileName = getStringValueFromBytes(returnPackage, 4);
745 fileNames.add(fileName);
746 command =
new byte[3];
747 command[0] = (byte) 0x01;
748 command[1] = (byte) 0x87;
749 copyUBYTEValueToBytes(handle, command, 2);
750 returnPackage = sendCommandAndReceiveReturnPackage(functionName, command);
751 status = getStatus(functionName, returnPackage, command[1]);
756 @
SimpleFunction(description =
"Get the firmware and protocol version numbers for the robot as" +
757 " a list where the first element is the firmware version number and the second element is" +
758 " the protocol version number.")
759 public List<String> GetFirmwareVersion() {
760 String functionName =
"GetFirmwareVersion";
761 if (!checkBluetooth(functionName)) {
762 return new ArrayList<String>();
765 byte[] command =
new byte[2];
766 command[0] = (byte) 0x01;
767 command[1] = (byte) 0x88;
768 byte[] returnPackage = sendCommandAndReceiveReturnPackage(functionName, command);
769 if (evaluateStatus(functionName, returnPackage, command[1])) {
770 List<String> versions =
new ArrayList<String>();
771 versions.add(returnPackage[6] +
"." + returnPackage[5]);
772 versions.add(returnPackage[4] +
"." + returnPackage[3]);
775 return new ArrayList<String>();
778 private Integer openWriteLinear(String functionName, String fileName,
long fileSize) {
779 byte[] command =
new byte[26];
780 command[0] = (byte) 0x01;
781 command[1] = (byte) 0x89;
782 copyStringValueToBytes(fileName, command, 2, 19);
783 copyULONGValueToBytes(fileSize, command, 22);
784 byte[] returnPackage = sendCommandAndReceiveReturnPackage(functionName, command);
785 if (evaluateStatus(functionName, returnPackage, command[1])) {
786 if (returnPackage.length == 4) {
787 return getUBYTEValueFromBytes(returnPackage, 3);
789 Log.w(logTag, functionName +
": unexpected return package length " +
790 returnPackage.length +
" (expected 4)");
800 @SimpleFunction(description =
"Set the brick name of the robot.")
801 public
void SetBrickName(String name) {
802 String functionName =
"SetBrickName";
803 if (!checkBluetooth(functionName)) {
807 byte[] command =
new byte[18];
808 command[0] = (byte) 0x01;
809 command[1] = (byte) 0x98;
810 copyStringValueToBytes(name, command, 2, 15);
811 byte[] returnPackage = sendCommandAndReceiveReturnPackage(functionName, command);
812 evaluateStatus(functionName, returnPackage, command[1]);
816 public String GetBrickName() {
817 String functionName =
"GetBrickName";
818 if (!checkBluetooth(functionName)) {
822 byte[] command =
new byte[2];
823 command[0] = (byte) 0x01;
824 command[1] = (byte) 0x9B;
825 byte[] returnPackage = sendCommandAndReceiveReturnPackage(functionName, command);
826 if (evaluateStatus(functionName, returnPackage, command[1])) {
827 return getStringValueFromBytes(returnPackage, 3);