|
|
|
@ -17,6 +17,9 @@ namespace PHPModbus\Packet; |
|
|
|
|
|
|
|
|
|
use PHPModbus\IecType; |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Builds/parses packet in Modbus TCP/IP format (http://www.simplymodbus.ca/TCP.htm) |
|
|
|
|
*/ |
|
|
|
|
class WriteMultipleCoilsPacket |
|
|
|
|
{ |
|
|
|
|
/** |
|
|
|
@ -30,49 +33,25 @@ class WriteMultipleCoilsPacket |
|
|
|
|
public static function build($unitId, $reference, array $data) |
|
|
|
|
{ |
|
|
|
|
$dataLen = 0; |
|
|
|
|
// build bool stream to the WORD array |
|
|
|
|
$data_word_stream = array(); |
|
|
|
|
$data_word = 0; |
|
|
|
|
$shift = 0; |
|
|
|
|
for ($i = 0, $len = count($data); $i < $len; $i++) { |
|
|
|
|
if ((($i % 8) === 0) && ($i > 0)) { |
|
|
|
|
$data_word_stream[] = $data_word; |
|
|
|
|
$shift = 0; |
|
|
|
|
$data_word = 0; |
|
|
|
|
$data_word |= (0x01 && $data[$i]) << $shift; |
|
|
|
|
$shift++; |
|
|
|
|
} else { |
|
|
|
|
$data_word |= (0x01 && $data[$i]) << $shift; |
|
|
|
|
$shift++; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
$data_word_stream[] = $data_word; |
|
|
|
|
// show binary stream to status string |
|
|
|
|
// foreach ($data_word_stream as $d) { |
|
|
|
|
// $this->status .= sprintf("byte=b%08b\n", $d); |
|
|
|
|
// } |
|
|
|
|
// build data section |
|
|
|
|
$buffer1 = ''; |
|
|
|
|
foreach ($data_word_stream as $key => $dataitem) { |
|
|
|
|
$buffer1 .= IecType::iecBYTE($dataitem); // register values x |
|
|
|
|
$dataLen += 1; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
list($pduData, $wordCount) = self::getDataConvertedToIecWords($data); |
|
|
|
|
$dataLen += $wordCount; |
|
|
|
|
|
|
|
|
|
// build body |
|
|
|
|
$buffer2 = ''; |
|
|
|
|
$buffer2 .= IecType::iecBYTE(15); // FC 15 = 15(0x0f) |
|
|
|
|
$buffer2 .= IecType::iecINT($reference); // refnumber = 12288 |
|
|
|
|
$buffer2 .= IecType::iecINT(count($data)); // bit count |
|
|
|
|
$buffer2 .= IecType::iecBYTE((count($data) + 7) / 8); // byte count |
|
|
|
|
$pduHeader = ''; |
|
|
|
|
$pduHeader .= IecType::iecBYTE(15); // FC 15 = 15(0x0f) |
|
|
|
|
$pduHeader .= IecType::iecINT($reference); // refnumber = 12288 |
|
|
|
|
$pduHeader .= IecType::iecINT(count($data)); // bit count |
|
|
|
|
$pduHeader .= IecType::iecBYTE((count($data) + 7) / 8); // byte count |
|
|
|
|
$dataLen += 6; |
|
|
|
|
// build header |
|
|
|
|
$buffer3 = ''; |
|
|
|
|
$buffer3 .= IecType::iecINT(mt_rand(0, 65000)); // transaction ID |
|
|
|
|
$buffer3 .= IecType::iecINT(0); // protocol ID |
|
|
|
|
$buffer3 .= IecType::iecINT($dataLen + 1); // length |
|
|
|
|
$buffer3 .= IecType::iecBYTE($unitId); // unit ID |
|
|
|
|
|
|
|
|
|
// return packet string |
|
|
|
|
return $buffer3 . $buffer2 . $buffer1; |
|
|
|
|
$mbapHeader = ''; |
|
|
|
|
$mbapHeader .= IecType::iecINT(mt_rand(0, 65000)); // transaction ID |
|
|
|
|
$mbapHeader .= IecType::iecINT(0); // protocol ID |
|
|
|
|
$mbapHeader .= IecType::iecINT($dataLen + 1); // length |
|
|
|
|
$mbapHeader .= IecType::iecBYTE($unitId); // unit ID |
|
|
|
|
|
|
|
|
|
return $mbapHeader . $pduHeader . $pduData; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
@ -86,4 +65,32 @@ class WriteMultipleCoilsPacket |
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* @param array $data |
|
|
|
|
* @return array |
|
|
|
|
*/ |
|
|
|
|
private static function getDataConvertedToIecWords(array $data) |
|
|
|
|
{ |
|
|
|
|
$data_word_stream = array(); |
|
|
|
|
$data_word = 0; |
|
|
|
|
$shift = 0; |
|
|
|
|
for ($i = 0, $len = count($data); $i < $len; $i++) { |
|
|
|
|
if ((($i % 8) === 0) && ($i > 0)) { |
|
|
|
|
//shift to next word |
|
|
|
|
$data_word_stream[] = $data_word; |
|
|
|
|
$shift = 0; |
|
|
|
|
$data_word = 0; |
|
|
|
|
} |
|
|
|
|
$data_word |= (0x01 && $data[$i]) << $shift; |
|
|
|
|
$shift++; |
|
|
|
|
} |
|
|
|
|
$data_word_stream[] = $data_word; |
|
|
|
|
|
|
|
|
|
$pduData = ''; |
|
|
|
|
foreach ($data_word_stream as $key => $dataitem) { |
|
|
|
|
$pduData .= IecType::iecBYTE($dataitem); |
|
|
|
|
} |
|
|
|
|
return array($pduData, count($data_word_stream)); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |