// -*- 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 a color sensor on a
* LEGO MINDSTORMS EV3 robot.
*
* @author jerry73204@gmail.com (jerry73204)
* @author spaded06543@gmail.com (Alvin Chang)
*/
@DesignerComponent(version = YaVersion.EV3_COLORSENSOR_COMPONENT_VERSION,
description = "A component that provides a high-level interface to a color sensor on a " +
"LEGO MINDSTORMS EV3 robot.",
category = ComponentCategory.LEGOMINDSTORMS,
nonVisible = true,
iconName = "images/legoMindstormsEv3.png")
@SimpleObject
public class Ev3ColorSensor extends LegoMindstormsEv3Sensor implements Deleteable {
private static final int SENSOR_TYPE = 29;
private static final int SENSOR_MODE_REFLECTED = 0;
private static final int SENSOR_MODE_AMBIENT = 1;
private static final int SENSOR_MODE_COLOR = 2;
private static final String SENSOR_MODE_REFLECTED_STRING = "reflected";
private static final String SENSOR_MODE_AMBIENT_STRING = "ambient";
private static final String SENSOR_MODE_COLOR_STRING = "color";
private static final int DEFAULT_BOTTOM_OF_RANGE = 30;
private static final int DEFAULT_TOP_OF_RANGE = 60;
private static final String DEFAULT_SENSOR_MODE_STRING = SENSOR_MODE_REFLECTED_STRING;
private static final int DELAY_MILLISECONDS = 50;
private int mode = 0;
private String modeString = SENSOR_MODE_REFLECTED_STRING;
private Handler eventHandler;
private final Runnable sensorValueChecker;
private int bottomOfRange;
private int topOfRange;
private int previousLightLevel = 0;
private int previousColor = -1;
private boolean belowRangeEventEnabled;
private boolean withinRangeEventEnabled;
private boolean aboveRangeEventEnabled;
private boolean colorChangedEventEnabled;
/**
* Creates a new Ev3ColorSensor component.
*/
public Ev3ColorSensor(ComponentContainer container) {
super(container, "Ev3ColorSensor");
eventHandler = new Handler();
sensorValueChecker = new Runnable() {
public void run() {
String functionName = "";
if (bluetooth != null && bluetooth.IsConnected()) {
if (mode == SENSOR_MODE_COLOR) {
int currentColor = getSensorValue(functionName);
if (previousColor < 0) {
previousColor = currentColor;
eventHandler.postDelayed(this, DELAY_MILLISECONDS);
return;
}
if (currentColor != previousColor && colorChangedEventEnabled)
ColorChanged(currentColor, toColorName(functionName, currentColor));
previousColor = currentColor;
} else { // mode == SENSOR_MODE_REFLECTED or mode == SENSOR_MODE_AMBIENT
int currentLightLevel = getSensorValue(functionName);
if (previousLightLevel < 0) {
previousLightLevel = currentLightLevel;
eventHandler.postDelayed(this, DELAY_MILLISECONDS);
return;
}
// trigger events according to the conditions
if (currentLightLevel < bottomOfRange) {
if (belowRangeEventEnabled && previousLightLevel >= bottomOfRange)
BelowRange();
} else if (currentLightLevel > topOfRange) {
if (aboveRangeEventEnabled && previousLightLevel <= topOfRange)
AboveRange();
} else {
if (withinRangeEventEnabled && (previousLightLevel < bottomOfRange || previousLightLevel > topOfRange))
WithinRange();
}
previousLightLevel = currentLightLevel;
}
}
eventHandler.postDelayed(this, DELAY_MILLISECONDS);
}
};
eventHandler.post(sensorValueChecker);
TopOfRange(DEFAULT_TOP_OF_RANGE);
BottomOfRange(DEFAULT_BOTTOM_OF_RANGE);
BelowRangeEventEnabled(false);
AboveRangeEventEnabled(false);
WithinRangeEventEnabled(false);
ColorChangedEventEnabled(false);
Mode(DEFAULT_SENSOR_MODE_STRING);
}
/**
* It returns the light level in percentage.
*/
@SimpleFunction(description = "It returns the light level in percentage, or " +
"-1 when the light level cannot be read.")
public int GetLightLevel() {
if (mode == SENSOR_MODE_COLOR)
return -1;
String functionName = "GetLightLevel";
return getSensorValue(functionName);
}
/**
* It returns the color code for the detected color.
*/
@SimpleFunction(description = "It returns the color code from 0 to 7 corresponding to no color, black, blue, green, yellow, red, white and brown.")
public int GetColorCode() {
if (mode != SENSOR_MODE_COLOR)
return 0;
String functionName = "GetColorCode";
return getSensorValue(functionName);
}
/**
* Returns the name of the detected color.
*/
@SimpleFunction(description = "Return the color name in one of \"No Color\", \"Black\", \"Blue\", \"Green\", \"Yellow\", \"Red\", \"White\", \"Brown\".")
public String GetColorName() {
if (mode != SENSOR_MODE_COLOR)
return "No Color";
String functionName = "GetColorName";
int colorCode = getSensorValue(functionName);
return toColorName(functionName, colorCode);
}
/**
* 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 light level
* goes below the BottomOfRange.
*/
@SimpleProperty(description = "Whether the BelowRange event should fire when the light level" +
" goes below the BottomOfRange.",
category = PropertyCategory.BEHAVIOR)
public boolean BelowRangeEventEnabled() {
return belowRangeEventEnabled;
}
/**
* Specifies whether the BelowRange event should fire when the light level
* goes below the BottomOfRange.
*/
@DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_BOOLEAN,
defaultValue = "False")
@SimpleProperty
public void BelowRangeEventEnabled(boolean enabled) {
belowRangeEventEnabled = enabled;
}
@SimpleEvent(description = "Light level has gone below the range.")
public void BelowRange() {
EventDispatcher.dispatchEvent(this, "BelowRange");
}
/**
* Returns whether the WithinRange event should fire when the light level
* goes between the BottomOfRange and the TopOfRange.
*/
@SimpleProperty(description = "Whether the WithinRange event should fire when the light level " +
"goes between the BottomOfRange and the TopOfRange.",
category = PropertyCategory.BEHAVIOR)
public boolean WithinRangeEventEnabled() {
return withinRangeEventEnabled;
}
/**
* Specifies whether the WithinRange event should fire when the light level
* goes between the BottomOfRange and the TopOfRange.
*/
@DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_BOOLEAN,
defaultValue = "False")
@SimpleProperty
public void WithinRangeEventEnabled(boolean enabled) {
withinRangeEventEnabled = enabled;
}
@SimpleEvent(description = "Light level has gone within the range.")
public void WithinRange() {
EventDispatcher.dispatchEvent(this, "WithinRange");
}
/**
* Returns whether the AboveRange event should fire when the light level
* goes above the TopOfRange.
*/
@SimpleProperty(description = "Whether the AboveRange event should fire when the light level " +
"goes above the TopOfRange.",
category = PropertyCategory.BEHAVIOR)
public boolean AboveRangeEventEnabled() {
return aboveRangeEventEnabled;
}
/**
* Specifies whether the AboveRange event should fire when the light level
* goes above the TopOfRange.
*/
@DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_BOOLEAN,
defaultValue = "False")
@SimpleProperty
public void AboveRangeEventEnabled(boolean enabled) {
aboveRangeEventEnabled = enabled;
}
@SimpleEvent(description = "Light level has gone above the range.")
public void AboveRange() {
EventDispatcher.dispatchEvent(this, "AboveRange");
}
/**
* Returns whether the ColorChanged event should fire when the DetectColor
* property is set to True and the detected color changes.
*/
@SimpleProperty(description = "Whether the ColorChanged event should fire when the Mode" +
" property is set to \"color\" and the detected color changes.",
category = PropertyCategory.BEHAVIOR)
public boolean ColorChangedEventEnabled() {
return colorChangedEventEnabled;
}
/**
* Specifies whether the ColorChanged event should fire when the DetectColor
* property is set to True and the detected color changes
*/
@DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_BOOLEAN,
defaultValue = "False")
@SimpleProperty
public void ColorChangedEventEnabled(boolean enabled) {
colorChangedEventEnabled = enabled;
}
/**
* Called when the detected color has changed.
*/
@SimpleEvent(description = "Called when the detected color has changed. The ColorChanged event will occur " +
"if the Mode property is set to \"color\" and the ColorChangedEventEnabled property " +
"is set to True.")
public void ColorChanged(int colorCode, String colorName) {
EventDispatcher.dispatchEvent(this, "ColorChanged", colorCode, colorName);
}
private int getSensorValue(String functionName) {
int level = readInputPercentage(functionName,
0, // assume layer = 0
sensorPortNumber,
SENSOR_TYPE,
mode);
// map values according to LEGO's convention
if (mode == SENSOR_MODE_COLOR) {
switch (level) {
case 0:
return 0;
case 12:
return 1;
case 25:
return 2;
case 37:
return 3;
case 50:
return 4;
case 62:
return 5;
case 75:
return 6;
case 87:
return 7;
default:
return 0;
}
} else {
return level;
}
}
private String toColorName(String functionName, int colorCode) {
if (mode != SENSOR_MODE_COLOR)
return "No Color";
switch (colorCode) {
case 0:
return "No Color";
case 1:
return "Black";
case 2:
return "Blue";
case 3:
return "Green";
case 4:
return "Yellow";
case 5:
return "Red";
case 6:
return "White";
case 7:
return "Brown";
default:
return "No Color";
}
}
/**
* Specifies the mode of the sensor.
*/
@DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_LEGO_EV3_COLOR_SENSOR_MODE,
defaultValue = DEFAULT_SENSOR_MODE_STRING)
@SimpleProperty
public void Mode(String modeName) {
String functionName = "Mode";
try {
setMode(modeName);
} catch(IllegalArgumentException e) {
form.dispatchErrorOccurredEvent(this, functionName, ErrorMessages.ERROR_EV3_ILLEGAL_ARGUMENT, functionName);
}
}
/**
* Returns the mode of the sensor.
*/
@SimpleProperty(description = "Get the current sensor mode.",
category = PropertyCategory.BEHAVIOR)
public String Mode() {
return modeString;
}
/**
* Enter the color detection mode.
*/
@SimpleFunction(description = "Enter the color detection mode.")
public void SetColorMode() {
String functionName = "SetColorMode";
try {
setMode(SENSOR_MODE_COLOR_STRING);
} catch(IllegalArgumentException e) {
form.dispatchErrorOccurredEvent(this, functionName, ErrorMessages.ERROR_EV3_ILLEGAL_ARGUMENT, functionName);
}
}
/**
* Make the sensor read the light level with reflected light.
*/
@SimpleFunction(description = "Make the sensor read the light level with reflected light.")
public void SetReflectedMode() {
String functionName = "SetReflectedMode";
try {
setMode(SENSOR_MODE_REFLECTED_STRING);
} catch(IllegalArgumentException e) {
form.dispatchErrorOccurredEvent(this, functionName, ErrorMessages.ERROR_EV3_ILLEGAL_ARGUMENT, functionName);
}
}
/**
* Make the sensor read the light level without reflected light.
*/
@SimpleFunction(description = "Make the sensor read the light level without reflected light.")
public void SetAmbientMode() {
String functionName = "SetAmbientMode";
try{
setMode(SENSOR_MODE_AMBIENT_STRING);
} catch(IllegalArgumentException e) {
form.dispatchErrorOccurredEvent(this, functionName, ErrorMessages.ERROR_EV3_ILLEGAL_ARGUMENT, functionName);
}
}
private void setMode(String newModeString) {
previousColor = -1;
previousLightLevel = -1;
if (SENSOR_MODE_REFLECTED_STRING.equals(newModeString))
mode = SENSOR_MODE_REFLECTED;
else if (SENSOR_MODE_AMBIENT_STRING.equals(newModeString))
mode = SENSOR_MODE_AMBIENT;
else if (SENSOR_MODE_COLOR_STRING.equals(newModeString))
mode = SENSOR_MODE_COLOR;
else
throw new IllegalArgumentException();
this.modeString = newModeString;
}
// interface Deleteable implementation
@Override
public void onDelete() {
eventHandler.removeCallbacks(sensorValueChecker);
super.onDelete();
}
}