1 /*
2  *  scope.cpp
3  *  PHD Guiding
4  *
5  *  Created by Craig Stark.
6  *  Copyright (c) 2006-2010 Craig Stark.
7  *  All rights reserved.
8  *
9  *  This source code is distributed under the following "BSD" license
10  *  Redistribution and use in source and binary forms, with or without
11  *  modification, are permitted provided that the following conditions are met:
12  *    Redistributions of source code must retain the above copyright notice,
13  *     this list of conditions and the following disclaimer.
14  *    Redistributions in binary form must reproduce the above copyright notice,
15  *     this list of conditions and the following disclaimer in the
16  *     documentation and/or other materials provided with the distribution.
17  *    Neither the name of Craig Stark, Stark Labs nor the names of its
18  *     contributors may be used to endorse or promote products derived from
19  *     this software without specific prior written permission.
20  *
21  *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22  *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23  *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24  *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
25  *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26  *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27  *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
28  *  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
29  *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30  *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31  *  POSSIBILITY OF SUCH DAMAGE.
32  *
33  */
34 #include "phd.h"
35 
36 #include "backlash_comp.h"
37 #include "calreview_dialog.h"
38 #include "calstep_dialog.h"
39 #include "image_math.h"
40 #include "socket_server.h"
41 
42 #include <wx/textfile.h>
43 
44 static const int DefaultCalibrationDuration = 750;
45 static const int DefaultMaxDecDuration = 2500;
46 static const int DefaultMaxRaDuration = 2500;
47 enum { MAX_DURATION_MIN = 50, MAX_DURATION_MAX = 8000, };
48 
49 static const DEC_GUIDE_MODE DefaultDecGuideMode = DEC_AUTO;
50 static const GUIDE_ALGORITHM DefaultRaGuideAlgorithm = GUIDE_ALGORITHM_HYSTERESIS;
51 static const GUIDE_ALGORITHM DefaultDecGuideAlgorithm = GUIDE_ALGORITHM_RESIST_SWITCH;
52 
53 static const int MAX_CALIBRATION_STEPS = 60;
54 static const int CAL_ALERT_MINSTEPS = 4;
55 static const double CAL_ALERT_ORTHOGONALITY_TOLERANCE = 12.5;               // Degrees
56 static const double CAL_ALERT_DECRATE_DIFFERENCE = 0.20;                    // Ratio tolerance
57 static const double CAL_ALERT_AXISRATES_TOLERANCE = 0.20;                   // Ratio tolerance
58 static const bool SANITY_CHECKING_ACTIVE = true;                            // Control calibration sanity checking
59 
60 static int LIMIT_REACHED_WARN_COUNT = 5;
61 static int MAX_NUDGES = 3;
62 static double NUDGE_TOLERANCE = 2.0;
63 
64 // enable dec compensation when calibration declination is less than this
65 const double Scope::DEC_COMP_LIMIT = M_PI / 2.0 * 2.0 / 3.0;   // 60 degrees
66 const double Scope::DEFAULT_MOUNT_GUIDE_SPEED = 0.5;
67 
Scope()68 Scope::Scope()
69     :
70     m_maxDecDuration(0),
71     m_maxRaDuration(0),
72     m_decGuideMode(DEC_NONE),
73     m_raLimitReachedDirection(NONE),
74     m_raLimitReachedCount(0),
75     m_decLimitReachedDirection(NONE),
76     m_decLimitReachedCount(0)
77 {
78     m_calibrationSteps = 0;
79     m_limitReachedDeferralTime = wxDateTime::GetTimeNow();
80     m_graphControlPane = nullptr;
81 
82     wxString prefix = "/" + GetMountClassName();
83     int calibrationDuration = pConfig->Profile.GetInt(prefix + "/CalibrationDuration", DefaultCalibrationDuration);
84     SetCalibrationDuration(calibrationDuration);
85 
86     int calibrationDistance = pConfig->Profile.GetInt(prefix + "/CalibrationDistance", CalstepDialog::DEFAULT_DISTANCE);
87     SetCalibrationDistance(calibrationDistance);
88 
89     int maxRaDuration  = pConfig->Profile.GetInt(prefix + "/MaxRaDuration", DefaultMaxRaDuration);
90     SetMaxRaDuration(maxRaDuration);
91 
92     int maxDecDuration = pConfig->Profile.GetInt(prefix + "/MaxDecDuration", DefaultMaxDecDuration);
93     SetMaxDecDuration(maxDecDuration);
94 
95     int decGuideMode = pConfig->Profile.GetInt(prefix + "/DecGuideMode", DefaultDecGuideMode);
96     SetDecGuideMode(decGuideMode);
97 
98     int raGuideAlgorithm = pConfig->Profile.GetInt(prefix + "/XGuideAlgorithm", DefaultRaGuideAlgorithm);
99     SetXGuideAlgorithm(raGuideAlgorithm);
100 
101     int decGuideAlgorithm = pConfig->Profile.GetInt(prefix + "/YGuideAlgorithm", DefaultDecGuideAlgorithm);
102     SetYGuideAlgorithm(decGuideAlgorithm);
103 
104     bool val = pConfig->Profile.GetBoolean(prefix + "/CalFlipRequiresDecFlip", false);
105     SetCalibrationFlipRequiresDecFlip(val);
106 
107     val = pConfig->Profile.GetBoolean(prefix + "/AssumeOrthogonal", false);
108     SetAssumeOrthogonal(val);
109 
110     val = pConfig->Profile.GetBoolean(prefix + "/UseDecComp", true);
111     EnableDecCompensation(val);
112 
113     m_backlashComp = new BacklashComp(this);
114 }
115 
~Scope()116 Scope::~Scope()
117 {
118     if (m_graphControlPane)
119     {
120         m_graphControlPane->m_pScope = nullptr;
121     }
122 }
123 
DefaultXGuideAlgorithm() const124 GUIDE_ALGORITHM Scope::DefaultXGuideAlgorithm() const
125 {
126     return DefaultRaGuideAlgorithm;
127 }
128 
DefaultYGuideAlgorithm() const129 GUIDE_ALGORITHM Scope::DefaultYGuideAlgorithm() const
130 {
131     return DefaultDecGuideAlgorithm;
132 }
133 
SetCalibrationDuration(int calibrationDuration)134 bool Scope::SetCalibrationDuration(int calibrationDuration)
135 {
136     bool bError = false;
137 
138     try
139     {
140         if (calibrationDuration <= 0)
141         {
142             throw ERROR_INFO("invalid calibrationDuration");
143         }
144 
145         m_calibrationDuration = calibrationDuration;
146     }
147     catch (const wxString& Msg)
148     {
149         POSSIBLY_UNUSED(Msg);
150         bError = true;
151         m_calibrationDuration = DefaultCalibrationDuration;
152     }
153 
154     pConfig->Profile.SetInt("/scope/CalibrationDuration", m_calibrationDuration);
155 
156     return bError;
157 }
158 
SetCalibrationDistance(int calibrationDistance)159 bool Scope::SetCalibrationDistance(int calibrationDistance)
160 {
161     bool bError = false;
162 
163     try
164     {
165         if (calibrationDistance <= 0)
166         {
167             throw ERROR_INFO("invalid calibrationDistance");
168         }
169 
170         m_calibrationDistance = calibrationDistance;
171     }
172     catch (const wxString& Msg)
173     {
174         POSSIBLY_UNUSED(Msg);
175         bError = true;
176         m_calibrationDistance = CalstepDialog::DEFAULT_DISTANCE;
177     }
178 
179     pConfig->Profile.SetInt("/scope/CalibrationDistance", m_calibrationDistance);
180     return bError;
181 }
182 
SetMaxDecDuration(int maxDecDuration)183 bool Scope::SetMaxDecDuration(int maxDecDuration)
184 {
185     bool bError = false;
186 
187     try
188     {
189         if (maxDecDuration < 0)
190         {
191             throw ERROR_INFO("maxDecDuration < 0");
192         }
193 
194         if (m_maxDecDuration != maxDecDuration)
195             pFrame->NotifyGuidingParam("Dec Max Duration", maxDecDuration);
196 
197         m_maxDecDuration = maxDecDuration;
198     }
199     catch (const wxString& Msg)
200     {
201         POSSIBLY_UNUSED(Msg);
202         bError = true;
203         m_maxDecDuration = DefaultMaxDecDuration;
204     }
205 
206     pConfig->Profile.SetInt("/scope/MaxDecDuration", m_maxDecDuration);
207 
208     return bError;
209 }
210 
SetMaxRaDuration(int maxRaDuration)211 bool Scope::SetMaxRaDuration(int maxRaDuration)
212 {
213     bool bError = false;
214 
215     try
216     {
217         if (maxRaDuration < 0)
218         {
219             throw ERROR_INFO("maxRaDuration < 0");
220         }
221 
222         if (m_maxRaDuration != maxRaDuration)
223         {
224             pFrame->NotifyGuidingParam("RA Max Duration", maxRaDuration);
225 
226             m_maxRaDuration = maxRaDuration;
227         }
228     }
229     catch (const wxString& Msg)
230     {
231         POSSIBLY_UNUSED(Msg);
232         bError = true;
233         m_maxRaDuration = DefaultMaxRaDuration;
234     }
235 
236     pConfig->Profile.SetInt("/scope/MaxRaDuration", m_maxRaDuration);
237 
238     return bError;
239 }
240 
DecGuideModeStr(DEC_GUIDE_MODE m)241 wxString Scope::DecGuideModeStr(DEC_GUIDE_MODE m)
242 {
243     switch (m)
244     {
245     case DEC_NONE:  return "Off";
246     case DEC_AUTO:  return "Auto";
247     case DEC_NORTH: return "North";
248     case DEC_SOUTH: return "South";
249     default:        return "Invalid";
250     }
251 }
252 
DecGuideModeLocaleStr(DEC_GUIDE_MODE m)253 wxString Scope::DecGuideModeLocaleStr(DEC_GUIDE_MODE m)
254 {
255     switch (m)
256     {
257     case DEC_NONE:  return _("Off");
258     case DEC_AUTO:  return _("Auto");
259     case DEC_NORTH: return _("North");
260     case DEC_SOUTH: return _("South");
261     default:        return _("Invalid");
262     }
263 }
264 
SetDecGuideMode(int decGuideMode)265 bool Scope::SetDecGuideMode(int decGuideMode)
266 {
267     bool bError = false;
268 
269     try
270     {
271         switch (decGuideMode)
272         {
273             case DEC_NONE:
274             case DEC_AUTO:
275             case DEC_NORTH:
276             case DEC_SOUTH:
277                 break;
278             default:
279                 throw ERROR_INFO("invalid decGuideMode");
280                 break;
281         }
282 
283         if (m_decGuideMode != decGuideMode)
284         {
285             m_decGuideMode = (DEC_GUIDE_MODE) decGuideMode;
286             if (pFrame && pFrame->pGraphLog)
287                 pFrame->pGraphLog->EnableDecControls(decGuideMode != DEC_NONE);
288 
289             wxString s = DecGuideModeStr(m_decGuideMode);
290             Debug.Write(wxString::Format("DecGuideMode set to %s (%d)\n", s, decGuideMode));
291             pFrame->NotifyGuidingParam("Dec Guide Mode", s);
292             BacklashComp *blc = GetBacklashComp();
293             if (blc)
294             {
295                 if (decGuideMode != DEC_AUTO)
296                     blc->EnableBacklashComp(false);             // Can't do blc in uni-direction mode because there's no recovery from over-shoots
297                 else
298                     blc->ResetBLCState();
299             }
300             pConfig->Profile.SetInt("/scope/DecGuideMode", m_decGuideMode);
301             if (pFrame)
302                 pFrame->UpdateStatusBarCalibrationStatus();
303         }
304     }
305     catch (const wxString& Msg)
306     {
307         POSSIBLY_UNUSED(Msg);
308         bError = true;
309     }
310 
311     return bError;
312 }
313 
CompareNoCase(const wxString & first,const wxString & second)314 static int CompareNoCase(const wxString& first, const wxString& second)
315 {
316     return first.CmpNoCase(second);
317 }
318 
INDIMountName()319 static wxString INDIMountName()
320 {
321     wxString val = pConfig->Profile.GetString("/indi/INDImount", wxEmptyString);
322     return val.empty() ? _("INDI Mount") : wxString::Format(_("INDI Mount [%s]"), val);
323 }
324 
MountList()325 wxArrayString Scope::MountList()
326 {
327     wxArrayString ScopeList;
328 
329     ScopeList.Add(_("None"));
330 #ifdef GUIDE_ASCOM
331     wxArrayString ascomScopes = ScopeASCOM::EnumAscomScopes();
332     for (unsigned int i = 0; i < ascomScopes.Count(); i++)
333         ScopeList.Add(ascomScopes[i]);
334 #endif
335 #ifdef GUIDE_ONCAMERA
336     ScopeList.Add(_T("On-camera"));
337 #endif
338 #ifdef GUIDE_ONSTEPGUIDER
339     ScopeList.Add(_T("On-AO"));
340 #endif
341 #ifdef GUIDE_GPUSB
342     ScopeList.Add(_T("GPUSB"));
343 #endif
344 #ifdef GUIDE_GPINT
345     ScopeList.Add(_T("GPINT 3BC"));
346     ScopeList.Add(_T("GPINT 378"));
347     ScopeList.Add(_T("GPINT 278"));
348 #endif
349 #ifdef GUIDE_VOYAGER
350     ScopeList.Add(_T("Voyager"));
351 #endif
352 #ifdef GUIDE_EQUINOX
353     ScopeList.Add(_T("Equinox 6"));
354     ScopeList.Add(_T("EQMAC"));
355 #endif
356 #ifdef GUIDE_GCUSBST4
357     ScopeList.Add(_T("GC USB ST4"));
358 #endif
359 #ifdef GUIDE_INDI
360     ScopeList.Add(INDIMountName());
361 #endif
362 
363     ScopeList.Sort(&CompareNoCase);
364 
365     return ScopeList;
366 }
367 
AuxMountList()368 wxArrayString Scope::AuxMountList()
369 {
370     wxArrayString scopeList;
371     scopeList.Add(_("None"));      // Keep this at the top of the list
372 
373 #ifdef GUIDE_ASCOM
374     wxArrayString positionAwareScopes = ScopeASCOM::EnumAscomScopes();
375     positionAwareScopes.Sort(&CompareNoCase);
376     for (unsigned int i = 0; i < positionAwareScopes.Count(); i++)
377         scopeList.Add(positionAwareScopes[i]);
378 #endif
379 
380 #ifdef GUIDE_INDI
381     scopeList.Add(INDIMountName());
382 #endif
383 
384     scopeList.Add(ScopeManualPointing::GetDisplayName());
385 
386     return scopeList;
387 }
388 
Factory(const wxString & choice)389 Scope *Scope::Factory(const wxString& choice)
390 {
391     Scope *pReturn = nullptr;
392 
393     try
394     {
395         if (choice.IsEmpty())
396         {
397             throw ERROR_INFO("ScopeFactory called with choice.IsEmpty()");
398         }
399 
400         Debug.Write(wxString::Format("ScopeFactory(%s)\n", choice));
401 
402         if (false) // so else ifs can follow
403         {
404         }
405         // do ASCOM and INDI first since they includes choices that could match stings below like Simulator
406 #ifdef GUIDE_ASCOM
407         else if (choice.Contains(_T("ASCOM")))
408             pReturn = new ScopeASCOM(choice);
409 #endif
410 #ifdef GUIDE_INDI
411         else if (choice.Contains(_("INDI")))
412             pReturn = INDIScopeFactory::MakeINDIScope();
413 #endif
414         else if (choice == _("None"))
415             pReturn = nullptr;
416 #ifdef GUIDE_ONCAMERA
417         else if (choice == _T("On-camera"))
418             pReturn = new ScopeOnCamera();
419 #endif
420 #ifdef GUIDE_ONSTEPGUIDER
421         else if (choice == _T("On-AO"))
422             pReturn = new ScopeOnStepGuider();
423 #endif
424 #ifdef GUIDE_GPUSB
425         else if (choice.Contains(_T("GPUSB")))
426             pReturn = new ScopeGpUsb();
427 #endif
428 #ifdef GUIDE_GPINT
429         else if (choice.Contains(_T("GPINT 3BC")))
430             pReturn = new ScopeGpInt((short) 0x3BC);
431         else if (choice.Contains(_T("GPINT 378")))
432             pReturn = new ScopeGpInt((short) 0x378);
433         else if (choice.Contains(_T("GPINT 278")))
434             pReturn = new ScopeGpInt((short) 0x278);
435 #endif
436 #ifdef GUIDE_VOYAGER
437         else if (choice.Contains(_T("Voyager")))
438         {
439             This needs work.  We have to move the setting of the IP address
440                 into the connect routine
441             ScopeVoyager *pVoyager = new ScopeVoyager();
442         }
443 #endif
444 #ifdef GUIDE_EQUINOX
445         else if (choice.Contains(_T("Equinox 6")))
446             pReturn = new ScopeEquinox();
447 #endif
448 #ifdef GUIDE_EQMAC
449         else if (choice.Contains(_T("EQMAC")))
450             pReturn = new ScopeEQMac();
451 #endif
452 #ifdef GUIDE_GCUSBST4
453         else if (choice.Contains(_T("GC USB ST4")))
454             pReturn = new ScopeGCUSBST4();
455 #endif
456         else if (choice.Contains(ScopeManualPointing::GetDisplayName()))
457             pReturn = new ScopeManualPointing();
458         else
459         {
460             throw ERROR_INFO("ScopeFactory: Unknown Scope choice");
461         }
462 
463         if (pReturn)
464         {
465             // virtual function call means we cannot do this in the Scope constructor
466             pReturn->EnableStopGuidingWhenSlewing(pConfig->Profile.GetBoolean("/scope/StopGuidingWhenSlewing",
467                 pReturn->CanCheckSlewing()));
468         }
469     }
470     catch (const wxString& Msg)
471     {
472         POSSIBLY_UNUSED(Msg);
473         if (pReturn)
474         {
475             delete pReturn;
476             pReturn = nullptr;
477         }
478     }
479 
480     return pReturn;
481 }
482 
RequiresCamera()483 bool Scope::RequiresCamera()
484 {
485     return false;
486 }
487 
RequiresStepGuider()488 bool Scope::RequiresStepGuider()
489 {
490     return false;
491 }
492 
CalibrationFlipRequiresDecFlip()493 bool Scope::CalibrationFlipRequiresDecFlip()
494 {
495     return m_calibrationFlipRequiresDecFlip;
496 }
497 
HasHPDecEncoder()498 bool Scope::HasHPDecEncoder()
499 {
500     return pConfig->Profile.GetBoolean("/scope/HiResEncoders", false);
501 }
502 
SetCalibrationFlipRequiresDecFlip(bool val)503 void Scope::SetCalibrationFlipRequiresDecFlip(bool val)
504 {
505     m_calibrationFlipRequiresDecFlip = val;
506     pConfig->Profile.SetBoolean("/scope/CalFlipRequiresDecFlip", val);
507 }
508 
SetAssumeOrthogonal(bool val)509 void Scope::SetAssumeOrthogonal(bool val)
510 {
511     m_assumeOrthogonal = val;
512     pConfig->Profile.SetBoolean("/scope/AssumeOrthogonal", val);
513 }
514 
EnableStopGuidingWhenSlewing(bool enable)515 void Scope::EnableStopGuidingWhenSlewing(bool enable)
516 {
517     if (enable)
518         Debug.Write("Scope: enabling slew check, guiding will stop when slew is detected\n");
519     else
520         Debug.Write("Scope: slew check disabled\n");
521 
522     pConfig->Profile.SetBoolean("/scope/StopGuidingWhenSlewing", enable);
523     m_stopGuidingWhenSlewing = enable;
524 }
525 
StartDecDrift()526 void Scope::StartDecDrift()
527 {
528     m_saveDecGuideMode = m_decGuideMode;
529     m_decGuideMode = DEC_NONE;
530 
531     Debug.Write(wxString::Format("StartDecDrift: DecGuideMode set to %s (%d)\n",
532         DecGuideModeStr(m_decGuideMode), m_decGuideMode));
533 
534     if (m_graphControlPane)
535     {
536         m_graphControlPane->m_pDecMode->SetSelection(DEC_NONE);
537         m_graphControlPane->m_pDecMode->Enable(false);
538     }
539 }
540 
EndDecDrift()541 void Scope::EndDecDrift()
542 {
543     m_decGuideMode = m_saveDecGuideMode;
544 
545     Debug.Write(wxString::Format("EndDecDrift: DecGuideMode set to %s (%d)\n",
546         DecGuideModeStr(m_decGuideMode), m_decGuideMode));
547 
548     if (m_graphControlPane)
549     {
550         m_graphControlPane->m_pDecMode->SetSelection(m_decGuideMode);
551         m_graphControlPane->m_pDecMode->Enable(true);
552     }
553 }
554 
IsDecDrifting() const555 bool Scope::IsDecDrifting() const
556 {
557     return m_decGuideMode == DEC_NONE;
558 }
559 
MoveAxis(GUIDE_DIRECTION direction,int duration,unsigned int moveOptions)560 Mount::MOVE_RESULT Scope::MoveAxis(GUIDE_DIRECTION direction, int duration, unsigned int moveOptions)
561 {
562     MOVE_RESULT result = MOVE_OK;
563 
564     Debug.Write(wxString::Format("scope move axis dir= %d dur= %d opts= 0x%x\n", direction, duration, moveOptions));
565 
566     try
567     {
568         MoveResultInfo move;
569         result = MoveAxis(direction, duration, moveOptions, &move);
570 
571         if (result != MOVE_OK)
572         {
573             throw THROW_INFO("Move failed");
574         }
575     }
576     catch (const wxString& Msg)
577     {
578         POSSIBLY_UNUSED(Msg);
579     }
580 
581     return result;
582 }
583 
CalibrationMoveSize()584 int Scope::CalibrationMoveSize()
585 {
586     return m_calibrationDuration;
587 }
588 
LimitReachedWarningKey(long axis)589 static wxString LimitReachedWarningKey(long axis)
590 {
591     // we want the key to be under "/Confirm" so ConfirmDialog::ResetAllDontAskAgain() resets it, but we also want the setting to be per-profile
592     return wxString::Format("/Confirm/%d/Max%sLimitWarningEnabled", pConfig->GetCurrentProfileId(), axis == GUIDE_RA ? "RA" : "Dec");
593 }
594 
SuppressLimitReachedWarning(long axis)595 static void SuppressLimitReachedWarning(long axis)
596 {
597     pConfig->Global.SetBoolean(LimitReachedWarningKey(axis), false);
598 }
599 
DeferPulseLimitAlertCheck()600 void Scope::DeferPulseLimitAlertCheck()
601 {
602     enum { LIMIT_REACHED_GRACE_PERIOD_SECONDS = 120 };
603 
604     m_limitReachedDeferralTime =
605         wxDateTime::GetTimeNow() + LIMIT_REACHED_GRACE_PERIOD_SECONDS;
606 }
607 
AlertLimitReached(int duration,GuideAxis axis)608 void Scope::AlertLimitReached(int duration, GuideAxis axis)
609 {
610     static time_t s_lastLogged;
611 
612     time_t now = wxDateTime::GetTimeNow();
613     if (s_lastLogged != 0 && now < s_lastLogged + 30)
614         return;
615 
616     s_lastLogged = now;
617 
618     if (now < m_limitReachedDeferralTime)
619         return;
620 
621     if (duration < MAX_DURATION_MAX)
622     {
623         int defaultVal = axis == GUIDE_RA ? DefaultMaxRaDuration : DefaultMaxDecDuration;
624         if (duration >= defaultVal)          // Max duration is probably ok, some other kind of problem
625         {
626             wxString msg;
627             if (axis == GUIDE_RA)
628             {
629                 if (CanPulseGuide())
630                 {
631                     msg = _("PHD2 is not able to make sufficient corrections in RA.  Check for cable snags, try re-doing your calibration, and "
632                             "check for problems with the mount mechanics.");
633                 }
634                 else
635                 {
636                     msg = _("PHD2 is not able to make sufficient corrections in RA.  Check for cable snags, try re-doing your calibration, and "
637                             "confirm the ST-4 cable is working properly.");
638                 }
639             }
640             else                          // Dec axis problems
641             {
642                 if (CanPulseGuide())
643                 {
644                     msg = _("PHD2 is not able to make sufficient corrections in Dec.  If you have just done a meridian flip, "
645                             "check to see if the 'Reverse Dec output option' on the Advanced Dialog guiding tab is wrong.  Otherwise, "
646                             "check for cable snags, try re-doing your calibration, and check for problems with the mount mechanics.");
647                 }
648                 else
649                 {
650                     msg = _("PHD2 is not able to make sufficient corrections in Dec.  Check for cable snags, try re-doing your calibration and "
651                             "confirm the ST-4 cable is working properly.");
652                 }
653             }
654             pFrame->SuppressableAlert(LimitReachedWarningKey(axis), msg, SuppressLimitReachedWarning, axis, false, wxICON_INFORMATION);
655         }
656         else                              // Max duration has been decreased by user, start by recommending use of default value
657         {
658             wxString s = axis == GUIDE_RA ? _("Max RA Duration setting") : _("Max Dec Duration setting");
659             pFrame->SuppressableAlert(LimitReachedWarningKey(axis),
660                 wxString::Format(_("Your %s is preventing PHD from making adequate corrections to keep the guide star locked. "
661                                    "Try restoring %s to its default value to allow PHD2 to make larger corrections."), s, s),
662                     SuppressLimitReachedWarning, axis, false, wxICON_INFORMATION);
663         }
664     }
665     else                       // Already at maximum allowed value
666     {
667         wxString which_axis = axis == GUIDE_RA ? _("RA") : _("Dec");
668         pFrame->SuppressableAlert(LimitReachedWarningKey(axis),
669             wxString::Format(_("Even using the maximum moves, PHD2 can't properly correct for the large guide star movements in %s. "
670                 "Guiding will be impaired until you can eliminate the source of these problems."), which_axis),
671             SuppressLimitReachedWarning, axis, false, wxICON_INFORMATION);
672     }
673 }
674 
MoveAxis(GUIDE_DIRECTION direction,int duration,unsigned int moveOptions,MoveResultInfo * moveResult)675 Mount::MOVE_RESULT Scope::MoveAxis(GUIDE_DIRECTION direction, int duration, unsigned int moveOptions, MoveResultInfo *moveResult)
676 {
677     MOVE_RESULT result = MOVE_OK;
678     bool limitReached = false;
679 
680     try
681     {
682         Debug.Write(wxString::Format("MoveAxis(%s, %d, %s)\n", DirectionChar(direction), duration, DumpMoveOptionBits(moveOptions)));
683 
684         if (!m_guidingEnabled && (moveOptions & MOVEOPT_MANUAL) == 0)
685         {
686             throw THROW_INFO("Guiding disabled");
687         }
688 
689         // Compute the actual guide durations
690 
691         switch (direction)
692         {
693             case NORTH:
694             case SOUTH:
695 
696                 // Enforce dec guide mode and max duration for guide step (or deduced step) moves
697                 if (moveOptions & (MOVEOPT_ALGO_RESULT | MOVEOPT_ALGO_DEDUCE))
698                 {
699                     if ((m_decGuideMode == DEC_NONE) ||
700                         (direction == SOUTH && m_decGuideMode == DEC_NORTH) ||
701                         (direction == NORTH && m_decGuideMode == DEC_SOUTH))
702                     {
703                         duration = 0;
704                         Debug.Write("duration set to 0 by GuideMode\n");
705                     }
706 
707                     if (duration > m_maxDecDuration)
708                     {
709                         duration = m_maxDecDuration;
710                         Debug.Write(wxString::Format("duration set to %d by maxDecDuration\n", duration));
711                         limitReached = true;
712                     }
713 
714                     if (limitReached && direction == m_decLimitReachedDirection)
715                     {
716                         if (++m_decLimitReachedCount >= LIMIT_REACHED_WARN_COUNT)
717                             AlertLimitReached(duration, GUIDE_DEC);
718                     }
719                     else
720                         m_decLimitReachedCount = 0;
721 
722                     if (limitReached)
723                         m_decLimitReachedDirection = direction;
724                     else
725                         m_decLimitReachedDirection = NONE;
726                 }
727                 break;
728             case EAST:
729             case WEST:
730 
731                 // Enforce max duration for guide step (or deduced step) moves
732                 if (moveOptions & (MOVEOPT_ALGO_RESULT | MOVEOPT_ALGO_DEDUCE))
733                 {
734                     if (duration > m_maxRaDuration)
735                     {
736                         duration = m_maxRaDuration;
737                         Debug.Write(wxString::Format("duration set to %d by maxRaDuration\n", duration));
738                         limitReached = true;
739                     }
740 
741                     if (limitReached && direction == m_raLimitReachedDirection)
742                     {
743                         if (++m_raLimitReachedCount >= LIMIT_REACHED_WARN_COUNT)
744                             AlertLimitReached(duration, GUIDE_RA);
745                     }
746                     else
747                         m_raLimitReachedCount = 0;
748 
749                     if (limitReached)
750                         m_raLimitReachedDirection = direction;
751                     else
752                         m_raLimitReachedDirection = NONE;
753                 }
754                 break;
755 
756             case NONE:
757                 break;
758         }
759 
760         // Actually do the guide
761         if (duration > 0)
762         {
763             result = Guide(direction, duration);
764             if (result != MOVE_OK)
765             {
766                 throw ERROR_INFO("guide failed");
767             }
768         }
769     }
770     catch (const wxString& Msg)
771     {
772         POSSIBLY_UNUSED(Msg);
773         if (result == MOVE_OK)
774             result = MOVE_ERROR;
775         duration = 0;
776     }
777 
778     Debug.Write(wxString::Format("Move returns status %d, amount %d\n", result, duration));
779 
780     if (moveResult)
781     {
782         moveResult->amountMoved = duration;
783         moveResult->limited = limitReached;
784     }
785 
786     return result;
787 }
788 
CalibrationWarningKey(CalibrationIssueType etype)789 static wxString CalibrationWarningKey(CalibrationIssueType etype)
790 {
791     wxString qual;
792     switch (etype)
793     {
794     case CI_Angle:
795         qual = "Angle";
796         break;
797     case CI_Different:
798         qual = "Diff";
799         break;
800     case CI_Steps:
801         qual = "Steps";
802         break;
803     case CI_Rates:
804         qual = "Rates";
805         break;
806     case CI_None:
807         qual = "Bogus";
808         break;
809 
810     }
811     wxString rtn = wxString::Format("/Confirm/%d/CalWarning_%s", pConfig->GetCurrentProfileId(), qual);
812     return rtn;
813 }
814 
SetCalibrationWarning(CalibrationIssueType etype,bool val)815 void Scope::SetCalibrationWarning(CalibrationIssueType etype, bool val)
816 {
817     pConfig->Global.SetBoolean(CalibrationWarningKey(etype), val);
818 }
819 
820 // Generic hook for "details" button in calibration sanity check alert
ShowCalibrationIssues(long scopeptr)821 static void ShowCalibrationIssues(long scopeptr)
822 {
823     Scope *pscope = reinterpret_cast<Scope *>(scopeptr);
824     pscope->HandleSanityCheckDialog();
825 }
826 
827 // Handle the "details" dialog for the calibration sanity check
HandleSanityCheckDialog()828 void Scope::HandleSanityCheckDialog()
829 {
830     if (pFrame->pCalSanityCheckDlg)
831         pFrame->pCalSanityCheckDlg->Destroy();
832 
833     pFrame->pCalSanityCheckDlg = new CalSanityDialog(pFrame, m_prevCalibration, m_prevCalibrationDetails, m_lastCalibrationIssue);
834     pFrame->pCalSanityCheckDlg->Show();
835 }
836 
837 // Do some basic sanity checking on the just-completed calibration, looking for things that are fishy.  Do the checking in the order of
838 // importance/confidence, since we only alert about a single condition
SanityCheckCalibration(const Calibration & oldCal,const CalibrationDetails & oldDetails)839 void Scope::SanityCheckCalibration(const Calibration& oldCal, const CalibrationDetails& oldDetails)
840 {
841     Calibration newCal;
842     GetLastCalibration(&newCal);
843 
844     CalibrationDetails newDetails;
845     LoadCalibrationDetails(&newDetails);
846 
847     m_lastCalibrationIssue = CI_None;
848     int xSteps = newDetails.raStepCount;
849     int ySteps = newDetails.decStepCount;
850 
851     wxString detailInfo;
852 
853     // Too few steps
854     if (xSteps < CAL_ALERT_MINSTEPS || (ySteps < CAL_ALERT_MINSTEPS && ySteps > 0))            // Dec guiding might be disabled
855     {
856         m_lastCalibrationIssue = CI_Steps;
857         detailInfo = wxString::Format("Actual RA calibration steps = %d, Dec calibration steps = %d", xSteps, ySteps);
858     }
859     else
860     {
861         // Non-orthogonal RA/Dec axes
862         double nonOrtho = degrees(fabs(fabs(norm_angle(newCal.xAngle - newCal.yAngle)) - M_PI / 2.));         // Delta from the nearest multiple of 90 degrees
863         if (nonOrtho >  CAL_ALERT_ORTHOGONALITY_TOLERANCE)
864         {
865             m_lastCalibrationIssue = CI_Angle;
866             detailInfo = wxString::Format("Non-orthogonality = %0.3f", nonOrtho);
867         }
868         else
869         {
870             // RA/Dec rates should be related by cos(dec) but don't check if Dec is too high or Dec guiding is disabled.  Also don't check if DecComp
871             // is disabled because a Sitech controller might be monkeying around with the apparent rates
872             if (newCal.declination != UNKNOWN_DECLINATION && newCal.yRate != CALIBRATION_RATE_UNCALIBRATED &&
873                 fabs(newCal.declination) <= Scope::DEC_COMP_LIMIT && DecCompensationEnabled())
874             {
875                 double expectedRatio = cos(newCal.declination);
876                 double speedRatio;
877                 if (newDetails.raGuideSpeed > 0.)                   // for mounts that may have different guide speeds on RA and Dec axes
878                     speedRatio = newDetails.decGuideSpeed / newDetails.raGuideSpeed;
879                 else
880                     speedRatio = 1.0;
881                 double actualRatio = newCal.xRate * speedRatio / newCal.yRate;
882                 if (fabs(expectedRatio - actualRatio) > CAL_ALERT_AXISRATES_TOLERANCE)
883                 {
884                     m_lastCalibrationIssue = CI_Rates;
885                     detailInfo = wxString::Format("Expected ratio at dec=%0.1f is %0.3f, actual is %0.3f", degrees(newCal.declination), expectedRatio, actualRatio);
886                 }
887             }
888         }
889 
890         // Finally check for a significantly different result but don't be stupid - ignore differences if the configuration looks quite different
891         // Can't do straight equality checks because of rounding - the "old" values have passed through the registry get/set routines
892         if (m_lastCalibrationIssue == CI_None && oldCal.isValid &&
893             fabs(oldDetails.imageScale - newDetails.imageScale) < 0.1 && (fabs(degrees(oldCal.xAngle - newCal.xAngle)) < 5.0))
894         {
895             if (newCal.yRate != CALIBRATION_RATE_UNCALIBRATED && oldCal.yRate != CALIBRATION_RATE_UNCALIBRATED)
896             {
897                 double newDecRate = newCal.yRate;
898                 if (newDecRate != 0.)
899                 {
900                     if (fabs(1.0 - (oldCal.yRate / newDecRate)) > CAL_ALERT_DECRATE_DIFFERENCE)
901                     {
902                         m_lastCalibrationIssue = CI_Different;
903                         detailInfo = wxString::Format("Current/previous Dec rate ratio is %0.3f", oldCal.yRate / newDecRate);
904                     }
905                 }
906             }
907         }
908     }
909 
910     if (m_lastCalibrationIssue != CI_None)
911     {
912         wxString alertMsg;
913 
914         FlagCalibrationIssue(newDetails, m_lastCalibrationIssue);
915         switch (m_lastCalibrationIssue)
916         {
917         case CI_Steps:
918             alertMsg = _("Advisory: Calibration completed but few guide steps were used, so accuracy is questionable");
919             break;
920         case CI_Angle:
921             alertMsg = _("Advisory: Calibration completed but RA/Dec axis angles are questionable and guiding may be impaired");
922             break;
923         case CI_Different:
924             alertMsg = _("Advisory: This calibration is substantially different from the previous one - have you changed configurations?");
925             break;
926         case CI_Rates:
927             alertMsg = _("Advisory: Calibration completed but RA and Dec rates vary by an unexpected amount (often caused by large Dec backlash)");
928         default:
929             break;
930         }
931 
932         // Suppression of calibration alerts is handled in the 'Details' dialog - a special case
933         if (pConfig->Global.GetBoolean(CalibrationWarningKey(m_lastCalibrationIssue), true))        // User hasn't disabled this type of alert
934         {
935             // Generate alert with 'Help' button that will lead to trouble-shooting section
936             pFrame->Alert(alertMsg, 0,
937                 _("Details..."), ShowCalibrationIssues, (long)this, true);
938         }
939         else
940         {
941             Debug.Write(wxString::Format("Alert detected in scope calibration but not shown to user - suppressed message was: %s\n", alertMsg));
942         }
943 
944         Debug.Write(wxString::Format("Calibration alert details: %s\n", detailInfo));
945     }
946     else
947     {
948         Debug.Write("Calibration passed sanity checks...\n");
949     }
950 }
951 
ClearCalibration()952 void Scope::ClearCalibration()
953 {
954     Mount::ClearCalibration();
955 
956     m_calibrationState = CALIBRATION_STATE_CLEARED;
957 }
958 
959 // Handle gross errors in cal_step duration when user changes mount guide speeds or binning.
CheckCalibrationDuration(int currDuration)960 void Scope::CheckCalibrationDuration(int currDuration)
961 {
962     CalibrationDetails calDetails;
963     LoadCalibrationDetails(&calDetails);
964 
965     bool binningChange = pCamera->Binning != calDetails.origBinning;
966 
967     // if binning changed, may need to update the calibration distance
968     if (binningChange)
969     {
970         int prevDistance = GetCalibrationDistance();
971         int newDistance = CalstepDialog::GetCalibrationDistance(pFrame->GetFocalLength(), pCamera->GetCameraPixelSize(),
972             pCamera->Binning);
973 
974         if (newDistance != prevDistance)
975         {
976             Debug.Write(wxString::Format("CalDistance adjusted at start of calibration from %d to %d because of binning change\n",
977                 prevDistance, newDistance));
978 
979             SetCalibrationDistance(newDistance);
980         }
981     }
982 
983     double raSpd;
984     double decSpd;
985     bool haveRates = !pPointingSource->GetGuideRates(&raSpd, &decSpd);          // units of degrees/sec as in ASCOM
986 
987     double currSpd = 0.0021;           // 0.5x sidereal, eliminate compiler warning
988     if (haveRates)
989     {
990         double const siderealSecsPerSec = 0.9973;
991         currSpd = wxMax(raSpd, decSpd) * 3600.0 / (15.0 * siderealSecsPerSec);          // fractional multiple of sidereal rate
992         if (currSpd >= 0.1 && currSpd <= 1.2)                                           // allow for rounding on 1x sidereal
993             haveRates = true;
994         else
995         {
996             Debug.Write("Mount driver is reporting bogus guide speeds\n");
997             haveRates = false;
998         }
999     }
1000 
1001     // Don't check the step size on very first calibration and don't adjust if the reported mount guide speeds are bogus
1002     if (!haveRates || calDetails.raGuideSpeed <= 0)
1003         return;
1004 
1005     bool refineStepSize = binningChange || (fabs(1.0 - raSpd / calDetails.raGuideSpeed) > 0.05);     // binning change or speed change of > 5%
1006     if (!refineStepSize)
1007         return;
1008 
1009     int rslt;
1010     CalstepDialog::GetCalibrationStepSize(pFrame->GetFocalLength(), pCamera->GetCameraPixelSize(),
1011         pCamera->Binning, currSpd, CalstepDialog::DEFAULT_STEPS, 0.0, GetCalibrationDistance(), 0, &rslt);
1012 
1013     if (rslt == currDuration)
1014         return;
1015 
1016     wxString why = binningChange ? " binning " : " mount guide speed ";
1017     Debug.Write(wxString::Format("CalDuration adjusted at start of calibration from %d to %d because of %s change\n",
1018         currDuration, rslt, why));
1019 
1020     SetCalibrationDuration(rslt);
1021 }
1022 
BeginCalibration(const PHD_Point & currentLocation)1023 bool Scope::BeginCalibration(const PHD_Point& currentLocation)
1024 {
1025     bool bError = false;
1026 
1027     try
1028     {
1029         if (!IsConnected())
1030         {
1031             throw ERROR_INFO("Not connected");
1032         }
1033 
1034         if (!currentLocation.IsValid())
1035         {
1036             throw ERROR_INFO("Must have a valid lock position");
1037         }
1038 
1039         CheckCalibrationDuration(m_calibrationDuration);          // Make sure guide speeds or binning haven't changed underneath us
1040         ClearCalibration();
1041         m_calibrationSteps = 0;
1042         m_calibrationInitialLocation = currentLocation;
1043         m_calibrationStartingLocation.Invalidate();
1044         m_calibrationStartingCoords.Invalidate();
1045         m_calibrationState = CALIBRATION_STATE_GO_WEST;
1046         m_calibrationDetails.raSteps.clear();
1047         m_calibrationDetails.decSteps.clear();
1048         m_raSteps = 0;
1049         m_decSteps = 0;
1050         m_calibrationDetails.lastIssue = CI_None;
1051         m_eastAlertShown = false;
1052     }
1053     catch (const wxString& Msg)
1054     {
1055         POSSIBLY_UNUSED(Msg);
1056         bError = true;
1057     }
1058 
1059     return bError;
1060 }
1061 
SetCalibration(const Calibration & cal)1062 void Scope::SetCalibration(const Calibration& cal)
1063 {
1064     m_calibration = cal;
1065     Mount::SetCalibration(cal);
1066 }
1067 
SetCalibrationDetails(const CalibrationDetails & calDetails,double xAngle,double yAngle,double binning)1068 void Scope::SetCalibrationDetails(const CalibrationDetails& calDetails, double xAngle, double yAngle, double binning)
1069 {
1070     m_calibrationDetails = calDetails;
1071 
1072     double ra_rate;
1073     double dec_rate;
1074     if (pPointingSource->GetGuideRates(&ra_rate, &dec_rate))        // true means error
1075     {
1076         ra_rate = -1.0;
1077         dec_rate = -1.0;
1078     }
1079 
1080     m_calibrationDetails.raGuideSpeed = ra_rate;
1081     m_calibrationDetails.decGuideSpeed = dec_rate;
1082     m_calibrationDetails.focalLength = pFrame->GetFocalLength();
1083     m_calibrationDetails.imageScale = pFrame->GetCameraPixelScale();
1084     m_calibrationDetails.orthoError = degrees(fabs(fabs(norm_angle(xAngle - yAngle)) - M_PI / 2.));         // Delta from the nearest multiple of 90 degrees
1085     m_calibrationDetails.origBinning = binning;
1086     m_calibrationDetails.origTimestamp = wxDateTime::Now().Format();
1087     m_calibrationDetails.origPierSide = pPointingSource->SideOfPier();
1088 
1089     SaveCalibrationDetails(m_calibrationDetails);
1090 }
1091 
FlagCalibrationIssue(const CalibrationDetails & calDetails,CalibrationIssueType issue)1092 void Scope::FlagCalibrationIssue(const CalibrationDetails& calDetails, CalibrationIssueType issue)
1093 {
1094     m_calibrationDetails = calDetails;
1095     m_calibrationDetails.lastIssue = issue;
1096 
1097     SaveCalibrationDetails(m_calibrationDetails);
1098 }
1099 
IsCalibrated() const1100 bool Scope::IsCalibrated() const
1101 {
1102     if (!Mount::IsCalibrated())
1103         return false;
1104 
1105     switch (m_decGuideMode)
1106     {
1107     case DEC_NONE:
1108         return true;
1109     case DEC_AUTO:
1110     case DEC_NORTH:
1111     case DEC_SOUTH:
1112         {
1113             bool have_ns_calibration = m_calibration.yRate != CALIBRATION_RATE_UNCALIBRATED;
1114             return have_ns_calibration;
1115         }
1116     default:
1117         assert(false);
1118         return true;
1119     }
1120 }
1121 
EnableDecCompensation(bool enable)1122 void Scope::EnableDecCompensation(bool enable)
1123 {
1124     m_useDecCompensation = enable;
1125     wxString prefix = "/" + GetMountClassName();
1126     pConfig->Profile.SetBoolean(prefix + "/UseDecComp", enable);
1127 }
1128 
CalibrationTotDistance()1129 int Scope::CalibrationTotDistance()
1130 {
1131     return GetCalibrationDistance();
1132 }
1133 
1134 // Convert camera coords to mount coords
MountCoords(const PHD_Point & cameraVector,double xCalibAngle,double yCalibAngle)1135 static PHD_Point MountCoords(const PHD_Point& cameraVector, double xCalibAngle, double yCalibAngle)
1136 {
1137     double hyp = cameraVector.Distance();
1138     double cameraTheta = cameraVector.Angle();
1139     double yAngleError = norm_angle((xCalibAngle - yCalibAngle) + M_PI / 2);
1140     double xAngle = cameraTheta - xCalibAngle;
1141     double yAngle = cameraTheta - (xCalibAngle + yAngleError);
1142     return PHD_Point(hyp * cos(xAngle), hyp * sin(yAngle));
1143 }
1144 
GetRADecCoordinates(PHD_Point * coords)1145 static void GetRADecCoordinates(PHD_Point *coords)
1146 {
1147     double ra, dec, lst;
1148     bool err = pPointingSource->GetCoordinates(&ra, &dec, &lst);
1149     if (err)
1150         coords->Invalidate();
1151     else
1152         coords->SetXY(ra, dec);
1153 }
1154 
DecBacklashAlertKey()1155 static wxString DecBacklashAlertKey()
1156 {
1157     // we want the key to be under "/Confirm" so ConfirmDialog::ResetAllDontAskAgain() resets it, but we also want the setting to be per-profile
1158     return wxString::Format("/Confirm/%d/DecBacklashWarningEnabled", pConfig->GetCurrentProfileId());
1159 }
1160 
SuppressDecBacklashAlert(long)1161 static void SuppressDecBacklashAlert(long)
1162 {
1163     pConfig->Global.SetBoolean(DecBacklashAlertKey(), false);
1164 }
1165 
CalibrationStatus(CalibrationStepInfo & info,const wxString & msg)1166 static void CalibrationStatus(CalibrationStepInfo& info, const wxString& msg)
1167 {
1168     info.msg = msg;
1169     pFrame->StatusMsg(info.msg);
1170     EvtServer.NotifyCalibrationStep(info);
1171 }
1172 
UpdateCalibrationState(const PHD_Point & currentLocation)1173 bool Scope::UpdateCalibrationState(const PHD_Point& currentLocation)
1174 {
1175     bool bError = false;
1176 
1177     try
1178     {
1179         if (!m_calibrationStartingLocation.IsValid())
1180         {
1181             m_calibrationStartingLocation = currentLocation;
1182             GetRADecCoordinates(&m_calibrationStartingCoords);
1183 
1184             Debug.Write(wxString::Format("Scope::UpdateCalibrationstate: starting location = %.2f,%.2f coords = %s\n",
1185                 currentLocation.X, currentLocation.Y,
1186                 m_calibrationStartingCoords.IsValid() ?
1187                     wxString::Format("%.2f,%.1f", m_calibrationStartingCoords.X, m_calibrationStartingCoords.Y) : wxString("N/A")));
1188         }
1189 
1190         double dX = m_calibrationStartingLocation.dX(currentLocation);
1191         double dY = m_calibrationStartingLocation.dY(currentLocation);
1192         double dist = m_calibrationStartingLocation.Distance(currentLocation);
1193         double dist_crit = GetCalibrationDistance();
1194         double blDelta;
1195         double blCumDelta;
1196         double nudge_amt;
1197         double nudgeDirCosX;
1198         double nudgeDirCosY;
1199         double cos_theta;
1200         double theta;
1201         double southDistMoved;
1202         double northDistMoved;
1203         double southAngle;
1204 
1205         switch (m_calibrationState)
1206         {
1207             case CALIBRATION_STATE_CLEARED:
1208                 assert(false);
1209                 break;
1210 
1211             case CALIBRATION_STATE_GO_WEST: {
1212 
1213                 // step number in the log is the step that just finished
1214                 CalibrationStepInfo info(this, _T("West"), m_calibrationSteps, dX, dY, currentLocation, dist);
1215                 GuideLog.CalibrationStep(info);
1216                 m_calibrationDetails.raSteps.push_back(wxRealPoint(dX, dY));
1217 
1218                 if (dist < dist_crit)
1219                 {
1220                     if (m_calibrationSteps++ > MAX_CALIBRATION_STEPS)
1221                     {
1222                         wxString msg(wxTRANSLATE("RA Calibration Failed: star did not move enough"));
1223                         const wxString& translated(wxGetTranslation(msg));
1224                         pFrame->Alert(translated);
1225                         GuideLog.CalibrationFailed(this, msg);
1226                         EvtServer.NotifyCalibrationFailed(this, msg);
1227                         throw ERROR_INFO("RA calibration failed");
1228                     }
1229                     CalibrationStatus(info, wxString::Format(_("West step %3d, dist=%4.1f"), m_calibrationSteps, dist));
1230                     pFrame->ScheduleAxisMove(this, WEST, m_calibrationDuration, MOVEOPTS_CALIBRATION_MOVE);
1231                     break;
1232                 }
1233 
1234                 // West calibration complete
1235 
1236                 m_calibration.xAngle = m_calibrationStartingLocation.Angle(currentLocation);
1237                 m_calibration.xRate = dist / (m_calibrationSteps * m_calibrationDuration);
1238 
1239                 m_calibration.raGuideParity = GUIDE_PARITY_UNKNOWN;
1240                 if (m_calibrationStartingCoords.IsValid())
1241                 {
1242                     PHD_Point endingCoords;
1243                     GetRADecCoordinates(&endingCoords);
1244                     if (endingCoords.IsValid())
1245                     {
1246                         // true westward motion decreases RA
1247                         double ONE_ARCSEC = 24.0 / (360. * 60. * 60.); // hours
1248                         double dra = endingCoords.X - m_calibrationStartingCoords.X;
1249                         if (dra < -ONE_ARCSEC)
1250                             m_calibration.raGuideParity = GUIDE_PARITY_EVEN;
1251                         else if (dra > ONE_ARCSEC)
1252                             m_calibration.raGuideParity = GUIDE_PARITY_ODD;
1253                     }
1254                 }
1255 
1256                 Debug.Write(wxString::Format("WEST calibration completes with steps=%d angle=%.1f rate=%.3f parity=%d\n",
1257                     m_calibrationSteps, degrees(m_calibration.xAngle), m_calibration.xRate * 1000.0, m_calibration.raGuideParity));
1258 
1259                 m_raSteps = m_calibrationSteps;
1260                 GuideLog.CalibrationDirectComplete(this, "West", m_calibration.xAngle, m_calibration.xRate, m_calibration.raGuideParity);
1261 
1262                 // for GO_EAST m_recenterRemaining contains the total remaining duration.
1263                 // Choose the largest pulse size that will not lose the guide star or exceed
1264                 // the user-specified max pulse
1265 
1266                 m_recenterRemaining = m_calibrationSteps * m_calibrationDuration;
1267 
1268                 if (pFrame->pGuider->IsFastRecenterEnabled())
1269                 {
1270                     m_recenterDuration = (int)floor((double)pFrame->pGuider->GetMaxMovePixels() / m_calibration.xRate);
1271                     if (m_recenterDuration > m_maxRaDuration)
1272                         m_recenterDuration = m_maxRaDuration;
1273                     if (m_recenterDuration < m_calibrationDuration)
1274                         m_recenterDuration = m_calibrationDuration;
1275                 }
1276                 else
1277                     m_recenterDuration = m_calibrationDuration;
1278 
1279                 m_calibrationSteps = DIV_ROUND_UP(m_recenterRemaining, m_recenterDuration);
1280                 m_calibrationState = CALIBRATION_STATE_GO_EAST;
1281                 m_eastStartingLocation = currentLocation;
1282 
1283                 // fall through
1284                 Debug.Write("Falling Through to state GO_EAST\n");
1285             }
1286 
1287             case CALIBRATION_STATE_GO_EAST: {
1288 
1289                 CalibrationStepInfo info(this, _T("East"), m_calibrationSteps, dX, dY, currentLocation, dist);
1290                 GuideLog.CalibrationStep(info);
1291                 m_calibrationDetails.raSteps.push_back(wxRealPoint(dX, dY));
1292 
1293                 if (m_recenterRemaining > 0)
1294                 {
1295                     int duration = m_recenterDuration;
1296                     if (duration > m_recenterRemaining)
1297                         duration = m_recenterRemaining;
1298 
1299                     CalibrationStatus(info, wxString::Format(_("East step %3d, dist=%4.1f"), m_calibrationSteps, dist));
1300 
1301                     m_recenterRemaining -= duration;
1302                     --m_calibrationSteps;
1303                     m_lastLocation = currentLocation;
1304 
1305                     pFrame->ScheduleAxisMove(this, EAST, duration, MOVEOPTS_CALIBRATION_MOVE);
1306                     break;
1307                 }
1308 
1309                 // If not pulse-guiding check for obvious guide cable problem and no useful east moves
1310                 if (!CanPulseGuide())
1311                 {
1312                     double eastDistMoved = m_eastStartingLocation.Distance(currentLocation);
1313                     double westDistMoved = m_calibrationStartingLocation.Distance(m_eastStartingLocation);
1314                     double eastAngle = m_eastStartingLocation.Angle(currentLocation);
1315                     // Want a significant east movement that re-traces the west vector to within 30 degrees
1316                     if (fabs(eastDistMoved) < 0.25 * westDistMoved || fabs(norm_angle(eastAngle - (m_calibration.xAngle + M_PI))) > radians(30))
1317                     {
1318                         wxString msg(wxTRANSLATE("Advisory: Little or no east movement was measured, so guiding will probably be impaired. "
1319                             "Check the guide cable and use the Manual Guide tool to confirm basic operation of the mount."));
1320                         const wxString& translated(wxGetTranslation(msg));
1321                         pFrame->Alert(translated, 0, wxEmptyString, 0, 0, true);
1322                         Debug.Write("Calibration alert: " + msg + "\n");
1323                         m_eastAlertShown = true;
1324                     }
1325                 }
1326                 // setup for clear backlash
1327 
1328                 m_calibrationSteps = 0;
1329                 dist = dX = dY = 0.0;
1330                 m_calibrationStartingLocation = currentLocation;
1331 
1332                 if (m_decGuideMode == DEC_NONE)
1333                 {
1334                     Debug.Write("Skipping Dec calibration as DecGuideMode == NONE\n");
1335                     m_calibrationState = CALIBRATION_STATE_COMPLETE;
1336                     m_calibration.yAngle = norm_angle(m_calibration.xAngle + M_PI / 2.); // choose an arbitrary angle perpendicular to xAngle
1337                     // indicate lack of Dec calibration data, see Scope::IsCalibrated.
1338                     m_calibration.yRate = CALIBRATION_RATE_UNCALIBRATED;
1339                     m_calibration.decGuideParity = GUIDE_PARITY_UNKNOWN;
1340                     break;
1341                 }
1342 
1343                 m_calibrationState = CALIBRATION_STATE_CLEAR_BACKLASH;
1344                 m_blMarkerPoint = currentLocation;
1345                 GetRADecCoordinates(&m_calibrationStartingCoords);
1346                 m_blExpectedBacklashStep = m_calibration.xRate * m_calibrationDuration * 0.6;
1347 
1348                 double RASpeed;
1349                 double DecSpeed;
1350                 if (!pPointingSource->GetGuideRates(&RASpeed, &DecSpeed) &&
1351                     RASpeed != 0.0 && RASpeed != DecSpeed)
1352                 {
1353                     m_blExpectedBacklashStep *= DecSpeed / RASpeed;
1354                 }
1355 
1356                 m_blMaxClearingPulses = wxMax(8, BL_MAX_CLEARING_TIME / m_calibrationDuration);
1357                 m_blLastCumDistance = 0;
1358                 m_blAcceptedMoves = 0;
1359                 Debug.Write(wxString::Format("Backlash: Looking for 3 moves of %0.1f px, max attempts = %d\n", m_blExpectedBacklashStep, m_blMaxClearingPulses));
1360                 // fall through
1361                 Debug.Write("Falling Through to state CLEAR_BACKLASH\n");
1362             }
1363 
1364             case CALIBRATION_STATE_CLEAR_BACKLASH: {
1365 
1366                 CalibrationStepInfo info(this, _T("Backlash"), m_calibrationSteps, dX, dY, currentLocation, dist);
1367                 GuideLog.CalibrationStep(info);
1368                 blDelta = m_blMarkerPoint.Distance(currentLocation);
1369                 blCumDelta = dist;
1370 
1371                 // Want to see the mount moving north for 3 moves of >= expected distance pixels without any direction reversals
1372                 if (m_calibrationSteps == 0)
1373                 {
1374                     // Get things moving with the first clearing pulse
1375                     Debug.Write(wxString::Format("Backlash: Starting north clearing using pulse width of %d\n",
1376                         m_calibrationDuration));
1377                     pFrame->ScheduleAxisMove(this, NORTH, m_calibrationDuration, MOVEOPTS_CALIBRATION_MOVE);
1378                     m_calibrationSteps = 1;
1379                     CalibrationStatus(info, _("Clearing backlash step 1"));
1380                     break;
1381                 }
1382 
1383                 if (blDelta >= m_blExpectedBacklashStep)
1384                 {
1385                     if (m_blAcceptedMoves == 0 || (blCumDelta > m_blLastCumDistance))    // Just starting or still moving in same direction
1386                     {
1387                         m_blAcceptedMoves++;
1388                         Debug.Write(wxString::Format("Backlash: Accepted clearing move of %0.1f\n", blDelta));
1389                     }
1390                     else
1391                     {
1392                         m_blAcceptedMoves = 0;            // Reset on a direction reversal
1393                         Debug.Write(wxString::Format("Backlash: Rejected clearing move of %0.1f, direction reversal\n", blDelta));
1394                     }
1395                 }
1396                 else
1397                 {
1398                     if (blCumDelta < m_blLastCumDistance)
1399                     {
1400                         m_blAcceptedMoves = 0;
1401                         Debug.Write(wxString::Format("Backlash: Rejected small direction reversal of %0.1f px\n", blDelta));
1402                     }
1403                     else
1404                         Debug.Write(wxString::Format("Backlash: Rejected small move of %0.1f px\n", blDelta));
1405                 }
1406 
1407                 if (m_blAcceptedMoves < BL_BACKLASH_MIN_COUNT)                    // More work to do
1408                 {
1409                     if (m_calibrationSteps < m_blMaxClearingPulses && blCumDelta < dist_crit)
1410                     {
1411                         // Still have attempts left, haven't moved the star by 25 px yet
1412                         pFrame->ScheduleAxisMove(this, NORTH, m_calibrationDuration, MOVEOPTS_CALIBRATION_MOVE);
1413                         m_calibrationSteps++;
1414                         m_blMarkerPoint = currentLocation;
1415                         GetRADecCoordinates(&m_calibrationStartingCoords);
1416                         m_blLastCumDistance = blCumDelta;
1417                         CalibrationStatus(info, wxString::Format(_("Clearing backlash step %3d"), m_calibrationSteps));
1418                         Debug.Write(wxString::Format("Backlash: %s, Last Delta = %0.2f px, CumDistance = %0.2f px\n", info.msg, blDelta, blCumDelta));
1419                         break;
1420                     }
1421                     else
1422                     {
1423                         // Used up all our attempts - might be ok or not
1424                         if (blCumDelta >= BL_MIN_CLEARING_DISTANCE)
1425                         {
1426                             // Exhausted all the clearing pulses without reaching the goal - but we did move the mount > 3 px (same as PHD1)
1427                             m_calibrationSteps = 0;
1428                             m_calibrationStartingLocation = currentLocation;
1429                             dX = 0;
1430                             dY = 0;
1431                             dist = 0;
1432                             Debug.Write("Backlash: Reached clearing limit but total displacement > 3px - proceeding with calibration\n");
1433                         }
1434                         else
1435                         {
1436                             wxString msg(wxTRANSLATE("Backlash Clearing Failed: star did not move enough"));
1437                             const wxString& translated(wxGetTranslation(msg));
1438                             pFrame->Alert(translated);
1439                             GuideLog.CalibrationFailed(this, msg);
1440                             EvtServer.NotifyCalibrationFailed(this, msg);
1441                             throw ERROR_INFO("Clear backlash failed");
1442                         }
1443                     }
1444                 }
1445                 else        //Got our 3 moves, move ahead
1446                 {
1447                     // We know the last backlash clearing move was big enough - include that as a north calibration move
1448 
1449                     // log the starting point
1450                     CalibrationStepInfo info(this, _T("North"), 0, 0.0, 0.0, m_blMarkerPoint, 0.0);
1451                     GuideLog.CalibrationStep(info);
1452                     m_calibrationDetails.decSteps.push_back(wxRealPoint(0.0, 0.0));
1453 
1454                     m_calibrationSteps = 1;
1455                     m_calibrationStartingLocation = m_blMarkerPoint;
1456                     dX = m_blMarkerPoint.dX(currentLocation);
1457                     dY = m_blMarkerPoint.dY(currentLocation);
1458                     dist = m_blMarkerPoint.Distance(currentLocation);
1459                     Debug.Write("Backlash: Got 3 acceptable moves, using last move as step 1 of N calibration\n");
1460                 }
1461 
1462                 m_blDistanceMoved = m_blMarkerPoint.Distance(m_calibrationInitialLocation);     // Need this to set nudging limit
1463 
1464                 Debug.Write(wxString::Format("Backlash: North calibration moves starting at {%0.1f,%0.1f}, Offset = %0.1f px\n",
1465                     m_blMarkerPoint.X, m_blMarkerPoint.Y, m_blDistanceMoved));
1466                 Debug.Write(wxString::Format("Backlash: Total distance moved = %0.1f\n",
1467                     currentLocation.Distance(m_calibrationInitialLocation)));
1468 
1469                 m_calibrationState = CALIBRATION_STATE_GO_NORTH;
1470                 // falling through to start moving north
1471                 Debug.Write("Backlash: Falling Through to state GO_NORTH\n");
1472             }
1473 
1474             case CALIBRATION_STATE_GO_NORTH: {
1475 
1476                 CalibrationStepInfo info(this, _T("North"), m_calibrationSteps, dX, dY, currentLocation, dist);
1477                 GuideLog.CalibrationStep(info);
1478                 m_calibrationDetails.decSteps.push_back(wxRealPoint(dX, dY));
1479 
1480                 if (dist < dist_crit)
1481                 {
1482                     if (m_calibrationSteps++ > MAX_CALIBRATION_STEPS)
1483                     {
1484                         wxString msg(wxTRANSLATE("DEC Calibration Failed: star did not move enough"));
1485                         const wxString& translated(wxGetTranslation(msg));
1486                         pFrame->Alert(translated);
1487                         GuideLog.CalibrationFailed(this, msg);
1488                         EvtServer.NotifyCalibrationFailed(this, msg);
1489                         throw ERROR_INFO("Dec calibration failed");
1490                     }
1491                     CalibrationStatus(info, wxString::Format(_("North step %3d, dist=%4.1f"), m_calibrationSteps, dist));
1492                     pFrame->ScheduleAxisMove(this, NORTH, m_calibrationDuration, MOVEOPTS_CALIBRATION_MOVE);
1493                     break;
1494                 }
1495 
1496                 // note: this calculation is reversed from the ra calculation, because
1497                 // that one was calibrating WEST, but the angle is really relative
1498                 // to EAST
1499                 if (m_assumeOrthogonal)
1500                 {
1501                     double a1 = norm_angle(m_calibration.xAngle + M_PI / 2.);
1502                     double a2 = norm_angle(m_calibration.xAngle - M_PI / 2.);
1503                     double yAngle = currentLocation.Angle(m_calibrationStartingLocation);
1504                     m_calibration.yAngle = fabs(norm_angle(a1 - yAngle)) < fabs(norm_angle(a2 - yAngle)) ? a1 : a2;
1505                     double dec_dist = dist * cos(yAngle - m_calibration.yAngle);
1506                     m_calibration.yRate = dec_dist / (m_calibrationSteps * m_calibrationDuration);
1507 
1508                     Debug.Write(wxString::Format("Assuming orthogonal axes: measured Y angle = %.1f, X angle = %.1f, orthogonal = %.1f, %.1f, best = %.1f, dist = %.2f, dec_dist = %.2f\n",
1509                         degrees(yAngle), degrees(m_calibration.xAngle), degrees(a1), degrees(a2), degrees(m_calibration.yAngle), dist, dec_dist));
1510                 }
1511                 else
1512                 {
1513                     m_calibration.yAngle = currentLocation.Angle(m_calibrationStartingLocation);
1514                     m_calibration.yRate = dist / (m_calibrationSteps * m_calibrationDuration);
1515                 }
1516 
1517                 m_decSteps = m_calibrationSteps;
1518 
1519                 m_calibration.decGuideParity = GUIDE_PARITY_UNKNOWN;
1520                 if (m_calibrationStartingCoords.IsValid())
1521                 {
1522                     PHD_Point endingCoords;
1523                     GetRADecCoordinates(&endingCoords);
1524                     if (endingCoords.IsValid())
1525                     {
1526                         // real Northward motion increases Dec
1527                         double ONE_ARCSEC = 1.0 / (60. * 60.); // degrees
1528                         double ddec = endingCoords.Y - m_calibrationStartingCoords.Y;
1529                         if (ddec > ONE_ARCSEC)
1530                             m_calibration.decGuideParity = GUIDE_PARITY_EVEN;
1531                         else if (ddec < -ONE_ARCSEC)
1532                             m_calibration.decGuideParity = GUIDE_PARITY_ODD;
1533                     }
1534                 }
1535 
1536                 Debug.Write(wxString::Format("NORTH calibration completes with angle=%.1f rate=%.3f parity=%d\n",
1537                     degrees(m_calibration.yAngle), m_calibration.yRate * 1000.0, m_calibration.decGuideParity));
1538 
1539                 GuideLog.CalibrationDirectComplete(this, "North", m_calibration.yAngle, m_calibration.yRate, m_calibration.decGuideParity);
1540 
1541                 // for GO_SOUTH m_recenterRemaining contains the total remaining duration.
1542                 // Choose the largest pulse size that will not lose the guide star or exceed
1543                 // the user-specified max pulse
1544                 m_recenterRemaining = m_calibrationSteps * m_calibrationDuration;
1545 
1546                 if (pFrame->pGuider->IsFastRecenterEnabled())
1547                 {
1548                     m_recenterDuration = (int)floor(0.8 * (double)pFrame->pGuider->GetMaxMovePixels() / m_calibration.yRate);
1549                     if (m_recenterDuration > m_maxDecDuration)
1550                         m_recenterDuration = m_maxDecDuration;
1551                     if (m_recenterDuration < m_calibrationDuration)
1552                         m_recenterDuration = m_calibrationDuration;
1553                 }
1554                 else
1555                     m_recenterDuration = m_calibrationDuration;
1556 
1557                 m_calibrationSteps = DIV_ROUND_UP(m_recenterRemaining, m_recenterDuration);
1558                 m_calibrationState = CALIBRATION_STATE_GO_SOUTH;
1559                 m_southStartingLocation = currentLocation;
1560 
1561                 // fall through
1562                 Debug.Write("Falling Through to state GO_SOUTH\n");
1563             }
1564 
1565             case CALIBRATION_STATE_GO_SOUTH: {
1566 
1567                 CalibrationStepInfo info(this, _T("South"), m_calibrationSteps, dX, dY, currentLocation, dist);
1568                 GuideLog.CalibrationStep(info);
1569                 m_calibrationDetails.decSteps.push_back(wxRealPoint(dX, dY));
1570 
1571                 if (m_recenterRemaining > 0)
1572                 {
1573                     int duration = m_recenterDuration;
1574                     if (duration > m_recenterRemaining)
1575                         duration = m_recenterRemaining;
1576 
1577                     CalibrationStatus(info, wxString::Format(_("South step %3d, dist=%4.1f"), m_calibrationSteps, dist));
1578 
1579                     m_recenterRemaining -= duration;
1580                     --m_calibrationSteps;
1581 
1582                     pFrame->ScheduleAxisMove(this, SOUTH, duration, MOVEOPTS_CALIBRATION_MOVE);
1583                     break;
1584                 }
1585 
1586                 // Check for obvious guide cable problem and no useful south moves
1587                 southDistMoved = m_southStartingLocation.Distance(currentLocation);
1588                 northDistMoved = m_calibrationStartingLocation.Distance(m_southStartingLocation);
1589                 southAngle = currentLocation.Angle(m_southStartingLocation);
1590                 // Want a significant south movement that re-traces the north vector to within 30 degrees
1591                 if (fabs(southDistMoved) < 0.25 * northDistMoved || fabs(norm_angle(southAngle - (m_calibration.yAngle + M_PI))) > radians(30))
1592                 {
1593                     wxString msg;
1594                     if (!CanPulseGuide())
1595                     {
1596                         if (fabs(southDistMoved) < 0.10 * northDistMoved)
1597                             msg = wxTRANSLATE("Advisory: Calibration succeessful but little or no south movement was measured, so guiding will probably be impaired.\n "
1598                             "This is usually caused by a faulty guide cable or very large Dec backlash. \n"
1599                             "Check the guide cable and read the online Help for how to identify these types of problems (Manual Guide, Declination backlash).");
1600                         else
1601                             msg = wxTRANSLATE("Advisory: Calibration successful but little south movement was measured, so guiding will probably be impaired. \n"
1602                             "This is usually caused by very large Dec backlash or other problems with the mount mechanics. \n"
1603                             "Read the online Help for how to identify these types of problems (Manual Guide, Declination backlash).");
1604                     }
1605                     else
1606                         msg = wxTRANSLATE("Advisory: Calibration successful but little south movement was measured, so guiding may be impaired.\n "
1607                         "This is usually caused by very large Dec backlash or other problems with the mount mechanics. \n"
1608                         "Read the online help for how to deal with this type of problem (Declination backlash).");
1609 
1610                     //if (!m_eastAlertShown)
1611                     //{
1612                     //    const wxString& translated(wxGetTranslation(msg));
1613                     //    pFrame->SuppressableAlert(DecBacklashAlertKey(), translated, SuppressDecBacklashAlert, 0, true);
1614                     //}
1615                     Debug.Write("Omitted calibration alert: " + msg + "\n");
1616                 }
1617 
1618                 m_lastLocation = currentLocation;
1619                 // Compute the vector for the north moves we made - use it to make sure any nudging is going in the correct direction
1620                 // These are the direction cosines of the vector
1621                 m_northDirCosX = m_calibrationInitialLocation.dX(m_southStartingLocation) / m_calibrationInitialLocation.Distance(m_southStartingLocation);
1622                 m_northDirCosY = m_calibrationInitialLocation.dY(m_southStartingLocation) / m_calibrationInitialLocation.Distance(m_southStartingLocation);
1623                 //Debug.Write(wxString::Format("Nudge: InitStart:{%0.1f,%0.1f}, southStart:{%.1f,%0.1f}, north_l:%0.2f, north_m:%0.2f\n",
1624                 //    m_calibrationInitialLocation.X, m_calibrationInitialLocation.Y,
1625                 //    m_southStartingLocation.X, m_southStartingLocation.Y, m_northDirCosX, m_northDirCosY));
1626                 // Get magnitude and sign convention for the south moves we already made
1627                 m_totalSouthAmt = MountCoords(m_southStartingLocation - m_lastLocation, m_calibration.xAngle, m_calibration.yAngle).Y;
1628                 m_calibrationState = CALIBRATION_STATE_NUDGE_SOUTH;
1629                 m_calibrationSteps = 0;
1630                 // Fall through to nudging
1631                 Debug.Write("Falling Through to state CALIBRATION_STATE_NUDGE_SOUTH\n");
1632             }
1633 
1634             case CALIBRATION_STATE_NUDGE_SOUTH:
1635                 // Nudge further South on Dec, get within 2 px North/South of starting point, don't try more than 3 times and don't do nudging at all if
1636                 // we're starting too far away from the target
1637                 nudge_amt = currentLocation.Distance(m_calibrationInitialLocation);
1638                 // Compute the direction cosines for the expected nudge op
1639                 nudgeDirCosX = currentLocation.dX(m_calibrationInitialLocation) / nudge_amt;
1640                 nudgeDirCosY = currentLocation.dY(m_calibrationInitialLocation) / nudge_amt;
1641                 // Compute the angle between the nudge and north move vector - they should be reversed, i.e. something close to 180 deg
1642                 cos_theta = nudgeDirCosX * m_northDirCosX + nudgeDirCosY * m_northDirCosY;
1643                 theta = acos(cos_theta);
1644                 //Debug.Write(wxString::Format("Nudge: currLoc:{%0.1f,%0.1f}, m_nudgeDirCosX: %0.2f, nudgeDirCosY: %0.2f, cos_theta: %0.2f\n",
1645                 //    currentLocation.X, currentLocation.Y, nudgeDirCosX, nudgeDirCosY, cos_theta));
1646                 Debug.Write(wxString::Format("Nudge: theta = %0.2f\n", theta));
1647                 if (fabs(fabs(theta) * 180.0/M_PI - 180.0) < 40.0)      // We're going at least roughly in the right direction
1648                 {
1649                     if (m_calibrationSteps <= MAX_NUDGES && nudge_amt > NUDGE_TOLERANCE &&
1650                         nudge_amt < dist_crit + m_blDistanceMoved)
1651                     {
1652                         // Compute how much more south we need to go
1653                         double decAmt = MountCoords(currentLocation - m_calibrationInitialLocation, m_calibration.xAngle, m_calibration.yAngle).Y;
1654                         Debug.Write(wxString::Format("South nudging, decAmt = %.3f, Normal south moves = %.3f\n", decAmt, m_totalSouthAmt));
1655 
1656                         if (decAmt * m_totalSouthAmt > 0.0)           // still need to move south to reach target based on matching sign
1657                         {
1658                             decAmt = fabs(decAmt);           // Sign doesn't matter now, we're always moving south
1659                             decAmt = wxMin(decAmt, (double)pFrame->pGuider->GetMaxMovePixels());
1660                             int pulseAmt = (int)floor(decAmt / m_calibration.yRate);
1661                             if (pulseAmt > m_calibrationDuration)
1662                                 pulseAmt = m_calibrationDuration;               // Be conservative, use durations that pushed us north in the first place
1663                             Debug.Write(wxString::Format("Sending NudgeSouth pulse of duration %d ms\n", pulseAmt));
1664                             ++m_calibrationSteps;
1665                             CalibrationStepInfo info(this, _T("NudgeSouth"), m_calibrationSteps, dX, dY, currentLocation, dist);
1666                             CalibrationStatus(info, wxString::Format(_("Nudge South %3d"), m_calibrationSteps));
1667                             pFrame->ScheduleAxisMove(this, SOUTH, pulseAmt, MOVEOPTS_CALIBRATION_MOVE);
1668                             break;
1669                         }
1670                     }
1671                 }
1672                 else
1673                 {
1674                     Debug.Write(wxString::Format("Nudging discontinued, wrong direction: %0.2f\n", theta));
1675                 }
1676 
1677                 Debug.Write(wxString::Format("Final south nudging status: Current loc = {%.3f,%.3f}, targeting {%.3f,%.3f}\n",
1678                     currentLocation.X, currentLocation.Y, m_calibrationInitialLocation.X, m_calibrationInitialLocation.Y));
1679 
1680                 m_calibrationState = CALIBRATION_STATE_COMPLETE;
1681                 // fall through
1682                 Debug.Write("Falling Through to state CALIBRATION_COMPLETE\n");
1683 
1684             case CALIBRATION_STATE_COMPLETE:
1685                 GetLastCalibration(&m_prevCalibration);
1686                 LoadCalibrationDetails(&m_prevCalibrationDetails);
1687                 Calibration cal(m_calibration);
1688                 cal.declination = pPointingSource->GetDeclination();
1689                 cal.pierSide = pPointingSource->SideOfPier();
1690                 cal.rotatorAngle = Rotator::RotatorPosition();
1691                 cal.binning = pCamera->Binning;
1692                 SetCalibration(cal);
1693                 m_calibrationDetails.raStepCount = m_raSteps;
1694                 m_calibrationDetails.decStepCount = m_decSteps;
1695                 SetCalibrationDetails(m_calibrationDetails, m_calibration.xAngle, m_calibration.yAngle, pCamera->Binning);
1696                 if (SANITY_CHECKING_ACTIVE)
1697                     SanityCheckCalibration(m_prevCalibration, m_prevCalibrationDetails);  // method gets "new" info itself
1698                 pFrame->StatusMsg(_("Calibration complete"));
1699                 GuideLog.CalibrationComplete(this);
1700                 EvtServer.NotifyCalibrationComplete(this);
1701                 Debug.Write("Calibration Complete\n");
1702                 pConfig->Flush();
1703                 break;
1704         }
1705     }
1706     catch (const wxString& Msg)
1707     {
1708         POSSIBLY_UNUSED(Msg);
1709 
1710         ClearCalibration();
1711 
1712         bError = true;
1713     }
1714 
1715     return bError;
1716 }
1717 
1718 // Get a value of declination, in radians, that can be used for adjusting the RA guide rate,
1719 // or UNKNOWN_DECLINATION if the declination is not known.
GetDeclination()1720 double Scope::GetDeclination()
1721 {
1722     return UNKNOWN_DECLINATION;
1723 }
1724 
1725 // Baseline implementations for non-ASCOM subclasses.  Methods will
1726 // return a sensible default or an error (true)
GetGuideRates(double * pRAGuideRate,double * pDecGuideRate)1727 bool Scope::GetGuideRates(double *pRAGuideRate, double *pDecGuideRate)
1728 {
1729     return true; // error, not implemented
1730 }
1731 
GetCoordinates(double * ra,double * dec,double * siderealTime)1732 bool Scope::GetCoordinates(double *ra, double *dec, double *siderealTime)
1733 {
1734     return true; // error
1735 }
1736 
GetSiteLatLong(double * latitude,double * longitude)1737 bool Scope::GetSiteLatLong(double *latitude, double *longitude)
1738 {
1739     return true; // error
1740 }
1741 
CanSlew()1742 bool Scope::CanSlew()
1743 {
1744     return false;
1745 }
1746 
CanSlewAsync()1747 bool Scope::CanSlewAsync()
1748 {
1749     return false;
1750 }
1751 
PreparePositionInteractive()1752 bool Scope::PreparePositionInteractive()
1753 {
1754     return false; // no error
1755 }
1756 
CanReportPosition()1757 bool Scope::CanReportPosition()
1758 {
1759     return false;
1760 }
1761 
CanPulseGuide()1762 bool Scope::CanPulseGuide()
1763 {
1764     return false;
1765 }
1766 
SlewToCoordinates(double ra,double dec)1767 bool Scope::SlewToCoordinates(double ra, double dec)
1768 {
1769     return true; // error
1770 }
1771 
SlewToCoordinatesAsync(double ra,double dec)1772 bool Scope::SlewToCoordinatesAsync(double ra, double dec)
1773 {
1774     return true; // error
1775 }
1776 
AbortSlew()1777 void Scope::AbortSlew()
1778 {
1779 }
1780 
CanCheckSlewing()1781 bool Scope::CanCheckSlewing()
1782 {
1783     return false;
1784 }
1785 
Slewing()1786 bool Scope::Slewing()
1787 {
1788     return false;
1789 }
1790 
SideOfPier()1791 PierSide Scope::SideOfPier()
1792 {
1793     return PIER_SIDE_UNKNOWN;
1794 }
1795 
GuideSpeedSummary()1796 static wxString GuideSpeedSummary()
1797 {
1798     // use the pointing source's guide speeds on the assumption that
1799     // the ASCOM/INDI reported guide rate will be the same as the ST4
1800     // guide rate
1801     Scope *scope = pPointingSource;
1802 
1803     double raSpeed, decSpeed;
1804     if (!scope->GetGuideRates(&raSpeed, &decSpeed))
1805     {
1806         return wxString::Format("RA Guide Speed = %0.1f a-s/s, Dec Guide Speed = %0.1f a-s/s",
1807             3600.0 * raSpeed, 3600.0 * decSpeed);
1808     }
1809     else
1810         return "RA Guide Speed = Unknown, Dec Guide Speed = Unknown";
1811 }
1812 
1813 // unstranslated settings summary
GetSettingsSummary() const1814 wxString Scope::GetSettingsSummary() const
1815 {
1816     Calibration calInfo;
1817     GetLastCalibration(&calInfo);
1818 
1819     CalibrationDetails calDetails;
1820     LoadCalibrationDetails(&calDetails);
1821 
1822     // return a loggable summary of current mount settings
1823     wxString ret = Mount::GetSettingsSummary() +
1824         wxString::Format(
1825             "Max RA duration = %d, Max DEC duration = %d, DEC guide mode = %s\n",
1826             GetMaxRaDuration(),
1827             GetMaxDecDuration(),
1828             DecGuideModeStr(GetDecGuideMode()));
1829 
1830     ret += GuideSpeedSummary() + ", ";
1831 
1832     ret += wxString::Format("Cal Dec = %s, Last Cal Issue = %s, Timestamp = %s\n",
1833         DeclinationStr(calInfo.declination, "%0.1f"), Mount::GetIssueString(calDetails.lastIssue),
1834         calDetails.origTimestamp);
1835 
1836     return ret;
1837 }
1838 
CalibrationSettingsSummary() const1839 wxString Scope::CalibrationSettingsSummary() const
1840 {
1841     return wxString::Format("Calibration Step = %d ms, Calibration Distance = %d px, "
1842                             "Assume orthogonal axes = %s\n",
1843                             GetCalibrationDuration(), GetCalibrationDistance(),
1844                             IsAssumeOrthogonal() ? "yes" : "no") +
1845             GuideSpeedSummary();
1846 }
1847 
GetMountClassName() const1848 wxString Scope::GetMountClassName() const
1849 {
1850     return wxString("scope");
1851 }
1852 
GetConfigDialogPane(wxWindow * pParent)1853 Mount::MountConfigDialogPane *Scope::GetConfigDialogPane(wxWindow *pParent)
1854 {
1855     return new ScopeConfigDialogPane(pParent, this);
1856 }
1857 
ScopeConfigDialogPane(wxWindow * pParent,Scope * pScope)1858 Scope::ScopeConfigDialogPane::ScopeConfigDialogPane(wxWindow *pParent, Scope *pScope)
1859     : MountConfigDialogPane(pParent, _("Mount Guide Algorithms"), pScope)
1860 {
1861     m_pScope = pScope;
1862 }
1863 
LayoutControls(wxPanel * pParent,BrainCtrlIdMap & CtrlMap)1864 void Scope::ScopeConfigDialogPane::LayoutControls(wxPanel *pParent, BrainCtrlIdMap& CtrlMap)
1865 {
1866     // All of the scope UI controls are hosted in the parent
1867     MountConfigDialogPane::LayoutControls(pParent, CtrlMap);
1868 }
1869 
LoadValues()1870 void Scope::ScopeConfigDialogPane::LoadValues()
1871 {
1872     MountConfigDialogPane::LoadValues();
1873 }
1874 
UnloadValues()1875 void Scope::ScopeConfigDialogPane::UnloadValues()
1876 {
1877     MountConfigDialogPane::UnloadValues();
1878 }
1879 
GetConfigDialogCtrlSet(wxWindow * pParent,Mount * pScope,AdvancedDialog * pAdvancedDialog,BrainCtrlIdMap & CtrlMap)1880 MountConfigDialogCtrlSet *Scope::GetConfigDialogCtrlSet(wxWindow *pParent, Mount *pScope, AdvancedDialog *pAdvancedDialog, BrainCtrlIdMap& CtrlMap)
1881 {
1882     return new ScopeConfigDialogCtrlSet(pParent, (Scope *) pScope, pAdvancedDialog, CtrlMap);
1883 }
1884 
ScopeConfigDialogCtrlSet(wxWindow * pParent,Scope * pScope,AdvancedDialog * pAdvancedDialog,BrainCtrlIdMap & CtrlMap)1885 ScopeConfigDialogCtrlSet::ScopeConfigDialogCtrlSet(wxWindow *pParent, Scope *pScope, AdvancedDialog *pAdvancedDialog, BrainCtrlIdMap& CtrlMap)
1886     : MountConfigDialogCtrlSet(pParent, pScope, pAdvancedDialog, CtrlMap)
1887 {
1888     int width;
1889     bool enableCtrls = pScope != nullptr;
1890 
1891     m_pScope = pScope;
1892     width = StringWidth(_T("00000"));
1893 
1894     wxBoxSizer *pCalibSizer = new wxBoxSizer(wxHORIZONTAL);
1895     m_pCalibrationDuration = pFrame->MakeSpinCtrl(GetParentWindow(AD_szCalibrationDuration), wxID_ANY, wxEmptyString, wxDefaultPosition,
1896         wxSize(width, -1), wxSP_ARROW_KEYS, 0, 10000, 1000, _T("Cal_Dur"));
1897     pCalibSizer->Add(MakeLabeledControl(AD_szCalibrationDuration, _("Calibration step (ms)"), m_pCalibrationDuration,
1898         _("How long a guide pulse should be used during calibration? Click \"Advanced...\" to compute a suitable value.")));
1899     m_pCalibrationDuration->Enable(enableCtrls);
1900 
1901     // create the 'auto' button and bind it to the associated event-handler
1902     wxButton *pAutoDuration = new wxButton(GetParentWindow(AD_szCalibrationDuration), wxID_OK, _("Advanced...") );
1903     pAutoDuration->SetToolTip(_("Click to open the Calibration Calculator Dialog to review or change all calibration parameters"));
1904     pAutoDuration->Bind(wxEVT_COMMAND_BUTTON_CLICKED, &ScopeConfigDialogCtrlSet::OnCalcCalibrationStep, this);
1905     pAutoDuration->Enable(enableCtrls);
1906 
1907     pCalibSizer->Add(pAutoDuration);
1908 
1909     wxBoxSizer *pCalibGroupSizer = new wxBoxSizer(wxVERTICAL);
1910     pCalibGroupSizer->Add(pCalibSizer);
1911 
1912     AddGroup(CtrlMap, AD_szCalibrationDuration, pCalibGroupSizer);
1913 
1914     m_pNeedFlipDec = new wxCheckBox(GetParentWindow(AD_cbReverseDecOnFlip), wxID_ANY,
1915         _("Reverse Dec output after meridian flip"));
1916     AddCtrl(CtrlMap, AD_cbReverseDecOnFlip, m_pNeedFlipDec,
1917         _("Check if your mount needs Dec output reversed after a meridian flip. Changing this setting will clear the existing calibration data"));
1918     m_pNeedFlipDec->Enable(enableCtrls);
1919 
1920     bool usingAO = TheAO() != nullptr;
1921     if (pScope && pScope->CanCheckSlewing())
1922     {
1923         m_pStopGuidingWhenSlewing = new wxCheckBox(GetParentWindow(AD_cbSlewDetection), wxID_ANY, _("Stop guiding when mount slews"));
1924         AddCtrl(CtrlMap, AD_cbSlewDetection, m_pStopGuidingWhenSlewing,
1925             _("When checked, PHD will stop guiding if the mount starts slewing"));
1926     }
1927     else
1928         m_pStopGuidingWhenSlewing = 0;
1929 
1930     m_assumeOrthogonal = new wxCheckBox(GetParentWindow(AD_cbAssumeOrthogonal), wxID_ANY,
1931         _("Assume Dec orthogonal to RA"));
1932     m_assumeOrthogonal->Enable(enableCtrls);
1933     AddCtrl(CtrlMap, AD_cbAssumeOrthogonal, m_assumeOrthogonal,
1934         _("Assume Dec axis is perpendicular to RA axis, regardless of calibration. Prevents RA periodic error from affecting Dec calibration. Option takes effect when calibrating DEC."));
1935 
1936     if (pScope)
1937     {
1938         wxBoxSizer *pComp1 = new wxBoxSizer(wxHORIZONTAL);
1939         BRAIN_CTRL_IDS blcCtrlId;
1940         if (usingAO)
1941             blcCtrlId = AD_szBumpBLCompCtrls;
1942         else
1943             blcCtrlId = AD_szBLCompCtrls;
1944         wxWindow *blcHostTab = GetParentWindow(blcCtrlId);
1945         m_pUseBacklashComp = new wxCheckBox(blcHostTab, wxID_ANY, _("Enable"));
1946         m_pUseBacklashComp->SetToolTip(_("Check this if you want to apply a backlash compensation guide pulse when declination direction is reversed."));
1947         pComp1->Add(m_pUseBacklashComp);
1948 
1949         double const blcMinVal = BacklashComp::GetBacklashPulseMinValue();
1950         double const blcMaxVal = BacklashComp::GetBacklashPulseMaxValue();
1951 
1952         m_pBacklashPulse = pFrame->MakeSpinCtrlDouble(blcHostTab, wxID_ANY, wxEmptyString, wxDefaultPosition,
1953             wxSize(width, -1), wxSP_ARROW_KEYS, blcMinVal, blcMaxVal, 450, blcMinVal);
1954         pComp1->Add(MakeLabeledControl(blcCtrlId, _("Amount"), m_pBacklashPulse,
1955             _("Size of backlash compensation guide pulse (mSec)")), wxSizerFlags().Border(wxLEFT, 26));
1956 
1957         wxBoxSizer *pCompVert =
1958             new wxStaticBoxSizer(wxVERTICAL, blcHostTab,
1959             usingAO ? _("Mount Backlash Compensation") : _("Backlash Compensation"));
1960         pCompVert->Add(pComp1);
1961 
1962         if (!usingAO)              // AO doesn't use auto-adjustments, so don't show min/max controls
1963         {
1964             wxBoxSizer *pComp2 = new wxBoxSizer(wxHORIZONTAL);
1965             m_pBacklashFloor = pFrame->MakeSpinCtrlDouble(blcHostTab, wxID_ANY, wxEmptyString, wxDefaultPosition,
1966                 wxSize(width, -1), wxSP_ARROW_KEYS, blcMinVal, blcMaxVal, 300, blcMinVal);
1967             m_pBacklashCeiling = pFrame->MakeSpinCtrlDouble(blcHostTab, wxID_ANY, wxEmptyString, wxDefaultPosition,
1968                 wxSize(width, -1), wxSP_ARROW_KEYS, blcMinVal, blcMaxVal, 300, blcMinVal);
1969             pComp2->Add(MakeLabeledControl(blcCtrlId, _("Min"), m_pBacklashFloor,
1970                 _("Minimum length of backlash compensation pulse (mSec).")), wxSizerFlags().Border(wxLEFT, 0));
1971             pComp2->Add(MakeLabeledControl(blcCtrlId, _("Max"), m_pBacklashCeiling,
1972                 _("Maximum length of backlash compensation pulse (mSec).")), wxSizerFlags().Border(wxLEFT, 18));
1973             pCompVert->Add(pComp2);
1974         }
1975         AddGroup(CtrlMap, blcCtrlId, pCompVert);
1976         if (!usingAO)
1977         {
1978             m_pUseDecComp = new wxCheckBox(GetParentWindow(AD_cbUseDecComp), wxID_ANY, _("Use Dec compensation"));
1979             m_pUseDecComp->Enable(enableCtrls && pPointingSource);
1980             AddCtrl(CtrlMap, AD_cbUseDecComp, m_pUseDecComp,
1981                 _("Automatically adjust RA guide rate based on scope declination"));
1982 
1983             width = StringWidth(_T("00000"));
1984             m_pMaxRaDuration = pFrame->MakeSpinCtrl(GetParentWindow(AD_szMaxRAAmt), wxID_ANY, wxEmptyString,
1985                 wxDefaultPosition, wxSize(width, -1), wxSP_ARROW_KEYS, MAX_DURATION_MIN, MAX_DURATION_MAX, 150,
1986                 _T("MaxRA_Dur"));
1987             AddLabeledCtrl(CtrlMap, AD_szMaxRAAmt, _("Max RA duration"), m_pMaxRaDuration,
1988                 _("Longest length of pulse to send in RA\nDefault = 2500 ms."));
1989 
1990             m_pMaxDecDuration = pFrame->MakeSpinCtrl(GetParentWindow(AD_szMaxDecAmt), wxID_ANY, wxEmptyString,
1991                 wxDefaultPosition, wxSize(width, -1), wxSP_ARROW_KEYS, MAX_DURATION_MIN, MAX_DURATION_MAX, 150,
1992                 _T("MaxDec_Dur"));
1993             AddLabeledCtrl(CtrlMap, AD_szMaxDecAmt, _("Max Dec duration"), m_pMaxDecDuration,
1994                 _("Longest length of pulse to send in declination\nDefault = 2500 ms.  Increase if drift is fast."));
1995 
1996             wxString dec_choices[] = {
1997                 Scope::DecGuideModeLocaleStr(DEC_NONE),
1998                 Scope::DecGuideModeLocaleStr(DEC_AUTO),
1999                 Scope::DecGuideModeLocaleStr(DEC_NORTH),
2000                 Scope::DecGuideModeLocaleStr(DEC_SOUTH),
2001             };
2002 
2003             width = StringArrayWidth(dec_choices, WXSIZEOF(dec_choices));
2004             m_pDecMode = new wxChoice(GetParentWindow(AD_szDecGuideMode), wxID_ANY, wxDefaultPosition,
2005                 wxSize(width + 35, -1), WXSIZEOF(dec_choices), dec_choices);
2006             AddLabeledCtrl(CtrlMap, AD_szDecGuideMode, _("Dec guide mode"), m_pDecMode, _("Directions in which Dec guide commands will be issued"));
2007             m_pDecMode->Bind(wxEVT_COMMAND_CHOICE_SELECTED, &ScopeConfigDialogCtrlSet::OnDecModeChoice, this);
2008         }
2009         m_pScope->currConfigDialogCtrlSet = this;
2010     }
2011 }
2012 
LoadValues()2013 void ScopeConfigDialogCtrlSet::LoadValues()
2014 {
2015     MountConfigDialogCtrlSet::LoadValues();
2016     int stepSize = m_pScope->GetCalibrationDuration();
2017     m_pCalibrationDuration->SetValue(stepSize);
2018     m_calibrationDistance = m_pScope->GetCalibrationDistance();
2019     m_pNeedFlipDec->SetValue(m_pScope->CalibrationFlipRequiresDecFlip());
2020     if (m_pStopGuidingWhenSlewing)
2021         m_pStopGuidingWhenSlewing->SetValue(m_pScope->IsStopGuidingWhenSlewingEnabled());
2022     m_assumeOrthogonal->SetValue(m_pScope->IsAssumeOrthogonal());
2023     int pulseSize;
2024     int floor;
2025     int ceiling;
2026     m_pScope->m_backlashComp->GetBacklashCompSettings(&pulseSize, &floor, &ceiling);
2027     m_pBacklashPulse->SetValue(pulseSize);
2028     m_pUseBacklashComp->SetValue(m_pScope->m_backlashComp->IsEnabled());
2029     bool usingAO = TheAO() != nullptr;
2030     if (!usingAO)
2031     {
2032         m_pBacklashFloor->SetValue(floor);
2033         m_pBacklashCeiling->SetValue(ceiling);
2034         m_pMaxRaDuration->SetValue(m_pScope->GetMaxRaDuration());
2035         m_pMaxDecDuration->SetValue(m_pScope->GetMaxDecDuration());
2036         int whichDecMode = m_pScope->GetDecGuideMode();
2037         m_pDecMode->SetSelection(whichDecMode);
2038         Mount::MountConfigDialogPane* pCurrMountPane = pFrame->pAdvancedDialog->GetCurrentMountPane();
2039         pCurrMountPane->EnableDecControls(whichDecMode != DEC_NONE);
2040         m_pUseDecComp->SetValue(m_pScope->DecCompensationEnabled());
2041         m_origBLCEnabled = m_pScope->m_backlashComp->IsEnabled();
2042         if (whichDecMode == DEC_AUTO)
2043         {
2044             m_pUseBacklashComp->SetValue(m_origBLCEnabled);
2045             m_pUseBacklashComp->Enable(true);
2046         }
2047         else
2048         {
2049             m_pUseBacklashComp->SetValue(false);
2050             m_pUseBacklashComp->Enable(false);
2051         }
2052 
2053 
2054     }
2055 }
2056 
UnloadValues()2057 void ScopeConfigDialogCtrlSet::UnloadValues()
2058 {
2059     bool usingAO = TheAO() != nullptr;
2060     m_pScope->SetCalibrationDuration(m_pCalibrationDuration->GetValue());
2061     m_pScope->SetCalibrationDistance(m_calibrationDistance);
2062     bool oldFlip = m_pScope->CalibrationFlipRequiresDecFlip();
2063     bool newFlip = m_pNeedFlipDec->GetValue();
2064     m_pScope->SetCalibrationFlipRequiresDecFlip(newFlip);
2065     if (oldFlip != newFlip)
2066     {
2067         m_pScope->ClearCalibration();
2068         Debug.Write(wxString::Format("User changed 'Dec-Flip' setting from %d to %d, calibration cleared\n", oldFlip, newFlip));
2069     }
2070     if (m_pStopGuidingWhenSlewing)
2071         m_pScope->EnableStopGuidingWhenSlewing(m_pStopGuidingWhenSlewing->GetValue());
2072     m_pScope->SetAssumeOrthogonal(m_assumeOrthogonal->GetValue());
2073     int newBC = m_pBacklashPulse->GetValue();
2074     int newFloor;
2075     int newCeiling;
2076     // Is using an AO, don't adjust the blc pulse size
2077     if (!usingAO)
2078     {
2079         newFloor = m_pBacklashFloor->GetValue();
2080         newCeiling = m_pBacklashCeiling->GetValue();
2081     }
2082     else
2083     {
2084         newFloor = newBC;
2085         newCeiling = newBC;
2086     }
2087 
2088     // SetBacklashPulseWidth will handle floor/ceiling values that don't make sense
2089     m_pScope->m_backlashComp->EnableBacklashComp(m_pUseBacklashComp->GetValue());
2090     m_pScope->m_backlashComp->SetBacklashPulseWidth(newBC, newFloor, newCeiling);
2091 
2092     // Following needed in case user changes max_duration with blc value already set
2093     if (m_pScope->m_backlashComp->IsEnabled() && m_pScope->GetMaxDecDuration() < newBC)
2094         m_pScope->SetMaxDecDuration(newBC);
2095     if (pFrame)
2096         pFrame->UpdateStatusBarCalibrationStatus();
2097 
2098     if (!usingAO)
2099     {
2100         m_pScope->EnableDecCompensation(m_pUseDecComp->GetValue());
2101         m_pScope->SetMaxRaDuration(m_pMaxRaDuration->GetValue());
2102         if (!m_pScope->m_backlashComp->IsEnabled())                       // handled above
2103             m_pScope->SetMaxDecDuration(m_pMaxDecDuration->GetValue());
2104         m_pScope->SetDecGuideMode(m_pDecMode->GetSelection());
2105 
2106     }
2107     MountConfigDialogCtrlSet::UnloadValues();
2108 }
2109 
ResetRAParameterUI()2110 void ScopeConfigDialogCtrlSet::ResetRAParameterUI()
2111 {
2112     m_pMaxRaDuration->SetValue(DefaultMaxRaDuration);
2113 }
2114 
ResetDecParameterUI()2115 void ScopeConfigDialogCtrlSet::ResetDecParameterUI()
2116 {
2117     m_pMaxDecDuration->SetValue(DefaultMaxDecDuration);
2118     m_pDecMode->SetSelection(1);                // 'Auto'
2119     m_pUseBacklashComp->SetValue(false);
2120 }
2121 
GetDecGuideModeUI()2122 DEC_GUIDE_MODE ScopeConfigDialogCtrlSet::GetDecGuideModeUI()
2123 {
2124     return (DEC_GUIDE_MODE)m_pDecMode->GetSelection();
2125 }
2126 
GetCalStepSizeCtrlValue()2127 int ScopeConfigDialogCtrlSet::GetCalStepSizeCtrlValue()
2128 {
2129     return m_pCalibrationDuration->GetValue();
2130 }
2131 
SetCalStepSizeCtrlValue(int newStep)2132 void ScopeConfigDialogCtrlSet::SetCalStepSizeCtrlValue(int newStep)
2133 {
2134     m_pCalibrationDuration->SetValue(newStep);
2135 }
2136 
OnDecModeChoice(wxCommandEvent & evt)2137 void ScopeConfigDialogCtrlSet::OnDecModeChoice(wxCommandEvent& evt)
2138 {
2139     int which = m_pDecMode->GetSelection();
2140     // User choice of 'none' will disable Dec algo params in UI
2141     Mount::MountConfigDialogPane* pCurrMountPane = pFrame->pAdvancedDialog->GetCurrentMountPane();
2142     pCurrMountPane->EnableDecControls(which != DEC_NONE);
2143     m_pUseDecComp->SetValue(m_pScope->DecCompensationEnabled());
2144     if (which != DEC_AUTO)
2145     {
2146         m_pUseBacklashComp->SetValue(false);
2147         m_pUseBacklashComp->Enable(false);
2148     }
2149     else
2150     {
2151         m_pUseBacklashComp->SetValue(m_origBLCEnabled);
2152         m_pUseBacklashComp->Enable(true);
2153     }
2154 }
2155 
OnCalcCalibrationStep(wxCommandEvent & evt)2156 void ScopeConfigDialogCtrlSet::OnCalcCalibrationStep(wxCommandEvent& evt)
2157 {
2158     int focalLength = 0;
2159     double pixelSize = 0;
2160     int binning = 1;
2161     AdvancedDialog *pAdvancedDlg = pFrame->pAdvancedDialog;
2162 
2163     if (pAdvancedDlg)
2164     {
2165         pixelSize = pAdvancedDlg->GetPixelSize();
2166         binning = pAdvancedDlg->GetBinning();
2167         focalLength = pAdvancedDlg->GetFocalLength();
2168     }
2169 
2170     CalstepDialog calc(m_pParent, focalLength, pixelSize, binning);
2171     if (calc.ShowModal() == wxID_OK)
2172     {
2173         int calibrationStep;
2174         int distance;
2175         if (calc.GetResults(&focalLength, &pixelSize, &binning, &calibrationStep, &distance))
2176         {
2177             // Following sets values in the UI controls of the various dialog tabs - not underlying data values
2178             pAdvancedDlg->SetFocalLength(focalLength);
2179             pAdvancedDlg->SetPixelSize(pixelSize);
2180             pAdvancedDlg->SetBinning(binning);
2181             m_pCalibrationDuration->SetValue(calibrationStep);
2182             m_calibrationDistance = distance;
2183         }
2184     }
2185 }
2186 
GetGraphControlPane(wxWindow * pParent,const wxString & label)2187 GraphControlPane *Scope::GetGraphControlPane(wxWindow *pParent, const wxString& label)
2188 {
2189     return new ScopeGraphControlPane(pParent, this, label);
2190 }
2191 
ScopeGraphControlPane(wxWindow * pParent,Scope * pScope,const wxString & label)2192 Scope::ScopeGraphControlPane::ScopeGraphControlPane(wxWindow *pParent, Scope *pScope, const wxString& label)
2193     : GraphControlPane(pParent, label)
2194 {
2195     int width;
2196     m_pScope = pScope;
2197     pScope->m_graphControlPane = this;
2198 
2199     width = StringWidth(_T("0000"));
2200     m_pMaxRaDuration = pFrame->MakeSpinCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(width, -1),
2201         wxSP_ARROW_KEYS, MAX_DURATION_MIN, MAX_DURATION_MAX, 0);
2202     m_pMaxRaDuration->SetToolTip(_("Longest length of pulse to send in RA\nDefault = 2500 ms."));
2203     m_pMaxRaDuration->Bind(wxEVT_COMMAND_SPINCTRL_UPDATED, &Scope::ScopeGraphControlPane::OnMaxRaDurationSpinCtrl, this);
2204     DoAdd(m_pMaxRaDuration, _("Mx RA"));
2205 
2206     width = StringWidth(_T("0000"));
2207     m_pMaxDecDuration = pFrame->MakeSpinCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(width, -1),
2208         wxSP_ARROW_KEYS, MAX_DURATION_MIN, MAX_DURATION_MAX, 0);
2209     m_pMaxDecDuration->SetToolTip(_("Longest length of pulse to send in declination\nDefault = 2500 ms.  Increase if drift is fast."));
2210     m_pMaxDecDuration->Bind(wxEVT_COMMAND_SPINCTRL_UPDATED, &Scope::ScopeGraphControlPane::OnMaxDecDurationSpinCtrl, this);
2211     DoAdd(m_pMaxDecDuration, _("Mx DEC"));
2212 
2213     wxString dec_choices[] = {
2214         DecGuideModeLocaleStr(DEC_NONE),
2215         DecGuideModeLocaleStr(DEC_AUTO),
2216         DecGuideModeLocaleStr(DEC_NORTH),
2217         DecGuideModeLocaleStr(DEC_SOUTH),
2218     };
2219     m_pDecMode = new wxChoice(this, wxID_ANY,
2220         wxDefaultPosition,wxDefaultSize, WXSIZEOF(dec_choices), dec_choices );
2221     m_pDecMode->SetToolTip(_("Directions in which Dec guide commands will be issued"));
2222     m_pDecMode->Bind(wxEVT_COMMAND_CHOICE_SELECTED, &Scope::ScopeGraphControlPane::OnDecModeChoice, this);
2223     m_pControlSizer->Add(m_pDecMode);
2224 
2225     m_pMaxRaDuration->SetValue(m_pScope->GetMaxRaDuration());
2226     m_pMaxDecDuration->SetValue(m_pScope->GetMaxDecDuration());
2227     m_pDecMode->SetSelection(m_pScope->GetDecGuideMode());
2228 }
2229 
~ScopeGraphControlPane()2230 Scope::ScopeGraphControlPane::~ScopeGraphControlPane()
2231 {
2232     if (m_pScope)
2233     {
2234         m_pScope->m_graphControlPane = nullptr;
2235     }
2236 }
2237 
OnMaxRaDurationSpinCtrl(wxSpinEvent & WXUNUSED (evt))2238 void Scope::ScopeGraphControlPane::OnMaxRaDurationSpinCtrl(wxSpinEvent& WXUNUSED(evt))
2239 {
2240     m_pScope->SetMaxRaDuration(m_pMaxRaDuration->GetValue());
2241 }
2242 
OnMaxDecDurationSpinCtrl(wxSpinEvent & WXUNUSED (evt))2243 void Scope::ScopeGraphControlPane::OnMaxDecDurationSpinCtrl(wxSpinEvent& WXUNUSED(evt))
2244 {
2245     m_pScope->SetMaxDecDuration(m_pMaxDecDuration->GetValue());
2246 }
2247 
OnDecModeChoice(wxCommandEvent & WXUNUSED (evt))2248 void Scope::ScopeGraphControlPane::OnDecModeChoice(wxCommandEvent& WXUNUSED(evt))
2249 {
2250     m_pScope->SetDecGuideMode(m_pDecMode->GetSelection());
2251 }
2252