/** {@inheritDoc} */
  @Override
  public void execute(final ScanContext context) throws Exception {
    final Device device =
        context.getDevice(context.getMacros().resolveMacros(command.getDeviceName()));

    // Separate read-back device, or use 'set' device?
    final Device readback;
    if (command.getReadback().isEmpty()) readback = device;
    else readback = context.getDevice(context.getMacros().resolveMacros(command.getReadback()));

    //  Wait for the device to reach the value?
    final NumericValueCondition condition;
    if (command.getWait()) {
      // When using completion, readback needs to match "right away"
      final double check_timeout = command.getCompletion() ? 1.0 : command.getTimeout();
      condition =
          new NumericValueCondition(
              readback,
              Comparison.EQUALS,
              command.getStart(),
              command.getTolerance(),
              TimeDuration.ofSeconds(check_timeout));
    } else condition = null;

    final double start = getLoopStart();
    final double end = getLoopEnd();
    final double step = getLoopStep();
    if (step > 0)
      for (double value = start; value <= end; value += step)
        executeStep(context, device, condition, readback, value);
    else // step is < 0, so stepping down
    for (double value = end; value >= start; value += step)
        executeStep(context, device, condition, readback, value);
  }
  /**
   * Execute one step of the loop
   *
   * @param context
   * @param device
   * @param condition
   * @param readback
   * @param value
   * @throws Exception
   */
  private void executeStep(
      final ScanContext context,
      final Device device,
      final NumericValueCondition condition,
      final Device readback,
      double value)
      throws Exception {
    logger.log(
        Level.INFO,
        "Loop setting {0} = {1}{2}",
        new Object[] {device.getAlias(), value, (condition != null ? " (waiting)" : "")});

    // Set device to value for current step of loop
    do_skip = false;
    synchronized (this) {
      thread = Thread.currentThread();
    }
    try {
      if (command.getCompletion())
        device.write(value, TimeDuration.ofSeconds(command.getTimeout()));
      else device.write(value);

      // .. wait for device to reach value
      if (condition != null) {
        condition.setDesiredValue(value);
        condition.await();
      }

      // Log the device's value?
      if (context.isAutomaticLogMode()) {
        final DataLog log = context.getDataLog().get();
        final long serial = log.getNextScanDataSerial();
        log.log(readback.getAlias(), VTypeHelper.createSample(serial, readback.read()));
      }
    } catch (InterruptedException ex) { // Ignore if 'next' was requested
      if (!do_skip) throw ex;
    } finally {
      synchronized (this) {
        thread = null;
      }
    }

    // Execute loop body or show some estimate of progress
    // (not including nested commands)
    if (do_skip) context.workPerformed(implementation.size());
    else context.execute(implementation);

    // If there are no commands that inc. the work units, do it yourself
    if (implementation.size() <= 0) context.workPerformed(1);
  }
  /** {@inheritDoc} */
  @Override
  public void execute(final ScanContext context) throws Exception {
    try {
      final ScanScriptContext script_context = new ScriptCommandContextImpl(context);
      script_object.run(script_context);
    } catch (PyException ex) {
      throw new Exception(command.getScript() + ":" + JythonSupport.getExceptionMessage(ex), ex);
    }

    context.workPerformed(1);
  }