public interface DistanceSensor extends Zeroable {
/**
* Gets the current distance, in inches, relative to the zero-point.
*
* @return the distance in inches
*/
public double getDistanceInInches();
/**
* Gets the current distance, in feet, relative to the zero-point.
*
* @return the distance in feet
*/
default public double getDistanceInFeet() {...}
/**
* Use the current distance value as the new zero-point,
* to which all subsequent measurements will be relative.
*
* @return this object to allow chaining of methods; never null
*/
@Override
default public DistanceSensor zero() {...}
/**
* Create a distance sensor for the given function that returns the distance.
*
* @param distanceSupplier the function that returns the distance; may not be null
* @return the angle sensor
*/
public static DistanceSensor create(DoubleSupplier distanceSupplier) {...}
/**
* Inverts the specified DistanceSensor so that negative distances
* become positive distances.
*
* @param sensor the DistanceSensor to invert
* @return the new DistanceSensor that reads the negated distance
* of the original sensor
*/
public static DistanceSensor invert(DistanceSensor sensor) {
}
Distance Sensors
A hardware distance sensor is any device that measures the distance of something relative to something else. Example distance sensors include digital or analog ultrasonic sensors and potentiometers.
This section describes Strongback’s interface that represent physical distance sensor devices.
DistanceSensor
The org.strongback.components.DistanceSensor
interface represents a simple distance sensor that returns the distance relative to some _zero point.
The getDistanceInInches()
method returns the relative distance in inches, meaning the value can be any positive or negative number based upon the zero-point. The getDistanceInFeet()
default method is implemented in terms of the getDistanceInInches()
method, making DistanceSensor
a functional interface.
The zero()
method is inherited from the Zeroable
interface, and it can be used to set the distance to which subsequent distances will be relative.
The create(DoubleSupplier)
static factory method will create a DistanceSensor
from any java.util.function.DoubleSupplier
. The DoubleSupplier
is a functional interface added in Java 8, and it has a single method that takes no parameters and returns a double
value. Therefore, create(DoubleSupplier)
can be used to create a DistanceSensor
from any object, lambda, or method reference that satisfies the same signature (no parameters and returns a double
). For example, the following fragment creates an DistanceSensor that always returns the value 100
:
DistanceSensor dist = DistanceSensor.create(()->100);
Or, given some imaginary class Foo
that has a method that takes no parameters and returns a double:
public class Foo {
...
public double bar() {...}
...
}
the following fragment shows how to create a DistanceSensor
that always uses the Foo.bar()
method to compute the distance, using a method reference to the bar()
method on the object foo
:
Foo foo = ...
DistanceSensor dist = DistanceSensor.create(foo::bar);
Finally, the AngleSensor
also defines the invert(AngleSensor)
method, which can be used to return a new AngleSensor
that always inverts the angle returned by another:
DistanceSensor actual = ...
DistanceSensor inverted = DistanceSensor.invert(actual);
This is far easier than always having to multiply the resulting distance by -1
!
Obtaining hardware angle sensors
Strongback provides a org.strongback.hardware.Hardware
class with static factory methods for common physical hardware angle sensing devices:
public class Hardware {
...
/**
* Factory method for distance sensors.
*/
public static final class DistanceSensors {
/**
* Create a digital ultrasonic distance sensor for an ultrasonic
* sensor that uses the specified channels.
*
* @param pingChannel the digital output channel to use for sending pings
* @param echoChannel the digital input channel to use for receiving echo responses
* @return a DistanceSensor linked to the specified channels
*/
public static DistanceSensor digitalUltrasonic(int pingChannel,
int echoChannel) {...}
/**
* Create an analog distance sensor for an analog input sensor using
* the specified channel.
*
* @param channel the channel the sensor is connected to
* @param voltsToInches the conversion from analog volts to inches
* @return a {@link DistanceSensor} linked to the specified channel
*/
public static DistanceSensor analogUltrasonic(int channel,
double voltsToInches) {...}
/**
* Create a new distance sensor from an analog potentiometer using
* the specified channel and scaling. Since no offset is provided,
* the resulting distance sensor may often be used with a limit switch
* to know precisely where a mechanism might be located in physical space,
* and the distance sensor can be zeroed at that position.
* (See potentiometer(int, double, double) when a switch is not used to
* help determine the location, and instead the zero point is
* pre-determined by the physical design of the mechanism.)
*
* The scale factor multiplies the 0-1 ratiometric value to return
* useful units. Generally, the most useful scale factor will be the
* angular or linear full scale of the potentiometer.
*
* For example, let's say you have an ideal single-turn linear
* potentiometer attached to a robot arm. This pot will turn 270 degrees
* over the 0V-5V range while the end of the arm travels 20 inches.
* Therefore, the `fullVoltageRangeToInches` scale factor is
* 20 inches / 5 V, or 4 inches/volt.
*
* @param channel The analog channel this potentiometer is plugged into.
* @param fullVoltageRangeToInches The scaling factor multiplied by
* the analog voltage value to obtain inches.
* @return the distance sensor that uses the potentiometer on the
* given channel; never null
*/
public static DistanceSensor potentiometer(int channel,
double fullVoltageRangeToInches) {...}
/**
* Create a new distance sensor from an analog potentiometer using
* the specified channel, scaling and offset. This method is often
* used when the offset can be hard-coded by first measuring the
* value of the potentiometer. On the other hand, if a limit switch
* is used to always determine the position of the mechanism upon
* startup, then see potentiometer(int, double).
*
* The scale factor multiplies the 0-1 ratiometric value to return
* useful units, and the offset is added after the scaling. Generally,
* the most useful scale factor will be the angular or linear full
* scale of the potentiometer.
*
* For example, let's say you have an ideal single-turn linear
* potentiometer attached to a robot arm. This pot will turn 270 degrees
* over the 0V-5V range while the end of the arm travels 20 inches.
* Therefore, the `fullVoltageRangeToInches` scale factor is
* 20 inches / 5 V, or 4 inches/volt.
*
* To prevent the potentiometer from breaking due to minor shifting
* in alignment of the mechanism, the potentiometer may be installed
* with the "zero-point" of the mechanism (e.g., arm in this case)
* a little ways into the potentiometer's range (for example 10 degrees).
* In this case, the `offset` value is measured from the physical
* mechanical design and can be specified to automatically remove
* the 10 degrees from the potentiometer output.
*
* @param channel The analog channel this potentiometer is plugged into.
* @param fullVoltageRangeToInches The scaling factor multiplied by
* the analog voltage value to obtain inches.
* @param offsetInInches The offset in inches that the distance sensor
* will subtract from the underlying value before
* returning the distance
* @return the distance sensor that uses the potentiometer on the
* given channel; never null
*/
public static DistanceSensor potentiometer(int channel,
double fullVoltageRangeToInches,
double offsetInInches) {...}
}
...
}
Obviously you should only use this class in code that will only run on the robot, so we recommend centralizing this logic inside one of your top-level robot classes:
public class SimpleRobot extends IterativeRobot {
private DistanceSensor dist;
@Override
public void robotInit() {
...
dist = Hardware.DistanceSensors.analogUltrasonic(3,4.0);
// pass 'dist' to the objects that need it ...
}
You can then pass the distance sensor reference around to other components that need them.
Tip
|
Testability tip
You might think that it’s easy for components to just get the Instead, it’s much better to use injection, which is just a fancy way of saying that when a component is created it should be handed all the objects it needs. So if we have a component that requires a sensor, then that component should require the sensor be given to it (usually through the constructor, if possible). The robot class can create this component and pass the sensor to it, while test cases can pass in a mock sensor. (We’ll talk about mocks in the next section.) The bottom line is that these other components should depend only upon injected |
Using distance sensors in tests
Your tests should never use the Hardware class to create distance sensors in your tests. Instead, Strongback provides a series of mock classes that can be used in place of the hardware implementations. These mocks extend the normal DistanceSensor
interface as expected, but they add setter methods that your test code can explicitly set the distance value returned by the sensor.
The MockDistanceSensor
class is defined as follows:
public class MockDistanceSensor extends MockZeroable implements DistanceSensor {
@Override
public MockDistanceSensor zero() {
super.zero();
return this;
}
@Override
public double getDistanceInInches() {
return super.getValue();
}
/**
* Set the distance in inches return by this object.
*
* @param distance the new distance in inches
* @return this instance to enable chaining methods; never null
* @see #setDistanceInFeet(double)
*/
public MockDistanceSensor setDistanceInInches(double distance) {...}
/**
* Set the distance in feet return by this object.
*
* @param distance the new distance in feet
* @return this instance to enable chaining methods; never null
* @see #setDistanceInInches(double)
*/
public MockDistanceSensor setDistanceInFeet(double distance) {
return setDistanceInInches(distance * 12.0);
}
where MockZeroable
manages the scalar value and zeroing functionality, and is defined as:
abstract class MockZeroable implements Zeroable {
private volatile double zero = 0;
private volatile double value;
protected double getValue() {
return value - zero;
}
protected void setValue( double value) {
this.value = value;
}
@Override
public MockZeroable zero() {
zero = value;
return this;
}
}
You can create mock objects using the org.strongback.mock.Mock
class' static factory methods. For example, here’s a fragment from a test that creates a mock compass:
MockDistanceSensor dist = Mock.distanceSensor();
dist.setDistanceInInches(21.1);
// pass compass to a component that needs and uses a DistanceSensor
// and verify the component correctly reads the distance