1 /*
2 Gpredict: Real-time satellite tracking and orbit prediction program
3
4 Copyright (C) 2001-2017 Alexandru Csete, OZ9AEC
5 Copyright (C) 2011 Charles Suprin, AA1VS
6
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
11
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, visit http://www.fsf.org/
19 */
20 /*
21 * Antenna rotator control window.
22 *
23 * The master rotator control UI is implemented as a Gtk+ Widget in order
24 * to allow multiple instances. The widget is created from the module
25 * popup menu and each module can have several rotator control windows
26 * attached to it. Note, however, that current implementation only
27 * allows one rotor control window per module.
28 *
29 */
30
31 #ifdef HAVE_CONFIG_H
32 #include <build-config.h>
33 #endif
34
35 /* NETWORK */
36 #ifndef WIN32
37 #include <arpa/inet.h> /* htons() */
38 #include <netdb.h> /* gethostbyname() */
39 #include <netinet/in.h> /* struct sockaddr_in */
40 #include <sys/socket.h> /* socket(), connect(), send() */
41 #else
42 #include <winsock2.h>
43 #endif
44
45 #include <errno.h>
46 #include <glib/gi18n.h>
47 #include <gtk/gtk.h>
48 #include <math.h>
49 #include <string.h> /* strerror() */
50
51 #include "compat.h"
52 #include "gpredict-utils.h"
53 #include "gtk-polar-plot.h"
54 #include "gtk-rot-knob.h"
55 #include "gtk-rot-ctrl.h"
56 #include "predict-tools.h"
57 #include "sat-log.h"
58
59
60 #define FMTSTR "%7.2f\302\260"
61 #define MAX_ERROR_COUNT 5
62
63 static GtkVBoxClass *parent_class = NULL;
64
65
66 /* Open the rotcld socket. Returns file descriptor or -1 if an error occurs */
rotctld_socket_open(const gchar * host,gint port)67 static gint rotctld_socket_open(const gchar * host, gint port)
68 {
69 struct sockaddr_in ServAddr;
70 struct hostent *h;
71 gint sock;
72 gint status;
73
74 sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
75 if (sock == -1)
76 {
77 sat_log_log(SAT_LOG_LEVEL_ERROR,
78 _("Failed to create rotctl socket: %s"), strerror(errno));
79 return sock;
80 }
81
82 sat_log_log(SAT_LOG_LEVEL_DEBUG,
83 _("%s: Network socket created successfully"), __func__);
84
85 memset(&ServAddr, 0, sizeof(ServAddr));
86 ServAddr.sin_family = AF_INET; /* Internet address family */
87 h = gethostbyname(host);
88 if (h == NULL)
89 {
90 sat_log_log(SAT_LOG_LEVEL_ERROR,
91 _("Name resolution of rotctld server %s failed."), host);
92 #ifdef WIN32
93 closesocket(sock);
94 #else
95 close(sock);
96 #endif
97 return -1;
98 }
99
100 memcpy((char *)&ServAddr.sin_addr.s_addr, h->h_addr_list[0], h->h_length);
101 ServAddr.sin_port = htons(port); /* Server port */
102
103 /* establish connection */
104 status = connect(sock, (struct sockaddr *)&ServAddr, sizeof(ServAddr));
105 if (status == -1)
106 {
107 sat_log_log(SAT_LOG_LEVEL_ERROR,
108 _("Connection to rotctld server at %s:%d failed: %s"),
109 host, port, strerror(errno));
110
111 #ifdef WIN32
112 closesocket(sock);
113 #else
114 close(sock);
115 #endif
116 return -1;
117 }
118
119 sat_log_log(SAT_LOG_LEVEL_DEBUG, _("%s: Connection opened to %s:%d"),
120 __func__, host, port);
121
122 return sock;
123 }
124
125 /* Close a rotcld socket. First send a q command to cleanly shut down rotctld */
rotctld_socket_close(gint * sock)126 static void rotctld_socket_close(gint * sock)
127 {
128 gint written;
129
130 /*shutdown the rotctld connect */
131 written = send(*sock, "q\x0a", 2, 0);
132 if (written != 2)
133 {
134 sat_log_log(SAT_LOG_LEVEL_ERROR,
135 _("%s:%s: Sent 2 bytes but sent %d."),
136 __FILE__, __func__, written);
137 }
138
139 #ifndef WIN32
140 shutdown(*sock, SHUT_RDWR);
141 close(*sock);
142 #else
143 shutdown(*sock, SD_BOTH);
144 closesocket(*sock);
145 #endif
146
147 *sock = -1;
148 }
149
150 /*
151 * Send a command to rotctld and read the response.
152 *
153 * Inputs are the socket, a string command, and a buffer and length for
154 * returning the output from rotctld.
155 */
rotctld_socket_rw(gint sock,gchar * buff,gchar * buffout,gint sizeout)156 static gboolean rotctld_socket_rw(gint sock, gchar * buff, gchar * buffout,
157 gint sizeout)
158 {
159 gint written;
160 gint size;
161
162 #ifdef WIN32
163 size = strlen(buff) - 1;
164 #else
165 size = strlen(buff);
166 #endif
167
168 /* send command */
169 written = send(sock, buff, size, 0);
170 if (written != size)
171 {
172 sat_log_log(SAT_LOG_LEVEL_ERROR,
173 _("%s: SIZE ERROR %d / %d"), __func__, written, size);
174 }
175 if (written == -1)
176 {
177 sat_log_log(SAT_LOG_LEVEL_ERROR,
178 _("%s: rotctld Socket Down"), __func__);
179 return FALSE;
180 }
181
182 /* try to read answer */
183 size = recv(sock, buffout, sizeout, 0);
184
185 if (size == -1)
186 {
187 sat_log_log(SAT_LOG_LEVEL_ERROR,
188 _("%s: rotctld Socket Down"), __func__);
189 return FALSE;
190 }
191
192 buffout[size] = '\0';
193 if (size == 0)
194 {
195 sat_log_log(SAT_LOG_LEVEL_ERROR,
196 _("%s:%s: Got 0 bytes from rotctld"), __FILE__, __func__);
197 }
198
199 return TRUE;
200 }
201
sat_name_compare(sat_t * a,sat_t * b)202 static gint sat_name_compare(sat_t * a, sat_t * b)
203 {
204 return (gpredict_strcmp(a->nickname, b->nickname));
205 }
206
rot_name_compare(const gchar * a,const gchar * b)207 static gint rot_name_compare(const gchar * a, const gchar * b)
208 {
209 return (gpredict_strcmp(a, b));
210 }
211
is_flipped_pass(pass_t * pass,rot_az_type_t type,gdouble azstoppos)212 static gboolean is_flipped_pass(pass_t * pass, rot_az_type_t type,
213 gdouble azstoppos)
214 {
215 gdouble max_az = 0, min_az = 0, offset = 0;
216 gdouble caz, last_az = pass->aos_az;
217 guint num, i;
218 pass_detail_t *detail;
219 gboolean retval = FALSE;
220
221 num = g_slist_length(pass->details);
222 if (type == ROT_AZ_TYPE_360)
223 {
224 min_az = 0;
225 max_az = 360;
226 }
227 else if (type == ROT_AZ_TYPE_180)
228 {
229 min_az = -180;
230 max_az = 180;
231 }
232
233 /* Offset by (azstoppos-min_az) to handle
234 * rotators with non-default positions.
235 * Note that the default positions of the rotator stops
236 * (eg. -180 for ROT_AZ_TYPE_180, and 0 for
237 * ROT_AZ_TYPE_360) will create an offset of 0, which
238 * seems like a pretty sane default. */
239 offset = azstoppos - min_az;
240 min_az += offset;
241 max_az += offset;
242
243 /* Assume that min_az and max_az are atleat 360 degrees apart
244 get the azimuth that is in a settable range */
245 while (last_az > max_az)
246 last_az -= 360;
247
248 while (last_az < min_az)
249 last_az += 360;
250
251 if (num > 1)
252 {
253 for (i = 1; i < num - 1; i++)
254 {
255 detail = PASS_DETAIL(g_slist_nth_data(pass->details, i));
256 caz = detail->az;
257
258 while (caz > max_az)
259 caz -= 360;
260
261 while (caz < min_az)
262 caz += 360;
263
264
265 if (fabs(caz - last_az) > 180)
266 retval = TRUE;
267
268 last_az = caz;
269 }
270 }
271 caz = pass->los_az;
272 while (caz > max_az)
273 caz -= 360;
274
275 while (caz < min_az)
276 caz += 360;
277
278 if (fabs(caz - last_az) > 180)
279 retval = TRUE;
280
281 return retval;
282 }
283
set_flipped_pass(GtkRotCtrl * ctrl)284 static inline void set_flipped_pass(GtkRotCtrl * ctrl)
285 {
286 if (ctrl->conf && ctrl->pass)
287 ctrl->flipped = is_flipped_pass(ctrl->pass, ctrl->conf->aztype,
288 ctrl->conf->azstoppos);
289 }
290
291 /**
292 * Read rotator position from device.
293 *
294 * \param ctrl Pointer to the GtkRotCtrl widget.
295 * \param az The current Az as read from the device
296 * \param el The current El as read from the device
297 * \return TRUE if the position was successfully retrieved, FALSE if an
298 * error occurred.
299 */
get_pos(GtkRotCtrl * ctrl,gdouble * az,gdouble * el)300 static gboolean get_pos(GtkRotCtrl * ctrl, gdouble * az, gdouble * el)
301 {
302 gchar *buff, **vbuff;
303 gchar buffback[128];
304 gboolean retcode;
305
306 if ((az == NULL) || (el == NULL))
307 {
308 sat_log_log(SAT_LOG_LEVEL_ERROR,
309 _("%s:%d: NULL storage."), __FILE__, __LINE__);
310 return FALSE;
311 }
312
313 /* send command */
314 buff = g_strdup_printf("p\x0a");
315 retcode = rotctld_socket_rw(ctrl->client.socket, buff, buffback, 128);
316
317 /* try to parse answer */
318 if (retcode)
319 {
320 if (strncmp(buffback, "RPRT", 4) == 0)
321 {
322 g_strstrip(buffback);
323 sat_log_log(SAT_LOG_LEVEL_ERROR,
324 _("%s:%d: rotctld returned error (%s)"),
325 __FILE__, __LINE__, buffback);
326 }
327 else
328 {
329 vbuff = g_strsplit(buffback, "\n", 3);
330 if ((vbuff[0] != NULL) && (vbuff[1] != NULL))
331 {
332 *az = g_strtod(vbuff[0], NULL);
333 *el = g_strtod(vbuff[1], NULL);
334 }
335 else
336 {
337 g_strstrip(buffback);
338 sat_log_log(SAT_LOG_LEVEL_ERROR,
339 _("%s:%d: rotctld returned bad response (%s)"),
340 __FILE__, __LINE__, buffback);
341 }
342
343 g_strfreev(vbuff);
344 }
345 }
346
347 g_free(buff);
348
349 return retcode;
350 }
351
352 /**
353 * Send new position to rotator device
354 *
355 * \param ctrl Pointer to the GtkRotCtrl widget
356 * \param az The new Azimuth
357 * \param el The new Elevation
358 * \return TRUE if the new position has been sent successfully
359 * FALSE if an error occurred
360 *
361 * \note The function does not perform any range check since the GtkRotKnob
362 * should always keep its value within range.
363 */
set_pos(GtkRotCtrl * ctrl,gdouble az,gdouble el)364 static gboolean set_pos(GtkRotCtrl * ctrl, gdouble az, gdouble el)
365 {
366 gchar *buff;
367 gchar buffback[128];
368 gboolean retcode;
369 gint retval;
370
371 /* send command */
372 buff = g_strdup_printf("P %.2f %.2f\x0a", az, el);
373 retcode = rotctld_socket_rw(ctrl->client.socket, buff, buffback, 128);
374 g_free(buff);
375
376 if (retcode == TRUE)
377 {
378 /* treat errors as soft errors */
379 retval = (gint) g_strtod(buffback + 4, NULL);
380 if (retval != 0)
381 {
382 g_strstrip(buffback);
383 sat_log_log(SAT_LOG_LEVEL_ERROR,
384 _
385 ("%s:%d: rotctld returned error %d with az %f el %f(%s)"),
386 __FILE__, __LINE__, retval, az, el, buffback);
387 }
388 }
389
390 return (retcode);
391 }
392
393 /* Rotctl client thread */
rotctld_client_thread(gpointer data)394 static gpointer rotctld_client_thread(gpointer data)
395 {
396 gdouble elapsed_time;
397 gdouble azi = 0.0;
398 gdouble ele = 0.0;
399 gboolean new_trg = FALSE;
400 gboolean io_error = FALSE;
401 GtkRotCtrl *ctrl = GTK_ROT_CTRL(data);
402
403 g_print("Starting rotctld client thread\n");
404
405 ctrl->client.socket = rotctld_socket_open(ctrl->conf->host,
406 ctrl->conf->port);
407 if (ctrl->client.socket == -1)
408 return GINT_TO_POINTER(-1);
409
410 ctrl->client.timer = g_timer_new();
411
412 ctrl->client.new_trg = FALSE;
413 ctrl->client.running = TRUE;
414
415 while (ctrl->client.running)
416 {
417 g_timer_start(ctrl->client.timer);
418 io_error = FALSE;
419
420 g_mutex_lock(&ctrl->client.mutex);
421 if (ctrl->client.new_trg)
422 {
423 azi = ctrl->client.azi_out;
424 ele = ctrl->client.ele_out;
425 new_trg = ctrl->client.new_trg;
426 }
427 g_mutex_unlock(&ctrl->client.mutex);
428
429 if (new_trg && !ctrl->monitor)
430 {
431 if (set_pos(ctrl, azi, ele))
432 new_trg = FALSE;
433 else
434 io_error = TRUE;
435 }
436
437 /* wait 100 ms before sending new command */
438 g_usleep(100000);
439 if (!get_pos(ctrl, &azi, &ele))
440 ctrl->client.io_error = TRUE;
441
442 g_mutex_lock(&ctrl->client.mutex);
443 ctrl->client.azi_in = azi;
444 ctrl->client.ele_in = ele;
445 ctrl->client.new_trg = new_trg;
446 ctrl->client.io_error = io_error;
447 g_mutex_unlock(&ctrl->client.mutex);
448
449 /* ensure rotctl duty cycle stays below 50%, but wait at least 700 ms (TBC) */
450 elapsed_time = MAX(g_timer_elapsed(ctrl->client.timer, NULL), 0.7);
451 g_usleep(elapsed_time * 1e6);
452 }
453
454 g_print("Stopping rotctld client thread\n");
455 g_timer_destroy(ctrl->client.timer);
456 rotctld_socket_close(&ctrl->client.socket);
457
458 return GINT_TO_POINTER(0);
459 }
460
461 /**
462 * Update count down label.
463 *
464 * \param ctrl Pointer to the RotCtrl widget.
465 * \param t The current time.
466 *
467 * This function calculates the new time to AOS/LOS of the currently
468 * selected target and updates the ctrl->SatCnt label widget.
469 */
update_count_down(GtkRotCtrl * ctrl,gdouble t)470 static void update_count_down(GtkRotCtrl * ctrl, gdouble t)
471 {
472 gdouble targettime;
473 gdouble delta;
474 gchar *buff;
475 guint h, m, s;
476
477 /* select AOS or LOS time depending on target elevation */
478 if (ctrl->target->el < 0.0)
479 targettime = ctrl->target->aos;
480 else
481 targettime = ctrl->target->los;
482
483 delta = targettime - t;
484
485 /* convert julian date to seconds */
486 s = (guint) (delta * 86400);
487
488 /* extract hours */
489 h = (guint) floor(s / 3600);
490 s -= 3600 * h;
491
492 /* extract minutes */
493 m = (guint) floor(s / 60);
494 s -= 60 * m;
495
496 if (h > 0)
497 buff = g_strdup_printf("%02d:%02d:%02d", h, m, s);
498 else
499 buff = g_strdup_printf("%02d:%02d", m, s);
500
501 gtk_label_set_text(GTK_LABEL(ctrl->SatCnt), buff);
502
503 g_free(buff);
504 }
505
506 /*
507 * Update rotator control state.
508 *
509 * This function is called by the parent, i.e. GtkSatModule, indicating that
510 * the satellite data has been updated. The function updates the internal state
511 * of the controller and the rotator.
512 */
gtk_rot_ctrl_update(GtkRotCtrl * ctrl,gdouble t)513 void gtk_rot_ctrl_update(GtkRotCtrl * ctrl, gdouble t)
514 {
515 gchar *buff;
516
517 ctrl->t = t;
518
519 if (ctrl->target)
520 {
521 /* update target displays */
522 buff = g_strdup_printf(FMTSTR, ctrl->target->az);
523 gtk_label_set_text(GTK_LABEL(ctrl->AzSat), buff);
524 g_free(buff);
525 buff = g_strdup_printf(FMTSTR, ctrl->target->el);
526 gtk_label_set_text(GTK_LABEL(ctrl->ElSat), buff);
527 g_free(buff);
528
529 update_count_down(ctrl, t);
530
531 /*if the current pass is too far away */
532 if ((ctrl->pass != NULL))
533 if (qth_small_dist(ctrl->qth, ctrl->pass->qth_comp) > 1.0)
534 {
535 free_pass(ctrl->pass);
536 ctrl->pass = NULL;
537 ctrl->pass = get_pass(ctrl->target, ctrl->qth, t, 3.0);
538 if (ctrl->pass)
539 {
540 set_flipped_pass(ctrl);
541 /* update polar plot */
542 gtk_polar_plot_set_pass(GTK_POLAR_PLOT(ctrl->plot),
543 ctrl->pass);
544 }
545 }
546
547 /* update next pass if necessary */
548 if (ctrl->pass != NULL)
549 {
550 /* if we are not in the current pass */
551 if ((ctrl->pass->aos > t) || (ctrl->pass->los < t))
552 {
553 /* the pass may not have met the minimum
554 elevation, calculate the pass and plot it */
555 if (ctrl->target->el >= 0.0)
556 {
557 /* inside an unexpected/unpredicted pass */
558 free_pass(ctrl->pass);
559 ctrl->pass = NULL;
560 ctrl->pass = get_current_pass(ctrl->target, ctrl->qth, t);
561 set_flipped_pass(ctrl);
562 gtk_polar_plot_set_pass(GTK_POLAR_PLOT(ctrl->plot),
563 ctrl->pass);
564 }
565 else if ((ctrl->target->aos - ctrl->pass->aos) >
566 (ctrl->delay / secday / 1000 / 4.0))
567 {
568 /* the target is expected to appear in a new pass
569 sufficiently later after the current pass says */
570
571 /* converted milliseconds to gpredict time and took a
572 fraction of it as a threshold for deciding a new pass */
573
574 /* if the next pass is not the one for the target */
575 free_pass(ctrl->pass);
576 ctrl->pass = NULL;
577 ctrl->pass = get_pass(ctrl->target, ctrl->qth, t, 3.0);
578 set_flipped_pass(ctrl);
579 /* update polar plot */
580 gtk_polar_plot_set_pass(GTK_POLAR_PLOT(ctrl->plot),
581 ctrl->pass);
582 }
583 }
584 else
585 {
586 /* inside a pass and target dropped below the
587 horizon so look for a new pass */
588 if (ctrl->target->el < 0.0)
589 {
590 free_pass(ctrl->pass);
591 ctrl->pass = NULL;
592 ctrl->pass = get_pass(ctrl->target, ctrl->qth, t, 3.0);
593 set_flipped_pass(ctrl);
594 /* update polar plot */
595 gtk_polar_plot_set_pass(GTK_POLAR_PLOT(ctrl->plot),
596 ctrl->pass);
597 }
598 }
599 }
600 else
601 {
602 /* we don't have any current pass; store the current one */
603 if (ctrl->target->el > 0.0)
604 ctrl->pass = get_current_pass(ctrl->target, ctrl->qth, t);
605 else
606 ctrl->pass = get_pass(ctrl->target, ctrl->qth, t, 3.0);
607
608 set_flipped_pass(ctrl);
609 /* update polar plot */
610 gtk_polar_plot_set_pass(GTK_POLAR_PLOT(ctrl->plot), ctrl->pass);
611 }
612 }
613 }
614
615 /* Select a satellite. */
gtk_rot_ctrl_select_sat(GtkRotCtrl * ctrl,gint catnum)616 void gtk_rot_ctrl_select_sat(GtkRotCtrl * ctrl, gint catnum)
617 {
618 sat_t *sat;
619 int i, n;
620
621 /* find index in satellite list */
622 n = g_slist_length(ctrl->sats);
623 for (i = 0; i < n; i++)
624 {
625 sat = SAT(g_slist_nth_data(ctrl->sats, i));
626 if (sat && sat->tle.catnr == catnum)
627 {
628 /* assume the index is the same in sat selector */
629 gtk_combo_box_set_active(GTK_COMBO_BOX(ctrl->SatSel), i);
630 break;
631 }
632 }
633 }
634
635 /*
636 * Create azimuth control widgets.
637 *
638 * This function creates and initialises the widgets for controlling the
639 * azimuth of the the rotator.
640 */
create_az_widgets(GtkRotCtrl * ctrl)641 static GtkWidget *create_az_widgets(GtkRotCtrl * ctrl)
642 {
643 GtkWidget *frame;
644 GtkWidget *table;
645 GtkWidget *label;
646
647 frame = gtk_frame_new(_("Azimuth"));
648
649 table = gtk_grid_new();
650 gtk_container_set_border_width(GTK_CONTAINER(table), 5);
651 gtk_grid_set_column_spacing(GTK_GRID(table), 5);
652 gtk_grid_set_row_spacing(GTK_GRID(table), 5);
653 gtk_container_add(GTK_CONTAINER(frame), table);
654
655 ctrl->AzSet = gtk_rot_knob_new(0.0, 360.0, 180.0);
656 gtk_grid_attach(GTK_GRID(table), ctrl->AzSet, 0, 0, 3, 1);
657
658 label = gtk_label_new(NULL);
659 gtk_label_set_markup(GTK_LABEL(label), _("Read:"));
660 g_object_set(label, "xalign", 1.0f, "yalign", 0.5f, NULL);
661 gtk_grid_attach(GTK_GRID(table), label, 0, 1, 1, 1);
662
663 ctrl->AzRead = gtk_label_new(" --- ");
664 g_object_set(ctrl->AzRead, "xalign", 0.0f, "yalign", 0.5f, NULL);
665 gtk_grid_attach(GTK_GRID(table), ctrl->AzRead, 1, 1, 1, 1);
666
667 return frame;
668 }
669
670 /*
671 * Create elevation control widgets.
672 *
673 * This function creates and initialises the widgets for controlling the
674 * elevation of the the rotator.
675 */
create_el_widgets(GtkRotCtrl * ctrl)676 static GtkWidget *create_el_widgets(GtkRotCtrl * ctrl)
677 {
678 GtkWidget *frame;
679 GtkWidget *table;
680 GtkWidget *label;
681
682 frame = gtk_frame_new(_("Elevation"));
683
684 table = gtk_grid_new();
685 gtk_container_set_border_width(GTK_CONTAINER(table), 5);
686 gtk_grid_set_column_spacing(GTK_GRID(table), 5);
687 gtk_grid_set_row_spacing(GTK_GRID(table), 5);
688 gtk_container_add(GTK_CONTAINER(frame), table);
689
690 ctrl->ElSet = gtk_rot_knob_new(0.0, 90.0, 45.0);
691 gtk_grid_attach(GTK_GRID(table), ctrl->ElSet, 0, 0, 3, 1);
692
693 label = gtk_label_new(NULL);
694 gtk_label_set_markup(GTK_LABEL(label), _("Read: "));
695 g_object_set(label, "xalign", 1.0f, "yalign", 0.5f, NULL);
696 gtk_grid_attach(GTK_GRID(table), label, 0, 1, 1, 1);
697
698 ctrl->ElRead = gtk_label_new(" --- ");
699 g_object_set(ctrl->ElRead, "xalign", 0.0f, "yalign", 0.5f, NULL);
700 gtk_grid_attach(GTK_GRID(table), ctrl->ElRead, 1, 1, 1, 1);
701
702 return frame;
703 }
704
705 /**
706 * Manage toggle signals (tracking)
707 *
708 * \param button Pointer to the GtkToggle button.
709 * \param data Pointer to the GtkRotCtrl widget.
710 */
track_toggle_cb(GtkToggleButton * button,gpointer data)711 static void track_toggle_cb(GtkToggleButton * button, gpointer data)
712 {
713 GtkRotCtrl *ctrl = GTK_ROT_CTRL(data);
714 gboolean locked;
715
716 locked = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(ctrl->LockBut));
717 ctrl->tracking = gtk_toggle_button_get_active(button);
718 gtk_widget_set_sensitive(ctrl->MonitorCheckBox,
719 !(ctrl->tracking || locked));
720 gtk_widget_set_sensitive(ctrl->AzSet, !ctrl->tracking);
721 gtk_widget_set_sensitive(ctrl->ElSet, !ctrl->tracking);
722 }
723
724
725 /**
726 * Rotator controller timeout function
727 *
728 * \param data Pointer to the GtkRotCtrl widget.
729 * \return Always TRUE to let the timer continue.
730 */
rot_ctrl_timeout_cb(gpointer data)731 static gboolean rot_ctrl_timeout_cb(gpointer data)
732 {
733 GtkRotCtrl *ctrl = GTK_ROT_CTRL(data);
734 gdouble rotaz = 0.0, rotel = 0.0;
735 gdouble setaz = 0.0, setel = 45.0;
736 gchar *text;
737 gboolean error = FALSE;
738 sat_t sat_working, *sat;
739
740 /* parameters for path predictions */
741 gdouble time_delta;
742 gdouble step_size;
743
744
745 /* If we are tracking and the target satellite is within
746 range, set the rotor position controller knob values to
747 the target values. If the target satellite is out of range
748 set the rotor controller to 0 deg El and to the Az where the
749 target sat is expected to come up or where it last went down
750 */
751 if (ctrl->tracking && ctrl->target)
752 {
753 if (ctrl->target->el < 0.0)
754 {
755 if (ctrl->pass != NULL)
756 {
757 if (ctrl->t < ctrl->pass->aos)
758 {
759 setaz = ctrl->pass->aos_az;
760 setel = 0;
761 }
762 else if (ctrl->t > ctrl->pass->los)
763 {
764 setaz = ctrl->pass->los_az;
765 setel = 0;
766 }
767 }
768 }
769 else
770 {
771 setaz = ctrl->target->az;
772 setel = ctrl->target->el;
773 }
774 /* if this is a flipped pass and the rotor supports it */
775 if ((ctrl->flipped) && (ctrl->conf->maxel >= 180.0))
776 {
777 setel = 180 - setel;
778 if (setaz > 180)
779 setaz -= 180;
780 else
781 setaz += 180;
782
783 while (setaz > ctrl->conf->maxaz)
784 setaz -= 360;
785
786 while (setaz < ctrl->conf->minaz)
787 setaz += 360;
788 }
789
790 if ((ctrl->conf->aztype == ROT_AZ_TYPE_180) && (setaz > 180.0))
791 setaz = setaz - 360.0;
792
793 if (!(ctrl->engaged))
794 {
795 gtk_rot_knob_set_value(GTK_ROT_KNOB(ctrl->AzSet), setaz);
796 gtk_rot_knob_set_value(GTK_ROT_KNOB(ctrl->ElSet), setel);
797 }
798
799 }
800 else
801 {
802 setaz = gtk_rot_knob_get_value(GTK_ROT_KNOB(ctrl->AzSet));
803 setel = gtk_rot_knob_get_value(GTK_ROT_KNOB(ctrl->ElSet));
804 }
805
806 if ((ctrl->engaged) && (ctrl->conf != NULL))
807 {
808
809 if (g_mutex_trylock(&ctrl->client.mutex))
810 {
811 error = ctrl->client.io_error;
812 rotaz = ctrl->client.azi_in;
813 rotel = ctrl->client.ele_in;
814 g_mutex_unlock(&ctrl->client.mutex);
815
816 if (error)
817 {
818 gtk_label_set_text(GTK_LABEL(ctrl->AzRead), _("ERROR"));
819 gtk_label_set_text(GTK_LABEL(ctrl->ElRead), _("ERROR"));
820 gtk_polar_plot_set_rotor_pos(GTK_POLAR_PLOT(ctrl->plot),
821 -10.0, -10.0);
822 }
823 else
824 {
825 /* update display widgets */
826 text = g_strdup_printf("%.2f\302\260", rotaz);
827 gtk_label_set_text(GTK_LABEL(ctrl->AzRead), text);
828 g_free(text);
829 text = g_strdup_printf("%.2f\302\260", rotel);
830 gtk_label_set_text(GTK_LABEL(ctrl->ElRead), text);
831 g_free(text);
832
833 if ((ctrl->conf->aztype == ROT_AZ_TYPE_180) && (rotaz < 0.0))
834 {
835 gtk_polar_plot_set_rotor_pos(GTK_POLAR_PLOT(ctrl->plot),
836 rotaz + 360.0, rotel);
837 }
838 else
839 {
840 gtk_polar_plot_set_rotor_pos(GTK_POLAR_PLOT(ctrl->plot),
841 rotaz, rotel);
842 }
843 }
844 }
845
846 /* if tolerance exceeded */
847 if ((fabs(setaz - rotaz) > ctrl->tolerance) ||
848 (fabs(setel - rotel) > ctrl->tolerance))
849 {
850 if (ctrl->tracking)
851 {
852 /* if we are in a pass try to lead the satellite
853 some so we are not always chasing it */
854 if (ctrl->target && ctrl->target->el > 0.0)
855 {
856 /* starting the rotator moving while we do some computation
857 * can lead to errors later */
858 /* compute a time in the future when the position is
859 within tolerance so and send the rotor there.
860 */
861
862 /* use a working copy so data does not get corrupted */
863 sat = memcpy(&(sat_working), ctrl->target, sizeof(sat_t));
864
865 /* compute az/el in the future that is past end of pass
866 or exceeds tolerance
867 */
868 if (ctrl->pass)
869 {
870 /* the next point is before the end of the pass
871 if there is one. */
872 time_delta = ctrl->pass->los - ctrl->t;
873 }
874 else
875 {
876 /* otherwise look 20 minutes into the future */
877 time_delta = 1.0 / 72.0;
878 }
879
880 /* have a minimum time delta */
881 step_size = time_delta / 2.0;
882 if (step_size < ctrl->delay / 1000.0 / (secday))
883 {
884 step_size = ctrl->delay / 1000.0 / (secday);
885 }
886 /* find a time when satellite is above horizon and at the
887 edge of tolerance. the final step size needs to be smaller
888 than delay. otherwise the az/el could be further away than
889 tolerance the next time we enter the loop and we end up
890 pushing ourselves away from the satellite.
891 */
892 while (step_size > (ctrl->delay / 1000.0 / 4.0 / (secday)))
893 {
894 predict_calc(sat, ctrl->qth, ctrl->t + time_delta);
895 /*update sat->az and sat->el to account for flips and az range */
896 if ((ctrl->flipped) && (ctrl->conf->maxel >= 180.0))
897 {
898 sat->el = 180.0 - sat->el;
899 if (sat->az > 180.0)
900 sat->az -= 180.0;
901 else
902 sat->az += 180.0;
903 }
904 if ((ctrl->conf->aztype == ROT_AZ_TYPE_180) &&
905 (sat->az > 180.0))
906 {
907 sat->az = sat->az - 360.0;
908 }
909 if ((sat->el < 0.0) || (sat->el > 180.0) ||
910 (fabs(setaz - sat->az) > (ctrl->tolerance)) ||
911 (fabs(setel - sat->el) > (ctrl->tolerance)))
912 {
913 time_delta -= step_size;
914 }
915 else
916 {
917 time_delta += step_size;
918 }
919 step_size /= 2.0;
920 }
921 setel = sat->el;
922 if (setel < 0.0)
923 setel = 0.0;
924
925 if (setel > 180.0)
926 setel = 180.0;
927
928 setaz = sat->az;
929 }
930 }
931
932 /* send controller values to rotator device */
933 /* this is the newly computed value which should be ahead of the current position */
934 gtk_rot_knob_set_value(GTK_ROT_KNOB(ctrl->AzSet), setaz);
935 gtk_rot_knob_set_value(GTK_ROT_KNOB(ctrl->ElSet), setel);
936 if (g_mutex_trylock(&ctrl->client.mutex))
937 {
938 ctrl->client.azi_out = setaz;
939 ctrl->client.ele_out = setel;
940 ctrl->client.new_trg = TRUE;
941 g_mutex_unlock(&ctrl->client.mutex);
942 }
943
944 }
945
946 /* check error status */
947 if (!error)
948 {
949 /* reset error counter */
950 ctrl->errcnt = 0;
951 }
952 else
953 {
954 if (ctrl->errcnt >= MAX_ERROR_COUNT)
955 {
956 /* disengage device */
957 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ctrl->LockBut),
958 FALSE);
959 ctrl->engaged = FALSE;
960 sat_log_log(SAT_LOG_LEVEL_ERROR,
961 _
962 ("%s: MAX_ERROR_COUNT (%d) reached. Disengaging device!"),
963 __func__, MAX_ERROR_COUNT);
964 ctrl->errcnt = 0;
965 //g_print ("ERROR. WROPS: %d RDOPS: %d\n", ctrl->wrops, ctrl->rdops);
966 }
967 else
968 {
969 /* increment error counter */
970 ctrl->errcnt++;
971 }
972 }
973 }
974 else
975 {
976 /* ensure rotor pos is not visible on plot */
977 gtk_polar_plot_set_rotor_pos(GTK_POLAR_PLOT(ctrl->plot), -10.0, -10.0);
978 }
979
980 /* update target object on polar plot */
981 if (ctrl->target != NULL)
982 {
983 gtk_polar_plot_set_target_pos(GTK_POLAR_PLOT(ctrl->plot),
984 ctrl->target->az, ctrl->target->el);
985 }
986
987 /* update controller circle on polar plot */
988 if (ctrl->conf != NULL)
989 {
990 if ((ctrl->conf->aztype == ROT_AZ_TYPE_180) && (setaz < 0.0))
991 {
992 gtk_polar_plot_set_ctrl_pos(GTK_POLAR_PLOT(ctrl->plot),
993 gtk_rot_knob_get_value(GTK_ROT_KNOB
994 (ctrl->AzSet)) +
995 360.0,
996 gtk_rot_knob_get_value(GTK_ROT_KNOB
997 (ctrl->ElSet)));
998 }
999 else
1000 {
1001 gtk_polar_plot_set_ctrl_pos(GTK_POLAR_PLOT(ctrl->plot),
1002 gtk_rot_knob_get_value(GTK_ROT_KNOB
1003 (ctrl->AzSet)),
1004 gtk_rot_knob_get_value(GTK_ROT_KNOB
1005 (ctrl->ElSet)));
1006 }
1007 gtk_widget_queue_draw(ctrl->plot);
1008 }
1009
1010 return TRUE;
1011 }
1012
1013 /**
1014 * Manage cycle delay changes.
1015 *
1016 * \param spin Pointer to the spin button.
1017 * \param data Pointer to the GtkRotCtrl widget.
1018 *
1019 * This function is called when the user changes the value of the
1020 * cycle delay.
1021 */
delay_changed_cb(GtkSpinButton * spin,gpointer data)1022 static void delay_changed_cb(GtkSpinButton * spin, gpointer data)
1023 {
1024 GtkRotCtrl *ctrl = GTK_ROT_CTRL(data);
1025
1026 ctrl->delay = (guint) gtk_spin_button_get_value(spin);
1027
1028 if (ctrl->timerid > 0)
1029 g_source_remove(ctrl->timerid);
1030
1031 ctrl->timerid = g_timeout_add(ctrl->delay, rot_ctrl_timeout_cb, ctrl);
1032 }
1033
1034 /**
1035 * Manage tolerance changes.
1036 *
1037 * \param spin Pointer to the spin button.
1038 * \param data Pointer to the GtkRotCtrl widget.
1039 *
1040 * This function is called when the user changes the value of the
1041 * tolerance.
1042 */
toler_changed_cb(GtkSpinButton * spin,gpointer data)1043 static void toler_changed_cb(GtkSpinButton * spin, gpointer data)
1044 {
1045 GtkRotCtrl *ctrl = GTK_ROT_CTRL(data);
1046
1047 ctrl->tolerance = gtk_spin_button_get_value(spin);
1048 }
1049
1050 /**
1051 * New rotor device selected.
1052 *
1053 * \param box Pointer to the rotor selector combo box.
1054 * \param data Pointer to the GtkRotCtrl widget.
1055 *
1056 * This function is called when the user selects a new rotor controller
1057 * device.
1058 */
rot_selected_cb(GtkComboBox * box,gpointer data)1059 static void rot_selected_cb(GtkComboBox * box, gpointer data)
1060 {
1061 GtkRotCtrl *ctrl = GTK_ROT_CTRL(data);
1062
1063 /* free previous configuration */
1064 if (ctrl->conf != NULL)
1065 {
1066 g_free(ctrl->conf->name);
1067 g_free(ctrl->conf->host);
1068 g_free(ctrl->conf);
1069 }
1070
1071 ctrl->conf = g_try_new(rotor_conf_t, 1);
1072 if (ctrl->conf == NULL)
1073 {
1074 sat_log_log(SAT_LOG_LEVEL_ERROR,
1075 _("%s:%d: Failed to allocate memory for rotator config"),
1076 __FILE__, __LINE__);
1077 return;
1078 }
1079
1080 /* load new configuration */
1081 ctrl->conf->name =
1082 gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(box));
1083 if (rotor_conf_read(ctrl->conf))
1084 {
1085 sat_log_log(SAT_LOG_LEVEL_INFO,
1086 _("Loaded new rotator configuration %s"),
1087 ctrl->conf->name);
1088
1089 /* update new ranges of the Az and El controller widgets */
1090 gtk_rot_knob_set_range(GTK_ROT_KNOB(ctrl->AzSet), ctrl->conf->minaz,
1091 ctrl->conf->maxaz);
1092 gtk_rot_knob_set_range(GTK_ROT_KNOB(ctrl->ElSet), ctrl->conf->minel,
1093 ctrl->conf->maxel);
1094
1095 /* Update flipped when changing rotor if there is a plot */
1096 set_flipped_pass(ctrl);
1097 }
1098 else
1099 {
1100 sat_log_log(SAT_LOG_LEVEL_ERROR,
1101 _("%s:%d: Failed to load rotator configuration %s"),
1102 __FILE__, __LINE__, ctrl->conf->name);
1103
1104 g_free(ctrl->conf->name);
1105 if (ctrl->conf->host)
1106 g_free(ctrl->conf->host);
1107 g_free(ctrl->conf);
1108 ctrl->conf = NULL;
1109 }
1110 }
1111
1112 /**
1113 * Monitor mode
1114 *
1115 * Inhibits command transmission
1116 */
rot_monitor_cb(GtkCheckButton * button,gpointer data)1117 static void rot_monitor_cb(GtkCheckButton * button, gpointer data)
1118 {
1119 GtkRotCtrl *ctrl = GTK_ROT_CTRL(data);
1120
1121 ctrl->monitor = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button));
1122 gtk_widget_set_sensitive(ctrl->AzSet, !ctrl->monitor);
1123 gtk_widget_set_sensitive(ctrl->ElSet, !ctrl->monitor);
1124 gtk_widget_set_sensitive(ctrl->track, !ctrl->monitor);
1125 }
1126
1127 /**
1128 * Rotor locked.
1129 *
1130 * \param button Pointer to the "Engage" button.
1131 * \param data Pointer to the GtkRotCtrl widget.
1132 *
1133 * This function is called when the user toggles the "Engage" button.
1134 */
rot_locked_cb(GtkToggleButton * button,gpointer data)1135 static void rot_locked_cb(GtkToggleButton * button, gpointer data)
1136 {
1137 GtkRotCtrl *ctrl = GTK_ROT_CTRL(data);
1138 gchar *buff;
1139 gchar buffback[128];
1140 gboolean retcode;
1141 gint retval;
1142
1143 if (!gtk_toggle_button_get_active(button))
1144 {
1145 ctrl->engaged = FALSE;
1146 gtk_widget_set_sensitive(ctrl->DevSel, TRUE);
1147 gtk_label_set_text(GTK_LABEL(ctrl->AzRead), "---");
1148 gtk_label_set_text(GTK_LABEL(ctrl->ElRead), "---");
1149
1150 if (!ctrl->client.running)
1151 /* client thread is not running; nothing to do */
1152 return;
1153
1154 /* stop moving rotor */
1155 /** FIXME: should use high level func */
1156 buff = g_strdup_printf("S\x0a");
1157 retcode = rotctld_socket_rw(ctrl->client.socket, buff, buffback, 128);
1158 g_free(buff);
1159 if (retcode == TRUE)
1160 {
1161 /* treat errors as soft errors */
1162 retval = (gint) g_strtod(buffback + 4, NULL);
1163 if (retval != 0)
1164 {
1165 g_strstrip(buffback);
1166 sat_log_log(SAT_LOG_LEVEL_ERROR,
1167 _
1168 ("%s:%d: rotctld returned error %d with stop-cmd (%s)"),
1169 __FILE__, __LINE__, retval, buffback);
1170 }
1171 }
1172
1173 ctrl->client.running = FALSE;
1174 g_thread_join(ctrl->client.thread);
1175 }
1176 else
1177 {
1178 if (ctrl->conf == NULL)
1179 {
1180 /* we don't have a working configuration */
1181 sat_log_log(SAT_LOG_LEVEL_ERROR,
1182 _
1183 ("%s: Controller does not have a valid configuration"),
1184 __func__);
1185 return;
1186 }
1187
1188 ctrl->client.thread =
1189 g_thread_new("gpredict_rotctl", rotctld_client_thread, ctrl);
1190
1191 gtk_widget_set_sensitive(ctrl->DevSel, FALSE);
1192 ctrl->engaged = TRUE;
1193 }
1194 }
1195
1196
1197 /**
1198 * Manage satellite selections
1199 *
1200 * \param satsel Pointer to the GtkComboBox.
1201 * \param data Pointer to the GtkRotCtrl widget.
1202 *
1203 * This function is called when the user selects a new satellite.
1204 */
sat_selected_cb(GtkComboBox * satsel,gpointer data)1205 static void sat_selected_cb(GtkComboBox * satsel, gpointer data)
1206 {
1207 GtkRotCtrl *ctrl = GTK_ROT_CTRL(data);
1208 gint i;
1209
1210 i = gtk_combo_box_get_active(satsel);
1211 if (i >= 0)
1212 {
1213 ctrl->target = SAT(g_slist_nth_data(ctrl->sats, i));
1214
1215 /* update next pass */
1216 if (ctrl->pass != NULL)
1217 free_pass(ctrl->pass);
1218
1219 if (ctrl->target->el > 0.0)
1220 ctrl->pass = get_current_pass(ctrl->target, ctrl->qth, ctrl->t);
1221 else
1222 ctrl->pass = get_pass(ctrl->target, ctrl->qth, ctrl->t, 3.0);
1223
1224 set_flipped_pass(ctrl);
1225 }
1226 else
1227 {
1228 sat_log_log(SAT_LOG_LEVEL_ERROR,
1229 _("%s:%s: Invalid satellite selection: %d"),
1230 __FILE__, __func__, i);
1231
1232 /* clear pass just in case... */
1233 if (ctrl->pass != NULL)
1234 {
1235 free_pass(ctrl->pass);
1236 ctrl->pass = NULL;
1237 }
1238 }
1239
1240 /* in either case, we set the new pass (even if NULL) on the polar plot */
1241 if (ctrl->plot != NULL)
1242 gtk_polar_plot_set_pass(GTK_POLAR_PLOT(ctrl->plot), ctrl->pass);
1243 }
1244
1245 /* Create target widgets */
create_target_widgets(GtkRotCtrl * ctrl)1246 static GtkWidget *create_target_widgets(GtkRotCtrl * ctrl)
1247 {
1248 GtkWidget *frame, *table, *label;
1249 gchar *buff;
1250 guint i, n;
1251 sat_t *sat = NULL;
1252
1253 buff = g_strdup_printf(FMTSTR, 0.0);
1254
1255 table = gtk_grid_new();
1256 gtk_container_set_border_width(GTK_CONTAINER(table), 5);
1257 gtk_grid_set_column_homogeneous(GTK_GRID(table), FALSE);
1258 gtk_grid_set_column_spacing(GTK_GRID(table), 5);
1259 gtk_grid_set_row_spacing(GTK_GRID(table), 5);
1260
1261 /* sat selector */
1262 ctrl->SatSel = gtk_combo_box_text_new();
1263 n = g_slist_length(ctrl->sats);
1264
1265 for (i = 0; i < n; i++)
1266 {
1267 sat = SAT(g_slist_nth_data(ctrl->sats, i));
1268 if (sat)
1269 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(ctrl->SatSel),
1270 sat->nickname);
1271 }
1272 gtk_combo_box_set_active(GTK_COMBO_BOX(ctrl->SatSel), 0);
1273 gtk_widget_set_tooltip_text(ctrl->SatSel, _("Select target object"));
1274 g_signal_connect(ctrl->SatSel, "changed", G_CALLBACK(sat_selected_cb),
1275 ctrl);
1276 gtk_grid_attach(GTK_GRID(table), ctrl->SatSel, 0, 0, 2, 1);
1277
1278 /* tracking button */
1279 ctrl->track = gtk_toggle_button_new_with_label(_("Track"));
1280 gtk_widget_set_tooltip_text(ctrl->track,
1281 _
1282 ("Track the satellite when it is within range"));
1283 gtk_grid_attach(GTK_GRID(table), ctrl->track, 2, 0, 1, 1);
1284 g_signal_connect(ctrl->track, "toggled", G_CALLBACK(track_toggle_cb),
1285 ctrl);
1286
1287 /* Azimuth */
1288 label = gtk_label_new(_("Az:"));
1289 g_object_set(label, "xalign", 1.0f, "yalign", 0.5f, NULL);
1290 gtk_grid_attach(GTK_GRID(table), label, 0, 1, 1, 1);
1291
1292 ctrl->AzSat = gtk_label_new(buff);
1293 g_object_set(label, "xalign", 1.0f, "yalign", 0.5f, NULL);
1294 gtk_grid_attach(GTK_GRID(table), ctrl->AzSat, 1, 1, 1, 1);
1295
1296 /* Elevation */
1297 label = gtk_label_new(_("El:"));
1298 g_object_set(label, "xalign", 1.0f, "yalign", 0.5f, NULL);
1299 gtk_grid_attach(GTK_GRID(table), label, 0, 2, 1, 1);
1300
1301 ctrl->ElSat = gtk_label_new(buff);
1302 g_object_set(label, "xalign", 1.0f, "yalign", 0.5f, NULL);
1303 gtk_grid_attach(GTK_GRID(table), ctrl->ElSat, 1, 2, 1, 1);
1304
1305 /* count down */
1306 label = gtk_label_new(_("\316\224T:"));
1307 g_object_set(label, "xalign", 1.0f, "yalign", 0.5f, NULL);
1308 gtk_grid_attach(GTK_GRID(table), label, 0, 3, 1, 1);
1309 ctrl->SatCnt = gtk_label_new("00:00:00");
1310 g_object_set(label, "xalign", 1.0f, "yalign", 0.5f, NULL);
1311 gtk_grid_attach(GTK_GRID(table), ctrl->SatCnt, 1, 3, 1, 1);
1312
1313 frame = gtk_frame_new(_("Target"));
1314 gtk_container_add(GTK_CONTAINER(frame), table);
1315
1316 g_free(buff);
1317
1318 return frame;
1319 }
1320
create_conf_widgets(GtkRotCtrl * ctrl)1321 static GtkWidget *create_conf_widgets(GtkRotCtrl * ctrl)
1322 {
1323 GtkWidget *frame, *table, *label, *timer, *toler;
1324 GDir *dir = NULL; /* directory handle */
1325 GError *error = NULL; /* error flag and info */
1326 gchar *dirname; /* directory name */
1327 gchar **vbuff;
1328 const gchar *filename; /* file name */
1329 gchar *rotname;
1330
1331 table = gtk_grid_new();
1332 gtk_container_set_border_width(GTK_CONTAINER(table), 5);
1333 gtk_grid_set_column_spacing(GTK_GRID(table), 5);
1334 gtk_grid_set_row_spacing(GTK_GRID(table), 5);
1335
1336 label = gtk_label_new(_("Device:"));
1337 g_object_set(label, "xalign", 1.0f, "yalign", 0.5f, NULL);
1338 gtk_grid_attach(GTK_GRID(table), label, 0, 0, 1, 1);
1339
1340 ctrl->DevSel = gtk_combo_box_text_new();
1341 gtk_widget_set_tooltip_text(ctrl->DevSel,
1342 _("Select antenna rotator device"));
1343
1344 /* open configuration directory */
1345 dirname = get_hwconf_dir();
1346
1347 dir = g_dir_open(dirname, 0, &error);
1348 if (dir)
1349 {
1350 /* read each .rot file */
1351 GSList *rots = NULL;
1352 gint i;
1353 gint n;
1354
1355 while ((filename = g_dir_read_name(dir)))
1356 {
1357 if (g_str_has_suffix(filename, ".rot"))
1358 {
1359 vbuff = g_strsplit(filename, ".rot", 0);
1360 rots =
1361 g_slist_insert_sorted(rots, g_strdup(vbuff[0]),
1362 (GCompareFunc) rot_name_compare);
1363 g_strfreev(vbuff);
1364 }
1365 }
1366 n = g_slist_length(rots);
1367 for (i = 0; i < n; i++)
1368 {
1369 rotname = g_slist_nth_data(rots, i);
1370 if (rotname)
1371 {
1372 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT
1373 (ctrl->DevSel), rotname);
1374 g_free(rotname);
1375 }
1376 }
1377 g_slist_free(rots);
1378 }
1379 else
1380 {
1381 sat_log_log(SAT_LOG_LEVEL_ERROR,
1382 _("%s:%d: Failed to open hwconf dir (%s)"),
1383 __FILE__, __LINE__, error->message);
1384 g_clear_error(&error);
1385 }
1386
1387 g_free(dirname);
1388 g_dir_close(dir);
1389
1390 gtk_combo_box_set_active(GTK_COMBO_BOX(ctrl->DevSel), 0);
1391 g_signal_connect(ctrl->DevSel, "changed", G_CALLBACK(rot_selected_cb),
1392 ctrl);
1393 gtk_grid_attach(GTK_GRID(table), ctrl->DevSel, 1, 0, 1, 1);
1394
1395 /* Engage button */
1396 ctrl->LockBut = gtk_toggle_button_new_with_label(_("Engage"));
1397 gtk_widget_set_tooltip_text(ctrl->LockBut,
1398 _("Engage the selected rotor device"));
1399 g_signal_connect(ctrl->LockBut, "toggled", G_CALLBACK(rot_locked_cb),
1400 ctrl);
1401 gtk_grid_attach(GTK_GRID(table), ctrl->LockBut, 2, 0, 1, 1);
1402
1403 /* Monitor checkbox */
1404 ctrl->MonitorCheckBox = gtk_check_button_new_with_label(_("Monitor"));
1405 gtk_widget_set_tooltip_text(ctrl->MonitorCheckBox,
1406 _("Monitor rotator but do not send any "
1407 "position commands"));
1408 g_signal_connect(ctrl->MonitorCheckBox, "toggled",
1409 G_CALLBACK(rot_monitor_cb), ctrl);
1410 gtk_grid_attach(GTK_GRID(table), ctrl->MonitorCheckBox, 1, 1, 1, 1);
1411
1412 /* Timeout */
1413 label = gtk_label_new(_("Cycle:"));
1414 g_object_set(label, "xalign", 1.0f, "yalign", 0.5f, NULL);
1415 gtk_grid_attach(GTK_GRID(table), label, 0, 2, 1, 1);
1416
1417 timer = gtk_spin_button_new_with_range(1000, 10000, 10);
1418 gtk_spin_button_set_digits(GTK_SPIN_BUTTON(timer), 0);
1419 gtk_widget_set_tooltip_text(timer,
1420 _("This parameter controls the delay between "
1421 "commands sent to the rotator."));
1422 gtk_spin_button_set_value(GTK_SPIN_BUTTON(timer), ctrl->delay);
1423 g_signal_connect(timer, "value-changed", G_CALLBACK(delay_changed_cb),
1424 ctrl);
1425 gtk_grid_attach(GTK_GRID(table), timer, 1, 2, 1, 1);
1426
1427 label = gtk_label_new(_("msec"));
1428 g_object_set(label, "xalign", 0.0f, "yalign", 0.5f, NULL);
1429 gtk_grid_attach(GTK_GRID(table), label, 2, 2, 1, 1);
1430
1431 /* Tolerance */
1432 label = gtk_label_new(_("Tolerance:"));
1433 g_object_set(label, "xalign", 1.0f, "yalign", 0.5f, NULL);
1434 gtk_grid_attach(GTK_GRID(table), label, 0, 3, 1, 1);
1435
1436 toler = gtk_spin_button_new_with_range(0.01, 50.0, 0.01);
1437 gtk_spin_button_set_digits(GTK_SPIN_BUTTON(toler), 2);
1438 gtk_widget_set_tooltip_text(toler,
1439 _("This parameter controls the tolerance "
1440 "between the target and rotator values for "
1441 "the rotator.\n"
1442 "If the difference between the target and "
1443 "rotator values is smaller than the "
1444 "tolerance, no new commands are sent"));
1445 gtk_spin_button_set_value(GTK_SPIN_BUTTON(toler), ctrl->tolerance);
1446 g_signal_connect(toler, "value-changed", G_CALLBACK(toler_changed_cb),
1447 ctrl);
1448 gtk_grid_attach(GTK_GRID(table), toler, 1, 3, 1, 1);
1449
1450 label = gtk_label_new(_("deg"));
1451 g_object_set(label, "xalign", 0.0f, "yalign", 0.5f, NULL);
1452 gtk_grid_attach(GTK_GRID(table), label, 2, 3, 1, 1);
1453
1454 /* load initial rotator configuration */
1455 rot_selected_cb(GTK_COMBO_BOX(ctrl->DevSel), ctrl);
1456
1457 frame = gtk_frame_new(_("Settings"));
1458 gtk_container_add(GTK_CONTAINER(frame), table);
1459
1460 return frame;
1461 }
1462
1463 /* Create target widgets */
create_plot_widget(GtkRotCtrl * ctrl)1464 static GtkWidget *create_plot_widget(GtkRotCtrl * ctrl)
1465 {
1466 GtkWidget *frame;
1467
1468 ctrl->plot = gtk_polar_plot_new(ctrl->qth, ctrl->pass);
1469
1470 frame = gtk_frame_new(NULL);
1471 gtk_container_add(GTK_CONTAINER(frame), ctrl->plot);
1472
1473 return frame;
1474 }
1475
1476 /** Copy satellite from hash table to singly linked list. */
store_sats(gpointer key,gpointer value,gpointer user_data)1477 static void store_sats(gpointer key, gpointer value, gpointer user_data)
1478 {
1479 GtkRotCtrl *ctrl = GTK_ROT_CTRL(user_data);
1480 sat_t *sat = SAT(value);
1481
1482 (void)key; /* avoid unused variable warning */
1483
1484 ctrl->sats = g_slist_insert_sorted(ctrl->sats, sat,
1485 (GCompareFunc) sat_name_compare);
1486 }
1487
1488 /** Check that we have at least one .rot file */
have_conf()1489 static gboolean have_conf()
1490 {
1491 GDir *dir = NULL; /* directory handle */
1492 GError *error = NULL; /* error flag and info */
1493 gchar *dirname; /* directory name */
1494 const gchar *filename; /* file name */
1495 gint i = 0;
1496
1497 /* open configuration directory */
1498 dirname = get_hwconf_dir();
1499
1500 dir = g_dir_open(dirname, 0, &error);
1501 if (dir)
1502 {
1503 /* read each .rot file */
1504 while ((filename = g_dir_read_name(dir)))
1505 {
1506 if (g_str_has_suffix(filename, ".rot"))
1507 {
1508 i++;
1509 /*once we have one we need nothing else */
1510 break;
1511 }
1512 }
1513 }
1514 else
1515 {
1516 sat_log_log(SAT_LOG_LEVEL_ERROR,
1517 _("%s:%d: Failed to open hwconf dir (%s)"),
1518 __FILE__, __LINE__, error->message);
1519 g_clear_error(&error);
1520 }
1521
1522 g_free(dirname);
1523 g_dir_close(dir);
1524
1525 return (i > 0) ? TRUE : FALSE;
1526 }
1527
gtk_rot_ctrl_init(GtkRotCtrl * ctrl)1528 static void gtk_rot_ctrl_init(GtkRotCtrl * ctrl)
1529 {
1530 ctrl->sats = NULL;
1531 ctrl->target = NULL;
1532 ctrl->pass = NULL;
1533 ctrl->qth = NULL;
1534 ctrl->plot = NULL;
1535
1536 ctrl->tracking = FALSE;
1537 ctrl->engaged = FALSE;
1538 ctrl->delay = 1000;
1539 ctrl->timerid = 0;
1540 ctrl->tolerance = 5.0;
1541 ctrl->errcnt = 0;
1542
1543 g_mutex_init(&ctrl->client.mutex);
1544 ctrl->client.thread = NULL;
1545 ctrl->client.socket = -1;
1546 ctrl->client.running = FALSE;
1547 }
1548
gtk_rot_ctrl_destroy(GtkWidget * widget)1549 static void gtk_rot_ctrl_destroy(GtkWidget * widget)
1550 {
1551 GtkRotCtrl *ctrl = GTK_ROT_CTRL(widget);
1552
1553 /* stop timer */
1554 if (ctrl->timerid > 0)
1555 g_source_remove(ctrl->timerid);
1556
1557 /* free configuration */
1558 if (ctrl->conf != NULL)
1559 {
1560 g_free(ctrl->conf->name);
1561 g_free(ctrl->conf->host);
1562 g_free(ctrl->conf);
1563 ctrl->conf = NULL;
1564 }
1565
1566 /* stop client thread */
1567 if (ctrl->client.running)
1568 {
1569 ctrl->client.running = FALSE;
1570 g_thread_join(ctrl->client.thread);
1571 }
1572
1573 g_mutex_clear(&ctrl->client.mutex);
1574
1575 (*GTK_WIDGET_CLASS(parent_class)->destroy) (widget);
1576 }
1577
gtk_rot_ctrl_class_init(GtkRotCtrlClass * class)1578 static void gtk_rot_ctrl_class_init(GtkRotCtrlClass * class)
1579 {
1580 GtkWidgetClass *widget_class = (GtkWidgetClass *) class;
1581
1582 widget_class->destroy = gtk_rot_ctrl_destroy;
1583 parent_class = g_type_class_peek_parent(class);
1584 }
1585
gtk_rot_ctrl_get_type()1586 GType gtk_rot_ctrl_get_type()
1587 {
1588 static GType gtk_rot_ctrl_type = 0;
1589
1590 if (!gtk_rot_ctrl_type)
1591 {
1592 static const GTypeInfo gtk_rot_ctrl_info = {
1593 sizeof(GtkRotCtrlClass),
1594 NULL, /* base_init */
1595 NULL, /* base_finalize */
1596 (GClassInitFunc) gtk_rot_ctrl_class_init,
1597 NULL, /* class_finalize */
1598 NULL, /* class_data */
1599 sizeof(GtkRotCtrl),
1600 5, /* n_preallocs */
1601 (GInstanceInitFunc) gtk_rot_ctrl_init,
1602 NULL
1603 };
1604
1605 gtk_rot_ctrl_type = g_type_register_static(GTK_TYPE_BOX,
1606 "GtkRotCtrl",
1607 >k_rot_ctrl_info, 0);
1608 }
1609
1610 return gtk_rot_ctrl_type;
1611 }
1612
gtk_rot_ctrl_new(GtkSatModule * module)1613 GtkWidget *gtk_rot_ctrl_new(GtkSatModule * module)
1614 {
1615 GtkRotCtrl *rot_ctrl;
1616 GtkWidget *table;
1617
1618 /* check that we have rot conf */
1619 if (!have_conf())
1620 return NULL;
1621
1622 rot_ctrl = GTK_ROT_CTRL(g_object_new(GTK_TYPE_ROT_CTRL, NULL));
1623
1624 /* store satellites */
1625 g_hash_table_foreach(module->satellites, store_sats, rot_ctrl);
1626
1627 rot_ctrl->target = SAT(g_slist_nth_data(rot_ctrl->sats, 0));
1628
1629 /* store current time (don't know if real or simulated) */
1630 rot_ctrl->t = module->tmgCdnum;
1631
1632 /* store QTH */
1633 rot_ctrl->qth = module->qth;
1634
1635 /* get next pass for target satellite */
1636 if (rot_ctrl->target)
1637 {
1638 if (rot_ctrl->target->el > 0.0)
1639 {
1640 rot_ctrl->pass = get_current_pass(rot_ctrl->target,
1641 rot_ctrl->qth, 0.0);
1642 }
1643 else
1644 {
1645 rot_ctrl->pass = get_next_pass(rot_ctrl->target,
1646 rot_ctrl->qth, 3.0);
1647 }
1648 }
1649
1650 /* create contents */
1651 table = gtk_grid_new();
1652 gtk_grid_set_column_homogeneous(GTK_GRID(table), TRUE);
1653 gtk_grid_set_row_homogeneous(GTK_GRID(table), FALSE);
1654 gtk_grid_set_row_spacing(GTK_GRID(table), 5);
1655 gtk_grid_set_column_spacing(GTK_GRID(table), 5);
1656 gtk_container_set_border_width(GTK_CONTAINER(table), 0);
1657 gtk_grid_attach(GTK_GRID(table), create_az_widgets(rot_ctrl), 0, 0, 1, 1);
1658 gtk_grid_attach(GTK_GRID(table), create_el_widgets(rot_ctrl), 1, 0, 1, 1);
1659 gtk_grid_attach(GTK_GRID(table), create_target_widgets(rot_ctrl),
1660 0, 1, 1, 1);
1661 gtk_grid_attach(GTK_GRID(table), create_conf_widgets(rot_ctrl),
1662 1, 1, 1, 1);
1663
1664 gtk_box_pack_start(GTK_BOX(rot_ctrl), create_plot_widget(rot_ctrl),
1665 TRUE, TRUE, 5);
1666 gtk_box_pack_start(GTK_BOX(rot_ctrl), table, FALSE, FALSE, 5);
1667 gtk_container_set_border_width(GTK_CONTAINER(rot_ctrl), 5);
1668
1669 rot_ctrl->timerid = g_timeout_add(rot_ctrl->delay,
1670 rot_ctrl_timeout_cb, rot_ctrl);
1671
1672 if (module->target > 0)
1673 gtk_rot_ctrl_select_sat(rot_ctrl, module->target);
1674
1675 return GTK_WIDGET(rot_ctrl);
1676 }
1677