70_TellStick.pm 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401
  1. ################################################################
  2. #
  3. # Copyright notice
  4. #
  5. # (c) 2012 Copyright: Kai 'wusel' Siering (wusel+fhem at uu dot org)
  6. # All rights reserved
  7. #
  8. # This code is free software; you can redistribute it and/or modify
  9. # it under the terms of the GNU General Public License as published by
  10. # the Free Software Foundation; either version 2 of the License, or
  11. # (at your option) any later version.
  12. #
  13. # The GNU General Public License can be found at
  14. # http://www.gnu.org/copyleft/gpl.html.
  15. # A copy is found in the textfile GPL.txt and important notices to the license
  16. # from the author is found in LICENSE.txt distributed with these scripts.
  17. #
  18. # This script is distributed in the hope that it will be useful,
  19. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  20. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  21. # GNU General Public License for more details.
  22. #
  23. # This copyright notice MUST APPEAR in all copies of the script!
  24. ###############################################
  25. ###########################
  26. # 70_TellStick.pm
  27. # Module for FHEM
  28. #
  29. # Contributed by Kai 'wusel' Siering <wusel+fhem@uu.org> in 2012
  30. # Based in part on work for FHEM by other authors ...
  31. # $Id: 70_TellStick.pm 11307 2016-04-25 08:02:06Z rudolfkoenig $
  32. ###########################
  33. package main;
  34. use strict;
  35. use warnings;
  36. #####################################
  37. sub
  38. TellStick_Initialize($)
  39. {
  40. my ($hash) = @_;
  41. # Consumer
  42. $hash->{DefFn} = "TellStick_Define";
  43. $hash->{Clients} =
  44. ":SIS_PMS:";
  45. my %mc = (
  46. "1:SIS_PMS" => "^socket ..:..:..:..:.. .+ state o.*",
  47. );
  48. $hash->{MatchList} = \%mc;
  49. $hash->{AttrList}= "loglevel:0,1,2,3,4,5,6";
  50. $hash->{ReadFn} = "TellStick_Read";
  51. $hash->{WriteFn} = "TellStick_Write";
  52. $hash->{UndefFn} = "TellStick_Undef";
  53. }
  54. #####################################
  55. sub
  56. TellStick_GetCurrentConfig($)
  57. {
  58. my ($hash) = @_;
  59. my $numdetected=0;
  60. my $currentdevice=0;
  61. my $FH;
  62. my $i;
  63. my $dev = sprintf("%s", $hash->{DeviceName});
  64. Log 3, "TellStick_GetCurrentConfig: Using \"$dev\" as parameter to open(); trying ...";
  65. my $tmpdev=sprintf("%s --list 2>&1 |", $dev);
  66. open($FH, $tmpdev);
  67. if(!$FH) {
  68. Log 3, "TellStick_GetCurrentConfig: Can't start $tmpdev: $!";
  69. return "Can't start $tmpdev: $!";
  70. }
  71. local $_;
  72. while (<$FH>) {
  73. my $msg=<$FH>;
  74. chomp($msg);
  75. my ($devid, $name, $state) = split('\t', $msg);
  76. Log 3, "TellStick_GetCurrentConfig: read: /$devid/$name/$state/";
  77. if(defined($devid) && defined($name) && defined($state)) {
  78. $numdetected++;
  79. Log 3, "TellStick_GetCurrentConfig: $devid $name $state";
  80. }
  81. }
  82. close($FH);
  83. Log 3, "TellStick_GetCurrentConfig: Initial read done, $numdetected devices found";
  84. if ($numdetected==0) {
  85. Log 3, "TellStick_GetCurrentConfig: No TellStick devices found.";
  86. return "no TellStick or configured devices found.";
  87. }
  88. $hash->{NUMDEVS} = $numdetected;
  89. $hash->{STATE} = "initialized";
  90. return undef;
  91. }
  92. #####################################
  93. sub
  94. TellStick_Define($$)
  95. {
  96. my ($hash, $def) = @_;
  97. my @a = split("[ \t][ \t]*", $def);
  98. my $numdetected=0;
  99. my $currentdevice=0;
  100. my $retval;
  101. return "Define the /path/to/tdtool as a parameter" if(@a != 3);
  102. my $FH;
  103. my $dev = sprintf("%s", $a[2]);
  104. $hash->{DeviceName} = $dev;
  105. Log 3, "TellStick using \"$dev\" as parameter to open(); trying ...";
  106. $retval=TellStick_GetCurrentConfig($hash);
  107. Log 3, "TellStick GetCurrentConfing done";
  108. if(defined($retval)) {
  109. Log 3, "TellStick: An error occured: $retval";
  110. return $retval;
  111. }
  112. if($hash->{NUMDEVS} < 1) {
  113. return "TellStick no configured devices found.";
  114. }
  115. $hash->{Timer} = 30;
  116. Log 3, "TellStick setting callback timer";
  117. my $oid = $init_done;
  118. $init_done = 1;
  119. InternalTimer(gettimeofday() + 10, "TellStick_GetStatus", $hash, 1);
  120. $init_done = $oid;
  121. Log 3, "TellStick initialized";
  122. return undef;
  123. }
  124. #####################################
  125. sub
  126. TellStick_Undef($$)
  127. {
  128. my ($hash, $def) = @_;
  129. my @a = split("[ \t][ \t]*", $def);
  130. my $name = $hash->{NAME};
  131. if(defined($hash->{FD})) {
  132. close($hash->{FD});
  133. delete $hash->{FD};
  134. }
  135. delete $selectlist{"$name.pipe"};
  136. $hash->{STATE}='undefined';
  137. Log 3, "$name shutdown complete";
  138. return undef;
  139. }
  140. #####################################
  141. sub
  142. TellStick_GetStatus($)
  143. {
  144. my ($hash) = @_;
  145. my $dnr = $hash->{DEVNR};
  146. my $name = $hash->{NAME};
  147. my $dev = $hash->{DeviceName};
  148. my $FH;
  149. my $i;
  150. Log 4, "TellStick contacting device";
  151. my $tmpdev=sprintf("%s --list", $dev);
  152. $tmpdev=sprintf("%s 2>&1 |", $tmpdev);
  153. open($FH, $tmpdev);
  154. if(!$FH) {
  155. return "TellStick Can't open pipe: $dev: $!";
  156. }
  157. $hash->{FD}=$FH;
  158. $selectlist{"$name.pipe"} = $hash;
  159. Log 4, "TellStick pipe opened";
  160. $hash->{STATE} = "reading";
  161. $hash->{pipeopentime} = time();
  162. }
  163. #####################################
  164. sub
  165. TellStick_Read($)
  166. {
  167. my ($hash) = @_;
  168. my $dnr = $hash->{DEVNR};
  169. my $name = $hash->{NAME};
  170. my $dev = $hash->{DeviceName};
  171. my $FH;
  172. my $inputline;
  173. Log 4, "TellStick Read entered";
  174. if(!defined($hash->{FD})) {
  175. Log 3, "Oops, TellStick FD undef'd";
  176. return undef;
  177. }
  178. if(!$hash->{FD}) {
  179. Log 3, "Oops, TellStick FD empty";
  180. return undef;
  181. }
  182. $FH = $hash->{FD};
  183. Log 4, "TellStick reading started";
  184. my @lines;
  185. my $eof;
  186. my $i=0;
  187. my $tn = TimeNow();
  188. my $reading;
  189. my $readingforstatus;
  190. ($eof, @lines) = nonblockGetLinesTellStick($FH);
  191. if(!defined($eof)) {
  192. Log 4, "TellStick FIXME: eof undefined?!";
  193. $eof=0;
  194. }
  195. Log 4, "TellStick reading ended with eof==$eof";
  196. # FIXME! Current observed behaviour is "would block", then read of only EOF.
  197. # Not sure if it's always that way; more correct would be checking
  198. # for empty $inputline or undef'd $rawreading,$val. -wusel, 2010-01-04
  199. # UPDATE: Seems to work so far, so I'll re-use this as-is ;) -wusel, 2012-01-21
  200. if($eof != 1) {
  201. foreach my $inputline ( @lines ) {
  202. Log 5, "TellStick read: $inputline";
  203. chomp($inputline);
  204. my ($devid, $name, $state) = split('\t', $inputline);
  205. if(defined($devid) && defined($name) && defined($state)) {
  206. $state=lc($state);
  207. my $dmsg = sprintf("socket te:ll:st:ck:01 %d state %s", $devid, $state);
  208. $name =~ s/\W/_/;
  209. $hash->{TMPLABEL} = $name;
  210. Dispatch($hash, $dmsg, undef);
  211. } else {
  212. Log 4, "TellStick line /$inputline/ ignored";
  213. }
  214. }
  215. }
  216. if($eof) {
  217. close($FH);
  218. delete $hash->{FD};
  219. delete $selectlist{"$name.pipe"};
  220. undef($hash->{TMPLABEL});
  221. # InternalTimer(gettimeofday()+ $hash->{Timer}, "TellStick_GetStatus", $hash, 1);
  222. $hash->{STATE} = "read";
  223. Log 4, "TellStick done reading pipe";
  224. } else {
  225. $hash->{STATE} = "reading";
  226. Log 4, "TellStick (further) reading would block";
  227. }
  228. }
  229. #####################################
  230. sub TellStick_Write($$$) {
  231. my ($hash,$fn,$msg) = @_;
  232. my $dev = $hash->{DeviceName};
  233. my ($serial, $devid, $what) = split(' ', $msg);
  234. Log 4, "TellStick_Write entered for $hash->{NAME}: $serial, $devid, $what";
  235. my $cmdline;
  236. my $cmdletter="l";
  237. if($what eq "on") {
  238. $cmdletter="n";
  239. } elsif($what eq "off") {
  240. $cmdletter="f";
  241. }
  242. $cmdline=sprintf("%s -%s %d 2>&1 >/dev/null", $dev, $cmdletter, $devid);
  243. system($cmdline);
  244. Log 4, "TellStick_Write executed $cmdline";
  245. return;
  246. }
  247. # From http://www.perlmonks.org/?node_id=713384 / http://davesource.com/Solutions/20080924.Perl-Non-blocking-Read-On-Pipes-Or-Files.html
  248. #
  249. # Used, hopefully, with permission ;)
  250. #
  251. # An non-blocking filehandle read that returns an array of lines read
  252. # Returns: ($eof,@lines)
  253. my %nonblockGetLines_lastTellStick;
  254. sub nonblockGetLinesTellStick {
  255. my ($fh,$timeout) = @_;
  256. $timeout = 0 unless defined $timeout;
  257. my $rfd = '';
  258. $nonblockGetLines_lastTellStick{$fh} = ''
  259. unless defined $nonblockGetLines_lastTellStick{$fh};
  260. vec($rfd,fileno($fh),1) = 1;
  261. return unless select($rfd, undef, undef, $timeout)>=0;
  262. # I'm not sure the following is necessary?
  263. return unless vec($rfd,fileno($fh),1);
  264. my $buf = '';
  265. my $n = sysread($fh,$buf,1024*1024);
  266. # If we're done, make sure to send the last unfinished line
  267. return (1,$nonblockGetLines_lastTellStick{$fh}) unless $n;
  268. # Prepend the last unfinished line
  269. $buf = $nonblockGetLines_lastTellStick{$fh}.$buf;
  270. # And save any newly unfinished lines
  271. $nonblockGetLines_lastTellStick{$fh} =
  272. (substr($buf,-1) !~ /[\r\n]/ && $buf =~ s/([^\r\n]*)$//)
  273. ? $1 : '';
  274. $buf ? (0,split(/\n/,$buf)) : (0);
  275. }
  276. 1;
  277. =pod
  278. =begin html
  279. <a name="TellStick"></a>
  280. <h3>TellStick</h3>
  281. <ul>
  282. <br>
  283. <a name="TellStickdefine"></a>
  284. <b>Define</b>
  285. <ul>
  286. <code>define &lt;name&gt; TellStick &lt;/path/to/tdtool&gt;</code>
  287. <br><br>
  288. Defines a path to the program "tdtool", which is used to control a (locally attached)
  289. "Telldus TellStick [Duo]" USB device. A TellStick controls a wide range of 433 MHz
  290. devices, like the widely available switchable power outlets from InterTechno.<br><br>
  291. To keep things simple, FHEM interfaces with the telldus-core suite (available
  292. for Linux, Windows, Mac OSX) via the supplied tool, "tdtool". This FHEM module
  293. will initially use "tdtool --list" to receive a list of configured devices, then let
  294. autocreate (if enabled) create them as <a href="#SIS_PMS">SIS_PMS</a> devices.<br></br>
  295. <i>Please make sure</i> that the user running FHEM under ("fhem" in a standard setup on
  296. Linux) has the <i>r/w-right to access the stick's device</i> ("/dev/tellstick"
  297. in telldus-core version 2.0) &mdash; if the state of your devices do not change when
  298. modified im FHEM, access rights problems are the most probable cause
  299. (<code>chmod o+rw /dev/tellstick</code> should fix that; you may want to automate it
  300. via udev or adding the fhem user to the proper group ;))<br></br>
  301. This module has only been tested with the 2.0 branch of teldus-core because of a known bug
  302. in 2.1, <a href="http://www.telldus.com/forum/viewtopic.php?f=15&t=1645"> preventing
  303. version 2.1 working properly with some TellSticks</a> and/or "tdtool" application; FTR, the
  304. "Batch: 8" version a was granted usage of for writing this module was impacted by it ...
  305. To actually control any power sockets, you need to define a <a href="#SIS_PMS">SIS_PMS</a>
  306. device &mdash; TellStick.pm uses SIS_PMS devices ("socket" is te:ll:st:ck:01, "socketnr"
  307. is the ID of the device in "tdtool"), as as of now only on/off switching is supported and
  308. this was the easiest implementation path. SIS_PMS is supported by <a href="http://sites.google.com/site/andfhem/">andFHEM</a>, the Android
  309. frontend, so this make some sense. (Furthermore, I don't own dimmable devices and they are
  310. actually not really cheap; >15 EUR/socket compared to the 15 EUR for 5 switch-only, non-self
  311. learning socket adapters from Intertechno at your local home improvement store.)
  312. <br><br>
  313. Example:
  314. <ul>
  315. <code>define TStick TellStick /usr/bin/tdtool</code><br>
  316. <code>define Deckenfluter SIS_PMS te:ll:st:ck:01 2</code><br>
  317. </ul>
  318. <br>
  319. </ul>
  320. <a name="TellStickset"></a>
  321. <b>Set</b> <ul>N/A</ul><br>
  322. <a name="TellStickget"></a>
  323. <b>Get</b> <ul>N/A</ul><br>
  324. <a name="TellStickattr"></a>
  325. <b>Attributes</b>
  326. <ul>
  327. <li>none <!--<a href="#model">model</a> (TellStick)--></li>
  328. </ul>
  329. <br>
  330. </ul>
  331. =end html
  332. =cut