Instrument Neutral Distributed Interface INDI  2.0.2
planewave_efa.cpp
Go to the documentation of this file.
1 /*
2  PlaneWave EFA Protocol
3 
4  Hendrick Focuser
5 
6  Copyright (C) 2020 Jasem Mutlaq (mutlaqja@ikarustech.com)
7 
8  This library is free software; you can redistribute it and/or
9  modify it under the terms of the GNU Lesser General Public
10  License as published by the Free Software Foundation; either
11  version 2.1 of the License, or (at your option) any later version.
12 
13  This library is distributed in the hope that it will be useful,
14  but WITHOUT ANY WARRANTY; without even the implied warranty of
15  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16  Lesser General Public License for more details.
17 
18  You should have received a copy of the GNU Lesser General Public
19  License along with this library; if not, write to the Free Software
20  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21 
22 */
23 
24 #include "planewave_efa.h"
25 #include "indicom.h"
27 
28 #include <cmath>
29 #include <memory>
30 #include <cstring>
31 #include <termios.h>
32 #include <unistd.h>
33 #include <regex>
34 #include <sys/ioctl.h>
35 
36 static std::unique_ptr<EFA> steelDrive(new EFA());
37 
42 {
43  setVersion(1, 2);
44 
45  // Focuser Capabilities
51 }
52 
54 {
56 
57  // Focuser Information
58  IUFillText(&InfoT[INFO_VERSION], "INFO_VERSION", "Version", "NA");
59  IUFillTextVector(&InfoTP, InfoT, 1, getDeviceName(), "INFO", "Info", MAIN_CONTROL_TAB, IP_RO, 60, IPS_IDLE);
60 
61  // Focuser temperature
62  IUFillNumber(&TemperatureN[TEMPERATURE_PRIMARY], "TEMPERATURE_PRIMARY", "Primary (c)", "%.2f", -50, 70., 0., 0.);
63  IUFillNumber(&TemperatureN[TEMPERATURE_AMBIENT], "TEMPERATURE_AMBIENT", "Ambient (c)", "%.2f", -50, 70., 0., 0.);
64  IUFillNumberVector(&TemperatureNP, TemperatureN, 2, getDeviceName(), "FOCUS_TEMPERATURE", "Temperature",
66 
67  // Fan Control
68  IUFillSwitch(&FanStateS[FAN_ON], "FAN_ON", "On", ISS_OFF);
69  IUFillSwitch(&FanStateS[FAN_OFF], "FAN_OFF", "Off", ISS_ON);
70  IUFillSwitchVector(&FanStateSP, FanStateS, 2, getDeviceName(), "FOCUS_FAN", "Fans", FAN_TAB, IP_RW, ISR_1OFMANY, 0,
71  IPS_IDLE);
72 
73  // Fan Control Mode
74  IUFillSwitch(&FanControlS[FAN_MANUAL], "FAN_MANUAL", "Manual", ISS_ON);
75  IUFillSwitch(&FanControlS[FAN_AUTOMATIC_ABSOLUTE], "FAN_AUTOMATIC_ABSOLUTE", "Auto. Absolute", ISS_OFF);
76  IUFillSwitch(&FanControlS[FAN_AUTOMATIC_RELATIVE], "FAN_AUTOMATIC_RELATIVE", "Auto. Relative", ISS_OFF);
77  IUFillSwitchVector(&FanControlSP, FanControlS, 3, getDeviceName(), "FOCUS_FAN_CONTROL", "Control Mode", FAN_TAB, IP_RW,
78  ISR_1OFMANY, 0, IPS_IDLE);
79 
80  // Fan Control Parameters
81  IUFillNumber(&FanControlN[FAN_MAX_ABSOLUTE], "FAN_MAX_ABSOLUTE", "Max Primary (c)", "%.2f", 0, 50., 5., 25.);
82  IUFillNumber(&FanControlN[FAN_MAX_RELATIVE], "FAN_MAX_RELATIVE", "Max Relative (c)", "%.2f", 0., 30., 1., 2.5);
83  IUFillNumber(&FanControlN[FAN_DEADZONE], "FAN_DEADZONE", "Deadzone (c)", "%.2f", 0.1, 10, 0.5, 0.5);
84  IUFillNumberVector(&FanControlNP, FanControlN, 3, getDeviceName(), "FOCUS_FAN_PARAMS", "Control Params",
85  FAN_TAB, IP_RW, 0, IPS_IDLE);
86 
87  // Fan Off on Disconnect
88  IUFillSwitch(&FanDisconnectS[FAN_OFF_ON_DISCONNECT], "FAN_OFF_ON_DISCONNECT", "Switch Off", ISS_ON);
89  IUFillSwitchVector(&FanDisconnectSP, FanDisconnectS, 1, getDeviceName(), "FOCUS_FAN_DISCONNECT", "On Disconnect", FAN_TAB,
91 
92  // Calibration Control
93  IUFillSwitch(&CalibrationStateS[CALIBRATION_ON], "CALIBRATION_ON", "Calibrated", ISS_OFF);
94  IUFillSwitch(&CalibrationStateS[CALIBRATION_OFF], "CALIBRATION_OFF", "Not Calibrated", ISS_ON);
95  IUFillSwitchVector(&CalibrationStateSP, CalibrationStateS, 2, getDeviceName(), "FOCUS_CALIBRATION", "Calibration",
97 
98  // Setup limits
99  FocusMaxPosN[0].value = 1e7;
100  FocusMaxPosN[0].max = 1e7;
101  FocusMaxPosN[0].step = FocusMaxPosN[0].max / 50;
102 
103  FocusAbsPosN[0].max = 1e7;
104  FocusAbsPosN[0].step = FocusAbsPosN[0].max / 50;
105 
106  FocusSyncN[0].max = 1e7;
107  FocusSyncN[0].step = FocusSyncN[0].max / 50;
108 
109  FocusRelPosN[0].max = FocusAbsPosN[0].max / 2;
110  FocusRelPosN[0].step = FocusRelPosN[0].max / 50;
111 
112  addAuxControls();
115 
116  // Lower RTS so serial port not monopolized and hand controller can work
117  int bits = TIOCM_RTS;
118  (void) ioctl(PortFD, TIOCMBIC, &bits);
119 
120  return true;
121 }
122 
127 {
129 
130  if (isConnected())
131  {
132  getStartupValues();
133 
134  defineProperty(&InfoTP);
135  defineProperty(&CalibrationStateSP);
136 
137  // Fan
138  defineProperty(&FanStateSP);
139  defineProperty(&FanControlSP);
140  loadConfig(true, FanControlSP.name);
141  defineProperty(&FanDisconnectSP);
142 
143  defineProperty(&TemperatureNP);
144  }
145  else
146  {
147  deleteProperty(InfoTP.name);
148  deleteProperty(CalibrationStateSP.name);
149 
150  deleteProperty(FanStateSP.name);
151  deleteProperty(FanControlSP.name);
152  deleteProperty(FanControlNP.name);
153  deleteProperty(FanDisconnectSP.name);
154 
155  deleteProperty(TemperatureNP.name);
156  }
157 
158  return true;
159 }
160 
165 {
166  if (FanDisconnectS[FAN_OFF_ON_DISCONNECT].s == ISS_ON)
167  setFanEnabled(false);
168 
169  return INDI::Focuser::Disconnect();
170 }
171 
176 {
177  std::string version;
178 
179  uint8_t cmd[DRIVER_LEN] = {0}, res[DRIVER_LEN] = {0}, len = 6;
180 
181  cmd[0] = DRIVER_SOM;
182  cmd[1] = 0x03;
183  cmd[2] = DEVICE_PC;
184  cmd[3] = DEVICE_FOC;
185  cmd[4] = GET_VERSION;
186  cmd[5] = calculateCheckSum(cmd, len);
187 
188  if (!validateLengths(cmd, len))
189  return false;
190 
191  if (!sendCommand(cmd, res, len, DRIVER_LEN))
192  return false;
193 
194  version = std::to_string(res[5]) + "." + std::to_string(res[6]);
195  IUSaveText(&InfoT[INFO_VERSION], version.c_str());
196 
197  LOGF_INFO("Detected version %s", version.c_str());
198 
199  return true;
200 }
201 
205 const char *EFA::getDefaultName()
206 {
207  return "PlaneWave EFA";
208 }
209 
213 bool EFA::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
214 {
215  if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
216  {
217  // Calibration State
218  if (!strcmp(CalibrationStateSP.name, name))
219  {
220  bool enabled = strcmp(CalibrationStateS[CALIBRATION_ON].name, IUFindOnSwitchName(states, names, n)) == 0;
221  if (setCalibrationEnabled(enabled))
222  {
223  IUUpdateSwitch(&CalibrationStateSP, states, names, n);
224  CalibrationStateSP.s = IPS_OK;
225  }
226  else
227  {
228  CalibrationStateSP.s = IPS_ALERT;
229  }
230 
231  IDSetSwitch(&CalibrationStateSP, nullptr);
232  return true;
233  }
234  // Fan State
235  else if (!strcmp(FanStateSP.name, name))
236  {
237  if (FanControlS[FAN_MANUAL].s == ISS_OFF)
238  {
239  FanStateSP.s = IPS_IDLE;
240  LOG_WARN("Cannot control fan while manual control is turned off.");
241  IDSetSwitch(&FanControlSP, nullptr);
242  return true;
243  }
244 
245  bool enabled = strcmp(FanStateS[FAN_ON].name, IUFindOnSwitchName(states, names, n)) == 0;
246  if (setFanEnabled(enabled))
247  {
248  IUUpdateSwitch(&FanStateSP, states, names, n);
249  FanStateSP.s = enabled ? IPS_OK : IPS_IDLE;
250  }
251  else
252  {
253  FanStateSP.s = IPS_ALERT;
254  }
255 
256  IDSetSwitch(&FanStateSP, nullptr);
257  return true;
258  }
259  // Fan Control
260  else if (!strcmp(FanControlSP.name, name))
261  {
262  IUUpdateSwitch(&FanControlSP, states, names, n);
263  if (FanControlS[FAN_MANUAL].s == ISS_ON)
264  {
265  deleteProperty(FanControlNP.name);
266  LOG_INFO("Fan is now controlled manually.");
267  }
268  else
269  {
270  LOG_INFO("Fan is now controlled automatically per the control parameters.");
271  defineProperty(&FanControlNP);
272  }
273 
274  FanControlSP.s = IPS_OK;
275  IDSetSwitch(&FanControlSP, nullptr);
276  return true;
277  }
278  // Fan Disconnect
279  else if (!strcmp(FanDisconnectSP.name, name))
280  {
281  IUUpdateSwitch(&FanDisconnectSP, states, names, n);
282 
283  if (FanDisconnectS[FAN_OFF_ON_DISCONNECT].s == ISS_ON)
284  LOG_INFO("Fan shall be turned off upon device disconnection.");
285  else
286  LOG_INFO("Fan shall left as-is upon device disconnection.");
287 
288  FanDisconnectSP.s = IPS_OK;
289  IDSetSwitch(&FanDisconnectSP, nullptr);
290  return true;
291  }
292  }
293 
294  return INDI::Focuser::ISNewSwitch(dev, name, states, names, n);
295 }
296 
300 bool EFA::ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
301 {
302  if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
303  {
304  // Fan Params
305  if (!strcmp(FanControlNP.name, name))
306  {
307  IUUpdateNumber(&FanControlNP, values, names, n);
308  FanControlNP.s = IPS_OK;
309  IDSetNumber(&FanControlNP, nullptr);
310  return true;
311  }
312  }
313 
314  return INDI::Focuser::ISNewNumber(dev, name, values, names, n);
315 }
316 
320 bool EFA::SyncFocuser(uint32_t ticks)
321 {
322  uint8_t cmd[DRIVER_LEN] = {0}, res[DRIVER_LEN] = {0}, len = 9;
323 
324  cmd[0] = DRIVER_SOM;
325  cmd[1] = 0x06;
326  cmd[2] = DEVICE_PC;
327  cmd[3] = DEVICE_FOC;
328  cmd[4] = MTR_OFFSET_CNT;
329  cmd[5] = (ticks >> 16) & 0xFF;
330  cmd[6] = (ticks >> 8) & 0xFF;
331  cmd[7] = (ticks >> 0) & 0xFF;
332  cmd[8] = calculateCheckSum(cmd, len);
333 
334  if (!validateLengths(cmd, len))
335  return false;
336 
337  if (!sendCommand(cmd, res, len, DRIVER_LEN))
338  return false;
339 
340  return (res[5] == 1);
341 }
342 
346 IPState EFA::MoveAbsFocuser(uint32_t targetTicks)
347 {
348  uint8_t cmd[DRIVER_LEN] = {0}, res[DRIVER_LEN] = {0}, len = 9;
349 
350  cmd[0] = DRIVER_SOM;
351  cmd[1] = 0x06;
352  cmd[2] = DEVICE_PC;
353  cmd[3] = DEVICE_FOC;
354  cmd[4] = MTR_GOTO_POS2;
355  cmd[5] = (targetTicks >> 16) & 0xFF;
356  cmd[6] = (targetTicks >> 8) & 0xFF;
357  cmd[7] = (targetTicks >> 0) & 0xFF;
358  cmd[8] = calculateCheckSum(cmd, len);
359 
360  if (!validateLengths(cmd, len))
361  return IPS_ALERT;
362 
363  if (!sendCommand(cmd, res, len, DRIVER_LEN))
364  return IPS_ALERT;
365 
366  return (res[5] == 1) ? IPS_BUSY : IPS_ALERT;
367 }
368 
373 {
374  int direction = (dir == FOCUS_INWARD) ? -1 : 1;
375  int reversed = (FocusReverseS[INDI_ENABLED].s == ISS_ON) ? -1 : 1;
376  int relative = static_cast<int>(ticks);
377  int targetAbsPosition = FocusAbsPosN[0].value + (relative * direction * reversed);
378 
379  targetAbsPosition = std::min(static_cast<uint32_t>(FocusAbsPosN[0].max),
380  static_cast<uint32_t>(std::max(static_cast<int>(FocusAbsPosN[0].min), targetAbsPosition)));
381 
382  return MoveAbsFocuser(targetAbsPosition);
383 }
384 
389 {
390  if (!isConnected())
391  return;
392 
393  IN_TIMER = true;
394 
395  readPosition();
396 
397  if (readTemperature())
398  {
399  // Send temperature is above threshold
400  bool aboveThreshold = false;
401  for (int i = 0; i < TemperatureNP.nnp; i++)
402  {
403  if (std::fabs(TemperatureN[i].value - m_LastTemperature[i]) > TEMPERATURE_THRESHOLD)
404  {
405  aboveThreshold = true;
406  m_LastTemperature[i] = TemperatureN[i].value;
407  }
408  }
409 
410  if (aboveThreshold)
411  IDSetNumber(&TemperatureNP, nullptr);
412 
413 
414  if (FanControlS[FAN_MANUAL].s == ISS_OFF)
415  {
416  bool turnOn = false, turnOff = false;
417  const bool isFanOn = FanStateS[FAN_ON].s == ISS_ON;
418 
419  // Check if we need to do automatic regulation of fan
420  if (FanControlS[FAN_AUTOMATIC_ABSOLUTE].s == ISS_ON)
421  {
422  // Adjust delta for deadzone
423  double min_delta = FanControlN[FAN_MAX_ABSOLUTE].value - FanControlN[FAN_DEADZONE].value;
424  double max_delta = FanControlN[FAN_MAX_ABSOLUTE].value + FanControlN[FAN_DEADZONE].value;
425 
426  turnOn = TemperatureN[TEMPERATURE_PRIMARY].value > max_delta;
427  turnOff = TemperatureN[TEMPERATURE_PRIMARY].value < min_delta;
428  }
429  else if (FanControlS[FAN_AUTOMATIC_RELATIVE].s == ISS_ON)
430  {
431  // Temperature delta
432  double tDiff = TemperatureN[TEMPERATURE_PRIMARY].value - TemperatureN[TEMPERATURE_AMBIENT].value;
433  // Adjust delta for deadzone
434  double min_delta = FanControlN[FAN_MAX_RELATIVE].value - FanControlN[FAN_DEADZONE].value;
435  double max_delta = FanControlN[FAN_MAX_RELATIVE].value + FanControlN[FAN_DEADZONE].value;
436 
437  // Check if we need to turn off/on fan
438  turnOn = tDiff > max_delta;
439  turnOff = tDiff < min_delta;
440  }
441 
442  if (isFanOn && turnOff)
443  {
444  setFanEnabled(false);
445  FanStateS[FAN_ON].s = ISS_OFF;
446  FanStateS[FAN_OFF].s = ISS_ON;
447  FanStateSP.s = IPS_IDLE;
448  IDSetSwitch(&FanStateSP, nullptr);
449  }
450  else if (!isFanOn && turnOn)
451  {
452  setFanEnabled(true);
453  FanStateS[FAN_ON].s = ISS_ON;
454  FanStateS[FAN_OFF].s = ISS_OFF;
455  FanStateSP.s = IPS_OK;
456  IDSetSwitch(&FanStateSP, nullptr);
457  }
458  }
459  }
460 
462  {
463  if (isGOTOComplete())
464  {
467  IDSetNumber(&FocusAbsPosNP, nullptr);
468  IDSetNumber(&FocusRelPosNP, nullptr);
469  LOG_INFO("Focuser reached requested position.");
470  }
471  else
472  IDSetNumber(&FocusAbsPosNP, nullptr);
473  }
474  else if (std::fabs(FocusAbsPosN[0].value - m_LastPosition) > 0)
475  {
476  m_LastPosition = FocusAbsPosN[0].value;
477  IDSetNumber(&FocusAbsPosNP, nullptr);
478  }
479 
480  IN_TIMER = false;
481 
483 }
484 
485 
490 {
491  return false;
492 }
493 
497 bool EFA::SetFocuserMaxPosition(uint32_t ticks)
498 {
499  uint8_t cmd[DRIVER_LEN] = {0}, res[DRIVER_LEN] = {0}, len = 9;
500 
501  cmd[0] = DRIVER_SOM;
502  cmd[1] = 0x06;
503  cmd[2] = DEVICE_PC;
504  cmd[3] = DEVICE_FOC;
505  cmd[4] = MTR_SLEWLIMITMAX;
506  cmd[5] = (ticks >> 16) & 0xFF;
507  cmd[6] = (ticks >> 8) & 0xFF;
508  cmd[7] = (ticks >> 0) & 0xFF;
509  cmd[8] = calculateCheckSum(cmd, len);
510 
511  if (!validateLengths(cmd, len))
512  return false;
513 
514  if (!sendCommand(cmd, res, len, DRIVER_LEN))
515  return false;
516 
517  return (res[5] == 1);
518 }
519 
523 bool EFA::ReverseFocuser(bool enabled)
524 {
525  INDI_UNUSED(enabled);
526  return true;
527 }
528 
532 bool EFA::saveConfigItems(FILE * fp)
533 {
535 
536  IUSaveConfigSwitch(fp, &FanControlSP);
537  if (FanControlNP.s == IPS_OK)
538  IUSaveConfigNumber(fp, &FanControlNP);
539  IUSaveConfigSwitch(fp, &FanDisconnectSP);
540  return true;
541 }
542 
546 void EFA::getStartupValues()
547 {
548  readPosition();
549  readCalibrationState();
550  readFanState();
551  readTemperature();
552 
553  if (readMaxSlewLimit())
554  {
555  FocusAbsPosN[0].max = FocusMaxPosN[0].max;
556  FocusAbsPosN[0].step = FocusAbsPosN[0].max / 50;
557 
558  FocusRelPosN[0].value = FocusAbsPosN[0].max / 50;
559  FocusRelPosN[0].max = FocusAbsPosN[0].max / 2;
560  FocusRelPosN[0].step = FocusRelPosN[0].max / 50;
561 
562  FocusMaxPosN[0].value = FocusMaxPosN[0].max;
563  FocusMaxPosN[0].step = FocusMaxPosN[0].max / 50;
564 
568  }
569 }
570 
574 inline int EFA::readByte(int fd, uint8_t *buf, int timeout, int *nbytes_read)
575 {
576  return tty_read(fd, (char *)buf, 1, timeout, nbytes_read);
577 }
578 
582 inline int EFA::readBytes(int fd, uint8_t *buf, int nbytes, int timeout, int *nbytes_read)
583 {
584  return tty_read(fd, (char *)buf, nbytes, timeout, nbytes_read);
585 }
586 
590 inline int EFA::writeBytes(int fd, const uint8_t *buf, int nbytes, int *nbytes_written)
591 {
592  return tty_write(fd, (const char *)buf, nbytes, nbytes_written);
593 }
594 
598 int EFA::readPacket(int fd, uint8_t *buf, int nbytes, int timeout, int *nbytes_read)
599 {
600  int len = 0, rc = 0, read_bytes = *nbytes_read = 0;
601 
602  if (nbytes < 6) // smallest packet is 6 bytes
603  {
604  LOGF_ERROR("Read needs at least 6 bytes; exceeds supplied buffer size (%d)", nbytes);
605  return TTY_READ_ERROR;
606  }
607 
608  for (int i = 0; i < 10; i++)
609  {
610  // look for SOM byte
611  rc = readByte(fd, &buf[0], timeout, &read_bytes);
612  if (rc == TTY_OK && read_bytes == 1)
613  {
614  if (buf[0] == DRIVER_SOM)
615  {
616  break;
617  }
618  else
619  {
620  LOGF_DEBUG("Looking for SOM (%02X); found %d byte (%02X)", DRIVER_SOM, read_bytes, buf[0]);
621  }
622  }
623  else
624  {
625  char errstr[MAXRBUF] = {0};
626  tty_error_msg(rc, errstr, MAXRBUF);
627  LOGF_DEBUG("Looking for SOM (%02X); found %s", DRIVER_SOM, errstr);
628  }
629  }
630  if (rc != TTY_OK || read_bytes != 1 || buf[0] != DRIVER_SOM)
631  {
632  LOGF_DEBUG("%s byte not encountered", "SOM");
633  if (rc == TTY_OK)
634  return TTY_TIME_OUT;
635  return rc;
636  }
637 
638  if ((rc = readByte(fd, &buf[1], timeout, &read_bytes)) != TTY_OK)
639  {
640  LOGF_DEBUG("%s byte not encountered", "LEN");
641  return rc;
642  }
643  len = buf[1];
644 
645  // read source
646  if ((rc = readByte(fd, &buf[2], timeout, &read_bytes)) != TTY_OK)
647  {
648  LOGF_DEBUG("%s byte not encountered", "SRC");
649  return rc;
650  }
651  // read receiver
652  if ((rc = readByte(fd, &buf[3], timeout, &read_bytes)) != TTY_OK)
653  {
654  LOGF_DEBUG("%s byte not encountered", "RCV");
655  return rc;
656  }
657  // read command
658  if ((rc = readByte(fd, &buf[4], timeout, &read_bytes)) != TTY_OK)
659  {
660  LOGF_DEBUG("%s byte not encountered", "CMD");
661  return rc;
662  }
663 
664  if ((len + 3) > nbytes)
665  {
666  LOGF_ERROR("Read (%d) will exceed supplied buffer size (%d) for command %02X", (len + 3), nbytes, buf[4]);
667  return TTY_READ_ERROR;
668  }
669 
670  // read data
671  int n;
672  for (n = 0; n < (len - 3); ++n)
673  {
674  if ((rc = readByte(fd, &buf[5 + n], timeout, &read_bytes)) != TTY_OK)
675  {
676  LOGF_DEBUG("%s byte not encountered", "DATA");
677  return rc;
678  }
679  }
680  // read checksum
681  if ((rc = readByte(fd, &buf[5 + n], timeout, &read_bytes)) != TTY_OK)
682  {
683  LOGF_DEBUG("%s byte not encountered", "DATA");
684  return rc;
685  }
686 
687  uint8_t chk = calculateCheckSum(buf, (len + 3));
688 
689  if (chk != buf[len + 2])
690  {
691  LOG_ERROR("Invalid checksum!");
692  return TTY_OK; // not a tty error, nbytes_read is still zero and it is used to indicate there was this problem
693  }
694 
695  *nbytes_read = len + 3;
696 
697  return rc;
698 }
699 
703 bool EFA::sendCommand(const uint8_t * cmd, uint8_t *res, uint32_t cmd_len, uint32_t res_len)
704 {
705  int nbytes_written = 0, nbytes_read = 0, bits = 0, rc = 0, hexbuflen = (DRIVER_LEN * 3 + 4);
706  char hexbuf[DRIVER_LEN * 3 + 4];
707 
708  for (int j = 0; j < 3; usleep(100000), j++)
709  {
710  // make sure RTS is lowered
711  bits = TIOCM_RTS;
712  (void) ioctl(PortFD, TIOCMBIC, &bits);
713  bits = 0;
714 
715  // Wait until CTS is cleared.
716  for (int i = 0; i < 10; i++)
717  {
718  if ((rc = ioctl(PortFD, TIOCMGET, &bits)) == 0 && (bits & TIOCM_CTS) == 0)
719  break;
720  usleep(100000);
721  }
722 
723  if (rc < 0 || (bits & TIOCM_CTS) != 0)
724  {
725  LOGF_ERROR("CTS timed out: %s", strerror(errno));
726  return false;
727  }
728 
729  // Now raise RTS
730  bits = TIOCM_RTS;
731  ioctl(PortFD, TIOCMBIS, &bits); // was TIOCMSET
732 
733  if (!IN_TIMER && efaDump(hexbuf, hexbuflen, cmd, cmd_len) != NULL)
734  {
735  LOGF_DEBUG("CMD: %s", hexbuf);
736  }
737  rc = writeBytes(PortFD, cmd, cmd_len, &nbytes_written);
738 
739  if (rc != TTY_OK)
740  {
741  continue;
742  }
743 
744  rc = readPacket(PortFD, res, res_len, DRIVER_TIMEOUT, &nbytes_read);
745 
746  if (rc != TTY_OK || nbytes_read == 0)
747  {
748  continue;
749  }
750 
751  if ((int)cmd_len == nbytes_read && memcmp(cmd, res, cmd_len) == 0)
752  {
753  // received an echo
754 
755  bits = TIOCM_RTS;
756  ioctl(PortFD, TIOCMBIC, &bits);
757 
758  // Next read the actual response from EFA
759  rc = readPacket(PortFD, res, res_len, DRIVER_TIMEOUT, &nbytes_read);
760 
761  if (rc != TTY_OK || nbytes_read == 0)
762  {
763  continue;
764  }
765  }
766  else if (efaDump(hexbuf, hexbuflen, cmd, cmd_len) != NULL)
767  {
768  // expected there to always be an echo, so note this occurence
769  LOGF_DEBUG("no echo for command packet: %s", hexbuf);
770  }
771 
772  if (!IN_TIMER && efaDump(hexbuf, hexbuflen, res, nbytes_read) != NULL)
773  {
774  LOGF_DEBUG("RESP: %s", hexbuf);
775  }
776 
777  if (cmd[2] != res[3] || cmd[3] != res[2] || cmd[4] != res[4])
778  {
779  LOGF_DEBUG("Send/Receive mismatch - %s!", "packet not for us");
780  continue;
781  }
782  break;
783  }
784 
785  // Extra lowering of RTS to make sure hand controller is made available
786  bits = TIOCM_RTS;
787  ioctl(PortFD, TIOCMBIC, &bits);
788 
789  if (rc != TTY_OK)
790  {
791  char errstr[MAXRBUF] = {0};
792  tty_error_msg(rc, errstr, MAXRBUF);
793  LOGF_ERROR("Serial I/O error: %s.", errstr);
794  return false;
795  }
796 
797  if (cmd[2] != res[3] || cmd[3] != res[2] || cmd[4] != res[4])
798  {
799  LOGF_ERROR("Send/Receive mismatch and %s", "timeout");
800  return false;
801  }
802 
803  return true;
804 }
805 
809 bool EFA::readPosition()
810 {
811  uint8_t cmd[DRIVER_LEN] = {0}, res[DRIVER_LEN] = {0}, len = 6;
812 
813  cmd[0] = DRIVER_SOM;
814  cmd[1] = 0x03;
815  cmd[2] = DEVICE_PC;
816  cmd[3] = DEVICE_FOC;
817  cmd[4] = MTR_GET_POS;
818  cmd[5] = calculateCheckSum(cmd, len);
819 
820  if (!validateLengths(cmd, len))
821  return false;
822 
823  if (!sendCommand(cmd, res, len, DRIVER_LEN))
824  return false;
825 
826  FocusAbsPosN[0].value = res[5] << 16 | res[6] << 8 | res[7];
827  return true;
828 }
829 
833 bool EFA::readMaxSlewLimit()
834 {
835  uint8_t cmd[DRIVER_LEN] = {0}, res[DRIVER_LEN] = {0}, len = 6;
836 
837  cmd[0] = DRIVER_SOM;
838  cmd[1] = 0x03;
839  cmd[2] = DEVICE_PC;
840  cmd[3] = DEVICE_FOC;
842  cmd[5] = calculateCheckSum(cmd, len);
843 
844  if (!validateLengths(cmd, len))
845  return false;
846 
847  if (!sendCommand(cmd, res, len, DRIVER_LEN))
848  return false;
849 
850  uint32_t limit = res[5] << 16 | res[6] << 8 | res[7];
851  if (limit > 0)
852  {
853  FocusMaxPosN[0].max = limit;
854  return true;
855  }
856 
857  return false;
858 
859 }
860 
864 bool EFA::isGOTOComplete()
865 {
866  uint8_t cmd[DRIVER_LEN] = {0}, res[DRIVER_LEN] = {0}, len = 6;
867 
868  cmd[0] = DRIVER_SOM;
869  cmd[1] = 0x03;
870  cmd[2] = DEVICE_PC;
871  cmd[3] = DEVICE_FOC;
872  cmd[4] = MTR_GOTO_OVER;
873  cmd[5] = calculateCheckSum(cmd, len);
874 
875  if (!validateLengths(cmd, len))
876  return false;
877 
878  if (!sendCommand(cmd, res, len, DRIVER_LEN))
879  return false;
880 
881  return (res[5] != 0);
882 }
883 
887 bool EFA::setFanEnabled(bool enabled)
888 {
889  uint8_t cmd[DRIVER_LEN] = {0}, res[DRIVER_LEN] = {0}, len = 7;
890 
891  cmd[0] = DRIVER_SOM;
892  cmd[1] = 0x04;
893  cmd[2] = DEVICE_PC;
894  cmd[3] = DEVICE_FAN;
895  cmd[4] = FANS_SET;
896  cmd[5] = enabled ? 1 : 0;
897  cmd[6] = calculateCheckSum(cmd, len);
898 
899  if (!validateLengths(cmd, len))
900  return false;
901 
902  if (!sendCommand(cmd, res, len, DRIVER_LEN))
903  return false;
904 
905  return (res[5] == 1);
906 }
907 
911 bool EFA::readFanState()
912 {
913  uint8_t cmd[DRIVER_LEN] = {0}, res[DRIVER_LEN] = {0}, len = 6;
914 
915  cmd[0] = DRIVER_SOM;
916  cmd[1] = 0x03;
917  cmd[2] = DEVICE_PC;
918  cmd[3] = DEVICE_FAN;
919  cmd[4] = FANS_GET;
920  cmd[5] = calculateCheckSum(cmd, len);
921 
922  if (!validateLengths(cmd, len))
923  return false;
924 
925  if (!sendCommand(cmd, res, len, DRIVER_LEN))
926  return false;
927 
928  bool enabled = (res[5] == 0);
929 
930  FanStateS[FAN_ON].s = enabled ? ISS_ON : ISS_OFF;
931  FanStateS[FAN_OFF].s = enabled ? ISS_OFF : ISS_ON;
932 
933  return true;
934 }
935 
939 bool EFA::setCalibrationEnabled(bool enabled)
940 {
941  uint8_t cmd[DRIVER_LEN] = {0}, res[DRIVER_LEN] = {0}, len = 8;
942 
943  cmd[0] = DRIVER_SOM;
944  cmd[1] = 0x05;
945  cmd[2] = DEVICE_PC;
946  cmd[3] = DEVICE_FOC;
948  cmd[5] = 0x40;
949  cmd[6] = enabled ? 1 : 0;
950  cmd[7] = calculateCheckSum(cmd, len);
951 
952  if (!validateLengths(cmd, len))
953  return false;
954 
955  if (!sendCommand(cmd, res, len, DRIVER_LEN))
956  return false;
957 
958  return (res[5] == 1);
959 }
960 
964 bool EFA::readCalibrationState()
965 {
966  uint8_t cmd[DRIVER_LEN] = {0}, res[DRIVER_LEN] = {0}, len = 7;
967 
968  cmd[0] = DRIVER_SOM;
969  cmd[1] = 0x04;
970  cmd[2] = DEVICE_PC;
971  cmd[3] = DEVICE_FOC;
973  cmd[5] = 0x40;
974  cmd[6] = calculateCheckSum(cmd, len);
975 
976  if (!validateLengths(cmd, len))
977  return false;
978 
979  if (!sendCommand(cmd, res, len, DRIVER_LEN))
980  return false;
981 
982  bool enabled = (res[5] == 1);
983 
984  CalibrationStateS[CALIBRATION_ON].s = enabled ? ISS_ON : ISS_OFF;
985  CalibrationStateS[CALIBRATION_OFF].s = enabled ? ISS_OFF : ISS_ON;
986 
987  return true;
988 }
989 
993 bool EFA::readTemperature()
994 {
995  uint8_t cmd[DRIVER_LEN] = {0}, res[DRIVER_LEN] = {0}, len = 7;
996 
997  for (uint8_t i = 0; i < 2; i++)
998  {
999  cmd[0] = DRIVER_SOM;
1000  cmd[1] = 0x04;
1001  cmd[2] = DEVICE_PC;
1002  cmd[3] = DEVICE_TEMP;
1003  cmd[4] = TEMP_GET;
1004  cmd[5] = i;
1005  cmd[6] = calculateCheckSum(cmd, len);
1006 
1007  if (!validateLengths(cmd, len))
1008  return false;
1009 
1010  if (!sendCommand(cmd, res, len, DRIVER_LEN))
1011  return false;
1012 
1013  TemperatureN[i].value = calculateTemperature(res[5], res[6]);
1014  }
1015 
1016  return true;
1017 }
1018 
1022 double EFA::calculateTemperature(uint8_t byte2, uint8_t byte3)
1023 {
1024  if (byte2 == 0x7F && byte3 == 0x7F)
1025  return -100;
1026 
1027  int raw_temperature = byte3 << 8 | byte2;
1028  if (raw_temperature & 0x8000)
1029  raw_temperature = raw_temperature - 0x10000;
1030 
1031  return raw_temperature / 16.0;
1032 }
1033 
1037 char * EFA::efaDump(char * buf, int buflen, const uint8_t * data, uint32_t size)
1038 {
1039  int needed = 0, idx = 0;
1040 
1041  needed = size * 3 + 4; // each byte goes to 2 chars plus 1 space (or trailing null char) plus 4 marker characters
1042 
1043  if (needed > buflen)
1044  {
1045  return NULL;
1046  }
1047 
1048  for (uint32_t i = 0; i < size; i++)
1049  {
1050  if (i == 4)
1051  {
1052  (void) sprintf(buf + idx, "<%02X> ", data[i]);
1053  idx += 5;
1054  }
1055  else if (i == 5 && i < (size - 1))
1056  {
1057  buf[idx++] = '[';
1058  for (uint32_t j = i, k = 3; j < (size - 1) && k < data[1]; j++, k++)
1059  {
1060  (void) sprintf(buf + idx, "%02X ", data[j]);
1061  idx += 3;
1062  i = j;
1063  }
1064  buf[idx - 1] = ']';
1065  buf[idx++] = ' ';
1066  }
1067  else
1068  {
1069  (void) sprintf(buf + idx, "%02X ", data[i]);
1070  idx += 3;
1071  }
1072  }
1073 
1074  buf[idx - 1] = '\0';
1075 
1076  return buf;
1077 }
1078 
1082 std::vector<std::string> EFA::split(const std::string &input, const std::string &regex)
1083 {
1084  // passing -1 as the submatch index parameter performs splitting
1085  std::regex re(regex);
1086  std::sregex_token_iterator
1087  first{input.begin(), input.end(), re, -1},
1088  last;
1089  return {first, last};
1090 }
1091 
1095 template <typename T>
1096 std::string EFA::to_string(const T a_value, const int n)
1097 {
1098  std::ostringstream out;
1099  out.precision(n);
1100  out << std::fixed << a_value;
1101  return out.str();
1102 }
1103 
1107 bool EFA::validateLengths(const uint8_t *cmd, uint32_t len)
1108 {
1109  if (len < 6)
1110  {
1111  LOGF_ERROR("packet length (%d) is too short for command %02X", len, cmd[4]);
1112  return false;
1113  }
1114  if (cmd[1] + 3 != (int)len)
1115  {
1116  LOGF_ERROR("packet length (%d) and data length (%d) discrepancy for command %02X", len, cmd[1], cmd[4]);
1117  return false;
1118  }
1119 
1120  return true;
1121 }
1122 
1126 uint8_t EFA::calculateCheckSum(const uint8_t *cmd, uint32_t len)
1127 {
1128  int32_t sum = 0;
1129 
1130  for (uint32_t i = 1; i < len - 1; i++)
1131  sum += cmd[i];
1132 
1133  return ((-sum) & 0xFF);
1134 }
void setDefaultBaudRate(BaudRate newRate)
setDefaultBaudRate Set default baud rate. The default baud rate is 9600 unless otherwise changed by t...
virtual bool SyncFocuser(uint32_t ticks) override
Sync focuser.
virtual IPState MoveRelFocuser(FocusDirection dir, unsigned int ticks) override
Move Focuser relatively.
@ MTR_SLEWLIMITMAX
Definition: planewave_efa.h:49
@ MTR_SLEWLIMITGETMAX
Definition: planewave_efa.h:50
@ GET_VERSION
Definition: planewave_efa.h:62
@ TEMP_GET
Definition: planewave_efa.h:53
@ FANS_GET
Definition: planewave_efa.h:55
@ MTR_GOTO_OVER
Definition: planewave_efa.h:48
@ MTR_GOTO_POS2
Definition: planewave_efa.h:46
@ MTR_OFFSET_CNT
Definition: planewave_efa.h:47
@ FANS_SET
Definition: planewave_efa.h:54
@ MTR_SET_CALIBRATION_STATE
Definition: planewave_efa.h:57
@ MTR_GET_CALIBRATION_STATE
Definition: planewave_efa.h:56
@ MTR_GET_POS
Definition: planewave_efa.h:45
virtual bool ReverseFocuser(bool enabled) override
Reverse Focuser Motion.
@ DEVICE_PC
Definition: planewave_efa.h:67
@ DEVICE_FOC
Definition: planewave_efa.h:69
@ DEVICE_TEMP
Definition: planewave_efa.h:71
@ DEVICE_FAN
Definition: planewave_efa.h:70
virtual bool saveConfigItems(FILE *fp) override
saveConfigItems Saves the Device Port and Focuser Presets in the configuration file
virtual bool Handshake() override
perform handshake with device to check communication
virtual bool SetFocuserMaxPosition(uint32_t ticks) override
Set Maximum Position.
const char * getDefaultName() override
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
virtual void TimerHit() override
Callback function to be called once SetTimer duration elapses.
virtual bool updateProperties() override
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
virtual IPState MoveAbsFocuser(uint32_t targetTicks) override
Move Absolute Focuser.
virtual bool AbortFocuser() override
AbortFocuser all focus motion.
virtual bool initProperties() override
Initilize properties initial state and value. The child class must implement this function.
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
virtual bool Disconnect() override
Disconnect from device.
bool isConnected() const
Definition: basedevice.cpp:520
const char * getDeviceName() const
Definition: basedevice.cpp:821
virtual bool Disconnect()
Disconnect from device.
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 loadConfig(bool silent=false, const char *property=nullptr)
Load the last saved configuration file.
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 FocusAbsPosNP
INumberVectorProperty FocusRelPosNP
void SetCapability(uint32_t cap)
FI::SetCapability sets the focuser capabilities. All capabilities must be initialized.
INumberVectorProperty FocusMaxPosNP
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 Device Port and Focuser Presets 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 initProperties() override
Initilize properties initial state and value. The child class must implement this function.
Definition: indifocuser.cpp:42
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
Connection::Serial * serialConnection
Definition: indifocuser.h:116
const char * MAIN_CONTROL_TAB
MAIN_CONTROL_TAB Where all the primary controls for the device are located.
double max(void)
int errno
double min(void)
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_1OFMANY
Definition: indiapi.h:173
@ ISR_NOFMANY
Definition: indiapi.h:175
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
void tty_error_msg(int err_code, char *err_msg, int err_msg_len)
Retrieve the tty error message.
Definition: indicom.c:1167
Implementations for common driver routines.
@ TTY_OK
Definition: indicom.h:150
@ TTY_READ_ERROR
Definition: indicom.h:151
@ TTY_TIME_OUT
Definition: indicom.h:154
void IUSaveConfigSwitch(FILE *fp, const ISwitchVectorProperty *svp)
Add a switch vector property value to the configuration file.
Definition: indidevapi.c:25
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
const char * IUFindOnSwitchName(ISState *states, char *names[], int n)
Returns the name of the first ON switch it finds in the supplied arguments.
Definition: indidevapi.c:137
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
#define INDI_UNUSED(x)
Definition: indidevapi.h:131
int IUUpdateSwitch(ISwitchVectorProperty *svp, ISState *states, char *names[], int n)
Update all switches in a switch vector property.
Definition: indidriver.c:1308
void IDSetNumber(const INumberVectorProperty *nvp, const char *fmt,...)
Definition: indidriver.c:1211
void IDSetSwitch(const ISwitchVectorProperty *svp, const char *fmt,...)
Definition: indidriver.c:1231
int IUUpdateNumber(INumberVectorProperty *nvp, double values[], char *names[], int n)
Update all numbers in a number vector property.
Definition: indidriver.c:1362
void IUUpdateMinMax(const INumberVectorProperty *nvp)
Function to update the min and max elements of a number in the client.
Definition: indidriver.c:1296
#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 MAXRBUF
Definition: indiserver.cpp:102
int fd
Definition: intelliscope.c:43
NLOHMANN_BASIC_JSON_TPL_DECLARATION std::string to_string(const NLOHMANN_BASIC_JSON_TPL &j)
user-defined to_string function for JSON values
Definition: json.h:23613
__u8 cmd[4]
Definition: pwc-ioctl.h:2
std::unique_ptr< SteelDrive > steelDrive(new SteelDrive())
char name[MAXINDINAME]
Definition: indiapi.h:323
char name[MAXINDINAME]
Definition: indiapi.h:371
char name[MAXINDINAME]
Definition: indiapi.h:250