34_NUT.pm 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444
  1. #
  2. # $Id: 34_NUT.pm 9023 2015-08-05 09:00:12Z narsskrarc $
  3. #
  4. # Abfrage einer UPS über die Network UPS Tools (www.networkupstools.org)
  5. #
  6. # 05.08.2015
  7. #
  8. # DEFINE bla NUT <upsname> [<host>[:<port>]]
  9. # Readings:
  10. # Da sind alle realisierbar, die die USV liefert.
  11. # Auf jeden Fall dabei ist
  12. # ups.status
  13. # da das auch den Status des Geräts ergibt.
  14. # Weitere sind mit dem Attribut asReadings definierbar (s.a. TODO).
  15. #
  16. # Attrs:
  17. # disable:0,1 Polling abklemmen
  18. # pollState Häufigkeit, mit der der Status der USV abgefragt wird (Default 5 sec)
  19. # pollVal Häufigkeit, mit der die anderen Readings abgefragt werden (Default 60 sec, Vielfaches von pollState)
  20. # asReadings Mit Kommata getrennte Liste der Werte der USV, die als Readings zur Verfügung stehen sollen
  21. # $readingFnAttributes
  22. #
  23. #
  24. #
  25. # TODO
  26. # A - Alive setzen
  27. # A - Sollte man als Reading einfach die Bezeichnung der USV nehmen (also z.B. input.voltage) oder
  28. # Aliase nehmen (z.B. voltage) bzw. modifizierte Bezeichnungen (z.B. inputVoltage)?
  29. # B - attr pollInterval: Wertebereich prüfen (min. 5, max. ?)
  30. # B - Zugriff auf Attribute per AttrVal
  31. # C - per GET könnte man alle Werte der USV verfügbar machen
  32. # D - SET implementieren, um diverse Dinge mit der USV anstellen zu können (Test, Ausschalten, ...)
  33. # D - Für die Web-Oberfläche wäre es vermutlich schick, wenn man die veränderbaren Variablen auch verändern könnte,
  34. # inklusive den ENUMs und RANGEs, die NUT für die Werte anbietet
  35. #
  36. #
  37. # FIXME
  38. # - Fehlermeldung in fhem.log: "Notify Loop for..." Wieso?
  39. #
  40. package main;
  41. use strict;
  42. use warnings;
  43. use POSIX;
  44. sub NUT_Initialize($);
  45. sub NUT_Define($$);
  46. sub NUT_Undef($$);
  47. sub NUT_Ready($);
  48. sub NUT_DevInit($);
  49. sub NUT_Read($);
  50. sub NUT_ListVar($);
  51. sub NUT_Auswertung($);
  52. sub NUT_makeReadings($);
  53. sub NUT_Initialize($) {
  54. my ($hash) = @_;
  55. require "$attr{global}{modpath}/FHEM/DevIo.pm";
  56. $hash->{DefFn} = "NUT_Define";
  57. $hash->{UndefFn} = "NUT_Undef";
  58. $hash->{ReadyFn} = "NUT_Ready";
  59. $hash->{ReadFn} = "NUT_Read";
  60. $hash->{AttrList} = "disable:0,1 pollState pollVal asReadings ".
  61. $readingFnAttributes;
  62. }
  63. sub NUT_Define($$) {
  64. my ( $hash, $def ) = @_;
  65. my @a = split( "[ \t][ \t]*", $def );
  66. # Syntaxprüfung
  67. if (@a < 3 or @a > 4) {
  68. my $msg = "wrong syntax: define <name> NUT <upsname> [<host>[:<port>]]";
  69. Log3 $hash, 2, $msg;
  70. return $msg;
  71. }
  72. DevIo_CloseDev($hash);
  73. # Parameter auswerten
  74. my $name = $a[0];
  75. $hash->{UpsName} = $a[2];
  76. my $dev = $a[3];
  77. if (defined $dev) {
  78. $dev .= ":3493" if ($dev !~ m/:/);
  79. } else {
  80. $dev = "localhost:3493";
  81. }
  82. $hash->{DeviceName} = $dev;
  83. $hash->{buffer} = '';
  84. # Defaults setzen
  85. $attr{$name}{pollState} = 10;
  86. $attr{$name}{pollVal} = 60;
  87. $attr{$name}{disable} = 0;
  88. $attr{$name}{asReadings} = 'battery.charge,battery.runtime,input.voltage,ups.load,ups.power,ups.realpower';
  89. $hash->{pollValState} = 0;
  90. $hash->{lastStatus} = "";
  91. return DevIo_OpenDev($hash, 0, "NUT_DevInit");
  92. }
  93. sub NUT_Undef($$) {
  94. my ($hash, $arg) = @_;
  95. my $name = $hash->{NAME};
  96. RemoveInternalTimer("pollTimer:".$name);
  97. DevIo_CloseDev($hash);
  98. return undef;
  99. }
  100. sub NUT_Ready($) {
  101. my ($hash) = @_;
  102. return DevIo_OpenDev($hash, 1, "NUT_DevInit");
  103. }
  104. sub NUT_DevInit($) {
  105. my ($hash) = @_;
  106. $hash->{pollValState} = 0;
  107. delete $hash->{WaitForAnswer};
  108. NUT_ListVar($hash);
  109. return undef;
  110. }
  111. sub NUT_Read($) {
  112. my ($hash) = @_;
  113. my $name = $hash->{NAME};
  114. # read from serial device
  115. my $buf = DevIo_SimpleRead($hash);
  116. return '' if (!defined($buf));
  117. $hash->{buffer} .= $buf;
  118. # Log3 $name, 5, "Current buffer content: " . $hash->{buffer};
  119. NUT_Auswertung($hash);
  120. return '';
  121. }
  122. sub NUT_ListVar($) {
  123. my ($hash) = @_;
  124. my $name = $hash->{NAME};
  125. if (not defined $attr{$name}{disable} or $attr{$name}{disable} == 0) {
  126. if ($hash->{STATE} eq 'disconnected') {
  127. # Verbindung scheint nicht zu bestehen
  128. # Alles abbrechen, ich verlasse mich auf DevIo_OpenDev, dass es alles wieder anwirft, sobald die Verbindung wieder steht
  129. $hash->{pollValState} = 0;
  130. RemoveInternalTimer("pollTimer:".$name);
  131. DevIo_OpenDev($hash, 1, "NUT_DevInit");
  132. return;
  133. }
  134. if (defined $hash->{WaitForAnswer}) {
  135. # Keine Antwort auf die letzte Frage -> NUT nicht mehr erreichbar!
  136. Log3 $name, 3, "NUT antwortet nicht";
  137. DevIo_Disconnected($hash);
  138. delete $hash->{DevIoJustClosed};
  139. $hash->{pollValState} = 0;
  140. delete $hash->{WaitForAnswer};
  141. RemoveInternalTimer("pollTimer:".$name);
  142. DevIo_OpenDev($hash, 1, "NUT_DevInit");
  143. return;
  144. }
  145. my $ups = $hash->{UpsName};
  146. if ($hash->{pollValState} == 0) {
  147. # Kompletten Datensatz anfordern
  148. Log3 $name, 5, "Sending 'LIST VAR $ups'...";
  149. DevIo_SimpleWrite($hash, "LIST VAR $ups\n", 0);
  150. } else {
  151. # Nur Status anfordern
  152. Log3 $name, 5, "Sending 'GET VAR $ups ups.status'...";
  153. DevIo_SimpleWrite($hash, "GET VAR $ups ups.status\n", 0);
  154. }
  155. $hash->{WaitForAnswer} = 1;
  156. } else {
  157. Log3 $name, 5, "NUT polling disabled.";
  158. delete $hash->{WaitForAnswer};
  159. }
  160. RemoveInternalTimer("pollTimer:".$name);
  161. InternalTimer(gettimeofday() + $attr{$name}{pollState}, "NUT_PollTimer", "pollTimer:".$name, 0);
  162. $hash->{pollValState} += $attr{$name}{pollState};
  163. $hash->{pollValState} = 0 if $hash->{pollValState} >= $attr{$name}{pollVal};
  164. }
  165. sub NUT_PollTimer($) {
  166. my $in = shift;
  167. my (undef,$name) = split(':',$in);
  168. my $hash = $defs{$name};
  169. NUT_ListVar($hash);
  170. }
  171. sub NUT_Auswertung($) {
  172. my ($hash) = @_;
  173. my $name = $hash->{NAME};
  174. my $buf = $hash->{buffer};
  175. my @lines = split /\n/, $buf;
  176. if (substr($buf, -1) ne "\n") {
  177. # Letzte Zeile ist noch unvollständig
  178. $hash->{buffer} = $lines[$#lines];
  179. $lines[$#lines] = '';
  180. if (length($hash->{buffer}) > 1024) {
  181. # Notbremse, wenn eine Zeile zu lange wird
  182. Log3 $name, 1, "NUT: Zeile > 1024 Zeichen!";
  183. $hash->{buffer} = '';
  184. }
  185. } else {
  186. $hash->{buffer} = '';
  187. }
  188. foreach my $line (@lines) {
  189. if (length($line) > 0) {
  190. Log3 $name, 5, "NUT RX: $line";
  191. my $arg = '';
  192. if ($line =~ m/(.*) "(.*)"/) {
  193. # Argument in "
  194. $line = $1;
  195. $arg = $2;
  196. }
  197. my @words = split / /, $line;
  198. if ($words[0] eq 'VAR') {
  199. # Variable erkannt
  200. if ($words[1] eq $hash->{UpsName}) {
  201. my $var = $words[2];
  202. $hash->{helper}{$var} = $arg;
  203. # Sonderfälle
  204. if ($var eq 'ups.status') {
  205. # Status wird sofort übernommen
  206. readingsSingleUpdate($hash, 'state', $hash->{helper}{'ups.status'}, 1);
  207. $hash->{lastStatus} = $hash->{helper}{'ups.status'};
  208. }
  209. } else {
  210. Log3 $name, 1, "NUT $hash->{UpsName}: VAR from wrong UPS $words[1]";
  211. }
  212. } elsif ($words[0] eq 'BEGIN') {
  213. # Anfang einer Liste
  214. if ($words[2] eq 'VAR') {
  215. # Hier werden die alten Daten gelöscht.
  216. # Das ist notwendig, da sonst nicht entschieden werden kann, welche Daten die USV liefert
  217. # und welche berechnet werden müssen.
  218. # FIXME Problem: Wenn in der Zeit, bis END LIST VAR durch ist, per GET Werte abgefragt werden sollen,
  219. # dann fehlen die möglicherweise. GET ist aber noch nicht implementiert.
  220. # Mögliche Lösung: Neue Werte in z.B. {helper}{NEW} einlesen und bei END LIST VAR umschieben.
  221. delete $hash->{helper};
  222. }
  223. } elsif ($words[0] eq 'END') {
  224. # Ende einer Liste
  225. if ($words[2] eq 'VAR') {
  226. # Ende einer Variablen-Liste
  227. # Umwidmen der Variablen in Readings
  228. NUT_makeReadings($hash);
  229. }
  230. } elsif ($words[0] eq 'ERR') {
  231. # Fehlermeldungen
  232. my $err = $words[1];
  233. Log3 $name, 2, "NUT Error: $err";
  234. readingsSingleUpdate($hash, 'lastError', $err, 1);
  235. readingsSingleUpdate($hash, 'state', $err, 1);
  236. if ($err=~ m/(ACCESS-DENIED|UNKNOWN-UPS)/) {
  237. # Das sind Fehlermeldungen, die keine Hoffnung machen, dass es noch funktionieren könnte
  238. $attr{$name}{disable} = 1;
  239. }
  240. } else {
  241. # TODO Es gibt noch viele Antworten, die interessant sein könnten...
  242. # http://www.networkupstools.org/docs/developer-guide.chunked/ar01s09.html
  243. Log3 $name, 5, "NUT: not implemented: $line";
  244. }
  245. delete $hash->{WaitForAnswer};
  246. } # if len > 0
  247. } # foreach
  248. }
  249. sub NUT_makeReadings($) {
  250. my ($hash) = @_;
  251. my $name = $hash->{NAME};
  252. # Die USV-Variablen en bloc in Readings überführen
  253. # FIXME Woher kommt die Fehlermeldung 'Notify loop for...' im Log?
  254. readingsBeginUpdate($hash);
  255. foreach (split (',', $attr{$name}{asReadings})) {
  256. s/^\s+//;
  257. s/\s+$//;
  258. readingsBulkUpdate($hash, $_, $hash->{helper}{$_}) if defined $hash->{helper}{$_};
  259. }
  260. readingsEndUpdate($hash, 1);
  261. }
  262. 1;
  263. =pod
  264. =begin html
  265. <a name="NUT"></a>
  266. <h3>NUT</h3>
  267. <ul>
  268. The Network UPS Tools (<a href="http://www.networkupstools.org">www.networkupstools.org</a>) provide support for Uninterruptable Power Supplies and the like.
  269. This module gives access to a running nut server. You can read data (status, runtime, input voltage, sometimes even temperature and so on). In the future it will
  270. also be possible to control the UPS (start test, switch off).<br>
  271. Which values you can use as readings is set with <a href="#NUT_asReadings">asReadings</a>. Which values are available with this UPS, you can check with
  272. <code>list theUPS</code>. Only ups.status is always read and used as the status of the device.<br>
  273. <br><br>
  274. <a name=NUTdefine"></a>
  275. <b>Define</b>
  276. <ul>
  277. <code>define &lt;name&gt; NUT &lt;ups&gt; [&lt;host&gt;[:&lt;port&gt;]]</code> <br>
  278. <br>
  279. &lt;ups&gt; is the name of a ups defined in the nut server.
  280. <br>
  281. [&lt;host&gt;[:&lt;port&gt;]] is the host of the nut server. If omitted, <code>localhost:3493</code> is used.
  282. <br><br>
  283. Example: <br>
  284. <code>define theUPS NUT myups otherserver</code>
  285. <br>
  286. </ul>
  287. <br>
  288. <a name="NUTset"></a>
  289. <b>Set</b> <ul>N/A</ul><br>
  290. <a name="NUTget"></a>
  291. <b>Get</b> <ul>N/A</ul><br>
  292. <a name="NUTattr"></a>
  293. <b>Attributes</b>
  294. <ul>
  295. <li><a href="#disable">disable</a></li><br>
  296. <li><a href="#readingFnAttributes">readingFnAttributes</a></li><br>
  297. <li><a name="">pollState</a><br>
  298. Polling interval in seconds for the state of the ups. Default: 10</li><br>
  299. <li><a name="">pollVal</a><br>
  300. Polling interval in seconds of the other Readings. This should be a multiple of pollState. Default: 60</li><br>
  301. <li><a name="#NUT_asReadings">asReadings</a><br>
  302. Values of the UPS which are used as Readings (ups.status is read anyway)<br>
  303. Example:<br>
  304. <code>attr theUPS asReadings battery.charge,battery.runtime,input.voltage,ups.load,ups.power,ups.realpower</code> </li><br>
  305. </ul>
  306. </ul>
  307. =end html
  308. =begin html_DE
  309. <a name="NUT"></a>
  310. <h3>NUT</h3>
  311. <ul>
  312. Die Network UPS Tools (<a href="http://www.networkupstools.org">www.networkupstools.org</a>) bieten Unterstützung für Unterbrechungsfreie Stromversorgungen (USV)
  313. und ähnliches. Dieses Modul ermöglicht den Zugriff auf einen NUT-Server, womit man Daten auslesen kann (z.B. den Status, Restlaufzeit, Eingangsspannung, manchmal
  314. auch Temperatur u.ä.) und zukünftig die USV auch steuern kann (Test aktivieren, USV herunterfahren u.ä.).<br>
  315. Welche Readings zur Verfügung stehen, bestimmt das Attribut <a href="#NUT_asReadings">asReadings</a>. Welche Werte eine USV zur Verfügung stellt, kann man mit
  316. <code>list dieUSV</code> unter <i>Helper:</i> ablesen. Nur ups.status wird immer ausgelesen und ergibt den Status des Geräts.<br>
  317. <br><br>
  318. <a name=NUTdefine"></a>
  319. <b>Define</b>
  320. <ul>
  321. <code>define &lt;name&gt; NUT &lt;ups&gt; [&lt;host&gt;[:&lt;port&gt;]]</code> <br>
  322. <br>
  323. &lt;ups&gt; ist der im NUT-Server definierte Name der USV.
  324. <br>
  325. [&lt;host&gt;[:&lt;port&gt;]] ist Host und Port des NUT-Servers. Default ist <code>localhost:3493</code>.
  326. <br><br>
  327. Beispiel: <br>
  328. <code>define dieUSV NUT myups einserver</code>
  329. <br>
  330. </ul>
  331. <br>
  332. <a name="NUTset"></a>
  333. <b>Set</b> <ul>N/A</ul><br>
  334. <a name="NUTget"></a>
  335. <b>Get</b> <ul>N/A</ul><br>
  336. <a name="NUTattr"></a>
  337. <b>Attributes</b>
  338. <ul>
  339. <li><a href="#disable">disable</a></li><br>
  340. <li><a href="#readingFnAttributes">readingFnAttributes</a></li><br>
  341. <li><a name="">pollState</a><br>
  342. Polling-Intervall in Sekunden für den Status der USV. Default: 10</li><br>
  343. <li><a name="">pollVal</a><br>
  344. Polling-Intervall für die anderen Werte. Dieser Wert wird auf ein Vielfaches von pollState gerundet. Default: 60</li><br>
  345. <li><a name="NUT_asReadings">asReadings</a><br>
  346. Mit Kommata getrennte Liste der USV-Werte, die als Readings verwendet werden sollen (ups.status wird auf jeden Fall gelesen).<br>
  347. Beispiel:<br>
  348. <code>attr dieUSV asReadings battery.charge,battery.runtime,input.voltage,ups.load,ups.power,ups.realpower</code> </li><br>
  349. </ul>
  350. </ul>
  351. =end html_DE
  352. =cut