Before clarifying some of the "sideshow" topics I want to address your immediate issues
That's most likely due to the fact that Software Timers run on a separate thread with very a limitted stack quota. Henc you should not call any "elaborate" functions and need to consider the call depth of your function and its functon calls.
The typical way to go about this would be to keep the hard work in the application thread (aka
loop()) and essentially let the timer callback only set a flag to tell
loop() that it's time to do something (some conditional checks and brief precessing is fine tho').
I'd have to double check, but for this scenario I'd rather opt for
SYSTEM_MODE(MANUAL) in conjunction with
SYSTEM_THREAD(ENABLED) to keep the system from getting "opinionated" about the need to reconnect.
Now on to the "sideshow" topics raised:
While it is true, that
Particle.function()s (same as
Particle.subscribe()) need to be registered the latest a few seconds after a connection is established but there is absolultely no need to hold back from registering them before a connection is made.
The above function calls only tell the device which entities should be registered with the cloud once the cloud connection becomes available. So doing it at the earliest possible point in time (e.g.
setup()) is the safest bet.
The reason that such nuances may be missing in the docs is probably owed to the fact that the majority of the reference docs stem from a time before non-AUTOMATIC system modes were introduced or widely used and with AUTOMATIC mode there only is a time after the cloud connection was established because user code won't be executed without it
As a safe guard, you could setup a timer that checks for
WiFi.listening() and if your application currently wouldn't agree with that (e.g. by means of a dedicated flag)
WiFi.listen(false) can be called to end Listening Mode.
In order to get earliest info about loss of connection you could register a
System.on() event handler to be informed ASAP.
I'd rather wirte this
if (WiFi.getCredentials(&credAP, 1))
Log.info("Need to find SSID from credentials: %s", (const char*)credAP.ssid);
For one, this makes it clear that you are only interested in a single set of credentials and also ensures that you are dealing with the correct string pointer.
WiFiAccessPoint::ssid may well be a
String object and requesting a pointer to it may not actually give you the pointer to the string buffer but to the object itself.
So - since I tend to be too lazy to look it up - I'd opt for the safe bet which will work either way (that's also why I prefer using
(const char*)someString over
someString.c_str() as the former works for both - C strings and
String - while the latter only works with
String objects )
And finally, you should really check whether you actually got back a set of creds before trying to access the
Also when traversing your
availableAPS you may want to
break the loop once you found your desired AP.
BTW, scanning WiFi networks may take some time, so it shouldn't be done too frequently.
Finally, about this
in connection with
You can either follow the
Particle.connect() call with a
waitUntil() or - if you want your code to keep running during the ongoing connection attempt, you'd set a timeout-guard for the retry (I'd personally not trust
Particle.connecting() for that as your might just slip between the change of state and get a false report).
This would be my take on a timeout-guard
const uint32_t msRetryConnect = 30000; // allow at least 30 sec between retries
uint32_t msOngoingConnect = 0;
// when not connected and last attempt was long gone or it's actually the first
if (!Particle.connected() && ((millis() - msOngoingConnect > msRetryConnect) || !msOngoingConnect)
msOngoingConnect = millis(); // set the timeout base
Particle.connect(); // initiate (re)connect
// carry on with business