Project

General

Profile

Statistics
| Revision:

root / trunk / code / projects / colonet / client / Colonet.java @ 679

History | View | Annotate | Download (34.1 KB)

1
import javax.swing.*;
2
import javax.swing.event.*;
3
import javax.imageio.*;
4
import java.awt.*;
5
import java.awt.image.*;
6
import java.awt.event.*;
7
import java.net.*;
8
import java.io.*;
9
import java.util.*;
10

    
11
/**
12
* The Colonet Graphical User Interface Applet for use locally and over an internet connection.
13
* @author Gregory Tress
14
*
15
* To generate javadoc on this file or other java files, use javadoc *.java -d doc, where doc
16
* is the name of the folder into which the files should be written.
17
*/
18
public class Colonet extends JApplet implements ActionListener, MouseInputListener, KeyListener {
19

    
20
        // Used for painting
21
        final int BUFFER = 50;
22
        final int RADIUS = 30;
23

    
24
        // Used for the robot controller
25
        final int VECTOR_CONTROLLER_HEIGHT = 190;
26
        final int VECTOR_CONTROLLER_WIDTH = 320;
27

    
28
        // Connection
29
        JTextField txtHost;
30
        JTextField txtPort;
31
        JButton btnConnect;
32
        JButton btnGetXBeeIDs;
33
        JLabel lblConnectionStatus;
34
        JTextArea txtInfo;
35
        JPanel panelConnect;
36
        JPanel panelServerInterface;
37
        Socket socket;
38
        DataUpdater dataUpdater;
39

    
40
        // Control
41
        JPanel panelControl;
42
        JTabbedPane tabPaneControl;
43
        JPanel panelRobotControl;
44
        JPanel panelRobotDirection;
45
        JPanel panelRobotDirectionButtons;
46
        JPanel panelRobotCommands;
47
        JButton btnF, btnB, btnL, btnR, btnActivate;
48
        JLabel lblBattery;
49
        JLabel lblSelected;
50
        BatteryIcon batteryIcon;
51
        JPanel panelBattery;
52
        VectorController vectorController;
53
        BufferedImage imageVectorControl;
54
        JButton btnAssignID;
55
        boolean setWaypoint;
56
        int setWaypointID;
57
    JButton btnSetBounds;
58
    RobotBoundary boundary;
59
        JButton btnCommand_MoveTo;
60
        JButton btnCommand_MoveAll;
61
        JButton btnCommand_StopTask;
62
        JButton btnCommand_ResumeTask;
63
        JButton btnCommand_ChargeNow;
64
        JButton btnCommand_StopCharging;
65

    
66
        // Task Manager
67
        JPanel panelTaskManager;
68
        JScrollPane spTaskManager;
69
        JPanel panelTaskManagerControls;
70
        JPanel panelTaskManagerControlsPriority;
71
        DefaultListModel taskListModel;
72
        JList taskList;
73
        JButton btnAddTask;
74
        JButton btnRemoveTask;
75
        JButton btnMoveTaskUp;
76
        JButton btnMoveTaskDown;
77
        JButton btnUpdateTasks;
78
        TaskAddWindow taskAddWindow;
79

    
80
        // Webcam
81
        WebcamPanel panelWebcam;
82
        GraphicsConfiguration gc;
83
        JTabbedPane tabPaneMain;
84
        WebcamLoader webcamLoader;
85
        
86
        // Robots
87
        volatile int selectedBot;         //the user has selected this bot graphically
88
        volatile RobotList robotIcons;         //contains boundary shapes around bots for click detection
89
        volatile int[] xbeeID;
90

    
91
        Colonet self = this;
92
        volatile ColonetServerInterface csi;
93

    
94
        public void init () {
95
                // Set the default look and feel - choose one
96
                String laf = UIManager.getSystemLookAndFeelClassName();
97
                //String laf = UIManager.getCrossPlatformLookAndFeelClassName();
98
                //String laf = "com.sun.java.swing.plaf.motif.MotifLookAndFeel";
99
                try {
100
                        UIManager.setLookAndFeel(laf);
101
                } catch (UnsupportedLookAndFeelException exc) {
102
                        System.err.println ("Warning: UnsupportedLookAndFeel: " + laf);
103
                } catch (Exception exc) {
104
                        System.err.println ("Error loading " + laf + ": " + exc);
105
                }
106

    
107
                // We should invoke and wait to avoid browser display difficulties
108
                Runnable r = new Runnable() {
109
                        public void run() {
110
                                createAndShowGUI();
111
                        }
112
                };
113

    
114
                try {
115
                        SwingUtilities.invokeAndWait(r);
116
                } catch (InterruptedException e) {
117
                        //Not really sure why we would be in this situation
118
                        System.out.println("InterruptedException in init: " + e);
119
                } catch (java.lang.reflect.InvocationTargetException e) {
120
                        //This could happen for various reasons if there is a problem in createAndShowGUI
121
                        e.printStackTrace();
122
                }
123
        }
124

    
125
        public void destroy () {
126
                if (csi != null) {
127
                        csi.disconnect();
128
                }
129
        }
130

    
131
        private synchronized void createAndShowGUI () {
132
                // Webcam
133
                gc = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
134
                panelWebcam = new WebcamPanel();
135
                tabPaneMain = new JTabbedPane();
136
                tabPaneMain.setFont(new Font("arial", Font.PLAIN, 16));
137
                tabPaneMain.add(panelWebcam, "Webcam");
138

    
139
                // Robots
140
                selectedBot = -1;
141
                robotIcons = new RobotList();
142

    
143
                // Connection area
144
                txtInfo = new JTextArea();
145
                txtInfo.setBorder(BorderFactory.createTitledBorder("Info"));
146
                txtHost = new JTextField(this.getDocumentBase().getHost());
147
                txtHost.setBorder(BorderFactory.createTitledBorder("Host"));
148
                txtPort = new JTextField("10123");
149
                txtPort.setBorder(BorderFactory.createTitledBorder("Port"));
150
                btnConnect = new JButton("Connect");
151
                btnConnect.setFont(new Font("arial", Font.BOLD, 16));
152
                btnGetXBeeIDs = new JButton("Get XBee IDs");
153
                getRootPane().setDefaultButton(btnConnect);
154
                lblConnectionStatus = new JLabel("Status: Offline");
155
                panelConnect = new JPanel();
156
                panelConnect.setLayout(new GridLayout(6,1));
157
                panelConnect.add(lblConnectionStatus);
158
                panelConnect.add(txtHost);
159
                panelConnect.add(txtPort);
160
                panelConnect.add(btnConnect);
161
                //panelConnect.add(btnGetXBeeIDs);
162
                panelServerInterface = new JPanel();
163
                panelServerInterface.setLayout(new GridLayout(2,1));
164
                panelServerInterface.add(panelConnect);
165
                panelServerInterface.add(txtInfo);
166

    
167
                // Robot direction panel
168
                panelRobotDirection = new JPanel();
169
                panelRobotDirectionButtons = new JPanel();
170

    
171
                Font f = new Font(null, Font.PLAIN, 18);
172
                btnF = new JButton("\u2191");
173
                btnF.setFont(f);
174
                btnB = new JButton("\u2193");
175
                btnB.setFont(f);
176
                btnL = new JButton("\u2190");
177
                btnL.setFont(f);
178
                btnR = new JButton("\u2192");
179
                btnR.setFont(f);
180
                btnActivate = new JButton("\u25A0");
181
                btnActivate.setFont(new Font(null, Font.BOLD, 36));
182

    
183
                panelRobotDirectionButtons.setLayout(new GridLayout(1,5));
184
                panelRobotDirectionButtons.add(btnActivate);
185
                panelRobotDirectionButtons.add(btnF);
186
                panelRobotDirectionButtons.add(btnB);
187
                panelRobotDirectionButtons.add(btnL);
188
                panelRobotDirectionButtons.add(btnR);
189

    
190
                imageVectorControl = gc.createCompatibleImage(VECTOR_CONTROLLER_WIDTH, VECTOR_CONTROLLER_HEIGHT);
191
                vectorController = new VectorController(imageVectorControl, self);
192
                panelRobotDirection.setLayout(new BorderLayout());
193
                panelRobotDirection.add(vectorController, BorderLayout.CENTER);
194
                panelRobotDirection.add(panelRobotDirectionButtons, BorderLayout.SOUTH);
195

    
196
                // Robot Control and Commands
197
                panelRobotCommands = new JPanel();
198
                panelRobotCommands.setLayout(new GridLayout(5,2));
199
                // Battery subset
200
                batteryIcon = new BatteryIcon(0);
201
                lblBattery = new JLabel(batteryIcon);
202
                lblSelected = new JLabel("None");
203
                // Management subset
204
                setWaypoint = false;
205
                setWaypointID = -1;
206
                btnAssignID = new JButton("Assign ID");
207
        boundary = new RobotBoundary();
208
        btnSetBounds = new JButton("Set Boundary");
209
        // Control subset
210
                btnCommand_MoveTo = new JButton("Move to ...");
211
                btnCommand_MoveAll = new JButton("Move all ...");
212
                btnCommand_StopTask = new JButton("Stop Current Task");
213
                btnCommand_ResumeTask = new JButton("Resume Current Task");
214
                btnCommand_ChargeNow = new JButton("Recharge Now");
215
                btnCommand_StopCharging = new JButton("Stop Recharging");
216
                panelRobotCommands.add(new JLabel("Battery Level: "));
217
                panelRobotCommands.add(lblBattery);
218
                panelRobotCommands.add(new JLabel("Selected Icon: "));
219
                panelRobotCommands.add(lblSelected);
220
                panelRobotCommands.add(btnAssignID);
221
                panelRobotCommands.add(btnSetBounds);
222
                panelRobotCommands.add(btnCommand_MoveTo);
223
                panelRobotCommands.add(btnCommand_MoveAll);
224
                //panelRobotCommands.add(btnCommand_StopTask);
225
                //panelRobotCommands.add(btnCommand_ResumeTask);
226
                //panelRobotCommands.add(btnCommand_ChargeNow);
227
                //panelRobotCommands.add(btnCommand_StopCharging);
228
                panelRobotControl = new JPanel();
229
                panelRobotControl.setLayout(new GridLayout(2,1));
230
                panelRobotControl.add(panelRobotDirection);
231
                panelRobotControl.add(panelRobotCommands);
232

    
233
                // Task Manager
234
                panelTaskManager = new JPanel();
235
                panelTaskManager.setLayout(new BorderLayout());
236
                taskListModel = new DefaultListModel();
237
                taskList = new JList(taskListModel);
238
                taskList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
239
                taskList.setSelectedIndex(0);
240
                spTaskManager = new JScrollPane(taskList);
241
                panelTaskManagerControls = new JPanel();
242
                panelTaskManagerControls.setLayout(new GridLayout(1,4));
243
                panelTaskManagerControlsPriority = new JPanel();
244
                panelTaskManagerControlsPriority.setLayout(new GridLayout(1,2));
245
                btnAddTask = new JButton("Add...");
246
                btnRemoveTask = new JButton("Remove");
247
                btnMoveTaskUp = new JButton("^");
248
                btnMoveTaskDown = new JButton("v");
249
                btnUpdateTasks = new JButton("Update");
250
                panelTaskManagerControlsPriority.add(btnMoveTaskUp);
251
                panelTaskManagerControlsPriority.add(btnMoveTaskDown);
252
                panelTaskManagerControls.add(btnAddTask);
253
                panelTaskManagerControls.add(btnRemoveTask);
254
                panelTaskManagerControls.add(btnUpdateTasks);
255
                panelTaskManagerControls.add(panelTaskManagerControlsPriority);
256
                panelTaskManager.add(spTaskManager, BorderLayout.CENTER);
257
                panelTaskManager.add(panelTaskManagerControls, BorderLayout.SOUTH);
258
                panelTaskManager.add(new JLabel("Current Task Queue"), BorderLayout.NORTH);
259
                taskAddWindow = new TaskAddWindow();
260

    
261
                // Main control mechanism
262
                panelControl = new JPanel();
263
                panelControl.setLayout(new GridLayout(1,1));
264
                tabPaneControl = new JTabbedPane(JTabbedPane.TOP);
265
                tabPaneControl.setFont(new Font("arial", Font.PLAIN, 16));
266
                tabPaneControl.setPreferredSize(new Dimension(VECTOR_CONTROLLER_WIDTH, 0));
267
                tabPaneControl.addTab("Connection", panelServerInterface);
268
                tabPaneControl.addTab("Robots", panelRobotControl);
269
                //tabPaneControl.addTab("Tasks", panelTaskManager);
270
                panelControl.add(tabPaneControl);
271

    
272

    
273
                // Put all elements in the ContentPane
274
                this.getContentPane().setLayout(new BorderLayout());
275
                this.getContentPane().add(tabPaneMain, BorderLayout.CENTER);
276
                this.getContentPane().add(panelControl, BorderLayout.EAST);
277
                this.setVisible(true);
278
                
279
                // Disable components before connecting
280
                btnConnect.setText("Connect");
281
        lblConnectionStatus.setText("Status: Disconnected");
282
        btnF.setEnabled(false);
283
        btnB.setEnabled(false);
284
        btnL.setEnabled(false);
285
        btnR.setEnabled(false);
286
        btnActivate.setEnabled(false);
287
        btnAssignID.setEnabled(false);
288
        btnSetBounds.setEnabled(false);
289
        btnCommand_MoveTo.setEnabled(false);
290
        btnCommand_MoveAll.setEnabled(false);
291

    
292
                /* Add all listeners here */
293
                // Task Management
294
                btnAddTask.addActionListener(this);
295
                btnRemoveTask.addActionListener(this);
296
                btnMoveTaskUp.addActionListener(this);
297
                btnMoveTaskDown.addActionListener(this);
298
                btnUpdateTasks.addActionListener(this);
299
                // Robot Control
300
                btnF.addActionListener(this);
301
                btnB.addActionListener(this);
302
                btnL.addActionListener(this);
303
                btnR.addActionListener(this);
304
                btnF.addKeyListener(this);
305
                btnB.addKeyListener(this);
306
                btnL.addKeyListener(this);
307
                btnR.addKeyListener(this);
308
                btnActivate.addActionListener(this);
309
                btnActivate.addKeyListener(this);
310
                btnCommand_MoveTo.addActionListener(this);
311
                btnCommand_MoveAll.addActionListener(this);
312
                btnCommand_StopTask.addActionListener(this);
313
                btnCommand_ResumeTask.addActionListener(this);
314
                btnCommand_ChargeNow.addActionListener(this);
315
                btnCommand_StopCharging.addActionListener(this);
316
                // Other
317
                btnConnect.addActionListener(this);
318
                btnGetXBeeIDs.addActionListener(this);
319
                btnAssignID.addActionListener(this);
320
        btnSetBounds.addActionListener(this);
321
                panelWebcam.addMouseListener(this);
322
                panelWebcam.addMouseMotionListener(this);
323
        }
324
        
325
        public ColonetServerInterface getCSI () {
326
            return csi;
327
        }
328

    
329
        public void paint (Graphics g) {
330
                super.paint(g);
331
        }
332

    
333
        public void update (Graphics g) {
334
                paint(g);
335
        }
336

    
337
        /**
338
        * Gets the JTextArea used for displaying debugging information. 
339
        *
340
        * @return the JTextArea where debugging information can be displayed.
341
        */
342
        public JTextArea getInfoPanel () {
343
                return txtInfo;
344
        }
345

    
346
        /**
347
        * Parses a String containing BOM matrix information.
348
        * The ColonetServerInterface receives lines of the BOM matrix.        (For encoding
349
        * information, see the ColonetServerInterface documentation.)         The entire matrix is passed
350
        * to the client when requested. This method takes a string of the form
351
        * "[command code] [command code] [number of robots] [data0] [data1] ..."
352
        * with tokens separated by spaces and containing no brackets.
353
        * The [command code]s are predefined values identifying this String as a BOM data
354
        * String, [number of robots] is an integer, and the values that follow are
355
        * the sensor readings of the robots in order, starting with robot 0.        Only [number of robots]^2
356
        * data entries will be read.        The matrix values are saved locally until the next String is parsed.
357
        *
358
        *
359
        * @param line the String containing BOM matrix information.
360
        * @throws ArrayIndexOutOfBoundsException if there are fewer than [number of robots]^2 data entries in the String
361
        */
362
        public void parseMatrix (String line) {
363
                txtInfo.setText("");
364
                String [] str = line.split(" ");
365
                int num = Integer.parseInt(str[2]);
366
                for (int i = 0; i < num; i++) {
367
                        for (int j = 0; j < num; j++) {
368
                                String next = str[3 + i*num + j];
369
                                if (next.equals("-1")) {
370
                                        txtInfo.append("-");
371
                                } else {
372
                                        txtInfo.append(next);
373
                                }
374

    
375
                                if (j < num - 1) {
376
                                        txtInfo.append(" ");
377
                                }
378
                        }
379

    
380
                        if (i < num - 1) {
381
                                txtInfo.append("\n");
382
                        }
383
                }
384
                repaint();
385
        }
386

    
387
        public void connect () {
388
        if (csi != null)
389
                return;
390
        csi = new ColonetServerInterface(self);
391
        csi.connect(txtHost.getText(), txtPort.getText());
392
        if (!csi.isReady()) {
393
                csi = null;
394
                return;
395
        }
396
        webcamLoader = new WebcamLoader(self);
397
        dataUpdater = new DataUpdater();
398
        dataUpdater.start();
399
        webcamLoader.start();
400
        Runnable r = new Runnable() {
401
                public void run () {
402
                        btnConnect.setText("Disconnect");
403
                        lblConnectionStatus.setText("Status: Connected");
404
                btnF.setEnabled(true);
405
                btnB.setEnabled(true);
406
                btnL.setEnabled(true);
407
                btnR.setEnabled(true);
408
                btnActivate.setEnabled(true);
409
                btnAssignID.setEnabled(true);
410
                btnSetBounds.setEnabled(true);
411
                btnCommand_MoveTo.setEnabled(true);
412
                btnCommand_MoveAll.setEnabled(true);
413
                    }
414
                };
415
                SwingUtilities.invokeLater(r);
416
        }
417

    
418
        public void disconnect () {
419
        try {
420
            dataUpdater.interrupt();
421
        } catch (Exception e) {
422
        }
423
        csi = null;
424
                Runnable r = new Runnable() {
425
                public void run () {
426
                        btnConnect.setText("Connect");
427
                    lblConnectionStatus.setText("Status: Disconnected");
428
                btnF.setEnabled(false);
429
                btnB.setEnabled(false);
430
                btnL.setEnabled(false);
431
                btnR.setEnabled(false);
432
                btnActivate.setEnabled(false);
433
                btnAssignID.setEnabled(false);
434
                btnSetBounds.setEnabled(false);
435
                btnCommand_MoveTo.setEnabled(false);
436
                btnCommand_MoveAll.setEnabled(false);
437
                    }
438
                };
439
                SwingUtilities.invokeLater(r);
440
        }
441

    
442
        /**
443
        * Parses a String containing a task queue update.
444
        * Format is currently not specified.
445
        * This method currently does nothing.
446
        *
447
        * @param line the String containing task queue update information.
448
        */
449
        public void parseQueue (String line) {
450

    
451
        }
452

    
453
        /**
454
        * Parses a String containing XBee ID values.
455
        * The ColonetServerInterface receives Strings of XBee information.        (For encoding
456
        * information, see the ColonetServerInterface documentation.)         This method takes
457
        * a string of the form "[command code] [command code] [number of robots] [id0] [id1] ..."
458
        * with tokens separated by spaces and containing no brackets.
459
        * The [command code]s are predefined values identifying this String as an XBee
460
        * ID String, [number of robots] is an integer, and the values that follow are
461
        * the IDs of the robots in order, starting with robot 0.        Only [number of robots]
462
        * will be read.         The ID values are saved locally until the next String is parsed.
463
        * The purpose of having this list is to ensure that robots are properly identified for control purposes.
464
        * This keeps robot identification consistent between sessions and prevents arbitrary assignment.
465
        *
466
        * @param line the String containing XBee ID information.
467
        * @throws ArrayIndexOutOfBoundsException if there are fewer than [number of robots] IDs in the String
468
        * @see ColonetServerInterface#sendXBeeIDRequest()
469
        */
470
        public void parseXBeeIDs (String line) {
471
                String [] str = line.split(" ");
472
                int num = Integer.parseInt(str[2]);
473
                xbeeID = new int[num];
474
                for (int i = 0; i < num; i++) {
475
                        xbeeID[i] = Integer.parseInt(str[i+3]);
476
                }
477
        }
478

    
479
        /**
480
        * Parses a String containing battery information.
481
        * The ColonetServerInterface receives Strings of battery information.         (For encoding
482
        * information, see the ColonetServerInterface documentation.)         This method takes
483
        * a string of the form "[command code] [command code] [robot ID] [value]"
484
        * with tokens separated by spaces and containing no brackets.
485
        * The [command code]s are predefined values identifying this String as a battery
486
        * information String, [robot ID] is an integer, and [value] is a battery measurement.
487
        * This updates the batery information for a single robot.
488
        *
489
        *
490
        * @param line the String containing battery information.
491
        * @see ColonetServerInterface#sendBatteryRequest(int)
492
        */
493
        public void parseBattery (String line) {
494
            int botNum, level;
495
            System.out.println("Got battery update:" + line);
496
            try {
497
                    String [] str = line.split(" ");
498
                    botNum = Integer.parseInt(str[2]);
499
                    level = Integer.parseInt(str[3]);
500
                } catch (NumberFormatException e) {
501
                    System.out.println("Could not parse battery update");
502
                    return;
503
                }
504

    
505
                RobotIcon r = robotIcons.get(botNum);
506
                if (r != null) {
507
                    r.battery = batteryIcon.convert(level);  // set contextual battery meter
508
                    batteryIcon.setLevel(level);             // set solo battery meter
509
                }
510
                super.repaint();
511
        }
512

    
513
        /**
514
        * Parses a String containing visual robot position information along with
515
        * canonical ID assignments.
516
        */
517
        public void parsePositions (String line) {
518
                String [] str = line.split(" ");
519
                RobotList newList = new RobotList();
520

    
521
                for (int i = 2; i < str.length; i+=3) {
522
                        int id = Integer.parseInt(str[i]);
523
                        int x = Integer.parseInt(str[i+1]);
524
                        int y = Integer.parseInt(str[i+2]);
525
                        RobotIcon newIcon = new RobotIcon(id, x, y);
526
                        // Save previous robot information
527
                        RobotIcon oldIcon = robotIcons.get(id);
528
                        if (oldIcon != null) {
529
                            newIcon.battery = oldIcon.battery;
530
                                newIcon.destx = oldIcon.destx;
531
                                newIcon.desty = oldIcon.desty;
532
                        }
533
                        if (newIcon.id >= 0) {
534
                                newIcon.color = Color.GREEN;
535
                        }
536
                        newList.put(id, newIcon);
537
                }
538
                robotIcons = newList;
539
                repaint();
540
        }
541
        
542
        /**
543
        * Set the ID of the selected robot
544
        */
545
        public void setSelectedBot (int id) {
546
            this.selectedBot = id;
547
        }
548
        
549
        /**
550
        * Returns the ID of the selected robot
551
        */
552
        public int getSelectedBot () {
553
            return this.selectedBot;
554
        }
555

    
556
        //
557
        // MouseListener methods
558
        //
559
        public void mousePressed(MouseEvent e) {
560
                //Start a new Thread to handle the MouseEvent
561
                (new MouseHandler(e)).start();
562
                repaint();
563
        }
564
        public void mouseExited(MouseEvent e) {
565
        }
566
        public void mouseEntered(MouseEvent e) {
567
        }
568
        public void mouseReleased(MouseEvent e) {
569
    (new MouseHandler(e)).start();
570
                repaint();
571
        }
572
        public void mouseClicked(MouseEvent e) {
573
        }
574
        public void mouseDragged(MouseEvent e) {
575
    (new MouseHandler(e)).start();
576
                repaint();
577
        }
578
        public void mouseMoved(MouseEvent e) {
579
        }
580

    
581
        //
582
        // KeyListener methods
583
        //
584
        public void keyPressed (KeyEvent e) {
585
                //Start a new Thread to handle the KeyEvent
586
                (new KeyHandler(e)).start();
587
                repaint();
588
        }
589
        public void keyReleased (KeyEvent e) {
590
        }
591
        public void keyTyped (KeyEvent e) {
592
        }
593

    
594
        //
595
        // ActionListener method
596
        //
597
        public void actionPerformed (ActionEvent e) {
598
                // Start a new Thread to handle the ActionEvent
599
                (new ActionHandler(e)).start();
600
                repaint();
601
        }
602

    
603
        class MouseHandler extends Thread {
604
                MouseEvent e;
605

    
606
                public MouseHandler (MouseEvent event) {
607
                        super("MouseHandler");
608
                        this.e = event;
609
                }
610

    
611
                public void run () {
612
                        Point pt = panelWebcam.convertClick(e);
613

    
614
                        // If we are selecting a waypoint (destination) for a specific bot
615
                        if (setWaypoint && setWaypointID >= 0 && e.getID() == MouseEvent.MOUSE_PRESSED) {
616
                                setWaypoint = false;
617
                                panelWebcam.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
618
                                if (selectedBot < 0) {
619
                                        return;
620
                                }
621
                                
622
                                RobotIcon r = robotIcons.get(selectedBot);
623
                                if (r != null) {
624
                                        r.destx = pt.x;
625
                                        r.desty = pt.y;
626
                                        if (csi != null) {
627
                                                csi.sendAbsoluteMove(r.id, r.destx, r.desty);
628
                                        }
629
                                }
630
                                return;
631
                        }
632

    
633
                        // Right-click also means we are moving a robot
634
                        if ((e.getButton() == MouseEvent.BUTTON2 || e.getButton() == MouseEvent.BUTTON3)  
635
          && e.getID() == MouseEvent.MOUSE_PRESSED) {
636
                                if (selectedBot < 0) {
637
                                        return;
638
                                }
639

    
640
                                RobotIcon r = robotIcons.get(selectedBot);
641
                                if (r != null) {
642
                                        r.destx = pt.x;
643
                                        r.desty = pt.y;
644
                                        if (csi != null) {
645
                                                csi.sendAbsoluteMove(r.id, r.destx, r.desty);
646
                                        }
647
                                }
648
                                return;
649
                        }
650

    
651
                        // If we are setting all waypoints
652
                        if (setWaypoint && e.getID() == MouseEvent.MOUSE_PRESSED) {
653
                                setWaypoint = false;
654
                                panelWebcam.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
655
                                for (Map.Entry<Integer,RobotIcon> entry : robotIcons.entrySet()) {
656
                                        RobotIcon r = entry.getValue();
657
                                        r.destx = pt.x;
658
                                        r.desty = pt.y;
659
                                }
660
                                return;
661
                        }
662
      
663
      // If we are drawing a boundary rectangle
664
      if (boundary.set) {
665
        // Begin 
666
        if (e.getID() == MouseEvent.MOUSE_PRESSED) {
667
          boundary.panel_p1 = new Point(e.getX(), e.getY());
668
          boundary.panel_p2 = new Point(e.getX(), e.getY());
669
          boundary.img_p1 = new Point(pt.x, pt.y);
670
        }
671
        // Resize
672
        else if (e.getID() == MouseEvent.MOUSE_DRAGGED){
673
          boundary.panel_p2 = new Point(e.getX(), e.getY());
674
        }
675
        // Finish
676
        else if (e.getID() == MouseEvent.MOUSE_RELEASED){
677
          boundary.panel_p2 = new Point(e.getX(), e.getY());
678
          boundary.img_p2 = new Point(pt.x, pt.y);
679
          panelWebcam.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
680
          boundary.set = false;
681
          boundary.active = true;
682
          boundary.sendToServer();
683
        }
684
        return;
685
      }
686

    
687
                        // Otherwise, we are selecting a bot, or doing nothing
688
                        RobotIcon r = robotIcons.getBoundingIcon(pt);
689
      if (r != null)
690
        selectedBot = r.id;
691
                }
692
        }
693

    
694
        class KeyHandler extends Thread {
695
                KeyEvent e;
696

    
697
                public KeyHandler (KeyEvent event) {
698
                        super("KeyHandler");
699
                        this.e = event;
700
                }
701

    
702
                public void run () {
703
                        int code = e.getKeyCode();
704
                        if (code == KeyEvent.VK_UP) {
705
                                vectorController.setMaxForward();
706
                                vectorController.sendToServer();
707
                        } else if (code == KeyEvent.VK_DOWN) {
708
                                vectorController.setMaxReverse();
709
                                vectorController.sendToServer();
710
                        } else if (code == KeyEvent.VK_LEFT) {
711
                                vectorController.setMaxLeft();
712
                                vectorController.sendToServer();
713
                        } else if (code == KeyEvent.VK_RIGHT) {
714
                                vectorController.setMaxRight();
715
                                vectorController.sendToServer();
716
                        } else if (code == KeyEvent.VK_S) {
717
                                vectorController.setZero();
718
                                vectorController.sendToServer();
719
                        }
720
                }
721
        }
722

    
723
        class ActionHandler extends Thread {
724
                ActionEvent e;
725

    
726
                public ActionHandler (ActionEvent event) {
727
                        super("ActionHandler");
728
                        this.e = event;
729
                }
730

    
731
                public void run () {
732
                        Object source = e.getSource();
733

    
734
                        // General Actions
735
                        if (source == btnConnect) {
736
                                if (csi == null) {
737
                                        connect();
738
                                } else {
739
                                        disconnect();
740
                                }
741
                        } else if (source == btnGetXBeeIDs) {
742
                                csi.sendXBeeIDRequest();
743
                        } else if (source == btnAssignID) {
744
                                String message;
745
                                if (selectedBot < 0) {
746
                                        message = "That robot is unidentified. Please specify its ID.";
747
                                } else {
748
                                        message = "That robot has ID " + selectedBot + ". You may reassign it now.";
749
                                }
750
                                String result = JOptionPane.showInputDialog(self, message, "Robot Identification", JOptionPane.QUESTION_MESSAGE);
751
                                if (result == null) {
752
                                        return;
753
                                }
754
                                int newID = -1;
755
                                try {
756
                                        newID = Integer.parseInt(result);
757
                                } catch (Exception ex) {
758
                                        csi.warn("Invalid ID.");
759
                                        return;
760
                                }
761
                                // Assign new ID and update display
762
                                if (csi != null) {
763
                                        csi.sendIDAssignment(selectedBot, newID);
764
                                }
765
                                selectedBot = newID;
766
                                lblSelected.setText("" + newID);
767
                        } else if (source == btnF) { // Robot Movement Controls
768
                                vectorController.setMaxForward();
769
                                vectorController.sendToServer();
770
                                robotIcons.cancelMoveTo(selectedBot);
771
                        } else if (source == btnB) {
772
                                vectorController.setMaxReverse();
773
                                vectorController.sendToServer();
774
                                robotIcons.cancelMoveTo(selectedBot);
775
                        } else if (source == btnL) {
776
                                vectorController.setMaxLeft();
777
                                vectorController.sendToServer();
778
                                robotIcons.cancelMoveTo(selectedBot);
779
                        } else if (source == btnR) {
780
                                vectorController.setMaxRight();
781
                                vectorController.sendToServer();
782
                                robotIcons.cancelMoveTo(selectedBot);
783
                        } else if (source == btnActivate) {
784
                                vectorController.setZero();
785
                                vectorController.sendToServer();
786
                                robotIcons.cancelMoveTo(selectedBot);
787
      } else if (source == btnSetBounds) {
788
        boundary.set = true;
789
        panelWebcam.setCursor(new Cursor(Cursor.CROSSHAIR_CURSOR));
790
                        } else if (source == btnCommand_MoveTo) {
791
                                if (selectedBot < 0) {
792
                                        return;
793
                                }
794
                                panelWebcam.setCursor(new Cursor(Cursor.CROSSHAIR_CURSOR));
795
                                setWaypoint = true;
796
                                setWaypointID = selectedBot;
797
                        } else if (source == btnCommand_MoveAll) {
798
                                panelWebcam.setCursor(new Cursor(Cursor.CROSSHAIR_CURSOR));
799
                                setWaypoint = true;
800
                                setWaypointID = -1;
801
                        } else if (source == btnCommand_StopTask) {
802

    
803
                        } else if (source == btnCommand_ResumeTask) {
804

    
805
                        } else if (source == btnCommand_ChargeNow) {
806

    
807
                        } else if (source == btnCommand_StopCharging) {
808

    
809
                        } else if (source == btnAddTask) { // Queue Management
810
                                taskAddWindow.prompt();
811
                        } else if (source == btnRemoveTask) {
812
                                if (taskList.getSelectedIndex() >= 0) {
813
                                        csi.sendQueueRemove(taskList.getSelectedIndex());
814
                                }
815
                                csi.sendQueueUpdate();
816
                        } else if (source == btnMoveTaskUp) {
817
                                csi.sendQueueReorder(taskList.getSelectedIndex(), taskList.getSelectedIndex() - 1);
818
                                csi.sendQueueUpdate();
819
                        } else if (source == btnMoveTaskDown) {
820
                                csi.sendQueueReorder(taskList.getSelectedIndex(), taskList.getSelectedIndex() + 1);
821
                                csi.sendQueueUpdate();
822
                        } else if (source == btnUpdateTasks) {
823
                                csi.sendQueueUpdate();
824
                        }
825

    
826
                }
827
        }
828

    
829
        /*
830
        * DataUpdater thread.
831
        *        The purpose of this thread is to request data from the server at regular intervals.
832
        *        Types of data requested: XBee IDs, robot positions, battery levels.
833
        *
834
        */
835
        class DataUpdater extends Thread {
836
                final int DATAUPDATER_DELAY = 200;
837
                int count;
838
                public DataUpdater () {
839
                        super("Colonet DataUpdater");
840
                        count = 0;
841
                }
842

    
843
                public synchronized void run () {
844
                        while (true) {
845
                                try {
846
                                        if (csi != null && csi.isReady()) {
847
                                                // XBee ID Request
848
                                                if (count % 5 == 0) {
849
                                                        csi.sendXBeeIDRequest();
850
                                                }
851
                                                // Robot Position Request
852
                                                if (count % 1 == 0) {
853
                                                        csi.sendPositionRequest();
854
                                                }
855
                                                // Battery Request
856
                                                if (count % 30 == 0) {
857
                                                        for (Map.Entry<Integer,RobotIcon> entry : robotIcons.entrySet()) {
858
                                                                RobotIcon r = entry.getValue();
859
                                                                        int id = r.id;
860
                                                                                if (id >= 0) {
861
                                                                                csi.sendBatteryRequest(id);
862
                                                                                System.out.println("Sent battery request (" + id + ")");
863
                                                                        }
864
                                                        }
865
                                                }
866
                                        }
867
                                        count++;
868
                                        if (count > 1000)
869
                                                count = 0;
870
                                        Thread.sleep(DATAUPDATER_DELAY);
871
                                } catch (InterruptedException e) {
872
                                        return;
873
                                } catch (NullPointerException e) {
874
                                    // This probably indicates that an object was destroyed by shutdown.
875
                                    return;
876
                                }
877
                        }
878
                }
879
        }
880

    
881

    
882
        /*
883
        * WebcamPanel class
884
        * Enables more efficient image handling in a component-controlled environment
885
        */
886
        class WebcamPanel extends JPanel {
887
                final int BORDER = 16;        // this is arbitrary. it makes the image look nice inside a border.
888
                final int BATTERY_WIDTH = 50;
889
                final int BATTERY_HEIGHT = 10;
890
                volatile BufferedImage img;
891
                BufferedImage buffer;
892

    
893
                public WebcamPanel () {
894
                        super(true);
895
                }
896

    
897
                public synchronized void setImage (BufferedImage newimg) {
898
                        if (img != null) {
899
                                img.flush();
900
                        }
901
                        System.gc();
902
                        img = newimg;
903
                        repaint();
904
                }
905

    
906
                public synchronized void paint (Graphics g) {
907
                        if (img == null) {
908
                                return;
909
                        }
910

    
911
                        // Calculate scaling
912
                        int maxWidth = getWidth() - 2*BORDER;
913
                        int maxHeight = getHeight() - 2*BORDER;
914
                        double widthRatio = 1.0 * maxWidth / img.getWidth();
915
                        double heightRatio = 1.0 * maxHeight / img.getHeight();
916
                        double scale = 0;
917
                        int newWidth = 0;
918
                        int newHeight = 0;
919
                        int x = 0;
920
                        int y = 0;
921

    
922
                        if (widthRatio > heightRatio) {         //height is the limiting factor
923
                                scale = heightRatio;
924
                                newHeight = maxHeight;
925
                                newWidth = (int) (img.getWidth() * scale);
926
                                y = BORDER;
927
                                x = (maxWidth - newWidth) / 2 + BORDER;
928
                        } else {        //width is the limiting factor
929
                                scale = widthRatio;
930
                                newWidth = maxWidth;
931
                                newHeight = (int) (img.getHeight() * scale);
932
                                x = BORDER;
933
                                y = (maxHeight - newHeight) / 2 + BORDER;
934
                        }
935

    
936
                        // Draw image onto the buffer
937
                        buffer = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB);
938
                        Graphics2D bufferedGraphics = (Graphics2D)buffer.getGraphics();
939
                        bufferedGraphics.setColor(Color.GRAY);
940
                        bufferedGraphics.fillRect(0, 0, this.getWidth(), this.getHeight());
941
                        Image imgScaled = img.getScaledInstance(newWidth, newHeight, Image.SCALE_FAST);
942
                        bufferedGraphics.drawImage(imgScaled, x, y, this);
943

    
944
            // Draw boundary
945
            if (boundary.set || boundary.active) {
946
               bufferedGraphics.setColor(Color.BLUE);
947
               int width = boundary.panel_p2.x - boundary.panel_p1.x;
948
               int height = boundary.panel_p2.y - boundary.panel_p1.y;
949
               bufferedGraphics.drawRect(boundary.panel_p1.x, boundary.panel_p1.y, width, height);
950
            }
951

    
952
                        // Draw Identifiers and battery levels
953
                        if (robotIcons != null) {
954
                                bufferedGraphics.setStroke(new BasicStroke(2));
955
                                for (Map.Entry<Integer,RobotIcon> entry : robotIcons.entrySet()) {
956
                                        RobotIcon r = entry.getValue();
957
                                        bufferedGraphics.setColor(r.color);
958
                                        // Identifier circle
959
                                        int px = (int) (x + r.x * scale);
960
                                        int py = (int) (y + r.y * scale);
961
                                        bufferedGraphics.drawOval(px-RADIUS, py-RADIUS, 2*r.RADIUS, 2*r.RADIUS);
962
                                        // ID, if applicable
963
                                        if (r.id > 0) {
964
                                            bufferedGraphics.setFont(new Font("arial", Font.PLAIN, 36));
965
                                            bufferedGraphics.drawString("" + r.id, px-10, py+10);
966
                                        }
967
                                        // Battery
968
                                        if (r.battery >= 0) {
969
                                            int pixels = r.battery * BATTERY_WIDTH / 100;
970
                                                if (r.battery > 50)
971
                                                    bufferedGraphics.setColor(Color.GREEN);
972
                                                else if (r.battery > 25)
973
                                                    bufferedGraphics.setColor(Color.YELLOW);
974
                                                else
975
                                                    bufferedGraphics.setColor(Color.RED);
976
                                                bufferedGraphics.fillRect(px+20, py+20, pixels, BATTERY_HEIGHT);
977
                                                bufferedGraphics.setColor(Color.BLACK);
978
                                                bufferedGraphics.drawRect(px+20, py+20, BATTERY_WIDTH, BATTERY_HEIGHT);
979
                                        }
980
                                        // If the robot has a destination, draw the vector
981
                                        if (r.destx >= 0) {
982
                                                bufferedGraphics.drawLine(px, py, (int)(x + r.destx * scale), (int)(y + r.desty * scale));
983
                                        }
984
                                }
985
                        }
986

    
987
                        // Identify currently-selected robot
988
                        RobotIcon r = robotIcons.get(selectedBot);
989
                        if (r != null) {
990
                                int px = (int) (x + r.x * scale);
991
                                int py = (int) (y + r.y * scale);
992
                                bufferedGraphics.setColor(Color.BLACK);
993
                                bufferedGraphics.drawOval(px-RADIUS-6, py-RADIUS-6, 2*r.RADIUS+12, 2*r.RADIUS+12);
994
                        }
995

    
996
                        //Display buffered content
997
                        g.drawImage(buffer, 0, 0, this);
998
                }
999

    
1000
                /**
1001
                * Convert a click on the webcam panel to a coordinate that is consistent with the
1002
                * original size of the image that the panel contains.
1003
                */
1004
                public Point convertClick (MouseEvent e) {
1005
                        if (img == null) {
1006
                                return new Point(e.getX(), e.getY());
1007
                        }
1008

    
1009
                        // Calculate scaling
1010
                        int clickx = e.getX();
1011
                        int clicky = e.getY();
1012
                        int maxWidth = getWidth() - 2*BORDER;
1013
                        int maxHeight = getHeight() - 2*BORDER;
1014
                        double widthRatio = 1.0 * maxWidth / img.getWidth();
1015
                        double heightRatio = 1.0 * maxHeight / img.getHeight();
1016
                        double scale = 0;
1017
                        int newWidth = 0;
1018
                        int newHeight = 0;
1019
                        int px = 0;
1020
                        int py = 0;
1021

    
1022
                        if (widthRatio > heightRatio) {         //height is the limiting factor
1023
                                scale = heightRatio;
1024
                                newHeight = maxHeight;
1025
                                newWidth = (int) (img.getWidth() * scale);
1026
                                py = clicky - BORDER;
1027
                                px = clickx - BORDER - (maxWidth - newWidth) / 2;
1028
                        } else {        //width is the limiting factor
1029
                                scale = widthRatio;
1030
                                newWidth = maxWidth;
1031
                                newHeight = (int) (img.getHeight() * scale);
1032
                                px = clickx - BORDER;
1033
                                py = clicky - BORDER - (maxHeight - newHeight) / 2;
1034
                        }
1035
                        py = (int) (py / scale);
1036
                        px = (int) (px / scale);
1037
                        return new Point(px, py);
1038
                }
1039
                
1040
        }
1041

    
1042
        /*
1043
        * WebcamLoader class
1044
        * Handles the loading of the webcam image.
1045
        */
1046
        class WebcamLoader extends Thread
1047
        {
1048
                final int WEBCAMLOADER_DELAY = 100;
1049
                final String IMAGE_PATH = "http://roboclub9.frc.ri.cmu.edu/colonet.jpg";
1050

    
1051
                URL imagePath;
1052

    
1053
                MediaTracker mt;
1054
                BufferedImage image;
1055
                Random rand;
1056

    
1057
                public WebcamLoader (JApplet applet)
1058
                {
1059
                        super("ColonetWebcamLoader");
1060
                        mt = new MediaTracker(applet);
1061
                        ImageIO.setUseCache(false);
1062
                        rand = new Random();
1063
                }
1064

    
1065
                public void run ()
1066
                {
1067
                        while (true) {
1068
                                try {
1069
                                        Thread.sleep(WEBCAMLOADER_DELAY);
1070
                                        if (image != null)
1071
                                                image.flush();
1072
                                        System.gc();
1073
                                        try {
1074
                                                imagePath = new URL(IMAGE_PATH + "?rand=" + rand.nextInt(100000));
1075
                                        } catch (MalformedURLException e) {
1076
                                                System.out.println("Malformed URL: could not form URL from: [" + IMAGE_PATH + "]\n");
1077
                                        }
1078
                                        image = ImageIO.read(imagePath);
1079
                                        // The MediaTracker waitForID pauses the thread until the image is loaded.
1080
                                        // We don't want to display a half-downloaded image.
1081
                                        mt.addImage(image, 1);
1082
                                        while(!mt.checkID(1))
1083
                                                Thread.sleep(20);
1084
                                        mt.removeImage(image);
1085
                                        // Save
1086
                                        panelWebcam.setImage(image);
1087
                                } catch (InterruptedException e) {
1088
                                        return;
1089
                                } catch (java.security.AccessControlException e) {
1090
                                        csi.warn("Could not load webcam.\n" + e);
1091
                                        return;
1092
                                } catch (IOException e) {
1093
                                        getInfoPanel().append("IOException while trying to load image.");
1094
                                }
1095
                        }
1096
                }
1097
        }
1098
  
1099
  class RobotBoundary {
1100
    public volatile boolean active, set;
1101
    public volatile Point img_p1, img_p2;
1102
    public volatile Point panel_p1, panel_p2;
1103
    
1104
    public RobotBoundary () {
1105
      active = false;
1106
      set = false;
1107
      img_p1 = new Point(-1,-1);
1108
      img_p2 = new Point(-1,-1);
1109
      panel_p1 = new Point(-1,-1); 
1110
      panel_p2 = new Point(-1,-1);
1111
    }
1112
    
1113
    public void sendToServer () {
1114
        if (csi != null) {
1115
            csi.sendBoundary(img_p1.x, img_p1.y, img_p2.x, img_p2.y);
1116
        }
1117
    }
1118
  }
1119

    
1120
}