23_KOSTALPIKO.pm 37 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241
  1. # $Id: 23_KOSTALPIKO.pm 15368 2017-10-31 19:00:55Z John $
  2. ####################################################################################################
  3. #
  4. # 23_KOSTALPIKO.pm
  5. #
  6. # This modul supports the KOSTAL Piko Inverter.
  7. # All Value of Piko's Home-page are captured.
  8. #
  9. # Futhermore the Global-Radion value is captured from http://www.proplanta.de/Wetter/<city>-Wetter-Heute.html
  10. # so the expected energy ca be estimated
  11. #
  12. # 2013-06-28 john : added some snippets for getting all readings
  13. # : added UndefFn
  14. # 2013-06-28 john : global radiation support; updated hourly; needs attribute GR.Link
  15. # the link must have the form of: http://www.proplanta.de/Wetter/<city>-Wetter-Heute.html
  16. # take a look to the site http://www.proplanta.de
  17. # you can calculate the expected daily power by using userReadings
  18. # Daily.Energy.Last is updated once at the hour 23
  19. # 2013-06-28 john : Delay.Counter added
  20. # will be decremented until 0,
  21. # if not 0, then only AC.Power is scanned, otherwise alle Values are scanned
  22. # 2013-07-02 john : AC.Power.Fast added
  23. # 2013-07-14 john : some fixes with minor priority
  24. # 2014-06-01 john V2.00 : adaption to common developer standards
  25. # attribute changes
  26. # - verbose is supported instead of loglevel
  27. # - disable is supported
  28. # - new attribute : GRIntervall : intervall for capturing global radiation
  29. # new software-design
  30. # - non-blocking calls for capturing and parsing of html-pages
  31. # - reducing side-effects for other devices due timeouts
  32. # 2014-06-29 john V2.01 : supporting sensor values for http://<name>/Info.fhtml
  33. # 2014-06-05 john V2.02 : supporting UV-Index and sunshine duration
  34. # 2014-07-05 john V2.03 : fix: value extraction was faulty
  35. # 2014-09-08 john V2.04 : fix: device name with dot made trouble (checked against Kostal Pikos Firmware 10.1)
  36. # adjusting KOSTALPIKO_Log
  37. # Inital Checkin to FHEM ; docu revised
  38. # 2014-09-08 john V2.05 : support of battery option; developed by jannik_78
  39. # 2014-12-22 john V2.06 : checked HTML
  40. # 2015-01-25 john V2.07 : adjusted argument agent for http-request of proplanta (thanks to framller)
  41. # 2016-02-25 john V2.08 : support of Piko 7 with only 2 strings instead of 3 (thanks to erwin)
  42. # 2016-02-25 john V2.09 : substitution of term given/when with if/then
  43. # 2017-19-26 john V2.10 : support of https
  44. ####################################################################################################
  45. # --------------------------------------------
  46. # parser for the site http://<ip-kostal>/index.fhtml
  47. package MyParser;
  48. use base qw(HTML::Parser);
  49. our @texte = ();
  50. my $isTD = 0;
  51. my $takeNext = 0;
  52. # is called if a text content is detected
  53. # results in an array of string with alternating description / value
  54. sub text
  55. {
  56. my ( $self, $text ) = @_;
  57. if ( $isTD == 1 ) # if we are inside a TD-Tag
  58. {
  59. $text =~ s/^\s+//; # trim string
  60. $text =~ s/\s+$//;
  61. if ( $takeNext == 1 ) # first text is description, next text is value
  62. {
  63. $takeNext = 0;
  64. push( @texte, $text );
  65. }
  66. # filter only interesting captions
  67. if ( $text eq "aktuell"
  68. || $text eq "Gesamtenergie"
  69. || $text eq "Tagesenergie"
  70. || $text eq "Status"
  71. || $text eq "Spannung"
  72. || $text eq "Strom"
  73. || $text eq "Leistung" )
  74. {
  75. $takeNext = 1; # expect next tag as value
  76. push( @texte, $text );
  77. }
  78. }
  79. }
  80. # callback, if start tag is detected
  81. sub start
  82. {
  83. my ( $self, $tagname, $attr, $attrseq, $origtext ) = @_;
  84. # we are only interested on TD-Tags
  85. if ( $tagname eq 'td' )
  86. {
  87. $isTD = 1;
  88. } else
  89. {
  90. $isTD = 0;
  91. }
  92. }
  93. # after end-tag reset TD-marker
  94. sub end
  95. {
  96. $isTD = 0;
  97. }
  98. # --------------------------------------------
  99. # parser for the site http://<ip-kostal>/BA.fhtml
  100. package MyBatteryParser;
  101. use base qw(HTML::Parser);
  102. our @texte = ();
  103. my $isTD = 0;
  104. my $isBold = 0;
  105. my $takeNext = 0;
  106. # is called if a text content is detected
  107. # results in an array of string with alternating description / value
  108. sub text
  109. {
  110. my ( $self, $text ) = @_;
  111. if ( $isTD == 1 ) # if we are inside a TD-Tag
  112. {
  113. # filter only interesting captions
  114. if ( $text eq "Ladezustand:"
  115. || $text eq "Spannung:"
  116. || $text eq "Ladestrom:"
  117. || $text eq "Temperatur:"
  118. || $text eq "Zyklenanzahl:"
  119. || $text eq "Solargenerator:"
  120. || $text eq "Batterie:"
  121. || $text eq "Netz:"
  122. || $text eq "Phase 1:"
  123. || $text eq "Phase 2:"
  124. || $text eq "Phase 3:" )
  125. {
  126. $takeNext = 1; # expect next tag as value
  127. push( @texte, $text );
  128. }
  129. }
  130. if ( $isBold == 1 && $takeNext == 1 )
  131. {
  132. $takeNext = 0;
  133. $text =~ s/[^0-9\.]//g;
  134. push( @texte, $text );
  135. }
  136. }
  137. # callback, if start tag is detected
  138. sub start
  139. {
  140. my ( $self, $tagname, $attr, $attrseq, $origtext ) = @_;
  141. # we are only interested on TD-Tags
  142. $isTD = 0;
  143. $isBold = 0;
  144. if ( $tagname eq 'td' )
  145. {
  146. $isTD = 1;
  147. }
  148. if ( $tagname eq 'b' )
  149. {
  150. $isBold = 1;
  151. }
  152. }
  153. # after end-tag reset TD-marker
  154. sub end
  155. {
  156. $isTD = 0;
  157. }
  158. ###############################################
  159. # parser for the global radiation
  160. package MyRadiationParser;
  161. use base qw(HTML::Parser);
  162. our @texte = ();
  163. my $lookupTag = "span";
  164. my $curTag = "";
  165. my $takeNext = 0;
  166. # here HTML::text/start/end are overridden
  167. sub text
  168. {
  169. my ( $self, $text ) = @_;
  170. if ( $curTag eq $lookupTag )
  171. {
  172. $text =~ s/^\s+//; # trim string
  173. $text =~ s/\s+$//;
  174. if ( $takeNext == 1 )
  175. {
  176. $takeNext = 0;
  177. push( @texte, $text );
  178. }
  179. if ( $text eq "Globalstrahlung" )
  180. {
  181. $takeNext = 1;
  182. push( @texte, $text );
  183. } elsif ( $text eq "UV-Index" )
  184. {
  185. $takeNext = 1;
  186. push( @texte, $text );
  187. } elsif ( $text eq "rel. Sonnenscheindauer" )
  188. {
  189. $takeNext = 1;
  190. push( @texte, $text );
  191. }
  192. }
  193. }
  194. sub start
  195. {
  196. my ( $self, $tagname, $attr, $attrseq, $origtext ) = @_;
  197. $curTag = $tagname;
  198. }
  199. sub end
  200. {
  201. $curTag = "";
  202. }
  203. ##############################################
  204. # parser for the site http://<kostal-piko-ip>/Info.fhtml with sensor values
  205. package MyInfoParser;
  206. use base qw(HTML::Parser);
  207. our @texte = ();
  208. my $isTD = 0;
  209. my $isBold = 0;
  210. my $takeNext = 0;
  211. # is called if a text content is detected
  212. sub text
  213. {
  214. my ( $self, $text ) = @_;
  215. if ( $isTD == 1 ) # if we are inside a TD-Tag
  216. {
  217. # filter only interesting captions
  218. if ( $text =~ m/.*Eingang.*/ )
  219. {
  220. $takeNext = 1; # expect next tag as value
  221. push( @texte, $text );
  222. }
  223. }
  224. if ( $isBold == 1 && $takeNext == 1 )
  225. {
  226. $takeNext = 0;
  227. $text =~ s/^\s+//; # trim string
  228. $text =~ s/\s+$//;
  229. $text =~ m/([0-9]+\.[0-9]+)/; # find substring 0.00V : 0.00
  230. my $value = $1;
  231. push( @texte, $value );
  232. }
  233. }
  234. # callback, if start tag is detected
  235. sub start
  236. {
  237. my ( $self, $tagname, $attr, $attrseq, $origtext ) = @_;
  238. # we are only interested on TD-Tags
  239. $isTD = 0;
  240. $isBold = 0;
  241. if ( $tagname eq 'td' )
  242. {
  243. $isTD = 1;
  244. }
  245. if ( $tagname eq 'b' )
  246. {
  247. $isBold = 1;
  248. }
  249. }
  250. # after end-tag reset TD-marker
  251. sub end
  252. {
  253. $isTD = 0;
  254. }
  255. ##############################################
  256. package main;
  257. use strict;
  258. use feature qw/say switch/;
  259. use warnings;
  260. use Data::Dumper;
  261. use LWP::UserAgent;
  262. use HTTP::Request;
  263. require 'Blocking.pm';
  264. require 'HttpUtils.pm';
  265. use vars qw($readingFnAttributes);
  266. use vars qw(%defs);
  267. my $MODUL = "KOSTALPIKO";
  268. my $KOSTAL_VERSION = "2.10";
  269. ########################################
  270. sub KOSTALPIKO_Log($$$)
  271. {
  272. my ( $hash, $loglevel, $text ) = @_;
  273. my $xline = ( caller(0) )[2];
  274. my $xsubroutine = ( caller(1) )[3];
  275. my $sub = ( split( ':', $xsubroutine ) )[2];
  276. $sub =~ s/KOSTALPIKO_//;
  277. my $instName = ( ref($hash) eq "HASH" ) ? $hash->{NAME} : $MODUL;
  278. Log3 $hash, $loglevel, "$MODUL $instName: $sub.$xline " . $text;
  279. }
  280. ###################################
  281. sub KOSTALPIKO_Initialize($)
  282. {
  283. my ($hash) = @_;
  284. $hash->{DefFn} = "KOSTALPIKO_Define";
  285. $hash->{UndefFn} = "KOSTALPIKO_Undef";
  286. $hash->{SetFn} = "KOSTALPIKO_Set";
  287. $hash->{AttrList} =
  288. "delay " . "delayCounter " . "GR.Link " . "GR.Interval " . "disable:0,1 " . "BAEnable:0,1 " . $readingFnAttributes;
  289. }
  290. ###################################
  291. sub KOSTALPIKO_Define($$)
  292. {
  293. my ( $hash, $def ) = @_;
  294. my $name = $hash->{NAME};
  295. my @a = split( "[ \t][ \t]*", $def );
  296. my $host = $a[2];
  297. my $user = $a[3];
  298. my $pass = $a[4];
  299. if ( int(@a) < 5 )
  300. {
  301. return "Wrong syntax: use define <name> KOSTALPIKO <ip-address> <user> <pass>";
  302. }
  303. $hash->{VERSION} = $KOSTAL_VERSION;
  304. $hash->{helper}{Host} = $host;
  305. $hash->{helper}{User} = $user;
  306. $hash->{helper}{Pass} = $pass;
  307. $hash->{helper}{GRHour} = 25;
  308. $hash->{helper}{TimerStatus} = $name . ".STATUS"; # like "Kostal.STATUS"
  309. $hash->{helper}{TimerGR} = $name . ".GR";
  310. InternalTimer( gettimeofday() + 10, "KOSTALPIKO_StatusTimer", $hash->{helper}{TimerStatus}, 0 );
  311. InternalTimer( gettimeofday() + 20, "KOSTALPIKO_GrTimer", $hash->{helper}{TimerGR}, 0 );
  312. return undef;
  313. }
  314. #####################################
  315. sub KOSTALPIKO_Undef($$)
  316. {
  317. my ( $hash, $arg ) = @_;
  318. RemoveInternalTimer( $hash->{helper}{TimerStatus} );
  319. RemoveInternalTimer( $hash->{helper}{TimerGR} );
  320. BlockingKill( $hash->{helper}{RUNNING_STATUS} ) if ( defined( $hash->{helper}{RUNNING_STATUS} ) );
  321. BlockingKill( $hash->{helper}{RUNNING_GR} ) if ( defined( $hash->{helper}{RUNNING_GR} ) );
  322. KOSTALPIKO_Log $hash, 3, "--- done ---";
  323. return undef;
  324. }
  325. #####################################
  326. sub KOSTALPIKO_Set($@)
  327. {
  328. my ( $hash, @a ) = @_;
  329. my $name = $hash->{NAME};
  330. my $reUINT = '^([\\+]?\\d+)$';
  331. my $usage = "Unknown argument $a[1], choose one of captureKostalData:noArg ";
  332. my $URL = AttrVal( $name, 'GR.Link', "" );
  333. if ($URL)
  334. {
  335. $usage .= "captureGlobalRadiation:noArg ";
  336. }
  337. # for debugging issues
  338. # $usage .= "test:noArg sleeper ";
  339. return $usage if ( @a < 2 );
  340. my $cmd = lc( $a[1] );
  341. if ( $cmd eq "?" )
  342. {
  343. return $usage;
  344. } elsif ( $cmd eq "capturekostaldata" )
  345. {
  346. KOSTALPIKO_Log $hash, 3, "set command: " . $a[1] . " para:" . $hash->{helper}{TimerStatus};
  347. KOSTALPIKO_StatusStart($hash);
  348. } elsif ( $cmd eq "captureglobalradiation" )
  349. {
  350. KOSTALPIKO_Log $hash, 3, "set command: " . $a[1];
  351. KOSTALPIKO_GrStart($hash);
  352. } elsif ( $cmd eq "test" )
  353. {
  354. KOSTALPIKO_Log $hash, 3, "set command: " . $a[1];
  355. KOSTALPIKO_GrStart($hash);
  356. } elsif ( $cmd eq "sleeper" )
  357. {
  358. return "Set sleeper needs a <value> parameter"
  359. if ( @a != 3 );
  360. my $value = $a[2];
  361. $value = ( $value =~ m/$reUINT/ ) ? $1 : undef;
  362. return "value " . $a[2] . " is not a number"
  363. if ( !defined($value) );
  364. KOSTALPIKO_Log $hash, 3, "set command: " . $a[1] . " value:" . $a[2];
  365. $hash->{helper}{Sleeper} = $a[2];
  366. } else
  367. {
  368. return $usage;
  369. }
  370. return;
  371. }
  372. #############################################
  373. # get hour as number, input is a serial date
  374. sub KOSTAL_GetHourSD($)
  375. {
  376. my @t = localtime(shift);
  377. return $t[2];
  378. }
  379. #############################################
  380. # current datetime round off to current hour
  381. sub KOSTAL_GetDateTrunc($)
  382. {
  383. my @t = localtime(shift);
  384. return sprintf( "%04d-%02d-%02d %02d:%02d:%02d", $t[5] + 1900, $t[4] + 1, $t[3], $t[2], 0, 0 );
  385. }
  386. #############################################
  387. # converts string-datetime to serial-datetime
  388. # input: datetime as string
  389. # output: serial datetime
  390. sub KOSTAL_DateStr2Serial($)
  391. {
  392. my $datestr = shift;
  393. my ( $yyyy, $mm, $dd, $hh, $mi, $ss ) = $datestr =~ /(\d+)-(\d+)-(\d+) (\d+)[:](\d+)[:](\d+)/;
  394. # months are zero based
  395. my $t2 = fhemTimeLocal( $ss, $mi, $hh, $dd, $mm - 1, $yyyy - 1900 );
  396. return $t2;
  397. }
  398. #####################################
  399. # acquires the sensor html page of kostalpiko
  400. sub KOSTALPIKO_SensorHtmlAcquire($)
  401. {
  402. my ($hash) = @_;
  403. return unless ( defined( $hash->{NAME} ) );
  404. my $err_log = '';
  405. my $URL =
  406. "http://" . $hash->{helper}{User} . ":" . $hash->{helper}{Pass} . "\@" . $hash->{helper}{Host} . "/Info.fhtml";
  407. KOSTALPIKO_Log $hash, 4, "$URL";
  408. my $agent = LWP::UserAgent->new( env_proxy => 1, keep_alive => 1, timeout => 3 );
  409. my $header = HTTP::Request->new( GET => $URL );
  410. my $request = HTTP::Request->new( 'GET', $URL, $header );
  411. my $response = $agent->request($request);
  412. $err_log .= "Can't get $URL -- " . $response->status_line
  413. unless $response->is_success;
  414. if ( $err_log ne "" )
  415. {
  416. KOSTALPIKO_Log $hash, 1, $err_log;
  417. return "";
  418. }
  419. return $response->content;
  420. }
  421. #####################################
  422. # acquires the battery html page of kostalpiko
  423. sub KOSTALPIKO_BatteryHtmlAcquire($)
  424. {
  425. my ($hash) = @_;
  426. return unless ( defined( $hash->{NAME} ) );
  427. my $err_log = '';
  428. my $URL =
  429. "http://" . $hash->{helper}{User} . ":" . $hash->{helper}{Pass} . "\@" . $hash->{helper}{Host} . "/BA.fhtml";
  430. # $URL = "http://192.168.178.20/XBA.html"; # for testing only uncomment
  431. KOSTALPIKO_Log $hash, 4, "$URL";
  432. my $agent = LWP::UserAgent->new( env_proxy => 1, keep_alive => 1, timeout => 3 );
  433. my $header = HTTP::Request->new( GET => $URL );
  434. my $request = HTTP::Request->new( 'GET', $URL, $header );
  435. my $response = $agent->request($request);
  436. $err_log .= "Can't get $URL -- " . $response->status_line
  437. unless $response->is_success;
  438. if ( $err_log ne "" )
  439. {
  440. KOSTALPIKO_Log $hash, 1, $err_log;
  441. return "";
  442. }
  443. return $response->content;
  444. }
  445. #####################################
  446. # acquires the html page of kostalpiko
  447. sub KOSTALPIKO_StatusHtmlAcquire($)
  448. {
  449. my ($hash) = @_;
  450. my $name = $hash->{NAME};
  451. return unless ( defined( $hash->{NAME} ) );
  452. my $err_log = '';
  453. my $URL =
  454. "http://" . $hash->{helper}{User} . ":" . $hash->{helper}{Pass} . "\@" . $hash->{helper}{Host} . "/index.fhtml";
  455. KOSTALPIKO_Log $hash, 4, "$URL";
  456. my $agent = LWP::UserAgent->new( env_proxy => 1, keep_alive => 1, timeout => 3 );
  457. my $header = HTTP::Request->new( GET => $URL );
  458. my $request = HTTP::Request->new( 'GET', $URL, $header );
  459. my $response = $agent->request($request);
  460. $err_log .= "Can't get $URL -- " . $response->status_line
  461. unless $response->is_success;
  462. if ( $err_log ne "" )
  463. {
  464. KOSTALPIKO_Log $hash, 1, $err_log;
  465. return "";
  466. }
  467. return $response->content;
  468. }
  469. #####################################
  470. sub KOSTALPIKO_StatusStart($)
  471. {
  472. my ($hash) = @_;
  473. my $name = $hash->{NAME};
  474. return unless ( defined( $hash->{NAME} ) );
  475. my $err_log = '';
  476. my $sdCurTime = gettimeofday();
  477. my $hour = KOSTAL_GetHourSD($sdCurTime);
  478. my $disable = AttrVal( $name, "disable", 0 );
  479. my $delay = AttrVal( $name, "delay", 300 );
  480. while (1)
  481. {
  482. KOSTALPIKO_Log $hash, 3, "--- started ---";
  483. # check disable attribute
  484. if ( $disable == 1 )
  485. {
  486. KOSTALPIKO_Log $hash, 3, "disabled";
  487. last;
  488. }
  489. if ( !defined( $hash->{helper}{delayCounter} ) )
  490. {
  491. $hash->{helper}{delayCounter} = AttrVal( $name, "delayCounter", "0" );
  492. }
  493. # wenn delayCounter aktiv
  494. if ( $hash->{helper}{delayCounter} > 0 )
  495. {
  496. $hash->{helper}{delayCounter}--;
  497. }
  498. $hash->{helper}{RUNNING_STATUS} = BlockingCall(
  499. "KOSTALPIKO_StatusRun", # callback worker task
  500. $name, # name of the device
  501. "KOSTALPIKO_StatusDone", # callback result method
  502. 50, # timeout seconds
  503. "KOSTALPIKO_StatusAborted", # callback for abortion
  504. $hash
  505. ); # parameter for abortion
  506. last;
  507. }
  508. KOSTALPIKO_Log $hash, 3, "--- done ---";
  509. }
  510. #####################################
  511. sub KOSTALPIKO_StatusRun($)
  512. {
  513. my ($string) = @_;
  514. my ( $name, $server ) = split( "\\|", $string );
  515. my $level = 5;
  516. return unless ( defined($name) );
  517. my $hash = $defs{$name};
  518. return unless ( defined( $hash->{NAME} ) );
  519. KOSTALPIKO_Log $hash, 3, "--- started ---";
  520. # acquire the html-page
  521. my $response = KOSTALPIKO_StatusHtmlAcquire($hash);
  522. # perform parsing
  523. #KOSTALPIKO_Log $hash, $level, "before parsing of response-Len:".length($response);
  524. my $parser = MyParser->new;
  525. @MyParser::texte = ();
  526. # parsing the complete html-page-response, needs some time
  527. # only <td> tags will be regarded
  528. $parser->parse($response);
  529. # for testing issues
  530. if ( defined( $hash->{helper}{Sleeper} ) )
  531. {
  532. my $sleep = $hash->{helper}{Sleeper};
  533. $hash->{helper}{Sleeper} = 0;
  534. sleep($sleep) if ( $sleep > 0 );
  535. }
  536. # pack the results in a single string
  537. my $ptext = $name;
  538. foreach my $text (@MyParser::texte)
  539. {
  540. $ptext = $ptext . "|" . $text;
  541. }
  542. #---------------------------- Sensor values
  543. $response = KOSTALPIKO_SensorHtmlAcquire($hash);
  544. $parser = MyInfoParser->new;
  545. @MyInfoParser::texte = ();
  546. $parser->parse($response);
  547. foreach my $text (@MyInfoParser::texte)
  548. {
  549. $ptext = $ptext . "|" . $text;
  550. }
  551. #---------------------------- battery values
  552. if ( AttrVal( $name, 'BAEnable', 0 ) == 1 )
  553. {
  554. $response = KOSTALPIKO_BatteryHtmlAcquire($hash);
  555. $parser = MyBatteryParser->new;
  556. @MyBatteryParser::texte = ();
  557. $parser->parse($response);
  558. foreach my $text (@MyBatteryParser::texte)
  559. {
  560. $ptext = $ptext . "|" . $text;
  561. }
  562. }
  563. #------------------------------ aquire is finished
  564. KOSTALPIKO_Log $hash, 3, "--- done ---";
  565. return $ptext;
  566. }
  567. #####################################
  568. # assyncronous callback by blocking
  569. sub KOSTALPIKO_StatusDone($)
  570. {
  571. my ($string) = @_;
  572. return unless ( defined($string) );
  573. # need to do this before split !!!
  574. my @nVoltages = $string =~ m/Spannung/g; ##MH how often did we find the word Spannung?
  575. my $strangCount = int( @nVoltages / 2 ); # the number of strings
  576. # all term are separated by "|" , the first ist the name of the instance
  577. my ( $name, @values ) = split( "\\|", $string );
  578. my $hash = $defs{$name};
  579. return unless ( defined( $hash->{NAME} ) );
  580. KOSTALPIKO_Log $hash, 3, '--- started --- with numStrings:' . $strangCount;
  581. # show the values
  582. KOSTALPIKO_Log $hash, 5, "values:" . join( ', ', @values );
  583. # delete the marker for running process
  584. delete( $hash->{helper}{RUNNING_STATUS} );
  585. #------------------
  586. while (1)
  587. {
  588. my $tag = ""; # der Name des parameters in der web site
  589. my $index = 0; # laufindex von 1..4 f. String x und Lx
  590. my $strang = 1; # gruppe String<n>/ L<n>
  591. my $rdName = ""; # name for reading
  592. my $rdValue; # value for reading
  593. my %hashValues = (); # hash for name,value
  594. my $sdCurTime = gettimeofday();
  595. my $hour = KOSTAL_GetHourSD($sdCurTime);
  596. foreach my $text (@values)
  597. {
  598. if ( $text eq "aktuell"
  599. || $text eq "Gesamtenergie"
  600. || $text eq "Tagesenergie"
  601. || $text eq "Status"
  602. || $text =~ m/.*analoger Eingang.*/
  603. || $text eq "Ladezustand:"
  604. || $text eq "Spannung:"
  605. || $text eq "Ladestrom:"
  606. || $text eq "Temperatur:"
  607. || $text eq "Zyklenanzahl:"
  608. || $text eq "Solargenerator:"
  609. || $text eq "Batterie:"
  610. || $text eq "Netz:"
  611. || $text eq "Phase 1:"
  612. || $text eq "Phase 2:"
  613. || $text eq "Phase 3:" )
  614. {
  615. $tag = $text; # remember the identifier
  616. } elsif ( $text eq "Spannung" || $text eq "Strom" || $text eq "Leistung" )
  617. {
  618. $index++;
  619. # there are max 4 values per group
  620. if ( $index > 4 )
  621. {
  622. $strang++;
  623. $index = 1;
  624. }
  625. $tag = $text; # remember the identifier
  626. } else
  627. {
  628. if ( $tag ne "" ) # last text was a identifier, so we expect a value
  629. {
  630. $rdValue = $text;
  631. # translate the identifier of the html.page to internal identifiers
  632. $rdName = "AC.Power" if ( $tag eq "aktuell" );
  633. $rdName = "Total.Energy" if ( $tag eq "Gesamtenergie" );
  634. $rdName = "Daily.Energy" if ( $tag eq "Tagesenergie" );
  635. $rdName = "Mode" if ( $tag eq "Status" );
  636. # MH change for PIKO7 (2 Strings only / should work for 3 string PIKO's
  637. if ( $tag eq "Spannung" )
  638. {
  639. $rdName = "output.$strang.voltage" if ( $index == 2 );
  640. if ( $index == 1 )
  641. {
  642. if ( $strang <= $strangCount )
  643. {
  644. $rdName = "generator.$strang.voltage";
  645. } else
  646. {
  647. # useful for PIKO7 with 2 Strings only
  648. $rdName = "output.$strang.voltage";
  649. }
  650. }
  651. }
  652. $rdName = "generator.$strang.current" if ( $tag eq "Strom" );
  653. $rdName = "output.$strang.power" if ( $tag eq "Leistung" );
  654. $rdName = "sensor.1" if ( $tag eq "1. analoger Eingang:" );
  655. $rdName = "sensor.2" if ( $tag eq "2. analoger Eingang:" );
  656. $rdName = "sensor.3" if ( $tag eq "3. analoger Eingang:" );
  657. $rdName = "sensor.4" if ( $tag eq "4. analoger Eingang:" );
  658. # BA.fhtml
  659. $rdName = "Battery.StateOfCharge" if ( $tag eq "Ladezustand:" );
  660. $rdName = "Battery.Voltage" if ( $tag eq "Spannung:" );
  661. $rdName = "Battery.ChargeCurrent" if ( $tag eq "Ladestrom:" );
  662. $rdName = "Battery.Temperature" if ( $tag eq "Temperatur:" );
  663. $rdName = "Battery.CycleCount" if ( $tag eq "Zyklenanzahl:" );
  664. $rdName = "Power.Solar" if ( $tag eq "Solargenerator:" );
  665. $rdName = "Power.Battery" if ( $tag eq "Batterie:" );
  666. $rdName = "Power.Net" if ( $tag eq "Netz:" );
  667. $rdName = "Power.Phase1" if ( $tag eq "Phase 1:" );
  668. $rdName = "Power.Phase2" if ( $tag eq "Phase 2:" );
  669. $rdName = "Power.Phase3" if ( $tag eq "Phase 3:" );
  670. # set 0, if "x x x" is given
  671. $rdValue = 0 if ( index( $rdValue, "x x x" ) != -1 );
  672. # add the pair of identifier and value to the hash
  673. $hashValues{$rdName} = $rdValue;
  674. #special treatment for fast value
  675. $hashValues{ $rdName . ".Fast" } = $rdValue if ( $rdName eq "AC.Power" );
  676. $tag = ""; # next text will be an identifier
  677. $rdName = "";
  678. }
  679. }
  680. } # foreach
  681. # add the state for reading update
  682. $rdValue = "W: " . $hashValues{"AC.Power"} . " - " . $hashValues{"Mode"};
  683. $hashValues{state} = $rdValue;
  684. # set the ModeNum
  685. my $NMode = 9;
  686. $rdValue = $hashValues{"Mode"};
  687. $NMode = 0 if ( $rdValue eq "Aus" );
  688. $NMode = 1 if ( $rdValue eq "Leerlauf" );
  689. $NMode = 2 if ( $rdValue eq "Einspeisen MPP" );
  690. $hashValues{ModeNum} = $NMode;
  691. # Daily.Energy.Last, remember the last value of dayly energy
  692. # check from 23 hour
  693. if ( defined( $hash->{READINGS}{"Daily.Energy"} ) && $hour == 23 )
  694. {
  695. my $ss = KOSTAL_GetDateTrunc($sdCurTime); # string date rounded to hour
  696. my $sdDateTrunc = KOSTAL_DateStr2Serial($ss); # string date to serial date
  697. $ss = ReadingsTimestamp( $name, "Daily.Energy.Last", $ss ); # determine reading timestamp
  698. my $sdEnergyLast = KOSTAL_DateStr2Serial($ss); # serial format
  699. KOSTALPIKO_Log $hash, 5, "DateTrunc : $ss sdDateTrunc: $sdDateTrunc sdEnergyLast:$sdEnergyLast";
  700. if ( $sdEnergyLast <= $sdDateTrunc )
  701. {
  702. KOSTALPIKO_Log $hash, 4, "update Daily.Energy.Last with " . $hash->{READINGS}{"Daily.Energy"}{VAL};
  703. readingsSingleUpdate( $hash, "Daily.Energy.Last", $hash->{READINGS}{"Daily.Energy"}{VAL}, 1 );
  704. }
  705. }
  706. # update readings
  707. my $upd;
  708. readingsBeginUpdate($hash);
  709. foreach my $xxx ( sort keys %hashValues )
  710. {
  711. $upd = 0;
  712. # update if reading not exists or if new/old value differs
  713. if ( !defined( $hash->{READINGS}{$xxx}{VAL} ) || $hash->{READINGS}{$xxx}{VAL} ne $hashValues{$xxx} )
  714. {
  715. # AC.Power.FAst will every time updated, the others only, if delaycount is 0
  716. if ( $xxx eq "AC.Power.Fast" || $hash->{helper}{delayCounter} == 0 )
  717. {
  718. readingsBulkUpdate( $hash, $xxx, $hashValues{$xxx} );
  719. $upd = 1;
  720. }
  721. }
  722. KOSTALPIKO_Log $hash, 4, "$xxx: $hashValues{ $xxx } upd:$upd";
  723. }
  724. readingsEndUpdate( $hash, 1 );
  725. last;
  726. }
  727. # wir arbeiten mit delay counter
  728. if ( AttrVal( $name, "delayCounter", "0" ) ne "0" && $hash->{helper}{delayCounter} == 0 )
  729. {
  730. $hash->{helper}{delayCounter} = AttrVal( $name, "delayCounter", "0" );
  731. KOSTALPIKO_Log $hash, 3, "delayCounter restarted";
  732. }
  733. KOSTALPIKO_Log $hash, 3, "--- done ---";
  734. }
  735. #####################################
  736. sub KOSTALPIKO_StatusAborted($)
  737. {
  738. my ($hash) = @_;
  739. delete( $hash->{helper}{RUNNING_STATUS} );
  740. KOSTALPIKO_Log $hash, 3, "--- done ---";
  741. }
  742. #####################################
  743. sub KOSTALPIKO_StatusTimer($)
  744. {
  745. my ($timerpara) = @_;
  746. #my ( $name, $func ) = split( /\./, $timerpara );
  747. my $index = rindex( $timerpara, "." ); # rechter punkt
  748. my $func = substr $timerpara, $index + 1, length($timerpara); # function extrahieren
  749. my $name = substr $timerpara, 0, $index; # name extrahieren
  750. my $hash = $defs{$name};
  751. #KOSTALPIKO_Log "", 3, "--- started --- name:$name";
  752. return unless ( defined( $hash->{NAME} ) );
  753. KOSTALPIKO_Log $hash, 3, "--- started ---";
  754. KOSTALPIKO_StatusStart($hash);
  755. $hash->{helper}{TimerInterval} = AttrVal( $name, "delay", 60 );
  756. # setup timer
  757. RemoveInternalTimer( $hash->{helper}{TimerStatus} );
  758. InternalTimer( gettimeofday() + $hash->{helper}{TimerInterval},
  759. "KOSTALPIKO_StatusTimer", $hash->{helper}{TimerStatus}, 0 );
  760. KOSTALPIKO_Log $hash, 3, "--- done ---";
  761. }
  762. #####################################
  763. # acquires the html page of Global radiation
  764. sub KOSTALPIKO_GrHtmlAcquire($)
  765. {
  766. my ($hash) = @_;
  767. my $name = $hash->{NAME};
  768. return unless ( defined( $hash->{NAME} ) );
  769. my $URL = AttrVal( $name, 'GR.Link', "" );
  770. # abbrechen, wenn wichtig parameter nicht definiert sind
  771. return "" if ( !defined($URL) );
  772. return "" if ( $URL eq "" );
  773. my $err_log = "";
  774. # my $agent = LWP::UserAgent->new( env_proxy => 1, keep_alive => 1, timeout => 3 );
  775. my $agent = LWP::UserAgent->new(
  776. env_proxy => 1,
  777. keep_alive => 1,
  778. protocols_allowed => ['http','https'],
  779. timeout => 10,
  780. agent => "Mozilla/5.0 (Windows NT 5.1) [de-DE,de;q=0.8,en-US;q=0.6,en;q=0.4]"
  781. );
  782. my $header = HTTP::Request->new( GET => $URL );
  783. my $request = HTTP::Request->new( 'GET', $URL, $header );
  784. my $response = $agent->request($request);
  785. $err_log = "Can't get $URL -- " . $response->status_line
  786. unless $response->is_success;
  787. if ( $err_log ne "" )
  788. {
  789. KOSTALPIKO_Log $hash, 1, "Error: $err_log";
  790. return "";
  791. }
  792. return $response->content;
  793. }
  794. #####################################
  795. sub KOSTALPIKO_GrStart($)
  796. {
  797. my ($hash) = @_;
  798. my $name = $hash->{NAME};
  799. return unless ( defined( $hash->{NAME} ) );
  800. return if ( AttrVal( $name, 'GR.Link', "" ) eq "" );
  801. while (1)
  802. {
  803. KOSTALPIKO_Log $hash, 3, "--- started ---";
  804. $hash->{helper}{RUNNING_GR} = BlockingCall(
  805. "KOSTALPIKO_GrRun", # callback worker task
  806. $name, # name of the device
  807. "KOSTALPIKO_GrDone", # callback result method
  808. 50, # timeout seconds
  809. "KOSTALPIKO_GrAborted", # callback for abortion
  810. $hash
  811. ); # parameter for abortion
  812. last;
  813. }
  814. KOSTALPIKO_Log $hash, 3, "--- done ---";
  815. }
  816. #####################################
  817. sub KOSTALPIKO_GrRun($)
  818. {
  819. my ($string) = @_;
  820. my ( $name, $server ) = split( "\\|", $string );
  821. my $ptext = $name;
  822. return unless ( defined($name) );
  823. my $hash = $defs{$name};
  824. return unless ( defined( $hash->{NAME} ) );
  825. KOSTALPIKO_Log $hash, 3, "--- started ---";
  826. while (1)
  827. {
  828. # acquire the html-page
  829. my $response = KOSTALPIKO_GrHtmlAcquire($hash);
  830. last if ( $response eq "" );
  831. my $parser = MyRadiationParser->new;
  832. @MyRadiationParser::texte = ();
  833. # parsing the complete html-page-response, needs some time
  834. # only <td> tags will be regarded
  835. $parser->parse($response);
  836. KOSTALPIKO_Log $hash, 4, "parsed terms:" . @MyRadiationParser::texte;
  837. # pack the results in a single string
  838. foreach my $text (@MyRadiationParser::texte)
  839. {
  840. $ptext = $ptext . "|" . $text;
  841. }
  842. last;
  843. }
  844. KOSTALPIKO_Log $hash, 3, "--- done ---";
  845. return $ptext;
  846. }
  847. #####################################
  848. # assyncronous callback by blocking
  849. sub KOSTALPIKO_GrDone($)
  850. {
  851. my ($string) = @_;
  852. return unless ( defined($string) );
  853. # all term are separated by "|" , the first ist the name of the instance
  854. my ( $name, @values ) = split( "\\|", $string );
  855. my $hash = $defs{$name};
  856. return unless ( defined( $hash->{NAME} ) );
  857. KOSTALPIKO_Log $hash, 3, "--- started ---";
  858. # show the values
  859. KOSTALPIKO_Log $hash, 5, "values:" . join( ', ', @values );
  860. # delete the marker for running process
  861. delete( $hash->{helper}{RUNNING_GR} );
  862. my $tag = "";
  863. my $rdName = "";
  864. my $rdValue = "";
  865. my %hashValues = ();
  866. # nach myRadiation suchen
  867. foreach my $text (@values)
  868. {
  869. if ( $text eq "Globalstrahlung" || $text eq "UV-Index" || $text eq "rel. Sonnenscheindauer" )
  870. {
  871. $tag = $text;
  872. } else
  873. {
  874. if ( $tag ne "" )
  875. {
  876. $rdValue = $text;
  877. $rdValue =~ tr/,/./; # komma gegen punkt tauschen
  878. $rdValue =~ m/([-,\+]?\d+\.?\d*)/; # zahl extrahieren
  879. $rdValue = $1;
  880. $rdName = $tag;
  881. $rdName = "Global.Radiation" if ( $tag eq "Globalstrahlung" );
  882. $rdName = "UV.Index" if ( $tag eq "UV-Index" );
  883. $rdName = "sunshine.duration" if ( $tag eq "rel. Sonnenscheindauer" );
  884. $hashValues{$rdName} = $rdValue;
  885. $tag = "";
  886. KOSTALPIKO_Log $hash, 5, "tag:$rdName value:$rdValue";
  887. }
  888. }
  889. }
  890. my $upd = 1;
  891. # hash sortieren und ausgeben, immer updaten, damit kurve angezeigt wird
  892. readingsBeginUpdate($hash);
  893. foreach my $xxx ( sort keys %hashValues ) # alle schluessel abfragen
  894. {
  895. readingsBulkUpdate( $hash, $xxx, $hashValues{$xxx} ); # alten zustand merken
  896. KOSTALPIKO_Log $hash, 5, "$xxx: $hashValues{ $xxx } upd:$upd";
  897. }
  898. readingsEndUpdate( $hash, 1 );
  899. KOSTALPIKO_Log $hash, 3, "--- done ---";
  900. }
  901. #####################################
  902. sub KOSTALPIKO_GrAborted($)
  903. {
  904. my ($hash) = @_;
  905. delete( $hash->{helper}{RUNNING_GR} );
  906. KOSTALPIKO_Log $hash, 3, "--- done ---";
  907. }
  908. #####################################
  909. sub KOSTALPIKO_GrTimer($)
  910. {
  911. my ($timerpara) = @_;
  912. # my ( $name, $func ) = split( /\./, $timerpara );
  913. my $index = rindex( $timerpara, "." ); # rechter punkt
  914. my $func = substr $timerpara, $index + 1, length($timerpara); # function extrahieren
  915. my $name = substr $timerpara, 0, $index; # name extrahieren
  916. my $hash = $defs{$name};
  917. return unless ( defined( $hash->{NAME} ) );
  918. KOSTALPIKO_Log $hash, 3, "--- started ---";
  919. $hash->{helper}{TimerGRInterval} = AttrVal( $name, "GR.Interval", 3600 );
  920. KOSTALPIKO_GrStart($hash);
  921. # setup timer
  922. RemoveInternalTimer( $hash->{helper}{TimerGR} );
  923. InternalTimer( gettimeofday() + $hash->{helper}{TimerGRInterval}, "KOSTALPIKO_GrTimer", $hash->{helper}{TimerGR}, 0 );
  924. KOSTALPIKO_Log $hash, 3, "--- done ---";
  925. }
  926. #####################################
  927. 1;
  928. =pod
  929. =item summary Module for Kostal Piko Inverter
  930. =begin html
  931. <a name="KOSTALPIKO"></a>
  932. <h3>KOSTALPIKO</h3>
  933. <div>
  934. <a name="KOSTALPIKOdefine" id="KOSTALPIKOdefine"></a> <b>Define</b>
  935. <div>
  936. <br />
  937. <code>define &lt;name&gt; KOSTALPIKO &lt;ip-address&gt; &lt;user&gt; &lt;password&gt;</code><br />
  938. <br />
  939. The module reads the current values from web page of a Kostal Piko inverter.<br />
  940. It can also be used, to capture the values of global radiation, UV-index and sunshine duration<br />
  941. from a special web-site (proplanta) regardless of the existence of the inverter.<br />
  942. <br />
  943. <b>Parameters:</b><br />
  944. <ul>
  945. <li><b>&lt;ip-address&gt;</b> - the ip address of the inverter</li>
  946. <li><b>&lt;user&gt;</b> - the login-user for the inverter's web page</li>
  947. <li><b>&lt;password&gt;</b> - the login-password for the inverter's web page</li>
  948. </ul><br />
  949. <br />
  950. <b>Example:</b><br />
  951. <div>
  952. <code>define Kostal KOSTALPIKO 192.168.2.4 pvserver pvwr</code><br />
  953. </div>
  954. </div><br />
  955. <a name="KOSTALPIKOset" id="KOSTALPIKOset"></a> <b>Set-Commands</b>
  956. <div>
  957. <br />
  958. <code>set &lt;name&gt; captureGlobalRadiation</code><br />
  959. <div>
  960. The values for global radiation, UV-index and sunshine duration are immediately polled.
  961. </div><br />
  962. <br />
  963. <code>set &lt;name&gt; captureKostalData</code><br />
  964. <div>
  965. All values of the inverter are immediately polled.
  966. </div><br />
  967. </div><a name="KOSTALPIKOattr" id="KOSTALPIKOattr"></a> <b>Attributes</b><br />
  968. <br />
  969. <ul>
  970. <li><a href="#readingFnAttributes">readingFnAttributes</a></li>
  971. <li><b>BAEnable</b> - if 1, data from ../BA.fhtml site is captured</li>
  972. <li><b>GR.Interval</b> - poll interval for global radiation in seconds</li>
  973. <li><b>GR.Link</b> - regionalised link the to the proplanta web page (global radiation, UV-index and sunshine
  974. duration)<br />
  975. (see Wiki for further information)</li>
  976. <li><b>delay</b> - poll interval for the values of the inverter in seconds</li>
  977. <li>
  978. <b>delayCounter</b> - delay counter for poll of invert's values beside AC.Power;<br />
  979. needed for fast acquisition scenarios to limit the log-output.
  980. </li>
  981. <li><b>disable</b> - if disable=1, the poll of inverter's values is disabled,<br /> ut not the the poll of proplanta-values</li>
  982. </ul><br />
  983. <br />
  984. <a name="KOSTALPIKOreading" id="KOSTALPIKOreading"></a> <b>Generated Readings/Events</b><br />
  985. <br />
  986. <ul>
  987. <li><b>AC.Power</b> - the current power, captured only if the internal delayCounter = 0</li>
  988. <li><b>AC.Power.Fast</b> - the current power, on each poll cycle; used for fast acquisition scenarios</li>
  989. <li><b>Daily.Energie</b> - the current procduced energie of the day</li>
  990. <li><b>Daily.Energie.Last</b> - the value of daily energy at 23:00 clock</li>
  991. <li><b>Global.Radiation</b> - the value of global radiation (proplanta);useful for determing the expected energy amount of the day</li>
  992. <li><b>ModeNum</b> - the current processing state of the inverter (1=off 2=idle 3=active)</li>
  993. <li><b>Mode</b> - the german term for the current ModeNum</li>
  994. <li><b>Total.Energy</b> - the total produced energie</li>
  995. <li><b>generator.1.current</b> - the electrical current at string 1</li>
  996. <li><b>generator.2.current</b> - the electrical current at string 2</li>
  997. <li><b>generator.3.current</b> - the electrical current at string 3</li>
  998. <li><b>generator.1.voltage</b> - the voltage at string 1</li>
  999. <li><b>generator.2.voltage</b> - the voltage at string 2</li>
  1000. <li><b>generator.3.voltage</b> - the voltage at string 3</li>
  1001. <li><b>output.1.voltage</b> - the voltage at output 1</li>
  1002. <li><b>output.2.voltage</b> - the voltage at output 2</li>
  1003. <li><b>output.3.voltage</b> - the voltage at output 3</li>
  1004. <li><b>output.1.power</b> - the power at output 1</li>
  1005. <li><b>output.2.power</b> - the power at output 2</li>
  1006. <li><b>output.3.power</b> - the power at output 3</li>
  1007. <li><b>sensor.1</b> - the voltage at analog input 1</li>
  1008. <li><b>sensor.2</b> - the voltage at analog input 2</li>
  1009. <li><b>sensor.3</b> - the voltage at analog input 3</li>
  1010. <li><b>sensor.4</b> - the voltage at analog input 4</li>
  1011. <li><b>UV.Index</b> - the UV Index (proplanta)</li>
  1012. <li><b>sunshine.duration</b> - the sunshine duration (proplanta)</li>
  1013. </ul><br />
  1014. <b>Additional Readings/Events, if BAEnable=1</b><br />
  1015. <br />
  1016. <ul>
  1017. <li><b>Battery.CycleCount</b> - count of charge cycles</li>
  1018. <li><b>Battery.StateOfCharge</b> - State of charge for the battery in percent</li>
  1019. <li><b>Battery.Voltage</b> - the voltage of the battery</li>
  1020. <li><b>Battery.ChargeCurrent</b> - the charge current of the battery</li>
  1021. <li><b>Battery.Temperature</b> - the temperature of the battery</li>
  1022. <li><b>Power.Solar</b> - the sum of the power produced by the solarinverter</li>
  1023. <li><b>Power.Battery</b> - the power drawn from the battery</li>
  1024. <li><b>Power.Net</b> - the power drawn from the main</li>
  1025. <li><b>Power.Phase1</b> - the power used on phase L1</li>
  1026. <li><b>Power.Phase2</b> - the power used on phase L2</li>
  1027. <li><b>Power.Phase3</b> - the power used on phase L3</li>
  1028. </ul><br />
  1029. <br />
  1030. <b>Additional information</b><br />
  1031. <br />
  1032. <ul>
  1033. <li><a href="http://forum.fhem.de/index.php/topic,24409.msg175253.html#msg175253">Discussion in FHEM forum</a></li>
  1034. <li><a href="http://www.fhemwiki.de/wiki/KostalPiko#FHEM-Modul">Information in FHEM Wiki</a></li>
  1035. </ul>
  1036. </div>
  1037. =end html
  1038. =cut