Mobile Life IoT project using the AT&T IoT Starter Kit Software and files for my device to monitor the status or our Airstream travel trailer RV. A full description of the project is at Hackster.IO here as part of the Realtime AT&T IoT Starter Kit Challenge: https://www.hackster.io/Anubus/mobile-life-iot-9c10be
Dependencies: FXOS8700CQ MODSERIAL mbed
Diff: wnc_control.cpp
- Revision:
- 0:bd276b1f1249
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/wnc_control.cpp Sun Apr 02 12:28:21 2017 +0000 @@ -0,0 +1,683 @@ +/* =================================================================== +Copyright c 2016, AVNET Inc. + +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. + +======================================================================== */ + +#include "mbed.h" +#include <cctype> +#include <string> +#include "config_me.h" +#include "wnc_control.h" +#include "hardware.h" + +// Outputs detailed WNC command info +#define WNC_CMD_DEBUG_ON + +// Full debug output, longer cmds and extra cellular status checking +#undef WNC_CMD_DEBUG_ON_VERBOSE + +extern string MyServerIpAddress; +extern string MySocketData; + +int reinitialize_mdm(void); + +enum WNC_ERR_e { + WNC_OK =0, + WNC_CMD_ERR = -1, + WNC_NO_RESPONSE = -2, + WNC_CELL_LINK_DOWN = -3, + WNC_EXTERR = -4 +}; + +// Contains result of last call to send_wnc_cmd(..) +WNC_ERR_e WNC_MDM_ERR = WNC_OK; + +// Contains the RAW WNC UART responses +static string wncStr; +static int socketOpen = 0; + +void software_init_mdm(void) +{ + // Temp put here to fix new boards needing init, + // the check for on the cellular network was preventing the PDNSET from happening!!!! + { + PUTS("SET APN STRING!\r\n"); + string * pRespStr; + string cmd_str("AT%PDNSET=1,"); + cmd_str += MY_APN_STR; + cmd_str += ",IP"; + at_send_wnc_cmd(cmd_str.c_str(), &pRespStr, 4*WNC_TIMEOUT_MS); // Set APN, cmd seems to take a little longer sometimes + } + + static bool reportStatus = true; + do + { + PUTS("------------ software_init_mdm! --------->\r\n"); + if (check_wnc_ready() == 0) + { + if (reportStatus == false) + { + PUTS("Re-connected to cellular network!\n\r"); + reportStatus = true; + } + + // WNC has SIM and registered on network + do + { + WNC_MDM_ERR = WNC_OK; + at_init_wnc(); + if (WNC_MDM_ERR == WNC_NO_RESPONSE) + { + reinitialize_mdm(); + at_init_wnc(true); // Hard reset occurred so make it go through the software init(); + } + } while (WNC_MDM_ERR != WNC_OK); + } + else + { + if (reportStatus == true) + { + PUTS("Not connected to cellular network!\n\r"); + reportStatus = false; + } + // Atempt to re-register +// string * pRespStr; +// PUTS("Force re-register!\r\n"); +// at_send_wnc_cmd("AT+CFUN=0,0", &pRespStr, WNC_TIMEOUT_MS); +// wait_ms(31000); +// at_send_wnc_cmd("AT+CFUN=1,0", &pRespStr, WNC_TIMEOUT_MS); +// wait_ms(31000); + WNC_MDM_ERR = WNC_CELL_LINK_DOWN; + } + } while (WNC_MDM_ERR != WNC_OK); +} + +void display_modem_firmware_version(void) +{ + string * pRespStr; + + PUTS("<-------- WNC Firmware Revision --------\r\n"); + at_send_wnc_cmd("ATI", &pRespStr, WNC_TIMEOUT_MS); + PUTS(pRespStr->c_str()); + PUTS("\r\n"); + PUTS("--------------------------------------->\r\n"); +} + +void resolve_mdm(void) +{ + do + { + WNC_MDM_ERR = WNC_OK; + at_dnsresolve_wnc(MY_SERVER_URL, &MyServerIpAddress); + if (WNC_MDM_ERR == WNC_NO_RESPONSE) + { + software_init_mdm(); + } + else if (WNC_MDM_ERR == WNC_CMD_ERR) + { + PUTS("Bad URL!!!!!!\r\n"); + } + } while (WNC_MDM_ERR != WNC_OK); + + PRINTF("My Server IP: %s\r\n", MyServerIpAddress.c_str()); +} + +void sockopen_mdm(void) +{ + do + { + WNC_MDM_ERR = WNC_OK; + at_sockopen_wnc(MyServerIpAddress, MY_PORT_STR); + if (WNC_MDM_ERR == WNC_NO_RESPONSE) + { + software_init_mdm(); + } + else if (WNC_MDM_ERR == WNC_CMD_ERR) + PUTS("Socket open fail!!!!\r\n"); + else + socketOpen = 1; + } while (WNC_MDM_ERR != WNC_OK); +} + +void sockwrite_mdm(const char * s) +{ + if (socketOpen == 1) + { + do + { + WNC_MDM_ERR = WNC_OK; + at_sockwrite_wnc(s); + if (WNC_MDM_ERR == WNC_NO_RESPONSE) + { + PUTS("Sock write no response!\r\n"); + software_init_mdm(); + } + else if (WNC_MDM_ERR == WNC_CMD_ERR) + { + PUTS("Socket Write fail!!!\r\n"); + software_init_mdm(); + }else if (WNC_MDM_ERR == WNC_EXTERR) + { + PUTS("Socket Disconnected (broken) !!!\r\n"); + sockclose_mdm(); + sockopen_mdm(); + //software_init_mdm(); + } + } while (WNC_MDM_ERR != WNC_OK); + } + else + PUTS("Socket is closed for write!\r\n"); +} + +unsigned sockread_mdm(string * sockData, int len, int retries) +{ + unsigned n = 0; + + if (socketOpen == 1) + { + do + { + WNC_MDM_ERR = WNC_OK; + n = at_sockread_wnc(sockData, len, retries); + if (WNC_MDM_ERR == WNC_NO_RESPONSE) + { + if (n == 0) + software_init_mdm(); + else + PUTS("Sock read partial data!!!\r\n"); + } + else if (WNC_MDM_ERR == WNC_CMD_ERR) + PUTS("Sock read fail!!!!\r\n"); + } while (WNC_MDM_ERR == WNC_NO_RESPONSE); + } + else + { + PUTS("Socket is closed for read\r\n"); + sockData->erase(); + } + + return (n); +} + +void sockclose_mdm(void) +{ + do + { + WNC_MDM_ERR = WNC_OK; + at_sockclose_wnc(); + // Assume close happened even if it went bad + // going bad will result in a re-init anyways and if close + // fails we're pretty much in bad state and not much can do + socketOpen = 0; + if (WNC_MDM_ERR == WNC_NO_RESPONSE) + { + software_init_mdm(); + } + else if (WNC_MDM_ERR == WNC_CMD_ERR) + PUTS("Sock close fail!!!\r\n"); + } while (WNC_MDM_ERR != WNC_OK); +} + +/** + * C++ version 0.4 char* style "itoa": + * Written by Lukas Chmela + * Released under GPLv3. +*/ + +char* itoa(int value, char* result, int base) +{ + // check that the base if valid + if ( base < 2 || base > 36 ) { + *result = '\0'; + return result; + } + + char* ptr = result, *ptr1 = result, tmp_char; + int tmp_value; + + do { + tmp_value = value; + value /= base; + *ptr++ = "zyxwvutsrqponmlkjihgfedcba9876543210123456789abcdefghijklmnopqrstuvwxyz"[35 + (tmp_value - value * base)]; + } while ( value ); + + // Apply negative sign + if ( tmp_value < 0 ) + *ptr++ = '-'; + *ptr-- = '\0'; + + while ( ptr1 < ptr ) { + tmp_char = *ptr; + *ptr-- = *ptr1; + *ptr1++ = tmp_char; + } + + return result; +} + +extern int mdm_sendAtCmdRsp(const char *cmd, const char **rsp_list, int timeout_ms, string * rsp, int * len); + +int check_wnc_ready(void) +{ + string * pRespStr; + size_t pos; + int regSts; + int cmdRes1, cmdRes2; + +#ifdef WNC_CMD_DEBUG_ON_VERBOSE + PUTS("<-------- Begin Cell Status ------------\r\n"); +#endif + cmdRes1 = at_send_wnc_cmd("AT+CSQ", &pRespStr, WNC_TIMEOUT_MS); // Check RSSI,BER + cmdRes2 = at_send_wnc_cmd("AT+CPIN?", &pRespStr, WNC_TIMEOUT_MS); // Check if SIM locked + + if ((cmdRes1 != 0) && (cmdRes2 != 0)) + { +#ifdef WNC_CMD_DEBUG_ON_VERBOSE + PUTS("------------ WNC No Response! --------->\r\n"); +#endif + return (-2); + } + + // If SIM Card not ready don't bother with commands! + if (pRespStr->find("CPIN: READY") == string::npos) + { +#ifdef WNC_CMD_DEBUG_ON_VERBOSE + PUTS("------------ WNC SIM Problem! --------->\r\n"); +#endif + return (-1); + } + + // SIM card OK, now check for signal and cellular network registration + cmdRes1 = at_send_wnc_cmd("AT+CREG?", &pRespStr, WNC_TIMEOUT_MS); // Check if registered on network + if (pRespStr->size() > 0) + { + pos = pRespStr->find("CREG: "); + if (pos != string::npos) + { + // The registration is the 2nd arg in the comma separated list + *pRespStr = pRespStr->substr(pos+8, 1); + regSts = atoi(pRespStr->c_str()); + // 1 - registered home, 5 - registered roaming + if ((regSts != 1) && (regSts != 5)) + { +#ifdef WNC_CMD_DEBUG_ON_VERBOSE + PUTS("------------ WNC Cell Link Down! ------>\r\n"); +#endif + return (-2); + } + } + +#ifdef WNC_CMD_DEBUG_ON_VERBOSE + PUTS("------------ WNC Ready ---------------->\r\n"); +#endif + } + else + { +#ifdef WNC_CMD_DEBUG_ON_VERBOSE + PUTS("------------ CREG No Reply !----------->\r\n"); +#endif + return (-2); + } + + return (0); +} + +// Sets a global with failure or success, assumes 1 thread all the time +int send_wnc_cmd(const char * s, string ** r, int ms_timeout) +{ + int cmdRes; + + if (check_wnc_ready() < 0) + { + static string noRespStr; + +#ifdef WNC_CMD_DEBUG_ON + PUTS("FAIL send cmd: "); + #ifdef WNC_CMD_DEBUG_ON_VERBOSE + PUTS(s); + PUTS("\r\n"); + #else + string truncStr(s, 50); + truncStr += "\r\n"; + PUTS(truncStr.c_str()); + #endif +#else + PUTS("FAIL send cmd!\r\n"); +#endif + + WNC_MDM_ERR = WNC_CELL_LINK_DOWN; + noRespStr.erase(); + *r = &noRespStr; + return (-3); + } + +#ifdef WNC_CMD_DEBUG_ON + #ifdef WNC_CMD_DEBUG_ON_VERBOSE + PUTS("[---------- Network Status -------------\r\n"); + #endif + string * pRespStr; + at_send_wnc_cmd("AT@SOCKDIAL?", &pRespStr, 5000); + #ifdef WNC_CMD_DEBUG_ON_VERBOSE + PUTS("---------------------------------------]\r\n"); + #endif +#endif + + // If WNC ready, send user command + cmdRes = at_send_wnc_cmd(s, r, ms_timeout); + + if (cmdRes == -1) + WNC_MDM_ERR = WNC_CMD_ERR; + + if (cmdRes == -2) + WNC_MDM_ERR = WNC_NO_RESPONSE; + + if (cmdRes == -3) { + WNC_MDM_ERR = WNC_EXTERR; + PRINTF("[[WNC_MDM_ERR = WNC_EXTERR]] \r\n"); + } + + if (cmdRes == 0) + WNC_MDM_ERR = WNC_OK; + + return (cmdRes); +} + +int at_send_wnc_cmd(const char * s, string ** r, int ms_timeout) +{ + //Eaddy + static const char * rsp_lst[] = { "OK", "ERROR","@EXTERR", "+CME", NULL }; + int len; + +#ifdef WNC_CMD_DEBUG_ON + #ifdef WNC_CMD_DEBUG_ON_VERBOSE + + #else + if (strlen(s) > 60) + { + string truncStr(s,57); + truncStr += "..."; + PRINTF("Send: <<%s>>\r\n",truncStr.c_str()); + } + else + #endif + PRINTF("Send: <<%s>>\r\n",s); +#endif + + int res = mdm_sendAtCmdRsp(s, rsp_lst, ms_timeout, &wncStr, &len); + *r = &wncStr; // Return a pointer to the static string + + if (res >= 0) + { + +#ifdef WNC_CMD_DEBUG_ON + PUTS("["); + #ifdef WNC_CMD_DEBUG_ON_VERBOSE + PUTS(wncStr.c_str()); + PUTS("]\r\n"); + #else + if (wncStr.size() < 51) + PUTS(wncStr.c_str()); + else + { + string truncStr = wncStr.substr(0,50) + "..."; + PUTS(truncStr.c_str()); + } + PUTS("]\r\n"); + #endif +#endif + +#if 0 + if (res > 0) + return -1; + else + return 0; +#else + //Eaddy added + if (res == 0) { + /* OK */ + return 0; + } else if (res == 2) { + /* @EXTERR */ + PRINTF("@EXTERR and res = %d \r\n", res); + return -3; + } else + return -1; +#endif + } + else + { + PUTS("No response from WNC!\n\r"); + return -2; + } +} + + +void at_at_wnc(void) +{ + string * pRespStr; + send_wnc_cmd("AT", &pRespStr, WNC_TIMEOUT_MS); // Heartbeat? +} + +void at_init_wnc(bool hardReset) +{ + static bool pdnSet = false; + static bool intSet = false; + static bool sockDialSet = false; + string * pRespStr; + int cmdRes; + + if (hardReset == true) + { + PUTS("Hard Reset!\r\n"); + pdnSet = false; + intSet = false; + sockDialSet = false; + } + + PUTS("Start AT init of WNC:\r\n"); + // Quick commands below do not need to check cellular connectivity + cmdRes = at_send_wnc_cmd("AT", &pRespStr, WNC_TIMEOUT_MS); // Heartbeat? + cmdRes += at_send_wnc_cmd("ATE0", &pRespStr, WNC_TIMEOUT_MS); // Echo Off + cmdRes += at_send_wnc_cmd("AT+CMEE=2", &pRespStr, WNC_TIMEOUT_MS); // 2 - verbose error, 1 - numeric error, 0 - just ERROR + + // If the simple commands are not working no chance of more complex. + // I have seen re-trying commands make it worse. + if (cmdRes < 0) + { + // Since I used the at_send_wnc_cmd I am setting the error state based upon + // the responses. And since these are simple commands, even if the WNC + // is saying ERROR, treat it like a no response. + WNC_MDM_ERR = WNC_NO_RESPONSE; + return ; + } + + if (intSet == false) + cmdRes = send_wnc_cmd("AT@INTERNET=1", &pRespStr, WNC_TIMEOUT_MS); + + if (cmdRes == 0) + intSet = true; + else + return ; + + if (pdnSet == false) + { + string cmd_str("AT%PDNSET=1,"); + cmd_str += MY_APN_STR; + cmd_str += ",IP"; + cmdRes = send_wnc_cmd(cmd_str.c_str(), &pRespStr, 4*WNC_TIMEOUT_MS); // Set APN, cmd seems to take a little longer sometimes + } + + if (cmdRes == 0) + pdnSet = true; + else + return ; + + if (sockDialSet == false) + cmdRes = send_wnc_cmd("AT@SOCKDIAL=1", &pRespStr, WNC_TIMEOUT_MS); + + if (cmdRes == 0) + sockDialSet = true; + else + return ; + + PUTS("SUCCESS: AT init of WNC!\r\n"); +} + +void at_sockopen_wnc(const string & ipStr, const char * port ) +{ + string * pRespStr; + send_wnc_cmd("AT@SOCKCREAT=1", &pRespStr, WNC_TIMEOUT_MS); + string cmd_str("AT@SOCKCONN=1,\""); + cmd_str += ipStr; + cmd_str += "\","; + cmd_str += port; + cmd_str += ",30"; + int cmd = send_wnc_cmd(cmd_str.c_str(), &pRespStr, 31000); + if (cmd != WNC_OK) { + // Per WNC: re-close even if open fails! + at_sockclose_wnc(); + } +} + +void at_sockclose_wnc(void) +{ + string * pRespStr; + send_wnc_cmd("AT@SOCKCLOSE=1", &pRespStr, WNC_TIMEOUT_MS); +} + +int at_dnsresolve_wnc(const char * s, string * ipStr) +{ + string * pRespStr; + string str(s); + str = "AT@DNSRESVDON=\"" + str + "\""; + if (send_wnc_cmd(str.c_str(), &pRespStr, 60000) == 0) + { + size_t pos_start = pRespStr->find(":\"") + 2; + size_t pos_end = pRespStr->find("\"", pos_start) - 1; + if ((pos_start != string::npos) && (pos_end != string::npos)) + { + if (pos_end > pos_start) + { + // Make a copy for use later (the source string is re-used) + *ipStr = pRespStr->substr(pos_start, pos_end - pos_start + 1); + return 1; + } + else + PUTS("URL Resolve fail, substr Err\r\n"); + } + else + PUTS("URL Resolve fail, no quotes\r\n"); + } + else + PUTS("URL Resolve fail, WNC cmd fail\r\n"); + + *ipStr = "192.168.0.1"; + + return -1; +} + +void at_sockwrite_wnc(const char * s) +{ + string * pRespStr; + char num2str[6]; + size_t sLen = strlen(s); + int res; + if (sLen <= 1500) + { + string cmd_str("AT@SOCKWRITE=1,"); + itoa(sLen, num2str, 10); + cmd_str += num2str; + cmd_str += ",\""; + while(*s != '\0') + { + itoa((int)*s++, num2str, 16); + // Always 2-digit ascii hex: + if (strlen(num2str) == 1) + { + num2str[2] = '\0'; + num2str[1] = num2str[0]; + num2str[0] = '0'; + } + cmd_str += num2str; + } + cmd_str += "\""; + res = send_wnc_cmd(cmd_str.c_str(), &pRespStr, 120000); + if (res == -3) + PUTS("sockwrite is disconnect \r\n"); + } + else + PUTS("sockwrite Err, string to long\r\n"); +} + +unsigned at_sockread_wnc(string * pS, unsigned n, unsigned retries = 0) +{ + unsigned i, numBytes = 0; + string * pRespStr; + string cmd_str("AT@SOCKREAD=1,"); + + // Clean slate + pS->erase(); + + if (n <= 1500) + { + char num2str[6]; + + itoa(n, num2str, 10); + cmd_str += num2str; + retries += 1; + while (retries--) + { + // Assuming someone is sending then calling this to receive response, invoke + // a pause to give the response some time to come back and then also + // between each retry. + wait_ms(10); + + if (send_wnc_cmd(cmd_str.c_str(), &pRespStr, WNC_TIMEOUT_MS) == 0) + { + size_t pos_start = pRespStr->find("\"") + 1; + size_t pos_end = pRespStr->rfind("\"") - 1; + + // Make sure search finds what it's looking for! + if (pos_start != string::npos && pos_end != string::npos) + i = (pos_end - pos_start + 1); // Num hex chars, 2 per byte + else + i = 0; + + if (i > 0) + { + retries = 1; // If any data found retry 1 more time to catch data that might be in another + // WNC payload + string byte; + while (pos_start < pos_end) + { + byte = pRespStr->substr(pos_start, 2); + *pS += (char)strtol(byte.c_str(), NULL, 16); + pos_start += 2; + } + numBytes += i/2; + } + } + else + { + PUTS("no readsock reply!\r\n"); + return (0); + } + } + } + else + PUTS("sockread Err, to many to read\r\n"); + + return (numBytes); +}