66_ECMD.pm 41 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009
  1. # $Id: 66_ECMD.pm 16372 2018-03-10 13:09:24Z neubert $
  2. ##############################################################################
  3. #
  4. # 66_ECMD.pm
  5. # Copyright by Dr. Boris Neubert
  6. # e-mail: omega at online dot de
  7. #
  8. # This file is part of fhem.
  9. #
  10. # Fhem is free software: you can redistribute it and/or modify
  11. # it under the terms of the GNU General Public License as published by
  12. # the Free Software Foundation, either version 2 of the License, or
  13. # (at your option) any later version.
  14. #
  15. # Fhem is distributed in the hope that it will be useful,
  16. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. # GNU General Public License for more details.
  19. #
  20. # You should have received a copy of the GNU General Public License
  21. # along with fhem. If not, see <http://www.gnu.org/licenses/>.
  22. #
  23. ##############################################################################
  24. package main;
  25. =for comment
  26. General rule:
  27. ECMD handles raw data, i.e. data that might contain control and non-printable characters.
  28. User input for raw data, e.g. setting attributes, and display of raw data is perl-encoded.
  29. Perl-encoded raw data in logs is not enclosed in double quotes.
  30. A carriage return/line feed (characters 13 and 10) is encoded as
  31. \r\n
  32. and logged as
  33. \r\n (\010\012)
  34. Decoding is handled by dq(). Encoding is handled by cq().
  35. changes as of 27 Nov 2016:
  36. - if split is used, the strings at which the messages are split are still part of the messages
  37. - no default attributes for requestSeparator and responseSeparator
  38. - input of raw data as perl-encoded string (for setting attributes)
  39. - be more verbose and explicit at loglevel 5
  40. - documentation corrected and amended
  41. =cut
  42. use strict;
  43. use warnings;
  44. use Time::HiRes qw(gettimeofday);
  45. use DevIo;
  46. sub ECMD_Attr($@);
  47. sub ECMD_Clear($);
  48. #sub ECMD_Parse($$$$$);
  49. sub ECMD_Read($);
  50. sub ECMD_ReadAnswer($$);
  51. sub ECMD_Ready($);
  52. sub ECMD_Write($$$);
  53. use vars qw {%attr %defs};
  54. #####################################
  55. sub
  56. ECMD_Initialize($)
  57. {
  58. my ($hash) = @_;
  59. # Provider
  60. $hash->{WriteFn} = "ECMD_Write";
  61. $hash->{ReadFn} = "ECMD_Read";
  62. $hash->{Clients} = ":ECMDDevice:";
  63. # Consumer
  64. $hash->{DefFn} = "ECMD_Define";
  65. $hash->{UndefFn} = "ECMD_Undef";
  66. $hash->{ReadyFn} = "ECMD_Ready";
  67. $hash->{GetFn} = "ECMD_Get";
  68. $hash->{SetFn} = "ECMD_Set";
  69. $hash->{AttrFn} = "ECMD_Attr";
  70. $hash->{AttrList}= "classdefs split logTraffic:0,1,2,3,4,5 timeout partial requestSeparator responseSeparator autoReopen stop:0,1";
  71. }
  72. #####################################
  73. sub
  74. ECMD_Define($$)
  75. {
  76. my ($hash, $def) = @_;
  77. my @a = split("[ \t]+", $def);
  78. my $name = $a[0];
  79. my $protocol = $a[2];
  80. if(@a < 4 || @a > 4 || (($protocol ne "telnet") && ($protocol ne "serial"))) {
  81. my $msg = "wrong syntax: define <name> ECMD telnet <ipaddress[:port]> or define <name> ECMD serial <devicename[\@baudrate]>";
  82. Log 2, $msg;
  83. return $msg;
  84. }
  85. $hash->{fhem}{".requestSeparator"}= undef;
  86. $hash->{fhem}{".responseSeparator"}= undef;
  87. $hash->{fhem}{".split"}= undef;
  88. DevIo_CloseDev($hash);
  89. $hash->{Protocol}= $protocol;
  90. my $devicename= $a[3];
  91. $hash->{DeviceName} = $devicename;
  92. my $ret = DevIo_OpenDev($hash, 0, undef);
  93. return $ret;
  94. }
  95. #####################################
  96. sub
  97. ECMD_Undef($$)
  98. {
  99. my ($hash, $arg) = @_;
  100. my $name = $hash->{NAME};
  101. # deleting port for clients
  102. foreach my $d (sort keys %defs) {
  103. if(defined($defs{$d}) &&
  104. defined($defs{$d}{IODev}) &&
  105. $defs{$d}{IODev} == $hash) {
  106. my $lev = ($reread_active ? 4 : 2);
  107. Log3 $hash, $lev, "deleting port for $d";
  108. delete $defs{$d}{IODev};
  109. }
  110. }
  111. DevIo_CloseDev($hash);
  112. return undef;
  113. }
  114. #####################################
  115. sub
  116. ECMD_DoInit($)
  117. {
  118. my $hash = shift;
  119. my $name = $hash->{NAME};
  120. my $msg = undef;
  121. ECMD_Clear($hash);
  122. $hash->{STATE} = "Initialized" if(!$hash->{STATE});
  123. return undef;
  124. }
  125. #####################################
  126. sub
  127. oq($)
  128. {
  129. my ($s)= @_;
  130. return join("", map { sprintf("\\%03o", ord($_)) } split("", $s));
  131. }
  132. sub
  133. dq($)
  134. {
  135. my ($s)= @_;
  136. return defined($s) ? ( $s eq "" ? "empty string" : escapeLogLine($s) . " (" . oq($s) . ")" ) : "<nothing>";
  137. }
  138. sub
  139. cq($)
  140. {
  141. my ($s)= @_;
  142. $s =~ s/\\(\d)(\d)(\d)/chr($1*64+$2*8+$3)/eg;
  143. $s =~ s/\\a/\a/g;
  144. $s =~ s/\\e/\e/g;
  145. $s =~ s/\\f/\f/g;
  146. $s =~ s/\\n/\n/g;
  147. $s =~ s/\\r/\r/g;
  148. $s =~ s/\\t/\t/g;
  149. $s =~ s/\\\\/\\/g;
  150. return $s;
  151. }
  152. #####################################
  153. sub
  154. ECMD_Log($$$)
  155. {
  156. my ($hash, $loglevel, $logmsg)= @_;
  157. my $name= $hash->{NAME};
  158. $loglevel= AttrVal($name, "logTraffic", undef) unless(defined($loglevel));
  159. return unless(defined($loglevel));
  160. Log3 $hash, $loglevel, "$name: $logmsg";
  161. }
  162. #####################################
  163. sub
  164. ECMD_Ready($)
  165. {
  166. my ($hash) = @_;
  167. return DevIo_OpenDev($hash, 1, "ECMD_DoInit")
  168. if($hash->{STATE} eq "disconnected");
  169. # This is relevant for windows/USB only
  170. my $po = $hash->{USBDev};
  171. my ($BlockingFlags, $InBytes, $OutBytes, $ErrorFlags);
  172. if($po) {
  173. ($BlockingFlags, $InBytes, $OutBytes, $ErrorFlags) = $po->status;
  174. }
  175. return ($InBytes && $InBytes>0);
  176. }
  177. #####################################
  178. sub
  179. ECMD_isStopped($)
  180. {
  181. my $dev = shift; # name or hash
  182. $dev = $dev->{NAME} if(defined($dev) && ref($dev) eq "HASH");
  183. my $isDisabled = AttrVal($dev, "stop", 0);
  184. }
  185. #####################################
  186. sub
  187. ECMD_SimpleRead($)
  188. {
  189. my $hash = shift;
  190. return undef if ECMD_isStopped($hash);
  191. my $answer= DevIo_SimpleRead($hash);
  192. ECMD_Log $hash, undef, "read " . dq($answer);
  193. return $answer;
  194. }
  195. sub
  196. ECMD_SimpleWrite($$)
  197. {
  198. my ($hash, $msg) = @_;
  199. return undef if ECMD_isStopped($hash);
  200. ECMD_Log $hash, undef, "write " . dq($msg);
  201. DevIo_SimpleWrite($hash, $msg, 0);
  202. }
  203. sub
  204. ECMD_SimpleExpect($$$)
  205. {
  206. my ($hash, $msg, $expect) = @_;
  207. my $name= $hash->{NAME};
  208. return undef if ECMD_isStopped($name);
  209. my $timeout= AttrVal($name, "timeout", 3.0);
  210. my $partialTimeout= AttrVal($name, "partial", 0.0);
  211. ECMD_Log $hash, undef, "write " . dq($msg) . ", expect $expect";
  212. my $answer= DevIo_Expect($hash, $msg, $timeout );
  213. #Debug "$name: Expect got \"" . escapeLogLine($answer) . "\".";
  214. # complete partial answers
  215. if($partialTimeout> 0) {
  216. my $t0= gettimeofday();
  217. while(!defined($answer) || ($answer !~ /^$expect$/)) {
  218. #Debug "$name: waiting for a match...";
  219. my $a= DevIo_SimpleReadWithTimeout($hash, $partialTimeout); # we deliberately use partialTimeout here!
  220. #Debug "$name: SimpleReadWithTimeout got \"" . escapeLogLine($a) . "\".";
  221. if(defined($a)) {
  222. $answer= ( defined($answer) ? $answer . $a : $a );
  223. }
  224. #Debug "$name: SimpleExpect has now answer \"" . escapeLogLine($answer) . "\".";
  225. last if(gettimeofday()-$t0> $partialTimeout);
  226. }
  227. }
  228. if(defined($answer)) {
  229. ECMD_Log $hash, undef, "read " . dq($answer);
  230. if($answer !~ m/^$expect$/) {
  231. ECMD_Log $hash, 1, "unexpected answer " . dq($answer) . " received (wrote " .
  232. dq($msg) . ", expected $expect)";
  233. }
  234. } else {
  235. ECMD_Log $hash, 1, "no answer received (wrote " .
  236. dq($msg) . ", expected $expect)";
  237. }
  238. return $answer;
  239. }
  240. #####################################
  241. sub
  242. ECMD_SetState($$$$)
  243. {
  244. my ($hash, $tim, $vt, $val) = @_;
  245. return undef;
  246. }
  247. #####################################
  248. sub
  249. ECMD_Clear($)
  250. {
  251. my $hash = shift;
  252. return undef if ECMD_isStopped($hash);
  253. # Clear the pipe
  254. DevIo_TimeoutRead($hash, 0.1);
  255. }
  256. #####################################
  257. sub
  258. ECMD_Get($@)
  259. {
  260. my ($hash, @a) = @_;
  261. return "get needs at least one parameter" if(@a < 2);
  262. my $name = $a[0];
  263. my $cmd= $a[1];
  264. my $arg = ($a[2] ? $a[2] : "");
  265. my @args= @a; shift @args; shift @args;
  266. my ($answer, $err);
  267. return "No get $cmd for dummies" if(IsDummy($name));
  268. if($cmd eq "raw") {
  269. return "get raw needs an argument" if(@a< 3);
  270. my $ecmd= join " ", @args;
  271. $ecmd= AnalyzePerlCommand(undef, $ecmd);
  272. # poor man's error catching...
  273. if($ecmd =~ "^Bareword \"") {
  274. $answer= $ecmd;
  275. } else {
  276. $answer= ECMD_SimpleExpect($hash, $ecmd, ".*");
  277. }
  278. } else {
  279. return "Unknown argument $cmd, choose one of raw";
  280. }
  281. $hash->{READINGS}{$cmd}{VAL} = $answer;
  282. $hash->{READINGS}{$cmd}{TIME} = TimeNow();
  283. return "$name $cmd => $answer";
  284. }
  285. #####################################
  286. sub
  287. ECMD_EvalClassDef($$$)
  288. {
  289. my ($hash, $classname, $filename)=@_;
  290. my $name= $hash->{NAME};
  291. # refuse overwriting existing definitions
  292. if(defined($hash->{fhem}{classDefs}{$classname})) {
  293. my $err= "$name: class $classname is already defined.";
  294. Log3 $hash, 1, $err;
  295. return $err;
  296. }
  297. # try and open the class definition file
  298. if(!open(CLASSDEF, $filename)) {
  299. my $err= "$name: cannot open file $filename for class $classname.";
  300. Log3 $hash, 1, $err;
  301. return $err;
  302. }
  303. my @classdef= <CLASSDEF>;
  304. close(CLASSDEF);
  305. # add the class definition
  306. Log3 $hash, 5, "$name: adding new class $classname from file $filename";
  307. $hash->{fhem}{classDefs}{$classname}{filename}= $filename;
  308. # format of the class definition:
  309. # params <params> parameters for device definition
  310. # get <cmdname> cmd {<perlexpression>} defines a get command
  311. # get <cmdname> params <params> parameters for get command
  312. # get <cmdname> expect regex expected regex for get command
  313. # get <cmdname> postproc { <perl command> } postprocessor for get command
  314. # set <cmdname> cmd {<perlexpression>} defines a set command
  315. # set <cmdname> expect regex expected regex for set command
  316. # set <cmdname> params <params> parameters for get command
  317. # set <cmdname> postproc { <perl command> } postprocessor for set command
  318. # all lines are optional
  319. #
  320. # eaxmple class definition 1:
  321. # get adc cmd {"adc get %channel"}
  322. # get adc params channel
  323. #
  324. # eaxmple class definition 1:
  325. # params btnup btnstop btndown
  326. # set up cmd {"io set ddr 2 ff\nio set port 2 1%btnup\nwait 1000\nio set port 2 00"}
  327. # set stop cmd {"io set ddr 2 ff\nio set port 2 1%btnstop\nwait 1000\nio set port 2 00"}
  328. # set down cmd {"io set ddr 2 ff\nio set port 2 1%btndown\nwait 1000\nio set port 2 00"}
  329. my $cont= "";
  330. foreach my $line (@classdef) {
  331. # kill trailing newline
  332. chomp $line;
  333. # kill comments and blank lines
  334. $line=~ s/\#.*$//;
  335. $line=~ s/\s+$//;
  336. $line= $cont . $line;
  337. if($line=~ s/\\$//) { $cont= $line; undef $line; }
  338. next unless($line);
  339. $cont= "";
  340. Log3 $hash, 5, "$name: evaluating >$line<";
  341. # split line into command and definition
  342. my ($cmd, $def)= split("[ \t]+", $line, 2);
  343. #if($cmd eq "nonl") {
  344. # Log3 $hash, 5, "$name: no newline";
  345. # $hash->{fhem}{classDefs}{$classname}{nonl}= 1;
  346. #}
  347. #
  348. # params
  349. #
  350. if($cmd eq "params") {
  351. Log3 $hash, 5, "$name: parameters are $def";
  352. $hash->{fhem}{classDefs}{$classname}{params}= $def;
  353. #
  354. # state
  355. #
  356. } elsif($cmd eq "state") {
  357. Log3 $hash, 5, "$name: state is determined as $def";
  358. $hash->{fhem}{classDefs}{$classname}{state}= $def;
  359. #
  360. # reading
  361. #
  362. } elsif($cmd eq "reading") {
  363. my ($readingname, $spec, $arg)= split("[ \t]+", $def, 3);
  364. #
  365. # match
  366. #
  367. if($spec eq "match") {
  368. if($arg !~ m/^"(.*)"$/s) {
  369. Log3 $hash, 1, "$name: match for reading $readingname is not enclosed in double quotes.";
  370. next;
  371. }
  372. $arg = $1;
  373. Log3 $hash, 5, "$name: reading $readingname will match $arg";
  374. $hash->{fhem}{classDefs}{$classname}{readings}{$readingname}{match}= $arg;
  375. #
  376. # postproc
  377. #
  378. } elsif($spec eq "postproc") {
  379. if($arg !~ m/^{.*}$/s) {
  380. Log3 $hash, 1, "$name: postproc command for reading $readingname is not a perl command.";
  381. next;
  382. }
  383. $arg =~ s/^(\\\n|[ \t])*//; # Strip space or \\n at the beginning
  384. $arg =~ s/[ \t]*$//;
  385. Log3 $hash, 5, "$name: reading $readingname postprocessor defined as $arg";
  386. $hash->{fhem}{classDefs}{$classname}{readings}{$readingname}{postproc}= $arg;
  387. #
  388. # anything else
  389. #
  390. } else {
  391. Log3 $hash, 1,
  392. "$name: illegal spec $spec for reading $readingname for class $classname in file $filename.";
  393. }
  394. #
  395. # set, get
  396. #
  397. } elsif($cmd eq "set" || $cmd eq "get") {
  398. my ($cmdname, $spec, $arg)= split("[ \t]+", $def, 3);
  399. if($spec eq "params") {
  400. if($cmd eq "set") {
  401. Log3 $hash, 5, "$name: set $cmdname has parameters $arg";
  402. $hash->{fhem}{classDefs}{$classname}{sets}{$cmdname}{params}= $arg;
  403. } elsif($cmd eq "get") {
  404. Log3 $hash, 5, "$name: get $cmdname has parameters $arg";
  405. $hash->{fhem}{classDefs}{$classname}{gets}{$cmdname}{params}= $arg;
  406. }
  407. } elsif($spec eq "cmd") {
  408. if($arg !~ m/^{.*}$/s) {
  409. Log3 $hash, 1, "$name: command for $cmd $cmdname is not a perl command.";
  410. next;
  411. }
  412. $arg =~ s/^(\\\n|[ \t])*//; # Strip space or \\n at the beginning
  413. $arg =~ s/[ \t]*$//;
  414. if($cmd eq "set") {
  415. Log3 $hash, 5, "$name: set $cmdname command defined as $arg";
  416. $hash->{fhem}{classDefs}{$classname}{sets}{$cmdname}{cmd}= $arg;
  417. } elsif($cmd eq "get") {
  418. Log3 $hash, 5, "$name: get $cmdname command defined as $arg";
  419. $hash->{fhem}{classDefs}{$classname}{gets}{$cmdname}{cmd}= $arg;
  420. }
  421. } elsif($spec eq "postproc") {
  422. if($arg !~ m/^{.*}$/s) {
  423. Log3 $hash, 1, "$name: postproc command for $cmd $cmdname is not a perl command.";
  424. next;
  425. }
  426. $arg =~ s/^(\\\n|[ \t])*//; # Strip space or \\n at the beginning
  427. $arg =~ s/[ \t]*$//;
  428. if($cmd eq "set") {
  429. Log3 $hash, 5, "$name: set $cmdname postprocessor defined as $arg";
  430. $hash->{fhem}{classDefs}{$classname}{sets}{$cmdname}{postproc}= $arg;
  431. } elsif($cmd eq "get") {
  432. Log3 $hash, 5, "$name: get $cmdname postprocessor defined as $arg";
  433. $hash->{fhem}{classDefs}{$classname}{gets}{$cmdname}{postproc}= $arg;
  434. }
  435. } elsif($spec eq "expect") {
  436. if($arg !~ m/^"(.*)"$/s) {
  437. Log3 $hash, 1, "$name: expect for $cmd $cmdname is not enclosed in double quotes.";
  438. next;
  439. }
  440. $arg = $1;
  441. if($cmd eq "set") {
  442. Log3 $hash, 5, "$name: set $cmdname expects $arg";
  443. $hash->{fhem}{classDefs}{$classname}{sets}{$cmdname}{expect}= $arg;
  444. } elsif($cmd eq "get") {
  445. Log3 $hash, 5, "$name: get $cmdname expects $arg";
  446. $hash->{fhem}{classDefs}{$classname}{gets}{$cmdname}{expect}= $arg;
  447. }
  448. } else {
  449. Log3 $hash, 1,
  450. "$name: illegal spec $spec for $cmd $cmdname for class $classname in file $filename.";
  451. }
  452. } else {
  453. Log3 $hash, 1, "$name: illegal tag $cmd for class $classname in file $filename.";
  454. }
  455. }
  456. # store class definitions in attribute
  457. $attr{$name}{classdefs}= "";
  458. my @a;
  459. foreach my $c (keys %{$hash->{fhem}{classDefs}}) {
  460. push @a, "$c=$hash->{fhem}{classDefs}{$c}{filename}";
  461. }
  462. $attr{$name}{"classdefs"}= join(":", @a);
  463. return undef;
  464. }
  465. #####################################
  466. sub
  467. ECMD_Attr($@)
  468. {
  469. my @a = @_;
  470. my $hash= $defs{$a[1]};
  471. my $name= $hash->{NAME};
  472. if($a[0] eq "set") {
  473. if($a[2] eq "classdefs") {
  474. my @classdefs= split(/:/,$a[3]);
  475. delete $hash->{fhem}{classDefs};
  476. foreach my $classdef (@classdefs) {
  477. my ($classname,$filename)= split(/=/,$classdef,2);
  478. ECMD_EvalClassDef($hash, $classname, $filename);
  479. }
  480. } elsif($a[2] eq "requestSeparator") {
  481. my $c= cq($a[3]);
  482. $hash->{fhem}{".requestSeparator"}= $c;
  483. Log3 $hash, 5, "$name: requestSeparator set to " . dq($c);
  484. } elsif($a[2] eq "responseSeparator") {
  485. my $c= cq($a[3]);
  486. $hash->{fhem}{".responseSeparator"}= $c;
  487. Log3 $hash, 5, "$name: responseSeparator set to " . dq($c);
  488. } elsif($a[2] eq "split") {
  489. my $c= cq($a[3]);
  490. $hash->{fhem}{".split"}= $c;
  491. Log3 $hash, 5, "$name: split set to " . dq($c);
  492. }
  493. } elsif($a[0] eq "del") {
  494. if($a[2] eq "requestSeparator") {
  495. $hash->{fhem}{".requestSeparator"}= undef;
  496. Log3 $hash, 5, "$name: requestSeparator deleted";
  497. } elsif($a[2] eq "responseSeparator") {
  498. $hash->{fhem}{".responseSeparator"}= undef;
  499. Log3 $hash, 5, "$name: responseSeparator deleted";
  500. } elsif($a[2] eq "split") {
  501. $hash->{fhem}{".split"}= undef;
  502. Log3 $hash, 5, "$name: split deleted";
  503. }
  504. }
  505. return undef;
  506. }
  507. #####################################
  508. sub
  509. ECMD_Reopen($)
  510. {
  511. my ($hash) = @_;
  512. return undef if ECMD_isStopped($hash);
  513. DevIo_CloseDev($hash);
  514. DevIo_OpenDev($hash, 1, undef);
  515. return undef;
  516. }
  517. #####################################
  518. sub
  519. ECMD_Set($@)
  520. {
  521. my ($hash, @a) = @_;
  522. my $name = $a[0];
  523. # usage check
  524. #my $usage= "Usage: set $name classdef <classname> <filename> OR set $name reopen";
  525. my $usage= "Unknown argument $a[1], choose one of reopen classdef";
  526. if((@a == 2) && ($a[1] eq "reopen")) {
  527. return ECMD_Reopen($hash);
  528. }
  529. return $usage if(@a != 4);
  530. return $usage if($a[1] ne "classdef");
  531. # from the definition
  532. my $classname= $a[2];
  533. my $filename= $a[3];
  534. return ECMD_EvalClassDef($hash, $classname, $filename);
  535. }
  536. #####################################
  537. # called from the global loop, when the select for hash->{FD} reports data
  538. sub ECMD_Read($)
  539. {
  540. my ($hash) = @_;
  541. return undef unless($hash->{STATE} eq "opened"); # avoid reading from closed device
  542. my $buf = ECMD_SimpleRead($hash);
  543. return unless(defined($buf));
  544. return if($buf eq "");
  545. ECMD_Log $hash, 5, "Spontaneously received " . dq($buf);
  546. Dispatch($hash, $buf, undef); # dispatch result to ECMDDevices
  547. }
  548. #####################################
  549. sub
  550. ECMD_Write($$$)
  551. {
  552. my ($hash,$msg,$expect) = @_;
  553. my $name= $hash->{NAME};
  554. my $lastWrite= defined($hash->{fhem}{".lastWrite"}) ? $hash->{fhem}{".lastWrite"} : 0;
  555. my $now= gettimeofday();
  556. my $autoReopen= AttrVal($name, "autoReopen", undef);
  557. if(defined($autoReopen)) {
  558. my ($timeout,$delay)= split(',',$autoReopen);
  559. ECMD_Reopen($hash) if($now>$lastWrite+$timeout);
  560. sleep($delay);
  561. }
  562. $hash->{fhem}{".lastWrite"}= $now;
  563. my $answer;
  564. my $ret= "";
  565. my $requestSeparator= $hash->{fhem}{".requestSeparator"};
  566. my $responseSeparator= $hash->{fhem}{".responseSeparator"};
  567. my @ecmds;
  568. if(defined($requestSeparator)) {
  569. @ecmds= split $requestSeparator, $msg;
  570. } else {
  571. push @ecmds, $msg;
  572. }
  573. ECMD_Log $hash, 5, "command split into " . ($#ecmds+1) . " parts, requestSeparator is " .
  574. dq($requestSeparator) if($#ecmds>0);
  575. foreach my $ecmd (@ecmds) {
  576. ECMD_Log $hash, 5, "sending command " . dq($ecmd);
  577. my $msg .= $ecmd;
  578. if(defined($expect)) {
  579. $answer= ECMD_SimpleExpect($hash, $msg, $expect);
  580. $answer= "" unless(defined($answer));
  581. ECMD_Log $hash, 5, "received answer " . dq($answer);
  582. $answer.= $responseSeparator if(defined($responseSeparator) && ($#ecmds>0));
  583. $ret.= $answer;
  584. } else {
  585. ECMD_SimpleWrite($hash, $msg);
  586. }
  587. }
  588. return $ret;
  589. }
  590. #####################################
  591. 1;
  592. =pod
  593. =item device
  594. =item summary configurable request/response-like communication (physical device)
  595. =item summary_DE konfigurierbare Frage/Antwort-Kommunikation (physisches Ger&auml;t)
  596. =begin html
  597. <a name="ECMD"></a>
  598. <h3>ECMD</h3>
  599. <ul>
  600. Any physical device with request/response-like communication capabilities
  601. over a serial line or TCP connection can be defined as ECMD device. A practical example
  602. of such a device is the AVR microcontroller board AVR-NET-IO from
  603. <a href="http://www.pollin.de">Pollin</a> with
  604. <a href="http://www.ethersex.de/index.php/ECMD">ECMD</a>-enabled
  605. <a href="http://www.ethersex.de">Ethersex</a> firmware. The original
  606. NetServer firmware from Pollin works as well. There is a plenitude of use cases.<p>
  607. A physical ECMD device can host any number of logical ECMD devices. Logical
  608. devices are defined as <a href="#ECMDDevice">ECMDDevice</a>s in fhem.
  609. ADC 0 to 3 and I/O port 0 to 3 of the above mentioned board
  610. are examples of such logical devices. ADC 0 to 3 all belong to the same
  611. device class ADC (analog/digital converter). I/O port 0 to 3 belong to the device
  612. class I/O port. By means of extension boards you can make your physical
  613. device drive as many logical devices as you can imagine, e.g. IR receivers,
  614. LC displays, RF receivers/transmitters, 1-wire devices, etc.<p>
  615. Defining one fhem module for any device class would create an unmanageable
  616. number of modules. Thus, an abstraction layer is used. You create a device class
  617. on the fly and assign it to a logical ECMD device. The
  618. <a href="#ECMDClassdef">class definition</a>
  619. names the parameters of the logical device, e.g. a placeholder for the number
  620. of the ADC or port, as well as the get and set capabilities. Worked examples
  621. are to be found in the documentation of the <a href="#ECMDDevice">ECMDDevice</a> device.
  622. <br><br>
  623. Note: this module requires the Device::SerialPort or Win32::SerialPort module
  624. if the module is connected via serial Port or USB.<p>
  625. <a name="ECMDcharcoding"></a>
  626. <b>Character coding</b><br><br>
  627. ECMD is suited to process any character including non-printable and control characters.
  628. User input for raw data, e.g. for setting attributes, and the display of raw data, e.g. in the log,
  629. is perl-encoded according to the following table (ooo stands for a three-digit octal number):<BR>
  630. <table>
  631. <tr><th>character</th><th>octal</th><th>code</th></tr>
  632. <tr><td>Bell</td><td>007</td><td>\a</td></tr>
  633. <tr><td>Backspace</td><td>008</td><td>\008</td></tr>
  634. <tr><td>Escape</td><td>033</td><td>\e</td></tr>
  635. <tr><td>Formfeed</td><td>014</td><td>\f</td></tr>
  636. <tr><td>Newline</td><td>012</td><td>\n</td></tr>
  637. <tr><td>Return</td><td>015</td><td>\r</td></tr>
  638. <tr><td>Tab</td><td>011</td><td>\t</td></tr>
  639. <tr><td>backslash</td><td>134</td><td>\134 or \\</td></tr>
  640. <tr><td>any</td><td>ooo</td><td>\ooo</td></tr>
  641. </table><br>
  642. In user input, use \134 for backslash to avoid conflicts with the way FHEM handles continuation lines.
  643. <br><br>
  644. <a name="ECMDdefine"></a>
  645. <b>Define</b><br><br>
  646. <ul>
  647. <code>define &lt;name&gt; ECMD telnet &lt;IPAddress:Port&gt;</code><br><br>
  648. or<br><br>
  649. <code>define &lt;name&gt; ECMD serial &lt;SerialDevice&gt;[&lt;@BaudRate&gt;]</code>
  650. <br><br>
  651. Defines a physical ECMD device. The keywords <code>telnet</code> or
  652. <code>serial</code> are fixed.<br><br>
  653. Examples:
  654. <ul>
  655. <code>define AVRNETIO ECMD telnet 192.168.0.91:2701</code><br>
  656. <code>define AVRNETIO ECMD serial /dev/ttyS0</code><br>
  657. <code>define AVRNETIO ECMD serial /dev/ttyUSB0@38400</code><br>
  658. </ul>
  659. <br>
  660. </ul>
  661. <a name="ECMDset"></a>
  662. <b>Set</b><br><br>
  663. <ul>
  664. <code>set &lt;name&gt; classdef &lt;classname&gt; &lt;filename&gt;</code>
  665. <br><br>
  666. Creates a new device class <code>&lt;classname&gt;</code> for logical devices.
  667. The class definition is in the file <code>&lt;filename&gt;</code>. You must
  668. create the device class before you create a logical device that adheres to
  669. that definition.
  670. <br><br>
  671. Example:
  672. <ul>
  673. <code>set AVRNETIO classdef /etc/fhem/ADC.classdef</code><br>
  674. </ul>
  675. <br>
  676. <code>set &lt;name&gt; reopen</code>
  677. <br><br>
  678. Closes and reopens the device. Could be handy if connection is lost and cannot be
  679. reestablished automatically.
  680. <br><br>
  681. </ul>
  682. <a name="ECMDget"></a>
  683. <b>Get</b><br><br>
  684. <ul>
  685. <code>get &lt;name&gt; raw &lt;command&gt;</code>
  686. <br><br>
  687. Sends the command <code>&lt;command&gt;</code> to the physical ECMD device
  688. <code>&lt;name&gt;</code> and reads the response. In the likely case that
  689. the command needs to be terminated by a newline character, you have to
  690. resort to a <a href="#perl">&lt;perl special&gt;</a>.
  691. <br><br>
  692. Example:
  693. <ul>
  694. <code>get AVRNETIO raw { "ip\n" }</code><br>
  695. </ul>
  696. </ul>
  697. <br><br>
  698. <a name="ECMDattr"></a>
  699. <b>Attributes</b><br><br>
  700. <ul>
  701. <li>classdefs<br>A colon-separated list of &lt;classname&gt;=&lt;filename&gt;.
  702. The list is automatically updated if a class definition is added. You can
  703. directly set the attribute. Example: <code>attr myECMD classdefs ADC=/etc/fhem/ADC.classdef:GPIO=/etc/fhem/AllInOne.classdef</code></li>
  704. <li>split &lt;separator&gt<br>
  705. Some devices send several readings in one transmission. The split attribute defines the
  706. separator to split such transmissions into separate messages. The regular expression for
  707. matching a reading is then applied to each message in turn. After splitting, the separator
  708. <b>is</b> still part of the single messages. Separator can be a single- or multi-character string,
  709. e.g. \n or \r\n.
  710. Example: <code>attr myECMD split \n</code> splits <code>foo 12\nbar off\n</code> into
  711. <code>foo 12\n</code> and <code>bar off\n</code>.</li>
  712. <li>logTraffic &lt;loglevel&gt;<br>Enables logging of sent and received datagrams with the given loglevel. Control characters in the logged datagrams are <a href="#ECMDcharcoding">escaped</a>, i.e. a double backslash is shown for a single backslash, \n is shown for a line feed character, etc.</li>
  713. <li>timeout &lt;seconds&gt;<br>Time in seconds to wait for a response from the physical ECMD device before FHEM assumes that something has gone wrong. The default is 3 seconds if this attribute is not set.</li>
  714. <li>partial &lt;seconds&gt;<br>Some physical ECMD devices split responses into several transmissions. If the partial attribute is set, this behavior is accounted for as follows: (a) If a response is expected for a get or set command, FHEM collects transmissions from the physical ECMD device until either the response matches the expected response (<code>reading ... match ...</code> in the <a href="#ECMDClassdef">class definition</a>) or the time in seconds given with the partial attribute has expired. (b) If a spontaneous transmission does not match the regular expression for any reading, the transmission is recorded and prepended to the next transmission. If the line is quiet for longer than the time in seconds given with the partial attribute, the recorded transmission is discarded. Use regular expressions that produce exact matches of the complete response (after combining partials and splitting).</li>
  715. <li>requestSeparator &lt;separator&gt<br>
  716. A single command from FHEM to the device might need to be broken down into several requests.
  717. A command string is split at all
  718. occurrences of the request separator. The request separator itself is removed from the command string and thus is
  719. not part of the request. The default is to have no request separator. Use a request separator that does not occur in the actual request.
  720. </li>
  721. <li>responseSeparator &lt;separator&gt<br>
  722. In order to identify the single responses from the device for each part of the command broken down by request separators, a response separator can be appended to the response to each single request.
  723. The response separator is only appended to commands split by means of a
  724. request separator. The default is to have no response separator, i.e. responses are simply concatenated. Use a response separator that does not occur in the actual response.
  725. </li>
  726. <li>autoReopen &lt;timeout&gt;,&lt;delay&gt;<br>
  727. If this attribute is set, the device is automatically reopened if no bytes were written for &lt;timeout&gt seconds or more. After reopening
  728. FHEM waits &lt;delay&gt; seconds before writing to the device. Use the delay with care because it stalls FHEM completely.
  729. </li>
  730. <li>stop<br>
  731. Disables read/write access to the device if set to 1. No data is written to the physical ECMD device. A read request always returns an undefined result.
  732. This attribute can be used to temporarily disable a device that is not available.
  733. </li>
  734. <li><a href="#verbose">verbose</a></li>
  735. </ul>
  736. <br><br>
  737. <b>Separators</b>
  738. <br><br>
  739. <i>When to use the split and partial attributes?</i><p>
  740. Set the <code>partial</code> attribute in combination with <code>reading ... match ...</code> in the <a href="#ECMDClassdef">class definition</a>, if you receive datagrams with responses which are broken into several transmissions, like <code>resp</code> followed by <code>onse\r\n</code>.<p>
  741. Set the <code>split</code> attribute if you
  742. receive several responses in one transmission, like <code>reply1\r\nreply2\r\n</code>.<p>
  743. <i>When to use the requestSeparator and responseSeparator attributes?</i><p>
  744. Set the <code>requestSeparator</code> attribute, if you want to send several requests in one command, with one transmission per request. The strings sent to the device for <code>set</code> and <code>get</code> commands
  745. as defined in the <a href="#ECMDClassdef">class definition</a> are broken down into several request/response
  746. interactions with the physical device. The request separator is not sent to the physical device.<p>
  747. Set the <code>responseSeparator</code> attribute to separate the responses received for a command
  748. broken down into several requests by means of a request separator. This is useful for easier postprocessing.<p>
  749. Example: you want to send the requests <code>request1</code> and <code>request2</code> in one command. The
  750. physical device would respond with <code>response1</code> and <code>response2</code> respectively for each
  751. of the requests. You set the request separator to \000 and the response separator to \001 and you define
  752. the command as <code>request1\000request2\000</code>. The command is broken down into <code>request1</code>
  753. and <code>request2</code>. <code>request1</code> is sent to the physical device and <code>response1</code>
  754. is received, followed by sending <code>request2</code> and receiving <code>response2</code>. The final
  755. result is <code>response1\001response2\001</code>.<p>
  756. You can think of this feature as of a macro. Splitting and partial matching is still done per single
  757. request/response within the macro.<p>
  758. <a name="ECMDDatagram"></a>
  759. <b>Datagram monitoring and matching</b>
  760. <br><br>
  761. Data to and from the physical device is processed as is. In particular, if you need to send a line feed you have to explicitely send a \n control character. On the other hand, control characters like line feeds are not stripped from the data received. This needs to be considered when defining a <a href="#ECMDClassdef">class definition</a>.<p>
  762. For debugging purposes, especially when designing a <a href="#ECMDClassdef">class definition</a>, it is advisable to turn traffic logging on. Use <code>attr myECMD logTraffic 3</code> to log all data to and from the physical device at level 3.<p>
  763. Datagrams and attribute values are logged with non-printable and control characters encoded as <a href="#ECMDcharcoding">here</a> followed by the octal representation in parantheses.
  764. Example: <code>#!foo\r\n (\043\041\146\157\157\015\012)</code>.<p>
  765. Data received from the physical device is processed as it comes in chunks. If for some reason a datagram from the device is split in transit, pattern matching and processing will most likely fail. You can use the <code>partial</code> attribute to make FHEM collect and recombine the chunks.
  766. <br><br>
  767. <a name="ECMDConnection"></a>
  768. <b>Connection error handling</b>
  769. <br><br>
  770. This modules handles unexpected disconnects of devices as follows (on Windows only for TCP connections):<p>
  771. Disconnects are detected if and only if data from the device in reply to data sent to the device cannot be received with at most two attempts. FHEM waits at most 3 seconds (or the time specified in the <code>timeout</code> attribute, see <a href="#ECMDattr">Attributes</a>). After the first failed attempt, the connection to the device is closed and reopened again. The state of the device
  772. is <code>failed</code>. Then the data is sent again to the device. If still no reply is received, the state of the device is <code>disconnected</code>, otherwise <code>opened</code>. You will have to fix the problem and then use <code>set myECMD reopen</code> to reconnect to the device.<p>
  773. Please design your class definitions in such a way that the double sending of data does not bite you in any case.
  774. <br><br>
  775. <a name="ECMDClassdef"></a>
  776. <b>Class definition</b>
  777. <br><br>
  778. The class definition for a logical ECMD device class is contained in a text file.
  779. The text file is made up of single lines. Empty lines and text beginning with #
  780. (hash) are ignored. Therefore make sure not to use hashes in commands.<br>
  781. The following commands are recognized in the device class definition:<br><br>
  782. <ul>
  783. <li><code>params &lt;parameter1&gt; [&lt;parameter2&gt; [&lt;parameter3&gt; ... ]]</code><br><br>
  784. Declares the names of the named parameters that must be present in the
  785. <a href="#ECMDDevicedefine">definition of the logical ECMD device</a>.
  786. <br><br>
  787. </li>
  788. <li><code>state &lt;reading&gt;</code><br><br>
  789. Normally, the state reading is set to the latest command or reading name followed
  790. by the value, if any. This command sets the state reading to the value of the
  791. named reading if and only if the reading is updated.<br><br>
  792. </li>
  793. <li><code>set &lt;commandname&gt; cmd { <a href="#perl">&lt;perl special&gt;</a> }</code><br>
  794. <code>get &lt;commandname&gt; cmd { <a href="#perl">&lt;perl special&gt;</a> }</code>
  795. <br><br>
  796. Declares a new set or get command <code>&lt;commandname&gt;</code>. If the user invokes the set or get command <code>&lt;commandname&gt;</code>, the string that results from the execution of the &lt;perl special&gt; is sent to the physical device.<p>
  797. A request separator (see <a href="#ECMDattr">Attributes</a>)
  798. can be used to split the command into chunks. This is required for sending multiple <a href="http://www.ethersex.de/index.php/ECMD">Ethersex commands</a> for one command in the class definition.
  799. The result string for the command is the
  800. concatenation of all responses received from the physical device, optionally with response separators
  801. (see <a href="#ECMDattr">Attributes</a>) in between.
  802. <br><br>
  803. </li>
  804. <li>
  805. <code>set &lt;commandname&gt; expect "&lt;regex&gt;"</code><br>
  806. <code>get &lt;commandname&gt; expect "&lt;regex&gt;"</code>
  807. <br><br>
  808. Declares what FHEM expects to receive after the execution of the get or set command <code>&lt;commandname&gt;</code>. <code>&lt;regex&gt;</code> is a Perl regular expression. The double quotes around the regular expression are mandatory and they are not part of the regular expression itself.
  809. <code>&lt;regex&gt;</code> must match the entire reply, as in <code>m/^&lt;regex&gt;$/</code>.
  810. Particularly, broken connections can only be detected if something is expected (see <a href="#ECMDConnection">Connection error handling</a>).
  811. <br><br>
  812. </li>
  813. <li>
  814. <code>set &lt;commandname&gt; postproc { <a href="#perl">&lt;perl special&gt;</a> }</code><br>
  815. <code>get &lt;commandname&gt; postproc { <a href="#perl">&lt;perl special&gt;</a> }</code>
  816. <br><br>
  817. Declares a postprocessor for the command <code>&lt;commandname&gt;</code>. The data received from the physical device in reply to the get or set command <code>&lt;commandname&gt;</code> is processed by the Perl code <code>&lt;perl command&gt;</code>. The perl code operates on <code>$_</code>. Make sure to return the result in <code>$_</code> as well. The result of the perl command is shown as the result of the get or set command.
  818. <br><br>
  819. </li>
  820. <li>
  821. <code>set &lt;commandname&gt; params &lt;parameter1&gt; [&lt;parameter2&gt; [&lt;parameter3&gt; ... ]]</code><br>
  822. <code>get &lt;commandname&gt; params &lt;parameter1&gt; [&lt;parameter2&gt; [&lt;parameter3&gt; ... ]]</code>
  823. <br><br>
  824. Declares the names of the named parameters that must be present in the
  825. set or get command <code>&lt;commandname&gt;</code></a>. Be careful not to use a parameter name that
  826. is already used in the device definition (see <code>params</code> above).
  827. <br><br>
  828. </li>
  829. <li>
  830. <code>reading &lt;reading&gt; match "&lt;regex&gt;"</code>
  831. <br><br>
  832. Declares a new reading named <code>&lt;reading&gt;</code>. A spontaneous data transmission from the physical device that matches the Perl regular expression <code>&lt;regex&gt;</code> is evaluated to become the value of the named reading. All ECMDDevice devices belonging to the ECMD device with readings with matching regular expressions will receive an update of the said readings.
  833. <code>&lt;regex&gt;</code> must match the entire reply, as in <code>m/^&lt;regex&gt;$/</code>.
  834. <br><br>
  835. </li>
  836. <li>
  837. <code>reading &lt;reading&gt; postproc { <a href="#perl">&lt;perl special&gt;</a> }</code>
  838. <br><br>
  839. Declares a postprocessor for the reading <code>&lt;reading&gt;</code>. The data received for the named reading is processed by the Perl code <code>&lt;perl command&gt;</code>. This works analogously to the <code>postproc</code> spec for set and get commands.
  840. <br><br>
  841. </li>
  842. </ul>
  843. The perl specials in the definitions above can
  844. contain macros:<br><br>
  845. <ul>
  846. <li>The macro <code>%NAME</code> will expand to the device name.</li>
  847. <li>The macro <code>%TYPE</code> will expand to the device type.</li>
  848. <li>The macro <code>%&lt;parameter&gt;</code> will expand to the
  849. current value of the named parameter. This can be either a parameter
  850. from the device definition or a parameter from the set or get
  851. command.</li>
  852. <li>The macro substitution occurs before perl evaluates the
  853. expression. It is a plain text substitution. Be careful not to use parameters with overlapping names like
  854. <code>%pin</code> and <code>%pin1</code>.</li>
  855. <li>If in doubt what happens, run the commands with loglevel 5 and
  856. inspect the log file.</li>
  857. </ul><br><br>
  858. The rules outlined in the <a href="#perl">documentation of perl specials</a>
  859. for the <code>&lt;perl command&gt</code> in the postprocessor definitions apply.
  860. <b>Note:</b> Beware of undesired side effects from e.g. doubling of semicolons!
  861. </ul>
  862. =end html
  863. =cut