1 package org.opentrafficsim.remotecontrol;
2
3 import java.awt.BorderLayout;
4 import java.awt.Dimension;
5 import java.awt.EventQueue;
6 import java.awt.FlowLayout;
7 import java.awt.Font;
8 import java.awt.event.ActionEvent;
9 import java.awt.event.ActionListener;
10 import java.awt.event.WindowEvent;
11 import java.awt.event.WindowListener;
12 import java.io.IOException;
13 import java.io.PrintStream;
14 import java.net.URL;
15 import java.nio.charset.StandardCharsets;
16 import java.util.Scanner;
17
18 import javax.swing.JButton;
19 import javax.swing.JComponent;
20 import javax.swing.JFrame;
21 import javax.swing.JPanel;
22 import javax.swing.JScrollPane;
23 import javax.swing.JTextArea;
24 import javax.swing.WindowConstants;
25 import javax.swing.border.EmptyBorder;
26
27 import org.djunits.unit.DurationUnit;
28 import org.djunits.value.vdouble.scalar.Duration;
29 import org.djunits.value.vdouble.scalar.Time;
30 import org.djutils.cli.Checkable;
31 import org.djutils.cli.CliUtil;
32 import org.djutils.decoderdumper.HexDumper;
33 import org.djutils.io.URLResource;
34 import org.djutils.logger.CategoryLogger;
35 import org.djutils.logger.LogCategory;
36 import org.djutils.serialization.SerializationException;
37 import org.pmw.tinylog.Level;
38 import org.sim0mq.Sim0MQException;
39 import org.sim0mq.message.Sim0MQMessage;
40 import org.zeromq.SocketType;
41 import org.zeromq.ZContext;
42 import org.zeromq.ZMQ;
43 import org.zeromq.ZMQException;
44
45 import picocli.CommandLine.Command;
46 import picocli.CommandLine.Option;
47
48
49
50
51
52
53
54
55
56
57
58
59 public class Sim0MQRemoteController extends JFrame implements WindowListener, ActionListener
60 {
61
62 private static final long serialVersionUID = 20200304L;
63
64
65
66
67 @Command(description = "Test program for Remote Control OTS", name = "Remote Control OTS", mixinStandardHelpOptions = true,
68 version = "1.0")
69 public static class Options implements Checkable
70 {
71
72 @Option(names = {"-p", "--port"}, description = "Internet port to use", defaultValue = "8888")
73 private int port;
74
75
76
77
78
79 public final int getPort()
80 {
81 return this.port;
82 }
83
84
85 @Option(names = {"-H", "--host"}, description = "Internet host to use", defaultValue = "localhost")
86 private String host;
87
88
89
90
91
92 public final String getHost()
93 {
94 return this.host;
95 }
96
97 @Override
98 public final void check() throws Exception
99 {
100 if (this.port <= 0 || this.port > 65535)
101 {
102 throw new Exception("Port should be between 1 and 65535");
103 }
104 }
105 }
106
107
108 @SuppressWarnings("checkstyle:visibilitymodifier")
109 static Sim0MQRemoteController gui = null;
110
111
112 private ZMQ.Socket toOTS;
113
114
115
116
117
118 public static void main(final String[] args)
119 {
120 CategoryLogger.setAllLogLevel(Level.WARNING);
121 CategoryLogger.setLogCategories(LogCategory.ALL);
122
123 try
124 {
125 EventQueue.invokeAndWait(new Runnable()
126 {
127
128 @Override
129 public void run()
130 {
131 try
132 {
133 gui = new Sim0MQRemoteController();
134 gui.setVisible(true);
135 }
136 catch (Exception e)
137 {
138 e.printStackTrace();
139 System.exit(ERROR);
140 }
141 }
142 });
143 }
144 catch (Exception e)
145 {
146 e.printStackTrace();
147 System.exit(ERROR);
148 }
149
150 Options options = new Options();
151 CliUtil.execute(options, args);
152 gui.processArguments(options.getHost(), options.getPort());
153 }
154
155
156 private ZContext zContext = new ZContext(1);
157
158
159 private Thread pollerThread;
160
161
162
163
164 class PollerThread extends Thread
165 {
166
167 private final ZContext context;
168
169
170 private final String slaveHost;
171
172
173 private final int slavePort;
174
175
176
177
178
179
180
181 PollerThread(final ZContext context, final String slaveHost, final int slavePort)
182 {
183 this.context = context;
184 this.slaveHost = slaveHost;
185 this.slavePort = slavePort;
186 }
187
188 @Override
189 public final void run()
190 {
191 int nextExpectedPacket = 0;
192 ZMQ.Socket slaveSocket = this.context.createSocket(SocketType.PAIR);
193 slaveSocket.setHWM(100000);
194 ZMQ.Socket awtSocketIn = this.context.createSocket(SocketType.PULL);
195 awtSocketIn.setHWM(100000);
196 ZMQ.Socket awtSocketOut = this.context.createSocket(SocketType.PUSH);
197 awtSocketOut.setHWM(100000);
198 slaveSocket.connect("tcp://" + this.slaveHost + ":" + this.slavePort);
199 awtSocketIn.bind("inproc://fromAWT");
200 awtSocketOut.bind("inproc://toAWT");
201 ZMQ.Poller items = this.context.createPoller(2);
202 items.register(slaveSocket, ZMQ.Poller.POLLIN);
203 items.register(awtSocketIn, ZMQ.Poller.POLLIN);
204 while (!Thread.currentThread().isInterrupted())
205 {
206 byte[] message;
207 items.poll();
208 if (items.pollin(0))
209 {
210 message = slaveSocket.recv(0);
211 String expectedSenderField = String.format("slave_%05d", ++nextExpectedPacket);
212 try
213 {
214 Object[] messageFields = Sim0MQMessage.decode(message).createObjectArray();
215 String senderTag = (String) messageFields[3];
216 if (!senderTag.equals(expectedSenderField))
217 {
218 System.err.println("Got message " + senderTag + " , expected " + expectedSenderField
219 + ", message is " + messageFields[5]);
220 }
221 }
222 catch (Sim0MQException | SerializationException e)
223 {
224 e.printStackTrace();
225 }
226
227
228 awtSocketOut.send(message);
229 }
230 if (items.pollin(1))
231 {
232 message = awtSocketIn.recv(0);
233
234 slaveSocket.send(message);
235 }
236 }
237
238 }
239
240 }
241
242
243
244
245
246
247 public void processArguments(final String host, final int port)
248 {
249 this.output.println("host is " + host + ", port is " + port);
250
251 this.pollerThread = new PollerThread(this.zContext, host, port);
252
253 this.pollerThread.start();
254
255 this.toOTS = this.zContext.createSocket(SocketType.PUSH);
256 this.toOTS.setHWM(100000);
257
258 new OTS2AWT(this.zContext).start();
259
260 this.toOTS.connect("inproc://fromAWT");
261 }
262
263
264
265
266
267
268 public void write(final String command) throws IOException
269 {
270 this.toOTS.send(command);
271
272 this.output.println("Sent string \"" + command + "\"");
273 }
274
275
276
277
278
279
280 public void write(final byte[] bytes) throws IOException
281 {
282 this.toOTS.send(bytes);
283
284
285 }
286
287
288 private JButton stepTo;
289
290
291
292
293 Sim0MQRemoteController()
294 {
295
296 setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
297 setBounds(100, 100, 1000, 800);
298 JPanel panelAll = new JPanel();
299 panelAll.setBorder(new EmptyBorder(5, 5, 5, 5));
300 panelAll.setLayout(new BorderLayout(0, 0));
301 setContentPane(panelAll);
302 JPanel panelControls = new JPanel();
303 panelAll.add(panelControls, BorderLayout.PAGE_START);
304 JTextArea textArea = new JTextArea();
305 textArea.setFont(new Font("monospaced", Font.PLAIN, 12));
306 JScrollPane scrollPane = new JScrollPane(textArea);
307 scrollPane.setPreferredSize(new Dimension(800, 400));
308 panelAll.add(scrollPane, BorderLayout.PAGE_END);
309 this.output = new PrintStream(new TextAreaOutputStream(textArea), true);
310 JPanel controls = new JPanel();
311 controls.setLayout(new FlowLayout());
312 JButton sendNetwork = new JButton("Send network");
313 sendNetwork.setActionCommand("SendNetwork");
314 sendNetwork.addActionListener(this);
315 controls.add(sendNetwork);
316 this.stepTo = new JButton("Step to 10 s");
317 this.stepTo.setActionCommand("StepTo");
318 this.stepTo.addActionListener(this);
319 controls.add(this.stepTo);
320 JButton step100TimesTo = new JButton("Step 100 times 10 s");
321 step100TimesTo.setActionCommand("Step100To");
322 step100TimesTo.addActionListener(this);
323 controls.add(step100TimesTo);
324 JButton getGTUPositions = new JButton("Get all GTU positions");
325 getGTUPositions.setActionCommand("GetAllGTUPositions");
326 getGTUPositions.addActionListener(this);
327 controls.add(getGTUPositions);
328 panelAll.add(controls, BorderLayout.CENTER);
329 }
330
331
332 @SuppressWarnings("checkstyle:visibilitymodifier")
333 PrintStream output = null;
334
335
336
337
338 public void shutDown()
339 {
340
341 }
342
343
344 @Override
345 public void windowOpened(final WindowEvent e)
346 {
347
348 }
349
350
351 @Override
352 public final void windowClosing(final WindowEvent e)
353 {
354 shutDown();
355 }
356
357
358 @Override
359 public void windowClosed(final WindowEvent e)
360 {
361
362 }
363
364
365 @Override
366 public void windowIconified(final WindowEvent e)
367 {
368
369 }
370
371
372 @Override
373 public void windowDeiconified(final WindowEvent e)
374 {
375
376 }
377
378
379 @Override
380 public void windowActivated(final WindowEvent e)
381 {
382
383 }
384
385
386 @Override
387 public void windowDeactivated(final WindowEvent e)
388 {
389
390 }
391
392
393
394
395 class OTS2AWT extends Thread
396 {
397
398 private final ZMQ.Socket fromOTS;
399
400
401
402
403
404 OTS2AWT(final ZContext zContext)
405 {
406 this.fromOTS = zContext.createSocket(SocketType.PULL);
407 this.fromOTS.setHWM(100000);
408 this.fromOTS.connect("inproc://toAWT");
409 }
410
411
412 @Override
413 public void run()
414 {
415 do
416 {
417 try
418 {
419
420 byte[] bytes = this.fromOTS.recv(0);
421
422 Object[] message = Sim0MQMessage.decode(bytes).createObjectArray();
423 if (message.length > 8 && message[5] instanceof String)
424 {
425 String command = (String) message[5];
426 switch (command)
427 {
428 case "GTUPOSITION":
429 Sim0MQRemoteController.this.output
430 .println(String.format("%10.10s: %s x=%8.3f y=%8.3f z=%8.3f heading=%6.1f, v=%s, a=%s",
431 message[8], message[9], message[10], message[11], message[12],
432 Math.toDegrees((Double) message[13]), message[14], message[15]));
433 break;
434
435 case "TIME_CHANGED_EVENT":
436 Sim0MQRemoteController.this.output.println(message[8]);
437 break;
438
439 case "TRAFFICCONTROL.CONTROLLER_WARNING":
440 Sim0MQRemoteController.this.output
441 .println(String.format("%s: warning %s", message[8], message[9]));
442 break;
443
444 case "TRAFFICCONTROL.CONTROLLER_EVALUATING":
445 Sim0MQRemoteController.this.output
446 .println(String.format("%s: evaluating %s", message[8], message[9]));
447 break;
448
449 case "TRAFFICCONTROL.CONFLICT_GROUP_CHANGED":
450 Sim0MQRemoteController.this.output.println(String.format(
451 "%s: conflict group changed from %s to %s", message[8], message[9], message[10]));
452 break;
453
454 case "NETWORK.GTU.ADD":
455 Sim0MQRemoteController.this.output.println(String.format("GTU added %s", message[8]));
456 break;
457
458 case "NETWORK.GTU.REMOVE":
459 Sim0MQRemoteController.this.output.println(String.format("GTU removed %s", message[8]));
460 break;
461
462 case "READY":
463 Sim0MQRemoteController.this.output.println("Slave is ready for the next command");
464 break;
465
466 default:
467 Sim0MQRemoteController.this.output.println("Unhandled reply: " + command);
468 Sim0MQRemoteController.this.output.println(HexDumper.hexDumper(bytes));
469 Sim0MQRemoteController.this.output.println("Received:");
470 Sim0MQRemoteController.this.output.println(Sim0MQMessage.print(message));
471 break;
472
473 }
474 }
475 else
476 {
477 Sim0MQRemoteController.this.output.println(HexDumper.hexDumper(bytes));
478 }
479 }
480 catch (ZMQException | Sim0MQException | SerializationException e)
481 {
482 e.printStackTrace();
483 return;
484 }
485 }
486 while (true);
487
488 }
489 }
490
491
492
493
494
495
496
497
498 public static String readStringFromURL(final URL url) throws IOException
499 {
500 try (Scanner scanner = new Scanner(url.openStream(), StandardCharsets.UTF_8.toString()))
501 {
502 scanner.useDelimiter("\\A");
503 return scanner.hasNext() ? scanner.next() : "";
504 }
505 }
506
507
508 @Override
509 public void actionPerformed(final ActionEvent e)
510 {
511 switch (e.getActionCommand())
512 {
513 case "SendNetwork":
514 {
515 String networkFile = "/TrafCODDemo2/TrafCODDemo2.xml";
516 Duration warmupDuration = Duration.ZERO;
517 Duration runDuration = new Duration(3600, DurationUnit.SECOND);
518 Long seed = 123456L;
519 URL url = URLResource.getResource(networkFile);
520
521 try
522 {
523 String xml = readStringFromURL(url);
524 System.out.println("xml length = " + xml.length());
525 try
526 {
527 write(Sim0MQMessage.encodeUTF8(true, 0, "RemoteControl", "OTS", "LOADNETWORK", 0, xml, warmupDuration,
528 runDuration, seed));
529 }
530 catch (IOException e1)
531 {
532 this.output.println("Write failed; Caught IOException");
533 ((JComponent) e.getSource()).setEnabled(false);
534 }
535 catch (Sim0MQException e1)
536 {
537 e1.printStackTrace();
538 }
539 catch (SerializationException e1)
540 {
541 e1.printStackTrace();
542 }
543 }
544 catch (IOException e2)
545 {
546 System.err.println("Could not load file " + networkFile);
547 e2.printStackTrace();
548 }
549 break;
550 }
551
552 case "StepTo":
553 {
554 JButton button = (JButton) e.getSource();
555 String caption = button.getText();
556 int position;
557 for (position = 0; position < caption.length(); position++)
558 {
559 if (Character.isDigit(caption.charAt(position)))
560 {
561 break;
562 }
563 }
564 Time toTime = Time.valueOf(caption.substring(position));
565 try
566 {
567 write(Sim0MQMessage.encodeUTF8(true, 0, "RemoteControl", "OTS", "SIMULATEUNTIL", 0, toTime));
568 toTime = toTime.plus(new Duration(10, DurationUnit.SECOND));
569 button.setText(caption.substring(0, position)
570 + String.format("%.0f %s", toTime.getInUnit(), toTime.getDisplayUnit()));
571 }
572 catch (IOException | Sim0MQException | SerializationException e1)
573 {
574 e1.printStackTrace();
575 }
576 break;
577 }
578
579 case "Step100To":
580 {
581 for (int i = 0; i < 100; i++)
582 {
583 actionPerformed(new ActionEvent(this.stepTo, 0, "StepTo"));
584 }
585 break;
586 }
587
588 case "GetAllGTUPositions":
589 {
590 try
591 {
592 write(Sim0MQMessage.encodeUTF8(true, 0, "RemoteControl", "OTS", "SENDALLGTUPOSITIONS", 0));
593 }
594 catch (IOException | Sim0MQException | SerializationException e1)
595 {
596 e1.printStackTrace();
597 }
598 break;
599 }
600
601 default:
602 this.output.println("Oops: unhandled action command");
603 }
604
605 }
606 }