void AMControlMoveAction::startImplementation() { const AMControlInfo& setpoint = *(controlMoveInfo()->controlInfo()); // lookup the control we need to use to run this control_ = getControlForAction(setpoint); // Must have a control, and it must be able to move. if(!control_) { AMErrorMon::report(AMErrorReport(this, AMErrorReport::Alert, -1, QString("There was an error moving the control '%1' into position, because the control was not found. Please report this problem to the Acquaman developers.").arg(setpoint.name()))); notifyFailed(); return; } // check we can move... if(!control_->canMove()) { AMErrorMon::report(AMErrorReport(this, AMErrorReport::Alert, -2, QString("There was an error moving the control '%1' into position, because the control was not connected and ready to move. Please report this problem to the beamline staff.") .arg(control_->name()))); notifyFailed(); return; } // check that the destination is in range... if(control_->valueOutOfRange(controlMoveInfo()->isRelativeMove() ? control_->value()+setpoint.value() : setpoint.value())) { AMErrorMon::report(AMErrorReport(this, AMErrorReport::Alert, -3, QString("There was an error moving the control '%1' into position, because the destination %2 %3 was outside its range. Please report this problem to the beamline staff.") .arg(control_->name()) .arg(setpoint.value()) .arg(setpoint.units()))); notifyFailed(); return; } // connect to its moveSucceeded and moveFailed signals connect(control_, SIGNAL(moveStarted()), this, SLOT(onMoveStarted())); connect(control_, SIGNAL(moveReTargetted()), this, SLOT(onMoveStarted())); // For controls that support allowsMovesWhileMoving(), they might already be moving when we request our move(). A moveReTargetted() signal from them also counts as a moveStarted() for us. connect(control_, SIGNAL(moveFailed(int)), this, SLOT(onMoveFailed(int))); connect(control_, SIGNAL(moveSucceeded()), this, SLOT(onMoveSucceeded())); // remember the start position: startPosition_ = control_->toInfo(); // start the move: int failureExplanation; if(controlMoveInfo()->isRelativeMove()) failureExplanation = control_->moveRelative(setpoint.value()); else failureExplanation = control_->move(setpoint.value()); if(failureExplanation != AMControl::NoFailure) onMoveFailed(failureExplanation); }
void AMExtendedControlEditor::setControl(AMControl *newControl) { if (control_ != newControl) { if (control_) { // Disconnect from control. disconnect( control_, 0, this, 0 ); disconnect( dialog_, 0, control_, 0 ); control_ = 0; // Clear the frame title. if (!titleSetManually_) setTitleText(""); } control_ = newControl; if (control_) { // Set the frame title. if (!titleSetManually_) { if (control_->description().isEmpty()) setTitleText(control_->name()); else setTitleText(control_->description()); } // Set control values precision_ = control_->displayPrecision(); maxValue_ = control_->maximumValue(); minValue_ = control_->minimumValue(); // Make connections. connect(control_, SIGNAL(valueChanged(double)), this, SLOT(onValueChanged(double))); connect(control_, SIGNAL(unitsChanged(QString)), this, SLOT(onUnitsChanged(QString))); connect(control_, SIGNAL(connected(bool)), this, SLOT(onConnectedChanged()) ); connect(control_, SIGNAL(movingChanged(bool)), this, SLOT(onMotion(bool))); connect(control_, SIGNAL(enumChanged()), this, SLOT(onControlEnumChanged())); connect(control_, SIGNAL(moveStarted()), this, SLOT(onControlMoveStarted())); if(!configureOnly_) connect(dialog_, SIGNAL(doubleValueSelected(double)), control_, SLOT(move(double))); else connect(dialog_, SIGNAL(doubleValueSelected(double)), this, SLOT(onNewSetpoint(double))); } onConnectedChanged(); emit controlChanged(control_); } }
// Start a move to the value setpoint: AMControl::FailureExplanation CLSMDriveMotorControl::move(double Setpoint) { if(isMoving()) { if(!allowsMovesWhileMoving()) { AMErrorMon::debug(this, AMPVCONTROL_COULD_NOT_MOVE_WHILE_MOVING, QString("AMPVControl: Could not move %1 (%2) to %3, because the control is already moving.").arg(name()).arg(writePV_->pvName()).arg(setpoint_)); return AlreadyMovingFailure; } if(!moveInProgress()) { // the control is already moving, but it's not one of our moves. In this situation, there is no way that we can start a move and be assured that we'll be notified when OUR move finishes. AMErrorMon::debug(this, AMPVCONTROL_COULD_NOT_MOVE_WHILE_MOVING_EXTERNAL, QString("AMPVControl: Could not move %1 (%2) to %3, because the control is already moving.").arg(name()).arg(writePV_->pvName()).arg(setpoint_)); return AlreadyMovingFailure; } // Otherwise: This control supports mid-move updates, and we're already moving. We just need to update the setpoint and send it. setpoint_ = writeUnitConverter()->convertToRaw(Setpoint); writePV_->setValue(setpoint_); // since the settling phase is considered part of a move, it's OK to be here while settling... But for Acquaman purposes, this will be considered a single re-targetted move, even though the hardware will see two. If we're settling, disable the settling timer, because we only want to respond to the end of the second move. if(settlingInProgress_) { settlingInProgress_ = false; settlingTimer_.stop(); } emit moveReTargetted(); // re-targetted moves will emit moveReTargetted(), although no moveSucceeded()/moveFailed() will be issued for the initial move. } else { settlingInProgress_ = false; stopInProgress_ = false; moveInProgress_ = false; // Flag that "our" move started: startInProgress_ = true; // This is our new target: setpoint_ = writeUnitConverter()->convertToRaw(Setpoint); // Special case: "null move" should complete immediately. Use only if moveStartTolerance() is non-zero, and the move distance is within moveStartTolerance(). if(moveStartTolerance() != 0 && fabs(setpoint()-value()) < moveStartTolerance()) { startInProgress_ = false; moveInProgress_ = true; emit moveStarted(); moveInProgress_ = false; emit moveSucceeded(); } // Normal move: else { // Issue the move command: writePV_->setValue(setpoint_); // start the timer to check if our move failed to start: moveStartTimer_.start(int(moveStartTimeout_*1000.0)); } } return NoFailure; }
void AMControlMoveAction::onMoveStarted() { disconnect(control_, SIGNAL(moveStarted()), this, SLOT(onMoveStarted())); disconnect(control_, SIGNAL(moveReTargetted()), this, SLOT(onMoveStarted())); // From now on, if we get a moveReTargetted() signal, this would be bad: someone is re-directing our move to a different setpoint, so this now counts as a failure. connect(control_, SIGNAL(moveReTargetted()), this, SLOT(onMoveReTargetted())); notifyStarted(); // start the progressTick timer connect(&progressTick_, SIGNAL(timeout()), this, SLOT(onProgressTick())); progressTick_.start(250); }
// This is used to handle the timeout of a move start: void CLSMDriveMotorControl::onMoveStartTimeout() { moveStartTimer_.stop(); // This is only meaningful if one of our moves is in progress. if(startInProgress_) { // give up on this move: startInProgress_ = false; // Special case: only applies if moveTimeoutTolerance_ != 0 AND we've gotten within moveTimeoutTolerance_ of the setpoint. if(moveTimeoutTolerance() != 0.0 && fabs(setpoint() - value()) < moveTimeoutTolerance_) { moveInProgress_ = true; emit moveStarted(); moveInProgress_ = false; emit moveSucceeded(); } else { // The move didn't start within our allowed start period. That counts as a move failed. emit moveFailed(AMControl::TimeoutFailure); } } }
// Re-implemented from AMReadOnlyPVwStatusControl: void AMPVwStatusControl::onMovingChanged(int isMovingValue) { bool nowMoving = (*statusChecker_)(isMovingValue); // according to the hardware. For checking moveSucceeded/moveStarted/moveFailed, use the value delivered in the signal argument, instead of re-checking the PV, in case we respond late and the hardware has already changed again. // In case the hardware is being silly and sending multiple MOVE ACTIVE, MOVE ACTIVE, MOVE ACTIVE states in a row, or MOVE DONE, MOVE DONE, MOVE DONE states in a row: only act on changes. [Edge detection] if(nowMoving == hardwareWasMoving_) return; hardwareWasMoving_ = nowMoving; // moveStarted, moveFailed, moveSucceeded, or transition to settling: /////////////////////////////////////////////////////////////////////// // if we requested one of our moves, and moving just started: if(startInProgress_ && nowMoving) { moveInProgress_ = true; startInProgress_ = false; // This is great... the device started moving within the timeout: // disable the moveStartTimer, we don't need it anymore moveStartTimer_.stop(); emit moveStarted(); } // If one of our moves was running, and we stopped moving: if(moveInProgress_ && !nowMoving) { // Mode 1: No settling: if( settlingTime_ == 0.0) { // That's the end of our move moveInProgress_ = false; // Check if we succeeded... if(inPosition()) { emit moveSucceeded(); } else { emit moveFailed(AMControl::ToleranceFailure); } } // Mode 2: allow settling else { if(!settlingInProgress_) { settlingInProgress_ = true; settlingTimer_.start(int(settlingTime_*1000)); // QTimer uses millisecond time intervals. } } } // "sucessfully" stopped due to a stop() command. if(stopInProgress_ && !nowMoving) { stopInProgress_ = false; // but the move itself has failed, due to a stop() intervention. emit moveFailed(AMControl::WasStoppedFailure); } // Emitting movingChanged(). ///////////////////////////////////// // For external purposes, isMoving() depends on whether the hardware says we're moving, or we're in the settling phase. nowMoving = isMoving(); if(nowMoving != wasMoving_) emit movingChanged(wasMoving_ = nowMoving); }
// Start a move to the value setpoint: AMControl::FailureExplanation AMPVControl::move(double setpoint) { if(isMoving()) { if(!allowsMovesWhileMoving()) { AMErrorMon::debug(this, AMPVCONTROL_COULD_NOT_MOVE_WHILE_MOVING, QString("AMPVControl: Could not move %1 (%2) to %3, because the control is already moving.").arg(name()).arg(writePV_->pvName()).arg(setpoint_)); return AlreadyMovingFailure; } // assuming this control can accept mid-move updates. We just need to update our setpoint and send it. if(!canMove()) { // this would be rare: a past move worked, but now we're no longer connected? AMErrorMon::debug(this, AMPVCONTROL_COULD_NOT_MOVE_BASED_ON_CANMOVE, QString("AMPVControl: Could not move %1 (%2) to %3.").arg(name()).arg(writePV_->pvName()).arg(setpoint_)); return NotConnectedFailure; } setpoint_ = setpoint; writePV_->setValue(setpoint_); completionTimer_.start(int(completionTimeout_*1000.0)); // restart the completion timer... Since this might be another move, give it some more time. // re-targetted moves will emit moveReTargetted(), although no moveSucceeded()/moveFailed() will be issued for the first move. emit moveReTargetted(); // check for done: if(inPosition()) { completionTimer_.stop(); emit movingChanged(moveInProgress_ = false); emit moveSucceeded(); } } // Regular case: start of a new move. else { // kill any old countdowns: completionTimer_.stop(); if(!canMove()) { AMErrorMon::debug(this, AMPVCONTROL_COULD_NOT_MOVE_BASED_ON_CANMOVE, QString("AMPVControl: Could not move %1 (%2) to %3.").arg(name()).arg(writePV_->pvName()).arg(setpoint_)); return NotConnectedFailure; } // new move target: setpoint_ = setpoint; // Issue the move, check on attemptMoveWhenWithinTolerance if(!attemptMoveWhenWithinTolerance_ && inPosition()){ emit moveSucceeded(); } else{ writePV_->setValue(setpoint_); // We're now moving! Let's hope this control makes it... (No way to actually check.) emit movingChanged(moveInProgress_ = true); // emit the signal that we started: emit moveStarted(); // Are we in-position? [With the default tolerance of AMCONTROL_TOLERANCE_DONT_CARE, we will always be in-position, and moves will complete right away, that's the intended behaviour, because we have no other way of knowing when they'll finish.] if(inPosition()) { emit movingChanged(moveInProgress_ = false); emit moveSucceeded(); } else { // start the countdown to see if we get there in time or stall out: (completionTimeout_ is in seconds) completionTimer_.start(int(completionTimeout_*1000.0)); } } } return NoFailure; }
AMControl::FailureExplanation REIXSSpectrometer::move(double setpoint) { if(!isConnected()) { AMErrorMon::report(AMErrorReport(this, AMErrorReport::Alert, 2, "Can't move the spectrometer: some motor controls are not connected. Check that the IOCs are running and the network connections are good.")); return NotConnectedFailure; } // can't start a move while moving if(moveInProgress() || isMoving()) { AMErrorMon::report(AMErrorReport(this, AMErrorReport::Alert, 2, "Can't move the spectrometer, because it's already moving. Stop the spectrometer first before attempting another move.")); return AlreadyMovingFailure; } specifiedEV_ = setpoint; currentFocusOffset_ = specifiedFocusOffset_; currentDetectorTiltOffset_ = specifiedDetectorTiltOffset_; if(currentGrating_ != specifiedGrating_) { emit gratingChanged(currentGrating_ = specifiedGrating_); } /// \todo limits? out of range? moveSetpoint_ = calibration_.computeSpectrometerPosition(specifiedGrating_, specifiedEV_, currentFocusOffset_, currentDetectorTiltOffset_); // build the move action (With sub-actions to run in parallel) moveAction_ = new AMListAction3(new AMListActionInfo3("spectrometer eV move", "spectrometer eV move"), AMListAction3::Parallel, this); // if we need to move the grating into position, add actions for that: if(!gratingInPosition()) { // make a sequential action for the grating moves AMListAction3* gratingAction = new AMListAction3(new AMListActionInfo3("grating move", "grating move")); // sequential by default. // first, move Z to 0 gratingAction->addSubAction(AMActionSupport::buildControlMoveAction(hexapod_->z(), 0)); // then move U,V,W to 0 AMListAction3* uvw0Action = new AMListAction3(new AMListActionInfo3("grating UVW move to 0"), AMListAction3::Parallel); uvw0Action->addSubAction(AMActionSupport::buildControlMoveAction(hexapod_->u(), 0)); uvw0Action->addSubAction(AMActionSupport::buildControlMoveAction(hexapod_->v(), 0)); uvw0Action->addSubAction(AMActionSupport::buildControlMoveAction(hexapod_->w(), 0)); gratingAction->addSubAction(uvw0Action); // then move R,S,T into position (Can do simultaneously with parallel action) AMListAction3* rstAction = new AMListAction3(new AMListActionInfo3("grating RST move"), AMListAction3::Parallel); rstAction->addSubAction(AMActionSupport::buildControlMoveAction(hexapod_->r(), moveSetpoint_.controlNamed("hexapodR").value())); rstAction->addSubAction(AMActionSupport::buildControlMoveAction(hexapod_->s(), moveSetpoint_.controlNamed("hexapodS").value())); rstAction->addSubAction(AMActionSupport::buildControlMoveAction(hexapod_->t(), moveSetpoint_.controlNamed("hexapodT").value())); gratingAction->addSubAction(rstAction); // move U,V,W to actual position AMListAction3* uvwAction = new AMListAction3(new AMListActionInfo3("grating UVW move"), AMListAction3::Parallel); uvwAction->addSubAction(AMActionSupport::buildControlMoveAction(hexapod_->u(), moveSetpoint_.controlNamed("hexapodU").value())); uvwAction->addSubAction(AMActionSupport::buildControlMoveAction(hexapod_->v(), moveSetpoint_.controlNamed("hexapodV").value())); uvwAction->addSubAction(AMActionSupport::buildControlMoveAction(hexapod_->w(), moveSetpoint_.controlNamed("hexapodW").value())); gratingAction->addSubAction(uvwAction); // then move X,Y,Z into position (can do simultaneously with parallel action) AMListAction3* xyzAction = new AMListAction3(new AMListActionInfo3("grating XYZ move"), AMListAction3::Parallel); xyzAction->addSubAction(AMActionSupport::buildControlMoveAction(hexapod_->x(), moveSetpoint_.controlNamed("hexapodX").value())); xyzAction->addSubAction(AMActionSupport::buildControlMoveAction(hexapod_->y(), moveSetpoint_.controlNamed("hexapodY").value())); xyzAction->addSubAction(AMActionSupport::buildControlMoveAction(hexapod_->z(), moveSetpoint_.controlNamed("hexapodZ").value())); gratingAction->addSubAction(xyzAction); moveAction_->addSubAction(gratingAction); } // add Lift, Tilt, and Translation moveAction_->addSubAction(AMActionSupport::buildControlMoveAction(spectrometerRotationDrive_, moveSetpoint_.controlNamed("spectrometerRotationDrive").value())); moveAction_->addSubAction(AMActionSupport::buildControlMoveAction(detectorTiltDrive_, moveSetpoint_.controlNamed("detectorTiltDrive").value())); moveAction_->addSubAction(AMActionSupport::buildControlMoveAction(detectorTranslation_, moveSetpoint_.controlNamed("detectorTranslation").value())); // Disabled for now: moveAction_->addSubAction(new AMInternalControlMoveAction(endstationTranslation_, moveSetpoint_.controlNamed("endstationTranslation").value())) // Watch the move action: succeeded or failed (or cancelled) connect(moveAction_, SIGNAL(stateChanged(int,int)), this, SLOT(onMoveActionStateChanged(int,int))); emit moveStarted(); moveAction_->start(); return NoFailure; }
void AMPIC887EpicsCoordinator::onAllConnected(bool connectedState) { if(connectedState) { if(!controller_->isInValidState()) { qDebug() << "Initialization: Setting Axes status to Error"; xAxisStatus_->move(4); yAxisStatus_->move(4); zAxisStatus_->move(4); uAxisStatus_->move(4); vAxisStatus_->move(4); wAxisStatus_->move(4); } else if (!connectedOnce_) { connectedOnce_ = true; // Initializing x Axis if(!xAxisFeedback_->withinTolerance(controller_->currentPosition(AMGCS2::XAxis))) { qDebug() << "Initialization: Setting x Axis position to " << controller_->currentPosition(AMGCS2::XAxis); xAxisFeedback_->move(controller_->currentPosition(AMGCS2::XAxis)); } if(controller_->movementStatuses().testFlag(AMGCS2::XAxisIsMoving) && !xAxisStatus_->withinTolerance(1)) { qDebug() << "Initialization: Setting x Axis status to moving"; xAxisStatus_->move(1); } else if (!xAxisStatus_->withinTolerance(0)) { qDebug() << "Initialization: Setting x Axis status to move done"; xAxisStatus_->move(0); } // Initializing y Axis if(!yAxisFeedback_->withinTolerance(controller_->currentPosition(AMGCS2::YAxis))) { qDebug() << "Initialzation: Setting y Axis position to " << controller_->currentPosition(AMGCS2::YAxis); yAxisFeedback_->move(controller_->currentPosition(AMGCS2::YAxis)); } if(controller_->movementStatuses().testFlag(AMGCS2::YAxisIsMoving) && !yAxisStatus_->withinTolerance(1)) { qDebug() << "Initialization: Setting y Axis status to moving"; yAxisStatus_->move(1); } else if (!yAxisStatus_->withinTolerance(0)) { qDebug() << "Initialization: Setting y Axis status to move done"; yAxisStatus_->move(0); } // Initializing z Axis if(!zAxisFeedback_->withinTolerance(controller_->currentPosition(AMGCS2::ZAxis))) { qDebug() << "Initialzation: Setting z Axis position to " << controller_->currentPosition(AMGCS2::ZAxis); zAxisFeedback_->move(controller_->currentPosition(AMGCS2::ZAxis)); } if(controller_->movementStatuses().testFlag(AMGCS2::ZAxisIsMoving) && !zAxisStatus_->withinTolerance(1)) { qDebug() << "Initialization: Setting z Axis status to moving"; zAxisStatus_->move(1); } else if (!zAxisStatus_->withinTolerance(0)) { qDebug() << "Initialization: Setting z Axis status to move done"; zAxisStatus_->move(0); } // Initializing u Axis if(!uAxisFeedback_->withinTolerance(controller_->currentPosition(AMGCS2::UAxis))) { qDebug() << "Initialzation: Setting u Axis position to " << controller_->currentPosition(AMGCS2::UAxis); uAxisFeedback_->move(controller_->currentPosition(AMGCS2::UAxis)); } if(controller_->movementStatuses().testFlag(AMGCS2::UAxisIsMoving) && !uAxisStatus_->withinTolerance(1)) { qDebug() << "Initialization: Setting u Axis status to moving"; uAxisStatus_->move(1); } else if (!uAxisStatus_->withinTolerance(0)) { qDebug() << "Initialization: Setting u Axis status to move done"; uAxisStatus_->move(0); } // Initializing v Axis if(!vAxisFeedback_->withinTolerance(controller_->currentPosition(AMGCS2::VAxis))) { qDebug() << "Initialzation: Setting v Axis position to " << controller_->currentPosition(AMGCS2::VAxis); vAxisFeedback_->move(controller_->currentPosition(AMGCS2::VAxis)); } if(controller_->movementStatuses().testFlag(AMGCS2::VAxisIsMoving) && !vAxisStatus_->withinTolerance(1)) { qDebug() << "Initialization: Setting v Axis status to moving"; vAxisStatus_->move(1); } else if (!vAxisStatus_->withinTolerance(0)) { qDebug() << "Initialization: Setting v Axis status to move done"; vAxisStatus_->move(0); } // Initializing w Axis if(!wAxisFeedback_->withinTolerance(controller_->currentPosition(AMGCS2::WAxis))) { qDebug() << "Initialzation: Setting w Axis position to " << controller_->currentPosition(AMGCS2::WAxis); wAxisFeedback_->move(controller_->currentPosition(AMGCS2::WAxis)); } if(controller_->movementStatuses().testFlag(AMGCS2::WAxisIsMoving) && !wAxisStatus_->withinTolerance(1)) { qDebug() << "Initialization: Setting w Axis status to moving"; wAxisStatus_->move(1); } else if (!wAxisStatus_->withinTolerance(0)) { qDebug() << "Initialization: Setting w Axis status to move done"; wAxisStatus_->move(0); } // Initialize system velocity if(!systemVelocityFeedback_->withinTolerance(controller_->systemVelocity())) { qDebug() << "Initialization: Setting system velocity to " << controller_->systemVelocity(); systemVelocityFeedback_->move(controller_->systemVelocity()); } if(!systemVelocitySetpoint_->withinTolerance(controller_->systemVelocity())) { qDebug() << "Initialization: Setting system velocity setpoint to " << controller_->systemVelocity(); systemVelocitySetpoint_->move(controller_->systemVelocity()); } // Initialize the record rate. if(!recordRateFeedback_->withinTolerance(controller_->recordRate())) { qDebug() << "Initialization: Setting record rate to " << controller_->recordRate(); recordRateFeedback_->move(controller_->recordRate()); } if(!recordRateSetpoint_->withinTolerance(controller_->recordRate())) { qDebug() << "Initialization: Setting record rate setpoint to " << controller_->recordRate(); recordRateSetpoint_->move(controller_->recordRate()); } // Initialize data recorder active state double dataRecorderActiveValue = controller_->isDataRecorderActive() ? 1.0 : 0.0; if(dataRecorderActive_->withinTolerance(dataRecorderActiveValue)) { qDebug() << "Initialization: Setting data recorder active status to " << dataRecorderActiveValue; dataRecorderActive_->move(dataRecorderActiveValue); } } connect(systemVelocitySetpoint_, SIGNAL(valueChanged(double)), this, SLOT(onSystemVelocitySetpointChanged(double))); connect(recordRateSetpoint_, SIGNAL(valueChanged(double)), this, SLOT(onRecordRateSetpointPVChanged(double))); connect(dataRecorderActive_, SIGNAL(valueChanged(double)), this, SLOT(onDataRecorderActivePVChanged(double))); connect(stopAll_, SIGNAL(valueChanged(double)), this, SLOT(onStopAll(double))); connect(controller_, SIGNAL(moveStarted(AMGCS2::AxisMovementStatuses)), this, SLOT(onMotionStartedChanged(AMGCS2::AxisMovementStatuses))); connect(controller_, SIGNAL(moveFailed(AMGCS2::AxisMovementStatuses)), this, SLOT(onMotionFailed(AMGCS2::AxisMovementStatuses))); connect(controller_, SIGNAL(moveComplete()), this, SLOT(onMotionCompleted())); connect(controller_, SIGNAL(positionUpdate(AMPIC887AxisMap<double>)), this, SLOT(onPositionUpdate(AMPIC887AxisMap<double>))); connect(controller_, SIGNAL(systemVelocityChanged(double)), this, SLOT(onSystemVelocityChanged(double))); connect(controller_, SIGNAL(recordRateChanged(double)), this, SLOT(onControllerRecordRateChanged(double))); connect(controller_, SIGNAL(dataRecorderActiveStateChanged(bool)), this, SLOT(onControllerDataRecorderActiveChanged(bool))); } }