70_JSONMETER.pm 46 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179
  1. ###############################################################
  2. # $Id: 70_JSONMETER.pm 12222 2016-09-29 18:35:21Z grompo $
  3. #
  4. # 70_JSONMETER.pm
  5. #
  6. # (c) 2014 Torsten Poitzsch
  7. # (c) 2014-2016 tupol http://forum.fhem.de/index.php?action=profile;u=5432
  8. #
  9. # This module reads data from devices that provide OBIS compatible data
  10. # in json format (e.g. power meters)
  11. #
  12. # Copyright notice
  13. #
  14. # This script is free software; you can redistribute it and/or modify
  15. # it under the terms of the GNU General Public License as published by
  16. # the Free Software Foundation; either version 2 of the License, or
  17. # (at your option) any later version.
  18. #
  19. # The GNU General Public License can be found at
  20. # http://www.gnu.org/copyleft/gpl.html.
  21. # A copy is found in the text file GPL.txt and important notices to the license
  22. # from the author is found in LICENSE.txt distributed with these scripts.
  23. #
  24. # This script is distributed in the hope that it will be useful,
  25. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  26. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  27. # GNU General Public License for more details.
  28. #
  29. # This copyright notice MUST APPEAR in all copies of the script!
  30. #
  31. ##############################################################################
  32. #
  33. # define <name> JSONMETER <type> <host> [interval]
  34. #
  35. # If <interval> is positive, new values are read every <interval> seconds.
  36. # The default for <interval> is 300 (i.e. 5 minutes).
  37. #
  38. ##############################################################################
  39. package main;
  40. use strict;
  41. use warnings;
  42. use Blocking;
  43. use IO::Socket::INET;
  44. use MIME::Base64;
  45. sub JSONMETER_Initialize($);
  46. sub JSONMETER_Define($$);
  47. sub JSONMETER_Undefine($$);
  48. sub JSONMETER_Attr($@);
  49. sub JSONMETER_Set($$@);
  50. sub JSONMETER_Get($@);
  51. sub JSONMETER_GetUpdate($);
  52. sub JSONMETER_GetJsonFile($);
  53. sub JSONMETER_ReadFromUrl($);
  54. sub JSONMETER_ReadFromFile($);
  55. sub JSONMETER_ParseJsonFile($);
  56. sub JSONMETER_UpdateAborted($);
  57. sub JSONMETER_doStatisticMinMax ($$$);
  58. sub JSONMETER_doStatisticMinMaxSingle ($$$$);
  59. sub JSONMETER_doStatisticDelta ($$$$$);
  60. sub JSONMETER_doStatisticDeltaSingle ($$$$$$);
  61. my $MODUL = "JSONMETER";
  62. ##############################################################
  63. # Syntax: meterType => port URL-Path
  64. ##############################################################
  65. my %meterTypes = ( ITF => "80 GetMeasuredValue.cgi"
  66. ,EFR => "80 json.txt"
  67. ,LS110 => "80 a?f=j"
  68. );
  69. ##############################################################
  70. # Syntax: valueType, code, FHEM reading name, statisticType, tariffType
  71. # valueType: 1=OBISvalue | 2=OBISvalueString | 3=jsonProperty | 4=jsonPropertyTime
  72. # statisticType: 0=noStatistic | 1=maxMinStatistic | 2=integralTimeStatistic | 3=State+IntegralTimeStatistic
  73. # tariffType: 0 = tariff cannot be selected, 1 = tariff can be selected via reading "activeTariff"
  74. ##############################################################
  75. my @jsonFields = (
  76. [3, "meterType", "meterType", 0, 0] # {"meterId": "0000000061015736", "meterType": "Simplex", "interval": 0, "entry": [
  77. ,[4, "timestamp", "deviceTime", 0, 0] # {"timestamp": 1389296286, "periodEntries": [
  78. ,[3, "cnt", "electricityConsumed", 3, 1] # {"cnt":" 22,285","pwr":764,"lvl":0,"dev":"","det":"","con":"OK","sts":"(06)","raw":0}
  79. ,[3, "energy", "electricityConsumed", 3, 1] # {"status":"ok","result":[{"energy":ENERGY,"energyOut":ENERGYOUT,"time":TIME},...]}
  80. ,[3, "energyOUT", "electricityProduced", 3, 1] # {"status":"ok","result":[{"energy":ENERGY,"energyOut":ENERGYOUT,"time":TIME},...]}
  81. ,[3, "power", "electricityPower", 3, 1] # {"status":"ok","result":[{"power":POWER,"time":TIME}]}
  82. ,[3, "pwr", "electricityPower", 1, 0] # {"cnt":" 22,285","pwr":764,"lvl":0,"dev":"","det":"","con":"OK","sts":"(06)","raw":0}
  83. ,[1, "010000090B00", "deviceTime", 0, 0] # { "obis":"010000090B00","value":"dd.mm.yyyy,hh:mm"}
  84. ,[2, "0.0.0", "meterID", 0, 0] # {"obis": "0.0.0", "scale": 0, "value": 1627477814, "unit": "", "valueString": "0000000061015736" },
  85. ,[1, "0100000000FF", "meterID", 0, 0] # # { "obis":"0100000000FF","value":"xxxxx"},
  86. ,[2, "0.2.0", "firmware", 0, 0] # {"obis": "0.2.0", "scale": 0, "value": 0, "unit": "", "valueString": "V320090704" },
  87. ,[1, "1.7.0|0100010700FF", "electricityPower", 1, 0] # {"obis": "1.7.0", "scale": 0, "value": 392, "unit": "W", "valueString": "0000392" },
  88. ,[1, "21.7.0|0100150700FF", "electricityPowerPhase1", 1, 0] # {"obis":"0100150700FF","value":209.40,"unit":"W"},
  89. ,[1, "41.7.0|0100290700FF", "electricityPowerPhase2", 1, 0] # {"obis":"0100290700FF","value":14.27,"unit":"W"},
  90. ,[1, "61.7.0|01003D0700FF", "electricityPowerPhase3", 1, 0] # {"obis":"01003D0700FF","value":89.40,"unit":"W"},
  91. ,[1, "1.8.0|0101010800FF", "electricityConsumed", 3, 1] # {"obis": "1.8.0", "scale": 0, "value": 8802276, "unit": "Wh", "valueString": "0008802.276" },
  92. ,[1, "1.8.1|0101010801FF", "electricityConsumedTariff1", 2, 0] # {"obis":"0101010801FF","value":33.53,"unit":"kWh"},
  93. ,[1, "1.8.2|0101010802FF", "electricityConsumedTariff2", 2, 0] # {"obis":"0101010802FF","value":33.53,"unit":"kWh"},
  94. ,[1, "1.8.3|0101010803FF", "electricityConsumedTariff3", 2, 0] # {"obis":"0101010803FF","value":33.53,"unit":"kWh"},
  95. ,[1, "1.8.4|0101010804FF", "electricityConsumedTariff4", 2, 0] # {"obis":"0101010804FF","value":33.53,"unit":"kWh"},
  96. ,[1, "1.8.5|0101010805FF", "electricityConsumedTariff5", 2, 0] # {"obis":"0101010805FF","value":33.53,"unit":"kWh"},
  97. ,[1, "010001080080", "electricityConsumedToday", 0, 0]
  98. ,[1, "010001080081", "electricityConsumedYesterday", 0, 0]
  99. ,[1, "010001080082", "electricityConsumedLastWeek", 0, 0]
  100. ,[1, "010001080083", "electricityConsumedLastMonth", 0, 0]
  101. ,[1, "010001080084", "electricityConsumedLastYear", 0, 0]
  102. ,[1, "010002080080", "electricityProducedToday", 0, 0]
  103. ,[1, "010002080081", "electricityProducedYesterday", 0, 0]
  104. ,[1, "010002080082", "electricityProducedLastWeek", 0, 0]
  105. ,[1, "010002080083", "electricityProducedLastMonth", 0, 0]
  106. ,[1, "010002080084", "electricityProducedLastYear", 0, 0]
  107. ,[1, "0101020800FF", "electricityPowerOutput", 1, 0]
  108. ,[1, "32.7.0|010020070000", "electricityVoltagePhase1", 1, 0] #{"obis":"010020070000","value":237.06,"unit":"V"},
  109. ,[1, "52.7.0|010034070000", "electricityVoltagePhase2", 1, 0] # {"obis":"010034070000","value":236.28,"unit":"V"},
  110. ,[1, "72.7.0|010048070000", "electricityVoltagePhase3", 1, 0] # {"obis":"010048070000","value":236.90,"unit":"V"},
  111. ,[1, "01000E070000", "electricityFrequency", 1, 0] # {"obis":"01000E070000","value":49.950,"unit":"Hz"}
  112. ,[1, "31.7.0", "electricityCurrentPhase1", 1, 0] # {"obis":"31.7.0","value":2.28,"unit":"A"},
  113. ,[1, "51.7.0", "electricityCurrentPhase2", 1, 0] # {"obis":"51.7.0","value":2.28,"unit":"A"},
  114. ,[1, "71.7.0", "electricityCurrentPhase3", 1, 0] # {"obis":"71.7.0","value":0.360,"unit":"A"}
  115. );
  116. ##############################################################
  117. sub ##########################################
  118. JSONMETER_Log($$$)
  119. {
  120. my ( $hash, $loglevel, $text ) = @_;
  121. my $xline = ( caller(0) )[2];
  122. my $xsubroutine = ( caller(1) )[3];
  123. my $sub = ( split( ':', $xsubroutine ) )[2];
  124. $sub =~ s/JSONMETER_//;
  125. my $instName = ( ref($hash) eq "HASH" ) ? $hash->{NAME} : $hash;
  126. Log3 $hash, $loglevel, "$MODUL $instName: $sub.$xline " . $text;
  127. }
  128. sub ##########################################
  129. JSONMETER_Initialize($)
  130. {
  131. my ($hash) = @_;
  132. $hash->{DefFn} = "JSONMETER_Define";
  133. $hash->{UndefFn} = "JSONMETER_Undefine";
  134. $hash->{SetFn} = "JSONMETER_Set";
  135. $hash->{GetFn} = "JSONMETER_Get";
  136. $hash->{AttrFn} = "JSONMETER_Attr";
  137. $hash->{AttrList} = "disable:0,1 "
  138. ."doStatistics:0,1 "
  139. ."timeOut "
  140. ."pathString "
  141. ."port "
  142. ."alwaysAnalyse:0,1 "
  143. .$readingFnAttributes;
  144. } # end JSONMETER_Initialize
  145. sub ##########################################
  146. JSONMETER_Define($$)
  147. {
  148. my ($hash, $def) = @_;
  149. my @args = split("[ \t][ \t]*", $def);
  150. return "Usage: define <name> JSONMETER <deviceType> <host> [interval]" if(@args <3 || @args >5);
  151. my $name = $args[0];
  152. my $type = $args[2];
  153. my $interval = 5*60;
  154. my $host;
  155. my $typeStr;
  156. if ($type eq "file")
  157. {
  158. return "Usage: define <name> JSONMETER url [interval]" if (@args >4);
  159. $interval = $args[3] if(int(@args) == 4);
  160. } else {
  161. return "Usage: define <name> JSONMETER <deviceType> <host> [interval]" if(@args <4);
  162. $host = $args[3];
  163. $interval = $args[4] if(int(@args) == 5);
  164. }
  165. $interval = 10 if( $interval < 10 && $interval != 0);
  166. if ($type ne "url" && $type ne "file") {
  167. $typeStr = $meterTypes{$type};
  168. return "Unknown type '$type': use url|file|". join ("|", keys(%meterTypes)) unless $typeStr;
  169. my @typeAttr = split / /, $typeStr;
  170. $hash->{PORT} = $typeAttr[0];
  171. $hash->{urlPath} = $typeAttr[1];
  172. }
  173. $hash->{NAME} = $name;
  174. $hash->{STATE} = "Initializing" if $interval > 0;
  175. $hash->{STATE} = "Manual mode" if $interval == 0;
  176. $hash->{HOST} = $host if $type ne "file";
  177. $hash->{INTERVAL} = $interval;
  178. $hash->{NOTIFYDEV} = "global";
  179. $hash->{deviceType} = $type;
  180. RemoveInternalTimer($hash);
  181. #Get first data after 13 seconds
  182. InternalTimer(gettimeofday() + 13, "JSONMETER_GetUpdate", $hash, 0) if $interval > 0;
  183. #Reset temporary values
  184. $hash->{fhem}{jsonInterpreter} = "";
  185. $hash->{fhem}{modulVersion} = '$Date: 2016-09-29 20:35:21 +0200 (Thu, 29 Sep 2016) $';
  186. return undef;
  187. } #end JSONMETER_Define
  188. sub ##########################################
  189. JSONMETER_Undefine($$)
  190. {
  191. my ($hash, $args) = @_;
  192. RemoveInternalTimer($hash);
  193. BlockingKill($hash->{helper}{RUNNING_PID}) if(defined($hash->{helper}{RUNNING_PID}));
  194. return undef;
  195. } # end JSONMETER_Undefine
  196. sub ##########################################
  197. JSONMETER_Attr($@)
  198. {
  199. my ($cmd,$name,$aName,$aVal) = @_;
  200. # $cmd can be "del" or "set"
  201. # $name is device name
  202. # aName and aVal are Attribute name and value
  203. if ($cmd eq "set") {
  204. if ($aName eq "1allowSetParameter") {
  205. eval { qr/$aVal/ };
  206. if ($@) {
  207. JSONMETER_Log $name, 3, "Invalid allowSetParameter in attr $name $aName $aVal: $@";
  208. return "Invalid allowSetParameter $aVal";
  209. }
  210. }
  211. }
  212. return undef;
  213. } # JSONMETER_Attr ende
  214. sub ##########################################
  215. JSONMETER_Set($$@)
  216. {
  217. my ($hash, $name, $cmd, $val) = @_;
  218. my $resultStr = "";
  219. if(lc $cmd eq 'update') {
  220. $hash->{LOCAL} = 1;
  221. JSONMETER_GetUpdate($hash);
  222. $hash->{LOCAL} = 0;
  223. return undef;
  224. }
  225. elsif(lc $cmd eq 'restartjsonanalysis') {
  226. $hash->{fhem}{jsonInterpreter} = "";
  227. $hash->{LOCAL} = 1;
  228. JSONMETER_GetUpdate($hash);
  229. $hash->{LOCAL} = 0;
  230. Log3 $name, 3, "JSONMETER: set $name $cmd";
  231. return undef;
  232. }
  233. elsif (lc $cmd eq 'resetstatistics') {
  234. if ($val =~ /all|statElectricityConsumed\.\.\.|statElectricityConsumedTariff\.\.\.|statElectricityPower\.\.\./) {
  235. my $regExp;
  236. if ($val eq "all") { $regExp = "stat"; }
  237. else { $regExp = substr $val, 0, -3; }
  238. foreach (sort keys %{ $hash->{READINGS} }) {
  239. if ($_ =~ /^\.?$regExp/ && $_ ne "state") {
  240. delete $hash->{READINGS}{$_};
  241. $resultStr .= " " . $_;
  242. }
  243. }
  244. }
  245. WriteStatefile();
  246. return $resultStr;
  247. }
  248. elsif(lc $cmd eq 'interval' && int(@_)==4 ) {
  249. $val = 10 if( $val < 10 );
  250. $hash->{INTERVAL}=$val;
  251. Log3 $name, 3, "JSONMETER: set $name $cmd $val";
  252. return undef;
  253. }
  254. elsif(lc $cmd eq 'activetariff' && int(@_)==4 ) {
  255. $val = 0 if( $val < 1 || $val > 9 );
  256. readingsSingleUpdate($hash,"activeTariff",$val, 1);
  257. $hash->{LOCAL} = 1;
  258. JSONMETER_GetUpdate($hash);
  259. $hash->{LOCAL} = 0;
  260. Log3 $name, 3, "JSONMETER: set $name $cmd $val";
  261. return undef;
  262. }
  263. my $list = "update:noArg"
  264. ." activeTariff:0,1,2,3,4,5,6,7,8,9"
  265. ." resetStatistics:all,statElectricityConsumed...,statElectricityConsumedTariff...,statElectricityPower..."
  266. ." restartJsonAnalysis:noArg"
  267. ." INTERVAL:slider,0,10,600";
  268. return "Unknown argument $cmd, choose one of $list";
  269. } # end JSONMETER_Set
  270. sub ##########################################
  271. JSONMETER_Get($@)
  272. {
  273. my ($hash, $name, $cmd) = @_;
  274. my $result;
  275. my $message;
  276. if ($cmd eq "jsonFile") {
  277. my $time = gettimeofday();
  278. $result = JSONMETER_GetJsonFile $name;
  279. my @a = split /\|/, $result;
  280. if ($a[1]==0) {
  281. $message = $a[2];
  282. } else {
  283. $message = decode_base64($a[2]);
  284. }
  285. $time = gettimeofday() - $time;
  286. if ($time > AttrVal($name, "timeOut", 10)) {
  287. $message = sprintf( "Runtime: %.2f s (!!! Increase attribute 'timeOut' !!!)\n_________________\n\n", $time) . $message;
  288. } else {
  289. $message = sprintf( "Runtime: %.2f s\n_________________\n\n", $time) . $message;
  290. }
  291. return $message;
  292. }
  293. elsif ($cmd eq "jsonAnalysis") {
  294. my $time = gettimeofday();
  295. $hash->{fhem}{jsonInterpreter} = "";
  296. $result = JSONMETER_GetJsonFile $name;
  297. my @a = split /\|/, $result;
  298. return $a[2] if $a[1]==0;
  299. $result = JSONMETER_ParseJsonFile $result;
  300. # my @a = split /\|/, $result;
  301. $time = gettimeofday() - $time;
  302. $message = sprintf( "Runtime: %.2f s\n_________________\n\n", $time);
  303. $message .= decode_base64($result); #$a[2]);
  304. return $message;
  305. }
  306. my $list = "jsonFile:noArg"
  307. ." jsonAnalysis:noArg";
  308. return "Unknown argument $cmd, choose one of $list";
  309. } # end JSONMETER_Get
  310. sub ##########################################
  311. JSONMETER_GetUpdate($)
  312. {
  313. my ($hash) = @_;
  314. my $name = $hash->{NAME};
  315. my $type = $hash->{deviceType};
  316. if(!$hash->{LOCAL} && $hash->{INTERVAL} > 0) {
  317. RemoveInternalTimer($hash);
  318. InternalTimer(gettimeofday()+$hash->{INTERVAL}, "JSONMETER_GetUpdate", $hash, 1);
  319. return undef if( AttrVal($name, "disable", 0 ) == 1 );
  320. }
  321. if ( ( $type eq "url" || $type eq "file" ) && ! defined($attr{$name}{"pathString"}) )
  322. {
  323. JSONMETER_Log $name,2,"Error reading device: Please define the attribute 'pathString'";
  324. $hash->{STATE} = "pathString missing";
  325. return "$name|0|Error reading device: Please define the attribute 'pathString'.";
  326. }
  327. my $timeOut = AttrVal($name, "timeOut", 10);
  328. $hash->{helper}{RUNNING_PID} = BlockingCall("JSONMETER_GetJsonFile", $name,
  329. "JSONMETER_ParseJsonFile", $timeOut,
  330. "JSONMETER_UpdateAborted", $hash)
  331. unless(exists($hash->{helper}{RUNNING_PID}));
  332. }
  333. ##########################################
  334. sub JSONMETER_GetJsonFile ($)
  335. {
  336. my ($name) = @_;
  337. my $returnStr;
  338. my $hash = $defs{$name};
  339. my $type = $hash->{deviceType};
  340. my $ip = "";
  341. $ip = $hash->{HOST} if defined $hash->{HOST};
  342. my $urlPath = "";
  343. $urlPath = $hash->{urlPath} if defined $hash->{urlPath};
  344. if (($type eq "url" || $type eq "file") && ! defined($attr{$name}{"pathString"}))
  345. {return "$name|0|Error: deviceType is '$type' - Please define the attribute 'pathString' first.";}
  346. my $pathString = AttrVal($name, "pathString", "");
  347. my $port = 80;
  348. $port = $hash->{PORT} if defined $hash->{PORT};
  349. $port = $attr{$name}{"port"} if $type eq "url" && defined($attr{$name}{"port"});
  350. $hash->{PORT} = $port if $type ne "file";
  351. if ( $type eq "file")
  352. {
  353. $returnStr = JSONMETER_ReadFromFile $name."|".$pathString;
  354. }
  355. else
  356. {
  357. $returnStr = JSONMETER_ReadFromUrl $name."|".$ip."|".$port."|".$urlPath.$pathString;
  358. }
  359. return $returnStr;
  360. }
  361. ##########################################
  362. sub JSONMETER_ReadFromFile($)
  363. {
  364. my ($string) = @_;
  365. my ($name, $pathString) = split "\\|", $string;
  366. JSONMETER_Log $name, 4, "Open file '$pathString'";
  367. if (open(IN, "<" . $pathString)) {
  368. my $message = join " ", <IN>;
  369. close(IN);
  370. JSONMETER_Log $name, 4, "Close file";
  371. $message = encode_base64($message,"");
  372. return "$name|1|$message" ;
  373. } else {
  374. JSONMETER_Log $name, 2, "Cannot open file $pathString: $!";
  375. return "$name|0|Error: Cannot open file $pathString: $!";;
  376. }
  377. } # end JSONMETER_ReadFromFile
  378. sub ##########################################
  379. JSONMETER_ReadFromUrl($)
  380. {
  381. my ($string) = @_;
  382. my ($name, $ip, $port, $pathString) = split "\\|", $string;
  383. my $buf ;
  384. my $message ;
  385. JSONMETER_Log $name, 4, "opening socket to host $ip port $port" ;
  386. my $socket = new IO::Socket::INET (
  387. PeerAddr => $ip,
  388. PeerPort => $port,
  389. Proto => 'tcp',
  390. Reuse => 0,
  391. Timeout => 9
  392. );
  393. if (!$socket) {
  394. JSONMETER_Log $name, 1, "Could not open connection to ip $ip port $port";
  395. return "$name|0|Can't connect to ip $ip port $port";
  396. }
  397. if (defined ($socket) and $socket and $socket->connected())
  398. {
  399. print $socket "GET /$pathString HTTP/1.0\r\n\r\n";
  400. JSONMETER_Log $name, 4, "Get json file from http://$ip:$port/$pathString";
  401. $socket->autoflush(1);
  402. while ((read $socket, $buf, 1024) > 0)
  403. {
  404. $message .= $buf;
  405. }
  406. JSONMETER_Log $name, 5, "received:\n $message";
  407. $socket->close();
  408. JSONMETER_Log $name, 4, "Socket closed";
  409. if ($message =~ /^HTTP\/1.\d 404 Not Found/) {
  410. return "$name|0|Error: URL 'http://$ip:$port/$pathString' returned 'Error 404: Page Not Found'";
  411. }
  412. $message = encode_base64($message,"");
  413. return "$name|1|$message" ;
  414. }
  415. } # end JSONMETER_ReadFromUrl
  416. ###########################
  417. sub JSONMETER_ParseJsonFile($)
  418. {
  419. my ($string) = @_;
  420. return unless(defined($string));
  421. my (@a) = split("\\|", $string);
  422. return unless (defined $defs{$a[0]});
  423. my $hash = $defs{$a[0]};
  424. my $name = $hash->{NAME};
  425. my $value;
  426. my $returnStr ="";
  427. my $statisticType;
  428. delete($hash->{helper}{RUNNING_PID});
  429. if ( $a[1] == 1 ){
  430. my $message = decode_base64($a[2]);
  431. readingsBeginUpdate($hash);
  432. my @fields=split(/\{/,$message); # JSON in einzelne Felder zerlegen
  433. my $jsonInterpreter = $hash->{fhem}{jsonInterpreter} || "";
  434. my $alwaysAnalyse = $attr{$name}{alwaysAnalyse} || 0;
  435. $returnStr .= "================= Find JSON property ==================\n\n";
  436. ####################################
  437. # ANALYSE once: Find all known obis codes in the first run and store in the item no,
  438. # value type and reading name in the jsonInterpreter
  439. ####################################
  440. if ( $jsonInterpreter eq "" || $alwaysAnalyse == 1 ) {
  441. JSONMETER_Log $name, 3, "Analyse JSON pathString for known readings" if $alwaysAnalyse != 1;
  442. JSONMETER_Log $name, 4, "Analyse JSON pathString for known readings" if $alwaysAnalyse == 1;
  443. foreach my $f (@jsonFields)
  444. {
  445. for(my $i=0; $i<=$#fields; $i++)
  446. {
  447. # if ($$f[0] =~ /^[15]$/) {
  448. if ($$f[0] == 1) {
  449. if ($fields[$i] =~ /"obis"\s*:\s*"($$f[1])"\s*[,}]/ && $fields[$i] =~ /"value"/) {
  450. $jsonInterpreter .= "|$i $$f[0] $$f[2] $$f[3] $$f[4]";
  451. JSONMETER_Log $name,4,"OBIS code \"$$f[1]\" will be stored in $$f[2]";
  452. $returnStr .= "OBIS code \"$$f[1]\" will be extracted as reading '$$f[2]' (statistic type: $$f[3]) from part $i:\n$fields[$i]\n\n";
  453. }
  454. } elsif ($$f[0] == 2) {
  455. if ($fields[$i] =~ /"obis"\s*:\s*"($$f[1])"\s*[,}]/ && $fields[$i] =~ /"valueString"/) {
  456. $jsonInterpreter .= "|$i $$f[0] $$f[2] $$f[3] $$f[4]";
  457. JSONMETER_Log $name,4,"OBIS code \"$$f[1]\" will be stored in $$f[2]";
  458. $returnStr .= "OBIS code \"$$f[1]\" will be extracted as reading '$$f[2]' (statistic type: $$f[3]) from part $i:\n$fields[$i]\n\n";
  459. }
  460. } elsif ($$f[0] == 3) {
  461. if ($fields[$i] =~ /"($$f[1])"\s*:/) {
  462. $jsonInterpreter .= "|$i $$f[0] $$f[2] $$f[3] $$f[4] $$f[1]";
  463. JSONMETER_Log $name,4,"Property \"$$f[1]\" will be stored in $$f[2]";
  464. $returnStr .= "Property \"$$f[1]\" will be extracted as reading '$$f[2]' (statistic type: $$f[3]) from part $i:\n$fields[$i]\n\n";
  465. }
  466. } elsif ($$f[0] == 4) {
  467. if ($fields[$i] =~ /"($$f[1])"\s*:/) {
  468. $jsonInterpreter .= "|$i $$f[0] $$f[2] $$f[3] $$f[4] $$f[1]";
  469. JSONMETER_Log $name,4,"Property \"$$f[1]\" will be stored in $$f[2]";
  470. $returnStr .= "Property \"$$f[1]\" will be extracted as reading '$$f[2]' (statistic type: $$f[3]) from part $i:\n$fields[$i]\n\n";
  471. }
  472. }
  473. }
  474. }
  475. if ($jsonInterpreter ne "") {
  476. JSONMETER_Log $name, 3, "Store results of JSON analysis for next device readings" if $alwaysAnalyse != 1;
  477. $jsonInterpreter = substr $jsonInterpreter, 1;
  478. $hash->{fhem}{jsonInterpreter} = $jsonInterpreter;
  479. } else {
  480. JSONMETER_Log $name, 2, "Could not interpret the JSON file => please contact FHEM community" if $jsonInterpreter eq "";
  481. }
  482. } else {
  483. $jsonInterpreter = $hash->{fhem}{jsonInterpreter} if exists $hash->{fhem}{jsonInterpreter};
  484. }
  485. ####################################
  486. # INTERPRETE AND STORE
  487. # use the previously filled jsonInterpreter to extract the correct values
  488. ####################################
  489. $returnStr .= "\n================= Extract JSON values ==================\n\n";
  490. my @a = split /\|/, $jsonInterpreter;
  491. JSONMETER_Log $name, 4, "Extract ".($#a+1)." readings from ".($#fields+1)." json parts";
  492. foreach (@a) {
  493. $statisticType = 0;
  494. JSONMETER_Log $name, 5, "Handle $_";
  495. my @b = split / /, $_ ;
  496. #obis value
  497. if ($b[1] == 1) {
  498. if ($fields[$b[0]] =~ /"value"\s*:\s*"(.*?)"\s*[,\}]/g || $fields[$b[0]] =~ /"value"\s*:\s*(.*?)\s*[,\}]/g) {
  499. $value = $1;
  500. # $value =~ s/^\s+|\s+$//g;
  501. JSONMETER_Log $name, 4, "Value $value for reading $b[2] extracted from '$fields[$b[0]]'";
  502. $returnStr .= "Value \"$value\" for reading '$b[2]' extracted from part $b[0]:\n$fields[$b[0]]\n\n";
  503. readingsBulkUpdate($hash,$b[2],$value);
  504. $statisticType = $b[3];
  505. } else {
  506. JSONMETER_Log $name, 4, "Could not extract value for reading $b[2] from '$fields[$b[0]]'";
  507. $returnStr .= "Could not extract value for reading '$b[2]' from part $b[0]:\n$fields[$b[0]]\n\n";
  508. }
  509. #obis valueString
  510. } elsif ($b[1] == 2) {
  511. if ($fields[$b[0]] =~ /"valueString"\s*:\s*"(.*?)"\s*[,}]/g ) {
  512. $value = $1;
  513. JSONMETER_Log $name, 4, "Value $value for reading $b[2] extracted from '$fields[$b[0]]'";
  514. $returnStr .= "Value \"$value\" for reading '$b[2]' extracted from part $b[0]:\n$fields[$b[0]]\n\n";
  515. readingsBulkUpdate($hash,$b[2],$value);
  516. $statisticType = $b[3];
  517. } else {
  518. JSONMETER_Log $name, 4, "Could not extract value for reading $b[2] from '$fields[$b[0]]'";
  519. $returnStr .= "Could not extract value for reading '$b[2]' from part $b[0]:\n$fields[$b[0]]\n\n";
  520. }
  521. # JSON-Property
  522. } elsif ($b[1] == 3) {
  523. if ($fields[$b[0]] =~ /"$b[5]"\s*:\s*"(.*?)"\s*[,}]/g || $fields[$b[0]] =~ /"$b[5]"\s*:\s*(.*?)\s*[,}]/g ) {
  524. $value = $1;
  525. $value =~ /^ *\d+(,\d\d\d)+/ && $value =~ s/,| //g;
  526. JSONMETER_Log $name, 4, "Value $value for reading $b[2] extracted from '$fields[$b[0]]'";
  527. $returnStr .= "Value \"$value\" for reading '$b[2]' extracted from part $b[0]:\n$fields[$b[0]]\n\n";
  528. readingsBulkUpdate($hash, $b[2], $value);
  529. $statisticType = $b[3];
  530. } else {
  531. JSONMETER_Log $name, 4, "Could not extract value for reading $b[2] from '$fields[$b[0]]'";
  532. $returnStr .= "Could not extract value for reading '$b[2]' from part $b[0]:\n$fields[$b[0]]\n\n";
  533. }
  534. # JSON-Property Time
  535. } elsif ($b[1] == 4) {
  536. if ($fields[$b[0]] =~ /"$b[5]"\s*:\s"?(\d*)"?\s*[,}]/g ) {
  537. $value = $1;
  538. JSONMETER_Log $name, 4, "Value $value for reading $b[2] extracted from '$fields[$b[0]]'";
  539. $returnStr .= "Value \"$value\" for reading '$b[2]' extracted from part $b[0]:\n$fields[$b[0]]\n\n";
  540. $value = strftime "%Y-%m-%d %H:%M:%S", localtime($value);
  541. readingsBulkUpdate($hash, $b[2], $value);
  542. $statisticType = $b[3];
  543. } else {
  544. JSONMETER_Log $name, 4, "Could not extract value for reading $b[2] from '$fields[$b[0]]'";
  545. $returnStr .= "Could not extract value for reading '$b[2]' from part $b[0]:\n$fields[$b[0]]\n\n";
  546. }
  547. }
  548. if ( AttrVal($name,"doStatistics",0) == 1) {
  549. my $activeTariff = ReadingsVal($name,"activeTariff",0);
  550. if ($b[4] == 0) { $activeTariff = 0;}
  551. # JSONMETER_doStatisticMinMax $hash, $readingName, $value
  552. if ($statisticType == 1 ) { JSONMETER_doStatisticMinMax $hash, "stat".ucfirst($b[2]), $value ; }
  553. # JSONMETER_doStatisticDelta: $hash, $readingName, $value, $special, $activeTariff
  554. if ($statisticType == 2 ) { JSONMETER_doStatisticDelta $hash, "stat".ucfirst($b[2]), $value, 0, $activeTariff ; }
  555. # JSONMETER_doStatisticDelta: $hash, $readingName, $value, $special, $activeTariff
  556. if ($statisticType == 3 ) { JSONMETER_doStatisticDelta $hash, "stat".ucfirst($b[2]), $value, 1, $activeTariff ; }
  557. }
  558. }
  559. readingsBulkUpdate($hash,"state","Connected");
  560. readingsEndUpdate($hash,1);
  561. DoTrigger($hash->{NAME}, undef) if ($init_done);
  562. } else {
  563. readingsSingleUpdate($hash,"state",$a[2],1);
  564. }
  565. return encode_base64($returnStr);
  566. }
  567. sub ############################
  568. JSONMETER_UpdateAborted($)
  569. {
  570. my ($hash) = @_;
  571. delete($hash->{helper}{RUNNING_PID});
  572. my $name = $hash->{NAME};
  573. my $host = $hash->{HOST};
  574. JSONMETER_Log $hash, 1, "Timeout when connecting to host $host";
  575. } # end JSONMETER_UpdateAborted
  576. # Calculates single MaxMin Values and informs about end of day and month
  577. sub ########################################
  578. JSONMETER_doStatisticMinMax ($$$)
  579. {
  580. my ($hash, $readingName, $value) = @_;
  581. my $dummy;
  582. my $lastReading;
  583. my $lastSums;
  584. my @newReading;
  585. my $yearLast;
  586. my $monthLast;
  587. my $dayLast;
  588. my $dayNow;
  589. my $monthNow;
  590. my $yearNow;
  591. # Determine date of last and current reading
  592. if (exists($hash->{READINGS}{$readingName."Day"}{TIME})) {
  593. ($yearLast, $monthLast, $dayLast) = $hash->{READINGS}{$readingName."Day"}{TIME} =~ /^(\d\d\d\d)-(\d\d)-(\d\d)/;
  594. } else {
  595. ($dummy, $dummy, $dummy, $dayLast, $monthLast, $yearLast) = localtime;
  596. $yearLast += 1900;
  597. $monthLast ++;
  598. }
  599. ($dummy, $dummy, $dummy, $dayNow, $monthNow, $yearNow) = localtime;
  600. $yearNow += 1900;
  601. $monthNow ++;
  602. # Daily Statistic
  603. #JSONMETER_doStatisticMinMaxSingle: $hash, $readingName, $value, $saveLast
  604. JSONMETER_doStatisticMinMaxSingle $hash, $readingName."Day", $value, ($dayNow != $dayLast);
  605. # Monthly Statistic
  606. #JSONMETER_doStatisticMinMaxSingle: $hash, $readingName, $value, $saveLast
  607. JSONMETER_doStatisticMinMaxSingle $hash, $readingName."Month", $value, ($monthNow != $monthLast);
  608. # Yearly Statistic
  609. #JSONMETER_doStatisticMinMaxSingle: $hash, $readingName, $value, $saveLast
  610. JSONMETER_doStatisticMinMaxSingle $hash, $readingName."Year", $value, ($yearNow != $yearLast);
  611. return ;
  612. }
  613. # Calculates single MaxMin Values and informs about end of day and month
  614. sub ########################################
  615. JSONMETER_doStatisticMinMaxSingle ($$$$)
  616. {
  617. my ($hash, $readingName, $value, $saveLast) = @_;
  618. my $result;
  619. my $lastReading = $hash->{READINGS}{$readingName}{VAL} || "";
  620. # Initializing
  621. if ( $lastReading eq "" ) {
  622. my $since = strftime "%Y-%m-%d_%H:%M:%S", localtime();
  623. $result = "Count: 1 Sum: $value ShowDate: 1";
  624. readingsBulkUpdate($hash, ".".$readingName, $result);
  625. $result = "Min: $value Avg: $value Max: $value (since: $since )";
  626. readingsBulkUpdate($hash, $readingName, $result);
  627. # Calculations
  628. } else {
  629. my @a = split / /, $hash->{READINGS}{"." . $readingName}{VAL}; # Internal values
  630. my @b = split / /, $lastReading;
  631. # Do calculations
  632. $a[1]++; # Count
  633. $a[3] += $value; # Sum
  634. if ($value < $b[1]) { $b[1]=$value; } # Min
  635. if ($a[1]>0) {$b[3] = sprintf "%.0f" , $a[3] / $a[1];} # Avg
  636. if ($value > $b[5]) { $b[5]=$value; } # Max
  637. # in case of period change, save "last" values and reset counters
  638. if ($saveLast) {
  639. $result = "Min: $b[1] Avg: $b[3] Max: $b[5]";
  640. if ($a[5] == 1) { $result .= " (since: $b[7] )"; }
  641. readingsBulkUpdate($hash, $readingName . "Last", $lastReading);
  642. $a[1] = 1; $a[3] = $value; $a[5] = 0;
  643. $b[1] = $value; $b[3] = $value; $b[5] = $value;
  644. }
  645. # Store internal calculation values
  646. $result = "Count: $a[1] Sum: $a[3] ShowDate: $a[5]";
  647. readingsBulkUpdate($hash, ".".$readingName, $result);
  648. # Store visible Reading
  649. $result = "Min: $b[1] Avg: $b[3] Max: $b[5]";
  650. if ($a[5] == 1) { $result .= " (since: $b[7] )"; }
  651. readingsBulkUpdate($hash, $readingName, $result);
  652. }
  653. return;
  654. }
  655. # Calculates deltas for day, month and year
  656. sub ########################################
  657. JSONMETER_doStatisticDelta ($$$$$)
  658. {
  659. my ($hash, $readingName, $value, $special, $activeTariff) = @_;
  660. my $dummy;
  661. my $result;
  662. my $deltaValue;
  663. my $previousTariff;
  664. my $showDate;
  665. # Determine if time period switched (day, month, year)
  666. # Get deltaValue and Tariff of previous call
  667. my $periodSwitch = 0;
  668. my $yearLast; my $monthLast; my $dayLast; my $hourLast; my $hourNow; my $dayNow; my $monthNow; my $yearNow;
  669. if (exists($hash->{READINGS}{"." . $readingName . "Before"})) {
  670. ($yearLast, $monthLast, $dayLast, $hourLast) = ($hash->{READINGS}{"." . $readingName . "Before"}{TIME} =~ /^(\d\d\d\d)-(\d\d)-(\d\d) (\d\d)/);
  671. $yearLast -= 1900;
  672. $monthLast --;
  673. ($dummy, $deltaValue, $dummy, $previousTariff, $dummy, $showDate) = split / /, $hash->{READINGS}{"." . $readingName . "Before"}{VAL} || "";
  674. $deltaValue = $value - $deltaValue;
  675. } else {
  676. ($dummy, $dummy, $hourLast, $dayLast, $monthLast, $yearLast) = localtime;
  677. $deltaValue = 0;
  678. $previousTariff = 0;
  679. $showDate = 8;
  680. }
  681. ($dummy, $dummy, $hourNow, $dayNow, $monthNow, $yearNow) = localtime;
  682. if ($yearNow != $yearLast) { $periodSwitch = 4; }
  683. elsif ($monthNow != $monthLast) { $periodSwitch = 3; }
  684. elsif ($dayNow != $dayLast) { $periodSwitch = 2; }
  685. elsif ($hourNow != $hourLast) { $periodSwitch = 1; }
  686. # Determine if "since" value has to be shown in current and last reading
  687. if ($periodSwitch == 4) {
  688. if ($showDate == 1) { $showDate = 0; } # Do not show the "since:" value for year changes anymore
  689. if ($showDate >= 2) { $showDate = 1; } # Shows the "since:" value for the first year change
  690. }
  691. if ($periodSwitch >= 3){
  692. if ($showDate == 3) { $showDate = 2; } # Do not show the "since:" value for month changes anymore
  693. if ($showDate >= 4) { $showDate = 3; } # Shows the "since:" value for the first month change
  694. }
  695. if ($periodSwitch >= 2){
  696. if ($showDate == 5) { $showDate = 4; } # Do not show the "since:" value for day changes anymore
  697. if ($showDate >= 6) { $showDate = 5; } # Shows the "since:" value for the first day change
  698. }
  699. if ($periodSwitch >= 1){
  700. if ($showDate == 7) { $showDate = 6; } # Do not show the "since:" value for day changes anymore
  701. if ($showDate >= 8) { $showDate = 7; } # Shows the "since:" value for the first hour change
  702. }
  703. # JSONMETER_doStatisticDeltaSingle; $hash, $readingName, $deltaValue, $special, $periodSwitch, $showDate, $firstCall
  704. JSONMETER_doStatisticDeltaSingle ($hash, $readingName, $deltaValue, $special, $periodSwitch, $showDate);
  705. foreach (1,2,3,4,5,6,7,8,9) {
  706. if ( $previousTariff == $_ ) {
  707. JSONMETER_doStatisticDeltaSingle ($hash, $readingName."Tariff".$_, $deltaValue, 0, $periodSwitch, $showDate);
  708. } elsif ($activeTariff == $_ || ($periodSwitch > 0 && exists($hash->{READINGS}{$readingName . "Tariff".$_}))) {
  709. JSONMETER_doStatisticDeltaSingle ($hash, $readingName."Tariff".$_, 0, 0 , $periodSwitch, $showDate);
  710. }
  711. }
  712. # Hidden storage of current values for next call(before values)
  713. $result = "Value: $value Tariff: $activeTariff ShowDate: $showDate ";
  714. readingsBulkUpdate($hash, ".".$readingName."Before", $result);
  715. return ;
  716. }
  717. sub ########################################
  718. JSONMETER_doStatisticDeltaSingle ($$$$$$)
  719. {
  720. my ($hash, $readingName, $deltaValue, $special, $periodSwitch, $showDate) = @_;
  721. my $dummy;
  722. my $result;
  723. # get existing statistic reading
  724. my @curr;
  725. if (exists($hash->{READINGS}{$readingName}{VAL})) {
  726. @curr = split / /, $hash->{READINGS}{$readingName}{VAL} || "";
  727. if ($curr[0] eq "Day:") { $curr[9]=$curr[7]; $curr[7]=$curr[5]; $curr[5]=$curr[3]; $curr[3]=$curr[1]; $curr[1]=0; }
  728. } else {
  729. $curr[1] = 0; $curr[3] = 0; $curr[5] = 0; $curr[7] = 0;
  730. $curr[9] = strftime "%Y-%m-%d_%H:%M:%S", localtime(); # start
  731. }
  732. # get statistic values of previous period
  733. my @last;
  734. if (exists ($hash->{READINGS}{$readingName."Last"})) {
  735. @last = split / /, $hash->{READINGS}{$readingName."Last"}{VAL};
  736. if ($last[0] eq "Day:") { $last[9]=$last[7]; $last[7]=$last[5]; $last[5]=$last[3]; $last[3]=$last[1]; $last[1]="-"; }
  737. } else {
  738. @last = split / /, "Hour: - Day: - Month: - Year: -";
  739. }
  740. # Do statistic
  741. $curr[1] += $deltaValue;
  742. $curr[3] += $deltaValue;
  743. $curr[5] += $deltaValue;
  744. $curr[7] += $deltaValue;
  745. # If change of year, change yearly statistic
  746. if ($periodSwitch == 4){
  747. $last[7] = $curr[7];
  748. $curr[7] = 0;
  749. if ($showDate == 1) { $last[9] = $curr[9]; }
  750. }
  751. # If change of month, change monthly statistic
  752. if ($periodSwitch >= 3){
  753. $last[5] = $curr[5];
  754. $curr[5] = 0;
  755. if ($showDate == 3) { $last[9] = $curr[9];}
  756. }
  757. # If change of day, change daily statistic
  758. if ($periodSwitch >= 2){
  759. $last[3] = $curr[3];
  760. $curr[3] = 0;
  761. if ($showDate == 5) {
  762. $last[9] = $curr[9];
  763. # Next monthly and yearly values start at 00:00 and show only date (no time)
  764. $curr[5] = 0;
  765. $curr[7] = 0;
  766. $curr[9] = strftime "%Y-%m-%d", localtime(); # start
  767. }
  768. }
  769. # If change of hour, change hourly statistic
  770. if ($periodSwitch >= 1){
  771. $last[1] = $curr[1];
  772. $curr[1] = 0;
  773. if ($showDate == 7) { $last[9] = $curr[9];}
  774. }
  775. # Store visible statistic readings (delta values)
  776. $result = "Hour: $curr[1] Day: $curr[3] Month: $curr[5] Year: $curr[7]";
  777. if ( $showDate >=2 ) { $result .= " (since: $curr[9] )"; }
  778. readingsBulkUpdate($hash,$readingName,$result);
  779. if ($special == 1) { readingsBulkUpdate($hash,$readingName."Today",$curr[3]) };
  780. # if changed, store previous visible statistic (delta) values
  781. if ($periodSwitch >= 1) {
  782. $result = "Hour: $last[1] Day: $last[3] Month: $last[5] Year: $last[7]";
  783. if ( $showDate =~ /1|3|5|7/ ) { $result .= " (since: $last[9] )";}
  784. readingsBulkUpdate($hash,$readingName."Last",$result);
  785. }
  786. }
  787. 1;
  788. =pod
  789. =begin html
  790. =item device
  791. =item summary reads OBIS data from measurement units
  792. =item summary_DE liest OBIS Daten von Messger&auml;ten
  793. <a name="JSONMETER"></a>
  794. <h3>JSONMETER</h3>
  795. <div>
  796. <ul>
  797. This module reads data from a measurement unit (so called smart meters for electricity, gas or heat)
  798. <br>
  799. that provides OBIS compliant data in JSON format on a webserver or on the FHEM file system.
  800. <br>
  801. It assumes normally, that the structur of the JSON data do not change.
  802. <br>
  803. &nbsp;
  804. <br>
  805. <b>Define</b>
  806. <ul>
  807. <br>
  808. <code>define &lt;name&gt; JSONMETER &lt;deviceType&gt; [&lt;ip address&gt;] [poll-interval]</code>
  809. <br>
  810. Example: <code>define powermeter JSONMETER ITF 192.168.178.20 300</code>
  811. <br>&nbsp;
  812. <li><code>&lt;deviceType&gt;</code>
  813. <br>
  814. Mandatory. Used to define the path and port to extract the json file.
  815. <br>
  816. The attribute 'pathString' can be used to add login information to the URL path of predefined devices.
  817. <br>&nbsp;
  818. <ul>
  819. <li><b>ITF</b> - FROETEC Simplex ME one tariff electrical meter (N-ENERGY) (<a href="http://www.itf-froeschl.de">ITF Fr&ouml;schl</a>)</li>
  820. <li><b>EFR</b> - <a href="http://www.efr.de">EFR</a> Smart Grid Hub for electrical meter (EON, N-ENERGY and EnBW)
  821. <br>
  822. &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;use the 'pathstring' attribute to specifiy your login information
  823. <br>
  824. &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<code>attr <device> pathString ?LogName=<i>user</i>&LogPSWD=<i>password</i></code>
  825. </li>
  826. <li><b>LS110</b> - <a href="http://www.youless.nl/productdetails/product/ls110.html">YouLess LS110</a> network sensor (counter) for electro mechanical electricity meter</li>
  827. <li><b>url</b> - use the URL defined via the attributes 'pathString' and 'port'</li>
  828. <li><b>file</b> - use the file defined via the attribute 'pathString' (positioned in the FHEM file system)</li>
  829. </ul>
  830. </li><br>
  831. <li><code>[&lt;ip address&gt;]</code>
  832. <br>
  833. IP address of the phyisical device. (not needed for 'url' and 'file')
  834. </li><br>
  835. <li><code>[poll-interval]</code>
  836. <br>
  837. Optional. Default is 300 seconds. Smallest possible value is 10. With 0 it will only update on "manual" request.
  838. </li>
  839. </ul>
  840. <br>
  841. <b>Set</b>
  842. <ul>
  843. <li><code>activeTariff &lt; 0 - 9 &gt;</code>
  844. <br>
  845. Allows the separate measurement of the consumption (doStatistics = 1) within different tariffs for all gages that miss this built-in capability (e.g. LS110). Also the possible gain of a change to a time-dependent tariff can be evaluated with this.<br>
  846. This value must be set at the correct point of time in accordance to the existing or planned tariff <b>by the FHEM command "at"</b>.<br>
  847. 0 = without separate tariffs
  848. </li><br>
  849. <li><code>INTERVAL &lt;polling interval&gt;</code>
  850. <br>
  851. Polling interval in seconds
  852. </li><br>
  853. <li><code>resetStatistics &lt;statReadings&gt;</code>
  854. <br>
  855. Deletes the selected statistic values: <i>all, statElectricityConsumed..., statElectricityConsumedTariff..., statElectricityPower...</i>
  856. </li><br>
  857. <li><code>restartJsonAnalysis</code><br>
  858. Restarts the analysis of the json file for known readings (compliant to the OBIS standard).
  859. <br>
  860. This analysis happens normally only once if readings have been found.
  861. </li><br>
  862. <li><code>statusRequest</code>
  863. <br>
  864. Update device information
  865. </li>
  866. </ul>
  867. <br>
  868. <b>Get</b>
  869. <ul>
  870. <li><code>jsonFile</code>
  871. <br>
  872. extracts and shows the json data
  873. </li><br>
  874. <li><code>jsonAnalysis</code>
  875. <br>
  876. extracts the json data and shows the result of the analysis</li>
  877. </ul>
  878. <br>
  879. <a name="JSONMETERattr"></a>
  880. <b>Attributes</b>
  881. <ul>
  882. <li><code>alwaysAnalyse &lt; 0 | 1 &gt;</code>
  883. <br>
  884. Repeats by each update the json analysis - use if structure of json data changes
  885. <br>
  886. Normally the once analysed structure is saved to reduce CPU load.
  887. </li><br>
  888. <li><code>doStatistics &lt; 0 | 1 &gt;</code>
  889. <br>
  890. Builds daily, monthly and yearly statistics for certain readings (average/min/max or cumulated values).
  891. <br>
  892. Logging and visualisation of the statistics should be done with readings of type 'stat<i>ReadingName</i><b>Last</b>'.
  893. </li><br>
  894. <li><code>pathString &lt;string&gt;</code>
  895. <ul>
  896. <li>if deviceType = 'file': specifies the local file name and path</li>
  897. <li>if deviceType = 'url': specifies the url path</li>
  898. <li>other deviceType: can be used to add login information to the url path of predefined devices</li>
  899. </ul>
  900. </li><br>
  901. <li><code>port &lt;number&gt;</code>
  902. <br>
  903. Specifies the IP port for the deviceType 'url' (default is 80)
  904. </li><br>
  905. <li><code>timeOut &lt;seconds&gt;</code>
  906. <br>
  907. Specifies the timeout for the reading of the raw data. (default is 10)
  908. <br>
  909. The run time of the reading process can be measured via "get <device> jsonFile".
  910. </li><br>
  911. <li><a href="#readingFnAttributes">readingFnAttributes</a></li>
  912. </ul>
  913. </ul>
  914. </div>
  915. =end html
  916. =begin html_DE
  917. <a name="JSONMETER"></a>
  918. <h3>JSONMETER</h3>
  919. <div>
  920. <ul>
  921. Dieses Modul liest Daten von Messger&auml;ten (z.B. Stromz&auml;hler, Gasz&auml;hler oder W&auml;rmez&auml;hler, so genannte Smartmeter),
  922. welche <a href="http://de.wikipedia.org/wiki/OBIS-Kennzahlen">OBIS</a> kompatible Daten im JSON-Format auf einem Webserver oder auf dem FHEM-Dateisystem zur Verf&uuml;gung stellen.
  923. <br>
  924. F&uuml;r detailierte Anleitungen bitte die <a href="http://www.fhemwiki.de/wiki/JSONMETER"><b>FHEM-Wiki</b></a> konsultieren und erg&auml;nzen.
  925. <br>
  926. &nbsp;
  927. <br>
  928. <b>Define</b>
  929. <ul>
  930. <code>define &lt;name&gt; JSONMETER &lt;Ger&auml;tetyp&gt; [&lt;IP-Adresse&gt;] [Abfrageinterval]</code>
  931. <br>
  932. Beispiel: <code>define Stromzaehler JSONMETER ITF 192.168.178.20 300</code>
  933. <br>&nbsp;
  934. <li><code>[Abfrageinterval]</code>
  935. <br>
  936. Optional. Standardm&auml;ssig 300 Sekunden. Der kleinste m&ouml;gliche Wert ist 30.
  937. <br>
  938. Bei 0 kann die Ger&auml;teabfrage nur manuell gestartet werden.
  939. </li><br>
  940. <li><code>&lt;Ger&auml;tetyp&gt;</code>
  941. <br>
  942. Definiert den Pfad und den Port, um die JSON-Datei einzulesen.
  943. <br>
  944. Mit dem Attribute 'pathString' k&ouml;nnen Login Information an den URL-Pfad von vordefinierten Ger&auml;te angehangen werden.
  945. <ul>
  946. <li><b>ITF</b> - FROETEC Simplex ME Eintarifz&auml;hler (N-ENERGY) (<a href="http://www.itf-froeschl.de">ITF Fr&ouml;schl</a>)</li>
  947. <li><b>EFR</b> - <a href="http://www.efr.de">EFR</a> Smart Grid Hub f&uuml;r Stromz&auml;hler (EON, N-ENERGY, EnBW)
  948. <br>
  949. &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Die Login-Information wird &uuml;ber das Attribute 'pathstring' angegeben.
  950. <br>
  951. &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<code>?LogName=<i>Benutzer</i>&LogPSWD=<i>Passwort</i></code></li>
  952. <li><b>LS110</b> - <a href="http://www.youless.nl/productdetails/product/ls110.html">YouLess LS110</a> Netzwerkf&auml;higer Sensor f&uuml;r elektromechanische Stromz&auml;hler</li>
  953. <li><b>url</b> - benutzt die URL, welche durch das Attribut 'pathString' und 'port' definiert wird.</li>
  954. <li><b>file</b> - benutzt die Datei, welche durch das Attribut 'pathString' definiert wird (im FHEM Dateisystem)</li>
  955. </ul>
  956. </li>
  957. </ul>
  958. <br>
  959. <b>Set</b>
  960. <ul>
  961. <li><code>activeTariff &lt; 0 - 9 &gt;</code>
  962. <br>
  963. Erlaubt die gezielte, separate Erfassung der statistischen Verbrauchswerte (doStatistics = 1) f&uuml;r verschiedene Tarife (Doppelstromz&auml;hler), wenn der Stromz&auml;hler dies selbst nicht unterscheiden kann (z.B. LS110) oder wenn gepr&uuml;ft werden soll, ob ein zeitabh&auml;ngiger Tarif preiswerter w&auml;re. Dieser Wert muss entsprechend des vorhandenen oder geplanten Tarifes zum jeweiligen Zeitpunkt z.B. durch den FHEM-Befehl "at" gesetzt werden.<br>
  964. 0 = tariflos
  965. </li><br>
  966. <li><code>INTERVAL &lt;Abfrageinterval&gt;</code>
  967. <br>
  968. Abfrageinterval in Sekunden
  969. </li><br>
  970. <li><code>resetStatistics &lt;statWerte&gt;</code>
  971. <br>
  972. L&ouml;scht die ausgew&auml;hlten statisischen Werte: <i>all, statElectricityConsumed..., statElectricityConsumedTariff..., statElectricityPower...</i>
  973. </li><br>
  974. <li><code>restartJsonAnalysis</code>
  975. <br>
  976. Neustart der Analyse der json-Datei zum Auffinden bekannter Ger&auml;tewerte (kompatibel zum OBIS Standard).
  977. Diese Analysie wird normaler Weise nur einmalig durchgef&uuml;hrt, nachdem Ger&auml;tewerte gefunden wurden.
  978. </li><br>
  979. <li><code>statusRequest</code>
  980. <br>
  981. Aktualisieren der Ger&auml;tewerte</li>
  982. </ul>
  983. <br>
  984. <b>Get</b>
  985. <ul>
  986. <li><code>jsonFile</code>
  987. <br>
  988. Liest die JSON-Datei ein und zeigt sie an.
  989. </li><br>
  990. <li><code>jsonAnalysis</code>
  991. <br>
  992. Extrahiert die JSON-Daten und zeigt das Resultat der JSON-Analyse.</li>
  993. </ul>
  994. <br>
  995. <a name="JSONMETERattr"></a>
  996. <b>Attributes</b>
  997. <ul>
  998. <li><code>alwaysAnalyse &lt; 0 | 1 &gt;</code>
  999. <br>
  1000. F&uuml;hrt bei jeder Abfrage der Ger&auml;tewerte eine Analyse der JSON-Datenstruktur durch.
  1001. <br>
  1002. Dies ist sinnvoll, wenn sich die JSON-Struktur &auml;ndert. Normalerweise wird die analysierte Struktur
  1003. zwischengespeichert, um die CPU-Last gering zu halten.
  1004. </li><br>
  1005. <li><code>doStatistics &lt; 0 | 1 &gt;</code>
  1006. <br>
  1007. Bildet t&auml;gliche, monatliche und j&auml;hrliche Statistiken bestimmter Ger&auml;tewerte (Mittel/Min/Max oder kumulierte Werte).
  1008. F&uuml;r grafische Auswertungen k&ouml;nnen die Werte der Form 'stat<i>ReadingName</i><b>Last</b>' genutzt werden.
  1009. </li><br>
  1010. <li><code>pathString &lt;Zeichenkette&gt;</code>
  1011. <ul>
  1012. <li>Ger&auml;tetyp 'file': definiert den lokalen Dateinamen und -pfad
  1013. </li>
  1014. <li>Ger&auml;tetyp 'url': Definiert den URL-Pfad
  1015. </li>
  1016. <li>Andere: Kann benutzt werden um Login-Information zum URL Pfad von vordefinerten Ger&auml;ten hinzuzuf&uuml;gen
  1017. </li>
  1018. </ul>
  1019. </li><br>
  1020. <li><code>port &lt;Nummer&gt;</code>
  1021. <br>
  1022. Beim Ger&auml;tetyp 'url' kann hier der URL-Port festgelegt werden. (standardm&auml;ssig 80)
  1023. </li><br>
  1024. <li><code>timeOut &lt;Sekunden&gt;</code>
  1025. <br>
  1026. Gibt an, nach wieviel Sekunden das Einlesen der Rohdaten abgebrochen werden soll. (standardm&auml;ssig 10)
  1027. <br>
  1028. Die Laufzeit des Einlesevorganges wird bei "get <device> jsonFile" angezeigt.
  1029. </li><br>
  1030. <li><a href="#readingFnAttributes">readingFnAttributes</a></li>
  1031. </ul>
  1032. </ul>
  1033. </div>
  1034. =end html_DE
  1035. =cut