fsfw/src/fsfw/devicehandlers/DeviceHandlerBase.h

1291 lines
51 KiB
C++

#ifndef FSFW_DEVICEHANDLERS_DEVICEHANDLERBASE_H_
#define FSFW_DEVICEHANDLERS_DEVICEHANDLERBASE_H_
#include <map>
#include "DeviceCommunicationIF.h"
#include "DeviceHandlerFailureIsolation.h"
#include "DeviceHandlerIF.h"
#include "DeviceHandlerThermalSet.h"
#include "fsfw/action/ActionHelper.h"
#include "fsfw/action/HasActionsIF.h"
#include "fsfw/datapool/PoolVariableIF.h"
#include "fsfw/datapoollocal/HasLocalDataPoolIF.h"
#include "fsfw/datapoollocal/LocalDataPoolManager.h"
#include "fsfw/health/HealthHelper.h"
#include "fsfw/ipc/MessageQueueIF.h"
#include "fsfw/modes/HasModesIF.h"
#include "fsfw/objectmanager/SystemObject.h"
#include "fsfw/parameters/ParameterHelper.h"
#include "fsfw/power/PowerSwitchIF.h"
#include "fsfw/returnvalues/HasReturnvaluesIF.h"
#include "fsfw/serviceinterface/ServiceInterface.h"
#include "fsfw/serviceinterface/serviceInterfaceDefintions.h"
#include "fsfw/tasks/ExecutableObjectIF.h"
#include "fsfw/tasks/PeriodicTaskIF.h"
namespace Factory {
void setStaticFrameworkObjectIds();
}
class StorageManagerIF;
/**
* @defgroup devices Devices
* Contains all devices and the DeviceHandlerBase class.
*/
/**
* @brief This is the abstract base class for device handlers.
* @details
* Documentation: Dissertation Baetz p.138,139, p.141-149
*
* It features handling of @link DeviceHandlerIF::Mode_t Modes @endlink,
* communication with physical devices, using the
* @link DeviceCommunicationIF @endlink, and communication with commanding
* objects. It inherits SystemObject and thus can be created by the
* ObjectManagerIF.
*
* This class uses the opcode of ExecutableObjectIF to perform a
* step-wise execution. For each step a different action is selected and
* executed. Currently, the device handler base performs a 4-step
* execution related to 4 communication steps (based on RMAP).
* NOTE: RMAP is a standard which is used for Flying Laptop.
* RMAP communication is not mandatory for projects implementing the FSFW.
* However, the communication principles are similar to RMAP as there are
* two write and two send calls involved.
*
* Device handler instances should extend this class and implement the abstract
* functions. Components and drivers can send so called cookies which are used
* for communication and contain information about the communcation (e.g. slave
* address for I2C or RMAP structs).
* The following abstract methods must be implemented by a device handler:
* 1. doStartUp()
* 2. doShutDown()
* 3. buildTransitionDeviceCommand()
* 4. buildNormalDeviceCommand()
* 5. buildCommandFromCommand()
* 6. fillCommandAndReplyMap()
* 7. scanForReply()
* 8. interpretDeviceReply()
*
* Other important virtual methods with a default implementation
* are the getTransitionDelayMs() function and the getSwitches() function.
* If a transition to MODE_ON is desired without commanding, override the
* intialize() function and call setMode(_MODE_START_UP) before calling
* DeviceHandlerBase::initialize().
*
* @ingroup devices
*/
class DeviceHandlerBase : public DeviceHandlerIF,
public HasReturnvaluesIF,
public ExecutableObjectIF,
public SystemObject,
public HasModesIF,
public HasHealthIF,
public HasActionsIF,
public ReceivesParameterMessagesIF,
public HasLocalDataPoolIF {
friend void(Factory::setStaticFrameworkObjectIds)();
public:
/**
* The constructor passes the objectId to the SystemObject().
*
* @param setObjectId the ObjectId to pass to the SystemObject() Constructor
* @param deviceCommuncation Communcation Interface object which is used
* to implement communication functions
* @param comCookie This object will be passed to the communication inter-
* face and can contain user-defined information about the communication.
* @param fdirInstance
* @param cmdQueueSize
*/
DeviceHandlerBase(object_id_t setObjectId, object_id_t deviceCommunication, CookieIF *comCookie,
FailureIsolationBase *fdirInstance = nullptr, size_t cmdQueueSize = 20);
void setHkDestination(object_id_t hkDestination);
/**
* If the device handler is controlled by the FSFW thermal building blocks,
* this function should be called to initialize all required components.
* The device handler will then take care of creating local pool entries
* for the device thermal state and device heating request.
* Custom local pool IDs can be assigned as well.
* @param thermalStatePoolId
* @param thermalRequestPoolId
*/
void setThermalStateRequestPoolIds(
lp_id_t thermalStatePoolId = DeviceHandlerIF::DEFAULT_THERMAL_STATE_POOL_ID,
lp_id_t thermalRequestPoolId = DeviceHandlerIF::DEFAULT_THERMAL_HEATING_REQUEST_POOL_ID,
uint32_t thermalSetId = DeviceHandlerIF::DEFAULT_THERMAL_SET_ID);
/**
* @brief Helper function to ease device handler development.
* This will instruct the transition to MODE_ON immediately
* (leading to doStartUp() being called for the transition to the ON mode),
* so external mode commanding is not necessary anymore.
*
* This has to be called before the task is started!
* (e.g. in the task factory). This is only a helper function for
* development. Regular mode commanding should be performed by commanding
* the AssemblyBase or Subsystem objects resposible for the device handler.
*/
void setStartUpImmediately();
/**
* @brief This function is the device handler base core component and is
* called periodically.
* @details
* General sequence, showing where abstract virtual functions are called:
* If the State is SEND_WRITE:
* 1. Set the cookie state to COOKIE_UNUSED and read the command queue
* 2. Handles Device State Modes by calling doStateMachine().
* This function calls callChildStatemachine() which calls the
* abstract functions doStartUp() and doShutDown()
* 3. Check switch states by calling checkSwitchStates()
* 4. Decrements counter for timeout of replies by calling
* decrementDeviceReplyMap()
* 5. Performs FDIR check for failures
* 6. If the device mode is MODE_OFF, return RETURN_OK.
* Otherwise, perform the Action property and performs depending
* on value specified by input value counter (incremented in PST).
* The child class tells base class what to do by setting this value.
* - SEND_WRITE: Send data or commands to device by calling
* doSendWrite() which calls sendMessage function
* of #communicationInterface
* and calls buildInternalCommand if the cookie state is COOKIE_UNUSED
* - GET_WRITE: Get ackknowledgement for sending by calling doGetWrite()
* which calls getSendSuccess of #communicationInterface.
* Calls abstract functions scanForReply() and interpretDeviceReply().
* - SEND_READ: Request reading data from device by calling doSendRead()
* which calls requestReceiveMessage of #communcationInterface
* - GET_READ: Access requested reading data by calling doGetRead()
* which calls readReceivedMessage of #communicationInterface
* @param counter Specifies which Action to perform
* @return RETURN_OK for successful execution
*/
virtual ReturnValue_t performOperation(uint8_t counter) override;
/**
* @brief Initializes the device handler
* @details
* Initialize Device Handler as system object and
* initializes all important helper classes.
* Calls fillCommandAndReplyMap().
* @return
*/
virtual ReturnValue_t initialize() override;
/**
* @brief Intialization steps performed after all tasks have been created.
* This function will be called by the executing task.
* @return
*/
virtual ReturnValue_t initializeAfterTaskCreation() override;
/** Destructor. */
virtual ~DeviceHandlerBase();
/**
* Implementation of ExecutableObjectIF function
* Used to setup the reference of the task, that executes this component
* @param task_ Pointer to the taskIF of this task
*/
virtual void setTaskIF(PeriodicTaskIF *task_) override;
virtual MessageQueueId_t getCommandQueue(void) const override;
/** Explicit interface implementation of getObjectId */
virtual object_id_t getObjectId() const override;
/**
* @param parentQueueId
*/
virtual void setParentQueue(MessageQueueId_t parentQueueId);
/** @brief Implementation required for HasActionIF */
ReturnValue_t executeAction(ActionId_t actionId, MessageQueueId_t commandedBy,
const uint8_t *data, size_t size) override;
Mode_t getTransitionSourceMode() const;
Submode_t getTransitionSourceSubMode() const;
virtual void getMode(Mode_t *mode, Submode_t *submode);
HealthState getHealth();
ReturnValue_t setHealth(HealthState health);
virtual ReturnValue_t getParameter(uint8_t domainId, uint8_t uniqueId,
ParameterWrapper *parameterWrapper,
const ParameterWrapper *newValues,
uint16_t startAtIndex) override;
protected:
/**
* @brief This is used to let the child class handle the transition from
* mode @c _MODE_START_UP to @c MODE_ON
* @details
* It is only called when the device handler is in mode @c _MODE_START_UP.
* That means, the device switch(es) are already set to on.
* Device handler commands are read and can be handled by the child class.
* If the child class handles a command, it should also send
* an reply accordingly.
* If an Command is not handled (ie #DeviceHandlerCommand is not @c CMD_NONE,
* the base class handles rejecting the command and sends a reply.
* The replies for mode transitions are handled by the base class.
*
* - If the device is started and ready for operation, the mode should be
* set to MODE_ON. It is possible to set the mode to _MODE_TO_ON to
* use the to on transition if available.
* - If the power-up fails, the mode should be set to _MODE_POWER_DOWN
* which will lead to the device being powered off.
* - If the device does not change the mode, the mode will be changed
* to _MODE_POWER_DOWN, after the timeout (from getTransitionDelay())
* has passed.
*
* #transitionFailure can be set to a failure code indicating the reason
* for a failed transition
*/
virtual void doStartUp() = 0;
/**
* @brief This is used to let the child class handle the transition
* from mode @c _MODE_SHUT_DOWN to @c _MODE_POWER_DOWN
* @details
* It is only called when the device handler is in mode @c _MODE_SHUT_DOWN.
* Device handler commands are read and can be handled by the child class.
* If the child class handles a command, it should also send an reply
* accordingly.
* If an Command is not handled (ie #DeviceHandlerCommand is not
* @c CMD_NONE, the base class handles rejecting the command and sends a
* reply. The replies for mode transitions are handled by the base class.
*
* - If the device ready to be switched off,
* the mode should be set to _MODE_POWER_DOWN.
* - If the device should not be switched off, the mode can be changed to
* _MODE_TO_ON (or MODE_ON if no transition is needed).
* - If the device does not change the mode, the mode will be changed to
* _MODE_POWER_DOWN, when the timeout (from getTransitionDelay())
* has passed.
*
* #transitionFailure can be set to a failure code indicating the reason
* for a failed transition
*/
virtual void doShutDown() = 0;
/* Command handling */
/**
* Build the device command to send for normal mode.
*
* This is only called in @c MODE_NORMAL. If multiple submodes for
* @c MODE_NORMAL are supported, different commands can built,
* depending on the submode.
*
* #rawPacket and #rawPacketLen must be set by this method to the
* packet to be sent. If variable command frequence is required, a counter
* can be used and the frequency in the reply map has to be set manually
* by calling updateReplyMap().
*
* @param[out] id the device command id that has been built
* @return
* - @c RETURN_OK to send command after setting #rawPacket and
* #rawPacketLen.
* - @c NOTHING_TO_SEND when no command is to be sent.
* - Anything else triggers an even with the returnvalue as a parameter.
*/
virtual ReturnValue_t buildNormalDeviceCommand(DeviceCommandId_t *id) = 0;
/**
* Build the device command to send for a transitional mode.
*
* This is only called in @c _MODE_TO_NORMAL, @c _MODE_TO_ON, @c _MODE_TO_RAW,
* @c _MODE_START_UP and @c _MODE_SHUT_DOWN. So it is used by doStartUp()
* and doShutDown() as well as doTransition(), by setting those
* modes in the respective functions.
*
* A good idea is to implement a flag indicating a command has to be built
* and a variable containing the command number to be built
* and filling them in doStartUp(), doShutDown() and doTransition() so no
* modes have to be checked here.
*
* #rawPacket and #rawPacketLen must be set by this method to the
* packet to be sent.
*
* @param[out] id the device command id built
* @return
* - @c RETURN_OK when a command is to be sent
* - @c NOTHING_TO_SEND when no command is to be sent
* - Anything else triggers an even with the returnvalue as a parameter
*/
virtual ReturnValue_t buildTransitionDeviceCommand(DeviceCommandId_t *id) = 0;
/**
* @brief Build a device command packet from data supplied by a direct
* command (PUS Service 8)
* @details
* This will be called if an functional command via PUS Service 8 is received and is
* the primary interface for functional command instead of #executeAction for users. The
* supplied ActionId_t action ID will be converted to a DeviceCommandId_t command ID after
* an internal check whether the action ID is a key in the device command map.
*
* #rawPacket and #rawPacketLen should be set by this method to the packet to be sent.
* The existence of the command in the command map and the command size check against 0 are
* done by the base class.
*
* @param deviceCommand The command to build, already checked against deviceCommandMap
* @param commandData Pointer to the data from the direct command
* @param commandDataLen Length of commandData
* @return
* - @c RETURN_OK to send command after #rawPacket and #rawPacketLen
* have been set.
* - @c HasActionsIF::EXECUTION_COMPLETE to generate a finish reply immediately. This can
* be used if no reply is expected
* - Anything else triggers an event with the return code as a parameter as well as a
* step reply failed with the return code
*/
virtual ReturnValue_t buildCommandFromCommand(DeviceCommandId_t deviceCommand,
const uint8_t *commandData,
size_t commandDataLen) = 0;
/* Reply handling */
/**
* @brief Scans a buffer for a valid reply.
* @details
* This is used by the base class to check the data received for valid packets.
* It only checks if a valid packet starts at @c start.
* It also only checks the structural validy of the packet,
* e.g. checksums lengths and protocol data. No information check is done,
* e.g. range checks etc.
*
* Errors should be reported directly, the base class does NOT report any
* errors based on the return value of this function.
*
* @param start start of remaining buffer to be scanned
* @param len length of remaining buffer to be scanned
* @param[out] foundId the id of the data found in the buffer.
* @param[out] foundLen length of the data found. Is to be set in function,
* buffer is scanned at previous position + foundLen.
* @return
* - @c RETURN_OK a valid packet was found at @c start, @c foundLen is valid
* - @c RETURN_FAILED no reply could be found starting at @c start,
* implies @c foundLen is not valid, base class will call scanForReply()
* again with ++start
* - @c DeviceHandlerIF::INVALID_DATA a packet was found but it is invalid,
* e.g. checksum error, implies @c foundLen is valid, can be used to
* skip some bytes
* - @c DeviceHandlerIF::LENGTH_MISSMATCH @c len is invalid
* - @c DeviceHandlerIF::IGNORE_REPLY_DATA Ignore this specific part of
* the packet
* - @c DeviceHandlerIF::IGNORE_FULL_PACKET Ignore the packet
* - @c APERIODIC_REPLY if a valid reply is received that has not been
* requested by a command, but should be handled anyway
* (@see also fillCommandAndCookieMap() )
*/
virtual ReturnValue_t scanForReply(const uint8_t *start, size_t len, DeviceCommandId_t *foundId,
size_t *foundLen) = 0;
/**
* @brief Interpret a reply from the device.
* @details
* This is called after scanForReply() found a valid packet, it can be
* assumed that the length and structure is valid.
* This routine extracts the data from the packet into a DataSet and then
* calls handleDeviceTM(), which either sends a TM packet or stores the
* data in the DataPool depending on whether it was an external command.
* No packet length is given, as it should be defined implicitly by the id.
*
* @param id the id found by scanForReply()
* @param packet
* @return
* - @c RETURN_OK when the reply was interpreted.
* - @c IGNORE_REPLY_DATA Ignore the reply and don't reset reply cycle
* counter.
* - @c RETURN_FAILED when the reply could not be interpreted,
* e.g. logical errors or range violations occurred
*/
virtual ReturnValue_t interpretDeviceReply(DeviceCommandId_t id, const uint8_t *packet) = 0;
MessageQueueId_t getCommanderQueueId(DeviceCommandId_t replyId) const;
/**
* Helper function to get pending command. This is useful for devices
* like SPI sensors to identify the last sent command.
* This only returns the command sent in the last SEND_WRITE cycle.
* @return
*/
DeviceCommandId_t getPendingCommand() const;
/* Specifying commands and replies */
/**
* @brief Fill the #DeviceCommandMap and #DeviceReplyMap called by the #initialize
* of the base class
* @details
* This is used to let the base class know which replies are expected.
* There are different scenarios regarding this:
*
* - "Normal" commands. These are commands, that trigger a direct reply
* from the device. In this case, the id of the command should be added
* to the command map with a commandData_t where maxDelayCycles is set
* to the maximum expected number of PST cycles the reply will take.
* Then, scanForReply returns the id of the command and the base class
* can handle time-out and missing replies.
*
* - Periodic, unrequested replies. These are replies that, once enabled,
* are sent by the device on its own in a defined interval.
* In this case, the id of the reply or a placeholder id should be added
* to the deviceCommandMap with a commandData_t where maxDelayCycles is
* set to the maximum expected number of PST cycles between two replies
* (also a tolerance should be added, as an FDIR message will be
* generated if it is missed).
* From then on, the base class handles the reception.
* Then, scanForReply returns the id of the reply or the placeholder id
* and the base class will take care of checking that all replies are
* received and the interval is correct.
*
* - Aperiodic, unrequested replies. These are replies that are sent
* by the device without any preceding command and not in a defined
* interval. These are not entered in the deviceCommandMap but
* handled by returning @c APERIODIC_REPLY in scanForReply().
*/
virtual void fillCommandAndReplyMap() = 0;
/**
* This is a helper method to facilitate inserting entries in the command map.
* @param deviceCommand Identifier of the command to add.
* @param maxDelayCycles The maximum number of delay cycles the command
* waits until it times out.
* @param replyLen Will be supplied to the requestReceiveMessage call of
* the communication interface.
* @param periodic Indicates if the command is periodic (i.e. it is sent
* by the device repeatedly without request) or not. Default is aperiodic (0).
* Please note that periodic replies are disabled by default. You can enable them with
* #updatePeriodicReply
* @return - @c RETURN_OK when the command was successfully inserted,
* - @c RETURN_FAILED else.
*/
ReturnValue_t insertInCommandAndReplyMap(DeviceCommandId_t deviceCommand, uint16_t maxDelayCycles,
LocalPoolDataSetBase *replyDataSet = nullptr,
size_t replyLen = 0, bool periodic = false,
bool hasDifferentReplyId = false,
DeviceCommandId_t replyId = 0);
/**
* @brief This is a helper method to insert replies in the reply map.
* @param deviceCommand Identifier of the reply to add.
* @param maxDelayCycles The maximum number of delay cycles the reply waits
* until it times out.
* @param periodic Indicates if the command is periodic (i.e. it is sent
* by the device repeatedly without request) or not. Default is aperiodic (0).
* Please note that periodic replies are disabled by default. You can enable them with
* #updatePeriodicReply
* @return - @c RETURN_OK when the command was successfully inserted,
* - @c RETURN_FAILED else.
*/
ReturnValue_t insertInReplyMap(DeviceCommandId_t deviceCommand, uint16_t maxDelayCycles,
LocalPoolDataSetBase *dataSet = nullptr, size_t replyLen = 0,
bool periodic = false);
/**
* @brief A simple command to add a command to the commandList.
* @param deviceCommand The command to add
* @return - @c RETURN_OK when the command was successfully inserted,
* - @c RETURN_FAILED else.
*/
ReturnValue_t insertInCommandMap(DeviceCommandId_t deviceCommand,
bool useAlternativeReply = false,
DeviceCommandId_t alternativeReplyId = 0);
/**
* Enables a periodic reply for a given command. It sets to delay cycles to the specified
* maximum delay cycles for a given reply ID if enabled or to 0 if disabled.
* @param enable Specify whether to enable or disable a given periodic reply
* @return
*/
ReturnValue_t updatePeriodicReply(bool enable, DeviceCommandId_t deviceReply);
/**
* @brief This function returns the reply length of the next reply to read.
*
* @param deviceCommand The command which triggered the device reply.
*
* @details The default implementation assumes only one reply is triggered by the command. In
* case the command triggers multiple replies (e.g. one acknowledgment, one data,
* and one execution status reply), this function can be overwritten to get the
* reply length of the next reply to read.
*/
virtual size_t getNextReplyLength(DeviceCommandId_t deviceCommand);
/**
* @brief This is a helper method to facilitate updating entries in the reply map.
* @param deviceCommand Identifier of the reply to update.
* @param delayCycles The current number of delay cycles to wait. As stated in
* #fillCommandAndReplyMap, to disable periodic commands, this is set to zero.
* @param maxDelayCycles The maximum number of delay cycles the reply waits
* until it times out. By passing 0 the entry remains untouched.
* @param periodic Indicates if the command is periodic (i.e. it is sent
* by the device repeatedly without request) or not. Default is aperiodic (0).
* Warning: The setting always overrides the value that was entered in the map.
* @return - @c RETURN_OK when the command was successfully inserted,
* - @c RETURN_FAILED else.
*/
ReturnValue_t updateReplyMapEntry(DeviceCommandId_t deviceReply, uint16_t delayCycles,
uint16_t maxDelayCycles, bool periodic = false);
/**
* @brief Can be used to set the dataset corresponding to a reply ID manually.
* @details
* Used by the local data pool manager.
*/
ReturnValue_t setReplyDataset(DeviceCommandId_t replyId, LocalPoolDataSetBase *dataset);
/**
* Get the time needed to transit from modeFrom to modeTo.
*
* Used for the following transitions:
* modeFrom -> modeTo:
* MODE_ON -> [MODE_ON, MODE_NORMAL, MODE_RAW, _MODE_POWER_DOWN]
* MODE_NORMAL -> [MODE_ON, MODE_NORMAL, MODE_RAW, _MODE_POWER_DOWN]
* MODE_RAW -> [MODE_ON, MODE_NORMAL, MODE_RAW, _MODE_POWER_DOWN]
* _MODE_START_UP -> MODE_ON (do not include time to set the switches,
* the base class got you covered)
*
* The default implementation returns 0 !
* @param modeFrom
* @param modeTo
* @return time in ms
*/
virtual uint32_t getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo) = 0;
/* Functions used by the local data pool manager */
/**
* This function is used to initialize the local housekeeping pool
* entries. The default implementation leaves the pool empty.
* @param localDataPoolMap
* @return
*/
virtual ReturnValue_t initializeLocalDataPool(localpool::DataPool &localDataPoolMap,
LocalDataPoolManager &poolManager) override;
/**
* @brief Set all datapool variables that are update periodically in
* normal mode invalid
* @details
* The default implementation will set all datasets which have been added
* in #fillCommandAndReplyMap to invalid. It will also set all pool
* variables inside the dataset to invalid. The user can override this
* method optionally.
*/
virtual void setNormalDatapoolEntriesInvalid();
/**
* @brief Get the dataset handle for a given SID.
* @details
* The default implementation will use the deviceCommandMap to look for the corresponding
* dataset handle. The user can override this function if this is not desired.
* @param sid
* @return
*/
virtual LocalPoolDataSetBase *getDataSetHandle(sid_t sid) override;
/* HasModesIF overrides */
virtual void startTransition(Mode_t mode, Submode_t submode) override;
virtual void setToExternalControl() override;
virtual void announceMode(bool recursive) override;
/**
* @brief Set the device handler mode
* @details
* Sets #timeoutStart with every call Also sets #transitionTargetMode if necessary so
* transitional states can be entered from everywhere without breaking the state machine
* (which relies on a correct #transitionTargetMode).
* The submode is left unchanged.
*
* @param newMode
*/
void setMode(Mode_t newMode);
/**
* @overload
* @param submode
*/
void setMode(Mode_t newMode, Submode_t submode);
/**
* @brief Should be implemented properly by child class.
* @param mode
* @param submode
* @return
* - @c RETURN_OK if valid
* - @c RETURN_FAILED if invalid
*/
virtual ReturnValue_t isModeCombinationValid(Mode_t mode, Submode_t submode);
/**
* @brief Notify child about mode change.
* @details
* Can be overriden to be used like a callback.
*/
virtual void modeChanged();
/* Power handling functions */
/**
* Return the switches connected to the device.
*
* The default implementation returns one switch set in the ctor.
*
* @param[out] switches pointer to an array of switches
* @param[out] numberOfSwitches length of returned array
* @return
* - @c RETURN_OK if the parameters were set
* - @c RETURN_FAILED if no switches exist
*/
virtual ReturnValue_t getSwitches(const uint8_t **switches, uint8_t *numberOfSwitches);
/**
* @brief Helper function to report a missed reply
* @details Can be overwritten by children to act on missed replies or to fake reporting Id.
* @param id of the missed reply
*/
virtual void missedReply(DeviceCommandId_t id);
/* Miscellaneous functions */
/**
* @brief Hook function for child handlers which is called once per
* performOperation(). Default implementation is empty.
*/
virtual void performOperationHook();
/**
* @brief Can be implemented by child handler to
* perform debugging
* @details Example: Calling this in performOperation
* to track values like mode.
* @param positionTracker Provide the child handler a way to know
* where the debugInterface was called
* @param objectId Provide the child handler object Id to
* specify actions for spefic devices
* @param parameter Supply a parameter of interest
* Please delete all debugInterface calls in DHB after debugging is finished !
*/
virtual void debugInterface(uint8_t positionTracker = 0, object_id_t objectId = 0,
uint32_t parameter = 0);
protected:
static const uint8_t INTERFACE_ID = CLASS_ID::DEVICE_HANDLER_BASE;
static const ReturnValue_t INVALID_CHANNEL = MAKE_RETURN_CODE(0xA0);
/* Return codes for scanForReply */
//! This is used to specify for replies from a device which are not replies to requests
static const ReturnValue_t APERIODIC_REPLY = MAKE_RETURN_CODE(0xB0);
//! Ignore parts of the received packet
static const ReturnValue_t IGNORE_REPLY_DATA = MAKE_RETURN_CODE(0xB1);
//! Ignore full received packet
static const ReturnValue_t IGNORE_FULL_PACKET = MAKE_RETURN_CODE(0xB2);
/* Return codes for command building */
//! Return this if no command sending in required
static const ReturnValue_t NOTHING_TO_SEND = MAKE_RETURN_CODE(0xC0);
static const ReturnValue_t COMMAND_MAP_ERROR = MAKE_RETURN_CODE(0xC2);
// Return codes for getSwitches */
static const ReturnValue_t NO_SWITCH = MAKE_RETURN_CODE(0xD0);
/* Mode handling error Codes */
static const ReturnValue_t CHILD_TIMEOUT = MAKE_RETURN_CODE(0xE0);
static const ReturnValue_t SWITCH_FAILED = MAKE_RETURN_CODE(0xE1);
static const MessageQueueId_t NO_COMMANDER = MessageQueueIF::NO_QUEUE;
//! Pointer to the raw packet that will be sent.
uint8_t *rawPacket = nullptr;
//! Size of the #rawPacket.
size_t rawPacketLen = 0;
/**
* The mode the device handler is currently in.
* This should never be changed directly but only with setMode()
*/
Mode_t mode;
/**
* The submode the device handler is currently in.
* This should never be changed directly but only with setMode()
*/
Submode_t submode;
/** This is the counter value from performOperation(). */
uint8_t pstStep = 0;
uint8_t lastStep = 0;
uint32_t pstIntervalMs = 0;
/**
* Wiretapping flag:
*
* indicates either that all raw messages to and from the device should be
* sent to #defaultRawReceiver
* or that all device TM should be downlinked to #defaultRawReceiver.
*/
enum WiretappingMode { OFF = 0, RAW = 1, TM = 2 } wiretappingMode;
/**
* @brief A message queue that accepts raw replies
*
* Statically initialized in initialize() to a configurable object.
* Used when there is no method of finding a recipient, ie raw mode and
* reporting erroneous replies
*/
MessageQueueId_t defaultRawReceiver = MessageQueueIF::NO_QUEUE;
store_address_t storedRawData;
/**
* @brief The message queue which wants to read all raw traffic
* If #isWiretappingActive all raw communication from and to the device
* will be sent to this queue
*/
MessageQueueId_t requestedRawTraffic = 0;
/**
* Pointer to the IPCStore.
* This caches the pointer received from the objectManager in the constructor.
*/
StorageManagerIF *IPCStore = nullptr;
/** The comIF object ID is cached for the intialize() function */
object_id_t deviceCommunicationId;
/** Communication object used for device communication */
DeviceCommunicationIF *communicationInterface = nullptr;
/** Cookie used for communication */
CookieIF *comCookie;
/* Health helper for HasHealthIF */
HealthHelper healthHelper;
/* Mode helper for HasModesIF */
ModeHelper modeHelper;
/* Parameter helper for ReceivesParameterMessagesIF */
ParameterHelper parameterHelper;
/* Action helper for HasActionsIF */
ActionHelper actionHelper;
/* Housekeeping Manager */
LocalDataPoolManager poolManager;
/**
* @brief Information about commands
*/
struct DeviceCommandInfo {
//! Indicates if the command is already executing.
bool isExecuting;
//! Dynamic value to indicate how many replies are expected.
//! Inititated with 0.
uint8_t expectedReplies;
//! if this is != NO_COMMANDER, DHB was commanded externally and shall
//! report everything to commander.
MessageQueueId_t sendReplyTo;
bool useAlternativeReplyId;
DeviceCommandId_t alternativeReplyId;
};
using DeviceCommandMap = std::map<DeviceCommandId_t, DeviceCommandInfo>;
/**
* Information about commands
*/
DeviceCommandMap deviceCommandMap;
/**
* @brief Information about expected replies
* This is used to keep track of pending replies.
*/
struct DeviceReplyInfo {
//! The maximum number of cycles the handler should wait for a reply
//! to this command.
uint16_t maxDelayCycles;
//! The currently remaining cycles the handler should wait for a reply,
//! 0 means there is no reply expected
uint16_t delayCycles;
size_t replyLen = 0; //!< Expected size of the reply.
//! if this is !=0, the delayCycles will not be reset to 0 but to
//! maxDelayCycles
bool periodic = false;
//! The dataset used to access housekeeping data related to the
//! respective device reply. Will point to a dataset held by
//! the child handler (if one is specified)
LocalPoolDataSetBase *dataSet = nullptr;
//! The command that expects this reply.
DeviceCommandMap::iterator command;
};
using DeviceReplyMap = std::map<DeviceCommandId_t, DeviceReplyInfo>;
using DeviceReplyIter = DeviceReplyMap::iterator;
/**
* This map is used to check and track correct reception of all replies.
*
* It has multiple use:
* - It stores the information on pending replies. If a command is sent,
* the DeviceCommandInfo.count is incremented.
* - It is used to time-out missing replies. If a command is sent, the
* DeviceCommandInfo.DelayCycles is set to MaxDelayCycles.
* - It is queried to check if a reply from the device can be interpreted.
* scanForReply() returns the id of the command a reply was found for.
* The reply is ignored in the following cases:
* - No entry for the returned id was found
* - The deviceReplyInfo.delayCycles is == 0
*/
DeviceReplyMap deviceReplyMap;
//! The MessageQueue used to receive device handler commands
//! and to send replies.
MessageQueueIF *commandQueue = nullptr;
DeviceHandlerThermalSet *thermalSet = nullptr;
/**
* Optional Error code. Can be set in doStartUp(), doShutDown() and
* doTransition() to signal cause for Transition failure.
*/
ReturnValue_t childTransitionFailure;
/** Counts if communication channel lost a reply, so some missed
* replys can be ignored. */
uint32_t ignoreMissedRepliesCount = 0;
/** Pointer to the used FDIR instance. If not provided by child,
* default class is instantiated. */
FailureIsolationBase *fdirInstance;
//! To correctly delete the default instance.
bool defaultFDIRUsed;
//! Indicates if SWITCH_WENT_OFF was already thrown.
bool switchOffWasReported;
/** Pointer to the task which executes this component,
is invalid before setTaskIF was called. */
PeriodicTaskIF *executingTask = nullptr;
//! Object which switches power on and off.
static object_id_t powerSwitcherId;
//! Object which receives RAW data by default.
static object_id_t rawDataReceiverId;
//! Object which may be the root cause of an identified fault.
static object_id_t defaultFdirParentId;
/**
* @brief Send a reply to a received device handler command.
*
* This also resets #DeviceHandlerCommand to 0.
*
* @param reply the reply type
* @param parameter parameter for the reply
*/
void replyReturnvalueToCommand(ReturnValue_t status, uint32_t parameter = 0);
/**
* TODO: Whats the difference between this and the upper command?
* @param status
* @param parameter
*/
void replyToCommand(ReturnValue_t status, uint32_t parameter = 0);
/**
* Do the transition to the main modes (MODE_ON, MODE_NORMAL and MODE_RAW).
*
* If the transition is complete, the mode should be set to the target mode,
* which can be deduced from the current mode which is
* [_MODE_TO_ON, _MODE_TO_NORMAL, _MODE_TO_RAW]
*
* The intended target submode is already set.
* The origin submode can be read in subModeFrom.
*
* If the transition can not be completed, the child class can try to reach
* an working mode by setting the mode either directly
* or setting the mode to an transitional mode (TO_ON, TO_NORMAL, TO_RAW)
* if the device needs to be reconfigured.
*
* If nothing works, the child class can wait for the timeout and the base
* class will reset the mode to the mode where the transition
* originated from (the child should report the reason for the failed transition).
*
* The intended way to send commands is to set a flag (enum) indicating
* which command is to be sent here and then to check in
* buildTransitionCommand() for the flag. This flag can also be used by
* doStartUp() and doShutDown() to get a nice and clean implementation of
* buildTransitionCommand() without switching through modes.
*
* When the the condition for the completion of the transition is met, the
* mode can be set, for example in the scanForReply() function.
*
* The default implementation goes into the target mode directly.
*
* #transitionFailure can be set to a failure code indicating the reason
* for a failed transition
*
* @param modeFrom
* The mode the transition originated from:
* [MODE_ON, MODE_NORMAL, MODE_RAW and _MODE_POWER_DOWN (if the mode changed
* from _MODE_START_UP to _MODE_TO_ON)]
* @param subModeFrom the subMode of modeFrom
*/
virtual void doTransition(Mode_t modeFrom, Submode_t subModeFrom);
/**
* Get the communication action for the current step.
* The step number can be read from #pstStep.
* @return The communication action to execute in this step
*/
virtual CommunicationAction getComAction();
/**
* Checks state of switches in conjunction with mode and triggers an event
* if they don't fit.
*/
virtual void checkSwitchState();
/**
* Reserved for the rare case where a device needs to perform additional
* operation cyclically in OFF mode.
*/
virtual void doOffActivity();
/**
* Reserved for the rare case where a device needs to perform additional
* operation cyclically in ON mode.
*/
virtual void doOnActivity();
/**
* Required for HasLocalDataPoolIF, return a handle to the local pool manager.
* @return
*/
LocalDataPoolManager *getHkManagerHandle() override;
/**
* Returns the delay cycle count of a reply.
* A count != 0 indicates that the command is already executed.
* @param deviceCommand The command to look for
* @return
* The current delay count. If the command does not exist (should never
* happen) it returns 0.
*/
uint8_t getReplyDelayCycles(DeviceCommandId_t deviceCommand);
/**
* Calls replyRawData() with #defaultRawReceiver, but checks if wiretapping
* is active and if so, does not send the data as the wiretapping will have
* sent it already
*/
void replyRawReplyIfnotWiretapped(const uint8_t *data, size_t len);
/**
* Enable the reply checking for a command
*
* Is only called, if the command was sent (i.e. the getWriteReply was
* successful). Must ensure that all replies are activated and correctly
* linked to the command that initiated it.
* The default implementation looks for a reply with the same id as the
* command id in the replyMap or uses the alternativeReplyId if flagged so.
* When found, copies maxDelayCycles to delayCycles in the reply information
* and sets the command to expect one reply.
*
* Can be overwritten by the child, if a command activates multiple replies
* or replyId differs from commandId.
* Notes for child implementations:
* - If the command was not found in the reply map,
* NO_REPLY_EXPECTED MUST be returned.
* - A failure code may be returned if something went fundamentally wrong.
*
* @param deviceCommand
* @return - RETURN_OK if a reply was activated.
* - NO_REPLY_EXPECTED if there was no reply found. This is not an
* error case as many commands do not expect a reply.
*/
virtual ReturnValue_t enableReplyInReplyMap(DeviceCommandMap::iterator command,
uint8_t expectedReplies = 1,
bool useAlternateId = false,
DeviceCommandId_t alternateReplyID = 0);
/**
* @brief Build the device command to send for raw mode.
* @details
* This is only called in @c MODE_RAW. It is for the rare case that in
* raw mode packets are to be sent by the handler itself. It is NOT needed
* for the raw commanding service. Its only current use is in the STR
* handler which gets its raw packets from a different source.
* Also it can be used for transitional commands, to get the device ready
* for @c MODE_RAW
*
* As it is almost never used, there is a default implementation
* returning @c NOTHING_TO_SEND.
*
* #rawPacket and #rawPacketLen must be set by this method to the packet
* to be sent.
*
* @param[out] id the device command id built
* @return
* - @c RETURN_OK when a command is to be sent
* - not @c NOTHING_TO_SEND when no command is to be sent
*/
virtual ReturnValue_t buildChildRawCommand();
/**
* @brief Construct a command reply containing a raw reply.
* @details
* It gets space in the #IPCStore, copies data there, then sends a raw reply
* containing the store address. This method is virtual, as devices can have different channels
* to send raw replies
*
* @param data data to send
* @param len length of @c data
* @param sendTo the messageQueueId of the one to send to
* @param isCommand marks the raw data as a command, the message then
* will be of type raw_command
*/
virtual void replyRawData(const uint8_t *data, size_t len, MessageQueueId_t sendTo,
bool isCommand = false);
/**
* Get the state of the PCDU switches in the local datapool
* @return
* - @c PowerSwitchIF::SWITCH_ON if all switches specified
* by #switches are on
* - @c PowerSwitchIF::SWITCH_OFF one of the switches specified by
* #switches are off
* - @c PowerSwitchIF::RETURN_FAILED if an error occured
*/
ReturnValue_t getStateOfSwitches();
/**
* Children can overwrite this function to suppress checking of the
* command Queue
*
* This can be used when the child does not want to receive a command in
* a certain situation. Care must be taken that checking is not
* permanentely disabled as this would render the handler unusable.
*
* @return whether checking the queue should NOT be done
*/
virtual bool dontCheckQueue();
Mode_t getBaseMode(Mode_t transitionMode);
bool isAwaitingReply();
void handleDeviceTM(SerializeIF *dataSet, DeviceCommandId_t replyId, bool forceDirectTm = false);
// void handleDeviceTM(uint8_t* data, size_t dataSize, DeviceCommandId_t replyId,
// bool forceDirectTm);
virtual ReturnValue_t checkModeCommand(Mode_t mode, Submode_t submode,
uint32_t *msToReachTheMode);
virtual ReturnValue_t letChildHandleMessage(CommandMessage *message);
/**
* Overwrites SystemObject::triggerEvent in order to inform FDIR"Helper"
* faster about executed events.
* This is a bit sneaky, but improves responsiveness of the device FDIR.
* @param event The event to be thrown
* @param parameter1 Optional parameter 1
* @param parameter2 Optional parameter 2
*/
void triggerEvent(Event event, uint32_t parameter1 = 0, uint32_t parameter2 = 0) override;
/**
* Same as triggerEvent, but for forwarding if object is used as proxy.
*/
virtual void forwardEvent(Event event, uint32_t parameter1 = 0,
uint32_t parameter2 = 0) const override;
/**
* Checks if current mode is transitional mode.
* @return true if mode is transitional, false else.
*/
bool isTransitionalMode();
/**
* Checks if current handler state allows reception of external device commands.
* Default implementation allows commands only in plain MODE_ON and MODE_NORMAL.
* @return RETURN_OK if commands are accepted, anything else otherwise.
*/
virtual ReturnValue_t acceptExternalDeviceCommands();
bool commandIsExecuting(DeviceCommandId_t commandId);
/**
* set all switches returned by getSwitches()
*
* @param onOff on == @c SWITCH_ON; off != @c SWITCH_ON
*/
void commandSwitch(ReturnValue_t onOff);
/**
* @brief This function can be used to insert device specific code during the do-send-read
* step.
*/
virtual ReturnValue_t doSendReadHook();
private:
/**
* State a cookie is in.
*
* Used to keep track of the state of the RMAP communication.
*/
enum CookieState_t {
COOKIE_UNUSED, //!< The Cookie is unused
COOKIE_WRITE_READY, //!< There's data available to send.
COOKIE_READ_SENT, //!< A sendRead command was sent with this cookie
COOKIE_WRITE_SENT //!< A sendWrite command was sent with this cookie
};
/**
* Information about a cookie.
*
* This is stored in a map for each cookie, to not only track the state,
* but also information about the sent command. Tracking this information
* is needed as the state of a commandId (waiting for reply) is done when a
* write reply is received.
*/
struct CookieInfo {
CookieState_t state;
DeviceCommandMap::iterator pendingCommand;
};
/**
* @brief Info about the #cookie
* Used to track the state of the communication
*/
CookieInfo cookieInfo;
/** the object used to set power switches */
PowerSwitchIF *powerSwitcher = nullptr;
/** HK destination can also be set individually */
object_id_t hkDestination = objects::NO_OBJECT;
/**
* @brief Used for timing out mode transitions.
* Set when setMode() is called.
*/
uint32_t timeoutStart = 0;
bool setStartupImmediately = false;
/**
* Delay for the current mode transition, used for time out
*/
uint32_t childTransitionDelay;
/**
* @brief The mode the current transition originated from
*
* This is private so the child can not change it and mess up the timeouts
*
* IMPORTANT: This is not valid during _MODE_SHUT_DOWN and _MODE_START_UP!!
* (it is _MODE_POWER_DOWN during this modes)
*
* is element of [MODE_ON, MODE_NORMAL, MODE_RAW]
*/
Mode_t transitionSourceMode;
/**
* the submode of the source mode during a transition
*/
Submode_t transitionSourceSubMode;
/**
* read the command queue
*/
void readCommandQueue(void);
/**
* Handle the device handler mode.
*
* - checks whether commands are valid for the current mode, rejects
* them accordingly
* - checks whether commanded mode transitions are required and calls
* handleCommandedModeTransition()
* - does the necessary action for the current mode or calls
* doChildStateMachine in modes @c MODE_TO_ON and @c MODE_TO_OFF
* - actions that happen in transitions (e.g. setting a timeout) are
* handled in setMode()
*/
void doStateMachine(void);
void buildRawDeviceCommand(CommandMessage *message);
void buildInternalCommand(void);
/**
* Decrement the counter for the timout of replies.
*
* This is called at the beginning of each cycle. It checks whether a
* reply has timed out (that means a reply was expected but not received).
*/
void decrementDeviceReplyMap(void);
/**
* Convenience function to handle a reply.
*
* Called after scanForReply() has found a packet. Checks if the found ID
* is in the #deviceCommandMap, if so, calls
* #interpretDeviceReply for further action.
*
* It also resets the timeout counter for the command id.
*
* @param data the found packet
* @param id the found id
* @foundLen the length of the packet
*/
void handleReply(const uint8_t *data, DeviceCommandId_t id, uint32_t foundLen);
void replyToReply(const DeviceCommandId_t command, DeviceReplyInfo &replyInfo,
ReturnValue_t status);
/**
* Build and send a command to the device.
*
* This routine checks whether a raw or direct command has been received,
* checks the content of the received command and calls
* buildCommandFromCommand() for direct commands or sets #rawpacket
* to the received raw packet.
* If no external command is received or the received command is invalid and
* the current mode is @c MODE_NORMAL or a transitional mode, it asks the
* child class to build a command (via getNormalDeviceCommand() or
* getTransitionalDeviceCommand() and buildCommand()) and
* sends the command via RMAP.
*/
void doSendWrite(void);
/**
* Check if the RMAP sendWrite action was successful.
*
* Depending on the result, the following is done
* - if the device command was external commanded, a reply is sent
* indicating the result
* - if the action was successful, the reply timout counter is initialized
*/
void doGetWrite(void);
/**
* Send a RMAP getRead command.
*
* The size of the getRead command is #maxDeviceReplyLen.
* This is always executed, independently from the current mode.
*/
void doSendRead(void);
/**
* Check the getRead reply and the contained data.
*
* If data was received scanForReply() and, if successful, handleReply()
* are called. If the current mode is @c MODE_RAW, the received packet
* is sent to the commanding object via commandQueue.
*/
void doGetRead(void);
/**
* Retrive data from the #IPCStore.
*
* @param storageAddress
* @param[out] data
* @param[out] len
* @return
* - @c RETURN_OK @c data is valid
* - @c RETURN_FAILED IPCStore is nullptr
* - the return value from the IPCStore if it was not @c RETURN_OK
*/
ReturnValue_t getStorageData(store_address_t storageAddress, uint8_t **data, size_t *len);
/**
* @param modeTo either @c MODE_ON, MODE_NORMAL or MODE_RAW, nothing else!
*/
void setTransition(Mode_t modeTo, Submode_t submodeTo);
/**
* Calls the right child function for the transitional submodes
*/
void callChildStatemachine();
ReturnValue_t handleDeviceHandlerMessage(CommandMessage *message);
virtual dur_millis_t getPeriodicOperationFrequency() const override;
void parseReply(const uint8_t *receivedData, size_t receivedDataLen);
void handleTransitionToOnMode(Mode_t commandedMode, Submode_t commandedSubmode);
/**
* Generic internal printer function which also handles printing the object ID.
* @param errorType
* @param functionName
* @param errorCode
* @param errorPrint
*/
void printWarningOrError(sif::OutputTypes errorType, const char *functionName,
ReturnValue_t errorCode = HasReturnvaluesIF::RETURN_FAILED,
const char *errorPrint = nullptr);
};
#endif /* FSFW_DEVICEHANDLERS_DEVICEHANDLERBASE_H_ */