diff --git a/examples/group.csv b/examples/group.csv new file mode 100644 index 0000000..7dd2f1e --- /dev/null +++ b/examples/group.csv @@ -0,0 +1,5 @@ +DEC,Mode,Name of Group,Group Description,  +100,DE,Example Name,Description, +1449,A,Fire Dispatch,Fire, +929,A,Titusville Fire Tac,Fire, +22033,DE,Law Dispatch,Fire, diff --git a/examples/lcn.csv b/examples/lcn.csv new file mode 100644 index 0000000..4c2d846 --- /dev/null +++ b/examples/lcn.csv @@ -0,0 +1,2 @@ +lcn1,lcn2,lcn3,lcn4,lcn5,...lcn26 +851375000,851800000,855987500,858487500,852312500 diff --git a/include/dsd.h b/include/dsd.h index 35fedb9..28646c3 100644 --- a/include/dsd.h +++ b/include/dsd.h @@ -62,6 +62,14 @@ static volatile int exitflag; +//group csv import struct +typedef struct +{ + unsigned long int groupNumber; + char groupMode[8]; //char *? + char groupName[50]; +} groupinfo; + typedef struct { uint8_t RFChannelType; @@ -247,7 +255,7 @@ typedef struct int p25status; int p25tg; int scoperate; - char audio_in_dev[1024]; + char audio_in_dev[2048]; //increase size for super long directory/file names? int audio_in_fd; SNDFILE *audio_in_file; SF_INFO *audio_in_file_info; @@ -359,6 +367,37 @@ typedef struct FILE *symbolfile; int call_alert; + //rigctl opt + int rigctl_sockfd; + int use_rigctl; + int rigctlportno; + char * rigctlhostname; + + //udp socket for GQRX, SDR++, etc + int udp_sockfd; + int udp_portno; + char * udp_hostname; + SNDFILE *udp_file_in; + + //tcp socket for SDR++, etc + int tcp_sockfd; + int tcp_portno; + char * tcp_hostname; + SNDFILE *tcp_file_in; + + //wav file sample rate, interpolator and decimator + int wav_sample_rate; + int wav_interpolator; + int wav_decimator; + + int p25_trunk; //enable experimental P25 trunking with RIGCTL (or RTLFM) + int p25_is_tuned; //set to 1 if currently on VC, set back to 0 if on CC + + //csv import filenames + char group_in_file[1024]; + char lcn_in_file[1024]; + //end import filenames + } dsd_opts; typedef struct @@ -612,16 +651,40 @@ typedef struct //dstar header for ncurses unsigned char dstarradioheader[41]; -#ifdef TRACE_DSD - char debug_prefix; - char debug_prefix_2; - unsigned int debug_sample_index; - unsigned int debug_sample_left_edge; - unsigned int debug_sample_right_edge; - FILE* debug_label_file; - FILE* debug_label_dibit_file; - FILE* debug_label_imbe_file; -#endif + //dmr trunking stuff + int dmr_rest_channel; + int dmr_mfid; //just when 'fid' is used as a manufacturer ID and not a feature set id + int dmr_vc_lcn; + int dmr_vc_lsn; + int dmr_tuned_lcn; + + //edacs + int ea_mode; + int esk_mode; + unsigned short esk_mask; + unsigned long long int edacs_site_id; + int edacs_lcn_count; //running tally of lcn's observed on edacs system + int edacs_cc_lcn; //current lcn for the edacs control channel + int edacs_vc_lcn; //current lcn for any active vc (not the one we are tuned/tuning to) + int edacs_tuned_lcn; //the vc we are currently tuned to...above variable is for updating all in the matrix + + //trunking group and lcn freq list + long int trunk_lcn_freq[26]; //max number on an EDACS system, should be enough on DMR too hopefully + groupinfo group_array[0x3FF]; //max supported by Cygwin is 3FFF (signed vs unsigned?) + unsigned int group_tally; //tally number of groups imported from CSV file for referencing later + int lcn_freq_count; + int lcn_freq_roll; //number we have 'rolled' to in search of the CC + time_t last_cc_sync_time; //use this to start hunting for CC after signal lost + + //new nxdn stuff + int nxdn_part_of_frame; + int nxdn_ran; + int nxdn_sf; + bool nxdn_sacch_non_superframe; //flag to indicate whether or not a sacch is a part of a superframe, or an individual piece + uint8_t nxdn_sacch_frame_segment[4][18]; //part of frame by 18 bits + uint8_t nxdn_sacch_frame_segcrc[4]; + uint8_t nxdn_alias_block_number; + char nxdn_alias_block_segment[4][4][8]; //might be too large? maybe just an 8? } dsd_state; @@ -662,8 +725,8 @@ typedef struct #define DMR_MS_DATA_SYNC "311131133313133331131113" #define DMR_MS_VOICE_SYNC "133313311131311113313331" -//Testing NXDN FSW again -#define NXDN_FSW "3131331131" +#define NXDN_FSW "3131331131" +#define INV_NXDN_FSW "1313113313" #define DMR_RC_DATA_SYNC "131331111133133133311313" @@ -677,6 +740,9 @@ typedef struct #define INV_PROVOICE_EA_SYNC "13313133113113333311313133133311" #define PROVOICE_EA_SYNC "31131311331331111133131311311133" +#define EDACS_SYNC "313131313131313131313111333133133131313131313131" +#define INV_EDACS_SYNC "131313131313131313131333111311311313131313131313" + #define DPMR_FRAME_SYNC_1 "111333331133131131111313" #define DPMR_FRAME_SYNC_2 "113333131331" #define DPMR_FRAME_SYNC_3 "133131333311" @@ -926,6 +992,36 @@ void process_FACCH_MAC_PDU (dsd_opts * opts, dsd_state * state, int payload[156] //P25 Channel to Frequency long int process_channel_to_freq (dsd_opts * opts, dsd_state * state, int channel); +//rigctl functions and TCP/UDP functions +void error(char *msg); +int Connect (char *hostname, int portno); +bool Send(int sockfd, char *buf); +bool Recv(int sockfd, char *buf); + +//rtl_fm udp tuning function +void rtl_udp_tune(dsd_opts * opts, dsd_state * state, long int frequency); + +bool GetCurrentFreq(int sockfd, long int freq); +bool SetFreq(int sockfd, long int freq); +bool SetModulation(int sockfd, int bandwidth); +//commands below unique to GQRX only, not usable on SDR++ +bool GetSignalLevel(int sockfd, double *dBFS); +bool GetSquelchLevel(int sockfd, double *dBFS); +bool SetSquelchLevel(int sockfd, double dBFS); +bool GetSignalLevelEx(int sockfd, double *dBFS, int n_samp); +//end gqrx-scanner + +//UDP socket connection +int UDPBind (char *hostname, int portno); + +//Edacs +void edacs(dsd_opts * opts, dsd_state * state); +unsigned long long int edacs_bch (unsigned long long int message); + +//csv imports +int csvGroupImport(dsd_opts * opts, dsd_state * state); +int csvLCNImport(dsd_opts * opts, dsd_state * state); + #ifdef __cplusplus extern "C" { #endif diff --git a/src/dsd_audio.c b/src/dsd_audio.c index 25ada7a..98b8523 100644 --- a/src/dsd_audio.c +++ b/src/dsd_audio.c @@ -545,31 +545,59 @@ openAudioOutDevice (dsd_opts * opts, int speed) void openAudioInDevice (dsd_opts * opts) { - // get info of device/file - if(strncmp(opts->audio_in_dev, "-", 1) == 0) - { - opts->audio_in_type = 1; - opts->audio_in_file_info = calloc(1, sizeof(SF_INFO)); - opts->audio_in_file_info->samplerate=48000; - opts->pulse_digi_rate_out = 8000; //set out rate to 8000 for stdin input - opts->audio_in_file_info->channels=1; - opts->audio_in_file_info->seekable=0; - opts->audio_in_file_info->format=SF_FORMAT_RAW|SF_FORMAT_PCM_16|SF_ENDIAN_LITTLE; - opts->audio_in_file = sf_open_fd(fileno(stdin), SFM_READ, opts->audio_in_file_info, 0); + char * extension; + const char ch = '.'; + extension = strrchr(opts->audio_in_dev, ch); //return extension if this is a .wav or .bin file - if(opts->audio_in_file == NULL) { - fprintf (stderr,"Error, couldn't open stdin with libsndfile: %s\n", sf_strerror(NULL)); - exit(1); //had this one disabled, re-enabling it now - } - } - //converted to handle any calls to use portaudio - else if(strncmp(opts->audio_in_dev, "pa:", 2) == 0) + // get info of device/file + if (strncmp(opts->audio_in_dev, "-", 1) == 0) { - opts->audio_in_type = 0; - fprintf (stderr,"Error, Port Audio is not supported by FME!\n"); - fprintf (stderr,"Using Pulse Audio Input Stream Instead! \n"); - sprintf (opts->audio_in_dev, "pulse"); + opts->audio_in_type = 1; + opts->audio_in_file_info = calloc(1, sizeof(SF_INFO)); + opts->audio_in_file_info->samplerate=opts->wav_sample_rate; + opts->audio_in_file_info->channels=1; + opts->audio_in_file_info->seekable=0; + opts->audio_in_file_info->format=SF_FORMAT_RAW|SF_FORMAT_PCM_16|SF_ENDIAN_LITTLE; + opts->audio_in_file = sf_open_fd(fileno(stdin), SFM_READ, opts->audio_in_file_info, 0); + + if(opts->audio_in_file == NULL) + { + fprintf(stderr, "Error, couldn't open stdin with libsndfile: %s\n", sf_strerror(NULL)); + exit(1); + } } + else if (strncmp(opts->audio_in_dev, "tcp", 3) == 0) + { + opts->audio_in_type = 8; + opts->audio_in_file_info = calloc(1, sizeof(SF_INFO)); + opts->audio_in_file_info->samplerate=opts->wav_sample_rate; + opts->audio_in_file_info->channels=1; + opts->audio_in_file_info->seekable=0; + opts->audio_in_file_info->format=SF_FORMAT_RAW|SF_FORMAT_PCM_16|SF_ENDIAN_LITTLE; + opts->tcp_file_in = sf_open_fd(opts->tcp_sockfd, SFM_READ, opts->audio_in_file_info, 0); + + if(opts->tcp_file_in == NULL) + { + fprintf(stderr, "Error, couldn't open TCP with libsndfile: %s\n", sf_strerror(NULL)); + exit(1); + } + } + else if (strncmp(opts->audio_in_dev, "udp", 3) == 0) + { + opts->audio_in_type = 6; + opts->audio_in_file_info = calloc(1, sizeof(SF_INFO)); + opts->audio_in_file_info->samplerate=opts->wav_sample_rate; + opts->audio_in_file_info->channels=1; + opts->audio_in_file_info->seekable=0; + opts->audio_in_file_info->format=SF_FORMAT_RAW|SF_FORMAT_PCM_16|SF_ENDIAN_LITTLE; + opts->udp_file_in = sf_open_fd(opts->udp_sockfd, SFM_READ, opts->audio_in_file_info, 0); + + if(opts->udp_file_in == NULL) + { + fprintf(stderr, "Error, couldn't open UDP with libsndfile: %s\n", sf_strerror(NULL)); + exit(1); + } + } else if(strncmp(opts->audio_in_dev, "rtl", 3) == 0) { opts->audio_in_type = 3; @@ -578,41 +606,58 @@ openAudioInDevice (dsd_opts * opts) { opts->audio_in_type = 0; } - //convert from opening wav files to OP25 symbol capture files since opening wav files is busted + else if (strncmp(extension, ".bin", 3) == 0) + { + struct stat stat_buf; + if (stat(opts->audio_in_dev, &stat_buf) != 0) + { + fprintf (stderr,"Error, couldn't open bin file %s\n", opts->audio_in_dev); + exit(1); + } + if (S_ISREG(stat_buf.st_mode)) + { + opts->symbolfile = fopen(opts->audio_in_dev, "r"); + opts->audio_in_type = 4; //symbol capture bin files + } + else + { + opts->audio_in_type = 0; + } + } + //open as wav file as last resort, wav files subseptible to sample rate issues if not 48000 else { struct stat stat_buf; if (stat(opts->audio_in_dev, &stat_buf) != 0) { - fprintf (stderr,"Error, couldn't open %s\n", opts->audio_in_dev); + fprintf (stderr,"Error, couldn't open wav file %s\n", opts->audio_in_dev); exit(1); } if (S_ISREG(stat_buf.st_mode)) { - // is this a regular file? then process with libsndfile. - //opts->pulse_digi_rate_out = 8000; //this for wav files input? - // opts->audio_in_type = 1; - // opts->audio_in_file_info = calloc(1, sizeof(SF_INFO)); - // opts->audio_in_file_info->channels = 1; - // opts->audio_in_file = sf_open(opts->audio_in_dev, SFM_READ, opts->audio_in_file_info); + opts->audio_in_type = 2; //two now, seperating STDIN and wav files + opts->audio_in_file_info = calloc(1, sizeof(SF_INFO)); + opts->audio_in_file_info->samplerate=opts->wav_sample_rate; + opts->audio_in_file_info->channels=1; + opts->audio_in_file_info->channels = 1; + opts->audio_in_file_info->seekable=0; + opts->audio_in_file_info->format=SF_FORMAT_RAW|SF_FORMAT_PCM_16|SF_ENDIAN_LITTLE; // - // if(opts->audio_in_file == NULL) - // { - // fprintf (stderr,"Error, couldn't open file %s\n", opts->audio_in_dev); - // exit(1); - // } + opts->audio_in_file = sf_open(opts->audio_in_dev, SFM_READ, opts->audio_in_file_info); + + if(opts->audio_in_file == NULL) + { + fprintf(stderr, "Error, couldn't open wav file %s\n", opts->audio_in_dev); + exit(1); + } - opts->symbolfile = fopen(opts->audio_in_dev, "r"); - opts->audio_in_type = 4; //symbol capture bin files } - else + //open pulse audio if no bin or wav + else { - // this is a device, use old handling, pulse audio now - opts->audio_in_type = 0; - + opts->audio_in_type = 0; //not sure if this works or needs to openPulse here } } - if (opts->split == 1) { fprintf (stderr,"Audio In Device: %s\n", opts->audio_in_dev); diff --git a/src/dsd_dibit.c b/src/dsd_dibit.c index bdb419a..24db757 100644 --- a/src/dsd_dibit.c +++ b/src/dsd_dibit.c @@ -221,12 +221,13 @@ static int digitize (dsd_opts* opts, dsd_state* state, int symbol) //int digitize (dsd_opts* opts, dsd_state* state, int symbol) { // determine dibit state - if ((state->synctype == 6) || (state->synctype == 14)|| (state->synctype == 18)) + if ((state->synctype == 6) || (state->synctype == 14)|| (state->synctype == 18) || (state->synctype == 37)) { // 6 +D-STAR // 14 +ProVoice // 18 +D-STAR_HD + // 37 +EDACS if (symbol > state->center) { @@ -241,11 +242,12 @@ static int digitize (dsd_opts* opts, dsd_state* state, int symbol) return (1); // +3 } } - else if ((state->synctype == 7) || (state->synctype == 15)|| (state->synctype == 19)) + else if ((state->synctype == 7) || (state->synctype == 15)|| (state->synctype == 19) || (state->synctype == 38)) { // 7 -D-STAR // 15 -ProVoice // 19 -D-STAR_HD + // 38 -EDACS if (symbol > state->center) { diff --git a/src/dsd_frame.c b/src/dsd_frame.c index fc1a61f..0cc55c7 100644 --- a/src/dsd_frame.c +++ b/src/dsd_frame.c @@ -287,9 +287,9 @@ processFrame (dsd_opts * opts, dsd_state * state) } else if ((state->synctype == 14) || (state->synctype == 15)) { - state->nac = 0; - state->lastsrc = 0; - state->lasttg = 0; + //state->nac = 0; + //state->lastsrc = 0; + //state->lasttg = 0; if (opts->errorbars == 1) { if (opts->verbose > 0) @@ -306,6 +306,12 @@ processFrame (dsd_opts * opts, dsd_state * state) processProVoice (opts, state); return; } + //edacs + else if ((state->synctype == 37) || (state->synctype == 38)) + { + edacs (opts, state); + return; + } //ysf else if ((state->synctype == 30) || (state->synctype == 31)) { diff --git a/src/dsd_frame_sync.c b/src/dsd_frame_sync.c index 95f9561..dc877fe 100644 --- a/src/dsd_frame_sync.c +++ b/src/dsd_frame_sync.c @@ -146,8 +146,44 @@ getFrameSync (dsd_opts * opts, dsd_state * state) * 34 = DMR RC Data * 35 = +P25 P2 * 36 = -P25 P2 + * 37 = +EDACS + * 38 = -EDACS */ + //start control channel hunting if using trunking, time needs updating on each successful sync + //will need to assign frequencies to a CC array for P25 since that isn't imported from CSV + if (opts->p25_is_tuned == 0 && opts->p25_trunk == 1 && time(NULL) - state->last_cc_sync_time > 3) + { + //start going through the lcn/frequencies CC/signal hunting + fprintf (stderr, "Control Channel Signal Lost. Searching for Control Channel.\n"); + //make sure our current roll value doesn't exceed value of frequencies imported + if (state->lcn_freq_roll > state->lcn_freq_count) + { + state->lcn_freq_roll = 0; //reset to zero + } + //check that we have a non zero value first, then tune next frequency + if (state->trunk_lcn_freq[state->lcn_freq_roll] != 0) + { + //do condition here, in future, will allow us to use tuning methods as well, or rtl_udp as well + if (opts->use_rigctl == 1) + { + SetModulation(opts->rigctl_sockfd, 12500); //may not use this here, not sure yet + SetFreq(opts->rigctl_sockfd, state->trunk_lcn_freq[state->lcn_freq_roll]); + } + + if (opts->audio_in_type == 3) + { + rtl_udp_tune (opts, state, state->trunk_lcn_freq[state->lcn_freq_roll]); + } + + fprintf (stderr, "Tuning to Control Channel Frequency: %.06lf MHz\n", + (double)state->trunk_lcn_freq[state->lcn_freq_roll]/1000000); + + } + state->lcn_freq_roll++; + state->last_cc_sync_time = time(NULL); //set again to give another x seconds + } + int i, j, t, o, dibit, sync, symbol, synctest_pos, lastt; char synctest[25]; @@ -156,6 +192,7 @@ getFrameSync (dsd_opts * opts, dsd_state * state) char synctest18[19]; char synctest32[33]; char synctest20[21]; //YSF + char synctest48[49]; //EDACS char modulation[8]; char *synctest_p; char synctest_buf[10240]; @@ -177,6 +214,7 @@ getFrameSync (dsd_opts * opts, dsd_state * state) synctest18[18] = 0; synctest32[32] = 0; synctest20[20] = 0; + synctest48[48] = 0; synctest_pos = 0; synctest_p = synctest_buf + 10; sync = 0; @@ -326,7 +364,7 @@ getFrameSync (dsd_opts * opts, dsd_state * state) // end digitize and dmr buffer testing *synctest_p = dibit; - if (t >= 18) + if (t >= 10) //t >= 18 { for (i = 0; i < 24; i++) { @@ -475,6 +513,7 @@ getFrameSync (dsd_opts * opts, dsd_state * state) printFrameSync (opts, state, "+P25p1 ", synctest_pos + 1, modulation); } state->lastsynctype = 0; + state->last_cc_sync_time = time(NULL); return (0); } if (strcmp (synctest, INV_P25P1_SYNC) == 0) @@ -491,6 +530,7 @@ getFrameSync (dsd_opts * opts, dsd_state * state) printFrameSync (opts, state, "-P25p1 ", synctest_pos + 1, modulation); } state->lastsynctype = 1; + state->last_cc_sync_time = time(NULL); return (1); } } @@ -652,6 +692,7 @@ getFrameSync (dsd_opts * opts, dsd_state * state) { pa_simple_flush(opts->pulse_raw_dev_out, NULL); } + state->last_cc_sync_time = time(NULL); return (35); //35 } } @@ -695,6 +736,7 @@ getFrameSync (dsd_opts * opts, dsd_state * state) { pa_simple_flush(opts->pulse_raw_dev_out, NULL); } + state->last_cc_sync_time = time(NULL); return (36); //36 } } @@ -1149,50 +1191,64 @@ getFrameSync (dsd_opts * opts, dsd_state * state) } //End if (opts->frame_dmr == 1) //end DMR Sync + + //ProVoice and EDACS sync if (opts->frame_provoice == 1) + { + strncpy (synctest32, (synctest_p - 31), 32); + strncpy (synctest48, (synctest_p - 47), 48); + if ((strcmp (synctest32, PROVOICE_SYNC) == 0) || (strcmp (synctest32, PROVOICE_EA_SYNC) == 0)) { - strncpy (synctest32, (synctest_p - 31), 32); - if ((strcmp (synctest32, PROVOICE_SYNC) == 0) || (strcmp (synctest32, PROVOICE_EA_SYNC) == 0)) - { - now = time(NULL); - state->carrier = 1; - state->offset = synctest_pos; - state->max = ((state->max) + lmax) / 2; - state->min = ((state->min) + lmin) / 2; - sprintf (state->ftype, "ProVoice "); - if (opts->errorbars == 1) - //if (opts->errorbars == 1 && (time(NULL) - now) > 2 ) - { - printFrameSync (opts, state, "-ProVoice ", synctest_pos + 1, modulation); - } - state->lastsynctype = 14; - if ( opts->monitor_input_audio == 1) - { + state->last_cc_sync_time = time(NULL); + state->carrier = 1; + state->offset = synctest_pos; + state->max = ((state->max) + lmax) / 2; + state->min = ((state->min) + lmin) / 2; + sprintf (state->ftype, "ProVoice "); + if (opts->errorbars == 1) + printFrameSync (opts, state, "+PV ", synctest_pos + 1, modulation); + state->lastsynctype = 14; + state->last_cc_sync_time = time(NULL); + return (14); + } + else if ((strcmp (synctest32, INV_PROVOICE_SYNC) == 0) || (strcmp (synctest32, INV_PROVOICE_EA_SYNC) == 0)) + { + state->last_cc_sync_time = time(NULL); + state->carrier = 1; + state->offset = synctest_pos; + state->max = ((state->max) + lmax) / 2; + state->min = ((state->min) + lmin) / 2; + sprintf (state->ftype, "ProVoice "); + printFrameSync (opts, state, "-PV ", synctest_pos + 1, modulation); + state->lastsynctype = 15; + state->last_cc_sync_time = time(NULL); + return (15); + } - pa_simple_flush(opts->pulse_raw_dev_out, NULL); - } - return (14); - } - else if ((strcmp (synctest32, INV_PROVOICE_SYNC) == 0) || (strcmp (synctest32, INV_PROVOICE_EA_SYNC) == 0)) - { - now = time(NULL); - state->carrier = 1; - state->offset = synctest_pos; - state->max = ((state->max) + lmax) / 2; - state->min = ((state->min) + lmin) / 2; - sprintf (state->ftype, "ProVoice "); - if (opts->errorbars == 1) - { - printFrameSync (opts, state, "-ProVoice ", synctest_pos + 1, modulation); - } - state->lastsynctype = 15; - if ( opts->monitor_input_audio == 1) - { - - pa_simple_flush(opts->pulse_raw_dev_out, NULL); - } - return (15); - } + else if ( strcmp (synctest48, EDACS_SYNC) == 0) + { + state->last_cc_sync_time = time(NULL); + state->carrier = 1; + state->offset = synctest_pos; + state->max = ((state->max) + lmax) / 2; + state->min = ((state->min) + lmin) / 2; + printFrameSync (opts, state, "-EDACS", synctest_pos + 1, modulation); + state->lastsynctype = 38; + state->last_cc_sync_time = time(NULL); + return (38); + } + else if ( strcmp (synctest48, INV_EDACS_SYNC) == 0) + { + state->last_cc_sync_time = time(NULL); + state->carrier = 1; + state->offset = synctest_pos; + state->max = ((state->max) + lmax) / 2; + state->min = ((state->min) + lmin) / 2; + printFrameSync (opts, state, "+EDACS", synctest_pos + 1, modulation); + state->lastsynctype = 37; + state->last_cc_sync_time = time(NULL); + return (37); + } } diff --git a/src/dsd_import.c b/src/dsd_import.c new file mode 100644 index 0000000..694d797 --- /dev/null +++ b/src/dsd_import.c @@ -0,0 +1,96 @@ + + +#include "dsd.h" +#define BSIZE 999 + +int csvGroupImport(dsd_opts * opts, dsd_state * state) +{ + char filename[1024] = "filename.csv"; + sprintf (filename, "%s", opts->group_in_file); + //filename[1023] = '\0'; //necessary? + char buffer[BSIZE]; + FILE * fp; + fp = fopen(filename, "r"); + if (fp == NULL) { + printf("Unable to open file '%s'\n", filename); + exit(1); + } + int row_count = 0; + int field_count = 0; + long int group_number = 0; //local group number for array index value + int i = 0; + while (fgets(buffer, BSIZE, fp)) { + field_count = 0; + row_count++; + if (row_count == 1) + continue; //don't want labels + char * field = strtok(buffer, ","); //seperate by comma + while (field) { + + if (field_count == 0) + { + //group_number = atol(field); + state->group_array[i].groupNumber = atol(field); + fprintf (stderr, "%ld, ", state->group_array[i].groupNumber); + } + if (field_count == 1) + { + strcpy(state->group_array[i].groupMode, field); + fprintf (stderr, "%s, ", state->group_array[i].groupMode); + } + if (field_count == 2) + { + strcpy(state->group_array[i].groupName, field); + fprintf (stderr, "%s ", state->group_array[i].groupName); + } + + field = strtok(NULL, ","); + field_count++; + } + fprintf (stderr, "\n"); + i++; + state->group_tally++; + } + fclose(fp); + return 0; +} + +int csvLCNImport(dsd_opts * opts, dsd_state * state) //LCN/LSN import for EDACS/DMR/NXDN? +{ + char filename[1024] = "filename.csv"; + sprintf (filename, "%s", opts->lcn_in_file); + //filename[1023] = '\0'; //necessary? + char buffer[BSIZE]; + FILE * fp; + fp = fopen(filename, "r"); + if (fp == NULL) { + printf("Unable to open file '%s'\n", filename); + //have this return -1 and handle it inside of main + exit(1); + } + int row_count = 0; + int field_count = 0; + + while (fgets(buffer, BSIZE, fp)) { + field_count = 0; + row_count++; + if (row_count == 1) + continue; //don't want labels + char * field = strtok(buffer, ","); //seperate by comma + while (field) { + + state->trunk_lcn_freq[field_count] = atol (field); + state->lcn_freq_count++; //keep tally of number of Frequencies imported + fprintf (stderr, "LCN [%d] [%ld]", field_count+1, state->trunk_lcn_freq[field_count]); + fprintf (stderr, "\n"); + + + field = strtok(NULL, ","); + field_count++; + } + fprintf (stderr, "LCN Count %d\n", state->lcn_freq_count); + + } + fclose(fp); + return 0; +} diff --git a/src/dsd_main.c b/src/dsd_main.c index c7bae21..b7b74a5 100644 --- a/src/dsd_main.c +++ b/src/dsd_main.c @@ -76,6 +76,43 @@ char * pEnd; //bugfix void noCarrier (dsd_opts * opts, dsd_state * state) { + + //tune back to last knwon CC when using trunking + if (opts->p25_trunk == 1 && opts->p25_is_tuned == 1) + { + if (state->p25_cc_freq != 0) //still need a con+ way to set this accurately, or atleast guess, maybe shim p25_cc_freq to lcn index 0? + { + //maybe I should make a seperate tuning function that can handle multiple tuning types + if (opts->use_rigctl == 1) //rigctl tuning + { + SetModulation(opts->rigctl_sockfd, 12500); //could use the bw field on iden_up for P25 to determine a good bw, but honestly it really depends on signal stregth + SetFreq(opts->rigctl_sockfd, state->p25_cc_freq); + } + + else if (opts->audio_in_type == 3) //rtl_fm tuning + { + //UDP command to tune the RTL dongle + rtl_udp_tune(opts, state, state->p25_cc_freq); + } + + opts->p25_is_tuned = 0; + state->edacs_tuned_lcn = -1; + } + //only for EDACS right now, disable this later on or work around + if (opts->wav_out_file != NULL) + { + closeWavOutFile(opts, state); + } + state->last_cc_sync_time = time(NULL); + //test to revert back to 10/4 P1 QPSK + if (opts->mod_qpsk == 1) + { + state->samplesPerSymbol = 10; + state->symbolCenter = 4; + } + + } + state->dibit_buf_p = state->dibit_buf + 200; memset (state->dibit_buf, 0, sizeof (int) * 200); //dmr buffer @@ -212,6 +249,16 @@ noCarrier (dsd_opts * opts, dsd_state * state) state->p25_vc_freq[0] = 0; state->p25_vc_freq[1] = 0; + //new nxdn stuff + state->nxdn_part_of_frame = 0; + state->nxdn_ran = 0; + state->nxdn_sf = 0; + memset (state->nxdn_sacch_frame_segcrc, 1, sizeof(state->nxdn_sacch_frame_segcrc)); //init on 1, bad CRC all + state->nxdn_sacch_non_superframe = TRUE; + memset (state->nxdn_sacch_frame_segment, 0, sizeof(state->nxdn_sacch_frame_segment)); + state->nxdn_alias_block_number = 0; + memset (state->nxdn_alias_block_segment, 0, sizeof(state->nxdn_alias_block_segment)); + } void @@ -304,6 +351,10 @@ initOpts (dsd_opts * opts) opts->pulse_digi_in_channels = 1; //2 opts->pulse_digi_out_channels = 2; //new default for XDMA + opts->wav_sample_rate = 48000; //default value (DSDPlus uses 96000 on raw signal wav files) + opts->wav_interpolator = 1; //default factor of 1 on 48000; 2 on 96000; sample rate / decimator + opts->wav_decimator = 48000; //maybe for future use? + sprintf (opts->output_name, "XDMA"); opts->pulse_flush = 1; //set 0 to flush, 1 for flushed opts->use_ncurses_terminal = 0; @@ -329,6 +380,25 @@ initOpts (dsd_opts * opts) opts->call_alert = 0; //call alert beeper for ncurses + //rigctl options + opts->use_rigctl = 0; + opts->rigctl_sockfd = 0; + opts->rigctlportno = 7356; //TCP Port Number; GQRX - 7356; SDR++ - 4532 + opts->rigctlhostname = "localhost"; + + //udp input options + opts->udp_sockfd = 0; + opts->udp_portno = 7355; //default favored by GQRX and SDR++ + opts->udp_hostname = "localhost"; + + //tcp input options + opts->tcp_sockfd = 0; + opts->tcp_portno = 7355; //default favored by SDR++ + opts->tcp_hostname = "localhost"; + + opts->p25_trunk = 0; //0 disabled, 1 is enabled + opts->p25_is_tuned = 0; //set to 1 if currently on VC, set back to 0 if on CC + } void @@ -600,17 +670,47 @@ initState (dsd_state * state) state->p25_chan_spac[i] = 0; state->p25_base_freq[i] = 0; } + //values displayed in ncurses terminal state->p25_cc_freq = 0; state->p25_vc_freq[0] = 0; state->p25_vc_freq[1] = 0; -#ifdef TRACE_DSD - state->debug_sample_index = 0; - state->debug_label_file = NULL; - state->debug_label_dibit_file = NULL; - state->debug_label_imbe_file = NULL; -#endif + //edacs + state->ea_mode = -1; //init on -1, 0 is standard, 1 is ea + state->esk_mode = -1; //same as above, but with esk or not + state->esk_mask = 0x0; //toggles from 0x0 to 0xA0 if esk mode enabled + state->edacs_site_id = 0; + state->edacs_lcn_count = 0; + state->edacs_cc_lcn = 0; + state->edacs_vc_lcn = 0; + state->edacs_tuned_lcn = -1; + + //trunking + memset (state->trunk_lcn_freq, 0, sizeof(state->trunk_lcn_freq)); + state->group_tally = 0; + state->lcn_freq_count = 0; //number of frequncies imported from LCN + state->lcn_freq_roll = 0; //needs reset if sync is found? + state->last_cc_sync_time = time(NULL); + + //dmr trunking/ncurses stuff + state->dmr_rest_channel = -1; //init on -1 + state->dmr_mfid = -1; // + state->dmr_tuned_lcn = -1; //logical slot, lcn * ts? + state->dmr_vc_lcn = -1; // + state->dmr_vc_lsn = -1; + + //new nxdn stuff + state->nxdn_part_of_frame = 0; + state->nxdn_ran = 0; + state->nxdn_sf = 0; + memset (state->nxdn_sacch_frame_segcrc, 1, sizeof(state->nxdn_sacch_frame_segcrc)); //init on 1, bad CRC all + state->nxdn_sacch_non_superframe = TRUE; + memset (state->nxdn_sacch_frame_segment, 0, sizeof(state->nxdn_sacch_frame_segment)); + state->nxdn_alias_block_number = 0; + memset (state->nxdn_alias_block_segment, 0, sizeof(state->nxdn_alias_block_segment)); + + initialize_p25_heuristics(&state->p25_heuristics); } @@ -641,6 +741,11 @@ usage () printf ("Input/Output options:\n"); printf (" -i Audio input device (default is pulse audio)\n"); printf (" - for piped stdin, rtl for rtl device\n"); + printf (" tcp for tcp client SDR++/GNURadio Companion/Other (Port 7355)\n"); + printf (" filename.bin for OP25/FME capture bin files\n"); + printf (" filename.wav for 48K/1 wav files (SDR++, GQRX)\n"); + printf (" filename.wav -s 96000 for 96K/1 wav files (DSDPlus)\n"); + printf (" (Use single quotes '/directory/audio file.wav' when directories/spaces are present)\n"); printf (" filename.bin for OP25/FME capture bin files\n"); printf (" -o Audio output device (default is pulse audio)(null for no audio output)\n"); printf (" -d Create mbe data files, use this directory\n"); @@ -680,7 +785,7 @@ usage () printf (" -fx Decode only X2-TDMA\n"); printf (" -fi Decode only NXDN48* (6.25 kHz) / IDAS*\n"); printf (" -fn Decode only NXDN96* (12.5 kHz)\n"); - printf (" -fp Decode only ProVoice*\n"); + printf (" -fp Decode only EDACS/ProVoice*\n"); printf (" -fm Decode only dPMR*\n"); printf (" -l Disable DMR and NXDN input filtering\n"); printf (" -pu Unmute Encrypted P25\n"); @@ -725,6 +830,15 @@ usage () printf (" \n"); printf (" -4 Force Privacy Key over FID and SVC bits \n"); printf ("\n"); + printf (" Experimental Functions and Features---------------------------------------------------\n"); + printf (" -1 Import LCN Frequencies from csv file (numeral 'one') \n"); + printf (" (See lcn.csv for example)\n"); + printf (" -2 Import Group List Allow/Block and Label from csv file (numeral 'two')\n"); + printf (" (See group.csv for example)\n"); + printf (" -3 Enable Experimental Trunking Features (P25/EDACS for now) with RIGCTL/TCP or RTL Input\n"); + printf (" -5 Enable RIGCTL/TCP; Set UDP Port for RIGCTL. (4532 on SDR++)\n"); + //printf (" (Currently only available on UDP port 4532)\n"); + printf ("\n"); exit (0); } @@ -856,15 +970,6 @@ cleanupAndExit (dsd_opts * opts, dsd_state * state) closeMbeOutFile (opts, state); } -#ifdef USE_RTLSDR - if(opts->audio_in_type == 3) - { - // TODO: cleanup demod threads - //temp disable to see if this corrects issues with closing - //cleanup_rtlsdr_stream(); - } -#endif - fprintf (stderr,"\n"); fprintf (stderr,"Total audio errors: %i\n", state->debug_audio_errors); fprintf (stderr,"Total header errors: %i\n", state->debug_header_errors); @@ -963,7 +1068,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:X:NQWrlZTF4")) != -1) + while ((c = getopt (argc, argv, "haep:P:qs:tv: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:NQWrlZTF1:2:345:")) != -1) { opterr = 0; switch (c) @@ -975,6 +1080,30 @@ main (int argc, char **argv) //printPortAudioDevices(); //exit(0); opts.call_alert = 1; + //placeholder until letters get re-arranged (or opt_long switched in) + case '1': //LCN/Frequency CSV + strncpy(opts.lcn_in_file, optarg, 1023); + opts.lcn_in_file[1023] = '\0'; + csvLCNImport (&opts, &state); + break; + //placeholder until letters get re-arranged + case '2': //Group Label CSV + strncpy(opts.group_in_file, optarg, 1023); + opts.group_in_file[1023] = '\0'; + csvGroupImport(&opts, &state); + break; + //placeholder until letters get re-arranged + case '3': + opts.p25_trunk = 1; + break; + //placeholder until letters get re-arranged + case '5': //RIGCTL UDP port + sscanf (optarg, "%d", &opts.rigctlportno); + if (opts.rigctlportno != 0) + { + opts.use_rigctl = 1; + } + break; case 'e': opts.errorbars = 1; opts.datascope = 0; @@ -1011,13 +1140,18 @@ main (int argc, char **argv) opts.verbose = 0; break; case 's': - opts.errorbars = 0; - opts.p25enc = 0; - opts.p25lc = 0; - opts.p25status = 0; - opts.p25tg = 0; - opts.datascope = 1; - opts.symboltiming = 0; + // opts.errorbars = 0; + // opts.p25enc = 0; + // opts.p25lc = 0; + // opts.p25status = 0; + // opts.p25tg = 0; + // opts.datascope = 1; + // opts.symboltiming = 0; + sscanf (optarg, "%d", &opts.wav_sample_rate); + opts.wav_interpolator = opts.wav_sample_rate / opts.wav_decimator; + state.samplesPerSymbol = state.samplesPerSymbol * opts.wav_interpolator; + state.symbolCenter = state.symbolCenter * opts.wav_interpolator; + //fprintf (stderr, "Are we getting this far?\n"); break; case 't': opts.symboltiming = 1; @@ -1193,9 +1327,8 @@ main (int argc, char **argv) fprintf (stderr,"Setting datascope frame rate to %i frame per second.\n", opts.scoperate); break; case 'i': - strncpy(opts.audio_in_dev, optarg, 1023); - opts.audio_in_dev[1023] = '\0'; - //fprintf (stderr,"audio_in_dev = %s\n", opts.audio_in_dev); + strncpy(opts.audio_in_dev, optarg, 2047); + opts.audio_in_dev[2047] = '\0'; break; case 'o': strncpy(opts.audio_out_dev, optarg, 1023); @@ -1338,7 +1471,7 @@ main (int argc, char **argv) opts.pulse_digi_out_channels = 1; opts.dmr_stereo = 0; state.dmr_stereo = 0; - sprintf (opts.output_name, "ProVoice"); + sprintf (opts.output_name, "EDACS/PV"); fprintf (stderr,"Setting symbol rate to 9600 / second\n"); fprintf (stderr,"Decoding only ProVoice frames.\n"); } @@ -1693,6 +1826,31 @@ main (int argc, char **argv) openSerial (&opts, &state); } + //need error handling on opening rigctl so we don't exit or crash on disconnect + if (opts.use_rigctl == 1) + { + opts.rigctl_sockfd = Connect(opts.rigctlhostname, opts.rigctlportno); + long int initfreq = 0; + GetCurrentFreq(opts.rigctl_sockfd, initfreq); + } + + if((strncmp(opts.audio_in_dev, "tcp", 3) == 0)) //tcp socket input from SDR++ and others + { + //use same handling as connect function from rigctl + //also still needs err handling + opts.tcp_sockfd = Connect(opts.tcp_hostname, opts.tcp_portno); + opts.audio_in_type = 8; + } + + //still need to work out why I can't use this + //issues with samples received, may be using UDP DGRAMS incorrectly, incorrect procedure? + if((strncmp(opts.audio_in_dev, "udp", 3) == 0)) //udp socket input from SDR++, GQRX, and others + { + //also still needs err handling + opts.udp_sockfd = UDPBind(opts.udp_hostname, opts.udp_portno); + opts.audio_in_type = 6; + } + if((strncmp(opts.audio_in_dev, "pulse", 5) == 0)) { opts.audio_in_type = 0; diff --git a/src/dsd_ncurses.c b/src/dsd_ncurses.c index d7b7a56..8a17f9d 100644 --- a/src/dsd_ncurses.c +++ b/src/dsd_ncurses.c @@ -95,8 +95,8 @@ char * SyncTypes[44] = { "DMR ", "DMR ", "DMR ", - "PROVOICE", - "PROVOICE", + "EDACS/PV", + "EDACS/PV", "NXDN VOICE", //DATA "NXDN VOICE", //DATA "DSTAR HD", @@ -118,8 +118,8 @@ char * SyncTypes[44] = { "DMR RC DATA", "P25P2", "P25P2", - "FDMA", //37 - "TDMA", //38 + "EDACS/PV", //37 + "EDACS/PV", //38 "", "" }; @@ -416,6 +416,10 @@ void ncursesOpen (dsd_opts * opts, dsd_state * state) //ncursesMenu void ncursesMenu (dsd_opts * opts, dsd_state * state) { + + //update sync time on cc sync so we don't immediately go CC hunting when exiting the menu + state->last_cc_sync_time = time(NULL); + //close pulse output if not null output if (opts->audio_out == 1 && opts->audio_out_type == 0) { @@ -427,6 +431,16 @@ void ncursesMenu (dsd_opts * opts, dsd_state * state) closePulseInput(opts); } + if (opts->audio_in_type == 8) //close TCP input SF file so we don't buffer audio while not decoding + { + sf_close(opts->tcp_file_in); + } + + if (opts->audio_in_type == 5) //close UDP input SF file so we don't buffer audio while not decoding + { + sf_close(opts->udp_file_in); //disable for testing + } + state->payload_keyid = 0; state->payload_keyidR = 0; @@ -1089,7 +1103,7 @@ void ncursesMenu (dsd_opts * opts, dsd_state * state) resetState (state); //use sparingly, may cause memory leak state->samplesPerSymbol = 5; state->symbolCenter = 2; - sprintf (opts->output_name, "ProVoice"); + sprintf (opts->output_name, "EDACS/PV"); opts->dmr_stereo = 0; //this value is the end user option state->dmr_stereo = 0; //this values toggles on and off depending on voice or data handling opts->pulse_digi_rate_out = 8000; @@ -1550,6 +1564,18 @@ void ncursesMenu (dsd_opts * opts, dsd_state * state) } #endif + if (opts->audio_in_type == 8) //re-open TCP input 'file' + { + opts->tcp_file_in = sf_open_fd(opts->tcp_sockfd, SFM_READ, opts->audio_in_file_info, 0); + } + + if (opts->audio_in_type == 5) //re-open UDP input 'file' + { + opts->udp_file_in = sf_open_fd(opts->udp_sockfd, SFM_READ, opts->audio_in_file_info, 0); + } + + //update sync time on cc sync so we don't immediately go CC hunting when exiting the menu + state->last_cc_sync_time = time(NULL); } //end Ncurses Menu @@ -1592,7 +1618,7 @@ ncursesPrinter (dsd_opts * opts, dsd_state * state) } //set lls sync types - if (state->synctype >= 0 && state->synctype < 37) //not sure if this will be okay + if (state->synctype >= 0 && state->synctype < 39) { lls = state->synctype; } @@ -1715,25 +1741,19 @@ ncursesPrinter (dsd_opts * opts, dsd_state * state) } //Call History Matrix Shuffling - //ProVoice - if ( (lls == 14 || lls == 15) && (time(NULL) - call_matrix[9][5] > 5) && state->carrier == 1) + //Edacs - ProVoice + if ( (lls == 14 || lls == 15 || lls == 37 || lls == 38) && state->carrier == 1) { - for (short int k = 0; k < 9; k++) + + if (state->edacs_vc_lcn != -1) { - call_matrix[k][0] = call_matrix[k+1][0]; - call_matrix[k][1] = call_matrix[k+1][1]; - call_matrix[k][2] = call_matrix[k+1][2]; - call_matrix[k][3] = call_matrix[k+1][3]; - call_matrix[k][4] = call_matrix[k+1][4]; - call_matrix[k][5] = call_matrix[k+1][5]; - } - - call_matrix[9][0] = lls; - call_matrix[9][1] = 1; - call_matrix[9][2] = 1; - call_matrix[9][3] = 1; - call_matrix[9][4] = 1; - call_matrix[9][5] = time(NULL); + call_matrix[state->edacs_vc_lcn][0] = lls; + call_matrix[state->edacs_vc_lcn][1] = state->edacs_vc_lcn; + call_matrix[state->edacs_vc_lcn][2] = state->lasttg; + call_matrix[state->edacs_vc_lcn][3] = state->lastsrc; + call_matrix[state->edacs_vc_lcn][4] = 1; + call_matrix[state->edacs_vc_lcn][5] = time(NULL); + } } @@ -2022,7 +2042,15 @@ ncursesPrinter (dsd_opts * opts, dsd_state * state) if (opts->dmr_stereo_wav == 1) //opts->wav_out_file[0] != 0 && { printw ("| Per Call - %s\n", opts->wav_out_file); - printw ("| Per Call - %s\n", opts->wav_out_fileR); + if (opts->dmr_stereo == 1) printw ("| Per Call - %s\n", opts->wav_out_fileR); + } + if (opts->use_rigctl == 1) + { + printw ("| RIGCTL Remote Control Client Established on Port [%d]\n", opts->rigctlportno); + } + if (opts->p25_trunk == 1 && (opts->use_rigctl == 1 || opts->audio_in_type == 3) ) + { + printw ("| Trunk Tracking Active (P25/EDACS)\n"); } @@ -2539,7 +2567,93 @@ ncursesPrinter (dsd_opts * opts, dsd_state * state) printw ("TID: [%s] RID: [%s] \n", state->dpmr_target_id, state->dpmr_caller_id); } - if (lls == 6 || lls == 7 || lls == 18 || lls == 19 || lls == 14 || lls == 15) + //EDACS and ProVoice + if (lls == 14 || lls == 15 || lls == 37 || lls == 38) + { + attroff (COLOR_PAIR(3)); //colors off for EDACS + if (state->edacs_site_id != 0) + { + if (opts->p25_is_tuned == 0) + { + printw ("| Monitoring Control Channel\n"); + } + else + { + printw ("| Monitoring Voice Channel - LCN [%02d]\n", state->edacs_tuned_lcn); + //since we are tuned, keep updating the time so it doesn't disappear during call + call_matrix[state->edacs_tuned_lcn][5] = time(NULL); + } + printw ("| SITE [%03lld][%02llX]", state->edacs_site_id, state->edacs_site_id); + + if (state->ea_mode == 1) + { + printw (" Extended Addressing"); + } + else printw (" Standard/Networked"); + if (state->esk_mask == 0xA0) + { + printw (" w/ ESK"); + } + else printw (" w/o ESK"); + printw ("\n"); + } + for (i = 1; i <= state->edacs_lcn_count; i++) + { + //shim 443 afs in here for display purposes + int a = (call_matrix[i][3] >> 7) & 0xF; + int fs = call_matrix[i][3] & 0x7F; + printw ("| - LCN [%02d][%.06lf]", i, (double)state->trunk_lcn_freq[i-1]/1000000); + + //print Control Channel on LCN line with the current Control Channel + if ( (i) == state->edacs_cc_lcn) + { + attron (COLOR_PAIR(1)); //yellow + printw (" Control Channel"); + attroff (COLOR_PAIR(1)); + } + //print active calls on corresponding LCN line + if ((i != state->edacs_cc_lcn) && time(NULL) - call_matrix[i][5] < 2) + { + attron (COLOR_PAIR(3)); + if (state->ea_mode == 1) printw (" TG [%5lld] SRC [%8lld]", call_matrix[i][2], call_matrix[i][3] ); + else printw (" AFS [%3llX][%02d-%03d]", call_matrix[i][3], a, fs ); + for (int k = 0; k < state->group_tally; k++) + { + if (state->group_array[k].groupNumber == call_matrix[i][2]) + { + printw (" [%s]", state->group_array[k].groupName); + printw ("[%s]", state->group_array[k].groupMode); + } + } + attroff (COLOR_PAIR(3)); + } + //print dying or dead calls in red for x seconds longer + if ( (i != state->edacs_cc_lcn) && (time(NULL) - call_matrix[i][5] >= 2) && (time(NULL) - call_matrix[i][5] < 5) ) + { + attron (COLOR_PAIR(2)); + if (state->ea_mode == 1) printw (" TG [%5lld] SRC [%8lld]", call_matrix[i][2], call_matrix[i][3] ); + else printw (" AFS [%3llX][%02d-%03d]", call_matrix[i][3], a, fs ); + for (int k = 0; k < state->group_tally; k++) + { + if (state->group_array[k].groupNumber == call_matrix[i][2]) + { + printw (" [%s]", state->group_array[k].groupName); + printw ("[%s]", state->group_array[k].groupMode); + } + } + attroff (COLOR_PAIR(2)); + } + if (i == state->edacs_tuned_lcn && opts->p25_is_tuned == 1) printw (" **T**"); //asterisk which lcn is opened + printw ("\n"); + } + if (state->carrier == 1) + { + attron (COLOR_PAIR(3)); + } + } + + + if (lls == 6 || lls == 7 || lls == 18 || lls == 19) { printw ("| %s ", SyncTypes[lls]); //printw ("%s", state->dmr_branding); @@ -2558,8 +2672,8 @@ ncursesPrinter (dsd_opts * opts, dsd_state * state) printw ("--Call History----------------------------------------------------------------\n"); for (short int j = 0; j < 10; j++) { - //only print if a valid time was assinged to the matrix - if ( ((time(NULL) - call_matrix[9-j][5]) < 9999) ) + //only print if a valid time was assigned to the matrix, and not EDACS/PV + if ( ((time(NULL) - call_matrix[9-j][5]) < 9999) && call_matrix[9-j][0] != 14 && call_matrix[9-j][0] != 15 && call_matrix[9-j][0] != 37 && call_matrix[9-j][0] != 38 ) { printw ("| %s ", SyncTypes[call_matrix[9-j][0]]); if (lls == 8 || lls == 9 || lls == 16 || lls == 17) @@ -2602,6 +2716,21 @@ ncursesPrinter (dsd_opts * opts, dsd_state * state) printw ("%s ", getDateC(call_matrix[9-j][5]) ); //You're welcome printw ("%s \n", getTimeC(call_matrix[9-j][5]) ); //Roman } + + //EDACS and ProVoice, outside of timestamp loop + if (call_matrix[j][0] == 14 || call_matrix[j][0] == 15 || call_matrix[j][0] == 37 || call_matrix[j][0] == 38 ) + { + if (call_matrix[j][3] != 0) + { + printw ("| %s ", SyncTypes[call_matrix[j][0]]); + printw ("LCN [%2lld] ", call_matrix[j][1]); + printw ("Group [%8lld] ", call_matrix[j][2]); + printw ("Source [%8lld] ", call_matrix[j][3]); + printw ("%s ", getDateC(call_matrix[j][5]) ); + printw ("%s \n", getTimeC(call_matrix[j][5]) ); + } + + } } //end Call History //fence bottom printw ("------------------------------------------------------------------------------\n"); diff --git a/src/dsd_rigctl.c b/src/dsd_rigctl.c new file mode 100644 index 0000000..a64b8a5 --- /dev/null +++ b/src/dsd_rigctl.c @@ -0,0 +1,285 @@ +/*------------------------------------------------------------------------------- + * dsd_rigctl.c + * Simple RIGCTL Client for DSD (remote control of GQRX, SDR++, etc) + * + * Portions from https://github.com/neural75/gqrx-scanner + * + * LWVMOBILE + * 2022-10 DSD-FME Florida Man Edition + *-----------------------------------------------------------------------------*/ + +#include "dsd.h" +#include +#include +#include +#include + +//UDP Specific +#include + +#define BUFSIZE 1024 +#define FREQ_MAX 4096 +#define SAVED_FREQ_MAX 1000 +#define TAG_MAX 100 + +// +// error - wrapper for perror +// +void error(char *msg) { + perror(msg); + exit(0); +} + +// +// Connect +// +int Connect (char *hostname, int portno) +{ + int sockfd, n; + struct sockaddr_in serveraddr; + struct hostent *server; + + + /* socket: create the socket */ + sockfd = socket(AF_INET, SOCK_STREAM, 0); + if (sockfd < 0) + { + fprintf(stderr,"ERROR opening socket\n"); + error("ERROR opening socket"); + } + + + /* gethostbyname: get the server's DNS entry */ + server = gethostbyname(hostname); + if (server == NULL) { + fprintf(stderr,"ERROR, no such host as %s\n", hostname); + exit(0); + } + + /* build the server's Internet address */ + bzero((char *) &serveraddr, sizeof(serveraddr)); + serveraddr.sin_family = AF_INET; + bcopy((char *)server->h_addr, + (char *)&serveraddr.sin_addr.s_addr, server->h_length); + serveraddr.sin_port = htons(portno); + + /* connect: create a connection with the server */ + if (connect(sockfd, (const struct sockaddr *) &serveraddr, sizeof(serveraddr)) < 0) + error("ERROR connecting"); + + return sockfd; +} +// +// Send +// +bool Send(int sockfd, char *buf) +{ + int n; + + n = write(sockfd, buf, strlen(buf)); + if (n < 0) + error("ERROR writing to socket"); + return true; +} + +// +// Recv +// +bool Recv(int sockfd, char *buf) +{ + int n; + + n = read(sockfd, buf, BUFSIZE); + if (n < 0) + error("ERROR reading from socket"); + buf[n]= '\0'; + return true; +} + + +// +// GQRX Protocol +// +// bool GetCurrentFreq(int sockfd, freq_t *freq) +bool GetCurrentFreq(int sockfd, long int freq) //compatible with P25 freq storage +{ + char buf[BUFSIZE]; + char * ptr; + char * token; + + Send(sockfd, "f\n"); + Recv(sockfd, buf); //buffer will contain a stored \n line break as well + + if (strcmp(buf, "RPRT 1") == 0 ) //sdr++ may return this in error + return false; + + token = strtok (buf, "\n"); //remove line break \n from return buf string + freq = strtol (token, &ptr, 10); //number is base, as in base 2 is binary, base 10 is dec, base 16 hex + //fprintf (stderr, "\nRIGCTL Buffer: [%s]\n", buf); + //fprintf (stderr, "\nRIGCTL VFO Freq: [%ld]\n", freq); + return true; +} +// bool SetFreq(int sockfd, freq_t freq) +bool SetFreq(int sockfd, long int freq) +{ + char buf[BUFSIZE]; + + sprintf (buf, "F %ld\n", freq); //llu + Send(sockfd, buf); + Recv(sockfd, buf); + + if (strcmp(buf, "RPRT 1") == 0 ) + return false; + + GetCurrentFreq(sockfd, freq); + + //freq_t freq_current = 0; + //long int freq_current = 0; + // do + // { + // GetCurrentFreq(sockfd, freq_current); + // } while (freq_current != freq); + + return true; +} + +bool SetModulation(int sockfd, int bandwidth) +{ + char buf[BUFSIZE]; + //ideally, on P25, we want to use the iden_up with bw, and calc bandwidth first + //bandwidth = 12500; //default value, if doing pV, may want to swtich to 25000 + sprintf (buf, "M FM %d\n", bandwidth); + Send(sockfd, buf); + Recv(sockfd, buf); + + if (strcmp(buf, "RPRT 1") == 0 ) + return false; + + //GetCurrentFreq(sockfd, freq); + + return true; +} + +bool GetSignalLevel(int sockfd, double *dBFS) +{ + char buf[BUFSIZE]; + + Send(sockfd, "l\n"); + Recv(sockfd, buf); + + if (strcmp(buf, "RPRT 1") == 0 ) + return false; + + sscanf(buf, "%lf", dBFS); + *dBFS = round((*dBFS) * 10)/10; + + if (*dBFS == 0.0) + return false; + return true; +} + +bool GetSquelchLevel(int sockfd, double *dBFS) +{ + char buf[BUFSIZE]; + + Send(sockfd, "l SQL\n"); + Recv(sockfd, buf); + + if (strcmp(buf, "RPRT 1") == 0 ) + return false; + + sscanf(buf, "%lf", dBFS); + *dBFS = round((*dBFS) * 10)/10; + + return true; +} + +bool SetSquelchLevel(int sockfd, double dBFS) +{ + char buf[BUFSIZE]; + + sprintf (buf, "L SQL %f\n", dBFS); + Send(sockfd, buf); + Recv(sockfd, buf); + + if (strcmp(buf, "RPRT 1") == 0 ) + return false; + + return true; +} +// +// GetSignalLevelEx +// Get a bunch of sample with some delay and calculate the mean value +// +bool GetSignalLevelEx(int sockfd, double *dBFS, int n_samp) +{ + double temp_level; + *dBFS = 0; + int errors = 0; + for (int i = 0; i < n_samp; i++) + { + if ( GetSignalLevel(sockfd, &temp_level) ) + *dBFS = *dBFS + temp_level; + else + errors++; + usleep(1000); + } + *dBFS = *dBFS / (n_samp - errors); + return true; +} + +//shoe in UDP input connection here...still having issues that I don't know how to resolve +int UDPBind (char *hostname, int portno) +{ + int sockfd, n; + struct sockaddr_in serveraddr, client_addr; + struct hostent *server; + + /* socket: create the socket */ + //UDP socket + sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + + if (sockfd < 0) + { + fprintf(stderr,"ERROR opening UDP socket\n"); + error("ERROR opening UDP socket"); + } + + /* build the server's Internet address */ + bzero((char *) &serveraddr, sizeof(serveraddr)); + serveraddr.sin_family = AF_INET; + serveraddr.sin_addr.s_addr = INADDR_ANY; //INADDR_ANY + serveraddr.sin_port = htons(portno); + + //Bind socket to listening + if (bind(sockfd, (struct sockaddr *) &serveraddr, sizeof(serveraddr)) < 0) { + perror("ERROR on binding UDP Port"); + } + + return sockfd; +} + +void rtl_udp_tune(dsd_opts * opts, dsd_state * state, long int frequency) +{ + int handle; + unsigned short udp_port = opts->rtl_udp_port; + char data[5] = {0}; //data buffer size is 5 for UDP frequency tuning + struct sockaddr_in address; + + uint32_t new_freq = frequency; + opts->rtlsdr_center_freq = new_freq; //for ncurses terminal display after rtl is started up + + handle = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + + data[0] = 0; + data[1] = new_freq & 0xFF; + data[2] = (new_freq >> 8) & 0xFF; + data[3] = (new_freq >> 16) & 0xFF; + data[4] = (new_freq >> 24) & 0xFF; + + memset((char * ) & address, 0, sizeof(address)); + address.sin_family = AF_INET; + address.sin_addr.s_addr = inet_addr("127.0.0.1"); //make user configurable later + address.sin_port = htons(udp_port); + sendto(handle, data, 5, 0, (const struct sockaddr * ) & address, sizeof(struct sockaddr_in)); +} \ No newline at end of file diff --git a/src/dsd_symbol.c b/src/dsd_symbol.c index 66217a3..9e56b04 100644 --- a/src/dsd_symbol.c +++ b/src/dsd_symbol.c @@ -100,32 +100,34 @@ getSymbol (dsd_opts * opts, dsd_state * state, int have_sync) } } - else if (opts->audio_in_type == 1) { - result = sf_read_short(opts->audio_in_file, &sample, 1); - //fprintf (stderr, ".."); - if (opts->monitor_input_audio == 1 && state->lastsynctype == -1 && sample < 32767 && sample > -32767) - { - state->pulse_raw_out_buffer = sample; //steal raw out buffer sample here? - pa_simple_write(opts->pulse_raw_dev_out, (void*)&state->pulse_raw_out_buffer, 2, NULL); - } - if(result == 0) { - cleanupAndExit (opts, state); - } + //stdin only, wav files moving to new number + else if (opts->audio_in_type == 1) + { + result = sf_read_short(opts->audio_in_file, &sample, 1); + if(result == 0) + { + //cleanupAndExit (opts, state); + sf_close(opts->audio_in_file); + opts->audio_in_type = 0; //set input type + openPulseInput(opts); //open pulse inpput + sample = 0; //send zero sample + } } + //wav files, same but using seperate value so we can still manipulate ncurses menu + //since we can not worry about getch/stdin conflict else if (opts->audio_in_type == 2) { -#ifdef USE_PORTAUDIO - PaError err = Pa_ReadStream( opts->audio_in_pa_stream, &sample, 1 ); - if( err != paNoError ) + result = sf_read_short(opts->audio_in_file, &sample, 1); + if(result == 0) { - fprintf( stderr, "An error occured while using the portaudio input stream\n" ); - fprintf( stderr, "Error number: %d\n", err ); - fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + + sf_close(opts->audio_in_file); + opts->audio_in_type = 0; //set input type + openPulseInput(opts); //open pulse inpput + sample = 0; //send zero sample + } - - -#endif - } + } else if (opts->audio_in_type == 3) { #ifdef USE_RTLSDR @@ -140,12 +142,30 @@ getSymbol (dsd_opts * opts, dsd_state * state, int have_sync) #endif } + //tcp socket input from SDR++ + else if (opts->audio_in_type == 8) + { + result = sf_read_short(opts->tcp_file_in, &sample, 1); + if(result == 0) { + sf_close(opts->tcp_file_in); + opts->audio_in_type = 0; //set input type + openPulseInput(opts); //open pulse inpput + sample = 0; //zero sample on bad result, keep the ball rolling + fprintf (stderr, "Connection to TCP Server Disconnected.\n"); + } + } + //UDP Socket input...not working correct. Reads samples, but no sync + else if (opts->audio_in_type == 6) + { + //I think this doesn't get the entire dgram when we run sf_read_short on the udp dgram + result = sf_read_short(opts->udp_file_in, &sample, 1); + // if (sample != 0) + // fprintf (stderr, "Result = %d Sample = %d \n", result, sample); + } + + -#ifdef TRACE_DSD - state->debug_sample_index++; -#endif - // printf ("res: %zd\n, offset: %lld", result, sf_seek(opts->audio_in_file, 0, SEEK_CUR)); if (opts->use_cosine_filter) { if ( (state->lastsynctype >= 10 && state->lastsynctype <= 13) || state->lastsynctype == 32 || state->lastsynctype == 33 || state->lastsynctype == 34) @@ -386,11 +406,11 @@ getSymbol (dsd_opts * opts, dsd_state * state, int have_sync) if (state->rf_mod == 2) //GFSK { symbol = state->symbolc; - if (state->symbolc == 0 && state->synctype >= 0) + if (state->symbolc == 0 ) { symbol = -3; //-1 } - if (state->symbolc == 1 && state->synctype >= 0) + if (state->symbolc == 1 ) { symbol = -1; //-3 } diff --git a/src/edacs-bch3.c b/src/edacs-bch3.c new file mode 100644 index 0000000..6b11c43 --- /dev/null +++ b/src/edacs-bch3.c @@ -0,0 +1,517 @@ +/* + * File: bch3.h + * Title: Encoder/decoder for binary BCH codes in C (Version 3.1) + * Author: Robert Morelos-Zaragoza + * Date: August 1994-June 13, 1997 + * Mod: December 14, 2021, for EDACS-FM BCH Polynomial Error Generation and Detection + * Mod2: October 2022, for DSD-FME port of EDACS-FM + * Source: www.eccpage.com + * + * =============== Encoder/Decoder for binary BCH codes in C ================= + * + * Version 1: Original program. The user provides the generator polynomial + * of the code (cumbersome!). + * Version 2: Computes the generator polynomial of the code. + * Version 3: No need to input the coefficients of a primitive polynomial of + * degree m, used to construct the Galois Field GF(2**m). The + * program now works for any binary BCH code of length such that: + * 2**(m-1) - 1 < length <= 2**m - 1 + * + * Note: You may have to change the size of the arrays to make it work. + * + * The encoding and decoding methods used in this program are based on the + * book "Error Control Coding: Fundamentals and Applications", by Lin and + * Costello, Prentice Hall, 1983. + * + * Thanks to Patrick Boyle (pboyle@era.com) for his observation that 'bch2.c' + * did not work for lengths other than 2**m-1 which led to this new version. + * Portions of this program are from 'rs.c', a Reed-Solomon encoder/decoder + * in C, written by Simon Rockliff (simon@augean.ua.oz.au) on 21/9/89. The + * previous version of the BCH encoder/decoder in C, 'bch2.c', was written by + * Robert Morelos-Zaragoza (robert@spectra.eng.hawaii.edu) on 5/19/92. + * + * NOTE: + * The author is not responsible for any malfunctioning of + * this program, nor for any damage caused by it. Please include the + * original program along with these comments in any redistribution. + * + * For more information, suggestions, or other ideas on implementing error + * correcting codes, please contact me at: + * + * Robert Morelos-Zaragoza + * 5120 Woodway, Suite 7036 + * Houston, Texas 77056 + * + * email: r.morelos-zaragoza@ieee.org + * + * COPYRIGHT NOTICE: This computer program is free for non-commercial purposes. + * You may implement this program for any non-commercial application. You may + * also implement this program for commercial purposes, provided that you + * obtain my written permission. Any modification of this program is covered + * by this copyright. + * + * == Copyright (c) 1994-7, Robert Morelos-Zaragoza. All rights reserved. == + * + * m = order of the Galois field GF(2**m) + * n = 2**m - 1 = size of the multiplicative group of GF(2**m) + * length = length of the BCH code + * t = error correcting capability (max. no. of errors the code corrects) + * d = 2*t + 1 = designed min. distance = no. of consecutive roots of g(x) + 1 + * k = n - deg(g(x)) = dimension (no. of information bits/codeword) of the code + * p[] = coefficients of a primitive polynomial used to generate GF(2**m) + * g[] = coefficients of the generator polynomial, g(x) + * alpha_to [] = log table of GF(2**m) + * index_of[] = antilog table of GF(2**m) + * ddata[] = information bits = coefficients of data polynomial, i(x) + * bb[] = coefficients of redundancy polynomial x^(length-k) i(x) modulo g(x) + * numerr = number of errors + * errpos[] = error positions + * recd[] = coefficients of the received polynomial + * decerror = number of decoding errors (in _message_ positions) + * + */ + +#include +#include +#include + +int m, n, length, k, t, d; +int p[21]; +int alpha_to[1048576], index_of[1048576], g[548576]; +int recd[1048576], ddata[1048576], bb[548576]; +int seed; +int numerr, errpos[1024], decerror = 0; + + +void +read_p() +/* + * Read m, the degree of a primitive polynomial p(x) used to compute the + * Galois field GF(2**m). Get precomputed coefficients p[] of p(x). Read + * the code length. + */ +{ + int i, ninf; + + do { + + m = 6; + } while ( !(m>1) || !(m<21) ); + for (i=1; ininf)) ); +} + + +void +generate_gf() +/* + * Generate field GF(2**m) from the irreducible polynomial p(X) with + * coefficients in p[0]..p[m]. + * + * Lookup tables: + * index->polynomial form: alpha_to[] contains j=alpha^i; + * polynomial form -> index form: index_of[j=alpha^i] = i + * + * alpha=2 is the primitive element of GF(2**m) + */ +{ + register int i, mask; + + mask = 1; + alpha_to[m] = 0; + for (i = 0; i < m; i++) { + alpha_to[i] = mask; + index_of[alpha_to[i]] = i; + if (p[i] != 0) + alpha_to[m] ^= mask; + mask <<= 1; + } + index_of[alpha_to[m]] = m; + mask >>= 1; + for (i = m + 1; i < n; i++) { + if (alpha_to[i - 1] >= mask) + alpha_to[i] = alpha_to[m] ^ ((alpha_to[i - 1] ^ mask) << 1); + else + alpha_to[i] = alpha_to[i - 1] << 1; + index_of[alpha_to[i]] = i; + } + index_of[0] = -1; +} + + +void +gen_poly() +/* + * Compute the generator polynomial of a binary BCH code. Fist generate the + * cycle sets modulo 2**m - 1, cycle[][] = (i, 2*i, 4*i, ..., 2^l*i). Then + * determine those cycle sets that contain integers in the set of (d-1) + * consecutive integers {1..(d-1)}. The generator polynomial is calculated + * as the product of linear factors of the form (x+alpha^i), for every i in + * the above cycle sets. + */ +{ + register int ii, jj, ll, kaux; + register int test, aux, nocycles, root, noterms, rdncy; + int cycle[1024][21], size[1024], min[1024], zeros[1024]; + + /* Generate cycle sets modulo n, n = 2**m - 1 */ + cycle[0][0] = 0; + size[0] = 1; + cycle[1][0] = 1; + size[1] = 1; + jj = 1; /* cycle set index */ + if (m > 9) { + + } + do { + /* Generate the jj-th cycle set */ + ii = 0; + do { + ii++; + cycle[jj][ii] = (cycle[jj][ii - 1] * 2) % n; + size[jj]++; + aux = (cycle[jj][ii] * 2) % n; + } while (aux != cycle[jj][0]); + /* Next cycle set representative */ + ll = 0; + do { + ll++; + test = 0; + for (ii = 1; ((ii <= jj) && (!test)); ii++) + /* Examine previous cycle sets */ + for (kaux = 0; ((kaux < size[ii]) && (!test)); kaux++) + if (ll == cycle[ii][kaux]) + test = 1; + } while ((test) && (ll < (n - 1))); + if (!(test)) { + jj++; /* next cycle set index */ + cycle[jj][0] = ll; + size[jj] = 1; + } + } while (ll < (n - 1)); + nocycles = jj; /* number of cycle sets modulo n */ + + t = 2; + + d = 2 * t + 1; + + /* Search for roots 1, 2, ..., d-1 in cycle sets */ + kaux = 0; + rdncy = 0; + for (ii = 1; ii <= nocycles; ii++) { + min[kaux] = 0; + test = 0; + for (jj = 0; ((jj < size[ii]) && (!test)); jj++) + for (root = 1; ((root < d) && (!test)); root++) + if (root == cycle[ii][jj]) { + test = 1; + min[kaux] = ii; + } + if (min[kaux]) { + rdncy += size[min[kaux]]; + kaux++; + } + } + noterms = kaux; + kaux = 1; + for (ii = 0; ii < noterms; ii++) + for (jj = 0; jj < size[min[ii]]; jj++) { + zeros[kaux] = cycle[min[ii]][jj]; + kaux++; + } + + k = length - rdncy; + + if (k<0) + { + exit(0); + } + + /* Compute the generator polynomial */ + g[0] = alpha_to[zeros[1]]; + g[1] = 1; /* g(x) = (X + zeros[1]) initially */ + for (ii = 2; ii <= rdncy; ii++) { + g[ii] = 1; + for (jj = ii - 1; jj > 0; jj--) + if (g[jj] != 0) + g[jj] = g[jj - 1] ^ alpha_to[(index_of[g[jj]] + zeros[ii]) % n]; + else + g[jj] = g[jj - 1]; + g[0] = alpha_to[(index_of[g[0]] + zeros[ii]) % n]; + } +} + + +void +encode_bch() +/* + * Compute redundacy bb[], the coefficients of b(x). The redundancy + * polynomial b(x) is the remainder after dividing x^(length-k)*ddata(x) + * by the generator polynomial g(x). + */ +{ + register int i, j; + register int feedback; + + for (i = 0; i < length - k; i++) + bb[i] = 0; + for (i = k - 1; i >= 0; i--) { + feedback = ddata[i] ^ bb[length - k - 1]; + if (feedback != 0) { + for (j = length - k - 1; j > 0; j--) + if (g[j] != 0) + bb[j] = bb[j - 1] ^ feedback; + else + bb[j] = bb[j - 1]; + bb[0] = g[0] && feedback; + } else { + for (j = length - k - 1; j > 0; j--) + bb[j] = bb[j - 1]; + bb[0] = 0; + } + } +} + + +void +decode_bch() +/* + * Simon Rockliff's implementation of Berlekamp's algorithm. + * + * Assume we have received bits in recd[i], i=0..(n-1). + * + * Compute the 2*t syndromes by substituting alpha^i into rec(X) and + * evaluating, storing the syndromes in s[i], i=1..2t (leave s[0] zero) . + * Then we use the Berlekamp algorithm to find the error location polynomial + * elp[i]. + * + * If the degree of the elp is >t, then we cannot correct all the errors, and + * we have detected an uncorrectable error pattern. We output the information + * bits uncorrected. + * + * If the degree of elp is <=t, we substitute alpha^i , i=1..n into the elp + * to get the roots, hence the inverse roots, the error location numbers. + * This step is usually called "Chien's search". + * + * If the number of errors located is not equal the degree of the elp, then + * the decoder assumes that there are more than t errors and cannot correct + * them, only detect them. We output the information bits uncorrected. + */ +{ + register int i, j, u, q, t2, count = 0, syn_error = 0; + int elp[1026][1024], d[1026], l[1026], u_lu[1026], s[1025]; + int root[200], loc[200], err[1024], reg[201]; + + t2 = 2 * t; + + /* first form the syndromes */ + for (i = 1; i <= t2; i++) { + s[i] = 0; + for (j = 0; j < length; j++) + if (recd[j] != 0) + s[i] ^= alpha_to[(i * j) % n]; + if (s[i] != 0) + syn_error = 1; /* set error flag if non-zero syndrome */ +/* + * Note: If the code is used only for ERROR DETECTION, then + * exit program here indicating the presence of errors. + */ + /* convert syndrome from polynomial form to index form */ + s[i] = index_of[s[i]]; + } + + if (syn_error) { /* if there are errors, try to correct them */ + /* + * Compute the error location polynomial via the Berlekamp + * iterative algorithm. Following the terminology of Lin and + * Costello's book : d[u] is the 'mu'th discrepancy, where + * u='mu'+1 and 'mu' (the Greek letter!) is the step number + * ranging from -1 to 2*t (see L&C), l[u] is the degree of + * the elp at that step, and u_l[u] is the difference between + * the step number and the degree of the elp. + */ + /* initialise table entries */ + d[0] = 0; /* index form */ + d[1] = s[1]; /* index form */ + elp[0][0] = 0; /* index form */ + elp[1][0] = 1; /* polynomial form */ + for (i = 1; i < t2; i++) { + elp[0][i] = -1; /* index form */ + elp[1][i] = 0; /* polynomial form */ + } + l[0] = 0; + l[1] = 0; + u_lu[0] = -1; + u_lu[1] = 0; + u = 0; + + do { + u++; + if (d[u] == -1) { + l[u + 1] = l[u]; + for (i = 0; i <= l[u]; i++) { + elp[u + 1][i] = elp[u][i]; + elp[u][i] = index_of[elp[u][i]]; + } + } else + /* + * search for words with greatest u_lu[q] for + * which d[q]!=0 + */ + { + q = u - 1; + while ((d[q] == -1) && (q > 0)) + q--; + /* have found first non-zero d[q] */ + if (q > 0) { + j = q; + do { + j--; + if ((d[j] != -1) && (u_lu[q] < u_lu[j])) + q = j; + } while (j > 0); + } + + /* + * have now found q such that d[u]!=0 and + * u_lu[q] is maximum + */ + /* store degree of new elp polynomial */ + if (l[u] > l[q] + u - q) + l[u + 1] = l[u]; + else + l[u + 1] = l[q] + u - q; + + /* form new elp(x) */ + for (i = 0; i < t2; i++) + elp[u + 1][i] = 0; + for (i = 0; i <= l[q]; i++) + if (elp[q][i] != -1) + elp[u + 1][i + u - q] = + alpha_to[(d[u] + n - d[q] + elp[q][i]) % n]; + for (i = 0; i <= l[u]; i++) { + elp[u + 1][i] ^= elp[u][i]; + elp[u][i] = index_of[elp[u][i]]; + } + } + u_lu[u + 1] = u - l[u + 1]; + + /* form (u+1)th discrepancy */ + if (u < t2) { + /* no discrepancy computed on last iteration */ + if (s[u + 1] != -1) + d[u + 1] = alpha_to[s[u + 1]]; + else + d[u + 1] = 0; + for (i = 1; i <= l[u + 1]; i++) + if ((s[u + 1 - i] != -1) && (elp[u + 1][i] != 0)) + d[u + 1] ^= alpha_to[(s[u + 1 - i] + + index_of[elp[u + 1][i]]) % n]; + /* put d[u+1] into index form */ + d[u + 1] = index_of[d[u + 1]]; + } + } while ((u < t2) && (l[u + 1] <= t)); + + u++; + if (l[u] <= t) {/* Can correct errors */ + /* put elp into index form */ + for (i = 0; i <= l[u]; i++) + elp[u][i] = index_of[elp[u][i]]; + + for (i = 0; i <= l[u]; i++) + + /* Chien search: find roots of the error location polynomial */ + for (i = 1; i <= l[u]; i++) + reg[i] = elp[u][i]; + count = 0; + for (i = 1; i <= n; i++) { + q = 1; + for (j = 1; j <= l[u]; j++) + if (reg[j] != -1) { + reg[j] = (reg[j] + j) % n; + q ^= alpha_to[reg[j]]; + } + if (!q) { /* store root and error + * location number indices */ + root[count] = i; + loc[count] = n - i; + count++; + } + } + + if (count == l[u]) + /* no. roots = degree of elp hence <= t errors */ + for (i = 0; i < l[u]; i++) + recd[loc[i]] ^= 1; + + } + } +} + +long int messagepp = 0x0; + +//very simplified version, just to encode, get frame and compare +unsigned long long int edacs_bch (unsigned long long int message) +{ + int i; + //not ideal to run these every frame, will want to eventually run once on start of DSD-FME, and store values + read_p(); /* Read m */ + generate_gf(); /* Construct the Galois Field GF(2**m) */ + gen_poly(); /* Compute the generator polynomial of BCH code */ + //test shows this works, but output from program is that the poly is backwards, not sure if it corrects okay though + for (i = 0; i < k; i++){ + ddata[i] = ( (message >> i) & 0x1 ); //loaded up backwards? or just outputs backwards? + } + + + encode_bch(); /* encode data */ + + /* + * recd[] are the coefficients of c(x) = x**(length-k)*data(x) + b(x) + */ + for (i = 0; i < length - k; i++) + recd[i] = bb[i]; + for (i = 0; i < k; i++) + recd[i + length - k] = ddata[i]; + + for (i = 0; i < length; i++) { + messagepp = (messagepp << 1) | recd[39-i]; //get it in the correct direction + } + + return (messagepp); + +} + + diff --git a/src/edacs-fme.c b/src/edacs-fme.c new file mode 100644 index 0000000..5867581 --- /dev/null +++ b/src/edacs-fme.c @@ -0,0 +1,418 @@ +/*------------------------------------------------------------------------------- + * EDACS-FME + * A program for decoding EDACS (ported to DSD-FME) + * https://github.com/lwvmobile/edacs-fm + * + * Portions of this software originally from: + * https://github.com/sp5wwp/ledacs + * XTAL Labs + * 30 IV 2016 + * Many thanks to SP5WWP for permission to use and modify this software + * + * Encoder/decoder for binary BCH codes in C (Version 3.1) + * Robert Morelos-Zaragoza + * 1994-7 + * + * LWVMOBILE + * 2022-10 Version EDACS-FM Florida Man Edition + *-----------------------------------------------------------------------------*/ +#include "dsd.h" + +char * getDateE(void) { + char datename[99]; //might honestly just need a much smaller size, and not a larger one + char * curr2; + struct tm * to; + time_t t; + t = time(NULL); + to = localtime( & t); + strftime(datename, sizeof(datename), "%Y-%m-%d", to); + curr2 = strtok(datename, " "); + return curr2; +} + +char * getTimeE(void) //get pretty hh:mm:ss timestamp +{ + time_t t = time(NULL); + + char * curr; + char * stamp = asctime(localtime( & t)); + + curr = strtok(stamp, " "); + curr = strtok(NULL, " "); + curr = strtok(NULL, " "); + curr = strtok(NULL, " "); + + return curr; +} + +void edacs(dsd_opts * opts, dsd_state * state) +{ + unsigned long long fr_1 = 0xFFFFFFFFFF; //40-bit frames + unsigned long long fr_2 = 0; //each is a 40 bit frame that repeats 3 times + unsigned long long fr_3 = 0; //two messages per frame + unsigned long long fr_4 = 0xFFFFFFFFFF; + unsigned long long fr_5 = 0; + unsigned long long fr_6 = 0; + + //BCH stuff + unsigned long long int fr_1m = 0xFFFFFFF; //28-bit 7X message portion to pass to bch handler + unsigned long long int fr_1t = 0xFFFFFFFFFF; //40 bit return from BCH with poly attached + unsigned long long int fr_4m = 0xFFFFFFF; //28-bit 7X message portion to pass to bch handler + unsigned long long int fr_4t = 0xFFFFFFFFFF; //40 bit return from BCH with poly attached + + unsigned char command = 0xFF; + unsigned char mt1 = 0x1F; + unsigned char mt2 = 0xF; + unsigned char mta = 0; + unsigned char lcn = 0; + + //commands; may not use these anymore + unsigned int vcmd = 0xEE; //voice command variable + unsigned int idcmd = 0xFD; + unsigned int peercmd = 0xF88; //using for EA detection test + unsigned int netcmd = 0xF3; //using for Networked Test + + state->edacs_vc_lcn = -1; //init on negative for ncurses and tuning + + int i, j; + int edacs_bit[241] = {0}; //zero out bit array and collect bits into it. + + for (i = 0; i < 240; i++) //288 bits every transmission minus 48 bit (24 dibit) sync pattern + { + edacs_bit[i] = getDibit (opts, state); //getDibit returns binary 0 or 1 on GFSK signal (Edacs and PV) + } + + //push the edacs_bit array into fr format from EDACS-FM + for (i = 0; i < 40; i++) + { + //only fr_1 and fr4 are going to matter + fr_1 = fr_1 << 1; + fr_1 = fr_1 | edacs_bit[i]; + fr_2 = fr_2 << 1; + fr_2 = fr_2 | edacs_bit[i+40]; + fr_3 = fr_3 << 1; + fr_3 = fr_3 | edacs_bit[i+80]; + + fr_4 = fr_4 << 1; + fr_4 = fr_4 | edacs_bit[i+120]; + fr_5 = fr_5 << 1; + fr_5 = fr_5 | edacs_bit[i+160]; + fr_6 = fr_6 << 1; + fr_6 = fr_6 | edacs_bit[i+200]; + + } + + fr_1 = fr_1 & 0xFFFFFFFFFF; + fr_4 = fr_4 & 0xFFFFFFFFFF; + + fr_1m = (fr_1 & 0xFFFFFFF000) >> 12; + fr_4m = (fr_4 & 0xFFFFFFF000) >> 12; + + //take our message and create a new crc for it, if it matches the old crc, then we have a good frame + fr_1t = edacs_bch (fr_1m); + fr_4t = edacs_bch (fr_4m); + + fr_1t = fr_1t & 0xFFFFFFFFFF; + fr_4t = fr_4t & 0xFFFFFFFFFF; + + if (fr_1 != fr_1t || fr_4 != fr_4t) + { + fprintf (stderr, " BCH FAIL "); + } + else //BCH Pass, continue from here. + { + + //ESK on/off detection, I honestly don't remember the logic for this anymore, but it works fine + if ( (((fr_1t & 0xF000000000) >> 36) != 0xB) && (((fr_1t & 0xF000000000) >> 36) != 0x1) && (((fr_1t & 0xFF00000000) >> 32) != 0xF3) ) + { + //experimenting with values here, not too high, and not too low + if ( (((fr_1t & 0xF000000000) >> 36) <= 0x8 )) + { + state->esk_mask = 0xA0; + } + //ideal value would be 5, but some other values exist that don't allow it + if ( (((fr_1t & 0xF000000000) >> 36) > 0x8 ) ) + { + state->esk_mask = 0x0; + } + } + + //Standard/Networked Auto Detection + //if (command == netcmd) //netcmd is F3 Unknown Command in Networked + if ( (fr_1t >> 32 == netcmd) ) + { + state->ea_mode = 0; //disable extended addressing mode + } + + //EA Auto detection //peercmd is 0xF88 peer site relay 0xFF80000000 >> 28 + if (fr_1t >> 28 == peercmd || fr_1t >> 28 == (peercmd ^ 0xA00) ) + { + state->ea_mode = 1; //enable extended addressing mode + } + + //Start Extended Addressing Mode + if (state->ea_mode == 1) + { + command = ((fr_1t & 0xFF00000000) >> 32) ^ state->esk_mask; + mt1 = (command & 0xF8) >> 3; + mt2 = (fr_1t & 0x780000000) >> 31; + lcn = (fr_1t & 0x3E0000000) >> 29; //only valid during calls, status + + //Site ID + unsigned long long int site_id = 0; //we probably could just make this an int as well as the state variables + if (mt1 == 0x1F && mt2 == 0xA) + { + site_id = ((fr_1 & 0x1F000) >> 12) | ((fr_1 & 0x1F000000) >> 19); + fprintf (stderr, "%s", KYEL); + fprintf (stderr, " Site ID [%02llX][%03lld] Extended Addressing", site_id, site_id); + fprintf (stderr, "%s", KNRM); + state->edacs_site_id = site_id; + } + //Patch Groups + else if (mt1 == 0x1F && mt2 == 0xC) + { + int patch_site = ((fr_4t & 0xFF00000000) >> 32); //is site info valid, 0 for all sites? else patch only good on site listed? + int sourcep = ((fr_1t & 0xFFFF000) >> 12); + int targetp = ((fr_4t & 0xFFFF000) >> 12); + fprintf (stderr, " Patch -- Site [%d] Source [%d] Target [%d] ", patch_site, sourcep, targetp); + } + //Adjacent Sites + else if (mt1 == 0x1F && mt2 == 0x1) + { + fprintf (stderr, " Adjacent Site"); + if ( ((fr_1t & 0xFF000) >> 12) > 0 ) + { + int adj = (fr_1t & 0xFF000) >> 12; + int adj_l = (fr_1t & 0x1F000000) >> 24; + fprintf (stderr, " [%02X][%03d] on CC LCN [%02d]", adj, adj, adj_l); + } + } + //Control Channel LCN + else if (mt1 == 0x1F && mt2 == 0x8) + { + fprintf (stderr, " Control Channel LCN"); + if (((fr_4t >> 12) & 0x1F) != 0) + { + state->edacs_cc_lcn = ((fr_4t >> 12) & 0x1F); + if (state->edacs_cc_lcn > state->edacs_lcn_count && lcn < 26) //26, or 27. shouldn't matter don't think cc lcn will give a status lcn val + { + state->edacs_lcn_count = state->edacs_cc_lcn; + } + fprintf (stderr, " [%d]", state->edacs_cc_lcn); + //set trunking cc here so we know where to come back to + if (opts->p25_trunk == 1 && state->trunk_lcn_freq[state->edacs_cc_lcn-1] != 0) + { + state->p25_cc_freq = state->trunk_lcn_freq[state->edacs_cc_lcn-1]; //index starts at zero, lcn's locally here start at 1 + } + } + } + //disabling kick command, data looks like its just FFFF, no actual values, can't verify accuracy + // else if (mt1 == 0x1F && mt2 == 0xB) //KICK LISTING for EA?? Unverified, but probably observed in Unitrunker way back when. + // { + // int kicked = (fr_4t & 0xFFFFF000) >> 12; + // fprintf (stderr, " FR_1 [%010llX]", fr_1t); + // fprintf (stderr, " FR_3 [%010llX]", fr_3); + // fprintf (stderr, " FR_4 [%010llX]", fr_4t); + // fprintf (stderr, " FR_6 [%010llX]", fr_6); + // fprintf (stderr, " Kick Command?"); + // } + //Voice Call Grant Update + // mt1 0x3 is Digital group voice call, 0x2 Group Data Channel, 0x1 TDMA call + else if (mt1 >= 0x1 && mt1 <= 0x3) + { + //LCNs greater than 26 are considered status values, "Busy, Queue, Deny, etc" + if (lcn > state->edacs_lcn_count && lcn < 26) + { + state->edacs_lcn_count = lcn; + } + + int group = (fr_1t & 0xFFFF000) >> 12; + int source = (fr_4t & 0xFFFFF000) >> 12; + if (group != 0) state->lasttg = group; + if (source != 0) state->lastsrc = source; + if (lcn != 0) state->edacs_vc_lcn = lcn; + fprintf (stderr, "%s", KGRN); + fprintf (stderr, " Group [%05d] Source [%08d] LCN[%02d]", group, source, lcn); + + char mode[8]; //allow, block, digital, enc, etc + + for (int i = 0; i < state->group_tally; i++) + { + if (state->group_array[i].groupNumber == group) + { + fprintf (stderr, " [%s]", state->group_array[i].groupName); + strcpy (mode, state->group_array[i].groupMode); + //fprintf (stderr, "[%s]", mode); //see if we are getting correct values + } + } + + if (mt1 == 0x1) fprintf (stderr, " TDMA Call"); //never observed, wonder if any EDACS systems ever carried a TDMA signal (X2-TDMA?) + if (mt1 == 0x2) fprintf (stderr, " Group Data Call"); //Never Seen this one before + if (mt1 == 0x3) fprintf (stderr, " Digital Call"); //ProVoice, this is what we always get on SLERS EA + fprintf (stderr, "%s", KNRM); + + //this is working now with the new import setup + if (opts->p25_trunk == 1 && (strcmp(mode, "DE") != 0) ) + { + if (lcn < 26 && state->trunk_lcn_freq[lcn-1] != 0) //don't tune if zero (not loaded or otherwise) + { + //openwav file and do per call right here, should probably check as well to make sure we have a valid trunking method active (rigctl, rtl) + if (opts->dmr_stereo_wav == 1) + { + sprintf (opts->wav_out_file, "./WAV/%s %s pV Site %lld TG %d SRC %d.wav", getDateE(), getTimeE(), state->edacs_site_id, group, source); + openWavOutFile (opts, state); + } + + //do condition here, in future, will allow us to use tuning methods as well, or rtl_udp as well + if (opts->use_rigctl == 1) + { + SetModulation(opts->rigctl_sockfd, 12500); //bw depends on system strength, but we want a wide one for now + SetFreq(opts->rigctl_sockfd, state->trunk_lcn_freq[lcn-1]); //minus one because the lcn index starts at zero + //probably should place these inside of the tuning function condition + state->edacs_tuned_lcn = lcn; + opts->p25_is_tuned = 1; + + } + + if (opts->audio_in_type == 3) //rtl dongle + { + //insert rtl udp command, make a new function for it! + rtl_udp_tune(opts, state, state->trunk_lcn_freq[lcn-1]); + //probably should place these inside of the tuning function condition + state->edacs_tuned_lcn = lcn; + opts->p25_is_tuned = 1; + } + + } + + } + } + else //print frames for debug/analysis + { + fprintf (stderr, " FR_1 [%010llX]", fr_1t); + fprintf (stderr, " FR_4 [%010llX]", fr_4t); + fprintf (stderr, " Unknown Command"); + } + + } + //Start Standard or Networked Mode + else if (state->ea_mode == 0) + { + //standard or networked + command = ((fr_1t & 0xFF00000000) >> 32) ^ state->esk_mask; + lcn = (fr_1t & 0xF8000000) >> 27; + + //site ID and CC LCN + if (command == 0xFD) + { + int site_id = (fr_1t & 0xFF000) >> 12; + int cc_lcn = (fr_1t & 0x1F000000) >> 24; + + fprintf (stderr, "%s", KYEL); + fprintf (stderr, " Site ID [%02X][%03d] on CC LCN [%02d] Standard/Networked", site_id, site_id, cc_lcn); + fprintf (stderr, "%s", KNRM); + state->edacs_site_id = site_id; + state->edacs_cc_lcn = cc_lcn; + if (state->edacs_cc_lcn > state->edacs_lcn_count && lcn < 26) + { + state->edacs_lcn_count = state->edacs_cc_lcn; + } + } + + //voice call assignment + else if (command == 0xEE || command == 0xEF) + { + + if (lcn > state->edacs_lcn_count && lcn < 26) + { + state->edacs_lcn_count = lcn; + } + state->edacs_vc_lcn = lcn; + //just going to use the default 4-4-3 A-FS scheme, making it user configurable would be a pain + int afs = (fr_1t & 0x7FF000) >> 12; + int a = afs >> 7; + int fs = afs & 0x7F; + int status = (fr_1t & 0xF00000000) >> 32; + if (afs > 0) state->lastsrc = afs; + fprintf (stderr, "%s", KGRN); + fprintf (stderr, " AFS [0x%03X] [%02d-%03d] LCN [%02d]", afs, a, fs, lcn); + + char mode[8]; //allow, block, digital, enc, etc + + for (int i = 0; i < state->group_tally; i++) + { + if (state->group_array[i].groupNumber == afs) + { + fprintf (stderr, " [%s]", state->group_array[i].groupName); + strcpy (mode, state->group_array[i].groupMode); + } + } + + if (command == 0xEE) + { + //no handling for raw audio yet...that works properly atleast + fprintf (stderr, " Analog"); + } + else //Digital Call (ProVoice, hopefully not Aegis in 2022) + { + fprintf (stderr, " Digital"); + //this is working now with the new import setup + if (opts->p25_trunk == 1 && (strcmp(mode, "DE") != 0) ) + { + if (lcn < 26 && state->trunk_lcn_freq[lcn-1] != 0) //don't tune if zero (not loaded or otherwise) + { + //openwav file and do per call right here + if (opts->dmr_stereo_wav == 1) + { + sprintf (opts->wav_out_file, "./WAV/%s %s pV Site %lld AFS %X.wav", getDateE(), getTimeE(), state->edacs_site_id, afs); + openWavOutFile (opts, state); + } + + if (opts->use_rigctl == 1) + { + SetModulation(opts->rigctl_sockfd, 12500); //bw depends on system strength, but we want a wide one for now + SetFreq(opts->rigctl_sockfd, state->trunk_lcn_freq[lcn-1]); //minus one because our index starts at zero + //probably should place these inside of the tuning function condition + state->edacs_tuned_lcn = lcn; + opts->p25_is_tuned = 1; + } + + if (opts->audio_in_type == 3) //rtl dongle + { + //rtl udp command here + rtl_udp_tune(opts, state, state->trunk_lcn_freq[lcn-1]); + //probably should place these inside of the tuning function condition + state->edacs_tuned_lcn = lcn; + opts->p25_is_tuned = 1; + } + + } + + } + } + fprintf (stderr, "%s", KNRM); + } + //Network Command, Not sure of how to handle, but just show frames + else if (command == 0xF3) + { + fprintf (stderr, " FR_1 [%010llX]", fr_1t); + fprintf (stderr, " FR_4 [%010llX]", fr_4t); + fprintf (stderr, " Network Command"); + } + + else //print frames for debug/analysis + { + fprintf (stderr, " FR_1 [%010llX]", fr_1t); + fprintf (stderr, " FR_4 [%010llX]", fr_4t); + fprintf (stderr, " Unknown Command"); + } + + } //end Standard or Networked + + } + + fprintf (stderr, "\n"); + +} + + \ No newline at end of file diff --git a/src/provoice.c b/src/provoice.c index 9639ec8..9678120 100644 --- a/src/provoice.c +++ b/src/provoice.c @@ -13,10 +13,14 @@ processProVoice (dsd_opts * opts, dsd_state * state) char imbe7100_fr2[7][24]; const int *w, *x; - if (opts->errorbars == 1) - { - fprintf (stderr,"VOICE "); - } + fprintf (stderr," VOICE"); + if (opts->p25_trunk == 1 && opts->p25_is_tuned == 1) + { + fprintf (stderr, "%s", KGRN); + fprintf (stderr, " Site [%02llX][%03lld] Group [%05d] Source [%08d] LCN[%02d] ", + state->edacs_site_id, state->edacs_site_id, state->lasttg, state->lastsrc, state->edacs_tuned_lcn); + fprintf (stderr, "%s", KNRM); + } for (i = 0; i < 64; i++) { diff --git a/src/rtl_sdr_fm.cpp b/src/rtl_sdr_fm.cpp index f024eea..c15e5e8 100644 --- a/src/rtl_sdr_fm.cpp +++ b/src/rtl_sdr_fm.cpp @@ -959,7 +959,7 @@ static void *socket_thread_fn(void *arg) { rtlsdr_set_center_freq(dongle.dev, dongle.freq); //verbose_set_frequency (dongle.dev, new_freq); //fprintf (stderr, "Tuning to: %d [Hz] (central freq: %d [Hz])\n", new_freq, new_freq + freq_offset); - fprintf (stderr, "Tuning to: %d [Hz] \n", new_freq); + fprintf (stderr, "\nTuning to: %d [Hz] \n", new_freq); } /* if(buffer[0] == 1) {