70_JSONMETER.pm 46 KB

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