1 /*
2     Rainbow Astro Focuser
3     Copyright (C) 2020 Abdulaziz Bouland (boulandab@ikarustech.com)
4 
5     This library is free software; you can redistribute it and/or
6     modify it under the terms of the GNU Lesser General Public
7     License as published by the Free Software Foundation; either
8     version 2.1 of the License, or (at your option) any later version.
9 
10     This library is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13     Lesser General Public License for more details.
14 
15     You should have received a copy of the GNU Lesser General Public
16     License along with this library; if not, write to the Free Software
17     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
18 
19 */
20 
21 /*
22 This is the Rainbow API. A pdf is in the driver docs folder.
23 
24 Command              Send      Receive     Comment
25 
26 Get Position         :Fp#      :FPsDD.DDD# s is + or -,
27                                            sDD.DDD is a float number
28                                            range: -08.000 to +08.000
29                                            unit millimeters
30 
31 Is Focus Moving      :Fs#      :FS0#       not moving
32                                :FS1#       moving
33 
34 Temperature          :Ft1#      :FT1sDD.D# s is + or -, DD.D temp in celcius
35 
36 Move Absolute        :FmsDDDD#  :FM#       s is + or -, DDDD from -8000 to 8000
37 
38 Move Relative        :FnsDDDD#  :FM#       s is + or -, DDDD from -8000 to 8000
39                                            neg numbers move away from main mirror
40 
41 Move Home            :Fh#       :FH#
42 */
43 
44 #include "rainbowRSF.h"
45 #include "indicom.h"
46 
47 #include <cmath>
48 #include <cstring>
49 #include <termios.h>
50 
51 static std::unique_ptr<RainbowRSF> rainbowRSF(new RainbowRSF());
52 
RainbowRSF()53 RainbowRSF::RainbowRSF()
54 {
55     setVersion(1, 0);
56 
57     FI::SetCapability(FOCUSER_CAN_ABS_MOVE | FOCUSER_CAN_REL_MOVE);
58 }
59 
60 /////////////////////////////////////////////////////////////////////////////
61 ///
62 /////////////////////////////////////////////////////////////////////////////
getDefaultName()63 const char *RainbowRSF::getDefaultName()
64 {
65     return "Rainbow Astro RSF";
66 }
67 
68 /////////////////////////////////////////////////////////////////////////////
69 ///
70 /////////////////////////////////////////////////////////////////////////////
initProperties()71 bool RainbowRSF::initProperties()
72 {
73     INDI::Focuser::initProperties();
74 
75     // Go home switch
76     IUFillSwitch(&GoHomeS[0], "GO_HOME", "Go Home", ISS_OFF);
77     IUFillSwitchVector(&GoHomeSP, GoHomeS, 1, getDeviceName(), "FOCUS_GO_HOME", "Home", MAIN_CONTROL_TAB, IP_RW, ISR_ATMOST1,
78                        60, IPS_IDLE);
79 
80     // Focuser temperature
81     IUFillNumber(&TemperatureN[0], "TEMPERATURE", "Celsius", "%.2f", -50, 70., 0., 0.);
82     IUFillNumberVector(&TemperatureNP, TemperatureN, 1, getDeviceName(), "FOCUS_TEMPERATURE", "Temperature", MAIN_CONTROL_TAB,
83                        IP_RO, 0, IPS_IDLE);
84 
85     // Focuser Limits
86     FocusAbsPosN[0].min = 0;
87     FocusAbsPosN[0].max = 16000;
88     FocusAbsPosN[0].step = 1000;
89 
90     FocusMaxPosN[0].min = 0 ;
91     FocusMaxPosN[0].max = 16000;
92     FocusMaxPosN[0].step = 1000;
93     FocusMaxPosN[0].value = 16000;
94     FocusMaxPosNP.p = IP_RO;
95 
96     FocusRelPosN[0].min = 0;
97     FocusRelPosN[0].max = 8000;
98     FocusRelPosN[0].step = 1000;
99 
100     addSimulationControl();
101     addDebugControl();
102 
103     return true;
104 }
105 
106 /////////////////////////////////////////////////////////////////////////////
107 ///
108 /////////////////////////////////////////////////////////////////////////////
updateProperties()109 bool RainbowRSF::updateProperties()
110 {
111     INDI::Focuser::updateProperties();
112 
113     if (isConnected())
114     {
115         defineProperty(&TemperatureNP);
116         defineProperty(&GoHomeSP);
117     }
118     else
119     {
120         deleteProperty(TemperatureNP.name);
121         deleteProperty(GoHomeSP.name);
122     }
123     return true;
124 }
125 
126 /////////////////////////////////////////////////////////////////////////////
127 ///
128 /////////////////////////////////////////////////////////////////////////////
ISNewSwitch(const char * dev,const char * name,ISState * states,char * names[],int n)129 bool RainbowRSF::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
130 {
131     if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
132     {
133         // Find Home
134         if (!strcmp(name, GoHomeSP.name))
135         {
136             GoHomeSP.s = findHome() ? IPS_BUSY : IPS_ALERT;
137 
138             if (GoHomeSP.s == IPS_BUSY)
139                 LOG_INFO("Moving to home position...");
140             else
141                 LOG_ERROR("Failed to move to home position.");
142 
143             IDSetSwitch(&GoHomeSP, nullptr);
144             return true;
145         }
146     }
147     return INDI::Focuser::ISNewSwitch(dev, name, states, names, n);
148 }
149 
150 /////////////////////////////////////////////////////////////////////////////
151 ///
152 /////////////////////////////////////////////////////////////////////////////
Handshake()153 bool RainbowRSF::Handshake()
154 {
155     if (updateTemperature())
156     {
157         LOG_INFO("Rainbow Astro is online. Getting focus parameters...");
158         return true;
159     }
160 
161     LOG_INFO(
162         "Error retrieving data from Rainbow Astro, please ensure Rainbow Astro controller is powered and the port is correct.");
163     return false;
164 }
165 
166 /////////////////////////////////////////////////////////////////////////////
167 ///
168 /////////////////////////////////////////////////////////////////////////////
169 
170 namespace {
parsePosition(char * result,int * pos)171 bool parsePosition(char *result, int *pos)
172 {
173   const int length = strlen(result);
174   if (length < 6) return false;
175   // Check for a decimal/period
176   char *period = strchr(result+3, '.');
177   if (period == nullptr) return false;
178 
179   float position;
180   if (sscanf(result, ":FP%f#", &position) == 1)
181   {
182       // position is a float number between -8 and +8 that needs to be multiplied by 1000.
183       *pos = position * 1000;
184       return true;
185   }
186   return false;
187 }
188 }  // namespace
189 
190 
updatePosition()191 bool RainbowRSF::updatePosition()
192 {
193     char res[DRIVER_LEN] = {0};
194 
195     /////////////////////////////////////////////////////////////////////////////
196     /// Simulation
197     /////////////////////////////////////////////////////////////////////////////
198     if (isSimulation())
199     {
200         // Move the focuser
201         if (FocusAbsPosN[0].value > m_TargetPosition)
202         {
203             FocusAbsPosN[0].value -= 500;
204             if (FocusAbsPosN[0].value < m_TargetPosition)
205                 FocusAbsPosN[0].value = m_TargetPosition;
206         }
207         else if (FocusAbsPosN[0].value < m_TargetPosition)
208         {
209             FocusAbsPosN[0].value += 500;
210             if (FocusAbsPosN[0].value > m_TargetPosition)
211                 FocusAbsPosN[0].value = m_TargetPosition;
212         }
213 
214         // update the states
215         if (FocusAbsPosN[0].value == m_TargetPosition)
216         {
217             if (GoHomeSP.s == IPS_BUSY)
218             {
219                 GoHomeSP.s = IPS_OK;
220                 FocusAbsPosNP.s = IPS_OK;
221                 FocusRelPosNP.s = IPS_OK;
222                 IDSetSwitch(&GoHomeSP, nullptr);
223                 IDSetNumber(&FocusAbsPosNP, nullptr);
224                 IDSetNumber(&FocusRelPosNP, nullptr);
225                 LOG_INFO("Focuser reached home position.");
226             }
227 
228             else if (FocusAbsPosNP.s == IPS_BUSY)
229             {
230                 FocusAbsPosNP.s = IPS_OK;
231                 FocusRelPosNP.s = IPS_OK;
232                 IDSetNumber(&FocusAbsPosNP, nullptr);
233                 IDSetNumber(&FocusRelPosNP, nullptr);
234                 LOG_INFO("Focuser reached target position.");
235             }
236         }
237 
238         return true;
239     }
240 
241     /////////////////////////////////////////////////////////////////////////////
242     /// Real Driver
243     /////////////////////////////////////////////////////////////////////////////
244     else if (sendCommand(":Fp#", res, DRIVER_LEN) == false)
245         return false;
246 
247     int newPosition { 0 };
248     bool ok = parsePosition(res, &newPosition);
249     if (ok) {
250         FocusAbsPosN[0].value = newPosition + 8000;
251 
252         const int offset = FocusAbsPosN[0].value - m_TargetPosition;
253         constexpr int TOLERANCE = 1;  // Off-by-one position is ok given the resolution of the response.
254         if (std::abs(offset) <= TOLERANCE)
255         {
256             if (GoHomeSP.s == IPS_BUSY)
257             {
258                 GoHomeSP.s = IPS_OK;
259                 GoHomeS[0].s = ISS_OFF;
260                 IDSetSwitch(&GoHomeSP, nullptr);
261 
262                 FocusAbsPosNP.s = IPS_OK;
263                 IDSetNumber(&FocusAbsPosNP, nullptr);
264 
265                 FocusRelPosNP.s = IPS_OK;
266                 IDSetNumber(&FocusRelPosNP, nullptr);
267 
268                 LOG_INFO("Focuser reached home position.");
269             }
270 
271             else if (FocusAbsPosNP.s == IPS_BUSY)
272             {
273                 FocusAbsPosNP.s = IPS_OK;
274                 IDSetNumber(&FocusAbsPosNP, nullptr);
275 
276                 FocusRelPosNP.s = IPS_OK;
277 
278                 IDSetNumber(&FocusRelPosNP, nullptr);
279                 LOG_INFO("Focuser reached target position.");
280             }
281         }
282         return true;
283     }
284     else
285     {
286         FocusAbsPosNP.s = IPS_ALERT;
287         return false;
288     }
289 
290 }
291 
292 /////////////////////////////////////////////////////////////////////////////
293 ///
294 /////////////////////////////////////////////////////////////////////////////
updateTemperature()295 bool RainbowRSF::updateTemperature()
296 {
297     char res[DRIVER_LEN] = {0};
298     float temperature = 0;
299 
300     if (isSimulation())
301         strncpy(res, ":FT1+23.5#", DRIVER_LEN);
302 
303     else if (sendCommand(":Ft1#", res, DRIVER_LEN) == false)
304         return false;
305 
306     if (sscanf(res, ":FT1%g", &temperature) == 1)
307     {
308         TemperatureN[0].value = temperature;
309         TemperatureNP.s = IPS_OK;
310         return true;
311     }
312     else
313     {
314         TemperatureNP.s = IPS_ALERT;
315         return false;
316     }
317 }
318 
319 /////////////////////////////////////////////////////////////////////////////////////
320 ///
321 /////////////////////////////////////////////////////////////////////////////////////
MoveAbsFocuser(uint32_t targetTicks)322 IPState RainbowRSF::MoveAbsFocuser(uint32_t targetTicks)
323 {
324     m_TargetPosition = targetTicks;
325 
326     char cmd[DRIVER_LEN] = {0};
327     char res[DRIVER_LEN] = {0};
328     int steps = targetTicks - 8000;
329 
330     snprintf(cmd, 16, ":Fm%c%04d#", steps >= 0 ? '+' : '-', std::abs(steps));
331 
332     if (isSimulation() == false)
333     {
334       if (sendCommand(cmd, res, DRIVER_LEN) == false)
335           return IPS_ALERT;
336     }
337     return IPS_BUSY;
338 }
339 
MoveRelFocuser(FocusDirection dir,uint32_t ticks)340 IPState RainbowRSF::MoveRelFocuser(FocusDirection dir, uint32_t ticks)
341 {
342     int reversed = (IUFindOnSwitchIndex(&FocusReverseSP) == INDI_ENABLED) ? -1 : 1;
343     int relativeTicks =  ((dir == FOCUS_INWARD) ? -ticks : ticks) * reversed;
344     double newPosition = FocusAbsPosN[0].value + relativeTicks;
345 
346     bool rc = MoveAbsFocuser(newPosition);
347 
348     return (rc ? IPS_BUSY : IPS_ALERT);
349 }
350 
351 /////////////////////////////////////////////////////////////////////////////////////
352 ///
353 /////////////////////////////////////////////////////////////////////////////////////
findHome()354 bool RainbowRSF::findHome()
355 {
356     if (isSimulation())
357     {
358         MoveAbsFocuser(homePosition);
359         FocusAbsPosNP.s = IPS_BUSY;
360         return true;
361     }
362     else
363     {
364         m_TargetPosition = homePosition;
365         FocusAbsPosNP.s = IPS_BUSY;
366         char res[DRIVER_LEN] = {0};
367         return sendCommand(":Fh#", res, DRIVER_LEN);
368     }
369 }
370 
371 /////////////////////////////////////////////////////////////////////////////
372 ///
373 /////////////////////////////////////////////////////////////////////////////
TimerHit()374 void RainbowRSF::TimerHit()
375 {
376     // position update
377     bool rc = updatePosition();
378     if (rc)
379     {
380         if (abs(m_LastPosition - FocusAbsPosN[0].value) > 0)
381         {
382             IDSetNumber(&FocusAbsPosNP, nullptr);
383             m_LastPosition = FocusAbsPosN[0].value;
384 
385             if (GoHomeSP.s == IPS_BUSY && FocusAbsPosN[0].value == homePosition)
386             {
387                 GoHomeSP.s = IPS_OK;
388                 LOG_INFO("Focuser arrived at home position.");
389                 IDSetSwitch(&GoHomeSP, nullptr);
390             }
391         }
392     }
393 
394     // temperature update
395     if (m_TemperatureCounter++ == DRIVER_TEMPERATURE_FREQ)
396     {
397         rc = updateTemperature();
398         if (rc)
399         {
400             if (abs(m_LastTemperature - TemperatureN[0].value) >= 0.05)
401             {
402                 IDSetNumber(&TemperatureNP, nullptr);
403                 m_LastTemperature = TemperatureN[0].value;
404             }
405         }
406         // Reset the counter
407         m_TemperatureCounter = 0;
408     }
409 
410     SetTimer(getCurrentPollingPeriod());
411 }
412 
413 /////////////////////////////////////////////////////////////////////////////
414 /// Send Command
415 /////////////////////////////////////////////////////////////////////////////
sendCommand(const char * cmd,char * res,int res_len)416 bool RainbowRSF::sendCommand(const char * cmd, char * res, int res_len)
417 {
418     if (cmd == nullptr || res == nullptr || res_len <= 0)
419       return false;
420     const int cmd_len = strlen(cmd);
421     if (cmd_len <= 0)
422       return false;
423 
424     tcflush(PortFD, TCIOFLUSH);
425 
426     LOGF_DEBUG("CMD <%s>", cmd);
427 
428     int nbytes_written = 0;
429     int rc = tty_write(PortFD, cmd, cmd_len, &nbytes_written);
430     if (rc != TTY_OK)
431     {
432         char errstr[MAXRBUF] = {0};
433         tty_error_msg(rc, errstr, MAXRBUF);
434         LOGF_ERROR("Serial write error: %s.", errstr);
435         return false;
436     }
437 
438     int nbytes_read = 0;
439     rc = tty_nread_section(PortFD, res, DRIVER_LEN, DRIVER_STOP_CHAR,
440                            DRIVER_TIMEOUT, &nbytes_read);
441     if (nbytes_read == DRIVER_LEN)
442         return false;
443     res[nbytes_read] = 0;
444     if (rc != TTY_OK)
445     {
446         char errstr[MAXRBUF] = {0};
447         tty_error_msg(rc, errstr, MAXRBUF);
448         LOGF_ERROR("Serial read error: %s.", errstr);
449         return false;
450     }
451 
452     LOGF_DEBUG("RES <%s>", res);
453 
454     tcflush(PortFD, TCIOFLUSH);
455 
456     return true;
457 }
458