Photon won't enter listening mode while connecting to wifi (works using setup button but not using external button)

My goal is to have a external button to put the photon into listening mode. It works generally, but calling WiFi.listen() from my button press does not always work. It is particularly important for listening mode to work in the case where someone changes their wifi password and needs to update the photon. That seems to be the area where I’m struggling. I’m in automatic mode so that my main loop doesn’t run until the cloud is connected. I’m trying to remedy that by adding code before the execution loop, but I’m running into trouble. Here are the details:

I’m currently on firmware v0.4.7 with SYSTEM_THREAD(ENABLED);, which seems to be working fine.
I’m currently putting all my code into setup() so that before loop() is called I am able to respond to button presses.
I confirmed that can respond to a button press and call code.
The only problem I have is that after a few milliseconds, calling WiFi.listen() doesn’t work. If I call it right away in setup(), it works. After a while, it won’t. I think this has to do with the photon blinking green and trying to connect to wifi. It does, however, work from the setup button on the photon itself. Is that because it uses interrupts? Or what can I do to make it work?

Have a look into the docs for STARTUP().
This ensures the code in the respective function is executed as early as possible - even before setup()


BTW: If you want help with “misbehaving” code, it would be good to show the code :wink:

1 Like

ScruffR,

Thanks for your reply. I’m not looking to execute code earlier, what I want is to not have WiFi.listen() be blocked by the photon connecting to wifi.

I did my best to reduce this down into the simplest case possible. If the delay is 100 or over, the photon will only reach listening mode after it is connected to the cloud. I want it to take priority over any wifi connection attempts the photon might be doing already.

#include "application.h"

SYSTEM_MODE(AUTOMATIC);

SYSTEM_THREAD(ENABLED);

void setup(void)
{
	delay(70);
	WiFi.listen();
}

Hopefully that helps!

Exactly this is the reason to use STARTUP because setup() will only execute after the device is cloud connected in SYSTEM_MODE(AUTOMATIC).
(Edit: With SYSTEM_THREAD(ENABLED) the system and the user thread start off “simultaniously” and “independently” till the might happen to meet at a shared resource where they need to synchronize their access to this resource - which seems to be the case here, whoever races the other out wins control over WiFi first and locks the other out)

Other ways around this are using non-AUTOMATIC system modes with or without SYSTEM_THREAD(ENABLED).

Acording to the docs, system_thread should execute setup immediately even in automatic mode.

When the system thread is enabled, application execution changes compared to
non-threaded execution:
setup() is executed immediately regardless of the system mode, which means
setup typically executes before the Network or Cloud is connected. Calls to
Particle.function(), Particle.variable() and Particle.subscribe() will work
as intended whether the cloud is connected or not. Particle.publish() will return
false when the cloud is not available and the event will not be published. see waitUntil below
for details on waiting for the network or cloud connection.

That is true, but WiFi.listen() in the OPs code on one side and most of the sub steps of cloud connection on the other side demand access to the WiFi module which is a shared resource.
So whoever comes first (which in this case will be the system thread) will set a mutex (or any other thread sync object) to block other threads from interfering, so your code is probably locked out till the temporary owner of the shared resource releases it.

So if you really need SYSTEM_MODE(AUTOMATIC) your best bet would be STARTUP() and the safest way would be to go non-AUTOMATIC.

1 Like

have to ask, I presumed that if wifi creds were originally setup, then the password on the wifi was changed later, the wifi connection would time out and go to the listening mode. Is that false? does it hang in a loop believing it will connect?

I’m pretty sure it won’t enter Listening Mode just by itself.

One reasion:
Since it could well be that the SSID stored and the one your device currently sees are the same but don’t actually name the same network (e.g. AndroidAP is quite common).
So you don’t necessarily want your device to go into Listening Mode but rather have it try one of the other stored WiFi credential sets agains this or another SSID availabe.

And yes, the device will keep trying to connect to one of the stored WiFis.
Imagine a device sitting in your car and collecting data and waiting for you to return home to hop on your home WiFi. You wouldn’t want your device falling into Listening Mode and hence missing your return home.

But still, in such cases (planned offline use and change in WiFi settings) it’s still best to choose a suitable system mode and code accordingly.

1 Like

@ScruffR & @MORA. Thanks for your help. Here’s the issue though, I’m not looking for code that executes before the photon connects to wifi. I’m wanting to enter setup mode after the photon has started trying to connect to wifi but before it successfully connects. You can replicate the issue by changing your wifi password or leaving the range of your wifi network and then reseting your photon. I can use the included “setup” button in order to enter setup mode. In my product, we have another button that fits with the case. We use it both for reset and to enter listening mode. Listening mode doesn’t work unless they’re already connected to the cloud, or the hold the button while resetting the device. When the photon is stuck connecting, the entering Wifi.listen() doesn’t work.

To achieve this you’d need to interrupt any running connect and the tool for this are interrupts.

But you’d need an event that triggers the interrupt.
So you’d need to set up your button as a way to trigger the interrupt.

Is the interrupt sure to work?

No guarantees, but give this a try

//SYSTEM_THREAD(ENABLED)
SYSTEM_MODE(SEMI_AUTOMATIC)
STARTUP(prepare())

#define DEBOUNCE 250

volatile uint32_t msListening;

void ISR()
{
    if (millis() - msListening < DEBOUNCE) return;
    msListening = millis();
    // start/stop listening depending on current state
    
    if (!WiFi.listening())
    {
        Particle.disconnect();
        WiFi.listen();
    }
    else
    {
        WiFi.listen(false);
        Particle.connect();
    }
}

void prepare()
{
    pinMode(D7, OUTPUT);
    pinMode(D6, INPUT_PULLUP);
    attachInterrupt(D6, ISR, FALLING);
    Particle.connect();
}

void setup() { }

void loop() { }
1 Like

hi ScruffR:
I was searching this forum, and it works perfectly to me and it’s what exactly I want. I just have a question. I saw a link from here. Photon Interrupt Simultaneous Pin Usage
this link doesn’t mention about D6 can be interrupt pin. So how does your code work so perfect?

This totally looks like it’s going to work!! Thanks so much! You saved me!!!

One last followup: this is totally different than how I was doing my button before. Is there a simple way to differentiate between short and long presses?

I guess you can have 2 debounce ifs… One for long one for short…??

If you want to distinguish between long and short press you need to get informed about the button release too.
You could either do that by moving most of the logic out of the ISR and only set msListening (which you'd rename to something apropriate for its new use), and then poll for the button release in loop().

Or you go the "because-I-can-do-it" way :wink:
For that you put all the timing logic into the ISR too and set the trigger on CHANGE instead of FALLING, read the trigger pin to see if it's a press or release event and act uppon the time between the two.

@mikelo, the thread you linked talks about a point to keep in mind when using attachInterrupt(), but the docs tell the whole story.

@ScruffR, the code has to stay in the ISR since the whole reason for the ISR is the fact that WiFi.listen() stalls loop(). The biggest thing to remember is that millis() does NOT run in an ISR so it can’t be used for timing, only debouncing. During bounce, the ISR is called multiple times but once the bounce has settled, it is no longer called. Even with a CHANGE interrupt, the ISR will not be able to time the amount of time the button is pushed since millis() won’t change within the ISR.

@peekay123, true to some extent.

Once Listening Mode is entered loop won’t run (unless SYSTEM_THREAD(ENABLED)), so entering Listening Mode and resetting will work independently even outside of loop.
And you can’t reset once you entered Listening Mode with that approach - unless you use SYSTEM_THREAD(ENABLED) since Listening Mode got fixed to not block in multi threading. But this would reintroduce the thread-sync-issue pointed out above, counteracting the abort-connect feature.

And true about millis() for one trigger to the ISR, but for the CHANGE you’ll get multiple triggers between which millis() will be pushed on, allowing to time the press-hold time (FALL triggers once and leaves, RAISE triggers again and leaves again - two different values for millis())
Alternatively micros() could be used even within one ISR call.

@ScruffR, good points highlighting the fact that we (Elites, Particle) should have a “standardized” way to dealing with the WiFi.listen() “states”.

As for the CHANGE interrupt, you will get both firing on the bounce, creating double interrupts which will get filtered via the debouncing. You are correct, however, in that once the button is released, a second (non debounce) millis() counter will indicate the amount of time the button was held down for, but ONLY when the button is released. So the user will need to short or long press/release for the ISR to do its magic :wink:

1 Like

Agreed, I’d go for the CHANGE ISR anyway and when I find time extend my above code for short/long press, just for the fun of it :sunglasses:

1 Like