#!/usr/bin/perl use FindBin; # find the script's directory use lib $FindBin::Bin; # add that directory to the library path use xPL::common; ################################################################################ # constants # $vendor_id = 'dspc'; # from xplproject.org $device_id = 'eib'; # max 8 chars $class_id = 'eib'; # max 8 chars $separator = '-' x 80; $indent = ' ' x 2; #------------------------------------------------------------------------------- # global variables # my %configuration; $configuration{serialPort} = 'doorSerial'; $configuration{levelNb} = 3; ################################################################################ # Input arguments # use Getopt::Std; my %opts; getopts('hvp:n:s:t:w:s:', \%opts); die("\n". "Usage: $0 [options]\n". "\n". "Options:\n". "${indent}-h display this help message\n". "${indent}-v verbose\n". "${indent}-p port the base UDP port\n". "${indent}-n id the instance id (max. 12 chars)\n". "${indent}-s str the serial port xPL client name\n". "${indent}-t mins the heartbeat interval in minutes\n". "${indent}-w secs the startup sleep interval\n". "\n". "Interfaces a Tapko EIB/KNX to RS232 bridge for EIB control.\n". "\n". "More information with: perldoc $0\n". "\n". "" ) if ($opts{h}); $verbose = $opts{'v'}; my $client_base_port = $opts{'p'} || 50000; my $instance_id = $opts{'n'} || xpl_build_automatic_instance_id; my $heartbeat_interval = $opts{'t'} || 5; my $startup_sleep_time = $opts{'w'} || 0; $configuration{serialPort} = $opts{'s'} || $configuration{serialPort}; ################################################################################ # Internal functions # #------------------------------------------------------------------------------- # sends a message to reset the bridge to the manufacturer state # sub reset_bridge { my ($xpl_socket, $xpl_port, $xpl_id, $serial_port_client) = @_; # reset to the manufacturer state xpl_send_message( $xpl_socket, $xpl_port, 'cmnd', $xpl_id, $serial_port_client, 'serPort.basic', ('command' => 'gci') ); } #------------------------------------------------------------------------------- # sends a message to set the bridge transparent and sets the number of levels # sub update_level_nb { my ($xpl_socket, $xpl_port, $xpl_id, $serial_port_client, $level_nb) = @_; # device transparent set my $DTS_flags = '15'; if ($level_nb == 2) { $DTS_flags = '35'; } xpl_send_message( $xpl_socket, $xpl_port, 'cmnd', $xpl_id, $serial_port_client, 'serPort.basic', ('command' => "dts \\\$$DTS_flags") ); } #------------------------------------------------------------------------------- # Parse an EIB serial port telegram and build the xPL message body # sub parse_EIB_telegram { my ($telegram) = @_; my %body; # transparent data indication if ($telegram =~ /\Atdi\s/) { # analyse message $telegram =~ s/\Atdi\s+//g; $telegram =~ s/\(|\)|\\x0A|\\x0D//g; $telegram =~ s/\s+\Z//; #print "telegram: <$telegram>\n"; my ($source_address, $group_address, $length, $data) = split(/\s+/, $telegram, 4); $group_address =~ s/\//./g; if ($length =~ /\A\$/) { $length =~ s/\$//; $length = hex($length); } $data =~ s/\$/0x/g; $data =~ s/\s+0x//g; # build xPL body %body = ( 'source' => $source_address, 'group' => $group_address, 'length' => $length, 'data' => $data ); } return(%body) } #------------------------------------------------------------------------------- # Translate an xPL command body to the corresponding EIB serial port telegram # sub build_EIB_telegram { my %body = @_; my $message_OK = 1; # group address my $group_address = $body{'group'}; if ($group_address eq '') { $message_OK = 0; } # length my $length = $body{'length'}; if ($length eq '') { $length = 0; } $length = '$' . sprintf('%02X', $length); # data my $data = $body{'data'}; if ($data eq '') { $message_OK = 0; } $data =~ s/0x/\$/g; # build message string my $tapko_message = "tds ($group_address $length) $data"; $tapko_message =~ s/\$/\\\$/g; if ($message_OK == 0) { $tapko_message = ''; } #print "message: $tapko_message\n"; return($tapko_message) } ################################################################################ # Catch control-C interrupt # $SIG{INT} = sub{ $xpl_end++ }; ################################################################################ # Main script # sleep($startup_sleep_time); # xPL parameters my $xpl_id = xpl_build_id($vendor_id, $device_id, $instance_id); my $xpl_ip = xpl_find_ip; # create xPL socket my ($client_port, $xpl_socket) = xpl_open_socket($xpl_port, $client_base_port); # display working parameters if ($verbose == 1) { system("clear"); print("$separator\n"); print "Controlling a Tapko EIB/KNX to RS232 bridge via serial port xPL client \"$configuration{serialPort}\".\n"; print($indent . "class id: $class_id\n"); print($indent . "xPL client id: $xpl_id\n"); print("\n"); } #------------------------------------------------------------------------------- # Place Tapko EIB/KNX to RS232 bridge in transparent mode # # reset to the manufacturer state reset_bridge($xpl_socket, $xpl_port, $xpl_id, $configuration{serialPort}); sleep(2); # set device transparent update_level_nb( $xpl_socket, $xpl_port, $xpl_id, $configuration{serialPort}, $configuration{levelNb} ); #------------------------------------------------------------------------------- # Main loop # my $timeout = 1; my $last_heartbeat_time = 0; while ( (defined($xpl_socket)) && ($xpl_end == 0) ) { # check time and send heartbeat $last_heartbeat_time = xpl_send_heartbeat( $xpl_socket, $xpl_id, $xpl_ip, $client_port, $heartbeat_interval, $last_heartbeat_time ); # get xpl-UDP message with timeout my ($xpl_message) = xpl_get_message($xpl_socket, $timeout); # process XPL message if ($xpl_message) { my ($type, $source, $target, $schema, %body) = xpl_get_message_elements($xpl_message); if ( xpl_is_for_me($xpl_id, $target) ) { # process commands if ($schema eq "$class_id.basic") { if ($type eq 'xpl-cmnd') { my $command = $body{'command'}; if ($verbose > 0) { print("Received command from \"$source\"\n"); } my $message = build_EIB_telegram(%body); if ($message) { if ($verbose == 1) { print($indent . "sending \"$message\"\n"); } xpl_send_message( $xpl_socket, $xpl_port, 'xpl-cmnd', $xpl_id, $configuration{serialPort}, 'serPort.basic', ('command' => $message) ); } } } # process replies if ($schema eq lc('serPort.response')) { if ($source eq $configuration{serialPort}) { my $status = $body{status}; my %body = parse_EIB_telegram($status); if ($body{data}) { if ($verbose == 1) { print("Received status $status\n"); print($indent . "source = $body{source}, " . "group = $body{group}, " . "length = $body{length}, " . "data = $body{data}" . "\n" ); } xpl_send_message( $xpl_socket, $xpl_port, 'xpl-trig', $xpl_id, '*', "$class_id.basic", %body ); } } } } } } xpl_disconnect($xpl_socket, $xpl_id, $xpl_ip, $client_port); ################################################################################ # Documentation (access it with: perldoc ) # __END__ =head1 NAME xpl-eib.pl - Interfaces a Tapko EIB/KNX to RS232 bridge for EIB control =head1 SYNOPSIS xpl-eib.pl [options] =head1 DESCRIPTION This xPL client translates xPL messages to and from an C in order to send and receive EIB telegrams. The C command and trigger messages contain following items: =over 8 =item B Telegram source address, only for trigger messages. =item B Group address. =item B The length of the data in bytes, 0 for 1 to 6 bit data (such as on/off controls). =item B The data (or value) associated to the group address. =back =head1 OPTIONS =over 8 =item B<-h> Display a help message. =item B<-v> Be verbose. =item B<-p port> Specify the base port from which the client searches for a free port. If not specified, the client will take a default value. =item B<-n id> Specify the instance id (name). The id is limited to 12 characters. If not specified, it is constructed from the host name and the serial port controller name. =item B<-s str> Specify the C target's id. =item B<-t mins> Specify the number of minutes between two heartbeat messages. =item B<-w secs> Specify the number of seconds before sending the first heartbeat. This allows to start the client after the hub, thus eliminating an prospective startup delay of one heartbeat interval. =item B<-s str> Specify the C target's id. =back =head1 SERIAL PORT CLIENT The C must be configured as: =over 8 =item B -e '\x0A\x0D' =item B baudrate 9600 databits 8 parity none stopbits 1 handshake none =back =head1 TEST Connect the Tapko EIB/KNX to RS232 bridge to the PC's first serial port C. Make sure you have an C running on the machine. Start the serial port controller: C. Set the EIB to RS232 bridge in transparent mode: C followed by C. You should receive some answers from the EIB to RS232 bridge. Turn light C<1/1/1> on: C. Turn it off: C. Start the EIB interface: C<./xpl-eib.pl -v -s eib -n eib>. Start C in another terminal window. You should see messages from the C and from the C each time a telegram is sent or received. Turn light C<1/1/1> on: C. Turn it off: C. Change the serial port controller: C. The C should still respond to queries, but not to messages from the EIB. =head1 AUTHOR Francois Corthay, DSPC =head1 VERSION 2.0, 2012 =cut