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                                                    &gtk_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