diff --git a/cpp/common/include/IpcMessage.h b/cpp/common/include/IpcMessage.h index 686b2b37a..52e00d7f1 100644 --- a/cpp/common/include/IpcMessage.h +++ b/cpp/common/include/IpcMessage.h @@ -90,6 +90,8 @@ class IpcMessage MsgValCmdStatus, //!< Status command message MsgValCmdConfigure, //!< Configure command message MsgValCmdRequestConfiguration, //!< Request configuration command message + MsgValCmdExecute, //!< Execute a command message + MsgValCmdRequestCommands, //!< Request available commands message MsgValCmdRequestVersion, //!< Request version information message MsgValCmdBufferConfigRequest, //!< Buffer configuration request MsgValCmdBufferPrechargeRequest, //!< Buffer precharge request diff --git a/cpp/common/src/IpcMessage.cpp b/cpp/common/src/IpcMessage.cpp index 7ec04c03e..37cf85237 100644 --- a/cpp/common/src/IpcMessage.cpp +++ b/cpp/common/src/IpcMessage.cpp @@ -602,6 +602,8 @@ void IpcMessage::msg_val_map_init() msg_val_map_.insert(MsgValMapEntry("status", MsgValCmdStatus)); msg_val_map_.insert(MsgValMapEntry("configure", MsgValCmdConfigure)); msg_val_map_.insert(MsgValMapEntry("request_configuration", MsgValCmdRequestConfiguration)); + msg_val_map_.insert(MsgValMapEntry("execute", MsgValCmdExecute)); + msg_val_map_.insert(MsgValMapEntry("request_commands", MsgValCmdRequestCommands)); msg_val_map_.insert(MsgValMapEntry("request_version", MsgValCmdRequestVersion)); msg_val_map_.insert(MsgValMapEntry("request_buffer_config", MsgValCmdBufferConfigRequest)); msg_val_map_.insert(MsgValMapEntry("request_buffer_precharge", MsgValCmdBufferPrechargeRequest)); diff --git a/cpp/frameProcessor/include/DummyUDPProcessPlugin.h b/cpp/frameProcessor/include/DummyUDPProcessPlugin.h index 522681327..514e000b5 100644 --- a/cpp/frameProcessor/include/DummyUDPProcessPlugin.h +++ b/cpp/frameProcessor/include/DummyUDPProcessPlugin.h @@ -42,6 +42,8 @@ class DummyUDPProcessPlugin : public FrameProcessorPlugin void configure(OdinData::IpcMessage& config, OdinData::IpcMessage& reply); void requestConfiguration(OdinData::IpcMessage& reply); + void execute(const std::string& command, OdinData::IpcMessage& reply); + std::vector requestCommands(); void status(OdinData::IpcMessage& status); bool reset_statistics(void); @@ -54,6 +56,9 @@ class DummyUDPProcessPlugin : public FrameProcessorPlugin /** Configuraiton constant for copy frame mode **/ static const std::string CONFIG_COPY_FRAME; + /** Command execution constant for print command **/ + static const std::string EXECUTE_PRINT; + void process_frame(boost::shared_ptr frame); void process_lost_packets(boost::shared_ptr& frame); diff --git a/cpp/frameProcessor/include/FrameProcessorController.h b/cpp/frameProcessor/include/FrameProcessorController.h index d2944bdd6..a4d57fd69 100644 --- a/cpp/frameProcessor/include/FrameProcessorController.h +++ b/cpp/frameProcessor/include/FrameProcessorController.h @@ -45,6 +45,8 @@ class FrameProcessorController : public IFrameCallback, void provideVersion(OdinData::IpcMessage& reply); void configure(OdinData::IpcMessage& config, OdinData::IpcMessage& reply); void requestConfiguration(OdinData::IpcMessage& reply); + void execute(OdinData::IpcMessage& config, OdinData::IpcMessage& reply); + void requestCommands(OdinData::IpcMessage& reply); void resetStatistics(OdinData::IpcMessage& reply); void configurePlugin(OdinData::IpcMessage& config, OdinData::IpcMessage& reply); void loadPlugin(const std::string& index, const std::string& name, const std::string& library); @@ -109,6 +111,9 @@ class FrameProcessorController : public IFrameCallback, /** Configuration constant for the value of a stored configuration object **/ static const std::string CONFIG_VALUE; + /** Configuration constant for the a command to execute **/ + static const std::string COMMAND_KEY; + /** Configuration constant for the meta TX channel high water mark **/ static const int META_TX_HWM; diff --git a/cpp/frameProcessor/include/FrameProcessorPlugin.h b/cpp/frameProcessor/include/FrameProcessorPlugin.h index 17921c511..ee3cb7f9b 100644 --- a/cpp/frameProcessor/include/FrameProcessorPlugin.h +++ b/cpp/frameProcessor/include/FrameProcessorPlugin.h @@ -45,6 +45,8 @@ class FrameProcessorPlugin : public IFrameCallback, public OdinData::IVersionedO std::vector get_warnings(); virtual void configure(OdinData::IpcMessage& config, OdinData::IpcMessage& reply); virtual void requestConfiguration(OdinData::IpcMessage& reply); + virtual void execute(const std::string& command, OdinData::IpcMessage& reply); + virtual std::vector requestCommands(); virtual void status(OdinData::IpcMessage& status); void add_performance_stats(OdinData::IpcMessage& status); void reset_performance_stats(); diff --git a/cpp/frameProcessor/src/DummyUDPProcessPlugin.cpp b/cpp/frameProcessor/src/DummyUDPProcessPlugin.cpp index 69826a08c..c14b1db2c 100644 --- a/cpp/frameProcessor/src/DummyUDPProcessPlugin.cpp +++ b/cpp/frameProcessor/src/DummyUDPProcessPlugin.cpp @@ -15,6 +15,8 @@ namespace FrameProcessor const std::string DummyUDPProcessPlugin::CONFIG_IMAGE_WIDTH = "width"; const std::string DummyUDPProcessPlugin::CONFIG_IMAGE_HEIGHT = "height"; const std::string DummyUDPProcessPlugin::CONFIG_COPY_FRAME = "copy_frame"; + // Command and parameters + const std::string DummyUDPProcessPlugin::EXECUTE_PRINT = "print"; /** * The constructor sets up default configuration parameters and logging used within the class. @@ -135,6 +137,43 @@ namespace FrameProcessor reply.set_param(base_str + DummyUDPProcessPlugin::CONFIG_COPY_FRAME, copy_frame_); } + /** + * Execute a command on the plugin. This receives an IpcMessage which should be processed + * to execute a command within the plugin, and any response can be added to the reply IpcMessage. + * The dummy plugin implements a single command "print" that prints the value of the parameter named. + * + * \param[in] config - String containing the command to execute. + * \param[out] reply - Reference to the reply IpcMessage object. + */ + void DummyUDPProcessPlugin::execute(const std::string& command, OdinData::IpcMessage& reply) + { + if (command == DummyUDPProcessPlugin::EXECUTE_PRINT){ + LOG4CXX_INFO(logger_, "Image width is " << image_width_); + LOG4CXX_INFO(logger_, "Image height is " << image_height_); + LOG4CXX_INFO(logger_, "Copy frame is " << copy_frame_); + } else { + std::stringstream is; + is << "Submitted command not supported: " << command; + LOG4CXX_ERROR(logger_, is.str()); + throw std::runtime_error(is.str().c_str()); + } + } + + /** + * Respond to command execution requests from clients. + * + * This method responds to command executions requests from client, populating the supplied IpcMessage + * reply with the commands and command parameters supported by this plugin. + * + * \return - Vector containing supported command strings. + */ + std::vector DummyUDPProcessPlugin::requestCommands() + { + // Reply with a vector of supported command strings. + std::vector cmds = {DummyUDPProcessPlugin::EXECUTE_PRINT}; + return cmds; + } + /** * Collate status information for the plugin. The status is added to the status IpcMessage object. * diff --git a/cpp/frameProcessor/src/FrameProcessorController.cpp b/cpp/frameProcessor/src/FrameProcessorController.cpp index 627b7f458..181215a8c 100644 --- a/cpp/frameProcessor/src/FrameProcessorController.cpp +++ b/cpp/frameProcessor/src/FrameProcessorController.cpp @@ -42,6 +42,8 @@ const std::string FrameProcessorController::CONFIG_EXECUTE = "exec const std::string FrameProcessorController::CONFIG_INDEX = "index"; const std::string FrameProcessorController::CONFIG_VALUE = "value"; +const std::string FrameProcessorController::COMMAND_KEY = "command"; + const int FrameProcessorController::META_TX_HWM = 10000; /** Construct a new FrameProcessorController class. @@ -135,6 +137,20 @@ void FrameProcessorController::handleCtrlChannel() LOG4CXX_DEBUG_LEVEL(3, logger_, "Control thread reply message (request configuration): " << replyMsg.encode()); } + else if ((ctrlMsg.get_msg_type() == OdinData::IpcMessage::MsgTypeCmd) && + (ctrlMsg.get_msg_val() == OdinData::IpcMessage::MsgValCmdExecute)) { + replyMsg.set_msg_type(OdinData::IpcMessage::MsgTypeAck); + this->execute(ctrlMsg, replyMsg); + LOG4CXX_DEBUG_LEVEL(3, logger_, "Control thread reply message (command): " + << replyMsg.encode()); + } + else if ((ctrlMsg.get_msg_type() == OdinData::IpcMessage::MsgTypeCmd) && + (ctrlMsg.get_msg_val() == OdinData::IpcMessage::MsgValCmdRequestCommands)) { + replyMsg.set_msg_type(OdinData::IpcMessage::MsgTypeAck); + this->requestCommands(replyMsg); + LOG4CXX_DEBUG_LEVEL(3, logger_, "Control thread reply message (request commands): " + << replyMsg.encode()); + } else if ((ctrlMsg.get_msg_type() == OdinData::IpcMessage::MsgTypeCmd) && (ctrlMsg.get_msg_val() == OdinData::IpcMessage::MsgValCmdStatus)) { replyMsg.set_msg_type(OdinData::IpcMessage::MsgTypeAck); @@ -507,6 +523,72 @@ void FrameProcessorController::requestConfiguration(OdinData::IpcMessage& reply) } } +/** + * Submit commands to the FrameProcessor plugins. + * + * Submits command(s) to execute on individual plugins if they + * support commands. The IpcMessage should contain the command + * name and a structure of any parameters required by the command. + * + * This method searches for command objects that have the same + * index as loaded plugins. If any of these are found then the + * commands are passed down to the plugin for execution. + * + * \param[in] config - IpcMessage containing command and any parameter data. + * \param[out] reply - Response IpcMessage. + */ +void FrameProcessorController::execute(OdinData::IpcMessage& config, OdinData::IpcMessage& reply) +{ + LOG4CXX_DEBUG_LEVEL(1, logger_, "Command submitted: " << config.encode()); + + // Loop over plugins, checking for command messages + std::map >::iterator iter; + bool commandPresent = false; + for (iter = plugins_.begin(); iter != plugins_.end(); ++iter) { + if (config.has_param(iter->first)) { + OdinData::IpcMessage subConfig(config.get_param(iter->first), + config.get_msg_type(), + config.get_msg_val()); + // Check if the payload has a command for this plugin + if (subConfig.has_param(FrameProcessorController::COMMAND_KEY)){ + commandPresent = true; + // Extract the command and execute on the plugin + std::string commandName = subConfig.get_param(FrameProcessorController::COMMAND_KEY); + iter->second->execute(commandName, reply); + } + } + } + if (!commandPresent){ + // If no valid commands have been found after checking through all plugins then NACK the reply + reply.set_nack("No valid commands found"); + } +} + +/** + * Request the command set supported by this FrameProcessorController and + * its loaded plugins. + * + * The method searches through all loaded plugins. Each plugin is + * also sent a request for its supported commands. + * + * \param[out] reply - Response IpcMessage with the current supported command set. + */ +void FrameProcessorController::requestCommands(OdinData::IpcMessage& reply) +{ + LOG4CXX_DEBUG_LEVEL(3, logger_, "Request for supported commands made"); + + // Loop over plugins and request current supported commands from each + std::map >::iterator iter; + std::vector::iterator cmd; + for (iter = plugins_.begin(); iter != plugins_.end(); ++iter) { + std::vector commands = iter->second->requestCommands(); + for (cmd = commands.begin(); cmd != commands.end(); ++cmd) { + std::string command_str = iter->first + "/" + FrameProcessorController::COMMAND_KEY + "[]"; + reply.set_param(command_str, *cmd); + } + } +} + /** * Reset statistics on all of the loaded plugins. * diff --git a/cpp/frameProcessor/src/FrameProcessorPlugin.cpp b/cpp/frameProcessor/src/FrameProcessorPlugin.cpp index 837e699f9..73458a1e5 100644 --- a/cpp/frameProcessor/src/FrameProcessorPlugin.cpp +++ b/cpp/frameProcessor/src/FrameProcessorPlugin.cpp @@ -143,7 +143,7 @@ std::vector FrameProcessorPlugin::get_warnings() return warning_messages_; } - /** Configure the plugin. +/** Configure the plugin. * * In this abstract class the configure method does perform any * actions, this should be overridden by subclasses. @@ -168,6 +168,38 @@ void FrameProcessorPlugin::requestConfiguration(OdinData::IpcMessage& reply) // Default method simply does nothing } +/** Execute a command within the plugin. + * + * In this abstract class the command method does perform any + * actions, this should be overridden by subclasses. + * + * \param[in] command - String containing the command to execute. + * \param[out] reply - Response IpcMessage. + */ +void FrameProcessorPlugin::execute(const std::string& command, OdinData::IpcMessage& reply) +{ + // A command has been submitted and this plugin has no execute implementation defined, + // throw a runtime error to report this. + std::stringstream is; + is << "Submitted command not supported: " << command; + LOG4CXX_ERROR(logger_, is.str()); + throw std::runtime_error(is.str().c_str()); +} + +/** Request the plugin's supported commands. + * + * In this abstract class the request method does perform any + * actions, this should be overridden by subclasses. + * + * \return - Vector containing supported command strings. + */ +std::vector FrameProcessorPlugin::requestCommands() +{ + // Default returns an empty vector. + std::vector reply; + return reply; +} + /** * Collate status information for the plugin. * diff --git a/cpp/frameProcessor/test/CMakeLists.txt b/cpp/frameProcessor/test/CMakeLists.txt index 704a3acfc..5c7c490e4 100644 --- a/cpp/frameProcessor/test/CMakeLists.txt +++ b/cpp/frameProcessor/test/CMakeLists.txt @@ -10,6 +10,7 @@ file(GLOB TEST_SOURCES FrameProcessorTest.cpp GapFillPluginTest.cpp MetaMessageTest.cpp + DummyUDPProcessPluginTest.cpp ) # Add tests for BloscPlugin if Blosc is present if (${BLOSC_FOUND}) @@ -39,6 +40,7 @@ target_link_libraries(frameProcessorTest LiveViewPlugin SumPlugin GapFillPlugin + DummyUDPProcessPlugin ${COMMON_LIBRARY}) # Link for BloscPlugin if Blosc is present diff --git a/cpp/frameProcessor/test/DummyUDPProcessPluginTest.cpp b/cpp/frameProcessor/test/DummyUDPProcessPluginTest.cpp new file mode 100644 index 000000000..b26cf15a2 --- /dev/null +++ b/cpp/frameProcessor/test/DummyUDPProcessPluginTest.cpp @@ -0,0 +1,47 @@ +/* + * DummyUDPProcessPluginTest.cpp + * + * Created on: 25 Sep 2024 + * Author: Alan Greer + */ + +#include +#include +#include "FrameProcessorDefinitions.h" +#include "DummyUDPProcessPlugin.h" +#include "IpcMessage.h" + +class DummyUDPProcessPluginTestFixture { +public: + DummyUDPProcessPluginTestFixture() { + set_debug_level(3); + + dummy_plugin.set_name("dummy"); + } + + ~DummyUDPProcessPluginTestFixture() {} + + FrameProcessor::DummyUDPProcessPlugin dummy_plugin; +}; + +BOOST_FIXTURE_TEST_SUITE(DummyUDPProcessPluginUnitTest, DummyUDPProcessPluginTestFixture); + +BOOST_AUTO_TEST_CASE( DummyUDPProcessPlugin_commands ) +{ + std::vector commands_reply; + OdinData::IpcMessage command_reply; + + // Request the command set of the DummyUDPProcessPlugin + BOOST_REQUIRE_NO_THROW(commands_reply = dummy_plugin.requestCommands()); + + // Verify the returned command set is as expected + BOOST_CHECK_EQUAL(commands_reply[0], std::string("print")); + + // Verify that an incorrect commmand request is rejected + BOOST_CHECK_THROW(dummy_plugin.execute("bad_command", command_reply), std::runtime_error); + + // Verify that a supported commmand request is accepted + BOOST_REQUIRE_NO_THROW(dummy_plugin.execute("print", command_reply)); +}; + +BOOST_AUTO_TEST_SUITE_END(); //DummyUDPProcessPluginUnitTest