Instrument Neutral Distributed Interface INDI 2.2.5
pegasus_focuscube3.cpp
Go to the documentation of this file.
1/*******************************************************************************
2 Copyright(c) 2023 Chrysikos Efstathios. All rights reserved.
3
4 Pegasus FocusCube
5
6 This program is free software; you can redistribute it and/or modify it
7 under the terms of the GNU General Public License as published by the Free
8 Software Foundation; either version 2 of the License, or (at your option)
9 any later version.
10
11 This program is distributed in the hope that it will be useful, but WITHOUT
12 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
14 more details.
15
16 You should have received a copy of the GNU Library General Public License
17 along with this library; see the file COPYING.LIB. If not, write to
18 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 Boston, MA 02110-1301, USA.
20
21 The full GNU General Public License is included in this distribution in the
22 file called LICENSE.
23*******************************************************************************/
24#include "pegasus_focuscube3.h"
25
26#include "indicom.h"
28
29#include <cmath>
30#include <cstring>
31#include <memory>
32#include <regex>
33#include <termios.h>
34#include <unistd.h>
35
36#define DMFC_TIMEOUT 3
37#define FOCUS_SETTINGS_TAB "Settings"
38#define TEMPERATURE_THRESHOLD 0.1
39
40static std::unique_ptr<PegasusFocusCube3> focusCube(new PegasusFocusCube3());
41
42
44{
45 setVersion(1, 1);
46 // Can move in Absolute & Relative motions, can AbortFocuser motion.
53}
54
55
57{
58 int tty_rc = 0, nbytes_written = 0, nbytes_read = 0;
59 char command[PEGASUS_LEN] = {0}, response[PEGASUS_LEN] = {0};
61 LOG_DEBUG("CMD <##>");
62
63
64
65
66 tcflush(PortFD, TCIOFLUSH);
67 strncpy(command, "##\r\n", PEGASUS_LEN);
68 if ( (tty_rc = tty_write_string(PortFD, command, &nbytes_written)) != TTY_OK)
69 {
70 char errorMessage[MAXRBUF];
71 tty_error_msg(tty_rc, errorMessage, MAXRBUF);
72 LOGF_ERROR("Serial write error: %s", errorMessage);
73 return false;
74 }
75
76 // Try first with stopChar as the stop character
77 if ( (tty_rc = tty_nread_section(PortFD, response, PEGASUS_LEN, stopChar, 1, &nbytes_read)) != TTY_OK)
78 {
79 // Try 0xA as the stop character
80 if (tty_rc == TTY_OVERFLOW || tty_rc == TTY_TIME_OUT)
81 {
82 tcflush(PortFD, TCIOFLUSH);
83 tty_write_string(PortFD, command, &nbytes_written);
84 stopChar = 0xA;
85 tty_rc = tty_nread_section(PortFD, response, PEGASUS_LEN, stopChar, 1, &nbytes_read);
86 }
87
88 if (tty_rc != TTY_OK)
89 {
90 char errorMessage[MAXRBUF];
91 tty_error_msg(tty_rc, errorMessage, MAXRBUF);
92 LOGF_ERROR("Serial read error: %s", errorMessage);
93 return false;
94 }
95 }
96
97 tcflush(PortFD, TCIOFLUSH);
98 response[nbytes_read - 1] = '\0';
99 LOGF_DEBUG("RES <%s>", response);
100
101 setupComplete = false;
102
103 return (strstr(response, "FC3") != nullptr);
104}
105
107{
108 return "Pegasus FocusCube3";
109}
110
112{
114
115 TemperatureNP[0].fill("TEMPERATURE", "Level", "%.0f", -40, 40, 1, 0);
116 TemperatureNP.fill(getDeviceName(), "FOCUS_TEMPERATURE", "Temperature", FOCUS_SETTINGS_TAB, IP_RW, 60, IPS_IDLE);
117
118 FirmwareVersionTP[0].fill("Version", "Version", "");
119 FirmwareVersionTP.fill(getDeviceName(), "Firmware", "Firmware", MAIN_CONTROL_TAB, IP_RO, 0, IPS_IDLE);
120
121 SpeedNP[0].fill("Speed", "Value", "%6.2f", 100, 1000., 100., 400.);
122 SpeedNP.fill(getDeviceName(), "MaxSpeed", "", FOCUS_SETTINGS_TAB, IP_RW, 0, IPS_IDLE);
123
124 // Relative and absolute movement
125 FocusRelPosNP[0].setMin(0.);
126 FocusRelPosNP[0].setMax(50000.);
127 FocusRelPosNP[0].setValue(0);
128 FocusRelPosNP[0].setStep(1000);
129
130 FocusAbsPosNP[0].setMin(0.);
131 FocusAbsPosNP[0].setValue(0);
132 FocusAbsPosNP[0].setStep(1000);
133
134 // Backlash compensation
135 FocusBacklashNP[0].setMin(1); // 0 is off.
136 FocusBacklashNP[0].setMax(1000);
137 FocusBacklashNP[0].setValue(1);
138 FocusBacklashNP[0].setStep(1);
139
140 FocusMaxPosNP[0].setMax(1317500);
141 FocusMaxPosNP[0].setValue(1317500);
142 FocusAbsPosNP[0].setMax(1317500);
143
148
149 return true;
150}
151
153{
155
156 if (isConnected())
157 {
158 defineProperty(TemperatureNP);
159
160 defineProperty(FirmwareVersionTP);
161 std::string firmware = getFirmwareVersion();
162 FirmwareVersionTP[0].setText(firmware);
163 FirmwareVersionTP.setState(IPS_OK);
164 FirmwareVersionTP.apply();
165 defineProperty(SpeedNP);
166
167
168 }
169 else
170 {
171 deleteProperty(TemperatureNP);
172 deleteProperty(FirmwareVersionTP);
173 deleteProperty(SpeedNP);
174 }
175
176 return true;
177}
178
179
180bool PegasusFocusCube3::ISNewNumber(const char * dev, const char * name, double values[], char * names[], int n)
181{
182 if (dev != nullptr && strcmp(dev, getDeviceName()))
183 {
184
185 if(SpeedNP.isNameMatch(name))
186 {
187 SpeedNP.update(values, names, n);
188 IPState result = IPS_OK;
189 if (isConnected())
190 {
191 if(!setSpeed(values[0]))
192 {
193 result = IPS_ALERT;
194 }
195 }
196 SpeedNP.setState(result);
197 SpeedNP.apply();
198 }
199
200 }
201
202 return INDI::Focuser::ISNewNumber(dev, name, values, names, n);
203}
204
206{
207 targetPosition = targetTicks;
208
209 bool rc = move(targetPosition);
210
211 if (!rc)
212 return IPS_ALERT;
213
215
216 return IPS_BUSY;
217}
218
220{
221 double newPosition = 0;
222 bool rc = false;
223
224 if (dir == FOCUS_INWARD)
225 newPosition = FocusAbsPosNP[0].getValue() - ticks;
226 else
227 newPosition = FocusAbsPosNP[0].getValue() + ticks;
228
229 rc = move(newPosition);
230
231 if (!rc)
232 return IPS_ALERT;
233
234 FocusRelPosNP[0].setValue(ticks);
236
237 return IPS_BUSY;
238}
239
240bool PegasusFocusCube3::move(uint32_t newPosition)
241{
242 char cmd[PEGASUS_LEN] = {0}, res[PEGASUS_LEN] = {0}, expectedResponse[PEGASUS_LEN] = {0};
243 snprintf(expectedResponse, PEGASUS_LEN, "%u", newPosition);
244 snprintf(cmd, PEGASUS_LEN, "FM:%d", newPosition);
245 if(sendCommand(cmd, res))
246 {
247 if(!strstr(expectedResponse, res))
248 {
249 LOGF_ERROR("Error on move [Cmd=%s Res=%s]", cmd, res);
250 return false;
251 }
252 }
253 else
254 {
255 LOGF_ERROR("Error on move [Position=%d]", newPosition);
256 return false;
257 }
258 return true;
259}
260
261
262bool PegasusFocusCube3::sendCommand(const char * cmd, char * res)
263{
264 int nbytes_read = 0, nbytes_written = 0, tty_rc = 0;
265 char command[PEGASUS_LEN] = {0};
266 LOGF_DEBUG("CMD <%s>", cmd);
267
268 for (int i = 0; i < 2; i++)
269 {
270 tcflush(PortFD, TCIOFLUSH);
271 snprintf(command, PEGASUS_LEN, "%s\n", cmd);
272 if ( (tty_rc = tty_write_string(PortFD, command, &nbytes_written)) != TTY_OK)
273 continue;
274
275 if (!res)
276 {
277 tcflush(PortFD, TCIOFLUSH);
278 return true;
279 }
280
281 if ( (tty_rc = tty_nread_section(PortFD, res, PEGASUS_LEN, stopChar, PEGASUS_TIMEOUT, &nbytes_read)) != TTY_OK
282 || nbytes_read == 1)
283 continue;
284
285 tcflush(PortFD, TCIOFLUSH);
286 res[nbytes_read - 1] = '\0';
287 LOGF_DEBUG("RES <%s>", res);
288 return true;
289 }
290
291 if (tty_rc != TTY_OK)
292 {
293 char errorMessage[MAXRBUF];
294 tty_error_msg(tty_rc, errorMessage, MAXRBUF);
295 LOGF_ERROR("Serial error: %s", errorMessage);
296 }
297
298 return false;
299}
300
301bool PegasusFocusCube3::setSpeed(uint16_t speed)
302{
303
304 char cmd[PEGASUS_LEN] = {0}, res[PEGASUS_LEN] = {0}, expectedResponse[PEGASUS_LEN] = {0};
305 snprintf(expectedResponse, PEGASUS_LEN, "%u", speed);
306 snprintf(cmd, PEGASUS_LEN, "SP:%d", speed);
307 if(sendCommand(expectedResponse, res))
308 {
309 if(!strstr(cmd, res))
310 {
311 LOGF_ERROR("Error on set speed [Cmd=%s Res=%s]", cmd, res);
312 return false;
313 }
314 }
315 else
316 {
317 LOGF_ERROR("Error on set speed [Speed=%d]", speed);
318 return false;
319 }
320 return true;
321}
322
323std::string PegasusFocusCube3::getFirmwareVersion()
324{
325 char cmd[PEGASUS_LEN] = {0}, res[PEGASUS_LEN] = {0};
326 snprintf(cmd, PEGASUS_LEN, "FV");
327 if(sendCommand(cmd, res))
328 {
329
330 }
331 else
332 {
333 LOG_ERROR("Error on get Firmware");
334 }
335 return res;
336}
337
338std::vector<std::string> PegasusFocusCube3::split(const std::string &input, const std::string &regex)
339{
340 // passing -1 as the submatch index parameter performs splitting
341 std::regex re(regex);
342 std::sregex_token_iterator
343 first{input.begin(), input.end(), re, -1},
344 last;
345 return {first, last};
346}
347
349{
350 char cmd[PEGASUS_LEN] = {0}, res[PEGASUS_LEN] = {0};
351 snprintf(cmd, PEGASUS_LEN, "FH");
352 if(sendCommand(cmd, res))
353 {
354
355 }
356 else
357 {
358 LOG_ERROR("Error on abort");
359 return false;
360 }
361
362
367 return true;
368}
369
371{
372 char cmd[PEGASUS_LEN] = {0}, res[PEGASUS_LEN] = {0}, expectedResponse[PEGASUS_LEN] = {0};
373 snprintf(expectedResponse, PEGASUS_LEN, "%u", ticks);
374 snprintf(cmd, PEGASUS_LEN, "FN:%ud", ticks);
375 if(sendCommand(cmd, res))
376 {
377 if(!strstr(expectedResponse, res))
378 {
379 LOGF_ERROR("Error on sync [Cmd=%s Res=%s]", cmd, res);
380 return false;
381 }
382 }
383 else
384 {
385 LOG_ERROR("Error on sync");
386 return false;
387 }
388
389 return true;
390}
391
393{
394 char cmd[PEGASUS_LEN] = {0}, res[PEGASUS_LEN] = {0};
395 snprintf(cmd, PEGASUS_LEN, "FD:%d", enabled ? 1 : 0);
396 if(sendCommand(cmd, res))
397 {
398
399 }
400 else
401 {
402 LOG_ERROR("Error on sync");
403 return false;
404 }
405
406 return true;
407}
408
410{
411 char cmd[PEGASUS_LEN] = {0}, res[PEGASUS_LEN] = {0}, expectedResponse[PEGASUS_LEN] = {0};
412 snprintf(expectedResponse, PEGASUS_LEN, "%u", steps);
413 snprintf(cmd, PEGASUS_LEN, "BL:%d", steps);
414 if(sendCommand(cmd, res))
415 {
416 if(!strstr(expectedResponse, res))
417 {
418 LOGF_ERROR("Error on sync [Cmd=%s Res=%s]", cmd, res);
419 return false;
420 }
421 }
422 else
423 {
424 LOG_ERROR("Error on sync");
425 return false;
426 }
427
428 return true;
429}
430
432{
433 if (!isConnected())
434 {
436 return;
437 }
438
439 bool rc = updateFocusParams();
440
441 if (rc)
442 {
444 {
445 if (isMoving == false)
446 {
451 LOG_INFO("Focuser reached requested position.");
452 }
453 }
454 }
455
457}
458
459bool PegasusFocusCube3::updateFocusParams()
460{
461 char cmd[PEGASUS_LEN] = {0}, res[PEGASUS_LEN] = {0};
462 snprintf(cmd, PEGASUS_LEN, "FA");
463 if(sendCommand(cmd, res))
464 {
465
466 }
467 else
468 {
469 LOGF_ERROR("Error on [Cmd=%s Res=%s]", cmd, res);
470 return false;
471 }
472
473 std::vector<std::string> result = split(res, ":");
474
475 // Status
476 if(result[0] != "FC3")
477 return false;
478
479
480 // #1 Position
481 currentPosition = atoi(result[1].c_str());
482 if (currentPosition != FocusAbsPosNP[0].getValue())
483 {
484 FocusAbsPosNP[0].setValue(currentPosition);
486 }
487
488 // #2 Moving Status
489 isMoving = (result[2] == "1");
490
491
492 // #3 Temperature
493 TemperatureNP[0].setValue(atof(result[3].c_str()));
494 TemperatureNP.setState(IPS_OK);
495 TemperatureNP.apply();
496
497
498 // #4 Reverse Status
499 int reverseStatus = atoi(result[4].c_str());
500 if (reverseStatus >= 0 && reverseStatus <= 1)
501 {
503 FocusReverseSP[INDI_ENABLED].setState((reverseStatus == 1) ? ISS_ON : ISS_OFF);
504 FocusReverseSP[INDI_DISABLED].setState((reverseStatus == 0) ? ISS_ON : ISS_OFF);
507 }
508
509
510 // #5 Backlash
511
512 int backlash = atoi(result[5].c_str());
513 // If backlash is zero then compensation is disabled
514 if (backlash == 0 && FocusBacklashSP[INDI_ENABLED].getState() == ISS_ON)
515 {
516 LOG_WARN("Backlash value is zero, disabling backlash switch...");
517
522 }
523 else if (backlash > 0 && (FocusBacklashSP[INDI_DISABLED].getState() == ISS_ON || backlash != FocusBacklashNP[0].getValue()))
524 {
525 if (backlash != FocusBacklashNP[0].getValue())
526 {
527 FocusBacklashNP[0].setValue(backlash);
530 }
531
532 if (FocusBacklashSP[INDI_DISABLED].getState() == ISS_ON)
533 {
538 }
539 }
540
541 return true;
542}
void setPortMatchPattern(const std::string &pattern)
setPortMatchPattern Set a regex pattern used to automatically select a serial port from the discovere...
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.
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.
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
INDI::PropertySwitch FocusReverseSP
void SetCapability(uint32_t cap)
FI::SetCapability sets the focuser capabilities. All capabilities must be initialized.
INDI::PropertyNumber FocusMaxPosNP
INDI::PropertySwitch FocusBacklashSP
INDI::PropertyNumber FocusBacklashNP
INDI::PropertyNumber FocusAbsPosNP
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,...
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Processes incoming new number values from the client for this device. Handles updates to FocuserInter...
Connection::Serial * serialConnection
void setState(IPState state)
void apply(const char *format,...) const ATTRIBUTE_FORMAT_PRINTF(2
bool isNameMatch(const char *otherName) const
bool update(const double values[], const char *const names[], int n)
void fill(const char *device, const char *name, const char *label, const char *group, IPerm permission, double timeout, IPState state)
void fill(const char *device, const char *name, const char *label, const char *group, IPerm permission, double timeout, IPState state)
virtual bool initProperties() override
Initializes the default properties for the focuser device. Sets up standard INDI properties,...
virtual bool SetFocuserBacklash(int32_t steps) override
SetFocuserBacklash Set the focuser backlash compensation value.
static constexpr const uint8_t PEGASUS_LEN
virtual bool ReverseFocuser(bool enabled) override
ReverseFocuser Reverse focuser motion direction.
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Processes incoming new number values from the client for this device. Handles updates to FocuserInter...
virtual bool SyncFocuser(uint32_t ticks) override
SyncFocuser Set current position to ticks without moving the focuser.
static constexpr const uint8_t PEGASUS_TIMEOUT
bool sendCommand(const char *cmd, char *res)
virtual bool updateProperties() override
Updates the state and definition of properties based on the current connection status....
virtual bool AbortFocuser() override
AbortFocuser all focus motion.
virtual IPState MoveAbsFocuser(uint32_t targetTicks) override
MoveFocuser the focuser to an absolute position.
const char * getDefaultName() override
virtual IPState MoveRelFocuser(FocusDirection dir, uint32_t ticks) override
MoveFocuser the focuser to an relative position.
virtual void TimerHit() override
Callback function to be called once SetTimer duration elapses.
std::vector< std::string > split(const std::string &input, const std::string &regex)
virtual bool Handshake() override
Performs a device-specific handshake after a connection is established. This method should be overrid...
const char * MAIN_CONTROL_TAB
MAIN_CONTROL_TAB Where all the primary controls for the device are located.
#define FOCUS_SETTINGS_TAB
Definition dmfc.cpp:34
@ ISS_OFF
Definition indiapi.h:149
@ ISS_ON
Definition indiapi.h:150
@ 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
int tty_write_string(int fd, const char *buf, int *nbytes_written)
Writes a null terminated string to fd.
Definition indicom.c:473
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
@ TTY_TIME_OUT
Definition indicom.h:58
@ TTY_OVERFLOW
Definition indicom.h:62
#define MAXRBUF
Definition indidriver.c:52
#define LOG_DEBUG(txt)
Definition indilogger.h:75
#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
uint8_t cmd[4]
Definition pwc-ioctl.h:2