From f3d2dbb4dd482d8f26f0a0f3c609f18425e7a52b Mon Sep 17 00:00:00 2001 From: lwvmobile <59371473+lwvmobile@users.noreply.github.com> Date: Thu, 22 Sep 2022 20:46:47 -0400 Subject: [PATCH] Call Alert; Call History Bug Fix; Minor Tweaks Call Alert (-a or toggle on/off in ncurses) --beep sound on radio id change when using ncurses terminal (tied to call history array) ---need to copy tone8.wav and tone24.wav files to /usr/share/ directory and chmod 777 them. ---updated all auto install scripts to copy and chmod as appropriate ----iz4tow, need to update windows tutorials for the wav files, else they won't play Tweaks/Bug Fix to Call History (specifically DMR) to only set src ids on voice sync, and not on data sync Manually Set P2 Parameters at CLI (-X BEE00ABC123) -- use -X (capital X) and then enter WACN/SYSID/CC altogether --- feature already available in ncurses terminal menu Update Linux Install Instructions and auto installers to copy/paste wav files into /usr/share/ directory Other Minor Tweaks --- README.md | 14 +++ download-and-install-nodeps.sh | 4 + download-and-install.sh | 4 + include/dsd.h | 3 +- install.sh | 4 + rebuild.sh | 4 + src/dmr_sync.c | 26 ++++++ src/dsd_main.c | 41 +++++++-- src/dsd_ncurses.c | 161 ++++++++++++++++++++++++++------- tone24.wav | Bin 0 -> 9058 bytes tone8.wav | Bin 0 -> 4044 bytes 11 files changed, 221 insertions(+), 40 deletions(-) create mode 100644 tone24.wav create mode 100644 tone8.wav diff --git a/README.md b/README.md index 13004b4..7d8f76e 100644 --- a/README.md +++ b/README.md @@ -186,6 +186,10 @@ git branch -a git checkout remotes/origin/pulseaudio git checkout -b pulseaudio git branch -a #double check to see if you are on pulseaudio branch +sudo cp tone8.wav /usr/share/ +sudo cp tone24.wav /usr/share/ +sudo chmod 777 /usr/share/tone8.wav +sudo chmod 777 /usr/share/tone24.wav mkdir build cd build cmake .. @@ -216,6 +220,16 @@ make -j `nproc` sudo make install sudo ldconfig ``` + +If the call alert wav files aren't playing, then make sure to run the following in the dsd-fme folder to copy the wav files to the /usr/share/ folder and give them adequate permission to be accessed. + +``` +sudo cp tone8.wav /usr/share/ +sudo cp tone24.wav /usr/share/ +sudo chmod 777 /usr/share/tone8.wav +sudo chmod 777 /usr/share/tone24.wav +``` + # Join the Conversation Want to help drive the direction of this project and read more about the latest updates and releases to DSD-FME? Then join the conversation on the 'unofficial official' [DSD-FME](https://forums.radioreference.com/threads/dsd-fme.438137/ "DSD-FME") Forum Thread on the Radio Reference Forums. diff --git a/download-and-install-nodeps.sh b/download-and-install-nodeps.sh index 157716d..38c7f1e 100644 --- a/download-and-install-nodeps.sh +++ b/download-and-install-nodeps.sh @@ -13,6 +13,10 @@ git branch -a git checkout remotes/origin/pulseaudio git checkout -b pulseaudio git branch -a #double check to see if you are on pulseaudio branch +sudo cp tone8.wav /usr/share/ +sudo cp tone24.wav /usr/share/ +sudo chmod 777 /usr/share/tone8.wav +sudo chmod 777 /usr/share/tone24.wav mkdir build cd build cmake .. diff --git a/download-and-install.sh b/download-and-install.sh index 397f7c7..cd3ec51 100644 --- a/download-and-install.sh +++ b/download-and-install.sh @@ -49,6 +49,10 @@ git branch -a git checkout remotes/origin/pulseaudio git checkout -b pulseaudio git branch -a #double check to see if you are on pulseaudio branch +sudo cp tone8.wav /usr/share/ +sudo cp tone24.wav /usr/share/ +sudo chmod 777 /usr/share/tone8.wav +sudo chmod 777 /usr/share/tone24.wav mkdir build cd build cmake .. diff --git a/include/dsd.h b/include/dsd.h index f7ddea9..cff7da8 100644 --- a/include/dsd.h +++ b/include/dsd.h @@ -356,6 +356,7 @@ typedef struct short int aggressive_framesync; FILE *symbolfile; + int call_alert; } dsd_opts; @@ -593,7 +594,7 @@ typedef struct int p2_is_lcch; //flag to tell us when a frame is lcch and not sacch //experimental symbol file capture read throttle - unsigned long long int symbol_throttle; //throttle speed + int symbol_throttle; //throttle speed int use_throttle; //only use throttle if set to 1 //dstar header for ncurses diff --git a/install.sh b/install.sh index 3a167ba..e0ea7a2 100644 --- a/install.sh +++ b/install.sh @@ -1,3 +1,7 @@ +sudo cp tone8.wav /usr/share/ +sudo cp tone24.wav /usr/share/ +sudo chmod 777 /usr/share/tone8.wav +sudo chmod 777 /usr/share/tone24.wav mkdir build cd build cmake .. diff --git a/rebuild.sh b/rebuild.sh index 2a332d1..8586dd4 100644 --- a/rebuild.sh +++ b/rebuild.sh @@ -7,6 +7,10 @@ sleep 1 ##Open your clone folder## git pull https://github.com/lwvmobile/dsd-fme pulseaudio sleep 2 +sudo cp tone8.wav /usr/share/ +sudo cp tone24.wav /usr/share/ +sudo chmod 777 /usr/share/tone8.wav +sudo chmod 777 /usr/share/tone24.wav ##cd into your build folder## cd build ##cmake usually isn't necesary, but could be if I update the cmakelist.txt diff --git a/src/dmr_sync.c b/src/dmr_sync.c index e170893..c890a4c 100644 --- a/src/dmr_sync.c +++ b/src/dmr_sync.c @@ -1757,6 +1757,32 @@ void ProcessCSBK(dsd_opts * opts, dsd_state * state, uint8_t info[196], uint8_t sprintf(state->dmr_branding, " Hytera XPT "); } + //Testing CSBK Voice Call on Tait //CSBK Protect RID ILLEGALLY_PARKED + // if ((IrrecoverableErrors == 0) && CRCCorrect) + // { + // if (DmrDataByte[0] == 0xAF) + // { + // if (state->currentslot == 0) + // { + // state->lastsrc = (DmrDataByte[4] << 16) | (DmrDataByte[5] << 8) | (DmrDataByte[6] << 0); + // state->lasttg = (DmrDataByte[7] << 16) | (DmrDataByte[8] << 8) | (DmrDataByte[9] << 0); + // fprintf (stderr, "%s", KGRN); + // fprintf (stderr, "\n SLOT %d ", state->currentslot+1); + // fprintf(stderr, "TGT=%u SRC=%u ", state->lasttg, state->lastsrc); + // fprintf(stderr, " CSBK Voice LC??"); + // } + // if (state->currentslot == 1) + // { + // state->lastsrcR = (DmrDataByte[4] << 16) | (DmrDataByte[5] << 8) | (DmrDataByte[6] << 0); + // state->lasttgR = (DmrDataByte[7] << 16) | (DmrDataByte[8] << 8) | (DmrDataByte[9] << 0); + // fprintf (stderr, "%s", KGRN); + // fprintf (stderr, "\n SLOT %d ", state->currentslot+1); + // fprintf(stderr, "TGT=%u SRC=%u ", state->lasttgR, state->lastsrcR); + // fprintf(stderr, " CSBK Voice LC??"); + // } + // } + // } + //Full if (opts->payload == 1) { diff --git a/src/dsd_main.c b/src/dsd_main.c index 761511f..ac1bfe7 100644 --- a/src/dsd_main.c +++ b/src/dsd_main.c @@ -69,6 +69,7 @@ comp (const void *a, const void *b) //struct for checking existence of directory to write to struct stat st = {0}; char wav_file_directory[1024] = {0}; +unsigned long long int p2vars = 0; void noCarrier (dsd_opts * opts, dsd_state * state) @@ -320,6 +321,8 @@ initOpts (dsd_opts * opts) opts->inverted_p2 = 0; opts->p2counter = 0; + opts->call_alert = 0; //call alert beeper for ncurses + } void @@ -403,6 +406,8 @@ initState (dsd_state * state) state->tgcount = 0; state->lasttg = 0; state->lastsrc = 0; + state->lasttgR = 0; + state->lastsrcR = 0; state->nac = 0; state->errs = 0; state->errs2 = 0; @@ -614,7 +619,7 @@ usage () printf (" -z Frame rate for datascope\n"); printf ("\n"); printf ("Input/Output options:\n"); - printf (" -i Audio input device (default is pulse audio, \n - for piped stdin, rtl for rtl device)\n"); + printf (" -i Audio input device (default is pulse audio), \n - for piped stdin, rtl for rtl device,\n filename.bin for OP25/FME capture bin files\n"); printf (" -o Audio output device (default is pulse audio)\n"); printf (" -d Create mbe data files, use this directory\n"); printf (" -r Read/Play saved mbe data from file(s)\n"); @@ -623,7 +628,9 @@ usage () printf (" -T Enable Per Call WAV file saving in XDMA and NXDN decoding classes\n"); printf (" (Per Call can only be used in Ncurses Terminal!)\n"); printf (" (Running in console will use static wav files)\n"); - printf (" -n Throttle Symbol Capture Bin Input"); + printf (" -a Enable Call Alert Beep (NCurses Terminal Only)\n"); + printf (" (Warning! Might be annoying.)"); + printf (" -n Throttle Symbol Capture Bin Input\n"); printf (" (useful when reading files still being written to by OP25)"); printf ("\n"); printf ("RTL-SDR options:\n"); @@ -662,11 +669,13 @@ usage () printf (" * denotes frame types that cannot be auto-detected.\n"); printf ("\n"); printf ("Advanced Decoder options:\n"); + printf (" -X Manually Set P2 Parameters (WACN, SYSID, CC/NAC)\n"); + printf (" (-X BEE00ABC123)\n"); // printf (" -A QPSK modulation auto detection threshold (default=26)\n"); - printf (" -S Symbol buffer size for QPSK decision point tracking\n"); - printf (" (default=36)\n"); - printf (" -M Min/Max buffer size for QPSK decision point tracking\n"); - printf (" (default=15)\n"); + // printf (" -S Symbol buffer size for QPSK decision point tracking\n"); + // printf (" (default=36)\n"); + // printf (" -M Min/Max buffer size for QPSK decision point tracking\n"); + // printf (" (default=15)\n"); // printf (" -F Enable DMR TDMA Stereo Passive Frame Sync\n"); // printf (" This feature will attempt to resync less often due to excessive voice errors\n"); // printf (" Use if skipping occurs, but may cause wonky audio due to loss of good sync\n"); @@ -918,7 +927,7 @@ main (int argc, char **argv) exitflag = 0; signal (SIGINT, sigfun); - while ((c = getopt (argc, argv, "haep:P:qstv:z:i:o:d:c:g:nw:B:C:R:f:m:u:x:A:S:M:G:D:L:V:U:Y:K:H:NQWrlZTF")) != -1) + while ((c = getopt (argc, argv, "haep:P:qstv:z:i:o:d:c:g:nw:B:C:R:f:m:u:x:A:S:M:G:D:L:V:U:Y:K:H:X:NQWrlZTF")) != -1) { opterr = 0; switch (c) @@ -928,7 +937,8 @@ main (int argc, char **argv) exit (0); case 'a': //printPortAudioDevices(); - exit(0); + //exit(0); + opts.call_alert = 1; case 'e': opts.errorbars = 1; opts.datascope = 0; @@ -1030,6 +1040,21 @@ main (int argc, char **argv) state.K1 = state.H; //shim break; + //manually set Phase 2 TDMA WACN/SYSID/CC + case 'X': + sscanf (optarg, "%llX", &p2vars); + if (p2vars > 0) + { + state.p2_wacn = p2vars >> 24; + state.p2_sysid = (p2vars >> 12) & 0xFFF; + state.p2_cc = p2vars & 0xFFF; + } + if (state.p2_wacn != 0 && state.p2_sysid != 0 && state.p2_cc != 0) + { + state.p2_hardset = 1; + } + break; + case 'G': //Set rtl device gain sscanf (optarg, "%d", &opts.rtl_gain_value); //multiple value by ten to make it consitent with the way rtl_fm really works break; diff --git a/src/dsd_ncurses.c b/src/dsd_ncurses.c index 8f39607..325ff45 100644 --- a/src/dsd_ncurses.c +++ b/src/dsd_ncurses.c @@ -157,6 +157,61 @@ char * DMRBusrtTypes[32] = { }; +void beeper (dsd_opts * opts, dsd_state * state, int type) +{ + FILE *beep; + char wav_name[1024] = {0}; + if (opts->dmr_stereo == 1) + { + //24k tone wav file + strncpy(wav_name, "/usr/share/tone24.wav", 1023); + } + else + { + //8k tone + strncpy(wav_name, "/usr/share/tone8.wav", 1023); + } + wav_name[1023] = '\0'; + struct stat stat_buf; + if (stat(wav_name, &stat_buf) == 0) + { + beep = fopen (wav_name, "ro"); + uint8_t buf[1024] = {0}; + short blip = 0; + int loop = 1; + while (loop == 1) + { + fread(buf, sizeof(buf), 1, beep); + if ( feof (beep) ) + { + loop = 0; + } + if (loop == 1) + { + //only beep on R if dmr_stereo is active and slot 2, else beep on L + if (type == 0 && state->dmr_stereo == 1) + { + pa_simple_write(opts->pulse_digi_dev_out, buf, sizeof(buf), NULL); + } + if (type == 1 && state->dmr_stereo == 1) + { + pa_simple_write(opts->pulse_digi_dev_outR, buf, sizeof(buf), NULL); + } + if (state->dmr_stereo == 0) + { + pa_simple_write(opts->pulse_digi_dev_out, buf, sizeof(buf), NULL); + } + + + } + + } + + fclose (beep); + } + +} + char * getDateN(void) { char datename[99]; //bug in 32-bit Ubuntu when using date in filename, date is garbage text char * curr2; @@ -209,7 +264,7 @@ char *choicesc[] = { "Replay Last Symbol Capture Bin", "Stop & Close Symbol Capture Bin Playback", "Stop & Close Symbol Capture Bin Saving", - " ", + "Toggle Call Alert Beep ", "Resume Decoding" }; @@ -621,6 +676,40 @@ void ncursesMenu (dsd_opts * opts, dsd_state * state) } + if (choicec == 7) //RTL UDP Retune + { + //read in new rtl frequency + #ifdef USE_RTLSDR + entry_win = newwin(6, WIDTH+18, starty+10, startx+10); + box (entry_win, 0, 0); + mvwprintw(entry_win, 2, 2, " Enter Frequency in Hz (851.8 MHz is 851800000 Hz) "); + mvwprintw(entry_win, 3, 3, " "); + echo(); + refresh(); + wscanw(entry_win, "%d", &opts->rtlsdr_center_freq); //ld, or lld? + noecho(); + //do the thing with the thing + data[0] = 0; + data[1] = opts->rtlsdr_center_freq & 0xFF; + data[2] = (opts->rtlsdr_center_freq >> 8) & 0xFF; + data[3] = (opts->rtlsdr_center_freq >> 16) & 0xFF; + data[4] = (opts->rtlsdr_center_freq >> 24) & 0xFF; + + temp_freq = opts->rtlsdr_center_freq; + #endif + choicec = 18; + } + + if (choicec == 8) + { + //vacant box + } + + if (choicec == 9) + { + //vacant box + } + if (choicec == 10) { if (opts->ncurses_compact == 0) @@ -728,28 +817,14 @@ void ncursesMenu (dsd_opts * opts, dsd_state * state) choicec = 18; //exit } - if (choicec == 7) //RTL UDP Retune + //toggle call alert beep + if (choicec == 17) { - //read in new rtl frequency - #ifdef USE_RTLSDR - entry_win = newwin(6, WIDTH+18, starty+10, startx+10); - box (entry_win, 0, 0); - mvwprintw(entry_win, 2, 2, " Enter Frequency in Hz (851.8 MHz is 851800000 Hz) "); - mvwprintw(entry_win, 3, 3, " "); - echo(); - refresh(); - wscanw(entry_win, "%d", &opts->rtlsdr_center_freq); //ld, or lld? - noecho(); - //do the thing with the thing - data[0] = 0; - data[1] = opts->rtlsdr_center_freq & 0xFF; - data[2] = (opts->rtlsdr_center_freq >> 8) & 0xFF; - data[3] = (opts->rtlsdr_center_freq >> 16) & 0xFF; - data[4] = (opts->rtlsdr_center_freq >> 24) & 0xFF; - - temp_freq = opts->rtlsdr_center_freq; - #endif - choicec = 18; + if (opts->call_alert == 0) + { + opts->call_alert = 1; + } + else opts->call_alert = 0; } if (choicec != 0 && choicec != 18 ) //return to last menu @@ -765,6 +840,7 @@ void ncursesMenu (dsd_opts * opts, dsd_state * state) //wrefresh(menu_win); break; } + if (choicec == 18) //exit both menus { //exit @@ -1495,7 +1571,8 @@ ncursesPrinter (dsd_opts * opts, dsd_state * state) } //DMR SRC - if ( (lls == 12 || lls == 13 || lls == 10 || lls == 11 || lls == 32) ) + // if ( (lls == 12 || lls == 13 || lls == 10 || lls == 11 || lls == 32) ) + if ( (lls == 12 || lls == 11 || lls == 32) ) { if (state->dmrburstL == 16 && state->lastsrc > 0) //state->currentslot == 0 && { @@ -1506,17 +1583,15 @@ ncursesPrinter (dsd_opts * opts, dsd_state * state) { rdR = state->lastsrcR; } - //move to seperate P25 version plz - //opts->p25enc = 0; + } //DMR TG - if ( (lls == 12 || lls == 13 || lls == 10 || lls == 11 || lls == 32) ) + if ( (lls == 12 || lls == 11 || lls == 32) ) { if (state->dmrburstL == 16 && state->lasttg > 0) //state->currentslot == 0 && { tg = state->lasttg; - } if (state->dmrburstR == 16 && state->lasttgR > 0) //state->currentslot == 1 && @@ -1524,7 +1599,6 @@ ncursesPrinter (dsd_opts * opts, dsd_state * state) tgR = state->lasttgR; } - //opts->p25enc = 0; } @@ -1629,10 +1703,15 @@ ncursesPrinter (dsd_opts * opts, dsd_state * state) openWavOutFileL (opts, state); //testing for now, will want to move to per call later } + if (opts->call_alert == 1) + { + beeper (opts, state, 0); + } + } //DMR MS - if ( call_matrix[9][2] != rd && (lls == 32 || lls == 33 || lls == 34) ) + if ( call_matrix[9][2] != rd && lls == 32) { for (short int k = 0; k < 10; k++) @@ -1661,10 +1740,15 @@ ncursesPrinter (dsd_opts * opts, dsd_state * state) openWavOutFileL (opts, state); //testing for now, will want to move to per call later } + if (opts->call_alert == 1) + { + beeper (opts, state, 0); + } + } //DMR BS Slot 1 - matrix 0-4 - if ( call_matrix[4][2] != rd && (lls == 12 || lls == 13 || lls == 10 || lls == 11 || lls == 35 || lls == 36) ) + if ( call_matrix[4][2] != rd && (lls == 11 || lls == 12 || lls == 35 || lls == 36) ) { for (short int k = 0; k < 4; k++) @@ -1694,10 +1778,15 @@ ncursesPrinter (dsd_opts * opts, dsd_state * state) openWavOutFileL (opts, state); //testing for now, will want to move to per call later } + if (opts->call_alert == 1) + { + beeper (opts, state, 0); + } + } //DMR BS Slot 2 - matrix 5-9 - if ( call_matrix[9][2] != rdR && (lls == 12 || lls == 13 || lls == 10 || lls == 11 || lls == 35 || lls == 36) ) + if ( call_matrix[9][2] != rdR && (lls == 11 || lls == 12 || lls == 35 || lls == 36) ) { for (short int k = 5; k < 9; k++) @@ -1726,6 +1815,11 @@ ncursesPrinter (dsd_opts * opts, dsd_state * state) openWavOutFileR (opts, state); //testing for now, will want to move to per call later } + if (opts->call_alert == 1) + { + beeper (opts, state, 1); + } + } //P25 P1 @@ -1757,6 +1851,11 @@ ncursesPrinter (dsd_opts * opts, dsd_state * state) openWavOutFileL (opts, state); //testing for now, will want to move to per call later } + if (opts->call_alert == 1) + { + beeper (opts, state, 0); + } + } //Start Printing Section diff --git a/tone24.wav b/tone24.wav new file mode 100644 index 0000000000000000000000000000000000000000..bac507830c09b08e17bde93ac93c857409aba9ac GIT binary patch literal 9058 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 TN6_Cj;Gh1-@5dzc