/* blindscan -- autosearch all parameters of a DVB signal
 *  version 0.1, 2004/08/01
 *
 * This tool is able to automatically find:
 * - frequency and polarization
 * - symbol rate
 * - spectral inversion
 * - fec rate
 * - used pids
 *
 * This is an experimental version and is only tested with a modified
 * mt312 frontend driver.
 *
 * Copyright (C) 2004 Roberto Ragusa (mail at robertoragusa dot it)
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */


  // LNB data
  int lof_lo_khz=9750000;
  int lof_hi_khz=10750000; // 10600000 for a "universal" LNB
  int lof_switch_khz=11700000;


  // scan parameters (sorry, no option parsing yet)
#if 1
  // find MCPC transponders on hotbird (13e) or astra (19e)
  int check_from_frequency_khz=10700000;
  int check_to_frequency_khz=12750000;
  int check_step_khz=15000; // try about symbolrate/2
  int check_from_polarization_0v_1h=0;
  int check_to_polarization_0v_1h=1;
  int check_symbol_rate=0x800; //explanation below
  double check_timeout=0.2; // time spent on a frequency
  double freq_adj_timeout=0.2; // time spent fine tuning a signal
  double pid_find_timeout=0; // 0 means "no search"
#else
  // accurately find SCPC feeds on eutelsat w3a (7e)
  int check_from_frequency_khz=10950000;
  int check_to_frequency_khz=11200000;
  int check_step_khz=3000; // try about symbolrate/2
  int check_from_polarization_0v_1h=0;
  int check_to_polarization_0v_1h=1;
  int check_symbol_rate=0x0c0; //explanation below
  double check_timeout=0.4; // time spent on a frequency
  double freq_adj_timeout=0.5; // time spent fine tuning a signal
  double pid_find_timeout=1; // 0 means "no search"
#endif
  /* all timeouts in seconds */
  /* longer timeouts for extra accuracy */
  /* shorter timeouts for extra speed */

  /*
    how to specify symbol rate (very very very mt312 specific!):
    - manual mode: you specify a symbol rate in kHz, e.g. 27500000 or 22000000
    - automode: you specify a number between 0x000 and 0xfff
                every bit is a range to be searched, you can turn on
		more than one bit, but mixing high and low rates
		doesn't work in my tests (hardware bug/limitation?)

                Here is the table (30000 is 30000000 symbols/s)

                mask   from    to
                ---------------------------
		0x800 30000   20000    
		0x400 20000   15000    
		0x200 15000   10000    
		0x100 10000    7500    
		0x080  7500    5000    
		0x040  5000    3750    
		0x020  3750    2500    
		0x010  2500    1875    
		0x008  1875    1250    
		0x004  1250     937.5  
		0x002   937.5   625   
		0x001   625     468.75 

                if you're searching MCPC signals (typically around 27500 or 22000),
		you can use 0x800,
                if you're searching SCPC signals (feeds, typically 5632, 6110,...),
		you can use 0x080 or try a wider range as 0x1c0 or 0x3c0.
  */


#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <unistd.h>

#include <linux/dvb/frontend.h>
#include <linux/dvb/dmx.h>

double maximum(double a, double b){
  return a>b?a:b;
}

double timestamp(){
  struct timeval tv;
  gettimeofday(&tv,NULL);
  return tv.tv_sec+1e-6*tv.tv_usec;
}

void sleep_seconds(double seconds){
  struct timespec tv;
  if(seconds<=0) return;
  tv.tv_sec=(time_t)seconds;
  tv.tv_nsec=(long)((seconds-(double)tv.tv_sec)*1e9);
  nanosleep(&tv,NULL);
}

void draw_bar(int percent){
  int i;
  for(i=0;i<20;i++){
    fprintf(stderr,(5*i<percent)?"-":" ");
  }
}

void panic(char *s){
  perror(s);
  exit(EXIT_FAILURE);
}

int tune_freq(int fe, int freq_khz, int polar_0v_1h){
  int tone;
  int lof;
  int ifreq_khz;
  static int last_polar_0v_1h=-1;
  static int last_tone=-1;
  static int last_ifreq_khz=-1;
  double unstable_delay=0;
  struct dvb_frontend_parameters tuneto;
  struct dvb_frontend_event ev;
  int r;

  // decide tone and ifreq
  if(freq_khz<lof_switch_khz){
    tone=0;
    lof=lof_lo_khz;
  }
  else{
    tone=1;
    lof=lof_hi_khz;
  }
  ifreq_khz=freq_khz-lof;
  // set H or V
  if(polar_0v_1h!=last_polar_0v_1h){
    r=ioctl(fe,FE_SET_VOLTAGE,polar_0v_1h==0?SEC_VOLTAGE_13:SEC_VOLTAGE_18);
    if(r==-1) panic("FE_SET_VOLTAGE failed");
    unstable_delay=maximum(unstable_delay,0.005);
  }
  // set tone or not
  if(tone!=last_tone){
    r=ioctl(fe,FE_SET_TONE,tone?SEC_TONE_ON:SEC_TONE_OFF);
    if(r==-1) panic("FE_SET_TONE failed");
    unstable_delay=maximum(unstable_delay,0.005);
  }
  // set ifreq
  if(ifreq_khz!=last_ifreq_khz){
    tuneto.frequency=ifreq_khz;
    tuneto.inversion=INVERSION_AUTO;
    tuneto.u.qpsk.symbol_rate=check_symbol_rate;
    tuneto.u.qpsk.fec_inner=FEC_AUTO;
    r=ioctl(fe,FE_SET_FRONTEND,&tuneto);
    if(r==-1) panic("FE_SET_FRONTEND failed");
    unstable_delay=maximum(unstable_delay,0.002);
  }
  // let it stabilize a little
  sleep_seconds(unstable_delay);
  // flush events
  while(ioctl(fe,FE_GET_EVENT,&ev)!=-1);
  // update frontend status
  last_polar_0v_1h=polar_0v_1h;
  last_tone=tone;
  last_ifreq_khz=ifreq_khz;
  unstable_delay=0;
  return lof; // this can be useful to the caller
}

int process_signal(int fe, int lof, int polar_0v_1h, int percent_of_check_time);

int check_signal(int fe, int freq_khz, int polar_0v_1h, int lof){
  double check_start;
  double check_delay;
  int percent_of_check_time;

  check_delay=0;
  // check result
  check_start=timestamp();
  do{
    fe_status_t status;
    uint16_t snr,signal;

    sleep_seconds(check_delay);

    ioctl(fe,FE_READ_STATUS,&status);
    ioctl(fe,FE_READ_SIGNAL_STRENGTH,&signal);
    ioctl(fe,FE_READ_SNR,&snr);

    // bar animation, useful to see if the timeout is too long or too short
    percent_of_check_time=(int)((timestamp()-check_start)/check_timeout*100+0.5);
    if(percent_of_check_time<0) percent_of_check_time=0;
    if(percent_of_check_time>100) percent_of_check_time=100;
    draw_bar(percent_of_check_time);
    fprintf(stderr," %5i %c ",
            freq_khz/1000,
	    polar_0v_1h==0?'V':'H');
    fprintf(stderr,"signal %04x  snr %04x  status %02x ",
            signal,snr,status);
    if(status&FE_HAS_LOCK) fprintf(stderr,"FE_HAS_LOCK");
    fprintf(stderr,"\r");fflush(stderr);

    if(status&FE_HAS_LOCK){ // found something!
      return process_signal(fe,lof,polar_0v_1h,percent_of_check_time);
    }
    else{
      check_delay=0.100;
    }
  } while(timestamp()<(check_start+check_timeout)); //keep checking
  return 0; // timeout elapsed, nothing found here
}

void find_pids(void);

int process_signal(int fe, int lof, int polar_0v_1h, int percent_of_check_time){
  double freq_adj_start;
  double freq_adj_delay;
  uint16_t frequency_offset;
  int frequency_correction_khz;
  int corrected_frequency_khz;
  struct dvb_frontend_parameters tuneto;
  int r;

  // get the good news
  r=ioctl(fe,FE_GET_FRONTEND,&tuneto);
  if(r==-1) panic("FE_GET_FRONTEND failed");
  // find where we should aim precisely
  freq_adj_delay=0;
  freq_adj_start=timestamp();
  // loop for a while because the hardware has to converge
  // datasheet says one second, but we can go much faster
  do{
    sleep_seconds(freq_adj_delay);
    // read frequency offset (this is a new ioctl)
    ioctl(fe,FE_READ_FREQUENCY_OFFSET,&frequency_offset);
    if(r==-1) panic("FE_GET_FREQUENCY_OFFSET failed");
    // correction
    frequency_correction_khz=(int)((double)(int)(int16_t)frequency_offset/512*1000+0.5);
    corrected_frequency_khz=tuneto.frequency+lof+frequency_correction_khz;
    draw_bar(percent_of_check_time);
    fprintf(stderr," %5i %c %c %i %i/%i (%5i%+i)  FOUND!                                     \r",
            corrected_frequency_khz/1000,
            polar_0v_1h==0?'V':'H',
            tuneto.inversion==0?'+':'-',
            tuneto.u.qpsk.symbol_rate/1000,
            tuneto.u.qpsk.fec_inner,tuneto.u.qpsk.fec_inner+1,
	    (tuneto.frequency+lof)/1000,
	    frequency_correction_khz/1000);fflush(stderr);
    freq_adj_delay=0.050;
  } while(timestamp()<freq_adj_start+freq_adj_timeout);
  fprintf(stderr,"\n");

  // find pids is we have to
  if(pid_find_timeout>0) find_pids();

  // skip out of the band this signal is occupying by returning
  // a suggestion for where to restart
  // (we're trying to avoid duplicates) (9/10 looks good)
  return corrected_frequency_khz+(tuneto.u.qpsk.symbol_rate/1000)*9/10;
}

void find_pids(void){
  double pid_find_start;
  double elapsed_seconds;
  int dem;
  int dvr;
  struct dmx_pes_filter_params pesfilter;
  int r;
  int i,sum;
  int pid;
  int bytes;
  int total_bytes;
  int count_clear[8192],count_encrypted[8192];
#define BUFSIZE 128*1024
  char buf[BUFSIZE];
  fd_set rfds;
  struct timeval tv;

  // we ask all the data from this stream
  dem=open("/dev/dvb/adapter0/demux0",O_RDWR);
  if(dem<0) panic("open demux failed");
  r=ioctl(dem,DMX_SET_BUFFER_SIZE,128*1024);
  if(r<0) panic("ioctl DMX_SET_BUFFER_SIZE failed");
  pesfilter.pid=8192; // special pid (all pids)
  pesfilter.input=DMX_IN_FRONTEND;
  pesfilter.output=DMX_OUT_TS_TAP;
  pesfilter.pes_type=DMX_PES_OTHER;
  pesfilter.flags=DMX_IMMEDIATE_START;
  r=ioctl(dem,DMX_SET_PES_FILTER,&pesfilter);
  if(r<0) panic("ioctl DMX_SET_PES_FILTER failed");

  // and watch which pids are coming
  for(i=0;i<8192;i++) count_clear[i]=count_encrypted[i]=0;
  bytes=0;
  total_bytes=0;

  dvr=open("/dev/dvb/adapter0/dvr0",O_RDONLY|O_NONBLOCK);
  if(dvr<0) panic("open dvr failed");

  pid_find_start=timestamp();
  fprintf(stderr,"pids");fflush(stderr);
  do{
    fprintf(stderr,".");fflush(stderr);
    // check if we can read stream data
    // if not, don't wait forever, just exit
    FD_ZERO(&rfds);
    FD_SET(dvr,&rfds);
    tv.tv_sec=3;
    tv.tv_usec=0;
    r=select(dvr+1,&rfds,NULL,NULL,&tv);
    if (r==-1) panic("select() failed");
    //if (r==0) panic("no data from vdr");
    if (r==0){fprintf(stderr,"dvr reading problem, we go on\n");close(dvr);close(dem); return;}

    // packets are 188 bytes long, so we're careful with alignment
    r=read(dvr,buf+bytes,BUFSIZE-bytes);
    if(r<=0) panic("can't read from dvr");
    bytes+=r;
    for(i=0;i<bytes/188;i++){
      if(buf[188*i]!=0x47) panic("lost sync while finding pids");
      total_bytes+=188;
      pid=(buf[188*i+1]&0x1f)<<8|buf[188*i+2];
      if((buf[188*i+3]&0x80)==0){ // in clear :-)
        count_clear[pid]++;
      }
      else{ // encrypted :-(
        count_encrypted[pid]++;
      }
    }
    // realign tail
    for(i=0;i<bytes%188;i++){
      buf[i]=buf[bytes/188*188+i];
    }
    bytes=bytes%188;
  } while(timestamp()<pid_find_start+pid_find_timeout);
  // stop watch, so we can calculate data speed
  elapsed_seconds=timestamp()-pid_find_start;
  close(dvr);
  close(dem);

  // display in a nice table what we found
  fprintf(stderr,"\r                                                             \r");
  fprintf(stderr,"         +------------+------------+------------+\n");
  fprintf(stderr,"         |    pid     | clear Mb/s | encr. Mb/s |\n");
  fprintf(stderr,"         +------------+------------+------------+\n");
  for(i=0;i<8192;i++){
    if(count_clear[i]||count_encrypted[i]){
      fprintf(stderr,"         |%04x (%4i) ",i,i);
      if(count_clear[i]){
        fprintf(stderr, "|   %6.3f   ",(double)count_clear[i]*188*8/1000000/elapsed_seconds);
      }
      else{
        fprintf(stderr,"|            ");
      }
      if(count_encrypted[i]){
        fprintf(stderr, "|   %6.3f   ",(double)count_encrypted[i]*188*8/1000000/elapsed_seconds);
      }
      else{
        fprintf(stderr,"|            ");
      }
      fprintf(stderr,"|\n");
    }
  }
  fprintf(stderr,"         +------------+------------+------------+\n");
  fprintf(stderr,"         |     TOTAL  ");
  sum=0;for(i=0;i<8192;i++) sum+=count_clear[i];
  if(sum){
    fprintf(stderr, "|   %6.3f   ",(double)sum*188*8/1000000/elapsed_seconds);
  }
  else{
    fprintf(stderr,"|           ");
  }
  sum=0;for(i=0;i<8192;i++) sum+=count_encrypted[i];
  if(sum){
    fprintf(stderr, "|   %6.3f   ",(double)sum*188*8/1000000/elapsed_seconds);
  }
  else{
    fprintf(stderr,"|            ");
  }
  fprintf(stderr,"|\n");
  fprintf(stderr,"         +------------+------------+------------+\n");
  fprintf(stderr,"\n");
}

int main(void){
  int fe;
  int r;
  struct dvb_frontend_info info;
  int freq_khz;
  int polar_0v_1h;
  int lof;
  int skip_to_freq_khz;

  // preliminary steps
  fe=open("/dev/dvb/adapter0/frontend0",O_RDWR|O_NONBLOCK);
  if(fe<0) panic("open frontend failed");

  r=ioctl(fe,FE_GET_INFO,&info);
  if(r<0) panic("ioctl FE_GET_INFO failed");
  if(info.type!=FE_QPSK) panic("frontend not DVB-S!");

  // main loop
  for(polar_0v_1h=check_from_polarization_0v_1h;polar_0v_1h<=check_to_polarization_0v_1h;polar_0v_1h++){
    skip_to_freq_khz=0;
    for(freq_khz=check_from_frequency_khz;freq_khz<=check_to_frequency_khz;freq_khz+=check_step_khz){
      if(freq_khz<skip_to_freq_khz) freq_khz=skip_to_freq_khz;
      lof=tune_freq(fe,freq_khz,polar_0v_1h);
      r=check_signal(fe,freq_khz,polar_0v_1h,lof);
      if(r!=0) skip_to_freq_khz=r;
    }
  }

  // already finished
  fprintf(stderr,"                                                                               \n");
  close(fe);
  return 0;
}
