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