00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032 """The wiimote_node broadcasts four topics, and listens to messages that control
00033 the Wiimote stick's rumble (vibration) and LEDs. Transmitted topics (@100Hz):
00034
00035 o wiijoy Messages contain the accelerometer and gyro axis data, and all button states.
00036 o imu/data Messages contain the accelerometer and gyro axis data, and covariance matrices
00037 o wiimote/state the wiijoy and /imu messages, the button states, the LED states,
00038 rumble (i.e. vibrator) state, IR light sensor readings, time since last zeroed,
00039 and battery state. See State.msg
00040 o imu/is_calibrated Latched message
00041 o nunchuk Joy messages using the nunchuk as a joystick
00042 o classic Joy messages using the nunchuck as a joystic
00043
00044 The node listens to the following messages:
00045
00046 o wiimote/rumble
00047 Instruct this node to turn on/off the rumble (i.e. vibrator). Rather
00048 than just switching rumble, the message can instead contain
00049 an array of on/off durations. This node will pulse the rumbler
00050 accordingly without the message sender's additional cooperation.
00051 See RumbleControl.mg and TimedSwitch.msg
00052 o wiimote/leds
00053 Turn each LED on the Wiimote on/off. The message can instead
00054 contain an array of TimedSwitch structures. Each such structure
00055 turns a respective LED on and off according to time intervals that
00056 are stored in the structure. See LEDControl.msg and TimedSwitch.msg
00057 o imu/calibrate
00058 Request to calibrate the device.
00059
00060 No command line parameters.
00061 """
00062
00063
00064
00065
00066
00067
00068
00069
00070
00071
00072
00073
00074
00075
00076
00077 import sys
00078 import threading
00079 import traceback
00080 import time
00081
00082
00083 import roslib; roslib.load_manifest('wiimote')
00084 import rospy
00085 from geometry_msgs.msg import Vector3
00086 from sensor_msgs.msg import Imu
00087 from std_srvs.srv import Empty
00088 from std_srvs.srv import EmptyResponse
00089 from std_msgs.msg import Bool
00090 from joy.msg import Joy
00091 from wiimote.msg import IrSourceInfo
00092 from wiimote.msg import State
00093 from wiimote.msg import TimedSwitch
00094 from wiimote.msg import LEDControl
00095 from wiimote.msg import RumbleControl
00096
00097
00098 from wiimote.wiimoteExceptions import *
00099 from wiimote.wiimoteConstants import *
00100 import wiimote.WIIMote
00101 import wiimote.wiiutils
00102
00103 GATHER_CALIBRATION_STATS = True
00104
00105 class WiimoteNode():
00106
00107
00108 def runWiimoteNode(self):
00109 """Initialize the wiimote_node, establishing its name for communication with the Master"""
00110
00111
00112
00113
00114 rospy.init_node('wiimote', anonymous=True, log_level=rospy.ERROR)
00115 wiimoteDevice = wiimote.WIIMote.WIIMote()
00116 wiimoteDevice.zeroDevice()
00117
00118 try:
00119 IMUSender(wiimoteDevice, freq=100).start()
00120 JoySender(wiimoteDevice, freq=100).start()
00121 WiiSender(wiimoteDevice, freq=100).start()
00122 NunSender(wiimoteDevice, freq=100).start()
00123 ClasSender(wiimoteDevice, freq=100).start()
00124 WiimoteListeners(wiimoteDevice).start()
00125
00126 rospy.spin()
00127
00128 except:
00129 rospy.loginfo("Error in startup")
00130 rospy.loginfo(sys.exc_info()[0])
00131 finally:
00132 try:
00133 wiimoteDevice.setRumble(False)
00134 wiimoteDevice.setLEDs([False, False, False, False])
00135 wiimoteDevice.shutdown()
00136 except:
00137 pass
00138
00139 def shutdown(self):
00140 try:
00141 IMUSender.stop
00142 JoySender.stop
00143 WiiSender.stop
00144 NunSender.stop
00145 WiimoteListener.stop
00146 except:
00147 pass
00148
00149 class WiimoteDataSender(threading.Thread):
00150
00151 def __init__(self, wiiMote, freq=100):
00152
00153 threading.Thread.__init__(self)
00154 self.wiiMote = wiiMote
00155 self.freq = freq
00156 self.sleepDuration = 1.0 / freq
00157
00158 varianceAccelerator = self.wiiMote.getVarianceAccelerator();
00159 self.linear_acceleration_covariance = [varianceAccelerator[X], 0., 0.,
00160 0., varianceAccelerator[Y], 0.,
00161 0., 0., varianceAccelerator[Z]]
00162
00163 varianceGyro = self.wiiMote.getVarianceGyro();
00164 self.angular_velocity_covariance = [varianceGyro[X], 0., 0.,
00165 0., varianceGyro[Y], 0.,
00166 0., 0., varianceGyro[Z]]
00167
00168
00169
00170
00171 self.gyroAbsence_covariance = [-1., 0., 0.,
00172 0., 0., 0.,
00173 0., 0., 0.]
00174
00175 def obtainWiimoteData(self):
00176 """Retrieve one set of Wiimote measurements from the Wiimote instance. Return scaled accelerator and gyro readings.
00177
00178 We canonicalize both accelerator and gyro data through
00179 scaling them by constants that turn them into m/sec^2, and
00180 radians/sec, respectively.
00181
00182 Return: list of canonicalized accelerator and gyro readings.
00183 """
00184
00185 while not rospy.is_shutdown():
00186 self.wiistate = self.wiiMote.getWiimoteState()
00187 if self.wiistate is not None and self.wiistate.acc is not None:
00188 break
00189 else:
00190 rospy.sleep(0.1)
00191
00192 return self.canonicalizeWiistate()
00193
00194 def canonicalizeWiistate(self):
00195 """Scale accelerator, nunchuk accelerator, and gyro readings to be m/sec^2, m/sec^2 and radians/sec, respectively."""
00196
00197 try:
00198
00199 canonicalAccel = self.wiistate.acc.scale(EARTH_GRAVITY)
00200
00201
00202
00203 if self.wiistate.nunchukPresent:
00204 canonicalNunchukAccel = self.wiistate.nunchukAcc.scale(EARTH_GRAVITY)
00205 else:
00206 canonicalNunchukAccel = None
00207
00208
00209
00210
00211 if self.wiistate.motionPlusPresent:
00212 canonicalAngleRate = self.wiistate.angleRate.scale(GYRO_SCALE_FACTOR)
00213 else:
00214 canonicalAngleRate = None
00215
00216 return [canonicalAccel, canonicalNunchukAccel, canonicalAngleRate]
00217 except AttributeError:
00218
00219
00220 rospy.loginfo(self.threadName + " shutting down.")
00221 exit(0)
00222
00223
00224 class IMUSender(WiimoteDataSender):
00225 """Broadcasting Wiimote accelerator and gyro readings as IMU messages to Topic sensor_data/Imu"""
00226
00227 def __init__(self, wiiMote, freq=100):
00228 """Initializes the Wiimote IMU publisher.
00229
00230 Parameters:
00231 wiiMote: a bluetooth-connected, calibrated WIIMote instance
00232 freq: the message sending frequency in messages/sec. Max is 100, because
00233 the Wiimote only samples the sensors at 100Hz.
00234 """
00235
00236 WiimoteDataSender.__init__(self, wiiMote, freq)
00237
00238 self.pub = rospy.Publisher('imu/data', Imu)
00239
00240 def run(self):
00241 """Loop that obtains the latest wiimote state, publishes the IMU data, and sleeps.
00242
00243 The IMU message, if fully filled in, contains information on orientation,
00244 acceleration (in m/s^2), and angular rate (in radians/sec). For each of
00245 these quantities, the IMU message format also wants the corresponding
00246 covariance matrix.
00247
00248 Wiimote only gives us acceleration and angular rate. So we ensure that the orientation
00249 data entry is marked invalid. We do this by setting the first
00250 entry of its associated covariance matrix to -1. The covariance
00251 matrices are the 3x3 matrix with the axes' variance in the
00252 diagonal. We obtain the variance from the Wiimote instance.
00253 """
00254
00255 rospy.loginfo("Wiimote IMU publisher starting (topic /imu/data).")
00256 self.threadName = "IMU topic Publisher"
00257 try:
00258 while not rospy.is_shutdown():
00259 (canonicalAccel, canonicalNunchukAccel, canonicalAngleRate) = self.obtainWiimoteData()
00260
00261 msg = Imu(header=None,
00262 orientation=None,
00263 orientation_covariance=[-1.,0.,0.,0.,0.,0.,0.,0.,0.],
00264 angular_velocity=None,
00265 angular_velocity_covariance=self.angular_velocity_covariance,
00266 linear_acceleration=None,
00267 linear_acceleration_covariance=self.linear_acceleration_covariance)
00268
00269
00270
00271
00272
00273
00274 if self.wiistate.motionPlusPresent:
00275 msg.angular_velocity.x = canonicalAngleRate[PHI]
00276 msg.angular_velocity.y = canonicalAngleRate[THETA]
00277 msg.angular_velocity.z = canonicalAngleRate[PSI]
00278 else:
00279 msg.angular_velocity_covariance = self.gyroAbsence_covariance
00280
00281 msg.linear_acceleration.x = canonicalAccel[X]
00282 msg.linear_acceleration.y = canonicalAccel[Y]
00283 msg.linear_acceleration.z = canonicalAccel[Z]
00284
00285 measureTime = self.wiistate.time
00286 timeSecs = int(measureTime)
00287 timeNSecs = int(abs(timeSecs - measureTime) * 10**9)
00288 msg.header.stamp.secs = timeSecs
00289 msg.header.stamp.nsecs = timeNSecs
00290
00291 try:
00292 self.pub.publish(msg)
00293 except rospy.ROSException:
00294 rospy.loginfo("Topic imu/data closed. Shutting down Imu sender.")
00295 exit(0)
00296
00297
00298
00299 rospy.sleep(self.sleepDuration)
00300 except rospy.ROSInterruptException:
00301 rospy.loginfo("Shutdown request. Shutting down Imu sender.")
00302 exit(0)
00303
00304
00305 class JoySender(WiimoteDataSender):
00306
00307 """Broadcasting Wiimote accelerator and gyro readings as Joy(stick) messages to Topic joy"""
00308
00309 def __init__(self, wiiMote, freq=100):
00310 """Initializes the Wiimote Joy(stick) publisher.
00311
00312 Parameters:
00313 wiiMote: a bluetooth-connected, calibrated WIIMote instance
00314 freq: the message sending frequency in messages/sec. Max is 100, because
00315 the Wiimote only samples the sensors at 100Hz.
00316 """
00317
00318 WiimoteDataSender.__init__(self, wiiMote, freq)
00319
00320
00321 self.pub = rospy.Publisher('joy', Joy)
00322
00323 def run(self):
00324 """Loop that obtains the latest wiimote state, publishes the joystick data, and sleeps.
00325
00326 The Joy.msg message types calls for just two fields: float32[] axes, and int32[] buttons.
00327 """
00328
00329 rospy.loginfo("Wiimote joystick publisher starting (topic wiijoy).")
00330 self.threadName = "Joy topic Publisher"
00331 try:
00332 while not rospy.is_shutdown():
00333 (canonicalAccel, canonicalNunchukAccel, canonicalAngleRate) = self.obtainWiimoteData()
00334
00335 msg = Joy(
00336 axes=[canonicalAccel[X], canonicalAccel[Y], canonicalAccel[Z]],
00337 buttons=None)
00338
00339
00340
00341 if self.wiistate.motionPlusPresent:
00342 msg.axes.extend([canonicalAngleRate[PHI], canonicalAngleRate[THETA], canonicalAngleRate[PSI]])
00343
00344
00345
00346
00347 theButtons = [False,False,False,False,False,False,False,False,False,False,False]
00348 theButtons[State.MSG_BTN_1] = self.wiistate.buttons[BTN_1]
00349 theButtons[State.MSG_BTN_2] = self.wiistate.buttons[BTN_2]
00350 theButtons[State.MSG_BTN_A] = self.wiistate.buttons[BTN_A]
00351 theButtons[State.MSG_BTN_B] = self.wiistate.buttons[BTN_B]
00352 theButtons[State.MSG_BTN_PLUS] = self.wiistate.buttons[BTN_PLUS]
00353 theButtons[State.MSG_BTN_MINUS] = self.wiistate.buttons[BTN_MINUS]
00354 theButtons[State.MSG_BTN_LEFT] = self.wiistate.buttons[BTN_LEFT]
00355 theButtons[State.MSG_BTN_RIGHT] = self.wiistate.buttons[BTN_RIGHT]
00356 theButtons[State.MSG_BTN_UP] = self.wiistate.buttons[BTN_UP]
00357 theButtons[State.MSG_BTN_DOWN] = self.wiistate.buttons[BTN_DOWN]
00358 theButtons[State.MSG_BTN_HOME] = self.wiistate.buttons[BTN_HOME]
00359
00360 msg.buttons = theButtons
00361
00362 measureTime = self.wiistate.time
00363 timeSecs = int(measureTime)
00364 timeNSecs = int(abs(timeSecs - measureTime) * 10**9)
00365
00366
00367
00368
00369 try:
00370 self.pub.publish(msg)
00371 except rospy.ROSException:
00372 rospy.loginfo("Topic wiijoy closed. Shutting down Joy sender.")
00373 exit(0)
00374
00375
00376
00377 rospy.sleep(self.sleepDuration)
00378 except rospy.ROSInterruptException:
00379 rospy.loginfo("Shutdown request. Shutting down Joy sender.")
00380 exit(0)
00381
00382 class NunSender(WiimoteDataSender):
00383
00384 """Broadcasting nunchuk accelerator and joystick readings as Joy(stick) messages to Topic joy"""
00385
00386 def __init__(self, wiiMote, freq=100):
00387 """Initializes the nunchuk Joy(stick) publisher.
00388
00389 Parameters:
00390 wiiMote: a bluetooth-connected, calibrated WIIMote instance
00391 freq: the message sending frequency in messages/sec. Max is 100, because
00392 the Wiimote only samples the sensors at 100Hz.
00393 """
00394
00395 WiimoteDataSender.__init__(self, wiiMote, freq)
00396
00397
00398
00399
00400
00401 self.pub = None
00402
00403 def run(self):
00404 """Loop that obtains the latest nunchuk state, publishes the joystick data, and sleeps.
00405
00406 The Joy.msg message types calls for just two fields: float32[] axes, and int32[] buttons.
00407 """
00408
00409 self.threadName = "nunchuk Joy topic Publisher"
00410 try:
00411 while not rospy.is_shutdown():
00412 rospy.sleep(self.sleepDuration)
00413 (canonicalAccel, scaledAcc, canonicalAngleRate) = self.obtainWiimoteData()
00414 if not self.wiistate.nunchukPresent:
00415 continue
00416 if self.pub is None:
00417 self.pub = rospy.Publisher('/wiimote/nunchuk', Joy)
00418 rospy.loginfo("Wiimote Nunchuk joystick publisher starting (topic nunchuk).")
00419
00420 (joyx, joyy) = self.wiistate.nunchukStick
00421
00422 msg = Joy(
00423 axes=[joyx, joyy,
00424 scaledAcc[X], scaledAcc[Y], scaledAcc[Z]],
00425 buttons=None)
00426
00427 theButtons = [False,False]
00428 theButtons[State.MSG_BTN_Z] = self.wiistate.nunchukButtons[BTN_Z]
00429 theButtons[State.MSG_BTN_C] = self.wiistate.nunchukButtons[BTN_C]
00430
00431 msg.buttons = theButtons
00432
00433 measureTime = self.wiistate.time
00434 timeSecs = int(measureTime)
00435 timeNSecs = int(abs(timeSecs - measureTime) * 10**9)
00436
00437
00438
00439
00440 try:
00441 self.pub.publish(msg)
00442 except rospy.ROSException:
00443 rospy.loginfo("Topic /wiimote/nunchuk closed. Shutting down Nun sender.")
00444 exit(0)
00445
00446
00447
00448
00449 except rospy.ROSInterruptException:
00450 rospy.loginfo("Shutdown request. Shutting down Nun sender.")
00451 exit(0)
00452
00453 class ClasSender(WiimoteDataSender):
00454
00455 """Broadcasting Classic Controller joystick readings as Joy(stick) messages to Topic joy"""
00456
00457 def __init__(self, wiiMote, freq=100):
00458 """Initializes the Classic Controller Joy(stick) publisher.
00459
00460 Parameters:
00461 wiiMote: a bluetooth-connected, calibrated WIIMote instance
00462 freq: the message sending frequency in messages/sec. Max is 100, because
00463 the Wiimote only samples the sensors at 100Hz.
00464 """
00465
00466 WiimoteDataSender.__init__(self, wiiMote, freq)
00467
00468
00469
00470 self.pub = None
00471
00472 def run(self):
00473 """Loop that obtains the latest classic controller state, publishes the joystick data, and sleeps.
00474
00475 The Joy.msg message types calls for just two fields: float32[] axes, and int32[] buttons.
00476 """
00477
00478 self.threadName = "Classic Controller Joy topic Publisher"
00479 try:
00480 while not rospy.is_shutdown():
00481 rospy.sleep(self.sleepDuration)
00482 self.obtainWiimoteData()
00483
00484 if not self.wiistate.classicPresent:
00485 continue
00486 if self.pub is None:
00487 rospy.Publisher('/wiimote/classic', Joy)
00488 rospy.loginfo("Wiimote Classic Controller joystick publisher starting (topic /wiimote/classic).")
00489
00490 (l_joyx, l_joyy) = self.wiistate.classicStickLeft
00491 (r_joyx, r_joyy) = self.wiistate.classicStickRight
00492
00493 l_joyx = -(l_joyx-33)/27.
00494 l_joyy = (l_joyy-33)/27.
00495 r_joyx = -(r_joyx-15)/13.
00496 r_joyy = (r_joyy-15)/13.
00497
00498 if abs(l_joyx) < .05:
00499 l_joyx = 0
00500 if abs(l_joyy) < .05:
00501 l_joyy = 0
00502 if abs(r_joyx) < .05:
00503 r_joyx = 0
00504 if abs(r_joyy) < .05:
00505 r_joyy = 0
00506
00507 msg = Joy(
00508 axes=[l_joyx, l_joyy,r_joyx, r_joyy],
00509 buttons=None)
00510
00511 theButtons = [False,False,False,False,False,False,False,False,False,False,False,False,False,False,False]
00512 theButtons[State.MSG_CLASSIC_BTN_X] = self.wiistate.classicButtons[CLASSIC_BTN_X]
00513 theButtons[State.MSG_CLASSIC_BTN_Y] = self.wiistate.classicButtons[CLASSIC_BTN_Y]
00514 theButtons[State.MSG_CLASSIC_BTN_A] = self.wiistate.classicButtons[CLASSIC_BTN_A]
00515 theButtons[State.MSG_CLASSIC_BTN_B] = self.wiistate.classicButtons[CLASSIC_BTN_B]
00516 theButtons[State.MSG_CLASSIC_BTN_PLUS] = self.wiistate.classicButtons[CLASSIC_BTN_PLUS]
00517 theButtons[State.MSG_CLASSIC_BTN_MINUS] = self.wiistate.classicButtons[CLASSIC_BTN_MINUS]
00518 theButtons[State.MSG_CLASSIC_BTN_LEFT] = self.wiistate.classicButtons[CLASSIC_BTN_LEFT]
00519 theButtons[State.MSG_CLASSIC_BTN_RIGHT] = self.wiistate.classicButtons[CLASSIC_BTN_RIGHT]
00520 theButtons[State.MSG_CLASSIC_BTN_UP] = self.wiistate.classicButtons[CLASSIC_BTN_UP]
00521 theButtons[State.MSG_CLASSIC_BTN_DOWN] = self.wiistate.classicButtons[CLASSIC_BTN_DOWN]
00522 theButtons[State.MSG_CLASSIC_BTN_HOME] = self.wiistate.classicButtons[CLASSIC_BTN_HOME]
00523 theButtons[State.MSG_CLASSIC_BTN_L] = self.wiistate.classicButtons[CLASSIC_BTN_L]
00524 theButtons[State.MSG_CLASSIC_BTN_R] = self.wiistate.classicButtons[CLASSIC_BTN_R]
00525 theButtons[State.MSG_CLASSIC_BTN_ZL] = self.wiistate.classicButtons[CLASSIC_BTN_ZL]
00526 theButtons[State.MSG_CLASSIC_BTN_ZR] = self.wiistate.classicButtons[CLASSIC_BTN_ZR]
00527
00528 msg.buttons = theButtons
00529
00530 measureTime = self.wiistate.time
00531 timeSecs = int(measureTime)
00532 timeNSecs = int(abs(timeSecs - measureTime) * 10**9)
00533
00534
00535
00536
00537 try:
00538 self.pub.publish(msg)
00539 except rospy.ROSException:
00540 rospy.loginfo("Topic /wiimote/classic closed. Shutting down Clas sender.")
00541 exit(0)
00542
00543
00544
00545
00546 except rospy.ROSInterruptException:
00547 rospy.loginfo("Shutdown request. Shutting down Clas sender.")
00548 exit(0)
00549
00550
00551 class WiiSender(WiimoteDataSender):
00552 """Broadcasting complete Wiimote messages to Topic wiimote"""
00553
00554 def __init__(self, wiiMote, freq=100):
00555 """Initializes the full-Wiimote publisher.
00556
00557 Parameters:
00558 wiiMote: a bluetooth-connected, calibrated WIIMote instance
00559 freq: the message sending frequency in messages/sec. Max is 100, because
00560 the Wiimote only samples the sensors at 100Hz.
00561 """
00562
00563 WiimoteDataSender.__init__(self, wiiMote, freq)
00564
00565 self.pub = rospy.Publisher('/wiimote/state', State)
00566
00567 def run(self):
00568 """Loop that obtains the latest wiimote state, publishes the data, and sleeps.
00569
00570 The wiimote message, if fully filled in, contains information in common with Imu.msg:
00571 acceleration (in m/s^2), and angular rate (in radians/sec). For each of
00572 these quantities, the IMU message format also wants the corresponding
00573 covariance matrix.
00574
00575 The covariance matrices are the 3x3 matrix with the axes' variance in the
00576 diagonal. We obtain the variance from the Wiimote instance.
00577 """
00578
00579 rospy.loginfo("Wiimote state publisher starting (topic /wiimote/state).")
00580 self.threadName = "Wiimote topic Publisher"
00581 try:
00582 while not rospy.is_shutdown():
00583 (canonicalAccel, canonicalNunchukAccel, canonicalAngleRate) = self.obtainWiimoteData()
00584
00585 zeroingTimeSecs = int(self.wiiMote.lastZeroingTime)
00586 zeroingTimeNSecs = int((self.wiiMote.lastZeroingTime - zeroingTimeSecs) * 10**9)
00587 msg = State(header=None,
00588 angular_velocity_zeroed=None,
00589 angular_velocity_raw=None,
00590 angular_velocity_covariance=self.angular_velocity_covariance,
00591 linear_acceleration_zeroed=None,
00592 linear_acceleration_raw=None,
00593 linear_acceleration_covariance=self.linear_acceleration_covariance,
00594 nunchuk_acceleration_zeroed=None,
00595 nunchuk_acceleration_raw=None,
00596 nunchuk_joystick_zeroed=None,
00597 nunchuk_joystick_raw=None,
00598 buttons=[False,False,False,False,False,False,False,False,False,False],
00599 nunchuk_buttons=[False,False],
00600 rumble=False,
00601 LEDs=None,
00602 ir_tracking = None,
00603 raw_battery=None,
00604 percent_battery=None,
00605 zeroing_time=rospy.Time(zeroingTimeSecs, zeroingTimeNSecs),
00606 errors=0)
00607
00608
00609
00610
00611
00612 if self.wiistate.motionPlusPresent:
00613 msg.angular_velocity_zeroed.x = canonicalAngleRate[PHI]
00614 msg.angular_velocity_zeroed.y = canonicalAngleRate[THETA]
00615 msg.angular_velocity_zeroed.z = canonicalAngleRate[PSI]
00616
00617 msg.angular_velocity_raw.x = self.wiistate.angleRateRaw[PHI]
00618 msg.angular_velocity_raw.y = self.wiistate.angleRateRaw[THETA]
00619 msg.angular_velocity_raw.z = self.wiistate.angleRateRaw[PSI]
00620
00621 else:
00622 msg.angular_velocity_covariance = self.gyroAbsence_covariance
00623
00624 msg.linear_acceleration_zeroed.x = canonicalAccel[X]
00625 msg.linear_acceleration_zeroed.y = canonicalAccel[Y]
00626 msg.linear_acceleration_zeroed.z = canonicalAccel[Z]
00627
00628 msg.linear_acceleration_raw.x = self.wiistate.accRaw[X]
00629 msg.linear_acceleration_raw.y = self.wiistate.accRaw[Y]
00630 msg.linear_acceleration_raw.z = self.wiistate.accRaw[Z]
00631
00632 if self.wiistate.nunchukPresent:
00633 msg.nunchuk_acceleration_zeroed.x = canonicalNunchukAccel[X]
00634 msg.nunchuk_acceleration_zeroed.y = canonicalNunchukAccel[Y]
00635 msg.nunchuk_acceleration_zeroed.z = canonicalNunchukAccel[Z]
00636
00637 msg.nunchuk_acceleration_raw.x = self.wiistate.nunchukAccRaw[X]
00638 msg.nunchuk_acceleration_raw.y = self.wiistate.nunchukAccRaw[Y]
00639 msg.nunchuk_acceleration_raw.z = self.wiistate.nunchukAccRaw[Z]
00640
00641 msg.nunchuk_joystick_zeroed = self.wiistate.nunchukStick
00642 msg.nunchuk_joystick_raw = self.wiistate.nunchukStickRaw
00643 moreButtons = []
00644 moreButtons.append(self.wiistate.nunchukButtons[BTN_Z])
00645 moreButtons.append(self.wiistate.nunchukButtons[BTN_C])
00646 msg.nunchuk_buttons = moreButtons
00647
00648 theButtons = []
00649 theButtons.append(self.wiistate.buttons[BTN_1])
00650 theButtons.append(self.wiistate.buttons[BTN_2])
00651 theButtons.append(self.wiistate.buttons[BTN_PLUS])
00652 theButtons.append(self.wiistate.buttons[BTN_MINUS])
00653 theButtons.append(self.wiistate.buttons[BTN_A])
00654 theButtons.append(self.wiistate.buttons[BTN_B])
00655 theButtons.append(self.wiistate.buttons[BTN_UP])
00656 theButtons.append(self.wiistate.buttons[BTN_DOWN])
00657 theButtons.append(self.wiistate.buttons[BTN_LEFT])
00658 theButtons.append(self.wiistate.buttons[BTN_RIGHT])
00659 theButtons.append(self.wiistate.buttons[BTN_HOME])
00660
00661 ledStates = self.wiiMote.getLEDs()
00662 for indx in range(len(msg.LEDs)):
00663 if ledStates[indx]:
00664 msg.LEDs[indx] = True
00665 else:
00666 msg.LEDs[indx] = False
00667
00668 msg.buttons = theButtons
00669
00670 msg.raw_battery = self.wiiMote.getBattery()
00671 msg.percent_battery = msg.raw_battery * 100./self.wiiMote.BATTERY_MAX
00672
00673 irSources = self.wiistate.IRSources
00674
00675 for irSensorIndx in range(NUM_IR_SENSORS):
00676 if irSources[irSensorIndx] is not None:
00677
00678 try:
00679 pos = irSources[irSensorIndx]['pos']
00680 except KeyError:
00681
00682 msg.ir_tracking.append(IrSourceInfo(State.INVALID_FLOAT, State.INVALID_FLOAT, State.INVALID))
00683
00684 else:
00685
00686
00687 try:
00688 size = irSources[irSensorIndx]['size']
00689 except KeyError:
00690
00691 size = State.INVALID
00692 lightInfo = IrSourceInfo(pos[0], pos[1], size)
00693 msg.ir_tracking.append(lightInfo)
00694 else:
00695 msg.ir_tracking.append(IrSourceInfo(State.INVALID_FLOAT, State.INVALID_FLOAT, State.INVALID))
00696
00697 measureTime = self.wiistate.time
00698 timeSecs = int(measureTime)
00699 timeNSecs = int(abs(timeSecs - measureTime) * 10**9)
00700 msg.header.stamp.secs = timeSecs
00701 msg.header.stamp.nsecs = timeNSecs
00702
00703 try:
00704 self.pub.publish(msg)
00705 except rospy.ROSException:
00706 rospy.loginfo("Topic /wiimote/state closed. Shutting down Wiimote sender.")
00707 exit(0)
00708
00709
00710
00711
00712
00713
00714 rospy.sleep(self.sleepDuration)
00715 except rospy.ROSInterruptException:
00716 rospy.loginfo("Shutdown request. Shutting down Wiimote sender.")
00717 exit(0)
00718
00719 class WiimoteListeners(threading.Thread):
00720 """Listen for request to rumble and LED blinking.
00721
00722 Subscribes to topics /rumble and /leds, listening for RumbleControl
00723 and LEDControl messages.
00724
00725 Parameters: The switch_mode field is either
00726 -1.0 to turn rumble on, zero to turn it off, or a
00727 positive float. If switch_mode is such a positive number,
00728 it is taken to be the repeat count for an on/off rumble
00729 pattern (see next parameter)
00730
00731 The pulse_pattern is a float32[MAX_RUMBLE_PATTERN_LENGTH],
00732 which contains fractional seconds that rumble is to be
00733 on or off.
00734 """
00735
00736 def __init__(self, wiiMote):
00737
00738 threading.Thread.__init__(self)
00739 self.wiiMote = wiiMote
00740 self.pulserThread = None
00741
00742
00743
00744
00745
00746 self.is_calibratedPublisher = rospy.Publisher('/imu/is_calibrated', Bool, latch=True)
00747
00748 self.is_CalibratedResponseMsg = Bool();
00749
00750
00751
00752
00753
00754
00755 self.is_CalibratedResponseMsg.data = self.wiiMote.latestCalibrationSuccessful;
00756 self.is_calibratedPublisher.publish(self.is_CalibratedResponseMsg)
00757
00758 def run(self):
00759
00760 def rumbleSwitchCallback(msg):
00761 """Callback for turning rumble on/off, and to initiate pulse rumble."""
00762
00763
00764
00765
00766 if self.pulserThread is not None:
00767 self.pulserThread.stop = True
00768
00769 self.pulserThread.join()
00770 self.pulserThread = None
00771
00772 if msg.rumble.switch_mode == TimedSwitch.ON:
00773 self.wiiMote.setRumble(True)
00774 return
00775 elif msg.rumble.switch_mode == TimedSwitch.OFF:
00776 self.wiiMote.setRumble(False)
00777 return
00778 elif msg.rumble.switch_mode != SWITCH_PULSE_PATTERN:
00779 rospy.loginfo("Illegal switch_mode value in rumble request from " + \
00780 rospy.get_caller_id() + \
00781 ": \n" + \
00782 str(msg))
00783 return
00784
00785
00786 if msg.rumble.num_cycles == 0:
00787 return
00788
00789
00790
00791
00792 self.pulserThread = SwitchPulser([OutputPattern(msg.rumble.pulse_pattern, msg.rumble.num_cycles)], RUMBLE, self.wiiMote)
00793 self.pulserThread.start()
00794 self.pulserThread.join()
00795
00796 return
00797
00798
00799 def ledControlCallback(msg):
00800 """Callback for incoming LEDCOntrol requests."""
00801
00802
00803
00804
00805
00806
00807
00808
00809
00810 patterns = []
00811 ledCommands = [None, None, None, None]
00812 individualLED_simple_on_or_off = False
00813
00814
00815
00816
00817
00818
00819 for timedSwitch, switchIndex in zip(msg.timed_switch_array, range(len(msg.timed_switch_array))):
00820
00821 if timedSwitch.switch_mode == TimedSwitch.ON:
00822
00823 individualLED_simple_on_or_off = True
00824 ledCommands[switchIndex] = TimedSwitch.ON
00825
00826
00827 patterns.append(None)
00828 continue
00829 elif timedSwitch.switch_mode == TimedSwitch.OFF:
00830
00831 individualLED_simple_on_or_off = True
00832 ledCommands[switchIndex] = TimedSwitch.OFF
00833
00834
00835 patterns.append(None)
00836 continue
00837 elif timedSwitch.switch_mode == TimedSwitch.NO_CHANGE:
00838 patterns.append(None)
00839 continue
00840
00841 patterns.append(OutputPattern(timedSwitch.pulse_pattern, timedSwitch.num_cycles))
00842
00843
00844
00845 if individualLED_simple_on_or_off:
00846 self.wiiMote.setLEDs(ledCommands)
00847
00848
00849 self.pulserThread = SwitchPulser(patterns, LED, self.wiiMote)
00850 self.pulserThread.start()
00851 self.pulserThread.join()
00852
00853 return
00854
00855 def calibrateCallback(req):
00856 """The imu/calibrate service handler."""
00857
00858 rospy.loginfo("Calibration request")
00859
00860 calibrationSuccess = self.wiiMote.zeroDevice()
00861
00862
00863
00864 self.is_CalibratedResponseMsg.data = calibrationSuccess
00865 self.is_calibratedPublisher.publish(self.is_CalibratedResponseMsg)
00866
00867 return EmptyResponse()
00868
00869
00870
00871
00872
00873 rospy.loginfo("Wiimote rumble listener starting (topic /wiimote/rumble).")
00874 rospy.Subscriber("/wiimote/rumble", RumbleControl, rumbleSwitchCallback)
00875 rospy.loginfo("Wiimote LED control listener starting (topic /wiimote/leds).")
00876 rospy.Subscriber("/wiimote/leds", LEDControl, ledControlCallback)
00877 rospy.loginfo("Wiimote calibration service starting (topic /imu/calibrate).")
00878 rospy.Service("imu/calibrate", Empty, calibrateCallback)
00879 rospy.loginfo("Wiimote latched is_calibrated publisher starting (topic /imu/is_calibrated).")
00880
00881 try:
00882 rospy.spin()
00883 except rospy.ROSInterruptException:
00884 rospy.loginfo("Shutdown request. Shutting down Wiimote listeners.")
00885 exit(0)
00886
00887
00888 class SwitchPulser(threading.Thread):
00889 """Thread for executing rumble and LED pulse patterns."""
00890
00891 def __init__(self, patternArray, outputIndicator, wiimoteDevice):
00892 """Parameters:
00893 o patternArray: For each pattern: an OutputPattern object
00894 There will only be one such object for Rumble output.
00895 For LEDs there will be one for each LED on the Wiimote.
00896 If one of the elements is None, that output indicator
00897 is left unchanged. For example, if the 2nd element
00898 in an LED pattern object array is None, the 2nd LED on the
00899 Wiimote will be left in its current state.
00900
00901 Note that the patterns may be of different lengths.
00902 So, one LED might have a 3-state pattern, while another
00903 LED's pattern is 5 states long.
00904 o RUMBLE or LED to indicate what is to be pulsed
00905 o A Wiimote device object
00906
00907 Note: We always start the affected indicators as if they were
00908 in the OFF state, and we always leave them in the off state.
00909 """
00910
00911 threading.Thread.__init__(self)
00912 self.patternArray = patternArray
00913 self.wiimoteDevice = wiimoteDevice
00914
00915 self.outputIndicator = outputIndicator
00916
00917
00918 self.stop = False
00919
00920 def run(self):
00921
00922
00923
00924
00925 self.turnIndicatorOn(self.outputIndicator)
00926
00927 numPatterns = len(self.patternArray)
00928 try:
00929 while not rospy.is_shutdown() and not self.stop:
00930
00931
00932 nextDuration = float('inf')
00933
00934
00935
00936
00937 for pattern in self.patternArray:
00938
00939 if pattern is None: continue
00940 patternHeadTime = pattern.timeRemaining()
00941 if patternHeadTime is None: continue
00942 nextDuration = min(nextDuration, patternHeadTime)
00943
00944
00945 if nextDuration == float('inf'):
00946
00947 exit(0)
00948
00949 rospy.sleep(nextDuration)
00950
00951
00952
00953
00954
00955
00956
00957
00958
00959
00960 durationJustFinished = nextDuration
00961 for pattern, patternIndex in zip(self.patternArray, range(numPatterns)):
00962
00963 if pattern is None:
00964 continue
00965
00966
00967
00968
00969
00970 reducedTime = pattern.reduceTimer(durationJustFinished)
00971
00972
00973
00974
00975 if pattern.startOfRepeat:
00976 self.turnIndicatorOn(self.outputIndicator)
00977
00978 if reducedTime is None: continue
00979 if reducedTime == 0.:
00980
00981 self.flipRumbleOrLED(patternIndex)
00982
00983
00984
00985 except rospy.ROSInterruptException:
00986 rospy.loginfo("Shutdown request. Shutting down pulse switcher.")
00987 finally:
00988
00989
00990
00991 if self.outputIndicator == RUMBLE:
00992 self.wiimoteDevice.setRumble(False)
00993 exit(0)
00994 elif self.outputIndicator == LED:
00995 for oneLED, LEDIndex in zip(self.LEDMask, range(len(self.LEDMask))):
00996 if oneLED is not None:
00997 self.LEDMask[LEDIndex] = False
00998 self.wiimoteDevice.setLEDs(self.LEDMask)
00999
01000
01001
01002 def turnIndicatorOn(self, theIndicator):
01003 """Turns indicator(s) of interest ON.
01004
01005 Parameter: RUMBLE or LED
01006 """
01007
01008
01009
01010
01011 if theIndicator == RUMBLE and self.patternArray[0] is not None:
01012
01013 self.wiimoteDevice.setRumble(True)
01014
01015
01016 elif theIndicator == LED:
01017
01018
01019
01020
01021
01022 self.LEDMask = []
01023 for i in range(min(NUM_LEDS, len(self.patternArray))):
01024 try:
01025 if self.patternArray[i] is None:
01026 self.LEDMask.append(None)
01027 else:
01028 self.LEDMask.append(True)
01029 except IndexError:
01030 pass
01031
01032 self.wiimoteDevice.setLEDs(self.LEDMask)
01033 else:
01034 raise ValueError("Only RUMBLE and LED are legal values here.")
01035
01036
01037 def flipRumbleOrLED(self, index=0):
01038
01039 if self.outputIndicator == RUMBLE:
01040 self.flipRumble()
01041 else:
01042 self.flipLED(index)
01043
01044
01045 def flipRumble(self):
01046 self.wiimoteDevice.setRumble(not self.wiimoteDevice.getRumble())
01047
01048
01049 def flipLED(self, index):
01050
01051 LEDStatus = self.wiimoteDevice.getLEDs()
01052
01053
01054 newLEDStatus = [None, None, None, None]
01055
01056 newLEDStatus[index] = not LEDStatus[index]
01057 self.wiimoteDevice.setLEDs(newLEDStatus)
01058
01059 class OutputPattern(object):
01060 """Instances encapsulate rumble or LED on/off time patterns as received from related ROS messages.
01061
01062 This class provides convenient encapsulation for the pattern arrays themselves,
01063 for associated pointers into the arrays, and for status change and inquiry requests.
01064 Terminology: 'Pattern Head' is the currently used time duration. A pattern is 'Spent'
01065 if all the time sequences have run, and no repeats are left.
01066
01067 Public instance variables:
01068 o startOfRepeat ; indicates whether pattern just starts to repeat. (see method reduceTimer())
01069 """
01070
01071
01072 def __init__(self, rosMsgPattern, numReps):
01073 """Takes a TimedSwitch type ROS message (pattern and number of repeats), and initializes the pointers."""
01074
01075
01076
01077
01078 self.origTimePattern = []
01079 for timeDuration in rosMsgPattern:
01080 self.origTimePattern.append(timeDuration)
01081
01082
01083
01084
01085 self.timePattern = self.origTimePattern[:]
01086 self.numReps = numReps
01087
01088 self.patternPt = 0
01089 self.patternSpent = False
01090 self.startOfRepeat = False
01091
01092 def timeRemaining(self):
01093 """Return the time at the pattern head. If pattern is spent, return None instead."""
01094
01095 if self.patternSpent:
01096 return None
01097 return self.timePattern[self.patternPt]
01098
01099 def resetForRepeat(self):
01100 """Get ready to repeat the pattern. Returns True if another repeat is allowed, else returns False"""
01101
01102 if self.patternSpent:
01103 return False
01104
01105 self.numReps -= 1
01106 if self.numReps <= 0:
01107 return False
01108
01109
01110 self.patternPt = 0
01111
01112
01113
01114
01115
01116 self.timePattern = self.origTimePattern[:]
01117
01118 return True
01119
01120 def reduceTimer(self, time):
01121 """Given a float fractional number of seconds, subtract the time from the pattern head
01122
01123 Returns the remaining time, rounded to 100th of a second, or None. If the
01124 remaining time after subtraction is <= 0, we check whether any repeats
01125 are left. If so, we get ready for another repeat, and return the time of the
01126 first pattern time. Else we return None, indicating that this pattern
01127 is spent.
01128
01129 After this method returns, this instance's public startOfRepeat variable
01130 will hold True or False, depending on whether the pattern is
01131 just starting over.
01132 """
01133
01134 if self.patternSpent:
01135 return None
01136
01137 res = self.timePattern[self.patternPt] - time
01138
01139 if res < 0:
01140 res = 0.0
01141
01142 self.timePattern[self.patternPt] = res
01143 res = round(res,2)
01144
01145 self.startOfRepeat = False
01146
01147 if res <= 0:
01148
01149 self.patternPt += 1
01150 if self.patternPt < len(self.timePattern):
01151 return res
01152
01153
01154 canRepeat = self.resetForRepeat()
01155
01156 if canRepeat:
01157 self.startOfRepeat = True
01158
01159
01160 return self.timePattern[self.patternPt]
01161 else:
01162
01163 self.patternSpent = True
01164 return None
01165 return res
01166
01167 def __repr__(self):
01168 res = "<OutputPattern. Reps:" + str(self.numReps) + ". Pattern: ["
01169 for i in range(min(3, len(self.timePattern))):
01170 res += str(self.timePattern[i]) + ", "
01171
01172 return res + "...]>"
01173
01174
01175 if __name__ == '__main__':
01176 wiimoteNode = WiimoteNode()
01177 try:
01178 wiimoteNode.runWiimoteNode()
01179
01180 except rospy.ROSInterruptException:
01181 rospy.loginfo("ROS Shutdown Request.")
01182 except KeyboardInterrupt, e:
01183 rospy.loginfo("Received keyboard interrupt.")
01184 except WiimoteNotFoundError, e:
01185 rospy.logfatal(str(e))
01186 except WiimoteEnableError, e:
01187 rospy.logfatal(str(e))
01188 except CallbackStackMultInstError, e:
01189 rospy.logfatal(str(e))
01190 except CallbackStackEmptyError, e:
01191 rospy.logfatal(str(e))
01192 except ResumeNonPausedError, e:
01193 rospy.logfatal(str(e))
01194 except CallbackStackEmptyError, e:
01195 rospy.logfatal(str(e))
01196
01197 except:
01198 excType, excValue, excTraceback = sys.exc_info()[:3]
01199 traceback.print_exception(excType, excValue, excTraceback)
01200 finally:
01201 if (wiimoteNode is not None):
01202 wiimoteNode.shutdown()
01203 rospy.loginfo("Exiting Wiimote node.")
01204 sys.exit(0)