This network that can be used with any radio and a DTMF transmitter.pull/1/head
parent
f2eb6b3e70
commit
72bdd3376f
@ -0,0 +1,40 @@ |
||||
AM_CPPFLAGS = -Wall -Wextra -g $(all_includes)
|
||||
|
||||
bin_PROGRAMS = \
|
||||
jollycom
|
||||
|
||||
jollycom_SOURCES = \
|
||||
jolly.c \
|
||||
dsp.c \
|
||||
voice.c \
|
||||
main.c
|
||||
jollycom_LDADD = \
|
||||
$(COMMON_LA) \
|
||||
../anetz/libgermanton.a \
|
||||
$(top_builddir)/src/libdebug/libdebug.a \
|
||||
$(top_builddir)/src/libmobile/libmobile.a \
|
||||
$(top_builddir)/src/libdisplay/libdisplay.a \
|
||||
$(top_builddir)/src/libgoertzel/libgoertzel.a \
|
||||
$(top_builddir)/src/libjitter/libjitter.a \
|
||||
$(top_builddir)/src/libsquelch/libsquelch.a \
|
||||
$(top_builddir)/src/libdtmf/libdtmf.a \
|
||||
$(top_builddir)/src/libtimer/libtimer.a \
|
||||
$(top_builddir)/src/libsamplerate/libsamplerate.a \
|
||||
$(top_builddir)/src/libemphasis/libemphasis.a \
|
||||
$(top_builddir)/src/libfilter/libfilter.a \
|
||||
$(top_builddir)/src/libwave/libwave.a \
|
||||
$(top_builddir)/src/libmncc/libmncc.a \
|
||||
$(top_builddir)/src/libsound/libsound.a \
|
||||
$(top_builddir)/src/libsample/libsample.a \
|
||||
$(top_builddir)/src/libfm/libfm.a \
|
||||
$(ALSA_LIBS) \
|
||||
-lm
|
||||
|
||||
if HAVE_SDR |
||||
jollycom_LDADD += \
|
||||
$(top_builddir)/src/libsdr/libsdr.a \
|
||||
$(top_builddir)/src/libfft/libfft.a \
|
||||
$(UHD_LIBS) \
|
||||
$(SOAPY_LIBS)
|
||||
endif |
||||
|
@ -0,0 +1,402 @@ |
||||
/* digital signal processing for jollycom
|
||||
* |
||||
* (C) 2017 by Andreas Eversberg <jolly@eversberg.eu> |
||||
* All Rights Reserved |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, either version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/ |
||||
|
||||
#define CHAN jolly->sender.kanal |
||||
|
||||
#include <stdio.h> |
||||
#include <stdint.h> |
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
#include <errno.h> |
||||
#include <math.h> |
||||
|
||||
#include "../libsample/sample.h" |
||||
#include "../libtimer/timer.h" |
||||
#include "../libdebug/debug.h" |
||||
#include "../libmobile/call.h" |
||||
#include "jolly.h" |
||||
#include "dsp.h" |
||||
#include "voice.h" |
||||
|
||||
#define db2level(db) pow(10, (double)db / 20.0) |
||||
|
||||
/* transceiver parameters */ |
||||
#define MAX_DEVIATION 5000.0 /* deviation of signal */ |
||||
#define MAX_MODULATION 4000.0 /* frequency spectrum of signal */ |
||||
#define DBM0_DEVIATION 3000.0 /* deviation of dBm0 at 1 kHz (generally used with 25 kHz channel spacing) */ |
||||
#define MAX_DISPLAY 1.0 /* maximum level to display */ |
||||
#define TX_INFO_TONE 1.0 /* Level of tone relative to dBm0 (each component) */ |
||||
#define TX_ACK_TONE 0.1 /* Level of tone relative to dBm0 */ |
||||
#define INFO_TONE_F1 640.0 |
||||
#define INFO_TONE_F2 670.0 |
||||
#define ACK_TONE 1000.0 |
||||
|
||||
/* Squelch */ |
||||
#define MUTE_TIME 0.1 /* Time until muting */ |
||||
#define DELAY_TIME 0.15 /* delay, so we don't hear the noise before squelch mutes */ |
||||
#define ACK_TIME 0.15 /* Time to play the ack tone */ |
||||
#define REPEATER_TIME 5.0 /* Time to transmit in repeater mode */ |
||||
|
||||
/* table for fast sine generation */ |
||||
static sample_t dsp_info_tone[65536]; |
||||
static sample_t dsp_ack_tone[65536]; |
||||
|
||||
/* global init for audio processing */ |
||||
void dsp_init(void) |
||||
{ |
||||
int i; |
||||
double s; |
||||
|
||||
PDEBUG(DDSP, DEBUG_DEBUG, "Generating sine tables.\n"); |
||||
for (i = 0; i < 65536; i++) { |
||||
s = sin((double)i / 65536.0 * 2.0 * M_PI); |
||||
dsp_info_tone[i] = s * TX_INFO_TONE; |
||||
dsp_ack_tone[i] = s * TX_ACK_TONE; |
||||
} |
||||
} |
||||
|
||||
/* Init transceiver instance. */ |
||||
int dsp_init_sender(jolly_t *jolly, int nbfm, double squelch_db, int repeater) |
||||
{ |
||||
int rc; |
||||
|
||||
/* init squelch */ |
||||
squelch_init(&jolly->squelch, jolly->sender.kanal, squelch_db, MUTE_TIME, MUTE_TIME); |
||||
if (!isinf(squelch_db)) |
||||
jolly->is_mute = 1; |
||||
|
||||
/* set modulation parameters (NBFM uses half channel spacing, so we use half deviation) */ |
||||
if (nbfm) |
||||
sender_set_fm(&jolly->sender, MAX_DEVIATION / 2.0, MAX_MODULATION, DBM0_DEVIATION / 2.0, MAX_DISPLAY); |
||||
else |
||||
sender_set_fm(&jolly->sender, MAX_DEVIATION, MAX_MODULATION, DBM0_DEVIATION, MAX_DISPLAY); |
||||
|
||||
/* init dtmf audio processing.
|
||||
* each frequency may be +6 dB deviation, which means a total deviation of +12 dB is allowed for detection. |
||||
* also we allow a minimum of -30 dB for each tone. */ |
||||
rc = dtmf_decode_init(&jolly->dtmf, jolly, jolly_receive_dtmf, 8000, db2level(6.0), db2level(-30.0)); |
||||
if (rc < 0) { |
||||
PDEBUG(DDSP, DEBUG_ERROR, "Failed to init DTMF decoder!\n"); |
||||
goto error; |
||||
} |
||||
|
||||
/* tones */ |
||||
jolly->dt_phaseshift65536[0] = 65536.0 / ((double)jolly->sender.samplerate / INFO_TONE_F1); |
||||
jolly->dt_phaseshift65536[1] = 65536.0 / ((double)jolly->sender.samplerate / INFO_TONE_F2); |
||||
jolly->ack_phaseshift65536 = 65536.0 / ((double)jolly->sender.samplerate / ACK_TONE); |
||||
PDEBUG(DDSP, DEBUG_DEBUG, "TX %.0f Hz phaseshift = %.4f\n", INFO_TONE_F1, jolly->dt_phaseshift65536[0]); |
||||
PDEBUG(DDSP, DEBUG_DEBUG, "TX %.0f Hz phaseshift = %.4f\n", INFO_TONE_F2, jolly->dt_phaseshift65536[1]); |
||||
PDEBUG(DDSP, DEBUG_DEBUG, "TX %.0f Hz phaseshift = %.4f\n", ACK_TONE, jolly->ack_phaseshift65536); |
||||
jolly->ack_max = (int)((double)jolly->sender.samplerate * ACK_TIME); |
||||
|
||||
/* delay buffer */ |
||||
jolly->delay_max = (int)((double)jolly->sender.samplerate * DELAY_TIME); |
||||
jolly->delay_spl = calloc(jolly->delay_max, sizeof(*jolly->delay_spl)); |
||||
if (!jolly->delay_spl) { |
||||
PDEBUG(DDSP, DEBUG_ERROR, "No mem for delay buffer!\n"); |
||||
goto error; |
||||
} |
||||
|
||||
/* repeater */ |
||||
jolly->repeater = repeater; |
||||
jolly->repeater_max = (int)((double)jolly->sender.samplerate * REPEATER_TIME); |
||||
rc = jitter_create(&jolly->repeater_dejitter, jolly->sender.samplerate / 5); |
||||
if (rc < 0) { |
||||
PDEBUG(DDSP, DEBUG_ERROR, "Failed to create and init repeater buffer!\n"); |
||||
goto error; |
||||
} |
||||
|
||||
|
||||
jolly->dmp_dtmf_low = display_measurements_add(&jolly->sender, "DTMF Low", "%.1f dB (last)", DISPLAY_MEAS_LAST, DISPLAY_MEAS_LEFT, -30.0, 6.0, 0.0); |
||||
jolly->dmp_dtmf_high = display_measurements_add(&jolly->sender, "DTMF High", "%.1f dB (last)", DISPLAY_MEAS_LAST, DISPLAY_MEAS_LEFT, -30.0, 6.0, 0.0); |
||||
|
||||
return 0; |
||||
|
||||
error: |
||||
dsp_cleanup_sender(jolly); |
||||
return -EINVAL; |
||||
} |
||||
|
||||
void dsp_cleanup_sender(jolly_t *jolly) |
||||
{ |
||||
jitter_destroy(&jolly->repeater_dejitter); |
||||
dtmf_decode_exit(&jolly->dtmf); |
||||
if (jolly->delay_spl) { |
||||
free(jolly->delay_spl); |
||||
jolly->delay_spl = NULL; |
||||
} |
||||
} |
||||
|
||||
void set_speech_string(jolly_t *jolly, char anouncement, const char *number) |
||||
{ |
||||
jolly->speech_string[0] = anouncement; |
||||
jolly->speech_string[1] = '\0'; |
||||
strncat(jolly->speech_string, number, sizeof(jolly->speech_string) - 1); |
||||
jolly->speech_digit = 0; |
||||
jolly->speech_pos = 0; |
||||
} |
||||
|
||||
void reset_speech_string(jolly_t *jolly) |
||||
{ |
||||
jolly->speech_string[0] = '\0'; |
||||
jolly->speech_digit = 0; |
||||
} |
||||
|
||||
/* Generate audio stream from voice samples. */ |
||||
static int speak_voice(jolly_t *jolly, sample_t *samples, int length) |
||||
{ |
||||
sample_t *spl; |
||||
int size; |
||||
int i; |
||||
int count = 0; |
||||
|
||||
again: |
||||
/* no speech */ |
||||
if (!jolly->speech_string[jolly->speech_digit]) |
||||
return count; |
||||
|
||||
/* select sample */ |
||||
switch (jolly->speech_string[jolly->speech_digit]) { |
||||
case 'i': |
||||
spl = jolly_voice.spl[10]; |
||||
size = jolly_voice.size[10]; |
||||
if (!jolly->speech_pos) |
||||
PDEBUG(DDSP, DEBUG_DEBUG, "speaking 'incoming'.\n"); |
||||
break; |
||||
case 'o': |
||||
spl = jolly_voice.spl[11]; |
||||
size = jolly_voice.size[11]; |
||||
if (!jolly->speech_pos) |
||||
PDEBUG(DDSP, DEBUG_DEBUG, "speaking 'outgoing'.\n"); |
||||
break; |
||||
case 'r': |
||||
spl = jolly_voice.spl[12]; |
||||
size = jolly_voice.size[12]; |
||||
if (!jolly->speech_pos) |
||||
PDEBUG(DDSP, DEBUG_DEBUG, "speaking 'released'.\n"); |
||||
break; |
||||
case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': |
||||
spl = jolly_voice.spl[jolly->speech_string[jolly->speech_digit] - '0']; |
||||
size = jolly_voice.size[jolly->speech_string[jolly->speech_digit] - '0']; |
||||
if (!jolly->speech_pos) |
||||
PDEBUG(DDSP, DEBUG_DEBUG, "speaking digit '%c'.\n", jolly->speech_string[jolly->speech_digit]); |
||||
break; |
||||
default: |
||||
jolly->speech_digit++; |
||||
goto again; |
||||
} |
||||
|
||||
/* copy sample */ |
||||
for (; length && jolly->speech_pos < size; i++) { |
||||
*samples++ = spl[jolly->speech_pos++]; |
||||
length--; |
||||
count++; |
||||
} |
||||
|
||||
if (jolly->speech_pos == size) { |
||||
jolly->speech_pos = 0; |
||||
jolly->speech_digit++; |
||||
if (!jolly->speech_string[jolly->speech_digit]) |
||||
speech_finished(jolly); |
||||
goto again; |
||||
} |
||||
|
||||
return count; |
||||
} |
||||
|
||||
static void delay_audio(jolly_t *jolly, sample_t *samples, int count) |
||||
{ |
||||
sample_t *spl, s; |
||||
int pos, max; |
||||
int i; |
||||
|
||||
spl = jolly->delay_spl; |
||||
pos = jolly->delay_pos; |
||||
max = jolly->delay_max; |
||||
|
||||
/* feed audio though delay buffer */ |
||||
for (i = 0; i < count; i++) { |
||||
s = samples[i]; |
||||
samples[i] = spl[pos]; |
||||
spl[pos] = s; |
||||
if (++pos == max) |
||||
pos = 0; |
||||
} |
||||
|
||||
jolly->delay_pos = pos; |
||||
} |
||||
|
||||
/* Generate audio stream from tone. Keep phase for next call of function. */ |
||||
static void dial_tone(jolly_t *jolly, sample_t *samples, int length) |
||||
{ |
||||
double *phaseshift, *phase; |
||||
int i; |
||||
|
||||
phaseshift = jolly->dt_phaseshift65536; |
||||
phase = jolly->dt_phase65536; |
||||
|
||||
for (i = 0; i < length; i++) { |
||||
*samples = dsp_info_tone[(uint16_t)(phase[0])]; |
||||
*samples++ += dsp_info_tone[(uint16_t)(phase[1])]; |
||||
phase[0] += phaseshift[0]; |
||||
if (phase[0] >= 65536) |
||||
phase[0] -= 65536; |
||||
phase[1] += phaseshift[1]; |
||||
if (phase[1] >= 65536) |
||||
phase[1] -= 65536; |
||||
} |
||||
} |
||||
|
||||
static void ack_tone(jolly_t *jolly, sample_t *samples, int length) |
||||
{ |
||||
double phaseshift, phase; |
||||
int i; |
||||
|
||||
phaseshift = jolly->ack_phaseshift65536; |
||||
phase = jolly->ack_phase65536; |
||||
|
||||
for (i = 0; i < length; i++) { |
||||
*samples++ = dsp_ack_tone[(uint16_t)phase]; |
||||
phase += phaseshift; |
||||
if (phase >= 65536) |
||||
phase -= 65536; |
||||
} |
||||
|
||||
jolly->ack_phase65536 = phase; |
||||
} |
||||
|
||||
/* Process received audio stream from radio unit. */ |
||||
void sender_receive(sender_t *sender, sample_t *samples, int length, double rf_level_db) |
||||
{ |
||||
jolly_t *jolly = (jolly_t *) sender; |
||||
sample_t *spl; |
||||
int count; |
||||
int pos; |
||||
int i; |
||||
|
||||
/* process signal mute/loss, also for DTMF tones */ |
||||
switch (squelch(&jolly->squelch, rf_level_db, (double)length / (double)jolly->sender.samplerate)) { |
||||
case SQUELCH_LOSS: |
||||
case SQUELCH_MUTE: |
||||
if (!jolly->is_mute) { |
||||
PDEBUG_CHAN(DDSP, DEBUG_INFO, "Low RF level, muting.\n"); |
||||
jolly->ack_count = jolly->ack_max; |
||||
jolly->repeater_count = jolly->repeater_max; |
||||
} |
||||
jolly->is_mute = 1; |
||||
memset(samples, 0, sizeof(*samples) * length); |
||||
break; |
||||
default: |
||||
if (jolly->is_mute) |
||||
PDEBUG_CHAN(DDSP, DEBUG_INFO, "High RF level, unmuting; turning transmitter on.\n"); |
||||
jolly->is_mute = 0; |
||||
break; |
||||
} |
||||
|
||||
/* delay audio to prevent noise before squelch mutes */ |
||||
delay_audio(jolly, samples, length); |
||||
|
||||
/* play ack tone */ |
||||
if (jolly->ack_count) { |
||||
ack_tone(jolly, samples, length); |
||||
jolly->ack_count -= length; |
||||
if (jolly->ack_count < 0) |
||||
jolly->ack_count = 0; |
||||
} |
||||
|
||||
/* if repeater mode, store sample in jitter buffer */ |
||||
if (jolly->repeater) |
||||
jitter_save(&jolly->repeater_dejitter, samples, length); |
||||
|
||||
/* downsample, decode DTMF */ |
||||
count = samplerate_downsample(&jolly->sender.srstate, samples, length); |
||||
dtmf_decode(&jolly->dtmf, samples, count); |
||||
|
||||
/* Forward audio to network (call process) and feed DTMF decoder. */ |
||||
if (jolly->callref) { |
||||
spl = jolly->sender.rxbuf; |
||||
pos = jolly->sender.rxbuf_pos; |
||||
for (i = 0; i < count; i++) { |
||||
spl[pos++] = samples[i]; |
||||
if (pos == 160) { |
||||
call_up_audio(jolly->callref, spl, 160); |
||||
pos = 0; |
||||
} |
||||
} |
||||
jolly->sender.rxbuf_pos = pos; |
||||
} else |
||||
jolly->sender.rxbuf_pos = 0; |
||||
|
||||
} |
||||
|
||||
/* Provide stream of audio toward radio unit */ |
||||
void sender_send(sender_t *sender, sample_t *samples, uint8_t *power, int length) |
||||
{ |
||||
jolly_t *jolly = (jolly_t *) sender; |
||||
int count; |
||||
|
||||
switch (jolly->state) { |
||||
case STATE_IDLE: |
||||
if (jolly->repeater && (!jolly->is_mute || jolly->ack_count || jolly->repeater_count)) { |
||||
memset(power, 1, length); |
||||
if (jolly->repeater_count) { |
||||
jolly->repeater_count -= length; |
||||
if (jolly->repeater_count < 0) { |
||||
PDEBUG_CHAN(DDSP, DEBUG_INFO, "turning transmitter off.\n"); |
||||
jolly->repeater_count = 0; |
||||
} |
||||
} |
||||
} else { |
||||
/* pwr off */ |
||||
memset(power, 0, length); |
||||
} |
||||
memset(samples, 0, length * sizeof(*samples)); |
||||
break; |
||||
case STATE_CALL: |
||||
case STATE_CALL_DIALING: |
||||
memset(power, 1, length); |
||||
jitter_load(&jolly->sender.dejitter, samples, length); |
||||
break; |
||||
case STATE_OUT_VERIFY: |
||||
case STATE_IN_PAGING: |
||||
case STATE_RELEASED: |
||||
memset(power, 1, length); |
||||
count = speak_voice(jolly, samples, length); |
||||
if (count) { |
||||
/* if voice ends, fill silence */ |
||||
if (count < length) |
||||
memset(samples + count, 0, sizeof(*samples) * (length - count)); |
||||
break; |
||||
} |
||||
/* fall through, in case of no voice */ |
||||
default: |
||||
memset(power, 1, length); |
||||
dial_tone(jolly, samples, length); |
||||
} |
||||
|
||||
/* if repeater mode, sum samples from jitter buffer to samples */ |
||||
if (jolly->repeater) { |
||||
sample_t uplink[length]; |
||||
int i; |
||||
jitter_load(&jolly->repeater_dejitter, uplink, length); |
||||
for (i = 0; i < length; i++) |
||||
samples[i] += uplink[i]; |
||||
} |
||||
} |
||||
|
@ -0,0 +1,7 @@ |
||||
|
||||
void dsp_init(void); |
||||
int dsp_init_sender(jolly_t *jolly, int nbfm, double squelch_db, int repeater); |
||||
void dsp_cleanup_sender(jolly_t *jolly); |
||||
void set_speech_string(jolly_t *jolly, char anouncement, const char *number); |
||||
void reset_speech_string(jolly_t *jolly); |
||||
|
@ -0,0 +1,636 @@ |
||||
/* protocol handling
|
||||
* |
||||
* (C) 2017 by Andreas Eversberg <jolly@eversberg.eu> |
||||
* All Rights Reserved |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, either version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/ |
||||
|
||||
/*
|
||||
* Usage: |
||||
* |
||||
* 1. Dial '*' + <number> + '#' (start with * again to correct false digits). |
||||
* 2. Listen to replied digits, dial again, if they are wrong or incomplete.
|
||||
* 3. Acknowledge number with '#' (within a few seconds) to make the call |
||||
* 4. Acknowledge incoming call with '#' and make the call |
||||
* 5. At any time (also while dialing) dial '*' + '#' to release a call |
||||
* |
||||
* States: |
||||
* |
||||
* IDLE No call, base station is idle |
||||
* OUT-DIALING Outgoing call, user is dialing |
||||
* OUT-VERIFY Outgoing call, digits are repeated |
||||
* CALL Active call |
||||
* CALL-DIALING User is dialing during call |
||||
* IN-PAGING Incomming call, user is paged |
||||
* RELEASED Fixed network released call |
||||
* |
||||
* Timers: |
||||
* |
||||
* T-DIAL Maximum time between digits (several seconds) |
||||
* T-DIAL2 Maximum time between digits during call (close to one second) |
||||
* T-PAGING Time to page user (30 or more seconds) |
||||
* |
||||
* Call events: |
||||
* |
||||
* call setup Make or receive call |
||||
* call release Release call or call has been released |
||||
* call alert Indicate that call is alerting |
||||
* call answer Indicate that the call has been answered |
||||
* |
||||
* State machine: |
||||
* |
||||
* state | event | action |
||||
* -------------+-----------------------+-------------------------------------- |
||||
* IDLE | '*' received | clear dial string |
||||
* | | start timer T-DIAL |
||||
* | | go to state OUT-DIALING |
||||
* | | |
||||
* | call setup | start timer T-PAGE |
||||
* | | start paging sequence |
||||
* | | call alert |
||||
* | | go to state IN-PAGING |
||||
* | | |
||||
* -------------+-----------------------+-------------------------------------- |
||||
* OUT-DIALING | '*' received | clear dial string |
||||
* | | restart timer T-DIAL |
||||
* | | |
||||
* | '0'..'9' received | append digit to dial string |
||||
* | | restart timer T-DIAL |
||||
* | | |
||||
* | '#' received | stop timer |
||||
* | | if empty dial string: |
||||
* | | go to state IDLE |
||||
* | | if dial string: |
||||
* | | go to state OUT-VERIFY |
||||
* | | play dialed digits |
||||
* | | |
||||
* | timeout | go to state IDLE |
||||
* | | |
||||
* -------------+-----------------------+-------------------------------------- |
||||
* OUT-VERIFY | end of playing digits | start timer T-DIAL |
||||
* | | |
||||
* | '*' received | clear dial string |
||||
* | | restart timer T-DIAL |
||||
* | | go to state OUT-DIALING |
||||
* | | |
||||
* | '#' received | stop timer |
||||
* | | call setup |
||||
* | | if call setup fails: |
||||
* | | play release anouncement |
||||
* | | go to state RELEASED |
||||
* | | go to state CALL |
||||
* | | |
||||
* | timeout | go to state IDLE |
||||
* | | |
||||
* -------------+-----------------------+-------------------------------------- |
||||
* CALL | '*' received | start timer T-DIAL2 |
||||
* | | go to state CALL-DIALING |
||||
* | | |
||||
* | call release | play release anouncement |
||||
* | | go to state RELEASED |
||||
* | | |
||||
* -------------+-----------------------+-------------------------------------- |
||||
* CALL-DIALING | '#' received | stop timer |
||||
* | | call release |
||||
* | | play release anouncement |
||||
* | | go to state RELEASED |
||||
* | | |
||||
* | timeout | go state CALL |
||||
* | | |
||||
* | call release | play release anouncement |
||||
* | | go to state RELEASED |
||||
* | | |
||||
* -------------+-----------------------+-------------------------------------- |
||||
* IN-PAGING | '#' received | call answer |
||||
* | | |
||||
* | timeout | call release |
||||
* | | go to state IDLE |
||||
* | | |
||||
* | call release | go to state IDLE |
||||
* | | |
||||
* -------------+-----------------------+-------------------------------------- |
||||
* RELEASED | end of anouncement | go to state IDLE |
||||
* | | |
||||
*/ |
||||
|
||||
#define CHAN jolly->sender.kanal |
||||
|
||||
#include <stdio.h> |
||||
#include <stdint.h> |
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
#include <errno.h> |
||||
#include <math.h> |
||||
#include "../libsample/sample.h" |
||||
#include "../libdebug/debug.h" |
||||
#include "../libtimer/timer.h" |
||||
#include "../libmobile/call.h" |
||||
#include "../libmncc/cause.h" |
||||
#include "jolly.h" |
||||
#include "dsp.h" |
||||
#include "voice.h" |
||||
|
||||
/* Call reference for calls from mobile station to network
|
||||
This offset of 0x400000000 is required for MNCC interface. */ |
||||
static int new_callref = 0x40000000; |
||||
|
||||
#define db2level(db) pow(10, (double)db / 20.0) |
||||
|
||||
/* Timers */ |
||||
#define T_DIAL 6 /* Time between digits */ |
||||
#define T_DIAL2 1.5 /* Time between digits during call*/ |
||||
#define T_PAGING 30 /* How long do we page the mobile party */ |
||||
#define SPEECH_DELAY_PAGING 1.0 /* time before speaking paging sequence */ |
||||
#define SPEECH_DELAY_VERIFY 2.0 /* time before speaking verifying sequence */ |
||||
#define SPEECH_DELAY_RELEASE 2.0/* time before speaking release sequence */ |
||||
|
||||
const char *jolly_state_name(enum jolly_state state) |
||||
{ |
||||
static char invalid[16]; |
||||
|
||||
switch (state) { |
||||
case STATE_NULL: |
||||
return "(NULL)"; |
||||
case STATE_IDLE: |
||||
return "IDLE"; |
||||
case STATE_OUT_DIALING: |
||||
return "OUT-DIALING"; |
||||
case STATE_OUT_VERIFY: |
||||
return "OUT-VERIFY"; |
||||
case STATE_CALL: |
||||
return "CALL"; |
||||
case STATE_CALL_DIALING: |
||||
return "CALL-DIALING"; |
||||
case STATE_IN_PAGING: |
||||
return "IN-PAGING"; |
||||
case STATE_RELEASED: |
||||
return "RELEASED"; |
||||
} |
||||
|
||||
sprintf(invalid, "invalid(%d)", state); |
||||
return invalid; |
||||
} |
||||
|
||||
void jolly_display_status(void) |
||||
{ |
||||
sender_t *sender; |
||||
jolly_t *jolly; |
||||
|
||||
display_status_start(); |
||||
for (sender = sender_head; sender; sender = sender->next) { |
||||
jolly = (jolly_t *) sender; |
||||
display_status_channel(jolly->sender.kanal, NULL, jolly_state_name(jolly->state)); |
||||
if (jolly->station_id[0]) |
||||
display_status_subscriber(jolly->station_id, NULL); |
||||
} |
||||
display_status_end(); |
||||
} |
||||
|
||||
static void jolly_new_state(jolly_t *jolly, enum jolly_state new_state) |
||||
{ |
||||
if (jolly->state == new_state) |
||||
return; |
||||
PDEBUG_CHAN(DJOLLY, DEBUG_DEBUG, "State change: %s -> %s\n", jolly_state_name(jolly->state), jolly_state_name(new_state)); |
||||
jolly->state = new_state; |
||||
jolly_display_status(); |
||||
} |
||||
|
||||
static void jolly_timeout(struct timer *timer); |
||||
static void jolly_speech_timeout(struct timer *timer); |
||||
static void jolly_go_idle(jolly_t *jolly); |
||||
|
||||
/* Create transceiver instance and link to a list. */ |
||||
int jolly_create(int kanal, double dl_freq, double ul_freq, double step, const char *audiodev, int use_sdr, int samplerate, double rx_gain, int pre_emphasis, int de_emphasis, const char *write_rx_wave, const char *write_tx_wave, const char *read_rx_wave, const char *read_tx_wave, int loopback, double squelch_db, int nbfm, int repeater) |
||||
{ |
||||
jolly_t *jolly; |
||||
int rc; |
||||
|
||||
jolly = calloc(1, sizeof(jolly_t)); |
||||
if (!jolly) { |
||||
PDEBUG(DJOLLY, DEBUG_ERROR, "No memory!\n"); |
||||
return -EIO; |
||||
} |
||||
|
||||
PDEBUG(DJOLLY, DEBUG_DEBUG, "Creating 'JollyCom' instance for 'Kanal' = %d (sample rate %d).\n", kanal, samplerate); |
||||
|
||||
dl_freq = dl_freq * 1e6 + step * 1e3 * (double)kanal; |
||||
ul_freq = ul_freq * 1e6 + step * 1e3 * (double)kanal; |
||||
|
||||
/* init general part of transceiver */ |
||||
rc = sender_create(&jolly->sender, kanal, dl_freq, ul_freq, audiodev, use_sdr, samplerate, rx_gain, pre_emphasis, de_emphasis, write_rx_wave, write_tx_wave, read_rx_wave, read_tx_wave, loopback, PAGING_SIGNAL_NONE); |
||||
if (rc < 0) { |
||||
PDEBUG(DJOLLY, DEBUG_ERROR, "Failed to init 'Sender' processing!\n"); |
||||
goto error; |
||||
} |
||||
|
||||
/* init audio processing */ |
||||
rc = dsp_init_sender(jolly, nbfm, squelch_db, repeater); |
||||
if (rc < 0) { |
||||
PDEBUG(DANETZ, DEBUG_ERROR, "Failed to init signal processing!\n"); |
||||
goto error; |
||||
} |
||||
|
||||
/* timers */ |
||||
timer_init(&jolly->timer, jolly_timeout, jolly); |
||||
timer_init(&jolly->speech_timer, jolly_speech_timeout, jolly); |
||||
|
||||
/* go into idle state */ |
||||
jolly_go_idle(jolly); |
||||
|
||||
PDEBUG(DJOLLY, DEBUG_NOTICE, "Created 'Kanal' #%d\n", kanal); |
||||
|
||||
return 0; |
||||
|
||||
error: |
||||
jolly_destroy(&jolly->sender); |
||||
|
||||
return rc; |
||||
} |
||||
|
||||
/* Destroy transceiver instance and unlink from list. */ |
||||
void jolly_destroy(sender_t *sender) |
||||
{ |
||||
jolly_t *jolly = (jolly_t *) sender; |
||||
|
||||
PDEBUG(DJOLLY, DEBUG_DEBUG, "Destroying 'JollyCom' instance for 'Kanal' = %d.\n", sender->kanal); |
||||
|
||||
dsp_cleanup_sender(jolly); |
||||
timer_exit(&jolly->timer); |
||||
timer_exit(&jolly->speech_timer); |
||||
sender_destroy(&jolly->sender); |
||||
free(sender); |
||||
} |
||||
|
||||
/* Abort connection towards mobile station changing to IDLE state */ |
||||
static void jolly_go_idle(jolly_t *jolly) |
||||
{ |
||||
timer_stop(&jolly->timer); |
||||
timer_stop(&jolly->speech_timer); |
||||
reset_speech_string(jolly); |
||||
|
||||
PDEBUG(DJOLLY, DEBUG_INFO, "Entering IDLE state on channel %d.\n", jolly->sender.kanal); |
||||
jolly->dialing[0] = '\0'; |
||||
jolly->station_id[0] = '\0'; /* remove station ID before state change, so status is shown correctly */ |
||||
jolly_new_state(jolly, STATE_IDLE); |
||||
} |
||||
|
||||
/* Release connection towards mobile station by sending idle tone for a while. */ |
||||
static void jolly_release(jolly_t *jolly) |
||||
{ |
||||
timer_stop(&jolly->timer); |
||||
timer_stop(&jolly->speech_timer); |
||||
reset_speech_string(jolly); |
||||
|
||||
PDEBUG(DJOLLY, DEBUG_INFO, "Sending Release sequence on channel %d.\n", jolly->sender.kanal); |
||||
timer_start(&jolly->speech_timer, SPEECH_DELAY_RELEASE); |
||||
jolly_new_state(jolly, STATE_RELEASED); |
||||
} |
||||
|
||||
/* Enter paging state and transmit 4 paging tones. */ |
||||
static void jolly_page(jolly_t *jolly, const char *dial_string) |
||||
{ |
||||
PDEBUG_CHAN(DJOLLY, DEBUG_INFO, "Entering paging state, sending paging sequence to '%s'.\n", dial_string); |
||||
/* set station ID before state change, so status is shown correctly */ |
||||
strncpy(jolly->station_id, dial_string, sizeof(jolly->station_id) - 1); |
||||
timer_start(&jolly->timer, T_PAGING); |
||||
timer_start(&jolly->speech_timer, SPEECH_DELAY_PAGING); |
||||
jolly_new_state(jolly, STATE_IN_PAGING); |
||||
} |
||||
|
||||
void speech_finished(jolly_t *jolly) |
||||
{ |
||||
PDEBUG(DJOLLY, DEBUG_DEBUG, "speaking finished.\n"); |
||||
switch (jolly->state) { |
||||
case STATE_OUT_VERIFY: |
||||
timer_start(&jolly->timer, T_DIAL); |
||||
break; |
||||
case STATE_IN_PAGING: |
||||
timer_start(&jolly->speech_timer, SPEECH_DELAY_PAGING); |
||||
break; |
||||
case STATE_RELEASED: |
||||
jolly_go_idle(jolly); |
||||
break; |
||||
default: |
||||
break; |
||||
} |
||||
} |
||||
|
||||
#define level2db(level) (20 * log10(level)) |
||||
|
||||
/* A DTMF digit was received */ |
||||
void jolly_receive_dtmf(void *priv, char digit, dtmf_meas_t *meas) |
||||
{ |
||||
jolly_t *jolly = (jolly_t *) priv; |
||||
|
||||
PDEBUG_CHAN(DJOLLY, DEBUG_INFO, "Received dtmf digit '%c' frequency %.1f %.1f amplitude %.1f %.1f dB.\n", |
||||
digit, |
||||
meas->frequency_low, meas->frequency_high, |
||||
level2db(meas->amplitude_low), level2db(meas->amplitude_high)); |
||||
|
||||
/* update measurements */ |
||||
display_measurements_update(jolly->dmp_dtmf_low, level2db(meas->amplitude_low), 0.0); |
||||
display_measurements_update(jolly->dmp_dtmf_high, level2db(meas->amplitude_high), 0.0); |
||||
|
||||
switch (jolly->state) { |
||||
case STATE_IDLE: |
||||
if (digit == '*') { |
||||
PDEBUG_CHAN(DJOLLY, DEBUG_INFO, "Received start digit, entering dialing state.\n"); |
||||
jolly->dialing[0] = '\0'; |
||||
timer_start(&jolly->timer, T_DIAL); |
||||
jolly_new_state(jolly, STATE_OUT_DIALING); |
||||
break; |
||||
} |
||||
break; |
||||
case STATE_OUT_DIALING: |
||||
if (digit == '*') { |
||||
PDEBUG_CHAN(DJOLLY, DEBUG_INFO, "Received start digit again, resetting dialing state.\n"); |
||||
jolly->dialing[0] = '\0'; |
||||
timer_start(&jolly->timer, T_DIAL); |
||||
break; |
||||
} |
||||
if (digit >= '0' && digit <= '9' && strlen(jolly->dialing) < sizeof(jolly->dialing) - 1) { |
||||
PDEBUG_CHAN(DJOLLY, DEBUG_INFO, "Received dialed digit '%c'\n", digit); |
||||
jolly->dialing[strlen(jolly->dialing) + 1] = '\0'; |
||||
jolly->dialing[strlen(jolly->dialing)] = digit; |
||||
timer_start(&jolly->timer, T_DIAL); |
||||
break; |
||||
} |
||||
if (digit == '#') { |
||||
timer_stop(&jolly->timer); |
||||
if (!jolly->dialing[0]) { |
||||
PDEBUG_CHAN(DJOLLY, DEBUG_INFO, "Received stop digit but no dial string, entering idle state.\n"); |
||||
jolly_go_idle(jolly); |
||||
break; |
||||
} |
||||
PDEBUG_CHAN(DJOLLY, DEBUG_INFO, "Received stop digit, entering verify state.\n"); |
||||
timer_start(&jolly->speech_timer, SPEECH_DELAY_VERIFY); |
||||
jolly_new_state(jolly, STATE_OUT_VERIFY); |
||||
break; |
||||
} |
||||
break; |
||||
case STATE_OUT_VERIFY: |
||||
if (digit == '*') { |
||||
PDEBUG_CHAN(DJOLLY, DEBUG_INFO, "Received start digit, entering dialing state.\n"); |
||||
reset_speech_string(jolly); |
||||
jolly->dialing[0] = '\0'; |
||||
timer_start(&jolly->timer, T_DIAL); |
||||
jolly_new_state(jolly, STATE_OUT_DIALING); |
||||
break; |
||||
} |
||||
if (digit == '#') { |
||||
int callref = ++new_callref; |
||||
int rc; |
||||
|
||||
PDEBUG_CHAN(DJOLLY, DEBUG_INFO, "Received ack digit, entering call state.\n"); |
||||
timer_stop(&jolly->timer); |
||||
rc = call_up_setup(callref, NULL, jolly->dialing); |
||||
if (rc < 0) { |
||||
PDEBUG_CHAN(DJOLLY, DEBUG_NOTICE, "Call rejected (cause %d), going idle.\n", -rc); |
||||
jolly_release(jolly); |
||||
break; |
||||
} |
||||
jolly->callref = callref; |
||||
jolly_new_state(jolly, STATE_CALL); |
||||
} |
||||
break; |
||||
case STATE_CALL: |
||||
if (digit == '*') { |
||||
PDEBUG_CHAN(DJOLLY, DEBUG_INFO, "Received start digit, entering call dialing state.\n"); |
||||
jolly->dialing[0] = '\0'; |
||||
timer_start(&jolly->timer, T_DIAL2); |
||||
jolly_new_state(jolly, STATE_CALL_DIALING); |
||||
break; |
||||
} |
||||
break; |
||||
case STATE_CALL_DIALING: |
||||
if (digit == '#') { |
||||
PDEBUG_CHAN(DJOLLY, DEBUG_INFO, "Received stop digit, going idle.\n"); |
||||
call_up_release(jolly->callref, CAUSE_NORMAL); |
||||
jolly->callref = 0; |
||||
jolly_release(jolly); |
||||
break; |
||||
} |
||||
break; |
||||
case STATE_IN_PAGING: |
||||
if (digit == '#') { |
||||
PDEBUG_CHAN(DJOLLY, DEBUG_INFO, "Received answer digit, entering call state.\n"); |
||||
call_up_answer(jolly->callref, jolly->station_id); |
||||
jolly_new_state(jolly, STATE_CALL); |
||||
break; |
||||
} |
||||
break; |
||||
default: |
||||
break; |
||||
} |
||||
} |
||||
|
||||
/* Timeout handling */ |
||||
static void jolly_timeout(struct timer *timer) |
||||
{ |
||||
jolly_t *jolly = (jolly_t *)timer->priv; |
||||
|
||||
switch (jolly->state) { |
||||
case STATE_OUT_DIALING: |
||||
PDEBUG_CHAN(DJOLLY, DEBUG_NOTICE, "Timeout while dialing, going idle.\n"); |
||||
jolly_go_idle(jolly); |
||||
break; |
||||
case STATE_OUT_VERIFY: |
||||
PDEBUG_CHAN(DJOLLY, DEBUG_NOTICE, "Timeout while verifying, going idle.\n"); |
||||
jolly_go_idle(jolly); |
||||
break; |
||||
case STATE_CALL_DIALING: |
||||
PDEBUG_CHAN(DJOLLY, DEBUG_NOTICE, "Timeout while dialing during call.\n"); |
||||
jolly_new_state(jolly, STATE_CALL); |
||||
break; |
||||
case STATE_IN_PAGING: |
||||
PDEBUG_CHAN(DJOLLY, DEBUG_NOTICE, "Timeout while paging, going idle.\n"); |
||||
call_up_release(jolly->callref, CAUSE_NOANSWER); |
||||
jolly->callref = 0; |
||||
jolly_go_idle(jolly); |
||||
break; |
||||
default: |
||||
break; |
||||
} |
||||
} |
||||
|
||||
static void jolly_speech_timeout(struct timer *timer) |
||||
{ |
||||
jolly_t *jolly = (jolly_t *)timer->priv; |
||||
|
||||
switch (jolly->state) { |
||||
case STATE_OUT_VERIFY: |
||||
PDEBUG_CHAN(DJOLLY, DEBUG_DEBUG, "Start verifying speech.\n"); |
||||
set_speech_string(jolly, 'o', jolly->dialing); |
||||
break; |
||||
case STATE_IN_PAGING: |
||||
PDEBUG_CHAN(DJOLLY, DEBUG_DEBUG, "Start paging speech.\n"); |
||||
set_speech_string(jolly, 'i', jolly->station_id); |
||||
break; |
||||
case STATE_RELEASED: |
||||
PDEBUG_CHAN(DJOLLY, DEBUG_DEBUG, "Start release speech.\n"); |
||||
set_speech_string(jolly, 'r', ""); |
||||
default: |
||||
break; |
||||
} |
||||
} |
||||
|
||||
/* Call control starts call towards mobile station. */ |
||||
int call_down_setup(int callref, const char __attribute__((unused)) *caller_id, enum number_type __attribute__((unused)) caller_type, const char *dialing) |
||||
{ |
||||
sender_t *sender; |
||||
jolly_t *jolly; |
||||
|
||||
/* 1. check if number is invalid, return INVALNUMBER */ |
||||
if (strlen(dialing) == 0) { |
||||
PDEBUG(DJOLLY, DEBUG_NOTICE, "Outgoing call to invalid number '%s', rejecting!\n", dialing); |
||||
return -CAUSE_INVALNUMBER; |
||||
} |
||||
|
||||
/* 2. check if given number is already in a call, return BUSY */ |
||||
for (sender = sender_head; sender; sender = sender->next) { |
||||
jolly = (jolly_t *) sender; |
||||
if (!strcmp(jolly->station_id, dialing)) |
||||
break; |
||||
} |
||||
if (sender) { |
||||
PDEBUG(DJOLLY, DEBUG_NOTICE, "Outgoing call to busy number, rejecting!\n"); |
||||
return -CAUSE_BUSY; |
||||
} |
||||
|
||||
/* 3. check if all senders are busy, return NOCHANNEL */ |
||||
for (sender = sender_head; sender; sender = sender->next) { |
||||
jolly = (jolly_t *) sender; |
||||
if (jolly->state == STATE_IDLE) |
||||
break; |
||||
} |
||||
if (!sender) { |
||||
PDEBUG(DJOLLY, DEBUG_NOTICE, "Outgoing call, but no free channel, rejecting!\n"); |
||||
return -CAUSE_NOCHANNEL; |
||||
} |
||||
|
||||
PDEBUG_CHAN(DJOLLY, DEBUG_INFO, "Call to mobile station.\n"); |
||||
|
||||
/* 4. trying to page mobile station */ |
||||
jolly->callref = callref; |
||||
jolly_page(jolly, dialing); |
||||
|
||||
call_up_alerting(callref); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
void call_down_answer(int __attribute__((unused)) callref) |
||||
{ |
||||
} |
||||
|
||||
/* Call control sends disconnect (with tones).
|
||||
* An active call stays active, so tones and annoucements can be received |
||||
* by mobile station. |
||||
*/ |
||||
void call_down_disconnect(int callref, int cause) |
||||
{ |
||||
sender_t *sender; |
||||
jolly_t *jolly; |
||||
|
||||
PDEBUG(DJOLLY, DEBUG_INFO, "Call has been disconnected by network.\n"); |
||||
|
||||
for (sender = sender_head; sender; sender = sender->next) { |
||||
jolly = (jolly_t *) sender; |
||||
if (jolly->callref == callref) |
||||
break; |
||||
} |
||||
if (!sender) { |
||||
PDEBUG(DJOLLY, DEBUG_NOTICE, "Outgoing disconnect, but no callref!\n"); |
||||
call_up_release(callref, CAUSE_INVALCALLREF); |
||||
return; |
||||
} |
||||
|
||||
/* Release when not active */ |
||||
if (jolly->state == STATE_CALL || jolly->state == STATE_CALL_DIALING) |
||||
return; |
||||
switch (jolly->state) { |
||||
case STATE_IN_PAGING: |
||||
PDEBUG_CHAN(DJOLLY, DEBUG_NOTICE, "Outgoing disconnect, during paging, releaseing.\n"); |
||||
jolly_go_idle(jolly); |
||||
break; |
||||
default: |
||||
break; |
||||
} |
||||
|
||||
call_up_release(callref, cause); |
||||
|
||||
jolly->callref = 0; |
||||
|
||||
} |
||||
|
||||
/* Call control releases call toward mobile station. */ |
||||
void call_down_release(int callref, __attribute__((unused)) int cause) |
||||
{ |
||||
sender_t *sender; |
||||
jolly_t *jolly; |
||||
|
||||
PDEBUG(DJOLLY, DEBUG_INFO, "Call has been released by network, releasing call.\n"); |
||||
|
||||
for (sender = sender_head; sender; sender = sender->next) { |
||||
jolly = (jolly_t *) sender; |
||||
if (jolly->callref == callref) |
||||
break; |
||||
} |
||||
if (!sender) { |
||||
PDEBUG(DJOLLY, DEBUG_NOTICE, "Outgoing release, but no callref!\n"); |
||||
/* don't send release, because caller already released */ |
||||
return; |
||||
} |
||||
|
||||
jolly->callref = 0; |
||||
|
||||
switch (jolly->state) { |
||||
case STATE_CALL: |
||||
case STATE_CALL_DIALING: |
||||
PDEBUG_CHAN(DJOLLY, DEBUG_NOTICE, "Outgoing release, during call, releasing\n"); |
||||
jolly_release(jolly); |
||||
break; |
||||
case STATE_IN_PAGING: |
||||
PDEBUG_CHAN(DJOLLY, DEBUG_NOTICE, "Outgoing release, during paging, releaseing.\n"); |
||||
jolly_go_idle(jolly); |
||||
break; |
||||
default: |
||||
break; |
||||
} |
||||
} |
||||
|
||||
/* Receive audio from call instance. */ |
||||
void call_down_audio(int callref, sample_t *samples, int count) |
||||
{ |
||||
sender_t *sender; |
||||
jolly_t *jolly; |
||||
|
||||
for (sender = sender_head; sender; sender = sender->next) { |
||||
jolly = (jolly_t *) sender; |
||||
if (jolly->callref == callref) |
||||
break; |
||||
} |
||||
if (!sender) |
||||
return; |
||||
|
||||
if (jolly->state == STATE_CALL || jolly->state == STATE_CALL_DIALING) { |
||||
sample_t up[(int)((double)count * jolly->sender.srstate.factor + 0.5) + 10]; |
||||
count = samplerate_upsample(&jolly->sender.srstate, samples, count, up); |
||||
jitter_save(&jolly->sender.dejitter, up, count); |
||||
} |
||||
} |
||||
|
||||
void dump_info(void) {} |
||||
|
@ -0,0 +1,56 @@ |
||||
#include "../libsquelch/squelch.h" |
||||
#include "../libmobile/sender.h" |
||||
#include "../libdtmf/dtmf_decode.h" |
||||
|
||||
enum jolly_state { |
||||
STATE_NULL = 0, |
||||
STATE_IDLE, |
||||
STATE_OUT_DIALING, |
||||
STATE_OUT_VERIFY, |
||||
STATE_CALL, |
||||
STATE_CALL_DIALING, |
||||
STATE_IN_PAGING, |
||||
STATE_RELEASED, |
||||
}; |
||||
|
||||
typedef struct jolly { |
||||
sender_t sender; |
||||
|
||||
/* sender's states */ |
||||
enum jolly_state state; /* current sender's state */ |
||||
int callref; /* call reference */ |
||||
char station_id[32]; /* current station ID */ |
||||
char dialing[32]; /* dial string */ |
||||
struct timer timer; |
||||
|
||||
/* display measurements */ |
||||
dispmeasparam_t *dmp_dtmf_low; |
||||
dispmeasparam_t *dmp_dtmf_high; |
||||
|
||||
/* dsp states */ |
||||
int repeater; /* mix audio of RX signal to TX signal */ |
||||
jitter_t repeater_dejitter; /* forwarding audio */ |
||||
int repeater_count; /* counter to count down repeater's "transmitter on" */ |
||||
int repeater_max; /* duration in samples */ |
||||
squelch_t squelch; /* squelch detection process */ |
||||
int is_mute; /* set if quelch has muted */ |
||||
dtmf_dec_t dtmf; /* dtmf decoder */ |
||||
double dt_phaseshift65536[2]; /* how much the phase of sine wave changes per sample */ |
||||
double dt_phase65536[2]; /* current phase */ |
||||
double ack_phaseshift65536; /* how much the phase of sine wave changes per sample */ |
||||
double ack_phase65536; /* current phase */ |
||||
int ack_count; /* counter to count down while playing ack tone */ |
||||
int ack_max; /* duration in samples */ |
||||
struct timer speech_timer; |
||||
char speech_string[40]; /* speech string */ |
||||
int speech_digit; /* counts digits */ |
||||
int speech_pos; /* counts samples */ |
||||
sample_t *delay_spl; /* delay buffer for delaying audio */ |
||||
int delay_pos; /* position in delay buffer */ |
||||
int delay_max; /* number of samples in delay buffer */ |
||||
} jolly_t; |
||||
|
||||
int jolly_create(int kanal, double dl_freq, double ul_freq, double step, const char *audiodev, int use_sdr, int samplerate, double rx_gain, int pre_emphasis, int de_emphasis, const char *write_rx_wave, const char *write_tx_wave, const char *read_rx_wave, const char *read_tx_wave, int loopback, double squelch_db, int nbfm, int repeater); |
||||
void jolly_destroy(sender_t *sender); |
||||
void speech_finished(jolly_t *jolly); |
||||
void jolly_receive_dtmf(void *priv, char digit, dtmf_meas_t *meas); |
@ -0,0 +1,219 @@ |
||||
/* main
|
||||
* |
||||
* (C) 2017 by Andreas Eversberg <jolly@eversberg.eu> |
||||
* All Rights Reserved |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, either version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/ |
||||
|
||||
#include <stdio.h> |
||||
#include <stdint.h> |
||||
#include <getopt.h> |
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
#include <fcntl.h> |
||||
#include <unistd.h> |
||||
#include <math.h> |
||||
#include <sys/types.h> |
||||
#include <sys/stat.h> |
||||
#include "../libsample/sample.h" |
||||
#include "../libmobile/main_mobile.h" |
||||
#include "../libdebug/debug.h" |
||||
#include "../libtimer/timer.h" |
||||
#include "../libmncc/mncc_sock.h" |
||||
#include "../anetz/freiton.h" |
||||
#include "../anetz/besetztton.h" |
||||
#include "jolly.h" |
||||
#include "dsp.h" |
||||
#include "voice.h" |
||||
|
||||
/* settings */ |
||||
int num_freq = 0; |
||||
double dl_freq = 438.600; |
||||
double ul_freq = 431.000; |
||||
double step = 25.0; |
||||
static double squelch_db = -INFINITY; |
||||
int nbfm = 0; |
||||
int repeater = 0; |
||||
|
||||
void print_help(const char *arg0) |
||||
{ |
||||
main_mobile_print_help(arg0, "[-F <downlink MHz>,<uplink MHz>] "); |
||||
/* - - */ |
||||
printf(" -F --frequency <downlink MHz>,<uplink MHz>,<step KHz>\n"); |
||||
printf(" Downlink and uplink frequency to use for channel 0.\n"); |
||||
printf(" Give step for channel spacing. (default = %.3f,%.3f,%.4f)\n", dl_freq, ul_freq, step); |
||||
printf(" -S --squelch <dB> | auto\n"); |
||||
printf(" Use given RF level to detect loss of signal. When the signal gets lost\n"); |
||||
printf(" and stays below this level, the connection is released.\n"); |
||||
printf(" Use 'auto' to do automatic noise floor calibration to detect loss.\n"); |
||||
printf(" Only works with SDR! (disabled by default)\n"); |
||||
printf(" -N --nbfm\n"); |
||||
printf(" Use Narrow band FM with deviation of 2.5 KHz instead of 5.0 KHz.\n"); |
||||
printf(" -R --relay\n"); |
||||
printf(" Use transceiver as repeater, so multiple radios can communicate with\n"); |
||||
printf(" each other. It is still possible to make and receive calls. Multiple\n"); |
||||
printf(" radios can talk then to the calling/called party.\n"); |
||||
printf("\nstation-id: Give 4 digits of station-id, you don't need to enter it\n"); |
||||
printf(" for every start of this program.\n"); |
||||
main_mobile_print_hotkeys(); |
||||
} |
||||
|
||||
static int handle_options(int argc, char **argv) |
||||
{ |
||||
int skip_args = 0; |
||||
|
||||
static struct option long_options_special[] = { |
||||
{"frequency", 1, 0, 'F'}, |
||||
{"squelch", 1, 0, 'S'}, |
||||
{"nbfm", 0, 0, 'N'}, |
||||
{"repeater", 0, 0, 'R'}, |
||||
{0, 0, 0, 0} |
||||
}; |
||||
|
||||
main_mobile_set_options("F:S:NR", long_options_special); |
||||
|
||||
while (1) { |
||||
int option_index = 0, c; |
||||
char *string, *string_dl, *string_ul, *string_step; |
||||
|
||||
c = getopt_long(argc, argv, optstring, long_options, &option_index); |
||||
|
||||
if (c == -1) |
||||
break; |
||||
|
||||
switch (c) { |
||||
case 'F': |
||||
string = strdup(optarg); |
||||
string_dl = strsep(&string, ","); |
||||
string_ul = strsep(&string, ","); |
||||
string_step = strsep(&string, ","); |
||||
if (!string_dl || !string_ul || !string_step) { |
||||
fprintf(stderr, "Please give 3 values for --frequency, seperated by comma and no space!\n"); |
||||
exit(0); |
||||
} |
||||
dl_freq = atof(string_dl); |
||||
ul_freq = atof(string_ul); |
||||
step = atof(string_step); |
||||
skip_args += 2; |
||||
break; |
||||
case 'S': |
||||
if (!strcasecmp(optarg, "auto")) |
||||
squelch_db = 0.0; |
||||
else |
||||
squelch_db = atof(optarg); |
||||
skip_args += 2; |
||||
break; |
||||
case 'N': |
||||
nbfm = 1; |
||||
skip_args += 1; |
||||
break; |
||||
case 'R': |
||||
repeater = 1; |
||||
skip_args += 1; |
||||
break; |
||||
default: |
||||
main_mobile_opt_switch(c, argv[0], &skip_args); |
||||
} |
||||
} |
||||
|
||||
free(long_options); |
||||
|
||||
return skip_args; |
||||
} |
||||
|
||||
int main(int argc, char *argv[]) |
||||
{ |
||||
int rc; |
||||
int skip_args; |
||||
const char *station_id = ""; |
||||
int mandatory = 0; |
||||
int i; |
||||
|
||||
/* init tones */ |
||||
init_freiton(); |
||||
init_besetzton(); |
||||
// init_ansage();
|
||||
|
||||
main_mobile_init(); |
||||
|
||||
skip_args = handle_options(argc, argv); |
||||
argc -= skip_args; |
||||
argv += skip_args; |
||||
|
||||
if (argc > 1) { |
||||
station_id = argv[1]; |
||||
if (strlen(station_id) != 4) { |
||||
printf("Given station ID '%s' does not have 4 digits\n", station_id); |
||||
return 0; |
||||
} |
||||
} |
||||
|
||||
if (!num_kanal) { |
||||
printf("No channel (\"Kanal\") is specified, Please use channel 0.\n\n"); |
||||
mandatory = 1; |
||||
} |
||||
if (use_sdr) { |
||||
/* set audiodev */ |
||||
for (i = 0; i < num_kanal; i++) |
||||
audiodev[i] = "sdr"; |
||||
num_audiodev = num_kanal; |
||||
} |
||||
if (num_kanal == 1 && num_audiodev == 0) |
||||
num_audiodev = 1; /* use default */ |
||||
if (num_kanal != num_audiodev) { |
||||
fprintf(stderr, "You need to specify as many sound devices as you have channels.\n"); |
||||
exit(0); |
||||
} |
||||