Solenoid

A hardware solenoid is any actuation device that can be extended or retracted. Examples include pneumatic cylinders and electrical solenoids.

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

Solenoid

The org.strongback.components.Solenoid interface represents a simple actuator device that can extend or retract. Many solenoids will be nearly instantaneous, although others may be extending or retracting for more than one control cycle.

@ThreadSafe
public interface Solenoid extends Requirable {

    /**
     * The direction of the solenoid.
     */
    static enum Direction {
        /** The solenoid is extending. */
        EXTENDING,
        /** The solenoid is retracting. */
        RETRACTING,
        /** The solenoid is stopped. */
        STOPPED;
    }

    /**
     * Get the current direction of this solenoid.
     * @return the current direction; never null
     */
    Direction getDirection();

    /**
     * Extends this solenoid.
     * @return this object to allow chaining of methods; never null
     */
    Solenoid extend();

    /**
     * Retracts this solenoid.
     * @return this object to allow chaining of methods; never null
     */
    Solenoid retract();

    /**
     * Determine if this solenoid is or was extending.
     *
     * @return true if this solenoid is in the process of extending but not yet
     *     fully extended, or false otherwise.
     */
    default boolean isExtending() {...}

    /**
     * Determine if this solenoid is or was retracting.
     *
     * @return true if this solenoid is in the process of retracting but not yet
     *     fully extended, or false otherwise.
     */
    default boolean isRetracting() {...}

    /**
     * Determine if this solenoid is stopped.
     *
     * @return {@code true} if this solenoid is not retracting or extending, or
     *     false otherwise
     */
    default boolean isStopped() {...}
}

Most of the methods should be easy to understand and use. The getDirection() method returns an enumeration that represents last known direction commanded to the solenoid.

SolenoidWithPosition

The org.strongback.components.SolenoidWithPosition interface is a type of Solenoid that is able to monitor its own position. An example might be a pneumatic cylinder with magnetic reed switches.

@ThreadSafe
public interface SolenoidWithPosition extends Solenoid {

    /**
     * The possible positions for a limited motor.
     */
    public enum Position {
        /** The motor is fully extended. **/
        EXTENDED,
        /** The motor is fully retracted. **/
        RETRACTED,
        /** The motor is not fully retracted or fully extended, but the exact position is unknown. **/
        UNKNOWN
    }

    /**
     * Get the current position of this solenoid.
     *
     * @return the current position; never null
     */
    public Position getPosition();

    /**
     * Determines if this Solenoid is extended.
     *
     * @return true if this solenoid is fully extended, or false otherwise
     */
    default public boolean isExtended() {...}

    /**
     * Determines if this Solenoid is retracted.
     *
     * @return true if this solenoid is fully retracted, or false otherwise
     */
    default public boolean isRetracted() {...}

    /**
     * Create a solenoid that uses the supplied function to determine the position.
     * @param solenoid the solenoid; may not be null
     * @param positionSupplier the function that returns the position; may not be null
     * @return the {@link SolenoidWithPosition} instance; never null
     */
    public static SolenoidWithPosition create(Solenoid solenoid,
                                              Supplier<Position> positionSupplier ) {...}

    /**
     * Create a solenoid that uses the two given switches to determine position.
     * @param solenoid the solenoid; may not be null
     * @param retractSwitch the switch that determines if the solenoid is retracted; may not be null
     * @param extendSwitch the switch that determines if the solenoid is extended; may not be null
     * @return the {@link SolenoidWithPosition} instance; never null
     */
    public static SolenoidWithPosition create(Solenoid solenoid,
                                              Switch retractSwitch,
                                              Switch extendSwitch ) {...}
}

Again, the methods are pretty straightforward.

The create(…​) static factory methods make it easy to create a SolenoidWithPosition from a Solenoid and one or two Switch objects.

Relay

The org.strongback.components.Relay interface that represents a device that can be turned on and off. A common example of a relay is the VEX Robotics Spike style relay used in FRC with pneumatic cylinders.

Note that a relay has one of 5 possible states:

  • ON - the relay is in the "on" position;

  • OFF - the relay is in the "off" position;

  • SWITCHING_ON - the relay was in the "off" position but has been changed and is not yet in the "on" position;

  • SWITCHING_OFF - the relay was in the "on" position but has been changed and is not yet in the "off" position; and

  • UNKNOWN - the relay position is not known

Not all Relay implementations use all relay states. Very simple relays that have no delay will simply only use State.ON and State.OFF, while relays that have some delay might also use State.SWITCHING_ON and State.SWITCHING_OFF. Those relay implementations that may not know their position upon startup may start out in the UNKNOWN state.

public interface Relay {

    static enum State {
        /**
         * The relay is presently switching into the "ON" state but has
         * not yet completed the change.
         */
        SWITCHING_ON,
        /**
         * The relay is presently in the "on" position.
         */
        ON,
        /**
         * The relay is presently switching into the "OFF" state but has
         * not yet completed the change.
         */
        SWITCHING_OFF,
        /**
         * The relay is presently in the "off" position.
         */
        OFF,
        /**
         * The actual state of the relay is not known.
         */
        UNKOWN
    }

    /**
     * Get the current state of this relay.
     * @return the current state; never null
     */
    State state();

    /**
     * Turn on this relay.
     *
     * @return this object to allow chaining of methods; never null
     */
    Relay on();

    /**
     * Turn off this relay.
     *
     * @return this object to allow chaining of methods; never null
     */
    Relay off();

    /**
     * Check whether this relay is known to be on. This is equivalent to calling
     * `state() == State.ON`.
     *
     * @return true if this relay is on; or false otherwise
     */
    default boolean isOn() {...}

    /**
     * Check whether this relay is known to be off. This is equivalent to calling
     * `state() == State.OFF`.
     *
     * @return true if this relay is off; or false otherwise
     */
    default boolean isOff() {...}

    /**
     * Check if this relay is switching on. This is equivalent to calling
     * `state() == State.SWITCHING_ON`.
     *
     * @return true if this relay is in the process of switching from off to on;
     * or false otherwise
     */
    default boolean isSwitchingOn() {...}

    /**
     * Check if this relay is switching off. This is equivalent to calling
     * `state() == State.SWITCHING_OFF`.
     *
     * @return true if this relay is in the process of switching from on to off;
     * or false otherwise
     */
    default boolean isSwitchingOff() {...}

    /**
     * Obtain a relay that remains in one fixed state, regardless of any
     * calls to on() or off().
     *
     * @param state the fixed state; may not be null
     * @return the constant relay; never null
     */
    static Relay fixed(State state) {...}
}

Conceptually a relay is similar to a Solenoid, although semantics are slightly different.

Obtaining hardware solenoids and relays

Strongback provides a org.strongback.hardware.Hardware class with static factory methods for physical hardware solenoids and relays:

public class Hardware {
    ...

    /**
     * Factory methods for solenoids.
     */
    public static final class Solenoids {
        /**
         * Create a double-acting solenoid that uses the specified channels on the default module.
         *
         * @param extendChannel the channel that extends the solenoid
         * @param retractChannel the channel that retracts the solenoid
         * @param initialDirection the initial direction for the solenoid; may not be null
         * @return a solenoid on the specified channels; never null
         */
        public static Solenoid doubleSolenoid(int extendChannel,
                                              int retractChannel,
                                              Solenoid.Direction initialDirection) {...}

        /**
         * Create a double-acting solenoid that uses the specified channels on the given module.
         *
         * @param module the module for the channels
         * @param extendChannel the channel that extends the solenoid
         * @param retractChannel the channel that retracts the solenoid
         * @param initialDirection the initial direction for the solenoid; may not be null
         * @return a solenoid on the specified channels; never null
         */
        public static Solenoid doubleSolenoid(int module,
                                              int extendChannel,
                                              int retractChannel,
                                              Solenoid.Direction initialDirection) {...}

        /**
         * Create a relay on the specified channel.
         *
         * @param channel the channel the relay is connected to
         * @return a relay on the specified channel
         */
        public static Relay relay(int channel) {...}
    }
    ...
}

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 Solenoid cylinder;

    @Override
    public void robotInit() {
        ...
        // pressure solenoid in channels 3 and 4
        Solenoid actualCylinder = Hardare.Solenoids.doubleSolenoid(3,4);
        // reed switches in channels 5 and 6
        Switch retracted = Hardware.Switches.normallyOpen(5);
        Switch extended = Hardware.Switches.normallyOpen(5);

        cylinder = SolenoidWithPosition.create(actualCylinder,retracted,extended);

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

You can then pass the switches around to other components that need them.

Tip
Testability tip

You might think that it’s easy for components to just get the cylinder field on the SimpleRobot, perhaps via getter methods. However, that embeds knowledge about how to find the sensors inside the component that uses it. This makes it very difficult to test the component off-robot, because it will always try to get a hardware-based sensor 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 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 Solenoid or SolenoidWithPosition objects, and should never know how to find one. This helps keep these other components testable.

Using switches in tests

Your tests should never use the Hardware class to create solenoids in your tests. Instead, Strongback provides a series of mock classes that can be used in place of the hardware implementations. The mock solenoid extend the Solenoid interface as expected, and normally this will be all your tests need. However, the MockSolenoid class does add a stop() method that stops the solenoid in whatever position it is (should that be necessary).

The MockSolenoid class is defined as follows:

public class MockSolenoid implements Solenoid {

    private volatile Direction direction = Direction.STOPPED;
    private final boolean completeImmediately;

    protected MockSolenoid( boolean completeImmediately ) {
        this.completeImmediately = completeImmediately;
    }

    @Override
    public MockSolenoid extend() {
        direction = Direction.EXTENDING;
        if ( completeImmediately ) direction = Direction.STOPPED;
        return this;
    }

    @Override
    public MockSolenoid retract() {
        direction = Direction.EXTENDING;
        if ( completeImmediately ) direction = Direction.STOPPED;
        return this;
    }

    @Override
    public Direction getDirection() {
        return direction;
    }

    /**
     * Stop any movement of this solenoid.
     * @return this object so that methods can be chained together; never null
     */
    public MockSolenoid stop() {
        direction = Direction.STOPPED;
        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 few instantaneous solenoids:

Solenoid s1 = Mock.instantaneousSolenoid();
s1.extend();
assert s1.isExtending();

There is no mock SolenoidWithPosition; instead, just create a mock solenoid with one or two mock switches:

Solenoid s1 = Mock.instantaneousSolenoid();
MockSwitch extended = Mock.notTriggered();
MockSwitch retracted = Mock.triggered();
SolenoidWithPosition cylinder = SolenoidWithPosition.create(s1,retracted,extended);
cylinder.extend();
assertThat(extended.isTriggered()).isTrue();

The MockRelay class is defined as follows:

public class MockRelay implements Relay {

    private State state;

    @Override
    public MockRelay off() {
        state = State.OFF;
        return this;
    }

    @Override
    public MockRelay on() {
        state = State.ON;
        return this;
    }

    /**
     * Set the state of this relay to State.SWITCHING_OFF.
     * @return this instance to enable chaining methods; never null
     */
    public MockRelay switchingOff() {
        state = State.SWITCHING_OFF;
        return this;
    }

    /**
     * Set the state of this relay to State.SWITCHING_ON.
     * @return this instance to enable chaining methods; never null
     */
    public MockRelay switchingOn() {
        state = State.SWITCHING_ON;
        return this;
    }

    @Override
    public State state() {
        return state;
    }
}

Using the mock relay is easy, although you’ll likely do this in tests of components that use the relay.

MockRelay relay = Mock.relay().on();

results matching ""

    No results matching ""