Instrument Neutral Distributed Interface INDI  2.0.2
celestron.cpp
Go to the documentation of this file.
1 /*
2  Celestron Focuser for SCT and EDGEHD
3 
4  Copyright (C) 2019 Jasem Mutlaq (mutlaqja@ikarustech.com)
5  Copyright (C) 2019 Chris Rowland
6 
7  This library is free software; you can redistribute it and/or
8  modify it under the terms of the GNU Lesser General Public
9  License as published by the Free Software Foundation; either
10  version 2.1 of the License, or (at your option) any later version.
11 
12  This library is distributed in the hope that it will be useful,
13  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15  Lesser General Public License for more details.
16 
17  You should have received a copy of the GNU Lesser General Public
18  License along with this library; if not, write to the Free Software
19  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20 
21 */
22 
23 #include "celestron.h"
25 #include "indicom.h"
26 
27 #include <cmath>
28 #include <cstring>
29 #include <memory>
30 
31 #include <termios.h>
32 #include <unistd.h>
33 
34 static std::unique_ptr<CelestronSCT> celestronSCT(new CelestronSCT());
35 
37  backlashMove(false),
38  finalPosition(0),
39  calibrateInProgress(false),
40  calibrateState(0),
41  focuserIsCalibrated(false)
42 {
43  // Can move in Absolute & Relative motions, can AbortFocuser motion.
44  // CR variable speed and sync removed
46 
47  communicator.source = Aux::Target::APP;
48 }
49 
51 {
53 
54  // Focuser backlash
55  // CR this is a value, positive or negative to define the direction. It will need to be implemented
56  // in the driver.
57  FocusBacklashN[0].min = -500;
58  FocusBacklashN[0].max = 500;
59  FocusBacklashN[0].step = 1;
60  FocusBacklashN[0].value = 0;
61 
62  // IUFillNumber(&FocusBacklashN[0], "STEPS", "Steps", "%.f", -500., 500., 1., 0.);
63  // IUFillNumberVector(&FocusBacklashNP, FocusBacklashN, 1, getDeviceName(), "FOCUS_BACKLASH", "Backlash",
64  // MAIN_CONTROL_TAB, IP_RW, 0, IPS_IDLE);
65 
66  // Focuser min limit
67  IUFillNumber(&FocusMinPosN[0], "FOCUS_MIN_VALUE", "Steps", "%.f", 0, 40000., 1., 0.);
68  IUFillNumberVector(&FocusMinPosNP, FocusMinPosN, 1, getDeviceName(), "FOCUS_MIN", "Min. Position",
70 
71  // focuser calibration
72  IUFillSwitch(&CalibrateS[0], "START", "Start Calibration", ISS_OFF);
73  IUFillSwitch(&CalibrateS[1], "STOP", "Stop Calibration", ISS_OFF);
74  IUFillSwitchVector(&CalibrateSP, CalibrateS, 2, getDeviceName(), "CALIBRATE", "Calibrate control",
76 
77  IUFillText(&CalibrateStateT[0], "CALIBRATE_STATE", "Calibrate state", "");
78  IUFillTextVector(&CalibrateStateTP, CalibrateStateT, 1, getDeviceName(), "CALIBRATE_STATE", "Calibrate State",
80 
81  // Speed range
82  // CR no need to have adjustable speed, how to remove?
83  FocusSpeedN[0].min = 1;
84  FocusSpeedN[0].max = 3;
85  FocusSpeedN[0].value = 1;
86 
87  // From online screenshots, seems maximum value is 60,000 steps
88  // max and min positions can be read from a calibrated focuser
89 
90  // Relative Position Range
91  FocusRelPosN[0].min = 0.;
92  FocusRelPosN[0].max = 30000.;
93  FocusRelPosN[0].value = 0;
94  FocusRelPosN[0].step = 1000;
95 
96  // Absolute Postition Range
97  FocusAbsPosN[0].min = 0.;
98  FocusAbsPosN[0].max = 60000.;
99  FocusAbsPosN[0].value = 0;
100  FocusAbsPosN[0].step = 1000;
101 
102  // Maximum Position Settings
103  FocusMaxPosN[0].max = 60000;
104  FocusMaxPosN[0].min = 1000;
105  FocusMaxPosN[0].value = 60000;
107 
108  // Poll every 500ms
110 
111  // Add debugging support
112  addDebugControl();
113 
114  // Set default baud rate to 19200
116 
117  communicator.setDeviceName(getDeviceName());
118 
119  // Defualt port to /dev/ttyACM0
120  //serialConnection->setDefaultPort("/dev/ttyACM0");
121 
122  //LOG_INFO("initProperties end");
123  return true;
124 }
125 
127 {
129 
130  if (isConnected())
131  {
132  //defineProperty(&FocusBacklashNP);
133 
134  defineProperty(&FocusMinPosNP);
135 
136  defineProperty(&CalibrateSP);
137  defineProperty(&CalibrateStateTP);
138 
139  if (getStartupParameters())
140  LOG_INFO("Celestron SCT focuser parameters updated, focuser ready for use.");
141  else
142  LOG_WARN("Failed to retrieve some focuser parameters. Check logs.");
143 
144  }
145  else
146  {
147  //deleteProperty(FocusBacklashNP.name);
148  deleteProperty(FocusMinPosNP.name);
149  deleteProperty(CalibrateSP.name);
150  deleteProperty(CalibrateStateTP.name);
151  }
152 
153  return true;
154 }
155 
157 {
158  if (Ack())
159  {
160  LOG_INFO("Celestron SCT Focuser is online. Getting focus parameters...");
161  return true;
162  }
163 
164  LOG_INFO("Error retrieving data from Celestron SCT, please ensure Celestron SCT controller is powered and the port is correct.");
165  return false;
166 }
167 
169 {
170  return "Celestron SCT";
171 }
172 
173 bool CelestronSCT::Ack()
174 {
175  // send simple command to focuser and check response to make sure
176  // it is online and responding
177  // use get firmware version command
178  Aux::buffer reply;
179  if (!communicator.sendCommand(PortFD, Aux::Target::FOCUSER, Aux::Command::GET_VER, reply))
180  return false;
181 
182  if (reply.size() == 4)
183  {
184  LOGF_INFO("Firmware Version %i.%i.%i", reply[0], reply [1], (reply[2] << 8) + reply[3]);
185  }
186  else
187  LOGF_INFO("Firmware Version %i.%i", reply[0], reply [1]);
188  return true;
189 }
190 
191 bool CelestronSCT::readPosition()
192 {
193  Aux::buffer reply;
195  return false;
196 
197  int position = (reply[0] << 16) + (reply[1] << 8) + reply[2];
198  LOGF_DEBUG("Position %i", position);
199  FocusAbsPosN[0].value = position;
200  return true;
201 }
202 
203 bool CelestronSCT::isMoving()
204 {
205  Aux::buffer reply(1);
207  return false;
208  return reply[0] != static_cast<uint8_t>(0xFF);
209 }
210 
211 // read the focuser limits from the hardware
212 bool CelestronSCT::readLimits()
213 {
214  Aux::buffer reply(8);
216  return false;
217 
218  int lo = (reply[0] << 24) + (reply[1] << 16) + (reply[2] << 8) + reply[3];
219  int hi = (reply[4] << 24) + (reply[5] << 16) + (reply[6] << 8) + reply[7];
220 
221  FocusAbsPosN[0].max = hi;
222  FocusAbsPosN[0].min = lo;
225 
226  FocusMaxPosN[0].value = hi;
228  IDSetNumber(&FocusMaxPosNP, nullptr);
229 
230  FocusMinPosN[0].value = lo;
231  FocusMinPosNP.s = IPS_OK;
232  IDSetNumber(&FocusMinPosNP, nullptr);
233 
234  // check on integrity of values, they must be sensible and the range must be more than 2 turns
235  if (hi > 0 && lo > 0 && hi - lo > 2000 && hi <= 60000 && lo < 50000)
236  {
237  focuserIsCalibrated = true;
238  LOGF_INFO("Focus range %i to %i valid", hi, lo);
239  }
240  else
241  {
242  focuserIsCalibrated = false;
243  LOGF_INFO("Focus range %i to %i invalid", hi, lo);
244  return false;
245  }
246 
247  return true;
248 }
249 
250 //bool CelestronSCT::ISNewNumber(const char * dev, const char * name, double values[], char * names[], int n)
251 //{
252 // if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
253 // {
254 // // Backlash
255 // if (!strcmp(name, FocusBacklashNP.name))
256 // {
257 // // just update the number
258 // IUUpdateNumber(&FocusBacklashNP, values, names, n);
259 // FocusBacklashNP.s = IPS_OK;
260 // IDSetNumber(&FocusBacklashNP, nullptr);
261 // return true;
262 // }
263 // }
264 // return INDI::Focuser::ISNewNumber(dev, name, values, names, n);
265 //}
266 
267 bool CelestronSCT::ISNewSwitch(const char * dev, const char * name, ISState *states, char * names[], int n)
268 {
269  if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
270  {
271  if (!strcmp(name, CalibrateSP.name))
272  {
273  IUUpdateSwitch(&CalibrateSP, states, names, n);
274  int index = IUFindOnSwitchIndex(&CalibrateSP);
275  Aux::buffer data = {1};
276  switch(index)
277  {
278  case 0:
279  // start calibrate
280  LOG_INFO("Focuser Calibrate start");
281  calibrateInProgress = true;
282  calibrateState = -1;
283  break;
284  case 1:
285  // abort calibrate
286  LOG_INFO("Focuser Calibrate abort");
287  data[0] = 0;
288  break;
289  default:
290  return false;
291  }
293  usleep(500000);
294  CalibrateSP.s = IPS_BUSY;
295  IDSetSwitch(&CalibrateSP, nullptr);
296  return true;
297  }
298  }
299  return INDI::Focuser::ISNewSwitch(dev, name, states, names, n);
300 }
301 
302 bool CelestronSCT::getStartupParameters()
303 {
304  bool rc1 = false, rc2 = false;
305 
306  if ( (rc1 = readPosition()))
307  IDSetNumber(&FocusAbsPosNP, nullptr);
308 
309  if ( !(rc2 = readLimits()))
310  {
311  LOG_WARN("Focuser not calibrated, You MUST calibrate before moves are allowed.");
312  }
313 
314  return (rc1 && rc2);
315 }
316 
318 {
319  // Send command to focuser
320  // If OK and moving, return IPS_BUSY (CR don't see this, it seems to just start a new move)
321  // If OK and motion already done (was very small), return IPS_OK
322  // If error, return IPS_ALERT
323 
324  if (!focuserIsCalibrated)
325  {
326  LOG_ERROR("Move not allowed because focuser is not calibrated.");
327  return IPS_ALERT;
328  }
329  if (calibrateInProgress)
330  {
331  LOG_WARN("Move not allowed because a calibration is in progress");
332  return IPS_ALERT;
333  }
334 
335  // the focuser seems happy to move 500 steps past the soft limit so don't check backlash
336  if (targetTicks > FocusMaxPosN[0].value ||
337  targetTicks < FocusMinPosN[0].value)
338  {
339  LOGF_ERROR("Move to %i not allowed because it is out of range", targetTicks);
340  return IPS_ALERT;
341  }
342 
343  uint32_t position = targetTicks;
344 
345  // implement backlash
346  int delta = targetTicks - FocusAbsPosN[0].value;
347  if ((FocusBacklashN[0].value < 0 && delta > 0) ||
348  (FocusBacklashN[0].value > 0 && delta < 0))
349  {
350  backlashMove = true;
351  finalPosition = position;
352  position -= FocusBacklashN[0].value;
353  }
354 
355  if (!startMove(position))
356  return IPS_ALERT;
357 
358  return IPS_BUSY;
359 }
360 
361 bool CelestronSCT::startMove(uint32_t position)
362 {
363  Aux::buffer data =
364  {
365  static_cast<uint8_t>((position >> 16) & 0xFF),
366  static_cast<uint8_t>((position >> 8) & 0xFF),
367  static_cast<uint8_t>(position & 0xFF)
368  };
369 
370  LOGF_DEBUG("startMove %i, %x %x %x", position, data[0], data[1], data[2]);
371 
373 }
374 
376 {
377  int32_t newPosition = 0;
378 
379  if (dir == FOCUS_INWARD)
380  newPosition = FocusAbsPosN[0].value - ticks;
381  else
382  newPosition = FocusAbsPosN[0].value + ticks;
383 
384  // Clamp
385  newPosition = std::max(0, std::min(static_cast<int32_t>(FocusAbsPosN[0].max), newPosition));
386  return MoveAbsFocuser(newPosition);
387 }
388 
390 {
391  if (!isConnected())
392  {
394  return;
395  }
396 
397  // Check position
398  double lastPosition = FocusAbsPosN[0].value;
399  bool rc = readPosition();
400  if (rc)
401  {
402  // Only update if there is actual change
403  if (fabs(lastPosition - FocusAbsPosN[0].value) > 1)
404  IDSetNumber(&FocusAbsPosNP, nullptr);
405  }
406 
408  {
409  // CR The backlash handling will probably have to be done here, if the move state
410  // shows that a backlash move has been done then the final move needs to be started
411  // and the states left at IPS_BUSY
412 
413  // There are two ways to know when focuser motion is over
414  // define class variable uint32_t m_TargetPosition and set it in MoveAbsFocuser(..) function
415  // then compare current value to m_TargetPosition
416  // The other way is to have a function that calls a focuser specific function about motion
417  if (!isMoving())
418  {
419  if (backlashMove)
420  {
421  backlashMove = false;
422  if (startMove(finalPosition))
423  LOGF_INFO("Backlash move to %i", finalPosition);
424  else
425  LOG_INFO("Backlash move failed");
426  }
427  else
428  {
431  IDSetNumber(&FocusAbsPosNP, nullptr);
432  IDSetNumber(&FocusRelPosNP, nullptr);
433  LOG_INFO("Focuser reached requested position.");
434  }
435  }
436  }
437 
438  if (calibrateInProgress)
439  {
440  usleep(500000); // slowing things down while calibrating seems to help
441  // check the calibration state
442  Aux::buffer reply;
444  bool complete = reply[0] > 0;
445  int state = reply[1];
446 
447  if (complete || state == 0)
448  {
449  // a completed calibration returns complete as true, an aborted calibration sets the status to zero
450 
451  const char *msg = complete ? "Calibrate complete" : "Calibrate aborted";
452  LOG_INFO(msg);
453  calibrateInProgress = false;
454  CalibrateS[1].s = ISS_OFF;
455  CalibrateSP.s = IPS_OK;
456  IUSaveText(&CalibrateStateT[0], msg);
457  IDSetSwitch(&CalibrateSP, nullptr);
458  IDSetText(&CalibrateStateTP, nullptr);
459  // read the new limits
460  if (complete && readLimits())
461  {
463  IDSetNumber(&FocusMaxPosNP, nullptr);
464  IDSetNumber(&FocusMinPosNP, nullptr);
465  }
466  }
467  else
468  {
469  if (state != calibrateState)
470  {
471  calibrateState = state;
472  char str[20];
473  snprintf(str, 20, "Calibrate state %i", state);
474  IUSaveText(&CalibrateStateT[0], str);
475  IDSetText(&CalibrateStateTP, nullptr);
476  }
477  }
478  }
479 
481 }
482 
484 {
485  if (calibrateInProgress)
486  {
487  LOG_WARN("Abort move not allowed when calibrating, use abort calibration to stop");
488  return false;
489  }
490  // send a command to move at rate 0
491  Aux::buffer data = {0};
493 }
494 
496 {
497  INDI_UNUSED(steps);
498  return true;
499 }
500 
#define FOC_CALIB_ENABLE
#define MC_GOTO_FAST
#define FOC_GET_HS_POSITIONS
#define GET_VER
#define FOC_CALIB_DONE
#define MC_GET_POSITION
#define MC_MOVE_POS
#define MC_SLEW_DONE
bool commandBlind(int port, Target dest, Command cmd, buffer data)
static void setDeviceName(const std::string &device)
bool sendCommand(int port, Target dest, Command cmd, buffer data, buffer &reply)
virtual bool updateProperties() override
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
Definition: celestron.cpp:126
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
Definition: celestron.cpp:267
virtual bool AbortFocuser() override
AbortFocuser Abort Focuser motion.
Definition: celestron.cpp:483
virtual IPState MoveAbsFocuser(uint32_t targetTicks) override
MoveAbsFocuser Move to an absolute target position.
Definition: celestron.cpp:317
virtual bool Handshake() override
Handshake Try to communicate with Focuser and see if there is a valid response.
Definition: celestron.cpp:156
virtual IPState MoveRelFocuser(FocusDirection dir, uint32_t ticks) override
MoveRelFocuser Move focuser for a relative amount of ticks in a specific direction.
Definition: celestron.cpp:375
virtual bool initProperties() override
Initilize properties initial state and value. The child class must implement this function.
Definition: celestron.cpp:50
virtual void TimerHit() override
TimerHit Primary Loop called every POLLMS milliseconds (set in Options) to check on the focuser statu...
Definition: celestron.cpp:389
virtual bool SetFocuserBacklash(int32_t steps) override
SetFocuserBacklash Set the focuser backlash compensation value.
Definition: celestron.cpp:495
const char * getDefaultName() override
Definition: celestron.cpp:168
void setDefaultBaudRate(BaudRate newRate)
setDefaultBaudRate Set default baud rate. The default baud rate is 9600 unless otherwise changed by t...
bool isConnected() const
Definition: basedevice.cpp:520
const char * getDeviceName() const
Definition: basedevice.cpp:821
void setDefaultPollingPeriod(uint32_t msec)
setDefaultPollingPeriod Change the default polling period to call TimerHit() function in the driver.
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.
int SetTimer(uint32_t ms)
Set a timer to call the function TimerHit after ms milliseconds.
void addDebugControl()
Add Debug control to the driver.
INumberVectorProperty FocusAbsPosNP
INumberVectorProperty FocusRelPosNP
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
Connection::Serial * serialConnection
Definition: indifocuser.h:116
const char * MAIN_CONTROL_TAB
MAIN_CONTROL_TAB Where all the primary controls for the device are located.
double max(void)
double min(void)
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
Implementations for common driver routines.
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 IUFillTextVector(ITextVectorProperty *tvp, IText *tp, int ntp, const char *dev, const char *name, const char *label, const char *group, IPerm p, double timeout, IPState s)
Assign attributes for a text vector property. The vector's auxiliary elements will be set to NULL.
Definition: indidevapi.c:291
void IUSaveText(IText *tp, const char *newtext)
Function to reliably save new text in a IText.
Definition: indidevapi.c:36
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 IUFillText(IText *tp, const char *name, const char *label, const char *initialText)
Assign attributes for a text property. The text's auxiliary elements will be set to NULL.
Definition: indidevapi.c:198
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
#define INDI_UNUSED(x)
Definition: indidevapi.h:131
int IUUpdateSwitch(ISwitchVectorProperty *svp, ISState *states, char *names[], int n)
Update all switches in a switch vector property.
Definition: indidriver.c:1308
void IDSetNumber(const INumberVectorProperty *nvp, const char *fmt,...)
Definition: indidriver.c:1211
void IDSetSwitch(const ISwitchVectorProperty *svp, const char *fmt,...)
Definition: indidriver.c:1231
void IUUpdateMinMax(const INumberVectorProperty *nvp)
Function to update the min and max elements of a number in the client.
Definition: indidriver.c:1296
void IDSetText(const ITextVectorProperty *tvp, const char *fmt,...)
Definition: indidriver.c:1191
#define LOGF_INFO(fmt,...)
Definition: indilogger.h:82
#define LOG_WARN(txt)
Definition: indilogger.h:73
#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
std::vector< uint8_t > buffer
@ FOCUSER
focuser motor
char name[MAXINDINAME]
Definition: indiapi.h:323
char name[MAXINDINAME]
Definition: indiapi.h:371
char name[MAXINDINAME]
Definition: indiapi.h:250