Accelerometers

A hardware accelerometer measures the acceleration along one, two, or three axes. The ADXL345 is an accelerometer that was included in the 2014 Kit of Parts, and in 2015 the RoboRIO had a built-in accelerometer.

This section describes Strongback’s multiple interfaces that represent physical accelerometer devices.

Accelerometer

The org.strongback.components.Accelerometer interface represents a single-axis accelerometer capable of sensing acceleration in one direction:

public interface Accelerometer {

    /**
     * Get the acceleration in g's.
     * @return the acceleration
     */
    public double getAcceleration();
}

As described above, an Accelerometer instance has a single method that returns the ratio of instantaneous acceleration to that of standard gravity at sea level. This scalar ratio is commonly know as g.

This interface measures acceleration in a single direction based upon the orientation of the particular sensor.

TwoAxisAccelerometer

Unlike the single-axis Accelerometer interface, the org.strongback.components.TwoAccessAccelerometer interface provides accelerations along two axes:

public interface TwoAxisAccelerometer {

    /**
     * Get the X-axis accelerometer.
     *
     * @return the accelerometer for the X-axis; never null
     */
    public Accelerometer getXDirection();

    /**
     * Get the Y-axis accelerometer.
     *
     * @return the accelerometer for the Y-axis; never null
     */
    public Accelerometer getYDirection();

    /**
     * Get the accelerometer for the axis with the given index,
     * where 0 is the X-axis and 1 is the Y-axis.
     *
     * @param axis the axis direction; must be either 0 or 1
     * @return the accelerometer; never null
     * @throws IllegalArgumentException if {@code axis} is invalid
     */
    default public Accelerometer getDirection(int axis) {...}

    /**
     * Get the instantaneous multidimensional acceleration values for all 2 axes.
     *
     * @return the acceleration values for 2 axes; never null
     */
    default public TwoAxisAcceleration getAcceleration() {...}

    /**
     * Create a 2-axis accelerometer from the two individual accelerometers.
     *
     * @param xAxis the accelerometer for the X-axis; may not be null
     * @param yAxis the accelerometer for the Y-axis; may not be null
     * @return the 2-axis accelerometer; never null
     */
    public static TwoAxisAccelerometer create(Accelerometer xAxis,
                                              Accelerometer yAxis) { ...}

}

As you can see, this interface is composed of an Accelerometer in the x- and y-directions. Each direction’s Accelerometer is accessed via a dedicated method or via a getDirection(int) method that takes an integer as a parameter to specify the desired direction.

The acceleration in both directions can be obtained atomically using the getAcceleration() method, which returns an immutable TwoAxisAcceleration object with scalar acceleration values (in g’s) for each direction, defined as:

public class TwoAxisAcceleration {

    /**
     * Get the acceleration in the X-direction.
     * @return the g's along the x-axis
     */
    public double getX() { ... }

    /**
     * Get the acceleration in the Y-direction.
     * @return the g's along the y-axis
     */
    public double getY() { ... }
}

Finally, the TwoAxisAccelerometer interface defines a static method called create(…​) that can be used to create a TwoAxisAccelerometer object from two individual Accelerometer objects:

Accelerometer axisX = ...
Accelerometer axisY = ...
TwoAxisAccelerometer accel = TwoAxisAccelerometer.create(axisX,axisY);

Or, because Accelerometer is a functional interface, we can actually use this method to create a TwoAxisAccelerometer object from two method references to methods that take no parameters and return a double. For example, given this imaginary class:

public class MyHardwareAccelerometer {
    public double getX() {...}
    public double getY() {...}
}

then we can create a TwoAxisAccelerometer using method references:

MyHardwareAccelerometer hw = ......
TwoAxisAccelerometer accel = TwoAxisAccelerometer.create(hw::getX,hw::getY);

ThreeAxisAccelerometer

The org.strongback.components.ThreeAxisAccelerometer interface provides accelerations along three axes. Note that it extends the TwoAxisAccelerometer and simply adds a third direction:

public interface ThreeAxisAccelerometer extends TwoAxisAccelerometer {

    /**
     * Get the Y-axis accelerometer.
     *
     * @return the accelerometer for the Y-axis; never null
     */
    public Accelerometer getZDirection();

    /**
     * Get the accelerometer for the axis with the given index, where 0 is the X-axis,
     1 is the Y-axis, and 2 is the Z-axis.
     *
     * @param axis the axis direction; must be either 0 or 1
     * @return the accelerometer; never null
     * @throws IllegalArgumentException if {@code axis} is invalid
     */
    default public Accelerometer getDirection(int axis) {...}

    /**
     * Get the instantaneous multidimensional acceleration values for all 3 axes.
     *
     * @return the acceleration values for 3 axes; never null
     */
    default public ThreeAxisAcceleration getAcceleration() {...}

    /**
     * Create a 3-axis accelerometer from the three individual accelerometers.
     *
     * @param xAxis the accelerometer for the X-axis; may not be null
     * @param yAxis the accelerometer for the Y-axis; may not be null
     * @param zAxis the accelerometer for the Z-axis; may not be null
     * @return the 3-axis accelerometer; never null
     */
    public static ThreeAxisAccelerometer create(Accelerometer xAxis,
                                                Accelerometer yAxis,
                                                Accelerometer zAxis) { ...}

    /**
     * Create a 3-axis accelerometer from a 2-axis accelerometer and a separate accelerometer for the Z-axis.
     * @param xAndY the 2-axis accelerometer for the X- and Y-axes; may not be null
     * @param zAxis the accelerometer for the Z-axis; may not be null
     * @return the 3-axis accelerometer; never null
     */
    public static ThreeAxisAccelerometer create(TwoAxisAccelerometer xAndY,
                                                Accelerometer zAxis) {
}

As you can see, this interface is composed of an Accelerometer in the x-, y-, and z-directions. Each direction’s Accelerometer is accessed via a dedicated method or via a getDirection(int) method that takes an integer as a parameter to specify the desired direction.

The acceleration in all three directions can be obtained atomically using the getAcceleration() method, which returns an immutable ThreeAxisAcceleration object with scalar acceleration values (in g’s) for each direction, defined as:

public class ThreeAxisAcceleration extends TwoAxisAcceleration {

    /**
     * Get the acceleration in the Z-direction.
     * @return the g's along the z-axis
     */
    public double getZ() { ... }
}

Finally, the ThreeAxisAccelerometer interface defines two static method called create(…​) that can be used to create a ThreeAxisAccelerometer object. The first creates one from three individual Accelerometer objects:

Accelerometer axisX = ...
Accelerometer axisY = ...
Accelerometer axisZ = ...
ThreeAxisAccelerometer accel = ThreeAxisAccelerometer.create(axisX,axisY,axisZ);

Because Accelerometer is a functional interface, we can actually use this method to create a ThreeAxisAccelerometer object from three method references to methods that take no parameters and return a double. For example, given this imaginary class:

public class MyHardwareAccelerometer {
    public double getX() {...}
    public double getY() {...}
    public double getZ() {...}
}

then we can create a ThreeAxisAccelerometer using method references:

MyHardwareAccelerometer hw = ......
ThreeAxisAccelerometer accel = ThreeAxisAccelerometer.create(hw::getX,hw::getY,hw::getZ);

We can also use a variant of the create(…​) to create a ThreeAxisAccelerometer object from a TwoAxisAccelerometer and a separate z-axis Accelerometer:

TwoAxisAccelerometer xy = ...
Accelerometer z = ...
ThreeAxisAccelerometer accel = ThreeAxisAccelerometer.create(xy,z);

or using method references or lambdas.

Obtaining hardware accelerometers

Strongback provides a org.strongback.hardware.Hardware class with static factory methods for common physical hardware devices:

public class Hardware {

    /**
     * Factory method for accelerometers.
     */
    public static final class Accelerometers {
        /**
         * Create a new ThreeAxisAccelerometer for the ADXL345
         * with the desired range using the specified I2C port.
         *
         * @param port the I2C port used by the accelerometer
         * @param range the desired range of the accelerometer
         * @return the accelerometer; never null
         */
        public static ThreeAxisAccelerometer accelerometer(I2C.Port port,
                                                           Range range) { ...}

        /**
         * Create a new ThreeAxisAccelerometer for the ADXL345
         * with the desired range using the specified SPI port.
         *
         * @param port the SPI port used by the accelerometer
         * @param range the desired range of the accelerometer
         * @return the accelerometer; never null
         */
        public static ThreeAxisAccelerometer accelerometer(SPI.Port port,
                                                           Range range) { ... }

        /**
         * Create a new ThreeAxisAccelerometer using the RoboRIO's
         * built-in accelerometer.
         *
         * @return the accelerometer; never null
         */
        public static ThreeAxisAccelerometer builtIn() { ... }

        /**
         * Create a new single-axis Accelerometer using the analog
         * accelerometer on the specified channel, with the given
         * sensitivity and zero value.
         *
         * @param channel the channel for the analog accelerometer
         * @param sensitivity the desired sensitivity in Volts per g
         *        (depends on actual hardware, such as 18mV/g or
         *        '0.018' for ADXL193)
         * @param zeroValueInVolts the voltage that represents no
         *        acceleration (should be determine experimentally)
         * @return the accelerometer; never null
         */
        public static Accelerometer analogAccelerometer(int channel,
                                                        double sensitivity,
                                                        double zeroValueInVolts) { ... }

        /**
         * Create a new single-axis Accelerometer using two analog
         * accelerometer on the specified channel, each with the given
         * sensitivity and zero value.
         *
         * @param xAxisChannel the channel for the X-axis analog accelerometer
         * @param yAxisChannel the channel for the Y-axis analog accelerometer
         * @param sensitivity the desired sensitivity in Volts per G
         *        (depends on actual hardware, such as 18mV/g or
         *        '0.018' for ADXL193)
         * @param zeroValueInVolts the voltage that represents no
         *        acceleration (should be determine experimentally)
         * @return the accelerometer; never null
         */
        public static TwoAxisAccelerometer analogAccelerometer(int xAxisChannel,
                                                               int yAxisChannel,
                                                               double sensitivity,
                                                               double zeroValueInVolts) { ... }
    }

}

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 TwoAxisAccelerometer accel;

    @Override
    public void robotInit() {
        ...
        accel = Hardware.Accelerometers.builtin();

        // pass 'accel' to the objects that need it ...
    }

You can then pass these objects around to other components that need the accelerometers.

Tip
Testability tip

You might think that it’s easy for components to just get the accel field on the SimpleRobot. However, that embeds knowledge about how to find the accelerometer inside the component. This makes it very difficult to test the component off-robot, because it will always try to get an accelerometer from the SimpleRobot object.

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 an accelerometer, then that component should require the accelerometer is given to it (usually through the constructor, if possible). So, the robot class can create this component and pass the accelerometer to it, while test cases can pass in a mock accelerometer. (We’ll talk about mocks in the next section.)

The bottom line is that these other components should depend only upon an injected Accelerometer, TwoAxisAccelerometer, or ThreeAxisAccelerometer object, and should never know how to find one. This helps keep this component testable.

Using accelerometers in tests

Your tests should never use the Hardware class to create accelerometers 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 interfaces as expected, but they add setter methods that your test code can explicitly set the values on these artificial accelerometer objects.

For example, the MockAccelerometer class is defined as follows:

public class MockAccelerometer implements Accelerometer {

    private volatile double accel;

    @Override
    public double getAcceleration() {
        return accel;
    }

    /**
     * Set the acceleration value {@link #getAcceleration() returned} by this object.
     * @param accel the acceleration value
     * @return this instance to enable chaining methods; never null
     */
    public MockAccelerometer setAcceleration(double accel) {
        this.accel = accel;
        return this;
    }
}

It implements Accelerometer, but rather than using real hardware it simply has a accel field in which it holds the current acceleration. Your tests can change the value of the acceleration at any time using the setAcceleration(double) method.

You can create an instance using the no-arg constructor, but we prefer you use the org.strongback.mock.Mock class' static factory methods. For example, here’s a fragment from a test that creates a mock accelerometer:

MockAccelerometer accel = Mock.accelerometer();
accel.setAcceleration(1.1);
// pass accel to a component that needs and uses an Accelerometer
// and verify the component reads the acceleration correctly

Strongback provides MockTwoAxisAccelerometer and MockThreeAxisAccelerometer classes, too, and likewise these can be created via the org.strongback.mock.Mock class' static factory methods. Here’s a fragment from a test that creates and uses a mock 2-axis accelerometer:

MockTwoAxisAccelerometer accel2D = Mock.accelerometer2Axis();
accel2D.getXDirection().setAcceleration(1.1);
accel2D.getYDirection().setAcceleration(1.3);
// pass accel2D to a component that needs and uses a TwoAxisAccelerometer
// and verify the component reads the accelerations correctly

And finally here’s a fragment from a test that creates and uses a mock 3-axis accelerometer:

MockThreeAxisAccelerometer accel3D = Mock.accelerometer3Axis();
accel3D.getXDirection().setAcceleration(1.1);
accel3D.getYDirection().setAcceleration(1.3);
accel3D.getZDirection().setAcceleration(0.95);
// pass accel3D to a component that needs and uses a ThreeAxisAccelerometer
// and verify the component reads the accelerations correctly

results matching ""

    No results matching ""