From 49b8efa836e4912afed073f69b151a33721c5996 Mon Sep 17 00:00:00 2001 From: "guilhem.duche" Date: Thu, 14 Feb 2008 17:52:20 +0000 Subject: [PATCH] first draft with new version working on wiiuse 0.10. New system to get events. git-svn-id: http://wiiusej.googlecode.com/svn/trunk@52 ae48ae66-6a45-0410-b38e-211266189506 --- WiiUseJ/src/tests/Tests.java | 383 +++++++++--------- WiiUseJ/src/wiiusej/WiiUseApi.java | 49 ++- WiiUseJ/src/wiiusej/WiiUseApiListener.java | 9 - WiiUseJ/src/wiiusej/WiiUseApiManager.java | 203 ++++++---- WiiUseJ/src/wiiusej/Wiimote.java | 52 ++- WiiUseJ/src/wiiusej/WiimoteListener.java | 8 - .../wiiuseapievents/DisconnectionEvent.java | 24 ++ .../wiiuseapievents/EventsGatherer.java | 180 ++++++++ .../wiiusej/wiiuseapievents/StatusEvent.java | 251 ++++++++++++ .../{ => wiiuseapievents}/WiiMoteEvent.java | 266 ++---------- .../wiiuseapievents/WiiUseApiEvent.java | 68 ++++ .../wiiuseapievents/WiiUseApiListener.java | 15 + .../wiiuseapievents/WiimoteListener.java | 12 + .../FloatValueRequest.java} | 24 +- .../{ => wiiuseapirequest}/LedsRequest.java | 4 +- .../WiiUseApiRequest.java | 5 +- WiiUseJ/wiiuse.dll | Bin 114688 -> 114688 bytes 17 files changed, 979 insertions(+), 574 deletions(-) delete mode 100644 WiiUseJ/src/wiiusej/WiiUseApiListener.java delete mode 100644 WiiUseJ/src/wiiusej/WiimoteListener.java create mode 100644 WiiUseJ/src/wiiusej/wiiuseapievents/DisconnectionEvent.java create mode 100644 WiiUseJ/src/wiiusej/wiiuseapievents/EventsGatherer.java create mode 100644 WiiUseJ/src/wiiusej/wiiuseapievents/StatusEvent.java rename WiiUseJ/src/wiiusej/{ => wiiuseapievents}/WiiMoteEvent.java (58%) create mode 100644 WiiUseJ/src/wiiusej/wiiuseapievents/WiiUseApiEvent.java create mode 100644 WiiUseJ/src/wiiusej/wiiuseapievents/WiiUseApiListener.java create mode 100644 WiiUseJ/src/wiiusej/wiiuseapievents/WiimoteListener.java rename WiiUseJ/src/wiiusej/{OrientThresholdRequest.java => wiiuseapirequest/FloatValueRequest.java} (55%) rename WiiUseJ/src/wiiusej/{ => wiiuseapirequest}/LedsRequest.java (88%) rename WiiUseJ/src/wiiusej/{ => wiiuseapirequest}/WiiUseApiRequest.java (89%) diff --git a/WiiUseJ/src/tests/Tests.java b/WiiUseJ/src/tests/Tests.java index c5a1ce4..8412aec 100644 --- a/WiiUseJ/src/tests/Tests.java +++ b/WiiUseJ/src/tests/Tests.java @@ -3,12 +3,15 @@ package tests; import java.awt.AWTException; import java.awt.Robot; import java.awt.event.InputEvent; + import wiiusej.Point2DInteger; -import wiiusej.WiiMoteEvent; -import wiiusej.WiiUseApiListener; import wiiusej.WiiUseApiManager; import wiiusej.Wiimote; -import wiiusej.WiimoteListener; +import wiiusej.wiiuseapievents.DisconnectionEvent; +import wiiusej.wiiuseapievents.StatusEvent; +import wiiusej.wiiuseapievents.WiiMoteEvent; +import wiiusej.wiiuseapievents.WiiUseApiListener; +import wiiusej.wiiuseapievents.WiimoteListener; /** * This class used to test this API. @@ -25,7 +28,7 @@ public class Tests implements WiimoteListener { private static int MOVE_MOUSE = 3; private static int ORIENT_THRESH_CONT = 4; private static int TEST_LEDS = 5; - + private Wiimote wiimote; int dump = DISPLAY_EACH_VALUE; @@ -44,209 +47,187 @@ public class Tests implements WiimoteListener { public void wiimoteEvent(WiiMoteEvent e) { if (dump == DISPLAY_EACH_VALUE) { - if (e.isConnected()) { - // System.out.println("*********** WIIMOTE ID : "+ - // e.getWiimoteId() + " **************"); - /* button ONE */ - if (e.isButtonOneJustPressed()) { - System.out.println("button one pressed"); - } - if (e.isButtonOneHeld()) { - System.out.println("button one held"); - } - if (e.isButtonOneJustReleased()) { - System.out.println("button one released"); - } + // System.out.println("*********** WIIMOTE ID : "+ + // e.getWiimoteId() + " **************"); + /* button ONE */ + if (e.isButtonOneJustPressed()) { + System.out.println("button one pressed"); + } + if (e.isButtonOneHeld()) { + System.out.println("button one held"); + } + if (e.isButtonOneJustReleased()) { + System.out.println("button one released"); + } - /* button TWO */ - if (e.isButtonTwoJustPressed()) { - System.out.println("button two pressed"); - } - if (e.isButtonTwoHeld()) { - System.out.println("button two held"); - } - if (e.isButtonTwoJustReleased()) { - System.out.println("button two released"); - } + /* button TWO */ + if (e.isButtonTwoJustPressed()) { + System.out.println("button two pressed"); + } + if (e.isButtonTwoHeld()) { + System.out.println("button two held"); + } + if (e.isButtonTwoJustReleased()) { + System.out.println("button two released"); + } - /* button A */ - if (e.isButtonAJustPressed()) { - System.out.println("button A pressed"); - } - if (e.isButtonAHeld()) { - System.out.println("button A held"); - } - if (e.isButtonAJustReleased()) { - System.out.println("button A released"); - } + /* button A */ + if (e.isButtonAJustPressed()) { + System.out.println("button A pressed"); + } + if (e.isButtonAHeld()) { + System.out.println("button A held"); + } + if (e.isButtonAJustReleased()) { + System.out.println("button A released"); + } - /* button B */ - if (e.isButtonBJustPressed()) { - System.out.println("button B pressed"); - } - if (e.isButtonBHeld()) { - System.out.println("button B held"); - } - if (e.isButtonBJustReleased()) { - System.out.println("button B released"); - } + /* button B */ + if (e.isButtonBJustPressed()) { + System.out.println("button B pressed"); + } + if (e.isButtonBHeld()) { + System.out.println("button B held"); + } + if (e.isButtonBJustReleased()) { + System.out.println("button B released"); + } - /* button LEFT */ - if (e.isButtonLeftJustPressed()) { - System.out.println("button Left pressed"); - } - if (e.isButtonLeftHeld()) { - System.out.println("button Left held"); - } - if (e.isButtonLeftJustReleased()) { - System.out.println("button Left released"); - } + /* button LEFT */ + if (e.isButtonLeftJustPressed()) { + System.out.println("button Left pressed"); + } + if (e.isButtonLeftHeld()) { + System.out.println("button Left held"); + } + if (e.isButtonLeftJustReleased()) { + System.out.println("button Left released"); + } - /* button RIGHT */ - if (e.isButtonRightJustPressed()) { - System.out.println("button Right pressed"); - } - if (e.isButtonRightHeld()) { - System.out.println("button Right held"); - } - if (e.isButtonRightJustReleased()) { - System.out.println("button Right released"); - } + /* button RIGHT */ + if (e.isButtonRightJustPressed()) { + System.out.println("button Right pressed"); + } + if (e.isButtonRightHeld()) { + System.out.println("button Right held"); + } + if (e.isButtonRightJustReleased()) { + System.out.println("button Right released"); + } - /* button UP */ - if (e.isButtonUpJustPressed()) { - System.out.println("button UP pressed"); - } - if (e.isButtonUpHeld()) { - System.out.println("button UP held"); - } - if (e.isButtonUpJustReleased()) { - System.out.println("button UP released"); - } + /* button UP */ + if (e.isButtonUpJustPressed()) { + System.out.println("button UP pressed"); + } + if (e.isButtonUpHeld()) { + System.out.println("button UP held"); + } + if (e.isButtonUpJustReleased()) { + System.out.println("button UP released"); + } - /* button DOWN */ - if (e.isButtonDownJustPressed()) { - System.out.println("button DOWN pressed"); - } - if (e.isButtonDownHeld()) { - System.out.println("button DOWN held"); - } - if (e.isButtonDownJustReleased()) { - System.out.println("button DOWN released"); - } + /* button DOWN */ + if (e.isButtonDownJustPressed()) { + System.out.println("button DOWN pressed"); + } + if (e.isButtonDownHeld()) { + System.out.println("button DOWN held"); + } + if (e.isButtonDownJustReleased()) { + System.out.println("button DOWN released"); + } - /* button MINUS */ - if (e.isButtonMinusJustPressed()) { - System.out.println("button MINUS pressed"); - } - if (e.isButtonMinusHeld()) { - System.out.println("button MINUS held"); - } - if (e.isButtonMinusJustReleased()) { - System.out.println("button MINUS released"); - } + /* button MINUS */ + if (e.isButtonMinusJustPressed()) { + System.out.println("button MINUS pressed"); + } + if (e.isButtonMinusHeld()) { + System.out.println("button MINUS held"); + } + if (e.isButtonMinusJustReleased()) { + System.out.println("button MINUS released"); + } - /* button PLUS */ - if (e.isButtonPlusJustPressed()) { - System.out.println("button PLUS pressed"); - } - if (e.isButtonPlusHeld()) { - System.out.println("button PLUS held"); - } - if (e.isButtonPlusJustReleased()) { - System.out.println("button PLUS released"); - } + /* button PLUS */ + if (e.isButtonPlusJustPressed()) { + System.out.println("button PLUS pressed"); + } + if (e.isButtonPlusHeld()) { + System.out.println("button PLUS held"); + } + if (e.isButtonPlusJustReleased()) { + System.out.println("button PLUS released"); + } - /* button HOME */ - if (e.isButtonHomeJustPressed()) { - System.out.println("button HOME pressed"); - } - if (e.isButtonHomeHeld()) { - System.out.println("button HOME held"); - } - if (e.isButtonHomeJustReleased()) { - System.out.println("button HOME released"); - } + /* button HOME */ + if (e.isButtonHomeJustPressed()) { + System.out.println("button HOME pressed"); + } + if (e.isButtonHomeHeld()) { + System.out.println("button HOME held"); + } + if (e.isButtonHomeJustReleased()) { + System.out.println("button HOME released"); + } - /* get status */ - if (e.isButtonMinusJustPressed() && e.isButtonPlusJustPressed()) { - wiimote.getStatus(); - } + /* get status */ + if (e.isButtonMinusJustPressed() && e.isButtonPlusJustPressed()) { + wiimote.getStatus(); + } - /* Activate rumble */ - if (e.isButtonOneJustPressed()) { - System.out.println("Rumble Activated"); - wiimote.activateRumble(); - } - if (e.isButtonTwoJustPressed()) { - System.out.println("Rumble Deactivated"); - wiimote.deactivateRumble(); - } + /* Activate rumble */ + if (e.isButtonOneJustPressed()) { + System.out.println("Rumble Activated"); + wiimote.activateRumble(); + } + if (e.isButtonTwoJustPressed()) { + System.out.println("Rumble Deactivated"); + wiimote.deactivateRumble(); + } - /* Activate IR Tracking */ - if (e.isButtonAJustPressed()) { - System.out.println("IR Activated"); - wiimote.activateIRTRacking(); - } - if (e.isButtonBJustPressed()) { - System.out.println("IR Deactivated"); - wiimote.deactivateIRTRacking(); - } + /* Activate IR Tracking */ + if (e.isButtonAJustPressed()) { + System.out.println("IR Activated"); + wiimote.activateIRTRacking(); + } + if (e.isButtonBJustPressed()) { + System.out.println("IR Deactivated"); + wiimote.deactivateIRTRacking(); + } - /* Activate Motion sensing */ - if (e.isButtonPlusJustPressed()) { - System.out.println("Motion sensing Activated"); - wiimote.activateMotionSensing(); - } - if (e.isButtonMinusJustPressed()) { - System.out.println("Motion sensing Deactivated"); - wiimote.deactivateMotionSensing(); - } + /* Activate Motion sensing */ + if (e.isButtonPlusJustPressed()) { + System.out.println("Motion sensing Activated"); + wiimote.activateMotionSensing(); + } + if (e.isButtonMinusJustPressed()) { + System.out.println("Motion sensing Deactivated"); + wiimote.deactivateMotionSensing(); + } - /* display status */ - if (e.getBatteryLevel() != -1) { - System.out - .println("battery level : " + e.getBatteryLevel()); - System.out.println("= --- Leds : " + e.getLeds() + "\n"); - System.out.println("= --- Rumble : " + e.isRumbleActive() - + "\n"); - System.out.println("= --- Continous : " - + e.isContinuousActive() + "\n"); - System.out.println("= --- Smoothing : " - + e.isSmoothingActive() + "\n"); - System.out.println("= --- Speaker : " - + e.isSpeakerEnabled() + "\n"); - System.out.println("= --- Attachment : " - + e.isThereAttachment() + "\n"); + /* display ir points */ + if (e.isIrActive()) { + Point2DInteger[] list = e.getIRPoints(); + for (int i = 0; i < list.length; i++) { + if (list[i] != null) + System.out.print("Point :(" + list[i].getX() + "," + + list[i].getY() + ") "); } + System.out.println(""); + } - /* display ir points */ - if (e.isIrActive()) { - Point2DInteger[] list = e.getIRPoints(); - for (int i = 0; i < list.length; i++) { - if (list[i] != null) - System.out.print("Point :(" + list[i].getX() + "," - + list[i].getY() + ") "); - } - System.out.println(""); - } + /* display motion sensing */ + if (e.isMotionSensingActive()) { + System.out.println("Motion Sensing :" + e.getOrientation() + + " , " + e.getGforce()); + } - /* display motion sensing */ - if (e.isMotionSensingActive()) { - System.out.println("Motion Sensing :" + e.getOrientation() - + " , " + e.getGforce()); - } - - /* leave test */ - if (e.isButtonHomeJustPressed()) { - System.out.println("LEAVING TEST"); - wiimote.disconnect(); - } - } else { - System.out.println(" WIIMOTE ID : " + e.getWiimoteId() - + " DISCONNECTED !!!!!"); + /* leave test */ + if (e.isButtonHomeJustPressed()) { + System.out.println("LEAVING TEST"); wiimote.disconnect(); } + } else if (dump == DUMP) { System.out.println(e); /* Activate all */ @@ -337,6 +318,10 @@ public class Tests implements WiimoteListener { System.out.println("Threshold orientation 0.5 degrees"); wiimote.setOrientationThreshold((float) 0.5); } + + //@TODO add accelereation threshold test, add alpha smoothing test + + System.out.println(e); /* leave test */ @@ -347,10 +332,10 @@ public class Tests implements WiimoteListener { } else if (dump == TEST_LEDS) { wiimote.activateMotionSensing(); if (e.isButtonUpJustPressed()) { - wiimote.setLeds(true, false, false, false); + wiimote.setLeds(true, false, false, false); } if (e.isButtonDownJustPressed()) { - wiimote.setLeds(false, true, false, false); + wiimote.setLeds(false, true, false, false); } if (e.isButtonLeftJustPressed()) { wiimote.setLeds(false, false, true, false); @@ -366,20 +351,30 @@ public class Tests implements WiimoteListener { } } } + + @Override + public void statusEvent(StatusEvent e) { + //Display status variables + System.out.println(e); + } + + @Override + public void disconnectionEvent(DisconnectionEvent e) { + System.out.println(e.getWiimoteId()+" notify it's been disconnected !!"); + } /** * @param args */ public static void main(String[] args) { - Wiimote[] wiimotes = WiiUseApiManager.getWiimotes(); - if (wiimotes.length>0){ + Wiimote[] wiimotes = WiiUseApiManager.getWiimotes(4); + if (wiimotes.length > 0) { System.out.println(wiimotes[0]); Tests tests = new Tests(wiimotes[0]); - }else{ + } else { System.out.println("No wiimotes found !!!"); } - - + // java.util.Timer timer = new java.util.Timer(); // timer.scheduleAtFixedRate(new LedsTask(), 0, 100); diff --git a/WiiUseJ/src/wiiusej/WiiUseApi.java b/WiiUseJ/src/wiiusej/WiiUseApi.java index 091f269..93042a1 100644 --- a/WiiUseJ/src/wiiusej/WiiUseApi.java +++ b/WiiUseJ/src/wiiusej/WiiUseApi.java @@ -1,5 +1,7 @@ package wiiusej; +import wiiusej.wiiuseapievents.EventsGatherer; + /** * Singleton used to manipulate WiiUse Api. * @author gduche @@ -19,23 +21,16 @@ public class WiiUseApi { */ static WiiUseApi getInstance(){ return instance; - } - - /** - * Load the library. - * - * @return 0 if there is an error, 1 if everything is ok. - */ - native int loadLibrary(); + } /** * Try to connect to 2 wiimotes. Make them rumble to show they are * connected. - * + * @param nb number of wiimotes to connect * @return 0 if there is an error otherwise it returns the number of * wiimotes connected. */ - native int doConnections(); + native int doConnections(int nb); /** * Close connection to the wiimote with the given id. @@ -103,6 +98,26 @@ public class WiiUseApi { */ native void setOrientThreshold(int id, float angle); + /** + * Set how much acceleration must change to generate an event. + * @param id id of the wiimote concerned + * @param value minimum value detected by an event + */ + native void setAccelThreshold(int id, float value); + + /** + * Set alpha smoothing parameter for the given id. + * @param id id of the wiimote concerned + * @param value alpha smoothing value + */ + native void setSmoothAlpha(int id, float value); + + /** + * Try to resync with the wiimote by starting a new handshake. + * @param id id of the wiimote concerned + */ + native void reSync(int id); + /** * Make the the accelerometers give smoother results. * This is set by default. @@ -137,26 +152,27 @@ public class WiiUseApi { native void getStatus(int id); /** - * Get status and values from the wiimotes and send it through callbacks. + * Check for new Events and Get it. * - * @param mote The WiimoteEvent object to fill with the datas. + * @param gath the object where we store all the new events. */ - native void specialPoll(WiiMoteEvent mote); + native void specialPoll(EventsGatherer gath); /* Tests */ public static void main(String[] args) { /* Test JNI Side */ + /* WiiUseApi manager = new WiiUseApi(); int value = manager.loadLibrary(); System.out.println("loadLibrary : " + value); value = manager.doConnections(); - System.out.println("doConnections : " + value); - - WiiMoteEvent mote = new WiiMoteEvent(); + System.out.println("doConnections : " + value); + WiiMoteEvent mote = new WiiMoteEvent(); + manager.getStatus(1); System.out.println("Status : \n" + mote); @@ -168,6 +184,7 @@ public class WiiUseApi { System.out.println(mote); mote.EmptyIRPoints(); } + */ // manager.closeConnectionsAndShutDown(); } diff --git a/WiiUseJ/src/wiiusej/WiiUseApiListener.java b/WiiUseJ/src/wiiusej/WiiUseApiListener.java deleted file mode 100644 index 81f3792..0000000 --- a/WiiUseJ/src/wiiusej/WiiUseApiListener.java +++ /dev/null @@ -1,9 +0,0 @@ -package wiiusej; - - - -public interface WiiUseApiListener extends java.util.EventListener { - - void wiimoteEvent(WiiMoteEvent e); - -} diff --git a/WiiUseJ/src/wiiusej/WiiUseApiManager.java b/WiiUseJ/src/wiiusej/WiiUseApiManager.java index 7ab9825..cc1de77 100644 --- a/WiiUseJ/src/wiiusej/WiiUseApiManager.java +++ b/WiiUseJ/src/wiiusej/WiiUseApiManager.java @@ -1,4 +1,4 @@ -package wiiusej; + package wiiusej; import java.util.ArrayList; import java.util.concurrent.ConcurrentLinkedQueue; @@ -6,6 +6,13 @@ import java.util.concurrent.atomic.AtomicBoolean; import javax.swing.event.EventListenerList; +import wiiusej.wiiuseapievents.EventsGatherer; +import wiiusej.wiiuseapievents.WiiUseApiEvent; +import wiiusej.wiiuseapievents.WiiUseApiListener; +import wiiusej.wiiuseapirequest.FloatValueRequest; +import wiiusej.wiiuseapirequest.LedsRequest; +import wiiusej.wiiuseapirequest.WiiUseApiRequest; + /** * Class that manage the use of Wiiuse API. * @@ -19,84 +26,66 @@ public class WiiUseApiManager extends Thread { private final EventListenerList listeners = new EventListenerList(); private Wiimote[] wiimotes; - - private WiiUseApi wiiuse = WiiUseApi.getInstance(); - private boolean loaded = false; + private WiiUseApi wiiuse = WiiUseApi.getInstance(); private int connected = -1; + private int nbMaxWiimotes = -1; + private AtomicBoolean running = new AtomicBoolean(false); - private ConcurrentLinkedQueue requests = new ConcurrentLinkedQueue(); + private ConcurrentLinkedQueue requests = new ConcurrentLinkedQueue(); public static WiiUseApiManager getInstance() { return instance; } - - /** - * Get wiimotes. - * Load library if necessary. - * Connect to wiimotes if necessary. - * Start polling if necessary. - * Return an array with the connected wiimotes. - * @return an array with connected wiimotes or NULL. - */ - public static Wiimote[] getWiimotes() { - WiiUseApiManager manager = getInstance(); - if (!manager.loaded){ - manager.loadLibrary(); - } - if (manager.connected<0){ - int nbWiimotes = manager.connectWiimotes(); - manager.wiimotes = new Wiimote[nbWiimotes]; - for (int i=0; i 0) { - loaded = true; - return true; - } else { - loaded = false; - System.out.println("Error loading the Wiimote library !!!"); - return false; + public static Wiimote[] getWiimotes(int nb) { + WiiUseApiManager manager = getInstance(); + if (manager.connected < 0) { + int nbWiimotes = manager.connectWiimotes(nb); + manager.wiimotes = new Wiimote[nbWiimotes]; + for (int i = 0; i < nbWiimotes; i++) { + Wiimote wim = new Wiimote(i, manager); + manager.wiimotes[i] = wim; + manager.addWiiUseApiListener(wim); } } - // already loaded - return loaded; + + if (manager.connected == 0) { + return new Wiimote[0]; + } + + if (!manager.isAlive()) + manager.start(); + + return manager.wiimotes; } /** * Connect wiimote and get the number of wiimotes connected. Supposed to be * used once. * + * @param nb + * try to connect nb wiimotes * @return 0 if nothing connected or the number of wiimotes connected. */ - public int connectWiimotes() { - if (connected < 0 && loaded) { - connected = wiiuse.doConnections(); - // System.out.println(connected + " wiimote(s) connected !!!"); + public int connectWiimotes(int nb) { + nbMaxWiimotes = nb; + if (connected < 0) { + connected = wiiuse.doConnections(nb); + // @TODO + System.out.println(connected + " wiimote(s) connected !!!"); return connected; } else {// library not loaded, no wiimotes connected return 0; @@ -109,9 +98,12 @@ public class WiiUseApiManager extends Thread { * @param id * id of the wiimote to disconnect. */ - public void closeConnection(int id) { + public void closeConnection(int id) { + removeWiiUseApiListener(wiimotes[id]); + wiimotes[id] = null; requests.add(new WiiUseApiRequest(id, WiiUseApiRequest.WIIUSE_CLOSE_CONNECTION_REQUEST)); + System.out.println("Wiimote " + id + " disconnected !"); } /** @@ -128,7 +120,6 @@ public class WiiUseApiManager extends Thread { */ public void shutdown() { running.set(false); - loaded = false; wiiuse.shutdownApi(); } @@ -270,10 +261,36 @@ public class WiiUseApiManager extends Thread { * threshold in degrees */ public void setOrientationThreshold(int id, float th) { - requests.add(new OrientThresholdRequest(id, + requests.add(new FloatValueRequest(id, WiiUseApiRequest.WIIUSE_ORIENT_THRESHOLHD_REQUEST, th)); } + /** + * Set the acceleration threshold for the given id. + * + * @param id + * id of the wiimote + * @param th + * threshold + */ + public void setAccelerationThreshold(int id, float th) { + requests.add(new FloatValueRequest(id, + WiiUseApiRequest.WIIUSE_ACCEL_THRESHOLHD_REQUEST, th)); + } + + /** + * Set alpha smoothing for the given id. + * + * @param id + * id of the wiimote + * @param th + * threshold + */ + public void setAlphaSmoothing(int id, float th) { + requests.add(new FloatValueRequest(id, + WiiUseApiRequest.WIIUSE_ALPHA_SMOOTHING_REQUEST, th)); + } + /** * Get Status for the wiimote for the given id. * @@ -288,10 +305,10 @@ public class WiiUseApiManager extends Thread { @Override public void run() { - if (loaded && (connected > 0)) { + if (connected > 0) { running.set(true); - WiiMoteEvent evt = new WiiMoteEvent(); + EventsGatherer gather = new EventsGatherer(nbMaxWiimotes); // Start polling and tell the observers when there Wiimote events while (running.get()) { @@ -299,11 +316,15 @@ public class WiiUseApiManager extends Thread { if (req != null) {// there is a request for the wiiuse api int id = req.getId(); if (req.getRequestType() == WiiUseApiRequest.WIIUSE_CLOSE_CONNECTION_REQUEST) { - /* Close connections requests */ - removeWiiUseApiListener(wiimotes[id]); - wiimotes[id]=null; + /* Close connections requests */ wiiuse.closeConnection(id); + connected--; + if (connected == 0) {// stop this thread if there is + // no more wiimotes connected. + System.out.println("No more wiimotes connected !!!"); + shutdown(); + } } else if (req.getRequestType() == WiiUseApiRequest.WIIUSE_STATUS_REQUEST) { /* Status requests */ wiiuse.getStatus(id); @@ -343,42 +364,44 @@ public class WiiUseApiManager extends Thread { /* Deactivate continuous requests */ wiiuse.deactivateContinuous(id); } else if (req.getRequestType() == WiiUseApiRequest.WIIUSE_ORIENT_THRESHOLHD_REQUEST) { - /* set orientation request */ + /* set orientation threshold request */ wiiuse.setOrientThreshold(req.getId(), - ((OrientThresholdRequest) req).getThresholhd()); + ((FloatValueRequest) req).getFloatValue()); + } else if (req.getRequestType() == WiiUseApiRequest.WIIUSE_ACCEL_THRESHOLHD_REQUEST) { + /* set acceleration threshold request */ + wiiuse.setOrientThreshold(req.getId(), + ((FloatValueRequest) req).getFloatValue()); + } else if (req.getRequestType() == WiiUseApiRequest.WIIUSE_ALPHA_SMOOTHING_REQUEST) { + /* set alpha smoothing request */ + wiiuse.setOrientThreshold(req.getId(), + ((FloatValueRequest) req).getFloatValue()); } else { System.out.println("Bad request to Wiiuse API !!!!!"); } } /* Polling */ - wiiuse.specialPoll(evt); + wiiuse.specialPoll(gather); - if (evt.getWiimoteId() != -1) {//event filled - if (!evt.isConnected()) {// check if it was a - // disconnection - connected--; - removeWiiUseApiListener(wiimotes[evt.getWiimoteId()]); - wiimotes[evt.getWiimoteId()]=null; - System.out.println("Wiimote " + evt.getWiimoteId() - + " disconnected !"); - } else {//there is an event notify observers - // not a disconnection notify listeners + /* deal with events gathered in Wiiuse API */ + for (WiiUseApiEvent evt : gather.getEvents()) { + if (evt.getWiimoteId() != -1) {// event filled + // there is an event notify observers notifyWiiUseApiListener(evt); - // create a new event to be filled - evt = new WiiMoteEvent(); - } - } - if (connected == 0) {// stop this thread if there is - //no more wiimotes connected. - System.out.println("No more wiimotes connected !!!"); - shutdown(); - } + if (evt.getEventType() == WiiUseApiEvent.DISCONNECTION_EVENT) { + // check if it was a disconnection + // in this case disconnect the wiimote + closeConnection(evt.getWiimoteId()); + } + } else { + System.out + .println("There is an event with id == -1 ??? there is a problem !!! : " + + evt); + } + } + gather.clearEvents(); } } else { - if (!loaded) { - System.out.println("Library not Loaded !"); - } if (connected <= 0) { System.out.println("No wiimotes connected !"); } @@ -421,9 +444,9 @@ public class WiiUseApiManager extends Thread { * @param evt * WiimoteEvent occured */ - private void notifyWiiUseApiListener(WiiMoteEvent evt) { + private void notifyWiiUseApiListener(WiiUseApiEvent evt) { for (WiiUseApiListener listener : getWiiUseApiListeners()) { - listener.wiimoteEvent(evt); + listener.wiiUseApiEvent(evt); } } diff --git a/WiiUseJ/src/wiiusej/Wiimote.java b/WiiUseJ/src/wiiusej/Wiimote.java index 5d7232e..7f8b198 100644 --- a/WiiUseJ/src/wiiusej/Wiimote.java +++ b/WiiUseJ/src/wiiusej/Wiimote.java @@ -2,6 +2,13 @@ package wiiusej; import javax.swing.event.EventListenerList; +import wiiusej.wiiuseapievents.DisconnectionEvent; +import wiiusej.wiiuseapievents.StatusEvent; +import wiiusej.wiiuseapievents.WiiMoteEvent; +import wiiusej.wiiuseapievents.WiiUseApiEvent; +import wiiusej.wiiuseapievents.WiiUseApiListener; +import wiiusej.wiiuseapievents.WiimoteListener; + /** * Class that represents a wiimote. * You can register as an observer of this wiimote to listen events from it. @@ -11,7 +18,7 @@ import javax.swing.event.EventListenerList; */ public class Wiimote implements WiiUseApiListener { - private int id;//wiimote id + private int id = -1;//wiimote id private EventListenerList listeners = new EventListenerList(); @@ -140,15 +147,20 @@ public class Wiimote implements WiiUseApiListener { public void getStatus() { manager.getStatus(id); } - - - @Override - public void wiimoteEvent(WiiMoteEvent e) { - if (e.getWiimoteId() == id){ - notifyWiiMoteEventListeners(e); - } - } + @Override + public void wiiUseApiEvent(WiiUseApiEvent e) { + if (e.getWiimoteId() == id){ + if (e.getEventType() == WiiUseApiEvent.GENERIC_EVENT){ + notifyWiiMoteEventListeners((WiiMoteEvent)e); + }else if (e.getEventType() == WiiUseApiEvent.STATUS_EVENT){ + notifyStatusEventListeners((StatusEvent)e); + }else if (e.getEventType() == WiiUseApiEvent.DISCONNECTION_EVENT){ + notifyDisconnectionEventListeners((DisconnectionEvent)e); + } + } + } + /** * Add a WiimoteListener to the listeners list. * @param listener a WiimoteListener @@ -183,9 +195,29 @@ public class Wiimote implements WiiUseApiListener { } } + /** + * Notify WiimoteListener that a status event occured. + * @param evt status event occured + */ + private void notifyStatusEventListeners(StatusEvent evt) { + for (WiimoteListener listener : getWiiMoteEventListeners()) { + listener.statusEvent(evt); + } + } + + /** + * Notify WiimoteListener that a status event occured. + * @param evt status event occured + */ + private void notifyDisconnectionEventListeners(DisconnectionEvent evt) { + for (WiimoteListener listener : getWiiMoteEventListeners()) { + listener.disconnectionEvent(evt); + } + } + @Override public String toString() { return "Wiimote with ID : "+id; - } + } } diff --git a/WiiUseJ/src/wiiusej/WiimoteListener.java b/WiiUseJ/src/wiiusej/WiimoteListener.java deleted file mode 100644 index b0cb6cd..0000000 --- a/WiiUseJ/src/wiiusej/WiimoteListener.java +++ /dev/null @@ -1,8 +0,0 @@ -package wiiusej; - - -public interface WiimoteListener extends java.util.EventListener { - - void wiimoteEvent(WiiMoteEvent e); - -} diff --git a/WiiUseJ/src/wiiusej/wiiuseapievents/DisconnectionEvent.java b/WiiUseJ/src/wiiusej/wiiuseapievents/DisconnectionEvent.java new file mode 100644 index 0000000..8c0da63 --- /dev/null +++ b/WiiUseJ/src/wiiusej/wiiuseapievents/DisconnectionEvent.java @@ -0,0 +1,24 @@ +package wiiusej.wiiuseapievents; + +public class DisconnectionEvent extends WiiUseApiEvent { + + /** + * Construct the DisconnectionEvent setting up the id. + * + * @param id + * the Wiimote id + */ + public DisconnectionEvent(int id) { + super(id,WiiUseApiEvent.DISCONNECTION_EVENT); + } + + @Override + public String toString() { + String out = ""; + /* Status */ + out += "/*********** DISCONNECTION EVENT : WIIMOTE ID :" + super.getWiimoteId() + " ********/\n"; + + return out; + } + +} diff --git a/WiiUseJ/src/wiiusej/wiiuseapievents/EventsGatherer.java b/WiiUseJ/src/wiiusej/wiiuseapievents/EventsGatherer.java new file mode 100644 index 0000000..699348f --- /dev/null +++ b/WiiUseJ/src/wiiusej/wiiuseapievents/EventsGatherer.java @@ -0,0 +1,180 @@ +package wiiusej.wiiuseapievents; + +import tests.Tests; + +/** + * Gather events in a call to the Wiiuse API. + * + * @author gduche + * + */ +public class EventsGatherer { + + private WiiUseApiEvent[] events; + private int index = 0; + private WiiMoteEvent genericEvent = null; + + /** + * Create EventsGatherer. + * + * @param nbWiimotes + * nb wiimotes (nb a of events possible in a call to Wiiuse API) + */ + public EventsGatherer(int nbWiimotes) { + events = new WiiUseApiEvent[nbWiimotes]; + } + + /** + * Add an event to the array. + * + * @param e + * the event to add. + */ + private void addEvent(WiiUseApiEvent e) { + events[index] = e; + index++; + } + + /** + * Prepare a wiimote event to add. + * + * @param id + * id of the wiimote. + * @param buttonsJustPressed + * buttons just pressed + * @param buttonsJustReleased + * buttons just released + * @param buttonsHeld + * buttons held + */ + public void prepareWiiMoteEvent(int id, short buttonsJustPressed, + short buttonsJustReleased, short buttonsHeld) { + genericEvent = new WiiMoteEvent(id, buttonsJustPressed, + buttonsJustReleased, buttonsHeld); + } + + /** + * Add an IR point to the WiiMoteEvent prepared + * + * @param x + * x coordinates + * @param y + * y coordinates + */ + public void addIRPointToPreparedWiiMoteEvent(int x, int y) { + if (genericEvent != null) { + genericEvent.addIRpoint(x, y); + } + } + + /** + * Set orientation and gravity force of the prepared event. + * + * @param r + * roll + * @param p + * pitch + * @param ya + * yaw + * @param x + * gravity force on x axis + * @param y + * gravity force on y axis + * @param z + * gravity force on z axis + */ + public void addMotionSensingValues(float r, float p, float ya, float x, + float y, float z) { + if (genericEvent != null) { + genericEvent.setOrientationAndGforce(r, p, ya, x, y, z); + } + } + + /** + * Add the prepared WiimoteEvent to the gatherer. + */ + public void addWiimoteEvent() { + if (genericEvent != null) { + addEvent(genericEvent); + genericEvent = null; + } + } + + /** + * Add a StatusEvent to the gatherer. + * + * @param id + * id of the wiimote + * @param connect + * true if the wiimote is connected + * @param batt + * battery level + * @param led + * status of leds + * @param speak + * speakers status + * @param attach + * attachment status + * @param rumbleState + * true if rumble is active + * @param orientationThreshold + * value of the minimum angle between two events with the + * accelerometer + * @param accelerationThreshold + * value of the value variation between two events with the + * accelerometer + * @param alphaSmooth + * value of the alpha smoothing parameter + * @param continuousState + * true if continuous flag is activated + * @param smoothingState + * true if smoothing flag is activated + * @param irState + * true if ir is active + * @param motionSensingState + * true if accelerometer is active + */ + public void addStatusEvent(int id, boolean connect, float batt, short led, + boolean speak, int attach, boolean rumbleState, + float orientationThreshold, float accelerationThreshold, + float alphaSmooth, boolean continuousState, boolean smoothingState, + boolean irState, boolean motionSensingState) { + StatusEvent evt = new StatusEvent(id, connect, batt, led, speak, + attach, rumbleState, orientationThreshold, + accelerationThreshold, alphaSmooth, continuousState, + smoothingState, irState, motionSensingState); + addEvent(evt); + } + + /** + * Add a DisconnectionEvent to the gatherer. + * + * @param id + * id of the wiimote + */ + public void addDisconnectionEvent(int id) { + DisconnectionEvent evt = new DisconnectionEvent(id); + addEvent(evt); + } + + /** + * Return an array containing the events. + * + * @return + */ + public WiiUseApiEvent[] getEvents() { + return java.util.Arrays.copyOfRange(events, 0, index); + } + + /** + * Clear the gatherer and remove objects. + */ + public void clearEvents() { + for (int i = 0; i < events.length; i++) { + events[i] = null; + } + genericEvent = null; + index = 0; + } + +} diff --git a/WiiUseJ/src/wiiusej/wiiuseapievents/StatusEvent.java b/WiiUseJ/src/wiiusej/wiiuseapievents/StatusEvent.java new file mode 100644 index 0000000..cff2f67 --- /dev/null +++ b/WiiUseJ/src/wiiusej/wiiuseapievents/StatusEvent.java @@ -0,0 +1,251 @@ +package wiiusej.wiiuseapievents; + +public class StatusEvent extends WiiUseApiEvent { + + private static short WIIMOTE_LED_1 = 1; + private static short WIIMOTE_LED_2 = 2; + private static short WIIMOTE_LED_3 = 4; + private static short WIIMOTE_LED_4 = 8; + + /* ATTACHMENT CONSTANTS */ + + private static short EXP_NONE = 0; + private static short EXP_NUNCHUK = 1; + private static short EXP_CLASSIC = 2; + private static short EXP_GUITAR_HERO_3 = 3; + + /* Status variables */ + private boolean connected = false; + + private float batteryLevel = -1; + + private short leds = 0; + + private boolean isSpeakerEnabled = false; + + private int attachment = 0; + + private boolean isRumbleActive = false; + + private float orientationThreshold = 0; + + private float accelerationThreshold = 0; + + private float alphaSmoothing = 0; + + private boolean isContinuousActive = false; + + private boolean isSmoothingActive = false; + + private boolean isIrActive = false; + + private boolean isMotionSensingActive = false; + + /** + * Construct the StatusEvent setting up the id. + * + * @param id + * the Wiimote id + */ + public StatusEvent(int id) { + super(id, WiiUseApiEvent.STATUS_EVENT); + } + + /** + * Build a StatusEvent with all fields set. + * + * @param id + * id of the wiimote + * @param connect + * true if the wiimote is connected + * @param batt + * battery level + * @param led + * status of leds + * @param speak + * speakers status + * @param attach + * attachment status + * @param rumbleState + * true if rumble is active + * @param orientationThreshold + * value of the minimum angle between two events with the + * accelerometer + * @param accelerationThreshold + * value of the value variation between two events with the + * accelerometer + * @param alphaSmooth + * value of the alpha smoothing parameter + * @param continuousState + * true if continuous flag is activated + * @param smoothingState + * true if smoothing flag is activated + * @param irState + * true if ir is active + * @param motionSensingState + * true if accelerometer is active + */ + public StatusEvent(int id, boolean connect, float batt, short led, + boolean speak, int attach, boolean rumbleState, + float orientationThreshold, float accelerationThreshold, + float alphaSmooth, boolean continuousState, boolean smoothingState, + boolean irState, boolean motionSensingState) { + super(id, WiiUseApiEvent.STATUS_EVENT); + connected = connect; + this.batteryLevel = batt; + this.leds = led; + this.isSpeakerEnabled = speak; + this.attachment = attach; + isRumbleActive = rumbleState; + this.orientationThreshold = orientationThreshold; + this.accelerationThreshold = accelerationThreshold; + alphaSmoothing = alphaSmooth; + isContinuousActive = continuousState; + isSmoothingActive = smoothingState; + isIrActive = irState; + isMotionSensingActive = motionSensingState; + } + + /** + * True if the wiimote is connected false otherwise. + * + * @return return the connected status. + */ + public boolean isConnected() { + return connected; + } + + /** + * Get battery level. + * + * @return battery level. 1 = 100% + */ + public float getBatteryLevel() { + return batteryLevel; + } + + /** + * Get status of the leds . + * + * @return a short representing LEDS turned on. + */ + public short getLeds() { + return leds; + } + + /** + * Tell if the speaker is enable for this wiimote + * + * @return TRUE if it enabled false otherwise + */ + public boolean isSpeakerEnabled() { + return isSpeakerEnabled; + } + + /** + * Get the int representing the attachment type. + * + * @return value of the Attachment Type + */ + public int getAttachment() { + return attachment; + } + + /** + * Get the status of rumble. + * + * @return true if the rumble is active false otherwise + */ + public boolean isRumbleActive() { + return isRumbleActive; + } + + /** + * Get orientation threshold. + * + * @return the orientationThreshold + */ + public float getOrientationThreshold() { + return orientationThreshold; + } + + /** + * Get acceleration threshold. + * + * @return the accelerationThreshold + */ + public float getAccelerationThreshold() { + return accelerationThreshold; + } + + /** + * Get alpha smoothing. + * + * @return the alphaSmoothing + */ + public float getAlphaSmoothing() { + return alphaSmoothing; + } + + /** + * Tell if the CONTINUOUS option is activated. + * + * @return the isContinuousActive + */ + public boolean isContinuousActive() { + return isContinuousActive; + } + + /** + * Tell if the option SMOOTHING is activated. + * + * @return the isSmoothingActive + */ + public boolean isSmoothingActive() { + return isSmoothingActive; + } + + /** + * Tell if the IR Tracking is active. + * + * @return TRUE if it is active or false otherwise. + */ + public boolean isIrActive() { + return isIrActive; + } + + /** + * Get the flag indicating if the motion sensing is active. + * + * @return true if the motion sensing is active false otherwise + */ + public boolean isMotionSensingActive() { + return isMotionSensingActive; + } + + @Override + public String toString() { + String out = ""; + /* Status */ + out += "/*********** STATUS EVENT : WIIMOTE ID :" + + super.getWiimoteId() + " ********/\n"; + out += "--- connected : " + connected + "\n"; + out += "--- Battery level : " + batteryLevel + "\n"; + out += "--- Leds : " + leds + "\n"; + out += "--- Speaker enabled : " + isSpeakerEnabled + "\n"; + out += "--- Attachment ? : " + attachment + "\n"; + out += "--- Rumble ? : " + isRumbleActive + "\n"; + out += "--- Orientation threshold value ? : " + orientationThreshold + + "\n"; + out += "--- Acceleration threshold value ? : " + accelerationThreshold + + "\n"; + out += "--- Alpha smoothing threshold value ? : " + alphaSmoothing + + "\n"; + out += "--- Continuous ? : " + isContinuousActive + "\n"; + out += "--- Smoothing ? : " + isSmoothingActive + "\n"; + out += "--- IR active ? : " + isIrActive + "\n"; + out += "--- Motion sensing active ? : " + isMotionSensingActive + "\n"; + return out; + } + +} diff --git a/WiiUseJ/src/wiiusej/WiiMoteEvent.java b/WiiUseJ/src/wiiusej/wiiuseapievents/WiiMoteEvent.java similarity index 58% rename from WiiUseJ/src/wiiusej/WiiMoteEvent.java rename to WiiUseJ/src/wiiusej/wiiuseapievents/WiiMoteEvent.java index 8e2ee7c..cea9ed7 100644 --- a/WiiUseJ/src/wiiusej/WiiMoteEvent.java +++ b/WiiUseJ/src/wiiusej/wiiuseapievents/WiiMoteEvent.java @@ -1,15 +1,19 @@ -package wiiusej; +package wiiusej.wiiuseapievents; import java.util.ArrayList; import java.util.Iterator; +import wiiusej.GForce; +import wiiusej.Orientation; +import wiiusej.Point2DInteger; + /** * Class that is a bean to be filled by the wiiuse API. * * @author gduche * */ -public class WiiMoteEvent { +public class WiiMoteEvent extends WiiUseApiEvent { /* Buttons MACRO */ private static short WIIMOTE_BUTTON_TWO = 0x0001; @@ -30,34 +34,7 @@ public class WiiMoteEvent { private static int WIIMOTE_BUTTON_UNKNOWN = 0x8000; private static short WIIMOTE_BUTTON_ALL = 0x1F9F; - private static short WIIMOTE_LED_1 = 1; - private static short WIIMOTE_LED_2 = 2; - private static short WIIMOTE_LED_3 = 4; - private static short WIIMOTE_LED_4 = 8; - - private static short NB_LEDS = 4; - - /* ID */ - private int wiimoteId = -1; - - /* Status variables */ - private boolean connected = false; - - private float batteryLevel = -1; - - private short leds = 0; - - private boolean isSpeakerEnabled = false; - - private boolean isThereAttachment = false; - - private boolean isRumbleActive = false; - - private float orientationThreshold = 0; - - private boolean isContinuousActive = false; - - private boolean isSmoothingActive = false; + private static short NB_POINTS = 4;// number of points IR can track /* Buttons */ private short buttonsJustPressed = 0; @@ -73,14 +50,6 @@ public class WiiMoteEvent { private Orientation orientation; private GForce gforce; - /** - * Default constructor - */ - public WiiMoteEvent() { - // init IRPoints array - IRPoints = new Point2DInteger[NB_LEDS]; - } - /** * Construct the Wiimote setting up the id. * @@ -88,153 +57,40 @@ public class WiiMoteEvent { * the Wiimote id */ public WiiMoteEvent(int id) { - this(); - wiimoteId = id; + super(id, WiiUseApiEvent.GENERIC_EVENT); + IRPoints = new Point2DInteger[NB_POINTS]; } /** - * Get Wiimote ID + * Construct the Wiimote setting up the id and the buttons. * - * @return the wiimote id. + * @param id + * the Wiimote id + * @param buttonsJustPressed + * buttons just pressed + * @param buttonsJustReleased + * buttons just released + * @param buttonsHeld + * buttons held */ - public int getWiimoteId() { - return wiimoteId; + public WiiMoteEvent(int id, short buttonsJustPressed, + short buttonsJustReleased, short buttonsHeld) { + super(id, WiiUseApiEvent.GENERIC_EVENT); + IRPoints = new Point2DInteger[NB_POINTS]; + setAllButtons(buttonsJustPressed, buttonsJustReleased, buttonsHeld); } /** - * Set Wiimote ID + * Construct the Wiimote setting up the id, the buttons and the infared. * - * @param wiimoteId - * id of the wiimote */ - void setWiimoteId(int wiimoteId) { - this.wiimoteId = wiimoteId; - } /** - * True if the wiimote is connected false otherwise. + * Construct the Wiimote setting up the id, the buttons, the accelerometer + * and the infrared. * - * @return return the connected status. */ - public boolean isConnected() { - return connected; - } - /** - * Set the connected value to true. - */ - void setConnected() { - this.connected = true; - } - - /** - * Set the connected value to false. - */ - void setDisconnected() { - this.connected = false; - } - - /** - * Get battery level. - * - * @return battery level. 1 = 100% - */ - public float getBatteryLevel() { - return batteryLevel; - } - - /** - * Get status of the leds . - * - * @return a short representing LEDS turned on. - */ - public short getLeds() { - return leds; - } - - /** - * Tell if the speaker is enable for this wiimote - * - * @return TRUE if it enabled false otherwise - */ - public boolean isSpeakerEnabled() { - return isSpeakerEnabled; - } - - /** - * Tell if there is an attachment to the Wiimote - * - * @return TRUE if it there is one false otherwise - */ - public boolean isThereAttachment() { - return isThereAttachment; - } - - /** - * Set battery level, leds, speaker state and attachment in one method. - * These method is called (and those variables are filled) only when a - * status has been requested on this wiimote. - * - * @param batt - * battery level - * @param led - * status of leds - * @param speak - * speakers status - * @param attach - * attachment status - */ - void setBatteryLedsSpeakerAttachment(float batt, short led, boolean speak, - boolean attach) { - this.batteryLevel = batt; - this.leds = led; - this.isSpeakerEnabled = speak; - this.isThereAttachment = attach; - } - - /** - * Get the status of rumble. - * - * @return true if the rumble is active false otherwise - */ - public boolean isRumbleActive() { - return isRumbleActive; - } - - /** - * Set Rumble flag to Inactive. - */ - void setRumbleInactive() { - this.isRumbleActive = false; - } - - /** - * Get orientation threshold. - * - * @return the orientationThreshold - */ - public float getOrientationThreshold() { - return orientationThreshold; - } - - /** - * Tell if the CONTINUOUS option is activated. - * - * @return the isContinuousActive - */ - public boolean isContinuousActive() { - return isContinuousActive; - } - - /** - * Tell if the option SMOOTHING is activated. - * - * @return the isSmoothingActive - */ - public boolean isSmoothingActive() { - return isSmoothingActive; - } - /** * Get the short storing the buttons just pressed * @@ -269,8 +125,8 @@ public class WiiMoteEvent { * @param buttonsJustReleased * @param buttonsHeld */ - void setAllButtons(short buttonsJustPressed, short buttonsJustReleased, - short buttonsHeld) { + private void setAllButtons(short buttonsJustPressed, + short buttonsJustReleased, short buttonsHeld) { this.buttonsJustPressed = buttonsJustPressed; this.buttonsJustReleased = buttonsJustReleased; this.buttonsHeld = buttonsHeld; @@ -302,7 +158,8 @@ public class WiiMoteEvent { * @param y * y value */ - void addIRpoint(int x, int y) { + public void addIRpoint(int x, int y) { + isIrActive = true; for (int i = 0; i < IRPoints.length; i++) { if (IRPoints[i] == null) { IRPoints[i] = new Point2DInteger(x, y); @@ -312,15 +169,6 @@ public class WiiMoteEvent { return; } - /** - * Clear IR points. - */ - void EmptyIRPoints() { - for (int i = 0; i < IRPoints.length; i++) { - IRPoints[i] = null; - } - } - /** * Get the flag indicating if the motion sensing is active. * @@ -330,41 +178,6 @@ public class WiiMoteEvent { return isMotionSensingActive; } - /** - * Set status variables always filled during an event. - * - * @param id - * id of the wiimote - * @param connect - * true if the wiimote is connected - * @param irState - * true if ir is active - * @param rumbleState - * true if rumble is active - * @param motionSensingState - * true if accelerometer is active - * @param orientationThreshold - * value of the minimum angle between two events with the - * accelerometer - * @param continuousState - * true if continuous flag is activated - * @param smoothingState - * true if smoothing flag is activated - */ - void setPermanentStatus(int id, boolean connect, boolean irState, - boolean rumbleState, boolean motionSensingState, - float orientationThreshold, boolean continuousState, - boolean smoothingState) { - wiimoteId = id; - connected = connect; - isIrActive = irState; - isRumbleActive = rumbleState; - isMotionSensingActive = motionSensingState; - this.orientationThreshold = orientationThreshold; - isContinuousActive = continuousState; - isSmoothingActive = smoothingState; - } - /** * @return the orientation */ @@ -397,8 +210,9 @@ public class WiiMoteEvent { * @param z * gravity force on z axis */ - void setOrientationAndGforce(float r, float p, float ya, float x, float y, - float z) { + public void setOrientationAndGforce(float r, float p, float ya, float x, + float y, float z) { + this.isMotionSensingActive = true; this.orientation = new Orientation(r, p, ya); this.gforce = new GForce(x, y, z); } @@ -579,20 +393,10 @@ public class WiiMoteEvent { @Override public String toString() { - super.toString(); String out = ""; /* Status */ - out += "/*********** WIIMOTE ID :" + wiimoteId + " ********/\n"; - out += "--- connected : " + connected + "\n"; - out += "--- Battery level : " + batteryLevel + "\n"; - out += "--- Leds : " + leds + "\n"; - out += "--- Speaker enabled : " + isSpeakerEnabled + "\n"; - out += "--- Attachment ? : " + isThereAttachment + "\n"; - out += "--- Rumble ? : " + isRumbleActive + "\n"; - out += "--- Orientation threshold value ? : " + orientationThreshold - + "\n"; - out += "--- Continuous ? : " + isContinuousActive + "\n"; - out += "--- Smoothing ? : " + isSmoothingActive + "\n"; + out += "/*********** GENERIC EVENT : WIIMOTE ID :" + + super.getWiimoteId() + " ********/\n"; /* Display buttons */ out += "/******** Buttons ********/\n"; out += "--- Buttons just pressed : " + buttonsJustPressed + "\n"; diff --git a/WiiUseJ/src/wiiusej/wiiuseapievents/WiiUseApiEvent.java b/WiiUseJ/src/wiiusej/wiiuseapievents/WiiUseApiEvent.java new file mode 100644 index 0000000..10eab44 --- /dev/null +++ b/WiiUseJ/src/wiiusej/wiiuseapievents/WiiUseApiEvent.java @@ -0,0 +1,68 @@ +package wiiusej.wiiuseapievents; + +public abstract class WiiUseApiEvent { + + public static int GENERIC_EVENT = 1; + public static int STATUS_EVENT = 2; + public static int DISCONNECTION_EVENT = 3; + + + /* Event Type */ + private int eventType; + + /* ID */ + private int wiimoteId = -1; + + /** + * Default constructor. + */ + public WiiUseApiEvent(){ + } + + /** + * Construct the WiiUseApiEvent setting up the id. + * + * @param id + * the Wiimote id + * @param type + * type of the event + */ + public WiiUseApiEvent(int id, int type) { + wiimoteId = id; + eventType = type; + } + + + /** + * Get Wiimote ID + * + * @return the wiimote id. + */ + public int getWiimoteId() { + return wiimoteId; + } + + /** + * Set Wiimote ID + * + * @param wiimoteId + * id of the wiimote + */ + void setWiimoteId(int wiimoteId) { + this.wiimoteId = wiimoteId; + } + + + + /** + * Get the event type. + * @return the eventType + */ + public int getEventType() { + return eventType; + } + + public abstract String toString(); + + +} diff --git a/WiiUseJ/src/wiiusej/wiiuseapievents/WiiUseApiListener.java b/WiiUseJ/src/wiiusej/wiiuseapievents/WiiUseApiListener.java new file mode 100644 index 0000000..c02477e --- /dev/null +++ b/WiiUseJ/src/wiiusej/wiiuseapievents/WiiUseApiListener.java @@ -0,0 +1,15 @@ +package wiiusej.wiiuseapievents; + + + +/** + * Interface defining the methods a WiiUseApiListener must have + * @author gduche + * + */ +public interface WiiUseApiListener extends java.util.EventListener { + + void wiiUseApiEvent(WiiUseApiEvent e); + + +} diff --git a/WiiUseJ/src/wiiusej/wiiuseapievents/WiimoteListener.java b/WiiUseJ/src/wiiusej/wiiuseapievents/WiimoteListener.java new file mode 100644 index 0000000..3b7adb6 --- /dev/null +++ b/WiiUseJ/src/wiiusej/wiiuseapievents/WiimoteListener.java @@ -0,0 +1,12 @@ +package wiiusej.wiiuseapievents; + + +public interface WiimoteListener extends java.util.EventListener { + + void wiimoteEvent(WiiMoteEvent e); + + void statusEvent(StatusEvent e); + + void disconnectionEvent(DisconnectionEvent e); + +} diff --git a/WiiUseJ/src/wiiusej/OrientThresholdRequest.java b/WiiUseJ/src/wiiusej/wiiuseapirequest/FloatValueRequest.java similarity index 55% rename from WiiUseJ/src/wiiusej/OrientThresholdRequest.java rename to WiiUseJ/src/wiiusej/wiiuseapirequest/FloatValueRequest.java index 0d3cc14..ed400fa 100644 --- a/WiiUseJ/src/wiiusej/OrientThresholdRequest.java +++ b/WiiUseJ/src/wiiusej/wiiuseapirequest/FloatValueRequest.java @@ -1,4 +1,4 @@ -package wiiusej; +package wiiusej.wiiuseapirequest; /** * Represents a request to set orientation Threshold in Wiiuse API. @@ -6,9 +6,9 @@ package wiiusej; * @author gduche * */ -public class OrientThresholdRequest extends WiiUseApiRequest { +public class FloatValueRequest extends WiiUseApiRequest { - private float thresholhd; + private float floatValue; /** * Constructor setting the id of the wiimote concerned. @@ -16,7 +16,7 @@ public class OrientThresholdRequest extends WiiUseApiRequest { * @param id * the id of the wiimote concerned. */ - public OrientThresholdRequest(int id, int type) { + public FloatValueRequest(int id, int type) { super(id, type); } @@ -30,23 +30,23 @@ public class OrientThresholdRequest extends WiiUseApiRequest { * @param th * threshold in degrees */ - public OrientThresholdRequest(int id, int type, float th) { + public FloatValueRequest(int id, int type, float th) { super(id, type); - thresholhd = th; + floatValue = th; } /** - * @return the thresholhd + * @return the float value */ - public float getThresholhd() { - return thresholhd; + public float getFloatValue() { + return floatValue; } /** - * @param thresholhd the thresholhd to set + * @param val the thresholhd to set */ - public void setThresholhd(float thresholhd) { - this.thresholhd = thresholhd; + public void setFloatValue(float val) { + this.floatValue = val; } diff --git a/WiiUseJ/src/wiiusej/LedsRequest.java b/WiiUseJ/src/wiiusej/wiiuseapirequest/LedsRequest.java similarity index 88% rename from WiiUseJ/src/wiiusej/LedsRequest.java rename to WiiUseJ/src/wiiusej/wiiuseapirequest/LedsRequest.java index c068ba3..b52d2c3 100644 --- a/WiiUseJ/src/wiiusej/LedsRequest.java +++ b/WiiUseJ/src/wiiusej/wiiuseapirequest/LedsRequest.java @@ -1,11 +1,11 @@ -package wiiusej; +package wiiusej.wiiuseapirequest; /** * Represents a request to set leds of the wiimote with WiiUse API. * @author gduche * */ -public class LedsRequest extends wiiusej.WiiUseApiRequest { +public class LedsRequest extends wiiusej.wiiuseapirequest.WiiUseApiRequest { private boolean led1, led2, led3, led4; diff --git a/WiiUseJ/src/wiiusej/WiiUseApiRequest.java b/WiiUseJ/src/wiiusej/wiiuseapirequest/WiiUseApiRequest.java similarity index 89% rename from WiiUseJ/src/wiiusej/WiiUseApiRequest.java rename to WiiUseJ/src/wiiusej/wiiuseapirequest/WiiUseApiRequest.java index 56998c7..528adb1 100644 --- a/WiiUseJ/src/wiiusej/WiiUseApiRequest.java +++ b/WiiUseJ/src/wiiusej/wiiuseapirequest/WiiUseApiRequest.java @@ -1,4 +1,4 @@ -package wiiusej; +package wiiusej.wiiuseapirequest; /** * Represents a request we could do to the WiiUse API. @@ -21,7 +21,8 @@ public class WiiUseApiRequest { public static int WIIUSE_DEACTIVATE_RUMBLE_REQUEST=-7; public static int WIIUSE_LEDS_REQUEST=8; public static int WIIUSE_ORIENT_THRESHOLHD_REQUEST=9; - + public static int WIIUSE_ACCEL_THRESHOLHD_REQUEST=10; + public static int WIIUSE_ALPHA_SMOOTHING_REQUEST=11; private int wiimoteId=0; private int requestType=0; diff --git a/WiiUseJ/wiiuse.dll b/WiiUseJ/wiiuse.dll index c6ea93fa8adb95a0baf88aa7dc92a353dbbc9293..f816e2165afa8f676dd7be003d562ae8d787eee5 100644 GIT binary patch delta 33823 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 X@#_pdK? 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