############################################## # $Id$ package main; use strict; use warnings; use SetExtensions; sub EnOcean_Define($$); sub EnOcean_Initialize($); sub EnOcean_Parse($$); sub EnOcean_Set($@); sub EnOcean_MD15Cmd($$$); sub EnOcean_SndRadio($$$$$$$); sub EnOcean_ReadingScaled($$$$); sub EnOcean_TimerSet($); sub EnOcean_Undef($$); my %EnO_rorgname = ("F6" => "switch", # RPS, org 05 "D5" =>" contact", # 1BS, org 06 "A5" => "sensor", # 4BS, org 07 "D2" => "vld", # VLD ); my @EnO_ptm200btn = ("AI", "A0", "BI", "B0", "CI", "C0", "DI", "D0"); my %EnO_ptm200btn; # Gateway Commands my @EnO_gwCmd = ("switching", "dimming", "setpointShift", "setpointBasic", "controlVar", "fanStage", "blindCmd"); my %EnO_gwCmd = ( "switching" => 1, "dimming" => 2, "setpointShift" => 3, "setpointBasic" => 4, "controlVar" => 5, "fanStage" => 6, "blindCmd" => 7, ); # Some Manufacturers (e.g. Jaeger Direkt) also sell EnOcean products without an # entry in the table below. This table is only needed for 4BS category devices. my %EnO_manuf = ( "001" => "Peha", "002" => "Thermokon", "003" => "Servodan", "004" => "EchoFlex Solutions", "005" => "Omnio AG", "006" => "Hardmeier electronics", "007" => "Regulvar Inc", "008" => "Ad Hoc Electronics", "009" => "Distech Controls", "00A" => "Kieback + Peter", "00B" => "EnOcean GmbH", "00C" => "Probare", "00D" => "Eltako", "00E" => "Leviton", "00F" => "Honeywell", "010" => "Spartan Peripheral Devices", "011" => "Siemens", "012" => "T-Mac", "013" => "Reliable Controls Corporation", "014" => "Elsner Elektronik GmbH", "015" => "Diehl Controls", "016" => "BSC Computer", "017" => "S+S Regeltechnik GmbH", "018" => "Masco Corporation", "019" => "Intesis Software SL", "01A" => "Res.", "01B" => "Lutuo Technology", "01C" => "CAN2GO", "7FF" => "Multi user Manufacturer ID", ); my %EnO_subType = ( "A5.02.01" => "tempSensor.01", "A5.02.02" => "tempSensor.02", "A5.02.03" => "tempSensor.03", "A5.02.04" => "tempSensor.04", "A5.02.05" => "tempSensor.05", "A5.02.06" => "tempSensor.06", "A5.02.07" => "tempSensor.07", "A5.02.08" => "tempSensor.08", "A5.02.09" => "tempSensor.09", "A5.02.0A" => "tempSensor.0A", "A5.02.0B" => "tempSensor.0B", "A5.02.10" => "tempSensor.10", "A5.02.11" => "tempSensor.11", "A5.02.12" => "tempSensor.12", "A5.02.13" => "tempSensor.13", "A5.02.14" => "tempSensor.14", "A5.02.15" => "tempSensor.15", "A5.02.16" => "tempSensor.16", "A5.02.17" => "tempSensor.17", "A5.02.18" => "tempSensor.18", "A5.02.19" => "tempSensor.19", "A5.02.1A" => "tempSensor.1A", "A5.02.1B" => "tempSensor.1B", "A5.02.20" => "tempSensor.20", "A5.02.30" => "tempSensor.30", "A5.04.01" => "roomSensorControl.01", "A5.04.02" => "tempHumiSensor.02", "A5.06.01" => "lightSensor.01", "A5.06.02" => "lightSensor.02", "A5.06.03" => "lightSensor.03", "A5.07.01" => "occupSensor.01", "A5.07.02" => "occupSensor.02", "A5.07.03" => "occupSensor.03", "A5.08.01" => "lightTempOccupSensor.01", "A5.08.02" => "lightTempOccupSensor.02", "A5.08.03" => "lightTempOccupSensor.03", "A5.09.01" => "COSensor.01", "A5.09.04" => "tempHumiCO2Sensor.01", "A5.09.05" => "vocSensor.01", "A5.09.06" => "radonSensor.01", "A5.09.07" => "particlesSensor.01", "A5.10.01" => "roomSensorControl.05", "A5.10.02" => "roomSensorControl.05", "A5.10.03" => "roomSensorControl.05", "A5.10.04" => "roomSensorControl.05", "A5.10.05" => "roomSensorControl.05", "A5.10.06" => "roomSensorControl.05", "A5.10.07" => "roomSensorControl.05", "A5.10.08" => "roomSensorControl.05", "A5.10.09" => "roomSensorControl.05", "A5.10.0A" => "roomSensorControl.05", "A5.10.0B" => "roomSensorControl.05", "A5.10.0C" => "roomSensorControl.05", "A5.10.0D" => "roomSensorControl.05", "A5.10.10" => "roomSensorControl.01", "A5.10.11" => "roomSensorControl.01", "A5.10.12" => "roomSensorControl.01", "A5.10.13" => "roomSensorControl.01", "A5.10.14" => "roomSensorControl.01", "A5.10.15" => "roomSensorControl.02", "A5.10.16" => "roomSensorControl.02", "A5.10.17" => "roomSensorControl.02", "A5.10.18" => "roomSensorControl.18", "A5.10.19" => "roomSensorControl.19", "A5.10.1A" => "roomSensorControl.1A", "A5.10.1B" => "roomSensorControl.1B", "A5.10.1C" => "roomSensorControl.1C", "A5.10.1D" => "roomSensorControl.1D", "A5.10.1E" => "roomSensorControl.1B", "A5.10.1F" => "roomSensorControl.1F", "A5.11.01" => "lightCtrlState.01", "A5.11.02" => "tempCtrlState.01", "A5.11.03" => "shutterCtrlState.01", "A5.11.04" => "lightCtrlState.02", "A5.12.00" => "autoMeterReading.00", "A5.12.01" => "autoMeterReading.01", "A5.12.02" => "autoMeterReading.02", "A5.12.03" => "autoMeterReading.03", "A5.13.01" => "environmentApp", "A5.13.02" => "environmentApp", "A5.13.03" => "environmentApp", "A5.13.04" => "environmentApp", "A5.13.05" => "environmentApp", "A5.13.06" => "environmentApp", "A5.13.10" => "environmentApp", "A5.14.01" => "multiFuncSensor", "A5.14.02" => "multiFuncSensor", "A5.14.03" => "multiFuncSensor", "A5.14.04" => "multiFuncSensor", "A5.14.05" => "multiFuncSensor", "A5.14.06" => "multiFuncSensor", "A5.20.01" => "MD15", "A5.30.01" => "digitalInput.01", "A5.30.02" => "digitalInput.02", "A5.38.08" => "gateway", "A5.3F.7F" => "manufProfile", "D5.00.01" => "contact", "F6.04.01" => "keycard", "F6.10.00" => "windowHandle", 1 => "switch", 2 => "sensor", 3 => "vld", 4 => "FRW", 5 => "PM101", 6 => "raw", ); my @EnO_models = qw ( other FSB14 FSB61 FSB70 FSM12 FSM61 FT55 FTS12 ); # Initialize sub EnOcean_Initialize($) { my ($hash) = @_; my %subTypeList; my %subTypeSetList; $hash->{Match} = "^EnOcean:"; $hash->{DefFn} = "EnOcean_Define"; $hash->{UndefFn} = "EnOcean_Undef"; $hash->{ParseFn} = "EnOcean_Parse"; $hash->{SetFn} = "EnOcean_Set"; $hash->{AttrList} = "IODev do_not_notify:1,0 ignore:0,1 dummy:0,1 " . "showtime:1,0 loglevel:0,1,2,3,4,5,6 " . "actualTemp angleMax:slider,-180,20,180 angleMin:slider,-180,20,180 " . "angleTime:0,1,2,3,4,5,6 destinationID dimValueOn " . "model:" . join(",", @EnO_models) . " " . "gwCmd:" . join(",", sort @EnO_gwCmd) . " " . "manufID:" . join(",", keys %EnO_manuf) . " " . "rampTime repeatingAllowed:yes,no " . "scaleDecimals:0,1,2,3,4,5,6,7,8,9 scaleMax scaleMin " . "securityLevel:unencrypted " . "shutTime shutTimeCloses subDef subDef0 subDefI " . "subType:" . join(",", sort grep { !$subTypeList{$_}++ } values %EnO_subType) . " " . "subTypeSet:" . join(",", sort grep { !$subTypeSetList{$_}++ } values %EnO_subType) . " " . "switchMode:switch,pushbutton " . "switchType:direction,universal,central " . $readingFnAttributes; for(my $i=0; $i<@EnO_ptm200btn;$i++) { $EnO_ptm200btn{$EnO_ptm200btn[$i]} = "$i:30"; } $EnO_ptm200btn{released} = "0:20"; return undef; } # Define sub EnOcean_Define($$) { my ($hash, $def) = @_; my @a = split("[ \t][ \t]*", $def); my $name = $hash->{NAME}; return "wrong syntax: define EnOcean 8-digit-hex-code" if(int(@a)!=3 || $a[2] !~ m/^[A-F0-9]{8}$/i); $modules{EnOcean}{defptr}{uc($a[2])} = $hash; AssignIoPort($hash); # Help FHEMWEB split up devices $attr{$name}{subType} = $1 if($name =~ m/EnO_(.*)_$a[2]/); return undef; } # Set sub EnOcean_Set($@) { my ($hash, @a) = @_; return "no set value specified" if(@a < 2); my $name = $hash->{NAME}; my $data; my $destinationID = AttrVal($name, "destinationID", undef); if (!defined $destinationID || $destinationID eq "multicast") { $destinationID = "FFFFFFFF"; } elsif ($destinationID eq "unicast") { $destinationID = $hash->{DEF}; } elsif ($destinationID !~ m/^[\dA-F]{8}$/) { return "DestinationID $destinationID wrong, choose <8-digit-hex-code>."; } my $ll2 = GetLogLevel($name, 2); my $manufID = AttrVal($name, "manufID", ""); my $model = AttrVal($name, "model", ""); my $rorg; my $sendCmd = "yes"; my $status = "00"; my $st = AttrVal($name, "subType", ""); my $stSet = AttrVal($name, "subTypeSet", undef); if (defined $stSet) {$st = $stSet;} my $subDef = AttrVal($name, "subDef", $hash->{DEF}); if ($subDef !~ m/^[\dA-F]{8}$/) {return "SenderID $subDef wrong, choose <8-digit-hex-code>.";} my $switchMode = AttrVal($name, "switchMode", "switch"); my $tn = TimeNow(); my $updateState = 1; shift @a; for(my $i = 0; $i < @a; $i++) { my $cmd = $a[$i]; if($st eq "MD15") { # Battery Powered Actuator (EEP A5-20-01) # [Kieback&Peter MD15-FTL-xx] # See also http://www.oscat.de/community/index.php/topic,985.30.html # Maintenance commands (runInit, liftSet, valveOpen, valveClosed) $rorg = "A5"; my %sets = ( "desired-temp" => "\\d+(\\.\\d)?", "actuator" => "\\d+", "unattended" => "", "initialize" => "", "runInit" => "", "liftSet" => "", "valveOpen" => "", "valveClosed" => "", ); my $re = $sets{$a[0]}; return "Unknown argument $cmd, choose one of ".join(" ", sort keys %sets) if(!defined($re)); return "Need a parameter" if ($re && @a < 2); return "Argument $a[1] is incorrect (expect $re)" if ($re && $a[1] !~ m/^$re$/); $hash->{CMD} = $cmd; $hash->{READINGS}{CMD}{TIME} = $tn; $hash->{READINGS}{CMD}{VAL} = $cmd; my $arg = "true"; if($re) { $arg = $a[1]; shift(@a); } $hash->{READINGS}{$cmd}{TIME} = $tn; $hash->{READINGS}{$cmd}{VAL} = $arg; } elsif ($st eq "gateway") { # Gateway (EEP A5-38-08) # select Command from attribute gwCmd or command line my $gwCmd = AttrVal($name, "gwCmd", undef); if ($gwCmd && $EnO_gwCmd{$gwCmd}) { # command from attribute gwCmd if ($EnO_gwCmd{$cmd}) { # shift $cmd $cmd = $a[1]; shift(@a); } } elsif ($EnO_gwCmd{$cmd}) { # command from command line $gwCmd = $cmd; $cmd = $a[1]; shift(@a); } else { return "Unknown Gateway command " . $cmd . ", choose one of " . join(" ", sort keys %EnO_gwCmd); } my $gwCmdID; $rorg = "A5"; my $setCmd = 0; my $time = 0; if ($gwCmd eq "switching") { # Switching $gwCmdID = 1; if($cmd eq "teach") { $data = sprintf "%02X000000", $gwCmdID; } elsif ($cmd eq "on" || $cmd eq "B0") { $setCmd = 9; if ($a[1]) { return "Usage: $cmd [lock|unlock]" if (($a[1] ne "lock") && ($a[1] ne "unlock")); $setCmd = $setCmd | 4 if ($a[1] eq "lock"); shift(@a); } $updateState = 0; $data = sprintf "%02X%04X%02X", $gwCmdID, $time, $setCmd; } elsif ($cmd eq "off" || $cmd eq "BI") { $setCmd = 8; if ($a[1]) { return "Usage: $cmd [lock|unlock]" if (($a[1] ne "lock") && ($a[1] ne "unlock")); $setCmd = $setCmd | 4 if ($a[1] eq "lock"); shift(@a); } $updateState = 0; $data = sprintf "%02X%04X%02X", $gwCmdID, $time, $setCmd; } else { my $cmdList = "B0 BI teach"; return SetExtensions ($hash, $cmdList, $name, @a); $updateState = 0; $data = sprintf "%02X%04X%02X", $gwCmdID, $time, $setCmd; } } elsif ($gwCmd eq "dimming") { # Dimming $gwCmdID = 2; my $dimVal = $hash->{READINGS}{dimValue}{VAL}; my $rampTime = AttrVal($name, "rampTime", 1); my $sendDimCmd = 0; $setCmd = 9; if ($cmd eq "teach") { $setCmd = 0; $data = sprintf "%02X000000", $gwCmdID; } elsif ($cmd eq "dim") { return "Usage: $cmd dim/% [rampTime/s lock|unlock]" if(@a < 2 || $a[1] < 0 || $a[1] > 100 || $a[1] !~ m/^[+-]?\d+$/); # for eltako relative (0-100) (but not compliant to EEP because DB0.2 is 0) # >> if manufID needed: set DB2.0 $dimVal = $a[1]; if ($dimVal > 0) { readingsSingleUpdate ($hash, "dimValueStored", $dimVal, 1); } shift(@a); if (defined($a[1])) { return "Usage: $cmd dim/% [rampTime/s lock|unlock]" if ($a[1] !~ m/^[+-]?\d+$/); $rampTime = $a[1]; shift(@a); } $sendDimCmd = 1; } elsif ($cmd eq "dimup") { return "Usage: $cmd dim/% [rampTime/s lock|unlock]" if(@a < 2 || $a[1] < 0 || $a[1] > 100 || $a[1] !~ m/^[+-]?\d+$/); $dimVal += $a[1]; if ($dimVal > 0) { readingsSingleUpdate ($hash, "dimValueStored", $dimVal, 1); } shift(@a); if (defined($a[1])) { return "Usage: $cmd dim/% [rampTime/s lock|unlock]" if ($a[1] !~ m/^[+-]?\d+$/); $rampTime = $a[1]; shift(@a); } $sendDimCmd = 1; } elsif ($cmd eq "dimdown") { return "Usage: $cmd dim/% [rampTime/s lock|unlock]" if(@a < 2 || $a[1] < 0 || $a[1] > 100 || $a[1] !~ m/^[+-]?\d+$/); $dimVal -= $a[1]; if ($dimVal > 0) { readingsSingleUpdate ($hash, "dimValueStored", $dimVal, 1); } shift(@a); if (defined($a[1])) { return "Usage: $cmd dim/% [rampTime/s lock|unlock]" if ($a[1] !~ m/^[+-]?\d+$/); $rampTime = $a[1]; shift(@a); } $sendDimCmd = 1; } elsif ($cmd eq "on" || $cmd eq "B0") { $rampTime = 1; my $dimValueOn = AttrVal($name, "dimValueOn", 100); if ($dimValueOn eq "stored") { $dimVal = ReadingsVal($name, "dimValueStored", 100); if ($dimVal < 1) { $dimVal = 100; readingsSingleUpdate ($hash, "dimValueStored", $dimVal, 1); } } elsif ($dimValueOn eq "last") { $dimVal = ReadingsVal ($name, "dimValueLast", 100); if ($dimVal < 1) { $dimVal = 100; } } else { if ($dimValueOn !~ m/^[+-]?\d+$/) { $dimVal = 100; } elsif ($dimValueOn > 100) { $dimVal = 100; } elsif ($dimValueOn < 1) { $dimVal = 1; } else { $dimVal = $dimValueOn; } } $sendDimCmd = 1 } elsif ($cmd eq "off" || $cmd eq "BI") { $dimVal = 0; $rampTime = 1; $setCmd = 8; $sendDimCmd = 1; } else { my $cmdList = "dim:slider,0,1,100 B0 BI teach"; return SetExtensions ($hash, $cmdList, $name, @a); } if($sendDimCmd) { if (defined $a[1]) { return "Usage: $cmd dim/% [rampTime/s lock|unlock]" if (($a[1] ne "lock") && ($a[1] ne "unlock")); if ($manufID eq "OOD") { # Eltako devices: block dimming value if ($a[1] eq "lock") { $setCmd = $setCmd | 4; } } else { # Dimming value relative $setCmd = $setCmd | 4; } shift(@a); } else { if ($manufID ne "OOD") { $setCmd = $setCmd | 4; } } if ($dimVal > 100) { $dimVal = 100; } if ($dimVal <= 0) { $dimVal = 0; $setCmd = 8; } if ($rampTime > 255) { $rampTime = 255; } if ($rampTime < 0) { $rampTime = 0; } $updateState = 0; $data = sprintf "%02X%02X%02X%02X", $gwCmdID, $dimVal, $rampTime, $setCmd; } } elsif ($gwCmd eq "setpointShift") { $gwCmdID = 3; if ($cmd eq "teach") { $data = sprintf "%02X000000", $gwCmdID; } elsif ($cmd eq "shift") { if (($a[1] =~ m/^[+-]?\d+(\.\d+)?$/) && ($a[1] >= -12.7) && ($a[1] <= 12.8)) { $updateState = 0; $data = sprintf "%02X00%02X08", $gwCmdID, ($a[1] + 12.7) * 10; shift(@a); } else { return "Usage: $a[1] is not numeric or out of range"; } } else { return "Unknown argument $cmd, choose one of teach shift"; } } elsif ($gwCmd eq "setpointBasic") { $gwCmdID = 4; if($cmd eq "teach") { $data = sprintf "A5%02X000000", $gwCmdID; } elsif ($cmd eq "basic") { if (($a[1] =~ m/^[+-]?\d+(\.\d+)?$/) && ($a[1] >= 0) && ($a[1] <= 51.2)) { $updateState = 0; $data = sprintf "%02X00%02X08", $gwCmdID, $a[1] * 5; shift(@a); } else { return "Usage: $cmd parameter is not numeric or out of range."; } } else { return "Unknown argument $cmd, choose one of teach basic"; } } elsif ($gwCmd eq "controlVar") { $gwCmdID = 5; my $controlVar = ReadingsVal($name, "controlVar", 0); if($cmd eq "teach") { $data = printf "A5%02X000000", $gwCmdID; } elsif ($cmd eq "presence") { if ($a[1] eq "standby") { $setCmd = 0x0A; } elsif ($a[1] eq "absent") { $setCmd = 9; } elsif ($a[1] eq "present") { $setCmd = 8; } else { return "Usage: $cmd parameter unknown."; } shift(@a); $data = sprintf "%02X00%02X%02X", $gwCmdID, $controlVar, $setCmd; } elsif ($cmd eq "energyHoldOff") { if ($a[1] eq "normal") { $setCmd = 8; } elsif ($a[1] eq "holdoff") { $setCmd = 0x0C; } else { return "Usage: $cmd parameter unknown."; } shift(@a); $data = sprintf "%02X00%02X%02X", $gwCmdID, $controlVar, $setCmd; } elsif ($cmd eq "controllerMode") { if ($a[1] eq "auto") { $setCmd = 8; } elsif ($a[1] eq "heating") { $setCmd = 0x28; } elsif ($a[1] eq "cooling") { $setCmd = 0x48; } elsif ($a[1] eq "off" || $a[1] eq "BI") { $setCmd = 0x68; } else { return "Usage: $cmd parameter unknown."; } shift(@a); $data = sprintf "%02X00%02X%02X", $gwCmdID, $controlVar, $setCmd; } elsif ($cmd eq "controllerState") { if ($a[1] eq "auto") { $setCmd = 8; } elsif ($a[1] eq "override") { $setCmd = 0x18; if (defined $a[2] && ($a[2] =~ m/^[+-]?\d+$/) && ($a[2] >= 0) && ($a[2] <= 100) ) { $controlVar = $a[2] * 255; shift(@a); } else { return "Usage: Control Variable Override is not numeric or out of range."; } } else { return "Usage: $cmd parameter unknown."; } shift(@a); $updateState = 0; $data = sprintf "%02X00%02X%02X", $gwCmdID, $controlVar, $setCmd; } else { return "Unknown argument, choose one of teach presence:absent,present,standby energyHoldOff:holdoff,normal controllerMode:cooling,heating,off controllerState:auto,override"; } } elsif ($gwCmd eq "fanStage") { $gwCmdID = 6; if($cmd eq "teach") { $data = sprintf "%02X000000", $gwCmdID; } elsif ($cmd eq "stage") { if ($a[1] eq "auto") { $updateState = 0; $data = sprintf "%02X00%02X08", $gwCmdID, 255; } elsif ($a[1] && $a[1] =~ m/^[0-3]$/) { $updateState = 0; $data = sprintf "%02X00%02X08", $gwCmdID, $a[1]; } else { return "Usage: $cmd parameter is not numeric or out of range" } shift(@a); } else { return "Unknown argument, choose one of teach stage:auto,0,1,2,3"; } } elsif ($gwCmd eq "blindCmd") { $gwCmdID = 7; my %blindFunc = ( "status" => 0, "stop" => 1, "opens" => 2, "closes" => 3, "position" => 4, "up" => 5, "down" => 6, "runtimeSet" => 7, "angleSet" => 8, "positionMinMax" => 9, "angleMinMax" => 10, "positionLogic" => 11, "teach" => 255, ); my $blindFuncID; if (defined $blindFunc {$cmd}) { $blindFuncID = $blindFunc {$cmd}; } else { return "Unknown Gateway Blind Central Function " . $cmd . ", choose one of ". join(" ", sort keys %blindFunc); } my $blindParam1 = 0; my $blindParam2 = 0; $setCmd = $blindFuncID << 4 | 8; if($blindFuncID == 255) { # teach $setCmd = 0; } elsif ($blindFuncID == 0) { # status $updateState = 0; } elsif ($blindFuncID == 1) { # stop $updateState = 0; } elsif ($blindFuncID == 2) { # opens $updateState = 0; } elsif ($blindFuncID == 3) { # closes $updateState = 0; } elsif ($blindFuncID == 4) { # position if (defined $a[1] && $a[1] =~ m/^[+-]?\d+$/ && $a[1] >= 0 && $a[1] <= 100) { $blindParam1 = $a[1]; if (defined $a[2] && $a[2] =~ m/^[+-]?\d+$/ && $a[2] >= -180 && $a[2] <= 180) { $blindParam2 = abs($a[2]) / 2; if ($a[2] < 0) {$blindParam2 |= 0x80;} shift(@a); } else { return "Usage: $cmd variable is not numeric or out of range."; } shift(@a); } else { return "Usage: $cmd variable is not numeric or out of range."; } $updateState = 0; } elsif ($blindFuncID == 5 || $blindFuncID == 6) { # up / down if (defined $a[1] && $a[1] =~ m/^[+-]?\d+$/ && $a[1] >= 0 && $a[1] <= 255) { $blindParam1 = $a[1]; if (defined $a[2] && $a[2] =~ m/^[+-]?\d+(\.\d+)?$/ && $a[2] >= 0 && $a[2] <= 25.5) { $blindParam2 = $a[2] * 10; shift(@a); } else { return "Usage: $cmd variable is not numeric or out of range."; } shift(@a); } else { return "Usage: $cmd variable is not numeric or out of range."; } $updateState = 0; } elsif ($blindFuncID == 7) { # runtimeSet if (defined $a[1] && $a[1] =~ m/^[+-]?\d+$/ && $a[1] >= 0 && $a[1] <= 255) { $blindParam1 = $a[1]; if (defined $a[2] && $a[2] =~ m/^[+-]?\d+$/ && $a[2] >= 0 && $a[2] <= 255) { $blindParam2 = $a[2]; shift(@a); } else { return "Usage: $cmd variable is not numeric or out of range."; } shift(@a); } else { return "Usage: $cmd variable is not numeric or out of range."; } ## readingsSingleUpdate($hash, "runTimeUp", $blindParam1, 1); readingsSingleUpdate($hash, "runTimeDown", $blindParam2, 1); $updateState = 0; } elsif ($blindFuncID == 8) { # angleSet if (defined $a[1] && $a[1] =~ m/^[+-]?\d+(\.\d+)?$/ && $a[1] >= 0 && $a[1] <= 25.5) { $blindParam1 = $a[1] * 10; ## readingsSingleUpdate($hash, "angleTime", (sprintf "%0.1f", $a[1]), 1); shift(@a); } else { return "Usage: $cmd variable is not numeric or out of range."; } $updateState = 0; } elsif ($blindFuncID == 9) { # positionMinMax if (defined $a[1] && $a[1] =~ m/^[+-]?\d+$/ && $a[1] >= 0 && $a[1] <= 100) { $blindParam1 = $a[1]; if (defined $a[2] && $a[2] =~ m/^[+-]?\d+$/ && $a[2] >= 0 && $a[2] <= 100) { $blindParam2 = $a[2]; shift(@a); } else { return "Usage: $cmd variable is not numeric or out of range."; } shift(@a); } else { return "Usage: $cmd variable is not numeric or out of range."; } if ($blindParam1 > $blindParam2) {($blindParam1, $blindParam2) = ($blindParam2, $blindParam1);} ## readingsSingleUpdate($hash, "positionMin", $blindParam1, 1); readingsSingleUpdate($hash, "positionMax", $blindParam2, 1); $updateState = 0; } elsif ($blindFuncID == 10) { # angleMinMax if (defined $a[1] && $a[1] =~ m/^[+-]?\d+$/ && $a[1] >= -180 && $a[1] <= 180) { if (!defined $a[2] || $a[2] !~ m/^[+-]?\d+$/ || $a[2] < -180 || $a[2] > 180) { return "Usage: $cmd variable is not numeric or out of range."; } if ($a[1] > $a[2]) {($a[1], $a[2]) = ($a[2], $a[1]);} $blindParam1 = abs($a[1]) / 2; if ($a[1] < 0) {$blindParam1 |= 0x80;} $blindParam2 = abs($a[2]) / 2; if ($a[2] < 0) {$blindParam2 |= 0x80;} } else { return "Usage: $cmd variable is not numeric or out of range."; } ## readingsSingleUpdate($hash, "angleMin", $a[1], 1); readingsSingleUpdate($hash, "angleMax", $a[2], 1); splice (@a, 0, 2); $updateState = 0; } elsif ($blindFuncID == 11) { # positionLogic if ($a[1] eq "normal") { $blindParam1 = 0; } elsif ($a[1] eq "inverse") { $blindParam1 = 1; } else { return "Usage: $cmd variable is unknown."; } shift(@a); $updateState = 0; } else { } $data = sprintf "%02X%02X%02X%02X", $gwCmdID, $blindParam1, $blindParam2, $setCmd; } else { return "Unknown Gateway command " . $cmd . ", choose one of ". join(" ", sort keys %EnO_gwCmd); } Log $ll2, "EnOcean: set $name $cmd $setCmd"; } elsif ($st eq "manufProfile") { if ($manufID eq "00D") { # Eltako Shutter my $angleMax = AttrVal($name, "angleMax", 90); my $angleMin = AttrVal($name, "angleMin", -90); my $anglePos = ReadingsVal($name, "anglePos", undef); my $angleTime = AttrVal($name, "angleTime", 0); my $position = ReadingsVal($name, "position", undef); $rorg = "A5"; my $shutTime = AttrVal($name, "shutTime", 255); my $shutTimeCloses = AttrVal($name, "shutTimeCloses", $shutTime); $shutTimeCloses = $shutTime if ($shutTimeCloses < $shutTimeCloses); my $shutCmd = 0; $angleMax = 90 if ($angleMax !~ m/^[+-]?\d+$/); $angleMax = 180 if ($angleMax > 180); $angleMax = -180 if ($angleMax < -180); $angleMin = -90 if ($angleMin !~ m/^[+-]?\d+$/); $angleMin = 180 if ($angleMin > 180); $angleMin = -180 if ($angleMin < -180); ($angleMax, $angleMin) = ($angleMin, $angleMax) if ($angleMin > $angleMax); $angleMax ++ if ($angleMin == $angleMax); $angleTime = 6 if ($angleTime !~ m/^[+-]?\d+$/); $angleTime = 6 if ($angleTime > 6); $angleTime = 0 if ($angleTime < 0); $shutTime = 255 if ($shutTime !~ m/^[+-]?\d+$/); $shutTime = 255 if ($shutTime > 255); $shutTime = 1 if ($shutTime < 1); if ($cmd eq "teach") { $data = "FFF80D80"; } elsif ($cmd eq "stop") { # stop # delete readings, as they are undefined CommandDeleteReading(undef, "$name anglePos"); CommandDeleteReading(undef, "$name position"); readingsSingleUpdate($hash, "state", "stop", 1); $shutCmd = 0; } elsif ($cmd eq "opens") { # opens >> B0 $anglePos = 0; readingsSingleUpdate($hash, "anglePos", $anglePos, 1); $position = 0; readingsSingleUpdate($hash, "position", $position, 1); $shutTime = $shutTimeCloses; $shutCmd = 1; $updateState = 0; } elsif ($cmd eq "closes") { # closes >> BI $anglePos = $angleMax; readingsSingleUpdate($hash, "anglePos", $anglePos, 1); $position = 100; readingsSingleUpdate($hash, "position", $position, 1); $shutTime = $shutTimeCloses; $shutCmd = 2; $updateState = 0; } elsif ($cmd eq "up" || $cmd eq "B0") { # up if(defined $a[1]) { if ($a[1] =~ m/^[+-]?\d+$/ && $a[1] >= 0 && $a[1] <= 255) { $position -= $a[1] / $shutTime * 100; if ($angleTime) { $anglePos -= ($angleMax - $angleMin) * $shutTime / $angleTime; if ($anglePos < $angleMin) { $anglePos = $angleMin; } } else { $anglePos = $angleMin; } if ($position <= 0) { $anglePos = 0; $position = 0; } $shutTime = $a[1]; shift(@a); } else { return "Usage: $a[1] is not numeric or out of range"; } } else { $anglePos = 0; $position = 0; } readingsSingleUpdate($hash, "anglePos", sprintf("%d", $anglePos), 1); readingsSingleUpdate($hash, "position", sprintf("%d", $position), 1); $shutCmd = 1; } elsif ($cmd eq "down" || $cmd eq "BI") { # down if(defined $a[1]) { if ($a[1] =~ m/^[+-]?\d+$/ && $a[1] >= 0 && $a[1] < 255) { $position += $a[1] / $shutTime * 100; if ($angleTime) { $anglePos += ($angleMax - $angleMin) * $shutTime / $angleTime; if ($anglePos > $angleMax) { $anglePos = $angleMax; } } else { $anglePos = $angleMax; } if($position > 100) { $anglePos = $angleMax; $position = 100; } $shutTime = $a[1]; shift(@a); } else { return "Usage: $a[1] is not numeric or out of range"; } } else { $anglePos = $angleMax; $position = 100; } readingsSingleUpdate($hash, "anglePos", sprintf("%d", $anglePos), 1); readingsSingleUpdate($hash, "position", sprintf("%d", $position), 1); $shutCmd = 2; } elsif ($cmd eq "position") { if (!defined $position) { return "Position unknown, please first opens the blinds completely." } elsif ($angleTime > 0 && !defined $anglePos){ return "Slats angle position unknown, please first opens the blinds completely." } else { my $anglePosLast = $anglePos; my $shutTimeSet = $shutTime; if (defined $a[2]) { if ($a[2] =~ m/^[+-]?\d+$/ && $a[2] >= $angleMin && $a[2] <= $angleMax) { $anglePos = $a[2]; } else { return "Usage: $a[1] $a[2] is not numeric or out of range"; } splice(@a,2,1); } else { $anglePos = $angleMax; } if (defined $a[1] && $a[1] =~ m/^[+-]?\d+$/ && $a[1] >= 0 && $a[1] <= 100) { if ($position < $a[1]) { # down $angleTime = $angleTime * ($angleMax - $anglePos)/($angleMax - $angleMin); $shutTime = $shutTime * ($a[1] - $position) / 100 + $angleTime; $position = $a[1] + $angleTime / $shutTimeSet * 100; if ($position >= 100) { $position = 100; } $shutCmd = 2; if ($angleTime) { my @timerCmd = ($name, "up", $angleTime); my %par = (hash => $hash, timerCmd => \@timerCmd); InternalTimer(gettimeofday() + $shutTime + 1, "EnOcean_TimerSet", \%par, 0); } } elsif ($position > $a[1]) { # up $angleTime = $angleTime * ($anglePos - $angleMin) /($angleMax - $angleMin); $shutTime = $shutTime * ($position - $a[1]) / 100 + $angleTime; $position = $a[1] - $angleTime / $shutTimeSet * 100; if ($position <= 0) { $position = 0; $anglePos = 0; } $shutCmd = 1; if ($angleTime && $a[1] > 0) { my @timerCmd = ($name, "down", $angleTime); my %par = (hash => $hash, timerCmd => \@timerCmd); InternalTimer(gettimeofday() + $shutTime + 1, "EnOcean_TimerSet", \%par, 0); } } else { if ($anglePosLast > $anglePos) { # up $shutTime = $angleTime * ($anglePosLast - $anglePos)/($angleMax - $angleMin); $shutCmd = 1; } elsif ($anglePosLast < $anglePos) { # down $shutTime = $angleTime * ($anglePos - $anglePosLast) /($angleMax - $angleMin); $shutCmd = 2; } else { # position and slats angle ok $shutCmd = 0; } } readingsSingleUpdate($hash, "anglePos", sprintf("%d", $anglePos), 1); readingsSingleUpdate($hash, "position", sprintf("%d", $position), 1); shift(@a); } else { return "Usage: $a[1] is not numeric or out of range"; } } } else { return "Unknown argument " . $cmd . ", choose one of closes down opens position:slider,0,5,100 stop teach up" } if($shutCmd || $cmd eq "stop") { $updateState = 0; $data = sprintf "%02X%02X%02X%02X", 0, $shutTime, $shutCmd, 8; } Log $ll2, "EnOcean: set $name $cmd"; } } elsif ($st eq "contact") { # 1BS Telegram # Single Input Contact (EEP D5-00-01) $rorg = "D5"; my $setCmd; if ($cmd eq "teach") { $setCmd = 0; } elsif ($cmd eq "closed") { $setCmd = 9; } elsif ($cmd eq "open") { $setCmd = 8; } else { return "Unknown argument $cmd, choose one of open closed teach"; } $data = sprintf "%02X", $setCmd; Log $ll2, "EnOcean: set $name $cmd"; } elsif ($st eq "raw") { # sent raw data # to do: optional data if ($cmd eq "4BS"){ # 4BS Telegram if ($a[1] && $a[1] =~ m/^[\dA-F]{8}$/) { $data = $a[1]; $rorg = "A5"; } else { return "Wrong parameter, choose 4BS [status 1 Byte hex]"; } } elsif ($cmd eq "1BS") { # 1BS Telegram if ($a[1] && $a[1] =~ m/^[\dA-F]{2}$/) { $data = $a[1]; $rorg = "D5"; } else { return "Wrong parameter, choose 1BS [status 1 Byte hex]"; } } elsif ($cmd eq "RPS") { # RPS Telegram if ($a[1] && $a[1] =~ m/^[\dA-F]{2}$/) { $data = $a[1]; $rorg = "F6"; } else { return "Wrong parameter, choose RPS [status 1 Byte hex]"; } } elsif ($cmd eq "VLD") { # VLD Telegram if ($a[1] && $a[1] =~ m/^[\dA-F]{2,28}$/ && !(length($a[1]) % 2)) { $data = $a[1]; $rorg = "D2"; } else { return "Wrong parameter, choose VLD [status 1 Byte hex]"; } } elsif ($cmd eq "timer") { ### test if ($a[1] && $a[1] =~ m/^[\d]{2}$/) { $data = "09"; $rorg = "F6"; readingsSingleUpdate($hash, "test", 127, 1); readingsSingleUpdate($hash, "testScaled", EnOcean_ReadingScaled($hash, 127, 0, 255), 1) ; my @timerCmd = ($name, "RPS", "08"); my %par = (hash => $hash, timerCmd => \@timerCmd); InternalTimer(gettimeofday() + $a[1], "EnOcean_TimerSet", \%par, 0); } else { return "Wrong parameter, choose timer