Created
October 26, 2012 22:25
-
-
Save andriesss/3961922 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
$stream = new Modbus('127.0.0.1'); | |
$stream->connect(); | |
//var_dump($stream->writeSingleRegister(10, 10, 1)); | |
var_dump($stream->writeSingleCoil(1, 0)); | |
var_dump($stream->readCoil(1, 1));die; | |
**/ | |
class Modbus | |
{ | |
/** | |
* @var string | |
*/ | |
protected $host; | |
/** | |
* @var int | |
*/ | |
protected $port = 502; | |
/** | |
* @var int | |
*/ | |
protected $timeout = 30; | |
/** | |
* @var resource | |
*/ | |
private $socket; | |
/** | |
* Current transaction identifier | |
* A value between 0 and 255^2 | |
* | |
* @var int | |
*/ | |
private $transactionIdentifier = 0; | |
/** | |
* @param string $host | |
* @param int $port | |
* @param int $timeout | |
*/ | |
public function __construct($host, $port = 502, $timeout = 30) | |
{ | |
$this->setHost($host); | |
$this->setPort($port); | |
$this->setTimeout($timeout); | |
} | |
public function connect() | |
{ | |
$start = time(); | |
do { | |
$this->socket = stream_socket_client($this->getConnectionString(), $errno, $errstr, $this->timeout); | |
if (false === $this->socket) { | |
throw new \RuntimeException($errstr, $errno); | |
} | |
if (!$this->socket) { | |
sleep($this->timeout); | |
} | |
} while (!$this->socket && (time() - $start) < $this->timeout); | |
if ($this->socket) { | |
stream_set_timeout($this->socket, 1); | |
stream_set_blocking($this->socket, 1); | |
} else { | |
throw new \RuntimeException('Could not connect to server.'); | |
} | |
return $this; | |
} | |
/** | |
* Write value to a single register of a PLC | |
* | |
* @param int $register | |
* @param int $value | |
* @param int $slaveAddress | |
* @return string | |
*/ | |
public function writeSingleRegister($register, $value, $slaveAddress = 255) | |
{ | |
$this->write( | |
$this->buildFrame(pack('CCnn', $slaveAddress, 0x06, $register, $value)) | |
); | |
return $this->read(); | |
} | |
/** | |
* @param int $register | |
* @param int $slaveAddress | |
* @return string | |
*/ | |
public function readInputRegister($register, $slaveAddress = 255) | |
{ | |
$this->write( | |
$this->buildFrame(pack('CCnn', $slaveAddress, 0x03, $register, $register -1)) | |
); | |
return $this->read(); | |
} | |
/** | |
* Write value to a single register of a PLC | |
* | |
* @param int $coil | |
* @param int $value | |
* @param int $slaveAddress | |
* @return string | |
*/ | |
public function writeSingleCoil($coil, $value, $slaveAddress = 255) | |
{ | |
$this->write( | |
$this->buildFrame(pack('CCnn', $slaveAddress, 0x05, $coil, $value)) | |
); | |
return $this->read(); | |
} | |
/** | |
* @param int $coil | |
* @param int $slaveAddress | |
* @return string | |
*/ | |
public function readCoil($coil, $slaveAddress = 255) | |
{ | |
$this->write( | |
$this->buildFrame(pack('CCn', $slaveAddress, 0x01, $coil)) | |
); | |
return $this->read(); | |
} | |
protected function buildFrame($body) | |
{ | |
return pack('nnn', $this->generateTransactionIdentifier(), 0x00, strlen($body)) . $body; | |
} | |
/** | |
* Generates a transaction identifier | |
*/ | |
protected function generateTransactionIdentifier() | |
{ | |
// if transaction identifier exceeds 2 byte size (255^2), start over | |
if ($this->transactionIdentifier === 0xffff) { | |
$this->transactionIdentifier = 0; | |
} else { | |
$this->transactionIdentifier++; | |
} | |
return $this->transactionIdentifier; | |
} | |
/** | |
* Writes a message to the modbus socket 7 | |
* @param string $message in binary | |
* @return Modbus | |
*/ | |
protected function write($message) | |
{ | |
fwrite($this->socket, $message); | |
return $this; | |
} | |
/** | |
* @return string | |
*/ | |
protected function read() | |
{ | |
$transaction = bin2hex(fread($this->socket, 2)); | |
$protocol = bin2hex(fread($this->socket, 2)); | |
$length = (int) bin2hex(fread($this->socket, 2)); | |
$data = fread($this->socket, $length); | |
$unit = bin2hex(substr($data, 0, 1)); | |
$function = bin2hex(substr($data, 1, 1)); | |
$data = bin2hex(substr($data, 3)); | |
return array( | |
'transaction' => $transaction, | |
'protocol' => $protocol, | |
'length' => $length, | |
'unit' => $unit, | |
'function' => $function, | |
'data' => $data, | |
); | |
} | |
/** | |
* @param int $timeout - socket timeout in seconds | |
* @return Modbus | |
* @throws \InvalidArgumentException | |
*/ | |
protected function setTimeout($timeout) | |
{ | |
if (!is_integer($timeout)) { | |
throw new \InvalidArgumentException('Invalid timeout provided. Expected integer, got ' . gettype($timeout)); | |
} | |
if ($timeout < 1) { | |
throw new \InvalidArgumentException('Invalid timeout provided. Should be minimum 1 second'); | |
} | |
$this->timeout = $timeout; | |
return $this; | |
} | |
/** | |
* @param string $host - hostname or ip address of modbus master | |
* @return Modbus | |
* @throws \InvalidArgumentException | |
*/ | |
protected function setHost($host) | |
{ | |
if (!is_string($host)) { | |
throw new \InvalidArgumentException('Invalid host provided. Expected string, got ' . gettype($host)); | |
} | |
$this->host = $host; | |
return $this; | |
} | |
/** | |
* @param int $port - tcp port number to connect on | |
* @return Modbus | |
* @throws \InvalidArgumentException | |
*/ | |
protected function setPort($port) | |
{ | |
if (!is_integer($port)) { | |
throw new \InvalidArgumentException('Invalid port provided. Expected integer, got ' . gettype($port)); | |
} | |
$this->port = $port; | |
return $this; | |
} | |
/** | |
* @return string | |
*/ | |
protected function getConnectionString() | |
{ | |
return 'tcp://' . $this->host . ':' . $this->port; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment