1 /*
2 * backlash_comp.cpp
3 * PHD Guiding
4 *
5 * Created by Bruce Waddington
6 * Copyright (c) 2015 Bruce Waddington and 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 Bret McKee, Dad Dog Development,
18 * Craig Stark, Stark Labs nor the names of its
19 * contributors may be used to endorse or promote products derived from
20 * this software without specific prior written permission.
21 *
22 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
23 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
26 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
27 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
28 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
29 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
30 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
31 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32 * POSSIBILITY OF SUCH DAMAGE.
33 *
34 */
35
36 #include "phd.h"
37 #include "backlash_comp.h"
38
39 #include <algorithm>
40
41 static const unsigned int MIN_COMP_AMOUNT = 20; // min pulse in ms, must be small enough to effectively disable blc
42 static const unsigned int MAX_COMP_AMOUNT = 8000; // max pulse in ms
43
44
45 class CorrectionTuple
46 {
47 public:
48 long timeSeconds;
49 double miss;
50
CorrectionTuple(long TimeInSecs,double Amount)51 CorrectionTuple(long TimeInSecs, double Amount)
52 {
53 timeSeconds = TimeInSecs;
54 miss = Amount;
55 }
56 };
57
58 class BLCEvent
59 {
60 public:
61 std::vector<CorrectionTuple> corrections;
62 bool initialOvershoot;
63 bool initialUndershoot;
64 bool stictionSeen;
65
BLCEvent()66 BLCEvent() {};
67
BLCEvent(long TimeSecs,double Amount)68 BLCEvent(long TimeSecs, double Amount)
69 {
70 corrections.push_back(CorrectionTuple(TimeSecs, Amount));
71 initialOvershoot = false;
72 initialUndershoot = false;
73 stictionSeen = false;
74 }
75
InfoCount() const76 size_t InfoCount() const
77 {
78 return corrections.size();
79 }
80
AddEventInfo(long TimeSecs,double Amount,double minMove)81 void AddEventInfo(long TimeSecs, double Amount, double minMove)
82 {
83 // Correction[0] is the deflection that triggered the BLC in the first place. Correction[1] is the first delta after the pulse was issued,
84 // Correction[2] is the (optional) subsequent delta, needed to detect stiction
85 if (InfoCount() < 3)
86 {
87 corrections.push_back(CorrectionTuple(TimeSecs, Amount)); // Regardless of size relative to min-move
88 if (fabs(Amount) > minMove)
89 {
90 if (InfoCount() == 2)
91 {
92 if (Amount > 0)
93 initialUndershoot = true;
94 else
95 initialOvershoot = true;
96
97 }
98 else
99 {
100 if (InfoCount() == 3)
101 {
102 stictionSeen = Amount < 0 && corrections[1].miss > 0; // 2nd follow-on miss was an over-shoot
103 }
104 }
105 }
106 }
107 }
108
109 };
110
111 // Basic operation
112 // Keep a record of the last <HISTORY_DEPTH> BLC events. Each event entry holds the initial BLC
113 // deflection and either one or two immediate follow-on deflections. The deflections are raw Dec
114 // amounts, unfiltered by min-move or guiding algorithm decisions. Recording of follow-on
115 // deflections is determined by windowOpen. The window is opened when the BLC is initially
116 // triggered and is then closed if a) A pulse-size adjustment is made based on the first follow-on
117 // deflection or b) 2 follow-on events have been recorded
118 // Algorithm behavior for adjusting BLC pulse size is in AdjustmentNeeded()
119
120 class BLCHistory
121 {
122 std::vector<BLCEvent> blcEvents;
123 int blcIndex = 0;
124 const int ENTRY_CAPACITY = 3;
125 const unsigned int HISTORY_DEPTH = 10;
126 bool windowOpen;
127 long timeBase;
128 int lastIncrease;
129
130 public:
131 struct RecentStats
132 {
133 int shortCount;
134 int longCount;
135 int stictionCount;
136 double avgInitialMiss;
137 double avgStictionAmount;
138
RecentStatsBLCHistory::RecentStats139 RecentStats() : shortCount(0), longCount(0), stictionCount(0), avgInitialMiss(0), avgStictionAmount(0)
140 {
141 }
142 };
143
WindowOpen() const144 bool WindowOpen() const
145 {
146 return windowOpen;
147 }
148
BLCHistory()149 BLCHistory()
150 {
151 windowOpen = false;
152 lastIncrease = 0;
153 timeBase = wxGetCurrentTime();
154 }
155
LogStatus(const wxString & Msg)156 static void LogStatus(const wxString& Msg)
157 {
158 Debug.Write(wxString::Format("BLC: %s\n", Msg));
159 }
160
CloseWindow()161 void CloseWindow()
162 {
163 windowOpen = false;
164 Debug.Write("BLC: window closed\n");
165 }
166
RecordNewBLCPulse(long when,double triggerDeflection)167 void RecordNewBLCPulse(long when, double triggerDeflection)
168 {
169 if (blcEvents.size() >= HISTORY_DEPTH)
170 {
171 blcEvents.erase(blcEvents.begin());
172 LogStatus("Oldest BLC event removed");
173 }
174 blcEvents.push_back(BLCEvent((when - timeBase), triggerDeflection));
175 blcIndex = blcEvents.size() - 1;
176 windowOpen = true;
177 }
178
AddDeflection(long When,double Amt,double MinMove)179 bool AddDeflection(long When, double Amt, double MinMove)
180 {
181 bool added = false;
182 if (blcIndex >= 0 && blcEvents[blcIndex].InfoCount() < ENTRY_CAPACITY)
183 {
184 blcEvents[blcIndex].AddEventInfo(When-timeBase, Amt, MinMove);
185 added = true;
186 //LogStatus("Deflection entry added for event " + std::to_string(blcIndex));
187 }
188 else
189 {
190 CloseWindow();
191 }
192 return added;
193 }
194
RemoveOldestOvershoots(int howMany)195 void RemoveOldestOvershoots(int howMany)
196 {
197 for (int ct = 1; ct <= howMany; ct++)
198 {
199 for (unsigned int inx = 0; inx < blcEvents.size() - 1; inx++)
200 {
201 if (blcEvents[inx].initialOvershoot)
202 {
203 blcEvents.erase(blcEvents.begin() + inx);
204 blcIndex = blcEvents.size() - 1;
205 break;
206 }
207 }
208 }
209 }
210
RemoveOldestStictions(int howMany)211 void RemoveOldestStictions(int howMany)
212 {
213 for (int ct = 1; ct <= howMany; ct++)
214 {
215 for (unsigned int inx = 0; inx < blcEvents.size() - 1; inx++)
216 {
217 if (blcEvents[inx].stictionSeen)
218 {
219 blcEvents.erase(blcEvents.begin() + inx);
220 blcIndex = blcEvents.size() - 1;
221 break;
222 }
223 }
224 }
225 }
226
ClearHistory()227 void ClearHistory()
228 {
229 blcEvents.clear();
230 CloseWindow();
231 LogStatus("History cleared");
232 }
233
234 // Stats over some number of recent events, returns the average initial miss
GetStats(int numEvents,RecentStats * Results) const235 double GetStats(int numEvents, RecentStats* Results) const
236 {
237 int bottom = std::max(0, blcIndex - (numEvents - 1));
238 double sum = 0;
239 double stictionSum = 0;
240 int ct = 0;
241 for (int inx = blcIndex; inx >= bottom; inx--)
242 {
243 const BLCEvent& evt = blcEvents[inx];
244 if (evt.initialOvershoot)
245 Results->longCount++;
246 else
247 Results->shortCount++;
248 if (evt.stictionSeen)
249 {
250 Results->stictionCount++;
251 stictionSum += evt.corrections[2].miss;
252 }
253 // Average only the initial misses immediately following the blcs
254 if (evt.InfoCount() > 1)
255 {
256 sum += evt.corrections[1].miss;
257 ct++;
258 }
259 }
260 if (ct > 0)
261 Results->avgInitialMiss = sum / ct;
262 else
263 Results->avgInitialMiss = 0;
264 if (Results->stictionCount > 0)
265 Results->avgStictionAmount = stictionSum / Results->stictionCount;
266 else
267 Results->avgStictionAmount = 0;
268 return Results->avgInitialMiss;
269 }
270
AdjustmentNeeded(double miss,double minMove,double yRate,double * correction)271 bool AdjustmentNeeded(double miss, double minMove, double yRate, double* correction)
272 {
273 bool adjust = false;
274 const BLCEvent *currEvent;
275 RecentStats stats;
276 *correction = 0;
277 double avgInitMiss = 0;
278 if (blcIndex >= 0)
279 {
280 avgInitMiss = GetStats(HISTORY_DEPTH, &stats);
281 currEvent = &blcEvents[blcIndex];
282 wxString deflections = " Deflections: 0=" + std::to_string(currEvent->corrections[0].miss) + ", 1:" +
283 wxString(std::to_string(currEvent->corrections[1].miss));
284 if (currEvent->InfoCount() > 2)
285 deflections += ", 2:" + std::to_string(currEvent->corrections[2].miss);
286 LogStatus(wxString::Format("History state: CurrMiss=%0.2f, AvgInitMiss=%0.2f, ShCount=%d, LgCount=%d, SticCount=%d, %s",
287 miss, stats.avgInitialMiss, stats.shortCount, stats.longCount, stats.stictionCount, deflections));
288 }
289 else
290 return false;
291
292 if (fabs(miss) >= minMove) // Most recent miss was big enough to look at
293 {
294 int corr;
295 corr = (int)(floor(abs(avgInitMiss) / yRate) + 0.5); // unsigned correction value
296 if (miss > 0)
297 // UNDER-SHOOT-------------------------------
298 {
299 if (avgInitMiss > 0)
300 {
301 // Might want to increase the blc value - but check for stiction and history of over-corrections
302 // Don't make any changes before getting two follow-on displacements after last BLC
303 if (currEvent->InfoCount() == ENTRY_CAPACITY)
304 {
305 // Check for stiction history
306 if (stats.stictionCount > 2)
307 LogStatus("Under-shoot, no adjustment because of stiction history");
308 else
309 {
310 // Check for over-shoot history
311 if (stats.longCount >= 2) // 2 or more over-shoots in window
312 LogStatus("Under-shoot; no adjustment because of over-shoot history");
313 else
314 {
315 adjust = true;
316 *correction = corr;
317 lastIncrease = corr;
318 LogStatus("Under-shoot: nominal increase by " + std::to_string(corr));
319 }
320 }
321 }
322 else
323 LogStatus("Under-shoot, no adjustment, waiting for more data");
324 }
325 else
326 {
327 LogStatus("Under-shoot, no adjustment, avgInitialMiss <= 0");
328 CloseWindow();
329 }
330 }
331 else
332 // OVER-SHOOT, miss < 0--------------------------------------
333 {
334 std::string msg = "";
335 if (currEvent->stictionSeen)
336 {
337 if (stats.stictionCount > 1) // Seeing and low min-move can look like stiction, don't over-react
338 {
339 msg = "Over-shoot, stiction seen, ";
340 double stictionCorr = (int)(floor(abs(stats.avgStictionAmount) / yRate) + 0.5);
341 *correction = -stictionCorr;
342 RemoveOldestStictions(1);
343 adjust = true;
344 LogStatus(msg + "nominal decrease by " + std::to_string(*correction));
345 }
346 else
347 LogStatus("Over-shoot, first stiction event, no adjustment");
348 }
349 else if (stats.longCount > stats.shortCount && blcIndex >= 4)
350 {
351 msg = "Recent history of over-shoots, ";
352 *correction = -corr;
353 RemoveOldestOvershoots(2);
354 adjust = true;
355 LogStatus(msg + "nominal decrease by " + std::to_string(*correction));
356 }
357 else if (avgInitMiss <= -0.1)
358 {
359 msg = "Average miss indicates over-shooting, ";
360 *correction = -corr;
361 adjust = true;
362 LogStatus(msg + "nominal decrease by " + std::to_string(*correction));
363 }
364 else
365 {
366 correction = 0;
367 std::string msg = "Over-shoot, no adjustment based on avgInitialMiss";
368 LogStatus(msg);
369 CloseWindow();
370 }
371 }
372 }
373 else
374 {
375 LogStatus("No correction, Miss < min_move");
376 }
377
378 if (adjust)
379 CloseWindow();
380 return adjust;
381 }
382 };
383
BacklashComp(Scope * scope)384 BacklashComp::BacklashComp(Scope *scope)
385 {
386 m_pScope = scope;
387 m_pHistory = new BLCHistory();
388 int lastAmt = pConfig->Profile.GetInt("/" + m_pScope->GetMountClassName() + "/DecBacklashPulse", 0);
389 int lastFloor = pConfig->Profile.GetInt("/" + m_pScope->GetMountClassName() + "/DecBacklashFloor", 0);
390 int lastCeiling = pConfig->Profile.GetInt("/" + m_pScope->GetMountClassName() + "/DecBacklashCeiling", 0);
391 if (lastAmt > 0)
392 m_compActive = pConfig->Profile.GetBoolean("/" + m_pScope->GetMountClassName() + "/BacklashCompEnabled", false);
393 else
394 m_compActive = false;
395 SetCompValues(lastAmt, lastFloor, lastCeiling);
396 m_lastDirection = NONE;
397 if (m_compActive)
398 Debug.Write(wxString::Format("BLC: Enabled with correction = %d ms, Floor = %d, Ceiling = %d, %s\n",
399 m_pulseWidth, m_adjustmentFloor, m_adjustmentCeiling, m_fixedSize ? "Fixed" : "Adjustable"));
400 else
401 Debug.Write("BLC: Backlash compensation is disabled\n");
402 }
403
~BacklashComp()404 BacklashComp::~BacklashComp()
405 {
406 delete m_pHistory;
407 }
408
GetBacklashPulseMaxValue()409 int BacklashComp::GetBacklashPulseMaxValue()
410 {
411 return MAX_COMP_AMOUNT;
412 }
413
GetBacklashPulseMinValue()414 int BacklashComp::GetBacklashPulseMinValue()
415 {
416 return MIN_COMP_AMOUNT;
417 }
418
GetBacklashCompSettings(int * pulseWidth,int * floor,int * ceiling) const419 void BacklashComp::GetBacklashCompSettings(int* pulseWidth, int* floor, int* ceiling) const
420 {
421 *pulseWidth = m_pulseWidth;
422 *floor = m_adjustmentFloor;
423 *ceiling = m_adjustmentCeiling;
424 }
425
426 // Private method to be sure all comp values are rational and comply with limits
427 // May change max-move value for Dec depending on the context
SetCompValues(int requestedSize,int floor,int ceiling)428 void BacklashComp::SetCompValues(int requestedSize, int floor, int ceiling)
429 {
430 m_pulseWidth = wxMax(0, wxMin(requestedSize, MAX_COMP_AMOUNT));
431 if (floor > m_pulseWidth || floor < MIN_COMP_AMOUNT) // Coming from GA or user input makes no sense
432 m_adjustmentFloor = MIN_COMP_AMOUNT;
433 else
434 m_adjustmentFloor = floor;
435 if (ceiling < m_pulseWidth)
436 m_adjustmentCeiling = wxMin(1.50 * m_pulseWidth, MAX_COMP_AMOUNT);
437 else
438 m_adjustmentCeiling = wxMin(ceiling, MAX_COMP_AMOUNT);
439 m_fixedSize = abs(m_adjustmentCeiling - m_adjustmentFloor) < MIN_COMP_AMOUNT;
440 if (m_pulseWidth > m_pScope->GetMaxDecDuration() && m_compActive)
441 m_pScope->SetMaxDecDuration(m_pulseWidth);
442 }
443
444 // Public method to ask for a set of backlash comp settings. Ceiling == 0 implies compute a default
SetBacklashPulseWidth(int ms,int floor,int ceiling)445 void BacklashComp::SetBacklashPulseWidth(int ms, int floor, int ceiling)
446 {
447 if (m_pulseWidth != ms || m_adjustmentFloor != floor || m_adjustmentCeiling != ceiling)
448 {
449 int oldBLC = m_pulseWidth;
450 SetCompValues(ms, floor, ceiling);
451 pFrame->NotifyGuidingParam("Backlash comp amount", m_pulseWidth);
452 Debug.Write(wxString::Format("BLC: Comp pulse set to %d ms, Floor = %d ms, Ceiling = %d ms, %s\n",
453 m_pulseWidth, m_adjustmentFloor, m_adjustmentCeiling, m_fixedSize ? "Fixed" : "Adjustable"));
454 if (abs(m_pulseWidth - oldBLC) > 100)
455 {
456 m_pHistory->ClearHistory();
457 m_pHistory->CloseWindow();
458 }
459 }
460
461 pConfig->Profile.SetInt("/" + m_pScope->GetMountClassName() + "/DecBacklashPulse", m_pulseWidth);
462 pConfig->Profile.SetInt("/" + m_pScope->GetMountClassName() + "/DecBacklashFloor", m_adjustmentFloor);
463 pConfig->Profile.SetInt("/" + m_pScope->GetMountClassName() + "/DecBacklashCeiling", m_adjustmentCeiling);
464 }
465
EnableBacklashComp(bool enable)466 void BacklashComp::EnableBacklashComp(bool enable)
467 {
468 if (m_compActive != enable)
469 {
470 pFrame->NotifyGuidingParam("Backlash comp enabled", enable);
471 if (enable)
472 ResetBLCState();
473 }
474
475 m_compActive = enable;
476 pConfig->Profile.SetBoolean("/" + m_pScope->GetMountClassName() + "/BacklashCompEnabled", m_compActive);
477 Debug.Write(wxString::Format("BLC: Backlash comp %s, Comp pulse = %d ms\n", m_compActive ? "enabled" : "disabled", m_pulseWidth));
478 }
479
ResetBLCState()480 void BacklashComp::ResetBLCState()
481 {
482 if (m_compActive)
483 {
484 m_lastDirection = NONE;
485 m_pHistory->CloseWindow();
486 Debug.Write("BLC: Last direction was reset\n");
487 }
488 }
489
TrackBLCResults(unsigned int moveOptions,double yRawOffset)490 void BacklashComp::TrackBLCResults(unsigned int moveOptions, double yRawOffset)
491 {
492 if (!m_compActive)
493 return;
494
495 if (!(moveOptions & MOVEOPT_USE_BLC))
496 {
497 // Calibration-type move that can move mount in Dec w/out notifying blc about direction
498 ResetBLCState();
499 return;
500 }
501
502 // only track algorithm result moves, do not track "fast
503 // recovery after dither" moves or deduced moves or AO bump
504 // moves
505 bool isAlgoResultMove = (moveOptions & MOVEOPT_ALGO_RESULT) != 0;
506 if (!isAlgoResultMove)
507 {
508 // non-algo blc move occurred before follow-up data were acquired for previous blc
509 m_pHistory->CloseWindow();
510 return;
511 }
512
513 if (!m_pHistory->WindowOpen() || m_fixedSize)
514 return;
515
516 // An earlier BLC was applied and we're tracking follow-up results
517
518 // Record the history even if residual error is zero. Sign convention has nothing to do with N or S direction - only whether we
519 // needed more correction (+) or less (-)
520 GUIDE_DIRECTION dir = yRawOffset > 0.0 ? DOWN : UP;
521 double yDistance = fabs(yRawOffset);
522 double miss;
523 if (dir == m_lastDirection)
524 miss = yDistance; // + => we needed more of the same, under-shoot
525 else
526 miss = -yDistance; // over-shoot
527
528 double minMove = fmax(m_pScope->GetYGuideAlgorithm()->GetMinMove(), 0.); // Algo w/ no min-move returns -1
529
530 m_pHistory->AddDeflection(wxGetCurrentTime(), miss, minMove);
531
532 double adjustment;
533 if (!m_pHistory->AdjustmentNeeded(miss, minMove, m_pScope->MountCal().yRate, &adjustment))
534 return;
535
536 int newBLC;
537 double nominalBLC = m_pulseWidth + adjustment;
538 if (nominalBLC > m_pulseWidth)
539 {
540 newBLC = ROUND(fmin(m_pulseWidth * 1.1, nominalBLC));
541 if (newBLC > m_adjustmentCeiling)
542 {
543 Debug.Write(wxString::Format("BLC: Pulse increase limited by ceiling of %d\n", m_adjustmentCeiling));
544 newBLC = m_adjustmentCeiling;
545 }
546 }
547 else
548 {
549 newBLC = ROUND(fmax(0.8 * m_pulseWidth, nominalBLC));
550 if (newBLC < m_adjustmentFloor)
551 {
552 Debug.Write(wxString::Format("BLC: Pulse decrease limited by floor of %d\n", m_adjustmentFloor));
553 newBLC = m_adjustmentFloor;
554 }
555 }
556 Debug.Write(wxString::Format("BLC: Pulse adjusted to %d\n", newBLC));
557 pConfig->Profile.SetInt("/" + m_pScope->GetMountClassName() + "/DecBacklashPulse", newBLC);
558 SetCompValues(newBLC, m_adjustmentFloor, m_adjustmentCeiling);
559 }
560
ApplyBacklashComp(unsigned int moveOptions,double yGuideDistance,int * yAmount)561 void BacklashComp::ApplyBacklashComp(unsigned int moveOptions, double yGuideDistance, int *yAmount)
562 {
563 if (!(moveOptions & MOVEOPT_USE_BLC))
564 return;
565 if (!m_compActive || m_pulseWidth <= 0 || yGuideDistance == 0.0)
566 return;
567
568 GUIDE_DIRECTION dir = yGuideDistance > 0.0 ? DOWN : UP;
569 bool isAlgoResultMove = (moveOptions & MOVEOPT_ALGO_RESULT) != 0;
570
571 if (m_lastDirection != NONE && dir != m_lastDirection)
572 {
573 *yAmount += m_pulseWidth;
574
575 if (isAlgoResultMove)
576 {
577 // Only track results or make adjustments for algorithm-controlled blc's
578 m_pHistory->RecordNewBLCPulse(wxGetCurrentTime(), yGuideDistance);
579 }
580 else
581 {
582 m_pHistory->CloseWindow();
583 Debug.Write("BLC: Compensation needed for non-algo type move\n");
584 }
585
586 Debug.Write(wxString::Format("BLC: Dec direction reversal from %s to %s, backlash comp pulse of %d applied\n",
587 m_pScope->DirectionStr(m_lastDirection), m_pScope->DirectionStr(dir), m_pulseWidth));
588 }
589 else if (!isAlgoResultMove)
590 {
591 Debug.Write("BLC: non-algo type move will not reverse Dec direction, no blc applied\n");
592 }
593
594 m_lastDirection = dir;
595 }
596
597 // Class for implementing the backlash graph dialog
598 class BacklashGraph : public wxDialog
599 {
600 public:
601 BacklashGraph(wxDialog *parent, const std::vector<double> &northSteps, const std::vector<double> &southSteps, int PulseSize);
602 wxBitmap CreateGraph(int graphicWidth, int graphicHeight, const std::vector<double> &northSteps, const std::vector<double> &southSteps, int PulseSize);
603 };
604
BacklashGraph(wxDialog * parent,const std::vector<double> & northSteps,const std::vector<double> & southSteps,int PulseSize)605 BacklashGraph::BacklashGraph(wxDialog *parent, const std::vector<double> &northSteps, const std::vector<double> &southSteps, int PulseSize)
606 : wxDialog(parent, wxID_ANY, wxGetTranslation(_("Backlash Results")), wxDefaultPosition, wxSize(500, 400))
607 {
608 // Just but a big button area for the graph with a button below it
609 wxBoxSizer *vSizer = new wxBoxSizer(wxVERTICAL);
610 // Use a bitmap button so we don't waste cycles in paint events
611 wxBitmap graph_bitmap = CreateGraph(450, 300, northSteps, southSteps, PulseSize);
612 wxStaticBitmap *graph = new wxStaticBitmap(this, wxID_ANY, graph_bitmap, wxDefaultPosition, wxDefaultSize, 0);
613 vSizer->Add(graph, 0, wxALIGN_CENTER_HORIZONTAL | wxALL | wxFIXED_MINSIZE, 5);
614
615 // ok button because we're modal
616 vSizer->Add(
617 CreateButtonSizer(wxOK),
618 wxSizerFlags(0).Expand().Border(wxALL, 10));
619
620 SetSizerAndFit(vSizer);
621 }
622
CreateGraph(int bmpWidth,int bmpHeight,const std::vector<double> & northSteps,const std::vector<double> & southSteps,int PulseSize)623 wxBitmap BacklashGraph::CreateGraph(int bmpWidth, int bmpHeight, const std::vector<double> &northSteps, const std::vector<double> &southSteps, int PulseSize)
624 {
625 wxMemoryDC dc;
626 wxBitmap bmp(bmpWidth, bmpHeight, -1);
627 wxColour decColor = pFrame->pGraphLog->GetDecOrDyColor();
628 wxColour idealColor("WHITE");
629 wxPen axisPen("GREY", 3, wxPENSTYLE_CROSS_HATCH);
630 wxPen decPen(decColor, 3, wxPENSTYLE_SOLID);
631 wxPen idealPen(idealColor, 3, wxPENSTYLE_SOLID);
632 wxBrush decBrush(decColor, wxBRUSHSTYLE_SOLID);
633 wxBrush idealBrush(idealColor, wxBRUSHSTYLE_SOLID);
634
635 double xScaleFactor;
636 double yScaleFactor;
637 int xOrigin;
638 int yOrigin;
639 int ptRadius;
640 int graphWindowWidth;
641 int graphWindowHeight;
642 int numNorth;
643 double northInc;
644 int numSouth;
645
646 // Find the max excursion from the origin in order to scale the points to fit the bitmap
647 double maxDec = -9999.0;
648 double minDec = 9999.0;
649 for (auto it = northSteps.begin(); it != northSteps.end(); ++it)
650 {
651 maxDec = wxMax(maxDec, *it);
652 minDec = wxMin(minDec, *it);
653 }
654
655 for (auto it = southSteps.begin(); it != southSteps.end(); ++it)
656 {
657 maxDec = wxMax(maxDec, *it);
658 minDec = wxMin(minDec, *it);
659 }
660
661 graphWindowWidth = bmpWidth;
662 graphWindowHeight = 0.7 * bmpHeight;
663 yScaleFactor = (graphWindowHeight) / (maxDec - minDec + 1);
664 xScaleFactor = (graphWindowWidth) / (northSteps.size() + southSteps.size());
665
666 // Since we get mount coordinates, north steps will always be in ascending order
667 numNorth = northSteps.size();
668 northInc = (northSteps.at(numNorth - 1) - northSteps.at(0)) / numNorth;
669 numSouth = southSteps.size(); // May not be the same as numNorth if some sort of problem occurred
670
671 dc.SelectObject(bmp);
672 dc.SetBackground(*wxBLACK_BRUSH);
673
674 dc.SetFont(wxFont(12, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL));
675 dc.Clear();
676
677 // Bottom and top labels
678 xOrigin = graphWindowWidth / 2;
679 yOrigin = graphWindowHeight + 40; // Leave room at the top for labels and such
680 dc.SetTextForeground(idealColor);
681 dc.DrawText(_("Ideal"), 0.7 * graphWindowWidth, bmpHeight - 25);
682 dc.SetTextForeground(decColor);
683 dc.DrawText(_("Measured"), 0.2 * graphWindowWidth, bmpHeight - 25);
684 dc.DrawText(_("North"), 0.1 * graphWindowWidth, 10);
685 dc.DrawText(_("South"), 0.8 * graphWindowWidth, 10);
686 if (PulseSize > 0)
687 {
688 wxString pulseSzTxt = wxString::Format(_("Pulse size = %d ms"), PulseSize);
689 int w, h;
690 GetTextExtent(pulseSzTxt, &w, &h);
691
692 dc.DrawText(pulseSzTxt, wxMax(0, graphWindowWidth / 2 - w / 2 - 10), yOrigin - (h + 10));
693 }
694 // Draw the axes
695 dc.SetPen(axisPen);
696 dc.DrawLine(0, yOrigin, graphWindowWidth, yOrigin); // x
697 dc.DrawLine(xOrigin, yOrigin, xOrigin, 0); // y
698
699 // Draw the north steps
700 dc.SetPen(decPen);
701 dc.SetBrush(decBrush);
702 ptRadius = 1;
703
704 for (int i = 0; i < numNorth; i++)
705 {
706 dc.DrawCircle(wxPoint(i * xScaleFactor, round(yOrigin - (northSteps.at(i) - minDec) * yScaleFactor)), ptRadius);
707 }
708
709 // Draw the south steps
710 for (int i = 0; i < numSouth; i++)
711 {
712 dc.DrawCircle(wxPoint((i + numNorth) * xScaleFactor, round(yOrigin - (southSteps.at(i) - minDec) * yScaleFactor)), ptRadius);
713 }
714
715 // Now show an ideal south recovery line
716 dc.SetPen(idealPen);
717 dc.SetBrush(idealBrush);
718
719 double peakSouth = southSteps.at(0);
720 for (int i = 1; i <= numNorth; i++)
721 {
722 wxPoint where = wxPoint((i + numNorth)* xScaleFactor, round(yOrigin - (peakSouth - i * northInc - minDec) * yScaleFactor));
723 dc.DrawCircle(where, ptRadius);
724 }
725
726 dc.SelectObject(wxNullBitmap);
727 return bmp;
728 }
729
730 // ------------------- BacklashTool Implementation
731
BacklashTool()732 BacklashTool::BacklashTool()
733 {
734 m_scope = TheScope();
735
736 m_lastDecGuideRate = GetLastDecGuideRate(); // -1 if we aren't calibrated
737 if (m_lastDecGuideRate > 0)
738 m_bltState = BLT_STATE_INITIALIZE;
739 else
740 {
741 m_bltState = BLT_STATE_ABORTED;
742 m_lastStatus = _("Backlash measurement cannot be run - please re-run your mount calibration");
743 Debug.Write("BLT: Could not get calibration data\n");
744 }
745 m_backlashResultPx = 0;
746 m_backlashResultMs = 0;
747 m_cumClearingDistance = 0;
748 m_backlashExemption = false;
749 }
750
~BacklashTool()751 BacklashTool::~BacklashTool()
752 {
753
754 }
755
IsGraphable()756 bool BacklashTool::IsGraphable()
757 {
758 return m_southBLSteps.size() > 0;
759 }
760
GetLastDecGuideRate()761 double BacklashTool::GetLastDecGuideRate()
762 {
763 double rtnVal;
764 Calibration lastCalibration;
765 m_scope->GetLastCalibration(&lastCalibration);
766
767 if (lastCalibration.isValid)
768 {
769 rtnVal = lastCalibration.yRate;
770 }
771 else
772 {
773 rtnVal = -1;
774 }
775 return rtnVal;
776 }
777
StartMeasurement(double DriftPerMin)778 void BacklashTool::StartMeasurement(double DriftPerMin)
779 {
780 m_bltState = BLT_STATE_INITIALIZE;
781 m_driftPerSec = DriftPerMin / 60.0;
782 m_northBLSteps.clear();
783 m_southBLSteps.clear();
784 m_northStats.ClearAll();
785 DecMeasurementStep(pFrame->pGuider->CurrentPosition());
786 }
787
StopMeasurement()788 void BacklashTool::StopMeasurement()
789 {
790 m_bltState = BLT_STATE_ABORTED;
791 DecMeasurementStep(pFrame->pGuider->CurrentPosition());
792 }
793
OutOfRoom(const wxSize & frameSize,double camX,double camY,int margin)794 static bool OutOfRoom(const wxSize& frameSize, double camX, double camY, int margin)
795 {
796 return camX < margin ||
797 camY < margin ||
798 camX >= frameSize.GetWidth() - margin ||
799 camY >= frameSize.GetHeight() - margin;
800 }
801
802 // Measure the apparent backlash by looking at the first south moves, looking to see when the mount moves consistently at the expected rate
803 // Goal is to establish a good seed value for backlash compensation, not to accurately measure the hardware performance
ComputeBacklashPx(double * bltPx,int * bltMs,double * northRate)804 BacklashTool::MeasurementResults BacklashTool::ComputeBacklashPx(double* bltPx, int* bltMs, double* northRate)
805 {
806 double expectedAmount;
807 double expectedMagnitude;
808 double earlySouthMoves = 0;
809 double blPx = 0;
810 double northDelta = 0;
811 double driftPxPerFrame;
812 double nRate = 0.;
813 BacklashTool::MeasurementResults rslt;
814
815 *bltPx = 0;
816 *bltMs = 0;
817 *northRate = m_lastDecGuideRate;
818 if (m_northBLSteps.size() > 3)
819 {
820 // figure out the drift-related corrections
821 double driftAmtPx = m_driftPerSec * (m_msmtEndTime - m_msmtStartTime) / 1000; // amount of drift in px for entire north measurement period
822 int stepCount = m_northStats.GetCount();
823 northDelta = m_northStats.GetSum();
824 nRate = fabs((northDelta - driftAmtPx) / (stepCount * m_pulseWidth)); // drift-corrected empirical measure of north rate
825 driftPxPerFrame = driftAmtPx / stepCount;
826 Debug.Write(wxString::Format("BLT: Drift correction of %0.2f px applied to total north moves of %0.2f px, %0.3f px/frame\n", driftAmtPx, northDelta, driftPxPerFrame));
827 Debug.Write(wxString::Format("BLT: Empirical north rate = %.2f px/s \n", nRate * 1000));
828
829 // Compute an expected movement of 90% of the median delta north moves (px). Use the 90% tolerance to accept situations where the south rate
830 // never matches the north rate yet the mount is moving consistently. Allow smoothing for odd mounts that produce sequences of short-long-short-long...
831 expectedAmount = 0.9 * m_northStats.GetMedian();
832 expectedMagnitude = fabs(expectedAmount);
833 int goodSouthMoves = 0;
834 double lastSouthMove = 0;
835 bool smoothing = false;
836 for (int step = 1; step < m_southBLSteps.size(); step++)
837 {
838 double southMove = m_southBLSteps[step] - m_southBLSteps[step-1];
839 earlySouthMoves += southMove;
840 if (southMove < 0 && (fabs(southMove) >= expectedMagnitude || fabs(southMove + lastSouthMove / 2.0) > expectedMagnitude)) // Big enough move and in the correct (south) direction
841 {
842 if (fabs(southMove) < expectedMagnitude)
843 smoothing = true;
844 goodSouthMoves++;
845 // We want two consecutive south moves that meet or exceed the expected magnitude. This sidesteps situations where the mount shows a "false start" south
846 if (goodSouthMoves == 2)
847 {
848 if (smoothing)
849 Debug.Write("BLT: Smoothing applied to south data points\n");
850 // bl = sum(expected moves) - sum(actual moves) - (drift correction for that period)
851 blPx = step * expectedMagnitude - fabs(earlySouthMoves - step * driftPxPerFrame); // drift-corrected backlash amount
852 if (blPx * nRate < -200)
853 rslt = MEASUREMENT_SANITY; // large negative number
854 else
855 if (blPx >= 0.7 * northDelta)
856 rslt = MEASUREMENT_TOO_FEW_NORTH; // bl large compared to total north moves
857 else
858 rslt = MEASUREMENT_VALID;
859 if (blPx < 0)
860 {
861 Debug.Write(wxString::Format("BLT: Negative measurement = %0.2f px, forcing to zero\n", blPx));
862 blPx = 0;
863 }
864 break;
865 }
866 }
867 else
868 {
869 if (goodSouthMoves > 0)
870 goodSouthMoves--;
871 }
872 lastSouthMove = southMove;
873 }
874 if (goodSouthMoves < 2)
875 rslt = MEASUREMENT_TOO_FEW_SOUTH;
876 }
877 else
878 rslt = MEASUREMENT_TOO_FEW_NORTH;
879 // Update the ref variables
880 *bltPx = blPx;
881 *bltMs = (int)(blPx / nRate);
882 *northRate = nRate;
883 return rslt;
884 }
885
DecMeasurementStep(const PHD_Point & currentCamLoc)886 void BacklashTool::DecMeasurementStep(const PHD_Point& currentCamLoc)
887 {
888 double decDelta = 0.;
889 double amt = 0;
890 // double fakeDeltas []= {0, -5, -2, 2, 4, 5, 5, 5, 5 };
891 PHD_Point currMountLocation;
892 double tol;
893 try
894 {
895 if (m_scope->TransformCameraCoordinatesToMountCoordinates(currentCamLoc, currMountLocation))
896 throw ERROR_INFO("BLT: CamToMount xForm failed");
897 if (m_bltState != BLT_STATE_INITIALIZE)
898 {
899 decDelta = currMountLocation.Y - m_markerPoint.Y;
900 m_cumClearingDistance += decDelta; // use signed value
901 //if (m_bltState == BLT_STATE_CLEAR_NORTH) // DEBUG ONLY
902 // decDelta = fakeDeltas[wxMin(m_stepCount, 7)];
903 }
904 Debug.Write("BLT: Entering DecMeasurementStep, state = " + std::to_string(m_bltState) + "\n");
905 switch (m_bltState)
906 {
907 case BLT_STATE_INITIALIZE:
908 m_stepCount = 0;
909 m_markerPoint = currMountLocation;
910 m_startingPoint = currMountLocation;
911 // Compute pulse size for clearing backlash - just use the last known guide rate
912 if (m_lastDecGuideRate <= 0)
913 m_lastDecGuideRate = GetLastDecGuideRate(); // try it again, maybe the user has since calibrated
914 if (m_lastDecGuideRate > 0)
915 {
916 m_pulseWidth = BACKLASH_EXPECTED_DISTANCE * 1.25 / m_lastDecGuideRate; // px/px_per_ms, bump it to sidestep near misses
917 m_acceptedMoves = 0;
918 m_lastClearRslt = 0;
919 m_cumClearingDistance = 0;
920 m_backlashExemption = false;
921 m_Rslt = MEASUREMENT_VALID;
922 // Get this state machine in synch with the guider state machine - let it drive us, starting with backlash clearing step
923 m_bltState = BLT_STATE_CLEAR_NORTH;
924 m_scope->SetGuidingEnabled(true);
925 pFrame->pGuider->EnableMeasurementMode(true); // Measurement results now come to us
926 }
927 else
928 {
929 m_bltState = BLT_STATE_ABORTED;
930 m_lastStatus = _("Backlash measurement cannot be run - Dec guide rate not available");
931 Debug.Write("BLT: Could not get calibration data\n");
932 }
933 break;
934
935 case BLT_STATE_CLEAR_NORTH:
936 // Want to see the mount moving north for 3 consecutive moves of >= expected distance pixels
937 if (m_stepCount == 0)
938 {
939 // Get things moving with the first clearing pulse
940 Debug.Write(wxString::Format("BLT starting North backlash clearing using pulse width of %d,"
941 " looking for moves >= %d px\n", m_pulseWidth, BACKLASH_EXPECTED_DISTANCE));
942 pFrame->ScheduleAxisMove(m_scope, NORTH, m_pulseWidth, MOVEOPTS_CALIBRATION_MOVE);
943 m_stepCount = 1;
944 m_lastStatus = wxString::Format(_("Clearing North backlash, step %d"), m_stepCount);
945 m_lastStatusDebug = wxString::Format("Clearing North backlash, step %d", m_stepCount);
946 break;
947 }
948 if (fabs(decDelta) >= BACKLASH_EXPECTED_DISTANCE)
949 {
950 if (m_acceptedMoves == 0 || (m_lastClearRslt * decDelta) > 0) // Just starting or still moving in same direction
951 {
952 m_acceptedMoves++;
953 Debug.Write(wxString::Format("BLT accepted clearing move of %0.2f\n", decDelta));
954 }
955 else
956 {
957 m_acceptedMoves = 0; // Reset on a direction reversal
958 Debug.Write(wxString::Format("BLT rejected clearing move of %0.2f, direction reversal\n", decDelta));
959 }
960 }
961 else
962 Debug.Write(wxString::Format("BLT backlash clearing move of %0.2f px was not large enough\n", decDelta));
963 if (m_acceptedMoves < BACKLASH_MIN_COUNT) // More work to do
964 {
965 if (m_stepCount < MAX_CLEARING_STEPS)
966 {
967 if (fabs(m_cumClearingDistance) > BACKLASH_EXEMPTION_DISTANCE)
968 {
969 // We moved the mount a substantial distance north but the individual moves were too small - probably a bad calibration,
970 // so let the user proceed with backlash measurement before we push the star too far
971 Debug.Write(wxString::Format("BLT: Cum backlash of %0.2f px is at least half of expected, continue with backlash measurement\n", m_cumClearingDistance));
972 m_backlashExemption = true;
973 }
974 else
975 {
976 if (!OutOfRoom(pCamera->FullSize, currentCamLoc.X, currentCamLoc.Y, pFrame->pGuider->GetMaxMovePixels()))
977 {
978 pFrame->ScheduleAxisMove(m_scope, NORTH, m_pulseWidth, MOVEOPTS_CALIBRATION_MOVE);
979 m_stepCount++;
980 m_markerPoint = currMountLocation;
981 m_lastClearRslt = decDelta;
982 m_lastStatus = wxString::Format(_("Clearing North backlash, step %d (up to limit of %d)"), m_stepCount, MAX_CLEARING_STEPS);
983 m_lastStatusDebug = wxString::Format("Clearing North backlash, step %d (up to limit of %d)", m_stepCount, MAX_CLEARING_STEPS);
984 Debug.Write(wxString::Format("BLT: %s, LastDecDelta = %0.2f px\n", m_lastStatusDebug, decDelta));
985 break;
986 }
987 }
988 }
989 else
990 {
991 m_lastStatus = _("Could not clear North backlash - test failed");
992 m_Rslt = MEASUREMENT_BL_NOT_CLEARED;
993 throw (wxString("BLT: Could not clear north backlash"));
994 }
995 }
996 if (m_acceptedMoves >= BACKLASH_MIN_COUNT || m_backlashExemption || OutOfRoom(pCamera->FullSize, currentCamLoc.X, currentCamLoc.Y, pFrame->pGuider->GetMaxMovePixels())) // Ok to go ahead with actual backlash measurement
997 {
998 m_bltState = BLT_STATE_STEP_NORTH;
999 double totalBacklashCleared = m_stepCount * m_pulseWidth;
1000 // Want to move the mount North at >=500 ms, regardless of image scale. But reduce pulse width if it would exceed 80% of the tracking rectangle -
1001 // need to leave some room for seeing deflections and dec drift
1002 m_pulseWidth = wxMax((int)NORTH_PULSE_SIZE, m_scope->GetCalibrationDuration());
1003 m_pulseWidth = wxMin(m_pulseWidth, (int)floor(0.7 * (double)pFrame->pGuider->GetMaxMovePixels() / m_lastDecGuideRate));
1004 m_stepCount = 0;
1005 // Move 50% more than the backlash we cleared or >=8 secs, whichever is greater. We want to leave plenty of room
1006 // for giving South moves time to clear backlash and actually get moving
1007 m_northPulseCount = wxMax((MAX_NORTH_PULSES + m_pulseWidth - 1) / m_pulseWidth,
1008 totalBacklashCleared * 1.5 / m_pulseWidth); // Up to 8 secs
1009
1010 Debug.Write(wxString::Format("BLT: Starting North moves at Dec=%0.2f\n", currMountLocation.Y));
1011 m_msmtStartTime = ::wxGetUTCTimeMillis().GetValue();
1012 // falling through to start moving North
1013 }
1014
1015 case BLT_STATE_STEP_NORTH:
1016 if (m_stepCount < m_northPulseCount && !OutOfRoom(pCamera->FullSize, currentCamLoc.X, currentCamLoc.Y, pFrame->pGuider->GetMaxMovePixels()))
1017 {
1018 m_lastStatus = wxString::Format(_("Moving North for %d ms, step %d / %d"), m_pulseWidth, m_stepCount + 1, m_northPulseCount);
1019 m_lastStatusDebug = wxString::Format("Moving North for %d ms, step %d / %d", m_pulseWidth, m_stepCount + 1, m_northPulseCount);
1020 double deltaN;
1021 if (m_stepCount >= 1)
1022 {
1023 deltaN = currMountLocation.Y - m_northBLSteps.back();
1024 m_northStats.AddGuideInfo(m_stepCount, deltaN, 0);
1025 }
1026 else
1027 {
1028 deltaN = 0;
1029 m_markerPoint = currMountLocation; // Marker point at start of Dec moves North
1030 }
1031 Debug.Write(wxString::Format("BLT: %s, DecLoc = %0.2f, DeltaDec = %0.2f\n", m_lastStatusDebug, currMountLocation.Y, deltaN));
1032 m_northBLSteps.push_back(currMountLocation.Y);
1033 pFrame->ScheduleAxisMove(m_scope, NORTH, m_pulseWidth, MOVEOPTS_CALIBRATION_MOVE);
1034 m_stepCount++;
1035 break;
1036 }
1037 else
1038 {
1039 // Either got finished or ran out of room
1040 m_msmtEndTime = ::wxGetUTCTimeMillis().GetValue();
1041 double deltaN = 0;
1042 if (m_stepCount >= 1)
1043 {
1044 deltaN = currMountLocation.Y - m_northBLSteps.back();
1045 m_northStats.AddGuideInfo(m_stepCount, deltaN, 0);
1046 }
1047 Debug.Write(wxString::Format("BLT: North pulses ended at Dec location %0.2f, TotalDecDelta=%0.2f px, LastDeltaDec = %0.2f\n", currMountLocation.Y, decDelta, deltaN));
1048 m_northBLSteps.push_back(currMountLocation.Y);
1049 if (m_stepCount < m_northPulseCount)
1050 {
1051 if (m_stepCount < 0.5 * m_northPulseCount)
1052 {
1053 m_lastStatus = _("Star too close to edge for accurate measurement of backlash. Choose a star farther from the edge.");
1054 m_Rslt = MEASUREMENT_TOO_FEW_NORTH;
1055 throw (wxString("BLT: Too few north moves"));
1056 }
1057 Debug.Write("BLT: North pulses truncated, too close to frame edge\n");
1058 }
1059 m_northPulseCount = m_stepCount;
1060 m_stepCount = 0;
1061 m_bltState = BLT_STATE_STEP_SOUTH;
1062 // falling through to moving back South
1063 }
1064
1065 case BLT_STATE_STEP_SOUTH:
1066 if (m_stepCount < m_northPulseCount)
1067 {
1068 m_lastStatus = wxString::Format(_("Moving South for %d ms, step %d / %d"), m_pulseWidth, m_stepCount + 1, m_northPulseCount);
1069 m_lastStatusDebug = wxString::Format("Moving South for %d ms, step %d / %d", m_pulseWidth, m_stepCount + 1, m_northPulseCount);
1070 Debug.Write(wxString::Format("BLT: %s, DecLoc = %0.2f\n", m_lastStatusDebug, currMountLocation.Y));
1071 m_southBLSteps.push_back(currMountLocation.Y);
1072 pFrame->ScheduleAxisMove(m_scope, SOUTH, m_pulseWidth, MOVEOPTS_CALIBRATION_MOVE);
1073 m_stepCount++;
1074 break;
1075 }
1076
1077 // Now see where we ended up - fall through to computing and testing a correction
1078 Debug.Write(wxString::Format("BLT: South pulses ended at Dec location %0.2f\n", currMountLocation.Y));
1079 m_southBLSteps.push_back(currMountLocation.Y);
1080 m_endSouth = currMountLocation;
1081 m_bltState = BLT_STATE_TEST_CORRECTION;
1082 m_stepCount = 0;
1083 // fall through
1084
1085 case BLT_STATE_TEST_CORRECTION:
1086 if (m_stepCount == 0)
1087 {
1088 m_Rslt = ComputeBacklashPx(&m_backlashResultPx, &m_backlashResultMs, &m_northRate);
1089 if (m_Rslt != MEASUREMENT_VALID)
1090 {
1091 // Abort the test and show an explanatory status in the GA dialog
1092 switch (m_Rslt)
1093 {
1094 case MEASUREMENT_SANITY:
1095 m_lastStatus = _("Dec movements too erratic - test failed");
1096 throw (wxString("BLT: Calculation failed sanity check"));
1097 break;
1098 case MEASUREMENT_TOO_FEW_NORTH:
1099 // Don't throw an exception - the test was completed but the bl result is not accurate - handle it in the GA UI
1100 break;
1101 case MEASUREMENT_TOO_FEW_SOUTH:
1102 m_lastStatus = _("Mount never established consistent south moves - test failed");
1103 throw (wxString("BLT: Too few acceptable south moves"));
1104 break;
1105 default:
1106 break;
1107 }
1108 }
1109
1110 double sigmaPx;
1111 double sigmaMs;
1112 GetBacklashSigma(&sigmaPx, &sigmaMs);
1113 Debug.Write(wxString::Format("BLT: Trial backlash amount is %0.2f px, %d ms, sigma = %0.1f px\n", m_backlashResultPx, m_backlashResultMs,
1114 sigmaPx));
1115 if (m_backlashResultMs > 0)
1116 {
1117 // Don't push the guide star outside the tracking region
1118 if (m_backlashResultPx < pFrame->pGuider->GetMaxMovePixels())
1119 {
1120 m_lastStatus = wxString::Format(_("Issuing test backlash correction of %d ms"), m_backlashResultMs);
1121 Debug.Write(m_lastStatus + "\n");
1122 // This should put us back roughly to where we issued the big North pulse unless the backlash is very large
1123 pFrame->ScheduleAxisMove(m_scope, SOUTH, m_backlashResultMs, MOVEOPTS_CALIBRATION_MOVE);
1124 m_stepCount++;
1125 break;
1126 }
1127 else
1128 {
1129 int maxFrameMove = (int)floor((double)0.8 * pFrame->pGuider->GetMaxMovePixels() / m_northRate);
1130 Debug.Write(wxString::Format("BLT: Clearing pulse is very large, issuing max S move of %d\n", maxFrameMove));
1131 pFrame->ScheduleAxisMove(m_scope, SOUTH, maxFrameMove, MOVEOPTS_CALIBRATION_MOVE); // One more pulse to cycle the state machine
1132 m_stepCount = 0;
1133 // Can't fine-tune the pulse size, just try to restore the star to < MaxMove of error
1134 m_bltState = BLT_STATE_RESTORE;
1135 break;
1136 }
1137 }
1138 else
1139 {
1140 m_bltState = BLT_STATE_RESTORE;
1141 m_stepCount = 0;
1142 // fall through, no need for test pulse
1143 }
1144
1145 }
1146 // See how close we came, maybe fine-tune a bit
1147 if (m_bltState == BLT_STATE_TEST_CORRECTION)
1148 {
1149 Debug.Write(wxString::Format("BLT: Trial backlash pulse resulted in net DecDelta = %0.2f px, Dec Location %0.2f\n", decDelta, currMountLocation.Y));
1150 tol = TRIAL_TOLERANCE_AS / pFrame->GetCameraPixelScale(); // tolerance in units of px
1151 if (fabs(decDelta) > tol) // decDelta = (current - markerPoint)
1152 {
1153 double pulse_delta = fabs(currMountLocation.Y - m_endSouth.Y); // How far we moved with the test pulse
1154 double target_delta = fabs(m_markerPoint.Y - m_endSouth.Y); // How far we needed to go
1155 if ((m_endSouth.Y - m_markerPoint.Y) * decDelta < 0) // Sign change, went too far
1156 {
1157 //m_backlashResultMs *= target_delta / pulse_delta;
1158 Debug.Write(wxString::Format("BLT: Nominal backlash value over-shot by %0.2f X\n", target_delta / pulse_delta));
1159 }
1160 else
1161 {
1162 Debug.Write(wxString::Format("BLT: Nominal backlash value under-shot by %0.2f X\n", target_delta / pulse_delta));
1163 }
1164 }
1165 else
1166 Debug.Write(wxString::Format("BLT: Nominal backlash pulse resulted in final delta of %0.1f a-s\n", fabs(decDelta) * pFrame->GetCameraPixelScale()));
1167 }
1168
1169 m_bltState = BLT_STATE_RESTORE;
1170 Debug.Write("BLT: normal result, moving to state=restore\n");
1171 m_stepCount = 0;
1172 // fall through
1173
1174 case BLT_STATE_RESTORE:
1175 // We could be a considerable distance from where we started, so get back close to the starting point without losing the star
1176 if (m_stepCount == 0)
1177 {
1178 Debug.Write(wxString::Format("BLT: Starting Dec position at %0.2f, Ending Dec position at %0.2f\n", m_markerPoint.Y, currMountLocation.Y));
1179 amt = fabs(currMountLocation.Y - m_startingPoint.Y);
1180 if (amt > pFrame->pGuider->GetMaxMovePixels()) // Too big, try to move guide star closer to starting position
1181 {
1182 m_restoreCount = (int)floor((amt / m_northRate) / m_pulseWidth);
1183 m_restoreCount = wxMin(m_restoreCount, 10); // Don't spend forever at it, something probably went wrong
1184 Debug.Write(wxString::Format("BLT: Final restore distance is %0.1f px, approx %d steps\n", amt, m_restoreCount));
1185 m_stepCount = 0;
1186 }
1187 else
1188 m_bltState = BLT_STATE_WRAPUP;
1189 }
1190 if (m_stepCount < m_restoreCount)
1191 {
1192
1193 pFrame->ScheduleAxisMove(m_scope, SOUTH, m_pulseWidth, MOVEOPTS_CALIBRATION_MOVE);
1194 m_stepCount++;
1195 m_lastStatus = _("Restoring star position");
1196 Debug.Write(wxString::Format("BLT: Issuing restore pulse count %d of %d ms\n", m_stepCount, m_pulseWidth));
1197 break;
1198 }
1199 m_bltState = BLT_STATE_WRAPUP;
1200 Debug.Write("BLT: normal result, moving to state=wrap-up\n");
1201 // fall through
1202
1203 case BLT_STATE_WRAPUP:
1204 m_lastStatus = _("Measurement complete");
1205 CleanUp();
1206 m_bltState = BLT_STATE_COMPLETED;
1207 break; // This will cycle the guider state machine and get normal guiding going
1208
1209 case BLT_STATE_COMPLETED: // Shouldn't happen
1210 break;
1211
1212 case BLT_STATE_ABORTED:
1213 m_lastStatus = _("Measurement halted");
1214 Debug.Write("BLT: measurement process halted by user or by error\n");
1215 CleanUp();
1216 break;
1217 } // end of switch on state
1218 }
1219 catch (const wxString& msg)
1220 {
1221 POSSIBLY_UNUSED(msg);
1222 Debug.Write(wxString::Format("BLT: Exception thrown in logical state %d\n", (int)m_bltState));
1223 m_bltState = BLT_STATE_ABORTED;
1224 Debug.Write("BLT: " + m_lastStatus + "\n");
1225 CleanUp();
1226 }
1227
1228 Debug.Write("BLT: Exiting DecMeasurementStep\n");
1229 }
1230
GetBacklashSigma(double * SigmaPx,double * SigmaMs)1231 void BacklashTool::GetBacklashSigma(double* SigmaPx, double* SigmaMs)
1232 {
1233 if ((m_Rslt == MEASUREMENT_VALID || m_Rslt == BacklashTool::MEASUREMENT_TOO_FEW_NORTH) && m_northStats.GetCount() > 1)
1234 {
1235 // Sigma of mean for north moves + sigma of two measurements going south, added in quadrature
1236 double variance = m_northStats.GetVariance();
1237 int count = m_northStats.GetCount();
1238 *SigmaPx = sqrt((variance / count) + (2 * variance / (count - 1)));
1239 *SigmaMs = *SigmaPx / m_northRate;
1240 }
1241 else
1242 {
1243 *SigmaPx = 0;
1244 *SigmaMs = 0;
1245 }
1246 }
1247
1248 // Launch modal dlg to show backlash test
ShowGraph(wxDialog * pGA,const std::vector<double> & northSteps,const std::vector<double> & southSteps,int PulseSize)1249 void BacklashTool::ShowGraph(wxDialog *pGA, const std::vector<double> &northSteps, const std::vector<double> &southSteps, int PulseSize)
1250 {
1251 BacklashGraph dlg(pGA, northSteps, southSteps, PulseSize);
1252 dlg.ShowModal();
1253 }
1254
CleanUp()1255 void BacklashTool::CleanUp()
1256 {
1257 m_scope->GetBacklashComp()->ResetBLCState(); // Normal guiding will start, don't want old BC state applied
1258 pFrame->pGuider->EnableMeasurementMode(false);
1259 Debug.Write("BLT: Cleanup completed\n");
1260 }
1261
1262 //------------------------------ End of BacklashTool implementation
1263