diff --git a/CHANGED b/CHANGED index cbdeac94a..15f61a991 100644 --- a/CHANGED +++ b/CHANGED @@ -1,4 +1,7 @@ +# Add changes at the top of the list. + - SVN + - feature: THRESHOLD Module by Damian - change: 30_HUEDevice: use new devStateIcon feature to show device color in room overview - feature: added example Setup SQL and configuration for SQLite diff --git a/FHEM/98_THRESHOLD.pm b/FHEM/98_THRESHOLD.pm new file mode 100644 index 000000000..9b9ee9419 --- /dev/null +++ b/FHEM/98_THRESHOLD.pm @@ -0,0 +1,633 @@ +############################################## +# 98_THRESHOLD by Damian Sordyl +# +# This file is part of fhem. +# +# Fhem is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# Fhem is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with fhem. If not, see . +# +############################################################################## + + +package main; +use strict; +use warnings; + +sub THRESHOLD_setValue($$); + +########################## +sub +THRESHOLD_Initialize($) +{ + my ($hash) = @_; + $hash->{DefFn} = "THRESHOLD_Define"; + $hash->{SetFn} = "THRESHOLD_Set"; + $hash->{NotifyFn} = "THRESHOLD_Notify"; + $hash->{AttrList} = "disable:0,1 loglevel:0,1,2,3,4,5,6"; +} + + +########################## +sub +THRESHOLD_Define($$$) +{ + my ($hash, $def) = @_; + my @b =split (/\|/,$def); + my @a = split("[ \t][ \t]*", $b[0]); + my $cmd1; + my $cmd2; + my $cmd_default; + my $actor; + my $init_desired_value; + + if (@b > 4 || @a < 3 || @a > 5) { + my $msg = "wrong syntax: define THRESHOLD" . + "[:[:][:] [AND|OR [:][:]] [][|][|][|]"; + Log 2, $msg; + return $msg; + } + my $pn = $a[0]; + # Sensor + my ($sensor, $reading, $hysteresis,$init_desired_value) = split(":", $a[2], 4); + + if(!$defs{$sensor}) { + my $msg = "$pn: THRESHOLD: Unknown sensor device $sensor specified"; + Log 2, $msg; + return $msg; + } + + $hash->{sensor} = $sensor; + $reading = "temperature" if (!$reading); + + if (!$hysteresis) { + if ($reading eq "temperature") { + $hysteresis=1; + } else { + $hysteresis=20; + } + } elsif ($hysteresis !~ m/^[\d\.]*$/ ) { + my $msg = "$pn: THRESHOLD: hysteresis needs a numeric parameter"; + Log 2, $msg; + return $msg; + } elsif ($init_desired_value) { + if ($init_desired_value !~ m/^[-\d\.]*$/) { + my $msg = "$pn: THRESHOLD: init_desired_value needs a numeric parameter"; + Log 2, $msg; + return $msg; + } + } + $hash->{sensor_reading} = $reading; + $hash->{hysteresis} = $hysteresis; + + # Sensor2 + my $operator=$a[3]; + if ($operator) { + if (($operator eq "AND") or ($operator eq "OR")) { + my ($sensor2, $sensor2_reading, $state) = split(":", $a[4], 3); + if(!$defs{$sensor2}) { + my $msg = "$pn: THRESHOLD: Unknown sensor2 device $sensor2 specified"; + Log 2, $msg; + return $msg; + } + $sensor2_reading = "state" if (!$sensor2_reading); + $state = "open" if (!$state); + $hash->{operator} = $operator; + $hash->{sensor2} = $sensor2; + $hash->{sensor2_reading} = $sensor2_reading; + $hash->{sensor2_state} = $state; + $actor = $a[5]; + } else { + $actor = $a[3]; + } + } + if ($actor) { + if (!$defs{$actor}) { + my $msg = "$pn: THRESHOLD: Unknown actor device $actor specified"; + Log 2, $msg; + return $msg; + } + } + if (@b == 1) { # no actor parameters + $cmd1 = "set $actor off"; + $cmd2 = "set $actor on"; + $cmd_default = 2; + } else { + $cmd1 = $b[1]; + $cmd2 = $b[2]; + $cmd_default = $b[3]; + if (!$cmd_default) { + $cmd_default = 0; + } elsif ($cmd_default !~ m/^[0-2]*$/ ) { + my $msg = "$pn: THRESHOLD: cmd_default_index needs 0,1,2"; + Log 2, $msg; + return $msg; + } + } + + if ($actor) { + $cmd1 =~ s/@/$actor/g; + $cmd2 =~ s/@/$actor/g; + } + + $hash->{helper}{actor_cmd1} = $cmd1; + $hash->{helper}{actor_cmd2} = $cmd2; + $hash->{helper}{actor_cmd_default} = $cmd_default; + $hash->{STATE} = 'initialized' if (!ReadingsVal($pn,"desired_value","")); + + if ($init_desired_value) + { + readingsBeginUpdate ($hash); + readingsBulkUpdate ($hash, "state", "active $init_desired_value"); + readingsBulkUpdate ($hash, "threshold_min", $init_desired_value-$hysteresis); + readingsBulkUpdate ($hash, "cmd","wait for next cmd"); + readingsBulkUpdate ($hash, "desired_value", $init_desired_value); + readingsEndUpdate ($hash, 1); + } + return undef; +} + +########################## +sub +THRESHOLD_Set($@) +{ + my ($hash, @a) = @_; + my $pn = $hash->{NAME}; + my $ret=""; + return "Need a parameter for set" if(@a < 2); + my $arg = $a[1]; + my $desired_value; + if ($arg eq "desired" ) { + return "Set desired needs a numeric parameter" if(@a != 3 || $a[2] !~ m/^[-\d\.]*$/); + Log GetLogLevel($pn,3), "THRESHOLD set $pn $arg $a[2]"; + readingsBeginUpdate ($hash); + readingsBulkUpdate ($hash, "state", "active $a[2]"); + readingsBulkUpdate ($hash, "threshold_min",$a[2]-$hash->{hysteresis}); + readingsBulkUpdate ($hash, "cmd","wait for next cmd"); + readingsBulkUpdate ($hash, "desired_value", $a[2]); + readingsEndUpdate ($hash, 1); + } elsif ($arg eq "deactivated" ) { + $desired_value = ReadingsVal($pn,"desired_value",""); + return "Set desired value first" if (!$desired_value); + $ret=CommandAttr(undef, "$pn disable 1"); + if (!$ret) { + readingsSingleUpdate ($hash, "state", "deactivated $desired_value",1); + } + } elsif ($arg eq "active" ) { + $desired_value = ReadingsVal($pn,"desired_value",""); + return "Set desired value first" if (!$desired_value); + $ret=CommandDeleteAttr(undef, "$pn disable"); + if (!$ret) { + readingsBeginUpdate ($hash); + readingsBulkUpdate ($hash, "state", "active $desired_value"); + readingsBulkUpdate ($hash, "cmd","wait for next cmd"); + readingsEndUpdate ($hash, 1); + } + } elsif ($arg eq "hysteresis" ) { + return "Hysteresis needs a numeric parameter" if ($a[2] !~ m/^[\d\.]*$/ ); + $hash->{hysteresis} = $a[2]; + $desired_value = ReadingsVal($pn,"desired_value",""); + if ($desired_value) { + readingsBeginUpdate ($hash); + readingsBulkUpdate ($hash, "threshold_min",$desired_value-$hash->{hysteresis}); + readingsBulkUpdate ($hash, "cmd","wait for next cmd"); + readingsEndUpdate ($hash, 1); + } + } else { + return "Unknown argument $a[1], choose one of desired active deactivated hysteresis" + } + return $ret; +} + +########################## +sub +THRESHOLD_Notify($$) +{ + my ($hash, $dev) = @_; + my $pn = $hash->{NAME}; + + return "" if(($attr{$pn} && $attr{$pn}{disable}) || !ReadingsVal($pn,"desired_value","")); + + my $sensor = $hash->{sensor}; + my $reading = $hash->{sensor_reading}; + my $sensor2 = $hash->{sensor2}; + my $reading2 = $hash->{sensor2_reading}; + + if ($dev->{NAME} ne $sensor) { + if ($sensor2) { + if ($dev->{NAME} ne $sensor2) { + return ""; + } + } else { + return ""; + } + } + + if(!($defs{$sensor}{READINGS}{$reading})) { + my $msg = "$pn: no reading yet for $sensor $reading"; + Log 2, $msg; + return ""; + } + + my $instr = $defs{$sensor}{READINGS}{$reading}{VAL}; + + $instr =~ /[^\d^\-^.]*([-\d.]*)/; + + my $s_value = $1; + + + my $sensor_max = ReadingsVal($pn,"desired_value",""); + my $sensor_min = ReadingsVal($pn,"threshold_min",""); + my $cmd_default = $hash->{helper}{actor_cmd_default}; + + readingsSingleUpdate ($hash, "sensor_value",$s_value, 1); + + if (!$hash->{operator}) { + if ($s_value > $sensor_max) { + THRESHOLD_setValue($hash,1); + } elsif ($s_value < $sensor_min) { + THRESHOLD_setValue($hash,2); + } else { + THRESHOLD_setValue($hash,$cmd_default) if (ReadingsVal($pn,"cmd","") eq "wait for next cmd" && $cmd_default != 0); + } + } else { + if (!($defs{$sensor2}{READINGS}{$reading2})) { + my $msg = "$pn: no reading yet for $sensor2 $reading2"; + Log 2, $msg; + return ""; + } + + my $s2_state = $defs{$sensor2}{READINGS}{$reading2}{VAL}; + my $sensor2_state = $hash->{sensor2_state}; + + readingsSingleUpdate ($hash, "sensor2_state",$s2_state, 1); + + if ($hash->{operator} eq "AND") { + if (($s_value > $sensor_max) && ($s2_state eq $sensor2_state)) { + THRESHOLD_setValue($hash,1); + } elsif (($s_value < $sensor_min) || ($s2_state ne $sensor2_state)){ + THRESHOLD_setValue($hash,2); + } else { + THRESHOLD_setValue($hash,$cmd_default) if (ReadingsVal($pn,"cmd","") eq "wait for next cmd" && $cmd_default != 0); + } + } elsif ($hash->{operator} eq "OR") { + if (($s_value > $sensor_max) || ($s2_state eq $sensor2_state)) { + THRESHOLD_setValue($hash,1); + } elsif (($s_value < $sensor_min) && ($s2_state ne $sensor2_state)){ + THRESHOLD_setValue($hash,2); + } else { + THRESHOLD_setValue($hash,$cmd_default) if (ReadingsVal($pn,"cmd","") eq "wait for next cmd" && $cmd_default != 0); + } + } + } + return ""; +} + +sub +THRESHOLD_setValue($$) +{ + my ($hash, $cmd_nr) = @_; + my $pn = $hash->{NAME}; + my $ret=0; + my @cmd =($hash->{helper}{actor_cmd1},$hash->{helper}{actor_cmd2}); + my @cmd_sym = ("cmd1","cmd2"); + my $cmd_now = $cmd[$cmd_nr-1]; + my $cmd_sym_now = $cmd_sym[$cmd_nr-1]; + + if ($cmd_now) { + if (ReadingsVal($pn,"cmd","") ne $cmd_sym_now) { + if ($ret = AnalyzeCommandChain(undef, $cmd_now)) { + Log GetLogLevel($pn,3), "output of $pn $cmd_now: $ret"; + } else { + readingsSingleUpdate ($hash, "cmd",$cmd_sym_now, 1); + } + } + } +} + +1; + +=pod +=begin html + + +

THRESHOLD

+
    + This module reads any sensor that provides values ​​in decimal and execute FHEM/Perl commands, if the value of the sensor is higher or lower than the threshold value. + So can be easily implemented a software thermostat, hygrostat and much more.
    +
    + It is controlled by setting a desired value with:
    +
    + set <name> desired <value>
    +
    + The switching behavior can also be influenced by another sensor or sensor group.
    +
    +
+ + Define +
    +
    + define <name> THRESHOLD <sensor>[:<reading>][:<hysteresis>][:<init_desired_value>] [AND|OR <sensor2>[:<reading2>][:<state>]] [<actor>][|<cmd1>][|<cmd2>][|<cmd_default_index>]
    +
    +
    +
  • sensor
    + a defined sensor in FHEM +
  • +
  • reading
    + reading of the sensor, which includes a value in decimal
    + default value: temperature +
  • +
  • hysteresis
    + Hysteresis, this provides the threshold_min = desired_value - hysteresis
    + default value: 1 at temperature, 10 at huminity +
  • +
  • init_desired_value
    + Initial value, if no value is specified, it must be set with "set desired value".
    + Defaultwert: no value +
  • +
    +
    +
  • AND|OR
    + logical operator with an optional second sensor +
  • +
  • sensor2
    + the second sensor +
  • +
  • reading2
    + reading of the second sensor + default value: state +
  • +
  • state
    + state of the second sensor
    + default value: open +

  • +
  • actor
    + actor device defined in FHEM +
  • +
  • cmd1
    + FHEM/Perl command that is executed, if the value of the sensor is higher than desired value and/or the value of sensor 2 is matchted. @ is a placeholder for the specified actor.
    + default value: set actor off, if actor defined +
  • +
  • cmd2
    + FHEM/Perl command that is executed, if the value of the sensor is lower than threshold_min or the value of sensor 2 is not matchted. @ is a placeholder for the specified actor.
    + default value: set actor on, if actor defined +
  • +
  • cmd_default_index
    + Index of command that is executed after setting the desired value until the desired value or threshold_min value is reached.
    + 0 - no command
    + 1 - cmd1
    + 2 - cmd2
    + Defaultwert: 2, if actor defined, else 0
    +
  • +
    +
    + Examples:
    +
    + Example for heating:
    +
    + define Thermostat THRESHOLD temp_sens heating
    +
    + set Thermostat desired 20
    +
    + Description:
    +
    + It is heated up to the desired value of 20. If the value below the threshold_min value of 19 (20-1) + the heating is switched on again.
    +
    + Example for heating with window contact:
    +
    + define Thermostat THRESHOLD temp_sens OR win_sens heating
    +
    + Example for heating with multiple window contacts:
    +
    + define W_ALL structure W1 W2 W3 ....
    + attr W_ALL clientstate_behavior relative
    + attr W_ALL clientstate_priority closed open
    +
    + then:
    +
    + define Thermostat THRESHOLD S1 OR W_ALL heating
    +
    + More examples:
    +
    + define Hygrostat THRESHOLD hym_sens:huminity dehydrator|set @ on|set @ off|1
    + define Hygrostat THRESHOLD hym_sens:huminity AND Sensor2:state:close dehydrator|set @ on|set @ off|1
    + define Thermostat THRESHOLD temp_sens:temperature:1 aircon|set @ on|set @ off|1
    + define Thermostat THRESHOLD temp_sens AND Sensor2:state:close aircon|set @ on|set @ off|1
    + define Hygrostat THRESHOLD hym_sens:huminity:20 watering|set @ off|set @ on|2
    +
    + Alternatively, each Perl commands are given..
    +
    + Examples:
    +
    + define Thermostat THRESHOLD Sensor |{fhem("set Switch1 on;set Switch2 on")|{fhem("set Switch1 off;set Switch2 off")|{fhem("set Switch1 on;set Switch2 on")
    + define Thermostat THRESHOLD Sensor Alarm|{Log 2,"value is exceeded"}|set @ off|{fhem("set @ on;set Switch2 on")}
    + define Thermostat THRESHOLD Sensor ||{Log 2,"value is reached"}||
    +
    +
+ + Set +
    +
  • set <name> desired <value>
    + Set the desired value. If no desired value is set, the module is not active. +
  • +
    +
  • set <name> deactivated <value>
    + Module is disabled. +
  • +
    +
  • set <name> active <value>
    + Module is activated again. +
  • +
    +
  • set <name> hysteresis <value>
    + Set hysteresis value. +
  • +
+
+ + + Get +
    + N/A +
+
+ + + Attributes + +
+ +=end html +=begin html_DE + + +

THRESHOLD

+
    + Dieses Modul liest einen beliebigen Sensor aus, der Werte als Dezimalzahlen liefert und führt beim Überschreiten der Schwellen-Obergrenze (Sollwert) bzw. beim Unterschreiten der Schwellen-Untergrenze beliebige FHEM/Perl-Befehle aus. Damit lässt sich leicht z. B. ein Software-Thermostat oder -Hygrostat realisieren.
    +
    + Nach der Definition eines Moduls vom Typ THRESHOLD erfolgt die eigentliche Steuerung über die Vorgabe eines Sollwertes.
    +
    + Das geschieht über:
    +
    + set <name> desired <value>
    +
    + Das Modul beginnt mit der Steuerung erst dann, wenn ein Sollwert gesetzt wird.
    +
    + Optional kann das Schaltverhalten zusätzlich durch einen weiteren Sensor oder eine Sensorgruppe, + definiert über structure (z. B. Fensterkontakte), beeinflusst werden.
    +
    +
+ + Define +
    +
    + define <name> THRESHOLD <sensor>[:<reading>][:<hysteresis>][:<init_desired_value>] [AND|OR <sensor2>[:<reading2>][:<state>]] [<actor>][|<cmd1>][|<cmd2>][|<cmd_default_index>]
    +
    +
    +
  • sensor
    + ein in FHEM definierter Sensor +
  • +
  • reading
    + Reading des Sensors, der einen Wert als Dezimalzahl beinhaltet
    + Defaultwert: temperature +
  • +
  • hysteresis
    + Hysterese, daraus errechnet sich die Untergrenze = Sollwert - hysteresis
    + Defaultwert: 1 Temperaturen, 10 bei Feuchtigkeit +
  • +
  • init_desired_value
    + Initial-Sollwert, wenn kein Wert vorgegeben wird, muss er mit "set desired value" gesetzt werden
    + Defaultwert: kein +
  • +
    +
    +
  • AND|OR
    + Verknüpfung mit einem optionalen zweiten Sensor +
  • +
  • sensor2
    + ein definierter Sensor, dessen Status abgefragt wird +
  • +
  • reading2
    + Reading, der den Status des Sensors beinhaltet + Defaultwert: state +
  • +
  • state
    + Status des Sensors, der zu einer Aktion führt
    + Defaultwert: open +

  • +
  • actor
    + ein in FHEM definierter Aktor +
  • +
  • cmd1
    + FHEM/Perl Befehl, der beim Überschreiten des Sollwertes ausgeführt wird bzw. + wenn status des sensor2 übereinstimmt. @ ist ein Platzhalter für den angegebenen Aktor. +
    + Defaultwert: set actor off, wenn Aktor angegeben ist +
  • +
  • cmd2
    + FHEM/Perl Befehl, der beim Unterschreiten der Untergrenze (Sollwert-Hysterese) ausgeführt wird bzw. + wenn status des sensor2 nicht übereinstimmt. @ ist ein Platzhalter für den angegebenen Aktor. +
    + Defaultwert: set actor on, wenn Aktor angegeben ist +
  • +
  • cmd_default_index
    + FHEM/Perl Befehl, der nach dem Setzen des Sollwertes ausgeführt wird, bis Sollwert oder die Untergrenze erreicht wird.
    + 0 - kein Befehl
    + 1 - cmd1
    + 2 - cmd2
    + Defaultwert: 2, wenn Aktor angegeben ist, sonst 0
    +
  • +
    + Beispiele:
    +
    + Beispiel für Heizung:
    +
    + define Thermostat THRESHOLD temp_sens heating
    +
    + set Thermostat desired 20
    +
    + Beschreibung:
    +
    + Es wird geheizt bis zum Maximalwert 20. Beim Unterschreiten des Untergrenze von 19 (20-1) wird die Heizung wieder eingeschaltet.
    +
    + Beispiel für Heizung mit Fensterkontakt:
    +
    + define Thermostat THRESHOLD temp_sens OR win_sens heating
    +
    + Beispiel für Heizung mit mehreren Fensterkontakten:
    +
    + define W_ALL structure W1 W2 W3 ....
    + attr W_ALL clientstate_behavior relative
    + attr W_ALL clientstate_priority closed open
    +
    + danach:
    +
    + define Thermostat THRESHOLD S1 OR W_ALL heating
    +
    + einige weitere Bespiele:
    +
    + define Hygrostat THRESHOLD hym_sens:huminity dehydrator|set @ on|set @ off|1
    + define Hygrostat THRESHOLD hym_sens:huminity AND Sensor2:state:close dehydrator|set @ on|set @ off|1
    + define Thermostat THRESHOLD temp_sens:temperature:1 aircon|set @ on|set @ off|1
    + define Thermostat THRESHOLD temp_sens AND Sensor2:state:close aircon|set @ on|set @ off|1
    + define Hygrostat THRESHOLD hym_sens:huminity:20 watering|set @ off|set @ on|2
    +
    + Alternativ können jeweils Perl-Befehle angegeben werden.
    +
    + Beispiele:
    +
    + define Thermostat THRESHOLD Sensor |{fhem("set Switch1 on;set Switch2 on")|{fhem("set Switch1 off;set Switch2 off")|{fhem("set Switch1 on;set Switch2 on")
    + define Thermostat THRESHOLD Sensor Alarm|{Log 2,"Wert überschritten"}|set @ off|{fhem("set @ on;set Switch2 on")}
    + define Thermostat THRESHOLD Sensor ||{Log 2,"Wert unterschritten"}||
    +
    +
+ + Set +
    +
  • set <name> desired <value>
    + Setzt den Sollwert. Wenn kein Sollwert gesetzt ist, ist das Modul nicht aktiv. +
  • +
    +
  • set <name> deactivated <value>
    + Modul wird deaktiviert. +
  • +
    +
  • set <name> active <value>
    + Modul wird wieder aktiviert. +
  • +
    +
  • set <name> hysteresis <value>
    + Setzt Hysterese-Wert. +
  • +
+
+ + Get +
    + N/A +
+
+ + + Attributes + +
+ +=end html_DE +=cut