Skip to content

Commit 25be97a

Browse files
authored
Merge pull request #36 from ilcato/callback-synchronization
Implementation of the Shadow Thing mechanism on the device side
2 parents dfc1877 + 5a2cf5c commit 25be97a

File tree

2 files changed

+115
-12
lines changed

2 files changed

+115
-12
lines changed

src/ArduinoIoTCloud.cpp

+88-4
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@
2020
#include "CloudSerial.h"
2121
#include "ArduinoIoTCloud.h"
2222

23+
#ifdef ARDUINO_ARCH_SAMD
24+
#include <RTCZero.h>
25+
RTCZero rtc;
26+
#endif
27+
2328
const static int keySlot = 0;
2429
const static int compressedCertSlot = 10;
2530
const static int serialNumberAndAuthorityKeyIdentifierSlot = 11;
@@ -37,11 +42,21 @@ static unsigned long getTime() {
3742
return time;
3843
}
3944

45+
static unsigned long getTimestamp() {
46+
#ifdef ARDUINO_ARCH_SAMD
47+
return rtc.getEpoch();
48+
#else
49+
#warning "No RTC available on this architecture - ArduinoIoTCloud will not keep track of local change timestamps ."
50+
return 0;
51+
#endif
52+
}
53+
4054
ArduinoIoTCloudClass::ArduinoIoTCloudClass() :
4155
_thing_id (""),
4256
_bearSslClient(NULL),
4357
_mqttClient (NULL),
44-
connection (NULL)
58+
connection (NULL),
59+
_lastSyncRequestTickTime(0)
4560
{
4661
}
4762

@@ -64,6 +79,9 @@ int ArduinoIoTCloudClass::begin(ConnectionManager *c, String brokerAddress, uint
6479
Client &connectionClient = c->getClient();
6580
_brokerAddress = brokerAddress;
6681
_brokerPort = brokerPort;
82+
#ifdef ARDUINO_ARCH_SAMD
83+
rtc.begin();
84+
#endif
6785
return begin(connectionClient, _brokerAddress, _brokerPort);
6886
}
6987

@@ -148,6 +166,8 @@ void ArduinoIoTCloudClass::mqttClientBegin()
148166
else {
149167
_dataTopicIn = "/a/t/" + _thing_id + "/e/i";
150168
_dataTopicOut = "/a/t/" + _thing_id + "/e/o";
169+
_shadowTopicIn = "/a/t/" + _thing_id + "/shadow/i";
170+
_shadowTopicOut = "/a/t/" + _thing_id + "/shadow/o";
151171
}
152172

153173
// use onMessage as callback for received mqtt messages
@@ -166,6 +186,10 @@ int ArduinoIoTCloudClass::connect()
166186
}
167187
_mqttClient->subscribe(_stdinTopic);
168188
_mqttClient->subscribe(_dataTopicIn);
189+
_mqttClient->subscribe(_shadowTopicIn);
190+
191+
_syncStatus = ArduinoIoTSynchronizationStatus::SYNC_STATUS_WAIT_FOR_CLOUD_VALUES;
192+
_lastSyncRequestTickTime = 0;
169193

170194
return 1;
171195
}
@@ -182,14 +206,18 @@ void ArduinoIoTCloudClass::poll()
182206
update();
183207
}
184208

185-
void ArduinoIoTCloudClass::update()
209+
void ArduinoIoTCloudClass::update(CallbackFunc onSyncCompleteCallback)
186210
{
187211
// If user call update() without parameters use the default ones
188-
update(MAX_RETRIES, RECONNECTION_TIMEOUT);
212+
update(MAX_RETRIES, RECONNECTION_TIMEOUT, onSyncCompleteCallback);
189213
}
190214

191-
void ArduinoIoTCloudClass::update(int const reconnectionMaxRetries, int const reconnectionTimeoutMs)
215+
void ArduinoIoTCloudClass::update(int const reconnectionMaxRetries, int const reconnectionTimeoutMs, CallbackFunc onSyncCompleteCallback)
192216
{
217+
unsigned long const timestamp = getTimestamp();
218+
//check if a property is changed
219+
if(timestamp != 0) Thing.updateTimestampOnChangedProperties(timestamp);
220+
193221
connectionCheck();
194222
if(iotStatus != IOT_STATUS_CLOUD_CONNECTED){
195223
return;
@@ -198,6 +226,26 @@ void ArduinoIoTCloudClass::update(int const reconnectionMaxRetries, int const re
198226
// MTTQClient connected!, poll() used to retrieve data from MQTT broker
199227
_mqttClient->poll();
200228

229+
switch (_syncStatus) {
230+
case ArduinoIoTSynchronizationStatus::SYNC_STATUS_SYNCHRONIZED:
231+
sendPropertiesToCloud();
232+
break;
233+
case ArduinoIoTSynchronizationStatus::SYNC_STATUS_WAIT_FOR_CLOUD_VALUES:
234+
if (millis() - _lastSyncRequestTickTime > TIMEOUT_FOR_LASTVALUES_SYNC) {
235+
requestLastValue();
236+
_lastSyncRequestTickTime = millis();
237+
}
238+
break;
239+
case ArduinoIoTSynchronizationStatus::SYNC_STATUS_VALUES_PROCESSED:
240+
if(onSyncCompleteCallback != NULL)
241+
(*onSyncCompleteCallback)();
242+
_syncStatus = ArduinoIoTSynchronizationStatus::SYNC_STATUS_SYNCHRONIZED;
243+
break;
244+
}
245+
}
246+
247+
void ArduinoIoTCloudClass::sendPropertiesToCloud()
248+
{
201249
uint8_t data[MQTT_TRANSMIT_BUFFER_SIZE];
202250
int const length = Thing.encode(data, sizeof(data));
203251
if (length > 0) {
@@ -254,6 +302,23 @@ int ArduinoIoTCloudClass::writeStdout(const byte data[], int length)
254302
return 1;
255303
}
256304

305+
int ArduinoIoTCloudClass::writeShadowOut(const byte data[], int length)
306+
{
307+
if (!_mqttClient->beginMessage(_shadowTopicOut, length, false, 0)) {
308+
return 0;
309+
}
310+
311+
if (!_mqttClient->write(data, length)) {
312+
return 0;
313+
}
314+
315+
if (!_mqttClient->endMessage()) {
316+
return 0;
317+
}
318+
319+
return 1;
320+
}
321+
257322
void ArduinoIoTCloudClass::onMessage(int length)
258323
{
259324
ArduinoCloud.handleMessage(length);
@@ -276,6 +341,20 @@ void ArduinoIoTCloudClass::handleMessage(int length)
276341
if (_dataTopicIn == topic) {
277342
Thing.decode((uint8_t*)bytes, length);
278343
}
344+
if ((_shadowTopicIn == topic) && _syncStatus == ArduinoIoTSynchronizationStatus::SYNC_STATUS_WAIT_FOR_CLOUD_VALUES) {
345+
Thing.decode((uint8_t*)bytes, length, true);
346+
sendPropertiesToCloud();
347+
_syncStatus = ArduinoIoTSynchronizationStatus::SYNC_STATUS_VALUES_PROCESSED;
348+
}
349+
}
350+
351+
void ArduinoIoTCloudClass::requestLastValue()
352+
{
353+
// Send the getLastValues CBOR message to the cloud
354+
// [{0: "r:m", 3: "getLastValues"}] = 81 A2 00 63 72 3A 6D 03 6D 67 65 74 4C 61 73 74 56 61 6C 75 65 73
355+
// Use http://cbor.me to easily generate CBOR encoding
356+
const uint8_t CBOR_REQUEST_LAST_VALUE_MSG[] = { 0x81, 0xA2, 0x00, 0x63, 0x72, 0x3A, 0x6D, 0x03, 0x6D, 0x67, 0x65, 0x74, 0x4C, 0x61, 0x73, 0x74, 0x56, 0x61, 0x6C, 0x75, 0x65, 0x73 };
357+
writeShadowOut(CBOR_REQUEST_LAST_VALUE_MSG, sizeof(CBOR_REQUEST_LAST_VALUE_MSG));
279358
}
280359

281360
void ArduinoIoTCloudClass::connectionCheck()
@@ -332,6 +411,11 @@ void ArduinoIoTCloudClass::connectionCheck()
332411
CloudSerial.begin(9600);
333412
CloudSerial.println("Hello from Cloud Serial!");
334413
}
414+
#ifdef ARDUINO_ARCH_SAMD
415+
unsigned long const epoch = getTime();
416+
if (epoch!=0)
417+
rtc.setEpoch(epoch);
418+
#endif
335419
break;
336420
}
337421
}

src/ArduinoIoTCloud.h

+27-8
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ typedef struct {
4040
int timeout;
4141
} mqttConnectionOptions;
4242

43+
typedef void (*CallbackFunc)(void);
44+
4345
extern ConnectionManager *ArduinoIoTPreferredConnection;
4446

4547
enum ArduinoIoTConnectionStatus {
@@ -51,6 +53,12 @@ enum ArduinoIoTConnectionStatus {
5153
IOT_STATUS_CLOUD_ERROR,
5254
};
5355

56+
enum class ArduinoIoTSynchronizationStatus {
57+
SYNC_STATUS_SYNCHRONIZED,
58+
SYNC_STATUS_WAIT_FOR_CLOUD_VALUES,
59+
SYNC_STATUS_VALUES_PROCESSED
60+
};
61+
5462
class ArduinoIoTCloudClass {
5563

5664
public:
@@ -63,18 +71,18 @@ class ArduinoIoTCloudClass {
6371
static const int MQTT_TRANSMIT_BUFFER_SIZE = 256;
6472
static const int MAX_RETRIES = 5;
6573
static const int RECONNECTION_TIMEOUT = 2000;
66-
74+
static const int TIMEOUT_FOR_LASTVALUES_SYNC = 10000;
6775

6876
void onGetTime(unsigned long(*callback)(void));
6977

7078
int connect ();
7179
bool disconnect();
7280

7381
void poll() __attribute__((deprecated)); /* Attention: Function is deprecated - use 'update' instead */
74-
void update();
82+
void update(CallbackFunc onSyncCompleteCallback = NULL);
7583

7684
// defined for users who want to specify max reconnections reties and timeout between them
77-
void update(int const reconnectionMaxRetries, int const reconnectionTimeoutMs);
85+
void update(int const reconnectionMaxRetries, int const reconnectionTimeoutMs, CallbackFunc onSyncCompleteCallback = NULL);
7886

7987
int connected();
8088
// Clean up existing Mqtt connection, create a new one and initialize it
@@ -92,17 +100,16 @@ class ArduinoIoTCloudClass {
92100

93101

94102
template<typename T, typename N=T>
95-
void addPropertyReal(T & property, String name, permissionType permission_type = READWRITE, long seconds = ON_CHANGE, void(*fn)(void) = NULL, N minDelta = N(0)) {
103+
void addPropertyReal(T & property, String name, permissionType permission_type = READWRITE, long seconds = ON_CHANGE, void(*fn)(void) = NULL, void(*synFn)(ArduinoCloudProperty<T> property) = CLOUD_WINS, N minDelta = N(0)) {
96104
Permission permission = Permission::ReadWrite;
97105
if (permission_type == READ ) permission = Permission::Read;
98106
else if(permission_type == WRITE) permission = Permission::Write;
99107
else permission = Permission::ReadWrite;
100108

101109
if(seconds == ON_CHANGE) {
102-
Thing.addPropertyReal(property, name, permission).publishOnChange((T)minDelta, DEFAULT_MIN_TIME_BETWEEN_UPDATES_MILLIS).onUpdate(fn);
103-
}
104-
else {
105-
Thing.addPropertyReal(property, name, permission).publishEvery(seconds).onUpdate(fn);
110+
Thing.addPropertyReal(property, name, permission).publishOnChange((T)minDelta, DEFAULT_MIN_TIME_BETWEEN_UPDATES_MILLIS).onUpdate(fn).onSync(synFn);
111+
} else {
112+
Thing.addPropertyReal(property, name, permission).publishEvery(seconds).onUpdate(fn).onSync(synFn);
106113
}
107114
}
108115

@@ -120,10 +127,14 @@ class ArduinoIoTCloudClass {
120127
friend class CloudSerialClass;
121128
int writeStdout(const byte data[], int length);
122129
int writeProperties(const byte data[], int length);
130+
int writeShadowOut(const byte data[], int length);
131+
123132
// Used to initialize MQTTClient
124133
void mqttClientBegin();
125134
// Function in charge of perform MQTT reconnection, basing on class parameters(retries,and timeout)
126135
bool mqttReconnect(int const maxRetries, int const timeout);
136+
// Used to retrieve last values from _shadowTopicIn
137+
void requestLastValue();
127138

128139
ArduinoIoTConnectionStatus getIoTStatus() { return iotStatus; }
129140
void setIoTConnectionState(ArduinoIoTConnectionStatus _newState);
@@ -132,6 +143,10 @@ class ArduinoIoTCloudClass {
132143
ConnectionManager *connection;
133144
static void onMessage(int length);
134145
void handleMessage(int length);
146+
ArduinoIoTSynchronizationStatus _syncStatus = ArduinoIoTSynchronizationStatus::SYNC_STATUS_SYNCHRONIZED;
147+
148+
void sendPropertiesToCloud();
149+
135150

136151
String _id,
137152
_thing_id,
@@ -140,10 +155,14 @@ class ArduinoIoTCloudClass {
140155
ArduinoCloudThing Thing;
141156
BearSSLClient* _bearSslClient;
142157
MqttClient* _mqttClient;
158+
int _lastSyncRequestTickTime;
159+
143160

144161
// Class attribute to define MTTQ topics 2 for stdIn/out and 2 for data, in order to avoid getting previous pupblished payload
145162
String _stdinTopic;
146163
String _stdoutTopic;
164+
String _shadowTopicOut;
165+
String _shadowTopicIn;
147166
String _dataTopicOut;
148167
String _dataTopicIn;
149168
String _otaTopic;

0 commit comments

Comments
 (0)