00_TUL.pm 33 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297
  1. ##############################################
  2. # $Id: 00_TUL.pm 13037 2017-01-10 19:24:35Z andi291 $
  3. # ABU 20150916 removed print: simpleWriteDate, cleaned init
  4. # ABU 20150918 fixed deprecated warning, fixed warning related to hex-conversion in simple-write
  5. # ABU 20151123 added error-label in getGroup. Responsible for error-handling, if knxd is not accesible
  6. # ABU 20151213 changed message-check in decode_tpuart() to avoid ignore while receiving repeated messages
  7. # ABU 20160308 remoced set, get. Changed loglevel to verbose. Added KNX/EIB-Split. Added EIB-backward-compatibility.
  8. # ABU 20160309 fixed log2
  9. # ABU 20160310 repaired dispatch events - inform EIB, only is useEIB is set
  10. # ABU 20160515 removed compatibility flag for EIB
  11. # ABU 20160516 added log entry for non-compatibility of tul
  12. # ABU 20160613 changed log entry for startup
  13. # ABU 20161108 added knxd. Added doku as well. Added summary. Treat it like eibd. See thread #58375
  14. # ABU 20170102 fixed write-mechanism, added mod for extended adressing (thx to its2bit)
  15. # ABU 20170110 removed mod for extended adressing
  16. package main;
  17. use strict;
  18. use warnings;
  19. use Time::HiRes qw(gettimeofday);
  20. sub TUL_Attr(@);
  21. sub TUL_Clear($);
  22. sub TUL_Parse($$$$$);
  23. sub TUL_Read($);
  24. sub TUL_Ready($);
  25. sub TUL_Write($$$);
  26. sub TUL_OpenDev($$);
  27. sub TUL_CloseDev($);
  28. sub TUL_SimpleWrite(@);
  29. sub TUL_SimpleRead($);
  30. sub TUL_Disconnected($);
  31. sub TUL_Shutdown($);
  32. my %gets = ( # Name, Data to send to the TUL, Regexp for the answer
  33. "raw" => ["r", '.*'],
  34. );
  35. my %sets = (
  36. "raw" => "",
  37. );
  38. my $clients = ":KNX:EIB:";
  39. my %matchList = (
  40. "2:KNX" => "^C.*",
  41. "3:EIB" => "^B.*",
  42. );
  43. sub
  44. TUL_Initialize($)
  45. {
  46. my ($hash) = @_;
  47. # Provider
  48. $hash->{ReadFn} = "TUL_Read";
  49. $hash->{WriteFn} = "TUL_Write";
  50. $hash->{ReadyFn} = "TUL_Ready";
  51. # Normal devices
  52. $hash->{DefFn} = "TUL_Define";
  53. $hash->{UndefFn} = "TUL_Undef";
  54. $hash->{StateFn} = "TUL_SetState";
  55. $hash->{AttrFn} = "TUL_Attr";
  56. #$hash->{AttrList}= "do_not_notify:1,0 dummy:1,0 " .
  57. # "showtime:1,0 model:TUL loglevel:0,1,2,3,4,5,6 ";
  58. $hash->{AttrList}= "do_not_notify:1,0 " .
  59. "dummy:1,0 " .
  60. "showtime:1,0 " .
  61. "verbose:0,1,2,3,4,5 " .
  62. "useEIB:1,0 ";
  63. $hash->{ShutdownFn} = "TUL_Shutdown";
  64. }
  65. #####################################
  66. sub
  67. TUL_Define($$)
  68. {
  69. my ($hash, $def) = @_;
  70. my @a = split("[ \t][ \t]*", $def);
  71. if(@a < 4)
  72. {
  73. my $msg = "wrong syntax: define <name> TUL <devicename> <device addr> [<line def in hex>]";
  74. Log (2, $msg);
  75. return $msg;
  76. }
  77. TUL_CloseDev($hash);
  78. my $name = $a[0];
  79. my $dev = $a[2];
  80. my $devaddr = tul_str2hex($a[3]);
  81. my $linedef = substr(tul_str2hex($a[4]),0,2) if(@a > 4);
  82. if($dev eq "none")
  83. {
  84. Log3 ($name, 1, "device is none, commands will be echoed only");
  85. $attr{$name}{dummy} = 1;
  86. return undef;
  87. }
  88. #Set attributes in order to control backward-compatibility
  89. #$attr{$name}{useEIB} = 1;
  90. #Log3 ($name, 1, "Using EIB is deprecated. Please migrate to KNX soon. Module 10_EIB is not maintained any longer.") if (AttrVal($name, "useEIB", 0) =~ m/1/);
  91. #Log3 ($name, 0, "Using EIB is deprecated. Please migrate to KNX soon. Module 10_EIB is not maintained any longer. If you still want to use the module EIB, please set the attribute useEIB to 1.") if (AttrVal($name, "useEIB", 0) =~ m/1/);
  92. Log3 ($name, 0, "Using EIB is deprecated. Please migrate to KNX soon. Module 10_EIB is not maintained any longer. If you still want to use the module EIB,
  93. please set the attribute useEIB to 1 within the tul-device. Please keep in mind, that 10_KNX has a changed syntax regarding the definition, arguments and readings. Please refer to the commandref.
  94. As well 10_EIB and 10_KNX are compatible to daemon eibd and knxd.") if (AttrVal($name, "useEIB", 0) =~ m/0/);
  95. $hash->{DeviceName} = $dev;
  96. $hash->{DeviceAddress} = $devaddr;
  97. $hash->{Clients} = $clients;
  98. $hash->{MatchList} = \%matchList;
  99. $hash->{AckLineDef}= $linedef;
  100. my $ret = TUL_OpenDev($hash, 0);
  101. return $ret;
  102. }
  103. #####################################
  104. sub
  105. TUL_Undef($$)
  106. {
  107. my ($hash, $arg) = @_;
  108. my $name = $hash->{NAME};
  109. foreach my $d (sort keys %defs)
  110. {
  111. if(defined($defs{$d}) && defined($defs{$d}{IODev}) && $defs{$d}{IODev} == $hash)
  112. {
  113. my $lev = ($reread_active ? 4 : 2);
  114. Log(GetLogLevel($name,$lev), "deleting port for $d");
  115. delete $defs{$d}{IODev};
  116. }
  117. }
  118. TUL_CloseDev($hash);
  119. return undef;
  120. }
  121. #####################################
  122. sub TUL_Shutdown($)
  123. {
  124. my ($hash) = @_;
  125. TUL_CloseDev($hash);
  126. return undef;
  127. }
  128. #####################################
  129. sub
  130. TUL_SetState($$$$)
  131. {
  132. my ($hash, $tim, $vt, $val) = @_;
  133. return undef;
  134. }
  135. sub
  136. TUL_Clear($)
  137. {
  138. my $hash = shift;
  139. #Clear the pipe
  140. #TUL has no pipe....
  141. }
  142. #####################################
  143. sub
  144. TUL_DoInit($)
  145. {
  146. my $hash = shift;
  147. my $name = $hash->{NAME};
  148. my $err;
  149. TUL_Clear($hash);
  150. # send any initializing request if needed
  151. # TODO move to device init
  152. return 1 unless openGroupSocket($hash);
  153. # reset buffer
  154. purgeReceiverBuf($hash);
  155. $hash->{STATE} = "Initialized" if(!$hash->{STATE});
  156. # Reset the counter
  157. delete($hash->{XMIT_TIME});
  158. delete($hash->{NR_CMD_LAST_H});
  159. return undef;
  160. }
  161. #####################################
  162. sub
  163. TUL_Write($$$)
  164. {
  165. my ($hash,$fn,$msg) = @_;
  166. my $name = $hash->{NAME};
  167. return if(!defined($fn));
  168. #Discard message, if not set to backward-compatibility
  169. if ((AttrVal($name, "useEIB", 0) =~ m/0/) and ($fn =~ m/\^B/))
  170. {
  171. Log3 ($name, 0, "EIB is no longer supported. Message discarded.");
  172. return;
  173. }
  174. Log3 ($name, 5, "sending $fn$msg");
  175. my $bstring = "$fn$msg";
  176. TUL_SimpleWrite($hash, $bstring);
  177. }
  178. #####################################
  179. # called from the global loop, when the select for hash->{FD} reports data
  180. sub
  181. TUL_Read($)
  182. {
  183. my ($hash) = @_;
  184. #reset the refused flag, so we can check if a telegram was refused
  185. # and therefor we did not get a response
  186. $hash->{REFUSED} = undef;
  187. my $buf = TUL_SimpleRead($hash);
  188. my $name = $hash->{NAME};
  189. # check if refused
  190. if(defined($hash->{REFUSED}))
  191. {
  192. Log3 ($name, 3,"TUL $name refused message: $hash->{REFUSED}");
  193. $hash->{REFUSED} = undef;
  194. return "";
  195. }
  196. ###########
  197. # Lets' try again: Some drivers return len(0) on the first read...
  198. if(defined($buf) && length($buf) == 0)
  199. {
  200. $buf = TUL_SimpleRead($hash);
  201. }
  202. if(!defined($buf) || length($buf) == 0)
  203. {
  204. TUL_Disconnected($hash);
  205. return "";
  206. }
  207. #place KNX-Message
  208. TUL_Parse($hash, $hash, $name, "B".$buf, $hash->{initString}) if (AttrVal($name, "useEIB", 0) =~ m/1/);
  209. #place EIB-Message
  210. TUL_Parse($hash, $hash, $name, "C".$buf, $hash->{initString});
  211. }
  212. sub
  213. TUL_Parse($$$$$)
  214. {
  215. my ($hash, $iohash, $name, $rmsg, $initstr) = @_;
  216. # there is nothing specal to do at the moment.
  217. # just dispatch
  218. my $dmsg = $rmsg;
  219. Log3 ($name, 4, "$name: $dmsg");
  220. $hash->{"${name}_MSGCNT"}++;
  221. $hash->{"${name}_TIME"} = TimeNow();
  222. $hash->{RAWMSG} = $rmsg;
  223. my %addvals = (RAWMSG => $rmsg);
  224. Dispatch($hash, $dmsg, \%addvals);
  225. }
  226. #####################################
  227. sub
  228. TUL_Ready($)
  229. {
  230. my ($hash) = @_;
  231. return TUL_OpenDev($hash, 1) if($hash->{STATE} eq "disconnected");
  232. # This is relevant for windows/USB only
  233. my $po = $hash->{USBDev};
  234. my ($BlockingFlags, $InBytes, $OutBytes, $ErrorFlags) = $po->status;
  235. return ($InBytes>0);
  236. }
  237. ########################
  238. sub
  239. TUL_SimpleWrite(@)
  240. {
  241. my ($hash, $msg) = @_;
  242. return if(!$hash);
  243. # Msg must have the format B(w,r,p)g1g2g3v....
  244. # w-> write, r-> read, p-> reply
  245. # g1,g2,g3 are the hex parts of the group name
  246. # v is a simple (1 Byte) or complex value (n bytes)
  247. # For eibd we need a more elaborate structure
  248. if($msg =~ /^[BC](.)(.{4})(.*)$/)
  249. {
  250. my $eibmsg;
  251. if($1 eq "w")
  252. {
  253. $eibmsg->{'type'} = 'write';
  254. }
  255. elsif ($1 eq "r")
  256. {
  257. $eibmsg->{'type'} = 'read';
  258. }
  259. elsif ($1 eq "p")
  260. {
  261. $eibmsg->{'type'} = 'reply';
  262. }
  263. $eibmsg->{'dst'} = $2;
  264. my $hexvalues = $3;
  265. #The array has to have a given length. During Hex-conversion Trailing
  266. #0 are recognizes for warnings.
  267. #Therefore we backup the length, trim, and reappend the 0
  268. #
  269. #save length and trim right side
  270. my $strLen = length ($hexvalues) / 2;
  271. $hexvalues =~ s/\s+$//;
  272. #convert hex-string to array with dezimal values
  273. my @data = map hex($_), $hexvalues =~ /(..)/g;
  274. #re-append 0x00
  275. for (my $i=0; $strLen - scalar @data; $i++)
  276. {
  277. push (@data, 0);
  278. }
  279. # check: first byte is only allowed to contain data in the lower 6bits
  280. # to make sure all is fine, we mask the first byte
  281. $data[0] = $data[0] & 0x3f if(defined($data[0]));
  282. $eibmsg->{'data'} = \@data;
  283. sendGroup($hash, $eibmsg);
  284. }
  285. else
  286. {
  287. Log3 ($hash->{NAME}, 1,"Could not parse message $msg");
  288. return undef;
  289. }
  290. select(undef, undef, undef, 0.001);
  291. }
  292. ########################
  293. sub
  294. TUL_SimpleRead($)
  295. {
  296. my ($hash) = @_;
  297. my $name = $hash->{NAME};
  298. my $msg = getGroup($hash);
  299. if(!defined($msg))
  300. {
  301. Log3 ($name, 4,"No data received.") ;
  302. return undef;
  303. }
  304. my $type = $msg->{'type'};
  305. my $dst = $msg->{'dst'};
  306. my $src = $msg->{'src'};
  307. my @bindata = @{$msg->{'data'}};
  308. my $data = "";
  309. # convert bin data to hex
  310. foreach my $c (@bindata)
  311. {
  312. $data .= sprintf ("%02x", $c);
  313. }
  314. Log3 ($name, 5, "SimpleRead msg.type: $type, msg.src: $msg->{'src'}, msg.dst: $msg->{'dst'}");
  315. Log3 ($name, 5, "SimpleRead data: $data");
  316. # we will build a string like:
  317. # Bs1s2s3(w|r|p)g1g2g3v
  318. # s -> src
  319. my $buf;
  320. #$buf = "C$src";
  321. $buf = $src;
  322. if($type eq "write")
  323. {
  324. $buf .= "w";
  325. }
  326. elsif ($type eq "read")
  327. {
  328. $buf .= "r";
  329. }
  330. else
  331. {
  332. $buf .= "p";
  333. }
  334. $buf .= $dst;
  335. $buf .= $data;
  336. Log(4,"SimpleRead: $buf\n");
  337. return $buf;
  338. }
  339. ########################
  340. sub
  341. TUL_CloseDev($)
  342. {
  343. my ($hash) = @_;
  344. my $name = $hash->{NAME};
  345. my $dev = $hash->{DeviceName};
  346. return if(!$dev);
  347. if($hash->{TCPDev})
  348. {
  349. $hash->{TCPDev}->close();
  350. delete($hash->{TCPDev});
  351. }
  352. elsif($hash->{USBDev})
  353. {
  354. $hash->{USBDev}->close() ;
  355. delete($hash->{USBDev});
  356. }
  357. delete($selectlist{"$name.$dev"});
  358. delete($readyfnlist{"$name.$dev"});
  359. delete($hash->{FD});
  360. }
  361. ########################
  362. sub
  363. TUL_OpenDev($$)
  364. {
  365. my ($hash, $reopen) = @_;
  366. my $dev = $hash->{DeviceName};
  367. my $name = $hash->{NAME};
  368. my $po;
  369. $hash->{PARTIAL} = "";
  370. Log 3, "TUL opening $name device $dev" if(!$reopen);
  371. # eibd:host[:port]
  372. #if($dev =~ m/^(eibd):(.+)$/)
  373. if($dev =~ m/^(eibd|knxd):(.+)$/)
  374. {
  375. my $host = $2;
  376. my $port = 6720;
  377. #host:port
  378. if($host =~ m/^(.+):([0-9]+)$/)
  379. {
  380. $host = $1;
  381. $port = $2;
  382. }
  383. # This part is called every time the timeout (5sec) is expired _OR_
  384. # somebody is communicating over another TCP connection. As the connect
  385. # for non-existent devices has a delay of 3 sec, we are sitting all the
  386. # time in this connect. NEXT_OPEN tries to avoid this problem.
  387. return if($hash->{NEXT_OPEN} && time() < $hash->{NEXT_OPEN});
  388. my $conn = IO::Socket::INET->new(PeerAddr => $host, PeerPort => $port,Proto => 'tcp');
  389. if($conn)
  390. {
  391. delete($hash->{NEXT_OPEN})
  392. }
  393. else
  394. {
  395. Log3 ($name, 3, "Can't connect to $dev: $!") if(!$reopen);
  396. $readyfnlist{"$name.$dev"} = $hash;
  397. $hash->{STATE} = "disconnected";
  398. $hash->{NEXT_OPEN} = time()+60;
  399. return "";
  400. }
  401. $hash->{DevType} = 'EIBD';
  402. $hash->{TCPDev} = $conn;
  403. $hash->{FD} = $conn->fileno();
  404. delete($readyfnlist{"$name.$dev"});
  405. $selectlist{"$name.$dev"} = $hash;
  406. }
  407. # tpuart:ttydev[@baudrate] / USB/Serial device
  408. elsif ($dev =~ m/^(tul|tpuart):(.+)$/)
  409. {
  410. my $dev = $2;
  411. my $baudrate;
  412. ($dev, $baudrate) = split("@", $dev);
  413. $baudrate = 19200 if(!$baudrate); # fix for TUL board
  414. if ($^O=~/Win/)
  415. {
  416. require Win32::SerialPort;
  417. $po = new Win32::SerialPort ($dev);
  418. } else
  419. {
  420. require Device::SerialPort;
  421. $po = new Device::SerialPort ($dev);
  422. }
  423. if(!$po)
  424. {
  425. return undef if($reopen);
  426. Log3 ($name, 3, "Can't open $dev: $!");
  427. $readyfnlist{"$name.$dev"} = $hash;
  428. $hash->{STATE} = "disconnected";
  429. return "";
  430. }
  431. $hash->{DevType} = 'TPUART';
  432. $hash->{USBDev} = $po;
  433. if( $^O =~ /Win/ )
  434. {
  435. $readyfnlist{"$name.$dev"} = $hash;
  436. }
  437. else
  438. {
  439. $hash->{FD} = $po->FILENO;
  440. delete($readyfnlist{"$name.$dev"});
  441. $selectlist{"$name.$dev"} = $hash;
  442. }
  443. # assumed always available
  444. if($baudrate)
  445. {
  446. $po->reset_error();
  447. Log3 ($name, 3, "TUL setting $name baudrate to $baudrate");
  448. $po->baudrate($baudrate);
  449. $po->databits(8);
  450. $po->parity('even');
  451. $po->stopbits(1);
  452. $po->handshake('none');
  453. # This part is for some Linux kernel versions which has strange default
  454. # settings. Device::SerialPort is nice: if the flag is not defined for your
  455. # OS then it will be ignored.
  456. $po->stty_icanon(0);
  457. #$po->stty_parmrk(0); # The debian standard install does not have it
  458. $po->stty_icrnl(0);
  459. $po->stty_echoe(0);
  460. $po->stty_echok(0);
  461. $po->stty_echoctl(0);
  462. # Needed for some strange distros
  463. $po->stty_echo(0);
  464. $po->stty_icanon(0);
  465. $po->stty_isig(0);
  466. $po->stty_opost(0);
  467. $po->stty_icrnl(0);
  468. }
  469. $po->write_settings;
  470. }
  471. # No more devices supported now
  472. else
  473. {
  474. Log3 ($name, 1, "$dev protocol is not supported");
  475. }
  476. if($reopen)
  477. {
  478. Log3 ($name, 1, "TUL $dev reappeared ($name)");
  479. }
  480. else
  481. {
  482. Log3 ($name, 3, "TUL device opened");
  483. }
  484. $hash->{STATE}=""; # Allow InitDev to set the state
  485. my $ret = TUL_DoInit($hash);
  486. if($ret)
  487. {
  488. TUL_CloseDev($hash);
  489. Log 1, "Cannot init $dev, ignoring it";
  490. }
  491. DoTrigger($name, "CONNECTED") if($reopen);
  492. return $ret;
  493. }
  494. ########################
  495. sub
  496. TUL_Disconnected($)
  497. {
  498. my $hash = shift;
  499. my $dev = $hash->{DeviceName};
  500. my $name = $hash->{NAME};
  501. return if(!defined($hash->{FD})); # Already deleted or RFR
  502. Log3 ($name, 1, "$dev disconnected, waiting to reappear");
  503. TUL_CloseDev($hash);
  504. $readyfnlist{"$name.$dev"} = $hash; # Start polling
  505. $hash->{STATE} = "disconnected";
  506. # Without the following sleep the open of the device causes a SIGSEGV,
  507. # and following opens block infinitely. Only a reboot helps.
  508. sleep(5);
  509. DoTrigger($name, "DISCONNECTED");
  510. }
  511. ########################
  512. sub
  513. TUL_Attr(@)
  514. {
  515. my @a = @_;
  516. return undef;
  517. }
  518. ####################################################################################
  519. ####################################################################################
  520. #
  521. #
  522. # The following section has been inspired by the EIB module from MrHouse project
  523. # written by Peter Sj?din peter@sjodin.net and Mike Pieper eibdmh@pieper-family.de
  524. # Code has been mainly changed to fit to the FHEM framework by Maz Rashid
  525. # (to be honest the code had to be reworked very intensively due the lack of code quality)
  526. #
  527. # Utility functions
  528. sub tul_hex2addr
  529. {
  530. my $str = lc($_[0]);
  531. if ($str =~ /([0-9a-f])([0-9a-f])([0-9a-f]{2})/)
  532. {
  533. return (hex($1) << 11) | (hex($2) << 8) | hex($3);
  534. }
  535. else
  536. {
  537. Log(3,"Bad EIB address string: \'$str\'\n");
  538. return;
  539. }
  540. }
  541. sub tul_addr2hex
  542. {
  543. my $a = $_[0];
  544. my $b = $_[1]; # 1 if local (group) address, else physical address
  545. my $str ;
  546. if ($b == 1)
  547. {
  548. #logical address used
  549. #old, short-syntax
  550. $str = sprintf "%01x%01x%02x", ($a >> 11) & 0xf, ($a >> 8) & 0x7, $a & 0xff;
  551. #extended adress-range
  552. #$str = sprintf "%02x%01x%02x", ($a >> 11) & 0x1f, ($a >> 8) & 0x7, $a & 0xff;
  553. }
  554. else
  555. {
  556. #physical address used
  557. $str = sprintf "%01x%01x%02x", $a >> 12, ($a >> 8) & 0xf, $a & 0xff;
  558. }
  559. return $str;
  560. }
  561. sub tul_str2hex
  562. {
  563. my $str = $_[0];
  564. if ($str =~ /(\d+)\/(\d+)\/(\d+)/)
  565. { # logical address
  566. my $hex = sprintf("%01x%01x%02x",$1,$2,$3);
  567. return $hex;
  568. }
  569. elsif ($str =~ /(\d+)\.(\d+)\.(\d+)/)
  570. { # physical address
  571. my $hex = sprintf("%01x%01x%02x",$1,$2,$3);
  572. return $hex;
  573. }
  574. }
  575. # For mapping between APCI symbols and values
  576. my @apcicodes = ('read', 'reply', 'write');
  577. my %apcivalues = ('read' => 0, 'reply' => 1, 'write' => 2,);
  578. # decode: unmarshall a string with an EIB message into a hash
  579. # The hash has the follwing fields:
  580. # - type: APCI (symbolic value)
  581. # - src: source address
  582. # - dst: destiniation address
  583. # - data: array of integers; one for each byte of data
  584. sub decode_eibd($)
  585. {
  586. my ($buf) = @_;
  587. my $drl = 0xe1; # dummy value
  588. my %msg;
  589. my @data;
  590. my ($src, $dst,$bytes) = unpack("nnxa*", $buf);
  591. my $apci;
  592. $apci = vec($bytes, 3, 2);
  593. # mask out apci bits, so we can use the whole byte as data:
  594. vec($bytes, 3, 2) = 0;
  595. if ($apci >= 0 && $apci <= $#apcicodes)
  596. {
  597. $msg{'type'} = $apcicodes[$apci];
  598. }
  599. else
  600. {
  601. $msg{'type'} = 'apci ' . $apci;
  602. }
  603. $msg{'src'} = tul_addr2hex($src,0);
  604. $msg{'dst'} = tul_addr2hex($dst,1);
  605. @data = unpack ("C" . length($bytes), $bytes);
  606. my $datalen = @data;
  607. Log (5, "decode_eibd byte len: " . length($bytes) . " array size: $datalen");
  608. # in case of data len > 1, the first byte (the one with apci) seems not to be used
  609. # and only the following byte are of interest.
  610. if($datalen>1)
  611. {
  612. shift @data;
  613. }
  614. $msg{'data'} = \@data;
  615. return \%msg;
  616. }
  617. # encode: marshall a hash into a EIB message string
  618. sub encode_eibd($)
  619. {
  620. my ($mref) = @_;
  621. my @msg;
  622. my $APCI;
  623. my @data;
  624. $APCI = $apcivalues{$mref->{'type'}};
  625. if (!(defined $APCI))
  626. {
  627. Log(3,"Bad EIB message type $mref->{'type'}\n");
  628. return;
  629. }
  630. @data = @{$mref->{'data'}};
  631. @data = (0x0) if(!@data || !defined($data[0])); #make sure data has at least one element
  632. #@data = (0x0) if(!(defined @data) || !(defined $data[0])); #make sure data has at least one element
  633. my $datalen = @data;
  634. Log(5,"encode_eibd dst: $mref->{'dst'} apci: $APCI datalen: $datalen data: @data");
  635. @msg = (
  636. tul_hex2addr( $mref->{'dst'}), # Destination address
  637. 0x0 | ($APCI >> 2), # TPDU type, Sequence no, APCI (msb)
  638. (($APCI & 0x3) << 6) | $data[0],
  639. );
  640. if ($datalen > 1)
  641. {
  642. shift(@data);
  643. push @msg, @data;
  644. }
  645. return @msg;
  646. }
  647. # decode: unmarshall a string with an EIB telegram into a hash
  648. # A typical telegram looks like: bc110a0002e100813a
  649. # checks:
  650. # - 1st byte must have at least the bits $90 set. (otherwise it is false or a repeat)
  651. # - 2nd/3rd byte are the source (1.1.10)
  652. # - 4th/5th byte are the dst group (0/0/2)
  653. # - 6th byte (msb if 1 dst is group, else a phys. address )
  654. # - low nibble is length of data (counting from 0) (->2)
  655. # - 7th byte is ignored
  656. # - 8th byte is the command / short data byte
  657. # - if 8th byte >>6 is 0 -> read
  658. # - is 2 -> write
  659. # - is 1 -> reply
  660. # - if length is 2 -> 8th byte & 0x3F is data
  661. # otherwise data start after 8th byte
  662. # - last byte is the crc (ignored)
  663. # The hash has the follwing fields:
  664. # - type: APCI (symbolic value)
  665. # - src: source address
  666. # - dst: destiniation address
  667. # - data: array of integers; one for each byte of data
  668. sub decode_tpuart($)
  669. {
  670. my ($buf) = @_;
  671. my ($ctrl,$src, $dst, $routingcnt,$cmd, $bytes) = unpack("CnnCxCa*", $buf);
  672. my $drl = $routingcnt >>7;
  673. my $len = ($routingcnt & 0x0F) +1;
  674. #if(($ctrl & 0xB0)!=0xB0)
  675. if(($ctrl & 0x90)!=0x90)
  676. {
  677. #Log(3,"Control Byte " . sprintf("0x%02x",$ctrl) . " does not match expected mask 0xB0");
  678. Log(3,"Control Byte " . sprintf("0x%02x",$ctrl) . " does not match expected mask 2x1001nnnn");
  679. return undef;
  680. }
  681. Log(5,"msg cmd: " . sprintf("0x%02x",$cmd) ." datalen: $len");
  682. my $apci = ($cmd >> 6) & 0x0F;
  683. if($len == 2)
  684. { # 1 byte data
  685. $bytes = pack("C",$cmd & 0x3F);
  686. }
  687. Log(5,"msg cmd: " . sprintf("0x%02x",$cmd) ." datalen: $len apci: $apci");
  688. my %msg;
  689. my @data;
  690. if ($apci >= 0 && $apci <= $#apcicodes)
  691. {
  692. $msg{'type'} = $apcicodes[$apci];
  693. }
  694. else
  695. {
  696. $msg{'type'} = 'apci ' . $apci;
  697. }
  698. $msg{'src'} = tul_addr2hex($src,0);
  699. $msg{'dst'} = tul_addr2hex($dst,$drl);
  700. @data = unpack ("C" . length($bytes), $bytes);
  701. my $datalen = @data;
  702. Log(5, "decode_tpuart byte len: " . length($bytes) . " array size: $datalen");
  703. $msg{'data'} = \@data;
  704. return \%msg;
  705. }
  706. # encode: marshall a hash into a EIB message string
  707. sub encode_tpuart($)
  708. {
  709. my ($mref) = @_;
  710. my @msg;
  711. my $APCI;
  712. my @data;
  713. $APCI = $apcivalues{$mref->{'type'}};
  714. if (!(defined $APCI))
  715. {
  716. Log(3,"Bad EIB message type $mref->{'type'}\n");
  717. return;
  718. }
  719. @data = @{$mref->{'data'}};
  720. my $datalen = @data;
  721. if($datalen > 14)
  722. {
  723. Log(3,"Bad EIB message length $datalen\n");
  724. return;
  725. }
  726. Log(5,"encode_tpuart dst: $mref->{'dst'} apci: $APCI datalen: $datalen data: @data");
  727. @msg = (
  728. 0xBC, # EIB ctrl byte
  729. tul_hex2addr($mref->{'src'}), # src address
  730. tul_hex2addr( $mref->{'dst'}), # Destination address
  731. 0xE0 | $datalen, # Routing counter + data len
  732. 0x00,
  733. (($APCI & 0x3) << 6) | $data[0],
  734. );
  735. if ($datalen > 1)
  736. {
  737. shift(@data);
  738. push @msg, @data;
  739. }
  740. # convert to byte array
  741. my $arraystr = pack("CnnC*",@msg);
  742. @msg = unpack("C*",$arraystr);
  743. my @tpuartmsg;
  744. # calculate crc
  745. my $crc = 0xFF;
  746. my $i;
  747. for($i=0; $i<@msg;$i++)
  748. {
  749. $crc ^= $msg[$i];
  750. push @tpuartmsg,(0x80 | $i);
  751. push @tpuartmsg, $msg[$i];
  752. }
  753. push @tpuartmsg,(0x40 | $i);
  754. push @tpuartmsg,$crc;
  755. return @tpuartmsg;
  756. }
  757. #
  758. # eibd communication part
  759. #
  760. # Functions four group socket communication
  761. # Open a group socket for group communication
  762. # openGroupSocket SOCK
  763. sub openGroupSocket($)
  764. {
  765. my $hash = shift;
  766. ## only needed if EIBD
  767. if($hash->{DevType} eq 'EIBD')
  768. {
  769. my @msg = (0x0026,0x0000,0x00); # EIB_OPEN_GROUPCON
  770. sendRequest ($hash, pack "nnC" ,@msg);
  771. goto error unless my $answer = getRequest($hash);
  772. my $head = unpack ("n", $answer);
  773. goto error unless $head == 0x0026;
  774. }
  775. return 1;
  776. error:
  777. print "openGroupSocket failed\n";
  778. return undef;
  779. }
  780. # Send group data
  781. # sendGroup Hash DEST DATA
  782. sub sendGroup($$)
  783. {
  784. my ($hash,$msgref) = @_;
  785. my $dst = $msgref->{'dst'};
  786. my $src = $hash->{DeviceAddress};
  787. $msgref->{'src'} = $src;
  788. if($hash->{DevType} eq 'EIBD')
  789. {
  790. my @encmsg = encode_eibd($msgref);
  791. Log(5,"SendGroup: dst: $dst, msg: @encmsg \n");
  792. my @msg = (0x0027); # EIB_GROUP_PACKET
  793. push @msg, @encmsg;
  794. sendRequest($hash, pack("nnCC*", @msg));
  795. }
  796. elsif($hash->{DevType} eq 'TPUART')
  797. {
  798. my @encmsg = encode_tpuart($msgref);
  799. Log(5,"SendGroup: dst: $dst, msg: @encmsg \n");
  800. sendRequest($hash, pack("C*", @encmsg));
  801. my $response = getRequestFixLength($hash,($#encmsg + 1)/2+1);
  802. }
  803. return 1;
  804. }
  805. # will read as much byte as exists at the
  806. # serial buffer.
  807. sub purgeReceiverBuf($)
  808. {
  809. my ($hash) = @_;
  810. if($hash->{DevType} eq 'TPUART')
  811. {
  812. Log(5,"purging receiver buffer ");
  813. my $data = undef;
  814. do
  815. {
  816. my(undef,$data) = $hash->{USBDev}->read(100);
  817. Log(5,"purging packet: ". unpack("H*",$data) . "\n") if(defined($data) and length($data)>0);
  818. } while(defined($data) and length($data)>0)
  819. }
  820. }
  821. sub getRequestFixLength($$)
  822. {
  823. my ($hash, $len) = @_;
  824. if($hash->{DevType} eq 'TPUART')
  825. {
  826. Log(5,"waiting to receive $len bytes ...");
  827. my $buf = "";
  828. while(length($buf)<$len)
  829. {
  830. #select(undef,undef,undef,0.5);
  831. my (undef,$data) = $hash->{USBDev}->read($len-length($buf));
  832. Log(5,"Received fixlen packet: ". unpack("H*",$data) . "\n") if(defined($data) and length($data)>0);
  833. $buf .= $data if(defined($data));
  834. #Log(5,"buf len: " . length($buf) . " expected: $len");
  835. # TODO: if we are longer than 5 seconds here, we should reset
  836. }
  837. # # we got more than needed
  838. if(length($buf)>$len)
  839. {
  840. #check if this is ok
  841. my $remainpart = substr($buf,$len);
  842. $hash->{PARTIAL} .= $remainpart;
  843. $buf = substr($buf,0,$len);
  844. Log(5,"we got too much.. buf(" .unpack("H*",$buf).") remainingpart(" .unpack("H*",$remainpart).")");
  845. }
  846. Log(5,"getRequest len: $len packet: ". unpack("H*",$buf) . "\n");
  847. return $buf;
  848. }
  849. return undef;
  850. }
  851. # Receive group data
  852. # getGroup hash
  853. sub getGroup($)
  854. {
  855. my $hash = shift;
  856. if($hash->{DevType} eq 'EIBD')
  857. {
  858. goto error unless my $buf = getRequest($hash);
  859. my ($head, $data) = unpack ("na*", $buf);
  860. goto error unless $head == 0x0027;
  861. return decode_eibd($data);
  862. }
  863. elsif($hash->{DevType} eq 'TPUART')
  864. {
  865. my $ackdst = $hash->{AckLineDef};
  866. my $buf = $hash->{PARTIAL};
  867. my $reqlen = 8;
  868. my $telegram;
  869. do
  870. {
  871. my $data = getRequestFixLength($hash,$reqlen-length($buf)) if($reqlen>length($buf));
  872. if(length($buf)==0 && (!defined($data)||length($data)==0))
  873. {
  874. Log(5,"read fix length delivered no data.");
  875. return undef;
  876. }
  877. $buf .= $data if(defined($data));
  878. # check that control byte is correct
  879. my $ctrl = unpack("C",$buf) if(length($buf)>0);
  880. if(defined($ctrl) && ($ctrl&0x40) )
  881. {
  882. $buf = substr($buf,1);
  883. $hash->{PARTIAL} = $buf;
  884. Log(5,"TPUART RSP " . sprintf("0x%02x",$ctrl) ." ignored.");
  885. return undef;
  886. }
  887. if(length($buf)>5)
  888. {
  889. my $routingcnt = unpack("xxxxxC", $buf);
  890. $reqlen = ($routingcnt & 0x0F)+8;
  891. Log(5,"receiving telegram with len: $reqlen");
  892. }
  893. if($reqlen <= length($buf))
  894. {
  895. $telegram = substr($buf,0,$reqlen-1);
  896. $buf = substr($buf,$reqlen);
  897. }
  898. }
  899. while(!defined($telegram));
  900. Log(5, "Telegram: (".length($telegram)."): " . unpack("H*",$telegram));
  901. Log(5, "Buf: (".length($buf)."): " . unpack("H*",$buf));
  902. $hash->{PARTIAL} = $buf;
  903. my $msg = decode_tpuart($telegram);
  904. #check if we refused a telegram (i.e. repeats)
  905. $hash->{REFUSED} = unpack("H*",$telegram) if(!defined($msg));
  906. # We are always too late for Ack
  907. # if(defined($msg) && (substr($msg->{'dst'},0,2) eq $ackdst))
  908. # {
  909. # # ACK
  910. # sendRequest($hash,pack('C',0x11));
  911. # Log(5,"Ack!");
  912. # }
  913. return $msg;
  914. }
  915. Log(2,"DevType $hash->{DevType} not supported for getGroup\n");
  916. return undef;
  917. error:
  918. print "seems like eibd not connected\n";
  919. return undef;
  920. }
  921. # Gets a request from eibd
  922. # DATA = getRequest SOCK
  923. sub getRequest($)
  924. {
  925. my $hash = shift;
  926. my ($data);
  927. if($hash->{TCPDev} && $hash->{DevType} eq 'EIBD')
  928. {
  929. goto error unless sysread($hash->{TCPDev}, $data, 2);
  930. my $size = unpack ("n", $data);
  931. goto error unless sysread($hash->{TCPDev}, $data, $size);
  932. Log(5,"Received packet: ". unpack("H*",$data) . "\n");
  933. return $data;
  934. }
  935. elsif($hash->{USBDev}) {
  936. my $data = $hash->{USBDev}->input();
  937. Log(5,"Received packet: ". unpack("H*",$data) . "\n") if(defined($data) and length($data)>0);
  938. return $data;
  939. }
  940. Log(1,"TUL $hash->{NAME}: can not select a source for reading data.");
  941. return undef;
  942. error:
  943. printf "eibd communication failed\n";
  944. return undef;
  945. }
  946. # Sends a request to eibd
  947. # sendRequest Hash,DATA
  948. sub sendRequest($$)
  949. {
  950. my ($hash,$str) = @_;
  951. Log(5,"sendRequest: ". unpack("H*",$str). "\n");
  952. if($hash->{TCPDev})
  953. {
  954. my $size = length($str);
  955. my @head = (($size >> 8) & 0xff, $size & 0xff);
  956. return undef unless syswrite($hash->{TCPDev},pack("CC", @head));
  957. return undef unless syswrite($hash->{TCPDev}, $str);
  958. }
  959. elsif($hash->{USBDev})
  960. {
  961. $hash->{USBDev}->write($str);
  962. }
  963. else
  964. {
  965. Log(2,"TUL $hash->{NAME}: No known physical protocoll defined.");
  966. return undef;
  967. }
  968. return 1;
  969. }
  970. 1;
  971. =pod
  972. =begin html
  973. <a name="TUL"></a>
  974. <h3>TUL</h3>
  975. <ul>
  976. <table>
  977. <tr><td>
  978. The TUL module is the representation of a EIB / KNX connector in FHEM.
  979. <a href="#KNX">KNX</a> instances represent the EIB / KNX devices and will need a TUL as IODev to communicate with the EIB / KNX network.<br>
  980. The TUL module is designed to connect to EIB network either using eibd, knxd or the <a href="http://busware.de/tiki-index.php?page=TUL" target="_blank">TUL usb stick</a> created by busware.de
  981. Note: this module may require the Device::SerialPort or Win32::SerialPort module if you attach the device via USB and the OS sets strange default parameters for serial devices.
  982. </td><td>
  983. <img src="IMG_0483.jpg" width="100%" height="100%"/>
  984. </td></tr>
  985. </table>
  986. <a name="TULdefine"></a>
  987. <b>Define</b>
  988. <ul>
  989. <code>define &lt;name&gt; TUL &lt;device&gt; &lt;physical address&gt;</code> <br>
  990. <br>
  991. TUL usb stick / TPUART serial devices:<br><ul>
  992. &lt;device&gt; specifies the serial port to communicate with the TUL. The name of the serial-device depends on your distribution, under linux the cdc_acm kernel module is responsible, and usually a
  993. /dev/ttyACM0 device will be created. If your distribution does not have a cdc_acm module, you can force usbserial to handle the TUL by the following command:<ul>modprobe usbserial vendor=0x03eb
  994. product=0x204b</ul>In this case the device is most probably /dev/ttyUSB0.<br><br>
  995. You can also specify a baudrate if the device name contains the @ character, e.g.: /dev/ttyACM0@19200<br><br>
  996. Note: For TUL usb stick the baudrate 19200 is needed and this is the default when no baudrate is given.
  997. <br><br>
  998. Example:<br>
  999. <code>define tul TUL tul:/dev/ttyACM0 1.1.249</code>
  1000. </ul>
  1001. EIBD:<br><ul>
  1002. &lt;device&gt; specifies the host:port of the eibd device. E.g. eibd:192.168.0.244:2323. When using the standard port, the port can be omitted.
  1003. <br><br>
  1004. Example:<br>
  1005. <code>define tul TUL eibd:localhost 1.1.249</code>
  1006. <code>define tul TUL knxd:192.168.178.1 1.1.248</code>
  1007. </ul>
  1008. <br>
  1009. If the device is called none, then no device will be opened, so you can experiment without hardware attached.<br>
  1010. The physical address is used as the source address of telegrams sent to EIB network.
  1011. </ul>
  1012. <br>
  1013. <a name="TULattr"></a>
  1014. <b>Attributes</b>
  1015. <ul>
  1016. <li><a href="#do_not_notify">do_not_notify</a></li><br>
  1017. <li><a href="#attrdummy">dummy</a></li><br>
  1018. <li><a href="#showtime">showtime</a></li><br>
  1019. <li><a href="#verbose">verbose</a></li><br>
  1020. <li><a href="#useEIB">useEIB</a></li><br>
  1021. <ul>
  1022. The device operates the module 10_EIB, if this flag is set to 1. This is used for backward compatibility only. Otherwise, only the client 10_KNX is used.
  1023. </ul>
  1024. </ul>
  1025. <br>
  1026. </ul>
  1027. =end html
  1028. =device
  1029. =item summary Connects FHEM to KNX-Bus (Base-device)
  1030. =item summary_DE Verbindet FHEM mit dem KNX-Bus (Basisger&umlat)
  1031. =begin html_DE
  1032. <a name="TUL"></a>
  1033. <h3>TUL</h3>
  1034. <ul>
  1035. <table>
  1036. <tr><td>
  1037. Das Modul TUL stellt die Verbindung von FHEM zum EIB / KNX dar.
  1038. <a href="#KNX">KNX</a> Instanzen stellen die Vrbindung zu den KNX-Gruppen dar und ben&Ouml;tigen ein TUL-Device als IO-Schnittstelle.<br>
  1039. Das Modul TUL kommuniziert mit dem KNX entweder &Uuml;ber den eibd, den knxd oder den TUL <a href="http://busware.de/tiki-index.php?page=TUL" target="_blank">TUL usb stick</a> hergestellt von busware.de
  1040. Anmerkung: das Modul ben&Ouml;tigt die Device::SerialPort oder Win32::SerialPort wenn der Stick &Uuml;ber USB angeschlossen wird, und das OS unrealistische Parameter f&Uuml;r das Device einstellt.
  1041. </td><td>
  1042. <img src="IMG_0483.jpg" width="100%" height="100%"/>
  1043. </td></tr>
  1044. </table>
  1045. <a name="TULdefine"></a>
  1046. <b>Define</b>
  1047. <ul>
  1048. <code>define &lt;name&gt; TUL &lt;device&gt; &lt;physical address&gt;</code> <br>
  1049. <br>
  1050. TUL usb stick / TPUART serial devices:<br><ul>
  1051. &lt;device&gt; enth&auml;lt die serielle Schnittstelle der TUL. Der name der Schnittstelle h&auml;ngt von Eurer Distribution ab. Unter linux wird f&Uuml;r gew&Ouml;hnlich /dev/ttyACM0 verwandt.
  1052. Wenn Eure Distribution das modul cdc_acm nicht enth&auml;lt, k&Ouml;nnt Ihr das Laden des handles der TUL mit dem folgenden Befehl erzwingen:<ul>modprobe usbserial vendor=0x03eb
  1053. product=0x204b</ul>Dann ist die Schnittstelle meist /dev/ttyUSB0.<br><br>
  1054. Ihr k&Ouml;nnt dem Ger&auml;t eine Baudrate vorgeben. Dazu dem Ger&auml;tenamen das Zeichen @ hinzuf&Uuml;gen, z.B.: /dev/ttyACM0@19200<br><br>
  1055. Anmerkung: F&Uuml;r den TUL-USB-Stick wird die Baudrate 19200 ben&Ouml;tigt. Dies entspricht der Defaulteinstellung.
  1056. <br><br>
  1057. Beispiel:<br>
  1058. <code>define tul TUL tul:/dev/ttyACM0 1.1.249</code>
  1059. </ul>
  1060. EIBD:<br><ul>
  1061. &lt;device&gt; entspricht dem host:port des eibd-servers. z.B. eibd:192.168.0.244:2323. Wenn der Standardport genutzt wird, muss dieser nicht angegeben werden.
  1062. <br><br>
  1063. Beispiel:<br>
  1064. <code>define tul TUL eibd:localhost 1.1.249</code>
  1065. <code>define tul TUL knxd:192.168.178.2 1.1.248</code>
  1066. </ul>
  1067. <br>
  1068. Wenn das Ger&auml;t none konfiguriert wird, wird kein device ge&Ouml;ffnet. So k&Ouml;nnt Ihr ohne angeschlossene Hardware experimentieren. <br>
  1069. Die physikalische Adresse wird als Absender f&Uuml;r KNX-Telegramme genutzt.
  1070. </ul>
  1071. <br>
  1072. <a name="TULattr"></a>
  1073. <b>Attribute</b>
  1074. <ul>
  1075. <li><a href="#do_not_notify">do_not_notify</a></li><br>
  1076. <li><a href="#attrdummy">dummy</a></li><br>
  1077. <li><a href="#showtime">showtime</a></li><br>
  1078. <li><a href="#verbose">verbose</a></li><br>
  1079. <li><a href="#useEIB">useEIB</a></li><br>
  1080. <ul>
  1081. Das Ger&auml;t kann das Modul 10_EIB bedienen, wenn das Flag auf 1 gesetzt ist. Dies ist nur f&Uuml;r R&Uuml;ckw&auml;rtskompatibili&auml;t genutzt. Andernfalls wird nur das Modul 10_KNX bedient.
  1082. </ul>
  1083. </ul>
  1084. <br>
  1085. </ul>
  1086. =end html_DE
  1087. =cut