70_EFR.pm 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422
  1. ##############################################################################
  2. #
  3. # 70_EFR.pm
  4. #
  5. # a module to show smartmeter data
  6. #
  7. # written 2013 by Gabriel Bentele <gabriel at bentele.de>>
  8. #
  9. # $Id: 70_EFR.pm 3799 2013-08-26 18:15:33Z bentele $
  10. #
  11. # Version = 1.3
  12. #
  13. ##############################################################################
  14. #
  15. # define <name> EFR <host> <port> [<interval> [<timeout>]]
  16. #
  17. # If <interval> is positive, new values are read every <interval> seconds.
  18. # If <interval> is 0, new values are read whenever a get request is called
  19. # on <name>. The default for <interval> is 300 (i.e. 5 minutes).
  20. #
  21. # get <name> <key>
  22. #
  23. ##############################################################################
  24. # { "obis":"8181C78227FF","value":""}, [03] Kundennummer
  25. # { "obis":"8181C78205FF","value":"xxxxx"}, [04] Vorname
  26. # { "obis":"8181C78206FF","value":"xxxxx"}, [05] Nachname
  27. # { "obis":"8181C78207FF","value":"xxxxx"}, [06] Anschrift
  28. # { "obis":"0100000000FF","value":"xxxxx"}, [07] Eigentums- bzw. Zählernummer
  29. # { "obis":"010000090B00","value":"dd.mm.yyyy,hh:mm"}], "values" : [ [08] Zeitangabe (Datum , Uhrzeit)
  30. # {"obis":"0101010800FF","value":41.42,"unit":"kWh" }, [09] BEZUG Wirkleistung Energiezählwerk - Summenzählwerk abrechnungsrelevant (Tariflos)
  31. # {"obis":"0101010801FF","value":33.53,"unit":"kWh"}, [10 BEZUG Wirkleistung Energiezählwerk NT
  32. # {"obis":"0100010700FF","value":313.07,"unit":"W"}, [11] Momentanleistung über alle 3 Phasen saldierend
  33. # {"obis":"0100150700FF","value":209.40,"unit":"W"}, [12] Momentanleistung Phase L1
  34. # {"obis":"0100290700FF","value":14.27,"unit":"W"}, [13] Momentanleistung Phase L2
  35. # {"obis":"01003D0700FF","value":89.40,"unit":"W"}, [14] Momentanleistung Phase L3
  36. # {"obis":"010020070000","value":237.06,"unit":"V"}, [15] Phasenspannung U1
  37. # {"obis":"010034070000","value":236.28,"unit":"V"}, [16] Phasenspannung U2
  38. # {"obis":"010048070000","value":236.90,"unit":"V"}, [17] Phasenspannung U3
  39. # {"obis":"01000E070000","value":49.950,"unit":"Hz"} ] }} [18] Netzfrequenz
  40. ##############################################################################
  41. package main;
  42. use strict;
  43. use IO::Socket::INET;
  44. use Blocking;
  45. use MIME::Base64;
  46. my @gets = ('xxx');
  47. sub
  48. EFR_Initialize($)
  49. {
  50. my ($hash) = @_;
  51. $hash->{DefFn} = "energy_efr_Define";
  52. $hash->{UndefFn} = "energy_efr_Undef";
  53. $hash->{GetFn} = "energy_efr_Get";
  54. $hash->{StateFn} = "energy_efr_State";
  55. $hash->{SetFn} = "energy_efr_Set";
  56. $hash->{AttrFn} = "energy_efr_Attr";
  57. $hash->{AttrList} = "URL FELDER FELDERNAME";
  58. }
  59. sub
  60. energy_efr_Attr($@)
  61. {
  62. my (@a) = @_;
  63. my $hash = $defs{$a[1]};
  64. my $name = $hash->{NAME};
  65. if($a[0] eq "set"){
  66. Log3 $hash, 3,"set attribute: $name attribute: $a[1] value:$a[2]";
  67. }
  68. elsif($a[0] eq "del")
  69. {
  70. # delete attribute
  71. Log3 $hash, 3,"del attribute: $name attribute: $a[1] value:$a[2]";
  72. }
  73. return undef;
  74. } # energy_efr_Attr ende
  75. sub
  76. energy_efr_State($$$$)
  77. {
  78. my ($hash, $tim, $vt, $val) = @_;
  79. $hash->{READINGS}{$vt}{VAL} = $val;
  80. $hash->{READINGS}{$vt}{TIME} = TimeNow();
  81. Log3 $hash, 4, "energy_efr_State: time: $tim name: $vt value: $val";
  82. return undef;
  83. }
  84. sub
  85. energy_efr_Set($$$$)
  86. {
  87. my ($hash, $tim, $vt, $val) = @_;
  88. Log3 $hash, 4, "SET device: $tim name: $vt value: $val";
  89. $hash->{READINGS}{$vt}{VAL} = $val;
  90. $hash->{READINGS}{$vt}{TIME} = TimeNow();
  91. if ( $vt eq "?"){
  92. return "Unknown argument ?, choose one of Interval";
  93. }
  94. if ( $vt eq "Interval"){
  95. $hash->{Interval} = $val;
  96. }
  97. return undef;
  98. }
  99. sub
  100. energy_efr_Define($$)
  101. {
  102. my ($hash, $def) = @_;
  103. my @args = split("[ \t]+", $def);
  104. if (int(@args) < 3)
  105. {
  106. return "energy_efr_Define: too few arguments. Usage:\n" .
  107. "define <name> EFR <host> [<interval> [<timeout>]]";
  108. }
  109. my $name = $args[0];
  110. $hash->{NAME} = $name;
  111. $hash->{Host} = $args[2];
  112. $hash->{Port} = 80;
  113. $hash->{Interval} = int(@args) >= 4 ? int($args[3]) : 300;
  114. $hash->{Timeout} = int(@args) >= 5 ? int($args[4]) : 4;
  115. Log3 $hash, 4, "$hash->{NAME} will read from EFR at $hash->{Host}:$hash->{Port} " ;
  116. $hash->{Rereads} = 2; # number of retries when reading curPwr of 0
  117. $hash->{UseSVTime} = ''; # use the SV time as timestamp (else: TimeNow())
  118. $hash->{STATE} = 'Initializing';
  119. my $timenow = TimeNow();
  120. RemoveInternalTimer($hash);
  121. InternalTimer(gettimeofday()+$hash->{Interval}, "energy_Update", $hash, 0);
  122. Log3 $hash, 3, "$hash->{NAME} will read from EFR at $hash->{Host}:$hash->{Port} " ;
  123. return undef;
  124. }
  125. sub
  126. energy_Update($)
  127. {
  128. my ($hash) = @_;
  129. my $name = $hash->{NAME};
  130. my $ip = $hash->{Host};
  131. my $port = $hash->{Port};
  132. my $interval = $hash->{Interval};
  133. if ( defined($attr{$name}{"URL"}) ){
  134. my $url = $attr{$name}{"URL"};
  135. $hash->{helper}{RUNNING_PID} = BlockingCall("energy_DoUpdate", $name."|".$ip."|".$port."|".$interval."|".$url, "energy_energyDone", 120, "energy_energyAborted", $hash) unless(exists($hash->{helper}{RUNNING_PID}));
  136. }else{
  137. Log3 $hash, 3, "$hash->{NAME} please define a valid URL as attribute" ;
  138. }
  139. }
  140. sub
  141. energy_DoUpdate($){
  142. my ($string) = @_;
  143. my ($name, $ip, $port,$interval,$url) = split("\\|", $string);
  144. my $success = 0;
  145. my %readings = ();
  146. my $timenow = TimeNow();
  147. my $timeout = 10;
  148. my $counts = 0 ;
  149. my $summary = 0 ;
  150. #my $url="/json.txt?LogName=user\&LogPSWD=user";
  151. #my $url="/efr/efr.txt";
  152. my $socket ;
  153. my $buf ;
  154. my $message ;
  155. Log3 $name, 4, "EFR $name ip: $ip port: $port URL: $url" ;
  156. $socket = new IO::Socket::INET (
  157. PeerAddr => $ip,
  158. PeerPort => $port,
  159. Proto => 'tcp',
  160. Reuse => 0,
  161. Timeout => $timeout
  162. );
  163. if (defined ($socket) and $socket and $socket->connected())
  164. {
  165. print $socket "GET $url HTTP/1.0\r\n\r\n";
  166. $socket->autoflush(1);
  167. while ((read $socket, $buf, 1024) > 0)
  168. {
  169. $message .= $buf;
  170. Log3 $name, 5, "buf: $buf";
  171. }
  172. $socket->close();
  173. Log3 $name, 4, "Socket closed";
  174. $success = 0;
  175. }else{
  176. Log3 $name, 3, "$name Cannot open socket ...";
  177. $success = 1;
  178. }
  179. $message = encode_base64($message,"");
  180. if ( $success == 0 ){
  181. my $back = $name ."|". $message;
  182. return "$name|$message" ;
  183. }else{
  184. return "$name|-1";
  185. }
  186. }
  187. sub
  188. energy_energyDone($)
  189. {
  190. my ($string) = @_;
  191. return unless(defined($string));
  192. my (@a) = split("\\|", $string);
  193. my $hash = $defs{$a[0]};
  194. my $message = decode_base64($a[1]);
  195. my @array;
  196. my $log = "";
  197. my $timenow = TimeNow();
  198. my $name = $hash->{NAME};
  199. my $felder = $attr{$name}{"FELDER"};
  200. Log3 $name, 4, "name: $name felder: $felder";
  201. delete($hash->{helper}{RUNNING_PID});
  202. if(!$hash->{LOCAL}) {
  203. RemoveInternalTimer($hash);
  204. InternalTimer(gettimeofday()+$hash->{Interval}, "energy_Update", $hash, 1);
  205. }
  206. if ($hash->{Interval} > 0) {
  207. InternalTimer(gettimeofday() + $hash->{Interval}, "energy_Update", $hash, 0);
  208. }
  209. my %pair;
  210. my $out = "";
  211. my $feldername = "";
  212. my $f = "";
  213. if ( $message ne "-1" ){
  214. @array=split(/\{/,$message);
  215. if ( $felder ne "" ){ # FELDER zu namen mappen und dann loggen
  216. my @field=split(/\|/,$felder);
  217. foreach $f (@field){
  218. my $value =$array[$f];
  219. $value =~ m/value":(.*)"unit":/;
  220. if ( $1 ne "" ){
  221. $out = $1;
  222. $out =~ s/\,//;
  223. # felder in namen mappen
  224. my $feldername = $attr{$name}{"FELDERNAME"};
  225. if ( $feldername ne "" ){
  226. %pair = map{split /=/, $_}(split /\|/, $feldername);
  227. if ($pair{$f} ne ""){
  228. $hash->{READINGS}{$pair{$f}}{VAL} = $out;
  229. $hash->{READINGS}{$pair{$f}}{TIME} = $timenow;
  230. push @{$hash->{CHANGED}}, "$pair{$f} $out" ;
  231. $log .= $pair{$f}.": ". $out;
  232. Log3 $hash, 4, "$name feld: $f value: $out hash: $pair{$f} mapped!";
  233. }else{
  234. $hash->{READINGS}{$f}{VAL} = $out;
  235. $hash->{READINGS}{$f}{TIME} = $timenow;
  236. push @{$hash->{CHANGED}}, "$f $out" ;
  237. $log .= $f.": ". $out;
  238. Log3 $hash, 4, "$name feld: $f value: $out ";
  239. }
  240. $log .= " ";
  241. }else{
  242. $log .= $f.": 0 ";
  243. }
  244. }
  245. } # for ende
  246. }else{
  247. my $count = "0";
  248. foreach my $f (@array){
  249. $count += 1;
  250. $f =~ m/value":(.*)"unit":/;
  251. $log = "Attribute FELDER not defined please define it with one or more of them:\n";
  252. if ( $1 ne "" ){
  253. my $out = $1;
  254. $out =~ s/\,//;
  255. $log .= "FELDER: ".$count.": ".$out ." ";
  256. }
  257. }
  258. }
  259. Log3 $hash, 5, "$name write log file: $log";
  260. ## felder in namen mappen
  261. #my $feldername = $attr{$name}{"FELDERNAME"};
  262. #Log3 $hash, 5, "$name : $feldername";
  263. #if ( $feldername ne "" ){
  264. # my %pair = map{split /=/, $_}(split /\|/, $feldername);
  265. # while ( my ($key, $value) = each(%pair) ) {
  266. # Log3 $hash, 5, "$name xx $key => $value";
  267. # $log =~ s/$key:/$value/g;
  268. # }
  269. #}
  270. #push @{$hash->{CHANGED}}, $log;
  271. DoTrigger($hash->{NAME}, undef) if ($init_done);
  272. #Log3 $hash, 4, "$hash->{NAME} write log file: $log";
  273. if ( $hash->{STATE} eq 'Initializing' || $hash->{STATE} eq 'disconnected' ){
  274. $hash->{STATE} = 'Connected';
  275. }
  276. $hash->{STATE} = $log;
  277. }else{
  278. $hash->{STATE} = 'disconnected';
  279. Log3 $hash, 3, "$hash->{NAME} can't update - device send a error";
  280. }
  281. Log3 $hash, 5, "$hash->{NAME} loop done " ;
  282. return undef;
  283. }
  284. sub
  285. energy_energyAborted($)
  286. {
  287. my ($hash) = @_;
  288. Log3 $hash->{NAME}, 3, "BlockingCall for ". $hash->{NAME} ." was aborted";
  289. RemoveInternalTimer($hash);
  290. delete($hash->{helper}{RUNNING_PID});
  291. }
  292. sub
  293. energy_efr_Get($@)
  294. {
  295. my ($hash, @args) = @_;
  296. return 'energy_efr_Get needs two arguments' if (@args != 2);
  297. energy_Update($hash) unless $hash->{Interval};
  298. my $get = $args[1];
  299. my $val = -1;
  300. my $name = $hash->{NAME};
  301. my $felder = $attr{$name}{"FELDER"};
  302. if ( $felder ne "" ){
  303. $felder =~ s/\|/ /g;
  304. my $feldername = $attr{$name}{"FELDERNAME"};
  305. Log3 $hash, 4, "felder: $felder $name : $feldername";
  306. if ( $feldername ne "" ){
  307. my %pair = map{split /=/, $_}(split /\|/, $feldername);
  308. while ( my ($key, $value) = each(%pair) ) {
  309. Log3 $hash, 4, "$name xx $key => $value felder: $felder feldername: $feldername";
  310. $felder =~ s/$key/$value/g;
  311. }
  312. }
  313. }
  314. if (defined($hash->{READINGS}{$get})) {
  315. $val = $hash->{READINGS}{$get}{VAL};
  316. } else {
  317. return "energy_efr_Get: no such reading: $get";
  318. }
  319. if ( $get eq "?"){
  320. return "Unknown argument ?, choose one of $felder";
  321. my $felder = $attr{$name}{"FELDER"};
  322. Log3 $name, 3, "felder: $felder";
  323. }
  324. Log3 $hash, 3, "$args[0] $get => $val";
  325. return $val;
  326. }
  327. sub
  328. energy_efr_Undef($$)
  329. {
  330. my ($hash, $args) = @_;
  331. RemoveInternalTimer($hash) if $hash->{Interval};
  332. BlockingKill($hash->{helper}{RUNNING_PID}) if(defined($hash->{helper}{RUNNING_PID}));
  333. return undef;
  334. }
  335. 1;
  336. =pod
  337. =begin html
  338. <a name="EFR"></a>
  339. <h3>EFR</h3>
  340. <ul><p>
  341. This module supports EFR Power Meter. <br>
  342. The electricity meter will be polled in a defined interval for new values.
  343. </p>
  344. <b>Define</b><br>
  345. <code>define &lt;name&gt; EFR &lt;host&gt; &lt;port&gt; [&lt;interval&gt; &lt;timeout&gt;]</code><br>
  346. <p>
  347. Example:<br>
  348. define StromZ1 EFR 192.168.178.20 <br>
  349. define StromZ2 EFR 192.168.10.25 300 60 <br>
  350. </p>
  351. <b>Set</b><br>
  352. set &lt;name&gt; &lt;value&gt; &lt;nummber&gt;<br>where value is one of:<br><br>
  353. <ul>
  354. <li><code>Interval</code> </li>
  355. </ul>
  356. <br>Example:<br>
  357. set &lt;name&gt; not implemented <br><br>
  358. <b>Get</b><br>
  359. get &lt;name&gt; &lt;value&gt; <br>where value is one of the defined FELDER:<br>
  360. <ul>
  361. <li><code>11</code></li>
  362. </ul>
  363. <br>Example:<br>
  364. get &lt;name&gt; 14<br><br>
  365. </ul>
  366. =end html
  367. =cut