File Coverage

blib/lib/NexStarCtl.pm
Criterion Covered Total %
statement 18 641 2.8
branch 0 238 0.0
condition 0 95 0.0
subroutine 6 66 9.0
pod 55 58 94.8
total 79 1098 7.1


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2              
3             ########################################################
4             # NexCtl - NexStar control library
5             #
6             #
7             # (c)2013-2014 by Rumen G.Bogdanovski
8             ########################################################
9              
10             =head1 NAME
11              
12             NexStarCtl - API to control NexStar compatible telescopes
13              
14             =head1 SYNOPSIS
15              
16             use NexStarCtl;
17            
18             my $port = open_telescope_port("/dev/XXX");
19             if (!defined $port) {
20             print "Can not open communication port.\n";
21             exit;
22             }
23            
24             # check if the mount is aligned
25             if (tc_check_align($port)) {
26             print "The telescope is aligned.\n";
27             } else {
28             print "The telescope is not aligned.\n";
29             }
30            
31             # Read the stored coordinates of the location
32             my ($lon,$lat) = tc_get_location_str($port);
33             if (! defined($lon)) {
34             print "Telescope did not respond\n";
35             close_telescope_port($port);
36             exit;
37             }
38             print "Location coordinates:\n LON=$lon\n LAT=$lat\n";
39              
40             my ($date,$time,$tz,$dst) = tc_get_time_str($port);
41             # ...
42             # Do some other stuff
43             # ...
44            
45             close_telescope_port($port);
46              
47             =head1 DESCRIPTION
48              
49             This module implements the serial commands supported by the Celestron NexStar hand control. This applies to the NexStar GPS,
50             NexStar GPS-SA, NexStar iSeries, NexStar SE Series, NexStar GT, CPC, SLT, Advanced-VX, Advanced-GT, and CGE mounts.
51              
52             Communication to the hand control is 9600 bits/sec, no parity and one stop bit via the RS-232 port on the base of the
53             hand control.
54              
55             For extended example how to use this perl module look in to the distribution folder for nexstarctl/nexstarctl.pl.
56             This program is a complete console tool to control NexStar telesctopes based on NexStarCtl module.
57              
58             =cut
59              
60             package NexStarCtl;
61              
62 1     1   24628 use POSIX;
  1         7630  
  1         7  
63 1     1   4717 use Time::Local;
  1         1973  
  1         71  
64 1     1   7 use strict;
  1         7  
  1         29  
65 1     1   5 use Exporter;
  1         1  
  1         207  
66              
67             if ($^O eq "MSWin32") {
68             eval "use Win32::SerialPort"; die $@ if $@;
69             } else {
70 1     1   1188 eval "use Device::SerialPort"; die $@ if $@;
  1         23384  
  1         36  
71             }
72              
73             our @ISA = qw(Exporter);
74             our @EXPORT = qw(
75             VERSION
76            
77             DEG2RAD RAD2DEG
78             notnum precess round
79             d2hms d2dms d2dm dms2d hms2d
80             dd2nex dd2pnex nex2dd pnex2dd
81              
82             get_model_name
83              
84             open_telescope_port
85             close_telescope_port
86             read_telescope
87              
88             tc_pass_through_cmd
89             tc_check_align
90             tc_goto_rade tc_goto_rade_p
91             tc_goto_azalt tc_goto_azalt_p
92             tc_get_rade tc_get_rade_p
93             tc_get_azalt tc_get_azalt_p
94             tc_sync_rade tc_sync_rade_p
95             tc_goto_in_progress tc_goto_cancel
96             tc_get_model
97             tc_get_version
98             tc_echo
99             tc_get_location tc_get_location_str
100             tc_get_time tc_get_time_str
101             tc_set_time tc_set_location
102             tc_get_tracking_mode tc_set_tracking_mode
103             tc_slew_variable tc_slew_fixed
104             tc_get_autoguide_rate tc_set_autoguide_rate
105             tc_get_backlash tc_set_backlash
106              
107             pec_seek_index pec_index_found
108             pec_record pec_record_complete
109             pec_playback pec_get_playback_index
110             pec_get_data_len pec_set_data pec_get_data
111              
112             TC_TRACK_OFF
113             TC_TRACK_ALT_AZ
114             TC_TRACK_EQ_NORTH
115             TC_TRACK_EQ_SOUTH
116              
117             TC_DIR_POSITIVE
118             TC_DIR_NEGATIVE
119              
120             TC_AXIS_RA_AZM
121             TC_AXIS_DE_ALT
122              
123             PEC_START
124             PEC_STOP
125             );
126              
127             our $VERSION = "0.12";
128              
129             use constant {
130 1         7584 TC_TRACK_OFF => 0,
131             TC_TRACK_ALT_AZ => 1,
132             TC_TRACK_EQ_NORTH => 2,
133             TC_TRACK_EQ_SOUTH => 3,
134              
135             TC_DIR_POSITIVE => 1,
136             TC_DIR_NEGATIVE => 0,
137              
138             TC_AXIS_RA_AZM => 1,
139             TC_AXIS_DE_ALT => 0,
140              
141             _TC_DIR_POSITIVE => 6,
142             _TC_DIR_NEGATIVE => 7,
143              
144             _TC_AXIS_RA_AZM => 16,
145             _TC_AXIS_DE_ALT => 17,
146              
147             PEC_START => 1,
148             PEC_STOP => 0,
149              
150             DEG2RAD => 3.1415926535897932384626433832795/180.0,
151             RAD2DEG => 180.0/3.1415926535897932384626433832795
152 1     1   6 };
  1         2  
153              
154             my %mounts = (
155             1 => "NexStar GPS Series",
156             3 => "NexStar i-Series",
157             4 => "NexStar i-Series SE",
158             5 => "CGE",
159             6 => "Advanced GT",
160             7 => "SLT",
161             9 => "CPC",
162             10 => "GT",
163             11 => "NexStar 4/5 SE",
164             12 => "NexStar 6/8 SE",
165             14 => "CGEM",
166             20 => "Advanced VX"
167             );
168              
169             =head1 TELESCOPE COMMUNICATION
170              
171             =over 8
172              
173             =item open_telescope_port(port_name)
174              
175             Opens a communication port to the telescope by name (like "/dev/ttyUSB0") and
176             returns it to be used in other functions. In case of error undef is returned.
177              
178             =cut
179             sub open_telescope_port($) {
180 0     0 1   my ($tty) = @_;
181              
182 0           my $port;
183 0 0         if ($^O eq "MSWin32") {
184 0           $port = new Win32::SerialPort($tty);
185             } else {
186 0           $port = new Device::SerialPort($tty);
187             }
188 0 0         if (! defined $port) {return undef;}
  0            
189             #$port->debug(1);
190 0           $port->baudrate(9600);
191 0           $port->parity("none");
192 0           $port->databits(8);
193 0           $port->stopbits(1);
194 0           $port->datatype('raw');
195 0           $port->write_settings;
196 0           $port->read_char_time(0); # don't wait for each character
197 0           $port->read_const_time(4000); # 4 second per unfulfilled "read" call
198 0           return $port;
199             }
200              
201             =item read_telescope(port, len)
202              
203             Reads data from the telescope. On error or in case less than len bytes are
204             read undef is returned.
205              
206             =cut
207             sub read_telescope($$) {
208 0     0 1   my ($port,$len) = @_;
209 0           my $response;
210             my $char;
211 0           my $count;
212 0           my $total=0;
213 0           do {
214 0           ($count,$char)=$port->read(1);
215 0 0         if ($count == 0) { return undef; }
  0            
216 0           $total += $count;
217 0           $response .= $char;
218             } while ($total < $len);
219            
220             # if the last byte is not '#', this means that the device did
221             # not respond and the next byte should be '#' (hopefully)
222 0 0         if ($char ne "#") {
223 0           ($count,$char)=$port->read(1);
224 0           return undef;
225             }
226 0           return $response;
227             }
228              
229             =item write_telescope(port, data)
230              
231             Writes data to the telescope and the result of write is returned.
232              
233             =cut
234             sub write_telescope($$) {
235 0     0 1   my ($port, $data) = @_;
236            
237 0           return $port->write($data);
238             }
239              
240             =item close_telescope_port(port)
241              
242             Closes the communication port to the telescope.
243              
244             =cut
245              
246             sub close_telescope_port($) {
247 0     0 1   my ($port) = @_;
248            
249 0           $port->close;
250 0           undef $port;
251             }
252              
253             #
254             # Telescope Commands
255             #
256              
257             =back
258              
259             =head1 TELESCOPE COMMANDS
260              
261             =over 8
262              
263             =item tc_check_align(port)
264              
265             If the telescope is aligned 1 is returned else 0 is returned. If no response received,
266             undef is returned.
267              
268             =cut
269             sub tc_check_align($) {
270 0     0 1   my ($port) = @_;
271              
272 0           $port->write("J");
273 0           my $response = read_telescope($port,2);
274 0 0         if (defined $response) {
275 0           return ord(substr($response, 0, 1));
276             } else {
277 0           return undef;
278             }
279             }
280              
281             =item tc_goto_rade(port, ra, de)
282              
283             =item tc_goto_rade_p(port, ra, de)
284              
285             Slew the telescope to RA/DEC coordinates ra, de (in decimal degrees).
286             Function tc_goto_rade_p uses precise GOTO.
287             If RA is not in [0;360] function returns -1. If DEC is not in [-90;90] -2 is returned.
288             If no response received, undef is returned.
289              
290             =cut
291             sub tc_goto_rade {
292 0     0 1   my ($port, $ra, $de, $precise) = @_;
293 0 0 0       if (($ra < -0.1) or ($ra > 360.1)) {
294 0           return -1;
295             }
296 0 0 0       if (($de < -90.1) or ($de > 90.1)) {
297 0           return -2;
298             }
299 0           my $nex;
300 0 0 0       if((defined $precise) and ($precise =! 0)) {
301 0           $nex=dd2pnex($ra, $de);
302 0           $port->write("r".$nex);
303             } else {
304 0           $nex=dd2nex($ra, $de);
305 0           $port->write("R".$nex);
306             }
307 0           my $response = read_telescope($port,1);
308 0 0         if (defined $response) {
309 0           return 1;
310             } else {
311 0           return undef;
312             }
313             }
314              
315             sub tc_goto_rade_p {
316 0     0 1   return tc_goto_rade(@_, 1);
317             }
318              
319             =item tc_goto_azalt(port, az, alt)
320              
321             =item tc_goto_azalt_p(port, az, alt)
322              
323             Slew the telescope to AZ/ALT coordinates az, alt (in decimal degrees). Function tc_goto_azalt_p uses precise GOTO.
324             If AZ is not in [0;360] function returns -1. If ALT is not in [-90;90] -2 is returned.
325             If no response received, undef is returned.
326              
327             =cut
328             sub tc_goto_azalt {
329 0     0 1   my ($port, $az, $alt, $precise) = @_;
330 0 0 0       if (($az < -0.1) or ($az > 360.1)) {
331 0           return -1;
332             }
333 0 0 0       if (($alt < -90.1) or ($alt > 90.1)) {
334 0           return -2;
335             }
336 0           my $nex;
337 0 0 0       if((defined $precise) and ($precise =! 0)) {
338 0           $nex=dd2pnex($az, $alt);
339 0           $port->write("b".$nex);
340             } else {
341 0           $nex=dd2nex($az, $alt);
342 0           $port->write("B".$nex);
343             }
344 0           my $response = read_telescope($port,1);
345 0 0         if (defined $response) {
346 0           return 1;
347             } else {
348 0           return undef;
349             }
350             }
351              
352             sub tc_goto_azalt_p {
353 0     0 1   return tc_goto_azalt(@_, 1);
354             }
355              
356             =item tc_get_rade(port)
357              
358             =item tc_get_rade_p(port)
359              
360             Returns the the current telescope RA/DEC coordinates ra, de (in decimal degrees). Function
361             tc_get_rade_p uses precise GET. If no response received, undef is returned.
362              
363             =cut
364             sub tc_get_rade {
365 0     0 1   my ($port, $precise) = @_;
366            
367 0           my $ra;
368             my $de;
369            
370 0 0 0       if((defined $precise) and ($precise =! 0)) {
371 0           $port->write("e");
372 0           my $response = read_telescope($port, 18);
373 0 0         if (! defined $response) {
374 0           return undef;
375             }
376 0           ($ra,$de) = pnex2dd($response);
377             } else {
378 0           $port->write("E");
379 0           my $response = read_telescope($port, 10);
380 0 0         if (! defined $response) {
381 0           return undef;
382             }
383 0           ($ra,$de) = nex2dd($response);
384             }
385            
386 0           return ($ra, $de);
387             }
388              
389             sub tc_get_rade_p {
390 0     0 1   return tc_get_rade(@_, 1);
391             }
392              
393             =item tc_get_azalt(port)
394              
395             =item tc_get_azalt_p(port)
396              
397             Returns the the currents telescope AZ/ALT coordinates az, alt (in decimal degrees). Function
398             tc_get_azalt_p uses precise GET. If no response received, undef is returned.
399              
400             =cut
401             sub tc_get_azalt {
402 0     0 1   my ($port, $precise) = @_;
403            
404 0           my $az;
405             my $alt;
406            
407 0 0 0       if((defined $precise) and ($precise =! 0)) {
408 0           $port->write("z");
409 0           my $response = read_telescope($port, 18);
410 0 0         if (! defined $response) {
411 0           return undef;
412             }
413 0           ($az,$alt) = pnex2dd($response);
414             } else {
415 0           $port->write("Z");
416 0           my $response = read_telescope($port, 10);
417 0 0         if (! defined $response) {
418 0           return undef;
419             }
420 0           ($az,$alt) = nex2dd($response);
421             }
422            
423 0           return ($az, $alt);
424             }
425              
426             sub tc_get_azalt_p {
427 0     0 1   return tc_get_azalt(@_, 1);
428             }
429              
430             =item tc_sync_rade(port, ra, de)
431              
432             =item tc_sync_rade_p(port, ra, de)
433              
434             Syncs the telescope to RA/DEC coordinates ra, de (in decimal degrees). Function tc_goto_azalt_p uses precise sync.
435             If RA is not in [0;360] function returns -1. If DEC is not in [-90;90] -2 is returned.
436             If no response received, undef is returned.
437              
438             =cut
439             sub tc_sync_rade {
440 0     0 1   my ($port, $ra, $de, $precise) = @_;
441 0 0 0       if (($ra < 0) or ($ra > 360)) {
442 0           return -1;
443             }
444 0 0 0       if (($de < -90) or ($de > 90)) {
445 0           return -2;
446             }
447 0           my $nex;
448 0 0 0       if((defined $precise) and ($precise =! 0)) {
449 0           $nex=dd2pnex($ra, $de);
450 0           $port->write("s".$nex);
451             } else {
452 0           $nex=dd2nex($ra, $de);
453 0           $port->write("S".$nex);
454             }
455 0           my $response = read_telescope($port, 1);
456 0 0         if (defined $response) {
457 0           return 1;
458             } else {
459 0           return undef;
460             }
461             }
462              
463             sub tc_sync_rade_p {
464 0     0 1   return tc_sync_rade(@_, 1);
465             }
466              
467             =item tc_goto_in_progress(port)
468              
469             Returns 1 if GOTO is in progress else 0 is returned. If no response received, undef is returned.
470              
471             =cut
472             sub tc_goto_in_progress($) {
473 0     0 1   my ($port) = @_;
474              
475 0           $port->write("L");
476 0           my $response = read_telescope($port, 2);
477 0 0         if (defined $response) {
478 0           return substr($response, 0, 1);
479             } else {
480 0           return undef;
481             }
482             }
483              
484             =item tc_goto_cancel(port)
485              
486             Cancels the GOTO operation. On success 1 is returned. If no response received, undef is returned.
487              
488             =cut
489             sub tc_goto_cancel($) {
490 0     0 1   my ($port) = @_;
491            
492 0           $port->write("M");
493 0           my $response = read_telescope($port, 1);
494 0 0         if (defined $response) {
495 0           return 1;
496             } else {
497 0           return undef;
498             }
499             }
500              
501             =item tc_echo(port, char)
502              
503             Checks the communication with the telescope. This function sends char to the telescope and
504             returns the echo received. If no response received, undef is returned.
505              
506             =cut
507             sub tc_echo($$) {
508 0     0 1   my ($port, $char) = @_;
509              
510 0           $port->write("K".substr($char, 0, 1));
511 0           my $response = read_telescope($port, 2);
512 0 0         if (defined $response) {
513 0           return substr($response, 0, 1);
514             } else {
515 0           return undef;
516             }
517             }
518              
519             =item tc_get_model(port)
520              
521             This function returns the mount model as a number. See CELESTRON documentation.
522             If no response received, undef is returned.
523              
524             =cut
525             sub tc_get_model($) {
526 0     0 1   my ($port) = @_;
527              
528 0           $port->write("m");
529 0           my $response = read_telescope($port, 2);
530 0 0         if (defined $response) {
531 0           return ord(substr($response, 0, 1));
532             } else {
533 0           return undef;
534             }
535             }
536              
537             =item tc_get_version(port)
538              
539             This function returns the mount version as a number. See CELESTRON documentation.
540             If no response received, undef is returned.
541              
542             =cut
543             sub tc_get_version($) {
544 0     0 1   my ($port) = @_;
545              
546 0           $port->write("V");
547 0           my $response = read_telescope($port, 3);
548 0 0         if (defined $response) {
549 0           return ord(substr($response, 0, 1)).
550             ".".ord(substr($response, 1, 1));
551             } else {
552 0           return undef;
553             }
554             }
555              
556              
557             =item tc_get_location(port)
558              
559             This function returns the stored location coordinates lon, lat in decimal degrees.
560             Negative longitude is WEST. Negative latitude is SOUTH.
561             If no response received, undef is returned.
562              
563             =item tc_get_location_str(port)
564              
565             This function returns the stored location coordinates lon and lat as strings.
566             If no response received, undef is returned.
567              
568             =cut
569             sub tc_get_location {
570 0     0 1   my ($port,$str) = @_;
571              
572 0           $port->write("w");
573 0           my $response = read_telescope($port, 9);
574 0 0         if (! defined $response) {
575 0           return undef;
576             }
577            
578 0           my $latd=ord(substr($response, 0, 1));
579 0           my $latm=ord(substr($response, 1, 1));
580 0           my $lats=ord(substr($response, 2, 1));
581 0           my $lato=ord(substr($response, 3, 1));
582 0           my $lond=ord(substr($response, 4, 1));
583 0           my $lonm=ord(substr($response, 5, 1));
584 0           my $lons=ord(substr($response, 6, 1));
585 0           my $lono=ord(substr($response, 7, 1));
586            
587 0           my $lon;
588             my $lat;
589 0 0 0       if((defined $str) and ($str =! 0)) {
590 0           $lat=sprintf("%d %02d'%02d\"N",$latd,$latm,$lats);
591 0           $lon=sprintf("%d %02d'%02d\"E",$lond,$lonm,$lons);
592 0 0         if ($lato) {
593 0           $lat =~ s/N$/S/;
594             }
595 0 0         if ($lono) {
596 0           $lon =~ s/E$/W/;
597             }
598             } else {
599 0           $lat=($latd + $latm/60.0 + $lats/3600.0);
600 0           $lon=($lond + $lonm/60.0 + $lons/3600.0);
601 0 0         if ($lato) {
602 0           $lat *= -1;
603             }
604 0 0         if ($lono) {
605 0           $lon *= -1;
606             }
607             }
608 0           return ($lon, $lat);
609             }
610              
611             sub tc_get_location_str {
612 0     0 1   return tc_get_location(@_, 1);
613             }
614              
615             =item tc_set_location(port,lon,lat)
616              
617             This function sets the location coordinates lon, lat in decimal degrees.
618             Negative longitude is WEST. Negative latitude is SOUTH.
619             If the coordinates are invalid -1 is returned.
620             If no response received, undef is returned.
621              
622             =cut
623             sub tc_set_location {
624 0     0 1   my ($port,$lon,$lat) = @_;
625 0           my $issouth = 0;
626 0           my $iswest = 0;
627            
628 0 0         if ($lon < 0) {
629 0           $lon *= -1;
630 0           $issouth = 1;
631             }
632 0 0         if ($lat < 0) {
633 0           $lat *= -1;
634 0           $iswest = 1;
635             }
636            
637 0 0 0       if (($lat > 90) or ($lon > 180)) {
638 0           return -1;
639             }
640            
641 0           my ($lond,$lonm,$lons) = d2dms2($lon);
642 0           my ($latd,$latm,$lats) = d2dms2($lat);
643              
644 0           $port->write("W");
645 0           $port->write(chr($latd));
646 0           $port->write(chr($latm));
647 0           $port->write(chr($lats));
648 0           $port->write(chr($issouth));
649 0           $port->write(chr($lond));
650 0           $port->write(chr($lonm));
651 0           $port->write(chr($lons));
652 0           $port->write(chr($iswest));
653              
654 0           my $response = read_telescope($port, 1);
655 0 0         if (defined $response) {
656 0           return 1;
657             } else {
658 0           return undef;
659             }
660             }
661              
662             =item tc_get_time(port)
663              
664             This function returns the stored time (in unixtime format), timezone (in hours) and daylight saving time(0|1).
665             If no response received, undef is returned.
666              
667             =item tc_get_time_str(port)
668              
669             This function returns the stored date, time (as strings), timezone (in hours) and daylight saving time(0|1).
670             If no response received, undef is returned.
671              
672             =cut
673              
674             sub tc_get_time {
675 0     0 1   my ($port,$str) = @_;
676              
677 0           $port->write("h");
678 0           my $response = read_telescope($port, 9);
679 0 0         if (! defined $response) {
680 0           return undef;
681             }
682            
683 0           my $h=ord(substr($response, 0, 1));
684 0           my $m=ord(substr($response, 1, 1));
685 0           my $s=ord(substr($response, 2, 1));
686 0           my $mon=ord(substr($response, 3, 1));
687 0           my $day=ord(substr($response, 4, 1));
688 0           my $year=ord(substr($response, 5, 1))+2000;
689 0           my $tz=ord(substr($response, 6, 1));
690 0 0         $tz -= 256 if ($tz > 12);
691 0           my $dst=ord(substr($response, 7, 1));
692            
693 0 0 0       if((defined $str) and ($str =! 0)) {
694 0           my $time=sprintf("%2d:%02d:%02d",$h,$m,$s);
695 0           my $date=sprintf("%02d-%02d-%04d",$day,$mon,$year);
696 0           return ($date,$time, $tz, $dst);
697             } else {
698 0           my $time = timelocal($s,$m,$h,$day,$mon-1,$year);
699 0           return ($time, $tz, $dst);
700             }
701             }
702              
703             sub tc_get_time_str {
704 0     0 1   return tc_get_time(@_, 1);
705             }
706              
707             =item tc_set_time(port, time, timezone, daylightsaving)
708              
709             This function sets the time (in unix time format), timezone (in hours) and daylight saving time(0|1).
710             On success 1 is returned.
711             If no response received, undef is returned. If the mount is known to have RTC
712             (currently only CGE and AdvancedVX) the date/time is set to RTC too.
713              
714             =cut
715             sub tc_set_time {
716 0     0 1   my ($port, $time, $tz, $dst) = @_;
717              
718 0           my $timezone = $tz;
719 0 0         $tz += 256 if ($tz < 0);
720              
721 0 0 0       if ((defined $dst) and ($dst != 0)) {
722 0           $dst=1;
723             } else {
724 0           $dst=0;
725             }
726              
727 0           my ($s,$m,$h,$day,$mon,$year,$wday,$yday,$isdst) = localtime($time);
728              
729 0           $port->write("H");
730 0           $port->write(chr($h));
731 0           $port->write(chr($m));
732 0           $port->write(chr($s));
733 0           $port->write(chr($mon+1));
734 0           $port->write(chr($day));
735             # $year is actual_year-1900
736             # here is required actual_year-2000
737             # so we need $year-100
738 0           $port->write(chr($year-100));
739 0           $port->write(chr($tz));
740 0           $port->write(chr($dst));
741              
742 0           my $response = read_telescope($port, 1);
743 0 0         if (! defined $response) {
744 0           return undef;
745             }
746              
747 0           my $model = tc_get_model($port);
748             # If the mount has RTC set date/time to RTC too
749             # I only know CGE(5) and AdvancedVX(20) to have RTC
750 0 0 0       if (($model == 5) or ($model == 20)) {
751             # RTC expects UT, convert localtime to UT
752 0           my ($s,$m,$h,$day,$mon,$year,$wday,$yday,$isdst) = localtime($time - (($timezone + $dst) * 3600));
753              
754             # Set year
755 0           my $response = tc_pass_through_cmd($port, 3, 178, 132,
756             int(($year + 1900) / 256),
757             int(($year + 1900) % 256), 0, 0);
758 0 0         if (! defined $response) {
759 0           return undef;
760             }
761              
762             # Set month and day
763 0           my $response = tc_pass_through_cmd($port, 3, 178, 131, $mon+1, $day, 0, 0);
764 0 0         if (! defined $response) {
765 0           return undef;
766             }
767              
768             # Set time
769 0           my $response = tc_pass_through_cmd($port, 4, 178, 179, $h, $m, $s, 0);
770 0 0         if (! defined $response) {
771 0           return undef;
772             }
773             }
774 0           return 1;
775             }
776              
777             =item tc_get_tracking_mode(port)
778              
779             Reads the tracking mode of the mount and returns one of the folowing:
780             TC_TRACK_OFF, TC_TRACK_ALT_AZ, TC_TRACK_EQ_NORTH, TC_REACK_EQ_SOUTH.
781             If no response received, undef is returned.
782              
783             =cut
784             sub tc_get_tracking_mode($) {
785 0     0 1   my ($port) = @_;
786              
787 0           $port->write("t");
788 0           my $response = read_telescope($port, 2);
789 0 0         if (defined $response) {
790 0           return ord(substr($response, 0, 1));
791             } else {
792 0           return undef;
793             }
794             }
795              
796             =item tc_set_tracking_mode(port, mode)
797              
798             Sets the tracking mode of the mount to one of the folowing:
799             TC_TRACK_OFF, TC_TRACK_ALT_AZ, TC_TRACK_EQ_NORTH, TC_REACK_EQ_SOUTH.
800             If the mode is not one of the listed -1 is returned.
801             If no response received, undef is returned.
802              
803             =cut
804             sub tc_set_tracking_mode($$) {
805 0     0 1   my ($port,$mode) = @_;
806              
807 0 0 0       if (($mode < 0) or ($mode > 3)) {
808 0           return -1;
809             }
810 0           $port->write("T");
811 0           $port->write(chr($mode));
812 0           my $response = read_telescope($port, 1);
813 0 0         if (defined $response) {
814 0           return 1;
815             } else {
816 0           return undef;
817             }
818             }
819              
820             =item tc_slew_fixed(port, axis, direction, rate)
821              
822             Move the telescope the telescope around a specified axis in a given direction with fixed rate.
823              
824             Accepted values for axis are TC_AXIS_RA_AZM and TC_AXIS_DE_ALT. Direction can accept values
825             TC_DIR_POSITIVE and TC_DIR_NEGATIVE. Rate is from 0 to 9. Where 0 stops slewing and 9 is
826             the fastest speed.
827              
828             On success 1 is returned. If rate is out of range -1 is returned.
829             If no response received, undef is returned.
830              
831             =cut
832             sub tc_slew_fixed {
833 0     0 1   my ($port,$axis,$direction,$rate) = @_;
834            
835 0 0         if ($axis>0) {
836 0           $axis = _TC_AXIS_RA_AZM;
837             } else {
838 0           $axis = _TC_AXIS_DE_ALT;
839             }
840              
841 0 0         if ($direction > 0) {
842 0           $direction = _TC_DIR_POSITIVE + 30;
843             } else {
844 0           $direction = _TC_DIR_NEGATIVE + 30;
845             }
846            
847 0 0 0       if (($rate < 0) or ($rate > 9)) {
848 0           return -1;
849             }
850 0           $rate = int($rate);
851              
852 0           my $response = tc_pass_through_cmd($port, 2, $axis, $direction, $rate, 0, 0, 0);
853 0 0         if (defined $response) {
854 0           return 1;
855             } else {
856 0           return undef;
857             }
858             }
859              
860             =item tc_slew_variable(port, axis, direction, rate)
861              
862             Move the telescope the telescope around a specified axis in a given direction with specified rate.
863              
864             Accepted values for axis are TC_AXIS_RA_AZM and TC_AXIS_DE_ALT. Direction can accept values
865             TC_DIR_POSITIVE and TC_DIR_NEGATIVE. Rate is the speed in arcsec/sec. For example 3600
866             represents 1degree/sec.
867              
868             On success 1 is returned. If no response received, undef is returned.
869              
870             =cut
871             sub tc_slew_variable {
872 0     0 1   my ($port,$axis,$direction,$rate) = @_;
873              
874 0 0         if ($axis>0) {
875 0           $axis = _TC_AXIS_RA_AZM;
876             } else {
877 0           $axis = _TC_AXIS_DE_ALT;
878             }
879              
880 0 0         if ($direction > 0) {
881 0           $direction = _TC_DIR_POSITIVE;
882             } else {
883 0           $direction = _TC_DIR_NEGATIVE;
884             }
885            
886 0           $rate = int(4*$rate);
887 0           my $rateH = int($rate / 256);
888 0           my $rateL = $rate % 256;
889             #print "RATEf : $rateH $rateL\n";
890              
891 0           my $response = tc_pass_through_cmd($port, 3, $axis, $direction, $rateH, $rateL, 0, 0);
892 0 0         if (defined $response) {
893 0           return 1;
894             } else {
895 0           return undef;
896             }
897             }
898              
899             =item get_model_name(model_id)
900              
901             Return the name of the mount by the id from tc_get_model().
902             If the mount is not known undef is returned.
903              
904             =cut
905              
906             sub get_model_name($) {
907 0     0 1   my ($model_id) = @_;
908 0           return $mounts{$model_id};
909             }
910              
911             =back
912              
913             =head1 AUX COMMANDS
914              
915             The following commands are not officially documented by Celestron. Please note that these
916             commands are reverse engineered and may not work exactly as expected.
917              
918             =over 8
919              
920             =item tc_get_autoguide_rate(port, axis)
921              
922             Get autoguide rate for the given axis in percents of the sidereal rate.
923              
924             Accepted values for axis are TC_AXIS_RA_AZM and TC_AXIS_DE_ALT.
925              
926             On success current value of autoguide rate is returned in the range [0-99].
927             If no response received, undef is returned.
928             =cut
929             sub tc_get_autoguide_rate($$) {
930 0     0 1   my ($port,$axis) = @_;
931              
932 0 0         if ($axis > 0) {
933 0           $axis = _TC_AXIS_RA_AZM;
934             } else {
935 0           $axis = _TC_AXIS_DE_ALT;
936             }
937              
938             # Get autoguide rate (0x47)
939 0           my $response = tc_pass_through_cmd($port, 1, $axis, 0x47, 0, 0, 0, 1);
940 0 0         if (defined $response) {
941 0           my $rate = ord(substr($response, 0, 1));
942 0           return int(100 * $rate / 256);
943             } else {
944 0           return undef;
945             }
946             }
947              
948             =item tc_set_autoguide_rate(port, axis, rate)
949              
950             Set autoguide rate for the given axis in percents of the sidereal rate.
951              
952             Accepted values for axis are TC_AXIS_RA_AZM and TC_AXIS_DE_ALT.
953             Rate must be in the range [0-99].
954              
955             On success 1 is returned. If rate is out of range -1 is returned.
956             If no response received, undef is returned.
957             =cut
958             sub tc_set_autoguide_rate($$$) {
959 0     0 1   my ($port,$axis,$rate) = @_;
960              
961 0 0         if ($axis > 0) {
962 0           $axis = _TC_AXIS_RA_AZM;
963             } else {
964 0           $axis = _TC_AXIS_DE_ALT;
965             }
966              
967             # $rate should be [0%-99%]
968 0           my $rate = int($rate);
969 0 0 0       if (($rate < 0) or ($rate > 99)) {
970 0           return -1;
971             }
972              
973             # This is wired, but is done to match as good as
974             # possible the values given by the HC
975 0           my$rrate;
976 0 0         if ($rate == 0) {
    0          
977 0           $rrate = 0;
978             } elsif ($rate == 99) {
979 0           $rrate = 255;
980             } else {
981 0           $rrate = int((256 * $rate / 100) + 1);
982             }
983              
984             # Set autoguide rate (0x46)
985 0           my $response = tc_pass_through_cmd($port, 2, $axis, 0x46, $rrate, 0, 0, 0);
986 0 0         if (defined $response) {
987 0           return 1;
988             } else {
989 0           return undef;
990             }
991             }
992              
993             =item tc_get_backlash(port, axis, direction)
994              
995             Get anti-backlash values for the specified axis in a given direction.
996              
997             Accepted values for axis are TC_AXIS_RA_AZM and TC_AXIS_DE_ALT. Direction
998             can accept values TC_DIR_POSITIVE and TC_DIR_NEGATIVE.
999              
1000             On success current value of backlash is returned in the range [0-99].
1001             If no response received, undef is returned.
1002             =cut
1003             sub tc_get_backlash($$$) {
1004 0     0 1   my ($port,$axis,$direction) = @_;
1005              
1006 0 0         if ($axis > 0) {
1007 0           $axis = _TC_AXIS_RA_AZM;
1008             } else {
1009 0           $axis = _TC_AXIS_DE_ALT;
1010             }
1011              
1012 0 0         if ($direction > 0) {
1013 0           $direction = 0x40; # Get positive backlash
1014             } else {
1015 0           $direction = 0x41; # Get negative backlash
1016             }
1017              
1018 0           my $response = tc_pass_through_cmd($port, 1, $axis, $direction, 0, 0, 0, 1);
1019 0 0         if (defined $response) {
1020 0           return ord(substr($response, 0, 1));
1021             } else {
1022 0           return undef;
1023             }
1024             }
1025              
1026             =item tc_set_backlash(port, axis, direction, backlash)
1027              
1028             Set anti-backlash values for the specified axis in a given direction.
1029              
1030             Accepted values for axis are TC_AXIS_RA_AZM and TC_AXIS_DE_ALT. Direction can accept
1031             values TC_DIR_POSITIVE and TC_DIR_NEGATIVE. Backlash must be in the range [0-99].
1032              
1033             On success 1 is returned. If backlash is out of range -1 is returned.
1034             If no response received, undef is returned.
1035             =cut
1036             sub tc_set_backlash($$$$) {
1037 0     0 1   my ($port,$axis,$direction,$backlash) = @_;
1038              
1039 0 0         if ($axis > 0) {
1040 0           $axis = _TC_AXIS_RA_AZM;
1041             } else {
1042 0           $axis = _TC_AXIS_DE_ALT;
1043             }
1044              
1045 0 0         if ($direction > 0) {
1046 0           $direction = 0x10; # Set positive backlash
1047             } else {
1048 0           $direction = 0x11; # Set negative backlash
1049             }
1050              
1051 0           my $backlash = int($backlash);
1052 0 0 0       if (($backlash < 0) or ($backlash > 99)) {
1053 0           return -1;
1054             }
1055              
1056 0           my $response = tc_pass_through_cmd($port, 2, $axis, $direction, $backlash, 0, 0, 0);
1057 0 0         if (defined $response) {
1058 0           return 1;
1059             } else {
1060 0           return undef;
1061             }
1062             }
1063              
1064             =item tc_pass_through_cmd(port, msg_len, dest_id, cmd_id, data1, data2, data3, res_len)
1065              
1066             Send a pass through command to a specific device. This function is meant for an internal
1067             library use and should not be used, unless you know exactly what you are doing.
1068             Calling this function with wrong parameters can be dangerous and can break the telescope!
1069              
1070             =cut
1071             sub tc_pass_through_cmd($$$$$$$$) {
1072 0     0 1   my ($port, $msg_len, $dest_id, $cmd_id, $data1, $data2, $data3, $res_len) = @_;
1073              
1074 0           $port->write("P");
1075 0           $port->write(pack("c", $msg_len));
1076 0           $port->write(pack("c", $dest_id));
1077 0           $port->write(pack("c", $cmd_id));
1078 0           $port->write(pack("c", $data1));
1079 0           $port->write(pack("c", $data2));
1080 0           $port->write(pack("c", $data3));
1081 0           $port->write(pack("c", $res_len));
1082              
1083             # we should read $res_len + 1 byes to accomodate '#' at the end
1084 0           return read_telescope($port, $res_len + 1);
1085             }
1086              
1087             =back
1088              
1089             =head1 PERIODIC ERROR CORRECTION FUNCTIONS
1090              
1091             The following commands are not officially documented by Celestron. Please note that these
1092             commands are reverse engineered and may not work exactly as expected.
1093              
1094             =over 8
1095              
1096             =item pec_index_found(port)
1097              
1098             Determine if the position index is found and the mount will know from where to start
1099             PEC data playback.
1100              
1101             If the index is found 1 is returned. If it is not found 0 is returned. In case of an error
1102             the function returs undef.
1103              
1104             =cut
1105              
1106             sub pec_index_found($) {
1107 0     0 1   my ($port) = @_;
1108              
1109 0           my $response = tc_pass_through_cmd($port, 1, _TC_AXIS_RA_AZM, 0x18, 0, 0, 0, 1);
1110 0 0         if (defined $response) {
1111 0 0         if (ord(substr($response, 0, 1))) {
1112 0           return 1;
1113             } else {
1114 0           return 0;
1115             }
1116             } else {
1117 0           return undef;
1118             }
1119             }
1120              
1121             =item pec_seek_index(port)
1122              
1123             This command will move the mount slightly until the position index is found, so that the PEC
1124             playback can be started from the correct position. The telescope will not return to the
1125             original position when the index is found. The completion of the operation can be checked with
1126             pec_index_found().
1127              
1128             On success 1 is returned. In case of an error undef is returned.
1129              
1130             =cut
1131              
1132             sub pec_seek_index($) {
1133 0     0 1   my ($port) = @_;
1134              
1135 0           my $response = tc_pass_through_cmd($port, 1, _TC_AXIS_RA_AZM, 0x19, 0, 0, 0, 0);
1136 0 0         if (defined $response) {
1137 0           return 1;
1138             } else {
1139 0           return undef;
1140             }
1141             }
1142              
1143             =item pec_record(port, action)
1144              
1145             Start or stop the recording of periodic error correction data. The action parameter can
1146             be PEC_START or PEC_STOP to start or stop the recording. The completion of the recording
1147             can be monitored with pec_record_complete(). The data is collected by the mount from the
1148             user or auto-guider corrections made during the recording process. This recording can take
1149             10-15 minutes depending on the type of the mount.
1150              
1151             On success 1 is returned. If a wrong parameter is provided -1 is returned. In case of an
1152             error undef is returned.
1153              
1154             =cut
1155              
1156             sub pec_record($$) {
1157 0     0 1   my ($port,$action) = @_;
1158 0           my $cmd_id;
1159              
1160 0 0         if ($action == PEC_START) {
    0          
1161 0           $cmd_id = 0x0C; # Start PEC record
1162             } elsif ($action == PEC_STOP){
1163 0           $cmd_id = 0x16; # Stop PEC record
1164             } else {
1165 0           return -1;
1166             }
1167              
1168 0           my $response = tc_pass_through_cmd($port, 1, _TC_AXIS_RA_AZM, $cmd_id, 0, 0, 0, 0);
1169 0 0         if (defined $response) {
1170 0           return 1;
1171             } else {
1172 0           return undef;
1173             }
1174             }
1175              
1176             =item pec_record_complete(port)
1177              
1178             Check the completion of pec_record().
1179              
1180             If recording is complete 1 is returned. If recording is still in progress 0 is returned.
1181             In case of an error undef is returned.
1182              
1183             =cut
1184              
1185             sub pec_record_complete($) {
1186 0     0 1   my ($port) = @_;
1187              
1188 0           my $response = tc_pass_through_cmd($port, 1, _TC_AXIS_RA_AZM, 0x15, 0, 0, 0, 1);
1189 0 0         if (defined $response) {
1190 0 0         if (ord(substr($response, 0, 1))) {
1191 0           return 1;
1192             } else {
1193 0           return 0;
1194             }
1195             } else {
1196 0           return undef;
1197             }
1198             }
1199              
1200             =item pec_playback(port, action)
1201              
1202             Start or stop PEC playback. The action parameter can be PEC_START or PEC_STOP
1203             to start or stop PEC playback respectively.
1204              
1205             On success 1 is returned. If a wrong parameter is provided -1 is returned. In case of an
1206             error undef is returned.
1207              
1208             =cut
1209              
1210             sub pec_playback($$) {
1211 0     0 1   my ($port,$action) = @_;
1212              
1213 0 0 0       if (($action != PEC_START) and ($action != PEC_STOP)) {
1214 0           return -1;
1215             }
1216              
1217 0           my $response = tc_pass_through_cmd($port, 2, _TC_AXIS_RA_AZM, 0x0d, $action, 0, 0, 0);
1218 0 0         if (defined $response) {
1219 0           return 1;
1220             } else {
1221 0           return undef;
1222             }
1223             }
1224              
1225             =item pec_get_playback_index(port)
1226              
1227             Get the index of the PEC data for the curent mount position in the range form 0 to
1228             the value returned by pec_get_data_len() minus 1.
1229              
1230             If the index position is not found yet, the function will always return 0.
1231             On error undef is returned.
1232              
1233             =cut
1234              
1235             sub pec_get_playback_index($) {
1236 0     0 1   my ($port) = @_;
1237              
1238 0           my $response = tc_pass_through_cmd($port, 1, _TC_AXIS_RA_AZM, 0x0e, 0, 0, 0, 1);
1239 0 0         if (defined $response) {
1240 0           return ord(substr($response, 0, 1));
1241             } else {
1242 0           return undef;
1243             }
1244             }
1245              
1246             =item pec_get_data_len(port)
1247              
1248             Get the length of the internal register array in which the PEC data is stored.
1249              
1250             On error undef is returned.
1251              
1252             =cut
1253              
1254             sub pec_get_data_len($) {
1255 0     0 1   my ($port) = @_;
1256              
1257 0           my $response = tc_pass_through_cmd($port, 2, _TC_AXIS_RA_AZM, 0x30, 0x3f, 0, 0, 1);
1258 0 0         if (defined $response) {
1259 0           return ord(substr($response, 0, 1));
1260             } else {
1261 0           return undef;
1262             }
1263             }
1264              
1265             sub _pec_set_data($$$) {
1266 0     0     my ($port, $index, $data) = @_;
1267              
1268 0           my $response = tc_pass_through_cmd($port, 4, _TC_AXIS_RA_AZM, 0x31, 0x40+$index, $data, 0, 0);
1269 0 0         if (defined $response) {
1270 0           return 1;
1271             } else {
1272 0           return undef;
1273             }
1274             }
1275              
1276             sub _pec_get_data($$) {
1277 0     0     my ($port, $index) = @_;
1278              
1279 0           my $response = tc_pass_through_cmd($port, 2, _TC_AXIS_RA_AZM, 0x30, 0x40+$index, 0, 0, 1);
1280 0 0         if (defined $response) {
1281 0           return unpack("c", substr($response, 0, 1));
1282             } else {
1283 0           return undef;
1284             }
1285             }
1286              
1287             =item pec_set_data(port, data)
1288              
1289             Upload the periodic error correction data to the mount. The data parameter is a reference to an
1290             array with size that matches the value returned by pec_get_data_len(). The values must be in
1291             arc seconds.
1292              
1293             On success 1 is returned. If the size of the data array does not match the mount data size -1
1294             is returned. If any of the PEC values is too big and can not fit in the internal data format -2 is returned.
1295             On other error undef is returned.
1296              
1297             =cut
1298              
1299             sub pec_set_data($$) {
1300 0     0 1   my ($port, $data) = @_;
1301 0           my $res;
1302              
1303 0           my $len = pec_get_data_len($port);
1304 0 0         if (!defined $len) {
1305 0           return undef;
1306             }
1307              
1308 0 0         if ($len != @{$data}) {
  0            
1309 0           return -1;
1310             }
1311              
1312 0           my $index = 0;
1313 0           my $current = 0;
1314 0           foreach my $val (@{$data}) {
  0            
1315 0           my $diff = $val - $current;
1316              
1317             # I have no idea why the values are different for positive and negative numbers
1318             # I thought the coefficient should be 0.0772 arcsec/unit as the resolution of
1319             # 24bit number for 360 degrees. I tried to mach as best as I could the values
1320             # returned by Celestron's PECTool and I came up with this numbers...
1321 0           my $rdiff;
1322 0 0         if ($diff < 0) {
1323 0           $rdiff = sprintf("%.0f", $val / 0.0845) - sprintf("%.0f", $current / 0.0845);
1324             } else {
1325 0           $rdiff = sprintf("%.0f", $val / 0.0774) - sprintf("%.0f", $current / 0.0774);
1326             }
1327 0 0 0       if (($rdiff > 127) or ($rdiff < -127)) {
1328 0           return -2;
1329             }
1330              
1331             # print "$index => $rdiff\t(diff = $diff, val = $val)\n";
1332 0           $res = _pec_set_data($port, $index, $rdiff);
1333 0 0         if (!defined ($res)) {
1334 0           return undef;
1335             }
1336 0           $index++;
1337 0           $current = $val;
1338             }
1339              
1340 0           $res = pec_record($port, PEC_STOP);
1341 0 0         if (!defined ($res)) {
1342 0           return undef;
1343             }
1344 0           return 1;
1345             }
1346              
1347             =item pec_get_data(port)
1348              
1349             Download the periodic error correction data from the mount. An array with the correction values is
1350             returned. The size of the array is equal to the value returned by pec_get_data_len(). The values
1351             are in arc seconds.
1352              
1353             On error undef is returned.
1354              
1355             =cut
1356              
1357             sub pec_get_data($) {
1358 0     0 1   my ($port) = @_;
1359 0           my @data;
1360              
1361 0           my $len = pec_get_data_len($port);
1362             # my $len = 11;
1363 0 0         if (!defined $len) {
1364 0           return undef;
1365             }
1366              
1367 0           my $current = 0;
1368 0           for (my $index = 0; $index < $len; $index++) {
1369 0           my $rdiff = _pec_get_data($port, $index);
1370             # my $rdiff = $b[$index];
1371 0 0         if (!defined ($rdiff)) {
1372 0           return undef;
1373             }
1374              
1375             # I have no idea why the values are different for positive and negative numbers.
1376             # Pease se the note in pec_set_data()!
1377 0 0         if ($rdiff > 0) {
1378 0           $current += $rdiff * 0.0774;
1379             } else {
1380 0           $current += $rdiff * 0.0845;
1381             }
1382 0           $data[$index] = $current;
1383             # print "$index => $current\n";
1384             }
1385 0           return @data;
1386             }
1387              
1388             =back
1389              
1390             =head1 UTILITY FUNCTIONS
1391              
1392             =over 8
1393              
1394             =item notnum(n)
1395              
1396             If "n" is a real number returns 0 else it returns 1.
1397              
1398             =cut
1399             sub notnum($)
1400 0     0 1   { my ($num) = @_;
1401 0 0         if ($num=~ /^[-+]?\d+\.?\d*$/) {return 0;}
  0            
  0            
1402             else {return 1;}
1403             }
1404              
1405              
1406             =item precess(ra0, dec0, equinox0, equinox1)
1407              
1408             Precesses coordinates ra0 and dec0 from equinox0 to equinox1 and returns the caclculated ra1 and dec1.
1409             Where ra and dec should be in decimal degrees and equinox should be in years (and fraction of the year).
1410              
1411             =cut
1412             sub precess(@)
1413             {
1414 0     0 1   my ($ra0,$de0,$eq0,$eq1)=@_;
1415 0           my ($cosd,$ra,$dec,
1416             $A,$B,$C,
1417             $x0,$y0,$z0,
1418             $x1,$y1,$z1,
1419             $ST,$T,$sec2rad);
1420 0           my @rot;
1421              
1422 0           my ($sinA,$sinB,$sinC,$cosA,$cosB,$cosC,$sind);
1423              
1424 0           my ($ra1,$de1);
1425              
1426 0           $ra = $ra0*DEG2RAD;
1427 0           $dec = $de0*DEG2RAD;
1428              
1429 0           $cosd = cos($dec);
1430              
1431 0           $x0=$cosd*cos($ra);
1432 0           $y0=$cosd*sin($ra);
1433 0           $z0=sin($dec);
1434              
1435 0           $ST=($eq0-2000.0)*0.001;
1436 0           $T=($eq1-$eq0)*0.001;
1437              
1438 0           $sec2rad=(DEG2RAD)/3600.0;
1439 0           $A=$sec2rad*$T*(23062.181+$ST*(139.656+0.0139*$ST)+$T*
1440             (30.188-0.344*$ST+17.998*$T));
1441 0           $B=$sec2rad*$T*$T*(79.280+0.410*$ST+0.205*$T)+$A;
1442 0           $C=$sec2rad*$T*(20043.109-$ST*(85.33+0.217*$ST)+$T*
1443             (-42.665-0.217*$ST-41.833*$T));
1444              
1445 0           $sinA=sin($A); $sinB=sin($B); $sinC=sin($C);
  0            
  0            
1446 0           $cosA=cos($A); $cosB=cos($B); $cosC=cos($C);
  0            
  0            
1447              
1448 0           $rot[0][0]=$cosA*$cosB*$cosC-$sinA*$sinB;
1449 0           $rot[0][1]=(-1)*$sinA*$cosB*$cosC-$cosA*$sinB;
1450 0           $rot[0][2]=(-1)*$sinC*$cosB;
1451              
1452 0           $rot[1][0]=$cosA*$cosC*$sinB+$sinA*$cosB;
1453 0           $rot[1][1]=(-1)*$sinA*$cosC*$sinB+$cosA*$cosB;
1454 0           $rot[1][2]=(-1)*$sinB*$sinC;
1455              
1456 0           $rot[2][0]=$cosA*$sinC;
1457 0           $rot[2][1]=(-1)*$sinA*$sinC;
1458 0           $rot[2][2]=$cosC;
1459            
1460 0           $x1=$rot[0][0]*$x0+$rot[0][1]*$y0+$rot[0][2]*$z0;
1461 0           $y1=$rot[1][0]*$x0+$rot[1][1]*$y0+$rot[1][2]*$z0;
1462 0           $z1=$rot[2][0]*$x0+$rot[2][1]*$y0+$rot[2][2]*$z0;
1463              
1464 0 0         if ($x1==0) {
  0            
1465 0 0         if ($y1 > 0) { $ra1=90.0;}
  0            
1466 0           else { $ra1=270.0;}
1467             }
1468             else {$ra1=atan2($y1,$x1)*RAD2DEG;}
1469 0 0         if($ra1<0) { $ra1+=360;}
  0            
1470              
1471 0           $de1=RAD2DEG*atan2($z1,sqrt(1-$z1*$z1));
1472              
1473 0           return ($ra1,$de1);
1474             }
1475              
1476             ########################################################
1477             ######## Mathematics
1478             ########################################################
1479              
1480             =item round(n)
1481              
1482             Returns the rounded number n.
1483              
1484             =cut
1485             sub round($){
1486 0     0 1   my($num)=@_;
1487 0           my ($retval);
1488 0 0         if (($num - floor($num)) < 0.5) { $retval = floor($num); }
  0            
1489 0           else { $retval = floor($num) + 1; }
1490 0           return $retval;
1491             }
1492              
1493              
1494             =item d2hms(deg)
1495              
1496             Converts deg (in decimal degrees) to string in hours, minutes and seconds notion (like "12h 10m 44s").
1497              
1498             =cut
1499             sub d2hms($) # Looks OK! :)
1500             {
1501 0     0 1   my ($ra)=@_;
1502              
1503 0           $ra=$ra/15;
1504 0           my $hr=int($ra*3600+0.5);
1505 0           my $hour=int($hr/3600);
1506 0           my $f=int($hr%3600);
1507 0           my $min=int($f/60);
1508 0           my $sec=int($f%60);
1509 0           my $ra_str = sprintf "%02dh %02dm %02ds", $hour,$min,$sec;
1510 0           return $ra_str;
1511             }
1512              
1513             =item d2dms(deg)
1514              
1515             Converts deg (in decimal degrees) to string in degrees, minutes and seconds notion
1516             (like "33:20:44").
1517              
1518             =cut
1519             sub d2dms($) # Looks OK! :)
1520             {
1521 0     0 1   my ($ang)=@_;
1522 0 0         if ($ang >= 0) {
1523 0           my $a=int($ang*3600+0.5);
1524 0           my $deg=int($a/3600);
1525 0           my $f=int($a%3600);
1526 0           my $min=int($f/60);
1527 0           my $sec=int($f%60);
1528 0           my $ang_str=sprintf "%02d:%02d:%02d",$deg,$min,$sec;
1529 0           return $ang_str;
1530             } else {
1531 0           $ang*=-1;
1532 0           my $a=int($ang*3600+0.5);
1533 0           my $deg=int($a/3600);
1534 0           my $f=int($a%3600);
1535 0           my $min=int($f/60);
1536 0           my $sec=int($f%60);
1537 0           my $ang_str=sprintf "-%02d:%02d:%02d",$deg,$min,$sec;
1538 0           return $ang_str;
1539             }
1540             }
1541            
1542             sub d2dms2($) {
1543 0     0 0   my ($ang)=@_;
1544 0 0         if ($ang >= 0) {
1545 0           my $a=int($ang*3600+0.5);
1546 0           my $deg=int($a/3600);
1547 0           my $f=int($a%3600);
1548 0           my $min=int($f/60);
1549 0           my $sec=int($f%60);
1550 0           return ($deg,$min,$sec);
1551             } else {
1552 0           $ang*=-1;
1553 0           my $a=int($ang*3600+0.5);
1554 0           my $deg=int($a/3600);
1555 0           my $f=int($a%3600);
1556 0           my $min=int($f/60);
1557 0           my $sec=int($f%60);
1558 0           return (-1*$deg,$min,$sec);
1559             }
1560             }
1561              
1562             =item d2dms(deg)
1563              
1564             converts deg (in decimal degrees) to string in degrees and minutes notion (like "33:20").
1565              
1566             =cut
1567             sub d2dm($)
1568             {
1569 0     0 0   my ($ang)=@_;
1570 0           my $a=int($ang*3600+0.5);
1571 0           my $deg=int($a/3600);
1572 0           my $f=int($a%3600);
1573 0           my $min=int($f/60);
1574 0           my $ang_str=sprintf "%02d:%02d",$deg,$min;
1575 0           return $ang_str;
1576             }
1577              
1578             =item dms2d(string)
1579              
1580             converts string of the format "dd:mm:ss" to decimal degrees. If the string format
1581             is invalid format, undef is returned.
1582              
1583             =cut
1584             sub dms2d($)
1585             {
1586 0     0 1   my ($angle)=@_;
1587            
1588 0           my (@dms)=split(/:/,$angle);
1589 0 0 0       if (@dms>3 or $angle eq "") {
1590 0           return undef;
1591             }
1592            
1593 0 0         if (!($dms[0]=~ /^[+-]?\d+$/)) {
1594 0           return undef;
1595             }
1596 0 0 0       if ($dms[1]<0 or $dms[1]>59 or $dms[1]=~/[\D]/) {
      0        
1597 0           return undef;
1598             }
1599 0 0 0       if ($dms[2]<0 or $dms[2]>59 or $dms[2]=~/[\D]/) {
      0        
1600 0           return undef;
1601             }
1602            
1603 0 0         if ($dms[0]=~ /^-/) {
1604 0           return ($dms[0]-$dms[1]/60-$dms[2]/3600);
1605             } else {
1606 0           return ($dms[0]+$dms[1]/60+$dms[2]/3600);
1607             }
1608             }
1609              
1610             =item hms2d(string)
1611              
1612             Converts string of the format "hh:mm:ss" to decimal degrees. If the string format
1613             is invalid format, undef is returned.
1614              
1615             =cut
1616             sub hms2d($)
1617             {
1618 0     0 1   my ($hours)=@_;
1619            
1620 0           my (@hms)=split(/:/,$hours);
1621 0 0 0       if (@hms>3 or $hours eq "") {
1622 0           return undef;
1623             }
1624            
1625 0 0 0       if ($hms[0]<0 or $hms[0]>23 or $hms[0]=~/[\D]/) {
      0        
1626 0           return undef;
1627             }
1628 0 0 0       if ($hms[1]<0 or $hms[1]>59 or $hms[1]=~/[\D]/) {
      0        
1629 0           return undef;
1630             }
1631 0 0 0       if ($hms[2]<0 or $hms[2]>59 or $hms[2]=~/[\D]/) {
      0        
1632 0           return undef;
1633             }
1634            
1635 0           return (($hms[0]+$hms[1]/60+$hms[2]/3600)*15);
1636             }
1637              
1638             ###############################################
1639             # NexStar coordinate conversion
1640             ###############################################
1641              
1642             =item nex2dd(string)
1643              
1644             Converts NexStar hexadecimal coordinate string (in fraction of a revolution) of
1645             the format "34AB,12CE" to two decimal degree coordinates.
1646              
1647             =cut
1648             sub nex2dd ($){
1649 0     0 1   my ($nexres) = @_;
1650 0           my $d1_factor = hex(substr($nexres, 0, 4)) / 65536;
1651 0           my $d2_factor = hex(substr($nexres, 5, 4)) / 65536;
1652 0           my $d1 = 360 * $d1_factor;
1653 0           my $d2 = 360 * $d2_factor;
1654              
1655             # bring $d2 in [-90,+90] range
1656             # use 90.00001 to fix some float errors
1657             # that lead +90 to be converted to -270
1658 0 0         $d2 = $d2 + 360 if ($d2 < -90.0002);
1659 0 0         $d2 = $d2 - 360 if ($d2 > 90.0002);
1660              
1661 0           return($d1, $d2);
1662             }
1663              
1664             =item pnex2dd(string)
1665              
1666             Converts precision NexStar hexadecimal coordinate string (in fraction of a revolution)
1667             of the format "12AB0500,40000500" to two decimal degree coordinates.
1668              
1669             =cut
1670             sub pnex2dd ($){
1671 0     0 1   my ($nexres) = @_;
1672 0           my $d1_factor = hex(substr($nexres, 0, 8)) / 0xffffffff;
1673 0           my $d2_factor = hex(substr($nexres, 9, 8)) / 0xffffffff;
1674 0           my $d1 = 360 * $d1_factor;
1675 0           my $d2 = 360 * $d2_factor;
1676              
1677             # bring $d2 in [-90,+90] range
1678             # use 90.00001 to fix some float errors
1679             # that lead +90 to be converted to -270
1680 0 0         $d2 = $d2 + 360 if ($d2 < -90.0002);
1681 0 0         $d2 = $d2 - 360 if ($d2 > 90.0002);
1682              
1683 0           return($d1, $d2);
1684             }
1685              
1686              
1687             =item dd2nex(deg1,deg2)
1688              
1689             Converts coordinates deg1 and deg2 (in decimal degrees) to NexStar hexadecimal coordinate
1690             string (in fraction of a revolution) of the format "34AB,12CE".
1691              
1692             =cut
1693             sub dd2nex ($$) {
1694 0     0 1   my ($d1, $d2) = @_;
1695              
1696             # bring $d1,$d2 in the range [0-360]
1697 0           $d1 = $d1 - 360 * int($d1/360);
1698 0           $d2 = $d2 - 360 * int($d2/360);
1699 0 0         $d1 = $d1 + 360 if ($d1 < 0);
1700 0 0         $d2 = $d2 + 360 if ($d2 < 0);
1701              
1702 0           my $d2_factor = $d2 / 360;
1703 0           my $d1_factor = $d1 / 360;
1704              
1705 0           my $nex1 = int($d1_factor*65536);
1706 0           my $nex2 = int($d2_factor*65536);
1707              
1708 0           return sprintf("%04X,%04X", $nex1,$nex2);
1709             }
1710              
1711             =item dd2nex(deg1,deg2)
1712              
1713             Converts coordinates deg1 and deg2 (in decimal degrees) to precise NexStar hexadecimal
1714             coordinate string (in fraction of a revolution) of the format "12AB0500,40000500".
1715              
1716             =cut
1717             sub dd2pnex ($$) {
1718 0     0 0   my ($d1, $d2) = @_;
1719              
1720             # bring $d1,$d2 in the range [0-360]
1721 0           $d1 = $d1 - 360 * int($d1/360);
1722 0           $d2 = $d2 - 360 * int($d2/360);
1723 0 0         $d1 = $d1 + 360 if ($d1 < 0);
1724 0 0         $d2 = $d2 + 360 if ($d2 < 0);
1725              
1726 0           my $d2_factor = $d2 / 360;
1727 0           my $d1_factor = $d1 / 360;
1728              
1729 0           my $nex1 = int($d1_factor*0xffffffff);
1730 0           my $nex2 = int($d2_factor*0xffffffff);
1731              
1732 0           return sprintf("%08X,%08X", $nex1,$nex2);
1733             }
1734              
1735             =back
1736              
1737             =head1 SEE ALSO
1738              
1739             For more information about the NexStar commands please refer to the original
1740             protocol specification described here:
1741             http://www.celestron.com/c3/images/files/downloads/1154108406_nexstarcommprot.pdf
1742              
1743             Some of the undocumented commands are described here:
1744             http://www.paquettefamily.ca/nexstar/NexStar_AUX_Commands_10.pdf
1745              
1746             =head1 AUTHOR
1747              
1748             Rumen Bogdanovski, Erumen@skyarchive.orgE
1749              
1750             =head1 COPYRIGHT AND LICENSE
1751              
1752             Copyright (C) 2013-2014 by Rumen Bogdanovski
1753              
1754             This library is free software; you can redistribute it and/or modify
1755             it under the same terms as Perl itself, either Perl version 5.12.4 or,
1756             at your option, any later version of Perl 5 you may have available.
1757              
1758             The author assumes no liability or responsibility for damage or injury
1759             to persons or property arising from any use of this product. Use it at
1760             your own risk.
1761              
1762             =cut
1763              
1764             1;