mirror of
https://github.com/fhem/fhem-mirror.git
synced 2025-04-20 19:36:02 +00:00
new features added:
- added attribute windowSensor. Defines a list of window sensors to avoid swichting when a window is open. Supports sensors of type CUL_FHTTK - the time parameter of switches can now be a Perlfuncion like sunrise_abs(). - a new optional Parameter can change the language of profiles: de, en fr are possible - symbolic constants like eco on off on-for-timer now a Parameter can be added. For example on-for-timer:30. The colon ist replaced by space. git-svn-id: https://svn.fhem.de/fhem/trunk@4118 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
parent
c75eb852b2
commit
10d31cb047
@ -28,8 +28,7 @@ use warnings;
|
|||||||
use POSIX;
|
use POSIX;
|
||||||
|
|
||||||
#####################################
|
#####################################
|
||||||
sub
|
sub Heating_Control_Initialize($)
|
||||||
Heating_Control_Initialize($)
|
|
||||||
{
|
{
|
||||||
my ($hash) = @_;
|
my ($hash) = @_;
|
||||||
|
|
||||||
@ -38,12 +37,11 @@ Heating_Control_Initialize($)
|
|||||||
$hash->{UndefFn} = "Heating_Control_Undef";
|
$hash->{UndefFn} = "Heating_Control_Undef";
|
||||||
$hash->{GetFn} = "Heating_Control_Get";
|
$hash->{GetFn} = "Heating_Control_Get";
|
||||||
$hash->{UpdFn} = "Heating_Control_Update";
|
$hash->{UpdFn} = "Heating_Control_Update";
|
||||||
$hash->{AttrList}= "disable:0,1 ".
|
$hash->{AttrList}= "disable:0,1 windowSensor ".
|
||||||
$readingFnAttributes;
|
$readingFnAttributes;
|
||||||
}
|
}
|
||||||
|
################################################################################
|
||||||
sub
|
sub Heating_Control_Get($@)
|
||||||
Heating_Control_Get($@)
|
|
||||||
{
|
{
|
||||||
my ($hash, @a) = @_;
|
my ($hash, @a) = @_;
|
||||||
return "argument is missing" if(int(@a) != 2);
|
return "argument is missing" if(int(@a) != 2);
|
||||||
@ -60,39 +58,60 @@ Heating_Control_Get($@)
|
|||||||
}
|
}
|
||||||
return "$a[0] $reading => $value";
|
return "$a[0] $reading => $value";
|
||||||
}
|
}
|
||||||
|
################################################################################
|
||||||
|
sub Heating_Control_Define($$)
|
||||||
sub
|
|
||||||
Heating_Control_Define($$)
|
|
||||||
{
|
{
|
||||||
my ($hash, $def) = @_;
|
my ($hash, $def) = @_;
|
||||||
|
|
||||||
|
my %longDays = ( "de" => ["Sonntag", "Montag","Dienstag","Mittwoch", "Donnerstag","Freitag", "Samstag" ],
|
||||||
|
"en" => ["Sunday", "Monday","Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
|
||||||
|
"fr" => ["Dimanche", "Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi","Samedi" ]);
|
||||||
|
my %shortDays = ( "de" => ["so","mo","di","mi","do","fr","sa"],
|
||||||
|
"en" => ["su","mo","tu","we","th","fr","sa"],
|
||||||
|
"fr" => ["di","lu","ma","me","je","ve","sa"]);
|
||||||
|
|
||||||
my @a = split("[ \t]+", $def);
|
my @a = split("[ \t]+", $def);
|
||||||
|
|
||||||
return "Usage: define <name> $hash->{TYPE} <device> <switching times> <condition|command>"
|
return "Usage: define <name> $hash->{TYPE} <device> <language> <switching times> <condition|command>"
|
||||||
if(@a < 4);
|
if(@a < 4);
|
||||||
|
|
||||||
my $name = shift @a;
|
my $name = shift @a;
|
||||||
my $type = shift @a;
|
my $type = shift @a;
|
||||||
my $device = shift @a;
|
my $device = shift @a;
|
||||||
|
|
||||||
my @switchingtimes;
|
my @switchingtimes;
|
||||||
my $conditionOrCommand = "";
|
my $conditionOrCommand = "";
|
||||||
|
|
||||||
my @Wochentage_de = ("Sonntag", "Montag","Dienstag","Mittwoch", "Donnerstag","Freitag", "Samstag" );
|
# ggf. language optional Parameter
|
||||||
my @Wochentage_en = ("Sunday", "Monday","Tuesday", "Wednesday", "Thursday", "Friday", "Saturday");
|
my $language = shift @a;
|
||||||
my @Wochentage_fr = ("Dimanche", "Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi","Samedi");
|
my $langRegExp = "(";
|
||||||
|
foreach my $l (keys(%shortDays)) {
|
||||||
|
$langRegExp .= $l . "|";
|
||||||
|
}
|
||||||
|
$langRegExp =~ s/\|$//g;
|
||||||
|
$langRegExp .= ")";
|
||||||
|
if ($language =~ m/^$langRegExp$/g) {
|
||||||
|
$hash->{LANGUAGE} = $language;
|
||||||
|
} else {
|
||||||
|
Log3 $hash, 3, "[$name] illegal language: $language, use one of $langRegExp" if (length() == 2);
|
||||||
|
unshift (@a,$language) if (length($language) != 2) ;
|
||||||
|
$hash->{LANGUAGE} = "de";
|
||||||
|
}
|
||||||
|
$language = $hash->{LANGUAGE};
|
||||||
|
|
||||||
|
# test if device is defined
|
||||||
return "invalid Device, given Device <$device> not found" if(!$defs{$device});
|
return "invalid Device, given Device <$device> not found" if(!$defs{$device});
|
||||||
|
|
||||||
#Altlasten bereinigen
|
#Altlasten bereinigen
|
||||||
|
delete($hash->{TIME_AS_PERL}) if($hash->{TIME_AS_PERL});
|
||||||
delete($hash->{helper}{CONDITION}) if($hash->{helper}{CONDITION});
|
delete($hash->{helper}{CONDITION}) if($hash->{helper}{CONDITION});
|
||||||
delete($hash->{helper}{COMMAND}) if($hash->{helper}{COMMAND});
|
delete($hash->{helper}{COMMAND}) if($hash->{helper}{COMMAND});
|
||||||
delete($hash->{helper}{SWITCHINGTIMES}) if($hash->{helper}{SWITCHINGTIMES});
|
delete($hash->{helper}{SWITCHINGTIMES}) if($hash->{helper}{SWITCHINGTIMES});
|
||||||
delete($hash->{helper}{SWITCHINGTIME}) if($hash->{helper}{SWITCHINGTIME});
|
delete($hash->{helper}{SWITCHINGTIME}) if($hash->{helper}{SWITCHINGTIME});
|
||||||
for (my $w=0; $w<@Wochentage_de; $w++) {
|
foreach my $l (keys(%shortDays)) {
|
||||||
delete($hash->{"PROFILE ".($w).": ".$Wochentage_de[$w]}) if($hash->{"PROFILE ".($w).": ".$Wochentage_de[$w]});
|
for (my $w=0; $w<7; $w++) {
|
||||||
delete($hash->{"PROFILE ".($w).": ".$Wochentage_en[$w]}) if($hash->{"PROFILE ".($w).": ".$Wochentage_en[$w]});
|
delete($hash->{"PROFILE ".($w).": ".$longDays{$l}[$w]}) if($hash->{"PROFILE ".($w).": ".$longDays{$l}[$w]});
|
||||||
delete($hash->{"PROFILE ".($w).": ".$Wochentage_fr[$w]}) if($hash->{"PROFILE ".($w).": ".$Wochentage_fr[$w]});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for(my $i=0; $i<@a; $i++) {
|
for(my $i=0; $i<@a; $i++) {
|
||||||
@ -107,6 +126,8 @@ Heating_Control_Define($$)
|
|||||||
last;
|
last;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
# wenn keine switchintime angegeben ist, dann Fehler
|
||||||
|
Log3 $hash, 3, "no Switchingtime found in <$conditionOrCommand>, check first parameter" if (@switchingtimes == 0);
|
||||||
|
|
||||||
$hash->{NAME} = $name;
|
$hash->{NAME} = $name;
|
||||||
$hash->{DEVICE} = $device;
|
$hash->{DEVICE} = $device;
|
||||||
@ -118,30 +139,58 @@ Heating_Control_Define($$)
|
|||||||
$hash->{helper}{COMMAND} = $conditionOrCommand;
|
$hash->{helper}{COMMAND} = $conditionOrCommand;
|
||||||
}
|
}
|
||||||
|
|
||||||
my $daysRegExp = "(mo|di|mi|do|fr|sa|so|tu|we|th|su|lu|ma|me|je|ve)";
|
# jetzt die switchingtimes und Tagesangaben verarbeiten.
|
||||||
my $daysRegExp_en = "(tu|we|th|su)";
|
Heating_Control_ParseSwitchingProfile($hash, \@switchingtimes, \$shortDays{$language});
|
||||||
my $daysRegExp_fr = "(lu|ma|me|je|ve)";
|
|
||||||
|
# Profile sortiert aufbauen
|
||||||
|
for (my $d=0; $d<=6; $d++) {
|
||||||
|
foreach my $st (sort (keys %{ $hash->{helper}{SWITCHINGTIME}{$d} })) {
|
||||||
|
my $para = $hash->{helper}{SWITCHINGTIME}{$d}{$st};
|
||||||
|
$hash->{"PROFILE ".($d).": ".$longDays{$language}[$d]} .= sprintf("%s %s, ", substr ($st,0,5), $para);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RemoveInternalTimer($hash);
|
||||||
|
my $now = time();
|
||||||
|
InternalTimer ($now+30, "$hash->{TYPE}_Update", $hash, 0);
|
||||||
|
|
||||||
|
if (defined($hash->{TIME_AS_PERL})) {
|
||||||
|
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($now);
|
||||||
|
my $midnight = $now + 24*3600 -(3600*$hour + 60*$min + $sec) + 10*60;
|
||||||
|
InternalTimer ($midnight, "$hash->{TYPE}_UpdatePerlTime", $hash, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
readingsBeginUpdate ($hash);
|
||||||
|
readingsBulkUpdate ($hash, "nextUpdate", strftime("Heute, %H:%M:%S",localtime($now+30)));
|
||||||
|
readingsBulkUpdate ($hash, "nextValue", "???");
|
||||||
|
readingsBulkUpdate ($hash, "state", "waiting...");
|
||||||
|
readingsEndUpdate ($hash, defined($hash->{LOCAL} ? 0 : 1));
|
||||||
|
|
||||||
|
return undef;
|
||||||
|
}
|
||||||
|
################################################################################
|
||||||
|
sub Heating_Control_ParseSwitchingProfile($$$) {
|
||||||
|
my ($hash, $switchingtimes, $shortDays) = @_;
|
||||||
|
|
||||||
|
my $name = $hash->{NAME};
|
||||||
|
my $language = $hash->{LANGUAGE};
|
||||||
|
|
||||||
my %dayNumber=();
|
my %dayNumber=();
|
||||||
my $idx = 0;
|
my $daysRegExp = "(";
|
||||||
foreach my $day ("so","mo","di","mi","do","fr","sa") {
|
for(my $idx=0; $idx<7; $idx++) {
|
||||||
$dayNumber{$day} = $idx; $idx++;
|
my $day = @{$$shortDays}[$idx];
|
||||||
}
|
$dayNumber{$day} = $idx;
|
||||||
$idx = 0;
|
$daysRegExp .= $day . "|";
|
||||||
foreach my $day ("su","mo","tu","we","th","fr","sa") {
|
|
||||||
$dayNumber{$day} = $idx; $idx++;
|
|
||||||
}
|
|
||||||
$idx = 0;
|
|
||||||
foreach my $day ("di","lu","ma","me","je","ve","sa") {
|
|
||||||
$dayNumber{$day} = $idx; $idx++;
|
|
||||||
}
|
}
|
||||||
|
$daysRegExp =~ s/\|$//g;
|
||||||
|
$daysRegExp .= ")";
|
||||||
|
|
||||||
my (@st, @days, $daylist, $time, $para, $englisch, $french);
|
my (@st, @days, $daylist, $time, $para);
|
||||||
for(my $i=0; $i<@switchingtimes; $i++) {
|
for(my $i=0; $i<@{$switchingtimes}; $i++) {
|
||||||
|
|
||||||
@st = split(/\|/, $switchingtimes[$i]);
|
@st = split(/\|/, @{$switchingtimes}[$i]);
|
||||||
if ( @st == 2) {
|
if ( @st == 2) {
|
||||||
$daylist = "1234567"; #jeden Tag/woche ist vordefiniert
|
$daylist = "1234567"; #jeden Tag/Woche ist vordefiniert
|
||||||
$time = $st[0];
|
$time = $st[0];
|
||||||
$para = $st[1];
|
$para = $st[1];
|
||||||
} elsif ( @st == 3) {
|
} elsif ( @st == 3) {
|
||||||
@ -152,7 +201,8 @@ Heating_Control_Define($$)
|
|||||||
|
|
||||||
my %hdays=();
|
my %hdays=();
|
||||||
|
|
||||||
#Aufzaehlung 1234 ...
|
# Angaben der Tage verarbeiten
|
||||||
|
# Aufzaehlung 1234 ...
|
||||||
if ( $daylist =~ m/^(\d){0,7}$/g) {
|
if ( $daylist =~ m/^(\d){0,7}$/g) {
|
||||||
|
|
||||||
$daylist =~ s/7/0/g;
|
$daylist =~ s/7/0/g;
|
||||||
@ -166,8 +216,6 @@ Heating_Control_Define($$)
|
|||||||
for (;length($daylist);) {
|
for (;length($daylist);) {
|
||||||
my $day = substr($daylist,0,2,"");
|
my $day = substr($daylist,0,2,"");
|
||||||
my $del = substr($daylist,0,1,"");
|
my $del = substr($daylist,0,1,"");
|
||||||
$englisch = ($day =~ m/^($daysRegExp_en)$/g);
|
|
||||||
$french = ($day =~ m/^($daysRegExp_fr)$/g);
|
|
||||||
my @subDays;
|
my @subDays;
|
||||||
if ($oldDel eq "-" ){
|
if ($oldDel eq "-" ){
|
||||||
# von bis Angabe: Mo-Di
|
# von bis Angabe: Mo-Di
|
||||||
@ -176,7 +224,8 @@ Heating_Control_Define($$)
|
|||||||
if ($low <= $high) {
|
if ($low <= $high) {
|
||||||
@subDays = ($low .. $high);
|
@subDays = ($low .. $high);
|
||||||
} else {
|
} else {
|
||||||
@subDays = ($dayNumber{so} .. $high, $low .. $dayNumber{sa});
|
#@subDays = ($dayNumber{so} .. $high, $low .. $dayNumber{sa});
|
||||||
|
@subDays = ( 00 .. $high, $low .. 06);
|
||||||
}
|
}
|
||||||
@hdays{@subDays}=1;
|
@hdays{@subDays}=1;
|
||||||
} else {
|
} else {
|
||||||
@ -192,72 +241,129 @@ Heating_Control_Define($$)
|
|||||||
|
|
||||||
@days = sort(SortNumber keys %hdays);
|
@days = sort(SortNumber keys %hdays);
|
||||||
|
|
||||||
return "invalid time in $name <$time> HH:MM"
|
# Zeitangabe verarbeiten.
|
||||||
if(!($time =~ m/^[0-2][0-9]:[0-5][0-9]$/g));
|
if($time =~ m/^\{.*\}$/g) { # Perlausdruck {*}
|
||||||
|
$time = eval($time); # must deliver HH:MM[:SS]
|
||||||
|
$hash->{TIME_AS_PERL} = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($time =~ m/^[0-2][0-9]:[0-5][0-9]$/g) { # HH:MM
|
||||||
|
$time .= ":00"; # HH:MM:SS erzeugen
|
||||||
|
} elsif ($time =~ m/^[0-2][0-9](:[0-5][0-9]){2,2}$/g) { # HH:MM:SS
|
||||||
|
; # ok.
|
||||||
|
} else {
|
||||||
|
Log3 $hash, 1, "[$name] invalid time in $name <$time> HH:MM[:SS]"
|
||||||
|
}
|
||||||
|
|
||||||
for (my $d=0; $d<@days; $d++) {
|
for (my $d=0; $d<@days; $d++) {
|
||||||
#Log3 $hash, 3, "Switchingtime: $switchingtimes[$i] : $days[$d] -> $time -> $para ";
|
Log3 $hash, 5, "[$name] Switchingtime: @{$switchingtimes}[$i] : $days[$d] -> $time -> $para ";
|
||||||
$hash->{helper}{SWITCHINGTIME}{$days[$d]}{$time} = $para;
|
$hash->{helper}{SWITCHINGTIME}{$days[$d]}{$time} = $para;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
my $rWochentage;
|
|
||||||
if ($englisch) {
|
|
||||||
$rWochentage = \@Wochentage_en;
|
|
||||||
} elsif ($french) {
|
|
||||||
$rWochentage = \@Wochentage_fr;
|
|
||||||
} else {
|
|
||||||
$rWochentage = \@Wochentage_de;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Profile sortiert aufbauen
|
|
||||||
for (my $d=0; $d<=6; $d++) {
|
|
||||||
foreach my $st (sort (keys %{ $hash->{helper}{SWITCHINGTIME}{$d} })) {
|
|
||||||
my $para = $hash->{helper}{SWITCHINGTIME}{$d}{$st};
|
|
||||||
$hash->{"PROFILE ".($d).": ".$$rWochentage[$d]} .= sprintf("%s %s, ", $st, $para);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RemoveInternalTimer($hash);
|
|
||||||
my $now = time();
|
|
||||||
InternalTimer ($now+30, "$hash->{TYPE}_Update", $hash, 0);
|
|
||||||
|
|
||||||
readingsBeginUpdate ($hash);
|
|
||||||
readingsBulkUpdate ($hash, "nextUpdate", strftime("Heute, %H:%M:%S",localtime($now+30)));
|
|
||||||
readingsBulkUpdate ($hash, "nextValue", "???");
|
|
||||||
readingsBulkUpdate ($hash, "state", "waiting...");
|
|
||||||
readingsEndUpdate ($hash, defined($hash->{LOCAL} ? 0 : 1));
|
|
||||||
|
|
||||||
return undef;
|
|
||||||
}
|
}
|
||||||
|
################################################################################
|
||||||
sub
|
sub Heating_Control_Undef($$) {
|
||||||
Heating_Control_Undef($$)
|
|
||||||
{
|
|
||||||
my ($hash, $arg) = @_;
|
my ($hash, $arg) = @_;
|
||||||
|
|
||||||
RemoveInternalTimer($hash);
|
RemoveInternalTimer($hash);
|
||||||
delete $modules{$hash->{TYPE}}{defptr}{$hash->{NAME}};
|
delete $modules{$hash->{TYPE}}{defptr}{$hash->{NAME}};
|
||||||
return undef;
|
return undef;
|
||||||
}
|
}
|
||||||
|
################################################################################
|
||||||
sub
|
sub Heating_Control_UpdatePerlTime($)
|
||||||
Heating_Control_Update($)
|
{
|
||||||
|
my ($hash) = @_;
|
||||||
|
Heating_Control_Define($hash, $hash->{NAME} . " " . $hash->{TYPE} . " " . $hash->{DEF} );
|
||||||
|
}
|
||||||
|
################################################################################
|
||||||
|
sub Heating_Control_Update($)
|
||||||
{
|
{
|
||||||
my ($hash) = @_;
|
my ($hash) = @_;
|
||||||
my $mod = "[".$hash->{NAME} ."] ";
|
my $mod = "[".$hash->{NAME} ."] ";
|
||||||
|
my $name = $hash->{NAME};
|
||||||
my $now = time() + 5; # garantiert > als die eingestellte Schlatzeit
|
my $now = time() + 5; # garantiert > als die eingestellte Schlatzeit
|
||||||
|
|
||||||
|
# Fenserkontakte abfragen - wenn einer im Status closed, dann Schaltung um 60 Sekunden verzögern
|
||||||
|
if (Heating_Control_FensterOffen($hash)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
# Schaltparameter ermitteln
|
||||||
|
my ($nowSwitch,$nextSwitch,$aktParam,$newParam,$nextParam)
|
||||||
|
= Heating_Control_akt_next_param($now, $hash);
|
||||||
|
# ggf. Device schalten
|
||||||
|
if ($nowSwitch gt "" && $aktParam ne $newParam ) {
|
||||||
|
Heating_Control_Device_Schalten($hash, $now, $nowSwitch, $newParam)
|
||||||
|
}
|
||||||
|
# Timer und Readings setzen.
|
||||||
|
InternalTimer ($nextSwitch, "$hash->{TYPE}_Update", $hash, 0);
|
||||||
|
|
||||||
|
my $active = 1;
|
||||||
|
if (defined $hash->{helper}{CONDITION}) {
|
||||||
|
$active = eval ($hash->{helper}{CONDITION});
|
||||||
|
}
|
||||||
|
readingsBeginUpdate($hash);
|
||||||
|
readingsBulkUpdate ($hash, "nextUpdate", strftime("%d.%m.%Y %H:%M:%S",localtime($nextSwitch)));
|
||||||
|
readingsBulkUpdate ($hash, "nextValue", $nextParam);
|
||||||
|
readingsBulkUpdate ($hash, "state", $active ? $newParam : "inactive" );
|
||||||
|
readingsEndUpdate ($hash, defined($hash->{LOCAL} ? 0 : 1));
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
################################################################################
|
||||||
|
sub Heating_Control_FensterOffen ($) {
|
||||||
|
my ($hash) = @_;
|
||||||
|
my $mod = "[".$hash->{NAME} ."]";
|
||||||
|
|
||||||
|
my %contacts = ( "CUL_FHTTK" => { "READING" => "Window", "STATUS" => "Open" },
|
||||||
|
"HM" => { "READING" => "hmw", "STATUS" => "hmo" },
|
||||||
|
"MAX" => { "READING" => "maxw", "STATUS" => "maxs"});
|
||||||
|
|
||||||
|
my $fensterKontakte = AttrVal($hash->{NAME}, "windowSensor", "nF");
|
||||||
|
Log3 $hash, 5, "$mod list of windowsenors found: '$fensterKontakte'";
|
||||||
|
if ($fensterKontakte ne "nF" ) {
|
||||||
|
my @kontakte = split(/ /, $fensterKontakte);
|
||||||
|
foreach my $fk (@kontakte) {
|
||||||
|
if(!$defs{$fk}) {
|
||||||
|
Log3 $hash, 3, "$mod Window sensor <$fk> not found - check name.";
|
||||||
|
} else {
|
||||||
|
my $fk_hash = $defs{$fk};
|
||||||
|
my $fk_typ = $fk_hash->{TYPE};
|
||||||
|
if (!defined($contacts{$fk_typ})) {
|
||||||
|
Log3 $hash, 3, "$mod TYPE '$fk_typ' of $fk not yet supported, $fk ignored - inform maintainer";
|
||||||
|
} else {
|
||||||
|
my $reading = $contacts{$fk_typ}{READING};
|
||||||
|
my $status = $contacts{$fk_typ}{STATUS};
|
||||||
|
my $windowStatus = ReadingsVal($fk,$reading,"nF");
|
||||||
|
if ($windowStatus eq "nF") {
|
||||||
|
Log3 $hash, 3, "$mod READING '$reading' of $fk not found, $fk ignored - inform maintainer";
|
||||||
|
} else {
|
||||||
|
Log3 $hash, 5, "$mod windowsensor '$fk' Reading '$reading' is '$windowStatus'";
|
||||||
|
if ($windowStatus eq $status) {
|
||||||
|
Log3 $hash, 3, "$mod switch of $hash->{DEVICE} delayed - windowsensor '$fk' Reading '$reading' is '$windowStatus'";
|
||||||
|
InternalTimer (time()+60, "$hash->{TYPE}_Update", $hash, 0);
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
################################################################################
|
||||||
|
sub Heating_Control_akt_next_param($$) {
|
||||||
|
my ($now, $hash) = @_;
|
||||||
|
|
||||||
|
my $mod = "[".$hash->{NAME} ."] ";
|
||||||
|
|
||||||
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($now);
|
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($now);
|
||||||
|
|
||||||
my $aktParam = ReadingsVal($hash->{DEVICE}, $hash->{helper}{DESIRED_TEMP_READING}, 0);
|
my $aktParam = ReadingsVal($hash->{DEVICE}, $hash->{helper}{DESIRED_TEMP_READING}, 0);
|
||||||
$aktParam = sprintf("%.1f", $aktParam) if ($aktParam =~ m/^[0-9]{1,3}$/i);
|
$aktParam = sprintf("%.1f", $aktParam) if ($aktParam =~ m/^[0-9]{1,3}$/i);
|
||||||
my $newParam = $aktParam; #default#
|
my $newParam = $aktParam; #default#
|
||||||
my $nextParam = 0;
|
|
||||||
my $next = 0;
|
|
||||||
my $nextSwitch = 0;
|
|
||||||
my $nowSwitch = 0;
|
|
||||||
|
|
||||||
|
my ($nextParam, $next, $nextSwitch, $nowSwitch) = (0,0,0,0);
|
||||||
|
# aktuellen und nächsten Schaltzeitpunkt ermitteln.
|
||||||
my $startIdx;
|
my $startIdx;
|
||||||
for (my $d=-1; $d>=-7; $d--) {
|
for (my $d=-1; $d>=-7; $d--) {
|
||||||
my $wd = ($d+$wday) % 7;
|
my $wd = ($d+$wday) % 7;
|
||||||
@ -273,7 +379,8 @@ Heating_Control_Update($)
|
|||||||
foreach my $st (sort (keys %{ $hash->{helper}{SWITCHINGTIME}{$wd} })) {
|
foreach my $st (sort (keys %{ $hash->{helper}{SWITCHINGTIME}{$wd} })) {
|
||||||
|
|
||||||
# Tagediff + Sekunden des Tages addieren
|
# Tagediff + Sekunden des Tages addieren
|
||||||
my $secondsToSwitch = $d*24*3600 + 3600*(int(substr($st,0,2)) - $hour) + 60*(int(substr($st,3,2)) - $min ) - $sec;
|
my @t = split(/:/, $st); # HH MM SS
|
||||||
|
my $secondsToSwitch = $d*24*3600 + 3600*($t[0] - $hour) + 60*($t[1] - $min) + $t[2] - $sec;
|
||||||
my $next = $now + $secondsToSwitch;
|
my $next = $now + $secondsToSwitch;
|
||||||
|
|
||||||
if ($secondsToSwitch<=10 && $secondsToSwitch>=-20) {
|
if ($secondsToSwitch<=10 && $secondsToSwitch>=-20) {
|
||||||
@ -292,23 +399,24 @@ Heating_Control_Update($)
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
my $name = $hash->{NAME};
|
|
||||||
my $command;
|
|
||||||
|
|
||||||
#$nextSwitch += get_SummerTimeOffset($now, $nextSwitch);
|
#$nextSwitch += get_SummerTimeOffset($now, $nextSwitch);
|
||||||
Log3 $hash, 4, $mod .strftime('%d.%m.%Y %H:%M:%S',localtime($nowSwitch))." ; AktDesiredTemp: $aktParam ; newDesTemperature: $newParam";
|
Log3 $hash, 4, $mod .strftime('%d.%m.%Y %H:%M:%S',localtime($nowSwitch))." ; aktParam: $aktParam ; newParam: $newParam";
|
||||||
Log3 $hash, 4, $mod .strftime('%d.%m.%Y %H:%M:%S',localtime($nextSwitch));
|
Log3 $hash, 4, $mod .strftime('%d.%m.%Y %H:%M:%S',localtime($nextSwitch));
|
||||||
|
return ($nowSwitch,$nextSwitch,$aktParam,$newParam,$nextParam);
|
||||||
|
}
|
||||||
|
################################################################################
|
||||||
|
sub Heating_Control_Device_Schalten($$$$) {
|
||||||
|
my ($hash, $now, $nowSwitch, $newParam) = @_;
|
||||||
|
|
||||||
|
my $command = "";
|
||||||
|
my $mod = "[".$hash->{NAME} ."] ";
|
||||||
|
|
||||||
#modifier des Zieldevices auswaehlen
|
#modifier des Zieldevices auswaehlen
|
||||||
my %modifier = ("MAX" => "desiredTemperature",
|
my %modifier = ("MAX"=>"desiredTemperature","FHT"=>"desired-temp","CUL_HM"=>"desired-temp","FS20"=>"");
|
||||||
"FHT" => "desired-temp",
|
|
||||||
"FS20" => "",
|
|
||||||
"CUL_HM" => "desired-temp");
|
|
||||||
$hash->{helper}{DESIRED_TEMP_READING} = "";
|
$hash->{helper}{DESIRED_TEMP_READING} = "";
|
||||||
$hash->{helper}{DESIRED_TEMP_READING} = $modifier{$defs{$hash->{DEVICE}}{TYPE}};
|
$hash->{helper}{DESIRED_TEMP_READING} = $modifier{$defs{$hash->{DEVICE}}{TYPE}};
|
||||||
|
|
||||||
if ($nowSwitch gt "" && $aktParam ne $newParam ) {
|
#if ($nowSwitch gt "" && $aktParam ne $newParam ) {
|
||||||
if (defined $hash->{helper}{CONDITION}) {
|
if (defined $hash->{helper}{CONDITION}) {
|
||||||
$command = '{ fhem("set @ '.$hash->{helper}{DESIRED_TEMP_READING}.' %") if' . $hash->{helper}{CONDITION} . '}';
|
$command = '{ fhem("set @ '.$hash->{helper}{DESIRED_TEMP_READING}.' %") if' . $hash->{helper}{CONDITION} . '}';
|
||||||
} elsif (defined $hash->{helper}{COMMAND}) {
|
} elsif (defined $hash->{helper}{COMMAND}) {
|
||||||
@ -316,9 +424,11 @@ Heating_Control_Update($)
|
|||||||
} else {
|
} else {
|
||||||
$command = '{ fhem("set @ '.$hash->{helper}{DESIRED_TEMP_READING}.' %") }';
|
$command = '{ fhem("set @ '.$hash->{helper}{DESIRED_TEMP_READING}.' %") }';
|
||||||
}
|
}
|
||||||
}
|
#}
|
||||||
|
|
||||||
|
#Kommando ausführen
|
||||||
my $secondsSinceSwitch = $nowSwitch - $now;
|
my $secondsSinceSwitch = $nowSwitch - $now;
|
||||||
|
# toDo besser eine Positivliste mit Geraeten, die geschaltet werden sollen
|
||||||
my %vergangenheitNichtSchalten = ("FS20" => 1);
|
my %vergangenheitNichtSchalten = ("FS20" => 1);
|
||||||
if ($vergangenheitNichtSchalten{$defs{$hash->{DEVICE}}{TYPE}} && $secondsSinceSwitch < -60) {
|
if ($vergangenheitNichtSchalten{$defs{$hash->{DEVICE}}{TYPE}} && $secondsSinceSwitch < -60) {
|
||||||
;
|
;
|
||||||
@ -333,24 +443,8 @@ Heating_Control_Update($)
|
|||||||
Log3 ($hash, 3, $ret) if($ret);
|
Log3 ($hash, 3, $ret) if($ret);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
my $active = 1;
|
|
||||||
if (defined $hash->{helper}{CONDITION}) {
|
|
||||||
$active = eval ($hash->{helper}{CONDITION});
|
|
||||||
}
|
|
||||||
|
|
||||||
RemoveInternalTimer($hash);
|
|
||||||
InternalTimer($nextSwitch, "$hash->{TYPE}_Update", $hash, 0);
|
|
||||||
|
|
||||||
readingsBeginUpdate($hash);
|
|
||||||
readingsBulkUpdate ($hash, "nextUpdate", strftime("%d.%m.%Y %H:%M:%S",localtime($nextSwitch)));
|
|
||||||
readingsBulkUpdate ($hash, "nextValue", $nextParam);
|
|
||||||
readingsBulkUpdate ($hash, "state", $active ? $newParam : "inactive" );
|
|
||||||
readingsEndUpdate ($hash, defined($hash->{LOCAL} ? 0 : 1));
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
}
|
||||||
#
|
################################################################################
|
||||||
sub Heating_Control_SetAllTemps() { # {Heating_Control_SetAllTemps()}
|
sub Heating_Control_SetAllTemps() { # {Heating_Control_SetAllTemps()}
|
||||||
|
|
||||||
foreach my $hc ( sort keys %{$modules{Heating_Control}{defptr}} ) {
|
foreach my $hc ( sort keys %{$modules{Heating_Control}{defptr}} ) {
|
||||||
@ -367,7 +461,7 @@ sub Heating_Control_SetAllTemps() { # {Heating_Control_SetAllTemps()}
|
|||||||
}
|
}
|
||||||
Log3 undef, 3, "Heating_Control_SetAllTemps() done!";
|
Log3 undef, 3, "Heating_Control_SetAllTemps() done!";
|
||||||
}
|
}
|
||||||
|
################################################################################
|
||||||
sub SortNumber {
|
sub SortNumber {
|
||||||
if($a < $b)
|
if($a < $b)
|
||||||
{ return -1; }
|
{ return -1; }
|
||||||
@ -390,7 +484,7 @@ sub SortNumber {
|
|||||||
<a name="Heating_Controldefine"></a>
|
<a name="Heating_Controldefine"></a>
|
||||||
<b>Define</b>
|
<b>Define</b>
|
||||||
<ul>
|
<ul>
|
||||||
<code>define <name> Heating_Control <device> <profile> <command>|<condition></code>
|
<code>define <name> Heating_Control <device> [<language>] <profile> <command>|<condition></code>
|
||||||
<br><br>
|
<br><br>
|
||||||
|
|
||||||
to set a weekly profile for <device>, eg. a heating sink.<br>
|
to set a weekly profile for <device>, eg. a heating sink.<br>
|
||||||
@ -410,14 +504,20 @@ sub SortNumber {
|
|||||||
The device to switch at the given time.
|
The device to switch at the given time.
|
||||||
</ul>
|
</ul>
|
||||||
<p>
|
<p>
|
||||||
|
<ul><b>language</b><br>
|
||||||
|
Specifies the language used for definition and profiles.
|
||||||
|
de,en,fr are possible. The parameter is optional.
|
||||||
|
</ul>
|
||||||
|
<p>
|
||||||
<ul><b>profile</b><br>
|
<ul><b>profile</b><br>
|
||||||
Define the weekly profile. All timings are separated by space. One switchingtime are defined
|
Define the weekly profile. All timings are separated by space. One switchingtime are defined
|
||||||
by the following example: <br>
|
by the following example: <br>
|
||||||
<ul><b>[<weekdays>|]<time>|<parameter></b></ul><br>
|
<ul><b>[<weekdays>|]<time>|<parameter></b></ul><br>
|
||||||
<u>weekdays:</u> optional, if not set every day is using.<br>
|
<u>weekdays:</u> optional, if not set every day is using.<br>
|
||||||
Otherwise you can define one day as number or as shortname.<br>
|
Otherwise you can define one day as number or as shortname.<br>
|
||||||
<u>time:</u>define the time to switch, format: HH:MM(HH in 24 hour format)<br>
|
<u>time:</u>define the time to switch, format: HH:MM:[SS](HH in 24 hour format) or a Perlfunction like {sunrise_abs()}<br>
|
||||||
<u>parameter:</u>the temperature to be set, using a float with mask 99.9 or a sybolic value like <b>eco</b> or <b>comfort</b> - whatever your thermostat understands<br>
|
<u>parameter:</u>the temperature to be set, using a float with mask 99.9 or a sybolic value like <b>eco</b> or <b>comfort</b> - whatever your thermostat understands.
|
||||||
|
The symbolic value can be added an additional parameter: dayTemp:16 night-temp:15. See examples <br>
|
||||||
</ul>
|
</ul>
|
||||||
<p>
|
<p>
|
||||||
<ul><b>command</b><br>
|
<ul><b>command</b><br>
|
||||||
@ -450,6 +550,12 @@ sub SortNumber {
|
|||||||
|
|
||||||
<code>define HCW Heating_Control WZ_Heizung Sa-Su,We|08:00|21 (ReadingsVal("WeAreThere", "state", "no") eq "yes")</code><br>
|
<code>define HCW Heating_Control WZ_Heizung Sa-Su,We|08:00|21 (ReadingsVal("WeAreThere", "state", "no") eq "yes")</code><br>
|
||||||
The temperature is only set if the dummy variable WeAreThere is "yes".<p>
|
The temperature is only set if the dummy variable WeAreThere is "yes".<p>
|
||||||
|
|
||||||
|
<code>define HCW Heating_Control WZ_Heizung en Su-Fr|{sunrise_abs()}|21 Mo-Fr|{sunset_abs()}|16</code><br>
|
||||||
|
The device is switched at sunrise/sunset. Language: english.
|
||||||
|
|
||||||
|
<code>define HCW Heating_Control WZ_Heizung en Mo-Fr|{myFunction}|night-temp:18 Mo-Fr|{myFunction()}|dayTemp:16</code><br>
|
||||||
|
The is switched at time myFunction(). It is sent the Command "night-temp 18" and "dayTemp 16".
|
||||||
</ul>
|
</ul>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
@ -466,7 +572,7 @@ sub SortNumber {
|
|||||||
<li><a href="#event-on-update-reading">event-on-update-reading</a></li>
|
<li><a href="#event-on-update-reading">event-on-update-reading</a></li>
|
||||||
<li><a href="#event-on-change-reading">event-on-change-reading</a></li>
|
<li><a href="#event-on-change-reading">event-on-change-reading</a></li>
|
||||||
<li><a href="#stateFormat">stateFormat</a></li>
|
<li><a href="#stateFormat">stateFormat</a></li>
|
||||||
</ul><br>
|
<li>windowSensor<br>Defines a list of window sensors. When one of its window state readings is <b>open</b> the aktual switch is delayed.</li> </ul><br>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
=end html
|
=end html
|
||||||
@ -480,7 +586,7 @@ sub SortNumber {
|
|||||||
<a name="Heating_Controldefine"></a>
|
<a name="Heating_Controldefine"></a>
|
||||||
<b>Define</b>
|
<b>Define</b>
|
||||||
<ul>
|
<ul>
|
||||||
<code>define <name> Heating_Control <device> <profile> <command>|<condition></code>
|
<code>define <name> Heating_Control <device> [<language>] <profile> <command>|<condition></code>
|
||||||
<br><br>
|
<br><br>
|
||||||
|
|
||||||
Bildet ein Wochenprofil für ein <device>, zb. Heizkörper, ab.<br>
|
Bildet ein Wochenprofil für ein <device>, zb. Heizkörper, ab.<br>
|
||||||
@ -496,7 +602,12 @@ sub SortNumber {
|
|||||||
<p>
|
<p>
|
||||||
Folgende Parameter sind im Define definiert:
|
Folgende Parameter sind im Define definiert:
|
||||||
<ul><b>device</b><br>
|
<ul><b>device</b><br>
|
||||||
Das an den Schaltpunkten zu schaltende Device.
|
Name des zu schaltenden Device.
|
||||||
|
</ul>
|
||||||
|
<p>
|
||||||
|
<ul><b>language</b><br>
|
||||||
|
Spezifiziert die Sprache für die Definition und die Anzeige der Profile in der Weboberfläche.
|
||||||
|
Zurzeit sind de,en,fr definiert. Der Parameter ist optional.
|
||||||
</ul>
|
</ul>
|
||||||
<p>
|
<p>
|
||||||
<ul><b>profile</b><br>
|
<ul><b>profile</b><br>
|
||||||
@ -506,9 +617,10 @@ sub SortNumber {
|
|||||||
<u>Wochentage:</u> optionale Angabe, falls nicht gesetzt wird der Schaltpunkt jeden Tag ausgeführt.
|
<u>Wochentage:</u> optionale Angabe, falls nicht gesetzt wird der Schaltpunkt jeden Tag ausgeführt.
|
||||||
Für die Tage an denen dieser Schaltpunkt aktiv sein soll, ist jeder Tag mit seiner
|
Für die Tage an denen dieser Schaltpunkt aktiv sein soll, ist jeder Tag mit seiner
|
||||||
Tagesnummer (Mo=1, ..., So=7) oder Name des Tages (Mo, Di, ..., So) einzusetzen.<br>
|
Tagesnummer (Mo=1, ..., So=7) oder Name des Tages (Mo, Di, ..., So) einzusetzen.<br>
|
||||||
<u>Uhrzeit:</u>Angabe der Uhrzeit an dem geschaltet werden soll, Format: HH:MM(HH im 24 Stunden format)<br>
|
<u>Uhrzeit:</u>Angabe der Uhrzeit zu der geschaltet werden soll, Format: HH:MM:[SS](HH im 24 Stunden Format) oder eine Perlfunction wie {sunrise_abs()}<br>
|
||||||
<u>Parameter:</u>Angabe der zu setzenden Temperatur als Zahl mit Format 99.9 oder als symbolische Konstante <b>eco</b>
|
<u>Parameter:</u>Angabe der zu setzenden Temperatur als Zahl mit Format 99.9 oder als symbolische Konstante <b>eco</b>
|
||||||
or <b>comfort</b> - was immer das Heizkörperthermostat versteht.<br>
|
or <b>comfort</b> - was immer das Heizkörperthermostat versteht.
|
||||||
|
Symbolischen Werten kann ein zusätzlicher Parameter angehängt werden: dayTemp:16 night-temp:15. Unten folgen Beispiele<br><br>
|
||||||
</ul>
|
</ul>
|
||||||
<p>
|
<p>
|
||||||
<ul><b>command</b><br>
|
<ul><b>command</b><br>
|
||||||
@ -532,7 +644,7 @@ sub SortNumber {
|
|||||||
<p>
|
<p>
|
||||||
<b>Beispiel:</b>
|
<b>Beispiel:</b>
|
||||||
<ul>
|
<ul>
|
||||||
<code>define HCB Heating_Control Bad_Heizung 12345|05:20|21 12345|05:25|comfort 17:20|21 17:25|eco</code><br>
|
<code>define HCW Heating_Control Bad_Heizung 12345|05:20|21 12345|05:25|comfort 17:20|21 17:25|eco</code><br>
|
||||||
Mo-Fr wird die Temperatur um 05:20Uhr auf 21°C, und um 05:25Uhr auf <b>comfort</b> gesetzt.
|
Mo-Fr wird die Temperatur um 05:20Uhr auf 21°C, und um 05:25Uhr auf <b>comfort</b> gesetzt.
|
||||||
Jeden Tag wird die Temperatur um 17:20Uhr auf 21°C und 17:25Uhr auf <b>eco</b> gesetzt.<p>
|
Jeden Tag wird die Temperatur um 17:20Uhr auf 21°C und 17:25Uhr auf <b>eco</b> gesetzt.<p>
|
||||||
|
|
||||||
@ -542,6 +654,13 @@ sub SortNumber {
|
|||||||
|
|
||||||
<code>define HCW Heating_Control WZ_Heizung Sa-So,Mi|08:00|21 (ReadingsVal("WeAreThere", "state", "no") eq "yes")</code><br>
|
<code>define HCW Heating_Control WZ_Heizung Sa-So,Mi|08:00|21 (ReadingsVal("WeAreThere", "state", "no") eq "yes")</code><br>
|
||||||
Die zu setzende Temperatur wird nur gesetzt, falls die Dummy Variable WeAreThere = "yes" ist.<p>
|
Die zu setzende Temperatur wird nur gesetzt, falls die Dummy Variable WeAreThere = "yes" ist.<p>
|
||||||
|
|
||||||
|
<code>define HCW Heating_Control WZ_Heizung en Su-Fr|{sunrise_abs()}|21 Mo-Fr|{sunset_abs()}|16</code><br>
|
||||||
|
Das Gerät wird bei Sonnenaufgang und Sonnenuntergang geschaltet. Sprache: Englisch.
|
||||||
|
|
||||||
|
<code>define HCW Heating_Control WZ_Heizung en Mo-Fr|{myFunction}|night-temp:18 Mo-Fr|{myFunction()}|dayTemp:16</code><br>
|
||||||
|
Das Gerät wird bei myFunction() geschaltet. Es wird das Kommando "night-temp 18" bzw. "dayTemp 16" gesendet.
|
||||||
|
<p>
|
||||||
</ul>
|
</ul>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
@ -558,6 +677,7 @@ sub SortNumber {
|
|||||||
<li><a href="#event-on-update-reading">event-on-update-reading</a></li>
|
<li><a href="#event-on-update-reading">event-on-update-reading</a></li>
|
||||||
<li><a href="#event-on-change-reading">event-on-change-reading</a></li>
|
<li><a href="#event-on-change-reading">event-on-change-reading</a></li>
|
||||||
<li><a href="#stateFormat">stateFormat</a></li>
|
<li><a href="#stateFormat">stateFormat</a></li>
|
||||||
|
<li>windowSensor<br>Definiert eine Liste mit Fensterkontakten. Wenn das Reading window state eines Fensterkontakts <b>open</b> ist, wird der aktuelle Schaltvorgang verzögert.</li>
|
||||||
</ul><br>
|
</ul><br>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
@ -32,6 +32,11 @@ WeekdayTimer_Initialize($)
|
|||||||
{
|
{
|
||||||
my ($hash) = @_;
|
my ($hash) = @_;
|
||||||
|
|
||||||
|
if(!$modules{Heating_Control}{LOADED} && -f "$attr{global}{modpath}/FHEM/98_Heating_Control.pm") {
|
||||||
|
my $ret = CommandReload(undef, "98_Heating_Control");
|
||||||
|
Log3 undef, 1, $ret if($ret);
|
||||||
|
}
|
||||||
|
|
||||||
# Consumer
|
# Consumer
|
||||||
$hash->{DefFn} = "WeekdayTimer_Define";
|
$hash->{DefFn} = "WeekdayTimer_Define";
|
||||||
$hash->{UndefFn} = "WeekdayTimer_Undef";
|
$hash->{UndefFn} = "WeekdayTimer_Undef";
|
||||||
@ -62,6 +67,14 @@ WeekdayTimer_Undef($$){
|
|||||||
return Heating_Control_Undef($hash, $arg);
|
return Heating_Control_Undef($hash, $arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub
|
||||||
|
WeekdayTimer_UpdatePerlTime($)
|
||||||
|
{
|
||||||
|
my ($hash) = @_;
|
||||||
|
|
||||||
|
Heating_Control_Define($hash, $hash->{NAME} . " " . $hash->{TYPE} . " " . $hash->{DEF} );
|
||||||
|
}
|
||||||
|
|
||||||
sub
|
sub
|
||||||
WeekdayTimer_Update($){
|
WeekdayTimer_Update($){
|
||||||
my ($hash) = @_;
|
my ($hash) = @_;
|
||||||
@ -80,7 +93,9 @@ sub WeekdayTimer_SetAllTemps() { # {WeekdayTimer_SetAllTemps()}
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
WeekdayTimer_Update($hash);
|
WeekdayTimer_Update($hash);
|
||||||
|
Log3 undef, 3, "WeekdayTimer_Update() for $hash->{NAME} done!";
|
||||||
}
|
}
|
||||||
|
Log3 undef, 3, "WeekdayTimer_SetAllTemps() done!";
|
||||||
}
|
}
|
||||||
|
|
||||||
1;
|
1;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user