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