<?php
|
require_once(__DIR__ . '/vendor/autoload.php');
|
|
class ISM7_Connection {
|
protected $conn;
|
protected $buffer;
|
|
protected $logger;
|
protected $dumper;
|
|
public function __construct($host, $port=9091) {
|
$this->logger = Logger::getLogger('ism7.connection');
|
$this->logger->debug('Connecting to '.$host.':'.$port);
|
$this->dumper = new Clue\Hexdump\Hexdump();
|
|
$context = stream_context_create(array(
|
'tls' => array(
|
'verify_peer' => FALSE,
|
),
|
));
|
$this->conn = stream_socket_client('sslv3://'.$host.':'.$port,
|
$errno, $errstr, ini_get("default_socket_timeout"),
|
STREAM_CLIENT_CONNECT, $context);
|
if($this->conn === FALSE) {
|
throw new Exception('stream_socket_client() failed! '.$errstr.' ('.$errno.')');
|
}
|
|
$this->logger->debug('Connected to '.$host.':'.$port);
|
}
|
|
protected function send($data) {
|
$data = (string)$data;
|
$this->logger->debug('Trying to send '.strlen($data).' bytes');
|
$this->logger->trace('Send data dump:'."\n".$this->dumper->dump($data));
|
|
for($written = 0; $written < strlen($data); $written += $length) {
|
$length = fwrite($this->conn, substr($data, $written));
|
if($length === FALSE) {
|
throw new Exception('fwrite() failed!');
|
}
|
}
|
|
$this->logger->debug('Sent '.$written.' bytes');
|
}
|
|
protected function recv($bytes = 1) {
|
$this->logger->debug('Trying to receive '.$bytes.' bytes');
|
|
$bytes = (int)$bytes;
|
while(strlen($this->buffer) < $bytes) {
|
$part = fread($this->conn, $bytes - strlen($this->buffer));
|
if($part === FALSE) {
|
throw new Exception('fread() failed!');
|
}
|
$this->buffer .= $part;
|
}
|
|
$data = substr($this->buffer, 0, $bytes);
|
$this->buffer = substr($this->buffer, $bytes);
|
|
$this->logger->debug('Received '.strlen($data).' bytes');
|
$this->logger->trace('Received data dump:'."\n".$this->dumper->dump($data));
|
|
return $data;
|
}
|
|
public function communicate(ISM7_Request $request) {
|
$this->logger->debug('Sending request: '.get_class($request));
|
|
$this->send($request->getMessage());
|
|
$header = $this->recv(6);
|
$response = ISM7_Response::getByHeader($header);
|
$data = $this->recv($response->getLength());
|
$response->setData($data);
|
|
$this->logger->debug('Got response: '.get_class($response));
|
|
return $response;
|
}
|
|
public function wait() {
|
$header = $this->recv(6);
|
$response = ISM7_Response::getByHeader($header);
|
$data = $this->recv($response->getLength());
|
$response->setData($data);
|
|
$this->logger->debug('Got push request: '.get_class($response));
|
|
return $response;
|
}
|
|
public function close() {
|
fclose($this->conn);
|
}
|
}
|
|
abstract class ISM7_Message {
|
protected static $typeToId = array(
|
'PortalLogonRequest' => 0,
|
'PortalLogonResponse' => 1,
|
'ReadSystemconfigRequest' => 2,
|
'ReadSystemconfigResponse' => 3,
|
'TelegramBundleRequest' => 4,
|
'TelegramBundleResponse' => 5,
|
'DirectLogonRequest' => 8,
|
'DirectLogonResponse' => 9,
|
);
|
protected static $idToType = NULL;
|
|
protected $address;
|
protected $type;
|
|
protected static $logger;
|
|
public function __construct($className, $address = 0) {
|
if(!self::$logger) {
|
self::$logger = Logger::getLogger('ism7.message');
|
}
|
|
$this->address = (int)$address;
|
assert(0 <= $this->address && $this->address <= 0xFFFF);
|
$this->type = substr($className, strpos($className, '_') + 1);
|
assert(isset(self::$typeToId[$this->type]));
|
}
|
|
public function getAddress() {
|
return $this->address;
|
}
|
public function getType() {
|
return $this->type;
|
}
|
public function getTypeId() {
|
return self::getIdByType($this->type);
|
}
|
|
protected static function getIdByType($type) {
|
return self::$typeToId[$type];
|
}
|
protected static function getTypeById($id) {
|
if(self::$idToType === NULL) {
|
self::$idToType = array_flip(self::$typeToId);
|
}
|
return self::$idToType[$id];
|
}
|
}
|
|
abstract class ISM7_Request extends ISM7_Message {
|
public function __construct($className, $address = 0) {
|
parent::__construct($className, $address);
|
}
|
|
function getMessage() {
|
$message = $this->getData();
|
$header = pack('nnn', $this->getAddress(), strlen($message), $this->getTypeId());
|
|
return $header . $message;
|
}
|
|
abstract function getData();
|
}
|
|
class ISM7_Response extends ISM7_Message {
|
private $length;
|
protected $data;
|
|
public function __construct($className, $address = 0) {
|
parent::__construct($className, $address);
|
}
|
|
public function getLength() {
|
return $this->length;
|
}
|
protected function setHeader($header) {
|
$this->address = $header['address'];
|
$this->length = $header['length'];
|
}
|
|
public static function getByHeader($header) {
|
$headerDecoded = unpack('naddress/nlength/ntype', $header);
|
$className = 'ISM7_' . ISM7_Message::getTypeById($headerDecoded['type']);
|
self::$logger->debug('Response seems to be '.$className);
|
|
$response = new $className();
|
$response->setHeader($headerDecoded);
|
return $response;
|
}
|
|
protected function setData($data) {
|
$this->data = new DOMDocument('1.0', 'utf-8');
|
$this->data->loadXML($data);
|
}
|
}
|
|
class ISM7_DirectLogonRequest extends ISM7_Request {
|
private $password;
|
|
public function __construct($password = '1111') {
|
parent::__construct(__CLASS__);
|
$this->password = (string)$password;
|
}
|
|
public function getData() {
|
$xml = new DOMDocument('1.0', 'utf-8');
|
$rootNode = $xml->createElement('direct-logon-request');
|
$xml->appendChild($rootNode);
|
$passNode = $xml->createElement('password');
|
$rootNode->appendChild($passNode);
|
$passText = $xml->createTextNode($this->password);
|
$passNode-> appendChild($passText);
|
return $xml->saveXML();
|
}
|
}
|
|
class ISM7_DirectLogonResponse extends ISM7_Response {
|
protected $sid;
|
protected $installationName;
|
protected $serialNumber;
|
protected $dateTime;
|
protected $softwareVersion;
|
protected $hardwareVersion;
|
protected $moduleType;
|
protected $wlanConnected;
|
|
public function __construct() {
|
parent::__construct(__CLASS__);
|
}
|
|
public function setData($data) {
|
parent::setData($data);
|
$rootNode = $this->data->documentElement;
|
assert($rootNode->tagName == 'direct-logon-response');
|
assert($rootNode->hasAttribute('state'));
|
assert(strtolower($rootNode->getAttribute('state')) == 'ok');
|
assert($rootNode->hasAttribute('sid'));
|
$this->sid = (string)$rootNode->getAttribute('sid');
|
|
foreach($rootNode->childNodes as $childNode) {
|
$text = trim((string)$childNode->textContent);
|
|
self::$logger->trace('DirectLogonResponse::setData(): '.$childNode->nodeName.'='.$text);
|
|
switch($childNode->nodeName) {
|
case 'installationname':
|
$this->installationName = $text;
|
break;
|
case 'serialnumber':
|
$this->serialNumber = $text;
|
break;
|
case 'date-time':
|
$this->dateTime = new DateTime($text);
|
break;
|
case 'ism-softwareversion':
|
$this->softwareVersion = $text;
|
break;
|
case 'ism-hardwareversion':
|
$this->hardwareVersion = $text;
|
break;
|
case 'type':
|
$this->moduleType = $text;
|
break;
|
case 'wlan-connected':
|
$this->wlanConnected = (strtolower($text) == 'true');
|
break;
|
default:
|
self::$logger->info('DirectLogonResponse::setData(): unknown tag '.$childNode->nodeName);
|
}
|
}
|
}
|
|
public function getSid() {
|
return $this->sid;
|
}
|
}
|
|
abstract class ISM7_Element {
|
protected static $logger;
|
|
public function __construct() {
|
if(!self::$logger) {
|
self::$logger = Logger::getLogger('ism7.element');
|
}
|
}
|
}
|
|
class ISM7_Systemconfig_Gateway extends ISM7_Element {
|
protected $gatewayType;
|
protected $softwareNumber;
|
protected $softwareVersion;
|
protected $wlan;
|
protected $g3;
|
|
public function __construct(DOMNode $node) {
|
parent::__construct();
|
|
foreach($node->attributes as $attribute) {
|
$text = $attribute->nodeValue;
|
self::$logger->trace('Systemconfig_Gateway: '.$attribute->nodeName.'='.$text);
|
|
switch($attribute->nodeName) {
|
case 'type':
|
$this->gatewayType = $text;
|
break;
|
case 'softwareNumber':
|
$this->softwareNumber = $text;
|
break;
|
case 'softwareVersion':
|
$this->softwareVersion = $text;
|
break;
|
case 'wlan':
|
$this->wlan = ($text == 'true');
|
break;
|
case 'g3':
|
$this->g3 = ($text == 'true');
|
break;
|
default:
|
self::$logger->info('Systemconfig_Gateway: unknown attribute '.$childNode->nodeName);
|
}
|
}
|
}
|
}
|
|
class ISM7_Systemconfig_Bus extends ISM7_Element {
|
protected $busType;
|
protected $devices = array();
|
|
public function __construct(DOMNode $node) {
|
parent::__construct();
|
|
if($node->hasAttribute('type')) {
|
$this->busType = $node->getAttribute('type');
|
}
|
|
foreach($node->childNodes as $childNode) {
|
self::$logger->trace('Systemconfig_Bus: '.$childNode->nodeName);
|
if($childNode->nodeName == 'busDevices') {
|
foreach($childNode->childNodes as $device) {
|
self::$logger->trace('Systemconfig_Bus: '.$device->nodeName);
|
assert($device->nodeName == 'busDevice');
|
$this->devices[] = new ISM7_Systemconfig_BusDevice($device);
|
}
|
} else {
|
self::$logger->info('Systemconfig_Bus: unknown tag '.$childNode->nodeName);
|
}
|
}
|
}
|
}
|
|
class ISM7_Systemconfig_BusDevice extends ISM7_Element {
|
protected $address;
|
protected $softwareNumber;
|
protected $softwareVersion;
|
protected $configuration;
|
protected $deviceId;
|
|
public function __construct(DOMNode $node) {
|
parent::__construct();
|
|
foreach($node->attributes as $attribute) {
|
$text = $attribute->nodeValue;
|
self::$logger->trace('Systemconfig_BusDevice: '.$attribute->nodeName.'='.$text);
|
|
switch($attribute->nodeName) {
|
case 'ba':
|
$this->address = hexdec($text);
|
break;
|
case 'sv':
|
$this->softwareNumber = hexdec($text);
|
break;
|
case 'sr':
|
$this->softwareVersion = hexdec($text);
|
break;
|
case 'cfg':
|
$this->configuration = hexdec($text);
|
break;
|
case 'did':
|
$this->deviceId = hexdec($text);
|
break;
|
default:
|
self::$logger->info('Systemconfig_BusDevice: unknown attribute '.$attribute->nodeName);
|
}
|
}
|
}
|
}
|
|
class ISM7_ReadSystemconfigRequest extends ISM7_Request {
|
private $sid;
|
|
public function __construct($sid = '1') {
|
parent::__construct(__CLASS__);
|
$this->sid = (string)$sid;
|
}
|
|
public function getData() {
|
$xml = new DOMDocument('1.0', 'utf-8');
|
$rootNode = $xml->createElement('read-systemconfig-request');
|
$rootNode->setAttribute('sid', $this->sid);
|
$xml->appendChild($rootNode);
|
return $xml->saveXML();
|
}
|
}
|
|
class ISM7_ReadSystemconfigResponse extends ISM7_Response {
|
protected $gateway;
|
protected $bus;
|
|
public function __construct() {
|
parent::__construct(__CLASS__);
|
}
|
|
public function setData($data) {
|
parent::setData($data);
|
|
$rootNode = $this->data->documentElement;
|
assert($rootNode->tagName == 'read-systemconfig-response');
|
foreach($rootNode->childNodes as $childNode) {
|
$text = trim((string)$childNode->textContent);
|
|
self::$logger->trace('ReadSystemconfigResponse::setData(): parsing '.$childNode->nodeName);
|
|
switch($childNode->nodeName) {
|
case 'gateway':
|
$this->gateway = new ISM7_Systemconfig_Gateway($childNode);
|
break;
|
case 'busconfig':
|
$this->bus = new ISM7_Systemconfig_Bus($childNode);
|
break;
|
default:
|
self::$logger->info('ReadSystemconfigResponse::setData(): unknown tag '.$childNode->nodeName);
|
}
|
}
|
}
|
}
|
|
interface ISM7_XML_Part {
|
public function getXmlPart($xml = NULL);
|
}
|
interface ISM7_XML_Parent {
|
public function append(ISM7_XML_Part $part);
|
}
|
|
class ISM7_Telegram_InfonumberRead extends ISM7_Element implements ISM7_XML_Part {
|
protected $sequence;
|
protected $address;
|
protected $infonumber;
|
protected $interval;
|
|
function __construct($sequence = NULL, $address = NULL, $infonumber = NULL, $interval = NULL) {
|
parent::__construct();
|
if($sequence === NULL && $address === NULL && $infonumber === NULL && $interval === NULL) {
|
return;
|
}
|
|
if($address instanceof ISM7_Systemconfig_BusDevice) {
|
$address = $address->address;
|
}
|
|
$this->sequence = (string)$sequence;
|
$this->address = (int)$address;
|
$this->infonumber = (int)$infonumber;
|
$this->interval = (int)$interval;
|
}
|
|
public function getXmlPart($xml = NULL) {
|
if(!($xml instanceof DOMDocument)) {
|
$xml = new DOMDocument('1.0', 'utf-8');
|
}
|
|
$rootNode = $xml->createElement('ird');
|
$rootNode->setAttribute('se', $this->sequence);
|
$rootNode->setAttribute('ba', '0x'.dechex($this->address));
|
$rootNode->setAttribute('in', $this->infonumber);
|
|
if($this->interval) {
|
$rootNode->setAttribute('is', $this->interval);
|
}
|
|
return $rootNode;
|
}
|
}
|
|
class ISM7_Telegram_Infonumber extends ISM7_Telegram_InfonumberRead {
|
protected $value = 0;
|
|
public function __construct(DOMNode $node) {
|
parent::__construct();
|
|
assert($node->hasAttribute('st'));
|
assert($node->getAttribute('st') == 'OK');
|
|
assert($node->hasAttribute('dh'));
|
assert($node->hasAttribute('dl'));
|
$this->value = (hexdec($node->getAttribute('dh')) << 8) | hexdec($node->getAttribute('dl'));
|
if($this->value > 127 * 256) {
|
$value -= 256 * 256;
|
}
|
self::$logger->trace('Telegram_Infonumber: d='.$this->value);
|
|
foreach($node->attributes as $attribute) {
|
$text = $attribute->nodeValue;
|
self::$logger->trace('Telegram_Infonumber: '.$attribute->nodeName.'='.$text);
|
|
switch($attribute->nodeName) {
|
case 'se':
|
$this->sequence = $text;
|
break;
|
case 'ba':
|
$this->address = hexdec($text);
|
break;
|
case 'in':
|
$this->infonumber = (int)$text;
|
break;
|
case 'dh':
|
case 'dl':
|
case 'st':
|
break;
|
default:
|
self::$logger->info('Systemconfig_BusDevice: unknown attribute '.$attribute->nodeName);
|
}
|
}
|
}
|
|
public function __get($name) {
|
return $this->$name;
|
}
|
}
|
|
class ISM7_TelegramBundleRequest extends ISM7_Request implements ISM7_XML_Parent {
|
protected $xml;
|
protected $rootNode;
|
|
protected $bundle;
|
protected $gateway;
|
protected $abortOnError;
|
protected $telegramType;
|
|
public function __construct($bundle = 1, $gateway = 1, $abortOnError = FALSE, $telegramType = 'pull') {
|
parent::__construct(__CLASS__);
|
|
assert(in_array($telegramType, array('pull', 'push', 'write', 'remove', 'clear')));
|
|
$this->bundle = (int)$bundle;
|
$this->gateway = (int)$gateway;
|
$this->abortOnError = (bool)$abortOnError;
|
$this->telegramType = (string)$telegramType;
|
|
$this->xml = new DOMDocument('1.0', 'utf-8');
|
$this->rootNode = $this->xml->createElement('tbreq');
|
$this->xml->appendChild($this->rootNode);
|
}
|
|
public function append(ISM7_XML_Part $part) {
|
$node = $part->getXmlPart($this->xml);
|
assert($node->nodeName == 'ird' || $node->nodeName == 'erd');
|
|
$this->rootNode->appendChild($node);
|
}
|
|
public function getData() {
|
$this->rootNode->setAttribute('bn', $this->bundle);
|
$this->rootNode->setAttribute('gw', $this->gateway);
|
$this->rootNode->setAttribute('ae', ($this->abortOnError ? 'true' : 'false'));
|
$this->rootNode->setAttribute('ty', $this->telegramType);
|
|
return $this->xml->saveXML();
|
}
|
}
|
|
class ISM7_TelegramBundleResponse extends ISM7_Response implements IteratorAggregate {
|
protected $bundle;
|
protected $gateway;
|
protected $dateTime;
|
protected $errorMessage;
|
|
protected $infonumbers = array();
|
|
public function __construct() {
|
parent::__construct(__CLASS__);
|
}
|
|
public function setData($data) {
|
parent::setData($data);
|
|
$rootNode = $this->data->documentElement;
|
assert($rootNode->tagName == 'tbres');
|
assert($rootNode->hasAttribute('st'));
|
assert($rootNode->getAttribute('st') == 'OK');
|
|
assert($rootNode->hasAttribute('bn'));
|
assert(ctype_digit($rootNode->getAttribute('bn')));
|
$this->bundle = (int)$rootNode->getAttribute('bn');
|
|
assert($rootNode->hasAttribute('gw'));
|
assert(ctype_digit($rootNode->getAttribute('gw')));
|
$this->gateway = (int)$rootNode->getAttribute('gw');
|
|
if($rootNode->hasAttribute('ts')) {
|
$this->dateTime = new DateTime($rootNode->getAttribute('ts'));
|
self::$logger->trace('TelegramBundleResponse::setData(): timestamp: '.$this->dateTime->format('Y-m-d H:i:s'));
|
}
|
if($rootNode->hasAttribute('emsg') && $rootNode->getAttribute('emsg')) {
|
self::$logger->warn('TelegramBundleResponse::setData(): error message: '.$rootNode->getAttribute('emsg'));
|
}
|
|
foreach($rootNode->childNodes as $childNode) {
|
$text = trim((string)$childNode->textContent);
|
|
self::$logger->trace('TelegramBundleResponse::setData(): parsing '.$childNode->nodeName);
|
|
switch($childNode->nodeName) {
|
case 'irs':
|
$this->infonumbers[] = new ISM7_Telegram_Infonumber($childNode);
|
break;
|
default:
|
self::$logger->info('TelegramBundleResponse::setData(): unknown tag '.$childNode->nodeName);
|
}
|
}
|
}
|
|
public function getIterator() {
|
return new ArrayIterator($this->infonumbers);
|
}
|
}
|