hasCredentials() always returns false unless .connect() has been called

Greetings!

I am using the SEMI_AUTOMATIC mode and would like to be able to show various connection states via an external LED. For the most part, this is doable by observing WiFi.ready(), WiFi.connecting(), and Spark.connected(). However, there seems to be no way to anticipate the core going into the smart config mode if no credentials are stored in the CC3000.

Specifically, I would expect to be able to call WiFi.hasCredentials() before doing WiFi.connect() to figure out if the smart config mode will get triggered. But the hasCredentials method seems to rely on reading a value from the NVMEM_Spark_File_Data variable, which does not get populated with the profile data stored in the CC3000 non-volatile memory until WiFi.connect() is called:

https://github.com/spark/firmware/blob/master/src/spark_wiring_wifi.cpp#L148

And of course, once WiFi.connect() is called and it goes into the smart config mode, the user code is blocked indefinitely, so WiFi.listening() is kind of useless.

Is there a different way to achieve what I am trying to do, or does this require a firmware change? And if so, is there a plan to address this in the future?

Perhaps it would be possible to add a WiFi.tryConnect() method that would return different values depending on the configuration of the CC3000. Something like:

if (WiFi.tryConnect() == WIFI_NO_CREDENTIALS) {

  // user code to toggle external LED to indicate credentials are needed
  WiFi.listen();
}

I am pretty new to C++, so apologies in advance for “suboptimal” code suggestion :smile:

Thanks,
Alexander

Maybe @zachary or @kennethlimcp can comment here. I thought that design was such that you are meant to call WiFi.setCredentials() with SSID/password strings if you always want to associate with a particular AP or APs.

Note that the strings could be hard-coded or they could come from non-volatile storage on the Spark. I can imagine having a way to update or add to the non-volative SSID/password storage over the air, preferably via Spark.function() since any other way you would have to send them in the clear or have your own encryption scheme.

1 Like

I will test it out and let you know. For now, based on what I remember, there’s a slight issue with the returned status and some flag setting issue.

Once I digged further, I will update this post :slight_smile:

1 Like

If you cannot test if you hasCredentials() before WiFi.connect()ing then in certain forseeable situations you are in a deadly embrace.

E.g. What if you only want to setCredentials() if you have not already done so? E.g. the user code contains a failsafe config only to be used if and only if no other credentials have been set.

The current situation is best seen as a bug. And one which possibly could be more easily fixed in code than it might be explained away or excused in text :smile:

Just jumping in to say that i’m looking at what’s causing the issue now :wink:

1 Like

Looking at my own code, I call WiFi.setCredentials() before calling WiFi.connect() and it works fine. The only surprise for me was that I had to increase my timeout value of the loop after connect looking for WiFi.ready() to be true to be 20 seconds. 10 seconds was not always enough.

Maybe I’m not fully understanding the problem, but to avoid listening mode, have you tried first calling Wifi.hasCredentials() and if it returns false don’t bother calling Wifi.connect()? AFAIK, will achieve the same result as your tryConnect() suggestion.

1 Like

Ok guys, need your inputs here now:

SYSTEM_MODE(SEMI_AUTOMATIC);

void setup(){
  if(!WiFi.hasCredentials()){
    WiFi.listen();
  }
}

has been tested working. However, if(WiFi.hasCredentials == "false") throws me a compilation error

you’ll kick yourself! :slight_smile: just remove the quotes - it the result is a bool rather than a string

if(WiFi.hasCredentials == false)
1 Like

opps. :wink: but anyways, the code above works with an empty factory reset core.

Tested and working for sure.

My understanding of the original question was for the opposite of this - to not enter listening mode but rather simply not connect so that the “not connected and no credentials” status can be displayed.

So should it be simply changing what’s in the loop (now containing WiFi.listen()?

if (WiFi.tryConnect() == WIFI_NO_CREDENTIALS) {

  // user code to toggle external LED to indicate credentials are needed
  WiFi.listen();
}

VS

SYSTEM_MODE(SEMI_AUTOMATIC);

void setup(){
  if(!WiFi.hasCredentials()){
   //light up led
    WiFi.listen();
  }
}

maybe something like this?

void setup(){
  if(!WiFi.hasCredentials()){
      // set LED to indicate no credentials
  } else {
      WiFi.connect();
  }
}
2 Likes

@mdma This is exactly what I’ve tried, but my observation is that hasCredentials() will ALWAYS RETURN FALSE regardless of whether you have credentials stored or not UNTIL WiFi.connect() has been called.

To put it differently, this code will ALWAYS trigger listen mode:

SYSTEM_MODE(SEMI_AUTOMATIC);

void setup(){
  if(!WiFi.hasCredentials()){
   //light up led
    WiFi.listen();
  }
}

but this code will work correctly:

SYSTEM_MODE(SEMI_AUTOMATIC);

void setup(){
  WiFi.connect();
  WiFi.disconnect();
  if(!WiFi.hasCredentials()){
   //light up led
    WiFi.listen();
  }
}

Except of course it would not, if you didn’t have credentials stored, because .connect() will put it in listening mode, so it will block until credentials are supplied via USB or Smart Config App.

The reason it @mdma 's code does not work is because hasCredentials() internally checks the value of a data structure that’s not been properly initialized:

https://github.com/spark/firmware/blob/master/src/spark_wiring_wifi.cpp#L348

Here: NVMEM_Spark_File_Data[WLAN_PROFILE_FILE_OFFSET] != 0 will always be 0 until it’s populated during .connect() method:

https://github.com/spark/firmware/blob/master/src/spark_wiring_wifi.cpp#L148

I suspect that the reason for this is that you need to start the wlan chip first to be able to query it for the stored credentials, and it would not make sense to start it until we want to use it (for power consumption reasons, etc.)

Do you see the problem now?

Thanks!
Alexander

2 Likes

Why don’t you post your code? I don’t get your point and don’t think those that you mentioned are causing some issue.

It also depends on how you are testing it to figure out if it works or if it does not.

You are saying that you want your television to remain off while being able to browse through the channel. How is that possible?

The CC3000 needs to be powered on in order to communicate. With that, I don’t mean executing WiFi.connect()

Thanks for that explanation @akiesels, now I get it! I think we can fix this one pretty quickly - the solution is to try to populate the NVMEM variable in SPARK_WLAN_Setup() which is called to initialize the cc3000. Then they will be available for use for testing if there are any credentials.

https://github.com/spark/firmware/blob/master/src/spark_wlan.cpp#L378

Just after Delay(100) add

nvmem_read(NVMEM_SPARK_FILE_ID, NVMEM_SPARK_FILE_SIZE, 0, NVMEM_Spark_File_Data);

That will ensure the NVMEM_Spark_File_Data is initialized without needing to call WiFi.connect();

Would be great if you could try this out and submit a PR if it works!

3 Likes

Thanks @mdma. Just got around to trying your fix. Compiles ok, but when I flash the core it comes up and immediately throws a flashing red “SOS” message. Removed the line - everything peachy (except that hasCredentials is false before connect, of course). Here is my code to try it out, btw:

SYSTEM_MODE(SEMI_AUTOMATIC);
void setup()
{
	pinMode(D0, OUTPUT);  // RED LED attached here
	pinMode(D1, OUTPUT);  // BLUE LED attached here

	if (WiFi.hasCredentials()) {
		digitalWrite(D0, LOW);
		digitalWrite(D1, HIGH);
	}
	else {
		digitalWrite(D0, HIGH);
		digitalWrite(D1, LOW);
	}

	WiFi.connect();

	if (WiFi.hasCredentials()) {
		digitalWrite(D0, LOW);
		digitalWrite(D1, HIGH);
	}
	else {
		digitalWrite(D0, HIGH);
		digitalWrite(D1, LOW);
	}
}

I’ve got a red LED connected to D0 and a blue LED to D1 (each through a 220 ohm resistor). When the core boots, the RED LED comes on first and once the WiFi is connected, it goes off and the BLUE one lights up. Since I have the credentials stored, I should only ever see the BLUE one light up.

So, what’s causing it to crash running that nvmem_read code on line 378? Maybe CC3000 is not quite ready after the 100ms delay? I doubt it, though, as I tried upping it to 1000ms and it still causes the same red SOS (except now I can catch a glimpse of solid white on the core LED when it first boots).

Any other suggestions I can try?

Thanks,
Alexander

1 Like

P.S. Tried adding wlan_start(0) right after delay(100) and before calling nvmem_read. Works correctly now - no crash and only the BLUE LED lights up.

But do we want to call wlan_start(0) at this point? What if I want to keep the wlan off for a while for power consumption reasons? I suppose we could add logic to hasCredentials to check to see if wlan has been started and if not, start it a run nvmem_read then? Otherwise, use the current flag-based implementation.

Your thoughts?

Ah, my mistake - I thought wlan_start(0) was already called. That was my intent.
(I’m working with a different version of the code, so confusing new/old code.)

You can try putting the nvmem read inside WiFi::on(), after the wlan_start(0) that’s there, and call Wifi::on() in your setup() function. That should fix it! :slight_smile:

2 Likes

Thanks Matthew! Are you guys working on refactoring this code already? I found some more issues with the way connection states are evaluated. E.g. calling WiFi.on() will toggle WiFi.connecting() state, even though you’re not actually connecting, or similarly, calling WiFi.connect() and then later WiFi.disconnect() will also make WiFi.connecting() evaluate to true - again incorrect. There are others, like the inability to programmatically detect wrong wifi credentials (the infinite flashing green LED problem). I am happy to help with this, if I can, since I will have to make it work for my specific application, but if these are all known issues and fixes are in the works, then I will focus my energy elsewhere :smile:

Thanks again,
Alexander

1 Like