diff --git a/html/status.html b/html/status.html index 8812322..519f3da 100755 --- a/html/status.html +++ b/html/status.html @@ -133,6 +133,7 @@ myTable += "Pos."; myTable += "Frequency"; myTable += "SR/BW"; + myTable += "Stream"; myTable += "Signal"; if (has_axe) { myTable += "Packets"; @@ -202,6 +203,12 @@ else myTable += ""; + // STREAM + var stream = state['ad_stream'][i]; + if (parseInt(stream) <= 0) + stream = ''; + myTable += "" + stream + " "; + // SIGNAL var signal = ""; if (!ad_enabled) { @@ -227,6 +234,7 @@ } myTable += "" + signal + " "; + // AXE if (has_axe) { myTable += "" + state['ad_axe_pktc'][i] + " "; myTable += "" + state['ad_axe_ccerr'][i] + " "; diff --git a/src/adapter.c b/src/adapter.c index e034e3e..9862114 100644 --- a/src/adapter.c +++ b/src/adapter.c @@ -75,6 +75,7 @@ adapter *adapter_alloc() /* diseqc setup */ ad->diseqc_param.fast = opts.diseqc_fast; + ad->diseqc_param.addr = opts.diseqc_addr; ad->diseqc_param.committed_no = opts.diseqc_committed_no; ad->diseqc_param.uncommitted_no = opts.diseqc_uncommitted_no; @@ -550,7 +551,7 @@ int getAdaptersCount() memset(&fe_map, -1, sizeof(fe_map)); k = 0; - for (i = 0; i < sizeof(order); i++) + for (i = 0; i < ARRAY_SIZE(order); i++) { int sys = order[i]; for (j = 0; j < ifes[sys]; j++) @@ -676,7 +677,7 @@ int get_free_adapter(transponder *tp) adapter *ad = a[0]; - if ((fe > 0) && (fe <= sizeof(fe_map)) && (fe_map[fe - 1] >= 0)) + if ((fe > 0) && (fe <= ARRAY_SIZE(fe_map)) && (fe_map[fe - 1] >= 0)) { fe = fe_map[fe - 1]; ad = a[fe]; @@ -878,7 +879,7 @@ int update_pids(int aid) if (dp) dump_pids(aid); dp = 0; - if (ad->pids[i].fd <= 0) + if (ad->pids[i].fd <= 0) { if ((ad->pids[i].fd = ad->set_pid(ad, ad->pids[i].pid)) < 0) { @@ -888,6 +889,8 @@ int update_pids(int aid) LOG0("Maximum pid filter reached, lowering the value to %d", opts.max_pids); break; } + ad->active_pids++; + } ad->pids[i].flags = 1; if (ad->pids[i].pid == 0) ad->pat_processed = 0; @@ -895,7 +898,6 @@ int update_pids(int aid) ad->pids[i].cc = 255; ad->pids[i].cc_err = 0; ad->pids[i].dec_err = 0; - ad->active_pids++; } if (ad->commit) ad->commit(ad); @@ -906,7 +908,9 @@ int update_pids(int aid) void post_tune(adapter *ad) { +#if !defined(DISABLE_PMT) || !defined(DISABLE_T2MI) int aid = ad->id; +#endif #ifndef DISABLE_PMT SPid *p = find_pid(aid, 0); SPid *p_all = find_pid(aid, 8192); @@ -1066,7 +1070,7 @@ void mark_pids_deleted(int aid, int sid, char *pids) //pids==NULL -> delete all pids ? pids : "NULL"); if (pids) { - la = split(arg, pids, MAX_PIDS, ','); + la = split(arg, pids, ARRAY_SIZE(arg), ','); for (i = 0; i < la; i++) { pid = map_int(arg[i], NULL); @@ -1146,7 +1150,7 @@ int mark_pids_add(int sid, int aid, char *pids) LOG("adding pids to adapter %d, sid %d, pids=%s", aid, sid, pids ? pids : "NULL"); - la = split(arg, pids, MAX_PIDS, ','); + la = split(arg, pids, ARRAY_SIZE(arg), ','); for (i = 0; i < la; i++) { pid = map_intd(arg[i], NULL, -1); @@ -1165,11 +1169,11 @@ int compare_tunning_parameters(int aid, transponder *tp) if (!ad) return -1; - LOGM("new parameters: f:%d, plp:%d, diseqc:%d, pol:%d, sr:%d, mtype:%d", - tp->freq, tp->plp, tp->diseqc, tp->pol, tp->sr, tp->mtype); - LOGM("old parameters: f:%d, plp:%d, diseqc:%d, pol:%d, sr:%d, mtype:%d", - ad->tp.freq, ad->tp.plp, ad->tp.diseqc, ad->tp.pol, ad->tp.sr, ad->tp.mtype); - if (tp->freq != ad->tp.freq || tp->plp != ad->tp.plp || tp->diseqc != ad->tp.diseqc || (tp->pol > 0 && tp->pol != ad->tp.pol) || (tp->sr > 1000 && tp->sr != ad->tp.sr) || (tp->mtype > 0 && tp->mtype != ad->tp.mtype)) + LOGM("new parameters: f:%d, plp/isi:%d, diseqc:%d, pol:%d, sr:%d, mtype:%d", + tp->freq, tp->plp_isi, tp->diseqc, tp->pol, tp->sr, tp->mtype); + LOGM("old parameters: f:%d, plp/isi:%d, diseqc:%d, pol:%d, sr:%d, mtype:%d", + ad->tp.freq, ad->tp.plp_isi, ad->tp.diseqc, ad->tp.pol, ad->tp.sr, ad->tp.mtype); + if (tp->freq != ad->tp.freq || tp->plp_isi != ad->tp.plp_isi || tp->diseqc != ad->tp.diseqc || (tp->pol > 0 && tp->pol != ad->tp.pol) || (tp->sr > 1000 && tp->sr != ad->tp.sr) || (tp->mtype > 0 && tp->mtype != ad->tp.mtype)) return 1; @@ -1196,11 +1200,11 @@ int set_adapter_parameters(int aid, int sid, transponder *tp) { mutex_unlock(&ad->mutex); LOG( - "secondary stream requested tune, not gonna happen ad: f:%d sr:%d pol:%d plp:%d src:%d mod %d -> \ - new: f:%d sr:%d pol:%d plp:%d src:%d mod %d", - ad->tp.freq, ad->tp.sr, ad->tp.pol, ad->tp.plp, + "secondary stream requested tune, not gonna happen ad: f:%d sr:%d pol:%d plp/isi:%d src:%d mod %d -> \ + new: f:%d sr:%d pol:%d plp/isi:%d src:%d mod %d", + ad->tp.freq, ad->tp.sr, ad->tp.pol, ad->tp.plp_isi, ad->tp.diseqc, ad->tp.mtype, tp->freq, tp->sr, tp->pol, - tp->plp, tp->diseqc, tp->mtype); + tp->plp_isi, tp->diseqc, tp->mtype); return -1; } ad->do_tune = 1; @@ -1242,7 +1246,7 @@ int set_adapter_parameters(int aid, int sid, transponder *tp) { char *arg[64]; int i, la; - la = split(arg, ad->tp.x_pmt, 64, ','); + la = split(arg, ad->tp.x_pmt, ARRAY_SIZE(arg), ','); for (i = 0; i < la; i++) { int pmt = map_int(arg[i], NULL); @@ -1346,7 +1350,7 @@ describe_adapter(int sid, int aid, char *dad, int ld) ad ? ad->tp.fe : aid + 1, strength, status, snr, (double)t->freq / 1000.0, t->bw / 1000000, get_delsys(t->sys), get_tmode(t->tmode), get_modulation(t->mtype), - get_gi(t->gi), get_fec(t->fec), t->plp, t->t2id, t->sm); + get_gi(t->gi), get_fec(t->fec), t->plp_isi, t->t2id, t->sm); else len = snprintf(dad, ld, @@ -1354,7 +1358,7 @@ describe_adapter(int sid, int aid, char *dad, int ld) ad ? ad->tp.fe : aid + 1, strength, status, snr, (double)t->freq / 1000, get_delsys(t->sys), get_modulation(t->mtype), t->sr / 1000, t->c2tft, t->ds, - t->plp, get_inversion(t->inversion)); + t->plp_isi, get_inversion(t->inversion)); if (use_ad) len += strlen(get_stream_pids(sid, dad + len, ld - len)); @@ -1436,12 +1440,12 @@ void set_disable(int i, int v) void enable_adapters(char *o) { int i, la, st, end, j; - char buf[100], *arg[20], *sep; + char buf[1000], *arg[40], *sep; for (i = 0; i < MAX_ADAPTERS; i++) set_disable(i, 1); SAFE_STRCPY(buf, o); - la = split(arg, buf, sizeof(arg), ','); + la = split(arg, buf, ARRAY_SIZE(arg), ','); for (i = 0; i < la; i++) { sep = strchr(arg[i], '-'); @@ -1463,10 +1467,10 @@ void enable_adapters(char *o) void set_unicable_adapters(char *o, int type) { int i, la, a_id, slot, freq, pin, o13v; - char buf[100], *arg[20], *sep1, *sep2, *sep3; + char buf[1000], *arg[40], *sep1, *sep2, *sep3; adapter *ad; SAFE_STRCPY(buf, o); - la = split(arg, buf, sizeof(arg), ','); + la = split(arg, buf, ARRAY_SIZE(arg), ','); for (i = 0; i < la; i++) { a_id = map_intd(arg[i], NULL, -1); @@ -1503,11 +1507,11 @@ void set_unicable_adapters(char *o, int type) void set_diseqc_adapters(char *o) { - int i, la, a_id, fast, committed_no, uncommitted_no; - char buf[100], *arg[20], *sep1, *sep2; + int i, la, a_id, fast, addr, committed_no, uncommitted_no; + char buf[1000], *arg[40], *sep1, *sep2; adapter *ad; SAFE_STRCPY(buf, o); - la = split(arg, buf, sizeof(arg), ','); + la = split(arg, buf, ARRAY_SIZE(arg), ','); for (i = 0; i < la; i++) { if (arg[i] && arg[i][0] == '*') @@ -1531,8 +1535,28 @@ void set_diseqc_adapters(char *o) if (!sep1 || !sep2) continue; - if ((fast = (sep1[1] == '*')) != 0) - sep1++; + + fast = 0; + addr = 0x10; + while (sep1[1] == '*' || sep1[1] == '@' || sep1[1] == '.') + { + if (sep1[1] == '*') + { + fast = 1; + sep1++; + } + else if (sep1[1] == '@') + { + addr = 0; + sep1++; + } + else if (sep1[1] == '.') + { + addr = 0x11; + sep1++; + } + } + committed_no = map_intd(sep1 + 1, NULL, -1); uncommitted_no = map_intd(sep2 + 1, NULL, -1); if (committed_no < 0 || uncommitted_no < 0) @@ -1541,12 +1565,14 @@ void set_diseqc_adapters(char *o) if (ad) { ad->diseqc_param.fast = fast; + ad->diseqc_param.addr = addr; ad->diseqc_param.committed_no = committed_no; ad->diseqc_param.uncommitted_no = uncommitted_no; } else { opts.diseqc_fast = fast; + opts.diseqc_addr = addr; opts.diseqc_committed_no = committed_no; opts.diseqc_uncommitted_no = uncommitted_no; int j; @@ -1554,23 +1580,24 @@ void set_diseqc_adapters(char *o) if (a[j]) { a[j]->diseqc_param.fast = fast; + a[j]->diseqc_param.addr = addr; a[j]->diseqc_param.committed_no = committed_no; a[j]->diseqc_param.uncommitted_no = uncommitted_no; } } LOG( - "Setting diseqc adapter %d fast %d committed_no %d uncommitted_no %d", - a_id, fast, committed_no, uncommitted_no); + "Setting diseqc adapter %d fast %d addr 0x%02x committed_no %d uncommitted_no %d", + a_id, fast, addr, committed_no, uncommitted_no); } } void set_diseqc_multi(char *o) { int i, la, a_id, position; - char buf[100], *arg[20], *sep1; + char buf[1000], *arg[40], *sep1; adapter *ad; SAFE_STRCPY(buf, o); - la = split(arg, buf, sizeof(arg), ','); + la = split(arg, buf, ARRAY_SIZE(arg), ','); for (i = 0; i < la; i++) { if (arg[i] && arg[i][0] == '*') @@ -1619,10 +1646,10 @@ void set_diseqc_multi(char *o) void set_lnb_adapters(char *o) { int i, la, a_id, lnb_low, lnb_high, lnb_switch; - char buf[100], *arg[20], *sep1, *sep2, *sep3; + char buf[1000], *arg[40], *sep1, *sep2, *sep3; adapter *ad; SAFE_STRCPY(buf, o); - la = split(arg, buf, sizeof(arg), ','); + la = split(arg, buf, ARRAY_SIZE(arg), ','); for (i = 0; i < la; i++) { if (arg[i] && arg[i][0] == '*') @@ -1692,11 +1719,11 @@ void set_diseqc_timing(char *o) int i, la, a_id; int before_cmd, after_cmd, after_repeated_cmd; int after_switch, after_burst, after_tone; - char buf[2000], *arg[20]; + char buf[2000], *arg[40]; char *sep1, *sep2, *sep3, *sep4, *sep5, *sep6; adapter *ad; SAFE_STRCPY(buf, o); - la = split(arg, buf, sizeof(arg), ','); + la = split(arg, buf, ARRAY_SIZE(arg), ','); for (i = 0; i < la; i++) { if (arg[i] && arg[i][0] == '*') @@ -1773,10 +1800,10 @@ void set_diseqc_timing(char *o) void set_slave_adapters(char *o) { int i, j, la, a_id, a_id2, master = 0; - char buf[100], *arg[20], *sep, *sep2; + char buf[1000], *arg[40], *sep, *sep2; adapter *ad; SAFE_STRCPY(buf, o); - la = split(arg, buf, sizeof(arg), ','); + la = split(arg, buf, ARRAY_SIZE(arg), ','); for (i = 0; i < la; i++) { a_id = map_intd(arg[i], NULL, -1); @@ -1826,13 +1853,13 @@ void set_timeout_adapters(char *o) { int i, j, la, a_id, a_id2; int timeout = opts.adapter_timeout / 1000; - char buf[100], *arg[20], *sep; + char buf[1000], *arg[40], *sep; adapter *ad; SAFE_STRCPY(buf, o); sep = strchr(buf, ':'); if (sep) timeout = map_intd(sep + 1, NULL, timeout); - la = split(arg, buf, sizeof(arg), ','); + la = split(arg, buf, ARRAY_SIZE(arg), ','); if (arg[0] && (arg[0][0] == '*')) { opts.adapter_timeout = timeout * 1000; @@ -1876,10 +1903,10 @@ extern char *fe_delsys[]; void set_adapters_delsys(char *o) { int i, la, a_id, ds; - char buf[100], *arg[20], *sep; + char buf[1000], *arg[40], *sep; adapter *ad; SAFE_STRCPY(buf, o); - la = split(arg, buf, sizeof(arg), ','); + la = split(arg, buf, ARRAY_SIZE(arg), ','); for (i = 0; i < la; i++) { a_id = map_intd(arg[i], NULL, -1); @@ -1916,10 +1943,10 @@ void set_adapters_delsys(char *o) void set_adapter_dmxsource(char *o) { int i, j, la, st, end, fd; - char buf[100], *arg[20], *sep, *seps; + char buf[1000], *arg[40], *sep, *seps; adapter *ad; SAFE_STRCPY(buf, o); - la = split(arg, buf, sizeof(arg), ','); + la = split(arg, buf, ARRAY_SIZE(arg), ','); for (i = 0; i < la; i++) { sep = strchr(arg[i], '-'); @@ -1956,10 +1983,10 @@ void set_signal_multiplier(char *o) { int i, la, a_id; float strength_multiplier, snr_multiplier; - char buf[100], *arg[20], *sep1, *sep2; + char buf[1000], *arg[40], *sep1, *sep2; adapter *ad; SAFE_STRCPY(buf, o); - la = split(arg, buf, sizeof(arg), ','); + la = split(arg, buf, ARRAY_SIZE(arg), ','); for (i = 0; i < la; i++) { if (arg[i] && arg[i][0] == '*') @@ -2193,6 +2220,7 @@ _symbols adapters_sym[] = {"ad_sr", VAR_AARRAY_INT, a, 1. / 1000, MAX_ADAPTERS, offsetof(adapter, tp.sr)}, {"ad_bw", VAR_AARRAY_INT, a, 1. / 1000, MAX_ADAPTERS, offsetof(adapter, tp.bw)}, {"ad_diseqc", VAR_AARRAY_INT, a, 1, MAX_ADAPTERS, offsetof(adapter, tp.diseqc)}, + {"ad_stream", VAR_AARRAY_INT, a, 1, MAX_ADAPTERS, offsetof(adapter, tp.plp_isi)}, {"ad_fe", VAR_AARRAY_INT, a, 1, MAX_ADAPTERS, offsetof(adapter, fe)}, {"ad_master", VAR_AARRAY_UINT8, a, 1, MAX_ADAPTERS, offsetof(adapter, master_sid)}, {"ad_sidcount", VAR_AARRAY_UINT8, a, 1, MAX_ADAPTERS, offsetof(adapter, sid_cnt)}, diff --git a/src/axe.c b/src/axe.c index 52e8dad..466f699 100644 --- a/src/axe.c +++ b/src/axe.c @@ -48,7 +48,7 @@ #ifndef DISABLE_LINUXDVB -void get_signal(adapter *ad, uint32_t *status, uint32_t *ber, uint16_t *strength, uint16_t *snr); +void get_signal(adapter *ad, int *status, int *ber, int *strength, int *snr); int send_jess(adapter *ad, int fd, int freq, int pos, int pol, int hiband, diseqc *d); int send_unicable(adapter *ad, int fd, int freq, int pos, int pol, int hiband, diseqc *d); int send_diseqc(adapter *ad, int fd, int pos, int pos_change, int pol, int hiband, diseqc *d); @@ -68,6 +68,8 @@ static inline void axe_fp_fd_write(const char *s) ssize_t r; axe_fp_fd_open(); + if (axe_fp_fd < 0) + return; len = strlen(b = s); while (len > 0) { @@ -152,6 +154,54 @@ void axe_post_init(adapter *ad) sockets_setread(ad->sock, axe_read); } +static void axe_stv0900_i2c_4(const char *name, int pa, int v) +{ + char buf[64]; + const char *b; + int fd; + size_t len; + ssize_t r; + + snprintf(buf, sizeof(buf), "/sys/devices/platform/i2c-stm.0/i2c-0/stv0900_%s%d", name, pa + 1); + fd = open(buf, O_WRONLY); + if (fd < 0) + return; + snprintf(buf, sizeof(buf), "%d", v); + len = strlen(b = buf); + while (len > 0) + { + r = write(fd, b, len); + if (r > 0) + { + len -= r; + b += r; + } + } + close(fd); +} + +static void axe_pls_isi(adapter *ad, transponder *tp) +{ + static int isi[4] = { -2, -2, -2, -2 }; + static int pls_code[4] = { -2, -2, -2, -2 }; + int v; + LOGM("axe: isi %d pls %d mode %d\n", tp->plp_isi, tp->pls_code, tp->pls_mode); + if (tp->plp_isi != isi[ad->pa]) { + v = tp->plp_isi < 0 ? -1 : (tp->plp_isi & 0xff); + axe_stv0900_i2c_4("mis", ad->pa, v); + isi[ad->pa] = tp->plp_isi; + } + if (tp->pls_code != pls_code[ad->pa]) { + v = tp->pls_code < 0 ? 0 : (tp->pls_code & 0x3ffff); + if (tp->pls_mode == PLS_MODE_GOLD) + v |= 0x40000; + else if (tp->pls_mode == PLS_MODE_COMBO) + v |= 0x80000; /* really? */ + axe_stv0900_i2c_4("pls", ad->pa, v); + pls_code[ad->pa] = tp->pls_code; + } +} + void axe_wakeup(void *_ad, int fe_fd, int voltage) { int i, mask; @@ -210,7 +260,7 @@ static inline int extra_quattro(int input, int diseqc, int *equattro) return *equattro; } -adapter *use_adapter(int input) +adapter *axe_use_adapter(int input) { int input2 = input < 4 ? input : -1; adapter *ad = get_configured_adapter(input2); @@ -229,8 +279,30 @@ adapter *use_adapter(int input) return ad; } -int tune_check(adapter *ad, int pol, int hiband, int diseqc) +int axe_get_hiband(transponder *tp, diseqc *diseqc_param) { + if (tp->pol > 2 && diseqc_param->lnb_circular > 0) + return 0; + if (tp->freq < diseqc_param->lnb_switch) + return 0; + return 1; +} + +int axe_get_freq(transponder *tp, diseqc *diseqc_param) +{ + int freq = tp->freq; + + if (tp->pol > 2 && diseqc_param->lnb_circular > 0) + return (freq - diseqc_param->lnb_circular); + if (freq < diseqc_param->lnb_switch) + return (freq - diseqc_param->lnb_low); + return (freq - diseqc_param->lnb_high); +} + +int axe_tune_check(adapter *ad, transponder *tp, diseqc *diseqc_param, int diseqc) +{ + int pol = (tp->pol - 1) & 1; + int hiband = axe_get_hiband(tp, diseqc_param); LOGM("axe: tune check for adapter %d, pol %d/%d, hiband %d/%d, diseqc %d/%d", ad->id, ad->old_pol, pol, ad->old_hiband, hiband, ad->old_diseqc, diseqc); if (ad->old_pol != pol) @@ -249,33 +321,25 @@ int axe_setup_switch(adapter *ad) { int frontend_fd = ad->fe; transponder *tp = &ad->tp; + diseqc *diseqc_param = &tp->diseqc_param; - int hiband = 0; + int hiband; + int freq; int diseqc = (tp->diseqc > 0) ? tp->diseqc - 1 : 0; - int freq = tp->freq; int pol = (tp->pol - 1) & 1; - if (tp->pol > 2 && tp->diseqc_param.lnb_circular > 0) - { - freq = (freq - tp->diseqc_param.lnb_circular); - hiband = 0; - } - else if (freq < tp->diseqc_param.lnb_switch) - { - freq = (freq - tp->diseqc_param.lnb_low); - hiband = 0; - } - else - { - freq = (freq - tp->diseqc_param.lnb_high); - hiband = 1; - } - adapter *ad2, *adm; int input = 0, aid, pos = 0, equattro = 0, master = -1; - if (tp->diseqc_param.switch_type != SWITCH_UNICABLE && - tp->diseqc_param.switch_type != SWITCH_JESS) + /* this is a new tune, so clear all adapter<->input mappings */ + for (aid = 0; aid < 4; aid++) + { + ad2 = a[aid]; + ad2->axe_used &= ~(1 << ad->id); + } + + if (diseqc_param->switch_type != SWITCH_UNICABLE && + diseqc_param->switch_type != SWITCH_JESS) { input = ad->id; if (!opts.quattro || extra_quattro(input, diseqc, &equattro)) @@ -298,7 +362,7 @@ int axe_setup_switch(adapter *ad) continue; if ((ad2->axe_used & ~(1 << ad->id)) == 0) continue; - if (!tune_check(ad2, pol, hiband, pos)) + if (!axe_tune_check(ad2, tp, &ad2->diseqc_param, pos)) continue; break; } @@ -327,7 +391,7 @@ int axe_setup_switch(adapter *ad) } diseqc = pos; master = aid; - adm = use_adapter(master); + adm = axe_use_adapter(master); if (adm == NULL) { LOG("axe_fe: unknown master adapter for input %d", input); @@ -337,7 +401,7 @@ int axe_setup_switch(adapter *ad) else { master = (ad->master_source >= 0) ? ad->master_source : ad->pa; - adm = use_adapter(master); + adm = axe_use_adapter(master); if (adm == NULL) { LOG("axe_fe: unknown master adapter for input %d", input); @@ -357,7 +421,7 @@ int axe_setup_switch(adapter *ad) if (ad2->sid_cnt > 0) break; } - if (adm != ad && aid < 4 && !tune_check(adm, pol, hiband, diseqc)) + if (adm != ad && aid < 4 && !axe_tune_check(adm, tp, &adm->diseqc_param, diseqc)) { LOG("unable to use slave adapter %d (master %d)", input, adm->pa); return 0; @@ -368,10 +432,13 @@ int axe_setup_switch(adapter *ad) if (master >= 0) { input = master; - if (!tune_check(adm, pol, hiband, diseqc)) + diseqc_param = &adm->diseqc_param; + hiband = axe_get_hiband(tp, diseqc_param); + freq = axe_get_freq(tp, diseqc_param); + if (!axe_tune_check(adm, tp, diseqc_param, diseqc)) { send_diseqc(adm, adm->fe2, diseqc, adm->old_diseqc != diseqc, - pol, hiband, &tp->diseqc_param); + pol, hiband, diseqc_param); adm->old_pol = pol; adm->old_hiband = hiband; adm->old_diseqc = diseqc; @@ -381,6 +448,7 @@ int axe_setup_switch(adapter *ad) } else if (opts.quattro) { + hiband = axe_get_hiband(tp, diseqc_param); if (opts.quattro_hiband == 1 && hiband) { LOG("axe_fe: hiband is not allowed for quattro config (adapter %d)", input); @@ -392,17 +460,19 @@ int axe_setup_switch(adapter *ad) return 0; } input = ((hiband ^ 1) << 1) | (pol ^ 1); - adm = use_adapter(input); + adm = axe_use_adapter(input); if (adm == NULL) { LOG("axe_fe: unknown master adapter %d", input); return 0; } adm->old_diseqc = diseqc = 0; - if (!tune_check(adm, pol, hiband, 0)) + diseqc_param = &adm->diseqc_param; + hiband = axe_get_hiband(tp, diseqc_param); + freq = axe_get_freq(tp, diseqc_param); + if (!axe_tune_check(adm, tp, diseqc_param, 0)) { - send_diseqc(adm, adm->fe2, 0, 0, pol, hiband, - &tp->diseqc_param); + send_diseqc(adm, adm->fe2, 0, 0, pol, hiband, diseqc_param); adm->old_pol = pol; adm->old_hiband = hiband; adm->old_diseqc = 0; @@ -414,9 +484,15 @@ int axe_setup_switch(adapter *ad) else { aid = ad->id & 3; - input = ad->master_source < 0 ? 0 : ad->master_source; //opts.axe_unicinp[aid]; + if (diseqc_param->switch_type == SWITCH_UNICABLE || + diseqc_param->switch_type == SWITCH_JESS) + { + input = ad->dmx_source < 0 ? 0 : ad->dmx_source; + } else { + input = ad->master_source < 0 ? 0 : ad->master_source; + } frontend_fd = ad->fe; - ad = use_adapter(input); + ad = axe_use_adapter(input); if (ad == NULL) { LOGM("axe setup: unable to find adapter %d", input); @@ -429,17 +505,20 @@ int axe_setup_switch(adapter *ad) ad->id, input, ad->fe, ad->fe2); } - if (tp->diseqc_param.switch_type == SWITCH_UNICABLE) + hiband = axe_get_hiband(tp, diseqc_param); + freq = axe_get_freq(tp, diseqc_param); + + if (diseqc_param->switch_type == SWITCH_UNICABLE) { freq = send_unicable(ad, ad->fe2, freq / 1000, diseqc, - pol, hiband, &tp->diseqc_param); + pol, hiband, diseqc_param); } - else if (tp->diseqc_param.switch_type == SWITCH_JESS) + else if (diseqc_param->switch_type == SWITCH_JESS) { freq = send_jess(ad, ad->fe2, freq / 1000, diseqc, - pol, hiband, &tp->diseqc_param); + pol, hiband, diseqc_param); } - else if (tp->diseqc_param.switch_type == SWITCH_SLAVE) + else if (diseqc_param->switch_type == SWITCH_SLAVE) { LOG("FD %d (%d) is a slave adapter", frontend_fd); } @@ -447,7 +526,7 @@ int axe_setup_switch(adapter *ad) { if (ad->old_pol != pol || ad->old_hiband != hiband || ad->old_diseqc != diseqc) send_diseqc(ad, frontend_fd, diseqc, ad->old_diseqc != diseqc, pol, - hiband, &tp->diseqc_param); + hiband, diseqc_param); else LOGM("Skip sending diseqc commands since " "the switch position doesn't need to be changed: " @@ -545,7 +624,11 @@ int axe_tune(int aid, transponder *tp) ADD_PROP(DTV_SYMBOL_RATE, tp->sr) ADD_PROP(DTV_INNER_FEC, tp->fec) #if DVBAPIVERSION >= 0x0502 - ADD_PROP(DTV_STREAM_ID, tp->plp) + if (tp->plp_isi >= 0) + ADD_PROP(DTV_STREAM_ID, tp->plp_isi & 0xFF) +#endif +#if DVBAPIVERSION >= 0x050b /* 5.11 */ + ADD_PROP(DTV_SCRAMBLING_SEQUENCE_INDEX, tp->pls_code) #endif LOG("tuning to %d(%d) pol: %s (%d) sr:%d fec:%s delsys:%s mod:%s rolloff:%s pilot:%s, ts clear=%jd, ts pol=%jd", @@ -569,7 +652,8 @@ int axe_tune(int aid, transponder *tp) ADD_PROP(DTV_TRANSMISSION_MODE, tp->tmode) ADD_PROP(DTV_HIERARCHY, HIERARCHY_AUTO) #if DVBAPIVERSION >= 0x0502 - ADD_PROP(DTV_STREAM_ID, tp->plp & 0xFF) + if (tp->plp_isi >= 0) + ADD_PROP(DTV_STREAM_ID, tp->plp_isi & 0xFF) #endif LOG( @@ -588,7 +672,12 @@ int axe_tune(int aid, transponder *tp) freq = freq * 1000; ADD_PROP(DTV_SYMBOL_RATE, tp->sr) #if DVBAPIVERSION >= 0x0502 - ADD_PROP(DTV_STREAM_ID, ((tp->ds & 0xFF) << 8) | (tp->plp & 0xFF)) + if (tp->plp_isi >= 0) { + int v = tp->plp_isi & 0xFF; + if (tp->ds >= 0) + v |= (tp->ds & 0xFF) << 8; + ADD_PROP(DTV_STREAM_ID, v); + } #endif // valid for DD DVB-C2 devices @@ -617,6 +706,8 @@ int axe_tune(int aid, transponder *tp) break; } + axe_pls_isi(ad, tp); + if ((ioctl(fd_frontend, FE_SET_PROPERTY, &p)) == -1) if (ioctl(fd_frontend, FE_SET_PROPERTY, &p) == -1) { @@ -669,8 +760,8 @@ fe_delivery_system_t axe_delsys(int aid, int fd, fe_delivery_system_t *sys) void axe_get_signal(adapter *ad) { - uint16_t strength = 0, snr = 0, tmp; - uint32_t status = 0, ber = 0; + int strength = 0, snr = 0, tmp; + int status = 0, ber = 0; get_signal(ad, &status, &ber, &strength, &snr); strength = strength * 240 / 24000; @@ -819,9 +910,11 @@ void free_axe_input(adapter *ad) for (aid = 0; aid < 4; aid++) { - ad2 = get_adapter(aid); - if (ad2) + ad2 = get_configured_adapter(aid); + if (ad2) { ad2->axe_used &= ~(1 << ad->id); + LOGM("axe: free input %d : %04x", ad2->id, ad2->axe_used); + } } } @@ -829,11 +922,11 @@ void free_axe_input(adapter *ad) void set_link_adapters(char *o) { int i, la, a_id, b_id; - char buf[100], *arg[20], *sep1; + char buf[1000], *arg[40], *sep1; strncpy(buf, o, sizeof(buf) - 1); buf[sizeof(buf) - 1] = '\0'; - la = split(arg, buf, sizeof(arg), ','); + la = split(arg, buf, ARRAY_SIZE(arg), ','); for (i = 0; i < la; i++) { a_id = map_intd(arg[i], NULL, -1); @@ -857,11 +950,11 @@ void set_link_adapters(char *o) void set_absolute_src(char *o) { int i, la, src, inp, pos; - char buf[100], *arg[20], *inps, *poss; + char buf[1000], *arg[40], *inps, *poss; strncpy(buf, o, sizeof(buf) - 1); buf[sizeof(buf) - 1] = '\0'; - la = split(arg, buf, sizeof(arg), ','); + la = split(arg, buf, ARRAY_SIZE(arg), ','); for (i = 0; i < la; i++) { inps = strchr(arg[i], ':'); diff --git a/src/dvb.c b/src/dvb.c index 5184c4d..fd2b6cf 100644 --- a/src/dvb.c +++ b/src/dvb.c @@ -120,6 +120,10 @@ char *fe_pol[] = {"none", "v", "h", "r", "l", NULL}; +char *fe_pls_mode[] = + {"root", "gold", "combo", + NULL}; + #define make_func(a) \ char *get_##a(int i) \ { \ @@ -142,6 +146,7 @@ make_func(gi); make_func(specinv); make_func(inversion); make_func(pol); +make_func(pls_mode); #define INVALID_URL(a) \ { \ @@ -177,7 +182,9 @@ int detect_dvb_parameters(char *s, transponder *tp) tp->diseqc = -1; tp->c2tft = -1; tp->ds = -1; - tp->plp = -1; + tp->plp_isi = -1; + tp->pls_mode = -1; + tp->pls_code = -1; tp->pids = tp->apids = tp->dpids = tp->x_pmt = NULL; @@ -192,7 +199,7 @@ int detect_dvb_parameters(char *s, transponder *tp) init_dvb_parameters(tp); LOG("detect_dvb_parameters (S)-> %s", s); - la = split(arg, s, 20, '&'); + la = split(arg, s, ARRAY_SIZE(arg), '&'); for (i = 0; i < la; i++) { @@ -228,8 +235,13 @@ int detect_dvb_parameters(char *s, transponder *tp) tp->c2tft = map_int(arg[i] + 6, NULL); if (strncmp("ds=", arg[i], 3) == 0) tp->ds = map_int(arg[i] + 3, NULL); - if (strncmp("plp=", arg[i], 4) == 0) - tp->plp = map_int(arg[i] + 4, NULL); + if (strncmp("plp=", arg[i], 4) == 0 || + strncmp("isi=", arg[i], 4) == 0) + tp->plp_isi = map_int(arg[i] + 4, NULL); + if (strncmp("plsm=", arg[i], 5) == 0) + tp->pls_mode = map_int(arg[i] + 5, fe_pls_mode); + if (strncmp("plsc=", arg[i], 5) == 0) + tp->pls_code = map_int(arg[i] + 5, NULL); if (strncmp("x_pmt=", arg[i], 6) == 0) tp->x_pmt = arg[i] + 6; @@ -275,6 +287,10 @@ void init_dvb_parameters(transponder *tp) tp->mtype = QAM_AUTO; tp->plts = PILOT_AUTO; tp->fec = FEC_AUTO; + tp->ds = TP_VALUE_NOT_ENABLED; + tp->plp_isi = TP_VALUE_NOT_ENABLED; + tp->pls_mode = TP_VALUE_NOT_ENABLED; + tp->pls_code = 1; } void copy_dvb_parameters(transponder *s, transponder *d) @@ -323,8 +339,12 @@ void copy_dvb_parameters(transponder *s, transponder *d) d->c2tft = s->c2tft; if (s->ds != -1) d->ds = s->ds; - if (s->plp != -1) - d->plp = s->plp; + if (s->plp_isi != -1) + d->plp_isi = s->plp_isi; + if (s->pls_mode != -1) + d->pls_mode = s->pls_mode; + if (s->pls_code != -1) + d->pls_code = s->pls_code; d->x_pmt = s->x_pmt; d->apids = s->apids; @@ -506,7 +526,9 @@ int send_diseqc(adapter *ad, int fd, int pos, int pos_change, int pol, int hiban posu = pos / 4; } + cmd.msg[1] = d->addr; cmd.msg[3] = 0xf0 | (((posc << 2) & 0x0c) | (hiband ? 1 : 0) | (pol ? 2 : 0)); + uncmd.msg[1] = d->addr; uncmd.msg[3] = 0xf0 | (posu & 0x0f); LOGM("send_diseqc fd %d, pos = %d (c %d u %d), pol = %d, hiband = %d", @@ -554,12 +576,13 @@ int send_unicable(adapter *ad, int fd, int freq, int pos, int pol, int hiband, d { struct dvb_diseqc_master_cmd cmd = { - {0xe0, 0x11, 0x5a, 0x00, 0x00}, 5}; + {0xe0, 0x10, 0x5a, 0x00, 0x00}, 5}; int t; int committed_no = d->committed_no; t = (freq + d->ufreq + 2) / 4 - 350; + cmd.msg[1] = d->addr; cmd.msg[3] = ((t & 0x0300) >> 8) | (d->uslot << 5) | (pos ? 0x10 : 0) | (hiband ? 4 : 0) | (pol ? 8 : 0); cmd.msg[4] = t & 0xff; @@ -825,7 +848,11 @@ int dvb_tune(int aid, transponder *tp) ADD_PROP(DTV_PILOT, tp->plts) ADD_PROP(DTV_ROLLOFF, tp->ro) #if DVBAPIVERSION >= 0x0502 - ADD_PROP(DTV_STREAM_ID, tp->plp) + if (tp->plp_isi >= 0) + ADD_PROP(DTV_STREAM_ID, tp->plp_isi) +#endif +#if DVBAPIVERSION >= 0x050b /* 5.11 */ + ADD_PROP(DTV_SCRAMBLING_SEQUENCE_INDEX, tp->pls_code) #endif #ifdef USE_DVBAPI3 @@ -856,7 +883,8 @@ int dvb_tune(int aid, transponder *tp) ADD_PROP(DTV_TRANSMISSION_MODE, tp->tmode) ADD_PROP(DTV_HIERARCHY, HIERARCHY_AUTO) #if DVBAPIVERSION >= 0x0502 - ADD_PROP(DTV_STREAM_ID, tp->plp & 0xFF) + if (tp->plp_isi >= 0) + ADD_PROP(DTV_STREAM_ID, tp->plp_isi & 0xFF) #endif // old DVBAPI version 3 @@ -894,7 +922,12 @@ int dvb_tune(int aid, transponder *tp) freq = freq * 1000; ADD_PROP(DTV_SYMBOL_RATE, tp->sr) #if DVBAPIVERSION >= 0x0502 - ADD_PROP(DTV_STREAM_ID, ((tp->ds & 0xFF) << 8) | (tp->plp & 0xFF)) + if (tp->plp_isi >= 0) { + int v = tp->plp_isi & 0xFF; + if (tp->ds >= 0) + v |= (tp->ds & 0xFF) << 8; + ADD_PROP(DTV_STREAM_ID, v); + } #endif // valid for DD DVB-C2 devices diff --git a/src/dvb.h b/src/dvb.h index 9830487..b9fd223 100644 --- a/src/dvb.h +++ b/src/dvb.h @@ -143,6 +143,12 @@ typedef enum fe_modulation { } fe_modulation_t; #endif +typedef enum fe_pls_mode { + PLS_MODE_ROOT, + PLS_MODE_GOLD, + PLS_MODE_COMBO, +} fe_pls_mode_t; + #if DVBAPIVERSION < 0x0505 #define DTV_ENUM_DELSYS 44 #define SYS_DVBC_ANNEX_A SYS_DVBC_ANNEX_AC @@ -182,6 +188,8 @@ typedef enum fe_modulation { #define MIN_FRQ_DVBS 950000 #define MAX_FRQ_DVBS 2150000 +#define TP_VALUE_NOT_ENABLED (-255) + typedef struct diseqc { #define SWITCH_UNICABLE 1 @@ -189,12 +197,14 @@ typedef struct diseqc #define SWITCH_SLAVE 3 int switch_type; /* parameters */ - int uslot; // unicable/jess slot - int ufreq; // unicable/jess frequency - int pin; - int only13v; // unicable - use 13V voltage only - int fast; // don't send diseqc without position change - int committed_no, uncommitted_no; // diseqc info + int uslot; // unicable/jess slot + int ufreq; // unicable/jess frequency + int pin; // unicable pin code + int only13v; // unicable - use 13V voltage only + int fast; // don't send diseqc without position change + int addr; // diseqc address (second byte in the sequence) + int committed_no; // committed switch number + int uncommitted_no; // uncommitted switch number /* timing */ int before_cmd; int after_cmd; @@ -232,10 +242,11 @@ typedef struct struct_transponder diseqc diseqc_param; - // DVB-C2 - int c2tft; - int ds; - int plp; + int c2tft; // DVB-C2 + int ds; // DVB-C2 (data slice) + int plp_isi; // DVB-T2/DVB-S2 + int pls_mode; // DVB-S2 + int pls_code; // DVB-S2 char *apids, *pids, *dpids, *x_pmt; } transponder; @@ -279,6 +290,7 @@ char *get_gi(int i); char *get_specinv(int i); char *get_pol(int i); char *get_inversion(int i); +char *get_pls_mode(int i); extern char *fe_delsys[]; extern char *fe_fec[]; diff --git a/src/minisatip.c b/src/minisatip.c index 0cd9b0b..884f14c 100644 --- a/src/minisatip.c +++ b/src/minisatip.c @@ -286,6 +286,8 @@ Help\n\ * eg: -d 0:1-0 (which is the default for each adapter).\n\ - note: * as adapter means apply to all adapters\n\ - note: * before committed number enables fast-switch (only voltage/tone)\n\ + - note: @ before committed number sets 'Any Device' diseqc address (0x00)\n\ + - note: . before committed number sets 'LNB' diseqc address (0x11)\n\ \n\ * -q --diseqc-timing ADAPTER1:BEFORE_CMD1-AFTER_CMD1-AFTER_REPEATED_CMD1-AFTER_SWITCH1-AFTER_BURST1-AFTER_TONE1[,...]\n\ \t* All timing values are in ms, default adapter values are: 15-54-15-15-15-0\n\ @@ -513,6 +515,7 @@ void set_options(int argc, char *argv[]) opts.document_root = "html"; opts.xml_path = DESC_XML; opts.th_priority = -1; + opts.diseqc_addr = 0x10; opts.diseqc_before_cmd = 15; opts.diseqc_after_cmd = 54; opts.diseqc_after_repeated_cmd = 15; @@ -552,7 +555,7 @@ void set_options(int argc, char *argv[]) #endif #ifdef AXE opts.no_threads = 1; - opts.document_root = "/usr/share/minisatip/html"; + opts.document_root = "/usr/share/minisatip8/html"; #define AXE_OPTS "7:QW:8:A:" #else #define AXE_OPTS "" @@ -614,7 +617,7 @@ void set_options(int argc, char *argv[]) int i; memset(buf, 0, sizeof(buf)); strncpy(buf, optarg, sizeof(buf) - 1); - int la = split(arg, buf, 50, ','); + int la = split(arg, buf, ARRAY_SIZE(arg), ','); for (i = 0; i < la; i++) { int level = map_intd(arg[i], loglevels, -1); @@ -1046,7 +1049,7 @@ int read_rtsp(sockets *s) return 0; } - la = split(arg, (char *)s->buf, 50, ' '); + la = split(arg, (char *)s->buf, ARRAY_SIZE(arg), ' '); cseq = 0; if (la < 2) LOG_AND_RETURN(0, @@ -1311,7 +1314,7 @@ int read_http(sockets *s) LOG("read HTTP from %d sid: %d: ", s->sock, s->sid); LOGM("%s", s->buf); - split(arg, (char *)s->buf, 50, ' '); + split(arg, (char *)s->buf, ARRAY_SIZE(arg), ' '); // LOG("args: %s -> %s -> %s",arg[0],arg[1],arg[2]); if (strncmp(arg[0], "GET", 3) && strncmp(arg[0], "POST", 4) && !is_head) REPLY_AND_RETURN(503); diff --git a/src/minisatip.h b/src/minisatip.h index 6512250..520de73 100644 --- a/src/minisatip.h +++ b/src/minisatip.h @@ -9,7 +9,7 @@ #define VERSION_BUILD "16" #define CC(a, b, c) #a b #c -#define VERSION CC(0.7., VERSION_BUILD, ) +#define VERSION CC(0.7., VERSION_BUILD, -axe214) void set_options(int argc, char *argv[]); @@ -94,6 +94,7 @@ struct struct_opts char no_threads; int th_priority; int diseqc_fast; + int diseqc_addr; int diseqc_committed_no; int diseqc_uncommitted_no; int diseqc_before_cmd; diff --git a/src/satipc.c b/src/satipc.c index 24c82ff..e748048 100644 --- a/src/satipc.c +++ b/src/satipc.c @@ -176,7 +176,7 @@ int satipc_reply(sockets *s) sip->option_no_option = 1; } - la = split(arg, (char *)s->buf, 100, ' '); + la = split(arg, (char *)s->buf, ARRAY_SIZE(arg), ' '); rc = map_int(arg[1], NULL); if (sip->option_no_session && sip->last_cmd == RTSP_OPTIONS && !sess && sip->session[0]) @@ -807,6 +807,12 @@ void get_s2_url(adapter *ad, char *url) FILL("&fec=%s", tp->fec, FEC_AUTO, get_fec(tp->fec)); FILL("&ro=%s", ro, ROLLOFF_AUTO, get_rolloff(ro)); FILL("&plts=%s", plts, PILOT_AUTO, get_pilot(plts)); + if (tp->plp_isi >= 0) + FILL("&isi=%d", tp->plp_isi, 0, tp->plp_isi); + if (tp->pls_mode >= 0) + FILL("&plsm=%s", tp->pls_mode, -1, get_pls_mode(tp->pls_mode)); + if (tp->pls_code >= 0) + FILL("&plsc=%d", tp->pls_code, -1, tp->pls_code); url[len] = 0; return; } @@ -831,7 +837,8 @@ void get_c2_url(adapter *ad, char *url) FILL("&specinv=%d", tp->inversion, INVERSION_AUTO, tp->inversion); FILL("&t2id=%d", tp->t2id, 0, tp->t2id); FILL("&sm=%d", tp->sm, 0, tp->sm); - FILL("&plp=%d", tp->plp, 0, tp->plp); + if (tp->plp_isi >= 0) + FILL("&plp=%d", tp->plp_isi, 0, tp->plp_isi); url[len] = 0; return; } @@ -854,8 +861,10 @@ void get_t2_url(adapter *ad, char *url) FILL("&tmode=%s", tp->tmode, TRANSMISSION_MODE_AUTO, get_tmode(tp->tmode)); FILL("&specinv=%d", tp->inversion, INVERSION_AUTO, tp->inversion); FILL("&c2tft=%d", tp->c2tft, 0, tp->c2tft); - FILL("&ds=%d", tp->ds, 0, tp->ds); - FILL("&plp=%d", tp->plp, 0, tp->plp); + if (tp->ds >= 0) + FILL("&ds=%d", tp->ds, 0, tp->ds); + if (tp->plp_isi >= 0) + FILL("&plp=%d", tp->plp_isi, 0, tp->plp_isi); url[len] = 0; return; } @@ -1288,7 +1297,7 @@ void find_satip_adapter(adapter **a) return; char satip_servers[strlen(opts.satip_servers) + 10]; strcpy(satip_servers, opts.satip_servers); - la = split(arg, satip_servers, 50, ','); + la = split(arg, satip_servers, ARRAY_SIZE(arg), ','); for (i = 0; i < la; i++) { @@ -1420,7 +1429,7 @@ void satip_getxml_data(char *data, int len, void *opaque, Shttp_client *h) eos = strchr(sep, '<'); if (eos) *eos = 0; - la = split(arg, sep, MAX_DVBAPI_SYSTEMS, ','); + la = split(arg, sep, ARRAY_SIZE(arg), ','); for (i = 0; i < la; i++) { int ds = map_intd(arg[i], satip_delsys, -1); @@ -1474,7 +1483,7 @@ int satip_getxml(void *x) memset(satip_xml, 0, sizeof(satip_xml)); memset(sxd, 0, sizeof(sxd)); strncpy(satip_xml, opts.satip_xml, sizeof(satip_xml) - 1); - la = split(arg, satip_xml, MAX_SATIP_XML, ','); + la = split(arg, satip_xml, ARRAY_SIZE(arg), ','); for (i = 0; i < la; i++) { SAFE_STRCPY(sxd[i].url, arg[i]); diff --git a/src/stream.c b/src/stream.c index bde2992..f587e93 100644 --- a/src/stream.c +++ b/src/stream.c @@ -422,7 +422,7 @@ int decode_transport(sockets *s, char *arg, char *default_rtp, int start_rtp) return 0; } - l = split(arg2, arg, 10, ';'); + l = split(arg2, arg, ARRAY_SIZE(arg2), ';'); } // LOG("arg2 %s %s %s",arg2[0],arg2[1],arg2[2]); memset(&p, 0, sizeof(p)); @@ -987,7 +987,7 @@ int process_dmx(sockets *s) #endif rlen = ad->rlen; - int packet_no_sid = check_cc(ad); + const int packet_no_sid = 0 /* check_cc(ad) */; if (ad->sid_cnt == 1 && ad->master_sid >= 0 && !packet_no_sid) // we have just 1 stream, do not check the pids, send everything to the destination { diff --git a/src/utils.h b/src/utils.h index 1c9aa0d..c184668 100644 --- a/src/utils.h +++ b/src/utils.h @@ -263,6 +263,8 @@ static inline int get_index_hash(void *p, int max, int struct_size, uint32_t key typedef ssize_t (*mywritev)(int fd, const struct iovec *io, int len); +#define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0])) + #ifdef TESTING #define writev(a, b, c) _writev(a, b, c)