Instrument Neutral Distributed Interface INDI  2.0.2
mount_driver.cpp
Go to the documentation of this file.
1 /*******************************************************************************
2  Copyright(c) 2019 Jasem Mutlaq. All rights reserved.
3 
4  This library is free software; you can redistribute it and/or
5  modify it under the terms of the GNU Library General Public
6  License version 2 as published by the Free Software Foundation.
7  .
8  This library is distributed in the hope that it will be useful,
9  but WITHOUT ANY WARRANTY; without even the implied warranty of
10  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11  Library General Public License for more details.
12  .
13  You should have received a copy of the GNU Library General Public License
14  along with this library; see the file COPYING.LIB. If not, write to
15  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
16  Boston, MA 02110-1301, USA.
17 *******************************************************************************/
18 
19 #include "mount_driver.h"
20 
21 #include "indicom.h"
22 
23 #include <libnova/sidereal_time.h>
24 #include <libnova/transform.h>
25 
26 #include <termios.h>
27 #include <cmath>
28 #include <cstring>
29 #include <memory>
30 
31 // Single unique pointer to the driver.
32 static std::unique_ptr<MountDriver> telescope_sim(new MountDriver());
33 
35 {
36  // Let's specify the driver version
37  setVersion(1, 0);
38 
39  // Set capabilities supported by the mount.
40  // The last parameters is the number of slew rates available.
45  4);
46 
47 }
48 
50 {
51  return "Mount Driver";
52 }
53 
55 {
56  // Make sure to init parent properties first
58 
59  // How fast do we guide compared to sidereal rate
60  IUFillNumber(&GuideRateN[AXIS_RA], "GUIDE_RATE_WE", "W/E Rate", "%.1f", 0, 1, 0.1, 0.5);
61  IUFillNumber(&GuideRateN[AXIS_DE], "GUIDE_RATE_NS", "N/S Rate", "%.1f", 0, 1, 0.1, 0.5);
62  IUFillNumberVector(&GuideRateNP, GuideRateN, 2, getDeviceName(), "GUIDE_RATE", "Guiding Rate", MOTION_TAB, IP_RW, 0,
63  IPS_IDLE);
64 
65  // Since we have 4 slew rates, let's fill them out
66  IUFillSwitch(&SlewRateS[SLEW_GUIDE], "SLEW_GUIDE", "Guide", ISS_OFF);
67  IUFillSwitch(&SlewRateS[SLEW_CENTERING], "SLEW_CENTERING", "Centering", ISS_OFF);
68  IUFillSwitch(&SlewRateS[SLEW_FIND], "SLEW_FIND", "Find", ISS_OFF);
69  IUFillSwitch(&SlewRateS[SLEW_MAX], "SLEW_MAX", "Max", ISS_ON);
70  IUFillSwitchVector(&SlewRateSP, SlewRateS, 4, getDeviceName(), "TELESCOPE_SLEW_RATE", "Slew Rate", MOTION_TAB,
72 
73  // Add Tracking Modes. If you have SOLAR, LUNAR..etc, add them here as well.
74  AddTrackMode("TRACK_SIDEREAL", "Sidereal", true);
75  AddTrackMode("TRACK_CUSTOM", "Custom");
76 
77  // The mount is initially in IDLE state.
79 
80  // How does the mount perform parking?
81  // Some mounts can handle the parking functionality internally in the controller.
82  // Other mounts have no native parking support and we use INDI to slew to a particular
83  // location (Equatorial or Horizontal) and then turn off tracking there and save the location to a file
84  // which would be remembered in the next power cycle.
85  // This is not required if there is native support in the mount controller itself.
87 
88  // Let init the pulse guiding properties
90 
91  // Add debug controls
93 
94  // Set the driver interface to indicate that we can also do pulse guiding
96 
97  // We want to query the mount every 500ms by default. The user can override this value.
99 
100  return true;
101 }
102 
104 {
106 
107  if (isConnected())
108  {
111  defineProperty(&GuideRateNP);
112 
113  // Read the parking file, and check if we can load any saved parking information.
114  if (InitPark())
115  {
116  // If loading parking data is successful, we just set the default parking values.
117  // By default in this example, we consider parking position Az=0 and Alt=0
120  }
121  else
122  {
123  // Otherwise, we set all parking data to default in case no parking data is found.
124  SetAxis1Park(0);
125  SetAxis2Park(0);
128  }
129  }
130  else
131  {
134  deleteProperty(GuideRateNP.name);
135  }
136 
137  return true;
138 }
139 
141 {
142  // This functin is ensure that we have communication with the mount
143  // Below we send it 0x6 byte and check for 'S' in the return. Change this
144  // to be valid for your driver. It could be anything, you can simply put this below
145  // return readScopeStatus()
146  // since this will try to read the position and if successful, then communicatoin is OK.
147  char cmd[DRIVER_LEN] = {0}, res[DRIVER_LEN] = {0};
148 
149  // Ack
150  cmd[0] = 0x6;
151 
152  bool rc = sendCommand(cmd, res, 1, 1);
153  if (rc == false)
154  return false;
155 
156  return res[0] == 'S';
157 }
158 
160 {
161 
162  // Here we read the mount position, pier side, any status of interest.
163  // This is called every POLLMS milliseconds (default 1000, but our driver set the default to 500)
164 
165  // For example, it could be a command like this
166  char cmd[DRIVER_LEN] = {0}, res[DRIVER_LEN] = {0};
167  if (sendCommand("GetCoordinates", res) == false)
168  return false;
169 
170  double currentRA = 0, currentDE = 0;
171  // Assuming we get response as RA:DEC (Hours:Degree) e.g. "12.4:-34.6"
172  sscanf(res, "%lf:%lf", &currentRA, &currentDE);
173 
174  char RAStr[DRIVER_LEN] = {0}, DecStr[DRIVER_LEN] = {0};
175  fs_sexa(RAStr, currentRA, 2, 3600);
176  fs_sexa(DecStr, currentDE, 2, 3600);
177  LOGF_DEBUG("Current RA: %s Current DEC: %s", RAStr, DecStr);
178 
179  NewRaDec(currentRA, currentDE);
180 
181  // E.g. get pier side as well
182  // assuming we need to send 3-bytes 0x11 0x22 0x33 to get the pier side, which is always 1 byte as 0 (EAST) or 1 (WEST)
183  cmd[0] = 0x11;
184  cmd[1] = 0x22;
185  cmd[2] = 0x33;
186 
187  // Let us not forget to reset res buffer by zeroing it out
188  memset(res, 0, DRIVER_LEN);
189  if (sendCommand(cmd, res, 3, 1))
190  {
191  setPierSide(res[0] == 0 ? PIER_EAST : PIER_WEST);
192  }
193 
194  return true;
195 }
196 
197 bool MountDriver::Goto(double RA, double DE)
198 {
199  char cmd[DRIVER_LEN] = {0}, res[DRIVER_LEN] = {0};
200 
201  // Assuming the command is in this format: sendCoords RA:DE
202  snprintf(cmd, DRIVER_LEN, "sendCoords %g:%g", RA, DE);
203  // Assuming response is 1-byte with '1' being OK, and anything else being failed.
204  if (sendCommand(cmd, res, -1, 1) == false)
205  return false;
206 
207  if (res[0] != '1')
208  return false;
209 
211 
212  char RAStr[DRIVER_LEN] = {0}, DecStr[DRIVER_LEN] = {0};
213  fs_sexa(RAStr, RA, 2, 3600);
214  fs_sexa(DecStr, DE, 2, 3600);
215  LOGF_INFO("Slewing to RA: %s - DEC: %s", RAStr, DecStr);
216  return true;
217 }
218 
219 bool MountDriver::Sync(double RA, double DE)
220 {
221  char cmd[DRIVER_LEN] = {0}, res[DRIVER_LEN] = {0};
222 
223  // Assuming the command is in this format: syncCoords RA:DE
224  snprintf(cmd, DRIVER_LEN, "syncCoords %g:%g", RA, DE);
225  // Assuming response is 1-byte with '1' being OK, and anything else being failed.
226  if (sendCommand(cmd, res, -1, 1) == false)
227  return false;
228 
229  if (res[0] != '1')
230  return false;
231 
232  NewRaDec(RA, DE);
233 
234  return true;
235 }
236 
238 {
239  // Send command for parking here
241  LOG_INFO("Parking telescope in progress...");
242  return true;
243 }
244 
246 {
247  SetParked(false);
248  return true;
249 }
250 
251 bool MountDriver::ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
252 {
253  if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
254  {
255  // Guide Rate
256  if (strcmp(name, "GUIDE_RATE") == 0)
257  {
258  IUUpdateNumber(&GuideRateNP, values, names, n);
259  GuideRateNP.s = IPS_OK;
260  IDSetNumber(&GuideRateNP, nullptr);
261  return true;
262  }
263 
264  // For guiding pulse, let's pass the properties up to the guide framework
265  if (strcmp(name, GuideNSNP.name) == 0 || strcmp(name, GuideWENP.name) == 0)
266  {
267  processGuiderProperties(name, values, names, n);
268  return true;
269  }
270  }
271 
272  // Otherwise, send it up the chains to INDI::Telescope to process any further properties
273  return INDI::Telescope::ISNewNumber(dev, name, values, names, n);
274 }
275 
276 
278 {
279  // Example of a function call where we expect no respose
280  return sendCommand("AbortMount");
281 }
282 
284 {
285  INDI_UNUSED(dir);
286  INDI_UNUSED(command);
287  if (TrackState == SCOPE_PARKED)
288  {
289  LOG_ERROR("Please unpark the mount before issuing any motion commands.");
290  return false;
291  }
292 
293  // Implement here the actual calls to do the motion requested
294  return true;
295 }
296 
298 {
299  INDI_UNUSED(dir);
300  INDI_UNUSED(command);
301  if (TrackState == SCOPE_PARKED)
302  {
303  LOG_ERROR("Please unpark the mount before issuing any motion commands.");
304  return false;
305  }
306 
307  // Implement here the actual calls to do the motion requested
308  return true;
309 }
310 
312 {
313  // Implement here the actual calls to do the motion requested
314  return IPS_BUSY;
315 }
316 
318 {
319  // Implement here the actual calls to do the motion requested
320  return IPS_BUSY;
321 }
322 
324 {
325  // Implement here the actual calls to do the motion requested
326  return IPS_BUSY;
327 }
328 
330 {
331  // Implement here the actual calls to do the motion requested
332  return IPS_BUSY;
333 }
334 
335 bool MountDriver::updateLocation(double latitude, double longitude, double elevation)
336 {
337  INDI_UNUSED(elevation);
338  // JM: INDI Longitude is 0 to 360 increasing EAST. libnova East is Positive, West is negative
339  m_GeographicLocation.lng = longitude;
340 
341  if (m_GeographicLocation.lng > 180)
342  m_GeographicLocation.lng -= 360;
343  m_GeographicLocation.lat = latitude;
344 
345  // Implement here the actual calls to the controller to set the location if supported.
346 
347  // Inform the client that location was updated if all goes well
348  LOGF_INFO("Location updated: Longitude (%g) Latitude (%g)", m_GeographicLocation.lng, m_GeographicLocation.lat);
349 
350  return true;
351 }
352 
354 {
355  // Depending on the parking type defined initially (PARK_RA_DEC or PARK_AZ_ALT...etc) set the current
356  // position AS the parking position.
357 
358  // Assumg PARK_AZ_ALT, we need to do something like this:
359 
360  // SetAxis1Park(getCurrentAz());
361  // SetAxis2Park(getCurrentAlt());
362 
363  // Or if currentAz, currentAlt are defined as variables in our driver, then
364  // SetAxis1Park(currentAz);
365  // SetAxis2Park(currentAlt);
366 
367  return true;
368 }
369 
371 {
372  // For RA_DE park, we can use something like this:
373 
374  // By default set RA to HA
376  // Set DEC to 90 or -90 depending on the hemisphere
377  SetAxis2Park((LocationN[LOCATION_LATITUDE].value > 0) ? 90 : -90);
378 
379  // For Az/Alt, we can use something like this:
380 
381  // Az = 0
382  SetAxis1Park(0);
383  // Alt = 0
384  SetAxis2Park(0);
385 
386  return true;
387 }
388 
389 bool MountDriver::SetTrackMode(uint8_t mode)
390 {
391  // Sidereal/Lunar/Solar..etc
392 
393  // Send actual command here to device
394  INDI_UNUSED(mode);
395  return true;
396 }
397 
399 {
400  // Tracking on or off?
401  INDI_UNUSED(enabled);
402  // Send actual command here to device
403  return true;
404 }
405 
406 bool MountDriver::SetTrackRate(double raRate, double deRate)
407 {
408  // Send actual command here to device
409  INDI_UNUSED(raRate);
410  INDI_UNUSED(deRate);
411  return true;
412 }
413 
414 bool MountDriver::sendCommand(const char * cmd, char * res, int cmd_len, int res_len)
415 {
416  int nbytes_written = 0, nbytes_read = 0, rc = -1;
417 
418  tcflush(PortFD, TCIOFLUSH);
419 
420  if (cmd_len > 0)
421  {
422  char hex_cmd[DRIVER_LEN * 3] = {0};
423  hexDump(hex_cmd, cmd, cmd_len);
424  LOGF_DEBUG("CMD <%s>", hex_cmd);
425  rc = tty_write(PortFD, cmd, cmd_len, &nbytes_written);
426  }
427  else
428  {
429  LOGF_DEBUG("CMD <%s>", cmd);
430  rc = tty_write_string(PortFD, cmd, &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  LOGF_DEBUG("RES <%s>", res);
466  }
467 
468  tcflush(PortFD, TCIOFLUSH);
469 
470  return true;
471 }
472 
473 void MountDriver::hexDump(char * buf, const char * data, int size)
474 {
475  for (int i = 0; i < size; i++)
476  sprintf(buf + 3 * i, "%02X ", static_cast<uint8_t>(data[i]));
477 
478  if (size > 0)
479  buf[3 * size - 1] = '\0';
480 }
bool isConnected() const
Definition: basedevice.cpp:520
const char * getDeviceName() const
Definition: basedevice.cpp:821
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 deleteProperty(const char *propertyName)
Delete a property and unregister it. It will also be deleted from all clients.
void defineProperty(INumberVectorProperty *property)
void setDriverInterface(uint16_t value)
setInterface Set driver interface. By default the driver interface is set to GENERAL_DEVICE....
uint16_t getDriverInterface() const
void addDebugControl()
Add Debug control to the driver.
INumberVectorProperty GuideNSNP
void initGuiderProperties(const char *deviceName, const char *groupName)
Initilize guider properties. It is recommended to call this function within initProperties() of your ...
INumberVectorProperty GuideWENP
void processGuiderProperties(const char *name, double values[], char *names[], int n)
Call this function whenever client updates GuideNSNP or GuideWSP properties in the primary device....
TelescopeStatus TrackState
void SetAxis1Park(double value)
SetRAPark Set current RA/AZ parking position. The data park file (stored in ~/.indi/ParkData....
void SetAxis1ParkDefault(double steps)
SetRAPark Set default RA/AZ parking position.
void SetTelescopeCapability(uint32_t cap, uint8_t slewRateCount)
SetTelescopeCapability sets the Telescope capabilities. All capabilities must be initialized.
virtual bool initProperties() override
Called to initialize basic properties required all the time.
virtual int AddTrackMode(const char *name, const char *label, bool isDefault=false)
AddTrackMode.
ISwitchVectorProperty SlewRateSP
virtual bool updateProperties() override
Called when connected state changes, to add/remove properties.
virtual void SetParked(bool isparked)
SetParked Change the mount parking status. The data park file (stored in ~/.indi/ParkData....
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
void NewRaDec(double ra, double dec)
The child class calls this function when it has updates.
INumber LocationN[3]
void setPierSide(TelescopePierSide side)
ISwitch * SlewRateS
bool InitPark()
InitPark Loads parking data (stored in ~/.indi/ParkData.xml) that contains parking status and parking...
void SetAxis2Park(double steps)
SetDEPark Set current DEC/ALT parking position. The data park file (stored in ~/.indi/ParkData....
void SetParkDataType(TelescopeParkData type)
setParkDataType Sets the type of parking data stored in the park data file and presented to the user.
void SetAxis2ParkDefault(double steps)
SetDEParkDefault Set default DEC/ALT parking position.
The MountDriver class provides a simple example for development of a new mount driver....
Definition: mount_driver.h:41
virtual IPState GuideWest(uint32_t ms) override
Guide west for ms milliseconds. West is defined as RA-.
virtual const char * getDefaultName() override
virtual bool Park() override
Parking commands.
bool sendCommand(const char *cmd, char *res=nullptr, int cmd_len=-1, int res_len=-1)
Utility Functions.
virtual bool updateProperties() override
Called when connected state changes, to add/remove properties.
virtual bool Abort() override
Abort Abort all motion. If tracking, stop it.
virtual bool SetTrackMode(uint8_t mode) override
Tracking Commands.
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
virtual bool Handshake() override
Communication Commands.
virtual bool SetTrackEnabled(bool enabled) override
SetTrackEnabled Engages or disengages mount tracking. If there are no tracking modes available,...
virtual IPState GuideEast(uint32_t ms) override
Guide east for ms milliseconds. East is defined as RA+.
virtual IPState GuideSouth(uint32_t ms) override
Guide south for ms milliseconds. South is defined as DEC-.
void hexDump(char *buf, const char *data, int size)
hexDump Helper function to print non-string commands to the logger so it is easier to debug
virtual bool MoveWE(INDI_DIR_WE dir, TelescopeMotionCommand command) override
MoveWE Start or Stop motion in the East/West RA Axis.
virtual bool SetCurrentPark() override
SetCurrentPark Set current coordinates/encoders value as the desired parking position.
virtual bool Sync(double RA, double DE) override
Set the telescope current RA and DEC coordinates to the supplied RA and DEC coordinates.
virtual bool initProperties() override
Called to initialize basic properties required all the time.
virtual bool SetTrackRate(double raRate, double deRate) override
SetTrackRate Set custom tracking rates.
virtual bool UnPark() override
Unpark the telescope if already parked.
virtual bool SetDefaultPark() override
SetDefaultPark Set default coordinates/encoders value as the desired parking position.
virtual bool MoveNS(INDI_DIR_NS dir, TelescopeMotionCommand command) override
Motions commands.
virtual IPState GuideNorth(uint32_t ms) override
Pulse Guiding Commands.
virtual bool updateLocation(double latitude, double longitude, double elevation) override
Time, Date & Location commands.
virtual bool Goto(double RA, double DE) override
GOTO & Sync commands.
virtual bool ReadScopeStatus() override
ReadScopeStatus Query the mount status, coordinate, any status indicators, pier side....
const char * MOTION_TAB
MOTION_TAB Where all the motion control properties of the device are located.
#define currentRA
Definition: ieq45.cpp:47
@ ISS_OFF
Definition: indiapi.h:151
@ ISS_ON
Definition: indiapi.h:152
@ IP_RW
Definition: indiapi.h:186
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
@ AXIS_DE
Definition: indibasetypes.h:36
@ AXIS_RA
Definition: indibasetypes.h:35
INDI_DIR_WE
Definition: indibasetypes.h:55
INDI_DIR_NS
Definition: indibasetypes.h:48
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 fs_sexa(char *out, double a, int w, int fracbase)
Converts a sexagesimal number to a string. sprint the variable a in sexagesimal format into out[].
Definition: indicom.c:141
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
double get_local_sidereal_time(double longitude)
get_local_sidereal_time Returns local sideral time given longitude and system clock.
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
#define INDI_UNUSED(x)
Definition: indidevapi.h:131
void IDSetNumber(const INumberVectorProperty *nvp, const char *fmt,...)
Definition: indidriver.c:1211
int IUUpdateNumber(INumberVectorProperty *nvp, double values[], char *names[], int n)
Update all numbers in a number vector property.
Definition: indidriver.c:1362
#define LOGF_INFO(fmt,...)
Definition: indilogger.h:82
#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
__u8 cmd[4]
Definition: pwc-ioctl.h:2
std::unique_ptr< ScopeSim > telescope_sim(new ScopeSim())
char name[MAXINDINAME]
Definition: indiapi.h:323