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