Instrument Neutral Distributed Interface INDI  2.0.2
focuser_driver.cpp
Go to the documentation of this file.
1 /*
2  FocuserDriver Focuser
3 
4  Copyright(c) 2019 Jasem Mutlaq. All rights reserved.
5 
6  This library is free software; you can redistribute it and/or
7  modify it under the terms of the GNU Lesser General Public
8  License as published by the Free Software Foundation; either
9  version 2.1 of the License, or (at your option) any later version.
10 
11  This library is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14  Lesser General Public License for more details.
15 
16  You should have received a copy of the GNU Lesser General Public
17  License along with this library; if not, write to the Free Software
18  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20 
21 #include "focuser_driver.h"
22 
23 #include "indicom.h"
24 
25 #include <cstring>
26 #include <termios.h>
27 #include <memory>
28 #include <thread>
29 #include <chrono>
30 
31 static std::unique_ptr<FocuserDriver> focuserDriver(new FocuserDriver());
32 
34 {
35  // Let's specify the driver version
36  setVersion(1, 0);
37 
38  // What capabilities do we support?
43 }
44 
46 {
48 
49  // Focuser temperature
50  IUFillNumber(&TemperatureN[0], "TEMPERATURE", "Celsius", "%6.2f", -100, 100, 0, 0);
51  IUFillNumberVector(&TemperatureNP, TemperatureN, 1, getDeviceName(), "FOCUS_TEMPERATURE", "Temperature",
53 
54  // Stepping Modes
55  IUFillSwitch(&SteppingModeS[STEPPING_FULL], "STEPPING_FULL", "Full", ISS_ON);
56  IUFillSwitch(&SteppingModeS[STEPPING_HALF], "STEPPING_HALF", "Half", ISS_OFF);
57  IUFillSwitchVector(&SteppingModeSP, SteppingModeS, 2, getDeviceName(), "STEPPING_MODE", "Mode",
58  STEPPING_TAB, IP_RW, ISR_1OFMANY, 0, IPS_OK);
59 
60 
62 
63  // Set limits as per documentation
64  FocusAbsPosN[0].min = 0;
65  FocusAbsPosN[0].max = 999999;
66  FocusAbsPosN[0].step = 1000;
67 
68  FocusRelPosN[0].min = 0;
69  FocusRelPosN[0].max = 999;
70  FocusRelPosN[0].step = 100;
71 
72  FocusSpeedN[0].min = 1;
73  FocusSpeedN[0].max = 254;
74  FocusSpeedN[0].step = 10;
75 
76  return true;
77 }
78 
80 {
81  return "Focuser Driver";
82 }
83 
85 {
86  if (isConnected())
87  {
88  // Read these values before defining focuser interface properties
89  readPosition();
90  }
91 
93 
94  if (isConnected())
95  {
96  if (readTemperature())
97  defineProperty(&TemperatureNP);
98 
99  bool rc = getStartupValues();
100 
101  // Settings
102  defineProperty(&SteppingModeSP);
103 
104  if (rc)
105  LOG_INFO("FocuserDriver is ready.");
106  else
107  LOG_WARN("Failed to query startup values.");
108  }
109  else
110  {
111  if (TemperatureNP.s == IPS_OK)
112  deleteProperty(TemperatureNP.name);
113 
114  deleteProperty(SteppingModeSP.name);
115  }
116 
117  return true;
118 }
119 
121 {
122  // This functin is ensure that we have communication with the focuser
123  // Below we send it 0x6 byte and check for 'S' in the return. Change this
124  // to be valid for your driver. It could be anything, you can simply put this below
125  // return readPosition()
126  // since this will try to read the position and if successful, then communicatoin is OK.
127  char cmd[DRIVER_LEN] = {0}, res[DRIVER_LEN] = {0};
128 
129  // Ack
130  cmd[0] = 0x6;
131 
132  bool rc = sendCommand(cmd, res, 1, 1);
133  if (rc == false)
134  return false;
135 
136  return res[0] == 'S';
137 }
138 
139 bool FocuserDriver::sendCommand(const char * cmd, char * res, int cmd_len, int res_len)
140 {
141  int nbytes_written = 0, nbytes_read = 0, rc = -1;
142 
143  tcflush(PortFD, TCIOFLUSH);
144 
145  if (cmd_len > 0)
146  {
147  char hex_cmd[DRIVER_LEN * 3] = {0};
148  hexDump(hex_cmd, cmd, cmd_len);
149  LOGF_DEBUG("CMD <%s>", hex_cmd);
150  rc = tty_write(PortFD, cmd, cmd_len, &nbytes_written);
151  }
152  else
153  {
154  LOGF_DEBUG("CMD <%s>", cmd);
155  rc = tty_write_string(PortFD, cmd, &nbytes_written);
156  }
157 
158  if (rc != TTY_OK)
159  {
160  char errstr[MAXRBUF] = {0};
161  tty_error_msg(rc, errstr, MAXRBUF);
162  LOGF_ERROR("Serial write error: %s.", errstr);
163  return false;
164  }
165 
166  if (res == nullptr)
167  return true;
168 
169  if (res_len > 0)
170  rc = tty_read(PortFD, res, res_len, DRIVER_TIMEOUT, &nbytes_read);
171  else
172  rc = tty_nread_section(PortFD, res, DRIVER_LEN, DRIVER_STOP_CHAR, DRIVER_TIMEOUT, &nbytes_read);
173 
174  if (rc != TTY_OK)
175  {
176  char errstr[MAXRBUF] = {0};
177  tty_error_msg(rc, errstr, MAXRBUF);
178  LOGF_ERROR("Serial read error: %s.", errstr);
179  return false;
180  }
181 
182  if (res_len > 0)
183  {
184  char hex_res[DRIVER_LEN * 3] = {0};
185  hexDump(hex_res, res, res_len);
186  LOGF_DEBUG("RES <%s>", hex_res);
187  }
188  else
189  {
190  LOGF_DEBUG("RES <%s>", res);
191  }
192 
193  tcflush(PortFD, TCIOFLUSH);
194 
195  return true;
196 }
197 
198 void FocuserDriver::hexDump(char * buf, const char * data, int size)
199 {
200  for (int i = 0; i < size; i++)
201  sprintf(buf + 3 * i, "%02X ", static_cast<uint8_t>(data[i]));
202 
203  if (size > 0)
204  buf[3 * size - 1] = '\0';
205 }
206 
207 bool FocuserDriver::ISNewSwitch(const char * dev, const char * name, ISState * states, char * names[], int n)
208 {
209  if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
210  {
211  // Stepping Mode
212  if (!strcmp(name, SteppingModeSP.name))
213  {
214  IUUpdateSwitch(&SteppingModeSP, states, names, n);
215  SteppingModeSP.s = IPS_OK;
216  IDSetSwitch(&SteppingModeSP, nullptr);
217  return true;
218  }
219  }
220 
221  return INDI::Focuser::ISNewSwitch(dev, name, states, names, n);
222 }
223 
224 bool FocuserDriver::getStartupValues()
225 {
226  bool rc1 = readStepping();
227  //bool rc2 = readFoo();
228  //bool rc2 = readBar();
229 
230  //return (rc1 && rc2 && rc3);
231 
232  return rc1;
233 }
234 
236 {
237  // Issue here the command necessary to move the focuser to targetTicks
238  return IPS_BUSY;
239 }
240 
242 {
243  m_TargetDiff = ticks * ((dir == FOCUS_INWARD) ? -1 : 1);
244  return MoveAbsFocuser(FocusAbsPosN[0].value + m_TargetDiff);
245 }
246 
248 {
249  return sendCommand("FOOBAR");
250 }
251 
253 {
254  if (isConnected() == false)
255  return;
256 
257  // What is the last read position?
258  double currentPosition = FocusAbsPosN[0].value;
259 
260  // Read the current position
261  readPosition();
262 
263  // Check if we have a pending motion
264  // if isMoving() is false, then we stopped, so we need to set the Focus Absolute
265  // and relative properties to OK
266  if ( (FocusAbsPosNP.s == IPS_BUSY || FocusRelPosNP.s == IPS_BUSY) && isMoving() == false)
267  {
270  IDSetNumber(&FocusAbsPosNP, nullptr);
271  IDSetNumber(&FocusRelPosNP, nullptr);
272  }
273  // If there was a different between last and current positions, let's update all clients
274  else if (currentPosition != FocusAbsPosN[0].value)
275  {
276  IDSetNumber(&FocusAbsPosNP, nullptr);
277  }
278 
279  // Read temperature periodically
280  if (TemperatureNP.s == IPS_OK && m_TemperatureCounter++ == DRIVER_TEMPERATURE_FREQ)
281  {
282  m_TemperatureCounter = 0;
283  if (readTemperature())
284  IDSetNumber(&TemperatureNP, nullptr);
285  }
286 
288 }
289 
290 bool FocuserDriver::isMoving()
291 {
292  char res[DRIVER_LEN] = {0};
293 
294  bool rc = sendCommand("FOOBAR", res, 1, 1);
295 
296  if (rc && !strcmp(res, "STOPPED"))
297  return true;
298 
299  return false;
300 }
301 
302 bool FocuserDriver::readTemperature()
303 {
304  char res[DRIVER_LEN] = {0};
305 
306  // This assumes we need to read 4 BYTES for the temperature. It can be anything
307  // If the response is terminated by the DRIVER_STOP_CHAR, we can simply call
308  // sendCommand("FOOBAR", res)
309  if (sendCommand("FOOBAR", res, strlen("FOOBAR"), 4) == false)
310  return false;
311 
312  float temperature = -1000;
313  sscanf(res, "%f", &temperature);
314 
315  if (temperature < -100)
316  return false;
317 
318  TemperatureN[0].value = temperature;
319  TemperatureNP.s = IPS_OK;
320 
321  return true;
322 }
323 
324 bool FocuserDriver::readPosition()
325 {
326  char cmd[DRIVER_LEN] = {0}, res[DRIVER_LEN] = {0};
327 
328  cmd[0] = 0xA;
329  cmd[1] = 0xB;
330  cmd[2] = 0xC;
331 
332  // since the command above is not NULL-TERMINATED, we need to specify the number of bytes (3)
333  // in the send command below. We also specify 7 bytes to be read which can be changed to any value.
334  if (sendCommand(cmd, res, 3, 7) == false)
335  return false;
336 
337  // For above, in case instead the response is terminated by DRIVER_STOP_CHAR, then the command would be
338  // (sendCommand(cmd, res, 3) == false)
339  // return false;
340 
341  int32_t pos = 1e6;
342  sscanf(res, "%d", &pos);
343 
344  if (pos == 1e6)
345  return false;
346 
347  FocusAbsPosN[0].value = pos;
348 
349  return true;
350 }
351 
352 bool FocuserDriver::readStepping()
353 {
354  char res[DRIVER_LEN] = {0};
355 
356  if (sendCommand("FOOBAR", res, 3, 1) == false)
357  return false;
358 
359  int32_t mode = 1e6;
360  sscanf(res, "%d", &mode);
361 
362  if (mode == 1e6)
363  return false;
364 
365  // Assuming the above function returns 10 for full step, and 11 for half step
366  // we can update the switch status as follows
367  SteppingModeS[STEPPING_FULL].s = (mode == 10) ? ISS_ON : ISS_OFF;
368  SteppingModeS[STEPPING_HALF].s = (mode == 10) ? ISS_OFF : ISS_ON;
369  SteppingModeSP.s = IPS_OK;
370 
371  return true;
372 }
373 
374 
375 bool FocuserDriver::SyncFocuser(uint32_t ticks)
376 {
377  char cmd[DRIVER_LEN] = {0};
378  snprintf(cmd, DRIVER_LEN, "#:SYNC+%06d#", ticks);
379  return sendCommand(cmd);
380 }
381 
382 bool FocuserDriver::setStepping(SteppingMode mode)
383 {
384  char cmd[DRIVER_LEN] = {0};
385  snprintf(cmd, DRIVER_LEN, "#FOOBAR%01d#", mode);
386  return sendCommand(cmd);
387 }
388 
390 {
392 
393  // We need to reserve and save stepping mode
394  // so that the next time the driver is loaded, it is remembered and applied.
395  IUSaveConfigSwitch(fp, &SteppingModeSP);
396 
397  return true;
398 }
bool SyncFocuser(uint32_t ticks) override
SyncFocuser Set current position to ticks without moving the focuser.
const char * getDefaultName() override
bool initProperties() override
Initilize properties initial state and value. The child class must implement this function.
IPState MoveRelFocuser(FocusDirection dir, uint32_t ticks) override
MoveFocuser the focuser to an relative position.
IPState MoveAbsFocuser(uint32_t targetTicks) override
MoveFocuser the focuser to an absolute position.
bool AbortFocuser() override
AbortFocuser all focus motion.
bool updateProperties() override
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
void TimerHit() override
Callback function to be called once SetTimer duration elapses.
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
bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
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.
int SetTimer(uint32_t ms)
Set a timer to call the function TimerHit after ms milliseconds.
void addDebugControl()
Add Debug control to the driver.
INumberVectorProperty FocusAbsPosNP
INumberVectorProperty FocusRelPosNP
void SetCapability(uint32_t cap)
FI::SetCapability sets the focuser capabilities. All capabilities must be initialized.
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
const char * MAIN_CONTROL_TAB
MAIN_CONTROL_TAB Where all the primary controls for the device are located.
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_IDLE
Definition: indiapi.h:161
@ IPS_OK
Definition: indiapi.h:162
@ ISR_1OFMANY
Definition: indiapi.h:173
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 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 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 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
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
#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
#define currentPosition
Definition: robofocus.cpp:37
char name[MAXINDINAME]
Definition: indiapi.h:323
char name[MAXINDINAME]
Definition: indiapi.h:371