1. Advertising
    y u no do it?

    Advertising (learn more)

    Advertise virtually anything here, with CPM banner ads, CPM email ads and CPC contextual links. You can target relevant areas of the site and show ads based on geographical location of the user if you wish.

    Starts at just $1 per CPM or $0.10 per CPC.

Using fgets takes a long time

Discussion in 'PHP' started by lektrikpuke, Mar 24, 2013.

  1. #1
    I've tried to communicate with a gps via serial port recently. I did a bit of research and the simplest thing I found (fgets) seems to take forever before displaying data. Can someone tell me why, and how to fix the problem? The first line is using the command line to set params.

    PHP:
    1. `mode com6: BAUD=4800 PARITY=N data=8 stop=1 xon=off`;
    2. $fp = fopen ("COM6:", "w+");
    3.   if (!$fp) {
    4.   echo "Uh-oh. Port not opened.";
    5.   } else {
    6.   $string  =  "Send"  ;
    7.   fputs ($fp, $string );
    8.   sleep(5);
    9.   $buffer = fgets($fp);
    10.   echo $buffer ;
    11.   fclose ($fp);
    12.   }

    Solved! View solution.
    lektrikpuke, Mar 24, 2013 IP
  2. deathshadow

    deathshadow Prominent Member

    Messages:
    5,978
    Likes Received:
    825
    Best Answers:
    144
    Trophy Points:
    395
    #2
    Well, how big is the response? 4800 baud is pretty damned slow - Lemme think, 8N1 so 10 bits per data byte counting the start bit, that's easy... 480 bytes a second. Gonna be a while?

    Though I'm not sure you've got the complete code since shouldn't that initial string be inside an EXEC command? Is the device you are reading from capable of faster throughput? Try upping the baud rate to 38400 or faster. (if you are lucky it might work at 115200 since COM6 is probably a USB device, not a real com port)

    Not that I'd be trying to do serial comms from PHP in the first place, it's REALLY not well suited to it.
    deathshadow, Mar 25, 2013 IP
  3. lektrikpuke

    lektrikpuke Active Member

    Messages:
    267
    Likes Received:
    1
    Best Answers:
    1
    Trophy Points:
    63
    #3
    NMEA 0183 dictates the device communicates at 4800 baud. By a while, I'm pretty sure it runs till PHP timeout. I guess I could check that by changing the ini file. The example I found on the internet used the exec for initializing the device (not necessary after that). Yes, com6 is usb, using usb to com driver. I'm just playing/curious to make it work neatly in PHP.
    lektrikpuke, Mar 25, 2013 IP
  4. deathshadow

    deathshadow Prominent Member

    Messages:
    5,978
    Likes Received:
    825
    Best Answers:
    144
    Trophy Points:
    395
    #4
    Silly question -- is that 5 second 'sleep' REALLY neccessary?!? I mean, what's that for? The device can't take that long to get ready after your 'Send'... did you mean to use usleep (microseconds) instead of sleep (seconds)?

    If that's not it, strange as it sounds, you might want to make a program that uses fgetc, and outputs the timestamp (or better, difference between timestamps) to show how long it takes to start communications and profile the throughput times. Two simple functions and one global:

    Code (Text):
    1. $testStamp=0;
    2.  
    3. function resetStamp() {
    4.     global $testStamp;
    5.     $testStamp=microtime(true);
    6. }
    7.  
    8. function stamp() {
    9.     global $testStamp;
    10.     $newStamp=microtime(true);
    11.     echo $newStamp - $testStamp, '<br />';
    12.     $testStamp = $newStamp;
    13. }
    and then

    Code (Text):
    1.  
    2. if ($commPort = fopen ("COM6:", "w+");) {
    3.  
    4.     resetStamp();
    5.     fputs($commPort,'Send');
    6.     stamp(' fputs');
    7.  
    8.     $c=' ';
    9.     do {
    10.         $last = $c;
    11.  
    12.         resetStamp();
    13.         $c = fgetc($commPort);
    14.         stamp(' fgetc');
    15.  
    16.     } while (($c != "/n") && ($last != "/r"));
    17.  
    18.     resetStamp();
    19.     fclose ($commPort);
    20.     stamp('fclose');
    21.  
    22. } else echo 'Uh-oh. Port not opened.';
    23.  
    This would let you see exactly where the bottleneck is, assuming there even is one and it's not just that 5 second sleep.
    deathshadow, Mar 25, 2013 IP
  5. lektrikpuke

    lektrikpuke Active Member

    Messages:
    267
    Likes Received:
    1
    Best Answers:
    1
    Trophy Points:
    63
    #5
    Thanks for the follow up, but the bottleneck is more in the order of 45 seconds. The sleep part is not necessary. I just pasted as much of the original code that I got from who knows where.
    lektrikpuke, Mar 25, 2013 IP
  6. ThePHPMaster

    ThePHPMaster Active Member

    Messages:
    596
    Likes Received:
    30
    Best Answers:
    18
    Trophy Points:
    75
    #6
    Is this a threaded process or single process?

    Try accessing the com in a non-blocking mode (if its supported, never communicated with coms before so I wouldn't know):

    Code (Text):
    1.  
    2. stream_set_blocking($fp, 0);
    3.  
    ThePHPMaster, Mar 25, 2013 IP
  7. deathshadow

    deathshadow Prominent Member

    Messages:
    5,978
    Likes Received:
    825
    Best Answers:
    144
    Trophy Points:
    395
    #7
    It's not socketed communications, it's file-system level... so there is no such thing as blocking/non-blocking on it... and if there is, exiting before you actually have the data could be... risky.
    deathshadow, Mar 25, 2013 IP
  8. lektrikpuke

    lektrikpuke Active Member

    Messages:
    267
    Likes Received:
    1
    Best Answers:
    1
    Trophy Points:
    63
    #8
    I tried to debug it, and that gave me nothing. Thinking about trying it on a different OS. Maybe Win 7 is to blame?
    lektrikpuke, Mar 25, 2013 IP
  9. deathshadow

    deathshadow Prominent Member

    Messages:
    5,978
    Likes Received:
    825
    Best Answers:
    144
    Trophy Points:
    395
    #9
    Just be warned on other platforms it won't be a COM# device, and the mode command won't work. It'll be something like /dev/ttyS6 or some such.

    How about that 5 second sleep -- is that necessary and/or your problem?
    deathshadow, Mar 25, 2013 IP
  10. lektrikpuke

    lektrikpuke Active Member

    Messages:
    267
    Likes Received:
    1
    Best Answers:
    1
    Trophy Points:
    63
    #10
    No, sleep was the first thing I suspected. I don't even have it in the script I'm using. Oh, also tried fgetc, and although it did what advertised (returned one char -- $), it still took about the same amount of time. Very strange.
    lektrikpuke, Mar 26, 2013 IP
  11. Vick.Kumar

    Vick.Kumar Active Member

    Messages:
    137
    Likes Received:
    6
    Best Answers:
    0
    Trophy Points:
    65
  12. deathshadow

    deathshadow Prominent Member

    Messages:
    5,978
    Likes Received:
    825
    Best Answers:
    144
    Trophy Points:
    395
    #12
    @Vick, I don't think that's related -- it's not a socket operation... nor should the data set be very large since it's a location pull off a GPS. -- Something I didn't quite get with my first questions being the filesize, the single line of data shouldn't even be more than 60 characters....

    Though I was thinking, the 'response' back from the device should be more than one line, multiple GSV lines for each satellite, multiple GSA lines for signal amplitude, multiple GMC lines for the SSV offsets, in addition to the first line GGA for the calculated location.

    This code only looks at the first line and then tries to disconnect while data is still in the stream. That could be the problem... are you sure the 'send' command is only responding with one line of data?

    Again, why I'd check the response (and how long it takes) from a normal terminal program.
    deathshadow, Mar 27, 2013 IP
  13. deathshadow

    deathshadow Prominent Member

    Messages:
    5,978
    Likes Received:
    825
    Best Answers:
    144
    Trophy Points:
    395
    #13
    Oh, and that timer check I posted, try that, but removing all but the first resetStamp()

    Code (Text):
    1. if ($commPort = fopen ("COM6:", "w+");) {
    2.  
    3.     resetStamp();
    4.     fputs($commPort,'Send');
    5.     stamp(' fputs');
    6.  
    7.     $c=' ';
    8.     do {
    9.         $last = $c;
    10.  
    11.         $c = fgetc($commPort);
    12.         stamp(' fgetc');
    13.  
    14.     } while (($c != "/n") && ($last != "/r"));
    15.  
    16.     fclose ($commPort);
    17.     stamp('fclose');
    18.  
    19. } else echo 'Uh-oh. Port not opened.';
    ... and post up the result here. I'd be VERY interested to see WHERE the bottleneck is. You may even want to compare to $_SERVER['REQUEST_TIME_FLOAT'] (assuming you have PHP 5.4.x installed, which you SHOULD) -- the key to that code is to figure out WHERE the bottleneck is, NOT how long it takes overall. (the fgetc was just to break to down smaller for code profiling, NOT to try and speed it up)
    deathshadow, Mar 27, 2013 IP
  14. lektrikpuke

    lektrikpuke Active Member

    Messages:
    267
    Likes Received:
    1
    Best Answers:
    1
    Trophy Points:
    63
    #14
    Okay, I'll try that when I get a chance. Thanks for the feedback.
    lektrikpuke, Mar 27, 2013 IP
  15. lektrikpuke

    lektrikpuke Active Member

    Messages:
    267
    Likes Received:
    1
    Best Answers:
    1
    Trophy Points:
    63
    #15
    Okay, decided to do it now. Results as follows:
    0.001068115234375
    54.944907903671
    5.6982040405273E-5
    3.0040740966797E-5
    2.8133392333984E-5
    2.7894973754883E-5
    2.7894973754883E-5
    0.038169145584106
    lektrikpuke, Mar 27, 2013 IP
  16. lektrikpuke

    lektrikpuke Active Member

    Messages:
    267
    Likes Received:
    1
    Best Answers:
    1
    Trophy Points:
    63
    #16
    I ran it three times with the results shown above typical. The 2nd line in the order of 1 minute and the rest less than 5 seconds.
    lektrikpuke, Mar 27, 2013 IP
  17. deathshadow

    deathshadow Prominent Member

    Messages:
    5,978
    Likes Received:
    825
    Best Answers:
    144
    Trophy Points:
    395
    #17
    Ok, so the bottleneck is between opening the connection and getting the first byte -- though at over two and a half seconds PER BYTE that is far, far, FAR slower than 4800 baud.

    In general, I'd say the OS level API or talk between PHP and the OS is definitely the problem. This isn't really a shock since PHP was never really designed to talk to serial ports in the first place.

    I'd be tempted to suggest using an actual language with low level hardware access instead of PHP. That it works at all is a surprise, that it is miserable at doing it isn't.

    If you're on windows 7/newer and are willing to expose the powershell, you might be able to use a powershell script to pull the data instead, but have the script return the value to the console, which PHP could then read.
    deathshadow, Mar 27, 2013 IP
  18. #18
    I just tossed together a quick FPC (free pascal) program using the synaser unit (part of the synapse cross platform serial/usb library) and tested it with one of my Teensy's to make sure it worked. I put a win32 executable and the source code in a .rar file here:

    http://www.cutcodedown.com/for_others/lektrikpuke/comms.rar

    The code is pretty simple:
    Code (Text):
    1. program test;
    2.  
    3. {$apptype console}
    4.  
    5. {$IFDEF FPC}
    6.   {$MODE DELPHI}
    7. {$ENDIF}
    8.  
    9. uses
    10.     classes, sysutils, synaser;
    11.    
    12. const
    13.     serXon  = true;
    14.     serXoff = false;
    15.     serCTS  = true;
    16.     serRTS  = false;
    17.     CRLF = #10 + #13;
    18.    
    19. var
    20.     comm:tBlockSerial;
    21.    
    22. begin
    23.     writeln('starting connection to COM6');
    24.     comm:=tBlockSerial.Create;
    25.     try
    26.         comm.connect('COM6');
    27.         comm.config(4800, 8, 'N', SB1, serXoff, serRTS);
    28.         comm.sendString('Send' + CRLF);
    29.         repeat until comm.sendingData = 0;
    30.         writeln('result: ', comm.recvString(20000)); { 2 seconds timeout }
    31.     finally
    32.         comm.free;
    33.         writeln('done');
    34.     end;
    35. end.
    Try that and see if it's any faster. If it's still taking over a minute, the problem is most likely the device just takes that long to respond. If it responds lickety-split, it is that PHP sucks at hardware access.

    If it hangs... then I've got no clue.

    In terms of building it yourself (should you not trust my .exe or want to build on another platform) I included the 'necessary' parts of the Synapse library in the rar file, so all you'd need is a version of Free Pascal appropriate to your platform.

    http://www.freepascal.org/

    One thing about synaser is the sendString method does not end with crlf, which I'm assuming your device needs -- and the recvString method reads until it gets a CRLF, but does not include that sequence in the string it returns!

    Using a 'real' program to do it instead of trying to trick the console, filesystem and php into handling it might be all that's needed here. Again, PHP is meant to glue together output from OTHER software into HTML, not write real software that digs into the hardware, or the OS API, etc, etc... Really this is where a LOT of languages tend to fall flat on their faces -- Python, Ruby, Perl, etc, etc... Sometimes you need to get right down into a real compiled language to do things.
    Last edited: Mar 27, 2013
    deathshadow, Mar 27, 2013 IP
  19. lektrikpuke

    lektrikpuke Active Member

    Messages:
    267
    Likes Received:
    1
    Best Answers:
    1
    Trophy Points:
    63
    #19
    Coming this century - perhaps??? Wow, looked at the parent dir and saw a lot, of what appears to be, help for others there. No wonder your site is due this century. =)

    Thanks for your help. I'll try that this weekend if I get the time. I used to do some C++, so I might use that if PHP really is the problem.

    Thanks again. You went above and beyond!
    lektrikpuke, Mar 28, 2013 IP
  20. deathshadow

    deathshadow Prominent Member

    Messages:
    5,978
    Likes Received:
    825
    Best Answers:
    144
    Trophy Points:
    395
    #20
    Actually, that site was originally going to be filled with tutorials, but my apprentice who became the master, partner for making it, and the motivation behind it, Dan Schulz, passed away in 2009 and the project has languished ever since.

    Most of what was written for it was before CSS3 became real world deployable anyways, so it all needs rewrites, and the project is also hanging on my "It's done when it's done" CMS.
    deathshadow, Mar 28, 2013 IP