| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404 |
- ##############################################
- # $Id: 00_TUL.pm 15613 2017-12-15 18:39:59Z andi291 $
- # ABU 20150916 removed print: simpleWriteDate, cleaned init
- # ABU 20150918 fixed deprecated warning, fixed warning related to hex-conversion in simple-write
- # ABU 20151123 added error-label in getGroup. Responsible for error-handling, if knxd is not accesible
- # ABU 20151213 changed message-check in decode_tpuart() to avoid ignore while receiving repeated messages
- # ABU 20160308 remoced set, get. Changed loglevel to verbose. Added KNX/EIB-Split. Added EIB-backward-compatibility.
- # ABU 20160309 fixed log2
- # ABU 20160310 repaired dispatch events - inform EIB, only is useEIB is set
- # ABU 20160515 removed compatibility flag for EIB
- # ABU 20160516 added log entry for non-compatibility of tul
- # ABU 20160613 changed log entry for startup
- # ABU 20161108 added knxd. Added doku as well. Added summary. Treat it like eibd. See thread #58375
- # ABU 20170102 fixed write-mechanism, added mod for extended adressing (thx to its2bit)
- # ABU 20170110 removed mod for extended adressing
- # ABU 20170427 reintegrated mechanism for extenden GAD-Support
- # ABU 20170427 cleaned logs
- # ABU 20171006 deactivated default-log-entry
- # ABU 20171006 EIB requires different handling of extended GAD --> added
- # docM 20171106 fixed problem when OBD-IP adapter is offline during FHEM startup
- package main;
- use strict;
- use warnings;
- use Time::HiRes qw(gettimeofday);
- sub TUL_Attr(@);
- sub TUL_Clear($);
- sub TUL_Parse($$$$$);
- sub TUL_Read($);
- sub TUL_Ready($);
- sub TUL_Write($$$);
- sub TUL_OpenDev($$);
- sub TUL_CloseDev($);
- sub TUL_SimpleWrite(@);
- sub TUL_SimpleRead($);
- sub TUL_Disconnected($);
- sub TUL_Shutdown($);
- my %gets = ( # Name, Data to send to the TUL, Regexp for the answer
- "raw" => ["r", '.*'],
- );
- my %sets = (
- "raw" => "",
- );
- my $clients = ":KNX:EIB:";
- my %matchList = (
- "2:KNX" => "^C.*",
- "3:EIB" => "^B.*",
- );
- my $useEIB = '0';
- sub
- TUL_Initialize($)
- {
- my ($hash) = @_;
- # Provider
- $hash->{ReadFn} = "TUL_Read";
- $hash->{WriteFn} = "TUL_Write";
- $hash->{ReadyFn} = "TUL_Ready";
- # Normal devices
- $hash->{DefFn} = "TUL_Define";
- $hash->{UndefFn} = "TUL_Undef";
- $hash->{StateFn} = "TUL_SetState";
- $hash->{AttrFn} = "TUL_Attr";
-
- $hash->{AttrList}= "do_not_notify:1,0 " .
- "dummy:1,0 " .
- "showtime:1,0 " .
- "verbose:0,1,2,3,4,5 " .
- "useEIB:1,0 ";
-
- $hash->{ShutdownFn} = "TUL_Shutdown";
-
- }
- #####################################
- sub
- TUL_Define($$)
- {
- my ($hash, $def) = @_;
- my @a = split("[ \t][ \t]*", $def);
- if(@a < 4)
- {
- my $msg = "wrong syntax: define <name> TUL <devicename> <device addr> [<line def in hex>]";
- return $msg;
- }
- TUL_CloseDev($hash);
- my $name = $a[0];
- my $dev = $a[2];
- my $devaddr = tul_str2hex($a[3]);
- my $linedef = substr(tul_str2hex($a[4]),0,2) if(@a > 4);
- if($dev eq "none")
- {
- Log3 ($name, 1, "device is none, commands will be echoed only");
- $attr{$name}{dummy} = 1;
- return undef;
- }
-
- #Set attributes in order to control backward-compatibility
- #$attr{$name}{useEIB} = 1;
- #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 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.
- #As well 10_EIB and 10_KNX are compatible to daemon eibd and knxd.") if (AttrVal($name, "useEIB", 0) =~ m/0/);
-
- $hash->{DeviceName} = $dev;
- $hash->{DeviceAddress} = $devaddr;
- $hash->{Clients} = $clients;
- $hash->{MatchList} = \%matchList;
- $hash->{AckLineDef}= $linedef;
-
- my $ret = TUL_OpenDev($hash, 0);
- return $ret;
- }
- #####################################
- sub
- TUL_Undef($$)
- {
- my ($hash, $arg) = @_;
- my $name = $hash->{NAME};
- foreach my $d (sort keys %defs)
- {
- if(defined($defs{$d}) && defined($defs{$d}{IODev}) && $defs{$d}{IODev} == $hash)
- {
- my $lev = ($reread_active ? 4 : 2);
- Log (GetLogLevel($name,$lev), "deleting port for $d");
- delete $defs{$d}{IODev};
- }
- }
- TUL_CloseDev($hash);
- return undef;
- }
- #####################################
- sub TUL_Shutdown($)
- {
- my ($hash) = @_;
- TUL_CloseDev($hash);
- return undef;
- }
- #####################################
- sub
- TUL_SetState($$$$)
- {
- my ($hash, $tim, $vt, $val) = @_;
- return undef;
- }
- sub
- TUL_Clear($)
- {
- my $hash = shift;
- #Clear the pipe
- #TUL has no pipe....
- }
- #####################################
- sub
- TUL_DoInit($)
- {
- my $hash = shift;
- my $name = $hash->{NAME};
- my $err;
- TUL_Clear($hash);
- # send any initializing request if needed
- # TODO move to device init
- # docM 2017-11-05
- # moved openGroupSocket() to TUL_OpenDev.
- # return 1 unless openGroupSocket($hash);
- # /docM
-
- # reset buffer
- purgeReceiverBuf($hash);
- $hash->{STATE} = "Initialized" if(!$hash->{STATE});
- # Reset the counter
- delete($hash->{XMIT_TIME});
- delete($hash->{NR_CMD_LAST_H});
- return undef;
- }
- #####################################
- sub
- TUL_Write($$$)
- {
- my ($hash,$fn,$msg) = @_;
- my $name = $hash->{NAME};
-
- return if(!defined($fn));
-
- # docm 2017-11-05
- # Discard message if TUL is disconnected
- return if($hash->{STATE} eq "disconnected");
- # /docm
-
- #Discard message, if not set to backward-compatibility
- if (($useEIB =~ m/0/) and ($fn =~ m/\^B/))
- {
- Log3 ($name, 0, "EIB is no longer supported. Message discarded.");
- return;
- }
- Log3 ($name, 5, "sending $fn$msg");
- my $bstring = "$fn$msg";
- TUL_SimpleWrite($hash, $bstring);
- }
- #####################################
- # called from the global loop, when the select for hash->{FD} reports data
- sub
- TUL_Read($)
- {
- my ($hash) = @_;
- #reset the refused flag, so we can check if a telegram was refused
- # and therefor we did not get a response
- $hash->{REFUSED} = undef;
- my $buf = TUL_SimpleRead($hash);
- my $name = $hash->{NAME};
- # check if refused
- if(defined($hash->{REFUSED}))
- {
- Log3 ($name, 3,"TUL $name refused message: $hash->{REFUSED}");
- $hash->{REFUSED} = undef;
- return "";
- }
- ###########
- # Lets' try again: Some drivers return len(0) on the first read...
- if(defined($buf) && length($buf) == 0)
- {
- $buf = TUL_SimpleRead($hash);
- }
- if(!defined($buf) || length($buf) == 0)
- {
- TUL_Disconnected($hash);
- return "";
- }
- #place KNX-Message
- TUL_Parse($hash, $hash, $name, "B".$buf, $hash->{initString}) if ($useEIB =~ m/1/);
- #place EIB-Message
- TUL_Parse($hash, $hash, $name, "C".$buf, $hash->{initString});
- }
- sub
- TUL_Parse($$$$$)
- {
- my ($hash, $iohash, $name, $rmsg, $initstr) = @_;
- # there is nothing specal to do at the moment.
- # just dispatch
-
- my $dmsg = $rmsg;
- Log3 ($name, 4, "$name: $dmsg");
- $hash->{"${name}_MSGCNT"}++;
- $hash->{"${name}_TIME"} = TimeNow();
- $hash->{RAWMSG} = $rmsg;
- my %addvals = (RAWMSG => $rmsg);
- Dispatch($hash, $dmsg, \%addvals);
- }
- #####################################
- sub
- TUL_Ready($)
- {
- my ($hash) = @_;
- return TUL_OpenDev($hash, 1) if($hash->{STATE} eq "disconnected");
- # This is relevant for windows/USB only
- my $po = $hash->{USBDev};
- my ($BlockingFlags, $InBytes, $OutBytes, $ErrorFlags) = $po->status;
- return ($InBytes>0);
- }
- ########################
- sub
- TUL_SimpleWrite(@)
- {
- my ($hash, $msg) = @_;
- return if(!$hash);
- # Msg must have the format B(w,r,p)g1g2g3v....
- # w-> write, r-> read, p-> reply
- # g1,g2,g3 are the hex parts of the group name
- # v is a simple (1 Byte) or complex value (n bytes)
- # For eibd we need a more elaborate structure
- # Old
- #if($msg =~ /^[BC](.)(.{4})(.*)$/)
- # New: its2bit
- #if($msg =~ /^[BC](.)(.{5})(.*)$/)
- #extended adressing
- if ((($useEIB =~ m/1/) and ($msg =~ /^[BC](.)(.{4})(.*)$/)) or (($useEIB =~ m/0/) and ($msg =~ /^[BC](.)(.{5})(.*)$/)))
- {
- my $eibmsg;
- if($1 eq "w")
- {
- $eibmsg->{'type'} = 'write';
- }
- elsif ($1 eq "r")
- {
- $eibmsg->{'type'} = 'read';
- }
- elsif ($1 eq "p")
- {
- $eibmsg->{'type'} = 'reply';
- }
- $eibmsg->{'dst'} = $2;
- my $hexvalues = $3;
-
- #The array has to have a given length. During Hex-conversion Trailing
- #0 are recognizes for warnings.
- #Therefore we backup the length, trim, and reappend the 0
- #
- #save length and trim right side
- my $strLen = length ($hexvalues) / 2;
- $hexvalues =~ s/\s+$//;
- #convert hex-string to array with dezimal values
- my @data = map hex($_), $hexvalues =~ /(..)/g;
- #re-append 0x00
- for (my $i=0; $strLen - scalar @data; $i++)
- {
- push (@data, 0);
- }
-
- # check: first byte is only allowed to contain data in the lower 6bits
- # to make sure all is fine, we mask the first byte
- $data[0] = $data[0] & 0x3f if(defined($data[0]));
-
- $eibmsg->{'data'} = \@data;
-
- sendGroup($hash, $eibmsg);
- }
- else
- {
- Log3 ($hash->{NAME}, 1,"Could not parse message $msg");
- return undef;
- }
- select(undef, undef, undef, 0.001);
- }
- ########################
- sub
- TUL_SimpleRead($)
- {
- my ($hash) = @_;
- my $name = $hash->{NAME};
- my $msg = getGroup($hash);
- if(!defined($msg))
- {
- Log3 ($name, 4,"No data received.") ;
- return undef;
- }
-
- my $type = $msg->{'type'};
- my $dst = $msg->{'dst'};
- my $src = $msg->{'src'};
- my @bindata = @{$msg->{'data'}};
- my $data = "";
-
- # convert bin data to hex
- foreach my $c (@bindata)
- {
- $data .= sprintf ("%02x", $c);
- }
-
- Log3 ($name, 5, "SimpleRead msg.type: $type, msg.src: $msg->{'src'}, msg.dst: $msg->{'dst'}");
- Log3 ($name, 5, "SimpleRead data: $data");
-
- # we will build a string like:
- # Bs1s2s3(w|r|p)g1g2g3v
- # s -> src
- my $buf;
- #$buf = "C$src";
- $buf = $src;
- if($type eq "write")
- {
- $buf .= "w";
- }
- elsif ($type eq "read")
- {
- $buf .= "r";
- }
- else
- {
- $buf .= "p";
- }
-
- $buf .= $dst;
- $buf .= $data;
-
- Log (4,"SimpleRead: $buf\n");
-
- return $buf;
- }
- ########################
- sub
- TUL_CloseDev($)
- {
- my ($hash) = @_;
- my $name = $hash->{NAME};
- my $dev = $hash->{DeviceName};
- return if(!$dev);
-
- if($hash->{TCPDev})
- {
- $hash->{TCPDev}->close();
- delete($hash->{TCPDev});
- }
- elsif($hash->{USBDev})
- {
- $hash->{USBDev}->close() ;
- delete($hash->{USBDev});
- }
-
- delete($selectlist{"$name.$dev"});
- delete($readyfnlist{"$name.$dev"});
- delete($hash->{FD});
- }
- ########################
- sub
- TUL_OpenDev($$)
- {
- my ($hash, $reopen) = @_;
- my $dev = $hash->{DeviceName};
- my $name = $hash->{NAME};
- my $po;
- $hash->{PARTIAL} = "";
- Log 3, "TUL opening $name device $dev" if(!$reopen);
- # eibd:host[:port]
- #if($dev =~ m/^(eibd):(.+)$/)
- if($dev =~ m/^(eibd|knxd):(.+)$/)
- {
- my $host = $2;
- my $port = 6720;
-
- #host:port
- if($host =~ m/^(.+):([0-9]+)$/)
- {
- $host = $1;
- $port = $2;
- }
- # This part is called every time the timeout (5sec) is expired _OR_
- # somebody is communicating over another TCP connection. As the connect
- # for non-existent devices has a delay of 3 sec, we are sitting all the
- # time in this connect. NEXT_OPEN tries to avoid this problem.
- return if($hash->{NEXT_OPEN} && time() < $hash->{NEXT_OPEN});
- my $conn = IO::Socket::INET->new(PeerAddr => $host, PeerPort => $port,Proto => 'tcp');
- if($conn)
- {
- delete($hash->{NEXT_OPEN})
- }
- else
- {
- Log3 ($name, 3, "Can't connect to $dev: $!") if(!$reopen);
- $readyfnlist{"$name.$dev"} = $hash;
- $hash->{STATE} = "disconnected";
- $hash->{NEXT_OPEN} = time()+60;
- return "";
- }
- $hash->{DevType} = 'EIBD';
- $hash->{TCPDev} = $conn;
- $hash->{FD} = $conn->fileno();
- # docM 2017-11-05
- # Call openGroupSocket() here, as it is part of device initialization.
- if (openGroupSocket($hash))
- {
- Log (3, "OpenDev: OBD response from $dev") if($reopen);
- }
- else
- {
- # failed to connect to OBD. Close socket and start polling
- Log (3, "OpenDev: No OBD response from $dev") if(!$reopen);
- TUL_CloseDev($hash);
- $readyfnlist{"$name.$dev"} = $hash;
- $hash->{STATE} = "disconnected";
- $hash->{NEXT_OPEN} = time()+60;
- return "";
- }
- # /docM
- delete($readyfnlist{"$name.$dev"});
- $selectlist{"$name.$dev"} = $hash;
- }
- # tpuart:ttydev[@baudrate] / USB/Serial device
- elsif ($dev =~ m/^(tul|tpuart):(.+)$/)
- {
- my $dev = $2;
- my $baudrate;
- ($dev, $baudrate) = split("@", $dev);
- $baudrate = 19200 if(!$baudrate); # fix for TUL board
-
- if ($^O=~/Win/)
- {
- require Win32::SerialPort;
- $po = new Win32::SerialPort ($dev);
- } else
- {
- require Device::SerialPort;
- $po = new Device::SerialPort ($dev);
- }
- if(!$po)
- {
- return undef if($reopen);
- Log3 ($name, 3, "Can't open $dev: $!");
- $readyfnlist{"$name.$dev"} = $hash;
- $hash->{STATE} = "disconnected";
- return "";
- }
-
- $hash->{DevType} = 'TPUART';
- $hash->{USBDev} = $po;
- if( $^O =~ /Win/ )
- {
- $readyfnlist{"$name.$dev"} = $hash;
- }
- else
- {
- $hash->{FD} = $po->FILENO;
- delete($readyfnlist{"$name.$dev"});
- $selectlist{"$name.$dev"} = $hash;
- }
-
- # assumed always available
- if($baudrate)
- {
- $po->reset_error();
- Log3 ($name, 3, "TUL setting $name baudrate to $baudrate");
- $po->baudrate($baudrate);
- $po->databits(8);
- $po->parity('even');
- $po->stopbits(1);
- $po->handshake('none');
- # This part is for some Linux kernel versions which has strange default
- # settings. Device::SerialPort is nice: if the flag is not defined for your
- # OS then it will be ignored.
- $po->stty_icanon(0);
- #$po->stty_parmrk(0); # The debian standard install does not have it
- $po->stty_icrnl(0);
- $po->stty_echoe(0);
- $po->stty_echok(0);
- $po->stty_echoctl(0);
- # Needed for some strange distros
- $po->stty_echo(0);
- $po->stty_icanon(0);
- $po->stty_isig(0);
- $po->stty_opost(0);
- $po->stty_icrnl(0);
- }
- $po->write_settings;
- }
- # No more devices supported now
- else
- {
- Log3 ($name, 1, "$dev protocol is not supported");
- }
- if($reopen)
- {
- Log3 ($name, 1, "TUL $dev reappeared ($name)");
- }
- else
- {
- Log3 ($name, 3, "TUL device opened");
- }
- $hash->{STATE}=""; # Allow InitDev to set the state
- my $ret = TUL_DoInit($hash);
- if($ret)
- {
- TUL_CloseDev($hash);
- Log (1, "OpenDev: Cannot init $dev, ignoring it");
- }
- DoTrigger($name, "CONNECTED") if($reopen);
- return $ret;
- }
- ########################
- sub
- TUL_Disconnected($)
- {
- my $hash = shift;
- my $dev = $hash->{DeviceName};
- my $name = $hash->{NAME};
- return if(!defined($hash->{FD})); # Already deleted or RFR
- Log3 ($name, 1, "$dev disconnected, waiting to reappear");
- TUL_CloseDev($hash);
- $readyfnlist{"$name.$dev"} = $hash; # Start polling
- $hash->{STATE} = "disconnected";
- # Without the following sleep the open of the device causes a SIGSEGV,
- # and following opens block infinitely. Only a reboot helps.
- sleep(5);
- DoTrigger($name, "DISCONNECTED");
- }
- ########################
- sub
- TUL_Attr(@)
- {
- my ($cmd,$name,$aName,$aVal) = @_;
- Log3 ($name, 5, "changing value, ATTR: $aName, VALUE: $aVal");
-
- if ($aName =~ m/useEIB/)
- {
- if ($aVal =~ m/1/)
- {
- $useEIB = '1';
- }
- else
- {
- $useEIB = '0';
- }
- }
-
- return undef;
- }
- ####################################################################################
- ####################################################################################
- #
- #
- # The following section has been inspired by the EIB module from MrHouse project
- # written by Peter Sj?din peter@sjodin.net and Mike Pieper eibdmh@pieper-family.de
- # Code has been mainly changed to fit to the FHEM framework by Maz Rashid
- # (to be honest the code had to be reworked very intensively due the lack of code quality)
- #
- # Utility functions
- sub tul_hex2addr
- {
- my $str = lc($_[0]);
- # Old
- #if ($str =~ /([0-9a-f])([0-9a-f])([0-9a-f]{2})/)
- # New its2bit
- #if ($str =~ /([0-9a-f]{2})([0-9a-f])([0-9a-f]{2})/)
- #extended adressing
- if ((($useEIB =~ m/1/) and ($str =~ /([0-9a-f])([0-9a-f])([0-9a-f]{2})/)) or (($useEIB =~ m/0/) and ($str =~ /([0-9a-f]{2})([0-9a-f])([0-9a-f]{2})/)))
- {
- return (hex($1) << 11) | (hex($2) << 8) | hex($3);
- }
- else
- {
- Log (3,"hex2addr: Bad KNX address string: \'$str\'\n");
- return;
- }
- }
- sub tul_addr2hex
- {
- my $a = $_[0];
- my $b = $_[1]; # 1 if local (group) address, else physical address
- my $str ;
-
- if ($b == 1)
- {
- #logical address used
- #old, short-syntax
- #$str = sprintf "%01x%01x%02x", ($a >> 11) & 0xf, ($a >> 8) & 0x7, $a & 0xff;
- #extended adress-range
- #$str = sprintf "%02x%01x%02x", ($a >> 11) & 0x1f, ($a >> 8) & 0x7, $a & 0xff;
-
- #extended adressing
- if ($useEIB =~ m/1/)
- {
- $str = sprintf "%01x%01x%02x", ($a >> 11) & 0xf, ($a >> 8) & 0x7, $a & 0xff;
- }
- else
- {
- $str = sprintf "%02x%01x%02x", ($a >> 11) & 0x1f, ($a >> 8) & 0x7, $a & 0xff;
- }
- }
- else
- {
- #physical address used
- # Old
- # $str = sprintf "%01x%01x%02x", $a >> 12, ($a >> 8) & 0xf, $a & 0xff;
- # New
- #$str = sprintf "%02x%01x%02x", $a >> 12, ($a >> 8) & 0xf, $a & 0xff;
-
- #extended adressing
- if ($useEIB =~ m/1/)
- {
- $str = sprintf "%01x%01x%02x", $a >> 12, ($a >> 8) & 0xf, $a & 0xff;
- }
- else
- {
- $str = sprintf "%02x%01x%02x", $a >> 12, ($a >> 8) & 0xf, $a & 0xff;
- }
- }
-
- return $str;
- }
- sub tul_str2hex
- {
- my $str = $_[0];
- my $hex;
-
- if (($str =~ /(\d+)\/(\d+)\/(\d+)/) or ($str =~ /(\d+)\.(\d+)\.(\d+)/))
- {
- # logical address
- # old
- # my $hex = sprintf("%01x%01x%02x",$1,$2,$3);
- # New
- #my $hex = sprintf("%02x%01x%02x",$1,$2,$3);
-
- #extended adressing
- if ($useEIB =~ m/1/)
- {
- $hex = sprintf("%01x%01x%02x",$1,$2,$3);
- }
- else
- {
- $hex = sprintf("%02x%01x%02x",$1,$2,$3);
- }
- return $hex;
- }
- }
- # For mapping between APCI symbols and values
- my @apcicodes = ('read', 'reply', 'write');
- my %apcivalues = ('read' => 0, 'reply' => 1, 'write' => 2,);
- # decode: unmarshall a string with an EIB message into a hash
- # The hash has the follwing fields:
- # - type: APCI (symbolic value)
- # - src: source address
- # - dst: destiniation address
- # - data: array of integers; one for each byte of data
- sub decode_eibd($)
- {
- my ($buf) = @_;
- my $drl = 0xe1; # dummy value
- my %msg;
- my @data;
- my ($src, $dst,$bytes) = unpack("nnxa*", $buf);
- my $apci;
- $apci = vec($bytes, 3, 2);
- # mask out apci bits, so we can use the whole byte as data:
- vec($bytes, 3, 2) = 0;
- if ($apci >= 0 && $apci <= $#apcicodes)
- {
- $msg{'type'} = $apcicodes[$apci];
- }
- else
- {
- $msg{'type'} = 'apci ' . $apci;
- }
- $msg{'src'} = tul_addr2hex($src,0);
- $msg{'dst'} = tul_addr2hex($dst,1);
- @data = unpack ("C" . length($bytes), $bytes);
- my $datalen = @data;
- Log (5, "decode_eibd: byte len: " . length($bytes) . " array size: $datalen");
-
- # in case of data len > 1, the first byte (the one with apci) seems not to be used
- # and only the following byte are of interest.
- if($datalen>1)
- {
- shift @data;
- }
-
- $msg{'data'} = \@data;
- return \%msg;
- }
- # encode: marshall a hash into a EIB message string
- sub encode_eibd($)
- {
- my ($mref) = @_;
- my @msg;
- my $APCI;
- my @data;
- $APCI = $apcivalues{$mref->{'type'}};
- if (!(defined $APCI))
- {
- Log (3,"encode_eibd: Bad KNX message type $mref->{'type'}\n");
- return;
- }
- @data = @{$mref->{'data'}};
-
- @data = (0x0) if(!@data || !defined($data[0])); #make sure data has at least one element
- #@data = (0x0) if(!(defined @data) || !(defined $data[0])); #make sure data has at least one element
- my $datalen = @data;
- Log (5,"encode_eibd: dst: $mref->{'dst'} apci: $APCI datalen: $datalen data: @data");
- @msg = (
- tul_hex2addr( $mref->{'dst'}), # Destination address
- 0x0 | ($APCI >> 2), # TPDU type, Sequence no, APCI (msb)
- (($APCI & 0x3) << 6) | $data[0],
- );
- if ($datalen > 1)
- {
- shift(@data);
- push @msg, @data;
- }
- return @msg;
- }
- # decode: unmarshall a string with an EIB telegram into a hash
- # A typical telegram looks like: bc110a0002e100813a
- # checks:
- # - 1st byte must have at least the bits $90 set. (otherwise it is false or a repeat)
- # - 2nd/3rd byte are the source (1.1.10)
- # - 4th/5th byte are the dst group (0/0/2)
- # - 6th byte (msb if 1 dst is group, else a phys. address )
- # - low nibble is length of data (counting from 0) (->2)
- # - 7th byte is ignored
- # - 8th byte is the command / short data byte
- # - if 8th byte >>6 is 0 -> read
- # - is 2 -> write
- # - is 1 -> reply
- # - if length is 2 -> 8th byte & 0x3F is data
- # otherwise data start after 8th byte
- # - last byte is the crc (ignored)
- # The hash has the follwing fields:
- # - type: APCI (symbolic value)
- # - src: source address
- # - dst: destiniation address
- # - data: array of integers; one for each byte of data
- sub decode_tpuart($)
- {
- my ($buf) = @_;
- my ($ctrl,$src, $dst, $routingcnt,$cmd, $bytes) = unpack("CnnCxCa*", $buf);
- my $drl = $routingcnt >>7;
- my $len = ($routingcnt & 0x0F) +1;
- #if(($ctrl & 0xB0)!=0xB0)
- if(($ctrl & 0x90)!=0x90)
- {
- Log (3,"decode_tpuart: Control Byte " . sprintf("0x%02x",$ctrl) . " does not match expected mask 2x1001nnnn");
- return undef;
- }
- Log (5,"decode_tpuart: msg cmd: " . sprintf("0x%02x",$cmd) ." datalen: $len");
-
- my $apci = ($cmd >> 6) & 0x0F;
- if($len == 2)
- { # 1 byte data
- $bytes = pack("C",$cmd & 0x3F);
- }
- Log (5,"decode_tpuart: msg cmd: " . sprintf("0x%02x",$cmd) ." datalen: $len apci: $apci");
-
- my %msg;
- my @data;
- if ($apci >= 0 && $apci <= $#apcicodes)
- {
- $msg{'type'} = $apcicodes[$apci];
- }
- else
- {
- $msg{'type'} = 'apci ' . $apci;
- }
- $msg{'src'} = tul_addr2hex($src,0);
- $msg{'dst'} = tul_addr2hex($dst,$drl);
- @data = unpack ("C" . length($bytes), $bytes);
- my $datalen = @data;
- Log (5, "decode_tpuart: decode_tpuart byte len: " . length($bytes) . " array size: $datalen");
-
- $msg{'data'} = \@data;
- return \%msg;
- }
- # encode: marshall a hash into a EIB message string
- sub encode_tpuart($)
- {
- my ($mref) = @_;
- my @msg;
- my $APCI;
- my @data;
- $APCI = $apcivalues{$mref->{'type'}};
- if (!(defined $APCI))
- {
- Log (3,"encode_tpuart: Bad KNX message type $mref->{'type'}\n");
- return;
- }
- @data = @{$mref->{'data'}};
- my $datalen = @data;
- if($datalen > 14)
- {
- Log (3,"encode_tpuart: Bad KNX message length $datalen\n");
- return;
-
- }
- Log (5,"encode_tpuart: dst: $mref->{'dst'} apci: $APCI datalen: $datalen data: @data");
- @msg = (
- 0xBC, # EIB ctrl byte
- tul_hex2addr($mref->{'src'}), # src address
- tul_hex2addr( $mref->{'dst'}), # Destination address
- 0xE0 | $datalen, # Routing counter + data len
- 0x00,
- (($APCI & 0x3) << 6) | $data[0],
- );
- if ($datalen > 1)
- {
- shift(@data);
- push @msg, @data;
- }
-
- # convert to byte array
- my $arraystr = pack("CnnC*",@msg);
- @msg = unpack("C*",$arraystr);
-
- my @tpuartmsg;
-
- # calculate crc
- my $crc = 0xFF;
- my $i;
- for($i=0; $i<@msg;$i++)
- {
- $crc ^= $msg[$i];
- push @tpuartmsg,(0x80 | $i);
- push @tpuartmsg, $msg[$i];
- }
-
- push @tpuartmsg,(0x40 | $i);
- push @tpuartmsg,$crc;
-
- return @tpuartmsg;
- }
- #
- # eibd communication part
- #
- # Functions four group socket communication
- # Open a group socket for group communication
- # openGroupSocket SOCK
- sub openGroupSocket($)
- {
- my $hash = shift;
- ## only needed if EIBD
- if($hash->{DevType} eq 'EIBD')
- {
- my @msg = (0x0026,0x0000,0x00); # EIB_OPEN_GROUPCON
- sendRequest ($hash, pack "nnC" ,@msg);
- # docM 2017-11-06
- use IO::Select;
- goto error unless (IO::Select->new($hash->{TCPDev})->can_read(10));
- # /docM
- goto error unless my $answer = getRequest($hash);
- my $head = unpack ("n", $answer);
- goto error unless $head == 0x0026;
- }
-
- return 1;
- error:
-
- Log (0,"openGroupSocket: failed\n");
-
- # docM 2017-11-05
- # removed print
- # print "openGroupSocket failed\n";
- # /docM
- return undef;
- }
- # Send group data
- # sendGroup Hash DEST DATA
- sub sendGroup($$)
- {
- my ($hash,$msgref) = @_;
- my $dst = $msgref->{'dst'};
- my $src = $hash->{DeviceAddress};
- $msgref->{'src'} = $src;
-
- if($hash->{DevType} eq 'EIBD')
- {
- my @encmsg = encode_eibd($msgref);
-
- Log (5,"SendGroup: dst: $dst, msg: @encmsg \n");
-
- my @msg = (0x0027); # EIB_GROUP_PACKET
- push @msg, @encmsg;
- sendRequest($hash, pack("nnCC*", @msg));
- }
- elsif($hash->{DevType} eq 'TPUART')
- {
- my @encmsg = encode_tpuart($msgref);
-
- Log (5,"SendGroup: dst: $dst, msg: @encmsg \n");
- sendRequest($hash, pack("C*", @encmsg));
- my $response = getRequestFixLength($hash,($#encmsg + 1)/2+1);
- }
- return 1;
- }
- # will read as much byte as exists at the
- # serial buffer.
- sub purgeReceiverBuf($)
- {
- my ($hash) = @_;
- if($hash->{DevType} eq 'TPUART')
- {
- Log (5,"purgeReceiverBuf: purging...");
- my $data = undef;
- do
- {
- my(undef,$data) = $hash->{USBDev}->read(100);
- Log (5,"purgeReceiverBuf: purging packet: ". unpack("H*",$data) . "\n") if(defined($data) and length($data)>0);
- } while(defined($data) and length($data)>0)
- }
- }
- sub getRequestFixLength($$)
- {
- my ($hash, $len) = @_;
-
- if($hash->{DevType} eq 'TPUART')
- {
- Log (5,"getRequestFixLength: waiting to receive $len bytes ...");
- my $buf = "";
- while(length($buf)<$len)
- {
- #select(undef,undef,undef,0.5);
- my (undef,$data) = $hash->{USBDev}->read($len-length($buf));
- Log (5,"getRequestFixLength: Received fixlen packet: ". unpack("H*",$data) . "\n") if(defined($data) and length($data)>0);
-
- $buf .= $data if(defined($data));
- #Log (5,"buf len: " . length($buf) . " expected: $len");
- # TODO: if we are longer than 5 seconds here, we should reset
- }
-
- # # we got more than needed
- if(length($buf)>$len)
- {
- #check if this is ok
- my $remainpart = substr($buf,$len);
- $hash->{PARTIAL} .= $remainpart;
- $buf = substr($buf,0,$len);
-
- Log (5,"getRequestFiLength: we got too much.. buf(" .unpack("H*",$buf).") remainingpart(" .unpack("H*",$remainpart).")");
- }
-
- Log (5,"getRequestFixLength: len: $len packet: ". unpack("H*",$buf) . "\n");
- return $buf;
- }
-
- return undef;
- }
- # Receive group data
- # getGroup hash
- sub getGroup($)
- {
- my $hash = shift;
- if($hash->{DevType} eq 'EIBD')
- {
- goto error unless my $buf = getRequest($hash);
- my ($head, $data) = unpack ("na*", $buf);
- goto error unless $head == 0x0027;
-
- return decode_eibd($data);
- }
- elsif($hash->{DevType} eq 'TPUART')
- {
- my $ackdst = $hash->{AckLineDef};
- my $buf = $hash->{PARTIAL};
- my $reqlen = 8;
- my $telegram;
-
- do
- {
- my $data = getRequestFixLength($hash,$reqlen-length($buf)) if($reqlen>length($buf));
- if(length($buf)==0 && (!defined($data)||length($data)==0))
- {
- Log (5,"getGroup: read fix length delivered no data.");
- return undef;
- }
- $buf .= $data if(defined($data));
-
- # check that control byte is correct
- my $ctrl = unpack("C",$buf) if(length($buf)>0);
- if(defined($ctrl) && ($ctrl&0x40) )
- {
- $buf = substr($buf,1);
- $hash->{PARTIAL} = $buf;
- Log (5,"getGroup: TPUART RSP " . sprintf("0x%02x",$ctrl) ." ignored.");
- return undef;
- }
-
- if(length($buf)>5)
- {
- my $routingcnt = unpack("xxxxxC", $buf);
- $reqlen = ($routingcnt & 0x0F)+8;
- Log (5,"getGroup: receiving telegram with len: $reqlen");
- }
-
-
- if($reqlen <= length($buf))
- {
- $telegram = substr($buf,0,$reqlen-1);
- $buf = substr($buf,$reqlen);
- }
- }
- while(!defined($telegram));
-
- Log (5, "getGroup: Telegram: (".length($telegram)."): " . unpack("H*",$telegram));
- Log (5, "getGroup: Buf: (".length($buf)."): " . unpack("H*",$buf));
-
- $hash->{PARTIAL} = $buf;
- my $msg = decode_tpuart($telegram);
-
- #check if we refused a telegram (i.e. repeats)
- $hash->{REFUSED} = unpack("H*",$telegram) if(!defined($msg));
-
- # We are always too late for Ack
- # if(defined($msg) && (substr($msg->{'dst'},0,2) eq $ackdst))
- # {
- # # ACK
- # sendRequest($hash,pack('C',0x11));
- # Log (5,"Ack!");
- # }
-
- return $msg;
- }
-
- Log (2,"GetGroup: DevType $hash->{DevType} not supported for getGroup\n");
- return undef;
-
- error:
-
- Log (2,"GetGroup: seems like knxd not connected\n");
- return undef;
- }
- # Gets a request from eibd
- # DATA = getRequest SOCK
- sub getRequest($)
- {
- my $hash = shift;
- my ($data);
-
- if($hash->{TCPDev} && $hash->{DevType} eq 'EIBD')
- {
- goto error unless sysread($hash->{TCPDev}, $data, 2);
- my $size = unpack ("n", $data);
- goto error unless sysread($hash->{TCPDev}, $data, $size);
- Log (5,"getRequest: Received packet: ". unpack("H*",$data) . "\n");
- return $data;
- }
- elsif($hash->{USBDev}) {
- my $data = $hash->{USBDev}->input();
- Log (5,"getRequest: Received packet: ". unpack("H*",$data) . "\n") if(defined($data) and length($data)>0);
- return $data;
- }
-
- Log (1,"getRequest: TUL $hash->{NAME}: can not select a source for reading data.");
- return undef;
-
- error:
- # docM 2017-11-05 remove print
- # printf "eibd communication failed\n";
- # /docM
- Log (2,"getRequest: communication to knxd failed\n");
- return undef;
-
- }
- # Sends a request to eibd
- # sendRequest Hash,DATA
- sub sendRequest($$)
- {
- my ($hash,$str) = @_;
- Log (5,"sendRequest: ". unpack("H*",$str). "\n");
- if($hash->{TCPDev})
- {
- my $size = length($str);
- my @head = (($size >> 8) & 0xff, $size & 0xff);
- return undef unless syswrite($hash->{TCPDev},pack("CC", @head));
- return undef unless syswrite($hash->{TCPDev}, $str);
- }
- elsif($hash->{USBDev})
- {
- $hash->{USBDev}->write($str);
- }
- else
- {
- Log (2,"sendRequest: TUL $hash->{NAME}: No known physical protocoll defined.");
- return undef;
- }
- return 1;
- }
- 1;
- =pod
- =begin html
- <a name="TUL"></a>
- <h3>TUL</h3>
- <ul>
- <table>
- <tr><td>
- The TUL module is the representation of a EIB / KNX connector in FHEM.
- <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>
- 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
- 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.
- </td><td>
- <img src="IMG_0483.jpg" width="100%" height="100%"/>
- </td></tr>
- </table>
- <a name="TULdefine"></a>
- <b>Define</b>
- <ul>
- <code>define <name> TUL <device> <physical address></code> <br>
- <br>
- TUL usb stick / TPUART serial devices:<br><ul>
- <device> 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
- /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
- product=0x204b</ul>In this case the device is most probably /dev/ttyUSB0.<br><br>
- You can also specify a baudrate if the device name contains the @ character, e.g.: /dev/ttyACM0@19200<br><br>
- Note: For TUL usb stick the baudrate 19200 is needed and this is the default when no baudrate is given.
- <br><br>
- Example:<br>
- <code>define tul TUL tul:/dev/ttyACM0 1.1.249</code>
- </ul>
-
- EIBD:<br><ul>
- <device> 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.
- <br><br>
- Example:<br>
- <code>define tul TUL eibd:localhost 1.1.249</code>
- <code>define tul TUL knxd:192.168.178.1 1.1.248</code>
- </ul>
- <br>
- If the device is called none, then no device will be opened, so you can experiment without hardware attached.<br>
- The physical address is used as the source address of telegrams sent to EIB network.
- </ul>
- <br>
- <a name="TULattr"></a>
- <b>Attributes</b>
- <ul>
- <li><a href="#do_not_notify">do_not_notify</a></li><br>
- <li><a href="#attrdummy">dummy</a></li><br>
- <li><a href="#showtime">showtime</a></li><br>
- <li><a href="#verbose">verbose</a></li><br>
- <li><a href="#useEIB">useEIB</a></li><br>
- <ul>
- 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.
- </ul>
- </ul>
- <br>
- </ul>
- =end html
- =device
- =item summary Connects FHEM to KNX-Bus (Base-device)
- =item summary_DE Verbindet FHEM mit dem KNX-Bus (Basisger¨at)
- =begin html_DE
- <a name="TUL"></a>
- <h3>TUL</h3>
- <ul>
- <table>
- <tr><td>
- Das Modul TUL stellt die Verbindung von FHEM zum EIB / KNX dar.
- <a href="#KNX">KNX</a> Instanzen stellen die Vrbindung zu den KNX-Gruppen dar und benÖtigen ein TUL-Device als IO-Schnittstelle.<br>
- Das Modul TUL kommuniziert mit dem KNX entweder Ü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
- Anmerkung: das Modul benÖtigt die Device::SerialPort oder Win32::SerialPort wenn der Stick Über USB angeschlossen wird, und das OS unrealistische Parameter fÜr das Device einstellt.
- </td><td>
- <img src="IMG_0483.jpg" width="100%" height="100%"/>
- </td></tr>
- </table>
- <a name="TULdefine"></a>
- <b>Define</b>
- <ul>
- <code>define <name> TUL <device> <physical address></code> <br>
- <br>
- TUL usb stick / TPUART serial devices:<br><ul>
- <device> enthält die serielle Schnittstelle der TUL. Der name der Schnittstelle hängt von Eurer Distribution ab. Unter linux wird fÜr gewÖhnlich /dev/ttyACM0 verwandt.
- Wenn Eure Distribution das modul cdc_acm nicht enthält, kÖnnt Ihr das Laden des handles der TUL mit dem folgenden Befehl erzwingen:<ul>modprobe usbserial vendor=0x03eb
- product=0x204b</ul>Dann ist die Schnittstelle meist /dev/ttyUSB0.<br><br>
- Ihr kÖnnt dem Gerät eine Baudrate vorgeben. Dazu dem Gerätenamen das Zeichen @ hinzufÜgen, z.B.: /dev/ttyACM0@19200<br><br>
- Anmerkung: FÜr den TUL-USB-Stick wird die Baudrate 19200 benÖtigt. Dies entspricht der Defaulteinstellung.
- <br><br>
- Beispiel:<br>
- <code>define tul TUL tul:/dev/ttyACM0 1.1.249</code>
- </ul>
-
- EIBD:<br><ul>
- <device> 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.
- <br><br>
- Beispiel:<br>
- <code>define tul TUL eibd:localhost 1.1.249</code>
- <code>define tul TUL knxd:192.168.178.2 1.1.248</code>
- </ul>
- <br>
- Wenn das Gerät none konfiguriert wird, wird kein device geÖffnet. So kÖnnt Ihr ohne angeschlossene Hardware experimentieren. <br>
- Die physikalische Adresse wird als Absender fÜr KNX-Telegramme genutzt.
- </ul>
- <br>
- <a name="TULattr"></a>
- <b>Attribute</b>
- <ul>
- <li><a href="#do_not_notify">do_not_notify</a></li><br>
- <li><a href="#attrdummy">dummy</a></li><br>
- <li><a href="#showtime">showtime</a></li><br>
- <li><a href="#verbose">verbose</a></li><br>
- <li><a href="#useEIB">useEIB</a></li><br>
- <ul>
- Das Gerät kann das Modul 10_EIB bedienen, wenn das Flag auf 1 gesetzt ist. Dies ist nur fÜr RÜckwärtskompatibiliät genutzt. Andernfalls wird nur das Modul 10_KNX bedient.
- </ul>
- </ul>
- <br>
- </ul>
- =end html_DE
- =cut
|