2
0
mirror of https://github.com/fhem/fhem-mirror.git synced 2025-03-12 22:56:34 +00:00

98_weekprofile: new module to manage week profiles

git-svn-id: https://svn.fhem.de/fhem/trunk@10244 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
risiko79 2015-12-23 18:49:59 +00:00
parent ce92adc00a
commit 905fb5b73f
5 changed files with 1222 additions and 0 deletions

View File

@ -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

750
fhem/FHEM/98_weekprofile.pm Normal file
View File

@ -0,0 +1,750 @@
##############################################
# $Id: 98_weekprofile.pm 0.01 2015-12-23 Risiko $
#
# Usage
#
# define <name> weekprofile <device>
#
# 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 <name> 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 <name> <json 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 <profile name> [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 <source> <target>' 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 <name>' 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 = "<a name=\"$d.edit\" onclick=\"weekprofile_DoEditWeek('$d')\" href=\"javascript:void(0)\">$icon</a>";
my $lnk = AttrVal($d, "alias", $d);
$lnk = "<a name=\"$d.detail\" href=\"$FW_ME$FW_subdir?detail=$d\">$lnk</a>" if($show_links);
my $args = "weekprofile";
my $curr = $hash->{PROFILES}[0]->{NAME};
$html .= "<table>";
$html .= "<tr><td>";
$html .= "<div class=\"devType\" id=\"weekprofile.$d.header\">";
$html .= "<div class=\"devType\" id=\"weekprofile.menu.base\">";
$html .= $icon."&nbsp;".$lnk;
$html .= "</di></div></td></tr>";
$html .= "<tr><td>";
$html .= "<div class=\"fhemWidget\" informId=\"$d\" cmd=\"\" arg=\"$args\" current=\"$curr\" dev=\"$d\">"; # div tag to support inform updates
$html .= "</div>";
$html .= "</td></tr>";
$html .= "</table>";
return $html;
}
1;
=pod
=begin html
<a name="weekprofile"></a>
<h3>weekprofile</h3>
<ul>
<b>ToDo: Übersetzung</b><br>
Mit dem Modul 'weekprofile' können mehrere Wochenprofile verwaltet und an unterschiedliche Geräte
übertragen werden. Aktuell werden folgende Hardware unterstützt:
<li>alle MAX Thermostate</li>
<li>Homatic HM-CC-RT-DN </li>
<li>Homatic HM-CC-TC </li>
<li>Homatic HM-TC-IT-WM-W-EU</li>
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.
<br>
<b>Achtung:</b> 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.
<br><br>
<a name="weekprofiledefine"></a>
<b>Define</b>
<ul>
<code>define &lt;name&gt; weekprofile [master device]</code><br>
<br>
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:
<li>Kann nicht gelöscht werden</li>
<li>Bei Ändern\Setzen des Proils wird es automatisch an das 'Master-Geräte' gesendet</li>
<li>Es wird sind mit abgespeicht</li>
<br>
Wird kein 'Master-Gerätes' angegeben, wird ein 'default' Profil angelegt.
</ul>
<a name="weekprofileset"></a>
<b>Set</b>
<ul>
<li>profile_data<br>
<code>set &lt;name&gt; profile_data &lt;profilname&gt; &lt;json data&gt; </code><br>
Es wird das Profil 'profilname' geändert. Die Profildaten müssen im json-Format übergeben werden.
</li>
<li>send_to_device<br>
<code>set &lt;name&gt; send_to_device &lt;profilname&gt; [device] </code><br>
Das Profil wird an ein Gerät übertragen. Wird kein Gerät angegeben, wird das 'Master-Gerät' verwendet.
</li>
<li>copy_profile<br>
<code>set &lt;name&gt; copy_profile &lt;quelle&gt; &lt;ziel&gt; </code><br>
Kopiert das Profil 'quelle' auf 'ziel'. 'ziel' wird überschrieben oder neu angelegt.
</li>
<li>remove_profile<br>
<code>set &lt;name&gt; remove_profile &lt;profilname&gt; </code><br>
Das Profil 'profilname' wird gelöscht.
</li>
</ul>
<a name="weekprofileget"></a>
<b>Get</b>
<ul>
<li>profile_data<br>
<code>get &lt;name&gt; profile_data &lt;profilname&gt; </code><br>
Liefert die Profildaten von 'profilname' im json-Format
</li>
<li>profile_names<br>
<code>set &lt;name&gt; profile_names</code><br>
Liefert alle Profilnamen getrennt durch ','
</li>
</ul>
<a name="weekprofileattr"></a>
<b>Attribute</b>
<ul>
<li>widgetWeekdays<br>
Liste von Wochentagen getrennt durch ',' welche im Widget angzeigt werden.
Beginnend bei Montag. z.B.
<code>attr name widgetWeekdays Montag,Dienstag,Mittwoch,Donnerstag,Freitag,Samstag,Sonntag</code>
</li>
<li>configFile<br>
Pfad und Dateiname wo die Profile gespeichert werden sollen.
Default: ./log/weekprofile-<name>.cfg
</li>
<li>icon<br>
Änders des Icons zum Bearbeiten
Default: edit_settings
</li>
</ul>
</ul>
=end html
=cut

View File

@ -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

View File

@ -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

View File

@ -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 = $("<div id='FW_weekprofileInputDiolog'>");
var content = $('<input type="'+inp+'">').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("<span>Device:</span>","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("<span>Name:</span>","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("<p>Delete Profile: '"+widget.CURPRF+"'&nbsp;?</p>","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 += "&nbsp;"
html += "<select name=\"PROFILES\" onchange=\"FW_weekprofilePRFCached('"+widget.DEVICE+"',this)\">";
for (var k=0; k < widget.PROFILENAMES.length; k++)
{
var selected = (widget.CURPRF == widget.PROFILENAMES[k]) ? "selected " : "";
html += "<option "+selected+"value=\""+widget.PROFILENAMES[k]+"\">"+widget.PROFILENAMES[k]+"</option>";
}
html += "</select>";
html += "&nbsp;"
html += "<button type=\"button\"onclick=\"FW_weekprofileCopyPrf('"+widget.DEVICE+"',this)\" data-toggle=\"tooltip\" title=\"copy profile\">+</button>";
html += "&nbsp;"
html += "<button type=\"button\" onclick=\"FW_weekprofileRemovePrf('"+widget.DEVICE+"',this)\" data-toggle=\"tooltip\" title=\"remove profile\">-</button>";
html += "&nbsp;"
html += "<button type=\"button\" onclick=\"FW_weekprofileSendToDev('"+widget.DEVICE+"',this)\" data-toggle=\"tooltip\" title=\"send to device\">--></button>";
$(widget.MENU.CONTENT).append(html);
}
if (!widget.PROFILE) {
return;
}
var table = widget.CONTENT;
for (var i = 0; i < shortDays.length; ++i) {
$(table).append('<tr class="'+ ( (i+1)%2==0 ? 'even':'odd')+ '"><td>'+shortDays[i]+'</td></tr>');
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('<td>'+str+ '</td>');
str = widget.PROFILE[shortDays[i]]['temp'][k]+' °C';
$(tr).append('<td>'+str+ '</td>');
}
}
}
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 = $("<div>").get(0);
$(div).append("<div style=\"margin-left:10px;margin:5px\">"+widget.WEEKDAYS[day]+"</div>");
var table = $("<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<times.length-1) ? times[i] : "24:00";
html += "<tr>";
//from
html += "<td><span name=\"STARTTIME\">"+startTime+"</span></td>";
html += "<td>-</td>";
//to
html += "<td><input type=\"text\" name=\"ENDTIME\" size=\"5\" maxlength=\"5\" align=\"center\" value=\""+endTime+"\" onblur=\"FW_weekprofileEditTimeChanged(this)\"/></td>";
//temp
html += "<td><select name=\"TEMP\" size=\"1\">";
for (var k=5; k <= 30; k+=.5)
{
var selected = (k == temps[i]) ? "selected " : "";
html += "<option "+selected+"value=\""+k.toFixed(1)+"\">"+k.toFixed(1)+"</option>";
}
html += "</select></td>";
//ADD-Button
html += "<td><input type=\"button\" name=\"ADD\" value=\"+\" onclick=\"FW_weekprofileEditAddInterval(this.parentNode.parentNode)\"></td>";
//DEL-Button
html += "<td><input type=\"button\" name=\"DEL\" value=\"-\" onclick=\"FW_weekprofileEditDelInterval(this.parentNode.parentNode)\"></td>";
html += "</tr>";
}
$(table).append(html);
$(div).append(table);
FW_weekprofileEditRowStyle(table);
return div;
}
function FW_weekprofileEditWeek(widget)
{
var table = widget.CONTENT;
var daysInRow = 2;
$(table).append('<tr>');
var tr = $(table).find("tr:last");
for (var i = 0; i < shortDays.length; ++i) {
tr.append('<td>');
tr.find('td:last').append(FW_weekprofileEditDay(widget,i));
if ((i+1)%daysInRow == 0){
$('<tr>').insertAfter(tr);
tr = $(table).find("tr:last");
}
}
tr.append("<td><table><tr>");
tr = tr.find("tr:last");
tr.append("<td><input type=\"button\" value=\"Speichern\" onclick=\"FW_weekprofilePrepAndSendProf('"+widget.DEVICE+"')\">");
tr.append("<td><input type=\"button\" value=\"Abbrechen\" onclick=\"FW_weekprofileEditAbort('"+widget.DEVICE+"')\">");
}
function FW_weekprofileSendCallback(devName, data)
{
var widget = $('div[informid="'+devName+'"]').get(0);
if(!data.match(/^[\r\n]*$/)) // ignore empty answers
FW_okDialog('<pre>'+data+'</pre>',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 = $('<table class="block wide weekprofile" id="weekprofile_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 = $('<div class="devType" id="weekprofile.menu.content" style="display:inline;padding:0px;margin:0px;">').get(0);
$(widget.MENU.BASE).append(menu);
widget.MENU.CONTENT = menu;
//inform profile_count changed
var prfCnt = $('<div informid="'+devName+'-profile_count" style="display:none">').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,
};