-
Notifications
You must be signed in to change notification settings - Fork 135
Expand file tree
/
Copy pathThingsBoard.h
More file actions
2356 lines (2098 loc) · 110 KB
/
Copy pathThingsBoard.h
File metadata and controls
2356 lines (2098 loc) · 110 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
ThingsBoard.h - Library API for sending data to the ThingsBoard
Based on PubSub MQTT library.
Created by Olender M. Oct 2018.
Released into the public domain.
*/
#ifndef ThingsBoard_h
#define ThingsBoard_h
// Library includes.
#include <stdarg.h>
#include <PubSubClient.h>
#if defined(ESP8266)
#include <Updater.h>
#elif defined(ESP32)
#include <Update.h>
#endif
// Definition for not defined function (RP2040)
#ifndef snprintf_P
#define snprintf_P snprintf
#endif // snprintf_P
#ifndef vsnprintf_P
#define vsnprintf_P vsnprintf
#endif // vsnprintf_P
// Local includes.
#include "Configuration.h"
#include "ThingsBoardDefaultLogger.h"
#include "HashGenerator.h"
#include "Telemetry.h"
#include "Constants.h"
#include "Shared_Attribute_Callback.h"
#include "Attribute_Request_Callback.h"
#include "RPC_Callback.h"
#include "RPC_Request_Callback.h"
#include "Provision_Callback.h"
#include "OTA_Update_Callback.h"
/// ---------------------------------
/// Constant strings in flash memory.
/// ---------------------------------
// Publish data topics.
#if THINGSBOARD_ENABLE_PROGMEM
constexpr char ATTRIBUTE_TOPIC[] PROGMEM = "v1/devices/me/attributes";
constexpr char TELEMETRY_TOPIC[] PROGMEM = "v1/devices/me/telemetry";
#else
constexpr char ATTRIBUTE_TOPIC[] = "v1/devices/me/attributes";
constexpr char TELEMETRY_TOPIC[] = "v1/devices/me/telemetry";
#endif // THINGSBOARD_ENABLE_PROGMEM
// RPC topics.
#if THINGSBOARD_ENABLE_PROGMEM
constexpr char RPC_SUBSCRIBE_TOPIC[] PROGMEM = "v1/devices/me/rpc/request/+";
constexpr char RPC_RESPONSE_SUBSCRIBE_TOPIC[] PROGMEM = "v1/devices/me/rpc/response/+";
constexpr char RPC_SEND_REQUEST_TOPIC[] PROGMEM = "v1/devices/me/rpc/request/%u";
constexpr char RPC_REQUEST_TOPIC[] PROGMEM = "v1/devices/me/rpc/request";
constexpr char RPC_RESPONSE_TOPIC[] PROGMEM = "v1/devices/me/rpc/response";
constexpr char RPC_SEND_RESPONSE_TOPIC[] = "v1/devices/me/rpc/response/%u";
#else
constexpr char RPC_SUBSCRIBE_TOPIC[] = "v1/devices/me/rpc/request/+";
constexpr char RPC_RESPONSE_SUBSCRIBE_TOPIC[] = "v1/devices/me/rpc/response/+";
constexpr char RPC_SEND_REQUEST_TOPIC[] = "v1/devices/me/rpc/request/%u";
constexpr char RPC_REQUEST_TOPIC[] = "v1/devices/me/rpc/request";
constexpr char RPC_RESPONSE_TOPIC[] = "v1/devices/me/rpc/response";
constexpr char RPC_SEND_RESPONSE_TOPIC[] = "v1/devices/me/rpc/response/%u";
#endif // THINGSBOARD_ENABLE_PROGMEM
// Firmware topics.
#if THINGSBOARD_ENABLE_PROGMEM
constexpr char FIRMWARE_RESPONSE_TOPIC[] PROGMEM = "v2/fw/response";
#else
constexpr char FIRMWARE_RESPONSE_TOPIC[] = "v2/fw/response";
#endif // THINGSBOARD_ENABLE_PROGMEM
// Shared attribute topics.
#if THINGSBOARD_ENABLE_PROGMEM
constexpr char ATTRIBUTE_REQUEST_TOPIC[] PROGMEM = "v1/devices/me/attributes/request/%u";
constexpr char ATTRIBUTE_RESPONSE_SUBSCRIBE_TOPIC[] PROGMEM = "v1/devices/me/attributes/response/+";
constexpr char ATTRIBUTE_RESPONSE_TOPIC[] PROGMEM = "v1/devices/me/attributes/response";
#else
constexpr char ATTRIBUTE_REQUEST_TOPIC[] = "v1/devices/me/attributes/request/%u";
constexpr char ATTRIBUTE_RESPONSE_SUBSCRIBE_TOPIC[] = "v1/devices/me/attributes/response/+";
constexpr char ATTRIBUTE_RESPONSE_TOPIC[] = "v1/devices/me/attributes/response";
#endif // THINGSBOARD_ENABLE_PROGMEM
// Provision topics.
#if THINGSBOARD_ENABLE_PROGMEM
constexpr char PROV_RESPONSE_TOPIC[] PROGMEM = "/provision/response";
#else
constexpr char PROV_RESPONSE_TOPIC[] = "/provision/response";
#endif // THINGSBOARD_ENABLE_PROGMEM
// Default login data.
#if THINGSBOARD_ENABLE_PROGMEM
constexpr char PROV_ACCESS_TOKEN[] PROGMEM = "provision";
constexpr char DEFAULT_CLIENT_ID[] PROGMEM = "TbDev";
#else
constexpr char PROV_ACCESS_TOKEN[] = "provision";
constexpr char DEFAULT_CLIENT_ID[] = "TbDev";
#endif // THINGSBOARD_ENABLE_PROGMEM
// Shared attribute request keys.
#if THINGSBOARD_ENABLE_PROGMEM
constexpr char SHARED_REQUEST_KEY[] PROGMEM = "sharedKeys";
constexpr char SHARED_RESPONSE_KEY[] PROGMEM = "shared";
#else
constexpr char SHARED_REQUEST_KEY[] = "sharedKeys";
constexpr char SHARED_RESPONSE_KEY[] = "shared";
#endif // THINGSBOARD_ENABLE_PROGMEM
// Client side attribute request keys.
#if THINGSBOARD_ENABLE_PROGMEM
constexpr char CLIENT_REQUEST_KEYS[] PROGMEM = "clientKeys";
constexpr char CLIENT_RESPONSE_KEY[] PROGMEM = "client";
#else
constexpr char CLIENT_REQUEST_KEYS[] = "clientKeys";
constexpr char CLIENT_RESPONSE_KEY[] = "client";
#endif // THINGSBOARD_ENABLE_PROGMEM
// RPC data keys.
#if THINGSBOARD_ENABLE_PROGMEM
constexpr char RPC_METHOD_KEY[] PROGMEM = "method";
constexpr char RPC_PARAMS_KEY[] PROGMEM = "params";
constexpr char RPC_EMPTY_PARAMS_VALUE[] PROGMEM = "{}";
#else
constexpr char RPC_METHOD_KEY[] = "method";
constexpr char RPC_PARAMS_KEY[] = "params";
constexpr char RPC_EMPTY_PARAMS_VALUE[] = "{}";
#endif // THINGSBOARD_ENABLE_PROGMEM
// Log messages.
#if THINGSBOARD_ENABLE_PROGMEM
constexpr char INVALID_BUFFER_SIZE[] PROGMEM = "Buffer size (%u) to small for the given payloads size (%u), increase with setBufferSize accordingly";
constexpr char NUMBER_PRINTF[] PROGMEM = "%u";
constexpr char MAX_RPC_EXCEEDED[] PROGMEM = "Too many server-side RPC subscriptions, increase MaxFieldsAmt or unsubscribe";
constexpr char MAX_RPC_REQUEST_EXCEEDED[] PROGMEM = "Too many client-side RPC subscriptions, increase MaxFieldsAmt or unsubscribe";
constexpr char MAX_SHARED_ATT_UPDATE_EXCEEDED[] PROGMEM = "Too many shared attribute update callback subscriptions, increase MaxFieldsAmt or unsubscribe";
constexpr char MAX_SHARED_ATT_REQUEST_EXCEEDED[] PROGMEM = "Too many shared attribute request callback subscriptions, increase MaxFieldsAmt";
constexpr char COMMA PROGMEM = ',';
constexpr char NO_KEYS_TO_REQUEST[] PROGMEM = "No keys to request were given";
constexpr char REQUEST_RPC[] PROGMEM = "Requesting client side RPC with the json (%s)";
constexpr char REQUEST_ATT[] PROGMEM = "Requesting shared attributes transformed from (%s) into json (%s)";
constexpr char RECEIVED_RPC_LOG_MESSAGE[] PROGMEM = "Received RPC:";
constexpr char RPC_METHOD_NULL[] PROGMEM = "RPC methodName is NULL";
constexpr char NO_RPC_PARAMS_PASSED[] PROGMEM = "No parameters passed with RPC, passing null JSON";
constexpr char CALLING_RPC[] PROGMEM = "Calling RPC:";
constexpr char RECEIVED_ATT_UPDATE[] PROGMEM = "Received shared attribute update";
constexpr char NOT_FOUND_ATT_UPDATE[] PROGMEM = "Shared attribute update key not found";
constexpr char ATT_CB_NO_KEYS[] PROGMEM = "No keys subscribed. Calling subscribed callback for any updated attributes (assumed to be subscribed to every possible key)";
constexpr char ATT_IS_NULL[] PROGMEM = "Subscribed shared attribute update key is NULL";
constexpr char ATT_IN_ARRAY[] PROGMEM = "Shared attribute update key: (%s) is subscribed";
constexpr char ATT_NO_CHANGE[] PROGMEM = "No keys that we subscribed too were changed, skipping callback";
constexpr char CALLING_ATT_CB[] PROGMEM = "Calling subscribed callback for updated shared attribute (%s)";
constexpr char RECEIVED_ATT[] PROGMEM = "Received shared attribute request";
constexpr char ATT_KEY_NOT_FOUND[] PROGMEM = "Attribute key not found";
constexpr char CALLING_REQUEST_CB[] PROGMEM = "Calling subscribed callback for response id (%u)";
constexpr char CB_ON_MESSAGE[] PROGMEM = "Callback onMQTTMessage from topic: (%s)";
constexpr char SUBSCRIBE_TOPIC_FAILED[] PROGMEM = "Subscribing the given topic failed";
constexpr char PROV_REQUEST[] PROGMEM = "Provision request:";
constexpr char PROV_RESPONSE[] PROGMEM = "Process provisioning response";
constexpr char RECEIVED_PROV_RESPONSE[] PROGMEM = "Received provision response";
#else
constexpr char INVALID_BUFFER_SIZE[] = "Buffer size (%u) to small for the given payloads size (%u), increase with setBufferSize accordingly";
constexpr char NUMBER_PRINTF[] = "%u";
constexpr char MAX_RPC_EXCEEDED[] = "Too many server-side RPC subscriptions, increase MaxFieldsAmt or unsubscribe";
constexpr char MAX_RPC_REQUEST_EXCEEDED[] = "Too many client-side RPC subscriptions, increase MaxFieldsAmt or unsubscribe";
constexpr char MAX_SHARED_ATT_UPDATE_EXCEEDED[] = "Too many shared attribute update callback subscriptions, increase MaxFieldsAmt or unsubscribe";
constexpr char MAX_SHARED_ATT_REQUEST_EXCEEDED[] = "Too many shared attribute request callback subscriptions, increase MaxFieldsAmt";
constexpr char COMMA = ',';
constexpr char NO_KEYS_TO_REQUEST[] = "No keys to request were given";
constexpr char REQUEST_RPC[] = "Requesting client side RPC with the json (%s)";
constexpr char REQUEST_ATT[] = "Requesting shared attributes transformed from (%s) into json (%s)";
constexpr char RECEIVED_RPC_LOG_MESSAGE[] = "Received RPC:";
constexpr char RPC_METHOD_NULL[] = "RPC methodName is NULL";
constexpr char NO_RPC_PARAMS_PASSED[] = "No parameters passed with RPC, passing null JSON";
constexpr char CALLING_RPC[] = "Calling RPC:";
constexpr char RECEIVED_ATT_UPDATE[] = "Received shared attribute update";
constexpr char NOT_FOUND_ATT_UPDATE[] = "Shared attribute update key not found";
constexpr char ATT_CB_NO_KEYS[] = "No keys subscribed. Calling subscribed callback for any updated attributes (assumed to be subscribed to every possible key)";
constexpr char ATT_IS_NULL[] = "Subscribed shared attribute update key is NULL";
constexpr char ATT_IN_ARRAY[] = "Shared attribute update key: (%s) is subscribed";
constexpr char ATT_NO_CHANGE[] = "No keys that we subscribed too were changed, skipping callback";
constexpr char CALLING_ATT_CB[] = "Calling subscribed callback for updated shared attribute (%s)";
constexpr char RECEIVED_ATT[] = "Received shared attribute request";
constexpr char ATT_KEY_NOT_FOUND[] = "Attribute key not found";
constexpr char CALLING_REQUEST_CB[] = "Calling subscribed callback for response id (%u)";
constexpr char CB_ON_MESSAGE[] = "Callback onMQTTMessage from topic: (%s)";
constexpr char SUBSCRIBE_TOPIC_FAILED[] = "Subscribing the given topic failed";
constexpr char PROV_REQUEST[] = "Provision request:";
constexpr char PROV_RESPONSE[] = "Process provisioning response";
constexpr char RECEIVED_PROV_RESPONSE[] = "Received provision response";
#endif // THINGSBOARD_ENABLE_PROGMEM
// Claim topics.
#if THINGSBOARD_ENABLE_PROGMEM
constexpr char CLAIM_TOPIC[] PROGMEM = "v1/devices/me/claim";
#else
constexpr char CLAIM_TOPIC[] = "v1/devices/me/claim";
#endif // THINGSBOARD_ENABLE_PROGMEM
// Provision topics.
#if THINGSBOARD_ENABLE_PROGMEM
constexpr char PROV_REQUEST_TOPIC[] PROGMEM = "/provision/request";
#else
constexpr char PROV_REQUEST_TOPIC[] = "/provision/request";
#endif // THINGSBOARD_ENABLE_PROGMEM
// Claim data keys.
#if THINGSBOARD_ENABLE_PROGMEM
constexpr char SECRET_KEY[] PROGMEM = "secretKey";
constexpr char DURATION_KEY[] PROGMEM = "durationMs";
constexpr char DEVICE_NAME_KEY[] PROGMEM = "deviceName";
constexpr char PROV_DEVICE_KEY[] PROGMEM = "provisionDeviceKey";
constexpr char PROV_DEVICE_SECRET_KEY[] PROGMEM = "provisionDeviceSecret";
constexpr char PROV_CRED_TYPE_KEY[] PROGMEM = "credentialsType";
constexpr char PROV_TOKEN[] PROGMEM = "token";
constexpr char PROV_CRED_USERNAME[] PROGMEM = "username";
constexpr char PROV_CRED_PASSWORD[] PROGMEM = "password";
constexpr char PROV_CRED_CLIENT_ID[] PROGMEM = "clientId";
constexpr char PROV_CRED_HASH[] PROGMEM = "hash";
#else
constexpr char SECRET_KEY[] = "secretKey";
constexpr char DURATION_KEY[] = "durationMs";
constexpr char DEVICE_NAME_KEY[] = "deviceName";
constexpr char PROV_DEVICE_KEY[] = "provisionDeviceKey";
constexpr char PROV_DEVICE_SECRET_KEY[] = "provisionDeviceSecret";
constexpr char PROV_CRED_TYPE_KEY[] = "credentialsType";
constexpr char PROV_TOKEN[] = "token";
constexpr char PROV_CRED_USERNAME[] = "username";
constexpr char PROV_CRED_PASSWORD[] = "password";
constexpr char PROV_CRED_CLIENT_ID[] = "clientId";
constexpr char PROV_CRED_HASH[] = "hash";
#endif // THINGSBOARD_ENABLE_PROGMEM
#if defined(ESP8266) || defined(ESP32)
// Firmware topics.
#if THINGSBOARD_ENABLE_PROGMEM
constexpr char FIRMWARE_RESPONSE_SUBSCRIBE_TOPIC[] PROGMEM = "v2/fw/response/#";
constexpr char FIRMWARE_REQUEST_TOPIC[] PROGMEM = "v2/fw/request/0/chunk/%u";
#else
constexpr char FIRMWARE_RESPONSE_SUBSCRIBE_TOPIC[] = "v2/fw/response/#";
constexpr char FIRMWARE_REQUEST_TOPIC[] = "v2/fw/request/0/chunk/%u";
#endif // THINGSBOARD_ENABLE_PROGMEM
// Firmware data keys.
#if THINGSBOARD_ENABLE_PROGMEM
constexpr char CURR_FW_TITLE_KEY[] PROGMEM = "current_fw_title";
constexpr char CURR_FW_VER_KEY[] PROGMEM = "current_fw_version";
constexpr char FW_ERROR_KEY[] PROGMEM = "fw_error";
constexpr char FW_STATE_KEY[] PROGMEM = "fw_state";
constexpr char FW_STATE_DOWNLOADING[] PROGMEM = "DOWNLOADING";
constexpr char FW_STATE_DOWNLOADED[] PROGMEM = "DOWNLOADED";
constexpr char FW_STATE_VERIFIED[] PROGMEM = "VERIFIED";
constexpr char FW_STATE_UPDATING[] PROGMEM = "UPDATING";
constexpr char FW_STATE_FAILED[] PROGMEM = "FAILED";
constexpr char FW_VER_KEY[] PROGMEM = "fw_version";
constexpr char FW_TITLE_KEY[] PROGMEM = "fw_title";
constexpr char FW_CHKS_KEY[] PROGMEM = "fw_checksum";
constexpr char FW_CHKS_ALGO_KEY[] PROGMEM = "fw_checksum_algorithm";
constexpr char FW_SIZE_KEY[] PROGMEM = "fw_size";
constexpr char CHECKSUM_AGORITM_MD5[] PROGMEM = "MD5";
constexpr char CHECKSUM_AGORITM_SHA256[] PROGMEM = "SHA256";
constexpr char CHECKSUM_AGORITM_SHA384[] PROGMEM = "SHA384";
constexpr char CHECKSUM_AGORITM_SHA512[] PROGMEM = "SHA512";
#else
constexpr char CURR_FW_TITLE_KEY[] = "current_fw_title";
constexpr char CURR_FW_VER_KEY[] = "current_fw_version";
constexpr char FW_ERROR_KEY[] = "fw_error";
constexpr char FW_STATE_KEY[] = "fw_state";
constexpr char FW_STATE_DOWNLOADING[] = "DOWNLOADING";
constexpr char FW_STATE_DOWNLOADED[] = "DOWNLOADED";
constexpr char FW_STATE_VERIFIED[] = "VERIFIED";
constexpr char FW_STATE_UPDATING[] = "UPDATING";
constexpr char FW_STATE_FAILED[] = "FAILED";
constexpr char FW_VER_KEY[] = "fw_version";
constexpr char FW_TITLE_KEY[] = "fw_title";
constexpr char FW_CHKS_KEY[] = "fw_checksum";
constexpr char FW_CHKS_ALGO_KEY[] = "fw_checksum_algorithm";
constexpr char FW_SIZE_KEY[] = "fw_size";
constexpr char CHECKSUM_AGORITM_MD5[] = "MD5";
constexpr char CHECKSUM_AGORITM_SHA256[] = "SHA256";
constexpr char CHECKSUM_AGORITM_SHA384[] = "SHA384";
constexpr char CHECKSUM_AGORITM_SHA512[] = "SHA512";
#endif // THINGSBOARD_ENABLE_PROGMEM
// Log messages.
#if THINGSBOARD_ENABLE_PROGMEM
constexpr char NO_FW[] PROGMEM = "No new firmware assigned on the given device";
constexpr char EMPTY_FW[] PROGMEM = "Given firmware was NULL";
constexpr char FW_UP_TO_DATE[] PROGMEM = "Firmware is already up to date";
constexpr char FW_NOT_FOR_US[] PROGMEM = "Firmware is not for us (title is different)";
constexpr char UNABLE_TO_WRITE[] PROGMEM = "Unable to write firmware";
constexpr char UNABLE_TO_DOWNLOAD[] PROGMEM = "Unable to download firmware";
constexpr char ERROR_UPDATE_BEGIN[] PROGMEM = "Error during Update.begin";
constexpr char ERROR_UPDATE_WRITE[] PROGMEM = "Error during Update.write";
constexpr char ERROR_UPDATE_END[] PROGMEM = "Error during Update.end, not all bytes written";
constexpr char CHKS_VER_FAILED[] PROGMEM = "Checksum verification failed";
constexpr char FW_CHKS_ALGO_NOT_SUPPORTED[] PROGMEM = "Checksum algorithm (%s) is not supported";
constexpr char PAGE_BREAK[] PROGMEM = "=================================";
constexpr char NEW_FW[] PROGMEM = "A new Firmware is available:";
constexpr char FROM_TOO[] PROGMEM = "(%s) => (%s)";
constexpr char DOWNLOADING_FW[] PROGMEM = "Attempting to download over MQTT...";
constexpr char NOT_ENOUGH_RAM[] PROGMEM = "Not enough RAM";
constexpr char SLASH PROGMEM = '/';
constexpr char FW_CHUNK[] PROGMEM = "Receive chunk (%i), with size (%u) bytes";
constexpr char HASH_ACTUAL[] PROGMEM = "(%s) actual checksum: (%s)";
constexpr char HASH_EXPECTED[] PROGMEM = "(%s) expected checksum: (%s)";
constexpr char CHKS_VER_SUCCESS[] PROGMEM = "Checksum is the same as expected";
constexpr char FW_UPDATE_SUCCESS[] PROGMEM = "Update success";
constexpr char RESETTING_FAILED[] PROGMEM = "Preparing for OTA firmware updates failed, attributes might be NULL";
#else
constexpr char NO_FW[] = "No new firmware assigned on the given device";
constexpr char EMPTY_FW[] = "Given firmware was NULL";
constexpr char FW_UP_TO_DATE[] = "Firmware is already up to date";
constexpr char FW_NOT_FOR_US[] = "Firmware is not for us (title is different)";
constexpr char UNABLE_TO_WRITE[] = "Unable to write firmware";
constexpr char UNABLE_TO_DOWNLOAD[] = "Unable to download firmware";
constexpr char ERROR_UPDATE_BEGIN[] = "Error during Update.begin";
constexpr char ERROR_UPDATE_WRITE[] = "Error during Update.write";
constexpr char ERROR_UPDATE_END[] = "Error during Update.end, not all bytes written";
constexpr char CHKS_VER_FAILED[] = "Checksum verification failed";
constexpr char FW_CHKS_ALGO_NOT_SUPPORTED[] = "Checksum algorithm (%s) is not supported";
constexpr char PAGE_BREAK[] = "=================================";
constexpr char NEW_FW[] = "A new Firmware is available:";
constexpr char FROM_TOO[] = "(%s) => (%s)";
constexpr char DOWNLOADING_FW[] = "Attempting to download over MQTT...";
constexpr char NOT_ENOUGH_RAM[] = "Not enough RAM";
constexpr char SLASH = '/';
constexpr char FW_CHUNK[] = "Receive chunk (%i), with size (%u) bytes";
constexpr char HASH_ACTUAL[] = "(%s) actual checksum: (%s)";
constexpr char HASH_EXPECTED[] = "(%s) expected checksum: (%s)";
constexpr char CHKS_VER_SUCCESS[] = "Checksum is the same as expected";
constexpr char FW_UPDATE_SUCCESS[] = "Update success";
constexpr char RESETTING_FAILED[] = "Preparing for OTA firmware updates failed, attributes might be NULL";
#endif // THINGSBOARD_ENABLE_PROGMEM
#endif // defined(ESP8266) || defined(ESP32)
/// @brief Wrapper around the PubSubClient to allow connecting and sending / retrieving data from ThingsBoard over the MQTT or MQTT with TLS/SSL protocol
/// @tparam Logger Logging class that should be used to print messages generated by ThingsBoard, default = ThingsBoardDefaultLogger
/// @tparam MaxFieldsAmt Maximum amount of key value pair that we will be able to sent to ThingsBoard in one call, default = 8
template<size_t MaxFieldsAmt = Default_Fields_Amt,
typename Logger = ThingsBoardDefaultLogger>
class ThingsBoardSized {
public:
/// @brief Constructs a ThingsBoardSized instance with the given network client
/// @param client Network client that should be used to establish the connection to ThingsBoard
/// @param bufferSize Maximum amount of data that can be either received or sent to ThingsBoard at once, if bigger packets are received they are discarded
/// and if we attempt to send data that is bigger, it will not be sent, can be changed later with the setBufferSize() method
/// @param enableQoS Wheter the PubSubClient should use Quality of Service Level 1 or not,
/// false means Level 0 is used, which means we simply send the data and hope it arrives if it is lost in transit it isn't resent.
/// Level 2 however stores the given message we wanted to send until a response is received from the server,
/// meaning that the message has been sent at least once, but it could have been sent multiple times as well,
/// will not be a problem if the optional timestamp is added to the sent data,
/// making duplicate sent data irrelevant because it simply overrides the data with the same timestamp,
/// see https://www.hivemq.com/blog/mqtt-essentials-part-6-mqtt-quality-of-service-levels/ for more information
inline ThingsBoardSized(Client& client, const uint16_t& bufferSize = Default_Payload, const bool& enableQoS = false)
: ThingsBoardSized(enableQoS)
{
setBufferSize(bufferSize);
setClient(client);
}
/// @brief Constructs a ThingsBoardSized instance without a network client, meaning it has to be added later with the setClient() and setBufferSize() method
/// @param enableQoS Wheter the PubSubClient should use Quality of Service Level 1 or not,
/// false means Level 0 is used, which means we simply send the data and hope it arrives if it is lost in transit it isn't resent.
/// Level 2 however stores the given message we wanted to send until a response is received from the server,
/// meaning that the message has been sent at least once, but it could have been sent multiple times as well,
/// will not be a problem if the optional timestamp is added to the sent data,
/// making duplicate sent data irrelevant because it simply overrides the data with the same timestamp,
/// see https://www.hivemq.com/blog/mqtt-essentials-part-6-mqtt-quality-of-service-levels/ for more information
inline ThingsBoardSized(const bool& enableQoS = false)
: m_client()
#if !THINGSBOARD_ENABLE_STL
, m_rpcIndex(0U)
#endif // !THINGSBOARD_ENABLE_STL
, m_rpcCallbacks()
#if !THINGSBOARD_ENABLE_STL
, m_rpcRequestIndex(0U)
#endif // !THINGSBOARD_ENABLE_STL
, m_rpcRequestCallbacks()
#if !THINGSBOARD_ENABLE_STL
, m_sharedAttributeIndex(0U)
#endif // !THINGSBOARD_ENABLE_STL
, m_sharedAttributeUpdateCallbacks()
#if !THINGSBOARD_ENABLE_STL
, m_attributeRequestIndex(0U)
#endif // !THINGSBOARD_ENABLE_STL
, m_attributeRequestCallbacks()
, m_provisionCallback()
, m_requestId(0U)
, m_qos(enableQoS)
#if defined(ESP8266) || defined(ESP32)
, m_fwState(false)
, m_fwCallback(nullptr)
, m_fwSize(0U)
, m_fwChecksumAlgorithm()
, m_fwAlgorithm()
, m_fwChecksum()
, m_fwSharedKeys{FW_CHKS_KEY, FW_CHKS_ALGO_KEY, FW_SIZE_KEY, FW_TITLE_KEY, FW_VER_KEY}
, m_fwRequestCallback(m_fwSharedKeys.cbegin(), m_fwSharedKeys.cend(), std::bind(&ThingsBoardSized::Firmware_Shared_Attribute_Received, this, std::placeholders::_1))
, m_fwUpdateCallback(m_fwSharedKeys.cbegin(), m_fwSharedKeys.cend(), std::bind(&ThingsBoardSized::Firmware_Shared_Attribute_Received, this, std::placeholders::_1))
, m_fwChunkReceive(0U)
#endif
{
#if !THINGSBOARD_ENABLE_STL
m_subscribedInstance = this;
#endif // !THINGSBOARD_ENABLE_STL
reserve_callback_size(MaxFieldsAmt);
}
/// @brief Destructor
inline ~ThingsBoardSized() {
// Nothing to do.
}
/// @brief Gets the currently connected PubSubClient as a reference,
/// allows for calling method directly on the client itself, not advised in normal use cases,
/// as it might cause problems if the library expects the client to be sending / receiving data
/// but can't anymore because it has been disconnected or certain settings were changed
/// @return Reference to the underlying PubSubClient connected to ThingsBoard
inline PubSubClient& getClient() {
return m_client;
}
/// @brief Sets the Quality of Service Level used by the MQTT client either true for Level 1 or false for Level 0
/// @param enableQoS Wheter the PubSubClient should use Quality of Service Level 1 or not,
/// false means Level 0 is used, which means we simply send the data and hope it arrives if it is lost in transit it isn't resent.
/// Level 2 however stores the given message we wanted to send until a response is received from the server,
/// meaning that the message has been sent at least once, but it could have been sent multiple times as well,
/// will not be a problem if the optional timestamp is added to the sent data,
/// making duplicate sent data irrelevant because it simply overrides the data with the same timestamp,
/// see https://www.hivemq.com/blog/mqtt-essentials-part-6-mqtt-quality-of-service-levels/ for more information
inline void enableMQTTQoS(const bool& enableQoS) {
m_qos = enableQoS;
}
/// @brief Sets the underlying network client that should be used to establish the connection to ThingsBoard,
/// ensure to disconnect and connect again after changing the underlying client
/// @param client Network client that should be used to establish the connection to ThingsBoard
inline void setClient(Client& client) {
m_client.setClient(client);
// Initalize callback.
#if THINGSBOARD_ENABLE_STL
m_client.setCallback(std::bind(&ThingsBoardSized::onMQTTMessage, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
#else
m_client.setCallback(ThingsBoardSized::onStaticMQTTMessage);
#endif // THINGSBOARD_ENABLE_STL
}
/// @brief Sets the size of the buffer for the underlying network client that will be used to establish the connection to ThingsBoard
/// @param bufferSize Maximum amount of data that can be either received or sent to ThingsBoard at once, if bigger packets are received they are discarded
/// and if we attempt to send data that is bigger, it will not be sent
/// @return Wheter allocating the needed memory for the given bufferSize was successful or not.
inline const bool setBufferSize(const uint16_t& bufferSize) {
return m_client.setBufferSize(bufferSize);
}
/// @brief Connects to the specified ThingsBoard server over the given port as the given device
/// @param host ThingsBoard server instance we want to connect to
/// @param access_token Access token that connects this device with a created device on the ThingsBoard server,
/// can be "provision", if the device creates itself instead
/// @param port Port that will be used to establish a connection and send / receive data from ThingsBoard over
/// @param client_id Client username that can be used to differentiate the user that is connecting the given device to ThingsBoard
/// @param password Client password that can be used to authenticate the user that is connecting the given device to ThingsBoard
/// @return Wheter connecting to ThingsBoard was successful or not
inline const bool connect(const char *host, const char *access_token = PROV_ACCESS_TOKEN, const uint16_t port = 1883, const char *client_id = DEFAULT_CLIENT_ID, const char *password = nullptr) {
if (!host) {
return false;
}
m_client.setServer(host, port);
return connect_to_host(access_token, client_id, password);
}
inline const bool connect(const IPAddress& host, const char *access_token = PROV_ACCESS_TOKEN, const uint16_t port = 1883, const char *client_id = DEFAULT_CLIENT_ID, const char *password = nullptr) {
m_client.setServer(host, port);
return connect_to_host(access_token, client_id, password);
}
/// @brief Disconnects any connection that has been established already
inline void disconnect() {
m_client.disconnect();
}
/// @brief Returns our current connection status to the cloud, true meaning we are connected,
/// false meaning we have been disconnected or have not established a connection yet
/// @return Wheter the underlying PubSubClient is currently connected or not
inline const bool connected() {
return m_client.connected();
}
/// @brief Receives / sends any outstanding messages from and to the MQTT broker
inline void loop() {
m_client.loop();
}
//----------------------------------------------------------------------------
// Claiming API
/// @brief Sends a claiming request for the given device, allowing any given user on the cloud to assign the device as their own (claim),
/// as long as they enter the given device name and secret key in the given amount of time.
/// Optionally a secret key can be passed or be left empty (cloud will allow any user to claim the device for the given amount of time)
/// @param secretKey Password the user additionaly to the device name needs to enter to claim it as their own,
/// pass nullptr or an empty string if the user should be able to claim the device without any password
/// @param durationMs Total time in milliseconds the user has to claim their device as their own
/// @return Wheter sending the claiming request was successful or not
inline const bool Claim_Request(const char *secretKey, const uint32_t& durationMs) {
StaticJsonDocument<JSON_OBJECT_SIZE(2)> requestBuffer;
const JsonObject respObj = requestBuffer.to<JsonObject>();
// Make the secret key optional,
// meaning if it is an empty string or null instead we don't send it at all.
if (secretKey != nullptr && secretKey[0] != '\0') {
respObj[SECRET_KEY] = secretKey;
}
respObj[DURATION_KEY] = durationMs;
const uint16_t currentBufferSize = m_client.getBufferSize();
const size_t objectSize = JSON_STRING_SIZE(measureJson(respObj));
char responsePayload[objectSize];
if (currentBufferSize < objectSize) {
char message[detectSize(INVALID_BUFFER_SIZE, currentBufferSize, objectSize)];
snprintf_P(message, sizeof(message), INVALID_BUFFER_SIZE, currentBufferSize, objectSize);
Logger::log(message);
return false;
}
// Serialize json does not include size of the string null terminator
else if (serializeJson(respObj, responsePayload, objectSize) < objectSize - 1) {
Logger::log(UNABLE_TO_SERIALIZE_JSON);
return false;
}
return m_client.publish(CLAIM_TOPIC, responsePayload, m_qos ? 1 : 0);
}
//----------------------------------------------------------------------------
// Provisioning API
/// @brief Sends provisioning request for a new device, meaning we want to create a device that we can then connect over,
/// where the given provision device key / secret decide which device profile is used to create the given device with.
/// Optionally a device name can be passed or be left empty (cloud will use the unqiue access token as a name instead).
/// The cloud then sends back json data containing our credentials, that the given callback, if creating the device was successful.
/// The data contained in that callbackcan then be used to disconnect and reconnect to the ThingsBoard server as our newly created device
/// @param callback Callback method that will be called
/// @return Wheter sending the provisioning request was successful or not
inline const bool Provision_Request(const Provision_Callback& callback) {
StaticJsonDocument<JSON_OBJECT_SIZE(9)> requestBuffer;
const JsonObject requestObject = requestBuffer.to<JsonObject>();
const char *provisionDeviceKey = callback.Get_Device_Key();
const char *provisionDeviceSecret = callback.Get_Device_Secret();
if (provisionDeviceKey == nullptr || provisionDeviceSecret == nullptr) {
return false;
}
// Ensure the response topic has been subscribed
else if (!Provision_Subscribe(callback)) {
return false;
}
const char *deviceName = callback.Get_Device_Name();
const char *accessToken = callback.Get_Device_Access_Token();
const char *credUsername = callback.Get_Credentials_Username();
const char *credPassword = callback.Get_Credentials_Password();
const char *credClientID = callback.Get_Credentials_Client_ID();
const char *hash = callback.Get_Certificate_Hash();
const char *credentialsType = callback.Get_Credentials_Type();
// Make the key-value pairs we don't want to send optional,
// meaning if it is an empty string or null instead we don't send it at all.
// Deciding which underlying provisioning method is restricted, by the Provision_Callback class.
// Meaning only the key-value pairs that are needed for the given provisionign method are set,
// meaning the rest will not be sent and therefore the provisoning request has the correct formatting
if (deviceName != nullptr && deviceName[0] != '\0') {
requestObject[DEVICE_NAME_KEY] = deviceName;
}
if (accessToken != nullptr && accessToken[0] != '\0') {
requestObject[PROV_TOKEN] = accessToken;
}
if (credUsername != nullptr && credUsername[0] != '\0') {
requestObject[PROV_CRED_USERNAME] = credUsername;
}
if (credPassword != nullptr && credPassword[0] != '\0') {
requestObject[PROV_CRED_PASSWORD] = credPassword;
}
if (credClientID != nullptr && credClientID[0] != '\0') {
requestObject[PROV_CRED_CLIENT_ID] = credClientID;
}
if (hash != nullptr && hash[0] != '\0') {
requestObject[PROV_CRED_HASH] = hash;
}
if (credentialsType != nullptr && credentialsType[0] != '\0') {
requestObject[PROV_CRED_TYPE_KEY] = credentialsType;
}
requestObject[PROV_DEVICE_KEY] = provisionDeviceKey;
requestObject[PROV_DEVICE_SECRET_KEY] = provisionDeviceSecret;
const uint16_t currentBufferSize = m_client.getBufferSize();
const size_t objectSize = JSON_STRING_SIZE(measureJson(requestObject));
char requestPayload[objectSize];
if (currentBufferSize < objectSize) {
char message[detectSize(INVALID_BUFFER_SIZE, currentBufferSize, objectSize)];
snprintf_P(message, sizeof(message), INVALID_BUFFER_SIZE, currentBufferSize, objectSize);
Logger::log(message);
return false;
}
// Serialize json does not include size of the string null terminator
else if (serializeJson(requestObject, requestPayload, objectSize) < objectSize - 1) {
Logger::log(UNABLE_TO_SERIALIZE_JSON);
return false;
}
Logger::log(PROV_REQUEST);
Logger::log(requestPayload);
return m_client.publish(PROV_REQUEST_TOPIC, requestPayload, m_qos ? 1 : 0);
}
//----------------------------------------------------------------------------
// Telemetry API
/// @brief Attempts to send telemetry data with the given key and value of the given type
/// @tparam T Type of the data value we want to send
/// @param key Key of the key value pair we want to send
/// @param value Value of the key value pair we want to send
/// @return Wheter sending the data was successful or not
template<class T>
inline const bool sendTelemetryData(const char *key, T value) {
return sendKeyValue(key, value);
}
/// @brief Attempts to send integer telemetry data with the given key and value
/// @param key Key of the key value pair we want to send
/// @param value Value of the key value pair we want to send
/// @return Wheter sending the data was successful or not
inline const bool sendTelemetryInt(const char *key, int value) {
return sendKeyValue(key, value);
}
/// @brief Attempts to send boolean telemetry data with the given key and value
/// @param key Key of the key value pair we want to send
/// @param value Value of the key value pair we want to send
/// @return Wheter sending the data was successful or not
inline const bool sendTelemetryBool(const char *key, bool value) {
return sendKeyValue(key, value);
}
/// @brief Attempts to send float telemetry data with the given key and value
/// @param key Key of the key value pair we want to send
/// @param value Value of the key value pair we want to send
/// @return Wheter sending the data was successful or not
inline const bool sendTelemetryFloat(const char *key, float value) {
return sendKeyValue(key, value);
}
/// @brief Attempts to send string telemetry data with the given key and value
/// @param key Key of the key value pair we want to send
/// @param value Value of the key value pair we want to send
/// @return Wheter sending the data was successful or not
inline const bool sendTelemetryString(const char *key, const char *value) {
return sendKeyValue(key, value);
}
/// @brief Attempts to send aggregated telemetry data
/// @param data Array containing all the data we want to send
/// @param data_count Amount of data entries in the array that we want to send
/// @return Wheter sending the data was successful or not
inline const bool sendTelemetry(const Telemetry *data, size_t data_count) {
return sendDataArray(data, data_count);
}
/// @brief Attempts to send custom json telemetry string
/// @param json String containing our json key value pairs we want to attempt to send
/// @return Wheter sending the data was successful or not
inline const bool sendTelemetryJson(const char *json) {
if (json == nullptr) {
return false;
}
const uint16_t currentBufferSize = m_client.getBufferSize();
const uint32_t json_size = JSON_STRING_SIZE(strlen(json));
if (currentBufferSize < json_size) {
char message[detectSize(INVALID_BUFFER_SIZE, currentBufferSize, json_size)];
snprintf_P(message, sizeof(message), INVALID_BUFFER_SIZE, currentBufferSize, json_size);
Logger::log(message);
return false;
}
return m_client.publish(TELEMETRY_TOPIC, json, m_qos ? 1 : 0);
}
/// @brief Attempts to send custom telemetry JsonObject
/// @param jsonObject JsonObject containing our json key value pairs we want to send
/// @param jsonSize Size of the data inside the JsonObject
/// @return Wheter sending the data was successful or not
inline const bool sendTelemetryJson(const JsonObject& jsonObject, const uint32_t& jsonSize) {
const uint32_t jsonObjectSize = jsonObject.size();
if (MaxFieldsAmt < jsonObjectSize) {
char message[detectSize(TOO_MANY_JSON_FIELDS, jsonObjectSize, MaxFieldsAmt)];
snprintf_P(message, sizeof(message), TOO_MANY_JSON_FIELDS, jsonObjectSize, MaxFieldsAmt);
Logger::log(message);
return false;
}
char json[jsonSize];
// Serialize json does not include size of the string null terminator
if (serializeJson(jsonObject, json, jsonSize) < jsonSize - 1) {
Logger::log(UNABLE_TO_SERIALIZE_JSON);
return false;
}
return sendTelemetryJson(json);
}
/// @brief Attempts to send custom telemetry JsonVariant
/// @param jsonVariant JsonVariant containing our json key value pairs we want to send
/// @param jsonSize Size of the data inside the JsonVariant
/// @return Wheter sending the data was successful or not
inline const bool sendTelemetryJson(const JsonVariant& jsonVariant, const uint32_t& jsonSize) {
const uint32_t jsonVariantSize = jsonVariant.size();
if (MaxFieldsAmt < jsonVariantSize) {
char message[detectSize(TOO_MANY_JSON_FIELDS, jsonVariantSize, MaxFieldsAmt)];
snprintf_P(message, sizeof(message), TOO_MANY_JSON_FIELDS, jsonVariantSize, MaxFieldsAmt);
Logger::log(message);
return false;
}
char json[jsonSize];
// Serialize json does not include size of the string null terminator
if (serializeJson(jsonVariant, json, jsonSize) < jsonSize - 1) {
Logger::log(UNABLE_TO_SERIALIZE_JSON);
return false;
}
return sendTelemetryJson(json);
}
//----------------------------------------------------------------------------
// Attribute API
/// @brief Attempts to send attribute data with the given key and value of the given type
/// @tparam T Type of the data value we want to send
/// @param key Key of the key value pair we want to send
/// @param value Value of the key value pair we want to send
/// @return Wheter sending the data was successful or not
template<class T>
inline const bool sendAttributeData(const char *attrName, T value) {
return sendKeyValue(attrName, value, false);
}
/// @brief Attempts to send integer attribute data with the given key and value
/// @param key Key of the key value pair we want to send
/// @param value Value of the key value pair we want to send
/// @return Wheter sending the data was successful or not
inline const bool sendAttributeInt(const char *attrName, int value) {
return sendKeyValue(attrName, value, false);
}
/// @brief Attempts to send boolean attribute data with the given key and value
/// @param key Key of the key value pair we want to send
/// @param value Value of the key value pair we want to send
/// @return Wheter sending the data was successful or not
inline const bool sendAttributeBool(const char *attrName, bool value) {
return sendKeyValue(attrName, value, false);
}
/// @brief Attempts to send float attribute data with the given key and value
/// @param key Key of the key value pair we want to send
/// @param value Value of the key value pair we want to send
/// @return Wheter sending the data was successful or not
inline const bool sendAttributeFloat(const char *attrName, float value) {
return sendKeyValue(attrName, value, false);
}
/// @brief Attempts to send string attribute data with the given key and value
/// @param key Key of the key value pair we want to send
/// @param value Value of the key value pair we want to send
/// @return Wheter sending the data was successful or not
inline const bool sendAttributeString(const char *attrName, const char *value) {
return sendKeyValue(attrName, value, false);
}
/// @brief Attempts to send aggregated attribute data
/// @param data Array containing all the data we want to send
/// @param data_count Amount of data entries in the array that we want to send
/// @return Wheter sending the data was successful or not
inline const bool sendAttributes(const Attribute *data, size_t data_count) {
return sendDataArray(data, data_count, false);
}
/// @brief Attempts to send custom json attribute string
/// @param json String containing our json key value pairs we want to attempt to send
/// @return Wheter sending the data was successful or not
inline const bool sendAttributeJSON(const char *json) {
if (json == nullptr) {
return false;
}
const uint16_t currentBufferSize = m_client.getBufferSize();
const uint32_t json_size = JSON_STRING_SIZE(strlen(json));
if (currentBufferSize < json_size) {
char message[detectSize(INVALID_BUFFER_SIZE, currentBufferSize, json_size)];
snprintf_P(message, sizeof(message), INVALID_BUFFER_SIZE, currentBufferSize, json_size);
Logger::log(message);
return false;
}
return m_client.publish(ATTRIBUTE_TOPIC, json, m_qos ? 1 : 0);
}
/// @brief Attempts to send custom attribute JsonObject
/// @param jsonObject JsonObject containing our json key value pairs we want to send
/// @param jsonSize Size of the data inside the JsonObject
/// @return Wheter sending the data was successful or not
inline const bool sendAttributeJSON(const JsonObject& jsonObject, const uint32_t& jsonSize) {
const uint32_t jsonObjectSize = jsonObject.size();
if (MaxFieldsAmt < jsonObjectSize) {
char message[detectSize(TOO_MANY_JSON_FIELDS, jsonObjectSize, MaxFieldsAmt)];
snprintf_P(message, sizeof(message), TOO_MANY_JSON_FIELDS, jsonObjectSize, MaxFieldsAmt);
Logger::log(message);
return false;
}
char json[jsonSize];
// Serialize json does not include size of the string null terminator
if (serializeJson(jsonObject, json, jsonSize) < jsonSize - 1) {
Logger::log(UNABLE_TO_SERIALIZE_JSON);
return false;
}
return sendAttributeJSON(json);
}
/// @brief Attempts to send custom attribute JsonVariant
/// @param jsonVariant JsonVariant containing our json key value pairs we want to send
/// @param jsonSize Size of the data inside the JsonVariant
/// @return Wheter sending the data was successful or not
inline const bool sendAttributeJSON(const JsonVariant& jsonVariant, const uint32_t& jsonSize) {
const uint32_t jsonVariantSize = jsonVariant.size();
if (MaxFieldsAmt < jsonVariantSize) {
char message[detectSize(TOO_MANY_JSON_FIELDS, jsonVariantSize, MaxFieldsAmt)];
snprintf_P(message, sizeof(message), TOO_MANY_JSON_FIELDS, jsonVariantSize, MaxFieldsAmt);
Logger::log(message);
return false;
}
char json[jsonSize];
// Serialize json does not include size of the string null terminator
if (serializeJson(jsonVariant, json, jsonSize) < jsonSize - 1) {
Logger::log(UNABLE_TO_SERIALIZE_JSON);
return false;
}
return sendAttributeJSON(json);
}
/// @brief Requests one client-side attribute calllback,
/// that will be called if the key-value pair from the server for the given client-side attributes is received
/// @param callback Callback method that will be called
/// @return Wheter requesting the given callback was successful or not
inline const bool Client_Attributes_Request(const Attribute_Request_Callback& callback) {
return Attributes_Request(callback, CLIENT_REQUEST_KEYS, CLIENT_RESPONSE_KEY);
}
//----------------------------------------------------------------------------
// Server-side RPC API
#if THINGSBOARD_ENABLE_STL
/// @brief Subscribes multiple server-side RPC callbacks,
/// that will be called if a request from the server for the method with the given name is received
/// @tparam InputIterator Class that points to the begin and end iterator
/// of the given data container, allows for using / passing either std::vector or std::array
/// @param first_itr Iterator pointing to the first element in the data container
/// @param last_itr Iterator pointing to the end of the data container (last element + 1)
/// @return Wheter subscribing the given callbacks was successful or not
template<class InputIterator>
inline const bool RPC_Subscribe(const InputIterator& first_itr, const InputIterator& last_itr) {
const uint32_t size = std::distance(first_itr, last_itr);
if (m_rpcCallbacks.size() + size > m_rpcCallbacks.capacity()) {
Logger::log(MAX_RPC_EXCEEDED);
return false;
}
else if (!m_client.subscribe(RPC_SUBSCRIBE_TOPIC, m_qos ? 1 : 0)) {
Logger::log(SUBSCRIBE_TOPIC_FAILED);
return false;
}
// Push back complete vector into our local m_rpcCallbacks vector.
m_rpcCallbacks.insert(m_rpcCallbacks.end(), first_itr, last_itr);
return true;
}
#else
/// @brief Subscribes multiple server-side RPC callbacks,
/// that will be called if a request from the server for the method with the given name is received
/// @param callbacks Pointer to the c-style array
/// @param callbacksSize Amount of values that should be subscribed, ensure size matches the actual array,
/// if not the system might crash unexpectedly at a later point
/// @return Wheter subscribing the given callbacks was successful or not
inline const bool RPC_Subscribe(const RPC_Callback *callbacks, const size_t& callbacksSize) {
if (callbacksSize + m_rpcIndex > sizeof(m_rpcCallbacks) / sizeof(*m_rpcCallbacks)) {
Logger::log(MAX_RPC_EXCEEDED);
return false;
}
else if (!m_client.subscribe(RPC_SUBSCRIBE_TOPIC, m_qos ? 1 : 0)) {
Logger::log(SUBSCRIBE_TOPIC_FAILED);
return false;
}
for (size_t i = 0; i < callbacksSize; ++i) {
m_rpcCallbacks[m_rpcIndex] = callbacks[i];
m_rpcIndex++;
}
return true;
}
#endif // THINGSBOARD_ENABLE_STL
/// @brief Subscribe one server-side RPC callback,
/// that will be called if a request from the server for the method with the given name is received
/// @param callback Callback method that will be called
/// @return Wheter subscribing the given callback was successful or not
inline const bool RPC_Subscribe(const RPC_Callback& callback) {
#if THINGSBOARD_ENABLE_STL
if (m_rpcCallbacks.size() + 1 > m_rpcCallbacks.capacity()) {
Logger::log(MAX_RPC_EXCEEDED);
return false;
}
else if (!m_client.subscribe(RPC_SUBSCRIBE_TOPIC, m_qos ? 1 : 0)) {
Logger::log(SUBSCRIBE_TOPIC_FAILED);
return false;
}
// Push back given callback into our local vector
m_rpcCallbacks.push_back(callback);
#else
if (1 + m_rpcIndex > sizeof(m_rpcCallbacks) / sizeof(*m_rpcCallbacks)) {
Logger::log(MAX_RPC_EXCEEDED);
return false;
}
else if (!m_client.subscribe(RPC_SUBSCRIBE_TOPIC, m_qos ? 1 : 0)) {
Logger::log(SUBSCRIBE_TOPIC_FAILED);
return false;
}
m_rpcCallbacks[m_rpcIndex] = callback;
m_rpcIndex++;
#endif // THINGSBOARD_ENABLE_STL
return true;
}
/// @brief Unsubcribes all server-side RPC callbacks
/// @return Wheter unsubcribing all the previously subscribed callbacks
/// and from the rpc topic, was successful or not
inline const bool RPC_Unsubscribe() {
// Empty all callbacks
#if THINGSBOARD_ENABLE_STL
m_rpcCallbacks.clear();
#else
m_rpcIndex = 0U;
#endif // THINGSBOARD_ENABLE_STL
return m_client.unsubscribe(RPC_SUBSCRIBE_TOPIC);
}
//----------------------------------------------------------------------------
// Client-side RPC API
/// @brief Requests one client-side RPC callback,
/// that will be called if a response from the server for the method with the given name is received
/// @param callback Callback method that will be called
/// @return Wheter requesting the given callback was successful or not
inline const bool RPC_Request(const RPC_Request_Callback& callback) {
// Ensure to have enough size for the infinite amount of possible parameters that could be sent to the cloud,
// therefore we set the size to the MaxFieldsAmt instead of JSON_OBJECT_SIZE(1), which will result in a JsonDocument with a size of 16 bytes
StaticJsonDocument<JSON_OBJECT_SIZE(MaxFieldsAmt)> requestBuffer;
// The .template variant of createing the JsonVariant has to be used,
// because we are passing a template to the StaticJsonDocument template list
// and it will generate a compile time error if not used
const JsonVariant requestVariant = requestBuffer.template as<JsonVariant>();
const char *methodName = callback.Get_Name();
const JsonArray* parameters = callback.Get_Parameters();
if (methodName == nullptr) {
Logger::log(RPC_METHOD_NULL);
return false;
}
RPC_Request_Callback* registeredCallback = nullptr;
// Ensure the response topic has been subscribed
if (!RPC_Request_Subscribe(callback, registeredCallback)) {
return false;
}
else if (registeredCallback == nullptr) {
return false;
}
requestVariant[RPC_METHOD_KEY] = methodName;
// Make the parameters for the client side RPC optional,
// meaning if it is an empty array or null instead we don't send it at all.
if (parameters != nullptr && !parameters->isNull()) {
requestVariant[RPC_PARAMS_KEY] = *parameters;
}
else {