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__