1 /*
2 * ascom.cpp
3 * PHD Guiding
4 *
5 * Created by Craig Stark.
6 * Copyright (c) 2006-2010 Craig Stark.
7 * All rights reserved.
8 *
9 * Modified by Bret McKee
10 * Copyright (c) 2012-2013 Bret McKee
11 * All rights reserved.
12 *
13 * This source code is distributed under the following "BSD" license
14 * Redistribution and use in source and binary forms, with or without
15 * modification, are permitted provided that the following conditions are met:
16 * Redistributions of source code must retain the above copyright notice,
17 * this list of conditions and the following disclaimer.
18 * Redistributions in binary form must reproduce the above copyright notice,
19 * this list of conditions and the following disclaimer in the
20 * documentation and/or other materials provided with the distribution.
21 * Neither the name of Craig Stark, Stark Labs nor the names of its
22 * contributors may be used to endorse or promote products derived from
23 * this software without specific prior written permission.
24 *
25 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
26 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
27 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
28 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
29 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
30 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
31 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
32 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
33 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
34 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
35 * POSSIBILITY OF SUCH DAMAGE.
36 *
37 */
38
39 #include "phd.h"
40
41 #ifdef GUIDE_ASCOM
42
43 #include "comdispatch.h"
44
45 #include <wx/msw/ole/oleutils.h>
46 #include <comdef.h>
47 #include <objbase.h>
48 #include <ole2ver.h>
49 #include <initguid.h>
50 #include <wx/textfile.h>
51 #include <wx/stdpaths.h>
52 #include <wx/stopwatch.h>
53
ScopeASCOM(const wxString & choice)54 ScopeASCOM::ScopeASCOM(const wxString& choice)
55 {
56 m_choice = choice;
57 m_canPulseGuide = false; // will get updated in Connect()
58
59 dispid_connected = DISPID_UNKNOWN;
60 dispid_ispulseguiding = DISPID_UNKNOWN;
61 dispid_isslewing = DISPID_UNKNOWN;
62 dispid_pulseguide = DISPID_UNKNOWN;
63 dispid_declination = DISPID_UNKNOWN;
64 dispid_rightascension = DISPID_UNKNOWN;
65 dispid_siderealtime = DISPID_UNKNOWN;
66 dispid_sitelatitude = DISPID_UNKNOWN;
67 dispid_sitelongitude = DISPID_UNKNOWN;
68 dispid_slewtocoordinates = DISPID_UNKNOWN;
69 dispid_raguiderate = DISPID_UNKNOWN;
70 dispid_decguiderate = DISPID_UNKNOWN;
71 dispid_sideofpier = DISPID_UNKNOWN;
72 dispid_abortslew = DISPID_UNKNOWN;
73 }
74
~ScopeASCOM()75 ScopeASCOM::~ScopeASCOM()
76 {
77 }
78
displayName(const wxString & ascomName)79 static wxString displayName(const wxString& ascomName)
80 {
81 if (ascomName.Find(_T("ASCOM")) != wxNOT_FOUND)
82 return ascomName;
83 return ascomName + _T(" (ASCOM)");
84 }
85
86 // map descriptive name to progid
87 static std::map<wxString, wxString> s_progid;
88
EnumAscomScopes()89 wxArrayString ScopeASCOM::EnumAscomScopes()
90 {
91 wxArrayString list;
92
93 try
94 {
95 DispatchObj profile;
96 if (!profile.Create(L"ASCOM.Utilities.Profile"))
97 throw ERROR_INFO("ASCOM Scope: could not instantiate ASCOM profile class ASCOM.Utilities.Profile. Is ASCOM installed?");
98
99 Variant res;
100 if (!profile.InvokeMethod(&res, L"RegisteredDevices", L"Telescope"))
101 throw ERROR_INFO("ASCOM Scope: could not query registered telescope devices: " + ExcepMsg(profile.Excep()));
102
103 DispatchClass ilist_class;
104 DispatchObj ilist(res.pdispVal, &ilist_class);
105
106 Variant vcnt;
107 if (!ilist.GetProp(&vcnt, L"Count"))
108 throw ERROR_INFO("ASCOM Scope: could not query registered telescopes: " + ExcepMsg(ilist.Excep()));
109
110 unsigned int const count = vcnt.intVal;
111 DispatchClass kvpair_class;
112
113 for (unsigned int i = 0; i < count; i++)
114 {
115 Variant kvpres;
116 if (ilist.GetProp(&kvpres, L"Item", i))
117 {
118 DispatchObj kvpair(kvpres.pdispVal, &kvpair_class);
119 Variant vkey, vval;
120 if (kvpair.GetProp(&vkey, L"Key") && kvpair.GetProp(&vval, L"Value"))
121 {
122 wxString ascomName = vval.bstrVal;
123 wxString displName = displayName(ascomName);
124 wxString progid = vkey.bstrVal;
125 s_progid[displName] = progid;
126 list.Add(displName);
127 }
128 }
129 }
130 }
131 catch (const wxString& msg)
132 {
133 POSSIBLY_UNUSED(msg);
134 }
135
136 return list;
137 }
138
Create(DispatchObj & obj)139 bool ScopeASCOM::Create(DispatchObj& obj)
140 {
141 try
142 {
143 // is there already an instance registered in the global interface table?
144 IDispatch *idisp = m_gitEntry.Get();
145 if (idisp)
146 {
147 obj.Attach(idisp, NULL);
148 return true;
149 }
150
151 Debug.Write(wxString::Format("Create ASCOM Scope: choice '%s' progid %s\n", m_choice, s_progid[m_choice]));
152
153 wxBasicString progid(s_progid[m_choice]);
154
155 if (!obj.Create(progid))
156 {
157 throw ERROR_INFO("Could not establish instance of " + wxString(progid));
158 }
159
160 Debug.Write(wxString::Format("pScopeDriver = 0x%p\n", obj.IDisp()));
161
162 // store the driver interface in the global table for access by other threads
163 m_gitEntry.Register(obj);
164 }
165 catch (const wxString& msg)
166 {
167 Debug.Write(msg + "\n");
168 return false;
169 }
170
171 return true;
172 }
173
HasSetupDialog() const174 bool ScopeASCOM::HasSetupDialog() const
175 {
176 return true;
177 }
178
SetupDialog()179 void ScopeASCOM::SetupDialog()
180 {
181 DispatchObj scope;
182 if (Create(scope))
183 {
184 Variant res;
185 if (!scope.InvokeMethod(&res, L"SetupDialog"))
186 {
187 wxString msg(scope.Excep().bstrSource);
188 if (scope.Excep().bstrDescription)
189 msg += ":\n" + wxString(scope.Excep().bstrDescription);
190 wxMessageBox(msg, _("Error"), wxOK | wxICON_ERROR);
191 }
192 }
193 // destroy the COM object now as this reduces the likelhood of getting into a
194 // state where the user has killed the ASCOM local server instance and PHD2 is
195 // holding a reference to the defunct driver instance in the global interface
196 // table
197 m_gitEntry.Unregister();
198 }
199
Connect()200 bool ScopeASCOM::Connect()
201 {
202 bool bError = false;
203
204 try
205 {
206 Debug.Write("ASCOM Scope: Connecting\n");
207
208 if (IsConnected())
209 {
210 wxMessageBox("Scope already connected",_("Error"));
211 throw ERROR_INFO("ASCOM Scope: Connected - Already Connected");
212 }
213
214 DispatchObj pScopeDriver;
215
216 if (!Create(pScopeDriver))
217 {
218 wxMessageBox(_T("Could not establish instance of ") + m_choice, _("Error"), wxOK | wxICON_ERROR);
219 throw ERROR_INFO("ASCOM Scope: Could not establish ASCOM Scope instance");
220 }
221
222 // --- get the dispatch IDs we need ...
223
224 // ... get the dispatch ID for the Connected property ...
225 if (!pScopeDriver.GetDispatchId(&dispid_connected, L"Connected"))
226 {
227 wxMessageBox(_T("ASCOM driver problem -- cannot connect"),_("Error"), wxOK | wxICON_ERROR);
228 throw ERROR_INFO("ASCOM Scope: Could not get the dispatch id for the Connected property");
229 }
230
231 // ... get the dispatch ID for the "IsPulseGuiding" property ....
232 m_canCheckPulseGuiding = true;
233 if (!pScopeDriver.GetDispatchId(&dispid_ispulseguiding, L"IsPulseGuiding"))
234 {
235 m_canCheckPulseGuiding = false;
236 Debug.Write("cannot get dispid_ispulseguiding\n");
237 // don't fail if we can't get the status on this - can live without it as it's really a safety net for us
238 }
239
240 // ... get the dispatch ID for the "Slewing" property ....
241 if (!pScopeDriver.GetDispatchId(&dispid_isslewing, L"Slewing"))
242 {
243 wxMessageBox(_T("ASCOM driver missing the Slewing property"),_("Error"), wxOK | wxICON_ERROR);
244 throw ERROR_INFO("ASCOM Scope: Could not get the dispatch id for the Slewing property");
245 }
246
247 // ... get the dispatch ID for the "PulseGuide" property ....
248 if (!pScopeDriver.GetDispatchId(&dispid_pulseguide, L"PulseGuide"))
249 {
250 wxMessageBox(_T("ASCOM driver missing the PulseGuide property"),_("Error"), wxOK | wxICON_ERROR);
251 throw ERROR_INFO("ASCOM Scope: Could not get the dispatch id for the PulseGuide property");
252 }
253
254 // ... get the dispatch ID for the "Declination" property ....
255 m_canGetCoordinates = true;
256 if (!pScopeDriver.GetDispatchId(&dispid_declination, L"Declination"))
257 {
258 m_canGetCoordinates = false;
259 Debug.Write("cannot get dispid_declination\n");
260 }
261 else if (!pScopeDriver.GetDispatchId(&dispid_rightascension, L"RightAscension"))
262 {
263 Debug.Write("cannot get dispid_rightascension\n");
264 m_canGetCoordinates = false;
265 }
266 else if (!pScopeDriver.GetDispatchId(&dispid_siderealtime, L"SiderealTime"))
267 {
268 Debug.Write("cannot get dispid_siderealtime\n");
269 m_canGetCoordinates = false;
270 }
271
272 if (!pScopeDriver.GetDispatchId(&dispid_sitelatitude, L"SiteLatitude"))
273 {
274 Debug.Write("cannot get dispid_sitelatitude\n");
275 }
276 if (!pScopeDriver.GetDispatchId(&dispid_sitelongitude, L"SiteLongitude"))
277 {
278 Debug.Write("cannot get dispid_sitelongitude\n");
279 }
280
281 m_canSlew = true;
282 if (!pScopeDriver.GetDispatchId(&dispid_slewtocoordinates, L"SlewToCoordinates"))
283 {
284 m_canSlew = false;
285 Debug.Write("cannot get dispid_slewtocoordinates\n");
286 }
287
288 // ... get the dispatch IDs for the two guide rate properties - if we can't get them, no sweat, doesn't matter for actual guiding
289 // Used for things like calibration sanity checking, backlash clearing, etc.
290 m_canGetGuideRates = true; // Likely case, required for any ASCOM driver at V2 or later
291 if (!pScopeDriver.GetDispatchId(&dispid_decguiderate, L"GuideRateDeclination"))
292 {
293 Debug.Write("cannot get dispid_decguiderate\n");
294 m_canGetGuideRates = false;
295 // don't throw if we can't get this one
296 }
297 else if (!pScopeDriver.GetDispatchId(&dispid_raguiderate, L"GuideRateRightAscension"))
298 {
299 Debug.Write("cannot get dispid_raguiderate\n");
300 m_canGetGuideRates = false;
301 // don't throw if we can't get this one
302 }
303
304 if (!pScopeDriver.GetDispatchId(&dispid_sideofpier, L"SideOfPier"))
305 {
306 Debug.Write("cannot get dispid_sideofpier\n");
307 dispid_sideofpier = DISPID_UNKNOWN;
308 }
309
310 if (!pScopeDriver.GetDispatchId(&dispid_abortslew, L"AbortSlew"))
311 {
312 Debug.Write("cannot get dispid_abortslew\n");
313 dispid_abortslew = DISPID_UNKNOWN;
314 }
315
316 struct ConnectInBg : public ConnectMountInBg
317 {
318 ScopeASCOM *sa;
319 ConnectInBg(ScopeASCOM *sa_) : sa(sa_) { }
320 bool Entry()
321 {
322 GITObjRef scope(sa->m_gitEntry);
323 // ... set the Connected property to true....
324 if (!scope.PutProp(sa->dispid_connected, true))
325 {
326 SetErrorMsg(ExcepMsg(scope.Excep()));
327 return true;
328 }
329 return false;
330 }
331 };
332 ConnectInBg bg(this);
333
334 // set the Connected property to true in a background thread
335 if (bg.Run())
336 {
337 wxMessageBox(_T("ASCOM driver problem during connection: ") + bg.GetErrorMsg(),
338 _("Error"), wxOK | wxICON_ERROR);
339 throw ERROR_INFO("ASCOM Scope: Could not set Connected property to true");
340 }
341
342 // get the scope name
343 Variant vRes;
344 if (!pScopeDriver.GetProp(&vRes, L"Name"))
345 {
346 wxMessageBox(_T("ASCOM driver problem getting Name property"), _("Error"), wxOK | wxICON_ERROR);
347 throw ERROR_INFO("ASCOM Scope: Could not get the scope name: " + ExcepMsg(pScopeDriver.Excep()));
348 }
349
350 m_Name = displayName(vRes.bstrVal);
351
352 Debug.Write(wxString::Format("Scope reports its name as %s\n", m_Name));
353
354 m_abortSlewWhenGuidingStuck = false;
355
356 if (m_Name.Find(_T("Gemini Telescope .NET")) != wxNOT_FOUND)
357 {
358 // Gemini2 firmware (2013 Oct 13 version, perhaps others) has been found to contain a
359 // bug where a pulse guide command can fail to complete, with the Guiding property
360 // returning true forever. The firmware developer suggests that PHD2 should issue an
361 // AbortSlew when this condition is detected.
362 Debug.Write("ASCOM scope: enabling stuck guide pulse workaround\n");
363 m_abortSlewWhenGuidingStuck = true;
364 }
365
366 m_checkForSyncPulseGuide = false;
367
368 if (m_Name.Find(_T("AstroPhysicsV2")) != wxNOT_FOUND)
369 {
370 // The Astro-Physics ASCOM driver can hang intermittently if its
371 // synchronous pulseguide option is enabled. We will attempt to
372 // detect synchronous guide pulses and display an alert if sync
373 // pulses are enabled.
374 Debug.Write("ASCOM scope: enabling sync pulse guide check\n");
375 m_checkForSyncPulseGuide = true;
376 }
377
378 // see if we can pulse guide
379 m_canPulseGuide = true;
380 if (!pScopeDriver.GetProp(&vRes, L"CanPulseGuide") || vRes.boolVal != VARIANT_TRUE)
381 {
382 Debug.Write("Connecting to ASCOM scope that does not support PulseGuide\n");
383 m_canPulseGuide = false;
384 }
385
386 // see if scope can slew
387 m_canSlewAsync = false;
388 if (m_canSlew)
389 {
390 if (!pScopeDriver.GetProp(&vRes, L"CanSlew"))
391 {
392 Debug.Write(wxString::Format("ASCOM scope got error invoking CanSlew: %s\n", ExcepMsg(pScopeDriver.Excep())));
393 m_canSlew = false;
394 }
395 else if (vRes.boolVal != VARIANT_TRUE)
396 {
397 Debug.Write("ASCOM scope reports CanSlew = false\n");
398 m_canSlew = false;
399 }
400
401 m_canSlewAsync = pScopeDriver.GetProp(&vRes, L"CanSlewAsync") && vRes.boolVal == VARIANT_TRUE;
402 Debug.Write(wxString::Format("ASCOM scope CanSlewAsync is %s\n", m_canSlewAsync ? "true" : "false"));
403 }
404
405 Debug.Write(wxString::Format("%s connected\n", Name()));
406
407 Scope::Connect();
408
409 Debug.Write("ASCOM Scope: Connect success\n");
410 }
411 catch (const wxString& Msg)
412 {
413 POSSIBLY_UNUSED(Msg);
414 bError = true;
415 }
416
417 return bError;
418 }
419
Disconnect()420 bool ScopeASCOM::Disconnect()
421 {
422 bool bError = false;
423
424 try
425 {
426 Debug.Write("ASCOM Scope: Disconnecting\n");
427
428 if (!IsConnected())
429 {
430 throw ERROR_INFO("ASCOM Scope: attempt to disconnect when not connected");
431 }
432
433 // Setting the Connected property to false will cause the scope to be disconnected for all
434 // ASCOM clients that are connected to the scope, and we do not want this!
435 bool const disconnectAscomDriver = false;
436 if (disconnectAscomDriver)
437 {
438 GITObjRef scope(m_gitEntry);
439
440 // Set the Connected property to false
441 if (!scope.PutProp(dispid_connected, false))
442 {
443 pFrame->Alert(_("ASCOM driver problem during disconnect, check the debug log for more information"));
444 throw ERROR_INFO("ASCOM Scope: Could not set Connected property to false: " + ExcepMsg(scope.Excep()));
445 }
446 }
447
448 m_gitEntry.Unregister();
449
450 Debug.Write("ASCOM Scope: Disconnected Successfully\n");
451 }
452 catch (const wxString& Msg)
453 {
454 POSSIBLY_UNUSED(Msg);
455 bError = true;
456 }
457
458 Scope::Disconnect();
459
460 return bError;
461 }
462
463 #define CheckSlewing(dispobj, result) \
464 do { \
465 if (IsStopGuidingWhenSlewingEnabled() && IsSlewing(dispobj)) \
466 { \
467 *(result) = MOVE_ERROR_SLEWING; \
468 throw ERROR_INFO("attempt to guide while slewing"); \
469 } \
470 } while (0)
471
SlewWarningEnabledKey()472 static wxString SlewWarningEnabledKey()
473 {
474 // we want the key to be under "/Confirm" so ConfirmDialog::ResetAllDontAskAgain() resets it, but we also want the setting to be per-profile
475 return wxString::Format("/Confirm/%d/SlewWarningEnabled", pConfig->GetCurrentProfileId());
476 }
477
SuppressSlewAlert(long)478 static void SuppressSlewAlert(long)
479 {
480 //If the user doesn't want to see these, we shouldn't be checking for the condition
481 TheScope()->EnableStopGuidingWhenSlewing(false);
482 }
483
PulseGuideFailedAlertEnabledKey()484 static wxString PulseGuideFailedAlertEnabledKey()
485 {
486 // we want the key to be under "/Confirm" so ConfirmDialog::ResetAllDontAskAgain() resets it, but we also want the setting to be per-profile
487 return wxString::Format("/Confirm/%d/PulseGuideFailedAlertEnabled", pConfig->GetCurrentProfileId());
488 }
489
SuppressPulseGuideFailedAlert(long)490 static void SuppressPulseGuideFailedAlert(long)
491 {
492 pConfig->Global.SetBoolean(PulseGuideFailedAlertEnabledKey(), false);
493 }
494
SyncPulseGuideAlertEnabledKey()495 static wxString SyncPulseGuideAlertEnabledKey()
496 {
497 // we want the key to be under "/Confirm" so
498 // ConfirmDialog::ResetAllDontAskAgain() resets it, but we also want the
499 // setting to be per-profile
500 return wxString::Format("/Confirm/%d/SyncPulseGuideAlertEnabled", pConfig->GetCurrentProfileId());
501 }
502
SuppressSyncPulseGuideAlert(long)503 static void SuppressSyncPulseGuideAlert(long)
504 {
505 pConfig->Global.SetBoolean(SyncPulseGuideAlertEnabledKey(), false);
506 }
507
Guide(GUIDE_DIRECTION direction,int duration)508 Mount::MOVE_RESULT ScopeASCOM::Guide(GUIDE_DIRECTION direction, int duration)
509 {
510 MOVE_RESULT result = MOVE_OK;
511
512 try
513 {
514 Debug.Write(wxString::Format("Guiding Dir = %d, Dur = %d\n", direction, duration));
515
516 if (!IsConnected())
517 {
518 throw ERROR_INFO("ASCOM Scope: attempt to guide when not connected");
519 }
520
521 if (!m_canPulseGuide)
522 {
523 // Could happen if move command is issued on the Aux mount or CanPulseGuide property got changed on the fly
524 pFrame->Alert(_("ASCOM driver does not support PulseGuide. Check your ASCOM driver settings."));
525 throw ERROR_INFO("ASCOM scope: guide command issued but PulseGuide not supported");
526 }
527
528 GITObjRef scope(m_gitEntry);
529
530 // First, check to see if already moving
531
532 CheckSlewing(&scope, &result);
533
534 if (IsGuiding(&scope))
535 {
536 Debug.Write("Entered PulseGuideScope while moving\n");
537 int i;
538 for (i = 0; i < 20; i++)
539 {
540 wxMilliSleep(50);
541
542 CheckSlewing(&scope, &result);
543
544 if (!IsGuiding(&scope))
545 break;
546
547 Debug.Write("Still moving\n");
548 }
549 if (i == 20)
550 {
551 Debug.Write("Still moving after 1s - aborting\n");
552 throw ERROR_INFO("ASCOM Scope: scope is still moving after 1 second");
553 }
554 else
555 {
556 Debug.Write("Movement stopped - continuing\n");
557 }
558 }
559
560 // Do the move
561
562 VARIANTARG rgvarg[2];
563 rgvarg[1].vt = VT_I2;
564 rgvarg[1].iVal = direction;
565 rgvarg[0].vt = VT_I4;
566 rgvarg[0].lVal = (long) duration;
567
568 DISPPARAMS dispParms;
569 dispParms.cArgs = 2;
570 dispParms.rgvarg = rgvarg;
571 dispParms.cNamedArgs = 0;
572 dispParms.rgdispidNamedArgs = NULL;
573
574 wxStopWatch swatch;
575
576 HRESULT hr;
577 ExcepInfo excep;
578 Variant vRes;
579
580 if (FAILED(hr = scope.IDisp()->Invoke(dispid_pulseguide, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD,
581 &dispParms, &vRes, &excep, NULL)))
582 {
583 Debug.Write(wxString::Format("pulseguide: [%x] %s\n", hr, _com_error(hr).ErrorMessage()));
584
585 // Make sure nothing got by us and the mount can really handle pulse guide - HIGHLY unlikely
586 if (scope.GetProp(&vRes, L"CanPulseGuide") && vRes.boolVal != VARIANT_TRUE)
587 {
588 Debug.Write("Tried to guide mount that has no PulseGuide support\n");
589 // This will trigger a nice alert the next time through Guide
590 m_canPulseGuide = false;
591 }
592 throw ERROR_INFO("ASCOM Scope: pulseguide command failed: " + ExcepMsg(excep));
593 }
594
595 long elapsed = swatch.Time();
596
597 if (m_checkForSyncPulseGuide)
598 {
599 // check for a long pulse and an elapsed time close to or longer
600 // than the pulse duration
601 if (duration >= 250 && elapsed >= duration - 30)
602 {
603 Debug.Write(wxString::Format("SyncPulseGuide checking: sync pulse detected. "
604 "Duration = %d Elapsed = %ld\n", duration, elapsed));
605
606 pFrame->SuppressableAlert(SyncPulseGuideAlertEnabledKey(),
607 _("Please disable the Synchronous PulseGuide option in the mount's ASCOM driver "
608 "settings. Enabling the setting can cause unpredictable results."),
609 SuppressSyncPulseGuideAlert, 0);
610
611 // only show the Alert once
612 m_checkForSyncPulseGuide = false;
613 }
614 }
615
616 if (elapsed < (long)duration)
617 {
618 unsigned long rem = (unsigned long)((long)duration - elapsed);
619
620 Debug.Write(wxString::Format("PulseGuide returned control before completion, sleep %lu\n", rem + 10));
621
622 if (WorkerThread::MilliSleep(rem + 10))
623 throw ERROR_INFO("ASCOM Scope: thread terminate requested");
624 }
625
626 if (IsGuiding(&scope))
627 {
628 Debug.Write("scope still moving after pulse duration time elapsed\n");
629
630 // try waiting a little longer. If scope does not stop moving after 1 second, try doing AbortSlew
631 // if it still does not stop after 2 seconds, bail out with an error
632
633 enum { GRACE_PERIOD_MS = 1000,
634 TIMEOUT_MS = GRACE_PERIOD_MS + 1000, };
635
636 bool timeoutExceeded = false;
637 bool didAbortSlew = false;
638
639 while (true)
640 {
641 ::wxMilliSleep(20);
642
643 if (WorkerThread::InterruptRequested())
644 throw ERROR_INFO("ASCOM Scope: thread interrupt requested");
645
646 CheckSlewing(&scope, &result);
647
648 if (!IsGuiding(&scope))
649 {
650 Debug.Write(wxString::Format("scope move finished after %ld + %ld ms\n", (long)duration, swatch.Time() - (long)duration));
651 break;
652 }
653
654 long now = swatch.Time();
655
656 if (!didAbortSlew && now > duration + GRACE_PERIOD_MS && m_abortSlewWhenGuidingStuck)
657 {
658 Debug.Write(wxString::Format("scope still moving after %ld + %ld ms, try aborting slew\n", (long)duration, now - (long)duration));
659 AbortSlew(&scope);
660 didAbortSlew = true;
661 continue;
662 }
663
664 if (now > duration + TIMEOUT_MS)
665 {
666 timeoutExceeded = true;
667 break;
668 }
669 }
670
671 if (timeoutExceeded && IsGuiding(&scope))
672 {
673 throw ERROR_INFO("timeout exceeded waiting for guiding pulse to complete");
674 }
675 }
676 }
677 catch (const wxString& msg)
678 {
679 POSSIBLY_UNUSED(msg);
680
681 if (result == MOVE_OK)
682 {
683 result = MOVE_ERROR;
684
685 if (!WorkerThread::InterruptRequested())
686 {
687 pFrame->SuppressableAlert(PulseGuideFailedAlertEnabledKey(), _("PulseGuide command to mount has failed - guiding is likely to be ineffective."),
688 SuppressPulseGuideFailedAlert, 0);
689 }
690 }
691 }
692
693 if (result == MOVE_ERROR_SLEWING)
694 {
695 pFrame->SuppressableAlert(SlewWarningEnabledKey(), _("Guiding stopped: the scope started slewing."),
696 SuppressSlewAlert, 0);
697 }
698
699 return result;
700 }
701
IsGuiding(DispatchObj * scope)702 bool ScopeASCOM::IsGuiding(DispatchObj *scope)
703 {
704 bool bReturn = true;
705
706 try
707 {
708 if (!m_canCheckPulseGuiding)
709 {
710 // Assume all is good - best we can do as this is really a fail-safe check. If we can't call this property (lame driver) guides will have to
711 // enforce the wait. But, enough don't support this that we can't throw an error.
712 throw ERROR_INFO("ASCOM Scope: IsGuiding - !m_canCheckPulseGuiding");
713 }
714
715 // First, check to see if already moving
716 Variant vRes;
717 if (!scope->GetProp(&vRes, dispid_ispulseguiding))
718 {
719 pFrame->Alert(_("ASCOM driver failed checking IsPulseGuiding. See the debug log for more information."));
720 throw ERROR_INFO("ASCOM Scope: IsGuiding - IsPulseGuiding failed: " + ExcepMsg(scope->Excep()));
721 }
722
723 bReturn = vRes.boolVal == VARIANT_TRUE;
724 }
725 catch (const wxString& Msg)
726 {
727 POSSIBLY_UNUSED(Msg);
728 bReturn = false;
729 }
730
731 Debug.Write(wxString::Format("IsGuiding returns %d\n", bReturn));
732
733 return bReturn;
734 }
735
IsSlewing(DispatchObj * scope)736 bool ScopeASCOM::IsSlewing(DispatchObj *scope)
737 {
738 Variant vRes;
739 if (!scope->GetProp(&vRes, dispid_isslewing))
740 {
741 Debug.Write(wxString::Format("ScopeASCOM::IsSlewing failed: %s\n", ExcepMsg(scope->Excep())));
742 pFrame->Alert(_("ASCOM driver failed checking for slewing, see the debug log for more information."));
743 return false;
744 }
745
746 bool result = vRes.boolVal == VARIANT_TRUE;
747
748 Debug.Write(wxString::Format("IsSlewing returns %d\n", result));
749
750 return result;
751 }
752
AbortSlew(DispatchObj * scope)753 void ScopeASCOM::AbortSlew(DispatchObj *scope)
754 {
755 Debug.Write("ScopeASCOM: AbortSlew\n");
756 Variant vRes;
757 if (!scope->InvokeMethod(&vRes, dispid_abortslew))
758 {
759 pFrame->Alert(_("ASCOM driver failed calling AbortSlew, see the debug log for more information."));
760 }
761 }
762
CanCheckSlewing()763 bool ScopeASCOM::CanCheckSlewing()
764 {
765 return true;
766 }
767
Slewing()768 bool ScopeASCOM::Slewing()
769 {
770 bool bReturn = true;
771
772 try
773 {
774 if (!IsConnected())
775 {
776 throw ERROR_INFO("ASCOM Scope: Cannot check Slewing when not connected to mount");
777 }
778
779 GITObjRef scope(m_gitEntry);
780 bReturn = IsSlewing(&scope);
781 }
782 catch (const wxString& Msg)
783 {
784 POSSIBLY_UNUSED(Msg);
785 bReturn = false;
786 }
787
788 return bReturn;
789 }
790
HasNonGuiMove()791 bool ScopeASCOM::HasNonGuiMove()
792 {
793 return true;
794 }
795
796 // return the declination in radians, or UNKNOWN_DECLINATION
GetDeclination()797 double ScopeASCOM::GetDeclination()
798 {
799 double dReturn = UNKNOWN_DECLINATION;
800
801 try
802 {
803 if (!IsConnected())
804 {
805 throw ERROR_INFO("ASCOM Scope: cannot get Declination when not connected to mount");
806 }
807
808 if (!m_canGetCoordinates)
809 {
810 throw THROW_INFO("!m_canGetCoordinates");
811 }
812
813 GITObjRef scope(m_gitEntry);
814
815 Variant vRes;
816 if (!scope.GetProp(&vRes, dispid_declination))
817 {
818 throw ERROR_INFO("GetDeclination() fails: " + ExcepMsg(scope.Excep()));
819 }
820
821 dReturn = radians(vRes.dblVal);
822 }
823 catch (const wxString& Msg)
824 {
825 POSSIBLY_UNUSED(Msg);
826 m_canGetCoordinates = false;
827 }
828
829 Debug.Write(wxString::Format("ScopeASCOM::GetDeclination() returns %s\n", DeclinationStr(dReturn)));
830
831 return dReturn;
832 }
833
834 // Return RA and Dec guide rates in native ASCOM units, degrees/sec.
835 // Convention is to return true on an error
GetGuideRates(double * pRAGuideRate,double * pDecGuideRate)836 bool ScopeASCOM::GetGuideRates(double *pRAGuideRate, double *pDecGuideRate)
837 {
838 bool bError = false;
839
840 try
841 {
842 if (!IsConnected())
843 {
844 throw ERROR_INFO("ASCOM Scope: cannot get guide rates when not connected");
845 }
846
847 if (!m_canGetGuideRates)
848 {
849 throw THROW_INFO("ASCOM Scope: not capable of getting guide rates");
850 }
851
852 GITObjRef scope(m_gitEntry);
853
854 Variant vRes;
855
856 if (!scope.GetProp(&vRes, dispid_decguiderate))
857 {
858 throw ERROR_INFO("ASCOM Scope: GuideRateDec() failed: " + ExcepMsg(scope.Excep()));
859 }
860
861 *pDecGuideRate = vRes.dblVal;
862
863 if (!scope.GetProp(&vRes, dispid_raguiderate))
864 {
865 throw ERROR_INFO("ASCOM Scope: GuideRateRA() failed: " + ExcepMsg(scope.Excep()));
866 }
867
868 *pRAGuideRate = vRes.dblVal;
869 }
870 catch (const wxString& Msg)
871 {
872 bError = true;
873 POSSIBLY_UNUSED(Msg);
874 }
875
876 Debug.Write(wxString::Format("ScopeASCOM::GetGuideRates returns %u %.3f %.3f a-s/sec\n", bError,
877 bError ? 0.0 : *pDecGuideRate * 3600., bError ? 0.0 : *pRAGuideRate * 3600.));
878
879 return bError;
880 }
881
GetCoordinates(double * ra,double * dec,double * siderealTime)882 bool ScopeASCOM::GetCoordinates(double *ra, double *dec, double *siderealTime)
883 {
884 bool bError = false;
885
886 try
887 {
888 if (!IsConnected())
889 {
890 throw ERROR_INFO("ASCOM Scope: cannot get coordinates when not connected");
891 }
892
893 if (!m_canGetCoordinates)
894 {
895 throw THROW_INFO("ASCOM Scope: not capable of getting coordinates");
896 }
897
898 GITObjRef scope(m_gitEntry);
899
900 Variant vRA;
901
902 if (!scope.GetProp(&vRA, dispid_rightascension))
903 {
904 throw ERROR_INFO("ASCOM Scope: get right ascension failed: " + ExcepMsg(scope.Excep()));
905 }
906
907 Variant vDec;
908
909 if (!scope.GetProp(&vDec, dispid_declination))
910 {
911 throw ERROR_INFO("ASCOM Scope: get declination failed: " + ExcepMsg(scope.Excep()));
912 }
913
914 Variant vST;
915
916 if (!scope.GetProp(&vST, dispid_siderealtime))
917 {
918 throw ERROR_INFO("ASCOM Scope: get sidereal time failed: " + ExcepMsg(scope.Excep()));
919 }
920
921 *ra = vRA.dblVal;
922 *dec = vDec.dblVal;
923 *siderealTime = vST.dblVal;
924 }
925 catch (const wxString& Msg)
926 {
927 bError = true;
928 POSSIBLY_UNUSED(Msg);
929 }
930
931 return bError;
932 }
933
GetSiteLatLong(double * latitude,double * longitude)934 bool ScopeASCOM::GetSiteLatLong(double *latitude, double *longitude)
935 {
936 if (dispid_sitelatitude == DISPID_UNKNOWN || dispid_sitelongitude == DISPID_UNKNOWN)
937 return true;
938
939 bool bError = false;
940
941 try
942 {
943 if (!IsConnected())
944 {
945 throw ERROR_INFO("ASCOM Scope: cannot get site latitude/longitude when not connected");
946 }
947
948 GITObjRef scope(m_gitEntry);
949
950 Variant vLat;
951
952 if (!scope.GetProp(&vLat, dispid_sitelatitude))
953 {
954 throw ERROR_INFO("ASCOM Scope: get site latitude failed: " + ExcepMsg(scope.Excep()));
955 }
956
957 Variant vLong;
958
959 if (!scope.GetProp(&vLong, dispid_sitelongitude))
960 {
961 throw ERROR_INFO("ASCOM Scope: get site longitude failed: " + ExcepMsg(scope.Excep()));
962 }
963
964 *latitude = vLat.dblVal;
965 *longitude = vLong.dblVal;
966 }
967 catch (const wxString& Msg)
968 {
969 bError = true;
970 POSSIBLY_UNUSED(Msg);
971 }
972
973 return bError;
974 }
975
CanSlew()976 bool ScopeASCOM::CanSlew()
977 {
978 try
979 {
980 if (!IsConnected())
981 {
982 throw ERROR_INFO("ASCOM Scope: cannot get CanSlew property when not connected to mount");
983 }
984
985 return m_canSlew;
986 }
987 catch (const wxString& Msg)
988 {
989 POSSIBLY_UNUSED(Msg);
990 return false;
991 }
992 }
993
CanSlewAsync()994 bool ScopeASCOM::CanSlewAsync()
995 {
996 try
997 {
998 if (!IsConnected())
999 {
1000 throw ERROR_INFO("ASCOM Scope: cannot get CanSlewAsync property when not connected to mount");
1001 }
1002
1003 return m_canSlewAsync;
1004 }
1005 catch (const wxString& Msg)
1006 {
1007 POSSIBLY_UNUSED(Msg);
1008 return false;
1009 }
1010 }
1011
CanReportPosition()1012 bool ScopeASCOM::CanReportPosition()
1013 {
1014 return true;
1015 }
1016
CanPulseGuide()1017 bool ScopeASCOM::CanPulseGuide()
1018 {
1019 return m_canPulseGuide;
1020 }
1021
SlewToCoordinates(double ra,double dec)1022 bool ScopeASCOM::SlewToCoordinates(double ra, double dec)
1023 {
1024 bool bError = false;
1025
1026 try
1027 {
1028 if (!IsConnected())
1029 {
1030 throw ERROR_INFO("ASCOM Scope: cannot slew when not connected");
1031 }
1032
1033 if (!m_canSlew)
1034 {
1035 throw THROW_INFO("ASCOM Scope: not capable of slewing");
1036 }
1037
1038 GITObjRef scope(m_gitEntry);
1039
1040 Variant vRes;
1041
1042 if (!scope.InvokeMethod(&vRes, dispid_slewtocoordinates, ra, dec))
1043 {
1044 throw ERROR_INFO("ASCOM Scope: slew to coordinates failed");
1045 }
1046 }
1047 catch (const wxString& Msg)
1048 {
1049 POSSIBLY_UNUSED(Msg);
1050 bError = true;
1051 }
1052
1053 return bError;
1054 }
1055
SlewToCoordinatesAsync(double ra,double dec)1056 bool ScopeASCOM::SlewToCoordinatesAsync(double ra, double dec)
1057 {
1058 bool bError = false;
1059
1060 try
1061 {
1062 if (!IsConnected())
1063 {
1064 throw ERROR_INFO("ASCOM Scope: cannot slew when not connected");
1065 }
1066
1067 if (!m_canSlewAsync)
1068 {
1069 throw THROW_INFO("ASCOM Scope: not capable of async slewing");
1070 }
1071
1072 GITObjRef scope(m_gitEntry);
1073
1074 Variant vRes;
1075
1076 if (!scope.InvokeMethod(&vRes, L"SlewToCoordinatesAsync", ra, dec))
1077 {
1078 throw ERROR_INFO("ASCOM Scope: async slew to coordinates failed");
1079 }
1080 }
1081 catch (const wxString& Msg)
1082 {
1083 POSSIBLY_UNUSED(Msg);
1084 bError = true;
1085 }
1086
1087 return bError;
1088 }
1089
AbortSlew()1090 void ScopeASCOM::AbortSlew()
1091 {
1092 GITObjRef scope(m_gitEntry);
1093 Variant vRes;
1094 scope.InvokeMethod(&vRes, L"AbortSlew");
1095 }
1096
SideOfPier()1097 PierSide ScopeASCOM::SideOfPier()
1098 {
1099 PierSide pierSide = PIER_SIDE_UNKNOWN;
1100
1101 try
1102 {
1103 if (!IsConnected())
1104 {
1105 throw ERROR_INFO("ASCOM Scope: cannot get side of pier when not connected");
1106 }
1107
1108 if (dispid_sideofpier == DISPID_UNKNOWN)
1109 {
1110 throw THROW_INFO("ASCOM Scope: not capable of getting side of pier");
1111 }
1112
1113 GITObjRef scope(m_gitEntry);
1114
1115 Variant vRes;
1116
1117 if (!scope.GetProp(&vRes, dispid_sideofpier))
1118 {
1119 throw ERROR_INFO("ASCOM Scope: SideOfPier failed: " + ExcepMsg(scope.Excep()));
1120 }
1121
1122 switch (vRes.intVal) {
1123 case 0: pierSide = PIER_SIDE_EAST; break;
1124 case 1: pierSide = PIER_SIDE_WEST; break;
1125 }
1126 }
1127 catch (const wxString& Msg)
1128 {
1129 POSSIBLY_UNUSED(Msg);
1130 }
1131
1132 Debug.Write(wxString::Format("ScopeASCOM::SideOfPier() returns %d\n", pierSide));
1133
1134 return pierSide;
1135 }
1136
1137 #endif /* GUIDE_ASCOM */
1138