Instrument Neutral Distributed Interface INDI 2.2.5
celestron.cpp
Go to the documentation of this file.
1/*
2 Celestron Focuser for SCT and EDGEHD
3
4 Copyright (C) 2019 Jasem Mutlaq ([email protected])
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
34static std::unique_ptr<CelestronSCT> celestronSCT(new CelestronSCT());
35
37 truePosMax(0xffffffff),
38 truePosMin(0),
39 backlashMove(false),
40 finalPosition(0),
41 calibrateInProgress(false),
42 calibrateState(0),
43 focuserIsCalibrated(false)
44
45{
46 // Can move in Absolute & Relative motions, can AbortFocuser motion.
47 // CR variable speed and sync removed
49
50 communicator.source = Aux::Target::APP;
51}
52
54{
56
57 // Focuser backlash
58 // CR this is a value, positive or negative to define the direction. It will need to be implemented
59 // in the driver.
60 FocusBacklashNP[0].setMin(-500);
61 FocusBacklashNP[0].setMax(500);
62 FocusBacklashNP[0].setStep(1);
63 FocusBacklashNP[0].setValue(0);
64
65 // IUFillNumber(&FocusBacklashN[0], "STEPS", "Steps", "%.f", -500., 500., 1., 0.);
66 // IUFillNumberVector(&FocusBacklashNP, FocusBacklashN, 1, getDeviceName(), "FOCUS_BACKLASH", "Backlash",
67 // MAIN_CONTROL_TAB, IP_RW, 0, IPS_IDLE);
68
69 // // Focuser min limit
70 // IUFillNumber(&FocusMinPosN[0], "FOCUS_MIN_VALUE", "Steps", "%.f", 0, 40000., 1., 0.);
71 // IUFillNumberVector(&FocusMinPosNP, FocusMinPosN, 1, getDeviceName(), "FOCUS_MIN", "Min. Position",
72 // MAIN_CONTROL_TAB, IP_RO, 0, IPS_IDLE);
73
74 // focuser calibration
75 CalibrateSP[START].fill("START", "Start Calibration", ISS_OFF);
76 CalibrateSP[STOP].fill("STOP", "Stop Calibration", ISS_OFF);
77 CalibrateSP.fill(getDeviceName(), "CALIBRATE", "Calibrate control",
79
80 CalibrateStateTP[0].fill("CALIBRATE_STATE", "Calibrate state", "");
81 CalibrateStateTP.fill(getDeviceName(), "CALIBRATE_STATE", "Calibrate State",
83
84 // Speed range
85 // CR no need to have adjustable speed, how to remove?
86 FocusSpeedNP[0].setMin(0);
87 FocusSpeedNP[0].setMax(3);
88 FocusSpeedNP[0].setValue(1);
89
90 // From online screenshots, seems maximum value is 60,000 steps
91 // max and min positions can be read from a calibrated focuser
92
93 // Relative Position Range
94 FocusRelPosNP[0].setMin(0.);
95 FocusRelPosNP[0].setMax(30000.);
96 FocusRelPosNP[0].setValue(0);
97 FocusRelPosNP[0].setStep(1000);
98
99 // Absolute Position Range
100 FocusAbsPosNP[0].setMin(0.);
101 FocusAbsPosNP[0].setMax(60000.);
102 FocusAbsPosNP[0].setValue(0);
103 FocusAbsPosNP[0].setStep(1000);
104
105 // Maximum Position Settings
106 FocusMaxPosNP[0].setMax(60000);
107 FocusMaxPosNP[0].setMin(1000);
108 FocusMaxPosNP[0].setValue(60000);
110
111 // Poll every 500ms
113
114 // Add debugging support
116
117 // Set default baud rate to 9600
118 // On aarch64 19200 or more seems to crash the whole USB hub.
119 // stty -a says the baud rate is 9600
121
122 communicator.setDeviceName(getDeviceName());
123
124 // Default port to /dev/ttyACM0
125 //serialConnection->setDefaultPort("/dev/ttyACM0");
126
127 //LOG_INFO("initProperties end");
128 return true;
129}
130
132{
134
135 if (isConnected())
136 {
137 //defineProperty(&FocusBacklashNP);
138
139 // defineProperty(&FocusMinPosNP);
140
141 defineProperty(CalibrateSP);
142 defineProperty(CalibrateStateTP);
143
144 if (getStartupParameters())
145 LOG_INFO("Celestron SCT focuser parameters updated, focuser ready for use.");
146 else
147 LOG_WARN("Failed to retrieve some focuser parameters. Check logs.");
148
149 }
150 else
151 {
152 //deleteProperty(FocusBacklashNP.name);
153 // deleteProperty(FocusMinPosNP.name);
154 deleteProperty(CalibrateSP);
155 deleteProperty(CalibrateStateTP);
156 }
157
158 return true;
159}
160
162{
163 if (Ack())
164 {
165 LOG_INFO("Celestron SCT Focuser is online. Getting focus parameters...");
166 return true;
167 }
168
169 LOG_INFO("Error retrieving data from Celestron SCT, please ensure Celestron SCT controller is powered and the port is correct.");
170 return false;
171}
172
174{
175 return "Celestron SCT";
176}
177
178bool CelestronSCT::Ack()
179{
180 // send simple command to focuser and check response to make sure
181 // it is online and responding
182 // use get firmware version command
183 Aux::buffer reply;
185 return false;
186
187 // Check minimum size for firmware version
188 if (reply.empty())
189 {
190 LOG_ERROR("Empty response from focuser");
191 return false;
192 }
193
194 // Ensure we have at least 2 bytes for major.minor version
195 if (reply.size() < 2)
196 {
197 LOG_ERROR("Incomplete firmware version response");
198 return false;
199 }
200
201 if (reply.size() >= 4)
202 {
203 // Full version with build number
204 uint16_t build = (reply[2] << 8);
205 if (reply.size() > 4)
206 build += reply[3];
207 LOGF_INFO("Firmware Version %d.%d.%d", reply[0], reply[1], build);
208 }
209 else
210 {
211 // Just major.minor version
212 LOGF_INFO("Firmware Version %d.%d", reply[0], reply[1]);
213 }
214 return true;
215}
216
217bool CelestronSCT::readPosition()
218{
219 Aux::buffer reply;
221 return false;
222
223 // Position response should be 3 bytes
224 if (reply.size() < 3)
225 {
226 LOG_ERROR("Invalid position response size");
227 return false;
228 }
229
230 int truePos = (reply[0] << 16) + (reply[1] << 8) + reply[2];
231 LOGF_DEBUG("True Position %d", truePos);
232 FocusAbsPosNP[0].setValue(absPos(truePos));
233 return true;
234}
235
236bool CelestronSCT::isMoving()
237{
238 Aux::buffer reply;
240 {
241 LOG_ERROR("Failed to get motion status");
242 return false;
243 }
244
245 if (reply.empty())
246 {
247 LOG_ERROR("Empty motion status response");
248 return false;
249 }
250
251 return reply[0] != static_cast<uint8_t>(0xFF);
252}
253
254// read the focuser limits from the hardware
255bool CelestronSCT::readLimits()
256{
257 Aux::buffer reply;
259 return false;
260
261 // Limits response should be 8 bytes (4 bytes each for min and max positions)
262 if (reply.size() < 8)
263 {
264 LOG_ERROR("Invalid limits response size");
265 return false;
266 }
267
268 truePosMin = (reply[0] << 24) + (reply[1] << 16) + (reply[2] << 8) + reply[3];
269 truePosMax = (reply[4] << 24) + (reply[5] << 16) + (reply[6] << 8) + reply[7];
270
271 // check on integrity of values
272 if (truePosMax <= truePosMin)
273 {
274 focuserIsCalibrated = false;
275 LOGF_INFO("Focus range %i to %i invalid", truePosMin, truePosMax);
276 return false;
277 }
278
279 // absolute direction is reverse from true
280 FocusAbsPosNP[0].setMax(absPos(truePosMin));
281 FocusMaxPosNP[0].setValue(absPos(truePosMin));
286
287 // FocusMinPosNP[0].setValue(lo);
288 // FocusMinPosNP.s = IPS_OK;
289 // IDSetNumber(&FocusMinPosNP, nullptr);
290
291 focuserIsCalibrated = true;
292 LOGF_INFO("Focus range %i to %i valid", truePosMin, truePosMax);
293
294 return true;
295}
296
297//bool CelestronSCT::ISNewNumber(const char * dev, const char * name, double values[], char * names[], int n)
298//{
299// if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
300// {
301// // Backlash
302// if (!strcmp(name, FocusBacklashNP.name))
303// {
304// // just update the number
305// IUUpdateNumber(&FocusBacklashNP, values, names, n);
306// FocusBacklashNP.s = IPS_OK;
307// IDSetNumber(&FocusBacklashNP, nullptr);
308// return true;
309// }
310// }
311// return INDI::Focuser::ISNewNumber(dev, name, values, names, n);
312//}
313
314bool CelestronSCT::ISNewSwitch(const char * dev, const char * name, ISState *states, char * names[], int n)
315{
316 if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
317 {
318 if (CalibrateSP.isNameMatch(name))
319 {
320 CalibrateSP.update(states, names, n);
321 int index = CalibrateSP.findOnSwitchIndex();
322 Aux::buffer data = {1};
323 switch(index)
324 {
325 case 0:
326 // start calibrate
327 LOG_INFO("Focuser Calibrate start");
328 calibrateInProgress = true;
329 calibrateState = -1;
330 break;
331 case 1:
332 // abort calibrate
333 LOG_INFO("Focuser Calibrate abort");
334 data[0] = 0;
335 break;
336 default:
337 return false;
338 }
340 usleep(500000);
341 CalibrateSP.setState(IPS_BUSY);
342 CalibrateSP.apply();
343 return true;
344 }
345 }
346 return INDI::Focuser::ISNewSwitch(dev, name, states, names, n);
347}
348
349bool CelestronSCT::getStartupParameters()
350{
351 bool rc1 = false, rc2 = false;
352
353 if ( (rc1 = readPosition()))
355
356 if ( !(rc2 = readLimits()))
357 {
358 LOG_WARN("Focuser not calibrated, You MUST calibrate before moves are allowed.");
359 }
360
361 return (rc1 && rc2);
362}
363
365{
366 // Send command to focuser
367 // If OK and moving, return IPS_BUSY (CR don't see this, it seems to just start a new move)
368 // If OK and motion already done (was very small), return IPS_OK
369 // If error, return IPS_ALERT
370
371 if (!focuserIsCalibrated)
372 {
373 LOG_ERROR("Move not allowed because focuser is not calibrated.");
374 return IPS_ALERT;
375 }
376 if (calibrateInProgress)
377 {
378 LOG_WARN("Move not allowed because a calibration is in progress");
379 return IPS_ALERT;
380 }
381
382 // the focuser seems happy to move 500 steps past the soft limit so don't check backlash
383 if (targetTicks > FocusMaxPosNP[0].getValue())
384 // targetTicks < FocusMinPosN[0].value)
385
386 {
387 LOGF_ERROR("Move to %i not allowed because it is out of range", targetTicks);
388 return IPS_ALERT;
389 }
390
391 uint32_t position = targetTicks;
392
393 // implement backlash
394 int delta = targetTicks - FocusAbsPosNP[0].getValue();
395 if ((FocusBacklashNP[0].getValue() < 0 && delta > 0) || (FocusBacklashNP[0].getValue() > 0 && delta < 0))
396 {
397 backlashMove = true;
398 finalPosition = position;
399 position -= FocusBacklashNP[0].getValue();
400 }
401
402 if (!startMove(position))
403 return IPS_ALERT;
404
405 return IPS_BUSY;
406}
407
408bool CelestronSCT::startMove(uint32_t absPos)
409{
410
411 int position = truePos(absPos);
412
413 Aux::buffer data =
414 {
415 static_cast<uint8_t>((position >> 16) & 0xFF),
416 static_cast<uint8_t>((position >> 8) & 0xFF),
417 static_cast<uint8_t>(position & 0xFF)
418 };
419
420 LOGF_DEBUG("startMove to true position %i, %x %x %x", position, data[0], data[1], data[2]);
421
423}
424
426{
427 int32_t newPosition = 0;
428
429 if (dir == FOCUS_INWARD)
430 newPosition = FocusAbsPosNP[0].getValue() - ticks;
431 else
432 newPosition = FocusAbsPosNP[0].getValue() + ticks;
433
434 // Clamp
435 newPosition = std::max(0, std::min(static_cast<int32_t>(FocusAbsPosNP[0].getMax()), newPosition));
436 return MoveAbsFocuser(newPosition);
437}
438
440{
441 if (!isConnected())
442 {
444 return;
445 }
446
447 // Check position
448 double lastPosition = FocusAbsPosNP[0].getValue();
449 bool rc = readPosition();
450 if (rc)
451 {
452 // Only update if there is actual change
453 if (fabs(lastPosition - FocusAbsPosNP[0].getValue()) > 1)
455 }
456
458 {
459 // CR The backlash handling will probably have to be done here, if the move state
460 // shows that a backlash move has been done then the final move needs to be started
461 // and the states left at IPS_BUSY
462
463 // There are two ways to know when focuser motion is over
464 // define class variable uint32_t m_TargetPosition and set it in MoveAbsFocuser(..) function
465 // then compare current value to m_TargetPosition
466 // The other way is to have a function that calls a focuser specific function about motion
467 if (!isMoving())
468 {
469 if (backlashMove)
470 {
471 backlashMove = false;
472 if (startMove(finalPosition))
473 LOGF_INFO("Backlash move to %i", finalPosition);
474 else
475 LOG_INFO("Backlash move failed");
476 }
477 else
478 {
483 LOG_INFO("Focuser reached requested position.");
484 }
485 }
486 }
487
488 if (calibrateInProgress)
489 {
490 usleep(500000); // slowing things down while calibrating seems to help
491 // check the calibration state
492 Aux::buffer reply;
494 {
495 LOG_ERROR("Failed to get calibration status");
496 return;
497 }
498
499 // Need at least 2 bytes for complete flag and state
500 if (reply.size() < 2)
501 {
502 LOG_ERROR("Invalid calibration status response size");
503 return;
504 }
505
506 bool complete = reply[0] > 0;
507 int state = reply[1];
508
509 if (complete || state == 0)
510 {
511 // a completed calibration returns complete as true, an aborted calibration sets the status to zero
512
513 const char *msg = complete ? "Calibrate complete" : "Calibrate aborted";
514 LOG_INFO(msg);
515 calibrateInProgress = false;
516 CalibrateSP[STOP].setState(ISS_OFF);
517 CalibrateSP.setState(IPS_OK);
518 CalibrateStateTP[0].setText(msg);
519 CalibrateSP.apply();
520 CalibrateStateTP.apply();
521 // read the new limits
522 if (complete && readLimits())
523 {
526 // IDSetNumber(&FocusMinPosNP, nullptr);
527 }
528 }
529 else
530 {
531 if (state != calibrateState)
532 {
533 calibrateState = state;
534 char str[20];
535 snprintf(str, 20, "Calibrate state %i", state);
536 CalibrateStateTP[0].setText(str);
537 CalibrateStateTP.apply(nullptr);
538 }
539 }
540 }
541
543}
544
546{
547 if (calibrateInProgress)
548 {
549 LOG_WARN("Abort move not allowed when calibrating, use abort calibration to stop");
550 return false;
551 }
552 // send a command to move at rate 0
553 Aux::buffer data = {0};
555}
556
558{
559 INDI_UNUSED(steps);
560 return true;
561}
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
Updates the state and definition of properties based on the current connection status....
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 AbortFocuser() override
AbortFocuser Abort Focuser motion.
virtual IPState MoveAbsFocuser(uint32_t targetTicks) override
MoveAbsFocuser Move to an absolute target position.
virtual bool Handshake() override
Handshake Try to communicate with Focuser and see if there is a valid response.
virtual IPState MoveRelFocuser(FocusDirection dir, uint32_t ticks) override
MoveRelFocuser Move focuser for a relative amount of ticks in a specific direction.
virtual bool initProperties() override
Initializes the default properties for the focuser device. Sets up standard INDI properties,...
Definition celestron.cpp:53
virtual void TimerHit() override
TimerHit Primary Loop called every POLLMS milliseconds (set in Options) to check on the focuser statu...
virtual bool SetFocuserBacklash(int32_t steps) override
SetFocuserBacklash Set the focuser backlash compensation value.
const char * getDefaultName() override
void setDefaultBaudRate(BaudRate newRate)
setDefaultBaudRate Set default baud rate. The default baud rate is 9600 unless otherwise changed by t...
bool isConnected() const
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.
INDI::PropertyNumber FocusRelPosNP
void SetCapability(uint32_t cap)
FI::SetCapability sets the focuser capabilities. All capabilities must be initialized.
INDI::PropertyNumber FocusSpeedNP
INDI::PropertyNumber FocusMaxPosNP
INDI::PropertyNumber FocusBacklashNP
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,...
Connection::Serial * serialConnection
void setState(IPState state)
void apply(const char *format,...) const ATTRIBUTE_FORMAT_PRINTF(2
bool isNameMatch(const char *otherName) const
void setPermission(IPerm permission)
bool update(const ISState states[], const char *const names[], int n)
void fill(const char *device, const char *name, const char *label, const char *group, IPerm permission, ISRule rule, double timeout, IPState state)
void fill(const char *device, const char *name, const char *label, const char *group, IPerm permission, double timeout, IPState state)
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
Implementations for common driver routines.
#define INDI_UNUSED(x)
Definition indidevapi.h:132
#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
#define getDeviceName
@ MC_GOTO_FAST
send 24 bit target
@ GET_VER
return 2 or 4 bytes major.minor.build
@ MC_GET_POSITION
return 24 bit position
@ FOC_GET_HS_POSITIONS
returns 2 ints low and high limits
@ FOC_CALIB_ENABLE
send 0 to start or 1 to stop
@ MC_MOVE_POS
send move rate 0-9
@ MC_SLEW_DONE
return 0xFF when move finished
@ FOC_CALIB_DONE
returns 2 bytes [0] done, [1] state 0-12
std::vector< uint8_t > buffer
@ FOCUSER
focuser motor