Instrument Neutral Distributed Interface INDI 2.2.5
rainbowRSF.cpp
Go to the documentation of this file.
1/*
2 Rainbow Astro Focuser
3 Copyright (C) 2020 Abdulaziz Bouland ([email protected])
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/*
22This is the Rainbow API. A pdf is in the driver docs folder.
23
24Command Send Receive Comment
25
26Get 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
31Is Focus Moving :Fs# :FS0# not moving
32 :FS1# moving
33
34Temperature :Ft1# :FT1sDD.D# s is + or -, DD.D temp in celcius
35
36Move Absolute :FmsDDDD# :FM# s is + or -, DDDD from -8000 to 8000
37
38Move Relative :FnsDDDD# :FM# s is + or -, DDDD from -8000 to 8000
39 neg numbers move away from main mirror
40
41Move 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
51static std::unique_ptr<RainbowRSF> rainbowRSF(new RainbowRSF());
52
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 FocusAbsPosNP[0].setMin(0);
87 FocusAbsPosNP[0].setMax(16000);
88 FocusAbsPosNP[0].setStep(1000);
89
90 FocusMaxPosNP[0].setMin(0 );
91 FocusMaxPosNP[0].setMax(16000);
92 FocusMaxPosNP[0].setStep(1000);
93 FocusMaxPosNP[0].setValue(16000);
95
96 FocusRelPosNP[0].setMin(0);
97 FocusRelPosNP[0].setMax(8000);
98 FocusRelPosNP[0].setStep(1000);
99
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
131bool 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
172namespace
173{
174bool 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 (FocusAbsPosNP[0].getValue() > m_TargetPosition)
205 {
206 FocusAbsPosNP[0].setValue(FocusAbsPosNP[0].getValue() - 500);
207 if (FocusAbsPosNP[0].getValue() < m_TargetPosition)
208 FocusAbsPosNP[0].setValue(m_TargetPosition);
209 }
210 else if (FocusAbsPosNP[0].getValue() < m_TargetPosition)
211 {
212 FocusAbsPosNP[0].setValue(FocusAbsPosNP[0].getValue() + 500);
213 if (FocusAbsPosNP[0].getValue() > m_TargetPosition)
214 FocusAbsPosNP[0].setValue(m_TargetPosition);
215 }
216
217 // update the states
218 if (FocusAbsPosNP[0].getValue() == m_TargetPosition)
219 {
220 if (GoHomeSP.s == IPS_BUSY)
221 {
222 GoHomeSP.s = IPS_OK;
225 IDSetSwitch(&GoHomeSP, nullptr);
228 LOG_INFO("Focuser reached home position.");
229 }
230
231 else if (FocusAbsPosNP.getState() == IPS_BUSY)
232 {
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 FocusAbsPosNP[0].setValue(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>(FocusAbsPosNP[0].getValue() - 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.getState() == 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>(FocusAbsPosNP[0].getValue()));
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
296
299
300 LOG_INFO("Focuser reached home position.");
301 }
302
303 else if (FocusAbsPosNP.getState() == IPS_BUSY)
304 {
307
309
311 LOGF_INFO("Focuser reached target position %d at %d.",
312 m_TargetPosition, static_cast<int>(FocusAbsPosNP[0].getValue()));
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
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 = (FocusReverseSP.findOnSwitchIndex() == INDI_ENABLED) ? -1 : 1;
378 int relativeTicks = ((dir == FOCUS_INWARD) ? -ticks : ticks) * reversed;
379 double newPosition = FocusAbsPosNP[0].getValue() + 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 - FocusAbsPosNP[0].getValue()) > 0)
417 {
419 m_LastPosition = FocusAbsPosNP[0].getValue();
420
421 if (GoHomeSP.s == IPS_BUSY && FocusAbsPosNP[0].getValue() == 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
452bool 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}
double temperature
bool isConnected() const
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().
INDI::PropertyNumber FocusRelPosNP
INDI::PropertySwitch FocusReverseSP
void SetCapability(uint32_t cap)
FI::SetCapability sets the focuser capabilities. All capabilities must be initialized.
INDI::PropertyNumber FocusMaxPosNP
INDI::PropertyNumber FocusAbsPosNP
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Processes incoming new switch states from the client for this device. Handles updates to FocuserInter...
virtual bool updateProperties() override
Updates the state and definition of properties based on the current connection status....
virtual bool initProperties() override
Initializes the default properties for the focuser device. Sets up standard INDI properties,...
void setState(IPState state)
void apply(const char *format,...) const ATTRIBUTE_FORMAT_PRINTF(2
void setPermission(IPerm permission)
virtual IPState MoveAbsFocuser(uint32_t targetTicks) override
Focuser Command Functions.
bool updatePosition()
virtual bool Handshake() override
Performs a device-specific handshake after a connection is established. This method should be overrid...
bool updateTemperature()
virtual void TimerHit() override
Callback function to be called once SetTimer duration elapses.
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Processes incoming new switch states from the client for this device. Handles updates to FocuserInter...
virtual IPState MoveRelFocuser(FocusDirection dir, unsigned int ticks) override
bool sendCommand(const char *cmd, char *res, int res_len)
Communication Functions.
bool findHome()
const char * getDefaultName() override
virtual bool updateProperties() override
Updates the state and definition of properties based on the current connection status....
virtual bool initProperties() override
Initializes the default properties for the focuser device. Sets up standard INDI properties,...
const char * MAIN_CONTROL_TAB
MAIN_CONTROL_TAB Where all the primary controls for the device are located.
ISState
Switch state.
Definition indiapi.h:148
@ ISS_OFF
Definition indiapi.h:149
@ IP_RW
Definition indiapi.h:184
@ IP_RO
Definition indiapi.h:182
IPState
Property state.
Definition indiapi.h:158
@ IPS_BUSY
Definition indiapi.h:161
@ IPS_ALERT
Definition indiapi.h:162
@ IPS_IDLE
Definition indiapi.h:159
@ IPS_OK
Definition indiapi.h:160
@ ISR_ATMOST1
Definition indiapi.h:172
int tty_write(int fd, const char *buf, int nbytes, int *nbytes_written)
Writes a buffer to fd.
Definition indicom.c:418
void tty_error_msg(int err_code, char *err_msg, int err_msg_len)
Retrieve the tty error message.
Definition indicom.c:1243
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:759
Implementations for common driver routines.
@ TTY_OK
Definition indicom.h:54
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:490
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:376
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:398
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:453
void IDSetNumber(const INumberVectorProperty *nvp, const char *fmt,...)
void IDSetSwitch(const ISwitchVectorProperty *svp, const char *fmt,...)
#define MAXRBUF
Definition indidriver.c:52
#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 getDeviceName
uint8_t cmd[4]
Definition pwc-ioctl.h:2
char name[MAXINDINAME]
Definition indiapi.h:321
char name[MAXINDINAME]
Definition indiapi.h:369