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