diff --git a/CHANGELOG.md b/CHANGELOG.md index f3b9a835..f65021ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,7 +16,7 @@ will consitute of a breaking change warranting a new major release: # [unreleased] -# [v2.2.0] to be released +# [v4.0.0] to be released - eive-tmtc: v4.0.0 (to be released) @@ -24,7 +24,7 @@ will consitute of a breaking change warranting a new major release: - CFDP low level protocol bugfix. Requires fsfw update and tmtc update. -# [v2.1.0] to be released +# [v3.0.0] to be released ## Changed @@ -55,6 +55,8 @@ will consitute of a breaking change warranting a new major release: - Transmitter state is not taken into account anymore for writing into the PTME. The PTME should be perfectly capable of generating a valid CADU, even when the transmitter is not ON for any reason. +- OFF mode is ignores in TM store for determining whether a store will be written. The modes will + only be used to cancel a transfer. ## Added @@ -63,6 +65,7 @@ will consitute of a breaking change warranting a new major release: - BPX battery handler is added for EM by default. - ACU dummy HK sets - IMTQ HK sets +- IMTQ dummy now handles power switch ## Fixed @@ -86,6 +89,14 @@ will consitute of a breaking change warranting a new major release: - HK TM store: The HK store dump success event was triggered for cancelled HK dumps. - When a PUS parsing error occured while parsing a TM store file, the dump completion procedure was always executed. +- Some smaller logic fixes in the TM store base class +- Fixed usage of C `abs` instead of C++ `std::abs`, which results in MTQ commands not being + scaled correctly between 1Am² and 0.2Am². +- TCS Heater Handler: Always trigger mode event if a heater goes `OFF` or `ON`. This event might + soon replace the `HEATER_WENT_ON` and `HEATER_WENT_OFF` events. +- Prevent spam of TCS controller heater unavailability event if all heaters are in external control. +- TCS heater switch info set contained invalid values because of a faulty `memcpy` in the TCS + controller. There is not crash risk but the heater states were invalid. # [v2.0.5] 2023-05-11 diff --git a/CMakeLists.txt b/CMakeLists.txt index b5ecf173..eaa2d340 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,9 +9,9 @@ # ############################################################################## cmake_minimum_required(VERSION 3.13) -set(OBSW_VERSION_MAJOR 2) +set(OBSW_VERSION_MAJOR 3) set(OBSW_VERSION_MINOR 0) -set(OBSW_VERSION_REVISION 5) +set(OBSW_VERSION_REVISION 0) # set(CMAKE_VERBOSE TRUE) @@ -98,7 +98,7 @@ set(OBSW_ADD_THERMAL_TEMP_INSERTER ${OBSW_Q7S_EM} CACHE STRING "Add thermal sensor temperature inserter") set(OBSW_ADD_ACS_BOARD - 1 + ${INIT_VAL} CACHE STRING "Add ACS board module") set(OBSW_ADD_GPS_CTRL ${INIT_VAL} diff --git a/bsp_q7s/em/emObjectFactory.cpp b/bsp_q7s/em/emObjectFactory.cpp index 59660b86..5b2935b7 100644 --- a/bsp_q7s/em/emObjectFactory.cpp +++ b/bsp_q7s/em/emObjectFactory.cpp @@ -97,10 +97,10 @@ void ObjectFactory::produce(void* args) { // TODO: Careful! Switching this on somehow messes with the communication with the ProASIC // and will cause xsc_boot_copy commands to always boot to 0 0 // createRadSensorComponent(gpioComIF); - // Still initialize chip select to avoid SPI bus issues. - createRadSensorChipSelect(gpioComIF); #if OBSW_ADD_ACS_BOARD == 1 + // Still initialize chip select to avoid SPI bus issues. + createRadSensorChipSelect(gpioComIF); createAcsBoardComponents(*spiMainComIF, gpioComIF, uartComIF, *pwrSwitcher, true, adis1650x::Type::ADIS16507); #else diff --git a/dummies/ImtqDummy.cpp b/dummies/ImtqDummy.cpp index 8f8d1513..0c8f9076 100644 --- a/dummies/ImtqDummy.cpp +++ b/dummies/ImtqDummy.cpp @@ -3,11 +3,12 @@ #include ImtqDummy::ImtqDummy(object_id_t objectId, object_id_t comif, CookieIF *comCookie, - bool enableHkSets) + power::Switch_t pwrSwitcher, bool enableHkSets) : DeviceHandlerBase(objectId, comif, comCookie), setNoTorque(this), setWithTorque(this), - enableHkSets(enableHkSets) {} + enableHkSets(enableHkSets), + switcher(pwrSwitcher) {} ImtqDummy::~ImtqDummy() = default; @@ -15,6 +16,15 @@ void ImtqDummy::doStartUp() { setMode(MODE_NORMAL); } void ImtqDummy::doShutDown() { setMode(_MODE_POWER_DOWN); } +ReturnValue_t ImtqDummy::getSwitches(const uint8_t **switches, uint8_t *numberOfSwitches) { + if (switcher != power::NO_SWITCH) { + *numberOfSwitches = 1; + *switches = &switcher; + return returnvalue::OK; + } + return DeviceHandlerBase::NO_SWITCH; +} + ReturnValue_t ImtqDummy::buildNormalDeviceCommand(DeviceCommandId_t *id) { return NOTHING_TO_SEND; } ReturnValue_t ImtqDummy::buildTransitionDeviceCommand(DeviceCommandId_t *id) { diff --git a/dummies/ImtqDummy.h b/dummies/ImtqDummy.h index 5a1de175..990df6e0 100644 --- a/dummies/ImtqDummy.h +++ b/dummies/ImtqDummy.h @@ -13,10 +13,12 @@ class ImtqDummy : public DeviceHandlerBase { static const uint8_t SIMPLE_COMMAND_DATA = 1; static const uint8_t PERIODIC_REPLY_DATA = 2; - ImtqDummy(object_id_t objectId, object_id_t comif, CookieIF *comCookie, bool enableHkSets); + ImtqDummy(object_id_t objectId, object_id_t comif, CookieIF *comCookie, + power::Switch_t pwrSwitcher, bool enableHkSets); ~ImtqDummy() override; protected: + ReturnValue_t getSwitches(const uint8_t **switches, uint8_t *numberOfSwitches) override; imtq::HkDatasetNoTorque setNoTorque; imtq::HkDatasetWithTorque setWithTorque; bool enableHkSets; @@ -38,6 +40,8 @@ class ImtqDummy : public DeviceHandlerBase { PoolEntry mtmRawWithTorque = PoolEntry(3); PoolEntry actStatusWithTorque = PoolEntry(1); + power::Switch_t switcher = power::NO_SWITCH; + void doStartUp() override; void doShutDown() override; ReturnValue_t buildNormalDeviceCommand(DeviceCommandId_t *id) override; diff --git a/dummies/helperFactory.cpp b/dummies/helperFactory.cpp index 16de01f0..157d11d3 100644 --- a/dummies/helperFactory.cpp +++ b/dummies/helperFactory.cpp @@ -75,9 +75,10 @@ void dummy::createDummies(DummyCfg cfg, PowerSwitchIF& pwrSwitcher, GpioIF* gpio } auto* imtqAssy = new ImtqAssembly(objects::IMTQ_ASSY); imtqAssy->connectModeTreeParent(satsystem::acs::ACS_SUBSYSTEM); - auto* imtqDummy = - new ImtqDummy(objects::IMTQ_HANDLER, objects::DUMMY_COM_IF, comCookieDummy, enableHkSets); + auto* imtqDummy = new ImtqDummy(objects::IMTQ_HANDLER, objects::DUMMY_COM_IF, comCookieDummy, + power::Switches::PDU1_CH3_MGT_5V, enableHkSets); imtqDummy->enableThermalModule(ThermalStateCfg()); + imtqDummy->setPowerSwitcher(&pwrSwitcher); imtqDummy->connectModeTreeParent(*imtqAssy); if (cfg.addOnlyAcuDummy) { new AcuDummy(objects::ACU_HANDLER, objects::DUMMY_COM_IF, comCookieDummy, enableHkSets); diff --git a/mission/com/TmStoreTaskBase.cpp b/mission/com/TmStoreTaskBase.cpp index 2158cf2a..0470dc04 100644 --- a/mission/com/TmStoreTaskBase.cpp +++ b/mission/com/TmStoreTaskBase.cpp @@ -131,25 +131,22 @@ ReturnValue_t TmStoreTaskBase::performDump(PersistentTmStoreWithTmQueue& store, dumpContext.ptmeBusyCounter = 0; tmSinkBusyCd.resetTimer(); ReturnValue_t result = store.getNextDumpPacket(tmReader, fileHasSwapped); - if (result != returnvalue::OK) { - sif::error << "PersistentTmStore: Getting next dump packet failed" << std::endl; - } else if (fileHasSwapped and result == PersistentTmStore::DUMP_DONE) { + if (fileHasSwapped and result == PersistentTmStore::DUMP_DONE) { // This can happen if a file is corrupted and the next file swap completes the dump. dumpDoneHandler(); return returnvalue::OK; + } else if (result != returnvalue::OK) { + sif::error << "PersistentTmStore: Getting next dump packet failed" << std::endl; + return result; } dumpedLen = tmReader.getFullPacketLen(); - // Only write to VC if mode is on, but always confirm the dump. - // If the mode is OFF, it is assumed the PTME is not usable and is not allowed to be written - // (e.g. to confirm a reset or the transmitter is off anyway). - if (mode == MODE_ON) { - result = channel.write(tmReader.getFullData(), dumpedLen); - if (result == DirectTmSinkIF::IS_BUSY) { - sif::warning << "PersistentTmStore: Unexpected VC channel busy" << std::endl; - } else if (result != returnvalue::OK) { - sif::warning << "PersistentTmStore: Unexpected VC channel write failure" << std::endl; - } + result = channel.write(tmReader.getFullData(), dumpedLen); + if (result == DirectTmSinkIF::IS_BUSY) { + sif::warning << "PersistentTmStore: Unexpected VC channel busy" << std::endl; + } else if (result != returnvalue::OK) { + sif::warning << "PersistentTmStore: Unexpected VC channel write failure" << std::endl; } + result = store.confirmDump(tmReader, fileHasSwapped); if ((result == PersistentTmStore::DUMP_DONE or result == returnvalue::OK)) { dumpPerformed = true; diff --git a/mission/com/VirtualChannel.cpp b/mission/com/VirtualChannel.cpp index ea5527a8..ff3749a9 100644 --- a/mission/com/VirtualChannel.cpp +++ b/mission/com/VirtualChannel.cpp @@ -18,9 +18,7 @@ uint8_t VirtualChannel::getVcid() const { return vcId; } const char* VirtualChannel::getName() const { return vcName.c_str(); } -bool VirtualChannel::isBusy() const { - return ptme.isBusy(vcId); -} +bool VirtualChannel::isBusy() const { return ptme.isBusy(vcId); } void VirtualChannel::cancelTransfer() { ptme.cancelTransfer(vcId); } diff --git a/mission/controller/ThermalController.cpp b/mission/controller/ThermalController.cpp index 7133dd88..6205ce44 100644 --- a/mission/controller/ThermalController.cpp +++ b/mission/controller/ThermalController.cpp @@ -175,7 +175,7 @@ void ThermalController::performControlOperation() { heaterHandler.getAllSwitchStates(heaterSwitchStateArray); { PoolReadGuard pg(&heaterInfo); - std::memcpy(heaterInfo.heaterSwitchState.value, heaterStates.data(), 8); + std::memcpy(heaterInfo.heaterSwitchState.value, heaterSwitchStateArray.data(), 8); { PoolReadGuard pg2(¤tVecPdu2); if (pg.getReadResult() == returnvalue::OK and pg2.getReadResult() == returnvalue::OK) { @@ -1631,13 +1631,18 @@ bool ThermalController::selectAndReadSensorTemp(HeaterContext& htrCtx) { bool ThermalController::chooseHeater(heater::Switch& switchNr, heater::Switch redSwitchNr) { bool heaterAvailable = true; - if (heaterHandler.getHealth(switchNr) != HasHealthIF::HEALTHY) { - if (heaterHandler.getHealth(redSwitchNr) == HasHealthIF::HEALTHY) { + HasHealthIF::HealthState mainHealth = heaterHandler.getHealth(switchNr); + HasHealthIF::HealthState redHealth = heaterHandler.getHealth(redSwitchNr); + if (mainHealth != HasHealthIF::HEALTHY) { + if (redHealth == HasHealthIF::HEALTHY) { switchNr = redSwitchNr; redSwitchNrInUse = true; } else { heaterAvailable = false; - triggerEvent(tcsCtrl::NO_HEALTHY_HEATER_AVAILABLE, switchNr, redSwitchNr); + // Special case: Ground might command/do something with the heaters, so prevent spam. + if (not(mainHealth == EXTERNAL_CONTROL and redHealth == EXTERNAL_CONTROL)) { + triggerEvent(tcsCtrl::NO_HEALTHY_HEATER_AVAILABLE, switchNr, redSwitchNr); + } } } else { redSwitchNrInUse = false; diff --git a/mission/controller/acs/ActuatorCmd.cpp b/mission/controller/acs/ActuatorCmd.cpp index d2fe2d65..a8fab6a4 100644 --- a/mission/controller/acs/ActuatorCmd.cpp +++ b/mission/controller/acs/ActuatorCmd.cpp @@ -61,7 +61,7 @@ void ActuatorCmd::cmdDipolMtq(const double *dipolMoment, int16_t *dipolMomentAct // Scaling along largest element if dipol exceeds maximum uint8_t maxIdx = 0; VectorOperations::maxAbsValue(dipolMomentActuatorDouble, 3, &maxIdx); - double maxAbsValue = abs(dipolMomentActuatorDouble[maxIdx]); + double maxAbsValue = std::abs(dipolMomentActuatorDouble[maxIdx]); if (maxAbsValue > maxDipol) { double scalingFactor = maxDipol / maxAbsValue; VectorOperations::mulScalar(dipolMomentActuatorDouble, scalingFactor, diff --git a/mission/tcs/HeaterHandler.cpp b/mission/tcs/HeaterHandler.cpp index 630dc92c..de4b600d 100644 --- a/mission/tcs/HeaterHandler.cpp +++ b/mission/tcs/HeaterHandler.cpp @@ -268,6 +268,8 @@ void HeaterHandler::handleSwitchOnCommand(heater::Switch heaterIdx) { triggerEvent(GPIO_PULL_HIGH_FAILED, result); } else { triggerEvent(HEATER_WENT_ON, heaterIdx, 0); + EventManagerIF::triggerEvent(helper.heaters[heaterIdx].first->getObjectId(), MODE_INFO, + MODE_ON, 0); { MutexGuard mg(handlerLock, LOCK_TYPE, LOCK_TIMEOUT, LOCK_CTX); heater.switchState = ON; @@ -324,6 +326,8 @@ void HeaterHandler::handleSwitchOffCommand(heater::Switch heaterIdx) { heater.switchState = OFF; } triggerEvent(HEATER_WENT_OFF, heaterIdx, 0); + EventManagerIF::triggerEvent(helper.heaters[heaterIdx].first->getObjectId(), MODE_INFO, + MODE_OFF, 0); // When all switches are off, also main line switch will be turned off if (allSwitchesOff()) { mainLineSwitcher->sendSwitchCommand(mainLineSwitch, PowerSwitchIF::SWITCH_OFF); diff --git a/mission/tmtc/PersistentTmStore.cpp b/mission/tmtc/PersistentTmStore.cpp index 7c3738fc..8174f8a0 100644 --- a/mission/tmtc/PersistentTmStore.cpp +++ b/mission/tmtc/PersistentTmStore.cpp @@ -256,6 +256,7 @@ ReturnValue_t PersistentTmStore::getNextDumpPacket(PusTmReader& reader, bool& fi if (state == State::IDLE or dumpParams.pendingPacketDump) { return returnvalue::FAILED; } + fileHasSwapped = false; reader.setReadOnlyData(fileBuf.data() + dumpParams.currentSize, fileBuf.size() - dumpParams.currentSize); // CRC check to fully ensure this is a valid TM @@ -270,7 +271,6 @@ ReturnValue_t PersistentTmStore::getNextDumpPacket(PusTmReader& reader, bool& fi fileHasSwapped = true; return loadNextDumpFile(); } - fileHasSwapped = false; dumpParams.pendingPacketDump = true; return returnvalue::OK; }