59_HCS.pm 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952
  1. ################################################################
  2. # $Id: 59_HCS.pm 15883 2018-01-14 09:53:48Z hjr $
  3. # vim: ts=2:et
  4. #
  5. # (c) 2012 Copyright: Martin Fischer (m_fischer at gmx dot de)
  6. # All rights reserved
  7. #
  8. # This script free software; you can redistribute it and/or modify
  9. # it under the terms of the GNU General Public License as published by
  10. # the Free Software Foundation; either version 2 of the License, or
  11. # any later version.
  12. #
  13. # The GNU General Public License can be found at
  14. # http://www.gnu.org/copyleft/gpl.html.
  15. # A copy is found in the textfile GPL.txt and important notices to the license
  16. # from the author is found in LICENSE.txt distributed with these scripts.
  17. #
  18. # This script is distributed in the hope that it will be useful,
  19. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  20. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  21. # GNU General Public License for more details.
  22. #
  23. ################################################################
  24. package main;
  25. use strict;
  26. use warnings;
  27. sub HCS_Initialize($$);
  28. sub HCS_Define($$);
  29. sub HCS_Undef($$);
  30. sub HCS_Notify($$);
  31. sub HCS_DoInit($$);
  32. sub HCS_checkState($);
  33. sub HCS_Get($@);
  34. sub HCS_Set($@);
  35. sub HCS_setState($$);
  36. sub HCS_getValues($$);
  37. my %gets = (
  38. "values" => "",
  39. );
  40. my %sets = (
  41. "interval" => "",
  42. "eco" => "",
  43. "mode" => "",
  44. "on" => "",
  45. "off" => "",
  46. );
  47. my %defaults = (
  48. "idleperiod" => 10,
  49. "interval" => 5,
  50. "deviceCmdOn" => "on",
  51. "deviceCmdOff" => "off",
  52. "ecoTemperatureOn" => 16.0,
  53. "ecoTemperatureOff" => 17.0,
  54. "eventOnChangeReading" => "state,devicestate,eco,overdrive",
  55. "mode" => "thermostat",
  56. "thermostatThresholdOn" => 0.5,
  57. "thermostatThresholdOff" => 0.5,
  58. "valveThresholdOn" => 40,
  59. "valveThresholdOff" => 35,
  60. );
  61. #####################################
  62. sub
  63. HCS_Initialize($$)
  64. {
  65. my ($hash) = @_;
  66. $hash->{DefFn} = "HCS_Define";
  67. $hash->{UndefFn} = "HCS_Undef";
  68. $hash->{GetFn} = "HCS_Get";
  69. $hash->{SetFn} = "HCS_Set";
  70. $hash->{NotifyFn} = "HCS_Notify";
  71. no warnings 'qw';
  72. my @attrList = qw(
  73. deviceCmdOff
  74. deviceCmdOn
  75. disable:0,1
  76. ecoTemperatureOff
  77. ecoTemperatureOn
  78. exclude
  79. idleperiod
  80. interval
  81. mode:thermostat,valve
  82. sensor
  83. sensorReading
  84. sensorThresholdOff
  85. sensorThresholdOn
  86. thermostatThresholdOff
  87. thermostatThresholdOn
  88. valveThresholdOff
  89. valveThresholdOn
  90. );
  91. use warnings 'qw';
  92. $hash->{AttrList} = join(" ", @attrList) ." ". $readingFnAttributes;
  93. }
  94. #####################################
  95. sub
  96. HCS_Define($$) {
  97. my ($hash, $def) = @_;
  98. my $type = $hash->{TYPE};
  99. # define <name> HCS <device>
  100. # define heatingControl HCS KG.hz.LC.SW1.01
  101. my @a = split("[ \t][ \t]*", $def);
  102. return "Wrong syntax: use 'define <name> HCS <device>'"
  103. if(@a < 3 || @a > 6);
  104. my $name = $a[0];
  105. if(!defined($defs{$a[2]})) {
  106. my $ret = "Device $a[2] not defined. Please add this device first!";
  107. Log3 $name, 1, "$type $name $ret";
  108. return $ret;
  109. }
  110. $hash->{DEVICE} = $a[2];
  111. $hash->{STATE} = "Defined";
  112. $hash->{NOTIFYDEV} = "global"; # NotifyFn nur aufrufen wenn global events (INITIALIZED)
  113. HCS_DoInit($hash,$init_done);
  114. return undef;
  115. }
  116. #####################################
  117. sub
  118. HCS_Undef($$) {
  119. my ($hash, $name) = @_;
  120. delete($modules{HCS}{defptr}{$hash->{NAME}});
  121. RemoveInternalTimer($hash);
  122. return undef;
  123. }
  124. #####################################
  125. sub
  126. HCS_Notify($$) {
  127. my ($hash,$dev) = @_;
  128. my $name = $hash->{NAME};
  129. my $type = $hash->{TYPE};
  130. return if(!grep(m/^INITIALIZED$/, @{$dev->{CHANGED}}) && !grep(m/^REREADCFG$/, @{$dev->{CHANGED}}));
  131. return if(AttrVal($name,"disable",""));
  132. HCS_DoInit($hash,1);
  133. return undef;
  134. }
  135. #####################################
  136. sub
  137. HCS_DoInit($$) {
  138. my ($hash,$complete_init) = @_;
  139. my $name = $hash->{NAME};
  140. my $type = $hash->{TYPE};
  141. # clean upd old stuff
  142. foreach my $r ( keys %{$hash->{READINGS}} ) {
  143. delete $hash->{READINGS}{$r} if($r =~ m/.*_state$/ || $r =~ m/.*_demand$/);
  144. }
  145. delete $hash->{READINGS}{"device"};
  146. $attr{$name}{deviceCmdOn} = AttrVal($name,"deviceCmdOn",$defaults{deviceCmdOn});
  147. $attr{$name}{deviceCmdOff} = AttrVal($name,"deviceCmdOff",$defaults{deviceCmdOff});
  148. $attr{$name}{"event-on-change-reading"} = AttrVal($name,"event-on-change-reading",$defaults{eventOnChangeReading});
  149. $attr{$name}{interval} = AttrVal($name,"interval",$defaults{interval});
  150. $attr{$name}{idleperiod} = AttrVal($name,"idleperiod",$defaults{idleperiod});
  151. $attr{$name}{mode} = AttrVal($name,"mode",$defaults{mode});
  152. if($attr{$name}{mode} ne "thermostat" && $attr{$name}{mode} ne "valve") {
  153. Log3 $name, 1, "$type $name unknown attribute mode '".$attr{$name}{mode}."'. Please use 'thermostat' or 'valve'.";
  154. return undef;
  155. }
  156. $attr{$name}{thermostatThresholdOn} = AttrVal($name,"thermostatThresholdOn",$defaults{thermostatThresholdOn});
  157. $attr{$name}{thermostatThresholdOff} = AttrVal($name,"thermostatThresholdOff",$defaults{thermostatThresholdOff});
  158. $attr{$name}{valveThresholdOn} = AttrVal($name,"valveThresholdOn",$defaults{valveThresholdOn});
  159. $attr{$name}{valveThresholdOff} = AttrVal($name,"valveThresholdOff",$defaults{valveThresholdOff});
  160. $hash->{STATE} = "Initialized";
  161. if($complete_init) {
  162. my $ret = HCS_getValues($hash,0);
  163. HCS_setState($hash,$ret);
  164. RemoveInternalTimer($hash);
  165. if(ReadingsVal($name,"state","off") ne "off") {
  166. Log3 $name, 4, "$type $name start interval timer.";
  167. my $timer = gettimeofday()+($attr{$name}{interval}*60);
  168. InternalTimer($timer, "HCS_checkState", $hash, 0);
  169. $hash->{NEXTCHECK} = FmtTime($timer);
  170. }
  171. else {
  172. readingsSingleUpdate($hash, "state", "off",0);
  173. }
  174. }
  175. return undef;
  176. }
  177. #####################################
  178. sub
  179. HCS_checkState($) {
  180. my ($hash) = @_;
  181. my $name = $hash->{NAME};
  182. my $interval = AttrVal($name,"interval",$defaults{interval});
  183. my $timer;
  184. my $ret;
  185. $ret = HCS_getValues($hash,0);
  186. HCS_setState($hash,$ret);
  187. RemoveInternalTimer($hash);
  188. $timer = gettimeofday()+($interval*60);
  189. InternalTimer($timer, "HCS_checkState", $hash, 0);
  190. $hash->{NEXTCHECK} = FmtTime($timer);
  191. return undef;
  192. }
  193. #####################################
  194. sub
  195. HCS_Get($@) {
  196. my ($hash, @a) = @_;
  197. my $name = $hash->{NAME};
  198. my $type = $hash->{TYPE};
  199. my $ret;
  200. # check syntax
  201. return "argument is missing @a"
  202. if(int(@a) != 2);
  203. # check argument
  204. return "Unknown argument $a[1], choose one of ".join(" ", sort keys %gets)
  205. if(!defined($gets{$a[1]}));
  206. # get argument
  207. my $arg = $a[1];
  208. if($arg eq "values") {
  209. $ret = HCS_getValues($hash,1);
  210. return $ret;
  211. }
  212. return undef;
  213. }
  214. #####################################
  215. sub
  216. HCS_Set($@) {
  217. my ($hash, @a) = @_;
  218. my $name = $hash->{NAME};
  219. my $type = $hash->{TYPE};
  220. my $timer;
  221. my $ret;
  222. my $str;
  223. # check syntax
  224. return "argument is missing @a"
  225. if(int(@a) < 2 || int(@a) > 3);
  226. # check argument
  227. return "Unknown argument $a[1], choose one of ".join(" ", sort keys %sets)
  228. if(!defined($sets{$a[1]}));
  229. # get argument
  230. my $arg = $a[1];
  231. if($arg eq "eco") {
  232. return "argument is missing, choose one of on off"
  233. if(int(@a) < 3);
  234. return "Unknown argument $a[2], choose one of on off"
  235. if(lc($a[2]) ne "on" && lc($a[2]) ne "off");
  236. my $ecoModeNew = lc($a[2]);
  237. my $ecoTempOn = AttrVal($name,"ecoTemperatureOn",undef);
  238. my $ecoTempOff = AttrVal($name,"ecoTemperatureOff",undef);
  239. if((!$ecoTempOn || !$ecoTempOff) && $ecoModeNew eq "on") {
  240. $str = "missing attribute 'ecoTemperatureOn'. Please define this attribute first."
  241. if(!$ecoTempOn);
  242. $str = "missing attribute 'ecoTemperatureOff'. Please define this attribute first."
  243. if(!$ecoTempOff);
  244. Log3 $name, 1, "$type $name $str";
  245. return $str;
  246. }
  247. my $ecoModeOld = ReadingsVal($name,"eco","off");
  248. if($ecoModeNew ne $ecoModeOld) {
  249. readingsSingleUpdate($hash, "eco",$ecoModeNew,1);
  250. $str = "switch eco mode $ecoModeNew";
  251. Log3 $name, 1, "$type $name $str";
  252. return $str;
  253. } else {
  254. return "eco mode '$ecoModeNew' already set.";
  255. }
  256. } elsif($arg eq "interval") {
  257. return "Wrong interval format: Only digits are allowed!"
  258. if($a[2] !~ m/^\d+$/);
  259. my $intervalNew = $a[2];
  260. my $intervalOld = AttrVal($name,"interval",10);
  261. RemoveInternalTimer($hash);
  262. $attr{$name}{interval} = $intervalNew;
  263. $timer = gettimeofday()+($intervalNew*60);
  264. InternalTimer($timer, "HCS_checkState", $hash, 0);
  265. $hash->{NEXTCHECK} = FmtTime($timer);
  266. Log3 $name, 1, "$type $name interval changed from $intervalOld to $intervalNew";
  267. } elsif($arg eq "mode") {
  268. return "argument is missing, choose one of thermostat valve"
  269. if(int(@a) < 3);
  270. return "Unknown argument $a[2], choose one of thermostat valve"
  271. if(lc($a[2]) ne "thermostat" && lc($a[2]) ne "valve");
  272. my $modeNew = $a[2];
  273. my $modeOld = AttrVal($name,"mode","thermostat");
  274. if($modeNew ne $modeOld) {
  275. $attr{$name}{mode} = "thermostat" if(lc($a[2]) eq "thermostat");
  276. $attr{$name}{mode} = "valve" if(lc($a[2]) eq "valve");
  277. $str = "mode changed from $modeOld to $modeNew";
  278. Log3 $name, 1, "$type $name $str";
  279. } else {
  280. return "mode '$modeNew' already set.";
  281. }
  282. } elsif($arg eq "on") {
  283. RemoveInternalTimer($hash);
  284. readingsSingleUpdate($hash,"state","started",1);
  285. HCS_checkState($hash);
  286. Log3 $name, 1, "$type $name monitoring of devices started";
  287. } elsif($arg eq "off") {
  288. RemoveInternalTimer($hash);
  289. $hash->{NEXTCHECK} = "offline";
  290. readingsSingleUpdate($hash, "state", "off",1);
  291. if ( AttrVal($name,"deviceCmdOn","") eq Value($hash->{DEVICE}) ) {
  292. my $cmd = AttrVal($name,"deviceCmdOff","");
  293. CommandSet(undef,"$hash->{DEVICE} $cmd");
  294. }
  295. Log3 $name, 1, "$type $name monitoring of devices stopped";
  296. }
  297. }
  298. #####################################
  299. sub
  300. HCS_setState($$) {
  301. my ($hash,$heatDemand) = @_;
  302. my $name = $hash->{NAME};
  303. my $type = $hash->{TYPE};
  304. my $device = $hash->{DEVICE};
  305. my $deviceValue = Value($device);
  306. my $deviceCmdOn = AttrVal($name,"deviceCmdOn",$defaults{deviceCmdOn});
  307. my $deviceCmdOff = AttrVal($name,"deviceCmdOff",$defaults{deviceCmdOff});
  308. my $eco = ReadingsVal($name,"eco","off");
  309. my $idlePeriod = AttrVal($name,"idleperiod",$defaults{idleperiod});
  310. my $lastSwitchTime = ($hash->{helper}{lastSentDeviceCmdOn}) ? $hash->{helper}{lastSentDeviceCmdOn} : 0;
  311. my $currentTime = int(gettimeofday());
  312. my $actIdleTime = int((int($currentTime)-int($lastSwitchTime))/60);
  313. my $overdrive = "off";
  314. my $idle = 0;
  315. my $wait = "00:00:00";
  316. my $cmd = $deviceCmdOff;
  317. my $state = "idle";
  318. return if(ReadingsVal($name,"state","off") eq "off");
  319. if($heatDemand == 0) {
  320. $state = "idle";
  321. } elsif($heatDemand == 1) {
  322. $state = "demand";
  323. $cmd = $deviceCmdOn;
  324. } elsif($heatDemand == 2) {
  325. $eco = "on";
  326. $state = "idle (eco)";
  327. } elsif($heatDemand == 3) {
  328. $eco = "on";
  329. $state = "demand (eco)";
  330. $cmd = $deviceCmdOn;
  331. } elsif($heatDemand == 4) {
  332. $overdrive = "on";
  333. $state = "idle (overdrive)";
  334. } elsif($heatDemand == 5) {
  335. $overdrive = "on";
  336. $state = "demand (overdrive)";
  337. $cmd = $deviceCmdOn;
  338. }
  339. my $eventOnChange = AttrVal($name,"event-on-change-reading","");
  340. my $eventOnUpdate = AttrVal($name,"event-on-update-reading","");
  341. my $deviceState = ReadingsVal($name,"devicestate",$defaults{deviceCmdOff});
  342. if($idlePeriod && $actIdleTime < $idlePeriod) {
  343. $wait = FmtTime((($idlePeriod-$actIdleTime)*60)-3600);
  344. if($cmd eq $deviceCmdOn) {
  345. $idle = 1;
  346. $state = "locked" if($deviceState eq $deviceCmdOff);
  347. }
  348. }
  349. readingsBeginUpdate($hash);
  350. if(!$defs{$device}) {
  351. $state = "error";
  352. Log3 $name, 1, "$type $name device '$device' does not exists.";
  353. } else {
  354. if($idle == 1 && $cmd eq $deviceCmdOn && $deviceState ne $deviceCmdOn) {
  355. Log3 $name, 3, "$type $name device $device locked for $wait min.";
  356. } else {
  357. if(!$eventOnChange ||
  358. ($eventOnUpdate =~ m/devicestate/ || $eventOnChange =~ m/devicestate/ ) &&
  359. ($cmd ne $deviceState || $deviceValue ne $deviceState)) {
  360. my $cmdret = CommandSet(undef,"$device $cmd");
  361. if($cmdret) {
  362. Log3 $name, 1, "$type $name An error occurred while switching device '$device': $cmdret";
  363. } else {
  364. readingsBulkUpdate($hash, "devicestate", $cmd);
  365. if($cmd eq $deviceCmdOn) {
  366. $hash->{helper}{lastSentDeviceCmdOn} = $currentTime;
  367. $wait = FmtTime((($idlePeriod)*60)-3600);
  368. }
  369. }
  370. }
  371. }
  372. }
  373. readingsBulkUpdate($hash, "eco", $eco);
  374. readingsBulkUpdate($hash, "locked", $wait);
  375. readingsBulkUpdate($hash, "overdrive", $overdrive);
  376. readingsBulkUpdate($hash, "state", $state);
  377. readingsEndUpdate($hash, 1);
  378. return undef;
  379. }
  380. #####################################
  381. sub
  382. HCS_getValues($$) {
  383. my ($hash,$list) = @_;
  384. my $name = $hash->{NAME};
  385. my $type = $hash->{TYPE};
  386. my %devs = ();
  387. my $exclude = AttrVal($name,"exclude","");
  388. my @lengthNames;
  389. my @lengthValves;
  390. my $ret;
  391. # get devices
  392. foreach my $d (sort keys %defs) {
  393. my $t = $defs{$d}{TYPE};
  394. # skipping unneeded devices
  395. next if($t ne "FHT" && $t ne "CUL_HM" && $t ne "MAX" && $t ne "ZWave");
  396. next if($t eq "MAX" && !$defs{$d}{type});
  397. next if($t eq "MAX" && $defs{$d}{type} !~ m/HeatingThermostat/);
  398. next if($t eq "CUL_HM" &&( !$attr{$d}{model}
  399. ||!( ($attr{$d}{model} eq "HM-CC-TC" && !$defs{$d}{device})
  400. ||($attr{$d}{model} eq "HM-CC-RT-DN" && !$defs{$d}{device}))));
  401. next if($t eq "ZWave" && $attr{$d}{classes} !~ m/THERMOSTAT_SETPOINT/);
  402. $devs{$d}{actuator} = ReadingsVal($d,"valveposition","n/a") if($t =~ m/(MAX)/);
  403. $devs{$d}{actuator} = ReadingsVal($d,"actuator","n/a") if($t =~ m/(FHT|CUL_HM|ZWave)/);
  404. if ($devs{$d}{actuator} =~ m/^\d+\s*%?$/) {
  405. $devs{$d}{actuator} =~ s/(\s+|%)//g;
  406. } else {
  407. $devs{$d}{actuator} = 0;
  408. }
  409. $devs{$d}{excluded} = ($exclude =~ m/$d/) ? 1 : 0;
  410. $devs{$d}{ignored} = ($attr{$d}{ignore} && $attr{$d}{ignore} == 1) ? 1 : 0;
  411. $devs{$d}{tempDesired} = ReadingsVal($d,"desired-temp","n/a") if($t =~ m/(FHT|CUL_HM)/);
  412. $devs{$d}{tempDesired} = ReadingsVal($d,"desiredTemperature","n/a") if($t =~ m/(MAX)/);
  413. $devs{$d}{tempDesired} = ReadingsNum($d,"setpointTemp","n/a",1) if($t =~ m/(ZWave)/);
  414. $devs{$d}{tempMeasured} = ReadingsVal($d,"measured-temp","n/a") if($t =~ m/(FHT|CUL_HM)/);
  415. $devs{$d}{tempMeasured} = ReadingsNum($d,"temperature","n/a",1) if($t =~ m/(MAX|ZWave)/);
  416. $devs{$d}{tempDesired} = ($t =~ m/(FHT)/) ? 5.5 : 4.5 if($devs{$d}{tempDesired} eq "off");
  417. $devs{$d}{tempDesired} = 30.5 if($devs{$d}{tempDesired} eq "on");
  418. $devs{$d}{type} = $t;
  419. $hash->{helper}{device}{$d}{excluded} = $devs{$d}{excluded};
  420. $hash->{helper}{device}{$d}{ignored} = $devs{$d}{ignored};
  421. push(@lengthNames,$d);
  422. push(@lengthValves,$devs{$d}{actuator});
  423. }
  424. my $ln = (reverse sort { $a <=> $b } map { length($_) } @lengthNames)[0];
  425. my $lv = (reverse sort { $a <=> $b } map { length($_) } @lengthValves)[0];
  426. # show list of devices
  427. if($list) {
  428. my $nextCheck = ($hash->{NEXTCHECK}) ? $hash->{NEXTCHECK} : "n/a";
  429. my $delta;
  430. my $str;
  431. foreach my $d (sort keys %{$hash->{helper}{device}}) {
  432. my $info = "";
  433. my $act = ($hash->{helper}{device}{$d}{actuator} eq "n/a") ? " n/a" :
  434. sprintf("%${lv}d",$hash->{helper}{device}{$d}{actuator});
  435. my $td = ($hash->{helper}{device}{$d}{tempDesired} eq "n/a") ? " n/a" :
  436. sprintf("%4.1f",$hash->{helper}{device}{$d}{tempDesired});
  437. my $tm = ($hash->{helper}{device}{$d}{tempMeasured} eq "n/a") ? " n/a" :
  438. sprintf("%4.1f",$hash->{helper}{device}{$d}{tempMeasured});
  439. $info = "idle" if($hash->{helper}{device}{$d}{demand} == 0);
  440. $info = "demand" if($hash->{helper}{device}{$d}{demand} == 1);
  441. $info = "(excluded)" if($hash->{helper}{device}{$d}{excluded} == 1);
  442. $info = "(ignored)" if($hash->{helper}{device}{$d}{ignored} == 1);
  443. if($td eq " n/a" || $tm eq " n/a") {
  444. $delta = " n/a";
  445. } else {
  446. $delta = sprintf("%+5.1f",$tm-$td);
  447. }
  448. $str .= sprintf("%-${ln}s: desired: %s°C measured: %s°C delta: %s valve: %${lv}d%% state: %s\n",
  449. $d,$td,$tm,$delta,$act,$info);
  450. }
  451. $str .= "next check: $nextCheck\n";
  452. $ret = $str;
  453. return $ret;
  454. }
  455. # housekeeping
  456. foreach my $d (sort keys %{$hash->{helper}{device}}) {
  457. delete $hash->{helper}{device}{$d} if(!exists $devs{$d});
  458. }
  459. # reset counter
  460. my $sumDemand = 0;
  461. my $sumExcluded = 0;
  462. my $sumFHT = 0;
  463. my $sumHMCCTC = 0;
  464. my $sumMAX = 0;
  465. my $sumZWave = 0;
  466. my $sumIdle = 0;
  467. my $sumIgnored = 0;
  468. my $sumTotal = 0;
  469. my $sumUnknown = 0;
  470. my $mode = AttrVal($name,"mode",$defaults{mode});
  471. readingsBeginUpdate($hash);
  472. foreach my $d (sort keys %devs) {
  473. my $devState;
  474. $hash->{helper}{device}{$d}{actuator} = $devs{$d}{actuator};
  475. $hash->{helper}{device}{$d}{excluded} = $devs{$d}{excluded};
  476. $hash->{helper}{device}{$d}{ignored} = $devs{$d}{ignored};
  477. $hash->{helper}{device}{$d}{tempDesired} = $devs{$d}{tempDesired};
  478. $hash->{helper}{device}{$d}{tempMeasured} = $devs{$d}{tempMeasured};
  479. $hash->{helper}{device}{$d}{type} = $devs{$d}{type};
  480. $sumMAX++ if(lc($devs{$d}{type}) eq "max");
  481. $sumFHT++ if(lc($devs{$d}{type}) eq "fht");
  482. $sumHMCCTC++ if(lc($devs{$d}{type}) eq "cul_hm");
  483. $sumZWave++ if(lc($devs{$d}{type}) eq "zwave");
  484. $sumTotal++;
  485. if($devs{$d}{ignored}) {
  486. $devState = "ignored";
  487. $hash->{helper}{device}{$d}{demand} = 0;
  488. readingsBulkUpdate($hash,$d,$devState);
  489. Log3 $name, 4, "$type $name $d: $devState";
  490. $sumIgnored++;
  491. next;
  492. }
  493. if($devs{$d}{excluded}) {
  494. $devState = "excluded";
  495. $hash->{helper}{device}{$d}{demand} = 0;
  496. readingsBulkUpdate($hash,$d,$devState);
  497. Log3 $name, 4, "$type $name $d: $devState";
  498. $sumExcluded++;
  499. next;
  500. }
  501. if($mode eq "thermostat" && ($devs{$d}{tempMeasured} eq "n/a" || $devs{$d}{tempDesired} eq "n/a")) {
  502. $devState = "unknown";
  503. $hash->{helper}{device}{$d}{demand} = 0;
  504. readingsBulkUpdate($hash,$d,$devState);
  505. Log3 $name, 4, "$type $name $d: $devState";
  506. $sumUnknown++;
  507. next;
  508. }
  509. my $lastState = ReadingsVal($name,$d,"idle");
  510. my $valve = $devs{$d}{actuator};
  511. my $tm = $devs{$d}{tempMeasured};
  512. my $td = $devs{$d}{tempDesired};
  513. if(!$hash->{helper}{device}{$d}{demand}) {
  514. $hash->{helper}{device}{$d}{demand} = 0;
  515. $lastState = "idle";
  516. }
  517. if($mode eq "thermostat") {
  518. my $tOn = AttrVal($name,"thermostatThresholdOn",$defaults{thermostatThresholdOn});
  519. my $tOff = AttrVal($name,"thermostatThresholdOff",$defaults{thermostatThresholdOff});
  520. if( $tm >= $td + $tOff ) {
  521. $devState = "idle";
  522. $hash->{helper}{device}{$d}{demand} = 0;
  523. $sumIdle++;
  524. } elsif( $tm <= $td - $tOn ) {
  525. $devState = "demand";
  526. $hash->{helper}{device}{$d}{demand} = 1;
  527. $sumDemand++;
  528. } else {
  529. $devState = $lastState;
  530. $sumIdle++ if($devState eq "idle");
  531. $sumDemand++ if($devState eq "demand");
  532. }
  533. } elsif($mode eq "valve") {
  534. my $vThreshold = AttrVal($name,"valveThresholdOn",$defaults{valveThresholdOn});
  535. if ( $lastState eq "demand" ) {
  536. $vThreshold = AttrVal($name,"valveThresholdOff",$defaults{valveThresholdOff});
  537. }
  538. if ( $valve > $vThreshold ) {
  539. $devState = "demand";
  540. $hash->{helper}{device}{$d}{demand} = 1;
  541. $sumDemand++;
  542. } else {
  543. $devState = "idle";
  544. $hash->{helper}{device}{$d}{demand} = 0;
  545. $sumIdle++;
  546. }
  547. }
  548. my $str = sprintf("desired: %4.1f measured: %4.1f delta: %+5.1f valve: %${lv}d%% state: %s",$td,$tm,$tm-$td,$valve,$devState);
  549. Log3 $name, 4, "$type $name $d: $str";
  550. readingsBulkUpdate($hash,$d,$devState);
  551. }
  552. readingsEndUpdate($hash,1);
  553. my $heatDemand = ($sumDemand > 0)? 1:0;
  554. # eco mode
  555. my $eco = "no";
  556. my $ecoTempOn = AttrVal($name,"ecoTemperatureOn",undef);
  557. my $ecoTempOff = AttrVal($name,"ecoTemperatureOff",undef);
  558. my $ecoState = ReadingsVal($name,"eco","off");
  559. if ( $ecoState eq "on" ) {
  560. if ( !$ecoTempOn ) {
  561. $attr{$name}{deviceCmdOn} = $defaults{deviceCmdOn};
  562. $ecoTempOn = $defaults{deviceCmdOn};
  563. Log3 $name, 1, "$type $name set attribute 'ecoTemperatureOn' to default $defaults{deviceCmdOn}."
  564. }
  565. if ( !$ecoTempOff ) {
  566. $attr{$name}{deviceCmdOff} = $defaults{deviceCmdOff};
  567. $ecoTempOff = $defaults{deviceCmdOff};
  568. Log3 $name, 1, "$type $name set attribute 'ecoTemperatureOff' to default $defaults{deviceCmdOff}."
  569. }
  570. foreach my $d (sort keys %{$hash->{helper}{device}}) {
  571. my $ignore = $hash->{helper}{device}{$d}{ignored};
  572. my $exclude = $hash->{helper}{device}{$d}{excluded};
  573. my $tempMeasured = $hash->{helper}{device}{$d}{tempMeasured};
  574. next if($tempMeasured eq "n/a");
  575. if(!$ignore && !$exclude) {
  576. $heatDemand = 2 if($tempMeasured >= $ecoTempOff && $heatDemand != 3);
  577. $heatDemand = 3 if($tempMeasured <= $ecoTempOn);
  578. $eco = "yes" if($heatDemand == 2 || $heatDemand == 3);
  579. }
  580. else {
  581. Log3 $name, 1, "Excl/Ignored $hash->{helper}{device}{$d}{name}.";
  582. }
  583. }
  584. }
  585. # overdrive mode
  586. my $overdrive = "no";
  587. my $sensor = AttrVal($name,"sensor",undef);
  588. my $sReading = AttrVal($name,"sensorReading",undef);
  589. my $sTresholdOn = AttrVal($name,"sensorThresholdOn",undef);
  590. my $sTresholdOff = AttrVal($name,"sensorThresholdOff",undef);
  591. if(!$sensor) {
  592. delete $hash->{READINGS}{sensor} if($hash->{READINGS}{sensor});
  593. } else {
  594. Log3 $name, 1, "$type $name Device $sensor not defined. Please add this device first!"
  595. if(!defined($defs{$sensor}));
  596. Log3 $name, 1, "$type $name missing attribute 'sensorReading'. Please define this attribute first."
  597. if(!$sReading);
  598. Log3 $name, 1, "$type $name missing attribute 'sensorThresholdOn'. Please define this attibute first."
  599. if(!$sTresholdOn);
  600. Log3 $name, 1, "$type $name missing attribute 'sensorThresholdOff'. Please define this attribute first."
  601. if(!$sTresholdOff);
  602. if($defs{$sensor} && $sReading && $sTresholdOn && $sTresholdOff) {
  603. my $tValue = ReadingsVal($sensor,$sReading,"n/a");
  604. if($tValue eq "n/a" || $tValue !~ m/^.*\d+.*$/) {
  605. Log3 $name, 1, "$type $name Device $sensor has no valid value.";
  606. } else {
  607. $tValue =~ s/(\s|°|[A-Z]|[a-z])+//g;
  608. $heatDemand = 4 if($tValue >= $sTresholdOff);
  609. $heatDemand = 5 if($tValue <= $sTresholdOn);
  610. $overdrive = "yes" if($heatDemand == 4 || $heatDemand == 5);
  611. readingsSingleUpdate($hash,"sensor",$tValue,1);
  612. }
  613. }
  614. }
  615. 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",
  616. $sumTotal,$sumFHT,$sumHMCCTC, $sumMAX, $sumZWave, $sumDemand,$sumIdle,$sumIgnored,$sumExcluded,$sumUnknown);
  617. Log3 $name, 3, "$type $name $str, eco: $eco overdrive: $overdrive";
  618. return $heatDemand;
  619. }
  620. 1;
  621. =pod
  622. =item helper
  623. =item summary monitor thermostats and control a central heating unit
  624. =item summary_DE &Uuml;berwache Thermostate und schalte eine zentrale Therme
  625. =begin html
  626. <a name="HCS"></a>
  627. <h3>HCS</h3>
  628. <ul>
  629. Defines a virtual device for monitoring thermostats (FHT, HM-CC-TC, MAX, Z-Wave) to control a central
  630. heating unit.<br><br>
  631. <a name="HCSdefine"></a>
  632. <b>Define</b>
  633. <ul>
  634. <code>define &lt;name&gt; HCS &lt;device&gt;</code>
  635. <br><br>
  636. <ul>
  637. <li><code>&lt;device&gt;</code> the name of a predefined device to switch.</li>
  638. </ul>
  639. <br>
  640. The HCS (heating control system) device monitors the state of all detected thermostats
  641. in a free definable interval (by default: 10 min).
  642. <br><br>
  643. Regulation for heating requirement or suppression of the request can be controlled by
  644. valve position or measured temperature (default) using also free definable thresholds.
  645. In doing so, the HCS device also includes the hysteresis between two states.
  646. <br><br>
  647. Example for monitoring measured temperature:
  648. <ul>
  649. Threshold temperature for heating requirement: 0.5 (default)<br>
  650. Threshold temperature for idle: 0.5 (default)<br>
  651. <br>
  652. Heating is required when the measured temperature of a thermostat is lower than
  653. 0.5&deg; Celsius as the desired temperature. HCS then activates the defined device
  654. until the measured temperature of the thermostat is 0.5&deg; Celsius higher as the
  655. desired temperature (threshold for idle). In this example, both tresholds are equal.
  656. </ul>
  657. <br>
  658. Example for monitoring valve position:
  659. <ul>
  660. Threshold valve position for heating requirement: 40% (default)<br>
  661. Threshold valve position for idle: 35% (default)<br>
  662. <br>
  663. Heating is required when the "open" position of a valve is more than 40%. HCS then
  664. activates the defined device until the "open" position of the valve has lowered to
  665. 35% or less (threshold for idle).
  666. </ul>
  667. <br>
  668. The HCS device supports an optional eco mode. The threshold oriented regulation by
  669. measured temperature or valve position can be overridden by setting economic thresholds.
  670. <br><br>
  671. Example:
  672. <ul>
  673. Threshold temperature economic mode on: 16&deg; Celsius<br>
  674. Threshold temperature economic mode off: 17&deg; Celsius<br>
  675. <br>
  676. HCS activates the defined device until the measured temperature of one ore more
  677. thermostats is lower or equal than 16&deg; Celsius. If a measured temperature of one
  678. or more thermostats is higher or equal than 17&deg; Celsius, HCS switch of the defined
  679. device (if none of the measured temperatures of all thermostats is lower or equal as
  680. 16&deg; Celsius).
  681. </ul>
  682. <br>
  683. In addition, the HCS device supports an optional temp-sensor. The threshold and economic
  684. oriented regulation can be overriden by the reading of the temp-sensor (overdrive mode).
  685. <br><br>
  686. Example:
  687. <ul>
  688. Threshold temperature reading for heating requirement: 10&deg; Celsius<br>
  689. Threshold temperature reading for idle: 18&deg; Celsius<br>
  690. <br>
  691. Is a measured temperature ore valve position reaching or exceeding the threshold for
  692. heating requirement, but the temperature reading is more than 18&deg; Celcius, the
  693. selected device will stay deactivated. The measured temperature or valve-position
  694. oriented regulation has been overridden by the temperature reading in this example.
  695. </ul>
  696. <br>
  697. The HCS device automatically detects devices which are ignored. Furthermore, certain
  698. devices can also be excluded of the monitoring manually.
  699. <br><br>
  700. To reduce the transmission load, use the attribute event-on-change-reading, e.g.
  701. <code>attr &lt;name&gt; event-on-change-reading state,devicestate,eco,overdrive</code>
  702. <br><br>
  703. To avoid frequent switching "on" and "off" of the device, a timeout (in minutes) can be set
  704. using the attribute <code>idleperiod</code>.
  705. <br><br>
  706. <a name="HCSget"></a>
  707. <b>Get </b>
  708. <ul>
  709. <li><code>values</code><br>
  710. returns the actual values of each device
  711. </li>
  712. </ul>
  713. <br>
  714. <a name="HCSset"></a>
  715. <b>Set</b>
  716. <ul>
  717. <li><code>eco &lt;on&gt;|&lt;off&gt;</code><br>
  718. enable (<code>on</code>) or disable (<code>off</code>) the economic mode.
  719. </li>
  720. <li><code>interval &lt;value&gt;</code><br>
  721. <code>value</code> modifies the interval of reading the actual valve positions.
  722. The unit is minutes.
  723. </li>
  724. <li><code>mode &lt;thermostat&gt;|&lt;valve&gt;</code><br>
  725. changes the operational mode:<br>
  726. <code>thermostat</code> controls the heating demand by defined temperature
  727. thresholds.<br>
  728. <code>valve</code> controls the heating demand by defined valve position thresholds.
  729. </li>
  730. <li><code>on</code><br>
  731. restarts the monitoring after shutdown by <code>off</code> switch.<br>
  732. HCS device starts up automatically upon FHEM start or after new device implementation!
  733. </li>
  734. <li><code>off</code><br>
  735. shutdown of monitoring, can be restarted by using the <code>on</code> command.
  736. </li>
  737. </ul>
  738. <br>
  739. <a name="HCSattr"></a>
  740. <b>Attributes</b>
  741. <ul>
  742. <li><code>deviceCmdOn</code> (mandatory)<br>
  743. command to activate the device, e.g. <code>on</code>.
  744. Default value: <code>on</code>
  745. </li>
  746. <li><code>deviceCmdOff</code> (mandatory)<br>
  747. command to deactivate the device, e.g. <code>off</code>.
  748. Default value: <code>off</code>
  749. </li>
  750. <li><code>ecoTemperatureOn</code> (Required by <code>eco</code> mode)<br>
  751. defines threshold for measured temperature upon which device is allways switched on
  752. </li>
  753. <li><code>ecoTemperatureOff</code> (Required by <code>eco</code> mode)<br>
  754. defines threshold for measured temperature upon which device is switched off
  755. </li>
  756. <li><code>exclude</code> (optional)<br>
  757. space or comma separated list of devices (FHT or HM-CC-TC) for excluding from
  758. monitoring
  759. </li>
  760. <li><code>idleperiod</code> (mandatory)<br>
  761. locks the device to be switched for the specified period. The unit is minutes.
  762. Default value: <code>10</code>
  763. </li>
  764. <li><code>mode</code> (mandatory)<br>
  765. defines the operational mode:<br>
  766. <code>thermostat</code> controls the heating demand by defined temperature
  767. thresholds.<br>
  768. <code>valve</code> controls the heating demand by defined valve position thresholds.<br>
  769. Default value: <code>thermostat</code>
  770. </li>
  771. <li><code>sensor</code> (optional)<br>
  772. device name of the temp-sensor
  773. </li>
  774. <li><code>sensorThresholdOn</code> (Required by <code>sensor</code>)<br>
  775. threshold for temperature reading activating the defined device
  776. Must be set if <code>sensor</code> has been defined
  777. </li>
  778. <li><code>sensorThresholdOff</code> (Required by <code>sensor</code>)<br>
  779. threshold for temperature reading deactivating the defined device.
  780. Must be set if <code>sensor</code> has been defined
  781. </li>
  782. <li><code>sensorReading</code> (Required by <code>sensor</code>)<br>
  783. name which is used for saving the "reading" of the defined temp-sensor.
  784. </li>
  785. <li><code>thermostatThresholdOn</code> (Required by operational mode <code>thermostat</code>)<br>
  786. defines delta threshold between desired and measured temperature upon which device
  787. is switched on (heating required).<br>
  788. Default value: <code>0.5</code>
  789. </li>
  790. <li><code>thermostatThresholdOff</code> (Required by operational mode <code>thermostat</code>)<br>
  791. defines delta threshold between desired and measured temperature upon which
  792. device is switched off (idle).<br>
  793. Default value: <code>0.5</code>
  794. </li>
  795. <li><code>valveThresholdOn</code> (Required by operational mode <code>valve</code>)<br>
  796. defines threshold of valve-position upon which device is switched on (heating
  797. required).<br>
  798. Default value: <code>40</code>
  799. </li>
  800. <li><code>valveThresholdOff</code> (Required by operational mode <code>valve</code>)<br>
  801. defines threshold of valve-position upon which device is switched off (idle).<br>
  802. Default value: <code>35</code>
  803. </li>
  804. <li><a href="#disable"><code>disable</code></a></li>
  805. <li><a href="#do_not_notify"><code>do_not_notify</code></a></li>
  806. <li><a href="#event-on-change-reading"><code>event-on-change-reading</code></a><br>
  807. default value: <code>state,devicestate,eco,overdrive</code>
  808. </li>
  809. <li><a href="#event-on-update-reading"><code>event-on-update-reading</code></a></li>
  810. <li><a href="#verbose"><code>verbose</code></a><br>
  811. verbose 4 (or lower) shows a complete statistic of scanned devices (FHT or HM-CC-TC).<br>
  812. verbose 3 shows a short summary of scanned devices.<br>
  813. verbose 2 suppressed the above messages.
  814. </li>
  815. </ul>
  816. <br>
  817. </ul>
  818. <br>
  819. </ul>
  820. =end html
  821. =cut