/**
*
* @author greg (at) myrobotlab.org
*
* This file is part of MyRobotLab (http://myrobotlab.org).
*
* MyRobotLab is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version (subject to the "Classpath" exception
* as provided in the LICENSE.txt file that accompanied this code).
*
* MyRobotLab is distributed in the hope that it will be useful or fun,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* All libraries in thirdParty bundle are subject to their own license
* requirements - please refer to http://myrobotlab.org/libraries for
* details.
*
* Enjoy !
*
* */
package org.myrobotlab.control;
import java.awt.BorderLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.List;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSlider;
import javax.swing.JTabbedPane;
import javax.swing.JTextArea;
import javax.swing.ScrollPaneConstants;
import javax.swing.SwingConstants;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.text.DefaultCaret;
import org.myrobotlab.framework.Service;
import org.myrobotlab.image.Util;
import org.myrobotlab.logging.LoggerFactory;
import org.myrobotlab.roomba.RoombaComm;
import org.myrobotlab.service.GUIService;
import org.myrobotlab.service.Roomba;
import org.myrobotlab.service.Runtime;
import org.slf4j.Logger;
public class RoombaGUI extends ServiceGUI implements ListSelectionListener, ActionListener, ChangeListener, KeyListener {
public final static Logger log = LoggerFactory.getLogger(RoombaGUI.class.getCanonicalName());
static final long serialVersionUID = 1L;
// private Roomba myRoomba = null;
JPanel ctrlPanel, selectPanel, buttonPanel, displayPanel;
JComboBox<String> portChoices;
JComboBox<String> protocolChoices;
JCheckBox handshakeButton;
JTextArea displayText;
JButton connectButton;
JSlider speedSlider;
JButton keyboardControl;
// RoombaCommSerial roombacomm; // in MRL'land can't have direct access to
// this - must message it
Roomba roombacomm; // in MRL'land can't have direct access to this - must
// message it
/** Returns an ImageIcon, or null if the path was invalid. */
protected static ImageIcon createImageIcon(String path, String description) {
return Util.getImageIcon("Roomba/" + path);
}
public RoombaGUI(final String boundServiceName, final GUIService myService, final JTabbedPane tabs) {
super(boundServiceName, myService, tabs);
roombacomm = (Roomba) Runtime.getService(boundServiceName);
}
/** implement actionlistener */
@Override
public void actionPerformed(ActionEvent event) {
String action = event.getActionCommand();
if ("comboBoxChanged".equals(action)) {
String portname = (String) portChoices.getSelectedItem();
roombacomm.setPortname(portname);
int i = protocolChoices.getSelectedIndex();
roombacomm.setProtocol((i == 0) ? "SCI" : "OI");
return;
}
updateDisplay(action + "\n");
if ("connect".equals(action)) {
connect();
return;
} else if ("disconnect".equals(action)) {
disconnect();
return;
}
// stop right here if we're not connected
if (!roombacomm.connected()) {
updateDisplay("not connected!\n");
return;
}
if ("stop".equals(action)) {
roombacomm.stop();
} else if ("forward".equals(action)) {
roombacomm.goForward();
} else if ("backward".equals(action)) {
roombacomm.goBackward();
} else if ("spinleft".equals(action)) {
roombacomm.spinLeft();
} else if ("spinright".equals(action)) {
roombacomm.spinRight();
} else if ("turnleft".equals(action)) {
roombacomm.turnLeft();
} else if ("turnright".equals(action)) {
roombacomm.turnRight();
} else if ("test".equals(action)) {
updateDisplay("Playing some notes\n");
roombacomm.playNote(72, 10); // C
Service.sleep(200);
roombacomm.playNote(79, 10); // G
Service.sleep(200);
roombacomm.playNote(76, 10); // E
Service.sleep(200);
updateDisplay("Spinning left, then right\n");
roombacomm.spinLeft();
Service.sleep(1000);
roombacomm.spinRight();
Service.sleep(1000);
roombacomm.stop();
updateDisplay("Going forward, then backward\n");
roombacomm.goForward();
Service.sleep(1000);
roombacomm.goBackward();
Service.sleep(1000);
roombacomm.stop();
} else if ("reset".equals(action)) {
roombacomm.stop();
roombacomm.startup();
roombacomm.control();
} else if ("power-off".equals(action)) {
roombacomm.powerOff();
} else if ("wakeup".equals(action)) {
roombacomm.wakeup();
} else if ("beep-lo".equals(action)) {
roombacomm.playNote(50, 32); // C1
Service.sleep(200);
} else if ("beep-hi".equals(action)) {
roombacomm.playNote(90, 32); // C7
Service.sleep(200);
} else if ("clean".equals(action)) {
roombacomm.clean();
} else if ("spot".equals(action)) {
roombacomm.spot();
} else if ("vacuum-on".equals(action)) {
roombacomm.vacuum(true);
} else if ("vacuum-off".equals(action)) {
roombacomm.vacuum(false);
} else if ("blink-leds".equals(action)) {
roombacomm.setLEDs(true, true, true, true, true, true, 255, 255);
Service.sleep(300);
roombacomm.setLEDs(false, false, false, false, false, false, 0, 128);
} else if ("sensors".equals(action)) {
if (roombacomm.updateSensors())
updateDisplay(roombacomm.sensorsAsString() + "\n");
else
updateDisplay("couldn't read Roomba. Is it connected?\n");
}
}
@Override
public void attachGUI() {
subscribe("publishState", "getState", Roomba.class);
myService.send(boundServiceName, "publishState");
}
/** */
public boolean connect() {
String portname = (String) portChoices.getSelectedItem();
// roombacomm.debug=true;
roombacomm.setWaitForDSR(handshakeButton.isSelected());
int i = protocolChoices.getSelectedIndex();
roombacomm.setProtocol((i == 0) ? "SCI" : "OI");
connectButton.setText("connecting");
try {
roombacomm.connect(portname);
} catch(Exception e){
log.error("could not connect", e);
updateDisplay("Couldn't connect to " + portname + "\n");
connectButton.setText(" connect ");
return false;
}
updateDisplay("Roomba startup\n");
roombacomm.startup();
roombacomm.control();
roombacomm.playNote(72, 10); // C , test note
Service.sleep(200);
connectButton.setText("disconnect");
connectButton.setActionCommand("disconnect");
// roombacomm.debug=true;
updateDisplay("Checking for Roomba... ");
if (roombacomm.updateSensors())
updateDisplay("Roomba found!\n");
else
updateDisplay("No Roomba. :( Is it turned on?\n");
return true;
}
@Override
public void detachGUI() {
unsubscribe("publishState", "getState", Roomba.class);
}
/** */
public void disconnect() {
roombacomm.disconnect();
connectButton.setText(" connect ");
connectButton.setActionCommand("connect");
}
// ripped from RoombacommPanel - 'thanks guys !
public void getState(Roomba roomba) {
if (roomba != null) {
setPorts(roomba.getPortNames());
}
}
@Override
public void init() {
display.setLayout(new BorderLayout());
makePanels();
setPorts(roombacomm.getPortNames());
}
/** Handle the key pressed event from the text field. */
@Override
public void keyPressed(KeyEvent e) {
int keyCode = e.getKeyCode();
if (keyCode == KeyEvent.VK_SPACE) {
updateDisplay("stop");
roombacomm.stop();
} else if (keyCode == KeyEvent.VK_UP) {
updateDisplay("forward");
roombacomm.goForward();
} else if (keyCode == KeyEvent.VK_DOWN) {
updateDisplay("backward");
roombacomm.goBackward();
} else if (keyCode == KeyEvent.VK_LEFT) {
updateDisplay("spinleft");
roombacomm.spinLeft();
} else if (keyCode == KeyEvent.VK_RIGHT) {
updateDisplay("spinright");
roombacomm.spinRight();
} else if (keyCode == KeyEvent.VK_COMMA) {
updateDisplay("speed down");
roombacomm.setSpeed(roombacomm.getSpeed() - 50);
} else if (keyCode == KeyEvent.VK_PERIOD) {
updateDisplay("speed up");
roombacomm.setSpeed(roombacomm.getSpeed() + 50);
} else if (keyCode == KeyEvent.VK_R) {
updateDisplay("reset");
roombacomm.reset();
roombacomm.control();
}
}
/** Handle the key released event from the text field. */
@Override
public void keyReleased(KeyEvent e) {
}
/** Handle the key typed event from the text field. */
@Override
public void keyTyped(KeyEvent e) {
}
/**
*
*/
void makeButtonPanel() {
buttonPanel = new JPanel(new GridLayout(8, 2));
buttonPanel.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createTitledBorder("Commands"), BorderFactory.createEmptyBorder(5, 5, 5, 5)));
JButton but_reset = new JButton("reset");
buttonPanel.add(but_reset);
but_reset.addActionListener(this);
JButton but_test = new JButton("test");
buttonPanel.add(but_test);
but_test.addActionListener(this);
JButton but_power = new JButton("power-off");
buttonPanel.add(but_power);
but_power.addActionListener(this);
JButton but_wakeup = new JButton("wakeup");
buttonPanel.add(but_wakeup);
but_wakeup.addActionListener(this);
JButton but_beeplo = new JButton("beep-lo");
buttonPanel.add(but_beeplo);
but_beeplo.addActionListener(this);
JButton but_beephi = new JButton("beep-hi");
buttonPanel.add(but_beephi);
but_beephi.addActionListener(this);
JButton but_clean = new JButton("clean");
buttonPanel.add(but_clean);
but_clean.addActionListener(this);
JButton but_spot = new JButton("spot");
buttonPanel.add(but_spot);
but_spot.addActionListener(this);
JButton but_vacon = new JButton("vacuum-on");
buttonPanel.add(but_vacon);
but_vacon.addActionListener(this);
JButton but_vacoff = new JButton("vacuum-off");
buttonPanel.add(but_vacoff);
but_vacoff.addActionListener(this);
JButton but_blinkleds = new JButton("blink-leds");
buttonPanel.add(but_blinkleds);
but_blinkleds.addActionListener(this);
JButton but_sensors = new JButton("sensors");
buttonPanel.add(but_sensors);
but_sensors.addActionListener(this);
keyboardControl = new JButton("keyboard control");
keyboardControl.addKeyListener(this);
buttonPanel.add(keyboardControl);
}
/**
*
*/
void makeCtrlPanel() {
JPanel ctrlPanel1 = new JPanel(new GridLayout(3, 3));
JButton but_turnleft = new JButton(createImageIcon("but_turnleft.png", "turnleft"));
ctrlPanel1.add(but_turnleft);
JButton but_forward = new JButton(createImageIcon("but_forward.png", "forward"));
ctrlPanel1.add(but_forward);
JButton but_turnright = new JButton(createImageIcon("but_turnright.png", "turnright"));
ctrlPanel1.add(but_turnright);
JButton but_spinleft = new JButton(createImageIcon("but_spinleft.png", "spinleft"));
ctrlPanel1.add(but_spinleft);
JButton but_stop = new JButton(createImageIcon("but_stop.png", "stop"));
ctrlPanel1.add(but_stop);
JButton but_spinright = new JButton(createImageIcon("but_spinright.png", "spinright"));
ctrlPanel1.add(but_spinright);
ctrlPanel1.add(new JLabel());
JButton but_backward = new JButton(createImageIcon("but_backward.png", "backward"));
ctrlPanel1.add(but_backward);
ctrlPanel1.add(new JLabel());
JLabel sliderLabel = new JLabel("speed (mm/s)", SwingConstants.CENTER);
speedSlider = new JSlider(SwingConstants.HORIZONTAL, 0, 500, 200);
speedSlider.setPaintTicks(true);
speedSlider.setMajorTickSpacing(100);
speedSlider.setMinorTickSpacing(25);
speedSlider.setPaintLabels(true);
speedSlider.addChangeListener(this);
ctrlPanel = new JPanel();
ctrlPanel.setLayout(new BoxLayout(ctrlPanel, BoxLayout.Y_AXIS));
ctrlPanel.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createTitledBorder("Movement"), BorderFactory.createEmptyBorder(5, 5, 5, 5)));
ctrlPanel.add(ctrlPanel1);
ctrlPanel.add(speedSlider);
ctrlPanel.add(sliderLabel);
but_turnleft.setActionCommand("turnleft");
but_turnright.setActionCommand("turnright");
but_spinleft.setActionCommand("spinleft");
but_spinright.setActionCommand("spinright");
but_forward.setActionCommand("forward");
but_backward.setActionCommand("backward");
but_stop.setActionCommand("stop");
but_turnleft.addActionListener(this);
but_turnright.addActionListener(this);
but_spinleft.addActionListener(this);
but_spinright.addActionListener(this);
but_forward.addActionListener(this);
but_backward.addActionListener(this);
but_stop.addActionListener(this);
}
/**
*
*/
void makeDisplayPanel() {
displayPanel = new JPanel();
displayPanel.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createTitledBorder("Display"), BorderFactory.createEmptyBorder(1, 1, 1, 1)));
displayText = new JTextArea(5, 30);
displayText.setLineWrap(true);
DefaultCaret dc = new DefaultCaret();
// only works on Java 1.5+
// dc.setUpdatePolicy( DefaultCaret.ALWAYS_UPDATE );
displayText.setCaret(dc);
JScrollPane scrollPane = new JScrollPane(displayText, ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
displayPanel.add(scrollPane);
}
/**
*
*/
void makePanels() {
makeSelectPanel();
display.add(selectPanel, BorderLayout.NORTH);
makeCtrlPanel();
display.add(ctrlPanel, BorderLayout.EAST);
makeButtonPanel();
display.add(buttonPanel, BorderLayout.CENTER);
makeDisplayPanel();
display.add(displayPanel, BorderLayout.SOUTH);
// pack(); //setVisible(true);
updateDisplay("RoombaComm, version " + RoombaComm.VERSION + "\n");
}
void makeSelectPanel() {
selectPanel = new JPanel();
// Create a combo box with protocols
String[] protocols = { "Roomba 1xx-4xx (SCI)", "Roomba 5xx (OI)" };
protocolChoices = new JComboBox<String>(protocols);
String p = roombacomm.getProtocol();
protocolChoices.setSelectedIndex(p.equals("SCI") ? 0 : 1);
// Create a combo box with choices.
String[] ports = roombacomm.listPorts();
portChoices = new JComboBox<String>(ports);
if (ports.length > 0) {
portChoices.setSelectedIndex(0);
for (int i = 0; i < ports.length; i++) {
String s = ports[i];
if (s.equals(roombacomm.getPortname())) {
portChoices.setSelectedItem(s);
}
}
} else {
log.error("no ports found!");
}
connectButton = new JButton();
connectButton.setText(" connect ");
connectButton.setActionCommand("connect");
handshakeButton = new JCheckBox("<html>h/w<br>handshake</html>");
// Add a border around the select panel.
selectPanel.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createTitledBorder("Select Roomba Type & Port"), BorderFactory.createEmptyBorder(1, 1, 1, 1)));
selectPanel.add(protocolChoices);
selectPanel.add(portChoices);
selectPanel.add(connectButton);
selectPanel.add(handshakeButton);
// Listen to events from the combo box.
portChoices.addActionListener(this);
connectButton.addActionListener(this);
protocolChoices.addActionListener(this);
}
/**
* Play a (MIDI) note, that is, make the Roomba a musical instrument notenums
* 32-127: notenum == corresponding note played thru beeper velocity ==
* duration in number of 1/64s of a second (e.g. 64==1second) notenum 24:
* notenum == main vacuum velocity == non-zero turns on, zero turns off
* notenum 25: blink LEDs, velcoity is color of Power LED notenum 28 & 29:
* spin left & spin right, velocity is speed
*
*/
public void playMidiNote(int notenum, int velocity) {
updateDisplay("play note: " + notenum + "," + velocity + "\n");
if (!roombacomm.connected())
return;
if (notenum >= 31) { // G and above
if (velocity == 0)
return;
if (velocity < 4)
velocity = 4; // has problems at lower durations
else
velocity = velocity / 2;
roombacomm.playNote(notenum, velocity);
} else if (notenum == 24) { // C
roombacomm.vacuum(!(velocity == 0));
} else if (notenum == 25) { // C#
boolean lon = (velocity != 0);
int inten = (lon) ? 255 : 128; // either full bright or half bright
roombacomm.setLEDs(lon, lon, lon, lon, lon, lon, velocity * 2, inten);
} else if (notenum == 28) { // E
if (velocity != 0)
roombacomm.spinLeftAt(velocity * 2);
else
roombacomm.stop();
} else if (notenum == 29) { // F
if (velocity != 0)
roombacomm.spinRightAt(velocity * 2);
else
roombacomm.stop();
}
}
/**
* setPorts is called by getState - which is called when the Arduino changes
* port state is NOT called by the GUIService component
*
* @param p
* FIXME - there should be a corresponding gui element for the
* serial.Port ie serial.PortGUI such
*/
public void setPorts(List<String> p) {
portChoices.removeAllItems();
portChoices.addItem(""); // the null port
for (int i = 0; i < p.size(); ++i) {
String n = p.get(i);
log.info(n);
portChoices.addItem(n);
}
}
/**
* Set to 'false' to hide the "h/w handshake" button, which seems to be only
* needed on Windows
*/
public void setShowHardwareHandhake(boolean b) {
handshakeButton.setVisible(b);
}
/** implement ChangeListener, for the slider */
@Override
public void stateChanged(ChangeEvent e) {
// System.err.println("stateChanged:"+e);
JSlider src = (JSlider) e.getSource();
if (!src.getValueIsAdjusting()) {
int speed = src.getValue();
speed = (speed < 1) ? 1 : speed; // don't allow zero speed
updateDisplay("setting speed = " + speed + "\n");
roombacomm.setSpeed(speed);
}
}
public void updateDisplay(String s) {
displayText.append(s);
displayText.setCaretPosition(displayText.getDocument().getLength());
}
@Override
public void valueChanged(ListSelectionEvent arg0) {
// TODO Auto-generated method stub
}
}