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