| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952 |
- ################################################################
- # $Id: 59_HCS.pm 15883 2018-01-14 09:53:48Z hjr $
- # vim: ts=2:et
- #
- # (c) 2012 Copyright: Martin Fischer (m_fischer at gmx dot de)
- # All rights reserved
- #
- # 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.
- #
- # The GNU General Public License can be found at
- # http://www.gnu.org/copyleft/gpl.html.
- # A copy is found in the textfile GPL.txt and important notices to the license
- # from the author is found in LICENSE.txt distributed with these scripts.
- #
- # This script 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.
- #
- ################################################################
- package main;
- use strict;
- use warnings;
- sub HCS_Initialize($$);
- sub HCS_Define($$);
- sub HCS_Undef($$);
- sub HCS_Notify($$);
- sub HCS_DoInit($$);
- sub HCS_checkState($);
- sub HCS_Get($@);
- sub HCS_Set($@);
- sub HCS_setState($$);
- sub HCS_getValues($$);
- my %gets = (
- "values" => "",
- );
- my %sets = (
- "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",
- "mode" => "thermostat",
- "thermostatThresholdOn" => 0.5,
- "thermostatThresholdOff" => 0.5,
- "valveThresholdOn" => 40,
- "valveThresholdOff" => 35,
- );
- #####################################
- sub
- HCS_Initialize($$)
- {
- my ($hash) = @_;
- $hash->{DefFn} = "HCS_Define";
- $hash->{UndefFn} = "HCS_Undef";
- $hash->{GetFn} = "HCS_Get";
- $hash->{SetFn} = "HCS_Set";
- $hash->{NotifyFn} = "HCS_Notify";
- no warnings 'qw';
- my @attrList = qw(
- deviceCmdOff
- deviceCmdOn
- disable:0,1
- ecoTemperatureOff
- ecoTemperatureOn
- exclude
- idleperiod
- interval
- mode:thermostat,valve
- sensor
- sensorReading
- sensorThresholdOff
- sensorThresholdOn
- thermostatThresholdOff
- thermostatThresholdOn
- valveThresholdOff
- valveThresholdOn
- );
- use warnings 'qw';
- $hash->{AttrList} = join(" ", @attrList) ." ". $readingFnAttributes;
- }
- #####################################
- sub
- HCS_Define($$) {
- my ($hash, $def) = @_;
- my $type = $hash->{TYPE};
- # define <name> HCS <device>
- # define heatingControl HCS KG.hz.LC.SW1.01
- my @a = split("[ \t][ \t]*", $def);
- return "Wrong syntax: use 'define <name> HCS <device>'"
- if(@a < 3 || @a > 6);
- my $name = $a[0];
- if(!defined($defs{$a[2]})) {
- my $ret = "Device $a[2] not defined. Please add this device first!";
- Log3 $name, 1, "$type $name $ret";
- return $ret;
- }
- $hash->{DEVICE} = $a[2];
- $hash->{STATE} = "Defined";
- $hash->{NOTIFYDEV} = "global"; # NotifyFn nur aufrufen wenn global events (INITIALIZED)
- HCS_DoInit($hash,$init_done);
- return undef;
- }
- #####################################
- sub
- HCS_Undef($$) {
- my ($hash, $name) = @_;
- delete($modules{HCS}{defptr}{$hash->{NAME}});
- RemoveInternalTimer($hash);
- return undef;
- }
- #####################################
- sub
- HCS_Notify($$) {
- my ($hash,$dev) = @_;
- my $name = $hash->{NAME};
- my $type = $hash->{TYPE};
- return if(!grep(m/^INITIALIZED$/, @{$dev->{CHANGED}}) && !grep(m/^REREADCFG$/, @{$dev->{CHANGED}}));
- return if(AttrVal($name,"disable",""));
- HCS_DoInit($hash,1);
- return undef;
- }
- #####################################
- sub
- HCS_DoInit($$) {
- my ($hash,$complete_init) = @_;
- 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$/);
- }
- delete $hash->{READINGS}{"device"};
- $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") {
- Log3 $name, 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});
- $hash->{STATE} = "Initialized";
- if($complete_init) {
- my $ret = HCS_getValues($hash,0);
- HCS_setState($hash,$ret);
- RemoveInternalTimer($hash);
- if(ReadingsVal($name,"state","off") ne "off") {
- Log3 $name, 4, "$type $name start interval timer.";
- my $timer = gettimeofday()+($attr{$name}{interval}*60);
- InternalTimer($timer, "HCS_checkState", $hash, 0);
- $hash->{NEXTCHECK} = FmtTime($timer);
- }
- else {
- readingsSingleUpdate($hash, "state", "off",0);
- }
- }
- return undef;
- }
- #####################################
- sub
- HCS_checkState($) {
- my ($hash) = @_;
- my $name = $hash->{NAME};
- my $interval = AttrVal($name,"interval",$defaults{interval});
- my $timer;
- my $ret;
- $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);
- return undef;
- }
- #####################################
- sub
- HCS_Get($@) {
- my ($hash, @a) = @_;
- my $name = $hash->{NAME};
- my $type = $hash->{TYPE};
- my $ret;
- # check syntax
- return "argument is missing @a"
- if(int(@a) != 2);
- # check argument
- return "Unknown argument $a[1], choose one of ".join(" ", sort keys %gets)
- if(!defined($gets{$a[1]}));
-
- # get argument
- my $arg = $a[1];
- if($arg eq "values") {
- $ret = HCS_getValues($hash,1);
- return $ret;
- }
- return undef;
- }
- #####################################
- sub
- HCS_Set($@) {
- my ($hash, @a) = @_;
- my $name = $hash->{NAME};
- my $type = $hash->{TYPE};
- my $timer;
- my $ret;
- my $str;
- # check syntax
- return "argument is missing @a"
- if(int(@a) < 2 || int(@a) > 3);
- # check argument
- return "Unknown argument $a[1], choose one of ".join(" ", sort keys %sets)
- if(!defined($sets{$a[1]}));
-
- # get argument
- my $arg = $a[1];
- 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);
- Log3 $name, 1, "$type $name $str";
- return $str;
- }
- my $ecoModeOld = ReadingsVal($name,"eco","off");
- if($ecoModeNew ne $ecoModeOld) {
- readingsSingleUpdate($hash, "eco",$ecoModeNew,1);
- $str = "switch eco mode $ecoModeNew";
- Log3 $name, 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+$/);
- my $intervalNew = $a[2];
- my $intervalOld = AttrVal($name,"interval",10);
- RemoveInternalTimer($hash);
- $attr{$name}{interval} = $intervalNew;
- $timer = gettimeofday()+($intervalNew*60);
- InternalTimer($timer, "HCS_checkState", $hash, 0);
- $hash->{NEXTCHECK} = FmtTime($timer);
- Log3 $name, 1, "$type $name interval changed from $intervalOld to $intervalNew";
- } 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");
- my $modeNew = $a[2];
- my $modeOld = AttrVal($name,"mode","thermostat");
- 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";
- Log3 $name, 1, "$type $name $str";
- } else {
- return "mode '$modeNew' already set.";
- }
- } elsif($arg eq "on") {
- RemoveInternalTimer($hash);
- readingsSingleUpdate($hash,"state","started",1);
- HCS_checkState($hash);
- Log3 $name, 1, "$type $name monitoring of devices started";
- } elsif($arg eq "off") {
- RemoveInternalTimer($hash);
- $hash->{NEXTCHECK} = "offline";
- readingsSingleUpdate($hash, "state", "off",1);
- if ( AttrVal($name,"deviceCmdOn","") eq Value($hash->{DEVICE}) ) {
- my $cmd = AttrVal($name,"deviceCmdOff","");
- CommandSet(undef,"$hash->{DEVICE} $cmd");
- }
- Log3 $name, 1, "$type $name monitoring of devices stopped";
- }
- }
- #####################################
- sub
- HCS_setState($$) {
- my ($hash,$heatDemand) = @_;
- my $name = $hash->{NAME};
- my $type = $hash->{TYPE};
- my $device = $hash->{DEVICE};
- my $deviceValue = 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 $lastSwitchTime = ($hash->{helper}{lastSentDeviceCmdOn}) ? $hash->{helper}{lastSentDeviceCmdOn} : 0;
- my $currentTime = int(gettimeofday());
- my $actIdleTime = int((int($currentTime)-int($lastSwitchTime))/60);
- my $overdrive = "off";
- my $idle = 0;
- my $wait = "00:00:00";
- my $cmd = $deviceCmdOff;
- my $state = "idle";
- return if(ReadingsVal($name,"state","off") eq "off");
-
- if($heatDemand == 0) {
- $state = "idle";
- } elsif($heatDemand == 1) {
- $state = "demand";
- $cmd = $deviceCmdOn;
- } elsif($heatDemand == 2) {
- $eco = "on";
- $state = "idle (eco)";
- } elsif($heatDemand == 3) {
- $eco = "on";
- $state = "demand (eco)";
- $cmd = $deviceCmdOn;
- } elsif($heatDemand == 4) {
- $overdrive = "on";
- $state = "idle (overdrive)";
- } elsif($heatDemand == 5) {
- $overdrive = "on";
- $state = "demand (overdrive)";
- $cmd = $deviceCmdOn;
- }
- my $eventOnChange = AttrVal($name,"event-on-change-reading","");
- my $eventOnUpdate = AttrVal($name,"event-on-update-reading","");
- my $deviceState = ReadingsVal($name,"devicestate",$defaults{deviceCmdOff});
- if($idlePeriod && $actIdleTime < $idlePeriod) {
- $wait = FmtTime((($idlePeriod-$actIdleTime)*60)-3600);
- if($cmd eq $deviceCmdOn) {
- $idle = 1;
- $state = "locked" if($deviceState eq $deviceCmdOff);
- }
- }
- readingsBeginUpdate($hash);
- if(!$defs{$device}) {
- $state = "error";
- Log3 $name, 1, "$type $name device '$device' does not exists.";
- } else {
- if($idle == 1 && $cmd eq $deviceCmdOn && $deviceState ne $deviceCmdOn) {
- Log3 $name, 3, "$type $name device $device locked for $wait min.";
- } else {
- if(!$eventOnChange ||
- ($eventOnUpdate =~ m/devicestate/ || $eventOnChange =~ m/devicestate/ ) &&
- ($cmd ne $deviceState || $deviceValue ne $deviceState)) {
- my $cmdret = CommandSet(undef,"$device $cmd");
- if($cmdret) {
- Log3 $name, 1, "$type $name An error occurred while switching device '$device': $cmdret";
- } else {
- readingsBulkUpdate($hash, "devicestate", $cmd);
- if($cmd eq $deviceCmdOn) {
- $hash->{helper}{lastSentDeviceCmdOn} = $currentTime;
- $wait = FmtTime((($idlePeriod)*60)-3600);
- }
- }
- }
- }
- }
- readingsBulkUpdate($hash, "eco", $eco);
- readingsBulkUpdate($hash, "locked", $wait);
- readingsBulkUpdate($hash, "overdrive", $overdrive);
- readingsBulkUpdate($hash, "state", $state);
- readingsEndUpdate($hash, 1);
- return undef;
- }
- #####################################
- sub
- HCS_getValues($$) {
- my ($hash,$list) = @_;
- my $name = $hash->{NAME};
- my $type = $hash->{TYPE};
- 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" && $t ne "MAX" && $t ne "ZWave");
- next if($t eq "MAX" && !$defs{$d}{type});
- next if($t eq "MAX" && $defs{$d}{type} !~ m/HeatingThermostat/);
- next if($t eq "CUL_HM" &&( !$attr{$d}{model}
- ||!( ($attr{$d}{model} eq "HM-CC-TC" && !$defs{$d}{device})
- ||($attr{$d}{model} eq "HM-CC-RT-DN" && !$defs{$d}{device}))));
-
- next if($t eq "ZWave" && $attr{$d}{classes} !~ m/THERMOSTAT_SETPOINT/);
- $devs{$d}{actuator} = ReadingsVal($d,"valveposition","n/a") if($t =~ m/(MAX)/);
- $devs{$d}{actuator} = ReadingsVal($d,"actuator","n/a") if($t =~ m/(FHT|CUL_HM|ZWave)/);
- if ($devs{$d}{actuator} =~ m/^\d+\s*%?$/) {
- $devs{$d}{actuator} =~ s/(\s+|%)//g;
- } else {
- $devs{$d}{actuator} = 0;
- }
- $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") if($t =~ m/(FHT|CUL_HM)/);
- $devs{$d}{tempDesired} = ReadingsVal($d,"desiredTemperature","n/a") if($t =~ m/(MAX)/);
- $devs{$d}{tempDesired} = ReadingsNum($d,"setpointTemp","n/a",1) if($t =~ m/(ZWave)/);
- $devs{$d}{tempMeasured} = ReadingsVal($d,"measured-temp","n/a") if($t =~ m/(FHT|CUL_HM)/);
- $devs{$d}{tempMeasured} = ReadingsNum($d,"temperature","n/a",1) if($t =~ m/(MAX|ZWave)/);
- $devs{$d}{tempDesired} = ($t =~ m/(FHT)/) ? 5.5 : 4.5 if($devs{$d}{tempDesired} eq "off");
- $devs{$d}{tempDesired} = 30.5 if($devs{$d}{tempDesired} eq "on");
- $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 %{$hash->{helper}{device}}) {
- my $info = "";
- my $act = ($hash->{helper}{device}{$d}{actuator} eq "n/a") ? " n/a" :
- sprintf("%${lv}d",$hash->{helper}{device}{$d}{actuator});
- my $td = ($hash->{helper}{device}{$d}{tempDesired} eq "n/a") ? " n/a" :
- sprintf("%4.1f",$hash->{helper}{device}{$d}{tempDesired});
- my $tm = ($hash->{helper}{device}{$d}{tempMeasured} eq "n/a") ? " n/a" :
- sprintf("%4.1f",$hash->{helper}{device}{$d}{tempMeasured});
- $info = "idle" if($hash->{helper}{device}{$d}{demand} == 0);
- $info = "demand" if($hash->{helper}{device}{$d}{demand} == 1);
- $info = "(excluded)" if($hash->{helper}{device}{$d}{excluded} == 1);
- $info = "(ignored)" if($hash->{helper}{device}{$d}{ignored} == 1);
- if($td eq " n/a" || $tm eq " n/a") {
- $delta = " n/a";
- } else {
- $delta = sprintf("%+5.1f",$tm-$td);
- }
- $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 $sumMAX = 0;
- my $sumZWave = 0;
- my $sumIdle = 0;
- my $sumIgnored = 0;
- my $sumTotal = 0;
- my $sumUnknown = 0;
- my $mode = AttrVal($name,"mode",$defaults{mode});
- 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};
- $sumMAX++ if(lc($devs{$d}{type}) eq "max");
- $sumFHT++ if(lc($devs{$d}{type}) eq "fht");
- $sumHMCCTC++ if(lc($devs{$d}{type}) eq "cul_hm");
- $sumZWave++ if(lc($devs{$d}{type}) eq "zwave");
- $sumTotal++;
- if($devs{$d}{ignored}) {
- $devState = "ignored";
- $hash->{helper}{device}{$d}{demand} = 0;
- readingsBulkUpdate($hash,$d,$devState);
- Log3 $name, 4, "$type $name $d: $devState";
- $sumIgnored++;
- next;
- }
- if($devs{$d}{excluded}) {
- $devState = "excluded";
- $hash->{helper}{device}{$d}{demand} = 0;
- readingsBulkUpdate($hash,$d,$devState);
- Log3 $name, 4, "$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);
- Log3 $name, 4, "$type $name $d: $devState";
- $sumUnknown++;
- next;
- }
- my $lastState = ReadingsVal($name,$d,"idle");
- my $valve = $devs{$d}{actuator};
- my $tm = $devs{$d}{tempMeasured};
- my $td = $devs{$d}{tempDesired};
- 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 + $tOff ) {
- $devState = "idle";
- $hash->{helper}{device}{$d}{demand} = 0;
- $sumIdle++;
- } elsif( $tm <= $td - $tOn ) {
- $devState = "demand";
- $hash->{helper}{device}{$d}{demand} = 1;
- $sumDemand++;
- } else {
- $devState = $lastState;
- $sumIdle++ if($devState eq "idle");
- $sumDemand++ if($devState eq "demand");
- }
- } elsif($mode eq "valve") {
- my $vThreshold = AttrVal($name,"valveThresholdOn",$defaults{valveThresholdOn});
- if ( $lastState eq "demand" ) {
- $vThreshold = AttrVal($name,"valveThresholdOff",$defaults{valveThresholdOff});
- }
- if ( $valve > $vThreshold ) {
- $devState = "demand";
- $hash->{helper}{device}{$d}{demand} = 1;
- $sumDemand++;
- } else {
- $devState = "idle";
- $hash->{helper}{device}{$d}{demand} = 0;
- $sumIdle++;
- }
- }
- my $str = sprintf("desired: %4.1f measured: %4.1f delta: %+5.1f valve: %${lv}d%% state: %s",$td,$tm,$tm-$td,$valve,$devState);
- Log3 $name, 4, "$type $name $d: $str";
- readingsBulkUpdate($hash,$d,$devState);
- }
- readingsEndUpdate($hash,1);
- my $heatDemand = ($sumDemand > 0)? 1:0;
- # 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" ) {
- if ( !$ecoTempOn ) {
- $attr{$name}{deviceCmdOn} = $defaults{deviceCmdOn};
- $ecoTempOn = $defaults{deviceCmdOn};
- Log3 $name, 1, "$type $name set attribute 'ecoTemperatureOn' to default $defaults{deviceCmdOn}."
- }
- if ( !$ecoTempOff ) {
- $attr{$name}{deviceCmdOff} = $defaults{deviceCmdOff};
- $ecoTempOff = $defaults{deviceCmdOff};
- Log3 $name, 1, "$type $name set attribute 'ecoTemperatureOff' to default $defaults{deviceCmdOff}."
- }
- 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);
- }
- else {
- Log3 $name, 1, "Excl/Ignored $hash->{helper}{device}{$d}{name}.";
- }
- }
- }
- # overdrive mode
- 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(!$sensor) {
- delete $hash->{READINGS}{sensor} if($hash->{READINGS}{sensor});
- } else {
- Log3 $name, 1, "$type $name Device $sensor not defined. Please add this device first!"
- if(!defined($defs{$sensor}));
- Log3 $name, 1, "$type $name missing attribute 'sensorReading'. Please define this attribute first."
- if(!$sReading);
- Log3 $name, 1, "$type $name missing attribute 'sensorThresholdOn'. Please define this attibute first."
- if(!$sTresholdOn);
- Log3 $name, 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+.*$/) {
- Log3 $name, 1, "$type $name Device $sensor has no valid value.";
- } else {
- $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);
- }
- }
- }
- my $str = sprintf("Found %d Device(s): %d FHT, %d HM-CC-TC, %d MAX, %d ZWave, demand: %d, idle: %d, ignored: %d, excluded: %d, unknown: %d",
- $sumTotal,$sumFHT,$sumHMCCTC, $sumMAX, $sumZWave, $sumDemand,$sumIdle,$sumIgnored,$sumExcluded,$sumUnknown);
- Log3 $name, 3, "$type $name $str, eco: $eco overdrive: $overdrive";
- return $heatDemand;
- }
- 1;
- =pod
- =item helper
- =item summary monitor thermostats and control a central heating unit
- =item summary_DE Überwache Thermostate und schalte eine zentrale Therme
- =begin html
- <a name="HCS"></a>
- <h3>HCS</h3>
- <ul>
- Defines a virtual device for monitoring thermostats (FHT, HM-CC-TC, MAX, Z-Wave) to control a central
- heating unit.<br><br>
- <a name="HCSdefine"></a>
- <b>Define</b>
- <ul>
- <code>define <name> HCS <device></code>
- <br><br>
- <ul>
- <li><code><device></code> the name of a predefined device to switch.</li>
- </ul>
- <br>
- The HCS (heating control system) device monitors the state of all detected thermostats
- in a free definable interval (by default: 10 min).
- <br><br>
- Regulation for heating requirement or suppression of the request can be controlled by
- valve position or measured temperature (default) using also free definable thresholds.
- In doing so, the HCS device also includes the hysteresis between two states.
- <br><br>
- Example for monitoring measured temperature:
- <ul>
- Threshold temperature for heating requirement: 0.5 (default)<br>
- Threshold temperature for idle: 0.5 (default)<br>
- <br>
- 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.
- </ul>
- <br>
- Example for monitoring valve position:
- <ul>
- Threshold valve position for heating requirement: 40% (default)<br>
- Threshold valve position for idle: 35% (default)<br>
- <br>
- 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).
- </ul>
- <br>
- 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.
- <br><br>
-
- Example:
- <ul>
- Threshold temperature economic mode on: 16° Celsius<br>
- Threshold temperature economic mode off: 17° Celsius<br>
- <br>
-
- HCS activates the defined device until the measured temperature of one ore more
- thermostats is lower or equal than 16° Celsius. If a measured temperature of one
- or more thermostats is higher or equal than 17° Celsius, HCS switch of the defined
- device (if none of the measured temperatures of all thermostats is lower or equal as
- 16° Celsius).
- </ul>
- <br>
-
- 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).
- <br><br>
- Example:
- <ul>
- Threshold temperature reading for heating requirement: 10° Celsius<br>
- Threshold temperature reading for idle: 18° Celsius<br>
- <br>
- 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.
- </ul>
- <br>
- The HCS device automatically detects devices which are ignored. Furthermore, certain
- devices can also be excluded of the monitoring manually.
- <br><br>
- To reduce the transmission load, use the attribute event-on-change-reading, e.g.
- <code>attr <name> event-on-change-reading state,devicestate,eco,overdrive</code>
- <br><br>
- To avoid frequent switching "on" and "off" of the device, a timeout (in minutes) can be set
- using the attribute <code>idleperiod</code>.
- <br><br>
- <a name="HCSget"></a>
- <b>Get </b>
- <ul>
- <li><code>values</code><br>
- returns the actual values of each device
- </li>
- </ul>
- <br>
- <a name="HCSset"></a>
- <b>Set</b>
- <ul>
- <li><code>eco <on>|<off></code><br>
- enable (<code>on</code>) or disable (<code>off</code>) the economic mode.
- </li>
- <li><code>interval <value></code><br>
- <code>value</code> modifies the interval of reading the actual valve positions.
- The unit is minutes.
- </li>
- <li><code>mode <thermostat>|<valve></code><br>
- changes the operational mode:<br>
- <code>thermostat</code> controls the heating demand by defined temperature
- thresholds.<br>
- <code>valve</code> controls the heating demand by defined valve position thresholds.
- </li>
- <li><code>on</code><br>
- restarts the monitoring after shutdown by <code>off</code> switch.<br>
- HCS device starts up automatically upon FHEM start or after new device implementation!
- </li>
- <li><code>off</code><br>
- shutdown of monitoring, can be restarted by using the <code>on</code> command.
- </li>
- </ul>
- <br>
- <a name="HCSattr"></a>
- <b>Attributes</b>
- <ul>
- <li><code>deviceCmdOn</code> (mandatory)<br>
- command to activate the device, e.g. <code>on</code>.
- Default value: <code>on</code>
- </li>
- <li><code>deviceCmdOff</code> (mandatory)<br>
- command to deactivate the device, e.g. <code>off</code>.
- Default value: <code>off</code>
- </li>
- <li><code>ecoTemperatureOn</code> (Required by <code>eco</code> mode)<br>
- defines threshold for measured temperature upon which device is allways switched on
- </li>
- <li><code>ecoTemperatureOff</code> (Required by <code>eco</code> mode)<br>
- defines threshold for measured temperature upon which device is switched off
- </li>
- <li><code>exclude</code> (optional)<br>
- space or comma separated list of devices (FHT or HM-CC-TC) for excluding from
- monitoring
- </li>
- <li><code>idleperiod</code> (mandatory)<br>
- locks the device to be switched for the specified period. The unit is minutes.
- Default value: <code>10</code>
- </li>
- <li><code>mode</code> (mandatory)<br>
- defines the operational mode:<br>
- <code>thermostat</code> controls the heating demand by defined temperature
- thresholds.<br>
- <code>valve</code> controls the heating demand by defined valve position thresholds.<br>
- Default value: <code>thermostat</code>
- </li>
- <li><code>sensor</code> (optional)<br>
- device name of the temp-sensor
- </li>
- <li><code>sensorThresholdOn</code> (Required by <code>sensor</code>)<br>
- threshold for temperature reading activating the defined device
- Must be set if <code>sensor</code> has been defined
- </li>
- <li><code>sensorThresholdOff</code> (Required by <code>sensor</code>)<br>
- threshold for temperature reading deactivating the defined device.
- Must be set if <code>sensor</code> has been defined
- </li>
- <li><code>sensorReading</code> (Required by <code>sensor</code>)<br>
- name which is used for saving the "reading" of the defined temp-sensor.
- </li>
- <li><code>thermostatThresholdOn</code> (Required by operational mode <code>thermostat</code>)<br>
- defines delta threshold between desired and measured temperature upon which device
- is switched on (heating required).<br>
- Default value: <code>0.5</code>
- </li>
- <li><code>thermostatThresholdOff</code> (Required by operational mode <code>thermostat</code>)<br>
- defines delta threshold between desired and measured temperature upon which
- device is switched off (idle).<br>
- Default value: <code>0.5</code>
- </li>
- <li><code>valveThresholdOn</code> (Required by operational mode <code>valve</code>)<br>
- defines threshold of valve-position upon which device is switched on (heating
- required).<br>
- Default value: <code>40</code>
- </li>
- <li><code>valveThresholdOff</code> (Required by operational mode <code>valve</code>)<br>
- defines threshold of valve-position upon which device is switched off (idle).<br>
- Default value: <code>35</code>
- </li>
- <li><a href="#disable"><code>disable</code></a></li>
- <li><a href="#do_not_notify"><code>do_not_notify</code></a></li>
- <li><a href="#event-on-change-reading"><code>event-on-change-reading</code></a><br>
- default value: <code>state,devicestate,eco,overdrive</code>
- </li>
- <li><a href="#event-on-update-reading"><code>event-on-update-reading</code></a></li>
- <li><a href="#verbose"><code>verbose</code></a><br>
- verbose 4 (or lower) shows a complete statistic of scanned devices (FHT or HM-CC-TC).<br>
- verbose 3 shows a short summary of scanned devices.<br>
- verbose 2 suppressed the above messages.
- </li>
- </ul>
- <br>
- </ul>
- <br>
- </ul>
- =end html
- =cut
|