Closed haha8x closed 2 years ago
Could you use https://github.com/aldas/modbus-tcp-client/blob/master/examples/index.php to request some of these double fields and paste the output here. These HEX dumps of responses would be helpful and if you know approximate value what some double field has - please add it here.
also - those doubles are probably 64bits (double precision floating point values). Float is 32bit.
maybe this works (I have not tried):
$request->float($param->address, $param->code, function ($result, $readRegisterAddress, $response) {
$tmp = $response->getQuadWordAt($readRegisterAddress->getAddress())->getData();
return unpack('E', $tmp)[1]; // "E" = double (machine dependent size, big endian byte order) See: https://www.php.net/manual/en/function.pack.php
});
I can add 64bit double precision floating point support to APIs but I need some examples.
Hello aldas
This is the request I made
Using: function code: 3, ip: 192.168.100.13, port: 8989, address: 801, quantity: 12, endianess: 5
Packet to be sent (in hex): 32810000000616030321000c
Binary received (in hex): 32810000001b16031841c1ccc330104d82000000000000000040423d3604507c3e
I tried the code but it throws address out of bounds
As the manual of the Siemens PAC-3200 To get the double data on 801 address, it needs the swap the low byte and high byte. Then divide it by 1000 something like in nodejs
let val = await client.readHoldingRegisters(801, 4);
meter_data[kwh] = Number.isNaN(val.buffer.readDoubleBE()) ? 0 : val.buffer.readDoubleBE() / 1000;
I have tried BIG_ENDIAN_LOW_WORD_FIRST but still can not get the correct value
You are trying with nodejs also? What number does node give you. I need approximate (ala 2328 or 597263, should it be 4 digits for integer part or 5,6 digits something like that (before dividing by 1000)) to know if I am on the right path and unpacking it into the right number.
One number I am getting is 597263.96812737
after dividing by 1000 but I have no idea if it is even close to number what nodejs gives.
Do you have some diplay, webinterface for that device which shows that kwh amount?
Yes the number is 597263.96812737 It is the correct one that show on the meter screen.
Ok, then unpack('E', $tmp)[1]
is correct way.
about
I tried the code but it throws address out of bounds
I think this is related to that $request->float(
requests only 2 registers worth of data so we should wrap this workaround inside 4 register type. ala $request->int64(
as you had previously.
$request->int64($param->address, $param->code, function ($result, $readRegisterAddress, $response) {
$tmp = $response->getQuadWordAt($readRegisterAddress->getAddress())->getData();
return unpack('E', $tmp)[1]; // "E" = double (machine dependent size, big endian byte order) See: https://www.php.net/manual/en/function.pack.php
});
you can try this as a workaround meanwhile I add double
support to API.
Very nice, the Above code is working flawlessly.
But I got another problem that, since I use the parallel request the read the fc3. Here is the address list
It is working very well on my Macbook But on the production server, It got connection refuse from modbus server
Turn out if I remove the address 801, the connection is accpeted again on production server
But on the localhost, that error never happen
if ($meter->model->modbus_type == 'inputRegister') {
$request = ReadRegistersBuilder::newReadInputRegisters("tcp://{$this->gateway->ip}:{$this->gateway->port}", $meter->slave_id);
} else {
Endian::$defaultEndian = Endian::BIG_ENDIAN;
$request = ReadRegistersBuilder::newReadHoldingRegisters("tcp://{$this->gateway->ip}:{$this->gateway->port}", $meter->slave_id);
}
foreach ($params as $param) {
switch ($param->data_type) {
case 'double':
$request->int64($param->address, $param->code, function ($result, $readRegisterAddress, $response) {
$tmp = $response->getQuadWordAt($readRegisterAddress->getAddress())->getData();
return unpack('E', $tmp)[1];
// "E" = double (machine dependent size, big endian byte order) See: https://www.php.net/manual/en/function.pack.php
});
break;
default:
$request->float($param->address, $param->code);
}
}
try {
$request = $request->build(); // returns array of 3 requests
$client = new NonBlockingClient([
'readTimeoutSec' => 10, // timeout when waiting response from server
'connectTimeoutSec' => 10, // timeout when establishing connection to the server
]);
$responseContainer = ($client)->sendRequests($request);
$error = $responseContainer->getErrors();
if ($error) {
Log::error(json_encode($error));
} else {
$data = $responseContainer->getData() + ['meter_id' => $meter->id];
MeterDataNew::create($data);
event(new MeterDataEvent($data));
}
} catch (Exception $exception) {
Log::error('Can not connect ' . $meter->id);
Log::error(json_encode($exception));
}
The production enviroment I use is
So I think the alpine is the root cause, since it may missing some function, which make the parallel can not split the address into mutiple request
Check how many requests are created before you add 801
and after - is it N (before) and N+1 (after)?.
Connection refused is quite hard to debug. From my personal experience is that I have seen PLCs crash/hang when there are too many connections/requests sent to them in parallel.
As a tests try sending request in serially - If your cycle time is not too low (ala 1+hz) then you are probably fine sending them not in parallel.
Sidenote: If you can - upgrade your PHP version. PHP 7.4 active support has ended and there are only security fixes released for it. See https://www.php.net/supported-versions.php
From this table
There is 11 request before the 801 as the final All 11 request have lenght = 2 while the 801 has lenght = 4
As I check the ReadRegistersBuilder::newReadHoldingRegisters if it detect request is over 124 registers, it will split into 2 request
But somehow, it doesn't work in docker php:7.4-fpm-alpine3.12
I ll tried upgrade to latest php 8.1 and see how it will go
Request splitting done because maximum amount of registers modbus response can return is limited to 124 sequential register contents . If you address range has huge caps (ala 600, 801) then you end up more then one requests as their addresses are too far apart in memory and can not fit into single response.
@haha8x I have added double support. It is in master branch. You can get it with composer require aldas/modbus-tcp-client:dev-master
. If it works I will tag a release for it.
Thank you The double support work flawlessly
about the Connection refuse which mean the code will keep sending request from address 69 + 124 until it reach 801 which will cause the timeout?
Try sending all those request serially and see if it timeouts
$connection = BinaryStreamConnection::getBuilder()
->setPort(5020)
->setHost('127.0.0.1')
->setConnectTimeoutSec(1.5) // timeout when establishing connection to the server
->setWriteTimeoutSec(0.5) // timeout when writing/sending packet to the server
->setReadTimeoutSec(0.3) // timeout when waiting response from server
->setLogger(new EchoLogger())
->build();
$requests = ReadRegistersBuilder::newReadHoldingRegisters('tcp://127.0.0.1:5022')
->bit(256, 15, 'pump2_feedbackalarm_do')
// ...
//...
->build(); // returns array of N requests
$connection->connect();
foreach ($requests as $request) { // send requests serially
$binaryData = $connection->sendAndReceive($request->getRequest());
$result = $request->parse($binaryData);
print_r($result);
}
Hello aldas
I am trying to read the FC3, HoldingRegister from a Siemens PAC-3200 using this script.
The thing is I am trying to get the value on address 801 from this meter. The manual say, the address 801 have data_type is double, it is quite different from another address, which data type is Float
So this is my code
All the value which have float data type are show up correctly Except the value in double data type
Plz help me