Instrument Neutral Distributed Interface INDI 2.2.5
pegasus_falcon.cpp
Go to the documentation of this file.
1/*******************************************************************************
2 Copyright(c) 2020 Jasem Mutlaq. All rights reserved.
3
4 Pegasus Falcon 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 'Falcon Rotator Serial Command Language Firmware >=v.1.3 (review Sep 2020)'
26*******************************************************************************/
27
28#include "pegasus_falcon.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 PegasusFalcon.
43static std::unique_ptr<PegasusFalcon> falcon(new PegasusFalcon());
44
46{
47 setVersion(1, 0);
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 // Firmware
77 FirmwareTP[0].fill("VERSION", "Version", "NA");
78 FirmwareTP.fill(getDeviceName(), "FIRMWARE_INFO", "Firmware", MAIN_CONTROL_TAB, IP_RO, 60,
79 IPS_IDLE);
80
81 return true;
82}
83
85{
87
88 if (isConnected())
89 {
90 // Main Control
91 defineProperty(DerotateNP);
92 defineProperty(FirmwareTP);
93 defineProperty(ReloadFirmwareSP);
94
95 }
96 else
97 {
98 // Main Control
99 deleteProperty(DerotateNP);
100 deleteProperty(FirmwareTP);
101 deleteProperty(ReloadFirmwareSP);
102 }
103
104 return true;
105}
106
108{
109 return "Pegasus Falcon";
110}
111
115bool PegasusFalcon::Handshake()
116{
117 return getFirmware();
118}
119
123bool PegasusFalcon::ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
124{
125 if (dev && !strcmp(dev, getDeviceName()))
126 {
127 // De-rotation
128 if (DerotateNP.isNameMatch(name))
129 {
130 const uint32_t ms = static_cast<uint32_t>(values[0]);
131 if (setDerotation(ms))
132 {
133 DerotateNP[0].setValue(values[0]);
134 if (values[0] > 0)
135 LOGF_INFO("De-rotation is enabled and set to 1 step per %u milliseconds.", ms);
136 else
137 LOG_INFO("De-rotaiton is disabled.");
138 DerotateNP.setState(IPS_OK);
139 }
140 else
141 DerotateNP.setState(IPS_ALERT);
142 DerotateNP.apply();
143 return true;
144 }
145 // Firmware 1.4 bug:
146 // If new angle differs 0.01° the rotator sometimes reports success even though there was no movement!
147 else if (!strcmp(name, "ABS_ROTATOR_ANGLE"))
148 {
149 if (std::abs(values[0] - GotoRotatorNP[0].getValue()) <= 0.01)
150 {
153 return true;
154 }
155 }
156 }
157 return Rotator::ISNewNumber(dev, name, values, names, n);
158}
159
163bool PegasusFalcon::ISNewSwitch(const char * dev, const char * name, ISState * states, char * names[], int n)
164{
165 if (dev && !strcmp(dev, getDeviceName()))
166 {
167 // ReloadFirmware
168 if (ReloadFirmwareSP.isNameMatch(name))
169 {
170 ReloadFirmwareSP.setState(reloadFirmware() ? IPS_OK : IPS_ALERT);
171 ReloadFirmwareSP.apply();
172 LOG_INFO("Reloading firmware...");
173 return true;
174 }
175 }
176
177 return Rotator::ISNewSwitch(dev, name, states, names, n);
178}
179
184{
185 char cmd[DRIVER_LEN] = {0}, res[DRIVER_LEN] = {0};
186 snprintf(cmd, DRIVER_LEN, "MD:%.2f", angle);
187 if (sendCommand(cmd, res))
188 {
189 return (!strncmp(res, cmd, 8) ? IPS_BUSY : IPS_ALERT);
190 //Restrict length to 8 chars for correct compare
191 }
192
193 return IPS_ALERT;
194}
195
200{
201 char res[DRIVER_LEN] = {0};
202 if (sendCommand("FH", res))
203 {
204 return (!strcmp(res, "FH:1"));
205 }
206
207 return false;
208}
209
214{
215 char cmd[DRIVER_LEN] = {0}, res[DRIVER_LEN] = {0};
216 snprintf(cmd, DRIVER_LEN, "FN:%d", enabled ? 1 : 0);
217 if (sendCommand(cmd, res))
218 {
219 return (!strncmp(res, cmd, 4)); //Restrict length to 4 chars!
220 }
221
222 return false;
223}
224
229{
230 char cmd[DRIVER_LEN] = {0};
231 snprintf(cmd, DRIVER_LEN, "SD:%.2f", angle);
232 return sendCommand(cmd, nullptr);
233}
234
238bool PegasusFalcon::reloadFirmware()
239{
240 return sendCommand("FF");
241}
242
246bool PegasusFalcon::setDerotation(uint32_t ms)
247{
248 char cmd[DRIVER_LEN] = {0};
249 snprintf(cmd, DRIVER_LEN, "DR:%d", ms);
250 return sendCommand(cmd, nullptr);
251}
252
257{
259 DerotateNP.save(fp);
260 return true;
261}
262
267{
268 if (!isConnected())
269 return;
270 getStatusData();
272}
273
277bool PegasusFalcon::getFirmware()
278{
279 char res[DRIVER_LEN] = {0};
280 if (sendCommand("FV", res))
281 {
282 FirmwareTP[0].setText(res + 3);
283 return true;
284 }
285
286 return false;
287}
288
292bool PegasusFalcon::getStatusData()
293{
294 char res[DRIVER_LEN] = {0};
295 if (sendCommand("FA", res))
296 {
297 std::vector<std::string> result = split(res, ":");
298 if (result.size() != 7)
299 {
300 LOG_WARN("Received wrong number of detailed sensor data. Retrying...");
301 return false;
302 }
303
304 if (result == lastStatusData)
305 return true;
306
307 // Position
308 const double position = std::stod(result[2]);
309 // Is running?
310 const IPState motionState = std::stoi(result[3]) == 1 ? IPS_BUSY : IPS_OK;
311
312 // Update Absolute Position property if either position changes, or status changes.
313 if (std::abs(position - GotoRotatorNP[0].getValue()) > 0.01 || GotoRotatorNP.getState() != motionState)
314 {
315 GotoRotatorNP[0].setValue(position);
316 GotoRotatorNP.setState(motionState);
318 }
319
320 // TODO add this later to properties (Light?)
321 //const bool limit = std::stoi(result[4]) == 1;
322
323 const bool derotation = std::stoi(result[5]) == 1;
324 const bool wasDerotated = DerotateNP[0].getValue() > 0;
325 // TODO check if we get value from firmware
326 if (derotation != wasDerotated)
327 {
328 DerotateNP.setState(derotation ? IPS_BUSY : IPS_IDLE);
329 DerotateNP.apply();
330 }
331
332 const bool reversed = std::stoi(result[6]) == 1;
333 const bool wasReversed = ReverseRotatorSP[INDI_ENABLED].getState() == ISS_ON;
334 if (reversed != wasReversed)
335 {
339 }
340
341 lastStatusData = result;
342 return true;
343 }
344
345 return false;
346}
347
351bool PegasusFalcon::sendCommand(const char * cmd, char * res, int cmd_len, int res_len)
352{
353 int nbytes_written = 0, nbytes_read = 0, rc = -1;
354
355 tcflush(PortFD, TCIOFLUSH);
356
357 if (cmd_len > 0)
358 {
359 char hex_cmd[DRIVER_LEN * 3] = {0};
360 hexDump(hex_cmd, cmd, cmd_len);
361 LOGF_DEBUG("CMD <%s>", hex_cmd);
362 rc = tty_write(PortFD, cmd, cmd_len, &nbytes_written);
363 }
364 else
365 {
366 LOGF_DEBUG("CMD <%s>", cmd);
367
368 char formatted_command[DRIVER_LEN] = {0};
369 snprintf(formatted_command, DRIVER_LEN, "%s\n", cmd);
370 rc = tty_write_string(PortFD, formatted_command, &nbytes_written);
371 }
372
373 if (rc != TTY_OK)
374 {
375 char errstr[MAXRBUF] = {0};
376 tty_error_msg(rc, errstr, MAXRBUF);
377 LOGF_ERROR("Serial write error: %s.", errstr);
378 return false;
379 }
380
381 if (res == nullptr)
382 return true;
383
384 if (res_len > 0)
385 rc = tty_read(PortFD, res, res_len, DRIVER_TIMEOUT, &nbytes_read);
386 else
387 rc = tty_nread_section(PortFD, res, DRIVER_LEN, DRIVER_STOP_CHAR, DRIVER_TIMEOUT, &nbytes_read);
388
389 if (rc != TTY_OK)
390 {
391 char errstr[MAXRBUF] = {0};
392 tty_error_msg(rc, errstr, MAXRBUF);
393 LOGF_ERROR("Serial read error: %s.", errstr);
394 return false;
395 }
396
397 if (res_len > 0)
398 {
399 char hex_res[DRIVER_LEN * 3] = {0};
400 hexDump(hex_res, res, res_len);
401 LOGF_DEBUG("RES <%s>", hex_res);
402 }
403 else
404 {
405 // Remove extra \r
406 res[nbytes_read - 1] = 0;
407 LOGF_DEBUG("RES <%s>", res);
408 }
409
410 tcflush(PortFD, TCIOFLUSH);
411
412 return true;
413}
414
418void PegasusFalcon::hexDump(char * buf, const char * data, uint32_t size)
419{
420 for (uint32_t i = 0; i < size; i++)
421 sprintf(buf + 3 * i, "%02X ", static_cast<uint8_t>(data[i]));
422
423 if (size > 0)
424 buf[3 * size - 1] = '\0';
425}
426
430std::vector<std::string> PegasusFalcon::split(const std::string &input, const std::string &regex)
431{
432 // passing -1 as the submatch index parameter performs splitting
433 std::regex re(regex);
434 std::sregex_token_iterator
435 first{input.begin(), input.end(), re, -1},
436 last;
437 return {first, last};
438}
439
443void PegasusFalcon::cleanupResponse(char *response)
444{
445 std::string s(response);
446 s.erase(std::remove_if(s.begin(), s.end(),
447 [](unsigned char x)
448 {
449 return std::isspace(x);
450 }), s.end());
451 snprintf(response, DRIVER_LEN, "%s", s.c_str());
452}
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 ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
virtual bool initProperties() override
Initialize properties initial state and value. The child class must implement this function.
virtual IPState MoveRotator(double angle) override
move to degrees (Command "MD:nn.nn"; Response "MD:nn.nn")
virtual bool updateProperties() override
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
virtual void TimerHit() override
Callback function to be called once SetTimer duration elapses.
virtual bool AbortRotator() override
AbortRotator Abort all motion.
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
virtual bool ReverseRotator(bool enabled) override
reverse action ("FN:0" disabled, "FN:1" enabled)
virtual bool SyncRotator(double angle) override
SyncRotator Set current angle as the supplied angle without moving the rotator.
const char * getDefaultName() override
virtual bool saveConfigItems(FILE *fp) override
saveConfigItems Saves the reverse direction property in the configuration file
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