diff --git a/fhem/CHANGED b/fhem/CHANGED index 2072b05bd..97f2a869d 100644 --- a/fhem/CHANGED +++ b/fhem/CHANGED @@ -1,5 +1,6 @@ # Add changes at the top of the list. Keep it in ASCII, and 80-char wide. # Do not insert empty lines here, update check depends on it. + - feature: 98_weekprofile: new module to manage week profiles - feature: 49_SSCam: function "enable" and "disable" for SS-Cams added - change: 49_SSCam: changed timout of Http-calls to a higher value, commandref enhanced diff --git a/fhem/FHEM/98_weekprofile.pm b/fhem/FHEM/98_weekprofile.pm new file mode 100644 index 000000000..efe27b4ad --- /dev/null +++ b/fhem/FHEM/98_weekprofile.pm @@ -0,0 +1,750 @@ +############################################## +# $Id: 98_weekprofile.pm 0.01 2015-12-23 Risiko $ +# +# 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}; + + 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}) { + my $type = weekprofile_getDeviceType($hash->{MASTERDEV}->{NAME}); + return if (!defined($type)); + + $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; + 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"); + + 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/) { + Log3 $me, 5, "$me(Notify): assign to device $own->{MASTERDEV}->{NAME}" if (defined($own->{MASTERDEV}->{NAME})); + 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 = $hash->{PROFILES}[0]->{NAME}; + + $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 diff --git a/fhem/HISTORY b/fhem/HISTORY index 4e67dcc52..36cadbeba 100644 --- a/fhem/HISTORY +++ b/fhem/HISTORY @@ -664,3 +664,6 @@ - added new function for running terms - improved commandref +- Wed Dec 23 2015 (risiko) + - added new module 98_weekprofile to manage week profiles + diff --git a/fhem/MAINTAINER.txt b/fhem/MAINTAINER.txt index bdf529c7c..d64951248 100644 --- a/fhem/MAINTAINER.txt +++ b/fhem/MAINTAINER.txt @@ -329,6 +329,7 @@ FHEM/98_structure.pm rudolfkoenig http://forum.fhem.de Automatis FHEM/98_telnet.pm rudolfkoenig http://forum.fhem.de Automatisierung FHEM/98_update.pm rudolfkoenig http://forum.fhem.de Sonstiges FHEM/98_weblink.pm rudolfkoenig http://forum.fhem.de Frontends +FHME/98_weekprofile.pm risiko http://forum.fhem.de Frontends FHEM/99_SUNRISE_EL.pm rudolfkoenig http://forum.fhem.de Automatisierung FHEM/99_Utils.pm rudolfkoenig http://forum.fhem.de Automatisierung FHEM/Blocking.pm rudolfkoenig http://forum.fhem.de Automatisierung @@ -380,6 +381,7 @@ www/pgm2/fhemweb_readingsHistory.js justme1968 http://forum.fhem.de Frontends www/pgm2/fhemweb_sortable.js markusbloch http://forum.fhem.de Frontends www/pgm2/fhemweb_fbcalllist.js markusbloch http://forum.fhem.de Frontends www/pgm2/fhemweb_uzsu.js justme1968 http://forum.fhem.de Frontends +www/pgm2/fhemweb_weekprofile.js risiko http://forum.fhem.de Frontends www/pgm2/* rudolfkoenig http://forum.fhem.de Frontends www/jscolor/* justme1968 http://forum.fhem.de Frontends www/frontend/* johannnes http://forum.fhem.de Frontends diff --git a/fhem/www/pgm2/fhemweb_weekprofile.js b/fhem/www/pgm2/fhemweb_weekprofile.js new file mode 100644 index 000000000..b93c49674 --- /dev/null +++ b/fhem/www/pgm2/fhemweb_weekprofile.js @@ -0,0 +1,466 @@ +//fhemweb_weekprofile.js 0.01 2015-12-23 Risiko + +//for tooltip +$(document).ready(function(){ + $('[data-toggle="tooltip"]').tooltip(); +}); + +var shortDays = ["Mon","Tue","Wed","Thu","Fri","Sat","Sun"]; + +function FW_weekprofileInputDialog(title,inp,parent, callback) +{ + var div = $("
"); + var content = $('').get(0); + $(div).append(title); + $(div).append(content); + $("body").append(div); + $(div).dialog({ + dialogClass:"no-close",modal:true, width:"auto", closeOnEscape:true, + maxWidth:$(window).width()*0.9, maxHeight:$(window).height()*0.9, + title: title, + buttons: [{text:"OK", click:function(){ + $(this).dialog("close"); + $(div).remove(); + if(callback) + callback(content.value,1); + }},{text:"CANCEL", click:function(){ + $(this).dialog("close"); + $(div).remove(); + content.value = null; + if(callback) + callback(content.value,0); + }}] + }); + + if(parent) + $(div).dialog( "option", "position", { + my: "left top", at: "right bottom", + of: parent, collision: "flipfit" + }); +} + +function weekprofile_DoEditWeek(devName) +{ + var widget = $('div[informid="'+devName+'"]').get(0); + widget.MODE = 'EDIT'; + + $(widget.MENU.BASE).hide(); + + widget.setValueFn("REUSEPRF"); +} + +function FW_weekprofilePRFCached(devName,select) +{ + var widget = $('div[informid="'+devName+'"]').get(0) + + var prfName = select.options[select.selectedIndex].value; + + widget.CURPRF = prfName; + widget.PROFILE = null; + FW_queryValue('get '+devName+' profile_data '+prfName, widget); +} + +function FW_weekprofileSendToDev(devName,lnk) +{ + var widget = $('div[informid="'+devName+'"]').get(0) + + FW_weekprofileInputDialog("Device:","text",lnk,function(device,ok){ + if (!device || device.length <=0) + return; + FW_cmd(FW_root+"?cmd=set "+widget.DEVICE+" send_to_device "+widget.CURPRF+" "+device+"&XHR=1",function(arg) {FW_weekprofileSendCallback(widget.DEVICE,arg);}); + }); +} + +function FW_weekprofileCopyPrf(devName,lnk) +{ + var widget = $('div[informid="'+devName+'"]').get(0) + + FW_weekprofileInputDialog("Name:","text",lnk,function(name,ok){ + if (!name || name.length <=0) + return; + FW_cmd(FW_root+"?cmd=set "+widget.DEVICE+" copy_profile "+widget.CURPRF+" "+name+"&XHR=1",function(arg) {FW_weekprofileSendCallback(widget.DEVICE,arg);}); + }); +} + +function FW_weekprofileRemovePrf(devName,lnk) +{ + var widget = $('div[informid="'+devName+'"]').get(0) + + FW_weekprofileInputDialog("

Delete Profile: '"+widget.CURPRF+"' ?

","hidden",lnk,function(name,ok){ + if (ok < 1) + return; + FW_cmd(FW_root+"?cmd=set "+widget.DEVICE+" remove_profile "+widget.CURPRF+"&XHR=1",function(arg) {FW_weekprofileSendCallback(widget.DEVICE,arg);}); + }); +} + + +function FW_weekprofileShow(widget) +{ + $(widget.MENU.BASE).show(); + $(widget.MENU.CONTENT).empty(); + + var html=''; + + if (widget.PROFILENAMES) { + html += " " + html += ""; + + html += " " + html += ""; + + html += " " + html += ""; + + html += " " + html += ""; + + $(widget.MENU.CONTENT).append(html); + } + + if (!widget.PROFILE) { + return; + } + + var table = widget.CONTENT; + for (var i = 0; i < shortDays.length; ++i) { + $(table).append(''+shortDays[i]+''); + + var tr = $(table).find("tr").get(i); + + for (var k = 0; k < widget.PROFILE[shortDays[i]]['temp'].length; ++k) { + + var str = ''; + k>0 ? str = widget.PROFILE[shortDays[i]]['time'][k-1] : str = '00:00'; + str = str + '-' + widget.PROFILE[shortDays[i]]['time'][k]; + + $(tr).append(''+str+ ''); + + str = widget.PROFILE[shortDays[i]]['temp'][k]+' °C'; + $(tr).append(''+str+ ''); + } + } +} + +function FW_weekprofileEditTimeChanged(inp) +{ + if (inp == null) {return;} + var times = inp.value.split(':'); + if (times.length == 0) + return; + + var hour = parseInt(times[0]); + var min = (times.length==2) ? parseInt(times[1]): 0; + + inp.value = ((hour<10)?("0"+hour):hour) +":"+ ((min<10)?("0"+min):min); + + //set new end time as new start time for the next interval + var nexttr = inp.parentNode.parentNode.nextSibling; + if (nexttr!=null){ + nexttr.firstChild.firstChild.innerHTML=inp.value; + } +} + +function FW_weekprofileEditRowStyle(table) +{ + var alltr = $(table).find("tr"); + for (var i = 0; i < alltr.length; ++i){ + var delButton = $(alltr[i]).find('input[name="DEL"]'); + var addButton = $(alltr[i]).find('input[name="ADD"]'); + var inp = $(alltr[i]).find('input[name="ENDTIME"]'); + + $(alltr[i]).attr('class',(i%2==0)? "odd":"even"); + delButton.attr('type',"button"); + addButton.attr('type',"button"); + inp.removeAttr('style'); + inp.removeAttr('readonly'); + + FW_weekprofileEditTimeChanged(inp.get(0)); + + if (i==0){ + $(alltr[i]).find('span[name="STARTTIME"]').get(0).innerHTML = "00:00"; + if (alltr.length == 1){ + delButton.attr('type',"hidden"); + } + } + + if (i==alltr.length-1){ + if (alltr.length > 1){ + addButton.attr('type',"hidden"); + } + inp.attr('style',"border:none;background:transparent;box-shadow:none"); + inp.get(0).value = "24:00"; + inp.attr('readonly',true); + } + } +} + +function FW_weekprofileEditAddInterval(tr) +{ + var newtr = $(tr).clone(true); + + var alltr = $(tr).parent().children(); + for (var i = 0; i < alltr.length; ++i) { + if ( $(alltr[i]).is($(tr))) { + newtr.insertAfter($(alltr[i])); + break; + } + } + + FW_weekprofileEditRowStyle($(tr).parent()); + + var timSel = newtr.find('input[name="ENDTIME"]'); + + if (alltr.length == 1) + timSel = $(tr).find('input[name="ENDTIME"]'); + + timSel.focus(); + timSel.select(); +} + +function FW_weekprofileEditDelInterval(tr) +{ + var parent = $(tr).parent(); + $(tr).remove(); + FW_weekprofileEditRowStyle(parent) +} + +function FW_weekprofileEditDay(widget,day) +{ + var div = $("
").get(0); + $(div).append("
"+widget.WEEKDAYS[day]+"
"); + + var table = $("").get(0); + $(table).attr('id',"weekprofile."+widget.DEVICE+"."+shortDays[day]); + $(table).attr('class',"block wide weekprofile"); + + var html; + var times = widget.PROFILE[shortDays[day]]['time']; + var temps = widget.PROFILE[shortDays[day]]['temp']; + + for (var i = 0; i < times.length; ++i) { + var startTime = (i>0) ? times[i-1] : "00:00"; + var endTime = (i"+startTime+""; + + html += ""; + //to + html += ""; + + //temp + html += ""; + //ADD-Button + html += ""; + //DEL-Button + html += ""; + html += ""; + } + $(table).append(html); + $(div).append(table); + FW_weekprofileEditRowStyle(table); + return div; +} + +function FW_weekprofileEditWeek(widget) +{ + var table = widget.CONTENT; + var daysInRow = 2; + + $(table).append(''); + var tr = $(table).find("tr:last"); + + for (var i = 0; i < shortDays.length; ++i) { + tr.append('').insertAfter(tr); + tr = $(table).find("tr:last"); + } + } + + tr.append("
-
'); + tr.find('td:last').append(FW_weekprofileEditDay(widget,i)); + + if ((i+1)%daysInRow == 0){ + $('
"); + tr = tr.find("tr:last"); + tr.append("
"); + tr.append(""); +} + +function FW_weekprofileSendCallback(devName, data) +{ + var widget = $('div[informid="'+devName+'"]').get(0); + if(!data.match(/^[\r\n]*$/)) // ignore empty answers + FW_okDialog('
'+data+'
',widget); +} + +function FW_weekprofilePrepAndSendProf(devName) +{ + var widget = $('div[informid="'+devName+'"]').get(0); + + var tableDay = $(widget).find("table[id*=\"weekprofile."+devName+"\"]"); + + if (tableDay.length == 0){ + FW_errmsg(widget.DEVICE+" internal error ",10000); + return; + } + + var prf=new Object(); + for (var i = 0; i < tableDay.length; ++i) { + var timeEL = $(tableDay[i]).find('input[name="ENDTIME"]'); + var tempEL = $(tableDay[i]).find('select[name="TEMP"]'); + + if (timeEL.length != tempEL.length){ + FW_errmsg(widget.DEVICE+" internal error ",10000); + return; + } + + var id = $(tableDay[i]).attr('id').split('.'); + var day = id[2]; + + prf[day] = new Object(); + prf[day]['time'] = new Array(); + prf[day]['temp'] = new Array(); + + for (var k = 0; k < timeEL.length; ++k) { + prf[day]['time'].push(timeEL[k].value); + prf[day]['temp'].push(tempEL[k].value); + } + } + try { + var data=JSON.stringify(prf); + FW_cmd(FW_root+"?cmd=set "+widget.DEVICE+" profile_data "+widget.CURPRF+" "+data+"&XHR=1",function(arg) {FW_weekprofileSendCallback(widget.DEVICE,arg);}); + } catch(e){ + FW_errmsg(devName+" Parameter "+e,5000); + return; + } + + for (var i = 0; i < shortDays.length; ++i) { + var day = shortDays[i]; + if (prf[day] != null){ + widget.PROFILE[day] = prf[day]; + } + } + widget.MODE = "SHOW"; + widget.setValueFn("REUSEPRF"); +} + +function FW_weekprofileEditAbort(devName) +{ + var widget = $('div[informid="'+devName+'"]').get(0); + widget.MODE = "SHOW"; + widget.setValueFn("REUSEPRF"); +} + +function FW_weekprofileSetValue(devName,data) +{ + var widget = $('div[informid="'+devName+'"]').get(0); + $(widget.CONTENT).empty(); + + var prf={}; + try { + (data == "REUSEPRF") ? prf = widget.PROFILE : prf=JSON.parse(data); + } catch(e){ + console.log(devName+" error parsing json '" +data+"'"); + FW_errmsg(devName+" Parameter "+e,5000); + return; + } + + widget.PROFILE = prf; + if (widget.MODE == 'SHOW' || widget.MODE == 'CREATE') + { + FW_weekprofileShow(widget); + } + else if (widget.MODE == 'EDIT') + { + FW_weekprofileEditWeek(widget); + } + else + { + FW_errmsg(devName+" unknown Mode",10000); + } +} + +function FW_weekprofileGetValues(devName,what,data) +{ + if(data.match(/^[\r\n]*$/)) {return;} + + var widget = $('div[informid="'+devName+'"]').get(0); + + if (what == "WEEKDAYS"){ + widget.WEEKDAYS = data.split(','); + } else if (what == "PROFILENAMES") { + widget.PROFILENAMES = data.split(','); + if (widget.MODE != 'EDIT') { + widget.setValueFn("REUSEPRF"); + } + } +} + +function +FW_weekprofileCreate(elName, devName, vArr, currVal, set, params, cmd) +{ + if( 0 ) { + console.log( "elName: "+elName ); + console.log( "devName: "+devName ); + console.log( "vArr: "+vArr ); + console.log( "currVal: "+currVal ); + console.log( "set: "+set ); + console.log( "params: "+params ); + console.log( "cmd: "+cmd ); + } + + if(!vArr.length || vArr[0] != "weekprofile") + return undefined; + + var widget = $('div[informid="'+devName+'"]').get(0); + + var content = $('').get(0); + $(widget).append(content); + + widget.CONTENT = content; + widget.HEADER = $('div[id="weekprofile.'+devName+'.header"]').get(0); + + widget.MENU = new Object(); + widget.MENU.BASE = $(widget.HEADER).find('div[id*="menu.base"]').get(0); + + var menu = $('
').get(0); + $(widget.MENU.BASE).append(menu); + widget.MENU.CONTENT = menu; + + //inform profile_count changed + var prfCnt = $('
').get(0); + prfCnt.setValueFn = function(arg){ + FW_cmd(FW_root+'?cmd=get '+devName+' profile_names&XHR=1',function(data){FW_weekprofileGetValues(devName,"PROFILENAMES",data);}); + } + $(widget.HEADER).append(prfCnt); + + widget.MODE = 'CREATE'; + widget.DEVICE = devName; + widget.WEEKDAYS = shortDays.slice(); + widget.CURPRF = currVal; + + widget.setValueFn = function(arg){FW_weekprofileSetValue(devName,arg);} + widget.activateFn = function(arg){ + FW_queryValue('get '+devName+' profile_data '+widget.CURPRF, widget); + FW_cmd(FW_root+'?cmd={AttrVal("'+devName+'","widgetWeekdays","")}&XHR=1',function(data){FW_weekprofileGetValues(devName,"WEEKDAYS",data);}); + FW_cmd(FW_root+'?cmd=get '+devName+' profile_names&XHR=1',function(data){FW_weekprofileGetValues(devName,"PROFILENAMES",data);}); + }; + return widget; +} + +FW_widgets['weekprofile'] = { + createFn:FW_weekprofileCreate, +};