PHS module SMA-01 library. see: https://developer.mbed.org/users/phsfan/notebook/abitusbmodem/
Dependencies: Socket lwip-sys lwip
Dependents: AbitUSBModem_HTTPTest AbitUSBModem_MQTTTest AbitUSBModem_WebsocketTest AbitUSBModem_SMSTest
Fork of VodafoneUSBModem by
at/ATCommandsInterface.cpp
- Committer:
- nherriot
- Date:
- 2012-09-26
- Revision:
- 46:c2282539c858
- Parent:
- 37:c1fd83bd31b4
- Child:
- 50:8ad4cb12749d
File content as of revision 46:c2282539c858:
/* ATCommandsInterface.cpp */ /* Copyright (C) 2012 mbed.org, MIT License * * Permission is hereby granted, free of charge, to any person obtaining a copy of this software * and associated documentation files (the "Software"), to deal in the Software without restriction, * including without limitation the rights to use, copy, modify, merge, publish, distribute, * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all copies or * substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #define __DEBUG__ 2//ERR+WARN #ifndef __MODULE__ #define __MODULE__ "ATCommandsInterface.cpp" #endif #include "core/fwk.h" #include <cstdio> //#include <cstring> //For memset, strstr... using std::memmove; #include "ATCommandsInterface.h" ATCommandsInterface::ATCommandsInterface(IOStream* pStream) : m_pStream(pStream), m_open(false), m_env2AT(), m_AT2Env(), m_processingMtx(), m_processingThread(&ATCommandsInterface::staticCallback, this, (osPriority)AT_THREAD_PRIORITY, 6*192), m_eventsMtx() { memset(m_eventsHandlers, 0, MAX_AT_EVENTS_HANDLERS * sizeof(IATEventsHandler*)); m_processingMtx.lock(); } //Open connection to AT Interface in order to execute command & register/unregister events int ATCommandsInterface::open() { if( m_open ) { WARN("AT interface is already open"); return OK; } DBG("Opening AT interface"); //Start processing m_processingThread.signal_set(AT_SIG_PROCESSING_START); m_processingMtx.unlock(); m_open = true; //Advertize this to events handlers m_eventsMtx.lock(); for(int i = 0; i < MAX_AT_EVENTS_HANDLERS; i++) //Find a free slot { if( m_eventsHandlers[i] != NULL ) { m_eventsHandlers[i]->onDispatchStart(); } } m_eventsMtx.unlock(); DBG("AT interface opened"); return OK; } //Initialize AT link int ATCommandsInterface::init() { DBG("Sending ATZ E1 V1"); //Should we flush m_pStream at this point ??? int err; int tries = 5; do { err = executeSimple("ATZ E1 V1", NULL, 3000); //Enable echo and verbosity if(err && tries) { WARN("No response, trying again"); Thread::wait(1000); //Give dongle time to recover } } while(err && tries--); if( err ) { ERR("Sending ATZ E1 V1 returned with err code %d", err); return err; } DBG("AT interface initialized"); return OK; } //Close connection int ATCommandsInterface::close() { if( !m_open ) { WARN("AT interface is already closed"); return OK; } DBG("Closing AT interface"); //Stop processing m_processingThread.signal_set(AT_SIG_PROCESSING_STOP); //m_stopSphre.release(); int* msg = m_env2AT.alloc(osWaitForever); *msg = AT_STOP; m_env2AT.put(msg); //Used to unstall the process if needed //Unlock process routine (abort read) m_pStream->abortRead(); //This is thread-safe m_processingMtx.lock(); m_open = false; //Advertize this to events handlers m_eventsMtx.lock(); for(int i = 0; i < MAX_AT_EVENTS_HANDLERS; i++) //Find a free slot { if( m_eventsHandlers[i] != NULL ) { m_eventsHandlers[i]->onDispatchStop(); } } m_eventsMtx.unlock(); DBG("AT interface closed"); return OK; } bool ATCommandsInterface::isOpen() { return m_open; } int ATCommandsInterface::executeSimple(const char* command, ATResult* pResult, uint32_t timeout/*=1000*/) { return execute(command, this, pResult, timeout); } int ATCommandsInterface::execute(const char* command, IATCommandsProcessor* pProcessor, ATResult* pResult, uint32_t timeout/*=1000*/) { DBG("Executing command %s", command); if(!m_open) { WARN("Interface is not open!"); return NET_INVALID; } //Lock transaction mutex m_transactionMtx.lock(); //Discard previous result if it arrived too late osEvent evt = m_AT2Env.get(0); if(evt.status == osEventMail) { m_AT2Env.free((int*)evt.value.p); WARN("Previous result discarded"); } //Send params to the process routine m_transactionCommand = command; if(pProcessor != NULL) { m_pTransactionProcessor = pProcessor; } else { m_pTransactionProcessor = this; //Use default behaviour } Thread::wait(100); //FIXME find stg else DBG("Sending command ready signal to AT thread & aborting current blocking read operation"); //Produce command ready signal int* msg = m_env2AT.alloc(osWaitForever); *msg = AT_CMD_READY; m_env2AT.put(msg); DBG("Trying to enter abortRead()"); //Unlock process routine (abort read) m_pStream->abortRead(); //This is thread-safe //Wait for a result (get result message) evt = m_AT2Env.get(timeout); if(evt.status != osEventMail) { //Cancel request msg = m_env2AT.alloc(osWaitForever); *msg = AT_TIMEOUT; m_env2AT.put(msg); DBG("Trying to enter abortRead()"); //Unlock process routine (abort read) m_pStream->abortRead(); //This is thread-safe WARN("Command returned no message"); m_transactionMtx.unlock(); return NET_TIMEOUT; } DBG("Command returned with message %d", *msg); m_AT2Env.free((int*)evt.value.p); if(pResult != NULL) { *pResult = m_transactionResult; } int ret = ATResultToReturnCode(m_transactionResult); if(ret != OK) { WARN("Command returned AT result %d with code %d", m_transactionResult.result, m_transactionResult.code); } DBG("Command returned successfully"); //Unlock transaction mutex m_transactionMtx.unlock(); return ret; } int ATCommandsInterface::registerEventsHandler(IATEventsHandler* pHdlr) { m_eventsMtx.lock(); for(int i = 0; i < MAX_AT_EVENTS_HANDLERS; i++) //Find a free slot { if( m_eventsHandlers[i] == NULL ) { m_eventsHandlers[i] = pHdlr; m_eventsMtx.unlock(); return OK; } } m_eventsMtx.unlock(); return NET_OOM; //No room left } int ATCommandsInterface::deregisterEventsHandler(IATEventsHandler* pHdlr) { m_eventsMtx.lock(); for(int i = 0; i < MAX_AT_EVENTS_HANDLERS; i++) //Find handler in list { if( m_eventsHandlers[i] == pHdlr ) { m_eventsHandlers[i] = NULL; m_eventsMtx.unlock(); return OK; } } m_eventsMtx.unlock(); return NET_NOTFOUND; //Not found } int ATCommandsInterface::tryReadLine() { static bool lineDetected = false; //Block on serial read or incoming command DBG("Trying to read a new line from stream"); int ret = m_pStream->waitAvailable(); //This can be aborted size_t readLen = 0; if(ret == OK) { ret = m_pStream->read((uint8_t*)m_inputBuf + m_inputPos, &readLen, AT_INPUT_BUF_SIZE - 1 - m_inputPos, 0); //Do NOT wait at this point } if(ret == OK) { m_inputPos+=readLen; m_inputBuf[m_inputPos] = '\0'; //Add null terminating character to ease the use of str* functions DBG("In buffer: [%s]", m_inputBuf); } if( ret == NET_INTERRUPTED ) //It is worth checking readLen as data might have been read even though the read was interrupted { DBG("Read was interrupted"); return NET_INTERRUPTED; //0 chars were read } else if(readLen == 0) { DBG("Nothing read"); return OK; //0 chars were read } DBG("Trying to process incoming line"); bool lineProcessed = false; do { lineProcessed = false; //Reset flag DBG("New iteration"); //Look for a new line if(!lineDetected) { DBG("No line detected yet"); //Try to look for a starting CRLF char* crPtr = strchr(m_inputBuf, CR); /* Different cases at this point: - CRLF%c sequence: this is the start of a line - CRLFCR(LF) sequence: this is the end of a line (followed by the beginning of the next one) - LF: this is the trailing LF char of the previous line, discard - CR / CRLF incomplete sequence: more data is needed to determine which action to take - %c ... CR sequence: this should be the echo of the previous sequence - %c sequence: This might be the echo of the previous command; more data is needed to determine which action to take In every case, move mem at the beginning */ if(crPtr != NULL) { DBG("CR char found"); #if 0 //Discard all preceding characters (can do nothing if m_inputBuf == crPtr) memmove(m_inputBuf, crPtr, (m_inputPos + 1) - (crPtr-m_inputBuf)); //Move null-terminating char as well m_inputPos = m_inputPos - (crPtr-m_inputBuf); //Adjust m_inputPos #endif //If the line starts with CR, this should be a result code if( crPtr == m_inputBuf ) { //To determine the sequence we need at least 3 chars if(m_inputPos >= 3) { //Look for a LF char next to the CR char if(m_inputBuf[1] == LF) { //At this point we can check whether this is the end of a preceding line or the beginning of a new one if(m_inputBuf[2] != CR) { DBG("Beginning of new line found"); //Beginning of a line lineDetected = true; //Move to next state-machine step } else { //End of an unprocessed line WARN("End of unprocessed line"); } //In both cases discard CRLF DBG("Discarding CRLF"); memmove(m_inputBuf, m_inputBuf + 2, (m_inputPos + 1) - 2); //Move null-terminating char as well m_inputPos = m_inputPos - 2; //Adjust m_inputPos } else { //This is completely unexpected, discard the CR char to try to recover good state WARN("Unexpected %c char (%02d code) found after CR char", m_inputBuf[1]); memmove(m_inputBuf, m_inputBuf + 1, (m_inputPos + 1) - 1); //Move null-terminating char as well m_inputPos = m_inputPos - 1; //Adjust m_inputPos } } } //if the line does NOT begin with CR, this can be an echo of the previous command, process it else { int crPos = crPtr - m_inputBuf; int lfOff = 0; //Offset for LF if present DBG("New line found (possible echo of command)"); //This is the end of line //Replace m_inputBuf[crPos] with null-terminating char m_inputBuf[crPos] = '\0'; //Check if there is a LF char afterwards if(m_inputPos - crPos >= 1) { if(m_inputBuf[crPos+1] == LF) { lfOff++; //We will discard LF char as well } } //Process line processReadLine(); //Shift remaining data to beginning of buffer memmove(m_inputBuf, m_inputBuf + crPos + lfOff + 1, (m_inputPos + 1) - (crPos + lfOff + 1)); //Move null-terminating char as well m_inputPos = m_inputPos - (crPos + lfOff + 1); //Adjust m_inputPos DBG("One line was successfully processed"); lineProcessed = true; //Line was processed with success lineDetected = false; //Search now for a new line } } else if(m_inputBuf[0] == LF) //If there is a remaining LF char from the previous line, discard it { DBG("Discarding single LF char"); memmove(m_inputBuf, m_inputBuf + 1, (m_inputPos + 1) - 1); //Move null-terminating char as well m_inputPos = m_inputPos - 1; //Adjust m_inputPos } } //Look for the end of line if(lineDetected) { DBG("Looking for end of line"); //Try to look for a terminating CRLF char* crPtr = strchr(m_inputBuf, CR); /* Different cases at this point: - CRLF sequence: this is the end of the line - CR%c sequence : unexpected - CR incomplete sequence: more data is needed to determine which action to take */ //Try to look for a '>' (greater than character) that marks an entry prompt char* greaterThanPtr = strchr(m_inputBuf, GD); /* This character must be detected as there is no CRLF sequence at the end of an entry prompt */ if(crPtr != NULL) { DBG("CR char found"); int crPos = crPtr - m_inputBuf; //To determine the sequence we need at least 2 chars if(m_inputPos - crPos >= 2) { //Look for a LF char next to the CR char if(m_inputBuf[crPos + 1] == LF) { DBG("End of new line found"); //This is the end of line //Replace m_inputBuf[crPos] with null-terminating char m_inputBuf[crPos] = '\0'; //Process line int ret = processReadLine(); if(ret) { m_inputPos = 0; lineDetected = false; return ret; } //If sendData has been called, all incoming data has been discarded if(m_inputPos > 0) { //Shift remaining data to beginning of buffer memmove(m_inputBuf, m_inputBuf + crPos + 2, (m_inputPos + 1) - (crPos + 2)); //Move null-terminating char as well m_inputPos = m_inputPos - (crPos + 2); //Adjust m_inputPos } DBG("One line was successfully processed"); lineProcessed = true; //Line was processed with success } else { //This is completely unexpected, discard all chars till the CR char to try to recover good state WARN("Unexpected %c char (%02d code) found in incoming line", m_inputBuf[crPos + 1]); memmove(m_inputBuf, m_inputBuf + crPos + 1, (m_inputPos + 1) - (crPos + 1)); //Move null-terminating char as well m_inputPos = m_inputPos - (crPos + 1); //Adjust m_inputPos } lineDetected = false; //In both case search now for a new line } } else if(greaterThanPtr != NULL) { DBG("> char found"); int gdPos = greaterThanPtr - m_inputBuf; //To determine the sequence we need at least 2 chars if(m_inputPos - gdPos >= 2) { //Look for a space char next to the GD char if(m_inputBuf[gdPos + 1] == ' ') { //This is an entry prompt //Replace m_inputBuf[gdPos] with null-terminating char m_inputBuf[gdPos] = '\0'; //Shift remaining data to beginning of buffer memmove(m_inputBuf, m_inputBuf + gdPos + 1, (m_inputPos + 1) - (gdPos + 1)); //Move null-terminating char as well m_inputPos = m_inputPos - (gdPos + 1); //Adjust m_inputPos //Process prompt ret = processEntryPrompt(); if(ret) { m_inputPos = 0; lineDetected = false; return ret; } DBG("One line was successfully processed"); lineProcessed = true; //Line was processed with success } else { //This is completely unexpected, discard all chars till the GD char to try to recover good state WARN("Unexpected %c char (%02d code) found in incoming line", m_inputBuf[gdPos + 1]); memmove(m_inputBuf, m_inputBuf + gdPos + 1, (m_inputPos + 1) - (gdPos + 1)); //Move null-terminating char as well m_inputPos = m_inputPos - (gdPos + 1); //Adjust m_inputPos } lineDetected = false; //In both case search now for a new line } } } } while(lineProcessed); //If one complete line was processed there might be other incoming lines that can also be processed without reading the buffer again //If the line could not be processed AND buffer is full, it means that we won't ever be able to process it (buffer too short) if(m_inputPos == AT_INPUT_BUF_SIZE - 1) { //Discard everything m_inputPos = 0; WARN("Incoming buffer is too short to process incoming line"); //Look for a new line lineDetected = false; } DBG("Processed every full incoming lines"); return OK; } int ATCommandsInterface::trySendCommand() { osEvent evt = m_env2AT.get(0); DBG("status = %d, msg = %d", evt.status, evt.value.p); if(evt.status == osEventMail) { int* msg = (int*) evt.value.p; if( *msg == AT_CMD_READY ) //Command pending { if(m_transactionState != IDLE) { WARN("Previous command not processed!"); } DBG("Sending pending command"); m_pStream->write((uint8_t*)m_transactionCommand, strlen(m_transactionCommand), osWaitForever); char cr = CR; m_pStream->write((uint8_t*)&cr, 1, osWaitForever); //Carriage return line terminator m_transactionState = COMMAND_SENT; } else { m_transactionState = IDLE; //State-machine reset } m_env2AT.free(msg); } return OK; } int ATCommandsInterface::processReadLine() { DBG("Processing read line [%s]", m_inputBuf); //The line is stored in m_inputBuf if(m_transactionState == COMMAND_SENT) { //If the command has been sent, checks echo to see if it has been received properly if( strcmp(m_transactionCommand, m_inputBuf) == 0 ) { DBG("Command echo received"); //If so, it means that the following lines will only be solicited results m_transactionState = READING_RESULT; return OK; } } if(m_transactionState == IDLE || m_transactionState == COMMAND_SENT) { bool found = false; char* pSemicol = strchr(m_inputBuf, ':'); char* pData = NULL; if( pSemicol != NULL ) //Split the identifier & the result code (if it exists) { *pSemicol = '\0'; pData = pSemicol + 1; if(pData[0]==' ') { pData++; //Suppress whitespace } } //Looks for a unsolicited result code; we can have m_transactionState == COMMAND_SENT as the code may have arrived just before we sent the command m_eventsMtx.lock(); //Go through the list for(int i = 0; i < MAX_AT_EVENTS_HANDLERS; i++) //Find a free slot { if( m_eventsHandlers[i] != NULL ) { if( m_eventsHandlers[i]->isATCodeHandled(m_inputBuf) ) { m_eventsHandlers[i]->onEvent(m_inputBuf, pData); found = true; //Do not break here as there might be multiple handlers for one event type } } } m_eventsMtx.unlock(); if(found) { return OK; } } if(m_transactionState == READING_RESULT) { //The following lines can either be a command response or a result code (OK / ERROR / CONNECT / +CME ERROR: %s / +CMS ERROR: %s) if(strcmp("OK", m_inputBuf) == 0) { DBG("OK result received"); m_transactionResult.code = 0; m_transactionResult.result = ATResult::AT_OK; m_transactionState = IDLE; int* msg = m_AT2Env.alloc(osWaitForever); *msg = AT_RESULT_READY; m_AT2Env.put(msg); //Command has been processed return OK; } else if(strcmp("ERROR", m_inputBuf) == 0) { DBG("ERROR result received"); m_transactionResult.code = 0; m_transactionResult.result = ATResult::AT_ERROR; m_transactionState = IDLE; int* msg = m_AT2Env.alloc(osWaitForever); *msg = AT_RESULT_READY; m_AT2Env.put(msg); //Command has been processed return OK; } else if(strncmp("CONNECT", m_inputBuf, 7 /*=strlen("CONNECT")*/) == 0) //Result can be "CONNECT" or "CONNECT %d", indicating baudrate { DBG("CONNECT result received"); m_transactionResult.code = 0; m_transactionResult.result = ATResult::AT_CONNECT; m_transactionState = IDLE; int* msg = m_AT2Env.alloc(osWaitForever); *msg = AT_RESULT_READY; m_AT2Env.put(msg); //Command has been processed return OK; } else if(strcmp("COMMAND NOT SUPPORT", m_inputBuf) == 0) //Huawei-specific, not normalized { DBG("COMMAND NOT SUPPORT result received"); m_transactionResult.code = 0; m_transactionResult.result = ATResult::AT_ERROR; m_transactionState = IDLE; int* msg = m_AT2Env.alloc(osWaitForever); *msg = AT_RESULT_READY; m_AT2Env.put(msg); //Command has been processed return OK; } else if(strstr(m_inputBuf, "+CME ERROR:") == m_inputBuf) //Mobile Equipment Error { std::sscanf(m_inputBuf + 12 /* =strlen("+CME ERROR: ") */, "%d", &m_transactionResult.code); DBG("+CME ERROR: %d result received", m_transactionResult.code); m_transactionResult.result = ATResult::AT_CME_ERROR; m_transactionState = IDLE; int* msg = m_AT2Env.alloc(osWaitForever); *msg = AT_RESULT_READY; m_AT2Env.put(msg); //Command has been processed return OK; } else if(strstr(m_inputBuf, "+CMS ERROR:") == m_inputBuf) //SIM Error { std::sscanf(m_inputBuf + 13 /* =strlen("+CME ERROR: ") */, "%d", &m_transactionResult.code); DBG("+CMS ERROR: %d result received", m_transactionResult.code); m_transactionResult.result = ATResult::AT_CMS_ERROR; m_transactionState = IDLE; int* msg = m_AT2Env.alloc(osWaitForever); *msg = AT_RESULT_READY; m_AT2Env.put(msg); //Command has been processed return OK; } else { DBG("Unprocessed result received: '%s'", m_inputBuf); //Must call transaction processor to complete line processing int ret = m_pTransactionProcessor->onNewATResponseLine(this, m_inputBuf); //Here sendData can be called return ret; } } return OK; } int ATCommandsInterface::processEntryPrompt() { DBG("Calling prompt handler"); int ret = m_pTransactionProcessor->onNewEntryPrompt(this); //Here sendData can be called if( ret != NET_MOREINFO ) //A new prompt is expected { DBG("Sending break character"); //Send CTRL+Z (break sequence) to exit prompt char seq[2] = {BRK, 0x00}; sendData(seq); } return OK; } //Commands that can be called during onNewATResponseLine callback, additionally to close() //Access to this method is protected (can ONLY be called on processing thread during IATCommandsProcessor::onNewATResponseLine execution) int ATCommandsInterface::sendData(const char* data) { //m_inputBuf is cleared at this point (and MUST therefore be empty) int dataLen = strlen(data); DBG("Sending raw string of length %d", dataLen); int ret = m_pStream->write((uint8_t*)data, dataLen, osWaitForever); if(ret) { WARN("Could not write to stream (returned %d)", ret); return ret; } int dataPos = 0; do { //Read echo size_t readLen; int ret = m_pStream->read((uint8_t*)m_inputBuf, &readLen, MIN(dataLen - dataPos, AT_INPUT_BUF_SIZE - 1), osWaitForever); //Make sure we do not read more than needed otherwise it could break the parser if(ret) { WARN("Could not read from stream (returned %d)", ret); return ret; }; if( memcmp(m_inputBuf, data + dataPos, readLen) != 0 ) { //Echo does not match output WARN("Echo does not match output"); return NET_DIFF; } dataPos += readLen; //If all characters have not been read yet } while(dataPos < dataLen); DBG("String sent successfully"); m_inputPos = 0; //Reset input buffer state return OK; } /*static*/ void ATCommandsInterface::staticCallback(void const* p) { ((ATCommandsInterface*)p)->process(); } int ATCommandsInterface::ATResultToReturnCode(ATResult result) //Helper { if(result.result == ATResult::AT_OK) { return OK; } else { return NET_MOREINFO; } } /*virtual*/ int ATCommandsInterface::onNewATResponseLine(ATCommandsInterface* pInst, const char* line) //Default implementation for simple commands handling { return OK; } /*virtual*/ int ATCommandsInterface::onNewEntryPrompt(ATCommandsInterface* pInst) //Default implementation (just sends Ctrl+Z to exit the prompt by returning OK right-away) { return OK; } void ATCommandsInterface::process() //Processing thread { DBG("AT Thread started"); while(true) { DBG("AT Processing on hold"); m_processingThread.signal_wait(AT_SIG_PROCESSING_START); //Block until the process is started m_processingMtx.lock(); DBG("AT Processing started"); //First of all discard buffer int ret; size_t readLen; do //Drop everything { ret = m_pStream->read((uint8_t*)m_inputBuf, &readLen, AT_INPUT_BUF_SIZE - 1, 0); //Do NOT wait at this point } while(ret == OK); m_inputPos = 0; //Clear input buffer do { DBG("Trying to read a new line"); tryReadLine(); DBG("Trying to send a pending command"); trySendCommand(); } while( m_processingThread.signal_wait(AT_SIG_PROCESSING_STOP, 0).status != osEventSignal ); //Loop until the process is interrupted m_processingMtx.unlock(); DBG("AT Processing stopped"); } }