Line data Source code
1 : #include "LR11x0.h"
2 :
3 : #include <string.h>
4 :
5 : #if !RADIOLIB_EXCLUDE_LR11X0
6 :
7 0 : int16_t LR11x0::isGnssScanCapable() {
8 : // get the version
9 : LR11x0VersionInfo_t version;
10 0 : int16_t state = this->getVersionInfo(&version);
11 0 : RADIOLIB_ASSERT(state);
12 :
13 : // check the device firmware version is sufficient
14 0 : uint16_t versionFull = ((uint16_t)version.fwMajor << 8) | (uint16_t)version.fwMinor;
15 0 : state = RADIOLIB_ERR_UNSUPPORTED;
16 0 : if((version.device == RADIOLIB_LR11X0_DEVICE_LR1110) && (versionFull >= 0x0401)) {
17 0 : state = RADIOLIB_ERR_NONE;
18 0 : } else if((version.device == RADIOLIB_LR11X0_DEVICE_LR1120) && (versionFull >= 0x0201)) {
19 0 : state = RADIOLIB_ERR_NONE;
20 : }
21 0 : RADIOLIB_ASSERT(state);
22 :
23 : // in debug mode, dump the almanac
24 : #if RADIOLIB_DEBUG_PROTOCOL
25 : uint32_t addr = 0;
26 : uint16_t sz = 0;
27 : state = this->gnssAlmanacReadAddrSize(&addr, &sz);
28 : RADIOLIB_ASSERT(state);
29 : RADIOLIB_DEBUG_BASIC_PRINTLN("Almanac@%08x, %d bytes", addr, sz);
30 : uint32_t buff[32] = { 0 };
31 : while(sz > 0) {
32 : size_t len = sz > 32 ? 32 : sz/sizeof(uint32_t);
33 : state = this->readRegMem32(addr, buff, len);
34 : RADIOLIB_ASSERT(state);
35 : RADIOLIB_DEBUG_HEXDUMP(NULL, reinterpret_cast<uint8_t*>(buff), len*sizeof(uint32_t), addr);
36 : addr += len*sizeof(uint32_t);
37 : sz -= len*sizeof(uint32_t);
38 : }
39 :
40 : uint8_t almanac[22] = { 0 };
41 : for(uint8_t i = 0; i < 128; i++) {
42 : RADIOLIB_DEBUG_BASIC_PRINTLN("Almanac[%d]:", i);
43 : state = this->gnssAlmanacReadSV(i, almanac);
44 : RADIOLIB_ASSERT(state);
45 : RADIOLIB_DEBUG_HEXDUMP(NULL, almanac, 22);
46 : }
47 :
48 : #endif
49 :
50 0 : return(state);
51 : }
52 :
53 0 : int16_t LR11x0::gnssScan(LR11x0GnssResult_t* res) {
54 0 : RADIOLIB_ASSERT_PTR(res);
55 :
56 : // go to standby
57 0 : int16_t state = standby();
58 0 : RADIOLIB_ASSERT(state);
59 :
60 : // set DIO mapping
61 0 : state = setDioIrqParams(RADIOLIB_LR11X0_IRQ_GNSS_DONE | RADIOLIB_LR11X0_IRQ_GNSS_ABORT);
62 0 : RADIOLIB_ASSERT(state);
63 :
64 : // set scan mode (single vs multiple)
65 0 : state = this->gnssSetMode(0x03);
66 0 : RADIOLIB_ASSERT(state);
67 :
68 : // set RF switch
69 0 : this->mod->setRfSwitchState(LR11x0::MODE_GNSS);
70 :
71 : // start scan with high effort
72 : RADIOLIB_DEBUG_BASIC_PRINTLN("GNSS scan start");
73 0 : state = this->gnssPerformScan(RADIOLIB_LR11X0_GNSS_EFFORT_MID, 0x3C, 16);
74 0 : RADIOLIB_ASSERT(state);
75 :
76 : // wait for scan finished or timeout
77 : // this can take very long if both GPS and BeiDou are enabled
78 0 : RadioLibTime_t softTimeout = 300UL * 1000UL;
79 0 : RadioLibTime_t start = this->mod->hal->millis();
80 0 : while(!this->mod->hal->digitalRead(this->mod->getIrq())) {
81 0 : this->mod->hal->yield();
82 0 : if(this->mod->hal->millis() - start > softTimeout) {
83 0 : this->gnssAbort();
84 : RADIOLIB_DEBUG_BASIC_PRINTLN("Timeout waiting for IRQ");
85 : }
86 : }
87 :
88 : // restore the switch
89 0 : this->mod->setRfSwitchState(Module::MODE_IDLE);
90 : RADIOLIB_DEBUG_BASIC_PRINTLN("GNSS scan done in %lu ms", (long unsigned int)(this->mod->hal->millis() - start));
91 :
92 : // distinguish between GNSS-done and GNSS-abort outcomes and clear the flags
93 0 : uint32_t irq = this->getIrqStatus();
94 0 : this->clearIrqState(RADIOLIB_LR11X0_IRQ_ALL);
95 0 : if(irq & RADIOLIB_LR11X0_IRQ_GNSS_ABORT) {
96 0 : return(RADIOLIB_ERR_RX_TIMEOUT);
97 : }
98 :
99 : // retrieve the demodulator status
100 0 : uint8_t info = 0;
101 0 : state = this->gnssReadDemodStatus(&res->demodStat, &info);
102 0 : RADIOLIB_ASSERT(state);
103 : RADIOLIB_DEBUG_BASIC_PRINTLN("Demod status %d, info %02x", (int)res->demodStat, (unsigned int)info);
104 :
105 : // retrieve the number of detected satellites
106 0 : state = this->gnssGetNbSvDetected(&res->numSatsDet);
107 0 : RADIOLIB_ASSERT(state);
108 :
109 : // retrieve the result size
110 0 : state = this->gnssGetResultSize(&res->resSize);
111 0 : RADIOLIB_ASSERT(state);
112 :
113 : // check and return demodulator status
114 0 : if(res->demodStat < RADIOLIB_LR11X0_GNSS_DEMOD_STATUS_TOW_FOUND) {
115 0 : return(RADIOLIB_ERR_GNSS_DEMOD(res->demodStat));
116 : }
117 :
118 0 : return(state);
119 : }
120 :
121 0 : int16_t LR11x0::getGnssAlmanacStatus(LR11x0GnssAlmanacStatus_t *stat) {
122 0 : RADIOLIB_ASSERT_PTR(stat);
123 :
124 : // save the time the time until subframe is relative to
125 0 : stat->start = this->mod->hal->millis();
126 :
127 : // get the raw data
128 0 : uint8_t raw[53] = { 0 };
129 0 : int16_t state = this->gnssReadAlmanacStatus(raw);
130 0 : RADIOLIB_ASSERT(state);
131 :
132 : // parse the reply
133 0 : stat->gps.status = (int8_t)raw[0];
134 0 : stat->gps.timeUntilSubframe = ((uint32_t)(raw[1]) << 24) | ((uint32_t)(raw[2]) << 16) | ((uint32_t)(raw[3]) << 8) | (uint32_t)raw[4];
135 0 : stat->gps.numSubframes = raw[5];
136 0 : stat->gps.nextSubframe4SvId = raw[6];
137 0 : stat->gps.nextSubframe5SvId = raw[7];
138 0 : stat->gps.nextSubframeStart = raw[8];
139 0 : stat->gps.numUpdateNeeded = raw[9];
140 0 : stat->gps.flagsUpdateNeeded[0] = ((uint32_t)(raw[10]) << 24) | ((uint32_t)(raw[11]) << 16) | ((uint32_t)(raw[12]) << 8) | (uint32_t)raw[13];
141 0 : stat->gps.flagsActive[0] = ((uint32_t)(raw[14]) << 24) | ((uint32_t)(raw[15]) << 16) | ((uint32_t)(raw[16]) << 8) | (uint32_t)raw[17];
142 0 : stat->beidou.status = (int8_t)raw[18];
143 0 : stat->beidou.timeUntilSubframe = ((uint32_t)(raw[19]) << 24) | ((uint32_t)(raw[20]) << 16) | ((uint32_t)(raw[21]) << 8) | (uint32_t)raw[22];
144 0 : stat->beidou.numSubframes = raw[23];
145 0 : stat->beidou.nextSubframe4SvId = raw[24];
146 0 : stat->beidou.nextSubframe5SvId = raw[25];
147 0 : stat->beidou.nextSubframeStart = raw[26];
148 0 : stat->beidou.numUpdateNeeded = raw[27];
149 0 : stat->beidou.flagsUpdateNeeded[0] = ((uint32_t)(raw[28]) << 24) | ((uint32_t)(raw[29]) << 16) | ((uint32_t)(raw[30]) << 8) | (uint32_t)raw[31];
150 0 : stat->beidou.flagsUpdateNeeded[1] = ((uint32_t)(raw[32]) << 24) | ((uint32_t)(raw[33]) << 16) | ((uint32_t)(raw[34]) << 8) | (uint32_t)raw[35];
151 0 : stat->beidou.flagsActive[0] = ((uint32_t)(raw[36]) << 24) | ((uint32_t)(raw[37]) << 16) | ((uint32_t)(raw[38]) << 8) | (uint32_t)raw[39];
152 0 : stat->beidou.flagsActive[1] = ((uint32_t)(raw[40]) << 24) | ((uint32_t)(raw[41]) << 16) | ((uint32_t)(raw[42]) << 8) | (uint32_t)raw[43];
153 0 : stat->beidouSvNoAlmanacFlags[0] = ((uint32_t)(raw[44]) << 24) | ((uint32_t)(raw[45]) << 16) | ((uint32_t)(raw[46]) << 8) | (uint32_t)raw[47];
154 0 : stat->beidouSvNoAlmanacFlags[1] = ((uint32_t)(raw[18]) << 24) | ((uint32_t)(raw[49]) << 16) | ((uint32_t)(raw[50]) << 8) | (uint32_t)raw[51];
155 0 : stat->nextAlmanacId = raw[52];
156 :
157 0 : return(state);
158 : }
159 :
160 0 : int16_t LR11x0::gnssDelayUntilSubframe(LR11x0GnssAlmanacStatus_t *stat, uint8_t constellation) {
161 0 : RADIOLIB_ASSERT_PTR(stat);
162 :
163 : // almanac update has to be called at least 1.3 seconds before the subframe
164 : // we use 2.3 seconds to be on the safe side
165 :
166 : // calculate absolute times
167 0 : RadioLibTime_t window = stat->start + stat->gps.timeUntilSubframe - 2300;
168 0 : if(constellation == RADIOLIB_LR11X0_GNSS_CONSTELLATION_BEIDOU) {
169 0 : window = stat->start + stat->beidou.timeUntilSubframe - 2300;
170 : }
171 0 : RadioLibTime_t now = this->mod->hal->millis();
172 0 : if(now > window) {
173 : // we missed the window
174 0 : return(RADIOLIB_ERR_GNSS_SUBFRAME_NOT_AVAILABLE);
175 : }
176 :
177 0 : RadioLibTime_t delay = window - now;
178 : RADIOLIB_DEBUG_BASIC_PRINTLN("Time until subframe %lu ms", delay);
179 0 : this->mod->hal->delay(delay);
180 0 : return(RADIOLIB_ERR_NONE);
181 : }
182 :
183 : // TODO fix last satellite always out of date
184 0 : int16_t LR11x0::updateGnssAlmanac(uint8_t constellation) {
185 0 : int16_t state = this->setDioIrqParams(RADIOLIB_LR11X0_IRQ_GNSS_DONE | RADIOLIB_LR11X0_IRQ_GNSS_ABORT);
186 0 : RADIOLIB_ASSERT(state);
187 :
188 0 : state = this->gnssAlmanacUpdateFromSat(RADIOLIB_LR11X0_GNSS_EFFORT_MID, constellation);
189 0 : RADIOLIB_ASSERT(state);
190 :
191 : // wait for scan finished or timeout, assumes 2 subframes and up to 2.3s pre-roll
192 0 : uint32_t softTimeout = 16UL * 1000UL;
193 0 : uint32_t start = this->mod->hal->millis();
194 0 : while (!this->mod->hal->digitalRead(this->mod->getIrq())) {
195 0 : this->mod->hal->yield();
196 0 : if(this->mod->hal->millis() - start > softTimeout) {
197 0 : this->gnssAbort();
198 : RADIOLIB_DEBUG_BASIC_PRINTLN("Timeout waiting for almanac update");
199 : }
200 : }
201 :
202 : RADIOLIB_DEBUG_BASIC_PRINTLN("GPS almanac update done in %lu ms", (long unsigned int)(this->mod->hal->millis() - start));
203 :
204 : // distinguish between GNSS-done and GNSS-abort outcomes and clear the flags
205 0 : uint32_t irq = this->getIrqStatus();
206 0 : this->clearIrqState(RADIOLIB_LR11X0_IRQ_ALL);
207 0 : if(irq & RADIOLIB_LR11X0_IRQ_GNSS_ABORT) {
208 0 : state = RADIOLIB_ERR_RX_TIMEOUT;
209 : }
210 :
211 0 : return(state);
212 : }
213 :
214 0 : int16_t LR11x0::getGnssPosition(LR11x0GnssPosition_t* pos, bool filtered) {
215 0 : RADIOLIB_ASSERT_PTR(pos);
216 :
217 0 : uint8_t error = 0;
218 : int16_t state;
219 0 : if(filtered) {
220 0 : state = this->gnssReadDopplerSolverRes(&error, &pos->numSatsUsed, NULL, NULL, NULL, NULL, &pos->latitude, &pos->longitude, &pos->accuracy, NULL);
221 : } else {
222 0 : state = this->gnssReadDopplerSolverRes(&error, &pos->numSatsUsed, &pos->latitude, &pos->longitude, &pos->accuracy, NULL, NULL, NULL, NULL, NULL);
223 : }
224 0 : RADIOLIB_ASSERT(state);
225 :
226 : // check the solver error
227 0 : if(error != 0) {
228 0 : return(RADIOLIB_ERR_GNSS_SOLVER(error));
229 : }
230 :
231 0 : return(state);
232 : }
233 :
234 0 : int16_t LR11x0::getGnssSatellites(LR11x0GnssSatellite_t* sats, uint8_t numSats) {
235 0 : RADIOLIB_ASSERT_PTR(sats);
236 0 : if(numSats >= 32) {
237 0 : return(RADIOLIB_ERR_MEMORY_ALLOCATION_FAILED);
238 : }
239 :
240 0 : uint8_t svId[32] = { 0 };
241 0 : uint8_t snr[32] = { 0 };
242 0 : int16_t doppler[32] = { 0 };
243 0 : int16_t state = this->gnssGetSvDetected(svId, snr, doppler, numSats);
244 0 : RADIOLIB_ASSERT(state);
245 0 : for(size_t i = 0; i < numSats; i++) {
246 0 : sats[i].svId = svId[i];
247 0 : sats[i].c_n0 = snr[i] + 31;
248 0 : sats[i].doppler = doppler[i];
249 : }
250 :
251 0 : return(state);
252 : }
253 :
254 0 : int16_t LR11x0::gnssReadRssi(int8_t* rssi) {
255 0 : uint8_t reqBuff[1] = { 0x09 }; // some undocumented magic byte, from the official driver
256 0 : uint8_t rplBuff[2] = { 0 };
257 0 : int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_READ_RSSI, false, rplBuff, sizeof(rplBuff), reqBuff, sizeof(reqBuff));
258 0 : RADIOLIB_ASSERT(state);
259 0 : if(rssi) { *rssi = rplBuff[1]; }
260 0 : return(state);
261 : }
262 :
263 0 : int16_t LR11x0::gnssSetConstellationToUse(uint8_t mask) {
264 0 : uint8_t buff[1] = { mask };
265 0 : return(this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_SET_CONSTELLATION_TO_USE, true, buff, sizeof(buff)));
266 : }
267 :
268 0 : int16_t LR11x0::gnssReadConstellationToUse(uint8_t* mask) {
269 0 : uint8_t buff[1] = { 0 };
270 0 : int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_READ_CONSTELLATION_TO_USE, false, buff, sizeof(buff));
271 :
272 : // pass the replies
273 0 : if(mask) { *mask = buff[0]; }
274 :
275 0 : return(state);
276 : }
277 :
278 0 : int16_t LR11x0::gnssSetAlmanacUpdate(uint8_t mask) {
279 0 : uint8_t buff[1] = { mask };
280 0 : return(this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_SET_ALMANAC_UPDATE, true, buff, sizeof(buff)));
281 : }
282 :
283 0 : int16_t LR11x0::gnssReadAlmanacUpdate(uint8_t* mask) {
284 0 : uint8_t buff[1] = { 0 };
285 0 : int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_READ_ALMANAC_UPDATE, false, buff, sizeof(buff));
286 :
287 : // pass the replies
288 0 : if(mask) { *mask = buff[0]; }
289 :
290 0 : return(state);
291 : }
292 :
293 0 : int16_t LR11x0::gnssSetFreqSearchSpace(uint8_t freq) {
294 0 : uint8_t buff[1] = { freq };
295 0 : return(this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_SET_FREQ_SEARCH_SPACE, true, buff, sizeof(buff)));
296 : }
297 :
298 0 : int16_t LR11x0::gnssReadFreqSearchSpace(uint8_t* freq) {
299 0 : uint8_t buff[1] = { 0 };
300 0 : int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_READ_FREQ_SEARCH_SPACE, false, buff, sizeof(buff));
301 0 : if(freq) { *freq = buff[0]; }
302 0 : return(state);
303 : }
304 :
305 0 : int16_t LR11x0::gnssReadVersion(uint8_t* fw, uint8_t* almanac) {
306 0 : uint8_t buff[2] = { 0 };
307 0 : int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_READ_VERSION, false, buff, sizeof(buff));
308 :
309 : // pass the replies
310 0 : if(fw) { *fw = buff[0]; }
311 0 : if(almanac) { *almanac = buff[1]; }
312 :
313 0 : return(state);
314 : }
315 :
316 0 : int16_t LR11x0::gnssReadSupportedConstellations(uint8_t* mask) {
317 0 : uint8_t buff[1] = { 0 };
318 0 : int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_READ_SUPPORTED_CONSTELLATIONS, false, buff, sizeof(buff));
319 :
320 : // pass the replies
321 0 : if(mask) { *mask = buff[0]; }
322 :
323 0 : return(state);
324 : }
325 :
326 0 : int16_t LR11x0::gnssSetMode(uint8_t mode) {
327 0 : uint8_t buff[1] = { mode };
328 0 : return(this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_SET_MODE, true, buff, sizeof(buff)));
329 : }
330 :
331 0 : int16_t LR11x0::gnssAutonomous(uint32_t gpsTime, uint8_t resMask, uint8_t nbSvMask) {
332 0 : uint8_t buff[7] = {
333 0 : (uint8_t)((gpsTime >> 24) & 0xFF), (uint8_t)((gpsTime >> 16) & 0xFF),
334 0 : (uint8_t)((gpsTime >> 8) & 0xFF), (uint8_t)(gpsTime & 0xFF),
335 : RADIOLIB_LR11X0_GNSS_AUTO_EFFORT_MODE, resMask, nbSvMask
336 0 : };
337 0 : return(this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_AUTONOMOUS, true, buff, sizeof(buff)));
338 : }
339 :
340 0 : int16_t LR11x0::gnssAssisted(uint32_t gpsTime, uint8_t effort, uint8_t resMask, uint8_t nbSvMask) {
341 : uint8_t buff[7] = {
342 0 : (uint8_t)((gpsTime >> 24) & 0xFF), (uint8_t)((gpsTime >> 16) & 0xFF),
343 0 : (uint8_t)((gpsTime >> 8) & 0xFF), (uint8_t)(gpsTime & 0xFF),
344 : effort, resMask, nbSvMask
345 0 : };
346 0 : return(this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_ASSISTED, true, buff, sizeof(buff)));
347 : }
348 :
349 0 : int16_t LR11x0::gnssSetAssistancePosition(float lat, float lon) {
350 0 : int16_t latRaw = (lat*2048.0f)/90.0f + 0.5f;
351 0 : int16_t lonRaw = (lon*2048.0f)/180.0f + 0.5f;
352 : uint8_t buff[4] = {
353 0 : (uint8_t)((latRaw >> 8) & 0xFF), (uint8_t)(latRaw & 0xFF),
354 0 : (uint8_t)((lonRaw >> 8) & 0xFF), (uint8_t)(lonRaw & 0xFF),
355 0 : };
356 0 : return(this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_SET_ASSISTANCE_POSITION, true, buff, sizeof(buff)));
357 : }
358 :
359 0 : int16_t LR11x0::gnssReadAssistancePosition(float* lat, float* lon) {
360 0 : uint8_t buff[4] = { 0 };
361 0 : int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_READ_ASSISTANCE_POSITION, false, buff, sizeof(buff));
362 :
363 : // pass the replies
364 0 : if(lat) {
365 0 : int16_t latRaw = ((int16_t)(buff[0]) << 8) | (int16_t)(buff[1]);
366 0 : *lat = ((float)latRaw*90.0f)/2048.0f;
367 : }
368 0 : if(lon) {
369 0 : int16_t lonRaw = ((int16_t)(buff[2]) << 8) | (int16_t)(buff[3]);
370 0 : *lon = ((float)lonRaw*180.0f)/2048.0f;
371 : }
372 :
373 0 : return(state);
374 : }
375 :
376 0 : int16_t LR11x0::gnssPushSolverMsg(uint8_t* payload, size_t len) {
377 0 : return(this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_PUSH_SOLVER_MSG, true, payload, len));
378 : }
379 :
380 0 : int16_t LR11x0::gnssPushDmMsg(uint8_t* payload, size_t len) {
381 0 : return(this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_PUSH_DM_MSG, true, payload, len));
382 : }
383 :
384 0 : int16_t LR11x0::gnssGetContextStatus(uint8_t* fwVersion, uint32_t* almanacCrc, uint8_t* errCode, uint8_t* almUpdMask, uint8_t* freqSpace) {
385 : // send the command - datasheet here shows extra bytes being sent in the request
386 : // but doing that fails so treat it like any other read command
387 0 : uint8_t buff[9] = { 0 };
388 0 : int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_GET_CONTEXT_STATUS, false, buff, sizeof(buff));
389 :
390 : // pass the replies
391 0 : if(fwVersion) { *fwVersion = buff[2]; }
392 0 : if(almanacCrc) { *almanacCrc = ((uint32_t)(buff[3]) << 24) | ((uint32_t)(buff[4]) << 16) | ((uint32_t)(buff[5]) << 8) | (uint32_t)buff[6]; }
393 0 : if(errCode) { *errCode = (buff[7] & 0xF0) >> 4; }
394 0 : if(almUpdMask) { *almUpdMask = (buff[7] & 0x0E) >> 1; }
395 0 : if(freqSpace) { *freqSpace = ((buff[7] & 0x01) << 1) | ((buff[8] & 0x80) >> 7); }
396 :
397 0 : return(state);
398 : }
399 :
400 0 : int16_t LR11x0::gnssGetNbSvDetected(uint8_t* nbSv) {
401 0 : uint8_t buff[1] = { 0 };
402 0 : int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_GET_NB_SV_DETECTED, false, buff, sizeof(buff));
403 :
404 : // pass the replies
405 0 : if(nbSv) { *nbSv = buff[0]; }
406 :
407 0 : return(state);
408 : }
409 :
410 0 : int16_t LR11x0::gnssGetSvDetected(uint8_t* svId, uint8_t* snr, int16_t* doppler, size_t nbSv) {
411 : // TODO this is arbitrary - is there an actual maximum?
412 0 : if(nbSv > RADIOLIB_LR11X0_SPI_MAX_READ_WRITE_LEN/sizeof(uint32_t)) {
413 0 : return(RADIOLIB_ERR_SPI_CMD_INVALID);
414 : }
415 :
416 : // build buffers
417 0 : size_t buffLen = nbSv*sizeof(uint32_t);
418 : #if RADIOLIB_STATIC_ONLY
419 : uint8_t dataBuff[RADIOLIB_LR11X0_SPI_MAX_READ_WRITE_LEN];
420 : #else
421 0 : uint8_t* dataBuff = new uint8_t[buffLen];
422 : #endif
423 :
424 0 : int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_GET_SV_DETECTED, false, dataBuff, buffLen);
425 0 : if(state == RADIOLIB_ERR_NONE) {
426 0 : for(size_t i = 0; i < nbSv; i++) {
427 0 : if(svId) { svId[i] = dataBuff[4*i]; }
428 0 : if(snr) { snr[i] = dataBuff[4*i + 1]; }
429 0 : if(doppler) { doppler[i] = ((uint16_t)(dataBuff[4*i + 2]) << 8) | (uint16_t)dataBuff[4*i + 3]; }
430 : }
431 : }
432 :
433 : #if !RADIOLIB_STATIC_ONLY
434 0 : delete[] dataBuff;
435 : #endif
436 0 : return(state);
437 : }
438 :
439 0 : int16_t LR11x0::gnssGetConsumption(uint32_t* cpu, uint32_t* radio) {
440 0 : uint8_t buff[8] = { 0 };
441 0 : int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_GET_CONSUMPTION, false, buff, sizeof(buff));
442 :
443 : // pass the replies
444 0 : if(cpu) { *cpu = ((uint32_t)(buff[0]) << 24) | ((uint32_t)(buff[1]) << 16) | ((uint32_t)(buff[2]) << 8) | (uint32_t)buff[3]; }
445 0 : if(radio) { *radio = ((uint32_t)(buff[4]) << 24) | ((uint32_t)(buff[5]) << 16) | ((uint32_t)(buff[6]) << 8) | (uint32_t)buff[7]; }
446 :
447 0 : return(state);
448 : }
449 :
450 0 : int16_t LR11x0::gnssGetResultSize(uint16_t* size) {
451 0 : uint8_t buff[2] = { 0 };
452 0 : int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_GET_RESULT_SIZE, false, buff, sizeof(buff));
453 :
454 : // pass the replies
455 0 : if(size) { *size = ((uint16_t)(buff[0]) << 8) | (uint16_t)buff[1]; }
456 :
457 0 : return(state);
458 : }
459 :
460 0 : int16_t LR11x0::gnssReadResults(uint8_t* result, uint16_t size) {
461 0 : RADIOLIB_ASSERT_PTR(result);
462 0 : return(this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_READ_RESULTS, false, result, size));
463 : }
464 :
465 0 : int16_t LR11x0::gnssAlmanacFullUpdateHeader(uint16_t date, uint32_t globalCrc) {
466 0 : uint8_t buff[RADIOLIB_LR11X0_GNSS_ALMANAC_BLOCK_SIZE] = {
467 : RADIOLIB_LR11X0_GNSS_ALMANAC_HEADER_ID,
468 0 : (uint8_t)((date >> 8) & 0xFF), (uint8_t)(date & 0xFF),
469 0 : (uint8_t)((globalCrc >> 24) & 0xFF), (uint8_t)((globalCrc >> 16) & 0xFF),
470 0 : (uint8_t)((globalCrc >> 8) & 0xFF), (uint8_t)(globalCrc & 0xFF),
471 0 : };
472 0 : return(this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_ALMANAC_FULL_UPDATE, true, buff, sizeof(buff)));
473 : }
474 :
475 0 : int16_t LR11x0::gnssAlmanacFullUpdateSV(uint8_t svn, const uint8_t* svnAlmanac) {
476 0 : uint8_t buff[RADIOLIB_LR11X0_GNSS_ALMANAC_BLOCK_SIZE] = { svn };
477 0 : memcpy(&buff[1], svnAlmanac, RADIOLIB_LR11X0_GNSS_ALMANAC_BLOCK_SIZE - 1);
478 0 : return(this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_ALMANAC_FULL_UPDATE, true, buff, sizeof(buff)));
479 : }
480 :
481 0 : int16_t LR11x0::gnssAlmanacReadAddrSize(uint32_t* addr, uint16_t* size) {
482 0 : uint8_t buff[6] = { 0 };
483 0 : int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_ALMANAC_READ_ADDR_SIZE, false, buff, sizeof(buff));
484 :
485 0 : if(addr) { *addr = ((uint32_t)(buff[0]) << 24) | ((uint32_t)(buff[1]) << 16) | ((uint32_t)(buff[2]) << 8) | (uint32_t)buff[3]; }
486 0 : if(size) { *size = ((uint16_t)(buff[4]) << 8) | (uint16_t)buff[5]; }
487 :
488 0 : return(state);
489 : }
490 :
491 0 : int16_t LR11x0::gnssAlmanacReadSV(uint8_t svId, uint8_t* almanac) {
492 0 : uint8_t reqBuff[2] = { svId, 0x01 }; // in theory multiple SV entries can be read at the same time, but we don't need that
493 0 : int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_READ_ALMANAC_PER_SATELLITE, false, almanac, 22, reqBuff, sizeof(reqBuff));
494 0 : RADIOLIB_ASSERT(state);
495 0 : return(state);
496 : }
497 :
498 0 : int16_t LR11x0::gnssGetNbSvVisible(uint32_t time, float lat, float lon, uint8_t constellation, uint8_t* nbSv) {
499 0 : int16_t latRaw = (lat*2048.0f)/90.0f + 0.5f;
500 0 : int16_t lonRaw = (lon*2048.0f)/180.0f + 0.5f;
501 : uint8_t reqBuff[9] = {
502 0 : (uint8_t)((time >> 24) & 0xFF), (uint8_t)((time >> 16) & 0xFF),
503 0 : (uint8_t)((time >> 8) & 0xFF), (uint8_t)(time & 0xFF),
504 0 : (uint8_t)((latRaw >> 8) & 0xFF), (uint8_t)(latRaw & 0xFF),
505 0 : (uint8_t)((lonRaw >> 8) & 0xFF), (uint8_t)(lonRaw & 0xFF),
506 : constellation,
507 0 : };
508 0 : return(this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_GET_SV_VISIBLE, false, nbSv, 1, reqBuff, sizeof(reqBuff)));
509 : }
510 :
511 0 : int16_t LR11x0::gnssGetSvVisible(uint8_t nbSv, uint8_t** svId, int16_t** doppler, int16_t** dopplerErr) {
512 : // enforce a maximum of 12 SVs
513 0 : if(nbSv > 12) {
514 0 : return(RADIOLIB_ERR_SPI_CMD_INVALID);
515 : }
516 :
517 0 : uint8_t buff[60] = { 0 };
518 0 : int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_GET_SV_VISIBLE_DOPPLER, false, buff, sizeof(buff));
519 0 : for(uint8_t i = 0; i < nbSv; i++) {
520 0 : if(svId && svId[i]) { *svId[i] = buff[i*12]; }
521 0 : if(doppler && doppler[i]) { *doppler[i] = ((uint16_t)(buff[i*12 + 1]) << 8) | (uint16_t)buff[i*12 + 2]; }
522 0 : if(dopplerErr && dopplerErr[i]) { *dopplerErr[i] = ((uint16_t)(buff[i*12 + 3]) << 8) | (uint16_t)buff[i*12 + 4]; }
523 : }
524 :
525 0 : return(state);
526 : }
527 :
528 0 : int16_t LR11x0::gnssPerformScan(uint8_t effort, uint8_t resMask, uint8_t nbSvMax) {
529 0 : uint8_t buff[3] = { effort, resMask, nbSvMax };
530 : // call the SPI write stream directly to skip waiting for BUSY - it will be set to high once the scan starts
531 0 : return(this->mod->SPIwriteStream(RADIOLIB_LR11X0_CMD_GNSS_SCAN, buff, sizeof(buff), false, false));
532 : }
533 :
534 0 : int16_t LR11x0::gnssReadLastScanModeLaunched(uint8_t* lastScanMode) {
535 0 : uint8_t buff[1] = { 0 };
536 0 : int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_READ_LAST_SCAN_MODE_LAUNCHED, false, buff, sizeof(buff));
537 :
538 : // pass the replies
539 0 : if(lastScanMode) { *lastScanMode = buff[0]; }
540 :
541 0 : return(state);
542 : }
543 :
544 0 : int16_t LR11x0::gnssFetchTime(uint8_t effort, uint8_t opt) {
545 0 : uint8_t buff[2] = { effort, opt };
546 : // call the SPI write stream directly to skip waiting for BUSY - it will be set to high once the scan starts
547 0 : return(this->mod->SPIwriteStream(RADIOLIB_LR11X0_CMD_GNSS_FETCH_TIME, buff, sizeof(buff), false, false));
548 : }
549 :
550 0 : int16_t LR11x0::gnssReadTime(uint8_t* err, uint32_t* time, uint32_t* nbUs, uint32_t* timeAccuracy) {
551 0 : uint8_t buff[12] = { 0 };
552 0 : int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_READ_TIME, false, buff, sizeof(buff));
553 :
554 : // pass the replies
555 0 : if(err) { *err = buff[0]; }
556 :
557 0 : if(time) {
558 0 : *time = ((uint32_t)(buff[1]) << 24) | ((uint32_t)(buff[2]) << 16) | ((uint32_t)(buff[3]) << 8) | (uint32_t)buff[4];
559 0 : *time += 2UL*1024UL*7UL*24UL*3600UL; // assume WN rollover is at 2, this will fail sometime in 2038
560 0 : *time += 315964800UL; // convert to UTC
561 : }
562 :
563 0 : if(nbUs) {
564 0 : *nbUs = ((uint32_t)(buff[5]) << 16) | ((uint32_t)(buff[6]) << 8) | (uint32_t)buff[7];
565 0 : *nbUs /= 16;
566 : }
567 :
568 0 : if(timeAccuracy) {
569 0 : *timeAccuracy = ((uint32_t)(buff[8]) << 24) | ((uint32_t)(buff[9]) << 16) | ((uint32_t)(buff[10]) << 8) | (uint32_t)buff[11];
570 0 : *timeAccuracy /= 16;
571 : }
572 :
573 0 : return(state);
574 : }
575 :
576 0 : int16_t LR11x0::gnssResetTime(void) {
577 0 : return(this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_RESET_TIME, true, NULL, 0));
578 : }
579 :
580 0 : int16_t LR11x0::gnssResetPosition(void) {
581 0 : return(this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_RESET_POSITION, true, NULL, 0));
582 : }
583 :
584 0 : int16_t LR11x0::gnssReadWeekNumberRollover(uint8_t* status, uint8_t* rollover) {
585 0 : uint8_t buff[2] = { 0 };
586 0 : int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_READ_WEEK_NUMBER_ROLLOWER, false, buff, sizeof(buff));
587 0 : if(status) { *status = buff[0]; }
588 0 : if(rollover) { *rollover = buff[1]; }
589 0 : return(state);
590 : }
591 :
592 0 : int16_t LR11x0::gnssReadDemodStatus(int8_t* status, uint8_t* info) {
593 0 : uint8_t buff[2] = { 0 };
594 0 : int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_READ_DEMOD_STATUS, false, buff, sizeof(buff));
595 :
596 : // pass the replies
597 0 : if(status) { *status = (int8_t)buff[0]; }
598 0 : if(info) { *info = buff[1]; }
599 :
600 0 : return(state);
601 : }
602 :
603 0 : int16_t LR11x0::gnssReadCumulTiming(uint32_t* timing, uint8_t* constDemod) {
604 0 : uint8_t rplBuff[125] = { 0 };
605 0 : int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_READ_CUMUL_TIMING, false, rplBuff, 125);
606 0 : RADIOLIB_ASSERT(state);
607 :
608 : // convert endians
609 0 : if(timing) {
610 0 : for(size_t i = 0; i < 31; i++) {
611 0 : timing[i] = ((uint32_t)rplBuff[i*sizeof(uint32_t)] << 24) | ((uint32_t)rplBuff[1 + i*sizeof(uint32_t)] << 16) | ((uint32_t)rplBuff[2 + i*sizeof(uint32_t)] << 8) | (uint32_t)rplBuff[3 + i*sizeof(uint32_t)];
612 : }
613 : }
614 :
615 0 : if(constDemod) { *constDemod = rplBuff[124]; }
616 :
617 0 : return(state);
618 : }
619 :
620 0 : int16_t LR11x0::gnssSetTime(uint32_t time, uint16_t accuracy) {
621 : uint8_t buff[6] = {
622 0 : (uint8_t)((time >> 24) & 0xFF), (uint8_t)((time >> 16) & 0xFF),
623 0 : (uint8_t)((time >> 8) & 0xFF), (uint8_t)(time & 0xFF),
624 0 : (uint8_t)((accuracy >> 8) & 0xFF), (uint8_t)(accuracy & 0xFF),
625 0 : };
626 0 : return(this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_SET_TIME, true, buff, sizeof(buff)));
627 : }
628 :
629 0 : int16_t LR11x0::gnssReadDopplerSolverRes(uint8_t* error, uint8_t* nbSvUsed, float* lat, float* lon, uint16_t* accuracy, uint16_t* xtal, float* latFilt, float* lonFilt, uint16_t* accuracyFilt, uint16_t* xtalFilt) {
630 0 : uint8_t buff[18] = { 0 };
631 0 : int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_READ_DOPPLER_SOLVER_RES, false, buff, sizeof(buff));
632 :
633 : // pass the replies
634 0 : if(error) { *error = buff[0]; }
635 0 : if(nbSvUsed) { *nbSvUsed = buff[1]; }
636 0 : if(lat) {
637 0 : int16_t latRaw = ((int16_t)(buff[2]) << 8) | (int16_t)buff[3];
638 0 : *lat = ((float)latRaw * 90.0f)/2048.0f;
639 : }
640 0 : if(lon) {
641 0 : int16_t lonRaw = ((int16_t)(buff[4]) << 8) | (int16_t)buff[5];
642 0 : *lon = ((float)lonRaw * 180.0f)/2048.0f;
643 : }
644 0 : if(accuracy) { *accuracy = ((uint16_t)(buff[6]) << 8) | (uint16_t)buff[7]; }
645 0 : if(xtal) { *xtal = ((uint16_t)(buff[8]) << 8) | (uint16_t)buff[9]; }
646 0 : if(latFilt) {
647 0 : int16_t latRaw = ((int16_t)(buff[10]) << 8) | (int16_t)buff[11];
648 0 : *latFilt = ((float)latRaw * 90.0f)/2048.0f;
649 : }
650 0 : if(lonFilt) {
651 0 : int16_t lonRaw = ((int16_t)(buff[12]) << 8) | (int16_t)buff[13];
652 0 : *lonFilt = ((float)lonRaw * 180.0f)/2048.0f;
653 : }
654 0 : if(accuracyFilt) { *accuracyFilt = ((uint16_t)(buff[14]) << 8) | (uint16_t)buff[15]; }
655 0 : if(xtalFilt) { *xtalFilt = ((uint16_t)(buff[16]) << 8) | (uint16_t)buff[17]; }
656 :
657 0 : return(state);
658 : }
659 :
660 0 : int16_t LR11x0::gnssReadDelayResetAP(uint32_t* delay) {
661 0 : uint8_t buff[3] = { 0 };
662 0 : int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_READ_DELAY_RESET_AP, false, buff, sizeof(buff));
663 :
664 0 : if(delay) { *delay = ((uint32_t)(buff[0]) << 16) | ((uint32_t)(buff[1]) << 8) | (uint32_t)buff[2]; }
665 :
666 0 : return(state);
667 : }
668 :
669 0 : int16_t LR11x0::gnssAlmanacUpdateFromSat(uint8_t effort, uint8_t bitMask) {
670 0 : uint8_t buff[2] = { effort, bitMask };
671 : // call the SPI write stream directly to skip waiting for BUSY - it will be set to high once the scan starts
672 0 : return(this->mod->SPIwriteStream(RADIOLIB_LR11X0_CMD_GNSS_ALMANAC_UPDATE_FROM_SAT, buff, sizeof(buff), false, false));
673 : }
674 :
675 0 : int16_t LR11x0::gnssReadKeepSyncStatus(uint8_t mask, uint8_t* nbSvVisible, uint32_t* elapsed) {
676 0 : uint8_t reqBuff[1] = { mask };
677 0 : uint8_t rplBuff[5] = { 0 };
678 0 : int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_READ_KEEP_SYNC_STATUS, false, rplBuff, sizeof(rplBuff), reqBuff, sizeof(reqBuff));
679 0 : RADIOLIB_ASSERT(state);
680 0 : if(nbSvVisible) { *nbSvVisible = rplBuff[0]; }
681 0 : if(elapsed) { *elapsed = ((uint32_t)(rplBuff[1]) << 24) | ((uint32_t)(rplBuff[2]) << 16) | ((uint32_t)(rplBuff[3]) << 8) | (uint32_t)rplBuff[4]; }
682 0 : return(state);
683 : }
684 :
685 0 : int16_t LR11x0::gnssReadAlmanacStatus(uint8_t* status) {
686 0 : return(this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_READ_ALMANAC_STATUS, false, status, 53));
687 : }
688 :
689 0 : int16_t LR11x0::gnssConfigAlmanacUpdatePeriod(uint8_t bitMask, uint8_t svType, uint16_t period) {
690 0 : uint8_t buff[4] = { bitMask, svType, (uint8_t)((period >> 8) & 0xFF), (uint8_t)(period & 0xFF) };
691 0 : return(this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_CONFIG_ALMANAC_UPDATE_PERIOD, true, buff, sizeof(buff)));
692 : }
693 :
694 0 : int16_t LR11x0::gnssReadAlmanacUpdatePeriod(uint8_t bitMask, uint8_t svType, uint16_t* period) {
695 0 : uint8_t reqBuff[2] = { bitMask, svType };
696 0 : uint8_t rplBuff[2] = { 0 };
697 0 : int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_READ_ALMANAC_UPDATE_PERIOD, false, rplBuff, sizeof(rplBuff), reqBuff, sizeof(reqBuff));
698 0 : RADIOLIB_ASSERT(state);
699 :
700 0 : if(period) { *period = ((uint16_t)(rplBuff[0]) << 8) | (uint16_t)rplBuff[1]; }
701 :
702 0 : return(state);
703 : }
704 :
705 0 : int16_t LR11x0::gnssConfigDelayResetAP(uint32_t delay) {
706 0 : uint8_t buff[3] = { (uint8_t)((delay >> 16) & 0xFF), (uint8_t)((delay >> 8) & 0xFF), (uint8_t)(delay & 0xFF) };
707 0 : return(this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_CONFIG_DELAY_RESET_AP, true, buff, sizeof(buff)));
708 : }
709 :
710 0 : int16_t LR11x0::gnssGetSvWarmStart(uint8_t bitMask, uint8_t* sv, uint8_t nbVisSat) {
711 0 : uint8_t reqBuff[1] = { bitMask };
712 0 : return(this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_GET_SV_WARM_START, false, sv, nbVisSat, reqBuff, sizeof(reqBuff)));
713 : }
714 :
715 0 : int16_t LR11x0::gnssGetSvSync(uint8_t mask, uint8_t nbSv, uint8_t* syncList) {
716 0 : uint8_t reqBuff[2] = { mask, nbSv };
717 0 : return(this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_GET_SV_SYNC, false, syncList, nbSv, reqBuff, sizeof(reqBuff)));
718 : }
719 :
720 0 : int16_t LR11x0::gnssReadWarmStartStatus(uint8_t bitMask, uint8_t* nbVisSat, uint32_t* timeElapsed) {
721 0 : uint8_t reqBuff[1] = { bitMask };
722 0 : uint8_t rplBuff[5] = { 0 };
723 0 : int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_READ_WARM_START_STATUS, false, rplBuff, sizeof(rplBuff), reqBuff, sizeof(reqBuff));
724 0 : RADIOLIB_ASSERT(state);
725 :
726 0 : if(nbVisSat) { *nbVisSat = rplBuff[0]; }
727 0 : if(timeElapsed) { *timeElapsed = ((uint32_t)(rplBuff[1]) << 24) | ((uint32_t)(rplBuff[2]) << 16) | ((uint32_t)(rplBuff[3]) << 8) | (uint32_t)rplBuff[4]; }
728 :
729 0 : return(state);
730 : }
731 :
732 0 : int16_t LR11x0::gnssWriteBitMaskSatActivated(uint8_t bitMask, uint32_t* bitMaskActivated0, uint32_t* bitMaskActivated1) {
733 0 : uint8_t reqBuff[1] = { bitMask };
734 0 : uint8_t rplBuff[8] = { 0 };
735 0 : size_t rplLen = (bitMask & 0x01) ? 8 : 4; // GPS only has the first bit mask
736 0 : int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_WRITE_BIT_MASK_SAT_ACTIVATED, false, rplBuff, rplLen, reqBuff, sizeof(reqBuff));
737 0 : RADIOLIB_ASSERT(state);
738 :
739 0 : if(bitMaskActivated0) { *bitMaskActivated0 = ((uint32_t)(rplBuff[0]) << 24) | ((uint32_t)(rplBuff[1]) << 16) | ((uint32_t)(rplBuff[2]) << 8) | (uint32_t)rplBuff[3]; }
740 0 : if(bitMaskActivated1) { *bitMaskActivated1 = ((uint32_t)(rplBuff[4]) << 24) | ((uint32_t)(rplBuff[5]) << 16) | ((uint32_t)(rplBuff[6]) << 8) | (uint32_t)rplBuff[7]; }
741 :
742 0 : return(state);
743 : }
744 :
745 0 : void LR11x0::gnssAbort() {
746 : // send the abort signal (single NOP)
747 0 : this->mod->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_CMD] = Module::BITS_8;
748 : // we need to call the most basic overload of the SPI write method otherwise the call will be ambiguous
749 0 : const uint8_t cmd[2] = { 0, 0 };
750 0 : this->mod->SPIwriteStream(cmd, 2, NULL, 0, false, false);
751 0 : this->mod->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_CMD] = Module::BITS_16;
752 :
753 : // wait for at least 2.9 seconds as specified by the user manual
754 0 : this->mod->hal->delay(3000);
755 0 : }
756 :
757 : #endif
|