Instrument Neutral Distributed Interface INDI 2.2.5
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.
32static 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
89 initGuiderProperties(getDeviceName(), MOTION_TAB);
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 function 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
197bool 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
219bool 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
251bool 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);
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);
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
335bool 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
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
406bool 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
414bool 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
473void 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
void setDefaultPollingPeriod(uint32_t msec)
setDefaultPollingPeriod Change the default polling period to call TimerHit() function in the driver.
void setDriverInterface(uint32_t value)
setInterface Set driver interface. By default the driver interface is set to GENERAL_DEVICE....
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 getDriverInterface() const
void addDebugControl()
Add Debug control to the driver.
INDI::PropertyNumber GuideNSNP
INDI::PropertyNumber GuideWENP
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.
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.
void setPierSide(TelescopePierSide side)
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....
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.
INDI::PropertySwitch SlewRateSP
#define currentRA
Definition ieq45.cpp:47
@ ISS_OFF
Definition indiapi.h:149
@ ISS_ON
Definition indiapi.h:150
@ IP_RW
Definition indiapi.h:184
IPState
Property state.
Definition indiapi.h:158
@ IPS_BUSY
Definition indiapi.h:161
@ IPS_IDLE
Definition indiapi.h:159
@ IPS_OK
Definition indiapi.h:160
@ ISR_1OFMANY
Definition indiapi.h:171
@ AXIS_DE
@ AXIS_RA
INDI_DIR_WE
INDI_DIR_NS
int tty_write(int fd, const char *buf, int nbytes, int *nbytes_written)
Writes a buffer to fd.
Definition indicom.c:418
int tty_read(int fd, char *buf, int nbytes, int timeout, int *nbytes_read)
read buffer from terminal
Definition indicom.c:548
int tty_write_string(int fd, const char *buf, int *nbytes_written)
Writes a null terminated string to fd.
Definition indicom.c:473
void tty_error_msg(int err_code, char *err_msg, int err_msg_len)
Retrieve the tty error message.
Definition indicom.c:1243
int 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:130
int tty_nread_section(int fd, char *buf, int nsize, char stop_char, int timeout, int *nbytes_read)
read buffer from terminal with a delimiter
Definition indicom.c:759
Implementations for common driver routines.
@ TTY_OK
Definition indicom.h:54
double get_local_sidereal_time(double longitude)
get_local_sidereal_time Returns local sidereal 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:490
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:376
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:398
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:453
#define INDI_UNUSED(x)
Definition indidevapi.h:132
void IDSetNumber(const INumberVectorProperty *nvp, const char *fmt,...)
#define MAXRBUF
Definition indidriver.c:52
int IUUpdateNumber(INumberVectorProperty *nvp, double values[], char *names[], int n)
Update all numbers in a number vector property.
#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 getDeviceName
uint8_t cmd[4]
Definition pwc-ioctl.h:2
char name[MAXINDINAME]
Definition indiapi.h:321