Merge branch 'main' into mekf-fix
Some checks failed
EIVE/eive-obsw/pipeline/pr-main There was a failure building this commit

This commit is contained in:
Marius Eggert 2024-02-05 10:12:13 +01:00
commit b4ace906bc
6 changed files with 168 additions and 389 deletions

View File

@ -16,6 +16,16 @@ will consitute of a breaking change warranting a new major release:
# [unreleased] # [unreleased]
## Changed
- Guidance now uses the coordinate functions from the FSFW.
- Idle should now point the STR away from the earth
## Fixed
- Fixed bugs in `Guidance::comparePtg` and corrected overloading
- Detumbling State Machine is now robust to commanded mode changes.
# [v7.6.0] 2024-01-30 # [v7.6.0] 2024-01-30
- Bumped `eive-tmtc` to v5.13.0 - Bumped `eive-tmtc` to v5.13.0

View File

@ -70,6 +70,9 @@ static constexpr Event SAFE_RATE_VIOLATION = MAKE_EVENT(0, severity::MEDIUM);
static constexpr Event PTG_RATE_VIOLATION = MAKE_EVENT(10, severity::MEDIUM); static constexpr Event PTG_RATE_VIOLATION = MAKE_EVENT(10, severity::MEDIUM);
//! [EXPORT] : [COMMENT] The system has recovered from a rate rotation violation. //! [EXPORT] : [COMMENT] The system has recovered from a rate rotation violation.
static constexpr Event RATE_RECOVERY = MAKE_EVENT(1, severity::MEDIUM); static constexpr Event RATE_RECOVERY = MAKE_EVENT(1, severity::MEDIUM);
//! [EXPORT] : [COMMENT] The detumble transition has failed.
//! //! P1: Last detumble state before failure.
static constexpr Event DETUMBLE_TRANSITION_FAILED = MAKE_EVENT(11, severity::HIGH);
//! [EXPORT] : [COMMENT] Multiple RWs are invalid, uncommandable and therefore higher ACS modes //! [EXPORT] : [COMMENT] Multiple RWs are invalid, uncommandable and therefore higher ACS modes
//! cannot be maintained. //! cannot be maintained.
static constexpr Event MULTIPLE_RW_INVALID = MAKE_EVENT(2, severity::HIGH); static constexpr Event MULTIPLE_RW_INVALID = MAKE_EVENT(2, severity::HIGH);

View File

@ -420,8 +420,8 @@ void AcsController::performPointingCtrl() {
switch (mode) { switch (mode) {
case acs::PTG_IDLE: case acs::PTG_IDLE:
guidance.targetQuatPtgSun(timeDelta, susDataProcessed.sunIjkModel.value, targetQuat, guidance.targetQuatPtgIdle(timeAbsolute, timeDelta, susDataProcessed.sunIjkModel.value,
targetSatRotRate); gpsDataProcessed.gpsPosition.value, targetQuat, targetSatRotRate);
guidance.comparePtg(quatBI, rotRateB, targetQuat, targetSatRotRate, errorQuat, guidance.comparePtg(quatBI, rotRateB, targetQuat, targetSatRotRate, errorQuat,
errorSatRotRate, errorAngle); errorSatRotRate, errorAngle);
ptgCtrl.ptgLaw(&acsParameters.idleModeControllerParameters, errorQuat, errorSatRotRate, ptgCtrl.ptgLaw(&acsParameters.idleModeControllerParameters, errorQuat, errorSatRotRate,
@ -440,9 +440,9 @@ void AcsController::performPointingCtrl() {
break; break;
case acs::PTG_TARGET: case acs::PTG_TARGET:
guidance.targetQuatPtgThreeAxes(timeAbsolute, timeDelta, gpsDataProcessed.gpsPosition.value, guidance.targetQuatPtgTarget(timeAbsolute, timeDelta, gpsDataProcessed.gpsPosition.value,
gpsDataProcessed.gpsVelocity.value, targetQuat, gpsDataProcessed.gpsVelocity.value, targetQuat,
targetSatRotRate); targetSatRotRate);
guidance.comparePtg(quatBI, rotRateB, targetQuat, targetSatRotRate, guidance.comparePtg(quatBI, rotRateB, targetQuat, targetSatRotRate,
acsParameters.targetModeControllerParameters.quatRef, acsParameters.targetModeControllerParameters.quatRef,
acsParameters.targetModeControllerParameters.refRotRate, errorQuat, acsParameters.targetModeControllerParameters.refRotRate, errorQuat,
@ -483,9 +483,8 @@ void AcsController::performPointingCtrl() {
break; break;
case acs::PTG_NADIR: case acs::PTG_NADIR:
guidance.targetQuatPtgNadirThreeAxes( guidance.targetQuatPtgNadir(timeAbsolute, timeDelta, gpsDataProcessed.gpsPosition.value,
timeAbsolute, timeDelta, gpsDataProcessed.gpsPosition.value, gpsDataProcessed.gpsVelocity.value, targetQuat, targetSatRotRate);
gpsDataProcessed.gpsVelocity.value, targetQuat, targetSatRotRate);
guidance.comparePtg(quatBI, rotRateB, targetQuat, targetSatRotRate, guidance.comparePtg(quatBI, rotRateB, targetQuat, targetSatRotRate,
acsParameters.nadirModeControllerParameters.quatRef, acsParameters.nadirModeControllerParameters.quatRef,
acsParameters.nadirModeControllerParameters.refRotRate, errorQuat, acsParameters.nadirModeControllerParameters.refRotRate, errorQuat,
@ -568,9 +567,16 @@ void AcsController::handleDetumbling() {
break; break;
case DetumbleState::DETUMBLE_FROM_PTG: case DetumbleState::DETUMBLE_FROM_PTG:
triggerEvent(acs::PTG_RATE_VIOLATION); triggerEvent(acs::PTG_RATE_VIOLATION);
detumbleTransitionCountdow.resetTimer();
detumbleState = DetumbleState::PTG_TO_SAFE_TRANSITION; detumbleState = DetumbleState::PTG_TO_SAFE_TRANSITION;
break; break;
case DetumbleState::PTG_TO_SAFE_TRANSITION: case DetumbleState::PTG_TO_SAFE_TRANSITION:
if (detumbleTransitionCountdow.hasTimedOut()) {
triggerEvent(acs::DETUMBLE_TRANSITION_FAILED, 2);
detumbleCounter = 0;
detumbleState = DetumbleState::NO_DETUMBLE;
break;
}
if (mode == acs::AcsMode::SAFE) { if (mode == acs::AcsMode::SAFE) {
detumbleState = DetumbleState::DETUMBLE_FROM_SAFE; detumbleState = DetumbleState::DETUMBLE_FROM_SAFE;
} }
@ -578,9 +584,14 @@ void AcsController::handleDetumbling() {
case DetumbleState::DETUMBLE_FROM_SAFE: case DetumbleState::DETUMBLE_FROM_SAFE:
detumbleCounter = 0; detumbleCounter = 0;
// Triggers detumble mode transition in subsystem // Triggers detumble mode transition in subsystem
triggerEvent(acs::SAFE_RATE_VIOLATION); if (mode == acs::AcsMode::SAFE) {
startTransition(mode, acs::SafeSubmode::DETUMBLE); triggerEvent(acs::SAFE_RATE_VIOLATION);
detumbleState = DetumbleState::IN_DETUMBLE; startTransition(mode, acs::SafeSubmode::DETUMBLE);
detumbleState = DetumbleState::IN_DETUMBLE;
break;
}
triggerEvent(acs::DETUMBLE_TRANSITION_FAILED, 3);
detumbleState = DetumbleState::NO_DETUMBLE;
break; break;
case DetumbleState::IN_DETUMBLE: case DetumbleState::IN_DETUMBLE:
if (fusedRotRateData.rotRateTotal.isValid() and if (fusedRotRateData.rotRateTotal.isValid() and
@ -885,6 +896,7 @@ ReturnValue_t AcsController::checkModeCommand(Mode_t mode, Submode_t submode,
} }
void AcsController::modeChanged(Mode_t mode, Submode_t submode) { void AcsController::modeChanged(Mode_t mode, Submode_t submode) {
guidance.resetValues();
if (mode == acs::AcsMode::SAFE) { if (mode == acs::AcsMode::SAFE) {
{ {
PoolReadGuard pg(&rw1SpeedSet); PoolReadGuard pg(&rw1SpeedSet);
@ -903,7 +915,12 @@ void AcsController::modeChanged(Mode_t mode, Submode_t submode) {
rw4SpeedSet.setRwSpeed(0, 10); rw4SpeedSet.setRwSpeed(0, 10);
} }
} }
guidance.resetValues(); if (submode == acs::SafeSubmode::DETUMBLE) {
detumbleState = DetumbleState::IN_DETUMBLE;
}
if (detumbleState == DetumbleState::IN_DETUMBLE and submode != acs::SafeSubmode::DETUMBLE) {
detumbleState = DetumbleState::NO_DETUMBLE;
}
return ExtendedControllerBase::modeChanged(mode, submode); return ExtendedControllerBase::modeChanged(mode, submode);
} }

View File

@ -283,6 +283,10 @@ class AcsController : public ExtendedControllerBase, public ReceivesParameterMes
// Initial delay to make sure all pool variables have been initialized their owners // Initial delay to make sure all pool variables have been initialized their owners
Countdown initialCountdown = Countdown(INIT_DELAY); Countdown initialCountdown = Countdown(INIT_DELAY);
// Countdown after which the detumbling mode change should have been finished
static constexpr dur_millis_t MAX_DURATION = 60 * 1e3;
Countdown detumbleTransitionCountdow = Countdown(MAX_DURATION);
}; };
#endif /* MISSION_CONTROLLER_ACSCONTROLLER_H_ */ #endif /* MISSION_CONTROLLER_ACSCONTROLLER_H_ */

View File

@ -4,415 +4,200 @@ Guidance::Guidance(AcsParameters *acsParameters_) { acsParameters = acsParameter
Guidance::~Guidance() {} Guidance::~Guidance() {}
[[deprecated]] void Guidance::targetQuatPtgSingleAxis(const timeval timeAbsolute, double posSatE[3], void Guidance::targetQuatPtgIdle(timeval timeAbsolute, const double timeDelta,
double velSatE[3], double sunDirI[3], const double sunDirI[3], const double posSatF[4],
double refDirB[3], double quatBI[4], double targetQuat[4], double targetSatRotRate[3]) {
double targetQuat[4], // positive z-Axis of EIVE in direction of sun
double targetSatRotRate[3]) { double zAxisXI[3] = {0, 0, 0};
//------------------------------------------------------------------------------------- VectorOperations<double>::normalize(sunDirI, zAxisXI, 3);
// Calculation of target quaternion to groundstation or given latitude, longitude and altitude
//-------------------------------------------------------------------------------------
// transform longitude, latitude and altitude to ECEF
double targetE[3] = {0, 0, 0};
MathOperations<double>::cartesianFromLatLongAlt( // determine helper vector to point x-Axis and therefore the STR away from Earth
acsParameters->targetModeControllerParameters.latitudeTgt, double helperXI[3] = {0, 0, 0}, posSatI[3] = {0, 0, 0};
acsParameters->targetModeControllerParameters.longitudeTgt, CoordinateTransformations::positionEcfToEci(posSatF, posSatI, &timeAbsolute);
acsParameters->targetModeControllerParameters.altitudeTgt, targetE); VectorOperations<double>::normalize(posSatI, helperXI, 3);
// target direction in the ECEF frame // construct y-axis from helper vector and z-axis
double targetDirE[3] = {0, 0, 0}; double yAxisXI[3] = {0, 0, 0};
VectorOperations<double>::subtract(targetE, posSatE, targetDirE, 3); VectorOperations<double>::cross(zAxisXI, helperXI, yAxisXI);
VectorOperations<double>::normalize(yAxisXI, yAxisXI, 3);
// transformation between ECEF and ECI frame // x-axis completes RHS
double dcmEI[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}}; double xAxisXI[3] = {0, 0, 0};
double dcmIE[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}}; VectorOperations<double>::cross(yAxisXI, zAxisXI, xAxisXI);
double dcmEIDot[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}}; VectorOperations<double>::normalize(xAxisXI, xAxisXI, 3);
MathOperations<double>::ecfToEciWithNutPre(timeAbsolute, *dcmEI, *dcmEIDot);
MathOperations<double>::inverseMatrixDimThree(*dcmEI, *dcmIE);
double dcmIEDot[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}}; // join transformation matrix
MathOperations<double>::inverseMatrixDimThree(*dcmEIDot, *dcmIEDot); double dcmXI[3][3] = {{xAxisXI[0], yAxisXI[0], zAxisXI[0]},
{xAxisXI[1], yAxisXI[1], zAxisXI[1]},
{xAxisXI[2], yAxisXI[2], zAxisXI[2]}};
QuaternionOperations::fromDcm(dcmXI, targetQuat);
// transformation between ECEF and Body frame // calculate of reference rotation rate
double dcmBI[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}}; targetRotationRate(timeDelta, targetQuat, targetSatRotRate);
double dcmBE[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}};
QuaternionOperations::toDcm(quatBI, dcmBI);
MatrixOperations<double>::multiply(*dcmBI, *dcmIE, *dcmBE, 3, 3, 3);
// target Direction in the body frame
double targetDirB[3] = {0, 0, 0};
MatrixOperations<double>::multiply(*dcmBE, targetDirE, targetDirB, 3, 3, 1);
// rotation quaternion from two vectors
double refDir[3] = {0, 0, 0};
refDir[0] = acsParameters->targetModeControllerParameters.refDirection[0];
refDir[1] = acsParameters->targetModeControllerParameters.refDirection[1];
refDir[2] = acsParameters->targetModeControllerParameters.refDirection[2];
double noramlizedTargetDirB[3] = {0, 0, 0};
VectorOperations<double>::normalize(targetDirB, noramlizedTargetDirB, 3);
VectorOperations<double>::normalize(refDir, refDir, 3);
double normTargetDirB = VectorOperations<double>::norm(noramlizedTargetDirB, 3);
double normRefDir = VectorOperations<double>::norm(refDir, 3);
double crossDir[3] = {0, 0, 0};
double dotDirections = VectorOperations<double>::dot(noramlizedTargetDirB, refDir);
VectorOperations<double>::cross(noramlizedTargetDirB, refDir, crossDir);
targetQuat[0] = crossDir[0];
targetQuat[1] = crossDir[1];
targetQuat[2] = crossDir[2];
targetQuat[3] = sqrt(pow(normTargetDirB, 2) * pow(normRefDir, 2) + dotDirections);
VectorOperations<double>::normalize(targetQuat, targetQuat, 4);
//-------------------------------------------------------------------------------------
// calculation of reference rotation rate
//-------------------------------------------------------------------------------------
double velSatB[3] = {0, 0, 0}, velSatBPart1[3] = {0, 0, 0}, velSatBPart2[3] = {0, 0, 0};
// velocity: v_B = dcm_BI * dcmIE * v_E + dcm_BI * DotDcm_IE * v_E
MatrixOperations<double>::multiply(*dcmBE, velSatE, velSatBPart1, 3, 3, 1);
double dcmBEDot[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}};
MatrixOperations<double>::multiply(*dcmBI, *dcmIEDot, *dcmBEDot, 3, 3, 3);
MatrixOperations<double>::multiply(*dcmBEDot, posSatE, velSatBPart2, 3, 3, 1);
VectorOperations<double>::add(velSatBPart1, velSatBPart2, velSatB, 3);
double normVelSatB = VectorOperations<double>::norm(velSatB, 3);
double normRefSatRate = normVelSatB / normTargetDirB;
double satRateDir[3] = {0, 0, 0};
VectorOperations<double>::cross(velSatB, targetDirB, satRateDir);
VectorOperations<double>::normalize(satRateDir, satRateDir, 3);
VectorOperations<double>::mulScalar(satRateDir, normRefSatRate, targetSatRotRate, 3);
//-------------------------------------------------------------------------------------
// Calculation of reference rotation rate in case of star tracker blinding
//-------------------------------------------------------------------------------------
if (acsParameters->targetModeControllerParameters.avoidBlindStr) {
double sunDirB[3] = {0, 0, 0};
MatrixOperations<double>::multiply(*dcmBI, sunDirI, sunDirB, 3, 3, 1);
double exclAngle = acsParameters->strParameters.exclusionAngle,
blindStart = acsParameters->targetModeControllerParameters.blindAvoidStart,
blindEnd = acsParameters->targetModeControllerParameters.blindAvoidStop;
double sightAngleSun =
VectorOperations<double>::dot(acsParameters->strParameters.boresightAxis, sunDirB);
if (!(strBlindAvoidFlag)) {
double critSightAngle = blindStart * exclAngle;
if (sightAngleSun < critSightAngle) {
strBlindAvoidFlag = true;
}
} else {
if (sightAngleSun < blindEnd * exclAngle) {
double normBlindRefRate = acsParameters->targetModeControllerParameters.blindRotRate;
double blindRefRate[3] = {0, 0, 0};
if (sunDirB[1] < 0) {
blindRefRate[0] = normBlindRefRate;
blindRefRate[1] = 0;
blindRefRate[2] = 0;
} else {
blindRefRate[0] = -normBlindRefRate;
blindRefRate[1] = 0;
blindRefRate[2] = 0;
}
VectorOperations<double>::add(blindRefRate, targetSatRotRate, targetSatRotRate, 3);
} else {
strBlindAvoidFlag = false;
}
}
}
// revert calculated quaternion from qBX to qIX
double quatIB[4] = {0, 0, 0, 1};
QuaternionOperations::inverse(quatBI, quatIB);
QuaternionOperations::multiply(quatIB, targetQuat, targetQuat);
} }
void Guidance::targetQuatPtgThreeAxes(const timeval timeAbsolute, const double timeDelta, void Guidance::targetQuatPtgTarget(timeval timeAbsolute, const double timeDelta, double posSatF[3],
double posSatE[3], double velSatE[3], double targetQuat[4], double velSatF[3], double targetQuat[4],
double targetSatRotRate[3]) { double targetSatRotRate[3]) {
//------------------------------------------------------------------------------------- //-------------------------------------------------------------------------------------
// Calculation of target quaternion for target pointing // Calculation of target quaternion for target pointing
//------------------------------------------------------------------------------------- //-------------------------------------------------------------------------------------
// transform longitude, latitude and altitude to cartesian coordiantes (ECEF) // transform longitude, latitude and altitude to cartesian coordiantes (ECEF)
double targetE[3] = {0, 0, 0}; double targetF[3] = {0, 0, 0};
MathOperations<double>::cartesianFromLatLongAlt( MathOperations<double>::cartesianFromLatLongAlt(
acsParameters->targetModeControllerParameters.latitudeTgt, acsParameters->targetModeControllerParameters.latitudeTgt,
acsParameters->targetModeControllerParameters.longitudeTgt, acsParameters->targetModeControllerParameters.longitudeTgt,
acsParameters->targetModeControllerParameters.altitudeTgt, targetE); acsParameters->targetModeControllerParameters.altitudeTgt, targetF);
double targetDirE[3] = {0, 0, 0}; double targetDirF[3] = {0, 0, 0};
VectorOperations<double>::subtract(targetE, posSatE, targetDirE, 3); VectorOperations<double>::subtract(targetF, posSatF, targetDirF, 3);
// transformation between ECEF and ECI frame
double dcmEI[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}};
double dcmIE[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}};
double dcmEIDot[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}};
MathOperations<double>::ecfToEciWithNutPre(timeAbsolute, *dcmEI, *dcmEIDot);
MathOperations<double>::inverseMatrixDimThree(*dcmEI, *dcmIE);
double dcmIEDot[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}};
MathOperations<double>::inverseMatrixDimThree(*dcmEIDot, *dcmIEDot);
// target direction in the ECI frame // target direction in the ECI frame
double posSatI[3] = {0, 0, 0}, targetI[3] = {0, 0, 0}, targetDirI[3] = {0, 0, 0}; double posSatI[3] = {0, 0, 0}, targetI[3] = {0, 0, 0}, targetDirI[3] = {0, 0, 0};
MatrixOperations<double>::multiply(*dcmIE, posSatE, posSatI, 3, 3, 1); CoordinateTransformations::positionEcfToEci(posSatF, posSatI, &timeAbsolute);
MatrixOperations<double>::multiply(*dcmIE, targetE, targetI, 3, 3, 1); CoordinateTransformations::positionEcfToEci(targetF, targetI, &timeAbsolute);
VectorOperations<double>::subtract(targetI, posSatI, targetDirI, 3); VectorOperations<double>::subtract(targetI, posSatI, targetDirI, 3);
// x-axis aligned with target direction // x-axis aligned with target direction
// this aligns with the camera, E- and S-band antennas // this aligns with the camera, E- and S-band antennas
double xAxis[3] = {0, 0, 0}; double xAxisXI[3] = {0, 0, 0};
VectorOperations<double>::normalize(targetDirI, xAxis, 3); VectorOperations<double>::normalize(targetDirI, xAxisXI, 3);
// transform velocity into inertial frame // transform velocity into inertial frame
double velocityI[3] = {0, 0, 0}, velPart1[3] = {0, 0, 0}, velPart2[3] = {0, 0, 0}; double velSatI[3] = {0, 0, 0};
MatrixOperations<double>::multiply(*dcmIE, velSatE, velPart1, 3, 3, 1); CoordinateTransformations::velocityEcfToEci(velSatF, posSatF, velSatI, &timeAbsolute);
MatrixOperations<double>::multiply(*dcmIEDot, posSatE, velPart2, 3, 3, 1);
VectorOperations<double>::add(velPart1, velPart2, velocityI, 3);
// orbital normal vector of target and velocity vector // orbital normal vector of target and velocity vector
double orbitalNormalI[3] = {0, 0, 0}; double orbitalNormalI[3] = {0, 0, 0};
VectorOperations<double>::cross(posSatI, velocityI, orbitalNormalI); VectorOperations<double>::cross(posSatI, velSatI, orbitalNormalI);
VectorOperations<double>::normalize(orbitalNormalI, orbitalNormalI, 3); VectorOperations<double>::normalize(orbitalNormalI, orbitalNormalI, 3);
// y-axis of satellite in orbit plane so that z-axis is parallel to long side of picture // y-axis of satellite in orbit plane so that z-axis is parallel to long side of picture
// resolution // resolution
double yAxis[3] = {0, 0, 0}; double yAxisXI[3] = {0, 0, 0};
VectorOperations<double>::cross(orbitalNormalI, xAxis, yAxis); VectorOperations<double>::cross(orbitalNormalI, xAxisXI, yAxisXI);
VectorOperations<double>::normalize(yAxis, yAxis, 3); VectorOperations<double>::normalize(yAxisXI, yAxisXI, 3);
// z-axis completes RHS // z-axis completes RHS
double zAxis[3] = {0, 0, 0}; double zAxisXI[3] = {0, 0, 0};
VectorOperations<double>::cross(xAxis, yAxis, zAxis); VectorOperations<double>::cross(xAxisXI, yAxisXI, zAxisXI);
// join transformation matrix // join transformation matrix
double dcmIX[3][3] = {{xAxis[0], yAxis[0], zAxis[0]}, double dcmIX[3][3] = {{xAxisXI[0], yAxisXI[0], zAxisXI[0]},
{xAxis[1], yAxis[1], zAxis[1]}, {xAxisXI[1], yAxisXI[1], zAxisXI[1]},
{xAxis[2], yAxis[2], zAxis[2]}}; {xAxisXI[2], yAxisXI[2], zAxisXI[2]}};
QuaternionOperations::fromDcm(dcmIX, targetQuat); QuaternionOperations::fromDcm(dcmIX, targetQuat);
int8_t timeElapsedMax = acsParameters->targetModeControllerParameters.timeElapsedMax; targetRotationRate(timeDelta, targetQuat, targetSatRotRate);
targetRotationRate(timeElapsedMax, timeDelta, targetQuat, targetSatRotRate);
} }
void Guidance::targetQuatPtgGs(const timeval timeAbsolute, const double timeDelta, void Guidance::targetQuatPtgGs(timeval timeAbsolute, const double timeDelta, double posSatF[3],
double posSatE[3], double sunDirI[3], double targetQuat[4], double sunDirI[3], double targetQuat[4],
double targetSatRotRate[3]) { double targetSatRotRate[3]) {
//------------------------------------------------------------------------------------- //-------------------------------------------------------------------------------------
// Calculation of target quaternion for ground station pointing // Calculation of target quaternion for ground station pointing
//------------------------------------------------------------------------------------- //-------------------------------------------------------------------------------------
// transform longitude, latitude and altitude to cartesian coordiantes (ECEF) // transform longitude, latitude and altitude to cartesian coordiantes (ECEF)
double groundStationE[3] = {0, 0, 0}; double posGroundStationF[3] = {0, 0, 0};
MathOperations<double>::cartesianFromLatLongAlt( MathOperations<double>::cartesianFromLatLongAlt(
acsParameters->gsTargetModeControllerParameters.latitudeTgt, acsParameters->gsTargetModeControllerParameters.latitudeTgt,
acsParameters->gsTargetModeControllerParameters.longitudeTgt, acsParameters->gsTargetModeControllerParameters.longitudeTgt,
acsParameters->gsTargetModeControllerParameters.altitudeTgt, groundStationE); acsParameters->gsTargetModeControllerParameters.altitudeTgt, posGroundStationF);
double targetDirE[3] = {0, 0, 0};
VectorOperations<double>::subtract(groundStationE, posSatE, targetDirE, 3);
// transformation between ECEF and ECI frame
double dcmEI[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}};
double dcmIE[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}};
double dcmEIDot[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}};
MathOperations<double>::ecfToEciWithNutPre(timeAbsolute, *dcmEI, *dcmEIDot);
MathOperations<double>::inverseMatrixDimThree(*dcmEI, *dcmIE);
double dcmIEDot[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}};
MathOperations<double>::inverseMatrixDimThree(*dcmEIDot, *dcmIEDot);
// target direction in the ECI frame // target direction in the ECI frame
double posSatI[3] = {0, 0, 0}, groundStationI[3] = {0, 0, 0}, groundStationDirI[3] = {0, 0, 0}; double posSatI[3] = {0, 0, 0}, posGroundStationI[3] = {0, 0, 0}, groundStationDirI[3] = {0, 0, 0};
MatrixOperations<double>::multiply(*dcmIE, posSatE, posSatI, 3, 3, 1); CoordinateTransformations::positionEcfToEci(posSatF, posSatI, &timeAbsolute);
MatrixOperations<double>::multiply(*dcmIE, groundStationE, groundStationI, 3, 3, 1); CoordinateTransformations::positionEcfToEci(posGroundStationI, posGroundStationI, &timeAbsolute);
VectorOperations<double>::subtract(groundStationI, posSatI, groundStationDirI, 3); VectorOperations<double>::subtract(posGroundStationI, posSatI, groundStationDirI, 3);
// negative x-axis aligned with target direction // negative x-axis aligned with target direction
// this aligns with the camera, E- and S-band antennas // this aligns with the camera, E- and S-band antennas
double xAxis[3] = {0, 0, 0}; double xAxisXI[3] = {0, 0, 0};
VectorOperations<double>::normalize(groundStationDirI, xAxis, 3); VectorOperations<double>::normalize(groundStationDirI, xAxisXI, 3);
VectorOperations<double>::mulScalar(xAxis, -1, xAxis, 3); VectorOperations<double>::mulScalar(xAxisXI, -1, xAxisXI, 3);
// get sun vector model in ECI // get sun vector model in ECI
VectorOperations<double>::normalize(sunDirI, sunDirI, 3); VectorOperations<double>::normalize(sunDirI, sunDirI, 3);
// calculate z-axis as projection of sun vector into plane defined by x-axis as normal vector // calculate z-axis as projection of sun vector into plane defined by x-axis as normal vector
// z = sPerpenticular = s - sParallel = s - (x*s)/norm(x)^2 * x // z = sPerpenticular = s - sParallel = s - (x*s)/norm(x)^2 * x
double xDotS = VectorOperations<double>::dot(xAxis, sunDirI); double xDotS = VectorOperations<double>::dot(xAxisXI, sunDirI);
xDotS /= pow(VectorOperations<double>::norm(xAxis, 3), 2); xDotS /= pow(VectorOperations<double>::norm(xAxisXI, 3), 2);
double sunParallel[3], zAxis[3]; double sunParallel[3], zAxisXI[3];
VectorOperations<double>::mulScalar(xAxis, xDotS, sunParallel, 3); VectorOperations<double>::mulScalar(xAxisXI, xDotS, sunParallel, 3);
VectorOperations<double>::subtract(sunDirI, sunParallel, zAxis, 3); VectorOperations<double>::subtract(sunDirI, sunParallel, zAxisXI, 3);
VectorOperations<double>::normalize(zAxis, zAxis, 3); VectorOperations<double>::normalize(zAxisXI, zAxisXI, 3);
// y-axis completes RHS // y-axis completes RHS
double yAxis[3]; double yAxisXI[3];
VectorOperations<double>::cross(zAxis, xAxis, yAxis); VectorOperations<double>::cross(zAxisXI, xAxisXI, yAxisXI);
VectorOperations<double>::normalize(yAxis, yAxis, 3); VectorOperations<double>::normalize(yAxisXI, yAxisXI, 3);
// join transformation matrix // join transformation matrix
double dcmTgt[3][3] = {{xAxis[0], yAxis[0], zAxis[0]}, double dcmXI[3][3] = {{xAxisXI[0], yAxisXI[0], zAxisXI[0]},
{xAxis[1], yAxis[1], zAxis[1]}, {xAxisXI[1], yAxisXI[1], zAxisXI[1]},
{xAxis[2], yAxis[2], zAxis[2]}}; {xAxisXI[2], yAxisXI[2], zAxisXI[2]}};
QuaternionOperations::fromDcm(dcmTgt, targetQuat); QuaternionOperations::fromDcm(dcmXI, targetQuat);
int8_t timeElapsedMax = acsParameters->gsTargetModeControllerParameters.timeElapsedMax; targetRotationRate(timeDelta, targetQuat, targetSatRotRate);
targetRotationRate(timeElapsedMax, timeDelta, targetQuat, targetSatRotRate);
} }
void Guidance::targetQuatPtgSun(double timeDelta, double sunDirI[3], double targetQuat[4], void Guidance::targetQuatPtgNadir(timeval timeAbsolute, const double timeDelta, double posSatE[3],
double targetSatRotRate[3]) { double velSatE[3], double targetQuat[4], double refSatRate[3]) {
//-------------------------------------------------------------------------------------
// Calculation of target quaternion to sun
//-------------------------------------------------------------------------------------
// positive z-Axis of EIVE in direction of sun
double zAxis[3] = {0, 0, 0};
VectorOperations<double>::normalize(sunDirI, zAxis, 3);
// assign helper vector (north pole inertial)
double helperVec[3] = {0, 0, 1};
// construct y-axis from helper vector and z-axis
double yAxis[3] = {0, 0, 0};
VectorOperations<double>::cross(zAxis, helperVec, yAxis);
VectorOperations<double>::normalize(yAxis, yAxis, 3);
// x-axis completes RHS
double xAxis[3] = {0, 0, 0};
VectorOperations<double>::cross(yAxis, zAxis, xAxis);
VectorOperations<double>::normalize(xAxis, xAxis, 3);
// join transformation matrix
double dcmTgt[3][3] = {{xAxis[0], yAxis[0], zAxis[0]},
{xAxis[1], yAxis[1], zAxis[1]},
{xAxis[2], yAxis[2], zAxis[2]}};
QuaternionOperations::fromDcm(dcmTgt, targetQuat);
//----------------------------------------------------------------------------
// Calculation of reference rotation rate
//----------------------------------------------------------------------------
int8_t timeElapsedMax = acsParameters->gsTargetModeControllerParameters.timeElapsedMax;
targetRotationRate(timeElapsedMax, timeDelta, targetQuat, targetSatRotRate);
}
[[deprecated]] void Guidance::targetQuatPtgNadirSingleAxis(const timeval timeAbsolute,
double posSatE[3], double quatBI[4],
double targetQuat[4], double refDirB[3],
double refSatRate[3]) {
//------------------------------------------------------------------------------------- //-------------------------------------------------------------------------------------
// Calculation of target quaternion for Nadir pointing // Calculation of target quaternion for Nadir pointing
//------------------------------------------------------------------------------------- //-------------------------------------------------------------------------------------
double targetDirE[3] = {0, 0, 0};
VectorOperations<double>::mulScalar(posSatE, -1, targetDirE, 3);
// transformation between ECEF and ECI frame
double dcmEI[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}};
double dcmIE[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}};
double dcmEIDot[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}};
MathOperations<double>::ecfToEciWithNutPre(timeAbsolute, *dcmEI, *dcmEIDot);
MathOperations<double>::inverseMatrixDimThree(*dcmEI, *dcmIE);
double dcmIEDot[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}};
MathOperations<double>::inverseMatrixDimThree(*dcmEIDot, *dcmIEDot);
// transformation between ECEF and Body frame
double dcmBI[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}};
double dcmBE[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}};
QuaternionOperations::toDcm(quatBI, dcmBI);
MatrixOperations<double>::multiply(*dcmBI, *dcmIE, *dcmBE, 3, 3, 3);
// target Direction in the body frame
double targetDirB[3] = {0, 0, 0};
MatrixOperations<double>::multiply(*dcmBE, targetDirE, targetDirB, 3, 3, 1);
// rotation quaternion from two vectors
double refDir[3] = {0, 0, 0};
refDir[0] = acsParameters->nadirModeControllerParameters.refDirection[0];
refDir[1] = acsParameters->nadirModeControllerParameters.refDirection[1];
refDir[2] = acsParameters->nadirModeControllerParameters.refDirection[2];
double noramlizedTargetDirB[3] = {0, 0, 0};
VectorOperations<double>::normalize(targetDirB, noramlizedTargetDirB, 3);
VectorOperations<double>::normalize(refDir, refDir, 3);
double normTargetDirB = VectorOperations<double>::norm(noramlizedTargetDirB, 3);
double normRefDir = VectorOperations<double>::norm(refDir, 3);
double crossDir[3] = {0, 0, 0};
double dotDirections = VectorOperations<double>::dot(noramlizedTargetDirB, refDir);
VectorOperations<double>::cross(noramlizedTargetDirB, refDir, crossDir);
targetQuat[0] = crossDir[0];
targetQuat[1] = crossDir[1];
targetQuat[2] = crossDir[2];
targetQuat[3] = sqrt(pow(normTargetDirB, 2) * pow(normRefDir, 2) + dotDirections);
VectorOperations<double>::normalize(targetQuat, targetQuat, 4);
//-------------------------------------------------------------------------------------
// Calculation of reference rotation rate
//-------------------------------------------------------------------------------------
refSatRate[0] = 0;
refSatRate[1] = 0;
refSatRate[2] = 0;
// revert calculated quaternion from qBX to qIX
double quatIB[4] = {0, 0, 0, 1};
QuaternionOperations::inverse(quatBI, quatIB);
QuaternionOperations::multiply(quatIB, targetQuat, targetQuat);
}
void Guidance::targetQuatPtgNadirThreeAxes(const timeval timeAbsolute, const double timeDelta,
double posSatE[3], double velSatE[3],
double targetQuat[4], double refSatRate[3]) {
//-------------------------------------------------------------------------------------
// Calculation of target quaternion for Nadir pointing
//-------------------------------------------------------------------------------------
// transformation between ECEF and ECI frame
double dcmEI[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}};
double dcmIE[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}};
double dcmEIDot[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}};
MathOperations<double>::ecfToEciWithNutPre(timeAbsolute, *dcmEI, *dcmEIDot);
MathOperations<double>::inverseMatrixDimThree(*dcmEI, *dcmIE);
double dcmIEDot[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}};
MathOperations<double>::inverseMatrixDimThree(*dcmEIDot, *dcmIEDot);
// satellite position in inertial reference frame // satellite position in inertial reference frame
double posSatI[3] = {0, 0, 0}; double posSatI[3] = {0, 0, 0};
MatrixOperations<double>::multiply(*dcmIE, posSatE, posSatI, 3, 3, 1); CoordinateTransformations::positionEcfToEci(posSatE, posSatI, &timeAbsolute);
// negative x-axis aligned with position vector // negative x-axis aligned with position vector
// this aligns with the camera, E- and S-band antennas // this aligns with the camera, E- and S-band antennas
double xAxis[3] = {0, 0, 0}; double xAxisXI[3] = {0, 0, 0};
VectorOperations<double>::normalize(posSatI, xAxis, 3); VectorOperations<double>::normalize(posSatI, xAxisXI, 3);
VectorOperations<double>::mulScalar(xAxis, -1, xAxis, 3); VectorOperations<double>::mulScalar(xAxisXI, -1, xAxisXI, 3);
// make z-Axis parallel to major part of camera resolution // make z-Axis parallel to major part of camera resolution
double zAxis[3] = {0, 0, 0}; double zAxisXI[3] = {0, 0, 0};
double velocityI[3] = {0, 0, 0}, velPart1[3] = {0, 0, 0}, velPart2[3] = {0, 0, 0}; double velSatI[3] = {0, 0, 0};
MatrixOperations<double>::multiply(*dcmIE, velSatE, velPart1, 3, 3, 1); CoordinateTransformations::velocityEcfToEci(velSatE, posSatE, velSatI, &timeAbsolute);
MatrixOperations<double>::multiply(*dcmIEDot, posSatE, velPart2, 3, 3, 1); VectorOperations<double>::cross(xAxisXI, velSatI, zAxisXI);
VectorOperations<double>::add(velPart1, velPart2, velocityI, 3); VectorOperations<double>::normalize(zAxisXI, zAxisXI, 3);
VectorOperations<double>::cross(xAxis, velocityI, zAxis);
VectorOperations<double>::normalize(zAxis, zAxis, 3);
// y-Axis completes RHS // y-Axis completes RHS
double yAxis[3] = {0, 0, 0}; double yAxisXI[3] = {0, 0, 0};
VectorOperations<double>::cross(zAxis, xAxis, yAxis); VectorOperations<double>::cross(zAxisXI, xAxisXI, yAxisXI);
// join transformation matrix // join transformation matrix
double dcmTgt[3][3] = {{xAxis[0], yAxis[0], zAxis[0]}, double dcmXI[3][3] = {{xAxisXI[0], yAxisXI[0], zAxisXI[0]},
{xAxis[1], yAxis[1], zAxis[1]}, {xAxisXI[1], yAxisXI[1], zAxisXI[1]},
{xAxis[2], yAxis[2], zAxis[2]}}; {xAxisXI[2], yAxisXI[2], zAxisXI[2]}};
QuaternionOperations::fromDcm(dcmTgt, targetQuat); QuaternionOperations::fromDcm(dcmXI, targetQuat);
int8_t timeElapsedMax = acsParameters->nadirModeControllerParameters.timeElapsedMax; targetRotationRate(timeDelta, targetQuat, refSatRate);
targetRotationRate(timeElapsedMax, timeDelta, targetQuat, refSatRate); }
void Guidance::targetRotationRate(const double timeDelta, double quatIX[4], double *refSatRate) {
if (VectorOperations<double>::norm(quatIXprev, 4) == 0) {
std::memcpy(quatIXprev, quatIX, sizeof(quatIXprev));
}
if (timeDelta != 0.0) {
QuaternionOperations::rotationFromQuaternions(quatIX, quatIXprev, timeDelta, refSatRate);
} else {
std::memcpy(refSatRate, ZERO_VEC3, 3 * sizeof(double));
}
std::memcpy(quatIXprev, quatIX, sizeof(quatIXprev));
} }
void Guidance::comparePtg(double currentQuat[4], double currentSatRotRate[3], double targetQuat[4], void Guidance::comparePtg(double currentQuat[4], double currentSatRotRate[3], double targetQuat[4],
double targetSatRotRate[3], double refQuat[4], double refSatRotRate[3], double targetSatRotRate[3], double refQuat[4], double refSatRotRate[3],
double errorQuat[4], double errorSatRotRate[3], double &errorAngle) { double errorQuat[4], double errorSatRotRate[3], double &errorAngle) {
// First calculate error quaternion between current and target orientation // First calculate error quaternion between current and target orientation without reference
double invTargetQuat[4] = {0, 0, 0, 0}; // quaternion
QuaternionOperations::inverse(targetQuat, invTargetQuat); double errorQuatWoRef[4] = {0, 0, 0, 0};
QuaternionOperations::multiply(currentQuat, invTargetQuat, errorQuat); QuaternionOperations::multiply(currentQuat, targetQuat, errorQuatWoRef);
// Last calculate add rotation from reference quaternion // Then add rotation from reference quaternion
QuaternionOperations::multiply(refQuat, errorQuat, errorQuat); QuaternionOperations::multiply(refQuat, errorQuatWoRef, errorQuat);
// Keep scalar part of quaternion positive // Keep scalar part of quaternion positive
if (errorQuat[3] < 0) { if (errorQuat[3] < 0) {
VectorOperations<double>::mulScalar(errorQuat, -1, errorQuat, 4); VectorOperations<double>::mulScalar(errorQuat, -1, errorQuat, 4);
@ -435,30 +220,9 @@ void Guidance::comparePtg(double currentQuat[4], double currentSatRotRate[3], do
void Guidance::comparePtg(double currentQuat[4], double currentSatRotRate[3], double targetQuat[4], void Guidance::comparePtg(double currentQuat[4], double currentSatRotRate[3], double targetQuat[4],
double targetSatRotRate[3], double errorQuat[4], double targetSatRotRate[3], double errorQuat[4],
double errorSatRotRate[3], double &errorAngle) { double errorSatRotRate[3], double &errorAngle) {
// First calculate error quaternion between current and target orientation double refQuat[4] = {0, 0, 0, 1}, refSatRotRate[3] = {0, 0, 0};
QuaternionOperations::multiply(currentQuat, targetQuat, errorQuat); comparePtg(currentQuat, currentSatRotRate, targetQuat, targetSatRotRate, refQuat, refSatRotRate,
// Keep scalar part of quaternion positive errorQuat, errorSatRotRate, errorAngle);
if (errorQuat[3] < 0) {
VectorOperations<double>::mulScalar(errorQuat, -1, errorQuat, 4);
}
// Calculate error angle
errorAngle = QuaternionOperations::getAngle(errorQuat, true);
// Calculate error satellite rotational rate
VectorOperations<double>::subtract(currentSatRotRate, targetSatRotRate, errorSatRotRate, 3);
}
void Guidance::targetRotationRate(const int8_t timeElapsedMax, const double timeDelta,
double quatIX[4], double *refSatRate) {
if (VectorOperations<double>::norm(quatIXprev, 4) == 0) {
std::memcpy(quatIXprev, quatIX, sizeof(quatIXprev));
}
if (timeDelta != 0.0) {
QuaternionOperations::rotationFromQuaternions(quatIX, quatIXprev, timeDelta, refSatRate);
} else {
std::memcpy(refSatRate, ZERO_VEC3, 3 * sizeof(double));
}
std::memcpy(quatIXprev, quatIX, sizeof(quatIXprev));
} }
ReturnValue_t Guidance::getDistributionMatrixRw(ACS::SensorValues *sensorValues, ReturnValue_t Guidance::getDistributionMatrixRw(ACS::SensorValues *sensorValues,

View File

@ -1,6 +1,7 @@
#ifndef GUIDANCE_H_ #ifndef GUIDANCE_H_
#define GUIDANCE_H_ #define GUIDANCE_H_
#include <fsfw/coordinates/CoordinateTransformations.h>
#include <fsfw/datapool/PoolReadGuard.h> #include <fsfw/datapool/PoolReadGuard.h>
#include <fsfw/globalfunctions/math/MatrixOperations.h> #include <fsfw/globalfunctions/math/MatrixOperations.h>
#include <fsfw/globalfunctions/math/QuaternionOperations.h> #include <fsfw/globalfunctions/math/QuaternionOperations.h>
@ -24,33 +25,18 @@ class Guidance {
ReturnValue_t solarArrayDeploymentComplete(); ReturnValue_t solarArrayDeploymentComplete();
void resetValues(); void resetValues();
// Function to get the target quaternion and reference rotation rate from gps position and void targetQuatPtgIdle(timeval timeAbsolute, const double timeDelta, const double sunDirI[3],
// position of the ground station const double posSatF[4], double targetQuat[4], double targetSatRotRate[3]);
void targetQuatPtgSingleAxis(const timeval timeAbsolute, double posSatE[3], double velSatE[3], void targetQuatPtgTarget(timeval timeAbsolute, const double timeDelta, double posSatF[3],
double sunDirI[3], double refDirB[3], double quatBI[4], double velSatE[3], double quatIX[4], double targetSatRotRate[3]);
double targetQuat[4], double targetSatRotRate[3]); void targetQuatPtgGs(timeval timeAbsolute, const double timeDelta, double posSatF[3],
void targetQuatPtgThreeAxes(const timeval timeAbsolute, const double timeDelta, double posSatE[3],
double velSatE[3], double quatIX[4], double targetSatRotRate[3]);
void targetQuatPtgGs(const timeval timeAbsolute, const double timeDelta, double posSatE[3],
double sunDirI[3], double quatIX[4], double targetSatRotRate[3]); double sunDirI[3], double quatIX[4], double targetSatRotRate[3]);
void targetQuatPtgNadir(timeval timeAbsolute, const double timeDelta, double posSatF[3],
double velSatF[3], double targetQuat[4], double refSatRate[3]);
// Function to get the target quaternion and reference rotation rate for sun pointing after ground void targetRotationRate(const double timeDelta, double quatInertialTarget[4],
// station double *targetSatRotRate);
void targetQuatPtgSun(const double timeDelta, double sunDirI[3], double targetQuat[4],
double targetSatRotRate[3]);
// Function to get the target quaternion and refence rotation rate from gps position for Nadir
// pointing
void targetQuatPtgNadirSingleAxis(const timeval timeAbsolute, double posSatE[3], double quatBI[4],
double targetQuat[4], double refDirB[3], double refSatRate[3]);
void targetQuatPtgNadirThreeAxes(const timeval timeAbsolute, const double timeDelta,
double posSatE[3], double velSatE[3], double targetQuat[4],
double refSatRate[3]);
// @note: Calculates the error quaternion between the current orientation and the target
// quaternion, considering a reference quaternion. Additionally the difference between the actual
// and a desired satellite rotational rate is calculated, again considering a reference rotational
// rate. Lastly gives back the error angle of the error quaternion.
void comparePtg(double currentQuat[4], double currentSatRotRate[3], double targetQuat[4], void comparePtg(double currentQuat[4], double currentSatRotRate[3], double targetQuat[4],
double targetSatRotRate[3], double refQuat[4], double refSatRotRate[3], double targetSatRotRate[3], double refQuat[4], double refSatRotRate[3],
double errorQuat[4], double errorSatRotRate[3], double &errorAngle); double errorQuat[4], double errorSatRotRate[3], double &errorAngle);
@ -58,11 +44,6 @@ class Guidance {
double targetSatRotRate[3], double errorQuat[4], double errorSatRotRate[3], double targetSatRotRate[3], double errorQuat[4], double errorSatRotRate[3],
double &errorAngle); double &errorAngle);
void targetRotationRate(const int8_t timeElapsedMax, const double timeDelta,
double quatInertialTarget[4], double *targetSatRotRate);
// @note: will give back the pseudoinverse matrix for the reaction wheel depending on the valid
// reation wheel maybe can be done in "commanding.h"
ReturnValue_t getDistributionMatrixRw(ACS::SensorValues *sensorValues, double *rwPseudoInv); ReturnValue_t getDistributionMatrixRw(ACS::SensorValues *sensorValues, double *rwPseudoInv);
private: private: