// -*- 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.DesignerProperty; import com.google.appinventor.components.annotations.PropertyCategory; import com.google.appinventor.components.annotations.SimpleEvent; import com.google.appinventor.components.annotations.SimpleFunction; import com.google.appinventor.components.annotations.SimpleObject; import com.google.appinventor.components.annotations.SimpleProperty; import com.google.appinventor.components.common.ComponentCategory; import com.google.appinventor.components.common.PropertyTypeConstants; import com.google.appinventor.components.common.YaVersion; import android.os.Handler; /** * A component that provides a high-level interface to an ultrasonic sensor on a LEGO * MINDSTORMS NXT robot. * */ @DesignerComponent(version = YaVersion.NXT_ULTRASONICSENSOR_COMPONENT_VERSION, description = "A component that provides a high-level interface to an ultrasonic sensor on a " + "LEGO MINDSTORMS NXT robot.", category = ComponentCategory.LEGOMINDSTORMS, nonVisible = true, iconName = "images/legoMindstormsNxt.png") @SimpleObject public class NxtUltrasonicSensor extends LegoMindstormsNxtSensor implements Deleteable { private enum State { UNKNOWN, BELOW_RANGE, WITHIN_RANGE, ABOVE_RANGE } private static final String DEFAULT_SENSOR_PORT = "4"; private static final int DEFAULT_BOTTOM_OF_RANGE = 30; private static final int DEFAULT_TOP_OF_RANGE = 90; private Handler handler; private final Runnable sensorReader; private State previousState; private int bottomOfRange; private int topOfRange; private boolean belowRangeEventEnabled; private boolean withinRangeEventEnabled; private boolean aboveRangeEventEnabled; /** * Creates a new NxtUltrasonicSensor component. */ public NxtUltrasonicSensor(ComponentContainer container) { super(container, "NxtUltrasonicSensor"); handler = new Handler(); previousState = State.UNKNOWN; sensorReader = new Runnable() { public void run() { if (bluetooth != null && bluetooth.IsConnected()) { SensorValue<Integer> sensorValue = getDistanceValue(""); if (sensorValue.valid) { State currentState; if (sensorValue.value < bottomOfRange) { currentState = State.BELOW_RANGE; } else if (sensorValue.value > topOfRange) { currentState = State.ABOVE_RANGE; } else { currentState = State.WITHIN_RANGE; } if (currentState != previousState) { if (currentState == State.BELOW_RANGE && belowRangeEventEnabled) { BelowRange(); } if (currentState == State.WITHIN_RANGE && withinRangeEventEnabled) { WithinRange(); } if (currentState == State.ABOVE_RANGE && aboveRangeEventEnabled) { AboveRange(); } } previousState = currentState; } } if (isHandlerNeeded()) { handler.post(sensorReader); } } }; SensorPort(DEFAULT_SENSOR_PORT); BottomOfRange(DEFAULT_BOTTOM_OF_RANGE); TopOfRange(DEFAULT_TOP_OF_RANGE); BelowRangeEventEnabled(false); WithinRangeEventEnabled(false); AboveRangeEventEnabled(false); } @Override protected void initializeSensor(String functionName) { setInputMode(functionName, port, SENSOR_TYPE_LOWSPEED_9V, SENSOR_MODE_RAWMODE); configureUltrasonicSensor(functionName); } private void configureUltrasonicSensor(String functionName) { // Set to continuous read mode. byte[] data = new byte[3]; data[0] = (byte) 0x02; data[1] = (byte) 0x41; data[2] = (byte) 0x02; lsWrite(functionName, port, data, 0); } /** * Specifies the sensor port that the sensor is connected to. */ @DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_LEGO_NXT_SENSOR_PORT, defaultValue = DEFAULT_SENSOR_PORT) @SimpleProperty(userVisible = false) public void SensorPort(String sensorPortLetter) { setSensorPort(sensorPortLetter); } @SimpleFunction(description = "Returns the current distance in centimeters as a value between " + "0 and 254, or -1 if the distance can not be read.") public int GetDistance() { String functionName = "GetDistance"; if (!checkBluetooth(functionName)) { return -1; } SensorValue<Integer> sensorValue = getDistanceValue(functionName); if (sensorValue.valid) { return sensorValue.value; } // invalid response return -1; } private SensorValue<Integer> getDistanceValue(String functionName) { // Send command to initiate read byte[] data = new byte[2]; data[0] = (byte) 0x02; data[1] = (byte) 0x42; lsWrite(functionName, port, data, 1); // Wait for read data to be ready, but only try 3 times. for (int i = 0; i < 3; i++) { int countAvailableBytes = lsGetStatus(functionName, port); if (countAvailableBytes > 0) { // Read data. byte[] returnPackage = lsRead(functionName, port); if (returnPackage != null) { int value = getUBYTEValueFromBytes(returnPackage, 4); if (value >= 0 && value <= 254) { return new SensorValue<Integer>(true, value); } } break; } } // invalid response or not ready after 3 tries return new SensorValue<Integer>(false, null); } /** * Returns the bottom of the range used for the BelowRange, WithinRange, * and AboveRange events. */ @SimpleProperty(description = "The bottom of the range used for the BelowRange, WithinRange," + " and AboveRange events.", category = PropertyCategory.BEHAVIOR) public int BottomOfRange() { return bottomOfRange; } /** * Specifies the bottom of the range used for the BelowRange, WithinRange, * and AboveRange events. */ @DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_NON_NEGATIVE_INTEGER, defaultValue = "" + DEFAULT_BOTTOM_OF_RANGE) @SimpleProperty public void BottomOfRange(int bottomOfRange) { this.bottomOfRange = bottomOfRange; previousState = State.UNKNOWN; } /** * Returns the top of the range used for the BelowRange, WithinRange, and * AboveRange events. */ @SimpleProperty(description = "The top of the range used for the BelowRange, WithinRange, and" + " AboveRange events.", category = PropertyCategory.BEHAVIOR) public int TopOfRange() { return topOfRange; } /** * Specifies the top of the range used for the BelowRange, WithinRange, and * AboveRange events. */ @DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_NON_NEGATIVE_INTEGER, defaultValue = "" + DEFAULT_TOP_OF_RANGE) @SimpleProperty public void TopOfRange(int topOfRange) { this.topOfRange = topOfRange; previousState = State.UNKNOWN; } /** * Returns whether the BelowRange event should fire when the distance * goes below the BottomOfRange. */ @SimpleProperty(description = "Whether the BelowRange event should fire when the distance" + " goes below the BottomOfRange.", category = PropertyCategory.BEHAVIOR) public boolean BelowRangeEventEnabled() { return belowRangeEventEnabled; } /** * Specifies whether the BelowRange event should fire when the distance * goes below the BottomOfRange. */ @DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_BOOLEAN, defaultValue = "False") @SimpleProperty public void BelowRangeEventEnabled(boolean enabled) { boolean handlerWasNeeded = isHandlerNeeded(); belowRangeEventEnabled = enabled; boolean handlerIsNeeded = isHandlerNeeded(); if (handlerWasNeeded && !handlerIsNeeded) { handler.removeCallbacks(sensorReader); } if (!handlerWasNeeded && handlerIsNeeded) { previousState = State.UNKNOWN; handler.post(sensorReader); } } @SimpleEvent(description = "Distance has gone below the range.") public void BelowRange() { EventDispatcher.dispatchEvent(this, "BelowRange"); } /** * Returns whether the WithinRange event should fire when the distance * goes between the BottomOfRange and the TopOfRange. */ @SimpleProperty(description = "Whether the WithinRange event should fire when the distance" + " goes between the BottomOfRange and the TopOfRange.", category = PropertyCategory.BEHAVIOR) public boolean WithinRangeEventEnabled() { return withinRangeEventEnabled; } /** * Specifies whether the WithinRange event should fire when the distance * goes between the BottomOfRange and the TopOfRange. */ @DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_BOOLEAN, defaultValue = "False") @SimpleProperty public void WithinRangeEventEnabled(boolean enabled) { boolean handlerWasNeeded = isHandlerNeeded(); withinRangeEventEnabled = enabled; boolean handlerIsNeeded = isHandlerNeeded(); if (handlerWasNeeded && !handlerIsNeeded) { handler.removeCallbacks(sensorReader); } if (!handlerWasNeeded && handlerIsNeeded) { previousState = State.UNKNOWN; handler.post(sensorReader); } } @SimpleEvent(description = "Distance has gone within the range.") public void WithinRange() { EventDispatcher.dispatchEvent(this, "WithinRange"); } /** * Returns whether the AboveRange event should fire when the distance * goes above the TopOfRange. */ @SimpleProperty(description = "Whether the AboveRange event should fire when the distance" + " goes above the TopOfRange.", category = PropertyCategory.BEHAVIOR) public boolean AboveRangeEventEnabled() { return aboveRangeEventEnabled; } /** * Specifies whether the AboveRange event should fire when the distance * goes above the TopOfRange. */ @DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_BOOLEAN, defaultValue = "False") @SimpleProperty public void AboveRangeEventEnabled(boolean enabled) { boolean handlerWasNeeded = isHandlerNeeded(); aboveRangeEventEnabled = enabled; boolean handlerIsNeeded = isHandlerNeeded(); if (handlerWasNeeded && !handlerIsNeeded) { handler.removeCallbacks(sensorReader); } if (!handlerWasNeeded && handlerIsNeeded) { previousState = State.UNKNOWN; handler.post(sensorReader); } } @SimpleEvent(description = "Distance has gone above the range.") public void AboveRange() { EventDispatcher.dispatchEvent(this, "AboveRange"); } private boolean isHandlerNeeded() { return belowRangeEventEnabled || withinRangeEventEnabled || aboveRangeEventEnabled; } // Deleteable implementation @Override public void onDelete() { handler.removeCallbacks(sensorReader); super.onDelete(); } }