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