AT Command Callback void* parameter


#1

I have been working with the Cellular.command() function for a few days now and I am confused about the void* pointer that we are supposed to pass to Cellular.command(), which in turn gets passed to the callback.

See screenshot:

Why do we need to pass “a pointer to the variable or structure being updated by the callback function”.

I assume this has something to do with thread safety, and that the Cellular.command() function actually uses RTOS to switch the thread over to the system thread and that the callback function is called from the system thread as AT response data is being received from the modem

Is that basically whats happening?

If so, does that imply that it is unsafe to change other variables that haven’t had a pointer passed to Cellular.command() from within the callback?

For example, would the following code be dangerous/bad somehow since the callback is updating variables other than the passed iccid char array?

bool foundICCID = false;
char complexVariable[15] = "";

// EXAMPLE - Get the ICCID number of the inserted SIM card
int callbackICCID(int type, const char* buf, int len, char* iccid)
{
  if ((type == TYPE_PLUS) && iccid) {
    if (sscanf(buf, "\r\n+CCID: %[^\r]\r\n", iccid) == 1){
      foundICCID = true;                        //<--Does this line break things?
      strcpy(complexVariable, "you did it!");   //<--Does this line break things?
   }
  }
  return WAIT;
}

void setup()
{
  Serial.begin(9600);
  char iccid[32] = "";
  if ((RESP_OK == Cellular.command(callbackICCID, iccid, 10000, "AT+CCID\r\n"))
    && (strcmp(iccid,"") != 0))
  {
     if(foundICCID){
        Serial.printlnf("SIM ICCID = %s  --> %s \r\n", iccid, complexVariable);
     }

  }
  else
  {
    Serial.println("SIM ICCID NOT FOUND!");
  }
}

void loop()
{
  // your loop code
}

#2

Since we are looking at asynchronous calls, which might take different amount of times to return, but may well be reusing the same callback function, that parameter provides you with a means to “abstract” your callback code. Instead of manipulating global variables in that code, you always act on that set of data that is tied to the causing Cellular.command() call even if the callback does not know which of the previous call’s results it is dealing with.

I don’t know I that was written understanable :blush: I know what I wanted to say, but I’m not sure if I found the correct words :flushed:


#3

I think I get what you are saying. Let me put it into other words and see if that is correct.

The void* is there so that you can pass a variable (the iccid variable in the example above) who’s scope is limited to the Cellular.command() calling function ( the setup() function in the example above) in the user application. That way, the callback can operate directly on a variable that it wouldn’t normally have access to.

What’s more is that if the user application function that originally called Cellular.command() gets called again before the previous Cellular.command() call results in a callback, then the callback function doesn’t have to know the context of the call to itself because it has a different pointer to a different out of scope variable (a different instance of iccid).

What I don’t understand is why this is necessary if Cellular.command() is not asynchronous In my testing, the Cellular.command() call is synchronous, and blocks my code until it returns. This is with SYSTEM_MODE(SEMI_AUTOMATIC) … and SYSTEM_THREAD(ENABLED)

not sure if SYSTEM_MODE(MANUAL) would make the Cellular.command() be asynchronous.

See debug log below:

0=~=~=~=~=~=~=~=~=~=~=~= PuTTY log 2017.04.20 22:18:15 =~=~=~=~=~=~=~=~=~=~=~=
0000007560 [app.main] WARN: Waiting for serial input before proceeding
0000008061 [app.main] WARN: Waiting for serial input before proceeding
0000008561 [app.main] WARN: Waiting for serial input before proceeding
0000009062 [app.main] WARN: Received serial input, now proceeding!
0000009062 [app.main] INFO: Serial monitor open!  Now proceeding!
0000009063 [app.main] INFO: Device ID: xxxxxxxxxxxxxxxx
0000009063 [app.main] INFO: SYSTEM RESET REASON IS: 
0000009063 [app.main] INFO: Power-down reset
0000009063 [app.main] WARN: Free RAM is: 98132 bytes
0000009064 [app.main] INFO: Checking if modem is on before attempting to proceed with acquireIMSI() function
0000009064 [app.main] INFO: Checking if the modem is on...
     9.065 AT send       4 "AT\r\n"
0000009175 [app.main] INFO: Response code was -1 (WAIT)
0000009175 [app.main] INFO: Returning modemIsOn() = FALSE
0000009175 [app.main] INFO: Need to turn modem on in order to get IMSI number.. this will take about 500 ms
[ ElectronSerialPipe::begin ] = = = = = = = =

[ Modem::powerOn ] = = = = = = = = = = = = = =
     9.487 AT send       4 "AT\r\n"
0000009681 [app.main] INFO: Checking if the modem is on...
     9.681 AT send       4 "AT\r\n"
0000010692 [app.main] INFO: Response code was -1 (WAIT)
0000010692 [app.main] INFO: Returning modemIsOn() = FALSE
0000010692 [app.main] INFO: Checking if the modem is on...
    10.693 AT send       4 "AT\r\n"
    10.807 AT send       4 "AT\r\n"
0000011703 [app.main] INFO: Response code was -1 (WAIT)
0000011703 [app.main] INFO: Returning modemIsOn() = FALSE
0000011703 [app.main] INFO: Checking if the modem is on...
    11.704 AT send       4 "AT\r\n"
    11.707 AT read UNK   3 "AT\r"
    11.714 AT read OK    6 "\r\nOK\r\n"
AT command response type = 0X110000 (TYPE_OK)
AT command response buffer = 
OK

Modem is on!
0000011714 [app.main] INFO: Response code was -2 (RESP_OK)
0000011715 [app.main] INFO: Returning modemIsOn() = TRUE
0000011715 [app.main] INFO: About to request the 4th byte of E.F. 6FAD from SIM card via modem
    11.716 AT send      25 "AT+CRSM=176,28589,0,3,1\r\n"
    11.726 AT read UNK  24 "AT+CRSM=176,28589,0,3,1\r"
AT command response type = 0 (TYPE_UNKNOWN)
AT command response buffer = AT+CRSM=176,28589,0,    11.727 AT read ERR   9 "\r\nERROR\r\n"
3,1

    12.038 AT send       4 "AT\r\n"
    12.048 AT read UNK   3 "AT\r"
    12.048 AT read OK    6 "\r\nOK\r\n"
AT command response type = 0X110000 (TYPE_OK)
AT command response buffer = 
OK
M=176,28589,0,3,1

0000012049 [app.main] ERROR: AT command to get MNC length returned RESP_OK but no MNC length was parsed!
0000012049 [app.main] INFO: About to request the 4th byte of E.F. 6FAD from SIM card via modem
    12.050 AT send      25 "AT+CRSM=176,28589,0,3,1\r\n"
    12.058 AT read UNK  24 "AT+CRSM=176,28589,0,3,1\r"
    12.061 AT read ERR   9 "\r\nERROR\r\n"
AT command response type = 0X120000 (TYPE_ERROR)
AT command response buffer = 
ERROR
 o
0000012061 [app.main] INFO: About to request the 4th byte of E.F. 6FAD from SIM card via modem
    12.062 AT send      25 "AT+CRSM=176,28589,0,3,1\r\n"
    12.073 AT read UNK  24 "AT+CRSM=176,28589,0,3,1\r"
AT command response type = 0 (TYPE_UNKNOWN)
AT command response buffer = AT+CRSM=176,28589,0,3,1

    12.079 AT read ERR   9 "\r\nERROR\r\n"
    12.389 AT send       4 "AT\r\n"
    12.394 AT read UNK   3 "AT\r"
AT command response type = 0 (TYPE_UNKNOWN)
AT command response buffer = AT
CRSM=176,28589,0,3,1

    12.399 AT read OK    6 "\r\nOK\r\n"
    12.399 AT send       7 "AT E0\r\n"
    12.404 AT read UNK   6 "AT E0\r"
AT command response type = 0 (TYPE_UNKNOWN)
AT command response buffer = AT E0
M=176,28589,0,3,1

    12.409 AT read OK    6 "\r\nOK\r\n"
    12.409 AT send      11 "AT+CMEE=2\r\n"
    12.415 AT read OK    6 "\r\nOK\r\n"
AT command response type = 0X110000 (TYPE_OK)
AT command response buffer = 
OK
M=176,28589,0,3,1

0000012415 [app.main] ERROR: AT command to get MNC length returned RESP_OK but no MNC length was parsed!
0000012416 [app.main] INFO: About to request the 4th byte of E.F. 6FAD from SIM card via modem
    12.417 AT send      25 "AT+CRSM=176,28589,0,3,1\r\n"
    12.427 AT read ERR  24 "\r\n+CME ERROR: SIM busy\r\n"
AT command response type = 0X120000 (TYPE_ERROR)
AT command response buffer = 
+CME ERROR: SIM busy

0000012428 [app.main] INFO: About to request the 4th byte of E.F. 6FAD from SIM card via modem
    12.428 AT send      25 "AT+CRSM=176,28589,0,3,1\r\n"
    12.439 AT read ERR  24 "\r\n+CME ERROR: SIM busy\r\n"
0000017439 [app.main] INFO: About to request the 4th byte of E.F. 6FAD from SIM card via modem
    17.439 AT send      25 "AT+CRSM=176,28589,0,3,1\r\n"
    17.470 AT read  +   21 "\r\n+CRSM: 144,0,\"02\"\r\n"
AT command response type = 0X400000 (TYPE_PLUS)
AT command response buffer = 
+CRSM: 144,0,"02"
:
Found MNC length == 2
    17.481 AT read OK    6 "\r\nOK\r\n"
AT command response type = 0X110000 (TYPE_OK)
AT command response buffer = 
OK
M: 144,0,"02"
:
0000017482 [app.main] INFO: Successfully parsed the MNC length..
================>  MNC length is: 2
0000017482 [app.main] INFO: About to request the IMSI number from the modem
    17.482 AT send       9 "AT+CIMI\r\n"
    17.493 AT read UNK  19 "\r\nxxxxxxxxxxxxxxxxxxx\r\n"
AT command response type = 0 (TYPE_UNKNOWN)
AT command response buffer = 

xxxxxxxxxxxxxxxxxx

Successfully Parsed IMSI: xxxxxxxxxxxxxxxxxxxxx
    17.504 AT read OK    6 "\r\nOK\r\n"
AT command response type = 0X110000 (TYPE_OK)
AT command response buffer = 
OK
74300556497

0000017504 [app.main] INFO: Successfully parsed a potential IMSI number..
================>  parsed string is: xxxxxxxxxxxxxxxxxxxxx
0000017505 [app.main] INFO: Now figuring out which cell credentials to use based on IMSI
0000017506 [app.main] INFO: MCC_str == 214
0000017506 [app.main] INFO: MNC string = 07
0000017506 [app.main] INFO: decoding IMSI resulted inL 
 MCC = 214  |  MNC = 07
0000017507 [app.main] INFO:   IMSI = xxxxxxxxxxxxxxxxx|  MCC = 214  |  MNC = 07  | MNC_len =                                                                                              ~
0000017509 [app.main] INFO: ASSUME A PARTICLE SIM CARD
0000017509 [app.main] ERROR: YOU HAVEN'T FINISHED CODING THIS BIT YET, NOW HAVE YOU? 8==D--
0000017510 [app.main] INFO: Attempting a quick credential change!
0000017510 [app.main] INFO: Calling Particle.connect()
0000017511 [app.main] WARN: setup() ending...


==================
      END
       OF
     SETUP()
==================

_
_
even though Cellular.command() is not listed in the list of Synchronous System Functions in the firmware reference:

What’s more is that I can’t think of a situation in which user application code could have two user threads running concurrently which would necessitate calling function variable context via the passed void* parameter. I didn’t think the Particle Firmware API allowed for user app multi-threading.

That begs the question though: are there there situations where the system thread could be running some user-written callback code that could itself have a Cellular.command() call contained within it? For example, could Cellular.command() be called from within the callback in order to chain AT commands sequences? If so, then the context bit starts to make sense.


#4

I’d have to double check, but just by the fact that we are dealying with two seperate modules (STM32 µC and a the ublox cellular modem) which communicate via RX/TX without flow control, I doubt the code will always be synchronous.
As soon the modem reports back that the command has been understood Cellular.command() will come back and continue.
Granted, some AT commands may be processed immediately and the modem might not return till these sync commands are finished, but other potentially longer running commands (e.g. requesting network information) may well be designed to run async.

BTW, the data parameter cannot overcome the restrictions imposed on local/automatic variables as well as it wouldn’t be safe to use a pointer into an object which might get relocated.
I rather thought of something like this silly sample (which lacks some details for clarity ;-))

struct foo {
  char cmd[32];
  char result[32];
}

foo cmdResults[] = 
{ {"AT+cmd0", ""}
, {"AT+cmd1", ""}
, {"AT+cmd2", ""}
, {"AT+cmd3", ""}
...
, {"AT+cmdN", ""}
};

int cb(int type, const char* buf, int len, void* param) {
  len = constrain(len, 0, 31);
  memcpy(((foo*)param)->result, buf, len);
  return len;
}

void setup() {
  for (int i = 0; i < N; i++) 
    Cellular.command(cb, &cmdResults[i], 10000, cmdResults[i].cmd);
}

This way the respective results will end up in the correct result field tied to the corresponding cmd field.