ThingsBoard Client SDK 0.16.0
Client SDK to connect with ThingsBoard IoT Platform from IoT devices (Arduino, Espressif, etc.)
Loading...
Searching...
No Matches
Client_Side_RPC.h
Go to the documentation of this file.
1#ifndef Client_Side_RPC_h
2#define Client_Side_RPC_h
3
4// Local includes.
7
8
9// client-side RPC topics.
10char constexpr RPC_RESPONSE_SUBSCRIBE_TOPIC[] = "v1/devices/me/rpc/response/+";
11char constexpr RPC_RESPONSE_TOPIC[] = "v1/devices/me/rpc/response/";
12char constexpr RPC_SEND_REQUEST_TOPIC[] = "v1/devices/me/rpc/request/%u";
13// Log messages.
14char constexpr CLIENT_RPC_METHOD_NULL[] = "Client-side RPC method name is NULL";
15#if !THINGSBOARD_ENABLE_DYNAMIC
16char constexpr RPC_REQUEST_OVERFLOWED[] = "Client-side RPC request overflowed, increase MaxRequestRPC (%u)";
17char constexpr CLIENT_SIDE_RPC_SUBSCRIPTIONS[] = "client-side RPC";
18#endif // !THINGSBOARD_ENABLE_DYNAMIC
19char constexpr RPC_EMPTY_PARAMS_VALUE[] = "{}";
20
21
25#if THINGSBOARD_ENABLE_DYNAMIC
26template <typename Logger = DefaultLogger>
27#else
33template<size_t MaxSubscriptions = DEFAULT_SUBSCRIPTION_AMOUNT, size_t MaxRequestRPC = DEFAULT_REQUEST_RPC_AMOUNT, typename Logger = DefaultLogger>
34#endif // THINGSBOARD_ENABLE_DYNAMIC
36 public:
38 Client_Side_RPC() = default;
39
40 ~Client_Side_RPC() override = default;
41
51 bool RPC_Request(RPC_Request_Callback const & callback) {
52 char const * method_name = callback.Get_Name();
53
54 if (Helper::String_IsNull_Or_Empty(method_name)) {
55 Logger::printfln(CLIENT_RPC_METHOD_NULL);
56 return false;
57 }
58 RPC_Request_Callback * registered_callback = nullptr;
59 if (!RPC_Request_Subscribe(callback, registered_callback)) {
60 return false;
61 }
62 else if (registered_callback == nullptr) {
63 return false;
64 }
65
66 JsonArray const * parameters = callback.Get_Parameters();
67
68#if THINGSBOARD_ENABLE_DYNAMIC
69 // String are const char* and therefore stored as a pointer --> zero copy, meaning the size for the strings is 0 bytes,
70 // Data structure size depends on the amount of key value pairs passed + the default method name and params key needed for the request.
71 // See https://arduinojson.org/v6/assistant/ for more information on the needed size for the JsonDocument
72 TBJsonDocument request_buffer(JSON_OBJECT_SIZE(parameters != nullptr ? parameters->size() + 2U : 2U));
73#else
74 // Ensure to have enough size for the infinite amount of possible parameters that could be sent to the cloud
75 StaticJsonDocument<JSON_OBJECT_SIZE(MaxRequestRPC)> request_buffer;
76#endif // THINGSBOARD_ENABLE_DYNAMIC
77
78 request_buffer[RPC_METHOD_KEY] = method_name;
79
80 if (parameters != nullptr && !parameters->isNull()) {
81 request_buffer[RPC_PARAMS_KEY] = *parameters;
82 }
83 else {
84 request_buffer[RPC_PARAMS_KEY] = RPC_EMPTY_PARAMS_VALUE;
85 }
86
87#if !THINGSBOARD_ENABLE_DYNAMIC
88 if (request_buffer.overflowed()) {
89 Logger::printfln(RPC_REQUEST_OVERFLOWED, MaxRequestRPC);
90 return false;
91 }
92#endif // !THINGSBOARD_ENABLE_DYNAMIC
93
94 size_t * p_request_id = m_get_request_id_callback.Call_Callback();
95 if (p_request_id == nullptr) {
96 Logger::printfln(REQUEST_ID_NULL);
97 return false;
98 }
99 auto & request_id = *p_request_id;
100
101 registered_callback->Set_Request_ID(++request_id);
102 auto & request_callback = registered_callback->Get_Request_Timeout();
103 request_callback.Start_Timeout_Timer();
104
105 char topic[Helper::Calculate_Print_Size(RPC_SEND_REQUEST_TOPIC, request_id)] = {};
106 (void)snprintf(topic, sizeof(topic), RPC_SEND_REQUEST_TOPIC, request_id);
107 return m_send_json_callback.Call_Callback(topic, request_buffer);
108 }
109
111 return API_Process_Type::JSON;
112 }
113
114 void Process_Response(char const * topic, uint8_t * payload, uint32_t length) override {
115 // Nothing to do
116 }
117
118 void Process_Json_Response(char const * topic, JsonDocument const & data) override {
119 auto const request_id = Helper::Split_Topic_Into_Request_ID(topic, strlen(RPC_RESPONSE_TOPIC));
120
121#if THINGSBOARD_ENABLE_STL
122 auto it = std::find_if(m_rpc_request_callbacks.begin(), m_rpc_request_callbacks.end(), [&request_id](RPC_Request_Callback & rpc_request) {
123 return rpc_request.Get_Request_ID() == request_id;
124 });
125 if (it != m_rpc_request_callbacks.end()) {
126 auto & rpc_request = *it;
127#else
128 for (auto it = m_rpc_request_callbacks.begin(); it != m_rpc_request_callbacks.end(); ++it) {
129 auto & rpc_request = *it;
130
131 if (rpc_request.Get_Request_ID() != request_id) {
132 continue;
133 }
134#endif // THINGSBOARD_ENABLE_STL
135 auto & request_timeout = rpc_request.Get_Request_Timeout();
136 request_timeout.Stop_Timeout_Timer();
137 rpc_request.Call_Callback(data);
138
139 // Delete callback because the changes have been requested and the callback is no longer needed
140 m_rpc_request_callbacks.erase(it);
141#if !THINGSBOARD_ENABLE_STL
142 break;
143#endif // !THINGSBOARD_ENABLE_STL
144 }
145
146 // Attempt to unsubscribe from the shared attribute request topic,
147 // if we are not waiting for any further responses with shared attributes from the server.
148 // Will be resubscribed if another request is sent anyway
149 if (m_rpc_request_callbacks.empty()) {
150 (void)RPC_Request_Unsubscribe();
151 }
152 }
153
154 bool Is_Response_Topic_Matching(char const * topic) const override {
155 return strncmp(RPC_RESPONSE_TOPIC, topic, strlen(RPC_RESPONSE_TOPIC)) == 0;
156 }
157
158 bool Unsubscribe() override {
159 return RPC_Request_Unsubscribe();
160 }
161
163 m_rpc_request_callbacks.clear();
164 return Unsubscribe();
165 }
166
167#if !THINGSBOARD_USE_ESP_TIMER
168 void loop() override {
169 for (auto & rpc_request : m_rpc_request_callbacks) {
170 auto & request_callback = rpc_request.Get_Request_Timeout();
171 request_callback.Update_Timeout_Timer();
172 }
173 }
174#endif // !THINGSBOARD_USE_ESP_TIMER
175
176 void Initialize() override {
177 // Nothing to do
178 }
179
181 m_send_json_callback.Set_Callback(send_json_callback);
182 m_subscribe_topic_callback.Set_Callback(subscribe_topic_callback);
183 m_unsubscribe_topic_callback.Set_Callback(unsubscribe_topic_callback);
184 m_get_request_id_callback.Set_Callback(get_request_id_callback);
185 }
186
187 private:
188#if THINGSBOARD_ENABLE_DYNAMIC
189 using Callback_Container = Container<RPC_Request_Callback>;
190#else
191 using Callback_Container = Container<RPC_Request_Callback, MaxSubscriptions>;
192#endif // THINGSBOARD_ENABLE_DYNAMIC
193
198 bool RPC_Request_Subscribe(RPC_Request_Callback const & callback, RPC_Request_Callback * & registered_callback) {
199#if !THINGSBOARD_ENABLE_DYNAMIC
200 if (m_rpc_request_callbacks.size() + 1 > m_rpc_request_callbacks.capacity()) {
202 return false;
203 }
204#endif // !THINGSBOARD_ENABLE_DYNAMIC
205 if (!m_subscribe_topic_callback.Call_Callback(RPC_RESPONSE_SUBSCRIBE_TOPIC)) {
207 return false;
208 }
209 m_rpc_request_callbacks.push_back(callback);
210 registered_callback = &m_rpc_request_callbacks.back();
211 return true;
212 }
213
216 bool RPC_Request_Unsubscribe() {
218 return m_unsubscribe_topic_callback.Call_Callback(RPC_RESPONSE_SUBSCRIBE_TOPIC);
219 }
220
221 Callback<bool, char const * const, JsonDocument const &> m_send_json_callback = {}; // Send json document callback
222 Callback<bool, char const * const> m_subscribe_topic_callback = {}; // Subscribe mqtt topic client callback
223 Callback<bool, char const * const> m_unsubscribe_topic_callback = {}; // Unubscribe mqtt topic client callback
224 Callback<size_t *> m_get_request_id_callback = {}; // Get internal request id callback
225 Callback_Container m_rpc_request_callbacks = {}; // Server side RPC callbacks vector
226};
227
228#endif // Client_Side_RPC_h
API_Process_Type
Possible processing types an API Implementation uses to handle responses from the server.
Definition: API_Process_Type.h:19
char constexpr RPC_SEND_REQUEST_TOPIC[]
Definition: Client_Side_RPC.h:12
char constexpr RPC_REQUEST_OVERFLOWED[]
Definition: Client_Side_RPC.h:16
char constexpr CLIENT_SIDE_RPC_SUBSCRIPTIONS[]
Definition: Client_Side_RPC.h:17
char constexpr CLIENT_RPC_METHOD_NULL[]
Definition: Client_Side_RPC.h:14
char constexpr RPC_RESPONSE_TOPIC[]
Definition: Client_Side_RPC.h:11
char constexpr RPC_EMPTY_PARAMS_VALUE[]
Definition: Client_Side_RPC.h:19
char constexpr RPC_RESPONSE_SUBSCRIBE_TOPIC[]
Definition: Client_Side_RPC.h:10
char constexpr MAX_SUBSCRIPTIONS_TEMPLATE_NAME[]
Definition: IAPI_Implementation.h:22
char constexpr RPC_PARAMS_KEY[]
Definition: IAPI_Implementation.h:27
char constexpr SUBSCRIBE_TOPIC_FAILED[]
Definition: IAPI_Implementation.h:23
char constexpr MAX_SUBSCRIPTIONS_EXCEEDED[]
Definition: IAPI_Implementation.h:20
char constexpr RPC_METHOD_KEY[]
Definition: IAPI_Implementation.h:26
char constexpr REQUEST_ID_NULL[]
Definition: IAPI_Implementation.h:24
General purpose safe callback wrapper. Expects either c-style or c++ style function pointer,...
Definition: Callback.h:30
std::function< return_type(argument_types... arguments)> function
Callback signature.
Definition: Callback.h:34
void Set_Callback(function callback)
Sets the callback method that will be called upon data arrival with the given data that was received....
Definition: Callback.h:72
return_type Call_Callback(argument_types const &... arguments) const
Calls the callback that was subscribed, when this class instance was initally created.
Definition: Callback.h:62
Handles the internal implementation of the ThingsBoard client-side RPC API. See https://thingsboard....
Definition: Client_Side_RPC.h:35
bool Is_Response_Topic_Matching(char const *topic) const override
Compares received response topic and the topic this api implementation handles responses on,...
Definition: Client_Side_RPC.h:154
bool RPC_Request(RPC_Request_Callback const &callback)
Requests the response from one client-side rpc method, which will call the passed callback....
Definition: Client_Side_RPC.h:51
void loop() override
Internal loop method to update inernal timers for API calls that can timeout.
Definition: Client_Side_RPC.h:168
~Client_Side_RPC() override=default
bool Unsubscribe() override
Unsubcribes all callbacks, to clear up any ongoing subscriptions and stop receiving information over ...
Definition: Client_Side_RPC.h:158
void Set_Client_Callbacks(Callback< void, IAPI_Implementation & >::function subscribe_api_callback, Callback< bool, char const *const, JsonDocument const & >::function send_json_callback, Callback< bool, char const *const, char const *const >::function send_json_string_callback, Callback< bool, char const *const >::function subscribe_topic_callback, Callback< bool, char const *const >::function unsubscribe_topic_callback, Callback< uint16_t >::function get_receive_size_callback, Callback< uint16_t >::function get_send_size_callback, Callback< bool, uint16_t, uint16_t >::function set_buffer_size_callback, Callback< size_t * >::function get_request_id_callback) override
Sets the underlying callbacks that are required for the different API Implementation to communicate w...
Definition: Client_Side_RPC.h:180
void Process_Response(char const *topic, uint8_t *payload, uint32_t length) override
Process callback that will be called upon response arrival.
Definition: Client_Side_RPC.h:114
void Initialize() override
Method that allows to construct internal objects, after the required callback member methods have bee...
Definition: Client_Side_RPC.h:176
Client_Side_RPC()=default
Constructor.
API_Process_Type Get_Process_Type() const override
Returns the way the server response should be processed.
Definition: Client_Side_RPC.h:110
bool Resubscribe_Permanent_Subscriptions() override
Forwards the call to let the API clear up any ongoing single-event subscriptions (Provision,...
Definition: Client_Side_RPC.h:162
void Process_Json_Response(char const *topic, JsonDocument const &data) override
Process callback that will be called upon response arrival.
Definition: Client_Side_RPC.h:118
Custom std::array or std::vector implementation that contains a partial vector-like interface impleme...
Definition: Container.h:29
size_type size() const
Gets the current amount of elements in the underlying data container.
Definition: Container.h:147
reference back()
Returns a reference to the last element of the array. If the array is empty this method will assert a...
Definition: Container.h:196
iterator begin()
Returns an iterator to the first element of the underlying data container. If the array is empty,...
Definition: Container.h:165
void erase(const_iterator position)
Removes the element at the given position, has to move all element one to the left if the iterator do...
Definition: Container.h:284
size_type constexpr capacity() const
Gets the maximum amount of elements that can be stored in the underlying data container.
Definition: Container.h:157
void push_back(const_reference element)
Appends the given element at the end of the underlying data container.
Definition: Container.h:230
bool empty() const
Returns whether there are any elements in the underlying data container.
Definition: Container.h:141
void clear()
Erases all elements from the container. After this call, size() returns zero.
Definition: Container.h:330
iterator end()
Returns a iterator to one-past-the-last element of the underlying data container.
Definition: Container.h:209
static size_t Calculate_Print_Size(char const *format, Args const &... args)
Returns the total amount of bytes needed to store the formatted string with null termination,...
Definition: Helper.h:32
static bool String_IsNull_Or_Empty(char const *str)
Returns wheter the given string is either a nullptr or is an empty string, meaning it only contains a...
Definition: Helper.cpp:27
static size_t Split_Topic_Into_Request_ID(char const *received_topic, size_t const &end_position)
Splits the topic at the given position and extracts the request id parameter from the remaining strin...
Definition: Helper.cpp:31
Base functionality required by all API implementation.
Definition: IAPI_Implementation.h:37
Client-side RPC callback wrapper, contains the needed configuration settings to create the request th...
Definition: RPC_Request_Callback.h:11
size_t const & Get_Request_ID() const
Gets the unique request identifier that is connected to the original request.
Definition: RPC_Request_Callback.cpp:14
Timeoutable_Request & Get_Request_Timeout()
Gets the request timeout callback.
Definition: RPC_Request_Callback.cpp:38
JsonArray const * Get_Parameters() const
Gets the paramaters we want to call the client-side RPC method on the cloud with.
Definition: RPC_Request_Callback.cpp:30
char const * Get_Name() const
Gets the name of the client-side RPC method we want to call on the cloud so that this method callback...
Definition: RPC_Request_Callback.cpp:22
void Set_Request_ID(size_t const &request_id)
Sets the unique request identifier that is connected to the original request.
Definition: RPC_Request_Callback.cpp:18
void Update_Timeout_Timer()
Updates the internal timeout timer.
Definition: Timeoutable_Request.cpp:20
void Start_Timeout_Timer()
Starts the internal timeout timer if we actually received a configured valid timeout time and a valid...
Definition: Timeoutable_Request.cpp:25
void Stop_Timeout_Timer()
Stops the internal timeout timer, is called as soon as an answer is received from the cloud....
Definition: Timeoutable_Request.cpp:32