23_LUXTRONIK2.pm 104 KB

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