/* HF amateur radio communicator * * digital HF modes (PSK31, RTTY, MT63, ...) * primary server module */ /* servermain: * * opens and initializes sound card * * initializer encoder and decoder classes (modes/psk31*.C) * * starts new thread for encoding and decoding * * offers interface functions for the user interface * * mutex operations for accesing en-/decoder classes from en/decoding * thread and from user interface thread (==interface functions) */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef USE_PTHREAD #include #else #define pthread_mutex_init(x,y) #define pthread_mutex_lock(x) #define pthread_mutex_unlock(x) #endif #include "../modes/psk31-receiver.h" #include "../modes/psk31-transmitter.h" #include "../modes/psk31-fft.h" #include "../modes/psk31-coder.h" #include "server.h" #define USE_REALTIME // receive / transmit state information int full_duplex; static char *audio_path="/dev/audio"; static char *ptt_path=NULL; static int audiofd=-1, pttfd=-1; static int ptt_invert=0; // schedule_transmit: 0=no 1=when dcd goes off, 2=immediately static int schedule_transmit=0, transmit=0; static int dcdlevel=0; /* information: mutex usage: * mutex_rx is used for accessing the psk31_receiver objects as well as * the psk31_fft object. this lock is set by the GUI if when * changing configuration parameters. * mutex_tx is similar for the psk31_transmitter object. * the buffer mutexes are used for the RX buffers, when copying * data to the GUI and when transfering data from the * psk31 objects in the master loop */ /* secondary RX decoders: [limit: N_BUFFERS-3] * a secondary can be started by commControl on an unused channel (3..254) * with command COMM_MODE and value set to MO_NORMAL * it can be disabled by COMM_MODE with value MO_DISABLED * if the channel is disable, no other command than COMM_MODE(MO_NORMAL) is * accepted. */ //static psk31_receiver *psk31rx; --- now in buffer structure! static psk31_transmitter *psk31tx; static psk31_fft *psk31fft; #ifdef USE_PTHREAD static pthread_t master_thr; static pthread_mutex_t mutex_rx = PTHREAD_MUTEX_INITIALIZER; static pthread_mutex_t mutex_tx = PTHREAD_MUTEX_INITIALIZER; #endif /* static pthread_mutex_t mutex_fft= PTHREAD_MUTEX_INITIALIZER; */ /* FFT uses RX mutex! Thus psk31rx also can access the fft */ typedef struct { int len; int rpos; int wpos; char *mem; psk31_receiver *psk31rx; #ifdef USE_PTHREAD pthread_mutex_t mutex; #endif } buffer_t; // Buffer==Channel: 0=TXdata-Echo 1=FFT-data 2,...=RX-data #define N_BUFFERS 6 static buffer_t buffers[N_BUFFERS]; static void init_buffer() { int i; for(i=0; ib->len) return -1; // too much data! pthread_mutex_lock( &b->mutex ); if(cnt> ((b->wpos-b->rpos)%b->len)) error=-1; else error=0; if(b->wpos+cnt > b->len) n=b->len-b->wpos; else n=cnt; bcopy( data, b->mem+b->wpos, n ); b->wpos += n; cnt-=n; if(cnt>0) { bcopy( data+n, b->mem, cnt); b->wpos = cnt; } pthread_mutex_unlock( &b->mutex); return error; } static int read_buffer(int bufnr, char *data, int cnt) { int n, buflen; buffer_t *b = buffers+bufnr; pthread_mutex_lock( &b->mutex ); buflen = (b->wpos-b->rpos)%b->len; if(buflen<0) buflen+=b->len; if(cnt>buflen) cnt=buflen; if(b->rpos+cnt > b->len) n=b->len-b->rpos; else n=cnt; bcopy( b->mem+b->rpos, data, n ); b->rpos += n; cnt-=n; if( cnt>0 ) { bcopy( b->mem, data+n, cnt ); b->rpos = cnt; } pthread_mutex_unlock( &b->mutex); return n; } #if 0 static void atexitfunc() { if(audiofd>=0) close(audiofd); } #endif char ibuf[65536]; char *ptr; static int init_audio() { int audio,val; audio=open(audio_path, O_RDWR|O_NONBLOCK); if(audio<0) return -1; val=8000; if( ioctl(audio, SNDCTL_DSP_SPEED, &val)<0 ) return -1; if(val!=8000) { fprintf(stderr,"inexact sampling rate: " "request for %d resulted in %d",8000,val); } val=AFMT_S16_LE; if( ioctl(audio, SNDCTL_DSP_SETFMT, &val)<0 ) return -1; val=DMA_BUF_BITS; if( ioctl(audio, SNDCTL_DSP_SETFRAGMENT, &val)<0 ) return -1; val=1; if( ioctl(audio, SNDCTL_DSP_NONBLOCK, &val)<0 ) return -1; val=0; if( ioctl(audio, SNDCTL_DSP_STEREO, &val)<0 ) return -1; // Check if the device is operating in full duplex mode if( ioctl(audio, SNDCTL_DSP_GETCAPS, &val)<0 ) perror("Warning: GETCAPS on audio device failed"); else if(val&DSP_CAP_DUPLEX) full_duplex=1; fprintf(stderr,"Using %s mode!\n", full_duplex?"full duplex":"halv duplex"); #if 0 val = 0; if (ioctl(audio, SNDCTL_DSP_SETTRIGGER, &val) == -1) perror("ioctl: SNDCTL_DSP_SETTRIGGER"); val = PCM_ENABLE_INPUT; if (ioctl(audio, SNDCTL_DSP_SETTRIGGER, &val) == -1) perror("ioctl: SNDCTL_DSP_SETTRIGGER"); #endif return audio; } static int ctl_ptt(int onoff); static int init_ptt(void) { pttfd = open(ptt_path, O_RDWR); if(pttfd>0) { if(ctl_ptt(0)<0) return -1; } return pttfd; } static int ctl_ptt(int onoff) { //fprintf(stderr,"pttctl[%d]\n",onoff); if(pttfd<0) return 0; // PTT control disabled, nothing to do... if(ptt_invert) onoff=!onoff; int arg = TIOCM_RTS|TIOCM_DTR; if(ioctl(pttfd, onoff?TIOCMBIS:TIOCMBIC, &arg)<0) return -1; return 0; } #ifdef USE_PTHREAD void master_handler(void); static void *master_thr_func(void *dummy) { while(1) { master_handler(); } } #endif void master_handler(void) { int res; fd_set rset, wset, eset; struct timeval tm; int dcd; buffers[COMM_RXCH].psk31rx->get_info(NULL,NULL,NULL,NULL, NULL,&dcd,NULL,NULL); if( (schedule_transmit==1&&dcd==0) || schedule_transmit==2) { ctl_ptt(1); psk31tx->send_char(TX_START); transmit=1; schedule_transmit=0; } FD_ZERO(&rset); FD_ZERO(&wset); FD_ZERO(&eset); FD_SET(audiofd, &rset); //FD_SET(audiofd, &eset); if(transmit) FD_SET(audiofd, &wset); tm.tv_sec=0; tm.tv_usec=50000; /* 50ms */ res=select(audiofd+1, &rset, &wset, &eset, &tm); /* In my older version I had the problem, that there exist * sound drivers which do not support select correctly. So this * code tries to read/write from/to the audiodevice without * respecting FD_ISSET. This should work with all drivers */ if( !transmit ) { short sample; for(;;) { res=read(audiofd, &sample, 2); if(res==0) { break; } else if(res!=2) { //fprintf(stderr,%d %d\n",res,errno); if(errno==EINTR) break; //continue; if(errno==EAGAIN||errno==EBUSY) break; perror("Audio read failed"); exit(1); } for(int r=COMM_RXCH; rprocess_rx_sample(sample); pthread_mutex_unlock(&mutex_rx); if(res!=NO_CHAR) { rx->get_info(NULL,NULL,NULL,NULL, NULL,&dcd,NULL,NULL); if (dcd||(dcdlevel==-1)) { char cd = (char)res; write_buffer(r, &cd, 1); } } } } } if(transmit) { char cd; for(;;) { if(full_duplex) { // clear buffer if necessary char buffer[128]; for(;;) { res=read(audiofd, buffer, 128); if(res!=128) break; } } pthread_mutex_lock(&mutex_tx); res=psk31tx->processor(); pthread_mutex_unlock(&mutex_tx); // echo-characters, including TX_END, // are delivered from psk31tx->processor at the // exact end of the transmission! if(res==TX_BUSY) { break; } if(res==TX_ERROR) { perror("tx error:"); break; } if(res==TX_END) { transmit=0; ctl_ptt(0); break; } cd = (char)(res&0xFF); write_buffer(0, &cd, 1); } } } #ifdef USE_PTHREAD static int run_master_thread() { pthread_attr_t attr; fprintf(stderr,"run_master_thread()\n"); #ifdef USEREALTIME struct sched_param schp; #endif if (pthread_attr_init(&attr)) { perror("Cannot initialize pthread attributes"); exit(4); } #ifdef USEREALTIME memset(&schp, 0, sizeof(schp)); if( pthread_attr_setschedpolicy(&attr, SCHED_RR) ) { perror("Cannot set realtime scheduling attribute"); exit(5); } schp.sched_priority = sched_get_priority_min(SCHED_RR)+1; if( pthread_attr_setschedparam(&attr, &schp) ) { perror("Cannot set realtime scheduling priority"); exit(6); } #endif if( pthread_create(&master_thr, &attr, master_thr_func, NULL) ) { perror("Cannot create IO processing thread"); exit(7); } if (pthread_attr_destroy(&attr)) { perror("Failed to destroy attribute"); } return 0; } #endif /* audio, ptt: full path to device * data: directory of coder data file (psk31.cod) * ptt may be something like "/dev/ttyS0,i" for inverted PTT */ int server_main(char *audio, char *ptt, char *datadir) { init_buffer(); if(audio) audio_path = strdup(audio); if(ptt) ptt_path = strdup(ptt); if(ptt_path!=NULL) { int len = strlen(ptt_path); if(len>2 && ptt_path[len-2]==',') { ptt_invert = (ptt_path[len-1]!='0'); ptt_path[len-2]=0; } else ptt_invert=0; } if( (audiofd=init_audio())<0 ) { fprintf(stderr, "Cannot open audio device "); perror(audio_path); return -1; } if( ptt_path && ( (pttfd=init_ptt())<0 ) ) { fprintf(stderr, "Cannot open PTT device "); perror(ptt_path); } psk31_coder::init_tables(datadir); psk31fft = new psk31_fft(); psk31fft->set_parameters(1024, 1024, psk31_fft::MODE_RXDATA); buffers[COMM_RXCH].psk31rx = new psk31_receiver(psk31fft); psk31tx = new psk31_transmitter(); psk31tx->set_audiofd(audiofd); #ifdef USE_PTHREAD if( run_master_thread()<0 ) return -1; #endif return 0; } int commWaitUpdate(unsigned long timeout) { return -1; } int commGetData(int channel, char *buffer, int buflen) { if(channel<0||channel>=N_BUFFERS) return -1; if(channel==COMM_FFTCH) { if(psk31fft->has_new_data()) { int len; psk31fft->get_parameters(&len, NULL, NULL); if(buflen<(int)(len*sizeof(float))) { fprintf(stderr,"no space in buffer for FFT data\n"); return 0; } psk31fft->get_abs_data((float *)buffer, 0, len, 1); #if 0 for(int i=0; i<1024; i++) fprintf(stderr,"%d ",((float *)buffer)[i]); #endif return len; } else return 0; } else { return read_buffer(channel, buffer, buflen); } } int commPutData(char *buffer, int buflen) { if(buflen<=0) buflen=strlen(buffer); pthread_mutex_lock(&mutex_tx); for(int i=0; isend_char((unsigned char)(buffer[i])); } pthread_mutex_unlock(&mutex_tx); return buflen; } static int txControl(int spec, int value) { int retval=0; static int mq=0, ml=0, mcw=0; pthread_mutex_lock(&mutex_tx); switch(spec) { case COMM_PTT: // if(transmit == (value&PTTON)) break; // nothing to do! switch(value) { case PTTON: // schedule TX start on DCD off schedule_transmit=1; break; case PTTON|PTTFORCE: // schedule TX start now schedule_transmit=2; break; case PTTOFF: // schedule TX end! psk31tx->send_char(TX_END); break; case PTTOFF|PTTFORCE: // shall we just clear the internal buffer and send // a postamble before aborting? should be the right // thing to do! TX_END|TX_URGENT does this job! // However, this does not allow "instant" abort // for emergency cases when something really goes // wrong... (TODO: clarify this matter...) psk31tx->send_char(TX_END|TX_URGENT); #if 0 transmit=0; ctl_ptt(0); #endif break; } break; case COMM_QPSK: mq=value; psk31tx->send_char(TX_MODE|(value?TXM_QPSK:0)|(ml?TXM_LSB:0)); break; case COMM_LSB: ml=value; psk31tx->send_char(TX_MODE|(mq?TXM_QPSK:0)|(value?TXM_LSB:0)); break; case COMM_MODE: //fprintf(stderr,"mode: value=%d\n",value); if(value==MO_TUNE) { psk31tx->send_char(TX_MODE|TXM_TUNE); mcw = 2; } else if(value==MO_CWSEND) { psk31tx->send_char(TX_MODE|TXM_CW); mcw = 1; } else { psk31tx->send_char( TX_MODE | (mq?TXM_QPSK:0) | (ml?TXM_LSB:0) ); mcw = 0; } break; case COMM_FREQ: psk31tx->send_char( TX_FREQ|(0xFFFFF&value) ); break; default: retval=-1; } pthread_mutex_unlock(&mutex_tx); return retval; } static int rxControl(int channel, int spec, int value) { int retval=0; int mqpsk, mlsb, mafc, musedcd, mdcd; float mfreq; int lastdelta, strength; psk31_receiver *rx = buffers[channel].psk31rx; if(rx==NULL) { if(spec==COMM_MODE && value==MO_NORMAL) { // start new decoder object #if 0 fprintf(stderr,"info: starting new decoder object" "on channel %d\n",channel); #endif buffers[channel].psk31rx = new psk31_receiver(NULL); return 0; } fprintf(stderr,"warning: rxControl on disabled RX channel\n" "channel=%d spec=%d value=%d\n",channel,spec,value); return -1; } pthread_mutex_lock(&mutex_rx); rx->get_info(&mqpsk, &mlsb, &mfreq, &musedcd, &mafc, &mdcd, &lastdelta, &strength); switch(spec) { case COMM_SWAP: /* exchange psk31_receiver objects of current channel and * channel */ // TODO: keep IQ FFT data reference on COMM_RXCH!! // (currently not used, so not a big issue) if(value=N_BUFFERS) { retval=-1; break; } { psk31_receiver *rx2 = buffers[value].psk31rx; buffers[channel].psk31rx = rx2; buffers[value].psk31rx = rx; if(value==COMM_RXCH) { rx->set_fft(psk31fft); rx2->set_fft(NULL); } else if(channel==COMM_RXCH) { rx2->set_fft(psk31fft); rx->set_fft(NULL); } } break; case COMM_DCD: rx->set_dcd(value); break; case COMM_DCDLEVEL: dcdlevel = value; rx->set_dcdlevel(value); break; case COMM_QPSK: rx->set_mode(value, mlsb); break; case COMM_LSB: rx->set_mode(mqpsk, value); break; case COMM_AFC: rx->set_afc(value); break; case COMM_MODE: if(value==MO_DISABLED) { if(channel==COMM_RXCH) { // do not delete master RX channel. won't work // as base FFT depends on this master channel retval=-1; break; } #if 0 fprintf(stderr,"info: deleting decoder object" "on channel %d\n",channel); #endif delete rx; buffers[channel].psk31rx=NULL; buffers[channel].rpos = buffers[channel].wpos = 0; } else { fprintf(stderr,"warning: active channel %d: " "COMM_MODE %d\n",channel,value); } break; case COMM_FREQ: rx->set_freq(0.01*value); break; default: retval=-1; } pthread_mutex_unlock(&mutex_rx); return retval; } int fftControl(int spec, int value) { int len, overlap, mode; int retval=0; pthread_mutex_lock(&mutex_rx); psk31fft->get_parameters(&len, &overlap, &mode); switch(spec) { case COMM_FFTN: len = value; psk31fft->set_parameters(len, overlap, mode); break; case COMM_FFTOVERLAP: overlap = value; psk31fft->set_parameters(len, overlap, mode); break; default: retval=-1; } pthread_mutex_unlock(&mutex_rx); return retval; } int commControl(int channel, int spec, int value) { if(channel==255) { // transmit channel return txControl(spec, value); } else if(channel==0) { // echo channel return 0; // no control here! } else if(channel==1) { // fft channel return fftControl(spec, value); } else if(channel>=COMM_RXCH&&channelptt = transmit; pthread_mutex_lock(&mutex_tx); psk31tx->get_info(&info->qpsk, &info->lsb, &info->cw, &freq); info->freq = (int)(100*freq); pthread_mutex_unlock(&mutex_tx); } else if(channel==COMM_ECHOCH) { // echo channel return -1; // no info on echo channel } else if(channel==COMM_FFTCH) { // fft channel return -1; // no info in fft channel (TODO) } else if(channel>=COMM_RXCH&&channelptt = transmit; pthread_mutex_lock(&mutex_rx); psk31_receiver *rx = buffers[channel].psk31rx; if(rx==NULL) { // TODO: different return value for invalid channel // vs disabled channel retval=-1; } else { rx->get_info(&info->qpsk, &info->lsb, &freq, &info->dcdlevel, &info->afc, &info->dcd, &info->phdelta, &info->strength ); info->freq = (int)(100*freq); } pthread_mutex_unlock(&mutex_rx); return retval; } else { return -1; // invalid channel } return 0; // or what should we return here? }