| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322 |
- ##############################################
- # $Id: 98_PID.pm 4930 2014-02-15 03:59:16Z betateilchen $
- # This module is derived from the contrib/99_PID by Alexander Titzel.
- package main;
- use strict;
- use warnings;
- sub PID_sv($$$);
- sub PID_setValue($);
- ##########################
- sub
- PID_Initialize($)
- {
- my ($hash) = @_;
- $hash->{DefFn} = "PID_Define";
- $hash->{SetFn} = "PID_Set";
- $hash->{NotifyFn} = "PID_Notify";
- $hash->{AttrList} = "disable:0,1 roundValveValue:0,1";
- }
- ##########################
- sub
- PID_Define($$$)
- {
- my ($pid, $def) = @_;
- my @a = split("[ \t][ \t]*", $def);
- my $pn = $a[0];
- if(@a < 4 || @a > 7) {
- return "wrong syntax: define <name> PID " .
- "<sensor>[:reading:regexp] <actor>[:cmd:min:max] [p i d]";
- }
- ###################
- # Sensor
- my ($sensor, $reading, $regexp) = split(":", $a[2], 3);
- if(!$defs{$sensor}) {
- my $msg = "$pn: Unknown sensor device $sensor specified";
- Log3 $pn, 2, $msg;
- return $msg;
- }
- $pid->{sensor} = $sensor;
- if(!$regexp) {
- my $t = $defs{$sensor}{TYPE};
- if($t eq "HMS" || $t eq "CUL_WS" || $t eq "CUL_HM" ) {
- $reading = "temperature";
- $regexp = '([\\d\\.]*)';
- } else {
- my $msg = "$pn: Unknown sensor type $t, specify regexp";
- Log3 $pn, 2, $msg;
- return $msg;
- }
- }
- $pid->{reading} = $reading;
- $pid->{regexp} = $regexp;
- ###################
- # Actor
- my ($actor, $cmd, $min, $max) = split(":", $a[3], 4);
- my ($p_p, $p_i, $p_d) = (0, 0, 0);
- if(!$defs{$actor}) {
- my $msg = "$pn: Unknown actor device $actor specified";
- Log3 $pn, 2, $msg;
- return $msg;
- }
- $pid->{actor} = $actor;
- if(!$max) {
- my $t = $defs{$actor}{TYPE};
- if($t eq "FHT8V") {
- $cmd = "valve";
- $min = 0;
- $max = 100;
- $p_p = 65.0/2.55;
- $p_i = 7.8/2.55;
- $p_d = 15.0/2.55;
- } else {
- my $msg = "$pn: Unknown actor type $t, specify command:min:max";
- Log3 $pn, 2, $msg;
- return $msg;
- }
- }
- $pid->{command} = $cmd;
- $pid->{pFactor} = (@a > 4 ? $a[4] : $p_p);
- $pid->{iFactor} = (@a > 5 ? $a[5] : $p_i);
- $pid->{dFactor} = (@a > 6 ? $a[6] : $p_d);
- $pid->{satMin} = $min;
- $pid->{satMax} = $max;
- PID_sv($pid, 'delta', 0.0);
- PID_sv($pid, 'actuation', 0.0);
- PID_sv($pid, 'integrator', 0.0);
- $pid->{STATE} = 'initialized';
- return undef;
- }
- ##########################
- sub
- PID_Set($@)
- {
- my ($pid, @a) = @_;
- my $pn = $pid->{NAME};
- return "" if($attr{$pn} && $attr{$pn}{disable});
- return "Need a parameter for set" if(@a < 2);
- my $arg = $a[1];
- if($arg eq "factors" ) {
- return "Set factors needs 3 parameters (p i d)" if(@a != 5);
- $pid->{pFactor} = $a[2];
- $pid->{iFactor} = $a[3];
- $pid->{dFactor} = $a[4];
- # modify DEF, alse save won't work.
- my @d = split(' ', $pid->{DEF});
- $pid->{DEF} = "$d[0] $d[1] $a[2] $a[3] $a[4]";
- } elsif ($arg eq "desired" ) {
- return "Set desired needs a numeric parameter"
- if(@a != 3 || $a[2] !~ m/^[\d\.]*$/);
- Log3 $pn, 3, "PID set $pn $arg $a[2]";
- PID_sv($pid, 'desired', $a[2]);
- PID_setValue($pid);
- } else {
- return "Unknown argument $a[1], choose one of factors desired"
- }
- return "";
- }
- ##########################
- sub
- PID_Notify($$)
- {
- my ($pid, $dev) = @_;
- my $pn = $pid->{NAME};
- return "" if($attr{$pn} && $attr{$pn}{disable});
- return if($dev->{NAME} ne $pid->{sensor});
- my $reading = $pid->{reading};
- my $max = int(@{$dev->{CHANGED}});
- for (my $i = 0; $i < $max; $i++) {
- my $s = $dev->{CHANGED}[$i];
- $s = "" if(!defined($s));
- next if($s !~ m/$reading/);
- PID_setValue($pid);
- last;
- }
- return "";
- }
- ##########################
- sub
- PID_saturate($$)
- {
- my ($pid, $v) = @_;
- return $pid->{satMax} if($v > $pid->{satMax});
- return $pid->{satMin} if($v < $pid->{satMin});
- return $v;
- }
- sub
- PID_sv($$$)
- {
- my ($pid,$name,$val) = @_;
- $pid->{READINGS}{$name}{VAL} = $val;
- $pid->{READINGS}{$name}{TIME} = TimeNow();
- }
- sub
- PID_gv($$)
- {
- my ($pid,$name) = @_;
- return $pid->{READINGS}{$name}{VAL}
- if($pid->{READINGS} && $pid->{READINGS}{$name});
- return undef;
- }
- sub
- PID_setValue($)
- {
- my ($pid) = @_;
- my $pn = $pid->{NAME};
- my $sensor = $pid->{sensor};
- my $reading = $pid->{reading};
- my $re = $pid->{regexp};
- # Get the value from the READING
- my $inStr;
- $inStr = $defs{$sensor}{READINGS}{$reading}{VAL}
- if($defs{$sensor}{READINGS} && $defs{$sensor}{READINGS}{$reading});
- if(!$inStr) {
- Log3 $pn, 4, "PID $pn: no $reading yet for $sensor";
- return;
- }
- $inStr =~ m/$re/;
- my $in = $1;
-
- my $desired = PID_gv($pid, 'desired');
- return if(!defined($desired));
- my $delta = $desired - $in;
- my $p = $delta * $pid->{pFactor};
- my $i = PID_saturate($pid, PID_gv($pid,'integrator')+$delta*$pid->{iFactor});
- PID_sv($pid, 'integrator', $i);
- my $d = ($delta - PID_gv($pid,'delta')) * $pid->{dFactor};
- PID_sv($pid, 'delta', $delta);
- my $a = PID_saturate($pid, $p + $i + $d);
- PID_sv($pid, 'actuation', $a);
- Log3 $pn, 4, sprintf("PID $pn: p:%.2f i:%.2f d:%.2f", $p, $i, $d);
- # Hack to round.
- my ($satMin, $satMax) = ($pid->{satMin}, $pid->{satMax});
- $a = int($a) if(AttrVal($pn, "roundValveValue", ($satMax-$satMin >= 100)));
- my $ret = fhem sprintf("set %s %s %g", $pid->{actor}, $pid->{command}, $a);
- Log3 $pn, 1, "output of $pn command: $ret" if($ret);
- $pid->{STATE} = "$in (delta $delta)";
- }
- 1;
- =pod
- not to be translated
- =begin html
- <a name="PID"></a>
- <h3>PID</h3>
- <ul>
- The PID device is a loop controller, used to set the value e.g of a heating
- valve dependent of the current and desired temperature.
- <br>
- <br>
- <a name="PIDdefine"></a>
- <b>Define</b>
- <ul>
- <code>define <name> PID sensor[:reading:regexp] actor[:cmd:min:max] [p i d]</code>
- <br><br>
- <code>sensor[:reading:regexp]</code> specifies the sensor, which is an
- already defined fhem device, e.g. a S300TH temperature sensor. The reading
- and regexp fields are necessary only for unknown devices (currently <a
- href="#CUL_WS">CUL_WS</a> and <a href="#HMS">HMS</a> devices are "known").
- Reading specifies the READINGS field of the sensor, and the regexp extracts
- the number from this field. E.g. for the complete definition for a CUL_WS
- device is: <code>s300th_dev:temperature:([\d\.]*)</code>
- <br><br>
- <code>actor[:cmd:min:max]</code> specifies the actor, which is an
- already defined fhem device, e.g. an FHT8V valve. The cmd, min and max
- fields are necessary only for unknown devices (currently <a
- href="#FHT8V">FHT8V</a> is "known"). cmd specifies the command name for the
- actor, min the minimum value and max the maximum value. The complete
- definition for an FHT8V device is:<code>fht8v_dev:valve:0:100</code>
- <br><br>
- p, i and d are the parameters use to controlling, see also the <a
- href="http://de.wikipedia.org/wiki/Regler">this</a> wikipedia entry.
- The default values are around 25.5, 3 and 5.88, you probably need to tune
- these values. They can be also changed later.
- <br><br>
- Examples:
- <ul>
- <code>define wz_pid PID wz_th wz_fht8v</code><br>
- </ul>
- </ul>
- <br>
- <a name="PIDset"></a>
- <b>Set </b>
- <ul>
- <li>set <name> factors p i d<br>
- Set the p, i and d factors, as described above.
- </li>
- <li>set <name> desired <value><br>
- Set the desired value (e.g. temperature). Note: until this value is not
- set, no command is issued.
- </li>
- </ul>
- <br>
- <a name="PIDget"></a>
- <b>Get </b>
- <ul>
- N/A
- </ul>
- <br>
- <a name="PIDattr"></a>
- <b>Attributes</b>
- <ul>
- <li><a href="#disable">disable</a></li>
- <a name="roundValveValue"></a>
- <li>roundValveValue<br>
- round the valve value to an integer, of the attribute is set to 1.
- The valve value is automatically rounded, if the attribtue is not set,
- and the difference between min and max is greater than 100.
- </li><br>
- </ul>
- <br>
- </ul>
- =end html
- =cut
|