1 /*
2     SPDX-FileCopyrightText: 2020 Hy Murveit <hy@murveit.com>
3 
4     SPDX-License-Identifier: GPL-2.0-or-later
5 */
6 
7 #include "guidelog.h"
8 
9 #include <math.h>
10 #include <cstdint>
11 
12 #include <QDateTime>
13 #include <QStandardPaths>
14 #include <QTextStream>
15 
16 #include "auxiliary/kspaths.h"
17 #include <version.h>
18 
19 // This class writes a guide log that is compatible with the phdlogview program.
20 // See https://openphdguiding.org/phd2-log-viewer/ for details on that program.
21 
22 namespace
23 {
24 
25 // These conversion aren't correct. I believe the KStars way of doing it, with RA_INC etc
26 // is better, however, it is consistent and will work with phdlogview.
directionString(GuideDirection direction)27 QString directionString(GuideDirection direction)
28 {
29     switch(direction)
30     {
31         case DEC_INC_DIR:
32             return "N";
33         case DEC_DEC_DIR:
34             return "S";
35         case RA_DEC_DIR:
36             return "E";
37         case RA_INC_DIR:
38             return "W";
39         case NO_DIR:
40             return "";
41     }
42     return "";
43 }
44 
directionStringLong(GuideDirection direction)45 QString directionStringLong(GuideDirection direction)
46 {
47     switch(direction)
48     {
49         case DEC_INC_DIR:
50             return "North";
51         case DEC_DEC_DIR:
52             return "South";
53         case RA_DEC_DIR:
54             return "East";
55         case RA_INC_DIR:
56             return "West";
57         case NO_DIR:
58             return "";
59     }
60     return "";
61 }
62 
pierSideString(ISD::Telescope::PierSide side)63 QString pierSideString(ISD::Telescope::PierSide side)
64 {
65     switch(side)
66     {
67         case ISD::Telescope::PierSide::PIER_WEST:
68             return QString("West");
69         case ISD::Telescope::PierSide::PIER_EAST:
70             return QString("East");
71         case ISD::Telescope::PierSide::PIER_UNKNOWN:
72             return QString("Unknown");
73     }
74     return QString("");
75 }
76 
degreesToHours(double degrees)77 double degreesToHours(double degrees)
78 {
79     return 24.0 * degrees / 360.0;
80 }
81 
82 } // namespace
83 
GuideLog()84 GuideLog::GuideLog()
85 {
86 }
87 
~GuideLog()88 GuideLog::~GuideLog()
89 {
90     endLog();
91 }
92 
appendToLog(const QString & lines)93 void GuideLog::appendToLog(const QString &lines)
94 {
95     if (!enabled)
96         return;
97     QTextStream out(&logFile);
98     out << lines;
99     out.flush();
100 }
101 
102 // Creates the filename and opens the file.
103 // Prints a line like the one below.
104 //   KStars version 3.4.0. PHD2 log version 2.5. Log enabled at 2019-11-21 00:00:48
startLog()105 void GuideLog::startLog()
106 {
107     QDir dir = QDir(KSPaths::writableLocation(QStandardPaths::AppDataLocation)).filePath("guidelogs");
108     dir.mkpath(".");
109 
110     logFileName = dir.filePath("guide_log-" + QDateTime::currentDateTime().toString("yyyy-MM-ddThh-mm-ss") + ".txt");
111     logFile.setFileName(logFileName);
112     logFile.open(QIODevice::WriteOnly | QIODevice::Text);
113 
114     appendToLog(QString("KStars version %1. PHD2 log version 2.5. Log enabled at %2\n\n")
115                 .arg(KSTARS_VERSION)
116                 .arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss")));
117 
118     initialized = true;
119 }
120 
121 // Prints a line like the one below and closes the file.
122 //   Log closed at 2019-11-21 08:46:38
endLog()123 void GuideLog::endLog()
124 {
125     if (!enabled || !initialized)
126         return;
127 
128     if (isGuiding && initialized)
129         endGuiding();
130 
131     appendToLog(QString("Log closed at %1\n")
132                 .arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss")));
133     logFile.close();
134 }
135 
136 // Output at the start of Guiding.
137 // Note that in the PHD2 generated versions of this log, there is a lot of guiding information here.
138 // We just output two lines which phdlogview needs, for pixel scale and RA/DEC.
startGuiding(const GuideInfo & info)139 void GuideLog::startGuiding(const GuideInfo &info)
140 {
141     if (!enabled)
142         return;
143     if (!initialized)
144         startLog();
145 
146     // Currently phdlogview just reads the Pixel scale value on the 2nd line, and
147     // just reads the Dec value on the 3rd line.
148     // Note the log wants hrs for RA, the input to this method is in degrees.
149     appendToLog(QString("Guiding Begins at %1\n"
150                         "Pixel scale = %2 arc-sec/px, Binning = %3, Focal length = %4 mm\n"
151                         "RA = %5 hr, Dec = %6 deg, Hour angle = N/A hr, Pier side = %7, "
152                         "Rotator pos = N/A, Alt = %8 deg, Az = %9 deg\n"
153                         "Mount = mount, xAngle = %10, xRate = %11, yAngle = %12, yRate = %13\n"
154                         "Frame,Time,mount,dx,dy,RARawDistance,DECRawDistance,RAGuideDistance,DECGuideDistance,"
155                         "RADuration,RADirection,DECDuration,DECDirection,XStep,YStep,StarMass,SNR,ErrorCode\n")
156                 .arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss"))
157                 .arg(QString::number(info.pixelScale, 'f', 2))
158                 .arg(info.binning)
159                 .arg(info.focalLength)
160                 .arg(QString::number(degreesToHours(info.ra), 'f', 2))
161                 .arg(QString::number(info.dec, 'f', 1))
162                 .arg(pierSideString(info.pierSide))
163                 .arg(QString::number(info.altitude, 'f', 1))
164                 .arg(QString::number(info.azimuth, 'f', 1))
165                 .arg(QString::number(info.xangle, 'f', 1))
166                 .arg(QString::number(info.xrate, 'f', 3))
167                 .arg(QString::number(info.yangle, 'f', 1))
168                 .arg(QString::number(info.yrate, 'f', 3)));
169 
170 
171     guideIndex = 1;
172     isGuiding = true;
173     timer.start();
174 }
175 
176 // Prints a line that looks something like this:
177 //   55,467.914,"Mount",-1.347,-2.160,2.319,-1.451,1.404,-0.987,303,W,218,N,,,2173,26.91,0
178 // See page 56-57 in https://openphdguiding.org/PHD2_User_Guide.pdf for definitions of the fields.
addGuideData(const GuideData & data)179 void GuideLog::addGuideData(const GuideData &data)
180 {
181     QString mountString = data.type == GuideData::MOUNT ? "\"Mount\"" : "\"DROP\"";
182     QString xStepString = "";
183     QString yStepString = "";
184     appendToLog(QString("%1,%2,%3,%4,%5,%6,%7,%8,%9,%10,%11,%12,%13,%14,%15,%16,%17,%18\n")
185                 .arg(guideIndex)
186                 .arg(QString::number(timer.elapsed() / 1000.0, 'f', 3))
187                 .arg(mountString)
188                 .arg(QString::number(data.dx, 'f', 3))
189                 .arg(QString::number(data.dy, 'f', 3))
190                 .arg(QString::number(data.raDistance, 'f', 3))
191                 .arg(QString::number(data.decDistance, 'f', 3))
192                 .arg(QString::number(data.raGuideDistance, 'f', 3))
193                 .arg(QString::number(data.decGuideDistance, 'f', 3))
194                 .arg(data.raDuration)
195                 .arg(directionString(data.raDirection))
196                 .arg(data.decDuration)
197                 .arg(directionString(data.decDirection))
198                 .arg(xStepString)
199                 .arg(yStepString)
200                 .arg(QString::number(data.mass, 'f', 0))
201                 .arg(QString::number(data.snr, 'f', 2))
202                 .arg(static_cast<int>(data.code)));
203     ++guideIndex;
204 }
205 
206 // Prints a line that looks like:
207 //   Guiding Ends at 2019-11-21 01:57:45
endGuiding()208 void GuideLog::endGuiding()
209 {
210     appendToLog(QString("Guiding Ends at %1\n\n")
211                 .arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss")));
212     isGuiding = false;
213 }
214 
215 // Note that in the PHD2 generated versions of this log, there is a lot of calibration information here.
216 // We just output two lines which phdlogview needs, for pixel scale and RA/DEC.
startCalibration(const GuideInfo & info)217 void GuideLog::startCalibration(const GuideInfo &info)
218 {
219     if (!enabled)
220         return;
221     if (!initialized)
222         startLog();
223     // Currently phdlogview just reads the Pixel scale value on the 2nd line, and
224     // just reads the Dec value on the 3rd line.
225     appendToLog(QString("Calibration Begins at %1\n"
226                         "Pixel scale = %2 arc-sec/px, Binning = %3, Focal length = %4 mm\n"
227                         "RA = %5 hr, Dec = %6 deg, Hour angle = N/A hr, Pier side = %7, "
228                         "Rotator pos = N/A, Alt = %8 deg, Az = %9 deg\n"
229                         "Direction,Step,dx,dy,x,y,Dist\n")
230                 .arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss"))
231                 .arg(QString::number(info.pixelScale, 'f', 2))
232                 .arg(info.binning)
233                 .arg(info.focalLength)
234                 .arg(QString::number(degreesToHours(info.ra), 'f', 2))
235                 .arg(QString::number(info.dec, 'f', 1))
236                 .arg(pierSideString(info.pierSide))
237                 .arg(QString::number(info.altitude, 'f', 1))
238                 .arg(QString::number(info.azimuth, 'f', 1)));
239 
240     calibrationIndex = 1;
241     timer.start();
242     lastCalibrationDirection = NO_DIR;
243 }
244 
245 // Prints a line that looks like:
246 //   West,2,-15.207,-1.037,54.800,58.947,15.242
addCalibrationData(GuideDirection direction,double x,double y,double xOrigin,double yOrigin)247 void GuideLog::addCalibrationData(GuideDirection direction, double x, double y, double xOrigin, double yOrigin)
248 {
249     if (direction != lastCalibrationDirection)
250         calibrationIndex = 1;
251     lastCalibrationDirection = direction;
252 
253     appendToLog(QString("%1,%2,%3,%4,%5,%6,%7\n")
254                 .arg(directionStringLong(direction))
255                 .arg(calibrationIndex)
256                 .arg(QString::number(x - xOrigin, 'f', 3))
257                 .arg(QString::number(y - yOrigin, 'f', 3))
258                 .arg(QString::number(x, 'f', 3))
259                 .arg(QString::number(y, 'f', 3))
260                 .arg(QString::number(hypot(x - xOrigin, y - yOrigin), 'f', 3)));
261 
262     // This is a little different than PHD2--they seem to count down in the reverse directions.
263     calibrationIndex++;
264 }
265 
266 // Prints a line that looks like:
267 //   West calibration complete. Angle = 106.8 deg
268 // Currently phdlogview ignores this line.
endCalibrationSection(GuideDirection direction,double degrees)269 void GuideLog::endCalibrationSection(GuideDirection direction, double degrees)
270 {
271     appendToLog(QString("%1 calibration complete. Angle = %2 deg\n")
272                 .arg(directionStringLong(direction))
273                 .arg(QString::number(degrees, 'f', 1)));
274 }
275 
276 // Prints two lines that look like:
277 //  Calibration guide speeds: RA = 191.5 a-s/s, Dec = 408.0 a-s/s
278 //  Calibration complete
279 // The failed version is not in the PHD2 log, will be ignored by the viewer.
endCalibration(double raSpeed,double decSpeed)280 void GuideLog::endCalibration(double raSpeed, double decSpeed)
281 {
282     if (raSpeed == 0 && decSpeed == 0)
283         appendToLog(QString("Calibration complete (Failed)\n\n"));
284     else
285         appendToLog(QString("Calibration guide speeds: RA = %1 a-s/s, Dec = %2 a-s/s\n"
286                             "Calibration complete\n\n")
287                     .arg(QString::number(raSpeed, 'f', 1))
288                     .arg(QString::number(decSpeed, 'f', 1)));
289 }
290 
ditherInfo(double dx,double dy,double x,double y)291 void GuideLog::ditherInfo(double dx, double dy, double x, double y)
292 {
293     appendToLog(QString("INFO: DITHER by %1, %2, new lock pos = %3, %4\n")
294                 .arg(QString::number(dx, 'f', 3))
295                 .arg(QString::number(dy, 'f', 3))
296                 .arg(QString::number(x, 'f', 3))
297                 .arg(QString::number(y, 'f', 3)));
298     // Below moved to ditherInfo from settleStartedInfo() to match phdlogview.
299     appendToLog("INFO: SETTLING STATE CHANGE, Settling started\n");
300 }
301 
pauseInfo()302 void GuideLog::pauseInfo()
303 {
304     appendToLog("INFO: Server received PAUSE\n");
305 }
306 
resumeInfo()307 void GuideLog::resumeInfo()
308 {
309     appendToLog("INFO: Server received RESUME\n");
310 }
311 
settleStartedInfo()312 void GuideLog::settleStartedInfo()
313 {
314     // This was moved to ditherInfo() to match phdlogview
315     // appendToLog("INFO: SETTLING STATE CHANGE, Settling started\n");
316 }
317 
settleCompletedInfo()318 void GuideLog::settleCompletedInfo()
319 {
320     appendToLog("INFO: SETTLING STATE CHANGE, Settling complete\n");
321 }
322