There is no subType 'dimmer' and 'dimmctrl' anymore these are replaced by 'eltakoDimmer'. The commands supported are 'dimto' and 'teach' The readings which will be set are 'state' and 'value'
484 lines
14 KiB
Perl
Executable File
484 lines
14 KiB
Perl
Executable File
##############################################
|
|
# $Id$
|
|
package main;
|
|
|
|
use strict;
|
|
use warnings;
|
|
|
|
sub EnOcean_Define($$);
|
|
sub EnOcean_Initialize($);
|
|
sub EnOcean_Parse($$);
|
|
sub EnOcean_Set($@);
|
|
sub EnOcean_MD15Cmd($$$);
|
|
sub EnOcean_GetMyDeviceId($);
|
|
|
|
my %rorgname = ("F6"=>"switch", # RPS
|
|
"D5"=>"contact", # 1BS
|
|
"A5"=>"sensor", # 4BS
|
|
);
|
|
my @ptm200btn = ("AI", "A0", "BI", "B0", "CI", "C0", "DI", "D0");
|
|
my %ptm200btn;
|
|
|
|
# Some Manufacturers (e.g. Jaeger Direkt) also sell EnOcean products without an
|
|
# intry in the table below. This table is only needed for A5 category devices
|
|
my %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",
|
|
);
|
|
|
|
my %subTypes = (
|
|
"A5.20.01" => "MD15",
|
|
);
|
|
|
|
sub
|
|
EnOcean_Initialize($)
|
|
{
|
|
my ($hash) = @_;
|
|
|
|
$hash->{Match} = "^EnOcean:";
|
|
$hash->{DefFn} = "EnOcean_Define";
|
|
$hash->{ParseFn} = "EnOcean_Parse";
|
|
$hash->{SetFn} = "EnOcean_Set";
|
|
$hash->{AttrList} = "IODev do_not_notify:1,0 ignore:0,1 " .
|
|
"showtime:1,0 loglevel:0,1,2,3,4,5,6 model " .
|
|
"subType:switch,contact,sensor,windowHandle,SR04,MD15,".
|
|
"eltakoDimmer subId actualTemp";
|
|
|
|
for(my $i=0; $i<@ptm200btn;$i++) {
|
|
$ptm200btn{$ptm200btn[$i]} = "$i:30";
|
|
}
|
|
$ptm200btn{released} = "0:20";
|
|
return undef;
|
|
}
|
|
|
|
#############################
|
|
sub
|
|
EnOcean_Define($$)
|
|
{
|
|
my ($hash, $def) = @_;
|
|
my @a = split("[ \t][ \t]*", $def);
|
|
my $name = $hash->{NAME};
|
|
return "wrong syntax: define <name> 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;
|
|
}
|
|
|
|
|
|
#############################
|
|
sub
|
|
EnOcean_Set($@)
|
|
{
|
|
my ($hash, @a) = @_;
|
|
return "no set value specified" if(@a < 2);
|
|
|
|
my $name = $hash->{NAME};
|
|
my $st = AttrVal($name, "subType", "");
|
|
my $ll2 = GetLogLevel($name, 2);
|
|
|
|
shift @a;
|
|
my $tn = TimeNow();
|
|
|
|
for(my $i = 0; $i < @a; $i++) {
|
|
my $cmd = $a[$i];
|
|
|
|
#####################
|
|
# See also http://www.oscat.de/community/index.php/topic,985.30.html
|
|
if($st eq "MD15") {
|
|
my %sets = (
|
|
"desired-temp" => "\\d+(\\.\\d)?",
|
|
"actuator" => "\\d+",
|
|
"unattended" => "",
|
|
"initialize" => "",
|
|
);
|
|
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 "eltakoDimmer") {
|
|
if($cmd eq "teach") {
|
|
my $idSrc=EnOcean_GetMyDeviceId($hash);
|
|
my $data=sprintf("A502000000%s00", $idSrc);
|
|
Log $ll2, "$st.Teach: " . $data;
|
|
IOWrite($hash, "000A0001", $data); # len:000a optlen:00 pakettype:1(radio)
|
|
|
|
} elsif($cmd eq "dimto") {
|
|
return "Usage: $cmd percent [time 01-FF FF:slowest] [on/off]" if(@a<2);
|
|
my $time=0;
|
|
my $onoff=1;
|
|
# for eltako relative (0-100) (but not compliant to EEP because DB0.2 is 0)
|
|
my $dimVal=$a[1];
|
|
shift(@a);
|
|
if(defined($a[1])) { $time=$a[1]; shift(@a); }
|
|
if(defined($a[1])) { $onoff=($a[1] eq "off") ? 0 : 1; shift(@a); }
|
|
# EEP: A5/38/08 Central Command ->Typ 0x02: Dimming
|
|
my $idSrc=EnOcean_GetMyDeviceId($hash);
|
|
#my $data=sprintf("A502%02X%02X%02X%s00", $dimVal, $time, $onoff|0x08, $hash->{DEF});
|
|
my $data=sprintf("A502%02X%02X%02X%s00", $dimVal, $time, $onoff|0x08, $idSrc);
|
|
IOWrite($hash, "000A0001", $data);
|
|
Log $ll2, "$st.$cnd: " . $data;
|
|
|
|
} else {
|
|
return "Unknown argument $cmd, choose one of: teach, dimto"
|
|
|
|
}
|
|
|
|
###########################
|
|
} else { # Simulate a PTM
|
|
my ($c1,$c2) = split(",", $cmd, 2);
|
|
return "Unknown argument $cmd, choose one of " .
|
|
join(" ", sort keys %ptm200btn)
|
|
if(!defined($ptm200btn{$c1}) || ($c2 && !defined($ptm200btn{$c2})));
|
|
Log $ll2, "EnOcean: set $name $cmd";
|
|
|
|
my ($db_3, $status) = split(":", $ptm200btn{$c1}, 2);
|
|
$db_3 <<= 5;
|
|
$db_3 |= 0x10 if($c1 ne "released"); # set the pressed flag
|
|
if($c2) {
|
|
my ($d2, undef) = split(":", $ptm200btn{$c2}, 2);
|
|
$db_3 |= ($d2<<1) | 0x01;
|
|
}
|
|
IOWrite($hash, "",
|
|
sprintf("6B05%02X000000%s%s", $db_3, $hash->{DEF}, $status));
|
|
|
|
}
|
|
|
|
select(undef, undef, undef, 0.1) if($i < int(@a)-1);
|
|
}
|
|
|
|
my $cmd = join(" ", @a);
|
|
$hash->{CHANGED}[0] = $cmd;
|
|
$hash->{STATE} = $cmd;
|
|
$hash->{READINGS}{state}{TIME} = $tn;
|
|
$hash->{READINGS}{state}{VAL} = $cmd;
|
|
return undef;
|
|
}
|
|
|
|
#############################
|
|
sub
|
|
EnOcean_Parse($$)
|
|
{
|
|
my ($iohash, $msg) = @_;
|
|
my (undef,$rorg,$data,$id,$status,$odata) = split(":", $msg);
|
|
|
|
my $rorgname = $rorgname{$rorg};
|
|
if(!$rorgname) {
|
|
Log 2, "Unknown EnOcean RORG ($rorg) received from $id";
|
|
return "";
|
|
}
|
|
|
|
my $hash = $modules{EnOcean}{defptr}{$id};
|
|
if(!$hash) {
|
|
Log 3, "EnOcean Unknown device with ID $id, please define it";
|
|
return "UNDEFINED EnO_${rorgname}_$id EnOcean $id";
|
|
}
|
|
|
|
my $name = $hash->{NAME};
|
|
my $ll4 = GetLogLevel($name, 4);
|
|
Log $ll4, "$name: ORG:$rorg DATA:$data ID:$id STATUS:$status";
|
|
|
|
my @event;
|
|
#push @event, "1:rp_counter:".(hex($status)&0xf);
|
|
|
|
my $dl = length($data);
|
|
my $db_3 = hex substr($data,0,2);
|
|
my $db_2 = hex substr($data,2,2) if($dl > 2);
|
|
my $db_1 = hex substr($data,4,2) if($dl > 4);
|
|
my $db_0 = hex substr($data,6,2) if($dl > 6);
|
|
my $st = AttrVal($name, "subType", "");
|
|
|
|
#################################
|
|
# RPS: PTM200 based switch/remote or a windowHandle
|
|
if($rorg eq "F6") {
|
|
my $nu = ((hex($status)&0x10)>>4);
|
|
|
|
# unused flags (AFAIK)
|
|
#push @event, "1:T21:".((hex($status)&0x20)>>5);
|
|
#push @event, "1:NU:$nu";
|
|
|
|
if($nu) {
|
|
|
|
# Theoretically there can be a released event with some of the A0,BI
|
|
# pins set, but with the plastic cover on this wont happen.
|
|
$msg = $ptm200btn[($db_3&0xe0)>>5];
|
|
$msg .= ",".$ptm200btn[($db_3&0x0e)>>1] if($db_3 & 1);
|
|
$msg .= " released" if(!($db_3 & 0x10));
|
|
|
|
} else {
|
|
|
|
# Couldnt test
|
|
if($db_3 == 112) { # KeyCard
|
|
$msg = "keycard inserted";
|
|
|
|
# Only the windowHandle is setting these bits when nu=0
|
|
} elsif($db_3 & 0xC0) {
|
|
$msg = "closed" if($db_3 == 0xF0);
|
|
$msg = "open" if($db_3 == 0xE0);
|
|
$msg = "tilted" if($db_3 == 0xD0);
|
|
$msg = "open from tilted" if($db_3 == 0xC0);
|
|
|
|
} else {
|
|
if($st eq "keycard") {
|
|
$msg = "keycard removed";
|
|
|
|
} else {
|
|
$msg = (($db_3&0x10) ? "pressed" : "released");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
# released events are disturbing when using a remote, since it overwrites
|
|
# the "real" state immediately
|
|
my $event = "state";
|
|
$event = "buttons" if($msg =~ m/released$/);
|
|
|
|
push @event, "3:$event:$msg";
|
|
|
|
#################################
|
|
# 1BS. Only contact is defined in the EEP2.1 for 1BS
|
|
} elsif($rorg eq "D5") {
|
|
push @event, "3:state:" . ($db_3&1 ? "closed" : "open");
|
|
push @event, "3:learnBtn:on" if(!($db_3&0x8));
|
|
|
|
#################################
|
|
} elsif($rorg eq "A5") {
|
|
|
|
if(($db_0 & 0x08) == 0) {
|
|
if($db_0 & 0x80) {
|
|
my $fn = sprintf "%02x", ($db_3>>2);
|
|
my $tp = sprintf "%02X", ((($db_3&3) << 5) | ($db_2 >> 3));
|
|
my $mf = sprintf "%03X", ((($db_2&7) << 8) | $db_1);
|
|
$mf = $manuf{$mf} if($manuf{$mf});
|
|
my $m = "teach-in:class A5.$fn.$tp (manufacturer: $mf)";
|
|
Log 1, $m;
|
|
push @event, "3:$m";
|
|
my $st = "A5.$fn.$tp";
|
|
$st = $subTypes{$st} if($subTypes{$st});
|
|
$attr{$name}{subType} = $st;
|
|
|
|
if("$fn.$tp" eq "20.01" && $iohash->{pair}) { # MD15
|
|
select(undef, undef, undef, 0.1); # max 10 Seconds
|
|
EnOcean_A5Cmd($hash, "800800F0", EnOcean_GetMyDeviceId($hash));
|
|
select(undef, undef, undef, 0.5);
|
|
EnOcean_MD15Cmd($hash, $name, 128); # 128 == 20 degree C
|
|
}
|
|
|
|
} else {
|
|
push @event, "3:teach-in:no type/manuf. data transmitted";
|
|
|
|
}
|
|
|
|
} elsif($st eq "SR04") {
|
|
my ($fspeed, $temp, $present);
|
|
$fspeed = 3;
|
|
$fspeed = 2 if($db_3 >= 145);
|
|
$fspeed = 1 if($db_3 >= 165);
|
|
$fspeed = 0 if($db_3 >= 190);
|
|
$fspeed = "Auto" if($db_3 >= 210);
|
|
$temp = sprintf("%0.1f", $db_1/6.375); # 40..0
|
|
$present= $db_0&0x1 ? "no" : "yes";
|
|
|
|
push @event, "3:state:temperature $temp";
|
|
push @event, "3:set_point:$db_3";
|
|
push @event, "3:fan:$fspeed";
|
|
push @event, "3:present:$present" if($present eq "yes");
|
|
push @event, "3:learnBtn:on" if(!($db_0&0x8));
|
|
push @event, "3:T:$temp SP: $db_3 F: $fspeed P: $present";
|
|
|
|
} elsif($st eq "MD15") {
|
|
push @event, "3:state:$db_3 %";
|
|
push @event, "3:currentValue:$db_3";
|
|
push @event, "3:serviceOn:" . (($db_2 & 0x80) ? "yes" : "no");
|
|
push @event, "3:energyInput:" . (($db_2 & 0x40) ? "enabled":"disabled");
|
|
push @event, "3:energyStorage:". (($db_2 & 0x20) ? "charged":"empty");
|
|
push @event, "3:battery:" . (($db_2 & 0x10) ? "ok" : "empty");
|
|
push @event, "3:cover:" . (($db_2 & 0x08) ? "open" : "closed");
|
|
push @event, "3:tempSensor:" . (($db_2 & 0x04) ? "failed" : "ok");
|
|
push @event, "3:window:" . (($db_2 & 0x02) ? "open" : "closed");
|
|
push @event, "3:actuatorStatus:".(($db_2 & 0x01) ? "obstructed" : "ok");
|
|
push @event, "3:measured-temp:". sprintf "%.1f", ($db_1*40/255);
|
|
EnOcean_MD15Cmd($hash, $name, $db_1);
|
|
|
|
} elsif($st eq "PM101") {
|
|
####################################
|
|
# Ratio Presence Sensor Eagle PM101, code by aicgazi
|
|
####################################
|
|
my $lux = sprintf "%3d", $db_2;
|
|
# content of $db_2 is the illuminance where max value 0xFF stands for 1000 lx
|
|
$lux = sprintf "%04.2f", ( $lux * 1000 / 255 ) ;
|
|
push @event, "3:brightness:$lux";
|
|
push @event, "3:channel1:" . ($db_0 & 0x01 ? "off" : "on");
|
|
push @event, "3:channel2:" . ($db_0 & 0x02 ? "off" : "on");
|
|
|
|
} elsif($st eq "eltakoDimmer") {
|
|
# response command from (Eltako-)Actor ( Central-Command:A5/38/08 )
|
|
if($db_3 eq 0x01) { # switch
|
|
push @event, "3:state:" . (($db_0 & 0x01) ? "on": "off");
|
|
push @event, "3:time:" . ($db_2<<8 + $db_1);
|
|
push @event, "3:timeType:" . (($db_0 & 0x02) ? "delay": "duration");
|
|
|
|
} elsif($db_3 eq 0x02) { # dimm
|
|
push @event, "3:state:" . (($db_0 & 0x01) ? "on": "off");
|
|
push @event, "3:value:$db_2";
|
|
|
|
} elsif($db_3 eq 0x03) { # setpoint-switch, todo
|
|
} elsif($db_3 eq 0x04) { # basic setpoint, todo
|
|
} elsif($db_3 eq 0x05) { # control-variable, todo
|
|
} elsif($db_3 eq 0x06) { # fan-stage, todo
|
|
}
|
|
|
|
} else {
|
|
push @event, "3:state:$db_3";
|
|
push @event, "3:sensor1:$db_3";
|
|
push @event, "3:sensor2:$db_2";
|
|
push @event, "3:sensor3:$db_1";
|
|
push @event, "3:D3:".(($db_0&0x8)?1:0);
|
|
push @event, "3:D2:".(($db_0&0x4)?1:0);
|
|
push @event, "3:D1:".(($db_0&0x2)?1:0);
|
|
push @event, "3:D0:".(($db_0&0x1)?1:0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
# Flag & 1: reading
|
|
# Flag & 2: changed
|
|
|
|
my $tn = TimeNow();
|
|
my @changed;
|
|
for(my $i = 0; $i < int(@event); $i++) {
|
|
my ($flag, $vn, $vv) = split(":", $event[$i], 3);
|
|
|
|
if($flag & 2) {
|
|
if($vn eq "state") {
|
|
$hash->{STATE} = $vv;
|
|
push @changed, $vv;
|
|
|
|
} else {
|
|
push @changed, "$vn: $vv";
|
|
|
|
}
|
|
}
|
|
|
|
if($flag & 1) {
|
|
$hash->{READINGS}{$vn}{TIME} = TimeNow();
|
|
$hash->{READINGS}{$vn}{VAL} = $vv;
|
|
}
|
|
}
|
|
$hash->{CHANGED} = \@changed;
|
|
|
|
return $name;
|
|
}
|
|
|
|
sub
|
|
EnOcean_MD15Cmd($$$)
|
|
{
|
|
my ($hash, $name, $db_1) = @_;
|
|
my $cmd = ReadingsVal($name, "CMD", undef);
|
|
if($cmd) {
|
|
my $msg; # Unattended
|
|
my $arg1 = ReadingsVal($name, $cmd, 0); # Command-Argument
|
|
|
|
if($cmd eq "actuator") {
|
|
$msg = sprintf("%02X000000", $arg1);
|
|
|
|
} elsif($cmd eq "desired-temp") {
|
|
$msg = sprintf("%02X%02X0400", $arg1*255/40,
|
|
AttrVal($name, "actualTemp", ($db_1*40/255)) * 255/40);
|
|
|
|
} elsif($cmd eq "initialize") {
|
|
$msg = sprintf("00006400");
|
|
|
|
}
|
|
|
|
if($msg) {
|
|
select(undef, undef, undef, 0.2);
|
|
EnOcean_A5Cmd($hash, $msg, EnOcean_GetMyDeviceId($hash));
|
|
if($cmd eq "initialize") {
|
|
delete($defs{$name}{READINGS}{CMD});
|
|
delete($defs{$name}{READINGS}{$cmd});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
sub
|
|
EnOcean_A5Cmd($$$)
|
|
{
|
|
my ($hash, $msg, $org) = @_;
|
|
IOWrite($hash, "000A0701", # varLen=0A optLen=07 msgType=01=radio,
|
|
sprintf("A5%s%s0001%sFF00",$msg,$org,$hash->{DEF}));
|
|
# type=A5 msg:4 senderId:4 status=00 subTelNum=01 destId:4 dBm=FF Security=00
|
|
}
|
|
|
|
# ---------------------------------------------
|
|
|
|
#
|
|
# compose the Src-Id for a command to send
|
|
#
|
|
sub EnOcean_GetMyDeviceId($)
|
|
{
|
|
my ($hash) = @_;
|
|
my $myId=0; # default: use Device-ID of EUL
|
|
my $name = $hash->{NAME};
|
|
my $baseId=hex($hash->{IODev}{BASEID}) if(defined $hash->{IODev}{BASEID});
|
|
my $subId = AttrVal($name, "subId", "");
|
|
if(defined $baseId and defined $subId) {
|
|
# if there is a base-id an a subid -> use this combination
|
|
$myId=sprintf("%08X", $baseId | $subId);
|
|
}
|
|
return $myId;
|
|
}
|
|
|
|
|
|
1;
|