socket_protocol = $protocol;
$this->host = $host;
}
/**
* __toString
*
* Magic method
*/
function __toString() {
return "
" . $this->status . "
";
}
/**
* connect
*
* Connect the socket
*
* @return bool
*/
private function connect(){
// Create a protocol specific socket
if ($this->socket_protocol == "TCP"){
// TCP socket
$this->sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
} elseif ($this->socket_protocol == "UDP"){
// UDP socket
$this->sock = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
} else {
throw new Exception("Unknown socket protocol, should be 'TCP' or 'UDP'");
}
// Bind the client socket to a specific local port
if (strlen($this->client)>0){
$result = socket_bind($this->sock, $this->client, $this->client_port);
if ($result === false) {
throw new Exception("socket_bind() failed.Reason: ($result)".
socket_strerror(socket_last_error($this->sock)));
} else {
$this->status .= "Bound\n";
}
}
// Socket settings
socket_set_option($this->sock, SOL_SOCKET, SO_SNDTIMEO, array('sec' => 1, 'usec' => 0));
// Connect the socket
$result = @socket_connect($this->sock, $this->host, $this->port);
if ($result === false) {
throw new Exception("socket_connect() failed.Reason: ($result)".
socket_strerror(socket_last_error($this->sock)));
} else {
$this->status .= "Connected\n";
return true;
}
}
/**
* disconnect
*
* Disconnect the socket
*/
private function disconnect(){
socket_close($this->sock);
$this->status .= "Disconnected\n";
}
/**
* send
*
* Send the packet via Modbus
*
* @param string $packet
*/
private function send($packet){
socket_write($this->sock, $packet, strlen($packet));
$this->status .= "Send\n";
}
/**
* rec
*
* Receive data from the socket
*
* @return bool
*/
private function rec(){
socket_set_nonblock($this->sock);
$readsocks[] = $this->sock;
$writesocks = NULL;
$exceptsocks = NULL;
$rec = "";
$lastAccess = time();
while (socket_select($readsocks,
$writesocks,
$exceptsocks,
0,
300000) !== FALSE) {
$this->status .= "Wait data ... \n";
if (in_array($this->sock, $readsocks)) {
while (@socket_recv($this->sock, $rec, 2000, 0)) {
$this->status .= "Data received\n";
return $rec;
}
$lastAccess = time();
} else {
if (time()-$lastAccess >= $this->timeout_sec) {
throw new Exception( "Watchdog time expired [ " .
$this->timeout_sec . " sec]!!! Connection to " .
$this->host . " is not established.");
}
}
$readsocks[] = $this->sock;
}
}
/**
* responseCode
*
* Check the Modbus response code
*
* @param string $packet
* @return bool
*/
private function responseCode($packet){
if((ord($packet[7]) & 0x80) > 0) {
// failure code
$failure_code = ord($packet[8]);
// failure code strings
$failures = array(
0x01 => "ILLEGAL FUNCTION",
0x02 => "ILLEGAL DATA ADDRESS",
0x03 => "ILLEGAL DATA VALUE",
0x04 => "SLAVE DEVICE FAILURE",
0x05 => "ACKNOWLEDGE",
0x06 => "SLAVE DEVICE BUSY",
0x08 => "MEMORY PARITY ERROR",
0x0A => "GATEWAY PATH UNAVAILABLE",
0x0B => "GATEWAY TARGET DEVICE FAILED TO RESPOND");
// get failure string
if(key_exists($failure_code, $failures)) {
$failure_str = $failures[$failure_code];
} else {
$failure_str = "UNDEFINED FAILURE CODE";
}
// exception response
throw new Exception("Modbus response error code: $failure_code ($failure_str)");
} else {
$this->status .= "Modbus response error code: NOERROR\n";
return true;
}
}
/**
* readCoils
*
* Modbus function FC 1(0x01) - Read Coils
*
* Reads {@link $quantity} of Coils (boolean) from reference
* {@link $reference} of a memory of a Modbus device given by
* {@link $unitId}.
*
* @param type $unitId
* @param type $reference
* @param type $quantity
*/
function readCoils($unitId, $reference, $quantity){
$this->status .= "readCoils: START\n";
// connect
$this->connect();
// send FC 1
$packet = $this->readCoilsPacketBuilder($unitId, $reference, $quantity);
$this->status .= $this->printPacket($packet);
$this->send($packet);
// receive response
$rpacket = $this->rec();
$this->status .= $this->printPacket($rpacket);
// parse packet
$receivedData = $this->readCoilsParser($rpacket, $quantity);
// disconnect
$this->disconnect();
$this->status .= "readCoils: DONE\n";
// return
return $receivedData;
}
/**
* fc1
*
* Alias to {@link readCoils} method
*
* @param type $unitId
* @param type $reference
* @param type $quantity
* @return type
*/
function fc1($unitId, $reference, $quantity){
return $this->readCoils($unitId, $reference, $quantity);
}
/**
* readCoilsPacketBuilder
*
* FC1 packet builder - read coils
*
* @param type $unitId
* @param type $reference
* @param type $quantity
* @return type
*/
private function readCoilsPacketBuilder($unitId, $reference, $quantity){
$dataLen = 0;
// build data section
$buffer1 = "";
// build body
$buffer2 = "";
$buffer2 .= iecType::iecBYTE(1); // FC 1 = 1(0x01)
// build body - read section
$buffer2 .= iecType::iecINT($reference); // refnumber = 12288
$buffer2 .= iecType::iecINT($quantity); // quantity
$dataLen += 5;
// build header
$buffer3 = '';
$buffer3 .= iecType::iecINT(rand(0,65000)); // transaction ID
$buffer3 .= iecType::iecINT(0); // protocol ID
$buffer3 .= iecType::iecINT($dataLen + 1); // lenght
$buffer3 .= iecType::iecBYTE($unitId); //unit ID
// return packet string
return $buffer3. $buffer2. $buffer1;
}
/**
* readCoilsParser
*
* FC 1 response parser
*
* @param type $packet
* @param type $quantity
* @return type
*/
private function readCoilsParser($packet, $quantity){
$data = array();
// check Response code
$this->responseCode($packet);
// get data from stream
for($i=0;$i> $i) & 0x01;
// build boolean array
if($v == 0){
$data_bolean_array[] = FALSE;
} else {
$data_bolean_array[] = TRUE;
}
$di++;
}
}
return $data_bolean_array;
}
/**
* readInputDiscretes
*
* Modbus function FC 2(0x02) - Read Input Discretes
*
* Reads {@link $quantity} of Inputs (boolean) from reference
* {@link $reference} of a memory of a Modbus device given by
* {@link $unitId}.
*
* @param type $unitId
* @param type $reference
* @param type $quantity
*/
function readInputDiscretes($unitId, $reference, $quantity){
$this->status .= "readInputDiscretes: START\n";
// connect
$this->connect();
// send FC 2
$packet = $this->readInputDiscretesPacketBuilder($unitId, $reference, $quantity);
$this->status .= $this->printPacket($packet);
$this->send($packet);
// receive response
$rpacket = $this->rec();
$this->status .= $this->printPacket($rpacket);
// parse packet
$receivedData = $this->readInputDiscretesParser($rpacket, $quantity);
// disconnect
$this->disconnect();
$this->status .= "readInputDiscretes: DONE\n";
// return
return $receivedData;
}
/**
* fc2
*
* Alias to {@link readInputDiscretes} method
*
* @param type $unitId
* @param type $reference
* @param type $quantity
* @return type
*/
function fc2($unitId, $reference, $quantity){
return $this->readInputDiscretes($unitId, $reference, $quantity);
}
/**
* readInputDiscretesPacketBuilder
*
* FC2 packet builder - read coils
*
* @param type $unitId
* @param type $reference
* @param type $quantity
* @return type
*/
private function readInputDiscretesPacketBuilder($unitId, $reference, $quantity){
$dataLen = 0;
// build data section
$buffer1 = "";
// build body
$buffer2 = "";
$buffer2 .= iecType::iecBYTE(2); // FC 2 = 2(0x02)
// build body - read section
$buffer2 .= iecType::iecINT($reference); // refnumber = 12288
$buffer2 .= iecType::iecINT($quantity); // quantity
$dataLen += 5;
// build header
$buffer3 = '';
$buffer3 .= iecType::iecINT(rand(0,65000)); // transaction ID
$buffer3 .= iecType::iecINT(0); // protocol ID
$buffer3 .= iecType::iecINT($dataLen + 1); // lenght
$buffer3 .= iecType::iecBYTE($unitId); //unit ID
// return packet string
return $buffer3. $buffer2. $buffer1;
}
/**
* readInputDiscretesParser
*
* FC 2 response parser, alias to FC 1 parser i.e. readCoilsParser.
*
* @param type $packet
* @param type $quantity
* @return type
*/
private function readInputDiscretesParser($packet, $quantity){
return $this->readCoilsParser($packet, $quantity);
}
/**
* readMultipleRegisters
*
* Modbus function FC 3(0x03) - Read Multiple Registers.
*
* This function reads {@link $quantity} of Words (2 bytes) from reference
* {@link $referenceRead} of a memory of a Modbus device given by
* {@link $unitId}.
*
*
* @param int $unitId usually ID of Modbus device
* @param int $reference Reference in the device memory to read data (e.g. in device WAGO 750-841, memory MW0 starts at address 12288).
* @param int $quantity Amounth of the data to be read from device.
* @return false|Array Success flag or array of received data.
*/
function readMultipleRegisters($unitId, $reference, $quantity){
$this->status .= "readMultipleRegisters: START\n";
// connect
$this->connect();
// send FC 3
$packet = $this->readMultipleRegistersPacketBuilder($unitId, $reference, $quantity);
$this->status .= $this->printPacket($packet);
$this->send($packet);
// receive response
$rpacket = $this->rec();
$this->status .= $this->printPacket($rpacket);
// parse packet
$receivedData = $this->readMultipleRegistersParser($rpacket);
// disconnect
$this->disconnect();
$this->status .= "readMultipleRegisters: DONE\n";
// return
return $receivedData;
}
/**
* fc3
*
* Alias to {@link readMultipleRegisters} method.
*
* @param int $unitId
* @param int $reference
* @param int $quantity
* @return false|Array
*/
function fc3($unitId, $reference, $quantity){
return $this->readMultipleRegisters($unitId, $reference, $quantity);
}
/**
* readMultipleRegistersPacketBuilder
*
* Packet FC 3 builder - read multiple registers
*
* @param int $unitId
* @param int $reference
* @param int $quantity
* @return string
*/
private function readMultipleRegistersPacketBuilder($unitId, $reference, $quantity){
$dataLen = 0;
// build data section
$buffer1 = "";
// build body
$buffer2 = "";
$buffer2 .= iecType::iecBYTE(3); // FC 3 = 3(0x03)
// build body - read section
$buffer2 .= iecType::iecINT($reference); // refnumber = 12288
$buffer2 .= iecType::iecINT($quantity); // quantity
$dataLen += 5;
// build header
$buffer3 = '';
$buffer3 .= iecType::iecINT(rand(0,65000)); // transaction ID
$buffer3 .= iecType::iecINT(0); // protocol ID
$buffer3 .= iecType::iecINT($dataLen + 1); // lenght
$buffer3 .= iecType::iecBYTE($unitId); //unit ID
// return packet string
return $buffer3. $buffer2. $buffer1;
}
/**
* readMultipleRegistersParser
*
* FC 3 response parser
*
* @param string $packet
* @return array
*/
private function readMultipleRegistersParser($packet){
$data = array();
// check Response code
$this->responseCode($packet);
// get data
for($i=0;$istatus .= "readMultipleInputRegisters: START\n";
// connect
$this->connect();
// send FC 4
$packet = $this->readMultipleInputRegistersPacketBuilder($unitId, $reference, $quantity);
$this->status .= $this->printPacket($packet);
$this->send($packet);
// receive response
$rpacket = $this->rec();
$this->status .= $this->printPacket($rpacket);
// parse packet
$receivedData = $this->readMultipleInputRegistersParser($rpacket);
// disconnect
$this->disconnect();
$this->status .= "readMultipleInputRegisters: DONE\n";
// return
return $receivedData;
}
/**
* fc4
*
* Alias to {@link readMultipleInputRegisters} method.
*
* @param int $unitId
* @param int $reference
* @param int $quantity
* @return false|Array
*/
function fc4($unitId, $reference, $quantity){
return $this->readMultipleInputRegisters($unitId, $reference, $quantity);
}
/**
* readMultipleInputRegistersPacketBuilder
*
* Packet FC 4 builder - read multiple input registers
*
* @param int $unitId
* @param int $reference
* @param int $quantity
* @return string
*/
private function readMultipleInputRegistersPacketBuilder($unitId, $reference, $quantity){
$dataLen = 0;
// build data section
$buffer1 = "";
// build body
$buffer2 = "";
$buffer2 .= iecType::iecBYTE(4); // FC 4 = 4(0x04)
// build body - read section
$buffer2 .= iecType::iecINT($reference); // refnumber = 12288
$buffer2 .= iecType::iecINT($quantity); // quantity
$dataLen += 5;
// build header
$buffer3 = '';
$buffer3 .= iecType::iecINT(rand(0,65000)); // transaction ID
$buffer3 .= iecType::iecINT(0); // protocol ID
$buffer3 .= iecType::iecINT($dataLen + 1); // lenght
$buffer3 .= iecType::iecBYTE($unitId); // unit ID
// return packet string
return $buffer3. $buffer2. $buffer1;
}
/**
* readMultipleInputRegistersParser
*
* FC 4 response parser
*
* @param string $packet
* @return array
*/
private function readMultipleInputRegistersParser($packet){
$data = array();
// check Response code
$this->responseCode($packet);
// get data
for($i=0;$istatus .= "writeSingleCoil: START\n";
// connect
$this->connect();
// send FC5
$packet = $this->writeSingleCoilPacketBuilder($unitId, $reference, $data);
$this->status .= $this->printPacket($packet);
$this->send($packet);
// receive response
$rpacket = $this->rec();
$this->status .= $this->printPacket($rpacket);
// parse packet
$this->writeSingleCoilParser($rpacket);
// disconnect
$this->disconnect();
$this->status .= "writeSingleCoil: DONE\n";
return true;
}
/**
* fc5
*
* Alias to {@link writeSingleCoil} method
*
* @param int $unitId
* @param int $reference
* @param array $data
* @param array $dataTypes
* @return bool
*/
function fc5($unitId, $reference, $data, $dataTypes){
return $this->writeSingleCoil($unitId, $reference, $data, $dataTypes);
}
/**
* writeSingleCoilPacketBuilder
*
* Packet builder FC5 - WRITE single register
*
* @param int $unitId
* @param int $reference
* @param array $data
* @param array $dataTypes
* @return string
*/
private function writeSingleCoilPacketBuilder($unitId, $reference, $data){
$dataLen = 0;
// build data section
$buffer1 = "";
foreach($data as $key=>$dataitem) {
if($dataitem == TRUE){
$buffer1 = iecType::iecINT(0xFF00);
} else {
$buffer1 = iecType::iecINT(0x0000);
};
};
$dataLen += 2;
// build body
$buffer2 = "";
$buffer2 .= iecType::iecBYTE(5); // FC5 = 5(0x05)
$buffer2 .= iecType::iecINT($reference); // refnumber = 12288
$dataLen += 3;
// build header
$buffer3 = '';
$buffer3 .= iecType::iecINT(rand(0,65000)); // transaction ID
$buffer3 .= iecType::iecINT(0); // protocol ID
$buffer3 .= iecType::iecINT($dataLen + 1); // lenght
$buffer3 .= iecType::iecBYTE($unitId); //unit ID
// return packet string
return $buffer3. $buffer2. $buffer1;
}
/**
* writeSingleCoilParser
*
* FC5 response parser
*
* @param string $packet
* @return bool
*/
private function writeSingleCoilParser($packet){
$this->responseCode($packet);
return true;
}
/**
* writeSingleRegister
*
* Modbus function FC6(0x06) - Write Single Register.
*
* This function writes {@link $data} single word value at {@link $reference} position of
* memory of a Modbus device given by {@link $unitId}.
*
*
* @param int $unitId usually ID of Modbus device
* @param int $reference Reference in the device memory (e.g. in device WAGO 750-841, memory MW0 starts at address 12288)
* @param array $data Array of values to be written.
* @param array $dataTypes Array of types of values to be written. The array should consists of string "INT", "DINT" and "REAL".
* @return bool Success flag
*/
function writeSingleRegister($unitId, $reference, $data, $dataTypes){
$this->status .= "writeSingleRegister: START\n";
// connect
$this->connect();
// send FC6
$packet = $this->writeSingleRegisterPacketBuilder($unitId, $reference, $data, $dataTypes);
$this->status .= $this->printPacket($packet);
$this->send($packet);
// receive response
$rpacket = $this->rec();
$this->status .= $this->printPacket($rpacket);
// parse packet
$this->writeSingleRegisterParser($rpacket);
// disconnect
$this->disconnect();
$this->status .= "writeSingleRegister: DONE\n";
return true;
}
/**
* fc6
*
* Alias to {@link writeSingleRegister} method
*
* @param int $unitId
* @param int $reference
* @param array $data
* @param array $dataTypes
* @return bool
*/
function fc6($unitId, $reference, $data, $dataTypes){
return $this->writeSingleRegister($unitId, $reference, $data, $dataTypes);
}
/**
* writeSingleRegisterPacketBuilder
*
* Packet builder FC6 - WRITE single register
*
* @param int $unitId
* @param int $reference
* @param array $data
* @param array $dataTypes
* @return string
*/
private function writeSingleRegisterPacketBuilder($unitId, $reference, $data, $dataTypes){
$dataLen = 0;
// build data section
$buffer1 = "";
foreach($data as $key=>$dataitem) {
$buffer1 .= iecType::iecINT($dataitem); // register values x
$dataLen += 2;
break;
}
// build body
$buffer2 = "";
$buffer2 .= iecType::iecBYTE(6); // FC6 = 6(0x06)
$buffer2 .= iecType::iecINT($reference); // refnumber = 12288
$dataLen += 3;
// build header
$buffer3 = '';
$buffer3 .= iecType::iecINT(rand(0,65000)); // transaction ID
$buffer3 .= iecType::iecINT(0); // protocol ID
$buffer3 .= iecType::iecINT($dataLen + 1); // lenght
$buffer3 .= iecType::iecBYTE($unitId); //unit ID
// return packet string
return $buffer3. $buffer2. $buffer1;
}
/**
* writeSingleRegisterParser
*
* FC6 response parser
*
* @param string $packet
* @return bool
*/
private function writeSingleRegisterParser($packet){
$this->responseCode($packet);
return true;
}
/**
* writeMultipleCoils
*
* Modbus function FC15(0x0F) - Write Multiple Coils
*
* This function writes {@link $data} array at {@link $reference} position of
* memory of a Modbus device given by {@link $unitId}.
*
* @param type $unitId
* @param type $reference
* @param type $data
* @return type
*/
function writeMultipleCoils($unitId, $reference, $data){
$this->status .= "writeMultipleCoils: START\n";
// connect
$this->connect();
// send FC15
$packet = $this->writeMultipleCoilsPacketBuilder($unitId, $reference, $data);
$this->status .= $this->printPacket($packet);
$this->send($packet);
// receive response
$rpacket = $this->rec();
$this->status .= $this->printPacket($rpacket);
// parse packet
$this->writeMultipleCoilsParser($rpacket);
// disconnect
$this->disconnect();
$this->status .= "writeMultipleCoils: DONE\n";
return true;
}
/**
* fc15
*
* Alias to {@link writeMultipleCoils} method
*
* @param int $unitId
* @param int $reference
* @param array $data
* @return bool
*/
function fc15($unitId, $reference, $data){
return $this->writeMultipleCoils($unitId, $reference, $data);
}
/**
* writeMultipleCoilsPacketBuilder
*
* Packet builder FC15 - Write multiple coils
*
* @param int $unitId
* @param int $reference
* @param array $data
* @return string
*/
private function writeMultipleCoilsPacketBuilder($unitId, $reference, $data){
$dataLen = 0;
// build bool stream to the WORD array
$data_word_stream = array();
$data_word = 0;
$shift = 0;
for($i=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;
}
// 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
$dataLen += 6;
// build header
$buffer3 = '';
$buffer3 .= iecType::iecINT(rand(0,65000)); // transaction ID
$buffer3 .= iecType::iecINT(0); // protocol ID
$buffer3 .= iecType::iecINT($dataLen + 1); // lenght
$buffer3 .= iecType::iecBYTE($unitId); // unit ID
// return packet string
return $buffer3. $buffer2. $buffer1;
}
/**
* writeMultipleCoilsParser
*
* FC15 response parser
*
* @param string $packet
* @return bool
*/
private function writeMultipleCoilsParser($packet){
$this->responseCode($packet);
return true;
}
/**
* writeMultipleRegister
*
* Modbus function FC16(0x10) - Write Multiple Register.
*
* This function writes {@link $data} array at {@link $reference} position of
* memory of a Modbus device given by {@link $unitId}.
*
*
* @param int $unitId usually ID of Modbus device
* @param int $reference Reference in the device memory (e.g. in device WAGO 750-841, memory MW0 starts at address 12288)
* @param array $data Array of values to be written.
* @param array $dataTypes Array of types of values to be written. The array should consists of string "INT", "DINT" and "REAL".
* @return bool Success flag
*/
function writeMultipleRegister($unitId, $reference, $data, $dataTypes){
$this->status .= "writeMultipleRegister: START\n";
// connect
$this->connect();
// send FC16
$packet = $this->writeMultipleRegisterPacketBuilder($unitId, $reference, $data, $dataTypes);
$this->status .= $this->printPacket($packet);
$this->send($packet);
// receive response
$rpacket = $this->rec();
$this->status .= $this->printPacket($rpacket);
// parse packet
$this->writeMultipleRegisterParser($rpacket);
// disconnect
$this->disconnect();
$this->status .= "writeMultipleRegister: DONE\n";
return true;
}
/**
* fc16
*
* Alias to {@link writeMultipleRegister} method
*
* @param int $unitId
* @param int $reference
* @param array $data
* @param array $dataTypes
* @return bool
*/
function fc16($unitId, $reference, $data, $dataTypes){
return $this->writeMultipleRegister($unitId, $reference, $data, $dataTypes);
}
/**
* writeMultipleRegisterPacketBuilder
*
* Packet builder FC16 - WRITE multiple register
* e.g.: 4dd90000000d0010300000030603e807d00bb8
*
* @param int $unitId
* @param int $reference
* @param array $data
* @param array $dataTypes
* @return string
*/
private function writeMultipleRegisterPacketBuilder($unitId, $reference, $data, $dataTypes){
$dataLen = 0;
// build data section
$buffer1 = "";
foreach($data as $key=>$dataitem) {
if($dataTypes[$key]=="INT"){
$buffer1 .= iecType::iecINT($dataitem); // register values x
$dataLen += 2;
}
elseif($dataTypes[$key]=="DINT"){
$buffer1 .= iecType::iecDINT($dataitem, $this->endianness); // register values x
$dataLen += 4;
}
elseif($dataTypes[$key]=="REAL") {
$buffer1 .= iecType::iecREAL($dataitem, $this->endianness); // register values x
$dataLen += 4;
}
else{
$buffer1 .= iecType::iecINT($dataitem); // register values x
$dataLen += 2;
}
}
// build body
$buffer2 = "";
$buffer2 .= iecType::iecBYTE(16); // FC 16 = 16(0x10)
$buffer2 .= iecType::iecINT($reference); // refnumber = 12288
$buffer2 .= iecType::iecINT($dataLen/2); // word count
$buffer2 .= iecType::iecBYTE($dataLen); // byte count
$dataLen += 6;
// build header
$buffer3 = '';
$buffer3 .= iecType::iecINT(rand(0,65000)); // transaction ID
$buffer3 .= iecType::iecINT(0); // protocol ID
$buffer3 .= iecType::iecINT($dataLen + 1); // lenght
$buffer3 .= iecType::iecBYTE($unitId); //unit ID
// return packet string
return $buffer3. $buffer2. $buffer1;
}
/**
* writeMultipleRegisterParser
*
* FC16 response parser
*
* @param string $packet
* @return bool
*/
private function writeMultipleRegisterParser($packet){
$this->responseCode($packet);
return true;
}
/**
* maskWriteRegister
*
* Modbus function FC22(0x16) - Mask Write Register.
*
* This function alter single bit(s) at {@link $reference} position of
* memory of a Modbus device given by {@link $unitId}.
*
* Result = (Current Contents AND And_Mask) OR (Or_Mask AND (NOT And_Mask))
*
* @param int $unitId usually ID of Modbus device
* @param int $reference Reference in the device memory (e.g. in device WAGO 750-841, memory MW0 starts at address 12288)
* @param int $andMask
* @param int $orMask
* @return bool Success flag
*/
function maskWriteRegister($unitId, $reference, $andMask, $orMask){
$this->status .= "maskWriteRegister: START\n";
// connect
$this->connect();
// send FC22
$packet = $this->maskWriteRegisterPacketBuilder($unitId, $reference, $andMask, $orMask);
$this->status .= $this->printPacket($packet);
$this->send($packet);
// receive response
$rpacket = $this->rec();
$this->status .= $this->printPacket($rpacket);
// parse packet
$this->maskWriteRegisterParser($rpacket);
// disconnect
$this->disconnect();
$this->status .= "maskWriteRegister: DONE\n";
return true;
}
/**
* fc22
*
* Alias to {@link maskWriteRegister} method
*
* @param int $unitId
* @param int $reference
* @param int $andMask
* @param int $orMask
* @return bool
*/
function fc22($unitId, $reference, $andMask, $orMask){
return $this->maskWriteRegister($unitId, $reference, $andMask, $orMask);
}
/**
* maskWriteRegisterPacketBuilder
*
* Packet builder FC22 - MASK WRITE register
*
* @param int $unitId
* @param int $reference
* @param int $andMask
* @param int $orMask
* @return string
*/
private function maskWriteRegisterPacketBuilder($unitId, $reference, $andMask, $orMask){
$dataLen = 0;
// build data section
$buffer1 = "";
// build body
$buffer2 = "";
$buffer2 .= iecType::iecBYTE(22); // FC 22 = 22(0x16)
$buffer2 .= iecType::iecINT($reference); // refnumber = 12288
$buffer2 .= iecType::iecINT($andMask); // AND mask
$buffer2 .= iecType::iecINT($orMask); // OR mask
$dataLen += 7;
// build header
$buffer3 = '';
$buffer3 .= iecType::iecINT(rand(0,65000)); // transaction ID
$buffer3 .= iecType::iecINT(0); // protocol ID
$buffer3 .= iecType::iecINT($dataLen + 1); // lenght
$buffer3 .= iecType::iecBYTE($unitId); //unit ID
// return packet string
return $buffer3. $buffer2. $buffer1;
}
/**
* maskWriteRegisterParser
*
* FC22 response parser
*
* @param string $packet
* @return bool
*/
private function maskWriteRegisterParser($packet){
$this->responseCode($packet);
return true;
}
/**
* readWriteRegisters
*
* Modbus function FC23(0x17) - Read Write Registers.
*
* This function writes {@link $data} array at reference {@link $referenceWrite}
* position of memory of a Modbus device given by {@link $unitId}. Simultanously,
* it returns {@link $quantity} of Words (2 bytes) from reference {@link $referenceRead}.
*
*
* @param int $unitId usually ID of Modbus device
* @param int $referenceRead Reference in the device memory to read data (e.g. in device WAGO 750-841, memory MW0 starts at address 12288).
* @param int $quantity Amounth of the data to be read from device.
* @param int $referenceWrite Reference in the device memory to write data.
* @param array $data Array of values to be written.
* @param array $dataTypes Array of types of values to be written. The array should consists of string "INT", "DINT" and "REAL".
* @return false|Array Success flag or array of data.
*/
function readWriteRegisters($unitId, $referenceRead, $quantity, $referenceWrite, $data, $dataTypes){
$this->status .= "readWriteRegisters: START\n";
// connect
$this->connect();
// send FC23
$packet = $this->readWriteRegistersPacketBuilder($unitId, $referenceRead, $quantity, $referenceWrite, $data, $dataTypes);
$this->status .= $this->printPacket($packet);
$this->send($packet);
// receive response
$rpacket = $this->rec();
$this->status .= $this->printPacket($rpacket);
// parse packet
$receivedData = $this->readWriteRegistersParser($rpacket);
// disconnect
$this->disconnect();
$this->status .= "writeMultipleRegister: DONE\n";
// return
return $receivedData;
}
/**
* fc23
*
* Alias to {@link readWriteRegisters} method.
*
* @param int $unitId
* @param int $referenceRead
* @param int $quantity
* @param int $referenceWrite
* @param array $data
* @param array $dataTypes
* @return false|Array
*/
function fc23($unitId, $referenceRead, $quantity, $referenceWrite, $data, $dataTypes){
return $this->readWriteRegisters($unitId, $referenceRead, $quantity, $referenceWrite, $data, $dataTypes);
}
/**
* readWriteRegistersPacketBuilder
*
* Packet FC23 builder - READ WRITE registers
*
*
* @param int $unitId
* @param int $referenceRead
* @param int $quantity
* @param int $referenceWrite
* @param array $data
* @param array $dataTypes
* @return string
*/
private function readWriteRegistersPacketBuilder($unitId, $referenceRead, $quantity, $referenceWrite, $data, $dataTypes){
$dataLen = 0;
// build data section
$buffer1 = "";
foreach($data as $key => $dataitem) {
if($dataTypes[$key]=="INT"){
$buffer1 .= iecType::iecINT($dataitem); // register values x
$dataLen += 2;
}
elseif($dataTypes[$key]=="DINT"){
$buffer1 .= iecType::iecDINT($dataitem, $this->endianness); // register values x
$dataLen += 4;
}
elseif($dataTypes[$key]=="REAL") {
$buffer1 .= iecType::iecREAL($dataitem, $this->endianness); // register values x
$dataLen += 4;
}
else{
$buffer1 .= iecType::iecINT($dataitem); // register values x
$dataLen += 2;
}
}
// build body
$buffer2 = "";
$buffer2 .= iecType::iecBYTE(23); // FC 23 = 23(0x17)
// build body - read section
$buffer2 .= iecType::iecINT($referenceRead); // refnumber = 12288
$buffer2 .= iecType::iecINT($quantity); // quantity
// build body - write section
$buffer2 .= iecType::iecINT($referenceWrite); // refnumber = 12288
$buffer2 .= iecType::iecINT($dataLen/2); // word count
$buffer2 .= iecType::iecBYTE($dataLen); // byte count
$dataLen += 10;
// build header
$buffer3 = '';
$buffer3 .= iecType::iecINT(rand(0,65000)); // transaction ID
$buffer3 .= iecType::iecINT(0); // protocol ID
$buffer3 .= iecType::iecINT($dataLen + 1); // lenght
$buffer3 .= iecType::iecBYTE($unitId); //unit ID
// return packet string
return $buffer3. $buffer2. $buffer1;
}
/**
* readWriteRegistersParser
*
* FC23 response parser
*
* @param string $packet
* @return array
*/
private function readWriteRegistersParser($packet){
$data = array();
// if not exception
if(!$this->responseCode($packet))
return false;
// get data
for($i=0;$i> 4) & 0x0F);
$l = dechex($value & 0x0F);
return "$h$l";
}
/**
* printPacket
*
* Print a packet in the hex form
*
* @param string $packet
* @return string
*/
private function printPacket($packet){
$str = "";
$str .= "Packet: ";
for($i=0;$ibyte2hex(ord($packet[$i]));
}
$str .= "\n";
return $str;
}
}