1 /*
2  *  Hamlib Rotator backend - Celestron
3  *  Copyright (c) 2011 by Stephane Fillod
4  *
5  *   This library is free software; you can redistribute it and/or
6  *   modify it under the terms of the GNU Lesser General Public
7  *   License as published by the Free Software Foundation; either
8  *   version 2.1 of the License, or (at your option) any later version.
9  *
10  *   This library is distributed in the hope that it will be useful,
11  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  *   Lesser General Public License for more details.
14  *
15  *   You should have received a copy of the GNU Lesser General Public
16  *   License along with this library; if not, write to the Free Software
17  *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
18  *
19  */
20 
21 #ifdef HAVE_CONFIG_H
22 #include "config.h"
23 #endif
24 
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <unistd.h>
29 #include <math.h>
30 #include <ctype.h>
31 #include <stddef.h>
32 #include <stdint.h>
33 
34 #include "hamlib/rotator.h"
35 #include "serial.h"
36 #include "misc.h"
37 #include "register.h"
38 
39 #include "rot_ioptron.h"
40 
41 #define ACK "#"
42 #define ACK1 '1'
43 
44 #define BUFSZ 128
45 
46 /**
47  * ioptron_transaction
48  *
49  * cmdstr - Command to be sent to the rig.
50  * data - Buffer for reply string.  Can be NULL, indicating that no reply is
51  *        is needed, but answer will still be read.
52  * data_len - in: Size of buffer. It is the caller's responsibily to provide
53  *            a large enough buffer for all possible replies for a command.
54  *
55  *  COMMANDS  note: as of 12/2018 a mixture of V2 and V3
56  * | TTTTTTTT(T) .01 arc seconds
57  * | alt- sign with 8 digits, az - 9 digits                           |
58  * | Command     | Attribute | Return value | Description              |
59  * -------------------------------------------------------------------|
60  * | :GAC# | .01 arcsec | sTTTTTTTTTTTTTTTTT# | gets alt(s8), az(9)   |
61  * | :SzTTTTTTTTT# | .01 arcsec | '1' == OK | Set Target azimuth      |
62  * | :SasTTTTTTTT# |.01 arcsec | '1' == OK | Set Target elevation     |
63  * | :Q#         | -        | '1' == OK    | Halt all slewing         |
64  * | :ST0#       | -        | '1' == OK    | Halt tracking            |
65  * | :MS#        | -        | '1' == OK    | GoTo Target              |
66  * |
67  * returns:
68  *   RIG_OK  -  if no error occurred.
69  *   RIG_EIO  -  if an I/O error occurred while sending/receiving data.
70  *   RIG_ETIMEOUT  -  if timeout expires without any characters received.
71  */
72 
73 static int
ioptron_transaction(ROT * rot,const char * cmdstr,char * data,size_t data_len)74 ioptron_transaction(ROT *rot, const char *cmdstr,
75                     char *data, size_t data_len)
76 {
77     struct rot_state *rs;
78     int retval;
79     int retry_read = 0;
80     char replybuf[BUFSZ];
81 
82     rs = &rot->state;
83 
84 transaction_write:
85 
86     rig_flush(&rs->rotport);
87 
88     if (cmdstr)
89     {
90         retval = write_block(&rs->rotport, cmdstr, strlen(cmdstr));
91 
92         if (retval != RIG_OK)
93         {
94             goto transaction_quit;
95         }
96     }
97 
98     /** Always read the reply to know whether the cmd went OK */
99     if (!data)
100     {
101         data = replybuf;
102     }
103 
104     if (!data_len)
105     {
106         data_len = BUFSZ;
107     }
108 
109     /** the answer */
110     memset(data, 0, data_len);
111     retval = read_string(&rs->rotport, data, data_len, ACK, strlen(ACK));
112 
113     if (retval < 0)
114     {
115         if (retry_read++ < rot->state.rotport.retry)
116         {
117             goto transaction_write;
118         }
119 
120         goto transaction_quit;
121     }
122 
123     /** check for acknowledge */
124     if (retval < 1)
125     {
126         rig_debug(RIG_DEBUG_ERR, "%s: unexpected response, len %d: '%s'\n", __func__,
127                   retval, data);
128         return -RIG_EPROTO;
129     }
130 
131     retval = RIG_OK;
132 transaction_quit:
133     return retval;
134 }
135 
136 /**
137  * Opens the Port and sets all needed parameters for operation
138  * as of 12/2018 initiates mount with V3 :MountInfo#
139  */
ioptron_open(ROT * rot)140 static int ioptron_open(ROT *rot)
141 {
142     rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__);
143 
144     return ioptron_transaction(rot, ":Mountinfo#", NULL, 0);
145 }
146 
147 /** sets mount position, requires 4 steps
148  * set azmiuth
149  * set altitude
150  * goto set
151  * stop tracking - mount starts tracking after goto
152  */
153 static int
ioptron_set_position(ROT * rot,azimuth_t az,elevation_t el)154 ioptron_set_position(ROT *rot, azimuth_t az, elevation_t el)
155 {
156     char cmdstr[32];
157     char retbuf[10];
158     int retval;
159     float faz, fel;
160 
161     rig_debug(RIG_DEBUG_TRACE, "%s called: %f %f\n", __func__, az, el);
162 
163     /* units .01 arc sec */
164     faz = az * 360000;
165     fel = el * 360000;
166     /* set azmiuth, returns '1" if OK */
167     sprintf(cmdstr, ":Sz%09.0f#", faz);
168     retval = ioptron_transaction(rot, cmdstr, retbuf, sizeof(retbuf));
169 
170     if (retval != RIG_OK || retbuf[0] != ACK1)
171     {
172         return  -RIG_EPROTO;
173     }
174 
175     /* set altitude, returns '1" if OK */
176     sprintf(cmdstr, ":Sa+%08.0f#", fel);
177     retval = ioptron_transaction(rot, cmdstr, retbuf, sizeof(retbuf));
178 
179     if (retval != RIG_OK || retbuf[0] != ACK1)
180     {
181         return  -RIG_EPROTO;
182     }
183 
184     /* move to set target, V2 command, returns '1" if OK */
185     sprintf(cmdstr, ":MS#"); //
186     retval = ioptron_transaction(rot, cmdstr, retbuf, sizeof(retbuf));
187 
188     if (retval != RIG_OK || retbuf[0] != ACK1)
189     {
190         return  -RIG_EPROTO;
191     }
192 
193     /* stop tracking, V2 command, returns '1" if OK */
194     sprintf(cmdstr, ":ST0#");
195     retval = ioptron_transaction(rot, cmdstr, retbuf, sizeof(retbuf));
196 
197     if (retval != RIG_OK || retbuf[0] != ACK1)
198     {
199         return  -RIG_EPROTO;
200     }
201 
202     return retval;
203 }
204 
205 /**  gets current position  */
206 static int
ioptron_get_position(ROT * rot,azimuth_t * az,elevation_t * el)207 ioptron_get_position(ROT *rot, azimuth_t *az, elevation_t *el)
208 {
209     char posbuf[32];
210     int retval;
211     float w;
212 
213     rig_debug(RIG_DEBUG_TRACE, "%s called\n", __func__);
214 
215     /** Get Az-Alt */
216     retval = ioptron_transaction(rot, ":GAC#", posbuf, sizeof(posbuf));
217 
218     if (retval != RIG_OK || strlen(posbuf) < 18)
219     {
220         return retval < 0 ? retval : -RIG_EPROTO;
221     }
222 
223     if (sscanf(posbuf, "%9f", &w) != 1)
224     {
225         return -RIG_EPROTO;
226     }
227 
228     /** convert from .01 arc sec to degrees  */
229     *el = ((elevation_t)w / 360000.);
230 
231     if (sscanf(posbuf + 9, "%9f", &w) != 1)
232     {
233         return -RIG_EPROTO;
234     }
235 
236     *az = ((azimuth_t)w / 360000.);
237 
238     rig_debug(RIG_DEBUG_TRACE, "%s: (az, el) = (%.1f, %.1f)\n",
239               __func__, *az, *el);
240 
241     return RIG_OK;
242 }
243 
244 /** stop everything  **/
245 static int
ioptron_stop(ROT * rot)246 ioptron_stop(ROT *rot)
247 {
248     int retval;
249     char retbuf[10];
250 
251     rig_debug(RIG_DEBUG_TRACE, "%s called\n", __func__);
252 
253     /** stop slew, returns "1" if OK */
254     retval = ioptron_transaction(rot, ":Q#", retbuf, 10);
255 
256     if (retval != RIG_OK || retbuf[0] != ACK1)
257     {
258         return  -RIG_EPROTO;
259     }
260 
261     /** stops tracking returns "1" if OK */
262     retval = ioptron_transaction(rot, ":ST0#", retbuf, 10);
263 
264     if (retval != RIG_OK || retbuf[0] != ACK1)
265     {
266         return  -RIG_EPROTO;
267     }
268 
269     return retval;
270 }
271 
272 /** get mount type code, initializes mount */
273 static const char *
ioptron_get_info(ROT * rot)274 ioptron_get_info(ROT *rot)
275 {
276     static char info[16];
277     char str[6];
278     int retval;
279 
280     rig_debug(RIG_DEBUG_TRACE, "%s called\n", __func__);
281 
282     retval = ioptron_transaction(rot, ":MountInfo#", str, sizeof(str));
283 
284     rig_debug(RIG_DEBUG_TRACE, "retval, RIG_OK str %d  %d  %str\n", retval, RIG_OK,
285               str);
286 
287     sprintf(info, "MountInfo %s", str);
288 
289     return info;
290 }
291 
292 
293 
294 /** *************************************************************************
295  *
296  * ioptron mount capabilities.
297  *
298  * Protocol documentation:
299  *  from ioptron:
300  *    RS232-Command_Language  pdf
301  * note that iOptron is currently (12/2018) using a mix of V2 and V3 commands :(
302  */
303 
304 const struct rot_caps ioptron_rot_caps =
305 {
306     ROT_MODEL(ROT_MODEL_IOPTRON),
307     .model_name =     "iOptron",
308     .mfg_name =       "iOptron",
309     .version =        "20191209.0",
310     .copyright =      "LGPL",
311     .status =         RIG_STATUS_ALPHA,
312     .rot_type =       ROT_TYPE_AZEL,
313     .port_type =      RIG_PORT_SERIAL,
314     .serial_rate_min  = 9600,
315     .serial_rate_max  = 9600,
316     .serial_data_bits = 8,
317     .serial_stop_bits = 1,
318     .serial_parity    = RIG_PARITY_NONE,
319     .serial_handshake = RIG_HANDSHAKE_NONE,
320     .write_delay      = 0,
321     .post_write_delay = 0,
322     .timeout          = 1000, /* worst case scenario 3500 */
323     .retry            = 1,
324 
325     .min_az =     0.0,
326     .max_az =     360.0,
327     .min_el =     0.0,
328     .max_el =     180.0,
329 
330     .rot_open     = ioptron_open,
331     .get_position = ioptron_get_position,
332     .set_position = ioptron_set_position,
333     .stop         = ioptron_stop,
334     .get_info     = ioptron_get_info,
335 };
336 
337 /* ****************************************************************** */
338 
DECLARE_INITROT_BACKEND(ioptron)339 DECLARE_INITROT_BACKEND(ioptron)
340 {
341     rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__);
342 
343     rot_register(&ioptron_rot_caps);
344 
345     return RIG_OK;
346 }
347 
348 /* ****************************************************************** */
349 /* end of file */
350 
351