1 /*
2  * Hamlib Rotator backend - SPID Rot1Prog & Rot2Prog
3  * Copyright (c) 2009-2011 by Norvald H. Ryeng, LA6YKA
4  *
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 
22 #ifdef HAVE_CONFIG_H
23 #include "config.h"
24 #endif
25 
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <unistd.h>
30 #include <math.h>
31 
32 #include "hamlib/rotator.h"
33 #include "serial.h"
34 #include "misc.h"
35 #include "register.h"
36 
37 #include "spid.h"
38 
39 #define TOK_AZRES 1
40 #define TOK_ELRES 2
41 
42 struct spid_rot2prog_priv_data
43 {
44     int az_resolution;
45     int el_resolution;
46 };
47 
spid_rot_init(ROT * rot)48 static int spid_rot_init(ROT *rot)
49 {
50     rig_debug(RIG_DEBUG_TRACE, "%s called\n", __func__);
51 
52     if (!rot || !rot->caps)
53     {
54         return -RIG_EINVAL;
55     }
56 
57     if (rot->caps->rot_model == ROT_MODEL_SPID_ROT2PROG ||
58             rot->caps->rot_model == ROT_MODEL_SPID_MD01_ROT2PROG)
59     {
60         struct spid_rot2prog_priv_data *priv;
61 
62         priv = (struct spid_rot2prog_priv_data *)malloc(sizeof(struct
63                 spid_rot2prog_priv_data));
64 
65         if (!priv)
66         {
67             return -RIG_ENOMEM;
68         }
69 
70         rot->state.priv = (void *)priv;
71 
72         priv->az_resolution = 0;
73         priv->el_resolution = 0;
74     }
75 
76     return RIG_OK;
77 }
78 
spid_rot_cleanup(ROT * rot)79 static int spid_rot_cleanup(ROT *rot)
80 {
81     rig_debug(RIG_DEBUG_TRACE, "%s called\n", __func__);
82 
83     if (!rot)
84     {
85         return -RIG_EINVAL;
86     }
87 
88     if (rot->state.priv && (rot->caps->rot_model == ROT_MODEL_SPID_ROT2PROG ||
89                             rot->caps->rot_model == ROT_MODEL_SPID_MD01_ROT2PROG))
90     {
91         free(rot->state.priv);
92     }
93 
94     rot->state.priv = NULL;
95 
96     return RIG_OK;
97 }
98 
spid_get_conf(ROT * rot,token_t token,char * val)99 static int spid_get_conf(ROT *rot, token_t token, char *val)
100 {
101     struct spid_rot2prog_priv_data *priv = (struct spid_rot2prog_priv_data *)
102                                            rot->state.priv;
103 
104     rig_debug(RIG_DEBUG_TRACE, "%s called %d\n", __func__, (int)token);
105 
106     if (rot->caps->rot_model != ROT_MODEL_SPID_ROT2PROG &&
107             rot->caps->rot_model != ROT_MODEL_SPID_MD01_ROT2PROG)
108     {
109         return -RIG_EINVAL;
110     }
111 
112     switch (token)
113     {
114     case TOK_AZRES:
115         sprintf(val, "%d", priv->az_resolution);
116         break;
117 
118     case TOK_ELRES:
119         sprintf(val, "%d", priv->el_resolution);
120         break;
121 
122     default:
123         return -RIG_EINVAL;
124     }
125 
126     return RIG_OK;
127 }
128 
spid_set_conf(ROT * rot,token_t token,const char * val)129 static int spid_set_conf(ROT *rot, token_t token, const char *val)
130 {
131     struct spid_rot2prog_priv_data *priv = (struct spid_rot2prog_priv_data *)
132                                            rot->state.priv;
133 
134     rig_debug(RIG_DEBUG_TRACE, "%s: called %d=%s\n", __func__, (int)token, val);
135 
136     if (rot->caps->rot_model != ROT_MODEL_SPID_ROT2PROG &&
137             rot->caps->rot_model != ROT_MODEL_SPID_MD01_ROT2PROG)
138     {
139         return -RIG_EINVAL;
140     }
141 
142     switch (token)
143     {
144     case TOK_AZRES:
145         priv->az_resolution = atoi(val);
146         break;
147 
148     case TOK_ELRES:
149         priv->el_resolution = atoi(val);
150         break;
151 
152     default:
153         return -RIG_EINVAL;
154     }
155 
156     return RIG_OK;
157 }
158 
spid_rot1prog_rot_set_position(ROT * rot,azimuth_t az,elevation_t el)159 static int spid_rot1prog_rot_set_position(ROT *rot, azimuth_t az,
160         elevation_t el)
161 {
162     struct rot_state *rs = &rot->state;
163     int retval;
164     char cmdstr[13];
165     unsigned int u_az;
166 
167     rig_debug(RIG_DEBUG_TRACE, "%s called: %f %f\n", __func__, az, el);
168 
169     u_az = 360 + az;
170 
171     cmdstr[0] = 0x57;                       /* S   */
172     cmdstr[1] = 0x30 + u_az / 100;          /* H1  */
173     cmdstr[2] = 0x30 + (u_az % 100) / 10;   /* H2  */
174     cmdstr[3] = 0x30 + (u_az % 10);         /* H3  */
175     cmdstr[4] = 0x30;                       /* H4  */
176     cmdstr[5] = 0x00;                       /* PH  */
177     cmdstr[6] = 0x00;                       /* V1  */
178     cmdstr[7] = 0x00;                       /* V2  */
179     cmdstr[8] = 0x00;                       /* V3  */
180     cmdstr[9] = 0x00;                       /* V4  */
181     cmdstr[10] = 0x00;                      /* PV  */
182     cmdstr[11] = 0x2F;                      /* K   */
183     cmdstr[12] = 0x20;                      /* END */
184 
185     retval = write_block(&rs->rotport, cmdstr, 13);
186 
187     if (retval != RIG_OK)
188     {
189         return retval;
190     }
191 
192     return RIG_OK;
193 }
194 
spid_rot2prog_rot_set_position(ROT * rot,azimuth_t az,elevation_t el)195 static int spid_rot2prog_rot_set_position(ROT *rot, azimuth_t az,
196         elevation_t el)
197 {
198     struct rot_state *rs = &rot->state;
199     struct spid_rot2prog_priv_data *priv = (struct spid_rot2prog_priv_data *)
200                                            rs->priv;
201     int retval;
202     int retry_read = 0;
203     char cmdstr[13];
204     unsigned int u_az, u_el;
205 
206     rig_debug(RIG_DEBUG_TRACE, "%s called: %f %f\n", __func__, az, el);
207 
208     if (!priv->az_resolution || !priv->el_resolution)
209     {
210         do
211         {
212             retval = write_block(&rs->rotport,
213                                  "\x57\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1F\x20", 13);
214 
215             if (retval != RIG_OK)
216             {
217                 return retval;
218             }
219 
220             memset(cmdstr, 0, 12);
221             retval = read_block(&rs->rotport, cmdstr, 12);
222         }
223         while (retval < 0 && retry_read++ < rot->state.rotport.retry);
224 
225         if (retval < 0)
226         {
227             return retval;
228         }
229     }
230     else
231     {
232         cmdstr[5] = priv->az_resolution;    /* PH  */
233         cmdstr[10] = priv->el_resolution;   /* PV  */
234     }
235 
236     u_az = cmdstr[5] * (360 + az);
237     u_el = cmdstr[10] * (360 + el);
238 
239     cmdstr[0] = 0x57;                       /* S   */
240     cmdstr[1] = 0x30 + u_az / 1000;         /* H1  */
241     cmdstr[2] = 0x30 + (u_az % 1000) / 100; /* H2  */
242     cmdstr[3] = 0x30 + (u_az % 100) / 10;   /* H3  */
243     cmdstr[4] = 0x30 + (u_az % 10);         /* H4  */
244 
245     cmdstr[6] = 0x30 + u_el / 1000;         /* V1  */
246     cmdstr[7] = 0x30 + (u_el % 1000) / 100; /* V2  */
247     cmdstr[8] = 0x30 + (u_el % 100) / 10;   /* V3  */
248     cmdstr[9] = 0x30 + (u_el % 10);         /* V4  */
249 
250     cmdstr[11] = 0x2F;                      /* K   */
251     cmdstr[12] = 0x20;                      /* END */
252 
253     retval = write_block(&rs->rotport, cmdstr, 13);
254 
255     if (retval != RIG_OK)
256     {
257         return retval;
258     }
259 
260     /* Unlike the original Rot2Prog, MD-01 and MD-02 return the position
261        after receiving the set position command. */
262     if (rot->caps->rot_model == ROT_MODEL_SPID_MD01_ROT2PROG)
263     {
264         retry_read = 0;
265 
266         do
267         {
268             retval = read_block(&rs->rotport, cmdstr, 12);
269         }
270         while ((retval < 0) && (retry_read++ < rot->state.rotport.retry));
271     }
272 
273     return RIG_OK;
274 }
275 
spid_rot_get_position(ROT * rot,azimuth_t * az,elevation_t * el)276 static int spid_rot_get_position(ROT *rot, azimuth_t *az, elevation_t *el)
277 {
278     struct rot_state *rs = &rot->state;
279     int retval;
280     int retry_read = 0;
281     char posbuf[12];
282 
283     rig_debug(RIG_DEBUG_TRACE, "%s called\n", __func__);
284 
285     do
286     {
287         retval = write_block(&rs->rotport,
288                              "\x57\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1F\x20", 13);
289 
290         if (retval != RIG_OK)
291         {
292             return retval;
293         }
294 
295         memset(posbuf, 0, 12);
296 
297         if (rot->caps->rot_model == ROT_MODEL_SPID_ROT1PROG)
298         {
299             retval = read_block(&rs->rotport, posbuf, 5);
300         }
301         else if (rot->caps->rot_model == ROT_MODEL_SPID_ROT2PROG ||
302                  rot->caps->rot_model == ROT_MODEL_SPID_MD01_ROT2PROG)
303         {
304             retval = read_block(&rs->rotport, posbuf, 12);
305         }
306         else
307         {
308             retval = -RIG_EINVAL;
309         }
310     }
311     while (retval < 0 && retry_read++ < rot->state.rotport.retry);
312 
313     if (retval < 0)
314     {
315         return retval;
316     }
317 
318     *az  = posbuf[1] * 100;
319     *az += posbuf[2] * 10;
320     *az += posbuf[3];
321 
322     if (rot->caps->rot_model == ROT_MODEL_SPID_ROT2PROG ||
323             rot->caps->rot_model == ROT_MODEL_SPID_MD01_ROT2PROG)
324     {
325         *az += posbuf[4] / 10.0;
326     }
327 
328     *az -= 360;
329 
330     *el = 0.0;
331 
332     if (rot->caps->rot_model == ROT_MODEL_SPID_ROT2PROG ||
333             rot->caps->rot_model == ROT_MODEL_SPID_MD01_ROT2PROG)
334     {
335         *el  = posbuf[6] * 100;
336         *el += posbuf[7] * 10;
337         *el += posbuf[8];
338         *el += posbuf[9] / 10.0;
339         *el -= 360;
340     }
341 
342     rig_debug(RIG_DEBUG_TRACE, "%s: (az, el) = (%.1f, %.1f)\n",
343               __func__, *az, *el);
344 
345     return RIG_OK;
346 }
347 
spid_rot_stop(ROT * rot)348 static int spid_rot_stop(ROT *rot)
349 {
350     struct rot_state *rs = &rot->state;
351     int retval;
352     int retry_read = 0;
353     char posbuf[12];
354 
355     rig_debug(RIG_DEBUG_TRACE, "%s called\n", __func__);
356 
357     do
358     {
359         retval = write_block(&rs->rotport,
360                              "\x57\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0F\x20", 13);
361 
362         if (retval != RIG_OK)
363         {
364             return retval;
365         }
366 
367         memset(posbuf, 0, 12);
368 
369         if (rot->caps->rot_model == ROT_MODEL_SPID_ROT1PROG)
370         {
371             retval = read_block(&rs->rotport, posbuf, 5);
372         }
373         else if (rot->caps->rot_model == ROT_MODEL_SPID_ROT2PROG ||
374                  rot->caps->rot_model == ROT_MODEL_SPID_MD01_ROT2PROG)
375         {
376             retval = read_block(&rs->rotport, posbuf, 12);
377         }
378     }
379     while (retval < 0 && retry_read++ < rot->state.rotport.retry);
380 
381     if (retval < 0)
382     {
383         return retval;
384     }
385 
386     return RIG_OK;
387 }
388 
spid_md01_rot2prog_rot_move(ROT * rot,int direction,int speed)389 static int spid_md01_rot2prog_rot_move(ROT *rot, int direction, int speed)
390 {
391     struct rot_state *rs = &rot->state;
392     char dir = 0x00;
393     int retval;
394     char cmdstr[13];
395 
396     rig_debug(RIG_DEBUG_TRACE, "%s called\n", __func__);
397 
398     switch (direction)
399     {
400     case ROT_MOVE_UP:
401         dir = 0x04;
402         break;
403 
404     case ROT_MOVE_DOWN:
405         dir = 0x08;
406         break;
407 
408     case ROT_MOVE_LEFT:
409         dir = 0x01;
410         break;
411 
412     case ROT_MOVE_RIGHT:
413         dir = 0x02;
414         break;
415     }
416 
417     cmdstr[0] = 0x57;                       /* S   */
418     cmdstr[1] = dir;                        /* H1  */
419     cmdstr[2] = 0x00;                       /* H2  */
420     cmdstr[3] = 0x00;                       /* H3  */
421     cmdstr[4] = 0x00;                       /* H4  */
422     cmdstr[6] = 0x00;                       /* V1  */
423     cmdstr[7] = 0x00;                       /* V2  */
424     cmdstr[8] = 0x00;                       /* V3  */
425     cmdstr[9] = 0x00;                       /* V4  */
426     cmdstr[11] = 0x14;                      /* K   */
427     cmdstr[12] = 0x20;                      /* END */
428 
429     /* The rotator must be stopped before changing directions. Since
430        we don't know which direction we're already moving in (if
431        moving at all), always send the stop command first. */
432     spid_rot_stop(rot);
433 
434     retval = write_block(&rs->rotport, cmdstr, 13);
435     return retval;
436 }
437 
438 const struct confparams spid_cfg_params[] =
439 {
440     {
441         TOK_AZRES, "az_resolution", "Azimuth resolution", "Number of pulses per degree, 0 = auto sense",
442         "0", RIG_CONF_NUMERIC, { .n = { 0, 0xff, 1 } }
443     },
444     {
445         TOK_ELRES, "el_resolution", "Eleveation resolution", "Number of pulses per degree, 0 = auto sense",
446         "0", RIG_CONF_NUMERIC, { .n = { 0, 0xff, 1 } }
447     },
448     { RIG_CONF_END, NULL, }
449 };
450 
451 const struct rot_caps spid_rot1prog_rot_caps =
452 {
453     ROT_MODEL(ROT_MODEL_SPID_ROT1PROG),
454     .model_name =        "Rot1Prog",
455     .mfg_name =          "SPID",
456     .version =           "20191208.0",
457     .copyright =         "LGPL",
458     .status =            RIG_STATUS_STABLE,
459     .rot_type =          ROT_TYPE_AZIMUTH,
460     .port_type =         RIG_PORT_SERIAL,
461     .serial_rate_min =   1200,
462     .serial_rate_max =   1200,
463     .serial_data_bits =  8,
464     .serial_stop_bits =  1,
465     .serial_parity =     RIG_PARITY_NONE,
466     .serial_handshake =  RIG_HANDSHAKE_NONE,
467     .write_delay =       0,
468     .post_write_delay =  0,
469     .timeout =           400,
470     .retry =             3,
471 
472     .min_az =            -180.0,
473     .max_az =            540.0,
474     .min_el =            0.0,
475     .max_el =            0.0,
476 
477     .cfgparams =         spid_cfg_params,
478     .get_conf =          spid_get_conf,
479     .set_conf =          spid_set_conf,
480 
481     .rot_init =          spid_rot_init,
482     .rot_cleanup =       spid_rot_cleanup,
483     .get_position =      spid_rot_get_position,
484     .set_position =      spid_rot1prog_rot_set_position,
485     .stop =              spid_rot_stop,
486 };
487 
488 const struct rot_caps spid_rot2prog_rot_caps =
489 {
490     ROT_MODEL(ROT_MODEL_SPID_ROT2PROG),
491     .model_name =        "Rot2Prog",
492     .mfg_name =          "SPID",
493     .version =           "20191208.0",
494     .copyright =         "LGPL",
495     .status =            RIG_STATUS_STABLE,
496     .rot_type =          ROT_TYPE_AZEL,
497     .port_type =         RIG_PORT_SERIAL,
498     .serial_rate_min =   600,
499     .serial_rate_max =   600,
500     .serial_data_bits =  8,
501     .serial_stop_bits =  1,
502     .serial_parity =     RIG_PARITY_NONE,
503     .serial_handshake =  RIG_HANDSHAKE_NONE,
504     .write_delay =       0,
505     .post_write_delay =  0,
506     .timeout =           400,
507     .retry =             3,
508 
509     .min_az =            -180.0,
510     .max_az =            540.0,
511     .min_el =            -20.0,
512     .max_el =            210.0,
513 
514     .cfgparams =         spid_cfg_params,
515     .get_conf =          spid_get_conf,
516     .set_conf =          spid_set_conf,
517 
518     .rot_init =          spid_rot_init,
519     .rot_cleanup =       spid_rot_cleanup,
520     .get_position =      spid_rot_get_position,
521     .set_position =      spid_rot2prog_rot_set_position,
522     .stop =              spid_rot_stop,
523 };
524 
525 const struct rot_caps spid_md01_rot2prog_rot_caps =
526 {
527     ROT_MODEL(ROT_MODEL_SPID_MD01_ROT2PROG),
528     .model_name =        "MD-01/02 (ROT2 mode)",
529     .mfg_name =          "SPID",
530     .version =           "20191208.0",
531     .copyright =         "LGPL",
532     .status =            RIG_STATUS_STABLE,
533     .rot_type =          ROT_TYPE_AZEL,
534     .port_type =         RIG_PORT_SERIAL,
535     .serial_rate_min =   600,
536     .serial_rate_max =   460800,
537     .serial_data_bits =  8,
538     .serial_stop_bits =  1,
539     .serial_parity =     RIG_PARITY_NONE,
540     .serial_handshake =  RIG_HANDSHAKE_NONE,
541     .write_delay =       0,
542     .post_write_delay =  0,
543     .timeout =           400,
544     .retry =             3,
545 
546     .min_az =            -180.0,
547     .max_az =            540.0,
548     .min_el =            -20.0,
549     .max_el =            210.0,
550 
551     .cfgparams =         spid_cfg_params,
552     .get_conf =          spid_get_conf,
553     .set_conf =          spid_set_conf,
554 
555     .rot_init =          spid_rot_init,
556     .rot_cleanup =       spid_rot_cleanup,
557     .get_position =      spid_rot_get_position,
558     .set_position =      spid_rot2prog_rot_set_position,
559     .move =              spid_md01_rot2prog_rot_move,
560     .stop =              spid_rot_stop,
561 };
562 
DECLARE_INITROT_BACKEND(spid)563 DECLARE_INITROT_BACKEND(spid)
564 {
565     rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__);
566 
567     rot_register(&spid_rot1prog_rot_caps);
568     rot_register(&spid_rot2prog_rot_caps);
569     rot_register(&spid_md01_rot2prog_rot_caps);
570 
571     return RIG_OK;
572 }
573