35 The wiimote_node broadcasts four topics, and listens to messages that control 36 the Wiimote stick's rumble (vibration) and LEDs. Transmitted topics (@100Hz): 38 o wiijoy Messages contain the accelerometer and gyro axis data, and all button states. 39 o imu/data Messages contain the accelerometer and gyro axis data, and covariance matrices 40 o wiimote/state the wiijoy and /imu messages, the button states, the LED states, 41 rumble (i.e. vibrator) state, IR light sensor readings, time since last zeroed, 42 and battery state. See State.msg 43 o imu/is_calibrated Latched message 44 o nunchuk Joy messages using the nunchuk as a joystick 45 o classic Joy messages using the nunchuck as a joystic 47 The node listens to the following messages: 50 Topic that listens to sensor_msgs/JoyFeedbackArray messages. 51 This controls the LEDs and the Rumble. There are 4 LEDs with ids of 0 through 3. 52 The "Player 1" LED is id 0; the "Player 4" LED is id3. 53 The Wiimote only has one rumble, so it is id 0. 55 Request to calibrate the device. 57 No command line parameters. 79 from geometry_msgs.msg
import Vector3
80 from sensor_msgs.msg
import Imu
81 from std_srvs.srv
import Empty
82 from std_srvs.srv
import EmptyResponse
83 from std_msgs.msg
import Bool
84 from sensor_msgs.msg
import Joy
85 from sensor_msgs.msg
import JoyFeedback
86 from sensor_msgs.msg
import JoyFeedbackArray
87 from wiimote.msg
import IrSourceInfo
88 from wiimote.msg
import State
95 GATHER_CALIBRATION_STATS =
True 100 """Initialize the wiimote_node, establishing its name for communication with the Master""" 105 rospy.init_node(
'wiimote', anonymous=
True, log_level=rospy.ERROR)
107 wiimoteDevice.zeroDevice()
110 IMUSender(wiimoteDevice, freq=100).start()
111 JoySender(wiimoteDevice, freq=100).start()
112 WiiSender(wiimoteDevice, freq=100).start()
113 NunSender(wiimoteDevice, freq=100).start()
120 rospy.loginfo(
"Error in startup")
121 rospy.loginfo(sys.exc_info()[0])
124 wiimoteDevice.setRumble(
False)
125 wiimoteDevice.setLEDs([
False,
False,
False,
False])
126 wiimoteDevice.shutdown()
143 threading.Thread.__init__(self)
148 varianceAccelerator = self.
wiiMote.getVarianceAccelerator()
150 0., varianceAccelerator[Y], 0.,
151 0., 0., varianceAccelerator[Z]]
153 varianceGyro = self.
wiiMote.getVarianceGyro()
155 0., varianceGyro[Y], 0.,
156 0., 0., varianceGyro[Z]]
167 Retrieve one set of Wiimote measurements from the Wiimote instance. 168 Return scaled accelerator and gyro readings. 170 We canonicalize both accelerator and gyro data through 171 scaling them by constants that turn them into m/sec^2, and 172 radians/sec, respectively. 174 Return: list of canonicalized accelerator and gyro readings. 177 while not rospy.is_shutdown():
188 Scale accelerator, nunchuk accelerator, and gyro readings to be 189 m/sec^2, m/sec^2 and radians/sec, respectively. 194 canonicalAccel = self.
wiistate.acc.scale(EARTH_GRAVITY)
199 canonicalNunchukAccel = self.
wiistate.nunchukAcc.scale(EARTH_GRAVITY)
201 canonicalNunchukAccel =
None 207 canonicalAngleRate = self.
wiistate.angleRate.scale(GYRO_SCALE_FACTOR)
209 canonicalAngleRate =
None 211 return [canonicalAccel, canonicalNunchukAccel, canonicalAngleRate]
212 except AttributeError:
215 rospy.loginfo(self.threadName +
" shutting down.")
220 """Broadcasting Wiimote accelerator and gyro readings as IMU messages to Topic sensor_data/Imu""" 224 Initializes the Wiimote IMU publisher. 227 wiiMote: a bluetooth-connected, calibrated WIIMote instance 228 freq: the message sending frequency in messages/sec. Max is 100, because 229 the Wiimote only samples the sensors at 100Hz. 232 WiimoteDataSender.__init__(self, wiiMote, freq)
234 self.
pub = rospy.Publisher(
'imu/data', Imu, queue_size=1)
238 Loop that obtains the latest wiimote state, publishes the IMU data, and sleeps. 240 The IMU message, if fully filled in, contains information on orientation, 241 acceleration (in m/s^2), and angular rate (in radians/sec). For each of 242 these quantities, the IMU message format also wants the corresponding 245 Wiimote only gives us acceleration and angular rate. So we ensure that the orientation 246 data entry is marked invalid. We do this by setting the first 247 entry of its associated covariance matrix to -1. The covariance 248 matrices are the 3x3 matrix with the axes' variance in the 249 diagonal. We obtain the variance from the Wiimote instance. 252 rospy.loginfo(
"Wiimote IMU publisher starting (topic /imu/data).")
255 while not rospy.is_shutdown():
256 (canonicalAccel, canonicalNunchukAccel, canonicalAngleRate) = self.
obtainWiimoteData()
258 msg = Imu(header=
None,
260 orientation_covariance=[
265 angular_velocity=
None,
267 linear_acceleration=
None,
275 msg.angular_velocity.x = canonicalAngleRate[PHI]
276 msg.angular_velocity.y = canonicalAngleRate[THETA]
277 msg.angular_velocity.z = canonicalAngleRate[PSI]
281 msg.linear_acceleration.x = canonicalAccel[X]
282 msg.linear_acceleration.y = canonicalAccel[Y]
283 msg.linear_acceleration.z = canonicalAccel[Z]
286 timeSecs = int(measureTime)
287 timeNSecs = int(abs(timeSecs - measureTime) * 10**9)
288 msg.header.stamp.secs = timeSecs
289 msg.header.stamp.nsecs = timeNSecs
292 self.
pub.publish(msg)
293 except rospy.ROSException:
294 rospy.loginfo(
"Topic imu/data closed. Shutting down Imu sender.")
298 except rospy.ROSInterruptException:
299 rospy.loginfo(
"Shutdown request. Shutting down Imu sender.")
304 """Broadcasting Wiimote accelerator and gyro readings as Joy(stick) messages to Topic joy""" 307 Initializes the Wiimote Joy(stick) publisher. 310 wiiMote: a bluetooth-connected, calibrated WIIMote instance 311 freq: the message sending frequency in messages/sec. Max is 100, because 312 the Wiimote only samples the sensors at 100Hz. 314 WiimoteDataSender.__init__(self, wiiMote, freq)
316 self.
pub = rospy.Publisher(
'joy', Joy, queue_size=1)
320 Loop that obtains the latest wiimote state, publishes the joystick data, and sleeps. 322 The Joy.msg message types calls for just two fields: float32[] axes, and int32[] buttons. 324 rospy.loginfo(
"Wiimote joystick publisher starting (topic wiijoy).")
327 while not rospy.is_shutdown():
328 (canonicalAccel, canonicalNunchukAccel, canonicalAngleRate) = self.
obtainWiimoteData()
330 msg = Joy(header=
None,
331 axes=[canonicalAccel[X], canonicalAccel[Y], canonicalAccel[Z]],
337 msg.axes.extend([canonicalAngleRate[PHI], canonicalAngleRate[THETA], canonicalAngleRate[PSI]])
348 theButtons[State.MSG_BTN_1] = self.
wiistate.buttons[BTN_1]
349 theButtons[State.MSG_BTN_2] = self.
wiistate.buttons[BTN_2]
350 theButtons[State.MSG_BTN_A] = self.
wiistate.buttons[BTN_A]
351 theButtons[State.MSG_BTN_B] = self.
wiistate.buttons[BTN_B]
352 theButtons[State.MSG_BTN_PLUS] = self.
wiistate.buttons[BTN_PLUS]
353 theButtons[State.MSG_BTN_MINUS] = self.
wiistate.buttons[BTN_MINUS]
354 theButtons[State.MSG_BTN_LEFT] = self.
wiistate.buttons[BTN_LEFT]
355 theButtons[State.MSG_BTN_RIGHT] = self.
wiistate.buttons[BTN_RIGHT]
356 theButtons[State.MSG_BTN_UP] = self.
wiistate.buttons[BTN_UP]
357 theButtons[State.MSG_BTN_DOWN] = self.
wiistate.buttons[BTN_DOWN]
358 theButtons[State.MSG_BTN_HOME] = self.
wiistate.buttons[BTN_HOME]
360 msg.buttons = theButtons
363 timeSecs = int(measureTime)
364 timeNSecs = int(abs(timeSecs - measureTime) * 10**9)
366 msg.header.stamp.secs = timeSecs
367 msg.header.stamp.nsecs = timeNSecs
370 self.
pub.publish(msg)
371 except rospy.ROSException:
372 rospy.loginfo(
"Topic wiijoy closed. Shutting down Joy sender.")
376 except rospy.ROSInterruptException:
377 rospy.loginfo(
"Shutdown request. Shutting down Joy sender.")
382 """Broadcasting nunchuk accelerator and joystick readings as Joy(stick) messages to Topic joy""" 385 Initializes the nunchuk Joy(stick) publisher. 388 wiiMote: a bluetooth-connected, calibrated WIIMote instance 389 freq: the message sending frequency in messages/sec. Max is 100, because 390 the Wiimote only samples the sensors at 100Hz. 392 WiimoteDataSender.__init__(self, wiiMote, freq)
400 Loop that obtains the latest nunchuk state, publishes the joystick data, and sleeps. 402 The Joy.msg message types calls for just two fields: float32[] axes, and int32[] buttons. 406 while not rospy.is_shutdown():
409 if not self.
wiistate.nunchukPresent:
412 self.
pub = rospy.Publisher(
'/wiimote/nunchuk', Joy, queue_size=1)
413 rospy.loginfo(
"Wiimote Nunchuk joystick publisher starting (topic nunchuk).")
415 (joyx, joyy) = self.
wiistate.nunchukStick
417 msg = Joy(header=
None,
419 scaledAcc[X], scaledAcc[Y], scaledAcc[Z]],
422 theButtons = [
False,
False]
423 theButtons[State.MSG_BTN_Z] = self.
wiistate.nunchukButtons[BTN_Z]
424 theButtons[State.MSG_BTN_C] = self.
wiistate.nunchukButtons[BTN_C]
426 msg.buttons = theButtons
429 timeSecs = int(measureTime)
430 timeNSecs = int(abs(timeSecs - measureTime) * 10**9)
431 msg.header.stamp.secs = timeSecs
432 msg.header.stamp.nsecs = timeNSecs
435 self.
pub.publish(msg)
436 except rospy.ROSException:
437 rospy.loginfo(
"Topic /wiimote/nunchuk closed. Shutting down Nun sender.")
440 except rospy.ROSInterruptException:
441 rospy.loginfo(
"Shutdown request. Shutting down Nun sender.")
446 """Broadcasting Classic Controller joystick readings as Joy(stick) messages to Topic joy""" 449 Initializes the Classic Controller Joy(stick) publisher. 452 wiiMote: a bluetooth-connected, calibrated WIIMote instance 453 freq: the message sending frequency in messages/sec. Max is 100, because 454 the Wiimote only samples the sensors at 100Hz. 456 WiimoteDataSender.__init__(self, wiiMote, freq)
464 Loop that obtains the latest classic controller state, publishes the joystick data, and sleeps. 466 The Joy.msg message types calls for just two fields: float32[] axes, and int32[] buttons. 470 while not rospy.is_shutdown():
474 if not self.
wiistate.classicPresent:
477 self.
pub = rospy.Publisher(
'/wiimote/classic', Joy)
478 rospy.loginfo(
"Wiimote Classic Controller joystick publisher starting (topic /wiimote/classic).")
480 (l_joyx, l_joyy) = self.
wiistate.classicStickLeft
481 (r_joyx, r_joyy) = self.
wiistate.classicStickRight
483 l_joyx = -(l_joyx-33)/27.
484 l_joyy = (l_joyy-33)/27.
485 r_joyx = -(r_joyx-15)/13.
486 r_joyy = (r_joyy-15)/13.
488 if abs(l_joyx) < .05:
490 if abs(l_joyy) < .05:
492 if abs(r_joyx) < .05:
494 if abs(r_joyy) < .05:
497 msg = Joy(header=
None,
498 axes=[l_joyx, l_joyy, r_joyx, r_joyy],
502 False,
False,
False,
False,
False,
503 False,
False,
False,
False,
False,
504 False,
False,
False,
False,
False 506 theButtons[State.MSG_CLASSIC_BTN_X] = self.
wiistate.classicButtons[CLASSIC_BTN_X]
507 theButtons[State.MSG_CLASSIC_BTN_Y] = self.
wiistate.classicButtons[CLASSIC_BTN_Y]
508 theButtons[State.MSG_CLASSIC_BTN_A] = self.
wiistate.classicButtons[CLASSIC_BTN_A]
509 theButtons[State.MSG_CLASSIC_BTN_B] = self.
wiistate.classicButtons[CLASSIC_BTN_B]
510 theButtons[State.MSG_CLASSIC_BTN_PLUS] = self.
wiistate.classicButtons[CLASSIC_BTN_PLUS]
511 theButtons[State.MSG_CLASSIC_BTN_MINUS] = self.
wiistate.classicButtons[CLASSIC_BTN_MINUS]
512 theButtons[State.MSG_CLASSIC_BTN_LEFT] = self.
wiistate.classicButtons[CLASSIC_BTN_LEFT]
513 theButtons[State.MSG_CLASSIC_BTN_RIGHT] = self.
wiistate.classicButtons[CLASSIC_BTN_RIGHT]
514 theButtons[State.MSG_CLASSIC_BTN_UP] = self.
wiistate.classicButtons[CLASSIC_BTN_UP]
515 theButtons[State.MSG_CLASSIC_BTN_DOWN] = self.
wiistate.classicButtons[CLASSIC_BTN_DOWN]
516 theButtons[State.MSG_CLASSIC_BTN_HOME] = self.
wiistate.classicButtons[CLASSIC_BTN_HOME]
517 theButtons[State.MSG_CLASSIC_BTN_L] = self.
wiistate.classicButtons[CLASSIC_BTN_L]
518 theButtons[State.MSG_CLASSIC_BTN_R] = self.
wiistate.classicButtons[CLASSIC_BTN_R]
519 theButtons[State.MSG_CLASSIC_BTN_ZL] = self.
wiistate.classicButtons[CLASSIC_BTN_ZL]
520 theButtons[State.MSG_CLASSIC_BTN_ZR] = self.
wiistate.classicButtons[CLASSIC_BTN_ZR]
522 msg.buttons = theButtons
525 timeSecs = int(measureTime)
526 timeNSecs = int(abs(timeSecs - measureTime) * 10**9)
527 msg.header.stamp.secs = timeSecs
528 msg.header.stamp.nsecs = timeNSecs
531 self.
pub.publish(msg)
532 except rospy.ROSException:
533 rospy.loginfo(
"Topic /wiimote/classic closed. Shutting down Clas sender.")
536 except rospy.ROSInterruptException:
537 rospy.loginfo(
"Shutdown request. Shutting down Clas sender.")
542 """Broadcasting complete Wiimote messages to Topic wiimote""" 545 Initializes the full-Wiimote publisher. 548 wiiMote: a bluetooth-connected, calibrated WIIMote instance 549 freq: the message sending frequency in messages/sec. Max is 100, because 550 the Wiimote only samples the sensors at 100Hz. 552 WiimoteDataSender.__init__(self, wiiMote, freq)
554 self.
pub = rospy.Publisher(
'/wiimote/state', State, queue_size=1)
558 Loop that obtains the latest wiimote state, publishes the data, and sleeps. 560 The wiimote message, if fully filled in, contains information in common with Imu.msg: 561 acceleration (in m/s^2), and angular rate (in radians/sec). For each of 562 these quantities, the IMU message format also wants the corresponding 565 The covariance matrices are the 3x3 matrix with the axes' variance in the 566 diagonal. We obtain the variance from the Wiimote instance. 568 rospy.loginfo(
"Wiimote state publisher starting (topic /wiimote/state).")
571 while not rospy.is_shutdown():
573 (canonicalAccel, canonicalNunchukAccel, canonicalAngleRate) = self.
obtainWiimoteData()
575 zeroingTimeSecs = int(self.
wiiMote.lastZeroingTime)
576 zeroingTimeNSecs = int((self.
wiiMote.lastZeroingTime - zeroingTimeSecs) * 10**9)
577 msg = State(header=
None,
578 angular_velocity_zeroed=
None,
579 angular_velocity_raw=
None,
581 linear_acceleration_zeroed=
None,
582 linear_acceleration_raw=
None,
584 nunchuk_acceleration_zeroed=
None,
585 nunchuk_acceleration_raw=
None,
586 nunchuk_joystick_zeroed=
None,
587 nunchuk_joystick_raw=
None,
591 False,
False,
False,
False 593 nunchuk_buttons=[
False,
False],
598 percent_battery=
None,
599 zeroing_time=rospy.Time(zeroingTimeSecs, zeroingTimeNSecs),
607 msg.angular_velocity_zeroed.x = canonicalAngleRate[PHI]
608 msg.angular_velocity_zeroed.y = canonicalAngleRate[THETA]
609 msg.angular_velocity_zeroed.z = canonicalAngleRate[PSI]
611 msg.angular_velocity_raw.x = self.
wiistate.angleRateRaw[PHI]
612 msg.angular_velocity_raw.y = self.
wiistate.angleRateRaw[THETA]
613 msg.angular_velocity_raw.z = self.
wiistate.angleRateRaw[PSI]
618 msg.linear_acceleration_zeroed.x = canonicalAccel[X]
619 msg.linear_acceleration_zeroed.y = canonicalAccel[Y]
620 msg.linear_acceleration_zeroed.z = canonicalAccel[Z]
622 msg.linear_acceleration_raw.x = self.
wiistate.accRaw[X]
623 msg.linear_acceleration_raw.y = self.
wiistate.accRaw[Y]
624 msg.linear_acceleration_raw.z = self.
wiistate.accRaw[Z]
627 msg.nunchuk_acceleration_zeroed.x = canonicalNunchukAccel[X]
628 msg.nunchuk_acceleration_zeroed.y = canonicalNunchukAccel[Y]
629 msg.nunchuk_acceleration_zeroed.z = canonicalNunchukAccel[Z]
631 msg.nunchuk_acceleration_raw.x = self.
wiistate.nunchukAccRaw[X]
632 msg.nunchuk_acceleration_raw.y = self.
wiistate.nunchukAccRaw[Y]
633 msg.nunchuk_acceleration_raw.z = self.
wiistate.nunchukAccRaw[Z]
635 msg.nunchuk_joystick_zeroed = self.
wiistate.nunchukStick
636 msg.nunchuk_joystick_raw = self.
wiistate.nunchukStickRaw
638 moreButtons.append(self.
wiistate.nunchukButtons[BTN_Z])
639 moreButtons.append(self.
wiistate.nunchukButtons[BTN_C])
640 msg.nunchuk_buttons = moreButtons
643 theButtons.append(self.
wiistate.buttons[BTN_1])
644 theButtons.append(self.
wiistate.buttons[BTN_2])
645 theButtons.append(self.
wiistate.buttons[BTN_PLUS])
646 theButtons.append(self.
wiistate.buttons[BTN_MINUS])
647 theButtons.append(self.
wiistate.buttons[BTN_A])
648 theButtons.append(self.
wiistate.buttons[BTN_B])
649 theButtons.append(self.
wiistate.buttons[BTN_UP])
650 theButtons.append(self.
wiistate.buttons[BTN_DOWN])
651 theButtons.append(self.
wiistate.buttons[BTN_LEFT])
652 theButtons.append(self.
wiistate.buttons[BTN_RIGHT])
653 theButtons.append(self.
wiistate.buttons[BTN_HOME])
655 ledStates = self.
wiiMote.getLEDs()
656 for indx
in range(len(msg.LEDs)):
658 msg.LEDs[indx] =
True 660 msg.LEDs[indx] =
False 662 msg.buttons = theButtons
664 msg.raw_battery = self.
wiiMote.getBattery()
665 msg.percent_battery = msg.raw_battery * 100./self.
wiiMote.BATTERY_MAX
669 for irSensorIndx
in range(NUM_IR_SENSORS):
670 if irSources[irSensorIndx]
is not None:
673 pos = irSources[irSensorIndx][
'pos']
676 msg.ir_tracking.append(IrSourceInfo(State.INVALID_FLOAT,
684 size = irSources[irSensorIndx][
'size']
688 lightInfo = IrSourceInfo(pos[0], pos[1], size)
689 msg.ir_tracking.append(lightInfo)
691 msg.ir_tracking.append(IrSourceInfo(State.INVALID_FLOAT, State.INVALID_FLOAT, State.INVALID))
694 timeSecs = int(measureTime)
695 timeNSecs = int(abs(timeSecs - measureTime) * 10**9)
696 msg.header.stamp.secs = timeSecs
697 msg.header.stamp.nsecs = timeNSecs
700 self.
pub.publish(msg)
701 except rospy.ROSException:
702 rospy.loginfo(
"Topic /wiimote/state closed. Shutting down Wiimote sender.")
705 except rospy.ROSInterruptException:
706 rospy.loginfo(
"Shutdown request. Shutting down Wiimote sender.")
711 """Listen for request to rumble and LED blinking.""" 714 threading.Thread.__init__(self)
737 def feedbackCallback(msg):
738 """The callback for handle the feedback array messages and sending that to the Wiimote""" 740 if fb.type == JoyFeedback.TYPE_LED:
742 if fb.intensity >= 0.5:
747 rospy.logwarn(
"LED ID out of bounds, ignoring!")
748 elif fb.type == JoyFeedback.TYPE_RUMBLE:
750 if fb.intensity >= 0.5:
755 rospy.logwarn(
"RUMBLE ID out of bounds, ignoring!")
762 def calibrateCallback(req):
763 """The imu/calibrate service handler.""" 765 rospy.loginfo(
"Calibration request")
767 calibrationSuccess = self.
wiiMote.zeroDevice()
774 return EmptyResponse()
780 rospy.loginfo(
"Wiimote feedback listener starting (topic /joy/set_feedback).")
781 rospy.Subscriber(
"joy/set_feedback", JoyFeedbackArray, feedbackCallback)
782 rospy.loginfo(
"Wiimote calibration service starting (topic /imu/calibrate).")
783 rospy.Service(
"imu/calibrate", Empty, calibrateCallback)
784 rospy.loginfo(
"Wiimote latched is_calibrated publisher starting (topic /imu/is_calibrated).")
788 except rospy.ROSInterruptException:
789 rospy.loginfo(
"Shutdown request. Shutting down Wiimote listeners.")
793 if __name__ ==
'__main__':
796 wiimoteNode.runWiimoteNode()
798 except rospy.ROSInterruptException:
799 rospy.loginfo(
"ROS Shutdown Request.")
800 except KeyboardInterrupt, e:
801 rospy.loginfo(
"Received keyboard interrupt.")
802 except WiimoteNotFoundError, e:
803 rospy.logfatal(str(e))
804 except WiimoteEnableError, e:
805 rospy.logfatal(str(e))
806 except CallbackStackMultInstError, e:
807 rospy.logfatal(str(e))
808 except CallbackStackEmptyError, e:
809 rospy.logfatal(str(e))
810 except ResumeNonPausedError, e:
811 rospy.logfatal(str(e))
812 except CallbackStackEmptyError, e:
813 rospy.logfatal(str(e))
816 excType, excValue, excTraceback = sys.exc_info()[:3]
817 traceback.print_exception(excType, excValue, excTraceback)
819 if (wiimoteNode
is not None):
820 wiimoteNode.shutdown()
821 rospy.loginfo(
"Exiting Wiimote node.")
def __init__(self, wiiMote, freq=100)
def __init__(self, wiiMote, freq=100)
def __init__(self, wiiMote, freq=100)
def __init__(self, wiiMote, freq=100)
def obtainWiimoteData(self)
def __init__(self, wiiMote)
linear_acceleration_covariance
angular_velocity_covariance
def __init__(self, wiiMote, freq=100)
def canonicalizeWiistate(self)
def __init__(self, wiiMote, freq=100)