X-Git-Url: https://defiant.homedns.org/gitweb/?p=ros_wild_thumper.git;a=blobdiff_plain;f=scripts%2Fwt_node.py;h=bffabce5d178bb9fec2954587565c3363fa2fea3;hp=1cf9d8d49fc42608ca1db0b1a0ed69873a95e4ef;hb=2f194b3fe82009a77b79021f0c57e6ca55c1706f;hpb=b301eb19e6487497a8bb946c538a25aa4ee28c1d diff --git a/scripts/wt_node.py b/scripts/wt_node.py index 1cf9d8d..bffabce 100755 --- a/scripts/wt_node.py +++ b/scripts/wt_node.py @@ -4,6 +4,7 @@ import rospy import tf import struct +import thread import prctl import spidev from time import sleep @@ -13,42 +14,15 @@ from geometry_msgs.msg import Twist from nav_msgs.msg import Odometry from diagnostic_msgs.msg import DiagnosticArray, DiagnosticStatus, KeyValue from dynamic_reconfigure.server import Server -from sensor_msgs.msg import Imu, Range -from wild_thumper.msg import LedStripe +from sensor_msgs.msg import Imu, Range, BatteryState from wild_thumper.cfg import WildThumperConfig +from std_srvs.srv import SetBool, SetBoolResponse WHEEL_DIST = 0.248 +BATTERY_CAPACITY_FULL = 2.750 # Ah, NiMH=2.750, LiFePO4=3.100 +UPDATE_RATE = 20.0 -class LPD8806: - def __init__(self, bus, device, num_leds): - self.spi = spidev.SpiDev() - self.spi.open(bus, device) - self.spi.mode=0b00 - self.spi.max_speed_hz=int(2e6) - self.num_leds = num_leds - self.latch() - self.l = [(0, 0, 0)] * num_leds - self.update() - - def set(self, i, red=0, green=0, blue=0): - if red > 127 or green > 127 or blue > 127 or red < 0 or green < 0 or blue < 0: - raise Exception("Bad RGB Value") - self.l[i] = (red, green, blue) - - def latch(self): - self.spi.writebytes([0x0 for i in range((self.num_leds+31)/32)]) - - def update(self): - l = [] - for i in range(self.num_leds): - red, green, blue = self.l[i] - l.append(0x80 | green) - l.append(0x80 | red) - l.append(0x80 | blue) - self.spi.writebytes(l) - self.latch() - -class MoveBase: +class WTBase: def __init__(self): rospy.init_node('wild_thumper') prctl.set_name("wild_thumper") @@ -63,40 +37,36 @@ class MoveBase: self.pub_range_fwd_left = rospy.Publisher("range_forward_left", Range, queue_size=16) self.pub_range_fwd_right = rospy.Publisher("range_forward_right", Range, queue_size=16) self.pub_range_bwd = rospy.Publisher("range_backward", Range, queue_size=16) - self.pub_range_left = rospy.Publisher("range_left", Range, queue_size=16) - self.pub_range_right = rospy.Publisher("range_right", Range, queue_size=16) + self.pub_battery = rospy.Publisher("battery", BatteryState, queue_size=16) self.cmd_vel = None self.cur_vel = (0, 0) self.bMotorManual = False self.set_speed(0, 0) - rospy.loginfo("Init done") + self.volt_last_warn = rospy.Time.now() i2c_write_reg(0x50, 0x90, struct.pack("BB", 1, 1)) # switch direction - self.pStripe = LPD8806(1, 0, 12) rospy.Subscriber("cmd_vel_out", Twist, self.cmdVelReceived) - rospy.Subscriber("led_stripe", LedStripe, self.led_stripe_received) rospy.Subscriber("imu", Imu, self.imuReceived) + rospy.Service('~manipulator_enable', SetBool, self.enableManipulator) + self.bDocked = False + self.bDocked_last = False + self.battery_capacity = BATTERY_CAPACITY_FULL*3600 # As + rospy.loginfo("Init done") self.run() def run(self): - rate = rospy.Rate(20.0) + rate = rospy.Rate(UPDATE_RATE) sleep(3) # wait 3s for ros to register and establish all subscriber connections before sending reset diag reset_val = self.get_reset() rospy.loginfo("Reset Status: 0x%x" % reset_val) - ir_count = 0 sonar_count = 0 + i2c_write_reg(0x50, 0xA4, struct.pack("B", 1)) # enable Watchdog while not rospy.is_shutdown(): rospy.logdebug("Loop alive") + #print "Watchdog", struct.unpack(">B", i2c_read_reg(0x50, 0xA4, 1))[0] #print struct.unpack(">B", i2c_read_reg(0x50, 0xA2, 1))[0] # count test self.get_motor_err() self.get_odom() - self.get_voltage() - - if ir_count == 0: - self.get_dist_left() - ir_count+=1 - else: - self.get_dist_right() - ir_count=0 + self.get_power() if sonar_count == 0: self.get_dist_forward_left() @@ -115,21 +85,25 @@ class MoveBase: self.set_speed(self.cmd_vel[0], self.cmd_vel[1]) self.cur_vel = self.cmd_vel self.cmd_vel = None + + self.check_docked() rate.sleep() def execute_dyn_reconf(self, config, level): self.bClipRangeSensor = config["range_sensor_clip"] self.range_sensor_max = config["range_sensor_max"] + self.range_sensor_fov = config["range_sensor_fov"] self.odom_covar_xy = config["odom_covar_xy"] self.odom_covar_angle = config["odom_covar_angle"] self.rollover_protect = config["rollover_protect"] self.rollover_protect_limit = config["rollover_protect_limit"] self.rollover_protect_pwm = config["rollover_protect_pwm"] + self.bStayDocked = config["stay_docked"] return config def imuReceived(self, msg): - if self.rollover_protect and any(self.cur_vel): + if self.rollover_protect and (any(self.cur_vel) or self.bMotorManual): (roll, pitch, yaw) = tf.transformations.euler_from_quaternion(msg.orientation.__getstate__()) if pitch > self.rollover_protect_limit*pi/180: self.bMotorManual = True @@ -197,19 +171,47 @@ class MoveBase: msg.status.append(stat) self.pub_diag.publish(msg) - def get_voltage(self): + def get_power(self): volt = struct.unpack(">h", i2c_read_reg(0x52, 0x09, 2))[0]/100.0 + current = struct.unpack(">h", i2c_read_reg(0x52, 0x0D, 2))[0]/1000.0 msg = DiagnosticArray() msg.header.stamp = rospy.Time.now() stat = DiagnosticStatus() - stat.name = "Voltage" - stat.level = DiagnosticStatus.ERROR if volt < 7 else DiagnosticStatus.OK - stat.message = "%.2fV" % volt + stat.name = "Power" + stat.level = DiagnosticStatus.ERROR if volt < 7 or current > 5 else DiagnosticStatus.OK + stat.message = "%.2fV, %.2fA" % (volt, current) msg.status.append(stat) self.pub_diag.publish(msg) + if volt < 7 and (rospy.Time.now() - self.volt_last_warn).to_sec() > 10: + rospy.logerr("Voltage critical: %.2fV" % (volt)) + self.volt_last_warn = rospy.Time.now() + + self.bDocked = volt > 10.1 + self.update_capacity(volt, current) + + if self.pub_battery.get_num_connections() > 0: + batmsg = BatteryState() + batmsg.header.stamp = rospy.Time.now() + batmsg.voltage = volt + batmsg.current = current + batmsg.charge = self.battery_capacity/3600.0 + batmsg.capacity = float('nan') + batmsg.design_capacity = BATTERY_CAPACITY_FULL + batmsg.percentage = batmsg.charge/batmsg.design_capacity + batmsg.power_supply_status = BatteryState.POWER_SUPPLY_STATUS_DISCHARGING + batmsg.power_supply_health = BatteryState.POWER_SUPPLY_HEALTH_UNKNOWN + batmsg.power_supply_technology = BatteryState.POWER_SUPPLY_TECHNOLOGY_NIMH + batmsg.present = True + self.pub_battery.publish(batmsg) + + def update_capacity(self, volt, current): + if self.bDocked: + self.battery_capacity = BATTERY_CAPACITY_FULL*3600 + else: + self.battery_capacity -= current/UPDATE_RATE def get_odom(self): speed_trans, speed_rot, posx, posy, angle = struct.unpack(">fffff", i2c_read_reg(0x50, 0x38, 20)) @@ -246,6 +248,7 @@ class MoveBase: odom.child_frame_id = "base_footprint" odom.twist.twist.linear.x = speed_trans odom.twist.twist.linear.y = 0.0 + odom.twist.twist.linear.z = 0.0 odom.twist.twist.angular.z = speed_rot odom.twist.covariance = odom.pose.covariance @@ -300,22 +303,10 @@ class MoveBase: msg.range = dist pub.publish(msg) - def get_dist_left(self): - if self.pub_range_left.get_num_connections() > 0: - dist = self.get_dist_ir(0x1) - if dist > -67: - self.send_range(self.pub_range_left, "ir_left", Range.INFRARED, 30.553/(dist - -67.534), 0.04, 0.3, 1) - - def get_dist_right(self): - if self.pub_range_right.get_num_connections() > 0: - dist = self.get_dist_ir(0x3) - if dist > 69: - self.send_range(self.pub_range_right, "ir_right", Range.INFRARED, 17.4/(dist - 69), 0.04, 0.3, 1) - def get_dist_forward_left(self): if self.pub_range_fwd_left.get_num_connections() > 0: dist = self.read_dist_srf(0x15) - self.send_range(self.pub_range_fwd_left, "sonar_forward_left", Range.ULTRASOUND, dist, 0.04, self.range_sensor_max, 30) + self.send_range(self.pub_range_fwd_left, "sonar_forward_left", Range.ULTRASOUND, dist, 0.04, self.range_sensor_max, self.range_sensor_fov) def update_dist_forward_left(self): if self.pub_range_fwd_left.get_num_connections() > 0: @@ -324,7 +315,7 @@ class MoveBase: def get_dist_backward(self): if self.pub_range_bwd.get_num_connections() > 0: dist = self.read_dist_srf(0x17) - self.send_range(self.pub_range_bwd, "sonar_backward", Range.ULTRASOUND, dist, 0.04, self.range_sensor_max, 30) + self.send_range(self.pub_range_bwd, "sonar_backward", Range.ULTRASOUND, dist, 0.04, self.range_sensor_max, self.range_sensor_fov) def update_dist_backward(self): if self.pub_range_bwd.get_num_connections() > 0: @@ -333,17 +324,40 @@ class MoveBase: def get_dist_forward_right(self): if self.pub_range_fwd_right.get_num_connections() > 0: dist = self.read_dist_srf(0x19) - self.send_range(self.pub_range_fwd_right, "sonar_forward_right", Range.ULTRASOUND, dist, 0.04, self.range_sensor_max, 30) + self.send_range(self.pub_range_fwd_right, "sonar_forward_right", Range.ULTRASOUND, dist, 0.04, self.range_sensor_max, self.range_sensor_fov) def update_dist_forward_right(self): if self.pub_range_fwd_right.get_num_connections() > 0: self.start_dist_srf(0xb) - def led_stripe_received(self, msg): - for led in msg.leds: - self.pStripe.set(led.num, red=led.red, green=led.green, blue=led.blue) - self.pStripe.update() - + def check_docked(self): + if self.bDocked and not self.bDocked_last: + rospy.loginfo("Docking event") + elif not self.bDocked and self.bDocked_last: + if not self.bStayDocked or self.cur_vel != (0, 0): + rospy.loginfo("Undocking event") + else: + rospy.loginfo("Undocking event..redocking") + thread.start_new_thread(self.redock, ()) + + self.bDocked_last = self.bDocked + + def redock(self): + self.cmd_vel = (-0.1, 0) + for i in range(100): + if self.bDocked: + break + sleep(0.01) + self.cmd_vel = (0, 0) + if self.bDocked: + rospy.loginfo("Redocking done") + else: + rospy.logerr("Redocking failed") + + def enableManipulator(self, msg): + rospy.loginfo("Set Manipulator: %d", msg.data) + i2c_write_reg(0x52, 0x0f, chr(msg.data)) + return SetBoolResponse(True, "") if __name__ == "__main__": - MoveBase() + WTBase()