From 5e9f17e51b61b3346b0cf2b9cf922f363ac4c582 Mon Sep 17 00:00:00 2001 From: mgehre <> Date: Fri, 15 Feb 2013 03:20:39 +0000 Subject: [PATCH] MAX: Rewrite faking of WallThermostat/ShutterContact git-svn-id: https://svn.fhem.de/fhem/trunk@2731 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/FHEM/10_MAX.pm | 35 ++++++++++++---- fhem/FHEM/14_CUL_MAX.pm | 90 ++++++++++++++++++++++++++++++++++++----- 2 files changed, 109 insertions(+), 16 deletions(-) diff --git a/fhem/FHEM/10_MAX.pm b/fhem/FHEM/10_MAX.pm index fb076d744..4684f2570 100755 --- a/fhem/FHEM/10_MAX.pm +++ b/fhem/FHEM/10_MAX.pm @@ -87,6 +87,7 @@ MAX_Define($$) my ($hash, $def) = @_; my @a = split("[ \t][ \t]*", $def); my $name = $hash->{NAME}; + return "name \"$name\" is reserved for internal use" if($name eq "fakeWallThermostat" or $name eq "fakeShutterContact"); return "wrong syntax: define MAX addr" if(int(@a)!=4 || $a[3] !~ m/^[A-F0-9]{6}$/i); @@ -368,14 +369,30 @@ MAX_Set($@) } elsif($setting ~~ ["associate", "deassociate"]) { my $dest = $args[0]; - if(exists($defs{$dest})) { - return "Destination is not a MAX device" if($defs{$dest}{TYPE} ne "MAX"); - $dest = $defs{$dest}{addr}; + my $destType; + if($dest eq "fakeWallThermostat") { + return "IODev is not CUL_MAX" if($hash->{IODev}->{TYPE} ne "CUL_MAX"); + $dest = AttrVal($hash->{IODev}->{NAME}, "fakeWTaddr", "111111"); + return "Invalid fakeWTaddr attribute set (must not be 000000)" if($dest eq "000000"); + $destType = MAX_TypeToTypeId("WallMountedThermostat"); + + } elsif($dest eq "fakeShutterContact") { + return "IODev is not CUL_MAX" if($hash->{IODev}->{TYPE} ne "CUL_MAX"); + $dest = AttrVal($hash->{IODev}->{NAME}, "fakeSCaddr", "222222"); + return "Invalid fakeSCaddr attribute set (must not be 000000)" if($dest eq "000000"); + $destType = MAX_TypeToTypeId("ShutterContact"); + } else { - return "No MAX device with address $dest" if(!exists($modules{MAX}{defptr}{$dest})); + if(exists($defs{$dest})) { + return "Destination is not a MAX device" if($defs{$dest}{TYPE} ne "MAX"); + $dest = $defs{$dest}{addr}; + } else { + return "No MAX device with address $dest" if(!exists($modules{MAX}{defptr}{$dest})); + } + my $destType = MAX_TypeToTypeId($modules{MAX}{defptr}{$dest}{type}); + Log 2, "Warning: Device do not have same groupid" if($hash->{groupid} != $modules{MAX}{defptr}{$dest}{groupid}); } - my $destType = MAX_TypeToTypeId($modules{MAX}{defptr}{$dest}{type}); - Log 2, "Warning: Device do not have same groupid" if($hash->{groupid} != $modules{MAX}{defptr}{$dest}{groupid}); + Log GetLogLevel($hash->{NAME}, 5), "Using dest $dest, destType $destType"; if($setting eq "associate") { return ($hash->{IODev}{Send})->($hash->{IODev},"AddLinkPartner",$hash->{addr},sprintf("%s%02x", $dest, $destType)); @@ -446,7 +463,11 @@ MAX_Set($@) my $assoclist; #Build list of devices which this device can be associated to if($hash->{type} =~ /HeatingThermostat.*/) { - $assoclist = join(",", map { defined($_->{type}) && $_->{type} ~~ ["HeatingThermostat", "HeatingThermostatPlus", "WallMountedThermostat", "ShutterContact"] ? $_->{NAME} : () } values %{$modules{MAX}{defptr}}); + $assoclist = join(",", map { defined($_->{type}) && $_->{type} ~~ ["HeatingThermostat", "HeatingThermostatPlus", "WallMountedThermostat", "ShutterContact"] && $_ != $hash ? $_->{NAME} : () } values %{$modules{MAX}{defptr}}); + if($hash->{IODev}->{TYPE} eq "CUL_MAX") { + $assoclist .= "," if(length($assoclist)); + $assoclist .= "fakeWallThermostat,fakeShutterContact"; + } } elsif($hash->{type} ~~ ["ShutterContact", "WallMountedThermostat"]) { $assoclist = join(",", map { defined($_->{type}) && $_->{type} =~ /HeatingThermostat.*/ ? $_->{NAME} : () } values %{$modules{MAX}{defptr}}); } diff --git a/fhem/FHEM/14_CUL_MAX.pm b/fhem/FHEM/14_CUL_MAX.pm index 9b756a30e..cc5c3cb2d 100644 --- a/fhem/FHEM/14_CUL_MAX.pm +++ b/fhem/FHEM/14_CUL_MAX.pm @@ -38,6 +38,7 @@ CUL_MAX_Initialize($) $hash->{UndefFn} = "CUL_MAX_Undef"; $hash->{ParseFn} = "CUL_MAX_Parse"; $hash->{SetFn} = "CUL_MAX_Set"; + $hash->{AttrFn} = "CUL_MAX_Attr"; $hash->{AttrList} = "IODev do_not_notify:1,0 ignore:0,1 " . "showtime:1,0 loglevel:0,1,2,3,4,5,6 ". $readingFnAttributes; @@ -66,12 +67,18 @@ CUL_MAX_Define($$) $hash->{sendQueue} = []; AssignIoPort($hash); - if(CUL_MAX_Check($hash)) { + if(CUL_MAX_Check($hash) >= 152) { #Doing this on older firmware disables MAX mode IOWrite($hash, "", "Za". $hash->{addr}); #Append to initString, so this is resend if cul disappears and then reappears $hash->{IODev}{initString} .= "\nZa". $hash->{addr}; } + if(CUL_MAX_Check($hash) >= 153) { + #Doing this on older firmware disables MAX mode + my $cmd = "Zw". CUL_MAX_fakeWTaddr($hash); + IOWrite($hash, "", $cmd); + $hash->{IODev}{initString} .= "\n".$cmd; + } #This interface is shared with 00_MAXLAN.pm $hash->{Send} = \&CUL_MAX_Send; @@ -111,11 +118,33 @@ CUL_MAX_Check($@) #Looks like "V 1.49 CUL868" $version =~ m/V (.*)\.(.*) .*/; my ($major_version,$minorversion) = ($1, $2); - if($major_version == 1 and $minorversion < 52) { - Log 2, "The current firmware of the CUL has known bugs with respect to MAX! support. Please update."; - return 0; + my $version = 100*$major_version + $minorversion; + if($version < 153) { + Log 2, "You are using an old version of the CUL firmware, which has known bugs with respect to MAX! support. Please update."; } - return 1; + return $version; +} + +sub +CUL_MAX_Attr(@) +{ + my ($hash, $action, $name, $attr, $value) = @_; + if ($action eq "set") { + return "No such attribute" if($attr ne "fakeWTaddr" && $attr ne "fakeSCaddr"); + return "Invalid value" if($value !~ /^[0-9a-fA-F]{6}$/); + } +} + +sub +CUL_MAX_fakeWTaddr($) +{ + return AttrVal(@_[0]->{NAME}, "fakeWTaddr", "111111"); +} + +sub +CUL_MAX_fakeSCaddr($) +{ + return AttrVal(@_[0]->{NAME}, "fakeSCaddr", "222222"); } sub @@ -132,6 +161,41 @@ CUL_MAX_Set($@) } elsif($setting eq "broadcastTime") { CUL_MAX_BroadcastTime($hash, 1); + } elsif($setting ~~ ["fakeSC", "fakeWT"]) { + return "Invalid number of arguments" if(@args == 0); + my $dest = $args[0]; + #$dest may be either a name or an address + if(exists($defs{$dest})) { + return "Destination is not a MAX device" if($defs{$dest}{TYPE} ne "MAX"); + $dest = $defs{$dest}{addr}; + } else { + return "No MAX device with address $dest" if(!exists($modules{MAX}{defptr}{$dest})); + } + + if($setting eq "fakeSC") { + return "Invalid number of arguments" if(@args != 2); + return "Invalid fakeSCaddr attribute set (must not be 000000)" if(CUL_MAX_fakeSCaddr($hash) eq "000000"); + + my $state = $args[1] ? "12" : "10"; + return CUL_MAX_Send($hash, "ShutterContactState",$dest,$state, flags => "06", src => CUL_MAX_fakeSCaddr($hash)); + + } elsif($setting eq "fakeWT") { + return "Invalid number of arguments" if(@args != 3); + return "desiredTemperature is invalid" if($args[1] < 4.5 || $args[2] > 30.5); + return "Invalid fakeWTaddr attribute set (must not be 000000)" if(CUL_MAX_fakeWTaddr($hash) eq "000000"); + + $args[2] = 0 if($args[2] < 0); #Clamp temperature to minimum of 0 degree + + #Encode into binary form + my $arg2 = int(10*$args[2]); + #First bit is 9th bit of temperature, rest is desiredTemperature + my $arg1 = (($arg2&0x100)>>1) | (int(2*$args[1])&0x7F); + $arg2 &= 0xFF; #only take the lower 8 bits + + return CUL_MAX_Send($hash,"WallThermostatControl",$dest, + sprintf("%02x%02x",$arg1,$arg2),flags => "04", src => CUL_MAX_fakeWTaddr($hash)); + } + } else { return "Unknown argument $setting, choose one of pairmode broadcastTime"; } @@ -172,16 +236,20 @@ CUL_MAX_Parse($$) if(exists($msgId2Cmd{$msgTypeRaw})) { if($msgType eq "Ack") { - return $shash->{NAME} if($src eq $shash->{addr}); #Ignore packets generated by culfw's auto-Ack + #Ignore packets generated by culfw's auto-Ack + return $shash->{NAME} if($src eq $shash->{addr}); + return $shash->{NAME} if($src eq CUL_MAX_fakeWTaddr($hash)); + return $shash->{NAME} if($src eq CUL_MAX_fakeSCaddr($hash)); + Dispatch($shash, "MAX,$isToMe,Ack,$src,$payload", {RAWMSG => $rmsg}); - return $shash->{NAME} if(!$isToMe); return $shash->{NAME} if(!@{$shash->{sendQueue}}); #we are not waiting for any Ack my $packet = $shash->{sendQueue}[0]; - if($packet->{dst} eq $src and $packet->{cnt} == hex($msgcnt)) { + if($packet->{src} eq $dst and $packet->{dst} eq $src and $packet->{cnt} == hex($msgcnt)) { Log $ll5, "Got matching ack"; - $packet->{sent} = 2; + my $isnak = unpack("C",pack("H*",$payload)) & 0x80; + $packet->{sent} = $isnak ? 3 : 2; return $shash->{NAME}; } @@ -273,6 +341,7 @@ CUL_MAX_Send(@) my $timeout = gettimeofday()+$ackTimeout; my $aref = $hash->{sendQueue}; push(@{$aref}, { "packet" => $packet, + "src" => $src, "dst" => $dst, "cnt" => hex($msgcnt), "time" => $timeout, @@ -348,6 +417,9 @@ CUL_MAX_SendQueueHandler($) Dispatch($hash, "MAX,1,Ack$packet->{cmd},$packet->{dst},$packet->{callbackParam}", {RAWMSG => ""}); } splice @{$hash->{sendQueue}}, 0, 1; #Remove from array + + } elsif( $packet->{sent} == 3 ) { #Got nack + splice @{$hash->{sendQueue}}, 0, 1; #Remove from array } return if(!@{$hash->{sendQueue}}); #everything done