1 /*
2  *  drift_tool.h
3  *  PHD Guiding
4  *
5  *  Created by Andy Galasso
6  *  Copyright (c) 2013-2014 Andy Galasso
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 
35 #include "phd.h"
36 #include "drift_tool.h"
37 
38 #include <wx/gbsizer.h>
39 #include <wx/valnum.h>
40 
41 enum Phase
42 {
43     PHASE_ADJUST_AZ,
44     PHASE_ADJUST_ALT,
45 };
46 
47 enum Mode
48 {
49     MODE_IDLE,
50     MODE_DRIFT,
51     MODE_ADJUST,
52 };
53 
54 enum CtrlIds
55 {
56     ID_SLEW = 10001,
57     ID_SAVE,
58     ID_DRIFT,
59     ID_ADJUST,
60     ID_PHASE,
61     ID_TIMER,
62 };
63 
64 struct DriftToolWin : public wxFrame
65 {
66     DriftToolWin();
67     ~DriftToolWin();
68 
69     void UpdatePhaseState();
70     void UpdateModeState();
71 
72     Phase m_phase;
73     Mode m_mode;
74     bool m_drifting;
75     bool m_need_end_dec_drift;
76     bool m_save_lock_pos_is_sticky;
77     bool m_save_use_subframes;
78     GraphLogClientWindow::GRAPH_MODE m_save_graph_mode;
79     int m_save_graph_length;
80     int m_save_graph_height;
81     bool m_location_prompt_done;
82 
83     bool m_can_slew;
84     bool m_slewing;
85     PHD_Point m_siteLatLong;
86 
87     wxStaticBitmap *m_bmp;
88     wxBitmap *m_azArrowBmp;
89     wxBitmap *m_altArrowBmp;
90     wxStaticText *m_instructions;
91     wxTextCtrl *m_raCurrent;
92     wxTextCtrl *m_decCurrent;
93     wxSpinCtrl *m_raSlew;
94     wxSpinCtrl *m_decSlew;
95     wxButton *m_slew;
96     wxButton *m_saveCoords;
97     wxStaticText *m_notesLabel;
98     wxTextCtrl *m_notes;
99     wxButton *m_drift;
100     wxButton *m_adjust;
101     wxButton *m_phaseBtn;
102     wxStatusBar *m_statusBar;
103     wxTimer *m_timer;
104 
105     void EnableSlew(bool enable);
106 
107     void OnSlew(wxCommandEvent& evt);
108     void OnSaveCoords(wxCommandEvent& evt);
109     void OnNotesText(wxCommandEvent& evt);
110     void OnDrift(wxCommandEvent& evt);
111     void OnAdjust(wxCommandEvent& evt);
112     void OnPhase(wxCommandEvent& evt);
113     void OnAppStateNotify(wxCommandEvent& evt);
114     void OnClose(wxCloseEvent& evt);
115     void OnTimer(wxTimerEvent& evt);
116 
117     void UpdateScopeCoordinates(void);
118 
119     void SetStatusText(const wxString &text, int number = 0) override;
120 
121     DECLARE_EVENT_TABLE()
122 };
123 
BEGIN_EVENT_TABLE(DriftToolWin,wxFrame)124 BEGIN_EVENT_TABLE(DriftToolWin, wxFrame)
125     EVT_BUTTON(ID_SLEW, DriftToolWin::OnSlew)
126     EVT_BUTTON(ID_SAVE, DriftToolWin::OnSaveCoords)
127     EVT_BUTTON(ID_DRIFT, DriftToolWin::OnDrift)
128     EVT_BUTTON(ID_ADJUST, DriftToolWin::OnAdjust)
129     EVT_BUTTON(ID_PHASE, DriftToolWin::OnPhase)
130     EVT_COMMAND(wxID_ANY, APPSTATE_NOTIFY_EVENT, DriftToolWin::OnAppStateNotify)
131     EVT_CLOSE(DriftToolWin::OnClose)
132     EVT_TIMER(ID_TIMER, DriftToolWin::OnTimer)
133 END_EVENT_TABLE()
134 
135 DriftToolWin::DriftToolWin()
136     : wxFrame(pFrame, wxID_ANY, _("Drift Align"), wxDefaultPosition, wxDefaultSize,
137               wxCAPTION|wxCLOSE_BOX|wxMINIMIZE_BOX|wxSYSTEM_MENU|wxTAB_TRAVERSAL|wxFRAME_FLOAT_ON_PARENT|wxFRAME_NO_TASKBAR),
138         m_need_end_dec_drift(false),
139         m_slewing(false)
140 {
141     SetSizeHints(wxDefaultSize, wxDefaultSize);
142 
143     // a vertical sizer holding everything
144     wxBoxSizer *topSizer = new wxBoxSizer(wxVERTICAL);
145 
146     // a horizontal box sizer for the bitmap and the instructions
147     wxBoxSizer *instrSizer = new wxBoxSizer(wxHORIZONTAL);
148 
149 #   include "icons/AzArrow.xpm"
150     m_azArrowBmp = new wxBitmap(AzArrow);
151 #   include "icons/AltArrow.xpm"
152     m_altArrowBmp = new wxBitmap(AltArrow);
153 
154     m_bmp = new wxStaticBitmap(this, wxID_ANY, *m_azArrowBmp, wxDefaultPosition, wxSize(80, 100));
155     instrSizer->Add(m_bmp, 0, wxALIGN_CENTER_VERTICAL|wxFIXED_MINSIZE, 5);
156 
157     m_instructions = new wxStaticText(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(400,120), wxALIGN_LEFT|wxST_NO_AUTORESIZE);
158 #ifdef __WXOSX__
159     m_instructions->SetFont(*wxSMALL_FONT);
160 #endif
161     m_instructions->Wrap(-1);
162     instrSizer->Add(m_instructions, 0, wxALIGN_CENTER_HORIZONTAL|wxALL, 5);
163 
164     topSizer->Add(instrSizer);
165 
166     // static box sizer holding the scope pointing controls
167     wxStaticBoxSizer *sbSizer = new wxStaticBoxSizer(new wxStaticBox(this, wxID_ANY, _("Scope Pointing")), wxVERTICAL);
168 
169     // a grid box sizer for laying out scope pointing the controls
170     wxGridBagSizer *gbSizer = new wxGridBagSizer(0, 0);
171     gbSizer->SetFlexibleDirection(wxBOTH);
172     gbSizer->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED);
173 
174     wxStaticText *txt;
175 
176     txt = new wxStaticText(this, wxID_ANY, wxString::Format(_("Meridian Offset (%s)"), DEGREES_SYMBOL));
177     txt->Wrap(-1);
178     gbSizer->Add(txt, wxGBPosition(0, 1), wxGBSpan(1, 1), wxALL, 5);
179 
180     txt = new wxStaticText(this, wxID_ANY, wxString::Format(_("Declination (%s)"), DEGREES_SYMBOL));
181     txt->Wrap(-1);
182     gbSizer->Add(txt, wxGBPosition(0, 2), wxGBSpan(1, 1), wxALL, 5);
183 
184     txt = new wxStaticText(this, wxID_ANY, _("Current"));
185     txt->Wrap(-1);
186     gbSizer->Add(txt, wxGBPosition(1, 0), wxGBSpan(1, 1), wxALL, 5);
187 
188     m_raCurrent = new wxTextCtrl(this, wxID_ANY, _T("--"), wxDefaultPosition, wxDefaultSize, wxTE_READONLY);
189     gbSizer->Add(m_raCurrent, wxGBPosition(1, 1), wxGBSpan(1, 1), wxEXPAND | wxALL, 5);
190 
191     m_decCurrent = new wxTextCtrl(this, wxID_ANY, _T("--"), wxDefaultPosition, wxDefaultSize, wxTE_READONLY);
192     gbSizer->Add(m_decCurrent, wxGBPosition(1, 2), wxGBSpan(1, 1), wxEXPAND | wxALL, 5);
193 
194     txt = new wxStaticText(this, wxID_ANY, _("Slew To"));
195     txt->Wrap(-1);
196     gbSizer->Add(txt, wxGBPosition(2, 0), wxGBSpan(1, 1), wxALL, 5);
197 
198     m_raSlew = new wxSpinCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, -90, 90);
199     gbSizer->Add(m_raSlew, wxGBPosition(2, 1), wxGBSpan(1, 1), wxEXPAND | wxALL, 5);
200 
201     m_decSlew = new wxSpinCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, -90, 90);
202     gbSizer->Add(m_decSlew, wxGBPosition(2, 2), wxGBSpan(1, 1), wxEXPAND | wxALL, 5);
203 
204     m_slew = new wxButton(this, ID_SLEW, _("Slew"));
205     m_slew->SetToolTip(_("Click to slew to given coordinates."));
206     gbSizer->Add(m_slew, wxGBPosition(2, 3), wxGBSpan(1, 1), wxALL, 5);
207 
208     wxString label = _("Save");
209     wxSize sz(GetTextExtent(label));
210     sz.SetHeight(-1);
211     sz.IncBy(16, 0);
212     m_saveCoords = new wxButton(this, ID_SAVE, label, wxDefaultPosition, sz, 0);
213     m_saveCoords->SetToolTip(_("Click to save these coordinates as the default location for this axis adjustment."));
214     gbSizer->Add(m_saveCoords, wxGBPosition(2, 4), wxGBSpan(1, 1), wxTOP | wxBOTTOM | wxRIGHT, 5);
215 
216     // add grid bag sizer to static sizer
217     sbSizer->Add(gbSizer, 1, wxALIGN_CENTER, 5);
218 
219     // add static sizer to top-level sizer
220     topSizer->Add(sbSizer, 0, wxALIGN_CENTER_HORIZONTAL|wxALL, 5);
221 
222     // add some padding below the static sizer
223     topSizer->Add(0, 3, 0, wxEXPAND, 3);
224 
225     m_notesLabel = new wxStaticText(this, wxID_ANY, _("Altitude adjustment notes"));
226     m_notesLabel->Wrap(-1);
227     topSizer->Add(m_notesLabel, 0, wxEXPAND|wxTOP|wxLEFT, 8);
228 
229     m_notes = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(-1, 54), wxTE_MULTILINE);
230     pFrame->RegisterTextCtrl(m_notes);
231     topSizer->Add(m_notes, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, 5);
232     m_notes->Bind(wxEVT_COMMAND_TEXT_UPDATED, &DriftToolWin::OnNotesText, this);
233 
234     // horizontal sizer for the buttons
235     wxBoxSizer *hSizer = new wxBoxSizer(wxHORIZONTAL);
236 
237     // proportional pad on left of Drift button
238     hSizer->Add(0, 0, 2, wxEXPAND, 5);
239 
240     m_drift = new wxButton(this, ID_DRIFT, _("Drift"));
241     hSizer->Add(m_drift, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5);
242 
243     // proportional pad on right of Drift button
244     hSizer->Add(0, 0, 1, wxEXPAND, 5);
245 
246     m_adjust = new wxButton(this, ID_ADJUST, _("Adjust"));
247     hSizer->Add(m_adjust, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5);
248 
249     // proportional pad on right of Align button
250     hSizer->Add(0, 0, 2, wxEXPAND, 5);
251 
252     m_phaseBtn = new wxButton(this, ID_PHASE, wxT("???"));
253     hSizer->Add(m_phaseBtn, 0, wxALL|wxALIGN_CENTER_VERTICAL, 5);
254 
255     // add button sizer to top level sizer
256     topSizer->Add(hSizer, 1, wxEXPAND|wxALL, 5);
257 
258     SetSizer(topSizer);
259 
260     m_statusBar = CreateStatusBar(1, wxST_SIZEGRIP, wxID_ANY);
261 
262     Layout();
263     topSizer->Fit(this);
264 
265     int xpos = pConfig->Global.GetInt("/DriftTool/pos.x", -1);
266     int ypos = pConfig->Global.GetInt("/DriftTool/pos.y", -1);
267     MyFrame::PlaceWindowOnScreen(this, xpos, ypos);
268 
269     // can mount slew?
270     m_can_slew = pPointingSource && pPointingSource->CanSlew();
271 
272     // get site lat/long from scope
273     double lat, lon;
274     m_siteLatLong.Invalidate();
275     if (pPointingSource && !pPointingSource->GetSiteLatLong(&lat, &lon))
276     {
277         m_siteLatLong.SetXY(lat, lon);
278     }
279 
280     m_timer = NULL;
281     if (m_can_slew || (pPointingSource && pPointingSource->CanReportPosition()))
282     {
283         enum { SCOPE_POS_POLL_MS = 1500 };
284         m_timer = new wxTimer(this, ID_TIMER);
285         m_timer->Start(SCOPE_POS_POLL_MS, false /* continuous */);
286     }
287 
288     // make sure graph window is showing
289     if (!pFrame->pGraphLog->IsShown())
290     {
291         wxCommandEvent evt;
292         evt.SetInt(1); // "Checked"
293         pFrame->OnGraph(evt);
294     }
295 
296     // graph must be showing RA/Dec
297     m_save_graph_mode = pFrame->pGraphLog->SetMode(GraphLogClientWindow::MODE_RADEC);
298 
299     // resize graph scale
300     m_save_graph_length = pFrame->pGraphLog->GetLength();
301     pFrame->pGraphLog->SetLength(pConfig->Global.GetInt("/DriftTool/GraphLength", GraphLogWindow::DefaultMaxLength));
302     m_save_graph_height = pFrame->pGraphLog->GetHeight();
303     pFrame->pGraphLog->SetHeight(pConfig->Global.GetInt("/DriftTool/GraphHeight", GraphLogWindow::DefaultMaxHeight));
304     pFrame->pGraphLog->Refresh();
305 
306     // we do not want sticky lock position enabled
307     m_save_lock_pos_is_sticky = pFrame->pGuider->LockPosIsSticky();
308     pFrame->pGuider->SetLockPosIsSticky(false);
309     pFrame->tools_menu->FindItem(EEGG_STICKY_LOCK)->Check(false);
310 
311     m_save_use_subframes = pCamera->UseSubframes;
312 
313     m_phase = PHASE_ADJUST_AZ;
314     m_mode = MODE_IDLE;
315     m_location_prompt_done = false;
316     UpdatePhaseState();
317     UpdateModeState();
318 }
319 
~DriftToolWin()320 DriftToolWin::~DriftToolWin()
321 {
322     delete m_timer;
323     delete m_azArrowBmp;
324     delete m_altArrowBmp;
325     pFrame->pDriftTool = NULL;
326 }
327 
EnableSlew(bool enable)328 void DriftToolWin::EnableSlew(bool enable)
329 {
330     m_raSlew->Enable(enable);
331     m_decSlew->Enable(enable);
332     m_slew->Enable(enable && !m_slewing);
333     m_saveCoords->Enable(enable);
334 }
335 
LoadRADec(Phase phase,double * ra,double * dec)336 static void LoadRADec(Phase phase, double *ra, double *dec)
337 {
338     if (phase == PHASE_ADJUST_AZ)
339     {
340         *ra = pConfig->Global.GetDouble("/DriftTool/Az/SlewRAOfs", 0.0);
341         *dec = pConfig->Global.GetDouble("/DriftTool/Az/SlewDec", 0.0);
342     }
343     else
344     {
345         *ra = pConfig->Global.GetDouble("/DriftTool/Alt/SlewRAOfs", -65.0);
346         *dec = pConfig->Global.GetDouble("/DriftTool/Alt/SlewDec", 0.0);
347     }
348 }
349 
SaveRADec(Phase phase,double ra,double dec)350 static void SaveRADec(Phase phase, double ra, double dec)
351 {
352     if (phase == PHASE_ADJUST_AZ)
353     {
354         pConfig->Global.SetDouble("/DriftTool/Az/SlewRAOfs", ra);
355         pConfig->Global.SetDouble("/DriftTool/Az/SlewDec", dec);
356     }
357     else
358     {
359         pConfig->Global.SetDouble("/DriftTool/Alt/SlewRAOfs", ra);
360         pConfig->Global.SetDouble("/DriftTool/Alt/SlewDec", dec);
361     }
362 }
363 
UpdatePhaseState()364 void DriftToolWin::UpdatePhaseState()
365 {
366     double ra, dec;
367     LoadRADec(m_phase, &ra, &dec);
368     m_raSlew->SetValue((int) floor(ra));
369     m_decSlew->SetValue((int) floor(dec));
370 
371     if (m_phase == PHASE_ADJUST_AZ)
372     {
373         SetTitle(_("Drift Align - Azimuth Adjustment"));
374         m_bmp->SetBitmap(*m_azArrowBmp);
375         m_instructions->SetLabel(
376             _("Slew to near the Meridian and the Equator.\n"
377               "Press Drift to measure drift, watch the Dec trend line.\n"
378               "Press Adjust and adjust your mount's azimuth.\n"
379               "Repeat Drift/Adjust until alignment is complete.\n"
380               "Then, click Altitude to begin Altitude adjustment."));
381         m_notesLabel->SetLabel(_("Azimuth adjustment notes"));
382         m_notes->SetValue(pConfig->Profile.GetString("/DriftTool/Az/Notes", wxEmptyString));
383         m_phaseBtn->SetLabel(_("> Altitude"));
384     }
385     else
386     {
387         SetTitle(_("Drift Align - Altitude Adjustment"));
388         m_bmp->SetBitmap(*m_altArrowBmp);
389         m_instructions->SetLabel(
390             _("Slew to a location near the Equator and the Eastern or Western horizon.\n"
391               "Press Drift to measure drift, watch the Dec trend line.\n"
392               "Press Adjust and adjust your mount's altitude.\n"
393               "Repeat Drift/Adjust until alignment is complete.\n"
394               "Click Azimuth to repeat Azimuth adjustment."));
395         m_notesLabel->SetLabel(_("Altitude adjustment notes"));
396         m_notes->SetValue(pConfig->Profile.GetString("/DriftTool/Alt/Notes", wxEmptyString));
397         m_phaseBtn->SetLabel(_("< Azimuth"));
398     }
399 }
400 
UpdateModeState()401 void DriftToolWin::UpdateModeState()
402 {
403     wxString idleStatus;
404 
405 repeat:
406 
407     if (m_mode == MODE_DRIFT)
408     {
409         m_drift->Enable(false);
410         m_adjust->Enable(true);
411         EnableSlew(false);
412 
413         // restore subframes setting
414         pCamera->UseSubframes = m_save_use_subframes;
415 
416         // restore normal guider behavior for star lost events
417         pFrame->pGuider->SetIgnoreLostStarLooping(false);
418 
419         if (!m_drifting)
420         {
421             if (!pCamera->Connected ||
422                 !pMount || !pMount->IsConnected())
423             {
424                 idleStatus = _("Please connect a camera and a mount");
425                 m_mode = MODE_IDLE;
426                 goto repeat;
427             }
428 
429             if (!pMount->IsCalibrated())
430             {
431                 idleStatus = _("Please calibrate before starting drift alignment");
432                 m_mode = MODE_IDLE;
433                 goto repeat;
434             }
435 
436             if (!pFrame->CaptureActive)
437             {
438                 // loop exposures
439                 SetStatusText(_("Start Looping..."));
440                 pFrame->StartLoopingInteractive(_T("DriftTool:drift"));
441                 return;
442             }
443 
444             GUIDER_STATE st = pFrame->pGuider->GetState();
445             switch (st)
446             {
447             case STATE_UNINITIALIZED:
448             case STATE_CALIBRATED:
449             case STATE_SELECTING:
450                 if (!pFrame->pGuider->IsLocked())
451                 {
452                     SetStatusText(_("Auto-selecting a star"));
453                     // call Guider's AutoSelect directly to skip MyFrame::AutoSelectStar guide state check
454                     pFrame->pGuider->AutoSelect();
455                 }
456                 return;
457             case STATE_CALIBRATING_PRIMARY:
458             case STATE_CALIBRATING_SECONDARY:
459                 if (!pMount->IsCalibrated())
460                     SetStatusText(_("Waiting for calibration to complete..."));
461                 return;
462             case STATE_SELECTED:
463                 SetStatusText(_("Start guiding..."));
464                 pFrame->GuideButtonClick(false, _T("DriftTool:drift"));
465                 return;
466             case STATE_GUIDING: {
467                 // turn of dec guiding
468                 if (!m_need_end_dec_drift)
469                 {
470                     pMount->StartDecDrift();
471                     m_need_end_dec_drift = true;
472                 }
473                 // clear graph data
474                 SetStatusText(_("Drifting... click Adjust when done drifting"));
475                 wxCommandEvent dummy;
476                 pFrame->pGraphLog->OnButtonClear(dummy);
477                 pFrame->pGraphLog->EnableTrendLines(true);
478                 m_drifting = true;
479                 return;
480             }
481             case STATE_STOP:
482                 return;
483             }
484         }
485     }
486     else if (m_mode == MODE_ADJUST)
487     {
488         m_drift->Enable(true);
489         m_adjust->Enable(false);
490         m_drifting = false;
491         EnableSlew(m_can_slew);
492         SetStatusText(m_phase == PHASE_ADJUST_AZ ? _("Adjust azimuth, click Drift when done") :
493             _("Adjust altitude, click Drift when done"));
494 
495         // use full frames for adjust phase
496         pCamera->UseSubframes = false;
497 
498         if (pFrame->pGuider->IsGuiding())
499         {
500             // stop guiding but continue looping
501             pFrame->StartLoopingInteractive(_T("DriftTool:adjust"));
502 
503             // Set the lock position to the where the star has drifted to. This will be the center of the polar align circle.
504             pFrame->pGuider->SetLockPosition(pFrame->pGuider->CurrentPosition());
505             // Make sure guider does not react to star lost (like by invalidating the lock position) while adjustment is in progress
506             pFrame->pGuider->SetIgnoreLostStarLooping(true);
507             pFrame->pGraphLog->Refresh();  // polar align circle is updated in graph window's OnPaint handler
508         }
509     }
510     else // MODE_IDLE
511     {
512         m_drift->Enable(true);
513         m_adjust->Enable(true);
514         m_drifting = false;
515         EnableSlew(m_can_slew);
516         SetStatusText(idleStatus);
517 
518         // restore subframes setting
519         pCamera->UseSubframes = m_save_use_subframes;
520 
521         // restore normal guider behavior for star lost events
522         pFrame->pGuider->SetIgnoreLostStarLooping(false);
523 
524         if (pFrame->pGuider->IsGuiding())
525         {
526             // stop guiding but continue looping
527             pFrame->StartLoopingInteractive(_T("DriftTool:idle"));
528         }
529     }
530 }
531 
OnSlew(wxCommandEvent & evt)532 void DriftToolWin::OnSlew(wxCommandEvent& evt)
533 {
534     double raSlew = (double) m_raSlew->GetValue();
535     double decSlew = (double) m_decSlew->GetValue();
536 
537     double cur_ra, cur_dec, cur_st;
538     if (pPointingSource->GetCoordinates(&cur_ra, &cur_dec, &cur_st))
539     {
540         Debug.Write("Drift tool: slew failed to get scope coordinates\n");
541         return;
542     }
543 
544     double slew_ra = norm_ra(cur_st + (raSlew * 24.0 / 360.0));
545 
546     Debug.Write(wxString::Format("Drift tool: slew from ra %.2f, dec %.1f to ra %.2f, dec %.1f\n",
547         cur_ra, cur_dec, slew_ra, decSlew));
548 
549     if (pPointingSource->CanSlewAsync())
550     {
551         struct SlewInBg : public RunInBg
552         {
553             double ra, dec;
554             SlewInBg(wxWindow *parent, double ra_, double dec_)
555                 : RunInBg(parent, _("Slew"), _("Slewing...")), ra(ra_), dec(dec_)
556             {
557                 SetPopupDelay(100);
558             }
559             bool Entry()
560             {
561                 bool err = pPointingSource->SlewToCoordinatesAsync(ra, dec);
562                 if (err)
563                 {
564                     SetErrorMsg(_("Slew failed!"));
565                     return true;
566                 }
567                 while (pPointingSource->Slewing())
568                 {
569                     wxMilliSleep(500);
570                     if (IsCanceled())
571                     {
572                         pPointingSource->AbortSlew();
573                         SetErrorMsg(_("Slew was canceled"));
574                         break;
575                     }
576                 }
577                 return false;
578             }
579         };
580         SlewInBg bg(this, slew_ra, decSlew);
581         if (bg.Run())
582         {
583             SetStatusText(bg.GetErrorMsg());
584         }
585     }
586     else
587     {
588         wxBusyCursor busy;
589         m_slewing = true;
590         m_slew->Enable(false);
591         GetStatusBar()->PushStatusText(_("Slewing ..."));
592         if (pPointingSource->SlewToCoordinates(slew_ra, decSlew))
593         {
594             GetStatusBar()->PopStatusText();
595             m_slewing = false;
596             m_slew->Enable(true);
597             Debug.Write("Drift tool: slew failed\n");
598         }
599     }
600 
601     SaveRADec(m_phase, raSlew, decSlew);
602 }
603 
OnSaveCoords(wxCommandEvent & evt)604 void DriftToolWin::OnSaveCoords(wxCommandEvent& evt)
605 {
606     double raSlew = (double) m_raSlew->GetValue();
607     double decSlew = (double) m_decSlew->GetValue();
608 
609     SaveRADec(m_phase, raSlew, decSlew);
610     SetStatusText(_("Coordinates saved."));
611 }
612 
OnNotesText(wxCommandEvent & evt)613 void DriftToolWin::OnNotesText(wxCommandEvent& evt)
614 {
615     if (m_phase == PHASE_ADJUST_AZ)
616     {
617         pConfig->Profile.SetString("/DriftTool/Az/Notes", m_notes->GetValue());
618     }
619     else
620     {
621         pConfig->Profile.SetString("/DriftTool/Alt/Notes", m_notes->GetValue());
622     }
623 }
624 
OnDrift(wxCommandEvent & evt)625 void DriftToolWin::OnDrift(wxCommandEvent& evt)
626 {
627     Debug.Write("Drift tool: Drift\n");
628 
629     if (!m_location_prompt_done)
630     {
631         if (pPointingSource && pPointingSource->IsConnected())
632         {
633             bool error = pPointingSource->PreparePositionInteractive();
634             if (error)
635                 return;
636             m_location_prompt_done = true;
637         }
638     }
639 
640     m_mode = MODE_DRIFT;
641     UpdateModeState();
642 }
643 
OnAdjust(wxCommandEvent & evt)644 void DriftToolWin::OnAdjust(wxCommandEvent& evt)
645 {
646     Debug.Write("Drift tool: Adjust\n");
647     m_mode = MODE_ADJUST;
648     UpdateModeState();
649 }
650 
OnPhase(wxCommandEvent & evt)651 void DriftToolWin::OnPhase(wxCommandEvent& evt)
652 {
653     if (m_phase == PHASE_ADJUST_ALT)
654         m_phase = PHASE_ADJUST_AZ;
655     else
656         m_phase = PHASE_ADJUST_ALT;
657 
658     Debug.Write(wxString::Format("Drift tool: Phase %s\n", m_phase == PHASE_ADJUST_ALT ? wxS("Alt") : wxS("Az")));
659 
660     m_location_prompt_done = false;
661 
662     UpdatePhaseState();
663 
664     if (m_mode != MODE_IDLE)
665     {
666         m_mode = MODE_IDLE;
667         UpdateModeState();
668     }
669 }
670 
OnAppStateNotify(wxCommandEvent & evt)671 void DriftToolWin::OnAppStateNotify(wxCommandEvent& evt)
672 {
673     UpdateModeState();
674 }
675 
OnClose(wxCloseEvent & evt)676 void DriftToolWin::OnClose(wxCloseEvent& evt)
677 {
678     Debug.Write("Drift tool: Close DriftTool\n");
679 
680     if (m_need_end_dec_drift)
681     {
682         pMount->EndDecDrift();
683         pFrame->pGraphLog->EnableTrendLines(false);
684         m_need_end_dec_drift = false;
685     }
686 
687     // restore graph mode
688     pFrame->pGraphLog->SetMode(m_save_graph_mode);
689 
690     // restore graph scale
691     pConfig->Global.SetInt("/DriftTool/GraphLength", pFrame->pGraphLog->GetLength());
692     pFrame->pGraphLog->SetLength(m_save_graph_length);
693     pConfig->Global.SetInt("/DriftTool/GraphHeight", pFrame->pGraphLog->GetHeight());
694     pFrame->pGraphLog->SetHeight(m_save_graph_height);
695     pFrame->pGraphLog->Refresh();
696 
697     // turn sticky lock position back on if we disabled it
698     if (m_save_lock_pos_is_sticky)
699     {
700         pFrame->pGuider->SetLockPosIsSticky(true);
701         pFrame->tools_menu->FindItem(EEGG_STICKY_LOCK)->Check(true);
702     }
703 
704     // restore subframes setting
705     pCamera->UseSubframes = m_save_use_subframes;
706 
707     // restore normal guider behavior for star lost events
708     pFrame->pGuider->SetIgnoreLostStarLooping(false);
709 
710     // save the window position
711     int x, y;
712     GetPosition(&x, &y);
713     pConfig->Global.SetInt("/DriftTool/pos.x", x);
714     pConfig->Global.SetInt("/DriftTool/pos.y", y);
715 
716     // restore polar align circle correction factor
717     pFrame->pGuider->SetPolarAlignCircleCorrection(1.0);
718 
719     Destroy();
720 }
721 
UpdateScopeCoordinates(void)722 void DriftToolWin::UpdateScopeCoordinates(void)
723 {
724     if (!pMount)
725         return;
726 
727     double ra_hrs, dec_deg, st_hrs;
728     if (pPointingSource->GetCoordinates(&ra_hrs, &dec_deg, &st_hrs))
729         return; // error
730     double ra_ofs_deg = (ra_hrs - st_hrs) * (360.0 / 24.0);
731     if (ra_ofs_deg > 180.0)
732         ra_ofs_deg -= 360.0;
733     if (ra_ofs_deg <= -180.0)
734         ra_ofs_deg += 360.0;
735 
736     m_raCurrent->SetValue(wxString::Format("%+.f", ra_ofs_deg));
737     m_decCurrent->SetValue(wxString::Format("%+.f", dec_deg));
738 
739     // update polar align circle radius
740     if (m_siteLatLong.IsValid())
741     {
742         double correction;
743 
744         if (m_phase == PHASE_ADJUST_AZ)
745         {
746             // azimuth correction from "Star Offset Positioning for Polar Axis Alignment", Frank Barrett, 2/19/2010
747             double dec_r = radians(dec_deg);
748             if (fabs(dec_r) < Scope::DEC_COMP_LIMIT)
749             {
750                 double alt_r = radians(90.0 - m_siteLatLong.X + dec_deg);
751                 correction = cos(alt_r) / cos(dec_r);
752             }
753             else
754             {
755                 correction = 1.0;
756             }
757         }
758         else
759         {
760             // altitude correction:
761             //     convert scope coordinates (RA = a, Dec = d) to cartesian coords:
762             //     x = cos a cos d
763             //     y = sin a cos d
764             //     z = sin d
765             // altitude adjustment is rotation about x-axis, so correction factor is radius of
766             // circle of projection of scope vector onto the plane of the meridian (y-z plane)
767             //     r^2 = y^2 + z^2
768             // substitute y and z above and use a = 90 - h   (h = hour angle)
769             //
770             //  -ag
771             //
772             double ha_r = radians(ra_ofs_deg);
773             double cos_dec = cos(radians(dec_deg));
774             double cos_ha = cos(ha_r);
775             correction = sqrt(1. + cos_dec * cos_dec * (cos_ha * cos_ha - 1.));
776 
777             // drift rate for the altitude measurement is assumed to be measured at the horizon,
778             // but rate decreases as we move away from the horizon - Measuring Polar Axis Alignment
779             // Error, Frank Barrett 2nd Edition 2 / 19 / 2010, Equation (2)
780             if (fabs(ra_ofs_deg) > 15.)
781             {
782                 correction /= fabs(sin(ha_r));
783             }
784             else
785             {
786                 correction = 1.0;
787             }
788         }
789 
790         pFrame->pGuider->SetPolarAlignCircleCorrection(correction);
791     }
792 }
793 
OnTimer(wxTimerEvent & evt)794 void DriftToolWin::OnTimer(wxTimerEvent& evt)
795 {
796     UpdateScopeCoordinates();
797 
798     if (m_slewing)
799     {
800         if (!pPointingSource || !pPointingSource->Slewing())
801         {
802             m_slew->Enable(true);
803             m_slewing = false;
804             GetStatusBar()->PopStatusText(); // clear "slewing" message
805         }
806     }
807 }
808 
SetStatusText(const wxString & text,int number)809 void DriftToolWin::SetStatusText(const wxString &text, int number)
810 {
811     Debug.Write(wxString::Format("Drift tool: status: %s\n", text));
812     wxFrame::SetStatusText(text, number);
813 }
814 
CreateDriftToolWindow()815 wxWindow *DriftTool::CreateDriftToolWindow()
816 {
817     if (!pCamera)
818     {
819         wxMessageBox(_("Please connect a camera first."));
820         return 0;
821     }
822 
823     // confirm that image scale is specified
824 
825     if (pFrame->GetCameraPixelScale() == 1.0)
826     {
827         bool confirmed = ConfirmDialog::Confirm(_(
828             "The Drift Align tool is most effective when PHD2 knows your guide\n"
829             "scope focal length and camera pixel size.\n"
830             "\n"
831             "Enter your guide scope focal length on the Global tab in the Brain.\n"
832             "Enter your camera pixel size on the Camera tab in the Brain.\n"
833             "\n"
834             "Would you like to run the drift tool anyway?"),
835                 "/drift_tool_without_pixscale");
836 
837         if (!confirmed)
838         {
839             return 0;
840         }
841     }
842 
843     return new DriftToolWin();
844 }
845