Skip to content

Instantly share code, notes, and snippets.

@andriesss
Created October 26, 2012 22:25
Show Gist options
  • Save andriesss/3961922 to your computer and use it in GitHub Desktop.
Save andriesss/3961922 to your computer and use it in GitHub Desktop.
<?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