File Coverage

blib/lib/HAM/Device/IcomCIVSerialIO.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             ##########################################################################
2             # HAM::Device::IcomCIVSerialIO -- Low Level IO Module for Icom CI-V radios
3             #
4             # Copyright (c) 2007 Ekkehard (Ekki) Plicht. All rights reserved.
5             #
6             # This program is free software; you can redistribute it and/or
7             # modify it under the same terms as Perl itself.
8             #
9             ##########################################################################
10            
11             =pod
12            
13             =head1 NAME
14            
15             HAM::Device::IcomCIVSerialIO - Low Level Serial IO for Icom CI-V Radios
16            
17             =head1 MODULE VERSION
18            
19             Version 0.02 02. Dec. 2007
20            
21             =head1 SYNOPSIS
22            
23             use HAM::Device::IcomCIVSerialIO;
24            
25             $ser = HAM::Device::IcomCIVSerialIO->new( '/dev/ttyS2', 19200, undef, debuglevel );
26             $ser->set_callback ( $thiscivadress, $myradio );
27             ...
28             $ser->send_civ( $thiscivadress, $own_adress, $command );
29             ...
30             $ser->clear_callback( $thiscivadress );
31             $ser->stop_serial();
32            
33             =head1 DESCRIPTION
34            
35             This module is the basic part of a bundle of modules that supports remote control of Icom radios equipped with the CI-V interface. It is used mainly by HAM::Device::IcomCIV and it's descendants.
36             To use it you need to open the serial port, send commands to the radio with send_civ() and receive callbacks (set with set_callback() to process received CI-V data.
37            
38             Note:
39            
40             This module is considered private, it will change it's interface and functionality in the future, when it will support multiple serial ports at the same time. Do not use it directly, use HAM::Device::IcomCIV or one of it's desceandants instead.
41            
42             =head2 EXPORTS
43            
44             Nothing by default.
45            
46             =head2 USES
47            
48             Device::SerialPort
49             Time::HiRes
50             Carp
51             $SIG{ALRM}
52            
53             =cut
54            
55             package HAM::Device::IcomCIVSerialIO;
56            
57 1     1   34609 use 5.008008;
  1         4  
  1         51  
58 1     1   5 use strict;
  1         2  
  1         35  
59 1     1   5 use warnings;
  1         7  
  1         48  
60 1     1   2217 use Device::SerialPort;
  0            
  0            
61             use Time::HiRes qw( ualarm );
62             use bytes;
63             use Carp;
64            
65             our $VERSION = '0.02';
66            
67             require Exporter;
68            
69             our @ISA = qw( Exporter );
70            
71             ###########################################################################
72             # Class Data
73            
74             my (%callbacks, $in_check_rx, $ser);
75             $SIG{ALRM} = \&check_rx; # Poll the receive buffer
76            
77             ###########################################################################
78             ###########################################################################
79            
80             =head1 METHODS
81            
82             =head2 new( device, baudrate, uselock, debug )
83            
84             Opens the serial device with baudrate, and returns handle of serial port. Dies on various reasons (lock not possible, open not possible etc.).
85            
86             This function also starts the ualarm() timer which polls regularly the incoming data. If data is received it is passed to the callback function.
87            
88             =over 4
89            
90             =item *
91            
92             I Is any valid devicename for a serial port, e.g. '/dev/ttyS1'.
93            
94             =item *
95            
96             I Is any valid baudrate supported by the attached Icom radio, e.g. 9600, 19200 etc. For performance reasons you should use 9600 and above.
97            
98             =item *
99            
100             I If defined will try to lock the serial device with a lockfile in /var/lock.
101             No locking if undefined.
102            
103             Note:
104             When using different distributions I found that Device::SerialPort sometimes uses 'sleep', at other times 'nanosleep' in the locking function. This leads to unexpected delays when using locking (2 seconds). If you experience this, don't use locking or patch your Device::SerialPort module.
105            
106             =item *
107            
108             I Debug flag, if >0 results in some diagnostic messages printed to STDERR.
109            
110             =back
111            
112             The new method clears the callback table! Set your callback[s] right after you have initiated a new serial device.
113            
114             =cut
115            
116             sub new {
117             my $class = shift;
118             my $self = {};
119             $self->{DEVICE} = shift;
120             $self->{BAUD} = shift;
121             $self->{USELOCK} = shift;
122             $self->{DEBUG} = shift;
123            
124             %callbacks = (); # initial clear callback table
125            
126             my $lockdevice = '';
127             if ( $self->{USELOCK} ) {
128             my @items = split "/", $self->{DEVICE};
129             $lockdevice = splice (@items,-1);
130             defined($lockdevice) || croak 'failed extracting serial device\n';
131             $lockdevice = '/var/lock/LCK..' . $lockdevice;
132             };
133            
134             $self->{SERDEV} = Device::SerialPort->new (
135             $self->{DEVICE},
136             0,
137             $lockdevice
138             ) || croak "Can't lock and open $self->{DEVICE}: $!";
139            
140             $self->{SERDEV}->baudrate($self->{BAUD}) || croak 'failed setting baudrate';
141             $self->{SERDEV}->parity('none') || croak 'failed setting parity to none';
142             $self->{SERDEV}->databits(8) || croak 'failed setting databits to 8';
143             $self->{SERDEV}->stopbits(1) || croak 'failed setting stopbits to 1';
144             $self->{SERDEV}->handshake('none') || croak 'failed setting handshake to none';
145             $self->{SERDEV}->datatype('raw') || croak 'failed setting datatype raw';
146             $self->{SERDEV}->write_settings || croak 'failed write settings';
147             $self->{SERDEV}->error_msg(1); # use built-in error messages
148             $self->{SERDEV}->user_msg(1); # ?
149             $self->{SERDEV}->read_const_time(100); # important for nice behaviour, otherwise hogs cpu
150             $self->{SERDEV}->read_char_time(100); # dto.
151            
152             $self->{SERDEV}->are_match( "\xFD" ); # end of CI-V data telegram
153            
154             bless ( $self, $class );
155            
156             $ser = $self->{SERDEV};
157            
158             # Finally set up alarm for polling
159             ualarm(100);
160            
161             return $self;
162             };
163            
164             =pod
165            
166             =head2 stop_serial( )
167            
168             Closes the serial port. Returns nothing.
169            
170             =cut
171            
172             sub stop_serial {
173             my $self = shift;
174             undef $self->{SERDEV};
175             };
176            
177             sub DESTROY {
178             my $self = shift;
179             undef $self->{SERDEV};
180             }
181            
182             =pod
183            
184             =head2 send_civ( to_adr, fm_adr, command )
185            
186             Assembles the data (to_adr, fm_adr, command) with header and tail of the CI-V
187             frame and sends this out over the serial line. Returns true if all data was
188             sent ok, otherwise false.
189            
190             =over 4
191            
192             =item *
193            
194             I Is the Icom CI-V bus adress of the radio to which this command is directed.
195             Must be Integer, will be converted to a char.
196            
197             =item *
198            
199             I Is the senders adress, usually 0xE0 for the controlling computer. Must be integer, will be converted to a char.
200            
201             =item *
202            
203             I Is the data to be sent (a string of bytes), everything after the adresses and up to, but not including the final 0xFD.
204            
205             =back
206            
207             =cut
208            
209            
210             sub send_civ {
211             my $self = shift;
212             my ($to, $fm, $cmd) = @_;
213            
214             # Incoming data is probably flagged as UTF-8,
215             # which leads to uf8ness of concatenated string,
216             # which leads to 0xFE etc. being coded as \x{C3BE} (or so)
217             # So I remove utf8ness
218             utf8::downgrade($cmd);
219             my $tele = chr(0xFE) . chr(0xFE) . chr($to) . chr($fm) . $cmd . chr(0xFD);
220            
221             if ( $self->{DEBUG} ) {
222             my $th = s2hex($tele);
223             warn "Tx: $th\n";
224             };
225            
226             return ( length($cmd) +5 == $self->{SERDEV}->write($tele) ) ? 1 : 0;
227             };
228            
229             ###
230             # Called by SIGALARM every 100 msec.
231             # Class Function!
232             sub check_rx {
233             # protect against re-entry if callback takes very long
234             return if ($in_check_rx);
235             $in_check_rx = 1;
236            
237             my $rxdata = $ser->lookfor;
238             if ($rxdata) {
239             my $th = s2hex($rxdata);
240             warn "Rx: $th\n";
241            
242            
243             # If from-adress is in callbacks, it's
244             # a) not my own echo
245             # b) a valid adress which I am responsible for
246             # TODO Improvement: transfer ref to rxdata array, not array itself
247             if ( exists $callbacks{ substr( $rxdata, 3, 1 ) } ) {
248             $callbacks{ substr( $rxdata, 3, 1 ) }->process_buffer($rxdata);
249             };
250             };
251             ualarm ( 100 ); # restart alarm
252             $in_check_rx = 0;
253             };
254            
255             =pod
256            
257             =head2 set_callback( civadress, object )
258            
259             Sets the callback object reference which is used for callback routine 'process_buffer', to be called whenever a complete CI-V telegram has been received by the serial routine. It's the responsibilty of this called routine to decode and act on the received telegram.
260            
261             This method must be called with the appropiate data for each upper level instance of IcomCIV, otherwise it won't work!
262            
263             =over 4
264            
265             =item *
266            
267             I The CI-V bus adress for which this callback adress feels responsible, as integer, not char. Callbacks are multiplexed to different IcomCIV instances, depending on CI-V adress. This enables an application to have several instances of IcomCIV and handle each separately.
268            
269             Currently this does not allow for duplicate CI-V bus adresses on the same serial port. So if you have two or more identical devices with identical adresses, you have to change them to make then unique to each radio. This is likely to change in the future, using a unique identifier for each radio (and will break the API).
270            
271             =item *
272            
273             I The blessed reference of a an instance of a IcomCIV object (or descendant thereof). The actual method which is called is named 'process_buffer' and receives one parameter (besides the usual $self), and that is the entire CI-V telegram from the leading 0xFE 0xFE up to and including the final 0xFD.
274            
275             =back
276            
277             =cut
278            
279             sub set_callback {
280             my $self = shift;
281             my ($civ, $obj) = @_;
282             $callbacks{ chr($civ) } = $obj;
283             };
284            
285             =pod
286            
287             =head2 clear_callback ( civadress )
288            
289             Deletes this CI-V bus adress from the callback table. Returns true on success, false if adress was not in table.
290            
291             =over 4
292            
293             =item *
294            
295             I The CI-V bus adress for which this callback adress feels responsible, as integer, not char.print "Serdev: $self->{SERDEV}\n";
296            
297             =back
298            
299             =cut
300            
301             sub clear_callback {
302             my $self = shift;
303             my $adr = chr(shift);
304             if ( exists $callbacks{$adr}) {
305             delete $callbacks{$adr};
306             return 1;
307             } else {
308             return 0;
309             };
310             }
311            
312             # For debugging only
313             sub s2hex {
314             # in: scalar
315             # out: string with each byte of input in 2-digit hex. space separated
316             #my $self = shift;
317             my ($c, $result, $tmp);
318             $tmp = shift;
319             my @bytes = unpack("C*", $tmp);
320             $result="";
321             foreach $c (@bytes) {
322             $result = $result . sprintf ("%02lX ", $c);
323             };
324             return $result;
325             }
326            
327            
328            
329             =pod
330            
331             =head1 SEE ALSO
332            
333             HAM::Device::IcomCIV
334             HAM::Device::IcomICR8500
335             HAM::Device::IcomICR75
336             and other IcomCIV modules
337            
338             Icom CI-V Protocol Specification by Icom
339             Documentation of the CI-V protocol in any recent Icom radio manual
340             Documentation of the CI-V protocol at the authors website:
341             http://www.df4or.de
342            
343             If you are looking for a library which supports more radios than just Icoms, look for 'grig' or 'hamlib'.
344            
345             =head1 Portability
346            
347             Due to the use of %SIG and Time::Hires this module is probably not very portable. The author has developed and used it only on various Linux platforms. If you have any feedback on the use of this module on other platforms, please let the author know. Thanks.
348            
349             =head1 AUTHOR
350            
351             Ekkehard (Ekki) Plicht, DF4OR, Eekki@plicht.deE
352            
353             =head1 COPYRIGHT AND LICENSE
354            
355             Copyright (c) 2007 Ekkehard (Ekki) Plicht. All rights reserved.
356            
357             This program is free software; you can redistribute it and/or
358             modify it under the same terms as Perl itself.
359            
360             =cut
361            
362             1;
363             __END__