/**
* Squidy Interaction Library is free software: you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of the License,
* or (at your option) any later version.
*
* Squidy Interaction Library is distributed in the hope that it will be
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Squidy Interaction Library. If not, see
* <http://www.gnu.org/licenses/>.
*
* 2009 Human-Computer Interaction Group, University of Konstanz.
* <http://hci.uni-konstanz.de>
*
* Please contact info@squidy-lib.de or visit our website
* <http://www.squidy-lib.de> for further information.
*/
package org.squidy.nodes;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.Vector;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlType;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.squidy.manager.ProcessException;
import org.squidy.manager.controls.CheckBox;
import org.squidy.manager.controls.ComboBox;
import org.squidy.manager.controls.Slider;
import org.squidy.manager.controls.TextField;
import org.squidy.manager.controls.ComboBoxControl.ComboBoxItemWrapper;
import org.squidy.manager.data.DataConstant;
import org.squidy.manager.data.Processor;
import org.squidy.manager.data.Property;
import org.squidy.manager.data.Processor.Status;
import org.squidy.manager.data.domainprovider.DomainProvider;
import org.squidy.manager.data.impl.DataPosition2D;
import org.squidy.manager.model.AbstractNode;
import org.squidy.nodes.reactivision.TuioClient;
import org.squidy.nodes.reactivision.TuioCursor;
import org.squidy.nodes.reactivision.TuioListener;
import org.squidy.nodes.reactivision.TuioObject;
import org.squidy.nodes.reactivision.remote.CalibrationWindow;
import org.squidy.nodes.reactivision.remote.control.CameraSettingsContainer;
import org.squidy.nodes.reactivision.remote.control.ControlServer;
import org.squidy.nodes.reactivision.remote.control.FiducialSet;
import org.squidy.nodes.reactivision.remote.image.ImageServer;
/**
* <code>ReacTIVision</code>.
*
* <pre>
* Date: Okt 30, 2009
* Time: 15:08:41 PM
* </pre>
*
* @author Andreas Ergenzinger, <a
* href="mailto:andreas.ergenzinger@uni-konstanz.de">andreas.ergenzinger@uni-konstanz.de</a>
* Human-Computer Interaction Group University of Konstanz
*
* @version $Id: ReacTIVision.java 772 2011-09-16 15:39:44Z raedle $
* @since 1.1.0
*/
@XmlType(name = "reacTIVision")
@Processor(
name = "reacTIVision",
icon = "/org/squidy/nodes/image/48x48/reactivision.png",
description = "/org/squidy/nodes/html/ReacTIVision.html",
types = { Processor.Type.INPUT },
tags = { "reactivision", "fiducial", "id", "token" }
)
public class ReacTIVision extends AbstractNode implements TuioListener {
// Logger to log info, error, debug,... messages.
private static final Log LOG = LogFactory.getLog(ReacTIVision.class);
public static final DataConstant TUIO_TOKEN = DataConstant.get(
String.class, "TUIO_TOKEN");
public static final DataConstant TUIO_CURSOR = DataConstant.get(
String.class, "TUIO_CURSOR");
public static final DataConstant TUIO_ALIVE = DataConstant.get(
Vector.class, "TUIO_ALIVE");
public void afterUnmarshal(Unmarshaller unmarshaller, Object parent) {
if (enableRemoteConfiguration)
startCommServers();
}
// ################################################################################
// BEGIN OF ADJUSTABLES
// ################################################################################
@XmlAttribute(name = "reactivision-identifier")
@Property(
name = "ReacTIVision Identifier",
description = "Information about which camera and ReacTIVision instance will connect to this node."
)
@TextField
private String reactivisionIdentifier = "id";
/**
* @return the port
*/
public final String getReactivisionIdentifier() {
return reactivisionIdentifier;
}
public void setReactivisionIdentifier(String reactivisionIdentifier) {
this.reactivisionIdentifier = reactivisionIdentifier;
}
@XmlAttribute(name = "tuio-port")
@Property(
name = "TUIO Port",
description = "The port on which the reacTIVision node receives TUIO messages."
)
@TextField
private int tuioPort = 4444;
/**
* @return the port
*/
public final int getTuioPort() {
return tuioPort;
}
/**
* Makes the following changes:
* <ul>
* <li>tuioPort = port</li>
* <li>controlServerPort = port + 1</li>
* <li>imageServerPort = port + 2</li>
* </ul>
* @param port the new value for the tuioPort variable
*/
public final void setTuioPort(int port) {
//set all affected port numbers
tuioPort = port;
refreshControlServerPort(port + CONTROL_SERVER_OFFSET);
refreshImageServerPort(port + IMAGE_SERVER_OFFSET);
if (tuioClient != null) {
stop();
start();
}
}
// ################################################################################
@XmlAttribute(name = "periodic-messages")
@Property(
name = "Periodic messages",
description = "Sends a periodic update every second just to indicate that the tracker is still available and to correct eventually lost packets in between"
)
@CheckBox
private boolean periodicMessages = false;
public synchronized boolean isPeriodicMessages() {
return periodicMessages;
}
public synchronized void setPeriodicMessages(boolean periodicMessages) {
this.periodicMessages = periodicMessages;
if (isProcessing() && periodicMessages) {
startPeriodicMessages();
}
}
// ################################################################################
@XmlAttribute(name = "counter-clockwise-angle")
@Property(name = "Counter clockwise angle", description = "Returns the object angle in a counter clockwise manner.")
@CheckBox
private boolean counterClockwiseAngle = false;
public boolean isCounterClockwiseAngle() {
return counterClockwiseAngle;
}
public void setCounterClockwiseAngle(boolean counterClockwiseAngle) {
this.counterClockwiseAngle = counterClockwiseAngle;
}
//REMOTE CONFIGURATION
private ControlServer controlServer;
private ImageServer imageServer;
private CalibrationWindow calibrationWindow;
@XmlAttribute(name = "enable-remote-configuration")
@Property(name = "Enable remote configuration", group = "Camera Configuration")
@CheckBox
private boolean enableRemoteConfiguration = false;
public boolean isEnableRemoteConfiguration() {
return enableRemoteConfiguration;
}
public void setEnableRemoteConfiguration(boolean enableRemoteConfiguration) {
if (enableRemoteConfiguration == true) {
int result = startCommServers();
switch (result) {
case 0:
//everything is fine
this.enableRemoteConfiguration = enableRemoteConfiguration;
break;
case 1:
showErrorPopUp("Could not create ControlServer, since port " + (tuioPort+1)
+ " is already in use.");
break;
case 2:
showErrorPopUp("Could not create ImageServer, since port " + (tuioPort+2)
+ " is already in use.");
break;
case 3:
showErrorPopUp("Could not create ControlServer and ImageServer, since "
+ "ports " + (tuioPort+1) + " and " + (tuioPort+2) + " are already in use.");
}
} else {
this.enableRemoteConfiguration = enableRemoteConfiguration;
if (calibrationWindow != null)
calibrationWindow.dispose();
this.refreshGridCalibration(MODE_OFF);
stopCommServers();
}
}
private int startCommServers() {
stopCommServers();
controlServer = new ControlServer(this, tuioPort + CONTROL_SERVER_OFFSET);
imageServer = new ImageServer(tuioPort + IMAGE_SERVER_OFFSET);
int i = 0;
if (!controlServer.start())
i += 1;
if (!imageServer.start())
i += 2;
return i;
}
private void stopCommServers() {
if (controlServer != null)
controlServer.stop();
if (imageServer != null)
imageServer.stop();
}
public static void showErrorPopUp(String errorMessage) {
//get screen size
Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
//create pop up
final JFrame frame = new JFrame();
frame.setResizable(false);
frame.setAlwaysOnTop(true);
frame.setUndecorated(true);
JLabel text = new JLabel(" " + errorMessage + " ");
frame.getContentPane().setBackground(Color.RED);
text.setForeground(Color.WHITE);
frame.add(text);
frame.pack();
frame.setLocation((dim.width - frame.getWidth())/2, (dim.height - frame.getHeight())/2);
frame.setVisible(true);
frame.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent arg0) {
frame.dispose();
}
});
}
//############################################################################
/**
* controlServerPort - tuioPort = ...
*/
private static final int CONTROL_SERVER_OFFSET = 1;
@XmlAttribute(name = "control-server-port")
@Property(
name = "ControlServer Port",
group = "Camera Configuration",
description = "The port ReacTIVision will connect to in order to receive control messages."
)
@TextField
private int controlServerPort = tuioPort + CONTROL_SERVER_OFFSET;
public final int getControlServerPort() {
return controlServerPort;
}
public final void setControlServerPort(int controlServerPort) {
if (controlServerPort != this.controlServerPort) {
//do not change port number
//but show message ...
publishNotification("ControlServer port number depends on TUIO port number.");
//... and reset displayed value
refreshControlServerPort(tuioPort + CONTROL_SERVER_OFFSET);
}
}
public final void refreshControlServerPort(int controlServerPort) {
fireStatusChange("controlServerPort", "", ((Integer)controlServerPort).toString());
this.controlServerPort = controlServerPort;
}
//############################################################################
private static final int IMAGE_SERVER_OFFSET = 2;
@XmlAttribute(name = "image-server-port")
@Property(
name = "ImageServer Port",
group = "Camera Configuration",
description = "The port ReacTIVision will connect to in order to send image data to Squidy."
)
@TextField
private int imageServerPort = tuioPort + IMAGE_SERVER_OFFSET;
public final int getImageServerPort() {
return imageServerPort;
}
public final void setImageServerPort(int imageServerPort) {
if (imageServerPort != this.imageServerPort) {
//do not change port number
//but show message ...
publishNotification("ImageServer port number depends on TUIO port number.");
//... and reset displayed value
refreshImageServerPort(tuioPort + IMAGE_SERVER_OFFSET);
}
}
public final void refreshImageServerPort(int imageServerPort) {
fireStatusChange("imageServerPort", "", ((Integer)imageServerPort).toString());
this.imageServerPort = imageServerPort;
}
//############################################################################
public final static int MODE_OFF = 0;
public final static int MODE_ON = 1;
@XmlAttribute(name = "grid-calibration")
@Property(name = "Grid Calibration", group = "Camera Configuration")
@ComboBox(domainProvider = OnOffModeDomainProvider.class)
private int gridCalibration = MODE_OFF;
public int getGridCalibration() {
return gridCalibration;
}
public void setGridCalibration(int mode) {
this.gridCalibration = mode;
if (enableRemoteConfiguration) {
if (mode == MODE_ON) {
calibrationWindow = new CalibrationWindow(this, controlServer, imageServer);
calibrationWindow.setVisible(true);
} else {
if (calibrationWindow != null)
calibrationWindow.dispose();
}
} else if (mode == MODE_ON){
//prevent setting mode to MODE_ON
fireStatusChange("gridCalibration",
MODE_ON, MODE_OFF);
}
}
public void refreshGridCalibration(int mode) {
fireStatusChange("gridCalibration",
getGridCalibration(), mode);
}
@XmlAttribute(name = "framerate")
@Property(name = "Framerate", group = "Camera Configuration", description = "Camera framerate")
@Slider(type = Integer.class, minimumValue = 0, maximumValue = 80, showLabels = true, showTicks = true, majorTicks = 20, minorTicks = 10, snapToTicks = false)
private int framerate = 20;
public int getFramerate() {
return framerate;
}
public void setFramerate(int framerate) {
this.framerate = framerate;
if (enableRemoteConfiguration) {
controlServer.setFramerate((float)framerate);
refreshCameraSettings();
}
}
public void refreshFramerate(int framerate) {
fireStatusChange("framerate", getFramerate(), framerate);
this.framerate = framerate;
}
@XmlAttribute(name = "exposure-time")
@Property(name = "Exposure Time", group = "Camera Configuration", description = "Camera exposure time")
@Slider(type = Integer.class, minimumValue = 0, maximumValue = 50, showLabels = true, showTicks = true, majorTicks = 10, minorTicks = 5, snapToTicks = false)
private int exposureTime = 20;
public int getExposureTime() {
return exposureTime;
}
public void setExposureTime(int exposureTime) {
this.exposureTime = exposureTime;
if (enableRemoteConfiguration) {
controlServer.setExposureTime((float)exposureTime);
refreshCameraSettings();
}
}
public void refreshExposureTime(int exposureTime) {
fireStatusChange("exposureTime", getExposureTime(), exposureTime);
this.exposureTime = exposureTime;
}
@XmlAttribute(name = "pixel-clock")
@Property(name = "Pixel Clock", group = "Camera Configuration", description = "Camera pixel clock value")
@Slider(type = Integer.class, minimumValue = 0, maximumValue = 60, showLabels = true, showTicks = true, majorTicks = 10, minorTicks = 1, snapToTicks = false)
private int pixelClock = 30;
public int getPixelClock() {
return pixelClock;
}
public void setPixelClock(int pixelClock) {
this.pixelClock = pixelClock;
if (enableRemoteConfiguration) {
controlServer.setPixelClock(pixelClock);
refreshCameraSettings();
}
}
public void refreshPixelClock(int pixelClock) {
fireStatusChange("pixelClock", getPixelClock(), pixelClock);
this.pixelClock = pixelClock;
}
@XmlAttribute(name = "hardware-gain")
@Property(name = "Hardware Gain", group = "Camera Configuration", description = "Hardware gain")
@Slider(type = Integer.class, minimumValue = 0, maximumValue = 100, showLabels = true, showTicks = true, majorTicks = 20, minorTicks = 5, snapToTicks = false)
private int hardwareGain = 30;
public int getHardwareGain() {
return hardwareGain;
}
public void setHardwareGain(int hardwareGain) {
this.hardwareGain = hardwareGain;
if (enableRemoteConfiguration) {
controlServer.setHardwareGain(hardwareGain);
refreshCameraSettings();
}
}
public void refreshHardwareGain(int hardwareGain) {
fireStatusChange("hardwareGain", getHardwareGain(), hardwareGain);
this.hardwareGain = hardwareGain;
}
@XmlAttribute(name = "edge-enhancement")
@Property(name = "Edge Enhancement", group = "Camera Configuration", description = "Edge enhancement")
@Slider(type = Integer.class, minimumValue = 0, maximumValue = 2, showLabels = true, showTicks = true, majorTicks = 1, minorTicks = 1, snapToTicks = true)
private int edgeEnhancement = 0;
public int getEdgeEnhancement() {
return edgeEnhancement;
}
public void setEdgeEnhancement(int edgeEnhancement) {
this.edgeEnhancement = edgeEnhancement;
if (enableRemoteConfiguration) {
controlServer.setEdgeEnhancement(edgeEnhancement);
refreshCameraSettings();
}
}
public void refreshEdgeEnhancement(int edgeEnhancement) {
fireStatusChange("edgeEnhancement", getEdgeEnhancement(), edgeEnhancement);
this.edgeEnhancement = edgeEnhancement;
}
@XmlAttribute(name = "gamma")
@Property(name = "Gamma", group = "Camera Configuration", description = "Gamma. Default value is 100.")
@Slider(type = Integer.class, minimumValue = 0, maximumValue = 1000, showLabels = true, showTicks = true, majorTicks = 250, minorTicks = 50, snapToTicks = false)
private int gamma = 100;
public int getGamma() {
return gamma;
}
public void setGamma(int gamma) {
this.gamma = gamma;
if (enableRemoteConfiguration) {
controlServer.setGamma(gamma);
refreshCameraSettings();
}
}
public void refreshGamma(int gamma) {
fireStatusChange("gamma", getGamma(), gamma);
this.gamma = gamma;
}
public void refreshCameraSettings() {
final CameraSettingsContainer container = controlServer.getCameraSettings();
if (container == null)
return;
//else
refreshFramerate(Math.round(container.framerate));
refreshExposureTime(Math.round(container.exposureTime));
refreshPixelClock(container.pixelClock);
refreshHardwareGain(container.hardwareGain);
refreshEdgeEnhancement(container.edgeEnhancement);
refreshGamma(container.gamma);
}
@XmlAttribute(name = "fiducial-set")
@Property(name = "Fiducial Set", group = "Camera Configuration")
@ComboBox(domainProvider = FiducialSetDomainProvider.class)
private FiducialSet fiducialSet = FiducialSet.AMOEBA_DEFAULT;
public FiducialSet getFiducialSet() {
return fiducialSet;
}
public void setFiducialSet(FiducialSet fiducialSet) {
this.fiducialSet = fiducialSet;
//make ReacTIVision write the desired set into its configuration file,
//terminate and restart ReacTIVision
if (enableRemoteConfiguration)
controlServer.setFiducialSet(fiducialSet);
}
public void refreshFiducialSet(FiducialSet fiducialSet) {
fireStatusChange("fiducialSet", getFiducialSet(), fiducialSet);
this.fiducialSet = fiducialSet;
}
// ################################################################################
// END OF ADJUSTABLES
// ################################################################################
private TuioClient tuioClient;
private Thread activePeriodicThread = null;
private boolean needPeriodicMessage = false;
/*
* (non-Javadoc)
*
* @see org.squidy.manager.ReflectionProcessable#onStart()
*/
@Override
public void onStart() throws ProcessException {
if (isPeriodicMessages()) {
startPeriodicMessages();
}
tuioClient = new TuioClient(tuioPort);
tuioClient.addTuioListener(this);
tuioClient.connect();
}
/*
* (non-Javadoc)
*
* @see org.squidy.manager.ReflectionProcessable#onStop()
*/
@Override
public void onStop() throws ProcessException {
if (tuioClient != null) {
tuioClient.disconnect();
tuioClient = null;
}
activePeriodicThread = null;
}
public static class OnOffModeDomainProvider implements DomainProvider {
/*
* (non-Javadoc)
*
* @see
* org.squidy.manager.data.domainprovider.DomainProvider#getValues
* ()
*/
public Object[] getValues() {
ComboBoxItemWrapper[] values = new ComboBoxItemWrapper[2];
values[0] = new ComboBoxItemWrapper(MODE_OFF, "Off");
values[1] = new ComboBoxItemWrapper(MODE_ON, "On");
return values;
}
}
public static class FiducialSetDomainProvider implements DomainProvider {
public Object[] getValues() {
ComboBoxItemWrapper[] values = new ComboBoxItemWrapper[FiducialSet.size()];
FiducialSet[] fSetValues = FiducialSet.values();
for (int i = 0; i < values.length; ++i)
values[i] = new ComboBoxItemWrapper(fSetValues[i], fSetValues[i].name().toLowerCase());
return values;
}
}
/**
*
*/
private void startPeriodicMessages() {
if (activePeriodicThread == null) {
activePeriodicThread = new Thread(new Runnable() {
/* (non-Javadoc)
* @see java.lang.Runnable#run()
*/
public void run() {
while (isPeriodicMessages()) {
needPeriodicMessage = true;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
stop();
}
if (isPeriodicMessages() && needPeriodicMessage) {
publishObject(null, "periodic");
}
}
activePeriodicThread = null;
}
});
activePeriodicThread.start();
}
}
// ################################################################################
// BEGIN OF TuioListener
// ################################################################################
/*
* (non-Javadoc)
*
* @see
* org.squidy.manager.input.impl.reactivision.TuioListener
* #addTuioObject
* (org.squidy.manager.input.impl.reactivision.TuioObject)
*/
public void addTuioObject(TuioObject tuioObject) {
publishObject(tuioObject, "add");
}
/*
* (non-Javadoc)
*
* @see
* org.squidy.manager.input.impl.reactivision.TuioListener
* #updateTuioObject
* (org.squidy.manager.input.impl.reactivision.TuioObject)
*/
public void updateTuioObject(TuioObject tuioObject) {
publishObject(tuioObject, "update");
}
/*
* (non-Javadoc)
*
* @see
* org.squidy.manager.input.impl.reactivision.TuioListener
* #removeTuioObject
* (org.squidy.manager.input.impl.reactivision.TuioObject)
*/
public void removeTuioObject(TuioObject tuioObject) {
publishObject(tuioObject, "remove");
}
/*
* (non-Javadoc)
*
* @see
* org.squidy.manager.input.impl.reactivision.TuioListener
* #addTuioCursor
* (org.squidy.manager.input.impl.reactivision.TuioCursor)
*/
public void addTuioCursor(TuioCursor tuioCursor) {
// ignore
}
/*
* (non-Javadoc)
*
* @see
* org.squidy.manager.input.impl.reactivision.TuioListener
* #updateTuioCursor
* (org.squidy.manager.input.impl.reactivision.TuioCursor)
*/
public void updateTuioCursor(TuioCursor tuioCursor) {
// ignore
}
/*
* (non-Javadoc)
*
* @see
* org.squidy.manager.input.impl.reactivision.TuioListener
* #removeTuioCursor
* (org.squidy.manager.input.impl.reactivision.TuioCursor)
*/
public void removeTuioCursor(TuioCursor tuioCursor) {
// ignore
}
/*
* (non-Javadoc)
*
* @see
* org.squidy.manager.input.impl.reactivision.TuioListener
* #refresh(long)
*/
public void refresh(long timestamp) {
// ignore
}
/**
* @param tuioObject
* @param objectState
*/
private void publishObject(TuioObject tuioObject, String objectState) {
needPeriodicMessage = false;
DataPosition2D dataPosition2D;
if (tuioObject != null) {
dataPosition2D = new DataPosition2D(ReacTIVision.class, tuioObject.getX(), tuioObject.getY());
dataPosition2D.setAttribute(TUIO.ORIGIN_ADDRESS, "/tuio/2Dobj");
dataPosition2D.setAttribute(DataConstant.SESSION_ID, ((Long) tuioObject.getSessionID()).intValue());
dataPosition2D.setAttribute(TUIO.FIDUCIAL_ID, tuioObject.getFiducialID());
dataPosition2D.setAttribute(TUIO.MOVEMENT_VECTOR_X, tuioObject.getSpeedX());
dataPosition2D.setAttribute(TUIO.MOVEMENT_VECTOR_Y, tuioObject.getSpeedY());
dataPosition2D.setAttribute(TUIO.ROTATION_VECTOR_A, tuioObject.getRotationSpeed());
dataPosition2D.setAttribute(TUIO.ANGLE_A, counterClockwiseAngle ? ((float) ((2 * Math.PI) - tuioObject.getAngle())) : tuioObject.getAngle());
dataPosition2D.setAttribute(TUIO.ROTATION_ACCELERATION, tuioObject.getRotationAccel());
dataPosition2D.setAttribute(TUIO.MOTION_ACCELERATION, tuioObject.getMotionAccel());
dataPosition2D.setAttribute(TUIO.OBJECT_STATE, objectState);
}
else {
dataPosition2D = new DataPosition2D(ReacTIVision.class, 0, 0);
dataPosition2D.setAttribute(TUIO.OBJECT_STATE, objectState);
}
publish(dataPosition2D);
}
// ################################################################################
// END OF TuioListener
// ################################################################################
}