host = $host; } /** * __toString * * Magic method */ function __toString() { return "
" . $this->status . "
"; } /** * connect * * Connect the socket * * @return bool */ private function connect(){ // UDP socket $this->sock = socket_create(AF_INET, SOCK_DGRAM, SOL_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"; } } // connect $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(($packet[7] & 0x80) > 0) { throw new Exception("Modbus response error code:" . ord($packet[8])); } 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 $referenceRead} 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 3 $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 readMultipleCoils} 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 $quantity) continue; // get boolean value $v = ($value >> $i) & 0x01; // build boolean array if($v == 0){ $data_bolean_array[] = FALSE; } else { $data_bolean_array[] = TRUE; } $di++; } } return $data_bolean_array; } /** * 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 .= "writeMultipleCoils: START\n"; // connect $this->connect(); // send FC16 $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; } /** * 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; } } ?>