/* psk31lx: PSK31/soundcard for linux * Copyright (C) 1998-2000 Hansi Reiser, dl9rdz (dl9rdz@amsat.org) * * term-psk31.C * text-base terminal for the PSK31 core * Version: 0.7 of 22 Feb 2000 * * * 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. * */ #include #include #include #include #include #include #include #include #include #include #include #include //TJW #include #include "text-window.h" #include "../server/server.h" #define DATADIR "/usr/local/share/psk31" static char *version_string="PSK31/Soundcard for Linux Version V0.7"; // 'Window' management static window *rxwin, *txwin; window *statwin; PSK31info rxinfo, txinfo; // Current settings... static char callsign[40] = "NOCALL"; /* User's callsign */ static int txtrack = 0; /* Tx frequency follows rx frequency flag */ static float rxfreq = 1150, txfreq = 1150; /* Rx & Tx tone frequencies (Hz) */ static int transmit=0, qpsk=0, lsb=0, dcd=0, passall=0, afc=1, phdelta=0; static int io_file=0; static int tuning=0; int OPTnew=0; static char macro[10][40]; void out_status(int force); /* Use this variable to remember original terminal attributes. */ struct termios saved_attributes; void reset_input_mode (void) { tcsetattr (STDIN_FILENO, TCSANOW, &saved_attributes); } void set_input_mode (void) { struct termios tattr; /* Make sure stdin is a terminal. */ if (!isatty (STDIN_FILENO)) { fprintf (stderr, "Not a terminal.\n"); exit (EXIT_FAILURE); } /* Save the terminal attributes so we can restore them later. */ tcgetattr (STDIN_FILENO, &saved_attributes); atexit (reset_input_mode); /* Set the funny terminal modes. */ tcgetattr (STDIN_FILENO, &tattr); tattr.c_lflag &= ~(ICANON|ECHO); /* Clear ICANON and ECHO. */ tattr.c_cc[VMIN] = 1; tattr.c_cc[VTIME] = 0; tcsetattr (STDIN_FILENO, TCSAFLUSH, &tattr); } void set_tx_freq(float freq) { txfreq=freq; commControl(COMM_TXCH, COMM_FREQ, (int)(txfreq*100)); } void set_rx_freq(float freq) { rxfreq=freq; commControl(COMM_RXCH, COMM_FREQ, (int)(rxfreq*100)); if( txtrack ) set_tx_freq(rxfreq); } void change_freq(float offset) { set_rx_freq(rxfreq+offset); } void setmode(int q, int l, int a, int d) { qpsk=q; lsb=l; dcd=d; afc=a; commControl(COMM_TXCH, COMM_QPSK, qpsk); commControl(COMM_RXCH, COMM_QPSK, qpsk); commControl(COMM_TXCH, COMM_LSB, lsb); commControl(COMM_RXCH, COMM_LSB, lsb); commControl(COMM_RXCH, COMM_AFC, afc); commControl(COMM_RXCH, COMM_DCD, dcd); } static void readconfig(void) { int cfgerr=0; FILE *fp=(FILE *)0; char cfgfile[1024], buf[256], *ptr; float f; ptr=getenv("HOME"); if(ptr) { strncpy(cfgfile, ptr, 1000); strcat(cfgfile, "/.psk31"); fp = fopen(cfgfile, "r"); } if(!fp && ptr) { strncpy(cfgfile, ptr, 1000); strcat(cfgfile, "/psk31.ini"); fp = fopen(cfgfile, "r"); } if(!fp) fp = fopen("psk31.ini", "r"); if(!fp) { strncpy(cfgfile, DATADIR, 1000); if(cfgfile[strlen(cfgfile)-1]!='/') strcat(cfgfile,"/"); strcat(cfgfile, "psk31.ini"); fp = fopen(cfgfile, "r"); } if(!fp) { fprintf(stderr, "Configuration file psk31.ini or ~/.psk31 " "not found"); exit(1); } while(fgets(buf, 40, fp) != (char *)0) { if(sscanf(buf, "CALL=\"%[^\"]", callsign) == 1) /**/; else if(sscanf(buf, "FREQ=%f", &f) == 1) txfreq = rxfreq = f; else if(sscanf(buf, "LSB=%d", &lsb) == 1) /**/; else if(sscanf(buf, "PASSALL=%d", &passall) == 1) /**/; else if(sscanf(buf, "TXTRACK=%d", &txtrack) == 1) /**/; else if(sscanf(buf, "MACRO1=\"%[^\"]", macro[0]) == 1 ) /**/; else if(sscanf(buf, "MACRO2=\"%[^\"]", macro[1]) == 1 ) /**/; else if(sscanf(buf, "MACRO3=\"%[^\"]", macro[2]) == 1 ) /**/; else if(sscanf(buf, "MACRO4=\"%[^\"]", macro[3]) == 1 ) /**/; else if(sscanf(buf, "MACRO5=\"%[^\"]", macro[4]) == 1 ) /**/; else if(sscanf(buf, "MACRO6=\"%[^\"]", macro[5]) == 1 ) /**/; else if(sscanf(buf, "MACRO7=\"%[^\"]", macro[6]) == 1 ) /**/; else if(sscanf(buf, "MACRO8=\"%[^\"]", macro[7]) == 1 ) /**/; else if(sscanf(buf, "MACRO9=\"%[^\"]", macro[8]) == 1 ) /**/; else { fprintf(stderr,"Illegal line in config file%s:\n%s", cfgfile, buf); cfgerr=1; } } fclose(fp); if(cfgerr) exit(1); } void usage_exit(char *argv0) { fprintf(stderr, "%s\n\n" "Usage: %s [-a audiodevice] [-q [-l]] [-t] [-x]\n" " -q: Enable QPSK mode\n" " -l: Invert phase shift for LSB mode\n" " -t: Start with transmit mode on (debug only)\n" " -x: TX freq folloes RX freq\n" " -i: io to file (debug only)\n" "\n", version_string, argv0); exit(1); } void out_tuning(int); #define STATUSLINE 18 void out_status(int force) { static int oq,ol,odcd,opa,ot,otr,oaf; static float orx,otx; char buf[128]; out_tuning(force); if( orx!=rxfreq||otx!=txfreq||oq!=qpsk||ol!=lsb||odcd!=dcd ||opa!=passall||ot!=transmit||otr!=txtrack||oaf!=afc) force=1; if(!force) return; sprintf(buf," ····· %4s%4s rx=%6.1f(AFC:%3s) tx=%6.1f(NET:%3s)" " DCD:%3s %7s %2s %c%c", qpsk?"QPSK":"BPSK", lsb?"/LSB":" ", rxfreq, afc?"on ":"off", txfreq, txtrack?"on ":"off", dcd?"on ":"off", passall?"PASSALL":" ", transmit?"TX":"RX", OPTnew&1?'F':'-', OPTnew&2?'S':'-'); orx=rxfreq; otx=txfreq; oq=qpsk; ol=lsb; odcd=dcd; ot=transmit; otr=txtrack; opa=passall; oaf=afc; window::outputline(STATUSLINE,buf); } void out_tuning(int force) { static int oldp=-1; int p; if(oldp==-1||force) { window::putsyx(STATUSLINE+0,0," ····· "); window::putsyx(STATUSLINE+1,0," · · "); window::putsyx(STATUSLINE+2,0,"· ·"); window::putsyx(STATUSLINE+3,0," · · "); window::putsyx(STATUSLINE+4,0," ····· "); } // phdelta: 0..255 // Position: 14 15 0 1 2 ...usw int pos[][2]={ {0,4}, {0,5}, {0,6}, {1,7}, {1,7}, {2,8}, {3,7}, {3,7},{4,6},{4,5}, {4,4}, {4,3}, {4,2}, {3,1}, {3,1}, {2,0}, {1,1}, {1,1},{0,2},{0,3} }; p=(int)((phdelta/256.0)*20+0.5); // 0..19 if(p>19) p=0; if(p!=oldp || force) window::putsyx(STATUSLINE+pos[p][0], pos[p][1], "O"); if(p!=oldp && oldp!=-1) { window::putsyx(STATUSLINE+pos[oldp][0], pos[oldp][1], "·"); oldp=p; } } void out_macroinfo() { char buf[128]; sprintf(buf," F1: %-25s F2: %-25s\n", macro[0], macro[1]); window::putsyx(STATUSLINE+1,10,buf); sprintf(buf," F3: %-25s F4: %-25s\n", macro[2], macro[3]); window::putsyx(STATUSLINE+2,10,buf); sprintf(buf," F5: %-25s F6: %-25s\n", macro[4], macro[5]); window::putsyx(STATUSLINE+3,10,buf); sprintf(buf," F7: %-25s F8: %-25s\n", macro[6], macro[7]); window::putsyx(STATUSLINE+4,10,buf); sprintf(buf," F9: %-25s F10:%-25s\n", macro[8], macro[9]); window::putsyx(STATUSLINE+5,10,buf); } /* If we are not transmitting: * - turn on PTT, transmit CW data, turn off PTT * if PTT is already on: * - switch mode to CW, transmit CW data * - if pttoff==0: leave PTT enabled, switch back to PSK31 mode * - if pttoff==1: turn of PTT after finishing transmisson */ void cwsend(char *s, int pttoff) { if(tuning) tuning=0; commControl(COMM_TXCH, COMM_MODE, MO_CWSEND); if(!transmit) { pttoff=1; commControl(COMM_TXCH, COMM_PTT, PTTON|PTTFORCE); } commPutData(s, 0); if(pttoff) { fprintf(stderr,"turning off PTT!\n"); commControl(COMM_TXCH, COMM_PTT, PTTOFF); } commControl(COMM_TXCH, COMM_MODE, MO_NORMAL); } void toggle_tune() { if(tuning) { commControl(COMM_TXCH, COMM_PTT, PTTOFF); commControl(COMM_TXCH, COMM_MODE, MO_NORMAL); tuning = 0; } else { commControl(COMM_TXCH, COMM_MODE, MO_TUNE ); if(!transmit) commControl(COMM_TXCH, COMM_PTT, PTTON|PTTFORCE); tuning = 1; } } int main(int argc, char **argv) { int res, c, exitfl=0; fd_set rset, wset, eset; struct timeval tm; char *audio_path="/dev/audio"; char *mixer_path="/dev/mixer"; char *ptt_path = NULL; srandom(time((time_t *)0)); readconfig(); /* parse command line arguments */ while( (c=getopt(argc, argv, "FSa:m:t:n:iqlxdT:?"))!=-1 ) { switch(c) { case 'a': audio_path = strdup(optarg); break; case 'm': mixer_path = strdup(optarg); break; case 't': ptt_path = strdup(optarg); break; case 'F': OPTnew|=1; // neuer filter break; case 'S': OPTnew|=2; // neue sync. break; case 'i': io_file=1; break; case 'x': txtrack=1; break; case 'q': qpsk=1; break; case 'l': lsb=1; break; case 'T': transmit=1; break; case '?': usage_exit(argv[0]); break; default: fprintf(stderr,"illegal option: %c\n",c); usage_exit(argv[0]); break; } } if( server_main(audio_path, ptt_path, DATADIR ) ) { exit(1); } /* init FFT */ /* fftsr_init(); */ /* init screen */ window::init_window(); atexit(window::end_window); window::outputline(0,"--------------------------------------------------------------------------------"); txwin=new window(1, 5, 80, 1); window::outputline(6,"--------------------------------------------------------------------------------"); rxwin=new window(7, 10, 80, 0); window::outputline(17,"--------------------------------------------------------------------------------"); statwin=new window(19, 5, 80, 0); out_status(1); out_macroinfo(); setmode(qpsk,lsb,afc,dcd); set_rx_freq(rxfreq); set_tx_freq(txfreq); if(io_file) { char *buf="TEST:[]{}$%TEST:" "\370\371\372\373\374\375\376\377 " "Test: öäü ÖÄÜ ß... öäüÖÄÜ test"; commControl(COMM_TXCH, COMM_PTT, PTTON|PTTFORCE); commPutData(buf, 0); commControl(COMM_TXCH, COMM_PTT, PTTOFF); } /* go into main loop */ while(!exitfl) { fflush(stdout); FD_ZERO(&rset); FD_ZERO(&wset); FD_ZERO(&eset); FD_SET(STDIN_FILENO, &rset); tm.tv_sec=0; tm.tv_usec=50000; /* 50ms */ res=select(17, &rset, &wset, &eset, &tm); if(FD_ISSET(STDIN_FILENO, &rset)) { /* handle keyboard input */ c=getch(); switch(c) { case 0: case 3: case 5: case 6: case 15: case 17: case 19: case 22: case 25: case 26: case 28: case 29: case 30: case 31: c=0; /* ignore */ break; case KEY_F(9): case KEY_F(1): case KEY_F(2): case KEY_F(3): case KEY_F(4): case KEY_F(5): case KEY_F(6): case KEY_F(7): case KEY_F(8): { char *txt = macro[c-KEY_F0-1]; commPutData( txt, 0 ); txwin->put_string( txt ); c=0; break; } case 27: /* ESC -> Exit */ /* exitfl=1;*/ c=0; break; case 24: /* ^X -> command mode */ c=0; break; case 1: /* ^A -> afc on/off */ setmode(qpsk,lsb,!afc,dcd); c=0; break; case 11: /* ^K -> txtrack on/off */ txtrack=!txtrack; out_status(0); c=0; break; case 2: /* ^B -> QPSK <> BPSK */ setmode(!qpsk,lsb,afc,dcd); c=0; break; case 16: /* ^P -> PassAll */ passall=!passall; out_status(0); c=0; break; case 4: /* ^D -> DCD on/off */ setmode(qpsk,lsb,afc,!dcd); c=0; break; case 23: /* ^W -> CWID */ char buffer[128]; sprintf(buffer," de %s",callsign); cwsend(buffer, 1); c=0; break; case 14: /* ^N -> toggle OPTnew (new filter, bitsync)*/ OPTnew++; if(OPTnew>=4) OPTnew=0; out_status(1); c=0; break; case 12: /* ^L -> LSB <> USB */ setmode(qpsk, !lsb, afc, dcd); c=0; break; case 21: /* ^U -> Tune on/off */ toggle_tune(); c=0; break; case KEY_UP: change_freq(1); c=0; break; case KEY_DOWN: change_freq(-1); c=0; break; case KEY_PPAGE: change_freq(8); c=0; break; case KEY_NPAGE: change_freq(-8); c=0; break; case 8: case 127: case KEY_BACKSPACE: c='\b'; break; /* Backspace */ case 7: case 9: case 10: case 13: break; /* ok (BEL,TAB,NL,CR) */ case 18: /* ^R */ if(tuning) toggle_tune(); else commControl(COMM_TXCH, COMM_PTT, PTTOFF); c=0; break; case 20: /* ^T */ if(tuning) { commControl(COMM_TXCH, COMM_MODE, MO_NORMAL); tuning = 0; } else { commControl(COMM_TXCH, COMM_PTT, PTTON|PTTFORCE); } c=0; break; } if(c) { char ch = (char)c; commPutData(&ch, 1); } if(c>0&&c<256) txwin->put_char(c); } commGetInfo(COMM_TXCH, &txinfo, sizeof(txinfo)); txfreq = 0.01 * txinfo.freq; commGetInfo(COMM_RXCH, &rxinfo, sizeof(rxinfo)); rxfreq = 0.01 * rxinfo.freq; dcd = rxinfo.dcd; phdelta = rxinfo.phdelta; out_status(0); // handle echo! we could use a different color? char buf[256]; int l = commGetData(COMM_ECHOCH, buf, sizeof buf); if(l>0) { for(int i=0; iput_char(buf[i]); } // handle receive! l = commGetData(COMM_RXCH, buf, sizeof buf); if(l>0) { for(int i=0; iput_char(buf[i]); } } }