Instrument Neutral Distributed Interface INDI  2.0.2
rainbowRSF.cpp
Go to the documentation of this file.
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 
54 {
55  setVersion(1, 0);
56 
58 }
59 
64 {
65  return "Rainbow Astro RSF";
66 }
67 
72 {
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;
95 
96  FocusRelPosN[0].min = 0;
97  FocusRelPosN[0].max = 8000;
98  FocusRelPosN[0].step = 1000;
99 
101  addDebugControl();
102 
103  m_MovementTimerActive = false;
104 
105  return true;
106 }
107 
112 {
114 
115  if (isConnected())
116  {
117  defineProperty(&TemperatureNP);
118  defineProperty(&GoHomeSP);
119  }
120  else
121  {
122  deleteProperty(TemperatureNP.name);
123  deleteProperty(GoHomeSP.name);
124  }
125  return true;
126 }
127 
131 bool RainbowRSF::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
132 {
133  if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
134  {
135  // Find Home
136  if (!strcmp(name, GoHomeSP.name))
137  {
138  GoHomeSP.s = findHome() ? IPS_BUSY : IPS_ALERT;
139 
140  if (GoHomeSP.s == IPS_BUSY)
141  LOG_INFO("Moving to home position...");
142  else
143  LOG_ERROR("Failed to move to home position.");
144 
145  IDSetSwitch(&GoHomeSP, nullptr);
146  return true;
147  }
148  }
149  return INDI::Focuser::ISNewSwitch(dev, name, states, names, n);
150 }
151 
156 {
157  if (updateTemperature())
158  {
159  LOG_INFO("Rainbow Astro is online. Getting focus parameters...");
160  return true;
161  }
162 
163  LOG_INFO(
164  "Error retrieving data from Rainbow Astro, please ensure Rainbow Astro controller is powered and the port is correct.");
165  return false;
166 }
167 
171 
172 namespace
173 {
174 bool parsePosition(char *result, int *pos)
175 {
176  const int length = strlen(result);
177  if (length < 6) return false;
178  // Check for a decimal/period
179  char *period = strchr(result + 3, '.');
180  if (period == nullptr) return false;
181 
182  float position;
183  if (sscanf(result, ":FP%f#", &position) == 1)
184  {
185  // position is a float number between -8 and +8 that needs to be multiplied by 1000.
186  *pos = position * 1000;
187  return true;
188  }
189  return false;
190 }
191 } // namespace
192 
193 
195 {
196  char res[DRIVER_LEN] = {0};
197 
201  if (isSimulation())
202  {
203  // Move the focuser
204  if (FocusAbsPosN[0].value > m_TargetPosition)
205  {
206  FocusAbsPosN[0].value -= 500;
207  if (FocusAbsPosN[0].value < m_TargetPosition)
208  FocusAbsPosN[0].value = m_TargetPosition;
209  }
210  else if (FocusAbsPosN[0].value < m_TargetPosition)
211  {
212  FocusAbsPosN[0].value += 500;
213  if (FocusAbsPosN[0].value > m_TargetPosition)
214  FocusAbsPosN[0].value = m_TargetPosition;
215  }
216 
217  // update the states
218  if (FocusAbsPosN[0].value == m_TargetPosition)
219  {
220  if (GoHomeSP.s == IPS_BUSY)
221  {
222  GoHomeSP.s = IPS_OK;
225  IDSetSwitch(&GoHomeSP, nullptr);
226  IDSetNumber(&FocusAbsPosNP, nullptr);
227  IDSetNumber(&FocusRelPosNP, nullptr);
228  LOG_INFO("Focuser reached home position.");
229  }
230 
231  else if (FocusAbsPosNP.s == IPS_BUSY)
232  {
235  IDSetNumber(&FocusAbsPosNP, nullptr);
236  IDSetNumber(&FocusRelPosNP, nullptr);
237  LOG_INFO("Focuser reached target position.");
238  }
239  }
240 
241  return true;
242  }
243 
247  else if (sendCommand(":Fp#", res, DRIVER_LEN) == false)
248  return false;
249 
250  int newPosition { 0 };
251  bool ok = parsePosition(res, &newPosition);
252  if (ok)
253  {
254  FocusAbsPosN[0].value = newPosition + 8000;
255 
256  constexpr int TOLERANCE = 1; // Off-by-one position is ok given the resolution of the response.
257  const int offset = std::abs(static_cast<int>(FocusAbsPosN[0].value - m_TargetPosition));
258  bool focuserDone = offset <= TOLERANCE;
259 
260  // Try to hit the target position, but if it has been close for a while
261  // then we believe the focuser movement is done. This is needed because it
262  // sometimes stops 1 position away from the target, and occasionally 2 or 3.
263  if (!focuserDone && ((GoHomeSP.s == IPS_BUSY) || (FocusAbsPosNP.s == IPS_BUSY)))
264  {
265  if (!m_MovementTimerActive)
266  {
267  // Waiting for motion completion. Initialize the start time for timeouts.
268  m_MovementTimer.start();
269  m_MovementTimerActive = true;
270  }
271  else
272  {
273  const double elapsedSeconds = m_MovementTimer.elapsed() / 1000.0;
274  if ((elapsedSeconds > 5 && offset < 3) ||
275  (elapsedSeconds > 10 && offset < 6) ||
276  (elapsedSeconds > 60))
277  {
278  focuserDone = true;
279  LOGF_INFO("Rainbow focuser timed out: %.1f seconds, offset %d (target %d, position %d)",
280  elapsedSeconds, offset, m_TargetPosition, static_cast<int>(FocusAbsPosN[0].value));
281  }
282  }
283  }
284 
285  if (focuserDone)
286  {
287  m_MovementTimerActive = false;
288  if (GoHomeSP.s == IPS_BUSY)
289  {
290  GoHomeSP.s = IPS_OK;
291  GoHomeS[0].s = ISS_OFF;
292  IDSetSwitch(&GoHomeSP, nullptr);
293 
295  IDSetNumber(&FocusAbsPosNP, nullptr);
296 
298  IDSetNumber(&FocusRelPosNP, nullptr);
299 
300  LOG_INFO("Focuser reached home position.");
301  }
302 
303  else if (FocusAbsPosNP.s == IPS_BUSY)
304  {
306  IDSetNumber(&FocusAbsPosNP, nullptr);
307 
309 
310  IDSetNumber(&FocusRelPosNP, nullptr);
311  LOGF_INFO("Focuser reached target position %d at %d.",
312  m_TargetPosition, static_cast<int>(FocusAbsPosN[0].value));
313  }
314  }
315  return true;
316  }
317  else
318  {
320  return false;
321  }
322 
323 }
324 
329 {
330  char res[DRIVER_LEN] = {0};
331  float temperature = 0;
332 
333  if (isSimulation())
334  strncpy(res, ":FT1+23.5#", DRIVER_LEN);
335 
336  else if (sendCommand(":Ft1#", res, DRIVER_LEN) == false)
337  return false;
338 
339  if (sscanf(res, ":FT1%g", &temperature) == 1)
340  {
341  TemperatureN[0].value = temperature;
342  TemperatureNP.s = IPS_OK;
343  return true;
344  }
345  else
346  {
347  TemperatureNP.s = IPS_ALERT;
348  return false;
349  }
350 }
351 
355 IPState RainbowRSF::MoveAbsFocuser(uint32_t targetTicks)
356 {
357  m_MovementTimerActive = false;
358  m_TargetPosition = targetTicks;
359 
360  char cmd[DRIVER_LEN] = {0};
361  char res[DRIVER_LEN] = {0};
362  int steps = targetTicks - 8000;
363 
364  snprintf(cmd, 16, ":Fm%c%04d#", steps >= 0 ? '+' : '-', std::abs(steps));
365 
366  if (isSimulation() == false)
367  {
368  if (sendCommand(cmd, res, DRIVER_LEN) == false)
369  return IPS_ALERT;
370  }
371  return IPS_BUSY;
372 }
373 
375 {
376  m_MovementTimerActive = false;
377  int reversed = (IUFindOnSwitchIndex(&FocusReverseSP) == INDI_ENABLED) ? -1 : 1;
378  int relativeTicks = ((dir == FOCUS_INWARD) ? -ticks : ticks) * reversed;
379  double newPosition = FocusAbsPosN[0].value + relativeTicks;
380 
381  bool rc = MoveAbsFocuser(newPosition);
382 
383  return (rc ? IPS_BUSY : IPS_ALERT);
384 }
385 
390 {
391  if (isSimulation())
392  {
393  MoveAbsFocuser(homePosition);
395  return true;
396  }
397  else
398  {
399  m_MovementTimerActive = false;
400  m_TargetPosition = homePosition;
402  char res[DRIVER_LEN] = {0};
403  return sendCommand(":Fh#", res, DRIVER_LEN);
404  }
405 }
406 
411 {
412  // position update
413  bool rc = updatePosition();
414  if (rc)
415  {
416  if (abs(m_LastPosition - FocusAbsPosN[0].value) > 0)
417  {
418  IDSetNumber(&FocusAbsPosNP, nullptr);
419  m_LastPosition = FocusAbsPosN[0].value;
420 
421  if (GoHomeSP.s == IPS_BUSY && FocusAbsPosN[0].value == homePosition)
422  {
423  GoHomeSP.s = IPS_OK;
424  LOG_INFO("Focuser arrived at home position.");
425  IDSetSwitch(&GoHomeSP, nullptr);
426  }
427  }
428  }
429 
430  // temperature update
431  if (m_TemperatureCounter++ == DRIVER_TEMPERATURE_FREQ)
432  {
433  rc = updateTemperature();
434  if (rc)
435  {
436  if (abs(m_LastTemperature - TemperatureN[0].value) >= 0.05)
437  {
438  IDSetNumber(&TemperatureNP, nullptr);
439  m_LastTemperature = TemperatureN[0].value;
440  }
441  }
442  // Reset the counter
443  m_TemperatureCounter = 0;
444  }
445 
447 }
448 
452 bool RainbowRSF::sendCommand(const char * cmd, char * res, int res_len)
453 {
454  if (cmd == nullptr || res == nullptr || res_len <= 0)
455  return false;
456  const int cmd_len = strlen(cmd);
457  if (cmd_len <= 0)
458  return false;
459 
460  tcflush(PortFD, TCIOFLUSH);
461 
462  LOGF_DEBUG("CMD <%s>", cmd);
463 
464  int nbytes_written = 0;
465  int rc = tty_write(PortFD, cmd, cmd_len, &nbytes_written);
466  if (rc != TTY_OK)
467  {
468  char errstr[MAXRBUF] = {0};
469  tty_error_msg(rc, errstr, MAXRBUF);
470  LOGF_ERROR("Serial write error: %s.", errstr);
471  return false;
472  }
473 
474  int nbytes_read = 0;
475  rc = tty_nread_section(PortFD, res, DRIVER_LEN, DRIVER_STOP_CHAR,
476  DRIVER_TIMEOUT, &nbytes_read);
477  if (nbytes_read == DRIVER_LEN)
478  return false;
479  res[nbytes_read] = 0;
480  if (rc != TTY_OK)
481  {
482  char errstr[MAXRBUF] = {0};
483  tty_error_msg(rc, errstr, MAXRBUF);
484  LOGF_ERROR("Serial read error: %s.", errstr);
485  return false;
486  }
487 
488  LOGF_DEBUG("RES <%s>", res);
489 
490  tcflush(PortFD, TCIOFLUSH);
491 
492  return true;
493 }
bool isConnected() const
Definition: basedevice.cpp:520
const char * getDeviceName() const
Definition: basedevice.cpp:821
void addSimulationControl()
Add Simulation control to the driver.
void setVersion(uint16_t vMajor, uint16_t vMinor)
Set driver version information to be defined in DRIVER_INFO property as vMajor.vMinor.
virtual bool deleteProperty(const char *propertyName)
Delete a property and unregister it. It will also be deleted from all clients.
void defineProperty(INumberVectorProperty *property)
uint32_t getCurrentPollingPeriod() const
getCurrentPollingPeriod Return the current polling period.
bool isSimulation() const
int SetTimer(uint32_t ms)
Set a timer to call the function TimerHit after ms milliseconds.
void addDebugControl()
Add Debug control to the driver.
int64_t elapsed() const
Returns the number of milliseconds since this ElapsedTimer was last started.
void start()
Starts this timer. Once started, a timer value can be checked with elapsed().
INumberVectorProperty FocusAbsPosNP
INumberVectorProperty FocusRelPosNP
ISwitchVectorProperty FocusReverseSP
void SetCapability(uint32_t cap)
FI::SetCapability sets the focuser capabilities. All capabilities must be initialized.
INumberVectorProperty FocusMaxPosNP
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
virtual bool updateProperties() override
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
virtual bool initProperties() override
Initilize properties initial state and value. The child class must implement this function.
Definition: indifocuser.cpp:42
virtual IPState MoveAbsFocuser(uint32_t targetTicks) override
Focuser Command Functions.
Definition: rainbowRSF.cpp:355
bool updatePosition()
Definition: rainbowRSF.cpp:194
virtual bool Handshake() override
perform handshake with device to check communication
Definition: rainbowRSF.cpp:155
bool updateTemperature()
Definition: rainbowRSF.cpp:328
virtual void TimerHit() override
Callback function to be called once SetTimer duration elapses.
Definition: rainbowRSF.cpp:410
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
Definition: rainbowRSF.cpp:131
virtual IPState MoveRelFocuser(FocusDirection dir, unsigned int ticks) override
Definition: rainbowRSF.cpp:374
bool sendCommand(const char *cmd, char *res, int res_len)
Communication Functions.
Definition: rainbowRSF.cpp:452
bool findHome()
Definition: rainbowRSF.cpp:389
const char * getDefaultName() override
Definition: rainbowRSF.cpp:63
virtual bool updateProperties() override
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
Definition: rainbowRSF.cpp:111
virtual bool initProperties() override
Initilize properties initial state and value. The child class must implement this function.
Definition: rainbowRSF.cpp:71
const char * MAIN_CONTROL_TAB
MAIN_CONTROL_TAB Where all the primary controls for the device are located.
ISState
Switch state.
Definition: indiapi.h:150
@ ISS_OFF
Definition: indiapi.h:151
@ IP_RW
Definition: indiapi.h:186
@ IP_RO
Definition: indiapi.h:184
IPState
Property state.
Definition: indiapi.h:160
@ IPS_BUSY
Definition: indiapi.h:163
@ IPS_ALERT
Definition: indiapi.h:164
@ IPS_IDLE
Definition: indiapi.h:161
@ IPS_OK
Definition: indiapi.h:162
@ ISR_ATMOST1
Definition: indiapi.h:174
int tty_write(int fd, const char *buf, int nbytes, int *nbytes_written)
Writes a buffer to fd.
Definition: indicom.c:424
void tty_error_msg(int err_code, char *err_msg, int err_msg_len)
Retrieve the tty error message.
Definition: indicom.c:1167
int tty_nread_section(int fd, char *buf, int nsize, char stop_char, int timeout, int *nbytes_read)
read buffer from terminal with a delimiter
Definition: indicom.c:666
Implementations for common driver routines.
@ TTY_OK
Definition: indicom.h:150
void IUFillNumberVector(INumberVectorProperty *nvp, INumber *np, int nnp, const char *dev, const char *name, const char *label, const char *group, IPerm p, double timeout, IPState s)
Assign attributes for a number vector property. The vector's auxiliary elements will be set to NULL.
Definition: indidevapi.c:272
int IUFindOnSwitchIndex(const ISwitchVectorProperty *svp)
Returns the index of first ON switch it finds in the vector switch property.
Definition: indidevapi.c:128
void IUFillSwitch(ISwitch *sp, const char *name, const char *label, ISState s)
Assign attributes for a switch property. The switch's auxiliary elements will be set to NULL.
Definition: indidevapi.c:158
void IUFillNumber(INumber *np, const char *name, const char *label, const char *format, double min, double max, double step, double value)
Assign attributes for a number property. The number's auxiliary elements will be set to NULL.
Definition: indidevapi.c:180
void IUFillSwitchVector(ISwitchVectorProperty *svp, ISwitch *sp, int nsp, const char *dev, const char *name, const char *label, const char *group, IPerm p, ISRule r, double timeout, IPState s)
Assign attributes for a switch vector property. The vector's auxiliary elements will be set to NULL.
Definition: indidevapi.c:235
void IDSetNumber(const INumberVectorProperty *nvp, const char *fmt,...)
Definition: indidriver.c:1211
void IDSetSwitch(const ISwitchVectorProperty *svp, const char *fmt,...)
Definition: indidriver.c:1231
#define LOGF_INFO(fmt,...)
Definition: indilogger.h:82
#define LOGF_DEBUG(fmt,...)
Definition: indilogger.h:83
#define LOG_ERROR(txt)
Shorter logging macros. In order to use these macros, the function (or method) "getDeviceName()" must...
Definition: indilogger.h:72
#define LOGF_ERROR(fmt,...)
Definition: indilogger.h:80
#define LOG_INFO(txt)
Definition: indilogger.h:74
#define MAXRBUF
Definition: indiserver.cpp:102
__u8 cmd[4]
Definition: pwc-ioctl.h:2
char name[MAXINDINAME]
Definition: indiapi.h:323
char name[MAXINDINAME]
Definition: indiapi.h:371