http://www.perlmonks.org?node_id=1006731

jagexCoder has asked for the wisdom of the Perl Monks concerning the following question:

Hi there I've come up with a routine that performs a serial read using the Win32:SerialPort library for a script I am writing. My issue is that everytime I run the script my output for the @buffer array is not completely what I expect. The size of the buffer is maximum of 80 bytes, any data beyond this point is ignored unless the 80 bytes collected do not include the packet of interest of which the collected data are disposed of and new data coming is stored. I am using a program called Docklight to simulate a serial port and test out various conditions for a packet that is built up of only [STX|VARIABLE PAYLOAD|ETX]. They are:
* STX / ETX have been found, example: 02 30 39 44 CE26 03 * > 80 bytes received on the serial line * > 80 bytes received but no STX/ETX needed packet found * A NAK was received
Ideally a packet of the type described in the first condition is needed, the other packets tested for the other conditions are ignored and the test for the right packet type is resumed again. My problem is that all of this is working just fine however if I send in any condition(s) other than the correct first one...and then I sent the proper condition at the end my results for the @buffer are like: G “GC(G 4 4L2[STX09DÎ&ETX] The part of the packet I've highlighted in brackets is the correct packet of interest. The data in front is junk and of which I don't need. The more error conditions that I test for (that is, send the error ridden packets) before I send the correct packet the more junk I get in front of the correct packet. However if I just submit the correct "condition" and not test out the other problematic scenarios I get the proper packet return with no junk in front: [STX09DÎ&ETX] here's my serial read code:
sub readSerialPort { my $stx_rx = 0; my $etx_rx = 0; my $nak = 0; @buffer = (); # Empty the buffer before the next packet read while ((!$etx_rx) || ((!$etx_rx) && ((scalar(@buffer) > 79)))) { $byte=$port->read(1); if (ord($byte) == 0) #if it's a space, skip { next; } if (($byte eq chr(0x15) && (scalar(@buffer) < $MAX_BUFFER))) # + if a NAK is found anywhere and the buffer is not filled yet { print "\nNAK was found! " . $byte . "\n"; $nak = 1; $retry_if_fail = 1; # Flag to tell the script to continue wa +iting for the proper packet after buffer reset print "\nRetry var changed to " . $retry_if_fail . "\n"; STDOUT -> flush(); print "BUFFER RETURNED WITH NAK => " . @buffer . "\n\n"; return; } if ($byte eq chr(0x02)) # if it's STX run this condition { $stx_rx = 1; print "\nSTX was found! " . $byte . "\n"; } elsif (($stx_rx == 1) && ($byte eq chr(0x03))) # if both the S +TX and ETX are found { print "\nETX was found! " . $byte . "\n"; $etx_rx = 1; $retry_if_fail = 0; # Zero indicates the SUCCESS condition STDOUT -> flush(); print "BUFFER RETURNED AT ALL OKAY => " . @buffer . "\n\n"; print "BUFFER RETURNED AT ALL OKAY => " . join("",@buffer) +. "\n\n"; } if (($stx_rx == 1) && ($etx_rx == 0) && ((scalar(@buffer) > ($ +MAX_BUFFER-1)))) # if only STX was found within 80 bytes { print "\nOnly STX was found! " . $byte . "\n"; $retry_if_fail = 1; # Flag to tell the script to continue w +aiting for the proper packet after buffer reset print "\nRetry var changed to " . $retry_if_fail . "\n"; STDOUT -> flush(); print "BUFFER RETURNED WITH ONLY STX => " . @buffer . "\n\n +"; return; } if (($stx_rx == 0) && ($etx_rx == 0) && scalar(@buffer) > ($M +AX_BUFFER-1)) { print "\n80 bytes seen but no STX/ETX was found within 80 +byte range! " . $byte . "\n"; $retry_if_fail = 1; # Flag to tell the script to continue +waiting for the proper packet after buffer reset print "\nRetry var changed to " . $retry_if_fail . "\n"; STDOUT -> flush(); print "BUFFER RETURNED with no STX or ETX=> " . @buffer . +"\n\n"; return; } push(@buffer, $byte); #if($retry_if_fail == 0) { # until (@buffer eq chr(0x02)) # { # shift(@buffer); # } # return @buffer; #} print "Contents of buffer is " . join("",@buffer) . "\n"; print "Buffer length is " . scalar(@buffer) . "\n"; print "Byte received : ".$byte."\n"; } } while(1) { STDOUT -> flush(); if ($retry_if_fail == 1) { # FAILED CONDITION readSerialPort(); print "Total Received Packet on failure was: " . join("",@buffer) . + "\n\n"; print "Error encountered! Continuing to look for incoming packets.\ +n"; } else { # SUCCESS CONDITION print "Okay cool I found what I was looking for -> " . @buffer . " +\n\n"; print "Okay cool I found what I was looking for -> " . join("",@bu +ffer) . "\n\n"; $retry_if_fail = 0; # Time to exit this loop and get on with life! +! last; } }
All advice appreciated guys! I've tried out various ways to remove the junk data that's in front of the correct packet but to no avail. I need the packet of interest (of type: STX_PAYLOAD_ETX) without any junk on the side for processing. Ideally I want it so that the junk data is not stored at all in the buffer and only the correct packet is stored however I am having trouble in getting this to work. Thank you EDIT: What I have noticed is that reducing the value of $MAX_BUFFER (set to 80) appears to reduce the amount of junk in front however I think for me this is not an option since I need this variable to be at 79/80 for my "conditions checks" to take place properly. EDIT2: Screenshot of error: http://i50.tinypic.com/eki1yg.png The data circled in red is the junk data whereas the data on the right of it is the packet of interest.

Replies are listed 'Best First'.
Re: Buffer in Serial read routine seemingly not working??
by graff (Chancellor) on Dec 03, 2012 at 00:02 UTC
    I'm not sure I understand your script, but let me see if I at least understand your task. What follows is actually a different plan from what you seem to be trying to do in the OP code, but (if I understand what you said about your sample data strings) I think it might be a useful approach (and might be easier to implement, relative to what you've tried so far):

    1. You want to read bytes continuously from a serial port
    2. Every time you receive STX (0x02), you want start capturing/storing input
    3. Having started capture/storage, you want to stop it when you receive ETX (0x03) and do something with the stored byte sequence, clear the capture buffer, and return to step 2.
    4. But if you get NAK (0x15) or if more than 79 (80?) bytes get stored before receiving ETX, you want to ditch the capture buffer without further ado and return to step 2.
    5. Something you didn't mention (but should be considered): if a second STX arrives before an ETX, NAK or 80 bytes have appeared, you should either treat this 2nd STX as data, or else ditch the capture buffer and start a fresh capture.

    In other words, a simple 2-state machine would do what you want. The initial state stores nothing and just watches for STX. Once STX is found, you switch to storing input and looking for ETX. You'll stop storing when either ETX or NAK is found or when 80 bytes are stored, whichever comes first, and return to the initial state; if ETX was found first, you'll do something with the stored data, otherwise you'll ditch it.

    If that isn't what you meant to describe, please try again to see if you can explain the task more clearly. If that is what you meant, then that description should lead you to a fairly simple script, along the lines of:

    my $capture_state = 0; # looking for STX my @buffer; while (1) { $byte = $port->read(1); if ( $byte == 0x02 ) { # got STX $capture_state = 1; @buffer = (); # (if we were already capturing for a prev STX, + ditch that) } if ( $capture_state ) { if ( $byte == 0x15 or @buffer > 79 ) { # got NAK or too many +bytes @buffer = (); $capture_state = 0; } else { push @buffer, $byte; if ( $byte == 0x03 ) { # got ETX printf "Received complete message of %d bytes:", scala +r @buffer; printf " %02x" for ( @buffer ); print "\n"; @buffer = (); $capture_state = 0; } } } }
    If you want a 2nd STX in the input to be treated as data, just comment out the @buffer = (); line in the first "if" clause.

    (update: made a minor fix to the 2nd "printf" format, to put a space between consecutive byte values)

      Thank you so much!! With some slight modifications this solution is working perfectly! :-) have a nice day.
      Yup you hit the hammer on the nail! I'll try out your advice later on today. Thank you
      So as per your method I would not be modifying my serial read() routine, just the infinite while loop I have at the bottom. Did I get that right? Thanks!