KNX Modul

Hallo zusammen,

ich fange gerade an, mich an die Modulentwicklung von Symcon heranzutasten. Dazu wollte ich ein Modul schreiben, das mit dem KNX Bus kommunizieren kann. Ich habe mich mit ConnectParent() an das EIB Gateway rangehängt und bekomme nun alle Nachrichten auf dem Bus mit.

Ich scheitere aber daran, etwas auf den Bus zu schreiben. Ich bekomme immer einen Fehler „Daten sind mit übergeordnetem Objekt nicht kompatibel“.

Ich sende wie folgt (Die GUID habe ich vom EIB Gateway Modul ausgelesen, ich weiß nicht, ob die Well-known ist oder nur bei mir so):


 $json = json_encode(
            Array(
                "DataID" => "{8A4D3B17-F8D7-4905-877F-9E69CEC3D579}", 
                "GroupAddress1" => 7,
                "GroupAddress2" => 1,
                "GroupAddress3" => 100,
                "Data" => "1"
                )
            );
        
$result = $this->SendDataToParent($json);

Das Problem scheint offensichtlich, dass ist das Data Feld in KNX Daten umwandeln muss. Eine DPT 1.001 True wäre dann C2 81.

Das gleiche gilt natürlich für die ankommenden Daten.

Gibt es nicht die Möglichkeit, mit dem EIB Gateway statt mit Rohdaten mit den „Netto“ Daten zu kommunizieren?

Grüße
Bernd

Die GUID zum Senden ist „{42DFD4E4-5831-4A27-91B9-6FF1B2960260}“. Deine GUID ist die zum Empfangen. (Die Datenpakete sind trotzdem gleich)

Du musst die Daten komplett nach KNX Spezifikation senden. „Netto“ Daten wie du Sie nennst, kannst du über die jeweiligen DPT Instanzen senden :slight_smile: (Übrigens ergibt das ja auch sinn, dass die „Logik“ zum Rohdatenaufbau in die einzelnen DPT Instanzen verteilt ist. Wir haben somit im Gateway den grundlegenden Code zur Kommunikation und die Payload wird in den einzelnen Instanzen kodiert/dekodiert)

Was möchtest du denn genau bauen?

paresy

Mit der (richtigen :D) GUID klappt das Senden jetzt.

Das löst mir natürlich nicht das Problem mit dem Message Encoding und /Decoding.

Was ich eigentlich erreichen möchte, ist Objekte, die als Symcon Module realisiert sind quasi als Knx Objekte zu exposen. Konkret habe ich Roborock S5 Sauger, die ich mit KNX ansteuern möchte. Das kann ich natürlich auch in Symcon mit den entsprechenden DPT Instanzen nachbauen, aber das wird schnell recht mühsam mit Skripten, und Events.

Mein Proxy soll eine Handvoll Kommunikationsobjekte bzw. Gruppenadressen implementieren und das Verhalten eines echten KNX Geräts abbilden.

Die Konfiguration des Proxies würden dann zum Einen eine Roborock Instanz erwarten und Gruppenadressen für die gewünschten Funktionen. z.B.:

Start/Stop: 7/1/100
Start/Stop Rückmeldung: 7/1/101
Aufladen: 7/1/102
Ladestand: 7/1/…
usw.

Wenn das EIB Gateway nicht nur mit Rohdaten arbeiten würde, sondern stattdessen „Type“: „DPTxxx“, Data:„Wert“ ausspucken würde, wäre das natürlich prima.

Als Alternative sehe ich nur die Möglichkeit, dem Anwender die Möglichkeit zu geben, in der Konfiguration die DPT Instanzen zu wählen anstatt direkt Gruppenadressen anzugeben. Dann würde ich wohl gar nicht mehr mit dem EIB Gateway sprechen sondern nur noch mit den DPT Instanzen. Eigentlich wollte ich aber vermeiden, dass man so viele Instanzen anlegen muss.

Wenn ich den zweiten Weg wählen würde und die DPT Instanzen programmatisch anlegen wollte, wo würde ich die dann am Besten aufhängen? Unsichtbar geht ja nicht, oder?

Grüße
Bernd

Du kannst die unterhalb deiner Instanz anlegen und dann mit IPS_SetHidden verstecken.

paresy

Dann werde ich den Weg mal versuchen.

Einen Plan, vom EIB Modul statt KNX Rohdaten DPT Type und echte Daten auszugeben bzw. zu akzeptieren, gibt es nicht, oder? Wäre doch immer wieder praktisch.

Nein, das wird ziemlich sicher nicht kommen. (Für uns ist die aktuelle Variante aus den oben beschriebenen Gründen super und du bist der Erste der so etwas gerne haben möchte :))

paresy

Vielleicht wäre das die Gelegenheit (für wen auch immer) ein generic KNX Konverter zu bauen.

Es gibt dann eine einmalig vorhandene Instanz die ich z.B. so aufrufe: $rawData = Build_KNX($instanceID, „DPT1“, „3/4/6“, true);
In Abhängigkeit der DPT kann ich ggf. noch mehr Parameter anhängen. Alternativ ein $rawData = BuildKNX_DPT1($instanceID, „3/4/6“,true);

Du könntest dir diese Methoden auch jetzt für dein Modul bauen und so geschickt „auslagern“, dass man die Zusatzklasse/Traits auch in anderen Projekten einbinden kann.

Ich bin auch neu auf diesem Gebiet und habe auch viele Schwierigkeiten.

Jetzt wollte ich denk Weg über KNX Instanzen gehen, aber da kann ich mich mit ConnectParent() scheinbar gar nicht dranhängen. Geht das irgendwie? Ich möchte ja z.B. einen Read Request vom KNX BUS auf eine GA mitbekommen, um dann einen Wert vom Saugrobi aktiv abzufragen und zurückzusenden.

Ne, du müsstest dich über Events (ggf. Message Sink) an Änderung der Status Variable registrieren.

Das Prinzip ist klar, aber wie geht das bei Symcon?

Was ich ja mitbekommen möchte ist ein READ Request auf dem KNX Bus. Der führt ja nicht mal zum Ändern der Variable…

Sorry habe die Frage falsch verstanden. Sagtest du nicht, du hast dich erfolgreich an den Splitter gehangen und bekamst Infos?
Da müsste doch auch ein Read Request drin stehen.
Vorgehen: PHP Modul bauen und an Splitter hängen. Dessen einziger Inhalt: $this-SendDebug( ** receivedData **)
Da müsste doch zu sehen sein, dass der Read Request (per ETS ausgelöst) auftaucht. Dann weißt du wie der aussieht und kannst den Receive Filter auf den Splitter anpassen, um weniger Daten zu erhalten.

Das geht aber irgendwie nicht zusammen. Ich kann mich ans EIB Gateway (Splitter) hängen und bekomme alle (!) KNX Nachrichten im Rohformat. Die müsste ich aber selbst interpretieren und das ist mir zu aufwändig. Eine entsprechende PHP Bibliothek (vergleichbar mit dem Falcon .NET SDK) kenne ich nicht.

An die konkreten DPT Instanzen, die das ganzen Encoding/Decoding machen, kann ich mich aber scheinbar nicht dranhängen. Ich hatte gehofft, dass ich da auch mit ConnectParent() rankomme.

Wie gesagt, du kannst doch einfach ein Event erstellen, dass bei Variablenänderung der KNX Device Instanz dein Modul/Script startet.
Siehe Doku zu Message Sink und dort z.B. den Abschnitt Änderung von Variable.

Die Doku zu MessageSink habe ich mir angesehen.

Ist interessant, hilft in meine Fall aber nicht. Die Variable wird ja nicht verändert. Vom KNX BUS kommt lediglich ein READ Request, da z.B. die eine UI den aktuellen Batteriestand abfragen möchte. Der READ Request möchte ich behandeln und einen Wert zurück liefern.

Wenn ich den Read Request über MessageSink bekommen würde, würde mir das natürlich helfen. Habe aber keine Ahnung, für was ich mich da registrieren müsste.

In der Doku steht: Nachrichten von IP-Symcon haben die Wertebasis 10000, solche von Modulinstanzen (noch undokumentiert) die Wertebasis 20000. :frowning:

Ein RegisterMessage(InstanceID, „*“) wäre hilfreich, dann könnte man zumindest irgendwie herausfinden, was an Messages rumschwirrt…

Sorry. Ich weiß nicht warum ich dauernd vergesse, dass du den Read befehl und nicht den write Befehl mitbekommen willst.
Was spricht denn dagegen dich auf die gleiche GA wie eine Instanz zum schreiben nochmal zu registrieren (über den Message Filter) und daraus dann anhand weniger Bit zu schließen, dass es sich um einen Lesebefehl handelt. Ich bin mir zu 99% sicher, dass der Lesebefehl immer der selbe ist. Und eben nur die GA und nicht den DPT enthält.

Die ggf. notwendige Antwort kannst du dann ja über eine vorhandene KNX_DPTx Instanz versenden.

Wenn du selber auf Anfragen reagieren willst, musst du zwangsläufig den Datenaustausch benutzen.
Und dann musst du auch selber die Nutzdaten interpretieren, oder in deinem Fall die Nutzdaten fertig aufbereitet versenden.
Das ist nicht nur bei KNX so, auch viele andere Symcon eigenen und fremden Module arbeiten nach diesem geteilten Prinzip, dass der Splitter nur zerlegt/aufbereitet/syncronisiert und die Device Instanzen die Nutzdaten verarbeiten.
Michael

Hallo,

ich hatte auch das Bedürfnis, direkt auf KNX-GAs hören und auch senden zu können. Allerdings schien mir der Nutzen, dies nur von einem speziell entwickelten Modul aus zu können, zu gering. Daher habe ich ein KNX<->MQTT Konverter-Modul geschrieben, welches den KNX-Traffic in eine MQTT-Topic-Hierarchie umsetzt. Darauf kann man mit den vorhandenen Modulen entweder per Skript oder durch ein eigenes Modul zugreifen, die Adressstruktur ist dann ein einfacher String, z.B.
$FreiWählbaresPrefix/knx/status/1/2/3. Man benötigt natürlich noch einen MQTT-Server dafür. Ich verwende Mosquitto, der in IPS enthaltene Server hat sich etwas undeterministisch verhalten, hier wollte ich keine Debugging-Zeit investieren.
Bislang werden 21 DPTs unterstützt und konvertiert.
Besteht Interesse? Doku ist miserabel, aber der Code aufgeräumt. Müsste das ganze auch erst aus einem größeren Projekt (generische MQTT-Bridge) rausschälen.

Ciao
Andreas

Hast du denn einen fertigen Umrechnungs-Stack der KNX Datenpunkte implementiert? An dem hätte ich großes Interesse.

Hallo,

ich habe eine fertige PHP-Klasse zum Umrechnen. Sie kann von einem KNX-Konfigurator, der einen XML-Export der GAs aus ETS geladen hat, automatisch den DPT ermitteln und in beide Richtungen umrechnen (ConvertGADataToDPT und ConvertGADataFromDPT). Hierfür die Tabelle über GenerateTable mit der Instanz-ID des Konfigurators erstellen lassen und mit SerializeTable als JSON speichern. Beim Nutzen der Klasse dann mit DeserializeTable die Tabelle aus dem JSON laden und dann automatisiert umrechnen lassen.

Hier die Klasse:
<?php

define( 'KNX_DPT_1', '1' );       // 1 Bit
define( 'KNX_DPT_2', '2' );       // 2 Bit
define( 'KNX_DPT_3', '3' );       // 4 Bit Dimmen/Jalousie Schritt
define( 'KNX_DPT_4', '4' );       // 1 Byte ASCII (-0) oder ISO 8859-1 (-1)
define( 'KNX_DPT_5', '5' );       // 1 Byte
define( 'KNX_DPST_5_1', '5.1' );   // 1 Byte auf 0-100
define( 'KNX_DPST_5_3', '5.3' );   // 1 Byte auf 0-360
define( 'KNX_DPT_6', '6' );       // 1 Byte mit Vorzeichen
define( 'KNX_DPT_7', '7' );       // 2 Byte
define( 'KNX_DPT_8', '8' );       // 2 Byte mit Vorzeichen
define( 'KNX_DPT_9', '9' );       // 2 Byte Gleitkomma
define( 'KNX_DPT_12', '12' );     // 4 Byte
define( 'KNX_DPT_13', '13' );     // 4 Byte mit Vorzeichen
define( 'KNX_DPT_14', '14' );     // 4 Byte Gleitkomma
define( 'KNX_DPT_16', '16' );     // 14 Byte ASCII (-0) oder ISO 8859-1 (-1)
define( 'KNX_DPT_17', '17' );     // 1 Byte Szene (unterste 6 Bit Szenennummer)
define( 'KNX_DPT_18', '18' );     // 1 Byte Szene (oberstes Bit Speichern, unterste 6 Bit Szenennummer)
define( 'KNX_DPT_20', '20' );     // 1 Byte
define( 'KNX_DPT_21', '21' );     // 8 Bit Feld
define( 'KNX_DPT_232', '232' );   // RGB-Wert 3x(0..255)
define( 'KNX_DPT_249', '249' );   // Helligkeit Farbtemperaturübergang
define( 'KNX_DPT_251', '251' );   // RGBW-Wert 4x(0..255)

class KNX_DPT_Mapper {

  private $GA_Conversion_Table = [];

  protected function Log( $Message, $Data ) {
    IPS_LogMessage( $Message, $Data );
  }

  private function str2hex( $string ) {
  	return substr( chunk_split( bin2hex( $string ), 2, ' ' ), 0, -1 );
  }

  public function GetDPT( $HG, $MG, $UG ) {
    $result = '';
    if ( array_key_exists( $HG, $this->GA_Conversion_Table ) ) {
      if ( array_key_exists( $MG, $this->GA_Conversion_Table[ $HG ] ) ) {
        if ( array_key_exists( $UG, $this->GA_Conversion_Table[ $HG ][ $MG ] ) ) {
          $result = $this->GA_Conversion_Table[ $HG ][ $MG ][ $UG ];
        }
      }
    }
    return $result;
  }

  private function GetDPTfromString( $DPT_String ) {
    // Mappt aus der XML-DPT-Information die interne DPT-Konstante
    $DPT_Array = explode( "-", $DPT_String );
    $DPT_ID = $DPT_Array[ 1 ];
    if ( array_key_exists( 2, $DPT_Array ) ) {
      $DPT_SID = $DPT_Array[ 2 ];
    } else {
      $DPT_SID = '';
    }
    switch( $DPT_ID ) {
      case '1': return KNX_DPT_1; break;
      case '2': return KNX_DPT_2; break;
      case '3': return KNX_DPT_3; break;
      case '4': return KNX_DPT_4; break;
      case '5':
        if ( '1' == $DPT_SID ) { return KNX_DPST_5_1; }
        if ( '3' == $DPT_SID ) { return KNX_DPST_5_3; }
        return KNX_DPT_5;
        break;
      case '6': return KNX_DPT_6; break;
      case '7': return KNX_DPT_7; break;
      case '8': return KNX_DPT_8; break;
      case '9': return KNX_DPT_9; break;
      case '12': return KNX_DPT_12; break;
      case '13': return KNX_DPT_13; break;
      case '14': return KNX_DPT_14; break;
      case '15': return KNX_DPT_15; break;
      case '16': return KNX_DPT_16; break;
      case '17': return KNX_DPT_17; break;
      case '18': return KNX_DPT_18; break;
      case '20': return KNX_DPT_20; break;
      case '21': return KNX_DPT_21; break;
      case '232': return KNX_DPT_232; break;
      case '249': return KNX_DPT_249; break;
      case '251': return KNX_DPT_251; break;
    }
    return '';
  }

  public function ConvertDPTtoMQTT( $HG, $MG, $UG, $Data ) {
    $DPTData = $this->ConvertGADataFromDPT( $HG, $MG, $UG, $Data );
    if ( NULL === $DPTData ) { return NULL; }
    if ( is_bool( $DPTData ) ) {
      $DPTVal = ( true == $DPTData ) ? '1' : '0';
    } else {
      $DPTVal = strval( $DPTData );
    }
    return $DPTVal;
  }

  public function ConvertMQTTtoDPT( $HG, $MG, $UG, $Data ) {
    return $this->ConvertGADataToDPT( $HG, $MG, $UG, $Data );
  }

  public function ConvertGADataFromDPT( $HG, $MG, $UG, $Data ) {
    $DPT_ID = $this->GetDPT( $HG, $MG, $UG );
    if ( '' === $DPT_ID ) { return NULL; }
    return $this->ConvertFromDPT( $DPT_ID, $Data );
  }

  public function ConvertGADataToDPT( $HG, $MG, $UG, $Data ) {
    $DPT_ID = $this->GetDPT( $HG, $MG, $UG );
    if ( '' === $DPT_ID ) { return NULL; }
    return $this->ConvertToDPT( $DPT_ID, $Data );
  }

  public function SerializeTable() {
    return json_encode( $this->GA_Conversion_Table );
  }

  public function DeserializeTable( $Table ) {
    $this->GA_Conversion_Table = json_decode( $Table, true );
  }

  public function GenerateTable( $KNX_Configurator_ID ) {
    $this->GA_Conversion_Table = $this->int_GenerateTable( $KNX_Configurator_ID );
  }

  public function GetTable() {
    return $this->GA_Conversion_Table;
  }

  public function GetNameTable( $KNX_Configurator_ID ) {
    return $this->int_GenerateTable( $KNX_Configurator_ID, true );
  }

  private function int_GenerateTable( $KNX_Configurator_ID, $WithNames = false ) {
    if ( ! IPS_InstanceExists ( $KNX_Configurator_ID ) ) {
      throw new \Exception( "Konfigurator-Instanz existiert nicht", 203 );
    }
    $KNX_Configurator_Config = IPS_GetConfiguration( $KNX_Configurator_ID );
    $KNX_Configurator_ConfigObj = json_decode( $KNX_Configurator_Config );
    if ( ! property_exists( $KNX_Configurator_ConfigObj, 'XMLExport' ) ) {
      throw new \Exception( "Konfigurator-Instanz hat keinen XML-Export geladen", 204 );
    }
    $KNX_Configurator_XMLExport = base64_decode( $KNX_Configurator_ConfigObj->XMLExport );
    if ( empty( $KNX_Configurator_XMLExport ) ) {
      throw new \Exception( "Konfigurator-Instanz hat keinen XML-Export geladen", 204 );
    }

    $XML_Obj = simplexml_load_string( $KNX_Configurator_XMLExport, "SimpleXMLElement", LIBXML_NOCDATA );
    if ( empty( $XML_Obj ) ) {
      throw new \Exception( "Konfigurator-Instanz hat keinen gültigen XML-Export geladen", 204 );
    }

    $XML_JSON = json_encode( $XML_Obj );
    $KNX_GA_Array = json_decode( $XML_JSON, true ); // konvertiert in ein Array

    $ConversionTable = [];

    foreach( $KNX_GA_Array[ 'GroupRange' ] as $HG ) {
      if ( array_key_exists( 'GroupRange', $HG ) ) {
        if ( $WithNames ) {
            $RangeStartHG = $HG[ '@attributes' ][ 'RangeStart' ];
            $Adr_HG = intdiv( $RangeStartHG, 2048 );
            $ConversionTable[ $Adr_HG ][ -1 ] = $HG[ '@attributes' ][ 'Name' ];
        }
        foreach( $HG[ 'GroupRange' ] as $MG ) {
          if ( array_key_exists( 'GroupAddress', $MG ) ) {
            if ( $WithNames ) {
                $RangeStartMG = $MG[ '@attributes' ][ 'RangeStart' ];
                $Adr_MG = intdiv( $RangeStartMG % 2048, 256 );
                $ConversionTable[ $Adr_HG ][ $Adr_MG ][ -1 ] = $MG[ '@attributes' ][ 'Name' ];
            }
            foreach( $MG[ 'GroupAddress' ] as $UG ) {
              if ( array_key_exists( '@attributes', $UG ) ) {
                if (
                  ( array_key_exists( 'Address', $UG[ '@attributes' ] ) ) &&
                  ( array_key_exists( 'DPTs', $UG[ '@attributes' ] ) )
                ) {
                  list( $Adr_HG, $Adr_MG, $Adr_UG ) = explode( "/", $UG[ '@attributes' ][ 'Address' ] );
                  if ( $WithNames ) {
                    $ConversionTable[ $Adr_HG ][ $Adr_MG ][ $Adr_UG ] = $UG[ '@attributes' ][ 'Name' ];
                  } else {
                    $DPT_String = $UG[ '@attributes' ][ 'DPTs' ];
                    $DPT_ID = $this->GetDPTfromString( $DPT_String );
                    $ConversionTable[ $Adr_HG ][ $Adr_MG ][ $Adr_UG ] = $DPT_ID;
                    if ( '' === $DPT_ID ) {
                      $this->Log( "KNX DPT Mapper", "GA $Adr_HG/$Adr_MG/$Adr_UG has an unsupported DPT: $DPT_String" );
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
    return $ConversionTable;
  }

  private function ConvertFromDPT( $DPT, $Value ) {
    $result = NULL;
    try {
      // Bei gesetztem Bit 8 im ersten Byte wird ein Wert geschrieben
      // Bei gesetztem Bit 7 im ersten Byte wird ein Wert erneut übertragen
      // Sind die Bits nicht gesetzt handelt es sich um eine Leseanforderung
      if ( 0 == ( ord( $Value[ 0 ] ) & 0b11000000 ) ) {
        return NULL;
      }
      switch ( $DPT ) {
        case KNX_DPT_1:
          // 1 Bit
          $Val = unpack( 'C', $Value );
          $result = ( bool ) ( $Val[ 1 ] & 0b00000001 );
          break;
        case KNX_DPT_2:
          // 2 Bit
          $Val = unpack( 'C', $Value );
          $result = $Val[ 1 ] & 0b00000011;
          break;
        case KNX_DPT_3:
          // Dimmen
          $Val = unpack( 'C', $Value );
          $result = ( 0 == ( $Val[ 1 ] & 0b00001000 ) ) ? -1 : 1; // Increase/Down : Decrease/Up
          $Val = $Val[ 1 ] & 0b00000111;
          $result = ( 0 == $Val ) ? 0 : $result * ( 1 <<  ( $Val - 1 ) );
          break;
        case KNX_DPT_4:
          // 1 Character
          $Val = unpack( 'a', $Value, 1 );
          $result = $Val[ 1 ];
          break;
        case KNX_DPST_5_1:
          // Prozent
          $Val = unpack( 'C', $Value, 1 );
          $result = intval( round( $Val[ 1 ] / 255 * 100 ) );
          break;
        case KNX_DPST_5_3:
          // Grad
          $Val = unpack( 'C', $Value, 1 );
          $result = intval( round( $Val[ 1 ] / 255 * 360 ) );
          break;
        case KNX_DPT_5:
          // 0-255
          $Val = unpack( 'C', $Value, 1 );
          $result = $Val[ 1 ];
          break;
        case KNX_DPT_6:
          // Zählimpulse -127 bis 127
          $Val = unpack( 'c', $Value, 1 );
          $result = $Val[ 1 ];
          break;
        case KNX_DPT_7:
          // 2 Byte
          $Val = unpack( 'n', $Value, 1 );
          $result = $Val[ 1 ];
          break;
        case KNX_DPT_8:
          // 2 Byte mit Vorzeichen
          $Val = unpack( 'n', $Value, 1 );
          $result = $Val[ 1 ];
          if ( 0x8000 & $result ) {
            $result = - ( 0x010000 - $result );
          }
          break;
        case KNX_DPT_9:
          // 2 Byte Gleitkomma
          $Val = unpack( 'n', $Value, 1 );
          $sign = ( 0 == $Val[ 1 ] >> 15 ) ? 1 : -1 ;
          $exp = ( ( $Val[ 1 ] >> 11 ) & 0xF);
          $frac = ( $Val[ 1 ] & 0x07FF );
          if ( -1 == $sign ) {
            $frac = ( ~ ( $frac - 1 ) ) & 0x07FF;
          }
          $result = $sign * 0.01 * $frac * pow( 2, $exp );
          break;
        case KNX_DPT_12:
          // 4 Byte
          $Val = unpack( 'N', $Value, 1 );
          $result = $Val[ 1 ];
          break;
        case KNX_DPT_13:
          // 4 Byte mit Vorzeichen
          $Val = unpack( 'N', $Value, 1 );
          $result = $Val[ 1 ];
          if ( 0x80000000 & $result ) {
            $result = - ( 0xFFFFFFFF - $result + 1 );
          }
          break;
        case KNX_DPT_14:
          // 4 Byte Gleitkomma
          $Val = unpack( 'N', $Value, 1 );
          $sign = ( 0 == $Val[ 1 ] >> 31 ) ? 1 : -1 ;
          $x = ( $Val[ 1 ] & ( ( 1 << 23 ) - 1 ) ) + ( 1 << 23 );
          $exp = ( $Val[ 1 ] >> 23 & 0xFF) - 127;
          $result = $sign * $x * pow( 2, $exp - 23 );
          break;
        case KNX_DPT_16:
          // 14 Byte String
          $Val = unpack( "Z14", $Value, 1 );
          $result = $Val[ 1 ];
          break;
        case KNX_DPT_17:
          // Szene 1-64
          $result = ( ord( $Value[ 1 ] ) & 0b00111111 ) + 1;
          break;
        case KNX_DPT_18:
          // Szene 1-64 mit Speichermöglichkeit
          $Val = ord( $Value[ 1 ] );
          $result = ( $Val & 0b00111111 ) + 1;
          if ( 0 != ( $Val & 0b10000000 ) ) {
            // Speichern, Szenenwert ist negativ
            $result = $result * -1;
          }
          break;
        case KNX_DPT_20:
          // 1 Byte
          $result = ord( $Value[ 1 ] );
          break;
        case KNX_DPT_21:
          // 8 Bit Feld
          $result = ord( $Value[ 1 ] );
          break;
        case KNX_DPT_232:
          // RGB-Wert 3x(0..255)
          $Val = unpack( "C3", $Value, 1 );
          $result = sprintf( '#%02x%02x%02x', $Val[ 1 ], $Val[ 2 ], $Val[ 3 ] );
          break;
        case KNX_DPT_249:
          // 6 Byte Helligkeit Farbtemperaturübergang
          $Val = unpack( "C6", $Value, 1 );
          $result = sprintf( '#%02x%02x%02x%02x%02x%02x', $Val[ 1 ], $Val[ 2 ], $Val[ 3 ], $Val[ 4 ], $Val[ 5 ], $Val[ 6 ] );
          break;
        case KNX_DPT_251:
          // RGBW-Wert 4x(0..255)
          $Val = unpack( "C4", $Value, 1 );
          $result = sprintf( '#%02x%02x%02x%02x', $Val[ 1 ], $Val[ 2 ], $Val[ 3 ], $Val[ 4 ] );
          break;
        default:
          $result = NULL;
          break;
      }
    } catch ( Throwable $t ) {
      $this->Log( "KNX DPT Converter", "Failed converting from $DPT with data " . $this->str2hex( $Value ) );
      $result = NULL;
    }
    return $result;
  }

  private function ConvertToDPT( $DPT, $Value ) {
    $result = NULL;
    try {
      switch ( $DPT ) {
        case KNX_DPT_1:
          // 1 Bit
          $Val = intval( ( bool ) $Value );
          $result = pack( "C", $Val | 0x80 );
          break;
        case KNX_DPT_2:
          // 2 Bit
          $Val = ( 4 > $Value ) ? $Value : 3;
          $result = pack( "C", intval( $Val ) | 0x80 );
          break;
        case KNX_DPT_3:
          // Dimmen
          if ( 0 > $Value ) {
            // Decrease/Up oder Stop
            $Val = 0b10001000;
            $Value = abs( $Value );
          } else {
            // Increase/Down
            $Val = 0b10000000;
          }
          if ( 64 < $Value ) {
            $Value = 64; // 2 hoch 7 ist das Maximum für den 3-Bit-Stepcode (0b111 - 1)
          }
          $Val |= ( 0 == $Value ) ? 0 : 0b00000111 & ( intval( floor( log( $Value, 2 ) ) ) + 1 );
          $result = pack( "C", $Val );
          break;
        case KNX_DPT_4:
          // 1 Character
          $result = pack( "Ca", 0x80, substr( $Value, 0, 1 ) );
          break;
        case KNX_DPST_5_1:
          // Prozent
          $result = pack( "CC", 0x80, round( round( $Value / 100 * 255 ) ) );
          break;
        case KNX_DPST_5_3:
          // Grad
          $result = pack( "CC", 0x80, intval( round( $Value / 360 * 255 ) ) );
          break;
        case KNX_DPT_5:
          // 0-255
          $result = pack( "CC", 0x80, $Value );
          break;
        case KNX_DPT_6:
          // Zählimpulse -127 bis 127
          $result = pack( "Cc", 0x80, $Value );
          break;
        case KNX_DPT_7:
          // 2 Byte
          $result = pack( "Cn", 0x80, $Value );
          break;
        case KNX_DPT_8:
          // 2 Byte mit Vorzeichen
          if ( 0 > $Value ) {
            $Value += 0x010000;
          }
          $result = pack( "Cn", 0x80, $Value );
          break;
        case KNX_DPT_9:
          // 2 Byte Gleitkomma
          $Value *= 100;
          $sign = 0;
          $exp = 0;
          if ( 0 > $Value ) {
            $sign = 0x8000;
            $Value = abs( $Value );
          }
          while ( $Value > 0x07ff ) {
            $Value >>= 1;
            $exp++;
          }
          if ( 0 != $sign ) {
            $Value = - $Value;
          }
          $Val = $sign | ( $Value & 0x07ff ) | ( ( $exp << 11 ) & 0x07800 ) & 0x0fff;
          $result = pack( "Cn", 0x80, $Val );
          break;
        case KNX_DPT_12:
          // 4 Byte
          $result = pack( "CN", 0x80, $Value );
          break;
        case KNX_DPT_13:
          // 4 Byte mit Vorzeichen
          if ( 0 > $Value ) {
            $Value += 0xFFFFFFFF + 1;
          }
          $result = pack( "CN", 0x80, $Value );
          break;
        case KNX_DPT_14:
          // 4 Byte Gleitkomma
          if ( 0 > $Value ) {
            $sign = 1;
            $Value = abs( $Value );
          } else {
            $sign = 0;
          }
          $exp = ( floor( log( $Value, 2 ) ) + 1 );
          $frac = ( $Value * pow( 2, -$exp ) );
          $exp += 126;
          $frac *= 2 << 23;
          $Byte1 = ( ( $sign << 7 ) & 0x80 ) | ( ( $exp >> 1 ) & 0x7F );
          $Byte2 = ( ( $exp << 7 ) & 0x80 ) | ( ( $frac >> 16 ) & 0x7F );
          $Byte3 = ( ( $frac >> 8 ) & 0xFF );
          $Byte4 = $frac & 0xFF;
          $result = pack( "C5", 0x80, $Byte1, $Byte2, $Byte3, $Byte4 );
          break;
        case KNX_DPT_16:
          // 14 Byte String, ASCII oder ISO 8859-1
          $result = pack( "CA14", 0x80, substr( str_pad( $Value, 14, "\0" ), 0, 14 ) );
          break;
        case KNX_DPT_17:
          // Szene 1-64
          $Val = abs( $Value );
          $Val = ( 64 > $Val ) ? $Val - 1 : 63 ;
          $result = chr( 0x80 ) . chr( $Val & 0b10111111 );
          break;
        case KNX_DPT_18:
          // Szene 1-64 mit Speichermöglichkeit
          $Val = abs( $Value );
          $Val = ( 64 > $Val ) ? $Val - 1 : 63 ;
          if ( 0 > $Value ) {
            $Val = $Val | 0b10000000;
          }
          $result = chr( 0x80 ) . chr( $Val & 0b10111111 );
          break;
        case KNX_DPT_20:
          // 1 Byte
          $result = pack( "CC", 0x80, $Value );
          break;
        case KNX_DPT_21:
          // 8 Bit Feld
          $result = pack( "CC", 0x80, $Value );
          break;
        case KNX_DPT_232:
          // RGB-Wert 3x(0..255) als #aabbcc oder #abc
          $len = strlen( $Value );
          if ( 4 == $len ) {
            list( $r, $g, $b ) = sscanf( $Value, "#%01x%01x%01x" );
            $r += $r * 0x10;
            $g += $g * 0x10;
            $b += $b * 0x10;
          } else if ( 7 == $len ) {
            list( $r, $g, $b ) = sscanf( $Value, "#%02x%02x%02x" );
          } else {
            $result = NULL;
            break;
          }
          $result = pack( "C4", 0x80, $r, $g, $b );
          break;
        case KNX_DPT_249:
          // 6 Byte Helligkeit Farbtemperaturübergang
          if ( 13 == strlen( $Value ) ) {
            list( $b1, $b2, $b3, $b4, $b5, $b6 ) = sscanf( $Value, "#%02x%02x%02x%02x%02x%02x" );
            $result = pack( "C7", 0x80, $b1, $b2, $b3, $b4, $b5, $b6 );
          }
          break;
        case KNX_DPT_251:
          // RGBW-Wert 4x(0..255) als #aabbccdd oder #abcd
          $len = strlen( $Value );
          if ( 5 == $len ) {
            list( $r, $g, $b, $w ) = sscanf( $Value, "#%01x%01x%01x%01x" );
            $r += $r * 0x10;
            $g += $g * 0x10;
            $b += $b * 0x10;
            $w += $w * 0x10;
          } else if ( 9 == $len ) {
            list( $r, $g, $b, $w ) = sscanf( $Value, "#%02x%02x%02x%02x" );
          } else {
            $result = NULL;
            break;
          }
          // alle vier Werte sollen berücksichtigt werden
          $result = pack( "C7", 0x80, $r, $g, $b, $w, 0, 0b00001111 );
          break;
        default:
          $result = NULL;
          break;
      }
    } catch ( Throwable $t ) {
      $this->Log( "KNX DPT Converter", "Failed converting to $DPT with data " . $this->str2hex( $Value ) );
      $result = NULL;
    }
    return $result;
  }

}

?>