|
From: <pf...@us...> - 2014-12-28 07:35:36
|
Revision: 2297
http://sourceforge.net/p/openautomation/code/2297
Author: pfry
Date: 2014-12-28 07:35:33 +0000 (Sun, 28 Dec 2014)
Log Message:
-----------
Aktualisierung
Modified Paths:
--------------
wiregate/plugin/generic/Heizungsregler.pl
Added Paths:
-----------
wiregate/plugin/generic/conf.d/Heizungsregler.conf2
Modified: wiregate/plugin/generic/Heizungsregler.pl
===================================================================
--- wiregate/plugin/generic/Heizungsregler.pl 2014-12-27 13:11:19 UTC (rev 2296)
+++ wiregate/plugin/generic/Heizungsregler.pl 2014-12-28 07:35:33 UTC (rev 2297)
@@ -4,6 +4,8 @@
# Wiregate-Plugin
# (c) 2012 Fry under the GNU Public License
+# COMPILE_PLUGIN
+
#$plugin_info{$plugname.'_cycle'}=0; return "deaktiviert";
use POSIX qw(floor strftime);
@@ -11,9 +13,16 @@
my $use_short_names=1; # 1 fuer GA-Kuerzel (erstes Wort des GA-Namens), 0 fuer die "nackte" Gruppenadresse
+sub groupaddress;
+
# Konfigfile seit dem letzten Mal geaendert?
my $conf="/etc/wiregate/plugin/generic/conf.d/$plugname";
$conf.='.conf' unless $conf=~s/\.pl$/.conf/;
+unless(-f $conf)
+{
+ plugin_log($plugname, "Config err: $conf nicht gefunden.");
+ exit;
+}
my $configtime=24*60*60*(-M $conf);
my $config_modified = ($configtime < $plugin_info{$plugname.'_configtime'}-1);
@@ -23,43 +32,39 @@
{ $event='restart'; } # Restart des daemons / Reboot
elsif ($plugin_info{$plugname.'_lastsaved'} > $plugin_info{$plugname.'_last'})
{ $event='modified'; } # Plugin modifiziert
-elsif (%msg)
-{
- $event='bus';
- unless($plugin_subscribe{$msg{dst}}{$plugname})
- {
- $event='cycle';
- }
- else
- {
- return if !$config_modified && $msg{apci} eq "A_GroupValue_Response";
- }
-} # Bustraffic
-elsif ($fh) { $event='socket'; } # Netzwerktraffic
+elsif (%msg) { $event='bus'; return if !$config_modified && $msg{apci} eq "A_GroupValue_Response"; } # Bustraffic
+#elsif ($fh) { $event='socket'; } # Netzwerktraffic
else { $event='cycle'; } # Zyklus
-# Konfigurationsfile einlesen
-my %house=();
-my $err=read_from_house_config();
-return $err if $err;
-
-# Initialisierung
-my %dyn=recall_from_plugin_info();
+# Rueckgabewert des Plugins
my $retval='';
+my $anynews=0;
my $t=time();
-if($event=~/restart|modified/)
+if($event=~/restart|modified/ || $config_modified || !defined $plugin_cache{$plugname}{house})
{
- # alle Variablen loeschen
+ my %house=();
+
+ # Konfigurationsfile einlesen
+ open CONFIG, "<$conf" || return "no config found";
+ my $lines = join '',<CONFIG>;
+ close CONFIG;
+ eval $lines;
+ return "config error: $@" if $@;
+
+ # Cleanup, wobei persistente dyn-Variablen (zB Solltemperaturen) gerettet werden:
+ my $dyn=recall_from_plugin_info(\%house);
for my $k (grep /^$plugname\_/, keys %plugin_info)
{
next if $k=~/^$plugname\_last/;
delete $plugin_info{$k};
}
- store_to_plugin_info(\%dyn);
+ store_to_plugin_info($dyn,\%house);
- $plugin_info{$plugname.'_configtime'}=$configtime;
-
+ # %house im Cache speichern, so muss nicht jedesmal das Config eingelesen werden
+ $plugin_cache{$plugname}{house}=\%house;
+ $plugin_cache{$plugname}{actuators}={}; # Status Stellventile
+
# Alle Controller-GAs abonnieren
for my $r (grep ref($house{$_}), keys %house)
{
@@ -70,69 +75,110 @@
$plugin_subscribe{groupaddress($house{$r}{reset})}{$plugname}=1 if defined $house{$r}{reset};
}
- $plugin_info{$plugname.'_cycle'}=$house{cycle};
+ $plugin_subscribe{groupaddress($house{control_switch})}{$plugname}=1 if defined $house{control_switch};
+ $plugin_subscribe{groupaddress($house{control_status})}{$plugname}=1 if defined $house{control_status};
+ $plugin_info{$plugname.'_control_status'}=1 unless defined $plugin_info{$plugname.'_control_status'};
- $retval.='initialisiert: ';
+ $plugin_info{$plugname.'_configtime'}=$configtime;
+
+ $retval.='initialisiert: '.(join ',', (grep ref($house{$_}), keys %house)).'; ';
$event='cycle';
}
+my $house=$plugin_cache{$plugname}{house};
+my $dyn=recall_from_plugin_info($house); # hier stehen bspw die persistenten Solltemperaturen
+
+#plugin_log($plugname, "event=$event");
+
# Zyklischer Aufruf - Regelung
if($event=~/cycle/)
{
+ return if $plugin_info{$plugname.'_control_status'}==0;
+
my $Vreq=undef;
- my $anynews=0;
- for my $r (sort grep ref($house{$_}), keys %house)
+ # Ist die Therme ueberhaupt an?
+ my $heat=knx_read($house->{heating},3000);
+ $heat=(defined $heat && $heat!~/^$house->{heating_off}$/i);
+ $retval.="Heating is OFF" unless $heat;
+ plugin_log($plugname, "Heating is OFF") unless $heat;
+
+ for my $r (sort grep ref($house->{$_}), keys %{$house})
{
-# plugin_log($plugname, "\$dyn{$r}{mode}==$dyn{$r}{mode}");
- if($dyn{$r}{mode} eq 'ON')
+ if($heat && $dyn->{$r}{mode} eq 'ON')
{
# PID-Regler
- my ($T,$T0,$U,$Vr)=PID($r);
+ my ($T,$T0,$U,$Vr)=PID($r,$house,$dyn);
$retval.=sprintf "$r\->%.1f(%.1f)%d%% ", $T?$T:"T?", $T0, 100*$U;
$anynews=1;
$Vreq=$Vr if defined $Vr && (!defined $Vreq || $Vr>$Vreq);
}
- elsif($dyn{$r}{mode} eq 'OPTIMIZE')
+ elsif($heat && $dyn->{$r}{mode} eq 'OPTIMIZE')
{
# Optimierung der PID-Parameter durch Ermittlung der Sprungantwort
- $retval.="$r\->".OPTIMIZE($r);
+ $retval.="$r\->".OPTIMIZE($r,$house,$dyn,$conf);
$anynews=1;
- $Vreq=$house{inflow_max} if defined $house{inflow_max};
+ $Vreq=$house->{inflow_max} if defined $house->{inflow_max};
}
- elsif($dyn{$r}{mode} eq 'OFF')
+ elsif(!$heat)
{
+ writeactuators($r,0,$house); # alle ventile AUS
+ }
+ else
+ {
+ writeactuators($r,0,$house); # alle ventile AUS
+ $dyn->{$r}{mode}='OFF';
$retval.="$r\->OFF ";
}
}
- if(defined $Vreq)
+ if($heat && defined $Vreq)
{
- $Vreq=$house{inflow_max} if defined $house{inflow_max} && $Vreq>$house{inflow_max};
- knx_write(groupaddress($house{inflow_control}),$Vreq,9.001) if defined $house{inflow_control};
- $retval.=sprintf "Vreq=%d", $Vreq;
+ $Vreq=$house->{inflow_max} if defined $house->{inflow_max} && $Vreq>$house->{inflow_max};
+# knx_write(groupaddress($house->{inflow_control}),$Vreq,9.001) if defined $house->{inflow_control};
+ $retval.=sprintf "KNX-would-be:Vreq=%d", $Vreq;
$anynews=1;
}
$retval=~s/\s*$//; # Space am Ende entfernen
+
+ $plugin_info{$plugname.'_cycle'}=$house->{cycle};
}
elsif($event=~/bus/)
{
- # Aufruf durch GA - neue Wunschtemperatur
+ # Aufruf durch GA
my $ga=$msg{dst};
+ # Jemand will den Heizungsregler an- oder ausschalten, oder den Schaltstatus erfahren
+ if($ga eq groupaddress($house->{control_switch}))
+ {
+ if($msg{apci} eq 'A_GroupValue_Write')
+ {
+ $plugin_info{$plugname.'_control_status'}=int($msg{value});
+ knx_write(groupaddress($house->{control_status}), $plugin_info{$plugname.'_control_status'});
+ $plugin_info{$plugname.'_cycle'}=1; # sofort zyklisch ausfuehren, um alle Ventile zu stellen
+ }
+ return $retval;
+ }
+ elsif($ga eq groupaddress($house->{control_status}))
+ {
+ knx_write(groupaddress($house->{control_status}), $plugin_info{$plugname.'_control_status'}, undef, 0x40)
+ if $msg{apci} eq 'A_GroupValue_Read';;
+ return $retval;
+ }
+
+ # Telegramm betrifft Wunschtemperatur (setzen oder lesen)
# erstmal den betroffenen Raum finden
- my @controls=(sort grep ref($house{$_}) && groupaddress($house{$_}{control}) eq $ga, keys %house);
- my @optimizes=(sort grep ref($house{$_}) && groupaddress($house{$_}{optimize}) eq $ga, keys %house);
- my @resets=(sort grep ref($house{$_}) && groupaddress($house{$_}{reset}) eq $ga, keys %house);
+ my @controls=(sort grep ref($house->{$_}) && groupaddress($house->{$_}{control}) eq $ga, keys %{$house});
+ my @optimizes=(sort grep ref($house->{$_}) && groupaddress($house->{$_}{optimize}) eq $ga, keys %{$house});
+ my @resets=(sort grep ref($house->{$_}) && groupaddress($house->{$_}{reset}) eq $ga, keys %{$house});
# Unbekannte GA de-abonnieren
unless(@controls || @optimizes || @resets)
{
# GA-Abonnement loeschen
+ plugin_log($plugname, "received $ga -> unsubscribed (".($plugin_subscribe{$ga}{$plugname} ? "was subscribed":"was not subscribed").")");
delete $plugin_subscribe{$ga}{$plugname};
- plugin_log($plugname, "received $ga -> unsubscribed");
-
return;
}
@@ -147,7 +193,7 @@
if(@controls)
{
$r=shift @controls;
- $action='control';
+ $action='control';
}
elsif(@resets)
{
@@ -166,65 +212,77 @@
# Wert des Telegramms, Modus des Reglers abholen
my $T0=0;
$T0 = $msg{value} if $action eq 'control' && defined $msg{value};
- my $mode=$dyn{$r}{mode};
+ my $mode=$dyn->{$r}{mode};
# Jemand moechte einen Sollwert wissen
if($action eq 'control' && $msg{apci} eq 'A_GroupValue_Read')
{
- $T0=$dyn{$r}{T0};
- $T0=$dyn{$r}{T0old} if $dyn{mode} eq 'OPTIMIZE';
+ $T0=$dyn->{$r}{T0};
+ $T0=$dyn->{$r}{T0old} if $dyn->{$r}{mode} eq 'OPTIMIZE';
knx_write($ga,$T0,9.001,0x40);
return;
}
- # spezielle Temperaturwerte sind 0=>OFF und -1=>OPTIMIZE
- if($action eq 'reset' || ($action eq 'control' && $T0==0))
+ # spezielle Temperaturwerte sind 0-15 Grad =>OFF und -1=>OPTIMIZE
+ if($action eq 'reset' || ($action eq 'control' && $T0>=0 && $T0<=15))
{
- RESET($r);
- writeactuators($r,0);
- $dyn{$r}{mode}='OFF';
- $retval.="$r\->OFF";
+ RESET($r,$dyn);
+ writeactuators($r,0,$house);
+ $dyn->{$r}{mode}='OFF';
+ $retval.="$r\->OFF";
+ $anynews=1;
}
elsif($action eq 'optimize' || ($action eq 'control' && $T0==-1))
{
- return if $dyn{$r}{mode} eq 'OPTIMIZE'; # Entprellen
+ return if $dyn->{$r}{mode} eq 'OPTIMIZE'; # Entprellen
# Initialisierung der Optimierungsfunktion
- $dyn{$r}{mode}='OPTIMIZE';
- $dyn{$r}{T0old}=$dyn{$r}{T0};
- writeactuators($r,0);
- my ($T,$V,$E,$R,$spread,$window)=readsensors($r);
+ $dyn->{$r}{mode}='OPTIMIZE';
+ $dyn->{$r}{T0old}=$dyn->{$r}{T0};
+ writeactuators($r,0,$house);
+ my ($T,$V,$E,$R,$spread,$window)=readsensors($r,$house);
$retval.=sprintf "$r\->OPT", $T;
+ $anynews=1;
}
elsif($action eq 'control') # neue Wunschtemperatur
{
- return if $dyn{$r}{T0} == $T0; # Entprellen
+ return if $dyn->{$r}{T0} == $T0; # Entprellen
- RESET($r) if $mode eq 'OPTIMIZE'; # Optimierung unterbrochen
- $dyn{$r}{mode}='ON'; # ansonsten uebrige Werte behalten
- $dyn{$r}{T0}=$T0;
- my ($T,$T0,$U,$Vr)=PID($r);
+ RESET($r,$dyn) if $mode eq 'OPTIMIZE'; # Optimierung unterbrochen
+ $dyn->{$r}{mode}='ON'; # ansonsten uebrige Werte behalten
+ $dyn->{$r}{T0}=$T0;
+ my ($T,$T0,$U,$Vr)=PID($r,$house,$dyn);
$retval.=sprintf "$r\->%.1f(%.1f)%d%%", $T, $T0, 100*$U;
+ $anynews=1;
}
}
}
# Speichere Statusvariablen aller Regler
-store_to_plugin_info(\%dyn);
+store_to_plugin_info($dyn,$house);
-return $retval eq '' ? 'Heizungsregler: nothing to do... event='.$event : $retval;
+#return $retval eq '' ? 'Heizungsregler: nothing to do... event='.$event : $retval;
+# Heizungsregler fuehrt ein separates Log
+open LOG, ">>/var/log/Heizungsregler.log";
+print LOG strftime("%F %X, ", localtime).$retval."\n";
+close LOG;
+return unless $anynews;
+return if $event=~/cycle/; # nicht den Log vollknallen
+return $retval;
+
+
########## Datenpersistenz - Speichern und Einlesen ###############
sub store_to_plugin_info
{
- my $dyn=shift;
+ my ($dyn,$house)=@_;
# Alle Laufzeitvariablen im Hash %{$dyn}
# in das (flache) Hash plugin_info schreiben
- for my $r (grep ref($house{$_}), keys %house)
+ for my $r (grep ref($house->{$_}), keys %{$house})
{
# Skalare
my @keylist=grep !/^(temps|times|Uvals)$/, keys %{$dyn->{$r}};
@@ -249,53 +307,51 @@
sub recall_from_plugin_info
{
- my %dyn=();
+ my $house=shift;
+ my $dyn={};
- for my $r (grep ref($house{$_}), keys %house)
+ for my $r (grep ref($house->{$_}), keys %{$house})
{
if(defined $plugin_info{$plugname.'_'.$r})
{
my $pi=$plugin_info{$plugname.'_'.$r};
# plugin_log($plugname,$r."> ".$pi) if $r eq 'D2';
- while($pi=~m/\'(.*?)\'=>\'(.*?)\'/g) { $dyn{$r}{$1}=$2; }
+ while($pi=~m/\'(.*?)\'=>\'(.*?)\'/g) { $dyn->{$r}{$1}=$2; }
}
for my $v (qw(temps times Uvals))
{
if(defined $plugin_info{$plugname.'_'.$r.'_'.$v})
{
- @{$dyn{$r}{$v}}=split ',', $plugin_info{$plugname.'_'.$r.'_'.$v};
+ @{$dyn->{$r}{$v}}=split ',', $plugin_info{$plugname.'_'.$r.'_'.$v};
}
else
{
- $dyn{$r}{$v}=[];
+ $dyn->{$r}{$v}=[];
}
}
}
-
- return %dyn;
-}
-sub read_from_house_config
-{
- open CONFIG, "<$conf" || return "no config found";
- my @lines = <CONFIG>;
- close CONFIG;
- eval("@lines");
- return "config error: $@" if $@;
+ return $dyn;
}
sub store_to_house_config
{
- my $r=shift; # der betreffende Raum im Haus
+ my ($r,$house,$conf)=@_; # der betreffende Raum im Haus
open CONFIG, ">>$conf";
- print CONFIG "\$house{$r}{pid}={";
- for my $k (sort keys %{$house{$r}{pid}})
+ print CONFIG "\$house->{$r}{pid}={";
+ for my $k (sort keys %{$house->{$r}{pid}})
{
- print CONFIG sprintf "$k=>%f, ", $house{$r}{pid}{$k} unless $k eq 'date';
- print CONFIG "$k=>'$house{$r}{pid}{date}'," if $k eq 'date';
+ unless($k eq 'date')
+ {
+ print CONFIG sprintf "'$k'=>%f, ", $house->{$r}{pid}{$k};
+ }
+ else
+ {
+ print CONFIG "'$k'=>'$house->{$r}{pid}{date}', " if $k eq 'date';
+ }
}
print CONFIG "};\n";
close CONFIG;
@@ -305,11 +361,11 @@
sub readsensors
{
- my $r=shift; # interessierender Raum
+ my ($r,$house)=@_; # interessierender Raum
my @substructures=();
- push @substructures, values %{$house{$r}->{circ}} if defined $house{$r}->{circ};
- push @substructures, $house{$r};
+ push @substructures, values %{$house->{$r}->{circ}} if defined $house->{$r}->{circ};
+ push @substructures, $house->{$r};
my %T=();
my %R=();
@@ -330,10 +386,13 @@
{
unless(defined $T{$type}{$s})
{
- # wir lesen mit 1000s aus dem Cache, denn die Temp-Sensoren sind sowieso auf 1wire
+ # wir lesen mit 3000s aus dem Cache, denn die Temp-Sensoren sind sowieso auf 1wire
# (und antworten daher nicht innerhalb der Plugin-Laufzeit).
# Achtung: sowohl Fensterkontakte als auch Temp-Sensoren sollten zyklisch senden!
- $T{$type}{$s}=knx_read($s,5*$house{cycle},$dpt,1000);
+ my $stime=time();
+ $T{$type}{$s}=knx_read($s,3000,$dpt);
+ $stime=time()-$stime;
+ plugin_log($plugname, "knx_read $s took $stime s") if $stime>0.8;
delete $T{$type}{$s} unless defined $T{$type}{$s} && $T{$type}{$s}!=85;
}
}
@@ -364,6 +423,9 @@
# Falls Fensterkontakte nicht lesbar -> Fenster als geschlossen annehmen
$R{window}=0 unless defined $R{window};
+ # Kaputten Estrichfuehler, erkennbar an zu tiefer Temperatur, durch Luftsensor ersetzen
+ $R{floor}=$R{sensor} if defined $R{floor} && defined $R{sensor} && $R{floor}<$R{sensor};
+
# outflow (Ruecklauf) und floor (Estrich) nehmen wir als gleich an,
# falls nicht beide Werte vorhanden sind. Das sollte immer noch besser
# sein als der globale Hauswert, um danach den Spread zu berechnen.
@@ -377,9 +439,13 @@
# nehmen wir die Hauswerte - falls diese verfuegbar sind
for my $type (qw(inflow outflow))
{
- if(!defined $R{$type} && defined $house{$type})
+ if(!defined $R{$type} && defined $house->{$type})
{
- $R{$type} = knx_read(groupaddress($house{$type}),5*$house{cycle},9);
+ my $stime=time();
+ $R{$type} = knx_read(groupaddress($house->{$type}),3000,9);
+ $stime=time()-$stime;
+ plugin_log($plugname, "knx_read $house->{$type} took $stime s") if $stime>0.8;
+
delete $R{$type} unless $R{$type};
}
}
@@ -391,21 +457,20 @@
}
# und wenn alle Stricke reissen, bleibt der vorkonfigurierte Wert
- $R{spread}=$house{spread} unless defined $R{spread};
+ $R{spread}=$house->{spread} unless defined $R{spread};
- #plugin_log($plugname, $r.": ".(join " ", map "$_=$R{$_}", qw(sensor inflow floor outflow spread window)));
+# plugin_log($plugname, $r.": ".(join " ", map "$_=$R{$_}", qw(sensor inflow floor outflow spread window)));
return @R{qw(sensor inflow floor outflow spread window)};
}
sub writeactuators
{
- my $r=shift; # Raum mit Substruktur
- my $U=shift; # Ventileinstellung
+ my ($r,$U,$house)=@_; # Raum mit Substruktur
my @substructures=();
- @substructures=values %{$house{$r}->{circ}} if defined $house{$r}->{circ};
- push @substructures, $house{$r};
+ @substructures=values %{$house->{$r}->{circ}} if defined $house->{$r}->{circ};
+ push @substructures, $house->{$r};
for my $ss (@substructures)
{
@@ -415,7 +480,9 @@
for my $s (@{$ss->{actuator}})
{
- knx_write(groupaddress($s),100*$U,5.001); # DPT NOCH UNKLAR ########
+ knx_write(groupaddress($s),100*$U,5.001);
+ update_rrd($s,'',100*$U) if $house->{rrd};
+ $plugin_cache{$plugname}{actuators}{$s}=100*$U;
}
}
}
@@ -425,45 +492,45 @@
sub RESET
{
- my $r=shift; # zu regelnder Raum im Haus
+ my ($r,$dyn)=@_; # zu regelnder Raum im Haus
- $dyn{$r} = {
- mode=>'OFF', T0=>20, Told=>0, told=>$t, IS=>0, DF=>0,
+ $dyn->{$r} = {
+ mode=>'OFF', T0=>15, Told=>0, told=>$t, IS=>0, DF=>0,
temps=>[], times=>[], Uvals=>[], U=>0
};
}
sub PID
{
- my $r=shift; # zu regelnder Raum im Haus
- my ($T,$V,$E,$R,$spread,$window)=readsensors($r);
+ my ($r,$house,$dyn)=@_; # zu regelnder Raum im Haus
+ my ($T,$V,$E,$R,$spread,$window)=readsensors($r,$house);
# Ohne Temperaturmessung und Spread keine Regelung -> aufgeben
my ($mode,$T0,$Told,$told,$IS,$DF,$temps,$times,$Uvals,$U)
- = @{$dyn{$r}}{qw(mode T0 Told told IS DF temps times Uvals U)};
+ = @{$dyn->{$r}}{qw(mode T0 Told told IS DF temps times Uvals U)};
return ($T,$T0,$U,0) unless $T && $spread;
# Regelparameter einlesen
my ($Tv,$Tn,$lim,$prop,$refspread)=(30,30,1,1,10); # Defaults
- if(defined $house{$r}{pid})
+ if(defined $house->{$r}{pid})
{
- ($Tv,$Tn,$lim,$prop,$refspread)=@{$house{$r}{pid}}{qw(Tv Tn lim prop refspread)};
+ ($Tv,$Tn,$lim,$prop,$refspread)=@{$house->{$r}{pid}}{qw(Tv Tn lim prop refspread)};
}
else
{
- $Tv=$house{Tv} if defined $house{Tv};
- $Tn=$house{Tn} if defined $house{Tn};
- $lim=$house{lim} if defined $house{lim};
- $prop=$house{prop} if defined $house{prop};
- $refspread=$house{refspread} if defined $house{refspread};
+ $Tv=$house->{Tv} if defined $house->{Tv};
+ $Tn=$house->{Tn} if defined $house->{Tn};
+ $lim=$house->{lim} if defined $house->{lim};
+ $prop=$house->{prop} if defined $house->{prop};
+ $refspread=$house->{refspread} if defined $house->{refspread};
}
$Tv*=60; $Tn*=60; # in Sekunden umrechnen
# Anzahl Datenpunkte fuer Steigungsberechnung
- my $S1=12; $S1=$house{mindata} if defined $house{mindata};
+ my $S1=12; $S1=$house->{mindata} if defined $house->{mindata};
# Anzahl Datenpunkte fuer Ermittlung neuer Vorhaltetemperatur
my $S2=$S1;
@@ -476,20 +543,22 @@
$U=0; # Heizung aus falls Fenster offen
push @{$Uvals}, $U; shift @{$Uvals} while @{$Uvals}>$S2;
- $dyn{$r} = {
+ $dyn->{$r} = {
mode=>$mode, T0=>$T0, Told=>$T, told=>$t, IS=>$IS, DF=>$DF,
temps=>$temps, times=>$times, Uvals=>$Uvals, U=>$U
};
- plugin_log($plugname, "$r: WINDOW");
+# plugin_log($plugname, "$r: WINDOW");
- writeactuators($r,$U);
+ writeactuators($r,$U,$house);
return ($T,$T0,$U,0);
}
- # Skalierung fuer aktuellen Spread
- my $coeff = $refspread/($spread*$prop);
-
+ # Skalierung fuer aktuellen Spread, Regelung wird aggressiver wenn Spread<1
+ my $coeff = $spread>1 ? $refspread/($spread*$prop) : 100;
+ $coeff = 100 if $coeff<0;
+ $coeff = 1 if $coeff<1;
+
# Proportionalteil (P)
my $P = $T0 - $T;
@@ -522,7 +591,14 @@
# und alles zusammen, skaliert mit der aktuellen Spreizung
$U = ($P + $IS + $DF) * $coeff;
- plugin_log($plugname, sprintf("$r: %.1f(%.1f) U = P+I+D = %d%+d%+d = %d",$T,$T0,100*$P*$coeff,100*$IS*$coeff,100*$DF*$coeff,100*$U));
+
+ if($r eq 'WZ')
+ {
+ open LOG, ">>/var/log/Heizungsregler.log";
+ print LOG strftime("%F %X, ", localtime).sprintf("$r: %.1f(%.1f) U = P+I+D = %d%%+%d%%+%d%% = %d%%",$T,$T0,100*$P*$coeff,100*$IS*$coeff,100*$DF*$coeff,100*$U)."\n";
+ print LOG strftime("%F %X, ", localtime).sprintf("$r: coeff = %f, refspread = %f, prop = %f, spread = %f",$coeff,$refspread,$prop,$spread)."\n";
+ close LOG;
+ }
# Stellwert begrenzen auf 0-1
$U=1 if $U>1;
@@ -533,24 +609,24 @@
my $Vr=$V;
if(defined $Vr)
{
- $Vr=$T0+3 if $Vr<$T0+3;
+ $Vr=$T0+3 if $Vr<$T0+3; # mindestens Raumwunschtemperatur + 3 Grad
my $Uavg=0; $Uavg+=$_ foreach (@{$Uvals}); $Uavg/=scalar(@{$Uvals});
$Vr+=1 if $Uavg>0.9;
- $Vr-=1 if $Uavg<0.6 && $V>$T0+6 && $spread>6;
- $Vr-=1 if $Uavg<0.75 && $V>$T0+5 && $spread>5;
- $Vr-=1 if $Uavg<0.7 && $V>$T0+4 && $spread>4;
- $Vr-=1 if $Uavg<0.6 && $V>$T0+3 && $spread>3;
+ $Vr-=1 if $Uavg<0.6 && $V>$T0+6 && $spread>6 || $Uavg<0.75 && $V>$T0+5 && $spread>5
+ || $Uavg<0.7 && $V>$T0+4 && $spread>4 || $Uavg<0.6 && $V>$T0+3 && $spread>3;
+
+# plugin_log($plugname, "room=$r, Uavg = $Uavg, T0=$T0, V=$V, spread=$spread, Vr=$Vr");
}
# Variablen zurueckschreiben
- $dyn{$r} = {
+ $dyn->{$r} = {
mode=>$mode, T0=>$T0, Told=>$T, told=>$t, IS=>$IS, DF=>$DF,
temps=>$temps, times=>$times, Uvals=>$Uvals, U=>$U
};
# Ventil einstellen
- writeactuators($r,$U);
+ writeactuators($r,$U,$house);
# Ist, Soll, Stellwert, Spread, Wunsch-Vorlauftemp.
return ($T,$T0,$U,$Vr);
@@ -560,25 +636,25 @@
sub OPTIMIZE
{
- my $r=shift;
- my ($T,$V,$E,$R,$spread,$window)=readsensors($r);
+ my ($r,$house,$dyn,$conf)=@_;
+ my ($T,$V,$E,$R,$spread,$window)=readsensors($r,$house);
# Ohne Temperaturmessung und Spread keine Regelung -> aufgeben
return "(OPT) " unless defined $T && defined $spread;
# Praktische Abkuerzungen fuer Statusvariablen
- my ($mode,$phase,$T0old) = @{$dyn{$r}}{qw(mode phase T0old)};
+ my ($mode,$phase,$T0old) = @{$dyn->{$r}}{qw(mode phase T0old)};
- plugin_log($plugname, "D2: phase=$phase") if $r eq 'D2';
+# plugin_log($plugname, "$r: phase=$phase");
# Falls Fenster offen -> Abbruch, Heizung aus und Regler resetten
if($window)
{
if($phase ne 'COOL')
{
- RESET($r);
- $dyn{$r}{mode}='ON';
- $dyn{$r}{T0}=$T0old;
+ RESET($r,$dyn);
+ $dyn->{$r}{mode}='ON';
+ $dyn->{$r}{T0}=$T0old;
return "FAILED:WINDOW ";
}
else
@@ -586,11 +662,11 @@
# Tn, Tv, prop und refspread wurden am Ende der HEAT-Periode bereits berechnet.
# Wir nutzen die "cooling"-Periode sowieso nicht fuer die Berechnung der Parameter.
# Also Parameter jetzt (Beginn "cooling") schon ins Konfig-File schreiben.
- my ($Tn, $Tv, $prop, $refspread) = @{$dyn{$r}}{qw(Tn Tv prop refspread)};
+ my ($Tn, $Tv, $prop, $refspread) = @{$dyn->{$r}}{qw(Tn Tv prop refspread)};
my $date=strftime("%F %X",localtime);
my $lim=0.5;
- $house{$r}{pid}={Tv=>$Tv, Tn=>$Tn, lim=>$lim, prop=>$prop, refspread=>$refspread, date=>$date};
- store_to_house_config($r);
+ $house->{$r}{pid}={Tv=>$Tv, Tn=>$Tn, lim=>$lim, prop=>$prop, refspread=>$refspread, date=>$date};
+ store_to_house_config($r,$house,$conf);
}
}
@@ -600,17 +676,17 @@
if($phase eq 'WAIT')
{
- if(defined $V && defined $house{inflow_max} && $V<$house{inflow_max}-3)
+ if(defined $V && defined $house->{inflow_max} && $V<$house->{inflow_max}-3)
{
- writeactuators($r,0); # noch nicht heizen
+ writeactuators($r,0,$house); # noch nicht heizen
return "WAIT(V=$V) ";
}
# Falls Heizung noch nicht voll an, jetzt starten
- writeactuators($r,1); # maximal heizen
+ writeactuators($r,1,$house); # maximal heizen
# Temperaturaufzeichnung beginnen
- $dyn{$r} = {
+ $dyn->{$r} = {
mode=>$mode, phase=>'HEAT',
T0old=>$T0old, told=>0, optstart=>$t,
maxpos=>0, maxslope=>0,
@@ -620,13 +696,13 @@
return sprintf("%.1f(HEAT)%.1f ",$T,$spread);
}
- my ($optstart, $sumspread, $told, $temps, $times) = @{$dyn{$r}}{qw(optstart sumspread told temps times)};
+ my ($optstart, $sumspread, $told, $temps, $times) = @{$dyn->{$r}}{qw(optstart sumspread told temps times)};
my $tp=$t-$optstart;
# falls aus irgendeinem Grund zu frueh aufgerufen, tu nichts
return sprintf("%.1f(", $T).'SKP'.sprintf(")%.1f ",$spread)
- if $tp-$told<$house{cycle}/2;
+ if $tp-$told<$house->{cycle}/2;
# Temperaturkurve aufzeichnen
push @{$times}, $tp;
@@ -635,11 +711,11 @@
# Anzahl Datenpunkte fuer Steigungsberechnung. Hier verdoppelt, weil wir
# mehr Praezision brauchen.
- my $S1=25; $S1=2*$house{mindata} if defined $house{mindata};
+ my $S1=25; $S1=2*$house->{mindata} if defined $house->{mindata};
if(scalar(@{$temps})<=$S1)
{
- $dyn{$r} = {
+ $dyn->{$r} = {
mode=>$mode, phase=>$phase,
T0old=>$T0old, told=>$tp, optstart=>$optstart,
maxpos=>0, maxslope=>0,
@@ -664,13 +740,13 @@
if($phase eq 'HEAT')
{
- my ($maxpos, $maxslope) = @{$dyn{$r}}{qw(maxpos maxslope)};
+ my ($maxpos, $maxslope) = @{$dyn->{$r}}{qw(maxpos maxslope)};
- plugin_log($plugname, "D2: maxpos=$maxpos maxslope=$maxslope slope=$slope") if $r eq 'D2';
+# plugin_log($plugname, "D2: maxpos=$maxpos maxslope=$maxslope slope=$slope") if $r eq 'D2';
- if($maxslope==0) { $maxslope=0.5; }
+# if($maxslope==0) { $maxslope=0.005; }
- if($slope<=0 || $maxslope<=0 || $slope>=0.6*$maxslope)
+ if($slope<=0 || $maxslope<=0.01 || $slope>=0.6*$maxslope)
{
my $retval='';
@@ -680,7 +756,7 @@
$maxpos = nearest(1,$#{$temps}-$S1/2);
$retval=sprintf "%.2fKph/max=%.2fKph)",$slope,$maxslope;
}
- elsif($slope>0)
+ elsif($slope>0 && $maxslope>0.01)
{
$retval=sprintf "%.2fKph=%d%%", $slope, 100*$slope/$maxslope;
}
@@ -690,7 +766,7 @@
}
# Statusvariablen zurueckschreiben
- $dyn{$r} = {
+ $dyn->{$r} = {
mode=>$mode, phase=>'HEAT',
T0old=>$T0old, told=>$tp, optstart=>$optstart,
maxpos=>$maxpos, maxslope=>$maxslope,
@@ -719,9 +795,9 @@
}
unless(defined $pos2)
{
- RESET($r);
- $dyn{$r}{mode}='ON'; # ansonsten uebrige Werte behalten
- $dyn{$r}{T0}=$T0old;
+ RESET($r,$dyn);
+ $dyn->{$r}{mode}='ON'; # ansonsten uebrige Werte behalten
+ $dyn->{$r}{T0}=$T0old;
return "FAILED:POS2 ";
}
$pos2 = nearest(1,$pos2-$S1/2);
@@ -764,16 +840,16 @@
# alle drei Parameter muessen positiv sein, sonst Fehler
unless($prop>=0 && $Tn>=0 && $Tv>=0)
{
- RESET($r);
- $dyn{$r}{mode}='ON';
- $dyn{$r}{T0}=$T0old;
- $dyn{$r}{Told}=$T;
+ RESET($r,$dyn);
+ $dyn->{$r}{mode}='ON';
+ $dyn->{$r}{T0}=$T0old;
+ $dyn->{$r}{Told}=$T;
return "FAILED:NEG ";
}
# Statusvariablen zurueckschreiben
- $dyn{$r} = {
+ $dyn->{$r} = {
mode=>$mode, phase=>'COOL',
T0old=>$T0old, told=>$tp, optstart=>$optstart,
Tn=>$Tn, Tv=>$Tv, prop=>$prop, refspread=>$refspread, tcool=>$t3,
@@ -781,7 +857,7 @@
};
# Abkuehlung einleiten
- writeactuators($r, 0);
+ writeactuators($r,0,$house);
return sprintf("%.1f(COOL) ",$T);
}
@@ -795,17 +871,17 @@
# aus der Laenge der "cooling"-Periode bis zum Maximum koennte man noch was berechnen,
# aber wir setzen $lim hier als Konstante
my ($Tn, $Tv, $prop, $refspread, $tcool)
- = @{$dyn{$r}}{qw(Tn Tv prop refspread tcool)};
+ = @{$dyn->{$r}}{qw(Tn Tv prop refspread tcool)};
my $date=strftime("%F %X",localtime);
my $lim=0.5;
- $house{$r}{pid}={Tv=>$Tv, Tn=>$Tn, lim=>$lim, prop=>$prop, refspread=>$refspread, date=>$date};
- store_to_house_config($r);
+ $house->{$r}{pid}={Tv=>$Tv, Tn=>$Tn, lim=>$lim, prop=>$prop, refspread=>$refspread, date=>$date};
+ store_to_house_config($r,$house,$conf);
# Regelung starten
- RESET($r);
- $dyn{$r}{mode}='ON';
- $dyn{$r}{T0}=$T0old;
- $dyn{$r}{Told}=$T;
+ RESET($r,$dyn);
+ $dyn->{$r}{mode}='ON';
+ $dyn->{$r}{T0}=$T0old;
+ $dyn->{$r}{Told}=$T;
# Info an den User
return sprintf "t=%dh:%02dmin Tv=%.1fmin Tn=%dmin lim=%.1f prop=%.1f spread=%.1f ", $tp/3600,($tp/60)%60,$Tv,$Tn,$lim,$prop,$refspread;
@@ -818,7 +894,7 @@
{
my $short=shift;
- return unless defined $short;
+ return undef unless defined $short;
if(ref $short)
{
@@ -849,38 +925,3 @@
}
}
-sub shortname
-{
- my $gas=shift;
-
- return unless defined $gas;
- return $gas unless $use_short_names;
-
- if(ref $gas)
- {
- my $sh=[];
- for my $ga (@{$gas})
- {
- if($ga=~/^[0-9\/]+$/ && defined $eibgaconf{$ga}{short})
- {
- push @{$sh}, $eibgaconf{$ga}{short};
- }
- else
- {
- push @{$sh}, $ga;
- }
- }
- return $sh;
- }
- else
- {
- my $sh=$gas;
-
- if($gas=~/^[0-9\/]+$/ && defined $eibgaconf{$gas}{short})
- {
- $sh=$eibgaconf{$gas}{short};
- }
-
- return $sh;
- }
-}
Added: wiregate/plugin/generic/conf.d/Heizungsregler.conf2
===================================================================
--- wiregate/plugin/generic/conf.d/Heizungsregler.conf2 (rev 0)
+++ wiregate/plugin/generic/conf.d/Heizungsregler.conf2 2014-12-28 07:35:33 UTC (rev 2297)
@@ -0,0 +1,113 @@
+#!/usr/bin/perl
+#
+# Konfiguration Heizungsregler ##########################################
+#
+%house=(
+ cycle => 60,
+
+ # Anzahl der Datenpunkte fuer Trendberechnung
+ mindata => 30,
+
+ inflow_control => 'TS_HE_Zentralvorlauf_Stellwert', # GA zum Setzen der Soll-Vorlauftemperatur
+ inflow_max => 55, # maximale Soll-Vorlauftemperatur
+
+ inflow => 'TO_HE_Zentralvorlauf2', # hausweite Vorlauftemp, falls nicht raumweise messbar
+ outflow => 'TO_HE_Zentralruecklauf', # hausweite Ruecklauftemp, falls nicht raumweise messbar
+ spread => 18, # hausweite Spreizung, falls alle Stricke reissen
+
+ heating => 'HS_Programm_Status', # Status der Therme
+ heating_off => '1|5', # bei diesen Stati ist die Therme "AUS"
+
+ control_switch =>'HR_Global_Stellwert', # Heizungsregler an- und ausschalten
+ control_status =>'HR_Global_Status', # Status des Heizungsreglers abfragen
+
+ # RRDs der Ventilsteuerungswerte schreiben
+ rrd=>1,
+
+ # PID-Regelparameter fuer alle Raeume, die keine eigenen Parametersaetze (siehe ganz unten) definieren
+ # Falls diese Werte fehlen, wird als Standard (30, 30, 1, 1, 10) genommen
+
+ # Vorausblickende Zeit - D-Koeffizient
+ # groessere Werte sind aggressiver
+ Tv=>20,
+
+ # Nachstellzeit - I-Koeffizient, kleinere Werte sind aggressiver
+ # kleinere Werte sind aggressiver
+ Tn=>50,
+
+ # maximaler Beitrag der I-Komponente zur Ventilstellung, verhindert "wind-up"-Effekt
+ # groessere Werte sind aggressiver
+ lim=>0.5,
+
+ # Proportionalitaetsbereich (K) - Koeffizient vor allen drei (P, I, D)-Regelbeitr\xE4gen
+ # kleinere Werte sind aggressiver
+ prop=>2.0,
+
+ # alle obigen Koeffizienten (au\xDFer lim) beziehen sich auf diesen Referenzspread und werden fuer andere Spreads angepasst
+ # Es kommt im Endeffekt nur auf den Quotienten prop/refspread an!
+ # groessere Werte sind aggressiver
+ refspread=>20,
+
+ # Definitionen der Raeume (Regelstrecken)
+ WZ => { control=>'TS_WZ', optimize=>'TZ_WZ', reset=>'TR_WZ', sensor=>'TL_WZ_u_Galerie',
+ circ=>{
+ Nord => { actuator=>'VS_WZ_Nord', floor=>'TE_WZ_Nord' },
+ West => { actuator=>'VS_WZ_West', floor=>'TE_WZ_Nord' },
+ Sued => { actuator=>'VS_WZ_Sued', floor=>'TE_WZ_Nord' },
+ },
+ window => ['KF_WZ_Ost','KF_WZ_Sued_L','KF_WZ_Sued_R','KF_WZ_West_L','KF_WZ_West_R',
+ 'KK_WZ_Ost','KK_WZ_Sued_L','KK_WZ_Sued_R','KK_WZ_West_L','KK_WZ_West_R',
+ 'VL_WZ_Kamin'], # Kamin als "virtuelles Fenster" realisiert, dies ist noch suboptimal!
+ },
+
+ SZ => { control=>'TS_SZ', optimize=>'TZ_SZ', reset=>'TR_SZ', sensor=>'TL_SZ_Waldfenster', #'TL_SZ_Ankleide'],
+ circ=>{
+ Ankleide => {actuator=>'VS_SZ_Ankleide',floor=>'TE_SZ_Ankleide'},
+ Mitte => {actuator=>'VS_SZ_Mitte',floor=>'TE_SZ_Mitte'},
+ Kinderecke => {actuator=>'VS_SZ_Kinderecke'}
+ },
+ window=>['KF_SZ_Ankleide','KF_SZ_Bett','KF_SZ_Wald_L','KF_SZ_Wald_R','KF_SZ_Dach',
+ 'KK_SZ_Ankleide','KK_SZ_Bett','KK_SZ_Wald_L','KK_SZ_Wald_R'],
+ },
+
+ D1 => { control=>'TS_D1', optimize=>'TZ_D1', reset=>'TR_D1', sensor=>'TL_D1', actuator=>'VS_D1_Boden', floor=>'TE_D1',
+ window=>['KF_D1','KK_D1'] },
+
+ BA => { control=>'TS_BA', optimize=>'TZ_BA', reset=>'TR_BA', sensor=>'TL_BA', actuator=>'VS_BA_Boden', floor=>'TE_BA',
+ window=>['KF_BA_Wanne','KF_BA_Dusche_L','KF_BA_Dusche_R','KF_BA_West',
+ 'KK_BA_Wanne','KK_BA_Dusche_L','KK_BA_Dusche_R','KK_BA_West'] },
+
+ K1 => { control=>'TS_K1', optimize=>'TZ_K1', reset=>'TR_K1', sensor=>'TL_K1', actuator=>'VS_K1', floor=>'TE_K1',
+ window=>['KF_K1','KF_K1R','KK_K1'] },
+
+ K2 => { control=>'TS_K2', optimize=>'TZ_K2', reset=>'TR_K2', sensor=>'TL_K2', actuator=>'VS_K2', floor=>'TE_K2',
+ window=>['KF_K2','KF_K2R','KK_K2'] },
+
+ K3 => { control=>'TS_K3', optimize=>'TZ_K3', reset=>'TR_K3', sensor=>'TL_K3', actuator=>'VS_K3', floor=>'TE_K3',
+ window=>['KF_K3','KF_K3R','KK_K3'] },
+
+ E => { control=>'TS_E', optimize=>'TZ_E', reset=>'TR_E', sensor=>'TL_E', actuator=>'VS_E', floor=>'TE_E',
+ window=>['KF_E','KK_E'] },
+
+ F1 => { control=>'TS_F1', optimize=>'TZ_F1', reset=>'TR_F1', sensor=>'TL_F1', actuator=>'VS_F1', floor=>'TE_F1' },
+
+ HW => { control=>'TS_HW', optimize=>'TZ_HW', reset=>'TR_HW', sensor=>'TL_HW', actuator=>'VS_HW', floor=>'TE_HW',
+ window=>['KF_HW_L','KF_HW_R','KK_HW_L','KK_HW_R'] },
+
+ AZ => { control=>'TS_AZ', optimize=>'TZ_AZ', reset=>'TR_AZ', sensor=>'TL_AZ', actuator=>'VS_AZ', floor=>'TE_AZ',
+ window=>['KF_AZ_L','KF_AZ_R','KK_AZ_L','KK_AZ_R'] },
+
+ EK => { control=>'TS_EK', optimize=>'TZ_EK', reset=>'TR_EK', sensor=>'TL_EK', actuator=>'VS_EK', floor=>'TE_EK',
+ window=>['KF_EK_Herd_L','KF_EK_Herd_R','KF_EK_Ecke_L','KF_EK_Ecke_R',
+ 'KK_EK_Herd_L','KK_EK_Herd_R','KK_EK_Ecke_L','KK_EK_Ecke_R'] },
+
+ F2 => { control=>'TS_F2', optimize=>'TZ_F2', reset=>'TR_F2', sensor=>'TL_F2', actuator=>'VS_F2', floor=>'TE_F2',
+ window=>['KF_F2','KK_F2'] },
+
+ D2 => { control=>'TS_D2', optimize=>'TZ_D2', reset=>'TR_D2', sensor=>'TL_D2', actuator=>'VS_D2', floor=>'TE_D2',
+ window=>['KF_D2','KK_D2'] },
+);
+
+# Ende der Konfiguration ##################################
+
+#$house{D2}{pid}={Tn=>105.516082, Tv=>104.999158, date=>'2013-03-08 10:29:28',lim=>0.500000, prop=>0.143682, refspread=>14.332398, };
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|