File Coverage

blib/lib/NexStarCtl.pm
Criterion Covered Total %
statement 24 568 4.2
branch 0 202 0.0
condition 0 92 0.0
subroutine 8 59 13.5
pod 46 50 92.0
total 78 971 8.0


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