diff --git a/libraries/WiFiS3/src/Modem.cpp b/libraries/WiFiS3/src/Modem.cpp index c8345637..c1dba332 100644 --- a/libraries/WiFiS3/src/Modem.cpp +++ b/libraries/WiFiS3/src/Modem.cpp @@ -1,104 +1,91 @@ #include "Modem.h" -#define RESULT_OK "OK\r\n" -#define RESULT_ERROR "ERROR\r\n" -#define RESULT_DATA "DATA\r\n" +#define OK_TOKEN "OK" +#define ERROR_TOKEN "ERROR" +#define TERM_TOKEN "\r\n" +#define RESULT_OK OK_TOKEN TERM_TOKEN +#define RESULT_ERROR OK_TOKEN TERM_TOKEN +#define RESULT_DATA "DATA" TERM_TOKEN using namespace std; /* -------------------------------------------------------------------------- */ -ModemClass::ModemClass(int tx, int rx) : beginned(false), delete_serial(false), _timeout(MODEM_TIMEOUT), trim_results(true), read_by_size(false) { +ModemClass::ModemClass(int tx, int rx) : beginned(false), delete_serial(false), _timeout(MODEM_TIMEOUT), trim_results(true) { /* -------------------------------------------------------------------------- */ - _serial = new UART(tx,rx); + _serial = new UART(tx,rx); } /* -------------------------------------------------------------------------- */ -ModemClass::ModemClass(UART * serial) : beginned(false) , delete_serial(true) , _serial(serial), _timeout(MODEM_TIMEOUT), trim_results(true), read_by_size(false) { -/* -------------------------------------------------------------------------- */ +ModemClass::ModemClass(UART * serial) : beginned(false) , delete_serial(true) , _serial(serial), _timeout(MODEM_TIMEOUT), trim_results(true) { +/* -------------------------------------------------------------------------- */ } /* -------------------------------------------------------------------------- */ ModemClass::~ModemClass() { -/* -------------------------------------------------------------------------- */ - if(_serial != nullptr && !delete_serial){ +/* -------------------------------------------------------------------------- */ + if(_serial != nullptr && !delete_serial){ delete _serial; _serial = nullptr; - } + } } /* -------------------------------------------------------------------------- */ void ModemClass::begin(int badurate){ -/* -------------------------------------------------------------------------- */ - if(_serial != nullptr && !beginned) { +/* -------------------------------------------------------------------------- */ + if(_serial != nullptr && !beginned) { _serial->begin(badurate); beginned = true; string res = ""; _serial->flush(); modem.write(string(PROMPT(_SOFTRESETWIFI)),res, "%s" , CMD(_SOFTRESETWIFI)); - } + } } /* -------------------------------------------------------------------------- */ void ModemClass::end(){ -/* -------------------------------------------------------------------------- */ - _serial->end(); +/* -------------------------------------------------------------------------- */ + _serial->end(); } /* -------------------------------------------------------------------------- */ bool ModemClass::passthrough(const uint8_t *data, size_t size) { -/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ _serial->write(data,size); - bool res = false; - bool found = false; - string data_res = ""; - - unsigned long start_time = millis(); - while(millis() - start_time < _timeout && !found){ - while(_serial->available()){ - char c = _serial->read(); - data_res += c; - - if(string::npos != data_res.rfind(RESULT_OK)){ - found = true; - res = true; - break; - } - else if (string::npos != data_res.rfind(RESULT_ERROR)) { - found = true; - res = false; - break; - } - } - } - - if(_serial_debug && _debug_level >= 2) { + + std::string prompt = DO_NOT_CHECK_CMD, data_res; + auto res = buf_read(prompt, data_res); + + if(_serial_debug && _debug_level >= 2) { _serial_debug->print(" ANSWER (passthrough): "); _serial_debug->println(data_res.c_str()); - if(res) { + if(res == Ok) { _serial_debug->println(" Result: OK"); + } else if(res == Error) { + _serial_debug->println(" Result: ERROR"); + } else if(res == Timeout) { + _serial_debug->println(" Result: TIMEOUT"); + } else { + _serial_debug->println(" Result: ParseError"); } - else { - _serial_debug->println(" Result: FAILED"); - } - } - - return res; + } + + return res == Ok; } /* -------------------------------------------------------------------------- */ void ModemClass::write_nowait(const string &cmd, string &str, const char * fmt, ...) { -/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ va_list va; va_start (va, fmt); vsnprintf((char *)tx_buff, MAX_BUFF_SIZE, fmt, va); va_end (va); - - if(_serial_debug && _debug_level >= 2) { + + if(_serial_debug && _debug_level >= 2) { _serial_debug->print("REQUEST (passthrough): "); _serial_debug->write(tx_buff,strlen((char *)tx_buff)); _serial_debug->println(); } - + _serial->write(tx_buff,strlen((char *)tx_buff)); return; } @@ -106,13 +93,13 @@ void ModemClass::write_nowait(const string &cmd, string &str, const char * fmt, /* -------------------------------------------------------------------------- */ bool ModemClass::write(const string &prompt, string &data_res, const char * fmt, ...){ -/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ data_res.clear(); va_list va; va_start (va, fmt); vsnprintf((char *)tx_buff, MAX_BUFF_SIZE, fmt, va); va_end (va); - + if(_serial_debug) { _serial_debug->println(); _serial_debug->print("REQUEST: "); @@ -121,184 +108,309 @@ bool ModemClass::write(const string &prompt, string &data_res, const char * fmt, } _serial->write(tx_buff,strlen((char *)tx_buff)); - return buf_read(prompt,data_res);; -} + auto res = buf_read(prompt, data_res); + if(_serial_debug) { + _serial_debug->print(" ANSWER: "); + _serial_debug->println(data_res.c_str()); + if(res == Ok) { + _serial_debug->println(" Result: OK"); + } else if(res == Error) { + _serial_debug->println(" Result: ERROR"); + } else if(res == Timeout) { + _serial_debug->println(" Result: TIMEOUT"); + } else { + _serial_debug->println(" Result: ParseError"); + } + } -typedef enum { - IDLE, - WAIT_FOR_SIZE, - WAIT_FOR_DATA -} ReadBySizeSt_t; + return res == Ok; +} /* -------------------------------------------------------------------------- */ -bool ModemClass::read_by_size_finished(string &rx) { -/* -------------------------------------------------------------------------- */ - bool rv = false; - static bool first_call = true; - static ReadBySizeSt_t st = IDLE; - static int data_to_be_received = 0; - static int data_received = 0; - if(first_call) { - first_call = false; - st = WAIT_FOR_SIZE; - } +ModemClass::ParseResult ModemClass::buf_read(const string &prompt, string &data_res) { +/* -------------------------------------------------------------------------- */ + /* + * This function implements as FSM that parses basic AT command responses + * The expected syntax should match the following regex + * - (?:\+(\w+)[:=][ ]?(\w*))?(?:\r\n)?(ERROR\r\n|OK\r\n) + * + "ERROR" "OK" + * + "+COMMAND: OK" + * + "+COMMAND: ERROR" + * + "+COMMAND: 1231231OK" (NOTE only one parameter supported) + * + "+COMMAND: 1231231ERROR" (NOTE only one parameter supported) + * - custom sized response: + * + "+COMMAND: 4| 123OK" + */ + enum class at_parse_state_t { + Begin = 0, + Cmd = 1, + Data = 2, + Sized = 3, + ResWaitLF = 4, + Res = 5, + Error = 6, + ParseError = 7, + Ok = 8, + Completed = 9, + }; - switch(st) { - case IDLE: - - break; - case WAIT_FOR_SIZE: { - int pos = rx.find("|"); - int pos_space = rx.find(" "); - if(pos != string::npos && pos_space != string::npos) { - string n = rx.substr(pos_space,pos); - int to_be_rx = atoi(n.c_str()); - if(to_be_rx <= 0) { - while( _serial->available() ){ - _serial->read(); - } - rv = true; - first_call = true; - st = IDLE; - } - else { - /* add 4 because OK\r\n is always added at the end of data */ - data_to_be_received = to_be_rx + 4; - data_received = 0; - st = WAIT_FOR_DATA; - } - rx.clear(); - } - } - break; - - case WAIT_FOR_DATA: - data_received++; - if(data_received == data_to_be_received) { - rv = true; - first_call = true; - st = IDLE; - } - break; + at_parse_state_t state = at_parse_state_t::Begin; + std::string commandName; - default: - st = IDLE; - break; - } - return rv; -} + ModemClass::ParseResult res = Error; + unsigned int sized_read_size = 0; + unsigned int sized_read_count = 0; + unsigned int result_parse = 0; + bool restart = false, + consume_char = true; // This flag is used to indicate to consume another character from the stream + char c; + // I expect the answer to be in this form: "ERROR" "OK" + // if prompt == DO_NOT_CHECK_CMD + const bool check_prompt = (prompt != DO_NOT_CHECK_CMD); -/* -------------------------------------------------------------------------- */ -bool ModemClass::buf_read(const string &prompt, string &data_res) { -/* -------------------------------------------------------------------------- */ - bool res = false; - bool found = false; - if(_serial_debug && _debug_level >= 1) { _serial_debug->print("RAW: "); } unsigned long start_time = millis(); - while((millis() - start_time < _timeout) && !found){ - while( _serial->available() ){ - char c = _serial->read(); - data_res += c; + while(state != at_parse_state_t::Completed) { + + if(millis() - start_time > _timeout) { + res = Timeout; + break; + } + + if(consume_char && !_serial->available()) { + // if there is nothing available, go to the beginning of the cycle + continue; + } else if(consume_char) { // available is true + c = _serial->read(); + } else if(!consume_char) { + // reset consume_char to true + consume_char = true; + } - - if(_serial_debug && _debug_level >= 1) { + if(_serial_debug && _debug_level >= 1 && consume_char) { + if(c == '\n') { + _serial_debug->print(""); + } else if (c == '\r') { + _serial_debug->print(""); + } else if (c == ' ') { + _serial_debug->print(""); + } else if(c < ' ') { + _serial_debug->print("<"); + _serial_debug->print((unsigned int)c); + _serial_debug->print(">"); + } else { _serial_debug->print(c); } - - - if(read_by_size) { - if(read_by_size_finished(data_res)) { - found = true; - read_by_size = false; - res = true; - if(data_res.size() > 0) { - data_res = data_res.substr(0, data_res.length() - (sizeof(RESULT_OK) - 1)); - } - else { - break; - } - } + } + if(_serial_debug && _debug_level >= 3) { + _serial_debug->print(" State "); + _serial_debug->println((int)state); + } + + switch(state) { + case at_parse_state_t::Begin: + /* + * In this state we wait for a '+' character, which will mark the beginning of a response + * or the status response code "ERROR" or "OK" + * we need to consume the available buffer if it doesn't match the expected response, + * in order to avoiding dealing with previous responses which were not parsed successfully + */ + + if(c == '+') { + // This line allow to strenghten the checks on the response prompt, + // it is required that passthrough() function is able to know the command prompt, + // provided in the previous call of write_nowait() + // the answer doesn't match the expected form, thus the response received + // is not related to the sent command we need to restart + // restart = !check_prompt; + + commandName += c; // prompt includes also '+' + state = at_parse_state_t::Cmd; + } else if(c == RESULT_OK[result_parse]) { + // the answer doesn't match the expected form, we need to restart + restart = check_prompt; + + state = at_parse_state_t::Ok; + result_parse++; + } else if(c == RESULT_ERROR[result_parse]) { + // the answer doesn't match the expected form, we need to restart + restart = check_prompt; + + state = at_parse_state_t::Error; + result_parse++; } - else { - if(string::npos != data_res.rfind(RESULT_DATA)) { - data_res = data_res.substr(0, data_res.length() - (sizeof(RESULT_DATA) - 1)); - if(prompt != DO_NOT_CHECK_CMD) { - if(removeAtBegin(data_res, prompt)) { - res = true; - found = true; - } else { - data_res.clear(); - continue; - } - } - else { - res = true; - found = true; - } - break; - } - else if(string::npos != data_res.rfind(RESULT_OK)){ - data_res = data_res.substr(0, data_res.length() - (sizeof(RESULT_OK) - 1) ); - if(prompt != DO_NOT_CHECK_CMD) { - if(removeAtBegin(data_res, prompt)) { - res = true; - found = true; - } else { - data_res.clear(); - continue; - } - } - else { - res = true; - found = true; - } - break; - } - else if (string::npos != data_res.rfind(RESULT_ERROR)) { - found = true; - data_res.substr(0, data_res.length() - (sizeof(RESULT_ERROR) - 1)); - res = false; - break; + // if we uncomment this we can force strict response matching + // else { + // state = at_parse_state_t::ParseError; + // } + + break; + case at_parse_state_t::Cmd: + /* + * In this state we parse the command prompt and wait for either ':' or '=' characters + * in order to go the next state + */ + + if(c == ':' || c == '=') { + commandName += c; // prompt includes also ':' + + if (check_prompt && commandName != prompt) { + // the response we got is not the one we were expecting, parse the wrong response till the end + // and start the parse of the next response + restart = true; + commandName = ""; } + state = at_parse_state_t::Data; + + data_res = ""; + // state = at_parse_state_t::Data; + } else { // no space should be present in the prompt response + commandName += c; + } + + break; + case at_parse_state_t::Data: + /* + * In this state we parse response parameters and push them into data_res + * in case multiple parameters separated by ',' are sent, they will be present in data_res + * - if we encounter we need to wait for + * - if we encounter we need to parse the response status + * - if we encounter '|', the next token will contain binary sized data, the current value in + * in data_res contains the length of the next token + */ + + if(c == '|') { // sized read, the previous parameter is the length + state = at_parse_state_t::Sized; + + sized_read_size = atoi(data_res.c_str()); + data_res.clear(); + } else if(c == '\r') { + state = at_parse_state_t::ResWaitLF; + } else if(c == '\n') { + state = at_parse_state_t::Res; + } else if(trim_results && c != ' ') { + data_res += c; // in case trim_result is true, avoid adding spaces + } else if(!trim_results) { + data_res += c; + } + + break; + case at_parse_state_t::Sized: + /* + * In this state we collect exactly sized_read_size characters into data_res + * when we consume all of them we go into Result parse state, where we supposedly + * wait for 'OK' + */ + data_res += c; + + if(++sized_read_count == sized_read_size) { + state = at_parse_state_t::Res; } + break; + case at_parse_state_t::ResWaitLF: + if(c == '\n') { + state = at_parse_state_t::Res; + } + + /* + * break is volountary not present, to cover for cases where the response status is in the + * following form: '...OK' 'ERROR' + */ + case at_parse_state_t::Res: + /* + * In this state we wait for either an 'O' or an 'E', in order to get an 'OK' + * or 'ERROR' + * The first two cases is when there is no parameter in the response, but just the OK and ERROR tokens + */ + + if(data_res == OK_TOKEN) { + res = Ok; + state = at_parse_state_t::Completed; + } else if(data_res == ERROR_TOKEN) { + res = Error; + state = at_parse_state_t::Completed; + } if(c == RESULT_OK[0]) { // OK response + state = at_parse_state_t::Ok; + result_parse = 1; + } else if(c == RESULT_ERROR[0]) { // Error response + state = at_parse_state_t::Error; + result_parse = 1; + } + // if we uncomment this we can force strict response matching + // else { + // state = at_parse_state_t::ParseError; + // } + break; + case at_parse_state_t::Ok: + /* + * In this state we want to match the exact 'K' response + */ + if(c != RESULT_OK[result_parse++]) { + state = at_parse_state_t::ParseError; + } + + if(result_parse == strlen(RESULT_OK)) { + res = Ok; + state = at_parse_state_t::Completed; + } + break; + case at_parse_state_t::Error: + /* + * In this state we want to match the exact 'RROR' response + */ + + if(c != RESULT_ERROR[result_parse++]) { + state = at_parse_state_t::ParseError; + } + + if(result_parse == strlen(RESULT_ERROR)) { + res = Error; + state = at_parse_state_t::Completed; + } + break; + case at_parse_state_t::ParseError: + res = ParseError; + // if we get a parseError, we go back from the beginning and try again to parse, unitl the timeout expires + state = at_parse_state_t::Begin; + restart = false; + consume_char = false; + break; + case at_parse_state_t::Completed: + break; + } + + if(restart && state == at_parse_state_t::Completed) { + state = at_parse_state_t::Begin; + restart = false; } } - if(trim_results) { - trim(data_res); + + if(_serial_debug && _debug_level >= 3) { + _serial_debug->print("Final State "); + _serial_debug->print((int)state); + _serial_debug->print(" res "); + _serial_debug->println((int)res); } + trim_results = true; - read_by_size = false; if(_serial_debug && _debug_level >= 1) { _serial_debug->print("<-RAW END"); _serial_debug->println(); } - if(_serial_debug) { - _serial_debug->print(" ANSWER: "); - _serial_debug->println(data_res.c_str()); - if(res) { - _serial_debug->println(" Result: OK"); - } - else { - _serial_debug->println(" Result: FAILED"); - } - } - - return res; } #ifdef ARDUINO_UNOWIFIR4 - ModemClass modem = ModemClass(&Serial2); + ModemClass modem = ModemClass(&Serial2); #else - ModemClass modem = ModemClass(D24,D25); + ModemClass modem = ModemClass(D24,D25); #endif diff --git a/libraries/WiFiS3/src/Modem.h b/libraries/WiFiS3/src/Modem.h index 78539e7e..3f785bc9 100644 --- a/libraries/WiFiS3/src/Modem.h +++ b/libraries/WiFiS3/src/Modem.h @@ -35,21 +35,21 @@ class ModemClass { } void read_using_size() { - read_by_size = true; - } + // read_by_size = true; // deprecated + } bool beginned; /* calling this function with no argument will enable debug message to be printed on Serial - use first parameter UART *u to redirect debug output to a different serial + use first parameter UART *u to redirect debug output to a different serial level from 0 defaul to 2 (maximum) */ void debug(Stream &u, uint8_t level = 0) { _serial_debug = &u; - - if(level > 2) { - level = 2; + + if(level > 3) { + level = 3; } _debug_level = level; } @@ -66,14 +66,19 @@ class ModemClass { void timeout(size_t timeout_ms) {_timeout = timeout_ms;} private: - bool buf_read(const std::string &cmd, std::string &data_res); + enum ParseResult { + Ok, + Error, + ParseError, + Timeout + }; + + ParseResult buf_read(const std::string &cmd, std::string &data_res); bool delete_serial; UART * _serial; unsigned long _timeout; uint8_t tx_buff[MAX_BUFF_SIZE]; bool trim_results; - bool read_by_size; - bool read_by_size_finished(std::string &rx); Stream * _serial_debug; uint8_t _debug_level = 0; };