1 /*
2  * Hamlib backend library for the DCU rotor command set.
3  *
4  * rotorez.c - (C) Nate Bargmann 2003,2009,2010 (n0nb at arrl.net)
5  *
6  * This shared library provides an API for communicating
7  * via serial interface to a Hy-Gain or Yaesu rotor using
8  * the Idiom Press Rotor-EZ or RotorCard interface.  It also
9  * supports the Hy-Gain DCU-1, and DF9GR ERC.
10  *
11  * Rotor-EZ is a trademark of Idiom Press
12  * Hy-Gain is a trademark of MFJ Enterprises
13  *
14  * Tested on a HAM-IV with the Rotor-EZ V1.4S interface installed.
15  *
16  *
17  *   This library is free software; you can redistribute it and/or
18  *   modify it under the terms of the GNU Lesser General Public
19  *   License as published by the Free Software Foundation; either
20  *   version 2.1 of the License, or (at your option) any later version.
21  *
22  *   This library is distributed in the hope that it will be useful,
23  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
24  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
25  *   Lesser General Public License for more details.
26  *
27  *   You should have received a copy of the GNU Lesser General Public
28  *   License along with this library; if not, write to the Free Software
29  *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
30  *
31  */
32 
33 #ifdef HAVE_CONFIG_H
34 #include "config.h"
35 #endif
36 
37 #include <stdio.h>
38 #include <stdlib.h>          /* Standard library definitions */
39 #include <string.h>          /* String function definitions */
40 #include <unistd.h>          /* UNIX standard function definitions */
41 #include <ctype.h>            /* for isdigit function */
42 
43 #include "hamlib/rotator.h"
44 #include "serial.h"
45 #include "misc.h"
46 #include "register.h"
47 #include "iofunc.h"
48 
49 #include "rotorez.h"
50 
51 
52 /*
53  * API local implementation
54  *
55  */
56 
57 static int rotorez_rot_init(ROT *rot);
58 static int rotorez_rot_cleanup(ROT *rot);
59 
60 static int rotorez_rot_set_position(ROT *rot, azimuth_t azimuth,
61                                     elevation_t elevation);
62 static int rotorez_rot_get_position(ROT *rot, azimuth_t *azimuth,
63                                     elevation_t *elevation);
64 static int erc_rot_get_position(ROT *rot, azimuth_t *azimuth,
65                                 elevation_t *elevation);
66 static int rt21_rot_get_position(ROT *rot, azimuth_t *azimuth,
67                                  elevation_t *elevation);
68 static int rt21_rot_set_position(ROT *rot, azimuth_t azimuth,
69                                  elevation_t elevation);
70 
71 static int rotorez_rot_reset(ROT *rot, rot_reset_t reset);
72 static int rotorez_rot_stop(ROT *rot);
73 static int dcu1_rot_stop(ROT *rot);
74 
75 static int rotorez_rot_set_conf(ROT *rot, token_t token, const char *val);
76 
77 static const char *rotorez_rot_get_info(ROT *rot);
78 
79 
80 /*
81  * Private data structure
82  */
83 struct rotorez_rot_priv_data
84 {
85     azimuth_t az;
86 };
87 
88 /*
89  * Private helper function prototypes
90  */
91 
92 static int rotorez_send_priv_cmd(ROT *rot, const char *cmd);
93 static int rotorez_send_priv_cmd2(ROT *rot, const char *cmd);
94 static int rotorez_flush_buffer(ROT *rot);
95 
96 /*
97  * local configuration parameters
98  */
99 static const struct confparams rotorez_cfg_params[] =
100 {
101     {
102         TOK_ENDPT, "endpt", "Endpoint option", "Endpoint option",
103         NULL, RIG_CONF_CHECKBUTTON, { }
104     },
105     {
106         TOK_JAM, "jam", "Jam protection", "Jam protection",
107         NULL, RIG_CONF_CHECKBUTTON, { }
108     },
109     {
110         TOK_OVRSHT, "oversht", "Overshoot option", "Overshoot option",
111         NULL, RIG_CONF_CHECKBUTTON, { }
112     },
113     {
114         TOK_UNSTICK, "unstick", "Unstick option", "Unstick option",
115         NULL, RIG_CONF_CHECKBUTTON, { }
116     },
117     { RIG_CONF_END, NULL, }
118 };
119 
120 /* *************************************
121  *
122  * Separate model capabilities
123  *
124  * *************************************
125  */
126 
127 
128 /*
129  * Idiom Press Rotor-EZ enhanced rotor capabilities for
130  * Hy-Gain CD45, Ham-II, Ham-III, Ham IV and all TailTwister rotors
131  */
132 
133 const struct rot_caps rotorez_rot_caps =
134 {
135     ROT_MODEL(ROT_MODEL_ROTOREZ),
136     .model_name =       "Rotor-EZ",
137     .mfg_name =         "Idiom Press",
138     .version =          "20100214.0",
139     .copyright =        "LGPL",
140     .status =           RIG_STATUS_BETA,
141     .rot_type =         ROT_TYPE_OTHER,
142     .port_type =        RIG_PORT_SERIAL,
143     .serial_rate_min =  4800,
144     .serial_rate_max =  4800,
145     .serial_data_bits = 8,
146     .serial_stop_bits = 1,
147     .serial_parity =    RIG_PARITY_NONE,
148     .serial_handshake = RIG_HANDSHAKE_NONE,
149     .write_delay =      0,
150     .post_write_delay = 500,
151     .timeout =          1500,
152     .retry =            2,
153 
154     .min_az =           0,
155     .max_az =           360,
156     .min_el =           0,
157     .max_el =           0,
158 
159     .priv =             NULL,   /* priv */
160     .cfgparams =        rotorez_cfg_params,
161 
162     .rot_init =         rotorez_rot_init,
163     .rot_cleanup =      rotorez_rot_cleanup,
164     .set_position =     rotorez_rot_set_position,
165     .get_position =     rotorez_rot_get_position,
166     .stop =             rotorez_rot_stop,
167     .set_conf =         rotorez_rot_set_conf,
168     .get_info =         rotorez_rot_get_info,
169 
170 };
171 
172 
173 /*
174  * Idiom Press RotorCard enhanced rotor capabilities for
175  * Yaesu SDX and DXA series rotors
176  */
177 
178 const struct rot_caps rotorcard_rot_caps =
179 {
180     ROT_MODEL(ROT_MODEL_ROTORCARD),
181     .model_name =       "RotorCard",
182     .mfg_name =         "Idiom Press",
183     .version =          "20100214.0",
184     .copyright =        "LGPL",
185     .status =           RIG_STATUS_UNTESTED,
186     .rot_type =         ROT_TYPE_OTHER,
187     .port_type =        RIG_PORT_SERIAL,
188     .serial_rate_min =  4800,
189     .serial_rate_max =  4800,
190     .serial_data_bits = 8,
191     .serial_stop_bits = 1,
192     .serial_parity =    RIG_PARITY_NONE,
193     .serial_handshake = RIG_HANDSHAKE_NONE,
194     .write_delay =      0,
195     .post_write_delay = 500,
196     .timeout =          1500,
197     .retry =            2,
198 
199     .min_az =           0,
200     .max_az =           360,
201     .min_el =           0,
202     .max_el =           0,
203 
204     .priv =             NULL,   /* priv */
205     .cfgparams =        rotorez_cfg_params,
206 
207     .rot_init =         rotorez_rot_init,
208     .rot_cleanup =      rotorez_rot_cleanup,
209     .set_position =     rotorez_rot_set_position,
210     .get_position =     rotorez_rot_get_position,
211     .stop =             rotorez_rot_stop,
212     .set_conf =         rotorez_rot_set_conf,
213     .get_info =         rotorez_rot_get_info,
214 
215 };
216 
217 
218 /*
219  * Hy-Gain DCU-1/DCU-1X rotor capabilities
220  */
221 
222 const struct rot_caps dcu_rot_caps =
223 {
224     ROT_MODEL(ROT_MODEL_DCU),
225     .model_name =       "DCU-1/DCU-1X",
226     .mfg_name =         "Hy-Gain",
227     .version =          "20100823.0",
228     .copyright =        "LGPL",
229     .status =           RIG_STATUS_STABLE,
230     .rot_type =         ROT_TYPE_OTHER,
231     .port_type =        RIG_PORT_SERIAL,
232     .serial_rate_min =  4800,
233     .serial_rate_max =  4800,
234     .serial_data_bits = 8,
235     .serial_stop_bits = 1,
236     .serial_parity =    RIG_PARITY_NONE,
237     .serial_handshake = RIG_HANDSHAKE_NONE,
238     .write_delay =      0,
239     .post_write_delay = 500,
240     .timeout =          1500,
241     .retry =            2,
242 
243     .min_az =           0,
244     .max_az =           360,
245     .min_el =           0,
246     .max_el =           0,
247 
248     .priv =             NULL,   /* priv */
249 
250     .rot_init =         rotorez_rot_init,
251     .rot_cleanup =      rotorez_rot_cleanup,
252     .set_position =     rotorez_rot_set_position,
253     .stop =             dcu1_rot_stop,
254     .reset =            rotorez_rot_reset,
255     .get_info =         rotorez_rot_get_info,
256 
257 };
258 
259 
260 /*
261  * Rotor capabilities for DF9GR ERC
262  *
263  * TODO: Learn of additional capabilities of the ERC
264  */
265 
266 const struct rot_caps erc_rot_caps =
267 {
268     ROT_MODEL(ROT_MODEL_ERC),
269     .model_name =       "ERC",
270     .mfg_name =         "DF9GR",
271     .version =          "20100823.2",      /* second revision on 23 Aug 2010 */
272     .copyright =        "LGPL",
273     .status =           RIG_STATUS_ALPHA,
274     .rot_type =         ROT_TYPE_OTHER,
275     .port_type =        RIG_PORT_SERIAL,
276     .serial_rate_min =  4800,
277     .serial_rate_max =  4800,
278     .serial_data_bits = 8,
279     .serial_stop_bits = 1,
280     .serial_parity =    RIG_PARITY_NONE,
281     .serial_handshake = RIG_HANDSHAKE_NONE,
282     .write_delay =      0,
283     .post_write_delay = 500,
284     .timeout =          1500,
285     .retry =            2,
286 
287     .min_az =           0,
288     .max_az =           360,
289     .min_el =           0,
290     .max_el =           0,
291 
292     .priv =             NULL,   /* priv */
293 //  .cfgparams =        rotorez_cfg_params,
294 
295     .rot_init =         rotorez_rot_init,
296     .rot_cleanup =      rotorez_rot_cleanup,
297     .set_position =     rotorez_rot_set_position,
298     .get_position =     erc_rot_get_position,
299     .stop =             dcu1_rot_stop,
300     .reset =            rotorez_rot_reset,
301 //  .stop =             rotorez_rot_stop,
302 //  .set_conf =         rotorez_rot_set_conf,
303     .get_info =         rotorez_rot_get_info,
304 
305 };
306 
307 
308 const struct rot_caps rt21_rot_caps =
309 {
310     ROT_MODEL(ROT_MODEL_RT21),
311     .model_name =       "RT-21",
312     .mfg_name =     "Green Heron",
313     .version =      "20210801.0",
314     .copyright =        "LGPL",
315     .status =       RIG_STATUS_STABLE,
316     .rot_type =     ROT_TYPE_OTHER,
317     .port_type =        RIG_PORT_SERIAL,
318     .serial_rate_min =  4800,
319     .serial_rate_max =  4800,
320     .serial_data_bits = 8,
321     .serial_stop_bits = 1,
322     .serial_parity =    RIG_PARITY_NONE,
323     .serial_handshake = RIG_HANDSHAKE_NONE,
324     .write_delay =      0,
325     .post_write_delay = 500,
326     .timeout =      1500,
327     .retry =        2,
328 
329     .min_az =       0,
330     .max_az =       359.9,
331     .min_el =       0,
332     .max_el =       90,
333 
334     .priv =         NULL,   /* priv */
335 //  .cfgparams =        rotorez_cfg_params,
336 
337     .rot_init =     rotorez_rot_init,
338     .rot_cleanup =      rotorez_rot_cleanup,
339     .set_position =     rt21_rot_set_position,
340     .get_position =     rt21_rot_get_position,
341 //  .stop =             rotorez_rot_stop,
342 //  .set_conf =         rotorez_rot_set_conf,
343 //  .get_info =         rotorez_rot_get_info,
344 
345 };
346 
347 
348 /* ************************************
349  *
350  * API functions
351  *
352  * ************************************
353  */
354 
355 
356 /*
357  * Initialize data structures
358  */
359 
rotorez_rot_init(ROT * rot)360 static int rotorez_rot_init(ROT *rot)
361 {
362     rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__);
363 
364     if (!rot)
365     {
366         return -RIG_EINVAL;
367     }
368 
369     rot->state.priv = (struct rotorez_rot_priv_data *)
370                       malloc(sizeof(struct rotorez_rot_priv_data));
371 
372     if (!rot->state.priv)
373     {
374         return -RIG_ENOMEM;
375     }
376 
377     rot->state.rotport.type.rig = RIG_PORT_SERIAL;
378 
379     ((struct rotorez_rot_priv_data *)rot->state.priv)->az = 0;
380 
381     return RIG_OK;
382 }
383 
384 /*
385  * Clean up allocated memory structures
386  */
387 
rotorez_rot_cleanup(ROT * rot)388 static int rotorez_rot_cleanup(ROT *rot)
389 {
390 
391     rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__);
392 
393     if (!rot)
394     {
395         return -RIG_EINVAL;
396     }
397 
398     if (rot->state.priv)
399     {
400         free(rot->state.priv);
401     }
402 
403     rot->state.priv = NULL;
404 
405     return RIG_OK;
406 }
407 
408 
409 /*
410  * Set position
411  * DCU protocol supports azimuth only--elevation is ignored
412  * Range is converted to an integer string, 0 to 360 degrees
413  */
414 
rotorez_rot_set_position(ROT * rot,azimuth_t azimuth,elevation_t elevation)415 static int rotorez_rot_set_position(ROT *rot, azimuth_t azimuth,
416                                     elevation_t elevation)
417 {
418     char cmdstr[8];
419     char execstr[5] = "AM1;";
420     int err;
421 
422     rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__);
423 
424     if (!rot)
425     {
426         return -RIG_EINVAL;
427     }
428 
429     if (azimuth < 0 || azimuth > 360)
430     {
431         return -RIG_EINVAL;
432     }
433 
434     if (azimuth > 359.4999)     /* DCU-1 compatibility */
435     {
436         azimuth = 0;
437     }
438 
439     sprintf(cmdstr, "AP1%03.0f;", azimuth);     /* Target bearing */
440     err = rotorez_send_priv_cmd(rot, cmdstr);
441 
442     if (err != RIG_OK)
443     {
444         return err;
445     }
446 
447     err = rotorez_send_priv_cmd(rot, execstr);  /* Execute command */
448 
449     if (err != RIG_OK)
450     {
451         return err;
452     }
453 
454     return RIG_OK;
455 }
456 
457 
458 /*
459  * RT-21 Set position.
460  *
461  * RT-21 has a custom command to set azimuth to a precision of 1/10 of a
462  * degree--"AP1XXX.Y\r;".  XXX must be zero padded.  The '\r' causes the
463  * command to be executed immediately (no need to send "AM1;").
464  */
465 
rt21_rot_set_position(ROT * rot,azimuth_t azimuth,elevation_t elevation)466 static int rt21_rot_set_position(ROT *rot, azimuth_t azimuth,
467                                  elevation_t elevation)
468 {
469     char cmdstr[16];
470     int err;
471 
472     rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__);
473 
474     if (!rot)
475     {
476         return -RIG_EINVAL;
477     }
478 
479     if (azimuth < 0 || azimuth > 360)
480     {
481         return -RIG_EINVAL;
482     }
483 
484     sprintf(cmdstr, "AP1%05.1f\r;", azimuth);   /* Total field width of 5 chars */
485     err = rotorez_send_priv_cmd(rot, cmdstr);
486 
487     if (err != RIG_OK)
488     {
489         return err;
490     }
491 
492     if (rot->state.rotport2.pathname[0] != 0)
493     {
494         sprintf(cmdstr, "AP1%05.1f\r;",
495                 elevation);    /* Total field width of 5 chars */
496     }
497 
498     err = rotorez_send_priv_cmd2(rot, cmdstr);
499 
500     if (err != RIG_OK)
501     {
502         return err;
503     }
504 
505     return RIG_OK;
506 }
507 
508 
509 /*
510  * Get position
511  * Returns current azimuth position in whole degrees.
512  * Range returned from Rotor-EZ is an integer, 0 to 359 degrees
513  * Elevation is set to 0
514  */
515 
rotorez_rot_get_position(ROT * rot,azimuth_t * azimuth,elevation_t * elevation)516 static int rotorez_rot_get_position(ROT *rot, azimuth_t *azimuth,
517                                     elevation_t *elevation)
518 {
519     struct rot_state *rs;
520     char cmdstr[5] = "AI1;";
521     char az[5];         /* read azimuth string */
522     char *p;
523     azimuth_t tmp = 0;
524     int err;
525 
526     rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__);
527 
528     if (!rot)
529     {
530         return -RIG_EINVAL;
531     }
532 
533     do
534     {
535         err = rotorez_send_priv_cmd(rot, cmdstr);
536 
537         if (err != RIG_OK)
538         {
539             return err;
540         }
541 
542         rs = &rot->state;
543 
544         err = read_block(&rs->rotport, az, AZ_READ_LEN);
545 
546         if (err != AZ_READ_LEN)
547         {
548             return -RIG_ETRUNC;
549         }
550 
551         /* The azimuth string should be ';xxx' beginning at offset 0.  If the
552          * ';' is not there, it's likely the RotorEZ has received an invalid
553          * command and the buffer needs to be flushed.  See
554          * rotorez_flush_buffer() definition below for a complete description.
555          */
556         if (az[0] != ';')
557         {
558             err = rotorez_flush_buffer(rot);
559 
560             if (err == -RIG_EIO)
561             {
562                 return err;
563             }
564             else
565             {
566                 err = -RIG_EINVAL;
567             }
568         }
569         else if (err == AZ_READ_LEN)
570         {
571             /* Check if remaining chars are digits if az[0] == ';' */
572             for (p = az + 1; p < az + 4; p++)
573                 if (isdigit((int)*p))
574                 {
575                     continue;
576                 }
577                 else
578                 {
579                     err = -RIG_EINVAL;
580                 }
581         }
582     }
583     while (err == -RIG_EINVAL);
584 
585     /*
586      * Rotor-EZ returns a four octet string consisting of a ';' followed
587      * by three octets containing the rotor's position in degrees.  The
588      * semi-colon is ignored when passing the string to atof().
589      */
590     az[4] = 0x00;               /* NULL terminated string */
591     p = az + 1;                 /* advance past leading ';' */
592     tmp = (azimuth_t)atof(p);
593     rig_debug(RIG_DEBUG_TRACE, "%s: \"%s\" after conversion = %.1f\n",
594               __func__, p, tmp);
595 
596     if (tmp == 360)
597     {
598         tmp = 0;
599     }
600     else if (tmp < 0 || tmp > 359)
601     {
602         return -RIG_EINVAL;
603     }
604 
605     *azimuth = tmp;
606     *elevation = 0;             /* RotorEZ does not support elevation */
607     rig_debug(RIG_DEBUG_TRACE,
608               "%s: azimuth = %.1f deg; elevation = %.1f deg\n",
609               __func__, *azimuth, *elevation);
610 
611     return RIG_OK;
612 }
613 
614 
615 /*
616  * Get position for ERC
617  * Returns current azimuth position in whole degrees.
618  * Range returned from ERC is an integer, 0 to 359 degrees
619  * Elevation is set to 0
620  */
621 
erc_rot_get_position(ROT * rot,azimuth_t * azimuth,elevation_t * elevation)622 static int erc_rot_get_position(ROT *rot, azimuth_t *azimuth,
623                                 elevation_t *elevation)
624 {
625     struct rot_state *rs;
626     char cmdstr[5] = "AI1;";
627     char az[5];         /* read azimuth string */
628     char *p;
629     azimuth_t tmp = 0;
630     int err;
631 
632     rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__);
633 
634     if (!rot)
635     {
636         return -RIG_EINVAL;
637     }
638 
639     do
640     {
641         err = rotorez_send_priv_cmd(rot, cmdstr);
642 
643         if (err != RIG_OK)
644         {
645             return err;
646         }
647 
648         rs = &rot->state;
649 
650         err = read_block(&rs->rotport, az, AZ_READ_LEN);
651 
652         if (err != AZ_READ_LEN)
653         {
654             return -RIG_ETRUNC;
655         }
656 
657         /*
658          * The azimuth string returned by the ERC should be 'xxx;'
659          * beginning at offset 0.  A new version may implement the
660          * Idiom PRess format, hence the test for ';xxx'.
661          */
662 
663         /* Check if remaining chars are digits if az[3] == ';' */
664         if (az[3] == ';')
665         {
666             for (p = az; p < az + 3; p++)
667                 if (isdigit((int)*p))
668                 {
669                     continue;
670                 }
671                 else
672                 {
673                     err = -RIG_EINVAL;
674                 }
675         }
676         else if (az[0] == ';')
677         {
678             /* Check if remaining chars are digits if az[0] == ';' */
679             for (p = az + 1; p < az + 4; p++)
680                 if (isdigit((int)*p))
681                 {
682                     continue;
683                 }
684                 else
685                 {
686                     err = -RIG_EINVAL;
687                 }
688         }
689     }
690     while (err == -RIG_EINVAL);
691 
692     /*
693      * Old ERC returns a four octet string consisting of three octets
694      * followed by ';' ('xxx;') containing the rotor's position in degrees.
695      * A new version will implement Idiom Press format of ';xxx'.
696      *
697      * We test for either case and pass a string of only digits to atof()
698      */
699     az[4] = 0x00;           /* NULL terminated string */
700     p = az;
701 
702     if (*p == ';')
703     {
704         p++;
705     }
706     else
707     {
708         az[3] = 0x00;    /* truncate trailing ';' */
709     }
710 
711     tmp = (azimuth_t)atof(p);
712     rig_debug(RIG_DEBUG_TRACE, "%s: \"%s\" after conversion = %.1f\n",
713               __func__, p, tmp);
714 
715     if (tmp == 360)
716     {
717         tmp = 0;
718     }
719     else if (tmp < 0 || tmp > 359)
720     {
721         return -RIG_EINVAL;
722     }
723 
724     *azimuth = tmp;
725     *elevation = 0;             /* ERC does not support elevation */
726     rig_debug(RIG_DEBUG_TRACE,
727               "%s: azimuth = %.1f deg; elevation = %.1f deg\n",
728               __func__, *azimuth, *elevation);
729 
730     return RIG_OK;
731 }
732 
733 
734 /*
735  * Get position from Green Heron RT-21 series of controllers Returns
736  * current azimuth position in degrees and tenths.  Range returned from
737  * RT-21 is a float, 0.0 to 359.9 degrees Elevation is set to 0
738  */
739 
rt21_rot_get_position(ROT * rot,azimuth_t * azimuth,elevation_t * elevation)740 static int rt21_rot_get_position(ROT *rot, azimuth_t *azimuth,
741                                  elevation_t *elevation)
742 {
743     struct rot_state *rs;
744     char az[8];     /* read azimuth string */
745     int err;
746 
747     rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__);
748 
749     if (!rot)
750     {
751         return -RIG_EINVAL;
752     }
753 
754     /* 'BI1' is an RT-21 specific command that queries for a floating
755      * point position (to the tenth of a degree).
756      */
757     err = rotorez_send_priv_cmd(rot, "BI1;");
758 
759     if (err != RIG_OK)
760     {
761         return err;
762     }
763 
764     rs = &rot->state;
765 
766     err = read_string(&rs->rotport, az, RT21_AZ_LEN + 1, ";", strlen(";"));
767 
768     if (err < 0)    /* read_string returns negative on error. */
769     {
770         return err;
771     }
772 
773     /* RT-21 returns a five to six octet string consisting of one to
774      * three octets containing the rotor's position in degrees, one
775      * octet containing a decimal '.', one octet containing the rotor's
776      * position in tenths of a degree, and one octet with the
777      * terminating ';' with a leading space as padding--'[xx| ]x.y;'.
778      * Seems as though at least five characters are returned and a
779      * space is used as a leading pad character if needed.
780      */
781     if ((isdigit((int)az[0])) || (isspace((int)az[0])))
782     {
783         azimuth_t tmp = strtof(az, NULL);
784         rig_debug(RIG_DEBUG_TRACE, "%s: \"%s\" after conversion = %.1f\n",
785                   __func__, az, tmp);
786 
787         if (tmp == 360.0)
788         {
789             tmp = 0;
790         }
791         else if (tmp < 0.0 || tmp > 359.9)
792         {
793             return -RIG_EINVAL;
794         }
795 
796         *azimuth = tmp;
797         *elevation = 0.0;   /* RT-21 backend does not support el at this time. */
798         rig_debug(RIG_DEBUG_TRACE,
799                   "%s: azimuth = %.1f deg; elevation = %.1f deg\n",
800                   __func__, *azimuth, *elevation);
801     }
802     else
803     {
804         return -RIG_EINVAL;
805     }
806 
807     return RIG_OK;
808 }
809 
810 
811 /*
812  * Stop rotation on RotorEZ, reset on DCU-1
813  *
814  * Sending the ";" string will stop rotation on the RotorEZ and reset the DCU-1
815  */
816 
rotorez_rot_stop(ROT * rot)817 static int rotorez_rot_stop(ROT *rot)
818 {
819     char cmdstr[2] = ";";
820     int err;
821 
822     rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__);
823 
824     if (!rot)
825     {
826         return -RIG_EINVAL;
827     }
828 
829     err = rotorez_send_priv_cmd(rot, cmdstr);
830 
831     if (err != RIG_OK)
832     {
833         return err;
834     }
835 
836     return RIG_OK;
837 }
838 
rotorez_rot_reset(ROT * rot,rot_reset_t reset)839 static int rotorez_rot_reset(ROT *rot, rot_reset_t reset)
840 {
841     rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__);
842 
843     return rotorez_rot_stop(rot);
844 }
845 
846 /*
847  * Stop rotation on DCU-1
848  */
849 
dcu1_rot_stop(ROT * rot)850 static int dcu1_rot_stop(ROT *rot)
851 {
852     char cmdstr[5] = "AS1;";
853     int err;
854 
855     rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__);
856 
857     if (!rot)
858     {
859         return -RIG_EINVAL;
860     }
861 
862     err = rotorez_send_priv_cmd(rot, cmdstr);
863 
864     if (err != RIG_OK)
865     {
866         return err;
867     }
868 
869     return RIG_OK;
870 }
871 
872 
873 /*
874  * Send configuration character
875  *
876  * Rotor-EZ interface will act on these commands immediately --
877  * no other command or command terminator is needed.  Expects token
878  * define in rotorez.h and *val of '1' or '0' (enable/disable).
879  */
880 
rotorez_rot_set_conf(ROT * rot,token_t token,const char * val)881 static int rotorez_rot_set_conf(ROT *rot, token_t token, const char *val)
882 {
883     char cmdstr[2];
884     char c;
885     int err;
886 
887     rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__);
888     rig_debug(RIG_DEBUG_TRACE, "%s: token = %d, *val = %c\n", __func__, (int)token,
889               *val);
890 
891     if (!rot)
892     {
893         return -RIG_EINVAL;
894     }
895 
896     if (*val < '0' || *val > '1')
897     {
898         return -RIG_EINVAL;
899     }
900 
901     switch (token)
902     {
903     case TOK_ENDPT:          /* Endpoint option */
904         if (*val == '1')
905         {
906             c = 'E';
907         }
908         else
909         {
910             c = 'e';
911         }
912 
913         break;
914 
915     case TOK_JAM:              /* Jam protection */
916         if (*val == '1')
917         {
918             c = 'J';
919         }
920         else
921         {
922             c = 'j';
923         }
924 
925         break;
926 
927     case TOK_OVRSHT:            /* Overshoot option */
928         if (*val == '1')
929         {
930             c = 'O';
931         }
932         else
933         {
934             c = 'o';
935         }
936 
937         break;
938 
939     case TOK_UNSTICK:          /* Unstick option */
940         if (*val == '1')
941         {
942             c = 'S';
943         }
944         else
945         {
946             c = 's';
947         }
948 
949         break;
950 
951     default:
952         return -RIG_EINVAL;
953     }
954 
955     rig_debug(RIG_DEBUG_TRACE, "%s: c = %c, *val = %c\n", __func__, c, *val);
956     snprintf(cmdstr, sizeof(cmdstr), "%c", c);
957 
958     rig_debug(RIG_DEBUG_TRACE, "%s: cmdstr = %s, *val = %c\n",
959               __func__, cmdstr, *val);
960 
961     err = rotorez_send_priv_cmd(rot, cmdstr);
962 
963     if (err != RIG_OK)
964     {
965         return err;
966     }
967 
968     return RIG_OK;
969 }
970 
971 
972 /*
973  * Get Info
974  * returns the model name string
975  */
rotorez_rot_get_info(ROT * rot)976 static const char *rotorez_rot_get_info(ROT *rot)
977 {
978     const struct rot_caps *rc;
979 
980     rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__);
981 
982     if (!rot)
983     {
984         return (const char *) - RIG_EINVAL;
985     }
986 
987     rc = rot->caps;
988 
989     return rc->model_name;
990 }
991 
992 
993 /*
994  * Send command string to rotor
995  */
996 
rotorez_send_priv_cmd(ROT * rot,const char * cmdstr)997 static int rotorez_send_priv_cmd(ROT *rot, const char *cmdstr)
998 {
999     struct rot_state *rs;
1000     int err;
1001 
1002     rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__);
1003 
1004     if (!rot)
1005     {
1006         return -RIG_EINVAL;
1007     }
1008 
1009     rs = &rot->state;
1010     err = write_block(&rs->rotport, cmdstr, strlen(cmdstr));
1011 
1012     if (err != RIG_OK)
1013     {
1014         return err;
1015     }
1016 
1017     return RIG_OK;
1018 }
1019 
1020 // send command to 2nd rotator port
rotorez_send_priv_cmd2(ROT * rot,const char * cmdstr)1021 static int rotorez_send_priv_cmd2(ROT *rot, const char *cmdstr)
1022 {
1023     struct rot_state *rs;
1024     int err;
1025 
1026     rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__);
1027 
1028     if (!rot)
1029     {
1030         return -RIG_EINVAL;
1031     }
1032 
1033     rs = &rot->state;
1034     err = write_block(&rs->rotport2, cmdstr, strlen(cmdstr));
1035 
1036     if (err != RIG_OK)
1037     {
1038         return err;
1039     }
1040 
1041     return RIG_OK;
1042 }
1043 
1044 
1045 /*
1046  * Flush the serial input buffer
1047  *
1048  * If the RotorEZ should receive an invalid command, such as an the ';'
1049  * character while the rotor is not in motion, as sent by the rotorez_rot_stop
1050  * function or the 'S' command from 'rotctl', it will output the following
1051  * string, "C2000 IDIOM V1.4S " into the input buffer.  This function flushes
1052  * the buffer by reading it until a timeout occurs.  Once the timeout occurs,
1053  * this function returns and the buffer is presumed to be empty.
1054  */
1055 
rotorez_flush_buffer(ROT * rot)1056 static int rotorez_flush_buffer(ROT *rot)
1057 {
1058     struct rot_state *rs;
1059     char garbage[32];         /* read buffer */
1060     int err = 0;
1061     size_t MAX = 31;
1062 
1063     rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__);
1064 
1065     if (!rot)
1066     {
1067         return -RIG_EINVAL;
1068     }
1069 
1070     rs = &rot->state;
1071 
1072     do
1073     {
1074         err = read_block(&rs->rotport, garbage, MAX);
1075 
1076         /* Oops!  An IO error was encountered.  Bail out! */
1077         if (err == -RIG_EIO)
1078         {
1079             return -RIG_EIO;
1080         }
1081     }
1082     while (err != -RIG_ETIMEOUT);
1083 
1084     return RIG_OK;
1085 }
1086 
1087 
1088 /*
1089  * Initialize backend
1090  */
1091 
DECLARE_INITROT_BACKEND(rotorez)1092 DECLARE_INITROT_BACKEND(rotorez)
1093 {
1094     rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__);
1095 
1096     rot_register(&rotorez_rot_caps);
1097     rot_register(&rotorcard_rot_caps);
1098     rot_register(&dcu_rot_caps);
1099     rot_register(&erc_rot_caps);
1100     rot_register(&rt21_rot_caps);
1101 
1102     return RIG_OK;
1103 }
1104