95_PachLog.pm 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341
  1. #######################################################################
  2. #
  3. # 95_PachLog.pm
  4. #
  5. # Logging to www.pachube.com
  6. # Autor: a[PUNKT]r[BEI]oo2p[PUNKT]net
  7. # Stand: 09.09.2009
  8. # Version: 0.9
  9. #######################################################################
  10. # Vorausetzung: Account bei www.pachube.com mit API-Key
  11. #######################################################################
  12. #
  13. # FHEM: Neues Pachube-Device erstelle: define <NAME> PachLog API-Key
  14. # "define PACH001 PachLog 1234kliceee77hgtzuippkk99"
  15. #
  16. # PACHUBE: FEED erstellen -> FEED-NR: DATASTREAM-ID:TAGS
  17. # Beispiel: HMS_TF (Temperatur und Feuchte Sensor)
  18. # FEED-NR: 1234
  19. # ID 0 => Temperatur (temperature)
  20. # ID 1 => rel. Luftfeuchte (humidity)
  21. #
  22. # FHEM: PachLog-Devices: PACH01
  23. # HMS_DEVICE: HMS_TF01
  24. # FEED-NR: 1234
  25. # ID 0 => Temperatur (temperature)
  26. # ID 1 => rel. Luftfeuchte (humidity)
  27. # "set PACH01 ADD HMS_TF01 1234:0:temperature:1:humidity"
  28. #
  29. # Hinweise:
  30. # Ein FEED kann nur komplett upgedated werden:
  31. # FEED 3456 -> ID 0 -> DEVICE A
  32. # FEED 3456 -> ID 1 -> DEVICE B
  33. # => geht nicht
  34. #
  35. # Es werden nur READINGS mit einfach Werten und Zahlen unterst?tzt.
  36. # Beispiele: NICHT unterst?tze READINGS
  37. # cum_month => CUM_MONTH: 37.173 CUM: 108.090 COST: 0.00
  38. # cum_day => 2009-09-09 00:03:19 T: 1511725.6 H: 4409616 W: 609.4 R: 150.4
  39. # israining no => (yes/no)
  40. #######################################################################
  41. # $Id: 95_PachLog.pm 3738 2013-08-18 14:13:59Z rudolfkoenig $
  42. package main;
  43. use strict;
  44. use warnings;
  45. use POSIX;
  46. use Data::Dumper;
  47. use LWP;
  48. use LWP::UserAgent;
  49. use HTTP::Request::Common;
  50. #######################################################################
  51. sub
  52. PachLog_Initialize($)
  53. {
  54. my ($hash) = @_;
  55. $hash->{DefFn} = "PachLog_Define";
  56. $hash->{SetFn} = "PachLog_Set";
  57. $hash->{NotifyFn} = "PachLog_Notify";
  58. $hash->{AttrList} = "do_not_notify:0,1";
  59. }
  60. #######################################################################
  61. sub PachLog_Define($@)
  62. {
  63. # define <NAME> PachLog Pachube-X-API-Key
  64. my ($hash, @a) = @_;
  65. # X-API-Key steht im DEF %defs{<NAME>}{DEF}
  66. # Alternativ nach $defs{<NAME>}{XAPIKEY}
  67. my($package, $filename, $line, $subroutine) = caller(3);
  68. return "Unknown argument count " . int(@a) . " , usage set <name> dataset value or set <name> delete dataset" if(int(@a) != 1);
  69. return undef;
  70. }
  71. #######################################################################
  72. sub PachLog_Set($@)
  73. {
  74. # set <NAME> ADD/DEL <DEVICENAME> FEED:STREAM:VALUE:STREAM:VALUE&FEED-2:STEAM,VALUE
  75. my ($hash, @a) = @_ ;
  76. # FHEMWEB Frage....Auswahliste
  77. return "Unknown argument $a[1], choose one of ". join(" ",sort keys %{$hash->{READINGS}}) if($a[1] eq "?");
  78. # Pruefen Uebergabeparameter
  79. # @a => a[0]:<NAME>; a[1]=ADD oder DEL; a[2]= DeviceName;
  80. # a[3]=FEED:STREAM:VALUE:STREAM:VALUE&FEED-2:STREAM,VALUE
  81. # READINGS setzten oder l?schen
  82. if($a[1] eq "DEL")
  83. {
  84. Log3 $hash, 2,
  85. "PACHLOG -> DELETE: A0= ". $a[0] . " A1= " . $a[1] . " A2=" . $a[2];
  86. if(defined($hash->{READINGS}{$a[2]}))
  87. {
  88. delete($hash->{READINGS}{$a[2]})
  89. }
  90. }
  91. if($a[1] eq "ADD")
  92. {
  93. if(!defined($defs{$a[2]})) {return "PACHLOG[". $a[2] . "] => Unkown Device";}
  94. # Mindestens 3 Parameter
  95. my @b = split(/:/, $a[3]);
  96. return "PACHLOG[". $a[2] . "] => Argumenete: " . $a[3] . " nicht eindeutig => mind. 3 => FEED-NR:DATASTREAM:READING" if(int(@b) < 3);
  97. my $feednr = shift(@b);
  98. #FEED-Nr darf nur Zahlen enthalten
  99. if($feednr !~ /^\d+$/) {return "PACHLOG[". $a[2] . "] => FEED-Nr >" . $feednr . "< ist ungueltig";}
  100. # ??? Pruefen ob READING vorhanden ???
  101. my ($i,$j);
  102. for ($i=0;$i<@b;$i++)
  103. {
  104. #Stream nur Zahlen
  105. if($b[$i] !~ /^\d+$/) {return "PACHLOG => FEED-Nr[" . $feednr ."] Stream-ID >" . $b[$i] . "< ungueltig";}
  106. # Reading existiert
  107. $j = $i + 1;
  108. if(!defined($defs{$a[2]}{READINGS}{$b[$j]})) {return "PACHLOG[". $a[2] . "] => Unkown READING >" . $b[$j] . "<";}
  109. # READING-Value validieren
  110. my $r = $defs{$a[2]}{READINGS}{$b[$j]}{VAL};
  111. my $rn = &ReadingToNumber($r);
  112. if(!defined($rn)) {return "PACHLOG[". $a[$i] . "] => READING not supported >" . $b[$j] . "<";}
  113. $i = $j;
  114. }
  115. $hash->{READINGS}{$a[2]}{TIME} = TimeNow();
  116. $hash->{READINGS}{$a[2]}{VAL} = $a[3];
  117. }
  118. $hash->{CHANGED}[0] = $a[1];
  119. $hash->{STATE} = $a[1];
  120. return undef;
  121. return "Unknown argument count " . int(@a) . " , usage set <name> ADD/DEL <DEVICE-NAME> FEED:STREAM:VALUE:STREAM:VALUE&FEED-2:STREAM,VALUE" if(int(@a) != 4);
  122. }
  123. #######################################################################
  124. sub PachLog_Notify ($$)
  125. {
  126. my ($me, $trigger) = @_;
  127. my $d = $me->{NAME};
  128. return "" if($attr{$d} && $attr{$d}{disable});
  129. my $t = $trigger->{NAME};
  130. # Eintrag fuer Trigger-Device vorhanden
  131. if(!defined($defs{$d}{READINGS}{$t}))
  132. {
  133. Log3 $d, 5, ("PACHLOG[INFO] => " . $t . " => Nicht definiert");
  134. return undef;}
  135. # Umwandeln 1234:0:temperature:1:humidity => %feed
  136. # Struktur:
  137. # %feed{FEED-NR}{READING}{VAL}
  138. # %feed{FEED-NR}{READING}{DATASTREAM}
  139. my ($dat,@a,$feednr,$i,$j);
  140. my %feed = ();
  141. $dat = $defs{$d}{READINGS}{$t}{VAL};
  142. @a = split(/:/, $dat);
  143. $feednr = shift(@a);
  144. for ($i=0;$i<@a;$i++)
  145. {
  146. $j = $i + 1;
  147. $feed{$feednr}{$a[$j]}{STREAM} = $a[$i];
  148. $i = $j;
  149. }
  150. # Werte aus Trigger-Device
  151. foreach my $r (keys %{$feed{$feednr}})
  152. {
  153. $i = $defs{$t}{READINGS}{$r}{VAL};
  154. # Werte Normalisieren
  155. # Einheit -> 21,1 (celsius) -> 21,1
  156. # FS20: VAL = on => 1 && VAL = off => 0
  157. # @a = split(' ', $i);
  158. $feed{$feednr}{$r}{VAL} = ReadingToNumber($i,$d) ;
  159. }
  160. # CVS-Data
  161. my @cvs = ();
  162. foreach my $r (keys %{$feed{$feednr}}) {
  163. $cvs[$feed{$feednr}{$r}{STREAM}] = $feed{$feednr}{$r}{VAL};
  164. }
  165. my $cvs_data = join(',',@cvs);
  166. Log3 $d, 5, "PACHLOG[CVSDATA] => $cvs_data";
  167. # Aufbereiten %feed als EEML-Data
  168. my $eeml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
  169. $eeml .= "<eeml xmlns=\"http://www.eeml.org/xsd/005\">\n";
  170. $eeml .= "<environment>\n";
  171. foreach my $r (keys %{$feed{$feednr}})
  172. {
  173. $eeml .= "<data id=\"" . $feed{$feednr}{$r}{STREAM} . "\">\n";
  174. $eeml .= "<value>" . $feed{$feednr}{$r}{VAL} . "</value>\n";
  175. # Unit fuer EEML: <unit symbol="C" type="derivedSI">Celsius</unit>
  176. my ($u_name,$u_symbol,$u_type,$u_tag) = split(',',PachLog_ReadingToUnit($r,$d));
  177. if(defined($u_name)) {
  178. $eeml .= "<tag>". $u_tag . "</tag>\n";
  179. $eeml .= "<unit symbol=\"" . $u_symbol. "\" type=\"" . $u_type. "\">" . $u_name . "<\/unit>\n";
  180. }
  181. $eeml .= "</data>\n";
  182. }
  183. $eeml .= "</environment>\n";
  184. $eeml .= "</eeml>\n";
  185. Log3 $d, 5, "PACHLOG -> " . $t . " EEML => " . $eeml;
  186. # Pachube-Update per EEML -> XML
  187. my ($res,$ret,$ua,$apiKey,$url);
  188. $apiKey = $defs{$d}{DEF};
  189. $url = "http://www.pachube.com/api/feeds/" . $feednr . ".xml";
  190. $ua = new LWP::UserAgent;
  191. $ua->default_header('X-PachubeApiKey' => $apiKey);
  192. #Timeout 3 sec ... default 180sec
  193. $ua->timeout(3);
  194. $res = $ua->request(PUT $url,'Content' => $eeml);
  195. # Ueberpruefen wir, ob alles okay war:
  196. if ($res->is_success())
  197. {
  198. Log3 $d, 5,("PACHLOG => Update[" . $t ."]: " . $cvs_data . " >> SUCCESS\n");
  199. # Time setzten
  200. $defs{$d}{READINGS}{$t}{TIME} = TimeNow();
  201. } else {
  202. Log3 $d, 0,("PACHLOG => Update[" . $t ."] ERROR: " .
  203. ($res->as_string) . "\n");}
  204. }
  205. ################################################################################
  206. sub PachLog_ReadingToUnit($$)
  207. {
  208. # Unit fuer EEML: <unit symbol="C" type="derivedSI">Celsius</unit>
  209. # Input: READING z.B. temperature
  210. # Output: Name,symbol,Type,Tag z.B. Celsius,C,derivedSI
  211. # weiters => www.eeml.org
  212. # No Match = undef
  213. my ($in,$d) = @_;
  214. my %unit = ();
  215. %unit = (
  216. 'temperature' => "Celsius,C,derivedSI,Temperature",
  217. 'dewpoint' => "Celsius,C,derivedSI,Temperature",
  218. 'current' => "Power,kW,derivedSI,EnergyConsumption",
  219. 'humidity' => "Humidity,rel%,contextDependentUnits,Humidity",
  220. 'rain' => "Rain,l/m2,contextDependentUnits,Rain",
  221. 'rain_now' => "Rain,l/m2,contextDependentUnits,Rain",
  222. 'wind' => "Wind,m/s,contextDependentUnits,Wind",
  223. );
  224. if(defined($unit{$in})) {
  225. Log3 $d,5,("PACHLOG[ReadingToUnit] " . $in . " >> " . $unit{$in} );
  226. return $unit{$in};
  227. } else {
  228. return undef;
  229. }
  230. }
  231. ################################################################################
  232. sub ReadingToNumber($$)
  233. {
  234. # Input: reading z.B. 21.1 (Celsius) oder dim10%, on-for-oldtimer etc.
  235. # Output: 21.1 oder 10
  236. # ERROR = undef
  237. # Alles au?er Nummern loeschen $t =~ s/[^0123456789.-]//g;
  238. my ($in,$d) = @_;
  239. Log3 $d, 5, "PACHLOG[ReadingToNumber] => in => $in";
  240. # Bekannte READINGS FS20 Devices oder FHT
  241. if($in =~ /^on|Switch.*on/i) {$in = 1;}
  242. if($in =~ /^off|Switch.*off|lime-protection/i) {$in = 0;}
  243. # Keine Zahl vorhanden
  244. if($in !~ /\d{1}/) {
  245. Log3 $d, 5, "PACHLOG[ReadingToNumber] No Number: $in";
  246. return undef;}
  247. # Mehrfachwerte in READING z.B. CUM_DAY: 5.040 CUM: 334.420 COST: 0.00
  248. my @b = split(' ', $in);
  249. if(int(@b) gt 2) {
  250. Log3 $d, 5, "PACHLOG[ReadingToNumber] Not Supportet Reading: $in";
  251. return undef;}
  252. # Nur noch Zahlen z.B. dim10% = 10 oder 21.1 (Celsius) = 21.1
  253. if (int(@b) eq 2){
  254. Log3 $d, 5, "PACHLOG[ReadingToNumber] Split:WhiteSpace-0- $b[0]";
  255. $in = $b[0];
  256. }
  257. $in =~ s/[^0123456789.-]//g;
  258. Log3 $d, 5, "PACHLOG[ReadingToNumber] => out => $in";
  259. return $in
  260. }
  261. 1;
  262. =pod
  263. =begin html
  264. <a name="PachLog"></a>
  265. <h3>PachLog</h3>
  266. <ul>
  267. The PachLog-Module Logs SensorData like (temperature and humidity) to <a href=http://www.pachube.com>www.pachube.com</a>.
  268. <br><br>
  269. Note: this module needs the HTTP::Request and LWP::UserAgent perl modules.
  270. <br><br>
  271. <a name="PachLogdefine"></a>
  272. <b>Define</b>
  273. <ul>
  274. <br><code>define &lt;name&gt; PachLog &lt;Pachube-API-Key&gt;</code> <br>
  275. <br>
  276. &lt;Pachube-API-Key&gt;:<br>
  277. The Pachube-API-Key however is what you need in your code to authenticate your application's access the Pachube service.<br>
  278. Don't share this with anyone: it's just like any other password.<br>
  279. <a href=http://www.pachube.com>www.pachube.com</a><br>
  280. </ul>
  281. <br>
  282. <a name="PachLogset"></a>
  283. <b>Set</b>
  284. <ul>
  285. <br>
  286. Add a new Device for Logging to www.pachube.com<br><br>
  287. <code>set &lt;NAME&gt; ADD &lt;FHEM-DEVICENAME&gt; FEED-NR:ID:READING:ID:READING</code><br><br>
  288. Example: KS300-Weather-Data<br><br>
  289. READINGS: temperature humidity wind rain<br><br>
  290. 1. Generate Input-Feed on www.pachube.com => Yout get your FEED-NR: 1234<br>
  291. 2. Add Datastreams to the Feed:<br>
  292. <ul>
  293. <table>
  294. <tr><td>ID</td><td>0</td><td>temperature</td></tr>
  295. <tr><td>ID</td><td>1</td><td>humidity</td></tr>
  296. <tr><td>ID</td><td>2</td><td>wind</td></tr>
  297. <tr><td>ID</td><td>3</td><td>rain</td></tr></table><br>
  298. </ul>
  299. 3. Add the KS300 to your PachLog-Device<br><br>
  300. <code>set &lt;NAME&gt; ADD &lt;My-KS300&gt; 1234:0temperature:1:humidity:2:wind:3:rain</code><br><br>
  301. Delete a Device form Logging to www.pachube.com<br><br>
  302. <code>set &lt;NAME&gt; DEL &lt;FHEM-DEVICENAME&gt;</code><br><br>
  303. </ul>
  304. <br>
  305. <a name="PachLogget"></a>
  306. <b>Get</b> <ul>N/A</ul><br>
  307. <a name="PachLogattr"></a>
  308. <b>Attributes</b>
  309. <ul>
  310. <li><a href="#do_not_notify">do_not_notify</a></li><br>
  311. <li>disable<br>
  312. Disables PachLog.
  313. Nor more Logging to www.pachube.com
  314. </li>
  315. </ul><br>
  316. </ul>
  317. =end html
  318. =cut