From d16e82ba5754f552254577afb7b1a920b8e2378f Mon Sep 17 00:00:00 2001 From: mfr69bs <> Date: Sun, 11 Nov 2012 20:36:02 +0000 Subject: [PATCH] rewrite of 59_HCS.pm git-svn-id: https://svn.fhem.de/fhem/trunk@2111 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/FHEM/59_HCS.pm | 910 ++++++++++++++++++++++++++++++-------------- 1 file changed, 614 insertions(+), 296 deletions(-) diff --git a/fhem/FHEM/59_HCS.pm b/fhem/FHEM/59_HCS.pm index 6ae28d4fc..43a3f2dee 100644 --- a/fhem/FHEM/59_HCS.pm +++ b/fhem/FHEM/59_HCS.pm @@ -1,11 +1,11 @@ -# $Id$ ################################################################ +# $Id$ # vim: ts=2:et # # (c) 2012 Copyright: Martin Fischer (m_fischer at gmx dot de) # All rights reserved # -# This script is free software; you can redistribute it and/or modify +# This script 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 # any later version. @@ -28,23 +28,40 @@ use warnings; sub HCS_Initialize($$); sub HCS_Define($$); +sub HCS_DoInit($); sub HCS_Undef($$); sub HCS_checkState($); sub HCS_Get($@); sub HCS_Set($@); sub HCS_setState($$); -sub HCS_getValves($$); +sub HCS_getValues($$); my %gets = ( - "valves" => "", + "values" => "", ); my %sets = ( - "interval" => "", - "on" => "", - "off" => "", - "valveThresholdOn" => "", - "valveThresholdOff" => "", + "interval" => "", + "eco" => "", + "mode" => "", + "on" => "", + "off" => "", +); + +my %defaults = ( + "idleperiod" => 10, + "interval" => 5, + "deviceCmdOn" => "on", + "deviceCmdOff" => "off", + "ecoTemperatureOn" => 16.0, + "ecoTemperatureOff" => 17.0, + "eventOnChangeReading" => "state,devicestate,eco,overdrive", + "loglevel" => 3, + "mode" => "thermostat", + "thermostatThresholdOn" => 0.5, + "thermostatThresholdOff" => 0.5, + "valveThresholdOn" => 35, + "valveThresholdOff" => 40, ); ##################################### @@ -57,54 +74,43 @@ HCS_Initialize($$) $hash->{UndefFn} = "HCS_Undef"; $hash->{GetFn} = "HCS_Get"; $hash->{SetFn} = "HCS_Set"; - $hash->{AttrList} = "device deviceCmdOn deviceCmdOff interval idleperiod ". + $hash->{NotifyFn} = "HCS_Notify"; + $hash->{AttrList} = "deviceCmdOn deviceCmdOff exclude ecoTemperatureOn ecoTemperatureOff ". + "interval idleperiod mode:thermostat,valve ". "sensor sensorThresholdOn sensorThresholdOff sensorReading ". - "valvesExcluded valveThresholdOn valveThresholdOff ". + "thermostatThresholdOn thermostatThresholdOff ". + "valveThresholdOn valveThresholdOff ". "do_not_notify:1,0 event-on-update-reading event-on-change-reading ". - "showtime:1,0 loglevel:0,1,2,3,4,5,6 disable:0,1"; + "loglevel:0,1,2,3,4,5,6 disable:0,1"; } ##################################### sub HCS_Define($$) { my ($hash, $def) = @_; + my $type = $hash->{TYPE}; - # define HCS [interval] [valveThresholdOn] [valveThresholdOff] - # define heatingControl HCS KG.hz.LC.SW1.01 10 40 30 + # define HCS + # define heatingControl HCS KG.hz.LC.SW1.01 my @a = split("[ \t][ \t]*", $def); - return "Wrong syntax: use 'define HCS [interval] [valveThresholdOn] [valveThresholdOff]'" + return "Wrong syntax: use 'define HCS '" if(@a < 3 || @a > 6); my $name = $a[0]; - $attr{$name}{device} = $a[2]; - $attr{$name}{deviceCmdOn} = AttrVal($name,"deviceCmdOn","on"); - $attr{$name}{deviceCmdOff} = AttrVal($name,"deviceCmdOff","off"); - $attr{$name}{interval} = AttrVal($name,"interval",(defined($a[3]) ? $a[3] : 10)); - $attr{$name}{valveThresholdOn} = AttrVal($name,"valveThresholdOn",(defined($a[4]) ? $a[4] : 40)); - $attr{$name}{valveThresholdOff} = AttrVal($name,"valveThresholdOff",(defined($a[5]) ? $a[5] : 35)); - - my $type = $hash->{TYPE}; - my $ret; if(!defined($defs{$a[2]})) { - $ret = "Device $a[2] not defined. Please add this device first!"; + my $ret = "Device $a[2] not defined. Please add this device first!"; Log 1, "$type $name $ret"; return $ret; } $hash->{STATE} = "Defined"; - my $interval = AttrVal($name,"interval",10); - my $timer; + readingsSingleUpdate($hash,"device",$a[2],1); - $ret = HCS_getValves($hash,0); - HCS_setState($hash,$ret); - - $timer = gettimeofday()+60; - InternalTimer($timer, "HCS_checkState", $hash, 0); - $hash->{NEXTCHECK} = FmtTime($timer); + HCS_DoInit($hash); return undef; } @@ -120,18 +126,81 @@ HCS_Undef($$) { return undef; } +##################################### +sub +HCS_Notify($$) { + my ($hash,$dev) = @_; + my $name = $hash->{NAME}; + my $type = $hash->{TYPE}; + + return if($dev->{NAME} ne "global" || + !grep(m/^INITIALIZED$/, @{$dev->{CHANGED}})); + + return if($attr{$name} && $attr{$name}{disable}); + + delete $modules{HCS}{NotifyFn}; + delete $hash->{NTFY_ORDER} if($hash->{NTFY_ORDER}); + + HCS_DoInit($hash); + + return undef; +} + +##################################### +sub +HCS_DoInit($) { + my ($hash) = @_; + my $name = $hash->{NAME}; + my $type = $hash->{TYPE}; + + # clean upd old stuff + foreach my $r ( keys %{$hash->{READINGS}} ) { + delete $hash->{READINGS}{$r} if($r =~ m/.*_state$/ || $r =~ m/.*_demand$/); + } + + $attr{$name}{deviceCmdOn} = AttrVal($name,"deviceCmdOn",$defaults{deviceCmdOn}); + $attr{$name}{deviceCmdOff} = AttrVal($name,"deviceCmdOff",$defaults{deviceCmdOff}); + $attr{$name}{"event-on-change-reading"} = AttrVal($name,"event-on-change-reading",$defaults{eventOnChangeReading}); + $attr{$name}{interval} = AttrVal($name,"interval",$defaults{interval}); + $attr{$name}{idleperiod} = AttrVal($name,"idleperiod",$defaults{idleperiod}); + $attr{$name}{mode} = AttrVal($name,"mode",$defaults{mode}); + if($attr{$name}{mode} ne "thermostat" && $attr{$name}{mode} ne "valve") { + Log 1, "$type $name unknown attribute mode '".$attr{$name}{mode}."'. Please use 'thermostat' or 'valve'."; + return undef; + } + $attr{$name}{thermostatThresholdOn} = AttrVal($name,"thermostatThresholdOn",$defaults{thermostatThresholdOn}); + $attr{$name}{thermostatThresholdOff} = AttrVal($name,"thermostatThresholdOff",$defaults{thermostatThresholdOff}); + $attr{$name}{valveThresholdOn} = AttrVal($name,"valveThresholdOn",$defaults{valveThresholdOn}); + $attr{$name}{valveThresholdOff} = AttrVal($name,"valveThresholdOff",$defaults{valveThresholdOff}); + + readingsSingleUpdate($hash,"state","Initialized",1); + + if($init_done) { + my $ret = HCS_getValues($hash,0); + HCS_setState($hash,$ret); + + RemoveInternalTimer($hash); + my $timer = gettimeofday()+($attr{$name}{interval}*60); + InternalTimer($timer, "HCS_checkState", $hash, 0); + $hash->{NEXTCHECK} = FmtTime($timer); + } + + return undef; +} + ##################################### sub HCS_checkState($) { my ($hash) = @_; my $name = $hash->{NAME}; - my $interval = AttrVal($name,"interval",10); + my $interval = AttrVal($name,"interval",$defaults{interval}); my $timer; my $ret; - $ret = HCS_getValves($hash,0); + $ret = HCS_getValues($hash,0); HCS_setState($hash,$ret); + RemoveInternalTimer($hash); $timer = gettimeofday()+($interval*60); InternalTimer($timer, "HCS_checkState", $hash, 0); $hash->{NEXTCHECK} = FmtTime($timer); @@ -157,8 +226,8 @@ HCS_Get($@) { # get argument my $arg = $a[1]; - if($arg eq "valves") { - $ret = HCS_getValves($hash,1); + if($arg eq "values") { + $ret = HCS_getValues($hash,1); return $ret; } @@ -173,6 +242,7 @@ HCS_Set($@) { my $type = $hash->{TYPE}; my $timer; my $ret; + my $str; # check syntax return "argument is missing @a" @@ -184,7 +254,36 @@ HCS_Set($@) { # get argument my $arg = $a[1]; - if($arg eq "interval") { + if($arg eq "eco") { + return "argument is missing, choose one of on off" + if(int(@a) < 3); + return "Unknown argument $a[2], choose one of on off" + if(lc($a[2]) ne "on" && lc($a[2]) ne "off"); + + my $ecoModeNew = lc($a[2]); + my $ecoTempOn = AttrVal($name,"ecoTemperatureOn",undef); + my $ecoTempOff = AttrVal($name,"ecoTemperatureOff",undef); + + if((!$ecoTempOn || !$ecoTempOff) && $ecoModeNew eq "on") { + $str = "missing attribute 'ecoTemperatureOn'. Please define this attribute first." + if(!$ecoTempOn); + $str = "missing attribute 'ecoTemperatureOff'. Please define this attribute first." + if(!$ecoTempOff); + Log 1, "$type $name $str"; + return $str; + } + + my $ecoModeOld = ReadingsVal($name,"eco","off"); + if($ecoModeNew ne $ecoModeOld) { + readingsSingleUpdate($hash, "eco",$ecoModeNew,1); + $str = "eco mode changed from $ecoModeOld to $ecoModeNew"; + Log 1, "$type $name $str"; + return $str; + } else { + return "eco mode '$ecoModeNew' already set."; + } + + } elsif($arg eq "interval") { return "Wrong interval format: Only digits are allowed!" if($a[2] !~ m/^\d+$/); @@ -198,38 +297,33 @@ HCS_Set($@) { $hash->{NEXTCHECK} = FmtTime($timer); Log 1, "$type $name interval changed from $intervalOld to $intervalNew"; - } elsif($arg eq "valveThresholdOn") { + } elsif($arg eq "mode") { + return "argument is missing, choose one of thermostat valve" + if(int(@a) < 3); + return "Unknown argument $a[2], choose one of thermostat valve" + if(lc($a[2]) ne "thermostat" && lc($a[2]) ne "valve"); - return "Wrong interval format: Only digits are allowed!" - if($a[2] !~ m/^\d+$/); + my $modeNew = $a[2]; + my $modeOld = AttrVal($name,"mode","thermostat"); - my $thresholdNew = $a[2]; - my $thresholdOld = AttrVal($name,"valveThresholdOn",40); - $attr{$name}{valveThresholdOn} = $thresholdNew; - Log 1, "$type $name valveThresholdOn changed from $thresholdOld to $thresholdNew"; - - } elsif($arg eq "valveThresholdOff") { - - return "Wrong interval format: Only digits are allowed!" - if($a[2] !~ m/^\d+$/); - - my $thresholdNew = $a[2]; - my $thresholdOld = AttrVal($name,"valveThresholdOff",35); - $attr{$name}{valveThresholdOff} = $thresholdNew; - Log 1, "$type $name valveThresholdOff changed from $thresholdOld to $thresholdNew"; + if($modeNew ne $modeOld) { + $attr{$name}{mode} = "thermostat" if(lc($a[2]) eq "thermostat"); + $attr{$name}{mode} = "valve" if(lc($a[2]) eq "valve"); + $str = "mode changed from $modeOld to $modeNew"; + Log 1, "$type $name $str"; + } else { + return "mode '$modeNew' already set."; + } } elsif($arg eq "on") { RemoveInternalTimer($hash); HCS_checkState($hash); - Log 1, "$type $name monitoring of valves started"; + Log 1, "$type $name monitoring of devices started"; } elsif($arg eq "off") { RemoveInternalTimer($hash); - #$hash->{STATE} = "off"; $hash->{NEXTCHECK} = "offline"; - readingsBeginUpdate($hash); - readingsBulkUpdate($hash, "state", "off"); - readingsEndUpdate($hash, 1); - Log 1, "$type $name monitoring of valves interrupted"; + readingsSingleUpdate($hash, "state", "off",1); + Log 1, "$type $name monitoring of devices interrupted"; } } @@ -240,228 +334,383 @@ HCS_setState($$) { my ($hash,$heatDemand) = @_; my $name = $hash->{NAME}; my $type = $hash->{TYPE}; - my $ll = AttrVal($name,"loglevel",3); - my $device = AttrVal($name,"device",""); - my $deviceState = Value($device); - my $deviceCmdOn = AttrVal($name,"deviceCmdOn","on"); - my $deviceCmdOff = AttrVal($name,"deviceCmdOff","off"); - my $idlePeriod = AttrVal($name,"idleperiod",0); - my $lastPeriodTime = ($hash->{helper}{lastSend}) ? $hash->{helper}{lastSend} : 0; - my $newPeriodTime = gettimeofday(); + my $ll = AttrVal($name,"loglevel",$defaults{loglevel}); + my $device = ReadingsVal($name,"device",""); + my $deviceState = Value($device); + my $deviceCmdOn = AttrVal($name,"deviceCmdOn",$defaults{deviceCmdOn}); + my $deviceCmdOff = AttrVal($name,"deviceCmdOff",$defaults{deviceCmdOff}); + my $eco = ReadingsVal($name,"eco","off"); + my $idlePeriod = AttrVal($name,"idleperiod",$defaults{idleperiod}); + my $lastPeriodTime = ($hash->{helper}{lastSentDeviceCmdOn}) ? $hash->{helper}{lastSentDeviceCmdOn} : 0; + my $newPeriodTime = int(gettimeofday()); my $diffPeriodTime = int((int($newPeriodTime)-int($lastPeriodTime))/60); - my $sensor = AttrVal($name,"sensor",undef); + my $overdrive = "off"; + my $idle = 0; + my $wait = "00:00:00"; my $cmd; - my $overdrive = 0; + my $mode; my $state; my $stateDevice; - if($heatDemand == 1) { + if($heatDemand == 0) { + $state = "idle"; + $cmd = $deviceCmdOff; + } elsif($heatDemand == 1) { $state = "demand"; $cmd = $deviceCmdOn; } elsif($heatDemand == 2) { - $overdrive = 1; - $state = "demand (overdrive)"; - $cmd = $deviceCmdOn; + $eco = "on"; + $state = "idle (eco)"; + $cmd = $deviceCmdOff; } elsif($heatDemand == 3) { - $overdrive = 1; + $eco = "on"; + $state = "demand (eco)"; + $cmd = $deviceCmdOn; + } elsif($heatDemand == 4) { + $overdrive = "on"; $state = "idle (overdrive)"; $cmd = $deviceCmdOff; - } else { - $state = "idle"; - $cmd = $deviceCmdOff; + } elsif($heatDemand == 5) { + $overdrive = "on"; + $state = "demand (overdrive)"; + $cmd = $deviceCmdOn; } - $state = "error" if(!defined($defs{$device})); - $stateDevice = ReadingsVal($name,"device",""); + my $eventOnChange = AttrVal($name,"event-on-change-reading",""); + my $eventOnUpdate = AttrVal($name,"event-on-update-reading",""); + $stateDevice = ReadingsVal($name,"devicestate",$defaults{deviceCmdOff}); + + if($idlePeriod && $diffPeriodTime < $idlePeriod) { + $wait = FmtTime((($idlePeriod-$diffPeriodTime)*60)-3600); + if($heatDemand == 1 || $heatDemand == 3 || $heatDemand == 5 && $cmd eq $deviceCmdOn) { + $idle = 1; + $state = "locked" if($stateDevice eq $deviceCmdOff); + } + } readingsBeginUpdate($hash); - readingsBulkUpdate($hash, "device", $cmd); - readingsBulkUpdate($hash, "overdrive", $overdrive) if($sensor); - readingsBulkUpdate($hash, "state", $state); - readingsEndUpdate($hash, 1); + if(!$defs{$device}) { + $state = "error"; + Log 1, "$type $name device '$device' does not exists."; + } else { + if($idle == 1 && $cmd eq $deviceCmdOn && $stateDevice ne $deviceCmdOn) { + Log $ll, "$type $name device $device locked for $wait min."; + } else { - if($defs{$device}) { - my $eventOnChange = AttrVal($name,"event-on-change-reading",""); - my $eventOnUpdate = AttrVal($name,"event-on-update-reading",""); - - if(!$eventOnChange || - ($eventOnUpdate && $eventOnUpdate =~ m/device/) || - ($eventOnChange && $eventOnChange =~ m/device/ && - ($cmd ne $stateDevice || $deviceState ne $stateDevice))) { - - if(!$idlePeriod || ($idlePeriod && $diffPeriodTime >= $idlePeriod)) { + if(!$eventOnChange || + ($eventOnUpdate && $eventOnUpdate =~ m/devicestate/ || + $eventOnChange && $eventOnChange =~ m/devicestate/ ) && + ($cmd ne $stateDevice || $deviceState ne $stateDevice)) { my $cmdret = CommandSet(undef,"$device $cmd"); - $hash->{helper}{lastSend} = $newPeriodTime; - Log 1, "$type $name An error occurred while switching device '$device': $cmdret" - if($cmdret); - } elsif($idlePeriod && $diffPeriodTime < $idlePeriod) { - Log $ll, "$type $name device $device blocked by idleperiod ($idlePeriod min.)"; + if($cmdret) { + Log 1, "$type $name An error occurred while switching device '$device': $cmdret"; + } else { + readingsBulkUpdate($hash, "devicestate", $cmd); + if($cmd eq $deviceCmdOn) { + $hash->{helper}{lastSentDeviceCmdOn} = $newPeriodTime; + $wait = FmtTime((($idlePeriod)*60)-3600); + } + } } } - } else { - Log 1, "$type $name device '$device' does not exists."; + } + readingsBulkUpdate($hash, "eco", $eco); + readingsBulkUpdate($hash, "locked", $wait); + readingsBulkUpdate($hash, "overdrive", $overdrive); + readingsBulkUpdate($hash, "state", $state); + readingsEndUpdate($hash, 1); + return undef; } ##################################### sub -HCS_getValves($$) { +HCS_getValues($$) { my ($hash,$list) = @_; my $name = $hash->{NAME}; my $type = $hash->{TYPE}; - my $excluded = AttrVal($name,"valvesExcluded",""); - my $heatDemand = 0; - my $valveThresholdOn = AttrVal($name,"valveThresholdOn",40); - my $valveThresholdOff = AttrVal($name,"valveThresholdOff",35); - my $ll = AttrVal($name,"loglevel",3); - my %valves = (); - my $valvesIdle = 0; - my $valveState; - my $valveLastDemand; - my $valveNewDemand; - my $value; + my %devs = (); + my $exclude = AttrVal($name,"exclude",""); + my @lengthNames; + my @lengthValves; my $ret; + # get devices + foreach my $d (sort keys %defs) { + my $t = $defs{$d}{TYPE}; + # skipping unneeded devices + next if($t ne "FHT" && $t ne "CUL_HM"); + next if($t eq "CUL_HM" && !$attr{$d}{model}); + next if($t eq "CUL_HM" && $attr{$d}{model} ne "HM-CC-TC"); + next if($t eq "CUL_HM" && $attr{$d}{model} eq "HM-CC-TC" && ($attr{$d}{device} || $attr{$d}{chanNo})); + + $devs{$d}{actuator} = ReadingsVal($d,"actuator","n/a"); + $devs{$d}{actuator} =~ s/(\s+|%)//g; + $devs{$d}{excluded} = ($exclude =~ m/$d/) ? 1 : 0; + $devs{$d}{ignored} = ($attr{$d}{ignore} && $attr{$d}{ignore} == 1) ? 1 : 0; + $devs{$d}{tempDesired} = ReadingsVal($d,"desired-temp","n/a"); + $devs{$d}{tempMeasured} = ReadingsVal($d,"measured-temp","n/a"); + $devs{$d}{type} = $t; + $hash->{helper}{device}{$d}{excluded} = $devs{$d}{excluded}; + $hash->{helper}{device}{$d}{ignored} = $devs{$d}{ignored}; + push(@lengthNames,$d); + push(@lengthValves,$devs{$d}{actuator}); + } + + my $ln = (reverse sort { $a <=> $b } map { length($_) } @lengthNames)[0]; + my $lv = (reverse sort { $a <=> $b } map { length($_) } @lengthValves)[0]; + + # show list of devices + if($list) { + my $nextCheck = ($hash->{NEXTCHECK}) ? $hash->{NEXTCHECK} : "n/a"; + my $delta; + my $str; + + foreach my $d (sort keys %devs) { + my $info = ""; + my $act = ($devs{$d}{actuator} eq "n/a") ? " n/a" : sprintf("%${lv}d",$devs{$d}{actuator}); + my $td = ($devs{$d}{tempDesired} eq "n/a") ? " n/a" : sprintf("%4.1f",$devs{$d}{tempDesired}); + my $tm = ($devs{$d}{tempMeasured} eq "n/a") ? " n/a" : sprintf("%4.1f",$devs{$d}{tempMeasured}); + $info = "idle" if($devs{$d}{ignored} == 0); + $info = "demand" if($devs{$d}{ignored} == 1); + $info = "(excluded)" if($devs{$d}{excluded} == 1); + $info = "(ignored)" if($devs{$d}{ignored} == 1); + + if($td eq " n/a" || $tm eq " n/a") { + $delta = " n/a"; + } else { + $delta = sprintf(" %.1f",$td-$tm); + $delta = sprintf("+%.1f",$tm-$td) if($tm > $td); + $delta = sprintf("-%.1f",$td-$tm) if($td > $tm); + } + $str .= sprintf("%-${ln}s: desired: %s°C measured: %s°C delta: %s valve: %${lv}d%% state: %s\n", + $d,$td,$tm,$delta,$act,$info); + } + + $str .= "next check: $nextCheck\n"; + $ret = $str; + + return $ret; + } + + # housekeeping + foreach my $d (sort keys %{$hash->{helper}{device}}) { + delete $hash->{helper}{device}{$d} if(!exists $devs{$d}); + } + # reset counter my $sumDemand = 0; + my $sumExcluded = 0; my $sumFHT = 0; my $sumHMCCTC = 0; - my $sumValves = 0; - my $sumExcluded = 0; + my $sumIdle = 0; my $sumIgnored = 0; + my $sumTotal = 0; + my $sumUnknown = 0; + my $ll = AttrVal($name,"loglevel",$defaults{loglevel}); + my $mode = AttrVal($name,"mode",$defaults{mode}); - foreach my $d (sort keys %defs) { - # skipping unneeded devices - next if($defs{$d}{TYPE} ne "FHT" && $defs{$d}{TYPE} ne "CUL_HM"); - next if($defs{$d}{TYPE} eq "CUL_HM" && !$attr{$d}{model}); - next if($defs{$d}{TYPE} eq "CUL_HM" && $attr{$d}{model} ne "HM-CC-TC"); - next if($defs{$d}{TYPE} eq "CUL_HM" && $attr{$d}{model} eq "HM-CC-TC" && ($attr{$d}{device} || $attr{$d}{chanNo})); + readingsBeginUpdate($hash); + foreach my $d (sort keys %devs) { + my $devState; + $hash->{helper}{device}{$d}{actuator} = $devs{$d}{actuator}; + $hash->{helper}{device}{$d}{excluded} = $devs{$d}{excluded}; + $hash->{helper}{device}{$d}{ignored} = $devs{$d}{ignored}; + $hash->{helper}{device}{$d}{tempDesired} = $devs{$d}{tempDesired}; + $hash->{helper}{device}{$d}{tempMeasured} = $devs{$d}{tempMeasured}; + $hash->{helper}{device}{$d}{type} = $devs{$d}{type}; + $sumFHT++ if(lc($devs{$d}{type}) eq "fht"); + $sumHMCCTC++ if(lc($devs{$d}{type}) eq "cul_hm"); + $sumTotal++; - # get current actuator state from each device - $valveState = $defs{$d}{READINGS}{"actuator"}{VAL}; - if($valveState || $valveState eq "0") { - $valveState =~ s/[\s%]//g; + if($devs{$d}{ignored}) { + $devState = "ignored"; + $hash->{helper}{device}{$d}{demand} = 0; + readingsBulkUpdate($hash,$d,$devState); + Log $ll+1, "$type $name $d: $devState"; + $sumIgnored++; + next; + } - if($attr{$d}{ignore}) { - $value = "$valveState% (ignored)"; - $valves{$defs{$d}{NAME}}{state} = $value; - $valves{$defs{$d}{NAME}}{demand} = 0; - $ret .= "$defs{$d}{NAME}: $value\n" if($list); - Log $ll+1, "$type $name $defs{$d}{NAME}: $value"; - $sumIgnored++; - $sumValves++; - $sumFHT++ if($defs{$d}{TYPE} eq "FHT"); - $sumHMCCTC++ if(defined($attr{$d}{model}) && $attr{$d}{model} eq "HM-CC-TC"); - next; + if($devs{$d}{excluded}) { + $devState = "excluded"; + $hash->{helper}{device}{$d}{demand} = 0; + readingsBulkUpdate($hash,$d,$devState); + Log $ll+1, "$type $name $d: $devState"; + $sumExcluded++; + next; + } + + if($mode eq "thermostat" && ($devs{$d}{tempMeasured} eq "n/a" || $devs{$d}{tempDesired} eq "n/a")) { + $devState = "unknown"; + $hash->{helper}{device}{$d}{demand} = 0; + readingsBulkUpdate($hash,$d,$devState); + Log $ll+1, "$type $name $d: $devState"; + $sumUnknown++; + next; + } + + my $lastState = ReadingsVal($name,$d,"idle"); + my $act = $devs{$d}{actuator}; + my $tm = $devs{$d}{tempMeasured}; + my $td = $devs{$d}{tempDesired}; + my $delta; + my $str; + + if(!$hash->{helper}{device}{$d}{demand}) { + $hash->{helper}{device}{$d}{demand} = 0; + $lastState = "idle"; + } + + if($mode eq "thermostat") { + my $tOn = AttrVal($name,"thermostatThresholdOn",$defaults{thermostatThresholdOn}); + my $tOff = AttrVal($name,"thermostatThresholdOff",$defaults{thermostatThresholdOff}); + + if($tm > $td && $tm-$td >= $tOff) { + $devState = "idle"; + $hash->{helper}{device}{$d}{demand} = 0; + $delta = $tm-$td; + $str = sprintf("desired: %4.1f measured: %4.1f delta: +%.1f open: %${lv}d%% state: %s",$td,$tm,$delta,$act,$devState); + $sumIdle++; + } elsif($td > $tm && $td-$tm >= $tOn) { + $devState = "demand"; + $hash->{helper}{device}{$d}{demand} = 1; + $delta = $td-$tm; + $str = sprintf("desired: %4.1f measured: %4.1f delta: -%.1f open: %${lv}d%% state: %s",$td,$tm,$delta,$act,$devState); + $sumDemand++; + } elsif($tm > $td) { + $devState = $lastState; + $delta = $tm-$td; + $str = sprintf("desired: %4.1f measured: %4.1f delta: +%.1f open: %${lv}d%% state: %s",$td,$tm,$delta,$act,$devState); + $sumIdle++ if($devState eq "idle"); + $sumDemand++ if($devState eq "demand"); + } elsif($td > $tm) { + $devState = $lastState; + $delta = $td-$tm; + $str = sprintf("desired: %4.1f measured: %4.1f delta: -%.1f open: %${lv}d%% state: %s",$td,$tm,$delta,$act,$devState); + $sumIdle++ if($devState eq "idle"); + $sumDemand++ if($devState eq "demand"); + } else { + $devState = $lastState; + $delta = $td-$tm; + $str = sprintf("desired: %4.1f measured: %4.1f delta: %.1f open: %${lv}d%% state: %s",$td,$tm,$delta,$act,$devState); + $sumIdle++ if($devState eq "idle"); + $sumDemand++ if($devState eq "demand"); } + } elsif($mode eq "valve") { + my $vOn = AttrVal($name,"valveThresholdOn",$defaults{valveThresholdOn}); + my $vOff = AttrVal($name,"valveThresholdOff",$defaults{valveThresholdOff}); + my $valve = $devs{$d}{actuator}; - if($excluded =~ m/$d/) { - $value = "$valveState% (excluded)"; - $valves{$defs{$d}{NAME}}{state} = $value; - $valves{$defs{$d}{NAME}}{demand} = 0; - $ret .= "$defs{$d}{NAME}: $value\n" if($list); - Log $ll+1, "$type $name $defs{$d}{NAME}: $value"; - $sumExcluded++; - $sumValves++; - $sumFHT++ if($defs{$d}{TYPE} eq "FHT"); - $sumHMCCTC++ if(defined($attr{$d}{model}) && $attr{$d}{model} eq "HM-CC-TC"); - next; - } - - $value = "$valveState%"; - $valves{$defs{$d}{NAME}}{state} = $value; - $ret .= "$defs{$d}{NAME}: $value" if($list); - Log $ll+1, "$type $name $defs{$d}{NAME}: $value"; - - # get last readings - $valveLastDemand = ReadingsVal($name,$d."_demand",0); - - # check heat demand from each valve - if($valveState >= $valveThresholdOn) { - $heatDemand = 1; - $valveNewDemand = $heatDemand; - $ret .= " (demand)\n" if($list); + if($valve >= $vOn) { + $devState = "demand"; + $hash->{helper}{device}{$d}{demand} = 1; $sumDemand++; } else { - if($valveLastDemand == 1) { - if($valveState > $valveThresholdOff) { - $heatDemand = 1; - $valveNewDemand = $heatDemand; - $ret .= " (demand)\n" if($list); + if($lastState eq "demand") { + if($valve > $vOff) { + $devState = "demand"; + $hash->{helper}{device}{$d}{demand} = 1; $sumDemand++; - } else { - $valveNewDemand = 0; - $ret .= " (idle)\n" if($list); - $valvesIdle++; + } else { + $devState = "idle"; + $hash->{helper}{device}{$d}{demand} = 0; + $sumIdle++; } } else { - $valveNewDemand = 0; - $ret .= " (idle)\n" if($list); - $valvesIdle++; + $devState = "idle"; + $hash->{helper}{device}{$d}{demand} = 0; + $sumIdle++; } + } + $delta = sprintf(" %.1f",$td-$tm); + $delta = sprintf("+%.1f",$tm-$td) if($tm > $td); + $delta = sprintf("-%.1f",$td-$tm) if($td > $tm); + $str = sprintf("desired: %4.1f measured: %4.1f delta: %s valve: %${lv}d%% state: %s",$td,$tm,$delta,$valve,$devState); + } - $valves{$defs{$d}{NAME}}{demand} = $valveNewDemand; + Log $ll+1, "$type $name $d: $str"; + readingsBulkUpdate($hash,$d,$devState); - # count devices - $sumFHT++ if($defs{$d}{TYPE} eq "FHT"); - $sumHMCCTC++ if(defined($attr{$d}{model}) && $attr{$d}{model} eq "HM-CC-TC"); - $sumValves++; + } + readingsEndUpdate($hash,1); + + my $heatDemand = 0; + + foreach my $d (sort keys %{$hash->{helper}{device}}) { + $heatDemand = 1 if($hash->{helper}{device}{$d}{demand} && $hash->{helper}{device}{$d}{demand} == 1); + } + + # eco mode + my $eco = "no"; + my $ecoTempOn = AttrVal($name,"ecoTemperatureOn",undef); + my $ecoTempOff = AttrVal($name,"ecoTemperatureOff",undef); + my $ecoState = ReadingsVal($name,"eco","off"); + + if($ecoState eq "on" && (!$ecoTempOn || !$ecoTempOff)) { + Log 1, "$type $name missing attribute 'ecoTemperatureOn'. Please define this attribute first." + if(!$ecoTempOn); + Log 1, "$type $name missing attribute 'ecoTemperatureOff'. Please define this attribute first." + if(!$ecoTempOff); + } elsif($ecoState eq "on") { + foreach my $d (sort keys %{$hash->{helper}{device}}) { + my $ignore = $hash->{helper}{device}{$d}{ignored}; + my $exclude = $hash->{helper}{device}{$d}{excluded}; + my $tempMeasured = $hash->{helper}{device}{$d}{tempMeasured}; + next if($tempMeasured eq "n/a"); + if(!$ignore && !$exclude) { + $heatDemand = 2 if($tempMeasured >= $ecoTempOff && $heatDemand != 3); + $heatDemand = 3 if($tempMeasured <= $ecoTempOn); + $eco = "yes" if($heatDemand == 2 || $heatDemand == 3); + } } } # overdrive mode - my $sensor = AttrVal($name,"sensor",undef); - my $sensorReading = AttrVal($name,"sensorReading",undef); - my $sensorThresholdOn = AttrVal($name,"sensorThresholdOn",undef); - my $sensorThresholdOff = AttrVal($name,"sensorThresholdOff",undef); - my $tempValue; - my $overdrive = "no"; + my $overdrive = "no"; + my $sensor = AttrVal($name,"sensor",undef); + my $sReading = AttrVal($name,"sensorReading",undef); + my $sTresholdOn = AttrVal($name,"sensorThresholdOn",undef); + my $sTresholdOff = AttrVal($name,"sensorThresholdOff",undef); - if(defined($sensor) && defined($sensorThresholdOn) && defined($sensorThresholdOff) && defined($sensorReading)) { - - if(!defined($defs{$sensor})) { - Log 1, "$type $name Device $sensor not defined. Please add this device first!"; - } else { - $tempValue = ReadingsVal($sensor,$sensorReading,""); - if(!$tempValue || $tempValue !~ m/^.*\d+.*$/) { + if(!$sensor) { + delete $hash->{READINGS}{sensor} if($hash->{READINGS}{sensor}); + } else { + Log 1, "$type $name Device $sensor not defined. Please add this device first!" + if(!defined($defs{$sensor})); + Log 1, "$type $name missing attribute 'sensorReading'. Please define this attribute first." + if(!$sReading); + Log 1, "$type $name missing attribute 'sensorThresholdOn'. Please define this attibute first." + if(!$sTresholdOn); + Log 1, "$type $name missing attribute 'sensorThresholdOff'. Please define this attribute first." + if(!$sTresholdOff); + + if($defs{$sensor} && $sReading && $sTresholdOn && $sTresholdOff) { + my $tValue = ReadingsVal($sensor,$sReading,"n/a"); + if($tValue eq "n/a" || $tValue !~ m/^.*\d+.*$/) { Log 1, "$type $name Device $sensor has no valid value."; } else { - $tempValue =~ s/(\s|°|[A-Z]|[a-z])+//g; - - $heatDemand = 2 if($tempValue <= $sensorThresholdOn); - $heatDemand = 3 if($tempValue > $sensorThresholdOff); - $overdrive = "yes" if($heatDemand == 2 || $heatDemand == 3); + $tValue =~ s/(\s|°|[A-Z]|[a-z])+//g; + $heatDemand = 4 if($tValue >= $sTresholdOff); + $heatDemand = 5 if($tValue <= $sTresholdOn); + $overdrive = "yes" if($heatDemand == 4 || $heatDemand == 5); + readingsSingleUpdate($hash,"sensor",$tValue,1); } } - } else { - if(!$sensor) { - delete $hash->{READINGS}{sensor}; - delete $hash->{READINGS}{overdrive}; - delete $attr{$name}{sensorReading}; - delete $attr{$name}{sensorThresholdOn}; - delete $attr{$name}{sensorThresholdOff}; - } + } + my $str = sprintf("Found %d Device(s): %d FHT, %d HM-CC-TC, demand: %d, idle: %d, ignored: %d, exlcuded: %d, unknown: %d", + $sumTotal,$sumFHT,$sumHMCCTC,$sumDemand,$sumIdle,$sumIgnored,$sumExcluded,$sumUnknown); + Log $ll, "$type $name $str, eco: $eco overdrive: $overdrive"; - #my $sumDemand = $sumValves-$valvesIdle-$sumIgnored-$sumExcluded; - Log $ll, "$type $name Found $sumValves Device(s): $sumFHT FHT, $sumHMCCTC HM-CC-TC. ". - "demand: $sumDemand, idle: $valvesIdle, ignored: $sumIgnored, excluded: $sumExcluded, overdrive: $overdrive"; + return $heatDemand; - readingsBeginUpdate($hash); - for my $d (sort keys %valves) { - readingsBulkUpdate($hash, $d."_state", $valves{$d}{state}); - readingsBulkUpdate($hash, $d."_demand", $valves{$d}{demand}); - } - readingsBulkUpdate($hash, "sensor", $tempValue) if(defined($tempValue) && $tempValue ne ""); - readingsEndUpdate($hash, 1); - - return ($list) ? $ret : $heatDemand; } 1; @@ -472,64 +721,95 @@ HCS_getValves($$) {

HCS

    - Defines a virtual device for monitoring heating valves (FHT, HM-CC-VD) to control - a central heating unit.

    + Defines a virtual device for monitoring thermostats (FHT, HM-CC-TC) to control a central + heating unit.

    Define
      - define <name> HCS <device> <interval> <valveThresholdOn> <valveThresholdOff> + define <name> HCS <device>

      • <device> the name of a predefined device to switch.
      • -
      • <interval> is a digit number. The unit is minutes.
      • -
      • <valveThresholdOn> is a digit number. Threshold upon which device is switched on (heating required).
      • -
      • <valveThresholdOff> is a digit number. Threshold upon which device is switched off (idle).

      - The HCS (heating control system) device monitors the state of all detected - valves in a free definable interval (by default: 10 min). + The HCS (heating control system) device monitors the state of all detected thermostats + in a free definable interval (by default: 10 min).

      Regulation for heating requirement or suppression of the request can be controlled by - valve position using also free definable thresholds. -

      - + valve position or measured temperature (default) using also free definable thresholds. In doing so, the HCS device also includes the hysteresis between two states.

      - Example:
      - Threshold valve position for heating requirement: 40% (default) - Threshold valve position for idle: 35% (default) + Example for monitoring measured temperature: +
        + Threshold temperature for heating requirement: 0.5 (default)
        + Threshold temperature for idle: 0.5 (default)
        +
        + + Heating is required when the measured temperature of a thermostat is lower than + 0.5° Celsius as the desired temperature. HCS then activates the defined device + until the measured temperature of the thermostat is 0.5° Celsius higher as the + desired temperature (threshold for idle). In this example, both tresholds are equal. +
      +
      + + Example for monitoring valve position: +
        + Threshold valve position for heating requirement: 40% (default)
        + Threshold valve position for idle: 35% (default)
        +
        + + Heating is required when the "open" position of a valve is more than 40%. HCS then + activates the defined device until the "open" position of the valve has lowered to + 35% or less (threshold for idle). +
      +
      + + The HCS device supports an optional eco mode. The threshold oriented regulation by + measured temperature or valve position can be overridden by setting economic thresholds. +

      + + Example: +
        + Threshold temperature economic mode on: 15° Celsius
        + Threshold temperature economic mode off: 25° Celsius
        +
        + + HCS activates the defined device until the measured temperature of one ore more + thermostats is lower or equal than 15° Celsius. If a measured temperature of one + or more thermostats is higher or equal than 25° Celsius, HCS switch of the defined + device (if none of the measured temperatures of all thermostats is lower or equal as + 15° Celsius). +
      +
      + + In addition, the HCS device supports an optional temp-sensor. The threshold and economic + oriented regulation can be overriden by the reading of the temp-sensor (overdrive mode).

      - Heating is required when the "open" position of a valve is more than 40%. HCS - then activates the defined device until the "open" position of the valve has - lowered to 35% or less (threshold for idle). -

      + Example: +
        + Threshold temperature reading for heating requirement: 10° Celsius
        + Threshold temperature reading for idle: 18° Celsius
        +
        - In addition, the HCS device supports an optional temp-sensor. The valve-position oriented - regulation can be overriden by the reading of the temp-sensor. -

        - - Example:
        - Threshold temperature reading for heating requirement: 10° Celsius - Threshold temperature reading for idle: 18° Celsius -

        - - Is a valve reaching or exceeding the threshold for heating requirement (>=40%), but the - temperature reading is more than 18° Celcius, the selected device will stay deactivated. - The valve-position oriented regulation has been overridden by the temperature reading in this example. -

        + Is a measured temperature ore valve position reaching or exceeding the threshold for + heating requirement, but the temperature reading is more than 18° Celcius, the + selected device will stay deactivated. The measured temperature or valve-position + oriented regulation has been overridden by the temperature reading in this example. +
      +
      The HCS device automatically detects devices which are ignored. Furthermore, certain devices can also be excluded of the monitoring manually.

      To reduce the transmission load, use the attribute event-on-change-reading, e.g. - attr <name> event-on-change-reading state,demand + attr <name> event-on-change-reading state,devicestate,eco,overdrive

      To avoid frequent switching "on" and "off" of the device, a timeout (in minutes) can be set @@ -539,75 +819,113 @@ HCS_getValves($$) { Get
        -
      • valves
        - returns the actual valve positions -

      • +
      • values
        + returns the actual values of each device +

      Set
        -
      • interval
        - modifies the interval of reading the actual valve positions. The unit is minutes. -

      • -
      • on
        +
      • eco <on>|<off>
        + enable (on) or disable (off) the economic mode. +
      • +
      • interval <value>
        + value modifies the interval of reading the actual valve positions. + The unit is minutes. +
      • +
      • mode <thermostat>|<valve>
        + changes the operational mode:
        + thermostat controls the heating demand by defined temperature + thresholds.
        + valve controls the heating demand by defined valve position thresholds. +
      • +
      • on
        restarts the monitoring after shutdown by off switch.
        HCS device starts up automatically upon FHEM start or after new device implementation! -

      • -
      • off
        +
      • +
      • off
        shutdown of monitoring, can be restarted by using the on command. -

      • -
      • valveThresholdOn
        - defines threshold upon which device is switched on (heating required). -

      • -
      • valveThresholdOff
        - defines threshold upon which device is switched off (idle). -

      • +

      Attributes
        -
      • device
        - optional; used to change the device. This is normally done in the define tag. -

      • -
      • deviceCmdOn
        +
      • deviceCmdOn (mandatory)
        command to activate the device, e.g. on. -

      • -
      • deviceCmdOff
        + Default value: on +
      • +
      • deviceCmdOff (mandatory)
        command to deactivate the device, e.g. off. -

      • -
      • idleperiod
        + Default value: off +
      • +
      • ecoTemperatureOn (Required by eco mode)
        + defines threshold for measured temperature upon which device is allways switched on +
      • +
      • ecoTemperatureOff (Required by eco mode)
        + defines threshold for measured temperature upon which device is switched off +
      • +
      • exclude (optional)
        + space or comma separated list of devices (FHT or HM-CC-TC) for excluding from + monitoring +
      • idleperiod (mandatory)
        locks the device to be switched for the specified period. The unit is minutes. -

      • -
      • sensor
        - device name of the temp-sensor (optional). -

      • -
      • sensorThresholdOn
        + Default value: 10 +
      • +
      • mode (mandatory)
        + defines the operational mode:
        + thermostat controls the heating demand by defined temperature + thresholds.
        + valve controls the heating demand by defined valve position thresholds.
        + Default value: thermostat +
      • +
      • sensor (optional)
        + device name of the temp-sensor +
      • +
      • sensorThresholdOn (Required by sensor)
        threshold for temperature reading activating the defined device Must be set if sensor has been defined -

      • -
      • sensorThresholdOff
        +
      • +
      • sensorThresholdOff (Required by sensor)
        threshold for temperature reading deactivating the defined device. Must be set if sensor has been defined -

      • -
      • sensorReading
        +
      • +
      • sensorReading (Required by sensor)
        name which is used for saving the "reading" of the defined temp-sensor. -

      • -
      • valvesExcluded
        - space separated list of devices (FHT or HM-CC-TC) for excluding -

      • -
      • valveThresholdOn
        - see Set -

      • -
      • valveThresholdOff
        -
      • see Set
        -
      • do_not_notify

      • -
      • showtime

      • -
      • event-on-update-reading

      • -
      • event-on-change-reading

      • + +
      • thermostatThresholdOn (Required by operational mode thermostat)
        + defines delta threshold between desired and measured temperature upon which device + is switched on (heating required).
        + Default value: 0.5 +
      • +
      • thermostatThresholdOff (Required by operational mode thermostat)
        + defines delta threshold between desired and measured temperature upon which + device is switched off (idle).
        + Default value: 0.5 +
      • +
      • valveThresholdOn (Required by operational mode valve)
        + defines threshold of valve-position upon which device is switched on (heating + required).
        + Default value: 40 +
      • +
      • valveThresholdOff (Required by operational mode valve)
        + defines threshold of valve-position upon which device is switched off (idle).
        + Default value: 35 +
      • +
      • disable
      • +
      • do_not_notify
      • +
      • event-on-change-reading
        + default value: state,devicestate,eco,overdrive +
      • +
      • event-on-update-reading
      • +
      • loglevel
        + loglevel 3 (or lower) shows a complete statistic of scanned devices (FHT or HM-CC-TC).
        + loglevel 4 shows a short summary of scanned devices.
        + loglevel 5 suppressed the above messages. +