This repository has been archived by the owner on Nov 11, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 102
/
Copy pathu_cell_pwr.c
3258 lines (2941 loc) · 140 KB
/
u_cell_pwr.c
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
/*
* Copyright 2019-2024 u-blox
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* Only #includes of u_* and the C standard library are allowed here,
* no platform stuff and no OS stuff. Anything required from
* the platform/OS must be brought in through u_port* to maintain
* portability.
*/
/** @file
* @brief Implementation of the power (both on/off and power saving)
* API for cellular.
*/
#ifdef U_CFG_OVERRIDE
# include "u_cfg_override.h" // For a customer's configuration override
#endif
#include "stdio.h" // snprintf()
#include "stddef.h" // NULL, size_t etc.
#include "stdint.h" // int32_t etc.
#include "stdbool.h"
#include "string.h" // strlen()
#include "u_cfg_sw.h"
#include "u_error_common.h"
#include "u_port.h"
#include "u_port_os.h"
#include "u_port_heap.h"
#include "u_port_debug.h"
#include "u_port_gpio.h"
#include "u_port_uart.h"
#include "u_ringbuffer.h"
#include "u_timeout.h"
#include "u_at_client.h"
#include "u_cell_module_type.h"
#include "u_cell_file.h"
#include "u_cell.h" // Order is
#include "u_cell_net.h" // important here
#include "u_cell_private.h" // don't change it
#include "u_cell_cfg.h"
#include "u_cell_mux.h"
#include "u_cell_mux_private.h"
#include "u_cell_ppp_shared.h"
#include "u_cell_pwr.h"
#include "u_cell_pwr_private.h"
/* ----------------------------------------------------------------
* COMPILE-TIME MACROS
* -------------------------------------------------------------- */
/** The number of times to poke the module to confirm that
* she's powered-on.
*/
#define U_CELL_PWR_IS_ALIVE_ATTEMPTS_POWER_ON 10
/** The number of time to try a configuration AT command by default.
*/
#define U_CELL_PWR_CONFIGURATION_COMMAND_TRIES 3
/** The UART power saving duration in GSM frames, needed for the
* UART power saving AT command.
*/
#define U_CELL_PWR_UART_POWER_SAVING_GSM_FRAMES ((U_CELL_POWER_SAVING_UART_INACTIVITY_TIMEOUT_SECONDS * 1000000) / 4615)
/** Convert a decoded EUTRAN paging window value into seconds for the given RAT.
*/
#define U_CELL_PWR_PAGING_WINDOW_DECODED_EUTRAN_TO_SECONDS(value, rat) (((rat) == U_CELL_NET_RAT_NB1) ? \
(((value) + 1) * 256 / 100) : \
(((value) + 1) * 128 / 100))
#ifndef U_CELL_PWR_GNSS_PROFILE_BITS_EXTRA
/** The extra bits to OR into the GNSS IO configuration (AT+UGPRF);
* set a negative value and this code will not set AT+UGPRF.
*/
# define U_CELL_PWR_GNSS_PROFILE_BITS_EXTRA 0
#endif
/** The number of time to try to get the module configured
* successfully.
*/
#define U_CELL_INITIAL_CONFIG_RETRIES 2
/* ----------------------------------------------------------------
* TYPES
* -------------------------------------------------------------- */
/** The UART power-saving modes: note that these numbers are defined
* by the AT interface and should NOT be changed.
*/
//lint -esym(749, uCellPwrUpsvMode_t::U_CELL_PWR_UPSV_MODE_RTS) Suppress not referenced
//lint -esym(749, uCellPwrUpsvMode_t::U_CELL_PWR_UPSV_MODE_DTR) Suppress not referenced
typedef enum {
U_CELL_PWR_UPSV_MODE_DISABLED = 0, /**< No UART power saving. */
U_CELL_PWR_UPSV_MODE_DATA = 1, /**< Module wakes up on TXD line activity,
SARA-U201/SARA-R5 version. */
U_CELL_PWR_UPSV_MODE_RTS = 2, /**< Module wakes up on RTS line being
asserted (not used in this code). */
U_CELL_PWR_UPSV_MODE_DTR = 3, /**< Module wakes up on DTR line being asserted. */
U_CELL_PWR_UPSV_MODE_DATA_SARA_R4_LENA_R8 = 4, /**< Module wakes up on TXD line
activity, SARA-R4/LENA-R8 version. */
U_CELL_PWR_UPSV_MODE_GPIO_LEXI_R10 = 5 /**< Module waking up is controlled by
the GPIO pin configured with GPIO
mode 34, LEXI-R10 version. */
} uCellPwrUpsvMode_t;
/** The UART power-saving max sleep modes for LEXI-R10.
* Note: these numbers are defined by the AT interface
* and should NOT be changed.
*/
typedef enum {
U_CELL_PWR_SLEEP_MODE_R10_1 = 2, /**< UART/32 kHz sleep, on-board
applications (IP/MQTT/HTTP) retained. */
U_CELL_PWR_SLEEP_MODE_R10_2 = 3, /**< Deep sleep, all on-board applications
(IP/MQTT/HTTP) lost. */
U_CELL_PWR_SLEEP_MODE_R10_HIBERNATE = 4 /**< Deepest sleep, all on-board
applications (IP/MQTT/HTTP) lost.*/
} uCellPwrSleepModeR10_t;
/** All the parameters for a wake-up-from-deep sleep callback.
*/
typedef struct {
uDeviceHandle_t cellHandle;
void (*pCallback) (uDeviceHandle_t, void *);
void *pCallbackParam;
} uCellPwrDeepSleepWakeUpCallback_t;
/** All the parameters for an E-DRX URC callback.
*/
typedef struct {
uDeviceHandle_t cellHandle;
void (*pCallback) (uDeviceHandle_t, uCellNetRat_t, bool, int32_t, int32_t, int32_t, void *);
uCellNetRat_t rat;
bool onNotOff;
int32_t eDrxSecondsRequested;
int32_t eDrxSecondsAssigned;
int32_t pagingWindowSecondsAssigned;
void *pCallbackParam;
} uCellPwrEDrxCallback_t;
/* ----------------------------------------------------------------
* VARIABLES
* -------------------------------------------------------------- */
/** Table of AT commands to send to all cellular module types
* during configuration.
*/
static const char *const gpConfigCommand[] = {"ATE0", // Echo off
#ifdef U_CFG_CELL_ENABLE_NUMERIC_ERROR
// With this compilation flag defined numeric errors will be
// returned and so uAtClientDeviceErrorGet() will be able
// to return a non-zero value for deviceError.code.
// IMPORTANT: this switch is simply for customer convenience,
// no ubxlib code should set it or depend on the value
// of deviceError.code.
"AT+CMEE=1", // Extended errors on, numeric format
#else
// The normal case: errors are reported by the module as
// verbose text, most useful when debugging normally with
// AT interface prints shown, uAtClientPrintAtSet() set
// to true.
"AT+CMEE=2", // Extended errors on, verbose/text format
#endif
#ifdef U_CFG_1V8_SIM_WORKAROUND
// This can be used to tell a SARA-R422 module that a 1.8V
// SIM which does NOT include 1.8V in its answer-to-reset
// really is a good 1.8V SIM.
"AT+UDCONF=92,1,1",
#endif
// SARA-R5xxx-01B remembers whether sockets are in hex mode or
// not so reset that here in order that all modules behave the
// same way
"AT+UDCONF=1,0",
"ATI9", // Firmware version
"AT&C1", // DCD circuit (109) changes with the carrier
"AT&D0", // Ignore changes to DTR
"AT+CGEREP=1" // "+CGEV" URC on context changes
};
/** Array to convert the RAT emited by AT+CEDRXS to one of our RATs.
*/
static const uCellNetRat_t gCedrxsRatToCellRat[] = {U_CELL_NET_RAT_UNKNOWN_OR_NOT_USED,
U_CELL_NET_RAT_UNKNOWN_OR_NOT_USED,
U_CELL_NET_RAT_GSM_GPRS_EGPRS, // 2 is GPRS
U_CELL_NET_RAT_UNKNOWN_OR_NOT_USED,
U_CELL_NET_RAT_CATM1, // 4 is LTE and is also CATM1
U_CELL_NET_RAT_NB1 // 5 is NB1
};
/** Array to convert one of our RATs to the RAT emited by AT+CEDRXS.
*/
static const int32_t gCellRatToCedrxsRat[] = { -1, // U_CELL_NET_RAT_DUMMY
-1, // U_CELL_NET_RAT_NKNOWN_OR_NOT_USED
2, // U_CELL_NET_RAT_GSM_GPRS_EGPRS
-1, // U_CELL_NET_RAT_GSM_COMPACT
-1, // U_CELL_NET_RAT_UTRAN
-1, // U_CELL_NET_RAT_EGPRS
-1, // U_CELL_NET_RAT_HSDPA
-1, // U_CELL_NET_RAT_HSDPA_HSUPA
4, // U_CELL_NET_RAT_LTE
-1, // U_CELL_NET_RAT_EC_GSM
4, // U_CELL_NET_RAT_CATM1
5 // U_CELL_NET_RAT_NB1
};
/** Array to convert E-DRX values for Cat-M1 in seconds into the number
* value of 24.008 table 10.5.5.34 (the index of the entry in the array
* is the number value).
*/
static const int32_t gEdrxCatM1SecondsToNumber[] = {5, 10, 20, 41, 61, 82, 102, 122, 143, 164, 328, 655, 1310, 2621};
/** Array to convert E-DRX values for NB1 in seconds into the number
* value of 24.008 table 10.5.5.32 (the index of the entry in the array
* is the number value). Note that some values are missing, denoted
* with entries of -1, and some just default to 20 seconds.
*/
static const int32_t gEdrxNb1SecondsToNumber[] = {-1, -1, 20, 41, 20, 82, 20, 20, 20, 164, 328, 655, 1310, 2621, 5243, 10486};
/** Array to compare the module names from the devices.
* This string array and the uCellModuleType_t should be kept synchronized.
* The order of module types must match.
* Where more than one module type string is required for a single module
* type from uCellModuleType_t then they should both be included
* in the same string with a "|" separating them, e.g. "blah|blim".
*/
static const char *gpModuleNames[] = {"SARA-U2",
"SARA-R410M-02B",
"SARA-R412M-02B",
"SARA-R412M-03B",
"SARA-R50|SARA-R51",
"SARA-R410M-03B",
"SARA-R422",
"LARA-R6",
"LENA-R8",
"SARA-R52",
"LEXI-R10",
"LEXI-R422",
"LEXI-R52"
};
/** The PWR_ON pin pulse durations, in milliseconds, to be
* used for the "any module" identification case.
*/
static const int32_t gPwrOnPinDurationMs[] = {300, 2000};
/* ----------------------------------------------------------------
* STATIC FUNCTIONS: 3GPP POWER SAVING
* -------------------------------------------------------------- */
// Convert an unsigned integer into binary.
static void uintToBinaryString(uint32_t num, char *pStr, int32_t strSize,
int32_t bitCount)
{
int32_t tmp = 0;
int32_t pos = 0;
for (int32_t x = 31; x >= 0; x--) {
tmp = (int32_t) (num >> x);
if (x < bitCount) {
if (pos < strSize) {
if (tmp & 1) {
*(pStr + pos) = 1 + '0';
} else {
*(pStr + pos) = 0 + '0';
}
}
pos++;
}
}
}
// Convert a string representing a binary value into an unsigned integer.
static uint32_t binaryStringToUint(const char *pStr)
{
size_t strSize = strlen(pStr);
uint32_t value = 0;
for (size_t x = 0; x < strSize; x++) {
value += ((unsigned) (int32_t) *(pStr + (strSize - x) - 1) - '0') << x; // *NOPAD*
}
return value;
}
// Set the power saving parameters using AT+CPSMS.
static int32_t setPowerSavingMode(const uCellPrivateInstance_t *pInstance,
bool onNotOff,
int32_t activeTimeSeconds,
int32_t periodicWakeupSeconds)
{
int32_t errorCode;
uAtClientHandle_t atHandle = pInstance->atHandle;
char ptEncoded[8 + 1] = {0}; // timer value encoded as 3GPP IE
char atEncoded[8 + 1] = {0}; // timer value encoded as 3GPP IE
uint32_t value;
int32_t t;
if ((activeTimeSeconds >= 0) && (periodicWakeupSeconds >= 0)) {
// PSM string encoding code borrowed from AT_CellularPower.cpp
// Table 10.5.163a/3GPP TS 24.008: GPRS Timer 3 information element
// Bits 5 to 1 represent the binary coded timer value.
// Bits 6 to 8 defines the timer value unit for the GPRS timer as follows:
// 8 7 6
// 0 0 0 value is incremented in multiples of 10 minutes
// 0 0 1 value is incremented in multiples of 1 hour
// 0 1 0 value is incremented in multiples of 10 hours
// 0 1 1 value is incremented in multiples of 2 seconds
// 1 0 0 value is incremented in multiples of 30 seconds
// 1 0 1 value is incremented in multiples of 1 minute
// 1 1 0 value is incremented in multiples of 320 hours (NOTE 1)
// 1 1 1 value indicates that the timer is deactivated (NOTE 2).
if (periodicWakeupSeconds <= 2 * 0x1f) { // multiples of 2 seconds
value = periodicWakeupSeconds / 2;
strncpy(ptEncoded, "01100000", sizeof(ptEncoded));
} else {
if (periodicWakeupSeconds <= 30 * 0x1f) { // multiples of 30 seconds
value = periodicWakeupSeconds / 30;
strncpy(ptEncoded, "10000000", sizeof(ptEncoded));
} else {
if (periodicWakeupSeconds <= 60 * 0x1f) { // multiples of 1 minute
value = periodicWakeupSeconds / 60;
strncpy(ptEncoded, "10100000", sizeof(ptEncoded));
} else {
if (periodicWakeupSeconds <= 10 * 60 * 0x1f) { // multiples of 10 minutes
value = periodicWakeupSeconds / (10 * 60);
strncpy(ptEncoded, "00000000", sizeof(ptEncoded));
} else {
if (periodicWakeupSeconds <= 60 * 60 * 0x1f) { // multiples of 1 hour
value = periodicWakeupSeconds / (60 * 60);
strncpy(ptEncoded, "00100000", sizeof(ptEncoded));
} else {
if (periodicWakeupSeconds <= 10 * 60 * 60 * 0x1f) { // multiples of 10 hours
value = periodicWakeupSeconds / (10 * 60 * 60);
strncpy(ptEncoded, "01000000", sizeof(ptEncoded));
} else { // multiples of 320 hours
t = periodicWakeupSeconds / (320 * 60 * 60);
if (t > 0x1f) {
t = 0x1f;
}
value = t;
strncpy(ptEncoded, "11000000", sizeof(ptEncoded));
}
}
}
}
}
}
uintToBinaryString(value, &ptEncoded[3], sizeof(ptEncoded) - 3, 5);
ptEncoded[8] = '\0';
// Table 10.5.172/3GPP TS 24.008: GPRS Timer information element
// Bits 5 to 1 represent the binary coded timer value.
// Bits 6 to 8 defines the timer value unit for the GPRS timer as follows:
// 8 7 6
// 0 0 0 value is incremented in multiples of 2 seconds
// 0 0 1 value is incremented in multiples of 1 minute
// 0 1 0 value is incremented in multiples of decihours
// 1 1 1 value indicates that the timer is deactivated.
// Other values shall be interpreted as multiples of 1 minute in this
// version of the protocol.
if (activeTimeSeconds <= 2 * 0x1f) { // multiples of 2 seconds
value = activeTimeSeconds / 2;
strncpy(atEncoded, "00000000", sizeof(atEncoded));
} else {
if (activeTimeSeconds <= 60 * 0x1f) { // multiples of 1 minute
value = (1 << 5) | (activeTimeSeconds / 60);
strncpy(atEncoded, "00100000", sizeof(atEncoded));
} else { // multiples of decihours
t = activeTimeSeconds / (6 * 60);
if (t > 0x1f) {
t = 0x1f;
}
value = t;
strncpy(atEncoded, "01000000", sizeof(atEncoded));
}
}
uintToBinaryString(value, &atEncoded[3], sizeof(atEncoded) - 3, 5);
atEncoded[8] = '\0';
}
value = 0;
if (onNotOff) {
value = 1;
}
uAtClientLock(atHandle);
// Can need a little longer for this
uAtClientTimeoutSet(atHandle, 10000);
uAtClientCommandStart(atHandle, "AT+CPSMS=");
// Write the on/off flag
uAtClientWriteInt(atHandle, (int32_t) value);
if ((activeTimeSeconds >= 0) && (periodicWakeupSeconds >= 0)) {
// Skip unused GPRS parameters
uAtClientWriteString(atHandle, "", false);
uAtClientWriteString(atHandle, "", false);
// Write wanted parameters
uAtClientWriteString(atHandle, ptEncoded, true);
uAtClientWriteString(atHandle, atEncoded, true);
}
uAtClientCommandStopReadResponse(atHandle);
errorCode = uAtClientUnlock(atHandle);
if (errorCode == 0) {
uPortLog("U_CELL_PWR: requested PSM %s, requested TAU time %d second(s),"
" requested active time %d second(s).\n",
onNotOff ? "on" : "off", periodicWakeupSeconds,
activeTimeSeconds);
// Note: the URC for deep sleep is switched on at power-on
if (pInstance->pSleepContext != NULL) {
// Assume that the network has agreed: this
// will be updated when the 3GPP power saving
// state is read and when we get a +CEREG
pInstance->pSleepContext->powerSaving3gppAgreed = onNotOff;
}
}
return errorCode;
}
/* ----------------------------------------------------------------
* STATIC FUNCTIONS: E-DRX
* -------------------------------------------------------------- */
// Create a sleep context.
static int32_t createSleepContext(uCellPrivateInstance_t *pInstance)
{
int32_t errorCode = (int32_t) U_ERROR_COMMON_NO_MEMORY;
uCellPrivateSleep_t *pContext;
pInstance->pSleepContext = (uCellPrivateSleep_t *) pUPortMalloc(sizeof(uCellPrivateSleep_t));
if (pInstance->pSleepContext != NULL) {
pContext = pInstance->pSleepContext;
memset(pContext, 0, sizeof(*pContext));
// Set the CEREG items up to an impossible set (can't be on if
// activeTimeSeconds is -1) so that when some genuine ones
// arrive we will notice the difference.
pContext->powerSaving3gppOnNotOffCereg = true;
pContext->activeTimeSecondsCereg = -1;
pContext->periodicWakeupSecondsCereg = -1;
errorCode = (int32_t) U_ERROR_COMMON_SUCCESS;
}
return errorCode;
}
// Return the 24.008 table 10.5.5.32 value for a given E-DRX value.
static int32_t edrxSecondsToNumber(int32_t seconds, uCellNetRat_t rat)
{
int32_t number = -1;
size_t y = 0;
const int32_t *pTmp = NULL;
switch (rat) {
case U_CELL_NET_RAT_GSM_GPRS_EGPRS:
number = (seconds * 100 * 1300 / 306) / 100;
break;
case U_CELL_NET_RAT_CATM1:
pTmp = gEdrxCatM1SecondsToNumber;
y = sizeof(gEdrxCatM1SecondsToNumber) / sizeof(gEdrxCatM1SecondsToNumber[0]);
break;
case U_CELL_NET_RAT_NB1:
pTmp = gEdrxNb1SecondsToNumber;
y = sizeof(gEdrxNb1SecondsToNumber) / sizeof(gEdrxNb1SecondsToNumber[0]);
break;
default:
break;
}
if ((pTmp != NULL) && (y > 0)) {
// For Cat-M1/NB1 need to look up the values up
// in a table as it is not a simple mapping
for (int32_t x = 0; (number < 0) && (x < (int32_t) y); x++) {
if ((*(pTmp + x) >= 0) && (seconds <= *(pTmp + x))) {
number = x;
}
}
// If we couldn't find one, use the largest
if (number < 0) {
number = *(pTmp + y - 1);
}
}
return number;
}
// Return the value in seconds for a given 24.008 table 10.5.5.32 E-DRX number.
static int32_t edrxNumberToSeconds(int32_t number, uCellNetRat_t rat)
{
int32_t seconds = -1;
switch (rat) {
case U_CELL_NET_RAT_GSM_GPRS_EGPRS:
seconds = (number * 100 * 306 / 1300) / 100;
break;
case U_CELL_NET_RAT_CATM1:
if ((number >= 0) && (number < (int32_t) (sizeof(gEdrxCatM1SecondsToNumber) /
sizeof(gEdrxCatM1SecondsToNumber[0])))) {
seconds = gEdrxCatM1SecondsToNumber[number];
}
break;
case U_CELL_NET_RAT_NB1:
if ((number >= 0) && (number < (int32_t) (sizeof(gEdrxNb1SecondsToNumber) /
sizeof(gEdrxNb1SecondsToNumber[0])))) {
seconds = gEdrxNb1SecondsToNumber[number];
}
break;
default:
break;
}
return seconds;
}
// Read CEDRXS or CEDRXRDP.
int32_t readCedrxsOrCedrxrdp(const uCellPrivateInstance_t *pInstance, bool rdpNotS,
uCellNetRat_t rat,
bool *pOnNotOffRequested, int32_t *pEDrxSecondsRequested,
int32_t *pPagingWindowSecondsRequested, bool *pOnNotOffAssigned,
int32_t *pEDrxSecondsAssigned, int32_t *pPagingWindowSecondsAssigned)
{
int32_t errorCode = (int32_t) U_ERROR_COMMON_INVALID_PARAMETER;
uAtClientHandle_t atHandle = pInstance->atHandle;
bool keepGoing = true;
char encoded[4 + 1]; // String representing four binary digits
int32_t firstInt = -1;
int32_t bytesRead;
int32_t value;
int32_t eDrxSecondsRequested = -1;
int32_t pagingWindowSecondsRequested = -1;
int32_t eDrxSecondsAssigned = -1;
int32_t pagingWindowSecondsAssigned = -1;
const char *pAtCommand = "AT+CEDRXS?";
const char *pAtResponse = "+CEDRXS:";
if (rdpNotS) {
pAtCommand = "AT+CEDRXRDP";
pAtResponse = "+CEDRXRDP:";
}
// CEDRXS and CEDRXP are very similar in format but not _quite_ the same.
//
// On SARA-R4 CEDRXS goes like this: a multi-line response giving the
// requested values for E-DRX and, optionally, paging window, where
// the lack of a line for a given RAT indicates that E-DRX is off, e.g.
//
// +CEDRXS: 2,"0111","0001"
// +CEDRXS: 4,"0111","0001"
//
//...means that E-DRX for NBIoT (RAT 5) is off but it is on for GPRS
// (RAT 2) and Cat-M1 (RAT 4).
//
// On SARA-R5, however, the +CEDRXS line is still present even if E-DRX
// is *off* for that RAT.
//
// CEDRXP, on the other hand, gives both the requested E-DRX value
// and the assigned E-DRX and assigned paging window values (in that
// order) and looks/ something like this on both SARA-R4 and SARA-R5:
//
// +CEDRXRDP: 2,"0111","0001","0001"
// +CEDRXRDP: 4,"0111","0001","0001"
//
// ...but in this case the first digit can also be 0 to indicate that E-DRX
// is disabled by the network. So to get the _requested_ E-DRX value on
// both SARA-R4 and SARA-R5 reliably use CEDRXRDP, to get the requested
// paging window value, where supported, use CEDRXS and to get the
// assigned values for both use CEDRXRDP.
if (((int32_t) rat >= 0) &&
// Cast in two stages to keep Lint happy
((size_t) (int32_t) rat < (sizeof(gCellRatToCedrxsRat) /
sizeof(gCellRatToCedrxsRat[0])))) {
errorCode = (int32_t) U_CELL_ERROR_AT;
uAtClientLock(atHandle);
// Set a short time-out so that we can
// detect the end of the response quickly
uAtClientTimeoutSet(atHandle, pInstance->pModule->responseMaxWaitMs);
uAtClientCommandStart(atHandle, pAtCommand);
uAtClientCommandStop(atHandle);
while (keepGoing) {
if (uAtClientResponseStart(atHandle, pAtResponse) == 0) {
// Read the RAT or, if CEDRXRDP, what might be 0 for
// "disabled by the network"
value = uAtClientReadInt(atHandle);
if ((value >= 0) &&
(value < (int32_t) (sizeof(gCedrxsRatToCellRat) / sizeof(gCedrxsRatToCellRat[0])))) {
if ((rat == gCedrxsRatToCellRat[value]) || (rdpNotS && (value == 0))) {
// If we're doing CEDRXRDP and the first integer is 0 then that means
// E-DRX is off but the values that follow may still be populated (e.g.
// if the network has refused a perfectly valid requested E-DRX setting)
if (rdpNotS && (value == 0) && (firstInt < 0)) {
firstInt = value;
}
// The first 4-bit binary thing is always the encoded requested E-DRX value
bytesRead = uAtClientReadString(atHandle, encoded, sizeof(encoded), false);
if (bytesRead == 4) {
// Convert the encoded value to a number
value = (int32_t) binaryStringToUint(encoded);
eDrxSecondsRequested = edrxNumberToSeconds(value, rat);
}
if (rdpNotS) {
// If we're reading CEDRXRDP then the next 4-bit binary thing
// is the assigned E-DRX value
bytesRead = uAtClientReadString(atHandle, encoded, sizeof(encoded), false);
if (bytesRead == 4) {
// Convert the encoded value to a number
value = (int32_t) binaryStringToUint(encoded);
eDrxSecondsAssigned = edrxNumberToSeconds(value, rat);
}
// ...and the thing that follows that is the assigned paging
// window value, if present
bytesRead = uAtClientReadString(atHandle, encoded, sizeof(encoded), false);
if (bytesRead == 4) {
// Convert the encoded value to a number
value = (int32_t) binaryStringToUint(encoded);
if (U_CELL_PRIVATE_RAT_IS_EUTRAN(rat)) {
pagingWindowSecondsAssigned = U_CELL_PWR_PAGING_WINDOW_DECODED_EUTRAN_TO_SECONDS(value,
rat);
} else {
pagingWindowSecondsAssigned = value;
}
}
} else {
// If we're doing CEDRXS then the only thing that can
// follow is the optional requested paging window value
bytesRead = uAtClientReadString(atHandle, encoded, sizeof(encoded), false);
if (bytesRead == 4) {
// Convert the encoded value to a number
value = (int32_t) binaryStringToUint(encoded);
if (U_CELL_PRIVATE_RAT_IS_EUTRAN(rat)) {
pagingWindowSecondsRequested = U_CELL_PWR_PAGING_WINDOW_DECODED_EUTRAN_TO_SECONDS(value,
rat);
} else {
pagingWindowSecondsRequested = value;
}
}
}
}
} else {
// Some platforms (e.g. SARAR-R41x) return "+CEDRXS:"
// followed by no digits whatsoever to indicate that
// E-DRX is off
if (!rdpNotS && (value < 0)) {
firstInt = 0;
}
keepGoing = false;
}
} else {
keepGoing = false;
}
uAtClientClearError(atHandle);
}
uAtClientResponseStop(atHandle);
uAtClientUnlock(atHandle);
if (eDrxSecondsRequested >= 0) {
// Having decoded a requested E-DRX value constitutes success
errorCode = (int32_t) U_ERROR_COMMON_SUCCESS;
if (pEDrxSecondsRequested != NULL) {
*pEDrxSecondsRequested = eDrxSecondsRequested;
}
} else if (firstInt == 0) {
// If the first integer is zero, or is absent then that
// means we're successful and the requested E-DRX state
// was "off"
errorCode = (int32_t) U_ERROR_COMMON_SUCCESS;
if (pOnNotOffRequested != NULL) {
*pOnNotOffRequested = false;
}
}
// Now fill everything else in
if ((pagingWindowSecondsRequested >= 0) && (pPagingWindowSecondsRequested != NULL)) {
*pPagingWindowSecondsRequested = pagingWindowSecondsRequested;
}
if ((eDrxSecondsAssigned >= 0) && (pEDrxSecondsAssigned != NULL)) {
*pEDrxSecondsAssigned = eDrxSecondsAssigned;
}
if ((pagingWindowSecondsAssigned >= 0) && (pPagingWindowSecondsAssigned != NULL)) {
*pPagingWindowSecondsAssigned = pagingWindowSecondsAssigned;
}
if (pOnNotOffRequested != NULL) {
if (eDrxSecondsRequested >= 0) {
*pOnNotOffRequested = true;
} else {
*pOnNotOffRequested = false;
}
}
if (pOnNotOffAssigned != NULL) {
if (eDrxSecondsAssigned >= 0) {
*pOnNotOffAssigned = true;
} else {
*pOnNotOffAssigned = false;
}
}
}
return errorCode;
}
// Callback via which the user's E-DRX parameter update callback is called.
// This is called through the uAtClientCallback() mechanism in order
// to prevent the AT client URC from blocking.
static void eDrxCallback(uAtClientHandle_t atHandle, void *pParameter)
{
uCellPwrEDrxCallback_t *pCallback = (uCellPwrEDrxCallback_t *) pParameter;
(void) atHandle;
if (pCallback != NULL) {
pCallback->pCallback(pCallback->cellHandle,
pCallback->rat,
pCallback->onNotOff,
pCallback->eDrxSecondsRequested,
pCallback->eDrxSecondsAssigned,
pCallback->pagingWindowSecondsAssigned,
pCallback->pCallbackParam);
uPortFree(pCallback);
}
}
// URC for when the E-DRX parameters change.
static void CEDRXP_urc(uAtClientHandle_t atHandle, void *pParameter)
{
uCellPrivateInstance_t *pInstance = (uCellPrivateInstance_t *) pParameter;
int32_t value;
uCellNetRat_t rat = U_CELL_NET_RAT_UNKNOWN_OR_NOT_USED;
char encoded[4 + 1]; // String representing four binary digits
int32_t bytesRead;
int32_t eDrxSecondsRequested = -1;
int32_t eDrxSecondsAssigned = -1;
int32_t pagingWindowSecondsAssigned = -1;
uCellPwrEDrxCallback_t *pCallback;
// +CEDRXP: 4,"0001","0001","0011
// Read the RAT, and this really is just the RAT, it is not also used
// to indicate "off" by being 0 or anything like that
value = uAtClientReadInt(atHandle);
if ((value >= 0) &&
(value < (int32_t) (sizeof(gCedrxsRatToCellRat) / sizeof(gCedrxsRatToCellRat[0])))) {
rat = gCedrxsRatToCellRat[value];
// The first 4-bit binary string is the encoded requested E-DRX value
bytesRead = uAtClientReadString(atHandle, encoded, sizeof(encoded), false);
if (bytesRead == 4) {
// Convert the encoded value to a number
value = (int32_t) binaryStringToUint(encoded);
eDrxSecondsRequested = edrxNumberToSeconds(value, rat);
}
// The second 4-bit binary string is the assigned E-DRX value
bytesRead = uAtClientReadString(atHandle, encoded, sizeof(encoded), false);
if (bytesRead == 4) {
// Convert the encoded value to a number
value = (int32_t) binaryStringToUint(encoded);
eDrxSecondsAssigned = edrxNumberToSeconds(value, rat);
}
// The last 4-bit binary string is the assigned paging window value
bytesRead = uAtClientReadString(atHandle, encoded, sizeof(encoded), false);
if (bytesRead == 4) {
// Convert the encoded value to a number
value = (int32_t) binaryStringToUint(encoded);
if (U_CELL_PRIVATE_RAT_IS_EUTRAN(rat)) {
pagingWindowSecondsAssigned = U_CELL_PWR_PAGING_WINDOW_DECODED_EUTRAN_TO_SECONDS(value,
rat);
} else {
pagingWindowSecondsAssigned = value;
}
}
}
if ((pInstance->pSleepContext != NULL) &&
(pInstance->pSleepContext->pEDrxCallback != NULL)) {
// Put all the data in a struct and pass a pointer to it to our
// local callback via the AT client's callback mechanism to decouple
// it from whatever might have called us.
// Note: eDrxCallback will free the allocated memory.
pCallback = (uCellPwrEDrxCallback_t *) pUPortMalloc(sizeof(*pCallback));
if (pCallback != NULL) {
pCallback->cellHandle = pInstance->cellHandle;
pCallback->pCallback = pInstance->pSleepContext->pEDrxCallback;
pCallback->rat = rat;
pCallback->onNotOff = (eDrxSecondsAssigned >= 0);
pCallback->eDrxSecondsRequested = eDrxSecondsRequested;
pCallback->eDrxSecondsAssigned = eDrxSecondsAssigned;
pCallback->pagingWindowSecondsAssigned = pagingWindowSecondsAssigned;
pCallback->pCallbackParam = pInstance->pSleepContext->pEDrxCallbackParam;
uAtClientCallback(pInstance->atHandle, eDrxCallback, pCallback);
}
}
}
// Switch the E-DRX URC on for all RATs where E-DRX is enabled.
static int32_t setEDrxUrc(const uCellPrivateInstance_t *pInstance)
{
int32_t errorCode = (int32_t) U_ERROR_COMMON_SUCCESS;
uAtClientHandle_t atHandle = pInstance->atHandle;
int32_t cedrxsRat[U_CELL_PRIVATE_MAX_NUM_SIMULTANEOUS_RATS];
int32_t value = 0;
int32_t bytesRead;
char encoded[4 + 1]; // String representing four binary digits
for (size_t x = 0; x < sizeof(cedrxsRat) / sizeof(cedrxsRat[0]); x++) {
cedrxsRat[x] = -1;
}
// Read the currently requested E-DRX values
uAtClientLock(atHandle);
// Set a short time-out so that we can
// detect the end of the response quickly
uAtClientTimeoutSet(atHandle, pInstance->pModule->responseMaxWaitMs);
uAtClientCommandStart(atHandle, "AT+CEDRXS?");
uAtClientCommandStop(atHandle);
for (size_t x = 0; (value >= 0) && (x < sizeof(cedrxsRat) / sizeof(cedrxsRat[0])); x++) {
uAtClientResponseStart(atHandle, "+CEDRXS:");
// Read the RAT
value = uAtClientReadInt(atHandle);
if ((value >= 0) &&
(value < (int32_t) (sizeof(gCedrxsRatToCellRat) / sizeof(gCedrxsRatToCellRat[0])))) {
// Got a valid RAT
cedrxsRat[x] = value;
// Read the requested E-DRX value for this RAT
bytesRead = uAtClientReadString(atHandle, encoded, sizeof(encoded), false);
if (bytesRead == 4) {
// Convert the encoded value to seconds
if (edrxNumberToSeconds((int32_t) binaryStringToUint(encoded),
gCedrxsRatToCellRat[cedrxsRat[x]]) < 0) {
// If it doesn't convert, remove the RAT from the list
cedrxsRat[x] = -1;
}
} else {
// Not enough characters in the string - remove the RAT from the list
cedrxsRat[x] = -1;
}
}
}
uAtClientResponseStop(atHandle);
uAtClientUnlock(atHandle);
for (size_t x = 0; (x < sizeof(cedrxsRat) / sizeof(cedrxsRat[0])) && (errorCode == 0); x++) {
if (cedrxsRat[x] >= 0) {
// For all the RATs that support E-DRX, write the
// command back again requesting that the URC is
// emitted; the other settings are remembered by the
// module and so don't need to be included
uAtClientLock(atHandle);
uAtClientCommandStart(atHandle, "AT+CEDRXS=");
// 2 means on and with the URC
uAtClientWriteInt(atHandle, 2);
// Write the RAT
uAtClientWriteInt(atHandle, cedrxsRat[x]);
uAtClientCommandStopReadResponse(atHandle);
errorCode = uAtClientUnlock(atHandle);
}
}
return errorCode;
}
/* ----------------------------------------------------------------
* STATIC FUNCTIONS: DEEP SLEEP
* -------------------------------------------------------------- */
// Callback via which the user's deep sleep wake-up callback is called.
// This is called through the uAtClientCallback() mechanism in order
// to prevent the AT client URC from blocking.
static void deepSleepWakeUpCallback(uAtClientHandle_t atHandle, void *pParameter)
{
uCellPwrDeepSleepWakeUpCallback_t *pCallback = (uCellPwrDeepSleepWakeUpCallback_t *) pParameter;
(void) atHandle;
if (pCallback != NULL) {
pCallback->pCallback(pCallback->cellHandle, pCallback->pCallbackParam);
uPortFree(pCallback);
}
}
// URC for the module's protocol stack entering/leaving deactivated
// mode; not that this doesn't _necessarily_ mean that the module is about
// to enter deep sleep, or woken up from deep sleep in fact.
static void UUPSMR_urc(uAtClientHandle_t atHandle, void *pParameter)
{
uCellPrivateInstance_t *pInstance = (uCellPrivateInstance_t *) pParameter;
int32_t x;
x = uAtClientReadInt(atHandle);
// 0 means waking up, but not necessarily waking up from deep sleep,
// any old waking up, so we can't infer anything from that,
// 1 means that the protocol stack has gone to sleep, which we note
// as a state but can't actually use for anything since the module
// is likely still responsive to AT commands,
// 2 means sleep is blocked.
if (x == 1) {
pInstance->deepSleepState = U_CELL_PRIVATE_DEEP_SLEEP_STATE_PROTOCOL_STACK_ASLEEP;
}
pInstance->deepSleepBlockedBy = -1;
if (x == 2) {
pInstance->deepSleepBlockedBy = uAtClientReadInt(atHandle);
}
}
/* ----------------------------------------------------------------
* STATIC FUNCTIONS: POWERING UP/DOWN
* -------------------------------------------------------------- */
// Configure one item in the cellular module.
static bool moduleConfigureOne(uAtClientHandle_t atHandle,
const char *pAtString,
int32_t configurationTries)
{
bool success = false;
for (size_t x = configurationTries; (x > 0) && !success; x--) {
uAtClientLock(atHandle);
uAtClientCommandStart(atHandle, pAtString);
uAtClientCommandStopReadResponse(atHandle);
success = (uAtClientUnlock(atHandle) == 0);
}
return success;
}
// Configure the cellular module.
static int32_t moduleConfigure(uCellPrivateInstance_t *pInstance,
bool andRadioOff, bool returningFromSleep)
{
int32_t errorCode = (int32_t) U_CELL_ERROR_NOT_CONFIGURED;
bool success = true;
uAtClientHandle_t atHandle = pInstance->atHandle;
uAtClientStreamHandle_t stream = U_AT_CLIENT_STREAM_HANDLE_DEFAULTS;
uCellPwrUpsvMode_t uartPowerSavingMode =
U_CELL_PWR_UPSV_MODE_DISABLED; // Assume no UART power saving
uCellPrivateUartSleepCache_t *pUartSleepCache = &(pInstance->uartSleepCache);
int32_t uartPowerSavingTimeout = U_CELL_PWR_UART_POWER_SAVING_GSM_FRAMES;
char buffer[20]; // Enough room for AT+UPSV=2,1300
#if U_CELL_PWR_GNSS_PROFILE_BITS_EXTRA >= 0
char *pServerNameGnss;
int32_t y;
#endif
// First send all the commands that everyone gets
for (size_t x = 0;
(x < sizeof(gpConfigCommand) / sizeof(gpConfigCommand[0])) &&
success; x++) {
success = moduleConfigureOne(atHandle, gpConfigCommand[x],
U_CELL_PWR_CONFIGURATION_COMMAND_TRIES);
}
if (success &&
U_CELL_PRIVATE_HAS(pInstance->pModule, U_CELL_PRIVATE_FEATURE_UCGED) &&
(U_CELL_PRIVATE_MODULE_IS_R4(pInstance->pModule->moduleType) ||
(pInstance->pModule->moduleType == U_CELL_MODULE_TYPE_LARA_R6) ||
(pInstance->pModule->moduleType == U_CELL_MODULE_TYPE_LEXI_R10))) {
// SARA-R4, LARA-R6, and LEXI-R10 only: switch on the right UCGED mode
// (SARA-R5 and SARA-U201 have a single mode and require no setting)
if (U_CELL_PRIVATE_HAS(pInstance->pModule, U_CELL_PRIVATE_FEATURE_UCGED5)) {
success = moduleConfigureOne(atHandle, "AT+UCGED=5",
U_CELL_PWR_CONFIGURATION_COMMAND_TRIES);
} else {
success = moduleConfigureOne(atHandle, "AT+UCGED=2",
U_CELL_PWR_CONFIGURATION_COMMAND_TRIES);
}
}
uAtClientStreamGetExt(atHandle, &stream);
if (success && (stream.type == U_AT_CLIENT_STREAM_TYPE_UART)) {
// Get the UART stream handle and set the flow
// control and power saving mode correctly for it
// TODO: check if AT&K3 requires both directions
// of flow control to be on or just one of them
// Note: we don't check the return code of moduleConfigureOne()
// here since AT&K is not supported on the USB interface
// of a cellular module
if (uPortUartIsRtsFlowControlEnabled(stream.handle.int32) &&
uPortUartIsCtsFlowControlEnabled(stream.handle.int32)) {
moduleConfigureOne(atHandle, "AT&K3", 1);
if (uAtClientWakeUpHandlerIsSet(atHandle)) {
// The RTS/CTS handshaking lines are being used
// for flow control by the UART HW. This complicates
// matters for power saving as, at least on SARA-R5
// (where power saving is a valued feature), the CTS
// line floats high during sleep, preventing the
// "wake-up" character being sent to the module to get
// it out of sleep.