24_NetIO230B.pm 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387
  1. ################################################################
  2. # fhem-module for NetIO 230B Power Distribution Unit
  3. #
  4. # http://www.koukaam.se/showproduct.php?article_id=1502
  5. #
  6. # Usage:
  7. # There are 2 modes:
  8. #
  9. # (1) - define the IP, user and password directly in fhem's fhem.cfg (or UI).
  10. #
  11. # define NetIO1 <IP address:port number> <socket number> [<user> <password>];
  12. # e.g. define NetIO1 192.168.178.2:80 1 admin admin;
  13. #
  14. # if you omit the user credentials, the module will look for a configuration file,
  15. # if no configuration file is found, it tries with 'admin', 'admin'
  16. #
  17. #
  18. # (2) - define your credentials using a config file.
  19. #
  20. # define NetIO1 <IP address:port number> <socket number> [<path_to_configuration_file>];
  21. # define NetIO1 192.168.178.2:80 1 /var/log/fhem/netio.conf);
  22. #
  23. # if you omit the configuration parameter, the module will look for a configuration
  24. # file at: /var/log/fhem/netio.conf
  25. #
  26. # NetIO230B Configuration file format:
  27. #
  28. # %config= (
  29. # host => "192.168.xx.xx:80",
  30. # user => "anyusername_without_spaces",
  31. # password => "anypassword_without_spaces"
  32. # );
  33. #
  34. ################################################################
  35. # created 2012 by Andy Fuchs
  36. #---------
  37. # Changes:
  38. #---------
  39. # 2012-02-03 0.1 initial realease
  40. # 2012-02-25 0.2 removed dependencies for LWP::UserAgent and HTTP::Request;
  41. # 2012-09-15 0.3 fixed missing param-list;
  42. # added slight checking of passed device-address (now explicitely requires setting a port)
  43. ################################################################
  44. package main;
  45. use strict;
  46. use warnings;
  47. use Data::Dumper;
  48. use IO::Socket;
  49. use HttpUtils;
  50. use constant PARAM_NAME => 1;
  51. use constant PARAM_HOST => 2;
  52. use constant PARAM_SOCK => 3;
  53. use constant PARAM_USER => 4;
  54. use constant PARAM_PASS => 5;
  55. use constant PARAM_FILE => 4;
  56. use constant DEBUG => 1;
  57. sub
  58. NetIO230B_Initialize($)
  59. {
  60. my ($hash) = @_;
  61. $hash->{SetFn} = "NetIO230B_Set";
  62. $hash->{GetFn} = "NetIO230B_Get";
  63. $hash->{DefFn} = "NetIO230B_Define";
  64. $hash->{AttrList} = "";
  65. }
  66. ###################################
  67. sub
  68. NetIO230B_Set($@)
  69. {
  70. my ($hash, @a) = @_;
  71. return "no set value specified" if(int(@a) != 2);
  72. return "Unknown argument $a[1], choose one of on off " if($a[1] eq "?");
  73. my $state = $a[1]; #initialize state to the passed parameter
  74. my $result = "command was not executed - $state is not 'on' or 'off'";
  75. if ($state eq "on" || $state eq "off")
  76. {
  77. $hash->{STATE} = $state;
  78. $state = int($state eq "on");
  79. #prepare the sockets default parameters; 'u' means: don't touch
  80. my @values=("u","u","u","u");
  81. my @sockets = @{$hash->{SOCKETS}};
  82. foreach (@sockets) {
  83. $values[$_-1] = $state;
  84. $hash->{READINGS}{"socket$_"}{TIME} = TimeNow();
  85. $hash->{READINGS}{"socket$_"}{VAL} = $state;
  86. }
  87. $result = NetIO230B_Request($hash, "set", join("",@values));
  88. }
  89. Log3 $hash, 3, "NetIO230B set @a => $result";
  90. return undef;
  91. }
  92. ###################################
  93. sub
  94. NetIO230B_Get($@)
  95. {
  96. my ($hash, @a) = @_;
  97. my $result = NetIO230B_Request($hash, "get");
  98. Log3 $hash, 3, "NetIO230B get @a => $result";
  99. return $hash->{STATE};
  100. }
  101. ###################################
  102. sub
  103. NetIO230B_Request($@)
  104. {
  105. my ($hash, $cmd, $list) = @_;
  106. my $URL='';
  107. my $timeout=4;
  108. my $noshutdown=1;
  109. my $log='';
  110. my $parm='l';
  111. if($cmd eq "set") {
  112. $parm = $list;
  113. }
  114. my $response = GetFileFromURL("http://"."$hash->{HOST}/tgi/control.tgi?l=p:". $hash->{USER}.":".$hash->{PASS}."&p=".$parm, $timeout, undef, $noshutdown );
  115. if(!$response or length($response)==0)
  116. {
  117. Log3 $hash, 3, "NetIO230B_Request failed: ".$log;
  118. return("");
  119. }
  120. # strip html tags
  121. $response =~ s/<(?:[^>'"]*|(['"]).*?\1)*>//gs;
  122. # strip leading whitespace
  123. $response =~ s/^\s+//;
  124. #strip trailing whitespace
  125. $response =~ s/\s+$//;
  126. return $response if ($cmd eq "set");
  127. #+++todo
  128. #555 FORBIDDEN
  129. #split the result into an array
  130. my @values=split(/ */,$response);
  131. #save the values to the readings hash
  132. my $state = "???";
  133. my @sockets = @{$hash->{SOCKETS}};
  134. foreach (@sockets) {
  135. $hash->{READINGS}{"socket$_"}{TIME} = TimeNow();
  136. $hash->{READINGS}{"socket$_"}{VAL} = $values[$_-1];
  137. if ($state == "???") { #initialize state
  138. $state = $values[$_-1];
  139. } else {
  140. $state = "???" if ($values[$_-1] != $state); #if states are mixed show ???
  141. }
  142. }
  143. $hash->{STATE} = $state;
  144. # debug output
  145. #my %k = %{$hash->{READINGS}};
  146. #foreach my $r (sort keys %k) {
  147. # Log3 $hash, 1, "$r S: $k{$r}{VAL} T: $k{$r}{TIME}";
  148. #}
  149. return $response;
  150. }
  151. ### _Define routing is called when fhem starts -> 'reloading' of the module does not call the define routine!!
  152. ###
  153. sub
  154. NetIO230B_Define($$)
  155. {
  156. my ($hash, $def) = @_;
  157. my @a = split("[ \t][ \t]*", $def);
  158. my $paramCount = int(@a);
  159. Log3 $hash, 3, "Wrong syntax: use 'define <name> NetIO230B <ip-address>:<portnumber> [<socket_number> <username> <password>]' or 'define <name> NetIO230B <ip-address>:<portnumber> [<socket_number> <configfilename>]'" if(int(@a) < 4); #5 = mit user/pass #4 = mit config
  160. #provide some default settings
  161. $hash->{CONFIGFILEPATH} = "/var/log/fhem/netio.conf"; #default file path is /var/log/fhem/netio.conf
  162. $hash->{USER} = "admin";
  163. $hash->{PASS} = "admin";
  164. @{$hash->{SOCKETS}} = (1,2,3,4); #default to all sockets
  165. #mandatory parameter: 'HOST'
  166. $hash->{HOST} = $a[PARAM_HOST]; #can be overridden, if using a config file
  167. #mandatory parameter: 'SOCKET #'; negative numbers are ignored
  168. my $buf = $a[PARAM_SOCK];
  169. if (($buf =~ m/^\d+$/) && ($buf >=0)) { #make sure input value is a positive number
  170. if ($buf == 0) #input socket '0' is used as 'all'
  171. {
  172. @{$hash->{SOCKETS}} = (1,2,3,4); #use this number as 'array of socket-numbers' to operate on
  173. } elsif ($buf <= 4) #input socket is a single number <=4
  174. {
  175. @{$hash->{SOCKETS}} = ($buf); #use this number as 'array of socket-numbers' to operate on
  176. } else {
  177. @{$hash->{SOCKETS}} = split("",$buf); #convert the input values to an array of sockets to operate on
  178. }
  179. }
  180. #optional parameter: 'CONFIGFILE_NAME_or_PATH'
  181. if ($paramCount == 5) #seems a config file is passed
  182. {
  183. $hash->{CONFIGFILEPATH} = $a[PARAM_FILE] if defined($a[PARAM_FILE]);;
  184. }
  185. #optional parameters: 'USER and PASS'
  186. if ($paramCount != 6)
  187. {
  188. my %config = NetIO230B_GetConfiguration($hash);
  189. if (%config) {
  190. $hash->{HOST} = $config{host} if (defined($config{host}));
  191. $hash->{USER} = $config{user} if (defined($config{user}));
  192. $hash->{PASS} = $config{password} if (defined($config{password}));
  193. } else {
  194. Log3 $hash, 3, "NetIO230B: Configuration could not be read. Trying default values...\n";
  195. }
  196. } else {
  197. #in any other case
  198. $hash->{USER} = $a[PARAM_USER] if defined($a[PARAM_USER]);
  199. $hash->{PASS} = $a[PARAM_PASS] if defined($a[PARAM_PASS]);
  200. }
  201. return "NetIO230B: Invalid device-address! Please use an address in the format: <ip-address>:<portnumber>" unless ($hash->{HOST} =~ m/^(.+):([0-9]+)$/);
  202. Log3 $hash, 3, "NetIO230B: device opened at host: $hash->{HOST} => @a\n";
  203. return undef;
  204. }
  205. ##########################################
  206. #
  207. # NetIO230B Configuration-Format:
  208. #
  209. # %config= (
  210. # host => "192.168.xx.xx",
  211. # user => "anyusername_without_spaces",
  212. # password => "anypassword_without_spaces"
  213. # );
  214. #
  215. #
  216. ##########################################
  217. ### _GetConfiguration reads a plain text file containing arbitrary information
  218. sub
  219. NetIO230B_GetConfiguration($)
  220. {
  221. my ($hash)= @_;
  222. my $configfilename = $hash->{CONFIGFILEPATH};
  223. if(!open(CONFIGFILE, $configfilename))
  224. {
  225. Log3 $hash, 3, "NetIO230B: Cannot open settings file '$configfilename'.";
  226. return ();
  227. }
  228. my @configfile=<CONFIGFILE>;
  229. close(CONFIGFILE);
  230. my %config;
  231. eval join("", @configfile);
  232. return %config;
  233. }
  234. 1;
  235. =pod
  236. =begin html
  237. <a name="NetIO230B"></a>
  238. <h3>NetIO230B</h3>
  239. <ul>
  240. <p>
  241. fhem-module for NetIO 230B Power Distribution Unit &nbsp;&nbsp; (see: <a
  242. href="http://www.koukaam.se/showproduct.php?article_id=1502">NetIO 230B
  243. (koukaam.se)</a>)
  244. </p>
  245. Note: this module needs the HTTP::Request and LWP::UserAgent perl modules.
  246. <br />
  247. Please also note: the PDU must use firmware 3.1 or later and set to unencrypted mode.
  248. <br /><br />
  249. <a name="NETIO230Bdefine"></a>
  250. <b>Define</b>
  251. <ul>
  252. <li><code>define &lt;name&gt; NetIO230B &lt;ip-address&gt; &lt;socket number(s)
  253. &gt; [&lt;user name&gt; &lt;password&gt;]</code></li>
  254. <li><code>define &lt;name&gt; NetIO230B &lt;ip-address&gt; &lt;socket number(s)
  255. &gt; [&lt;config file path&gt;]</code></li>
  256. <p>
  257. Defines a switching device, where sockets can be switched
  258. </p>
  259. <ul>
  260. <li>separately (just use 0-4 as socket number)</li>
  261. <li>all together (use 1234 as socket number)</li>
  262. <li>in arbitrary groups (e.g 13 switches socket 1 and 3, 42
  263. switches socket 2 and 4, etc...), invalid numbers are
  264. ignored</li>
  265. </ul>
  266. <p>
  267. User name and password are optional. When no user name or
  268. password is passed, the module looks for a configfile at
  269. '/var/log/fhem/netio.conf'. If no config file is found, it
  270. uses 'admin/admin' as user/pass, since this is the default
  271. configuration for the device.
  272. <p>
  273. Alternatively you can pass a path to a configfile instead of
  274. the user/pass combo. (e.g. /var/tmp/tmp.conf)
  275. Configfile-Format:<br />
  276. <ul>
  277. <code>
  278. %config= (<br />
  279. &nbsp;&nbsp;&nbsp;host => "192.168.61.40",<br />
  280. &nbsp;&nbsp;&nbsp;user => "admin",<br />
  281. &nbsp;&nbsp;&nbsp;password => "admin"<br />
  282. );</code>
  283. <br /><br /><small>(All settings optional)</small>
  284. </ul>
  285. </p>
  286. <p>Examples:</p>
  287. <ul>
  288. <li><code>define Socket3 NetIO230B 192.168.178.10 3</code></li>
  289. <li><code>define Socket1_and_4 NetIO230B 192.168.178.10 14</code></li>
  290. <li><code>define coffeemaker NetIO230B 192.168.178.10 1 username secretpassword</code></li>
  291. <li><code>define coffeemaker_and_light NetIO230B 192.168.178.10 23 /var/log/kitchen.conf</code></li>
  292. </ul>
  293. </ul>
  294. <br>
  295. <a name="NETIO230Bget"></a>
  296. <b>Get </b>
  297. <ul>
  298. <code>get &lt;name&gt; state</code>
  299. <br><br>
  300. returns the state of the socket(s)<br>
  301. Example:
  302. <ul>
  303. <code>get coffeemaker_and_light</code>&nbsp;&nbsp; => <code>on or off</code><br>
  304. </ul>
  305. <br>
  306. </ul>
  307. <a name="NETIO230Bset"></a>
  308. <b>Set </b>
  309. <ul>
  310. <code>set &lt;name&gt; &lt;value&gt;</code>
  311. <br><br>
  312. where <code>value</code> is one of:<br>
  313. <pre>
  314. on
  315. off
  316. </pre>
  317. Examples:
  318. <ul>
  319. <code>set coffeemaker_and_light on</code><br>
  320. </ul>
  321. <br>
  322. </ul>
  323. </ul>
  324. =end html
  325. =cut