aldas / modbus-tcp-client

PHP client for Modbus TCP and Modbus RTU over TCP (can be used for serial)
Apache License 2.0
191 stars 55 forks source link

BUG: stream_select interrupted by an incoming signal #151

Open netoscz opened 11 months ago

netoscz commented 11 months ago

I use this example script to communicate with Solax X3 G4 on COM port using RS485/ETH converter a then using modbus RTU over TCP protocol:

<?php

use ModbusTcpClient\Network\BinaryStreamConnection;
use ModbusTcpClient\Packet\ModbusFunction\ReadHoldingRegistersRequest;
use ModbusTcpClient\Packet\RtuConverter;
use ModbusTcpClient\Utils\Packet;

require '../vendor/autoload.php';

$connection = BinaryStreamConnection::getBuilder()
    ->setPort(myPort)
    ->setHost('myIP')
    ->setReadTimeoutSec(3) // increase read timeout to 3 seconds
    ->setIsCompleteCallback(function ($binaryData, $streamIndex) {
        return Packet::isCompleteLengthRTU($binaryData);
    })
    ->build();

$startAddress = 182;
$quantity = 1;
$slaveId = 1; // RTU packet slave id equivalent is Modbus TCP unitId

$tcpPacket = new ReadHoldingRegistersRequest($startAddress, $quantity, $slaveId);
$rtuPacket = RtuConverter::toRtu($tcpPacket);

try {
    $binaryData = $connection->connect()->sendAndReceive($rtuPacket);
    echo 'RTU Binary received (in hex):   ' . unpack('H*', $binaryData)[1] . PHP_EOL;

    $response = RtuConverter::fromRtu($binaryData);
    echo 'Parsed packet (in hex):     ' . $response->toHex() . PHP_EOL;
    echo 'Data parsed from packet (bytes):' . PHP_EOL;
    print_r($response->getData());

} catch (Exception $exception) {
    echo 'An exception occurred' . PHP_EOL;
    echo $exception->getMessage() . PHP_EOL;
    echo $exception->getTraceAsString() . PHP_EOL;
} finally {
    $connection->close();
}
?>

If I try to insert the same input data into any Windows EXE program used to test modbus RTU over TCP communication, then there is no problem, everything communicates as it should. But if I use them in the PHP code, see above, the result is always the error "An exception occurred stream_select interrupted by an incoming signal".

Specifically: An exception occurred stream_select interrupted by an incoming signal #0 /home/www/xxx.cz/www/fve.xxx.cz/vendor/aldas/modbus-tcp-client/src/Network/BinaryStreamConnection.php(67): ModbusTcpClient\Network\BinaryStreamConnection->receiveFrom() #1 /home/www/xxx.cz/www/fve.xxx.cz/vendor/aldas/modbus-tcp-client/src/Network/BinaryStreamConnection.php(90): ModbusTcpClient\Network\BinaryStreamConnection->receive() #2 /home/www/xxx.cz/www/fve.xxx.cz/ote/rtu.php(27): ModbusTcpClient\Network\BinaryStreamConnection->sendAndReceive() #3 {main}

It looks like a library bug. Please help.

aldas commented 11 months ago

https://github.com/aldas/modbus-tcp-client/issues/150#issuecomment-1669435654 or https://github.com/aldas/modbus-tcp-client/issues/150#issuecomment-1669610776 maybe this helps

netoscz commented 11 months ago

Unfortunately the thread ends with words "no luck :/". In the end, the problem was not solved. But do you think that this is not a problem of the library as such? But then I'm already desperate, where to look for the error elsewhere, when other programs connect to the inverter fine.

netoscz commented 11 months ago

The administrator of my VPS analyzed the situation on his side and wrote me the following: I tried to trace the system calls of the process that handled the running of the modbus3.php script, and I found that a connection was successfully established with the IP address MyIP, port MyPort. The process then sent 8 bytes of data on this connection and waited 300 milliseconds for a response, which did not arrive within this time limit. According to the files that were worked with subsequently (IOException.php, ModbusException.php), I conclude that the used library considers it an error condition and no longer waits.

I therefore recommend checking whether the connection and data transfer can be implemented in such a short time, and possibly increase the time limit.

Then I tried setting ->setDelayRead(5000_000) so I tried editing that piece of code in StreamHandler.php. Both without success.

aldas commented 11 months ago

ok, could you first try with https://www.modbusdriver.com/modpoll.html and see if it works or not. This is to verify if this mainly PHP/this library problem or something bigger.

That inverter manual says about timeouts: image

From https://gbc-solino.cz/wp-content/uploads/2022/07/Hybrid-X1X3-G4-ModbusTCPRTU-V3.21-English_0622-public-version.pdf page 8

after that I have modified your example to add more waits between steps and added logger to see debug output.

<?php

use ModbusTcpClient\Network\BinaryStreamConnection;
use ModbusTcpClient\Packet\ModbusFunction\ReadHoldingRegistersRequest;
use ModbusTcpClient\Packet\RtuConverter;
use ModbusTcpClient\Utils\Packet;

require __DIR__ . '/../vendor/autoload.php';
require __DIR__ . '/logger.php';

$connection = BinaryStreamConnection::getBuilder()
    ->setPort(5020) // FIXME to correct port
    ->setHost('myIP')
    ->setReadTimeoutSec(3) // increase read timeout to 3 seconds
    ->setIsCompleteCallback(function ($binaryData, $streamIndex) {
        return Packet::isCompleteLengthRTU($binaryData);
    })
    ->setLogger(new EchoLogger())
    ->build();

$startAddress = 182;
$quantity = 1;
$slaveId = 1; // RTU packet slave id equivalent is Modbus TCP unitId

$tcpPacket = new ReadHoldingRegistersRequest($startAddress, $quantity, $slaveId);
$rtuPacket = RtuConverter::toRtu($tcpPacket);

try {
    $connection->connect();
    usleep(200_000); // wait 200ms after connecting

    $connection->send($rtuPacket);
    usleep(300_000); // wait 500ms after send the packet, try different wait times here.
    $binaryData = $connection->receive();
    echo 'RTU Binary received (in hex):   ' . unpack('H*', $binaryData)[1] . PHP_EOL;

    $response = RtuConverter::fromRtu($binaryData);
    echo 'Parsed packet (in hex):     ' . $response->toHex() . PHP_EOL;
    echo 'Data parsed from packet (bytes):' . PHP_EOL;
    print_r($response->getData());

} catch (Exception $exception) {
    echo 'An exception occurred' . PHP_EOL;
    echo $exception->getMessage() . PHP_EOL;
    echo $exception->getTraceAsString() . PHP_EOL;
} finally {
    $connection->close();
}
aldas commented 11 months ago

https://community.home-assistant.io/t/universal-solar-inverter-over-modbus-rs485-tcp-custom-component-growatt-sofar-solax-solis/140143/786 in this tread people are talking about 1s delays.

netoscz commented 11 months ago

Hello, thanks for your care but it still fails. I use program PowerHud Modbus Tester and everything works perfectly in it. It connects fine and reads the register value with a refresh rate of 1s. It works like a charm. Unfortunately, the PHP script using the modbus-tcp-client library still does not work. Still the same error:

An exception occurred stream_select interrupted by an incoming signal #0 /home/www/xxx.cz/www/fve.bilek.cz/vendor/aldas/modbus-tcp-client/src/Network/BinaryStreamConnection.php(67): ModbusTcpClient\Network\BinaryStreamConnection->receiveFrom() #1 /home/www/bilek.cz/www/fve.xxx.cz/ote/modbus4.php(37): ModbusTcpClient\Network\BinaryStreamConnection->receive() #2 {main}

I tried this sleep values:

`try { $connection->connect(); usleep(2000_000); // wait 2000ms after connecting

$connection->send($rtuPacket);
usleep(5000_000); // wait 5000ms after send the packet, try different wait times here.
$binaryData = $connection->receive();
echo 'RTU Binary received (in hex):   ' . unpack('H*', $binaryData)[1] . PHP_EOL;`

Unfortunately, I couldn't use logger.php because I don't understand how the path should work require DIR . '/logger.php';

I don't have a logger.php file in the same directory from which I run my test script. Moreover, I did not find such a file anywhere in the library structure.

netoscz commented 11 months ago

I can also tell you that I previously tried modbus TCP (ie not RTU) communication using a wifi dongle in my inverter. The script using your library ran fine. The problem arose only after that when I connected the same inverter via the COM port RS485 serial line via the RS485/ETH Waveshare converter to the ethernet part connected by a cable to the router and using the new modbus RTU over TCP. But I repeat that even this RTU over TCP communication works without problems in the external program.