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); } }