Go Down

Topic: MEGA 2560, receiving large data from serial port (Read 4442 times) previous topic - next topic

cbodio

What names do you want to use for the two states? "Waiting for response" would need to be one of them.
I don't know, may be I need only one state. I think that I must have such state

enum state_t { IDLE, ERROR, CHECK, AT_RESP, SERVER_READ, METER_WRITE_GPRS, METER_WRITE_CSD, SEND_DATA_GPRS,  } ;

And I can send at commands from IDLE, CHECK, or METER_WRITE_GPRS states. After them I change state to  AT_RESP.

CHECK - state for check gsm, gprs, tcp connection. It must check when no client is connected each 5 min.

-dev

Ok, you're missing the fundamental point.  You can't sit in a loop like SendATCommand and wait for the response.  You have to send the command and return immediately.

Back in loop, you have to read just one character and add it to the response buffer.  If no characters are available, just keep going.  loop will be called again Real Soon.

If a char is available, and it's a newline, then you finally have the entire response.  That's when you see if you got the "OK".  If you got the OK, send the next command... and return immediately.  etc.

Let's start with a simple example: reading a line of text from Serial.  Here are two sketches that look similar, but one blocks and one does not block.  First, the blocking version:

Code: [Select]
void setup()
{
  Serial.begin( 9600 );
}

uint32_t loopIterations = 0UL;

char buf[ 100 ];
int  bufIndex = 0;
bool newlineReceived = false;

void loop()
{
  loopIterations++;

  while (!newlineReceived) {
  
    while (Serial.available()) {
      char c = Serial.read();

      if (c == '\n') {

        // End of line!
        newlineReceived = true;
        buf[ bufIndex ] = '\0'; // NUL-terminate the C string (char array)

      } else {

        // Save one more char, if there's room
        if (bufIndex < sizeof(buf)-1)
          buf[ bufIndex ] = c;
        bufIndex++;
      }
    }
  }

  Serial.print( loopIterations );
  Serial.print( ' ' );
  Serial.println( buf );
  
  bufIndex = 0;
  newlineReceived = false;
  loopIterations = 0;
}

Notice how it always prints that there was just one loop iteration.  Nothing else can happen while it's waiting for the newline char.

Now here's the non-blocking version, like Serial Input Basics:

Code: [Select]
void setup()
{
  Serial.begin( 9600 );
}

uint32_t loopIterations = 0UL;

char buf[ 100 ];
int  bufIndex = 0;
bool newlineReceived = false;

void loop()
{
  loopIterations++;

  while (Serial.available()) {
    char c = Serial.read();

    if (c == '\n') {

      // End of line!
      newlineReceived = true;
      buf[ bufIndex ] = '\0'; // NUL-terminate the C string (char array)

    } else {

      // Save one more char, if there's room
      if (bufIndex < sizeof(buf)-1)
        buf[ bufIndex ] = c;
      bufIndex++;
    }
  }

  if (newlineReceived) {
    Serial.print( loopIterations );
    Serial.print( ' ' );
    Serial.println( buf );

    bufIndex = 0;
    newlineReceived = false;
    loopIterations = 0;
  }
}

This one will print that there were thousands of loop iterations while it was checking (not waiting) for the newline.  If no chars were available, it just continues past the while, then returns from loop.  The newlineReceived is only true when the '\n' char is read, so it is false thousands of times through loop.

You have to take the same approach.  Your program must be structured to accumulate chars if they're available.  If not, it has to move on to the next thing (like checking Serial2 and Serial3, or checking millis() to see if it has been 5 minutes).

If a char is available on a serial port, read it into the buffer.  If it is a newline (or 64th byte), then you can interpret the whole line and do the next thing (like send the next command, or forward a CSD packet).

Study those two programs above, because you can't get any further until you understand the difference between the two.  This is how one FSM works.

You will have several FSMs, because several things are happening at the same time: MENU commands, AT commands and Energy Meter data are three concurrent data sources (serial ports), so I know there are at least 3 FSMs.  There is probably another for the MODE.

Cheers,
/dev
Really, I used to be /dev.  :(

cbodio

OK) I already understand strategy of using FSM. I start my sketch from the begin, to convert all my function to FSM strategy.
These are my sktech now. I start from ERROR state. This state mean that that gprs or tcp connections is break;
Code: [Select]

enum state_t { IDLE, ERROR, CHECK, AT_RESP, SERVER_READ, METER_WRITE_GPRS, METER_WRITE_CSD, SEND_DATA_GPRS,  } ;
state_t currentState = ERROR; //module is not connected to gprs and server
state_t prevState;
byte Client_MODE, Server_MODE, CSD_mode;
char at_expectedResp1[50];
char at_expectedResp2[50];
void setup()
{
GSMBegin();
}
void loop()
{
switch (currentState) {
case IDLE:
//ReceiveMenuCommands();
break;
case ERROR:
if (isReadyGSM == true) {
StartGPRS();
}
/*if (isReadyGPRS == true && Client_MODE == true) {
StartTCPClient();
SendData(MODEM_CONN_STR, strlen(MODEM_CONN_STR));
}*/
if (isTCPReady == true) currentState == IDLE;
break;
case CHECK:
break;
case AT_RESP:
ReadResponse();
currentState = prevState;
break;
case SERVER_READ:
break;
case METER_WRITE_GPRS:
break;
case METER_WRITE_CSD:
break;
case SEND_DATA_GPRS:
break;
default:
break;
}
 
}
byte StartGPRS()
{
byte ret = false;
if (isReadyGSM == true) {
if (IsRegisterGPRS() == true) {
DeactivateGPRS();
}
if (ActivateGPRS() == true) {
SendAtCommands("AT+CIPSHUT", AT_OK, AT_ERROR);
char at_apn[60] = "AT+CSTT=\"";
strcat(at_apn, APN);
strcat(at_apn, "\",\"");
strcat(at_apn, APN_user);
strcat(at_apn, "\",\"");
strcat(at_apn, APN_pwd);
strcat(at_apn, "\"");
if (SendAtCommands(at_apn, AT_OK, AT_ERROR) == 1) {
if (SendAtCommands("AT+CIICR", AT_OK, AT_ERROR) == 1) {
Serial.println("GPRS - OK");
GetLocalIpAddress();
ret = true;
isReadyGPRS = true;
}
}
}
}
return ret;
}
void SendAtCommands(char const * at_command, char const * _at_expectedResp1, char const * _at_expectedResp2)
{
Serial.println(at_command);
//if (Serial3.available() > 0) Serial3.read();
Serial3.write(at_command);
Serial3.write("\r\n");
strcpy(at_expectedResp1, _at_expectedResp1);
strcpy(at_expectedResp2, _at_expectedResp2);
prevState = currentState;
currentState = AT_RESP;
}
uint8_t ReadResponse()
{
uint8_t ret = 0;
int index = 0;
char buf[BUF_SIZE_AT];
memset(buf, '\0', BUF_SIZE_AT);

while (Serial3.available()) {
if (index < BUF_SIZE_AT - 1) buf[index++] = (char)Serial3.read();
        else Serial3.read();
}

if (at_expectedResp1 && (at_expectedResp1[0] != '\0'))
if (strstr(buf, at_expectedResp1)) ret = 1;
        else if (at_expectedResp2 && (at_expectedResp2[0] != '\0'))
        if (strstr(buf, at_expectedResp2)) ret = 2;
        else ret = 3;
Serial.println(buf);
return index;
}

I have SendAtCommands that send at command, set two _at_expectedResp1(2) and change FSM state to currentState = AT_RESP;. After change to AT_RESP state ReadResponse function will be called. It read answer from modem and change FSM state to prev state. But to start GPRS sessions (for example) I need to send some at commands. If I send at commands from IsRegisterGPRS() it must change FSM state and return to main loop. After will work AT_RESP state and ReadResponse() function. After getting response ReadResponse function change my FSM state to ERROR and I need to send next at commands (DeactivateGPRS()) in StartGPRS(), not again IsRegisterGPRS(). May be I need to make some kind of state in StartGPRS() function to send at commands one by one?
How to make it?

-dev

Although your SendATcommand is a reasonable, non-blocking helper routine, ReadResponse is not structured to be called repeatedly until the entire response has been received.  It also compares the response after every char, not just after the newline is received.  It has to be like nonblocking.ino:

Code: [Select]
bool responseAvailable = false; // other parts of the sketch can test this flag
int  responseLength = 0;
char response[BUF_SIZE_AT];

void ReadResponse()
{
  static int responseIndex = 0;

  while (Serial3.available()) {
    char c = Serial3.read();

    if (c == '\n') {
      // Line is all here now!
      response[ responseIndex ] = '\0'; // NUL-terminate C string
      responseLength    = responseIndex;
      responseIndex     = 0; // reset for next line
      responseAvailable = true; // Announce that the response is ready to be compared or parsed

    } else if (responseIndex < BUF_SIZE_AT - 1)
      // Not a newline, save another char
      buf[ responseIndex++ ] = c;
    }
  }
}

I think that's all ReadResponse can do: gather up a line after being called thousands of times.  It sets a flag when the line is ready.  Testing the response will have to be done elsewhere, because the test is dependent on other things.

Quote
Maybe I need to make some kind of state in StartGPRS() function to send at commands one by one?
Yes!  A non-blocking FSM will get called thousands of times.  Most times it will do nothing.  When the newline for one response is finally received by the serial-reading-FSM (ReadResponse or nonblocking.ino above), some variable is set (e.g., responseAvailable = true) to indicate that a new response line is available.  That is called an "event".

Another FSM (GPRS?) will be waiting for that event so it can send the next command:

Code: [Select]
switch (GPRSstate) {
  case IDLE:
    break;

  case SENDING_LIST:
    if (responseAvailable) {
      responseAvailable = false; // reset the flag

      if (strstr( responseList[ commandIndex ], AT_OK )) {

        commandIndex++; // next command

        if (commandIndex == lastCmdIndex)
          // All done sending commands.  Go IDLE until somethings starts sending another list of commands
          GPRScommandsSent = true;
          GPRSstate = IDLE;
        } else {
          // Send next command in list
          SendATCommand( commandList[ commandIndex ] ); // no timeout, just send it
        }
      } else {
        // Error handling.  Go to IDLE state?  Set a flag for some other FSM?
      }
    } // else nothing to do
    break;

Some other part of the program will start sending a list of commands:

Code: [Select]

    ...
  StartGPRS();
    ...

//.........................................
char *ServerCommands[] = { "AT+xx", "ATxx=xx,xxxx,x", "AT xx" };
char *ServerResponses[] = { "AT OK", "AT xxxx", "AT xxxx" };
const int ServerCommandCount = sizeof(ServerCommands)/sizeof(ServerCommands[0]);

void StartGPRS()
{
  SendATCommandList( StartGPRScmds, StartGPRScmdCount );
  xxxState = GPRS_STARTING;
}

//.........................................
char **commandList;
char **responseList;
int commandCount;
int commandIndex; // current command

void SendATCommandList( char **cmds, char **rsps, int count )
{
  // At this point, the GPRS FSM should be in the IDLE state.  You can check it here and
  //    emit a debug statement if it isn't.  You can ignore the list of cmds if you want.
  if (GPRSState != IDLE) {
    debug.print( F("SendATCommandList - GPRS State is " );
    debug.print( GPRSState );
    debug.println( F(", not IDLE!") );
    return; // ?
  }

  // Hold on to the list of commands
  commandList  = cmds;
  responseList = rsps;
  commandCount = count;

  // Send the first command
  commandIndex = 0;
  SendATCommand( commandList[ commandIndex ] ); // no timeout, just send it

  // Set the GPRS FSM into the "SENDING" state
  GPRSState = SENDING_LIST;
}

This starts sending the list of commands.  When a response finally arrives, the GPRS FSM sends the next command.  When all commands have been sent, the GPRS FSM goes back to IDLE.  Some other code tests the GPRScommandsSent flag constantly.  When it finally goes high, it resets the flag and does something else.

Code: [Select]
switch (xxxState) {
    case XXX:
       ...
      break;

    case GPRS_STARTING:
      if (GPRScommandsSent) {
        GPRScommandsSent = false;
        doSomething(); // ???
        xxxState = GPRS_STARTED;
      } // else nothing to do...
      break;

    case GPRS_STARTED:
      if (???) {
      }
      break;

I want to emphasize that there are several FSMs.  Therefore, there are several state variables.  Each FSM can have its own set of states, so there are several enum types with their own names.

The 3 FSMs that are reading each port (Serial, Serial2 and Serial3) are trivial, and look like nonblocking.ino (or ReadResponse above).  They will be called constantly from loop.  They will have their own event flags: xxxAvailable.

But there are other, higher-level FSMs.  Each has their own enum and state variable, and each FSM will watch for events, like xxxAvailable or xxxSent.  Those higher-level FSMs can "emit" their own events by setting their own flags.  And those flags can be watched by other FSMs.

For debugging, you can print out the current FSM states in loop.  As events happen (i.e., flags are set or cleared), you should be able to see the states change.

Don't feel bad... One FSM with one state variable is fairly easy to understand.  It is much harder to understand when your system has several FSMs, and an event in one FSM triggers changes in another FSM.

Cheers,
/dev
Really, I used to be /dev.  :(

Go Up