23_KOSTALPIKO.pm 37 KB

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