/* * Copyright (C) 2012 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package interactivespaces.hardware.driver.gaming.wii; import com.google.common.collect.Lists; import interactivespaces.InteractiveSpacesException; import interactivespaces.hardware.driver.DriverSupport; import interactivespaces.service.comm.serial.bluetooth.BluetoothCommunicationEndpoint; import interactivespaces.service.comm.serial.bluetooth.BluetoothCommunicationEndpointService; import java.util.List; import java.util.concurrent.Future; /** * A driver for the Wii remote. * * @author Keith M. Hughes */ public class WiiRemoteDriver extends DriverSupport { /** * The Bluetooth report for reading from the Wii remote. */ public static final int WII_REMOTE_RECEIVE_PORT = 13; /** * The Bluetooth report for writing to the Wii remote. */ public static final int WII_REMOTE_SEND_PORT = 11; /** * Command for setting the lights. */ private static final byte COMMAND_LIGHT = 0x11; private static final byte[] FULL_COMMAND_LIGHT_0 = { 0x52, COMMAND_LIGHT, 0x10 }; private static final byte[] FULL_COMMAND_LIGHT_1 = { 0x52, COMMAND_LIGHT, 0x20 }; private static final byte[] FULL_COMMAND_LIGHT_2 = { 0x52, COMMAND_LIGHT, 0x40 }; private static final byte[] FULL_COMMAND_LIGHT_3 = { 0x52, COMMAND_LIGHT, (byte) 0x80 }; private static final byte[][] FULL_COMMAND_LIGHTS = { FULL_COMMAND_LIGHT_0, FULL_COMMAND_LIGHT_1, FULL_COMMAND_LIGHT_2, FULL_COMMAND_LIGHT_3 }; /** * The command for telling the remote to calibrate. */ private static byte COMMAND_READ_CALIBRATION = 0x17; /** * The full command for reading remote calibration. */ private static byte[] FULL_COMMAND_READ_CALIBRATION = new byte[] { 0x52, COMMAND_READ_CALIBRATION, 0x00, 0x00, 0x00, 0x16, 0x00, 0x08 }; /** * Command for setting the reporting that the rmeote provides. */ private static final int COMMAND_SET_REPORT = 0x52; /** * The calibration response report. */ private static final byte REPORT_CALIBRATION_RESPONSE = 0x21; /** * A button event only is being reported. */ private static final byte REPORT_BUTTON_ONLY = 0x30; /** * A button and accelerometer event are being reported. */ private static final byte REPORT_BUTTON_ACCELEROMETER = 0x31; /** * The full command for setting the report to button only. */ private static final byte[] FULL_COMMAND_SET_REPORT_BUTTON = new byte[] { COMMAND_SET_REPORT, 0x12, 0x00, REPORT_BUTTON_ONLY }; /** * The full command for setting the report to button and accelerometer. */ private static final byte[] FULL_COMMAND_SET_REPORT_BUTTON_ACCELEROMETER = new byte[] { COMMAND_SET_REPORT, 0x12, 0x00, REPORT_BUTTON_ACCELEROMETER }; /** * The Bluetooth address for the remote. */ private String address; /** * The endpoint for speaing with the remote. */ private BluetoothCommunicationEndpoint remoteEndpoint; /** * The reader future. */ private Future<?> readerFuture; /** * Denominator for the X calibration for the accelerometer. */ private double calibrationX; /** * Denominator for the Y calibration for the accelerometer. */ private double calibrationY; /** * Denominator for the Z calibration for the accelerometer. */ private double calibrationZ; /** * 0 point for the X calibration for the accelerometer. */ private double calibrationX0; /** * 0 point for the Y calibration for the accelerometer. */ private double calibrationY0; /** * 0 point for the Z calibration for the accelerometer. */ private double calibrationZ0; /** * The list of all listeners for events. */ private List<WiiRemoteEventListener> listeners = Lists.newArrayList(); /** * Construct a driver for the given address. * * @param address * the bluetooth address of the Wii Remote */ public WiiRemoteDriver(String address) { this.address = address; } @Override public void startup() { BluetoothCommunicationEndpointService connectionEndpointService = spaceEnvironment.getServiceRegistry().getRequiredService( BluetoothCommunicationEndpointService.SERVICE_NAME); try { remoteEndpoint = connectionEndpointService.newDualEndpoint(address, WII_REMOTE_RECEIVE_PORT, WII_REMOTE_SEND_PORT); remoteEndpoint.startup(); } catch (Exception e) { throw new InteractiveSpacesException("Unable to connect to device", e); } readerFuture = spaceEnvironment.getExecutorService().submit(new RemoteReader()); readCalibration(); } @Override public void shutdown() { if (readerFuture != null) { readerFuture.cancel(true); readerFuture = null; } if (remoteEndpoint != null) { remoteEndpoint.shutdown(); remoteEndpoint = null; } } /** * Add a new event listener. * * @param listener * the listener to add */ public void addEventListener(WiiRemoteEventListener listener) { listeners.add(listener); } /** * Remove an event listener. * * <p> * Does nothing if listener not registered. * * @param listener * the listener to remove */ public void removeEventListener(WiiRemoteEventListener listener) { listeners.add(listener); } public void setAccelerometerReporting(boolean accelerometerReporting) { remoteEndpoint.write(accelerometerReporting ? FULL_COMMAND_SET_REPORT_BUTTON_ACCELEROMETER : FULL_COMMAND_SET_REPORT_BUTTON); } /** * Set a light on. * * @param light * the light to turn on */ public void setLight(int light) { if (light >= 0 && light <= 3) { try { remoteEndpoint.write(FULL_COMMAND_LIGHTS[light]); } catch (Exception e) { throw new InteractiveSpacesException("Error sending light command", e); } } } /** * Read the calibration from the remote */ private void readCalibration() { try { remoteEndpoint.write(FULL_COMMAND_READ_CALIBRATION); } catch (Exception e) { throw new InteractiveSpacesException("Error sending read calibration command", e); } } /** * A button has been pressed. Notify all listeners. * * @param button * the button which has been pressed */ private void notifyButtonEvent(int button) { for (WiiRemoteEventListener listener : listeners) { listener.onWiiRemoteButtonEvent(button); } } /** * A button has been pressed. Notify all listeners. * * @param button * the button which has been pressed * @param x * the x component of the accelerometer * @param y * the y component of the accelerometer * @param z * the z component of the accelerometer */ private void notifyButtonAccelerometerEvent(int button, double x, double y, double z) { for (WiiRemoteEventListener listener : listeners) { listener.onWiiRemoteButtonAccelerometerEvent(button, x, y, z); } } /** * The reader for information coming from the Wii * * @author Keith M. Hughes */ private class RemoteReader implements Runnable { /** * The read buffer */ private byte[] buffer = new byte[32]; @Override public void run() { while (!Thread.interrupted()) { try { if (remoteEndpoint.read(buffer) == -1) { break; } switch (buffer[1]) { case REPORT_CALIBRATION_RESPONSE: decodeCalibrationResponse(); break; case REPORT_BUTTON_ONLY: handleButtonOnlyEvent(); break; case REPORT_BUTTON_ACCELEROMETER: handleButtonAccelerometerEvent(); break; } } catch (Exception e) { log.error(String.format("Error while reading Wii remote with address %s", address), e); // TODO(keith): Decide whether to look at message of string // to decide if to break or not. If remote turned off, // Bluecove gives "Peer closed connection". break; } } } /** * Decode the calibration response. */ private void decodeCalibrationResponse() { byte b10 = buffer[10]; calibrationX0 = ((buffer[7] & 0xFF) << 2) + (b10 & 3); calibrationY0 = ((buffer[8] & 0xFF) << 2) + ((b10 & 0xC) >> 2); calibrationZ0 = ((buffer[9] & 0xFF) << 2) + ((b10 & 0x30) >> 4); byte b14 = buffer[14]; double calibrationX1 = ((buffer[11] & 0xFF) << 2) + (b14 & 3); double calibrationY1 = ((buffer[12] & 0xFF) << 2) + ((b14 & 0xC) >> 2); double calibrationZ1 = ((buffer[13] & 0xFF) << 2) + ((b14 & 0x30) >> 4); calibrationX = calibrationX1 - calibrationX0; calibrationY = calibrationY1 - calibrationY0; calibrationZ = calibrationZ1 - calibrationX0; } /** * Handle a button only event. */ private void handleButtonOnlyEvent() { int button = ((buffer[2] << 8) & 0x1f00) | (buffer[3] & 0x9f); notifyButtonEvent(button); } /** * Handle the full sensor event. */ private void handleButtonAccelerometerEvent() { byte b2 = buffer[2]; byte b3 = buffer[3]; int button = ((b2 << 8) & 0x1f00) | (b3 & 0x9f); int x = ((buffer[4] & 0xff) << 2) + ((b2 >> 5) & 0x03); // Byte 3 has bit 1 of y and z. No bit 0 for y and z int y = ((buffer[5] & 0xff) << 2) + ((b3 >> 4) & 0x02); int z = ((buffer[6] & 0xff) << 2) + ((b3 >> 5) & 0x02); double xaccel = ((double) x - calibrationX0) / (calibrationX); double yaccel = ((double) y - calibrationY0) / (calibrationY); double zaccel = ((double) z - calibrationZ0) / (calibrationZ); notifyButtonAccelerometerEvent(button, xaccel, yaccel, zaccel); } } }