bool ScrollAnimatorWin::scroll(ScrollbarOrientation orientation, ScrollGranularity granularity, float step, float multiplier)
{
    // Don't animate jumping to the beginning or end of the document.
    if (granularity == ScrollByDocument)
        return ScrollAnimator::scroll(orientation, granularity, step, multiplier);

    // This is an animatable scroll.  Calculate the scroll delta.
    PerAxisData* data = (orientation == VerticalScrollbar) ? &m_verticalData : &m_horizontalData;
    float newPos = std::max(std::min(data->m_desiredPos + (step * multiplier), static_cast<float>(m_scrollableArea->scrollSize(orientation))), 0.0f);
    if (newPos == data->m_desiredPos)
        return false;
    data->m_desiredPos = newPos;

    // Calculate the animation velocity.
    if (*data->m_currentPos == data->m_desiredPos)
        return false;
    bool alreadyAnimating = data->m_animationTimer.isActive();
    // There are a number of different sources of scroll requests.  We want to
    // make both keyboard and wheel-generated scroll requests (which can come at
    // unpredictable rates) and autoscrolling from holding down the mouse button
    // on a scrollbar part (where the request rate can be obtained from the
    // scrollbar theme) feel smooth, responsive, and similar.
    //
    // When autoscrolling, the scrollbar's autoscroll timer will call us to
    // increment the desired position by |step| (with |multiplier| == 1) every
    // ScrollbarTheme::theme()->autoscrollTimerDelay() seconds. If we set
    // the desired velocity to exactly this rate, smooth scrolling will neither
    // race ahead (and then have to slow down) nor increasingly lag behind, but
    // will be smooth and synchronized.
    //
    // Note that because of the acceleration period, the current position in
    // this case would lag the desired one by a small, constant amount (see
    // comments on animateScroll()); the exact amount is given by
    //   lag = |step| - v(0.5tA + tD)
    // Where
    //   v = The steady-state velocity,
    //       |step| / ScrollbarTheme::theme()->autoscrollTimerDelay()
    //   tA = accelerationTime()
    //   tD = The time we pretend has already passed when starting to scroll,
    //        |animationTimerDelay|
    //
    // This lag provides some buffer against timer jitter so we're less likely
    // to hit the desired position and stop (and thus have to re-accelerate,
    // causing a visible hitch) while waiting for the next autoscroll increment.
    //
    // Thus, for autoscroll-timer-triggered requests, the ideal steady-state
    // distance to travel in each time interval is:
    //   float animationStep = step;
    // Note that when we're not already animating, this is exactly the same as
    // the distance to the target position.  We'll return to that in a moment.
    //
    // For keyboard and wheel scrolls, we don't know when the next increment
    // will be requested.  If we set the target velocity based on how far away
    // from the target position we are, then for keyboard/wheel events that come
    // faster than the autoscroll delay, we'll asymptotically approach the
    // velocity needed to stay smoothly in sync with the user's actions; for
    // events that come slower, we'll scroll one increment and then pause until
    // the next event fires.
    float animationStep = fabs(newPos - *data->m_currentPos);
    // If a key is held down (or the wheel continually spun), then once we have
    // reached a velocity close to the steady-state velocity, we're likely to
    // hit the desired position at around the same time we'd expect the next
    // increment to occur -- bad because it leads to hitching as described above
    // (if autoscroll-based requests didn't result in a small amount of constant
    // lag).  So if we're called again while already animating, we want to trim
    // the animationStep slightly to maintain lag like what's described above.
    // (I say "maintain" since we'll already be lagged due to the acceleration
    // during the first scroll period.)
    //
    // Remember that trimming won't cause us to fall steadily further behind
    // here, because the further behind we are, the larger the base step value
    // above.  Given the scrolling algorithm in animateScroll(), the practical
    // effect will actually be that, assuming a constant trim factor, we'll lag
    // by a constant amount depending on the rate at which increments occur
    // compared to the autoscroll timer delay.  The exact lag is given by
    //   lag = |step| * ((r / k) - 1)
    // Where
    //   r = The ratio of the autoscroll repeat delay,
    //       ScrollbarTheme::theme()->autoscrollTimerDelay(), to the
    //       key/wheel repeat delay (i.e. > 1 when keys repeat faster)
    //   k = The velocity trim constant given below
    //
    // We want to choose the trim factor such that for calls that come at the
    // autoscroll timer rate, we'll wind up with the same lag as in the
    // "perfect" case described above (or, to put it another way, we'll end up
    // with |animationStep| == |step| * |multiplier| despite the actual distance
    // calculated above being larger than that).  This will result in "perfect"
    // behavior for autoscrolling without having to special-case it.
    if (alreadyAnimating)
        animationStep /= (2.0 - ((1.0 / ScrollbarTheme::theme()->autoscrollTimerDelay()) * (0.5 * accelerationTime() + animationTimerDelay)));
    // The result of all this is that single keypresses or wheel flicks will
    // scroll in the same time period as single presses of scrollbar elements;
    // holding the mouse down on a scrollbar part will scroll as fast as
    // possible without hitching; and other repeated scroll events will also
    // scroll with the same time lag as holding down the mouse on a scrollbar
    // part.
    data->m_desiredVelocity = animationStep / ScrollbarTheme::theme()->autoscrollTimerDelay();

    // If we're not already scrolling, start.
    if (!alreadyAnimating)
        animateScroll(data);
    return true;
}
Exemple #2
0
int main(int argc, char *argv[]) {

  if (argc != 6) {
    std::cout << "Arguments:" << std::endl
      	      << "(1) device file" << std::endl
	      << "(2) CAN deviceID" << std::endl
      	      << "(3) sync rate [msec]" << std::endl
	      << "(4) target velocity [rad/sec]" << std::endl
      	      << "(5) acceleration [rad/sec^2]" << std::endl
	      << "Example 1: ./move_device /dev/pcan32 12 10 0.2 0.05" << std::endl
      	      << "Example 2 (reverse direction): "
	      << "./move_device /dev/pcan32 12 10 -0.2 -0.05" << std::endl;
    return -1;
  }
  std::cout << "Interrupt motion with Ctrl-C" << std::endl;
  std::string deviceFile = std::string(argv[1]);
  uint16_t deviceID = std::stoi(std::string(argv[2]));
  canopen::syncInterval = std::chrono::milliseconds(std::stoi(std::string(argv[3])));
  double targetVel = std::stod(std::string(argv[4]));
  double accel = std::stod(std::string(argv[5]));

  // In this example, the canopen::chainMap is constructed manually,
  // but there are also functions to construct it from a YAML file,
  // or (in ros_canopen) from the ROS parameter server, cf. user manual.
  canopen::using_master_thread = true;
  canopen::DeviceDescription CANdevice;
  CANdevice.name = "device1";
  CANdevice.id = deviceID;
  CANdevice.bus = deviceFile;
  canopen::ChainDescription CANchain;
  CANchain.name = "chain1";
  CANchain.devices.push_back(CANdevice);
  canopen::initChainMap( { CANchain } );

  if (!canopen::openConnection("/dev/pcan32")) {
    std::cout << "Cannot open CAN device; aborting." << std::endl;
    return -1;
  } 

  canopen::initListenerThread();
  canopen::initIncomingPDOProcessorThread();
  std::this_thread::sleep_for(std::chrono::seconds(1));
  canopen::initMasterThread();

  canopen::initNMT();
  for (auto it : canopen::chainMap) 
    it.second->CANopenInit();

  // send velocity commands; these could also come from
  // a different thread, or even a different process
  // as in ros_canopen
  //std::thread client_thread(clientFunc);
  //client_thread.detach();
  std::chrono::milliseconds accelerationTime
    ( static_cast<int>(round( 1000.0 * targetVel / accel)) );

  while (false) {
  double vel = 0;
  auto startTime = std::chrono::high_resolution_clock::now();
  auto tic = std::chrono::high_resolution_clock::now();

  // increasing velocity ramp up to target velocity:
  std::cout << "Accelerating to target velocity" << std::endl;
  while (tic < startTime + accelerationTime) {
    tic = std::chrono::high_resolution_clock::now();
    vel = accel * 0.000001 *
      std::chrono::duration_cast<std::chrono::microseconds>(tic-startTime).count();
    canopen::chainMap["chain1"]->setVel({vel});
    std::this_thread::sleep_for
      (canopen::syncInterval - (std::chrono::high_resolution_clock::now() - tic));
  }

  // constant velocity when target vel has been reached:
  std::cout << "Target velocity reached!" << std::endl;
  canopen::chainMap["chain1"]->setVel({targetVel});
  std::this_thread::sleep_for(std::chrono::seconds(1));
  canopen::chainMap["chain1"]->setVel({0});
  std::this_thread::sleep_for(std::chrono::milliseconds(300));
  
  targetVel = -targetVel;
  accel = -accel;
  }

  while (true)
    std::this_thread::sleep_for(std::chrono::seconds(1));

  return 0;
}
void ScrollAnimatorWin::animateScroll(PerAxisData* data)
{
    // Note on smooth scrolling perf versus non-smooth scrolling perf:
    // The total time to perform a complete scroll is given by
    //   t = t0 + 0.5tA - tD + tS
    // Where
    //   t0 = The time to perform the scroll without smooth scrolling
    //   tA = The acceleration time,
    //        ScrollbarTheme::theme()->autoscrollTimerDelay() (see below)
    //   tD = |animationTimerDelay|
    //   tS = A value less than or equal to the time required to perform a
    //        single scroll increment, i.e. the work done due to calling
    //        client()->valueChanged() (~0 for simple pages, larger for complex
    //        pages).
    //
    // Because tA and tD are fairly small, the total lag (as users perceive it)
    // is negligible for simple pages and roughly tS for complex pages.  Without
    // knowing in advance how large tS is it's hard to do better than this.
    // Perhaps we could try to remember previous values and forward-compensate.


    // We want to update the scroll position based on the time it's been since
    // our last update.  This may be longer than our ideal time, especially if
    // the page is complex or the system is slow.
    //
    // To avoid feeling laggy, if we've just started smooth scrolling we pretend
    // we've already accelerated for one ideal interval, so that we'll scroll at
    // least some distance immediately.
    double lastScrollInterval = data->m_currentVelocity ? (WTF::currentTime() - data->m_lastAnimationTime) : animationTimerDelay;

    // Figure out how far we've actually traveled and update our current
    // velocity.
    float distanceTraveled;
    if (data->m_currentVelocity < data->m_desiredVelocity) {
        // We accelerate at a constant rate until we reach the desired velocity.
        float accelerationRate = data->m_desiredVelocity / accelerationTime();

        // Figure out whether contant acceleration has caused us to reach our
        // target velocity.
        float potentialVelocityChange = accelerationRate * lastScrollInterval;
        float potentialNewVelocity = data->m_currentVelocity + potentialVelocityChange;
        if (potentialNewVelocity > data->m_desiredVelocity) {
            // We reached the target velocity at some point between our last
            // update and now.  The distance traveled can be calculated in two
            // pieces: the distance traveled while accelerating, and the
            // distance traveled after reaching the target velocity.
            float actualVelocityChange = data->m_desiredVelocity - data->m_currentVelocity;
            float accelerationInterval = actualVelocityChange / accelerationRate;
            // The distance traveled under constant acceleration is the area
            // under a line segment with a constant rising slope.  Break this
            // into a triangular portion atop a rectangular portion and sum.
            distanceTraveled = ((data->m_currentVelocity + (actualVelocityChange / 2)) * accelerationInterval);
            // The distance traveled at the target velocity is simply
            // (target velocity) * (remaining time after accelerating).
            distanceTraveled += (data->m_desiredVelocity * (lastScrollInterval - accelerationInterval));
            data->m_currentVelocity = data->m_desiredVelocity;
        } else {
            // Constant acceleration through the entire time interval.
            distanceTraveled = (data->m_currentVelocity + (potentialVelocityChange / 2)) * lastScrollInterval;
            data->m_currentVelocity = potentialNewVelocity;
        }
    } else {
        // We've already reached the target velocity, so the distance we've
        // traveled is simply (current velocity) * (elapsed time).
        distanceTraveled = data->m_currentVelocity * lastScrollInterval;
        // If our desired velocity has decreased, drop the current velocity too.
        data->m_currentVelocity = data->m_desiredVelocity;
    }

    // Now update the scroll position based on the distance traveled.
    if (distanceTraveled >= fabs(data->m_desiredPos - *data->m_currentPos)) {
        // We've traveled far enough to reach the desired position.  Stop smooth
        // scrolling.
        *data->m_currentPos = data->m_desiredPos;
        data->m_currentVelocity = 0;
        data->m_desiredVelocity = 0;
    } else {
        // Not yet at the target position.  Travel towards it and set up the
        // next update.
        if (*data->m_currentPos > data->m_desiredPos)
            distanceTraveled = -distanceTraveled;
        *data->m_currentPos += distanceTraveled;
        data->m_animationTimer.startOneShot(animationTimerDelay);
        data->m_lastAnimationTime = WTF::currentTime();
    }

    notifyPositionChanged();
}
int main(int argc, char *argv[]) {

    if (argc != 6) {
        std::cout << "Arguments:" << std::endl
                  << "(1) CAN interface " << std::endl
                  << "(2) CAN deviceID" << std::endl
                  << "(3) sync rate [msec]" << std::endl
                  << "(4) target velocity [rad/sec]" << std::endl
                  << "(5) acceleration [rad/sec^2]" << std::endl
                  << "(enter acceleration '0' to omit acceleration phase)" << std::endl
                  << "Example 1: ./move_device can0 12 10 0.2 0.05" << std::endl
                  << "Example 2 (reverse direction): "
                  << "./move_device can0 12 10 -0.2 -0.05" << std::endl;
        return -1;
    }
    std::cout << "Interrupt motion with Ctrl-C" << std::endl;
    std::string deviceFile = std::string(argv[1]);
    uint16_t CANid = std::stoi(std::string(argv[2]));
    canopen::syncInterval = std::chrono::milliseconds(std::stoi(std::string(argv[3])));
    double targetVel = std::stod(std::string(argv[4]));
    double accel = std::stod(std::string(argv[5]));

    canopen::Device dev(CANid);
    canopen::devices[ CANid ] = dev; 
    std::this_thread::sleep_for(std::chrono::milliseconds(10));

    canopen::incomingPDOHandlers[ 0x180 + CANid ] = [CANid](const TPCANRdMsg m) { canopen::defaultPDO_incoming_status( CANid, m ); };
    canopen::incomingPDOHandlers[ 0x480 + CANid ] = [CANid](const TPCANRdMsg m) { canopen::defaultPDO_incoming_pos( CANid, m ); };
   // canopen::sendPos = canopen::defaultPDOOutgoing_interpolated;

    std::string chainName = "test_chain";
    std::vector <uint8_t> ids;
    ids.push_back(CANid);
    std::vector <std::string> j_names;
    j_names.push_back("joint_1");
    canopen::deviceGroups[ chainName ] = canopen::DeviceGroup(ids, j_names);

    canopen::init(deviceFile, chainName, canopen::syncInterval);
    std::this_thread::sleep_for(std::chrono::milliseconds(100));

    canopen::sendSync();

    canopen::setMotorState((uint16_t)CANid, canopen::MS_OPERATION_ENABLED);

    // start at current position
    dev.setDesiredPos((double)canopen::devices[CANid].getActualPos());
    dev.setDesiredVel(0);
    canopen::sendData((uint16_t)CANid, (double)dev.getDesiredPos());

    canopen::controlPDO((uint16_t)CANid, canopen::CONTROLWORD_ENABLE_MOVEMENT, 0x00);
    std::this_thread::sleep_for(std::chrono::milliseconds(10));
    dev.setInitialized(true);

    if (accel != 0) {  // accel of 0 means "move at target vel immediately"
        std::cout << "Ramping up desired velocity at acceleration " << accel << std::endl;
        std::chrono::milliseconds accelerationTime( static_cast<int>(round( 1000.0 * targetVel / accel)) );
        double vel = 0;
        auto startTime = std::chrono::high_resolution_clock::now();
        auto tic = std::chrono::high_resolution_clock::now();

        // increasing velocity ramp up to target velocity:
        while (tic < startTime + accelerationTime) {
            tic = std::chrono::high_resolution_clock::now();
            vel = accel * 0.000001 * std::chrono::duration_cast<std::chrono::microseconds>(tic-startTime).count();
            std::cout << ".... vel " << vel << std::endl;
            dev.setDesiredVel(vel);
            std::this_thread::sleep_for(canopen::syncInterval - (std::chrono::high_resolution_clock::now() - tic));
            canopen::sendSync();
        }
        std::cout << "Target velocity reached\n";
    }
    else
    {
      std::cout << "Setting desired velocity to " << targetVel << std::endl;
      dev.setDesiredVel(targetVel);
    }

    // constant velocity when target vel has been reached:
    std::cout << "Running with desired velocity " << dev.getDesiredVel() << std::endl;
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    //std::cout << "sending Statusword request" << std::endl;
    //canopen::sendSDO(CANid, canopen::STATUSWORD);
    std::this_thread::sleep_for(std::chrono::milliseconds(100));

    while (true) {
        std::this_thread::sleep_for(std::chrono::seconds(500));
        std::cout << "Desired Pos " << dev.getDesiredPos() << ", actual pos " << dev.getActualPos() << std::endl;
        canopen::sendSync();
    }
}