1# INDI Alignment Subsystem 2 3## Introduction 4The INDI alignment subsystem is a collection of classes that together provide support for telescope alignment using a database of stored sync points. Support is also provided for "Math Plugin Modules". One of these runtime loadable modules is active at any one time. The currently loaded module uses the sync point database to provide conversion functions to and from coordinates in the celestial reference frame and the telescope mount's local reference frame. 5 6During observing runs the sync point database is held in memory within the INDI device driver. It can also be loaded and saved to and from a file on the system the driver is running. The database can be edited via INDI properties (for details of the properties see the class MapPropertiesToInMemoryDatabase), by an API class for use in INDI drivers(InMemoryDatabase), or an API class for use in INDI clients(ClientAPIForAlignmentDatabase). 7 8The current math plugin module can be selected and initialised via INDI properties (for details of the properties see the class MathPluginManagement), by and API class for use in INDI drivers(MathPluginManagement), or by an API class for use in INDI clients(ClientAPIForMathPluginManagement). 9 10## Math Plugins 11The following math plugins are included in the first release. 12 13### Built in math plugin 14This is the default plugin which is used if no other plugin has been loaded. The plugin is normally initialised or re-initialised when the database has been loaded or when a new sync point has been added. The initialisation process scans the current database and builds a number of transformation matrices depending on how many sync points are present. Before the matrices are computed all celestial reference frame Right Ascension and Declination coordinates are transformed to horizontal Altitude Azimuth coordinates using the julian date stored in the sync point entry. This means that all transformations using the computed transformation matrices will be to and from a zenith aligned celestial reference frame and the telescope mounts local reference frame. This has the advantage of incorporating in the transformation any systematic alignment errors which occur due to the direction the mount is pointing relative to the zenith. Examples of such errors include those due to atmospheric refraction, and those due the effect of gravity on the telescope and mount. All transformation matrices are computed using the simple method proposed by [Toshimi Taki](http://www.geocities.jp/toshimi_taki/matrix/matrix_method_rev_e.pdf). This is quick and dirty but can result in matrices that are not true transforms. These will be reported as errors and an identity matrix will be substituted. 15 16#### No sync points present 17 18No action is taken. 19 20#### One sync point present 21 22A transformation matrix is computed using a hint to mounts approximate alignment supplied by the driver, this can either be ZENITH, NORTH_CELESTIAL_POLE or SOUTH_CELESTIAL_POLE. The hint is used to make a dummy second sync point entry. A dummy third entry is computed from the cross product of the first two. A single transformation matrix and its inverse is computed from these three points. 23 24#### Two sync points present 25 26A transformation matrix is computed using the two sync points and a dummy third sync point computed from the cross product of the first two. A single transformation matrix and its inverse is computed from these three points. 27 28#### Three sync points present 29 30A single transformation matrix and its inverse is computed from the three sync points. 31 32#### Four or more sync points present 33 34Two convex hulls are computed. One from the zenith aligned celestial reference frame sync point coordinates plus a dummy nadir, and the other from the mounts local reference frame sync point coordinates plus a dummy nadir. These convex hulls are made up of triangular facets. Forward and inverse transformation matrices are then computed for each corresponding pair of facets and stored alongside the facet in the relevant convex hull. 35 36#### Coordinate conversion 37 38If when the plugin is asked to translate a coordinate it only has a single conversion matrix (the one, two and three sync points case) this will be used. Otherwise (the four or more sync points case) a ray will shot from the origin of the requested source reference frame in the requested direction into the relevant convex hull and the transformation matrix from the facet it intersects will be used for the conversion. 39 40### SVD math plugin 41This plugin works in an identical manner to the built in math plugin. The only difference being that [Markley's Singular Value Decomposition algorithm](http://www.control.auc.dk/~tb/best/aug23-Bak-svdalg.pdf) is used to calculate the transformation matrices. This is a highly robust method and forms the basis of the pointing system used in many professional telescope installations. 42 43## Using the Alignment Subsystem from KStars 44The easiest way to use a telescope driver that supports the Alignment Subsystem is via an INDI aware client such as KStars. The following example uses the indi_SkywatcherAltAzMount driver and a Synscan 114GT mount. If you are using a different driver then the name of that driver will appear in KStars not "skywatcherAPIMount". 45 461. Firstly connect the mount to the computer that is to run the driver. I use a readily available PL2303 chip based serial to USB converter cable. 472. From the handset utility menu select PC direct mode. As it is the computer that will be driving the mount not the handset, you can enter whatever values you want to get through the handset initialisation process. 483. Start indiserver and the indi_SkyWatcherAPIMount driver. Using the following command in a terminal: 49 50 indiserver indi_skywatcherAPIMount 51 524. Start KStars and from the tools menu select "Devices" and then "Device Manager". 53![Tools menu](toolsmenu.png) 54 555. In the device manager window select the "Client" tab, and in the client tab select the host that indiserver is running on. Click on connect. 56![Device Manager](devicemanager.png) 57 586. An INDI Control Panel window should open with a skywatcherAPIMount tab. Select the "Options" subtab (I think I have invented this word!). Ensure that the port property is set to the correct serial device. My PL2303 usb cable always appears as /dev/ttyUSB0. 59![INDI Control Panel](controlpanel1.png) 60 617. Select the "Main Control" tab and click on connect. 62![INDI Control Panel](controlpanel2.png) 63 648. After a few seconds pause (whilst the driver determines what type of motor board is in use) a number of extra tabs should appear. One of these should be the "Site Management" tab. Select this and ensure that you have correct values entered for "Scope Location", you can safely ignore elevation at this time. 65![INDI Control Panel](controlpanel3.png) 66 679. At this point it is probably wise to save the configuration. Return to the "Options" tab and click on "Configuration" "Save". 68![INDI Control Panel](controlpanel4.png) 69 7010. Check that the "Alignment" tab is present and select it. Using the controls on this tab you can view and manipulate the entries in the alignment database, and select which math plugin you want to use. It probably best to ignore this tab for the time being and use KStars to create sync points to align your mount. 71![INDI Control Panel](controlpanel5.png) 72 7311. To create a sync point using KStars. First ensure your target star is visible in the KStars display. I usually do this using the "Pointing...Find Object" tool. 74![Find Object Tool](findobject.png) 75 7612. Once you have the target in the KStars window right click on it and then hover your mouse over the "Sync" option in the "skywatcherAltAzMount" sub-menu. Do not left click on the "Sync" option yet. N.B. The "Centre and Track" item in the main popup menu is nothing to do with your mount. It merely tells KStars to keep this object centered in the display window. 77![Object popup menu](objectpopup.png) 78 7913. Go back to your scope and centre the target in the eyepiece. Quickly get back to your computer and left click the mouse (be careful not to move it off the Sync menu item or you will have to right click to bring it up again). If you have been successful you should see the KStars telescope crosshairs displayed around the target. 80![Crosshair Display](crosshair.png). 81 8214. The Alignment Subsystem is now in "one star" alignment mode. You can try this out by right clicking on your target star or a nearby star and selecting "Track" from the "skywatcherAltAzMount" sub-menu. The further away the object you track is from the sync point star the less accurate the initial slew will be and the more quickly the tracked star will drift off centre. To correct this you need to add more sync points. 83 8415. To add another sync point you can select a new target star in KStars and use the slew command from the "skywatcherAltAzMount" sub-menu to approximately slew your scope onto the target. The procedure for adding the sync point is the same as before. With the default math plugin one achieves maximum accuracy for a particular triangular patch of sky when it is surrounded by three sync points. If more than three sync points are defined then more triangular patches will be added to the mesh. 85 8616. If would be very useful if you could collect information on how well the alignment mechanism holds a star centred, measured in degrees of drift per second. Please share these on the indi-devel list. 87 88## Adding Alignment Subsystem support to an INDI driver 89The Alignment Subsystem provides two API classes and a support function class for use in drivers. These are MapPropertiesToInMemoryDatabase, MathPluginManagement, and TelescopeDirectionVectorSupportFunctions. Driver developers can use these classes individually, however, the easiest way to use them is via the AlignmentSubsystemForDrivers class. To use this class simply ensure that is a parent of your driver class. 90 91 class ScopeSim : public INDI::Telescope, public INDI::GuiderInterface, public INDI::AlignmentSubsystem::AlignmentSubsystemForDrivers 92 93Somewhere in your drivers initProperties function add a call to AlignmentSubsystemForDrivers::InitAlignmentProperties. 94 95 bool ScopeSim::initProperties() 96 { 97 /* Make sure to init parent properties first */ 98 INDI::Telescope::initProperties(); 99 100 ... 101 102 /* Add debug controls so we may debug driver if necessary */ 103 addDebugControl(); 104 105 // Add alignment properties 106 InitAlignmentProperties(this); 107 108 return true; 109 } 110 111Hook the alignment subsystem into your drivers processing of properties by putting calls to AlignmentSubsystemForDrivers::ProcessNumberProperties, 112AlignmentSubsystemForDrivers::ProcessSwitchProperties, AlignmentSubsystemForDrivers::ProcessBLOBProperties AlignmentSubsystemForDrivers::ProcessTextProperties, in the relevant routines. 113 114 bool ScopeSim::ISNewNumber (const char *dev, const char *name, double values[], char *names[], int n) 115 { 116 // first check if it's for our device 117 118 if(strcmp(dev,getDeviceName())==0) 119 { 120 ... 121 122 // Process alignment properties 123 ProcessNumberProperties(this, name, values, names, n); 124 125 } 126 127 // if we didn't process it, continue up the chain, let somebody else 128 // give it a shot 129 return INDI::Telescope::ISNewNumber(dev,name,values,names,n); 130 } 131 132 bool ScopeSim::ISNewSwitch (const char *dev, const char *name, ISState *states, char *names[], int n) 133 { 134 if(strcmp(dev,getDeviceName())==0) 135 { 136 ... 137 // Process alignment properties 138 ProcessSwitchProperties(this, name, states, names, n); 139 } 140 141 // Nobody has claimed this, so, ignore it 142 return INDI::Telescope::ISNewSwitch(dev,name,states,names,n); 143 } 144 145 bool ScopeSim::ISNewBLOB (const char *dev, const char *name, int sizes[], int blobsizes[], char *blobs[], char *formats[], char *names[], int n) 146 { 147 if(strcmp(dev,getDeviceName())==0) 148 { 149 // Process alignment properties 150 ProcessBlobProperties(this, name, sizes, blobsizes, blobs, formats, names, n); 151 } 152 // Pass it up the chain 153 return INDI::Telescope::ISNewBLOB(dev, name, sizes, blobsizes, blobs, formats, names, n); 154 } 155 156 bool ScopeSim::ISNewText (const char *dev, const char *name, char *texts[], char *names[], int n) 157 { 158 if(strcmp(dev,getDeviceName())==0) 159 { 160 // Process alignment properties 161 ProcessTextProperties(this, name, texts, names, n); 162 } 163 // Pass it up the chain 164 return INDI::Telescope::ISNewText(dev, name, texts, names, n); 165 } 166 167Then make sure you override the INDI::Telescope::updateLocation function and call the AligmmentSubsystemForDrivers::UpdateLocation function. 168 169 bool ScopeSim::updateLocation(double latitude, double longitude, double elevation) 170 { 171 UpdateLocation(latitude, longitude, elevation); 172 return true; 173 } 174 175 176The next step is to add the handling of sync points into your drivers Sync function. 177 178 bool ScopeSim::Sync(double ra, double dec) 179 { 180 struct ln_hrz_posn AltAz; 181 AltAz.alt = double(CurrentEncoderMicrostepsDEC) / MICROSTEPS_PER_DEGREE; 182 AltAz.az = double(CurrentEncoderMicrostepsRA) / MICROSTEPS_PER_DEGREE; 183 184 AlignmentDatabaseEntry NewEntry; 185 NewEntry.ObservationJulianDate = ln_get_julian_from_sys(); 186 NewEntry.RightAscension = ra; 187 NewEntry.Declination = dec; 188 NewEntry.TelescopeDirection = TelescopeDirectionVectorFromAltitudeAzimuth(AltAz); 189 NewEntry.PrivateDataSize = 0; 190 191 if (!CheckForDuplicateSyncPoint(NewEntry)) 192 { 193 194 GetAlignmentDatabase().push_back(NewEntry); 195 196 // Tell the client about size change 197 UpdateSize(); 198 199 // Tell the math plugin to reinitialise 200 Initialise(this); 201 202 return true; 203 } 204 return false; 205 } 206 207The final step is to add coordinate conversion to ReadScopeStatus, TimerHit (for tracking), and Goto. 208 209 bool ScopeSim::ReadScopeStatus() 210 { 211 struct ln_hrz_posn AltAz; 212 AltAz.alt = double(CurrentEncoderMicrostepsDEC) / MICROSTEPS_PER_DEGREE; 213 AltAz.az = double(CurrentEncoderMicrostepsRA) / MICROSTEPS_PER_DEGREE; 214 TelescopeDirectionVector TDV = TelescopeDirectionVectorFromAltitudeAzimuth(AltAz); 215 216 double RightAscension, Declination; 217 if (!TransformTelescopeToCelestial( TDV, RightAscension, Declination)) 218 { 219 if (TraceThisTick) 220 DEBUG(DBG_SIMULATOR, "ReadScopeStatus - TransformTelescopeToCelestial failed"); 221 222 bool HavePosition = false; 223 ln_lnlat_posn Position; 224 if ((NULL != IUFindNumber(&LocationNP, "LAT")) && ( 0 != IUFindNumber(&LocationNP, "LAT")->value) 225 && (NULL != IUFindNumber(&LocationNP, "LONG")) && ( 0 != IUFindNumber(&LocationNP, "LONG")->value)) 226 { 227 // I assume that being on the equator and exactly on the prime meridian is unlikely 228 Position.lat = IUFindNumber(&LocationNP, "LAT")->value; 229 Position.lng = IUFindNumber(&LocationNP, "LONG")->value; 230 HavePosition = true; 231 } 232 struct ln_equ_posn EquatorialCoordinates; 233 if (HavePosition) 234 { 235 if (TraceThisTick) 236 DEBUG(DBG_SIMULATOR, "ReadScopeStatus - HavePosition true"); 237 TelescopeDirectionVector RotatedTDV(TDV); 238 switch (GetApproximateMountAlignment()) 239 { 240 case ZENITH: 241 if (TraceThisTick) 242 DEBUG(DBG_SIMULATOR, "ReadScopeStatus - ApproximateMountAlignment ZENITH"); 243 break; 244 245 case NORTH_CELESTIAL_POLE: 246 if (TraceThisTick) 247 DEBUG(DBG_SIMULATOR, "ReadScopeStatus - ApproximateMountAlignment NORTH_CELESTIAL_POLE"); 248 // Rotate the TDV coordinate system anticlockwise (positive) around the y axis by 90 minus 249 // the (positive)observatory latitude. The vector itself is rotated clockwise 250 RotatedTDV.RotateAroundY(90.0 - Position.lat); 251 AltitudeAzimuthFromTelescopeDirectionVector(RotatedTDV, AltAz); 252 break; 253 254 case SOUTH_CELESTIAL_POLE: 255 if (TraceThisTick) 256 DEBUG(DBG_SIMULATOR, "ReadScopeStatus - ApproximateMountAlignment SOUTH_CELESTIAL_POLE"); 257 // Rotate the TDV coordinate system clockwise (negative) around the y axis by 90 plus 258 // the (negative)observatory latitude. The vector itself is rotated anticlockwise 259 RotatedTDV.RotateAroundY(-90.0 - Position.lat); 260 AltitudeAzimuthFromTelescopeDirectionVector(RotatedTDV, AltAz); 261 break; 262 } 263 ln_get_equ_from_hrz(&AltAz, &Position, ln_get_julian_from_sys(), &EquatorialCoordinates); 264 } 265 else 266 { 267 if (TraceThisTick) 268 DEBUG(DBG_SIMULATOR, "ReadScopeStatus - HavePosition false"); 269 270 // The best I can do is just do a direct conversion to RA/DEC 271 EquatorialCoordinatesFromTelescopeDirectionVector(TDV, EquatorialCoordinates); 272 } 273 // libnova works in decimal degrees 274 RightAscension = EquatorialCoordinates.ra * 24.0 / 360.0; 275 Declination = EquatorialCoordinates.dec; 276 } 277 278 if (TraceThisTick) 279 DEBUGF(DBG_SIMULATOR, "ReadScopeStatus - RA %lf hours DEC %lf degrees", RightAscension, Declination); 280 281 NewRaDec(RightAscension, Declination); 282 283 return true; 284 } 285 286 bool ScopeSim::Sync(double ra, double dec) 287 { 288 struct ln_hrz_posn AltAz; 289 AltAz.alt = double(CurrentEncoderMicrostepsDEC) / MICROSTEPS_PER_DEGREE; 290 AltAz.az = double(CurrentEncoderMicrostepsRA) / MICROSTEPS_PER_DEGREE; 291 292 AlignmentDatabaseEntry NewEntry; 293 NewEntry.ObservationJulianDate = ln_get_julian_from_sys(); 294 NewEntry.RightAscension = ra; 295 NewEntry.Declination = dec; 296 NewEntry.TelescopeDirection = TelescopeDirectionVectorFromAltitudeAzimuth(AltAz); 297 NewEntry.PrivateDataSize = 0; 298 299 if (!CheckForDuplicateSyncPoint(NewEntry)) 300 { 301 302 GetAlignmentDatabase().push_back(NewEntry); 303 304 // Tell the client about size change 305 UpdateSize(); 306 307 // Tell the math plugin to reinitialise 308 Initialise(this); 309 310 return true; 311 } 312 return false; 313 } 314 315 void ScopeSim::TimerHit() 316 { 317 // Simulate mount movement 318 319 ... 320 321 INDI::Telescope::TimerHit(); // This will call ReadScopeStatus 322 323 // OK I have updated the celestial reference frame RA/DEC in ReadScopeStatus 324 // Now handle the tracking state 325 switch(TrackState) 326 { 327 case SCOPE_SLEWING: 328 if ((STOPPED == AxisStatusRA) && (STOPPED == AxisStatusDEC)) 329 { 330 if (ISS_ON == IUFindSwitch(&CoordSP,"TRACK")->s) 331 { 332 // Goto has finished start tracking 333 DEBUG(DBG_SIMULATOR, "TimerHit - Goto finished start tracking"); 334 TrackState = SCOPE_TRACKING; 335 // Fall through to tracking case 336 } 337 else 338 { 339 TrackState = SCOPE_IDLE; 340 break; 341 } 342 } 343 else 344 break; 345 346 case SCOPE_TRACKING: 347 { 348 // Continue or start tracking 349 // Calculate where the mount needs to be in POLLMS time 350 // POLLMS is hardcoded to be one second 351 double JulianOffset = 1.0 / (24.0 * 60 * 60); // TODO may need to make this longer to get a meaningful result 352 TelescopeDirectionVector TDV; 353 ln_hrz_posn AltAz; 354 if (TransformCelestialToTelescope(CurrentTrackingTarget.ra, CurrentTrackingTarget.dec, 355 JulianOffset, TDV)) 356 AltitudeAzimuthFromTelescopeDirectionVector(TDV, AltAz); 357 else 358 { 359 // Try a conversion with the stored observatory position if any 360 bool HavePosition = false; 361 ln_lnlat_posn Position; 362 if ((NULL != IUFindNumber(&LocationNP, "LAT")) && ( 0 != IUFindNumber(&LocationNP, "LAT")->value) 363 && (NULL != IUFindNumber(&LocationNP, "LONG")) && ( 0 != IUFindNumber(&LocationNP, "LONG")->value)) 364 { 365 // I assume that being on the equator and exactly on the prime meridian is unlikely 366 Position.lat = IUFindNumber(&LocationNP, "LAT")->value; 367 Position.lng = IUFindNumber(&LocationNP, "LONG")->value; 368 HavePosition = true; 369 } 370 struct ln_equ_posn EquatorialCoordinates; 371 // libnova works in decimal degrees 372 EquatorialCoordinates.ra = CurrentTrackingTarget.ra * 360.0 / 24.0; 373 EquatorialCoordinates.dec = CurrentTrackingTarget.dec; 374 if (HavePosition) 375 ln_get_hrz_from_equ(&EquatorialCoordinates, &Position, 376 ln_get_julian_from_sys() + JulianOffset, &AltAz); 377 else 378 { 379 // No sense in tracking in this case 380 TrackState = SCOPE_IDLE; 381 break; 382 } 383 } 384 385 // My altitude encoder runs -90 to +90 386 if ((AltAz.alt > 90.0) || (AltAz.alt < -90.0)) 387 { 388 DEBUG(DBG_SIMULATOR, "TimerHit tracking - Altitude out of range"); 389 // This should not happen 390 return; 391 } 392 393 // My polar encoder runs 0 to +360 394 if ((AltAz.az > 360.0) || (AltAz.az < -360.0)) 395 { 396 DEBUG(DBG_SIMULATOR, "TimerHit tracking - Azimuth out of range"); 397 // This should not happen 398 return; 399 } 400 401 if (AltAz.az < 0.0) 402 { 403 DEBUG(DBG_SIMULATOR, "TimerHit tracking - Azimuth negative"); 404 AltAz.az = 360.0 + AltAz.az; 405 } 406 407 long AltitudeOffsetMicrosteps = int(AltAz.alt * MICROSTEPS_PER_DEGREE - CurrentEncoderMicrostepsDEC); 408 long AzimuthOffsetMicrosteps = int(AltAz.az * MICROSTEPS_PER_DEGREE - CurrentEncoderMicrostepsRA); 409 410 DEBUGF(DBG_SIMULATOR, "TimerHit - Tracking AltitudeOffsetMicrosteps %d AzimuthOffsetMicrosteps %d", 411 AltitudeOffsetMicrosteps, AzimuthOffsetMicrosteps); 412 413 if (0 != AzimuthOffsetMicrosteps) 414 { 415 // Calculate the slewing rates needed to reach that position 416 // at the correct time. This is simple as interval is one second 417 if (AzimuthOffsetMicrosteps > 0) 418 { 419 if (AzimuthOffsetMicrosteps < MICROSTEPS_PER_REVOLUTION / 2.0) 420 { 421 // Foward 422 AxisDirectionRA = FORWARD; 423 AxisSlewRateRA = AzimuthOffsetMicrosteps; 424 } 425 else 426 { 427 // Reverse 428 AxisDirectionRA = REVERSE; 429 AxisSlewRateRA = MICROSTEPS_PER_REVOLUTION - AzimuthOffsetMicrosteps; 430 } 431 } 432 else 433 { 434 AzimuthOffsetMicrosteps = abs(AzimuthOffsetMicrosteps); 435 if (AzimuthOffsetMicrosteps < MICROSTEPS_PER_REVOLUTION / 2.0) 436 { 437 // Foward 438 AxisDirectionRA = REVERSE; 439 AxisSlewRateRA = AzimuthOffsetMicrosteps; 440 } 441 else 442 { 443 // Reverse 444 AxisDirectionRA = FORWARD; 445 AxisSlewRateRA = MICROSTEPS_PER_REVOLUTION - AzimuthOffsetMicrosteps; 446 } 447 } 448 AxisSlewRateRA = abs(AzimuthOffsetMicrosteps); 449 AxisDirectionRA = AzimuthOffsetMicrosteps > 0 ? FORWARD : REVERSE; // !!!! BEWARE INERTIA FREE MOUNT 450 AxisStatusRA = SLEWING; 451 DEBUGF(DBG_SIMULATOR, "TimerHit - Tracking AxisSlewRateRA %lf AxisDirectionRA %d", 452 AxisSlewRateRA, AxisDirectionRA); 453 } 454 else 455 { 456 // Nothing to do - stop the axis 457 AxisStatusRA = STOPPED; // !!!! BEWARE INERTIA FREE MOUNT 458 DEBUG(DBG_SIMULATOR, "TimerHit - Tracking nothing to do stopping RA axis"); 459 } 460 461 if (0 != AltitudeOffsetMicrosteps) 462 { 463 // Calculate the slewing rates needed to reach that position 464 // at the correct time. 465 AxisSlewRateDEC = abs(AltitudeOffsetMicrosteps); 466 AxisDirectionDEC = AltitudeOffsetMicrosteps > 0 ? FORWARD : REVERSE; // !!!! BEWARE INERTIA FREE MOUNT 467 AxisStatusDEC = SLEWING; 468 DEBUGF(DBG_SIMULATOR, "TimerHit - Tracking AxisSlewRateDEC %lf AxisDirectionDEC %d", 469 AxisSlewRateDEC, AxisDirectionDEC); 470 } 471 else 472 { 473 // Nothing to do - stop the axis 474 AxisStatusDEC = STOPPED; // !!!! BEWARE INERTIA FREE MOUNT 475 DEBUG(DBG_SIMULATOR, "TimerHit - Tracking nothing to do stopping DEC axis"); 476 } 477 478 break; 479 } 480 481 default: 482 break; 483 } 484 485 bool ScopeSim::Goto(double ra,double dec) 486 { 487 488 DEBUGF(DBG_SIMULATOR, "Goto - Celestial reference frame target right ascension %lf(%lf) declination %lf", ra * 360.0 / 24.0, ra, dec); 489 490 if (ISS_ON == IUFindSwitch(&CoordSP,"TRACK")->s) 491 { 492 char RAStr[32], DecStr[32]; 493 fs_sexa(RAStr, ra, 2, 3600); 494 fs_sexa(DecStr, dec, 2, 3600); 495 CurrentTrackingTarget.ra = ra; 496 CurrentTrackingTarget.dec = dec; 497 DEBUG(DBG_SIMULATOR, "Goto - tracking requested"); 498 } 499 500 // Call the alignment subsystem to translate the celestial reference frame coordinate 501 // into a telescope reference frame coordinate 502 TelescopeDirectionVector TDV; 503 ln_hrz_posn AltAz; 504 if (TransformCelestialToTelescope(ra, dec, 0.0, TDV)) 505 { 506 // The alignment subsystem has successfully transformed my coordinate 507 AltitudeAzimuthFromTelescopeDirectionVector(TDV, AltAz); 508 } 509 else 510 { 511 // The alignment subsystem cannot transform the coordinate. 512 // Try some simple rotations using the stored observatory position if any 513 bool HavePosition = false; 514 ln_lnlat_posn Position; 515 if ((NULL != IUFindNumber(&LocationNP, "LAT")) && ( 0 != IUFindNumber(&LocationNP, "LAT")->value) 516 && (NULL != IUFindNumber(&LocationNP, "LONG")) && ( 0 != IUFindNumber(&LocationNP, "LONG")->value)) 517 { 518 // I assume that being on the equator and exactly on the prime meridian is unlikely 519 Position.lat = IUFindNumber(&LocationNP, "LAT")->value; 520 Position.lng = IUFindNumber(&LocationNP, "LONG")->value; 521 HavePosition = true; 522 } 523 struct ln_equ_posn EquatorialCoordinates; 524 // libnova works in decimal degrees 525 EquatorialCoordinates.ra = ra * 360.0 / 24.0; 526 EquatorialCoordinates.dec = dec; 527 if (HavePosition) 528 { 529 ln_get_hrz_from_equ(&EquatorialCoordinates, &Position, ln_get_julian_from_sys(), &AltAz); 530 TDV = TelescopeDirectionVectorFromAltitudeAzimuth(AltAz); 531 switch (GetApproximateMountAlignment()) 532 { 533 case ZENITH: 534 break; 535 536 case NORTH_CELESTIAL_POLE: 537 // Rotate the TDV coordinate system clockwise (negative) around the y axis by 90 minus 538 // the (positive)observatory latitude. The vector itself is rotated anticlockwise 539 TDV.RotateAroundY(Position.lat - 90.0); 540 break; 541 542 case SOUTH_CELESTIAL_POLE: 543 // Rotate the TDV coordinate system anticlockwise (positive) around the y axis by 90 plus 544 // the (negative)observatory latitude. The vector itself is rotated clockwise 545 TDV.RotateAroundY(Position.lat + 90.0); 546 break; 547 } 548 AltitudeAzimuthFromTelescopeDirectionVector(TDV, AltAz); 549 } 550 else 551 { 552 // The best I can do is just do a direct conversion to Alt/Az 553 TDV = TelescopeDirectionVectorFromEquatorialCoordinates(EquatorialCoordinates); 554 AltitudeAzimuthFromTelescopeDirectionVector(TDV, AltAz); 555 } 556 } 557 558 // My altitude encoder runs -90 to +90 559 if ((AltAz.alt > 90.0) || (AltAz.alt < -90.0)) 560 { 561 DEBUG(DBG_SIMULATOR, "Goto - Altitude out of range"); 562 // This should not happen 563 return false; 564 } 565 566 // My polar encoder runs 0 to +360 567 if ((AltAz.az > 360.0) || (AltAz.az < -360.0)) 568 { 569 DEBUG(DBG_SIMULATOR, "Goto - Azimuth out of range"); 570 // This should not happen 571 return false; 572 } 573 574 if (AltAz.az < 0.0) 575 { 576 DEBUG(DBG_SIMULATOR, "Goto - Azimuth negative"); 577 AltAz.az = 360.0 + AltAz.az; 578 } 579 580 DEBUGF(DBG_SIMULATOR, "Goto - Scope reference frame target altitude %lf azimuth %lf", AltAz.alt, AltAz.az); 581 582 GotoTargetMicrostepsDEC = int(AltAz.alt * MICROSTEPS_PER_DEGREE); 583 if (GotoTargetMicrostepsDEC == CurrentEncoderMicrostepsDEC) 584 AxisStatusDEC = STOPPED; 585 else 586 { 587 if (GotoTargetMicrostepsDEC > CurrentEncoderMicrostepsDEC) 588 AxisDirectionDEC = FORWARD; 589 else 590 AxisDirectionDEC = REVERSE; 591 AxisStatusDEC = SLEWING_TO; 592 } 593 GotoTargetMicrostepsRA = int(AltAz.az * MICROSTEPS_PER_DEGREE); 594 if (GotoTargetMicrostepsRA == CurrentEncoderMicrostepsRA) 595 AxisStatusRA = STOPPED; 596 else 597 { 598 if (GotoTargetMicrostepsRA > CurrentEncoderMicrostepsRA) 599 AxisDirectionRA = (GotoTargetMicrostepsRA - CurrentEncoderMicrostepsRA) < MICROSTEPS_PER_REVOLUTION / 2.0 ? FORWARD : REVERSE; 600 else 601 AxisDirectionRA = (CurrentEncoderMicrostepsRA - GotoTargetMicrostepsRA) < MICROSTEPS_PER_REVOLUTION / 2.0 ? REVERSE : FORWARD; 602 AxisStatusRA = SLEWING_TO; 603 } 604 605 TrackState = SCOPE_SLEWING; 606 607 EqNP.s = IPS_BUSY; 608 609 return true; 610 } 611 612 613## Developing Alignment Subsystem clients 614The Alignment Subsystem provides two API classes for use in clients. These are ClientAPIForAlignmentDatabase and ClientAPIForMathPluginManagement. Client developers can use these classes individually, however, the easiest way to use them is via the AlignmentSubsystemForClients class. To use this class simply ensure that is a parent of your client class. 615 616 class LoaderClient : public INDI::BaseClient, INDI::AlignmentSubsystem::AlignmentSubsystemForClients 617 618Somewhere in the initialisation of your client make a call to the Initalise method of the AlignmentSubsystemForClients class for example: 619 620 void LoaderClient::Initialise(int argc, char* argv[]) 621 { 622 std::string HostName("localhost"); 623 int Port = 7624; 624 625 if (argc > 1) 626 DeviceName = argv[1]; 627 if (argc > 2) 628 HostName = argv[2]; 629 if (argc > 3) 630 { 631 std::istringstream Parameter(argv[3]); 632 Parameter >> Port; 633 } 634 635 AlignmentSubsystemForClients::Initialise(DeviceName.c_str(), this); 636 637 setServer(HostName.c_str(), Port); 638 639 watchDevice(DeviceName.c_str()); 640 641 connectServer(); 642 643 setBLOBMode(B_ALSO, DeviceName.c_str(), NULL); 644 } 645 646To hook the Alignment Subsystem into the clients property handling you must ensure that the following virtual functions are overriden. 647 648 virtual void newBLOB(IBLOB *bp); 649 virtual void newDevice(INDI::BaseDevice *dp); 650 virtual void newNumber(INumberVectorProperty *nvp); 651 virtual void newProperty(INDI::Property *property); 652 virtual void newSwitch(ISwitchVectorProperty *svp); 653 654A call to the Alignment Subsystems property handling functions must then be placed in the body of these functions. 655 656 void LoaderClient::newBLOB(IBLOB *bp) 657 { 658 ProcessNewBLOB(bp); 659 } 660 661 void LoaderClient::newDevice(INDI::BaseDevice *dp) 662 { 663 ProcessNewDevice(dp); 664 } 665 666 void LoaderClient::newNumber(INumberVectorProperty *nvp) 667 { 668 ProcessNewNumber(nvp); 669 } 670 671 void LoaderClient::newProperty(INDI::Property *property) 672 { 673 ProcessNewProperty(property); 674 } 675 676 void LoaderClient::newSwitch(ISwitchVectorProperty *svp) 677 { 678 ProcessNewSwitch(svp); 679 } 680 681See the documentation for the ClientAPIForAlignmentDatabase and ClientAPIForMathPluginManagement to see what other functionality is available. 682