############################################## # $Id$ package main; use strict; use warnings; # Open Tasks # - precision for model percent to 0,1 # - allow defined groups that are only used for sending of data (no status shown) my %eib_c2b = ( "off" => "00", "on" => "01", "on-for-timer" => "01", "on-till" => "01", "raw" => "", "value" => "" #value must be last.. because of slider functionality in Set ); my %codes = ( "00" => "off", "01" => "on", "" => "value", ); my %readonly = ( "dummy" => 1, ); my $eib_simple ="off on value on-for-timer on-till"; my %models = ( ); my %eib_dpttypes = ( # 1-Octet unsigned value "dpt5" => {"CODE"=>"dpt5", "UNIT"=>"", "factor"=>1}, "percent" => {"CODE"=>"dpt5", "UNIT"=>"%", "factor"=>100/255, "slider"=>"0,1,100"}, "dpt5.003" => {"CODE"=>"dpt5", "UNIT"=>"°", "factor"=>360/255}, "angle" => {"CODE"=>"dpt5", "UNIT"=>"°", "factor"=>360/255}, # alias for dpt5.003 "dpt5.004" => {"CODE"=>"dpt5", "UNIT"=>"%", "factor"=>1}, "percent255" => {"CODE"=>"dpt5", "UNIT"=>"%", "factor"=>1 , "slider"=>"0,1,255"}, #alias for dpt5.004 # 2-Octet unsigned Value (current, length, brightness) "dpt7" => {"CODE"=>"dpt7", "UNIT"=>""}, "length-mm" => {"CODE"=>"dpt7", "UNIT"=>"mm"}, "current-mA" => {"CODE"=>"dpt7", "UNIT"=>"mA"}, "brightness" => {"CODE"=>"dpt7", "UNIT"=>"lux"}, "timeperiod-ms" => {"CODE"=>"dpt7", "UNIT"=>"ms"}, "timeperiod-min" => {"CODE"=>"dpt7", "UNIT"=>"min"}, "timeperiod-h" => {"CODE"=>"dpt7", "UNIT"=>"h"}, # 2-Octet Float Value (Temp / Light) "dpt9" => {"CODE"=>"dpt9", "UNIT"=>""}, "tempsensor" => {"CODE"=>"dpt9", "UNIT"=>"°C"}, "lightsensor" => {"CODE"=>"dpt9", "UNIT"=>"Lux"}, "speedsensor" => {"CODE"=>"dpt9", "UNIT"=>"m/s"}, "speedsensor-km/h" => {"CODE"=>"dpt9", "UNIT"=>"km/h"}, "pressuresensor" => {"CODE"=>"dpt9", "UNIT"=>"Pa"}, "rainsensor" => {"CODE"=>"dpt9", "UNIT"=>"l/mē"}, "time1sensor" => {"CODE"=>"dpt9", "UNIT"=>"s"}, "time2sensor" => {"CODE"=>"dpt9", "UNIT"=>"ms"}, "humiditysensor" => {"CODE"=>"dpt9", "UNIT"=>"%"}, "airqualitysensor" => {"CODE"=>"dpt9", "UNIT"=>"ppm"}, "voltage-mV" => {"CODE"=>"dpt9", "UNIT"=>"mV"}, "current-mA2" => {"CODE"=>"dpt9", "UNIT"=>"mA"}, "power" => {"CODE"=>"dpt9", "UNIT"=>"kW"}, "powerdensity" => {"CODE"=>"dpt9", "UNIT"=>"W/mē"}, # Time of Day "dpt10" => {"CODE"=>"dpt10", "UNIT"=>""}, "time" => {"CODE"=>"dpt10", "UNIT"=>""}, # Date "dpt11" => {"CODE"=>"dpt11", "UNIT"=>""}, "date" => {"CODE"=>"dpt11", "UNIT"=>""}, # 4-Octet unsigned value (handled as dpt7) "dpt12" => {"CODE"=>"dpt7", "UNIT"=>""}, # 4-Octet single precision float "dpt14" => {"CODE"=>"dpt14", "UNIT"=>""}, ); sub EIB_Initialize($) { my ($hash) = @_; $hash->{Match} = "^B.*"; $hash->{GetFn} = "EIB_Get"; $hash->{SetFn} = "EIB_Set"; $hash->{StateFn} = "EIB_SetState"; $hash->{DefFn} = "EIB_Define"; $hash->{UndefFn} = "EIB_Undef"; $hash->{ParseFn} = "EIB_Parse"; $hash->{AttrList} = "IODev do_not_notify:1,0 ignore:0,1 dummy:1,0 showtime:1,0 loglevel:0,1,2,3,4,5,6 model:" . join(",", keys %eib_dpttypes); } ############################# sub EIB_Define($$) { my ($hash, $def) = @_; my @a = split("[ \t][ \t]*", $def); my $u = "wrong syntax: define EIB [*]"; return $u if(int(@a) < 3); return "Define $a[0]: wrong group name format: specify as 0-15/0-15/0-255 or as hex" if( ($a[2] !~ m/^[0-9]{1,2}\/[0-9]{1,2}\/[0-9]{1,3}$/i) && (lc($a[2]) !~ m/^[0-9a-f]{4}$/i)); my $groupname = eib_name2hex(lc($a[2])); $hash->{GROUP} = lc($groupname); my $code = "$groupname"; my $ncode = 1; my $name = $a[0]; $hash->{CODE}{$ncode++} = $code; # add read group names if(int(@a)>3) { for (my $count = 3; $count < int(@a); $count++) { $hash->{CODE}{$ncode++} = eib_name2hex(lc($a[$count]));; } } $modules{EIB}{defptr}{$code}{$name} = $hash; AssignIoPort($hash); } ############################# sub EIB_Undef($$) { my ($hash, $name) = @_; foreach my $c (keys %{ $hash->{CODE} } ) { $c = $hash->{CODE}{$c}; # As after a rename the $name may be different from the $defptr{$c}{$n} # we look for the hash. foreach my $dname (keys %{ $modules{EIB}{defptr}{$c} }) { delete($modules{EIB}{defptr}{$c}{$dname}) if($modules{EIB}{defptr}{$c}{$dname} == $hash); } } return undef; } ##################################### sub EIB_SetState($$$$) { my ($hash, $tim, $vt, $val) = @_; Log(5,"EIB setState tim: $tim vt: $vt val: $val"); $val = $1 if($val =~ m/^(.*) \d+$/); return "Undefined value $val" if(!defined($eib_c2b{$val})); return undef; } ################################### sub EIB_Get($@) { my ($hash, @a) = @_; return "" if($a[1] && $a[1] eq "?"); # Temporary hack for FHEMWEB IOWrite($hash, "B", "r" . $hash->{GROUP}); return "Current value for $hash->{NAME} ($hash->{GROUP}) requested."; } ################################### sub EIB_Set($@) { my ($hash, @a) = @_; my $ret = undef; my $na = int(@a); return "no set value specified" if($na < 2 || $na > 4); return "Readonly value $a[1]" if(defined($readonly{$a[1]})); return "No $a[1] for dummies" if(IsDummy($hash->{NAME})); my $name = $a[0]; my $value = $a[1]; my $arg1 = undef; my $arg2 = undef; $arg1 = $a[2] if($na>2); $arg2 = $a[3] if($na>3); my $model = $attr{$name}{"model"}; my $sliderdef = !defined($model)?undef:$eib_dpttypes{"$model"}{"slider"}; my $c = $eib_c2b{$value}; if(!defined($c)) { my $resp = "Unknown argument $value, choose one of " . join(" ", sort keys %eib_c2b); $resp = $resp . ":slider,$sliderdef" if(defined $sliderdef); 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 my $groupnr = 1; $groupnr = $1 if($na>2 && $a[$na-1]=~ m/g([0-9]*)/); return "groupnr $groupnr not known." if(!$hash->{CODE}{$groupnr}); my $v = join(" ", @a); Log GetLogLevel($name,2), "EIB set $v"; (undef, $v) = split(" ", $v, 2); # Not interested in the name... if($value eq "raw" && defined($arg1)) { # complex value command. # the additional argument is transfered alone. $c = $arg1; } elsif ($value eq "value" && defined($arg1)) { # value to be translated according to datapoint type $c = EIB_EncodeByDatapointType($hash,$name,$arg1); # set the value to the back translated value $v = EIB_ParseByDatapointType($hash,$name,$c); } my $groupcode = $hash->{CODE}{$groupnr}; IOWrite($hash, "B", "w" . $groupcode . $c); ########################################### # Delete any timer for on-for_timer if($modules{EIB}{ldata}{$name}) { CommandDelete(undef, $name . "_timer"); delete $modules{EIB}{ldata}{$name}; } ########################################### # Add a timer if any for-timer command has been chosen if($value =~ m/for-timer/ && defined($arg1)) { my $dur = $arg1; my $to = sprintf("%02d:%02d:%02d", $dur/3600, ($dur%3600)/60, $dur%60); $modules{EIB}{ldata}{$name} = $to; Log 4, "Follow: +$to set $name off g$groupnr"; CommandDefine(undef, $name . "_timer at +$to set $name off g$groupnr"); } ########################################### # Delete any timer for on-till if($modules{EIB}{till}{$name}) { CommandDelete(undef, $name . "_till"); delete $modules{EIB}{till}{$name}; } ########################################### # Add a timer if on-till command has been chosen if($value =~ m/on-till/ && defined($arg1)) { my ($err, $hr, $min, $sec, $fn) = GetTimeSpec($arg1); if($err) { Log(2,"Error trying to parse timespec for $a[0] $a[1] $a[2] : $err"); } else { my @lt = localtime; my $hms_till = sprintf("%02d:%02d:%02d", $hr, $min, $sec); my $hms_now = sprintf("%02d:%02d:%02d", $lt[2], $lt[1], $lt[0]); if($hms_now ge $hms_till) { Log 4, "on-till: won't switch as now ($hms_now) is later than $hms_till"; } else { $modules{EIB}{till}{$name} = $hms_till; Log 4, "Follow: $hms_till set $name off g$groupnr"; CommandDefine(undef, $name . "_till at $hms_till set $name off g$groupnr"); } } } ########################## # Look for all devices with the same code, and set state, timestamp my $code = "$hash->{GROUP}"; my $tn = TimeNow(); foreach my $n (keys %{ $modules{EIB}{defptr}{$code} }) { my $lh = $modules{EIB}{defptr}{$code}{$n}; $lh->{CHANGED}[0] = $v; $lh->{STATE} = $v; $lh->{READINGS}{state}{TIME} = $tn; $lh->{READINGS}{state}{VAL} = $v; } return $ret; } sub EIB_Parse($$) { my ($hash, $msg) = @_; # Msg format: # B(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 if($msg =~ m/^B(.{4})[w|p](.{4})(.*)$/) { # only interested in write / reply group messages my $src = $1; my $dev = $2; my $val = $3; my $v = $codes{$val}; $v = "$val" if(!defined($v)); my $rawv = $v; my @list; my $found = 0; # check if the code is within the read groups # we will inform all definitions subsribed to this code foreach my $mod (keys %{$modules{EIB}{defptr}}) { my $def = $modules{EIB}{defptr}{"$mod"}; if($def) { foreach my $n (keys %{ $def }) { my $lh = $def->{$n}; foreach my $c (keys %{ $lh->{CODE} } ) { $c = $lh->{CODE}{$c}; if($c eq $dev) { $n = $lh->{NAME}; # It may be renamed next if(IsIgnored($n)); # Little strange. # parse/translate by datapoint type $v = EIB_ParseByDatapointType($lh,$n,$rawv); $lh->{CHANGED}[0] = $v; $lh->{STATE} = $v; $lh->{RAWSTATE} = $rawv; $lh->{LASTGROUP} = $dev; $lh->{READINGS}{state}{TIME} = TimeNow(); $lh->{READINGS}{state}{VAL} = $v; Log 2, "EIB $n $v"; push(@list, $n); $found = 1; } }} } } return @list if $found>0; if($found==0) { my $dev_name = eib_hex2name($dev); Log(3, "EIB Unknown device $dev ($dev_name), Value $val, please define it"); return "UNDEFINED EIB_$dev EIB $dev"; } } } sub EIB_EncodeByDatapointType($$$) { my ($hash, $name, $value) = @_; my $model = $attr{$name}{"model"}; # nothing to do if no model is given return $value if(!defined($model)); my $dpt = $eib_dpttypes{"$model"}; Log(4,"EIB encode $value for $name model: $model dpt: $dpt"); return $value if(!defined($dpt)); my $code = $eib_dpttypes{"$model"}{"CODE"}; my $unit = $eib_dpttypes{"$model"}{"UNIT"}; my $transval = undef; Log(4,"EIB encode $value for $name model: $model dpt: $code unit: $unit"); if ($code eq "dpt5") { my $dpt5factor = $eib_dpttypes{"$model"}{"factor"}; my $fullval = sprintf("00%.2x",($value/$dpt5factor)); $transval = $fullval; Log(5,"EIB $code encode $value = $fullval factor = $dpt5factor translated: $transval"); } elsif ($code eq "dpt7") { my $fullval = sprintf("00%.2x",$value); $transval = $fullval; Log(5,"EIB $code encode $value = $fullval translated: $transval"); } elsif($code eq "dpt9") { my $sign = $value<0?-1:1; my $absval = abs($value); my $exp = $absval==0?0:int(log($absval)/log(2)); my $mant = $absval / (2**$exp) *100; $mant = ((~($mant+1))&0x07FF) if($sign<0); my $fullval = $mant+2048*$exp; $fullval |=0x8000 if($sign<0); $transval = sprintf("00%.4x",$fullval); Log(5,"EIB $code encode $value = $fullval sign: $sign mant: $mant exp: $exp translated: $transval"); } elsif ($code eq "dpt10") { # set current Time my ($secs,$mins,$hours,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time); $year+=1900; $mon++; my $fullval = $secs + ($mins<<8) + ($hours<<16); $transval = sprintf("00%.6x",$fullval); Log(5,"EIB $code encode $value = $fullval hours: $hours mins: $mins secs: $secs translated: $transval"); } elsif ($code eq "dpt11") { # set current Date my ($secs,$mins,$hours,$day,$month,$year,$wday,$yday,$isdst) = localtime(time); $year+=1900; $month++; my $fullval = ($year-2000) + ($month<<8) + ($day<<16); $transval = sprintf("00%.6x",$fullval); Log(5,"EIB $code encode $value = $fullval day: $day month: $month year: $year translated: $transval"); } elsif ($code eq "dptxx") { } # set state to translated value if(defined($transval)) { Log(4,"EIB $name translated $value $unit to $transval"); $value = "$transval"; } else { Log(4,"EIB $name model $model value $value could not be translated/encoded. Just do a dec2hex translation"); $value = sprintf("00%.2x",$value); } return $value; } sub EIB_ParseByDatapointType($$$) { my ($hash, $name, $value) = @_; my $model = $attr{$name}{"model"}; # nothing to do if no model is given return $value if(!defined($model)); my $dpt = $eib_dpttypes{"$model"}; Log(4,"EIB parse $value for $name model: $model dpt: $dpt"); return $value if(!defined($dpt)); my $code = $eib_dpttypes{"$model"}{"CODE"}; my $unit = $eib_dpttypes{"$model"}{"UNIT"}; my $transval = undef; Log(4,"EIB parse $value for $name model: $model dpt: $code unit: $unit"); if ($code eq "dpt5") { my $dpt5factor = $eib_dpttypes{"$model"}{"factor"}; my $fullval = hex($value); $transval = $fullval; $transval = sprintf("%.0f",$transval * $dpt5factor) if($dpt5factor != 0); Log(5,"EIB $code parse $value = $fullval factor = $dpt5factor translated: $transval"); } elsif ($code eq "dpt7") { my $fullval = hex($value); $transval = $fullval; Log(5,"EIB $code parse $value = $fullval translated: $transval"); } elsif($code eq "dpt9") { my $fullval = hex($value); my $sign = 1; $sign = -1 if(($fullval & 0x8000)>0); my $exp = ($fullval & 0x7800)>>11; my $mant = ($fullval & 0x07FF); $mant = -(~($mant-1)&0x07FF) if($sign==-1); $transval = (1<<$exp)*0.01*$mant; Log(5,"EIB $code parse $value = $fullval sign: $sign mant: $mant exp: $exp translated: $transval"); } elsif ($code eq "dpt10") { # Time my $fullval = hex($value); my $hours = ($fullval & 0x1F0000)>>16; my $mins = ($fullval & 0x3F00)>>8; my $secs = ($fullval & 0x3F); $transval = sprintf("%02d:%02d:%02d",$hours,$mins,$secs); Log(5,"EIB $code parse $value = $fullval hours: $hours mins: $mins secs: $secs translated: $transval"); } elsif ($code eq "dpt11") { # Date my $fullval = hex($value); my $day = ($fullval & 0x1F0000)>>16; my $month = ($fullval & 0x0F00)>>8; my $year = ($fullval & 0x7F); #translate year (21st cent if <90 / else 20th century) $year += 1900 if($year>=90); $year += 2000 if($year<90); $transval = sprintf("%02d.%02d.%04d",$day,$month,$year); Log(5,"EIB $code parse $value = $fullval day: $day month: $month year: $year translated: $transval"); } elsif ($code eq "dpt14") # contributed by Olaf { # 4 byte single precision float my $byte0 = hex(substr($value,0,2)); my $sign = ($byte0 & 0x80) ? -1 : 1; my $bytee = hex(substr($value,0,4)); my $expo = (($bytee & 0x7F80) >> 7) - 127; my $bytem = hex(substr($value,2,6)); my $mant = ($bytem & 0x7FFFFF | 0x800000); $transval = $sign * (2 ** $expo) * ($mant / (1 <<23)); Log(5,"EIB $code parse $value translated: $transval"); } elsif ($code eq "dptxx") { } # set state to translated value if(defined($transval)) { Log(4,"EIB $name translated to $transval $unit"); $value = "$transval $unit"; } else { Log(4,"EIB $name model $model value $value could not be translated."); } return $value; } ############################# sub eib_hex2name($) { 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; } ############################# sub eib_name2hex($) { my $v = shift; my $r = $v; Log(5, "name2hex: $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; } 1; =pod =begin html

EIB / KNX

    EIB/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. While the module TUL represents the connection to the EIB network, the EIB modules represent individual EIB devices. This module provides a basic set of operations (on, off, on-till, etc.) to switch on/off EIB devices. Sophisticated setups can be achieved by combining a number of EIB module instances or by sending raw hex values to the network (set raw ). EIB/KNX defines a series of Datapoint Type as standard data types used to allow general interpretation of values of devices manufactured by diferent companies. This datatypes are used to interpret the status of a device, so the state in FHEM will then show the correct value.

    Define
      define <name> EIB <main group> [<additional group> ..]

      Define an EIB 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). The <main group> is used for sending of commands to the EIB network. The state of the instance will be updated when a new state is received from the network for any of the given groups. This is usefull for example for toggle switches where a on command is send to one group and the real state (on or off) is responded back on a second group. For actors and sensors the autocreate module may help.
      Example:
        define lamp1 EIB 0/10/12
        define lamp1 EIB 0/10/12 0/0/5
        define lamp1 EIB 0A0C

    Set
      set <name> <value> [<time> g<groupnr>]
      where value one of:
    • on switch on device
    • off switch off device
    • on-for-timer switch on the device for the given time. After the specified seconds a switch off command is sent.
    • on-till
    • raw sends the given value as raw data to the device.
    • value transforms the value according to the chosen model and send the result to the device.

    • Example:
        set lamp1 on
        set lamp1 off
        set lamp1 on-for-timer 10
        set lamp1 on-till 13:15:00
        set lamp1 raw 234578
        set lamp1 value 23.44
      When as last argument a g<groupnr> is present, the command will be sent to the EIB group indexed by the groupnr (starting by 1, in the order as given in Define).
      Example:
        define lamp1 EIB 0/10/01 0/10/02
        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 EIB 0/0/5 attr dim1 model percent attr dim1 webCmd value
      The current date and time can be sent to the bus by the following settings:
        define timedev EIB 0/0/7 attr timedev model dpt10 attr timedev eventMap /value now:now/ attr timedev webCmd now define datedev EIB 0/0/8 attr datedev model dpt11 attr datedev eventMap /value now:now/ 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

    Attributes
    • eventMap
    • webCmd
    • IODev
    • loglevel
    • do_not_notify
    • ignore
    • dummy
    • showtime
    • model set the model according to the datapoint types defined by the (EIB / KNX specifications).
      The device state in FHEM is interpreted and shown according to the specification.
      • dpt5
      • dpt5.003
      • angle
      • percent
      • dpt5.004
      • percent255
      • dpt7
      • length-mm
      • current-mA
      • brightness
      • timeperiod-ms
      • timeperiod-min
      • timeperiod-h
      • dpt9
      • tempsensor
      • lightsensor
      • speedsensor
      • speedsensor-km/h
      • pressuresensor
      • rainsensor
      • time1sensor
      • time2sensor
      • humiditysensor
      • airqualitysensor
      • voltage-mV
      • current-mA2
      • current-mA2
      • power
      • powerdensity
      • dpt10
      • time
      • dpt11
      • date
      • dpt12
      • dpt14

=end html =cut