Skip to content

Commit 5d9f39d

Browse files
committed
Add Class to handle retries and state transitions
1 parent c9d808b commit 5d9f39d

File tree

4 files changed

+166
-62
lines changed

4 files changed

+166
-62
lines changed

src/ArduinoIoTCloudTCP.cpp

+49-54
Original file line numberDiff line numberDiff line change
@@ -59,18 +59,11 @@ unsigned long getTime()
5959

6060
ArduinoIoTCloudTCP::ArduinoIoTCloudTCP()
6161
: _state{State::ConnectPhy}
62+
, _connection_attempt(0,0)
6263
, _tz_offset{0}
6364
, _tz_offset_property{nullptr}
6465
, _tz_dst_until{0}
6566
, _tz_dst_until_property{nullptr}
66-
, _next_connection_attempt_tick{0}
67-
, _last_connection_attempt_cnt{0}
68-
, _next_device_subscribe_attempt_tick{0}
69-
, _last_device_subscribe_cnt{0}
70-
, _next_thing_subscribe_attempt_tick{0}
71-
, _last_thing_subscribe_attempt_cnt{0}
72-
, _next_sync_attempt_tick{0}
73-
, _last_sync_attempt_cnt{0}
7467
, _mqtt_data_buf{0}
7568
, _mqtt_data_len{0}
7669
, _mqtt_data_request_retransmit{false}
@@ -113,7 +106,12 @@ int ArduinoIoTCloudTCP::begin(ConnectionHandler & connection, bool const enable_
113106
#else
114107
_brokerPort = brokerPort;
115108
#endif
109+
110+
/* Setup TimeService */
116111
_time_service.begin(&connection);
112+
113+
/* Setup retry timers */
114+
_connection_attempt.begin(AIOT_CONFIG_RECONNECTION_RETRY_DELAY_ms, AIOT_CONFIG_MAX_RECONNECTION_RETRY_DELAY_ms);
117115
return begin(enable_watchdog, _brokerAddress, _brokerPort);
118116
}
119117

@@ -132,15 +130,16 @@ int ArduinoIoTCloudTCP::begin(bool const enable_watchdog, String brokerAddress,
132130
if(!_password.length())
133131
{
134132
#endif
133+
135134
#if defined(BOARD_HAS_SECURE_ELEMENT)
136135
if (!_selement.begin())
137136
{
138137
DEBUG_ERROR("ArduinoIoTCloudTCP::%s could not initialize secure element.", __FUNCTION__);
139-
#if defined(ARDUINO_UNOWIFIR4)
138+
#if defined(ARDUINO_UNOWIFIR4)
140139
if (String(WiFi.firmwareVersion()) < String("0.4.1")) {
141140
DEBUG_ERROR("ArduinoIoTCloudTCP::%s In order to read device certificate, WiFi firmware needs to be >= 0.4.1, current %s", __FUNCTION__, WiFi.firmwareVersion());
142141
}
143-
#endif
142+
#endif
144143
return 0;
145144
}
146145
if (!SElementArduinoCloudDeviceId::read(_selement, getDeviceId(), SElementArduinoCloudSlot::DeviceId))
@@ -317,8 +316,7 @@ ArduinoIoTCloudTCP::State ArduinoIoTCloudTCP::handle_ConnectPhy()
317316
{
318317
if (_connection->check() == NetworkConnectionState::CONNECTED)
319318
{
320-
bool const is_retry_attempt = (_last_connection_attempt_cnt > 0);
321-
if (!is_retry_attempt || (is_retry_attempt && (millis() > _next_connection_attempt_tick)))
319+
if (!_connection_attempt.isRetry() || (_connection_attempt.isRetry() && _connection_attempt.isExpired()))
322320
return State::SyncTime;
323321
}
324322

@@ -339,18 +337,20 @@ ArduinoIoTCloudTCP::State ArduinoIoTCloudTCP::handle_ConnectMqttBroker()
339337
{
340338
if (_mqttClient.connect(_brokerAddress.c_str(), _brokerPort))
341339
{
342-
_last_connection_attempt_cnt = 0;
340+
DEBUG_VERBOSE("ArduinoIoTCloudTCP::%s connected to %s:%d", __FUNCTION__, _brokerAddress.c_str(), _brokerPort);
341+
/* Reconfigure timers for next state */
342+
_connection_attempt.begin(AIOT_CONFIG_DEVICE_TOPIC_SUBSCRIBE_RETRY_DELAY_ms, AIOT_CONFIG_MAX_DEVICE_TOPIC_SUBSCRIBE_RETRY_DELAY_ms);
343343
return State::SendDeviceProperties;
344344
}
345345

346346
/* Can't connect to the broker. Wait: 2s -> 4s -> 8s -> 16s -> 32s -> 32s ... */
347-
_last_connection_attempt_cnt++;
348-
unsigned long reconnection_retry_delay = (1 << _last_connection_attempt_cnt) * AIOT_CONFIG_RECONNECTION_RETRY_DELAY_ms;
349-
reconnection_retry_delay = min(reconnection_retry_delay, static_cast<unsigned long>(AIOT_CONFIG_MAX_RECONNECTION_RETRY_DELAY_ms));
350-
_next_connection_attempt_tick = millis() + reconnection_retry_delay;
347+
#pragma GCC diagnostic push
348+
#pragma GCC diagnostic ignored "-Wunused-variable"
349+
unsigned long const reconnection_retry_delay = _connection_attempt.retry();
350+
#pragma GCC diagnostic pop
351351

352352
DEBUG_ERROR("ArduinoIoTCloudTCP::%s could not connect to %s:%d", __FUNCTION__, _brokerAddress.c_str(), _brokerPort);
353-
DEBUG_VERBOSE("ArduinoIoTCloudTCP::%s %d next connection attempt in %d ms", __FUNCTION__, _last_connection_attempt_cnt, reconnection_retry_delay);
353+
DEBUG_VERBOSE("ArduinoIoTCloudTCP::%s %d next connection attempt in %d ms", __FUNCTION__, _connection_attempt.getRetryCount(), reconnection_retry_delay);
354354
/* Go back to ConnectPhy and retry to get time from network (invalid time for SSL handshake?)*/
355355
return State::ConnectPhy;
356356
}
@@ -375,11 +375,10 @@ ArduinoIoTCloudTCP::State ArduinoIoTCloudTCP::handle_SubscribeDeviceTopic()
375375
return State::Disconnect;
376376
}
377377

378-
bool const is_retry_attempt = (_last_device_subscribe_cnt > 0);
379-
if (is_retry_attempt && (millis() < _next_device_subscribe_attempt_tick))
378+
if (_connection_attempt.isRetry() && !_connection_attempt.isExpired())
380379
return State::SubscribeDeviceTopic;
381380

382-
if (is_retry_attempt)
381+
if (_connection_attempt.isRetry())
383382
{
384383
/* Configuration not received or device not attached to a valid thing. Try to resubscribe */
385384
DEBUG_ERROR("ArduinoIoTCloudTCP::%s device waiting for valid thing_id %d", __FUNCTION__, _time_service.getTime());
@@ -396,18 +395,17 @@ ArduinoIoTCloudTCP::State ArduinoIoTCloudTCP::handle_SubscribeDeviceTopic()
396395
}
397396

398397
/* Max retry than disconnect */
399-
if (_last_device_subscribe_cnt > AIOT_CONFIG_DEVICE_TOPIC_MAX_RETRY_CNT)
398+
if (_connection_attempt.getRetryCount() > AIOT_CONFIG_DEVICE_TOPIC_MAX_RETRY_CNT)
400399
{
401-
_last_device_subscribe_cnt = 0;
402400
return State::Disconnect;
403401
}
404402

405403
/* No device configuration received. Wait: 4s -> 8s -> 16s -> 32s -> 32s ...*/
406-
_last_device_subscribe_cnt++;
407-
unsigned long subscribe_retry_delay = (1 << _last_device_subscribe_cnt) * AIOT_CONFIG_DEVICE_TOPIC_SUBSCRIBE_RETRY_DELAY_ms;
408-
subscribe_retry_delay = min(subscribe_retry_delay, static_cast<unsigned long>(AIOT_CONFIG_MAX_DEVICE_TOPIC_SUBSCRIBE_RETRY_DELAY_ms));
409-
_next_device_subscribe_attempt_tick = millis() + subscribe_retry_delay;
410-
DEBUG_VERBOSE("ArduinoIoTCloudTCP::%s %d next configuration request in %d ms", __FUNCTION__, _last_device_subscribe_cnt, subscribe_retry_delay);
404+
#pragma GCC diagnostic push
405+
#pragma GCC diagnostic ignored "-Wunused-variable"
406+
unsigned long const subscribe_retry_delay = _connection_attempt.retry();
407+
#pragma GCC diagnostic pop
408+
DEBUG_VERBOSE("ArduinoIoTCloudTCP::%s %d next configuration request in %d ms", __FUNCTION__, _connection_attempt.getRetryCount(), subscribe_retry_delay);
411409

412410
return State::SubscribeDeviceTopic;
413411
}
@@ -426,18 +424,18 @@ ArduinoIoTCloudTCP::State ArduinoIoTCloudTCP::handle_CheckDeviceConfig()
426424
/* Device configuration received, but invalid thing_id. Do not increase counter, but recompute delay.
427425
* Device not attached. Wait: 40s -> 80s -> 160s -> 320s -> 640s -> 1280s -> 1280s ...
428426
*/
429-
unsigned long attach_retry_delay = (1 << _last_device_subscribe_cnt) * AIOT_CONFIG_DEVICE_TOPIC_ATTACH_RETRY_DELAY_ms;
430-
attach_retry_delay = min(attach_retry_delay, static_cast<unsigned long>(AIOT_CONFIG_MAX_DEVICE_TOPIC_ATTACH_RETRY_DELAY_ms));
431-
_next_device_subscribe_attempt_tick = millis() + attach_retry_delay;
432-
427+
#pragma GCC diagnostic push
428+
#pragma GCC diagnostic ignored "-Wunused-variable"
429+
unsigned long const attach_retry_delay = _connection_attempt.reconfigure(AIOT_CONFIG_DEVICE_TOPIC_ATTACH_RETRY_DELAY_ms, AIOT_CONFIG_MAX_DEVICE_TOPIC_ATTACH_RETRY_DELAY_ms);
430+
#pragma GCC diagnostic pop
433431
DEBUG_VERBOSE("ArduinoIoTCloudTCP::%s device not attached, next configuration request in %d ms", __FUNCTION__, attach_retry_delay);
434432
return State::SubscribeDeviceTopic;
435433
}
436434

437435
DEBUG_VERBOSE("ArduinoIoTCloudTCP::%s device attached to a new valid thing_id %s %d", __FUNCTION__, getThingId().c_str(), _time_service.getTime());
438436

439-
/* Received valid thing_id reset counters and go on */
440-
_last_device_subscribe_cnt = 0;
437+
/* Received valid thing_id, reconfigure timers for next state and go on */
438+
_connection_attempt.begin(AIOT_CONFIG_THING_TOPICS_SUBSCRIBE_RETRY_DELAY_ms);
441439

442440
return State::SubscribeThingTopics;
443441
}
@@ -449,18 +447,15 @@ ArduinoIoTCloudTCP::State ArduinoIoTCloudTCP::handle_SubscribeThingTopics()
449447
return State::Disconnect;
450448
}
451449

452-
bool const is_retry_attempt = (_last_thing_subscribe_attempt_cnt > 0);
453-
if (is_retry_attempt && (millis() < _next_thing_subscribe_attempt_tick))
450+
if (_connection_attempt.isRetry() && !_connection_attempt.isExpired())
454451
return State::SubscribeThingTopics;
455452

456-
if (_last_thing_subscribe_attempt_cnt > AIOT_CONFIG_THING_TOPICS_SUBSCRIBE_MAX_RETRY_CNT)
453+
if (_connection_attempt.getRetryCount() > AIOT_CONFIG_THING_TOPICS_SUBSCRIBE_MAX_RETRY_CNT)
457454
{
458-
_last_thing_subscribe_attempt_cnt = 0;
459455
return State::Disconnect;
460456
}
461457

462-
_next_thing_subscribe_attempt_tick = millis() + AIOT_CONFIG_THING_TOPICS_SUBSCRIBE_RETRY_DELAY_ms;
463-
_last_thing_subscribe_attempt_cnt++;
458+
_connection_attempt.retry();
464459

465460
if (!_mqttClient.subscribe(_dataTopicIn))
466461
{
@@ -480,6 +475,8 @@ ArduinoIoTCloudTCP::State ArduinoIoTCloudTCP::handle_SubscribeThingTopics()
480475
DEBUG_INFO("Thing ID: %s", getThingId().c_str());
481476
execCloudEventCallback(ArduinoIoTCloudEvent::CONNECT);
482477

478+
/* Successfully subscribed to thing topics, reconfigure timers for next state and go on */
479+
_connection_attempt.begin(AIOT_CONFIG_TIMEOUT_FOR_LASTVALUES_SYNC_ms);
483480
return State::RequestLastValues;
484481
}
485482

@@ -491,25 +488,21 @@ ArduinoIoTCloudTCP::State ArduinoIoTCloudTCP::handle_RequestLastValues()
491488
}
492489

493490
/* Check whether or not we need to send a new request. */
494-
bool const is_retry_attempt = (_last_sync_attempt_cnt > 0);
495-
if (is_retry_attempt && (millis() < _next_sync_attempt_tick))
491+
if (_connection_attempt.isRetry() && !_connection_attempt.isExpired())
496492
return State::RequestLastValues;
497493

498-
if (_last_sync_attempt_cnt > AIOT_CONFIG_LASTVALUES_SYNC_MAX_RETRY_CNT)
494+
/* Track the number of times a get-last-values request was sent to the cloud.
495+
* If no data is received within a certain number of retry-requests it's a better
496+
* strategy to disconnect and re-establish connection from the ground up.
497+
*/
498+
if (_connection_attempt.getRetryCount() > AIOT_CONFIG_LASTVALUES_SYNC_MAX_RETRY_CNT)
499499
{
500-
/* Track the number of times a get-last-values request was sent to the cloud.
501-
* If no data is received within a certain number of retry-requests it's a better
502-
* strategy to disconnect and re-establish connection from the ground up.
503-
*/
504-
_last_sync_attempt_cnt = 0;
505500
return State::Disconnect;
506501
}
507502

508-
DEBUG_VERBOSE("ArduinoIoTCloudTCP::%s [%d] last values requested", __FUNCTION__, _time_service.getTime());
503+
_connection_attempt.retry();
509504
requestLastValue();
510-
_next_sync_attempt_tick = millis() + AIOT_CONFIG_TIMEOUT_FOR_LASTVALUES_SYNC_ms;
511-
_last_sync_attempt_cnt++;
512-
505+
DEBUG_VERBOSE("ArduinoIoTCloudTCP::%s [%d] last values requested", __FUNCTION__, _time_service.getTime());
513506
return State::RequestLastValues;
514507
}
515508

@@ -608,10 +601,14 @@ ArduinoIoTCloudTCP::State ArduinoIoTCloudTCP::handle_Disconnect()
608601
} else {
609602
_mqttClient.unsubscribe(_shadowTopicIn);
610603
_mqttClient.unsubscribe(_dataTopicIn);
604+
/* TODO add device topic */
611605
_mqttClient.stop();
612606
}
613607
DEBUG_INFO("Disconnected from Arduino IoT Cloud");
614608
execCloudEventCallback(ArduinoIoTCloudEvent::DISCONNECT);
609+
610+
/* Setup timer for broker connection and restart */
611+
_connection_attempt.begin(AIOT_CONFIG_RECONNECTION_RETRY_DELAY_ms, AIOT_CONFIG_MAX_RECONNECTION_RETRY_DELAY_ms);
615612
return State::ConnectPhy;
616613
}
617614

@@ -633,7 +630,6 @@ void ArduinoIoTCloudTCP::handleMessage(int length)
633630
/* Topic for OTA properties and device configuration */
634631
if (_deviceTopicIn == topic) {
635632
CBORDecoder::decode(_device_property_container, (uint8_t*)bytes, length);
636-
_last_device_subscribe_cnt = 0;
637633
_state = State::CheckDeviceConfig;
638634
}
639635

@@ -649,7 +645,6 @@ void ArduinoIoTCloudTCP::handleMessage(int length)
649645
CBORDecoder::decode(_thing_property_container, (uint8_t*)bytes, length, true);
650646
_time_service.setTimeZoneData(_tz_offset, _tz_dst_until);
651647
execCloudEventCallback(ArduinoIoTCloudEvent::SYNC);
652-
_last_sync_attempt_cnt = 0;
653648
_state = State::Connected;
654649
}
655650
}

src/ArduinoIoTCloudTCP.h

+2-8
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include <AIoTC_Config.h>
2626
#include <ArduinoIoTCloud.h>
2727
#include <ArduinoMqttClient.h>
28+
#include <utility/time/TimedAttempt.h>
2829

2930
#if defined(BOARD_HAS_SECURE_ELEMENT)
3031
#include <Arduino_SecureElement.h>
@@ -121,20 +122,13 @@ class ArduinoIoTCloudTCP: public ArduinoIoTCloudClass
121122
};
122123

123124
State _state;
125+
TimedAttempt _connection_attempt;
124126

125127
int _tz_offset;
126128
Property * _tz_offset_property;
127129
unsigned int _tz_dst_until;
128130
Property * _tz_dst_until_property;
129131

130-
unsigned long _next_connection_attempt_tick;
131-
unsigned int _last_connection_attempt_cnt;
132-
unsigned long _next_device_subscribe_attempt_tick;
133-
unsigned int _last_device_subscribe_cnt;
134-
unsigned long _next_thing_subscribe_attempt_tick;
135-
unsigned int _last_thing_subscribe_attempt_cnt;
136-
unsigned long _next_sync_attempt_tick;
137-
unsigned int _last_sync_attempt_cnt;
138132
String _brokerAddress;
139133
uint16_t _brokerPort;
140134
uint8_t _mqtt_data_buf[MQTT_TRANSMIT_BUFFER_SIZE];

src/utility/time/TimedAttempt.cpp

+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/*
2+
This file is part of the Arduino_SecureElement library.
3+
4+
Copyright (c) 2024 Arduino SA
5+
6+
This Source Code Form is subject to the terms of the Mozilla Public
7+
License, v. 2.0. If a copy of the MPL was not distributed with this
8+
file, You can obtain one at http://mozilla.org/MPL/2.0/.
9+
*/
10+
11+
/******************************************************************************
12+
* INCLUDE
13+
******************************************************************************/
14+
15+
#include <Arduino.h>
16+
#include "TimedAttempt.h"
17+
18+
/******************************************************************************
19+
* CTOR/DTOR
20+
******************************************************************************/
21+
22+
TimedAttempt::TimedAttempt(unsigned long minDelay, unsigned long maxDelay)
23+
: _minDelay(minDelay)
24+
, _maxDelay(maxDelay) {
25+
}
26+
27+
/******************************************************************************
28+
* PUBLIC MEMBER FUNCTIONS
29+
******************************************************************************/
30+
31+
void TimedAttempt::begin(unsigned long delay) {
32+
_retryCount = 0;
33+
_minDelay = delay;
34+
_maxDelay = delay;
35+
}
36+
37+
void TimedAttempt::begin(unsigned long minDelay, unsigned long maxDelay) {
38+
_retryCount = 0;
39+
_minDelay = minDelay;
40+
_maxDelay = maxDelay;
41+
}
42+
43+
unsigned long TimedAttempt::reconfigure(unsigned long minDelay, unsigned long maxDelay) {
44+
_minDelay = minDelay;
45+
_maxDelay = maxDelay;
46+
return reload();
47+
}
48+
49+
unsigned long TimedAttempt::retry() {
50+
_retryCount++;
51+
return reload();
52+
}
53+
54+
unsigned long TimedAttempt::reload() {
55+
unsigned long retryDelay = (1 << _retryCount) * _minDelay;
56+
retryDelay = min(retryDelay, _maxDelay);
57+
_nextRetryTick = millis() + retryDelay;
58+
return retryDelay;
59+
}
60+
61+
void TimedAttempt::reset() {
62+
_retryCount = 0;
63+
}
64+
65+
bool TimedAttempt::isRetry() {
66+
return _retryCount > 0;
67+
}
68+
69+
bool TimedAttempt::isExpired() {
70+
return millis() > _nextRetryTick;
71+
}
72+
73+
unsigned int TimedAttempt::getRetryCount() {
74+
return _retryCount;
75+
}

src/utility/time/TimedAttempt.h

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
This file is part of the Arduino_SecureElement library.
3+
4+
Copyright (c) 2024 Arduino SA
5+
6+
This Source Code Form is subject to the terms of the Mozilla Public
7+
License, v. 2.0. If a copy of the MPL was not distributed with this
8+
file, You can obtain one at http://mozilla.org/MPL/2.0/.
9+
*/
10+
11+
#ifndef TIMED_ATTEMPT_H
12+
#define TIMED_ATTEMPT_H
13+
14+
/******************************************************************************
15+
* CLASS DECLARATION
16+
******************************************************************************/
17+
18+
class TimedAttempt {
19+
20+
public:
21+
TimedAttempt(unsigned long minDelay, unsigned long maxDelay);
22+
23+
void begin(unsigned long delay);
24+
void begin(unsigned long minDelay, unsigned long maxDelay);
25+
unsigned long reconfigure(unsigned long minDelay, unsigned long maxDelay);
26+
unsigned long retry();
27+
unsigned long reload();
28+
void reset();
29+
bool isRetry();
30+
bool isExpired();
31+
unsigned int getRetryCount();
32+
33+
private:
34+
unsigned long _minDelay;
35+
unsigned long _maxDelay;
36+
unsigned long _nextRetryTick;
37+
unsigned int _retryCount;
38+
};
39+
40+
#endif /* TIMED_ATTEMPT_H */

0 commit comments

Comments
 (0)