edouard@3940: /*
edouard@3940:  * Beremiz C++ runtime
edouard@3940:  *
edouard@3940:  * This file implements Beremiz C++ runtime Command Line Interface for POSIX
edouard@3940:  *
edouard@3940:  * Based on erpcsniffer.cpp, BSD-3-Clause, Copyright 2017 NXP
edouard@3940:  *
edouard@3940:  * Copyright 2024 Beremiz SAS
edouard@3940:  * 
edouard@3940:  * See COPYING for licensing details
edouard@3940:  */
edouard@3937: 
edouard@3937: #include <stdlib.h>
edouard@3937: #include <vector>
edouard@3937: #include <filesystem>
edouard@3937: 
edouard@3949: // eRPC includes
edouard@3937: #include "erpc_basic_codec.hpp"
edouard@3937: #include "erpc_serial_transport.hpp"
edouard@3937: #include "erpc_tcp_transport.hpp"
edouard@3937: #include "erpc_simple_server.hpp"
edouard@3937: 
edouard@3949: // eRPC generated includes
edouard@3937: #include "erpc_PLCObject_server.hpp"
edouard@3949: 
edouard@3949: // erpcgen includes (re-uses erpcgen's logging system and options parser)
edouard@3937: #include "Logging.hpp"
edouard@3937: #include "options.hpp"
edouard@3937: 
edouard@3937: #include "PLCObject.hpp"
edouard@3937: 
edouard@3937: using namespace erpc;
edouard@3937: using namespace std;
edouard@3937: 
edouard@3949: #define MSG_SIZE 1024*6
edouard@3937: class MyMessageBufferFactory : public MessageBufferFactory
edouard@3937: {
edouard@3937: public:
edouard@3937:     virtual MessageBuffer create()
edouard@3937:     {
edouard@3949:         uint8_t *buf = new uint8_t[MSG_SIZE];
edouard@3949:         return MessageBuffer(buf, MSG_SIZE);
edouard@3937:     }
edouard@3937: 
edouard@3937:     virtual void dispose(MessageBuffer *buf)
edouard@3937:     {
edouard@3937:         erpc_assert(buf);
edouard@3937:         if (*buf)
edouard@3937:         {
edouard@3937:             delete[] buf->get();
edouard@3937:         }
edouard@3937:     }
edouard@3937: };
edouard@3937: 
edouard@3937: 
edouard@3937: namespace beremizRuntime {
edouard@3937: 
edouard@3937: /*! The tool's name. */
edouard@3937: const char k_toolName[] = "beremizRuntime";
edouard@3937: 
edouard@3937: /*! Current version number for the tool. */
edouard@3937: const char k_version[] = __STRING(BEREMIZ_VERSION);
edouard@3937: 
edouard@3937: /*! Copyright string. */
edouard@3937: const char k_copyright[] = "Copyright 2024 Beremiz SAS. All rights reserved.";
edouard@3937: 
edouard@3937: static const char *k_optionsDefinition[] = { "?|help",
edouard@3937:                                              "V|version",
edouard@3937:                                              "v|verbose",
edouard@3937:                                              "t:transport <transport>",
edouard@3937:                                              "b:baudrate <baudrate>",
edouard@3937:                                              "p:port <port>",
edouard@3937:                                              "h:host <host>",
edouard@3957:                                              "a|autoload",
edouard@3937:                                              NULL };
edouard@3937: 
edouard@3937: /*! Help string. */
edouard@3937: const char k_usageText[] =
edouard@3937:     "\nOptions:\n\
edouard@3937:   -?/--help                    Show this help\n\
edouard@3937:   -V/--version                 Display tool version\n\
edouard@3937:   -v/--verbose                 Print extra detailed log information\n\
edouard@3937:   -t/--transport <transport>   Type of transport.\n\
edouard@3937:   -b/--baudrate <baudrate>     Baud rate.\n\
edouard@3937:   -p/--port <port>             Port name or port number.\n\
edouard@3937:   -h/--host <host>             Host definition.\n\
edouard@3957:   -a/--autoload                Autoload.\n\
edouard@3937: \n\
edouard@3937: Available transports (use with -t option):\n\
edouard@3937:   tcp      Tcp transport type (host, port number).\n\
edouard@3937:   serial   Serial transport type (port name, baud rate).\n\
edouard@3937: \n";
edouard@3937: 
edouard@3937: 
edouard@3937: /*!
edouard@3937:  * @brief Class that encapsulates the beremizRuntime tool.
edouard@3937:  *
edouard@3937:  * A single global logger instance is created during object construction. It is
edouard@3937:  * never freed because we need it up to the last possible minute, when an
edouard@3937:  * exception could be thrown.
edouard@3937:  */
edouard@3937: class beremizRuntimeCLI
edouard@3937: {
edouard@3937: protected:
edouard@3937:     enum class verbose_type_t
edouard@3937:     {
edouard@3937:         kWarning,
edouard@3937:         kInfo,
edouard@3937:         kDebug,
edouard@3937:         kExtraDebug
edouard@3937:     }; /*!< Types of verbose outputs from beremizRuntime application. */
edouard@3937: 
edouard@3937:     enum class transports_t
edouard@3937:     {
edouard@3937:         kNoneTransport,
edouard@3937:         kTcpTransport,
edouard@3937:         kSerialTransport
edouard@3937:     }; /*!< Type of transport to use. */
edouard@3937: 
edouard@3937:     typedef vector<string> string_vector_t;
edouard@3937: 
edouard@3937:     int m_argc;                   /*!< Number of command line arguments. */
edouard@3937:     char **m_argv;                /*!< String value for each command line argument. */
edouard@3937:     StdoutLogger *m_logger;       /*!< Singleton logger instance. */
edouard@3937:     verbose_type_t m_verboseType; /*!< Which type of log is need to set (warning, info, debug). */
edouard@3937:     const char *m_workingDir;     /*!< working directory. */
edouard@3937:     string_vector_t m_positionalArgs;
edouard@3937:     transports_t m_transport; /*!< Transport used for receiving messages. */
edouard@3937:     uint32_t m_baudrate;      /*!< Baudrate rate speed. */
edouard@3937:     const char *m_port;       /*!< Name or number of port. Based on used transport. */
edouard@3937:     const char *m_host;       /*!< Host name */
edouard@3957:     bool m_autoload = false;          /*!< Autoload flag. */
edouard@3937: 
edouard@3937: public:
edouard@3937:     /*!
edouard@3937:      * @brief Constructor.
edouard@3937:      *
edouard@3937:      * @param[in] argc Count of arguments in argv variable.
edouard@3937:      * @param[in] argv Pointer to array of arguments.
edouard@3937:      *
edouard@3937:      * Creates the singleton logger instance.
edouard@3937:      */
edouard@3937:     beremizRuntimeCLI(int argc, char *argv[]) :
edouard@3937:     m_argc(argc), m_argv(argv), m_logger(0), m_verboseType(verbose_type_t::kWarning),
edouard@3937:     m_workingDir(NULL), m_transport(transports_t::kNoneTransport), m_baudrate(115200), m_port(NULL),
edouard@3937:     m_host(NULL)
edouard@3937:     {
edouard@3937:         // create logger instance
edouard@3937:         m_logger = new StdoutLogger();
edouard@3937:         m_logger->setFilterLevel(Logger::log_level_t::kWarning);
edouard@3937:         Log::setLogger(m_logger);
edouard@3937:     }
edouard@3937: 
edouard@3937:     /*!
edouard@3937:      * @brief Destructor.
edouard@3937:      */
edouard@3937:     ~beremizRuntimeCLI() {}
edouard@3937: 
edouard@3937:     /*!
edouard@3937:      * @brief Reads the command line options passed into the constructor.
edouard@3937:      *
edouard@3937:      * This method can return a return code to its caller, which will cause the
edouard@3937:      * tool to exit immediately with that return code value. Normally, though, it
edouard@3937:      * will return -1 to signal that the tool should continue to execute and
edouard@3937:      * all options were processed successfully.
edouard@3937:      *
edouard@3937:      * The Options class is used to parse command line options. See
edouard@3937:      * #k_optionsDefinition for the list of options and #k_usageText for the
edouard@3937:      * descriptive help for each option.
edouard@3937:      *
edouard@3937:      * @retval -1 The options were processed successfully. Let the tool run normally.
edouard@3937:      * @return A zero or positive result is a return code value that should be
edouard@3937:      *      returned from the tool as it exits immediately.
edouard@3937:      */
edouard@3937:     int processOptions()
edouard@3937:     {
edouard@3937:         Options options(*m_argv, k_optionsDefinition);
edouard@3937:         OptArgvIter iter(--m_argc, ++m_argv);
edouard@3937: 
edouard@3937:         // process command line options
edouard@3937:         int optchar;
edouard@3937:         const char *optarg;
edouard@3937:         while ((optchar = options(iter, optarg)))
edouard@3937:         {
edouard@3937:             switch (optchar)
edouard@3937:             {
edouard@3937:                 case '?':
edouard@3937:                 {
edouard@3937:                     printUsage(options);
edouard@3937:                     return 0;
edouard@3937:                 }
edouard@3937: 
edouard@3937:                 case 'V':
edouard@3937:                 {
edouard@3937:                     printf("%s %s\n%s\n", k_toolName, k_version, k_copyright);
edouard@3937:                     return 0;
edouard@3937:                 }
edouard@3937: 
edouard@3937:                 case 'v':
edouard@3937:                 {
edouard@3937:                     if (m_verboseType != verbose_type_t::kExtraDebug)
edouard@3937:                     {
edouard@3937:                         m_verboseType = (verbose_type_t)(((int)m_verboseType) + 1);
edouard@3937:                     }
edouard@3937:                     break;
edouard@3937:                 }
edouard@3937: 
edouard@3937:                 case 't':
edouard@3937:                 {
edouard@3937:                     string transport = optarg;
edouard@3937:                     if (transport == "tcp")
edouard@3937:                     {
edouard@3937:                         m_transport = transports_t::kTcpTransport;
edouard@3937:                     }
edouard@3937:                     else if (transport == "serial")
edouard@3937:                     {
edouard@3937:                         m_transport = transports_t::kSerialTransport;
edouard@3937:                     }
edouard@3937:                     else
edouard@3937:                     {
edouard@3937:                         Log::error("error: unknown transport type %s", transport.c_str());
edouard@3937:                         return 1;
edouard@3937:                     }
edouard@3937:                     break;
edouard@3937:                 }
edouard@3937: 
edouard@3937:                 case 'b':
edouard@3937:                 {
edouard@3937:                     m_baudrate = strtoul(optarg, NULL, 10);
edouard@3937:                     break;
edouard@3937:                 }
edouard@3937: 
edouard@3937:                 case 'p':
edouard@3937:                 {
edouard@3937:                     m_port = optarg;
edouard@3937:                     break;
edouard@3937:                 }
edouard@3937: 
edouard@3937:                 case 'h':
edouard@3937:                 {
edouard@3937:                     m_host = optarg;
edouard@3937:                     break;
edouard@3937:                 }
edouard@3937: 
edouard@3957:                 case 'a':
edouard@3957:                 {
edouard@3957:                     m_autoload = true;
edouard@3957:                     break;
edouard@3957:                 }
edouard@3957: 
edouard@3937:                 default:
edouard@3937:                 {
edouard@3937:                     Log::error("error: unrecognized option\n\n");
edouard@3937:                     printUsage(options);
edouard@3937:                     return 0;
edouard@3937:                 }
edouard@3937:             }
edouard@3937:         }
edouard@3937: 
edouard@3937:         // handle positional args
edouard@3937:         if (iter.index() < m_argc)
edouard@3937:         {
edouard@3937:             if (m_argc - iter.index() > 1){
edouard@3937:                 Log::error("error: too many arguments\n\n");
edouard@3937:                 printUsage(options);
edouard@3937:                 return 0;
edouard@3937:             }
edouard@3937:             int i;
edouard@3937:             for (i = iter.index(); i < m_argc; ++i)
edouard@3937:             {
edouard@3937:                 m_positionalArgs.push_back(m_argv[i]);
edouard@3937:             }
edouard@3937:         }
edouard@3937: 
edouard@3937:         // all is well
edouard@3937:         return -1;
edouard@3937:     }
edouard@3937: 
edouard@3937:     /*!
edouard@3937:      * @brief Prints help for the tool.
edouard@3937:      *
edouard@3937:      * @param[in] options Options, which can be used.
edouard@3937:      */
edouard@3937:     void printUsage(Options &options)
edouard@3937:     {
edouard@3937:         options.usage(cout, "[path]");
edouard@3937:         printf(k_usageText);
edouard@3937:     }
edouard@3937: 
edouard@3937:     /*!
edouard@3937:      * @brief Core of the tool.
edouard@3937:      *
edouard@3937:      * Calls processOptions() to handle command line options before performing the
edouard@3937:      * real work the tool does.
edouard@3937:      *
edouard@3937:      * @retval 1 The functions wasn't processed successfully.
edouard@3937:      * @retval 0 The function was processed successfully.
edouard@3937:      *
edouard@3937:      * @exception Log::error This function is called, when function wasn't
edouard@3937:      *              processed successfully.
edouard@3937:      * @exception runtime_error Thrown, when positional args is empty.
edouard@3937:      */
edouard@3937:     int run()
edouard@3937:     {
edouard@3937:         try
edouard@3937:         {
edouard@3937:             // read command line options
edouard@3937:             int result;
edouard@3937:             if ((result = processOptions()) != -1)
edouard@3937:             {
edouard@3937:                 return result;
edouard@3937:             }
edouard@3937: 
edouard@3937:             // set verbose logging
edouard@3937:             setVerboseLogging();
edouard@3937: 
edouard@3937:             if (!m_positionalArgs.size())
edouard@3937:             {
edouard@3937:                 m_workingDir = std::filesystem::current_path().c_str();
edouard@3937:             } else {
edouard@3937:                 m_workingDir = m_positionalArgs[0].c_str();
edouard@3940:                 std::filesystem::current_path(m_workingDir);
edouard@3940:             }
edouard@3940: 
edouard@3940:             // remove temporary directory if it already exists
edouard@3940:             if (std::filesystem::exists("tmp"))
edouard@3940:             {
edouard@3940:                 std::filesystem::remove_all("tmp");
edouard@3940:             }
edouard@3940: 
edouard@3940:             // Create temporary directory in working directory
edouard@3940:             std::filesystem::create_directory("tmp");
edouard@3937: 
edouard@3937:             Transport *_transport;
edouard@3937:             switch (m_transport)
edouard@3937:             {
edouard@3937:                 case transports_t::kTcpTransport:
edouard@3937:                 {
edouard@3937:                     uint16_t portNumber = strtoul(m_port, NULL, 10);
edouard@3937:                     TCPTransport *tcpTransport = new TCPTransport(m_host, portNumber, true);
edouard@3937:                     if (erpc_status_t err = tcpTransport->open())
edouard@3937:                     {
edouard@3937:                         return err;
edouard@3937:                     }
edouard@3937:                     _transport = tcpTransport;
edouard@3937:                     break;
edouard@3937:                 }
edouard@3937: 
edouard@3937:                 case transports_t::kSerialTransport:
edouard@3937:                 {
edouard@3937:                     SerialTransport *serialTransport = new SerialTransport(m_port, m_baudrate);
edouard@3937: 
edouard@3937:                     uint8_t vtime = 0;
edouard@3937:                     uint8_t vmin = 1;
edouard@3937:                     while (kErpcStatus_Success != serialTransport->init(vtime, vmin))
edouard@3937:                         ;
edouard@3937: 
edouard@3937:                     _transport = serialTransport;
edouard@3937:                     break;
edouard@3937:                 }
edouard@3937: 
edouard@3937:                 default:
edouard@3937:                 {
edouard@3937:                     break;
edouard@3937:                 }
edouard@3937:             }
edouard@3937: 
edouard@3949:             Crc16 crc;
edouard@3949:             _transport->setCrc16(&crc);
edouard@3949: 
edouard@3937:             MyMessageBufferFactory _msgFactory;
edouard@3937:             BasicCodecFactory _basicCodecFactory;
edouard@3937:             SimpleServer _server;
edouard@3937: 
edouard@3937:             Log::info("Starting ERPC server...\n");
edouard@3937: 
edouard@3937:             _server.setMessageBufferFactory(&_msgFactory);
edouard@3937:             _server.setTransport(_transport);
edouard@3937:             _server.setCodecFactory(&_basicCodecFactory);
edouard@3937: 
edouard@3957:             PLCObject plc_object = PLCObject();
edouard@3957:             BeremizPLCObjectService_service svc = BeremizPLCObjectService_service(&plc_object);
edouard@3957: 
edouard@3957:             _server.addService(&svc);
edouard@3957: 
edouard@3957:             if(m_autoload)
edouard@3957:             {
edouard@3957:                 plc_object.AutoLoad();
edouard@3957:             }
edouard@3937: 
edouard@3937:             _server.run();
edouard@3937: 
edouard@3937:             return 0;
edouard@3937:         }
edouard@3937:         catch (exception &e)
edouard@3937:         {
edouard@3937:             Log::error("error: %s\n", e.what());
edouard@3937:             return 1;
edouard@3937:         }
edouard@3937:         catch (...)
edouard@3937:         {
edouard@3937:             Log::error("error: unexpected exception\n");
edouard@3937:             return 1;
edouard@3937:         }
edouard@3937: 
edouard@3937:         return 0;
edouard@3937:     }
edouard@3937: 
edouard@3937:     /*!
edouard@3937:      * @brief Turns on verbose logging.
edouard@3937:      */
edouard@3937:     void setVerboseLogging()
edouard@3937:     {
edouard@3937:         // verbose only affects the INFO and DEBUG filter levels
edouard@3937:         // if the user has selected quiet mode, it overrides verbose
edouard@3937:         switch (m_verboseType)
edouard@3937:         {
edouard@3937:             case verbose_type_t::kWarning:
edouard@3937:                 Log::getLogger()->setFilterLevel(Logger::log_level_t::kWarning);
edouard@3937:                 break;
edouard@3937:             case verbose_type_t::kInfo:
edouard@3937:                 Log::getLogger()->setFilterLevel(Logger::log_level_t::kInfo);
edouard@3937:                 break;
edouard@3937:             case verbose_type_t::kDebug:
edouard@3937:                 Log::getLogger()->setFilterLevel(Logger::log_level_t::kDebug);
edouard@3937:                 break;
edouard@3937:             case verbose_type_t::kExtraDebug:
edouard@3937:                 Log::getLogger()->setFilterLevel(Logger::log_level_t::kDebug2);
edouard@3937:                 break;
edouard@3937:         }
edouard@3937:     }
edouard@3937: };
edouard@3937: 
edouard@3937: } // namespace beremizRuntime
edouard@3937: 
edouard@3937: /*!
edouard@3937:  * @brief Main application entry point.
edouard@3937:  *
edouard@3937:  * Creates a tool instance and lets it take over.
edouard@3937:  */
edouard@3937: int main(int argc, char *argv[], char *envp[])
edouard@3937: {
edouard@3937:     (void)envp;
edouard@3937:     try
edouard@3937:     {
edouard@3937:         return beremizRuntime::beremizRuntimeCLI(argc, argv).run();
edouard@3937:     }
edouard@3937:     catch (...)
edouard@3937:     {
edouard@3937:         Log::error("error: unexpected exception\n");
edouard@3937:         return 1;
edouard@3937:     }
edouard@3937: 
edouard@3937:     return 0;
edouard@3937: }