David Rowe
Created Dec 2015
- FreeDV beacon daemon. Listens for freedv signals, then transmits a
- reply. Saves received signal from radio and decoded audio as wavefiles
- so they can be saved to a web site.
-
- If stereo audio device we use left channel only. RTS and DTR is
- rasised on Tx to trigger Radio PTT.
+ FreeDV 1600 beacon. Listens for FreeDV signals, then transmits a
+ reply. Places the received signal files on a web server. Requires a
+ Linux machine with a sound card and RS232-PTT interface to your radio.
+ Just one sound card is required.
+
+ When a "trigger" string is detected in the rx FreeDV text message
+ (e.g. "hello beacon", or the beacon callsign), the beacon will
+ transmit a signal report back to you.
+
+ It requires a "txfilename" wave file to transmit, e.g. some one
+ saying "Hi, I am a FreeDV beacon blah blah". The signal report is
+ encoded into the transmit text message. Make the wave file long
+ enough so that the the signal report is repeated a few times, say 30
+ seconds. Transmit will stop when the wave file is played once.
+
+ Freebeacon saves the received audio from the radio AND the decoded
+ audio as wavefiles. The file length is limited to 60 seconds. If
+ you run freebeacon in a webserver directory these will appear on the
+ Web. Add a cron job to your machine to clean these files up once a
+ day.
+
+ If your input audio device is stereo note we only listen to
+ the left channel. RTS and DTR is raised on transmit, and lowered
+ otherwise.
A whole lot of code was lifted from freedv-dev for this program.
[X] prog sound dongle debug
[X] RS232 PTT code
[X] writing to wave files
- [ ] test mode to tx straight away then end, to check levels, debug RS232
- [ ] test OTA
- [ ] OTA on RPi
+ [X] test mode to tx straight away then end, to check levels, debug RS232
+ [ ] FreeDV 700 support
+ [ ] daemonise
+ + change all fprintfs to use log file in daemon mode
+ [ ] test OTA on laptop
+ [ ] test OTA on RPi
[ ] writing text string to a web page (cat, create if doesn't exist)
[ ] samples from stdin option to work from sdr
+ [ ] monitor rx and tx audio on another sound device
[ ] option to not tx, just log info, for rx only stations
+ [ ] Hamlib support for keying different radios
[ ] basic SM1000 version
- + has audio interfaces
+ + add mode, state machine
+ + has audio interfaces, PTT, so neat solution
Building:
Note you need the libraries on the gcc line installed (TODO find apt-get package names)
gcc -I/usr/local/include/codec2 freebeacon.c -o freebeacon -lsamplerate -lportaudio -lsndfile -lcodec2
- Running:
- ./freebeacon
+ Usage:
+ ./freebeacon -h
+
+ List sound devices
+ ./freebeacon -l
+
+ Example usage:
+ ./freebeacon -c /dev/ttyUSB1 --txfilename ~/codec2-dev/wav/vk5qi.wav --dev 4 -v --trigger hello
*/
#define UNSYNC_FRAMES 25 // frames of lost sync we need to see to change state
#define PEAK_COUNTER 10 // how often to report peak input level
#define COM_HANDLE_INVALID -1
+#define LOG_COUNTER 50
-/* globals used to communicate with async events */
+/* globals used to communicate with async events and callback functions */
volatile int keepRunning;
char txtMsg[MAX_CHAR], *ptxtMsg, triggerString[MAX_CHAR];
int triggered;
float snr_est, snr_sample;
-int com_handle;
+int com_handle, verbose;
/* state machine defines */
void raiseRTS(void);
void lowerRTS(void);
+
+/*--------------------------------------------------------------------------------------------------------*\
+
+ FUNCTIONS
+
+\*--------------------------------------------------------------------------------------------------------*/
+
/* Called on Ctrl-C */
void intHandler(int dummy) {
keepRunning = 0;
+ fprintf(stderr,"\nShutting Down ......\n");
}
/* returns number of output samples generated by resampling */
"Options:\n"
"\t-l --list (audio devices)\n"
"\t-c (comm port for Tx PTT)\n"
+ "\t-t (tx on start up, useful for testing)\n"
"\t-v (verbose)\n", argv[0]);
for(i=0; i<num_opts-1; i++) {
if(long_options[i].has_arg == no_argument) {
*ptxtMsg++ = c;
*ptxtMsg = 0;
ptxtMsg = txtMsg;
- fprintf(stderr, "RX txtMsg: %s\n", txtMsg);
+ if (verbose)
+ fprintf(stderr, " RX txtMsg: %s\n", txtMsg);
if (strstr(txtMsg, triggerString) != NULL) {
triggered = 1;
snr_sample = snr_est;
- fprintf(stderr, "Tx triggered!\n");
+ if (verbose)
+ fprintf(stderr, " Tx triggered!\n");
}
}
}
}
+/*--------------------------------------------------------------------------------------------------------*\
+
+ MAIN
+\*--------------------------------------------------------------------------------------------------------*/
int main(int argc, char *argv[]) {
struct freedv *f;
PaStreamParameters inputParameters, outputParameters;
const PaDeviceInfo *deviceInfo = NULL;
PaStream *stream = NULL;
- int n8k, j, src_error, inputChannels, nin, devNum;
+ int j, src_error, inputChannels, nin, devNum;
int outputChannels;
int state, next_state;
SRC_STATE *rxsrc, *txsrc;
int sfFs;
int fssc;
int triggerf, txfilenamef, callsignf, sampleratef;
- int sync, verbose;
+ int sync;
char commport[MAX_CHAR];
char callsign[MAX_CHAR];
FILE *ftmp;
unsigned int sync_counter, peakCounter;
unsigned int tnout,mnout;
+ short peak;
+ unsigned int logCounter;
/* debug raw file */
verbose = 0;
com_handle = COM_HANDLE_INVALID;
mnout = 60*FS8;
-
+ state = SRX_IDLE;
+ *txtMsg = 0;
+ sfRecFileFromRadio = NULL;
+ sfRecFileDecAudio = NULL;
+
if (Pa_Initialize()) {
fprintf(stderr, "Port Audio failed to initialize");
exit(1);
/* Process command line options -----------------------------------------------------------*/
- char* opt_string = "hlvc";
+ char* opt_string = "hlvc:t";
struct option long_options[] = {
{ "dev", required_argument, &devNum, 1 },
{ "trigger", required_argument, &triggerf, 1 },
case 'c':
strcpy(commport, optarg);
if (openComPort(commport) != 0) {
- fprintf(stderr, "Can't topne comm port: %s\n",commport);
+ fprintf(stderr, "Can't open comm port: %s\n",commport);
exit(1);
}
break;
verbose = 1;
break;
+ case 't':
+ sprintf(txtMsg,"tx Test");
+ state = STX;
+ break;
+
case 'l':
listAudioDevices();
exit(0);
txsrc = src_new(SRC_SINC_FASTEST, 1, &src_error); assert(txsrc != NULL);
playsrc = src_new(SRC_SINC_FASTEST, 1, &src_error); assert(playsrc != NULL);
- /* Open Port Audio device and set up config structures */
+ /* Open Port Audio device and set up config structures -----------------------------------------------------*/
deviceInfo = Pa_GetDeviceInfo(devNum);
if (deviceInfo == NULL) {
exit(1);
}
- fprintf(stderr, "Ctrl-C to exit\n");
- fprintf(stderr, "trigger string: %s txFileName: %s\n", triggerString, txFileName);
- fprintf(stderr, "PortAudio devNum: %d samplerate: %d\n", devNum, fssc);
- signal(SIGINT, intHandler); /* ctrl-C to exit gracefully */
+ /* Init for main loop ----------------------------------------------------------------------------*/
- /* init for main loop */
+ fprintf(stderr, "\nCtrl-C to exit\n");
+ fprintf(stderr, "trigger string: %s\ntxFileName: %s\n", triggerString, txFileName);
+ fprintf(stderr, "PortAudio devNum: %d\nsamplerate: %d\n", devNum, fssc);
- state = SRX_IDLE;
+ signal(SIGINT, intHandler); /* ctrl-C to exit gracefully */
keepRunning = 1;
- *txtMsg = 0;
ptxtMsg = txtMsg;
triggered = 0;
peakCounter = 0;
+ logCounter = 0;
if (com_handle != COM_HANDLE_INVALID) {
lowerRTS(); lowerDTR();
}
+ if (state == STX) {
+ raiseRTS(); raiseDTR();
+ sfPlayFile = openPlayFile(txFileName, &sfFs);
+ }
+
+ /* Main loop -------------------------------------------------------------------------------------*/
while(keepRunning) {
short demod_in[freedv_get_n_max_modem_samples(f)];
short speech_out[freedv_get_n_speech_samples(f)];
- /* Read samples, resample to modem sample rate */
+ /* Read samples froun sound card, resample to modem sample rate */
Pa_ReadStream(stream, stereo, n48);
for(j=0; j<n48; j++)
rx48k[j] = stereo[j];
}
- //printf("%d\n", rx48k[0]);
- //printf("fsm: %d fssc: %d n8m: %d n48: %d\n", fsm, fssc, n8m, n48);
int n8m_out = resample(rxsrc, rxfsm, rx48k, fsm, fssc, n8m, n48);
//fwrite(rxfsm, sizeof(short), n8m, ftmp);
if (verbose) {
- short peak = 0;
+ /* crude input signal level meter */
+ peak = 0;
for (j=0; j<n8m_out; j++)
if (rxfsm[j] > peak)
peak = rxfsm[j];
if (peakCounter++ == PEAK_COUNTER) {
peakCounter = 0;
- fprintf(stderr, "peak: %d\n", peak);
}
}
/* demodulate to decoded speech samples */
nin = freedv_nin(f);
- //printf("n8m_out: %d fifo_used: %d nin: %d\n", n8m_out, fifo_used(fifo), nin);
while (fifo_read(fifo, demod_in, nin) == 0) {
int nout = freedv_rx(f, speech_out, demod_in);
freedv_get_modem_stats(f, &sync, &snr_est);
}
}
+
if (state == STX) {
short mod_out[freedv_get_n_max_modem_samples(f)];
short speech_in[freedv_get_n_speech_samples(f)];
-
- /* TODO: assert PTT, e.g. via RS232 */
if (sfPlayFile != NULL) {
/* resample input sound file as can't guarantee 8KHz sample rate */
unsigned int nsf = freedv_get_n_speech_samples(f)*sfFs/FS8;
short insf[nsf];
unsigned int n = sf_read_short(sfPlayFile, insf, nsf);
- n8k = resample(playsrc, speech_in, insf, FS8, sfFs, freedv_get_n_speech_samples(f), nsf);
+ resample(playsrc, speech_in, insf, FS8, sfFs, freedv_get_n_speech_samples(f), nsf);
//fwrite(speech_in, sizeof(short), freedv_get_n_nom_modem_samples(f), ftmp);
char timeStr[MAX_CHAR];
sprintf(timeStr, "%s",asctime( localtime(<ime) ) );
int i=0;
- unsigned char str[]="a ";
- while (str[i]) {
- if (isspace(str[i]))
- str[i]='_';
+ while (timeStr[i]) {
+ if (isspace(timeStr[i]) || (timeStr[i] == ':'))
+ timeStr[i]='_';
+ else
+ timeStr[i] = tolower(timeStr[i]);
i++;
}
char recFileFromRadioName[MAX_CHAR], recFileDecAudioName[MAX_CHAR];
break;
case SRX_SYNC:
sync_counter++;
- if (((sync_counter % SYNC_FRAMES) == 0) && verbose)
- fprintf(stderr, "sync: %d snr: %3.1f\n", sync, snr_est);
if (!sync) {
sync_counter = 0;
next_state = SRX_MAYBE_UNSYNC;
sprintf(tmpStr, "SNR: %3.1f BER: %4.3f de %s\r",
snr_sample, ber, callsign);
strcpy(txtMsg, tmpStr);
- fprintf(stderr, "TX txtMsg: %s\n", txtMsg);
+ //fprintf(stderr, "TX txtMsg: %s\n", txtMsg);
ptxtMsg = txtMsg;
sfPlayFile = openPlayFile(txFileName, &sfFs);
if (com_handle != COM_HANDLE_INVALID) {
raiseRTS(); raiseDTR();
}
- next_state = STX;
+ next_state = STX;
}
else {
next_state = SRX_IDLE;
if (sfPlayFile == NULL) {
if (com_handle != COM_HANDLE_INVALID) {
- raiseRTS(); raiseDTR();
+ lowerRTS(); lowerDTR();
}
next_state = SRX_IDLE;
}
break;
}
- /* filter out IDLE->MAYBE_SYNC as this fires on channel noise all the time */
+ if (verbose) {
+ if (logCounter++ == LOG_COUNTER) {
+ logCounter = 0;
+ fprintf(stderr, "state: %-20s peak: %6d sync: %d SNR: %3.1f triggered: %d\n",
+ state_str[state], peak, sync, snr_est, triggered);
+ }
+ }
- if ((next_state != state) && verbose)
- fprintf(stderr, "state: %-20s next_state: %-20s\n", state_str[state], state_str[next_state]);
state = next_state;
}
+ /* lower PTT lines */
+
+ if (com_handle != COM_HANDLE_INVALID) {
+ lowerRTS(); lowerDTR();
+ }
+
/* Shut down port audio */
err = Pa_StopStream(stream);
}
-/* Comm port fuctions lifted from FreeDV -------------------------------------------------------------------*/
+/*--------------------------------------------------------------------------------------------------------*\
+
+ Comm port fuctions lifted from FreeDV
+
+\*--------------------------------------------------------------------------------------------------------*/
//----------------------------------------------------------------
// openComPort() opens the com port specified by the string