Instrument Neutral Distributed Interface INDI 2.2.5
pegasus_falconv2.cpp
Go to the documentation of this file.
1/*******************************************************************************
2 Copyright(c) 2024 Chrysikos Efstathios. All rights reserved.
3
4 Pegasus FalconV2 Rotator
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 Corrections by T. Schriber 2022 following
25 'FalconV2 Rotator Serial Command Language Firmware >=v.1.3 (review Sep 2020)'
26*******************************************************************************/
27
28#include "pegasus_falconv2.h"
29#include "indicom.h"
31
32#include <cmath>
33#include <memory>
34#include <regex>
35#include <termios.h>
36#include <cstring>
37#include <sys/ioctl.h>
38#include <chrono>
39#include <math.h>
40#include <iomanip>
41
42// We declare an auto pointer to PegasusFalconV2.
43static std::unique_ptr<PegasusFalconV2> falconv2(new PegasusFalconV2());
44
46{
47 setVersion(1, 1);
48 lastStatusData.reserve(7);
49}
50
52{
54
58
61
65 // Reload Firmware
66 ReloadFirmwareSP[0].fill("RELOAD", "Reload", ISS_OFF);
67 ReloadFirmwareSP.fill(getDeviceName(), "RELOAD_FIRMWARE", "Firmware", MAIN_CONTROL_TAB,
69 60, IPS_IDLE);
70
71 // Derotate
72 DerotateNP[0].fill("INTERVAL", "Interval (ms)", "%.f", 0, 10000, 1000, 0);
73 DerotateNP.fill(getDeviceName(), "ROTATOR_DEROTATE", "Derotation", MAIN_CONTROL_TAB, IP_RW,
74 60, IPS_IDLE);
75
76 // Rotator Speed
77 RotatorSpeedNP[0].fill("SPEED", "Speed (steps/sec)", "%.f", 10, 4500, 10, 500);
78 RotatorSpeedNP.fill(getDeviceName(), "ROTATOR_SPEED", "Speed", MAIN_CONTROL_TAB, IP_RW,
79 60, IPS_IDLE);
80
81 // Firmware
82 FirmwareTP[0].fill("VERSION", "Version", "NA");
83 FirmwareTP.fill(getDeviceName(), "FIRMWARE_INFO", "Firmware", MAIN_CONTROL_TAB, IP_RO, 60,
84 IPS_IDLE);
85
86 return true;
87}
88
90{
92
93 if (isConnected())
94 {
95 // Main Control
96 defineProperty(DerotateNP);
97 defineProperty(RotatorSpeedNP);
98 defineProperty(FirmwareTP);
99 defineProperty(ReloadFirmwareSP);
100
101 }
102 else
103 {
104 // Main Control
105 deleteProperty(DerotateNP);
106 deleteProperty(RotatorSpeedNP);
107 deleteProperty(FirmwareTP);
108 deleteProperty(ReloadFirmwareSP);
109 }
110
111 return true;
112}
113
115{
116 return "Pegasus FalconV2";
117}
118
122bool PegasusFalconV2::Handshake()
123{
124 if (!getFirmware())
125 return false;
126
127 // Try to read the rotator speed, but don't fail if it doesn't work
128 if (!getRotatorSpeed())
129 LOG_WARN("Failed to read rotator speed during connection.");
130
131 return true;
132}
133
137bool PegasusFalconV2::ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
138{
139 if (dev && !strcmp(dev, getDeviceName()))
140 {
141 // De-rotation
142 if (DerotateNP.isNameMatch(name))
143 {
144 const uint32_t ms = static_cast<uint32_t>(values[0]);
145 if (setDerotation(ms))
146 {
147 DerotateNP[0].setValue(values[0]);
148 if (values[0] > 0)
149 LOGF_INFO("De-rotation is enabled and set to 1 step per %u milliseconds.", ms);
150 else
151 LOG_INFO("De-rotaiton is disabled.");
152 DerotateNP.setState(IPS_OK);
153 }
154 else
155 DerotateNP.setState(IPS_ALERT);
156 DerotateNP.apply();
157 return true;
158 }
159
160 // Rotator Speed
161 if (RotatorSpeedNP.isNameMatch(name))
162 {
163 const uint32_t speed = static_cast<uint32_t>(values[0]);
164 if (setRotatorSpeed(speed))
165 {
166 RotatorSpeedNP[0].setValue(values[0]);
167 LOGF_INFO("Rotator speed set to %u steps/sec.", speed);
168 RotatorSpeedNP.setState(IPS_OK);
169 }
170 else
171 RotatorSpeedNP.setState(IPS_ALERT);
172 RotatorSpeedNP.apply();
173 return true;
174 }
175 }
176 return Rotator::ISNewNumber(dev, name, values, names, n);
177}
178
182bool PegasusFalconV2::ISNewSwitch(const char * dev, const char * name, ISState * states, char * names[], int n)
183{
184 if (dev && !strcmp(dev, getDeviceName()))
185 {
186 // ReloadFirmware
187 if (ReloadFirmwareSP.isNameMatch(name))
188 {
189 ReloadFirmwareSP.setState(reloadFirmware() ? IPS_OK : IPS_ALERT);
190 ReloadFirmwareSP.apply();
191 LOG_INFO("Reloading firmware...");
192 return true;
193 }
194 }
195
196 return Rotator::ISNewSwitch(dev, name, states, names, n);
197}
198
203{
204 char cmd[DRIVER_LEN] = {0}, res[DRIVER_LEN] = {0};
205 snprintf(cmd, DRIVER_LEN, "MD:%.2f", angle);
206 if (sendCommand(cmd, res))
207 {
208 return (!strncmp(res, cmd, 8) ? IPS_BUSY : IPS_ALERT);
209 //Restrict length to 8 chars for correct compare
210 }
211
212 return IPS_ALERT;
213}
214
219{
220 char res[DRIVER_LEN] = {0};
221 if (sendCommand("FH", res))
222 {
223 return (!strcmp(res, "FH:1"));
224 }
225
226 return false;
227}
228
233{
234 char cmd[DRIVER_LEN] = {0}, res[DRIVER_LEN] = {0};
235 snprintf(cmd, DRIVER_LEN, "FN:%d", enabled ? 1 : 0);
236 if (sendCommand(cmd, res))
237 {
238 return (!strncmp(res, cmd, 4)); //Restrict length to 4 chars!
239 }
240
241 return false;
242}
243
248{
249 char cmd[DRIVER_LEN] = {0};
250 snprintf(cmd, DRIVER_LEN, "SD:%.2f", angle);
251 return sendCommand(cmd, nullptr);
252}
253
257bool PegasusFalconV2::reloadFirmware()
258{
259 return sendCommand("FQ");
260}
261
265bool PegasusFalconV2::setDerotation(uint32_t ms)
266{
267 char cmd[DRIVER_LEN] = {0};
268 snprintf(cmd, DRIVER_LEN, "DR:%d", ms);
269 return sendCommand(cmd, nullptr);
270}
271
275bool PegasusFalconV2::getRotatorSpeed()
276{
277 char res[DRIVER_LEN] = {0};
278 if (sendCommand("FS", res))
279 {
280 // Response format: "FS:<value>"
281 std::vector<std::string> result = split(res, ":");
282 if (result.size() == 2 && result[0] == "FS")
283 {
284 try
285 {
286 const uint32_t speed = std::stoul(result[1]);
287 RotatorSpeedNP[0].setValue(speed);
288 RotatorSpeedNP.setState(IPS_OK);
289 LOGF_INFO("Rotator speed is %u steps/sec.", speed);
290 return true;
291 }
292 catch (...)
293 {
294 RotatorSpeedNP.setState(IPS_ALERT);
295 LOGF_ERROR("Failed to parse speed response: %s", res);
296 return false;
297 }
298 }
299 else
300 {
301 LOGF_ERROR("Invalid speed response format: %s", res);
302 RotatorSpeedNP.setState(IPS_ALERT);
303 return false;
304 }
305 }
306 return false;
307}
308
312bool PegasusFalconV2::setRotatorSpeed(uint32_t speed)
313{
314 char cmd[DRIVER_LEN] = {0}, res[DRIVER_LEN] = {0};
315 snprintf(cmd, DRIVER_LEN, "FS:%u", speed);
316 if (sendCommand(cmd, res))
317 {
318 // Response should be "FS:" without the value
319 return (!strncmp(res, "FS:", 3));
320 }
321 return false;
322}
323
328{
330 DerotateNP.save(fp);
331 return true;
332}
333
338{
339 if (!isConnected())
340 return;
341 getStatusData();
343}
344
348bool PegasusFalconV2::getFirmware()
349{
350 char res[DRIVER_LEN] = {0};
351 if (sendCommand("FV", res))
352 {
353 FirmwareTP[0].setText(res + 3);
354 return true;
355 }
356
357 return false;
358}
359
363bool PegasusFalconV2::getStatusData()
364{
365 char res[DRIVER_LEN] = {0};
366 if (sendCommand("FA", res))
367 {
368 std::vector<std::string> result = split(res, ":");
369 if (result.size() != 6)
370 {
371 LOG_WARN("Received wrong number of detailed sensor data. Retrying...");
372 return false;
373 }
374
375 if (result == lastStatusData)
376 return true;
377
378 // Position
379 const double position = std::stod(result[1]);
380 // Is running?
381 const IPState motionState = std::stoi(result[2]) == 1 ? IPS_BUSY : IPS_OK;
382
383 // Update Absolute Position property if either position changes, or status changes.
384 if (std::abs(position - GotoRotatorNP[0].getValue()) > 0.01 || GotoRotatorNP.getState() != motionState)
385 {
386 GotoRotatorNP[0].setValue(position);
387 GotoRotatorNP.setState(motionState);
389 }
390
391
392 const bool reversed = std::stoi(result[5]) == 1;
393 const bool wasReversed = ReverseRotatorSP[INDI_ENABLED].getState() == ISS_ON;
394 if (reversed != wasReversed)
395 {
399 }
400
401 lastStatusData = result;
402 return true;
403 }
404
405 return false;
406}
407
411bool PegasusFalconV2::sendCommand(const char * cmd, char * res, int cmd_len, int res_len)
412{
413 int nbytes_written = 0, nbytes_read = 0, rc = -1;
414
415 tcflush(PortFD, TCIOFLUSH);
416
417 if (cmd_len > 0)
418 {
419 char hex_cmd[DRIVER_LEN * 3] = {0};
420 hexDump(hex_cmd, cmd, cmd_len);
421 LOGF_DEBUG("CMD <%s>", hex_cmd);
422 rc = tty_write(PortFD, cmd, cmd_len, &nbytes_written);
423 }
424 else
425 {
426 LOGF_DEBUG("CMD <%s>", cmd);
427
428 char formatted_command[DRIVER_LEN] = {0};
429 snprintf(formatted_command, DRIVER_LEN, "%s\n", cmd);
430 rc = tty_write_string(PortFD, formatted_command, &nbytes_written);
431 }
432
433 if (rc != TTY_OK)
434 {
435 char errstr[MAXRBUF] = {0};
436 tty_error_msg(rc, errstr, MAXRBUF);
437 LOGF_ERROR("Serial write error: %s.", errstr);
438 return false;
439 }
440
441 if (res == nullptr)
442 return true;
443
444 if (res_len > 0)
445 rc = tty_read(PortFD, res, res_len, DRIVER_TIMEOUT, &nbytes_read);
446 else
447 rc = tty_nread_section(PortFD, res, DRIVER_LEN, DRIVER_STOP_CHAR, DRIVER_TIMEOUT, &nbytes_read);
448
449 if (rc != TTY_OK)
450 {
451 char errstr[MAXRBUF] = {0};
452 tty_error_msg(rc, errstr, MAXRBUF);
453 LOGF_ERROR("Serial read error: %s.", errstr);
454 return false;
455 }
456
457 if (res_len > 0)
458 {
459 char hex_res[DRIVER_LEN * 3] = {0};
460 hexDump(hex_res, res, res_len);
461 LOGF_DEBUG("RES <%s>", hex_res);
462 }
463 else
464 {
465 // Remove extra \r
466 res[nbytes_read - 1] = 0;
467 LOGF_DEBUG("RES <%s>", res);
468 }
469
470 tcflush(PortFD, TCIOFLUSH);
471
472 return true;
473}
474
478void PegasusFalconV2::hexDump(char * buf, const char * data, uint32_t size)
479{
480 for (uint32_t i = 0; i < size; i++)
481 sprintf(buf + 3 * i, "%02X ", static_cast<uint8_t>(data[i]));
482
483 if (size > 0)
484 buf[3 * size - 1] = '\0';
485}
486
490std::vector<std::string> PegasusFalconV2::split(const std::string &input, const std::string &regex)
491{
492 // passing -1 as the submatch index parameter performs splitting
493 std::regex re(regex);
494 std::sregex_token_iterator
495 first{input.begin(), input.end(), re, -1},
496 last;
497 return {first, last};
498}
499
503void PegasusFalconV2::cleanupResponse(char *response)
504{
505 std::string s(response);
506 s.erase(std::remove_if(s.begin(), s.end(),
507 [](unsigned char x)
508 {
509 return std::isspace(x);
510 }), s.end());
511 snprintf(response, DRIVER_LEN, "%s", s.c_str());
512}
void setPortMatchPattern(const std::string &pattern)
setPortMatchPattern Set a regex pattern used to automatically select a serial port from the discovere...
bool isConnected() const
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.
void addAuxControls()
Add Debug, Simulation, and Configuration options to the driver.
int SetTimer(uint32_t ms)
Set a timer to call the function TimerHit after ms milliseconds.
void setState(IPState state)
void apply(const char *format,...) const ATTRIBUTE_FORMAT_PRINTF(2
void save(FILE *f) const
save Save property to configuration file.
bool isNameMatch(const char *otherName) const
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, 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)
void SetCapability(uint32_t cap)
SetRotatorCapability sets the Rotator capabilities. All capabilities must be initialized.
INDI::PropertySwitch ReverseRotatorSP
INDI::PropertyNumber GotoRotatorNP
Connection::Serial * serialConnection
Definition indirotator.h:95
virtual bool initProperties() override
Initialize properties initial state and value. The child class must implement this function.
virtual bool updateProperties() override
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
virtual bool saveConfigItems(FILE *fp) override
saveConfigItems Saves the reverse direction property in the configuration file
virtual bool updateProperties() override
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
virtual bool saveConfigItems(FILE *fp) override
saveConfigItems Saves the reverse direction property in the configuration file
virtual bool AbortRotator() override
AbortRotator Abort all motion.
virtual void TimerHit() override
Callback function to be called once SetTimer duration elapses.
virtual bool initProperties() override
Initialize properties initial state and value. The child class must implement this function.
virtual bool ReverseRotator(bool enabled) override
reverse action ("FN:0" disabled, "FN:1" enabled)
const char * getDefaultName() override
virtual IPState MoveRotator(double angle) override
move to degrees (Command "MD:nn.nn"; Response "MD:nn.nn")
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
virtual bool SyncRotator(double angle) override
SyncRotator Set current angle as the supplied angle without moving the rotator.
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
@ 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
@ 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
int tty_read(int fd, char *buf, int nbytes, int timeout, int *nbytes_read)
read buffer from terminal
Definition indicom.c:548
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
#define MAXRBUF
Definition indidriver.c:52
#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 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