Is Cellular.command Thread safe?

Is Cellular.command thread safe? If not does what would be needed to make it thread safe?

Thanks

You can call Celluar.command from a worker thread. The caveat is that the modem is protected by a mutex, so if the system is using the modem (including during connecting to the cloud), the Cellular.command won’t execute until it’s done. This can take up to 5 minutes. Also if you’re issuing a different modem command from the application/loop thread, that would also block preventing your worker thread from continuing until the loop thread was done executing its modem command.

1 Like

Sometimes even the particle functions that handle cell connectivity block the main loop. Even in system_mode manual and threading enabled the loop stops running because “something” (not the user code) is executing AT commands… Not a good thing.

@rickkas7 Is there any way to know when the system is using the modem? This would be useful to know so that I would only issue Celluar. command when it’s free to prevent any blocked code execution.

Right now I’m using the below code, but from the above this could get blocked if the system is trying to send data and then the below gets called.

void PublishQueueAsync::getRSSI() {
	#if PLATFORM_ID == PLATFORM_ELECTRON_PRODUCTION
	static uint32_t prevgetRSSIMillis = 0;
	char response[10] = "";
	response[0] = 0;
	if (millis() - prevgetRSSIMillis >= 15000 && millis() > 5000){
		if (Particle.connected() && Cellular.ready()) {
			CellularSignal sig = Cellular.RSSI();
			wirelessRSSI = sig.rssi;
				
			if (String(mode5).compareTo("MODE_5_DISABLED") == 0 && millis() > 25000) {
				if (RESP_OK == Cellular.command("AT+UCGED=5\r\n")) {
					mode5[0] = 0; 
				}
			}

			rsrp[0] = 0; //resets the array
			if (RESP_OK == Cellular.command(callbackUCGED, response, 200, "AT+UCGED?\r\n")) {
			} else {
				earfcn = 0;
				rsrp[0] = 0; //resets the array
			}
			//Serial.println(rsrp);
		} else {
			wirelessRSSI = 0;
			earfcn = 0;
			rsrp[0] = 0; //resets the array
		}

		prevgetRSSIMillis = millis();
	}
	#endif
}

I just added the below (if (!haveEvent), to prevent this but I think this is a bad work around.

if (!haveEvent) {	
				if (String(mode5).compareTo("MODE_5_DISABLED") == 0 && millis() > 25000) {
					if (RESP_OK == Cellular.command("AT+UCGED=5\r\n")) {
						mode5[0] = 0; 
					}
				}

				rsrp[0] = 0; //resets the array
				if (RESP_OK == Cellular.command(callbackUCGED, response, 200, "AT+UCGED?\r\n")) {
				} else {
					earfcn = 0;
					rsrp[0] = 0; //resets the array
				}
			}

@wesner0019, ideally you would be able to test the mutex used by the DeviceOS which I will leave to @rickkas7 to help you with.

One note though is that you may want to NOT use String() in if (String(mode5).compareTo("MODE_5_DISABLED") == 0so as to avoid heap fragmentation. Instead, you should use if (strcmp(mode5, "MODE_5_DISABLED") == 0 assuming mode5 is a NULL terminated char array.

1 Like

@peekay123
mode 5 is used as below. Is there a way I can make it NULL terminated?

char mode5[20]; 
int callbackUCGED(int type, const char* buf, int len, char* response) {

// buf format:+RSRP: 162,5110,"-075.00",

sscanf(buf, "\r\n+RSRP: %*d,%d,\"%s\",\r\n", &earfcn, rsrp);
sscanf(buf, "\r\n+UCGED: %s \r\n", mode5);
if (earfcn >= 600 && earfcn <=1199) earfcn = 2; //1900Mhz
else if (earfcn >= 1950 && earfcn <=2399) earfcn = 4; //1700Mhz
else if (earfcn >= 5010 && earfcn <=5179) earfcn = 12; //700Mhz
else if (earfcn == 65535) earfcn = 0; //not known or not detectable
return WAIT;
}

We do recommend running Particle.publish calls and Cellular.RSSI(), or direct calls to AT+UCGED, in a worker thread to minimize disruption if they block. Often they can share a thread, or you can use a dedicated thread to only doing RSSI.

You can check Particle.connected() from your worker thread before calling Cellular.command(). This eliminates the most common cause of blocking that occurs during connecting to cellular. If you are not using the Particle cloud you can use Cellular.ready() instead.

It still could block for 5 seconds, but at least not 5 minutes. And since it’s in a dedicated thread it wouldn’t block the rest of your code so other things will still stay responsive.

1 Like

Is it best to use: (edit if looks like the 2nd option is used in particle code)

char mode5[20] = {0};

or

  1. char mode5[20];
  2. memset(mode5, 0, sizeof(mode5));
if (strcmp(mode5, "MODE_5_DISABLED") == 0 && millis() > 25000) {
   if (RESP_OK == Cellular.command("AT+UCGED=5\r\n")) {
       memset(mode5, 0, sizeof(mode5));
   }
}

You can use strncmp() to only compare the first n characters. No need to terminate (IIRC).

2 Likes

@ScruffR

so this could work?

if (strncmp(mode5, "MODE_5_DISABLED", 6) == 0 && millis() > 25000) {
   if (RESP_OK == Cellular.command("AT+UCGED=5\r\n")) {
       memset(mode5, 0, sizeof(mode5));
   }
} 

@wesner0019, yes if you only want to compare the first 6 characters.

1 Like

@wesner0019,

Can I ask what you are trying to accomplish with this UCGED mode 5 command?

I’m curious, as I am also issuing this call to my Electron LTE’s in order to get an accurate cell signal level. I’m doing this because the Electron LTE RSSI and quality readings are not useful. Are you experiencing the same issue?

I was wanting to get the actual RSRP singnal value and also wnated to determine what band the device was on. Below is a callback I used

int callbackUCGED(int type, const char* buf, int len, char* response) {
  // buf format:+RSRP: 162,5110,"-075.00",
  sscanf(buf, "\r\n+RSRP: %*d,%d,\"%s\",\r\n", &earfcn, rsrp);
  sscanf(buf, "\r\n+UCGED:  %s \r\n", mode5);
   if (earfcn >= 600 && earfcn <=1199) earfcn = 2; //1900Mhz
   else if (earfcn >= 1950 && earfcn <=2399) earfcn = 4; //1700Mhz
   else if (earfcn >= 5010 && earfcn <=5179) earfcn = 12; //700Mhz
   else if (earfcn == 65535) earfcn = 0; //not known or not detectable
  return WAIT;
}