/*
* wiiuse
*
* Written By:
* Michael Laforest < para >
* Email: < thepara (--AT--) g m a i l [--DOT--] com >
*
* Copyright 2006-2007
*
* This file is part of wiiuse.
*
* This program 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 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
* $Header$
*
*/
/**
* @file
* @brief Handles wiimote events.
*
* The file includes functions that handle the events
* that are sent from the wiimote to us.
*/
#include "wiiuse_internal.h"
#include "events.h"
#include "classic.h" /* for classic_ctrl_disconnected, etc */
#include "dynamics.h" /* for calculate_gforce, etc */
#include "guitar_hero_3.h" /* for guitar_hero_3_disconnected, etc */
#include "ir.h" /* for calculate_basic_ir, etc */
#include "nunchuk.h" /* for nunchuk_disconnected, etc */
#include "wiiboard.h" /* for wii_board_disconnected, etc */
#include "motion_plus.h" /* for motion_plus_disconnected, etc */
#include "os.h" /* for wiiuse_os_poll */
#include /* for printf, perror */
#include /* for free, malloc */
#include /* for memcpy, memset */
static void event_data_read(struct wiimote_t* wm, byte* msg);
static void event_data_write(struct wiimote_t *wm, byte *msg);
static void event_status(struct wiimote_t* wm, byte* msg);
static void handle_expansion(struct wiimote_t* wm, byte* msg);
static void save_state(struct wiimote_t* wm);
static int state_changed(struct wiimote_t* wm);
/**
* @brief Poll the wiimotes for any events.
*
* @param wm An array of pointers to wiimote_t structures.
* @param wiimotes The number of wiimote_t structures in the \a wm array.
*
* @return Returns number of wiimotes that an event has occurred on.
*
* It is necessary to poll the wiimote devices for events
* that occur. If an event occurs on a particular wiimote,
* the event variable will be set.
*/
int wiiuse_poll(struct wiimote_t** wm, int wiimotes) {
return wiiuse_os_poll(wm, wiimotes);
}
int wiiuse_update(struct wiimote_t** wiimotes, int nwiimotes, wiiuse_update_cb callback) {
int evnt = 0;
if (wiiuse_poll(wiimotes, nwiimotes)) {
static struct wiimote_callback_data_t s;
int i = 0;
for (; i < nwiimotes; ++i) {
switch (wiimotes[i]->event) {
case WIIUSE_NONE:
break;
default:
/* this could be: WIIUSE_EVENT, WIIUSE_STATUS, WIIUSE_CONNECT, etc.. */
s.uid = wiimotes[i]->unid;
s.leds = wiimotes[i]->leds;
s.battery_level = wiimotes[i]->battery_level;
s.accel = wiimotes[i]->accel;
s.orient = wiimotes[i]->orient;
s.gforce = wiimotes[i]->gforce;
s.ir = wiimotes[i]->ir;
s.buttons = wiimotes[i]->btns;
s.buttons_held = wiimotes[i]->btns_held;
s.buttons_released = wiimotes[i]->btns_released;
s.event = wiimotes[i]->event;
s.state = wiimotes[i]->state;
s.expansion = wiimotes[i]->exp;
callback(&s);
evnt++;
break;
}
}
}
return evnt;
}
/**
* @brief Called on a cycle where no significant change occurs.
*
* @param wm Pointer to a wiimote_t structure.
*/
void idle_cycle(struct wiimote_t* wm) {
/*
* Smooth the angles.
*
* This is done to make sure that on every cycle the orientation
* angles are smoothed. Normally when an event occurs the angles
* are updated and smoothed, but if no packet comes in then the
* angles remain the same. This means the angle wiiuse reports
* is still an old value. Smoothing needs to be applied in this
* case in order for the angle it reports to converge to the true
* angle of the device.
*/
if (WIIUSE_USING_ACC(wm) && WIIMOTE_IS_FLAG_SET(wm, WIIUSE_SMOOTHING)) {
apply_smoothing(&wm->accel_calib, &wm->orient, SMOOTH_ROLL);
apply_smoothing(&wm->accel_calib, &wm->orient, SMOOTH_PITCH);
}
/* clear out any old read requests */
clear_dirty_reads(wm);
}
/**
* @brief Clear out all old 'dirty' read requests.
*
* @param wm Pointer to a wiimote_t structure.
*/
void clear_dirty_reads(struct wiimote_t* wm) {
struct read_req_t* req = wm->read_req;
while (req && req->dirty) {
WIIUSE_DEBUG("Cleared old read request for address: %x", req->addr);
wm->read_req = req->next;
free(req);
req = wm->read_req;
}
}
/**
* @brief Handle accel data in a wiimote message.
*
* @param wm Pointer to a wiimote_t structure.
* @param msg The message specified in the event packet.
*/
static void handle_wm_accel(struct wiimote_t* wm, byte* msg) {
wm->accel.x = msg[2];
wm->accel.y = msg[3];
wm->accel.z = msg[4];
/* calculate the remote orientation */
calculate_orientation(&wm->accel_calib, &wm->accel, &wm->orient, WIIMOTE_IS_FLAG_SET(wm, WIIUSE_SMOOTHING));
/* calculate the gforces on each axis */
calculate_gforce(&wm->accel_calib, &wm->accel, &wm->gforce);
}
/**
* @brief Analyze the event that occurred on a wiimote.
*
* @param wm Pointer to a wiimote_t structure.
* @param event The event that occurred.
* @param msg The message specified in the event packet.
*
* Pass the event to the registered event callback.
*/
void propagate_event(struct wiimote_t* wm, byte event, byte* msg) {
save_state(wm);
switch (event) {
case WM_RPT_BTN: {
/* button */
wiiuse_pressed_buttons(wm, msg);
break;
}
case WM_RPT_BTN_ACC: {
/* button - motion */
wiiuse_pressed_buttons(wm, msg);
handle_wm_accel(wm, msg);
break;
}
case WM_RPT_READ: {
/* data read */
event_data_read(wm, msg);
/* yeah buttons may be pressed, but this wasn't an "event" */
return;
}
case WM_RPT_CTRL_STATUS: {
/* controller status */
event_status(wm, msg);
/* don't execute the event callback */
return;
}
case WM_RPT_BTN_EXP: {
/* button - expansion */
wiiuse_pressed_buttons(wm, msg);
handle_expansion(wm, msg + 2);
break;
}
case WM_RPT_BTN_ACC_EXP: {
/* button - motion - expansion */
wiiuse_pressed_buttons(wm, msg);
handle_wm_accel(wm, msg);
handle_expansion(wm, msg + 5);
break;
}
case WM_RPT_BTN_ACC_IR: {
/* button - motion - ir */
wiiuse_pressed_buttons(wm, msg);
handle_wm_accel(wm, msg);
/* ir */
calculate_extended_ir(wm, msg + 5);
break;
}
case WM_RPT_BTN_IR_EXP: {
/* button - ir - expansion */
wiiuse_pressed_buttons(wm, msg);
handle_expansion(wm, msg + 12);
/* ir */
calculate_basic_ir(wm, msg + 2);
break;
}
case WM_RPT_BTN_ACC_IR_EXP: {
/* button - motion - ir - expansion */
wiiuse_pressed_buttons(wm, msg);
handle_wm_accel(wm, msg);
handle_expansion(wm, msg + 15);
/* ir */
calculate_basic_ir(wm, msg + 5);
break;
}
case WM_RPT_WRITE: {
event_data_write(wm, msg);
break;
}
default: {
WIIUSE_WARNING("Unknown event, can not handle it [Code 0x%x].", event);
return;
}
}
/* was there an event? */
if (state_changed(wm)) {
wm->event = WIIUSE_EVENT;
}
}
/**
* @brief Find what buttons are pressed.
*
* @param wm Pointer to a wiimote_t structure.
* @param msg The message specified in the event packet.
*/
void wiiuse_pressed_buttons(struct wiimote_t* wm, byte* msg) {
int16_t now;
/* convert from big endian */
now = from_big_endian_uint16_t(msg) & WIIMOTE_BUTTON_ALL;
/* pressed now & were pressed, then held */
wm->btns_held = (now & wm->btns);
/* were pressed or were held & not pressed now, then released */
wm->btns_released = ((wm->btns | wm->btns_held) & ~now);
/* buttons pressed now */
wm->btns = now;
}
/**
* @brief Received a data packet from a read request.
*
* @param wm Pointer to a wiimote_t structure.
* @param msg The message specified in the event packet.
*
* Data from the wiimote comes in packets. If the requested
* data segment size is bigger than one packet can hold then
* several packets will be received. These packets are first
* reassembled into one, then the registered callback function
* that handles data reads is invoked.
*/
static void event_data_read(struct wiimote_t* wm, byte* msg) {
/* we must always assume the packet received is from the most recent request */
byte err;
byte len;
uint16_t offset;
struct read_req_t* req = wm->read_req;
wiiuse_pressed_buttons(wm, msg);
/* find the next non-dirty request */
while (req && req->dirty) {
req = req->next;
}
/* if we don't have a request out then we didn't ask for this packet */
if (!req) {
WIIUSE_WARNING("Received data packet when no request was made.");
return;
}
err = msg[2] & 0x0F;
if (err == 0x08) {
WIIUSE_WARNING("Unable to read data - address does not exist.");
} else if (err == 0x07) {
WIIUSE_WARNING("Unable to read data - address is for write-only registers.");
} else if (err) {
WIIUSE_WARNING("Unable to read data - unknown error code %x.", err);
}
if (err) {
/* this request errored out, so skip it and go to the next one */
/* delete this request */
wm->read_req = req->next;
free(req);
/* if another request exists send it to the wiimote */
if (wm->read_req) {
wiiuse_send_next_pending_read_request(wm);
}
return;
}
len = ((msg[2] & 0xF0) >> 4) + 1;
offset = from_big_endian_uint16_t(msg + 3);
req->addr = (req->addr & 0xFFFF);
req->wait -= len;
if (req->wait >= req->size)
/* this should never happen */
{
req->wait = 0;
}
WIIUSE_DEBUG("Received read packet:");
WIIUSE_DEBUG(" Packet read offset: %i bytes", offset);
WIIUSE_DEBUG(" Request read offset: %i bytes", req->addr);
WIIUSE_DEBUG(" Read offset into buf: %i bytes", offset - req->addr);
WIIUSE_DEBUG(" Read data size: %i bytes", len);
WIIUSE_DEBUG(" Still need: %i bytes", req->wait);
/* reconstruct this part of the data */
memcpy((req->buf + offset - req->addr), (msg + 5), len);
#ifdef WITH_WIIUSE_DEBUG
{
int i = 0;
printf("Read: ");
for (; i < req->size - req->wait; ++i) {
printf("%x ", req->buf[i]);
}
printf("\n");
}
#endif
/* if all data has been received, execute the read event callback or generate event */
if (!req->wait) {
if (req->cb) {
/* this was a callback, so invoke it now */
req->cb(wm, req->buf, req->size);
/* delete this request */
wm->read_req = req->next;
free(req);
} else {
/*
* This should generate an event.
* We need to leave the event in the array so the client
* can access it still. We'll flag is as being 'dirty'
* and give the client one cycle to use it. Next event
* we will remove it from the list.
*/
wm->event = WIIUSE_READ_DATA;
req->dirty = 1;
}
/* if another request exists send it to the wiimote */
if (wm->read_req) {
wiiuse_send_next_pending_read_request(wm);
}
}
}
static void event_data_write(struct wiimote_t *wm, byte *msg) {
struct data_req_t* req = wm->data_req;
wiiuse_pressed_buttons(wm, msg);
/* if we don't have a request out then we didn't ask for this packet */
if (!req) {
WIIUSE_WARNING("Transmitting data packet when no request was made.");
return;
}
if (!(req->state == REQ_SENT)) {
WIIUSE_WARNING("Transmission is not necessary");
/* delete this request */
wm->data_req = req->next;
free(req);
return;
}
req->state = REQ_DONE;
if (req->cb) {
/* this was a callback, so invoke it now */
req->cb(wm, NULL, 0);
/* delete this request */
wm->data_req = req->next;
free(req);
} else {
/*
* This should generate an event.
* We need to leave the event in the array so the client
* can access it still. We'll flag is as being 'REQ_DONE'
* and give the client one cycle to use it. Next event
* we will remove it from the list.
*/
wm->event = WIIUSE_WRITE_DATA;
}
/* if another request exists send it to the wiimote */
if (wm->data_req) {
wiiuse_send_next_pending_write_request(wm);
}
}
/**
* @brief Read the controller status.
*
* @param wm Pointer to a wiimote_t structure.
* @param msg The message specified in the event packet.
*
* Read the controller status and execute the registered status callback.
*/
static void event_status(struct wiimote_t* wm, byte* msg) {
int led[4] = {0, 0, 0, 0};
int attachment = 0;
int ir = 0;
int exp_changed = 0;
struct data_req_t* req = wm->data_req;
/* initial handshake is not finished yet, ignore this */
if (WIIMOTE_IS_SET(wm, WIIMOTE_STATE_HANDSHAKE)) {
return;
}
/*
* An event occurred.
* This event can be overwritten by a more specific
* event type during a handshake or expansion removal.
*/
wm->event = WIIUSE_STATUS;
wiiuse_pressed_buttons(wm, msg);
/* find what LEDs are lit */
if (msg[2] & WM_CTRL_STATUS_BYTE1_LED_1) {
led[0] = 1;
}
if (msg[2] & WM_CTRL_STATUS_BYTE1_LED_2) {
led[1] = 1;
}
if (msg[2] & WM_CTRL_STATUS_BYTE1_LED_3) {
led[2] = 1;
}
if (msg[2] & WM_CTRL_STATUS_BYTE1_LED_4) {
led[3] = 1;
}
/* probe for Motion+ */
if (!WIIMOTE_IS_SET(wm, WIIMOTE_STATE_MPLUS_PRESENT)) {
wiiuse_probe_motion_plus(wm);
}
/* is an attachment connected to the expansion port? */
if ((msg[2] & WM_CTRL_STATUS_BYTE1_ATTACHMENT) == WM_CTRL_STATUS_BYTE1_ATTACHMENT) {
WIIUSE_DEBUG("Attachment detected!");
attachment = 1;
}
/* is the speaker enabled? */
if ((msg[2] & WM_CTRL_STATUS_BYTE1_SPEAKER_ENABLED) == WM_CTRL_STATUS_BYTE1_SPEAKER_ENABLED) {
WIIMOTE_ENABLE_STATE(wm, WIIMOTE_STATE_SPEAKER);
}
/* is IR sensing enabled? */
if ((msg[2] & WM_CTRL_STATUS_BYTE1_IR_ENABLED) == WM_CTRL_STATUS_BYTE1_IR_ENABLED) {
ir = 1;
}
/* find the battery level and normalize between 0 and 1 */
wm->battery_level = (msg[5] / (float)WM_MAX_BATTERY_CODE);
/* expansion port */
if (attachment && !WIIMOTE_IS_SET(wm, WIIMOTE_STATE_EXP) && !WIIMOTE_IS_SET(wm, WIIMOTE_STATE_EXP_HANDSHAKE)) {
/* send the initialization code for the attachment */
handshake_expansion(wm, NULL, 0);
exp_changed = 1;
} else if (!attachment && WIIMOTE_IS_SET(wm, WIIMOTE_STATE_EXP)) {
/* attachment removed */
disable_expansion(wm);
exp_changed = 1;
}
#ifdef WIIUSE_WIN32
if (!attachment) {
WIIUSE_DEBUG("Setting timeout to normal %i ms.", wm->normal_timeout);
wm->timeout = wm->normal_timeout;
}
#endif
/*
* From now on the remote will only send status packets.
* We need to send a WIIMOTE_CMD_REPORT_TYPE packet to
* reenable other incoming reports.
*/
if (exp_changed && WIIMOTE_IS_SET(wm, WIIMOTE_STATE_IR)) {
/*
* Since the expansion status changed IR needs to
* be reset for the new IR report mode.
*/
WIIMOTE_DISABLE_STATE(wm, WIIMOTE_STATE_IR);
wiiuse_set_ir(wm, 1);
} else {
wiiuse_set_report_type(wm);
return;
}
/* handling new Tx for changed exp */
if (!req) {
return;
}
if (!(req->state == REQ_SENT)) {
return;
}
wm->data_req = req->next;
req->state = REQ_DONE;
/* if(req->cb!=NULL) req->cb(wm,msg,6); */
free(req);
}
/**
* @brief Handle data from the expansion.
*
* @param wm A pointer to a wiimote_t structure.
* @param msg The message specified in the event packet for the expansion.
*/
static void handle_expansion(struct wiimote_t* wm, byte* msg) {
switch (wm->exp.type) {
case EXP_NUNCHUK:
nunchuk_event(&wm->exp.nunchuk, msg);
break;
case EXP_CLASSIC:
classic_ctrl_event(&wm->exp.classic, msg);
break;
case EXP_GUITAR_HERO_3:
guitar_hero_3_event(&wm->exp.gh3, msg);
break;
case EXP_WII_BOARD:
wii_board_event(&wm->exp.wb, msg);
break;
case EXP_MOTION_PLUS:
case EXP_MOTION_PLUS_CLASSIC:
case EXP_MOTION_PLUS_NUNCHUK:
motion_plus_event(&wm->exp.mp, wm->exp.type, msg);
break;
default:
break;
}
}
/**
* @brief Handle the handshake data from the expansion device.
*
* @param wm A pointer to a wiimote_t structure.
* @param data The data read in from the device.
* @param len The length of the data block, in bytes.
*
* Tries to determine what kind of expansion was attached
* and invoke the correct handshake function.
*
* If the data is NULL then this function will try to start
* a handshake with the expansion.
*/
void handshake_expansion(struct wiimote_t* wm, byte* data, uint16_t len) {
int id;
byte val = 0;
byte buf = 0x00;
byte* handshake_buf;
int gotIt = 0;
WIIUSE_DEBUG("handshake_expansion with state %d", wm->expansion_state);
switch (wm->expansion_state) {
/* These two initialization writes disable the encryption */
case 0:
wm->expansion_state = 1;
/* increase the timeout until the handshake completes */
#ifdef WIIUSE_WIN32
WIIUSE_DEBUG("write 0x55 - Setting timeout to expansion %i ms.", wm->exp_timeout);
wm->timeout = wm->exp_timeout;
#endif
buf = 0x55;
wiiuse_write_data_cb(wm, WM_EXP_MEM_ENABLE1, &buf, 1, handshake_expansion);
break;
case 1:
wm->expansion_state = 2;
/* increase the timeout until the handshake completes */
#ifdef WIIUSE_WIN32
WIIUSE_DEBUG("write 0x00 - Setting timeout to expansion %i ms.", wm->exp_timeout);
wm->timeout = wm->exp_timeout;
#endif
val = 0x00;
wiiuse_write_data_cb(wm, WM_EXP_MEM_ENABLE2, &buf, 1, handshake_expansion);
break;
case 2:
wm->expansion_state = 3;
/* get the calibration data */
if (WIIMOTE_IS_SET(wm, WIIMOTE_STATE_EXP)) {
disable_expansion(wm);
}
handshake_buf = malloc(EXP_HANDSHAKE_LEN * sizeof(byte));
/* tell the wiimote to send expansion data */
WIIMOTE_ENABLE_STATE(wm, WIIMOTE_STATE_EXP);
wiiuse_read_data_cb(wm, handshake_expansion, handshake_buf, WM_EXP_MEM_CALIBR, EXP_HANDSHAKE_LEN);
break;
case 3:
if (!data || !len) {
WIIUSE_DEBUG("no handshake data received from expansion");
disable_expansion(wm);
return;
}
wm->expansion_state = 0;
id = from_big_endian_uint32_t(data + 220);
switch (id) {
case EXP_ID_CODE_NUNCHUK:
if (nunchuk_handshake(wm, &wm->exp.nunchuk, data, len)) {
wm->event = WIIUSE_NUNCHUK_INSERTED;
gotIt = 1;
}
break;
case EXP_ID_CODE_CLASSIC_CONTROLLER:
if (classic_ctrl_handshake(wm, &wm->exp.classic, data, len)) {
wm->event = WIIUSE_CLASSIC_CTRL_INSERTED;
gotIt = 1;
}
break;
case EXP_ID_CODE_GUITAR:
if (guitar_hero_3_handshake(wm, &wm->exp.gh3, data, len)) {
wm->event = WIIUSE_GUITAR_HERO_3_CTRL_INSERTED;
gotIt = 1;
}
break;
case EXP_ID_CODE_MOTION_PLUS:
case EXP_ID_CODE_MOTION_PLUS_CLASSIC:
case EXP_ID_CODE_MOTION_PLUS_NUNCHUK:
/* wiiuse_motion_plus_handshake(wm, data, len); */
wm->event = WIIUSE_MOTION_PLUS_ACTIVATED;
gotIt = 1;
break;
case EXP_ID_CODE_WII_BOARD:
if (wii_board_handshake(wm, &wm->exp.wb, data, len)) {
wm->event = WIIUSE_WII_BOARD_CTRL_INSERTED;
gotIt = 1;
}
break;
default:
WIIUSE_WARNING("Unknown expansion type. Code: 0x%x", id);
break;
}
free(data);
if (gotIt) {
WIIMOTE_DISABLE_STATE(wm, WIIMOTE_STATE_EXP_HANDSHAKE);
WIIMOTE_ENABLE_STATE(wm, WIIMOTE_STATE_EXP);
} else {
WIIUSE_WARNING("Could not handshake with expansion id: 0x%x", id);
}
wiiuse_set_ir_mode(wm);
wiiuse_status(wm);
break;
}
}
/**
* @brief Disable the expansion device if it was enabled.
*
* @param wm A pointer to a wiimote_t structure.
* @param data The data read in from the device.
* @param len The length of the data block, in bytes.
*
* If the data is NULL then this function will try to start
* a handshake with the expansion.
*/
void disable_expansion(struct wiimote_t* wm) {
WIIUSE_DEBUG("Disabling expansion");
if (!WIIMOTE_IS_SET(wm, WIIMOTE_STATE_EXP)) {
return;
}
/* tell the associated module the expansion was removed */
switch (wm->exp.type) {
case EXP_NUNCHUK:
nunchuk_disconnected(&wm->exp.nunchuk);
wm->event = WIIUSE_NUNCHUK_REMOVED;
break;
case EXP_CLASSIC:
classic_ctrl_disconnected(&wm->exp.classic);
wm->event = WIIUSE_CLASSIC_CTRL_REMOVED;
break;
case EXP_GUITAR_HERO_3:
guitar_hero_3_disconnected(&wm->exp.gh3);
wm->event = WIIUSE_GUITAR_HERO_3_CTRL_REMOVED;
break;
case EXP_WII_BOARD:
wii_board_disconnected(&wm->exp.wb);
wm->event = WIIUSE_WII_BOARD_CTRL_REMOVED;
break;
case EXP_MOTION_PLUS:
case EXP_MOTION_PLUS_CLASSIC:
case EXP_MOTION_PLUS_NUNCHUK:
motion_plus_disconnected(&wm->exp.mp);
wm->event = WIIUSE_MOTION_PLUS_REMOVED;
break;
default:
break;
}
WIIMOTE_DISABLE_STATE(wm, WIIMOTE_STATE_EXP);
wm->exp.type = EXP_NONE;
wm->expansion_state = 0;
}
/**
* @brief Save important state data.
* @param wm A pointer to a wiimote_t structure.
*/
static void save_state(struct wiimote_t* wm) {
/* wiimote */
wm->lstate.btns = wm->btns;
wm->lstate.accel = wm->accel;
/* ir */
if (WIIUSE_USING_IR(wm)) {
wm->lstate.ir_ax = wm->ir.ax;
wm->lstate.ir_ay = wm->ir.ay;
wm->lstate.ir_distance = wm->ir.distance;
}
/* expansion */
switch (wm->exp.type) {
case EXP_NUNCHUK:
wm->lstate.exp_ljs_ang = wm->exp.nunchuk.js.ang;
wm->lstate.exp_ljs_mag = wm->exp.nunchuk.js.mag;
wm->lstate.exp_btns = wm->exp.nunchuk.btns;
wm->lstate.exp_accel = wm->exp.nunchuk.accel;
break;
case EXP_CLASSIC:
wm->lstate.exp_ljs_ang = wm->exp.classic.ljs.ang;
wm->lstate.exp_ljs_mag = wm->exp.classic.ljs.mag;
wm->lstate.exp_rjs_ang = wm->exp.classic.rjs.ang;
wm->lstate.exp_rjs_mag = wm->exp.classic.rjs.mag;
wm->lstate.exp_r_shoulder = wm->exp.classic.r_shoulder;
wm->lstate.exp_l_shoulder = wm->exp.classic.l_shoulder;
wm->lstate.exp_btns = wm->exp.classic.btns;
break;
case EXP_GUITAR_HERO_3:
wm->lstate.exp_ljs_ang = wm->exp.gh3.js.ang;
wm->lstate.exp_ljs_mag = wm->exp.gh3.js.mag;
wm->lstate.exp_r_shoulder = wm->exp.gh3.whammy_bar;
wm->lstate.exp_btns = wm->exp.gh3.btns;
break;
case EXP_WII_BOARD:
wm->lstate.exp_wb_rtr = wm->exp.wb.rtr;
wm->lstate.exp_wb_rtl = wm->exp.wb.rtl;
wm->lstate.exp_wb_rbr = wm->exp.wb.rbr;
wm->lstate.exp_wb_rbl = wm->exp.wb.rbl;
break;
case EXP_MOTION_PLUS:
case EXP_MOTION_PLUS_CLASSIC:
case EXP_MOTION_PLUS_NUNCHUK: {
wm->lstate.drx = wm->exp.mp.raw_gyro.pitch;
wm->lstate.dry = wm->exp.mp.raw_gyro.roll;
wm->lstate.drz = wm->exp.mp.raw_gyro.yaw;
if (wm->exp.type == EXP_MOTION_PLUS_CLASSIC) {
wm->lstate.exp_ljs_ang = wm->exp.classic.ljs.ang;
wm->lstate.exp_ljs_mag = wm->exp.classic.ljs.mag;
wm->lstate.exp_rjs_ang = wm->exp.classic.rjs.ang;
wm->lstate.exp_rjs_mag = wm->exp.classic.rjs.mag;
wm->lstate.exp_r_shoulder = wm->exp.classic.r_shoulder;
wm->lstate.exp_l_shoulder = wm->exp.classic.l_shoulder;
wm->lstate.exp_btns = wm->exp.classic.btns;
} else {
wm->lstate.exp_ljs_ang = wm->exp.nunchuk.js.ang;
wm->lstate.exp_ljs_mag = wm->exp.nunchuk.js.mag;
wm->lstate.exp_btns = wm->exp.nunchuk.btns;
wm->lstate.exp_accel = wm->exp.nunchuk.accel;
}
break;
}
case EXP_NONE:
break;
}
}
/**
* @brief Determine if the current state differs significantly from the previous.
* @param wm A pointer to a wiimote_t structure.
* @return 1 if a significant change occurred, 0 if not.
*/
static int state_changed(struct wiimote_t* wm) {
#define STATE_CHANGED(a, b) if (a != b) return 1
#define CROSS_THRESH(last, now, thresh) \
do { \
if (WIIMOTE_IS_FLAG_SET(wm, WIIUSE_ORIENT_THRESH)) { \
if ((diff_f(last.roll, now.roll) >= thresh) || \
(diff_f(last.pitch, now.pitch) >= thresh) || \
(diff_f(last.yaw, now.yaw) >= thresh)) \
{ \
last = now; \
return 1; \
} \
} else { \
if (last.roll != now.roll) return 1; \
if (last.pitch != now.pitch) return 1; \
if (last.yaw != now.yaw) return 1; \
} \
} while (0)
#define CROSS_THRESH_XYZ(last, now, thresh) \
do { \
if (WIIMOTE_IS_FLAG_SET(wm, WIIUSE_ORIENT_THRESH)) { \
if ((diff_f(last.x, now.x) >= thresh) || \
(diff_f(last.y, now.y) >= thresh) || \
(diff_f(last.z, now.z) >= thresh)) \
{ \
last = now; \
return 1; \
} \
} else { \
if (last.x != now.x) return 1; \
if (last.y != now.y) return 1; \
if (last.z != now.z) return 1; \
} \
} while (0)
/* ir */
if (WIIUSE_USING_IR(wm)) {
STATE_CHANGED(wm->lstate.ir_ax, wm->ir.ax);
STATE_CHANGED(wm->lstate.ir_ay, wm->ir.ay);
STATE_CHANGED(wm->lstate.ir_distance, wm->ir.distance);
}
/* accelerometer */
if (WIIUSE_USING_ACC(wm)) {
/* raw accelerometer */
CROSS_THRESH_XYZ(wm->lstate.accel, wm->accel, wm->accel_threshold);
/* orientation */
CROSS_THRESH(wm->lstate.orient, wm->orient, wm->orient_threshold);
}
/* expansion */
switch (wm->exp.type) {
case EXP_NUNCHUK: {
STATE_CHANGED(wm->lstate.exp_ljs_ang, wm->exp.nunchuk.js.ang);
STATE_CHANGED(wm->lstate.exp_ljs_mag, wm->exp.nunchuk.js.mag);
STATE_CHANGED(wm->lstate.exp_btns, wm->exp.nunchuk.btns);
CROSS_THRESH(wm->lstate.exp_orient, wm->exp.nunchuk.orient, wm->exp.nunchuk.orient_threshold);
CROSS_THRESH_XYZ(wm->lstate.exp_accel, wm->exp.nunchuk.accel, wm->exp.nunchuk.accel_threshold);
break;
}
case EXP_CLASSIC: {
STATE_CHANGED(wm->lstate.exp_ljs_ang, wm->exp.classic.ljs.ang);
STATE_CHANGED(wm->lstate.exp_ljs_mag, wm->exp.classic.ljs.mag);
STATE_CHANGED(wm->lstate.exp_rjs_ang, wm->exp.classic.rjs.ang);
STATE_CHANGED(wm->lstate.exp_rjs_mag, wm->exp.classic.rjs.mag);
STATE_CHANGED(wm->lstate.exp_r_shoulder, wm->exp.classic.r_shoulder);
STATE_CHANGED(wm->lstate.exp_l_shoulder, wm->exp.classic.l_shoulder);
STATE_CHANGED(wm->lstate.exp_btns, wm->exp.classic.btns);
break;
}
case EXP_GUITAR_HERO_3: {
STATE_CHANGED(wm->lstate.exp_ljs_ang, wm->exp.gh3.js.ang);
STATE_CHANGED(wm->lstate.exp_ljs_mag, wm->exp.gh3.js.mag);
STATE_CHANGED(wm->lstate.exp_r_shoulder, wm->exp.gh3.whammy_bar);
STATE_CHANGED(wm->lstate.exp_btns, wm->exp.gh3.btns);
break;
}
case EXP_WII_BOARD: {
STATE_CHANGED(wm->lstate.exp_wb_rtr, wm->exp.wb.tr);
STATE_CHANGED(wm->lstate.exp_wb_rtl, wm->exp.wb.tl);
STATE_CHANGED(wm->lstate.exp_wb_rbr, wm->exp.wb.br);
STATE_CHANGED(wm->lstate.exp_wb_rbl, wm->exp.wb.bl);
break;
}
case EXP_MOTION_PLUS:
case EXP_MOTION_PLUS_CLASSIC:
case EXP_MOTION_PLUS_NUNCHUK: {
STATE_CHANGED(wm->lstate.drx, wm->exp.mp.raw_gyro.pitch);
STATE_CHANGED(wm->lstate.dry, wm->exp.mp.raw_gyro.roll);
STATE_CHANGED(wm->lstate.drz, wm->exp.mp.raw_gyro.yaw);
if (wm->exp.type == EXP_MOTION_PLUS_CLASSIC) {
STATE_CHANGED(wm->lstate.exp_ljs_ang, wm->exp.classic.ljs.ang);
STATE_CHANGED(wm->lstate.exp_ljs_mag, wm->exp.classic.ljs.mag);
STATE_CHANGED(wm->lstate.exp_rjs_ang, wm->exp.classic.rjs.ang);
STATE_CHANGED(wm->lstate.exp_rjs_mag, wm->exp.classic.rjs.mag);
STATE_CHANGED(wm->lstate.exp_r_shoulder, wm->exp.classic.r_shoulder);
STATE_CHANGED(wm->lstate.exp_l_shoulder, wm->exp.classic.l_shoulder);
STATE_CHANGED(wm->lstate.exp_btns, wm->exp.classic.btns);
} else {
STATE_CHANGED(wm->lstate.exp_ljs_ang, wm->exp.nunchuk.js.ang);
STATE_CHANGED(wm->lstate.exp_ljs_mag, wm->exp.nunchuk.js.mag);
STATE_CHANGED(wm->lstate.exp_btns, wm->exp.nunchuk.btns);
CROSS_THRESH(wm->lstate.exp_orient, wm->exp.nunchuk.orient, wm->exp.nunchuk.orient_threshold);
CROSS_THRESH_XYZ(wm->lstate.exp_accel, wm->exp.nunchuk.accel, wm->exp.nunchuk.accel_threshold);
}
break;
}
case EXP_NONE: {
break;
}
}
STATE_CHANGED(wm->lstate.btns, wm->btns);
return 0;
}