View Javadoc
1   /*
2    * Copyright © 2016 Greg Chabala
3    *
4    * This file is part of brick-control-lab.
5    *
6    * brick-control-lab is free software: you can redistribute it and/or modify
7    * it under the terms of the GNU Lesser General Public License as
8    * published by the Free Software Foundation, either version 3 of the
9    * License, or (at your option) any later version.
10   *
11   * brick-control-lab is distributed in the hope that it will be useful,
12   * but WITHOUT ANY WARRANTY; without even the implied warranty of
13   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14   * GNU Lesser General Public License for more details.
15   *
16   * You should have received a copy of the GNU Lesser General Public License
17   * along with brick-control-lab.  If not, see http://www.gnu.org/licenses/.
18   */
19  package org.chabala.brick.controllab;
20  
21  import org.chabala.brick.controllab.sensor.*;
22  import org.junit.Ignore;
23  import org.junit.Test;
24  import org.slf4j.Logger;
25  import org.slf4j.LoggerFactory;
26  
27  import java.io.IOException;
28  import java.lang.invoke.MethodHandles;
29  import java.util.*;
30  import java.util.concurrent.atomic.AtomicBoolean;
31  import java.util.stream.Collectors;
32  
33  import static javax.management.timer.Timer.ONE_SECOND;
34  import static org.awaitility.Awaitility.await;
35  import static org.chabala.brick.controllab.PortChooser.choosePort;
36  import static org.hamcrest.Matchers.*;
37  import static org.junit.Assert.*;
38  import static org.junit.Assume.assumeNoException;
39  
40  /**
41   * Integration tests for the {@link ControlLab}.
42   *
43   * <p>These tests require a connection to the hardware. There's no way to validate
44   * the behavior other than observing the control lab, so there are no assertions.
45   */
46  @SuppressWarnings({"squid:S2699","squid:S2925"})
47  public class ControlLabIT {
48      private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
49  
50      @Test
51      public void testTurnOutputOff() throws Exception {
52          try (ControlLab controlLab = ControlLab.newControlLab()) {
53              try {
54                  controlLab.open(choosePort(controlLab));
55              } catch (IOException e) {
56                  assumeNoException(e);
57              }
58              Thread.sleep(ONE_SECOND * 3);
59              controlLab.turnOutputOn(OutputId.ALL);
60              Thread.sleep(ONE_SECOND * 3);
61              controlLab.turnOutputOff(EnumSet.range(OutputId.A, OutputId.D));
62              Thread.sleep(ONE_SECOND * 3);
63          }
64      }
65  
66      @Test
67      public void testControlLabOutputs() throws Exception {
68          try (ControlLab controlLab = ControlLab.newControlLab()) {
69              try {
70                  controlLab.open(choosePort(controlLab));
71              } catch (IOException e) {
72                  assumeNoException(e);
73              }
74              Thread.sleep(ONE_SECOND);
75  
76              controlLab.turnOutputOn(OutputId.ALL);
77              Thread.sleep(ONE_SECOND);
78  
79              controlLab.setOutputDirection(Direction.LEFT, EnumSet.of(OutputId.E, OutputId.F));
80              Thread.sleep(ONE_SECOND);
81  
82              controlLab.getOutput(EnumSet.range(OutputId.E, OutputId.H)).reverseDirection();
83              Thread.sleep(ONE_SECOND);
84  
85              controlLab.getOutput(OutputId.H).setDirection(Direction.RIGHT);
86              Thread.sleep(ONE_SECOND);
87  
88              for (OutputId o : descendingRange(OutputId.H, OutputId.E)) {
89                  controlLab.getOutput(o).turnOff();
90                  Thread.sleep(ONE_SECOND);
91              }
92  
93              controlLab.turnOutputOff(EnumSet.range(OutputId.A, OutputId.D));
94              Thread.sleep(ONE_SECOND * 5);
95          }
96      }
97  
98      @Test
99      public void testFluentOutputControl() throws Exception {
100         try (ControlLab controlLab = ControlLab.newControlLab()) {
101             try {
102                 controlLab.open(choosePort(controlLab));
103             } catch (IOException e) {
104                 assumeNoException(e);
105             }
106             Output output = controlLab.getOutput(OutputId.A);
107             output.setDirection(Direction.LEFT).setPowerLevel(PowerLevel.P2).turnOn();
108             Thread.sleep(ONE_SECOND * 5);
109 
110             output.reverseDirection().setPowerLevel(PowerLevel.P8);
111             Thread.sleep(ONE_SECOND * 5);
112         }
113     }
114 
115     @Ignore("Requires interaction with stop button to complete, only run manually")
116     @Test
117     public void testControlLabInputs() throws Exception {
118         AtomicBoolean stop = new AtomicBoolean(false);
119         try (ControlLab controlLab = ControlLab.newControlLab()) {
120             try {
121                 controlLab.open(choosePort(controlLab));
122             } catch (IOException e) {
123                 assumeNoException(e);
124             }
125             controlLab.getStopButton().addListener(stopButtonEvent -> stop.set(true));
126             Map<InputId, String> lastTouchValues = Collections.synchronizedMap(new EnumMap<>(InputId.class));
127             List<InputId> passiveInputs = Arrays.stream(InputId.values())
128                                                 .filter(i -> i.getInputType().equals(InputType.PASSIVE))
129                                                 .collect(Collectors.toList());
130             passiveInputs.forEach(i -> lastTouchValues.put(i, ""));
131             TouchSensorListener touchSensorListener = sensorEvent -> {
132                 InputId input = sensorEvent.getInput();
133                 String newValue = sensorEvent.getValue().touchStatus();
134                 if ("".equals(newValue)) {
135                     return;
136                 }
137                 String oldValue = lastTouchValues.put(input, newValue);
138                 if (!newValue.equals(oldValue)) {
139                     log.info("{} value changed: {}", input, newValue);
140                 }
141             };
142             for (InputId passiveInput : passiveInputs) {
143                 controlLab.getInput(passiveInput).addListener(touchSensorListener);
144             }
145             LightSensorListener lightSensorListener = sensorEvent -> {
146                 InputId input = sensorEvent.getInput();
147                 log.info("{} value changed: {}", input, sensorEvent.getValue());
148             };
149             Arrays.stream(InputId.values())
150                   .filter(i -> i.getInputType().equals(InputType.ACTIVE))
151                   .map(controlLab::getInput)
152                   .forEach(i -> i.addListener(lightSensorListener));
153 
154             await().forever().until(stop::get);
155         }
156     }
157 
158     @Ignore("Requires interaction with stop button to complete, only run manually")
159     @Test
160     public void testControlLabInputsRaw() throws Exception {
161         AtomicBoolean stop = new AtomicBoolean(false);
162         try (ControlLab controlLab = ControlLab.newControlLab()) {
163             try {
164                 controlLab.open(choosePort(controlLab));
165             } catch (IOException e) {
166                 assumeNoException(e);
167             }
168             controlLab.getStopButton().addListener(stopButtonEvent -> stop.set(true));
169             SensorListener sensorListener = sensorEvent ->
170                     log.info("{} value changed: {}", sensorEvent.getInput(), sensorEvent.getValue());
171             Arrays.stream(InputId.values())
172                   .map(controlLab::getInput)
173                   .forEach(i -> i.addListener(sensorListener));
174 
175             await().forever().until(stop::get);
176         }
177     }
178 
179     @Test
180     public void testOutputPowerLevels() throws Exception {
181         try (ControlLab controlLab = ControlLab.newControlLab()) {
182             try {
183                 controlLab.open(choosePort(controlLab));
184             } catch (IOException e) {
185                 assumeNoException(e);
186             }
187             Thread.sleep(ONE_SECOND);
188 
189             Output output = controlLab.getOutput(OutputId.A);
190             output.setPowerLevel(PowerLevel.P1).turnOn();
191             Thread.sleep(ONE_SECOND);
192 
193             for (PowerLevel p : EnumSet.range(PowerLevel.P2, PowerLevel.P8)) {
194                 output.setPowerLevel(p);
195                 Thread.sleep(ONE_SECOND);
196             }
197 
198             output.setPowerLevel(PowerLevel.P0);
199             Thread.sleep(ONE_SECOND);
200 
201             output.turnOn();
202             Thread.sleep(ONE_SECOND);
203         }
204     }
205 
206     @Ignore("Requires interaction with stop button to complete, only run manually")
207     @Test
208     public void testRunUntilStopButtonPressed() throws Exception {
209         AtomicBoolean stop = new AtomicBoolean(false);
210         try (ControlLab controlLab = ControlLab.newControlLab()) {
211             try {
212                 controlLab.open(choosePort(controlLab));
213             } catch (IOException e) {
214                 assumeNoException(e);
215             }
216             controlLab.getStopButton().addListener(stopButtonEvent -> stop.set(true));
217             await().forever().until(stop::get);
218         }
219     }
220 
221     /**
222      * This test starts all outputs at the highest power level, gradually decreases to the minimum power
223      * level, then changes the direction and increases back to maximum. This continues until a touch sensor
224      * on Input 1 is pressed. It can be used to test an output device to see how it behaves under different
225      * power supply levels.
226      * @throws Exception on any issue with the test
227      */
228     @Ignore("Requires interaction with Input 1 to complete, only run manually")
229     @Test
230     public void testMotorInBothDirections() throws Exception {
231         AtomicBoolean stop = new AtomicBoolean(false);
232         try (ControlLab controlLab = ControlLab.newControlLab()) {
233             try {
234                 controlLab.open(choosePort(controlLab));
235             } catch (IOException e) {
236                 assumeNoException(e);
237             }
238             Thread.sleep(ONE_SECOND);
239             controlLab.getInput(InputId.I1).addListener((TouchSensorListener) sensorEvent -> stop.set(true));
240             Output outputs = controlLab.getOutput(OutputId.ALL);
241             while (!stop.get()) {
242                 outputs.setPowerLevel(PowerLevel.P8).setDirection(Direction.RIGHT).turnOn();
243                 Thread.sleep(ONE_SECOND);
244                 if (stop.get()) {
245                     return;
246                 }
247 
248                 for (PowerLevel p : descendingRange(PowerLevel.P7, PowerLevel.P1)) {
249                     outputs.setPowerLevel(p);
250                     Thread.sleep(ONE_SECOND);
251                     if (stop.get()) {
252                         return;
253                     }
254                 }
255 
256                 outputs.reverseDirection();
257                 Thread.sleep(ONE_SECOND);
258                 if (stop.get()) {
259                     return;
260                 }
261 
262                 for (PowerLevel p : EnumSet.range(PowerLevel.P2, PowerLevel.P8)) {
263                     outputs.setPowerLevel(p);
264                     Thread.sleep(ONE_SECOND);
265                     if (stop.get()) {
266                         return;
267                     }
268                 }
269             }
270         }
271     }
272 
273     /**
274      * Returns a list of enums in descending order. {@link EnumSet#range(Enum, Enum)} provides
275      * an iterator for ascending order, but descending requires wrapping in a list and reversing
276      * the collection.
277      */
278     private <E extends Enum<E>> List<E> descendingRange(E from, E to) {
279         EnumSet<E> rangeSet;
280         if (from.compareTo(to) > 0) {
281             rangeSet = EnumSet.range(to, from);
282         } else {
283             rangeSet = EnumSet.range(from, to);
284         }
285         List<E> rangeList = new ArrayList<>(rangeSet);
286         Collections.reverse(rangeList);
287         return rangeList;
288     }
289 
290     @Test
291     public void testDescendingRange() throws Exception {
292         List<PowerLevel> powerLevelList = new ArrayList<>(EnumSet.range(PowerLevel.P1, PowerLevel.P3));
293         assertThat(powerLevelList, contains(PowerLevel.P1, PowerLevel.P2, PowerLevel.P3));
294         assertThat(powerLevelList.get(0), is(PowerLevel.P1));
295         assertThat(powerLevelList.get(2), is(PowerLevel.P3));
296 
297         powerLevelList = descendingRange(PowerLevel.P1, PowerLevel.P3);
298         assertThat(powerLevelList, contains(PowerLevel.P3, PowerLevel.P2, PowerLevel.P1));
299         assertThat(powerLevelList.get(0), is(PowerLevel.P3));
300         assertThat(powerLevelList.get(2), is(PowerLevel.P1));
301 
302         powerLevelList = descendingRange(PowerLevel.P3, PowerLevel.P1);
303         assertThat(powerLevelList, contains(PowerLevel.P3, PowerLevel.P2, PowerLevel.P1));
304         assertThat(powerLevelList.get(0), is(PowerLevel.P3));
305         assertThat(powerLevelList.get(2), is(PowerLevel.P1));
306     }
307 }