HttpClient times out on first request after some idle time

I have made a simple RFID timeclock. All the Photon does is read in the serial from an RFID reader and uses the HttpClient to GET a url like www.domain.com/rfid.php?id=AAFD46358E… The PHP handles the rest.

When you boot the Photon, it works fine. You can read RFID cards and it will hit the URL no issues. The problem happens after the photon has been sitting for a while. Prior to the RFID scan, the photon is “breathing” blue. You scan the card, and HttpClient returns -1 after 5 seconds or so. If at that point you scan again, it goes through no problem as well as any subsequent scans. I’ve scanned 30 cards in a row without an issue.

I have added a “if(WiFi.ready())” before calling the HttpClient but the Photon feels it’s ready. But for whatever reason that first request does not want to go through. Any thoughts?

Code snippet below:

void loop() {
    
    if (ledTimeout && (ledTimeout < millis())) 
    {
        ledTimeout = 0;
        RGB.control(false);
    }
}

void serialEvent1()
{
    nextTime = millis() + 300;  // start the 300ms timer
    
    while(nextTime > millis())
    {// wait 300ms for the rest of the data.
        if (Serial1.available())
        {
            char c = Serial1.read();
            incoming = incoming + c;
        }
    }    // end 300ms timout..
   
    postRfidRead(incoming.substring(1,13));   // clean up some extra characters
    incoming = "";   
}

void postRfidRead(String tag)
{
    request.hostname = "example.com";
    request.port = 80;
    request.path = "timeclock.php?id=" ;
    request.path.concat(tag);

    // Get request
    if(WiFi.ready())
    {
        http.get(request, response, headers);
      
        RGB.control(true);  
        RGB.brightness(255);
        
        if(response.status != -1)
        {
            if(response.body.startsWith("clockin"))
            {
                RGB.color(0, 255, 0); // green
            }
            else
            {
                if(response.body.startsWith("clockout"))
                {
                    RGB.color(255, 0, 0); //  red
                }
                else
                {
                    RGB.color(255, 255, 0); // unrecognized card - yellow
                }
            }
        }
        else
        {   // there was an error with the request
            RGB.color(255, 255, 255);  // white
        }
    }
    else
        RGB.color(44, 254, 249);   // wifi not ready  teal
    
     ledTimeout = millis() + 1500;  // start the LED timeout timer          
}
void serialEvent1()
{
    nextTime = millis() + 300;  // start the 300ms timer
    
    while(nextTime > millis())
    {// wait 300ms for the rest of the data.
        if (Serial1.available())
        {
            char c = Serial1.read();
            incoming = incoming + c;
        }
    }    // end 300ms timout..
   
    postRfidRead(incoming.substring(1,13));   // clean up some extra characters
    incoming = "";   
}

What data type is your nextTime variable?
It should be uint32_t, unsigned int or unsigned long

Next, you should do millis() compare like this instead

uint32_t ms = millis();
while(millis() - ms < 300) { ... }

You should also refrain from delaying serialEvent() or at least add a Particle.process() call in your delay loop.

Your serialEvent logic doesn’t seem to have any protection against “half-read” data other than the “timeout logic”

nextTime is an unsigned int

Can you explain a bit why you set up your while() that way? I try not to put math in loops like that. I’d rather do the math once than every iteration. Does it matter in this case and platform? Probably not, there is plenty of computing power available. Guess it’s just a habit from when it did matter…

I agree with you about the less than optimal serial loop. It was quick and dirty. Ideally it should be re-written to process each character and watch for the end of line. But the current code does read the RFID serial without issue. The RFID reader has a minimum cycle time after a read so it won’t be sending more than one card in 300ms. And any card that is not an exact match for one in the database (like a partial read) will be rejected by the server and show yellow alerting the user of a bad read (this has not happened in any of my testing).

Thanks!

There is a great explanation about this over here

BTW

Do you mean breathing cyan, blue would indicate an issue

But looking into the HTTPClient lib, this seems to be part of your troubles

static const uint16_t TIMEOUT = 5000; // Allow maximum 5s between data packets.

Ok, thanks for the link. While the probability of a scan exactly at the 49 day rollover is infinitely small, better to have code that works 100% of the time. I have updated my while loop.

And yes, after a comparison, it is indeed breathing cyan, not blue.

I’m unclear how the timeout could be an issue. I agree when it doesn’t work, it is indeed timing out but the question is why it’s timing out. I have not looked at it with wireshark or anything, but Apache logs show the request is never sent. Guess I’ll have to enable logging on the HttpClient lib and see if that tells me anything useful.

Thanks again!

I could imagine that this is a case of a half open connection where either side might have closed it while the other one is still hanging on to it.
In order to (re)open a valid connection the side holding the half-open needs to let go of that first (e.g. client.stop() on the underlying TCPClient in HTTPClient).
This seems to happen implicitly with your first failing request and hence subsequent ones do succeede.

I think you are in the right track as far as it being a half open connection issue. The HttpClient has a client.stop() before it returns the results so I’m not sure what the issue is. I feel like someone would have run into this issue before so I’m guessing it’s something specific to my implementation or environment. The server is a standard cPanel/ centos server.

In the mean time, adding a 5 minute server heartbeat has provided a bandaid. So far it has not had an issues since having it ping the server every 5 minutes. Not really an ideal fix though. Thanks for the help!

1 Like