############################################## # $Id$ # # Usage # # define weekprofile # # Changelog # # V 0.01 2015-12-23 - first version ############################################## package main; use strict; use warnings; use JSON; #libjson-perl use vars qw(%defs); use vars qw($FW_ME); use vars qw($FW_wname); use vars qw($FW_subdir); my @shortDays = ("Mon","Tue","Wed","Thu","Fri","Sat","Sun"); my %DEV_READINGS; # MAX $DEV_READINGS{"Mon"}{"MAX"} = "weekprofile-2-Mon"; $DEV_READINGS{"Tue"}{"MAX"} = "weekprofile-3-Tue"; $DEV_READINGS{"Wed"}{"MAX"} = "weekprofile-4-Wed"; $DEV_READINGS{"Thu"}{"MAX"} = "weekprofile-5-Thu"; $DEV_READINGS{"Fri"}{"MAX"} = "weekprofile-6-Fri"; $DEV_READINGS{"Sat"}{"MAX"} = "weekprofile-0-Sat"; $DEV_READINGS{"Sun"}{"MAX"} = "weekprofile-1-Sun"; # HM-CC-RT-DN $DEV_READINGS{"Mon"}{"HM-CC-RT-DN"} = "R_2_tempListMon"; $DEV_READINGS{"Tue"}{"HM-CC-RT-DN"} = "R_3_tempListTue"; $DEV_READINGS{"Wed"}{"HM-CC-RT-DN"} = "R_4_tempListWed"; $DEV_READINGS{"Thu"}{"HM-CC-RT-DN"} = "R_5_tempListThu"; $DEV_READINGS{"Fri"}{"HM-CC-RT-DN"} = "R_6_tempListFri"; $DEV_READINGS{"Sat"}{"HM-CC-RT-DN"} = "R_0_tempListSat"; $DEV_READINGS{"Sun"}{"HM-CC-RT-DN"} = "R_1_tempListSun"; # HM-CC-TC $DEV_READINGS{"Mon"}{"HM-CC-TC"} = "R_2_tempListMon"; $DEV_READINGS{"Tue"}{"HM-CC-TC"} = "R_3_tempListTue"; $DEV_READINGS{"Wed"}{"HM-CC-TC"} = "R_4_tempListWed"; $DEV_READINGS{"Thu"}{"HM-CC-TC"} = "R_5_tempListThu"; $DEV_READINGS{"Fri"}{"HM-CC-TC"} = "R_6_tempListFri"; $DEV_READINGS{"Sat"}{"HM-CC-TC"} = "R_0_tempListSat"; $DEV_READINGS{"Sun"}{"HM-CC-TC"} = "R_1_tempListSun"; # HM-TC-IT-WM-W-EU $DEV_READINGS{"Mon"}{"HM-TC-IT-WM-W-EU"} = "R_P1_2_tempListMon"; $DEV_READINGS{"Tue"}{"HM-TC-IT-WM-W-EU"} = "R_P1_3_tempListTue"; $DEV_READINGS{"Wed"}{"HM-TC-IT-WM-W-EU"} = "R_P1_4_tempListWed"; $DEV_READINGS{"Thu"}{"HM-TC-IT-WM-W-EU"} = "R_P1_5_tempListThu"; $DEV_READINGS{"Fri"}{"HM-TC-IT-WM-W-EU"} = "R_P1_6_tempListFri"; $DEV_READINGS{"Sat"}{"HM-TC-IT-WM-W-EU"} = "R_P1_0_tempListSat"; $DEV_READINGS{"Sun"}{"HM-TC-IT-WM-W-EU"} = "R_P1_1_tempListSun"; ############################################## sub weekprofile_getDeviceType($) { my ($device) = @_; # determine device type my $devHash = $main::defs{$device}; if (!defined($devHash)){ return undef; } my $type = undef; if ($devHash->{TYPE} =~ /CUL_HM/){ $type = AttrVal($device,"model",""); } elsif ($devHash->{TYPE} =~ /MAX/){ $type = "MAX"; } elsif ($devHash->{TYPE} =~ /dummy/){ $type = "MAX" if ($device =~ /.*MAX.*/); #dummy (FAKE WT) with name MAX inside for testing } return $type; } ############################################## sub weekprofile_readDayProfile($@) { my ($device,$day,$type,$me) = @_; my @times; my @temps; $type = weekprofile_getDeviceType($device) if (!defined($type)); return if (!defined($type)); my $reading = $DEV_READINGS{$day}{$type}; #Log3 $me, 5, "$me(ReadDayProfile): $reading"; if($type eq "MAX") { @temps = split('/',ReadingsVal($device,"$reading-temp","")); @times = split('/',ReadingsVal($device,"$reading-time","")); # only use to to interval 'from-to' for(my $i = 0; $i < scalar(@times); $i+=1){ my $interval = $times[$i]; my @parts = split('-',$interval); $times[$i] = ($parts[1] ne "00:00") ? $parts[1] : "24:00"; } } else { # Homatic # get temp list for the day my $prf = ReadingsVal($device,$reading,""); # split into time temp time temp etc. # 06:00 17.0 22:00 21.0 24:00 17.0 my @timeTemp = split(' ', $prf); for(my $i = 0; $i < scalar(@timeTemp); $i += 2) { push(@times, $timeTemp[$i]); push(@temps, $timeTemp[$i+1]); } } for(my $i = 0; $i < scalar(@temps); $i+=1){ $temps[$i] =~s/[^\d.]//g; #only numbers } for(my $i = 0; $i < scalar(@times); $i+=1){ $times[$i] =~ s/^\s+|\s+$//g; #trim whitespace both ends } return (\@times, \@temps); } ############################################## sub weekprofile_readDevProfile(@) { my ($device,$type,$me) = @_; $type = weekprofile_getDeviceType($device) if (!defined($type)); return "" if (!defined ($type)); my $prf = {}; foreach my $day (@shortDays){ my ($dayTimes, $dayTemps) = weekprofile_readDayProfile($device,$day,$type,$me); $prf->{$day}->{"temp"} = $dayTemps; $prf->{$day}->{"time"} = $dayTimes; } return $prf; } ############################################## sub weekprofile_createDefaultPofile(@) { my ($hash) = @_; my $prf = {}; foreach my $day (@shortDays){ my @times; push(@times, "24:00"); my @temps; push(@temps, "18.0"); $prf->{$day}->{"temp"} = \@temps; $prf->{$day}->{"time"} = \@times; } return $prf; } ############################################## sub weekprofile_sendDevProfile(@) { my ($device,$prf,$me) = @_; my $type = weekprofile_getDeviceType($device); return "Error device type not supported" if (!defined ($type)); my $devPrf = weekprofile_readDevProfile($device,$type,$me); # only send changed days my @dayToTransfer = (); foreach my $day (@shortDays){ my $tmpCnt = scalar(@{$prf->{$day}->{"temp"}}); next if ($tmpCnt <= 0); if ($tmpCnt != scalar(@{$devPrf->{$day}->{"temp"}})) { push @dayToTransfer , $day; next; } my $equal = 1; for (my $i = 0; $i < $tmpCnt; $i++) { if ( ($prf->{$day}->{"temp"}[$i] ne $devPrf->{$day}->{"temp"}[$i] ) || $prf->{$day}->{"time"}[$i] ne $devPrf->{$day}->{"time"}[$i] ) { $equal = 0; last; } } if ($equal == 0) { push @dayToTransfer , $day; next; } } if (scalar(@dayToTransfer) <=0) { Log3 $me, 4, "$me(sendDevProfile): nothing to do"; return undef; } my $cmd; if($type eq "MAX") { $cmd = "set $device weekProfile "; foreach my $day (@dayToTransfer){ my $tmpCnt = scalar(@{$prf->{$day}->{"temp"}}); $cmd.=$day.' '; for (my $i = 0; $i < $tmpCnt; $i++) { my $endTime = $prf->{$day}->{"time"}[$i]; $endTime = ($endTime eq "24:00") ? ' ' : ','.$endTime.','; $cmd.=$prf->{$day}->{"temp"}[$i].$endTime; } } } else { #Homatic my $k=0; my $dayCnt = scalar(@dayToTransfer); foreach my $day (@dayToTransfer){ $cmd .= "set $device tempList"; $cmd .= $day; $cmd .= ($k < $dayCnt-1) ? " prep": " exec"; my $tmpCnt = scalar(@{$prf->{$day}->{"temp"}}); for (my $i = 0; $i < $tmpCnt; $i++) { $cmd .= " ".$prf->{$day}->{"time"}[$i]." ".$prf->{$day}->{"temp"}[$i]; } $cmd .= ($k < $dayCnt-1) ? ";;": ""; $k++; } } $cmd =~ s/^\s+|\s+$//g; Log3 $me, 4, "$me(sendDevProfile): $cmd"; fhem($cmd); return undef; } ############################################## sub weekprofile_assignDev($) { my ($hash) = @_; my $me = $hash->{NAME}; my $prf = undef; if ($hash->{MASTERDEV}->{NAME}) { Log3 $me, 5, "$me(assignDev): assign to device $hash->{MASTERDEV}->{NAME}"; my $type = weekprofile_getDeviceType($hash->{MASTERDEV}->{NAME}); if (!defined($type)) { Log3 $me, 2, "$me(assignDev): device $hash->{MASTERDEV}->{NAME} not supported or defined"; return; } $hash->{MASTERDEV}->{TYPE} = $type; my $prfDev = weekprofile_readDevProfile($hash->{MASTERDEV}->{NAME},$type, $me); if(defined($prfDev)) { $prf = {}; $prf->{DATA} = $prfDev; $prf->{NAME} = 'master'; } $hash->{STATE} = "assigned"; } else { my $prfDev = weekprofile_createDefaultPofile($hash); if(defined($prfDev)) { $prf = {}; $prf->{DATA} = $prfDev; $prf->{NAME} = 'default'; } $hash->{STATE} = "created"; } if(defined($prf)) { push @{$hash->{PROFILES}} , $prf; } readingsBeginUpdate($hash); readingsBulkUpdate($hash,"state",$hash->{STATE}); readingsEndUpdate($hash, 1); } ############################################## sub weekprofile_updateReadings($) { my ($hash) = @_; my $prfCnt = scalar(@{$hash->{PROFILES}}); readingsBeginUpdate($hash); readingsBulkUpdate($hash,"profile_count",$prfCnt); #readings with profile names??? #my $idx = 1; #foreach my $prf (@{$hash->{PROFILES}}){ #my $str = sprintf("profile_name_%02d",$idx); #readingsBulkUpdate($hash,$str,$prf->{NAME}); #$idx++; #} readingsEndUpdate($hash, 1); } ############################################## sub weekprofile_Initialize($) { my ($hash) = @_; $hash->{DefFn} = "weekprofile_Define"; $hash->{SetFn} = "weekprofile_Set"; $hash->{GetFn} = "weekprofile_Get"; $hash->{SetFn} = "weekprofile_Set"; $hash->{StateFn} = "weekprofile_State"; $hash->{NotifyFn} = "weekprofile_Notify"; $hash->{AttrList} = "widgetWeekdays configFile ".$readingFnAttributes; $hash->{FW_summaryFn} = "weekprofile_SummaryFn"; $hash->{FW_atPageEnd} = 1; } ############################################## sub weekprofile_Define($$) { my ($hash, $def) = @_; my @a = split("[ \t][ \t]*", $def); if(@a < 1) { my $msg = "wrong syntax: define weekprofile [device]"; Log3 undef, 2, $msg; return $msg; } my $me = $a[0]; $hash->{MASTERDEV}->{NAME} = undef; $hash->{MASTERDEV}->{NAME} = $a[2] if (@a > 1); $hash->{STATE} = "defined"; my @profiles = (); $hash->{PROFILES} = \@profiles; #$attr{$me}{verbose} = 5; weekprofile_assignDev($hash); weekprofile_updateReadings($hash); return undef; } ############################################## sub weekprofile_Get($$@) { my ($hash, $name, $cmd, @params) = @_; my $list = ''; my $prfCnt = scalar(@{$hash->{PROFILES}}); $list.= 'profile_data:' if ($prfCnt > 0); foreach my $prf (@{$hash->{PROFILES}}){ $list.= $prf->{NAME}.","; } $list = substr($list, 0, -1) if ($prfCnt > 0); if($cmd eq "profile_data") { return "no profile" if ($prfCnt <= 0); my $prf = undef; my $idx=0; if($params[0]){ foreach my $prf (@{$hash->{PROFILES}}){ last if ( $prf->{NAME} eq $params[0]); $idx++; } return "profile $params[0] not found" if ($idx >= $prfCnt); } $prf = $hash->{PROFILES}[$idx]; my $json = JSON->new; my $json_text = $json->encode($prf->{DATA}); return $json_text; } $list.= ' profile_names:noArg'; if($cmd eq "profile_names") { my $names = ''; foreach my $prf (@{$hash->{PROFILES}}){ $names .=$prf->{NAME}.","; } $names = substr($names, 0, -1); return $names; } $list =~ s/ $//; return "Unknown argument $cmd choose one of $list"; } ############################################## sub weekprofile_Set($$@) { my ($hash, $me, $cmd, @params) = @_; my $prfCnt = scalar(@{$hash->{PROFILES}}); my $list = ''; $list.= "profile_data"; if ($cmd eq 'profile_data') { return 'usage: profile_data ' if(@params < 2); my $json = JSON->new; my $data = undef; eval { $data = $json->decode($params[1]); }; if ($@) { Log3 $me, 1, "$me(Set): Error parsing profile data."; return "Error parsing profile data. No valid json format"; }; foreach my $prf (@{$hash->{PROFILES}}){ if ( $prf->{NAME} eq $params[0]){ $prf->{DATA} = $data; # automatic we send master profile to master device if ($params[0] eq "master"){ weekprofile_sendDevProfile($hash->{MASTERDEV}->{NAME},$prf->{DATA},$me); } else { weekprofile_writeProfilesToFile($hash); } return undef; } } my $prfNew = {}; $prfNew->{NAME} = $params[0]; $prfNew->{DATA} = $data; push @{$hash->{PROFILES}}, $prfNew; weekprofile_updateReadings($hash); weekprofile_writeProfilesToFile($hash); return undef; } #---------------------------------------------------------- $list.= ' send_to_device' if ($prfCnt > 0); if ($cmd eq 'send_to_device') { return 'usage: send_to_device [device]' if(@params < 1); my $profile = $params[0]; my $device = $hash->{MASTERDEV}->{NAME}; if (@params == 2){ $device = $params[1]; } return "Error no master device" unless (defined($device)); my $found = undef; foreach my $prf (@{$hash->{PROFILES}}){ if ( $prf->{NAME} eq $profile){ $found = $prf; last; } } if (!$found) { Log3 $me, 1, "$me(Set): Error unknown profile $profile"; return "Error unknown profile $profile"; } my $ret = weekprofile_sendDevProfile($device,$found->{DATA},$me); Log3 $me, 1, "$me(Set): $ret" if ($ret); return $ret; } #---------------------------------------------------------- $list.= " copy_profile"; if ($cmd eq 'copy_profile') { return 'usage: copy_profile ' if(@params < 2); my $srcName = $params[0]; my $destName= $params[1]; my $prfSrc = undef; my $prfDest = undef; foreach my $prf (@{$hash->{PROFILES}}){ $prfSrc = $prf if ($prf->{NAME} eq $srcName); $prfDest = $prf if ($prf->{NAME} eq $destName); } return "Error unknown profile $srcName" unless($prfSrc); Log3 $me, 4, "$me(Set): override profile $destName" if ($prfDest); if ($prfDest){ $prfDest->{DATA} = $prfSrc->{DATA} } else { $prfDest = {}; $prfDest->{NAME} = $destName; $prfDest->{DATA} = $prfSrc->{DATA}; push @{$hash->{PROFILES}}, $prfDest; } weekprofile_writeProfilesToFile($hash); weekprofile_updateReadings($hash); return undef; } #---------------------------------------------------------- $list.= " remove_profile"; if ($cmd eq 'remove_profile') { return 'usage: remove_profile ' if(@params < 1); return 'Error master profile can not removed' if($params[0] eq "master"); return 'Error Remove last profile is not allowed' if(scalar(@{$hash->{PROFILES}}) == 1); my $delprf = undef; my $idx = 0; foreach my $prf (@{$hash->{PROFILES}}){ if ( $prf->{NAME} eq $params[0]){ $delprf = $prf; last; } $idx++; } return "Error unknown profile $params[0]" unless($delprf); splice(@{$hash->{PROFILES}},$idx, 1); weekprofile_writeProfilesToFile($hash); weekprofile_updateReadings($hash); return undef; } $list =~ s/ $//; return "Unknown argument $cmd, choose one of $list"; } ############################################## sub weekprofile_State($$$$) { my ($hash, $time, $name, $val) = @_; my $me = $hash->{NAME}; } ############################################## sub weekprofile_Notify($$) { my ($own, $dev) = @_; my $me = $own->{NAME}; # own name / hash my $devName = $dev->{NAME}; # Device that created the events return undef if ($devName ne "global"); my $max = int(@{$dev->{CHANGED}}); # number of events / changes for (my $i = 0; $i < $max; $i++) { my $s = $dev->{CHANGED}[$i]; next if(!defined($s)); my ($what,$who) = split(' ',$s); if ($what =~ m/INITIALIZED/) { splice($own->{PROFILES}); weekprofile_assignDev($own); weekprofile_readProfilesFromFile($own); weekprofile_updateReadings($own); } } return undef; } ############################################## sub weekprofile_writeProfilesToFile(@) { my ($hash) = @_; my $me = $hash->{NAME}; my $filename = "./log/weekprofile-$me.cfg"; $filename = AttrVal($me,"configFile",$filename); Log3 $me, 5, "$me(writeProfileToFile): write profiles to $filename"; my $ret = open(my $fh, '>', $filename); if (!$ret){ Log3 $me, 1, "Could not open file '$filename' $!"; return; } my $json = JSON->new; my $start = (defined($hash->{MASTERDEV}->{NAME})) ? 1:0; my $prfCnt = scalar(@{$hash->{PROFILES}}); for (my $i = $start; $i < $prfCnt; $i++) { print $fh $hash->{PROFILES}[$i]->{NAME}."=".$json->encode($hash->{PROFILES}[$i]->{DATA})."\n"; } close $fh; } ############################################## sub weekprofile_readProfilesFromFile(@) { my ($hash) = @_; my $me = $hash->{NAME}; my $filename = "./log/weekprofile-$me.cfg"; $filename = AttrVal($me,"configFile",$filename); Log3 $me, 5, "$me(readProfilesFromFile): read profiles from $filename"; unless (-e $filename) { Log3 $me, 5, "$me(readProfilesFromFile): file do not exist '$filename'"; return; } my $ret = open(my $fh, '<:encoding(UTF-8)', $filename); if (!$ret){ Log3 $me, 1, "$me(readProfilesFromFile): Could not open file '$filename' $!"; return; } my $json = JSON->new; my $rowCnt = 0; while (my $row = <$fh>) { chomp $row; Log3 $me, 5, "$me(readProfilesFromFile): data row $row"; my @data = split('=',$row); if(@data<2){ Log3 $me, 1, "$me(readProfilesFromFile): incorrect data row"; next; } my $prfData=undef; eval { $prfData = $json->decode($data[1]); }; if ($@) { Log3 $me, 1, "$me(readProfilesFromFile): Error parsing profile data $data[1]"; next; }; my $prfNew = {}; $prfNew->{NAME} = $data[0]; $prfNew->{DATA} = $prfData; if (!$hash->{MASTERDEV}->{NAME} && $rowCnt == 0) { $hash->{PROFILES}[0] = $prfNew; # replace default } else { push @{$hash->{PROFILES}}, $prfNew; } $rowCnt++; } close $fh; } ############################################## sub weekprofile_SummaryFn() { my ($FW_wname, $d, $room, $extPage) = @_; my $hash = $defs{$d}; my $show_links = 1; $show_links = 0 if($FW_hiddenroom{detail}); my $html; my $iconName = AttrVal($d, "icon", "edit_settings"); my $icon = FW_iconName($iconName) ? FW_makeImage($iconName,$iconName,"icon") : ""; $icon = "$icon"; my $lnk = AttrVal($d, "alias", $d); $lnk = "$lnk" if($show_links); my $args = "weekprofile"; my $curr = undef; $curr = $hash->{PROFILES}[0]->{NAME} if (@{$hash->{PROFILES}} > 0 ); $html .= ""; $html .= ""; $html .= ""; $html .= "
"; $html .= "
"; $html .= "
"; $html .= $icon." ".$lnk; $html .= "
"; $html .= "
"; # div tag to support inform updates $html .= "
"; $html .= "
"; return $html; } 1; =pod =begin html

weekprofile

    ToDo: Übersetzung
    Mit dem Modul 'weekprofile' können mehrere Wochenprofile verwaltet und an unterschiedliche Geräte übertragen werden. Aktuell werden folgende Hardware unterstützt:
  • alle MAX Thermostate
  • Homatic HM-CC-RT-DN
  • Homatic HM-CC-TC
  • Homatic HM-TC-IT-WM-W-EU
  • Im Standardfall wird das Modul mit einem Geräte = 'Master-Gerät' assoziiert, um das Wochenprofil vom Gerät grafisch bearbeiten zu können und andere Profile auf das Gerät zu bringen.
    Achtung: Das Übertragen von Wochenprofilen erfordet eine Menge an Credits. Dies wird vom Modul nicht berücksichtigt. So kann es sein, dass nach dem Setzen\Aktualisieren eines Profils das Proil im Modul nicht mit dem Profil im Gerät übereinstimmt.

    Define
      define <name> weekprofile [master device]

      Aktiviert das Modul. Bei der Angabe eines 'Master-Gerätes' wird das Profil 'master' entprechende dem Wochenrofil vom Gerät angelegt. Sonderbehandlung des 'master' Profils:
    • Kann nicht gelöscht werden
    • Bei Ändern\Setzen des Proils wird es automatisch an das 'Master-Geräte' gesendet
    • Es wird sind mit abgespeicht

    • Wird kein 'Master-Gerätes' angegeben, wird ein 'default' Profil angelegt.
    Set
    • profile_data
      set <name> profile_data <profilname> <json data>
      Es wird das Profil 'profilname' geändert. Die Profildaten müssen im json-Format übergeben werden.
    • send_to_device
      set <name> send_to_device <profilname> [device]
      Das Profil wird an ein Gerät übertragen. Wird kein Gerät angegeben, wird das 'Master-Gerät' verwendet.
    • copy_profile
      set <name> copy_profile <quelle> <ziel>
      Kopiert das Profil 'quelle' auf 'ziel'. 'ziel' wird überschrieben oder neu angelegt.
    • remove_profile
      set <name> remove_profile <profilname>
      Das Profil 'profilname' wird gelöscht.
    Get
    • profile_data
      get <name> profile_data <profilname>
      Liefert die Profildaten von 'profilname' im json-Format
    • profile_names
      set <name> profile_names
      Liefert alle Profilnamen getrennt durch ','
    Attribute
    • widgetWeekdays
      Liste von Wochentagen getrennt durch ',' welche im Widget angzeigt werden. Beginnend bei Montag. z.B. attr name widgetWeekdays Montag,Dienstag,Mittwoch,Donnerstag,Freitag,Samstag,Sonntag
    • configFile
      Pfad und Dateiname wo die Profile gespeichert werden sollen. Default: ./log/weekprofile-.cfg
    • icon
      Änders des Icons zum Bearbeiten Default: edit_settings
=end html =cut