#if 0 Celestron GPS Copyright (C) 2003 Jasem Mutlaq (mutlaqja@ikarustech.com) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA #endif #include #include #include #include #include #include #include #include "celestronprotocol.h" #include "celestrongps.h" #define RA_THRESHOLD 0.01 #define DEC_THRESHOLD 0.05 #define mydev "Celestron GPS" CelestronGPS *telescope = NULL; /* There is _one_ binary for all LX200 drivers, but each binary is renamed ** to its device name (i.e. lx200gps, lx200_16..etc). The main function will ** fetch from std args the binary name and ISInit will create the apporpiate ** device afterwards. If the binary name does not match any known devices, ** we simply create a generic device */ extern char* me; #define COMM_GROUP "Communication" #define BASIC_GROUP "Main Control" #define MOVE_GROUP "Movement Control" static void ISPoll(void *); /*INDI controls */ static ISwitch PowerS[] = {{"CONNECT" , "Connect" , ISS_OFF, 0, 0},{"DISCONNECT", "Disconnect", ISS_ON, 0, 0}}; static ISwitch SlewModeS[] = {{"Slew", "", ISS_ON, 0, 0}, {"Find", "", ISS_OFF, 0, 0}, {"Centering", "", ISS_OFF, 0, 0}, {"Guide", "", ISS_OFF, 0, 0}}; static ISwitch OnCoordSetS[] = {{"SLEW", "Slew", ISS_ON, 0 , 0}, {"TRACK", "Track", ISS_OFF, 0, 0}, {"SYNC", "Sync", ISS_OFF, 0, 0}}; static ISwitch abortSlewS[] = {{"ABORT", "Abort", ISS_OFF, 0, 0}}; static ISwitch MovementS[] = {{"N", "North", ISS_OFF, 0, 0}, {"W", "West", ISS_OFF, 0, 0}, {"E", "East", ISS_OFF, 0, 0}, {"S", "South", ISS_OFF, 0, 0}}; /* equatorial position */ static INumber eq[] = { {"RA", "RA H:M:S", "%10.6m", 0., 24., 0., 0., 0, 0, 0}, {"DEC", "Dec D:M:S", "%10.6m", -90., 90., 0., 0., 0, 0, 0}, }; //TODO decide appropiate TIME_OUT static INumberVectorProperty eqNum = { mydev, "EQUATORIAL_EOD_COORD", "Equatorial JNow", BASIC_GROUP, IP_RW, 0, IPS_IDLE, eq, NARRAY(eq), "", 0}; /* Fundamental group */ static ISwitchVectorProperty PowerSw = { mydev, "CONNECTION" , "Connection", COMM_GROUP, IP_RW, ISR_1OFMANY, 0, IPS_IDLE, PowerS, NARRAY(PowerS), "", 0}; static IText PortT[] = {{"PORT", "Port", 0, 0, 0, 0}}; static ITextVectorProperty Port = { mydev, "DEVICE_PORT", "Ports", COMM_GROUP, IP_RW, 0, IPS_IDLE, PortT, NARRAY(PortT), "", 0}; /* Movement group */ static ISwitchVectorProperty OnCoordSetSw = { mydev, "ON_COORD_SET", "On Set", BASIC_GROUP, IP_RW, ISR_1OFMANY, 0, IPS_IDLE, OnCoordSetS, NARRAY(OnCoordSetS), "", 0}; static ISwitchVectorProperty abortSlewSw = { mydev, "ABORT_MOTION", "Abort Slew/Track", BASIC_GROUP, IP_RW, ISR_1OFMANY, 0, IPS_IDLE, abortSlewS, NARRAY(abortSlewS), "", 0}; static ISwitchVectorProperty SlewModeSw = { mydev, "Slew rate", "", MOVE_GROUP, IP_RW, ISR_1OFMANY, 0, IPS_IDLE, SlewModeS, NARRAY(SlewModeS), "", 0}; static ISwitchVectorProperty MovementSw = { mydev, "MOVEMENT", "Move toward", MOVE_GROUP, IP_RW, ISR_1OFMANY, 0, IPS_IDLE, MovementS, NARRAY(MovementS), "", 0}; /* send client definitions of all properties */ void ISInit() { static int isInit=0; if (isInit) return; isInit = 1; PortT[0].text = strcpy(new char[32], "/dev/ttyS0"); telescope = new CelestronGPS(); IEAddTimer (POLLMS, ISPoll, NULL); } void ISGetProperties (const char *dev) { ISInit(); telescope->ISGetProperties(dev);} void ISNewSwitch (const char *dev, const char *name, ISState *states, char *names[], int n) { ISInit(); telescope->ISNewSwitch(dev, name, states, names, n);} void ISNewText (const char *dev, const char *name, char *texts[], char *names[], int n) { ISInit(); telescope->ISNewText(dev, name, texts, names, n);} void ISNewNumber (const char *dev, const char *name, double values[], char *names[], int n) { ISInit(); telescope->ISNewNumber(dev, name, values, names, n);} void ISPoll (void *p) { telescope->ISPoll(); IEAddTimer (POLLMS, ISPoll, NULL); p=p;} void ISNewBLOB (const char */*dev*/, const char */*name*/, int */*sizes[]*/, char **/*blobs[]*/, char **/*formats[]*/, char **/*names[]*/, int /*n*/) {} /************************************************** *** LX200 Generic Implementation ***************************************************/ CelestronGPS::CelestronGPS() { targetRA = lastRA = 0; targetDEC = lastDEC = 0; currentSet = 0; lastSet = -1; lastMove[0] = lastMove[1] = lastMove[2] = lastMove[3] = 0; JD = 0; // Children call parent routines, this is the default IDLog("initilizaing from Celeston GPS device...\n"); } void CelestronGPS::ISGetProperties(const char *dev) { if (dev && strcmp (mydev, dev)) return; // COMM_GROUP IDDefSwitch (&PowerSw, NULL); IDDefText (&Port, NULL); // BASIC_GROUP IDDefNumber (&eqNum, NULL); IDDefSwitch (&OnCoordSetSw, NULL); IDDefSwitch (&abortSlewSw, NULL); IDDefSwitch (&SlewModeSw, NULL); // Movement group IDDefSwitch (&MovementSw, NULL); /* Send the basic data to the new client if the previous client(s) are already connected. */ if (PowerSw.s == IPS_OK) getBasicData(); } void CelestronGPS::ISNewText (const char *dev, const char *name, char *texts[], char *names[], int n) { IText *tp; // suppress warning n=n; // ignore if not ours // if (strcmp (dev, mydev)) return; if (!strcmp(name, Port.name) ) { Port.s = IPS_OK; tp = IUFindText( &Port, names[0] ); if (!tp) return; tp->text = new char[strlen(texts[0])+1]; strcpy(tp->text, texts[0]); IDSetText (&Port, NULL); return; } } int CelestronGPS::handleCoordSet() { int i=0; char RAStr[32], DecStr[32]; switch (currentSet) { // Slew case 0: lastSet = 0; if (eqNum.s == IPS_BUSY) { StopNSEW(); // sleep for 500 mseconds usleep(500000); } if ((i = SlewToCoords(targetRA, targetDEC))) { slewError(i); return (-1); } eqNum.s = IPS_BUSY; fs_sexa(RAStr, targetRA, 2, 3600); fs_sexa(DecStr, targetDEC, 2, 3600); IDSetNumber(&eqNum, "Slewing to JNOW RA %s - DEC %s", RAStr, DecStr); IDLog("Slewing to JNOW RA %s - DEC %s", RAStr, DecStr); break; // Track case 1: if (eqNum.s == IPS_BUSY) { StopNSEW(); // sleep for 500 mseconds usleep(500000); } if ( (fabs ( targetRA - currentRA ) >= TRACKING_THRESHOLD) || (fabs (targetDEC - currentDEC) >= TRACKING_THRESHOLD)) { IDLog("Exceeded Tracking threshold, will attempt to slew to the new target.\n"); IDLog("targetRA is %g, currentRA is %g\n", targetRA, currentRA); IDLog("targetDEC is %g, currentDEC is %g\n*************************\n", targetDEC, currentDEC); if (( i = SlewToCoords(targetRA, targetDEC))) { slewError(i); return (-1); } fs_sexa(RAStr, targetRA, 2, 3600); fs_sexa(DecStr, targetDEC, 2, 3600); eqNum.s = IPS_BUSY; IDSetNumber(&eqNum, "Slewing to JNow RA %s - DEC %s", RAStr, DecStr); IDLog("Slewing to JNOW RA %s - DEC %s", RAStr, DecStr); } else { IDLog("Tracking called, but tracking threshold not reached yet.\n"); eqNum.s = IPS_OK; eqNum.np[0].value = currentRA; eqNum.np[1].value = currentDEC; if (lastSet != 1) IDSetNumber(&eqNum, "Tracking..."); else IDSetNumber(&eqNum, NULL); } lastSet = 1; break; // Sync case 2: lastSet = 2; OnCoordSetSw.s = IPS_OK; SyncToCoords(targetRA, targetDEC); eqNum.s = IPS_OK; IDSetNumber(&eqNum, "Synchronization successful."); break; } return (0); } void CelestronGPS::ISNewNumber (const char *dev, const char *name, double values[], char *names[], int n) { double newRA=0, newDEC=0; // ignore if not ours // if (strcmp (dev, mydev)) return; struct tm *tp; time_t t; time (&t); tp = gmtime (&t); if (!strcmp (name, eqNum.name)) { int i=0, nset=0; if (checkPower(&eqNum)) return; for (nset = i = 0; i < n; i++) { INumber *eqp = IUFindNumber (&eqNum, names[i]); if (eqp == &eq[0]) { newRA = values[i]; nset += newRA >= 0 && newRA <= 24.0; } else if (eqp == &eq[1]) { newDEC = values[i]; nset += newDEC >= -90.0 && newDEC <= 90.0; } } if (nset == 2) { //eqNum.s = IPS_BUSY; tp->tm_mon += 1; tp->tm_year += 1900; // update JD JD = UTtoJD(tp); IDLog("We recevined JNOW RA %f - DEC %f\n", newRA, newDEC);; /*apparentCoord( (double) J2000, JD, &newRA, &newDEC); IDLog("Processed to RA %f - DEC %f\n", newRA, newDEC);*/ //eqNum.np[0].value = values[0]; //eqNum.np[1].value = values[1]; targetRA = newRA; targetDEC = newDEC; if (MovementSw.s == IPS_BUSY) { for (int i=0; i < 4; i++) { lastMove[i] = 0; MovementS[i].s = ISS_OFF; } MovementSw.s = IPS_IDLE; IDSetSwitch(&MovementSw, NULL); } if (handleCoordSet()) { eqNum.s = IPS_IDLE; IDSetNumber(&eqNum, NULL); } } else { eqNum.s = IPS_IDLE; IDSetNumber(&eqNum, "RA or Dec missing or invalid."); } return; } } void CelestronGPS::ISNewSwitch (const char *dev, const char *name, ISState *states, char *names[], int n) { int index; ISwitch *swp; // Suppress warning names = names; //IDLog("in new Switch with Device= %s and Property= %s and #%d items\n", dev, name,n); //IDLog("SolarSw name is %s\n", SolarSw.name); // ignore if not ours // if (strcmp (dev, mydev)) return; // FIRST Switch ALWAYS for power if (!strcmp (name, PowerSw.name)) { IUResetSwitches(&PowerSw); IUUpdateSwitches(&PowerSw, states, names, n); powerTelescope(); return; } if (!strcmp(name, OnCoordSetSw.name)) { if (checkPower(&OnCoordSetSw)) return; IUResetSwitches(&OnCoordSetSw); IUUpdateSwitches(&OnCoordSetSw, states, names, n); currentSet = getOnSwitch(&OnCoordSetSw); } // Abort Slew if (!strcmp (name, abortSlewSw.name)) { if (checkPower(&abortSlewSw)) { abortSlewSw.s = IPS_IDLE; IDSetSwitch(&abortSlewSw, NULL); return; } IUResetSwitches(&abortSlewSw); StopNSEW(); if (eqNum.s == IPS_BUSY) { abortSlewSw.s = IPS_OK; eqNum.s = IPS_IDLE; IDSetSwitch(&abortSlewSw, "Slew aborted."); IDSetNumber(&eqNum, NULL); } else if (MovementSw.s == IPS_BUSY) { for (int i=0; i < 4; i++) lastMove[i] = 0; MovementSw.s = IPS_IDLE; abortSlewSw.s = IPS_OK; eqNum.s = IPS_IDLE; IUResetSwitches(&MovementSw); IUResetSwitches(&abortSlewSw); IDSetSwitch(&abortSlewSw, "Slew aborted."); IDSetSwitch(&MovementSw, NULL); IDSetNumber(&eqNum, NULL); } else { IUResetSwitches(&MovementSw); abortSlewSw.s = IPS_IDLE; IDSetSwitch(&abortSlewSw, NULL); } return; } // Slew mode if (!strcmp (name, SlewModeSw.name)) { if (checkPower(&SlewModeSw)) return; IUResetSwitches(&SlewModeSw); IUUpdateSwitches(&SlewModeSw, states, names, n); index = getOnSwitch(&SlewModeSw); SetRate(index); SlewModeSw.s = IPS_OK; IDSetSwitch(&SlewModeSw, NULL); return; } // Movement if (!strcmp (name, MovementSw.name)) { if (checkPower(&MovementSw)) return; index = -1; IUUpdateSwitches(&MovementSw, states, names, n); swp = IUFindSwitch(&MovementSw, names[0]); if (!swp) { StopNSEW(); IUResetSwitches(&MovementSw); MovementSw.s = IPS_IDLE; IDSetSwitch(&MovementSw, NULL); } if (swp == &MovementS[0]) index = 0; else if (swp == &MovementS[1]) index = 1; else if (swp == &MovementS[2]) index = 2; else index = 3; lastMove[index] = lastMove[index] == 0 ? 1 : 0; if (lastMove[index] == 0) MovementS[index].s = ISS_OFF; // North/South movement is illegal if (lastMove[NORTH] && lastMove[SOUTH]) { StopNSEW(); for (int i=0; i < 4; i++) lastMove[i] = 0; IUResetSwitches(&MovementSw); MovementSw.s = IPS_IDLE; IDSetSwitch(&MovementSw, "Slew aborted."); return; } // East/West movement is illegal if (lastMove[EAST] && lastMove[WEST]) { StopNSEW(); for (int i=0; i < 4; i++) lastMove[i] = 0; IUResetSwitches(&MovementSw); MovementSw.s = IPS_IDLE; IDSetSwitch(&MovementSw, "Slew aborted."); return; } //IDLog("We have switch %d \n ", index); //IDLog("NORTH: %d -- WEST: %d -- EAST: %d -- SOUTH %d\n", lastMove[0], lastMove[1], lastMove[2], lastMove[3]); if (lastMove[index] == 1) StartSlew(index); else StopSlew(index); if (!lastMove[0] && !lastMove[1] && !lastMove[2] && !lastMove[3]) MovementSw.s = IPS_IDLE; if (lastMove[index] == 0) IDSetSwitch(&MovementSw, "Moving toward %s aborted.", Direction[index]); else { MovementSw.s = IPS_BUSY; IDSetSwitch(&MovementSw, "Moving %s...", Direction[index]); } return; } } int CelestronGPS::getOnSwitch(ISwitchVectorProperty *sp) { for (int i=0; i < sp->nsp ; i++) if (sp->sp[i].s == ISS_ON) return i; return -1; } int CelestronGPS::checkPower(ISwitchVectorProperty *sp) { if (PowerSw.s != IPS_OK) { if (!strcmp(sp->label, "")) IDMessage (mydev, "Cannot change property %s while the telescope is offline.", sp->name); else IDMessage (mydev, "Cannot change property %s while the telescope is offline.", sp->label); sp->s = IPS_IDLE; IDSetSwitch(sp, NULL); return -1; } return 0; } int CelestronGPS::checkPower(INumberVectorProperty *np) { if (PowerSw.s != IPS_OK) { if (!strcmp(np->label, "")) IDMessage (mydev, "Cannot change property %s while the telescope is offline.", np->name); else IDMessage (mydev, "Cannot change property %s while the telescope is offline.", np->label); np->s = IPS_IDLE; IDSetNumber(np, NULL); return -1; } return 0; } int CelestronGPS::checkPower(ITextVectorProperty *tp) { if (PowerSw.s != IPS_OK) { if (!strcmp(tp->label, "")) IDMessage (mydev, "Cannot change property %s while the telescope is offline.", tp->name); else IDMessage (mydev, "Cannot change property %s while the telescope is offline.", tp->label); tp->s = IPS_IDLE; IDSetText(tp, NULL); return -1; } return 0; } void CelestronGPS::ISPoll() { double dx, dy; double currentRA, currentDEC; int status; switch (eqNum.s) { case IPS_IDLE: if (PowerSw.s != IPS_OK) break; currentRA = GetRA(); currentDEC = GetDec(); if ( fabs (currentRA - lastRA) > 0.01 || fabs (currentDEC - lastDEC) > 0.01) { eqNum.np[0].value = currentRA; eqNum.np[1].value = currentDEC; lastRA = currentRA; lastDEC = currentDEC; IDSetNumber (&eqNum, NULL); } break; case IPS_BUSY: currentRA = GetRA(); currentDEC = GetDec(); dx = targetRA - currentRA; dy = targetDEC - currentDEC; IDLog("targetRA is %f, currentRA is %f\n", (float) targetRA, (float) currentRA); IDLog("targetDEC is %f, currentDEC is %f\n****************************\n", (float) targetDEC, (float) currentDEC); eqNum.np[0].value = currentRA; eqNum.np[1].value = currentDEC; status = CheckCoords(targetRA, targetDEC); // Wait until acknowledged or within 3.6', change as desired. switch (status) { case 0: /* goto in progress */ IDSetNumber (&eqNum, NULL); break; case 1: /* goto complete within tolerance */ case 2: /* goto complete but outside tolerance */ currentRA = targetRA; currentDEC = targetDEC; /*apparentCoord( JD, (double) J2000, ¤tRA, ¤tDEC);*/ eqNum.np[0].value = currentRA; eqNum.np[1].value = currentDEC; eqNum.s = IPS_OK; if (currentSet == 0) { IUResetSwitches(&OnCoordSetSw); OnCoordSetSw.sp[0].s = ISS_ON; IDSetNumber (&eqNum, "Slew is complete"); } else { IUResetSwitches(&OnCoordSetSw); OnCoordSetSw.sp[1].s = ISS_ON; IDSetNumber (&eqNum, "Slew is complete. Tracking..."); } IDSetSwitch (&OnCoordSetSw, NULL); break; } break; case IPS_OK: if (PowerSw.s != IPS_OK) break; currentRA = GetRA(); currentDEC = GetDec(); if ( fabs (currentRA - lastRA) > 0.01 || fabs (currentDEC - lastDEC) > 0.01) { eqNum.np[0].value = currentRA; eqNum.np[1].value = currentDEC; lastRA = currentRA; lastDEC = currentDEC; IDSetNumber (&eqNum, NULL); } break; case IPS_ALERT: break; } switch (MovementSw.s) { case IPS_IDLE: break; case IPS_BUSY: currentRA = GetRA(); currentDEC = GetDec(); /*apparentCoord( JD, (double) J2000, ¤tRA, ¤tDEC);*/ eqNum.np[0].value = currentRA; eqNum.np[1].value = currentDEC; IDSetNumber (&eqNum, NULL); break; case IPS_OK: break; case IPS_ALERT: break; } } void CelestronGPS::getBasicData() { targetRA = GetRA(); targetDEC = GetDec(); eqNum.np[0].value = targetRA; eqNum.np[1].value = targetDEC; IDSetNumber(&eqNum, NULL); } void CelestronGPS::powerTelescope() { switch (PowerSw.sp[0].s) { case ISS_ON: if (ConnectTel(Port.tp[0].text) < 0) { PowerS[0].s = ISS_OFF; PowerS[1].s = ISS_ON; IDSetSwitch (&PowerSw, "Error connecting to port %s", Port.tp[0].text); return; } PowerSw.s = IPS_OK; IDSetSwitch (&PowerSw, "Telescope is online. Retrieving basic data..."); getBasicData(); break; case ISS_OFF: IDSetSwitch (&PowerSw, "Telescope is offline."); IDLog("Telescope is offline."); DisconnectTel(); break; } } void CelestronGPS::slewError(int slewCode) { eqNum.s = IPS_IDLE; switch (slewCode) { case 1: IDSetNumber (&eqNum, "Invalid newDec in SlewToCoords"); break; case 2: IDSetNumber (&eqNum, "RA count overflow in SlewToCoords"); break; case 3: IDSetNumber (&eqNum, "Dec count overflow in SlewToCoords"); break; case 4: IDSetNumber (&eqNum, "No acknowledgement from telescope after SlewToCoords"); break; default: IDSetNumber (&eqNum, "Unknown error"); break; } }