ThingsBoard Client SDK 0.16.0
Client SDK to connect with ThingsBoard IoT Platform from IoT devices (Arduino, Espressif, etc.)
Loading...
Searching...
No Matches
OTA_Handler.h
Go to the documentation of this file.
1#ifndef OTA_Handler_h
2#define OTA_Handler_h
3
4// Local include.
5#include "Configuration.h"
6
7// Local include.
8#include "Callback_Watchdog.h"
9#include "HashGenerator.h"
10#include "OTA_Update_Callback.h"
12#include "Helper.h"
13
14// Library includes.
15#include <string.h>
16
17
18// Firmware data keys.
19char constexpr FW_STATE_DOWNLOADING[] = "DOWNLOADING";
20char constexpr FW_STATE_DOWNLOADED[] = "DOWNLOADED";
21char constexpr FW_STATE_UPDATING[] = "UPDATING";
22char constexpr FW_STATE_FAILED[] = "FAILED";
23char constexpr FW_STATE_UPDATED[] = "UPDATED";
24
25// Log messages.
26char constexpr OTA_CB_IS_NULL[] = "OTA update callback is NULL, has it been deleted";
27char constexpr UNABLE_TO_REQUEST_CHUNCKS[] = "Unable to request firmware chunk";
28char constexpr RECEIVED_UNEXPECTED_CHUNK[] = "Received chunk (%u), not the same as requested chunk (%u)";
29char constexpr RECEIVED_UNEXPECTED_CHUNK_SIZE[] = "Received chunk size (%u), not the same as expected chunk size (%u)";
30char constexpr ERROR_UPDATE_BEGIN[] = "Failed to initalize flash updater, ensure that the partition scheme has two app sections";
31char constexpr ERROR_UPDATE_WRITE[] = "Only wrote (%u) bytes of binary data instead of expected (%u)";
32char constexpr ERROR_UPDATE_END[] = "Error during flash updater not all bytes written";
33char constexpr CHECKSUM_VERIFICATION_FAILED[] = "Calculated checksum (%s), not the same as expected checksum (%s)";
34char constexpr FW_UPDATE_ABORTED[] = "Firmware update aborted";
35char constexpr CHUNK_REQUEST_TIMED_OUT[] = "Failed to receive requested chunk (%u) in (%llu) us. Internet connection might have been lost";
36#if THINGSBOARD_ENABLE_DEBUG
37char constexpr FW_CHUNK[] = "Receive chunk (%u), with size (%u) bytes";
38char constexpr HASH_EXPECTED[] = "Expected checksum: (%s)";
39char constexpr CHECKSUM_VERIFICATION_SUCCESS[] = "Checksum is the same as expected";
40char constexpr FW_UPDATE_SUCCESS[] = "Update success";
41#endif // THINGSBOARD_ENABLE_DEBUG
42
43
49template <typename Logger>
51 public:
57 : m_fw_callback(nullptr)
58 , m_publish_callback(publish_callback)
59 , m_send_fw_state_callback(send_fw_state_callback)
60 , m_finish_callback(finish_callback)
61 , m_fw_size(0U)
62 , m_fw_checksum()
63 , m_fw_checksum_algorithm()
64 , m_hash()
65 , m_total_chunks(0U)
66 , m_requested_chunks(0U)
67 , m_retries(0U)
68 {
69#if !THINGSBOARD_ENABLE_STL
70 m_subscribedInstance = nullptr;
71#endif // !THINGSBOARD_ENABLE_STL
72 }
73
80 void Start_Firmware_Update(OTA_Update_Callback & fw_callback, size_t const & fw_size, char const * fw_checksum, mbedtls_md_type_t const & fw_checksum_algorithm) {
81 m_fw_callback = &fw_callback;
82 m_fw_size = fw_size;
83 m_total_chunks = (m_fw_size / m_fw_callback->Get_Chunk_Size()) + 1U;
84 (void)strncpy(m_fw_checksum, fw_checksum, sizeof(m_fw_checksum));
85 m_fw_checksum_algorithm = fw_checksum_algorithm;
86 auto & request_timeout = m_fw_callback->Get_Request_Timeout();
87#if THINGSBOARD_ENABLE_STL
88 request_timeout.Set_Timeout_Callback(std::bind(&OTA_Handler::Handle_Request_Timeout, this));
89#else
90 request_timeout.Set_Timeout_Callback(OTA_Handler::Static_Handle_Request_Timeout);
91#endif // THINGSBOARD_ENABLE_STL
92 Request_First_Firmware_Packet();
93 (void)m_send_fw_state_callback.Call_Callback(FW_STATE_DOWNLOADING, "");
94 }
95
100 auto & request_timeout = m_fw_callback->Get_Request_Timeout();
101 request_timeout.Stop_Timeout_Timer();
102 auto fw_updater = m_fw_callback->Get_Updater();
103 fw_updater->reset();
104 Logger::printfln(FW_UPDATE_ABORTED);
105 Handle_Failure(OTA_Failure_Response::RETRY_NOTHING, FW_UPDATE_ABORTED);
106 m_fw_callback = nullptr;
107 }
108
116 void Process_Firmware_Packet(size_t const & current_chunk, uint8_t * payload, size_t const & total_bytes) {
117 if (current_chunk != m_requested_chunks) {
118 Logger::printfln(RECEIVED_UNEXPECTED_CHUNK, current_chunk, m_requested_chunks);
119 return;
120 }
121 size_t expected_chunk_size = 0U;
122 if (!Received_Valid_Chunk_Size(total_bytes, expected_chunk_size)) {
123 Logger::printfln(RECEIVED_UNEXPECTED_CHUNK_SIZE, expected_chunk_size, total_bytes);
124 return;
125 }
126
127 auto & request_timeout = m_fw_callback->Get_Request_Timeout();
128 request_timeout.Stop_Timeout_Timer();
129 #if THINGSBOARD_ENABLE_DEBUG
130 Logger::printfln(FW_CHUNK, current_chunk, total_bytes);
131 #endif // THINGSBOARD_ENABLE_DEBUG
132
133 auto fw_updater = m_fw_callback->Get_Updater();
134 if (current_chunk == 0U && !fw_updater->begin(m_fw_size)) {
135 Logger::printfln(ERROR_UPDATE_BEGIN);
136 return Handle_Failure(OTA_Failure_Response::RETRY_UPDATE, ERROR_UPDATE_BEGIN);
137 }
138
139 auto const written_bytes = fw_updater->write(payload, total_bytes);
140 if (written_bytes != total_bytes) {
141 char message[Helper::Calculate_Print_Size(ERROR_UPDATE_WRITE, written_bytes, total_bytes)] = {};
142 (void)snprintf(message, sizeof(message), ERROR_UPDATE_WRITE, written_bytes, total_bytes);
143 Logger::printfln(message);
144 return Handle_Failure(OTA_Failure_Response::RETRY_UPDATE, message);
145 }
146
147 // Update hash value only if writing with updater implementation was a success, result is ignored,
148 // because it can only fail if the input parameters are invalid
149 (void)m_hash.update(payload, total_bytes);
150
151 m_requested_chunks = current_chunk + 1;
152 m_fw_callback->Call_Progress_Callback(m_requested_chunks, m_total_chunks);
153
154 // Ensure to check if the update was cancelled during the progress callback,
155 // if it was the callback variable was reset and there is no need to request the next firmware packet
156 if (m_fw_callback == nullptr) {
157 Logger::printfln(OTA_CB_IS_NULL);
158 return Handle_Failure(OTA_Failure_Response::RETRY_NOTHING, OTA_CB_IS_NULL);
159 }
160
161 Reset_Retries();
162 Request_Next_Firmware_Packet();
163 }
164
165#if !THINGSBOARD_USE_ESP_TIMER
168 void update() {
169 auto & request_timeout = m_fw_callback->Get_Request_Timeout();
170 request_timeout.Update_Timeout_Timer();
171 }
172#endif // !THINGSBOARD_USE_ESP_TIMER
173
174 private:
176 void Reset_Retries() {
177 m_retries = m_fw_callback->Get_Chunk_Retries();
178 }
179
186 bool Received_Valid_Chunk_Size(size_t const & received_chunk_size, size_t & expected_chunk_size) {
187 bool const is_last_chunk = m_requested_chunks + 1 >= m_total_chunks;
188 if (is_last_chunk) {
189 auto const last_chunk_expected_size = m_fw_size % m_fw_callback->Get_Chunk_Size();
190 expected_chunk_size = last_chunk_expected_size;
191 return received_chunk_size == last_chunk_expected_size;
192 }
193 expected_chunk_size = m_fw_callback->Get_Chunk_Size();
194 return received_chunk_size == m_fw_callback->Get_Chunk_Size();
195 }
196
198 void Request_First_Firmware_Packet() {
199 m_requested_chunks = 0U;
200 Reset_Retries();
201 // Hash start result is ignored, because it can only fail if the input parameters are invalid
202 (void)m_hash.start(m_fw_checksum_algorithm);
203 auto & request_timeout = m_fw_callback->Get_Request_Timeout();
204 request_timeout.Stop_Timeout_Timer();
205 auto fw_updater = m_fw_callback->Get_Updater();
206 fw_updater->reset();
207 Request_Next_Firmware_Packet();
208 }
209
212 void Request_Next_Firmware_Packet() {
213 // Check if we have already requested and handled the last remaining chunk
214 if (m_requested_chunks >= m_total_chunks) {
215 Finish_Firmware_Update();
216 return;
217 }
218
219 if (!m_publish_callback.Call_Callback(m_fw_callback->Get_Request_ID(), m_requested_chunks)) {
220 Logger::printfln(UNABLE_TO_REQUEST_CHUNCKS);
221 }
222
223 // Request timeout gets started no matter if publishing previous request was successful or not in hopes,
224 // that after the given timeout the callback calls this method again and can then publish the request successfully.
225 // This works because the request fails most of the time, because the internet connection might have been temporarily disconnected.
226 // Therefore waiting a while and then retrying, means we might be reconnected again
227 auto & request_timeout = m_fw_callback->Get_Request_Timeout();
228 request_timeout.Start_Timeout_Timer();
229 }
230
235 void Finish_Firmware_Update() {
236 (void)m_send_fw_state_callback.Call_Callback(FW_STATE_DOWNLOADED, "");
237 auto const calculated_checksum = m_hash.finish();
238
239 if (strncmp(m_fw_checksum, calculated_checksum.hash, strlen(m_fw_checksum)) != 0) {
240 char message[Helper::Calculate_Print_Size(CHECKSUM_VERIFICATION_FAILED, calculated_checksum.hash, m_fw_checksum)] = {};
241 (void)snprintf(message, sizeof(message), CHECKSUM_VERIFICATION_FAILED, calculated_checksum.hash, m_fw_checksum);
242 Logger::printfln(message);
243 return Handle_Failure(OTA_Failure_Response::RETRY_UPDATE, message);
244 }
245
246 #if THINGSBOARD_ENABLE_DEBUG
247 Logger::printfln(CHECKSUM_VERIFICATION_SUCCESS);
248 #endif // THINGSBOARD_ENABLE_DEBUG
249
250 auto fw_updater = m_fw_callback->Get_Updater();
251 if (!fw_updater->end()) {
252 Logger::printfln(ERROR_UPDATE_END);
253 return Handle_Failure(OTA_Failure_Response::RETRY_UPDATE, ERROR_UPDATE_END);
254 }
255
256 #if THINGSBOARD_ENABLE_DEBUG
257 Logger::printfln(FW_UPDATE_SUCCESS);
258 #endif // THINGSBOARD_ENABLE_DEBUG
259
260 (void)m_send_fw_state_callback.Call_Callback(FW_STATE_UPDATING, "");
261 m_fw_callback->Call_Callback(true);
262 (void)m_finish_callback.Call_Callback();
263 }
264
270 void Handle_Failure(OTA_Failure_Response failure_response, char const * error_message) {
271 if (m_retries <= 0) {
272 Abort_Firmware_Update(error_message);
273 return;
274 }
275
276 // Decrease the amount of retries of downloads for the current chunk,
277 // reset as soon as the next chunk has been received and handled successfully
278 m_retries--;
279
280 switch (failure_response) {
281 case OTA_Failure_Response::RETRY_CHUNK:
282 Request_Next_Firmware_Packet();
283 break;
284 case OTA_Failure_Response::RETRY_UPDATE:
285 Request_First_Firmware_Packet();
286 break;
287 case OTA_Failure_Response::RETRY_NOTHING:
288 Abort_Firmware_Update(error_message);
289 break;
290 }
291 }
292
297 void Abort_Firmware_Update(char const * error_message) {
298 (void)m_send_fw_state_callback.Call_Callback(FW_STATE_FAILED, error_message);
299 if (m_fw_callback != nullptr) {
300 m_fw_callback->Call_Callback(false);
301 }
302 (void)m_finish_callback.Call_Callback();
303 }
304
306 void Handle_Request_Timeout() {
307 auto const & request_timeout = m_fw_callback->Get_Request_Timeout();
308 uint64_t const & timeout = request_timeout.Get_Timeout();
309 char message[Helper::Calculate_Print_Size(CHUNK_REQUEST_TIMED_OUT, m_requested_chunks, timeout)] = {};
310 (void)snprintf(message, sizeof(message), CHUNK_REQUEST_TIMED_OUT, m_requested_chunks, timeout);
311 Logger::printfln(message);
312 Handle_Failure(OTA_Failure_Response::RETRY_CHUNK, message);
313 }
314
315#if !THINGSBOARD_ENABLE_STL
316 static bool Static_Handle_Request_Timeout() {
317 if (m_subscribedInstance == nullptr) {
318 return false;
319 }
320 return m_subscribedInstance->Handle_Request_Timeout();
321 }
322
323 static OTA_Handler *m_subscribedInstance;
324#endif // !THINGSBOARD_ENABLE_STL
325
326 OTA_Update_Callback *m_fw_callback = {}; // Callback method that contains configuration information, about the over the air update
327 Callback<bool, size_t const &, size_t const &> m_publish_callback = {}; // Callback that is used to request the firmware chunk of the firmware binary with the given chunk number
328 Callback<bool, char const * const, char const * const> m_send_fw_state_callback = {}; // Callback that is used to send information about the current state of the over the air update
329 Callback<bool> m_finish_callback = {}; // Callback that is called once the update has been finished and the user should be informed of the failure or success of the over the air update
330 size_t m_fw_size = {}; // Total size of the firmware binary we will receive. Allows for a binary size of up to theoretically 4 GB
331 char m_fw_checksum[MAX_STRING_HASH_SIZE] = {}; // Checksum of the complete firmware binary, should be the same as the actually written data in the end
332 mbedtls_md_type_t m_fw_checksum_algorithm = {}; // Algorithm type used to hash the firmware binary
333 HashGenerator m_hash = {}; // Class instance that allows to generate a hash from received firmware binary data
334 size_t m_total_chunks = {}; // Total amount of chunks that need to be received to get the complete firmware binary
335 size_t m_requested_chunks = {}; // Amount of successfully requested and received firmware binary chunks
336 uint8_t m_retries = {}; // Amount of request retries we attempt for each chunk, increasing makes the connection more stable
337};
338
339#if !THINGSBOARD_ENABLE_STL
340OTA_Handler *OTA_Handler::m_subscribedInstance = nullptr;
341#endif
342
343#endif // OTA_Handler_h
size_t constexpr MAX_STRING_HASH_SIZE
Definition: HashGenerator.h:17
OTA_Failure_Response
Possible responses to error states the OTA update might fall into.
Definition: OTA_Failure_Response.h:11
char constexpr FW_STATE_DOWNLOADED[]
Definition: OTA_Handler.h:20
char constexpr ERROR_UPDATE_END[]
Definition: OTA_Handler.h:32
char constexpr FW_STATE_DOWNLOADING[]
Definition: OTA_Handler.h:19
char constexpr FW_STATE_UPDATING[]
Definition: OTA_Handler.h:21
char constexpr CHUNK_REQUEST_TIMED_OUT[]
Definition: OTA_Handler.h:35
char constexpr RECEIVED_UNEXPECTED_CHUNK_SIZE[]
Definition: OTA_Handler.h:29
char constexpr FW_STATE_FAILED[]
Definition: OTA_Handler.h:22
char constexpr UNABLE_TO_REQUEST_CHUNCKS[]
Definition: OTA_Handler.h:27
char constexpr FW_UPDATE_ABORTED[]
Definition: OTA_Handler.h:34
char constexpr RECEIVED_UNEXPECTED_CHUNK[]
Definition: OTA_Handler.h:28
char constexpr FW_STATE_UPDATED[]
Definition: OTA_Handler.h:23
char constexpr CHECKSUM_VERIFICATION_FAILED[]
Definition: OTA_Handler.h:33
char constexpr ERROR_UPDATE_BEGIN[]
Definition: OTA_Handler.h:30
char constexpr OTA_CB_IS_NULL[]
Definition: OTA_Handler.h:26
char constexpr ERROR_UPDATE_WRITE[]
Definition: OTA_Handler.h:31
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
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
Wrapper class which allows generating a hash of a given type from any arbitrary byte payload,...
Definition: HashGenerator.h:28
bool start(mbedtls_md_type_t const &type)
Starts the hashing process.
Definition: HashGenerator.cpp:11
bool update(uint8_t const *data, size_t const &length)
Update the current hash value with new data.
Definition: HashGenerator.cpp:24
HashString finish()
Calculates the final hash string representation and stops the hash calculation no further calls to up...
Definition: HashGenerator.cpp:28
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
virtual void reset()=0
Resets the writing of the given data so it can be restarted with begin.
Handles the complete processing of received binary firmware data including writing the data into some...
Definition: OTA_Handler.h:50
void Stop_Firmware_Update()
Stops the firmware update completly and informs that user that the update has failed because it has b...
Definition: OTA_Handler.h:99
void Process_Firmware_Packet(size_t const &current_chunk, uint8_t *payload, size_t const &total_bytes)
Called when the chunk response is received from the server and if successfull sends the request for t...
Definition: OTA_Handler.h:116
void Start_Firmware_Update(OTA_Update_Callback &fw_callback, size_t const &fw_size, char const *fw_checksum, mbedtls_md_type_t const &fw_checksum_algorithm)
Starts the firmware update with requesting the first firmware packet and initalizes the underlying ne...
Definition: OTA_Handler.h:80
OTA_Handler(Callback< bool, size_t const &, size_t const & >::function publish_callback, Callback< bool, char const *const, char const *const >::function send_fw_state_callback, Callback< bool >::function finish_callback)
Constructor.
Definition: OTA_Handler.h:56
void update()
Used to update the watchdog timer which uses a simple software time in the background....
Definition: OTA_Handler.h:168
Over the air firmware update callback wrapper.
Definition: OTA_Update_Callback.h:19
Timeoutable_Request & Get_Request_Timeout()
Gets the request timeout callback.
Definition: OTA_Update_Callback.cpp:83
void Call_Progress_Callback(size_t const &current, size_t const &total) const
Calls the progress callback that was subscribed, when this class instance was initally created.
Definition: OTA_Update_Callback.cpp:51
uint8_t Get_Chunk_Retries() const
Gets the amount of times we attempt to download each chunk of the OTA firmware binary file.
Definition: OTA_Update_Callback.cpp:67
uint16_t Get_Chunk_Size() const
Gets the size of the chunks that the firmware binary data will be split into.
Definition: OTA_Update_Callback.cpp:75
IUpdater * Get_Updater() const
Gets the updater implementation, used to write the actual firmware data into the needed memory locati...
Definition: OTA_Update_Callback.cpp:35
size_t const & Get_Request_ID() const
Gets the unique request identifier that is connected to the original request.
Definition: OTA_Update_Callback.cpp:43
void Set_Timeout_Callback(Callback_Watchdog::function timeout_callback)
Sets the callback method that will be called upon request timeout (did not receive a response from th...
Definition: Timeoutable_Request.cpp:36
uint64_t const & Get_Timeout() const
Gets the amount of microseconds until we expect to have received a response.
Definition: Timeoutable_Request.cpp:11
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