Ankündigung

Einklappen
Keine Ankündigung bisher.

PHP Skript zur KNX IP Tunneling Verbindung

Einklappen
X
 
  • Filter
  • Zeit
  • Anzeigen
Alles löschen
neue Beiträge

    PHP Skript zur KNX IP Tunneling Verbindung

    Hallo,

    ich stelle diesen kleinen PHP Code bereit für die, die Interesse haben, mehr mit PHP und KNX zu erstellen. Er baut eine IP Tunneling Verbindung auf, sendet Daten für DPT 1, 5 oder 9 und trennt sich danach wieder.

    Das hier hat auch nichts mit dem Wrapper von mir zu tun, es ist komplett eigenständig.



    PHP-Code:
    <?php
    header
    ('Content-Type: text/html; charset=utf-8');

    class 
    KNXTunnel {
        private 
    string $ip;
        private 
    int $port;
        private 
    $socket null;
        private 
    int $channelId 0;
        private 
    int $sequenceNumber 0;

        public function 
    __construct(string $ipint $port 3671) {
            
    $this->ip $ip;
            
    $this->port $port;
        }

        private function 
    log(string $msg): void {
            echo 
    $msg "<br>\n";
            if (
    ob_get_level() > 0) { @ob_flush(); }
            @
    flush();
        }

        private function 
    getLocalIp(): string {
            
    $sock socket_create(AF_INETSOCK_DGRAMSOL_UDP);
            
    socket_connect($sock$this->ip$this->port);
            
    socket_getsockname($sock$addr);
            
    socket_close($sock);
            return 
    $addr;
        }

        private function 
    statusText(int $status): string {
            return match (
    $status) {
                
    0x00 => 'E_NO_ERROR',
                
    0x22 => 'E_CONNECTION_TYPE (Verbindungstyp nicht unterstützt)',
                
    0x23 => 'E_CONNECTION_OPTION (Option nicht unterstützt)',
                
    0x24 => 'E_NO_MORE_CONNECTIONS (max. Anzahl Tunnel-Verbindungen erreicht)',
                
    0x26 => 'E_DATA_CONNECTION',
                
    0x27 => 'E_KNX_CONNECTION',
                
    0x29 => 'E_NO_MORE_UNIQUE_CONNECTIONS',
                default => 
    'unbekannt (0x' sprintf('%02X'$status) . ')',
            };
        }

        public function 
    connect(): bool {
            
    $this->socket socket_create(AF_INETSOCK_DGRAMSOL_UDP);
            if (!
    $this->socket) {
                
    $this->log("❌ Socket konnte nicht erstellt werden : " socket_strerror(socket_last_error()));
                return 
    false;
            }

            
    socket_set_option($this->socketSOL_SOCKETSO_RCVTIMEO, ['sec' => 3'usec' => 0]);

            
    $myPcIp $this->getLocalIp();
            
    $this->log("ℹ️ Lokale IP : {$myPcIp}");

            if (!@
    socket_bind($this->socket$myPcIp0)) {
                
    $this->log("❌ Fehler beim Binden an PC IP {$myPcIp} : " socket_strerror(socket_last_error($this->socket)));
                return 
    false;
            }

            
    $localPort 0;
            
    socket_getsockname($this->socket$myPcIp$localPort);
            
    $ipParts array_map('intval'explode('.'$myPcIp));

            
    $hpaiControl pack('C*'0x080x01$ipParts[0], $ipParts[1], $ipParts[2], $ipParts[3]) . pack('n'$localPort);

            
    $hpaiData pack('C*'0x080x01$ipParts[0], $ipParts[1], $ipParts[2], $ipParts[3]) . pack('n'$localPort);

            
    $header "\x06\x10\x02\x05\x00\x1a";

            
    $cri "\x04\x04\x02\x00";

            
    $connectRequest $header $hpaiControl $hpaiData $cri;

            
    socket_sendto($this->socket$connectRequeststrlen($connectRequest), 0$this->ip$this->port);
            
    $this->log("📤 CONNECT_REQUEST gesendet an {$this->ip}:{$this->port}");

            
    $buffer '';
            
    $from '';
            
    $fromPort 0;
            
    $bytes = @socket_recvfrom($this->socket$buffer10240$from$fromPort);

            if (
    $bytes === false) {
                
    $this->log("❌ recvfrom Fehler : " socket_strerror(socket_last_error($this->socket)));
                return 
    false;
            }

            if (
    $bytes 8) {
                
    $this->log("❌ Zu kurze Antwort empfangen ({$bytes} Bytes).");
                return 
    false;
            }

            
    $this->log("📥 Rohdaten empfangen von {$from}:{$fromPort} (" $bytes " Bytes): " bin2hex($buffer));

            
    $serviceType = (ord($buffer[2]) << 8) | ord($buffer[3]);

            if (
    $serviceType !== 0x0206) {
                
    $this->log("❌ Unerwarteter Service Type : 0x" sprintf('%04X'$serviceType) . " (erwartet : 0x0206 CONNECT_RESPONSE)");
                return 
    false;
            }

            
    $status ord($buffer[7]);
            if (
    $status !== 0x00) {
                
    $this->log("❌ IP Gateway abbruch, Status : 0x" sprintf('%02X'$status) . " (" $this->statusText($status) . ")");
                return 
    false;
            }

            
    $this->channelId ord($buffer[6]);
            
    $this->sequenceNumber 0;
            
    $this->log("✅ IP Gateway Tunnel erfolgreich aufgebaut! (Channel ID : {$this->channelId})");
            return 
    true;
        }

        private function 
    parseGroupAddress(string $ga): string {
            
    $parts explode('/'$ga);
            if (
    count($parts) === 3) {
                
    $main = (int)$parts[0] & 0x1F;
                
    $middle = (int)$parts[1] & 0x07;
                
    $sub = (int)$parts[2] & 0xFF;
                
    $val = ($main << 11) | ($middle << 8) | $sub;
                return 
    pack('n'$val);
            }
            return 
    "\x00\x00";
        }

        public function 
    encodeDPT(string $dptType$value): string {
            switch (
    strtoupper($dptType)) {
                case 
    'DPT1':
                    return 
    pack('C'0x80 | ($value 0));

                case 
    'DPT5':
                    
    $val max(0min(255, (int)$value));
                    return 
    pack('CC'0x80$val);

                case 
    'DPT9':
                    
    $v = (float)$value;
                    
    $sign = ($v 0) ? 0x8000 0x0000;
                    
    $v abs($v);
                    
    $mantissa = (int)round($v 100);
                    
    $exponent 0;
                    while (
    $mantissa 2047) {
                        
    $mantissa >>= 1;
                        
    $exponent++;
                    }
                    if (
    $sign$mantissa = -$mantissa;
                    
    $raw $sign | (($exponent 0x0F) << 11) | ($mantissa 0x07FF);
                    return 
    pack('C*'0x80, ($raw >> 8) & 0xFF$raw 0xFF);

                default:
                    throw new 
    Exception("DPT Typ '{$dptType}' nicht unterstützt.");
            }
        }

        public function 
    send(string $groupAddressstring $dptType$value): bool {
            if (
    $this->channelId === 0) {
                
    $this->log("❌ send() ohne aktiven Tunnel aufgerufen, zuerst connect() erfolgreich ausführen.");
                return 
    false;
            }

            
    $gaBytes $this->parseGroupAddress($groupAddress);
            
    $payload $this->encodeDPT($dptType$value);
            
    $payloadLen strlen($payload);

            
    $cemi "\x11\x00\xbc\xe0\x00\x00" $gaBytes pack('C'$payloadLen) . "\x00" $payload;
            
    $totalLen strlen($cemi);

            
    $header "\x06\x10\x04\x20" pack('n'$totalLen);
            
    $connectionHeader pack('CC'0x04$this->channelId) . pack('CC'$this->sequenceNumber0x00);

            
    $packet $header $connectionHeader $cemi;

            
    socket_sendto($this->socket$packetstrlen($packet), 0$this->ip$this->port);
            
    $this->log("📤 Telegramm an <b>{$groupAddress}</b> gesendet! (Seq: {$this->sequenceNumber})");

            
    $ack $this->receiveACK();
            
    $this->sequenceNumber = ($this->sequenceNumber 1) % 256;

            return 
    $ack;
        }

        private function 
    receiveACK(): bool {
            
    $buffer '';
            
    $from '';
            
    $port 0;
            
    $bytes = @socket_recvfrom($this->socket$buffer10240$from$port);

            if (
    $bytes === false) {
                
    $this->log("❌ Kein ACK erhalten (Timeout) : " socket_strerror(socket_last_error($this->socket)));
                return 
    false;
            }

            if (
    $bytes >= 10 && ord($buffer[2]) === 0x04 && ord($buffer[3]) === 0x21) {
                
    $status ord($buffer[9]);
                if (
    $status === 0x00) {
                    
    $this->log("📥 ACK vom IP Gateway erhalten.");
                    return 
    true;
                }
                
    $this->log("⚠️ ACK erhalten, aber mit Fehlerstatus: 0x" sprintf('%02X'$status));
                return 
    false;
            }

            
    $this->log("⚠️ Unerwartete Antwort statt ACK erhalten : " bin2hex($buffer));
            return 
    false;
        }

        public function 
    disconnect(): void {
            if (
    $this->channelId !== && $this->socket) {
                
    $disconnectRequest "\x06\x10\x02\x09\x00\x10" pack('CC'$this->channelId0x00) . "\x08\x01\x00\x00\x00\x00\x00\x00";
                @
    socket_sendto($this->socket$disconnectRequeststrlen($disconnectRequest), 0$this->ip$this->port);
                
    $this->log("🔌 IP Gateway Tunnel Verbindung (Channel {$this->channelId}) ordnungsgemäß getrennt.");
                
    $this->channelId 0;
            }
            if (
    $this->socket) {
                @
    socket_close($this->socket);
                
    $this->socket null;
            }
        }
    }

    $Ip '192.168.178.42';
    $knx = new KNXTunnel($Ip);

    try {
        if (
    $knx->connect()) {
            
    $knx->send('1/0/0''DPT1'true);
        }
    } finally {
        
    $knx->disconnect();
    }
    Mit besten Grüßen,
    Guido

    Meine Projekte hier : KNX Busmonitor​​ / KNX Schalter mit Audio​ / KNX PiRadio
Lädt...
X