1 /*
2  *  Tvheadend - Linux DVB DiseqC Rotor
3  *
4  *  Copyright (C) 2013 Adam Sutton
5  *
6  *  This program is free software: you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License as published by
8  *  the Free Software Foundation, either version 3 of the License, or
9  *  (at your option) any later version.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #ifndef ROTOR_TEST
21 #include "tvheadend.h"
22 #include "linuxdvb_private.h"
23 #include "settings.h"
24 
25 #include <sys/ioctl.h>
26 #include <sys/types.h>
27 #include <sys/stat.h>
28 #include <unistd.h>
29 #include <fcntl.h>
30 #include <assert.h>
31 #include <math.h>
32 #include <linux/dvb/frontend.h>
33 
34 #else /* ROTOR_TEST */
35 
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <stdint.h>
39 #include <string.h>
40 #include <math.h>
41 
42 typedef struct {
43   double   ls_site_lat;
44   double   ls_site_lon;
45   double   ls_site_altitude;
46   int      ls_site_lat_south;
47   int      ls_site_lon_west;
48 } linuxdvb_satconf_t;
49 
50 typedef struct {
51   linuxdvb_satconf_t *lse_parent;
52 } linuxdvb_satconf_ele_t;
53 
54 double gazimuth, gelevation;
55 
56 #endif
57 
58 /* **************************************************************************
59  * Class definition
60  * *************************************************************************/
61 
62 typedef struct linuxdvb_rotor
63 {
64 #ifndef ROTOR_TEST
65   linuxdvb_diseqc_t;
66 #endif
67 
68   uint32_t  lr_powerup_time;
69   uint32_t  lr_cmd_time;
70 
71   double    lr_sat_lon;
72   uint32_t  lr_position;
73 
74 } linuxdvb_rotor_t;
75 
76 #ifndef ROTOR_TEST
77 
78 static const char *
linuxdvb_rotor_class_get_title(idnode_t * o,const char * lang)79 linuxdvb_rotor_class_get_title ( idnode_t *o, const char *lang )
80 {
81   static char buf[256];
82   linuxdvb_diseqc_t *ld = (linuxdvb_diseqc_t*)o;
83   snprintf(buf, sizeof(buf), "Rotor: %s", ld->ld_type);
84   return buf;
85 }
86 
87 extern const idclass_t linuxdvb_diseqc_class;
88 
89 CLASS_DOC(linuxdvb_satconf)
90 
91 const idclass_t linuxdvb_rotor_class = {
92   .ic_super       = &linuxdvb_diseqc_class,
93   .ic_class       = "linuxdvb_rotor",
94   .ic_doc         = tvh_doc_linuxdvb_satconf_class,
95   .ic_caption     = N_("TV Adapters - SatConfig - DiseqC Rotor"),
96   .ic_get_title   = linuxdvb_rotor_class_get_title,
97   .ic_properties  = (const property_t[]) {
98     {
99       .type    = PT_U32,
100       .id      = "powerup_time",
101       .name    = N_("Power-up time (ms) (15-200)"),
102       .desc    = N_("Time (in milliseconds) for the rotor to power up."),
103       .off     = offsetof(linuxdvb_rotor_t, lr_powerup_time),
104       .def.u32 = 100,
105     },
106     {
107       .type    = PT_U32,
108       .id      = "cmd_time",
109       .name    = N_("Command time (ms) (10-100)"),
110       .desc    = N_("Time (in milliseconds) for a command to complete."),
111       .off     = offsetof(linuxdvb_rotor_t, lr_cmd_time),
112       .def.u32 = 25
113     },
114     {}
115   }
116 };
117 
118 const idclass_t linuxdvb_rotor_gotox_class =
119 {
120   .ic_super       = &linuxdvb_rotor_class,
121   .ic_class       = "linuxdvb_rotor_gotox",
122   .ic_caption     = N_("TV Adapters - SatConfig - GOTOX Rotor"),
123   .ic_properties  = (const property_t[]) {
124     {
125       .type   = PT_U16,
126       .id     = "position",
127       .name   = N_("GOTOX position"),
128       .desc   = N_("Satellite position."),
129       .off    = offsetof(linuxdvb_rotor_t, lr_position),
130     },
131     {
132       .type   = PT_DBL,
133       .id     = "sat_lon",
134       .name   = N_("Satellite longitude"),
135       .desc   = N_("Satellite longitude."),
136       .off    = offsetof(linuxdvb_rotor_t, lr_sat_lon),
137     },
138     {}
139   }
140 };
141 
142 const idclass_t linuxdvb_rotor_usals_class =
143 {
144   .ic_super       = &linuxdvb_rotor_class,
145   .ic_class       = "linuxdvb_rotor_usals",
146   .ic_caption     = N_("TV Adapters - SatConfig - USALS Rotor"),
147   .ic_properties  = (const property_t[]) {
148     {
149       .type   = PT_DBL,
150       .id     = "sat_lon",
151       .name   = N_("Satellite longitude"),
152       .desc   = N_("Satellite longitude."),
153       .off    = offsetof(linuxdvb_rotor_t, lr_sat_lon),
154     },
155 
156     {}
157   }
158 };
159 
160 #endif /* ROTOR_TEST */
161 
162 /*
163  *
164  */
165 
166 static inline
pos_to_integer(double pos)167 int pos_to_integer( double pos )
168 {
169   if (pos < 0)
170     return (pos - 0.05) * 10;
171   else
172     return (pos + 0.05) * 10;
173 }
174 
175 static inline
to_radians(double val)176 double to_radians( double val )
177 {
178   return ((val * M_PI) / 180.0);
179 }
180 
181 static inline
to_degrees(double val)182 double to_degrees( double val )
183 {
184   return ((val * 180.0) / M_PI);
185 }
186 
187 static inline
to_rev(double val)188 double to_rev( double val )
189 {
190   return val - floor(val / 360.0) * 360;
191 }
192 
193 static
sat_azimuth_and_elevation(double site_lat,double site_lon,double site_alt,double sat_lon,double * azimuth,double * elevation)194 void sat_azimuth_and_elevation
195   ( double site_lat, double site_lon, double site_alt, double sat_lon,
196     double *azimuth, double *elevation )
197 {
198   const double f     =  1.00 / 298.257;  // Earth flattening factor
199   const double r_sat =  42164.57;        // Distance from earth centre to satellite
200   const double r_eq  =   6378.14;        // Earth radius
201 
202   const double a0    =  0.58804392;
203   const double a1    = -0.17941557;
204   const double a2    =  0.29906946E-1;
205   const double a3    = -0.25187400E-2;
206   const double a4    =  0.82622101E-4;
207 
208   double sin_site_lat  = sin(to_radians(site_lat));
209   double cos_site_lat  = cos(to_radians(site_lat));
210   double Rstation      = r_eq / sqrt(1.00 - f*(2.00-f)*sin_site_lat*sin_site_lat);
211   double Ra            = (Rstation+site_alt)*cos_site_lat;
212   double Rz            = Rstation*(1.00-f)*(1.00-f)*sin_site_lat;
213   double alfa_rx       = r_sat*cos(to_radians(sat_lon-site_lon)) - Ra;
214   double alfa_ry       = r_sat*sin(to_radians(sat_lon-site_lon));
215   double alfa_rz       = -Rz;
216   double alfa_r_north  = -alfa_rx*sin_site_lat + alfa_rz*cos_site_lat;
217 
218   double alfa_r_zenith = alfa_rx*cos_site_lat + alfa_rz*sin_site_lat;
219   double El_geometric  = to_degrees(atan(alfa_r_zenith/sqrt(alfa_r_north*alfa_r_north+alfa_ry*alfa_ry)));
220   double x             = fabs(El_geometric+0.589);
221   double refraction    = fabs(a0+a1*x+a2*x*x+a3*x*x*x+a4*x*x*x*x);
222 
223   *azimuth = 0.00;
224   if (alfa_r_north < 0)
225     *azimuth = 180+to_degrees(atan(alfa_ry/alfa_r_north));
226   else
227     *azimuth = to_rev(360+to_degrees(atan(alfa_ry/alfa_r_north)));
228 
229   *elevation = 0.00;
230   if (El_geometric > 10.2)
231     *elevation = El_geometric+0.01617*(cos(to_radians(fabs(El_geometric)))/
232                                        sin(to_radians(fabs(El_geometric))));
233   else
234     *elevation = El_geometric+refraction;
235   if (alfa_r_zenith < -3000)
236     *elevation = -99;
237 }
238 
239 /*
240  * Site Latitude
241  * Site Longtitude
242  * Site Altitude
243  * Satellite Longtitute
244  */
245 static double
sat_angle(linuxdvb_rotor_t * lr,linuxdvb_satconf_ele_t * ls)246 sat_angle( linuxdvb_rotor_t *lr, linuxdvb_satconf_ele_t *ls )
247 {
248   linuxdvb_satconf_t *lsp = ls->lse_parent;
249   double site_lat = lsp->ls_site_lat;
250   double site_lon = lsp->ls_site_lon;
251   double site_alt = lsp->ls_site_altitude;
252   double sat_lon  = lr->lr_sat_lon;
253 
254   if (lsp->ls_site_lat_south)
255     site_lat = -site_lat;
256   if (lsp->ls_site_lon_west)
257     site_lon = 360 - site_lon;
258   if (sat_lon < 0)
259     sat_lon = 360 + sat_lon;
260 
261   double azimuth, elevation;
262 
263 #ifndef ROTOR_TEST
264   tvhtrace(LS_DISEQC, "site: lat %.4f, lon %.4f, alt %.4f; sat lon %.4f",
265                      site_lat, site_lon, site_alt, sat_lon);
266 #endif
267 
268   sat_azimuth_and_elevation(site_lat, site_lon, site_alt, sat_lon,
269                             &azimuth, &elevation);
270 
271 #ifndef ROTOR_TEST
272   tvhtrace(LS_DISEQC, "rotor angle azimuth %.4f elevation %.4f", azimuth, elevation);
273 #else
274   gazimuth = azimuth;
275   gelevation = elevation;
276 #endif
277 
278   double rad_azimuth   = to_radians(azimuth);
279   double rad_elevation = to_radians(elevation);
280   double rad_site_lat  = to_radians(site_lat);
281   double cos_elevation = cos(rad_elevation);
282   double a, b, value;
283 
284   a = -cos_elevation * sin(rad_azimuth);
285   b =  sin(rad_elevation) * cos(rad_site_lat) -
286        cos_elevation * sin(rad_site_lat) * cos(rad_azimuth);
287 
288   value = 180 + to_degrees(atan(a/b));
289 
290   if (azimuth > 270) {
291     value = value + 180;
292     if (value > 360)
293       value = 360 - (value-360);
294   }
295   if (azimuth < 90)
296     value = 180 - value;
297 
298   int ret;
299 
300   if (site_lat >= 0) {
301     ret = round(fabs(180 - value) * 10.0);
302     if (value >= 180)
303       ret = -(ret);
304   } else if (value < 180) {
305     ret = -round(fabs(value) * 10.0);
306   } else {
307     ret = round(fabs(360 - value) * 10.0);
308   }
309 
310   return ret;
311 }
312 
313 #ifndef ROTOR_TEST
314 
315 /* **************************************************************************
316  * Class methods
317  * *************************************************************************/
318 
319 static int
linuxdvb_rotor_grace(linuxdvb_diseqc_t * ld,dvb_mux_t * lm)320 linuxdvb_rotor_grace
321   ( linuxdvb_diseqc_t *ld, dvb_mux_t *lm )
322 {
323   linuxdvb_rotor_t *lr = (linuxdvb_rotor_t*)ld;
324   linuxdvb_satconf_t *ls = ld->ld_satconf->lse_parent;
325   int newpos, delta, tunit, min, res;
326 
327   if (!ls->ls_last_orbital_pos || ls->ls_motor_rate == 0)
328     return ls->ls_max_rotor_move;
329 
330   newpos = pos_to_integer(lr->lr_sat_lon);
331 
332   tunit  = 10000; /* 1/1000 sec per one degree */
333 
334   delta  = abs(deltaI32(ls->ls_last_orbital_pos, newpos));
335 
336   /* ignore very small movements like 0.8W and 1W */
337   if (delta <= 2)
338     return 0;
339 
340   /* add one extra second, because of the rounding issue */
341   res = ((ls->ls_motor_rate*delta+(tunit-1))/tunit) + 1;
342 
343   min = 1 + ls->ls_min_rotor_move;
344   if (res < min)
345     res = min;
346 
347   return res;
348 }
349 
350 static int
linuxdvb_rotor_check_orbital_pos(linuxdvb_rotor_t * lr,dvb_mux_t * lm,linuxdvb_satconf_ele_t * ls)351 linuxdvb_rotor_check_orbital_pos
352   ( linuxdvb_rotor_t *lr, dvb_mux_t *lm, linuxdvb_satconf_ele_t *ls )
353 {
354   linuxdvb_satconf_t *lsp = ls->lse_parent;
355   int pos = lsp->ls_last_orbital_pos;
356   char dir;
357 
358   if (!pos)
359     return 0;
360 
361   if (abs(pos_to_integer(lr->lr_sat_lon) - pos) > 2)
362     return 0;
363 
364   dir = 'E';
365   if (pos < 0) {
366     pos = -(pos);
367     dir = 'W';
368   }
369   tvhdebug(LS_DISEQC, "rotor already positioned to %i.%i%c",
370                      pos / 10, pos % 10, dir);
371   return 1;
372 }
373 
374 /* GotoX */
375 static int
linuxdvb_rotor_gotox_tune(linuxdvb_rotor_t * lr,dvb_mux_t * lm,linuxdvb_satconf_t * lsp,linuxdvb_satconf_ele_t * ls)376 linuxdvb_rotor_gotox_tune
377   ( linuxdvb_rotor_t *lr, dvb_mux_t *lm,
378     linuxdvb_satconf_t *lsp, linuxdvb_satconf_ele_t *ls )
379 {
380   int i, fd = linuxdvb_satconf_fe_fd(lsp);
381 
382   for (i = 0; i <= ls->lse_parent->ls_diseqc_repeats; i++) {
383     if (linuxdvb_diseqc_send(fd, 0xE0, 0x31, 0x6B, 1, (int)lr->lr_position)) {
384       tvherror(LS_DISEQC, "failed to set GOTOX pos %d", lr->lr_position);
385       return -1;
386     }
387     tvh_safe_usleep(MINMAX(lr->lr_cmd_time, 10, 100) * 1000);
388   }
389 
390   tvhdebug(LS_DISEQC, "rotor GOTOX pos %d sent", lr->lr_position);
391 
392   return linuxdvb_rotor_grace((linuxdvb_diseqc_t*)lr,lm);
393 }
394 
395 /* USALS */
396 static int
linuxdvb_rotor_usals_tune(linuxdvb_rotor_t * lr,dvb_mux_t * lm,linuxdvb_satconf_t * lsp,linuxdvb_satconf_ele_t * ls)397 linuxdvb_rotor_usals_tune
398   ( linuxdvb_rotor_t *lr, dvb_mux_t *lm,
399     linuxdvb_satconf_t *lsp, linuxdvb_satconf_ele_t *ls )
400 {
401   static const uint8_t xtable[10] =
402          { 0x00, 0x02, 0x03, 0x05, 0x06, 0x08, 0x0A, 0x0B, 0x0D, 0x0E };
403 
404   int i, angle = sat_angle(lr, ls), fd = linuxdvb_satconf_fe_fd(lsp);
405   uint32_t cmd = 0xE000;
406 
407   if (angle < 0) {
408     angle = -(angle);
409     cmd   = 0xD000;
410   }
411   cmd |= (angle / 10) * 0x10 + xtable[angle % 10];
412 
413   tvhdebug(LS_DISEQC, "rotor USALS goto %0.1f%c (motor %0.1f %sclockwise)",
414            fabs(lr->lr_sat_lon), (lr->lr_sat_lon > 0.0) ? 'E' : 'W',
415            ((double)angle / 10.0), (cmd & 0xF000) == 0xD000 ? "counter-" : "");
416 
417   for (i = 0; i <= lsp->ls_diseqc_repeats; i++) {
418     if (linuxdvb_diseqc_send(fd, 0xE0, 0x31, 0x6E, 2,
419                              (cmd >> 8) & 0xff, cmd & 0xff)) {
420       tvherror(LS_DISEQC, "failed to send USALS command");
421       return -1;
422     }
423     tvh_safe_usleep(MINMAX(lr->lr_cmd_time, 10, 100) * 1000);
424   }
425 
426   return linuxdvb_rotor_grace((linuxdvb_diseqc_t*)lr,lm);
427 }
428 
429 static int
linuxdvb_rotor_tune(linuxdvb_diseqc_t * ld,dvb_mux_t * lm,linuxdvb_satconf_t * lsp,linuxdvb_satconf_ele_t * ls,int vol,int pol,int band,int freq)430 linuxdvb_rotor_tune
431   ( linuxdvb_diseqc_t *ld, dvb_mux_t *lm,
432     linuxdvb_satconf_t *lsp, linuxdvb_satconf_ele_t *ls,
433     int vol, int pol, int band, int freq )
434 {
435   linuxdvb_rotor_t *lr = (linuxdvb_rotor_t*)ld;
436 
437   if (linuxdvb_rotor_check_orbital_pos(lr, lm, ls))
438     return 0;
439 
440   /* Force to 18v (quicker movement) */
441   if (linuxdvb_satconf_start(lsp, MINMAX(lr->lr_powerup_time, 15, 200), 1))
442     return -1;
443 
444   /* GotoX */
445   if (idnode_is_instance(&lr->ld_id, &linuxdvb_rotor_gotox_class))
446     return linuxdvb_rotor_gotox_tune(lr, lm, lsp, ls);
447 
448   /* USALS */
449   return linuxdvb_rotor_usals_tune(lr, lm, lsp, ls);
450 }
451 
452 static int
linuxdvb_rotor_post(linuxdvb_diseqc_t * ld,dvb_mux_t * lm,linuxdvb_satconf_t * lsp,linuxdvb_satconf_ele_t * ls)453 linuxdvb_rotor_post
454   ( linuxdvb_diseqc_t *ld, dvb_mux_t *lm,
455     linuxdvb_satconf_t *lsp, linuxdvb_satconf_ele_t *ls )
456 {
457   linuxdvb_rotor_t *lr = (linuxdvb_rotor_t*)ld;
458 
459   lsp->ls_last_orbital_pos = pos_to_integer(lr->lr_sat_lon);
460   return 0;
461 }
462 
463 /* **************************************************************************
464  * Create / Config
465  * *************************************************************************/
466 
467 struct {
468   const char      *name;
469   const idclass_t *idc;
470 } linuxdvb_rotor_all[] = {
471   {
472     .name = N_("GOTOX"),
473     .idc  = &linuxdvb_rotor_gotox_class
474   },
475   {
476     .name = N_("USALS"),
477     .idc  = &linuxdvb_rotor_usals_class
478   }
479 };
480 
481 htsmsg_t *
linuxdvb_rotor_list(void * o,const char * lang)482 linuxdvb_rotor_list ( void *o, const char *lang )
483 {
484   int i;
485   htsmsg_t *m = htsmsg_create_list();
486   htsmsg_add_msg(m, NULL, htsmsg_create_key_val("", tvh_gettext_lang(lang, N_("None"))));
487   for (i = 0; i < ARRAY_SIZE(linuxdvb_rotor_all); i++)
488     htsmsg_add_msg(m, NULL, htsmsg_create_key_val(linuxdvb_rotor_all[i].name, tvh_gettext_lang(lang, linuxdvb_rotor_all[i].name)));
489   return m;
490 }
491 
492 linuxdvb_diseqc_t *
linuxdvb_rotor_create0(const char * name,htsmsg_t * conf,linuxdvb_satconf_ele_t * ls)493 linuxdvb_rotor_create0
494   ( const char *name, htsmsg_t *conf, linuxdvb_satconf_ele_t *ls )
495 {
496   int i;
497   linuxdvb_diseqc_t *ld = NULL;
498   linuxdvb_rotor_t *lr;
499 
500   for (i = 0; i < ARRAY_SIZE(linuxdvb_rotor_all); i++) {
501     if (!strcmp(name ?: "", linuxdvb_rotor_all[i].name)) {
502       ld = linuxdvb_diseqc_create0(calloc(1, sizeof(linuxdvb_rotor_t)),
503                                    NULL, linuxdvb_rotor_all[i].idc, conf,
504                                    linuxdvb_rotor_all[i].name, ls);
505       if (ld) {
506         ld->ld_tune  = linuxdvb_rotor_tune;
507         ld->ld_grace = linuxdvb_rotor_grace;
508         ld->ld_post  = linuxdvb_rotor_post;
509         lr = (linuxdvb_rotor_t *)ld;
510         if (lr->lr_powerup_time == 0)
511           lr->lr_powerup_time = 100;
512         if (lr->lr_cmd_time == 0)
513           lr->lr_cmd_time = 25;
514       }
515     }
516   }
517 
518   return ld;
519 }
520 
521 void
linuxdvb_rotor_destroy(linuxdvb_diseqc_t * lr)522 linuxdvb_rotor_destroy ( linuxdvb_diseqc_t *lr )
523 {
524   linuxdvb_diseqc_destroy(lr);
525   free(lr);
526 }
527 
528 #else /* ROTOR_TEST */
529 
main(int argc,char * argv[])530 int main(int argc, char *argv[])
531 {
532   if (argc < 5) {
533     fprintf(stderr, "Usage: <site_latitude> <site_lontitude> <site_altitude> <sat_longtitude>\n");
534     return 1;
535   }
536   linuxdvb_rotor_t lr;
537   linuxdvb_satconf_t ls;
538   linuxdvb_satconf_ele_t lse;
539   int angle;
540 
541   memset(&lr, 0, sizeof(lr));
542   memset(&ls, 0, sizeof(ls));
543   memset(&lse, 0, sizeof(lse));
544 
545   lse.lse_parent = &ls;
546 
547   ls.ls_site_lat = atof(argv[1]);
548   ls.ls_site_lon = atof(argv[2]);
549   ls.ls_site_altitude = atof(argv[3]);
550   lr.lr_sat_lon  = atof(argv[4]);
551 
552   angle = sat_angle(&lr, &lse);
553 
554   printf("Input values:\n");
555   printf("  %20s: %.4f\n", "Site Latidude", ls.ls_site_lat);
556   printf("  %20s: %.4f\n", "Site Longtitude", ls.ls_site_lon);
557   printf("  %20s: %.4f\n", "Site Altitude", ls.ls_site_altitude);
558   printf("  %20s: %.4f\n", "Satellite Longtitude", lr.lr_sat_lon);
559   printf("\nResult:\n");
560   printf("  %20s: %.4f\n", "Azimuth", gazimuth);
561   printf("  %20s: %.4f\n", "Elevation", gelevation);
562   printf("  %20s: %.1f %sclockwise\n", "Angle", (double)abs(angle) / 10.0, angle < 0 ? "counter-" : "");
563   return 0;
564 }
565 
566 #endif
567 
568 /******************************************************************************
569  * Editor Configuration
570  *
571  * vim:sts=2:ts=2:sw=2:et
572  *****************************************************************************/
573