Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 43 additions & 33 deletions src/main/java/ev3dev/actuators/ev3/EV3Led.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,20 @@

import ev3dev.hardware.EV3DevDevice;
import ev3dev.hardware.EV3DevPlatform;
import ev3dev.utils.Sysfs;
import ev3dev.utils.DataChannelRewriter;
import lejos.hardware.LED;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.Closeable;
import java.io.IOException;

/**
* This class provides methods for interacting with the LEDs on the EV3Brick.
*
* <p><i>Only EV3Bricks are supported.</i>
*/
public class EV3Led extends EV3DevDevice implements LED {
public class EV3Led extends EV3DevDevice implements LED, Closeable {

/**
* Directions of the LED.
Expand All @@ -21,16 +24,26 @@ public enum Direction {
LEFT,
RIGHT
}

private static final Direction[] directionArray = {Direction.LEFT,Direction.RIGHT};

private static final Logger log = LoggerFactory.getLogger(EV3Led.class);

/**
* @deprecated Use EV3LedDirection.LEFT instead.
*/
@Deprecated
public static final int LEFT = 0;
/**
* @deprecated Use EV3Led.Direction.RIGHT instead.
*/
@Deprecated
public static final int RIGHT = 1;

private final Direction direction;

private final String LED_RED;
private final String LED_GREEN;
private final DataChannelRewriter redWriter;
private final DataChannelRewriter greenWriter;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I realized here that some attributes may not be read-only or write-only. For example, the duty_cycle_sp attribute is both written and read in BaseRegulatedMotor. This requires having two separate objects for the reads & writes (that might be OK though).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've only prototyped BaseRegulatedMotor so far. Are there any conditions where we'd want read and write in the same interaction? Or where they'd interfere with each other? We could build something like a random access file if needed.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIUC they will not interfere with each other, so having two objects, one for reading and the other for writing, should work fine. I just got surprised that we are going to have two separate objects for one "real" object/sysfs attribute.


/**
* Create an EV3LED object associated with the LED of the specified direction.
Expand All @@ -50,11 +63,11 @@ public EV3Led(final Direction direction) {
this.direction = direction;

if (direction == Direction.LEFT) {
LED_RED = ev3DevProperties.getProperty("ev3.led.left.red");
LED_GREEN = ev3DevProperties.getProperty("ev3.led.left.green");
redWriter = new DataChannelRewriter(ev3DevProperties.getProperty("ev3.led.left.red"));
greenWriter = new DataChannelRewriter(ev3DevProperties.getProperty("ev3.led.left.green"));
} else {
LED_RED = ev3DevProperties.getProperty("ev3.led.right.red");
LED_GREEN = ev3DevProperties.getProperty("ev3.led.right.green");
redWriter = new DataChannelRewriter(ev3DevProperties.getProperty("ev3.led.right.red"));
greenWriter = new DataChannelRewriter(ev3DevProperties.getProperty("ev3.led.right.green"));
}
}

Expand All @@ -65,22 +78,9 @@ public EV3Led(final Direction direction) {
* @throws RuntimeException if LED feature is not supported on the current platform.
* @deprecated Use {@link #EV3Led(Direction)} instead.
*/
@Deprecated
public EV3Led(final int button) {
checkPlatform();

if (button == LEFT) {
LED_RED = ev3DevProperties.getProperty("ev3.led.left.red");
LED_GREEN = ev3DevProperties.getProperty("ev3.led.left.green");
direction = Direction.LEFT;
} else if (button == RIGHT) {
LED_RED = ev3DevProperties.getProperty("ev3.led.right.red");
LED_GREEN = ev3DevProperties.getProperty("ev3.led.right.green");
direction = Direction.RIGHT;
} else {
log.error("You are not specifying any button.");
throw new IllegalArgumentException("You are not specifying any button.");
}

this(directionArray[button]);
}

/**
Expand All @@ -96,7 +96,6 @@ private void checkPlatform() {
}

//TODO Add Enums for patterns

/**
* Sets the pattern of light to be shown with this LED.
*
Expand All @@ -109,19 +108,22 @@ private void checkPlatform() {
*/
@Override
public void setPattern(final int pattern) {
//Off

final String off = Integer.toString(0);
final String on = Integer.toString(255);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we want to change this, it might be worth it to also have only a single writeString call below for each LED. Each if branch would only set the value to pass to writeString().

Copy link
Contributor Author

@dwalend dwalend Jan 6, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I kind of want to let the kids play with the integer settings. But I think the best they can do is red + green = some kind of glowing brown. An ambitious kid could have the colors fade instead of change suddenly. That level of control is not for this fix.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant this only about the code style - there should be no problem with adjusting the integer value later. It's just that the code below this line that calls writeString() looks repetitive and it might be better to join these calls into just two calls at the end of the if-ladder.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll give that a try tonight and see what it looks like.


if (pattern == 0) {
Sysfs.writeInteger(LED_RED, 0);
Sysfs.writeInteger(LED_GREEN, 0);
greenWriter.writeString(off);
redWriter.writeString(off);
} else if (pattern == 1) {
Sysfs.writeInteger(LED_RED, 0);
Sysfs.writeInteger(LED_GREEN, 255);
greenWriter.writeString(on);
redWriter.writeString(off);
} else if (pattern == 2) {
Sysfs.writeInteger(LED_RED, 255);
Sysfs.writeInteger(LED_GREEN, 0);
greenWriter.writeString(off);
redWriter.writeString(on);
} else if (pattern == 3) {
Sysfs.writeInteger(LED_RED, 255);
Sysfs.writeInteger(LED_GREEN, 255);
greenWriter.writeString(on);
redWriter.writeString(on);
} else if (pattern > 3) {
log.debug("This feature is not implemented");
}
Expand All @@ -135,4 +137,12 @@ public void setPattern(final int pattern) {
public Direction getDirection() {
return direction;
}

@Override
public void close() throws IOException {
greenWriter.close();
redWriter.close();
}


}
19 changes: 9 additions & 10 deletions src/main/java/ev3dev/sensors/Battery.java
Original file line number Diff line number Diff line change
Expand Up @@ -67,38 +67,37 @@ private Battery() {
currentRereader = new DataChannelRereader(batteryPath + "/" + batteryEv3 + "/" + current);
}

public int getVoltageMicroVolts() {
public int getVoltageMicroVolt() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This changes public API

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm, nevermind, this was likely accepted in the previous pull request.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I fixed the new method name to match the existing unit postfix.

return Integer.parseInt(voltageRereader.readString());
}

/**
* @return voltage of the battery in millivolts.
*/
@Override
public int getVoltageMilliVolt() {
return getVoltageMicroVolts() / 1000;
return getVoltageMicroVolt() / 1000;
}

/**
* Returns voltage of the battery in microvolts.
*
* @return voltage
* @return voltage of the battery in microvolts.
*/
public float getVoltage() {
return getVoltageMicroVolts() / 1000000f;
return getVoltageMicroVolt() / 1000000f;
}

//TODO Review output
//TODO Review units

/**
* Returns the current of the battery in amps.
*
* @return current
* @return current from the battery in amps, or Float.NaN if run on something other than EV3BRICK
*/
public float getBatteryCurrent() {
if (CURRENT_PLATFORM.equals(EV3DevPlatform.EV3BRICK)) {
return Float.parseFloat(currentRereader.readString());
Copy link
Contributor

@JakubVanek JakubVanek Jan 5, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is unrelated to DataChannelRereader/DataChannelRewriter and as such it should be addressed in a different PR, but this method likely contains a bug. I'm wondering if this applies universally: if you're using readFloat() for a sysfs attribute, you're likely doing something wrong. Kernel doesn't usually work with floats, it works with scaled integers.

The problem is that the value will be read in microamps instead of amps. As per https://github.com/torvalds/linux/blob/master/Documentation/power/power_supply_class.rst:

Quoting include/linux/power_supply.h:

All voltages, currents, charges, energies, time and temperatures in µV, µA, µAh, µWh, seconds and tenths of degree Celsius unless otherwise stated. It's driver's job to convert its raw values to units in which this class operates.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is that going to be a problem for sensor's GenericMode? It's filling arrays of floats.

The gyroscope only returns ints. I think the color sensor's API says it gives values between 0 and 1, but I haven't plugged ours in to verify that yet.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GenericMode is luckily aware of the scaling, so while it reads floats from sysfs, it multiplies them by a scaling factor to get results in proper SI units. It should be possible to put read-integer there without breaking anything.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's an undercurrent of intent in the code to replace GenericMode with more specific modes. Maybe that's some future work to take up.

Floating point is pretty easy to start with, but turns out to be remarkably hard to explain to kids why == is a bad choice. Rational numbers in contrast are easy. With really small units - like millimeter, millisecond, or microvolt - it is really easy to explain why == is a bad choice. The real twist with GenericMode was "If I only want the number of degrees why do I have to pass in a float array of one float?"

} else {
LOGGER.warn("This method is not available for {} & {}", EV3DevPlatform.PISTORMS, EV3DevPlatform.BRICKPI);
return -1f;
return Float.NaN;
}
}

Expand Down
75 changes: 75 additions & 0 deletions src/main/java/ev3dev/utils/DataChannelRewriter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package ev3dev.utils;

import java.io.Closeable;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;

/**
* Writer of streams that can rewrite the same channel for structured data of
* known length. The focus of this class is on performance.
*
* @author David Walend
*/
public class DataChannelRewriter implements Closeable {

private final Path path;
private final ByteBuffer byteBuffer;
private final FileChannel channel;

/**
* Create a DataChannelRewriter for path with a bufferLength byte buffer
*
* @param path path to the file to reread
* @param bufferLength length of the buffer to hold the structure
*/
public DataChannelRewriter(Path path, int bufferLength) {
this.path = path;
this.byteBuffer = ByteBuffer.allocate(bufferLength);
try {
this.channel = FileChannel.open(path, StandardOpenOption.WRITE);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

StandardOpenOption.CREATE might be needed by some unit tests in the future if it's not enabled by default already.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not enabled by default. I had to use a different codepath to create the files in the test. (It does give a very nice message if it can't create a file.)

I'd like to leave it out until we actually need it. Are there any cases in ev3dev where we'd want DataChannelRewriter to create a new file - to control something in ev3dev?

We'd also want to rename the class - it wouldn't be rewriting a new file.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not in ev3dev - it should be fine there. However, some sensor unit tests may be not be creating all attributes that the class uses. I agree that it might be better to just fix the tests instead of changing the code here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really need to get some background on how the existing tests work. It's not what I thought I would find - via a properties change to set up a file system that looks like the ev3dev's, and I'm having trouble seeing through the varied test styles into the setup code.

} catch (IOException e) {
throw new RuntimeException("While opening " + path,e);
}
}

/**
* Create a DataChannelRewriter for pathString with the default 32-byte buffer.
*
* @param pathString Path to the file to reread
*/
public DataChannelRewriter(String pathString) {
this(Paths.get(pathString),32);
}

/**
* @param string to write. A new line character
*/
public synchronized void writeString(String string) {
try {
byteBuffer.clear();
byteBuffer.put(string.getBytes(StandardCharsets.UTF_8));
byteBuffer.put(((byte)'\n'));
byteBuffer.flip();
channel.truncate(0);
channel.write(byteBuffer,0);
channel.force(false);
} catch (IOException e) {
throw new RuntimeException("Problem writing path: " + path, e);
}
}

public Path getPath() {
return path;
}

@Override
public synchronized void close() throws IOException {
channel.close();
}
}

14 changes: 7 additions & 7 deletions src/test/java/ev3dev/actuators/ev3/EV3LedTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public void constructorLeftTest() throws Exception {

final FakeBattery fakeBattery = new FakeBattery(EV3DevPlatform.EV3BRICK);

LED led = new EV3Led(EV3Led.LEFT);
@SuppressWarnings("deprecation") LED led = new EV3Led(EV3Led.LEFT);
led = new EV3Led(EV3Led.Direction.LEFT);
}

Expand All @@ -44,7 +44,7 @@ public void constructorRightTest() throws Exception {

final FakeBattery fakeBattery = new FakeBattery(EV3DevPlatform.EV3BRICK);

LED led = new EV3Led(EV3Led.RIGHT);
@SuppressWarnings("deprecation") LED led = new EV3Led(EV3Led.RIGHT);
led = new EV3Led(EV3Led.Direction.RIGHT);
}

Expand All @@ -56,7 +56,7 @@ public void usingLedOnEV3BrickPlatformTest() throws Exception {

final FakeBattery fakeBattery = new FakeBattery(EV3DevPlatform.BRICKPI);

LED led = new EV3Led(EV3Led.LEFT);
LED led = new EV3Led(EV3Led.Direction.LEFT);
}

@Test
Expand All @@ -66,7 +66,7 @@ public void badButtonTest() throws Exception {

final FakeBattery fakeBattery = new FakeBattery(EV3DevPlatform.EV3BRICK);

LED led = new EV3Led(2);
@SuppressWarnings("deprecation") LED led = new EV3Led(4);
}

@Test
Expand All @@ -85,7 +85,7 @@ public void leftLedPatternsTest() throws Exception {
final FakeBattery fakeBattery = new FakeBattery(EV3DevPlatform.EV3BRICK);
final FakeLed fakeLed = new FakeLed(EV3DevPlatform.EV3BRICK);

LED led = new EV3Led(EV3Led.LEFT);
LED led = new EV3Led(EV3Led.Direction.LEFT);
led.setPattern(1);
led.setPattern(2);
led.setPattern(3);
Expand All @@ -104,7 +104,7 @@ public void rightLedPatternsTest() throws Exception {
final FakeBattery fakeBattery = new FakeBattery(EV3DevPlatform.EV3BRICK);
final FakeLed fakeLed = new FakeLed(EV3DevPlatform.EV3BRICK);

LED led = new EV3Led(EV3Led.RIGHT);
@SuppressWarnings("deprecation") LED led = new EV3Led(EV3Led.RIGHT);
led.setPattern(1);
led.setPattern(2);
led.setPattern(3);
Expand All @@ -123,7 +123,7 @@ public void getDirectionTest() throws Exception {
final FakeBattery fakeBattery = new FakeBattery(EV3DevPlatform.EV3BRICK);
final FakeLed fakeLed = new FakeLed(EV3DevPlatform.EV3BRICK);

EV3Led led = new EV3Led(EV3Led.RIGHT);
@SuppressWarnings("deprecation") EV3Led led = new EV3Led(EV3Led.RIGHT);
Assert.assertEquals(EV3Led.Direction.RIGHT, led.getDirection());

led = new EV3Led(EV3Led.Direction.RIGHT);
Expand Down
Loading