Instrument Neutral Distributed Interface INDI  2.0.2
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"
30 
31 #include <cmath>
32 #include <memory>
33 #include <regex>
34 #include <termios.h>
35 #include <cstring>
36 #include <sys/ioctl.h>
37 #include <chrono>
38 #include <math.h>
39 #include <iomanip>
40 
41 // We declare an auto pointer to PegasusFalcon.
42 static std::unique_ptr<PegasusFalcon> falcon(new PegasusFalcon());
43 
45 {
46  setVersion(1, 0);
47  lastStatusData.reserve(7);
48 }
49 
51 {
53 
57 
59 
63  // Reload Firmware
64  IUFillSwitch(&ReloadFirmwareS[0], "RELOAD", "Reload", ISS_OFF);
65  IUFillSwitchVector(&ReloadFirmwareSP, ReloadFirmwareS, 1, getDeviceName(), "RELOAD_FIRMWARE", "Firmware", MAIN_CONTROL_TAB,
67  60, IPS_IDLE);
68 
69  // Derotate
70  IUFillNumber(&DerotateN[0], "INTERVAL", "Interval (ms)", "%.f", 0, 10000, 1000, 0);
71  IUFillNumberVector(&DerotateNP, DerotateN, 1, getDeviceName(), "ROTATOR_DEROTATE", "Derotation", MAIN_CONTROL_TAB, IP_RW,
72  60, IPS_IDLE);
73 
74  // Firmware
75  IUFillText(&FirmwareT[0], "VERSION", "Version", "NA");
76  IUFillTextVector(&FirmwareTP, FirmwareT, 1, getDeviceName(), "FIRMWARE_INFO", "Firmware", MAIN_CONTROL_TAB, IP_RO, 60,
77  IPS_IDLE);
78 
79  return true;
80 }
81 
83 {
85 
86  if (isConnected())
87  {
88  // Main Control
89  defineProperty(&DerotateNP);
90  defineProperty(&FirmwareTP);
91  defineProperty(&ReloadFirmwareSP);
92 
93  }
94  else
95  {
96  // Main Control
97  deleteProperty(DerotateNP.name);
98  deleteProperty(FirmwareTP.name);
99  deleteProperty(ReloadFirmwareSP.name);
100  }
101 
102  return true;
103 }
104 
106 {
107  return "Pegasus Falcon";
108 }
109 
113 bool PegasusFalcon::Handshake()
114 {
115  return getFirmware();
116 }
117 
121 bool PegasusFalcon::ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
122 {
123  if (dev && !strcmp(dev, getDeviceName()))
124  {
125  // De-rotation
126  if (!strcmp(name, DerotateNP.name))
127  {
128  const uint32_t ms = static_cast<uint32_t>(values[0]);
129  if (setDerotation(ms))
130  {
131  DerotateN[0].value = values[0];
132  if (values[0] > 0)
133  LOGF_INFO("De-rotation is enabled and set to 1 step per %u milliseconds.", ms);
134  else
135  LOG_INFO("De-rotaiton is disabled.");
136  }
137  else
138  DerotateNP.s = IPS_ALERT;
139  IDSetNumber(&DerotateNP, nullptr);
140  return true;
141  }
142  // Firmware 1.4 bug:
143  // If new angle differs 0.01° the rotator sometimes reports success even though there was no movement!
144  if (!strcmp(name, "ABS_ROTATOR_ANGLE"))
145  if (std::abs(values[0] - GotoRotatorN[0].value) <= 0.01)
146  {
148  IDSetNumber(&GotoRotatorNP, nullptr);
149  return true;
150  }
151  }
152  return Rotator::ISNewNumber(dev, name, values, names, n);
153 }
154 
158 bool PegasusFalcon::ISNewSwitch(const char * dev, const char * name, ISState * states, char * names[], int n)
159 {
160  if (dev && !strcmp(dev, getDeviceName()))
161  {
162  // ReloadFirmware
163  if (!strcmp(name, ReloadFirmwareSP.name))
164  {
165  ReloadFirmwareSP.s = reloadFirmware() ? IPS_OK : IPS_ALERT;
166  IDSetSwitch(&ReloadFirmwareSP, nullptr);
167  LOG_INFO("Reloading firmware...");
168  return true;
169  }
170  }
171 
172  return Rotator::ISNewSwitch(dev, name, states, names, n);
173 }
174 
179 {
180  char cmd[DRIVER_LEN] = {0}, res[DRIVER_LEN] = {0};
181  snprintf(cmd, DRIVER_LEN, "MD:%.2f", angle);
182  if (sendCommand(cmd, res))
183  {
184  return (!strncmp(res, cmd, 8) ? IPS_BUSY : IPS_ALERT);
185  //Restrict length to 8 chars for correct compare
186  }
187 
188  return IPS_ALERT;
189 }
190 
195 {
196  char res[DRIVER_LEN] = {0};
197  if (sendCommand("FH", res))
198  {
199  return (!strcmp(res, "FH:1"));
200  }
201 
202  return false;
203 }
204 
209 {
210  char cmd[DRIVER_LEN] = {0}, res[DRIVER_LEN] = {0};
211  snprintf(cmd, DRIVER_LEN, "FN:%d", enabled ? 1 : 0);
212  if (sendCommand(cmd, res))
213  {
214  return (!strncmp(res, cmd, 4)); //Restrict length to 4 chars!
215  }
216 
217  return false;
218 }
219 
223 bool PegasusFalcon::SyncRotator(double angle)
224 {
225  char cmd[DRIVER_LEN] = {0};
226  snprintf(cmd, DRIVER_LEN, "SD:%.2f", angle);
227  return sendCommand(cmd, nullptr);
228 }
229 
233 bool PegasusFalcon::reloadFirmware()
234 {
235  return sendCommand("FF");
236 }
237 
241 bool PegasusFalcon::setDerotation(uint32_t ms)
242 {
243  char cmd[DRIVER_LEN] = {0};
244  snprintf(cmd, DRIVER_LEN, "DR:%d", ms);
245  return sendCommand(cmd, nullptr);
246 }
247 
252 {
254  IUSaveConfigNumber(fp, &DerotateNP);
255  return true;
256 }
257 
262 {
263  if (!isConnected())
264  return;
265  getStatusData();
267 }
268 
272 bool PegasusFalcon::getFirmware()
273 {
274  char res[DRIVER_LEN] = {0};
275  if (sendCommand("FV", res))
276  {
277  IUSaveText(&FirmwareT[0], res + 3);
278  return true;
279  }
280 
281  return false;
282 }
283 
287 bool PegasusFalcon::getStatusData()
288 {
289  char res[DRIVER_LEN] = {0};
290  if (sendCommand("FA", res))
291  {
292  std::vector<std::string> result = split(res, ":");
293  if (result.size() != 7)
294  {
295  LOG_WARN("Received wrong number of detailed sensor data. Retrying...");
296  return false;
297  }
298 
299  if (result == lastStatusData)
300  return true;
301 
302  // Position
303  const double position = std::stod(result[2]);
304  // Is running?
305  const IPState motionState = std::stoi(result[3]) == 1 ? IPS_BUSY : IPS_OK;
306 
307  // Update Absolute Position property if either position changes, or status changes.
308  if (std::abs(position - GotoRotatorN[0].value) > 0.01 || GotoRotatorNP.s != motionState)
309  {
310  GotoRotatorN[0].value = position;
311  GotoRotatorNP.s = motionState;
312  IDSetNumber(&GotoRotatorNP, nullptr);
313  }
314 
315  // TODO add this later to properties (Light?)
316  //const bool limit = std::stoi(result[4]) == 1;
317 
318  const bool derotation = std::stoi(result[5]) == 1;
319  const bool wasDerotated = DerotateN[0].value > 0;
320  // TODO check if we get value from firmware
321  if (derotation != wasDerotated)
322  {
323  DerotateNP.s = derotation ? IPS_BUSY : IPS_IDLE;;
324  IDSetNumber(&DerotateNP, nullptr);
325  }
326 
327  const bool reversed = std::stoi(result[6]) == 1;
328  const bool wasReversed = ReverseRotatorS[INDI_ENABLED].s == ISS_ON;
329  if (reversed != wasReversed)
330  {
331  ReverseRotatorS[INDI_ENABLED].s = reversed ? ISS_ON : ISS_OFF;
332  ReverseRotatorS[INDI_DISABLED].s = reversed ? ISS_OFF : ISS_ON;
333  IDSetSwitch(&ReverseRotatorSP, nullptr);
334  }
335 
336  lastStatusData = result;
337  return true;
338  }
339 
340  return false;
341 }
342 
346 bool PegasusFalcon::sendCommand(const char * cmd, char * res, int cmd_len, int res_len)
347 {
348  int nbytes_written = 0, nbytes_read = 0, rc = -1;
349 
350  tcflush(PortFD, TCIOFLUSH);
351 
352  if (cmd_len > 0)
353  {
354  char hex_cmd[DRIVER_LEN * 3] = {0};
355  hexDump(hex_cmd, cmd, cmd_len);
356  LOGF_DEBUG("CMD <%s>", hex_cmd);
357  rc = tty_write(PortFD, cmd, cmd_len, &nbytes_written);
358  }
359  else
360  {
361  LOGF_DEBUG("CMD <%s>", cmd);
362 
363  char formatted_command[DRIVER_LEN] = {0};
364  snprintf(formatted_command, DRIVER_LEN, "%s\n", cmd);
365  rc = tty_write_string(PortFD, formatted_command, &nbytes_written);
366  }
367 
368  if (rc != TTY_OK)
369  {
370  char errstr[MAXRBUF] = {0};
371  tty_error_msg(rc, errstr, MAXRBUF);
372  LOGF_ERROR("Serial write error: %s.", errstr);
373  return false;
374  }
375 
376  if (res == nullptr)
377  return true;
378 
379  if (res_len > 0)
380  rc = tty_read(PortFD, res, res_len, DRIVER_TIMEOUT, &nbytes_read);
381  else
382  rc = tty_nread_section(PortFD, res, DRIVER_LEN, DRIVER_STOP_CHAR, DRIVER_TIMEOUT, &nbytes_read);
383 
384  if (rc != TTY_OK)
385  {
386  char errstr[MAXRBUF] = {0};
387  tty_error_msg(rc, errstr, MAXRBUF);
388  LOGF_ERROR("Serial read error: %s.", errstr);
389  return false;
390  }
391 
392  if (res_len > 0)
393  {
394  char hex_res[DRIVER_LEN * 3] = {0};
395  hexDump(hex_res, res, res_len);
396  LOGF_DEBUG("RES <%s>", hex_res);
397  }
398  else
399  {
400  // Remove extra \r
401  res[nbytes_read - 1] = 0;
402  LOGF_DEBUG("RES <%s>", res);
403  }
404 
405  tcflush(PortFD, TCIOFLUSH);
406 
407  return true;
408 }
409 
413 void PegasusFalcon::hexDump(char * buf, const char * data, uint32_t size)
414 {
415  for (uint32_t i = 0; i < size; i++)
416  sprintf(buf + 3 * i, "%02X ", static_cast<uint8_t>(data[i]));
417 
418  if (size > 0)
419  buf[3 * size - 1] = '\0';
420 }
421 
425 std::vector<std::string> PegasusFalcon::split(const std::string &input, const std::string &regex)
426 {
427  // passing -1 as the submatch index parameter performs splitting
428  std::regex re(regex);
429  std::sregex_token_iterator
430  first{input.begin(), input.end(), re, -1},
431  last;
432  return {first, last};
433 }
434 
438 void PegasusFalcon::cleanupResponse(char *response)
439 {
440  std::string s(response);
441  s.erase(std::remove_if(s.begin(), s.end(),
442  [](unsigned char x)
443  {
444  return std::isspace(x);
445  }), s.end());
446  strncpy(response, s.c_str(), DRIVER_LEN);
447 }
bool isConnected() const
Definition: basedevice.cpp:520
const char * getDeviceName() const
Definition: basedevice.cpp:821
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.
INumberVectorProperty GotoRotatorNP
void SetCapability(uint32_t cap)
SetRotatorCapability sets the Rotator capabilities. All capabilities must be initialized.
ISwitchVectorProperty ReverseRotatorSP
virtual bool initProperties() override
Initilize properties initial state and value. The child class must implement this function.
Definition: indirotator.cpp:37
virtual bool updateProperties() override
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
Definition: indirotator.cpp:94
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
Initilize properties initial state and value. The child class must implement this function.
virtual IPState MoveRotator(double angle) override
move to degrees (Commmand "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.
void ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
Update the value of an existing switch vector property.
void ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
ISState
Switch state.
Definition: indiapi.h:150
@ ISS_OFF
Definition: indiapi.h:151
@ ISS_ON
Definition: indiapi.h:152
@ 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
int tty_write(int fd, const char *buf, int nbytes, int *nbytes_written)
Writes a buffer to fd.
Definition: indicom.c:424
int tty_read(int fd, char *buf, int nbytes, int timeout, int *nbytes_read)
read buffer from terminal
Definition: indicom.c:482
int tty_write_string(int fd, const char *buf, int *nbytes_written)
Writes a null terminated string to fd.
Definition: indicom.c:474
void tty_error_msg(int err_code, char *err_msg, int err_msg_len)
Retrieve the tty error message.
Definition: indicom.c:1167
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:666
Implementations for common driver routines.
@ TTY_OK
Definition: indicom.h:150
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
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 IUSaveConfigNumber(FILE *fp, const INumberVectorProperty *nvp)
Add a number vector property value to the configuration file.
Definition: indidevapi.c:15
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
void IDSetNumber(const INumberVectorProperty *nvp, const char *fmt,...)
Definition: indidriver.c:1211
void IDSetSwitch(const ISwitchVectorProperty *svp, const char *fmt,...)
Definition: indidriver.c:1231
#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 MAXRBUF
Definition: indiserver.cpp:102
__u8 cmd[4]
Definition: pwc-ioctl.h:2
char name[MAXINDINAME]
Definition: indiapi.h:323
char name[MAXINDINAME]
Definition: indiapi.h:371
char name[MAXINDINAME]
Definition: indiapi.h:250