7

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:

  • Laravel lumen
  • PhpMqtt
  • lepiaf\SerialPort
Robin
  • 1,567
  • 3
  • 25
  • 67
  • https://stackoverflow.com/questions/56844170/how-to-read-serial-port-with-php this might help you but it will require additional python script to handle the reading from serial port and sending the data to PHP page – zimorok Nov 30 '21 at 03:50
  • 2
    `$this->serialPort->open($serialDevice);` inside the while loop seems superfluous. The port is already open at that point. Attempting to open it again might cause an error. – PMF Nov 30 '21 at 06:09
  • @PMF My bad, I copied a little bit too much, I have updated the question with the valid code. – Robin Dec 02 '21 at 13:22
  • Depending on the implementation (don't know about that PHP library) reading might be blocking. So if nothing is received, read won't return. – PMF Dec 02 '21 at 14:23
  • did you try using try & catch block in your loop to see if there is some exception messages?! – John Dec 04 '21 at 15:46
  • Yesn there is no exceptions. The reason it doesn't work with a timeout is because of the "waiting for a character" of the library... – Robin Dec 04 '21 at 16:38

2 Answers2

3

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
}
Daniels118
  • 1,149
  • 1
  • 8
  • 17
0
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.

Kumara
  • 39
  • 3