1 /*
2  * Hamlib backend library for the HD 1780 Intellirotor command set.
3  *
4  * hd1780.c - (C) Nate Bargmann 2003 (n0nb at arrl.net)
5  *            (C) Rob Frohne 2008 (kl7na at arrl.net)
6  *
7  * This shared library provides an API for communicating
8  * via serial interface to a Heathkit HD 1780 Intellirotor.
9  *
10  * Heathkit is a trademark of Heath Company
11  *
12  *
13  *   This library is free software; you can redistribute it and/or
14  *   modify it under the terms of the GNU Lesser General Public
15  *   License as published by the Free Software Foundation; either
16  *   version 2.1 of the License, or (at your option) any later version.
17  *
18  *   This library is distributed in the hope that it will be useful,
19  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
20  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
21  *   Lesser General Public License for more details.
22  *
23  *   You should have received a copy of the GNU Lesser General Public
24  *   License along with this library; if not, write to the Free Software
25  *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
26  *
27  */
28 
29 #ifdef HAVE_CONFIG_H
30 #include "config.h"
31 #endif
32 
33 #include <stdio.h>
34 #include <stdlib.h>             /* Standard library definitions */
35 #include <string.h>             /* String function definitions */
36 #include <unistd.h>             /* UNIX standard function definitions */
37 
38 #include "hamlib/rotator.h"
39 #include "serial.h"
40 #include "misc.h"
41 #include "register.h"
42 
43 #include "hd1780.h"
44 
45 
46 /*
47  * Private data structure
48  */
49 struct hd1780_rot_priv_data
50 {
51     azimuth_t az;
52 };
53 
54 /*
55  * Private helper function prototypes
56  */
57 
58 static int hd1780_send_priv_cmd(ROT *rot, const char *cmd);
59 
60 
61 /*
62  * HD 1780 Intellirotor rotor capabilities
63  */
64 
65 const struct rot_caps hd1780_rot_caps =
66 {
67     ROT_MODEL(ROT_MODEL_HD1780),
68     .model_name =         "HD 1780 Intellirotor",
69     .mfg_name =           "Heathkit",
70     .version =            "20200112.0",
71     .copyright =          "LGPL",
72     .status =             RIG_STATUS_BETA,
73     .rot_type =           ROT_TYPE_OTHER,
74     .port_type =          RIG_PORT_SERIAL,
75     .serial_rate_min =    300,
76     .serial_rate_max =    9600,
77     .serial_data_bits =   8,
78     .serial_stop_bits =   1,
79     .serial_parity =      RIG_PARITY_NONE,
80     .serial_handshake =   RIG_HANDSHAKE_NONE,
81     .write_delay =        10,
82     .post_write_delay =   500,
83     .timeout =            60000,
84     .retry =              0,
85 
86     .min_az =             -180,
87     .max_az =             180,
88     .min_el =             0,
89     .max_el =             0,
90 
91     .priv =  NULL,    /* priv */
92 
93     .rot_init =           hd1780_rot_init,
94     .rot_cleanup =        hd1780_rot_cleanup,
95     .set_position =       hd1780_rot_set_position,
96     .get_position =       hd1780_rot_get_position,
97 };
98 
99 
100 /* ************************************
101  *
102  * API functions
103  *
104  * ************************************
105  */
106 
107 
108 /*
109  * Initialize data structures
110  */
111 
hd1780_rot_init(ROT * rot)112 static int hd1780_rot_init(ROT *rot)
113 {
114     struct hd1780_rot_priv_data *priv;
115 
116     rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__);
117 
118     if (!rot)
119     {
120         return -RIG_EINVAL;
121     }
122 
123     rot->state.priv = (struct hd1780_rot_priv_data *)
124                       malloc(sizeof(struct hd1780_rot_priv_data));
125 
126     if (!rot->state.priv)
127     {
128         return -RIG_ENOMEM;
129     }
130 
131     priv = rot->state.priv;
132 
133     rot->state.rotport.type.rig = RIG_PORT_SERIAL;
134 
135     priv->az = 0;
136 
137     return RIG_OK;
138 }
139 
140 /*
141  * Clean up allocated memory structures
142  */
143 
hd1780_rot_cleanup(ROT * rot)144 static int hd1780_rot_cleanup(ROT *rot)
145 {
146 
147     rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__);
148 
149     if (!rot)
150     {
151         return -RIG_EINVAL;
152     }
153 
154     if (rot->state.priv)
155     {
156         free(rot->state.priv);
157     }
158 
159     rot->state.priv = NULL;
160 
161     return RIG_OK;
162 }
163 
164 
165 /*
166  * Set position
167  * HD 1780 protocol supports azimuth only--elevation is ignored
168  * Range is converted to an integer string, 0 to 360 degrees
169  */
170 
hd1780_rot_set_position(ROT * rot,azimuth_t azimuth,elevation_t elevation)171 static int hd1780_rot_set_position(ROT *rot, azimuth_t azimuth,
172                                    elevation_t elevation)
173 {
174     struct rot_state *rs;
175     char cmdstr[8];
176     char execstr[5] = "\r", ok[3];
177     int err;
178 
179     rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__);
180 
181     if (!rot)
182     {
183         return -RIG_EINVAL;
184     }
185 
186     if (azimuth < hd1780_rot_caps.min_az || azimuth > hd1780_rot_caps.max_az)
187     {
188         return -RIG_EINVAL;
189     }
190 
191     if (azimuth < 0) { azimuth = azimuth + 360; }
192 
193     sprintf(cmdstr, "%03.0f", azimuth);    /* Target bearing */
194     err = hd1780_send_priv_cmd(rot, cmdstr);
195 
196     if (err != RIG_OK)
197     {
198         return err;
199     }
200 
201     err = hd1780_send_priv_cmd(rot, execstr); /* Execute command */
202 
203     if (err != RIG_OK)
204     {
205         return err;
206     }
207 
208     /* We need to look for the <CR> +<LF> to signify that everything finished.  The HD 1780
209      * sends a <CR> when it is finished rotating.
210      */
211     rs = &rot->state;
212     err = read_block(&rs->rotport, ok, 2);
213 
214     if (err != 2)
215     {
216         return -RIG_ETRUNC;
217     }
218 
219     if ((ok[0] != '\r') || (ok[1] != '\n'))
220     {
221         return -RIG_ETRUNC;
222     }
223 
224 
225     return RIG_OK;
226 }
227 
228 
229 /*
230  * Get position
231  * Returns current azimuth position in whole degrees.
232  * Range returned from Rotor-EZ is an integer, 0 to 359 degrees
233  * Elevation is set to 0
234  */
235 
hd1780_rot_get_position(ROT * rot,azimuth_t * azimuth,elevation_t * elevation)236 static int hd1780_rot_get_position(ROT *rot, azimuth_t *azimuth,
237                                    elevation_t *elevation)
238 {
239     struct rot_state *rs;
240     char cmdstr[3] = "b\r";
241     char az[7];          /* read azimuth string */
242     char *p;
243     azimuth_t tmp = 0;
244     int err;
245 
246     rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__);
247 
248     if (!rot)
249     {
250         return -RIG_EINVAL;
251     }
252 
253     err = hd1780_send_priv_cmd(rot, cmdstr);
254 
255     if (err != RIG_OK)
256     {
257         return err;
258     }
259 
260     rs = &rot->state;
261     err = read_block(&rs->rotport, az, AZ_READ_LEN);
262 
263     if (err != AZ_READ_LEN)
264     {
265         return -RIG_ETRUNC;
266     }
267 
268     /*
269      * HD 1780 returns a four octet string consisting of
270      * three octets containing the rotor's position in degrees followed by a
271      * space and a <CR> and a <LF>.  The
272      * space is ignored when passing the string to atof().
273      */
274     az[4] = 0x00;                 /* NULL terminated string */
275     p = az; /* for hd1780 */
276     tmp = (azimuth_t)atof(p);
277     rig_debug(RIG_DEBUG_TRACE, "%s: \"%s\" after conversion = %.1f\n",
278               __func__, p, tmp);
279 
280     if (tmp < 0 || tmp > 359)
281     {
282         return -RIG_EINVAL;
283     }
284 
285     *azimuth = tmp;
286     *elevation = 0;               /* assume aiming at the horizon */
287 
288     rig_debug(RIG_DEBUG_TRACE,
289               "%s: azimuth = %.1f deg; elevation = %.1f deg\n",
290               __func__, *azimuth, *elevation);
291 
292     return RIG_OK;
293 }
294 
295 
296 
297 /*
298  * Send command string to rotor.
299  *
300  * To make sure that the rotor is ready to take a command, we send it a <CR> and
301  * it will normally reply with a ? <CR> <LF>.  If you don't do this, using the
302  * w: command of rotctl it is possible to get it out of sequence.  This kind of
303  * resets its command buffer before sending the command.
304  */
305 
hd1780_send_priv_cmd(ROT * rot,const char * cmdstr)306 static int hd1780_send_priv_cmd(ROT *rot, const char *cmdstr)
307 {
308     struct rot_state *rs;
309     int err;
310 
311     rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__);
312 
313     if (!rot)
314     {
315         return -RIG_EINVAL;
316     }
317 
318     rs = &rot->state;
319 
320     err = write_block(&rs->rotport, cmdstr, strlen(cmdstr));
321 
322     if (err != RIG_OK)
323     {
324         return err;
325     }
326 
327     return RIG_OK;
328 }
329 
330 
331 /*
332  * Initialize backend
333  */
334 
DECLARE_INITROT_BACKEND(heathkit)335 DECLARE_INITROT_BACKEND(heathkit)
336 {
337     rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__);
338 
339     rot_register(&hd1780_rot_caps);
340 
341     return RIG_OK;
342 }
343 
344 
345