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 edouard@3937: #include edouard@3937: #include edouard@3937: 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@3937: #include "erpc_PLCObject_server.hpp" 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@3937: class MyMessageBufferFactory : public MessageBufferFactory edouard@3937: { edouard@3937: public: edouard@3937: virtual MessageBuffer create() edouard@3937: { edouard@3937: uint8_t *buf = new uint8_t[1024]; edouard@3937: return MessageBuffer(buf, 1024); 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 ", edouard@3937: "b:baudrate ", edouard@3937: "p:port ", edouard@3937: "h:host ", 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 Type of transport.\n\ edouard@3937: -b/--baudrate Baud rate.\n\ edouard@3937: -p/--port Port name or port number.\n\ edouard@3937: -h/--host Host definition.\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_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@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@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@3937: MyMessageBufferFactory _msgFactory; edouard@3937: BasicCodecFactory _basicCodecFactory; edouard@3937: SimpleServer _server; edouard@3937: edouard@3937: BeremizPLCObjectService_service *svc; 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@3937: svc = new BeremizPLCObjectService_service(new PLCObject()); edouard@3937: edouard@3937: _server.addService(svc); 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: }