Switches

A hardware switch is any device that have an active state when triggered and an inactive state when not triggered. Conceptually switches are relatively abstract, but can represent multiple kinds of hardware devices, including Reed switches, limit switches, proximity sensors, buttons, and magnetic switches. Fuses are a specialization of a switch that can be programmatically reset. Switches can also be used to represent abstract components that are on or off.

This section describes Strongback’s multiple interfaces that represent physical switches and fuses.

Switch

The org.strongback.components.Switch interface represents a simple device that exists in one of two states: triggered or not triggered. It has one abstract method, and therefore is a functional interface in Java 8.

public interface Switch {
    /**
     * Checks if this switch is triggered.
     *
     * @return true if this switch was triggered, or false otherwise
     */
    public boolean isTriggered();

    /**
     * Create a switch that is always triggered.
     * @return the always-triggered switch; never null
     */
    public static Switch alwaysTriggered() {...}

    /**
     * Create a switch that is never triggered.
     * @return the never-triggered switch; never null
     */
    public static Switch neverTriggered() {...}
}

The isTriggered() method returns true if triggered or false otherwise.

Switches are so simple that they don’t even need a static factory method, since any lambda or method reference to a method that returns a boolean can be used as Switch.

Switch swtch = ()->true;

Or, given some imaginary class Foo that has a method that takes no parameters and returns a boolean:

public class Foo {
    ...
    public boolean bar() {...}
    ...
}

and a method that takes a switch:

public interface DataRecorder {
    ...
    public DataRecorder register(String name, Switch swtch);
    ...
}

the following fragment shows how to use the Foo instance as a switch:

DataRecorder recorder = ...
Foo foo = ...
recorder.register("button",foo::bar);

Fuse

The org.strongback.components.Fuse interface is a specialization of a Switch that allows the triggered state to be (re)set:

public interface Fuse extends Switch {
    /**
     * Trigger the fuse. Once this method is called, the `isTriggered()`
     * method switches but then will never change until it is `reset()`.
     *
     * @return this object to allow for chaining methods together; never null
     */
    public Fuse trigger();

    /**
     * Reset the fuse so it is no longer triggered.
     *
     * @return this object to allow for chaining methods together; never null
     */
    public Fuse reset();

    /**
     * Create a simple fuse that is manually triggered and manually reset().
     *
     * @return the fuse; never null
     */
    public static Fuse create() {...}

    /**
     * Create a fuse that can be manually reset but that will automatically
     * reset after the given delay.
     *
     * @param delay the time after the fuse is triggered that it should
     *        automatically reset; must be positive
     * @param unit the time units for the delay; may not be null
     * @param clock the clock that the fuse should use; if null, the system
     *        clock will be used
     * @return the auto-resetting fuse; never null
     */
    public static Fuse autoResetting(long delay, TimeUnit unit, Clock clock) {...}
}

The trigger() method changes the fuse’s state to be triggered. Only after reset() is called will the state change to not-triggered.

Other than physical fuses, you can use the static factory method create() to create fuses that operate entirely manually:

Fuse fuse = Fuse.create();
assert !fuse.isTriggered();

// manually trigger the fuse
fuse.trigger();
assert fuse.isTriggered();

// manually reset the fuse
fuse.reset();
assert !fuse.isTriggered();

How might you use a manual fuse? Really, you can use it anywhere you need to maintain or represent a boolean state. For example, your robot might have two modes that can be changed based upon commands or some sensor state, and you can represent this easily with a manual fuse that both the command and sensor can manually alter.

Strongback also has an automatically resetting fuse. You can also use the static factory method autoResetting(…​) to create a fuse that can be manually triggered and reset, but that will automatically reset after a specified delay following it being manually triggered. Here’s a non-realistic example:

Clock clock = Clock.system();
Fuse fuse = Fuse.autoResetting(5,TimeUnit.SECONDS,clock);
assert !fuse.isTriggered();

// manually trigger the fuse
fuse.trigger();
assert fuse.isTriggered();

Thread.sleep(5010);
assert !fuse.isTriggered();

How might you use an automatic fuse? One example might be to use an automatic fuse to model a physical mechanism that after being used requires 4 seconds to "recharge" before it can be used again:

Fuse mechanism = Fuse.autoResetting(4,TimeUnit.SECONDS,clock);
...
if ( mechanism.isTriggered() ) {
   // Still not ready ...
   ...
}

Obtaining hardware switches and fuses

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

public class Hardware {
    ...

    /**
     * Factory method for different kinds of switches.
     */
    public static final class Switches {

        /**
         * 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) {...}

        /**
         * Create a generic normally closed digital switch sensor on the
         * specified digital channel.
         *
         * @param channel the channel the switch is connected to
         * @return a switch on the specified channel
         */
        public static Switch normallyClosed(int channel) {...}

        /**
         * Create a generic normally open digital switch sensor on the
         * specified digital channel.
         *
         * @param channel the channel the switch is connected to
         * @return a switch on the specified channel
         */
        public static Switch normallyOpen(int channel) {...}

        /**
         * Option for analog switches.
         *
         * @see #analog(int, double, double, AnalogOption, TriggerMode)
         */
        public static enum AnalogOption {
            /**
             * The filtering option of the analog trigger uses a 3-point
             * average reject filter. This filter uses a circular
             * buffer of the last three data points and selects the
             * outlier point nearest the median as the output. The primary
             * use of this filter is to reject data points which errantly
             * (due to averaging or sampling) appear within the
             * window when detecting transitions using the Rising Edge
             * and Falling Edge functionality of the analog trigger
             */
            FILTERED,
            /**
             * The analog output is averaged and over sampled.
             */
            AVERAGED,
            /**
              * No filtering or averaging is to be used.
              */
            NONE;
        }

        /**
         * Trigger mode for analog switches.
         *
         * @see #analog(int, double, double, AnalogOption, TriggerMode)
         */
        public static enum TriggerMode {
            /**
             * The switch is triggered only when the analog value
             * is inside the range, and not triggered if it is
             * outside (above or below)
             */
            IN_WINDOW,
            /**
             * The switch is triggered only when the value is above
             * the upper limit, and not triggered if it is below
             * the lower limit and maintains the previous state
             * if in between (hysteresis)
             */
            AVERAGED;
        }

        /**
         * Create an analog switch sensor that is triggered when the
         * value exceeds the specified upper voltage and that is no
         * longer triggered when the value drops below the specified
         * lower voltage.
         *
         * @param channel the port to use for the analog trigger 0-3
         *        are on-board, 4-7 are on the MXP port
         * @param lowerVoltage the lower voltage limit that below
         *        which will result in the switch no longer being triggered
         * @param upperVoltage the upper voltage limit that above which
         *        will result in triggering the switch
         * @param option the trigger option; may not be null
         * @param mode the trigger mode; may not be null
         * @return the analog switch; never null
         */
        public static Switch analog(int channel,
                                    double lowerVoltage,
                                    double upperVoltage,
                                    AnalogOption option,
                                    TriggerMode mode) {...}
    }
    ...
}

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 Switch retracted;
    private Switch extended;

    @Override
    public void robotInit() {
        ...
        // limit switches in channels 3 and 4
        retracted = Hardware.Switches.normallyOpen(3);
        extended = Hardware.Switches.normallyOpen(4);

        // pass 'retracted' and/or 'extended` 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 extended or retracted fields 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 Switch or Fuse 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 switches or fuses 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 Switch interface as expected, but they add setter methods that your test code can explicitly set whether a mock switch is triggered.

The MockSwitch class is defined as follows:

public class MockSwitch implements Switch {

    private boolean triggered = false;

    @Override
    public boolean isTriggered() {
        return triggered;
    }

    /**
     * Set whether this switch is to be triggered.
     * @param triggered true if the switch is to be triggered, or false otherwise
     * @return this object to allow chaining of methods; never null
     */
    public MockSwitch setTriggered( boolean triggered ) {
        this.triggered = triggered;
        return this;
    }

    /**
     * Set this switch as being triggered.
     * @return this object to allow chaining of methods; never null
     */
    public MockSwitch setTriggered() {
        setTriggered(true);
        return this;
    }

    /**
     * Set this switch as being not triggered.
     * @return this object to allow chaining of methods; never null
     */
    public MockSwitch setNotTriggered() {
        setTriggered(false);
        return this;
    }

    @Override
    public String toString() {
        return triggered ? "closed" : "open";
    }
}

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 mocks switches with different initial states:

MockSwitch s1 = Mock.notTriggeredSwitch();
MockSwitch s2 = Mock.triggeredSwitch();
assert !s1.isTriggered();
assert s2.isTriggered();

s2.setNotTriggered();
assert !s2.isTriggered();

Strongback does not provide a MockFuse, since Fuse already has methods to set the state. You can, however, create "mock" fuses using the Mock static factory methods:

Fuse f1 = Mock.notTriggeredFuse();
Fuse f2 = Mock.triggeredFuse();
assert !f1.isTriggered();
assert f2.isTriggered();

f2.reset();
assert !f2.isTriggered();

results matching ""

    No results matching ""