Example #1
0
    def updateDynamicConfiguration_SERIALPORT(self):
        """
        Update dynamic configuration of ROS

        :param:

        :returns: none

        """

        global dynamicConfig_SERIAL_PORT

        # intialize dynamic configuration
        dynamicConfigServer = Server(DWM1001_Tune_SerialConfig,
                                     self.callbackDynamicConfig)
        # set close port to true
        dynamicConfig_CLOSE_PORT.update({"close_port": True})
        # set the open port to false
        dynamicConfig_OPEN_PORT.update({"open_port": False})
        # update the server
        dynamicConfigServer.update_configuration(dynamicConfig_OPEN_PORT)
        dynamicConfigServer.update_configuration(dynamicConfig_CLOSE_PORT)
        # update the server with opened port
        dynamicConfig_CLOSE_PORT.update({"close_port": False})
        # update the server with close port
        dynamicConfig_OPEN_PORT.update({"open_port": True})
        # update name of serial port in dynamic configuration
        dynamicConfig_SERIAL_PORT = {
            "serial_port": str(serialPortDWM1001.name)
        }
        # now update server configuration
        dynamicConfigServer.update_configuration(dynamicConfig_OPEN_PORT)
        dynamicConfigServer.update_configuration(dynamicConfig_OPEN_PORT)
        dynamicConfigServer.update_configuration(dynamicConfig_CLOSE_PORT)
        dynamicConfigServer.update_configuration(dynamicConfig_SERIAL_PORT)
Example #2
0
def updateDynamicConfiguration_SERIALPORT():
    global dynamicConfig_SERIAL_PORT

    # intialize dynamic configuration
    dynamicConfigServer = Server(DWM1001_Tune_SerialConfig, callbackDynamicConfig)
    # set close port to true
    dynamicConfig_CLOSE_PORT.update({"close_port": True})
    # set the open port to false
    dynamicConfig_OPEN_PORT.update({"open_port" : False})
    # update the server
    dynamicConfigServer.update_configuration(dynamicConfig_OPEN_PORT)
    dynamicConfigServer.update_configuration(dynamicConfig_CLOSE_PORT)
    # update the server with opened port
    dynamicConfig_CLOSE_PORT.update({"close_port": False})
    # update the server with close port
    dynamicConfig_OPEN_PORT.update({"open_port": True})
    # update name of serial port in dynamic configuration
    dynamicConfig_SERIAL_PORT = {"serial_port": str(SERIAL_PORT_DETAILS.name)}
    # now update server configuration
    dynamicConfigServer.update_configuration(dynamicConfig_OPEN_PORT)
    dynamicConfigServer.update_configuration(dynamicConfig_OPEN_PORT)
    dynamicConfigServer.update_configuration(dynamicConfig_CLOSE_PORT)
    dynamicConfigServer.update_configuration(dynamicConfig_SERIAL_PORT)
Example #3
0
class OGridServer:
    def __init__(self, frame_id='enu', map_size=500, resolution=0.3, rate=1):
        self.frame_id = frame_id
        self.ogrids = {}
        self.odom = None

        # Some default values
        self.plow = True
        self.plow_factor = 0
        self.ogrid_min_value = -1
        self.draw_bounds = False
        self.resolution = resolution
        self.ogrid_timeout = 2
        self.enforce_bounds = False
        self.enu_bounds = [[0, 0, 1], [0, 0, 1], [0, 0, 1], [0, 0, 1]]

        # Default to centering the ogrid
        position = np.array(
            [-(map_size * resolution) / 2, -(map_size * resolution) / 2, 0])
        quaternion = np.array([0, 0, 0, 1])
        self.origin = navigator_tools.numpy_quat_pair_to_pose(
            position, quaternion)

        self.global_ogrid = self.create_grid((map_size, map_size))

        set_odom = lambda msg: setattr(
            self, 'odom', navigator_tools.pose_to_numpy(msg.pose.pose))
        rospy.Subscriber('/odom', Odometry, set_odom)
        self.publisher = rospy.Publisher('/ogrid_master',
                                         OccupancyGrid,
                                         queue_size=1)

        self.ogrid_server = Server(OgridConfig, self.dynamic_cb)
        dynam_client = Client("bounds_server", config_callback=self.bounds_cb)
        self.ogrid_server.update_configuration({'width': 500})

        rospy.Service("/center_ogrid", Trigger, self.center_ogrid)
        rospy.Timer(rospy.Duration(1.0 / rate), self.publish)

    def center_ogrid(self, srv):
        fprint("CENTERING OGRID AT POSITION", msg_color='blue')
        if self.odom is None:
            return

        dim = -(self.map_size[0] * self.resolution) / 2
        new_org = self.odom[0] + np.array([dim, dim, 0])
        config = {}
        config['origin_x'] = float(new_org[0])
        config['origin_y'] = float(new_org[1])
        config['set_origin'] = True
        self.ogrid_server.update_configuration(config)

    def bounds_cb(self, config):
        fprint("BOUNDS DYNAMIC CONFIG UPDATE!", msg_color='blue')
        if hasattr(config, "enu_1_lat"):
            self.enu_bounds = [[config['enu_1_lat'], config['enu_1_long'], 1],
                               [config['enu_2_lat'], config['enu_2_long'], 1],
                               [config['enu_3_lat'], config['enu_3_long'], 1],
                               [config['enu_4_lat'], config['enu_4_long'], 1],
                               [config['enu_1_lat'], config['enu_1_long'], 1]]
        self.enforce_bounds = config['enforce']

    def dynamic_cb(self, config, level):
        fprint("OGRID DYNAMIC CONFIG UPDATE!", msg_color='blue')
        topics = config['topics'].replace(' ', '').split(',')
        replace_topics = config['replace_topics'].replace(' ', '').split(',')
        new_grids = {}

        for topic in topics:
            new_grids[topic] = OGrid(
                topic) if topic not in self.ogrids else self.ogrids[topic]

        for topic in replace_topics:
            new_grids[topic] = OGrid(
                topic, replace=True
            ) if topic not in self.ogrids else self.ogrids[topic]

        self.ogrids = new_grids

        map_size = map(int, (config['height'], config['width']))
        self.map_size = map_size

        self.plow = config['plow']
        self.plow_factor = config['plow_factor']
        self.ogrid_min_value = config['ogrid_min_value']
        self.draw_bounds = config['draw_bounds']
        self.resolution = config['resolution']
        self.ogrid_timeout = config['ogrid_timeout']

        if config['set_origin']:
            fprint("Setting origin!")
            position = np.array([config['origin_x'], config['origin_y'], 0])
            quaternion = np.array([0, 0, 0, 1])
            self.origin = navigator_tools.numpy_quat_pair_to_pose(
                position, quaternion)
        else:
            position = np.array([
                -(map_size[1] * self.resolution) / 2,
                -(map_size[0] * self.resolution) / 2, 0
            ])
            quaternion = np.array([0, 0, 0, 1])
            self.origin = navigator_tools.numpy_quat_pair_to_pose(
                position, quaternion)

        self.global_ogrid = self.create_grid(map_size)
        return config

    def create_grid(self, map_size):
        """
        Creates blank ogrids for everyone for the low low price of $9.95!
        
        `map_size` should be in the form of (h, w)
        """

        ogrid = OccupancyGrid()
        ogrid.header.stamp = rospy.Time.now()
        ogrid.header.frame_id = self.frame_id

        ogrid.info.resolution = self.resolution
        ogrid.info.width = map_size[1]
        ogrid.info.height = map_size[0]

        ogrid.info.origin = self.origin

        # TODO: Make sure this produces the correct sized ogrid
        ogrid.data = np.full((map_size[1], map_size[0]), -1).flatten()

        # fprint('Created Occupancy Map', msg_color='blue')
        return ogrid

    def publish(self, *args):
        # fprint("Merging Maps", newline=2)
        global_ogrid = deepcopy(self.global_ogrid)
        np_grid = numpyify(global_ogrid)

        # Global ogrid (only compute once)
        corners = get_enu_corners(global_ogrid)
        index_limits = transform_enu_to_ogrid(corners, global_ogrid)[:, :2]

        g_x_min = index_limits[0][0]
        g_x_max = index_limits[1][0]
        g_y_min = index_limits[0][1]
        g_y_max = index_limits[1][1]

        to_add = [o for o in self.ogrids.itervalues() if not o.replace]
        to_replace = [o for o in self.ogrids.itervalues() if o.replace]

        for ogrids in [to_add, to_replace]:
            for ogrid in ogrids:
                # Hard coded 5 second timeout - probably no need to reconfig this.
                if ogrid.nav_ogrid is None or ogrid.callback_delta > self.ogrid_timeout:
                    #fprint("Ogrid too old!")
                    continue

                # Proactively checking for errors.
                # This should be temporary but probably wont be.
                l_h, l_w = ogrid.nav_ogrid.info.height, ogrid.nav_ogrid.info.width
                g_h, g_w = global_ogrid.info.height, global_ogrid.info.width
                if l_h > g_h or l_w > g_w:
                    fprint("Proactively preventing errors in ogrid size.",
                           msg_color="red")
                    new_size = max(l_w, g_w, l_h, g_h)
                    self.global_ogrid = self.create_grid([new_size, new_size])

                # Local Ogrid (get everything in global frame though)
                corners = get_enu_corners(ogrid.nav_ogrid)
                index_limits = transform_enu_to_ogrid(corners, ogrid.nav_ogrid)
                index_limits = transform_between_ogrids(
                    index_limits, ogrid.nav_ogrid, global_ogrid)[:, :2]

                l_x_min = index_limits[0][0]
                l_x_max = index_limits[1][0]
                l_y_min = index_limits[0][1]
                l_y_max = index_limits[1][1]

                xs = np.sort([g_x_max, g_x_min, l_x_max, l_x_min])
                ys = np.sort([g_y_max, g_y_min, l_y_max, l_y_min])
                # These are indicies in cell units
                start_x, end_x = np.round(xs[1:3])  # Grabbing indices 1 and 2
                start_y, end_y = np.round(ys[1:3])

                # Should be indicies
                l_ogrid_start = transform_between_ogrids([start_x, start_y, 1],
                                                         global_ogrid,
                                                         ogrid.nav_ogrid)

                # fprint("ROI {},{} {},{}".format(start_x, start_y, end_x, end_y))
                index_width = l_ogrid_start[
                    0] + end_x - start_x  # I suspect rounding will be a source of error
                index_height = l_ogrid_start[1] + end_y - start_y
                # fprint("width: {}, height: {}".format(index_width, index_height))
                # fprint("Ogrid size: {}, {}".format(ogrid.nav_ogrid.info.height, ogrid.nav_ogrid.info.width))

                to_add = ogrid.np_map[l_ogrid_start[1]:index_height,
                                      l_ogrid_start[0]:index_width]

                # fprint("to_add shape: {}".format(to_add.shape))

                # Make sure the slicing doesn't produce an error
                end_x = start_x + to_add.shape[1]
                end_y = start_y + to_add.shape[0]

                try:
                    # fprint("np_grid shape: {}".format(np_grid[start_y:end_y, start_x:end_x].shape))
                    fprint("{}, {}".format(ogrid.topic, ogrid.replace))
                    if ogrid.replace:
                        np_grid[start_y:end_y, start_x:end_x] = to_add
                    else:
                        np_grid[start_y:end_y, start_x:end_x] += to_add
                except Exception as e:
                    fprint("Exception caught, probably a dimension mismatch:",
                           msg_color='red')
                    print e
                    fprint("w: {}, h: {}".format(global_ogrid.info.width,
                                                 global_ogrid.info.height),
                           msg_color='red')

        if self.draw_bounds and self.enforce_bounds:
            ogrid_bounds = transform_enu_to_ogrid(
                self.enu_bounds, global_ogrid).astype(np.int32)
            for i, point in enumerate(ogrid_bounds[:, :2]):
                if i == 0:
                    last_point = point
                    continue
                cv2.line(np_grid, tuple(point), tuple(last_point), 100, 3)
                last_point = point

        if self.plow:
            self.plow_snow(np_grid, global_ogrid)

    # Clip and flatten grid
        np_grid = np.clip(np_grid, self.ogrid_min_value, 100)
        global_ogrid.data = np_grid.flatten().astype(np.int8)

        self.publisher.publish(global_ogrid)

    def plow_snow(self, np_grid, ogrid):
        """Remove region around the boat so we never touch an occupied cell (making lqrrt not break
        if something touches us).

        """
        if self.odom is None:
            return np_grid

        p, q = self.odom

        yaw_rot = trns.euler_from_quaternion(q)[2]  # rads
        boat_width = params.boat_length + params.boat_buffer + self.plow_factor  # m
        boat_height = params.boat_width + params.boat_buffer + self.plow_factor  # m

        x, y, _ = transform_enu_to_ogrid([p[0], p[1], 1], ogrid)
        theta = yaw_rot
        w = boat_width / ogrid.info.resolution
        h = boat_height / ogrid.info.resolution

        box = cv2.cv.BoxPoints(((x, y), (w, h), np.degrees(theta)))
        box = np.int0(box)
        cv2.drawContours(np_grid, [box], 0, 0, -1)

        # Draw a "boat" in the ogrid
        boat_width = params.boat_length + params.boat_buffer
        boat_height = params.boat_width + params.boat_buffer

        x, y, _ = transform_enu_to_ogrid([p[0], p[1], 1], ogrid)
        w = boat_width / ogrid.info.resolution
        h = boat_height / ogrid.info.resolution

        box = cv2.cv.BoxPoints(((x, y), (w, h), np.degrees(theta)))
        box = np.int0(box)
        cv2.drawContours(np_grid, [box], 0, 40, -1)

        # fprint("Plowed snow!")
        return np_grid
Example #4
0
class JoyController:
    def __init__(self,options=None):     
        self.options = options
        self.warntime = time.time()
        self.USB = False
           
        self.joy_scale = [-1,1,-1,1,1] #RPYTY
        self.trim_roll = 0
        self.trim_pitch = 0
        self.max_angle = 30
        self.max_yawangle = 200
        
        
        self.max_thrust = 80.
        self.min_thrust = 25.
        self.max_thrust_raw = percentageToThrust(self.max_thrust)
        self.min_thrust_raw = percentageToThrust(self.min_thrust)       
        self.old_thurst_raw = 0
        
        self.slew_limit = 45
        self.slew_rate = 30
        self.slew_limit_raw = percentageToThrust(self.slew_limit)            
        self.slew_rate_raw = percentageToThrust(self.slew_rate)   
        
        self.dynserver = None
        self.prev_cmd = None
        self.curr_cmd = None
        self.yaw_joy = True
        
        self.sub_joy   = rospy.Subscriber("/joy", JoyMSG, self.new_joydata)
        self.sub_tf    = tf.TransformListener()         
        self.pub_tf    = tf.TransformBroadcaster()    
        self.pub_cfJoy = rospy.Publisher("/cfjoy", CFJoyMSG, queue_size=2)
        self.pub_PID = rospy.Publisher("/pid", PidMSG,queue_size=50)

        # PIDs for R,P,Y,THROTTLE
        self.PID = (PID(), PID(), PID(), PID()) # XY, Throttle, Yaw
        self.PIDActive = (True, True, True, True) # XY, YAw, Throttle on/off
        self.PIDDelay = 0.0
        self.PIDSource = Source.Qualisys
        self.PIDControl = True
        self.goal = [0.0, 0.0, 0.0, 0.0] # X Y Z YAW
        self.prev_msg = None
        self.PIDSetCurrentAuto = True

        self.thrust = (40., 90., 66.) # Min, Max, Base thrust
        self.distFunc = fLIN
        self.wandControl = False

        # Dynserver
        self.dynserver = DynamicReconfigureServer(CFJoyCFG, self.reconfigure)

    def setXYRGoal(self, msg):
        new_settings = {}
        new_settings["x"] = msg.pose.position.x
        new_settings["y"] = msg.pose.position.y
        new_settings["rz"] = degrees(tf.transformations.euler_from_quaternion(
            [msg.pose.orientation.x,
             msg.pose.orientation.y,
             msg.pose.orientation.z,
             msg.pose.orientation.w])[2])
        os.system("beep -f 6000 -l 35&")
        print new_settings
        self.dynserver.update_configuration(new_settings)
        
    def released(self, id):
        return self.prev_cmd.buttons[id] and not self.curr_cmd.buttons[id]
    
    def pressed(self, id):
        return not self.prev_cmd.buttons[id] and self.curr_cmd.buttons[id]
    
    def held(self, id):
        return self.prev_cmd.buttons[id] and self.curr_cmd.buttons[id]    
        
    #Needed so the class can change the dynserver values with button presses    
    def set_dynserver(self, server):
        self.dynserver = server
        
        
    def thurstToRaw(self, joy_thrust):
        # Deadman button or invalid thrust
        if not self.curr_cmd.buttons[Button.L1] or joy_thrust>1:
            return 0
        
        raw_thrust = 0
        if joy_thrust > 0.016:
            raw_thrust = self.min_thrust_raw + joy_thrust*(self.max_thrust_raw-self.min_thrust_raw)
              
        
        if self.slew_rate_raw>0 and self.slew_limit_raw > raw_thrust:
            if self.old_thurst_raw > self.slew_limit_raw:
                self.old_thurst_raw = self.slew_limit_raw
            if raw_thrust < (self.old_thurst_raw - (self.slew_rate_raw/100)):
                raw_thrust = self.old_thurst_raw - self.slew_rate_raw/100
            if joy_thrust < 0 or raw_thrust < self.min_thrust_raw:
                raw_thrust = 0
        self.old_thurst_raw = raw_thrust
        
        return raw_thrust        


    def new_joydata(self, joymsg):
        global Button
        global Axes
        #Should run at 100hz. Use launch files roslaunch crazyflie_ros joy.launch
        if self.prev_cmd == None:
            self.prev_cmd = joymsg
            print len(joymsg.axes)
            
            #USB mode
            if len(joymsg.axes) == 27:
                rospy.logwarn("Experimental: Using USB mode. Axes/Buttons may be different")
                Button = enum(L1=10,R1=11,Select=0,Start=3,L2=8,R2=9,Up=4,Right=5,Down=6,Left=7,Square=15,Cross=14,Triangle=12,Circle=13)
                Axes = enum(SLL=0,SLU=1,SRL=2,SRU=3,Up=4+4,Right=5+4,Down=6+4,Left=7+4,L2=4+8,R2=4+9,L1=4+10,R1=4+11,Triangle=4+12,Circle=4+13,Cross=4+14,Square=4+15,AccL=16,AccF=17,AccU=18,GyroY=19)
                self.USB = True
            return


        # bp = [i for i in range(len(joymsg.buttons)) if joymsg.buttons[i]]
        # ap = [(i,round(joymsg.axes[i],2)) for i in range(len(joymsg.axes)) if abs(joymsg.axes[i])>0.51 ]
        # if len(bp)>0:
        #     print "Buttons:", bp
        # if len(ap)>0:
        #     print "Axes   :", ap

        if self.USB:
            # USB buttons go from 1 to -1, Bluetooth go from 0 to -1, so normalise
            joymsg.axes =np.array(joymsg.axes, dtype=np.float32)
            joymsg.axes[8:19] -=1
            joymsg.axes[8:19] /=2
            # Strange bug: left button as axis doesnt work. So use button instead
            joymsg.axes[11] = -joymsg.buttons[Button.Left]/2.

        self.curr_cmd = joymsg
        hover = False

        x = 0
        y = 0
        z = 0
        r = 0
        r2 = 0
        # Get stick positions [-1 1]
        if self.curr_cmd.buttons[Button.L1]:  # Deadman pressed (=allow flight)
            x = self.joy_scale[0] * self.curr_cmd.axes[Axes.SLL] # Roll
            y = self.joy_scale[1] * self.curr_cmd.axes[Axes.SLU] # Pitch
            r = self.joy_scale[2] * self.curr_cmd.axes[Axes.SRL] # Yaw
            z = self.joy_scale[3] * self.curr_cmd.axes[Axes.SRU] # Thrust
            r2 = self.joy_scale[4] * (self.curr_cmd.axes[Axes.L2] - self.curr_cmd.axes[Axes.R2])
            hover = self.curr_cmd.axes[Axes.L1]<-0.75
        
        roll = x * self.max_angle
        pitch = y * self.max_angle        
        yaw = 0
        
        
        #TODO use dyn reconf to decide which to use
        if r2!=0:
            yaw = r2 * self.max_yawangle
        else:
            if self.yaw_joy:
                # Deadzone     
                if r < -0.2 or r > 0.2:
                    if r < 0:
                        yaw = (r + 0.2) * self.max_yawangle * 1.25
                    else:
                        yaw = (r - 0.2) * self.max_yawangle * 1.25
                
                
        if hover:
            thrust = int(round(deadband(z,0.2)*32767 + 32767)) #Convert to uint16
        else:    
            thrust = self.thurstToRaw(z)  
            
        trimmed_roll = roll + self.trim_roll
        trimmed_pitch = pitch + self.trim_pitch
        
            
        # Control trim manually
        new_settings = {}
        if joymsg.header.seq%10 == 0:

            if self.curr_cmd.buttons[Button.Left]:
                new_settings["trim_roll"] = max(self.trim_roll + self.curr_cmd.axes[Axes.Left], -20)
            if self.curr_cmd.buttons[Button.Right]:
                new_settings["trim_roll"] =  min(self.trim_roll - self.curr_cmd.axes[Axes.Right], 20)
            if self.curr_cmd.buttons[Button.Down]:
                new_settings["trim_pitch"] = max(self.trim_pitch + self.curr_cmd.axes[Axes.Down], -20)
            if self.curr_cmd.buttons[Button.Up]:
                new_settings["trim_pitch"] = min(self.trim_pitch - self.curr_cmd.axes[Axes.Up], 20)




        msg = CFJoyMSG()
        msg.header.stamp = rospy.Time.now()
        msg.roll = trimmed_roll
        msg.pitch = trimmed_pitch
        msg.yaw = yaw
        msg.thrust = thrust
        msg.hover = hover
        #msg.calib = self.pressed(Button.Triangle)



        # TODO: rotation is wrong! Cannot simply decompose x,y into r,p.
        # TODO: roll, pitch should be dependent from the distance and the angle.
        if self.PIDControl: # and hover:
            try:
                rtime = rospy.Time.now()

                # Update goal position
                if hover and joymsg.header.seq%10 == 0:
                    if self.PIDActive[0]:
                        if abs(x)>0.016:
                            new_settings["x"] = self.goal[0]-roll/70.
                        if abs(y)>0.016:
                            new_settings["y"] = self.goal[1]-pitch/70.

                    if self.PIDActive[1]:
                        if abs(z)>0.016:
                            new_settings["z"] = self.goal[2]+z/5

                    if self.PIDActive[2]:
                        if abs(yaw)>0:
                           new_settings["rz"] = self.goal[3]-yaw/self.max_yawangle*20

                    if  new_settings.has_key("x") or new_settings.has_key("y") or new_settings.has_key("z") or new_settings.has_key("rz"):
                        os.system("beep -f 6000 -l 15&")


                # Get error from current position to goal if possible
                [px,py,alt,rz] = self.getErrorToGoal()

                # Starting control mode
                if hover and not self.prev_msg.hover:
                    # Starting Goal should be from current position
                    if self.PIDSetCurrentAuto:
                        new_settings["SetCurrent"] = True

                    # Reset PID controllers
                    for p in range(4):
                        self.PID[p].reset(rtime)

                    # Set yaw difference as I part
                    #self.PID[3].error_sum = 0






                if hover:
                    msg.hover = False #TODO should be an option

                    pidmsg = PidMSG()
                    pidmsg.header.stamp = rtime


                    # Get PID control

                    #XY control depends on the asin of the distance
                    #cr, crp, cri, crd = self.PID[0].get_command(-py,rtime,f=lambda d: degrees(asin(d))/90.*self.max_angle)
                    #cp, cpp, cpi, cpd  = self.PID[1].get_command(px,rtime,f=lambda d: degrees(asin(d))/90.*self.max_angle)



                    #cr, crp, cri, crd = self.PID[0].get_command(-py,rtime)
                    #cp, cpp, cpi, cpd  = self.PID[1].get_command(px,rtime)


                    # Distance to angle (X direction = pitch, Y direction = roll)
                    cr, crp, cri, crd = self.PID[0].get_command(-py,rtime,f=self.distFunc )
                    cp, cpp, cpi, cpd  = self.PID[1].get_command(px,rtime,f=self.distFunc )

                    # THRUST
                    ct, ctp, cti, ctd  = self.PID[2].get_command(alt,rtime)
                    # YAW VEL
                    cy, cyp, cyi, cyd  = self.PID[3].get_command(-rz,rtime)#,d=rz,i=copysign(1,-rz)) #TODO: normalise rotation



                    # ROll/Pitch
                    if self.PIDActive[0]:
                        msg.roll = cr + self.trim_roll
                        msg.pitch = cp + self.trim_pitch

                        pidmsg.ypid = [cr, crp, cri, -crd, -py]
                        pidmsg.xpid = [cp, cpp, cpi, -cpd, px]



                    # THRUST
                    if self.PIDActive[1]:

                        # Add base thrust
                        ct += self.thrust[2]

                        # Add roll compensation
                        # TODO: done on the flie itself now @ 250hz


                        # Bound
                        ct = max(self.thrust[0],ct)
                        ct = min(self.thrust[1],ct)

                        # ct in thrust percent
                        msg.thrust = percentageToThrust(ct)
                        pidmsg.zpid= [ct, ctp, cti, ctd, alt]


                    # YAW
                    if self.PIDActive[2]:
                        msg.yaw = cy
                        pidmsg.rzpid= [cy, cyp, cyi, cyd, -rz]



                    self.pub_PID.publish(pidmsg)



                if self.pressed(Button.Cross):
                    new_settings["SetCurrent"] = True


                q = tf.transformations.quaternion_from_euler(radians(msg.roll-self.trim_roll), radians(msg.pitch - self.trim_pitch), radians(-msg.yaw))
                self.pub_tf.sendTransform((0,0,0), q,  rospy.Time.now(), "/cmd", "/cf_gt2d")





            except (tf.LookupException, tf.ConnectivityException, tf.ExtrapolationException):

                if time.time() - self.warntime>2:
                    rospy.logwarn('Could not look up cf_gt2d -> goal')
                    self.warntime = time.time()


        global tid
        if self.pressed(Button.Circle):

            tid = (tid +1)%len(trajectory)
            new_settings["x"], new_settings["y"], new_settings["z"], new_settings["rz"] = trajectory[tid]

        # previous
        if self.pressed(Button.Triangle):
            tid = (tid -1)%len(trajectory)
            new_settings["x"], new_settings["y"], new_settings["z"], new_settings["rz"] = trajectory[tid]


        # Set trim to current input
        if self.released(Button.R1):
            new_settings["trim_roll"] = min(20, max(msg.roll, -20))
            new_settings["trim_pitch"] = min(20, max(msg.pitch, -20))
            rospy.loginfo("Trim updated Roll/Pitch: %r/%r", round(new_settings["trim_roll"],2), round(new_settings["trim_pitch"],2))

        # Reset Trim
        if self.released(Button.Square):
            new_settings["trim_roll"] = 0.0
            new_settings["trim_pitch"] = 0.0
            rospy.loginfo("Pitch reset to 0/0")


        if new_settings != {} and self.dynserver is not None:
            if self.USB:
                new_settings = numpy2python(new_settings)
            self.dynserver.update_configuration(new_settings)
        
        
        # Cache prev joy command

        self.pub_cfJoy.publish(msg)


        self.prev_cmd = self.curr_cmd
        self.prev_msg = msg
        self.prev_msg.hover = hover


    def lookupTransformInWorld(self, frame='/cf_gt', forceRealtime = False, poseNoiseMeters=0.0 ):
        now = rospy.Time(0)
        if self.PIDDelay > 0.00001 and not forceRealtime:
            #rospy.logwarn('Delay in TF Pose: %5.3s', self.PIDDelay)
            now = rospy.Time.now()-rospy.Duration(self.PIDDelay)
        (trans,rot) = self.sub_tf.lookupTransform('/world', frame, now)

        if poseNoiseMeters>0:
            trans = [trans[0]+random.normalvariate(0,poseNoiseMeters), trans[1]+random.normalvariate(0,poseNoiseMeters), trans[2]+random.normalvariate(0,poseNoiseMeters)]
        return (trans,rot)


    def getErrorToGoal(self):
        # Look up flie position, convert to 2d, publish, return error

        if self.wandControl:
            #update goal using wand
            try:
                (t,r) = self.lookupTransformInWorld(frame='/wand', forceRealtime=True)
                e = tf.transformations.euler_from_quaternion(r)
                self.goal= [t[0], t[1], t[2], degrees(e[2]-e[0])]
            except (tf.LookupException, tf.ConnectivityException, tf.ExtrapolationException):
                rospy.loginfo('Could not find wand')


        # Publish GOAL from world frame
        q = tf.transformations.quaternion_from_euler(0, 0, radians(self.goal[3]))
        self.pub_tf.sendTransform((self.goal[0],self.goal[1],self.goal[2]), q, rospy.Time.now(), "/goal", "/world")


        # Look up 6D flie pose, publish 3d+yaw pose
        (trans,rot) = self.lookupTransformInWorld(frame='/cf_gt', poseNoiseMeters=0.0)
        euler = tf.transformations.euler_from_quaternion(rot)
        q = tf.transformations.quaternion_from_euler(0, 0, euler[2])
        self.pub_tf.sendTransform(trans, q, rospy.Time.now(), "/cf_gt2d", "/world")

        # Look up error goal->cf
        (trans,rot) = self.sub_tf.lookupTransform('/cf_gt2d', '/goal', rospy.Time(0))
        #self.pub_tf.sendTransform(trans, rot, rospy.Time.now(), "/goalCF", "/cf_gt2d")
        euler = tf.transformations.euler_from_quaternion(rot)
        return trans[0], trans[1], trans[2], degrees(euler[2])




    def setGoalFromCurrent(self,config={}):
        try:
            (trans,rot) = self.lookupTransformInWorld(frame='/cf_gt', forceRealtime=True)
            euler = tf.transformations.euler_from_quaternion(rot)
            config['x'],config['y'],config['z'],config['rz']= trans[0], trans[1], trans[2], degrees(euler[2])
            rospy.loginfo('Updated goal state: [%.2f %.2f %.2f %.1f]', trans[0], trans[1], trans[2], degrees(euler[2]))
            self.goal = [config['x'],config['y'],config['z'],config['rz']]
        except (tf.LookupException, tf.ConnectivityException, tf.ExtrapolationException):
            rospy.logwarn('Could not look up transform world -> cf_gt')
        return config
    
    def reconfigure(self, config, level):
        # Manual control stuff
        self.trim_roll = config["trim_roll"]
        self.trim_pitch = config["trim_pitch"]
        self.max_angle = config["max_angle"]
        self.max_yawangle = config["max_yawangle"]
        self.max_thrust = config["max_thrust"]
        self.min_thrust = config["min_thrust"]
        self.slew_limit = config["slew_limit"]
        self.slew_rate = config["slew_rate"]
        self.max_thrust_raw = percentageToThrust(self.max_thrust)
        self.min_thrust_raw = percentageToThrust(self.min_thrust)
        self.slew_limit_raw = percentageToThrust(self.slew_limit)
        self.slew_rate_raw = percentageToThrust(self.slew_rate) 
        self.yaw_joy =  config["yaw_joy"]


        #PID control stuff
        if config["PIDPreset"]>0:
            if config["PIDPreset"]==1:
                # Aggressive
                config["DistFunc"] = 1 # ATAN
                config["Pxy"] = 1.0
                config["Ixy"] = 0.0
                config["Dxy"] = 0.2
                config["Pyaw"] = 8
                config["Iyaw"] = 0.0
                config["Dyaw"] = 0.0
                config["Pz"] = 20
                config["Iz"] = 5
                config["Dz"] = 8
                config["RP_maxAngle"] = 35
                config["Y_maxVel"] = 250
                config["z_minThrust"] = 50
                config["z_maxThrust"] = 100
                config["z_baseThrust"] = 73
                config["Response"] = 0.3

            if config["PIDPreset"]==2:
                # Passive
                config["DistFunc"] = 2 # ASIN
                config["Pxy"] = 2.0
                config["Ixy"] = 0.0
                config["Dxy"] = 0.4
                config["Pyaw"] = 2.0
                config["Iyaw"] = 0.0
                config["Dyaw"] = 0.0
                config["Pz"] = 20
                config["Iz"] = 5
                config["Dz"] = 8
                config["RP_maxAngle"] = 7
                config["Y_maxVel"] = 80
                config["z_minThrust"] = 50
                config["z_maxThrust"] = 90
                config["z_baseThrust"] = 73
                config["Response"] = 0.175

            if config["PIDPreset"]==3:
                # Linear
                config["DistFunc"] = 0
                config["Pxy"] = 17
                config["Ixy"] = 0.0
                config["Dxy"] = 5
                config["Pyaw"] = 3.3
                config["Iyaw"] = 0.0
                config["Dyaw"] = 0.0
                config["Pz"] = 20
                config["Iz"] = 5
                config["Dz"] = 8
                config["RP_maxAngle"] = 25
                config["Y_maxVel"] = 300
                config["z_minThrust"] = 50
                config["z_maxThrust"] = 90
                config["z_baseThrust"] = 73
                config["Response"] = 1


            rospy.loginfo("Preset Loaded")
            config["PIDPreset"] = 0




        # Settings
        self.PIDDelay = config["Delay"]
        self.PIDSource = config["Source"]
        self.PIDControl = config["Control"]
        self.max_anglePID = config['RP_maxAngle']

        # Gains
        self.PID[0].set_gains(config["Pxy"], config["Ixy"], config["Dxy"] )
        self.PID[1].set_gains(config["Pxy"], config["Ixy"], config["Dxy"] )
        self.PID[2].set_gains(config["Pz"],  config["Iz"], config["Dz"] )
        self.PID[3].set_gains(config["Pyaw"], config["Iyaw"], config["Dyaw"] )

        # Goal
        self.wandControl = config['WandControl']
        if config['SetCurrent']:
            config['SetCurrent'] = False
            config = self.setGoalFromCurrent(config)

        if config['Set'] or config['LiveUpdate']:
            config['Set'] = False
            if not self.wandControl:
                self.goal = [config['x'],config['y'],config['z'],config['rz']]

        # Limits
        #self.limits = (config['RP_maxAngle'], config['Y_maxVel'], config['z_maxAcc'])
        self.PID[0].set_limits(config['RP_maxAngle'])
        self.PID[1].set_limits(config['RP_maxAngle'])
        self.PID[2].set_limits(100)
        self.PID[3].set_limits(config['Y_maxVel'])

        self.thrust = (config['z_minThrust'],config['z_maxThrust'], config['z_baseThrust'] )


        # On/OFF
        self.PIDActive = (config['xyControl'],config['thrustControl'],config['yawControl'])
        self.PIDSetCurrentAuto = config['SetCurrentAuto']

        self.distFunc = [lambda x: fLIN(x,config["Response"]),
                         lambda x: fATAN(x,config["Response"]),
                         lambda x: fASIN(x,config["Response"])][config['DistFunc']]



        return config 
Example #5
0
class Driver:
    def __init__(self, options):
        self.options = options

        self.crazyflie = Crazyflie()
        self.battery_monitor = BatteryMonitor()
        cflib.crtp.init_drivers()

        self.zspeed = zSpeed()

        # Keep track of this to know if the user changed imu mode to/from imu6/9
        self.preMagcalib = None

        #self.csvwriter = csv.writer(open('baro.csv', 'wb'), delimiter=',',quotechar='"', quoting=csv.QUOTE_MINIMAL)
        #self.baro_start_time = None

        # Some shortcuts
        self.HZ100 = 10
        self.HZ10 = 100
        self.HZ1 = 1000
        self.HZ500 = 2
        self.HZ250 = 4
        self.HZ125 = 8
        self.HZ50 = 20

        # Callbacks the CF driver calls
        self.crazyflie.connectSetupFinished.add_callback(
            self.connectSetupFinishedCB)
        self.crazyflie.connectionFailed.add_callback(self.connectionFailedCB)
        self.crazyflie.connectionInitiated.add_callback(
            self.connectionInitiatedCB)
        self.crazyflie.disconnected.add_callback(self.disconnectedCB)
        self.crazyflie.connectionLost.add_callback(self.connectionLostCB)
        self.crazyflie.linkQuality.add_callback(self.linkQualityCB)

        # Advertise publishers
        self.pub_acc = rospy.Publisher("/cf/acc", accMSG)
        self.pub_mag = rospy.Publisher("/cf/mag", magMSG)
        self.pub_gyro = rospy.Publisher("/cf/gyro", gyroMSG)
        self.pub_baro = rospy.Publisher("/cf/baro", baroMSG)
        self.pub_motor = rospy.Publisher("/cf/motor", motorMSG)
        self.pub_hover = rospy.Publisher("/cf/hover", hoverMSG)
        self.pub_attitude = rospy.Publisher("/cf/attitude", attitudeMSG)
        self.pub_bat = rospy.Publisher("/cf/bat", batMSG)
        self.pub_zpid = rospy.Publisher("/cf/zpid", zpidMSG)
        self.pub_tf = tf.TransformBroadcaster()

        # Subscribers
        self.sub_tf = tf.TransformListener()
        self.sub_joy = rospy.Subscriber("/cfjoy", joyMSG, self.new_joydata)
        self.sub_magCalib = rospy.Subscriber("/magCalib", magCalibMSG,
                                             self.new_magCalib)

        # Keep track of link quality to send out with battery status
        self.link = 0

        # Loggers
        self.logMotor = None
        self.logBaro = None
        self.logMag = None
        self.logGyro = None
        self.logAttitude = None
        self.logAcc = None
        self.logBat = None
        self.logHover = None
        self.logZPid = None
        self.logGravOffset = None  #only here till it works

        # keep tracks if loggers are running
        self.acc_monitor = False
        self.gyro_monitor = False
        self.baro_monitor = False
        self.mag_monitor = False
        self.bat_monitor = False
        self.motor_monitor = False
        self.attitude_monitor = False
        self.hover_monitor = False
        self.zpid_monitor = False

        # CF params we wanna be able to change
        self.cf_param_groups = ["hover", "sensorfusion6", "magCalib"]
        self.cf_params_cache = {}

        # Dynserver
        self.dynserver = DynamicReconfigureServer(driverCFG, self.reconfigure)

        # Finished set up. Connect to flie
        self.connect(self.options)

    def new_magCalib(self, msg):
        """ Receive magnetometer calibration results. Send to flie via dyn conf"""
        new_settings = {}
        #TODO
        new_settings["magCalib_off_x"] = msg.offset[0]
        new_settings["magCalib_off_y"] = msg.offset[1]
        new_settings["magCalib_off_z"] = msg.offset[2]
        new_settings["magCalib_thrust_x"] = msg.thrust_comp[0]
        new_settings["magCalib_thrust_y"] = msg.thrust_comp[1]
        new_settings["magCalib_thrust_z"] = msg.thrust_comp[2]
        new_settings["magCalib_scale_x"] = msg.scale[0]
        new_settings["magCalib_scale_y"] = msg.scale[1]
        new_settings["magCalib_scale_z"] = msg.scale[2]
        self.dynserver.update_configuration(new_settings)
        rospy.loginfo("Updated magnetometer calibration data")

    def connect(self, options):
        """ Look for crazyflie every 2s and connect to the specified one if found"""
        rospy.loginfo("Waiting for crazyflie...")
        while not rospy.is_shutdown():
            interfaces = cflib.crtp.scan_interfaces()
            if len(interfaces) > 1:
                radio = None
                rospy.loginfo("Found: ")
                for i in interfaces:
                    rospy.loginfo("--> %s [%s]", i[0], i[1])
                    if i[0].startswith(options.uri):
                        radio = i[0]
                if radio != None:
                    self.crazyflie.open_link(radio)
                    break
            else:
                rospy.sleep(2)

    def new_joydata(self, joymsg):
        """ Joydata arrived. Should happen at 100hz."""

        # Parse joydata and extract commands
        roll = joymsg.roll
        pitch = joymsg.pitch
        yawrate = joymsg.yaw
        thrust = joymsg.thrust
        hover = joymsg.hover
        set_hover = joymsg.hover_set
        hover_change = joymsg.hover_change

        # Send to flie
        self.send_control(
            (roll, pitch, yawrate, thrust, hover, set_hover, hover_change))

    def reconfigure(self, config, level):
        """ Fill in local variables with values received from dynamic reconfigure clients (typically the GUI)."""
        config = self.zspeed.reconfigure(config)

        self.deadband = config["deadband"]

        # On / off logging
        if self.zpid_monitor != config["read_zpid"]:
            self.zpid_monitor = config["read_zpid"]
            if self.logZPid != None:
                if self.zpid_monitor:
                    self.logZPid.start()
                    rospy.loginfo("zPID Logging started")
                else:
                    self.logZPid.stop()
                    rospy.loginfo("zPID Logging stopped")

        if self.gyro_monitor != config["read_gyro"]:
            self.gyro_monitor = config["read_gyro"]
            if self.logGyro != None:
                if self.gyro_monitor:
                    self.logGyro.start()
                    rospy.loginfo("Gyro Logging started")
                else:
                    self.logGyro.stop()
                    rospy.loginfo("Gyro Logging stopped")

        if self.hover_monitor != config["read_hover"]:
            self.hover_monitor = config["read_hover"]
            if self.logHover != None:
                if self.hover_monitor:
                    self.logHover.start()
                    rospy.loginfo("Hover Logging started")
                else:
                    self.logHover.stop()
                    rospy.loginfo("Hover Logging stopped")

        if self.acc_monitor != config["read_acc"]:
            self.acc_monitor = config["read_acc"]
            if self.logGyro != None:
                if self.acc_monitor:
                    self.logAcc.start()
                    rospy.loginfo("Acc Logging started")
                else:
                    self.logAcc.stop()
                    rospy.loginfo("Acc Logging stopped")

        if self.baro_monitor != config["read_baro"]:
            self.baro_monitor = config["read_baro"]
            if self.logBaro != None:
                if self.baro_monitor:
                    self.logBaro.start()
                    rospy.loginfo("Baro Logging started")
                else:
                    self.logBaro.stop()
                    rospy.loginfo("Baro Logging stopped")

        if self.mag_monitor != config["read_mag"]:
            self.mag_monitor = config["read_mag"]
            if self.logMag != None:
                if self.mag_monitor:
                    self.logMag.start()
                    rospy.loginfo("Mag Logging started")
                else:
                    self.logMag.stop()
                    rospy.loginfo("Mag Logging stopped")

        if self.bat_monitor != config["read_bat"]:
            self.bat_monitor = config["read_bat"]
            if self.logBat != None:
                if self.bat_monitor:
                    self.logBat.start()
                    rospy.loginfo("Battery/Link Logging started")
                else:
                    self.logBat.stop()
                    rospy.loginfo("Battery/Link Logging stopped")

        if self.attitude_monitor != config["read_attitude"]:
            self.attitude_monitor = config["read_attitude"]
            if self.logAttitude != None:
                if self.attitude_monitor:
                    self.logAttitude.start()
                    rospy.loginfo("Attitude Logging started")
                else:
                    self.logAttitude.stop()
                    rospy.loginfo("Attitude Logging stopped")

        if self.motor_monitor != config["read_motor"]:
            self.motor_monitor = config["read_motor"]
            if self.logMotor != None:
                if self.motor_monitor:
                    self.logMotor.start()
                    rospy.loginfo("Motor Logging started")
                else:
                    self.logMotor.stop()
                    rospy.loginfo("Motor Logging stopped")

        # SET CRAZYFLIE PARAMS

        for key, value in config.iteritems():
            # Skip this key (whats its purpose??)
            if key == "groups":
                continue

            # Continue if variable exists and is correct
            if self.cf_params_cache.has_key(key):
                if config[key] == self.cf_params_cache[key]:
                    # Nothing changed
                    if not key.startswith("magCalib") or (
                            self.preMagcalib
                            or not config["sensorfusion6_magImu"]):
                        continue

            # Doesnt exist or has changed

            # Split into group and name
            s = key.split("_", 1)

            # Make sure it is a group name pair
            if len(s) == 2:
                # make sure group is valid
                if s[0] in self.cf_param_groups:
                    # update cache, send variable to flie
                    self.cf_params_cache[key] = value
                    self.send_param(s[0] + "." + s[1], value)
                    print "updated: ", s[0] + "." + s[1], "->", value
                    rospy.sleep(0.1)

        self.preMagcalib = config["sensorfusion6_magImu"]
        return config

    def send_param(self, key, value):
        self.crazyflie.param.set_value(key, str(value))

    def restart(self):
        rospy.loginfo("Restarting Driver")
        rospy.sleep(1)
        self.connect(self.options)

    def logErrorCB(self, errmsg):
        rospy.logerr("Log error: %s", errmsg)

    def send_control(self, cmd):
        """ Roll, pitch in deg, yaw in deg/s, thrust in 10000-60000, hover bool, set_hover bool, hover chance float -1 to +1 """
        roll, pitch, yawrate, thrust, hover, set_hover, hover_change = cmd
        if self.crazyflie.state == CFState.CONNECTED:
            self.crazyflie.commander.send_setpoint(roll, pitch, yawrate,
                                                   thrust, hover, set_hover,
                                                   hover_change)

    def setup_log(self):
        """ Console callbacks """
        self.crazyflie.console.receivedChar.add_callback(self.consoleCB)

        rospy.sleep(0.25)
        """ ATTITUDE LOGGING @ 100hz """
        logconf = LogConfig("attitude", self.HZ100 * 2)  #ms
        logconf.addVariable(LogVariable("attitude.q0", "float"))
        logconf.addVariable(LogVariable("attitude.q1", "float"))
        logconf.addVariable(LogVariable("attitude.q2", "float"))
        logconf.addVariable(LogVariable("attitude.q3", "float"))
        self.logAttitude = self.crazyflie.log.create_log_packet(logconf)
        if (self.logAttitude is not None):
            self.logAttitude.dataReceived.add_callback(
                self.logCallbackAttitude)
            self.logAttitude.error.add_callback(self.logErrorCB)
            if self.attitude_monitor:
                self.logAttitude.start()
                rospy.loginfo("Attitude Logging started")
        else:
            rospy.logwarn("Could not setup Attitude logging!")

        rospy.sleep(0.25)
        """ ZPID LOGGING @ 100hz """
        logconf = LogConfig("zpid", self.HZ100)  #ms
        logconf.addVariable(LogVariable("zpid.p", "float"))
        logconf.addVariable(LogVariable("zpid.i", "float"))
        logconf.addVariable(LogVariable("zpid.d", "float"))
        logconf.addVariable(LogVariable("zpid.pid", "float"))
        self.logZPid = self.crazyflie.log.create_log_packet(logconf)
        if (self.logZPid is not None):
            self.logZPid.dataReceived.add_callback(self.logCallbackZPid)
            self.logZPid.error.add_callback(self.logErrorCB)
            if self.zpid_monitor:
                self.logZPid.start()
                rospy.loginfo("ZPID Logging started")
        else:
            rospy.logwarn("Could not setup ZPID logging!")
        rospy.sleep(0.25)
        """ HOVER LOGGING @ 100hz """
        logconf = LogConfig("hover", self.HZ100)  #ms
        logconf.addVariable(LogVariable("hover.err", "float"))
        logconf.addVariable(LogVariable("hover.target", "float"))
        logconf.addVariable(LogVariable("hover.zSpeed", "float"))
        logconf.addVariable(LogVariable("hover.zBias", "float"))
        logconf.addVariable(LogVariable("hover.acc_vspeed", "float"))
        logconf.addVariable(LogVariable("hover.asl_vspeed", "float"))

        self.logHover = self.crazyflie.log.create_log_packet(logconf)
        if (self.logHover is not None):
            self.logHover.dataReceived.add_callback(self.logCallbackHover)
            self.logHover.error.add_callback(self.logErrorCB)
            if self.hover_monitor:
                self.logHover.start()
                rospy.loginfo("Hover Logging started")
        else:
            rospy.logwarn("Could not setup Hover logging!")

        rospy.sleep(0.25)
        """ GYROMETER LOGGING @ 100hz """
        logconf = LogConfig("LoggingGyro", self.HZ100)  #ms
        logconf.addVariable(LogVariable("gyro.x", "float"))
        logconf.addVariable(LogVariable("gyro.y", "float"))
        logconf.addVariable(LogVariable("gyro.z", "float"))
        self.logGyro = self.crazyflie.log.create_log_packet(logconf)
        if (self.logGyro is not None):
            self.logGyro.dataReceived.add_callback(self.logCallbackGyro)
            self.logGyro.error.add_callback(self.logErrorCB)
            if self.gyro_monitor:
                self.logGyro.start()
                rospy.loginfo("Gyro Logging started")
        else:
            rospy.logwarn("Could not setup Gyro logging!")

        rospy.sleep(0.25)
        """ ACCELEROMETER LOGGING @ 100hz """
        logconf = LogConfig("LoggingAcc", self.HZ100)  #ms
        logconf.addVariable(LogVariable("acc.x", "float"))
        logconf.addVariable(LogVariable("acc.y", "float"))
        logconf.addVariable(LogVariable("acc.z", "float"))
        logconf.addVariable(LogVariable("acc.xw", "float"))
        logconf.addVariable(LogVariable("acc.yw", "float"))
        logconf.addVariable(LogVariable("acc.zw", "float"))
        self.logAcc = self.crazyflie.log.create_log_packet(logconf)
        if (self.logAcc is not None):
            self.logAcc.dataReceived.add_callback(self.logCallbackAcc)
            self.logAcc.error.add_callback(self.logErrorCB)
            if self.acc_monitor:
                self.logAcc.start()
                rospy.loginfo("Acc Logging started")
        else:
            rospy.logwarn("Could not setup Acc logging!")

        rospy.sleep(0.25)
        """ MAGNETOMETER LOGGING @ 100hz """
        logconf = LogConfig("LoggingMag", self.HZ100)  #ms
        logconf.addVariable(LogVariable("mag.x", "float"))
        logconf.addVariable(LogVariable("mag.y", "float"))
        logconf.addVariable(LogVariable("mag.z", "float"))
        logconf.addVariable(LogVariable("mag.x_raw", "float"))
        logconf.addVariable(LogVariable("mag.y_raw", "float"))
        logconf.addVariable(LogVariable("mag.z_raw", "float"))

        self.logMag = self.crazyflie.log.create_log_packet(logconf)
        if (self.logMag is not None):
            self.logMag.dataReceived.add_callback(self.logCallbackMag)
            self.logMag.error.add_callback(self.logErrorCB)
            if self.mag_monitor:
                self.logMag.start()
                rospy.loginfo("Mag Logging started")
        else:
            rospy.logwarn("Could not setup Mag logging!")

        rospy.sleep(0.25)
        """ BAROMETER LOGGING @ 100hz """
        logconf = LogConfig("LoggingBaro", self.HZ100)  #ms
        logconf.addVariable(LogVariable("baro.aslRaw", "float"))
        logconf.addVariable(LogVariable("baro.asl", "float"))
        logconf.addVariable(LogVariable("baro.temp", "float"))
        logconf.addVariable(LogVariable("baro.pressure", "float"))
        logconf.addVariable(LogVariable("baro.aslLong", "float"))
        self.logBaro = self.crazyflie.log.create_log_packet(logconf)
        if (self.logBaro is not None):
            self.logBaro.dataReceived.add_callback(self.logCallbackBaro)
            self.logBaro.error.add_callback(self.logErrorCB)
            if self.baro_monitor:
                self.logBaro.start()
                rospy.loginfo("Baro Logging started")
        else:
            rospy.logwarn("Could not setup Baro logging!")

        rospy.sleep(0.25)
        """ BATTERY/LINK LOGGING @ 10hz """
        logconf = LogConfig("LoggingBat", self.HZ10)  #ms
        logconf.addVariable(LogVariable("pm.vbat", "float"))
        logconf.addVariable(LogVariable("pm.state", "uint8_t"))
        logconf.addVariable(LogVariable("pm.state_charge", "uint8_t"))
        self.logBat = self.crazyflie.log.create_log_packet(logconf)
        if (self.logBat is not None):
            self.logBat.dataReceived.add_callback(self.logCallbackBat)
            self.logBat.error.add_callback(self.logErrorCB)
            if self.bat_monitor:
                self.logBat.start()
                rospy.loginfo("Bat Logging started")
        else:
            rospy.logwarn("Could not setup Battery/Link logging!")

        rospy.sleep(0.25)

        #TODO motor logging not working
        """ MOTOR LOGGING @ 100hz"""
        logconf = LogConfig("LoggingMotor", self.HZ100)  #ms
        logconf.addVariable(LogVariable("motor.m1", "uint32_t"))
        logconf.addVariable(LogVariable("motor.m2", "uint32_t"))
        logconf.addVariable(LogVariable("motor.m3", "uint32_t"))
        logconf.addVariable(LogVariable("motor.m4", "uint32_t"))
        logconf.addVariable(LogVariable("motor.thrust", "uint16_t"))
        logconf.addVariable(LogVariable("motor.vLong", "float"))  #TODO move
        self.logMotor = self.crazyflie.log.create_log_packet(logconf)
        if (self.logMotor is not None):
            self.logMotor.dataReceived.add_callback(self.logCallbackMotor)
            self.logMotor.error.add_callback(self.logErrorCB)
            if self.motor_monitor:
                self.logMotor.start()
                rospy.loginfo("Motor Logging started")
        else:
            rospy.logwarn("Could not setup Motor logging!")

    def logCallbackAcc(self, data):
        """Accelerometer axis data in mG --> G"""
        msg = accMSG()
        msg.header.stamp = rospy.Time.now()
        msg.acc[0] = data["acc.x"]
        msg.acc[1] = data["acc.y"]
        msg.acc[2] = data["acc.z"]
        msg.acc_w[0] = data["acc.xw"]
        msg.acc_w[1] = data["acc.yw"]
        msg.acc_w[2] = data["acc.zw"]

        self.zspeed.add(msg.acc_w[2])

        self.pub_acc.publish(msg)

        self.pub_tf.sendTransform(msg.acc_w, (0, 0, 0, 1), rospy.Time.now(),
                                  "/acc_w", "/cf_q")

        self.pub_tf.sendTransform(msg.acc, (0, 0, 0, 1), rospy.Time.now(),
                                  "/acc", "/cf_q")

    def logCallbackMag(self, data):
        """TODO UNITS"""
        msg = magMSG()
        msg.header.stamp = rospy.Time.now()
        msg.mag[0] = data["mag.x"]
        msg.mag[1] = data["mag.y"]
        msg.mag[2] = data["mag.z"]
        msg.magRaw[0] = data["mag.x_raw"]
        msg.magRaw[1] = data["mag.y_raw"]
        msg.magRaw[2] = data["mag.z_raw"]
        self.pub_mag.publish(msg)

        if np.linalg.norm(msg.magRaw) > 1:
            self.pub_tf.sendTransform(msg.mag / np.linalg.norm(msg.magRaw),
                                      (0, 0, 0, 1), rospy.Time.now(),
                                      "/mag_raw", "/cf_q")
        if np.linalg.norm(msg.mag) > 1:
            self.pub_tf.sendTransform(msg.mag / np.linalg.norm(msg.mag),
                                      (0, 0, 0, 1), rospy.Time.now(),
                                      "/mag_calib", "/cf_q")

    def logCallbackGyro(self, data):
        """Gyro axis data in deg/s -> rad/s"""
        msg = gyroMSG()
        msg.header.stamp = rospy.Time.now()
        msg.gyro[0] = radians(data["gyro.x"])
        msg.gyro[1] = radians(data["gyro.y"])
        msg.gyro[2] = radians(data["gyro.z"])
        self.pub_gyro.publish(msg)

    def logCallbackHover(self, data):
        """Output data regarding hovering"""
        msg = hoverMSG()
        msg.header.stamp = rospy.Time.now()

        msg.err = deadband(data["hover.err"], self.deadband)
        msg.zSpeed = data["hover.zSpeed"]
        msg.acc_vspeed = data[
            "hover.acc_vspeed"]  # * self.cf_params_cache["hover_acc_vspeedFac"]
        msg.asl_vspeed = data[
            "hover.asl_vspeed"]  # * self.cf_params_cache["hover_asl_vspeedFac"]
        msg.target = data["hover.target"]
        msg.zBias = data["hover.zBias"]

        self.pub_hover.publish(msg)

    def logCallbackZPid(self, data):
        msg = zpidMSG()
        msg.header.stamp = rospy.Time.now()
        msg.p = data["zpid.p"]
        msg.i = data["zpid.i"]
        msg.d = data["zpid.d"]
        msg.pid = data["zpid.pid"]

        self.pub_zpid.publish(msg)

    def logCallbackAttitude(self, data):
        msg = attitudeMSG()
        msg.header.stamp = rospy.Time.now()
        q0 = data["attitude.q0"]
        q1 = data["attitude.q1"]
        q2 = data["attitude.q2"]
        q3 = data["attitude.q3"]
        msg.q = np.array((q0, q1, q2, q3))
        self.pub_attitude.publish(msg)
        self.pub_tf.sendTransform((0, 0, 0), (q1, q2, q3, q0),
                                  rospy.Time.now(), "/cf_q", "/world")

    def logCallbackGravOffset(self, data):
        pass

    def logCallbackBaro(self, data):
        msg = baroMSG()
        msg.header.stamp = rospy.Time.now()
        msg.temp = data["baro.temp"]
        msg.pressure = data["baro.pressure"]
        msg.asl = data["baro.asl"]
        msg.aslRaw = data["baro.aslRaw"]
        msg.aslLong = data["baro.aslLong"]
        self.pub_baro.publish(msg)

        #if self.baro_start_time == None:
        #    self.baro_start_time = rospy.Time.now()
        #self.csvwriter.writerow([(msg.header.stamp-self.baro_start_time).to_sec(),msg.asl, msg.asl_raw,msg.temperature,msg.pressure])

    def logCallbackBat(self, data):
        msg = batMSG()
        msg.header.stamp = rospy.Time.now()
        msg.link = self.link
        msg.bat_v = data["pm.vbat"]
        msg.bat_p = (data["pm.vbat"] - 3.0) / 1.15 * 100.
        msg.charge_state = data["pm.state_charge"]
        msg.state = data["pm.state"]
        self.pub_bat.publish(msg)
        self.battery_monitor.update(msg.state, msg.charge_state, msg.bat_v)

    def logCallbackMotor(self, data):
        msg = motorMSG()
        msg.header.stamp = rospy.Time.now()
        msg.pwm[0] = thrustToPercentage(data["motor.m1"])
        msg.pwm[1] = thrustToPercentage(data["motor.m2"])
        msg.pwm[2] = thrustToPercentage(data["motor.m3"])
        msg.pwm[3] = thrustToPercentage(data["motor.m4"])
        msg.thrust = thrustToPercentage(data["motor.thrust"]) / 100.
        msg.thrust_raw = data["motor.thrust"]
        msg.vLong = data["motor.vLong"]
        self.pub_motor.publish(msg)

    def connectSetupFinishedCB(self, uri=None):
        rospy.loginfo("...Connected")
        self.setup_log()

    def connectionFailedCB(self, msg=None, errmsg=None):
        rospy.logerr("Connection failed: %s", errmsg)
        exit()
        #self.restart()

    def connectionInitiatedCB(self, msg=None):
        rospy.loginfo("Connecting to: %s...", msg)

    def disconnectedCB(self, msg=None):
        self.battery_monitor.done()
        if msg != None:
            rospy.loginfo("Disconnected from: %s", msg)

    def connectionLostCB(self, msg=None, errmsg=None):
        rospy.logerr("Connection lost: %s", errmsg)
        self.restart()

    def linkQualityCB(self, msg=None):
        self.link = msg

    def consoleCB(self, msg):
        rospy.loginfo(msg)

    def shutdown(self):
        self.crazyflie.close_link()
Example #6
0
def main():
    rospy.init_node('ur_driver', disable_signals=True)
    if rospy.get_param("use_sim_time", False):
        rospy.logwarn("use_sim_time is set!!!")

    global prevent_programming
    reconfigure_srv = Server(URDriverConfig, reconfigure_callback)

    prefix = rospy.get_param("~prefix", "")
    print "Setting prefix to %s" % prefix
    global joint_names
    joint_names = [prefix + name for name in JOINT_NAMES]

    # Parses command line arguments
    parser = optparse.OptionParser(
        usage="usage: %prog robot_hostname [reverse_port]")
    (options, args) = parser.parse_args(rospy.myargv()[1:])
    if len(args) < 1:
        parser.error("You must specify the robot hostname")
    elif len(args) == 1:
        robot_hostname = args[0]
        reverse_port = DEFAULT_REVERSE_PORT
    elif len(args) == 2:
        robot_hostname = args[0]
        reverse_port = int(args[1])
        if not (0 <= reverse_port <= 65535):
            parser.error("You entered an invalid port number")
    else:
        parser.error("Wrong number of parameters")

    # Reads the calibrated joint offsets from the URDF
    global joint_offsets
    joint_offsets = load_joint_offsets(joint_names)
    if len(joint_offsets) > 0:
        rospy.loginfo("Loaded calibration offsets from urdf: %s" %
                      joint_offsets)
    else:
        rospy.loginfo("No calibration offsets loaded from urdf")

    # Reads the maximum velocity
    # The max_velocity parameter is only used for debugging in the ur_driver. It's not related to actual velocity limits
    global max_velocity
    max_velocity = rospy.get_param("~max_velocity", MAX_VELOCITY)  # [rad/s]
    rospy.loginfo("Max velocity accepted by ur_driver: %s [rad/s]" %
                  max_velocity)

    # Reads the minimum payload
    global min_payload
    min_payload = rospy.get_param("~min_payload", MIN_PAYLOAD)
    # Reads the maximum payload
    global max_payload
    max_payload = rospy.get_param("~max_payload", MAX_PAYLOAD)
    rospy.loginfo("Bounds for Payload: [%s, %s]" % (min_payload, max_payload))

    # Sets up the server for the robot to connect to
    server = TCPServer(("", reverse_port), CommanderTCPHandler)
    thread_commander = threading.Thread(name="CommanderHandler",
                                        target=server.serve_forever)
    thread_commander.daemon = True
    thread_commander.start()

    with open(roslib.packages.get_pkg_dir('ur_driver') + '/prog') as fin:
        program = fin.read() % {
            "driver_hostname": get_my_ip(robot_hostname, PORT),
            "driver_reverseport": reverse_port
        }
    connection = URConnection(robot_hostname, PORT, program)
    connection.connect()
    connection.send_reset_program()

    connectionRT = URConnectionRT(robot_hostname, RT_PORT)
    connectionRT.connect()

    set_io_server()

    service_provider = None
    action_server = None
    try:
        while not rospy.is_shutdown():
            # Checks for disconnect
            if getConnectedRobot(wait=False):
                time.sleep(0.2)
                try:
                    prevent_programming = rospy.get_param(
                        "~prevent_programming")
                    update = {'prevent_programming': prevent_programming}
                    reconfigure_srv.update_configuration(update)
                except KeyError, ex:
                    print "Parameter 'prevent_programming' not set. Value: " + str(
                        prevent_programming)
                    pass
                if prevent_programming:
                    print "Programming now prevented"
                    connection.send_reset_program()
            else:
                print "Disconnected.  Reconnecting"
                if action_server:
                    action_server.set_robot(None)

                rospy.loginfo("Programming the robot")
                while True:
                    # Sends the program to the robot
                    while not connection.ready_to_program():
                        print "Waiting to program"
                        time.sleep(1.0)
                    try:
                        prevent_programming = rospy.get_param(
                            "~prevent_programming")
                        update = {'prevent_programming': prevent_programming}
                        reconfigure_srv.update_configuration(update)
                    except KeyError, ex:
                        print "Parameter 'prevent_programming' not set. Value: " + str(
                            prevent_programming)
                        pass
                    connection.send_program()

                    r = getConnectedRobot(wait=True, timeout=1.0)
                    if r:
                        break
                rospy.loginfo("Robot connected")

                #provider for service calls
                if service_provider:
                    service_provider.set_robot(r)
                else:
                    service_provider = URServiceProvider(r, connection)

                if action_server:
                    action_server.set_robot(r)
                else:
                    action_server = URTrajectoryFollower(
                        r, rospy.Duration(1.0))
                    action_server.start()
Example #7
0
    # init dynamic reconfigure server
    cfg_srv = Server(PoseControllerConfig, cfg_callback)
    set_server_value(cfg_srv)
    # Get ros args
    if rospy.has_param('~vel_topic'):
        vel_topic = rospy.get_param('~vel_topic', vel_topic)
    if rospy.has_param('~cmd_vel'):
        cmd_vel_topic = rospy.get_param('~cmd_vel', cmd_vel_topic)
    if rospy.has_param('~goal_topic'):
        goal_topic = rospy.get_param('~goal_topic', goal_topic)
    if rospy.has_param('~pose_topic'):
        pose_topic = rospy.get_param('~pose_topic', pose_topic)

    if rospy.has_param('~max_vel'):
        max_vel = rospy.get_param('~max_vel', max_vel)
        cfg_srv.update_configuration({"max_vel": max_vel})
    if rospy.has_param('~min_vel'):
        min_vel = rospy.get_param('~min_vel', min_vel)
        cfg_srv.update_configuration({"min_vel": min_vel})
    if rospy.has_param('~max_angle'):
        max_angle = rospy.get_param('~max_angle', max_angle)
        cfg_srv.update_configuration({"max_angle": max_angle})
    ## PID params
    if rospy.has_param(
            '~kP_pose'
    ):  #pose means vel in case of repeatativenes but just now
        kP_pose = rospy.get_param('~kP_pose', kP_pose)
        cfg_srv.update_configuration({"kP_pose": kP_pose})
    if rospy.has_param('~kI_pose'):
        kI_pose = rospy.get_param('~kI_pose', kI_pose)
        cfg_srv.update_configuration({"kI_pose": kI_pose})
Example #8
0
class img_interface_node: ## This is the LISTENER for the layer ABOVE
    """The 'img_interface_node' is the class which communicates to the layer above
    according to this structure. It provides services and dynamic reconfigure server
    and also publishes topics."""



###############################################
##  *****     Static Data Members     *****
###############################################
# ****   Services provided to the above layer
#==================================================
    listOf_services = {}



###############################################
##  *****     Constructor Method     *****
###############################################

    def __init__(self, translator, driverMgr):
        """img_interface_node constructor is meant to launch the dynamic reconfigure server
        until it is called to launch services for listening. It doesn't receive or return
        anything."""
        self.translator = translator
        self.driverMgr = driverMgr
        self.avoidRemoteReconf = 0
        try:
            ## Dynamic Reconfigure
            self.avoidRemoteReconf = self.avoidRemoteReconf + 1
            # Launch self DynamicReconfigure server
            self.dynServer = DynamicReconfigureServer(PropertiesConfig, self.dynServerCallback)
            # Launch request listener services
            self.listenToRequests()
            rospy.loginfo("Interface for requests created.")
        except:
            rospy.logerr("Error while trying to initialise dynamic parameter server.")
            raise
        return





###################################################
##  *****     General Purpose Methods     *****
###################################################
# ****   Launch services for receiving requests
#==================================================

    def listenToRequests(self):
        """Main services creator method in order to remain listening for requests.
        It receives nothing and returns true just for future needs."""
        self.listOf_services['get/StringProperty'] = rospy.Service('get/StringProperty', stringValue, self.getStringProperty)
        self.listOf_services['get/IntProperty'] = rospy.Service('get/IntProperty', intValue, self.getIntProperty)
        self.listOf_services['get/FloatProperty'] = rospy.Service('get/FloatProperty', floatValue, self.getFloatProperty)
        self.listOf_services['get/BoolProperty'] = rospy.Service('get/BoolProperty', booleanValue, self.getBoolProperty)

##MICHI: 14Feb2012
        self.listOf_services['get/DispImage'] = rospy.Service('get/DispImage', disparityImage, self.getDispImage)
        self.listOf_services['get/Image'] = rospy.Service('get/Image', normalImage, self.getImage)
##MICHI: 13Mar2012
        self.listOf_services['publishDispImages'] = rospy.Service('publishDispImages', requestTopic, self.publishDispImages)
        self.listOf_services['publishImages'] = rospy.Service('publishImages', requestTopic, self.publishImages)
##MICHI: 16Apr2012
        self.listOf_services['get/TopicLocation'] = rospy.Service('get/TopicLocation', stringValue, self.getStringProperty)
        self.listOf_services['set/TopicLocation'] = rospy.Service('set/TopicLocation', setString, self.setTopicLocation)

        self.listOf_services['set/StrProperty'] = rospy.Service('set/StrProperty', setString, self.setStrProperty)
        self.listOf_services['set/IntProperty'] = rospy.Service('set/IntProperty', setInteger, self.setIntProperty)
        self.listOf_services['set/FloatProperty'] = rospy.Service('set/FloatProperty', setFloat, self.setFloatProperty)
        self.listOf_services['set/BoolProperty'] = rospy.Service('set/BoolProperty', setBoolean, self.setBoolProperty)
        rospy.loginfo("Ready to answer service requests.")
        return True



###################################################
# ****   Dynamic Reconfigure related methods
#==================================================

    def dynServerCallback(self, dynConfiguration, levelCode):
        """Handler for the changes in the Dynamic Reconfigure server
        Must return a configuration in the same format as received."""
        if self.avoidRemoteReconf > 0:
            rospy.loginfo("LOCAL config was changed by self (levelCode = %s)"%levelCode)
            self.avoidRemoteReconf = self.avoidRemoteReconf - 1
        elif levelCode != 0:
            rospy.loginfo("LOCAL configuration changed.".rjust(80, '_'))
            for elem in dynConfiguration:
                if self.translator.canSet(elem):
                    success = self.setAnyProperty(elem, dynConfiguration[elem])
                    if success == False or success == None:
                        rospy.logerr('|__> ...error... while setting property "%s"'%elem)
                        rospy.logerr("Changing %s failed with %s..."%(elem,dynConfiguration[elem]))
                        dynConfiguration[elem] = self.getAnyProperty(elem)
                        rospy.logdebug("...%s used"%(dynConfiguration[elem]))
        return dynConfiguration

    def updateSelfParameters(self, dynConfiguration, avoidPropagation = True):
        """This method is responsible for changing the node's own dynamic reconfigure parameters
        from its own code ***but avoiding a chain of uncontrolled callbacks!!.
        It returns True if everything went as expected."""
        newConfig = {}
        for elem in dynConfiguration:
            paramName = self.translator.reverseInterpret(elem)
            if paramName != "":
                newValue = dynConfiguration[elem]
                newConfig[paramName] = newValue
            else:
                rospy.logerr("Error while retrieving reverse translation.")

        if avoidPropagation == True:
            self.avoidRemoteReconf = self.avoidRemoteReconf + 1
        requestResult = self.dynServer.update_configuration(newConfig)
        if requestResult != None:
            return requestResult
        return False


    def setTopicLocation(self, setStrMsg):
        rospy.loginfo (str("Received call to set/TopicLocation " + setStrMsg.topicName + " " + setStrMsg.newValue))
        translation = self.translator.interpret(setStrMsg.topicName)
        if translation != None and (translation[PPTY_KIND]=="publishedTopic" or translation[PPTY_KIND]=="topic"):
            oldAddress = translation[PPTY_REF]
        elif manager3D.is_published(setStrMsg.topicName):
            oldAddress = setStrMsg.topicName
        else:
            print str("The " + setStrMsg.topicName + " topic wasn't found neither as topic or as a name:")
            return str("Exception while relocating. Topic not found.")
        
        try:
            rospy.loginfo (str("...relocating " + oldAddress + "..."))
            self.driverMgr.relocateTopic(oldAddress, setStrMsg.newValue)
            ## If it was a name; the value needs to be updated in the translator
            if oldAddress == setStrMsg.topicName:
                self.translator.updateValue(setStrMsg.topicName, setStrMsg.newValue)
        except Exception as e:
            rospy.logerr("Exception while relocating: %s"%(e))
            return str("Exception while relocating: %s"%(e))
        return "Success!"


###################################################
# ****   Generic handlers for getting and setting parameters
#==================================================

    def setAnyProperty(self, propertyName, newValue):
        """Generic setter in order to redirect to the type-specific setter method."""
        propertyData = self.translator.interpret(propertyName)
        if propertyData == None:
            return None
        #else:
        if (propertyData[PPTY_TYPE].lower()).find("string") >= 0:
#            if type(newValue) != str:
#                print "Error: value '%s' seems to be %s instead of '%s' which is needed"%(newValue, type(newValue), propertyData[PPTY_TYPE])
#                return None
            valueType = str
        elif (propertyData[PPTY_TYPE].lower()).find("int") >= 0:
            valueType = int
        elif (propertyData[PPTY_TYPE].lower()).find("double") >= 0 or (propertyData[PPTY_TYPE].lower()).find("float") >= 0:
            valueType = float
        elif (propertyData[PPTY_TYPE].lower()).find("bool") >= 0:
            valueType = bool
        else:
            rospy.logerr("Error: non registered value type.")
            return None
        return self.setFixedTypeProperty(propertyName, newValue, valueType)


	####################################
	# Specific fixed type property setters
	#===================================
    ## The next methods are the callbacks for the setter services
    ##all of them are supposed to return the resulting values to
    ##inform in case the set event failed.
    ## In case there's an error; the error message is sent no
    ##matter the returning type is
    def setStrProperty(self, setterMessage):
        return self.setFixedTypeProperty(setterMessage.topicName, setterMessage.newValue, str)
    def setIntProperty(self, setterMessage):
        return self.setFixedTypeProperty(setterMessage.topicName, setterMessage.newValue, int)
    def setFloatProperty(self, setterMessage):
        return self.setFixedTypeProperty(setterMessage.topicName, setterMessage.newValue, float)
    def setBoolProperty(self, setterMessage):
        return self.setFixedTypeProperty(setterMessage.topicName, setterMessage.newValue, bool)

    def setFixedTypeProperty(self, propertyName, newValue, valueType):
        """Main property setter receiving the property name, the value to assign and its type
        and returning the response from the "setParameter" method from the low level driver."""
        propertyData = self.translator.interpret(propertyName)
        #TODO: Any type checking here?
        if propertyData == None:
            rospy.logerr("Error: trying to set not found property.")
            return None
#        else:
#            print "Obtained %s = %s."%(propertyName, propertyData[PPTY_REF])
        resp1 = True
        if propertyData[PPTY_KIND] == "dynParam":
            paramName = propertyTranslator.getBasename(propertyData[PPTY_REF])
            resp1 = self.driverMgr.setParameter(paramName, newValue, self.translator.getDynServerPath(paramName), self)
        elif propertyData[PPTY_KIND] == "topic":
            self.driverMgr.sendByTopic(rospy.get_namespace() + propertyData[PPTY_REF], newValue, remoteType[valueType])
        else:
            resp1 = False
            rospy.logerr("Error: unable to set property %s of kind: '%s'"%(propertyName, propertyData[PPTY_KIND]))
        return valueType(resp1)


# Getter and getter handlers
    # Generic setter in order to redirect to the appropriate method
    def getAnyProperty(self, propertyName):
        """Generic getter in order to redirect to the type-specific getter method."""
        propertyData = self.translator.interpret(propertyName)
        if propertyData == None:
            return None
        #else:
        if propertyData[PPTY_TYPE].find("string") >= 0:
            valueType = str
        elif (propertyData[PPTY_TYPE].lower()).find("int") >= 0:
            valueType = int
        elif (propertyData[PPTY_TYPE].lower()).find("double") >= 0 or (propertyData[PPTY_TYPE].lower()).find("float") >= 0:
            valueType = float
        elif (propertyData[PPTY_TYPE].lower()).find("bool") >= 0:
            valueType = bool
        else:
            rospy.logerr("Error: non registered value type")
            return None
        return self.getFixedTypeProperty(propertyName, valueType)

    # Generic getter in order to redirect to the appropriate method
    #Specific fixed type property getters
    def getStringProperty(self, srvMsg):
        return self.getFixedTypeProperty(srvMsg.topicName, str)
    def getIntProperty(self, srvMsg):
        return self.getFixedTypeProperty(srvMsg.topicName, int)
    def getFloatProperty(self, srvMsg):
        return self.getFixedTypeProperty(srvMsg.topicName, float)
    def getBoolProperty(self, srvMsg):
        return self.getFixedTypeProperty(srvMsg.topicName, bool)
    def getDispImage(self, srvMsg):
        respImages = []
        while len(respImages) < srvMsg.nImages:
            respImages.append(self.getFixedTypeProperty(srvMsg.topicName, None))
        for image in respImages:
            self.driverMgr.sendByTopic("testImages", image, DisparityImage)
        return respImages
    def getImage(self, srvMsg):
        respImages = []
        while len(respImages) < srvMsg.nImages:
            respImages.append(self.getFixedTypeProperty(srvMsg.topicName, None))
        return respImages
##AMPLIATION: I could add normalImage.timestamp (time[] timestamp) if needed (including the changes in the "normalImage.srv"
## in that case I would need a normalImage variable and set both fields: images and timestamp

    def getFixedTypeProperty(self, propertyName, valueType):
        """Main property getter receiving the property name and the value type
        and returning the current value from the low level driver."""
        propertyData = self.translator.interpret(propertyName)
        if propertyData == None:
            return None
        #else:
        if propertyData[PPTY_KIND] == "dynParam":
            paramName = propertyTranslator.getBasename(propertyData[PPTY_REF])
            resp1 = self.driverMgr.getParameter(paramName, self.translator.getDynServerPath(propertyName))
        elif propertyData[PPTY_KIND] == "publishedTopic":
            resp1 = self.driverMgr.getTopic(propertyData[PPTY_REF], valueType)
        elif propertyData[PPTY_KIND] == "readOnlyParam":
            paramName = propertyTranslator.getBasename(propertyData[PPTY_REF])
            location = rospy.search_param(param_name)
            resp1 = rospy.get_param(location)
        elif propertyData[PPTY_KIND] == "topic":
##MICHI: 14Feb2012
            if valueType == str:
                resp1 = str(propertyData[PPTY_REF])
            else: ## Special case for reading disparity images
                valueType2 = DisparityImage
                if propertyData[PPTY_TYPE].find("Disparity") < 0:
                    valueType2 = Image
                resp1 = self.driverMgr.getTopic(propertyData[PPTY_REF], valueType2)
        else:
            rospy.logerr("Error: unable to get property %s of kind %s"%(propertyName, propertyData[PPTY_TYPE]))
            resp1 = None
        return resp1


    # Method to request the complete list of properties
    def get_property_list(self):
        """Auxiliary method for receiving the list of properties known by the translator."""
        return self.translator.get_property_list()
    
    # Methods related to requesting topics to publish
    def publishDispImages(self, srvMsg):
        """Specific purpose method meant to retransmit a disparity image topic on a specific topic
        path receiving the original path, the new path and the amount of messages. It returns a
        message telling if it worked."""
        topicPath = self.translator.get_topic_path(srvMsg.sourceTopic)
        self.driverMgr.retransmitTopic(srvMsg.nImages, topicPath, srvMsg.responseTopic, DisparityImage)
        "%s images sent from %s topic to %s."%(srvMsg.nImages, srvMsg.sourceTopic, srvMsg.responseTopic)
        return "%s images sent from %s topic to %s."%(srvMsg.nImages, srvMsg.sourceTopic, srvMsg.responseTopic)
    
    def publishImages(self, srvMsg):
        """Specific purpose method meant to retransmit an image topic on a specific topic
        path receiving the original path, the new path and the amount of messages. It returns
        a message telling if it worked."""
        topicPath = self.translator.get_topic_path(srvMsg.sourceTopic)
        self.driverMgr.retransmitTopic(srvMsg.nImages, topicPath, srvMsg.responseTopic, Image)
        "%s images sent from %s topic to %s."%(srvMsg.nImages, srvMsg.sourceTopic, srvMsg.responseTopic)
        return "%s images sent from %s topic to %s."%(srvMsg.nImages, srvMsg.sourceTopic, srvMsg.responseTopic)
Example #9
0
def talker():
    global counter_x, counter_y, counter_z, dummy_tag, angle
    global cars

    # initialize node
    rospy.init_node('DWM1001_API', anonymous=False)

    # initialize our topics
    pub_Network = rospy.Publisher('DWM1001_Network', String, queue_size=10)
    pub_Anchor_0 = rospy.Publisher('DWM1001_Network_Anchor_0',
                                   String,
                                   queue_size=10)
    pub_Anchor_1 = rospy.Publisher('DWM1001_Network_Anchor_1',
                                   String,
                                   queue_size=10)
    pub_Anchor_2 = rospy.Publisher('DWM1001_Network_Anchor_2',
                                   String,
                                   queue_size=10)
    pub_Anchor_3 = rospy.Publisher('DWM1001_Network_Anchor_3',
                                   String,
                                   queue_size=10)
    pub_Tag = rospy.Publisher('DWM1001_Network_Tag', String, queue_size=10)

    srv = Server(GUIConfig, callback)
    rate = rospy.Rate(10)  # 10hz

    while not rospy.is_shutdown():

        coordinates_tag = dummy_tag.split()
        coordinates_anchor_0 = dummy_anchor_0.split()

        inc_x = int(float(coordinates_tag[0]) - float(coordinates_anchor_0[0]))
        inc_y = int(float(coordinates_tag[1]) - float(coordinates_anchor_0[1]))

        angle_to_goal = math.atan2(inc_y, inc_x)

        #radius = 15
        #theta = math.radians(angle)
        #counter_y = radius * math.cos(theta) + 0
        #counter_x = radius * math.sin(theta) + 0

        #angle = angle + 1

        # if angle > 360:
        #    angle = 0

        dummy_tag = str(counter_x) + " " + str(counter_y) + " " + str(
            counter_z)
        rospy.loginfo("sending dummy values: tag = " + str(dummy_tag))

        pub_Anchor_0.publish(dummy_anchor_0)
        pub_Anchor_1.publish(dummy_anchor_1)
        pub_Anchor_2.publish(dummy_anchor_2)
        pub_Anchor_3.publish(dummy_anchor_3)
        pub_Tag.publish(dummy_tag)

        srv.update_configuration(cars)

        #if counter_y > 26:
        #    counter_x = counter_x + 0.3

        #if counter_y > 30:
        #    counter_y = 30

        #if counter_x > 26:
        #    counter_y = counter_y - 0.3

        #if counter_y < 26:
        #    counter_y = counter_y + 0.3
        #hi

        counter_y = counter_y + 0.01
        #counter_2 = counter_2 + 0.001
        #counter_3 = counter_3 + 0.00001

        rate.sleep()
Example #10
0
class Driver:
    def __init__(self, options):
        self.options = options        
        
        self.crazyflie = Crazyflie()
        self.battery_monitor = BatteryMonitor()
        cflib.crtp.init_drivers()
        
        self.zspeed = zSpeed()
        
        # Keep track of this to know if the user changed imu mode to/from imu6/9
        self.preMagcalib = None
                
        
        #self.csvwriter = csv.writer(open('baro.csv', 'wb'), delimiter=',',quotechar='"', quoting=csv.QUOTE_MINIMAL)
        #self.baro_start_time = None
                 
        # Some shortcuts
        self.HZ100 = 10
        self.HZ10 = 100
        self.HZ1 = 1000        
        self.HZ500 = 2
        self.HZ250 = 4
        self.HZ125 = 8
        self.HZ50 = 20

        # Callbacks the CF driver calls      
        self.crazyflie.connectSetupFinished.add_callback(self.connectSetupFinishedCB)
        self.crazyflie.connectionFailed.add_callback(self.connectionFailedCB)
        self.crazyflie.connectionInitiated.add_callback(self.connectionInitiatedCB)
        self.crazyflie.disconnected.add_callback(self.disconnectedCB)
        self.crazyflie.connectionLost.add_callback(self.connectionLostCB)
        self.crazyflie.linkQuality.add_callback(self.linkQualityCB)   
        
        # Advertise publishers 
        self.pub_acc   = rospy.Publisher("/cf/acc", accMSG)
        self.pub_mag   = rospy.Publisher("/cf/mag", magMSG)
        self.pub_gyro  = rospy.Publisher("/cf/gyro", gyroMSG)
        self.pub_baro  = rospy.Publisher("/cf/baro", baroMSG)
        self.pub_motor = rospy.Publisher("/cf/motor", motorMSG)
        self.pub_hover = rospy.Publisher("/cf/hover", hoverMSG)
        self.pub_attitude = rospy.Publisher("/cf/attitude", attitudeMSG)
        self.pub_bat   = rospy.Publisher("/cf/bat", batMSG)
        self.pub_zpid   = rospy.Publisher("/cf/zpid", zpidMSG)
        self.pub_tf    = tf.TransformBroadcaster()            
        
        # Subscribers           
        self.sub_tf    = tf.TransformListener()         
        self.sub_joy   = rospy.Subscriber("/cfjoy", joyMSG, self.new_joydata)
        self.sub_magCalib   = rospy.Subscriber("/magCalib", magCalibMSG, self.new_magCalib)
        
        # Keep track of link quality to send out with battery status
        self.link = 0
        
        # Loggers 
        self.logMotor = None
        self.logBaro = None
        self.logMag = None
        self.logGyro = None
        self.logAttitude = None
        self.logAcc = None
        self.logBat = None
        self.logHover = None
        self.logZPid = None                   
        self.logGravOffset = None #only here till it works
        
        # keep tracks if loggers are running
        self.acc_monitor = False
        self.gyro_monitor = False
        self.baro_monitor = False
        self.mag_monitor = False
        self.bat_monitor = False
        self.motor_monitor = False    
        self.attitude_monitor = False   
        self.hover_monitor = False
        self.zpid_monitor = False 
        
        # CF params we wanna be able to change
        self.cf_param_groups = ["hover", "sensorfusion6", "magCalib"]
        self.cf_params_cache = {}
       
       
        
        # Dynserver                
        self.dynserver = DynamicReconfigureServer(driverCFG, self.reconfigure)
        
        # Finished set up. Connect to flie            
        self.connect(self.options)
        
    def new_magCalib(self, msg):  
        """ Receive magnetometer calibration results. Send to flie via dyn conf"""      
        new_settings = {}
        #TODO        
        new_settings["magCalib_off_x"] = msg.offset[0]
        new_settings["magCalib_off_y"] = msg.offset[1]
        new_settings["magCalib_off_z"] = msg.offset[2]
        new_settings["magCalib_thrust_x"] = msg.thrust_comp[0]
        new_settings["magCalib_thrust_y"] = msg.thrust_comp[1]
        new_settings["magCalib_thrust_z"] = msg.thrust_comp[2]
        new_settings["magCalib_scale_x"] = msg.scale[0]
        new_settings["magCalib_scale_y"] = msg.scale[1]
        new_settings["magCalib_scale_z"] = msg.scale[2]        
        self.dynserver.update_configuration(new_settings)  
        rospy.loginfo("Updated magnetometer calibration data") 
    
    def connect(self, options):     
        """ Look for crazyflie every 2s and connect to the specified one if found"""                          
        rospy.loginfo("Waiting for crazyflie...")
        while not rospy.is_shutdown():
            interfaces = cflib.crtp.scan_interfaces()
            if len(interfaces)>1:
                radio = None
                rospy.loginfo("Found: ")  
                for i in interfaces:
                    rospy.loginfo("--> %s [%s]", i[0],i[1])
                    if i[0].startswith(options.uri):                                
                        radio = i[0]
                if radio!=None:
                    self.crazyflie.open_link(radio)
                    break
            else:
                rospy.sleep(2)                                     
 
 
    def new_joydata(self, joymsg):
        """ Joydata arrived. Should happen at 100hz."""
        
        # Parse joydata and extract commands
        roll = joymsg.roll
        pitch = joymsg.pitch
        yawrate = joymsg.yaw
        thrust = joymsg.thrust
        hover = joymsg.hover
        set_hover = joymsg.hover_set
        hover_change = joymsg.hover_change
        
        # Send to flie              
        self.send_control((roll, pitch, yawrate, thrust, hover, set_hover, hover_change))
        

    def reconfigure(self, config, level):
        """ Fill in local variables with values received from dynamic reconfigure clients (typically the GUI)."""
        config = self.zspeed.reconfigure(config)
        
        self.deadband = config["deadband"]

        # On / off logging
        if self.zpid_monitor != config["read_zpid"]:
            self.zpid_monitor = config["read_zpid"]           
            if self.logZPid != None:
                if self.zpid_monitor:               
                    self.logZPid.start()
                    rospy.loginfo("zPID Logging started")
                else:   
                    self.logZPid.stop()
                    rospy.loginfo("zPID Logging stopped")

        
        if self.gyro_monitor != config["read_gyro"]:
            self.gyro_monitor = config["read_gyro"]           
            if self.logGyro != None:
                if self.gyro_monitor:               
                    self.logGyro.start()
                    rospy.loginfo("Gyro Logging started")
                else:   
                    self.logGyro.stop()
                    rospy.loginfo("Gyro Logging stopped")

        if self.hover_monitor != config["read_hover"]:
            self.hover_monitor = config["read_hover"]           
            if self.logHover != None:
                if self.hover_monitor:               
                    self.logHover.start()
                    rospy.loginfo("Hover Logging started")
                else:   
                    self.logHover.stop()
                    rospy.loginfo("Hover Logging stopped")                    
                    
        if self.acc_monitor != config["read_acc"]:
            self.acc_monitor = config["read_acc"]           
            if self.logGyro != None:
                if self.acc_monitor:               
                    self.logAcc.start()
                    rospy.loginfo("Acc Logging started")
                else:   
                    self.logAcc.stop()
                    rospy.loginfo("Acc Logging stopped")
                    
        if self.baro_monitor != config["read_baro"]:
            self.baro_monitor = config["read_baro"]           
            if self.logBaro != None:
                if self.baro_monitor:               
                    self.logBaro.start()
                    rospy.loginfo("Baro Logging started")
                else:   
                    self.logBaro.stop()
                    rospy.loginfo("Baro Logging stopped")
                    
        if self.mag_monitor != config["read_mag"]:
            self.mag_monitor = config["read_mag"]           
            if self.logMag != None:
                if self.mag_monitor:               
                    self.logMag.start()
                    rospy.loginfo("Mag Logging started")
                else:   
                    self.logMag.stop()
                    rospy.loginfo("Mag Logging stopped")
                    
        if self.bat_monitor != config["read_bat"]:
            self.bat_monitor = config["read_bat"]           
            if self.logBat != None:
                if self.bat_monitor:               
                    self.logBat.start()
                    rospy.loginfo("Battery/Link Logging started")
                else:   
                    self.logBat.stop()
                    rospy.loginfo("Battery/Link Logging stopped")
                    
        if self.attitude_monitor != config["read_attitude"]:
            self.attitude_monitor = config["read_attitude"]           
            if self.logAttitude != None:
                if self.attitude_monitor:               
                    self.logAttitude.start()
                    rospy.loginfo("Attitude Logging started")
                else:   
                    self.logAttitude.stop()
                    rospy.loginfo("Attitude Logging stopped")
                                                                                                                        
        if self.motor_monitor != config["read_motor"]:
            self.motor_monitor = config["read_motor"]           
            if self.logMotor != None:
                if self.motor_monitor:               
                    self.logMotor.start()
                    rospy.loginfo("Motor Logging started")
                else:   
                    self.logMotor.stop()
                    rospy.loginfo("Motor Logging stopped")                                                                                                                        

            
        # SET CRAZYFLIE PARAMS        
        
        for key, value in config.iteritems():
            # Skip this key (whats its purpose??)            
            if key == "groups":
                continue
                    
            # Continue if variable exists and is correct
            if self.cf_params_cache.has_key(key):
                if config[key] == self.cf_params_cache[key]:
                    # Nothing changed
                    if not key.startswith("magCalib") or ( self.preMagcalib or not config["sensorfusion6_magImu"]):
                        continue
            
            # Doesnt exist or has changed
            
            # Split into group and name
            s = key.split("_",1)
            
            # Make sure it is a group name pair
            if len(s) == 2:
                # make sure group is valid
                if s[0] in self.cf_param_groups:
                    # update cache, send variable to flie
                    self.cf_params_cache[key] = value                    
                    self.send_param(s[0]+"."+s[1], value)    
                    print "updated: ",s[0]+"."+s[1],"->",value   
                    rospy.sleep(0.1)
                

        self.preMagcalib = config["sensorfusion6_magImu"]                                   
        return config
    
    def send_param(self, key, value):
        self.crazyflie.param.set_value(key, str(value))
         
    def restart(self):
        rospy.loginfo("Restarting Driver")
        rospy.sleep(1)
        self.connect(self.options)         
        
    def logErrorCB(self, errmsg):
        rospy.logerr("Log error: %s", errmsg)
        
    def send_control(self,cmd):
        """ Roll, pitch in deg, yaw in deg/s, thrust in 10000-60000, hover bool, set_hover bool, hover chance float -1 to +1 """
        roll, pitch, yawrate, thrust, hover, set_hover, hover_change = cmd    
        if self.crazyflie.state == CFState.CONNECTED:    
            self.crazyflie.commander.send_setpoint(roll, pitch, yawrate, thrust, hover, set_hover, hover_change)
                
        
        
    def setup_log(self):
        
        """ Console callbacks """
        self.crazyflie.console.receivedChar.add_callback(self.consoleCB)
 
        rospy.sleep(0.25)
        
        """ ATTITUDE LOGGING @ 100hz """
        logconf = LogConfig("attitude", self.HZ100 * 2) #ms
        logconf.addVariable(LogVariable("attitude.q0", "float"))
        logconf.addVariable(LogVariable("attitude.q1", "float"))
        logconf.addVariable(LogVariable("attitude.q2", "float"))
        logconf.addVariable(LogVariable("attitude.q3", "float"))             
        self.logAttitude = self.crazyflie.log.create_log_packet(logconf)  
        if (self.logAttitude is not None):
            self.logAttitude.dataReceived.add_callback(self.logCallbackAttitude)
            self.logAttitude.error.add_callback(self.logErrorCB)
            if self.attitude_monitor:
                self.logAttitude.start()
                rospy.loginfo("Attitude Logging started")     
        else:
            rospy.logwarn("Could not setup Attitude logging!")   
            
            
            
            
        rospy.sleep(0.25)
        
        """ ZPID LOGGING @ 100hz """
        logconf = LogConfig("zpid", self.HZ100) #ms
        logconf.addVariable(LogVariable("zpid.p", "float"))
        logconf.addVariable(LogVariable("zpid.i", "float"))   
        logconf.addVariable(LogVariable("zpid.d", "float"))   
        logconf.addVariable(LogVariable("zpid.pid", "float"))              
        self.logZPid = self.crazyflie.log.create_log_packet(logconf)  
        if (self.logZPid is not None):
            self.logZPid.dataReceived.add_callback(self.logCallbackZPid)
            self.logZPid.error.add_callback(self.logErrorCB)
            if self.zpid_monitor:
                self.logZPid.start()
                rospy.loginfo("ZPID Logging started")     
        else:
            rospy.logwarn("Could not setup ZPID logging!")               
        rospy.sleep(0.25)
        
        
        """ HOVER LOGGING @ 100hz """
        logconf = LogConfig("hover", self.HZ100) #ms
        logconf.addVariable(LogVariable("hover.err", "float"))
        logconf.addVariable(LogVariable("hover.target", "float"))        
        logconf.addVariable(LogVariable("hover.zSpeed", "float"))
        logconf.addVariable(LogVariable("hover.zBias", "float"))    
        logconf.addVariable(LogVariable("hover.acc_vspeed", "float"))
        logconf.addVariable(LogVariable("hover.asl_vspeed", "float"))
        
       
        self.logHover = self.crazyflie.log.create_log_packet(logconf)  
        if (self.logHover is not None):
            self.logHover.dataReceived.add_callback(self.logCallbackHover)
            self.logHover.error.add_callback(self.logErrorCB)
            if self.hover_monitor:
                self.logHover.start()
                rospy.loginfo("Hover Logging started")     
        else:
            rospy.logwarn("Could not setup Hover logging!")               
             
        rospy.sleep(0.25)  
        
       
        
        """ GYROMETER LOGGING @ 100hz """
        logconf = LogConfig("LoggingGyro", self.HZ100) #ms
        logconf.addVariable(LogVariable("gyro.x", "float"))
        logconf.addVariable(LogVariable("gyro.y", "float"))
        logconf.addVariable(LogVariable("gyro.z", "float"))         
        self.logGyro = self.crazyflie.log.create_log_packet(logconf)  
        if (self.logGyro is not None):
            self.logGyro.dataReceived.add_callback(self.logCallbackGyro)
            self.logGyro.error.add_callback(self.logErrorCB)
            if self.gyro_monitor:
                self.logGyro.start()
                rospy.loginfo("Gyro Logging started")     
        else:
            rospy.logwarn("Could not setup Gyro logging!")  
            
        rospy.sleep(0.25)          
             
        """ ACCELEROMETER LOGGING @ 100hz """
        logconf = LogConfig("LoggingAcc", self.HZ100) #ms
        logconf.addVariable(LogVariable("acc.x", "float"))
        logconf.addVariable(LogVariable("acc.y", "float"))
        logconf.addVariable(LogVariable("acc.z", "float"))
        logconf.addVariable(LogVariable("acc.xw", "float"))
        logconf.addVariable(LogVariable("acc.yw", "float"))
        logconf.addVariable(LogVariable("acc.zw", "float"))   
        self.logAcc = self.crazyflie.log.create_log_packet(logconf)  
        if (self.logAcc is not None):
            self.logAcc.dataReceived.add_callback(self.logCallbackAcc)
            self.logAcc.error.add_callback(self.logErrorCB)
            if self.acc_monitor:
                self.logAcc.start()
                rospy.loginfo("Acc Logging started")     
        else:
            rospy.logwarn("Could not setup Acc logging!")     
            
        rospy.sleep(0.25)       

        """ MAGNETOMETER LOGGING @ 100hz """
        logconf = LogConfig("LoggingMag", self.HZ100) #ms          
        logconf.addVariable(LogVariable("mag.x", "float"))        
        logconf.addVariable(LogVariable("mag.y", "float"))
        logconf.addVariable(LogVariable("mag.z", "float")) 
        logconf.addVariable(LogVariable("mag.x_raw", "float"))
        logconf.addVariable(LogVariable("mag.y_raw", "float"))
        logconf.addVariable(LogVariable("mag.z_raw", "float"))
               
        self.logMag = self.crazyflie.log.create_log_packet(logconf)  
        if (self.logMag is not None):
            self.logMag.dataReceived.add_callback(self.logCallbackMag)
            self.logMag.error.add_callback(self.logErrorCB)
            if self.mag_monitor:
                self.logMag.start()
                rospy.loginfo("Mag Logging started")     
        else:
            rospy.logwarn("Could not setup Mag logging!")            


        rospy.sleep(0.25)
        
        """ BAROMETER LOGGING @ 100hz """
        logconf = LogConfig("LoggingBaro", self.HZ100) #ms
        logconf.addVariable(LogVariable("baro.aslRaw", "float"))
        logconf.addVariable(LogVariable("baro.asl", "float"))
        logconf.addVariable(LogVariable("baro.temp", "float"))
        logconf.addVariable(LogVariable("baro.pressure", "float"))
        logconf.addVariable(LogVariable("baro.aslLong", "float"))                 
        self.logBaro = self.crazyflie.log.create_log_packet(logconf)  
        if (self.logBaro is not None):
            self.logBaro.dataReceived.add_callback(self.logCallbackBaro)
            self.logBaro.error.add_callback(self.logErrorCB)
            if self.baro_monitor:
                self.logBaro.start()
                rospy.loginfo("Baro Logging started")     
        else:
            rospy.logwarn("Could not setup Baro logging!")                                                 
            
        rospy.sleep(0.25)    
            
        """ BATTERY/LINK LOGGING @ 10hz """
        logconf = LogConfig("LoggingBat", self.HZ10) #ms
        logconf.addVariable(LogVariable("pm.vbat", "float"))
        logconf.addVariable(LogVariable("pm.state", "uint8_t"))
        logconf.addVariable(LogVariable("pm.state_charge", "uint8_t"))
        self.logBat = self.crazyflie.log.create_log_packet(logconf)  
        if (self.logBat is not None):
            self.logBat.dataReceived.add_callback(self.logCallbackBat)
            self.logBat.error.add_callback(self.logErrorCB)
            if self.bat_monitor:
                self.logBat.start()
                rospy.loginfo("Bat Logging started")     
        else:
            rospy.logwarn("Could not setup Battery/Link logging!")
            
        rospy.sleep(0.25)    
      
            
        #TODO motor logging not working
        """ MOTOR LOGGING @ 100hz"""
        logconf = LogConfig("LoggingMotor", self.HZ100) #ms
        logconf.addVariable(LogVariable("motor.m1", "uint32_t")) 
        logconf.addVariable(LogVariable("motor.m2", "uint32_t"))
        logconf.addVariable(LogVariable("motor.m3", "uint32_t"))
        logconf.addVariable(LogVariable("motor.m4", "uint32_t"))  
        logconf.addVariable(LogVariable("motor.thrust", "uint16_t"))  
        logconf.addVariable(LogVariable("motor.vLong", "float")) #TODO move
        self.logMotor = self.crazyflie.log.create_log_packet(logconf)  
        if (self.logMotor is not None):
            self.logMotor.dataReceived.add_callback(self.logCallbackMotor)
            self.logMotor.error.add_callback(self.logErrorCB)
            if self.motor_monitor:
                self.logMotor.start()
                rospy.loginfo("Motor Logging started")     
        else:
            rospy.logwarn("Could not setup Motor logging!")                                    
        
         
 
    def logCallbackAcc(self, data):
        """Accelerometer axis data in mG --> G"""
        msg = accMSG()
        msg.header.stamp = rospy.Time.now()
        msg.acc[0] = data["acc.x"]
        msg.acc[1] = data["acc.y"]
        msg.acc[2] = data["acc.z"]
        msg.acc_w[0] = data["acc.xw"]
        msg.acc_w[1] = data["acc.yw"]
        msg.acc_w[2] = data["acc.zw"]      
        
        self.zspeed.add(msg.acc_w[2])  
        
        self.pub_acc.publish(msg)
        
        self.pub_tf.sendTransform(msg.acc_w,(0,0,0,1),
             rospy.Time.now(), 
             "/acc_w","/cf_q")
        
        self.pub_tf.sendTransform(msg.acc,(0,0,0,1),
             rospy.Time.now(), 
             "/acc", "/cf_q")
        
    def logCallbackMag(self, data):
        """TODO UNITS"""
        msg = magMSG()
        msg.header.stamp = rospy.Time.now()
        msg.mag[0] = data["mag.x"]
        msg.mag[1] = data["mag.y"]
        msg.mag[2] = data["mag.z"]     
        msg.magRaw[0] = data["mag.x_raw"]
        msg.magRaw[1] = data["mag.y_raw"]
        msg.magRaw[2] = data["mag.z_raw"] 
        self.pub_mag.publish(msg)       
        
        if np.linalg.norm(msg.magRaw)>1:
            self.pub_tf.sendTransform(msg.mag/np.linalg.norm(msg.magRaw),(0,0,0,1),
                 rospy.Time.now(), 
                 "/mag_raw",
                 "/cf_q")
        if np.linalg.norm(msg.mag)>1:
            self.pub_tf.sendTransform(msg.mag/np.linalg.norm(msg.mag),(0,0,0,1),
                 rospy.Time.now(), 
                 "/mag_calib",
                 "/cf_q")            
                
    def logCallbackGyro(self, data):    
        """Gyro axis data in deg/s -> rad/s"""       
        msg = gyroMSG()   
        msg.header.stamp = rospy.Time.now()
        msg.gyro[0] = radians(data["gyro.x"])
        msg.gyro[1] = radians(data["gyro.y"])
        msg.gyro[2] = radians(data["gyro.z"])
        self.pub_gyro.publish(msg)
        
    def logCallbackHover(self, data): 
        """Output data regarding hovering"""         
        msg = hoverMSG()   
        msg.header.stamp = rospy.Time.now()
        
        msg.err = deadband(data["hover.err"], self.deadband)
        msg.zSpeed = data["hover.zSpeed"]
        msg.acc_vspeed = data["hover.acc_vspeed"]# * self.cf_params_cache["hover_acc_vspeedFac"]
        msg.asl_vspeed = data["hover.asl_vspeed"]# * self.cf_params_cache["hover_asl_vspeedFac"]
        msg.target = data["hover.target"]
        msg.zBias = data["hover.zBias"]
     
        self.pub_hover.publish(msg)        


    def logCallbackZPid(self, data):
        msg = zpidMSG()   
        msg.header.stamp = rospy.Time.now()
        msg.p = data["zpid.p"]
        msg.i = data["zpid.i"]
        msg.d = data["zpid.d"]
        msg.pid = data["zpid.pid"]
        
        self.pub_zpid.publish(msg) 
          
    def logCallbackAttitude(self, data):
        msg = attitudeMSG()   
        msg.header.stamp = rospy.Time.now()
        q0 = data["attitude.q0"]
        q1 = data["attitude.q1"]
        q2 = data["attitude.q2"]
        q3 = data["attitude.q3"]
        msg.q = np.array((q0,q1,q2,q3))
        self.pub_attitude.publish(msg)        
        self.pub_tf.sendTransform((0, 0, 0),(q1,q2,q3,q0),
             rospy.Time.now(), 
             "/cf_q",
             "/world") 
        
    def logCallbackGravOffset(self, data):
        pass
        
    def logCallbackBaro(self, data):
        msg = baroMSG()
        msg.header.stamp = rospy.Time.now()
        msg.temp = data["baro.temp"]
        msg.pressure = data["baro.pressure"]
        msg.asl = data["baro.asl"]
        msg.aslRaw = data["baro.aslRaw"]
        msg.aslLong = data["baro.aslLong"]
        self.pub_baro.publish(msg)           
        
        #if self.baro_start_time == None:
        #    self.baro_start_time = rospy.Time.now() 
        #self.csvwriter.writerow([(msg.header.stamp-self.baro_start_time).to_sec(),msg.asl, msg.asl_raw,msg.temperature,msg.pressure])
      
                
    def logCallbackBat(self, data):
        msg = batMSG()
        msg.header.stamp = rospy.Time.now()
        msg.link = self.link
        msg.bat_v = data["pm.vbat"]
        msg.bat_p = (data["pm.vbat"]-3.0)/1.15*100.
        msg.charge_state = data["pm.state_charge"]
        msg.state = data["pm.state"]        
        self.pub_bat.publish(msg)
        self.battery_monitor.update(msg.state, msg.charge_state, msg.bat_v)

        
    def logCallbackMotor(self, data):
        msg = motorMSG()
        msg.header.stamp = rospy.Time.now()
        msg.pwm[0] = thrustToPercentage(data["motor.m1"])
        msg.pwm[1] = thrustToPercentage(data["motor.m2"])
        msg.pwm[2] = thrustToPercentage(data["motor.m3"])
        msg.pwm[3] = thrustToPercentage(data["motor.m4"])
        msg.thrust = thrustToPercentage(data["motor.thrust"])/100.
        msg.thrust_raw = data["motor.thrust"]
        msg.vLong = data["motor.vLong"]
        self.pub_motor.publish(msg)                                                  
        
        
    def connectSetupFinishedCB(self, uri=None):
        rospy.loginfo("...Connected")      
        self.setup_log()
        
    def connectionFailedCB(self, msg=None, errmsg=None):
        rospy.logerr("Connection failed: %s", errmsg)
        exit()
        #self.restart()
        
    def connectionInitiatedCB(self, msg=None):
        rospy.loginfo("Connecting to: %s...", msg)
        
    def disconnectedCB(self, msg=None):
        self.battery_monitor.done()
        if msg!=None:
            rospy.loginfo("Disconnected from: %s", msg)
        
    def connectionLostCB(self, msg=None, errmsg=None):
        rospy.logerr("Connection lost: %s", errmsg)        
        self.restart()
        
    def linkQualityCB(self, msg=None):
        self.link = msg
        
    def consoleCB(self, msg):
        rospy.loginfo(msg)        

    def shutdown(self):
        self.crazyflie.close_link()
Example #11
0
class Behavior:

    def InitSaliencyCounter(self):
        self.saliency_counter = random.randint(int(self.saliency_time_min * self.synthesizer_rate),int(self.saliency_time_max * self.synthesizer_rate))


    def InitFacesCounter(self):
        self.faces_counter = random.randint(int(self.faces_time_min * self.synthesizer_rate),int(self.faces_time_max * self.synthesizer_rate))


    def InitEyesCounter(self):
        self.eyes_counter = random.randint(int(self.eyes_time_min * self.synthesizer_rate),int(self.eyes_time_max * self.synthesizer_rate))


    def InitAudienceCounter(self):
        self.audience_counter = random.randint(int(self.audience_time_min * self.synthesizer_rate),int(self.audience_time_max * self.synthesizer_rate))


    def InitGestureCounter(self):
        self.gesture_counter = random.randint(int(self.gesture_time_min * self.synthesizer_rate),int(self.gesture_time_max * self.synthesizer_rate))


    def InitExpressionCounter(self):
        self.expression_counter = random.randint(int(self.expression_time_min * self.synthesizer_rate),int(self.expression_time_max * self.synthesizer_rate))


    def InitAllFacesStartCounter(self):
        self.all_faces_start_counter = random.randint(int(self.all_faces_start_time_min * self.synthesizer_rate),int(self.all_faces_start_time_max * self.synthesizer_rate))


    def InitAllFacesDurationCounter(self):
        self.all_faces_duration_counter = random.randint(int(self.all_faces_duration_min * self.synthesizer_rate),int(self.all_faces_duration_max * self.synthesizer_rate))


    def __init__(self):

        # create lock
        self.lock = threading.Lock()

        self.robot_name = rospy.get_param("/robot_name")

        self.config_dir = os.path.join(rospy.get_param("/robots_config_dir"), 'heads', self.robot_name)
        # setup face, hand and saliency structures
        self.faces = {}  # index = cface_id, which should be relatively steady from vision_pipeline
        self.current_face_id = 0  # cface_id of current face
        self.last_face_id = 0  # most recent cface_id of added face
        self.last_talk_ts = 0  # ts of last seen face or talking
        self.hand = None  # current hand
        self.last_hand_ts = 0  # ts of last seen hand
        self.saliencies = {}  # index = ts, and old saliency vectors will be removed after time
        self.current_saliency_ts = 0  # ts of current saliency vector
        self.current_eye = 0  # current eye (0 = left, 1 = right, 2 = mouth)

        self.gaze_delay_counter = 0  # delay counter after with gaze or head follows head or gaze
        self.gaze_pos = None  # current gaze position

        # animations
        self.animations = None
        self.current_gestures_name = None
        self.current_expressions_name = None

        self.tf_listener = tf.TransformListener(False, rospy.Duration(1))

        # setup dynamic reconfigure parameters
        self.enable_flag = True
        self.synthesizer_rate = 10.0
        self.keep_time = 1.0
        self.saliency_time_min = 0.1
        self.saliency_time_max = 3.0
        self.faces_time_min = 0.1
        self.faces_time_max = 3.0
        self.eyes_time_min = 0.1
        self.eyes_time_max = 3.0
        self.audience_time_min = 0.1
        self.audience_time_max = 3.0
        self.gesture_time_min = 0.1
        self.gesture_time_max = 3.0
        self.expression_time_min = 0.1
        self.expression_time_max = 3.0
        self.InitSaliencyCounter()
        self.InitFacesCounter()
        self.InitEyesCounter()
        self.InitAudienceCounter()
        self.InitGestureCounter()
        self.InitExpressionCounter()
        self.hand_state_decay = 2.0
        self.face_state_decay = 2.0
        self.gaze_delay = 1.0
        self.gaze_speed = 0.5
        self.all_faces_start_time_min = 4.0
        self.all_faces_start_time_max = 6.0
        self.all_faces_duration_min = 2.0
        self.all_faces_duration_max = 4.0
        self.InitAllFacesStartCounter()
        self.InitAllFacesDurationCounter()
        self.eyecontact = EyeContact.IDLE
        self.lookat = LookAt.IDLE
        self.mirroring = Mirroring.IDLE
        self.gaze = Gaze.GAZE_ONLY
        self.state = State.SLEEPING

        # take candidate streams exactly like RealSense Tracker until fusion is better defined and we can rely on combined camera stuff
        rospy.Subscriber('/{}/perception/realsense/cface'.format(self.robot_name), CandidateFace, self.HandleFace)
        rospy.Subscriber('/{}/perception/realsense/chand'.format(self.robot_name), CandidateHand, self.HandleHand)
        rospy.Subscriber('/{}/perception/wideangle/csaliency'.format(self.robot_name), CandidateSaliency, self.HandleSaliency)
        rospy.Subscriber('/{}/perception/acousticmagic/raw_audiodir'.format(self.robot_name), AudioDirection, self.HandleAudioDirection)
        rospy.Subscriber('/{}/perception/motion/raw_motion'.format(self.robot_name), MotionVector, self.HandleMotion)

        rospy.Subscriber('/{}/chat_events'.format(self.robot_name), String, self.HandleChatEvents)
        rospy.Subscriber('/{}/speech_events'.format(self.robot_name), String, self.HandleSpeechEvents)

        self.head_focus_pub = rospy.Publisher('/blender_api/set_face_target', Target, queue_size=1)
        self.gaze_focus_pub = rospy.Publisher('/blender_api/set_gaze_target', Target, queue_size=1)
        self.expressions_pub = rospy.Publisher('/blender_api/set_emotion_state', EmotionState, queue_size=1)
        self.gestures_pub = rospy.Publisher('/blender_api/set_gesture', SetGesture, queue_size=1)
        self.animationmode_pub = rospy.Publisher('/blender_api/set_animation_mode', UInt8, queue_size=1)
        self.setpau_pub = rospy.Publisher('/blender_api/set_pau', pau, queue_size=1)
        self.tts_pub = rospy.Publisher('/{}/tts'.format(self.robot_name), TTS, queue_size=1)  # for debug messages

        self.hand_events_pub = rospy.Publisher('/hand_events', String, queue_size=1)

        # dynamic reconfigure client to the vision pipelines
        self.lefteye_config = dynamic_reconfigure.client.Client("/{}/perception/lefteye/vision_pipeline".format(self.robot_name),timeout=30,config_callback=self.HandleLeftEyeConfig)
        self.righteye_config = dynamic_reconfigure.client.Client("/{}/perception/righteye/vision_pipeline".format(self.robot_name),timeout=30,config_callback=self.HandleRightEyeConfig)
        self.wideangle_config = dynamic_reconfigure.client.Client("/{}/perception/wideangle/vision_pipeline".format(self.robot_name),timeout=30,config_callback=self.HandleWideAngleConfig)
        self.realsense_config = dynamic_reconfigure.client.Client("/{}/perception/realsense/vision_pipeline".format(self.robot_name),timeout=30,config_callback=self.HandleRealSenseConfig)

        # TEMP: set all pipelines to 1Hz
        self.lefteye_config.update_configuration({"pipeline_rate":1.0,"detect_rate":1.0})
        self.righteye_config.update_configuration({"pipeline_rate":1.0,"detect_rate":1.0})
        self.wideangle_config.update_configuration({"pipeline_rate":1.0,"detect_rate":1.0})
        self.realsense_config.update_configuration({"pipeline_rate":1.0,"detect_rate":1.0})

        # start timer
        self.config_server = FakeConfigServer()  # this is a workaround because self.HandleTimer could be triggered before the config_server actually exists
        self.timer = rospy.Timer(rospy.Duration(1.0 / self.synthesizer_rate),self.HandleTimer)

        # start dynamic reconfigure server
        self.config_server = Server(BehaviorConfig, self.HandleConfig)


    def UpdateStateDisplay(self):

        self.config_server.update_configuration({
            "eyecontact_state":self.eyecontact,
            "lookat_state":self.lookat,
            "mirroring_state":self.mirroring,
            "gaze_state":self.gaze,
            "eyecontact_state":self.eyecontact
        })


    def HandleConfig(self, config, level):

        # Load gestures and expressions from configs first time loaded
        if config.reload_animations or (self.animations == None):
            try:
                self.animations = YamlConfig.load(self.config_dir, 'r2_behavior_anim.yaml')
            except IOError:
                self.animations = YamlConfig.load(os.path.join(os.path.dirname(os.path.dirname(__file__)),'cfg'),
                                                    'r2_behavior_anim.default.yaml')
            config.reload_animations = False

        if self.current_gestures_name == None:
            self.current_gestures_name = "idle_gestures"

        if self.current_expressions_name == None:
            self.current_expressions_name = "idle_expressions"

        if self.enable_flag != config.enable_flag:
            self.enable_flag = config.enable_flag
            # TODO: enable or disable the behaviors

        if self.synthesizer_rate != config.synthesizer_rate:
            self.synthesizer_rate = config.synthesizer_rate
            self.timer.shutdown()
            self.timer = rospy.Timer(rospy.Duration(1.0 / self.synthesizer_rate),self.HandleTimer)
            self.InitSaliencyCounter()
            self.InitFacesCounter()
            self.InitEyesCounter()
            self.InitAudienceCounter()
            self.InitGestureCounter()
            self.InitExpressionCounter()
            self.InitAllFacesStartCounter()
            self.InitAllFacesDurationCounter()

        # keep time
        self.keep_time = config.keep_time

        # update the counter ranges (and counters if the ranges changed)
        if config.saliency_time_max < config.saliency_time_min:
            config.saliency_time_max = config.saliency_time_min
        if config.saliency_time_min != self.saliency_time_min or config.saliency_time_max != self.saliency_time_max:
            self.saliency_time_min = config.saliency_time_min
            self.saliency_time_max = config.saliency_time_max
            self.InitSaliencyCounter()

        if config.faces_time_max < config.faces_time_min:
            config.faces_time_max = config.faces_time_min
        if config.faces_time_min != self.faces_time_min or config.faces_time_max != self.faces_time_max:
            self.faces_time_min = config.faces_time_min
            self.faces_time_max = config.faces_time_max
            self.InitFacesCounter()

        if config.eyes_time_max < config.eyes_time_min:
            config.eyes_time_max = config.eyes_time_min
        if config.eyes_time_min != self.eyes_time_min or config.eyes_time_max != self.eyes_time_max:
            self.eyes_time_min = config.eyes_time_min
            self.eyes_time_max = config.eyes_time_max
            self.InitEyesCounter()

        if config.audience_time_max < config.audience_time_min:
            config.audience_time_max = config.audience_time_min
        if config.audience_time_min != self.audience_time_min or config.audience_time_max != self.audience_time_max:
            self.audience_time_min = config.audience_time_min
            self.audience_time_max = config.audience_time_max
            self.InitAudienceCounter()

        if config.gesture_time_max < config.gesture_time_min:
            config.gesture_time_max = config.gesture_time_min
        if config.gesture_time_min != self.gesture_time_min or config.gesture_time_max != self.gesture_time_max:
            self.gesture_time_min = config.gesture_time_min
            self.gesture_time_max = config.gesture_time_max
            self.InitGestureCounter()

        if config.expression_time_max < config.expression_time_min:
            config.expression_time_max = config.expression_time_min
        if config.expression_time_min != self.expression_time_min or config.expression_time_max != self.expression_time_max:
            self.expression_time_min = config.expression_time_min
            self.expression_time_max = config.expression_time_max
            self.InitExpressionCounter()

        self.hand_state_decay = config.hand_state_decay
        self.face_state_decay = config.face_state_decay

        self.gaze_delay = config.gaze_delay
        self.gaze_speed = config.gaze_speed

        if config.all_faces_start_time_max < config.all_faces_start_time_min:
            config.all_faces_start_time_max = config.all_faces_start_time_min
        if config.all_faces_start_time_min != self.all_faces_start_time_min or config.all_faces_start_time_max != self.all_faces_start_time_max:
            self.all_faces_start_time_min = config.all_faces_start_time_min
            self.all_faces_start_time_max = config.all_faces_start_time_max
            self.InitAllFacesStartCounter()

        if config.all_faces_duration_max < config.all_faces_duration_min:
            config.all_faces_duration_max = config.all_faces_duration_min
        if config.all_faces_duration_min != self.all_faces_duration_min or config.all_faces_duration_max != self.all_faces_duration_max:
            self.all_faces_duration_min = config.all_faces_duration_min
            self.all_faces_duration_max = config.all_faces_duration_max
            self.InitAllFacesDurationCounter()

        # and set the states for each state machine
        self.SetEyeContact(config.eyecontact_state)
        self.SetLookAt(config.lookat_state)
        self.SetMirroring(config.mirroring_state)
        self.SetGaze(config.gaze_state)

        # and finally the overall state
        self.SetState(config.state)

        return config


    def HandleLeftEyeConfig(self,config):
        return config


    def HandleRightEyeConfig(self,config):
        return config


    def HandleWideAngleConfig(self,config):
        return config


    def HandleRealSenseConfig(self,config):
        return config


    def Say(self,text):
        # publish TTS message
        msg = TTS()
        msg.text = text
        msg.lang = 'en-US'
        self.tts_pub.publish(msg)


    def SetGazeFocus(self,pos,speed):
        msg = Target()
        msg.x = pos.x
        msg.y = pos.y
        msg.z = pos.z
        msg.speed = speed
        self.gaze_focus_pub.publish(msg)


    def SetHeadFocus(self,pos,speed):
        msg = Target()
        msg.x = pos.x
        msg.y = pos.y
        msg.z = pos.z
        msg.speed = speed
        self.head_focus_pub.publish(msg)


    def UpdateGaze(self,pos):

        self.gaze_pos = pos

        if self.gaze == Gaze.GAZE_ONLY:
            self.SetGazeFocus(pos,5.0)

        elif self.gaze == Gaze.HEAD_ONLY:
            self.SetHeadFocus(pos,3.0)

        elif self.gaze == Gaze.GAZE_AND_HEAD:
            self.SetGazeFocus(pos,5.0)
            self.SetHeadFocus(pos,3.0)

        elif self.gaze == Gaze.GAZE_LEADS_HEAD:
            self.SetGazeFocus(pos,5.0)

        elif self.gaze == Gaze.HEAD_LEADS_GAZE:
            self.SetHeadFocus(pos,3.0)


    def SelectNextFace(self):
        # switch to the next (or first) face
        if len(self.faces) == 0:
            # there are no faces, so select none
            self.current_face_id = 0
            return
        if self.current_face_id == 0:
            self.current_face_id = self.faces.keys()[0]
        else:
            if self.current_face_id in self.faces:
                next = self.faces.keys().index(self.current_face_id) + 1
                if next >= len(self.faces.keys()):
                    next = 0
            else:
                next = 0
            self.current_face_id = self.faces.keys()[next]


    def SelectNextSaliency(self):
        # switch to the next (or first) saliency vector
        if len(self.saliencies) == 0:
            # there are no saliency vectors, so select none
            self.current_saliency_ts = 0
            return
        if self.current_saliency_ts == 0:
            self.current_saliency_ts = self.saliencies.keys()[0]
        else:
            if self.current_saliency_ts in self.saliencies:
                next = self.saliencies.keys().index(self.current_saliency_ts) + 1
                if next >= len(self.saliencies):
                    next = 0
            else:
                next = 0
            self.current_saliency_ts = self.saliencies.keys()[next]


    def SelectNextAudience(self):
        # TODO: switch to next audience (according to audience ROI)
        ()


    def HandleTimer(self,data):

        # this is the heart of the synthesizer, here the lookat and eyecontact state machines take care of where the robot is looking, and random expressions and gestures are triggered to look more alive (like RealSense Tracker)

        ts = data.current_expected

        # ==== handle lookat
        if self.lookat == LookAt.IDLE:
            # no specific target, let Blender do it's soma cycle thing
            ()

        elif self.lookat == LookAt.AVOID:
            # TODO: find out where there is no saliency, hand or face
            # TODO: head_focus_pub
            ()

        elif self.lookat == LookAt.SALIENCY:
            self.saliency_counter -= 1
            if self.saliency_counter == 0:
                self.InitSaliencyCounter()
                self.SelectNextSaliency()
            if self.current_saliency_ts != 0:
                cursaliency = self.saliencies[self.current_saliency_ts]
                self.UpdateGaze(cursaliency.direction)

        elif self.lookat == LookAt.HAND:
            # stare at hand
            if self.hand != None:
                self.UpdateGaze(self.hand.position)

        elif self.lookat == LookAt.AUDIENCE:
            self.audience_counter -= 1
            if self.audience_counter == 0:
                self.InitAudienceCounter()
                self.SelectNextAudience()
                # TODO: self.UpdateGaze()

        elif self.lookat == LookAt.SPEAKER:
            ()
            # TODO: look at the speaker, according to speaker ROI

        else:
            if self.lookat == LookAt.ALL_FACES:
                self.faces_counter -= 1
                if self.faces_counter == 0:
                    self.InitFacesCounter()
                    self.SelectNextFace()

            # take the current face
            if self.current_face_id != 0:
                curface = self.faces[self.current_face_id]
                face_pos = curface.position

                # ==== handle eyecontact (only for LookAt.ONE_FACE and LookAt.ALL_FACES)

                # calculate where left eye, right eye and mouth are on the current face
                left_eye_pos = Float32XYZ()
                right_eye_pos = Float32XYZ()
                mouth_pos = Float32XYZ()

                # all are 5cm in front of the center of the face
                left_eye_pos.x = face_pos.x - 0.05
                right_eye_pos.x = face_pos.x - 0.05
                mouth_pos.x = face_pos.x - 0.05

                left_eye_pos.y = face_pos.y + 0.03  # left eye is 3cm to the left of the center
                right_eye_pos.y = face_pos.y - 0.03  # right eye is 3cm to the right of the center
                mouth_pos.y = face_pos.y  # mouth is dead center

                left_eye_pos.z = face_pos.z + 0.06  # left eye is 6cm above the center
                right_eye_pos.z = face_pos.z + 0.06  # right eye is 6cm above the center
                mouth_pos.z = face_pos.z - 0.04  # mouth is 4cm below the center

                if self.eyecontact == EyeContact.IDLE:
                    # look at center of the head
                    self.UpdateGaze(face_pos)

                elif self.eyecontact == EyeContact.LEFT_EYE:
                    # look at left eye
                    self.UpdateGaze(left_eye_pos)

                elif self.eyecontact == EyeContact.RIGHT_EYE:
                    # look at right eye
                    self.UpdateGaze(right_eye_pos)

                elif self.eyecontact == EyeContact.BOTH_EYES:
                    # switch between eyes back and forth
                    self.eyes_counter -= 1
                    if self.eyes_counter == 0:
                        self.InitEyesCounter()
                        if self.current_eye == 1:
                            self.current_eye = 0
                        else:
                            self.current_eye = 1
                    # look at that eye
                    if self.current_eye == 0:
                        cur_eye_pos = left_eye_pos
                    else:
                        cur_eye_pos = right_eye_pos
                    self.UpdateGaze(cur_eye_pos)

                elif self.eyecontact == EyeContact.TRIANGLE:
                    # cycle between eyes and mouth
                    self.eyes_counter -= 1
                    if self.eyes_counter == 0:
                        self.InitEyesCounter()
                        if self.current_eye == 2:
                            self.current_eye = 0
                        else:
                            self.current_eye += 1
                    # look at that eye
                    if self.current_eye == 0:
                        cur_eye_pos = left_eye_pos
                    elif self.current_eye == 1:
                        cur_eye_pos = right_eye_pos
                    elif self.current_eye == 2:
                        cur_eye_pos = mouth_pos
                    self.UpdateGaze(cur_eye_pos)

                # mirroring
                msg = pau()
                msg.m_coeffs = [ ]
                msg.m_shapekeys = [ ]

                if self.mirroring == Mirroring.EYEBROWS or self.mirroring == Mirroring.EYES or self.mirroring == Mirroring.MOUTH_EYEBROWS or self.mirroring == Mirroring.ALL:
                    # mirror eyebrows
                    left_brow = curface.left_brow
                    right_brow = curface.right_brow
                    msg.m_coeffs.append("brow_outer_UP.L")
                    msg.m_shapekeys.append(left_brow)
                    msg.m_coeffs.append("brow_inner_UP.L")
                    msg.m_shapekeys.append(left_brow * 0.8)
                    msg.m_coeffs.append("brow_outer_DN.L")
                    msg.m_shapekeys.append(1.0 - left_brow)
                    msg.m_coeffs.append("brow_outer_up.R")
                    msg.m_shapekeys.append(right_brow)
                    msg.m_coeffs.append("brow_inner_UP.R")
                    msg.m_shapekeys.append(right_brow * 0.8)
                    msg.m_coeffs.append("brow_outer_DN.R")
                    msg.m_shapekeys.append(1.0 - right_brow)

                if self.mirroring == Mirroring.EYELIDS or self.mirroring == Mirroring.EYES or self.mirroring == Mirroring.MOUTH_EYELIDS or self.mirroring == Mirroring.ALL:
                    # mirror eyelids
                    eyes_closed = ((1.0 - curface.left_eyelid) + (1.0 - curface.right_eyelid)) / 2.0
                    msg.m_coeffs.append("eye-blink.UP.R")
                    msg.m_shapekeys.append(eyes_closed)
                    msg.m_coeffs.append("eye-blink.UP.L")
                    msg.m_shapekeys.append(eyes_closed)
                    msg.m_coeffs.append("eye-blink.LO.R")
                    msg.m_shapekeys.append(eyes_closed)
                    msg.m_coeffs.append("eye-blink.LO.L")
                    msg.m_shapekeys.append(eyes_closed)

                if self.mirroring == Mirroring.MOUTH or self.mirroring == Mirroring.MOUTH_EYEBROWS or self.mirroring == Mirroring.MOUTH_EYELIDS:
                    # mirror mouth
                    mouth_open = curface.mouth_open
                    msg.m_coeffs.append("lip-JAW.DN")
                    msg.m_shapekeys.append(mouth_open)

                if self.mirroring != Mirroring.IDLE:
                    self.StartPauMode()
                    self.setpau_pub.publish(msg)


        # start random gestures
        self.gesture_counter -= 1
        if self.gesture_counter == 0:
            self.InitGestureCounter()

            if self.animations != None:

                # list all gestures that would fire right now according to probability
                firing = []
                for g in self.animations[self.current_gestures_name]:
                    if random.uniform(0.0,1.0) <= g["probability"]:
                        firing.append(g)

                # start randomly from that list
                if len(firing) > 0:
                    g = firing[random.randint(0,len(firing) - 1)]
                    msg = SetGesture()
                    msg.name = g["name"]
                    msg.repeat = False
                    msg.speed = random.uniform(g["speed_min"],g["speed_max"])
                    msg.magnitude = random.uniform(g["magnitude_min"],g["magnitude_max"])
                    self.gestures_pub.publish(msg)

        # start random expressions
        self.expression_counter -= 1
        if self.expression_counter == 0:
            self.InitExpressionCounter()

            if self.animations != None:

                # list all expressions that would fire right now according to probability
                firing = []
                for g in self.animations[self.current_expressions_name]:
                    if random.uniform(0.0,1.0) <= g["probability"]:
                        firing.append(g)

                # start randomly from that list
                if len(firing) > 0:
                    g = firing[random.randint(0,len(firing) - 1)]
                    msg = EmotionState()
                    msg.name = g["name"]
                    msg.magnitude = random.uniform(g["magnitude_min"],g["magnitude_max"])
                    msg.duration = rospy.Duration(random.uniform(g["duration_min"],g["duration_max"]))
                    self.expressions_pub.publish(msg)

        prune_before_time = ts - rospy.Duration.from_sec(self.keep_time)

        # flush faces dictionary, update current face accordingly
        to_be_removed = []
        for face in self.faces.values():
            if face.ts < prune_before_time:
                to_be_removed.append(face.cface_id)
        # remove the elements
        for key in to_be_removed:
            del self.faces[key]
            # make sure the selected face is always valid
            if self.current_face_id == key:
                self.SelectNextFace()
                
        # remove hand if it is too old
        if self.hand != None:
            if self.hand.ts < prune_before_time:
                self.hand = None

        # flush saliency dictionary
        to_be_removed = []
        for key in self.saliencies.keys():
            if key < prune_before_time:
                to_be_removed.append(key)
        # remove the elements
        for key in to_be_removed:
            del self.saliencies[key]
            # make sure the selected saliency is always valid
            if self.current_saliency_ts == key:
                self.SelectNextSaliency()

        # decay from FOCUSED to IDLE if hand was not seen for a while
        if self.state == State.FOCUSED and self.last_hand_ts < ts - rospy.Duration.from_sec(self.hand_state_decay):
            self.SetState(State.IDLE)
            self.UpdateStateDisplay()

        # decay from SPEAKING or LISTENING to IDLE
        if ((self.state == State.SPEAKING) or (self.state == State.LISTENING)) and self.last_talk_ts < ts - rospy.Duration.from_sec(self.face_state_decay):
            self.SetState(State.IDLE)
            self.UpdateStateDisplay()

        # have gaze or head follow head or gaze after a while
        if self.gaze_delay_counter > 0 and self.gaze_pos != None:

            self.gaze_delay_counter -= 1
            if self.gaze_delay_counter == 0:

                if self.gaze == Gaze.GAZE_LEADS_HEAD:
                    self.SetHeadFocus(self.gaze_pos,self.gaze_speed)
                    self.gaze_delay_counter = int(self.gaze_delay * self.synthesizer_rate)

                elif self.gaze == Gaze.HEAD_LEADS_GAZE:
                    self.SetGazeFocus(self.gaze_pos,self.gaze_speed)
                    self.gaze_delay_counter = int(self.gaze_delay * self.synthesizer_rate)


        # when speaking, sometimes look at all faces
        if self.state == State.SPEAKING:

            if self.lookat == LookAt.AVOID:

                self.all_faces_start_counter -= 1
                if self.all_faces_start_counter == 0:
                    self.InitAllFacesStartCounter()
                    self.SetLookAt(LookAt.ALL_FACES)
                    self.UpdateStateDisplay()

            elif self.lookat == LookAt.ALL_FACES:

                self.all_faces_duration_counter -= 1
                if self.all_faces_duration_counter == 0:
                    self.InitAllFacesDurationCounter()
                    self.SetLookAt(LookAt.AVOID)
                    self.UpdateStateDisplay()


    def SetEyeContact(self, neweyecontact):

        if neweyecontact == self.eyecontact:
            return

        self.eyecontact = neweyecontact

        if self.eyecontact == EyeContact.BOTH_EYES or self.eyecontact == EyeContact.TRIANGLE:
            self.InitEyesCounter()


    def SetLookAt(self, newlookat):

        if newlookat == self.lookat:
            return

        self.lookat = newlookat

        if self.lookat == LookAt.SALIENCY:
            self.InitSaliencyCounter()

        elif self.lookat == LookAt.ONE_FACE:
            self.InitEyesCounter()

        elif self.lookat == LookAt.ALL_FACES:
            self.InitFacesCounter()
            self.InitEyesCounter()

        elif self.lookat == LookAt.AUDIENCE:
            self.InitAudienceCounter()


    def StartPauMode(self):

        mode = UInt8()
        mode.data = 148
        self.animationmode_pub.publish(mode)


    def StopPauMode(self):

        mode = UInt8()
        mode.data = 0
        self.animationmode_pub.publish(mode)


    def SetMirroring(self, newmirroring):

        if newmirroring == self.mirroring:
            return

        self.mirroring = newmirroring

        if self.mirroring == Mirroring.IDLE:
            self.StopPauMode()
        else:
            self.StartPauMode()


    def SetGaze(self, newgaze):

        if newgaze == self.gaze:
            return

        self.gaze = newgaze

        if self.gaze == Gaze.GAZE_LEADS_HEAD or self.gaze == Gaze.HEAD_LEADS_GAZE:
            self.gaze_delay_counter = int(self.gaze_delay * self.synthesizer_rate)


    # ==== MAIN STATE MACHINE

    def SetState(self, newstate):

        # this is where the new main state is initialized, it sets up lookat and eyecontact states appropriately, manage perception system refresh rates and load random gesture and expression probabilities to be processed by HandleTimer

        if newstate == self.state:
            return

        self.state = newstate

        # initialize new state
        if self.state == State.SLEEPING:
            # the robot sleeps
            print("State.SLEEPING")
            self.current_gestures_name = "sleeping_gestures"
            self.current_expressions_name = "sleeping_expressions"
            #self.lefteye_config.update_configuration({"pipeline_rate":1.0,"detect_rate":1.0})
            #self.righteye_config.update_configuration({"pipeline_rate":1.0,"detect_rate":1.0})
            #self.wideangle_config.update_configuration({"pipeline_rate":1.0,"detect_rate":1.0})
            #self.realsense_config.update_configuration({"pipeline_rate":1.0,"detect_rate":1.0})
            #self.SetEyeContact(EyeContact.IDLE)
            #self.SetLookAt(LookAt.IDLE)
            #self.SetMirroring(Mirroring.IDLE)
            #self.SetGaze(Gaze.GAZE_ONLY)
            # IDEA: at SLEEPING, wakeup by ROS message and transition to IDLE
            # IDEA: at SLEEPING, wakeup by loud noise
            
        elif self.state == State.IDLE:
            # the robot is idle
            print("State.IDLE")
            self.current_gestures_name = "idle_gestures"
            self.current_expressions_name = "idle_expressions"
            self.lefteye_config.update_configuration({"pipeline_rate":1.0,"detect_rate":1.0})
            self.righteye_config.update_configuration({"pipeline_rate":1.0,"detect_rate":1.0})
            self.wideangle_config.update_configuration({"pipeline_rate":10.0,"detect_rate":10.0})
            self.realsense_config.update_configuration({"pipeline_rate":10.0,"detect_rate":20.0})
            self.SetEyeContact(EyeContact.IDLE)
            self.SetLookAt(LookAt.IDLE)
            self.SetMirroring(Mirroring.IDLE)
            self.SetGaze(Gaze.GAZE_ONLY)

        elif self.state == State.INTERESTED:
            # the robot is actively idle
            print("State.INTERESTED")
            self.current_gestures_name = "interested_gestures"
            self.current_expressions_name = "interested_expressions"
            self.lefteye_config.update_configuration({"pipeline_rate":1.0,"detect_rate":1.0})
            self.righteye_config.update_configuration({"pipeline_rate":1.0,"detect_rate":1.0})
            self.wideangle_config.update_configuration({"pipeline_rate":20.0,"detect_rate":10.0})
            self.realsense_config.update_configuration({"pipeline_rate":20.0,"detect_rate":20.0})
            self.SetEyeContact(EyeContact.IDLE)
            self.SetLookAt(LookAt.SALIENCY)
            self.SetMirroring(Mirroring.IDLE)
            self.SetGaze(Gaze.GAZE_ONLY)

        elif self.state == State.FOCUSED:
            # the robot is very interested at something specific
            print("State.FOCUSED")
            self.current_gestures_name = "focused_gestures"
            self.current_expressions_name = "focused_expressions"
            self.lefteye_config.update_configuration({"pipeline_rate":1.0,"detect_rate":1.0})
            self.righteye_config.update_configuration({"pipeline_rate":1.0,"detect_rate":1.0})
            self.wideangle_config.update_configuration({"pipeline_rate":20.0,"detect_rate":20.0})
            self.realsense_config.update_configuration({"pipeline_rate":20.0,"detect_rate":20.0})
            self.SetEyeContact(EyeContact.IDLE)
            self.SetLookAt(LookAt.HAND)
            self.SetMirroring(Mirroring.IDLE)
            self.SetGaze(Gaze.GAZE_AND_HEAD)

        elif self.state == State.SPEAKING:
            # the robot is speaking (directly/intimately) to people
            print("State.SPEAKING")
            self.current_gestures_name = "speaking_gestures"
            self.current_expressions_name = "speaking_expressions"
            self.lefteye_config.update_configuration({"pipeline_rate":1.0,"detect_rate":1.0})
            self.righteye_config.update_configuration({"pipeline_rate":1.0,"detect_rate":1.0})
            self.wideangle_config.update_configuration({"pipeline_rate":20.0,"detect_rate":10.0})
            self.realsense_config.update_configuration({"pipeline_rate":20.0,"detect_rate":20.0})
            self.SetEyeContact(EyeContact.IDLE)
            self.SetLookAt(LookAt.AVOID)
            self.SetMirroring(Mirroring.IDLE)
            self.SetGaze(Gaze.GAZE_LEADS_HEAD)
            self.last_talk_ts = rospy.get_rostime()

        elif self.state == State.LISTENING:
            # the robot is listening to whoever is speaking
            print("State.LISTENING")
            self.current_gestures_name = "listening_gestures"
            self.current_expressions_name = "listening_expressions"
            self.lefteye_config.update_configuration({"pipeline_rate":1.0,"detect_rate":1.0})
            self.righteye_config.update_configuration({"pipeline_rate":1.0,"detect_rate":1.0})
            self.wideangle_config.update_configuration({"pipeline_rate":20.0,"detect_rate":20.0})
            self.realsense_config.update_configuration({"pipeline_rate":20.0,"detect_rate":20.0})
            self.SetEyeContact(EyeContact.BOTH_EYES)
            self.SetLookAt(LookAt.ONE_FACE)
            self.SetMirroring(Mirroring.IDLE)
            self.SetGaze(Gaze.HEAD_LEADS_GAZE)
            self.last_talk_ts = rospy.get_rostime()

        elif self.state == State.PRESENTING:
            # the robot is presenting to the audience
            print("State.PRESENTING")
            self.current_gestures_name = "presenting_gestures"
            self.current_expressions_name = "presenting_expressions"
            self.lefteye_config.update_configuration({"pipeline_rate":1.0,"detect_rate":1.0})
            self.righteye_config.update_configuration({"pipeline_rate":1.0,"detect_rate":1.0})
            self.wideangle_config.update_configuration({"pipeline_rate":20.0,"detect_rate":10.0})
            self.realsense_config.update_configuration({"pipeline_rate":20.0,"detect_rate":20.0})
            self.SetEyeContact(EyeContact.IDLE)
            self.SetLookAt(LookAt.AUDIENCE)
            self.SetMirroring(Mirroring.IDLE)
            self.SetGaze(Gaze.GAZE_AND_HEAD)


    def HandleFace(self, msg):

        self.faces[msg.cface_id] = msg
        self.last_face = msg.cface_id
        self.last_talk_ts = msg.ts

        # TEMP: if there is no current face, make this the current face
        if self.current_face_id == 0:
            self.current_face_id = msg.cface_id


    def HandleHand(self, msg):

        self.hand = msg

        self.last_hand_ts = msg.ts

        # transition from IDLE or INTERESTED to FOCUSED
        if self.state == State.IDLE or self.state == State.INTERESTED:
            self.SetState(State.FOCUSED)
            self.UpdateStateDisplay()


    def HandleSaliency(self, msg):

        self.saliencies[msg.ts] = msg

        # TEMP: if there is no current saliency vector, make this the current saliency vector
        if self.current_saliency_ts == 0:
            self.saliency_counter = 1
            self.current_saliency_ts = msg.ts

        # transition from IDLE to INTERESTED
        if self.state == State.IDLE:
            self.SetState(State.INTERESTED)
            self.UpdateStateDisplay()


    def HandleChatEvents(self, msg):

        # triggered when someone starts talking to the robot

        self.last_talk_ts = rospy.get_rostime()

        # transition from IDLE, INTERESTED or FOCUSED to LISTENING
        if self.state == State.IDLE or self.state == State.INTERESTED or self.state == State.FOCUSED:
            self.SetState(State.LISTENING)
            self.UpdateStateDisplay()


    def HandleSpeechEvents(self, msg):

        # triggered when the robot starts or stops talking

        self.last_talk_ts = rospy.get_rostime()

        if msg.data == "start":
            # transition from IDLE, INTERESTED, FOCUSED or LISTENING to SPEAKING
            if self.state == State.IDLE or self.state == State.INTERESTED or self.state == State.FOCUSED or self.state == State.LISTENING:
                self.SetState(State.SPEAKING)
                self.UpdateStateDisplay()

        elif msg.data == "stop":
            # transition from SPEAKING to IDLE
            if self.state == State.SPEAKING:
                self.SetState(State.IDLE)
                self.UpdateStateDisplay()



    def HandleAudioDirection(self, msg):

        # use to correlate with person speaking to select correct current face
        ()


    def HandleMotion(self, msg):

        # use to trigger awareness of people even without seeing them
        ()
Example #12
0
class Attention:
    def InitCounter(self, counter, minmax):
        try:
            min_v = min(int(getattr(self, "{}_min".format(minmax))),
                        int(getattr(self, "{}_max".format(minmax))))
            max_v = max(int(getattr(self, "{}_min".format(minmax))),
                        int(getattr(self, "{}_max".format(minmax))))
            val = random.randint(int(min_v * self.synthesizer_rate),
                                 int(max_v * self.synthesizer_rate))
        except:
            val = 1
        setattr(self, "{}_counter".format(counter), val)

    def __getattr__(self, item):
        # Allow configuration attributes to be accessed directly
        if self.config is None:
            raise AttributeError
        return self.config.__getattr__(item)

    def __init__(self):

        # create lock
        self.lock = threading.Lock()

        self.robot_name = rospy.get_param("/robot_name")
        self.eyecontact = 0
        self.lookat = 0
        self.mirroring = 0
        # By default look with head and eyes
        self.gaze = 2
        # setup face, hand and saliency structures
        self.state = State()
        self.current_face_index = -1  # index to current face
        self.wanted_face_id = 0  # ID for wanted face
        self.current_saliency_index = -1  # index of current saliency vector
        self.current_eye = 0  # current eye (0 = left, 1 = right, 2 = mouth)
        self.interrupted_state = LookAt.IDLE  # which state was interrupted to look at all faces
        self.interrupting = False  # LookAt state is currently interrupted to look at all faces

        self.gaze_delay_counter = 0  # delay counter after with gaze or head follows head or gaze
        self.gaze_pos = None  # current gaze position
        # counter to run if face not visible
        self.no_face_counter = 0
        self.no_switch_counter = 0
        self.last_pose_counter = 0
        self.last_pose = None
        self.eyecontact_state = EyeContact.TRIANGLE
        self.tf_listener = tf.TransformListener(False,
                                                rospy.Duration.from_sec(1))

        # tracks last face by fsdk_id, if changes informs eye tracking to pause
        self.last_target = -2
        rospy.Subscriber('/{}/perception/state'.format(self.robot_name), State,
                         self.HandleState)

        self.head_focus_pub = rospy.Publisher('/blender_api/set_face_target',
                                              Target,
                                              queue_size=1)
        self.gaze_focus_pub = rospy.Publisher('/blender_api/set_gaze_target',
                                              Target,
                                              queue_size=1)
        self.expressions_pub = rospy.Publisher(
            '/blender_api/set_emotion_state', EmotionState, queue_size=1)
        self.gestures_pub = rospy.Publisher('/blender_api/set_gesture',
                                            SetGesture,
                                            queue_size=1)
        self.animationmode_pub = rospy.Publisher(
            '/blender_api/set_animation_mode', UInt8, queue_size=1)
        self.setpau_pub = rospy.Publisher('/blender_api/set_pau',
                                          pau,
                                          queue_size=1)
        # Topic to publish target changes
        self.current_target_pub = rospy.Publisher('/behavior/current_target',
                                                  Int64,
                                                  queue_size=5,
                                                  latch=True)

        self.synthesizer_rate = 30

        self.hand_events_pub = rospy.Publisher('/hand_events',
                                               String,
                                               queue_size=1)

        # Timer, started by config server
        self.timer = None

        # start dynamic reconfigure server
        self.configs_init = False
        self.config_server = Server(AttentionConfig,
                                    self.HandleConfig,
                                    namespace='/current/attention')

        # API calls (when the overall state is not automatically setting these values via dynamic reconfigure)
        self.eyecontact_sub = rospy.Subscriber(
            '/behavior/attention/api/eyecontact', UInt8, self.HandleEyeContact)
        self.lookat_sub = rospy.Subscriber('/behavior/attention/api/lookat',
                                           APILookAt, self.HandleLookAt)
        self.mirroring_sub = rospy.Subscriber(
            '/behavior/attention/api/mirroring', UInt8, self.HandleMirroring)
        self.gaze_sub = rospy.Subscriber('/behavior/attention/api/gaze', UInt8,
                                         self.HandleGaze)

    def ChangeTarget(self, id=None):
        if id is None:
            id = int(time.time() * 10)
        if id <> self.last_target:
            self.last_target = id
            self.current_target_pub.publish(id)

    def UpdateStateDisplay(self):
        self.config_server.update_configuration({
            "eyecontact_state": self.eyecontact,
            "lookat_state": self.lookat,
            "mirroring_state": self.mirroring,
            "gaze_state": self.gaze,
        })

    def HandleConfig(self, config, level):
        with self.lock:
            self.config = config
            # and set the states for each state machine
            #self.SetEyeContact(config.eyecontact_state)
            self.SetLookAt(config.lookat_state)
            # self.SetMirroring(config.mirroring_state)
            # self.SetGaze(config.gaze_state)
            # Counters
            if not self.configs_init:
                self.timer = rospy.Timer(
                    rospy.Duration.from_sec(1.0 / self.synthesizer_rate),
                    self.HandleTimer)
                # self.InitCounter("saliency","saliency_time")
                self.InitCounter("faces", "faces_time")
                self.InitCounter("region", "region_time")
                # self.InitCounter("rest", "rest_time")

            self.configs_init = True
        return config

    def getBlenderPos(self, pos, ts, frame_id):
        if frame_id == 'blender':
            return pos
        else:
            ps = PointStamped()
            ps.header.seq = 0
            ps.header.stamp = ts
            ps.header.frame_id = frame_id
            ps.point.x = pos.x
            ps.point.y = pos.y
            ps.point.z = pos.z
            if self.tf_listener.canTransform("blender", frame_id, ts):
                pst = self.tf_listener.transformPoint("blender", ps)
                return pst.point
            else:
                raise Exception("tf from robot to blender did not work")

    def SetGazeFocus(self, pos, speed, ts, frame_id='robot'):
        try:
            pos = self.getBlenderPos(pos, ts, frame_id)
            msg = Target()
            msg.x = max(0.3, pos.x)
            msg.y = pos.y if not math.isnan(pos.y) else 0
            msg.z = pos.z if not math.isnan(pos.z) else 0
            msg.z = max(-0.3, min(0.3, msg.z))
            msg.speed = speed
            self.gaze_focus_pub.publish(msg)
        except Exception as e:
            logger.warn("Gaze focus exception: {}".format(e))

    def SetHeadFocus(self, pos, speed, ts, frame_id='robot'):
        try:
            pos = self.getBlenderPos(pos, ts, frame_id)
            msg = Target()
            msg.x = max(0.3, pos.x)
            msg.y = pos.y if not math.isnan(pos.y) else 0
            msg.z = pos.z if not math.isnan(pos.z) else 0
            msg.z = max(-0.3, min(0.3, msg.z))
            msg.speed = speed
            self.head_focus_pub.publish(msg)
        except Exception as e:
            logger.warn("Head focus exception: {}".format(e))

    def UpdateGaze(self, pos, ts, frame_id="robot"):

        self.gaze_pos = pos

        if self.gaze == Gaze.GAZE_ONLY:
            self.SetGazeFocus(pos, 5.0, ts, frame_id)

        elif self.gaze == Gaze.HEAD_ONLY:
            self.SetHeadFocus(pos, self.head_speed, ts, frame_id)

        elif self.gaze == Gaze.GAZE_AND_HEAD:
            self.SetGazeFocus(pos, 5.0, ts, frame_id)
            self.SetHeadFocus(pos, self.head_speed, ts, frame_id)

        elif self.gaze == Gaze.GAZE_LEADS_HEAD:
            self.SetGazeFocus(pos, 5.0, ts, frame_id)

        elif self.gaze == Gaze.HEAD_LEADS_GAZE:
            self.SetHeadFocus(pos, self.head_speed, ts, frame_id)

    def SelectNextFace(self):
        # switch to the next (or first) face
        if self.state is None or len(self.state.faces) == 0:
            # there are no faces, so select none
            self.current_face_index = -1
            return

        if self.lookat == LookAt.NEAREST_FACE:
            self.current_face_index, f = min(
                enumerate(self.state.faces),
                key=lambda f: (f[1].position.x**2 + f[1].position.y**2))
        else:
            # Pick random or any other face from list
            if self.current_face_index == -1:
                self.current_face_index = 0
            else:
                self.current_face_index += 1
                if self.current_face_index >= len(self.state.faces):
                    self.current_face_index = 0

    def SelectNextPose(self):
        # switch to the next (or first) face
        if self.state is None or len(self.state.poses) == 0:
            # there are no faces, so select none
            self.last_pose_counter = 0
            return False
        # Select nearest pose to the robot, otherwise select nearest pose to previous
        if self.last_pose is None or self.last_pose_counter <= 0:
            i, p = min(enumerate(self.state.poses),
                       key=lambda f: (f[1].position.x**2 + f[1].position.y**2))
            self.last_pose_counter = self.synthesizer_rate * self.min_time_between_targets
        else:
            i, p = min(enumerate(self.state.poses),
                       key=lambda f:
                       ((self.last_pose.position.x - f[1].position.x)**2 +
                        (self.last_pose.position.y - f[1].position.y)**2 +
                        (self.last_pose.position.z - f[1].position.z)**2))
            self.last_pose_counter -= 1
        self.last_pose = p
        return p

    def SelectNextSalientPoint(self):

        # switch to the next (or first) saliency vector
        if (self.state == None):
            self.current_saliency_index = -1
        if len(self.state.salientpoints) == 0:
            # there are no saliency vectors, so select none
            self.current_saliency_index = -1
            return
        if self.current_saliency_index == -1:
            self.current_saliency_index = 0
        else:
            self.current_saliency_index += 1
            if self.current_saliency_index >= len(self.state.salientpoints):
                self.current_saliency_index = 0

    def SelectNextRegion(self):
        # Check if setperformance has set regions
        regions = rospy.get_param(
            "/{}/performance_regions".format(self.robot_name), {})
        if len(regions) == 0:
            regions = rospy.get_param("/{}/regions".format(self.robot_name),
                                      {})
        point = AttentionRegion.get_point_from_regions(
            regions, REGIONS[self.attention_region])
        return Point(x=point['x'], y=point['y'], z=point['z'])

    def StepLookAtFace(self, ts):

        if self.current_face_index == -1:
            raise Exception("No face available")

        try:
            curface = self.state.faces[self.current_face_index]
        except:
            raise Exception("No face available")

        self.ChangeTarget(curface.fsdk_id)
        face_pos = curface.position

        # ==== handle eyecontact (only for LookAt.ONE_FACE and LookAt.ALL_FACES)

        # calculate where left eye, right eye and mouth are on the current face
        left_eye_pos = Point()
        right_eye_pos = Point()
        mouth_pos = Point()

        # all are 5cm in front of the center of the face
        left_eye_pos.x = face_pos.x - 0.05
        right_eye_pos.x = face_pos.x - 0.05
        mouth_pos.x = face_pos.x - 0.05

        left_eye_pos.y = face_pos.y + 0.03  # left eye is 3cm to the left of the center
        right_eye_pos.y = face_pos.y - 0.03  # right eye is 3cm to the right of the center
        mouth_pos.y = face_pos.y  # mouth is dead center

        left_eye_pos.z = face_pos.z + 0.06  # left eye is 6cm above the center
        right_eye_pos.z = face_pos.z + 0.06  # right eye is 6cm above the center
        mouth_pos.z = face_pos.z - 0.04  # mouth is 4cm below the center

        if self.eyecontact == EyeContact.IDLE:
            # look at center of the head
            self.UpdateGaze(face_pos, ts)

        elif self.eyecontact == EyeContact.LEFT_EYE:
            # look at left eye
            self.UpdateGaze(left_eye_pos, ts)

        elif self.eyecontact == EyeContact.RIGHT_EYE:
            # look at right eye
            self.UpdateGaze(right_eye_pos, ts)

        elif self.eyecontact == EyeContact.BOTH_EYES:
            # switch between eyes back and forth
            self.eyes_counter -= 1
            if self.eyes_counter == 0:
                self.InitCounter("eyes", "eyes_time")
                if self.current_eye == 1:
                    self.current_eye = 0
                else:
                    self.current_eye = 1
            # look at that eye
            if self.current_eye == 0:
                cur_eye_pos = left_eye_pos
            else:
                cur_eye_pos = right_eye_pos
            self.UpdateGaze(cur_eye_pos, ts)

        elif self.eyecontact == EyeContact.TRIANGLE:
            # cycle between eyes and mouth
            self.eyes_counter -= 1
            if self.eyes_counter == 0:
                self.InitCounter("eyes", "eyes_time")
                if self.current_eye == 2:
                    self.current_eye = 0
                else:
                    self.current_eye += 1
            # look at that eye
            if self.current_eye == 0:
                cur_eye_pos = left_eye_pos
            elif self.current_eye == 1:
                cur_eye_pos = right_eye_pos
            elif self.current_eye == 2:
                cur_eye_pos = mouth_pos
            self.UpdateGaze(cur_eye_pos, ts)

        # mirroring
        # msg = pau()
        # msg.m_coeffs = []
        # msg.m_shapekeys = []

        # if self.mirroring == Mirroring.EYEBROWS or self.mirroring == Mirroring.EYES or self.mirroring == Mirroring.MOUTH_EYEBROWS or self.mirroring == Mirroring.ALL:
        #    # mirror eyebrows
        #    left_brow = curface.left_brow
        #    right_brow = curface.right_brow
        #    msg.m_coeffs.append("brow_outer_UP.L")
        #    msg.m_shapekeys.append(left_brow)
        #    msg.m_coeffs.append("brow_inner_UP.L")
        #    msg.m_shapekeys.append(left_brow * 0.8)
        #    msg.m_coeffs.append("brow_outer_DN.L")
        #    msg.m_shapekeys.append(1.0 - left_brow)
        #    msg.m_coeffs.append("brow_outer_up.R")
        #    msg.m_shapekeys.append(right_brow)
        #    msg.m_coeffs.append("brow_inner_UP.R")
        #    msg.m_shapekeys.append(right_brow * 0.8)
        #    msg.m_coeffs.append("brow_outer_DN.R")
        #    msg.m_shapekeys.append(1.0 - right_brow)

        # if self.mirroring == Mirroring.EYELIDS or self.mirroring == Mirroring.EYES or self.mirroring == Mirroring.MOUTH_EYELIDS or self.mirroring == Mirroring.ALL:
        #    # mirror eyelids
        #    eyes_closed = ((1.0 - curface.left_eyelid) + (1.0 - curface.right_eyelid)) / 2.0
        #    msg.m_coeffs.append("eye-blink.UP.R")
        #    msg.m_shapekeys.append(eyes_closed)
        #    msg.m_coeffs.append("eye-blink.UP.L")
        #    msg.m_shapekeys.append(eyes_closed)
        #    msg.m_coeffs.append("eye-blink.LO.R")
        #    msg.m_shapekeys.append(eyes_closed)
        #    msg.m_coeffs.append("eye-blink.LO.L")
        #    msg.m_shapekeys.append(eyes_closed)

        # if self.mirroring == Mirroring.MOUTH or self.mirroring == Mirroring.MOUTH_EYEBROWS or self.mirroring == Mirroring.MOUTH_EYELIDS:
        #    # mirror mouth
        #    mouth_open = curface.mouth_open
        #    msg.m_coeffs.append("lip-JAW.DN")
        #    msg.m_shapekeys.append(mouth_open)

        # if self.mirroring != Mirroring.IDLE:
        #    self.StartPauMode()
        #    self.setpau_pub.publish(msg)

    def HandleTimer(self, data):
        looking_at_face = False
        with self.lock:
            if not self.configs_init:
                return False
            if not self.enable_flag:

                self.ChangeTarget(-1)
                return False

            # this is the heart of the synthesizer, here the lookat and eyecontact state machines take care of where the robot is looking, and random expressions and gestures are triggered to look more alive (like RealSense Tracker)
            ts = data.current_expected
            #If nowhere to look, straighten the head
            idle_point = Point(x=1, y=0, z=0)
            # Fallback to look at region
            region = False
            # Fallback o rest
            idle = False
            # ==== handle lookat
            if self.lookat == LookAt.AVOID:
                # TODO: find out where there is no saliency, hand or face
                # TODO: head_focus_pub
                ()

            if self.lookat == LookAt.HOLD or self.lookat == LookAt.IDLE:
                # Do nothing
                pass

            # if self.lookat == LookAt.SALIENCY:
            #     self.saliency_counter -= 1
            #     if self.saliency_counter == 0:
            #         self.SelectNextSalientPoint()
            #         # Reset head position if nothing happens
            #         if self.current_saliency_index == -1:
            #             self.UpdateGaze(idle_point, ts, frame_id='blender')
            #         else:
            #             # Init counter only if any salient point found
            #             self.InitCounter("saliency","saliency_time")
            #
            #     if self.current_saliency_index != -1:
            #         cursaliency = self.state.salientpoints[self.current_saliency_index]
            #         self.UpdateGaze(cursaliency.position, ts)

            elif self.lookat == LookAt.REGION:
                self.region_counter -= 1
                if self.region_counter == 0:
                    self.InitCounter("region", "region_time")
                    # SelectNextRegion returns idle point if no region set
                    point = self.SelectNextRegion()
                    # Attention points are calculated in blender frame
                    self.UpdateGaze(point, ts, frame_id='blender')
            # elif self.lookat == LookAt.POSES:
            #     pose = self.SelectNextPose()
            #     if pose:
            #         self.UpdateGaze(pose.position, ts)
            #         self.no_switch_counter = self.synthesizer_rate * self.min_time_between_targets
            #     else:
            #         self.no_switch_counter -= 1
            #         if self.no_switch_counter < 0:
            #             point = self.SelectNextRegion()
            #             self.UpdateGaze(point, ts, frame_id='blender')
            #             self.no_switch_counter = self.synthesizer_rate * self.min_time_between_targets
            else:
                if self.lookat == LookAt.ALL_FACES or self.lookat == LookAt.NEAREST_FACE or self.lookat == LookAt.POSES:
                    self.faces_counter -= 1
                    if self.faces_counter == 0:
                        self.SelectNextFace()
                        self.no_switch_counter = self.synthesizer_rate * self.min_time_between_targets
                        self.InitCounter("faces", "faces_time")
                    try:
                        # This will make sure robot will look somewhere so eye contact only should be paused
                        # It should fallback to attention region if defined or idle point
                        looking_at_face = True
                        # only look at faces after switch is allowed:
                        self.StepLookAtFace(ts)
                        # Reset after face is found
                        self.no_face_counter = 0
                    except:
                        # Look at poses if no faces are visible, and then look at region otherwise
                        pose = self.SelectNextPose()
                        self.no_face_counter -= 1
                        if pose:
                            self.UpdateGaze(pose.position, ts)
                            self.no_switch_counter = self.synthesizer_rate * self.min_time_between_targets
                        else:
                            self.no_switch_counter -= 1
                            if self.no_switch_counter <= 0:
                                if self.no_face_counter <= 0:
                                    point = self.SelectNextRegion()
                                    self.InitCounter('no_face', 'region_time')
                                    self.UpdateGaze(point,
                                                    ts,
                                                    frame_id='blender')
                                    self.no_switch_counter = self.synthesizer_rate * self.min_time_between_targets

            if self.lookat == LookAt.REGION or region:
                self.region_counter -= 1
                if self.region_counter == 0:
                    # Eye contact enabled when looking at region
                    if self.eyecontact > 0:
                        looking_at_face = True
                    self.InitCounter("region", "region_time")
                    # SelectNextRegion returns idle point if no region set
                    try:
                        point = self.SelectNextRegion()
                        # Attention points are calculated in blender frqame
                        self.UpdateGaze(point, ts, frame_id='blender')
                        # Target have been changed

                    except:
                        idle = True

            # if self.lookat == LookAt.IDLE or idle:
            #     self.rest_counter -= 1
            #     if self.rest_counter == 0:
            #         self.InitCounter("rest", "rest_time")
            #         if self.eyecontact > 0:
            #             looking_at_face = True
            #         idle_point = Point(x=1, y=random.uniform(-self.rest_range_x, self.rest_range_y)
            #                            , z=random.uniform(-self.rest_range_y, self.rest_range_y))
            #         # Attention points are calculated in blender frame
            #         self.UpdateGaze(idle_point, ts, frame_id='blender')
            #         # Target have been changed
            #         self.ChangeTarget()

            # have gaze or head follow head or gaze after a while
            if self.gaze_delay_counter > 0 and self.gaze_pos != None:

                self.gaze_delay_counter -= 1
                if self.gaze_delay_counter == 0:

                    if self.gaze == Gaze.GAZE_LEADS_HEAD:
                        self.SetHeadFocus(self.gaze_pos, self.gaze_speed, ts)
                        self.gaze_delay_counter = int(self.gaze_delay *
                                                      self.synthesizer_rate)

                    elif self.gaze == Gaze.HEAD_LEADS_GAZE:
                        self.SetGazeFocus(self.gaze_pos, self.gaze_speed, ts)
                        self.gaze_delay_counter = int(self.gaze_delay *
                                                      self.synthesizer_rate)

            # when speaking, sometimes look at all faces
            # if self.interrupt_to_all_faces:
            #
            #     if self.interrupting:
            #         self.all_faces_duration_counter -= 1
            #         if self.all_faces_duration_counter <= 0:
            #             self.interrupting = False
            #             self.InitCounter("all_faces_duration", "all_faces_duration")
            #             self.SetLookAt(self.interrupted_state)
            #             self.UpdateStateDisplay()
            #     else:
            #         self.all_faces_start_counter -= 1
            #         if self.all_faces_start_counter <= 0:
            #             self.interrupting = True
            #             self.InitCounter("all_faces_start", "all_faces_start_time")
            #             self.interrupted_state = self.lookat
            #             self.SetLookAt(LookAt.ALL_FACES)
            #             self.UpdateStateDisplay()

        if not looking_at_face:
            self.ChangeTarget(-1)

    def SetEyeContact(self, neweyecontact):

        if neweyecontact == self.eyecontact:
            return

        self.eyecontact = neweyecontact

        # if self.eyecontact == EyeContact.BOTH_EYES or self.eyecontact == EyeContact.TRIANGLE:
        #     self.InitCounter("eyes", "eyes_time")

    def SetLookAt(self, newlookat):

        if newlookat == self.lookat:
            return

        self.lookat = newlookat

        if self.lookat == LookAt.SALIENCY:
            self.InitCounter("saliency", "saliency_time")

        # elif self.lookat == LookAt.ONE_FACE:
        #     self.InitCounter("eyes", "eyes_time")

        elif self.lookat == LookAt.ALL_FACES:
            self.InitCounter("faces", "faces_time")
            # self.InitCounter("eyes", "eyes_time")

        elif self.lookat == LookAt.REGION:
            self.InitCounter("region", "region_time")

        # elif self.lookat == LookAt.IDLE:
        #     self.InitCounter("rest", "rest_time")

    def StartPauMode(self):

        mode = UInt8()
        mode.data = 148
        self.animationmode_pub.publish(mode)

    def StopPauMode(self):

        mode = UInt8()
        mode.data = 0
        self.animationmode_pub.publish(mode)

    def SetMirroring(self, newmirroring):
        pass
        # logger.warn("SetMirroring {}".format(newmirroring))
        # if newmirroring == self.mirroring: return
        #
        # self.mirroring = newmirroring
        #
        # if self.mirroring == Mirroring.IDLE:
        #     self.StopPauMode()
        # else:
        #     self.StartPauMode()

    def SetGaze(self, newgaze):

        if newgaze == self.gaze:
            return

        self.gaze = newgaze

        if self.gaze == Gaze.GAZE_LEADS_HEAD or self.gaze == Gaze.HEAD_LEADS_GAZE:
            self.gaze_delay_counter = int(self.gaze_delay *
                                          self.synthesizer_rate)

    def HandleState(self, data):
        with self.lock:

            self.state = data

            # if there is a wanted face, try to find it and use that
            if self.wanted_face_id != 0:
                index = 0
                self.current_face_index = -1
                for face in self.state.faces:
                    if face.id == self.wanted_face_id:
                        self.current_face_index = index
                        break
                    index += 1

            # otherwise, just make sure current_face_index is valid
            elif (self.current_face_index >= len(
                    self.state.faces)) or (self.current_face_index == -1):
                # Only try to look at new faces once there are no
                if self.no_switch_counter <= 0:
                    self.SelectNextFace()
            # TODO: it's better to have the robot look at the same ID, regardless of which index in the stateface list

            # # if there is no current saliency or the current saliency is out of range, select a new current saliency
            # if (self.current_saliency_index >= len(self.state.salientpoints)) or (self.current_saliency_index == -1):
            #     self.SelectNextSaliency()

    def HandleEyeContact(self, data):

        with self.lock:

            self.SetEyeContact(data)

        self.UpdateStateDisplay()

    def HandleLookAt(self, data):

        with self.lock:

            if data.mode == LookAt.ONE_FACE:  # if client wants to the robot to look at one specific face (even if the face temporarily disappears from the state)
                self.wanted_face_id = data.id
            elif data.mode == LookAt.REGION:
                self.attention_region = data.id
            else:
                self.wanted_face_id = 0
            self.SetLookAt(self, data.mode)

        self.UpdateStateDisplay()

    def HandleMirroring(self, data):

        with self.lock:

            self.SetMirroring(data)

        self.UpdateStateDisplay()

    def HandleGaze(self, data):

        with self.lock:

            self.SetGaze(data)

        self.UpdateStateDisplay()
Example #13
0
class Robot(HierarchicalMachine):

    state_cls = InteractiveState

    def __init__(self):
        # Wait for service to set initial params
        rospy.wait_for_service('/current/attention/set_parameters')
        rospy.wait_for_service('/current/animations/set_parameters')
        # Current state config
        self.state_config = None
        self.config = None
        self.starting = True
        # Current TTS mode: chatbot_responses - auto, web_sresponses - operator
        self.current_tts_mode = "chatbot_responses"
        # Controls states_timeouts
        self._state_timer = None
        # ROS Topics and services
        self.robot_name = rospy.get_param('/robot_name')
        # ROS publishers
        self.topics = {
            'running_performance':
            rospy.Publisher('/running_performance', String, queue_size=1),
            'chatbot_speech':
            rospy.Publisher('/{}/chatbot_speech'.format(self.robot_name),
                            ChatMessage,
                            queue_size=10),
            'soma_pub':
            rospy.Publisher('/blender_api/set_soma_state',
                            SomaState,
                            queue_size=10),
            'state_pub':
            rospy.Publisher('/current_state', String, latch=True),
        }
        # ROS Subscribers
        self.subscribers = {
            'speech':
            rospy.Subscriber('/{}/speech'.format(self.robot_name), ChatMessage,
                             self.speech_cb),
            'running_performance':
            rospy.Subscriber('/performances/running_performance', String,
                             self.performance_cb),
            'performance_events':
            rospy.Subscriber('/performances/events', PerformanceEvent,
                             self.performance_events_cb),
            'speech_events':
            rospy.Subscriber('/{}/speech_events'.format(self.robot_name),
                             String, self.speech_events_cb),
            'chat_events':
            rospy.Subscriber('/{}/chat_events'.format(self.robot_name), String,
                             self.chat_events_cb),
            'state_switch':
            rospy.Subscriber('/state_switch', String, self.state_callback),
            'tts_mux':
            rospy.Subscriber('/{}/tts_mux/selected'.format(self.robot_name),
                             String, self.tts_mux_cb)
        }
        # ROS Services
        # Wait for all services to become available
        rospy.wait_for_service('/blender_api/set_param')
        rospy.wait_for_service('/performances/current')
        # All services required.
        self.services = {
            'performance_runner':
            rospy.ServiceProxy('/performances/run_full_performance',
                               srv.RunByName),
            'blender_param':
            rospy.ServiceProxy('/blender_api/set_param', SetParam),
        }
        # Configure clients
        self.clients = {
            'attention':
            dynamic_reconfigure.client.Client('/current/attention',
                                              timeout=0.1),
            'animation':
            dynamic_reconfigure.client.Client('/current/animations',
                                              timeout=0.1),
        }
        # robot properties
        self.props = {
            'disable_attention': None,
            'disable_animations': None,
            'disable_blinking': None,
            'disable_saccades': None,
            'disable_keepalive': None,
        }

        self._before_presentation = ''
        self._current_performance = None
        # State server mostly used for checking current states settings
        self.state_server = Server(StateConfig,
                                   self.state_config_callback,
                                   namespace='/current/state_settings')
        # Machine starts
        HierarchicalMachine.__init__(
            self,
            states=STATES,
            transitions=TRANSITIONS,
            initial='idle',
            ignore_invalid_triggers=True,
            after_state_change=self.state_changed,
            before_state_change=self.on_before_state_change)
        # Main param server
        self.server = Server(StatesConfig,
                             self.config_callback,
                             namespace='/behavior/behavior_settings')
        self.faces_db_file = None
        self.known_faces = {}
        self.faces_last_update = time.time()
        # Faces last seen database:
        try:
            assemblies = rospy.get_param('/assemblies')
            assembly = assemblies[0] if self.robot_name in assemblies[
                0] else assemblies[1]
            self.faces_db_file = os.path.join(assembly, 'known_faces.yaml')
            with open(self.faces_db_file, 'r') as stream:
                try:
                    self.known_faces = yaml.load(stream)
                except yaml.YAMLError as exc:
                    logger.warn(exc)
        except Exception as e:
            logger.error("Cant load the known faces {}".format(e))
        rospy.Subscriber('/{}/perception/state'.format(self.robot_name), State,
                         self.perception_state_cb)

    def perception_state_cb(self, msg):
        # Ignore perception while no interacting
        if not self.state == 'interacting_interested':
            return
        if len(msg.faces) > 0:
            for f in msg.faces:
                if f.first_name is not "":
                    name = f.first_name
                    # repeating face, enroll but don't play any timeline
                    if name not in self.known_faces.keys():
                        self.known_faces[name] = {'last_seen': time.time()}
                        self.sync_faces()
                    else:
                        last_seen = self.known_faces[name]['last_seen']
                        rospy.set_param('/last_known_face', name)
                        current_time = time.time()
                        self.known_faces[name]['last_seen'] = current_time
                        # Update every 10s
                        if self.faces_last_update + 10 < time.time():
                            self.sync_faces()
                        if current_time - last_seen > 60 * 60 * 24:
                            performance_kwd = 'faceid_{}_{}'.format(
                                'long', name)
                            performances = self.find_performance_by_speech(
                                performance_kwd)
                            if len(performances) > 0:
                                self.services['performance_runner'](
                                    random.choice(performances))
                                return
                        if current_time - last_seen > 60 * 15:
                            performance_kwd = 'faceid_{}_{}'.format(
                                'short', name)
                            performances = self.find_performance_by_speech(
                                performance_kwd)
                            if len(performances) > 0:
                                self.services['performance_runner'](
                                    random.choice(performances))
                                return
                        if current_time - last_seen > 60 * 60 * 24:
                            performance_kwd = 'faceid_{}_unknown'.format(
                                'long', name)
                            performances = self.find_performance_by_speech(
                                performance_kwd)
                            if len(performances) > 0:
                                self.services['performance_runner'](
                                    random.choice(performances))
                                return
                        if current_time - last_seen > 60 * 15:
                            performance_kwd = 'faceid_{}_unknown'.format(
                                'short', name)
                            performances = self.find_performance_by_speech(
                                performance_kwd)
                            if len(performances) > 0:
                                self.services['performance_runner'](
                                    random.choice(performances))
                                return

    def sync_faces(self):
        try:
            self.faces_last_update = time.time()
            with open(self.faces_db_file, 'w') as outfile:
                yaml.dump(self.known_faces, outfile, default_flow_style=False)
        except:
            pass

    # Calls after each state change to apply new configs
    def state_changed(self):
        rospy.set_param('/current_state', self.state)
        self.topics['state_pub'].publish(String(self.state))
        # State object
        state = self.get_state(self.state)
        logger.warn(self.state)
        if state.attention_config:
            self.clients['attention'].update_configuration(
                state.attention_config)
        if state.animations_config:
            self.clients['animation'].update_configuration(
                state.animations_config)
        # Aply general behavior for states
        if state.state_config:
            self.state_server.update_configuration(state.state_config)

    def state_callback(self, msg):
        state = msg.data
        # Only 3 main switches for now
        try:
            if state == 'idle':
                self.go_idle()
            elif state == 'presenting':
                self.start_presentation()
            elif state == 'interacting':
                self.start_interacting()
        except Exception as e:
            logger.error(e)

    def state_config_callback(self, config, level):
        # Apply properties
        self.disable_animations = config.disable_animations
        self.disable_attention = config.disable_attention
        self.disable_blinking = config.disable_blinking
        self.disable_keepalive = config.disable_keepalive
        self.disable_saccades = config.disable_saccades
        self.state_config = config
        return config

    #  ROS Callback methods
    def config_callback(self, config, level):
        self.config = config
        # Set correct init state on loading. Will set the parameters as well
        if self.starting:
            self.starting = False
            if self.config['init_state'] == 'interacting':
                self.start_interacting()
            if self.config['init_state'] == 'presentation':
                self.start_presentation()
        return config

    # Handles all speech inputs
    def speech_cb(self, msg):
        try:
            speech = str(msg.utterance).lower()
            # Check if performance is not waiting for same keyword to continue in timeline
            if self.is_presenting(allow_substates=True
                                  ) and self.config['chat_during_performance']:
                keywords = rospy.get_param('/performances/keywords_listening',
                                           False)
                # Don't pass the keywords if pause node waits for same keyword (i.e resume performance).
                if keywords and pause.event_matched(keywords, msg.utterance):
                    return
            # Allow trigger performances by keywords
            if self.state_config.performances_by_keyword:
                performances = self.find_performance_by_speech(speech)
                # Split between performances for general modes and analysis
                analysis_performances = [
                    p for p in performances
                    if ('shared/analysis' in p or 'robot/analysis' in p)
                ]
                for a in analysis_performances:
                    performances.remove(a)
                if performances and self.state != 'analysis':
                    self.services['performance_runner'](
                        random.choice(performances))
                    return
                elif analysis_performances and self.state == 'analysis':
                    self.services['performance_runner'](
                        random.choice(analysis_performances))
                    return

            # If chat is not enabled for specific state ignore it
            if not self.state_config.chat_enabled:
                return
            # Need to respond to speech
            # Check if state allows chat
            self.speech_finished()
            self.topics['chatbot_speech'].publish(msg)
        except Exception as e:
            logger.error(e)
            self.topics['chatbot_speech'].publish(msg)

    def speech_events_cb(self, msg):
        if msg.data == 'start':
            # Robot starts talking
            self.start_talking()
        if msg.data == 'stop':
            # Talking finished, robot starts listening
            self.finish_talking()

    def chat_events_cb(self, msg):
        if msg.data == 'speechstart':
            self.speech_start()

    def tts_mux_cb(self, msg):
        self.current_tts_mode = msg.data

    def find_performance_by_speech(self, speech):
        """ Finds performances which one of keyword matches"""
        performances = []
        for performance, keywords in self.get_keywords().items():
            if self.performance_keyword_match(keywords, speech):
                performances.append(performance)
        return performances

    @staticmethod
    def performance_keyword_match(keywords, input):
        for keyword in keywords:
            if not keyword:
                continue
            # Currently only simple matching
            if re.search(r"\b{}\b".format(keyword), input,
                         flags=re.IGNORECASE):
                return True
        return False

    def get_keywords(self, performances=None, keywords=None, path='.'):
        if performances is None:
            performances = rospy.get_param(
                os.path.join('/', self.robot_name, 'webui/performances'))
            keywords = {}

        if 'properties' in performances and 'keywords' in performances[
                'properties']:
            keywords[path] = performances['properties']['keywords']

        for key, value in performances.items():
            if key != 'properties':
                self.get_keywords(performances[key], keywords,
                                  os.path.join(path, key).strip('./'))

        return keywords

    def performance_cb(self, msg):
        try:
            # Track current performance
            self._current_performance = msg.data
            if msg.data == "null":
                if self._before_presentation:
                    if self._before_presentation.startswith("interacting"):
                        self.start_interacting()
                    # Might be usefull if we do idle out
                    elif self._before_presentation.startswith("idle"):
                        self.to_idle()
                    else:
                        self.timeline_finished()
                else:
                    # Stay in presentation
                    self.timeline_finished()
            else:
                # New performance loaded
                self._before_presentation = self.state
                self.start_presentation()
        except Exception as e:
            logger.error(e)

    def performance_events_cb(self, msg):
        if msg.event in ['resume', 'running']:
            self.run_timeline()
        elif msg.event in ['paused']:
            self.timeline_paused()
        else:
            self.timeline_finished()

    def timeline_finished(self):
        pass

    def need_to_think(self):
        # Think in operator control mode (semi automatic or manual)
        return self.config.thinking_operator and self.current_tts_mode == 'web_responses'

    def on_enter_interacting_listening(self):
        # Listen only for some time
        self._state_timer = threading.Timer(self.config.listening_time,
                                            self.finish_listening)
        self._state_timer.start()

    def on_enter_interacting_thinking(self):
        # If robot doesnt start speaking go back to listening
        self._state_timer = threading.Timer(self.config.thinking_time,
                                            self.could_think_of_anything)
        self._state_timer.start()

    def on_before_state_change(self):
        # Clean state timer
        if self._state_timer:
            try:
                logger.warn("STOP")
                self._state_timer.cancel()
                self._state_timer = False
            except Exception as e:
                logger.warn(e)
                pass

    @property
    def disable_attention(self):
        return self.props['disable_attention']

    @disable_attention.setter
    def disable_attention(self, val):
        if self.props['disable_attention'] != val:
            self.props['disable_attention'] = val
            try:
                self.clients['attention'].update_configuration(
                    {'enable_flag': not val})
            except Exception as e:
                logger.errot(e)

    @property
    def disable_animations(self):
        return self.props['disable_animations']

    @disable_animations.setter
    def disable_animations(self, val):
        if self.props['disable_animations'] != val:
            self.props['disable_animations'] = val
            try:
                logger.warn("Aniamtions disabled: {}".format(val))
                self.clients['animation'].update_configuration(
                    {'enable_flag': not val})
            except Exception as e:
                logger.error(e)

    @property
    def disable_blinking(self):
        return self.props['disable_blinking']

    @disable_blinking.setter
    def disable_blinking(self, val):
        if self.props['disable_blinking'] != val:
            try:
                self.services['blender_param'](
                    "bpy.data.scenes[\"Scene\"].actuators.ACT_blink_randomly.HEAD_PARAM_enabled",
                    str(not val))
            except Exception as e:
                logger.error(e)

    @property
    def disable_saccades(self):
        return self.props['disable_saccades']

    @disable_saccades.setter
    def disable_saccades(self, val):
        if self.props['disable_saccades'] != val:
            try:
                self.services['blender_param'](
                    "bpy.data.scenes[\"Scene\"].actuators.ACT_saccade.HEAD_PARAM_enabled",
                    str(not val))
            except Exception as e:
                logger.error(e)

    @property
    def disable_keepalive(self):
        return self.props['disable_keepalive']

    @disable_keepalive.setter
    def disable_keepalive(self, val):
        if self.props['disable_keepalive'] != val:
            self.props['disable_keepalive'] = val
            try:
                magnitude = 0.0 if val else 1.0
                self.topics['soma_pub'].publish(
                    self._get_soma('normal', magnitude))
                self.topics['soma_pub'].publish(
                    self._get_soma('breathing', magnitude))
                self.topics['soma_pub'].publish(
                    self._get_soma('normal-saccades', magnitude))
            except Exception as e:
                logger.error(e)

    @staticmethod
    def _get_soma(name, magnitude):
        """ Speech"""
        s = SomaState()
        s.name = name
        s.ease_in.secs = 0
        s.ease_in.nsecs = 0.1 * 1000000000
        s.magnitude = magnitude
        s.rate = 1
        return s
Example #14
0
def main():
    rospy.init_node('ur_driver', disable_signals=True)
    if rospy.get_param("use_sim_time", False):
        rospy.logwarn("use_sim_time is set!!!")
    
    global prevent_programming
    reconfigure_srv = Server(URDriverConfig, reconfigure_callback)
    
    prefix = rospy.get_param("~prefix", "")
    print "Setting prefix to %s" % prefix
    global joint_names
    joint_names = [prefix + name for name in JOINT_NAMES]

    # Parses command line arguments
    parser = optparse.OptionParser(usage="usage: %prog robot_hostname [reverse_port]")
    (options, args) = parser.parse_args(rospy.myargv()[1:])
    if len(args) < 1:
        parser.error("You must specify the robot hostname")
    elif len(args) == 1:
        robot_hostname = args[0]
        reverse_port = DEFAULT_REVERSE_PORT
    elif len(args) == 2:
        robot_hostname = args[0]
        reverse_port = int(args[1])
        if not (0 <= reverse_port <= 65535):
                parser.error("You entered an invalid port number")
    else:
        parser.error("Wrong number of parameters")

    # Reads the calibrated joint offsets from the URDF
    global joint_offsets
    joint_offsets = load_joint_offsets(joint_names)
    if len(joint_offsets) > 0:
        rospy.loginfo("Loaded calibration offsets from urdf: %s" % joint_offsets)
    else:
        rospy.loginfo("No calibration offsets loaded from urdf")

    # Reads the maximum velocity
    # The max_velocity parameter is only used for debugging in the ur_driver. It's not related to actual velocity limits
    global max_velocity
    max_velocity = rospy.get_param("~max_velocity", MAX_VELOCITY) # [rad/s] 
    rospy.loginfo("Max velocity accepted by ur_driver: %s [rad/s]" % max_velocity)
    
    # Reads the minimum payload
    global min_payload
    min_payload = rospy.get_param("~min_payload", MIN_PAYLOAD)
    # Reads the maximum payload
    global max_payload
    max_payload = rospy.get_param("~max_payload", MAX_PAYLOAD)
    rospy.loginfo("Bounds for Payload: [%s, %s]" % (min_payload, max_payload))
    

    # Sets up the server for the robot to connect to
    server = TCPServer(("", reverse_port), CommanderTCPHandler)
    thread_commander = threading.Thread(name="CommanderHandler", target=server.serve_forever)
    thread_commander.daemon = True
    thread_commander.start()

    with open(roslib.packages.get_pkg_dir('ur_driver') + '/prog') as fin:
        program = fin.read() % {"driver_hostname": get_my_ip(robot_hostname, PORT), "driver_reverseport": reverse_port}
    connection = URConnection(robot_hostname, PORT, program)
    connection.connect()
    connection.send_reset_program()
    
    connectionRT = URConnectionRT(robot_hostname, RT_PORT)
    connectionRT.connect()
    
    set_io_server()
    
    service_provider = None
    action_server = None
    try:
        while not rospy.is_shutdown():
            # Checks for disconnect
            if getConnectedRobot(wait=False):
                time.sleep(0.2)
                try:
                    prevent_programming = rospy.get_param("~prevent_programming")
                    update = {'prevent_programming': prevent_programming}
                    reconfigure_srv.update_configuration(update)
                except KeyError, ex:
                    print "Parameter 'prevent_programming' not set. Value: " + str(prevent_programming)
                    pass
                if prevent_programming:
                    print "Programming now prevented"
                    connection.send_reset_program()
            else:
                print "Disconnected.  Reconnecting"
                if action_server:
                    action_server.set_robot(None)

                rospy.loginfo("Programming the robot")
                while True:
                    # Sends the program to the robot
                    while not connection.ready_to_program():
                        print "Waiting to program"
                        time.sleep(1.0)
                    try:
                        prevent_programming = rospy.get_param("~prevent_programming")
                        update = {'prevent_programming': prevent_programming}
                        reconfigure_srv.update_configuration(update)
                    except KeyError, ex:
                        print "Parameter 'prevent_programming' not set. Value: " + str(prevent_programming)
                        pass
                    connection.send_program()

                    r = getConnectedRobot(wait=True, timeout=1.0)
                    if r:
                        break
                rospy.loginfo("Robot connected")

                #provider for service calls
                if service_provider:
                    service_provider.set_robot(r)
                else:
                    service_provider = URServiceProvider(r)
                
                if action_server:
                    action_server.set_robot(r)
                else:
                    action_server = URTrajectoryFollower(r, rospy.Duration(1.0))
                    action_server.start()
Example #15
0
class img_interface_node:  ## This is the LISTENER for the layer ABOVE
    """The 'img_interface_node' is the class which communicates to the layer above
    according to this structure. It provides services and dynamic reconfigure server
    and also publishes topics."""

    ###############################################
    ##  *****     Static Data Members     *****
    ###############################################
    # ****   Services provided to the above layer
    #==================================================
    listOf_services = {}

    ###############################################
    ##  *****     Constructor Method     *****
    ###############################################

    def __init__(self, translator, driverMgr):
        """img_interface_node constructor is meant to launch the dynamic reconfigure server
        until it is called to launch services for listening. It doesn't receive or return
        anything."""
        self.translator = translator
        self.driverMgr = driverMgr
        self.avoidRemoteReconf = 0
        try:
            ## Dynamic Reconfigure
            self.avoidRemoteReconf = self.avoidRemoteReconf + 1
            # Launch self DynamicReconfigure server
            self.dynServer = DynamicReconfigureServer(PropertiesConfig,
                                                      self.dynServerCallback)
            # Launch request listener services
            self.listenToRequests()
            rospy.loginfo("Interface for requests created.")
        except:
            rospy.logerr(
                "Error while trying to initialise dynamic parameter server.")
            raise
        return

###################################################
##  *****     General Purpose Methods     *****
###################################################
# ****   Launch services for receiving requests
#==================================================

    def listenToRequests(self):
        """Main services creator method in order to remain listening for requests.
        It receives nothing and returns true just for future needs."""
        self.listOf_services['get/StringProperty'] = rospy.Service(
            'get/StringProperty', stringValue, self.getStringProperty)
        self.listOf_services['get/IntProperty'] = rospy.Service(
            'get/IntProperty', intValue, self.getIntProperty)
        self.listOf_services['get/FloatProperty'] = rospy.Service(
            'get/FloatProperty', floatValue, self.getFloatProperty)
        self.listOf_services['get/BoolProperty'] = rospy.Service(
            'get/BoolProperty', booleanValue, self.getBoolProperty)

        ##MICHI: 14Feb2012
        self.listOf_services['get/DispImage'] = rospy.Service(
            'get/DispImage', disparityImage, self.getDispImage)
        self.listOf_services['get/Image'] = rospy.Service(
            'get/Image', normalImage, self.getImage)
        ##MICHI: 13Mar2012
        self.listOf_services['publishDispImages'] = rospy.Service(
            'publishDispImages', requestTopic, self.publishDispImages)
        self.listOf_services['publishImages'] = rospy.Service(
            'publishImages', requestTopic, self.publishImages)
        ##MICHI: 16Apr2012
        self.listOf_services['get/TopicLocation'] = rospy.Service(
            'get/TopicLocation', stringValue, self.getStringProperty)
        self.listOf_services['set/TopicLocation'] = rospy.Service(
            'set/TopicLocation', setString, self.setTopicLocation)

        self.listOf_services['set/StrProperty'] = rospy.Service(
            'set/StrProperty', setString, self.setStrProperty)
        self.listOf_services['set/IntProperty'] = rospy.Service(
            'set/IntProperty', setInteger, self.setIntProperty)
        self.listOf_services['set/FloatProperty'] = rospy.Service(
            'set/FloatProperty', setFloat, self.setFloatProperty)
        self.listOf_services['set/BoolProperty'] = rospy.Service(
            'set/BoolProperty', setBoolean, self.setBoolProperty)
        rospy.loginfo("Ready to answer service requests.")
        return True

###################################################
# ****   Dynamic Reconfigure related methods
#==================================================

    def dynServerCallback(self, dynConfiguration, levelCode):
        """Handler for the changes in the Dynamic Reconfigure server
        Must return a configuration in the same format as received."""
        if self.avoidRemoteReconf > 0:
            rospy.loginfo("LOCAL config was changed by self (levelCode = %s)" %
                          levelCode)
            self.avoidRemoteReconf = self.avoidRemoteReconf - 1
        elif levelCode != 0:
            rospy.loginfo("LOCAL configuration changed.".rjust(80, '_'))
            for elem in dynConfiguration:
                if self.translator.canSet(elem):
                    success = self.setAnyProperty(elem, dynConfiguration[elem])
                    if success == False or success == None:
                        rospy.logerr(
                            '|__> ...error... while setting property "%s"' %
                            elem)
                        rospy.logerr("Changing %s failed with %s..." %
                                     (elem, dynConfiguration[elem]))
                        dynConfiguration[elem] = self.getAnyProperty(elem)
                        rospy.logdebug("...%s used" % (dynConfiguration[elem]))
        return dynConfiguration

    def updateSelfParameters(self, dynConfiguration, avoidPropagation=True):
        """This method is responsible for changing the node's own dynamic reconfigure parameters
        from its own code ***but avoiding a chain of uncontrolled callbacks!!.
        It returns True if everything went as expected."""
        newConfig = {}
        for elem in dynConfiguration:
            paramName = self.translator.reverseInterpret(elem)
            if paramName != "":
                newValue = dynConfiguration[elem]
                newConfig[paramName] = newValue
            else:
                rospy.logerr("Error while retrieving reverse translation.")

        if avoidPropagation == True:
            self.avoidRemoteReconf = self.avoidRemoteReconf + 1
        requestResult = self.dynServer.update_configuration(newConfig)
        if requestResult != None:
            return requestResult
        return False

    def setTopicLocation(self, setStrMsg):
        rospy.loginfo(
            str("Received call to set/TopicLocation " + setStrMsg.topicName +
                " " + setStrMsg.newValue))
        translation = self.translator.interpret(setStrMsg.topicName)
        if translation != None and (translation[PPTY_KIND] == "publishedTopic"
                                    or translation[PPTY_KIND] == "topic"):
            oldAddress = translation[PPTY_REF]
        elif manager3D.is_published(setStrMsg.topicName):
            oldAddress = setStrMsg.topicName
        else:
            print str("The " + setStrMsg.topicName +
                      " topic wasn't found neither as topic or as a name:")
            return str("Exception while relocating. Topic not found.")

        try:
            rospy.loginfo(str("...relocating " + oldAddress + "..."))
            self.driverMgr.relocateTopic(oldAddress, setStrMsg.newValue)
            ## If it was a name; the value needs to be updated in the translator
            if oldAddress == setStrMsg.topicName:
                self.translator.updateValue(setStrMsg.topicName,
                                            setStrMsg.newValue)
        except Exception as e:
            rospy.logerr("Exception while relocating: %s" % (e))
            return str("Exception while relocating: %s" % (e))
        return "Success!"

###################################################
# ****   Generic handlers for getting and setting parameters
#==================================================

    def setAnyProperty(self, propertyName, newValue):
        """Generic setter in order to redirect to the type-specific setter method."""
        propertyData = self.translator.interpret(propertyName)
        if propertyData == None:
            return None
        #else:
        if (propertyData[PPTY_TYPE].lower()).find("string") >= 0:
            #            if type(newValue) != str:
            #                print "Error: value '%s' seems to be %s instead of '%s' which is needed"%(newValue, type(newValue), propertyData[PPTY_TYPE])
            #                return None
            valueType = str
        elif (propertyData[PPTY_TYPE].lower()).find("int") >= 0:
            valueType = int
        elif (propertyData[PPTY_TYPE].lower()).find("double") >= 0 or (
                propertyData[PPTY_TYPE].lower()).find("float") >= 0:
            valueType = float
        elif (propertyData[PPTY_TYPE].lower()).find("bool") >= 0:
            valueType = bool
        else:
            rospy.logerr("Error: non registered value type.")
            return None
        return self.setFixedTypeProperty(propertyName, newValue, valueType)

####################################
# Specific fixed type property setters
#===================================
## The next methods are the callbacks for the setter services
##all of them are supposed to return the resulting values to
##inform in case the set event failed.
## In case there's an error; the error message is sent no
##matter the returning type is

    def setStrProperty(self, setterMessage):
        return self.setFixedTypeProperty(setterMessage.topicName,
                                         setterMessage.newValue, str)

    def setIntProperty(self, setterMessage):
        return self.setFixedTypeProperty(setterMessage.topicName,
                                         setterMessage.newValue, int)

    def setFloatProperty(self, setterMessage):
        return self.setFixedTypeProperty(setterMessage.topicName,
                                         setterMessage.newValue, float)

    def setBoolProperty(self, setterMessage):
        return self.setFixedTypeProperty(setterMessage.topicName,
                                         setterMessage.newValue, bool)

    def setFixedTypeProperty(self, propertyName, newValue, valueType):
        """Main property setter receiving the property name, the value to assign and its type
        and returning the response from the "setParameter" method from the low level driver."""
        propertyData = self.translator.interpret(propertyName)
        #TODO: Any type checking here?
        if propertyData == None:
            rospy.logerr("Error: trying to set not found property.")
            return None
#        else:
#            print "Obtained %s = %s."%(propertyName, propertyData[PPTY_REF])
        resp1 = True
        if propertyData[PPTY_KIND] == "dynParam":
            paramName = propertyTranslator.getBasename(propertyData[PPTY_REF])
            resp1 = self.driverMgr.setParameter(
                paramName, newValue,
                self.translator.getDynServerPath(paramName), self)
        elif propertyData[PPTY_KIND] == "topic":
            self.driverMgr.sendByTopic(
                rospy.get_namespace() + propertyData[PPTY_REF], newValue,
                remoteType[valueType])
        else:
            resp1 = False
            rospy.logerr("Error: unable to set property %s of kind: '%s'" %
                         (propertyName, propertyData[PPTY_KIND]))
        return valueType(resp1)

# Getter and getter handlers
# Generic setter in order to redirect to the appropriate method

    def getAnyProperty(self, propertyName):
        """Generic getter in order to redirect to the type-specific getter method."""
        propertyData = self.translator.interpret(propertyName)
        if propertyData == None:
            return None
        #else:
        if propertyData[PPTY_TYPE].find("string") >= 0:
            valueType = str
        elif (propertyData[PPTY_TYPE].lower()).find("int") >= 0:
            valueType = int
        elif (propertyData[PPTY_TYPE].lower()).find("double") >= 0 or (
                propertyData[PPTY_TYPE].lower()).find("float") >= 0:
            valueType = float
        elif (propertyData[PPTY_TYPE].lower()).find("bool") >= 0:
            valueType = bool
        else:
            rospy.logerr("Error: non registered value type")
            return None
        return self.getFixedTypeProperty(propertyName, valueType)

    # Generic getter in order to redirect to the appropriate method
    #Specific fixed type property getters
    def getStringProperty(self, srvMsg):
        return self.getFixedTypeProperty(srvMsg.topicName, str)

    def getIntProperty(self, srvMsg):
        return self.getFixedTypeProperty(srvMsg.topicName, int)

    def getFloatProperty(self, srvMsg):
        return self.getFixedTypeProperty(srvMsg.topicName, float)

    def getBoolProperty(self, srvMsg):
        return self.getFixedTypeProperty(srvMsg.topicName, bool)

    def getDispImage(self, srvMsg):
        respImages = []
        while len(respImages) < srvMsg.nImages:
            respImages.append(self.getFixedTypeProperty(
                srvMsg.topicName, None))
        for image in respImages:
            self.driverMgr.sendByTopic("testImages", image, DisparityImage)
        return respImages

    def getImage(self, srvMsg):
        respImages = []
        while len(respImages) < srvMsg.nImages:
            respImages.append(self.getFixedTypeProperty(
                srvMsg.topicName, None))
        return respImages
##AMPLIATION: I could add normalImage.timestamp (time[] timestamp) if needed (including the changes in the "normalImage.srv"
## in that case I would need a normalImage variable and set both fields: images and timestamp

    def getFixedTypeProperty(self, propertyName, valueType):
        """Main property getter receiving the property name and the value type
        and returning the current value from the low level driver."""
        propertyData = self.translator.interpret(propertyName)
        if propertyData == None:
            return None
        #else:
        if propertyData[PPTY_KIND] == "dynParam":
            paramName = propertyTranslator.getBasename(propertyData[PPTY_REF])
            resp1 = self.driverMgr.getParameter(
                paramName, self.translator.getDynServerPath(propertyName))
        elif propertyData[PPTY_KIND] == "publishedTopic":
            resp1 = self.driverMgr.getTopic(propertyData[PPTY_REF], valueType)
        elif propertyData[PPTY_KIND] == "readOnlyParam":
            paramName = propertyTranslator.getBasename(propertyData[PPTY_REF])
            location = rospy.search_param(param_name)
            resp1 = rospy.get_param(location)
        elif propertyData[PPTY_KIND] == "topic":
            ##MICHI: 14Feb2012
            if valueType == str:
                resp1 = str(propertyData[PPTY_REF])
            else:  ## Special case for reading disparity images
                valueType2 = DisparityImage
                if propertyData[PPTY_TYPE].find("Disparity") < 0:
                    valueType2 = Image
                resp1 = self.driverMgr.getTopic(propertyData[PPTY_REF],
                                                valueType2)
        else:
            rospy.logerr("Error: unable to get property %s of kind %s" %
                         (propertyName, propertyData[PPTY_TYPE]))
            resp1 = None
        return resp1

    # Method to request the complete list of properties
    def get_property_list(self):
        """Auxiliary method for receiving the list of properties known by the translator."""
        return self.translator.get_property_list()

    # Methods related to requesting topics to publish
    def publishDispImages(self, srvMsg):
        """Specific purpose method meant to retransmit a disparity image topic on a specific topic
        path receiving the original path, the new path and the amount of messages. It returns a
        message telling if it worked."""
        topicPath = self.translator.get_topic_path(srvMsg.sourceTopic)
        self.driverMgr.retransmitTopic(srvMsg.nImages, topicPath,
                                       srvMsg.responseTopic, DisparityImage)
        "%s images sent from %s topic to %s." % (
            srvMsg.nImages, srvMsg.sourceTopic, srvMsg.responseTopic)
        return "%s images sent from %s topic to %s." % (
            srvMsg.nImages, srvMsg.sourceTopic, srvMsg.responseTopic)

    def publishImages(self, srvMsg):
        """Specific purpose method meant to retransmit an image topic on a specific topic
        path receiving the original path, the new path and the amount of messages. It returns
        a message telling if it worked."""
        topicPath = self.translator.get_topic_path(srvMsg.sourceTopic)
        self.driverMgr.retransmitTopic(srvMsg.nImages, topicPath,
                                       srvMsg.responseTopic, Image)
        "%s images sent from %s topic to %s." % (
            srvMsg.nImages, srvMsg.sourceTopic, srvMsg.responseTopic)
        return "%s images sent from %s topic to %s." % (
            srvMsg.nImages, srvMsg.sourceTopic, srvMsg.responseTopic)
Example #16
0
class CameraSynchronizer:
  def __init__(self, forearm=True):
    stereo_camera_names = [ "narrow_stereo", "wide_stereo" ] # narrow must be first as it can be alternate, and hence has more period restrictions. 
    forearm_camera_names = [ "forearm_r", "forearm_l" ]
    self.camera_names = stereo_camera_names
    if forearm:
        self.camera_names = self.camera_names + forearm_camera_names
    # Parameter names are pretty symmetric. Build them up automatically.
    parameters = [ param_rate, param_trig_mode ]
    camera_parameters = dict((name, dict((suffix, name+"_"+suffix) for suffix in parameters)) for name in self.camera_names)
    # Some parameters are common to the wide and narrow cameras. Also store
    # the node names.
    for camera in stereo_camera_names:
      camera_parameters[camera][param_rate] = "stereo_rate"
      camera_parameters[camera]["node_name"] = camera+"_both"
    if forearm:
      for camera in forearm_camera_names:
        camera_parameters[camera]["node_name"] = camera[-1]+"_forearm_cam"
    for i in range(0, len(self.camera_names)):
      camera_parameters[self.camera_names[i]]["level"] = 1 << i; # This only works because of the specific levels in the .cfg file.

    self.projector = Projector()
    self.cameras = dict((name, Camera(proj = self.projector, **camera_parameters[name])) for name in self.camera_names)
    self.prosilica_inhibit = ProsilicaInhibitTriggerController('prosilica_inhibit_projector_controller', "prosilica_projector_inhibit", 0x0A, 0x00)
    
    self.controllers = [
      ProjectorTriggerController('projector_trigger', self.projector),
      DualCameraTriggerController('head_camera_trigger', *[self.cameras[name] for name in stereo_camera_names])]
    if forearm:
      self.controllers = self.controllers + [
          SingleCameraTriggerController('r_forearm_cam_trigger', self.cameras["forearm_r"]),
          SingleCameraTriggerController('l_forearm_cam_trigger', self.cameras["forearm_l"]),
        ]

    self.server = DynamicReconfigureServer(ConfigType, self.reconfigure)

  @staticmethod
  def print_threads():
    threads = threading.enumerate()
    n = 0
    for t in threads:
        if not t.isDaemon() and t != threading.currentThread():
            try: 
               print t.name
            except:
                print "Unknown thread ", t
            n = n + 1
    return n

  def kill(self):
    print "\nWaiting for all threads to die..."
    killAsynchronousUpdaters()
    #time.sleep(1)
    while self.print_threads() > 0:         
        print "\nStill waiting for all threads to die..."
        time.sleep(1)
    print

  def reconfigure(self, config, level):
    # print "Reconfigure", config
    # Reconfigure the projector.
    self.projector.process_update(config, level)
    self.prosilica_inhibit.process_update(config, level)
    # Reconfigure the cameras.
    for camera in self.cameras.values():
      camera.process_update(config, level)
    #for trig_controller in self.trig_controllers:
    #  trig_controller.update()
    #for camera in self.cameras.keys():
    #  camera.update()
    for controller in self.controllers:
        controller.update();
    for camera in self.cameras.values():
        camera.apply_update()
    #print config
    self.config = config
    return config
  
  def update_diagnostics(self):
    da = DiagnosticArray()
    ds = DiagnosticStatus()
    ds.name = rospy.get_caller_id().lstrip('/') + ": Tasks"
    in_progress = 0;
    longest_interval = 0;
    for updater in list(asynchronous_updaters):
        (name, interval) = updater.getStatus()
        if interval == 0:
            msg = "Idle"
        else:
            in_progress = in_progress + 1
            msg = "Update in progress (%i s)"%interval
        longest_interval = max(interval, longest_interval)
        ds.values.append(KeyValue(name, msg))
    if in_progress == 0:
        ds.message = "Idle"
    else:
        ds.message = "Updates in progress: %i"%in_progress
    if longest_interval > 10:
        ds.level = 1
        ds.message = "Update is taking too long: %i"%in_progress
    ds.hardware_id = "none"
    da.status.append(ds)
    da.header.stamp = rospy.get_rostime()
    self.diagnostic_pub.publish(da)

  def spin(self):
    self.diagnostic_pub = rospy.Publisher("/diagnostics", DiagnosticArray)
    try:
      reset_count = 0
      rospy.loginfo("Camera synchronizer is running...")
      controller_update_count = 0
      while not rospy.is_shutdown():
          if self.config['camera_reset'] == True:
              reset_count = reset_count + 1
              if reset_count > 3:
                  self.server.update_configuration({'camera_reset' : False})
          else:
              reset_count = 0
          self.update_diagnostics()
          # In case the controllers got restarted, refresh their state.
          controller_update_count += 1
          if controller_update_count >= 10:
              controller_update_count = 0
              for controller in self.controllers:
                  controller.update();
          rospy.sleep(1)
    finally:
      rospy.signal_shutdown("Main thread exiting")
      self.kill()
      print "Main thread exiting"
Example #17
0
class JoyController:
    def __init__(self,options=None):     
        self.options = options
           
        self.joy_scale = [-1,1,-1,1,1] #RPYTY
        self.trim_roll = 0
        self.trim_pitch = 0
        self.max_angle = 30
        self.max_yawangle = 200
        
        
        self.max_thrust = 80.
        self.min_thrust = 25.
        self.max_thrust_raw = percentageToThrust(self.max_thrust)
        self.min_thrust_raw = percentageToThrust(self.min_thrust)       
        self.old_thurst_raw = 0
        
        self.slew_limit = 45
        self.slew_rate = 30
        self.slew_limit_raw = percentageToThrust(self.slew_limit)            
        self.slew_rate_raw = percentageToThrust(self.slew_rate)   
        
        self.dynserver = None
        self.prev_cmd = None
        self.curr_cmd = None
        self.yaw_joy = True
        
        self.sub_joy   = rospy.Subscriber("/joy", JoyMSG, self.new_joydata)
        self.sub_tf    = tf.TransformListener()         
        self.pub_tf    = tf.TransformBroadcaster()    
        self.pub_cfJoy = rospy.Publisher("/cfjoy", CFJoyMSG)
        
        # Dynserver               
        
        self.dynserver = DynamicReconfigureServer(CFJoyCFG, self.reconfigure)
        
        
    def released(self, id):
        return self.prev_cmd.buttons[id] and not self.curr_cmd.buttons[id]
    
    def pressed(self, id):
        return not self.prev_cmd.buttons[id] and self.curr_cmd.buttons[id]
    
    def held(self, id):
        return self.prev_cmd.buttons[id] and self.curr_cmd.buttons[id]    
        
    #Needed so the class can change the dynserver values with button presses    
    def set_dynserver(self, server):
        self.dynserver = server
        
        
    def thurstToRaw(self, joy_thrust):           
        
        # Deadman button or invalid thrust
        if not self.curr_cmd.buttons[Button.L1] or joy_thrust>1:
            return 0
        
        raw_thrust = 0
        if joy_thrust > 0.01:
            raw_thrust = self.min_thrust_raw + joy_thrust*(self.max_thrust_raw-self.min_thrust_raw)
              
        
        if self.slew_rate_raw>0 and self.slew_limit_raw > raw_thrust:
            if self.old_thurst_raw > self.slew_limit_raw:
                self.old_thurst_raw = self.slew_limit_raw
            if raw_thrust < (self.old_thurst_raw - (self.slew_rate_raw/100)):
                raw_thrust = self.old_thurst_raw - self.slew_rate_raw/100
            if joy_thrust < 0 or raw_thrust < self.min_thrust_raw:
                raw_thrust = 0
        self.old_thurst_raw = raw_thrust
        
        return raw_thrust        
    
    def new_joydata(self, joymsg):
        global Button
        global Axes
        #Should run at 100hz. Use launch files roslaunch crazyflie_ros joy.launch
        if self.prev_cmd == None:
            self.prev_cmd = joymsg
            print len(joymsg.axes)
            
            #USB mode
            if len(joymsg.axes) == 27:
                Button = enum(L1=10,R1=11,Select=0,Start=3,L2=8,R2=9,Up=4,Right=5,Down=6,Left=7,Square=15,Cross=14,Triangle=12,Circle=13)
                Axes = enum(SLL=0,SLU=1,SRL=2,SRU=3,Up=4,Right=5,Down=6,Left=7,L2=4+8,R2=4+9,L1=4+10,R1=4+11,Triangle=4+12,Circle=4+13,Cross=4+14,Square=4+15,AccL=16,AccF=17,AccU=18,GyroY=19)   
            return 
        
        self.curr_cmd = joymsg
        hover = False
        
        
#        print "AXES"
#        for i,x in enumerate(joymsg.axes):
#            print " ",i,":",x
#        print "BUTTONS
#        for i,x in enumerate(joymsg.buttons):
#            print " ",i,":",x
            
        x = 0
        y = 0
        z = 0
        r = 0
        r2 = 0
        # Get stick positions [-1 1]
        if self.curr_cmd.buttons[Button.L1]:         
            x = self.joy_scale[0] * self.curr_cmd.axes[Axes.SLL] # Roll
            y = self.joy_scale[1] * self.curr_cmd.axes[Axes.SLU] # Pitch
            r = self.joy_scale[2] * self.curr_cmd.axes[Axes.SRL] # Yaw
            z = self.joy_scale[3] * self.curr_cmd.axes[Axes.SRU] # Thrust            
            r2 = self.joy_scale[4] * (self.curr_cmd.axes[Axes.L2] - self.curr_cmd.axes[Axes.R2])
            hover = self.curr_cmd.axes[Axes.L1]<-0.75
        
        roll = x * self.max_angle
        pitch = y * self.max_angle        
        yaw = 0
        
        
        #TODO use dyn reconf to decide which to use
        if r2!=0:
            yaw = r2 * self.max_yawangle
        else:
            if self.yaw_joy:
                # Deadzone     
                if r < -0.2 or r > 0.2:
                    if r < 0:
                        yaw = (r + 0.2) * self.max_yawangle * 1.25
                    else:
                        yaw = (r - 0.2) * self.max_yawangle * 1.25
                
                
        if hover:
            thrust = int(round(deadband(z,0.2)*32767 + 32767)) #Convert to uint16
        else:    
            thrust = self.thurstToRaw(z)  
            
        trimmed_roll = roll + self.trim_roll
        trimmed_pitch = pitch + self.trim_pitch
        
            
        # Control trim manually        
        new_settings = {}       
        if self.curr_cmd.buttons[Button.Left]:
            new_settings["trim_roll"] = max(self.trim_roll + self.curr_cmd.axes[Axes.Left]/10.0, -10)                         
        if self.curr_cmd.buttons[Button.Right]:
            new_settings["trim_roll"] =  min(self.trim_roll - self.curr_cmd.axes[Axes.Right]/10.0, 10)  
        if self.curr_cmd.buttons[Button.Down]:
            new_settings["trim_pitch"] = max(self.trim_pitch + self.curr_cmd.axes[Axes.Down]/10.0, -10)                
        if self.curr_cmd.buttons[Button.Up]:
            new_settings["trim_pitch"] = min(self.trim_pitch - self.curr_cmd.axes[Axes.Up]/10.0, 10)
        
        # Set trim to current input
        if self.released(Button.R1):
            new_settings["trim_roll"] = min(10, max(trimmed_roll, -10))
            new_settings["trim_pitch"] = min(10, max(trimmed_pitch, -10))   
            rospy.loginfo("Trim updated Roll/Pitch: %r/%r", round(new_settings["trim_roll"],2), round(new_settings["trim_roll"],2))
        
        # Reset Trim
        if self.released(Button.Square):
            new_settings["trim_roll"] = 0
            new_settings["trim_pitch"] = 0       
            rospy.loginfo("Pitch reset to 0/0")        
            
        if new_settings != {} and self.dynserver!=None:
            self.dynserver.update_configuration(new_settings)            
       
        
        
        # TODO: new firmware doesnt need hover_change, it just uses thrust directly
        #(trimmed_roll,trimmed_pitch,yaw,thrust,hover,hover_set, z)
        msg = CFJoyMSG()
        msg.header.stamp = rospy.Time.now()
        msg.roll = trimmed_roll
        msg.pitch = trimmed_pitch
        msg.yaw = yaw
        msg.thrust = thrust
        msg.hover = hover
        #msg.calib = self.pressed(Button.Triangle)
        self.pub_cfJoy.publish(msg)       
        
        
        # Cache prev joy command
        self.prev_cmd = self.curr_cmd
        
    
    def reconfigure(self, config, level):
        self.trim_roll = config["trim_roll"]
        self.trim_pitch = config["trim_pitch"]
        self.max_angle = config["max_angle"]
        self.max_yawangle = config["max_yawangle"]
        self.max_thrust = config["max_thrust"]
        self.min_thrust = config["min_thrust"]
        self.slew_limit = config["slew_limit"]
        self.slew_rate = config["slew_rate"]  
        self.max_thrust_raw = percentageToThrust(self.max_thrust)
        self.min_thrust_raw = percentageToThrust(self.min_thrust)
        self.slew_limit_raw = percentageToThrust(self.slew_limit)
        self.slew_rate_raw = percentageToThrust(self.slew_rate) 
        self.yaw_joy =  config["yaw_joy"]            
        return config 
Example #18
0
class ROSFollowControllers(object):

    def __init__(self):
        rospy.init_node('follow_controllers')
        cmd_topic = rospy.get_param('~cmd_topic', 'cmd_vel_input')

        use_mocap = rospy.get_param("~use_mocap", True)

        self.active_controller_pub = rospy.Publisher(
            'active_controller', UInt8, latch=True, queue_size=None)

        self.controllers = FollowControllers(
            v1_model_path=rospy.get_param('~v1_model_path'),
            v2_model_path=rospy.get_param('~v2_model_path'),
            v3_model_path=rospy.get_param('~v3_model_path'))
        self.should_publish_all_controllers = rospy.get_param('~enable_debug_pubs')
        self.active_controller = Controller(rospy.get_param('~controller'))
        self.srv = Server(ControllersConfig, self.callback)
        video = message_filters.Subscriber('video', CompressedImage)
        vo_odom_drone = message_filters.Subscriber('vo_odom_drone', Odometry)
        subs = [video, vo_odom_drone]

        if use_mocap:
            mocap_odom_drone = message_filters.Subscriber('mocap_odom_drone', Odometry)
            mocap_odom_head = message_filters.Subscriber('mocap_odom_head', Odometry)
            subs += [mocap_odom_drone, mocap_odom_head]

        self.controller_pub = {controller: rospy.Publisher(
            'output_{name}'.format(name=controller.name), Twist, queue_size=1)
            for controller in Controller if controller != Controller.idle}
        self.cmd_pub = rospy.Publisher(cmd_topic, Twist, queue_size=1)

        message_filters.ApproximateTimeSynchronizer(
            subs, queue_size=10, slop=0.1).registerCallback(self.update)

        self.last_controller_duration = {}

        updater = diagnostic_updater.Updater()
        updater.setHardwareID("ML")
        updater.add("Controllers", self.controllers_diagnostics)

        def update_diagnostics(event):
            updater.update()

        rospy.Timer(rospy.Duration(1), update_diagnostics)
        freq_bounds = {'min': 4, 'max': 6}

        self.pub_freq = diagnostic_updater.HeaderlessTopicDiagnostic(
            cmd_topic, updater, diagnostic_updater.FrequencyStatusParam(freq_bounds, 0.1, 10))

        if use_mocap:
            self.start_pose = PoseStamped()
            self.start_pose.header.frame_id = 'World'
            self.start_pose.pose.position = Point(
                rospy.get_param('~start_x', 0),
                rospy.get_param('~start_y', 0),
                rospy.get_param('~start_z', 1))
            start_yaw = rospy.get_param('~start_yaw', 0)
            self.start_pose.pose.orientation.w = np.cos(start_yaw * 0.5)
            self.start_pose.pose.orientation.z = np.sin(start_yaw * 0.5)

            #self.goto = actionlib.SimpleActionClient('fence_control', GoToPoseAction)
            #self.goto.wait_for_server()
        else:
            self.goto = None
            self.start_pose = None
        # self.target_pub = rospy.Publisher('target_pose', PoseStamped, queue_size=1)

        rospy.Subscriber('joy', Joy, self.stop, queue_size=1)
        rospy.loginfo("Ready to start")

    def stop(self, msg):
        #if msg.buttons[6] == 1 and self.goto is not None and self.start_pose is not None:
        #    self.start_pose.header.stamp = rospy.Time.now()
        #    goal = GoToPoseGoal(target_pose=self.start_pose)
        #    self.goto.send_goal(goal)
        #    self.goto.wait_for_result(rospy.Duration(20))
        #    return
        old_controller = self.active_controller
        if msg.buttons[7] == 1:
            self.active_controller = Controller.idle
        elif msg.axes[5] > 0:
            self.active_controller = Controller.exact
        elif msg.axes[4] < 0:
            self.active_controller = Controller.v1
        elif msg.axes[5] < 0:
            self.active_controller = Controller.v2
        elif msg.axes[4] > 0:
            self.active_controller = Controller.v3
        else:
            return
        if old_controller != self.active_controller:
            self.srv.update_configuration(
                {'controller': self.active_controller.value,
                 'enable_debug_pubs': self.should_publish_all_controllers})
            self.active_controller_pub.publish(self.active_controller.value)

    def controllers_diagnostics(self, stat):
        stat.summary(diagnostic_msgs.msg.DiagnosticStatus.OK, "")
        for c in Controller:
            if c == Controller.idle:
                continue

            if c in self.last_controller_duration:
                value = 'last update {0:.1f} [ms]'.format(1000 * self.last_controller_duration[c])
            else:
                value = '-'
            if c == self.active_controller:
                value += ' (active)'
            stat.add(c.name, value)

    def callback(self, config, level):
        self.should_publish_all_controllers = config['enable_debug_pubs']
        old_controller = self.active_controller
        self.active_controller = Controller(config['controller'])
        if old_controller != self.active_controller:
            self.active_controller_pub.publish(self.active_controller.value)
        return config

    def update(self, video, vo_odom_drone, mocap_odom_drone=None, mocap_odom_head=None):

        active_controller = self.active_controller

        # TODO: check shape
        frame = frame_from_msg(video)
        rospy.loginfo(frame.shape)
        v = vo_odom_drone.twist.twist.linear
        drone_velocity = [v.x, v.y, v.z]

        if mocap_odom_drone is not None and mocap_odom_head is not None:
            p = mocap_odom_drone.pose.pose.position
            drone_position_world = np.array([p.x, p.y, p.z])
            p = mocap_odom_head.pose.pose.position
            head_position_world = np.array([p.x, p.y, p.z])
            q = mocap_odom_drone.pose.pose.orientation
            # We assume pitch and roll = 0 (as the camera is stabilized)
            _, _, drone_yaw_world = euler_from_quaternion([q.x, q.y, q.z, q.w])
            q = mocap_odom_head.pose.pose.orientation
            _, _, head_yaw_world = euler_from_quaternion([q.x, q.y, q.z, q.w])
            head_position = rotate_z(head_position_world - drone_position_world, -drone_yaw_world)
            head_yaw = head_yaw_world - drone_yaw_world
        else:
            head_position = None
            head_yaw = None

        last_controller_duration = {}

        if active_controller != Controller.idle:
            start = time()
            output = self.controllers.update(
                controller=active_controller, frame=frame, drone_velocity=drone_velocity,
                target_position=head_position, target_yaw=head_yaw)
            duration = time() - start
            output = twist_from_control(output)
            if self.should_publish_all_controllers:
                self.controller_pub[active_controller].publish(output)
            self.cmd_pub.publish(output)
            self.pub_freq.tick()
            last_controller_duration[active_controller] = duration

        if self.should_publish_all_controllers:
            for controller in Controller:
                if controller not in [Controller.idle, active_controller]:
                    start = time()
                    output = self.controllers.update(
                        controller=controller, frame=frame, drone_velocity=drone_velocity,
                        target_position=head_position, target_yaw=head_yaw)
                    output = twist_from_control(output)
                    duration = time() - start
                    self.controller_pub[controller].publish(output)
                    last_controller_duration[controller] = duration
        self.last_controller_duration = last_controller_duration
class VisionServer:
    def __init__(self):
        self.server = actionlib.SimpleActionServer('track_object', TrackObjectAction, self.execute, False)
        self.server.start()

        self.lower = None
        self.upper = None
        self.targetType = None

        self.srv = Server(ThresholdsConfig, self.updateThresholds)

        self.thresholds = {int(k):v for k,v in rospy.get_param("/vision_thresholds").items()}

        self.bridge = CvBridge()

        self.leftImage = np.zeros((1,1,3), np.uint8)
        self.leftModel = image_geometry.PinholeCameraModel()
        self.newLeft = False

        self.rightImage = np.zeros((1,1,3), np.uint8)
        self.rightModel = image_geometry.PinholeCameraModel()
        self.newRight = False

        self.downImage = np.zeros((1,1,3), np.uint8)
        self.downModel = image_geometry.PinholeCameraModel()
        self.newDown = False

        self.disparityImage = np.zeros((1,1,3), np.uint8)
        self.stereoModel = image_geometry.StereoCameraModel()
        self.newDisparity = False

        self.poleFinder = PoleFinder()
        self.gateFinder = GateFinder()
        self.diceFinder = DiceFinder()
        self.pathFinder = PathFinder()
        self.response = TrackObjectResult()

        self.downSub = rospy.Subscriber('/down_camera/image_color', Image, self.downwardsCallback)
        self.downInfoSub = rospy.Subscriber('/down_camera/info', CameraInfo, self.downInfoCallback)
        
        self.stereoSub = rospy.Subscriber('/stereo/disparity', Image, self.stereoCallback)
        #self.stereoInfoSub = rospy.Subscriber('/stereo/info', CameraInfo, self.stereoInfoCallback)

        self.leftSub = rospy.Subscriber('/stereo/left/image', WFOVImage, self.leftCallback)
        #self.leftInfoSub = rospy.Subscriber('/stereo/left/image', WFOVImage, self.leftInfoCallback)
        
        self.rightSub = rospy.Subscriber('/stereo/right/image', WFOVImage, self.rightCallback)
        #self.rightInfoSub = rospy.Subscriber('/stereo/right/image', WFOVImage, self.rightInfoCallback)

        self.bridge = CvBridge()
        self.image_pub = rospy.Publisher("/thresh_image", Image, queue_size=10)
        

        #self.thresholds = self.loadThresholds()

    def updateThresholds(self, config, level):
        self.lower = np.array([config["lowH"], config["lowS"], config["lowL"]],dtype=np.uint8)
        self.upper = np.array([config["upH"], config["upS"], config["upL"]],dtype=np.uint8)

        if self.targetType is not None:
            self.thresholds[self.targetType] = [self.lower.tolist(), self.upper.tolist()]
            rospy.set_param("/vision_thresholds/"+str(self.targetType), [self.lower.tolist(), self.upper.tolist()])

        return config

    def setThresholds(self, lower, upper):
        self.lower = np.array(lower,dtype=np.uint8)
        self.upper = np.array(upper,dtype=np.uint8)

        self.srv.update_configuration({"lowH":lower[0], "lowS":lower[1], "lowV":lower[2], "upH":upper[0], "upS":upper[1], "upV":upper[2]})

    def execute(self, goal):
        self.targetType = goal.objectType
        self.setThresholds(*self.thresholds[self.targetType])
        self.feedback = TrackObjectFeedback()
        self.found = False

        self.running = True
        self.ok = True

        rightImageRect = np.zeros(self.rightImage.shape, self.rightImage.dtype)
        leftImageRect = np.zeros(self.leftImage.shape, self.leftImage.dtype)
        downImageRect = np.zeros(self.downImage.shape, self.downImage.dtype)

        r = rospy.Rate(60)
        while self.running and not rospy.is_shutdown():
            if self.server.is_preempt_requested() or self.server.is_new_goal_available():
                self.running = False
                continue
            
            r.sleep()
            
            if self.rightModel.width is not None and self.rightImage is not None:
                self.rightModel.rectifyImage(self.rightImage, rightImageRect)
            else:
                rospy.logwarn_throttle(1, "No right camera model")
                continue
                
            if self.leftModel.width is not None and self.leftImage is not None:
                self.leftModel.rectifyImage(self.leftImage, leftImageRect)
            else:
                rospy.logwarn_throttle(1, "No left camera model")
                continue #We need the left camera model for stuff

            if self.downModel.width is not None and self.downImage is not None:
                self.downModel.rectifyImage(self.downImage, downImageRect)
            else:
                rospy.logwarn_throttle(1, "No down camera model")
                continue #We need the down camera model for stuff
            
            if self.targetType == TrackObjectGoal.startGate:
                if self.newRight:
                    self.feedback = self.gateFinder.process(leftImageRect, rightImageRect, self.disparityImage, self.leftModel, self.stereoModel, self.upper, self.lower)
                    self.newRight = False

            elif self.targetType == TrackObjectGoal.pole:
                if self.newRight:
                    self.feedback = self.poleFinder.process(leftImageRect, rightImageRect, self.disparityImage, self.leftModel, self.stereoModel, self.upper, self.lower)
                    self.newRight = False

            elif self.targetType == TrackObjectGoal.dice:
                if self.newRight:
                    self.feedback = self.diceFinder.process(leftImageRect, rightImageRect, self.disparityImage, self.leftModel, self.stereoModel, goal.diceNum, self.upper, self.lower)
                    self.newRight = False

            elif self.targetType == TrackObjectGoal.path:
                if self.newDown:
                    self.feedback = self.pathFinder.process(downImageRect, self.downModel, self.upper, self.lower)
                    self.newDown = False

            else:
                self.ok = False
                break

            if self.feedback.found:
                self.server.publish_feedback(self.feedback)
                self.feedback.found = False
                self.response.found=True
            if not goal.servoing:
                self.running = False

        self.response.stoppedOk=self.ok
        self.server.set_succeeded(self.response)

#TODO: Fix color thresholds

    def downwardsCallback(self, msg):
        try:
            self.downImage = self.bridge.imgmsg_to_cv2(msg, "bgr8")
            self.newDown = True
        except CvBridgeError as e:
            print(e)

        #self.downModel.rectifyImage(self.downImage, self.downImage)

    def stereoCallback(self, msg):
        try:
            self.disparityImage = self.bridge.imgmsg_to_cv2(msg, "bgr8")
            self.newDisparity = True
        except CvBridgeError as e:
            print(e)

    def leftCallback(self, msg):
        try:
            self.leftImage = self.bridge.imgmsg_to_cv2(msg.image, "bgr8")
            self.leftModel.fromCameraInfo(msg.info)
            self.newLeft = True
        except CvBridgeError as e:
            print(e)

    def rightCallback(self, msg):
        try:
            self.rightImage = self.bridge.imgmsg_to_cv2(msg.image, "bgr8")
            self.rightModel.fromCameraInfo(msg.info)
            self.newRight = True
        except CvBridgeError as e:
            print(e)


    def downInfoCallback(self, msg):
        self.downModel.fromCameraInfo(msg)

    def rightInfoCallback(self, msg):
        self.rightModel.fromCameraInfo(msg.info)

    def leftInfoCallback(self, msg):
        self.leftModel.fromCameraInfo(msg.info)

    def loadThresholds(self):
        #with open(os.path.dirname(__file__) + '/../thresholds.json') as data_file:
            #json_data = json.load(data_file)
        data = {}
        #for entry in json_data:
            # high = entry['high']
            #low = entry['low']
            #data[entry['color']] = vision_utils.Thresholds(upperThresh=(high['hue'],high['sat'],high['val']), lowerThresh=(low['hue'],low['sat'],low['val']))
        return data
Example #20
0
class OGridServer:
    def __init__(self, frame_id='enu', map_size=500, resolution=0.3, rate=1):
        self.frame_id = frame_id
        self.ogrids = {}
        self.odom = None

        # Some default values
        self.plow = True
        self.plow_factor = 0
        self.ogrid_min_value = -1
        self.draw_bounds = False
        self.resolution = resolution
        self.ogrid_timeout = 2
        self.enforce_bounds = False
        self.enu_bounds = [[0, 0, 1], [0, 0, 1], [0, 0, 1], [0, 0, 1]]

        # Default to centering the ogrid
        position = np.array([-(map_size * resolution) / 2, -(map_size * resolution) / 2, 0])
        quaternion = np.array([0, 0, 0, 1])
        self.origin = mil_tools.numpy_quat_pair_to_pose(position, quaternion)

        self.global_ogrid = self.create_grid((map_size, map_size))

        def set_odom(msg):
            return setattr(self, 'odom', mil_tools.pose_to_numpy(msg.pose.pose))
        rospy.Subscriber('/odom', Odometry, set_odom)
        self.publisher = rospy.Publisher('/ogrid_master', OccupancyGrid, queue_size=1)

        self.ogrid_server = Server(OgridConfig, self.dynamic_cb)
        self.dynam_client = Client("bounds_server", config_callback=self.bounds_cb)
        self.ogrid_server.update_configuration({'width': 500})

        rospy.Service("/center_ogrid", Trigger, self.center_ogrid)
        rospy.Timer(rospy.Duration(1.0 / rate), self.publish)

    def center_ogrid(self, srv):
        fprint("CENTERING OGRID AT POSITION", msg_color='blue')
        if self.odom is None:
            return

        dim = -(self.map_size[0] * self.resolution) / 2
        new_org = self.odom[0] + np.array([dim, dim, 0])
        config = {}
        config['origin_x'] = float(new_org[0])
        config['origin_y'] = float(new_org[1])
        config['set_origin'] = True
        self.ogrid_server.update_configuration(config)

    def bounds_cb(self, config):
        fprint("BOUNDS DYNAMIC CONFIG UPDATE!", msg_color='blue')
        if hasattr(config, "enu_1_lat"):
            self.enu_bounds = [[config['enu_1_lat'], config['enu_1_long'], 1],
                               [config['enu_2_lat'], config['enu_2_long'], 1],
                               [config['enu_3_lat'], config['enu_3_long'], 1],
                               [config['enu_4_lat'], config['enu_4_long'], 1],
                               [config['enu_1_lat'], config['enu_1_long'], 1]]
        self.enforce_bounds = config['enforce']

    def dynamic_cb(self, config, level):
        fprint("OGRID DYNAMIC CONFIG UPDATE!", msg_color='blue')
        topics = config['topics'].replace(' ', '').split(',')
        replace_topics = config['replace_topics'].replace(' ', '').split(',')
        new_grids = {}

        for topic in topics:
            new_grids[topic] = OGrid(topic) if topic not in self.ogrids else self.ogrids[topic]

        for topic in replace_topics:
            new_grids[topic] = OGrid(topic, replace=True) if topic not in self.ogrids else self.ogrids[topic]

        self.ogrids = new_grids

        map_size = map(int, (config['height'], config['width']))
        self.map_size = map_size

        self.plow = config['plow']
        self.plow_factor = config['plow_factor']
        self.ogrid_min_value = config['ogrid_min_value']
        self.draw_bounds = config['draw_bounds']
        self.resolution = config['resolution']
        self.ogrid_timeout = config['ogrid_timeout']

        if config['set_origin']:
            fprint("Setting origin!")
            position = np.array([config['origin_x'], config['origin_y'], 0])
            quaternion = np.array([0, 0, 0, 1])
            self.origin = mil_tools.numpy_quat_pair_to_pose(position, quaternion)
        else:
            position = np.array([-(map_size[1] * self.resolution) / 2, -(map_size[0] * self.resolution) / 2, 0])
            quaternion = np.array([0, 0, 0, 1])
            self.origin = mil_tools.numpy_quat_pair_to_pose(position, quaternion)

        self.global_ogrid = self.create_grid(map_size)
        return config

    def create_grid(self, map_size):
        """
        Creates blank ogrids for everyone for the low low price of $9.95!

        `map_size` should be in the form of (h, w)
        """

        ogrid = OccupancyGrid()
        ogrid.header.stamp = rospy.Time.now()
        ogrid.header.frame_id = self.frame_id

        ogrid.info.resolution = self.resolution
        ogrid.info.width = map_size[1]
        ogrid.info.height = map_size[0]

        ogrid.info.origin = self.origin

        # TODO: Make sure this produces the correct sized ogrid
        ogrid.data = np.full((map_size[1], map_size[0]), -1).flatten()

        # fprint('Created Occupancy Map', msg_color='blue')
        return ogrid

    def publish(self, *args):
        # fprint("Merging Maps", newline=2)
        global_ogrid = deepcopy(self.global_ogrid)
        np_grid = numpyify(global_ogrid)

        # Global ogrid (only compute once)
        corners = get_enu_corners(global_ogrid)
        index_limits = transform_enu_to_ogrid(corners, global_ogrid)[:, :2]

        g_x_min = index_limits[0][0]
        g_x_max = index_limits[1][0]
        g_y_min = index_limits[0][1]
        g_y_max = index_limits[1][1]

        to_add = [o for o in self.ogrids.itervalues() if not o.replace]
        to_replace = [o for o in self.ogrids.itervalues() if o.replace]

        for ogrids in [to_add, to_replace]:
            for ogrid in ogrids:
                # Hard coded 5 second timeout - probably no need to reconfig this.
                if ogrid.nav_ogrid is None or ogrid.callback_delta > self.ogrid_timeout:
                    # fprint("Ogrid too old!")
                    continue

                # Proactively checking for errors.
                # This should be temporary but probably wont be.
                l_h, l_w = ogrid.nav_ogrid.info.height, ogrid.nav_ogrid.info.width
                g_h, g_w = global_ogrid.info.height, global_ogrid.info.width
                if l_h > g_h or l_w > g_w:
                    fprint("Proactively preventing errors in ogrid size.", msg_color="red")
                    new_size = max(l_w, g_w, l_h, g_h)
                    self.global_ogrid = self.create_grid([new_size, new_size])

                # Local Ogrid (get everything in global frame though)
                corners = get_enu_corners(ogrid.nav_ogrid)
                index_limits = transform_enu_to_ogrid(corners, ogrid.nav_ogrid)
                index_limits = transform_between_ogrids(index_limits, ogrid.nav_ogrid, global_ogrid)[:, :2]

                l_x_min = index_limits[0][0]
                l_x_max = index_limits[1][0]
                l_y_min = index_limits[0][1]
                l_y_max = index_limits[1][1]

                xs = np.sort([g_x_max, g_x_min, l_x_max, l_x_min])
                ys = np.sort([g_y_max, g_y_min, l_y_max, l_y_min])
                # These are indicies in cell units
                start_x, end_x = np.round(xs[1:3])  # Grabbing indices 1 and 2
                start_y, end_y = np.round(ys[1:3])

                # Should be indicies
                l_ogrid_start = transform_between_ogrids([start_x, start_y, 1], global_ogrid, ogrid.nav_ogrid)

                # fprint("ROI {},{} {},{}".format(start_x, start_y, end_x, end_y))
                index_width = l_ogrid_start[0] + end_x - start_x  # I suspect rounding will be a source of error
                index_height = l_ogrid_start[1] + end_y - start_y
                # fprint("width: {}, height: {}".format(index_width, index_height))
                # fprint("Ogrid size: {}, {}".format(ogrid.nav_ogrid.info.height, ogrid.nav_ogrid.info.width))

                to_add = ogrid.np_map[l_ogrid_start[1]:index_height, l_ogrid_start[0]:index_width]

                # fprint("to_add shape: {}".format(to_add.shape))

                # Make sure the slicing doesn't produce an error
                end_x = start_x + to_add.shape[1]
                end_y = start_y + to_add.shape[0]

                try:
                    # fprint("np_grid shape: {}".format(np_grid[start_y:end_y, start_x:end_x].shape))
                    fprint("{}, {}".format(ogrid.topic, ogrid.replace))
                    if ogrid.replace:
                        np_grid[start_y:end_y, start_x:end_x] = to_add
                    else:
                        np_grid[start_y:end_y, start_x:end_x] += to_add
                except Exception as e:
                    fprint("Exception caught, probably a dimension mismatch:", msg_color='red')
                    print e
                    fprint("w: {}, h: {}".format(global_ogrid.info.width, global_ogrid.info.height), msg_color='red')

        if self.draw_bounds and self.enforce_bounds:
            ogrid_bounds = transform_enu_to_ogrid(self.enu_bounds, global_ogrid).astype(np.int32)
            for i, point in enumerate(ogrid_bounds[:, :2]):
                if i == 0:
                    last_point = point
                    continue
                cv2.line(np_grid, tuple(point), tuple(last_point), 100, 3)
                last_point = point

        if self.plow:
            self.plow_snow(np_grid, global_ogrid)

        # Clip and flatten grid
        np_grid = np.clip(np_grid, self.ogrid_min_value, 100)
        global_ogrid.data = np_grid.flatten().astype(np.int8)

        self.publisher.publish(global_ogrid)

    def plow_snow(self, np_grid, ogrid):
        """Remove region around the boat so we never touch an occupied cell (making lqrrt not break
        if something touches us).

        """
        if self.odom is None:
            return np_grid

        p, q = self.odom

        yaw_rot = trns.euler_from_quaternion(q)[2]  # rads
        boat_width = params.boat_length + params.boat_buffer + self.plow_factor  # m
        boat_height = params.boat_width + params.boat_buffer + self.plow_factor  # m

        x, y, _ = transform_enu_to_ogrid([p[0], p[1], 1], ogrid)
        theta = yaw_rot
        w = boat_width / ogrid.info.resolution
        h = boat_height / ogrid.info.resolution

        box = cv2.boxPoints(((x, y), (w, h), np.degrees(theta)))
        box = np.int0(box)
        cv2.drawContours(np_grid, [box], 0, 0, -1)

        # Draw a "boat" in the ogrid
        boat_width = params.boat_length + params.boat_buffer
        boat_height = params.boat_width + params.boat_buffer

        x, y, _ = transform_enu_to_ogrid([p[0], p[1], 1], ogrid)
        w = boat_width / ogrid.info.resolution
        h = boat_height / ogrid.info.resolution

        box = cv2.boxPoints(((x, y), (w, h), np.degrees(theta)))
        box = np.int0(box)
        cv2.drawContours(np_grid, [box], 0, 40, -1)

        # fprint("Plowed snow!")
        return np_grid
Example #21
0
class VectorDriver:
    def __init__(self):

        """
        Variables to track communication frequency for debugging
        """
        self.summer=0
        self.samp = 0
        self.avg_freq = 0.0
        self.start_frequency_samp = False
        self.need_to_terminate = False
        self.flush_rcvd_data=True
        self.update_base_local_planner = False
        self.parameter_server_is_updating = False
        self.last_move_base_update = rospy.Time.now().to_sec()

        """
        Initialize the publishers for VECTOR
        """
        self.vector_data = VECTOR_DATA()
        
        """
        Start the thread for the linear actuator commands
        """
        self._linear = LinearActuator()
        if (False == self._linear.init_success):
            rospy.logerr("Could not initialize the linear actuator interface! exiting...")
            return    
        
        """
        Initialize faultlog related items
        """
        self.is_init = True
        self.extracting_faultlog = False
        
        """
        Initialize the dynamic reconfigure server for VECTOR
        """
        self.param_server_initialized = False
        self.dyn_reconfigure_srv = Server(vectorConfig, self._dyn_reconfig_callback)

        """
        Wait for the parameter server to set the configs and then set the IP address from that.
        Note that this must be the current ethernet settings of the platform. If you want to change it
        set the ethernet settings at launch to the current ethernet settings, power up, change them, power down, set the
        the ethernet settings at launch to the new ones and relaunch
        """
        r = rospy.Rate(10)
        start_time = rospy.Time.now().to_sec()
        while ((rospy.Time.now().to_sec() - start_time) < 3.0) and (False == self.param_server_initialized):
            r.sleep()
        
        if (False == self.param_server_initialized):
            rospy.logerr("Parameter server not found, you must pass an initial yaml in the launch! exiting...")
            return            
        
        """
        Create the thread to run VECTOR communication
        """
        self.tx_queue_ = multiprocessing.Queue()
        self.rx_queue_ = multiprocessing.Queue()
        self.comm = IoEthThread((os.environ['VECTOR_IP_ADDRESS'],int(os.environ['VECTOR_IP_PORT_NUM'])),
                                self.tx_queue_,
                                self.rx_queue_,
                                max_packet_size=1248)
                                    
        
        if (False == self.comm.link_up):
            rospy.logerr("Could not open socket for VECTOR...")
            self.comm.close()
            return
        
        """
        Initialize the publishers and subscribers for the node
        """
        self.faultlog_pub = rospy.Publisher('/vector/feedback/faultlog', Faultlog, queue_size=10,latch=True)
        rospy.Subscriber("/vector/cmd_vel", Twist, self._add_motion_command_to_queue)
        rospy.Subscriber("/vector/gp_command",ConfigCmd,self._add_config_command_to_queue)
        rospy.Subscriber("/move_base/DWAPlannerROS/parameter_updates",Config,self._update_move_base_params)

        """
        Start the receive handler thread
        """
        self.terminate_mutex = threading.RLock()
        self.last_rsp_rcvd = rospy.Time.now().to_sec()
        self._rcv_thread   = threading.Thread(target = self._run)
        self._rcv_thread.start()
        
        """
        Start streaming continuous data
        """
        rospy.loginfo("Stopping the data stream")
        if (False == self._continuous_data(False)):
            rospy.logerr("Could not stop VECTOR communication stream")
            self.Shutdown()
            return
        
        """
        Extract the faultlog at startup
        """
        self.flush_rcvd_data=False
        rospy.loginfo("Extracting the faultlog")
        self.extracting_faultlog = True
        
        if (False == self._extract_faultlog()):
            rospy.logerr("Could not get retrieve VECTOR faultlog")
            self.Shutdown()
            return          
        
        """
        Start streaming continuous data
        """
        rospy.loginfo("Starting the data stream")
        if (False == self._continuous_data(True)):
            rospy.logerr("Could not start VECTOR communication stream")
            self.Shutdown()
            return
            
        self.start_frequency_samp = True
        
        """
        Force the configuration to update the first time to ensure that the variables are set to
        the correct values on the machine
        """
        if (False == self._force_machine_configuration(True)):
            rospy.logerr("Initial configuration parameteters my not be valid, please check them in the yaml file")
            rospy.logerr("The ethernet address must be set to the present address at startup, to change it:")
            rospy.logerr("start the machine; change the address using rqt_reconfigure; shutdown; update the yaml and restart")
            self.Shutdown()
            return
            
            
        """
        Uncomment the line below to always unlock gain tuning and force the upload based on the parameters in the yaml file
        you must have the key to do this because it can be dangerous if one sets unstable gains. 0x00000000 needs to be replaced
        by the key
        """
        #self._unlock_gain_tuning(0x00000000)
        #self._force_pid_configuration(True)
        
        """
        Finally update the values for dynamic reconfigure with the ones reported by the machine
        """
        new_config = dict({"x_vel_limit_mps" : self.vector_data.config_param.machcfg.x_vel_limit_mps,
                           "y_vel_limit_mps" : self.vector_data.config_param.machcfg.y_vel_limit_mps,
                           "accel_limit_mps2" : self.vector_data.config_param.machcfg.accel_limit_mps2,
                           "dtz_decel_limit_mps2": self.vector_data.config_param.machcfg.dtz_decel_limit_mps2,
                           "yaw_rate_limit_rps" : self.vector_data.config_param.machcfg.yaw_rate_limit_rps,
                           "wheel_diameter_m": self.vector_data.config_param.machcfg.wheel_diameter_m,
                           "wheel_base_length_m": self.vector_data.config_param.machcfg.wheelbase_length_m,
                           "wheel_track_width_m": self.vector_data.config_param.machcfg.wheel_track_width_m,
                           "gear_ratio" : self.vector_data.config_param.machcfg.gear_ratio,
                           "motion_while_charging" : ((self.vector_data.config_param.machcfg.config_bitmap >> DISABLE_AC_PRESENT_CSI_SHIFT) & 1) ^ 1,
                           "motion_ctl_input_filter" : (self.vector_data.config_param.machcfg.config_bitmap >> MOTION_MAPPING_FILTER_SHIFT) & VALID_MOTION_MAPPING_FILTER_MASK,
                           
                           "p_gain_rps_per_rps" : self.vector_data.config_param.ctlconfig.p_gain_rps_per_rps,
                           "i_gain_rps_per_rad" : self.vector_data.config_param.ctlconfig.i_gain_rps_per_rad,
                           "d_gain_rps_per_rps2" : self.vector_data.config_param.ctlconfig.d_gain_rps_per_rps2,
                           "fdfwd_gain_rps_per_motor_rps" : self.vector_data.config_param.ctlconfig.fdfwd_gain_rps_per_motor_rps,
                           "p_error_limit_rps" : self.vector_data.config_param.ctlconfig.p_error_limit_rps,
                           "i_error_limit_rad" : self.vector_data.config_param.ctlconfig.i_error_limit_rad,
                           "i_error_drain_rate_rad_per_frame" : self.vector_data.config_param.ctlconfig.i_error_drain_rate_rad_per_frame,
                           "input_target_limit_rps" : self.vector_data.config_param.ctlconfig.input_target_limit_rps,
                           "output_limit_rps" : self.vector_data.config_param.ctlconfig.input_target_limit_rps})

        self.dyn_reconfigure_srv.update_configuration(new_config)
        
        rospy.loginfo("Vector Driver is up and running")
    
    def Shutdown(self):
        with self.terminate_mutex:
            self.need_to_terminate = True
        rospy.loginfo("Vector Driver has called the Shutdown method, terminating")
        self._linear.Shutdown()
        self.comm.Close()
        self.tx_queue_.close()
        self.rx_queue_.close()    
    
    def _run(self):
        
        while not self.need_to_terminate:
            """
            Run until signaled to stop
            Perform the actions defined based on the flags passed out
            """
            result = select.select([self.rx_queue_._reader],[],[],0.02)
            if len(result[0]) > 0:
                data = result[0][0].recv()
                with self.terminate_mutex:
                    if not self.need_to_terminate:
                        self._handle_rsp(data)

        
    def _add_command_to_queue(self,command):
        
        """
        Create a byte array with the CRC from the command
        """
        cmd_bytes = generate_cmd_bytes(command)
        
        """
        Send it
        """
        self.tx_queue_.put(cmd_bytes)
        
    def _update_rcv_frq(self):
        if (True == self.start_frequency_samp):
            self.samp+=1
            self.summer+=1.0/(rospy.Time.now().to_sec() - self.last_rsp_rcvd)
            self.avg_freq = self.summer/self.samp
        self.last_rsp_rcvd = rospy.Time.now().to_sec()
                        
    def _handle_rsp(self,data_bytes):
        
        self._update_rcv_frq()
        if (True == self.flush_rcvd_data):
            return
        

        valid_data,rsp_data = validate_response(data_bytes)
        if (False == valid_data):
            rospy.logerr("bad vector data packet")
            return
        if (self.extracting_faultlog):
            if (len(rsp_data) == NUMBER_OF_FAULTLOG_WORDS):
                self.extracting_faultlog = False
                faultlog_msg = Faultlog()
                faultlog_msg.data = rsp_data
                self.faultlog_pub.publish(faultlog_msg)
        elif (len(rsp_data) == NUMBER_OF_VECTOR_RSP_WORDS):
            
            header_stamp = self.vector_data.status.parse(rsp_data[START_STATUS_BLOCK:END_STATUS_BLOCK])
            wheel_circum = self.vector_data.config_param.parse(rsp_data[START_APP_CONFIG_BLOCK:END_FRAM_CONFIG_BLOCK],header_stamp)
            self.vector_data.auxiliary_power.parse(rsp_data[START_BATTERY_DATA_BLOCK:END_BATTERY_DATA_BLOCK],header_stamp)
            self.vector_data.propulsion.parse(rsp_data[START_PROPULSION_DATA_BLOCK:END_PROPULSION_DATA_BLOCK],header_stamp)
            self.vector_data.dynamics.parse(rsp_data[START_DYNAMICS_DATA_BLOCK:END_DYNAMICS_DATA_BLOCK],header_stamp,wheel_circum)            
            self.vector_data.imu.parse_data(rsp_data[START_IMU_DATA_BLOCK:END_IMU_DATA_BLOCK],header_stamp)
            
            rospy.logdebug("feedback received from vector")
        
    def _add_motion_command_to_queue(self,command):
        
        """
        Add the command to the queue, platform does command limiting and mapping
        """
        cmds = [MOTION_CMD_ID,[convert_float_to_u32(command.linear.x),
                               convert_float_to_u32(command.linear.y),
                               convert_float_to_u32(command.angular.z)]]
        if (False == self.parameter_server_is_updating):
            self._add_command_to_queue(cmds)
            
    def _add_config_command_to_queue(self,command):
        try:
            cmds = [GENERAL_PURPOSE_CMD_ID,[command_ids[command.gp_cmd],command.gp_param]]
            self._add_command_to_queue(cmds)
        except:
            rospy.logerr("Config param failed, it is probably not known")
            return

    def _dyn_reconfig_callback(self,config,level):
        """
        Note: for some reason the commands collide (queue gets garbled) if the
        motion commands and reconfiguration are sent at the same time; so
        we just gate the motion commands when reconfiguring which should be OK in general
        since most result in zero velocity on the embedded side. Need to dig in further
        to understand why this is happening
        """
        self.parameter_server_is_updating = True
        rospy.sleep(0.1)
        
        """
        The first time through we want to ignore the values because they are just defaults from the ROS
        parameter server which has no knowledge of the platform being used
        """     
        if (True == self.is_init):
            self.is_init = False
            return config
    
        """
        Create the configuration bitmap from the appropriate variables
        """
        config_bitmap = (((config.motion_while_charging^1) << DISABLE_AC_PRESENT_CSI_SHIFT)|
                         (config.motion_ctl_input_filter << MOTION_MAPPING_FILTER_SHIFT))
        
        """
        Define the configuration parameters for all the platforms
        """
        self.valid_config_cmd  = [LOAD_MACH_CONFIG_CMD_ID,
                                  [convert_float_to_u32(config.x_vel_limit_mps),
                                   convert_float_to_u32(config.y_vel_limit_mps),
                                   convert_float_to_u32(config.accel_limit_mps2),
                                   convert_float_to_u32(config.decel_limit_mps2),
                                   convert_float_to_u32(config.dtz_decel_limit_mps2),
                                   convert_float_to_u32(config.yaw_rate_limit_rps),
                                   convert_float_to_u32(config.yaw_accel_limit_rps2),
                                   convert_float_to_u32(config.wheel_diameter_m),
                                   convert_float_to_u32(config.wheel_base_length_m),
                                   convert_float_to_u32(config.wheel_track_width_m),
                                   convert_float_to_u32(config.gear_ratio),
                                   config_bitmap]]
             
        """
        The teleop limits are always the minimum of the actual machine limit and the ones set for teleop
        """
        config.teleop_x_vel_limit_mps = minimum_f(config.teleop_x_vel_limit_mps, config.x_vel_limit_mps)
        config.teleop_y_vel_limit_mps = minimum_f(config.teleop_y_vel_limit_mps, config.x_vel_limit_mps)
        config.teleop_accel_limit_mps2 = minimum_f(config.teleop_accel_limit_mps2, config.accel_limit_mps2)
        config.teleop_yaw_rate_limit_rps = minimum_f(config.teleop_yaw_rate_limit_rps, config.yaw_rate_limit_rps)
        config.teleop_yaw_accel_limit_rps2 = minimum_f(config.teleop_yaw_accel_limit_rps2, config.teleop_yaw_accel_limit_rps2)      
        
        """
        Set the teleop configuration in the feedback
        """
        self.vector_data.config_param.SetTeleopConfig([config.teleop_x_vel_limit_mps,
                                                    config.teleop_y_vel_limit_mps,
                                                    config.teleop_accel_limit_mps2,
                                                    config.teleop_yaw_rate_limit_rps,
                                                    config.teleop_yaw_accel_limit_rps2])
                                                    
        
        """
        Define the configuration parameters for all the platforms
        """
        self.valid_pid_cmd  = [SET_PID_CONFIG_CMD_ID,
                                  [convert_float_to_u32(config.p_gain_rps_per_rps),
                                   convert_float_to_u32(config.i_gain_rps_per_rad),
                                   convert_float_to_u32(config.d_gain_rps_per_rps2),
                                   convert_float_to_u32(config.fdfwd_gain_rps_per_motor_rps),
                                   convert_float_to_u32(config.p_error_limit_rps),
                                   convert_float_to_u32(config.i_error_limit_rad),
                                   convert_float_to_u32(config.d_error_limit_rps2),
                                   convert_float_to_u32(config.i_error_drain_rate_rad_per_frame),
                                   convert_float_to_u32(config.output_limit_rps),
                                   convert_float_to_u32(config.input_target_limit_rps)]]
                                                    
        """
        Update the linear actuator velocity limit
        """
 
        
        """
        Check and see if we need to store the parameters in NVM before we try, although the NVM is F-RAM
        with unlimited read/write, uneccessarily setting the parameters only introduces risk for error 
        """
        load_config_params = False
        if self.param_server_initialized:
            if ((1<<7) == ((1<<7) & level)):
                self._linear.UpdateVelLimit(config.linear_actuator_vel_limit_mps)
            if ((1<<2) == ((1<<2) & level)):
                self._force_machine_configuration()
                load_config_params = True

            config.x_vel_limit_mps = round(self.vector_data.config_param.machcfg.x_vel_limit_mps,3)
            config.y_vel_limit_mps = round(self.vector_data.config_param.machcfg.y_vel_limit_mps,3)
            config.accel_limit_mps2 = round(self.vector_data.config_param.machcfg.accel_limit_mps2,3)
            config.decel_limit_mps2 = round(self.vector_data.config_param.machcfg.decel_limit_mps2,3)
            config.dtz_decel_limit_mps2 = round(self.vector_data.config_param.machcfg.dtz_decel_limit_mps2,3)
            config.yaw_rate_limit_rps = round(self.vector_data.config_param.machcfg.yaw_rate_limit_rps,3)
            config.yaw_accel_limit_rps2 = round(self.vector_data.config_param.machcfg.yaw_accel_limit_rps2,3)
            config.wheel_diameter_m = round(self.vector_data.config_param.machcfg.wheel_diameter_m,3)
            config.wheel_base_length_m = round(self.vector_data.config_param.machcfg.wheelbase_length_m,3)
            config.wheel_track_width_m = round(self.vector_data.config_param.machcfg.wheel_track_width_m,3)
            config.gear_ratio = round(self.vector_data.config_param.machcfg.gear_ratio,3)
            config.motion_while_charging = ((self.vector_data.config_param.machcfg.config_bitmap >> DISABLE_AC_PRESENT_CSI_SHIFT) & 1) ^ 1
            config.motion_ctl_input_filter = (self.vector_data.config_param.machcfg.config_bitmap >> MOTION_MAPPING_FILTER_SHIFT) & VALID_MOTION_MAPPING_FILTER_MASK
        

                                   
        if self.param_server_initialized:
            if ((1<<5) == ((1<<5) & level)):
                if self.vector_data.config_param.ctlconfig.control_tuning_unlocked:
                    self._force_pid_configuration()
                    
            if (True == config.set_default_gains):
                cmds = [GENERAL_PURPOSE_CMD_ID,[2002,0]]
                self._add_command_to_queue(cmds)
                config.set_default_gains = False
                rospy.sleep(0.1)

            config.p_gain_rps_per_rps = round(self.vector_data.config_param.ctlconfig.p_gain_rps_per_rps,3)
            config.i_gain_rps_per_rad = round(self.vector_data.config_param.ctlconfig.i_gain_rps_per_rad,3)
            config.d_gain_rps_per_rps2 = round(self.vector_data.config_param.ctlconfig.d_gain_rps_per_rps2,3)
            config.fdfwd_gain_rps_per_motor_rps = round(self.vector_data.config_param.ctlconfig.fdfwd_gain_rps_per_motor_rps,3)
            config.p_error_limit_rps = round(self.vector_data.config_param.ctlconfig.p_error_limit_rps,3)
            config.i_error_limit_rad = round(self.vector_data.config_param.ctlconfig.i_error_limit_rad,3)
            config.d_error_limit_rps2 = round(self.vector_data.config_param.ctlconfig.d_error_limit_rps2,3)
            config.i_error_drain_rate_rad_per_frame = round(self.vector_data.config_param.ctlconfig.i_error_drain_rate_rad_per_frame,5)
            config.output_limit_rps = round(self.vector_data.config_param.ctlconfig.output_limit_rps,3)
            config.input_target_limit_rps = round(self.vector_data.config_param.ctlconfig.input_target_limit_rps,3)

                
        if (True == config.send_unlock_request):
            try:
                key = int(config.unlock_key,16)
                self._unlock_gain_tuning(key)
            except:
                rospy.logerr("Invalid Key Value!!!! Must be a string in 32bit hex format") 
            config.send_unlock_request = False            
        
        
        self.valid_config = config
        
        if (True == load_config_params) or (False == self.param_server_initialized):
            self.update_base_local_planner = True
            self._update_move_base_params()
        
        self.param_server_initialized = True
        self.parameter_server_is_updating = False
        return config
        
    
    def _update_move_base_params(self):
        
        """
        If parameter updates have not been called in the last 5 seconds allow the
        subscriber callback to set them
        """
        if ((rospy.Time.now().to_sec()-self.last_move_base_update) > 5.0):
            self.update_base_local_planner = True
            
        if self.update_base_local_planner:
            self.update_base_local_planner = False
            self.last_move_base_update = rospy.Time.now().to_sec()
            
            try:
                dyn_reconfigure_client= Client("/move_base/DWAPlannerROS",timeout=1.0)
                changes = dict()
                changes['acc_lim_x'] = maximum_f(self.valid_config.accel_limit_mps2, self.valid_config.decel_limit_mps2)
                changes['acc_lim_y'] = maximum_f(self.valid_config.accel_limit_mps2, self.valid_config.decel_limit_mps2)
                changes['acc_lim_th'] = self.valid_config.yaw_accel_limit_rps2
                changes['max_vel_x'] = self.valid_config.x_vel_limit_mps
                changes['max_vel_y'] = self.valid_config.y_vel_limit_mps
                changes['max_rot_vel'] = self.valid_config.yaw_rate_limit_rps
                dyn_reconfigure_client.update_configuration(changes)
                dyn_reconfigure_client.close()
            except:
                pass
            
            
            rospy.loginfo("Vector Driver updated move_base parameters to match machine parameters")
            
    def _unlock_gain_tuning(self,key):
        r = rospy.Rate(10)
        attempts = 0
        while (False == self.vector_data.config_param.ctlconfig.control_tuning_unlocked) and (attempts < 3):
            
            cmds = [GENERAL_PURPOSE_CMD_ID,[2001,key]]
            self._add_command_to_queue(cmds)
            attempts += 1
            r.sleep()
        if (True == self.vector_data.config_param.ctlconfig.control_tuning_unlocked):
            rospy.loginfo("Controller tuning successfully unlocked")
        else:
            rospy.logerr("Failed to unlock controller tuning, the key is likely incorrect")
            
               

    def _continuous_data(self,start_cont):
        set_continuous = [GENERAL_PURPOSE_CMD_ID,[GENERAL_PURPOSE_CMD_SEND_CONTINUOUS_DATA,start_cont]]
        ret = False
        
        if (True == start_cont):
            r = rospy.Rate(10)
            start_time = rospy.Time.now().to_sec()
            while ((rospy.Time.now().to_sec() - start_time) < 3.0) and (True == self.vector_data.status.init):
                self._add_command_to_queue(set_continuous)
                r.sleep()
            ret = not self.vector_data.status.init
        else:
            r = rospy.Rate(5)
            start_time = rospy.Time.now().to_sec()
            while ((rospy.Time.now().to_sec() - start_time) < 3.0) and (False == ret):
                self._add_command_to_queue(set_continuous)
                rospy.sleep(0.6)
                if ((rospy.Time.now().to_sec() - self.last_rsp_rcvd) > 0.5):
                    ret = True
                r.sleep()
            self.vector_data.status.init = True

        return ret
    
    def _extract_faultlog(self):
        r = rospy.Rate(2)        
        start_time = rospy.Time.now().to_sec()
        while ((rospy.Time.now().to_sec() - start_time) < 3.0) and (True == self.extracting_faultlog):
            self._add_command_to_queue([GENERAL_PURPOSE_CMD_ID,[GENERAL_PURPOSE_CMD_SEND_FAULTLOG,0]]) 
            r.sleep()
            
        return not self.extracting_faultlog
    
    def _force_machine_configuration(self,echo=False):
        """
        Load all the parameters on the machine at startup; first check if they match, if they do continue.
        Otherwise load them and check again.
        """
        r = rospy.Rate(5)
        start_time = rospy.get_time()
        params_loaded = False
        new_params = False
        while ((rospy.get_time() - start_time) < 10.0) and (False == params_loaded):
            load_params = False
            for i in range(NUMBER_OF_CONFIG_PARAM_VARIABLES):
                if (self.vector_data.config_param.configuration_feedback[i] != self.valid_config_cmd[1][i]):
                    load_params = True
            if (True == load_params):
                self._add_command_to_queue(self.valid_config_cmd)
                new_params = True
                r.sleep()
            else:
                params_loaded = True
        
        if (new_params == True) or (echo == True):
            rospy.loginfo("New Machine Parameters Loaded.......")
            rospy.loginfo("x_vel_limit_mps:          %.4f"%self.vector_data.config_param.machcfg.x_vel_limit_mps)
            rospy.loginfo("y_vel_limit_mps:          %.4f"%self.vector_data.config_param.machcfg.y_vel_limit_mps)
            rospy.loginfo("accel_limit_mps2:         %.4f"%self.vector_data.config_param.machcfg.accel_limit_mps2)
            rospy.loginfo("decel_limit_mps2:         %.4f"%self.vector_data.config_param.machcfg.decel_limit_mps2)
            rospy.loginfo("dtz_decel_limit_mps2:     %.4f"%self.vector_data.config_param.machcfg.dtz_decel_limit_mps2)
            rospy.loginfo("yaw_rate_limit_rps:       %.4f"%self.vector_data.config_param.machcfg.yaw_rate_limit_rps)
            rospy.loginfo("yaw_accel_limit_rps2:     %.4f"%self.vector_data.config_param.machcfg.yaw_accel_limit_rps2)
            rospy.loginfo("wheel_diameter_m:         %.4f"%self.vector_data.config_param.machcfg.wheel_diameter_m)
            rospy.loginfo("wheel_base_length_m:      %.4f"%self.vector_data.config_param.machcfg.wheelbase_length_m)
            rospy.loginfo("wheel_track_width_m:      %.4f"%self.vector_data.config_param.machcfg.wheel_track_width_m)
            rospy.loginfo("gear_ratio:               %.4f"%self.vector_data.config_param.machcfg.gear_ratio)
            rospy.loginfo("motion_while_charging:    %u"%(((self.vector_data.config_param.machcfg.config_bitmap >> DISABLE_AC_PRESENT_CSI_SHIFT) & 1) ^ 1))
            rospy.loginfo("motion_ctl_input_filter:  %u\n"%((self.vector_data.config_param.machcfg.config_bitmap >> MOTION_MAPPING_FILTER_SHIFT) & VALID_MOTION_MAPPING_FILTER_MASK))
        elif not params_loaded:
            rospy.logerr("Failed to load machine parameters!!!!!!!!!!")
            
        return params_loaded

    def _force_pid_configuration(self,echo=False):                
        r = rospy.Rate(5)
        start_time = rospy.get_time()
        params_loaded = False
        new_params = False
        while ((rospy.get_time() - start_time) < 10.0) and (False == params_loaded):
            load_params = False
            for i in range(16,(16+NUMBER_OF_PID_PARAM_VARIABLES)):
                if (self.vector_data.config_param.configuration_feedback[i] != self.valid_pid_cmd[1][i-16]):
                    load_params = True

            if (True == load_params):
                self._add_command_to_queue(self.valid_pid_cmd)
                r.sleep()
                new_params = True
            else:
                params_loaded = True
                
        if (new_params == True) or (echo == True):                
            rospy.loginfo("New PID Parameters Loaded.......")
            rospy.loginfo("p_gain_rps_per_rps:                %.4f"%self.vector_data.config_param.ctlconfig.p_gain_rps_per_rps)
            rospy.loginfo("i_gain_rps_per_rps:                %.4f"%self.vector_data.config_param.ctlconfig.i_gain_rps_per_rad)
            rospy.loginfo("d_gain_rps_per_rps:                %.4f"%self.vector_data.config_param.ctlconfig.d_gain_rps_per_rps2)
            rospy.loginfo("fdfwd_gain_rps_per_motor_rps:      %.4f"%self.vector_data.config_param.ctlconfig.fdfwd_gain_rps_per_motor_rps)
            rospy.loginfo("p_error_limit_rps:                 %.4f"%self.vector_data.config_param.ctlconfig.p_error_limit_rps)
            rospy.loginfo("i_error_limit_rps:                 %.4f"%self.vector_data.config_param.ctlconfig.i_error_limit_rad)
            rospy.loginfo("d_error_limit_rps:                 %.4f"%self.vector_data.config_param.ctlconfig.d_error_limit_rps2)
            rospy.loginfo("i_error_drain_rate_rad_per_frame:  %.4f"%self.vector_data.config_param.ctlconfig.i_error_drain_rate_rad_per_frame)
            rospy.loginfo("output_limit_rps:                  %.4f"%self.vector_data.config_param.ctlconfig.output_limit_rps)
            rospy.loginfo("input_target_limit_rps:            %.4f\n"%self.vector_data.config_param.ctlconfig.input_target_limit_rps)
        elif not params_loaded:
            rospy.logerr("Failed to load machine parameters!!!!!!!!!!")        
        return params_loaded
class CameraSynchronizer:
    def __init__(self, forearm=True):
        stereo_camera_names = [
            "narrow_stereo", "wide_stereo"
        ]  # narrow must be first as it can be alternate, and hence has more period restrictions.
        forearm_camera_names = ["forearm_r", "forearm_l"]
        self.camera_names = stereo_camera_names
        if forearm:
            self.camera_names = self.camera_names + forearm_camera_names
        # Parameter names are pretty symmetric. Build them up automatically.
        parameters = [param_rate, param_trig_mode]
        camera_parameters = dict(
            (name, dict((suffix, name + "_" + suffix)
                        for suffix in parameters))
            for name in self.camera_names)
        # Some parameters are common to the wide and narrow cameras. Also store
        # the node names.
        for camera in stereo_camera_names:
            camera_parameters[camera][param_rate] = "stereo_rate"
            camera_parameters[camera]["node_name"] = camera + "_both"
        if forearm:
            for camera in forearm_camera_names:
                camera_parameters[camera][
                    "node_name"] = camera[-1] + "_forearm_cam"
        for i in range(0, len(self.camera_names)):
            camera_parameters[self.camera_names[i]]["level"] = 1 << i
            # This only works because of the specific levels in the .cfg file.

        self.projector = Projector()
        self.cameras = dict(
            (name, Camera(proj=self.projector, **camera_parameters[name]))
            for name in self.camera_names)
        self.prosilica_inhibit = ProsilicaInhibitTriggerController(
            'prosilica_inhibit_projector_controller',
            "prosilica_projector_inhibit", 0x0A, 0x00)

        self.controllers = [
            ProjectorTriggerController('projector_trigger', self.projector),
            DualCameraTriggerController(
                'head_camera_trigger',
                *[self.cameras[name] for name in stereo_camera_names])
        ]
        if forearm:
            self.controllers = self.controllers + [
                SingleCameraTriggerController('r_forearm_cam_trigger',
                                              self.cameras["forearm_r"]),
                SingleCameraTriggerController('l_forearm_cam_trigger',
                                              self.cameras["forearm_l"]),
            ]

        self.server = DynamicReconfigureServer(ConfigType, self.reconfigure)

    @staticmethod
    def print_threads():
        threads = threading.enumerate()
        n = 0
        for t in threads:
            if not t.isDaemon() and t != threading.currentThread():
                try:
                    print t.name
                except:
                    print "Unknown thread ", t
                n = n + 1
        return n

    def kill(self):
        print "\nWaiting for all threads to die..."
        killAsynchronousUpdaters()
        #time.sleep(1)
        while self.print_threads() > 0:
            print "\nStill waiting for all threads to die..."
            time.sleep(1)
        print

    def reconfigure(self, config, level):
        # print "Reconfigure", config
        # Reconfigure the projector.
        self.projector.process_update(config, level)
        self.prosilica_inhibit.process_update(config, level)
        # Reconfigure the cameras.
        for camera in self.cameras.values():
            camera.process_update(config, level)
        #for trig_controller in self.trig_controllers:
        #  trig_controller.update()
        #for camera in self.cameras.keys():
        #  camera.update()
        for controller in self.controllers:
            controller.update()
        for camera in self.cameras.values():
            camera.apply_update()
        #print config
        self.config = config
        return config

    def update_diagnostics(self):
        da = DiagnosticArray()
        ds = DiagnosticStatus()
        ds.name = rospy.get_caller_id().lstrip('/') + ": Tasks"
        in_progress = 0
        longest_interval = 0
        for updater in list(asynchronous_updaters):
            (name, interval) = updater.getStatus()
            if interval == 0:
                msg = "Idle"
            else:
                in_progress = in_progress + 1
                msg = "Update in progress (%i s)" % interval
            longest_interval = max(interval, longest_interval)
            ds.values.append(KeyValue(name, msg))
        if in_progress == 0:
            ds.message = "Idle"
        else:
            ds.message = "Updates in progress: %i" % in_progress
        if longest_interval > 10:
            ds.level = 1
            ds.message = "Update is taking too long: %i" % in_progress
        ds.hardware_id = "none"
        da.status.append(ds)
        da.header.stamp = rospy.get_rostime()
        self.diagnostic_pub.publish(da)

    def spin(self):
        self.diagnostic_pub = rospy.Publisher("/diagnostics", DiagnosticArray)
        try:
            reset_count = 0
            rospy.loginfo("Camera synchronizer is running...")
            controller_update_count = 0
            while not rospy.is_shutdown():
                if self.config['camera_reset'] == True:
                    reset_count = reset_count + 1
                    if reset_count > 3:
                        self.server.update_configuration(
                            {'camera_reset': False})
                else:
                    reset_count = 0
                self.update_diagnostics()
                # In case the controllers got restarted, refresh their state.
                controller_update_count += 1
                if controller_update_count >= 10:
                    controller_update_count = 0
                    for controller in self.controllers:
                        controller.update()
                rospy.sleep(1)
        finally:
            rospy.signal_shutdown("Main thread exiting")
            self.kill()
            print "Main thread exiting"
Example #23
0
class JoyController:
    def __init__(self,options=None):     
        self.options = options
           
        self.joy_scale = [-1,1,-1,1,1] #RPYTY
        self.trim_roll = 0
        self.trim_pitch = 0
        self.max_angle = 30
        self.max_yawangle = 200
        
        
        self.max_thrust = 80.
        self.min_thrust = 25.
        self.max_thrust_raw = percentageToThrust(self.max_thrust)
        self.min_thrust_raw = percentageToThrust(self.min_thrust)       
        self.old_thurst_raw = 0
        
        self.slew_limit = 45
        self.slew_rate = 30
        self.slew_limit_raw = percentageToThrust(self.slew_limit)            
        self.slew_rate_raw = percentageToThrust(self.slew_rate)   
        
        self.dynserver = None
        self.prev_cmd = None
        self.curr_cmd = None
        self.hover = False
        
        self.sub_joy   = rospy.Subscriber("/joy", JoyMSG, self.new_joydata)
        self.sub_tf    = tf.TransformListener()         
        self.pub_tf    = tf.TransformBroadcaster()    
        self.pub_cfJoy = rospy.Publisher("/cfjoy", CFJoyMSG)
        
        # Dynserver               
        
        self.dynserver = DynamicReconfigureServer(CFJoyCFG, self.reconfigure)
        
        
    def released(self, id):
        return self.prev_cmd.buttons[id] and not self.curr_cmd.buttons[id]
    
    def pressed(self, id):
        return not self.prev_cmd.buttons[id] and self.curr_cmd.buttons[id]
    
    def held(self, id):
        return self.prev_cmd.buttons[id] and self.curr_cmd.buttons[id]    
        
    #Needed so the class can change the dynserver values with button presses    
    def set_dynserver(self, server):
        self.dynserver = server
        
        
    def thurstToRaw(self, joy_thrust):           
        
        # Deadman button or invalid thrust
        if not self.curr_cmd.buttons[Button.L1] or joy_thrust>1:
            return 0
        
        raw_thrust = 0
        if joy_thrust > 0.01:
            raw_thrust = self.min_thrust_raw + joy_thrust*(self.max_thrust_raw-self.min_thrust_raw)
              
        
        if self.slew_rate_raw>0 and self.slew_limit_raw > raw_thrust:
            if self.old_thurst_raw > self.slew_limit_raw:
                self.old_thurst_raw = self.slew_limit_raw
            if raw_thrust < (self.old_thurst_raw - (self.slew_rate_raw/100)):
                raw_thrust = self.old_thurst_raw - self.slew_rate_raw/100
            if joy_thrust < 0 or raw_thrust < self.min_thrust_raw:
                raw_thrust = 0
        self.old_thurst_raw = raw_thrust
        
        return raw_thrust        
    
    def new_joydata(self, joymsg):
        global Button
        global Axes
        #Should run at 100hz. Use launch files roslaunch crazyflie_row joy.launch
        if self.prev_cmd == None:
            self.prev_cmd = joymsg
            print len(joymsg.axes)
            
            #USB mode
            if len(joymsg.axes) == 27:
                Button = enum(L1=10,R1=11,Select=0,Start=3,L2=8,R2=9,Up=4,Right=5,Down=6,Left=7,Square=15,Cross=14,Triangle=12,Circle=13)
                Axes = enum(SLL=0,SLU=1,SRL=2,SRU=3,Up=4,Right=5,Down=6,Left=7,L2=4+8,R2=4+9,L1=4+10,R1=4+11,Triangle=4+12,Circle=4+13,Cross=4+14,Square=4+15,AccL=16,AccF=17,AccU=18,GyroY=19)
           
            return (0,0,0,0,False,False,0)
        
        self.curr_cmd = joymsg
        hover = False
        
        
#        print "AXES"
#        for i,x in enumerate(joymsg.axes):
#            print " ",i,":",x
#        print "BUTTONS
#        for i,x in enumerate(joymsg.buttons):
#            print " ",i,":",x
            
        x = 0
        y = 0
        z = 0
        r = 0
        r2 = 0
        # Get stick positions [-1 1]
        if self.curr_cmd.buttons[Button.L1]:         
            x = self.joy_scale[0] * self.curr_cmd.axes[Axes.SLL] # Roll
            y = self.joy_scale[1] * self.curr_cmd.axes[Axes.SLU] # Pitch
            r = self.joy_scale[2] * self.curr_cmd.axes[Axes.SRL] # Yaw
            z = self.joy_scale[3] * self.curr_cmd.axes[Axes.SRU] # Thrust            
            r2 = self.joy_scale[4] * (self.curr_cmd.axes[Axes.L2] - self.curr_cmd.axes[Axes.R2])
            hover = self.curr_cmd.axes[Axes.L1]<-0.75
        
        roll = x * self.max_angle
        pitch = y * self.max_angle        
        yaw = 0
        
        
        #TODO use dyn reconf to decide which to use
        if r2!=0:
            yaw = r2 * self.max_yawangle
        else:
            # Deadzone     
            if r < -0.2 or r > 0.2:
                if r < 0:
                    yaw = (r + 0.2) * self.max_yawangle * 1.25
                else:
                    yaw = (r - 0.2) * self.max_yawangle * 1.25
                
        thrust = self.thurstToRaw(z)   
        trimmed_roll = roll + self.trim_roll
        trimmed_pitch = pitch + self.trim_pitch
        
            
        # Control trim manually        
        new_settings = {}       
        if self.curr_cmd.buttons[Button.Left]:
            new_settings["trim_roll"] = max(self.trim_roll + self.curr_cmd.axes[Axes.Left]/10.0, -10)                         
        if self.curr_cmd.buttons[Button.Right]:
            new_settings["trim_roll"] =  min(self.trim_roll - self.curr_cmd.axes[Axes.Right]/10.0, 10)  
        if self.curr_cmd.buttons[Button.Down]:
            new_settings["trim_pitch"] = max(self.trim_pitch + self.curr_cmd.axes[Axes.Down]/10.0, -10)                
        if self.curr_cmd.buttons[Button.Up]:
            new_settings["trim_pitch"] = min(self.trim_pitch - self.curr_cmd.axes[Axes.Up]/10.0, 10)
        
        # Set trim to current input
        if self.released(Button.R1):
            new_settings["trim_roll"] = min(10, max(trimmed_roll, -10))
            new_settings["trim_pitch"] = min(10, max(trimmed_pitch, -10))   
            rospy.loginfo("Trim updated Roll/Pitch: %r/%r", round(new_settings["trim_roll"],2), round(new_settings["trim_roll"],2))
        
        # Reset Trim
        if self.released(Button.Square):
            new_settings["trim_roll"] = 0
            new_settings["trim_pitch"] = 0       
            rospy.loginfo("Pitch reset to 0/0")        
            
        if new_settings != {} and self.dynserver!=None:
            self.dynserver.update_configuration(new_settings)            
            
        hover_set = hover and not self.hover       
        self.hover = hover
        
        #(trimmed_roll,trimmed_pitch,yaw,thrust,hover,hover_set, z)
        msg = CFJoyMSG()
        msg.header.stamp = rospy.Time.now()
        msg.roll = trimmed_roll
        msg.pitch = trimmed_pitch
        msg.yaw = yaw
        msg.thrust = thrust
        msg.hover = hover
        msg.hover_set = hover_set
        msg.hover_change = z
        msg.calib = self.pressed(Button.Triangle)
        self.pub_cfJoy.publish(msg)       
        
        
        # Cache prev joy command
        self.prev_cmd = self.curr_cmd
        
    
    def reconfigure(self, config, level):
        self.trim_roll = config["trim_roll"]
        self.trim_pitch = config["trim_pitch"]
        self.max_angle = config["max_angle"]
        self.max_yawangle = config["max_yawangle"]
        self.max_thrust = config["max_thrust"]
        self.min_thrust = config["min_thrust"]
        self.slew_limit = config["slew_limit"]
        self.slew_rate = config["slew_rate"]  
        self.max_thrust_yaw = percentageToThrust(self.max_thrust)
        self.min_thrust_yaw = percentageToThrust(self.min_thrust)
        self.slew_limit_yaw = percentageToThrust(self.slew_limit)
        self.slew_rate_yaw = percentageToThrust(self.slew_rate)            
        return config 
Example #24
0
        vel_topic = rospy.get_param('~vel_topic', vel_topic)

        ## GPIO
        servo_pin = rospy.get_param('~servo_pin', servo_pin)
        motor_pin = rospy.get_param('~motor_pin', motor_pin)
        middle_servo = rospy.get_param('~middle_servo', middle_servo)
        middle_motor = rospy.get_param('~middle_motor', middle_motor)
        revers_servo = rospy.get_param('~revers_servo', revers_servo)
        offset = rospy.get_param('~servo_offset', offset)

        ## rover params

        wheelbase = rospy.get_param('~wheelbase', wheelbase)
        if rospy.has_param('~max_vel'):
            max_vel = rospy.get_param('~max_vel', max_vel)
            cfg_srv.update_configuration({"max_vel": max_vel})
        if rospy.has_param('~min_vel'):
            min_vel = rospy.get_param('~min_vel', min_vel)
            cfg_srv.update_configuration({"min_vel": min_vel})
        if rospy.has_param('~max_angle'):
            max_angle = rospy.get_param('~max_angle', max_angle)
            cfg_srv.update_configuration({"max_angle": max_angle})

        ## PID params
        if rospy.has_param('~use_imu_vel'):
            use_odometry_vel = rospy.get_param('~use_imu_vel',
                                               use_odometry_vel)
            cfg_srv.update_configuration({"use_imu_vel": use_odometry_vel})
        if rospy.has_param('~kP'):
            kP = rospy.get_param('~kP', kP)
            cfg_srv.update_configuration({"kP": kP})
Example #25
0
    return config


if __name__ == "__main__":
    rospy.init_node("pid_coeff_publisher", anonymous=True)
    rospy.loginfo("Start pid coefficient publisher")

    pid_coeff_cmd_topic = rospy.get_param("~pid_coeff_cmd_topic")
    default_pid_coeff_file = rospy.get_param("~default_pid_coeff_file",
                                             "../config/PidCoeffDefault.yaml")
    pub = rospy.Publisher(pid_coeff_cmd_topic, Float32MultiArray, queue_size=1)

    srv = Server(PidCoeffConfig, callback)

    try:
        with open(default_pid_coeff_file, 'r') as f:
            def_pid_coeff = yaml.load(f)
        for item in coeff_names:
            PidCoeffConfig.defaults[item] = def_pid_coeff[item]
    except Exception as e:
        rospy.logerr(
            "Failed to setup default pid coefficients. Error : {}".format(e))

    # Send once to initialize:
    #print "!!!! PUBLISH DEFAULT VALUES : {}".format(PidCoeffConfig.defaults)
    # !!! Probably, this does not work !!!
    srv.update_configuration(PidCoeffConfig.defaults)

    rospy.spin()