mirror of https://github.com/XEphem/XEphem.git
1238 lines
33 KiB
C
1238 lines
33 KiB
C
/*
|
|
* liblx200 v0.8.1
|
|
* A library for controlling the Meade LX200 series scopes
|
|
*
|
|
* Copyright (C) 1999, Mike Stute
|
|
*
|
|
* Todo:
|
|
* V0.6 will make this a daemon controlled with the usual
|
|
* signals with all functions available
|
|
* V1.1 will add lx200_errno queue and be concurrent
|
|
* non-blocking or async I/O or alarm on telescope reads
|
|
* V1.2 will add the TDB (telescope data block) for
|
|
* faster data retrieving and less scope chatter
|
|
* V1.3 will add configuration templates (if we get that far)
|
|
* V1.5 Clean up, bug free (hah) and ready for GTCCS
|
|
* V2.0 will swallow the file descriptor and no longer let
|
|
* the application program have access to it. But
|
|
* since this is concurrent, we do synchronization
|
|
* not the app., this may or may not be a needed by the
|
|
* time I get there. This means all apps would need to
|
|
* be rewritten; hence, the major version change
|
|
*
|
|
* Throughout all versions I am sure functions will be added
|
|
* to increase the high-level functionality as I get
|
|
* feedback (hopefully).
|
|
*
|
|
* A few notes:
|
|
* This library was designed for my use in writing the
|
|
* GTCCS modules (Generic Telescope and Camera Control System)
|
|
* I toyed with two main ideas when writing it and didn't
|
|
* end up with the initial design (hindsight vs. foresight).
|
|
*
|
|
* Originally I used a table look up with a set of tokens
|
|
* to pull the command from the tokenized table and a set
|
|
* of about 5 functions in the Base Library Functions
|
|
* with a function dispatcher to handle scope control.
|
|
* The dispatcher was called with a token and a buffer.
|
|
* This worked great, but did limit what the
|
|
* application program could do because the intelligence
|
|
* was built into the dispatcher and table. If an application
|
|
* needed lower level services, they weren't there. This was
|
|
* because my original design was high-level control only.
|
|
*
|
|
* But I decided a library that could provide both low and
|
|
* high level services would be more useful to more people
|
|
* and rewrote it as a module block library, that is a library
|
|
* of bottom-up functions where small low-level are combined
|
|
* to create high-level calls, thus allowing the application
|
|
* access to both high- and low-level services. This yielded
|
|
* the Base Library Functions, the Main Library Functions,
|
|
* and the Support Library Functions. This means
|
|
* more stack frames and a little more overhead, but in
|
|
* addition to providing both kinds of services, it also
|
|
* makes the code extremely easy to maintain and read.
|
|
*
|
|
* Two defines are called public and private. This is
|
|
* just a indication to application program developers
|
|
* on my original intentions. It's a free use library
|
|
* but I suggest you don't call private functions directly.
|
|
* I reserve the right to change then from version to version.
|
|
* All scope functions can be accessed through the public
|
|
* functions.
|
|
*
|
|
* As always, comments and suggestions are welcome.
|
|
* Mike Stute
|
|
* Feb 1999
|
|
* mrstute@attbi.com
|
|
*
|
|
* Changes:
|
|
* Fixed lx200_fset_longitude(x,y) macro as
|
|
* pointed out by Jason Etherton
|
|
*/
|
|
|
|
#include <fcntl.h>
|
|
#include <termios.h>
|
|
#include <stdio.h>
|
|
#include <math.h>
|
|
#include <unistd.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <sys/time.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
|
|
#include "liblx200.h"
|
|
|
|
#define TREADTO 10 /* telescope read timeout, secs */
|
|
|
|
/* Globals*/
|
|
private static struct termios lx200_oldtio,lx200_newtio;
|
|
private static char *lx200_planets[9]={"Mercury","Venus","","Mars","Jupiter","Saturn","Uranus","Neptune","Pluto"};
|
|
private static char fHardwareEmulate=FALSE;
|
|
private static char iHardwareMode=TRUE;
|
|
/*TDB structure goes here*/
|
|
|
|
#undef DEBUG
|
|
/**************************************************************
|
|
* I/O Functions *
|
|
* These are the low-level input and output routines defined *
|
|
* for the scope. There is no generic "read-a-char" because *
|
|
* the point of liblx200 is to provided a "black box" module *
|
|
* for talking to the scope. An application program shouldn't *
|
|
* need to directly call any of the I/O routines, except *
|
|
* opening and closing the scope. However, currently (v1.0) *
|
|
* the application program is returned the file descriptor *
|
|
* so it is possible for an app. to handle all communications *
|
|
* with the scope directly. In that case, why is this code *
|
|
* linked in anyway? *
|
|
* Exports: *
|
|
* lx200_open-scope *
|
|
* lx200_close_scope *
|
|
**************************************************************/
|
|
public int
|
|
lx200_open_scope(char *szDevice)
|
|
{
|
|
int fd;
|
|
|
|
/*Return the fake port if emulating a scope*/
|
|
if(fHardwareEmulate)
|
|
return(LX200_EMULATE_FD);
|
|
|
|
/* Open serial device to telescope */
|
|
fd = open(szDevice,O_RDWR|O_NOCTTY);
|
|
if (fd<0) {
|
|
#ifdef DEBUG
|
|
perror(szDevice);
|
|
#endif
|
|
return(LX200_FALSE);
|
|
}
|
|
|
|
/* Set the terminal for 9600,8-N-1
|
|
* Non-canonical input processing
|
|
* no flow control
|
|
*/
|
|
|
|
/*Clear the new term struct*/
|
|
memset(&lx200_newtio,0,sizeof(lx200_newtio));
|
|
tcgetattr(fd,&lx200_oldtio); /*save port settings*/
|
|
|
|
/* Set for 9600 CS8=8n1n CLOCAL=local connection CREAD=enable reading */
|
|
cfsetospeed(&lx200_newtio, (speed_t) B9600);
|
|
cfsetispeed(&lx200_newtio, (speed_t) B9600);
|
|
lx200_newtio.c_cflag = BAUDRATE | CS8 | CLOCAL | CREAD;
|
|
lx200_newtio.c_iflag = IGNPAR;
|
|
lx200_newtio.c_oflag = 0; /* Raw output */
|
|
|
|
/* set input mode (non-canonical, no echo, ... */
|
|
lx200_newtio.c_lflag = 0;
|
|
|
|
lx200_newtio.c_cc[VTIME] = 0; /* inter-character timer unused, block instead */
|
|
lx200_newtio.c_cc[VMIN] = 1; /* read 1 character minimum */
|
|
|
|
tcflush(fd, TCIFLUSH); /* clear the channel */
|
|
tcsetattr(fd,TCSANOW,&lx200_newtio); /* set it */
|
|
return(fd);
|
|
}
|
|
|
|
/* Simple for serial scopes, not so simple for parallel
|
|
* or special dev scopes.
|
|
* Return saved terminal settings and close the device.
|
|
*/
|
|
public int
|
|
lx200_close_scope(int fd)
|
|
{
|
|
if(!fHardwareEmulate) {
|
|
tcsetattr(fd,TCSANOW,&lx200_oldtio);
|
|
close(fd);
|
|
}
|
|
return(LX200_TRUE);
|
|
}
|
|
|
|
/*
|
|
* You would think Meade designed the scope with blocking I/O in mind
|
|
* since they terminate everything with a TERMINATOR of #. But they
|
|
* broke their own rule, when they return "0" pr "1" with no terminator.
|
|
* This function reads and verifies an expected "0"
|
|
* if it doesn't ever come back from the scope, we're toast
|
|
* in the current version. But that indicates a problem with
|
|
* either the scope or the serial link, so it may just stay that
|
|
* way in the future.
|
|
*
|
|
* Perhaps ASYNC IO is in order for the scope channel
|
|
*/
|
|
private int
|
|
lx200_read_ok(int fd)
|
|
{
|
|
char cIn;
|
|
|
|
/*Fake a hardware response*/
|
|
if(fHardwareEmulate) {
|
|
switch(iHardwareMode) {
|
|
case LX200_EMULATE_FALSE:
|
|
return(LX200_FALSE);
|
|
break;
|
|
case LX200_EMULATE_TRUE:
|
|
return(LX200_TRUE);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if((cIn=lx200_read_one(fd))==LX200_FALSE)
|
|
return(LX200_FALSE);
|
|
if(cIn=='1')
|
|
return(LX200_TRUE);
|
|
return(LX200_FALSE);
|
|
}
|
|
|
|
/*
|
|
* Again Meade doesn't terminate with a '#' when returning
|
|
* a 0, 1, or 2 for some calls
|
|
* I rest my case
|
|
* The scope uses these to indicate success or errors for a few different calls
|
|
*/
|
|
private char
|
|
lx200_read_one(int fd)
|
|
{
|
|
struct timeval tv;
|
|
fd_set rd;
|
|
char szIn[2];
|
|
int iBytes;
|
|
|
|
/* Fake a hardware response*/
|
|
if(fHardwareEmulate) {
|
|
switch(iHardwareMode) {
|
|
case LX200_EMULATE_FALSE:
|
|
return('0');
|
|
break;
|
|
case LX200_EMULATE_TRUE:
|
|
return('1');
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* only wait so long for a response */
|
|
FD_ZERO (&rd);
|
|
FD_SET (fd, &rd);
|
|
tv.tv_sec = TREADTO;
|
|
tv.tv_usec = 0;
|
|
if (select (fd+1, &rd, NULL, NULL, &tv) != 1)
|
|
return(LX200_FALSE);
|
|
|
|
iBytes = read(fd,szIn,1);
|
|
if(iBytes==1)
|
|
return(szIn[0]);
|
|
return(LX200_FALSE);
|
|
}
|
|
|
|
/* Read two from the scope and return them in a string*/
|
|
private int
|
|
lx200_read_two(int fd, char *szIn)
|
|
{
|
|
int iBytes;
|
|
|
|
if(fHardwareEmulate) {
|
|
switch(iHardwareMode) {
|
|
case LX200_EMULATE_FALSE:
|
|
return(LX200_FALSE);
|
|
break;
|
|
case LX200_EMULATE_TRUE:
|
|
szIn[0]='1';
|
|
szIn[1]='2';
|
|
szIn[2]='\0';
|
|
return(LX200_TRUE);
|
|
break;
|
|
}
|
|
}
|
|
|
|
iBytes = lx200_read_one (fd);
|
|
if (iBytes == LX200_FALSE)
|
|
return(LX200_FALSE);
|
|
szIn[0] = iBytes;
|
|
iBytes = lx200_read_one (fd);
|
|
if (iBytes == LX200_FALSE)
|
|
return(LX200_FALSE);
|
|
szIn[1] = iBytes;
|
|
szIn[2]='\0';
|
|
return(LX200_TRUE);
|
|
}
|
|
|
|
/*
|
|
* Write a command to the scope
|
|
* Prefix the INITIATOR
|
|
* Suffix the TERMINATOR
|
|
* write to the stream
|
|
* ALL comunications to the scope must use this
|
|
* except send ACK in get_scope_mode()
|
|
* Gotta love Meade's consistentecy
|
|
*/
|
|
private int
|
|
lx200_write_to_scope(int fd, char *szpCmd)
|
|
{
|
|
char szCmd[17];
|
|
|
|
strcpy(szCmd,LX200_INITIATOR); /*Initiator*/
|
|
strncat(szCmd,szpCmd,14);
|
|
strcat(szCmd,LX200_TERMINATOR); /*Terminator*/
|
|
#ifdef DEBUG
|
|
printf("Out: %s on %d\n",szCmd, fd);
|
|
#endif
|
|
|
|
if(fHardwareEmulate) {
|
|
switch(iHardwareMode) {
|
|
case LX200_EMULATE_FALSE:
|
|
return(LX200_FALSE);
|
|
break;
|
|
case LX200_EMULATE_TRUE:
|
|
return(strlen(szCmd));
|
|
break;
|
|
}
|
|
}
|
|
|
|
return(write(fd,szCmd,strlen(szCmd)));
|
|
}
|
|
|
|
/*
|
|
* Send an ACK to the scope
|
|
* This is only used by lx200_get_mode()
|
|
*/
|
|
private int
|
|
lx200_send_ACK(int fd)
|
|
{
|
|
char szACK[1]={(char)0x06};
|
|
|
|
if(fHardwareEmulate) {
|
|
switch(iHardwareMode) {
|
|
case LX200_EMULATE_FALSE:
|
|
return(0);
|
|
break;
|
|
case LX200_EMULATE_TRUE:
|
|
return(1);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return(write(fd,szACK,1));
|
|
}
|
|
|
|
/*
|
|
* Reads a terminated line from the scope
|
|
* Returns the number of chars read
|
|
*/
|
|
private int
|
|
lx200_read_from_scope(int fd, char *szpBuffer)
|
|
{
|
|
unsigned char szIn[5], *szp=szpBuffer;
|
|
int iChars = 0, iBytes;
|
|
|
|
/* Emulation will be performed with an addition to lx200_get_generic()
|
|
* of a parameter of what to return in each instance. But,
|
|
* full hardware emulation is for later
|
|
*/
|
|
if(fHardwareEmulate)
|
|
return(TRUE);
|
|
|
|
while(1) { /* loop from input */
|
|
iBytes = lx200_read_one (fd);
|
|
if (iBytes == LX200_FALSE)
|
|
return (LX200_FALSE);
|
|
szIn[0] = iBytes; /* to debug later */
|
|
if(szIn[0]==LX200_TERMINATOR_C) {
|
|
*szp='\0';
|
|
#ifdef DEBUG
|
|
printf("Reply: %s\n",szpBuffer);
|
|
#endif
|
|
break;
|
|
}
|
|
*szp++=szIn[0];
|
|
iChars++;
|
|
}
|
|
return iChars;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Base Library Functions *
|
|
* These functions perform a single action by communicating with the scope *
|
|
* in a simple step. The output is performed and any input is returned. *
|
|
* All functions return 1 on success and -1 on failure. *
|
|
* Any informational functions return values as parameters *
|
|
* These simple functions are combined to perform more powerful functions *
|
|
* All functions should should make a single call to use, and await a *
|
|
* return value, indicating success or failure. *
|
|
* *
|
|
* An application may choose to use some of these, so most are exported *
|
|
* Exports: *
|
|
****************************************************************************/
|
|
|
|
/*
|
|
* The generic get routine
|
|
* Given a command, it returns the raw string form the scope
|
|
* with appropriate error checking
|
|
*/
|
|
private int
|
|
lx200_get_generic(int fd, char *szCmd, char *buf, char *szEmulated)
|
|
{
|
|
|
|
if(fHardwareEmulate) {
|
|
if(iHardwareMode==TRUE) {
|
|
strcpy(buf,szEmulated);
|
|
return(LX200_TRUE);
|
|
}
|
|
else {
|
|
return(LX200_FALSE);
|
|
}
|
|
}
|
|
|
|
if(!lx200_write_to_scope(fd,szCmd)) /*Request RA*/
|
|
return(LX200_FALSE);
|
|
if(!lx200_read_from_scope(fd, buf))
|
|
return(LX200_FALSE);
|
|
return(LX200_TRUE);
|
|
}
|
|
|
|
/********************** Get Functions ****************************/
|
|
/* Most of these are implemented through a macro in liblx200.h
|
|
* to call lx200_get_generic()
|
|
* There really ins't a lot of code this way.
|
|
* See lx200_get in liblx200.h
|
|
*/
|
|
|
|
/*
|
|
* Request the alignment mode
|
|
* returns on of the following:
|
|
* LX200_OPT_MODE_ALTAZ - Alt-Az mode
|
|
* LX200_OPT_MODE_POLAR - Polar aligned
|
|
* LX200_OPT_MODE_LAND - Land mode (no tracking)
|
|
* LX200_OPT_MODE_GPOLAR - German Polar
|
|
* LX200_FALSE - Smell the smoke?
|
|
*/
|
|
public int
|
|
lx200_get_mode(int fd)
|
|
{
|
|
char cMode;
|
|
|
|
if(fHardwareEmulate) {
|
|
if(iHardwareMode==TRUE)
|
|
return(LX200_MODE_POLAR);
|
|
else
|
|
return(LX200_FALSE);
|
|
}
|
|
|
|
if(lx200_send_ACK(fd)==LX200_FALSE)
|
|
return(LX200_FALSE);
|
|
cMode = lx200_read_one(fd);
|
|
switch(cMode) {
|
|
case 'A':
|
|
return(LX200_MODE_ALTAZ);
|
|
case 'L':
|
|
return(LX200_MODE_LAND);
|
|
case 'P':
|
|
return(LX200_MODE_POLAR);
|
|
case 'G':
|
|
return(LX200_MODE_GPOLAR);
|
|
default:
|
|
return(LX200_FALSE);
|
|
}
|
|
}
|
|
|
|
/* Macro defined functions
|
|
* Request the Alt and return a string in format sDD:MM:SS
|
|
* lx200_get_alt()
|
|
|
|
* Request the Az and return a string in format DDD:MM:SS
|
|
* lx200_get_az()
|
|
|
|
* Request the sidereal time and return a string in format HH:MM:SS
|
|
* lx200_get_sidereal()
|
|
|
|
* Request the local time and return a string in format DDD:MM:SS
|
|
* lx200_get_local12()
|
|
|
|
* Request the local time and return a string in format DDD:MM:SS
|
|
* lx200_get_local24()
|
|
|
|
* Request the date and return a string in format MM/DD/YY
|
|
* lx200_get_date()
|
|
|
|
* Request the site latitude and return a string in format sDD*MM
|
|
* lx200_get_latitude()
|
|
|
|
* Request the site longitude and return a string in format DDD*MM
|
|
* lx200_get_longitude()
|
|
|
|
* Request the offset from GMT and return a string in format sHH
|
|
* lx200_get_GMT_offset()
|
|
|
|
* Request the object RA and return a string in format HH:MM:SS
|
|
* lx200_get_obj_RA()
|
|
|
|
* Request the object dec and return a string in format sDD*MM:SS
|
|
* lx200_get_obj_dec()
|
|
|
|
* Request the filter type string and return a string containing the types
|
|
* Types can be looked up with lx200_filter_type_map()
|
|
* A type string can be broken into an array of types with lx200_filter_string_map()
|
|
* lx200_get_filter_type()
|
|
|
|
* Request the filter quality and return a string with the quality code
|
|
* The quality code can be looked up with lx200_filter_quality_map()
|
|
* lx200_get_filter_quality()
|
|
|
|
* Request the filter horizon limit and return a string in format DD*
|
|
* lx200_get_filter_horizon()
|
|
|
|
* Request the filter minimum magnitude and return a string in format sMM.M
|
|
* lx200_get_filter_minmag()
|
|
|
|
* Request the filter maximum magnitude and return a string in format sMM.M
|
|
* lx200_get_filter_maxmag()
|
|
|
|
* Request the filter minimum size and return a string in format NNN'
|
|
* lx200_get_filter_minsize()
|
|
|
|
* Request the filter maximum size and return a string in format NNN'
|
|
* lx200_get_filter_maxsize()
|
|
|
|
* Request the field radius and return a string in format NNN'
|
|
* lx200_get_field_radius()
|
|
|
|
* Request the field information and return it in a string
|
|
* The string contains the number of objects in the field
|
|
* and the object closest to the center
|
|
* use lx200_field_info_map to crack it
|
|
* lx200_get_field_info()
|
|
|
|
* Request the object information
|
|
* use lx200_obj_info_map() to build an lx200_obj structure
|
|
* lx200_get_obj_field()
|
|
|
|
* Request the track frequency in the format TT.T
|
|
* use lx200_freq_map() to map to a double
|
|
* lx200_get_track()
|
|
|
|
* Request the status bars on a GOTO
|
|
* use lx200_status_map() to map to an integer indicating
|
|
* how far the GOTO is along
|
|
* lx200_get_status()
|
|
|
|
*/
|
|
|
|
/*
|
|
* Request a site name (0-4), this is a three letter code
|
|
* The code is virtually worthless, but it probably
|
|
* means something to the user
|
|
*/
|
|
public int
|
|
lx200_get_site_name(int fd, char *buf, int iSite)
|
|
{
|
|
char szCmd[3]="G";
|
|
|
|
switch(iSite) {
|
|
case 1:
|
|
szCmd[1]='M';
|
|
break;
|
|
case 2:
|
|
szCmd[1]='N';
|
|
break;
|
|
case 3:
|
|
szCmd[1]='O';
|
|
break;
|
|
case 4:
|
|
szCmd[1]='P';
|
|
break;
|
|
default:
|
|
return(LX200_FALSE);
|
|
}
|
|
szCmd[2]='\0'; /*Don't forget the Terminator*/
|
|
return(lx200_get_generic(fd, szCmd, buf,"PBY"));
|
|
}
|
|
|
|
/*
|
|
* Request the status of the clock
|
|
* returns LX200_OPT_CLOCK24 if it's set for 24 hours
|
|
* returns LX200_OPT_CLOCK12 if it's set for 12 hours
|
|
*/
|
|
public int
|
|
lx200_get_clock_format(int fd)
|
|
{
|
|
char szIn[3];
|
|
|
|
if(fHardwareEmulate) {
|
|
if(iHardwareMode==TRUE)
|
|
return(LX200_OPT_CLOCK12);
|
|
else
|
|
return(LX200_FALSE);
|
|
}
|
|
|
|
if(lx200_read_two(fd, szIn)==LX200_FALSE)
|
|
return(LX200_FALSE);
|
|
if(szIn[0]=='1' && szIn[1]=='2')
|
|
return(LX200_OPT_CLOCK12);
|
|
if(szIn[0]=='2' && szIn[1]=='4')
|
|
return(LX200_OPT_CLOCK24);
|
|
return(LX200_FALSE);
|
|
}
|
|
|
|
/******************* Telescope Movement ************************/
|
|
/* Starts the scope slewing to set object */
|
|
public int
|
|
lx200_goto(int fd)
|
|
{
|
|
char response,szReturn[201];
|
|
|
|
if(fHardwareEmulate) {
|
|
if(iHardwareMode==TRUE)
|
|
return(LX200_TRUE);
|
|
else
|
|
return(LX200_FALSE);
|
|
}
|
|
|
|
if(lx200_write_to_scope(fd,"MS")==LX200_FALSE)
|
|
return(LX200_FALSE);
|
|
response=lx200_read_one(fd);
|
|
if(response=='0')
|
|
return(LX200_TRUE);
|
|
lx200_read_from_scope(fd,szReturn);
|
|
return(LX200_FALSE);
|
|
}
|
|
|
|
/************************ Set Commands **************************/
|
|
/* All _fset_ expect a preformatted string
|
|
* Use the lx200_format commands to format the strings properly
|
|
* Here was the first decision I could go either way with.
|
|
*
|
|
* All these functions except a string, ie, they have been
|
|
* preformatted. The intention being an app. first calls
|
|
* a formatter, then the set command. But this means the
|
|
* app. has to make two calls, one to format then one
|
|
* to send. Some apps may not need to format, others
|
|
* might, so I came up with two sets of calls.
|
|
*
|
|
* The unformatted calls in which each function accepts the
|
|
* proper number of parameters, calls the formatter, and
|
|
* sends the command. Each function has a varying number of
|
|
* parameters depending on it's function. This means lots
|
|
* of referencing the library documentation to find out
|
|
* what accepts what. Ugly (especially when, if you're
|
|
* reading this, you're reading the documentation for now).
|
|
*
|
|
* The formatted set expects to to be previously
|
|
* formatted and sends it. This makes the unformatted calls
|
|
* wrappers to the formatted calls. Then we use about 10 functions
|
|
* to create the proper format. They implement the unformatted
|
|
* calls as macros (most anyway) to call the proper formatter and the
|
|
* asscoiated formatted call. This means less reference work,
|
|
* but more complex library code. I'd rather have the library
|
|
* complicated then the application.
|
|
*
|
|
* With this in place, anyone gets whatever they need, though
|
|
* most will probably end up using the macros in the header
|
|
* file. If the application performs verification and formatting
|
|
* the _fset_ calls are used
|
|
*/
|
|
|
|
/*
|
|
* Send out a set command
|
|
* All sets are followed by the return of "Ok"
|
|
*/
|
|
private int
|
|
lx200_set_generic(int fd, char *szCmd, char *szValue)
|
|
{
|
|
char szFCmd[255];
|
|
|
|
snprintf(szFCmd,254,"%s%s",szCmd,szValue);
|
|
if(lx200_write_to_scope(fd,szFCmd)==LX200_FALSE)
|
|
return(LX200_FALSE);
|
|
return(lx200_read_ok(fd));
|
|
}
|
|
|
|
/* This sends a command that the scope will do or not do
|
|
* as it sees fit, because it doesn't tell us anything.
|
|
* Therefore we always return the value of the write
|
|
* operation the scope is actually does it
|
|
*
|
|
* Used primarily for macro expansion
|
|
*/
|
|
private int
|
|
lx200_send_command(int fd, char *szCmd)
|
|
{
|
|
return((lx200_write_to_scope(fd,szCmd)==LX200_FALSE));
|
|
}
|
|
|
|
/***** The formated calls*******
|
|
* Keep in mind that using these means the application is
|
|
* responsible for ALL data verifcation. If you ask a
|
|
* user and supply that input to a _fset_, make sure
|
|
* you validate it before passing it to the library.
|
|
*
|
|
* Use the _set_ functions with raw data to get proper
|
|
* validation
|
|
*/
|
|
|
|
/*
|
|
* lx200_toggle_format()
|
|
*
|
|
* The scope can be set for long format using this call
|
|
* Scopes with version 3.30 or above can use this
|
|
* Note with liblx200 v1.0 the short format is
|
|
* NOT supported (too inaccurate) and probably never
|
|
* will be.
|
|
*
|
|
* There is no way to determine the current state of the scope,
|
|
* so this function can only toggle. Use lx200_set_format()
|
|
* to specify a particular format.
|
|
*
|
|
* The formatted ( _fset_ ) functions are marked private
|
|
* but feel free to call them. You won't be invalidating
|
|
* the library.
|
|
*
|
|
* (MACRO)
|
|
*/
|
|
|
|
|
|
/* The macro functions*/
|
|
/*
|
|
* Set sidereal time
|
|
* Format is HH:MM:SS (24 hour clock)
|
|
* lx200_fset_sidereal()
|
|
|
|
* Set target local time
|
|
* Format is HH:MM:SS
|
|
* lx200_fset_local24()
|
|
|
|
* Set target date
|
|
* Format is MM/DD/YY
|
|
* lx200_fset_date()
|
|
|
|
* Set site latitude
|
|
* Format is sDD*MM
|
|
* lx200_fset_latitude()
|
|
|
|
* Set site longitude
|
|
* Format is sDD*MM
|
|
* lx200_fset_longitude()
|
|
|
|
* Set GMT offset
|
|
* Format is sHH
|
|
* lx200_fset_GMT_offset()
|
|
|
|
* Set object RA
|
|
* Format is HH:MM:SS
|
|
* lx200_fset_obj_RA()
|
|
|
|
* Set object declination
|
|
* Format is sDD*MM:SS
|
|
* lx200_fset_obj_dec()
|
|
|
|
* Set the filter type string
|
|
* Format is special
|
|
* lx200_fset_filter_type()
|
|
|
|
* Set the filter horizon
|
|
* Format is DD
|
|
* lx200_fset_filter_horizon()
|
|
|
|
* Set the filter minimum magnitude
|
|
* Format is sMM.M
|
|
* lx200_fset_filter_minmag()
|
|
|
|
* Set the filter maximum magnitude
|
|
* Format is sMM.M
|
|
* lx200_fset_filter_maxmag()
|
|
|
|
* Set the filter minimum size
|
|
* Format is NNN (in arc minutes)
|
|
* lx200_fset_filter_minsize()
|
|
|
|
* Set the filter maximum size
|
|
* Format is NNN (in arc minutes)
|
|
* lx200_fset_filter_maxsize()
|
|
|
|
* Set the field radius
|
|
* Format is NNN
|
|
* lx200_fset_field_radius
|
|
|
|
* Set the catalog to use for stars
|
|
* format is N
|
|
|
|
* Set target to a star
|
|
* Format is NNNN
|
|
* This selects within the current catalog
|
|
* Use lx200_set_star_catalog() to choose a catalog
|
|
* lx200_goto_star() does both
|
|
*/
|
|
|
|
|
|
/*
|
|
* Set the calendar date
|
|
* Could have used a macro but
|
|
* the scope returns "Updating planetary data"
|
|
* first followed by a string of blanks.
|
|
* The only questions is, "Why?"
|
|
*/
|
|
public int
|
|
lx200_fset_date(int fd, char *sDate)
|
|
{
|
|
char szReturn[51],szCmd[11];
|
|
|
|
snprintf(szCmd,10,"SC%8s",sDate);
|
|
if(lx200_write_to_scope(fd,szCmd)==LX200_FALSE)
|
|
return(LX200_FALSE);
|
|
if(lx200_read_from_scope(fd,szReturn))
|
|
return(LX200_FALSE);
|
|
if(lx200_read_from_scope(fd,szReturn))
|
|
return(LX200_FALSE);
|
|
return(LX200_TRUE);
|
|
}
|
|
|
|
/*
|
|
* Set the reticle brightness to a level 1-3
|
|
* Mode is
|
|
* LX200_OPT_CONTINOUS reticle on all the time
|
|
* LX200_OPT_FLASH50 reticle on 50%
|
|
* LX200_OPT_FLASH25 reticle on 25%
|
|
* LX200_OPT_FLASH10 reticle on 10%
|
|
*/
|
|
public int
|
|
lx200_set_reticle_flash(int fd, int iMode)
|
|
{
|
|
char szCmd[4]="B";
|
|
|
|
if(iMode<LX200_OPT_CONTINOUS || iMode>LX200_OPT_FLASH10)
|
|
return(LX200_FALSE);
|
|
|
|
szCmd[1]=iMode - LX200_OPT_CONTINOUS;
|
|
szCmd[2]='\0';
|
|
return(lx200_send_command(fd,szCmd));
|
|
|
|
}
|
|
|
|
/*
|
|
* Sync on current object
|
|
*/
|
|
public int
|
|
lx200_obj_sync(int fd, char *szpObj)
|
|
{
|
|
if(lx200_send_command(fd,"CM")==FALSE)
|
|
return(LX200_FALSE);
|
|
if(lx200_read_from_scope(fd,szpObj)==LX200_FALSE)
|
|
return(LX200_FALSE);
|
|
return(LX200_TRUE);
|
|
}
|
|
|
|
/*
|
|
* Set a site (1-4) name to a
|
|
* three letter name
|
|
*/
|
|
public int
|
|
lx200_set_site_name(int fd, char *buf, int iSite)
|
|
{
|
|
char szCmd[3]="S",szSite[4];
|
|
|
|
if(iSite<1 || iSite>4) /*Error condition invalid site*/
|
|
return(LX200_FALSE);
|
|
if(strlen(buf)>3 || buf[0]=='\0') /*Error condition invalid name*/
|
|
return(LX200_FALSE); /*Probably ought to verify all characters are acceptable*/
|
|
szCmd[1]='L' + iSite;
|
|
szCmd[2]='\0';
|
|
snprintf(szSite,6,"%3s",buf);
|
|
return(lx200_set_generic(fd,szCmd,szSite));
|
|
}
|
|
|
|
/************************************************************************
|
|
* Main Library Functions *
|
|
* These functions are the ones the most applications programs will *
|
|
* call. They combine the base library functions together to perform *
|
|
* large actions, such as selecting and slewing to a target object, *
|
|
* returning scope information, or performing telescope finds. *
|
|
* *
|
|
* These are consider the high-level routines of the library. *
|
|
************************************************************************/
|
|
/*
|
|
* Given a star number and catalog, this performs a goto
|
|
*/
|
|
/*
|
|
public int
|
|
lx200_goto_star(int fd, int iStar, int iCatalog)
|
|
{
|
|
|
|
#ifdef DEBUG
|
|
printf("Called lx200_goto_star for %d\n",iStar,iCatalog); fflush(stdout);
|
|
#endif
|
|
if(lx200_set_star_catalog(fd,iCatalog)==LX200_FALSE)
|
|
return(LX200_FALSE);
|
|
if(lx200_set_star(fd, iStar)==LX200_FALSE)
|
|
return(LX200_FALSE);
|
|
return(lx200_goto(fd));
|
|
}
|
|
*/
|
|
|
|
/*
|
|
* Goto a given Dec and RA
|
|
*/
|
|
public int
|
|
lx200_goto_RADec(int fd, char *szpRa, char *szpDec)
|
|
{
|
|
if(lx200_fset_obj_RA(fd, szpRa)==LX200_FALSE)
|
|
return(LX200_FALSE);
|
|
if(lx200_fset_obj_dec(fd, szpDec)==FALSE)
|
|
return(LX200_FALSE);
|
|
return(lx200_goto(fd));
|
|
}
|
|
|
|
/*
|
|
* Goto to an extended object
|
|
* Need catalog which is one of the following
|
|
* LX200_MESSIER_CATLOG
|
|
* LX200_NGC_CATALOG
|
|
* LX200_IC_CATALOG
|
|
* LX200_UGC_CATALOG
|
|
* and the object number
|
|
*/
|
|
public int
|
|
lx200_goto_ext(int fd, int iObj, int iCatalog)
|
|
{
|
|
char szObj[6];
|
|
|
|
/*Check catalog. Is it valid?*/
|
|
if(iCatalog<LX200_MESSIER_CATALOG || iCatalog > LX200_UGC_CATALOG)
|
|
return(LX200_FALSE);
|
|
if(iObj<1) /*Object less then one*/
|
|
return(LX200_FALSE);
|
|
|
|
switch(iCatalog) {
|
|
case LX200_MESSIER_CATALOG:
|
|
if(iObj>101)
|
|
return(LX200_FALSE);
|
|
if(lx200_format_messier(iObj,szObj))
|
|
return(LX200_FALSE);
|
|
if(lx200_fset_messier(fd,szObj))
|
|
return(LX200_FALSE);
|
|
break;
|
|
case LX200_NGC_CATALOG:
|
|
if(iObj>7840)
|
|
return(LX200_FALSE);
|
|
/*I can cheat, it's my library!*/
|
|
if(lx200_set_ext_ngc(fd)==LX200_FALSE)
|
|
return(LX200_FALSE);
|
|
if(lx200_format_ngc(iObj,szObj))
|
|
return(LX200_FALSE);
|
|
if(lx200_fset_ext(fd,szObj)==LX200_FALSE)
|
|
return(LX200_FALSE);
|
|
break;
|
|
case LX200_IC_CATALOG:
|
|
if(iObj>5386)
|
|
return(LX200_FALSE);
|
|
if(lx200_set_ext_ic(fd)==LX200_FALSE)
|
|
return(LX200_FALSE);
|
|
if(lx200_format_ic(iObj,szObj))
|
|
return(LX200_FALSE);
|
|
return(lx200_fset_ext(fd,szObj));
|
|
break;
|
|
case LX200_UGC_CATALOG:
|
|
if(iObj>12921)
|
|
return(LX200_FALSE);
|
|
if(lx200_set_ext_ugc(fd)==LX200_FALSE)
|
|
return(LX200_FALSE);
|
|
if(lx200_format_ugc(iObj,szObj))
|
|
return(LX200_FALSE);
|
|
if(lx200_fset_ext(fd,szObj)==LX200_FALSE)
|
|
return(LX200_FALSE);
|
|
break;
|
|
default:
|
|
return(LX200_FALSE);
|
|
}
|
|
return(lx200_goto(fd));
|
|
}
|
|
|
|
/* Set the format to long or short
|
|
* All other liblx200 functions
|
|
* assume the format is long, so the application
|
|
* currently needs to call lx200_set_format
|
|
* passing in LX200_OPT_LONG_FORMAT
|
|
* if anything involing degrees
|
|
* hours minutes and seconds
|
|
* Old format New format
|
|
* HH:MM.T HH:MM:SS
|
|
* sDD*MM sDD:MM:SS
|
|
* DDD*MM SSS*MM:SS
|
|
*/
|
|
public int
|
|
lx200_set_format(int fd, int iSetState)
|
|
{
|
|
char szBuf[15];
|
|
int iCurState;
|
|
|
|
/* Get the current state.*/
|
|
if(lx200_get_dec(fd, szBuf)==LX200_FALSE)
|
|
return(LX200_FALSE);
|
|
#ifdef DEBUG
|
|
printf("lx200lib %s\n",szBuf);
|
|
#endif
|
|
if(strlen(szBuf)<8)
|
|
iCurState=LX200_OPT_SHORT_FORMAT;
|
|
else
|
|
iCurState=LX200_OPT_LONG_FORMAT;
|
|
|
|
if(iSetState!=iCurState)
|
|
return(lx200_toggle_format(fd));
|
|
return(LX200_TRUE);
|
|
}
|
|
|
|
/*
|
|
* Set filter type
|
|
* Type is a bitmap of the following
|
|
* LX200_TYPE_GALAXIES
|
|
* LX200_TYPE_PLANETARY
|
|
* LX200_TYPE_DIFFUSE
|
|
* LX200_TYPE_GLOBULAR
|
|
* LX200_TYPE_OPEN
|
|
*/
|
|
private int
|
|
lx200_set_filter_type(int fd, int iType)
|
|
{
|
|
char szType[5]="";
|
|
|
|
/*Build type string*/
|
|
if(iType & LX200_TYPE_GALAXIES)
|
|
strcat(szType,"G");
|
|
if(iType & LX200_TYPE_PLANETARY)
|
|
strcat(szType,"P");
|
|
if(iType & LX200_TYPE_DIFFUSE)
|
|
strcat(szType,"D");
|
|
if(iType & LX200_TYPE_GLOBULAR)
|
|
strcat(szType,"C");
|
|
if(iType & LX200_TYPE_OPEN)
|
|
strcat(szType,"O");
|
|
|
|
return(lx200_fset_filter_type(fd,szType));
|
|
}
|
|
|
|
/* Set site number 1 - 4
|
|
* see lx200_get_site_name() and lx200_set_site_name()
|
|
*/
|
|
public int
|
|
lx200_set_site_number(int fd, int iSite)
|
|
{
|
|
char szCmd[5]="W";
|
|
|
|
if(iSite<1 || iSite>4)
|
|
return(LX200_FALSE);
|
|
szCmd[1]='0' + iSite;
|
|
return(lx200_send_command(fd,szCmd));
|
|
}
|
|
|
|
/*************************************************************************
|
|
* Support Library Functions *
|
|
* These functions are support functions that don't actually perform *
|
|
* telescope functions. Instead they perform such actions as formatting *
|
|
* data, performing table lookups, and handling data conversion. *
|
|
*************************************************************************/
|
|
|
|
/*
|
|
* A very fast way to break LX200 RA string into integers
|
|
* And no, I won't stop using atoi() in lieu of strtol().
|
|
* Some things just don't need to be fixed. You can have
|
|
* it when you pry the keyboard from my cold, dead
|
|
* fingers.
|
|
*/
|
|
public int
|
|
lx200_convert_RA(char *szpRA, int *RH, int *RM, int *RS)
|
|
{
|
|
char s[3];
|
|
|
|
s[2] = 0;
|
|
/*Get R HOUR*/
|
|
s[0] = szpRA[0];
|
|
s[1] = szpRA[1];
|
|
*RH=atoi(s);
|
|
|
|
/*Get R Minute*/
|
|
s[0] = szpRA[3];
|
|
s[1] = szpRA[4];
|
|
*RM=atoi(s);
|
|
|
|
/*Get R Seond*/
|
|
s[0] = szpRA[6];
|
|
s[1] = szpRA[7];
|
|
*RS=atoi(s);
|
|
|
|
return(LX200_TRUE);
|
|
}
|
|
|
|
/*
|
|
* A very fast way to break LX200 Dec string into integers
|
|
*/
|
|
public int
|
|
lx200_convert_Dec(char *szpRA, int *DD, int *DM, int *DS)
|
|
{
|
|
char s[4];
|
|
|
|
s[3] = 0;
|
|
|
|
/*Get Dec Degrees*/
|
|
s[0] = szpRA[0];
|
|
s[1] = szpRA[1];
|
|
s[2] = szpRA[2];
|
|
*DD=atoi(s);
|
|
|
|
/*Get Dec Minute*/
|
|
s[0] = szpRA[4];
|
|
s[1] = szpRA[5];
|
|
s[2] = 0;
|
|
*DM=atoi(s);
|
|
|
|
/*Get D Second*/
|
|
s[0] = szpRA[7];
|
|
s[1] = szpRA[8];
|
|
*DS=atoi(s);
|
|
|
|
return(LX200_TRUE);
|
|
}
|
|
|
|
/*
|
|
* Given a planet name, this function returns the star number
|
|
* if the planet string isn't real, return -1 */
|
|
public int
|
|
lx200_map_planet_id(char *szName)
|
|
{
|
|
int i;
|
|
|
|
for(i=0;i<9;i++)
|
|
if(!strcmp(szName,lx200_planets[i]))
|
|
return(900 + i);
|
|
return(LX200_FALSE);
|
|
}
|
|
|
|
/************************ Format functions ******************************
|
|
* These perform generic format routines
|
|
* and are called by macro in almost all
|
|
* cases
|
|
* Once again, an application can call them
|
|
* directly, but the macros make more sense.
|
|
* the _format_ macros are put together from
|
|
* the are _fmt_ functions
|
|
*/
|
|
|
|
/*An integer iDigits in length*/
|
|
private int
|
|
lx200_fmt_number(int iNumber, int iDigits, char cEnd, char *szp)
|
|
{
|
|
char szFormat[8],szEnd[2];
|
|
|
|
sprintf(szFormat,"%c%ds",'%',iDigits);
|
|
sprintf(szp,szFormat,iNumber);
|
|
if(cEnd!='\0') {
|
|
szEnd[0]=cEnd;
|
|
szEnd[1]='\0';
|
|
strcat(szp,szEnd);
|
|
}
|
|
|
|
return(LX200_TRUE);
|
|
}
|
|
|
|
/* NN<ifs>NN<ifs>NN for
|
|
* HH:MM:SS
|
|
* MM/DD/YY
|
|
*/
|
|
private int
|
|
lx200_fmt_time(int HH, int MM, int SS, char cIFS1, char cIFS2, int iSigned, char *szp)
|
|
{
|
|
if(iSigned)
|
|
snprintf(szp,9,"%c%2d%c%2d%c%2d",HH < 0 ? '-' : '+',HH,cIFS1,MM,cIFS2,SS);
|
|
else
|
|
snprintf(szp,8,"%2d%c%2d%c%2d",HH,cIFS1,MM,cIFS2,SS);
|
|
return(LX200_TRUE);
|
|
}
|
|
|
|
/*DDD<ifs>MM<ifs>SS*/
|
|
private int
|
|
lx200_fmt_coord(int DDD, int MM, int SS, char cIFS1, char cIFS2, char *szp)
|
|
{
|
|
snprintf(szp,9,"%3d%c%2d%c%2d",DDD,cIFS1,MM,cIFS2,SS);
|
|
return(LX200_TRUE);
|
|
}
|
|
|
|
/*sHH or DD or DD(*) */
|
|
private int
|
|
lx200_fmt_hour(int HH, int iSigned, char cEnd, char *szp)
|
|
{
|
|
char szEnd[2];
|
|
|
|
if(iSigned)
|
|
snprintf(szp,3,"%c%2d",HH < 0 ? '-' : '+',HH);
|
|
else
|
|
snprintf(szp,2,"%2d",HH);
|
|
if(cEnd!='\0') {
|
|
szEnd[0]=cEnd;
|
|
szEnd[1]='\0';
|
|
strcat(szp,szEnd);
|
|
}
|
|
return(LX200_TRUE);
|
|
}
|
|
|
|
/*sMM.M or TT.T*/
|
|
private int
|
|
lx200_fmt_magnitude(double d, int iSigned, char *szp)
|
|
{
|
|
if(iSigned)
|
|
snprintf(szp,5,"%c%3.1f",(char)d < 0 ? '-' : '+',d);
|
|
else
|
|
snprintf(szp,4,"%3.1f",d);
|
|
return(LX200_TRUE);
|
|
}
|
|
|
|
|
|
/******************* MISC *********************/
|
|
|
|
/*
|
|
* Return the version of liblx200 in a string
|
|
*/
|
|
public int
|
|
lx200_get_lib_version(char *sz)
|
|
{
|
|
strcpy (sz, "$Revision: 1.8 $");
|
|
return(LX200_TRUE);
|
|
}
|
|
|
|
/*
|
|
* Control hardware emulate mode
|
|
*/
|
|
public int
|
|
lx200_set_lib_emulate(int fEmulate, int iMode)
|
|
{
|
|
if(iMode!=LX200_EMULATE_FALSE && iMode!=LX200_EMULATE_TRUE)
|
|
return(LX200_FALSE);
|
|
if(fEmulate==LX200_TRUE) {
|
|
fHardwareEmulate=LX200_TRUE;
|
|
iHardwareMode=iMode;
|
|
}
|
|
else
|
|
if(fEmulate==LX200_FALSE)
|
|
fHardwareEmulate=LX200_FALSE;
|
|
else
|
|
return(LX200_FALSE);
|
|
return(LX200_TRUE);
|
|
}
|
|
|