77_SMAEM.pm 42 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984
  1. ################################################################################################
  2. # $Id: 77_SMAEM.pm 16201 2018-02-17 15:01:35Z DS_Starter $
  3. #
  4. # Copyright notice
  5. #
  6. # (c) 2016 Copyright: Volker Kettenbach
  7. # e-mail: volker at kettenbach minus it dot de
  8. #
  9. # Credits:
  10. # - DS_Starter (Heiko) for persistent readings
  11. # and various improvements
  12. #
  13. # Description:
  14. # This is an FHEM-Module for the SMA Energy Meter,
  15. # a bidirectional energy meter/counter used in photovoltaics
  16. #
  17. # Requirements:
  18. # This module requires:
  19. # - Perl Module: IO::Socket::Multicast
  20. # On a Debian (based) system, these requirements can be fullfilled by:
  21. # - apt-get install install libio-socket-multicast-perl
  22. #
  23. # Origin:
  24. # https://github.com/kettenbach-it/FHEM-SMA-Speedwire
  25. #
  26. #################################################################################################
  27. # Versions History done by DS_Starter
  28. #
  29. # 3.1.0 12.02.2018 extend error handling in define
  30. # 3.0.1 26.11.2017 use abort cause of BlockingCall
  31. # 3.0.0 29.09.2017 make SMAEM ready for multimeter usage
  32. # 2.9.1 29.05.2017 DbLog_splitFn added, some function names adapted
  33. # 2.9.0 25.05.2017 own SMAEM_setCacheValue, SMAEM_getCacheValue, new internal VERSION
  34. # 2.8.2 03.12.2016 Prefix SMAEMserialnumber for Reading "state" removed, commandref adapted
  35. # 2.8.1 02.12.2016 encode / decode $data
  36. # 2.8 02.12.2016 plausibility check of measured differences, attr diffAccept, timeout
  37. # validation checks, improvement of failure prevention
  38. # 2.7 01.12.2016 logging of discarded cycles
  39. # 2.6 01.12.2016 some improvements, better logging possibility
  40. # 2.5 30.11.2016 some improvements
  41. # 2.4 30.11.2016 some improvements, attributes disable, timeout for BlockingCall added
  42. # 2.3 30.11.2016 SMAEM_getsum, SMAEM_setsum changed
  43. # 2.2 29.11.2016 check error while writing values to file -> set state with error
  44. # 2.1 29.11.2016 move $hash->{GRIDin_SUM}, $hash->{GRIDOUT_SUM} calc to smaread_ParseDone,
  45. # some little improvements to logging process
  46. # 2.0 28.11.2016 switch to nonblocking
  47. package main;
  48. use strict;
  49. use warnings;
  50. use bignum;
  51. use IO::Socket::Multicast;
  52. use Blocking;
  53. my $SMAEMVersion = "3.1.0";
  54. ###############################################################
  55. # SMAEM Initialize
  56. ###############################################################
  57. sub SMAEM_Initialize($) {
  58. my ($hash) = @_;
  59. $hash->{ReadFn} = "SMAEM_Read";
  60. $hash->{DefFn} = "SMAEM_Define";
  61. $hash->{UndefFn} = "SMAEM_Undef";
  62. $hash->{DeleteFn} = "SMAEM_Delete";
  63. $hash->{DbLog_splitFn} = "SMAEM_DbLog_splitFn";
  64. $hash->{AttrFn} = "SMAEM_Attr";
  65. $hash->{AttrList} = "interval ".
  66. "disable:1,0 ".
  67. "diffAccept ".
  68. "disableSernoInReading:1,0 ".
  69. "feedinPrice ".
  70. "powerCost ".
  71. "timeout ".
  72. "$readingFnAttributes";
  73. }
  74. ###############################################################
  75. # SMAEM Define
  76. ###############################################################
  77. sub SMAEM_Define($$) {
  78. my ($hash, $def) = @_;
  79. my $name= $hash->{NAME};
  80. my ($success, $gridin_sum, $gridout_sum);
  81. my $socket;
  82. $hash->{INTERVAL} = 60 ;
  83. $hash->{VERSION} = $SMAEMVersion;
  84. $hash->{HELPER}{FAULTEDCYCLES} = 0;
  85. $hash->{HELPER}{STARTTIME} = time();
  86. Log3 $hash, 3, "SMAEM $name - Opening multicast socket...";
  87. eval {
  88. $socket = IO::Socket::Multicast->new(
  89. Proto => 'udp',
  90. LocalPort => '9522',
  91. ReuseAddr => '1',
  92. ReusePort => defined(&ReusePort) ? 1 : 0,
  93. ); };
  94. if($@) {
  95. Log3 $hash, 1, "SMAEM $name - Can't bind: $@";
  96. return;
  97. }
  98. Log3 $hash, 3, "SMAEM $name - Multicast socket opened";
  99. $socket->mcast_add('239.12.255.254');
  100. $hash->{TCPDev}= $socket;
  101. $hash->{FD} = $socket->fileno();
  102. delete($readyfnlist{"$name"});
  103. $selectlist{"$name"} = $hash;
  104. # gespeicherte Serialnummern lesen und extrahieren
  105. my $retcode = SMAEM_getserials($hash);
  106. $hash->{HELPER}{READFILEERROR} = $retcode if($retcode);
  107. if($hash->{HELPER}{ALLSERIALS}) {
  108. my @allserials = split(/_/,$hash->{HELPER}{ALLSERIALS});
  109. foreach(@allserials) {
  110. my $smaserial = $_;
  111. # gespeicherte Energiezählerwerte von File einlesen
  112. my $retcode = SMAEM_getsum($hash,$smaserial);
  113. $hash->{HELPER}{READFILEERROR} = $retcode if($retcode);
  114. }
  115. }
  116. return undef;
  117. }
  118. ###############################################################
  119. # SMAEM Undefine
  120. ###############################################################
  121. sub SMAEM_Undef($$) {
  122. my ($hash, $arg) = @_;
  123. my $name= $hash->{NAME};
  124. my $socket= $hash->{TCPDev};
  125. BlockingKill($hash->{HELPER}{RUNNING_PID}) if(defined($hash->{HELPER}{RUNNING_PID}));
  126. Log3 $hash, 3, "SMAEM $name - Closing multicast socket...";
  127. $socket->mcast_drop('239.12.255.254');
  128. my $ret = close($hash->{TCPDev});
  129. Log3 $hash, 4, "SMAEM $name - Close-ret: $ret";
  130. delete($hash->{TCPDev});
  131. delete($selectlist{"$name"});
  132. delete($hash->{FD});
  133. return;
  134. }
  135. ###############################################################
  136. # SMAEM Delete
  137. ###############################################################
  138. sub SMAEM_Delete {
  139. my ($hash, $arg) = @_;
  140. my $index = $hash->{TYPE}."_".$hash->{NAME}."_energysum";
  141. # gespeicherte Energiezählerwerte löschen
  142. setKeyValue($index, undef);
  143. return undef;
  144. }
  145. ###############################################################
  146. # SMAEM Attr
  147. ###############################################################
  148. sub SMAEM_Attr {
  149. my ($cmd,$name,$aName,$aVal) = @_;
  150. my $hash = $defs{$name};
  151. my $do;
  152. # $cmd can be "del" or "set"
  153. # $name is device name
  154. # aName and aVal are Attribute name and value
  155. if ($aName eq "interval") {
  156. if($cmd eq "set") {
  157. $hash->{INTERVAL} = $aVal;
  158. } else {
  159. $hash->{INTERVAL} = "60";
  160. }
  161. }
  162. if ($aName eq "disableSernoInReading") {
  163. delete $defs{$name}{READINGS};
  164. readingsSingleUpdate($hash, "state", "initialized", 1);
  165. }
  166. if ($aName eq "timeout" || $aName eq "diffAccept") {
  167. unless ($aVal =~ /^[0-9]+$/) { return " The Value of $aName is not valid. Use only figures 0-9 without decimal places !";}
  168. }
  169. if ($aName eq "disable") {
  170. if($cmd eq "set") {
  171. $do = ($aVal) ? 1 : 0;
  172. }
  173. $do = 0 if($cmd eq "del");
  174. my $val = ($do == 1 ? "disabled" : "initialized");
  175. readingsSingleUpdate($hash, "state", $val, 1);
  176. }
  177. return undef;
  178. }
  179. ###############################################################
  180. # SMAEM Read (Hauptschleife)
  181. ###############################################################
  182. # called from the global loop, when the select for hash->{FD} reports data
  183. sub SMAEM_Read($) {
  184. my ($hash) = @_;
  185. my $name= $hash->{NAME};
  186. my $socket= $hash->{TCPDev};
  187. my $timeout = AttrVal($name, "timeout", 60);
  188. my $data;
  189. return if(IsDisabled($name));
  190. return unless $socket->recv($data, 600); # Each SMAEM packet is 600 bytes of packed payload
  191. return if (time() <= $hash->{HELPER}{STARTTIME}+30);
  192. # decode serial number of dataset received
  193. my $hex = unpack('H*', $data);
  194. my $smaserial = hex(substr($hex,40,8));
  195. return if(!$smaserial);
  196. # alle Serialnummern in HELPER sammeln und ggf. speichern
  197. if(!defined($hash->{HELPER}{ALLSERIALS}) || $hash->{HELPER}{ALLSERIALS} !~ /$smaserial/) {
  198. my $sep = $hash->{HELPER}{ALLSERIALS}?"_":undef;
  199. if($sep) {
  200. $hash->{HELPER}{ALLSERIALS} = $hash->{HELPER}{ALLSERIALS}.$sep.$smaserial;
  201. } else {
  202. $hash->{HELPER}{ALLSERIALS} = $smaserial;
  203. }
  204. SMAEM_setserials($hash);
  205. }
  206. if ( !$hash->{HELPER}{'LASTUPDATE_'.$smaserial} || time() >= ($hash->{HELPER}{'LASTUPDATE_'.$smaserial}+$hash->{INTERVAL}) ) {
  207. Log3 ($name, 4, "SMAEM $name - ##############################################################");
  208. Log3 ($name, 4, "SMAEM $name - ### Begin of new SMA Energymeter $smaserial get data cycle ###");
  209. Log3 ($name, 4, "SMAEM $name - ##############################################################");
  210. Log3 ($name, 4, "SMAEM $name - discarded cycles since module start: $hash->{HELPER}{FAULTEDCYCLES}");
  211. if($hash->{HELPER}{RUNNING_PID}) {
  212. Log3 ($name, 3, "SMAEM $name - WARNING - old process $hash->{HELPER}{RUNNING_PID}{pid} has been killed to start a new BlockingCall");
  213. BlockingKill($hash->{HELPER}{RUNNING_PID});
  214. delete($hash->{HELPER}{RUNNING_PID});
  215. }
  216. # update time
  217. SMAEM_setlastupdate($hash,$smaserial);
  218. my $dataenc = encode_base64($data,"");
  219. $hash->{HELPER}{RUNNING_PID} = BlockingCall("SMAEM_DoParse", "$name|$dataenc|$smaserial", "SMAEM_ParseDone", $timeout, "SMAEM_ParseAborted", $hash);
  220. Log3 ($name, 4, "SMAEM $name - Blocking process with PID: $hash->{HELPER}{RUNNING_PID}{pid} started");
  221. } else {
  222. Log3 $hash, 5, "SMAEM $name: - received " . length($data) . " bytes but interval $hash->{INTERVAL}s isn't expired.";
  223. }
  224. return undef;
  225. }
  226. ###############################################################
  227. # non-blocking Inverter Datenabruf
  228. ###############################################################
  229. sub SMAEM_DoParse($) {
  230. my ($string) = @_;
  231. my ($name,$dataenc,$smaserial) = split("\\|", $string);
  232. my $hash = $defs{$name};
  233. my $data = decode_base64($dataenc);
  234. my $discycles = $hash->{HELPER}{FAULTEDCYCLES};
  235. my $diffaccept = AttrVal($name, "diffAccept", 10);
  236. my @row_array;
  237. my @array;
  238. Log3 ($name, 4, "SMAEM $name -> Start BlockingCall SMAEM_DoParse");
  239. my $gridinsum = $hash->{'GRIDIN_SUM_'.$smaserial} ?sprintf("%.4f",$hash->{'GRIDIN_SUM_'.$smaserial}):'';
  240. my $gridoutsum = $hash->{'GRIDOUT_SUM_'.$smaserial}?sprintf("%.4f",$hash->{'GRIDOUT_SUM_'.$smaserial}):'';
  241. # check if cacheSMAEM-file has been opened at module start and try again if not
  242. if($hash->{HELPER}{READFILEERROR}) {
  243. my $retcode = SMAEM_getsum($hash,$smaserial);
  244. if ($retcode) {
  245. my $error = encode_base64($retcode,"");
  246. Log3 ($name, 4, "SMAEM $name -> BlockingCall SMAEM_DoParse finished");
  247. $discycles++;
  248. return "$name|''|''|''|$error|$discycles|''";
  249. } else {
  250. delete($hash->{HELPER}{READFILEERROR})
  251. }
  252. }
  253. # Format of the udp packets of the SMAEM:
  254. # http://www.sma.de/fileadmin/content/global/Partner/Documents/SMA_Labs/EMETER-Protokoll-TI-de-10.pdf
  255. # http://www.eb-systeme.de/?page_id=1240
  256. # Conversion like in this python code:
  257. # http://www.unifox.at/sma_energy_meter/
  258. # https://github.com/datenschuft/SMA-EM
  259. # unpack big-endian to 2-digit hex (bin2hex)
  260. my $hex = unpack('H*', $data);
  261. ################ Aufbau Ergebnis-Array ####################
  262. # Extract datasets from hex:
  263. # Generic:
  264. my $susyid = hex(substr($hex,36,4));
  265. # $smaserial = hex(substr($hex,40,8));
  266. my $milliseconds = hex(substr($hex,48,8));
  267. # Prestring with SMAEM and SERIALNO or not
  268. my $ps = (!AttrVal($name, "disableSernoInReading", undef)) ? "SMAEM".$smaserial."_" : "";
  269. # Counter Divisor: [Hex-Value]=Ws => Ws/1000*3600=kWh => divide by 3600000
  270. # Sum L1-3
  271. my $bezug_wirk = hex(substr($hex,64,8))/10;
  272. my $bezug_wirk_count = hex(substr($hex,80,16))/3600000;
  273. my $einspeisung_wirk = hex(substr($hex,104,8))/10;
  274. my $einspeisung_wirk_count = hex(substr($hex,120,16))/3600000;
  275. # calculation of GRID-hashes and persist to file
  276. Log3 ($name, 4, "SMAEM $name - old GRIDIN_SUM_$smaserial got from RAM: $gridinsum");
  277. Log3 ($name, 4, "SMAEM $name - old GRIDOUT_SUM_$smaserial got from RAM: $gridoutsum");
  278. my $plausibility_out = 0;
  279. if( !$gridoutsum || ($bezug_wirk_count && $bezug_wirk_count < $gridoutsum) ) {
  280. $gridoutsum = $bezug_wirk_count;
  281. Log3 ($name, 4, "SMAEM $name - gridoutsum_$smaserial new set: $gridoutsum");
  282. } else {
  283. if ($gridoutsum && $bezug_wirk_count >= $gridoutsum) {
  284. if(($bezug_wirk_count - $gridoutsum) <= $diffaccept) {
  285. # Plausibilitätscheck ob Differenz kleiner als erlaubter Wert -> Fehlerprävention
  286. my $diffb = ($bezug_wirk_count - $gridoutsum)>0 ? sprintf("%.4f",$bezug_wirk_count - $gridoutsum) : 0;
  287. Log3 ($name, 4, "SMAEM $name - bezug_wirk_count: $bezug_wirk_count");
  288. Log3 ($name, 4, "SMAEM $name - gridoutsum_$smaserial: $gridoutsum");
  289. Log3 ($name, 4, "SMAEM $name - diffb: $diffb");
  290. $gridoutsum = $bezug_wirk_count;
  291. push(@row_array, $ps."Bezug_WirkP_Zaehler_Diff ".$diffb."\n");
  292. push(@row_array, $ps."Bezug_WirkP_Kosten_Diff ".sprintf("%.4f", $diffb*AttrVal($name, "powerCost", 0))."\n");
  293. $plausibility_out = 1;
  294. } else {
  295. # Zyklus verwerfen wenn Plausibilität nicht erfüllt
  296. my $errtxt = "cycle discarded due to allowed diff GRIDOUT exceeding";
  297. my $error = encode_base64($errtxt,"");
  298. Log3 ($name, 1, "SMAEM $name - $errtxt");
  299. Log3 ($name, 4, "SMAEM $name -> BlockingCall SMAEM_DoParse finished");
  300. $gridinsum = $einspeisung_wirk_count;
  301. $gridoutsum = $bezug_wirk_count;
  302. $discycles++;
  303. return "$name|''|$gridinsum|$gridoutsum|''|$discycles|''";
  304. }
  305. }
  306. }
  307. my $plausibility_in = 0;
  308. if( !$gridinsum || ($einspeisung_wirk_count && $einspeisung_wirk_count < $gridinsum) ) {
  309. $gridinsum = $einspeisung_wirk_count;
  310. Log3 ($name, 4, "SMAEM $name - gridinsum_$smaserial new set: $gridinsum");
  311. } else {
  312. if ($gridinsum && $einspeisung_wirk_count >= $gridinsum) {
  313. if(($einspeisung_wirk_count - $gridinsum) <= $diffaccept) {
  314. # Plausibilitätscheck ob Differenz kleiner als erlaubter Wert -> Fehlerprävention
  315. my $diffe = ($einspeisung_wirk_count - $gridinsum)>0 ? sprintf("%.4f",$einspeisung_wirk_count - $gridinsum) : 0;
  316. Log3 ($name, 4, "SMAEM $name - einspeisung_wirk_count: $einspeisung_wirk_count");
  317. Log3 ($name, 4, "SMAEM $name - gridinsum_$smaserial: $gridinsum");
  318. Log3 ($name, 4, "SMAEM $name - diffe: $diffe");
  319. $gridinsum = $einspeisung_wirk_count;
  320. push(@row_array, $ps."Einspeisung_WirkP_Zaehler_Diff ".$diffe."\n");
  321. push(@row_array, $ps."Einspeisung_WirkP_Verguet_Diff ".sprintf("%.4f", $diffe*AttrVal($name, "feedinPrice", 0))."\n");
  322. $plausibility_in = 1;
  323. } else {
  324. # Zyklus verwerfen wenn Plausibilität nicht erfüllt
  325. my $errtxt = "cycle discarded due to allowed diff GRIDIN exceeding";
  326. my $error = encode_base64($errtxt,"");
  327. Log3 ($name, 1, "SMAEM $name - $errtxt");
  328. Log3 ($name, 4, "SMAEM $name -> BlockingCall SMAEM_DoParse finished");
  329. $gridinsum = $einspeisung_wirk_count;
  330. $gridoutsum = $bezug_wirk_count;
  331. $discycles++;
  332. return "$name|''|$gridinsum|$gridoutsum|''|$discycles|''";
  333. }
  334. }
  335. }
  336. # write GRIDIN_SUM and GRIDOUT_SUM to file if plausibility check ok
  337. Log3 ($name, 4, "SMAEM $name - plausibility check done: GRIDIN -> $plausibility_in, GRIDOUT -> $plausibility_out");
  338. my $retcode = SMAEM_setsum($hash,$smaserial,$gridinsum,$gridoutsum) if($plausibility_in && $plausibility_out);
  339. # error while writing values to file
  340. if ($retcode) {
  341. my $error = encode_base64($retcode,"");
  342. Log3 ($name, 4, "SMAEM $name -> BlockingCall SMAEM_DoParse finished");
  343. $discycles++;
  344. return "$name|''|''|''|$error|$discycles|''";
  345. }
  346. push(@row_array, "state ".sprintf("%.1f", $einspeisung_wirk-$bezug_wirk)."\n");
  347. push(@row_array, $ps."Saldo_Wirkleistung ".sprintf("%.1f",$einspeisung_wirk-$bezug_wirk)."\n");
  348. push(@row_array, $ps."Saldo_Wirkleistung_Zaehler ".sprintf("%.1f",$einspeisung_wirk_count-$bezug_wirk_count)."\n");
  349. push(@row_array, $ps."Bezug_Wirkleistung ".sprintf("%.1f",$bezug_wirk)."\n");
  350. push(@row_array, $ps."Bezug_Wirkleistung_Zaehler ".sprintf("%.4f",$bezug_wirk_count)."\n");
  351. push(@row_array, $ps."Einspeisung_Wirkleistung ".sprintf("%.1f",$einspeisung_wirk)."\n");
  352. push(@row_array, $ps."Einspeisung_Wirkleistung_Zaehler ".sprintf("%.4f",$einspeisung_wirk_count)."\n");
  353. my $bezug_blind=hex(substr($hex,144,8))/10;
  354. my $bezug_blind_count=hex(substr($hex,160,16))/3600000;
  355. my $einspeisung_blind=hex(substr($hex,184,8))/10;
  356. my $einspeisung_blind_count=hex(substr($hex,200,16))/3600000;
  357. push(@row_array, $ps."Bezug_Blindleistung ".sprintf("%.1f",$bezug_blind)."\n");
  358. push(@row_array, $ps."Bezug_Blindleistung_Zaehler ".sprintf("%.1f",$bezug_blind_count)."\n");
  359. push(@row_array, $ps."Einspeisung_Blindleistung ".sprintf("%.1f",$einspeisung_blind)."\n");
  360. push(@row_array, $ps."Einspeisung_Blindleistung_Zaehler ".sprintf("%.1f",$einspeisung_blind_count)."\n");
  361. my $bezug_schein=hex(substr($hex,224,8))/10;
  362. my $bezug_schein_count=hex(substr($hex,240,16))/3600000;
  363. my $einspeisung_schein=hex(substr($hex,264,8))/10;
  364. my $einspeisung_schein_count=hex(substr($hex,280,16))/3600000;
  365. push(@row_array, $ps."Bezug_Scheinleistung ".sprintf("%.1f",$bezug_schein)."\n");
  366. push(@row_array, $ps."Bezug_Scheinleistung_Zaehler ".sprintf("%.1f",$bezug_schein_count)."\n");
  367. push(@row_array, $ps."Einspeisung_Scheinleistung ".sprintf("%.1f",$einspeisung_schein)."\n");
  368. push(@row_array, $ps."Einspeisung_Scheinleistung_Zaehler ".sprintf("%.1f",$einspeisung_schein_count)."\n");
  369. my $cosphi=hex(substr($hex,304,8))/1000;
  370. push(@row_array, $ps."CosPhi ".sprintf("%.3f",$cosphi)."\n");
  371. # L1
  372. my $l1_bezug_wirk=hex(substr($hex,320,8))/10;
  373. my $l1_bezug_wirk_count=hex(substr($hex,336,16))/3600000;
  374. my $l1_einspeisung_wirk=hex(substr($hex,360,8))/10;
  375. my $l1_einspeisung_wirk_count=hex(substr($hex,376,16))/3600000;
  376. push(@row_array, $ps."L1_Saldo_Wirkleistung ".sprintf("%.1f",$l1_einspeisung_wirk-$l1_bezug_wirk)."\n");
  377. push(@row_array, $ps."L1_Saldo_Wirkleistung_Zaehler ".sprintf("%.1f",$l1_einspeisung_wirk_count-$l1_bezug_wirk_count)."\n");
  378. push(@row_array, $ps."L1_Bezug_Wirkleistung ".sprintf("%.1f",$l1_bezug_wirk)."\n");
  379. push(@row_array, $ps."L1_Bezug_Wirkleistung_Zaehler ".sprintf("%.1f",$l1_bezug_wirk_count)."\n");
  380. push(@row_array, $ps."L1_Einspeisung_Wirkleistung ".sprintf("%.1f",$l1_einspeisung_wirk)."\n");
  381. push(@row_array, $ps."L1_Einspeisung_Wirkleistung_Zaehler ".sprintf("%.1f",$l1_einspeisung_wirk_count)."\n");
  382. my $l1_bezug_blind=hex(substr($hex,400,8))/10;
  383. my $l1_bezug_blind_count=hex(substr($hex,416,16))/3600000;
  384. my $l1_einspeisung_blind=hex(substr($hex,440,8))/10;
  385. my $l1_einspeisung_blind_count=hex(substr($hex,456,16))/3600000;
  386. push(@row_array, $ps."L1_Bezug_Blindleistung ".sprintf("%.1f",$l1_bezug_blind)."\n");
  387. push(@row_array, $ps."L1_Bezug_Blindleistung_Zaehler ".sprintf("%.1f",$l1_bezug_blind_count)."\n");
  388. push(@row_array, $ps."L1_Einspeisung_Blindleistung ".sprintf("%.1f",$l1_einspeisung_blind)."\n");
  389. push(@row_array, $ps."L1_Einspeisung_Blindleistung_Zaehler ".sprintf("%.1f",$l1_einspeisung_blind_count)."\n");
  390. my $l1_bezug_schein=hex(substr($hex,480,8))/10;
  391. my $l1_bezug_schein_count=hex(substr($hex,496,16))/3600000;
  392. my $l1_einspeisung_schein=hex(substr($hex,520,8))/10;
  393. my $l1_einspeisung_schein_count=hex(substr($hex,536,16))/3600000;
  394. push(@row_array, $ps."L1_Bezug_Scheinleistung ".sprintf("%.1f",$l1_bezug_schein)."\n");
  395. push(@row_array, $ps."L1_Bezug_Scheinleistung_Zaehler ".sprintf("%.1f",$l1_bezug_schein_count)."\n");
  396. push(@row_array, $ps."L1_Einspeisung_Scheinleistung ".sprintf("%.1f",$l1_einspeisung_schein)."\n");
  397. push(@row_array, $ps."L1_Einspeisung_Scheinleistung_Zaehler ".sprintf("%.1f",$l1_einspeisung_schein_count)."\n");
  398. my $l1_thd=hex(substr($hex,560,8))/1000;
  399. my $l1_v=hex(substr($hex,576,8))/1000;
  400. my $l1_cosphi=hex(substr($hex,592,8))/1000;
  401. push(@row_array, $ps."L1_THD ".sprintf("%.2f",$l1_thd)."\n");
  402. push(@row_array, $ps."L1_Spannung ".sprintf("%.1f",$l1_v)."\n");
  403. push(@row_array, $ps."L1_CosPhi ".sprintf("%.3f",$l1_cosphi)."\n");
  404. # L2
  405. my $l2_bezug_wirk=hex(substr($hex,608,8))/10;
  406. my $l2_bezug_wirk_count=hex(substr($hex,624,16))/3600000;
  407. my $l2_einspeisung_wirk=hex(substr($hex,648,8))/10;
  408. my $l2_einspeisung_wirk_count=hex(substr($hex,664,16))/3600000;
  409. push(@row_array, $ps."L2_Saldo_Wirkleistung ".sprintf("%.1f",$l2_einspeisung_wirk-$l2_bezug_wirk)."\n");
  410. push(@row_array, $ps."L2_Saldo_Wirkleistung_Zaehler ".sprintf("%.1f",$l2_einspeisung_wirk_count-$l2_bezug_wirk_count)."\n");
  411. push(@row_array, $ps."L2_Bezug_Wirkleistung ".sprintf("%.1f",$l2_bezug_wirk)."\n");
  412. push(@row_array, $ps."L2_Bezug_Wirkleistung_Zaehler ".sprintf("%.1f",$l2_bezug_wirk_count)."\n");
  413. push(@row_array, $ps."L2_Einspeisung_Wirkleistung ".sprintf("%.1f",$l2_einspeisung_wirk)."\n");
  414. push(@row_array, $ps."L2_Einspeisung_Wirkleistung_Zaehler ".sprintf("%.1f",$l2_einspeisung_wirk_count)."\n");
  415. my $l2_bezug_blind=hex(substr($hex,688,8))/10;
  416. my $l2_bezug_blind_count=hex(substr($hex,704,16))/3600000;
  417. my $l2_einspeisung_blind=hex(substr($hex,728,8))/10;
  418. my $l2_einspeisung_blind_count=hex(substr($hex,744,16))/3600000;
  419. push(@row_array, $ps."L2_Bezug_Blindleistung ".sprintf("%.1f",$l2_bezug_blind)."\n");
  420. push(@row_array, $ps."L2_Bezug_Blindleistung_Zaehler ".sprintf("%.1f",$l2_bezug_blind_count)."\n");
  421. push(@row_array, $ps."L2_Einspeisung_Blindleistung ".sprintf("%.1f",$l2_einspeisung_blind)."\n");
  422. push(@row_array, $ps."L2_Einspeisung_Blindleistung_Zaehler ".sprintf("%.1f",$l2_einspeisung_blind_count)."\n");
  423. my $l2_bezug_schein=hex(substr($hex,768,8))/10;
  424. my $l2_bezug_schein_count=hex(substr($hex,784,16))/3600000;
  425. my $l2_einspeisung_schein=hex(substr($hex,808,8))/10;
  426. my $l2_einspeisung_schein_count=hex(substr($hex,824,16))/3600000;
  427. push(@row_array, $ps."L2_Bezug_Scheinleistung ".sprintf("%.1f",$l2_bezug_schein)."\n");
  428. push(@row_array, $ps."L2_Bezug_Scheinleistung_Zaehler ".sprintf("%.1f",$l2_bezug_schein_count)."\n");
  429. push(@row_array, $ps."L2_Einspeisung_Scheinleistung ".sprintf("%.1f",$l2_einspeisung_schein)."\n");
  430. push(@row_array, $ps."L2_Einspeisung_Scheinleistung_Zaehler ".sprintf("%.1f",$l2_einspeisung_schein_count)."\n");
  431. my $l2_thd=hex(substr($hex,848,8))/1000;
  432. my $l2_v=hex(substr($hex,864,8))/1000;
  433. my $l2_cosphi=hex(substr($hex,880,8))/1000;
  434. push(@row_array, $ps."L2_THD ".sprintf("%.2f",$l2_thd)."\n");
  435. push(@row_array, $ps."L2_Spannung ".sprintf("%.1f",$l2_v)."\n");
  436. push(@row_array, $ps."L2_CosPhi ".sprintf("%.3f",$l2_cosphi)."\n");
  437. # L3
  438. my $l3_bezug_wirk=hex(substr($hex,896,8))/10;
  439. my $l3_bezug_wirk_count=hex(substr($hex,912,16))/3600000;
  440. my $l3_einspeisung_wirk=hex(substr($hex,936,8))/10;
  441. my $l3_einspeisung_wirk_count=hex(substr($hex,952,16))/3600000;
  442. push(@row_array, $ps."L3_Saldo_Wirkleistung ".sprintf("%.1f",$l3_einspeisung_wirk-$l3_bezug_wirk)."\n");
  443. push(@row_array, $ps."L3_Saldo_Wirkleistung_Zaehler ".sprintf("%.1f",$l3_einspeisung_wirk_count-$l3_bezug_wirk_count)."\n");
  444. push(@row_array, $ps."L3_Bezug_Wirkleistung ".sprintf("%.1f",$l3_bezug_wirk)."\n");
  445. push(@row_array, $ps."L3_Bezug_Wirkleistung_Zaehler ".sprintf("%.1f",$l3_bezug_wirk_count)."\n");
  446. push(@row_array, $ps."L3_Einspeisung_Wirkleistung ".sprintf("%.1f",$l3_einspeisung_wirk)."\n");
  447. push(@row_array, $ps."L3_Einspeisung_Wirkleistung_Zaehler ".sprintf("%.1f",$l3_einspeisung_wirk_count)."\n");
  448. my $l3_bezug_blind=hex(substr($hex,976,8))/10;
  449. my $l3_bezug_blind_count=hex(substr($hex,992,16))/3600000;
  450. my $l3_einspeisung_blind=hex(substr($hex,1016,8))/10;
  451. my $l3_einspeisung_blind_count=hex(substr($hex,1032,16))/3600000;
  452. push(@row_array, $ps."L3_Bezug_Blindleistung ".sprintf("%.1f",$l3_bezug_blind)."\n");
  453. push(@row_array, $ps."L3_Bezug_Blindleistung_Zaehler ".sprintf("%.1f",$l3_bezug_blind_count)."\n");
  454. push(@row_array, $ps."L3_Einspeisung_Blindleistung ".sprintf("%.1f",$l3_einspeisung_blind)."\n");
  455. push(@row_array, $ps."L3_Einspeisung_Blindleistung_Zaehler ".sprintf("%.1f",$l3_einspeisung_blind_count)."\n");
  456. my $l3_bezug_schein=hex(substr($hex,1056,8))/10;
  457. my $l3_bezug_schein_count=hex(substr($hex,1072,16))/3600000;
  458. my $l3_einspeisung_schein=hex(substr($hex,1096,8))/10;
  459. my $l3_einspeisung_schein_count=hex(substr($hex,1112,16))/3600000;
  460. push(@row_array, $ps."L3_Bezug_Scheinleistung ".sprintf("%.1f",$l3_bezug_schein)."\n");
  461. push(@row_array, $ps."L3_Bezug_Scheinleistung_Zaehler ".sprintf("%.1f",$l3_bezug_schein_count)."\n");
  462. push(@row_array, $ps."L3_Einspeisung_Scheinleistung ".sprintf("%.1f",$l3_einspeisung_schein)."\n");
  463. push(@row_array, $ps."L3_Einspeisung_Scheinleistung_Zaehler ".sprintf("%.1f",$l3_einspeisung_schein_count)."\n");
  464. my $l3_thd=hex(substr($hex,1136,8))/1000;
  465. my $l3_v=hex(substr($hex,1152,8))/1000;
  466. my $l3_cosphi=hex(substr($hex,1168,8))/1000;
  467. push(@row_array, $ps."L3_THD ".sprintf("%.2f",$l3_thd)."\n");
  468. push(@row_array, $ps."L3_Spannung ".sprintf("%.1f",$l3_v)."\n");
  469. push(@row_array, $ps."L3_CosPhi ".sprintf("%.3f",$l3_cosphi)."\n");
  470. Log3 ($name, 5, "$name - row_array before encoding:");
  471. foreach my $row (@row_array) {
  472. chomp $row;
  473. Log3 ($name, 5, "SMAEM $name - $row");
  474. }
  475. # encoding result
  476. my $rowlist = join('|', @row_array);
  477. $rowlist = encode_base64($rowlist,"");
  478. Log3 ($name, 4, "SMAEM $name -> BlockingCall SMAEM_DoParse finished");
  479. return "$name|$rowlist|$gridinsum|$gridoutsum|''|$discycles|$smaserial";
  480. }
  481. ###############################################################
  482. # Auswertung non-blocking Inverter Datenabruf
  483. ###############################################################
  484. sub SMAEM_ParseDone ($) {
  485. my ($string) = @_;
  486. my @a = split("\\|",$string);
  487. my $name = $a[0];
  488. my $hash = $defs{$name};
  489. my $rowlist = decode_base64($a[1]);
  490. my $gridinsum = $a[2];
  491. my $gridoutsum = $a[3];
  492. my $error = decode_base64($a[4]) if($a[4]);
  493. my $discycles = $a[5];
  494. my $smaserial = $a[6];
  495. Log3 ($name, 4, "SMAEM $name -> Start BlockingCall SMAEM_ParseDone");
  496. $hash->{HELPER}{FAULTEDCYCLES} = $discycles;
  497. # update time
  498. SMAEM_setlastupdate($hash,$smaserial);
  499. if ($error) {
  500. readingsSingleUpdate($hash, "state", $error, 1);
  501. Log3 ($name, 4, "SMAEM $name -> BlockingCall SMAEM_ParseDone finished");
  502. delete($hash->{HELPER}{RUNNING_PID});
  503. return;
  504. }
  505. $hash->{'GRIDIN_SUM_'.$smaserial} = $gridinsum;
  506. $hash->{'GRIDOUT_SUM_'.$smaserial} = $gridoutsum;
  507. Log3($name, 4, "SMAEM $name - wrote new energy values to INTERNALS - GRIDIN_SUM_$smaserial: $gridinsum, GRIDOUT_SUM_$smaserial: $gridoutsum");
  508. my @row_array = split("\\|", $rowlist);
  509. Log3 ($name, 5, "SMAEM $name - row_array after decoding:");
  510. foreach my $row (@row_array) {
  511. chomp $row;
  512. Log3 ($name, 5, "SMAEM $name - $row");
  513. }
  514. readingsBeginUpdate($hash);
  515. foreach my $row (@row_array) {
  516. chomp $row;
  517. my @a = split(" ", $row, 2);
  518. readingsBulkUpdate($hash, $a[0], $a[1]);
  519. }
  520. readingsEndUpdate($hash, 1);
  521. delete($hash->{HELPER}{RUNNING_PID});
  522. Log3 ($name, 4, "SMAEM $name -> BlockingCall SMAEM_ParseDone finished");
  523. return;
  524. }
  525. ###############################################################
  526. # Abbruchroutine Timeout Inverter Abfrage
  527. ###############################################################
  528. sub SMAEM_ParseAborted($) {
  529. my ($hash,$cause) = @_;
  530. my $name = $hash->{NAME};
  531. my $discycles = $hash->{HELPER}{FAULTEDCYCLES};
  532. $cause = $cause?$cause:"Timeout: process terminated";
  533. $discycles++;
  534. $hash->{HELPER}{FAULTEDCYCLES} = $discycles;
  535. Log3 ($name, 1, "SMAEM $name -> BlockingCall $hash->{HELPER}{RUNNING_PID}{fn} $cause");
  536. readingsSingleUpdate($hash, "state", $cause, 1);
  537. delete($hash->{HELPER}{RUNNING_PID});
  538. }
  539. ###############################################################
  540. # DbLog_splitFn
  541. ###############################################################
  542. sub SMAEM_DbLog_splitFn($) {
  543. my ($event,$device) = @_;
  544. my ($reading, $value, $unit) = "";
  545. # Log3 ($device, 5, "SMAEM $device - splitFn splits event: ".$event);
  546. my @parts = split(/ /,$event,3);
  547. $reading = $parts[0];
  548. $reading =~ tr/://d;
  549. $value = $parts[1];
  550. if($reading =~ m/.*leistung$/) {
  551. $unit = 'W';
  552. } elsif($reading =~ m/.*Spannung/) {
  553. $unit = 'V';
  554. } elsif($reading =~ m/.*leistung_Zaehler$/) {
  555. $unit = 'kWh';
  556. } elsif($reading =~ m/.*THD$/) {
  557. $unit = '%';
  558. } else {
  559. if(!defined($parts[1])) {
  560. $reading = "state";
  561. $value = $event;
  562. $unit = 'W';
  563. } else {
  564. $value = $parts[1];
  565. $value = $value." ".$parts[2] if(defined($parts[2]));
  566. }
  567. }
  568. Log3 ($device, 5, "SMAEM $device - splitFn returns Reading: ".$reading.", Value: ".
  569. defined($value)?$value:''.", Unit: ".defined($unit)?$unit:'');
  570. return ($reading, $value, $unit);
  571. }
  572. ###############################################################
  573. # Hilfsroutinen
  574. ###############################################################
  575. ###############################################################
  576. ### alle Serial-Nummern in cacheSMAEM speichern
  577. sub SMAEM_setserials ($) {
  578. my ($hash) = @_;
  579. my $name = $hash->{NAME};
  580. my ($index,$retcode,$as);
  581. my $modpath = AttrVal("global", "modpath", undef);
  582. $as = $hash->{HELPER}{ALLSERIALS};
  583. $index = $hash->{TYPE}."_".$hash->{NAME}."_allserials";
  584. $retcode = SMAEM_setCacheValue($index,$as);
  585. if ($retcode) {
  586. Log3($name, 1, "SMAEM $name - ERROR while saving all serial numbers - $retcode");
  587. } else {
  588. Log3($name, 4, "SMAEM $name - all serial numbers were saved to $modpath/FHEM/FhemUtils/cacheSMAEM");
  589. }
  590. return ($retcode);
  591. }
  592. ###############################################################
  593. ### Summenwerte für GridIn, GridOut speichern
  594. sub SMAEM_setsum ($$$$) {
  595. my ($hash,$smaserial,$gridinsum,$gridoutsum) = @_;
  596. my $name = $hash->{NAME};
  597. my ($index,$retcode,$sumstr);
  598. my $modpath = AttrVal("global", "modpath", undef);
  599. $sumstr = $gridinsum."_".$gridoutsum;
  600. $index = $hash->{TYPE}."_".$hash->{NAME}."_".$smaserial;
  601. $retcode = SMAEM_setCacheValue($index,$sumstr);
  602. if ($retcode) {
  603. Log3($name, 1, "SMAEM $name - ERROR while saving summary of energy values - $retcode");
  604. } else {
  605. Log3($name, 4, "SMAEM $name - new energy values saved to $modpath/FHEM/FhemUtils/cacheSMAEM");
  606. Log3($name, 4, "SMAEM $name - GRIDIN_SUM_$smaserial: $gridinsum, GRIDOUT_SUM_$smaserial: $gridoutsum");
  607. }
  608. return ($retcode);
  609. }
  610. ###############################################################
  611. ### Schreibroutine in eigenes Keyvalue-File
  612. sub SMAEM_setCacheValue($$) {
  613. my ($key,$value) = @_;
  614. my $fName = $attr{global}{modpath}."/FHEM/FhemUtils/cacheSMAEM";
  615. my $param = {
  616. FileName => $fName,
  617. ForceType => "file",
  618. };
  619. my ($err, @old) = FileRead($param);
  620. SMAEM_createCacheFile() if($err);
  621. my @new;
  622. my $fnd;
  623. foreach my $l (@old) {
  624. if($l =~ m/^$key:/) {
  625. $fnd = 1;
  626. push @new, "$key:$value" if(defined($value));
  627. } else {
  628. push @new, $l;
  629. }
  630. }
  631. push @new, "$key:$value" if(!$fnd && defined($value));
  632. return FileWrite($param, @new);
  633. }
  634. ###############################################################
  635. ### gespeicherte Serial-Nummern auslesen
  636. sub SMAEM_getserials ($) {
  637. my ($hash) = @_;
  638. my $name = $hash->{NAME};
  639. my ($index,$retcode,$serials);
  640. my $modpath = AttrVal("global", "modpath", undef);
  641. $index = $hash->{TYPE}."_".$hash->{NAME}."_allserials";
  642. ($retcode, $serials) = SMAEM_getCacheValue($index);
  643. if ($retcode) {
  644. Log3($name, 1, "SMAEM $name - $retcode") if ($retcode);
  645. Log3($name, 3, "SMAEM $name - Create new cacheFile $modpath/FHEM/FhemUtils/cacheSMAEM");
  646. $retcode = SMAEM_createCacheFile();
  647. } else {
  648. if ($serials) {
  649. $hash->{HELPER}{ALLSERIALS} = $serials;
  650. Log3 ($name, 3, "SMAEM $name - read saved serial numbers from $modpath/FHEM/FhemUtils/cacheSMAEM");
  651. }
  652. }
  653. return ($retcode);
  654. }
  655. ###############################################################
  656. ### Summenwerte für GridIn, GridOut auslesen
  657. sub SMAEM_getsum ($$) {
  658. my ($hash,$smaserial) = @_;
  659. my $name = $hash->{NAME};
  660. my ($index,$retcode,$sumstr);
  661. my $modpath = AttrVal("global", "modpath", undef);
  662. $index = $hash->{TYPE}."_".$hash->{NAME}."_".$smaserial;
  663. ($retcode, $sumstr) = SMAEM_getCacheValue($index);
  664. if ($retcode) {
  665. Log3($name, 1, "SMAEM $name - $retcode") if ($retcode);
  666. } else {
  667. if ($sumstr) {
  668. ($hash->{'GRIDIN_SUM_'.$smaserial}, $hash->{'GRIDOUT_SUM_'.$smaserial}) = split(/_/, $sumstr);
  669. Log3 ($name, 3, "SMAEM $name - read saved energy values from $modpath/FHEM/FhemUtils/cacheSMAEM");
  670. Log3 ($name, 3, "SMAEM $name - GRIDIN_SUM_$smaserial: $hash->{'GRIDIN_SUM_'.$smaserial}, GRIDOUT_SUM_$smaserial: $hash->{'GRIDOUT_SUM_'.$smaserial}");
  671. }
  672. }
  673. return ($retcode);
  674. }
  675. ###############################################################
  676. ### Leseroutine aus eigenem Keyvalue-File
  677. sub SMAEM_getCacheValue($) {
  678. my ($key) = @_;
  679. my $fName = $attr{global}{modpath}."/FHEM/FhemUtils/cacheSMAEM";
  680. my $param = {
  681. FileName => $fName,
  682. ForceType => "file",
  683. };
  684. my ($err, @l) = FileRead($param);
  685. return ($err, undef) if($err);
  686. for my $l (@l) {
  687. return (undef, $1) if($l =~ m/^$key:(.*)/);
  688. }
  689. return (undef, undef);
  690. }
  691. ###############################################################
  692. ### Anlegen eigenes Keyvalue-File wenn nicht vorhanden
  693. sub SMAEM_createCacheFile {
  694. my $fName = $attr{global}{modpath}."/FHEM/FhemUtils/cacheSMAEM";
  695. my $param = {
  696. FileName => $fName,
  697. ForceType => "file",
  698. };
  699. my @new;
  700. push(@new, "# This file is auto generated from 77_SMAEM.",
  701. "# Please do not modify, move or delete it.",
  702. "");
  703. return FileWrite($param, @new);
  704. }
  705. ###############################################################
  706. ### $update time of last update
  707. sub SMAEM_setlastupdate ($$) {
  708. my ($hash,$smaserial) = @_;
  709. my $name = $hash->{NAME};
  710. return if(!$smaserial); # Abbruch wenn keine Seriennummer extrahiert
  711. $hash->{HELPER}{'LASTUPDATE_'.$smaserial} = time();
  712. my ($sec,$min,$hour,$mday,$mon,$year,undef,undef,undef) = localtime();
  713. $hash->{'LASTUPDATE_'.$smaserial} = sprintf "%02d.%02d.%04d / %02d:%02d:%02d" , $mday , $mon+=1 ,$year+=1900 , $hour , $min , $sec ;
  714. Log3 ($name, 4, "SMAEM $name - last update time set to: $hash->{'LASTUPDATE_'.$smaserial}");
  715. return;
  716. }
  717. 1;
  718. =pod
  719. =item summary Integration of SMA Energy Meters
  720. =item summary_DE Integration von SMA Energy Meter
  721. =begin html
  722. <a name="SMAEM"></a>
  723. <h3>SMAEM</h3>
  724. <br>
  725. <a name="SMAEMdefine"></a>
  726. <b>Define</b>
  727. <ul>
  728. <code>define &lt;name&gt; SMAEM </code><br>
  729. <br>
  730. Defines a SMA Energy Meter (SMAEM), a bidirectional energy meter/counter used in photovoltaics.
  731. <br><br>
  732. You need at least one SMAEM on your local subnet or behind a multicast enabled network of routers to receive multicast messages from the SMAEM over the
  733. multicast group 239.12.255.254 on udp/9522. Multicast messages are sent by SMAEM once a second (firmware 1.02.04.R, March 2016).
  734. <br><br>
  735. The update interval will be set by attribute "interval". If not set, it defaults to 60s. Since the SMAEM sends updates once a second, you can
  736. update the readings once a second by lowering the interval to 1 (Not recommended, since it puts FHEM under heavy load).
  737. <br><br>
  738. The parameter "disableSernoInReading" changes the way readings are named: if disableSernoInReading is false or unset, the readings will be named
  739. "SMAEM&lt;serialnumber_&gt;.....".
  740. If set to true, the prefix "SMAEM&lt;serialnumber_&gt;" is skipped.
  741. Set this to true if you only have one SMAEM device on your network and you want shorter reading names.
  742. If unsure, leave it unset.
  743. <br><br>
  744. You need the perl module IO::Socket::Multicast. Under Debian (based) systems it can be installed with <code>apt-get install libio-socket-multicast-perl</code>.
  745. </ul>
  746. <br>
  747. <br>
  748. <a name="SMAEMattr"></a>
  749. <b>Attribute</b>
  750. <ul>
  751. <li><b>disableSernoInReading</b> : prevents the prefix "SMAEM&lt;serialnumber_&gt;....." </li>
  752. <li><b>feedinPrice</b> : the individual amount of refund of one kilowatt hour</li>
  753. <li><b>interval</b> : evaluation interval in seconds </li>
  754. <li><b>disable</b> : 1 = the module is disabled </li>
  755. <li><b>diffAccept</b> : diffAccept determines the threshold, up to that a calaculated difference between two
  756. straight sequently meter readings (Readings with *_Diff) should be commenly accepted (default = 10). <br>
  757. Hence faulty DB entries with a disproportional high difference values will be eliminated, don't
  758. tamper the result and the measure cycles will be discarded. </li>
  759. <li><b>powerCost</b> : the individual amount of power cost per kWh </li>
  760. <li><b>timeout</b> : adjustment timeout of backgound processing (default 60s). The value of timeout has to be higher than the value of "interval". </li>
  761. </ul>
  762. <br>
  763. <a name="SMAEMreadings"></a>
  764. <b>Readings</b> <br><br>
  765. The created readings of SMAEM mostly are self-explanatory.
  766. However there are readings what maybe need some explanation. <br>
  767. <ul>
  768. <li><b>&lt;Phase&gt;_THD</b> : (Total Harmonic Distortion) - Proportion or quota of total effective value
  769. of all harmonic component to effective value of fundamental component.
  770. Total ratio of harmonic component and interference of pure sinusoidal wave
  771. in %.
  772. It is a rate of interferences. d is 0, if sinusoidal voltage exists and a sinusoidal
  773. current exists as well. As larger d, as more harmonic component are existing.
  774. According EN 50160/1999 the value mustn't exceed 8 %.
  775. If a current interference is so powerful that it is causing a voltage interference of
  776. more than 5 % (THD), that points to an issue with electrical potential. </li>
  777. </ul>
  778. <br>
  779. =end html
  780. =begin html_DE
  781. <a name="SMAEM"></a>
  782. <h3>SMAEM</h3>
  783. <br>
  784. <a name="SMAEMdefine"></a>
  785. <b>Define</b>
  786. <ul>
  787. <code>define &lt;name&gt; SMAEM </code><br>
  788. <br>
  789. Definiert ein SMA Energy Meter (SMAEM), einen bidirektionalen Stromzähler, der häufig in Photovolatikanlagen der Firma SMA zum Einsatz kommt.
  790. <br><br>
  791. Sie brauchen mindest ein SMAEM in Ihrem lokalen Netzwerk oder hinter einen multicastfähigen Netz von Routern, um die Daten des SMAEM über die
  792. Multicastgruppe 239.12.255.254 auf udp/9522 zu empfangen. Die Multicastpakete werden vom SMAEM einmal pro Sekunde ausgesendet (firmware 1.02.04.R, März 2016).
  793. <br><br>
  794. Das update interval kann über das Attribut "interval" gesetzt werden. Wenn es nicht gesetzt wird, werden updates per default alle 60 Sekunden durchgeführt.
  795. Da das SMAEM seine Daten sekündlich aktualisiert, kann das update interval auf bis zu einer Sekunde reduziert werden. Das wird nicht empfohlen, da FHEM
  796. sonst unter große Last gesetzt wird.
  797. <br><br>
  798. Der Parameter "disableSernoInReading" ändert die Art und Weise, wie die Readings des SMAEN bezeichnet werden: ist der Parameter false
  799. oder nicht gesetzt, werden die Readings mit "SMAEM&lt;serialnumber_&gt;....." bezeichnet.
  800. Wird der Parameter auf true gesetzt, wird das Prefix "SMAEM&lt;serialnumber_&gt;....." weg gelassen.
  801. Sie können diesen Parameter auf true setzen, wenn Sie nicht mehr als ein SMAEM-Gerät in Ihrem Netzwerk haben und kürzere Namen für die Readings wünschen.
  802. Falls Sie unsicher sind, setzen Sie diesen Parameter nicht.
  803. <br><br>
  804. Sie benötigen das Perl-Module IO::Socket::Multicast für dieses FHEM Modul. Unter Debian (basierten) System, kann dies
  805. mittels <code>apt-get install libio-socket-multicast-perl</code> installiert werden.
  806. </ul>
  807. <br>
  808. <a name="SMAEMattr"></a>
  809. <b>Attribute</b>
  810. <ul>
  811. <li><b>disableSernoInReading</b> : unterdrückt das Prefix "SMAEM&lt;serialnumber_&gt;....." </li>
  812. <li><b>feedinPrice</b> : die individuelle Höhe der Vergütung pro Kilowattstunde </li>
  813. <li><b>interval</b> : Auswertungsinterval in Sekunden </li>
  814. <li><b>disable</b> : 1 = das Modul ist disabled </li>
  815. <li><b>diffAccept</b> : diffAccept legt fest, bis zu welchem Schwellenwert eine berechnete positive Werte-Differenz
  816. zwischen zwei unmittelbar aufeinander folgenden Zählerwerten (Readings mit *_Diff) akzeptiert werden
  817. soll (Standard ist 10). <br>
  818. Damit werden eventuell fehlerhafte Differenzen mit einem unverhältnismäßig hohen Differenzwert von der Berechnung
  819. ausgeschlossen und der Messzyklus verworfen. </li>
  820. <li><b>powerCost</b> : die individuelle Höhe der Stromkosten pro Kilowattstunde </li>
  821. <li><b>timeout</b> : Einstellung timeout für Hintergrundverarbeitung (default 60s). Der timeout-Wert muss größer als das Wert von "interval" sein. </li>
  822. </ul>
  823. <br>
  824. <a name="SMAEMreadings"></a>
  825. <b>Readings</b> <br><br>
  826. Die meisten erzeugten Readings von SMAEM sind selbsterklärend.
  827. Es gibt allerdings Readings die einer Erläuterung bedürfen. <br>
  828. <ul>
  829. <li><b>&lt;Phase&gt;_THD</b> : (Total Harmonic Distortion) - Verzerrungs- oder Gesamtklirrfaktor - Verhältnis oder
  830. Anteil des Gesamteffektivwert aller Oberschwingungen zum Effektivwert der
  831. Grundschwingung. Gesamtanteil an Oberschwingungen und Störung der reinen Sinuswelle
  832. in % bzw. Verhältnis vom nutzbaren Grundschwingungsstrom zu den
  833. nicht nutzbaren Oberschwingungsströmen. Es ist ein Maß für Störungen. d ist 0, wenn bei
  834. sinusförmiger Spannung ein sinusförmiger Strom fließt. Je größer d, um so mehr
  835. Oberschwingungen sind vorhanden. Nach EN 50160/1999 z.B. darf der Wert 8 % nicht
  836. überschreiten. Wenn eine Stromstörung so stark ist, dass sie eine Spannungsstörung
  837. (THD) von über 5 % verursacht, deutet dies auf ein Potentialproblem hin. </li>
  838. </ul>
  839. <br>
  840. =end html_DE