Robin
Robin

Reputation: 1587

PHP Serial connection read timeout

I'm trying to figure something out here

I have a 'sequence' to be executed via a serial port (on an RPI).

I have an supervisored PHP command in Laravel running that connects to a MQTT broker.

When I send a message to that broker, the RPI picks it up and processes it. Now, I have a moment in which I wait for user interaction. The issue here is, sometimes the user does not interact with the system and the PI keeps "waiting" for serial data. When a user presses a button, I get serial data, which I can process.

I tried to use a while (true) {} loop that reads the serial data, but it just stops suddenly. Here is some example code;

$configure = new TTYConfigure();
$configure->removeOption("9600");
$configure->setOption("115200");

$this->serialPort = new SerialPort(new SeparatorParser("\n"), $configure);

$serialDevice = config('app.device_type') === 'usb' ? '/dev/ttyACM0' : '/dev/ttyAMA0';

$this->serialPort->open($serialDevice);

// this is a special one, we add an timeout here of 15 seconds, to prevent that the machine would get stuck.
$timeoutStart = time();
$timeout = $timeoutStart + 15; // 15 seconds of timeout.
$aborted = false;

while (true) {
    $data2 = $this->serialPort->read();

    if (Str::contains($data2, "Whatever I want to check for")) {
        // Process the data and get out of this loop via a 'break;' statement
    }


    // check if 15 seconds have passed, if so, then we want to stop the vend sequence.
    if (time() >= $timeout) {
        $this->serialPort->write("C,STOP\n"); // STOP vending
        $aborted = true;
        $this->alert("vending sequence stopped");
    }
}

When I place logs in the true loop, I see it loops, but suddenly stops looping (I bet it's the $data2 = $this->serialPort->read(); that just "stops" reading or keeps reading the serial port.

I want to be able to kill the loops and do an API call to revert some changes that hapend before that action.

Is this possible? If so how?

Packages I use:

Upvotes: 7

Views: 892

Answers (2)

Daniels118
Daniels118

Reputation: 1227

If you look at the source of lepiaf\SerialPort you'll find it sets the stream in non blocking mode, however the read method does an infinite loop until it finds the separator. This means it will never return unless the separator is received, and depending on your configuration your script will be killed once the php max execution time is reached. Since the library is very simple the better option is to edit the read method adding a timeout parameter. Edit the file "lepiaf/SerialPort/SerialPort.php", scroll down to the read method (line 107) and change it as follows:

public function read($maxElapsed = 'infinite')
{
    $this->ensureDeviceOpen();
    
    $chars = [];
    $timeout = $maxElapsed == 'infinite' ? 1.7976931348623E+308 : (microtime(true) + $maxElapsed);
    do {
        $char = fread($this->fd, 1);
        if ($char === '') {
            if (microtime(true) > $timeout) return false;
            usleep(100);    //Why waste CPU?
            continue;
        }
        $chars[] = $char;
    } while ($char !== $this->getParser()->getSeparator());

    return $this->getParser()->parse($chars);
}

Then in your code call the method as:

$data2 = $this->serialPort->read(15);
if ($data2 === false) {
    //Timeout occurred
} elseif (Str::contains($data2, "Whatever I want to check for")) {
    //String found
} else {
    //Data received but string not found
}

Upvotes: 3

Kumara
Kumara

Reputation: 47

if (Str::contains($data2, "Whatever I want to check for"))

above code is your culprit.

$data2 = $this->serialPort->read();

might not read the whole string at once it will give data when it gets in to the read buffer. so best to collect data in an internal buffer and check the buffer for your condition.

$data2 .= $this->serialPort->read();

make sure to initialize data2 before the loop.

Upvotes: 0

Related Questions