Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
Dependencies: FXOS8700CQ MODSERIAL mbed
Fork of Avnet_ATT_Cellular_IOT by
wnc_control.cpp
00001 /* =================================================================== 00002 Copyright c 2016, AVNET Inc. 00003 00004 Licensed under the Apache License, Version 2.0 (the "License"); 00005 you may not use this file except in compliance with the License. 00006 You may obtain a copy of the License at 00007 00008 http://www.apache.org/licenses/LICENSE-2.0 00009 00010 Unless required by applicable law or agreed to in writing, 00011 software distributed under the License is distributed on an 00012 "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 00013 either express or implied. See the License for the specific 00014 language governing permissions and limitations under the License. 00015 00016 ======================================================================== */ 00017 00018 #include "mbed.h" 00019 #include <cctype> 00020 #include <string> 00021 #include "config_me.h" 00022 #include "wnc_control.h" 00023 #include "hardware.h" 00024 00025 // Outputs detailed WNC command info 00026 #define WNC_CMD_DEBUG_ON 00027 00028 // Full debug output, longer cmds and extra cellular status checking 00029 #undef WNC_CMD_DEBUG_ON_VERBOSE 00030 00031 extern string MyServerIpAddress; 00032 extern string MySocketData; 00033 00034 int reinitialize_mdm(void); 00035 00036 enum WNC_ERR_e { 00037 WNC_OK =0, 00038 WNC_CMD_ERR = -1, 00039 WNC_NO_RESPONSE = -2, 00040 WNC_CELL_LINK_DOWN = -3, 00041 WNC_EXTERR = -4 00042 }; 00043 00044 // Contains result of last call to send_wnc_cmd(..) 00045 WNC_ERR_e WNC_MDM_ERR = WNC_OK; 00046 00047 // Contains the RAW WNC UART responses 00048 static string wncStr; 00049 static int socketOpen = 0; 00050 00051 void software_init_mdm(void) 00052 { 00053 // Temp put here to fix new boards needing init, 00054 // the check for on the cellular network was preventing the PDNSET from happening!!!! 00055 { 00056 PUTS("SET APN STRING!\r\n"); 00057 string * pRespStr; 00058 string cmd_str("AT%PDNSET=1,"); 00059 cmd_str += MY_APN_STR; 00060 cmd_str += ",IP"; 00061 at_send_wnc_cmd(cmd_str.c_str(), &pRespStr, 4*WNC_TIMEOUT_MS); // Set APN, cmd seems to take a little longer sometimes 00062 } 00063 00064 static bool reportStatus = true; 00065 do 00066 { 00067 PUTS("------------ software_init_mdm! --------->\r\n"); 00068 if (check_wnc_ready() == 0) 00069 { 00070 if (reportStatus == false) 00071 { 00072 PUTS("Re-connected to cellular network!\n\r"); 00073 reportStatus = true; 00074 } 00075 00076 // WNC has SIM and registered on network 00077 do 00078 { 00079 WNC_MDM_ERR = WNC_OK; 00080 at_init_wnc(); 00081 if (WNC_MDM_ERR == WNC_NO_RESPONSE) 00082 { 00083 reinitialize_mdm(); 00084 at_init_wnc(true); // Hard reset occurred so make it go through the software init(); 00085 } 00086 } while (WNC_MDM_ERR != WNC_OK); 00087 } 00088 else 00089 { 00090 if (reportStatus == true) 00091 { 00092 PUTS("Not connected to cellular network!\n\r"); 00093 reportStatus = false; 00094 } 00095 // Atempt to re-register 00096 // string * pRespStr; 00097 // PUTS("Force re-register!\r\n"); 00098 // at_send_wnc_cmd("AT+CFUN=0,0", &pRespStr, WNC_TIMEOUT_MS); 00099 // wait_ms(31000); 00100 // at_send_wnc_cmd("AT+CFUN=1,0", &pRespStr, WNC_TIMEOUT_MS); 00101 // wait_ms(31000); 00102 WNC_MDM_ERR = WNC_CELL_LINK_DOWN; 00103 } 00104 } while (WNC_MDM_ERR != WNC_OK); 00105 } 00106 00107 void display_modem_firmware_version(void) 00108 { 00109 string * pRespStr; 00110 00111 PUTS("<-------- WNC Firmware Revision --------\r\n"); 00112 at_send_wnc_cmd("ATI", &pRespStr, WNC_TIMEOUT_MS); 00113 PUTS(pRespStr->c_str()); 00114 PUTS("\r\n"); 00115 PUTS("--------------------------------------->\r\n"); 00116 } 00117 00118 void resolve_mdm(void) 00119 { 00120 do 00121 { 00122 WNC_MDM_ERR = WNC_OK; 00123 at_dnsresolve_wnc(MY_SERVER_URL, &MyServerIpAddress); 00124 if (WNC_MDM_ERR == WNC_NO_RESPONSE) 00125 { 00126 software_init_mdm(); 00127 } 00128 else if (WNC_MDM_ERR == WNC_CMD_ERR) 00129 { 00130 PUTS("Bad URL!!!!!!\r\n"); 00131 } 00132 } while (WNC_MDM_ERR != WNC_OK); 00133 00134 PRINTF("My Server IP: %s\r\n", MyServerIpAddress.c_str()); 00135 } 00136 00137 void sockopen_mdm(void) 00138 { 00139 do 00140 { 00141 WNC_MDM_ERR = WNC_OK; 00142 at_sockopen_wnc(MyServerIpAddress, MY_PORT_STR); 00143 if (WNC_MDM_ERR == WNC_NO_RESPONSE) 00144 { 00145 software_init_mdm(); 00146 } 00147 else if (WNC_MDM_ERR == WNC_CMD_ERR) 00148 PUTS("Socket open fail!!!!\r\n"); 00149 else 00150 socketOpen = 1; 00151 } while (WNC_MDM_ERR != WNC_OK); 00152 } 00153 00154 void sockwrite_mdm(const char * s) 00155 { 00156 if (socketOpen == 1) 00157 { 00158 do 00159 { 00160 WNC_MDM_ERR = WNC_OK; 00161 at_sockwrite_wnc(s); 00162 if (WNC_MDM_ERR == WNC_NO_RESPONSE) 00163 { 00164 PUTS("Sock write no response!\r\n"); 00165 software_init_mdm(); 00166 } 00167 else if (WNC_MDM_ERR == WNC_CMD_ERR) 00168 { 00169 PUTS("Socket Write fail!!!\r\n"); 00170 software_init_mdm(); 00171 }else if (WNC_MDM_ERR == WNC_EXTERR) 00172 { 00173 PUTS("Socket Disconnected (broken) !!!\r\n"); 00174 sockclose_mdm(); 00175 sockopen_mdm(); 00176 //software_init_mdm(); 00177 } 00178 } while (WNC_MDM_ERR != WNC_OK); 00179 } 00180 else 00181 PUTS("Socket is closed for write!\r\n"); 00182 } 00183 00184 unsigned sockread_mdm(string * sockData, int len, int retries) 00185 { 00186 unsigned n = 0; 00187 00188 if (socketOpen == 1) 00189 { 00190 do 00191 { 00192 WNC_MDM_ERR = WNC_OK; 00193 n = at_sockread_wnc(sockData, len, retries); 00194 if (WNC_MDM_ERR == WNC_NO_RESPONSE) 00195 { 00196 if (n == 0) 00197 software_init_mdm(); 00198 else 00199 PUTS("Sock read partial data!!!\r\n"); 00200 } 00201 else if (WNC_MDM_ERR == WNC_CMD_ERR) 00202 PUTS("Sock read fail!!!!\r\n"); 00203 } while (WNC_MDM_ERR == WNC_NO_RESPONSE); 00204 } 00205 else 00206 { 00207 PUTS("Socket is closed for read\r\n"); 00208 sockData->erase(); 00209 } 00210 00211 return (n); 00212 } 00213 00214 void sockclose_mdm(void) 00215 { 00216 do 00217 { 00218 WNC_MDM_ERR = WNC_OK; 00219 at_sockclose_wnc(); 00220 // Assume close happened even if it went bad 00221 // going bad will result in a re-init anyways and if close 00222 // fails we're pretty much in bad state and not much can do 00223 socketOpen = 0; 00224 if (WNC_MDM_ERR == WNC_NO_RESPONSE) 00225 { 00226 software_init_mdm(); 00227 } 00228 else if (WNC_MDM_ERR == WNC_CMD_ERR) 00229 PUTS("Sock close fail!!!\r\n"); 00230 } while (WNC_MDM_ERR != WNC_OK); 00231 } 00232 00233 /** 00234 * C++ version 0.4 char* style "itoa": 00235 * Written by Lukas Chmela 00236 * Released under GPLv3. 00237 */ 00238 00239 char* itoa(int value, char* result, int base) 00240 { 00241 // check that the base if valid 00242 if ( base < 2 || base > 36 ) { 00243 *result = '\0'; 00244 return result; 00245 } 00246 00247 char* ptr = result, *ptr1 = result, tmp_char; 00248 int tmp_value; 00249 00250 do { 00251 tmp_value = value; 00252 value /= base; 00253 *ptr++ = "zyxwvutsrqponmlkjihgfedcba9876543210123456789abcdefghijklmnopqrstuvwxyz"[35 + (tmp_value - value * base)]; 00254 } while ( value ); 00255 00256 // Apply negative sign 00257 if ( tmp_value < 0 ) 00258 *ptr++ = '-'; 00259 *ptr-- = '\0'; 00260 00261 while ( ptr1 < ptr ) { 00262 tmp_char = *ptr; 00263 *ptr-- = *ptr1; 00264 *ptr1++ = tmp_char; 00265 } 00266 00267 return result; 00268 } 00269 00270 extern int mdm_sendAtCmdRsp(const char *cmd, const char **rsp_list, int timeout_ms, string * rsp, int * len); 00271 00272 int check_wnc_ready(void) 00273 { 00274 string * pRespStr; 00275 size_t pos; 00276 int regSts; 00277 int cmdRes1, cmdRes2; 00278 00279 #ifdef WNC_CMD_DEBUG_ON_VERBOSE 00280 PUTS("<-------- Begin Cell Status ------------\r\n"); 00281 #endif 00282 cmdRes1 = at_send_wnc_cmd("AT+CSQ", &pRespStr, WNC_TIMEOUT_MS); // Check RSSI,BER 00283 cmdRes2 = at_send_wnc_cmd("AT+CPIN?", &pRespStr, WNC_TIMEOUT_MS); // Check if SIM locked 00284 00285 if ((cmdRes1 != 0) && (cmdRes2 != 0)) 00286 { 00287 #ifdef WNC_CMD_DEBUG_ON_VERBOSE 00288 PUTS("------------ WNC No Response! --------->\r\n"); 00289 #endif 00290 return (-2); 00291 } 00292 00293 // If SIM Card not ready don't bother with commands! 00294 if (pRespStr->find("CPIN: READY") == string::npos) 00295 { 00296 #ifdef WNC_CMD_DEBUG_ON_VERBOSE 00297 PUTS("------------ WNC SIM Problem! --------->\r\n"); 00298 #endif 00299 return (-1); 00300 } 00301 00302 // SIM card OK, now check for signal and cellular network registration 00303 cmdRes1 = at_send_wnc_cmd("AT+CREG?", &pRespStr, WNC_TIMEOUT_MS); // Check if registered on network 00304 if (pRespStr->size() > 0) 00305 { 00306 pos = pRespStr->find("CREG: "); 00307 if (pos != string::npos) 00308 { 00309 // The registration is the 2nd arg in the comma separated list 00310 *pRespStr = pRespStr->substr(pos+8, 1); 00311 regSts = atoi(pRespStr->c_str()); 00312 // 1 - registered home, 5 - registered roaming 00313 if ((regSts != 1) && (regSts != 5)) 00314 { 00315 #ifdef WNC_CMD_DEBUG_ON_VERBOSE 00316 PUTS("------------ WNC Cell Link Down! ------>\r\n"); 00317 #endif 00318 return (-2); 00319 } 00320 } 00321 00322 #ifdef WNC_CMD_DEBUG_ON_VERBOSE 00323 PUTS("------------ WNC Ready ---------------->\r\n"); 00324 #endif 00325 } 00326 else 00327 { 00328 #ifdef WNC_CMD_DEBUG_ON_VERBOSE 00329 PUTS("------------ CREG No Reply !----------->\r\n"); 00330 #endif 00331 return (-2); 00332 } 00333 00334 return (0); 00335 } 00336 00337 // Sets a global with failure or success, assumes 1 thread all the time 00338 int send_wnc_cmd(const char * s, string ** r, int ms_timeout) 00339 { 00340 int cmdRes; 00341 00342 if (check_wnc_ready() < 0) 00343 { 00344 static string noRespStr; 00345 00346 #ifdef WNC_CMD_DEBUG_ON 00347 PUTS("FAIL send cmd: "); 00348 #ifdef WNC_CMD_DEBUG_ON_VERBOSE 00349 PUTS(s); 00350 PUTS("\r\n"); 00351 #else 00352 string truncStr(s, 50); 00353 truncStr += "\r\n"; 00354 PUTS(truncStr.c_str()); 00355 #endif 00356 #else 00357 PUTS("FAIL send cmd!\r\n"); 00358 #endif 00359 00360 WNC_MDM_ERR = WNC_CELL_LINK_DOWN; 00361 noRespStr.erase(); 00362 *r = &noRespStr; 00363 return (-3); 00364 } 00365 00366 #ifdef WNC_CMD_DEBUG_ON 00367 #ifdef WNC_CMD_DEBUG_ON_VERBOSE 00368 PUTS("[---------- Network Status -------------\r\n"); 00369 #endif 00370 string * pRespStr; 00371 at_send_wnc_cmd("AT@SOCKDIAL?", &pRespStr, 5000); 00372 #ifdef WNC_CMD_DEBUG_ON_VERBOSE 00373 PUTS("---------------------------------------]\r\n"); 00374 #endif 00375 #endif 00376 00377 // If WNC ready, send user command 00378 cmdRes = at_send_wnc_cmd(s, r, ms_timeout); 00379 00380 if (cmdRes == -1) 00381 WNC_MDM_ERR = WNC_CMD_ERR; 00382 00383 if (cmdRes == -2) 00384 WNC_MDM_ERR = WNC_NO_RESPONSE; 00385 00386 if (cmdRes == -3) { 00387 WNC_MDM_ERR = WNC_EXTERR; 00388 PRINTF("[[WNC_MDM_ERR = WNC_EXTERR]] \r\n"); 00389 } 00390 00391 if (cmdRes == 0) 00392 WNC_MDM_ERR = WNC_OK; 00393 00394 return (cmdRes); 00395 } 00396 00397 int at_send_wnc_cmd(const char * s, string ** r, int ms_timeout) 00398 { 00399 //Eaddy 00400 static const char * rsp_lst[] = { "OK", "ERROR","@EXTERR", "+CME", NULL }; 00401 int len; 00402 00403 #ifdef WNC_CMD_DEBUG_ON 00404 #ifdef WNC_CMD_DEBUG_ON_VERBOSE 00405 00406 #else 00407 if (strlen(s) > 60) 00408 { 00409 string truncStr(s,57); 00410 truncStr += "..."; 00411 PRINTF("Send: <<%s>>\r\n",truncStr.c_str()); 00412 } 00413 else 00414 #endif 00415 PRINTF("Send: <<%s>>\r\n",s); 00416 #endif 00417 00418 int res = mdm_sendAtCmdRsp(s, rsp_lst, ms_timeout, &wncStr, &len); 00419 *r = &wncStr; // Return a pointer to the static string 00420 00421 if (res >= 0) 00422 { 00423 00424 #ifdef WNC_CMD_DEBUG_ON 00425 PUTS("["); 00426 #ifdef WNC_CMD_DEBUG_ON_VERBOSE 00427 PUTS(wncStr.c_str()); 00428 PUTS("]\r\n"); 00429 #else 00430 if (wncStr.size() < 51) 00431 PUTS(wncStr.c_str()); 00432 else 00433 { 00434 string truncStr = wncStr.substr(0,50) + "..."; 00435 PUTS(truncStr.c_str()); 00436 } 00437 PUTS("]\r\n"); 00438 #endif 00439 #endif 00440 00441 #if 0 00442 if (res > 0) 00443 return -1; 00444 else 00445 return 0; 00446 #else 00447 //Eaddy added 00448 if (res == 0) { 00449 /* OK */ 00450 return 0; 00451 } else if (res == 2) { 00452 /* @EXTERR */ 00453 PRINTF("@EXTERR and res = %d \r\n", res); 00454 return -3; 00455 } else 00456 return -1; 00457 #endif 00458 } 00459 else 00460 { 00461 PUTS("No response from WNC!\n\r"); 00462 return -2; 00463 } 00464 } 00465 00466 00467 void at_at_wnc(void) 00468 { 00469 string * pRespStr; 00470 send_wnc_cmd("AT", &pRespStr, WNC_TIMEOUT_MS); // Heartbeat? 00471 } 00472 00473 void at_init_wnc(bool hardReset) 00474 { 00475 static bool pdnSet = false; 00476 static bool intSet = false; 00477 static bool sockDialSet = false; 00478 string * pRespStr; 00479 int cmdRes; 00480 00481 if (hardReset == true) 00482 { 00483 PUTS("Hard Reset!\r\n"); 00484 pdnSet = false; 00485 intSet = false; 00486 sockDialSet = false; 00487 } 00488 00489 PUTS("Start AT init of WNC:\r\n"); 00490 // Quick commands below do not need to check cellular connectivity 00491 cmdRes = at_send_wnc_cmd("AT", &pRespStr, WNC_TIMEOUT_MS); // Heartbeat? 00492 cmdRes += at_send_wnc_cmd("ATE0", &pRespStr, WNC_TIMEOUT_MS); // Echo Off 00493 cmdRes += at_send_wnc_cmd("AT+CMEE=2", &pRespStr, WNC_TIMEOUT_MS); // 2 - verbose error, 1 - numeric error, 0 - just ERROR 00494 00495 // If the simple commands are not working no chance of more complex. 00496 // I have seen re-trying commands make it worse. 00497 if (cmdRes < 0) 00498 { 00499 // Since I used the at_send_wnc_cmd I am setting the error state based upon 00500 // the responses. And since these are simple commands, even if the WNC 00501 // is saying ERROR, treat it like a no response. 00502 WNC_MDM_ERR = WNC_NO_RESPONSE; 00503 return ; 00504 } 00505 00506 if (intSet == false) 00507 cmdRes = send_wnc_cmd("AT@INTERNET=1", &pRespStr, WNC_TIMEOUT_MS); 00508 00509 if (cmdRes == 0) 00510 intSet = true; 00511 else 00512 return ; 00513 00514 if (pdnSet == false) 00515 { 00516 string cmd_str("AT%PDNSET=1,"); 00517 cmd_str += MY_APN_STR; 00518 cmd_str += ",IP"; 00519 cmdRes = send_wnc_cmd(cmd_str.c_str(), &pRespStr, 4*WNC_TIMEOUT_MS); // Set APN, cmd seems to take a little longer sometimes 00520 } 00521 00522 if (cmdRes == 0) 00523 pdnSet = true; 00524 else 00525 return ; 00526 00527 if (sockDialSet == false) 00528 cmdRes = send_wnc_cmd("AT@SOCKDIAL=1", &pRespStr, WNC_TIMEOUT_MS); 00529 00530 if (cmdRes == 0) 00531 sockDialSet = true; 00532 else 00533 return ; 00534 00535 PUTS("SUCCESS: AT init of WNC!\r\n"); 00536 } 00537 00538 void at_sockopen_wnc(const string & ipStr, const char * port ) 00539 { 00540 string * pRespStr; 00541 send_wnc_cmd("AT@SOCKCREAT=1", &pRespStr, WNC_TIMEOUT_MS); 00542 string cmd_str("AT@SOCKCONN=1,\""); 00543 cmd_str += ipStr; 00544 cmd_str += "\","; 00545 cmd_str += port; 00546 cmd_str += ",30"; 00547 int cmd = send_wnc_cmd(cmd_str.c_str(), &pRespStr, 31000); 00548 if (cmd != WNC_OK) { 00549 // Per WNC: re-close even if open fails! 00550 at_sockclose_wnc(); 00551 } 00552 } 00553 00554 void at_sockclose_wnc(void) 00555 { 00556 string * pRespStr; 00557 send_wnc_cmd("AT@SOCKCLOSE=1", &pRespStr, WNC_TIMEOUT_MS); 00558 } 00559 00560 int at_dnsresolve_wnc(const char * s, string * ipStr) 00561 { 00562 string * pRespStr; 00563 string str(s); 00564 str = "AT@DNSRESVDON=\"" + str + "\""; 00565 if (send_wnc_cmd(str.c_str(), &pRespStr, 60000) == 0) 00566 { 00567 size_t pos_start = pRespStr->find(":\"") + 2; 00568 size_t pos_end = pRespStr->find("\"", pos_start) - 1; 00569 if ((pos_start != string::npos) && (pos_end != string::npos)) 00570 { 00571 if (pos_end > pos_start) 00572 { 00573 // Make a copy for use later (the source string is re-used) 00574 *ipStr = pRespStr->substr(pos_start, pos_end - pos_start + 1); 00575 return 1; 00576 } 00577 else 00578 PUTS("URL Resolve fail, substr Err\r\n"); 00579 } 00580 else 00581 PUTS("URL Resolve fail, no quotes\r\n"); 00582 } 00583 else 00584 PUTS("URL Resolve fail, WNC cmd fail\r\n"); 00585 00586 *ipStr = "192.168.0.1"; 00587 00588 return -1; 00589 } 00590 00591 void at_sockwrite_wnc(const char * s) 00592 { 00593 string * pRespStr; 00594 char num2str[6]; 00595 size_t sLen = strlen(s); 00596 int res; 00597 if (sLen <= 1500) 00598 { 00599 string cmd_str("AT@SOCKWRITE=1,"); 00600 itoa(sLen, num2str, 10); 00601 cmd_str += num2str; 00602 cmd_str += ",\""; 00603 while(*s != '\0') 00604 { 00605 itoa((int)*s++, num2str, 16); 00606 // Always 2-digit ascii hex: 00607 if (strlen(num2str) == 1) 00608 { 00609 num2str[2] = '\0'; 00610 num2str[1] = num2str[0]; 00611 num2str[0] = '0'; 00612 } 00613 cmd_str += num2str; 00614 } 00615 cmd_str += "\""; 00616 res = send_wnc_cmd(cmd_str.c_str(), &pRespStr, 120000); 00617 if (res == -3) 00618 PUTS("sockwrite is disconnect \r\n"); 00619 } 00620 else 00621 PUTS("sockwrite Err, string to long\r\n"); 00622 } 00623 00624 unsigned at_sockread_wnc(string * pS, unsigned n, unsigned retries = 0) 00625 { 00626 unsigned i, numBytes = 0; 00627 string * pRespStr; 00628 string cmd_str("AT@SOCKREAD=1,"); 00629 00630 // Clean slate 00631 pS->erase(); 00632 00633 if (n <= 1500) 00634 { 00635 char num2str[6]; 00636 00637 itoa(n, num2str, 10); 00638 cmd_str += num2str; 00639 retries += 1; 00640 while (retries--) 00641 { 00642 // Assuming someone is sending then calling this to receive response, invoke 00643 // a pause to give the response some time to come back and then also 00644 // between each retry. 00645 wait_ms(10); 00646 00647 if (send_wnc_cmd(cmd_str.c_str(), &pRespStr, WNC_TIMEOUT_MS) == 0) 00648 { 00649 size_t pos_start = pRespStr->find("\"") + 1; 00650 size_t pos_end = pRespStr->rfind("\"") - 1; 00651 00652 // Make sure search finds what it's looking for! 00653 if (pos_start != string::npos && pos_end != string::npos) 00654 i = (pos_end - pos_start + 1); // Num hex chars, 2 per byte 00655 else 00656 i = 0; 00657 00658 if (i > 0) 00659 { 00660 retries = 1; // If any data found retry 1 more time to catch data that might be in another 00661 // WNC payload 00662 string byte; 00663 while (pos_start < pos_end) 00664 { 00665 byte = pRespStr->substr(pos_start, 2); 00666 *pS += (char)strtol(byte.c_str(), NULL, 16); 00667 pos_start += 2; 00668 } 00669 numBytes += i/2; 00670 } 00671 } 00672 else 00673 { 00674 PUTS("no readsock reply!\r\n"); 00675 return (0); 00676 } 00677 } 00678 } 00679 else 00680 PUTS("sockread Err, to many to read\r\n"); 00681 00682 return (numBytes); 00683 }
Generated on Tue Jul 12 2022 17:36:30 by
1.7.2
