// -*- mode: java; c-basic-offset: 2; -*-
// Copyright 2016 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 com.google.appinventor.components.runtime.util.ErrorMessages;
import android.os.Handler;
/**
* A component that provides a high-level interface to an ultrasonic sensor on a LEGO
* MINDSTORMS EV3 robot.
*
* @author jerry73204@gmail.com (jerry73204)
* @author spaded06543@gmail.com (Alvin Chang)
*/
@DesignerComponent(version = YaVersion.EV3_ULTRASONICSENSOR_COMPONENT_VERSION,
description = "A component that provides a high-level interface to an ultrasonic sensor on a " +
"LEGO MINDSTORMS EV3 robot.",
category = ComponentCategory.LEGOMINDSTORMS,
nonVisible = true,
iconName = "images/legoMindstormsEv3.png")
@SimpleObject
public class Ev3UltrasonicSensor extends LegoMindstormsEv3Sensor implements Deleteable {
private static final int SENSOR_TYPE = 30;
private static final int SENSOR_MODE_CM = 0;
private static final int SENSOR_MODE_INCH = 1;
private static final String SENSOR_MODE_CM_STRING = "cm";
private static final String SENSOR_MODE_INCH_STRING = "inch";
private static final int DEFAULT_BOTTOM_OF_RANGE = 30;
private static final int DEFAULT_TOP_OF_RANGE = 90;
private static final String DEFAULT_SENSOR_MODE_STRING = SENSOR_MODE_CM_STRING;
private static final int DELAY_MILLISECONDS = 50;
private String modeString = SENSOR_MODE_CM_STRING;
private int mode = SENSOR_MODE_CM;
private Handler eventHandler;
private final Runnable sensorValueChecker;
private double previousDistance = -1.0;
private int bottomOfRange;
private int topOfRange;
private boolean belowRangeEventEnabled;
private boolean withinRangeEventEnabled;
private boolean aboveRangeEventEnabled;
/**
* Creates a new Ev3UltrasonicSensor component.
*/
public Ev3UltrasonicSensor(ComponentContainer container) {
super(container, "Ev3UltrasonicSensor");
eventHandler = new Handler();
sensorValueChecker = new Runnable() {
public void run() {
String functionName = "";
if (bluetooth != null && bluetooth.IsConnected()) {
double currentDistance = getDistance(functionName);
if (previousDistance < 0.0) {
previousDistance = currentDistance;
eventHandler.postDelayed(this, DELAY_MILLISECONDS);
return;
}
if (currentDistance < bottomOfRange) {
if (belowRangeEventEnabled && previousDistance >= bottomOfRange)
BelowRange();
} else if (currentDistance > topOfRange) {
if (aboveRangeEventEnabled && previousDistance <= topOfRange)
AboveRange();
} else {
if (withinRangeEventEnabled && (previousDistance < bottomOfRange || previousDistance > topOfRange))
WithinRange();
}
previousDistance = currentDistance;
}
eventHandler.postDelayed(this, DELAY_MILLISECONDS);
}
};
eventHandler.post(sensorValueChecker);
TopOfRange(DEFAULT_TOP_OF_RANGE);
BottomOfRange(DEFAULT_BOTTOM_OF_RANGE);
BelowRangeEventEnabled(false);
AboveRangeEventEnabled(false);
WithinRangeEventEnabled(false);
Unit(DEFAULT_SENSOR_MODE_STRING);
}
@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 double GetDistance() {
String functionName = "GetDistance";
return getDistance(functionName);
}
private double getDistance(String functionName) {
double distance = readInputSI(functionName, 0, sensorPortNumber, SENSOR_TYPE, mode);
return distance == 255 ? -1.0 : distance;
}
/**
* 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;
}
/**
* 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;
}
/**
* 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) {
belowRangeEventEnabled = enabled;
}
/**
* Called when the detected distance has gone below the range.
*/
@SimpleEvent(description = "Called when the detected 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) {
withinRangeEventEnabled = enabled;
}
/**
* Called when the detected distance has gone within the range.
*/
@SimpleEvent(description = "Called when the detected 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) {
aboveRangeEventEnabled = enabled;
}
/**
* Called when the detected distance has gone above the range.
*/
@SimpleEvent(description = "Called when the detected distance has gone above the range.")
public void AboveRange() {
EventDispatcher.dispatchEvent(this, "AboveRange");
}
/**
* Specifies the unit of distance.
*/
@DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_LEGO_EV3_ULTRASONIC_SENSOR_MODE,
defaultValue = DEFAULT_SENSOR_MODE_STRING)
@SimpleProperty
public void Unit(String unitName) {
String functionName = "Unit";
try {
setMode(unitName);
} catch(IllegalArgumentException e) {
form.dispatchErrorOccurredEvent(this, functionName, ErrorMessages.ERROR_EV3_ILLEGAL_ARGUMENT, functionName);
}
}
/**
* Returns the unit of distance.
*/
@SimpleProperty(description = "The distance unit, which can be either \"cm\" or \"inch\".",
category = PropertyCategory.BEHAVIOR)
public String Unit() {
return modeString;
}
/**
* Measure the distance in centimeters.
*/
@SimpleFunction(description = "Measure the distance in centimeters.")
public void SetCmUnit() {
String functionName = "SetCmUnit";
try {
setMode(SENSOR_MODE_CM_STRING);
} catch(IllegalArgumentException e) {
form.dispatchErrorOccurredEvent(this, functionName, ErrorMessages.ERROR_EV3_ILLEGAL_ARGUMENT, functionName);
}
}
/**
* Measure the distance in inches.
*/
@SimpleFunction(description = "Measure the distance in inches.")
public void SetInchUnit() {
String functionName = "SetInchUnit";
try {
setMode(SENSOR_MODE_INCH_STRING);
} catch(IllegalArgumentException e) {
form.dispatchErrorOccurredEvent(this, functionName, ErrorMessages.ERROR_EV3_ILLEGAL_ARGUMENT, functionName);
}
}
private void setMode(String newModeString) {
previousDistance = -1.0;
if (SENSOR_MODE_CM_STRING.equals(newModeString)) {
mode = SENSOR_MODE_CM;
}
else if (SENSOR_MODE_INCH_STRING.equals(newModeString)) {
mode = SENSOR_MODE_INCH;
}
else
throw new IllegalArgumentException();
this.modeString = newModeString;
}
// Deleteable implementation
@Override
public void onDelete() {
eventHandler.removeCallbacks(sensorValueChecker);
super.onDelete();
}
}