1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.chabala.brick.controllab;
20
21 import org.junit.Ignore;
22 import org.junit.Test;
23 import org.slf4j.Logger;
24 import org.slf4j.LoggerFactory;
25
26 import java.io.IOException;
27 import java.lang.invoke.MethodHandles;
28 import java.util.*;
29 import java.util.concurrent.CountDownLatch;
30 import java.util.concurrent.atomic.AtomicBoolean;
31 import java.util.stream.Collectors;
32 import java.util.stream.IntStream;
33 import java.util.stream.Stream;
34
35 import static javax.management.timer.Timer.ONE_SECOND;
36 import static org.awaitility.Awaitility.await;
37 import static org.chabala.brick.controllab.PortChooser.choosePort;
38 import static org.hamcrest.Matchers.*;
39 import static org.junit.Assert.assertThat;
40 import static org.junit.Assume.assumeNoException;
41
42
43
44
45
46
47
48 @SuppressWarnings({"squid:S2699","squid:S2925"})
49 public class MultipleControlLabIT {
50
51 private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
52
53 private static final int AVAILABLE_CONTROL_LABS = 4;
54
55
56
57
58
59
60 @Ignore("Requires interaction with stop button to complete, only run manually")
61 @Test
62 public void testPortIdentification() throws Exception {
63 for (int portPosition = 1; portPosition <= AVAILABLE_CONTROL_LABS; portPosition++) {
64 AtomicBoolean stop = new AtomicBoolean(false);
65 try (ControlLab controlLab = ControlLab.newControlLab()) {
66 try {
67 controlLab.open(choosePort(controlLab, portPosition));
68 } catch (IOException e) {
69 assumeNoException(e);
70 }
71 controlLab.getStopButton().addListener(stopButtonEvent -> stop.set(true));
72 await().forever().until(stop::get);
73 }
74 }
75 }
76
77
78
79
80
81 @Test
82 public void testOperatingInUnison() throws Exception {
83 Set<ControlLab> controlLabs = new HashSet<>();
84 try {
85 for (int portPosition = 1; portPosition <= AVAILABLE_CONTROL_LABS; portPosition++) {
86 ControlLab controlLab = ControlLab.newControlLab();
87 controlLabs.add(controlLab);
88 try {
89 controlLab.open(choosePort(controlLab, portPosition));
90 } catch (IOException e) {
91 assumeNoException(e);
92 }
93 }
94 OutputId prev = OutputId.H;
95 for (OutputId next : OutputId.ALL) {
96 switchOutput(controlLabs, prev, next);
97 prev = next;
98 }
99 } finally {
100 controlLabs.parallelStream().forEach(c -> {
101 try {
102 c.close();
103 } catch (IOException e) {
104 assumeNoException(e);
105 }
106 });
107 }
108 }
109
110 private void switchOutput(Set<ControlLab> controlLabs, OutputId off, OutputId on) throws InterruptedException {
111 controlLabs.parallelStream().forEach(c -> {
112 try {
113 c.getOutput(off).turnOff();
114 c.getOutput(on).turnOn();
115 } catch (IOException e) {
116 assumeNoException(e);
117 }
118 });
119 Thread.sleep(ONE_SECOND / 2);
120 }
121
122
123
124
125
126
127
128
129
130 @Ignore("Requires interaction with stop button to complete, only run manually")
131 @Test
132 public void testOperatingInTandem() throws Exception {
133 List<ControlLab> controlLabs = new ArrayList<>();
134 AtomicBoolean stop = new AtomicBoolean(false);
135 List<String> portNameOrder = getSerialPortsInOrderOfStopButtonPressed();
136 log.info("Ports in order pressed: {}", String.join(", ", portNameOrder));
137 assertThat(portNameOrder, hasSize(AVAILABLE_CONTROL_LABS));
138 try {
139 for (String portName : portNameOrder) {
140 ControlLab controlLab = ControlLab.newControlLab();
141 controlLabs.add(controlLab);
142 try {
143 controlLab.open(portName);
144 } catch (IOException e) {
145 assumeNoException(e);
146 }
147 controlLab.getStopButton().addListener(event -> stop.set(true));
148 }
149 List<Output> outputOrder = getOutputOrder(controlLabs);
150 Output prev = null;
151 Output next = null;
152 Iterator<Output> outputIterator = new CyclicalIterator<>(outputOrder);
153 while (outputIterator.hasNext()) {
154 next = outputIterator.next();
155 next.turnOn();
156 if (prev != null) {
157 prev.turnOff();
158 }
159 prev = next;
160 Thread.sleep(ONE_SECOND / 8);
161 if (stop.get()) {
162 break;
163 }
164 }
165 if (next != null) {
166 next.turnOff();
167 }
168 } finally {
169 controlLabs.parallelStream().forEach(c -> {
170 try {
171 c.close();
172 } catch (IOException e) {
173 assumeNoException(e);
174 }
175 });
176 }
177 }
178
179 private List<Output> getOutputOrder(List<ControlLab> controlLabs) {
180 List<Output> outputOrder = new ArrayList<>();
181 List<EnumSet<OutputId>> columnOrder = Arrays.asList(
182 EnumSet.of(OutputId.A, OutputId.E),
183 EnumSet.of(OutputId.B, OutputId.F),
184 EnumSet.of(OutputId.C, OutputId.G),
185 EnumSet.of(OutputId.D, OutputId.H)
186 );
187 int splitIndex = AVAILABLE_CONTROL_LABS / 2;
188 List<ControlLab> leftToRight = controlLabs.subList(0, splitIndex);
189 List<ControlLab> rightToLeft = controlLabs.subList(splitIndex, controlLabs.size());
190 for (ControlLab controlLab : leftToRight) {
191 for (EnumSet<OutputId> column : columnOrder) {
192 outputOrder.add(controlLab.getOutput(column));
193 }
194 }
195 Collections.reverse(columnOrder);
196 for (ControlLab controlLab : rightToLeft) {
197 for (EnumSet<OutputId> column : columnOrder) {
198 outputOrder.add(controlLab.getOutput(column));
199 }
200 }
201 return outputOrder;
202 }
203
204
205
206
207
208
209 private final class CyclicalIterator<E> implements Iterator<E> {
210
211 private final Iterable<E> iterable;
212 private Iterator<E> iterator;
213
214 private CyclicalIterator(Iterable<E> iterable) {
215 this.iterable = iterable;
216 iterator = iterable.iterator();
217 }
218
219
220
221
222
223
224
225
226 @Override
227 public boolean hasNext() {
228 boolean hasNext = iterator.hasNext();
229 if (!hasNext) {
230 iterator = iterable.iterator();
231 hasNext = iterator.hasNext();
232 }
233 return hasNext;
234 }
235
236
237
238
239
240
241
242 @Override
243 public E next() {
244 if (!hasNext()) {
245 throw new NoSuchElementException();
246 }
247 return iterator.next();
248 }
249 }
250
251
252
253
254
255
256 private List<String> getSerialPortsInOrderOfStopButtonPressed() throws InterruptedException {
257 List<String> portNameOrder = Collections.synchronizedList(new ArrayList<>());
258 CountDownLatch portIdLatch = new CountDownLatch(AVAILABLE_CONTROL_LABS);
259 List<ControlLab> controlLabs = Stream.generate(ControlLab::newControlLab)
260 .limit(AVAILABLE_CONTROL_LABS)
261 .parallel()
262 .peek(controlLab -> controlLab.getStopButton().addListener(event -> {
263 portNameOrder.add(controlLab.getConnectedPortName());
264 try {
265 controlLab.close();
266 } catch (IOException e) {
267 assumeNoException(e);
268 } finally {
269 portIdLatch.countDown();
270 }
271 })).collect(Collectors.toList());
272 try {
273 IntStream.rangeClosed(1, AVAILABLE_CONTROL_LABS)
274 .parallel()
275 .forEach(portPosition -> {
276 ControlLab controlLab = controlLabs.get(portPosition - 1);
277 try {
278 controlLab.open(choosePort(controlLab, portPosition));
279 } catch (IOException e) {
280 assumeNoException(e);
281 }
282 });
283 portIdLatch.await();
284 } finally {
285 controlLabs.parallelStream().forEach(c -> {
286 try {
287 c.close();
288 } catch (IOException e) {
289 assumeNoException(e);
290 }
291 });
292 }
293 return portNameOrder;
294 }
295 }