/**
   * Wait for value of device to reach the desired value (within tolerance)
   *
   * @throws TimeoutException on timeout
   * @throws Exception on interruption or device read error
   */
  @Override
  public void await() throws TimeoutException, Exception {
    final WaitWithTimeout timeout = new WaitWithTimeout(this.timeout);

    // Fetch initial value with get-callback
    initial_value = VTypeHelper.toDouble(device.read(value_check_timeout));

    device.addListener(this);
    try {
      // Synchronize to avoid the following situation:
      // 1. not at desired value
      // 2. device changes and we would be notified
      // 3. ... but that's before we call wait, so we wait forever
      synchronized (this) {
        is_condition_met = isConditionMet();
        while (!is_condition_met) { // Wait for update from device
          if (timeout.waitUntilTimeout(this))
            throw new TimeoutException(
                "Timeout while waiting for " + device + " " + comparison + " " + desired_value);
          if (error != null) throw error;
        }
      }
    } finally {
      device.removeListener(this);
    }
  }
 /** Trigger another check of device's value {@inheritDoc} */
 @Override
 public void deviceChanged(final Device device) {
   synchronized (this) {
     try {
       if (Double.isNaN(initial_value)) initial_value = VTypeHelper.toDouble(device.read());
       is_condition_met = isConditionMet();
     } catch (Exception ex) {
       is_condition_met = false;
       error = ex;
     }
     // Notify await() so it can check again.
     notifyAll();
   }
 }
  /**
   * Simulate one step in the loop iteration
   *
   * @param context {@link SimulationContext}
   * @param device {@link SimulatedDevice} that the loop modifies
   * @param value Value of the loop variable for this iteration
   * @throws Exception on error
   */
  private void simulateStep(
      final SimulationContext context, final SimulatedDevice device, final double value)
      throws Exception {
    // Get previous value
    final double original = VTypeHelper.toDouble(device.read());

    // Estimate execution time
    final double time_estimate = command.getWait() ? device.getChangeTimeEstimate(value) : 0.0;

    // Show command
    final StringBuilder buf = new StringBuilder();
    buf.append("Loop '").append(command.getDeviceName()).append("' = ").append(value);
    command.appendConditionDetail(buf);
    if (!Double.isNaN(original)) buf.append(" [was ").append(original).append("]");
    context.logExecutionStep(context.getMacros().resolveMacros(buf.toString()), time_estimate);

    // Set to (simulated) new value
    device.write(value);

    // Simulate loop body
    context.simulate(implementation);
  }
 /**
  * Determine if the condition is currently met
  *
  * @return <code>true</code> if condition is met
  * @throws Exception on error reading from the device
  */
 public boolean isConditionMet() throws Exception {
   final double value = VTypeHelper.toDouble(device.read());
   // Note that these need to fail "safe" if any of the values are NaN
   switch (comparison) {
     case EQUALS:
       return Math.abs(desired_value - value) <= tolerance;
     case AT_LEAST:
       return value >= desired_value;
     case ABOVE:
       return value > desired_value;
     case AT_MOST:
       return value <= desired_value;
     case BELOW:
       return value < desired_value;
     case INCREASE_BY:
       return value >= initial_value + desired_value;
     case DECREASE_BY:
       return value <= initial_value - desired_value;
     default:
       throw new Error("Condition not implemented: " + comparison);
   }
 }