94_PWM.pm 40 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193
  1. #
  2. #
  3. # 94_PWM.pm
  4. # written by Andreas Goebel 2012-07-25
  5. # e-mail: ag at goebel-it dot de
  6. #
  7. # 21.09.15 GA update, use Log3
  8. # 07.10.15 GA initial version published
  9. # 13.10.15 GA add event-on-change-reading
  10. # 13.10.15 GA add several readings
  11. # 15.10.15 GA add reading for avg pulses
  12. # 19.10.15 GA add overall heating switch
  13. # 22.10.15 GA add new definition for overall heating switch. Decision now based on threshold for pulseMax
  14. # 30.11.15 GA add new definition for overall heating switch. based on pulseMax or roomsOn
  15. # 30.11.15 GA add new followUpTime can now delay switching of OverallHeatingSwitch from "on" to "off"
  16. # 26.01.16 GA fix don't call AssignIoPort
  17. # 26.01.16 GA fix IODev from PWMR object is now a reference to PWM object
  18. # 29.06.16 GA add attribute valveProtectIdlePeriod
  19. # 16.08.16 GA add event-min-interval
  20. # 23.09.16 GA fix set default for maxPulse to 1 (from 0.85)
  21. # 28.09.16 GA add "get timers" to collect a maximum of all timers from the rooms attached
  22. # 11.10.16 GA add new delayTimeOn can now suspend switching of OverallHeadtingSwitch from "off" to "on"
  23. # 16.11.16 GA add new attribute overallHeatingSwitchRef for threshold based configuration
  24. # 17.11.16 GA add internals for configuration parameters: p_interval, p_cycletime, p_minOnOffTime,
  25. # p_maxPulse, p_roomsMinOnOffThreshold and p_overallHeatingSwitch
  26. ##############################################
  27. # $Id: 94_PWM.pm 12594 2016-11-17 11:21:13Z jamesgo $
  28. # module for PWM (Pulse Width Modulation) calculation
  29. # this module uses PWMR (R like room) to
  30. # - get information (ReadRoom)
  31. # - set actors (SetRoom)
  32. #
  33. # standard heating devices support 0 to 100% heating they can be driven by the PID module
  34. # heating devices only supporing "on" of "off" can be driven by PWM
  35. # in PWM 50% is realised by defining a timeframe (cycletime)
  36. # and switch the defive "on" for 50% of this time
  37. # basis for calculation of this pulse is a factor multiplied with the difference
  38. # between desired-temp and act-temp
  39. #
  40. # default for cycletime is 15 minutes (900 sec)
  41. # since the devices act very slow
  42. # there is a parameter minonofftime to prevent "senseless" switches
  43. # PWM recalculates the needed pulse every 60 seconds and
  44. # then decides if the devices will be switched
  45. # "on->off", "off->on" or stays in the current state
  46. #
  47. package main;
  48. use strict;
  49. use warnings;
  50. sub PWM_Get($@);
  51. sub PWM_Set($@);
  52. sub PWM_Define($$);
  53. sub PWM_Calculate($);
  54. sub PWM_Undef($$);
  55. sub PWM_CalcRoom(@);
  56. my %roomsWaitOffset = ();
  57. ###################################
  58. sub
  59. PWM_Initialize($)
  60. {
  61. my ($hash) = @_;
  62. $hash->{GetFn} = "PWM_Get";
  63. $hash->{SetFn} = "PWM_Set";
  64. $hash->{DefFn} = "PWM_Define";
  65. $hash->{UndefFn} = "PWM_Undef";
  66. $hash->{AttrFn} = "PWM_Attr";
  67. $hash->{AttrList} = "event-on-change-reading event-min-interval valveProtectIdlePeriod overallHeatingSwitchRef:pulseMax,pulseSum,pulseAvg,pulseAvg2,pulseAvg3,avgPulseRoomsOn";
  68. #$hash->{GetList} = "status timers";
  69. }
  70. ###################################
  71. sub
  72. PWM_Calculate($)
  73. {
  74. my ($hash) = @_;
  75. my $name = $hash->{NAME};
  76. my %RoomsToSwitchOn = ();
  77. my %RoomsToSwitchOff = ();
  78. my %RoomsToStayOn = ();
  79. my %RoomsToStayOff = ();
  80. my %RoomsValveProtect = ();
  81. my %RoomsPulses = ();
  82. my $roomsActive = 0;
  83. my $newpulseMax = 0;
  84. my $newpulseSum = 0;
  85. my $newpulseAvg = 0;
  86. my $newpulseAvg2 = 0;
  87. my $newpulseAvg3 = 0;
  88. my $wkey = "";
  89. if($hash->{INTERVAL} > 0) {
  90. InternalTimer(gettimeofday() + $hash->{INTERVAL}, "PWM_Calculate", $hash, 0);
  91. }
  92. Log3 ($hash, 3, "PWM_Calculate $name");
  93. readingsBeginUpdate ($hash);
  94. #$hash->{STATE} = "lastrun: ".TimeNow();
  95. #$hash->{STATE} = "calculating";
  96. readingsBulkUpdate ($hash, "lastrun", "calculating");
  97. $hash->{STATE} = "lastrun: ".$hash->{READINGS}{lastrun}{TIME};
  98. # loop over all devices
  99. # fetch all PWMR devices
  100. # which are not disabled
  101. # and are linked to me (via IODev)
  102. foreach my $d (sort keys %defs) {
  103. if ( (defined ($defs{$d}{TYPE})) && $defs{$d}{TYPE} eq "PWMR" ) { # all PWMR objects
  104. if (!defined ($attr{$d}{disable}) or $attr{$d}{disable} == 0) { # not disabled
  105. #if ($hash->{NAME} eq $defs{$d}{IODev}) { # referencing to this fb
  106. #if ($hash->{NAME} eq $defs{$d}{IODev}{NAME}) { # referencing to this fb
  107. if ($hash->{NAME} eq $defs{$d}{IODev}->{NAME}) { # referencing to this fb
  108. Log3 ($hash, 4, "PWM_Calculate calc $name, room $d");
  109. ########################
  110. # calculate room
  111. # $newstate is "" if state is unchanged
  112. # $newstate is "on" or "off" if state changes
  113. # $newstate may be "on_vp" or "off_vp" if valve protection is active
  114. my ($newstate, $newpulse, $cycletime, $oldstate) = PWM_CalcRoom($hash, $defs{$d});
  115. $defs{$d}->{READINGS}{oldpulse}{TIME} = TimeNow();
  116. $defs{$d}->{READINGS}{oldpulse}{VAL} = $newpulse;
  117. my $onoff = $newpulse * $cycletime;
  118. if ($newstate =~ "off.*") {
  119. $onoff = (1 - $newpulse) * $cycletime
  120. }
  121. if ($newstate eq "on_vp") {
  122. $RoomsValveProtect{$d} = "on";
  123. } elsif ($newstate eq "off_vp") {
  124. $RoomsValveProtect{$d} = "off";
  125. }
  126. $wkey = $name."_".$d;
  127. if (defined ($roomsWaitOffset{$wkey})) {
  128. $newpulse += $roomsWaitOffset{$wkey};
  129. } else {
  130. $roomsWaitOffset{$wkey} = 0;
  131. }
  132. $roomsActive++;
  133. $RoomsPulses{$d} = $newpulse;
  134. $newpulseSum += $newpulse;
  135. $newpulseMax = max($newpulseMax, $newpulse);
  136. # $newstate ne "" -> state changed "on" -> "off" or "off" -> "on"
  137. if ((int($hash->{MINONOFFTIME}) > 0) &&
  138. (($newstate eq "on") or ($newstate eq "off")) &&
  139. ($onoff < int($hash->{MINONOFFTIME}))
  140. ) {
  141. #######################
  142. # actor devices take 3 minutes for an open/close cycle
  143. # this is handled by MINONOFFTIME
  144. Log3 ($hash, 3, "PWM_Calculate $d: F0 stay unchanged $oldstate: ".
  145. "($onoff < $hash->{MINONOFFTIME} sec)");
  146. if ($oldstate eq "off") {
  147. $RoomsToStayOff{$d} = $newpulse;
  148. } else {
  149. $RoomsToStayOn{$d} = $newpulse;
  150. }
  151. } else {
  152. # state changed and it is worth to move the device
  153. if ($newstate eq "on") {
  154. $RoomsToSwitchOn{$d} = $newpulse;
  155. } elsif ($newstate eq "off") {
  156. $RoomsToSwitchOff{$d} = $newpulse;
  157. } elsif ($newstate eq "") {
  158. if ($oldstate eq "on") {
  159. $RoomsToStayOn{$d} = $newpulse;
  160. } else {
  161. $RoomsToStayOff{$d} = $newpulse;
  162. }
  163. }
  164. }
  165. }
  166. }
  167. }
  168. }
  169. # synchronize the heating on the "off" edge of the pulse
  170. # try to minimize the situation where all rooms are "on" at the same time
  171. #
  172. # algorithm:
  173. # -> if more than 2 rooms are switched off at the same time,
  174. # -> simply keep some on (but this will last only for one calculation cycle)
  175. #
  176. # assumption: 100% "on" time is not allowed (max newpulse = 85%)
  177. # -> in the morning all rooms will be switched on at the same time
  178. # -> and then off at the same time
  179. # normally we switch off only one room at the same time
  180. # normally we switch on only one room at the same time
  181. my $switchOn = $hash->{MaxSwitchOnPerCycle}; # default 1
  182. my $switchOff = $hash->{MaxSwitchOffPerCycle}; # default 1
  183. # rooms may stay on due to logic below ...
  184. #
  185. # switch off only (one) the room with lowest need for heating
  186. # sort rooms with ascending "newpulse"
  187. foreach my $room (sort { $RoomsToSwitchOff{$a} <=> $RoomsToSwitchOff{$b} } keys %RoomsToSwitchOff) {
  188. # only the first room in the list will be switched off
  189. # all others will stay on
  190. # first room has the lowest need for heating ... it will be switched off
  191. $switchOff--;
  192. if ($switchOff >= 0) {
  193. Log3 ($hash, 3, "PWM_Calculate $room: F99 switch off ".
  194. "(pulse=$RoomsToSwitchOff{$room})");
  195. next;
  196. }
  197. Log3 ($hash, 3, "PWM_Calculate $room: F99 keep room on ".
  198. "(pulse=$RoomsToSwitchOff{$room})");
  199. $RoomsToStayOn{$room} = 1;
  200. if (defined($RoomsToSwitchOff{$room})) {
  201. delete ($RoomsToSwitchOff{$room});
  202. }
  203. }
  204. # try to minimize the situation where all rooms are "on" at the same time
  205. # switch "on" only one room at the same time
  206. # sort rooms with decending "newpulse"
  207. foreach my $room (sort { $RoomsToSwitchOn{$b} <=> $RoomsToSwitchOn{$a} } keys %RoomsToSwitchOn) {
  208. # only the first room in the list will be switched on
  209. # all others will stay off
  210. # first room has the highest need for heating ... it will be switched on
  211. $switchOn--;
  212. if ($switchOn >= 0) {
  213. Log3 ($hash, 3, "PWM_Calculate $room: F98 switch on ".
  214. "(pulse=$RoomsToSwitchOn{$room})");
  215. next;
  216. }
  217. Log3 ($hash, 3, "PWM_Calculate $room: F98 keep room off ".
  218. "(pulse=$RoomsToSwitchOn{$room})");
  219. my $wkey = $name."_".$room;
  220. $roomsWaitOffset{$wkey} += 0.0001;
  221. $RoomsToStayOff{$room} = 1;
  222. if (defined($RoomsToSwitchOn{$room})) {
  223. delete ($RoomsToSwitchOn{$room});
  224. }
  225. }
  226. # in addition to the above max. of 85% of the active rooms may be on at the same time
  227. # 11 * 0.8 = 8.8 ... 8 is ok ... 9, 10, 11 is not (laraEG!)
  228. my $roomsOn = (scalar keys %RoomsToStayOn) - (scalar keys %RoomsToSwitchOff);
  229. # treat less than 8 active rooms as 8 (more can get active)
  230. # 16.01.2015
  231. #my $maxRoomsOn = $roomsActive * 0.7;
  232. # 23.09.2015
  233. #my $maxRoomsOn = $roomsActive * 0.6; # 11 rooms -> max 6 active
  234. #$maxRoomsOn = (8 * 0.7) if ($roomsActive < 8);
  235. my $maxRoomsOn = $roomsActive - $hash->{NoRoomsToStayOff};
  236. #
  237. # looks complicated but this will work if more than one room would be switched on
  238. #
  239. # prevent rooms to be switched on if maxRoomsOn is reached
  240. #
  241. while (
  242. (($roomsOn + (scalar keys %RoomsToSwitchOn)) > $maxRoomsOn) &&
  243. ((scalar keys %RoomsToSwitchOn) > 0)
  244. ) {
  245. # sort rooms with ascending "newpulse"
  246. foreach my $room (sort { $RoomsToSwitchOn{$a} <=> $RoomsToSwitchOn{$b} } keys %RoomsToSwitchOn) {
  247. Log3 ($hash, 3, "PWM_Calculate $room: F97 keep room off ".
  248. "(pulse=$RoomsToSwitchOn{$room}) (max=$maxRoomsOn)");
  249. my $wkey = $name."_".$room;
  250. $roomsWaitOffset{$wkey} += 0.001;
  251. $RoomsToStayOff{$room} = 1;
  252. if (defined($RoomsToSwitchOn{$room})) {
  253. delete ($RoomsToSwitchOn{$room});
  254. }
  255. last; # continue in while loop
  256. }
  257. }
  258. # in addition to the above try to prevent that too many rooms are off
  259. # use $roomsActive and $newpulseSum to differentiate if heating is required
  260. # 11 * 0.27 = 2.97 ... 3 rooms is ok ... 0,1 or 2 is not
  261. # 23.09.2015
  262. #my $minRoomsOn = $roomsActive * 0.29;
  263. # if overall required heating is below 0.42 ... possibly drive Vaillant into "Sperrzeit"
  264. # 15.01.2015: adjust this from 0.42 to 0.25 (=25% Pulse needed)
  265. # 23.09.2015
  266. #if ($roomsActive == 0 or $newpulseSum/$roomsActive < 0.42) {
  267. # $minRoomsOn = 0;
  268. #}
  269. my $minRoomsOn = $hash->{NoRoomsToStayOn};
  270. my $minRoomsOnList = "";
  271. if ($minRoomsOn > 0) {
  272. my $roomsCounted = 0;
  273. my $pulseSum = 0;
  274. foreach my $room (sort { $RoomsPulses{$b} <=> $RoomsPulses{$a} } keys %RoomsPulses) {
  275. last if ($roomsCounted == $minRoomsOn);
  276. Log3 ($hash, 3, "PWM_Calculate: loop $roomsCounted $room $RoomsPulses{$room}");
  277. $minRoomsOnList .= "$room,";
  278. $pulseSum += $RoomsPulses{$room};
  279. $roomsCounted++;
  280. }
  281. $minRoomsOnList =~ s/,$//;
  282. if ($roomsActive == 0 or $hash->{NoRoomsToStayOnThreshold} == 0 or $pulseSum/$roomsCounted < $hash->{NoRoomsToStayOnThreshold}) {
  283. $minRoomsOn = 0;
  284. $minRoomsOnList = "";
  285. }
  286. #Log3 ($hash, 3, "PWM_Calculate: newpulseSum $newpulseSum avg ".$newpulseSum/$roomsActive." minRoomsOn(".$minRoomsOn.")") if ($roomsActive > 0);
  287. Log3 ($hash, 3, "PWM_Calculate: pulseSum $pulseSum avg ".$pulseSum/$roomsCounted." minRoomsOn(".$minRoomsOn.")") if ($roomsActive > 0);
  288. }
  289. #
  290. # looks complicated but this will work if more than one room would stay on
  291. #
  292. while (
  293. (((scalar keys %RoomsToStayOn) + (scalar keys %RoomsToSwitchOn)) < $minRoomsOn) &&
  294. ((scalar keys %RoomsToSwitchOff) > 0)
  295. ) {
  296. # sort rooms with decending "newpulse"
  297. foreach my $room (sort { $RoomsToSwitchOff{$b} <=> $RoomsToSwitchOff{$a} } keys %RoomsToSwitchOff) {
  298. my $ron = 1 + (scalar keys %RoomsToStayOn) + (scalar keys %RoomsToSwitchOn);
  299. Log3 ($hash, 3, "PWM_Calculate $room: F96 keep room on ".
  300. "(pulse=$RoomsToSwitchOff{$room}) (min=$minRoomsOn) (roomsOn=$ron)");
  301. my $wkey = $name."_".$room;
  302. $roomsWaitOffset{$wkey} -= 0.001;
  303. $RoomsToStayOn{$room} = 1;
  304. if (defined($RoomsToSwitchOff{$room})) {
  305. delete ($RoomsToSwitchOff{$room});
  306. }
  307. last; # continue in while loop
  308. }
  309. }
  310. #
  311. # now process the calculated actions
  312. #
  313. my $cntRoomsOn = 0;
  314. my $cntRoomsOff = 0;
  315. my $pulseRoomsOn = 0;
  316. my $pulseRoomsOff = 0;
  317. foreach my $roomStay (sort keys %RoomsToStayOff) {
  318. PWMR_SetRoom ($defs{$roomStay}, "");
  319. $cntRoomsOff++;
  320. $pulseRoomsOff += $RoomsPulses{$roomStay};
  321. }
  322. foreach my $roomStay (sort keys %RoomsToStayOn) {
  323. PWMR_SetRoom ($defs{$roomStay}, "");
  324. $cntRoomsOn++;
  325. $pulseRoomsOn += $RoomsPulses{$roomStay};
  326. }
  327. foreach my $roomOff (sort keys %RoomsToSwitchOff) {
  328. PWMR_SetRoom ($defs{$roomOff}, "off");
  329. $cntRoomsOff++;
  330. $pulseRoomsOff += $RoomsPulses{$roomOff};
  331. }
  332. foreach my $roomOn (sort keys %RoomsToSwitchOn) {
  333. my $wkey = $name."-".$roomOn;
  334. $roomsWaitOffset{$wkey} = 0;
  335. PWMR_SetRoom ($defs{$roomOn}, "on");
  336. $cntRoomsOn++;
  337. $pulseRoomsOn += $RoomsPulses{$roomOn};
  338. }
  339. foreach my $roomVP (sort keys %RoomsValveProtect) {
  340. my $wkey = $name."-".$roomVP;
  341. $roomsWaitOffset{$wkey} = 0;
  342. if ( $RoomsValveProtect{$roomVP} eq "on") {
  343. PWMR_SetRoom ($defs{$roomVP}, "on");
  344. $cntRoomsOn++;
  345. $pulseRoomsOn += $RoomsPulses{$roomVP};
  346. } else {
  347. PWMR_SetRoom ($defs{$roomVP}, "off");
  348. $cntRoomsOff++;
  349. $pulseRoomsOff += $RoomsPulses{$roomVP};
  350. }
  351. }
  352. my $cntAvg = 0;
  353. # sort rooms with decending "newpulse"
  354. foreach my $room (sort { $RoomsPulses{$b} <=> $RoomsPulses{$a} } keys %RoomsPulses) {
  355. $newpulseAvg += $RoomsPulses{$room};
  356. $newpulseAvg2 += $RoomsPulses{$room} if ($cntAvg < 2);
  357. $newpulseAvg3 += $RoomsPulses{$room} if ($cntAvg < 3);
  358. $cntAvg++;
  359. }
  360. $newpulseAvg = sprintf ("%.02f", $newpulseAvg / $cntAvg) if ($cntAvg > 0);
  361. $newpulseAvg2 = sprintf ("%.02f", $newpulseAvg2 / minNum (2, $cntAvg)) if ($cntAvg > 0);
  362. $newpulseAvg3 = sprintf ("%.02f", $newpulseAvg3 / minNum (3, $cntAvg)) if ($cntAvg > 0);
  363. readingsBulkUpdate ($hash, "roomsActive", $roomsActive);
  364. readingsBulkUpdate ($hash, "roomsOn", $cntRoomsOn);
  365. readingsBulkUpdate ($hash, "roomsOff", $cntRoomsOff);
  366. readingsBulkUpdate ($hash, "avgPulseRoomsOn", ($cntRoomsOn > 0 ? sprintf ("%.2f", $pulseRoomsOn / $cntRoomsOn) : 0));
  367. readingsBulkUpdate ($hash, "avgPulseRoomsOff", ($cntRoomsOff > 0 ? sprintf ("%.2f", $pulseRoomsOff /$cntRoomsOff) : 0));
  368. readingsBulkUpdate ($hash, "pulseMax", $newpulseMax);
  369. readingsBulkUpdate ($hash, "pulseSum", $newpulseSum);
  370. readingsBulkUpdate ($hash, "pulseAvg", $newpulseAvg);
  371. readingsBulkUpdate ($hash, "pulseAvg2", $newpulseAvg2);
  372. readingsBulkUpdate ($hash, "pulseAvg3", $newpulseAvg3);
  373. if ( $hash->{NoRoomsToStayOn} > 0) {
  374. readingsBulkUpdate ($hash, "roomsToStayOn", $minRoomsOn);
  375. readingsBulkUpdate ($hash, "roomsToStayOnList", $minRoomsOnList);
  376. }
  377. if ( defined ($hash->{OverallHeatingSwitch}) ) {
  378. if ( $hash->{OverallHeatingSwitch} ne "") {
  379. my $newstateOHS = "on";
  380. if ( $hash->{OverallHeatingSwitch_threshold} > 0) {
  381. # threshold based
  382. my $refValue = $newpulseMax;
  383. if (defined($attr{$name}{overallHeatingSwitchRef})) {
  384. my $ref = $attr{$name}{overallHeatingSwitchRef};
  385. $refValue = $newpulseMax if ($ref eq "pulseMax");
  386. $refValue = $newpulseSum if ($ref eq "pulseSum");
  387. $refValue = $newpulseAvg if ($ref eq "pulseAvg");
  388. $refValue = $newpulseAvg2 if ($ref eq "pulseAvg2");
  389. $refValue = $newpulseAvg3 if ($ref eq "pulseAvg3");
  390. }
  391. $newstateOHS = ($refValue > $hash->{OverallHeatingSwitch_threshold}) ? "on" : "off";
  392. } else {
  393. # room based
  394. $newstateOHS = ($cntRoomsOn > 0) ? "on" : "off";
  395. }
  396. my $actor = $hash->{OverallHeatingSwitch};
  397. my $actstateOHS = ($defs{$actor}{STATE} =~ $hash->{OverallHeatingSwitch_regexp_on}) ? "on" : "off";
  398. if ($hash->{OverallHeatingSwitch_followUpTime} > 0) {
  399. if ($actstateOHS eq "on" and $newstateOHS eq "off") {
  400. if ($hash->{READINGS}{OverallHeatingSwitchWaitUntilOff}{VAL} eq "") {
  401. $newstateOHS = "on";
  402. Log3 ($name, 2, "PWM_Calculate: $name: OverallHeatingSwitch wait for followUpTime before switching off (init timestamp)");
  403. readingsBulkUpdate ($hash, "OverallHeatingSwitchWaitUntilOff", FmtDateTime(time() + $hash->{OverallHeatingSwitch_followUpTime}));
  404. } elsif ($hash->{READINGS}{OverallHeatingSwitchWaitUntilOff}{VAL} ge TimeNow()) {
  405. $newstateOHS = "on";
  406. Log3 ($name, 2, "PWM_Calculate: $name: OverallHeatingSwitch wait for followUpTime before switching off");
  407. } else {
  408. readingsBulkUpdate ($hash, "OverallHeatingSwitchWaitUntilOff", "");
  409. }
  410. } else {
  411. readingsBulkUpdate ($hash, "OverallHeatingSwitchWaitUntilOff", "");
  412. }
  413. }
  414. if ($hash->{OverallHeatingSwitch_delayTimeOn} > 0) {
  415. if ($actstateOHS eq "off" and $newstateOHS eq "on") {
  416. if ($hash->{READINGS}{OverallHeatingSwitchWaitBeforeOn}{VAL} eq "") {
  417. $newstateOHS = "off";
  418. Log3 ($name, 2, "PWM_Calculate: $name: OverallHeatingSwitch wait for delayTimeOn before switching on (init timestamp)");
  419. readingsBulkUpdate ($hash, "OverallHeatingSwitchWaitBeforeOn", FmtDateTime(time() + $hash->{OverallHeatingSwitch_delayTimeOn}));
  420. } elsif ($hash->{READINGS}{OverallHeatingSwitchWaitBeforeOn}{VAL} ge TimeNow()) {
  421. $newstateOHS = "off";
  422. Log3 ($name, 2, "PWM_Calculate: $name: OverallHeatingSwitch wait for delayTimeOn before switching on");
  423. } else {
  424. readingsBulkUpdate ($hash, "OverallHeatingSwitchWaitBeforeOn", "");
  425. }
  426. } else {
  427. readingsBulkUpdate ($hash, "OverallHeatingSwitchWaitBeforeOn", "");
  428. }
  429. }
  430. if ($newstateOHS ne $actstateOHS or $hash->{READINGS}{OverallHeatingSwitch}{VAL} ne $actstateOHS) {
  431. my $ret = fhem sprintf ("set %s %s", $hash->{OverallHeatingSwitch}, $newstateOHS);
  432. if (!defined($ret)) { # sucessfull
  433. Log3 ($name, 4, "PWMR_SetRoom: $name: set $actor $newstateOHS");
  434. readingsBulkUpdate ($hash, "OverallHeatingSwitch", $newstateOHS);
  435. # push @{$room->{CHANGED}}, "actor $newstateOHS";
  436. # DoTrigger($name, undef);
  437. } else {
  438. Log3 ($name, 4, "PWMR_SetRoom $name: set $actor $newstateOHS failed ($ret)");
  439. }
  440. }
  441. }
  442. }
  443. readingsEndUpdate($hash, 1);
  444. # if(!$hash->{LOCAL}) {
  445. # DoTrigger($name, undef) if($init_done);
  446. # }
  447. }
  448. ###################################
  449. sub
  450. PWM_CalcRoom(@)
  451. {
  452. my ($hash, $room) = @_;
  453. my $name = $hash->{NAME};
  454. Log3 ($hash, 4, "PWM_CalcRoom: $name ($room->{NAME})");
  455. my $cycletime = $hash->{CYCLETIME};
  456. my ($temperaturV, $actorV, $factor, $oldpulse, $newpulse, $prevswitchtime, $windowV) =
  457. PWMR_ReadRoom($room, $cycletime, $hash->{MaxPulse});
  458. my $nextswitchtime;
  459. if ($actorV eq "on") {
  460. $nextswitchtime = int($oldpulse * $cycletime) + $prevswitchtime;
  461. } else {
  462. $nextswitchtime = int((1-$oldpulse) * $cycletime) + $prevswitchtime;
  463. }
  464. #Log3 ($hash, 4, "PWM_CalcRoom $room->{NAME}: $cycletime ($prevswitchtime/$nextswitchtime)=".($nextswitchtime-$prevswitchtime));
  465. if ($actorV eq "on") # current state is "on"
  466. {
  467. # ----------------
  468. # check if valve protection is active, keep this state for 5 minutes
  469. if (defined ($room->{helper}{valveProtectLastSwitch})) {
  470. if ( $room->{helper}{valveProtectLastSwitch} + 300 > time()) {
  471. Log3 ($hash, 3, "PWM_CalcRoom $room->{NAME}: F13 valveProtect continue");
  472. return ("", $newpulse, $cycletime, $actorV);
  473. } else {
  474. Log3 ($hash, 3, "PWM_CalcRoom $room->{NAME}: F14 valveProtect off");
  475. delete ($room->{helper}{valveProtectLastSwitch});
  476. return ("off_vp", $newpulse, $cycletime, $actorV);
  477. }
  478. }
  479. # ----------------
  480. # decide if to change to "off"
  481. if ($newpulse == 1) {
  482. Log3 ($hash, 3, "PWM_CalcRoom $room->{NAME}: F10 stay on");
  483. return ("", $newpulse, $cycletime, $actorV);
  484. }
  485. if ($newpulse < $oldpulse) { # on: was 80% now it is 30%
  486. if ( time() >= $nextswitchtime ) # F3
  487. {
  488. Log3 ($hash, 3, "PWM_CalcRoom $room->{NAME}: F3 new off");
  489. return ("off", $newpulse, $cycletime, $actorV);
  490. # state changed and it is worth to move the device
  491. }
  492. else #( time() < $nextswitchtime ) # F1
  493. {
  494. Log3 ($hash, 3, "PWM_CalcRoom $room->{NAME}: F1 stay on");
  495. return ("", $newpulse, $cycletime, $actorV);
  496. }
  497. } else { #($newpulse >= $oldpulse) # unchanged, or was 30% now 40%
  498. # maybe we switch off
  499. # - because several cycles were not calculated
  500. # - or on time is simply over
  501. # - newpulse 0 is also handled here
  502. if ( time() >= $nextswitchtime) { # F4
  503. Log3 ($hash, 3, "PWM_CalcRoom $room->{NAME}: F4 new off");
  504. return ("off", $newpulse, $cycletime, $actorV);
  505. } else {
  506. Log3 ($hash, 3, "PWM_CalcRoom $room->{NAME}: F9 stay on");
  507. return ("", $newpulse, $cycletime, $actorV);
  508. }
  509. }
  510. }
  511. elsif ($actorV eq "off") # current state is "off"
  512. {
  513. # ----------------
  514. # check if valve protection is activated (attribute valveProtectIdlePeriod is set)
  515. if (defined ($attr{$name}{"valveProtectIdlePeriod"})) {
  516. # period is defined in days (*86400)
  517. if ($room->{READINGS}{lastswitch}{VAL} + ($attr{$name}{"valveProtectIdlePeriod"} * 86400) < time()) {
  518. $room->{helper}{valveProtectLastSwitch} = time();
  519. Log3 ($hash, 3, "PWM_CalcRoom $room->{NAME}: F12 valve protect");
  520. return ("on_vp", $newpulse, $cycletime, $actorV);
  521. }
  522. }
  523. # ----------------
  524. # decide if to change to "on"
  525. if ($oldpulse == 0 && $newpulse > 0) { # was 0% now heating is required
  526. Log3 ($hash, 3, "PWM_CalcRoom $room->{NAME}: F7 new on");
  527. return ("on", $newpulse, $cycletime, $actorV);
  528. }
  529. if ($newpulse == 0) {
  530. Log3 ($hash, 3, "PWM_CalcRoom $room->{NAME}: F11 stay off (0)");
  531. return ("", $newpulse, $cycletime, $actorV);
  532. }
  533. if ($newpulse > $oldpulse) { # was 30% now it is 80%
  534. # F5
  535. if ( time() < $nextswitchtime )
  536. {
  537. Log3 ($hash, 3, "PWM_CalcRoom $room->{NAME}: F5 stay off");
  538. return ("", $newpulse, $cycletime, $actorV);
  539. }
  540. else # time >= $nextswitchtime
  541. {
  542. # F6
  543. Log3 ($hash, 3, "PWM_CalcRoom $room->{NAME}: F6 new on");
  544. return ("on", $newpulse, $cycletime, $actorV);
  545. }
  546. } else { # unchanged, was 80% now 30%
  547. # F2
  548. if ( time() >= $nextswitchtime ) {
  549. Log3 ($hash, 3, "PWM_CalcRoom $room->{NAME}: F2 new on");
  550. return ("on", $newpulse, $cycletime, $actorV);
  551. } else {
  552. Log3 ($hash, 3, "PWM_CalcRoom $room->{NAME}: F8 stay off");
  553. return ("", $newpulse, $cycletime, $actorV);
  554. }
  555. }
  556. }
  557. else # $actorV not "on" of "off"
  558. {
  559. Log3 ($hash, 3, "PWM_CalcRoom -> $name -> $room->{NAME}: invalid actor state ($actorV) try to switch off");
  560. return ("off", 0, $cycletime, $actorV);
  561. }
  562. return ("", $newpulse, $cycletime, $actorV);
  563. }
  564. ###################################
  565. sub
  566. PWM_Get($@)
  567. {
  568. my ($hash, @a) = @_;
  569. my $name = $hash->{NAME};
  570. return "argument is missing" if(int(@a) != 2);
  571. my $msg;
  572. if ($a[1] eq "status") {
  573. return $hash->{STATE};
  574. } elsif ($a[1] eq "timers") {
  575. Log3 ($hash, 1, "in get timers");
  576. my $cnt = 0;
  577. my %tmpTimersFrom = ();
  578. my %tmpTimersTo = ();
  579. foreach my $d (sort keys %defs) {
  580. if ( (defined ($defs{$d}{TYPE})) && $defs{$d}{TYPE} eq "PWMR" ) { # all PWMR objects
  581. if (!defined ($attr{$d}{disable}) or $attr{$d}{disable} == 0) { # not disabled
  582. if ($name eq $defs{$d}{IODev}->{NAME}) { # referencing to this fb
  583. my $room = $defs{$d};
  584. Log3 ($hash, 1, "PWM_Get $name collect $room->{NAME}");
  585. foreach my $reading ("timer1_Mo", "timer2_Di", "timer3_Mi", "timer4_Do", "timer5_Fr", "timer6_Sa", "timer7_So") {
  586. if (defined ($room->{READINGS}{$reading}) and $room->{READINGS}{$reading} ne "") {
  587. $cnt++;
  588. Log3 ($hash, 1, "PWM_Get $name collect $room->{NAME} $reading");
  589. my (@timers) = split / /, $room->{READINGS}{$reading}{VAL};
  590. my ($mintime, $minTempId, $minTemp ) = split /,/, $timers[0];
  591. my ($maxtime, $maxTempId, $maxTemp ) = split /,/, $timers[$#timers];
  592. my ($minfrom, $minto) = split /-/, $mintime;
  593. my ($maxfrom, $maxto) = split /-/, $maxtime;
  594. $tmpTimersFrom{$reading} = $minfrom unless defined($tmpTimersFrom{$reading});
  595. $tmpTimersTo{$reading} = $maxto unless defined($tmpTimersTo{$reading});
  596. $tmpTimersFrom{$reading} = $minfrom if ($tmpTimersFrom{$reading} > $minfrom);
  597. $tmpTimersTo{$reading} = $maxto if ($tmpTimersTo{$reading} < $maxto);
  598. }
  599. }
  600. }
  601. }
  602. }
  603. }
  604. if ($cnt == 0) {
  605. foreach my $reading ("timer1_Mo", "timer2_Di", "timer3_Mi", "timer4_Do", "timer5_Fr", "timer6_Sa", "timer7_So") {
  606. delete ($hash->{READINGS}{$reading});
  607. }
  608. } else {
  609. readingsBeginUpdate ($hash);
  610. foreach my $reading ("timer1_Mo", "timer2_Di", "timer3_Mi", "timer4_Do", "timer5_Fr", "timer6_Sa", "timer7_So") {
  611. readingsBulkUpdate ($hash, "$reading", $tmpTimersFrom{$reading}."-".$tmpTimersTo{$reading});
  612. }
  613. readingsEndUpdate($hash, 1);
  614. }
  615. #return "$reading from $minfrom to $maxto";
  616. return "";
  617. } else {
  618. return "Unknown argument $a[1], choose one of status timers";
  619. }
  620. #return $hash->{READINGS}{STATE}{VAL};
  621. #return $hash->{STATE};
  622. }
  623. #############################
  624. sub
  625. PWM_Set($@)
  626. {
  627. my ($hash, @a) = @_;
  628. my $u = "Unknown argument $a[1], choose one of recalc interval cycletime";
  629. if ( $a[1] =~ /^interval$|^cycletime$/ ) {
  630. return $u if(int(@a) != 3);
  631. my $hw = uc($a[1]);
  632. $hash->{$hw}= $a[2];
  633. } elsif ( $a[1] =~ /^recalc$/ ) {
  634. #$hash->{LOCAL} = 1;
  635. RemoveInternalTimer($hash);
  636. my $v = PWM_Calculate($hash);
  637. #delete $hash->{LOCAL};
  638. } else {
  639. return $u;
  640. }
  641. return undef;
  642. }
  643. #############################
  644. sub
  645. PWM_Define($$)
  646. {
  647. my ($hash, $def) = @_;
  648. my @a = split("[ \t][ \t]*", $def);
  649. my $name = $hash->{NAME};
  650. return "syntax: define <name> PWM [<interval>] [<cycletime>] [<minonofftime>] [<maxPulse>] [<maxSwitchOnPerCycle>,<maxSwitchOffPerCycle>] [<roomStayOn>,<roomStayOff>,<stayOnThreshold>]".
  651. " [<overallHeatingSwitch>[,<pulseThreshold>[,<followUpTime>[,<h_regexp_on>[,<delayTimeOn>]]]]"
  652. if(int(@a) < 2 || int(@a) > 9);
  653. my $interval = ((int(@a) > 2) ? $a[2] : 60);
  654. my $cycletime = ((int(@a) > 3) ? $a[3] : 900);
  655. my $minonofftime = ((int(@a) > 4) ? $a[4] : 120);
  656. my $maxPulse = ((int(@a) > 5) ? minNum ($a[5], 1.00) : 1.00);
  657. $hash->{INTERVAL} = $interval;
  658. $hash->{CYCLETIME} = $cycletime;
  659. $hash->{MINONOFFTIME} = $minonofftime;
  660. $hash->{MaxPulse} = $maxPulse;
  661. $hash->{STATE} = "defined";
  662. $hash->{p_interval} = $interval;
  663. $hash->{p_cycletime} = $cycletime;
  664. $hash->{p_minOnOfftime} = $minonofftime;
  665. $hash->{p_maxPulse} = $maxPulse;
  666. ##########
  667. # [<maxSwitchOnPerCycle>,<maxSwitchOffPerCycle>]
  668. if (int(@a) > 6) {
  669. my ($maxOn, $maxOff) = split (",", $a[6]);
  670. $maxOff = $maxOn unless (defined($maxOff));
  671. $hash->{MaxSwitchOnPerCycle} = $maxOn;
  672. $hash->{MaxSwitchOffPerCycle} = $maxOff;
  673. } else {
  674. if ($maxPulse == 1) {
  675. $hash->{MaxSwitchOnPerCycle} = 99;
  676. $hash->{MaxSwitchOffPerCycle} = 99;
  677. } else {
  678. $hash->{MaxSwitchOnPerCycle} = 1;
  679. $hash->{MaxSwitchOffPerCycle} = 1;
  680. }
  681. }
  682. ##########
  683. # [<roomStayOn>,<roomStayOff>,<stayOnThreshold>]
  684. if (int(@a) > 7) {
  685. my ($stayOn, $stayOff, $onThreshold) = split (",", $a[7]);
  686. $stayOff = 1 unless (defined($stayOff)); # one room stays off
  687. $onThreshold = 0.3 unless (defined($onThreshold)); # $stayOn is used only if average pluse is >= 0.3
  688. $hash->{NoRoomsToStayOn} = $stayOn; # eg. 4 rooms stay switched on (unless average pulse is less then threshold)
  689. $hash->{NoRoomsToStayOff} = $stayOff; # 1 room stays off to limit energy used (maxPulse should be < 1 if this is used)
  690. $hash->{NoRoomsToStayOnThreshold} = $onThreshold; # $stayOn is used only if average pluse is >= threshold
  691. $hash->{p_roomsMinOnOffThreshold} = $a[7];
  692. } else {
  693. $hash->{NoRoomsToStayOn} = 0; # switch off all rooms is allowd
  694. $hash->{NoRoomsToStayOff} = 0; # switch on all rooms if allowed
  695. $hash->{NoRoomsToStayOnThreshold} = 0; # pulse threshold to use "NoRoomsToStayOn"
  696. $hash->{p_minOnOffThreshold} = "";
  697. }
  698. ##########
  699. # [<overallHeatingSwitch>]
  700. if (int(@a) > 8) {
  701. my ($hactor, $h_threshold, $h_followUpTime, $h_regexp_on, $h_delayTimeOn) = split (",", $a[8], 5);
  702. $h_followUpTime = 0 unless ($h_followUpTime);
  703. $h_threshold = 0 unless ($h_threshold);
  704. $h_regexp_on = "on" unless ($h_regexp_on);
  705. $h_delayTimeOn = 0 unless ($h_delayTimeOn);
  706. if (!$defs{$hactor} && $hactor ne "dummy")
  707. {
  708. my $msg = "$name: Unknown actor device $hactor specified";
  709. Log3 ($hash, 3, "PWM_Define $msg");
  710. return $msg;
  711. }
  712. $hash->{OverallHeatingSwitch} = $hactor;
  713. $hash->{OverallHeatingSwitch_threshold} = $h_threshold;
  714. $hash->{OverallHeatingSwitch_regexp_on} = $h_regexp_on;
  715. $hash->{OverallHeatingSwitch_roomBased} = ($h_threshold > 0) ? "off" : "on";
  716. $hash->{OverallHeatingSwitch_followUpTime} = $h_followUpTime;
  717. $hash->{OverallHeatingSwitch_delayTimeOn} = $h_delayTimeOn;
  718. $hash->{p_overallHeatingSwitch} = $a[8];
  719. readingsSingleUpdate ($hash, "OverallHeatingSwitchWaitUntilOff", "", 0);
  720. readingsSingleUpdate ($hash, "OverallHeatingSwitchWaitBeforeOn", "", 0);
  721. readingsSingleUpdate ($hash, "OverallHeatingSwitch", "", 0);
  722. delete ($hash->{READINGS}{OverallHeatingSwitchWaitUntil}) if defined ($hash->{READINGS}{OverallHeatingSwitchWaitUntil});
  723. delete ($hash->{READINGS}{OverallHeatingSwitchWaitBefore}) if defined ($hash->{READINGS}{OverallHeatingSwitchWaitBefore});
  724. } else {
  725. $hash->{OverallHeatingSwitch} = "";
  726. $hash->{OverallHeatingSwitch_threshold} = "";
  727. $hash->{OverallHeatingSwitch_regexp_on} = "";
  728. $hash->{OverallHeatingSwitch_roomBased} = "";
  729. $hash->{OverallHeatingSwitch_followUpTime} = "";
  730. $hash->{OverallHeatingSwitch_delayTimeOn} = "";
  731. $hash->{p_overallHeatingSwitch} = "";
  732. readingsSingleUpdate ($hash, "OverallHeatingSwitchWaitUntilOff", "", 0);
  733. readingsSingleUpdate ($hash, "OverallHeatingSwitchWaitBeforeOn", "", 0);
  734. readingsSingleUpdate ($hash, "OverallHeatingSwitch", "", 0);
  735. delete ($hash->{READINGS}{OverallHeatingSwitchWaitUntil}) if defined ($hash->{READINGS}{OverallHeatingSwitchWaitUntil});
  736. delete ($hash->{READINGS}{OverallHeatingSwitchWaitBefore}) if defined ($hash->{READINGS}{OverallHeatingSwitchWaitBefore});
  737. }
  738. #AssignIoPort($hash);
  739. if($hash->{INTERVAL} > 0) {
  740. InternalTimer(gettimeofday() + 10, "PWM_Calculate", $hash, 0);
  741. }
  742. Log3 ($hash, 3, "PWM Define $name");
  743. return undef;
  744. }
  745. ###################################
  746. sub PWM_Undef($$)
  747. {
  748. my ($hash, $args) = @_;
  749. my $name = $hash->{NAME};
  750. Log3 ($hash, 3, "PWM Undef $name");
  751. if ( $hash->{INTERVAL} )
  752. {
  753. RemoveInternalTimer($hash);
  754. }
  755. return undef;
  756. }
  757. sub
  758. PWM_Attr(@)
  759. {
  760. my @a = @_;
  761. my ($action, $name, $attrname, $attrval) = @a;
  762. my $hash = $defs{$name};
  763. $attrval = "" unless defined ($attrval);
  764. if ($action eq "del")
  765. {
  766. if (defined $attr{$name}{$attrname}) {
  767. delete ($attr{$name}{$attrname});
  768. }
  769. return undef;
  770. }
  771. elsif ($action eq "set")
  772. {
  773. if (defined $attr{$name}{$attrname})
  774. {
  775. }
  776. }
  777. Log3 (undef, 2, "called PWM_Attr($a[0],$a[1],$a[2],<$a[3]>)");
  778. return undef;
  779. }
  780. ###################################
  781. 1;
  782. =pod
  783. =item device
  784. =item summary Device for room temperature control using PWM. See also 93_PWM.pm
  785. =begin html
  786. <a name="PWM"></a>
  787. <h3>PWM</h3>
  788. <ul>
  789. <table>
  790. <tr><td>
  791. The PMW module implements temperature regulation for heating systems only capeable of switching on/off.<br><br>
  792. PWM is based on Pulse Width Modulation which means valve position 70% is implemented in switching the device on for 70% and off for 30% in a given timeframe.<br>
  793. PWM defines a calculation unit and depents on objects based on PWMR which define the rooms to be heated.<br>
  794. <br>
  795. </td></tr>
  796. </table>
  797. <b>Define</b>
  798. <ul>
  799. <code>define &lt;name&gt; PWM [&lt;interval&gt;] [&lt;cycletime&gt;] [&lt;minonofftime&gt;] [&lt;maxPulse&gt;] [&lt;maxSwitchOnPerCycle&gt;,&lt;maxSwitchOffPerCycle&gt;] [&lt;roomStayOn&gt;,&lt;roomStayOff&gt;,&lt;stayOnThreshold&gt;] [&lt;overallHeatingSwitch&gt;[,&lt;pulseThreshold&gt;[,&lt;followUpTime&gt;[,&lt;h_regexp_on&gt;[,&lt;delayTimeOn&gt;]]]]]<br></code>
  800. <br>
  801. eg. define fb PWM 60 900 120 1 99,99 0,0,0 pumpactor<br>
  802. <br>
  803. Define a calculation object with the following parameters:<br>
  804. <ul>
  805. <li>interval<br>
  806. Calculate the pulses every <i>interval</i> seconds. Default is 60 seconds.<br>
  807. </li>
  808. <li>cycletime<br>
  809. Timeframe to which the pulses refere to. Default is 900 seconds (=15 Minutes). "valve position" of 100% calculates to "on" for this period.<br>
  810. </li>
  811. <li>minonofftime<br>
  812. Default is 120 seconds.
  813. Floor heating systems are driven by thermomechanic elements which react very slow. on/off status changes for lower periods are ignored.<br>
  814. </li>
  815. <li>maxPulse<br>
  816. Default is 1, which means that a device can be switched on for the full <i>cylcetime</i> period.<br>
  817. For energy saving reasons it may be wanted to prevent situations were all rooms are switched on (high energy usage) and afterwards off.<br>
  818. In this case <i>maxPulse</i> is set to 0.85 (=12:45 minutes) which forces a room with a pulse of 1 (=100%) to be switched off after 12:45 minutes to give another
  819. room the chance to be switched on.
  820. <br>
  821. </li>
  822. <li>maxSwitchOnPerCycle,maxSwitchoffPerCycle<br>
  823. Defaults are 99 for both values. This means that 99 PWMR object can be switched on or off at the same time.<br>
  824. To prevent energy usage peaks followend by "no energy consumption" situations set both values to "1".<br>
  825. This means after the room the the least energy required is switched off the next will be switched off.<br>
  826. Rooms are switched on or off one after the other (in <interval> cycles) and not all at one time.<br>
  827. Waiting times are honored by a addon to the pulse.<br>
  828. <br>
  829. </li>
  830. <li>roomStayOn,roomStayOff,stayOnThreshold<br>
  831. Defauts: <br>
  832. <i>roomStayOn</i> = 0 ... all rooms can be switched off at the same time.<br>
  833. <i>roomStayOff</i> = 0 ... all rooms can be switched on at the same time.<br>
  834. <i>stayOnThreshold</i> = 0 ... no impact.<br>
  835. For energy saving reasons the following may be set: "4,1,0.25". This means:<br>
  836. The room with the least pulse will be kept off (<i>roomsStayOff</i>=1)<br>
  837. If the average pulse for the (<i>roomsStayOn</i>=4) rooms with the most heating required is greater than (<i>stayOnThreshold</i>=0.25) then <i>maxRoomStayOn</i> will be kept in state "on", even if the time for the current pulse is reached.
  838. If the threshold is not reached (not so much heating required) then all rooms can be switched off at the same time.<br>
  839. <br>
  840. </li>
  841. <li>&lt;overallHeatingSwitch&gt[,&lt;pulseThreshold&gt[,&lt;followUpTime&gt;[,&lt;regexp_on&gt;[,&lt;delayTimeOn&gt;]]]]<br>
  842. Universal switch to controll eg. pumps or the heater itself. It will be set to "off" if no heating is required and otherwise "on".<br>
  843. <i>pulseThreshold</i> defines a threshold which is applied to reading <i>pulseMax</i>, <i>pulseSum</i>, <i>pulseAvg</i>, <i>pulseAvg2</i> or <i>pulseAvg3</i> of the PWM object to decide if heating is required. If (calculated pulse > threshold) then actor is set to "on", otherwise "off".<br>
  844. If <i>pulseThreshold</i> is set to 0 (or is not defined) then the decision is based on <i>roomsOn</i>. If (roomsOn > 0) then actor is set to "on", otherwise "off".<br>
  845. <i>followUpTime</i> defines a number of seconds which is used to delay the status change from "on" to "off". This can be used to prevent a toggling switch.<br>
  846. <i>regexp_on</i> defines a regular expression to be applied to the state of the actor. Default is "on". If state matches the regular expression it is handled as "on", otherwise "off".<br>
  847. <i>delayTimeOn</i> defines a number of seconds which is used to delay the status change from "off" to "on". This can be used to give the valves time to open before switching..<br>
  848. The pulse used for comparision is defined by attribute <i>overallHeatingSwitchRef</i>. Default is <i>maxPulse</i>.<br>
  849. <br>
  850. </li>
  851. </ul>
  852. <br>
  853. Example:<br>
  854. <br>
  855. <code>define fh PWM</code>
  856. <br>which is equal to<br>
  857. <code>define fh PWM 60 900 120 1 99,99 0,0,0</code>
  858. <br>Energy saving definition might be<br>
  859. <code>define fh PWM 60 900 120 0.85 1,1 4,1,0.25</code>
  860. <br><br>
  861. </ul>
  862. <br>
  863. <b>Set </b>
  864. <ul>
  865. <li>cycletime<br>
  866. Temporary change of parameter <i>cycletime</i>.
  867. </li><br>
  868. <li>interval<br>
  869. Temporary change of parameter <i>interval</i>.
  870. </li><br>
  871. <li>recalc<br>
  872. Cause recalculation that normally appeary every <i>interval</i> seconds.
  873. </li><br>
  874. </ul>
  875. <b>Get</b>
  876. <ul>
  877. <li>status<br>
  878. Retrieve content of variable <i>STATE</i>.
  879. </li><br>
  880. <li>timers<br>
  881. Retrieve values from the readings "timer?_??" from the attached rooms..<br>
  882. The readings define start and end times for different room temperatures.<br>
  883. This funktion will retrieve the first start and the last end time. <i>STATE</i>.
  884. </li><br>
  885. </ul>
  886. <br>
  887. <b>Attributes</b>
  888. <ul>
  889. <li>valveProtectIdlePeriod<br>
  890. Protect Valve by switching on actor for 300 seconds.<br>
  891. After <i>valveProtectIdlePeriod</i> number of days without switching the valve the actor is set to "on" for 300 seconds.
  892. overallHeatingSwitch is not affected.
  893. </li><br>
  894. <li>overallHeatingSwitchRef<br>
  895. Defines which reading is used for threshold comparision for <i>OverallHeatingSwitch</i> calculation. Possible values are:<br>
  896. <i>pulseMax</i>,
  897. <i>pulseSum</i>,
  898. <i>pulseAvg</i>,
  899. <i>pulseAvg2</i>,
  900. <i>pulseAvg3</i>,
  901. <i>avgPulseRoomsOn</i><br>
  902. pulseAvg is an average pulse of all rooms which should be switched to "on".<br>
  903. pulseAvg2 and pulseAvg3 refer to the 2 or 3 romms with highest pulses.
  904. </li><br>
  905. </ul>
  906. <br>
  907. </ul>
  908. =end html
  909. =cut