23_LUXTRONIK2.pm 98 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360
  1. ###############################################################
  2. # $Id: 23_LUXTRONIK2.pm 15291 2017-10-19 19:29:29Z tupol $Date: $
  3. #
  4. # 23_LUXTRONIK2.pm
  5. #
  6. # (c) 2012-2017 Torsten Poitzsch
  7. # (c) 2012-2013 Jan-Hinrich Fessel (oskar at fessel . org)
  8. #
  9. # Copyright notice
  10. #
  11. # The modul reads and writes parameters of the heat pump controller
  12. # Luxtronik 2.0 used in Alpha Innotec and Siemens Novelan (WPR NET) heat pumps.
  13. #
  14. # This script is free software; you can redistribute it and/or modify
  15. # it under the terms of the GNU General Public License as published by
  16. # the Free Software Foundation; either version 2 of the License, or
  17. # (at your option) any later version.
  18. #
  19. # The GNU General Public License can be found at
  20. # http://www.gnu.org/copyleft/gpl.html.
  21. # A copy is found in the textfile GPL.txt and important notices to the license
  22. # from the author is found in LICENSE.txt distributed with these scripts.
  23. #
  24. # This script is distributed in the hope that it will be useful,
  25. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  26. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  27. # GNU General Public License for more details.
  28. #
  29. # This copyright notice MUST APPEAR in all copies of the script!
  30. #
  31. ################################################################
  32. ##############################################
  33. package main;
  34. use strict;
  35. use warnings;
  36. use Blocking;
  37. use IO::Socket;
  38. use Time::HiRes qw/ time /;
  39. use Net::Telnet;
  40. sub LUXTRONIK2_doStatisticThermalPower ($$$$$$$$$);
  41. sub LUXTRONIK2_doStatisticMinMax ($$$);
  42. sub LUXTRONIK2_doStatisticMinMaxSingle ($$$$);
  43. sub LUXTRONIK2_storeReadings ($$$$$$);
  44. sub LUXTRONIK2_doStatisticDelta ($$$$$) ;
  45. sub LUXTRONIK2_doStatisticDeltaSingle ($$$$$$$);
  46. #List of firmware versions that are known to be compatible with this modul
  47. my $testedFirmware = "#V1.51#V1.54C#V1.60#V1.61#V1.64#V1.69#V1.70#V1.73#V1.77#V1.80#";
  48. my $compatibleFirmware = "#V1.51#V1.54C#V1.60#V1.61#V1.64#V1.69#V1.70#V1.73#V1.77#V1.80#";
  49. sub ##########################################
  50. LUXTRONIK2_Log($$$)
  51. {
  52. my ( $hash, $loglevel, $text ) = @_;
  53. my $xline = ( caller(0) )[2];
  54. my $xsubroutine = ( caller(1) )[3];
  55. my $sub = ( split( ':', $xsubroutine ) )[2];
  56. $sub =~ s/LUXTRONIK2_//;
  57. my $instName = ( ref($hash) eq "HASH" ) ? $hash->{NAME} : $hash;
  58. Log3 $instName, $loglevel, "LUXTRONIK2 $instName: $sub.$xline " . $text;
  59. }
  60. sub ########################################
  61. LUXTRONIK2_Initialize($)
  62. {
  63. my ($hash) = @_;
  64. $hash->{DefFn} = "LUXTRONIK2_Define";
  65. $hash->{UndefFn} = "LUXTRONIK2_Undefine";
  66. $hash->{GetFn} = "LUXTRONIK2_Get";
  67. $hash->{SetFn} = "LUXTRONIK2_Set";
  68. $hash->{AttrFn} = "LUXTRONIK2_Attr";
  69. $hash->{AttrList} = "disable:0,1 ".
  70. "allowSetParameter:0,1 ".
  71. "autoSynchClock:slider,10,5,300 ".
  72. "boilerVolumn ".
  73. "heatPumpElectricalPowerFactor ".
  74. "heatPumpElectricalPowerWatt ".
  75. "heatRodElectricalPowerWatt ".
  76. "compressor2ElectricalPowerWatt ".
  77. "doStatistics:0,1 ".
  78. "ignoreFirmwareCheck:0,1 ".
  79. "statusHTML ".
  80. $readingFnAttributes;
  81. }
  82. sub ########################################
  83. LUXTRONIK2_Define($$)
  84. {
  85. my ($hash, $def) = @_;
  86. my @a = split("[ \t][ \t]*", $def);
  87. return "Usage: define <name> LUXTRONIK2 <ip-address> [poll-interval]"
  88. if(@a <3 || @a >4);
  89. my $name = $a[0];
  90. my $host = $a[2];
  91. my $interval = 5*60;
  92. $interval = $a[3] if(int(@a) == 4);
  93. $interval = 10 if( $interval < 10 );
  94. $hash->{NAME} = $name;
  95. $hash->{STATE} = "Initializing";
  96. $hash->{HOST} = $host;
  97. if ( $host =~ /(.*):(.*)/ ) {
  98. $hash->{HOST} = $1;
  99. $hash->{PORT} = $2;
  100. $hash->{fhem}{portDefined} = 1;
  101. }
  102. else {
  103. $hash->{HOST} = $host;
  104. $hash->{PORT} = 8888;
  105. $hash->{fhem}{portDefined} = 0;
  106. }
  107. $hash->{INTERVAL} = $interval;
  108. $hash->{NOTIFYDEV} = "global";
  109. RemoveInternalTimer($hash);
  110. #Get first data after 10 seconds
  111. InternalTimer(gettimeofday() + 10, "LUXTRONIK2_GetUpdate", $hash, 0);
  112. #Reset temporary values
  113. $hash->{fhem}{durationFetchReadingsMin} = 0;
  114. $hash->{fhem}{durationFetchReadingsMax} = 0;
  115. $hash->{fhem}{alertFirmware} = 0;
  116. $hash->{fhem}{statBoilerHeatUpStep} = 0;
  117. $hash->{fhem}{statBoilerCoolDownStep} = 0;
  118. $hash->{fhem}{defrost}{mode}="none";
  119. $hash->{fhem}{hotWaterLastRun} = time();
  120. $hash->{fhem}{heatingPumpLastStop} = time();
  121. $hash->{fhem}{heatingPumpLastRun} = time();
  122. $hash->{fhem}{modulVersion} = '$Date: 2017-10-19 21:29:29 +0200 (Thu, 19 Oct 2017) $';
  123. return undef;
  124. }
  125. sub ########################################
  126. LUXTRONIK2_Undefine($$)
  127. {
  128. my ($hash, $arg) = @_;
  129. RemoveInternalTimer($hash);
  130. BlockingKill($hash->{helper}{RUNNING_PID}) if(defined($hash->{helper}{RUNNING_PID}));
  131. return undef;
  132. }
  133. sub ########################################
  134. LUXTRONIK2_Attr(@)
  135. {
  136. my ($cmd,$name,$aName,$aVal) = @_;
  137. # $cmd can be "del" or "set"
  138. # $name is device name
  139. # aName and aVal are Attribute name and value
  140. if ($cmd eq "set") {
  141. if ($aName eq "1allowSetParameter") {
  142. eval { qr/$aVal/ };
  143. if ($@) {
  144. LUXTRONIK2_Log $name, 3, "Invalid allowSetParameter in attr $name $aName $aVal: $@";
  145. return "Invalid allowSetParameter $aVal";
  146. }
  147. }
  148. }
  149. return undef;
  150. }
  151. sub ########################################
  152. LUXTRONIK2_Set($$@)
  153. {
  154. my ($hash, $name, $cmd, $val) = @_;
  155. my $resultStr = "";
  156. if($cmd eq 'statusRequest') {
  157. $hash->{LOCAL} = 1;
  158. Log3 $name, 3, "set $name $cmd";
  159. LUXTRONIK2_GetUpdate($hash);
  160. $hash->{LOCAL} = 0;
  161. return undef;
  162. }
  163. elsif ($cmd eq 'resetStatistics') {
  164. Log3 $name, 3, "set $name $cmd $val";
  165. if ( $val eq "statBoilerGradientCoolDownMin"
  166. && exists($hash->{READINGS}{statBoilerGradientCoolDownMin})) {
  167. delete $hash->{READINGS}{statBoilerGradientCoolDownMin};
  168. $resultStr .= " statBoilerGradientCoolDownMin";
  169. }
  170. elsif ($val =~ /all|statAmbientTemp\.\.\.|statElectricity\.\.\.|statHours\.\.\.|statHeatQ\.\.\./) {
  171. my $regExp;
  172. if ($val eq "all") { $regExp = "stat"; }
  173. else { $regExp = substr $val, 0, -3; }
  174. foreach (sort keys %{ $hash->{READINGS} }) {
  175. if ($_ =~ /^\.?$regExp/ && $_ ne "state") {
  176. delete $hash->{READINGS}{$_};
  177. $resultStr .= " " . $_;
  178. }
  179. }
  180. }
  181. if ( $resultStr eq "" ) {
  182. $resultStr = "$name: No statistics to reset";
  183. } else {
  184. $resultStr = "$name: Statistic value(s) deleted:" . $resultStr;
  185. WriteStatefile();
  186. }
  187. # LUXTRONIK2_Log $hash, 3, $resultStr;
  188. return $resultStr;
  189. }
  190. elsif($cmd eq 'INTERVAL' && int(@_)==4 ) {
  191. Log3 $name, 3, "set $name $cmd $val";
  192. $val = 10 if( $val < 10 );
  193. $hash->{INTERVAL}=$val;
  194. return "Polling interval set to $val seconds.";
  195. }
  196. elsif($cmd eq 'activeTariff' && int(@_)==4 ) {
  197. $val = 0 if( $val < 1 || $val > 9 );
  198. readingsSingleUpdate($hash,"activeTariff",$val, 1);
  199. $hash->{LOCAL} = 1;
  200. LUXTRONIK2_GetUpdate($hash);
  201. $hash->{LOCAL} = 0;
  202. Log3 $name, 3, "set $name $cmd $val";
  203. return undef;
  204. }
  205. #Check Firmware and Set-Parameter-lock
  206. if ( $cmd =~ /^(synchronizeClockHeatPump|hotWaterTemperatureTarget|opModeHotWater)$/i )
  207. {
  208. my $firmware = ReadingsVal($name,"firmware","");
  209. my $firmwareCheck = LUXTRONIK2_checkFirmware($firmware);
  210. # stop in case of incompatible firmware
  211. if ($firmwareCheck eq "fwNotCompatible") {
  212. LUXTRONIK2_Log $name, 3, " Error: Host firmware '$firmware' not compatible for parameter setting.";
  213. return "Firmware '$firmware' not compatible for parameter setting. ";
  214. # stop in case of untested firmware and firmware check enabled
  215. } elsif (AttrVal($name, "ignoreFirmwareCheck", 0)!= 1 &&
  216. $firmwareCheck eq "fwNotTested") {
  217. LUXTRONIK2_Log $name, 3, " Error: Host firmware '$firmware' not tested for parameter setting. To test set attribute 'ignoreFirmwareCheck' to 1";
  218. return "Firmware '$firmware' not compatible for parameter setting. To test set attribute 'ignoreFirmwareCheck' to 1.";
  219. # stop in case setting of parameters is not enabled
  220. } elsif ( AttrVal($name, "allowSetParameter", 0) != 1) {
  221. LUXTRONIK2_Log $name, 3, " Error: Setting of parameters not allowed. Please set attribut 'allowSetParameter' to 1";
  222. return "Setting of parameters not allowed. To unlock, please set attribut 'allowSetParameter' to 1.";
  223. }
  224. }
  225. if ($cmd eq 'synchronizeClockHeatPump') {
  226. $hash->{LOCAL} = 1;
  227. $resultStr = LUXTRONIK2_synchronizeClock($hash);
  228. $hash->{LOCAL} = 0;
  229. LUXTRONIK2_Log $name, 3, $resultStr;
  230. return $resultStr;
  231. }
  232. elsif ($cmd eq 'boostHotWater' && int(@_)<=4) {
  233. Log3 $name, 3, "set $name $cmd" unless $val;
  234. Log3 $name, 3, "set $name $cmd $val" if $val;
  235. return LUXTRONIK2_boostHotWater_Start( $hash, $val );
  236. }
  237. elsif(int(@_)==4 &&
  238. ($cmd eq 'hotWaterTemperatureTarget'
  239. || $cmd eq 'opModeHotWater'
  240. || $cmd eq 'returnTemperatureHyst'
  241. || $cmd eq 'returnTemperatureSetBack'
  242. || $cmd eq 'heatingCurveEndPoint'
  243. || $cmd eq 'heatingCurveOffset'
  244. || $cmd eq 'heatSourceDefrostAirEnd'
  245. || $cmd eq 'heatSourceDefrostAirThreshold')) {
  246. Log3 $name, 3, "set $name $cmd $val";
  247. $hash->{LOCAL} = 1;
  248. $resultStr = LUXTRONIK2_SetParameter ($hash, $cmd, $val);
  249. $hash->{LOCAL} = 0;
  250. return $resultStr;
  251. }
  252. elsif( int(@_)==4 && $cmd eq 'hotWaterCircPumpDeaerate' ) { # Einstellung->Entlüftung
  253. Log3 $name, 3, "set $name $cmd $val";
  254. return "$name Error: Wrong parameter given for opModeHotWater, use Automatik,Party,Off"
  255. if $val !~ /on|off/;
  256. $hash->{LOCAL} = 1;
  257. $resultStr = LUXTRONIK2_SetParameter ($hash, $cmd, $val);
  258. if ($val eq "on" ) { $resultStr .= LUXTRONIK2_SetParameter ($hash, "runDeaerate", 1); }
  259. else { $resultStr .= LUXTRONIK2_SetParameter ($hash, "runDeaerate", 0); } # only send if no Deaerate checkbox is selected at all.
  260. $hash->{LOCAL} = 0;
  261. return $resultStr;
  262. }
  263. my $list = "statusRequest:noArg"
  264. ." activeTariff:0,1,2,3,4,5,6,7,8,9"
  265. ." boostHotWater"
  266. ." heatingCurveEndPoint"
  267. ." heatingCurveOffset"
  268. ." hotWaterCircPumpDeaerate:on,off"
  269. ." hotWaterTemperatureTarget "
  270. ." resetStatistics:all,statBoilerGradientCoolDownMin,statAmbientTemp...,statElectricity...,statHours...,statHeatQ..."
  271. ." returnTemperatureHyst "
  272. ." returnTemperatureSetBack "
  273. ." opModeHotWater:Auto,Party,Off"
  274. ." synchronizeClockHeatPump:noArg"
  275. ." INTERVAL ";
  276. return "Unknown argument $cmd, choose one of $list";
  277. }
  278. sub ########################################
  279. LUXTRONIK2_Get($$@)
  280. {
  281. my ($hash, $name, $cmd, @val ) = @_;
  282. my $resultStr = "";
  283. if($cmd eq 'heatingCurveParameter') {
  284. # Log3 $name, 3, "get $name $cmd";
  285. if (int @val !=4 ) {
  286. my $msg = "Wrong number of parameter (".int @val.")in get $name $cmd";
  287. Log3 $name, 3, $msg;
  288. return $msg;
  289. }
  290. else {
  291. return LUXTRONIK2_calcHeatingCurveParameter ( $hash, $val[0], $val[1], $val[2], $val[3]);
  292. }
  293. }
  294. elsif($cmd eq 'heatingCurveReturnTemperature') {
  295. # Log3 $name, 3, "get $name $cmd";
  296. if (int @val !=1) {
  297. my $msg = "Wrong number of parameter (".int @val.")in get $name $cmd";
  298. Log3 $name, 3, $msg;
  299. return $msg;
  300. }
  301. else {
  302. my $heatingCurveEndPoint = $hash->{READINGS}{heatingCurveEndPoint}{VAL};
  303. my $heatingCurveOffset = $hash->{READINGS}{heatingCurveOffset}{VAL};
  304. return LUXTRONIK2_getHeatingCurveReturnTemperature ( $hash, $val[0], $heatingCurveEndPoint, $heatingCurveOffset);
  305. }
  306. }
  307. my $list = "heatingCurveParameter "
  308. . "heatingCurveReturnTemperature ";
  309. return "Unknown argument $cmd, choose one of $list";
  310. }
  311. sub ########################################
  312. LUXTRONIK2_GetUpdate($)
  313. {
  314. my ($hash) = @_;
  315. my $name = $hash->{NAME};
  316. if(!$hash->{LOCAL}) {
  317. RemoveInternalTimer($hash);
  318. InternalTimer(gettimeofday()+$hash->{INTERVAL}, "LUXTRONIK2_GetUpdate", $hash, 1);
  319. return undef if( AttrVal($name, "disable", 0 ) == 1 );
  320. }
  321. $hash->{helper}{RUNNING_PID} = BlockingCall("LUXTRONIK2_DoUpdate", $name, "LUXTRONIK2_UpdateDone", 25, "LUXTRONIK2_UpdateAborted", $hash) unless(exists($hash->{helper}{RUNNING_PID}));
  322. }
  323. ########################################
  324. sub LUXTRONIK2_DoUpdate($)
  325. {
  326. my ($name) = @_;
  327. my $hash = $defs{$name};
  328. my $host = $hash->{HOST};
  329. my $port = $hash->{PORT};
  330. my @heatpump_values;
  331. my @heatpump_parameters;
  332. my @heatpump_visibility;
  333. my $count=0;
  334. my $result="";
  335. my $readingStartTime = time();
  336. LUXTRONIK2_Log $name, 5, "Opening connection to $host:$port";
  337. my $socket = new IO::Socket::INET (
  338. PeerAddr => $host,
  339. PeerPort => $port,
  340. # Type = SOCK_STREAM, # probably needed on some systems
  341. Proto => 'tcp'
  342. );
  343. if (!$socket) {
  344. LUXTRONIK2_Log $name, 1, "Could not open connection to host $host:$port";
  345. return "$name|0|Can't connect to $host:$port";
  346. }
  347. $socket->autoflush(1);
  348. ############################
  349. #Fetch operational values (FOV)
  350. ############################
  351. LUXTRONIK2_Log $name, 5, "Ask host for operational values";
  352. $socket->send( pack( "N2", (3004,0) ) );
  353. LUXTRONIK2_Log $name, 5, "Start to receive operational values";
  354. #(FOV) read first 4 bytes of response -> should be request_echo = 3004
  355. $socket->recv( $result,4, MSG_WAITALL );
  356. $count = unpack("N", $result);
  357. if($count != 3004) {
  358. LUXTRONIK2_Log $name, 2, "Fetching operational values - wrong echo of request 3004: ".length($result)." -> ".$count;
  359. $socket->close();
  360. return "$name|0|3004 != $count";
  361. }
  362. #(FOV) read next 4 bytes of response -> should be status = 0
  363. $socket->recv($result,4, MSG_WAITALL );
  364. $count = unpack("N", $result);
  365. if($count > 0) {
  366. LUXTRONIK2_Log $name, 4, "Parameter on target changed, restart parameter reading after 5 seconds";
  367. $socket->close();
  368. return "$name|2|Status = $count - parameter on target changed, restart device reading after 5 seconds";
  369. }
  370. #(FOV) read next 4 bytes of response -> should be count_calc_values > 0
  371. $socket->recv($result,4, MSG_WAITALL );
  372. my $count_calc_values = unpack("N", $result);
  373. if($count_calc_values == 0) {
  374. LUXTRONIK2_Log $name, 2, "Fetching operational values - 0 values announced: ".length($result)." -> ".$count_calc_values;
  375. $socket->close();
  376. return "$name|0|0 values read";
  377. }
  378. #(FOV) read remaining response -> should be previous number of parameters
  379. $socket->recv( $result, $count_calc_values*4, MSG_WAITALL );
  380. if( length($result) != $count_calc_values*4 ) {
  381. LUXTRONIK2_Log $name, 1, "Operational values length check: ".length($result)." should have been ". $count_calc_values * 4;
  382. $socket->close();
  383. return "$name|0|Number of values read mismatch ( $!)\n";
  384. }
  385. #(FOV) unpack response in array
  386. @heatpump_values = unpack("N$count_calc_values", $result);
  387. if(scalar(@heatpump_values) != $count_calc_values) {
  388. LUXTRONIK2_Log $name, 2, "Unpacking problem by operation values: ".scalar(@heatpump_values)." instead of ".$count_calc_values;
  389. $socket->close();
  390. return "$name|0|Unpacking problem of operational values";
  391. }
  392. LUXTRONIK2_Log $name, 5, "$count_calc_values operational values received";
  393. ############################
  394. #Fetch set parameters (FSP)
  395. ############################
  396. LUXTRONIK2_Log $name, 5, "Ask host for set parameters";
  397. $socket->send( pack( "N2", (3003,0) ) );
  398. LUXTRONIK2_Log $name, 5, "Start to receive set parameters";
  399. #(FSP) read first 4 bytes of response -> should be request_echo=3003
  400. $socket->recv($result,4, MSG_WAITALL );
  401. $count = unpack("N", $result);
  402. if($count != 3003) {
  403. LUXTRONIK2_Log $name, 2, "Wrong echo of request 3003: ".length($result)." -> ".$count;
  404. $socket->close();
  405. return "$name|0|3003 != 3003";
  406. }
  407. #(FSP) read next 4 bytes of response -> should be number_of_parameters > 0
  408. $socket->recv($result,4, MSG_WAITALL );
  409. my $count_set_parameter = unpack("N", $result);
  410. if($count_set_parameter == 0) {
  411. LUXTRONIK2_Log $name, 2, "0 parameter read: ".length($result)." -> ".$count_set_parameter;
  412. $socket->close();
  413. return "$name|0|0 parameter read";
  414. }
  415. #(FSP) read remaining response -> should be previous number of parameters
  416. $socket->recv( $result, $count_set_parameter*4, MSG_WAITALL );
  417. if( length($result) != $count_set_parameter*4 ) {
  418. LUXTRONIK2_Log $name, 1, "Parameter length check: ".length($result)." should have been ". ($count_set_parameter * 4);
  419. $socket->close();
  420. return "$name|0|Number of parameters read mismatch ( $!)\n";
  421. }
  422. @heatpump_parameters = unpack("N$count_set_parameter", $result);
  423. if(scalar(@heatpump_parameters) != $count_set_parameter) {
  424. LUXTRONIK2_Log $name, 2, "Unpacking problem by set parameter: ".scalar(@heatpump_parameters)." instead of ".$count_set_parameter;
  425. $socket->close();
  426. return "$name|0|Unpacking problem of set parameters";
  427. }
  428. LUXTRONIK2_Log $name, 5, "$count_set_parameter set values received";
  429. ############################
  430. #Fetch Visibility Attributes (FVA)
  431. ############################
  432. LUXTRONIK2_Log $name, 5, "Ask host for visibility attributes";
  433. $socket->send( pack( "N2", (3005,0) ) );
  434. LUXTRONIK2_Log $name, 5, "Start to receive visibility attributes";
  435. #(FVA) read first 4 bytes of response -> should be request_echo=3005
  436. $socket->recv($result,4, MSG_WAITALL );
  437. $count = unpack("N", $result);
  438. if($count != 3005) {
  439. LUXTRONIK2_Log $name, 2, "Wrong echo of request 3005: ".length($result)." -> ".$count;
  440. $socket->close();
  441. return "$name|0|3005 != $count";
  442. }
  443. #(FVA) read next 4 bytes of response -> should be number_of_Visibility_Attributes > 0
  444. $socket->recv($result,4, MSG_WAITALL );
  445. my $countVisibAttr = unpack("N", $result);
  446. if($countVisibAttr == 0) {
  447. LUXTRONIK2_Log $name, 2, "0 visibility attributes announced: ".length($result)." -> ".$countVisibAttr;
  448. $socket->close();
  449. return "$name|0|0 visibility attributes announced";
  450. }
  451. #(FVA) read remaining response bytewise -> should be previous number of parameters
  452. $socket->recv( $result, $countVisibAttr, MSG_WAITALL );
  453. if( length( $result ) != $countVisibAttr ) {
  454. LUXTRONIK2_Log $name, 1, "Visibility attributes length check: ".length($result)." should have been ". $countVisibAttr;
  455. $socket->close();
  456. return "$name|0|Number of Visibility attributes read mismatch ( $!)\n";
  457. }
  458. @heatpump_visibility = unpack("C$countVisibAttr", $result);
  459. if(scalar(@heatpump_visibility) != $countVisibAttr) {
  460. LUXTRONIK2_Log $name, 2, "Unpacking problem by visibility attributes: ".scalar(@heatpump_visibility)." instead of ".$countVisibAttr;
  461. $socket->close();
  462. return "$name|0|Unpacking problem of visibility attributes";
  463. }
  464. LUXTRONIK2_Log $name, 5, "$countVisibAttr visibility attributs received";
  465. ####################################
  466. LUXTRONIK2_Log $name, 5, "Closing connection to host $host";
  467. $socket->close();
  468. my $readingEndTime = time();
  469. #return certain readings for further processing
  470. # 0 - name
  471. my $return_str="$name";
  472. # 1 - no error = 1
  473. $return_str .= "|1";
  474. # 2 - opStateHeatPump1
  475. $return_str .= "|".$heatpump_values[117];
  476. # 3 - opStateHeatPump3
  477. $return_str .= "|".$heatpump_values[119];
  478. # 4 - Stufe - ID_WEB_HauptMenuAHP_Stufe
  479. $return_str .= "|".$heatpump_values[121];
  480. # 5 - Temperature Value - ID_WEB_HauptMenuAHP_Temp
  481. $return_str .= "|".$heatpump_values[122];
  482. # 6 - Compressor1
  483. $return_str .= "|".$heatpump_values[44];
  484. # 7 - opModeHotWater
  485. $return_str .= "|".$heatpump_parameters[4];
  486. # 8 - hotWaterMonitoring
  487. $return_str .= "|".$heatpump_values[124];
  488. # 9 - hotWaterBoilerValve
  489. $return_str .= "|".$heatpump_values[38];
  490. # 10 - opModeHeating
  491. $return_str .= "|".$heatpump_parameters[3];
  492. # 11 - heatingLimit
  493. $return_str .= "|".$heatpump_parameters[699];
  494. # 12 - ambientTemperature
  495. $return_str .= "|".$heatpump_values[15];
  496. # 13 - averageAmbientTemperature
  497. $return_str .= "|".$heatpump_values[16];
  498. # 14 - hotWaterTemperature
  499. $return_str .= "|".$heatpump_values[17];
  500. # 15 - flowTemperature
  501. $return_str .= "|".$heatpump_values[10];
  502. # 16 - returnTemperature
  503. $return_str .= "|".$heatpump_values[11];
  504. # 17 - returnTemperatureTarget
  505. $return_str .= "|".$heatpump_values[12];
  506. # 18 - returnTemperatureExtern
  507. $return_str .= "|".($heatpump_visibility[24]==1 ? $heatpump_values[13] : "no");
  508. # 19 - flowRate
  509. $return_str .= "|".($heatpump_parameters[870]!=0 ? $heatpump_values[155] : "no");
  510. # 20 - firmware
  511. my $fwvalue = "";
  512. for(my $fi=81; $fi<91; $fi++) {
  513. $fwvalue .= chr($heatpump_values[$fi]) if $heatpump_values[$fi];
  514. }
  515. $return_str .= "|".$fwvalue;
  516. # 21 - thresholdHeatingLimit
  517. $return_str .= "|".$heatpump_parameters[700];
  518. # 22 - rawDeviceTimeCalc
  519. $return_str .= "|".$heatpump_values[134];
  520. # 23 - heatSourceIN
  521. $return_str .= "|".$heatpump_values[19];
  522. # 24 - heatSourceOUT
  523. $return_str .= "|".$heatpump_values[20];
  524. # 25 - hotWaterTemperatureTarget
  525. $return_str .= "|".$heatpump_values[18];
  526. # 26 - hotGasTemperature
  527. $return_str .= "|".$heatpump_values[14];
  528. # 27 - heatingSystemCircPump
  529. $return_str .= "|".$heatpump_values[39];
  530. # 28 - hotWaterCircPumpExtern
  531. $return_str .= "|". ($heatpump_visibility[57]==1 ? $heatpump_values[46] : "no");
  532. # 29 - readingFhemStartTime
  533. $return_str .= "|".$readingStartTime;
  534. # 30 - readingFhemEndTime
  535. $return_str .= "|".$readingEndTime;
  536. # 31 - typeHeatpump
  537. $return_str .= "|".$heatpump_values[78];
  538. # 32 - counterHours2ndHeatSource1
  539. $return_str .= "|". ($heatpump_visibility[84]==1 ? $heatpump_values[60] : "no");
  540. # 33 - counterHoursHeatpump
  541. $return_str .= "|". ($heatpump_visibility[87]==1 ? $heatpump_values[63] : "no");
  542. # 34 - counterHoursHeating
  543. $return_str .= "|". ($heatpump_visibility[195]==1 ? $heatpump_values[64] : "no");
  544. # 35 - counterHoursHotWater
  545. $return_str .= "|". ($heatpump_visibility[196]==1 ? $heatpump_values[65] : "no");
  546. # 36 - counterHeatQHeating
  547. $return_str .= "|". ($heatpump_visibility[0]==1 ? $heatpump_values[151] : "no");
  548. # 37 - counterHeatQHotWater
  549. $return_str .= "|". ($heatpump_visibility[1]==1 ? $heatpump_values[152] : "no");
  550. # 38 - counterHours2ndHeatSource2
  551. $return_str .= "|". ($heatpump_visibility[85]==1 ? $heatpump_values[61] : "no");
  552. # 39 - counterHours2ndHeatSource3
  553. $return_str .= "|". ($heatpump_visibility[86]==1 ? $heatpump_values[62] : "no");
  554. # 40 - opStateHeatPump2
  555. $return_str .= "|".$heatpump_values[118];
  556. # 41 - opStateHeatPump2Duration
  557. $return_str .= "|".$heatpump_values[120];
  558. # 42 - timeError0
  559. $return_str .= "|".$heatpump_values[95];
  560. # 43 - bivalentLevel
  561. $return_str .= "|".$heatpump_values[79];
  562. # 44 - Number of calculated values
  563. $return_str .= "|".$count_calc_values;
  564. # 45 - Number of set parameters
  565. $return_str .= "|".$count_set_parameter;
  566. # 46 - opStateHeating
  567. $return_str .= "|".$heatpump_values[125];
  568. # 47 - deltaHeatingReduction
  569. $return_str .= "|".$heatpump_parameters[13];
  570. # 48 - thresholdTemperatureSetBack
  571. $return_str .= "|".$heatpump_parameters[111];
  572. # 49 - hotWaterTemperatureHysterese
  573. $return_str .= "|".$heatpump_parameters[74];
  574. # 50 - solarCollectorTemperature
  575. $return_str .= "|". ($heatpump_visibility[36]==1 ? $heatpump_values[26] : "no");
  576. # 51 - solarBufferTemperature
  577. $return_str .= "|". ($heatpump_visibility[37]==1 ? $heatpump_values[27] : "no");
  578. # 52 - counterHoursSolar
  579. $return_str .= "|". ($heatpump_visibility[248]==1 ? $heatpump_values[161] : "no");
  580. # 53 - Number of visibility attributes
  581. $return_str .= "|".$countVisibAttr;
  582. # 54 - returnTemperatureSetBack
  583. $return_str .= "|".$heatpump_parameters[1];
  584. # 55 - mixer1FlowTemperature
  585. $return_str .= "|". ($heatpump_visibility[31]==1 ? $heatpump_values[21] : "no");
  586. # 56 - mixer1TargetTemperature
  587. $return_str .= "|". ($heatpump_visibility[32]==1 ? $heatpump_values[22] : "no");
  588. # 57 - mixer2FlowTemperature
  589. $return_str .= "|". ($heatpump_visibility[34]==1 ? $heatpump_values[24] : "no");
  590. # 58 - mixer2TargetTemperature
  591. $return_str .= "|". ($heatpump_visibility[35]==1 ? $heatpump_values[25] : "no");
  592. # 59 - mixer3FlowTemperature
  593. $return_str .= "|". ($heatpump_visibility[210]==1 ? $heatpump_values[137] : "no");
  594. # 60 - mixer3TargetTemperature
  595. $return_str .= "|". ($heatpump_visibility[211]==1 ? $heatpump_values[136] : "no");
  596. # 61 - hotWaterCircPumpDeaerate
  597. $return_str .= "|". ($heatpump_visibility[167]==1 ? $heatpump_parameters[684] : "no");
  598. # 62 - counterHeatQPool
  599. $return_str .= "|". ($heatpump_visibility[2]==1 ? $heatpump_values[153] : "no");
  600. # 63 - returnTemperatureTargetMin
  601. $heatpump_visibility[295]=1 unless defined($heatpump_visibility[295]);
  602. $return_str .= "|". ($heatpump_visibility[295]==1 ? $heatpump_parameters[979] : "no");
  603. # 64 - heatSourceMotor
  604. $return_str .= "|". ($heatpump_visibility[54]==1 ? $heatpump_values[43] : "no");
  605. # 65 - typeSerial
  606. $return_str .= "|";
  607. $return_str .= substr($heatpump_parameters[874],0,4)."/".substr($heatpump_parameters[874],4).= "-".sprintf("%03X",$heatpump_parameters[875])
  608. if $heatpump_parameters[874] || $heatpump_parameters[875] ;
  609. # 66 - heatSourceDefrostTimer
  610. $return_str .= "|". ($heatpump_visibility[219]==1 ? $heatpump_values[141] : "no");
  611. # 67 - defrostValve
  612. $return_str .= "|". ($heatpump_visibility[47]==1 ? $heatpump_values[37] : "no");
  613. # 68 - returnTempHyst
  614. $return_str .= "|". ($heatpump_visibility[93]==1 ? $heatpump_parameters[88] : "no");
  615. # 69 - Heating curve end point
  616. $return_str .= "|". ($heatpump_visibility[207]==1 ? $heatpump_parameters[11] : "no");
  617. # 70 - Heating curve parallel offset
  618. $return_str .= "|". ($heatpump_visibility[207]==1 ? $heatpump_parameters[12] : "no");
  619. # 71 - heatSourcedefrostAirThreshold
  620. $return_str .= "|". ($heatpump_visibility[97]==1 ? $heatpump_parameters[44] : "no");
  621. # 72 - heatSourcedefrostAirEnd
  622. $return_str .= "|". ($heatpump_visibility[105]==1 ? $heatpump_parameters[98] : "no");
  623. # 73 - analogOut4 - Voltage heating system circulation pump
  624. $return_str .= "|". ($heatpump_visibility[267]==1 ? $heatpump_values[163] : "no");
  625. # 74 - solarPump
  626. $return_str .= "|". ($heatpump_visibility[63]==1 ? $heatpump_values[52] : "no");
  627. # 75 - 2ndHeatSource1
  628. $return_str .= "|". ($heatpump_visibility[59]==1 ? $heatpump_values[48] : "no");
  629. return $return_str;
  630. }
  631. sub ########################################
  632. LUXTRONIK2_UpdateDone($)
  633. {
  634. my ($string) = @_;
  635. return unless(defined($string));
  636. my $value = "";
  637. my $state = "";
  638. my @a = split("\\|",$string);
  639. my $hash = $defs{$a[0]};
  640. my $name = $a[0];
  641. delete($hash->{helper}{RUNNING_PID});
  642. return if($hash->{helper}{DISABLED});
  643. my $cop = 0;
  644. LUXTRONIK2_Log $hash, 5, $string;
  645. #Define Status Messages
  646. my %wpOpStat1 = ( 0 => "Waermepumpe laeuft",
  647. 1 => "Waermepumpe steht",
  648. 2 => "Waermepumpe kommt",
  649. 4 => "Fehler",
  650. 5 => "Abtauen",
  651. 6 => "Warte auf LIN-Verbindung",
  652. 7 => "Verdichter heizt auf",
  653. 8 => "Pumpenvorlauf" );
  654. my %wpOpStat2 = ( 0 => "Heizbetrieb",
  655. 1 => "Keine Anforderung",
  656. 2 => "Netz Einschaltverzoegerung",
  657. 3 => "Schaltspielzeit",
  658. 4 => "EVU Sperrzeit",
  659. 5 => "Brauchwasser",
  660. 6 => "Stufe",
  661. 7 => "Abtauen",
  662. 8 => "Pumpenvorlauf",
  663. 9 => "Thermische Desinfektion",
  664. 10 => "Kuehlbetrieb",
  665. 12 => "Schwimmbad/Photovoltaik",
  666. 13 => "Heizen_Ext_En",
  667. 14 => "Brauchw_Ext_En",
  668. 16 => "Durchflussueberwachung",
  669. 17 => "Elektrische Zusatzheizung" );
  670. my %wpMode = ( 0 => "Automatik",
  671. 1 => "Zusatzheizung",
  672. 2 => "Party",
  673. 3 => "Ferien",
  674. 4 => "Aus" );
  675. my %heatingState = ( 0 => "Abgesenkt",
  676. 1 => "Normal",
  677. 3 => "Aus");
  678. my %wpType = ( 0 => "ERC", 1 => "SW1",
  679. 2 => "SW2", 3 => "WW1",
  680. 4 => "WW2", 5 => "L1I",
  681. 6 => "L2I", 7 => "L1A",
  682. 8 => "L2A", 9 => "KSW",
  683. 10 => "KLW", 11 => "SWC",
  684. 12 => "LWC", 13 => "L2G",
  685. 14 => "WZS", 15 => "L1I407",
  686. 16 => "L2I407", 17 => "L1A407",
  687. 18 => "L2A407", 19 => "L2G407",
  688. 20 => "LWC407", 21 => "L1AREV", 22 => "L2AREV", 23 => "WWC1",
  689. 24 => "WWC2", 25 => "L2G404", 26 => "WZW", 27 => "L1S",
  690. 28 => "L1H", 29 => "L2H", 30 => "WZWD", 31 => "ERC",
  691. 40 => "WWB_20", 41 => "LD5", 42 => "LD7", 43 => "SW 37_45",
  692. 44 => "SW 58_69", 45 => "SW 29_56", 46 => "LD5 (230V)",
  693. 47 => "LD7 (230 V)", 48 => "LD9", 49 => "LD5 REV",
  694. 50 => "LD7 REV", 51 => "LD5 REV 230V",
  695. 52 => "LD7 REV 230V", 53 => "LD9 REV 230V",
  696. 54 => "SW 291", 55 => "LW SEC", 56 => "HMD 2", 57 => "MSW 4",
  697. 58 => "MSW 6", 59 => "MSW 8", 60 => "MSW 10", 61 => "MSW 12",
  698. 62 => "MSW 14", 63 => "MSW 17", 64 => "MSW 19", 65 => "MSW 23",
  699. 66 => "MSW 26", 67 => "MSW 30", 68 => "MSW 4S", 69 => "MSW 6S",
  700. 70 => "MSW 8S", 71 => "MSW 10S",72 => "MSW 13S", 73 => "MSW 16S",
  701. 74 => "MSW2-6S", 75 => "MSW4-16",76 => "LD2AG", 77 => "LWD90V",
  702. 78 => "MSW3-12", 79 => "MSW3-12S");
  703. my $counterRetry = $hash->{fhem}{counterRetry};
  704. $counterRetry++;
  705. my $doStatistic = AttrVal($name,"doStatistics",0);
  706. # Error
  707. if ($a[1]==0 ) {
  708. readingsSingleUpdate($hash,"state","Error: ".$a[2],1);
  709. $counterRetry = 0;
  710. if ( $hash->{fhem}{portDefined} == 0 ) {
  711. if ($hash->{PORT} == 8888 ) {
  712. $hash->{PORT} = 8889;
  713. LUXTRONIK2_Log $name, 3, "Error when using port 8888. Changed port to 8889";
  714. }
  715. elsif ($hash->{PORT} == 8889 ) {
  716. $hash->{PORT} = 8888;
  717. LUXTRONIK2_Log $name, 3, "Error when using port 8889. Changed port to 8888";
  718. }
  719. }
  720. }
  721. # Busy, restart update
  722. elsif ($a[1]==2 ) {
  723. if ($counterRetry <=3) {
  724. InternalTimer(gettimeofday() + 5, "LUXTRONIK2_GetUpdate", $hash, 0);
  725. }
  726. else {
  727. readingsSingleUpdate($hash,"state","Error: Reading skipped after $counterRetry tries",1);
  728. LUXTRONIK2_Log $hash, 2, "Device reading skipped after $counterRetry tries with parameter change on target";
  729. }
  730. }
  731. # Update readings
  732. elsif ($a[1]==1 ) {
  733. $counterRetry = 0;
  734. readingsBeginUpdate($hash);
  735. # Temporary storage of values because needed several times
  736. my $ambientTemperature = LUXTRONIK2_CalcTemp($a[12]);
  737. my $averageAmbientTemperature = LUXTRONIK2_CalcTemp($a[13]);
  738. my $hotWaterTemperature = LUXTRONIK2_CalcTemp($a[14]);
  739. my $hotWaterTemperatureTarget = LUXTRONIK2_CalcTemp($a[25]);
  740. my $hotWaterTemperatureThreshold = LUXTRONIK2_CalcTemp($a[25] - $a[49]);
  741. my $heatSourceIN = LUXTRONIK2_CalcTemp($a[23]);
  742. my $heatSourceOUT = LUXTRONIK2_CalcTemp($a[24]);
  743. my $thresholdHeatingLimit = LUXTRONIK2_CalcTemp($a[21]);
  744. my $thresholdTemperatureSetBack = LUXTRONIK2_CalcTemp($a[48]);
  745. my $flowTemperature = LUXTRONIK2_CalcTemp($a[15]);
  746. my $returnTemperature = LUXTRONIK2_CalcTemp($a[16]);
  747. my $returnTemperatureTarget = LUXTRONIK2_CalcTemp($a[17]);
  748. my $returnTempHyst = LUXTRONIK2_CalcTemp($a[68]);
  749. my $returnTemperatureTargetMin = ($a[63] eq "no"?15:LUXTRONIK2_CalcTemp($a[63]) );
  750. my $compressor1 = $a[6]; #Ausgang Verdichter 1
  751. my $heatSourceMotor = $a[64]; #Ausgang Ventilator_BOSUP
  752. my $defrostValve = $a[67]; #AVout
  753. my $hotWaterBoilerValve = $a[9]; #BUP
  754. my $heatingSystemCircPump = $a[27]; #HUP
  755. my $opStateHeatPump3 = $a[3];
  756. my $analogOut4 = $a[73]; #Voltage heating system circulation pump
  757. my $flowRate = $a[19]; # flow rate
  758. # skips inconsistent flow rates (known problem of the used flow measurement devices)
  759. if ($flowRate !~ /no/ && $heatingSystemCircPump) {
  760. $flowRate = "inconsistent" if $flowRate == 0;
  761. }
  762. my $heatPumpPower = 0;
  763. my $heatRodPower = AttrVal($name, "heatRodElectricalPowerWatt", 0);
  764. #WM[kW] = delta_Temp [K] * Durchfluss [l/h] / ( 3.600 [kJ/kWh] / ( 4,179 [kJ/(kg*K)] (H2O Wärmekapazität bei 30 & 40°C) * 0,994 [kg/l] (H2O Dichte bei 35°C) )
  765. my $thermalPower = 0;
  766. # 0=Heizen, 5=Brauchwasser, 7=Abtauen, 16=Durchflussüberwachung
  767. if ($opStateHeatPump3 =~ /^(0|5|16)$/) {
  768. if ($flowRate !~ /no|inconsistent/) { $thermalPower = abs($flowTemperature - $returnTemperature) * $flowRate / 866.65; } #Nur bei Wärmezählern
  769. $heatPumpPower = AttrVal($name, "heatPumpElectricalPowerWatt", -1);
  770. $heatPumpPower *= (1 + ($flowTemperature-35) * AttrVal($name, "heatPumpElectricalPowerFactor", 0));
  771. }
  772. if ($flowRate !~ /no|inconsistent/) { readingsBulkUpdate( $hash, "thermalPower", sprintf "%.1f", $thermalPower); } #Nur bei Wärmezählern
  773. if ($heatPumpPower >-1 ) { readingsBulkUpdate( $hash, "heatPumpElectricalPowerEstimated", sprintf "%.0f", $heatPumpPower); }
  774. if ($heatPumpPower > 0 && $flowRate !~ /no|inconsistent/) { #Nur bei Wärmezählern
  775. $cop = $thermalPower * 1000 / $heatPumpPower;
  776. readingsBulkUpdate( $hash, "COP", sprintf "%.2f", $cop);
  777. }
  778. # if selected, do all the statistic calculations
  779. if ( $doStatistic == 1) {
  780. #LUXTRONIK2_doStatisticBoilerHeatUp $hash, $currOpHours, $currHQ, $currTemp, $opState, $target
  781. $value = LUXTRONIK2_doStatisticBoilerHeatUp ($hash, $a[35], $a[37]/10, $hotWaterTemperature, $opStateHeatPump3,$hotWaterTemperatureTarget);
  782. if ($value ne "") {
  783. readingsBulkUpdate($hash,"statBoilerGradientHeatUp",$value);
  784. LUXTRONIK2_Log $name, 3, "statBoilerGradientHeatUp set to $value";
  785. }
  786. #LUXTRONIK2_doStatisticBoilerCoolDown $hash, $time, $currTemp, $opState, $target, $threshold
  787. $value = LUXTRONIK2_doStatisticBoilerCoolDown ($hash, $a[22], $hotWaterTemperature, $opStateHeatPump3, $hotWaterTemperatureTarget, $hotWaterTemperatureThreshold);
  788. if ($value ne "") {
  789. readingsBulkUpdate($hash,"statBoilerGradientCoolDown",$value);
  790. LUXTRONIK2_Log $name, 3, "statBoilerGradientCoolDown set to $value";
  791. my @new = split / /, $value;
  792. if ( exists( $hash->{READINGS}{statBoilerGradientCoolDownMin} ) ) {
  793. my @old = split / /, $hash->{READINGS}{statBoilerGradientCoolDownMin}{VAL};
  794. if ($new[5]>6 && $new[1]>$old[1] && $new[1] < 0) {
  795. readingsBulkUpdate($hash,"statBoilerGradientCoolDownMin",$value,1);
  796. LUXTRONIK2_Log $name, 3, "statBoilerGradientCoolDownMin set to '$value'";
  797. }
  798. } elsif ($new[5]>6 && $new[1] < 0) {
  799. readingsBulkUpdate($hash,"statBoilerGradientCoolDownMin",$value,1);
  800. LUXTRONIK2_Log $name, 3, "statBoilerGradientCoolDownMin set to '$value'";
  801. }
  802. }
  803. # LUXTRONIK2_doStatisticThermalPower: $hash, $MonitoredOpState, $currOpState, $currHeatQuantity, $currOpHours, $currAmbTemp, $currHeatSourceIn, $TargetTemp, $electricalPower
  804. $value = LUXTRONIK2_doStatisticThermalPower ($hash, 5, $opStateHeatPump3, $a[37]/10, $a[35], $ambientTemperature, $heatSourceIN,$hotWaterTemperatureTarget, $heatPumpPower);
  805. if ($value ne "") { readingsBulkUpdate($hash,"statThermalPowerBoiler",$value); }
  806. $value = LUXTRONIK2_doStatisticThermalPower ($hash, 0, $opStateHeatPump3, $a[36]/10, $a[34], $ambientTemperature, $heatSourceIN, $returnTemperatureTarget, $heatPumpPower);
  807. if ($value ne "") { readingsBulkUpdate($hash,"statThermalPowerHeating",$value); }
  808. # LUXTRONIK2_doStatisticMinMax $hash, $readingName, $value
  809. LUXTRONIK2_doStatisticMinMax ( $hash, "statAmbientTemp", $ambientTemperature);
  810. }
  811. #Operating status of heat pump
  812. my $opStateHeatPump1 = $wpOpStat1{$a[2]}; ##############
  813. $opStateHeatPump1 = "unbekannt (".$a[2].")" unless $opStateHeatPump1;
  814. readingsBulkUpdate($hash,"opStateHeatPump1",$opStateHeatPump1);
  815. my $opStateHeatPump2 = "unknown ($a[40])"; ##############
  816. my $prefix = "";
  817. if ($a[40] == 0 || $a[40] == 2) { $prefix = "seit ";}
  818. elsif ($a[40] == 1) { $prefix = "in ";}
  819. if ($a[40] == 2) { #Sonderbehandlung bei WP-Fehlern
  820. $opStateHeatPump2 = $prefix . strftime "%d.%m.%Y %H:%M:%S", localtime($a[42]);
  821. }
  822. else {
  823. $opStateHeatPump2 = $prefix . LUXTRONIK2_FormatDuration($a[41]);
  824. }
  825. readingsBulkUpdate($hash,"opStateHeatPump2",$opStateHeatPump2);
  826. my $opStateHeatPump3Txt = $wpOpStat2{$opStateHeatPump3}; ##############
  827. # refine text of third state
  828. if ($opStateHeatPump3==6) {
  829. $opStateHeatPump3Txt = "Stufe ".$a[4]." ".LUXTRONIK2_CalcTemp($a[5])." C ";
  830. }
  831. elsif ($opStateHeatPump3==7) {
  832. if ( $defrostValve==1 ) {$opStateHeatPump3Txt = "Abtauen (Kreisumkehr)";}
  833. elsif ( $compressor1==0 && $heatSourceMotor==1 ) {$opStateHeatPump3Txt = "Luftabtauen";}
  834. else {$opStateHeatPump3Txt = "Abtauen";}
  835. }
  836. $opStateHeatPump3Txt = "unbekannt (".$opStateHeatPump3.")" unless $opStateHeatPump3Txt;
  837. readingsBulkUpdate($hash,"opStateHeatPump3",$opStateHeatPump3Txt);
  838. # Hot water operating mode
  839. $value = $wpMode{$a[7]};
  840. $value = "unbekannt (".$a[7].")" unless $value;
  841. readingsBulkUpdate($hash,"opModeHotWater",$value);
  842. # opStateHotWater
  843. if ($a[8]==0) {$value="Sperrzeit";}
  844. elsif ($a[8]==1 && $hotWaterBoilerValve==1) {$value="Aufheizen";}
  845. elsif ($a[8]==1 && $hotWaterBoilerValve==0) {$value="Temp. OK";}
  846. elsif ($a[8]==3) {$value="Aus";}
  847. else {$value = "unbekannt (".$a[8]."/".$hotWaterBoilerValve.")";}
  848. readingsBulkUpdate($hash,"opStateHotWater",$value);
  849. # Heating operating mode
  850. $value = $wpMode{$a[10]};
  851. $value = "unbekannt (".$a[10].")" unless $value;
  852. readingsBulkUpdate($hash,"opModeHeating",$value);
  853. # Heating operating state
  854. # Consider also heating limit
  855. if ($a[10] == 0 && $a[11] == 1
  856. && $averageAmbientTemperature >= $thresholdHeatingLimit
  857. && ($returnTemperatureTarget eq $returnTemperatureTargetMin || $returnTemperatureTarget == 20 && $ambientTemperature<10)
  858. ) {
  859. if ($ambientTemperature>=10 ) {
  860. $value = "Heizgrenze (Soll ".$returnTemperatureTargetMin." C)";
  861. }
  862. else {
  863. $value = "Frostschutz (Soll 20 C)";
  864. }
  865. } else {
  866. $value = $heatingState{$a[46]};
  867. $value = "unbekannt (".$a[46].")" unless $value;
  868. # Consider heating reduction limit
  869. if ($a[46] == 0) {
  870. if ($thresholdTemperatureSetBack <= $ambientTemperature) {
  871. $value .= " ".LUXTRONIK2_CalcTemp($a[47])." C"; #° &deg; &#176; &#x00B0;
  872. } else {
  873. $value = "Normal da < ".$thresholdTemperatureSetBack." C";
  874. }
  875. }
  876. }
  877. readingsBulkUpdate($hash,"opStateHeating",$value);
  878. # Defrost times
  879. if ($compressor1 != $heatSourceMotor) {
  880. if ($hash->{fhem}{defrost}{mode} eq "none") {
  881. $hash->{fhem}{defrost}{startTime} = time();
  882. $hash->{fhem}{defrost}{mode} = "air" if $heatSourceMotor;
  883. $hash->{fhem}{defrost}{mode} = "reverse" unless $heatSourceMotor;
  884. $hash->{fhem}{defrost}{ambStart} = $ambientTemperature;
  885. $hash->{fhem}{defrost}{hsInStart} = $heatSourceIN;
  886. $hash->{fhem}{defrost}{hsOutStart} = $heatSourceOUT;
  887. }
  888. $hash->{fhem}{defrost}{amb} = $ambientTemperature;
  889. $hash->{fhem}{defrost}{hsIn} = $heatSourceIN;
  890. $hash->{fhem}{defrost}{hsOut} = $heatSourceOUT;
  891. }
  892. elsif ( $hash->{fhem}{defrost}{mode} ne "none" ) {
  893. my $value = "Mode: " . $hash->{fhem}{defrost}{mode} . " Time: ";
  894. $value .= strftime ( "%M:%S", localtime( time() - $hash->{fhem}{defrost}{startTime} ) );
  895. $value .= " Amb: ".$hash->{fhem}{defrost}{ambStart} . " - ". $hash->{fhem}{defrost}{amb};
  896. $value .= " hsIN: ".$hash->{fhem}{defrost}{hsInStart} . " - ". $hash->{fhem}{defrost}{hsIn};
  897. #$value .= " hsOUT: ".$hash->{fhem}{defrost}{hsOutStart} . " - ". $heatSourceOUT;
  898. readingsBulkUpdate( $hash, "heatSourceDefrostLast", $value);
  899. $hash->{fhem}{defrost}{mode} = "none";
  900. # 16 => "Durchflussueberwachung"
  901. if ($opStateHeatPump3 == 16) {
  902. readingsBulkUpdate( $hash, "heatSourceDefrostLastTimeout", "Amb: ".$hash->{fhem}{defrost}{amb}." hsIN: ".$hash->{fhem}{defrost}{hsIn}." hsOUT: ".$hash->{fhem}{defrost}{hsOut});
  903. }
  904. }
  905. # Determine last real heatings system return temperature, circulation needs to run at least 3 min or has been stopped less than 2 min ago
  906. $hash->{fhem}{hotWaterLastRun} = time() if $hotWaterBoilerValve;
  907. $hash->{fhem}{heatingPumpLastStop} = time() if !$heatingSystemCircPump;
  908. $hash->{fhem}{heatingPumpLastRun} = time() if $heatingSystemCircPump;
  909. readingsBulkUpdate( $hash, "returnTemperatureHeating", $returnTemperature)
  910. if ( $heatingSystemCircPump && !$hotWaterBoilerValve
  911. && time() - $hash->{fhem}{hotWaterLastRun} >= 180
  912. && time() - $hash->{fhem}{heatingPumpLastStop} >= 120);
  913. # || ( !$heatingSystemCircPump && !$hotWaterBoilerValve
  914. # && time() - $hash->{fhem}{hotWaterLastRun} >= 180
  915. # && time() - $hash->{fhem}{heatingPumpLastRun} < $hash->{INTERVAL}+10);
  916. # Device and reading times, delays and durations
  917. $value = strftime "%Y-%m-%d %H:%M:%S", localtime($a[22]);
  918. readingsBulkUpdate($hash, "deviceTimeCalc", $value);
  919. my $delayDeviceTimeCalc=sprintf("%.0f",$a[29]-$a[22]);
  920. readingsBulkUpdate($hash, "delayDeviceTimeCalc", $delayDeviceTimeCalc);
  921. my $durationFetchReadings = sprintf("%.3f",$a[30]-$a[29]);
  922. readingsBulkUpdate($hash, "durationFetchReadings", $durationFetchReadings);
  923. #Remember min and max reading durations, will be reset when initializing the device
  924. if ($hash->{fhem}{durationFetchReadingsMin} == 0 || $hash->{fhem}{durationFetchReadingsMin} > $durationFetchReadings) {
  925. $hash->{fhem}{durationFetchReadingsMin} = $durationFetchReadings;
  926. }
  927. if ($hash->{fhem}{durationFetchReadingsMax} < $durationFetchReadings) {
  928. $hash->{fhem}{durationFetchReadingsMax} = $durationFetchReadings;
  929. }
  930. # Temperatures and flow rate
  931. readingsBulkUpdate( $hash, "ambientTemperature", $ambientTemperature);
  932. readingsBulkUpdate( $hash, "averageAmbientTemperature", $averageAmbientTemperature);
  933. readingsBulkUpdate( $hash, "heatingLimit",$a[11]?"on":"off");
  934. readingsBulkUpdate( $hash, "thresholdHeatingLimit", $thresholdHeatingLimit);
  935. readingsBulkUpdate( $hash, "thresholdTemperatureSetBack", $thresholdTemperatureSetBack);
  936. if ($a[69] !~ /no/) {readingsBulkUpdate( $hash, "heatingCurveEndPoint",LUXTRONIK2_CalcTemp($a[69]));}
  937. if ($a[70] !~ /no/) {readingsBulkUpdate( $hash, "heatingCurveOffset",LUXTRONIK2_CalcTemp($a[70]));}
  938. readingsBulkUpdate( $hash, "hotWaterTemperature", $hotWaterTemperature);
  939. readingsBulkUpdate( $hash, "hotWaterTemperatureTarget",$hotWaterTemperatureTarget);
  940. readingsBulkUpdate( $hash, "flowTemperature", $flowTemperature);
  941. readingsBulkUpdate( $hash, "returnTemperature", $returnTemperature);
  942. readingsBulkUpdate( $hash, "returnTemperatureTarget", $returnTemperatureTarget);
  943. readingsBulkUpdate( $hash, "returnTemperatureHyst", $returnTempHyst);
  944. readingsBulkUpdate( $hash, "returnTemperatureSetBack",LUXTRONIK2_CalcTemp($a[54]));
  945. if ($a[18] !~ /no/) {readingsBulkUpdate( $hash, "returnTemperatureExtern",LUXTRONIK2_CalcTemp($a[18]));}
  946. if ($analogOut4 !~ /no/) {readingsBulkUpdate( $hash, "heatingSystemCircPumpVoltage", $analogOut4/100);}
  947. if ($flowRate !~ /no|inconsistent/ ) { readingsBulkUpdate( $hash, "flowRate",$flowRate); }
  948. readingsBulkUpdate( $hash, "heatSourceIN", $heatSourceIN );
  949. readingsBulkUpdate( $hash, "heatSourceOUT", $heatSourceOUT );
  950. readingsBulkUpdate( $hash, "heatSourceMotor", $heatSourceMotor?"on":"off");
  951. if ($a[71] !~ /no/) {readingsBulkUpdate( $hash, "heatSourceDefrostAirThreshold",LUXTRONIK2_CalcTemp($a[71]));}
  952. if ($a[72] !~ /no/) {readingsBulkUpdate( $hash, "heatSourceDefrostAirEnd",LUXTRONIK2_CalcTemp($a[72]));}
  953. if ($a[66] !~ /no/) {readingsBulkUpdate( $hash, "heatSourceDefrostTimer",$a[66]);}
  954. readingsBulkUpdate( $hash, "compressor1",$compressor1?"on":"off");
  955. readingsBulkUpdate( $hash, "hotGasTemperature",LUXTRONIK2_CalcTemp($a[26]));
  956. if ($a[55] !~ /no/) {readingsBulkUpdate( $hash, "mixer1FlowTemperature",LUXTRONIK2_CalcTemp($a[55]));}
  957. if ($a[56] !~ /no/) {readingsBulkUpdate( $hash, "mixer1TargetTemperature",LUXTRONIK2_CalcTemp($a[56]));}
  958. if ($a[57] !~ /no/) {readingsBulkUpdate( $hash, "mixer2FlowTemperature",LUXTRONIK2_CalcTemp($a[57]));}
  959. if ($a[58] !~ /no/) {readingsBulkUpdate( $hash, "mixer2TargetTemperature",LUXTRONIK2_CalcTemp($a[58]));}
  960. if ($a[59] !~ /no/) {readingsBulkUpdate( $hash, "mixer3FlowTemperature",LUXTRONIK2_CalcTemp($a[59]));}
  961. if ($a[60] !~ /no/) {readingsBulkUpdate( $hash, "mixer3TargetTemperature",LUXTRONIK2_CalcTemp($a[60]));}
  962. # Operating hours (seconds->hours) and heat quantities
  963. # LUXTRONIK2_storeReadings: $hash, $readingName, $value, $factor, $doStatistic, $electricalPower
  964. LUXTRONIK2_storeReadings $hash, "counterHours2ndHeatSource1", $a[32], 3600, $doStatistic, $heatRodPower;
  965. LUXTRONIK2_storeReadings $hash, "counterHours2ndHeatSource2", $a[38], 3600, $doStatistic, $heatRodPower;
  966. LUXTRONIK2_storeReadings $hash, "counterHours2ndHeatSource3", $a[39], 3600, $doStatistic, $heatRodPower;
  967. LUXTRONIK2_storeReadings $hash, "counterHoursHeatPump", $a[33], 3600, $doStatistic, $heatPumpPower;
  968. LUXTRONIK2_storeReadings $hash, "counterHoursHeating", $a[34], 3600, $doStatistic, $heatPumpPower;
  969. LUXTRONIK2_storeReadings $hash, "counterHoursHotWater", $a[35], 3600, $doStatistic, $heatPumpPower;
  970. my $heatQTotal = 0 ;
  971. if ($a[36] !~ /no/) {
  972. LUXTRONIK2_storeReadings $hash, "counterHeatQHeating", $a[36], 10, ($flowRate !~ /no/ ? $doStatistic : 0), -1;
  973. $heatQTotal += $a[36];
  974. }
  975. if ($a[37] !~ /no/) {
  976. LUXTRONIK2_storeReadings $hash, "counterHeatQHotWater", $a[37], 10, ($flowRate !~ /no/ ? $doStatistic : 0), -1;
  977. $heatQTotal += $a[37];
  978. }
  979. if ($a[62] !~ /no/) {
  980. LUXTRONIK2_storeReadings $hash, "counterHeatQPool", $a[62], 10, ($flowRate !~ /no/ ? $doStatistic : 0), -1;
  981. $heatQTotal += $a[62];
  982. }
  983. LUXTRONIK2_storeReadings $hash, "counterHeatQTotal", $heatQTotal, 10, ($flowRate !~ /no/ ? $doStatistic : 0), -1;
  984. # Input / Output status
  985. readingsBulkUpdate($hash,"heatingSystemCircPump",$heatingSystemCircPump?"on":"off");
  986. readingsBulkUpdate($hash,"hotWaterCircPumpExtern",$a[28]?"on":"off");
  987. readingsBulkUpdate($hash,"hotWaterSwitchingValve",$hotWaterBoilerValve?"on":"off");
  988. if ($a[74] !~ /no/) { readingsBulkUpdate($hash,"solarPump",$a[74]?"on":"off"); }
  989. if ($a[75] !~ /no/) { readingsBulkUpdate($hash,"2ndHeatSource1",$a[75]?"on":"off"); }
  990. # Deaerate Function
  991. readingsBulkUpdate( $hash, "hotWaterCircPumpDeaerate",$a[61]?"on":"off") unless $a[61] eq "no";
  992. # bivalentLevel
  993. readingsBulkUpdate($hash,"bivalentLevel",$a[43]);
  994. # Firmware
  995. my $firmware = $a[20];
  996. readingsBulkUpdate($hash,"firmware",$firmware);
  997. my $firmwareCheck = LUXTRONIK2_checkFirmware($firmware);
  998. # if unknown firmware, ask at each startup to inform comunity
  999. if ($hash->{fhem}{alertFirmware} != 1 && $firmwareCheck eq "fwNotTested") {
  1000. $hash->{fhem}{alertFirmware} = 1;
  1001. LUXTRONIK2_Log $hash, 2, "Alert: Host uses untested Firmware '$a[20]'. Please inform FHEM comunity about compatibility.";
  1002. }
  1003. # Type of Heatpump
  1004. $value = $wpType{$a[31]};
  1005. $value = "unbekannt (".$a[31].")" unless $value;
  1006. readingsBulkUpdate($hash,"typeHeatpump",$value);
  1007. readingsBulkUpdate($hash,"typeSerial",$a[65]) if $a[65] ne "";
  1008. # Solar
  1009. if ($a[50] !~ /no/) {readingsBulkUpdate($hash, "solarCollectorTemperature", LUXTRONIK2_CalcTemp($a[50]));}
  1010. if ($a[51] !~ /no/) {readingsBulkUpdate($hash, "solarBufferTemperature", LUXTRONIK2_CalcTemp($a[51]));}
  1011. if ($a[52] !~ /no/) {readingsBulkUpdate($hash, "counterHoursSolar", sprintf("%.1f", $a[52]/3600));}
  1012. # HTML for floorplan
  1013. if(AttrVal($name, "statusHTML", "none") ne "none") {
  1014. $value = ""; #"<div class=fp_" . $a[0] . "_title>" . $a[0] . "</div> \n";
  1015. $value .= "$opStateHeatPump1<br>\n";
  1016. $value .= "$opStateHeatPump2<br>\n";
  1017. $value .= "$opStateHeatPump3Txt<br>\n";
  1018. $value .= "Brauchwasser: ".$hotWaterTemperature."&deg;C";
  1019. readingsBulkUpdate($hash,"floorplanHTML",$value);
  1020. }
  1021. # State update
  1022. $value = "$opStateHeatPump1 $opStateHeatPump2 - $opStateHeatPump3Txt";
  1023. if ($thermalPower != 0) {
  1024. $value .= sprintf (" (%.1f kW", $thermalPower);
  1025. if ($heatPumpPower>0) {$value .= sprintf (", COP: %.2f", $cop);}
  1026. $value .= ")"; }
  1027. readingsBulkUpdate($hash, "state", $value);
  1028. # Special readings
  1029. # $compressor1 = $a[6]; #Ausgang Verdichter 1
  1030. # $heatSourceMotor = $a[64]; #Ausgang Ventilator_BOSUP
  1031. # $defrostValve = $a[67]; #AVout
  1032. # $hotWaterBoilerValve = $a[9]; #BUP
  1033. # $heatingSystemCircPump = $a[27]; #HUP
  1034. # 0=Heizen, 1=keine Anforderung, 3=Schaltspielzeit, 5=Brauchwasser, 7=Abtauen, 16=Durchflussüberwachung
  1035. my $lastHeatingCycle = ReadingsVal($name, "heatingCycle", "");
  1036. if ( $opStateHeatPump3 == 0 ) {
  1037. readingsBulkUpdate($hash, "heatingCycle", "running");
  1038. }
  1039. elsif ( $opStateHeatPump3 > 1 && $lastHeatingCycle eq "running") {
  1040. readingsBulkUpdate($hash, "heatingCycle", "paused");
  1041. }
  1042. elsif ( $opStateHeatPump3 == 1 && $lastHeatingCycle eq "running") {
  1043. readingsBulkUpdate($hash, "heatingCycle", "finished");
  1044. }
  1045. elsif ( $opStateHeatPump3 =~ /1|3/ && $lastHeatingCycle ne "finished" && $returnTemperature-$returnTemperatureTarget >= $returnTempHyst ) {
  1046. readingsBulkUpdate($hash, "heatingCycle", "finished");
  1047. }
  1048. elsif ( $opStateHeatPump3 == 1 && $lastHeatingCycle eq "paused") {
  1049. readingsBulkUpdate($hash, "heatingCycle", "discontinued");
  1050. }
  1051. readingsEndUpdate($hash,1);
  1052. $hash->{helper}{fetched_calc_values} = $a[44];
  1053. $hash->{helper}{fetched_parameters} = $a[45];
  1054. $hash->{helper}{fetched_visib_attr} = $a[53];
  1055. ############################
  1056. #Auto Synchronize Device Clock
  1057. my $autoSynchClock = AttrVal($name, "autoSynchClock", 0);
  1058. $autoSynchClock = 10 unless ($autoSynchClock >= 10 || $autoSynchClock == 0);
  1059. $autoSynchClock = 600 unless $autoSynchClock <= 600;
  1060. if ($autoSynchClock != 0 and abs($delayDeviceTimeCalc) > $autoSynchClock ) {
  1061. LUXTRONIK2_Log $name, 3, "autoSynchClock triggered (delayDeviceTimeCalc ".abs($delayDeviceTimeCalc)." > $autoSynchClock).";
  1062. # Firmware not tested and Firmware Check not ignored
  1063. if ($firmwareCheck eq "fwNotTested" && AttrVal($name, "ignoreFirmwareCheck", 0)!= 1) {
  1064. LUXTRONIK2_Log $name, 1, "Host firmware '$firmware' not tested for clock synchronization. To test set 'ignoreFirmwareCheck' to 1.";
  1065. $attr{$name}{autoSynchClock} = 0;
  1066. LUXTRONIK2_Log $name, 3, "Attribute 'autoSynchClock' set to 0.";
  1067. #Firmware not compatible
  1068. } elsif ($firmwareCheck eq "fwNotCompatible") {
  1069. LUXTRONIK2_Log $name, 1, "Host firmware '$firmware' not compatible for host clock synchronization.";
  1070. $attr{$name}{autoSynchClock} = 0;
  1071. LUXTRONIK2_Log $name, 3, "Attribute 'autoSynchClock' set to 0.";
  1072. #Firmware OK -> Synchronize Clock
  1073. } else {
  1074. $value = LUXTRONIK2_synchronizeClock($hash, 600);
  1075. LUXTRONIK2_Log $hash, 3, $value;
  1076. }
  1077. }
  1078. #End of Auto Synchronize Device Clock
  1079. ############################
  1080. }
  1081. else {
  1082. LUXTRONIK2_Log $hash, 5, "Status = $a[1]";
  1083. }
  1084. $hash->{fhem}{counterRetry} = $counterRetry;
  1085. }
  1086. sub ########################################
  1087. LUXTRONIK2_UpdateAborted($)
  1088. {
  1089. my ($hash) = @_;
  1090. delete($hash->{helper}{RUNNING_PID});
  1091. my $name = $hash->{NAME};
  1092. my $host = $hash->{HOST};
  1093. LUXTRONIK2_Log $hash, 1, "Timeout when connecting to host $host";
  1094. }
  1095. sub ########################################
  1096. LUXTRONIK2_CalcTemp($)
  1097. {
  1098. my ($temp) = @_;
  1099. #change unsigned into signed
  1100. if ($temp > 2147483648) {$temp = $temp-4294967296;}
  1101. $temp /= 10;
  1102. return sprintf ("%.1f", $temp);
  1103. }
  1104. ########################################
  1105. sub LUXTRONIK2_FormatDuration($)
  1106. {
  1107. my ($value) = @_;
  1108. my $returnstr;
  1109. $returnstr = sprintf "%01dd ", int($value/86400)
  1110. if $value >= 86400;
  1111. $value %= 86400;
  1112. $returnstr .= sprintf "%02d:", int($value/3600);
  1113. $value %= 3600;
  1114. $returnstr .= sprintf "%02d:", int($value/60);
  1115. $value %= 60;
  1116. $returnstr .= sprintf "%02d", $value;
  1117. return $returnstr;
  1118. }
  1119. ########################################
  1120. sub LUXTRONIK2_SetParameter ($$$)
  1121. {
  1122. my ($hash, $parameterName, $realValue) = @_;
  1123. my $setParameter = 0;
  1124. my $setValue = 0;
  1125. my $result;
  1126. my $buffer;
  1127. my $host = $hash->{HOST};
  1128. my $name = $hash->{NAME};
  1129. my %opMode = ( "Auto" => 0,
  1130. "Party" => 2,
  1131. "Off" => 4);
  1132. if(AttrVal($name, "allowSetParameter", 0) != 1) {
  1133. return $name." Error: Setting of parameters not allowed. Please set attribut 'allowSetParameter' to 1";
  1134. }
  1135. if ($parameterName eq "hotWaterTemperatureTarget") {
  1136. #parameter number
  1137. $setParameter = 2;
  1138. #limit temperature range
  1139. $realValue = 30 if( $realValue < 30 );
  1140. $realValue = 65 if( $realValue > 65 );
  1141. #Allow only integer temperatures with decimal .1
  1142. $setValue = int($realValue * 10);
  1143. $realValue = $setValue / 10;
  1144. }
  1145. elsif ($parameterName eq "heatingCurveEndPoint") {
  1146. #parameter number
  1147. $setParameter = 11;
  1148. #limit temperature range
  1149. $realValue = 20 if( $realValue < 20.0 );
  1150. $realValue = 70 if( $realValue > 70.0 );
  1151. #Allow only integer temperatures
  1152. $setValue = int($realValue * 10);
  1153. $realValue = $setValue / 10;
  1154. }
  1155. elsif ($parameterName eq "heatingCurveOffset") {
  1156. #parameter number
  1157. $setParameter = 12;
  1158. #limit temperature range
  1159. $realValue = 5 if( $realValue < 5.0 );
  1160. $realValue = 35 if( $realValue > 35.0 );
  1161. #Allow only integer temperatures
  1162. $setValue = int($realValue * 10);
  1163. $realValue = $setValue / 10;
  1164. }
  1165. elsif ($parameterName eq "heatSourceDefrostAirEnd") {
  1166. #parameter number
  1167. $setParameter = 98;
  1168. #limit temperature range
  1169. $realValue = 1 if( $realValue < 1.0 );
  1170. $realValue = 24 if( $realValue > 24.0 );
  1171. #Allow only integer temperatures
  1172. $setValue = int($realValue * 10);
  1173. $realValue = $setValue / 10;
  1174. }
  1175. elsif ($parameterName eq "heatSourceDefrostAirThreshold") {
  1176. #parameter number
  1177. $setParameter = 44;
  1178. #limit temperature range
  1179. $realValue = 1.5 if( $realValue < 1.5 );
  1180. $realValue = 20 if( $realValue > 20.0 );
  1181. #Allow only integer temperatures
  1182. $setValue = int($realValue * 10);
  1183. $realValue = $setValue / 10;
  1184. }
  1185. elsif ($parameterName eq "opModeHotWater") {
  1186. if (! exists($opMode{$realValue})) {
  1187. return "$name Error: Wrong parameter given for opModeHotWater, use Automatik,Party,Off"
  1188. }
  1189. $setParameter = 4;
  1190. $setValue = $opMode{$realValue};
  1191. }
  1192. elsif ($parameterName eq "returnTemperatureHyst") {
  1193. #parameter number
  1194. $setParameter = 88;
  1195. #limit temperature range
  1196. $realValue = 0.5 if( $realValue < 0.5 );
  1197. $realValue = 3.0 if( $realValue > 3.0 );
  1198. #Allow only temperatures with decimal .1
  1199. $setValue = int($realValue * 10);
  1200. $realValue = $setValue / 10;
  1201. }
  1202. elsif ($parameterName eq "returnTemperatureSetBack") {
  1203. #parameter number
  1204. $setParameter = 1;
  1205. #limit temperature range
  1206. $realValue = -5 if( $realValue < -5 );
  1207. $realValue = 5 if( $realValue > 5 );
  1208. #Allow only temperatures with decimal .1
  1209. $setValue = int($realValue * 10);
  1210. $realValue = $setValue / 10;
  1211. }
  1212. elsif ($parameterName eq "hotWaterCircPumpDeaerate") { #isVisible(167)
  1213. $setParameter = 684;
  1214. $setValue = $realValue eq "on" ? 1 : 0;
  1215. }
  1216. elsif ($parameterName eq "runDeaerate") {
  1217. $setParameter = 158;
  1218. $setValue = $realValue;
  1219. }
  1220. else {
  1221. return "$name LUXTRONIK2_SetParameter-Error: unknown parameter $parameterName";
  1222. }
  1223. ############################
  1224. # Send new parameter to host
  1225. ############################
  1226. if ($setParameter != 0) {
  1227. LUXTRONIK2_Log $name, 5, "Opening connection to host ".$host;
  1228. my $socket = new IO::Socket::INET ( PeerAddr => $host,
  1229. PeerPort => 8888,
  1230. Proto => 'tcp'
  1231. );
  1232. # Socket error
  1233. if (!$socket) {
  1234. LUXTRONIK2_Log $name, 1, "Could not open connection to host ".$host;
  1235. return "$name Error: Could not open connection to host ".$host;
  1236. }
  1237. $socket->autoflush(1);
  1238. LUXTRONIK2_Log $name, 5, "Set parameter $parameterName ($setParameter) = $realValue ($setValue)";
  1239. $socket->send( pack( "N3", (3002, $setParameter, $setValue) ) );
  1240. LUXTRONIK2_Log $name, 5, "Receive confirmation";
  1241. #read first 4 bytes of response -> should be request_echo = 3002
  1242. $socket->recv($buffer,4, MSG_WAITALL );
  1243. $result = unpack("N", $buffer);
  1244. if($result != 3002) {
  1245. LUXTRONIK2_Log $name, 2, "Set parameter $parameterName - wrong echo of request: $result instead of 3002";
  1246. $socket->close();
  1247. return "$name Error: Host did not confirm parameter setting";
  1248. }
  1249. #Read next 4 bytes of response -> should be setParameter
  1250. $socket->recv($buffer,4, MSG_WAITALL );
  1251. $result = unpack("N", $buffer);
  1252. if($result !=$setParameter) {
  1253. LUXTRONIK2_Log $name, 2, "Set parameter $parameterName - missing confirmation: $result instead of $setParameter";
  1254. $socket->close();
  1255. return "$name Error: Host did not confirm parameter setting";
  1256. }
  1257. LUXTRONIK2_Log $name, 5, "Parameter setting confirmed";
  1258. $socket->close();
  1259. readingsSingleUpdate($hash,$parameterName,$realValue,0) unless $parameterName eq "runDeaerate";
  1260. return undef;
  1261. }
  1262. }
  1263. ########################################
  1264. sub LUXTRONIK2_synchronizeClock (@)
  1265. {
  1266. my ($hash,$maxDelta) = @_;
  1267. my $host = $hash->{HOST};
  1268. my $name = $hash->{NAME};
  1269. my $delay = 0;
  1270. my $returnStr = "";
  1271. $maxDelta = 60 unless defined $maxDelta;
  1272. $maxDelta = 60 unless $maxDelta >= 0;
  1273. $maxDelta = 600 unless $maxDelta <= 600;
  1274. LUXTRONIK2_Log $name, 5, "Open telnet connection to $host";
  1275. my $telnet = new Net::Telnet ( host=>$host, port => 23, timeout=>10, errmode=>'return');
  1276. if (!$telnet) {
  1277. my $msg = "Could not open telnet connection to $host: $!";
  1278. LUXTRONIK2_Log $name, 1, $msg;
  1279. return "$name synchronizeDeviceClock-Error: ".$msg;
  1280. }
  1281. LUXTRONIK2_Log $name, 5, "Log into $host";
  1282. if (!$telnet->login('root', '')) {
  1283. LUXTRONIK2_Log $name, 1, $telnet->errmsg;
  1284. return "$name synchronizeDeviceClock-Error: ".$telnet->errmsg;
  1285. }
  1286. LUXTRONIK2_Log $name, 5, "Read current time of host";
  1287. my @output = $telnet->cmd('date +%s');
  1288. $delay = sprintf("%.1f",time() - $output[0]);
  1289. LUXTRONIK2_Log $name, 5, "Current time is ".localtime($output[0])." Delay is $delay seconds.";
  1290. if (abs($delay)>$maxDelta && $maxDelta!=0) {
  1291. $returnStr = "Do not dare to synchronize. Device clock of host $host differs by $delay seconds (max. is $maxDelta).";
  1292. } elsif ($delay == 0) {
  1293. $returnStr = "Internal clock of host $host has no delay. -> not synchronized";
  1294. } else {
  1295. my $newTime = strftime "%m%d%H%M%Y.%S", localtime();
  1296. LUXTRONIK2_Log $name, 5, "Run command 'date ".$newTime."'";
  1297. @output=$telnet->cmd('date '.$newTime);
  1298. $returnStr = "Internal clock of host $host corrected by $delay seconds. -> ".$output[0];
  1299. readingsSingleUpdate($hash,"deviceTimeLastSync",TimeNow,1);
  1300. }
  1301. LUXTRONIK2_Log $name, 5, "Close telnet connection.";
  1302. $telnet->close;
  1303. return $returnStr;
  1304. }
  1305. ########################################
  1306. sub LUXTRONIK2_boostHotWater_Start ($$)
  1307. { my ($hash, $temperature) = @_;
  1308. my $name = $hash->{NAME};
  1309. return "boostHotWater not implemented yet";
  1310. return "$temperature is not a number." if defined $temperature && $temperature !~ /^\d*\.?\d*$/;
  1311. my $currTarget = $hash->{READINGS}{hotWaterTemperatureTarget}{VAL};
  1312. return "Could not determine current hotWaterTemperatureTarget." unless defined $currTarget;
  1313. readingsSingleUpdate($hash,"bhwLastTarget",$currTarget, 0) unless $hash->{READINGS}{bhwLastTarget}{VAL};
  1314. my $currMode = $hash->{READINGS}{opModeHotWater}{VAL};
  1315. return "Could not determine current opModeHotWater." unless defined $currMode;
  1316. readingsSingleUpdate($hash,"bhwLastMode",$currMode, 0) unless $hash->{READINGS}{bhwLastMode}{VAL};
  1317. my $currState = $hash->{READINGS}{opStateHotWater}{VAL};
  1318. return "Could not determine current opStateHotWater." unless defined $currState;
  1319. $hash->{boostHotWater} = 1;
  1320. if ( defined $temperature ) {
  1321. LUXTRONIK2_Log $name, 4, "set 'hotWaterTemperatureTarget' temporarly to ".$temperature;
  1322. LUXTRONIK2_SetParameter($hash, "hotWaterTemperatureTarget", $temperature);
  1323. }
  1324. if ( $currState !~ /Aufheizen|Temp. OK/) {
  1325. LUXTRONIK2_Log $name, 4, "set 'opModeHotWater' temporarly to 'Party'";
  1326. LUXTRONIK2_SetParameter($hash, "opModeHotWater", "Party");
  1327. }
  1328. }
  1329. ########################################
  1330. sub LUXTRONIK2_calcHeatingCurveParameter ($$$$$)
  1331. { my ($hash, $aussen_1, $rtSoll_1, $aussen_2, $rtSoll_2) = @_;
  1332. if ($aussen_1 > $aussen_2) {
  1333. my $temp= $aussen_1;
  1334. $aussen_1=$aussen_2;
  1335. $aussen_2=$temp;
  1336. $temp= $rtSoll_1;
  1337. $rtSoll_1=$rtSoll_2;
  1338. $rtSoll_2=$temp;
  1339. }
  1340. my $endPoint_Ist = $hash->{READINGS}{heatingCurveEndPoint}{VAL};
  1341. my $endPoint = $endPoint_Ist;
  1342. my $offset_Ist = $hash->{READINGS}{heatingCurveOffset}{VAL};
  1343. my $offset = $offset_Ist;
  1344. my $rtIst_1 = LUXTRONIK2_getHeatingCurveReturnTemperature ( $hash, $aussen_1, $endPoint, $offset);
  1345. my $rtIst_2 = LUXTRONIK2_getHeatingCurveReturnTemperature ( $hash, $aussen_2, $endPoint, $offset);
  1346. my $delta_1; my $delta_2;
  1347. my $msg; my $i;
  1348. #get Heizung heatingCurveParameter 0 27 10 25
  1349. for ( $i=0; $i<1000; $i++ ) {
  1350. $delta_1 = LUXTRONIK2_getHeatingCurveReturnTemperature ( $hash, $aussen_1, $endPoint, $offset) - $rtSoll_1;
  1351. $delta_1 = int(10.0 * $delta_1 + 0.5) / 10.0;
  1352. $delta_2 = LUXTRONIK2_getHeatingCurveReturnTemperature ( $hash, $aussen_2, $endPoint, $offset) - $rtSoll_2;
  1353. $delta_2 = int(10.0 * $delta_2 + 0.5) / 10.0;
  1354. $msg = "Calculate loop $i: hcEndPoint=$endPoint, hcOffset=$offset, delta($aussen_1)=$delta_1, delta($aussen_2)=$delta_2)\n";
  1355. LUXTRONIK2_Log $hash, 4, $msg;
  1356. last if $delta_1 == 0 && $delta_2 == 0;
  1357. if ($delta_2 > 0) {
  1358. $offset -= 0.1;
  1359. }
  1360. elsif ($delta_2 < 0) {
  1361. $offset += 0.1;
  1362. }
  1363. elsif ($delta_1 > 0) {
  1364. $endPoint -= 0.1;
  1365. }
  1366. elsif ($delta_1 < 0) {
  1367. $endPoint += 0.1;
  1368. }
  1369. $endPoint = int(10.0 * $endPoint + 0.5) / 10.0;
  1370. $offset = int(10.0 * $offset + 0.5) / 10.0;
  1371. }
  1372. LUXTRONIK2_Log $hash, 3, "Heating-Curve-Parameter calculated in $i loops.";
  1373. $msg = "New Values: heatingCurveEndPoint=$endPoint heatingCurveOffset=$offset\n";
  1374. $msg .= "Old Values: heatingCurveEndPoint=$endPoint_Ist heatingCurveOffset=$offset_Ist\n\n";
  1375. $msg .= "New Heating-Curve: returnTemp($aussen_1)=".($delta_1+$rtSoll_1)." and returnTemp($aussen_2)=".($delta_2+$rtSoll_2)."\n";
  1376. $msg .= "Old Heating-Curve: returnTemp($aussen_1)=".($rtIst_1)." and returnTemp($aussen_2)=".($rtIst_2)."\n";
  1377. $msg .= "calculated in $i loops\n";
  1378. return $msg;
  1379. }
  1380. ########################################
  1381. sub LUXTRONIK2_getHeatingCurveReturnTemperature ($$$$)
  1382. { my ($hash, $aussen, $endPoint, $offset) = @_;
  1383. LUXTRONIK2_Log $hash, 5, "Calculate return-temperature at $aussen with heating curve ($endPoint, $offset)";
  1384. my $result = $offset + ($endPoint - 20.0) * ($offset - $aussen) / (20.0 - ($aussen - $offset) / 2);
  1385. $result = int(10.0 * $result + 0.5) / 10.0;
  1386. return $result;
  1387. }
  1388. ########################################
  1389. sub LUXTRONIK2_checkFirmware ($)
  1390. {
  1391. my ($myFirmware) = @_;
  1392. #Firmware not tested
  1393. if (index($testedFirmware,"#".$myFirmware."#") == -1) {
  1394. return "fwNotTested";
  1395. #Firmware tested but not compatible
  1396. } elsif (index($compatibleFirmware,"#".$myFirmware."#") == -1) {
  1397. return "fwNotCompatible";
  1398. #Firmware compatible
  1399. } else {
  1400. return "fwCompatible";
  1401. }
  1402. }
  1403. # Calculate heat-up gradients of boiler based on hotWaterTemperature and counterHeatQHeating
  1404. sub ########################################
  1405. LUXTRONIK2_doStatisticThermalPower ($$$$$$$$$)
  1406. {
  1407. my ($hash, $MonitoredOpState, $currOpState, $currHeatQuantity, $currOpHours, $currAmbTemp, $currHeatSourceIn, $targetTemp, $electricalPower) = @_;
  1408. my @last = split / /, $hash->{fhem}{"statThermalPowerOpState_".$MonitoredOpState} || "1";
  1409. my $returnStr = "";
  1410. my $value1;
  1411. my $value2;
  1412. my $value3;
  1413. my $save = 0;
  1414. if ( $last[0] != $MonitoredOpState && $currOpState == $MonitoredOpState ) {
  1415. # Save start values at the beginning of the monitored operation (5=Hot Water, 0=Heating)
  1416. $save = 1;
  1417. $last[0] = $currOpState;
  1418. $last[1] = $currHeatQuantity;
  1419. $last[2] = $currOpHours;
  1420. $last[3] = $currAmbTemp;
  1421. $last[4] = $currHeatSourceIn;
  1422. $last[5] = 1;
  1423. $last[6] = $targetTemp;
  1424. $last[7] = $electricalPower;
  1425. } elsif ($last[0] == $MonitoredOpState && ($currOpState == $MonitoredOpState || $currOpState == 16) ) { #16=Durchflussüberwachung
  1426. # Store intermediate values as long as the correct opMode runs
  1427. $save = 1;
  1428. $last[3] += $currAmbTemp;
  1429. $last[4] += $currHeatSourceIn;
  1430. $last[5]++;
  1431. $last[7] += $electricalPower;
  1432. } elsif ($last[0] == $MonitoredOpState && $currOpState != $MonitoredOpState && $currOpState != 16 ) { #16=Durchflussüberwachung
  1433. # Do statistics at the end of the monitored operation if it run at least 9.5 minutes
  1434. $save = 1;
  1435. $last[0] = $currOpState;
  1436. $value2 = ($currOpHours - $last[2])/60;
  1437. if ($value2 >= 6) {
  1438. $returnStr = sprintf "aT: %.1f iT: %.1f tT: %.1f", $last[3]/$last[5], $last[4]/$last[5], $targetTemp;
  1439. $value1 = $currHeatQuantity - $last[1];
  1440. $value3 = $value1 * 60 / $value2;
  1441. $returnStr .= sprintf " thP: %.1f DQ: %.1f t: %.0f", $value3, $value1, $value2;
  1442. if ($last[7]>0) {
  1443. $value1 = $value3 *1000 / $last[7] * $last[5];;
  1444. $returnStr .= sprintf " COP: %.2f", $value1;
  1445. }
  1446. if ($last[6] > $targetTemp) { $returnStr .= " tTStart: " . $last[6]; }
  1447. }
  1448. }
  1449. if ($save == 1) { $hash->{fhem}{"statThermalPowerOpState_".$MonitoredOpState} = join( " ", @last);}
  1450. return $returnStr;
  1451. }
  1452. # Calculate heat-up gradients of boiler based on hotWaterTemperature and counterHeatQHeating
  1453. sub ########################################
  1454. LUXTRONIK2_doStatisticBoilerHeatUp ($$$$$$)
  1455. {
  1456. my ($hash, $currOpHours, $currHQ, $currTemp, $opState, $target) = @_;
  1457. my $name = $hash->{NAME};
  1458. my $step = $hash->{fhem}{statBoilerHeatUpStep};
  1459. my $minTemp = $hash->{fhem}{statBoilerHeatUpMin};
  1460. my $maxTemp = $hash->{fhem}{statBoilerHeatUpMax};
  1461. my $lastHQ = $hash->{fhem}{statBoilerHeatUpHQ};
  1462. my $lastOpHours = $hash->{fhem}{statBoilerHeatUpOpHours};
  1463. my $value1 = 0;
  1464. my $value2 = 0;
  1465. my $value3 = 0;
  1466. my $returnStr = "";
  1467. # step 0 = Initialize - if hot water preparation is off
  1468. if ($step == 0) {
  1469. if ($opState != 5) { # wait till hot water preparation stopped
  1470. LUXTRONIK2_Log $name, 4, "Statistic Boiler Heat-Up step 0->1: Initializing Measurment";
  1471. $step = 1;
  1472. $lastOpHours = $currOpHours;
  1473. $lastHQ = $currHQ;
  1474. $minTemp = $currTemp;
  1475. }
  1476. # step 1 = wait till hot water preparation starts -> monitor Tmin, take previous HQ and previous operating hours
  1477. } elsif ($step == 1) {
  1478. if ($currTemp < $minTemp) { # monitor minimum temperature
  1479. LUXTRONIK2_Log $name, 4, "Statistic Boiler Heat-Up step 1: Monitor minimum temperature ($minTemp -> $currTemp)";
  1480. $minTemp = $currTemp;
  1481. }
  1482. if ($opState != 5) { # wait -> update operating hours and HQ to be used as start value in calculations
  1483. $lastOpHours = $currOpHours;
  1484. $lastHQ = $currHQ;
  1485. } else { # go to step 2 - if hot water preparation running
  1486. LUXTRONIK2_Log $name, 4, "Statistic Boiler Heat-Up step 1->2: Hot water preparation started ".($currOpHours-$lastOpHours)." s ago";
  1487. $step = 2;
  1488. $maxTemp = $currTemp;
  1489. }
  1490. # step 2 = wait till hot water preparation done and target reached
  1491. } elsif ($step == 2) {
  1492. if ($currTemp < $minTemp) { # monitor minimal temperature
  1493. LUXTRONIK2_Log $name, 4, "Statistic Boiler Heat-Up step 2: Boiler temperature still decreasing ($minTemp -> $currTemp)";
  1494. $minTemp = $currTemp;
  1495. }
  1496. if ($currTemp > $maxTemp) { # monitor maximal temperature
  1497. LUXTRONIK2_Log $name, 4, "Statistic Boiler Heat-Up step 2: Boiler temperature increasing ($maxTemp -> $currTemp)";
  1498. $maxTemp = $currTemp;
  1499. }
  1500. if ($opState != 5) { # wait till hot water preparation stopped
  1501. if ($currTemp >= $target) {
  1502. LUXTRONIK2_Log $name, 4, "Statistic Boiler Heat-Up step 2->3: Hot water preparation stopped";
  1503. $step = 3;
  1504. } else {
  1505. LUXTRONIK2_Log $name, 4, "Statistic Boiler Heat-Up step 2->1: Measurement cancelled (hot water preparation stopped but target not reached, $currTemp < $target)";
  1506. $step = 1;
  1507. $lastOpHours = $currOpHours;
  1508. $lastHQ = $currHQ;
  1509. $minTemp = $currTemp;
  1510. }
  1511. }
  1512. # step 3 = wait with calculation till temperature maximum reached once
  1513. } elsif ($step == 3) {
  1514. # cancel measurement - if hot water preparation has restarted
  1515. if ($opState == 5) {
  1516. LUXTRONIK2_Log $name, 4, "Statistic Boiler Heat-Up step 3->0: Measurement cancelled (hot water preparation restarted before maximum reached)";
  1517. $step = 0;
  1518. # monitor maximal temperature
  1519. } elsif ($currTemp > $maxTemp) {
  1520. LUXTRONIK2_Log $name, 4, "Statistic Boiler Heat-Up step 3: Temperature still increasing ($maxTemp -> $currTemp)";
  1521. $maxTemp = $currTemp;
  1522. # else calculate temperature gradient
  1523. } else {
  1524. LUXTRONIK2_Log $name, 4, "Statistic Boiler Heat-Up step 3->1: Boiler heat-up measurement finished";
  1525. $value1 = ( int(10 * $maxTemp) - int(10 * $minTemp) ) / 10; # delta hot water temperature
  1526. $value2 = ( $currOpHours - $lastOpHours ) / 60; # delta time (minutes)
  1527. $value3 = $currHQ - $lastHQ; # delta heat quantity, average thermal power
  1528. $returnStr = sprintf "DT/min: %.2f DT: %.2f Dmin: %.0f DQ: %.1f thP: %.1f", $value1/$value2, $value1, $value2, $value3, $value3*60/$value2;
  1529. #real (mixed) Temperature-Difference
  1530. my $boilerVolumn = AttrVal($name, "boilerVolumn", 0);
  1531. if ($boilerVolumn >0 ) {
  1532. # (delta T) [K] = Wärmemenge [kWh] / #Volumen [l] * ( 3.600 [kJ/kWh] / ( 4,179 [kJ/(kg*K)] (H2O Wärmekapazität bei 40°C) * 0,992 [kg/l] (H2O Dichte bei 40°C) ) [K/(kWh*l)] )
  1533. $value2 = 868.4 * $value3 / $boilerVolumn ;
  1534. $returnStr .= sprintf " realDT: %.0f", $value2;
  1535. }
  1536. $step = 1;
  1537. $lastOpHours = $currOpHours;
  1538. $lastHQ = $currHQ;
  1539. $minTemp = $currTemp;
  1540. }
  1541. }
  1542. $hash->{fhem}{statBoilerHeatUpStep} = $step;
  1543. $hash->{fhem}{statBoilerHeatUpMin} = $minTemp;
  1544. $hash->{fhem}{statBoilerHeatUpMax} = $maxTemp;
  1545. $hash->{fhem}{statBoilerHeatUpHQ} = $lastHQ;
  1546. $hash->{fhem}{statBoilerHeatUpOpHours} = $lastOpHours;
  1547. return $returnStr;
  1548. }
  1549. # Calculate heat loss gradients of boiler based on hotWaterTemperature and counterHeatQHeating
  1550. sub ########################################
  1551. LUXTRONIK2_doStatisticBoilerCoolDown ($$$$$$)
  1552. {
  1553. my ($hash, $time, $currTemp, $opState, $target, $threshold) = @_;
  1554. my $name = $hash->{NAME};
  1555. my $step = $hash->{fhem}{statBoilerCoolDownStep};
  1556. my $maxTemp = $hash->{fhem}{statBoilerCoolDownMax};
  1557. my $startTime = $hash->{fhem}{statBoilerCoolDownStartTime};
  1558. my $lastTime = $hash->{fhem}{statBoilerCoolDownLastTime};
  1559. my $lastTemp = $hash->{fhem}{statBoilerCoolDownLastTemp};
  1560. my $value1 = 0;
  1561. my $value2 = 0;
  1562. my $value3 = 0;
  1563. my $returnStr = "";
  1564. # step 0 = Initialize - if hot water preparation is off and target reached,
  1565. if ($step == 0) {
  1566. if ($opState == 5 || $currTemp < $target) { # -> stay step 0
  1567. # LUXTRONIK2_Log $name, 4, "Statistic Boiler Cool-Down step 0: Wait till hot water preparation stops and target is reached ($currTemp < $target)";
  1568. } else {
  1569. LUXTRONIK2_Log $name, 4, "Statistic Boiler Cool-Down step 0->1: Initializing, target reached ($currTemp >= $target)";
  1570. $step = 1;
  1571. $startTime = $time;
  1572. $maxTemp = $currTemp;
  1573. }
  1574. # step 1 = wait till threshold is reached -> do calculation, monitor maximal temperature
  1575. } elsif ($step == 1) {
  1576. if ($currTemp > $maxTemp) { # monitor maximal temperature
  1577. LUXTRONIK2_Log $name, 4, "Statistic Boiler Cool-Down step 1: Temperature still increasing ($currTemp > $maxTemp)";
  1578. $maxTemp = $currTemp;
  1579. $startTime = $time;
  1580. }
  1581. if ($opState == 5 || $currTemp <= $threshold) {
  1582. if ($opState == 5) {
  1583. LUXTRONIK2_Log $name, 4, "Statistic Boiler Cool-Down step 1->0: Heat-up started, measurement finished";
  1584. $value1 = $lastTemp - $maxTemp; # delta hot water temperature
  1585. $value2 = ( $lastTime - $startTime ) / 3600; # delta time (hours)
  1586. } elsif ($currTemp <= $threshold) {
  1587. LUXTRONIK2_Log $name, 4, "Statistic Boiler Cool-Down step 1->0: Measurement finished, threshold reached ($currTemp <= $threshold)";
  1588. $value1 = $currTemp - $maxTemp; # delta hot water temperature
  1589. $value2 = ( $time - $startTime ) / 3600; # delta time (hours)
  1590. }
  1591. $returnStr = sprintf "DT/h: %.2f DT: %.1f Dh: %.2f", $value1/$value2, $value1, $value2;
  1592. $step = 0;
  1593. }
  1594. }
  1595. $hash->{fhem}{statBoilerCoolDownStep} = $step;
  1596. $hash->{fhem}{statBoilerCoolDownMax} = $maxTemp;
  1597. $hash->{fhem}{statBoilerCoolDownStartTime} = $startTime;
  1598. $hash->{fhem}{statBoilerCoolDownLastTime} = $time;
  1599. $hash->{fhem}{statBoilerCoolDownLastTemp} = $currTemp;
  1600. return $returnStr;
  1601. }
  1602. # Calculates single MaxMin Values and informs about end of day and month
  1603. sub ########################################
  1604. LUXTRONIK2_doStatisticMinMax ($$$)
  1605. {
  1606. my ($hash, $readingName, $value) = @_;
  1607. my $dummy;
  1608. my $saveLast;
  1609. my $statReadingName;
  1610. my $lastReading;
  1611. my $lastSums;
  1612. my @newReading;
  1613. my $yearLast;
  1614. my $monthLast;
  1615. my $dayLast;
  1616. my $dayNow;
  1617. my $monthNow;
  1618. my $yearNow;
  1619. # Determine date of last and current reading
  1620. if (exists($hash->{READINGS}{$readingName."Day"}{TIME})) {
  1621. ($yearLast, $monthLast, $dayLast) = $hash->{READINGS}{$readingName."Day"}{TIME} =~ /^(\d\d\d\d)-(\d\d)-(\d\d)/;
  1622. } else {
  1623. ($dummy, $dummy, $dummy, $dayLast, $monthLast, $yearLast) = localtime;
  1624. $yearLast += 1900;
  1625. $monthLast ++;
  1626. }
  1627. ($dummy, $dummy, $dummy, $dayNow, $monthNow, $yearNow) = localtime;
  1628. $yearNow += 1900;
  1629. $monthNow ++;
  1630. # Daily Statistic
  1631. $saveLast = ($dayNow != $dayLast);
  1632. $statReadingName = $readingName."Day";
  1633. LUXTRONIK2_doStatisticMinMaxSingle $hash, $statReadingName, $value, $saveLast;
  1634. # Monthly Statistic
  1635. $saveLast = ($monthNow != $monthLast);
  1636. $statReadingName = $readingName."Month";
  1637. LUXTRONIK2_doStatisticMinMaxSingle $hash, $statReadingName, $value, $saveLast;
  1638. # Yearly Statistic
  1639. $saveLast = ($yearNow != $yearLast);
  1640. $statReadingName = $readingName."Year";
  1641. LUXTRONIK2_doStatisticMinMaxSingle $hash, $statReadingName, $value, $saveLast;
  1642. return ;
  1643. }
  1644. # Calculates single MaxMin Values and informs about end of day and month
  1645. sub ########################################
  1646. LUXTRONIK2_doStatisticMinMaxSingle ($$$$)
  1647. {
  1648. my ($hash, $readingName, $value, $saveLast) = @_;
  1649. my $result;
  1650. my $lastReading = $hash->{READINGS}{$readingName}{VAL} || "";
  1651. # Initializing
  1652. if ( $lastReading eq "" ) {
  1653. my $since = strftime "%Y-%m-%d_%H:%M:%S", localtime();
  1654. $result = "Count: 1 Sum: $value ShowDate: 1";
  1655. readingsBulkUpdate($hash, ".".$readingName, $result);
  1656. $result = "Min: $value Avg: $value Max: $value (since: $since )";
  1657. readingsBulkUpdate($hash, $readingName, $result);
  1658. # Calculations
  1659. } else {
  1660. my @a = split / /, $hash->{READINGS}{"." . $readingName}{VAL}; # Internal values
  1661. my @b = split / /, $lastReading;
  1662. # Do calculations
  1663. $a[1]++; # Count
  1664. $a[3] += $value; # Sum
  1665. if ($value < $b[1]) { $b[1]=$value; } # Min
  1666. if ($a[1]>0) {$b[3] = sprintf "%.1f" , $a[3] / $a[1] ;} # Avg
  1667. if ($value > $b[5]) { $b[5]=$value; } # Max
  1668. # in case of period change, save "last" values and reset counters
  1669. if ($saveLast) {
  1670. $result = "Min: $b[1] Avg: $b[3] Max: $b[5]";
  1671. if ($a[5] == 1) { $result .= " (since: $b[7] )"; }
  1672. readingsBulkUpdate($hash, $readingName . "Last", $lastReading);
  1673. $a[1] = 1; $a[3] = $value; $a[5] = 0;
  1674. $b[1] = $value; $b[3] = $value; $b[5] = $value;
  1675. }
  1676. # Store internal calculation values
  1677. $result = "Count: $a[1] Sum: $a[3] ShowDate: $a[5]";
  1678. readingsBulkUpdate($hash, ".".$readingName, $result);
  1679. # Store visible Reading
  1680. if ($a[5] == 1) {
  1681. $result = "Min: $b[1] Avg: $b[3] Max: $b[5] (since: $b[7] )";
  1682. } else {
  1683. $result = "Min: $b[1] Avg: $b[3] Max: $b[5]";
  1684. }
  1685. readingsBulkUpdate($hash, $readingName, $result);
  1686. }
  1687. return;
  1688. }
  1689. sub ########################################
  1690. LUXTRONIK2_storeReadings($$$$$$)
  1691. {
  1692. my ($hash, $readingName, $value, $factor, $doStatistics, $electricalPower) = @_;
  1693. if ($value eq "no" || $value == 0 ) { return; }
  1694. readingsBulkUpdate($hash, $readingName, sprintf("%.1f", $value / $factor));
  1695. $readingName =~ s/counter//;
  1696. # LUXTRONIK2_doStatisticDelta: $hash, $readingName, $value, $factor, $electricalPower
  1697. if ( $doStatistics == 1) { LUXTRONIK2_doStatisticDelta $hash, "stat".$readingName, $value, $factor, $electricalPower; }
  1698. }
  1699. # Calculates deltas for day, month and year
  1700. sub ########################################
  1701. LUXTRONIK2_doStatisticDelta ($$$$$)
  1702. {
  1703. my ($hash, $readingName, $value, $factor, $electricalPower) = @_;
  1704. my $name = $hash->{NAME};
  1705. my $dummy;
  1706. my $result;
  1707. my $deltaValue;
  1708. my $previousTariff;
  1709. my $showDate;
  1710. # Determine if time period switched (day, month, year)
  1711. # Get deltaValue and Tariff of previous call
  1712. my $periodSwitch = 0;
  1713. my $yearLast; my $monthLast; my $dayLast; my $dayNow; my $monthNow; my $yearNow;
  1714. if (exists($hash->{READINGS}{"." . $readingName . "Before"})) {
  1715. ($yearLast, $monthLast, $dayLast) = ($hash->{READINGS}{"." . $readingName . "Before"}{TIME} =~ /^(\d\d\d\d)-(\d\d)-(\d\d)/);
  1716. $yearLast -= 1900;
  1717. $monthLast --;
  1718. ($dummy, $deltaValue, $dummy, $previousTariff, $dummy, $showDate) = split / /, $hash->{READINGS}{"." . $readingName . "Before"}{VAL} || "";
  1719. $deltaValue = $value - $deltaValue;
  1720. } else {
  1721. ($dummy, $dummy, $dummy, $dayLast, $monthLast, $yearLast) = localtime;
  1722. $deltaValue = 0;
  1723. $previousTariff = 0;
  1724. $showDate = 6;
  1725. }
  1726. ($dummy, $dummy, $dummy, $dayNow, $monthNow, $yearNow) = localtime;
  1727. if ($yearNow != $yearLast) { $periodSwitch = 3; }
  1728. elsif ($monthNow != $monthLast) { $periodSwitch = 2; }
  1729. elsif ($dayNow != $dayLast) { $periodSwitch = 1; }
  1730. # Determine if "since" value has to be shown in current and last reading
  1731. if ($periodSwitch == 3) {
  1732. if ($showDate == 1) { $showDate = 0; } # Do not show the "since:" value for year changes anymore
  1733. if ($showDate >= 2) { $showDate = 1; } # Shows the "since:" value for the first year change
  1734. }
  1735. if ($periodSwitch >= 2){
  1736. if ($showDate == 3) { $showDate = 2; } # Do not show the "since:" value for month changes anymore
  1737. if ($showDate >= 4) { $showDate = 3; } # Shows the "since:" value for the first month change
  1738. }
  1739. if ($periodSwitch >= 1){
  1740. if ($showDate == 5) { $showDate = 4; } # Do not show the "since:" value for day changes anymore
  1741. if ($showDate >= 6) { $showDate = 5; } # Shows the "since:" value for the first day change
  1742. }
  1743. # LUXTRONIK2_doStatisticDeltaSingle; $hash, $readingName, $deltaValue, $periodSwitch, $showDate, $firstCall
  1744. LUXTRONIK2_doStatisticDeltaSingle ($hash, $readingName, $deltaValue, $factor, $periodSwitch, $showDate, 0);
  1745. my $activeTariff = ReadingsVal($name,"activeTariff",0);
  1746. if ( $electricalPower >=0 ) {
  1747. my $readingNamePower = $readingName;
  1748. $readingNamePower =~ s/Hours/Electricity/ ;
  1749. if ($activeTariff > 0) {
  1750. foreach (1,2,3,4,5,6,7,8,9) {
  1751. if ( $previousTariff == $_ ) {
  1752. LUXTRONIK2_doStatisticDeltaSingle ($hash, $readingNamePower."Tariff".$_, $deltaValue * $electricalPower, $factor, $periodSwitch, $showDate, 1);
  1753. } elsif ($activeTariff == $_ || ($periodSwitch > 0 && exists($hash->{READINGS}{$readingNamePower . "Tariff".$_}))) {
  1754. LUXTRONIK2_doStatisticDeltaSingle ($hash, $readingNamePower."Tariff".$_, 0, $factor, $periodSwitch, $showDate, 1);
  1755. }
  1756. }
  1757. } else {
  1758. LUXTRONIK2_doStatisticDeltaSingle ($hash, $readingNamePower, $deltaValue * $electricalPower, $factor, $periodSwitch, $showDate, 1);
  1759. }
  1760. }
  1761. # Hidden storage of current values for next call(before values)
  1762. $result = "Value: $value Tariff: $activeTariff ShowDate: $showDate";
  1763. readingsBulkUpdate($hash, ".".$readingName."Before", $result);
  1764. return ;
  1765. }
  1766. sub ########################################
  1767. LUXTRONIK2_doStatisticDeltaSingle ($$$$$$$)
  1768. {
  1769. my ($hash, $readingName, $deltaValue, $factor, $periodSwitch, $showDate, $specMonth) = @_;
  1770. my $dummy;
  1771. my $result;
  1772. # get existing statistic reading
  1773. my @curr;
  1774. if (exists($hash->{READINGS}{".".$readingName}{VAL})) {
  1775. @curr = split / /, $hash->{READINGS}{".".$readingName}{VAL} || "";
  1776. } else {
  1777. $curr[1] = 0; $curr[3] = 0; $curr[5] = 0;
  1778. if ($showDate>5) {$curr[7] = strftime "%Y-%m-%d_%H:%M:%S", localtime();} # start
  1779. else {$curr[7] = strftime "%Y-%m-%d", localtime();} # start
  1780. }
  1781. # get statistic values of previous period
  1782. my @last;
  1783. if ($periodSwitch >= 1) {
  1784. if (exists ($hash->{READINGS}{$readingName."Last"})) {
  1785. @last = split / /, $hash->{READINGS}{$readingName."Last"}{VAL};
  1786. } else {
  1787. @last = split / /, "Day: - Month: - Year: -";
  1788. }
  1789. }
  1790. # Do statistic
  1791. $curr[1] += $deltaValue;
  1792. $curr[3] += $deltaValue;
  1793. $curr[5] += $deltaValue;
  1794. # If change of year, change yearly statistic
  1795. if ($periodSwitch == 3){
  1796. if ($specMonth) { $last[5] = sprintf("%.3f",$curr[5] / $factor/ 1000); }
  1797. else {$last[5] = sprintf("%.0f",$curr[5] / $factor);}
  1798. $curr[5] = 0;
  1799. if ($showDate == 1) { $last[7] = $curr[7]; }
  1800. }
  1801. # If change of month, change monthly statistic
  1802. if ($periodSwitch >= 2){
  1803. if ($specMonth) { $last[3] = sprintf("%.3f",$curr[3] / $factor/ 1000); }
  1804. else {$last[3] = sprintf("%.0f",$curr[3] / $factor);}
  1805. $curr[3] = 0;
  1806. if ($showDate == 3) { $last[7] = $curr[7];}
  1807. }
  1808. # If change of day, change daily statistic
  1809. if ($periodSwitch >= 1){
  1810. $last[1] = sprintf("%.1f",$curr[1] / $factor);
  1811. $curr[1] = 0;
  1812. if ($showDate == 5) {
  1813. $last[7] = $curr[7];
  1814. # Next monthly and yearly values start at 00:00 and show only date (no time)
  1815. $curr[3] = 0;
  1816. $curr[5] = 0;
  1817. $curr[7] = strftime "%Y-%m-%d", localtime(); # start
  1818. }
  1819. }
  1820. # Store hidden statistic readings (delta values)
  1821. $result = "Day: $curr[1] Month: $curr[3] Year: $curr[5]";
  1822. if ( $showDate >=2 ) { $result .= " (since: $curr[7] )"; }
  1823. readingsBulkUpdate($hash,".".$readingName,$result);
  1824. # Store visible statistic readings (delta values)
  1825. if ($specMonth) { $result = sprintf "Day: %.1f Month: %.3f Year: %.3f", $curr[1]/$factor, $curr[3]/$factor/1000, $curr[5]/$factor/1000; }
  1826. else { $result = sprintf "Day: %.1f Month: %.0f Year: %.0f", $curr[1]/$factor, $curr[3]/$factor, $curr[5]/$factor; }
  1827. if ( $showDate >=2 ) { $result .= " (since: $curr[7] )"; }
  1828. readingsBulkUpdate($hash,$readingName,$result);
  1829. # if changed, store previous visible statistic (delta) values
  1830. if ($periodSwitch >= 1) {
  1831. $result = "Day: $last[1] Month: $last[3] Year: $last[5]";
  1832. if ( $showDate =~ /1|3|5/ ) { $result .= " (since: $last[7] )";}
  1833. readingsBulkUpdate($hash,$readingName."Last",$result);
  1834. }
  1835. }
  1836. 1;
  1837. =pod
  1838. =item device
  1839. =item summary Controls a Luxtronik 2.0 controller for heat pumps
  1840. =item summary_DE Steuert eine Luxtronik 2.0 Heizungssteuerung für W&auml;rmepumpen.
  1841. =begin html
  1842. <a name="LUXTRONIK2"></a>
  1843. <h3>LUXTRONIK2</h3>
  1844. <div>
  1845. <ul>
  1846. Luxtronik 2.0 is a heating controller used in <a href="http://www.alpha-innotec.de">Alpha Innotec</a>, Siemens Novelan (WPR NET) and Wolf Heiztechnik (BWL/BWS) heat pumps.
  1847. <br>
  1848. It has a built-in ethernet port, so it can be directly integrated into a local area network (LAN).
  1849. <br>
  1850. <i>The modul is reported to work with firmware: V1.51, V1.54C, V1.60, V1.64, V1.69, V1.70, V1.73, V1.77.</i>
  1851. <br>
  1852. More Info on the particular <a href="http://www.fhemwiki.de/wiki/Luxtronik_2.0">page of FHEM-Wiki</a> (in German).
  1853. <br>
  1854. &nbsp;
  1855. <br>
  1856. <a name="LUXTRONIK2define"></a>
  1857. <b>Define</b>
  1858. <ul>
  1859. <code>define &lt;name&gt; LUXTRONIK2 &lt;IP-address[:Port]&gt; [poll-interval]</code><br>
  1860. If the pool interval is omitted, it is set to 300 (seconds). Smallest possible value is 10.
  1861. <br>
  1862. Usually, the port needs not to be defined.
  1863. <br>
  1864. Example: <code>define Heizung LUXTRONIK2 192.168.0.12 600</code>
  1865. </ul>
  1866. <br>
  1867. <a name="LUXTRONIK2set"></a>
  1868. <b>Set</b>
  1869. <ul>A firmware check assures before each set operation that a heat pump with untested firmware is not damaged accidently.
  1870. <li><code>activeTariff &lt; 0 - 9 &gt;</code>
  1871. <br>
  1872. Allows the separate measurement of the consumption (doStatistics = 1) within different tariffs.<br>
  1873. This value must be set at the correct point of time in accordance to the existing or planned tariff <b>by the FHEM command "at"</b>.<br>
  1874. 0 = without separate tariffs
  1875. </li><br>
  1876. <li><code>INTERVAL &lt;polling interval&gt;</code><br>
  1877. Polling interval in seconds
  1878. </li><br>
  1879. <li><code>hotWaterTemperatureTarget &lt;temperature&gt;</code><br>
  1880. Target temperature of domestic hot water boiler in &deg;C
  1881. </li><br>
  1882. <li><code>hotWaterCircPumpDeaerate &lt;on | off&gt;</code><br>
  1883. Switches the external circulation pump for the hot water on or off. The circulation prevents a cool down of the hot water in the pipes but increases the heat consumption drastically.
  1884. <br>
  1885. NOTE! It uses the deaerate function of the controller. So, the pump alternates always 5 minutes on and 5 minutes off.
  1886. </li><br>
  1887. <li><code>opModeHotWater &lt;Mode&gt;</code><br>
  1888. Operating Mode of domestic hot water boiler (Auto | Party | Off)
  1889. </li><br>
  1890. <li><code>resetStatistics &lt;statReadings&gt;</code>
  1891. <br>
  1892. Deletes the selected statistic values <i>all, statBoilerGradientCoolDownMin, statAmbientTemp..., statElectricity..., statHours..., statHeatQ...</i>
  1893. </li><br>
  1894. <li><code>returnTemperatureHyst &lt;Temperature&gt;</code>
  1895. <br>
  1896. Hysteresis of the returnTemperatureTarget of the heating controller . 0.5 K till 3 K. Adjustable in 0.1 steps.
  1897. </li><br>
  1898. <li><code>returnTemperatureSetBack &lt;Temperature&gt;</code>
  1899. <br>
  1900. Decreasing or increasing of the returnTemperatureTarget by -5 K till + 5 K. Adjustable in 0.1 steps.
  1901. </li><br>
  1902. <li><code>statusRequest</code><br>
  1903. Update device information
  1904. </li><br>
  1905. <li><code>synchClockHeatPump</code><br>
  1906. Synchronizes controller clock with FHEM time. <b>!! This change is lost in case of controller power off!!</b></li>
  1907. </ul>
  1908. <br>
  1909. <a name="LUXTRONIK2get"></a>
  1910. <b>Get</b>
  1911. <ul>
  1912. No get implemented yet ...
  1913. </ul>
  1914. <br>
  1915. <a name="LUXTRONIK2attr"></a>
  1916. <b>Attributes</b>
  1917. <ul>
  1918. <li><code>allowSetParameter &lt; 0 | 1 &gt;</code>
  1919. <br>
  1920. The <a href="#LUXTRONIK2set">parameters</a> of the heat pump controller can only be changed if this attribut is set to 1.
  1921. </li><br>
  1922. <li><code>autoSynchClock &lt;delay&gt;</code>
  1923. <br>
  1924. Corrects the clock of the heatpump automatically if a certain <i>delay</i> (10 s - 600 s) against the FHEM time is exeeded. Does a firmware check before.
  1925. <br>
  1926. <i>(A 'delayDeviceTimeCalc' &lt;= 2 s can be caused by the internal calculation interval of the heat pump controller.)</i>
  1927. </li><br>
  1928. <li><code>compressor2ElectricalPowerWatt</code><br>
  1929. Electrical power of the 2nd compressor to calculated the COP and estimate electrical consumption (calculations not implemented yet)
  1930. </li><br>
  1931. <li><code>doStatistics &lt; 0 | 1 &gt;</code>
  1932. <br>
  1933. Calculates statistic values: <i>statBoilerGradientHeatUp, statBoilerGradientCoolDown, statBoilerGradientCoolDownMin (boiler heat loss)</i>
  1934. <br>
  1935. Builds daily, monthly and yearly statistics for certain readings (average/min/max or cumulated values).
  1936. <br>
  1937. Logging and visualisation of the statistic should be done with readings of type 'stat<i>ReadingName</i><b>Last</b>'.
  1938. </li><br>
  1939. <li><code>heatPumpElectricalPowerWatt</code><br>
  1940. Electrical power of the heat pump by a flow temperature of 35&deg;C to calculated coefficency factor and estimate electrical consumption
  1941. </li><br>
  1942. <li><code>heatPumpElectricalPowerFactor</code><br>
  1943. Change of electrical power consumption per 1 K flow temperature differenz to 35&deg;C (e.g. 2% per 1 K = 0,02)
  1944. </li><br>
  1945. <li><code>heatRodElectricalPowerWatt</code><br>
  1946. Electrical power of the heat rods (2nd heat source) to estimate electrical consumption
  1947. </li><br>
  1948. <li><code>ignoreFirmwareCheck &lt; 0 | 1 &gt;</code>
  1949. <br>
  1950. A firmware check assures before each set operation that a heatpump controller with untested firmware is not damaged accidently.
  1951. <br>
  1952. If this attribute is set to 1, the firmware check is ignored and new firmware can be tested for compatibility.
  1953. </li><br>
  1954. <li><code>statusHTML</code>
  1955. <br>
  1956. If set, a HTML-formatted reading named "floorplanHTML" is created. It can be used with the <a href="#FLOORPLAN">FLOORPLAN</a> module.
  1957. <br>
  1958. Currently, if the value of this attribute is not NULL, the corresponding reading consists of the current status of the heat pump and the temperature of the water.
  1959. </li><br>
  1960. <li><a href="#readingFnAttributes">readingFnAttributes</a></li>
  1961. </ul>
  1962. </ul>
  1963. </div>
  1964. =end html
  1965. =begin html_DE
  1966. <a name="LUXTRONIK2"></a>
  1967. <h3>LUXTRONIK2</h3>
  1968. <div>
  1969. <ul>
  1970. Die Luxtronik 2.0 ist eine Heizungssteuerung der Firma <a href="http://www.alpha-innotec.de">Alpha Innotec</a>, welche in W&auml;rmepumpen von Alpha Innotec,
  1971. Siemens Novelan (WPR NET), Roth (ThermoAura®, ThermoTerra), Elco und Wolf Heiztechnik (BWL/BWS) verbaut ist.
  1972. Sie besitzt einen Ethernet Anschluss, so dass sie direkt in lokale Netzwerke (LAN) integriert werden kann.
  1973. <br>
  1974. <i>Das Modul wurde bisher mit folgender Steuerungs-Firmware getestet: V1.51, V1.54C, V1.60, V1.64, V1.69, V1.70, V1.73, V1.77.</i>
  1975. <br>
  1976. Mehr Infos im entsprechenden <u><a href="http://www.fhemwiki.de/wiki/Luxtronik_2.0">Artikel der FHEM-Wiki</a></u>.
  1977. <br>&nbsp;
  1978. <br>
  1979. <a name="LUXTRONIK2define"></a>
  1980. <b>Define</b>
  1981. <ul>
  1982. <code>define &lt;name&gt; LUXTRONIK2 &lt;IP-Adresse[:Port]&gt; [Abfrageinterval]</code>
  1983. <br>
  1984. Wenn das Abfrage-Interval nicht angegeben ist, wird es auf 300 (Sekunden) gesetzt. Der kleinste m&ouml;gliche Wert ist 10.
  1985. <br>
  1986. Die Angabe des Portes kann gew&ouml;hnlich entfallen.
  1987. <br>
  1988. Beispiel: <code>define Heizung LUXTRONIK2 192.168.0.12 600</code>
  1989. </ul>
  1990. <br>
  1991. <a name="LUXTRONIK2set"></a>
  1992. <b>Set</b><br>
  1993. <ul>
  1994. Durch einen Firmware-Test wird vor jeder Set-Operation sichergestellt, dass W&auml;rmepumpen mit ungetester Firmware nicht unabsichtlich besch&auml;digt werden.
  1995. <br>&nbsp;
  1996. <li><code>activeTariff &lt; 0 - 9 &gt;</code>
  1997. <br>
  1998. Erlaubt die gezielte, separate Erfassung der statistischen Verbrauchswerte (doStatistics = 1) f&uuml;r verschiedene Tarife (Doppelstromz&auml;hler)<br>
  1999. Dieser Wert muss entsprechend des vorhandenen oder geplanten Tarifes zum jeweiligen Zeitpunkt z.B. durch den FHEM-Befehl "at" gesetzt werden.<br>
  2000. 0 = tariflos
  2001. </li><br>
  2002. <li><code>hotWaterCircPumpDeaerate &lt;on | off&gt;</code><br>
  2003. Schaltet die externe Warmwasser-Zirkulationspumpe an oder aus. Durch die Zirkulation wird das Abk&uuml;hlen des Warmwassers in den Hausleitungen verhindert. Der W&auml;rmeverbrauch steigt jedoch drastisch.
  2004. <br>
  2005. Achtung! Es wird die Entl&uuml;ftungsfunktion der Steuerung genutzt. Dadurch taktet die Pumpe jeweils 5 Minuten ein und 5 Minuten aus.
  2006. </li><br>
  2007. <li><code>hotWaterTemperatureTarget &lt;Temperatur&gt;</code>
  2008. <br>
  2009. Soll-Temperatur des Hei&szlig;wasserboilers in &deg;C
  2010. </li><br>
  2011. <li><code>opModeHotWater &lt;Betriebsmodus&gt;</code>
  2012. <br>
  2013. Betriebsmodus des Hei&szlig;wasserboilers ( Auto | Party | Off )
  2014. </li><br>
  2015. <li><code>resetStatistics &lt;statWerte&gt;</code>
  2016. <br>
  2017. L&ouml;scht die ausgew&auml;hlten statisischen Werte: <i>all, statBoilerGradientCoolDownMin, statAmbientTemp..., statElectricity..., statHours..., statHeatQ...</i>
  2018. </li><br>
  2019. <li><code>returnTemperatureHyst &lt;Temperatur&gt;</code>
  2020. <br>
  2021. Sollwert-Hysterese der Heizungsregelung. 0.5 K bis 3 K. In 0.1er Schritten einstellbar.
  2022. </li><br>
  2023. <li><code>returnTemperatureSetBack &lt;Temperatur&gt;</code>
  2024. <br>
  2025. Absenkung oder Anhebung der R&uuml;cklauftemperatur von -5 K bis + 5K. In 0.1er Schritten einstellbar.
  2026. </li><br>
  2027. <li><code>INTERVAL &lt;Sekunden&gt;</code>
  2028. <br>
  2029. Abfrageinterval in Sekunden
  2030. </li><br>
  2031. <li><code>statusRequest</code>
  2032. <br>
  2033. Aktualisieren der Ger&auml;tewerte
  2034. </li><br>
  2035. <li><code>synchClockHeatPump</code>
  2036. <br>
  2037. Abgleich der Uhr der Steuerung mit der FHEM Zeit. <b>Diese &Auml;nderung geht verloren, sobald die Steuerung ausgeschaltet wird!!</b></li>
  2038. </ul>
  2039. <br>
  2040. <a name="LUXTRONIK2get"></a>
  2041. <b>Get</b>
  2042. <ul>
  2043. Es wurde noch kein "get" implementiert ...
  2044. </ul>
  2045. <br>
  2046. <a name="LUXTRONIK2attr"></a>
  2047. <b>Attribute</b>
  2048. <ul>
  2049. <li><code>allowSetParameter &lt; 0 | 1 &gt;</code>
  2050. <br>
  2051. Die internen <a href="#LUXTRONIK2set">Parameter</a> der W&auml;rmepumpensteuerung k&ouml;nnen
  2052. nur ge&auml;ndert werden, wenn dieses Attribut auf 1 gesetzt ist.
  2053. </li><br>
  2054. <li><code>autoSynchClock &lt;Zeitunterschied&gt;</code>
  2055. <br>
  2056. Die Uhr der W&auml;rmepumpe wird automatisch korrigiert, wenn ein gewisser <i>Zeitunterschied</i> (10 s - 600 s)
  2057. gegen&uuml;ber der FHEM Zeit erreicht ist. Zuvor wird die Kompatibilit&auml;t der Firmware &uuml;berpr&uuml;ft.<br>
  2058. <i>(Ein Ger&auml;tewert 'delayDeviceTimeCalc' &lt;= 2 s ist auf die internen Berechnungsintervale der
  2059. W&auml;rmepumpensteuerung zur&uuml;ckzuf&uuml;hren.)</i>
  2060. </li><br>
  2061. <li><code>compressor2ElectricalPowerWatt</code><br>
  2062. Betriebsleistung des zweiten Kompressors zur Berechung der Arbeitszahl (erzeugte W&auml;rme pro elektrische Energieeinheit)
  2063. und Absch&auml;tzung des elektrischen Verbrauches (Auswertungen noch nicht implementiert)
  2064. </li><br>
  2065. <li><code>doStatistics &lt; 0 | 1 &gt;</code>
  2066. <br>
  2067. Berechnet statistische Werte: <i>statBoilerGradientHeatUp, statBoilerGradientCoolDown,
  2068. statBoilerGradientCoolDownMin (W&auml;rmeverlust des Boilers)</i>
  2069. <br>
  2070. Bildet t&auml;gliche, monatliche und j&auml;hrliche Statistiken bestimmter Ger&auml;tewerte.<br>
  2071. F&uuml;r grafische Auswertungen k&ouml;nnen die Werte der Form 'stat<i>ReadingName</i><b>Last</b>' genutzt werden.
  2072. </li><br>
  2073. <li><code>heatPumpElectricalPowerWatt &lt;E-Leistung in Watt&gt;</code><br>
  2074. Elektrische Leistungsaufnahme der W&auml;rmepumpe in Watt bei einer Vorlauftemperatur von 35 &deg;C zur Berechung der Arbeitszahl (erzeugte W&auml;rme pro elektrische Energieeinheit)
  2075. und Absch&auml;tzung des elektrischen Verbrauches
  2076. </li><br>
  2077. <li><code>heatPumpElectricalPowerFactor</code><br>
  2078. &Auml;nderung der elektrischen Leistungsaufnahme pro 1 K Vorlauftemperaturdifferenz zu 35 &deg;C
  2079. <br>
  2080. (z.B. 2% pro 1 K = 0,02)
  2081. </li><br>
  2082. <li><code>heatRodElectricalPowerWatt &lt;E-Leistung in Watt&gt;</code><br>
  2083. Elektrische Leistungsaufnahme der Heizst&auml;be in Watt zur Absch&auml;tzung des elektrischen Verbrauches
  2084. </li><br>
  2085. <li><code>ignoreFirmwareCheck &lt; 0 | 1 &gt;</code>
  2086. <br>
  2087. Durch einen Firmware-Test wird vor jeder Set-Operation sichergestellt, dass W&auml;rmepumpen
  2088. mit ungetester Firmware nicht unabsichtlich besch&auml;digt werden. Wenn dieses Attribute auf 1
  2089. gesetzt ist, dann wird der Firmware-Test ignoriert und neue Firmware kann getestet werden.
  2090. Dieses Attribut wird jedoch ignoriert, wenn die Steuerungs-Firmware bereits als nicht kompatibel berichtet wurde.
  2091. </li><br>
  2092. <li><code>statusHTML</code>
  2093. <br>
  2094. wenn gesetzt, dann wird ein HTML-formatierter Wert "floorplanHTML" erzeugt,
  2095. welcher vom Modul <a href="#FLOORPLAN">FLOORPLAN</a> genutzt werden kann.<br>
  2096. Momentan wird nur gepr&uuml;ft, ob der Wert dieses Attributes ungleich NULL ist,
  2097. der entsprechende Ger&auml;tewerte besteht aus dem aktuellen W&auml;rmepumpenstatus und der Heizwassertemperatur.
  2098. </li><br>
  2099. <li><a href="#readingFnAttributes">readingFnAttributes</a>
  2100. </li><br>
  2101. </ul>
  2102. </ul>
  2103. </div>
  2104. =end html_DE
  2105. =cut