From b6652630fae83b6919db0ae22c59f945dd6904b0 Mon Sep 17 00:00:00 2001 From: Mk16kawai <1041324852hzq@sina.com> Date: Mon, 11 Nov 2024 14:04:46 +0800 Subject: [PATCH] add modbus.Master --- components/comm/include/maix_modbus.hpp | 607 ++++++++++++++++-- components/comm/src/maix_modbus.cpp | 522 +++++++++------ .../.gitignore | 0 .../README.md | 2 +- .../app.yaml | 4 +- .../main/CMakeLists.txt | 0 .../main/Kconfig | 0 .../main/include/main.h | 0 .../maix_modbus_loopback/main/src/main.cpp | 222 +++++++ examples/maix_modbus_master/main/src/main.cpp | 63 -- 10 files changed, 1102 insertions(+), 318 deletions(-) rename examples/{maix_modbus_master => maix_modbus_loopback}/.gitignore (100%) rename examples/{maix_modbus_master => maix_modbus_loopback}/README.md (71%) rename examples/{maix_modbus_master => maix_modbus_loopback}/app.yaml (66%) rename examples/{maix_modbus_master => maix_modbus_loopback}/main/CMakeLists.txt (100%) rename examples/{maix_modbus_master => maix_modbus_loopback}/main/Kconfig (100%) rename examples/{maix_modbus_master => maix_modbus_loopback}/main/include/main.h (100%) create mode 100644 examples/maix_modbus_loopback/main/src/main.cpp delete mode 100644 examples/maix_modbus_master/main/src/main.cpp diff --git a/components/comm/include/maix_modbus.hpp b/components/comm/include/maix_modbus.hpp index 1368109d..01ab8b49 100644 --- a/components/comm/include/maix_modbus.hpp +++ b/components/comm/include/maix_modbus.hpp @@ -388,46 +388,192 @@ namespace maix::comm::modbus { }; /** - * Class for modbus Master - * @maixpy maix.comm.modbus.Master + * @brief Set the master debug ON/OFF + * + * @param debug True(ON) or False(OFF) + * + * @maixpy maix.comm.modbus.set_master_debug + */ + void set_master_debug(bool debug); + + /** + * Class for modbus MasterRTU + * @maixpy maix.comm.modbus.MasterRTU */ - class Master { + class MasterRTU final { public: /** - * @brief Construct a new Master object + * @brief Construct a new MasterRTU object * - * @param mode Specifies the communication mode: RTU or TCP. - * @see modbus.Mode for valid modes. - * @param ip_or_device The UART device name if using RTU mode. - * If TCP mode is chosen, this parameter is ignored. - * @param rtu_baud The baud rate for RTU communication. - * Supported rates include: 110, 300, 600, 1200, 2400, 4800, - * 9600, 19200, 38400, 57600, 115200, 230400, 460800, - * 500000, 576000, 921600, 1000000, 1152000, 1500000, - * 2500000, 3000000, 3500000, 4000000. - * Default is 115200. - * @param rtu_slave The RTU slave address. Ignored in TCP mode. Default is 1. - * @param tcp_port The port used for TCP communication. Ignored in RTU mode. Default is 5020. - * @param debug A boolean flag to enable or disable debug mode. Default is false. + * @param device Default uart device. + * @param baudrate Default uart baudrate. * - * @maixpy maix.comm.modbus.Master.__init__ + * @maixpy maix.comm.modbus.MasterRTU.__init__ */ - Master(::maix::comm::modbus::Mode mode, const std::string& ip_or_device, - int rtu_baud=115200, uint8_t rtu_slave=1, - int tcp_port=5020, bool debug=false); + MasterRTU(const std::string& device="", const int baudrate=115200); + + MasterRTU(const MasterRTU&) = default; + MasterRTU& operator=(const MasterRTU&) = default; + + MasterRTU(MasterRTU&&) = default; + MasterRTU& operator=(MasterRTU&&) = default; + + ~MasterRTU(); + + /** + * @brief Reads coils from the Modbus device. + * + * This function reads a specified number of coils starting from a given address. + * It includes timeout settings to define how long to wait for a response. + * + * @param slave_id The RTU slave address. + * @param size The number of coils to read. + * @param addr The starting address for reading coils. Default is 0. + * @param timeout_ms The timeout duration for waiting to receive a request. + * - A value of -1 makes the function block indefinitely until a request + * is received. + * - A value of 0 makes it non-blocking, returning immediately without + * waiting for a request. + * - A positive value specifies the maximum time (in milliseconds) to wait + * for a request before timing out. + * @param device The UART device to use. An empty string ("") indicates that the + * default device from the constructor will be used. + * @param baudrate The UART baud rate. A value of -1 signifies that the default baud rate + * from the constructor will be applied. + * + * @return std::vector/list[int] A vector containing the read coil values. + * + * @maixpy maix.comm.modbus.MasterRTU.read_coils + */ + std::vector read_coils(const uint32_t slave_id, const uint32_t size, + const uint32_t addr=0, const int timeout_ms=-1, + const std::string& device="", const int baudrate=-1); + + /** + * @brief Writes values to coils on the Modbus device. + * + * This function writes the specified data to the coils starting from a given address. + * + * @param slave_id The RTU slave address. + * @param data A vector containing the coil values to write. + * @param addr The starting address for writing coils. Default is 0. + * @param timeout_ms The timeout duration for the write operation. + * - A value of -1 makes the function block until the write is complete. + * - A value of 0 makes it non-blocking. + * - A positive value specifies the maximum time (in milliseconds) to wait. + * + * @param device The UART device to use. An empty string ("") indicates that the + * default device from the constructor will be used. + * @param baudrate The UART baud rate. A value of -1 signifies that the default baud rate + * from the constructor will be applied. + * @return int Returns the number of bytes written on success, or a value less than 0 on failure. + * + * @maixpy maix.comm.modbus.MasterRTU.write_coils + */ + int write_coils(const uint32_t slave_id, const std::vector& data, + const uint32_t addr=0, const int timeout_ms=-1, + const std::string& device="", const int baudrate=-1); + + /** + * @brief Reads discrete inputs from the Modbus device. + * + * This function reads a specified number of discrete inputs starting from a given address. + * + * @param slave_id The RTU slave address. + * @param size The number of discrete inputs to read. + * @param addr The starting address for reading discrete inputs. Default is 0. + * @param timeout_ms The timeout duration for the write operation. + * - A value of -1 makes the function block until the write is complete. + * - A value of 0 makes it non-blocking. + * - A positive value specifies the maximum time (in milliseconds) to wait. + * + * @param device The UART device to use. An empty string ("") indicates that the + * default device from the constructor will be used. + * @param baudrate The UART baud rate. A value of -1 signifies that the default baud rate + * from the constructor will be applied. + * + * @return std::vector/list[int] A vector containing the read discrete input values. + * + * @maixpy maix.comm.modbus.MasterRTU.read_discrete_input + */ + std::vector read_discrete_input(const uint32_t slave_id, const uint32_t size, + const uint32_t addr=0, const int timeout_ms=-1, + const std::string& device="", const int baudrate=-1); + + /** + * @brief Reads input registers from the Modbus device. + * + * This function reads a specified number of input registers starting from a given address. + * + * @param slave_id The RTU slave address. + * @param size The number of input registers to read. + * @param addr The starting address for reading input registers. Default is 0. + * @param timeout_ms The timeout duration for the write operation. + * - A value of -1 makes the function block until the write is complete. + * - A value of 0 makes it non-blocking. + * - A positive value specifies the maximum time (in milliseconds) to wait. + * + * @param device The UART device to use. An empty string ("") indicates that the + * default device from the constructor will be used. + * @param baudrate The UART baud rate. A value of -1 signifies that the default baud rate + * from the constructor will be applied. + * @return std::vector/list[int] A vector containing the read input register values. + * + * @maixpy maix.comm.modbus.MasterRTU.read_input_registers + */ + std::vector read_input_registers(const uint32_t slave_id, const uint32_t size, + const uint32_t addr=0, const int timeout_ms=-1, + const std::string& device="", const int baudrate=-1); /** - * std::unique_ptr is used in this class, which means the copy constructor will be deleted - * to prevent accidental copying of objects that manage unique resources. + * @brief Reads holding registers from the Modbus device. + * + * This function reads a specified number of holding registers starting from a given address. + * + * @param slave_id The RTU slave address. + * @param size The number of holding registers to read. + * @param addr The starting address for reading holding registers. Default is 0. + * @param timeout_ms The timeout duration for the write operation. + * - A value of -1 makes the function block until the write is complete. + * - A value of 0 makes it non-blocking. + * - A positive value specifies the maximum time (in milliseconds) to wait. * - * The move constructor allows for the transfer of ownership of resources from one - * object to another using std::move(). This enables efficient resource management - * and avoids unnecessary deep copies. + * @param device The UART device to use. An empty string ("") indicates that the + * default device from the constructor will be used. + * @param baudrate The UART baud rate. A value of -1 signifies that the default baud rate + * from the constructor will be applied. + * @return std::vector/list[int] A vector containing the read holding register values. + * + * @maixpy maix.comm.modbus.MasterRTU.read_holding_registers */ - Master(Master&& other) noexcept; - Master& operator=(Master&& other) noexcept; + std::vector read_holding_registers(const uint32_t slave_id, const uint32_t size, + const uint32_t addr=0, const int timeout_ms=-1, + const std::string& device="", const int baudrate=-1); - ~Master(); + /** + * @brief Writes values to holding registers on the Modbus device. + * + * This function writes the specified data to the holding registers starting from a given address. + * + * @param slave_id The RTU slave address. + * @param data A vector containing the values to write to holding registers. + * @param addr The starting address for writing holding registers. Default is 0. + * @param timeout_ms The timeout duration for the write operation. + * - A value of -1 makes the function block until the write is complete. + * - A value of 0 makes it non-blocking. + * - A positive value specifies the maximum time (in milliseconds) to wait. + * + * @param device The UART device to use. An empty string ("") indicates that the + * default device from the constructor will be used. + * @param baudrate The UART baud rate. A value of -1 signifies that the default baud rate + * from the constructor will be applied. + * @return int Returns the number of bytes written on success, or a value less than 0 on failure. + * + * @maixpy maix.comm.modbus.MasterRTU.write_holding_registers + */ + int write_holding_registers(const uint32_t slave_id, const std::vector& data, + const uint32_t addr=0, const int timeout_ms=-1, + const std::string& device="", const int baudrate=-1); /** * @brief Reads coils from the Modbus device. @@ -435,6 +581,9 @@ namespace maix::comm::modbus { * This function reads a specified number of coils starting from a given address. * It includes timeout settings to define how long to wait for a response. * + * @param device The UART device to use. + * @param baudrate he UART baud rate. + * @param slave_id The RTU slave address. * @param size The number of coils to read. * @param addr The starting address for reading coils. Default is 0. * @param timeout_ms The timeout duration for waiting to receive a request. @@ -445,17 +594,22 @@ namespace maix::comm::modbus { * - A positive value specifies the maximum time (in milliseconds) to wait * for a request before timing out. * - * @return std::vector A vector containing the read coil values. + * @return std::vector/list[int] A vector containing the read coil values. * - * @maixpy maix.comm.modbus.Master.read_coils + * @maixcdk maix.comm.modbus.MasterRTU.read_coils */ - std::vector read_coils(const uint32_t size, const uint32_t addr=0, const int timeout_ms=-1); + static std::vector read_coils( const std::string& device, const int baudrate, + const uint32_t slave_id, const uint32_t size, + const uint32_t addr=0, const int timeout_ms=-1); /** * @brief Writes values to coils on the Modbus device. * * This function writes the specified data to the coils starting from a given address. * + * @param device The UART device to use. + * @param baudrate The UART baud rate. + * @param slave_id The RTU slave address. * @param data A vector containing the coil values to write. * @param addr The starting address for writing coils. Default is 0. * @param timeout_ms The timeout duration for the write operation. @@ -465,107 +619,418 @@ namespace maix::comm::modbus { * * @return int Returns the number of bytes written on success, or a value less than 0 on failure. * - * @maixpy maix.comm.modbus.Master.write_coils + * @maixcdk maix.comm.modbus.MasterRTU.write_coils */ - int write_coils(const std::vector& data, const uint32_t addr=0, const int timeout_ms=-1); + static int write_coils(const std::string& device, const int baudrate, + const uint32_t slave_id, const std::vector& data, + const uint32_t addr=0, const int timeout_ms=-1); /** * @brief Reads discrete inputs from the Modbus device. * * This function reads a specified number of discrete inputs starting from a given address. * + * @param device The UART device to use. + * @param baudrate The UART baud rate. + * @param slave_id The RTU slave address. * @param size The number of discrete inputs to read. * @param addr The starting address for reading discrete inputs. Default is 0. - * @param timeout_ms The timeout duration for waiting to receive a request. + * @param timeout_ms The timeout duration for the write operation. + * - A value of -1 makes the function block until the write is complete. + * - A value of 0 makes it non-blocking. + * - A positive value specifies the maximum time (in milliseconds) to wait. * - * @return std::vector A vector containing the read discrete input values. + * @return std::vector/list[int] A vector containing the read discrete input values. * - * @maixpy maix.comm.modbus.Master.read_discrete_input + * @maixcdk maix.comm.modbus.MasterRTU.read_discrete_input */ - std::vector read_discrete_input(const uint32_t size, const uint32_t addr=0, const int timeout_ms=-1); + static std::vector read_discrete_input(const std::string& device, const int baudrate, + const uint32_t slave_id, const uint32_t size, + const uint32_t addr=0, const int timeout_ms=-1); /** * @brief Reads input registers from the Modbus device. * * This function reads a specified number of input registers starting from a given address. * + * @param device The UART device to use. + * @param baudrate The UART baud rate. + * @param slave_id The RTU slave address. * @param size The number of input registers to read. * @param addr The starting address for reading input registers. Default is 0. - * @param timeout_ms The timeout duration for waiting to receive a request. + * @param timeout_ms The timeout duration for the write operation. + * - A value of -1 makes the function block until the write is complete. + * - A value of 0 makes it non-blocking. + * - A positive value specifies the maximum time (in milliseconds) to wait. * - * @return std::vector A vector containing the read input register values. + * @return std::vector/list[int] A vector containing the read input register values. * - * @maixpy maix.comm.modbus.Master.read_input_registers + * @maixcdk maix.comm.modbus.MasterRTU.read_input_registers */ - std::vector read_input_registers(const uint32_t size, const uint32_t addr=0, const int timeout_ms=-1); + static std::vector read_input_registers(const std::string& device, const int baudrate, + const uint32_t slave_id, const uint32_t size, + const uint32_t addr=0, const int timeout_ms=-1); /** * @brief Reads holding registers from the Modbus device. * * This function reads a specified number of holding registers starting from a given address. * + * @param device The UART device to use. + * @param baudrate The UART baud rate. + * @param slave_id The RTU slave address. * @param size The number of holding registers to read. * @param addr The starting address for reading holding registers. Default is 0. + * @param timeout_ms The timeout duration for the write operation. + * - A value of -1 makes the function block until the write is complete. + * - A value of 0 makes it non-blocking. + * - A positive value specifies the maximum time (in milliseconds) to wait. + * + * @return std::vector/list[int] A vector containing the read holding register values. + * + * @maixcdk maix.comm.modbus.MasterRTU.read_holding_registers + */ + static std::vector read_holding_registers(const std::string& device, const int baudrate, + const uint32_t slave_id, const uint32_t size, + const uint32_t addr=0, const int timeout_ms=-1); + + /** + * @brief Writes values to holding registers on the Modbus device. + * + * This function writes the specified data to the holding registers starting from a given address. + * + * @param device The UART device to use. + * @param baudrate The UART baud rate. + * @param slave_id The RTU slave address. + * @param data A vector containing the values to write to holding registers. + * @param addr The starting address for writing holding registers. Default is 0. + * @param timeout_ms The timeout duration for the write operation. + * - A value of -1 makes the function block until the write is complete. + * - A value of 0 makes it non-blocking. + * - A positive value specifies the maximum time (in milliseconds) to wait. + * + * @return int Returns the number of bytes written on success, or a value less than 0 on failure. + * + * @maixcdk maix.comm.modbus.MasterRTU.write_holding_registers + */ + static int write_holding_registers(const std::string& device, const int baudrate, + const uint32_t slave_id, const std::vector& data, + const uint32_t addr=0, const int timeout_ms=-1); + + private: + std::pair get_cfg(const std::string& device, const int baudrate) noexcept; + + private: + std::string device_; + int baudrate_; + }; + + /** + * Class for modbus Master + * @maixpy maix.comm.modbus.MasterTCP + */ + class MasterTCP final { + public: + /** + * @brief Construct a new MasterTCP object + * + * @param port Device tcp port. + * + * @maixpy maix.comm.modbus.MasterTCP.__init__ + */ + MasterTCP(const int port=502); + + ~MasterTCP(); + + /** + * @brief Reads coils from the Modbus device. + * + * This function reads a specified number of coils starting from a given address. + * It includes timeout settings to define how long to wait for a response. + * + * @param ip The TCP IP address. + * @param size The number of coils to read. + * @param addr The starting address for reading coils. Default is 0. * @param timeout_ms The timeout duration for waiting to receive a request. + * - A value of -1 makes the function block indefinitely until a request + * is received. + * - A value of 0 makes it non-blocking, returning immediately without + * waiting for a request. + * - A positive value specifies the maximum time (in milliseconds) to wait + * for a request before timing out. + * + * @param port The TCP port. A value of -1 signifies that the default port + * from the constructor will be applied. + * + * @return std::vector/list[int] A vector containing the read coil values. + * + * @maixpy maix.comm.modbus.MasterTCP.read_coils + */ + std::vector read_coils(const std::string ip, const uint32_t size, + const uint32_t addr=0, const int timeout_ms=-1, + const int port=-1); + + /** + * @brief Writes values to coils on the Modbus device. + * + * This function writes the specified data to the coils starting from a given address. + * + * @param ip The TCP IP address. + * @param data A vector containing the coil values to write. + * @param addr The starting address for writing coils. Default is 0. + * @param timeout_ms The timeout duration for the write operation. + * - A value of -1 makes the function block until the write is complete. + * - A value of 0 makes it non-blocking. + * - A positive value specifies the maximum time (in milliseconds) to wait. + * + * @param port The TCP port. A value of -1 signifies that the default port + * from the constructor will be applied. + * + * @return int Returns the number of bytes written on success, or a value less than 0 on failure. + * + * @maixpy maix.comm.modbus.MasterTCP.write_coils + */ + int write_coils(const std::string ip, const std::vector& data, + const uint32_t addr=0, const int timeout_ms=-1, + const int port=-1); + + /** + * @brief Reads discrete inputs from the Modbus device. + * + * This function reads a specified number of discrete inputs starting from a given address. + * + * @param ip The TCP IP address. + * @param size The number of discrete inputs to read. + * @param addr The starting address for reading discrete inputs. Default is 0. + * @param timeout_ms The timeout duration for the write operation. + * - A value of -1 makes the function block until the write is complete. + * - A value of 0 makes it non-blocking. + * - A positive value specifies the maximum time (in milliseconds) to wait. + * + * @param port The TCP port. A value of -1 signifies that the default port + * from the constructor will be applied. + * + * @return std::vector/list[int] A vector containing the read discrete input values. + * + * @maixpy maix.comm.modbus.MasterTCP.read_discrete_input + */ + std::vector read_discrete_input(const std::string ip, const uint32_t size, + const uint32_t addr=0, const int timeout_ms=-1, + const int port=-1); + + /** + * @brief Reads input registers from the Modbus device. + * + * This function reads a specified number of input registers starting from a given address. * - * @return std::vector A vector containing the read holding register values. + * @param ip The TCP IP address. + * @param size The number of input registers to read. + * @param addr The starting address for reading input registers. Default is 0. + * @param timeout_ms The timeout duration for the write operation. + * - A value of -1 makes the function block until the write is complete. + * - A value of 0 makes it non-blocking. + * - A positive value specifies the maximum time (in milliseconds) to wait. + * + * @param port The TCP port. A value of -1 signifies that the default port + * from the constructor will be applied. + * + * @return std::vector/list[int] A vector containing the read input register values. + * + * @maixpy maix.comm.modbus.MasterTCP.read_input_registers + */ + std::vector read_input_registers(const std::string ip, const uint32_t size, + const uint32_t addr=0, const int timeout_ms=-1, + const int port=-1); + + /** + * @brief Reads holding registers from the Modbus device. + * + * This function reads a specified number of holding registers starting from a given address. + * + * @param ip The TCP IP address. + * @param size The number of holding registers to read. + * @param addr The starting address for reading holding registers. Default is 0. + * @param timeout_ms The timeout duration for the write operation. + * - A value of -1 makes the function block until the write is complete. + * - A value of 0 makes it non-blocking. + * - A positive value specifies the maximum time (in milliseconds) to wait. * - * @maixpy maix.comm.modbus.Master.read_holding_registers + * @param port The TCP port. A value of -1 signifies that the default port + * from the constructor will be applied. + * + * @return std::vector/list[int] A vector containing the read holding register values. + * + * @maixpy maix.comm.modbus.MasterTCP.read_holding_registers */ - std::vector read_holding_registers(const uint32_t size, const uint32_t addr=0, const int timeout_ms=-1); + std::vector read_holding_registers(const std::string ip, const uint32_t size, + const uint32_t addr=0, const int timeout_ms=-1, + const int port=-1); /** * @brief Writes values to holding registers on the Modbus device. * * This function writes the specified data to the holding registers starting from a given address. * + * @param ip The TCP IP address. * @param data A vector containing the values to write to holding registers. * @param addr The starting address for writing holding registers. Default is 0. * @param timeout_ms The timeout duration for the write operation. + * - A value of -1 makes the function block until the write is complete. + * - A value of 0 makes it non-blocking. + * - A positive value specifies the maximum time (in milliseconds) to wait. + * + * @param port The TCP port. A value of -1 signifies that the default port + * from the constructor will be applied. * * @return int Returns the number of bytes written on success, or a value less than 0 on failure. * - * @maixpy maix.comm.modbus.Master.write_holding_registers + * @maixpy maix.comm.modbus.MasterTCP.write_holding_registers */ - int write_holding_registers(const std::vector& data, const uint32_t addr=0, const int timeout_ms=-1); + int write_holding_registers(const std::string ip, const std::vector& data, + const uint32_t addr=0, const int timeout_ms=-1, + const int port=-1); /** - * @brief Returns the raw pointer to the modbus_t + * @brief Reads coils from the Modbus device. * - * @note: The returned pointer must not be deleted or freed. + * This function reads a specified number of coils starting from a given address. + * It includes timeout settings to define how long to wait for a response. * - * @return ::modbus_t* type + * @param ip The TCP IP address. + * @param port The TCP port. + * @param size The number of coils to read. + * @param addr The starting address for reading coils. Default is 0. + * @param timeout_ms The timeout duration for waiting to receive a request. + * - A value of -1 makes the function block indefinitely until a request + * is received. + * - A value of 0 makes it non-blocking, returning immediately without + * waiting for a request. + * - A positive value specifies the maximum time (in milliseconds) to wait + * for a request before timing out. + * + * @return std::vector/list[int] A vector containing the read coil values. + * + * @maixcdk maix.comm.modbus.MasterTCP.read_coils */ - ::modbus_t* context() const noexcept; + static std::vector read_coils( const std::string ip, const int port, + const uint32_t size, + const uint32_t addr=0, const int timeout_ms=-1); /** - * @brief Returns the raw pointer to the modbus_t + * @brief Writes values to coils on the Modbus device. * - * @note: The returned pointer must not be deleted or freed. + * This function writes the specified data to the coils starting from a given address. * - * @return ::modbus_t* type + * @param ip The TCP IP address. + * @param port The TCP port. + * @param data A vector containing the coil values to write. + * @param addr The starting address for writing coils. Default is 0. + * @param timeout_ms The timeout duration for the write operation. + * - A value of -1 makes the function block until the write is complete. + * - A value of 0 makes it non-blocking. + * - A positive value specifies the maximum time (in milliseconds) to wait. + * + * @return int Returns the number of bytes written on success, or a value less than 0 on failure. + * + * @maixcdk maix.comm.modbus.MasterTCP.write_coils */ - ::modbus_t* operator()() const noexcept; + static int write_coils(const std::string ip, const int port, + const std::vector& data, + const uint32_t addr=0, const int timeout_ms=-1); + + /** + * @brief Reads discrete inputs from the Modbus device. + * + * This function reads a specified number of discrete inputs starting from a given address. + * + * @param ip The TCP IP address. + * @param port The TCP port. + * @param size The number of discrete inputs to read. + * @param addr The starting address for reading discrete inputs. Default is 0. + * @param timeout_ms The timeout duration for the write operation. + * - A value of -1 makes the function block until the write is complete. + * - A value of 0 makes it non-blocking. + * - A positive value specifies the maximum time (in milliseconds) to wait. + * + * @return std::vector/list[int] A vector containing the read discrete input values. + * + * @maixcdk maix.comm.modbus.MasterTCP.read_discrete_input + */ + static std::vector read_discrete_input(const std::string ip, const int port, + const uint32_t size, + const uint32_t addr=0, const int timeout_ms=-1); + + /** + * @brief Reads input registers from the Modbus device. + * + * This function reads a specified number of input registers starting from a given address. + * + * @param ip The TCP IP address. + * @param port The TCP port. + * @param size The number of input registers to read. + * @param addr The starting address for reading input registers. Default is 0. + * @param timeout_ms The timeout duration for the write operation. + * - A value of -1 makes the function block until the write is complete. + * - A value of 0 makes it non-blocking. + * - A positive value specifies the maximum time (in milliseconds) to wait. + * + * @return std::vector/list[int] A vector containing the read input register values. + * + * @maixcdk maix.comm.modbus.MasterTCP.read_input_registers + */ + static std::vector read_input_registers(const std::string ip, const int port, + const uint32_t size, + const uint32_t addr=0, const int timeout_ms=-1); + + /** + * @brief Reads holding registers from the Modbus device. + * + * This function reads a specified number of holding registers starting from a given address. + * + * @param ip The TCP IP address. + * @param port The TCP port. + * @param size The number of holding registers to read. + * @param addr The starting address for reading holding registers. Default is 0. + * @param timeout_ms The timeout duration for the write operation. + * - A value of -1 makes the function block until the write is complete. + * - A value of 0 makes it non-blocking. + * - A positive value specifies the maximum time (in milliseconds) to wait. + * + * @return std::vector/list[int] A vector containing the read holding register values. + * + * @maixcdk maix.comm.modbus.MasterTCP.read_holding_registers + */ + static std::vector read_holding_registers(const std::string ip, const int port, + const uint32_t size, + const uint32_t addr=0, const int timeout_ms=-1); + + /** + * @brief Writes values to holding registers on the Modbus device. + * + * This function writes the specified data to the holding registers starting from a given address. + * + * @param ip The TCP IP address. + * @param port The TCP port. + * @param data A vector containing the values to write to holding registers. + * @param addr The starting address for writing holding registers. Default is 0. + * @param timeout_ms The timeout duration for the write operation. + * - A value of -1 makes the function block until the write is complete. + * - A value of 0 makes it non-blocking. + * - A positive value specifies the maximum time (in milliseconds) to wait. + * + * @return int Returns the number of bytes written on success, or a value less than 0 on failure. + * + * @maixcdk maix.comm.modbus.MasterTCP.write_holding_registers + */ + static int write_holding_registers(const std::string ip, const int port, + const std::vector& data, + const uint32_t addr=0, const int timeout_ms=-1); private: - void rtu_init(const std::string& device, int baud, uint8_t slave); - void tcp_init(const std::string& ip, int port); - const std::string TAG() const noexcept; - void debug_init(); - ::maix::err::Err set_timeout(uint32_t sec, uint32_t usec); - template - std::vector read(const uint32_t size, const uint32_t index, const int timeout_ms, - const std::string& name, std::function func); - template - int write(const std::vector& data, const uint32_t index, const int timeout_ms, - const std::string& name, std::function func); + int get_cfg(int port) noexcept; private: - std::unique_ptr - ctx_{nullptr, &modbus_free}; - bool debug_; - uint32_t curr_timeout_sec_{165}; - uint32_t curr_timeout_usec_{528}; + int port_; }; } diff --git a/components/comm/src/maix_modbus.cpp b/components/comm/src/maix_modbus.cpp index ae558c3b..e6732853 100644 --- a/components/comm/src/maix_modbus.cpp +++ b/components/comm/src/maix_modbus.cpp @@ -1,11 +1,12 @@ #include "maix_modbus.hpp" #include "maix_log.hpp" -#include // std::memcpy -#include // std::runtime_error -#include // close -#include -#include // select +#include // std::memcpy +#include // std::runtime_error +#include // close +#include // std::numeric_limits +#include // select +#include // std::stringstream namespace maix::comm::modbus { @@ -482,207 +483,204 @@ ::modbus_t* Slave::operator()() const noexcept /****************************** Master *********************************/ -const std::string Master::TAG() const noexcept -{ - return "[Maix Modbus Master]"; -} +class MasterOperator final { +public: + template + using ModbusOpsRFunc = std::function; -void Master::rtu_init(const std::string& device, int baud, uint8_t slave) -{ - if (this->debug_) { - log::info("%s Mode: RTU, Port: %s, Baudrate: %d-8N1, Slave addr: %u.", - this->TAG().c_str(), device.c_str(), baud, slave); - } + template + using ModbusOpsWFunc = std::function; - this->ctx_.reset(::modbus_new_rtu(device.c_str(), baud, 'N', 8, 1)); - if (this->ctx_ == nullptr) { - const std::string msg(this->TAG()+" malloc failed!"); - __error_and_throw__(msg); - } + MasterOperator() = delete; + MasterOperator(const MasterOperator&) = delete; + MasterOperator(MasterOperator&&) = delete; - if (::modbus_set_slave(this->ctx_.get(), slave) < 0) { - const std::string msg(this->TAG()+" set slave failed!"); - __error_and_throw__(msg); - } + MasterOperator& operator=(const MasterOperator&) = delete; + MasterOperator& operator=(MasterOperator&&) = delete; - this->debug_init(); + ~MasterOperator() = delete; - if (::modbus_connect(this->ctx_.get()) < 0) { - const std::string msg(this->TAG()+" connect failed!"+std::string(::modbus_strerror(errno))); - __error_and_throw__(msg); - } -} + static const std::string TAG() noexcept; + static void debug(bool debug) noexcept; + static void deinit(::modbus_t* ptr) noexcept; -void Master::tcp_init(const std::string& ip, int port) -{ - if (this->debug_) { - log::info("%s Mode: TCP, Port: %d", - this->TAG().c_str(), port); - } + static std::unique_ptr<::modbus_t, decltype(&deinit)> rtu_init(const std::string& device, int baud, int slave) noexcept; + static std::unique_ptr<::modbus_t, decltype(&deinit)> tcp_init(const std::string& ip, int port) noexcept; - this->ctx_.reset(::modbus_new_tcp(ip.c_str(), port)); - if (this->ctx_.get() == nullptr) { - const std::string msg(this->TAG()+" malloc failed!"); - __error_and_throw__(msg); - } + template + static std::vector read(::modbus_t* ctx, const uint32_t size, const uint32_t index, + const int timeout_ms, const std::string& name, ModbusOpsRFunc func); - this->debug_init(); + template + static int write(::modbus_t* ctx, const std::vector& data, const uint32_t index, const int timeout_ms, + const std::string& name, ModbusOpsWFunc func); - if (::modbus_connect(this->ctx_.get()) < 0) { - const std::string msg(this->TAG()+" connect failed!"+std::string(::modbus_strerror(errno))); - __error_and_throw__(msg); - } +private: + static int debug_init(::modbus_t* ctx) noexcept; + static int set_timeout(::modbus_t* ctx, int timeout=-1) noexcept; + static inline std::unique_ptr<::modbus_t, decltype(&MasterOperator::deinit)> empty_ctx() noexcept; + +private: + static bool debug_; +}; + +bool MasterOperator::debug_ = false; + +const std::string MasterOperator::TAG() noexcept +{ + return "[Maix Modbus Master]"; } -void Master::debug_init() +void MasterOperator::debug(bool debug) noexcept { - if (::modbus_set_debug(this->ctx_.get(), this->debug_) < 0) { - const std::string msg(this->TAG()+" set debug failed!"+std::string(::modbus_strerror(errno))); - __error_and_throw__(msg); - } - if (this->debug_) { - log::info("%s set debug succ", this->TAG().c_str()); + debug_ = debug; +} + +std::unique_ptr<::modbus_t, decltype(&MasterOperator::deinit)> MasterOperator::empty_ctx() noexcept +{ + return std::unique_ptr<::modbus_t, decltype(&MasterOperator::deinit)>(nullptr, &MasterOperator::deinit); +} + +int MasterOperator::debug_init(::modbus_t* ctx) noexcept +{ + if (::modbus_set_debug(ctx, debug_) < 0) { + const std::string msg(TAG()+" set debug failed!"+std::string(::modbus_strerror(errno))); + log::error(msg.c_str()); + return -1; } + return 0; } -::maix::err::Err Master::set_timeout(uint32_t sec, uint32_t usec) +int MasterOperator::set_timeout(::modbus_t* ctx, int timeout_ms) noexcept { - // modbus_set_response_timeout - // nonblocking - if (sec == 0 && usec == 0) { - if (this->curr_timeout_sec_ == 0 && this->curr_timeout_usec_ == 1) - return ::maix::err::Err::ERR_NONE; - if (this->debug_) { - log::info("%s Timeout: 0", this->TAG().c_str()); + auto __set_timeout__ = [&ctx](uint32_t sec, uint32_t usec, const std::string& name){ + if (debug_) { + log::info("%s timeout %s", TAG().c_str(), name.c_str()); } - this->curr_timeout_sec_ = 0; - this->curr_timeout_usec_ = 1; - if (::modbus_set_response_timeout(this->ctx_.get(), this->curr_timeout_sec_, this->curr_timeout_usec_) < 0) { - std::string msg(this->TAG()+" set timeout failed!"+std::string(::modbus_strerror(errno))); + if (::modbus_set_response_timeout(ctx, sec, usec) < 0) { + std::string msg(TAG()+" set timeout failed! "+std::string(::modbus_strerror(errno))); log::warn(msg.c_str()); - return ::maix::err::Err::ERR_RUNTIME; - // __error_and_throw__(msg); + return 1; } - return ::maix::err::Err::ERR_NONE; + return 0; + }; + + if (timeout_ms < 0) { + return __set_timeout__(std::numeric_limits::max(), 0, ""); } - // blocking - if (usec > 999999 || sec == std::numeric_limits::max()) { - if (this->curr_timeout_sec_ == std::numeric_limits::max() && this->curr_timeout_usec_ == 999999) - return ::maix::err::Err::ERR_NONE; - if (this->debug_) { - log::info("%s Timeout: max", this->TAG().c_str()); - } - this->curr_timeout_sec_ = std::numeric_limits::max(); - this->curr_timeout_usec_ = 999999; - if (::modbus_set_response_timeout(this->ctx_.get(), this->curr_timeout_sec_, this->curr_timeout_usec_) < 0) { - std::string msg(this->TAG()+" set timeout failed!"+std::string(::modbus_strerror(errno))); - log::warn(msg.c_str()); - return ::maix::err::Err::ERR_RUNTIME; - // __error_and_throw__(msg); - } - return ::maix::err::Err::ERR_NONE; + if (timeout_ms == 0) { + return __set_timeout__(0, 1, "<0>"); } - if (this->curr_timeout_sec_ == sec && this->curr_timeout_usec_ == usec) { - return ::maix::err::Err::ERR_NONE; + int sec = timeout_ms / 1000; + int usec = timeout_ms % 1000 * 1000; + std::stringstream ss; + if (debug_) + ss << '<' << sec << 's' << usec << "us>"; + + return __set_timeout__(sec, usec, ss.str()); +} + +std::unique_ptr<::modbus_t, decltype(&MasterOperator::deinit)> MasterOperator::rtu_init(const std::string& device, int baud, int slave) noexcept +{ + if (debug_) { + log::info("%s Mode: RTU, Port: %s, Baudrate: %d-8N1, Slave addr: %u.", + TAG().c_str(), device.c_str(), baud, slave); } - if (this->debug_) { - log::info("%s Timeout: %u sec %u usec", - this->TAG().c_str(), sec, usec); + + auto ctx = std::unique_ptr<::modbus_t, decltype(&MasterOperator::deinit)>( + ::modbus_new_rtu(device.c_str(), baud, 'N', 8, 1), + &MasterOperator::deinit + ); + if (!ctx) { + const std::string msg(TAG()+" malloc failed!"); + log::error(msg.c_str()); + return empty_ctx(); } - this->curr_timeout_sec_ = sec; - this->curr_timeout_usec_ = usec; - if (::modbus_set_response_timeout(this->ctx_.get(), this->curr_timeout_sec_, this->curr_timeout_usec_) < 0) { - std::string msg(this->TAG()+" set timeout failed!"+std::string(::modbus_strerror(errno))); - log::warn(msg.c_str()); - return ::maix::err::Err::ERR_RUNTIME; - // __error_and_throw__(msg); + + if (::modbus_set_slave(ctx.get(), slave) < 0) { + const std::string msg(TAG()+" set slave failed!"); + log::error(msg.c_str()); + return empty_ctx(); } - return ::maix::err::Err::ERR_NONE; -} -Master::Master(::maix::comm::modbus::Mode mode, const std::string& ip_or_device, - int rtu_baud, uint8_t rtu_slave, int tcp_port, bool debug) : debug_(debug) -{ - if (mode == Mode::RTU) - this->rtu_init(ip_or_device, rtu_baud, rtu_slave); - else if (mode == Mode::TCP) - this->tcp_init(ip_or_device, tcp_port); - else - __error_and_throw__(this->TAG()+" unknown Mode!"); + if (debug_init(ctx.get()) < 0) { + return empty_ctx(); + } - if (this->set_timeout( - std::numeric_limits::max(), - std::numeric_limits::max() - ) != ::maix::err::Err::ERR_NONE) { - const std::string errmsg(this->TAG()+" Set timeout failed"); - log::error(errmsg.c_str()); - throw std::runtime_error(errmsg); + if (::modbus_connect(ctx.get()) < 0) { + const std::string msg(TAG()+" connect failed!"+std::string(::modbus_strerror(errno))); + log::error(msg.c_str()); + return empty_ctx(); } -} -Master::Master(Master&& other) noexcept - : ctx_(std::move(other.ctx_)), debug_(other.debug_), - curr_timeout_sec_(other.curr_timeout_sec_), - curr_timeout_usec_(other.curr_timeout_usec_) -{ - other.debug_ = false; - other.curr_timeout_sec_ = 165; - other.curr_timeout_usec_ = 528; + return ctx; } -Master& Master::operator=(Master&& other) noexcept +std::unique_ptr<::modbus_t, decltype(&MasterOperator::deinit)> MasterOperator::tcp_init(const std::string& ip, int port) noexcept { - this->ctx_ = std::move(other.ctx_); - this->debug_ = other.debug_; - this->curr_timeout_sec_ = other.curr_timeout_sec_; - this->curr_timeout_usec_ = other.curr_timeout_usec_; + if (debug_) { + log::info("%s Mode: TCP, Port: %d", + TAG().c_str(), port); + } - other.debug_ = false; - other.curr_timeout_sec_ = 165; - other.curr_timeout_usec_ = 528; + auto ctx = std::unique_ptr<::modbus_t, decltype(&MasterOperator::deinit)>( + ::modbus_new_tcp(ip.c_str(), port), + &MasterOperator::deinit + ); + // auto ctx = std::make_unique<::modbus_t, decltype(&MasterOperator::deinit)>(::modbus_new_tcp(ip.c_str(), port), &deinit); + if (!ctx) { + const std::string msg(TAG()+" malloc failed!"); + log::error(msg.c_str()); + return empty_ctx(); + } - return *this; + if (debug_init(ctx.get()) < 0) { + return empty_ctx(); + } + + if (::modbus_connect(ctx.get()) < 0) { + const std::string msg(TAG()+" connect failed!"+std::string(::modbus_strerror(errno))); + log::error(msg.c_str()); + return empty_ctx(); + } + + return ctx; } -Master::~Master() +void MasterOperator::deinit(::modbus_t* ptr) noexcept { - if (this->ctx_ != nullptr) - ::modbus_close(this->ctx_.get()); + if (ptr != nullptr) { + ::modbus_close(ptr); + ::modbus_free(ptr); + } } template -std::vector Master::read(const uint32_t size, const uint32_t index, const int timeout_ms, - const std::string& name, std::function func) +std::vector MasterOperator::read(::modbus_t* ctx, const uint32_t size, const uint32_t index, + const int timeout_ms, const std::string& name, ModbusOpsRFunc func) { if (size == 0) { - std::string msg(this->TAG()+" read length cannot be zero!"); + std::string msg(TAG()+" read length cannot be zero!"); __throw_in_maixpy__(msg); return {}; } - if (timeout_ms == 0) { - this->set_timeout(0, 0); - }else if (timeout_ms < 0) { - this->set_timeout(std::numeric_limits::max(), std::numeric_limits::max()); - } else { - uint32_t sec = timeout_ms / 1000; - uint32_t usec = timeout_ms % 1000 * 1000; - this->set_timeout(sec, usec); - } + set_timeout(ctx, timeout_ms); + + if (debug_) log::info("%s read %s: index<%u>, len<%u>", TAG().c_str(), name.c_str(), index, size); - if (this->debug_) log::info("%s read %s: index<%u>, len<%u>", this->TAG().c_str(), name.c_str(), index, size); std::vector __res__(size); int rc = func( - this->ctx_.get(), + ctx, static_cast(index), static_cast(size), __res__.data() ); if (rc <= 0) { - if (this->debug_) log::warn("%s read %s failed!", this->TAG().c_str(), name.c_str()); + if (debug_) log::warn("%s read %s failed!", TAG().c_str(), name.c_str()); return {}; } @@ -690,79 +688,241 @@ std::vector Master::read(const uint32_t size, const uint32_t index, const int } template -int Master::write(const std::vector& data, const uint32_t index, const int timeout_ms, - const std::string& name, std::function func) +int MasterOperator::write(::modbus_t* ctx, const std::vector& data, const uint32_t index, const int timeout_ms, + const std::string& name, ModbusOpsWFunc func) { if (data.empty()) { - std::string msg(this->TAG()+" read length cannot be zero!"); + std::string msg(TAG()+" write length cannot be zero!"); __throw_in_maixpy__(msg); return -1; } - if (timeout_ms == 0) { - this->set_timeout(0, 0); - }else if (timeout_ms < 0) { - this->set_timeout(std::numeric_limits::max(), std::numeric_limits::max()); - } else { - uint32_t sec = timeout_ms / 1000; - uint32_t usec = timeout_ms % 1000 * 1000; - this->set_timeout(sec, usec); - } + set_timeout(ctx, timeout_ms); - if (this->debug_) log::info("%s write %s: index<%u>, len<%llu>", this->TAG().c_str(), name.c_str(), index, data.size()); + if (debug_) log::info("%s write %s: index<%u>, len<%u>", TAG().c_str(), name.c_str(), index, data.size()); int rc = func( - this->ctx_.get(), + ctx, static_cast(index), static_cast(data.size()), data.data() ); if (rc <= 0) { - if (this->debug_) log::warn("%s write %s failed!", this->TAG().c_str(), name.c_str()); + if (debug_) log::warn("%s write %s failed!", TAG().c_str(), name.c_str()); return -1; } return rc; } -std::vector Master::read_coils(const uint32_t size, const uint32_t index, const int timeout_ms) +/**************************************Master DEBUG**************************************/ + +void set_master_debug(bool debug) { - return this->read(size, index, timeout_ms, "coils", ::modbus_read_bits); + MasterOperator::debug(debug); } -std::vector Master::read_discrete_input(const uint32_t size, const uint32_t index, const int timeout_ms) +/**************************************Master RTU**************************************/ + +MasterRTU::MasterRTU(const std::string& device, const int baudrate) + : device_(device), baudrate_(baudrate) {} + +MasterRTU::~MasterRTU() {} + +std::pair MasterRTU::get_cfg(const std::string& device, const int baudrate) noexcept { - return this->read(size, index, timeout_ms, "discrete input", ::modbus_read_input_bits); + std::string dev = device; + int baud = baudrate; + + if (device.empty()) { + dev = this->device_; + } + if (baudrate <= 0) { + baud = this->baudrate_; + } + + return std::make_pair(dev, baud); } -std::vector Master::read_input_registers(const uint32_t size, const uint32_t index, const int timeout_ms) +std::vector MasterRTU::read_coils(const uint32_t slave_id, const uint32_t size, + const uint32_t addr, const int timeout_ms, const std::string& device, const int baudrate) { - return this->read(size, index, timeout_ms, "input registers", ::modbus_read_input_registers); + auto [dev, baud] = this->get_cfg(device, baudrate); + return read_coils(dev, baud, slave_id, size, addr, timeout_ms); } -std::vector Master::read_holding_registers(const uint32_t size, const uint32_t index, const int timeout_ms) +int MasterRTU::write_coils(const uint32_t slave_id, const std::vector& data, + const uint32_t addr, const int timeout_ms, const std::string& device, const int baudrate) { - return this->read(size, index, timeout_ms, "holding registers", ::modbus_read_registers); + auto [dev, baud] = this->get_cfg(device, baudrate); + return write_coils(dev, baud, slave_id, data, addr, timeout_ms); } -int Master::write_coils(const std::vector& data, const uint32_t index, const int timeout_ms) +std::vector MasterRTU::read_discrete_input(const uint32_t slave_id, const uint32_t size, + const uint32_t addr, const int timeout_ms, const std::string& device, const int baudrate) { - return this->write(data, index, timeout_ms, "coils", ::modbus_write_bits); + auto [dev, baud] = this->get_cfg(device, baudrate); + return read_discrete_input(dev, baud, slave_id, size, addr, timeout_ms); } -int Master::write_holding_registers(const std::vector& data, const uint32_t index, const int timeout_ms) +std::vector MasterRTU::read_input_registers(const uint32_t slave_id, const uint32_t size, + const uint32_t addr, const int timeout_ms, const std::string& device, const int baudrate) { - return this->write(data, index, timeout_ms, "holding registers", ::modbus_write_registers); + auto [dev, baud] = this->get_cfg(device, baudrate); + return read_input_registers(dev, baud, slave_id, size, addr, timeout_ms); } -::modbus_t* Master::context() const noexcept +std::vector MasterRTU::read_holding_registers(const uint32_t slave_id, const uint32_t size, + const uint32_t addr, const int timeout_ms, const std::string& device, const int baudrate) { - return this->ctx_.get(); + auto [dev, baud] = this->get_cfg(device, baudrate); + return read_holding_registers(dev, baud, slave_id, size, addr, timeout_ms); +} + +int MasterRTU::write_holding_registers(const uint32_t slave_id, const std::vector& data, + const uint32_t addr, const int timeout_ms, const std::string& device, const int baudrate) +{ + auto [dev, baud] = this->get_cfg(device, baudrate); + return write_holding_registers(dev, baud, slave_id, data, addr, timeout_ms); +} + +std::vector MasterRTU::read_coils( const std::string& device, const int baudrate, + const uint32_t slave_id, const uint32_t size, const uint32_t addr, const int timeout_ms) +{ + auto ctx = MasterOperator::rtu_init(device, baudrate, slave_id); + return MasterOperator::read(ctx.get(), size, addr, timeout_ms, "coils", ::modbus_read_bits); +} + +int MasterRTU::write_coils(const std::string& device, const int baudrate, + const uint32_t slave_id, const std::vector& data, const uint32_t addr, const int timeout_ms) +{ + auto ctx = MasterOperator::rtu_init(device, baudrate, slave_id); + return MasterOperator::write(ctx.get(), data, addr, timeout_ms, "coils", ::modbus_write_bits); +} + +std::vector MasterRTU::read_discrete_input(const std::string& device, const int baudrate, + const uint32_t slave_id, const uint32_t size, const uint32_t addr, const int timeout_ms) +{ + auto ctx = MasterOperator::rtu_init(device, baudrate, slave_id); + return MasterOperator::read(ctx.get(), size, addr, timeout_ms, "discrete input", ::modbus_read_input_bits); +} + +std::vector MasterRTU::read_input_registers(const std::string& device, const int baudrate, + const uint32_t slave_id, const uint32_t size, const uint32_t addr, const int timeout_ms) +{ + auto ctx = MasterOperator::rtu_init(device, baudrate, slave_id); + return MasterOperator::read(ctx.get(), size, addr, timeout_ms, "input registers", ::modbus_read_input_registers); +} + +std::vector MasterRTU::read_holding_registers(const std::string& device, const int baudrate, + const uint32_t slave_id, const uint32_t size, const uint32_t addr, const int timeout_ms) +{ + auto ctx = MasterOperator::rtu_init(device, baudrate, slave_id); + return MasterOperator::read(ctx.get(), size, addr, timeout_ms, "holding registers", ::modbus_read_registers); +} + +int MasterRTU::write_holding_registers(const std::string& device, const int baudrate, + const uint32_t slave_id, const std::vector& data, const uint32_t addr, const int timeout_ms) +{ + auto ctx = MasterOperator::rtu_init(device, baudrate, slave_id); + return MasterOperator::write(ctx.get(), data, addr, timeout_ms, "holding registers", ::modbus_write_registers); +} + +/**************************************Master TCP**************************************/ + +MasterTCP::MasterTCP(const int port) : port_(port) {} + +MasterTCP::~MasterTCP() {} + +int MasterTCP::get_cfg(int port) noexcept +{ + if (port < 0) + return this->port_; + return port; +} + +std::vector MasterTCP::read_coils(const std::string ip, const uint32_t size, + const uint32_t addr, const int timeout_ms, const int port) +{ + auto p = this->get_cfg(port); + return read_coils(ip, p, size, addr, timeout_ms); +} + +int MasterTCP::write_coils(const std::string ip, const std::vector& data, + const uint32_t addr, const int timeout_ms, const int port) +{ + auto p = this->get_cfg(port); + return write_coils(ip, p, data, addr, timeout_ms); +} + +std::vector MasterTCP::read_discrete_input(const std::string ip, const uint32_t size, + const uint32_t addr, const int timeout_ms, const int port) +{ + auto p = this->get_cfg(port); + return read_discrete_input(ip, p, size, addr, timeout_ms); +} + +std::vector MasterTCP::read_input_registers(const std::string ip, const uint32_t size, + const uint32_t addr, const int timeout_ms, const int port) +{ + auto p = this->get_cfg(port); + return read_input_registers(ip, p, size, addr, timeout_ms); +} + +std::vector MasterTCP::read_holding_registers(const std::string ip, const uint32_t size, + const uint32_t addr, const int timeout_ms, const int port) +{ + auto p = this->get_cfg(port); + return read_holding_registers(ip, p, size, addr, timeout_ms); +} + +int MasterTCP::write_holding_registers(const std::string ip, const std::vector& data, + const uint32_t addr, const int timeout_ms, const int port) +{ + auto p = this->get_cfg(port); + return write_holding_registers(ip, p, data, addr, timeout_ms); +} + +std::vector MasterTCP::read_coils( const std::string ip, const int port, + const uint32_t size, const uint32_t addr, const int timeout_ms) +{ + auto ctx = MasterOperator::tcp_init(ip, port); + return MasterOperator::read(ctx.get(), size, addr, timeout_ms, "coils", ::modbus_read_bits); +} + +int MasterTCP::write_coils(const std::string ip, const int port, + const std::vector& data, const uint32_t addr, const int timeout_ms) +{ + auto ctx = MasterOperator::tcp_init(ip, port); + return MasterOperator::write(ctx.get(), data, addr, timeout_ms, "coils", ::modbus_write_bits); +} + +std::vector MasterTCP::read_discrete_input(const std::string ip, const int port, + const uint32_t size, const uint32_t addr, const int timeout_ms) +{ + auto ctx = MasterOperator::tcp_init(ip, port); + return MasterOperator::read(ctx.get(), size, addr, timeout_ms, "discrete input", ::modbus_read_input_bits); +} + +std::vector MasterTCP::read_input_registers(const std::string ip, const int port, + const uint32_t size, const uint32_t addr, const int timeout_ms) +{ + auto ctx = MasterOperator::tcp_init(ip, port); + return MasterOperator::read(ctx.get(), size, addr, timeout_ms, "input registers", ::modbus_read_registers); +} + +std::vector MasterTCP::read_holding_registers(const std::string ip, const int port, + const uint32_t size, const uint32_t addr, const int timeout_ms) +{ + auto ctx = MasterOperator::tcp_init(ip, port); + return MasterOperator::read(ctx.get(), size, addr, timeout_ms, "holding registers", ::modbus_read_registers); } -::modbus_t* Master::operator()() const noexcept +int MasterTCP::write_holding_registers(const std::string ip, const int port, + const std::vector& data, const uint32_t addr, const int timeout_ms) { - return this->context(); + auto ctx = MasterOperator::tcp_init(ip, port); + return MasterOperator::write(ctx.get(), data, addr, timeout_ms, "holding registers", ::modbus_write_registers); } } \ No newline at end of file diff --git a/examples/maix_modbus_master/.gitignore b/examples/maix_modbus_loopback/.gitignore similarity index 100% rename from examples/maix_modbus_master/.gitignore rename to examples/maix_modbus_loopback/.gitignore diff --git a/examples/maix_modbus_master/README.md b/examples/maix_modbus_loopback/README.md similarity index 71% rename from examples/maix_modbus_master/README.md rename to examples/maix_modbus_loopback/README.md index c3aac7b4..2883eb78 100644 --- a/examples/maix_modbus_master/README.md +++ b/examples/maix_modbus_loopback/README.md @@ -1,4 +1,4 @@ -maix_modbus_master Project based on MaixCDK +maix_modbus_loopback Project based on MaixCDK ==== diff --git a/examples/maix_modbus_master/app.yaml b/examples/maix_modbus_loopback/app.yaml similarity index 66% rename from examples/maix_modbus_master/app.yaml rename to examples/maix_modbus_loopback/app.yaml index efc1f454..f7124696 100644 --- a/examples/maix_modbus_master/app.yaml +++ b/examples/maix_modbus_loopback/app.yaml @@ -1,5 +1,5 @@ -id: maix_modbus_master -name: maix_modbus_master +id: maix_modbus_loopback +name: maix_modbus_loopback name[zh]: version: 1.0.0 #icon: assets/hello.png diff --git a/examples/maix_modbus_master/main/CMakeLists.txt b/examples/maix_modbus_loopback/main/CMakeLists.txt similarity index 100% rename from examples/maix_modbus_master/main/CMakeLists.txt rename to examples/maix_modbus_loopback/main/CMakeLists.txt diff --git a/examples/maix_modbus_master/main/Kconfig b/examples/maix_modbus_loopback/main/Kconfig similarity index 100% rename from examples/maix_modbus_master/main/Kconfig rename to examples/maix_modbus_loopback/main/Kconfig diff --git a/examples/maix_modbus_master/main/include/main.h b/examples/maix_modbus_loopback/main/include/main.h similarity index 100% rename from examples/maix_modbus_master/main/include/main.h rename to examples/maix_modbus_loopback/main/include/main.h diff --git a/examples/maix_modbus_loopback/main/src/main.cpp b/examples/maix_modbus_loopback/main/src/main.cpp new file mode 100644 index 00000000..f58458e0 --- /dev/null +++ b/examples/maix_modbus_loopback/main/src/main.cpp @@ -0,0 +1,222 @@ + +#include "maix_basic.hpp" +#include "main.h" +#include "maix_modbus.hpp" +#include "maix_pinmap.hpp" +#include // std::setw and std::setfill +#include // std::stringstream +#include // std::thread + +using namespace maix; +using namespace maix::comm; + +/** MODE: RTU/TCP + * NOTE: RTU UART0(Slave) <--> UART1(Master) + */ +constexpr modbus::Mode MODE = modbus::Mode::RTU; +// constexpr modbus::Mode MODE = modbus::Mode::TCP; + +/* slave cfg */ +constexpr uint8_t REGISTERS_START_ADDRESS = 0x00; +constexpr uint32_t REGISTERS_NUMBER = 10; + +/* rtu cfg */ +constexpr uint32_t RTU_SLAVE_ID = 1; +constexpr int RTU_BAUDRATE = 115200; + +/* tcp cfg */ +constexpr int TCP_PORT = 5020; + +int master_rtu_thread() +{ + if(peripheral::pinmap::set_pin_function("A19", "UART1_TX") != err::Err::ERR_NONE) { + log::error("init uart1 failed!"); + return -1; + } + if (peripheral::pinmap::set_pin_function("A18", "UART1_RX") != err::Err::ERR_NONE) { + log::error("init uart failed!"); + return -1; + } + + // modbus::set_master_debug(true); + + modbus::MasterRTU master("/dev/ttyS1", RTU_BAUDRATE); + + while (!app::need_exit()) { + // log::info("master thread running..."); + + std::vector rt = master.read_holding_registers(RTU_SLAVE_ID, REGISTERS_NUMBER, REGISTERS_START_ADDRESS, 2000); + if (rt.empty()) + continue; + + std::stringstream ss; + ss << "Master read: "; + for (const auto& value : rt) { + ss << "0x" << std::hex << std::setw(4) << std::setfill('0') << value << " "; + } + log::info(ss.str().c_str()); + time::sleep(1); + } + + return 0; +} + +int slave_rtu_thread() +{ + modbus::Registers cfg; + cfg.coils.start_address = REGISTERS_START_ADDRESS; + cfg.coils.size = REGISTERS_NUMBER; + cfg.discrete_inputs.start_address = REGISTERS_START_ADDRESS; + cfg.discrete_inputs.size = REGISTERS_NUMBER; + cfg.holding_registers.start_address = REGISTERS_START_ADDRESS; + cfg.holding_registers.size = REGISTERS_NUMBER; + cfg.input_registers.start_address = REGISTERS_START_ADDRESS; + cfg.input_registers.size = REGISTERS_NUMBER; + + modbus::Slave slave( + modbus::Mode::RTU, + "/dev/ttyS0", + cfg, + RTU_BAUDRATE, + RTU_SLAVE_ID + ); + + while (!app::need_exit()) { + // log::info("slave thread running..."); + + auto rt = slave.receive(2000); + if (rt != err::Err::ERR_NONE) + continue; + + modbus::RequestType type = slave.request_type(); + if (type == modbus::RequestType::READ_HOLDING_REGISTERS) { + std::stringstream ss; + ss << "Slave update: "; + for (int i = 0; i < slave->nb_registers; ++i) { + slave->tab_registers[i]++; + ss << "0x" << std::hex << std::setw(4) << std::setfill('0') << slave->tab_registers[i] << " "; + } + log::info(ss.str().c_str()); + } + + slave.reply(); + } + + return 0; +} + +void rtu_loopback() +{ + std::thread slave_th(slave_rtu_thread); + time::sleep_ms(500); + std::thread master_th(master_rtu_thread); + + slave_th.join(); + master_th.join(); +} + +int slave_tcp_thread() +{ + modbus::Registers cfg; + cfg.coils.start_address = REGISTERS_START_ADDRESS; + cfg.coils.size = REGISTERS_NUMBER; + cfg.discrete_inputs.start_address = REGISTERS_START_ADDRESS; + cfg.discrete_inputs.size = REGISTERS_NUMBER; + cfg.holding_registers.start_address = REGISTERS_START_ADDRESS; + cfg.holding_registers.size = REGISTERS_NUMBER; + cfg.input_registers.start_address = REGISTERS_START_ADDRESS; + cfg.input_registers.size = REGISTERS_NUMBER; + + modbus::Slave slave( + modbus::Mode::TCP, + "", + cfg, + 0, + 0, + TCP_PORT + ); + + while (!app::need_exit()) { + // log::info("slave thread running..."); + + auto rt = slave.receive(2000); + if (rt != err::Err::ERR_NONE) + continue; + + modbus::RequestType type = slave.request_type(); + if (type == modbus::RequestType::READ_HOLDING_REGISTERS) { + std::stringstream ss; + ss << "Slave update: "; + for (int i = 0; i < slave->nb_registers; ++i) { + slave->tab_registers[i]++; + ss << "0x" << std::hex << std::setw(4) << std::setfill('0') << slave->tab_registers[i] << " "; + } + log::info(ss.str().c_str()); + } + + slave.reply(); + } + + return 0; +} + +int master_tcp_thread() +{ + // modbus::set_master_debug(true); + + modbus::MasterTCP master(TCP_PORT); + + while (!app::need_exit()) { + // log::info("master thread running..."); + + std::vector rt = master.read_holding_registers("127.0.0.1", REGISTERS_NUMBER, REGISTERS_START_ADDRESS, 2000); + if (rt.empty()) + continue; + + std::stringstream ss; + ss << "Master read: "; + for (const auto& value : rt) { + ss << "0x" << std::hex << std::setw(4) << std::setfill('0') << value << " "; + } + log::info(ss.str().c_str()); + time::sleep(1); + } + + return 0; +} + + +void tcp_loopback() +{ + std::thread slave_th(slave_tcp_thread); + time::sleep_ms(500); + std::thread master_th(master_tcp_thread); + + slave_th.join(); + master_th.join(); +} + +int _main(int argc, char* argv[]) +{ + + if constexpr (MODE == modbus::Mode::RTU) { + rtu_loopback(); + } else { + tcp_loopback(); + } + + return 0; +} + +int main(int argc, char* argv[]) +{ + // Catch signal and process + sys::register_default_signal_handle(); + + // Use CATCH_EXCEPTION_RUN_RETURN to catch exception, + // if we don't catch exception, when program throw exception, the objects will not be destructed. + // So we catch exception here to let resources be released(call objects' destructor) before exit. + CATCH_EXCEPTION_RUN_RETURN(_main, -1, argc, argv); +} + + diff --git a/examples/maix_modbus_master/main/src/main.cpp b/examples/maix_modbus_master/main/src/main.cpp deleted file mode 100644 index 750a006d..00000000 --- a/examples/maix_modbus_master/main/src/main.cpp +++ /dev/null @@ -1,63 +0,0 @@ - -#include "maix_basic.hpp" -#include "main.h" -#include "maix_modbus.hpp" -#include "maix_pinmap.hpp" - -using namespace maix; -using namespace maix::comm; - -int _main(int argc, char* argv[]) -{ - - if(peripheral::pinmap::set_pin_function("A19", "UART1_TX") != err::Err::ERR_NONE) { - log::error("init uart1 failed!"); - return -1; - } - if (peripheral::pinmap::set_pin_function("A18", "UART1_RX") != err::Err::ERR_NONE) { - log::error("init uart failed!"); - return -1; - } - - auto master = modbus::Master( - modbus::Mode::RTU, - "/dev/ttyS1", - 115200, 1, - 0, - false - ); - - // auto master = modbus::Master( - // modbus::Mode::TCP, - // "192.168.1.168", // slave ip - // 0, 0, - // 5020, // slave port - // false - // ); - - while (!app::need_exit()) { - auto hr = master.read_holding_registers(10, 0, 1000); - if (hr.empty()) - continue; - log::info0("Master read hr: "); - for (const auto i : hr) { - printf("0x%04x ", i); - }printf("\n"); - time::sleep(1); - } - - return 0; -} - -int main(int argc, char* argv[]) -{ - // Catch signal and process - sys::register_default_signal_handle(); - - // Use CATCH_EXCEPTION_RUN_RETURN to catch exception, - // if we don't catch exception, when program throw exception, the objects will not be destructed. - // So we catch exception here to let resources be released(call objects' destructor) before exit. - CATCH_EXCEPTION_RUN_RETURN(_main, -1, argc, argv); -} - -