############################################## # $Id$ # ABU 20160307 First release # ABU 20160309 Fixed issue for sending group-indexed with dpt1. Added debug-information. Fixed issue for indexed get. Fixed regex-replace-issue. # ABU 20160312 Fixed error while receiving numeric DPT with value 0. Added factor for dpt 08.010. # ABU 20160312 Fixed Regex-Attributes. Syntax changed from space-seperated to " /". # ABU 20160322 Fixed dpt1.008 # ABU 20160326 Added fix for stateFormat # ABU 20160327 Removed readingRegex, writingRegex, created stateRegex, stateCmd, added reading-name support, fixed dblog-split # ABU 20160403 Fixed various minor perl warnings # ABU 20160413 Changed SplitFn # ABU 20160414 Changed SplitFn again # ABU 20160416 Changed SplitFn again # ABU 20160422 Added dpt9.021 - mA # ABU 20160529 Changed Doku package main; use strict; use warnings; #set to 1 for debug my $debug = 0; #string constant for autocreate my $modelErr = "MODEL_NOT_DEFINED"; #valid set commands my %sets = ( #"off" => "noArg", #"on" => "noArg", "off" => "", "on" => "", "on-for-timer" => "", "on-until" => "", "value" => "", "string" => "", "raw" => "" ); #identifier for TUL my $id = 'C'; #regex patterns my $PAT_GAD = qr/^[0-9]{1,2}\/[0-9]{1,2}\/[0-9]{1,3}$/; my $PAT_GAD_HEX = qr/^[0-9a-f]{4}$/; my $PAT_GNO = qr/[gG][1-9][0-9]?/; #CODE is the identifier for the en- and decode algos. See encode and decode functions #UNIT is appended to state for a better reading #FACTOR and OFFSET are used to normalize a value. value = FACTOR * (RAW - OFFSET). Must be undef for non-numeric values. #PATTERN is used to check an trim the input-values #MIN and MAX are used to cast numeric values. Must be undef for non-numeric dpt. Special Usecase: DPT1 - MIN represents 00, MAX represents 01 my %dpttypes = ( #Binary value "dpt1" => {CODE=>"dpt1", UNIT=>"", FACTOR=>undef, OFFSET=>undef, PATTERN=>qr/([oO][nN])|([oO][fF][fF])|(0?1)|(0?0)/, MIN=>"off", MAX=>"on"}, "dpt1.001" => {CODE=>"dpt1", UNIT=>"", FACTOR=>undef, OFFSET=>undef, PATTERN=>qr/([oO][nN])|([oO][fF][fF])|(0?1)|(0?0)/, MIN=>"off", MAX=>"on"}, "dpt1.002" => {CODE=>"dpt1", UNIT=>"", FACTOR=>undef, OFFSET=>undef, PATTERN=>qr/([tT][rR][uU][eE])|([fF][aA][lL][sS][eE])|(0?1)|(0?0)/, MIN=>"false", MAX=>"true"}, "dpt1.003" => {CODE=>"dpt1", UNIT=>"", FACTOR=>undef, OFFSET=>undef, PATTERN=>qr/(([eE][nN]|[dD][iI][sS])[aA][bB][lL][eE])|(0?1)|(0?0)/, MIN=>"disable", MAX=>"enable"}, "dpt1.008" => {CODE=>"dpt1", UNIT=>"", FACTOR=>undef, OFFSET=>undef, PATTERN=>qr/([uU][pP])|([dD][oO][wW][nN])|(0?1)|(0?0)/, MIN=>"up", MAX=>"down"}, "dpt1.009" => {CODE=>"dpt1", UNIT=>"", FACTOR=>undef, OFFSET=>undef, PATTERN=>qr/([cC][lL][oO][sS][eE][dD])|([oO][pP][eE][nN])|(0?1)|(0?0)/, MIN=>"open", MAX=>"closed"}, "dpt1.019" => {CODE=>"dpt1", UNIT=>"", FACTOR=>undef, OFFSET=>undef, PATTERN=>qr/([cC][lL][oO][sS][eE][dD])|([oO][pP][eE][nN])|(0?1)|(0?0)/, MIN=>"closed", MAX=>"open"}, #Step value (four-bit) "dpt3" => {CODE=>"dpt3", UNIT=>"", FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,3}/, MIN=>-100, MAX=>100}, # 1-Octet unsigned value "dpt5" => {CODE=>"dpt5", UNIT=>"", FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,3}/, MIN=>0, MAX=>255}, "dpt5.001" => {CODE=>"dpt5", UNIT=>"%", FACTOR=>100/255, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,3}/, MIN=>0, MAX=>100}, "dpt5.003" => {CODE=>"dpt5", UNIT=>"°", FACTOR=>360/255, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,3}/, MIN=>0, MAX=>360}, "dpt5.004" => {CODE=>"dpt5", UNIT=>"%", FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,3}/, MIN=>0, MAX=>255}, # 1-Octet signed value "dpt6" => {CODE=>"dpt6", UNIT=>"", FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,3}/, MIN=>-127, MAX=>127}, "dpt6.001" => {CODE=>"dpt6", UNIT=>"%", FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,3}/, MIN=>0, MAX=>100}, # 2-Octet unsigned Value "dpt7" => {CODE=>"dpt7", UNIT=>"", FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,5}/, MIN=>0, MAX=>65535}, "dpt7.005" => {CODE=>"dpt7", UNIT=>"s", FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,5}/, MIN=>0, MAX=>65535}, "dpt7.006" => {CODE=>"dpt7", UNIT=>"m", FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,5}/, MIN=>0, MAX=>65535}, "dpt7.013" => {CODE=>"dpt7", UNIT=>"lux", FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,5}/, MIN=>0, MAX=>65535}, # 2-Octet signed Value "dpt8" => {CODE=>"dpt8", UNIT=>"", FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,5}/, MIN=>-32768, MAX=>32768}, "dpt8.005" => {CODE=>"dpt8", UNIT=>"s", FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,5}/, MIN=>-32768, MAX=>32768}, "dpt8.010" => {CODE=>"dpt8", UNIT=>"%", FACTOR=>0.01, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,5}/, MIN=>-32768, MAX=>32768}, "dpt8.011" => {CODE=>"dpt8", UNIT=>"°", FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,5}/, MIN=>-32768, MAX=>32768}, # 2-Octet Float value "dpt9" => {CODE=>"dpt9", UNIT=>"", FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,6}[.,]?\d{1,2}/, MIN=>-670760, MAX=>670760}, "dpt9.001" => {CODE=>"dpt9", UNIT=>"°C", FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,6}[.,]?\d{1,2}/, MIN=>-670760, MAX=>670760}, "dpt9.004" => {CODE=>"dpt9", UNIT=>"lux", FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,6}[.,]?\d{1,2}/, MIN=>-670760, MAX=>670760}, "dpt9.006" => {CODE=>"dpt9", UNIT=>"Pa", FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,6}[.,]?\d{1,2}/, MIN=>-670760, MAX=>670760}, "dpt9.005" => {CODE=>"dpt9", UNIT=>"m/s", FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,6}[.,]?\d{1,2}/, MIN=>-670760, MAX=>670760}, "dpt9.009" => {CODE=>"dpt9", UNIT=>"m³/h", FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,6}[.,]?\d{1,2}/, MIN=>-670760, MAX=>670760}, "dpt9.010" => {CODE=>"dpt9", UNIT=>"s", FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,6}[.,]?\d{1,2}/, MIN=>-670760, MAX=>670760}, "dpt9.021" => {CODE=>"dpt9", UNIT=>"mA", FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,6}[.,]?\d{1,2}/, MIN=>-670760, MAX=>670760}, "dpt9.024" => {CODE=>"dpt9", UNIT=>"kW", FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,6}[.,]?\d{1,2}/, MIN=>-670760, MAX=>670760}, "dpt9.025" => {CODE=>"dpt9", UNIT=>"l/h", FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,6}[.,]?\d{1,2}/, MIN=>-670760, MAX=>670760}, "dpt9.026" => {CODE=>"dpt9", UNIT=>"l/h", FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,6}[.,]?\d{1,2}/, MIN=>-670760, MAX=>670760}, "dpt9.028" => {CODE=>"dpt9", UNIT=>"km/h", FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,6}[.,]?\d{1,2}/, MIN=>-670760, MAX=>670760}, # Time of Day "dpt10" => {CODE=>"dpt10", UNIT=>"", FACTOR=>undef, OFFSET=>undef, PATTERN=>qr/((2[0-4]|[0?1][0-9]):(60|[0?1-5]?[0-9]):(60|[0?1-5]?[0-9]))|([nN][oO][wW])/, MIN=>undef, MAX=>undef}, # Date "dpt11" => {CODE=>"dpt11", UNIT=>"", FACTOR=>undef, OFFSET=>undef, PATTERN=>qr/((3[01]|[0-2]?[0-9]).(1[0-2]|0?[0-9]).(19[0-9][0-9]|2[01][0-9][0-9]))|([nN][oO][wW])/, MIN=>undef, MAX=>undef}, # 4-Octet unsigned value (handled as dpt7) "dpt12" => {CODE=>"dpt12", UNIT=>"", FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,10}/, MIN=>0, MAX=>4294967295}, # 4-Octet Signed Value "dpt13" => {CODE=>"dpt13", UNIT=>"", FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,10}/, MIN=>-2147483647, MAX=>2147483647}, "dpt13.010" => {CODE=>"dpt13", UNIT=>"Wh", FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,10}/, MIN=>-2147483647, MAX=>2147483647}, "dpt13.013" => {CODE=>"dpt13", UNIT=>"kWh", FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,10}/, MIN=>-2147483647, MAX=>2147483647}, # 4-Octet single precision float "dpt14" => {CODE=>"dpt14", UNIT=>"", FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,40}[.,]?\d{1,4}/, MIN=>undef, MAX=>undef}, "dpt14.019" => {CODE=>"dpt14", UNIT=>"A", FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,40}[.,]?\d{1,4}/, MIN=>undef, MAX=>undef}, "dpt14.027" => {CODE=>"dpt14", UNIT=>"V", FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,40}[.,]?\d{1,4}/, MIN=>undef, MAX=>undef}, "dpt14.056" => {CODE=>"dpt14", UNIT=>"W", FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,40}[.,]?\d{1,4}/, MIN=>undef, MAX=>undef}, "dpt14.068" => {CODE=>"dpt14", UNIT=>"°C", FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,40}[.,]?\d{1,4}/, MIN=>undef, MAX=>undef}, "dpt14.076" => {CODE=>"dpt14", UNIT=>"m³", FACTOR=>1, OFFSET=>0, PATTERN=>qr/[+-]?\d{1,40}[.,]?\d{1,4}/, MIN=>undef, MAX=>undef}, # 14-Octet String "dpt16" => {CODE=>"dpt16", UNIT=>"", FACTOR=>undef, OFFSET=>undef, PATTERN=>qr/.{1,14}/, MIN=>undef, MAX=>undef}, ); #Init this device #This declares the interface to fhem ############################# sub KNX_Initialize($) { my ($hash) = @_; $hash->{Match} = "^$id.*"; $hash->{GetFn} = "KNX_Get"; $hash->{SetFn} = "KNX_Set"; $hash->{StateFn} = "KNX_State"; $hash->{DefFn} = "KNX_Define"; $hash->{UndefFn} = "KNX_Undef"; $hash->{ParseFn} = "KNX_Parse"; $hash->{AttrFn} = "KNX_Attr"; $hash->{NotifyFn} = "KNX_Notify"; $hash->{DbLog_splitFn} = "KNX_DbLog_split"; $hash->{AttrList} = "IODev " . #tells the module the IO-Device to communicate with. Optionally set within definition. "do_not_notify:1,0 " . #supress any notification (including log) "listenonly:1,0 " . #device may not send any messages. answering is prohibited. get is prohibited. "readonly:1,0 " . #device may not send any messages. answering is prohibited. get is allowed. "showtime:1,0 " . #shows time instead of received value in state "answerReading:1,0 " . #allows FHEM to answer a read telegram "stateRegex " . #modifies state value "stateCmd " . #modify state value "format " . #supplies post-string "slider " . #creates slider. Syntax: min, step, max "$readingFnAttributes "; #standard attributes } #Define this device #Is called at every define ############################# sub KNX_Define($$) { my ($hash, $def) = @_; my @a = split("[ \t][ \t]*", $def); #device name my $name = $a[0]; #set verbose to 5, if debug enabled $attr{$name}{verbose} = 5 if ($debug eq 1); my $tempStr = join (", ", @a); Log3 ($name, 5, "define $name: enter $hash, attributes: $tempStr"); #too less arguments return "wrong syntax - define KNX [*] []" if (int(@a) < 3); #check for IODev #is last argument a group or a group:model pair? my $lastGroupDef = int(@a); #if (($a[int(@a) - 1] !~ m/^[0-9]{1,2}\/[0-9]{1,2}\/[0-9]{1,3}$/i) and ($a[int(@a) - 1] !~ m/^[0-9a-f]{4}$/i) and ($a[int(@a) - 1] !~ m/[0-9a-fA-F]:[dD][pP][tT]/i)) if (($a[int(@a) - 1] !~ m/${PAT_GAD}/i) and ($a[int(@a) - 1] !~ m/${PAT_GAD_HEX}/i) and ($a[int(@a) - 1] !~ m/[0-9a-fA-F]:[dD][pP][tT]/i)) { $attr{$name}{IODev} = $a[int(@a) - 1]; $lastGroupDef--; } #create groups and models, iterate through all possible args for (my $i = 2; $i < $lastGroupDef; $i++) { #backup actual GAD my $inp = lc($a[$i]); my ($group, $model, $rdname) = split /:/, $inp; my $groupc; #G-nr my $gno = $i - 1; #GAD not defined return "GAD not defined for group-number $gno" if (!defined($group)); #GAD wrong syntax #either 1/2/3 or 1203 #return "wrong group name format: specify as 0-15/0-15/0-255 or as hex" if (($group !~ m/^[0-9]{1,2}\/[0-9]{1,2}\/[0-9]{1,3}$/i) && (lc($group) !~ m/^[0-9a-f]{4}$/i)); return "wrong group name format: specify as 0-15/0-15/0-255 or as hex" if (($group !~ m/${PAT_GAD}/i) && (lc($group) !~ m/${PAT_GAD_HEX}/i)); #check if model supplied return "no model defined" if (!defined($model)); #within autocreate no model is supplied - throw warning if ($model eq $modelErr) { Log3 ($name, 2, "define $name: autocreate defines no model - only restricted functions are available"); } else { #check model-type return "invalid model. Use " .join(",", keys %dpttypes) if (!defined($dpttypes{$model})); } #convert to string, if supplied in Hex $group = hexToName ($group) if ($group =~ m/^[0-9a-f]{4}$/i); $groupc = nameToHex ($group); Log3 ($name, 5, "define $name: found GAD: $group, NO: $gno, HEX: $groupc, DPT: $model"); Log3 ($name, 5, "define $name: found Readings-Name: $rdname") if (defined ($rdname)); #add indexed group to hash. Index starts with one #readable GAD $hash->{GADDR}{$gno} = $group; #same as hex $hash->{GCODE}{$gno} = $groupc; #model $hash->{MODEL}{$gno} = $model; #backup readings-name $hash->{READINGSNAME}{$gno} = $rdname if (defined ($rdname) and !($rdname eq "")); } #common name $hash->{NAME} = $name; #backup name for a later rename $hash->{DEVNAME} = $name; #finally create decice #defptr is needed to supress FHEM input $modules{KNX}{defptr}{$name} = $hash; #assign io-dev automatically, if not given via definition AssignIoPort($hash); Log3 ($name, 5, "exit define"); return undef; } #Release this device #Is called at every delete / shutdown ############################# sub KNX_Undef($$) { my ($hash, $name) = @_; Log3 ($name, 5, "enter undef $name: hash: $hash name: $name"); #remove all groups foreach my $group (keys %{$hash->{GCODE}}) { Log3 ($name, 5, "undef $name: remove name: $hash->{NAME}, orig.-Name: $hash->{DEVNAME}, GAD: $group"); delete $hash->{GADDR}{$group}; delete $hash->{GCODE}{$group}; delete $hash->{MODEL}{$group}; } #remove module. Refer to DevName, because module may be renamed delete $modules{KNX}{defptr}{$hash->{DEVNAME}}; #remove name delete $hash->{NAME}; #remove backuped name delete $hash->{DEVNAME}; Log3 ($name, 5, "exit undef"); return undef; } #Places a "read" Message on the KNX-Bus #The answer is treated as regular telegram ############################# sub KNX_Get($@) { my ($hash, @a) = @_; my $name = $hash->{NAME}; my $groupnr = 1; my $tempStr = join (", ", @a); Log3 ($name, 5, "enter get $name: hash: $hash, attributes: $tempStr"); #FHEM asks with a ? at startup - no action, no log #return "" if($a[1] && $a[1] eq "?"); return "Unknown argument ?, choose one of -" if($a[1] && $a[1] eq "?"); splice(@a, 1, 1) if (defined ($a[1]) and ($a[1] =~ m/-/)); my $na = int(@a); #not more then 2 arguments allowed return "too much arguments. Maximum two arguments allowed" if($na>2); # the command can be send to any of the defined groups indexed starting by 1 # optional last argument starting with g indicates the group if(defined ($a[1])) { #check syntax if ($a[1]=~ m/${PAT_GNO}/) { #assign group-no $groupnr = $a[1]; $groupnr =~ s/^g//; } else { return "$a[1] is invalid. Second argument only may be a group g"; } } #get group from hash (hex) my $groupc = $hash->{GCODE}{$groupnr}; #get group from hash my $group = $hash->{GADDR}{$groupnr}; #return, if unknown group return "groupnr: $groupnr not known" if(!$groupc); #exit, if reat is prohibited return "did not request a value - \"listenonly\" is set." if (AttrVal ($name, "listenonly", 0) =~ m/1/); #send read-request to the bus Log3 ($name, 5, "get $name: request value for GAD $group"); IOWrite($hash, $id, "r" . $groupc); Log3 ($name, 5, "exit get"); return "current value for $name ($group) requested"; } #Does something according the given cmd... ############################# sub KNX_Set($@) { my ($hash, @a) = @_; my $name = $hash->{NAME}; my $ret = ""; my $na = int(@a); my $tempStr = join (", ", @a); #log only, if not called with cmd = ? Log3 ($name, 5, "enter set $name: hash: $hash, attributes: $tempStr") if (not ($a[1] eq "?")); #return, if no set value specified return "no set value specified" if($na < 2); #return, if this is a readonly-device return "this device is readonly" if(defined($hash->{readonly})); #backup values my $cmd = lc($a[1]); #remove whitespaces $cmd =~ s/^\s+|\s+$//g; #get slider definition my $slider = AttrVal ($name, "slider", undef); #check command #append slider, if wanted if(!defined($sets{$cmd})) { #my $resp = "unknown argument, choose one of " . join(" ", sort keys %sets); my $resp = "unknown argument, choose one of"; foreach my $key (sort keys %sets) { my $value = $sets{$key}; $resp = $resp . " " . $key; $resp = $resp . ":" . $sets{$key} if (defined($value) and not ($value eq "")); } #append slider-definition, if set...Necessary for FHEM $resp = $resp . ":slider,$slider" if(defined $slider); return $resp; } #the command can be send to any of the defined groups indexed starting by 1 #optional last argument starting with g indicates the group #default my $groupnr = 1; my $lastArg = $na - 1; #select another group, if the last arg starts with a g if($na > 2 && $a[$lastArg]=~ m/${PAT_GNO}/) { $groupnr = $a[$lastArg]; #remove "g" $groupnr =~ s/^g//g; $lastArg--; } #unknown groupnr return "group-no. not found" if(!defined($groupnr)); #group my $group = $hash->{GADDR}{$groupnr}; my $groupc = $hash->{GCODE}{$groupnr}; #unknown groupnr return "group-no. $groupnr not known" if(!defined($group)); #get model my $model = $hash->{MODEL}{$groupnr}; my $code = $dpttypes{$model}{CODE}; Log3 ($name, 5, "set $name: model: $model, GAD: $group, GAD hex: $groupc, gno: $groupnr"); #This contains the input my $value = ""; #delete any running timers if ($hash->{"ON-FOR-TIMER_G$groupnr"}) { CommandDelete(undef, $name . "_timer_$groupnr"); delete $hash->{"ON-FOR-TIMER_G$groupnr"}; } if($hash->{"ON-UNTIL_G$groupnr"}) { CommandDelete(undef, $name . "_until_$groupnr"); delete $hash->{"ON-UNTIL_G$groupnr"}; } #set on-for-timer if ($cmd =~ m/on-for-timer/) { return "\"on-for-timer\" only allowed for dpt1" if (not($code eq "dpt1")); #get duration my $duration = sprintf("%02d:%02d:%02d", $a[2]/3600, ($a[2]%3600)/60, $a[2]%60); #$modules{KNX}{"on-for-timer"}{$name} = $duration; $hash->{"ON-FOR-TIMER_G$groupnr"} = $duration; Log3 ($name, 5, "set $name: \"on-for-timer\" for $duration"); #set to on $value = 1; #place at-command for switching off CommandDefine(undef, $name . "_timer_$groupnr at +$duration set $name off g$groupnr"); } #set on-till elsif ($cmd =~ m/on-until/) { return "\"on\" only allowed for dpt1" if (not($code eq "dpt1")); #get off-time my ($err, $hr, $min, $sec, $fn) = GetTimeSpec($a[2]); return "Error trying to parse timespec for $a[2]: $err" if (defined($err)); #build of-time my @lt = localtime; my $hms_til = sprintf("%02d:%02d:%02d", $hr, $min, $sec); my $hms_now = sprintf("%02d:%02d:%02d", $lt[2], $lt[1], $lt[0]); return "Won't switch - now ($hms_now) is later than $hms_til" if($hms_now ge $hms_til); #$modules{KNX}{"on-until"}{$name} = $hms_til; $hash->{"ON-UNTIL_G$groupnr"} = $hms_til; Log3 ($name, 5, "set $name: \"on-until\" up to $hms_til"); #set to on $value = 1; #place at-command for switching off CommandDefine(undef, $name . "_until_$groupnr at $hms_til set $name off g$groupnr"); } #set on elsif ($cmd =~ m/on/) { return "\"on\" only allowed for dpt1" if (not($code eq "dpt1")); $value = 1; } #set off elsif ($cmd =~ m/off/) { return "\"off\" only allowed for dpt1" if (not($code eq "dpt1")); $value = 0; } #set raw elsif ($cmd =~ m/raw/) { return "no data for cmd $cmd" if ($lastArg < 2); #check for 1-16 hex-digits if ($a[2] =~ m/[0-9a-fA-F]{1,16}/) { $value = lc($a[2]); } else { return "$a[2] has wrong syntax. Use hex-format only."; } } #set value elsif ($cmd =~ m/value/) { #return "\"value\" not allowed for dpt1 and dpt16" if (($code eq "dpt1") or ($code eq "dpt16")); return "\"value\" not allowed for dpt1 and dpt16" if ($code eq "dpt16"); return "no data for cmd $cmd" if ($lastArg < 2); $value = $a[2]; #replace , with . $value =~ s/,/\./g; } #set string elsif ($cmd =~ m/string/) { return "\"string\" only allowed for dpt16" if (not($code eq "dpt16")); return "no data for cmd $cmd" if ($lastArg < 2); #join string for (my $i=2; $i<=$lastArg; $i++) { $value.= $a[$i]." "; } } #check and cast value my $transval = checkAndClean($hash, $value, $groupnr); return "invalid value: $value" if (!defined($transval)); #exit, if sending is prohibited return "did not send value - \"listenonly\" is set." if (AttrVal ($name, "listenonly", 0) =~ m/1/); return "did not send value - \"readonly\" is set." if (AttrVal ($name, "readonly", 0) =~ m/1/); #send value $transval = encodeByDpt($hash, $transval, $groupnr); IOWrite($hash, $id, "w" . $groupc . $transval); Log3 ($name, 5, "set $name: cmd: $cmd, value: $value, translated: $transval"); #build readingsName my $rdName = $hash->{READINGSNAME}{$groupnr}; if (defined ($rdName) and !($rdName eq "")) { Log3 ($name, 5, "set name: $name, replaced \"getG\" with custom readingName \"$rdName\""); $rdName = $rdName . "-set"; } else { $rdName = "setG" . $groupnr; } #re-read value, do not modify variable name due to usage in cmdAttr $transval = decodeByDpt($hash, $transval, $groupnr); #append post-string, if supplied my $suffix = AttrVal($name, "format",undef); $transval = $transval . " " . $suffix if (defined($suffix)); #execute regex, if defined my $regAttr = AttrVal($name, "stateRegex", undef); my $state = replaceByRegex ($regAttr, $rdName . ":", $transval); Log3 ($name, 5, "set name: $name - replaced $rdName:$transval to $state") if (not ($transval eq $state)); if (defined($state)) { readingsBeginUpdate($hash); readingsBulkUpdate($hash, $rdName, $transval); #execute state-command if defined #must be placed after first reading, because it may have a reference my $cmdAttr = AttrVal($name, "stateCmd", undef); if (defined ($cmdAttr) and !($cmdAttr eq "")) { $state = eval $cmdAttr; Log3 ($name, 5, "set name: $name - state replaced via command, result: state:$state"); } readingsBulkUpdate($hash, "state", $state); readingsEndUpdate($hash, 1); } Log3 ($name, 5, "exit set"); return undef; } #In case setstate is executed, a readingsupdate is initiated ############################# sub KNX_State($$$$) { my ($hash, $time, $reading, $value) = @_; my $name = $hash->{NAME}; my $tempStr = join (", ", @_); Log3 ($name, 5, "enter state: hash: $hash name: $name, attributes: $tempStr"); #in some cases state is submitted within value - if found, take only the stuff after state #my @strings = split("[sS][tT][aA][tT][eE]", $val); #$val = $strings[int(@strings) - 1]; return undef if (not (defined($value))); return undef if (not (defined($reading))); #remove whitespaces $value =~ s/^\s+|\s+$//g; $reading =~ s/^\s+|\s+$//g; $reading = lc ($reading) if ($reading =~ m/[sS][tT][aA][tT][eE]/); Log3 ($name, 5, "state $name: update $reading with value: $value"); #write value and update reading readingsSingleUpdate($hash, $reading, $value, 1); return undef; } #Get the chance to qualify attributes ############################# sub KNX_Attr(@) { my ($cmd,$name,$aName,$aVal) = @_; return undef; } #Split reading for DBLOG ############################# sub KNX_DbLog_split($) { my ($event) = @_; my ($reading, $value, $unit); my $tempStr = join (", ", @_); Log (5, "splitFn - enter, attributes: $tempStr"); #detect reading - real reading or state? my $isReading = "false"; $isReading = "true" if ($event =~ m/: /); #split input-string my @strings = split (" ", $event); my $startIndex = undef; $unit = ""; return undef if (not defined ($strings[0])); #real reading? if ($isReading =~ m/true/) { #first one is always reading $reading = $strings[0]; $reading =~ s/:?$//; $startIndex = 1; } #plain state else { #for reading state nothing is supplied $reading = "state"; $startIndex = 0; } return undef if (not defined ($strings[$startIndex])); #per default join all single pieces $value = join(" ", @strings[$startIndex..(int(@strings) - 1)]); #numeric value? #if ($strings[$startIndex] =~ /^[+-]?\d*[.,]?\d+/) if ($strings[$startIndex] =~ /^[+-]?\d*[.,]?\d+$/) { $value = $strings[$startIndex]; #single numeric value? Assume second par is unit... if ((defined ($strings[$startIndex + 1])) && !($strings[$startIndex+1] =~ /^[+-]?\d*[.,]?\d+/)) { $unit = $strings[$startIndex + 1] if (defined ($strings[$startIndex + 1])); } } #numeric value? #if ($strings[$startIndex] =~ /^[+-]?\d*[.,]?\d+/) #{ # $value = $strings[$startIndex]; # $unit = $strings[$startIndex + 1] if (defined ($strings[$startIndex + 1])); #} #string or raw #else #{ # $value = join(" ", @strings[$startIndex..(int(@strings) - 1)]); #} Log (5, "splitFn - READING: $reading, VALUE: $value, UNIT: $unit"); return ($reading, $value, $unit); } #Handle incoming messages ############################# sub KNX_Parse($$) { my ($hash, $msg) = @_; #Msg format: #C(w/r/p) i.e. Bw00000101 #we will also take reply telegrams into account, #as they will be sent if the status is asked from bus #split message into parts $msg =~ m/^$id(.{4})(.{1})(.{4})(.*)$/; my $src = $1; my $cmd = $2; my $dest = $3; my $val = $4; my @foundMsgs; Log3 ($hash->{NAME}, 5, "enter parse: hash: $hash name: $hash->{NAME}, msg: $msg"); #check if the code is within the read groups foreach my $deviceName (keys %{$modules{KNX}{defptr}}) { #fetch device my $deviceHash = $modules{KNX}{defptr}{$deviceName}; #skip, if name not defined next if (!defined($deviceHash)); #loop through all defined group-numbers foreach my $gno (keys %{$deviceHash->{GCODE}}) { #fetch groupcode my $groupc = $deviceHash->{GCODE}{$gno}; #GAD in message is matching GAD in device if (defined ($groupc) and ($groupc eq $dest)) { #get details my $name = $deviceHash->{NAME}; my $groupAddr = $deviceHash->{GADDR}{$gno}; my $model = $deviceHash->{MODEL}{$gno}; Log3 ($name, 5, "parse device hash: $deviceHash name: $name, GADDR: $groupAddr, GCODE: $groupc, MODEL: $model"); #handle write and reply messages if ($cmd =~ /[w|p]/) { #decode message my $transval = decodeByDpt ($deviceHash, $val, $gno); #message invalid if (not defined($transval) or ($transval eq "")) { Log3 ($name, 2, "parse device hash: $deviceHash name: $name, message could not be decoded - see log for details"); next; } Log3 ($name, 5, "received hash: $deviceHash name: $name, STATE: $transval, GNO: $gno, SENDER: $src"); #build readingsName my $rdName = $deviceHash->{READINGSNAME}{$gno}; if (defined ($rdName) and !($rdName eq "")) { Log3 ($name, 5, "parse device hash: $deviceHash name: $name, replaced \"getG\" with custom readingName \"$rdName\""); $rdName = $rdName . "-get"; } else { $rdName = "getG" . $gno; } #append post-string, if supplied my $suffix = AttrVal($name, "format",undef); $transval = $transval . " " . $suffix if (defined($suffix)); #execute regex, if defined my $regAttr = AttrVal($name, "stateRegex", undef); my $state = replaceByRegex ($regAttr, $rdName . ":", $transval); Log3 ($name, 5, "parse device hash: $deviceHash name: $name - replaced $rdName:$transval to $state") if (not ($transval eq $state)); if (defined($state)) { readingsBeginUpdate($deviceHash); readingsBulkUpdate($deviceHash, $rdName, $transval); readingsBulkUpdate($deviceHash, "last-sender", hexToName($src)); #execute state-command if defined #must be placed after first readings, because it may have a reference my $cmdAttr = AttrVal($name, "stateCmd", undef); if (defined ($cmdAttr) and !($cmdAttr eq "")) { $state = eval $cmdAttr; Log3 ($name, 5, "parse device hash: $deviceHash name: $name - state replaced via command - result: state:$state"); } readingsBulkUpdate($deviceHash, "state", $state); readingsEndUpdate($deviceHash, 1); } } #handle read messages, if Attribute is set elsif (($cmd =~ /[r]/) && (AttrVal($name, "answerReading",0) =~ m/1/)) { Log3 ($name, 5, "received hash: $deviceHash name: $name, GET"); my $transval = encodeByDpt($deviceHash, $deviceHash->{STATE}, $gno); if (defined($transval)) { Log3 ($name, 5, "received hash: $deviceHash name: $name, GET: $transval, GNO: $gno"); IOWrite ($deviceHash, "B", "p" . $groupc . $transval); } } #skip, if this is ignored next if (IsIgnored($name)); #save to list push(@foundMsgs, $name); } } } Log3 ($hash->{NAME}, 5, "exit parse"); #return values if (int(@foundMsgs)) { return @foundMsgs; } else { my $gad = hexToName($dest); my $ret = "KNX Unknown device $dest ($gad), Value $val, please define it"; Log3 ($dest, 3, "KNX Unknown device $dest ($gad), Value $val, please define it"); #needed for autocreate return "UNDEFINED KNX_$dest KNX $gad:$modelErr"; } } #Function is called at every notify ############################# sub KNX_Notify($$) { my ($ownHash, $callHash) = @_; #own name / hash my $ownName = $ownHash->{NAME}; #Device that created the events my $callName = $callHash->{NAME}; return undef; } #Private function to convert GAD from hex to readable version ############################# sub hexToName ($) { my $v = shift; my $p1 = hex(substr($v,0,1)); my $p2 = hex(substr($v,1,1)); my $p3 = hex(substr($v,2,2)); my $r = sprintf("%d/%d/%d", $p1,$p2,$p3); return $r; } #Private function to convert GAD from readable version to hex ############################# sub nameToHex ($) { my $v = shift; my $r = $v; if($v =~ /^([0-9]{1,2})\/([0-9]{1,2})\/([0-9]{1,3})$/) { $r = sprintf("%01x%01x%02x",$1,$2,$3); } #elsif($v =~ /^([0-9]{1,2})\.([0-9]{1,2})\.([0-9]{1,3})$/) #{ # $r = sprintf("%01x%01x%02x",$1,$2,$3); #} return $r; } #Private function to clean input string according DPT ############################# sub checkAndClean ($$$) { my ($hash, $value, $gno) = @_; my $name = $hash->{NAME}; my $orgValue = $value; Log3 ($name, 5, "check value: $value, gno: $gno"); #get model my $model = $hash->{MODEL}{$gno}; #return unchecked, if this is a autocreate-device return $value if ($model eq $modelErr); #get pattern my $pattern = $dpttypes{$model}{PATTERN}; #trim whitespaces at the end $value =~ s/^\s+|\s+$//g; #match against model pattern my @tmp = ($value =~ m/$pattern/g); #loop through results my $found = 0; foreach my $str (@tmp) { #assign first match and exit loop if (defined($str)) { $found = 1; $value = $str; last; } } return undef if ($found == 0); #get min my $min = $dpttypes{"$model"}{MIN}; #if min is numeric, cast to min $value = $min if (defined ($min) and ($min =~ /^[+-]?\d*[.,]?\d+/) and ($value < $min)); #get max my $max = $dpttypes{"$model"}{MAX}; #if max is numeric, cast to max $value = $max if (defined ($max) and ($max =~ /^[+-]?\d*[.,]?\d+/) and ($value > $max)); Log3 ($name, 3, "check value: input-value $orgValue was casted to $value") if (not($orgValue eq $value)); Log3 ($name, 5, "check value: $value, gno: $gno, model: $model, pattern: $pattern"); return $value; } #Private function to encode KNX-Message according DPT ############################# sub encodeByDpt ($$$) { my ($hash, $value, $gno) = @_; my $name = $hash->{NAME}; Log3 ($name, 5, "encode value: $value, gno: $gno"); #get model my $model = $hash->{MODEL}{$gno}; my $code = $dpttypes{$model}{CODE}; #return unchecked, if this is a autocreate-device return $value if ($model eq $modelErr); #this one stores the translated value (readble) my $numval = undef; #this one stores the translated hex-value my $hexval = undef; Log3 ($name, 5, "encode model: $model, code: $code, value: $value"); #get correction details my $factor = $dpttypes{$model}{FACTOR}; my $offset = $dpttypes{$model}{OFFSET}; #correct value $value /= $factor if (defined ($factor)); $value -= $offset if (defined ($offset)); Log3 ($name, 5, "encode normalized value: $value"); #Binary value if ($code eq "dpt1") { $numval = "00" if ($value eq 0); $numval = "01" if ($value eq 1); $numval = "00" if ($value eq $dpttypes{$model}{MIN}); $numval = "01" if ($value eq $dpttypes{$model}{MAX}); $hexval = $numval; } #Step value (four-bit) elsif ($code eq "dpt3") { $numval = 0; #get dim-direction my $sign = 0; $sign = 1 if ($value >= 0); #trim sign $value =~ s/^-//g; #get dim-value $numval = 7 if ($value >= 1); $numval = 6 if ($value >= 3); $numval = 5 if ($value >= 6); $numval = 4 if ($value >= 12); $numval = 3 if ($value >= 25); $numval = 2 if ($value >= 50); $numval = 1 if ($value >= 75); #assign dim direction $numval += 8 if ($sign == 1); #get hex representation $hexval = sprintf("%.2x",$numval); } #1-Octet unsigned value elsif ($code eq "dpt5") { $numval = $value; $hexval = sprintf("00%.2x",($numval)); } #1-Octet signed value elsif ($code eq "dpt6") { #build 2-complement $numval = $value; $numval += 0x100 if ($numval < 0); $numval = 0x00 if ($numval < 0x00); $numval = 0xFF if ($numval > 0xFF); #get hex representation $hexval = sprintf("00%.2x",$numval); } #2-Octet unsigned Value elsif ($code eq "dpt7") { $numval = $value; $hexval = sprintf("00%.4x",($numval)); } #2-Octet signed Value elsif ($code eq "dpt8") { #build 2-complement $numval = $value; $numval += 0x10000 if ($numval < 0); $numval = 0x00 if ($numval < 0x00); $numval = 0xFFFF if ($numval > 0xFFFF); #get hex representation $hexval = sprintf("00%.4x",$numval); } #2-Octet Float value elsif ($code eq "dpt9") { my $sign = ($value <0 ? 0x8000 : 0); my $exp = 0; my $mant = 0; $mant = int($value * 100.0); while (abs($mant) > 0x7FF) { $mant /= 2; $exp++; } $numval = $sign | ($exp << 11) | ($mant & 0x07ff); #get hex representation $hexval = sprintf("00%.4x",$numval); } #Time of Day elsif ($code eq "dpt10") { if (lc($value) eq "now") { #get actual time my ($secs,$mins,$hours,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time); my $hoffset; #add offsets $year+=1900; $mon++; # calculate offset for weekday $wday = 7 if ($wday eq "0"); $hoffset = 32*$wday; $hours += $hoffset; $value = "$hours:$mins:$secs"; $numval = $secs + ($mins<<8) + (($hoffset + $hours)<<16); } else { my ($hh, $mm, $ss) = split (":", $value); $numval = $ss + ($mm<<8) + (($hh)<<16); } #get hex representation $hexval = sprintf("00%.6x",$numval); } #Date elsif ($code eq "dpt11") { if (lc($value) eq "now") { #get actual time my ($secs,$mins,$hours,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time); my $hoffset; #add offsets $year+=1900; $mon++; # calculate offset for weekday $wday = 7 if ($wday eq "0"); $value = "$mday.$mon.$year"; $numval = ($year - 2000) + ($mon<<8) + ($mday<<16); } else { my ($dd, $mm, $yyyy) = split (/\./, $value); if ($yyyy >= 2000) { $yyyy -= 2000; } else { $yyyy -= 1900; } $numval = ($yyyy) + ($mm<<8) + ($dd<<16); } #get hex representation $hexval = sprintf("00%.6x",$numval); } #4-Octet unsigned value (handled as dpt7) elsif ($code eq "dpt12") { $numval = $value; $hexval = sprintf("00%.8x",($numval)); } #4-Octet Signed Value elsif ($code eq "dpt13") { #build 2-complement $numval = $value; $numval += 4294967296 if ($numval < 0); $numval = 0x00 if ($numval < 0x00); $numval = 0xFFFFFFFF if ($numval > 0xFFFFFFFF); #get hex representation $hexval = sprintf("00%.8x",$numval); } #4-Octet single precision float elsif ($code eq "dpt14") { $numval = unpack("L", pack("f", $value)); #get hex representation $hexval = sprintf("00%.8x",$numval); } #14-Octet String elsif ($code eq "dpt16") { #convert to hex-string my $dat = unpack "H*", $value; #format for 14-byte-length $dat = sprintf("%-028s",$dat); #append leading zeros $dat = "00" . $dat; $numval = $value; $hexval = $dat; } else { Log3 ($name, 2, "encode model: $model, no vaild model defined"); return undef; } Log3 ($name, 5, "encode model: $model, code: $code, value: $value, numval: $numval, hexval: $hexval"); return $hexval; } #Private function to replace state-values ############################# sub replaceByRegex ($$$) { my ($regAttr, $prefix, $input) = @_; my $retVal = $input; #execute regex, if defined if (defined($regAttr)) { #get array of given attributes my @reg = split(" /", $regAttr); my $tempVal = $prefix . $input; #loop over all regex foreach my $regex (@reg) { #trim leading and trailing slashes $regex =~ s/^\/|\/$//g; #get pairs my @regPair = split("\/", $regex); #skip if not at least 2 values supplied #next if (int(@regPair < 2)); next if (not defined($regPair[0])); if (not defined ($regPair[1])) { #cut value $tempVal =~ s/$regPair[0]//g; } else { #replace value $tempVal =~ s/$regPair[0]/$regPair[1]/g; } #restore value $retVal = $tempVal; } } return $retVal; } #Private function to decode KNX-Message according DPT ############################# sub decodeByDpt ($$$) { my ($hash, $value, $gno) = @_; my $name = $hash->{NAME}; Log3 ($name, 5, "decode value: $value, gno: $gno"); #get model my $model = $hash->{MODEL}{$gno}; my $code = $dpttypes{$model}{CODE}; #return unchecked, if this is a autocreate-device return $value if ($model eq $modelErr); #this one stores the translated value (readble) my $numval = undef; #this one contains the return-value my $state = undef; Log3 ($name, 5, "decode model: $model, code: $code, value: $value"); #get correction details my $factor = $dpttypes{$model}{FACTOR}; my $offset = $dpttypes{$model}{OFFSET}; #Binary value if ($code eq "dpt1") { my $min = $dpttypes{"$model"}{MIN}; my $max = $dpttypes{"$model"}{MAX}; $numval = $min if (lc($value) eq "00"); $numval = $max if (lc($value) eq "01"); $state = $numval; } #Step value (four-bit) elsif ($code eq "dpt3") { #get numeric value $numval = hex ($value); $state = 1 if ($numval & 7); $state = 3 if ($numval & 6); $state = 6 if ($numval & 5); $state = 12 if ($numval & 4); $state = 25 if ($numval & 3); $state = 50 if ($numval & 2); $state = 100 if ($numval & 1); #get dim-direction $state = 0 - $state if (not ($numval & 8)); #correct value $state -= $offset if (defined ($offset)); $state *= $factor if (defined ($factor)); $state = sprintf ("%d", $state); } #1-Octet unsigned value elsif ($code eq "dpt5") { $numval = hex ($value); $state = $numval; #correct value $state -= $offset if (defined ($offset)); $state *= $factor if (defined ($factor)); $state = sprintf ("%d", $state); } #1-Octet signed value elsif ($code eq "dpt6") { $numval = hex ($value); $numval -= 0x100 if ($numval >= 0x80); $state = $numval; #correct value $state -= $offset if (defined ($offset)); $state *= $factor if (defined ($factor)); $state = sprintf ("%d", $state); } #2-Octet unsigned Value elsif ($code eq "dpt7") { $numval = hex ($value); $state = $numval; #correct value $state -= $offset if (defined ($offset)); $state *= $factor if (defined ($factor)); $state = sprintf ("%d", $state); } #2-Octet signed Value elsif ($code eq "dpt8") { $numval = hex ($value); $numval -= 0x10000 if ($numval >= 0x8000); $state = $numval; #correct value $state -= $offset if (defined ($offset)); $state *= $factor if (defined ($factor)); $state = sprintf ("%d", $state); } #2-Octet Float value elsif ($code eq "dpt9") { $numval = hex($value); my $sign = 1; $sign = -1 if(($numval & 0x8000) > 0); my $exp = ($numval & 0x7800) >> 11; my $mant = ($numval & 0x07FF); $mant = -(~($mant-1) & 0x07FF) if($sign == -1); $numval = (1 << $exp) * 0.01 * $mant; #correct value $state -= $offset if (defined ($offset)); $state *= $factor if (defined ($factor)); $state = sprintf ("%.2f","$numval"); } #Time of Day elsif ($code eq "dpt10") { $numval = hex($value); my $hours = ($numval & 0x1F0000)>>16; my $mins = ($numval & 0x3F00)>>8; my $secs = ($numval & 0x3F); $state = sprintf("%02d:%02d:%02d",$hours,$mins,$secs); } #Date elsif ($code eq "dpt11") { $numval = hex($value); my $day = ($numval & 0x1F0000) >> 16; my $month = ($numval & 0x0F00) >> 8; my $year = ($numval & 0x7F); #translate year (21st cent if <90 / else 20th century) $year += 1900 if($year >= 90); $year += 2000 if($year < 90); $state = sprintf("%02d.%02d.%04d",$day,$month,$year); } #4-Octet unsigned value (handled as dpt7) elsif ($code eq "dpt12") { $numval = hex ($value); $state = $numval; #correct value $state -= $offset if (defined ($offset)); $state *= $factor if (defined ($factor)); $state = sprintf ("%d", $state); } #4-Octet Signed Value elsif ($code eq "dpt13") { $numval = hex ($value); $numval -= 4294967296 if ($numval >= 0x80000000); $state = $numval; #correct value $state -= $offset if (defined ($offset)); $state *= $factor if (defined ($factor)); $state = sprintf ("%d", $state); } #4-Octet single precision float elsif ($code eq "dpt14") { $numval = unpack "f", pack "L", hex ($value); #correct value $state -= $offset if (defined ($offset)); $state *= $factor if (defined ($factor)); $state = sprintf ("%.3f","$numval"); } #14-Octet String elsif ($code eq "dpt16") { $numval = 0; $state = ""; for (my $i = 0; $i < 14; $i++) { my $c = hex(substr($value, $i * 2, 2)); #exit at string terminator, otherwise append current char if ($c eq 0) { $i = 14; } else { $state .= sprintf("%c", $c); } } } else { Log3 ($name, 2, "decode model: $model, no valid model defined"); return undef; } #append unit, if supplied my $unit = $dpttypes{$model}{UNIT}; $state = $state . " " . $unit if (defined ($unit) and not($unit eq "")); Log3 ($name, 5, "decode model: $model, code: $code, value: $value, numval: $numval, state: $state"); return $state; } 1; =pod =begin html

KNX

    KNX is a standard for building automation / home automation. It is mainly based on a twisted pair wiring, but also other mediums (ip, wireless) are specified.

    For getting started, please refer to this document: KNX-Basics

    While the module TUL represents the connection to the KNX network, the KNX modules represent individual KNX devices. This module provides a basic set of operations (on, off, on-until, on-for-timer) to switch on/off KNX devices. For numeric DPT you can use value (set <devname> value <177.45>). For string-DPT you can use string (set <devname> string <Hello World>). For other, non-defined dpt you can send raw hex values to the network (set <devname> raw <hexval>).
    Sophisticated setups can be achieved by combining a number of KNX module instances. Therefore you can define a number of different GAD/DPT combinations per each device.

    KNX defines a series of Datapoint Type as standard data types used to allow general interpretation of values of devices manufactured by different companies. These datatypes are used to interpret the status of a device, so the state in FHEM will then show the correct value. For each received telegram there will be a reading with state, getG<group> and the sender address. For every set, there will be a reading with state and setG<group>.

    Define

      define <name> KNX <group>:<DPT>:<[;readingName]> [<group>:<DPT> ..] [IODev]

      A KNX device need a concrete DPT. Please refer to Available DPT. Otherwise the system cannot en- or decode the messages. Furthermore you can supply a IO-Device directly at startup. This can be done later on via attribute as well.

      Define an KNX device, connected via a TUL. The <group> parameters are either a group name notation (0-15/0-15/0-255) or the hex representation of the value (0-f0-f0-ff). All of the defined groups can be used for bus-communication. Without further attributes, all incoming messages are translated into state. Per default, the first group is used for sending. If you want to send via a different group, you have to index it (set <devname> value <17.0> <g2>).
      If you use the readingName, readings are based on this name (e.g. hugo-set, hugo-get for name hugo).

      The module autocreate is creating a new definition for any unknown sender. The device itself will be NOT fully available, until you added a DPT to the definition.

      Example:

            define lamp1 KNX 0/10/12:dpt1
            define lamp1 KNX 0/10/12:dpt1:meinName 0/0/5:dpt1.001
            define lamp1 KNX 0A0C:dpt1.003 myTul
            

    Set

      set <name> <on, off> [g<groupnr>] set <name> <on-for-timer, on-until> <time> [g<groupnr>] set <name> <value> [g<groupnr>] set <name> <string> [g<groupnr>] set <name> <raw> [g<groupnr>]

      Example:

            set lamp1 on
            set lamp1 off
            set lamp1 on-for-timer 10
            set lamp1 on-till 13:15:00
            set foobar raw 234578
            set thermo value 23.44
      	  set message value Hallo Welt
          

      When as last argument a g<groupnr> is present, the command will be sent to the KNX group indexed by the groupnr (starting by 1, in the order as given in define).

            define lamp1 KNX 0/10/01:dpt1 0/10/02:dpt1
            set lamp1 on g2 (will send "on" to 0/10/02)
      	

      A dimmer can be used with a slider as shown in following example:

            define dim1 KNX 0/0/5:dpt5.001
            attr dim1 slider 0,1,100
            attr dim1 webCmd value
      	

      The current date and time can be sent to the bus by the following settings:

            define timedev KNX 0/0/7:dpt10
            attr timedev webCmd now
            
            define datedev KNX 0/0/8:dpt11
            attr datedev webCmd now
            
            # send every midnight the new date
            define dateset at *00:00:00 set datedev value now
            
            # send every hour the current time
            define timeset at +*01:00:00 set timedev value now
      	

    Get

      If you execute get for a KNX-Element the status will be requested a state from the device. The device has to be able to respond to a read - this is not given for all devices.
      The answer from the bus-device is not shown in the toolbox, but is treated like a regular telegram.

    Attributes

    format

      The content of this attribute is added to every received value, before this is copied to state.

      Example:

            define myTemperature KNX 0/1/1:dpt5
            attr myTemperature format °C;
            

    stateRegex

      You can pass n pairs of regex-pattern and string to replace, seperated by a slash. Internally the "new" state is always in the format getG<group>:<state-value>. The substitution is done every time, a new object is received. You can use this function for converting, adding units, having more fun with icons, ... This function has only an impact on the content of state - no other functions are disturbed. It is executed directly after replacing the reading-names and setting the formats, but before stateCmd

      Example:

            define myLamp KNX 0/1/1:dpt1 0/1/2:dpt1 0/1/2:dpt1
            attr myLamp stateRegex /getG1:/steuern:/ /getG2:/status:/ /getG3:/sperre:/ /setG[13]:/steuern:/ /setG[3]://
      	  attr myLamp devStateIcon status.on:general_an status.off:general_aus sperre.on:lock steuern.*:hourglass
            

    stateCmd

      You can supply a perl-command for modifying state. This command is executed directly before updating the reading - so after renaming, format and regex. Please supply a valid perl command like using the attribute stateFormat. Unlike stateFormat the stateCmd modifies also the content of the reading, not only the hash-conten for visualization.

      Example:

            define myLamp KNX 0/1/1:dpt1 0/1/2:dpt1 0/1/2:dpt1
            attr myLamp stateCmd {$state = sprintf("%s", ReadingsVal($name,"getG2","undef"))}
            

    answerReading

      If enabled, FHEM answers on read requests. The content of state is send to the bus as answer.

      If set to 1, read-requests are answered

    listenonly

      If set to 1, the device may not send any messages. As well answering requests although get is prohibited.

    readonly

      If set to 1, the device may not send any messages. Answering requests are prohibited.Get is allowed.

    slider

      slider <min>,<step>,<max>
      With this attribute you can add a slider to any device.

      Example:

            define myDimmer KNX 0/1/1:dpt5
            attr myDimmer slider 0,1,100
      	  attr myDimmer webCmd value
            

    DPT - datapoint-types

      The following dpt are implemented and have to be assigned within the device definition.

      dpt1 on, off
      dpt1.001 on, off
      dpt1.002 true, false
      dpt1.003 enable, disable
      dpt1.008 up, down
      dpt1.009 open, closed
      dpt1.019 closed, open
      dpt3 -100..+100
      dpt5 0..255
      dpt5.001 0..100 %
      dpt5.003 0..360 °
      dpt5.004 0..255 %
      dpt6 -127..+127
      dpt6.001 0..100 %
      dpt7 0..65535
      dpt7.005 0..65535 s
      dpt7.005 0..65535 m
      dpt7.013 0..65535 lux
      dpt8 -32768..32768
      dpt8.005 -32768..32768 s
      dpt8.010 -32768..32768 %
      dpt8.011 -32768..32768 °
      dpt9 -670760.0..+670760.0
      dpt9.001 -670760.0..+670760.0 °
      dpt9.004 -670760.0..+670760.0 lux
      dpt9.006 -670760.0..+670760.0 Pa
      dpt9.005 -670760.0..+670760.0 m/s
      dpt9.009 -670760.0..+670760.0 m³/h
      dpt9.010 -670760.0..+670760.0 s
      dpt9.021 -670760.0..+670760.0 mA
      dpt9.024 -670760.0..+670760.0 kW
      dpt9.025 -670760.0..+670760.0 l/h
      dpt9.026 -670760.0..+670760.0 l/h
      dpt9.028 -670760.0..+670760.0 km/h
      dpt10 01:00:00
      dpt11 01.01.2000
      dpt12 0..+Inf
      dpt13 -Inf..+Inf
      dpt13.010 -Inf..+Inf Wh
      dpt13.013 -Inf..+Inf kWh
      dpt14 -Inf.0..+Inf.0
      dpt14.019 -Inf.0..+Inf.0 A
      dpt14.027 -Inf.0..+Inf.0 V
      dpt14.056 -Inf.0..+Inf.0 W
      dpt14.068 -Inf.0..+Inf.0 °C;
      dpt14.076 -Inf.0..+Inf.0 m³
      dpt16 String
=end html =begin html_DE

KNX

    KNX ist ein Standard zur Haus- und Gebäudeautomatisierung. Der Standard begründet sich hauptsächlich auf twisted pair, findet aber auch zunehmende Verbreitung auf andere Medien (Funk, Ethernet, ...)

    Für Anfänger sei folgende Lektüre empfohlen: KNX-Basics

    Das Modul TUL stellt die Verbindung zum Bus her, Das KNX-Modul stellt die Verbindung zu den einzelnen KNX-/EIB-Geräten her. Das Modul stellt Befehle (on, off, on-until, on-for-timer) zum ein- und Ausschalten von Geräten zur Verfügung. Für numerische DPT nutzt bitte value (set <devname> value <177.45>). Für string-DPT nutzt bitte string (set <devname> string <Hello World>). Für andere, undefinierte DPT könnt Ihr raw hex Werte ans Netzwerk senden (set <devname> raw <hexval>).
    Komplexe Konfigurationen können aufgebaut werden, indem mehrere Modulinstanzen in einem Gerät definiert werden. Dafür werden mehrere Kombinationen aus GAD und DPT in einem Gerät definiert werden.

    Der KNX-Standard stellt eine Reihe vordefinierter Datentypen zur Verfügung. Dies sichert die Herstellerübergreifende Kompatibilität. Basierend auf diesen DPT wird der Status eines Gerätes interpretiert und in FHEM angezeigt. Für jedes empfangene Telegramm wird ein reading mit state, getG<group> und der Absenderadresse angelegt. Für jedes ser-command wird ein Reading mit state und setG<group> angelegt.

    Define

      define <name> KNX <group>:<DPT>:<[;readingName]> [<group>:<DPT> ..] [IODev]

      Ein KNX-device benötigt einen konkreten DPT. Bitte schaut die verfügbaren DPT unter Available DPT nach. Wird kein korrekter DPT angegeben, kann das system die Nachrichten nicht korrekt de- / codieren. Weiterhin kann bei der Gerätedefinition eine IO-Schnittstelle angegeben werden. Dies kann später ebenfalls per Attribut erfolgen.

      Jedes Device muss an eine TUL gebunden sein. Die <group> Parameter werden entweder als Gruppenadresse (0-15/0-15/0-255) oder als Hex-notation angegeben (0-f0-f0-ff). Alle definierten Gruppen können für die Buskommunikation verwendet werden. Ohne weitere Attribute, werden alle eingehenden Nachrichten in state übersetzt. Per default wird über die erste Gruppe gesendet.
      Wenn Ihr einen readingNamen angebt, wird dieser als Basis für die Readings benutzt (z.B. hugo-set, hugo-get for name hugo).
      Wollt Ihr über eine andere Gruppe senden. müsst Ihr diese indizieren (set <devname> value <17.0> <g2>).

      Das Modul autocreate generiert eine Instanz für jede unbekannte Gruppenadresse. Das Gerät selbst wird jedoch NICHT korrekt funktionieren, so lange noch kein korrekter DPT angelegt ist.

      Example:

            define lamp1 KNX 0/10/12:dpt1
            define lamp1 KNX 0/10/12:dpt1:meinName 0/0/5:dpt1.001
            define lamp1 KNX 0A0C:dpt1.003 myTul
            

    Set

      set <name> <on, off> [g<groupnr>] set <name> <on-for-timer, on-until> <time> [g<groupnr>] set <name> <value> [g<groupnr>] set <name> <string> [g<groupnr>] set <name> <raw> [g<groupnr>]

      Example:

            set lamp1 on
            set lamp1 off
            set lamp1 on-for-timer 10
            set lamp1 on-till 13:15:00
            set foobar raw 234578
            set thermo value 23.44
      	  set message value Hallo Welt
          

      Wenn eine Gruppe angegeben wurde (g<groupnr>) wird das Telegramm an de indizierte Gruppe gesendet (start bei 1, wie in der Definition angegeben).

            define lamp1 KNX 0/10/01:dpt1 0/10/02:dpt1
            set lamp1 on g2 (will send "on" to 0/10/02)
      	

      Ein Dimmer mit Slider:

            define dim1 KNX 0/0/5:dpt5.001
            attr dim1 slider 0,1,100
            attr dim1 webCmd value
      	

      Aktuelle Uhrzeit / Datum können wie folgt auf den Bus gelegt werden:

            define timedev KNX 0/0/7:dpt10
            attr timedev webCmd now
            
            define datedev KNX 0/0/8:dpt11
            attr datedev webCmd now
            
            # send every midnight the new date
            define dateset at *00:00:00 set datedev value now
            
            # send every hour the current time
            define timeset at +*01:00:00 set timedev value now
      	

    Get

      Bei jeder Ausführung wird eine Leseanfrage an die entsprechende Gruppe geschickt. Die Gruppe muss in der Lage sein, auf diese Anfrage zu antworten (dies ist nicht immer der Fall).
      Die Antwort der Gruppe wird nicht im FHEMWEB angezeigt. Das empfangene Telegramm wird (wie jedes andere) ausgewertet.

    Attributes

    format

      Der Inhalt dieses Attributes wird bei jedem empfangenen Wert angehangen, bevor der Wert in state kopeiert wird.

      Example:

            define myTemperature KNX 0/1/1:dpt5
            attr myTemperature format °C;
            

    stateRegex

      Es kann eine Reihe an Search/Replace Patterns übergeben werden (getrennt durch einen Slash). Intern wird der neue Wert von state immer im Format getG<group>:<state-value>. abgebildet. Die Ersetzungen werden bei bei jedem neuen Telegramm vorgenommen. Ihr könnt die Funktion für Konvertierungen nutzen, Einheiten hinzufügen, Spaß mit Icons haben, ... Diese Funktion wirkt nur auf den Inhalt von State - sonst wird nichts beeinflusst. Die Funktion wird direkt nach dem Ersetzen der Readings-Namen und dem ergänzen der Formate ausgeführt.

      Example:

            define myLamp KNX 0/1/1:dpt1 0/1/2:dpt1 0/1/2:dpt1
      	  attr myLamp stateRegex /getG1:/steuern:/ /getG2:/status:/ /getG3:/sperre:/ /setG[13]:/steuern:/ /setG[3]://
      	  attr myLamp devStateIcon status.on:general_an status.off:general_aus sperre.on:lock steuern.*:hourglass
            

    stateCmd

      Hier könnt Ihr ein perl-Kommando angeben, welches state beeinflusst. Die Funktion wird unmittelbar vor dem Update des Readings aufgerufen - also nach dem Umbennenen der Readings, format und regex. Es ist ein gültiges Perl-Kommando anzugeben (vgl. stateFormat). Im Gegensatz zu StateFormat wirkt sich dieses Attribut inhaltlich auf das Reading aus, und nicht "nur" auf die Anzeige im FHEMWEB.

      Beispiel:

            define myLamp KNX 0/1/1:dpt1 0/1/2:dpt1 0/1/2:dpt1
            attr myLamp stateCmd {$state = sprintf("%s", ReadingsVal($name,"getG2","undef"))}
            

    answerReading

      Wenn aktiviert, antwortet FHEM auf Leseanfragen. Der Inhalt von state wird auf den Bus gelegt.

      Leseanfragen werden beantwortet, wenn der Wert auf 1 gesetzt ist.

    listenonly

      Wenn auf 1 gesetzt, kann das Gerät keine Nachrichten senden. Sowohl Leseanfragen als auch get sind verboten.

    readonly

      Wenn auf 1 gesetzt, kann das Gerät keine Nachrichten senden. Leseanfragen sind verboten. Get ist erlaubt.

    slider

      slider <min>,<step>,<max>
      Mit diesem Attribut könnt Ihr jedem Gerät einen Slider verpassen.

      Example:

            define myDimmer KNX 0/1/1:dpt5
            attr myDimmer slider 0,1,100
      	  attr myDimmer webCmd value
            

    DPT - datapoint-types

      Die folgenden DPT sind implementiert und müssen in der Gruppendefinition angegeben werden.

      dpt1 on, off
      dpt1.001 on, off
      dpt1.002 true, false
      dpt1.003 enable, disable
      dpt1.008 up, down
      dpt1.009 open, closed
      dpt1.019 closed, open
      dpt3 -100..+100
      dpt5 0..255
      dpt5.001 0..100 %
      dpt5.003 0..360 °
      dpt5.004 0..255 %
      dpt6 -127..+127
      dpt6.001 0..100 %
      dpt7 0..65535
      dpt7.005 0..65535 s
      dpt7.006 0..65535 m
      dpt7.013 0..65535 lux
      dpt8 -32768..32768
      dpt8.005 -32768..32768 s
      dpt8.010 -32768..32768 %
      dpt8.011 -32768..32768 °
      dpt9 -670760.0..+670760.0
      dpt9.001 -670760.0..+670760.0 °
      dpt9.004 -670760.0..+670760.0 lux
      dpt9.006 -670760.0..+670760.0 Pa
      dpt9.005 -670760.0..+670760.0 m/s
      dpt9.009 -670760.0..+670760.0 m³/h
      dpt9.010 -670760.0..+670760.0 s
      dpt9.021 -670760.0..+670760.0 mA
      dpt9.024 -670760.0..+670760.0 kW
      dpt9.025 -670760.0..+670760.0 l/h
      dpt9.026 -670760.0..+670760.0 l/h
      dpt9.028 -670760.0..+670760.0 km/h
      dpt10 01:00:00
      dpt11 01.01.2000
      dpt12 0..+Inf
      dpt13 -Inf..+Inf
      dpt13.010 -Inf..+Inf Wh
      dpt13.013 -Inf..+Inf kWh
      dpt14 -Inf.0..+Inf.0
      dpt14.019 -Inf.0..+Inf.0 A
      dpt14.027 -Inf.0..+Inf.0 V
      dpt14.056 -Inf.0..+Inf.0 W
      dpt14.068 -Inf.0..+Inf.0 °C;
      dpt14.076 -Inf.0..+Inf.0 m³
      dpt16 String
=end html_DE =cut