1 /*
2 SPDX-FileCopyrightText: 2016 Jasem Mutlaq <mutlaqja@ikarustech.com>
3
4 SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
7 #include "remoteastrometryparser.h"
8
9 #include "align.h"
10 #include "ekos_align_debug.h"
11 #include "Options.h"
12 #include "indi/clientmanager.h"
13 #include "indi/driverinfo.h"
14 #include "indi/guimanager.h"
15 #include "indi/indidevice.h"
16
17 #include <indicom.h>
18
19 #include <KMessageBox>
20
21 namespace Ekos
22 {
RemoteAstrometryParser()23 RemoteAstrometryParser::RemoteAstrometryParser() : AstrometryParser()
24 {
25 }
26
init()27 bool RemoteAstrometryParser::init()
28 {
29 if (remoteAstrometry == nullptr)
30 {
31 align->appendLogText(
32 i18n("Cannot set solver to remote. The Ekos equipment profile must include the astrometry Auxiliary driver."));
33 return false;
34 }
35
36 return true;
37 }
38
verifyIndexFiles(double,double)39 void RemoteAstrometryParser::verifyIndexFiles(double, double)
40 {
41 }
42
startSolver(const QString & filename,const QStringList & args,bool generated)43 bool RemoteAstrometryParser::startSolver(const QString &filename, const QStringList &args, bool generated)
44 {
45 INDI_UNUSED(generated);
46
47 QFile fp(filename);
48 if (fp.open(QIODevice::ReadOnly) == false)
49 {
50 align->appendLogText(i18n("Cannot open file %1 for reading.", filename));
51 emit solverFailed();
52 return false;
53 }
54
55 auto solverSwitch = remoteAstrometry->getBaseDevice()->getSwitch("ASTROMETRY_SOLVER");
56 auto solverBLOB = remoteAstrometry->getBaseDevice()->getBLOB("ASTROMETRY_DATA");
57
58 if (!solverSwitch || !solverBLOB)
59 {
60 align->appendLogText(i18n("Failed to find solver properties."));
61 fp.close();
62 emit solverFailed();
63 return false;
64 }
65
66 sendArgs(args);
67
68 auto enableSW = solverSwitch->findWidgetByName("ASTROMETRY_SOLVER_ENABLE");
69 if (enableSW->getState() == ISS_OFF)
70 {
71 IUResetSwitch(solverSwitch);
72 enableSW->setState(ISS_ON);
73 remoteAstrometry->getDriverInfo()->getClientManager()->sendNewSwitch(solverSwitch);
74 }
75
76 // #PS: TODO
77 IBLOB *bp = &(solverBLOB->bp[0]);
78
79 bp->bloblen = bp->size = fp.size();
80
81 bp->blob = (uint8_t *)realloc(bp->blob, bp->size);
82 if (bp->blob == nullptr)
83 {
84 align->appendLogText(i18n("Not enough memory for file %1.", filename));
85 fp.close();
86 emit solverFailed();
87 return false;
88 }
89
90 memcpy(bp->blob, fp.readAll().constData(), bp->size);
91
92 solverRunning = true;
93
94 remoteAstrometry->getDriverInfo()->getClientManager()->startBlob(solverBLOB->device, solverBLOB->name, timestamp());
95
96 #if (INDI_VERSION_MINOR >= 4 && INDI_VERSION_RELEASE >= 2)
97 remoteAstrometry->getDriverInfo()->getClientManager()->sendOneBlob(bp);
98 #else
99 remoteAstrometry->getDriverInfo()->getClientManager()->sendOneBlob(bp->name, bp->size, bp->format, bp->blob);
100 #endif
101
102 remoteAstrometry->getDriverInfo()->getClientManager()->finishBlob();
103
104 align->appendLogText(i18n("Starting remote solver..."));
105 solverTimer.start();
106
107 return true;
108 }
109
sendArgs(const QStringList & args)110 bool RemoteAstrometryParser::sendArgs(const QStringList &args)
111 {
112 auto solverSettings = remoteAstrometry->getBaseDevice()->getText("ASTROMETRY_SETTINGS");
113
114 if (!solverSettings)
115 {
116 align->appendLogText(i18n("Failed to find solver settings."));
117 emit solverFailed();
118 return false;
119 }
120
121 QStringList solverArgs = args;
122 // Add parity option if none is give and we already know parity before
123 // and is NOT a blind solve
124 if (Options::astrometryDetectParity() && parity.isEmpty() == false && args.contains("parity") == false &&
125 (args.contains("-3") || args.contains("-L")))
126 solverArgs << "--parity" << parity;
127
128 //for (int i = 0; i < solverSettings->ntp; i++)
129 for (auto &it : *solverSettings)
130 {
131 // 2016-10-20: Disable setting this automatically since remote device might have different
132 // settings
133 /*if (!strcmp(solverSettings->tp[i].name, "ASTROMETRY_SETTINGS_BINARY"))
134 IUSaveText(&solverSettings->tp[i], Options::astrometrySolver().toLatin1().constData());*/
135 if (it.isNameMatch("ASTROMETRY_SETTINGS_OPTIONS"))
136 it.setText(solverArgs.join(" ").toLatin1().constData());
137 }
138
139 remoteAstrometry->getDriverInfo()->getClientManager()->sendNewText(solverSettings);
140 INDI_D *guiDevice = GUIManager::Instance()->findGUIDevice(remoteAstrometry->getDeviceName());
141 if (guiDevice)
142 guiDevice->updateTextGUI(solverSettings);
143
144 return true;
145 }
146
setEnabled(bool enable)147 void RemoteAstrometryParser::setEnabled(bool enable)
148 {
149 auto solverSwitch = remoteAstrometry->getBaseDevice()->getSwitch("ASTROMETRY_SOLVER");
150
151 if (!solverSwitch)
152 return;
153
154 auto enableSW = solverSwitch->findWidgetByName("ASTROMETRY_SOLVER_ENABLE");
155 auto disableSW = solverSwitch->findWidgetByName("ASTROMETRY_SOLVER_DISABLE");
156
157 if (!enableSW || !disableSW)
158 return;
159
160 if (enable && enableSW->getState() == ISS_OFF)
161 {
162 solverSwitch->reset();
163 enableSW->setState(ISS_ON);
164 remoteAstrometry->getDriverInfo()->getClientManager()->sendNewSwitch(solverSwitch);
165 solverRunning = true;
166 qCDebug(KSTARS_EKOS_ALIGN) << "Enabling remote solver...";
167 }
168 else if (enable == false && disableSW->s == ISS_OFF)
169 {
170 solverSwitch->reset();
171 disableSW->setState(ISS_ON);
172 remoteAstrometry->getDriverInfo()->getClientManager()->sendNewSwitch(solverSwitch);
173 solverRunning = false;
174 qCDebug(KSTARS_EKOS_ALIGN) << "Disabling remote solver...";
175 }
176 }
177
setCCD(const QString & ccd)178 bool RemoteAstrometryParser::setCCD(const QString &ccd)
179 {
180 targetCCD = ccd;
181
182 if (!remoteAstrometry)
183 return false;
184
185 auto activeDevices = remoteAstrometry->getBaseDevice()->getText("ACTIVE_DEVICES");
186
187 if (!activeDevices)
188 return false;
189
190 auto activeCCD = activeDevices->findWidgetByName("ACTIVE_CCD");
191
192 if (!activeCCD)
193 return false;
194
195 // If same device, no need to update
196 if (QString(activeCCD->getText()) == ccd)
197 return true;
198
199 activeCCD->setText(ccd.toLatin1().data());
200 remoteAstrometry->getDriverInfo()->getClientManager()->sendNewText(activeDevices);
201
202 return true;
203 }
204
stopSolver()205 bool RemoteAstrometryParser::stopSolver()
206 {
207 if (solverRunning == false)
208 return true;
209
210 // Disable solver
211 auto svp = remoteAstrometry->getBaseDevice()->getSwitch("ASTROMETRY_SOLVER");
212 if (!svp)
213 return false;
214
215 auto disableSW = svp->findWidgetByName("ASTROMETRY_SOLVER_DISABLE");
216 if (disableSW->getState() == ISS_OFF)
217 {
218 svp->reset();
219 disableSW->setState(ISS_ON);
220 remoteAstrometry->getDriverInfo()->getClientManager()->sendNewSwitch(svp);
221 }
222
223 solverRunning = false;
224
225 return true;
226 }
227
setAstrometryDevice(ISD::GDInterface * device)228 void RemoteAstrometryParser::setAstrometryDevice(ISD::GDInterface *device)
229 {
230 if (device == remoteAstrometry)
231 return;
232
233 remoteAstrometry = device;
234
235 remoteAstrometry->disconnect(this);
236
237 connect(remoteAstrometry, SIGNAL(switchUpdated(ISwitchVectorProperty*)), this,
238 SLOT(checkStatus(ISwitchVectorProperty*)));
239 connect(remoteAstrometry, SIGNAL(numberUpdated(INumberVectorProperty*)), this,
240 SLOT(checkResults(INumberVectorProperty*)));
241
242 if (targetCCD.isEmpty() == false)
243 setCCD(targetCCD);
244 }
245
checkStatus(ISwitchVectorProperty * svp)246 void RemoteAstrometryParser::checkStatus(ISwitchVectorProperty *svp)
247 {
248 if (solverRunning == false || strcmp(svp->name, "ASTROMETRY_SOLVER"))
249 return;
250
251 if (svp->s == IPS_ALERT)
252 {
253 stopSolver();
254 align->appendLogText(i18n("Solver failed. Try again."));
255 emit solverFailed();
256 return;
257 }
258 // In case the remote solver started solving by listening to ACTIVE_CCD BLOB remotely via snooping
259 // then we need to start the timer.
260 else if (svp->s == IPS_BUSY)
261 {
262 solverTimer.restart();
263 }
264 }
265
checkResults(INumberVectorProperty * nvp)266 void RemoteAstrometryParser::checkResults(INumberVectorProperty *nvp)
267 {
268 if (solverRunning == false || strcmp(nvp->name, "ASTROMETRY_RESULTS") || nvp->s != IPS_OK)
269 return;
270
271 double pixscale, ra, de, orientation;
272 pixscale = ra = de = orientation = -1000;
273
274 for (int i = 0; i < nvp->nnp; i++)
275 {
276 if (!strcmp(nvp->np[i].name, "ASTROMETRY_RESULTS_PIXSCALE"))
277 pixscale = nvp->np[i].value;
278 else if (!strcmp(nvp->np[i].name, "ASTROMETRY_RESULTS_ORIENTATION"))
279 orientation = nvp->np[i].value;
280 else if (!strcmp(nvp->np[i].name, "ASTROMETRY_RESULTS_RA"))
281 ra = nvp->np[i].value;
282 else if (!strcmp(nvp->np[i].name, "ASTROMETRY_RESULTS_DE"))
283 de = nvp->np[i].value;
284 else if (!strcmp(nvp->np[i].name, "ASTROMETRY_RESULTS_PARITY"))
285 {
286 if (nvp->np[i].value == 1)
287 parity = "pos";
288 else if (nvp->np[i].value == -1)
289 parity = "neg";
290 }
291 }
292
293 if (pixscale != -1000 && ra != -1000 && de != -1000 && orientation != -1000)
294 {
295 int elapsed = (int)round(solverTimer.elapsed() / 1000.0);
296 align->appendLogText(i18np("Solver completed in %1 second.", "Solver completed in %1 seconds.", elapsed));
297 stopSolver();
298 emit solverFinished(orientation, ra, de, pixscale, parity != "pos");
299 }
300 }
301 }
302