############################################## # $Id$ # Written by Matthias Gehre, M.Gehre@gmx.de, 2012 package main; use strict; use warnings; use MIME::Base64; use Data::Dumper; sub MAX_Define($$); sub MAX_Initialize($); sub MAX_Parse($$); sub MAX_Set($@); sub MAX_MD15Cmd($$$); sub MAX_DateTime2Internal($); my @ctrl_modes = ( "auto", "manual", "temporary" ); my %interfaces = ( "Cube" => undef, "HeatingThermostat" => "thermostat;battery;temperature", "HeatingThermostatPlus" => "thermostat;battery;temperature", "WallMountedThermostat" => "thermostat;temperature;battery", "ShutterContact" => "switch_active;battery", "PushButton" => "switch_passive;battery" ); sub MAX_Initialize($) { my ($hash) = @_; Log 5, "Calling MAX_Initialize"; $hash->{Match} = "^MAX"; $hash->{DefFn} = "MAX_Define"; $hash->{ParseFn} = "MAX_Parse"; $hash->{SetFn} = "MAX_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 event-on-update-reading event-on-change-reading"; return undef; } ############################# sub MAX_Define($$) { my ($hash, $def) = @_; my @a = split("[ \t][ \t]*", $def); my $name = $hash->{NAME}; return "wrong syntax: define MAX addr" if(int(@a)!=4 || $a[3] !~ m/^[A-F0-9]{6}$/i); my $type = $a[2]; my $addr = $a[3]; Log 5, "Max_define $type with addr $addr "; $hash->{type} = $type; $hash->{addr} = $addr; $hash->{STATE} = "waiting for data"; $modules{MAX}{defptr}{$addr} = $hash; $hash->{internals}{interfaces} = $interfaces{$type}; AssignIoPort($hash); return undef; } sub MAX_DateTime2Internal($) { my($day, $month, $year, $hour, $min) = ($_[0] =~ /^(\d{2}).(\d{2})\.(\d{4}) (\d{2}):(\d{2})$/); return (($month&0xE) << 20) | ($day << 16) | (($month&1) << 15) | (($year-2000) << 8) | ($hour*2 + int($min/30)); } ############################# sub MAX_Set($@) { my ($hash, $devname, @a) = @_; my ($setting, @args) = @a; return "Cannot set without IODev" if(!exists($hash->{IODev})); if($setting eq "desiredTemperature"){ return "can only set desiredTemperature for HeatingThermostat" if($hash->{type} ne "HeatingThermostat"); return "missing a value" if(@args == 0); my $temperature; my $until = undef; my $ctrlmode = 1; #0=auto, 1=manual; 2=temporary if($args[0] eq "auto") { #This enables the automatic/schedule mode where the thermostat follows the weekly program $temperature = 0; $ctrlmode = 0; #auto #TODO: auto mode with temperature is also possible } elsif($args[0] eq "eco") { $temperature = $hash->{ecoTemperature}; } elsif($args[0] eq "comfort") { $temperature = $hash->{comfortTemperature}; } elsif($args[0] eq "on") { $temperature = 30.5; } elsif($args[0] eq "off") { $temperature = 4.5; }else{ $temperature = $args[0]; } if(@args > 1 and $args[1] eq "until" and $ctrlmode == 1) { $ctrlmode = 2; #temporary $until = sprintf("%06x",MAX_DateTime2Internal($args[2]." ".$args[3])); } my $groupid = $hash->{groupid}; $groupid = 0; #comment this line to control the whole group, no only one device $temperature = int($temperature*2.0) | ($ctrlmode << 6); #convert to internal representation my $payload; if(defined($until)) { $payload = pack("CCCCCCH6CCH6",0x00,$groupid?0x04:0,0x40,0x00,0x00,0x00,$hash->{addr},$groupid,$temperature,$until); }else{ $payload = pack("CCCCCCH6CC" ,0x00,$groupid?0x04:0,0x40,0x00,0x00,0x00,$hash->{addr},$groupid,$temperature); } return ($hash->{IODev}{SendDeviceCmd})->($hash->{IODev},$payload); }elsif($setting eq "groupid"){ return "argument needed" if(@args == 0); return ($hash->{IODev}{SendDeviceCmd})->($hash->{IODev},pack("CCCCCCH6CC",0x00,0x00,34,0x00,0x00,0x00,$hash->{addr},0x00,$args[0])); }elsif( $setting ~~ ["ecoTemperature", "comfortTemperature", "temperatureOffset", "maximumTemperature", "minimumTemperature", "windowOpenTemperature", "windowOpenDuration" ]) { return "can only set configuration for HeatingThermostat" if($hash->{type} ne "HeatingThermostat"); return "Invalid comfortTemperature" if(!exists($hash->{comfortTemperature}) or $hash->{comfortTemperature} < 4.5 or $hash->{comfortTemperature} > 30.5); return "Invalid ecoTemperature" if(!exists($hash->{ecoTemperature}) or $hash->{ecoTemperature} < 4.5 or $hash->{ecoTemperature} > 30.5); return "Invalid maximumTemperature" if(!exists($hash->{maximumTemperature}) or $hash->{maximumTemperature} < 4.5 or $hash->{maximumTemperature} > 30.5); return "Invalid minimumTemperature" if(!exists($hash->{minimumTemperature}) or $hash->{minimumTemperature} < 4.5 or $hash->{minimumTemperature} > 30.5); return "Invalid windowOpenTemperature" if(!exists($hash->{windowOpenTemperature}) or $hash->{windowOpenTemperature} < 4.5 or $hash->{windowOpenTemperature} > 30.5); return "Invalid temperatureOffset" if(!exists($hash->{temperatureOffset}) or $hash->{temperatureOffset} < -3.5 or $hash->{temperatureOffset} > 3.5); return "Invalid windowOpenDuration" if(!exists($hash->{windowOpenDuration}) or $hash->{windowOpenDuration} < 0 or $hash->{windowOpenDuration} > 60); $hash->{$setting} = $args[0]; my $comfort = int($hash->{comfortTemperature}*2); my $eco = int($hash->{ecoTemperature}*2); my $max = int($hash->{maximumTemperature}*2); my $min = int($hash->{minimumTemperature}*2); my $offset = int(($hash->{temperatureOffset} + 3.5)*2); my $windowOpenTemp = int($hash->{windowOpenTemperature}*2); my $windowOpenTime = int($hash->{windowOpenDuration}/5); my $payload = pack("CCCCCCH6C"."CCCCCCC",0x00,0x00,17,0x00,0x00,0x00,$hash->{addr},0x00, $comfort,$eco,$max,$min,$offset,$windowOpenTemp,$windowOpenTime); return ($hash->{IODev}{SendDeviceCmd})->($hash->{IODev},$payload); }else{ if($hash->{type} eq "HeatingThermostat") { #Create numbers from 4.5 to 30.5 my $templist = join(",",map { $_/2 } (9..61)); return "Unknown argument $setting, choose one of desiredTemperature:eco,comfort,$templist ecoTemperature comfortTemperature temperatureOffset maximumTemperature minimumTemperature windowOpenTemperature windowOpenDuration groupid"; } else { return "Unknown argument $setting"; } } } ############################# sub MAX_Parse($$) { my ($hash, $msg) = @_; my ($MAX,$msgtype,$addr,@args) = split(",",$msg); #Find the device with the given addr my $shash = $modules{MAX}{defptr}{$addr}; if(!$shash) { if($msgtype eq "define"){ my $devicetype = $args[0]; return "UNDEFINED MAX_$addr MAX $devicetype $addr"; }else{ return; } } if($msgtype eq "define"){ my $devicetype = $args[0]; Log 1, "Device changed type from $shash->{type} to $devicetype" if($shash->{type} ne $devicetype); if(@args > 1){ my $serial = $args[1]; Log 1, "Device changed serial from $shash->{serial} to $serial" if($shash->{serial} and ($shash->{serial} ne $serial)); $shash->{serial} = $serial; } if(@args > 2){ my $groupid = $args[2]; $shash->{groupid} = $groupid; } } elsif($msgtype eq "HeatingThermostatState") { my $settemp = $args[0]; my $mode = $ctrl_modes[$args[1]]; my $until = $args[2]; my $batterylow = $args[3]; my $rferror = $args[4]; my $dstsetting = $args[5]; my $valveposition = $args[6]; my $measuredTemperature = ""; $measuredTemperature = $args[7] if(@args > 7); $shash->{mode} = $mode; $shash->{rferror} = $rferror; $shash->{dstsetting} = $dstsetting; if($mode eq "temporary"){ $shash->{until} = "$until"; }else{ delete($shash->{until}); } readingsBeginUpdate($shash); readingsBulkUpdate($shash, "battery", $batterylow ? "low" : "ok"); readingsBulkUpdate($shash, "desiredTemperature", $settemp); readingsBulkUpdate($shash, "valveposition", $valveposition); if($measuredTemperature ne "") { readingsBulkUpdate($shash, "temperature", $measuredTemperature); } readingsEndUpdate($shash, 0); }elsif($msgtype eq "ShutterContactState"){ my $isopen = $args[0]; my $batterylow = $args[1]; my $rferror = $args[2]; $shash->{rferror} = $rferror; readingsBeginUpdate($shash); readingsBulkUpdate($shash, "battery", $batterylow ? "low" : "ok"); readingsBulkUpdate($shash,"onoff",$isopen); readingsEndUpdate($shash, 0); }elsif($msgtype eq "CubeClockState"){ my $clockset = $args[0]; $shash->{clocknotset} = !$clockset; }elsif($msgtype eq "CubeConnectionState"){ my $connected = $args[0]; readingsSingleUpdate($shash,"connection",$connected,0); } elsif($msgtype eq "HeatingThermostatConfig") { $shash->{ecoTemperature} = $args[0]; $shash->{comfortTemperature} = $args[1]; $shash->{boostValveposition} = $args[2]; $shash->{boostDuration} = $args[3]; $shash->{temperatureOffset} = $args[4]; $shash->{maximumTemperature} = $args[5]; $shash->{minimumTemperature} = $args[6]; $shash->{windowOpenTemperature} = $args[7]; $shash->{windowOpenDuration} = $args[8]; } #Build $shash->{STATE} $shash->{STATE} = "waiting for data"; if(exists($shash->{READINGS})) { $shash->{STATE} = $shash->{READINGS}{connection}{VAL} ? "connected" : "not connected" if(exists($shash->{READINGS}{connection})); $shash->{STATE} = "$shash->{READINGS}{desiredTemperature}{VAL} °C" if(exists($shash->{READINGS}{desiredTemperature})); $shash->{STATE} = $shash->{READINGS}{onoff}{VAL} ? "opened" : "closed" if(exists($shash->{READINGS}{onoff})); } $shash->{STATE} .= " (clock not set)" if($shash->{clocknotset}); $shash->{STATE} .= " (auto)" if(exists($shash->{mode}) and $shash->{mode} eq "auto"); #Don't print this: it's the standard mode #$shash->{STATE} .= " (manual)" if(exists($shash->{mode}) and $shash->{mode} eq "manual"); $shash->{STATE} .= " (until ".$shash->{until}.")" if(exists($shash->{mode}) and $shash->{mode} eq "temporary" ); $shash->{STATE} .= " (battery low)" if($shash->{batterylow}); $shash->{STATE} .= " (rf error)" if($shash->{rferror}); return $shash->{NAME} } 1; =pod =begin html

MAX

=end html =cut