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()
124 wiimoteDevice.setRumble(
False)
125 wiimoteDevice.setLEDs([
False,
False,
False,
False])
126 wiimoteDevice.shutdown()
139 threading.Thread.__init__(self)
144 varianceAccelerator = self.
wiiMote.getVarianceAccelerator()
146 0., varianceAccelerator[Y], 0.,
147 0., 0., varianceAccelerator[Z]]
149 varianceGyro = self.
wiiMote.getVarianceGyro()
151 0., varianceGyro[Y], 0.,
152 0., 0., varianceGyro[Z]]
163 Retrieve one set of Wiimote measurements from the Wiimote instance.
164 Return scaled accelerator and gyro readings.
166 We canonicalize both accelerator and gyro data through
167 scaling them by constants that turn them into m/sec^2, and
168 radians/sec, respectively.
170 Return: list of canonicalized accelerator and gyro readings.
173 while not rospy.is_shutdown():
184 Scale accelerator, nunchuk accelerator, and gyro readings to be
185 m/sec^2, m/sec^2 and radians/sec, respectively.
190 canonicalAccel = self.
wiistate.acc.scale(EARTH_GRAVITY)
195 canonicalNunchukAccel = self.
wiistate.nunchukAcc.scale(EARTH_GRAVITY)
197 canonicalNunchukAccel =
None
203 canonicalAngleRate = self.
wiistate.angleRate.scale(GYRO_SCALE_FACTOR)
205 canonicalAngleRate =
None
207 return [canonicalAccel, canonicalNunchukAccel, canonicalAngleRate]
208 except AttributeError:
211 rospy.loginfo(self.threadName +
" shutting down.")
216 """Broadcasting Wiimote accelerator and gyro readings as IMU messages to Topic sensor_data/Imu"""
220 Initializes the Wiimote IMU publisher.
223 wiiMote: a bluetooth-connected, calibrated WIIMote instance
224 freq: the message sending frequency in messages/sec. Max is 100, because
225 the Wiimote only samples the sensors at 100Hz.
228 WiimoteDataSender.__init__(self, wiiMote, freq)
230 self.
pub = rospy.Publisher(
'imu/data', Imu, queue_size=1)
234 Loop that obtains the latest wiimote state, publishes the IMU data, and sleeps.
236 The IMU message, if fully filled in, contains information on orientation,
237 acceleration (in m/s^2), and angular rate (in radians/sec). For each of
238 these quantities, the IMU message format also wants the corresponding
241 Wiimote only gives us acceleration and angular rate. So we ensure that the orientation
242 data entry is marked invalid. We do this by setting the first
243 entry of its associated covariance matrix to -1. The covariance
244 matrices are the 3x3 matrix with the axes' variance in the
245 diagonal. We obtain the variance from the Wiimote instance.
248 rospy.loginfo(
"Wiimote IMU publisher starting (topic /imu/data).")
251 while not rospy.is_shutdown():
252 (canonicalAccel, canonicalNunchukAccel, canonicalAngleRate) = self.
obtainWiimoteData()
254 msg = Imu(header=
None,
256 orientation_covariance=[
261 angular_velocity=
None,
263 linear_acceleration=
None,
271 msg.angular_velocity.x = canonicalAngleRate[PHI]
272 msg.angular_velocity.y = canonicalAngleRate[THETA]
273 msg.angular_velocity.z = canonicalAngleRate[PSI]
277 msg.linear_acceleration.x = canonicalAccel[X]
278 msg.linear_acceleration.y = canonicalAccel[Y]
279 msg.linear_acceleration.z = canonicalAccel[Z]
282 timeSecs = int(measureTime)
283 timeNSecs = int(abs(timeSecs - measureTime) * 10**9)
284 msg.header.stamp.secs = timeSecs
285 msg.header.stamp.nsecs = timeNSecs
288 self.
pub.publish(msg)
289 except rospy.ROSException:
290 rospy.loginfo(
"Topic imu/data closed. Shutting down Imu sender.")
294 except rospy.ROSInterruptException:
295 rospy.loginfo(
"Shutdown request. Shutting down Imu sender.")
300 """Broadcasting Wiimote accelerator and gyro readings as Joy(stick) messages to Topic joy"""
303 Initializes the Wiimote Joy(stick) publisher.
306 wiiMote: a bluetooth-connected, calibrated WIIMote instance
307 freq: the message sending frequency in messages/sec. Max is 100, because
308 the Wiimote only samples the sensors at 100Hz.
310 WiimoteDataSender.__init__(self, wiiMote, freq)
312 self.
pub = rospy.Publisher(
'joy', Joy, queue_size=1)
316 Loop that obtains the latest wiimote state, publishes the joystick data, and sleeps.
318 The Joy.msg message types calls for just two fields: float32[] axes, and int32[] buttons.
320 rospy.loginfo(
"Wiimote joystick publisher starting (topic wiijoy).")
323 while not rospy.is_shutdown():
324 (canonicalAccel, canonicalNunchukAccel, canonicalAngleRate) = self.
obtainWiimoteData()
326 msg = Joy(header=
None,
327 axes=[canonicalAccel[X], canonicalAccel[Y], canonicalAccel[Z]],
333 msg.axes.extend([canonicalAngleRate[PHI], canonicalAngleRate[THETA], canonicalAngleRate[PSI]])
344 theButtons[State.MSG_BTN_1] = self.
wiistate.buttons[BTN_1]
345 theButtons[State.MSG_BTN_2] = self.
wiistate.buttons[BTN_2]
346 theButtons[State.MSG_BTN_A] = self.
wiistate.buttons[BTN_A]
347 theButtons[State.MSG_BTN_B] = self.
wiistate.buttons[BTN_B]
348 theButtons[State.MSG_BTN_PLUS] = self.
wiistate.buttons[BTN_PLUS]
349 theButtons[State.MSG_BTN_MINUS] = self.
wiistate.buttons[BTN_MINUS]
350 theButtons[State.MSG_BTN_LEFT] = self.
wiistate.buttons[BTN_LEFT]
351 theButtons[State.MSG_BTN_RIGHT] = self.
wiistate.buttons[BTN_RIGHT]
352 theButtons[State.MSG_BTN_UP] = self.
wiistate.buttons[BTN_UP]
353 theButtons[State.MSG_BTN_DOWN] = self.
wiistate.buttons[BTN_DOWN]
354 theButtons[State.MSG_BTN_HOME] = self.
wiistate.buttons[BTN_HOME]
356 msg.buttons = theButtons
359 timeSecs = int(measureTime)
360 timeNSecs = int(abs(timeSecs - measureTime) * 10**9)
362 msg.header.stamp.secs = timeSecs
363 msg.header.stamp.nsecs = timeNSecs
366 self.
pub.publish(msg)
367 except rospy.ROSException:
368 rospy.loginfo(
"Topic wiijoy closed. Shutting down Joy sender.")
372 except rospy.ROSInterruptException:
373 rospy.loginfo(
"Shutdown request. Shutting down Joy sender.")
378 """Broadcasting nunchuk accelerator and joystick readings as Joy(stick) messages to Topic joy"""
381 Initializes the nunchuk Joy(stick) publisher.
384 wiiMote: a bluetooth-connected, calibrated WIIMote instance
385 freq: the message sending frequency in messages/sec. Max is 100, because
386 the Wiimote only samples the sensors at 100Hz.
388 WiimoteDataSender.__init__(self, wiiMote, freq)
396 Loop that obtains the latest nunchuk state, publishes the joystick data, and sleeps.
398 The Joy.msg message types calls for just two fields: float32[] axes, and int32[] buttons.
402 while not rospy.is_shutdown():
405 if not self.
wiistate.nunchukPresent:
408 self.
pub = rospy.Publisher(
'/wiimote/nunchuk', Joy, queue_size=1)
409 rospy.loginfo(
"Wiimote Nunchuk joystick publisher starting (topic nunchuk).")
411 (joyx, joyy) = self.
wiistate.nunchukStick
413 msg = Joy(header=
None,
415 scaledAcc[X], scaledAcc[Y], scaledAcc[Z]],
418 theButtons = [
False,
False]
419 theButtons[State.MSG_BTN_Z] = self.
wiistate.nunchukButtons[BTN_Z]
420 theButtons[State.MSG_BTN_C] = self.
wiistate.nunchukButtons[BTN_C]
422 msg.buttons = theButtons
425 timeSecs = int(measureTime)
426 timeNSecs = int(abs(timeSecs - measureTime) * 10**9)
427 msg.header.stamp.secs = timeSecs
428 msg.header.stamp.nsecs = timeNSecs
431 self.
pub.publish(msg)
432 except rospy.ROSException:
433 rospy.loginfo(
"Topic /wiimote/nunchuk closed. Shutting down Nun sender.")
436 except rospy.ROSInterruptException:
437 rospy.loginfo(
"Shutdown request. Shutting down Nun sender.")
442 """Broadcasting Classic Controller joystick readings as Joy(stick) messages to Topic joy"""
445 Initializes the Classic Controller Joy(stick) publisher.
448 wiiMote: a bluetooth-connected, calibrated WIIMote instance
449 freq: the message sending frequency in messages/sec. Max is 100, because
450 the Wiimote only samples the sensors at 100Hz.
452 WiimoteDataSender.__init__(self, wiiMote, freq)
460 Loop that obtains the latest classic controller state, publishes the joystick data, and sleeps.
462 The Joy.msg message types calls for just two fields: float32[] axes, and int32[] buttons.
466 while not rospy.is_shutdown():
470 if not self.
wiistate.classicPresent:
473 self.
pub = rospy.Publisher(
'/wiimote/classic', Joy)
474 rospy.loginfo(
"Wiimote Classic Controller joystick publisher starting (topic /wiimote/classic).")
476 (l_joyx, l_joyy) = self.
wiistate.classicStickLeft
477 (r_joyx, r_joyy) = self.
wiistate.classicStickRight
479 l_joyx = -(l_joyx-33)/27.
480 l_joyy = (l_joyy-33)/27.
481 r_joyx = -(r_joyx-15)/13.
482 r_joyy = (r_joyy-15)/13.
484 if abs(l_joyx) < .05:
486 if abs(l_joyy) < .05:
488 if abs(r_joyx) < .05:
490 if abs(r_joyy) < .05:
493 msg = Joy(header=
None,
494 axes=[l_joyx, l_joyy, r_joyx, r_joyy],
498 False,
False,
False,
False,
False,
499 False,
False,
False,
False,
False,
500 False,
False,
False,
False,
False
502 theButtons[State.MSG_CLASSIC_BTN_X] = self.
wiistate.classicButtons[CLASSIC_BTN_X]
503 theButtons[State.MSG_CLASSIC_BTN_Y] = self.
wiistate.classicButtons[CLASSIC_BTN_Y]
504 theButtons[State.MSG_CLASSIC_BTN_A] = self.
wiistate.classicButtons[CLASSIC_BTN_A]
505 theButtons[State.MSG_CLASSIC_BTN_B] = self.
wiistate.classicButtons[CLASSIC_BTN_B]
506 theButtons[State.MSG_CLASSIC_BTN_PLUS] = self.
wiistate.classicButtons[CLASSIC_BTN_PLUS]
507 theButtons[State.MSG_CLASSIC_BTN_MINUS] = self.
wiistate.classicButtons[CLASSIC_BTN_MINUS]
508 theButtons[State.MSG_CLASSIC_BTN_LEFT] = self.
wiistate.classicButtons[CLASSIC_BTN_LEFT]
509 theButtons[State.MSG_CLASSIC_BTN_RIGHT] = self.
wiistate.classicButtons[CLASSIC_BTN_RIGHT]
510 theButtons[State.MSG_CLASSIC_BTN_UP] = self.
wiistate.classicButtons[CLASSIC_BTN_UP]
511 theButtons[State.MSG_CLASSIC_BTN_DOWN] = self.
wiistate.classicButtons[CLASSIC_BTN_DOWN]
512 theButtons[State.MSG_CLASSIC_BTN_HOME] = self.
wiistate.classicButtons[CLASSIC_BTN_HOME]
513 theButtons[State.MSG_CLASSIC_BTN_L] = self.
wiistate.classicButtons[CLASSIC_BTN_L]
514 theButtons[State.MSG_CLASSIC_BTN_R] = self.
wiistate.classicButtons[CLASSIC_BTN_R]
515 theButtons[State.MSG_CLASSIC_BTN_ZL] = self.
wiistate.classicButtons[CLASSIC_BTN_ZL]
516 theButtons[State.MSG_CLASSIC_BTN_ZR] = self.
wiistate.classicButtons[CLASSIC_BTN_ZR]
518 msg.buttons = theButtons
521 timeSecs = int(measureTime)
522 timeNSecs = int(abs(timeSecs - measureTime) * 10**9)
523 msg.header.stamp.secs = timeSecs
524 msg.header.stamp.nsecs = timeNSecs
527 self.
pub.publish(msg)
528 except rospy.ROSException:
529 rospy.loginfo(
"Topic /wiimote/classic closed. Shutting down Clas sender.")
532 except rospy.ROSInterruptException:
533 rospy.loginfo(
"Shutdown request. Shutting down Clas sender.")
538 """Broadcasting complete Wiimote messages to Topic wiimote"""
541 Initializes the full-Wiimote publisher.
544 wiiMote: a bluetooth-connected, calibrated WIIMote instance
545 freq: the message sending frequency in messages/sec. Max is 100, because
546 the Wiimote only samples the sensors at 100Hz.
548 WiimoteDataSender.__init__(self, wiiMote, freq)
550 self.
pub = rospy.Publisher(
'/wiimote/state', State, queue_size=1)
554 Loop that obtains the latest wiimote state, publishes the data, and sleeps.
556 The wiimote message, if fully filled in, contains information in common with Imu.msg:
557 acceleration (in m/s^2), and angular rate (in radians/sec). For each of
558 these quantities, the IMU message format also wants the corresponding
561 The covariance matrices are the 3x3 matrix with the axes' variance in the
562 diagonal. We obtain the variance from the Wiimote instance.
564 rospy.loginfo(
"Wiimote state publisher starting (topic /wiimote/state).")
567 while not rospy.is_shutdown():
569 (canonicalAccel, canonicalNunchukAccel, canonicalAngleRate) = self.
obtainWiimoteData()
571 zeroingTimeSecs = int(self.
wiiMote.lastZeroingTime)
572 zeroingTimeNSecs = int((self.
wiiMote.lastZeroingTime - zeroingTimeSecs) * 10**9)
573 msg = State(header=
None,
574 angular_velocity_zeroed=
None,
575 angular_velocity_raw=
None,
577 linear_acceleration_zeroed=
None,
578 linear_acceleration_raw=
None,
580 nunchuk_acceleration_zeroed=
None,
581 nunchuk_acceleration_raw=
None,
582 nunchuk_joystick_zeroed=
None,
583 nunchuk_joystick_raw=
None,
587 False,
False,
False,
False
589 nunchuk_buttons=[
False,
False],
594 percent_battery=
None,
595 zeroing_time=rospy.Time(zeroingTimeSecs, zeroingTimeNSecs),
603 msg.angular_velocity_zeroed.x = canonicalAngleRate[PHI]
604 msg.angular_velocity_zeroed.y = canonicalAngleRate[THETA]
605 msg.angular_velocity_zeroed.z = canonicalAngleRate[PSI]
607 msg.angular_velocity_raw.x = self.
wiistate.angleRateRaw[PHI]
608 msg.angular_velocity_raw.y = self.
wiistate.angleRateRaw[THETA]
609 msg.angular_velocity_raw.z = self.
wiistate.angleRateRaw[PSI]
614 msg.linear_acceleration_zeroed.x = canonicalAccel[X]
615 msg.linear_acceleration_zeroed.y = canonicalAccel[Y]
616 msg.linear_acceleration_zeroed.z = canonicalAccel[Z]
618 msg.linear_acceleration_raw.x = self.
wiistate.accRaw[X]
619 msg.linear_acceleration_raw.y = self.
wiistate.accRaw[Y]
620 msg.linear_acceleration_raw.z = self.
wiistate.accRaw[Z]
623 msg.nunchuk_acceleration_zeroed.x = canonicalNunchukAccel[X]
624 msg.nunchuk_acceleration_zeroed.y = canonicalNunchukAccel[Y]
625 msg.nunchuk_acceleration_zeroed.z = canonicalNunchukAccel[Z]
627 msg.nunchuk_acceleration_raw.x = self.
wiistate.nunchukAccRaw[X]
628 msg.nunchuk_acceleration_raw.y = self.
wiistate.nunchukAccRaw[Y]
629 msg.nunchuk_acceleration_raw.z = self.
wiistate.nunchukAccRaw[Z]
631 msg.nunchuk_joystick_zeroed = self.
wiistate.nunchukStick
632 msg.nunchuk_joystick_raw = self.
wiistate.nunchukStickRaw
634 moreButtons.append(self.
wiistate.nunchukButtons[BTN_Z])
635 moreButtons.append(self.
wiistate.nunchukButtons[BTN_C])
636 msg.nunchuk_buttons = moreButtons
639 theButtons.append(self.
wiistate.buttons[BTN_1])
640 theButtons.append(self.
wiistate.buttons[BTN_2])
641 theButtons.append(self.
wiistate.buttons[BTN_PLUS])
642 theButtons.append(self.
wiistate.buttons[BTN_MINUS])
643 theButtons.append(self.
wiistate.buttons[BTN_A])
644 theButtons.append(self.
wiistate.buttons[BTN_B])
645 theButtons.append(self.
wiistate.buttons[BTN_UP])
646 theButtons.append(self.
wiistate.buttons[BTN_DOWN])
647 theButtons.append(self.
wiistate.buttons[BTN_LEFT])
648 theButtons.append(self.
wiistate.buttons[BTN_RIGHT])
649 theButtons.append(self.
wiistate.buttons[BTN_HOME])
651 ledStates = self.
wiiMote.getLEDs()
652 for indx
in range(len(msg.LEDs)):
654 msg.LEDs[indx] =
True
656 msg.LEDs[indx] =
False
658 msg.buttons = theButtons
660 msg.raw_battery = self.
wiiMote.getBattery()
661 msg.percent_battery = msg.raw_battery * 100./self.
wiiMote.BATTERY_MAX
665 for irSensorIndx
in range(NUM_IR_SENSORS):
666 if irSources[irSensorIndx]
is not None:
669 pos = irSources[irSensorIndx][
'pos']
672 msg.ir_tracking.append(IrSourceInfo(State.INVALID_FLOAT,
680 size = irSources[irSensorIndx][
'size']
684 lightInfo = IrSourceInfo(pos[0], pos[1], size)
685 msg.ir_tracking.append(lightInfo)
687 msg.ir_tracking.append(IrSourceInfo(State.INVALID_FLOAT, State.INVALID_FLOAT, State.INVALID))
690 timeSecs = int(measureTime)
691 timeNSecs = int(abs(timeSecs - measureTime) * 10**9)
692 msg.header.stamp.secs = timeSecs
693 msg.header.stamp.nsecs = timeNSecs
696 self.
pub.publish(msg)
697 except rospy.ROSException:
698 rospy.loginfo(
"Topic /wiimote/state closed. Shutting down Wiimote sender.")
701 except rospy.ROSInterruptException:
702 rospy.loginfo(
"Shutdown request. Shutting down Wiimote sender.")
707 """Listen for request to rumble and LED blinking."""
710 threading.Thread.__init__(self)
733 def feedbackCallback(msg):
734 """The callback for handle the feedback array messages and sending that to the Wiimote"""
736 if fb.type == JoyFeedback.TYPE_LED:
738 if fb.intensity >= 0.5:
743 rospy.logwarn(
"LED ID out of bounds, ignoring!")
744 elif fb.type == JoyFeedback.TYPE_RUMBLE:
746 if fb.intensity >= 0.5:
751 rospy.logwarn(
"RUMBLE ID out of bounds, ignoring!")
758 def calibrateCallback(req):
759 """The imu/calibrate service handler."""
761 rospy.loginfo(
"Calibration request")
763 calibrationSuccess = self.
wiiMote.zeroDevice()
770 return EmptyResponse()
776 rospy.loginfo(
"Wiimote feedback listener starting (topic /joy/set_feedback).")
777 rospy.Subscriber(
"joy/set_feedback", JoyFeedbackArray, feedbackCallback)
778 rospy.loginfo(
"Wiimote calibration service starting (topic /imu/calibrate).")
779 rospy.Service(
"imu/calibrate", Empty, calibrateCallback)
780 rospy.loginfo(
"Wiimote latched is_calibrated publisher starting (topic /imu/is_calibrated).")
784 except rospy.ROSInterruptException:
785 rospy.loginfo(
"Shutdown request. Shutting down Wiimote listeners.")
789 if __name__ ==
'__main__':
792 wiimoteNode.runWiimoteNode()
794 except rospy.ROSInterruptException:
795 rospy.loginfo(
"ROS Shutdown Request.")
796 except KeyboardInterrupt, e:
797 rospy.loginfo(
"Received keyboard interrupt.")
798 except WiimoteNotFoundError, e:
799 rospy.logfatal(str(e))
800 except WiimoteEnableError, e:
801 rospy.logfatal(str(e))
802 except CallbackStackMultInstError, e:
803 rospy.logfatal(str(e))
804 except CallbackStackEmptyError, e:
805 rospy.logfatal(str(e))
806 except ResumeNonPausedError, e:
807 rospy.logfatal(str(e))
808 except CallbackStackEmptyError, e:
809 rospy.logfatal(str(e))
812 if (wiimoteNode
is not None):
813 wiimoteNode.shutdown()
814 rospy.loginfo(
"Exiting Wiimote node.")