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