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