diff --git a/components/esp_modem/docs/advanced_api.rst b/components/esp_modem/docs/advanced_api.rst index 965f59cd7..8fc939b15 100644 --- a/components/esp_modem/docs/advanced_api.rst +++ b/components/esp_modem/docs/advanced_api.rst @@ -19,13 +19,14 @@ All the functionality is provided by the DCE factory .. doxygengroup:: ESP_MODEM_DCE_FACTORY :members: +.. _create_custom_module: Create custom module -------------------- Creating a custom module is necessary if the application needs to use a specific device that is not supported and their commands differ from any of the supported devices. In this case it is recommended to define a new class -representing this specific device and derive from the :cpp:class:`GenericModule`. In order to instantiate +representing this specific device and derive from the :cpp:class:`esp_modem::GenericModule`. In order to instantiate the appropriate DCE of this module, application could use :ref:`the DCE factory`, and build the DCE with the specific module, using :cpp:func:`esp_modem::dce_factory::Factory::build`. diff --git a/components/esp_modem/docs/internal_docs.rst b/components/esp_modem/docs/internal_docs.rst index c1c64f679..1416edcc3 100644 --- a/components/esp_modem/docs/internal_docs.rst +++ b/components/esp_modem/docs/internal_docs.rst @@ -9,6 +9,13 @@ The esp-modem actually implements the DCE class, which in turn aggregates these - :ref:`Netif` to provide the network connectivity - :ref:`Module` to define the specific command library +Developers would typically have to + +* Add support for a new module +* Implement a generic (common for all modules) AT command + +This is explained in the :ref:`Module` section, as :ref:`Adding new module or command` + ------------ .. doxygengroup:: ESP_MODEM_DCE @@ -63,6 +70,36 @@ Module abstraction .. doxygengroup:: ESP_MODEM_MODULE :members: +.. _module_addition: + +Adding new devices +^^^^^^^^^^^^^^^^^^ + +To support a new module, developers would have to implement a new class derived from :cpp:class:`esp_modem::GenericModule` the same way +as it is described in the :ref:`Advanced user manual`. The only difference is that the new class (and factory extension) +would be available in the esp_modem code base. + +Implement a new generic command +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Adding a generic command, i.e. the command that is shared for all modules and is included in the :cpp:class:`esp_modem::GenericModule`, +has to be declared first in the ``include/generate/esp_modem_command_declare.inc`` file, which is the single source +of supported command definitions, that is used in: + +* public C API +* public CPP API +* generated documentation +* implementation of the command + +Therefore, a care must be taken, to correctly specify all parameters and types, especially: + +* Keep number of parameters low (<= 6, used in preprocessor's forwarding to the command library) +* Use macros to specify parameter types (as they are used both from C and C++ with different underlying types) +* Parameter names are used only for clarity and documentation, they get expanded to numbered arguments. + +Please use the following pattern: ``INT_IN(p1, baud)``, meaning that the parameter is an input integer, +human readable argument name is ``baud``, it's the first argument, so expands to ``p1`` (second argument would be ``p2``, etc) + Command library ^^^^^^^^^^^^^^^ diff --git a/components/esp_modem/include/cxx_include/esp_modem_dce.hpp b/components/esp_modem/include/cxx_include/esp_modem_dce.hpp index 70452bf03..698045704 100644 --- a/components/esp_modem/include/cxx_include/esp_modem_dce.hpp +++ b/components/esp_modem/include/cxx_include/esp_modem_dce.hpp @@ -85,7 +85,7 @@ protected: /** * @brief Common abstraction of the modem DCE, specialized by the GenericModule which is a parent class for the supported - * defices and most common modems, as well. + * devices and most common modems, as well. */ class DCE : public DCE_T { public: diff --git a/components/esp_modem/include/cxx_include/esp_modem_dce_factory.hpp b/components/esp_modem/include/cxx_include/esp_modem_dce_factory.hpp index d3d6ddca4..57fa96926 100644 --- a/components/esp_modem/include/cxx_include/esp_modem_dce_factory.hpp +++ b/components/esp_modem/include/cxx_include/esp_modem_dce_factory.hpp @@ -13,6 +13,7 @@ // limitations under the License. #pragma once +#include "esp_log.h" /** * @defgroup ESP_MODEM_DCE_FACTORY @@ -30,62 +31,67 @@ using config = ::esp_modem_dce_config; /** - * @brief Helper class for creating a uder define pointer in a specific way, either as a plain pointer, shared_ptr or unique_ptr + * @brief Helper class for creating a user define pointer in a specific way, either as a plain pointer, shared_ptr or unique_ptr */ class FactoryHelper { public: static std::unique_ptr create_pdp_context(std::string &apn); - template - static auto make(Args&&... args) -> typename std::enable_if::value, T*>::type + template + static auto make(Args&&... args) -> typename std::enable_if::value, T*>::type { return new T(std::forward(args)...); } - template - static auto make(Args&&... args) -> typename std::enable_if>::value, std::shared_ptr>::type + template + static auto make(Args&&... args) -> typename std::enable_if>::value, std::shared_ptr>::type { return std::make_shared(std::forward(args)...); } - template , typename ...Args> - static auto make(Args&&... args) -> typename std::enable_if>::value, std::unique_ptr>::type + template , typename ...Args> + static auto make(Args&&... args) -> typename std::enable_if>::value, std::unique_ptr>::type { return std::unique_ptr(new T(std::forward(args)...)); } - }; /** - * @brief Builder class for building a DCE_T in a specific way, either form a Module object or by default from the DTE and netif + * @brief Builder class for building a DCE_T in a specific way, either from a Module object or by default from the DTE and netif + * + * @throws + * - esp_modem::esp_err_exception on invalid arguments + * - std::bad_alloc if failed to allocate */ -template +template class Builder { - static_assert(std::is_base_of::value, "Builder must be used only for Module classes"); + static_assert(std::is_base_of::value, "Builder must be used only for Module classes"); public: - Builder(std::shared_ptr x, esp_netif_t* esp_netif): dte(std::move(x)), device(nullptr), netif(esp_netif) + Builder(std::shared_ptr dte, esp_netif_t* esp_netif): dte(std::move(dte)), device(nullptr), netif(esp_netif) { throw_if_false(netif != nullptr, "Null netif"); } - Builder(std::shared_ptr dte, esp_netif_t* esp_netif, std::shared_ptr dev): dte(std::move(dte)), device(std::move(dev)), netif(esp_netif) + Builder(std::shared_ptr dte, esp_netif_t* esp_netif, std::shared_ptr dev): dte(std::move(dte)), device(std::move(dev)), netif(esp_netif) { throw_if_false(netif != nullptr, "Null netif"); } ~Builder() { - throw_if_false(device == nullptr, "module was captured or created but never used"); + if (device == nullptr) { + ESP_LOGE("dce_factory::~Builder", "module was captured or created but never used"); + } } - template - auto create_module(const esp_modem_dce_config *config) -> Ptr + template + auto create_module(const esp_modem_dce_config *config) -> T_Ptr { - return FactoryHelper::make(dte, config); + return FactoryHelper::make(dte, config); } - template - auto create(const esp_modem_dce_config *config) -> Ptr + template + auto create(const esp_modem_dce_config *config) -> T_Ptr { if (dte == nullptr) return nullptr; @@ -94,19 +100,19 @@ public: if (device == nullptr) return nullptr; } - return FactoryHelper::make(std::move(dte), std::move(device), netif); + return FactoryHelper::make(std::move(dte), std::move(device), netif); } private: std::shared_ptr dte; - std::shared_ptr device; + std::shared_ptr device; esp_netif_t *netif; }; /** * @brief Specific modem choice when creating by the Factory */ -enum class Modem { +enum class ModemType { GenericModule, /*!< Default generic module with the most common commands */ SIM7600, /*!< Derived from the GenericModule, specifics applied to SIM7600 model */ BG96, /*!< Derived from the GenericModule, specifics applied to BG69 model */ @@ -119,7 +125,7 @@ enum class Modem { */ class Factory { public: - explicit Factory(Modem modem): m(modem) {} + explicit Factory(ModemType modem): m(modem) {} /** * @brief Create a default unique_ptr DCE in a specific way (from the module) @@ -129,10 +135,10 @@ public: * @param args typically a DTE object and a netif handle for PPP network * @return unique_ptr DCE of the created DCE on success */ - template + template static std::unique_ptr build_unique(const config *cfg, Args&&... args) { - return build_generic_DCE>(cfg, std::forward(args)...); + return build_generic_DCE>(cfg, std::forward(args)...); } /** @@ -143,17 +149,17 @@ public: * @param args typically a DTE object and a netif handle for PPP network * @return DCE pointer the created DCE on success */ - template + template static DCE* build(const config *cfg, Args&&... args) { - return build_generic_DCE(cfg, std::forward(args)...); + return build_generic_DCE(cfg, std::forward(args)...); } - template - static std::shared_ptr build_shared_module(const config *cfg, Args&&... args) + template + static std::shared_ptr build_shared_module(const config *cfg, Args&&... args) { - return build_module_T(cfg, std::forward(args)...); + return build_module_T(cfg, std::forward(args)...); } @@ -161,13 +167,13 @@ public: std::shared_ptr build_shared_module(const config *cfg, Args&&... args) { switch (m) { - case Modem::SIM800: + case ModemType::SIM800: return build_shared_module(cfg, std::forward(args)...); - case Modem::SIM7600: + case ModemType::SIM7600: return build_shared_module(cfg, std::forward(args)...); - case Modem::BG96: + case ModemType::BG96: return build_shared_module(cfg, std::forward(args)...); - case Modem::GenericModule: + case ModemType::GenericModule: return build_shared_module(cfg, std::forward(args)...); default: break; @@ -186,13 +192,13 @@ public: std::unique_ptr build_unique(const config *cfg, Args&&... args) { switch (m) { - case Modem::SIM800: + case ModemType::SIM800: return build_unique(cfg, std::forward(args)...); - case Modem::SIM7600: + case ModemType::SIM7600: return build_unique(cfg, std::forward(args)...); - case Modem::BG96: + case ModemType::BG96: return build_unique(cfg, std::forward(args)...); - case Modem::GenericModule: + case ModemType::GenericModule: return build_unique(cfg, std::forward(args)...); default: break; @@ -204,13 +210,13 @@ public: DCE* build(const config *cfg, Args&&... args) { switch (m) { - case Modem::SIM800: + case ModemType::SIM800: return build(cfg, std::forward(args)...); - case Modem::SIM7600: + case ModemType::SIM7600: return build(cfg, std::forward(args)...); - case Modem::BG96: + case ModemType::BG96: return build(cfg, std::forward(args)...); - case Modem::GenericModule: + case ModemType::GenericModule: return build(cfg, std::forward(args)...); default: break; @@ -219,22 +225,22 @@ public: } private: - Modem m; + ModemType m; protected: - template , typename ...Args> + template , typename ...Args> static Ptr build_module_T(const config *cfg, Args&&... args) { - Builder b(std::forward(args)...); + Builder b(std::forward(args)...); return b.template create_module(cfg); } - template , typename DcePtr = Dce*, typename ...Args> - static DcePtr build_generic_DCE(const config *cfg, Args&&... args) + template , typename T_DcePtr = T_Dce*, typename ...Args> + static auto build_generic_DCE(const config *cfg, Args&&... args) -> T_DcePtr { - Builder b(std::forward(args)...); - return b.template create(cfg); + Builder b(std::forward(args)...); + return b.template create(cfg); } }; diff --git a/components/esp_modem/include/generate/esp_modem_command_declare.inc b/components/esp_modem/include/generate/esp_modem_command_declare.inc index 2c26d37f6..0c1a6bdbb 100644 --- a/components/esp_modem/include/generate/esp_modem_command_declare.inc +++ b/components/esp_modem/include/generate/esp_modem_command_declare.inc @@ -29,14 +29,14 @@ #define BOOL_OUT(param, name) bool& _ARG(param, name) #define INT_OUT(param, name) int& _ARG(param, name) -#define STRUCT_OUT(struct_name, x) struct_name& x +#define STRUCT_OUT(struct_name, p1) struct_name& p1 #else #define STRING_IN(param, name) const char* _ARG(param, name) #define STRING_OUT(param, name) char* _ARG(param, name) #define BOOL_IN(param, name) const bool _ARG(param, name) #define BOOL_OUT(param, name) bool* _ARG(param, name) #define INT_OUT(param, name) int* _ARG(param, name) -#define STRUCT_OUT(struct_name, x) struct struct_name* x +#define STRUCT_OUT(struct_name, p1) struct struct_name* p1 #endif #define DECLARE_ALL_COMMAND_APIS(...) \ @@ -50,7 +50,7 @@ ESP_MODEM_DECLARE_DCE_COMMAND(sync, command_result, 0) \ * @param[out] name module name * @return OK, FAIL or TIMEOUT */ \ -ESP_MODEM_DECLARE_DCE_COMMAND(get_operator_name, command_result, 1, STRING_OUT(x, name)) \ +ESP_MODEM_DECLARE_DCE_COMMAND(get_operator_name, command_result, 1, STRING_OUT(p1, name)) \ \ /** * @brief Stores current user profile @@ -63,28 +63,28 @@ ESP_MODEM_DECLARE_DCE_COMMAND(store_profile, command_result, 0) \ * @param[in] pin Pin * @return OK, FAIL or TIMEOUT */\ -ESP_MODEM_DECLARE_DCE_COMMAND(set_pin, command_result, 1, STRING_IN(x, pin)) \ +ESP_MODEM_DECLARE_DCE_COMMAND(set_pin, command_result, 1, STRING_IN(p1, pin)) \ \ /** * @brief Checks if the SIM needs a PIN * @param[out] pin_ok true if the SIM card doesn't need a PIN to unlock * @return OK, FAIL or TIMEOUT */ \ -ESP_MODEM_DECLARE_DCE_COMMAND(read_pin, command_result, 1, BOOL_OUT(x, pin_ok)) \ +ESP_MODEM_DECLARE_DCE_COMMAND(read_pin, command_result, 1, BOOL_OUT(p1, pin_ok)) \ \ /** * @brief Sets echo mode * @param[in] echo_on true if echo mode on (repeats the commands) * @return OK, FAIL or TIMEOUT */ \ -ESP_MODEM_DECLARE_DCE_COMMAND(set_echo, command_result, 1, BOOL_IN(x, echo_on)) \ +ESP_MODEM_DECLARE_DCE_COMMAND(set_echo, command_result, 1, BOOL_IN(p1, echo_on)) \ \ /** * @brief Sets the Txt or Pdu mode for SMS (only txt is supported) * @param[in] txt true if txt mode * @return OK, FAIL or TIMEOUT */ \ -ESP_MODEM_DECLARE_DCE_COMMAND(sms_txt_mode, command_result, 1, BOOL_IN(x, txt)) \ +ESP_MODEM_DECLARE_DCE_COMMAND(sms_txt_mode, command_result, 1, BOOL_IN(p1, txt)) \ \ /** * @brief Sets the default (GSM) charater set @@ -98,7 +98,7 @@ ESP_MODEM_DECLARE_DCE_COMMAND(sms_character_set, command_result, 0) \ * @param[in] message Text message to be sent * @return OK, FAIL or TIMEOUT */ \ -ESP_MODEM_DECLARE_DCE_COMMAND(send_sms, command_result, 2, STRING_IN(x, number), STRING_IN(y, message)) \ +ESP_MODEM_DECLARE_DCE_COMMAND(send_sms, command_result, 2, STRING_IN(p1, number), STRING_IN(p2, message)) \ \ /** * @brief Resumes data mode (Switches back to th data mode, which was temporarily suspended) @@ -108,10 +108,10 @@ ESP_MODEM_DECLARE_DCE_COMMAND(resume_data_mode, command_result, 0) \ \ /** * @brief Sets php context - * @param[in] x PdP context struct to setup modem cellular connection + * @param[in] p1 PdP context struct to setup modem cellular connection * @return OK, FAIL or TIMEOUT */ \ -ESP_MODEM_DECLARE_DCE_COMMAND(set_pdp_context, command_result, 1, STRUCT_OUT(PdpContext, x)) \ +ESP_MODEM_DECLARE_DCE_COMMAND(set_pdp_context, command_result, 1, STRUCT_OUT(PdpContext, p1)) \ \ /** * @brief Switches to the command mode @@ -130,21 +130,21 @@ ESP_MODEM_DECLARE_DCE_COMMAND(set_cmux, command_result, 0) \ * @param[out] imsi Module's IMSI number * @return OK, FAIL or TIMEOUT */ \ -ESP_MODEM_DECLARE_DCE_COMMAND(get_imsi, command_result, 1, STRING_OUT(x, imsi)) \ +ESP_MODEM_DECLARE_DCE_COMMAND(get_imsi, command_result, 1, STRING_OUT(p1, imsi)) \ \ /** * @brief Reads the IMEI number * @param[out] imei Module's IMEI number * @return OK, FAIL or TIMEOUT */ \ -ESP_MODEM_DECLARE_DCE_COMMAND(get_imei, command_result, 1, STRING_OUT(x, imei)) \ +ESP_MODEM_DECLARE_DCE_COMMAND(get_imei, command_result, 1, STRING_OUT(p1, imei)) \ \ /** * @brief Reads the module name * @param[out] name module name * @return OK, FAIL or TIMEOUT */ \ -ESP_MODEM_DECLARE_DCE_COMMAND(get_module_name, command_result, 1, STRING_OUT(x, name)) \ +ESP_MODEM_DECLARE_DCE_COMMAND(get_module_name, command_result, 1, STRING_OUT(p1, name)) \ \ /** * @brief Sets the modem to data mode @@ -158,7 +158,7 @@ ESP_MODEM_DECLARE_DCE_COMMAND(set_data_mode, command_result, 0) \ * @param[out] ber channel bit error rate * @return OK, FAIL or TIMEOUT */ \ -ESP_MODEM_DECLARE_DCE_COMMAND(get_signal_quality, command_result, 2, INT_OUT(x, rssi), INT_OUT(y, ber)) \ +ESP_MODEM_DECLARE_DCE_COMMAND(get_signal_quality, command_result, 2, INT_OUT(p1, rssi), INT_OUT(p2, ber)) \ \ /** * @brief Sets HW control flow @@ -166,7 +166,7 @@ ESP_MODEM_DECLARE_DCE_COMMAND(get_signal_quality, command_result, 2, INT_OUT(x, * @param[in] dte_flow 0=none, 2=CTS hw flow control of DTE * @return OK, FAIL or TIMEOUT */ \ -ESP_MODEM_DECLARE_DCE_COMMAND(set_flow_control, command_result, 2, INT_IN(x, dce_flow), INT_IN(y, dte_flow)) \ +ESP_MODEM_DECLARE_DCE_COMMAND(set_flow_control, command_result, 2, INT_IN(p1, dce_flow), INT_IN(p2, dte_flow)) \ \ /** * @brief Hangs up current data call @@ -181,7 +181,7 @@ ESP_MODEM_DECLARE_DCE_COMMAND(hang_up, command_result, 0) \ * @param[out] bcl 1-100% battery capacity, -1-Not available * @return OK, FAIL or TIMEOUT */ \ -ESP_MODEM_DECLARE_DCE_COMMAND(get_battery_status, command_result, 3, INT_OUT(x, voltage), INT_OUT(y, bcs), INT_OUT(z, bcl)) \ +ESP_MODEM_DECLARE_DCE_COMMAND(get_battery_status, command_result, 3, INT_OUT(p1, voltage), INT_OUT(p2, bcs), INT_OUT(p3, bcl)) \ \ /** * @brief Power down the module @@ -193,14 +193,14 @@ ESP_MODEM_DECLARE_DCE_COMMAND(power_down, command_result, 0) \ * @brief Reset the module * @return OK, FAIL or TIMEOUT */ \ -ESP_MODEM_DECLARE_DCE_COMMAND(reset, command_result, 0)\ +ESP_MODEM_DECLARE_DCE_COMMAND(reset, command_result, 0) \ \ /** * @brief Configures the baudrate * @param[in] baud Desired baud rate of the DTE * @return OK, FAIL or TIMEOUT */ \ -ESP_MODEM_DECLARE_DCE_COMMAND(set_baud, command_result, 1, INT_IN(x, baud)) +ESP_MODEM_DECLARE_DCE_COMMAND(set_baud, command_result, 1, INT_IN(p1, baud)) #ifdef GENERATE_DOCS diff --git a/components/esp_modem/src/esp_modem_api.cpp b/components/esp_modem/src/esp_modem_api.cpp index 2ed69ce89..654c96854 100644 --- a/components/esp_modem/src/esp_modem_api.cpp +++ b/components/esp_modem/src/esp_modem_api.cpp @@ -46,7 +46,7 @@ std::shared_ptr create_vfs_dte(const dte_config *config) { static inline std::unique_ptr -create_modem_dce(dce_factory::Modem m, const dce_config *config, std::shared_ptr dte, esp_netif_t *netif) { +create_modem_dce(dce_factory::ModemType m, const dce_config *config, std::shared_ptr dte, esp_netif_t *netif) { dce_factory::Factory f(m); TRY_CATCH_RET_NULL( return f.build_unique(config, std::move(dte), netif); @@ -54,19 +54,19 @@ create_modem_dce(dce_factory::Modem m, const dce_config *config, std::shared_ptr } std::unique_ptr create_SIM7600_dce(const dce_config *config, std::shared_ptr dte, esp_netif_t *netif) { - return create_modem_dce(dce_factory::Modem::SIM7600, config, std::move(dte), netif); + return create_modem_dce(dce_factory::ModemType::SIM7600, config, std::move(dte), netif); } std::unique_ptr create_SIM800_dce(const dce_config *config, std::shared_ptr dte, esp_netif_t *netif) { - return create_modem_dce(dce_factory::Modem::SIM800, config, std::move(dte), netif); + return create_modem_dce(dce_factory::ModemType::SIM800, config, std::move(dte), netif); } std::unique_ptr create_BG96_dce(const dce_config *config, std::shared_ptr dte, esp_netif_t *netif) { - return create_modem_dce(dce_factory::Modem::BG96, config, std::move(dte), netif); + return create_modem_dce(dce_factory::ModemType::BG96, config, std::move(dte), netif); } std::unique_ptr create_generic_dce(const dce_config *config, std::shared_ptr dte, esp_netif_t *netif) { - return create_modem_dce(dce_factory::Modem::GenericModule, config, std::move(dte), netif); + return create_modem_dce(dce_factory::ModemType::GenericModule, config, std::move(dte), netif); } } // namespace esp_modem diff --git a/components/esp_modem/src/esp_modem_c_api.cpp b/components/esp_modem/src/esp_modem_c_api.cpp index ffc464519..c012669bf 100644 --- a/components/esp_modem/src/esp_modem_c_api.cpp +++ b/components/esp_modem/src/esp_modem_c_api.cpp @@ -30,7 +30,7 @@ using namespace esp_modem; struct esp_modem_dce_wrap // need to mimic the polymorphic dispatch as CPP uses templated dispatch { enum class modem_wrap_dte_type { UART, } dte_type; - dce_factory::Modem modem_type; + dce_factory::ModemType modem_type; DCE* dce; }; @@ -47,18 +47,18 @@ static inline esp_err_t command_response_to_esp_err(command_result res) return ESP_ERR_INVALID_ARG; } -static inline dce_factory::Modem convert_modem_enum(esp_modem_dce_device_t module) +static inline dce_factory::ModemType convert_modem_enum(esp_modem_dce_device_t module) { switch (module) { case ESP_MODEM_DCE_SIM7600: - return esp_modem::dce_factory::Modem::SIM7600; + return esp_modem::dce_factory::ModemType::SIM7600; case ESP_MODEM_DCE_BG96: - return esp_modem::dce_factory::Modem::BG96; + return esp_modem::dce_factory::ModemType::BG96; case ESP_MODEM_DCE_SIM800: - return esp_modem::dce_factory::Modem::SIM800; + return esp_modem::dce_factory::ModemType::SIM800; default: case ESP_MODEM_DCE_GENETIC: - return esp_modem::dce_factory::Modem::GenericModule; + return esp_modem::dce_factory::ModemType::GenericModule; } } diff --git a/components/esp_modem/src/esp_modem_modules.cpp b/components/esp_modem/src/esp_modem_modules.cpp index 67a498267..f5ee1f7d8 100644 --- a/components/esp_modem/src/esp_modem_modules.cpp +++ b/components/esp_modem/src/esp_modem_modules.cpp @@ -27,9 +27,13 @@ GenericModule::GenericModule(std::shared_ptr dte, const dce_config *config) // Helper macros to handle multiple arguments of declared API #define ARGS0 -#define ARGS1 , x -#define ARGS2 , x , y -#define ARGS3 , x , y , z +#define ARGS1 , p1 +#define ARGS2 , p1 , p2 +#define ARGS3 , p1 , p2 , p3 +#define ARGS4 , p1 , p2 , p3, p4 +#define ARGS5 , p1 , p2 , p3, p4, p5 +#define ARGS6 , p1 , p2 , p3, p4, p5, p6 + #define _ARGS(x) ARGS ## x #define ARGS(x) _ARGS(x)