#include "BlockChaser.h" using namespace mathutils; BlockChaser::BlockChaser(string name, unsigned int targetSID, string color) : VisualRoutinesStateNode(name) //Pass the name to the parent calss , headpointer_id(MotionManager::invalid_MC_ID) //Store the various motion manager ids , armSequence_id(MotionManager::invalid_MC_ID) , armMC_id(MotionManager::invalid_MC_ID) , m_status(IDLE) //Set the current status to IDLE , TargetEventSID( targetSID ) //Store the visual event id , TargetColor( color ) //Store the target color , shldrIdx(0) //Set the servo indexes to 0 , elbowIdx(0) , wristIdx(0) , leftGripperIdx(0) , rightGripperIdx(0) , panIdx(0) , tiltIdx(0) , alignedShldr(0.0) //Set the aligned servo values to 0.0 , alignedElbow(0.0) , alignedWrist(0.0) , lastHoriz(0.0) //Set the last camera space target location to 0.0, 0.0 , lastVert(0.0) , yDist(0.0) //Set the last world location of the target to 0.0, 0.0 , xDist(0.0) { } void BlockChaser::DoStart() { //Start function for the behavior BehaviorBase::DoStart(); //Call base behavior //Add a persistent motion which will control where the camera looks SharedObject head_mc; headpointer_id = motman->addPersistentMotion(head_mc); //Set the status to AQUIRING_TARGET m_status = AQUIRING_TARGET; //Find the servo indexes for the camera, arm and gripper panIdx = capabilities.findOutputOffset(RobotInfo::outputNames[RobotInfo::HeadOffset+RobotInfo::PanOffset]); tiltIdx = capabilities.findOutputOffset(RobotInfo::outputNames[RobotInfo::HeadOffset+RobotInfo::TiltOffset]); shldrIdx = findArmIndex( RobotInfo::ArmShoulderOffset ); elbowIdx = findArmIndex( RobotInfo::ArmElbowOffset ); wristIdx = findArmIndex( RobotInfo::WristYawOffset ); leftGripperIdx = findArmIndex( RobotInfo::LeftGripperOffset ); rightGripperIdx = findArmIndex( RobotInfo::RightGripperOffset ); //Add the default listeners needed in the program addDefaultListeners(); //Set the camera to point to one edge of the table where the targets should be located MMAccessor headpointer(headpointer_id); headpointer->setJoints(-0.995503,0.673052,-0.995503); } void BlockChaser::DoStop() { //Stop function for the behavior if( headpointer_id != MotionManager::invalid_MC_ID && motman->isOwner( headpointer_id ) > 0 ) { //Remove the head motion motman->removeMotion( headpointer_id ); } //Send the completion event in case this behavior is in a state machine postStateCompletion(); //Base stop function BehaviorBase::DoStop(); } void BlockChaser::processEvent(const EventBase& event) { //Process the timer, motion and vision events if( event.getGeneratorID() == EventBase::motmanEGID ) { //A motion sequence event will be triggered when various motions are completed if( m_status == ALIGNING_WITH_TARGET ) { //If the arm is finished aligning then move to grabbing target m_status = GRABBING_TARGET; } else if( m_status == WAITING_FOR_GRAB ) { //If the arm has moved to try to grab the target if( event.getTypeID() == EventBase::statusETID ) { //If the move is complete, move to checking grab m_status = CHECKING_GRAB; } } else if( m_status == DONE_GRABBING && event.getTypeID() == EventBase::statusETID ) { //If the arm has grabbed the target then remove the listeners and stop the behavior erouter->remove( this ); DoStop(); } return; } else if( event.getGeneratorID() == EventBase::timerEGID ) { //Handle timer events, timers are used to make sure the program //continues should no motion or vision events be triggered if( m_status == WAITING_FOR_GRAB ) { //If we are moving to the target see if we are ready to grab it if( findArm( false ) ) { //If we are ready to grab then move to Checking Grab m_status = CHECKING_GRAB; } else { //If we are not ready to grab make sure the gripper is pointing at the target alignGripperWithTarget(); } } else if( m_status == CHECKING_GRAB ) { //If we are done moving to the target, check to see if we are aligned properly bool onTarget = findArm(); if( onTarget ) { //If we are then go to DONE_GRABBING and grab and move the target m_status = DONE_GRABBING; grabAndMoveTarget(); } else { //If we missed then print an error message cout << "Missed, retrying" << endl; //Remove the motion and listener motman->removeMotion( armMC_id ); erouter->remove( this ); //Add the default listeners again addDefaultListeners(); //Return to the alignment step returnToAlignment(); } } return; } else if(event.getGeneratorID()==EventBase::visObjEGID) { //The following code is adapted from the track ball behavior //It stores the horizontal and vertical coordinates of the target in camera space if(event.getTypeID()==EventBase::statusETID) { const VisionObjectEvent& objev=static_cast(event); lastHoriz=objev.getCenterX(); lastVert=objev.getCenterY(); } else { const VisionObjectEvent &visev = *reinterpret_cast(&event); float const minBallArea = 0.01; // one percent of the camera image if ( visev.getTypeID() == EventBase::deactivateETID || visev.getObjectArea() < minBallArea ) { return; } } } //Re-Calculate the target's world space location updateTargetPosition(); if( ( abs( lastHoriz ) >= 0.01 || abs( lastVert ) >= 0.01 ) && m_status == AQUIRING_TARGET ) { //If the target is not in the center of the camera then return early return; //Wait while the camera aligns } else if( m_status == AQUIRING_TARGET ) { //If the target is in the center of the camera viewport align the gripper m_status = ALIGNING_WITH_TARGET; alignWithTarget(); } else if( m_status == GRABBING_TARGET ) { //If we have finished aligning then move towards the target m_status = WAITING_FOR_GRAB; moveToTarget(); } } void BlockChaser::addDefaultListeners() { //Add a timer listener and visual event listener erouter->addTimer(this, 5, 0); erouter->addListener(this,EventBase::timerEGID,5); erouter->addListener(this,EventBase::visObjEGID,TargetEventSID); } void BlockChaser::updateTargetPosition() { // This code is adapted from the tracking behavior // move the camera a small distance in the direction of the target // This is "proportional" control, because we move the head proportionally further when the error (horiz and vert) is larger float tilt=state->outputs[tiltIdx] - ( lastVert*CameraFOV/7.0f ); float pan=state->outputs[panIdx] + ( lastHoriz*CameraFOV/6.0f ); // now request access to the headpointer we added in DoStart and set the joint angles MMAccessor headpointer(headpointer_id); // Set the camera to look at the target if(NumHeadJoints>2) tilt/=2; // we're going to replicate the tilt parameter in the next call, so divide by 2 headpointer->setJoints(tilt,pan,tilt); float newTilt = deg2rad( 90.0 ) - abs( tilt ); //Subtract the tilt from 90 degrees to make it useable float newPan = -1 * pan; //Take the negative of the pan (assuming the target is on one side of the table) float distance = ( 20.875 * ( tan( newTilt ) ) ) + 3; //Distance from base to target directly //Rotate the distance by the pan //A2:=Cos (45)*A - Sin (45)*B //B2:=Sin (45)*A + Cos (45)*B xDist = ( -1 * distance ) * sin( newPan ); yDist = distance * cos( newPan ); } void BlockChaser::alignWithTarget() { //Align the gripper with the target vertically if( xDist > 0 ) //Make sure the target is on the right side of the table { cout << "Y Dist: " << yDist << endl; if( xDist > 20.25 ) { //Make sure we can reach the target //20.25 = 8.5 + 7.5 + 4.25 = shoulder-elbow + elbow-wrist + wrist-gripper cout << "Out of range" << endl; return; } //First turn the shoulder away from the arm slightly //Shoulder to elbow: 8.5 inches //Elbow to wrist: 7.5 inches cout << "Y Dist: " << yDist << " X Dist " << xDist << endl; float newShldr = 0.0; if( xDist < 7.5 ) { //If the distance in the x axis is less then the length of the elbow to wrist then //set the shoulder to 90 degrees newShldr = deg2rad( 90.0 ); newShldr += asin( ( 7.5 - xDist ) / ( 8.5 ) ); } else if( xDist < 16 ) { //Find the angle which will allow the elbow-wrist portion to be parallel to the x-axis newShldr = asin( ( xDist - 7.5 ) / ( 8.5 ) ); } else { //If we need to extend the arm directly towards the target then set the shoulder to //a convenient value newShldr = 0.734472; } cout << "New Shoulder: " << rad2deg( newShldr ) << endl; //Find the elbow value based on the shoulder value float newElbow = deg2rad( 90.0 ) - abs( newShldr ); newElbow = deg2rad( 90.0 ) - newElbow; newElbow *= -1; //Set the wrist to be pointing towards the target float newWrist = deg2rad( -90.0 ); //Create a motion sequence to move the arm to the new location SharedObject mseq_mc; mseq_mc->advanceTime(750); // 0.75 secs to load position mseq_mc->loadFile("SafePos.pos"); mseq_mc->advanceTime(1500); // 0.75 sec to align vertically mseq_mc->setOutputCmd(shldrIdx, newShldr); mseq_mc->setOutputCmd(elbowIdx, newElbow); mseq_mc->setOutputCmd(wristIdx, newWrist); mseq_mc->setOutputCmd(leftGripperIdx, 0.5); mseq_mc->setOutputCmd(rightGripperIdx, 0.5); //Store the calculated values for the aligned arm alignedShldr = newShldr; alignedElbow = newElbow; alignedWrist = newWrist; //Remove the arm motion if it exists if( armSequence_id != MotionManager::invalid_MC_ID && motman->isOwner( armSequence_id ) > 0 ) { motman->removeMotion( armSequence_id ); } //Add the motion sequence armSequence_id = motman->addPrunableMotion(mseq_mc); //Listen for the motion to complete erouter->addListener( this, EventBase::motmanEGID, armSequence_id ); } } void BlockChaser::moveToTarget() { //Use moveToPoint to move directly towards the target //Create a motion sequence SharedObject arm_mc; armMC_id = motman->addPersistentMotion(arm_mc); //one inch = 25.4 millimeters, use this to convert the inches to millimeters float yMilli = yDist * 25.4; float xMilli = xDist * -25.4; //The x location is negated since my coordinate system is //different then Tekkotsu's //Make sure the arm does not move too fast and use moveToPoint to move to xMilli, yMilli for( int i = 0; i < 5; i++ ) { MMAccessor(armMC_id)->setMaxSpeed(i, 0.3); } MMAccessor(armMC_id)->moveToPoint(yMilli, xMilli, 0); cout << "Moving to: " << xMilli << "," << yMilli << " Target: " << xDist << ", " << yDist << endl; //Listen for when the motion is complete erouter->addListener( this, EventBase::motmanEGID, armMC_id ); } void BlockChaser::grabAndMoveTarget() { //Close the gripper, move the target away and release it //Create a new motion sequence SharedObject mseq_mc; mseq_mc->advanceTime(750); // 0.75 secs to grab mseq_mc->setOutputCmd(shldrIdx, state->outputs[shldrIdx]); mseq_mc->setOutputCmd(elbowIdx, state->outputs[elbowIdx]); mseq_mc->setOutputCmd(wristIdx, state->outputs[wristIdx]); mseq_mc->loadFile("GrabPos.pos"); //Close the gripper, keep the arm stationary mseq_mc->advanceTime(1500); // 0.75 secs to move target mseq_mc->loadFile("FinalPos.pos"); mseq_mc->advanceTime(2250); // 0.75 secs to open gripper mseq_mc->loadFile("OpenGripper.pos"); mseq_mc->advanceTime(3000); // 0.75 secs to move away from target mseq_mc->loadFile("SafeFinal.pos"); //Remove arm motion if( armSequence_id != MotionManager::invalid_MC_ID && motman->isOwner( armSequence_id ) > 0 ) { motman->removeMotion( armSequence_id ); } motman->removeMotion( armMC_id ); //Add motion to motion manager armSequence_id = motman->addPrunableMotion(mseq_mc); erouter->remove( this ); //Listen for when the motion is complete erouter->addListener( this, EventBase::motmanEGID, armSequence_id ); } bool BlockChaser::findArm(bool printResults /*= true*/) { //Check to see if the gripper is surrounding the target //Clear the shape and sketch spaces camShS.clear(); camSkS.clear(); //Find the blue lines and ellipses NEW_SKETCH(camFrame, uchar, sketchFromSeg()); NEW_SKETCH(blue_stuff, bool, visops::colormask(camFrame,"blue")); NEW_SHAPEVEC(lines, LineData, LineData::extractLines(blue_stuff)); NEW_SHAPEVEC(blueEllipses, EllipseData, EllipseData::extractEllipses(blue_stuff)); //If no ellipses were found then print an error message if( blueEllipses.size() == 0 ) { if( printResults ) { cout << "ERROR: Can't see gripper!" << endl; } return false; } //Find an ellipse of the target color NEW_SKETCH(target_stuff, bool, visops::colormask(camFrame,TargetColor)); NEW_SHAPEVEC(ellipses, EllipseData, EllipseData::extractEllipses(target_stuff)); //If no targets were found print an error message if( ellipses.size() == 0 ) { if( printResults ) { cout << "ERROR: Can't see target, assuming I didn't get it!" << endl; } return false; } //We need to find the blue lines on the gripper //--------------------------------------- //The following block of code will find all the lines and store their //distances from the blue ellipse in a vector vector< int > distances; SHAPEVEC_ITERATE(lines, LineData, line) EndPoint point = line->end1Pt(); EndPoint point2 = line->end2Pt(); float xCoord = ( point.coordX() + point2.coordX() ) / 2.0; float yCoord = ( point.coordY() + point2.coordY() ) / 2.0; float actX = blueEllipses[ 0 ]->getCentroid().coordX(); float actY = blueEllipses[ 0 ]->getCentroid().coordY(); float dist = sqrt( pow( ( xCoord - actX ), 2 ) + pow( ( yCoord - actY ), 2 ) ); distances.push_back( dist ); END_ITERATE; //--------------------------------------- //This block of code will find the line with the minimum distance from the ellipse //--------------------------------------- float minDist = -1; unsigned int minIndex = -1; for( unsigned int idx = 0; idx < distances.size(); idx++ ) { if( minDist == -1 || distances[ idx ] < minDist ) { minDist = distances[ idx ]; minIndex = idx; } } //--------------------------------------- //This block of code will find the line with the second //shortest distance from the ellipse //--------------------------------------- float minDist1 = -1; float minDist2 = -1; unsigned int minIndex1 = -1; unsigned int minIndex2 = -1; for( unsigned int idx = 0; idx < distances.size(); idx++ ) { if( idx != minIndex ) { if( minDist1 == -1 || distances[ idx ] < minDist1 ) { minDist2 = minDist1; minIndex2 = minIndex1; minDist1 = distances[ idx ]; minIndex1 = idx; } else if( minDist2 == -1 || distances[ idx ] < minDist2 ) { minDist2 = distances[ idx ]; minIndex2 = idx; } } if( minDist == -1 || distances[ idx ] < minDist ) { minDist = distances[ idx ]; minIndex = idx; } } //--------------------------------------- //If two lines were not found print an error message if( minDist1 == -1 || minDist2 == -1 ) { if( printResults ) { cout << "ERROR: Can not see gripper..." << endl; } return false; } //Create a sketch which will store the outline of the grab area Sketch grabSOutline(camSkS, "GrabOutline"); grabSOutline = false; //Clear the sketch //Add all permutations of the endpoints to the sketch as lines to ensure the outline is added addLineToSketch( grabSOutline, lines[ minIndex1 ]->end1Pt(), lines[ minIndex2 ]->end1Pt() ); //1-1 to 2-1 addLineToSketch( grabSOutline, lines[ minIndex1 ]->end1Pt(), lines[ minIndex2 ]->end2Pt() ); //1-1 to 2-2 addLineToSketch( grabSOutline, lines[ minIndex1 ]->end2Pt(), lines[ minIndex2 ]->end1Pt() ); //1-2 to 2-1 addLineToSketch( grabSOutline, lines[ minIndex1 ]->end2Pt(), lines[ minIndex2 ]->end2Pt() ); //1-2 to 2-2 addLineToSketch( grabSOutline, lines[ minIndex1 ]->end1Pt(), lines[ minIndex1 ]->end2Pt() ); //1-1 to 1-2 addLineToSketch( grabSOutline, lines[ minIndex2 ]->end1Pt(), lines[ minIndex2 ]->end2Pt() ); //2-1 to 2-2 //Set the outline to green and draw it grabSOutline->setColor("green"); grabSOutline->V(); //Create a final sketch of the target area by filling in the previous sketch and adding back in //the lines NEW_SKETCH(b_filled, bool, visops::fillInterior( grabSOutline )); addLineToSketch( b_filled, lines[ minIndex1 ]->end1Pt(), lines[ minIndex2 ]->end1Pt() ); //1-1 to 2-1 addLineToSketch( b_filled, lines[ minIndex1 ]->end1Pt(), lines[ minIndex2 ]->end2Pt() ); //1-1 to 2-2 addLineToSketch( b_filled, lines[ minIndex1 ]->end2Pt(), lines[ minIndex2 ]->end1Pt() ); //1-2 to 2-1 addLineToSketch( b_filled, lines[ minIndex1 ]->end2Pt(), lines[ minIndex2 ]->end2Pt() ); //1-2 to 2-2 addLineToSketch( b_filled, lines[ minIndex1 ]->end1Pt(), lines[ minIndex1 ]->end2Pt() ); //1-1 to 1-2 addLineToSketch( b_filled, lines[ minIndex2 ]->end1Pt(), lines[ minIndex2 ]->end2Pt() ); //2-1 to 2-2 //Find the center of the ellipse int centerX = ellipses[ 0 ]->getCentroid().coordX(); int centerY = ellipses[ 0 ]->getCentroid().coordY(); //Return true if the center exists in the grab zone false otherwise return b_filled( centerX, centerY ); } void BlockChaser::addLineToSketch( Sketch& sketch, EndPoint point1, EndPoint point2 ) { //This code is based off of the Bresenham Line algorithm and will draw a line //from point1 to point2 in the sketch int x0 = point1.coordX(); int y0 = point1.coordY(); int x1 = point2.coordX(); int y1 = point2.coordY(); //Bresenham's line algorithm bool steep = abs( y1 - y0 ) > abs( x1 - x0 ); if( steep ) { swap( x0, y0 ); swap( x1, y1 ); } if( x0 > x1 ) { swap( x0, x1 ); swap( y0, y1 ); } int deltaX = x1 - x0; int deltaY = abs( y1 - y0 ); int error = deltaX / 2; int ystep; int y = y0; if( y0 < y1 ) { ystep = 1; } else { ystep = -1; } for( int x = x0; x <= x1; x++ ) { if( steep ) { sketch( y, x ) = true; } else { sketch( x, y ) = true; } error -= deltaY; if( error < 0 ) { y += ystep; error += deltaX; } } } void BlockChaser::alignGripperWithTarget() { //Let's find where the wrist is! float shldr= state->outputs[shldrIdx]; float elbow= state->outputs[elbowIdx]; //First use the shoulder rotation //A2:=Cos (45)*A - Sin (45)*B //B2:=Sin (45)*A + Cos (45)*B float xWrist = -8.5 * sin( shldr ); float yWrist = 8.5 * cos( shldr ); float xGripper = xWrist + ( -12.0 * sin( shldr + elbow ) ); float yGripper = yWrist + ( 12.0 * cos( shldr + elbow ) ); //Now add in the elbow rotation xWrist += ( -7.5 * sin( shldr + elbow ) ); yWrist += ( 7.5 * cos( shldr + elbow ) ); //Lets find the angle between the lines defined by the following points: //Line1: ( xWrist, yWrist ) to ( xGripper, yGripper ) //Line2: ( xWrist, yWrist ) to ( xDist, yDist ) //tan( angle ) = ( slope2 - slope1 ) / ( 1 + ( slope1 * slope2 ) ) //angle = arctan of that float slope1 = ( yGripper - yWrist ) / ( xGripper - xWrist ); float slope2 = ( yDist - yWrist ) / ( xDist - xWrist ); float wrist = atan( ( slope2 - slope1 ) / ( 1 + ( slope1 * slope2 ) ) ); wrist = -1 * abs( wrist ); //Wrist now stores the value for the wrist to have to point at the target //Set the wrist servo to use the new value MMAccessor(armMC_id)->setJointValue(2, wrist); } void BlockChaser::returnToAlignment() { //If we missed the gripper then return to the alignment step //Set the status to the ALIGNING_WITH_TARGET m_status = ALIGNING_WITH_TARGET; //Create a motion sequence SharedObject mseq_mc; //Move back to the aligned position mseq_mc->advanceTime(1000); // 1 sec to align vertically mseq_mc->setOutputCmd(shldrIdx, alignedShldr); mseq_mc->setOutputCmd(elbowIdx, alignedElbow); mseq_mc->setOutputCmd(wristIdx, alignedWrist); mseq_mc->setOutputCmd(leftGripperIdx, 0.5); //Open the gripper mseq_mc->setOutputCmd(rightGripperIdx, 0.5); //Remove the arm motion sequence if( armSequence_id != MotionManager::invalid_MC_ID && motman->isOwner( armSequence_id ) > 0 ) { motman->removeMotion( armSequence_id ); } //Add the motion sequence armSequence_id = motman->addPrunableMotion(mseq_mc); //Listen for when the motion sequence is complete erouter->addListener( this, EventBase::motmanEGID, armSequence_id ); }