root / trunk / code / projects / colonet / client / Colonet.java @ 758
History | View | Annotate | Download (37.6 KB)
1 | 32 | gtress | import javax.swing.*; |
---|---|---|---|
2 | 320 | gtress | import javax.swing.event.*; |
3 | import javax.imageio.*; |
||
4 | 32 | gtress | import java.awt.*; |
5 | import java.awt.image.*; |
||
6 | import java.awt.event.*; |
||
7 | import java.net.*; |
||
8 | import java.io.*; |
||
9 | 427 | gtress | import java.util.*; |
10 | 32 | gtress | |
11 | 320 | gtress | /**
|
12 | 531 | emarinel | * The Colonet Graphical User Interface Applet for use locally and over an internet connection.
|
13 | * @author Gregory Tress
|
||
14 | 527 | emarinel | *
|
15 | 531 | emarinel | * 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 | 320 | gtress | */
|
18 | 671 | gtress | public class Colonet extends JApplet implements ActionListener, MouseInputListener, KeyListener { |
19 | 320 | gtress | |
20 | 32 | gtress | // Connection
|
21 | 527 | emarinel | JTextField txtHost;
|
22 | JTextField txtPort;
|
||
23 | JButton btnConnect;
|
||
24 | 414 | gtress | JButton btnGetXBeeIDs;
|
25 | 32 | gtress | JLabel lblConnectionStatus;
|
26 | 527 | emarinel | JTextArea txtInfo;
|
27 | 32 | gtress | JPanel panelConnect;
|
28 | JPanel panelServerInterface;
|
||
29 | 527 | emarinel | Socket socket;
|
30 | DataUpdater dataUpdater; |
||
31 | |||
32 | 32 | gtress | // Control
|
33 | JTabbedPane tabPaneControl;
|
||
34 | JPanel panelRobotControl;
|
||
35 | JPanel panelRobotDirection;
|
||
36 | 320 | gtress | JPanel panelRobotDirectionButtons;
|
37 | 32 | gtress | JPanel panelRobotCommands;
|
38 | JButton btnF, btnB, btnL, btnR, btnActivate;
|
||
39 | 320 | gtress | JLabel lblBattery;
|
40 | 428 | gtress | JLabel lblSelected;
|
41 | 333 | gtress | BatteryIcon batteryIcon; |
42 | 320 | gtress | JPanel panelBattery;
|
43 | VectorController vectorController; |
||
44 | BufferedImage imageVectorControl;
|
||
45 | 427 | gtress | JButton btnAssignID;
|
46 | 702 | gtress | JButton btnLocateStation;
|
47 | boolean setStation;
|
||
48 | ChargingStation station; |
||
49 | 428 | gtress | boolean setWaypoint;
|
50 | 429 | gtress | int setWaypointID;
|
51 | 739 | gtress | JButton btnSetBounds;
|
52 | JButton btnClearBounds;
|
||
53 | RobotBoundary boundary; |
||
54 | 428 | gtress | JButton btnCommand_MoveTo;
|
55 | 429 | gtress | JButton btnCommand_MoveAll;
|
56 | 320 | gtress | JButton btnCommand_StopTask;
|
57 | JButton btnCommand_ResumeTask;
|
||
58 | JButton btnCommand_ChargeNow;
|
||
59 | JButton btnCommand_StopCharging;
|
||
60 | 527 | emarinel | |
61 | 32 | gtress | // Task Manager
|
62 | JPanel panelTaskManager;
|
||
63 | JScrollPane spTaskManager;
|
||
64 | JPanel panelTaskManagerControls;
|
||
65 | JPanel panelTaskManagerControlsPriority;
|
||
66 | DefaultListModel taskListModel;
|
||
67 | JList taskList;
|
||
68 | JButton btnAddTask;
|
||
69 | JButton btnRemoveTask;
|
||
70 | JButton btnMoveTaskUp;
|
||
71 | JButton btnMoveTaskDown;
|
||
72 | 320 | gtress | JButton btnUpdateTasks;
|
73 | TaskAddWindow taskAddWindow; |
||
74 | 527 | emarinel | |
75 | 585 | gtress | // Webcam
|
76 | 320 | gtress | WebcamPanel panelWebcam; |
77 | 32 | gtress | GraphicsConfiguration gc;
|
78 | 320 | gtress | JTabbedPane tabPaneMain;
|
79 | 585 | gtress | WebcamLoader webcamLoader; |
80 | |||
81 | // Robots
|
||
82 | 531 | emarinel | volatile int selectedBot; //the user has selected this bot graphically |
83 | 671 | gtress | volatile RobotList robotIcons; //contains boundary shapes around bots for click detection |
84 | 320 | gtress | volatile int[] xbeeID; |
85 | 527 | emarinel | |
86 | 501 | gtress | Colonet self = this;
|
87 | 549 | gtress | volatile ColonetServerInterface csi;
|
88 | 32 | gtress | |
89 | public void init () { |
||
90 | 701 | gtress | // Set the default look and feel
|
91 | String laf = ColonetConstants.LOOK_AND_FEEL;
|
||
92 | 531 | emarinel | try {
|
93 | UIManager.setLookAndFeel(laf);
|
||
94 | } catch (UnsupportedLookAndFeelException exc) { |
||
95 | System.err.println ("Warning: UnsupportedLookAndFeel: " + laf); |
||
96 | } catch (Exception exc) { |
||
97 | System.err.println ("Error loading " + laf + ": " + exc); |
||
98 | } |
||
99 | |||
100 | 32 | gtress | // We should invoke and wait to avoid browser display difficulties
|
101 | Runnable r = new Runnable() { |
||
102 | public void run() { |
||
103 | createAndShowGUI(); |
||
104 | } |
||
105 | }; |
||
106 | 531 | emarinel | |
107 | 32 | gtress | try {
|
108 | SwingUtilities.invokeAndWait(r);
|
||
109 | } catch (InterruptedException e) { |
||
110 | //Not really sure why we would be in this situation
|
||
111 | 320 | gtress | System.out.println("InterruptedException in init: " + e); |
112 | 32 | gtress | } catch (java.lang.reflect.InvocationTargetException e) {
|
113 | 320 | gtress | //This could happen for various reasons if there is a problem in createAndShowGUI
|
114 | e.printStackTrace(); |
||
115 | 32 | gtress | } |
116 | } |
||
117 | 527 | emarinel | |
118 | 32 | gtress | public void destroy () { |
119 | 633 | emarinel | if (csi != null) { |
120 | csi.disconnect(); |
||
121 | 570 | gtress | } |
122 | 32 | gtress | } |
123 | |||
124 | private synchronized void createAndShowGUI () { |
||
125 | 585 | gtress | // Webcam
|
126 | 527 | emarinel | gc = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
|
127 | 320 | gtress | panelWebcam = new WebcamPanel();
|
128 | tabPaneMain = new JTabbedPane(); |
||
129 | 739 | gtress | tabPaneMain.setFont(new Font("arial", Font.PLAIN, 14)); |
130 | 320 | gtress | tabPaneMain.add(panelWebcam, "Webcam");
|
131 | 527 | emarinel | |
132 | 585 | gtress | // Robots
|
133 | 428 | gtress | selectedBot = -1;
|
134 | 671 | gtress | robotIcons = new RobotList();
|
135 | 527 | emarinel | |
136 | 32 | gtress | // Connection area
|
137 | txtInfo = new JTextArea(); |
||
138 | txtInfo.setBorder(BorderFactory.createTitledBorder("Info")); |
||
139 | 470 | gtress | txtHost = new JTextField(this.getDocumentBase().getHost()); |
140 | 32 | gtress | txtHost.setBorder(BorderFactory.createTitledBorder("Host")); |
141 | txtPort = new JTextField("10123"); |
||
142 | txtPort.setBorder(BorderFactory.createTitledBorder("Port")); |
||
143 | btnConnect = new JButton("Connect"); |
||
144 | 555 | emarinel | btnConnect.setFont(new Font("arial", Font.BOLD, 16)); |
145 | 414 | gtress | btnGetXBeeIDs = new JButton("Get XBee IDs"); |
146 | 389 | gtress | getRootPane().setDefaultButton(btnConnect); |
147 | 32 | gtress | lblConnectionStatus = new JLabel("Status: Offline"); |
148 | panelConnect = new JPanel(); |
||
149 | panelConnect.setLayout(new GridLayout(6,1)); |
||
150 | panelConnect.add(lblConnectionStatus); |
||
151 | panelConnect.add(txtHost); |
||
152 | panelConnect.add(txtPort); |
||
153 | panelConnect.add(btnConnect); |
||
154 | panelServerInterface = new JPanel(); |
||
155 | panelServerInterface.setLayout(new GridLayout(2,1)); |
||
156 | panelServerInterface.add(panelConnect); |
||
157 | 585 | gtress | panelServerInterface.add(txtInfo); |
158 | 527 | emarinel | |
159 | 32 | gtress | // Robot direction panel
|
160 | panelRobotDirection = new JPanel(); |
||
161 | 320 | gtress | panelRobotDirectionButtons = new JPanel(); |
162 | 555 | emarinel | |
163 | 739 | gtress | Font f = new Font(null, Font.PLAIN, 16); |
164 | 555 | emarinel | btnF = new JButton("\u2191"); |
165 | btnF.setFont(f); |
||
166 | btnB = new JButton("\u2193"); |
||
167 | btnB.setFont(f); |
||
168 | btnL = new JButton("\u2190"); |
||
169 | btnL.setFont(f); |
||
170 | btnR = new JButton("\u2192"); |
||
171 | btnR.setFont(f); |
||
172 | btnActivate = new JButton("\u25A0"); |
||
173 | 710 | gtress | btnActivate.setFont(new Font(null, Font.BOLD, 24)); |
174 | 555 | emarinel | |
175 | 320 | gtress | panelRobotDirectionButtons.setLayout(new GridLayout(1,5)); |
176 | panelRobotDirectionButtons.add(btnActivate); |
||
177 | panelRobotDirectionButtons.add(btnF); |
||
178 | panelRobotDirectionButtons.add(btnB); |
||
179 | panelRobotDirectionButtons.add(btnL); |
||
180 | panelRobotDirectionButtons.add(btnR); |
||
181 | 527 | emarinel | |
182 | 701 | gtress | imageVectorControl = gc.createCompatibleImage(ColonetConstants.VECTOR_CONTROLLER_WIDTH, ColonetConstants.VECTOR_CONTROLLER_HEIGHT); |
183 | 638 | gtress | vectorController = new VectorController(imageVectorControl, self);
|
184 | 320 | gtress | panelRobotDirection.setLayout(new BorderLayout()); |
185 | 585 | gtress | panelRobotDirection.add(vectorController, BorderLayout.CENTER);
|
186 | 320 | gtress | panelRobotDirection.add(panelRobotDirectionButtons, BorderLayout.SOUTH);
|
187 | 527 | emarinel | |
188 | 32 | gtress | // Robot Control and Commands
|
189 | panelRobotCommands = new JPanel(); |
||
190 | 739 | gtress | panelRobotCommands.setLayout(new GridLayout(6,2)); |
191 | 333 | gtress | // Battery subset
|
192 | 514 | gtress | batteryIcon = new BatteryIcon(0); |
193 | 333 | gtress | lblBattery = new JLabel(batteryIcon); |
194 | 428 | gtress | lblSelected = new JLabel("None"); |
195 | 664 | gtress | // Management subset
|
196 | 702 | gtress | setStation = false;
|
197 | 428 | gtress | setWaypoint = false;
|
198 | 429 | gtress | setWaypointID = -1;
|
199 | 427 | gtress | btnAssignID = new JButton("Assign ID"); |
200 | 739 | gtress | btnLocateStation = new JButton("Identify Station"); |
201 | boundary = new RobotBoundary();
|
||
202 | btnSetBounds = new JButton("Set Boundary"); |
||
203 | btnClearBounds = new JButton("Clear Boundary"); |
||
204 | // Control subset
|
||
205 | 428 | gtress | btnCommand_MoveTo = new JButton("Move to ..."); |
206 | 429 | gtress | btnCommand_MoveAll = new JButton("Move all ..."); |
207 | 320 | gtress | btnCommand_StopTask = new JButton("Stop Current Task"); |
208 | btnCommand_ResumeTask = new JButton("Resume Current Task"); |
||
209 | btnCommand_ChargeNow = new JButton("Recharge Now"); |
||
210 | btnCommand_StopCharging = new JButton("Stop Recharging"); |
||
211 | panelRobotCommands.add(new JLabel("Battery Level: ")); |
||
212 | panelRobotCommands.add(lblBattery); |
||
213 | 428 | gtress | panelRobotCommands.add(new JLabel("Selected Icon: ")); |
214 | panelRobotCommands.add(lblSelected); |
||
215 | 427 | gtress | panelRobotCommands.add(btnAssignID); |
216 | 702 | gtress | panelRobotCommands.add(btnLocateStation); |
217 | 739 | gtress | panelRobotCommands.add(btnCommand_ChargeNow); |
218 | panelRobotCommands.add(btnCommand_StopCharging); |
||
219 | 664 | gtress | panelRobotCommands.add(btnSetBounds); |
220 | 682 | gtress | panelRobotCommands.add(btnClearBounds); |
221 | 428 | gtress | panelRobotCommands.add(btnCommand_MoveTo); |
222 | 429 | gtress | panelRobotCommands.add(btnCommand_MoveAll); |
223 | 427 | gtress | //panelRobotCommands.add(btnCommand_StopTask);
|
224 | //panelRobotCommands.add(btnCommand_ResumeTask);
|
||
225 | 32 | gtress | panelRobotControl = new JPanel(); |
226 | panelRobotControl.setLayout(new GridLayout(2,1)); |
||
227 | panelRobotControl.add(panelRobotDirection); |
||
228 | panelRobotControl.add(panelRobotCommands); |
||
229 | 527 | emarinel | |
230 | 32 | gtress | // Task Manager
|
231 | panelTaskManager = new JPanel(); |
||
232 | panelTaskManager.setLayout(new BorderLayout()); |
||
233 | taskListModel = new DefaultListModel(); |
||
234 | taskList = new JList(taskListModel); |
||
235 | taskList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||
236 | taskList.setSelectedIndex(0);
|
||
237 | spTaskManager = new JScrollPane(taskList); |
||
238 | panelTaskManagerControls = new JPanel(); |
||
239 | 320 | gtress | panelTaskManagerControls.setLayout(new GridLayout(1,4)); |
240 | 32 | gtress | panelTaskManagerControlsPriority = new JPanel(); |
241 | panelTaskManagerControlsPriority.setLayout(new GridLayout(1,2)); |
||
242 | btnAddTask = new JButton("Add..."); |
||
243 | btnRemoveTask = new JButton("Remove"); |
||
244 | btnMoveTaskUp = new JButton("^"); |
||
245 | btnMoveTaskDown = new JButton("v"); |
||
246 | 320 | gtress | btnUpdateTasks = new JButton("Update"); |
247 | 32 | gtress | panelTaskManagerControlsPriority.add(btnMoveTaskUp); |
248 | panelTaskManagerControlsPriority.add(btnMoveTaskDown); |
||
249 | panelTaskManagerControls.add(btnAddTask); |
||
250 | panelTaskManagerControls.add(btnRemoveTask); |
||
251 | 320 | gtress | panelTaskManagerControls.add(btnUpdateTasks); |
252 | 32 | gtress | panelTaskManagerControls.add(panelTaskManagerControlsPriority); |
253 | panelTaskManager.add(spTaskManager, BorderLayout.CENTER);
|
||
254 | panelTaskManager.add(panelTaskManagerControls, BorderLayout.SOUTH);
|
||
255 | panelTaskManager.add(new JLabel("Current Task Queue"), BorderLayout.NORTH); |
||
256 | 320 | gtress | taskAddWindow = new TaskAddWindow();
|
257 | 527 | emarinel | |
258 | 32 | gtress | // Main control mechanism
|
259 | tabPaneControl = new JTabbedPane(JTabbedPane.TOP); |
||
260 | 739 | gtress | tabPaneControl.setFont(new Font("arial", Font.PLAIN, 14)); |
261 | 701 | gtress | tabPaneControl.setPreferredSize(new Dimension(ColonetConstants.VECTOR_CONTROLLER_WIDTH, 0)); |
262 | 32 | gtress | tabPaneControl.addTab("Connection", panelServerInterface);
|
263 | tabPaneControl.addTab("Robots", panelRobotControl);
|
||
264 | 514 | gtress | //tabPaneControl.addTab("Tasks", panelTaskManager);
|
265 | 527 | emarinel | |
266 | 320 | gtress | // Put all elements in the ContentPane
|
267 | 32 | gtress | this.getContentPane().setLayout(new BorderLayout()); |
268 | 320 | gtress | this.getContentPane().add(tabPaneMain, BorderLayout.CENTER); |
269 | 702 | gtress | this.getContentPane().add(tabPaneControl, BorderLayout.EAST); |
270 | 32 | gtress | this.setVisible(true); |
271 | 559 | gtress | |
272 | // Disable components before connecting
|
||
273 | btnConnect.setText("Connect");
|
||
274 | 682 | gtress | lblConnectionStatus.setText("Status: Disconnected");
|
275 | 702 | gtress | setComponentsEnabled(false);
|
276 | 527 | emarinel | |
277 | 320 | gtress | /* Add all listeners here */
|
278 | // Task Management
|
||
279 | 32 | gtress | btnAddTask.addActionListener(this);
|
280 | btnRemoveTask.addActionListener(this);
|
||
281 | btnMoveTaskUp.addActionListener(this);
|
||
282 | btnMoveTaskDown.addActionListener(this);
|
||
283 | 320 | gtress | btnUpdateTasks.addActionListener(this);
|
284 | // Robot Control
|
||
285 | btnF.addActionListener(this);
|
||
286 | btnB.addActionListener(this);
|
||
287 | btnL.addActionListener(this);
|
||
288 | btnR.addActionListener(this);
|
||
289 | btnF.addKeyListener(this);
|
||
290 | btnB.addKeyListener(this);
|
||
291 | btnL.addKeyListener(this);
|
||
292 | btnR.addKeyListener(this);
|
||
293 | btnActivate.addActionListener(this);
|
||
294 | btnActivate.addKeyListener(this);
|
||
295 | 428 | gtress | btnCommand_MoveTo.addActionListener(this);
|
296 | 429 | gtress | btnCommand_MoveAll.addActionListener(this);
|
297 | 320 | gtress | btnCommand_StopTask.addActionListener(this);
|
298 | btnCommand_ResumeTask.addActionListener(this);
|
||
299 | btnCommand_ChargeNow.addActionListener(this);
|
||
300 | btnCommand_StopCharging.addActionListener(this);
|
||
301 | // Other
|
||
302 | 32 | gtress | btnConnect.addActionListener(this);
|
303 | 414 | gtress | btnGetXBeeIDs.addActionListener(this);
|
304 | 427 | gtress | btnAssignID.addActionListener(this);
|
305 | 702 | gtress | btnLocateStation.addActionListener(this);
|
306 | 682 | gtress | btnSetBounds.addActionListener(this);
|
307 | btnClearBounds.addActionListener(this);
|
||
308 | 527 | emarinel | panelWebcam.addMouseListener(this);
|
309 | 670 | gtress | panelWebcam.addMouseMotionListener(this);
|
310 | 32 | gtress | } |
311 | 638 | gtress | |
312 | public ColonetServerInterface getCSI () {
|
||
313 | 739 | gtress | return csi;
|
314 | 638 | gtress | } |
315 | 527 | emarinel | |
316 | 428 | gtress | public void paint (Graphics g) { |
317 | 531 | emarinel | super.paint(g);
|
318 | 428 | gtress | } |
319 | 527 | emarinel | |
320 | 429 | gtress | public void update (Graphics g) { |
321 | 531 | emarinel | paint(g); |
322 | 429 | gtress | } |
323 | 527 | emarinel | |
324 | /**
|
||
325 | 585 | gtress | * Gets the JTextArea used for displaying debugging information.
|
326 | 333 | gtress | *
|
327 | 585 | gtress | * @return the JTextArea where debugging information can be displayed.
|
328 | 527 | emarinel | */
|
329 | 585 | gtress | public JTextArea getInfoPanel () { |
330 | return txtInfo;
|
||
331 | 320 | gtress | } |
332 | 527 | emarinel | |
333 | /**
|
||
334 | 333 | gtress | * Parses a String containing BOM matrix information.
|
335 | 531 | emarinel | * The ColonetServerInterface receives lines of the BOM matrix. (For encoding
|
336 | * information, see the ColonetServerInterface documentation.) The entire matrix is passed
|
||
337 | 527 | emarinel | * to the client when requested. This method takes a string of the form
|
338 | 333 | gtress | * "[command code] [command code] [number of robots] [data0] [data1] ..."
|
339 | 527 | emarinel | * with tokens separated by spaces and containing no brackets.
|
340 | 333 | gtress | * The [command code]s are predefined values identifying this String as a BOM data
|
341 | 527 | emarinel | * String, [number of robots] is an integer, and the values that follow are
|
342 | 531 | emarinel | * the sensor readings of the robots in order, starting with robot 0. Only [number of robots]^2
|
343 | * data entries will be read. The matrix values are saved locally until the next String is parsed.
|
||
344 | 333 | gtress | *
|
345 | 527 | emarinel | *
|
346 | 333 | gtress | * @param line the String containing BOM matrix information.
|
347 | * @throws ArrayIndexOutOfBoundsException if there are fewer than [number of robots]^2 data entries in the String
|
||
348 | */
|
||
349 | 320 | gtress | public void parseMatrix (String line) { |
350 | 585 | gtress | txtInfo.setText("");
|
351 | 320 | gtress | String [] str = line.split(" "); |
352 | int num = Integer.parseInt(str[2]); |
||
353 | for (int i = 0; i < num; i++) { |
||
354 | for (int j = 0; j < num; j++) { |
||
355 | String next = str[3 + i*num + j]; |
||
356 | 531 | emarinel | if (next.equals("-1")) { |
357 | 585 | gtress | txtInfo.append("-");
|
358 | 531 | emarinel | } else {
|
359 | 585 | gtress | txtInfo.append(next); |
360 | 531 | emarinel | } |
361 | |||
362 | if (j < num - 1) { |
||
363 | 585 | gtress | txtInfo.append(" ");
|
364 | 531 | emarinel | } |
365 | 320 | gtress | } |
366 | 531 | emarinel | |
367 | if (i < num - 1) { |
||
368 | 585 | gtress | txtInfo.append("\n");
|
369 | 531 | emarinel | } |
370 | 136 | gtress | } |
371 | 510 | gtress | repaint(); |
372 | 136 | gtress | } |
373 | 527 | emarinel | |
374 | 420 | gtress | public void connect () { |
375 | 682 | gtress | if (csi != null) |
376 | return;
|
||
377 | csi = new ColonetServerInterface(self);
|
||
378 | csi.connect(txtHost.getText(), txtPort.getText()); |
||
379 | if (!csi.isReady()) {
|
||
380 | csi = null;
|
||
381 | return;
|
||
382 | } |
||
383 | webcamLoader = new WebcamLoader(self);
|
||
384 | dataUpdater = new DataUpdater();
|
||
385 | dataUpdater.start(); |
||
386 | webcamLoader.start(); |
||
387 | Runnable r = new Runnable() { |
||
388 | public void run () { |
||
389 | btnConnect.setText("Disconnect");
|
||
390 | lblConnectionStatus.setText("Status: Connected");
|
||
391 | 702 | gtress | setComponentsEnabled(true);
|
392 | 682 | gtress | } |
393 | }; |
||
394 | SwingUtilities.invokeLater(r);
|
||
395 | 420 | gtress | } |
396 | 527 | emarinel | |
397 | 420 | gtress | public void disconnect () { |
398 | 702 | gtress | try {
|
399 | dataUpdater.interrupt(); |
||
400 | } catch (Exception e) { |
||
401 | } |
||
402 | csi = null;
|
||
403 | 559 | gtress | Runnable r = new Runnable() { |
404 | 702 | gtress | public void run () { |
405 | btnConnect.setText("Connect");
|
||
406 | lblConnectionStatus.setText("Status: Disconnected");
|
||
407 | setComponentsEnabled(false);
|
||
408 | 708 | gtress | boundary = null;
|
409 | station = null;
|
||
410 | 702 | gtress | } |
411 | 559 | gtress | }; |
412 | SwingUtilities.invokeLater(r);
|
||
413 | 420 | gtress | } |
414 | 702 | gtress | |
415 | private void setComponentsEnabled (boolean enabled) { |
||
416 | btnF.setEnabled(enabled); |
||
417 | btnB.setEnabled(enabled); |
||
418 | btnL.setEnabled(enabled); |
||
419 | btnR.setEnabled(enabled); |
||
420 | btnActivate.setEnabled(enabled); |
||
421 | btnAssignID.setEnabled(enabled); |
||
422 | btnLocateStation.setEnabled(enabled); |
||
423 | btnSetBounds.setEnabled(enabled); |
||
424 | btnClearBounds.setEnabled(enabled); |
||
425 | 739 | gtress | btnCommand_ChargeNow.setEnabled(enabled); |
426 | btnCommand_StopCharging.setEnabled(enabled); |
||
427 | 702 | gtress | btnCommand_MoveTo.setEnabled(enabled); |
428 | btnCommand_MoveAll.setEnabled(enabled); |
||
429 | } |
||
430 | 527 | emarinel | |
431 | 333 | gtress | /**
|
432 | * Parses a String containing a task queue update.
|
||
433 | * Format is currently not specified.
|
||
434 | * This method currently does nothing.
|
||
435 | *
|
||
436 | * @param line the String containing task queue update information.
|
||
437 | */
|
||
438 | 320 | gtress | public void parseQueue (String line) { |
439 | 585 | gtress | |
440 | 320 | gtress | } |
441 | 527 | emarinel | |
442 | 333 | gtress | /**
|
443 | * Parses a String containing XBee ID values.
|
||
444 | 531 | emarinel | * The ColonetServerInterface receives Strings of XBee information. (For encoding
|
445 | * information, see the ColonetServerInterface documentation.) This method takes
|
||
446 | 333 | gtress | * a string of the form "[command code] [command code] [number of robots] [id0] [id1] ..."
|
447 | 527 | emarinel | * with tokens separated by spaces and containing no brackets.
|
448 | 333 | gtress | * The [command code]s are predefined values identifying this String as an XBee
|
449 | 527 | emarinel | * ID String, [number of robots] is an integer, and the values that follow are
|
450 | 531 | emarinel | * the IDs of the robots in order, starting with robot 0. Only [number of robots]
|
451 | * will be read. The ID values are saved locally until the next String is parsed.
|
||
452 | 333 | gtress | * The purpose of having this list is to ensure that robots are properly identified for control purposes.
|
453 | 527 | emarinel | * This keeps robot identification consistent between sessions and prevents arbitrary assignment.
|
454 | 333 | gtress | *
|
455 | * @param line the String containing XBee ID information.
|
||
456 | * @throws ArrayIndexOutOfBoundsException if there are fewer than [number of robots] IDs in the String
|
||
457 | * @see ColonetServerInterface#sendXBeeIDRequest()
|
||
458 | */
|
||
459 | 320 | gtress | public void parseXBeeIDs (String line) { |
460 | String [] str = line.split(" "); |
||
461 | int num = Integer.parseInt(str[2]); |
||
462 | xbeeID = new int[num]; |
||
463 | 531 | emarinel | for (int i = 0; i < num; i++) { |
464 | 320 | gtress | xbeeID[i] = Integer.parseInt(str[i+3]); |
465 | 531 | emarinel | } |
466 | 320 | gtress | } |
467 | 527 | emarinel | |
468 | 333 | gtress | /**
|
469 | * Parses a String containing battery information.
|
||
470 | 531 | emarinel | * The ColonetServerInterface receives Strings of battery information. (For encoding
|
471 | * information, see the ColonetServerInterface documentation.) This method takes
|
||
472 | 333 | gtress | * a string of the form "[command code] [command code] [robot ID] [value]"
|
473 | 527 | emarinel | * with tokens separated by spaces and containing no brackets.
|
474 | 333 | gtress | * The [command code]s are predefined values identifying this String as a battery
|
475 | * information String, [robot ID] is an integer, and [value] is a battery measurement.
|
||
476 | * This updates the batery information for a single robot.
|
||
477 | *
|
||
478 | 527 | emarinel | *
|
479 | 333 | gtress | * @param line the String containing battery information.
|
480 | * @see ColonetServerInterface#sendBatteryRequest(int)
|
||
481 | */
|
||
482 | 320 | gtress | public void parseBattery (String line) { |
483 | 739 | gtress | int botNum, level;
|
484 | try {
|
||
485 | 625 | gtress | String [] str = line.split(" "); |
486 | botNum = Integer.parseInt(str[2]); |
||
487 | level = Integer.parseInt(str[3]); |
||
488 | } catch (NumberFormatException e) { |
||
489 | System.out.println("Could not parse battery update"); |
||
490 | return;
|
||
491 | } |
||
492 | 531 | emarinel | |
493 | 561 | gtress | RobotIcon r = robotIcons.get(botNum); |
494 | if (r != null) { |
||
495 | 625 | gtress | r.battery = batteryIcon.convert(level); // set contextual battery meter
|
496 | 638 | gtress | batteryIcon.setLevel(level); // set solo battery meter
|
497 | 425 | gtress | } |
498 | 638 | gtress | super.repaint();
|
499 | 320 | gtress | } |
500 | 527 | emarinel | |
501 | 459 | gtress | /**
|
502 | 527 | emarinel | * Parses a String containing visual robot position information along with
|
503 | 459 | gtress | * canonical ID assignments.
|
504 | */
|
||
505 | public void parsePositions (String line) { |
||
506 | String [] str = line.split(" "); |
||
507 | 671 | gtress | RobotList newList = new RobotList();
|
508 | 527 | emarinel | |
509 | 459 | gtress | for (int i = 2; i < str.length; i+=3) { |
510 | int id = Integer.parseInt(str[i]); |
||
511 | int x = Integer.parseInt(str[i+1]); |
||
512 | int y = Integer.parseInt(str[i+2]); |
||
513 | 470 | gtress | RobotIcon newIcon = new RobotIcon(id, x, y);
|
514 | 646 | gtress | // Save previous robot information
|
515 | RobotIcon oldIcon = robotIcons.get(id); |
||
516 | if (oldIcon != null) { |
||
517 | newIcon.battery = oldIcon.battery; |
||
518 | newIcon.destx = oldIcon.destx; |
||
519 | newIcon.desty = oldIcon.desty; |
||
520 | } |
||
521 | 531 | emarinel | if (newIcon.id >= 0) { |
522 | newIcon.color = Color.GREEN;
|
||
523 | } |
||
524 | 671 | gtress | newList.put(id, newIcon); |
525 | 459 | gtress | } |
526 | 671 | gtress | robotIcons = newList; |
527 | 510 | gtress | repaint(); |
528 | 459 | gtress | } |
529 | 682 | gtress | |
530 | /**
|
||
531 | * Parses a message that indicates a robot has reached its destination
|
||
532 | */
|
||
533 | public void parseMoveUpdate (String line) { |
||
534 | System.out.println("Got move update: " + line); |
||
535 | String [] str = line.split(" "); |
||
536 | int id = Integer.parseInt(str[1]); |
||
537 | robotIcons.cancelMoveTo(id); |
||
538 | } |
||
539 | 671 | gtress | |
540 | /**
|
||
541 | * Set the ID of the selected robot
|
||
542 | */
|
||
543 | public void setSelectedBot (int id) { |
||
544 | this.selectedBot = id;
|
||
545 | } |
||
546 | |||
547 | /**
|
||
548 | * Returns the ID of the selected robot
|
||
549 | */
|
||
550 | public int getSelectedBot () { |
||
551 | return this.selectedBot; |
||
552 | } |
||
553 | 527 | emarinel | |
554 | 32 | gtress | //
|
555 | 320 | gtress | // MouseListener methods
|
556 | 32 | gtress | //
|
557 | 501 | gtress | public void mousePressed(MouseEvent e) { |
558 | 531 | emarinel | //Start a new Thread to handle the MouseEvent
|
559 | (new MouseHandler(e)).start();
|
||
560 | 501 | gtress | } |
561 | 320 | gtress | public void mouseExited(MouseEvent e) { |
562 | } |
||
563 | public void mouseEntered(MouseEvent e) { |
||
564 | } |
||
565 | public void mouseReleased(MouseEvent e) { |
||
566 | 664 | gtress | (new MouseHandler(e)).start();
|
567 | 320 | gtress | } |
568 | public void mouseClicked(MouseEvent e) { |
||
569 | } |
||
570 | public void mouseDragged(MouseEvent e) { |
||
571 | 664 | gtress | (new MouseHandler(e)).start();
|
572 | 320 | gtress | } |
573 | public void mouseMoved(MouseEvent e) { |
||
574 | } |
||
575 | 527 | emarinel | |
576 | 320 | gtress | //
|
577 | // KeyListener methods
|
||
578 | //
|
||
579 | public void keyPressed (KeyEvent e) { |
||
580 | 503 | gtress | //Start a new Thread to handle the KeyEvent
|
581 | 531 | emarinel | (new KeyHandler(e)).start();
|
582 | 579 | gtress | repaint(); |
583 | 181 | gtress | } |
584 | 320 | gtress | public void keyReleased (KeyEvent e) { |
585 | } |
||
586 | public void keyTyped (KeyEvent e) { |
||
587 | } |
||
588 | 527 | emarinel | |
589 | 320 | gtress | //
|
590 | // ActionListener method
|
||
591 | //
|
||
592 | public void actionPerformed (ActionEvent e) { |
||
593 | 501 | gtress | // Start a new Thread to handle the ActionEvent
|
594 | 510 | gtress | (new ActionHandler(e)).start();
|
595 | 579 | gtress | repaint(); |
596 | 501 | gtress | } |
597 | 527 | emarinel | |
598 | 501 | gtress | class MouseHandler extends Thread { |
599 | 531 | emarinel | MouseEvent e;
|
600 | 527 | emarinel | |
601 | 531 | emarinel | public MouseHandler (MouseEvent event) { |
602 | super("MouseHandler"); |
||
603 | this.e = event;
|
||
604 | } |
||
605 | 527 | emarinel | |
606 | 531 | emarinel | public void run () { |
607 | Point pt = panelWebcam.convertClick(e);
|
||
608 | 527 | emarinel | |
609 | 531 | emarinel | // If we are selecting a waypoint (destination) for a specific bot
|
610 | 664 | gtress | if (setWaypoint && setWaypointID >= 0 && e.getID() == MouseEvent.MOUSE_PRESSED) { |
611 | 701 | gtress | // If the user clicks outside the boundary, do nothing
|
612 | if (boundary.isActive() && !boundary.contains(e.getX(), e.getY())) {
|
||
613 | return;
|
||
614 | } |
||
615 | 531 | emarinel | setWaypoint = false;
|
616 | panelWebcam.setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); |
||
617 | if (selectedBot < 0) { |
||
618 | return;
|
||
619 | } |
||
620 | 556 | gtress | |
621 | RobotIcon r = robotIcons.get(selectedBot); |
||
622 | if (r != null) { |
||
623 | r.destx = pt.x; |
||
624 | r.desty = pt.y; |
||
625 | if (csi != null) { |
||
626 | csi.sendAbsoluteMove(r.id, r.destx, r.desty); |
||
627 | 545 | gtress | } |
628 | 539 | emarinel | } |
629 | 531 | emarinel | return;
|
630 | } |
||
631 | 702 | gtress | |
632 | // If we are ordering a charge (right-click on a station)
|
||
633 | if ((e.getButton() == MouseEvent.BUTTON2 || e.getButton() == MouseEvent.BUTTON3) |
||
634 | && e.getID() == MouseEvent.MOUSE_PRESSED
|
||
635 | && station != null && station.contains(e.getX(), e.getY())) {
|
||
636 | 739 | gtress | if (selectedBot >= 0) |
637 | csi.sendRecharge(selectedBot); |
||
638 | 702 | gtress | return;
|
639 | } |
||
640 | 527 | emarinel | |
641 | 531 | emarinel | // Right-click also means we are moving a robot
|
642 | 664 | gtress | if ((e.getButton() == MouseEvent.BUTTON2 || e.getButton() == MouseEvent.BUTTON3) |
643 | && e.getID() == MouseEvent.MOUSE_PRESSED) {
|
||
644 | 701 | gtress | // If the user clicks outside the boundary, do nothing
|
645 | if (boundary.isActive() && !boundary.contains(e.getX(), e.getY())) {
|
||
646 | return;
|
||
647 | } |
||
648 | 531 | emarinel | if (selectedBot < 0) { |
649 | return;
|
||
650 | } |
||
651 | 527 | emarinel | |
652 | 556 | gtress | RobotIcon r = robotIcons.get(selectedBot); |
653 | if (r != null) { |
||
654 | r.destx = pt.x; |
||
655 | r.desty = pt.y; |
||
656 | if (csi != null) { |
||
657 | csi.sendAbsoluteMove(r.id, r.destx, r.desty); |
||
658 | 545 | gtress | } |
659 | 531 | emarinel | } |
660 | return;
|
||
661 | } |
||
662 | 527 | emarinel | |
663 | 531 | emarinel | // If we are setting all waypoints
|
664 | 671 | gtress | if (setWaypoint && e.getID() == MouseEvent.MOUSE_PRESSED) { |
665 | 701 | gtress | // If the user clicks outside the boundary, do nothing
|
666 | if (boundary.isActive() && !boundary.contains(e.getX(), e.getY())) {
|
||
667 | return;
|
||
668 | } |
||
669 | 531 | emarinel | setWaypoint = false;
|
670 | panelWebcam.setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); |
||
671 | 596 | gtress | for (Map.Entry<Integer,RobotIcon> entry : robotIcons.entrySet()) { |
672 | RobotIcon r = entry.getValue(); |
||
673 | 531 | emarinel | r.destx = pt.x; |
674 | r.desty = pt.y; |
||
675 | } |
||
676 | return;
|
||
677 | } |
||
678 | 664 | gtress | |
679 | // If we are drawing a boundary rectangle
|
||
680 | 702 | gtress | if (boundary != null && boundary.isSetting()) { |
681 | 664 | gtress | // Begin
|
682 | if (e.getID() == MouseEvent.MOUSE_PRESSED) { |
||
683 | 670 | gtress | boundary.panel_p1 = new Point(e.getX(), e.getY()); |
684 | boundary.panel_p2 = new Point(e.getX(), e.getY()); |
||
685 | boundary.img_p1 = new Point(pt.x, pt.y); |
||
686 | 664 | gtress | } |
687 | // Resize
|
||
688 | else if (e.getID() == MouseEvent.MOUSE_DRAGGED){ |
||
689 | 670 | gtress | boundary.panel_p2 = new Point(e.getX(), e.getY()); |
690 | 664 | gtress | } |
691 | 670 | gtress | // Finish
|
692 | 664 | gtress | else if (e.getID() == MouseEvent.MOUSE_RELEASED){ |
693 | 670 | gtress | boundary.panel_p2 = new Point(e.getX(), e.getY()); |
694 | boundary.img_p2 = new Point(pt.x, pt.y); |
||
695 | 664 | gtress | panelWebcam.setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); |
696 | boundary.set = false;
|
||
697 | boundary.active = true;
|
||
698 | boundary.sendToServer(); |
||
699 | } |
||
700 | return;
|
||
701 | } |
||
702 | 702 | gtress | |
703 | // If we are locating a charging station
|
||
704 | if (setStation) {
|
||
705 | setStation = false;
|
||
706 | station = new ChargingStation(e.getX(), e.getY());
|
||
707 | station.setActive(true);
|
||
708 | return;
|
||
709 | } |
||
710 | 527 | emarinel | |
711 | 531 | emarinel | // Otherwise, we are selecting a bot, or doing nothing
|
712 | 671 | gtress | RobotIcon r = robotIcons.getBoundingIcon(pt); |
713 | if (r != null) |
||
714 | selectedBot = r.id; |
||
715 | 531 | emarinel | } |
716 | 320 | gtress | } |
717 | 527 | emarinel | |
718 | 503 | gtress | class KeyHandler extends Thread { |
719 | KeyEvent e;
|
||
720 | 527 | emarinel | |
721 | 503 | gtress | public KeyHandler (KeyEvent event) { |
722 | super("KeyHandler"); |
||
723 | this.e = event;
|
||
724 | } |
||
725 | 527 | emarinel | |
726 | 503 | gtress | public void run () { |
727 | int code = e.getKeyCode();
|
||
728 | if (code == KeyEvent.VK_UP) { |
||
729 | vectorController.setMaxForward(); |
||
730 | vectorController.sendToServer(); |
||
731 | } else if (code == KeyEvent.VK_DOWN) { |
||
732 | vectorController.setMaxReverse(); |
||
733 | vectorController.sendToServer(); |
||
734 | } else if (code == KeyEvent.VK_LEFT) { |
||
735 | vectorController.setMaxLeft(); |
||
736 | vectorController.sendToServer(); |
||
737 | } else if (code == KeyEvent.VK_RIGHT) { |
||
738 | vectorController.setMaxRight(); |
||
739 | vectorController.sendToServer(); |
||
740 | } else if (code == KeyEvent.VK_S) { |
||
741 | vectorController.setZero(); |
||
742 | vectorController.sendToServer(); |
||
743 | } |
||
744 | } |
||
745 | } |
||
746 | 527 | emarinel | |
747 | 501 | gtress | class ActionHandler extends Thread { |
748 | 531 | emarinel | ActionEvent e;
|
749 | 527 | emarinel | |
750 | 531 | emarinel | public ActionHandler (ActionEvent event) { |
751 | super("ActionHandler"); |
||
752 | this.e = event;
|
||
753 | } |
||
754 | 527 | emarinel | |
755 | 531 | emarinel | public void run () { |
756 | Object source = e.getSource();
|
||
757 | 527 | emarinel | |
758 | 531 | emarinel | // General Actions
|
759 | if (source == btnConnect) {
|
||
760 | if (csi == null) { |
||
761 | connect(); |
||
762 | } else {
|
||
763 | disconnect(); |
||
764 | } |
||
765 | } else if (source == btnGetXBeeIDs) { |
||
766 | csi.sendXBeeIDRequest(); |
||
767 | } else if (source == btnAssignID) { |
||
768 | String message;
|
||
769 | 556 | gtress | if (selectedBot < 0) { |
770 | 531 | emarinel | message = "That robot is unidentified. Please specify its ID.";
|
771 | } else {
|
||
772 | 556 | gtress | message = "That robot has ID " + selectedBot + ". You may reassign it now."; |
773 | 531 | emarinel | } |
774 | String result = JOptionPane.showInputDialog(self, message, "Robot Identification", JOptionPane.QUESTION_MESSAGE); |
||
775 | if (result == null) { |
||
776 | return;
|
||
777 | } |
||
778 | int newID = -1; |
||
779 | try {
|
||
780 | newID = Integer.parseInt(result);
|
||
781 | } catch (Exception ex) { |
||
782 | csi.warn("Invalid ID.");
|
||
783 | return;
|
||
784 | } |
||
785 | // Assign new ID and update display
|
||
786 | 549 | gtress | if (csi != null) { |
787 | 556 | gtress | csi.sendIDAssignment(selectedBot, newID); |
788 | 549 | gtress | } |
789 | 556 | gtress | selectedBot = newID; |
790 | 545 | gtress | lblSelected.setText("" + newID);
|
791 | 702 | gtress | } else if (source == btnLocateStation) { |
792 | setStation = true;
|
||
793 | 531 | emarinel | } else if (source == btnF) { // Robot Movement Controls |
794 | vectorController.setMaxForward(); |
||
795 | vectorController.sendToServer(); |
||
796 | 671 | gtress | robotIcons.cancelMoveTo(selectedBot); |
797 | 531 | emarinel | } else if (source == btnB) { |
798 | vectorController.setMaxReverse(); |
||
799 | vectorController.sendToServer(); |
||
800 | 671 | gtress | robotIcons.cancelMoveTo(selectedBot); |
801 | 531 | emarinel | } else if (source == btnL) { |
802 | vectorController.setMaxLeft(); |
||
803 | vectorController.sendToServer(); |
||
804 | 671 | gtress | robotIcons.cancelMoveTo(selectedBot); |
805 | 531 | emarinel | } else if (source == btnR) { |
806 | vectorController.setMaxRight(); |
||
807 | vectorController.sendToServer(); |
||
808 | 671 | gtress | robotIcons.cancelMoveTo(selectedBot); |
809 | 531 | emarinel | } else if (source == btnActivate) { |
810 | vectorController.setZero(); |
||
811 | vectorController.sendToServer(); |
||
812 | 671 | gtress | robotIcons.cancelMoveTo(selectedBot); |
813 | 664 | gtress | } else if (source == btnSetBounds) { |
814 | boundary.set = true;
|
||
815 | panelWebcam.setCursor(new Cursor(Cursor.CROSSHAIR_CURSOR)); |
||
816 | 682 | gtress | } else if (source == btnClearBounds) { |
817 | boundary.active = false;
|
||
818 | boundary.set = false;
|
||
819 | boundary.img_p1 = new Point(-1, -1); |
||
820 | boundary.img_p2 = new Point(-1, -1); |
||
821 | boundary.panel_p1 = new Point(-1, -1); |
||
822 | boundary.panel_p2 = new Point(-1, -1); |
||
823 | boundary.sendToServer(); |
||
824 | 664 | gtress | } else if (source == btnCommand_MoveTo) { |
825 | 531 | emarinel | if (selectedBot < 0) { |
826 | return;
|
||
827 | } |
||
828 | panelWebcam.setCursor(new Cursor(Cursor.CROSSHAIR_CURSOR)); |
||
829 | setWaypoint = true;
|
||
830 | setWaypointID = selectedBot; |
||
831 | } else if (source == btnCommand_MoveAll) { |
||
832 | panelWebcam.setCursor(new Cursor(Cursor.CROSSHAIR_CURSOR)); |
||
833 | setWaypoint = true;
|
||
834 | setWaypointID = -1;
|
||
835 | } else if (source == btnCommand_StopTask) { |
||
836 | 527 | emarinel | |
837 | 531 | emarinel | } else if (source == btnCommand_ResumeTask) { |
838 | 527 | emarinel | |
839 | 531 | emarinel | } else if (source == btnCommand_ChargeNow) { |
840 | 527 | emarinel | |
841 | 531 | emarinel | } else if (source == btnCommand_StopCharging) { |
842 | 527 | emarinel | |
843 | 531 | emarinel | } else if (source == btnAddTask) { // Queue Management |
844 | taskAddWindow.prompt(); |
||
845 | } else if (source == btnRemoveTask) { |
||
846 | if (taskList.getSelectedIndex() >= 0) { |
||
847 | csi.sendQueueRemove(taskList.getSelectedIndex()); |
||
848 | } |
||
849 | csi.sendQueueUpdate(); |
||
850 | } else if (source == btnMoveTaskUp) { |
||
851 | csi.sendQueueReorder(taskList.getSelectedIndex(), taskList.getSelectedIndex() - 1);
|
||
852 | csi.sendQueueUpdate(); |
||
853 | } else if (source == btnMoveTaskDown) { |
||
854 | csi.sendQueueReorder(taskList.getSelectedIndex(), taskList.getSelectedIndex() + 1);
|
||
855 | csi.sendQueueUpdate(); |
||
856 | } else if (source == btnUpdateTasks) { |
||
857 | csi.sendQueueUpdate(); |
||
858 | } |
||
859 | 527 | emarinel | |
860 | 531 | emarinel | } |
861 | 32 | gtress | } |
862 | 527 | emarinel | |
863 | 32 | gtress | /*
|
864 | 531 | emarinel | * DataUpdater thread.
|
865 | 635 | gtress | * The purpose of this thread is to request data from the server at regular intervals.
|
866 | * Types of data requested: XBee IDs, robot positions, battery levels.
|
||
867 | 32 | gtress | *
|
868 | */
|
||
869 | 320 | gtress | class DataUpdater extends Thread { |
870 | 633 | emarinel | final int DATAUPDATER_DELAY = 200; |
871 | 635 | gtress | int count;
|
872 | 320 | gtress | public DataUpdater () {
|
873 | super("Colonet DataUpdater"); |
||
874 | 635 | gtress | count = 0;
|
875 | 320 | gtress | } |
876 | 527 | emarinel | |
877 | 625 | gtress | public synchronized void run () { |
878 | 320 | gtress | while (true) { |
879 | try {
|
||
880 | if (csi != null && csi.isReady()) { |
||
881 | 635 | gtress | // XBee ID Request
|
882 | if (count % 5 == 0) { |
||
883 | csi.sendXBeeIDRequest(); |
||
884 | 425 | gtress | } |
885 | 635 | gtress | // Robot Position Request
|
886 | if (count % 1 == 0) { |
||
887 | csi.sendPositionRequest(); |
||
888 | } |
||
889 | // Battery Request
|
||
890 | if (count % 30 == 0) { |
||
891 | for (Map.Entry<Integer,RobotIcon> entry : robotIcons.entrySet()) { |
||
892 | RobotIcon r = entry.getValue(); |
||
893 | int id = r.id;
|
||
894 | if (id >= 0) { |
||
895 | csi.sendBatteryRequest(id); |
||
896 | } |
||
897 | } |
||
898 | } |
||
899 | 320 | gtress | } |
900 | 635 | gtress | count++; |
901 | if (count > 1000) |
||
902 | count = 0;
|
||
903 | 320 | gtress | Thread.sleep(DATAUPDATER_DELAY);
|
904 | } catch (InterruptedException e) { |
||
905 | return;
|
||
906 | 625 | gtress | } catch (NullPointerException e) { |
907 | // This probably indicates that an object was destroyed by shutdown.
|
||
908 | return;
|
||
909 | 527 | emarinel | } |
910 | 320 | gtress | } |
911 | } |
||
912 | } |
||
913 | 527 | emarinel | |
914 | |||
915 | 320 | gtress | /*
|
916 | 531 | emarinel | * WebcamPanel class
|
917 | 701 | gtress | * Contains the webcam image
|
918 | 320 | gtress | */
|
919 | class WebcamPanel extends JPanel { |
||
920 | 701 | gtress | |
921 | 320 | gtress | volatile BufferedImage img; |
922 | 511 | gtress | BufferedImage buffer;
|
923 | 527 | emarinel | |
924 | 320 | gtress | public WebcamPanel () {
|
925 | 517 | gtress | super(true); |
926 | 320 | gtress | } |
927 | 527 | emarinel | |
928 | 506 | gtress | public synchronized void setImage (BufferedImage newimg) { |
929 | 320 | gtress | if (img != null) { |
930 | img.flush(); |
||
931 | 273 | gtress | } |
932 | 429 | gtress | System.gc();
|
933 | 320 | gtress | img = newimg; |
934 | 510 | gtress | repaint(); |
935 | 270 | gtress | } |
936 | 527 | emarinel | |
937 | 506 | gtress | public synchronized void paint (Graphics g) { |
938 | 533 | emarinel | if (img == null) { |
939 | 427 | gtress | return;
|
940 | 533 | emarinel | } |
941 | 527 | emarinel | |
942 | 511 | gtress | // Calculate scaling
|
943 | 701 | gtress | int maxWidth = getWidth() - 2*ColonetConstants.WEBCAM_BORDER; |
944 | int maxHeight = getHeight() - 2*ColonetConstants.WEBCAM_BORDER; |
||
945 | 470 | gtress | double widthRatio = 1.0 * maxWidth / img.getWidth(); |
946 | double heightRatio = 1.0 * maxHeight / img.getHeight(); |
||
947 | 504 | gtress | double scale = 0; |
948 | 470 | gtress | int newWidth = 0; |
949 | int newHeight = 0; |
||
950 | int x = 0; |
||
951 | int y = 0; |
||
952 | 527 | emarinel | |
953 | 531 | emarinel | if (widthRatio > heightRatio) { //height is the limiting factor |
954 | 533 | emarinel | scale = heightRatio; |
955 | newHeight = maxHeight; |
||
956 | newWidth = (int) (img.getWidth() * scale);
|
||
957 | 701 | gtress | y = ColonetConstants.WEBCAM_BORDER; |
958 | x = (maxWidth - newWidth) / 2 + ColonetConstants.WEBCAM_BORDER;
|
||
959 | 531 | emarinel | } else { //width is the limiting factor |
960 | 533 | emarinel | scale = widthRatio; |
961 | newWidth = maxWidth; |
||
962 | newHeight = (int) (img.getHeight() * scale);
|
||
963 | 701 | gtress | x = ColonetConstants.WEBCAM_BORDER; |
964 | y = (maxHeight - newHeight) / 2 + ColonetConstants.WEBCAM_BORDER;
|
||
965 | 427 | gtress | } |
966 | 527 | emarinel | |
967 | 664 | gtress | // Draw image onto the buffer
|
968 | 512 | gtress | buffer = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB); |
969 | 511 | gtress | Graphics2D bufferedGraphics = (Graphics2D)buffer.getGraphics(); |
970 | bufferedGraphics.setColor(Color.GRAY);
|
||
971 | bufferedGraphics.fillRect(0, 0, this.getWidth(), this.getHeight()); |
||
972 | 470 | gtress | Image imgScaled = img.getScaledInstance(newWidth, newHeight, Image.SCALE_FAST); |
973 | 511 | gtress | bufferedGraphics.drawImage(imgScaled, x, y, this);
|
974 | 527 | emarinel | |
975 | 701 | gtress | // Draw boundary
|
976 | 758 | gtress | if (boundary != null && (boundary.isSetting() || boundary.isActive())) { |
977 | 701 | gtress | bufferedGraphics.setColor(Color.BLUE);
|
978 | int width = boundary.panel_p2.x - boundary.panel_p1.x;
|
||
979 | int height = boundary.panel_p2.y - boundary.panel_p1.y;
|
||
980 | bufferedGraphics.drawRect(boundary.panel_p1.x, boundary.panel_p1.y, width, height); |
||
981 | } |
||
982 | 702 | gtress | |
983 | // Draw charging station
|
||
984 | if (station != null && station.isActive()) { |
||
985 | bufferedGraphics.setStroke(new BasicStroke(2)); |
||
986 | bufferedGraphics.setColor(Color.ORANGE);
|
||
987 | int width = ColonetConstants.STATION_SIZE;
|
||
988 | bufferedGraphics.drawRect(station.getX() - width/2, station.getY() - width/2, width, width); |
||
989 | } |
||
990 | 527 | emarinel | |
991 | 505 | gtress | // Draw Identifiers and battery levels
|
992 | 515 | gtress | if (robotIcons != null) { |
993 | 533 | emarinel | bufferedGraphics.setStroke(new BasicStroke(2)); |
994 | 596 | gtress | for (Map.Entry<Integer,RobotIcon> entry : robotIcons.entrySet()) { |
995 | RobotIcon r = entry.getValue(); |
||
996 | 533 | emarinel | bufferedGraphics.setColor(r.color); |
997 | // Identifier circle
|
||
998 | int px = (int) (x + r.x * scale); |
||
999 | int py = (int) (y + r.y * scale); |
||
1000 | 701 | gtress | int radius = ColonetConstants.ROBOT_RADIUS;
|
1001 | bufferedGraphics.drawOval(px-radius, py-radius, 2*radius, 2*radius); |
||
1002 | 671 | gtress | // ID, if applicable
|
1003 | if (r.id > 0) { |
||
1004 | bufferedGraphics.setFont(new Font("arial", Font.PLAIN, 36)); |
||
1005 | bufferedGraphics.drawString("" + r.id, px-10, py+10); |
||
1006 | } |
||
1007 | 533 | emarinel | // Battery
|
1008 | 561 | gtress | if (r.battery >= 0) { |
1009 | 701 | gtress | int pixels = r.battery * ColonetConstants.BATTERY_WIDTH / 100; |
1010 | 625 | gtress | if (r.battery > 50) |
1011 | bufferedGraphics.setColor(Color.GREEN);
|
||
1012 | else if (r.battery > 25) |
||
1013 | bufferedGraphics.setColor(Color.YELLOW);
|
||
1014 | else
|
||
1015 | bufferedGraphics.setColor(Color.RED);
|
||
1016 | 701 | gtress | bufferedGraphics.fillRect(px+20, py+20, pixels, ColonetConstants.BATTERY_HEIGHT); |
1017 | 535 | emarinel | bufferedGraphics.setColor(Color.BLACK);
|
1018 | 701 | gtress | bufferedGraphics.drawRect(px+20, py+20, ColonetConstants.BATTERY_WIDTH, ColonetConstants.BATTERY_HEIGHT); |
1019 | 561 | gtress | } |
1020 | 533 | emarinel | // If the robot has a destination, draw the vector
|
1021 | if (r.destx >= 0) { |
||
1022 | bufferedGraphics.drawLine(px, py, (int)(x + r.destx * scale), (int)(y + r.desty * scale)); |
||
1023 | 531 | emarinel | } |
1024 | 533 | emarinel | } |
1025 | 320 | gtress | } |
1026 | 527 | emarinel | |
1027 | 428 | gtress | // Identify currently-selected robot
|
1028 | 556 | gtress | RobotIcon r = robotIcons.get(selectedBot); |
1029 | if (r != null) { |
||
1030 | int px = (int) (x + r.x * scale); |
||
1031 | int py = (int) (y + r.y * scale); |
||
1032 | 701 | gtress | int radius = ColonetConstants.ROBOT_RADIUS;
|
1033 | 556 | gtress | bufferedGraphics.setColor(Color.BLACK);
|
1034 | 701 | gtress | bufferedGraphics.drawOval(px-radius-6, py-radius-6, 2*radius+12, 2*radius+12); |
1035 | 515 | gtress | } |
1036 | 527 | emarinel | |
1037 | 511 | gtress | //Display buffered content
|
1038 | 515 | gtress | g.drawImage(buffer, 0, 0, this); |
1039 | 270 | gtress | } |
1040 | 527 | emarinel | |
1041 | 635 | gtress | /**
|
1042 | 527 | emarinel | * Convert a click on the webcam panel to a coordinate that is consistent with the
|
1043 | 522 | gtress | * original size of the image that the panel contains.
|
1044 | */
|
||
1045 | public Point convertClick (MouseEvent e) { |
||
1046 | 532 | emarinel | if (img == null) { |
1047 | return new Point(e.getX(), e.getY()); |
||
1048 | } |
||
1049 | |||
1050 | 522 | gtress | // Calculate scaling
|
1051 | 701 | gtress | int border = ColonetConstants.WEBCAM_BORDER;
|
1052 | 522 | gtress | int clickx = e.getX();
|
1053 | 532 | emarinel | int clicky = e.getY();
|
1054 | 701 | gtress | int maxWidth = getWidth() - 2*border; |
1055 | int maxHeight = getHeight() - 2*border; |
||
1056 | 522 | gtress | double widthRatio = 1.0 * maxWidth / img.getWidth(); |
1057 | double heightRatio = 1.0 * maxHeight / img.getHeight(); |
||
1058 | double scale = 0; |
||
1059 | int newWidth = 0; |
||
1060 | int newHeight = 0; |
||
1061 | int px = 0; |
||
1062 | int py = 0; |
||
1063 | 527 | emarinel | |
1064 | 531 | emarinel | if (widthRatio > heightRatio) { //height is the limiting factor |
1065 | 532 | emarinel | scale = heightRatio; |
1066 | newHeight = maxHeight; |
||
1067 | newWidth = (int) (img.getWidth() * scale);
|
||
1068 | 701 | gtress | py = clicky - border; |
1069 | px = clickx - border - (maxWidth - newWidth) / 2;
|
||
1070 | 531 | emarinel | } else { //width is the limiting factor |
1071 | 532 | emarinel | scale = widthRatio; |
1072 | newWidth = maxWidth; |
||
1073 | newHeight = (int) (img.getHeight() * scale);
|
||
1074 | 701 | gtress | px = clickx - border; |
1075 | py = clicky - border - (maxHeight - newHeight) / 2;
|
||
1076 | 522 | gtress | } |
1077 | 549 | gtress | py = (int) (py / scale);
|
1078 | px = (int) (px / scale);
|
||
1079 | 532 | emarinel | return new Point(px, py); |
1080 | 522 | gtress | } |
1081 | 667 | gtress | |
1082 | 270 | gtress | } |
1083 | 527 | emarinel | |
1084 | 270 | gtress | /*
|
1085 | 531 | emarinel | * WebcamLoader class
|
1086 | * Handles the loading of the webcam image.
|
||
1087 | 270 | gtress | */
|
1088 | 527 | emarinel | class WebcamLoader extends Thread |
1089 | 320 | gtress | { |
1090 | URL imagePath;
|
||
1091 | MediaTracker mt;
|
||
1092 | BufferedImage image;
|
||
1093 | 471 | gtress | Random rand;
|
1094 | 527 | emarinel | |
1095 | 320 | gtress | public WebcamLoader (JApplet applet) |
1096 | { |
||
1097 | super("ColonetWebcamLoader"); |
||
1098 | mt = new MediaTracker(applet); |
||
1099 | ImageIO.setUseCache(false); |
||
1100 | 471 | gtress | rand = new Random(); |
1101 | 270 | gtress | } |
1102 | 527 | emarinel | |
1103 | 470 | gtress | public void run () |
1104 | 320 | gtress | { |
1105 | 270 | gtress | while (true) { |
1106 | try {
|
||
1107 | 701 | gtress | Thread.sleep(ColonetConstants.WEBCAM_DELAY);
|
1108 | 527 | emarinel | if (image != null) |
1109 | 320 | gtress | image.flush(); |
1110 | 429 | gtress | System.gc();
|
1111 | 471 | gtress | try {
|
1112 | 701 | gtress | imagePath = new URL(ColonetConstants.WEBCAM_PATH + "?rand=" + rand.nextInt(100000)); |
1113 | 535 | emarinel | } catch (MalformedURLException e) { |
1114 | 701 | gtress | System.out.println("Malformed URL: could not form URL from: [" + ColonetConstants.WEBCAM_PATH + "]\n"); |
1115 | 535 | emarinel | } |
1116 | 320 | gtress | image = ImageIO.read(imagePath);
|
1117 | // The MediaTracker waitForID pauses the thread until the image is loaded.
|
||
1118 | // We don't want to display a half-downloaded image.
|
||
1119 | mt.addImage(image, 1);
|
||
1120 | 679 | gtress | while(!mt.checkID(1)) |
1121 | Thread.sleep(20); |
||
1122 | 320 | gtress | mt.removeImage(image); |
1123 | // Save
|
||
1124 | panelWebcam.setImage(image); |
||
1125 | 270 | gtress | } catch (InterruptedException e) { |
1126 | return;
|
||
1127 | 320 | gtress | } catch (java.security.AccessControlException e) {
|
1128 | 389 | gtress | csi.warn("Could not load webcam.\n" + e);
|
1129 | 320 | gtress | return;
|
1130 | } catch (IOException e) { |
||
1131 | 585 | gtress | getInfoPanel().append("IOException while trying to load image.");
|
1132 | 320 | gtress | } |
1133 | 270 | gtress | } |
1134 | } |
||
1135 | 459 | gtress | } |
1136 | 664 | gtress | |
1137 | class RobotBoundary { |
||
1138 | 667 | gtress | public volatile boolean active, set; |
1139 | 670 | gtress | public volatile Point img_p1, img_p2; |
1140 | public volatile Point panel_p1, panel_p2; |
||
1141 | 664 | gtress | |
1142 | public RobotBoundary () {
|
||
1143 | active = false;
|
||
1144 | set = false;
|
||
1145 | 670 | gtress | img_p1 = new Point(-1,-1); |
1146 | img_p2 = new Point(-1,-1); |
||
1147 | panel_p1 = new Point(-1,-1); |
||
1148 | panel_p2 = new Point(-1,-1); |
||
1149 | 664 | gtress | } |
1150 | |||
1151 | public void sendToServer () { |
||
1152 | 682 | gtress | if (csi != null) { |
1153 | 698 | gtress | if (img_p1.x == -1 || img_p1.y == -1) |
1154 | csi.sendBoundaryClear(); |
||
1155 | else
|
||
1156 | csi.sendBoundary(img_p1.x, img_p1.y, img_p2.x, img_p2.y); |
||
1157 | 682 | gtress | } |
1158 | 664 | gtress | } |
1159 | 701 | gtress | |
1160 | public boolean isActive () { |
||
1161 | return active;
|
||
1162 | } |
||
1163 | |||
1164 | 702 | gtress | public boolean isSetting () { |
1165 | 701 | gtress | return set;
|
1166 | } |
||
1167 | |||
1168 | /**
|
||
1169 | * Returns a Rectangle designating the active area
|
||
1170 | * in the coordinate system of the JPanel.
|
||
1171 | * Returns null if the boundary is not active.
|
||
1172 | */
|
||
1173 | 702 | gtress | public Rectangle toRectangle () { |
1174 | 701 | gtress | if (!active)
|
1175 | return null; |
||
1176 | return new Rectangle (panel_p1.x, panel_p1.y, (panel_p2.x-panel_p1.x), (panel_p2.y-panel_p1.y)); |
||
1177 | } |
||
1178 | |||
1179 | /**
|
||
1180 | * Determines whether a coordinate in the coordinate
|
||
1181 | * system of the JPanel is contained within the boundary.
|
||
1182 | * If the boundary is not active, then this method
|
||
1183 | * returns false.
|
||
1184 | */
|
||
1185 | 702 | gtress | public boolean contains (int px, int py) { |
1186 | Rectangle rect = this.toRectangle(); |
||
1187 | 701 | gtress | if (rect == null || !this.active) |
1188 | return false; |
||
1189 | 702 | gtress | return rect.contains(px, py);
|
1190 | 701 | gtress | } |
1191 | |||
1192 | 664 | gtress | } |
1193 | 702 | gtress | |
1194 | class ChargingStation { |
||
1195 | private int x, y; |
||
1196 | private boolean active; |
||
1197 | |||
1198 | public ChargingStation () {
|
||
1199 | super();
|
||
1200 | } |
||
1201 | |||
1202 | public ChargingStation (int x, int y) { |
||
1203 | this();
|
||
1204 | setLocation(x, y); |
||
1205 | } |
||
1206 | |||
1207 | public int getX () { |
||
1208 | return x;
|
||
1209 | } |
||
1210 | |||
1211 | public int getY () { |
||
1212 | return y;
|
||
1213 | } |
||
1214 | |||
1215 | /**
|
||
1216 | * Sets the center of the charging station to
|
||
1217 | * the specified coordinate point.
|
||
1218 | */
|
||
1219 | public void setLocation (int x, int y) { |
||
1220 | this.x = x;
|
||
1221 | this.y = y;
|
||
1222 | } |
||
1223 | |||
1224 | public boolean isActive () { |
||
1225 | return active;
|
||
1226 | } |
||
1227 | |||
1228 | public void setActive (boolean status) { |
||
1229 | this.active = status;
|
||
1230 | } |
||
1231 | |||
1232 | /**
|
||
1233 | * Determines whether a coordinate is contained within
|
||
1234 | * the graphical area of the charging station.
|
||
1235 | * Returns false if the charging station is not active.
|
||
1236 | */
|
||
1237 | public boolean contains (int px, int py) { |
||
1238 | if (!active)
|
||
1239 | return false; |
||
1240 | int width = ColonetConstants.STATION_SIZE;
|
||
1241 | Rectangle rect = new Rectangle(this.x - width/2, this.y - width/2, width, width); |
||
1242 | return rect.contains(px, py);
|
||
1243 | } |
||
1244 | |||
1245 | } |
||
1246 | 527 | emarinel | |
1247 | 638 | gtress | } |