parent
4af2dca10d
commit
90c47658c1
@ -0,0 +1,53 @@ |
||||
AM_CPPFLAGS = -Wall -Wextra -g $(all_includes)
|
||||
|
||||
bin_PROGRAMS = \
|
||||
eurosignal
|
||||
|
||||
eurosignal_SOURCES = \
|
||||
eurosignal.c \
|
||||
dsp.c \
|
||||
image.c \
|
||||
es_mitte.c \
|
||||
es_ges.c \
|
||||
es_teilges.c \
|
||||
es_kaudn.c \
|
||||
main.c
|
||||
eurosignal_LDADD = \
|
||||
$(COMMON_LA) \
|
||||
../anetz/libgermanton.a \
|
||||
$(top_builddir)/src/liboptions/liboptions.a \
|
||||
$(top_builddir)/src/libdebug/libdebug.a \
|
||||
$(top_builddir)/src/libmobile/libmobile.a \
|
||||
$(top_builddir)/src/libdisplay/libdisplay.a \
|
||||
$(top_builddir)/src/libjitter/libjitter.a \
|
||||
$(top_builddir)/src/libtimer/libtimer.a \
|
||||
$(top_builddir)/src/libsamplerate/libsamplerate.a \
|
||||
$(top_builddir)/src/libemphasis/libemphasis.a \
|
||||
$(top_builddir)/src/libfsk/libfsk.a \
|
||||
$(top_builddir)/src/libfm/libfm.a \
|
||||
$(top_builddir)/src/libfilter/libfilter.a \
|
||||
$(top_builddir)/src/libwave/libwave.a \
|
||||
$(top_builddir)/src/libmncc/libmncc.a \
|
||||
$(top_builddir)/src/libsample/libsample.a \
|
||||
-lm
|
||||
|
||||
if HAVE_ALSA |
||||
eurosignal_LDADD += \
|
||||
$(top_builddir)/src/libsound/libsound.a \
|
||||
$(ALSA_LIBS)
|
||||
|
||||
endif |
||||
|
||||
if HAVE_SDR |
||||
eurosignal_LDADD += \
|
||||
$(top_builddir)/src/libsdr/libsdr.a \
|
||||
$(top_builddir)/src/libam/libam.a \
|
||||
$(top_builddir)/src/libfft/libfft.a \
|
||||
$(UHD_LIBS) \
|
||||
$(SOAPY_LIBS)
|
||||
endif |
||||
|
||||
if HAVE_ALSA |
||||
AM_CPPFLAGS += -DHAVE_ALSA
|
||||
endif |
||||
|
@ -0,0 +1,326 @@ |
||||
/* Eurosignal signal processing
|
||||
* |
||||
* (C) 2019 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 euro->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 "eurosignal.h" |
||||
#include "dsp.h" |
||||
|
||||
#define PI 3.1415927 |
||||
|
||||
/* signaling */ |
||||
#define MAX_DEVIATION 5000.0 /* FIXME */ |
||||
#define MAX_MODULATION 2000.0 |
||||
#define TONE_DEVIATION 5000.0 /* FIXME */ |
||||
#define TONE_INDEX 0.92 |
||||
#define MAX_DISPLAY 1.4 /* something above tone level */ |
||||
|
||||
#define DIGIT_DURATION 0.1 /* duration of digit */ |
||||
#define PAUSE_DURATION 0.22 /* duration of pause */ |
||||
|
||||
#define FREQUENCY_MIN 313.3 |
||||
#define FREQUENCY_MAX 1153.1 |
||||
#define FREQUENCY_TOL 15.0 /* tolerance of frequency */ |
||||
#define DIGIT_DETECT 200 /* time for a tone to sustain (in samples) */ |
||||
#define TIMEOUT_DETECT 4000 /* time for a tone to sustain (in samples) */ |
||||
|
||||
static struct dsp_digits { |
||||
char digit; |
||||
char *name; |
||||
double frequency; |
||||
double phaseshift65536; |
||||
} dsp_digits[] = { |
||||
{ 'I', "Idle", 1153.1, 0 }, |
||||
{ 'R', "Repeat", 1062.9, 0 }, |
||||
{ '0', "Digit 0", 979.8, 0 }, |
||||
{ '1', "Digit 1", 903.1, 0 }, |
||||
{ '2', "Digit 2", 832.5, 0 }, |
||||
{ '3', "Digit 3", 767.4, 0 }, |
||||
{ '4', "Digit 4", 707.4, 0 }, |
||||
{ '5', "Digit 5", 652.0, 0 }, |
||||
{ '6', "Digit 6", 601.0, 0 }, |
||||
{ '7', "Digit 7", 554.0, 0 }, |
||||
{ '8', "Digit 8", 510.7, 0 }, |
||||
{ '9', "Digit 9", 470.8, 0 }, |
||||
{ 'A', "Digit 10", 433.9, 0 }, |
||||
{ 'B', "Digit 11", 400.0, 0 }, |
||||
{ 'C', "Digit 12", 368.7, 0 }, |
||||
{ 'D', "Digit 13", 339.9, 0 }, |
||||
{ 'E', "Digit 14", 313.3, 0 }, |
||||
{ '\0', NULL, 0.0, 0 }, |
||||
}; |
||||
|
||||
static const char *digit_to_name(char digit) |
||||
{ |
||||
int i; |
||||
|
||||
for (i = 0; dsp_digits[i].digit; i++) { |
||||
if (dsp_digits[i].digit == digit) |
||||
return dsp_digits[i].name; |
||||
} |
||||
|
||||
return "<none>"; |
||||
} |
||||
|
||||
static double digit_to_phaseshift65536(char digit) |
||||
{ |
||||
int i; |
||||
|
||||
for (i = 0; dsp_digits[i].digit; i++) { |
||||
if (dsp_digits[i].digit == digit) |
||||
return dsp_digits[i].phaseshift65536; |
||||
} |
||||
|
||||
return 0.0; |
||||
} |
||||
|
||||
static double dsp_tone[65536]; |
||||
|
||||
/* global init for FSK */ |
||||
void dsp_init(int samplerate) |
||||
{ |
||||
int i; |
||||
|
||||
PDEBUG(DDSP, DEBUG_DEBUG, "Generating phase shiftings for tones.\n"); |
||||
for (i = 0; dsp_digits[i].digit; i++) |
||||
dsp_digits[i].phaseshift65536 = 65536.0 / ((double)samplerate / dsp_digits[i].frequency); |
||||
|
||||
PDEBUG(DDSP, DEBUG_DEBUG, "Generating sine table for tones.\n"); |
||||
for (i = 0; i < 65536; i++) |
||||
dsp_tone[i] = sin((double)i / 65536.0 * 2.0 * PI); |
||||
} |
||||
|
||||
/* Init transceiver instance. */ |
||||
int dsp_init_sender(euro_t *euro, int samplerate, int fm) |
||||
{ |
||||
int rc = 0; |
||||
|
||||
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Init DSP for 'Sender'.\n"); |
||||
|
||||
/* set modulation parameters */ |
||||
if (fm) |
||||
sender_set_fm(&euro->sender, MAX_DEVIATION, MAX_MODULATION, TONE_DEVIATION, MAX_DISPLAY); |
||||
else |
||||
sender_set_am(&euro->sender, MAX_MODULATION, 1.0, MAX_DISPLAY, TONE_INDEX); |
||||
|
||||
euro->sample_duration = 1.0 / (double)samplerate; |
||||
|
||||
/* initial phase shift */ |
||||
euro->tx_phaseshift65536 = digit_to_phaseshift65536('I'); |
||||
|
||||
/* init demodulator */ |
||||
rc = fm_demod_init(&euro->rx_demod, 8000, (FREQUENCY_MIN + FREQUENCY_MAX) / 2.0, FREQUENCY_MAX - FREQUENCY_MIN); |
||||
if (rc) |
||||
goto error; |
||||
|
||||
/* use fourth order (2 iter) filter, since it is as fast as second order (1 iter) filter */ |
||||
iir_lowpass_init(&euro->rx_lp, 25.0, 8000, 2); |
||||
|
||||
euro->dmp_tone_level = display_measurements_add(&euro->sender.dispmeas, "Tone Level", "%.1f %%", DISPLAY_MEAS_LAST, DISPLAY_MEAS_LEFT, 0.0, 150.0, 100.0); |
||||
//euro->dmp_tone_quality = display_measurements_add(&euro->sender.dispmeas, "Tone Quality", "%.1f %%", DISPLAY_MEAS_LAST, DISPLAY_MEAS_LEFT, 0.0, 100.0, 100.0);
|
||||
|
||||
return 0; |
||||
|
||||
error: |
||||
dsp_cleanup_sender(euro); |
||||
|
||||
return -rc; |
||||
} |
||||
|
||||
/* Cleanup transceiver instance. */ |
||||
void dsp_cleanup_sender(euro_t *euro) |
||||
{ |
||||
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Cleanup DSP for 'Sender'.\n"); |
||||
|
||||
/* cleanup demodulator */ |
||||
fm_demod_exit(&euro->rx_demod); |
||||
} |
||||
|
||||
//#define DEBUG
|
||||
|
||||
static void tone_decode(euro_t *euro, sample_t *samples, int length) |
||||
{ |
||||
sample_t frequency[length], f; |
||||
sample_t I[length], Q[length]; |
||||
int i, d; |
||||
char digit; |
||||
|
||||
/* tone demodulation */ |
||||
fm_demodulate_real(&euro->rx_demod, frequency, length, samples, I, Q); |
||||
|
||||
/* reduce bandwidth of tone detector */ |
||||
iir_process(&euro->rx_lp, frequency, length); |
||||
|
||||
/* detect tone */ |
||||
for (i = 0; i < length; i++) { |
||||
/* get frequency */ |
||||
f = frequency[i] + (FREQUENCY_MIN + FREQUENCY_MAX) / 2.0; |
||||
#ifdef DEBUG |
||||
if (i == 0) printf("%s %.5f ", debug_amplitude(frequency[i] / (FREQUENCY_MAX - FREQUENCY_MIN) * 2.0), f); |
||||
#endif |
||||
for (d = 0; dsp_digits[d].digit; d++) { |
||||
if (f >= dsp_digits[d].frequency - FREQUENCY_TOL && f <= dsp_digits[d].frequency + FREQUENCY_TOL) |
||||
break; |
||||
} |
||||
#ifdef DEBUG |
||||
if (i == 0) printf("%c\n", dsp_digits[d].digit); |
||||
#endif |
||||
|
||||
/* change detection and collect digits */ |
||||
digit = dsp_digits[d].digit; |
||||
if (digit != euro->rx_digit_last) { |
||||
euro->rx_digit_last = digit; |
||||
euro->rx_digit_count = 0; |
||||
} |
||||
euro->rx_digit_count++; |
||||
switch (digit) { |
||||
case 'I': |
||||
/* pause tone */ |
||||
if (euro->rx_digit_count == DIGIT_DETECT) { |
||||
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Detected Idle tone, starting.\n"); |
||||
euro->rx_digit_receiving = 1; |
||||
euro->rx_digit_index = 0; |
||||
euro->rx_timeout_count = 0; |
||||
} |
||||
break; |
||||
case '\0': |
||||
/* we are not yet receiving digits */ |
||||
if (!euro->rx_digit_receiving) |
||||
break; |
||||
if (euro->rx_digit_count == DIGIT_DETECT) { |
||||
/* out of range tone */ |
||||
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Detected tone out of range, aborting.\n"); |
||||
euro->rx_digit_receiving = 0; |
||||
} |
||||
break; |
||||
default: |
||||
/* we are not yet receiving digits */ |
||||
if (!euro->rx_digit_receiving) |
||||
break; |
||||
/* got digit */ |
||||
if (euro->rx_digit_count == DIGIT_DETECT) { |
||||
double level; |
||||
level = sqrt(I[i] * I[i] + Q[i] * Q[i]) * 2; |
||||
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Detected digit '%s' (level = %.0f%%)\n", digit_to_name(digit), level * 100.0); |
||||
display_measurements_update(euro->dmp_tone_level, level * 100.0, 0.0); |
||||
euro->rx_digits[euro->rx_digit_index] = digit; |
||||
euro->rx_digit_index++; |
||||
euro->rx_timeout_count = 0; |
||||
if (euro->rx_digit_index == 6 || euro->rx_digits[0] == 'R') { |
||||
euro->rx_digits[euro->rx_digit_index] = '\0'; |
||||
euro_receive_id(euro, euro->rx_digits); |
||||
euro->rx_digit_receiving = 0; |
||||
} |
||||
break; |
||||
} |
||||
} |
||||
|
||||
/* abort if tone sustains too long or next tone will not become steady */ |
||||
if (euro->rx_digit_receiving && euro->rx_digit_index) { |
||||
euro->rx_timeout_count++; |
||||
if (euro->rx_timeout_count == TIMEOUT_DETECT) { |
||||
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Timeout receiving, aborting.\n"); |
||||
euro->rx_digit_receiving = 0; |
||||
} |
||||
} |
||||
|
||||
euro->rx_digit_last = digit; |
||||
} |
||||
} |
||||
|
||||
/* Process received audio stream from radio unit. */ |
||||
void sender_receive(sender_t *sender, sample_t *samples, int length, double __attribute__((unused)) rf_level_db) |
||||
{ |
||||
euro_t *euro = (euro_t *) sender; |
||||
sample_t down[length]; |
||||
int count; |
||||
|
||||
/* downsample and decode */ |
||||
memcpy(down, samples, sizeof(down)); // copy, so audio will not be corrupted at loopback
|
||||
count = samplerate_downsample(&euro->sender.srstate, down, length); |
||||
|
||||
if (euro->rx) |
||||
tone_decode(euro, down, count); |
||||
|
||||
/* whenever a chunk of anouncement audio should be played toward caller */ |
||||
euro->chunk_count += count; |
||||
if (euro->chunk_count >= 160) { |
||||
euro->chunk_count -= 160; |
||||
euro_clock_chunk(sender); |
||||
} |
||||
} |
||||
|
||||
/* Generate tone of paging digits. */ |
||||
static void tone_send(euro_t *euro, sample_t *samples, int length) |
||||
{ |
||||
int i; |
||||
|
||||
for (i = 0; i < length; i++) { |
||||
if (!euro->tx_digits[0]) { |
||||
if (euro->tx_time >= PAUSE_DURATION) { |
||||
euro->tx_time -= PAUSE_DURATION; |
||||
euro_get_id(euro, euro->tx_digits); |
||||
euro->tx_digit_index = 0; |
||||
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Sending digit '%s'\n", digit_to_name(euro->tx_digits[0])); |
||||
euro->tx_phaseshift65536 = digit_to_phaseshift65536(euro->tx_digits[0]); |
||||
} |
||||
} else { |
||||
if (euro->tx_time >= DIGIT_DURATION) { |
||||
euro->tx_time -= DIGIT_DURATION; |
||||
if (++euro->tx_digit_index == 6) { |
||||
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Sending Idle tone'\n"); |
||||
euro->tx_digits[0] = '\0'; |
||||
euro->tx_phaseshift65536 = digit_to_phaseshift65536('I'); |
||||
} else { |
||||
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Sending digit '%s'\n", digit_to_name(euro->tx_digits[euro->tx_digit_index])); |
||||
euro->tx_phaseshift65536 = digit_to_phaseshift65536(euro->tx_digits[euro->tx_digit_index]); |
||||
} |
||||
} |
||||
} |
||||
|
||||
*samples++ = dsp_tone[(uint16_t)euro->tx_phase]; |
||||
euro->tx_phase += euro->tx_phaseshift65536; |
||||
if (euro->tx_phase >= 65536.0) |
||||
euro->tx_phase -= 65536.0; |
||||
euro->tx_time += euro->sample_duration; |
||||
} |
||||
} |
||||
|
||||
/* Provide stream of audio toward radio unit */ |
||||
void sender_send(sender_t *sender, sample_t *samples, uint8_t *power, int length) |
||||
{ |
||||
euro_t *euro = (euro_t *) sender; |
||||
|
||||
if (euro->tx) { |
||||
memset(power, 1, length); |
||||
tone_send(euro, samples, length); |
||||
} else { |
||||
memset(power, 0, length); |
||||
memset(samples, 0, sizeof(*samples) * length); |
||||
} |
||||
} |
||||
|
@ -0,0 +1,5 @@ |
||||
|
||||
void dsp_init(int samplerate); |
||||
int dsp_init_sender(euro_t *euro, int samplerate, int fm); |
||||
void dsp_cleanup_sender(euro_t *euro); |
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,3 @@ |
||||
|
||||
void init_es_ges(void); |
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,3 @@ |
||||
|
||||
void init_es_kaudn(void); |
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,3 @@ |
||||
|
||||
void init_es_mitte(void); |
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,3 @@ |
||||
|
||||
void init_es_teilges(void); |
||||
|
@ -0,0 +1,816 @@ |
||||
/* Eurosignal protocol handling
|
||||
* |
||||
* (C) 2019 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 euro->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 "../libmobile/call.h" |
||||
#include "../libmncc/cause.h" |
||||
#include "eurosignal.h" |
||||
#include "dsp.h" |
||||
|
||||
/* anouncement timers */ |
||||
#define ANSWER_TIME 1.0 /* wait after answer */ |
||||
#define OOO_TIME 3.8 /* announcement 1.7 s, pause 2.1 s */ |
||||
#define UNASSIGNED_TIME1 2.2 /* announcement 2.2 s s */ |
||||
#define UNASSIGNED_TIME2 2.9 /* announcement 2.2 s, pause 0.7 s */ |
||||
#define DEGRADED_TIME 4.95 /* announcement 2.25 s, pause 2.7 s */ |
||||
#define ACKNOWLEDGE_TIME1 2.8 /* announcement 1.7 s, pause 1.1 s */ |
||||
#define ACKNOWLEDGE_TIME2 4.6 /* announcement 1.7 s, pause 2.9 s */ |
||||
#define BEEP_TIME 4.0 /* beep after answer */ |
||||
|
||||
/* Call reference for calls from mobile station to network
|
||||
This offset of 0x400000000 is required for MNCC interface. */ |
||||
static int new_callref = 0x40000000; |
||||
|
||||
euro_call_t *ooo_call_list = NULL; |
||||
|
||||
struct id_list { |
||||
struct id_list *next; |
||||
char id[7]; |
||||
}; |
||||
|
||||
static struct id_list *id_list = NULL, **id_list_tail = &id_list; |
||||
|
||||
/* add ID that is allowed to page */ |
||||
void euro_add_id(const char *id) |
||||
{ |
||||
struct id_list *entry; |
||||
|
||||
entry = calloc(1, sizeof(struct id_list)); |
||||
if (!entry) { |
||||
fprintf(stderr, "No mem!\n"); |
||||
abort(); |
||||
} |
||||
strcpy(entry->id, id); |
||||
(*id_list_tail) = entry; |
||||
id_list_tail = &entry->next; |
||||
} |
||||
|
||||
static int search_id(const char *id) |
||||
{ |
||||
struct id_list *entry = id_list; |
||||
int count = 0; |
||||
|
||||
while (entry) { |
||||
count++; |
||||
if (!strcmp(entry->id, id)) |
||||
return count; |
||||
entry = entry->next; |
||||
} |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static void euro_call_destroy(euro_call_t *call); |
||||
|
||||
static void flush_id(void) |
||||
{ |
||||
struct id_list *entry; |
||||
|
||||
while (ooo_call_list) |
||||
euro_call_destroy(ooo_call_list); |
||||
while (id_list) { |
||||
entry = id_list; |
||||
id_list = entry->next; |
||||
free(entry); |
||||
} |
||||
id_list_tail = &id_list; |
||||
} |
||||
|
||||
static const char *call_state_name(enum euro_call_state state) |
||||
{ |
||||
static char invalid[16]; |
||||
|
||||
switch (state) { |
||||
case EURO_CALL_NULL: |
||||
return "(NULL)"; |
||||
case EURO_CALL_ANSWER: |
||||
return "ANSWER"; |
||||
case EURO_CALL_DEGRADED: |
||||
return "DEGRADED"; |
||||
case EURO_CALL_ACKNOWLEDGE: |
||||
return "ACKNOWLEDGE"; |
||||
case EURO_CALL_RELEASED: |
||||
return "RELEASED"; |
||||
case EURO_CALL_UNASSIGNED: |
||||
return "UNASSIGNED"; |
||||
case EURO_CALL_OUTOFORDER: |
||||
return "OUT-OF-ORDER"; |
||||
case EURO_CALL_BEEPING: |
||||
return "BEEPING"; |
||||
} |
||||
|
||||
sprintf(invalid, "invalid(%d)", state); |
||||
return invalid; |
||||
} |
||||
|
||||
static void euro_display_status(void) |
||||
{ |
||||
sender_t *sender; |
||||
euro_t *euro; |
||||
euro_call_t *call; |
||||
|
||||
display_status_start(); |
||||
for (call = ooo_call_list; call; call = call->next) |
||||
display_status_subscriber(call->station_id, call_state_name(call->state)); |
||||
for (sender = sender_head; sender; sender = sender->next) { |
||||
euro = (euro_t *) sender; |
||||
display_status_channel(euro->sender.kanal, NULL, (euro->degraded) ? "Degraded" : "Working"); |
||||
for (call = euro->call_list; call; call = call->next) |
||||
display_status_subscriber(call->station_id, call_state_name(call->state)); |
||||
} |
||||
display_status_end(); |
||||
} |
||||
|
||||
|
||||
static void call_new_state(euro_call_t *call, enum euro_call_state new_state) |
||||
{ |
||||
if (call->state == new_state) |
||||
return; |
||||
PDEBUG(DEURO, DEBUG_DEBUG, "State change: %s -> %s\n", call_state_name(call->state), call_state_name(new_state)); |
||||
call->state = new_state; |
||||
euro_display_status(); |
||||
} |
||||
|
||||
/* Convert channel number to frequency */ |
||||
double euro_kanal2freq(const char *kanal, int fm) |
||||
{ |
||||
double freq = 87.34; |
||||
char digit = kanal[0]; |
||||
|
||||
if (strlen(kanal) != 1) |
||||
return 0.0; |
||||
|
||||
if (digit >= 'a' && digit <= 'd') |
||||
digit -= 'a'; |
||||
else if (digit >= 'A' && digit <= 'D') |
||||
digit -= 'A'; |
||||
else |
||||
return 0.0; |
||||
|
||||
if (fm) |
||||
freq -= 0.0075; |
||||
|
||||
return (freq + 0.025 * (double)digit) * 1e6; |
||||
} |
||||
|
||||
static struct channel_info { |
||||
const char *kanal; |
||||
const char *country; |
||||
const char *station; |
||||
} channel_info[] = { |
||||
{ "A", "Germany", "Eurosignal Mitte" }, |
||||
{ NULL, "France", "Centre Est" }, |
||||
{ "B", "Germany", "Eurosignal Nord" }, |
||||
{ NULL, "Germany", "Eurosignal Sued" }, |
||||
{ NULL, "France", "North" }, |
||||
{ "C", "France", "West" }, |
||||
{ NULL, "France", "South East" }, |
||||
{ NULL, "France", "East" }, |
||||
{ "D", "France", "South West" }, |
||||
{ NULL, "France", "South West (Corsica)" }, |
||||
{ NULL, "Switzerland", "" }, |
||||
{ NULL, NULL, NULL }, |
||||
}; |
||||
|
||||
/* list all channels */ |
||||
void euro_list_channels(void) |
||||
{ |
||||
int i; |
||||
|
||||
printf("Channel\t\tFrequency\tCountry\t\tStation Name\n"); |
||||
printf("------------------------------------------------------------------------\n"); |
||||
for (i = 0; channel_info[i].country; i++) { |
||||
if (channel_info[i].kanal) |
||||
printf("%s\t\t%.3f MHz\t", channel_info[i].kanal, euro_kanal2freq(channel_info[i].kanal, 0) / 1e6); |
||||
else |
||||
printf("\t\t\t\t"); |
||||
printf("%s\t", channel_info[i].country); |
||||
if (strlen(channel_info[i].country) < 8) |
||||
printf("\t"); |
||||
printf("%s\n", channel_info[i].station); |
||||
} |
||||
printf("\n"); |
||||
} |
||||
|
||||
sample_t beep_tone[160]; |
||||
|
||||
/* global init */ |
||||
int euro_init(void) |
||||
{ |
||||
int i; |
||||
|
||||
for (i = 0; i < 160; i++) |
||||
beep_tone[i] = sin(2.0 * M_PI * (double)i * 2500.0 / 8000.0); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
/* global exit */ |
||||
void euro_exit(void) |
||||
{ |
||||
flush_id(); |
||||
} |
||||
|
||||
static void call_timeout(struct timer *timer); |
||||
|
||||
/* Create transceiver instance and link to a list. */ |
||||
int euro_create(const char *kanal, const char *audiodev, int use_sdr, int samplerate, double rx_gain, int fm, int tx, int rx, int repeat, int degraded, int random, uint32_t scan_from, uint32_t scan_to, const char *write_rx_wave, const char *write_tx_wave, const char *read_rx_wave, const char *read_tx_wave, int loopback) |
||||
{ |
||||
euro_t *euro; |
||||
int rc; |
||||
|
||||
if (euro_kanal2freq(kanal, 0) == 0.0) { |
||||
PDEBUG(DEURO, DEBUG_ERROR, "Channel ('Kanal') number %s invalid, use 'list' to get a list.\n", kanal); |
||||
return -EINVAL; |
||||
} |
||||
|
||||
if (atoi(kanal) == 19) { |
||||
PDEBUG(DEURO, DEBUG_ERROR, "Selected calling channel ('Rufkanal') number %s can't be used as traffic channel.\n", kanal); |
||||
return -EINVAL; |
||||
} |
||||
|
||||
euro = calloc(1, sizeof(*euro)); |
||||
if (!euro) { |
||||
PDEBUG(DEURO, DEBUG_ERROR, "No memory!\n"); |
||||
return -ENOMEM; |
||||
} |
||||
|
||||
PDEBUG(DEURO, DEBUG_DEBUG, "Creating 'Eurosignal' instance for 'Kanal' = %s (sample rate %d).\n", kanal, samplerate); |
||||
|
||||
/* init general part of transceiver */ |
||||
rc = sender_create(&euro->sender, kanal, euro_kanal2freq(kanal, fm), euro_kanal2freq(kanal, fm), audiodev, use_sdr, samplerate, rx_gain, 0, 0, write_rx_wave, write_tx_wave, read_rx_wave, read_tx_wave, loopback, PAGING_SIGNAL_NONE); |
||||
if (rc < 0) { |
||||
PDEBUG(DEURO, DEBUG_ERROR, "Failed to init transceiver process!\n"); |
||||
goto error; |
||||
} |
||||
|
||||
/* init audio processing */ |
||||
rc = dsp_init_sender(euro, samplerate, fm); |
||||
if (rc < 0) { |
||||
PDEBUG(DEURO, DEBUG_ERROR, "Failed to init audio processing!\n"); |
||||
goto error; |
||||
} |
||||
|
||||
euro->tx = tx; |
||||
euro->rx = rx; |
||||
euro->repeat = repeat; |
||||
euro->degraded = degraded; |
||||
euro->random = random; |
||||
euro->scan_from = scan_from; |
||||
euro->scan_to = scan_to; |
||||
|
||||
euro_display_status(); |
||||
|
||||
PDEBUG(DEURO, DEBUG_NOTICE, "Created 'Kanal' %s\n", kanal); |
||||
|
||||
return 0; |
||||
|
||||
error: |
||||
euro_destroy(&euro->sender); |
||||
|
||||
return rc; |
||||
} |
||||
|
||||
/* Destroy transceiver instance and unlink from list. */ |
||||
void euro_destroy(sender_t *sender) |
||||
{ |
||||
euro_t *euro = (euro_t *) sender; |
||||
|
||||
PDEBUG(DEURO, DEBUG_DEBUG, "Destroying 'Eurosignal' instance for 'Kanal' = %s.\n", sender->kanal); |
||||
|
||||
while (euro->call_list) |
||||
euro_call_destroy(euro->call_list); |
||||
dsp_cleanup_sender(euro); |
||||
sender_destroy(&euro->sender); |
||||
free(euro); |
||||
} |
||||
|
||||
/* Create call instance */ |
||||
static euro_call_t *euro_call_create(euro_t *euro, uint32_t callref, const char *id) |
||||
{ |
||||
euro_call_t *call, **callp; |
||||
|
||||
PDEBUG(DEURO, DEBUG_INFO, "Creating call instance to page ID '%s'.\n", id); |
||||
|
||||
/* create */ |
||||
call = calloc(1, sizeof(*call)); |
||||
if (!call) { |
||||
PDEBUG(DEURO, DEBUG_ERROR, "No mem!\n"); |
||||
abort(); |
||||
} |
||||
|
||||
/* init */ |
||||
call->callref = callref; |
||||
strcpy(call->station_id, id); |
||||
if (euro) |
||||
call->page_count = euro->repeat; |
||||
timer_init(&call->timer, call_timeout, call); |
||||
timer_start(&call->timer, ANSWER_TIME); |
||||
|
||||
/* link */ |
||||
call->euro = euro; |
||||
callp = (euro) ? (&euro->call_list) : (&ooo_call_list); |
||||
while ((*callp)) |
||||
callp = &(*callp)->next; |
||||
(*callp) = call; |
||||
|
||||
return call; |
||||
} |
||||
|
||||
/* Destroy call instance */ |
||||
static void euro_call_destroy(euro_call_t *call) |
||||
{ |
||||
euro_call_t **callp; |
||||
|
||||
/* unlink */ |
||||
callp = (call->euro) ? (&call->euro->call_list) : (&ooo_call_list); |
||||
while ((*callp) != call) |
||||
callp = &(*callp)->next; |
||||
(*callp) = call->next; |
||||
|
||||
/* cleanup */ |
||||
timer_exit(&call->timer); |
||||
|
||||
/* destroy */ |
||||
free(call); |
||||
|
||||
/* update display */ |
||||
euro_display_status(); |
||||
} |
||||
|
||||
/* Return ID to page. If there is nothing scheduled, return idle pattern */ |
||||
void euro_get_id(euro_t *euro, char *id) |
||||
{ |
||||
euro_call_t *call; |
||||
int i; |
||||
|
||||
if (euro->sender.loopback) { |
||||
PDEBUG_CHAN(DEURO, DEBUG_NOTICE, "Transmitting test ID '123456'.\n"); |
||||
memcpy(id, "123456", 6); |
||||
goto encode; |
||||
} |
||||
|
||||
if (euro->scan_from < euro->scan_to) { |
||||
sprintf(id, "%06d", euro->scan_from++); |
||||
PDEBUG_CHAN(DEURO, DEBUG_NOTICE, "Transmitting ID '%s'.\n", id); |
||||
goto encode; |
||||
} |
||||
|
||||
for (call = euro->call_list; call; call = call->next) { |
||||
if ((call->state == EURO_CALL_ACKNOWLEDGE || call->state == EURO_CALL_RELEASED) && call->page_count) { |
||||
call->page_count--; |
||||
PDEBUG_CHAN(DEURO, DEBUG_NOTICE, "Transmitting ID '%s'.\n", call->station_id); |
||||
memcpy(id, call->station_id, 6); |
||||
if (call->page_count == 0 && call->state == EURO_CALL_RELEASED) |
||||
euro_call_destroy(call); |
||||
/* reset random counter, so aferr call has been transmitted, the random mode continues with a new ID */ |
||||
euro->random_count = 0; |
||||
break; |
||||
} |
||||
} |
||||
|
||||
if (!call && euro->random) { |
||||
if (!euro->random_count) { |
||||
if (((uint32_t)random() % 2) == 0 && euro->random_id[0] != 'R') { |
||||
memcpy(euro->random_id, "RIIIII", 6); |
||||
euro->random_count = (uint32_t)random() % 2 + 2; |
||||
} else { |
||||
sprintf(euro->random_id, "%06d", (uint32_t)(random() % 1000000)); |
||||
euro->random_count = (uint32_t)random() % 3 + 2; |
||||
} |
||||
} |
||||
memcpy(id, euro->random_id, 6); |
||||
euro->random_count--; |
||||
if (id[0] == 'R') { |
||||
PDEBUG_CHAN(DEURO, DEBUG_NOTICE, "Randomly transmitting Idle sequence.\n"); |
||||
return; |
||||
} |
||||
PDEBUG_CHAN(DEURO, DEBUG_NOTICE, "Randomly transmitting ID '%s'.\n", euro->random_id); |
||||
goto encode; |
||||
} |
||||
|
||||
if (!call) { |
||||
PDEBUG_CHAN(DEURO, DEBUG_DEBUG, "Transmitting Idle sequence.\n"); |
||||
memcpy(id, "RIIIII", 6); |
||||
return; |
||||
} |
||||
|
||||
encode: |
||||
/* return station ID with repeat digit */ |
||||
for (i = 1; i < 6; i++) { |
||||
if (id[i - 1] == id[i]) |
||||
id[i] = 'R'; |
||||
} |
||||
} |
||||
|
||||
/* A station ID was received. */ |
||||
void euro_receive_id(euro_t *euro, char *id) |
||||
{ |
||||
int i; |
||||
int count = 0; |
||||
|
||||
if (id[0] == 'R') { |
||||
PDEBUG_CHAN(DEURO, DEBUG_DEBUG, "Received Idle sequence'\n"); |
||||
return; |
||||
} |
||||
|
||||
/* turn repeat digit to normal digit */ |
||||
for (i = 1; i < 6; i++) { |
||||
if (id[i] == 'R') |
||||
id[i] = id[i - 1]; |
||||
} |
||||
|
||||
/* loopback display */ |
||||
if (euro->sender.loopback) { |
||||
PDEBUG_CHAN(DEURO, DEBUG_NOTICE, "Received ID '%s'\n", id); |
||||
return; |
||||
} |
||||
|
||||
/* check for ID selection */ |
||||
if (id_list) { |
||||
count = search_id(id); |
||||
if (!count) { |
||||
PDEBUG_CHAN(DEURO, DEBUG_INFO, "Received ID '%s' is not for us.\n", id); |
||||
return; |
||||
} |
||||
} |
||||
|
||||
PDEBUG_CHAN(DEURO, DEBUG_NOTICE, "Received ID '%s'\n", id); |
||||
|
||||
/* we want to send beep via MNCC */ |
||||
if (id_list) { |
||||
sender_t *sender; |
||||
euro_t *e; |
||||
euro_call_t *call; |
||||
char dialing[32]; |
||||
int callref; |
||||
int rc; |
||||
|
||||
/* check if we already have a call that beeps */ |
||||
for (sender = sender_head; sender; sender = sender->next) { |
||||
e = (euro_t *) sender; |
||||
for (call = e->call_list; call; call = call->next) { |
||||
if (!strcmp(call->station_id, id) && call->state == EURO_CALL_BEEPING) |
||||
break; |
||||
} |
||||
if (call) |
||||
break; |
||||
} |
||||
/* if already beeping */ |
||||
if (sender) |
||||
return; |
||||
|
||||
/* create call and send setup */ |
||||
PDEBUG_CHAN(DEURO, DEBUG_INFO, "Sending setup towards network.'\n"); |
||||
callref = ++new_callref; |
||||
call = euro_call_create(euro, callref, id); |
||||
call_new_state(call, EURO_CALL_BEEPING); |
||||
sprintf(dialing, "%d", count); |
||||
rc = call_up_setup(callref, call->station_id, dialing); |
||||
if (rc < 0) |
||||
euro_call_destroy(call); |
||||
} |
||||
} |
||||
|
||||
int16_t *es_mitte_spl; |
||||
int es_mitte_size; |
||||
int16_t *es_ges_spl; |
||||
int es_ges_size; |
||||
int16_t *es_teilges_spl; |
||||
int es_teilges_size; |
||||
int16_t *es_kaudn_spl; |
||||
int es_kaudn_size; |
||||
|
||||
/* play announcement for one call */ |
||||
static void call_play_announcement(euro_call_t *call) |
||||
{ |
||||
int i; |
||||
int16_t chunk[160]; |
||||
sample_t spl[160]; |
||||
|
||||
for (i = 0; i < 160; i++) { |
||||
/* announcement or silence, if finished or not set */ |
||||
if (call->announcement_index < call->announcement_size) |
||||
chunk[i] = call->announcement_spl[call->announcement_index++] >> 2; |
||||
else |
||||
chunk[i] = 0.0; |
||||
} |
||||
int16_to_samples(spl, chunk, 160); |
||||
call_up_audio(call->callref, spl, 160); |
||||
} |
||||
|
||||
/* play paging tone */ |
||||
static void call_play_beep(euro_call_t *call) |
||||
{ |
||||
call_up_audio(call->callref, beep_tone, 160); |
||||
} |
||||
|
||||
/* loop through all calls and play the announcement */ |
||||
void euro_clock_chunk(sender_t *sender) |
||||
{ |
||||
euro_t *euro; |
||||
euro_call_t *call; |
||||
|
||||
if (sender == sender_head) { |
||||
/* use first tranceiver clock to clock calls without a transceiver */ |
||||
for (call = ooo_call_list; call; call = call->next) { |
||||
/* no callref */ |
||||
if (!call->callref) |
||||
continue; |
||||
/* beep or announcement */ |
||||
if (call->state == EURO_CALL_BEEPING) |
||||
call_play_beep(call); |
||||
else |
||||
call_play_announcement(call); |
||||
} |
||||
} |
||||
euro = (euro_t *) sender; |
||||
for (call = euro->call_list; call; call = call->next) { |
||||
/* no callref */ |
||||
if (!call->callref) |
||||
continue; |
||||
/* beep or announcement */ |
||||
if (call->state == EURO_CALL_BEEPING) |
||||
call_play_beep(call); |
||||
else |
||||
call_play_announcement(call); |
||||
} |
||||
} |
||||
|
||||
/* Timeout handling */ |
||||
static void call_timeout(struct timer *timer) |
||||
{ |
||||
euro_call_t *call = (euro_call_t *)timer->priv; |
||||
|
||||
switch (call->state) { |
||||
case EURO_CALL_ANSWER: |
||||
/* if no station is linked to the call, we are out-of-order */ |
||||
if (!call->euro) { |
||||
PDEBUG(DEURO, DEBUG_INFO, "Station is unavailable, playing announcement.\n"); |
||||
call->announcement_spl = es_ges_spl; |
||||
call->announcement_size = es_ges_size; |
||||
call->announcement_index = 0; |
||||
timer_start(&call->timer, OOO_TIME); |
||||
call_new_state(call, EURO_CALL_OUTOFORDER); |
||||
break; |
||||
} |
||||
/* if subcriber list is available, but ID is not found, we are unassigned */ |
||||
if (id_list && !search_id(call->station_id)) { |
||||
PDEBUG(DEURO, DEBUG_INFO, "Subscriber unknwon, playing announcement.\n"); |
||||
call->announcement_spl = es_kaudn_spl; |
||||
call->announcement_size = es_kaudn_size; |
||||
call->announcement_index = 0; |
||||
call->announcement_count = 1; |
||||
timer_start(&call->timer, UNASSIGNED_TIME1); |
||||
call_new_state(call, EURO_CALL_UNASSIGNED); |
||||
break; |
||||
} |
||||
/* if station is degraded, play that announcement */ |
||||
if (call->euro->degraded) { |
||||
PDEBUG(DEURO, DEBUG_INFO, "Station is degraded, playing announcement.\n"); |
||||
call->announcement_spl = es_teilges_spl; |
||||
call->announcement_size = es_teilges_size; |
||||
call->announcement_index = 0; |
||||
timer_start(&call->timer, DEGRADED_TIME); |
||||
call_new_state(call, EURO_CALL_DEGRADED); |
||||
break; |
||||
} |
||||
/* fall through */ |
||||
case EURO_CALL_DEGRADED: |
||||
PDEBUG(DEURO, DEBUG_INFO, "Station acknowledges, playing announcement.\n"); |
||||
call->announcement_spl = es_mitte_spl; |
||||
call->announcement_size = es_mitte_size; |
||||
call->announcement_index = 0; |
||||
call->announcement_count = 1; |
||||
timer_start(&call->timer, ACKNOWLEDGE_TIME1); |
||||
call_new_state(call, EURO_CALL_ACKNOWLEDGE); |
||||
break; |
||||
case EURO_CALL_ACKNOWLEDGE: |
||||
if (call->announcement_count == 1) { |
||||
call->announcement_spl = es_mitte_spl; |
||||
call->announcement_size = es_mitte_size; |
||||
call->announcement_index = 0; |
||||
call->announcement_count = 2; |
||||
timer_start(&call->timer, ACKNOWLEDGE_TIME2); |
||||
break; |
||||
} |
||||
if (call->page_count) { |
||||
PDEBUG(DEURO, DEBUG_INFO, "Announcement played, receiver has not been paged yet, releasing call.\n"); |
||||
call_up_release(call->callref, CAUSE_NORMAL); |
||||
call->callref = 0; |
||||
call_new_state(call, EURO_CALL_RELEASED); |
||||
break; |
||||
} |
||||
PDEBUG(DEURO, DEBUG_INFO, "Announcement played, receiver has been paged, releasing call.\n"); |
||||
call_up_release(call->callref, CAUSE_NORMAL); |
||||
euro_call_destroy(call); |
||||
break; |
||||
case EURO_CALL_OUTOFORDER: |
||||
PDEBUG(DEURO, DEBUG_INFO, "Announcement played, releasing call.\n"); |
||||
call_up_release(call->callref, CAUSE_NORMAL); |
||||
euro_call_destroy(call); |
||||
break; |
||||
case EURO_CALL_UNASSIGNED: |
||||
if (call->announcement_count == 1) { |
||||
call->announcement_spl = es_kaudn_spl; |
||||
call->announcement_size = es_kaudn_size; |
||||
call->announcement_index = 0; |
||||
call->announcement_count = 2; |
||||
timer_start(&call->timer, UNASSIGNED_TIME2); |
||||
break; |
||||
} |
||||
PDEBUG(DEURO, DEBUG_INFO, "Announcement played, playing again.\n"); |
||||
call->announcement_spl = es_kaudn_spl; |
||||
call->announcement_size = es_kaudn_size; |
||||
call->announcement_index = 0; |
||||
call->announcement_count = 1; |
||||
timer_start(&call->timer, UNASSIGNED_TIME1); |
||||
break; |
||||
case EURO_CALL_BEEPING: |
||||
PDEBUG(DEURO, DEBUG_INFO, "Beep played, releasing.\n"); |
||||
call_up_release(call->callref, CAUSE_NORMAL); |
||||
call->callref = 0; |
||||
euro_call_destroy(call); |
||||
break; |
||||
default: |
||||
break; |
||||
} |
||||
} |
||||
|
||||
/* Call control starts call towards paging network. */ |
||||
int call_down_setup(int callref, const char __attribute__((unused)) *caller_id, enum number_type __attribute__((unused)) caller_type, const char *dialing) |
||||
{ |
||||
char channel = '\0'; |
||||
sender_t *sender; |
||||
euro_t *euro; |
||||
euro_call_t *call; |
||||
int i; |
||||
|
||||
/* check prefix to choose correct channel */ |
||||
if (strlen(dialing) == 10) { |
||||
if (!strncmp(dialing, "0279", 4)) { |
||||
dialing += 4; |
||||
channel = 'A'; |
||||
} |
||||
if (!strncmp(dialing, "0509", 4)) { |
||||
dialing += 4; |
||||
channel = 'B'; |
||||
} |
||||
if (!strncmp(dialing, "0709", 4)) { |
||||
dialing += 4; |
||||
channel = 'B'; |
||||
} |
||||
} |
||||
/* number invalid */ |
||||
if (strlen(dialing) != 6) { |
||||
inval: |
||||
PDEBUG(DEURO, DEBUG_NOTICE, "Call to invalid ID '%s', rejecting!\n", dialing); |
||||
return -CAUSE_INVALNUMBER; |
||||
} |
||||
for (i = 0; i < 6; i++) { |
||||
if (dialing[i] < '0' || dialing[i] > '9') |
||||
goto inval; |
||||
} |
||||
|
||||
/* find transmitter */ |
||||
for (sender = sender_head; sender; sender = sender->next) { |
||||
/* skip channels that are different than requested */ |
||||
if (channel && sender->kanal[0] != channel) |
||||
continue; |
||||
euro = (euro_t *) sender; |
||||
/* check if base station cannot transmit */ |
||||
if (!euro->tx) |
||||
continue; |
||||
break; |
||||
} |
||||
/* just (ab)use busy signal when no station is available */ |
||||
if (!sender) { |
||||
if (channel) |
||||
PDEBUG(DEURO, DEBUG_NOTICE, "Cannot page receiver, because given station not available, rejecting!\n"); |
||||
else |
||||
PDEBUG(DEURO, DEBUG_NOTICE, "Cannot page receiver, no station not available, rejecting!\n"); |
||||
euro = NULL; |
||||
} |
||||
|
||||
/* create call process to page station or send out-of-order message */ |
||||
call = euro_call_create(euro, callref, dialing); |
||||
call_new_state(call, EURO_CALL_ANSWER); |
||||
call_up_answer(callref, dialing); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
void call_down_answer(int __attribute__((unused)) callref) |
||||
{ |
||||
sender_t *sender; |
||||
euro_t *euro; |
||||
euro_call_t *call; |
||||
|
||||
PDEBUG(DEURO, DEBUG_INFO, "Call has been answered by network.\n"); |
||||
|
||||
for (sender = sender_head; sender; sender = sender->next) { |
||||
euro = (euro_t *) sender; |
||||
for (call = euro->call_list; call; call = call->next) { |
||||
if (call->callref == callref) |
||||
break; |
||||
} |
||||
if (call) |
||||
break; |
||||
} |
||||
if (!call) { |
||||
PDEBUG(DEURO, DEBUG_NOTICE, "Outgoing disconnect, but no callref!\n"); |
||||
call_up_release(callref, CAUSE_INVALCALLREF); |
||||
return; |
||||
} |
||||
|
||||
timer_start(&call->timer, BEEP_TIME); |
||||
} |
||||
|
||||
static void _release(int callref, int __attribute__((unused)) cause) |
||||
{ |
||||
sender_t *sender; |
||||
euro_t *euro; |
||||
euro_call_t *call; |
||||
|
||||
PDEBUG(DEURO, DEBUG_INFO, "Call has been disconnected by network.\n"); |
||||
|
||||
for (sender = sender_head; sender; sender = sender->next) { |
||||
euro = (euro_t *) sender; |
||||
for (call = euro->call_list; call; call = call->next) { |
||||
if (call->callref == callref) |
||||
break; |
||||
} |
||||
if (call) |
||||
break; |
||||
} |
||||
if (!sender) { |
||||
for (call = ooo_call_list; call; call = call->next) { |
||||
if (call->callref == callref) |
||||
break; |
||||
} |
||||
} |
||||
if (!call) { |
||||
PDEBUG(DEURO, DEBUG_NOTICE, "Outgoing disconnect, but no callref!\n"); |
||||
call_up_release(callref, CAUSE_INVALCALLREF); |
||||
return; |
||||
} |
||||
|
||||
call->callref = 0; |
||||
|
||||
/* queued ID will keep in release state until trasmission has finished */ |
||||
if (call->state == EURO_CALL_ACKNOWLEDGE && call->page_count) { |
||||
call_new_state(call, EURO_CALL_RELEASED); |
||||
return; |
||||
} |
||||
|
||||
euro_call_destroy(call); |
||||
} |
||||
|
||||
/* Call control sends disconnect.
|
||||
* A queued ID will be kept until transmitted by mobile station. |
||||
*/ |
||||
void call_down_disconnect(int callref, int cause) |
||||
{ |
||||
_release(callref, cause); |
||||
|
||||
call_up_release(callref, cause); |
||||
} |
||||
|
||||
/* Call control releases call toward mobile station. */ |
||||
void call_down_release(int callref, int cause) |
||||
{ |
||||
_release(callref, cause); |
||||
} |
||||
|
||||
/* Receive audio from call instance. */ |
||||
void call_down_audio(int __attribute__((unused)) callref, sample_t __attribute__((unused)) *samples, int __attribute__((unused)) count) |
||||
{ |
||||
} |
||||
|
||||
void dump_info(void) {} |
||||
|
@ -0,0 +1,91 @@ |
||||
#include "../libfm/fm.h" |
||||
#include "../libmobile/sender.h" |
||||
#include "../libtimer/timer.h" |
||||
|
||||
/* current state of transmitter */ |
||||
enum euro_health_state { |
||||
EURO_HEALTH_WORKING = 0,/* */ |
||||
EURO_HEALTH_DEGRADED, /* */ |
||||
EURO_HEALTH_OUTOFORDER, /* */ |
||||
}; |
||||
|
||||
/* current state of incoming call */ |
||||
enum euro_call_state { |
||||
EURO_CALL_NULL = 0, |
||||
EURO_CALL_ANSWER, /* answer the call */ |
||||
EURO_CALL_DEGRADED, /* play announcement 'teilgestoert' */ |
||||
EURO_CALL_ACKNOWLEDGE, /* play announcement 'eurosignal xxx', transmit ID */ |
||||
EURO_CALL_RELEASED, /* caller hangs up, ID still not transmitted */ |
||||
EURO_CALL_UNASSIGNED, /* play announcement 'kein anschluss' */ |
||||
EURO_CALL_OUTOFORDER, /* play announcement 'gestoert' */ |
||||
EURO_CALL_BEEPING, /* call towards MNCC that beeps */ |
||||
}; |
||||
|
||||
struct eurosignal; |
||||
|
||||
/* instance of incoming call */ |
||||
typedef struct euro_call { |
||||
struct euro_call *next; |
||||
struct eurosignal *euro; |
||||
int callref; /* call reference */ |
||||
char station_id[7]; /* current station ID */ |
||||
int page_count; /* number of transmissions left */ |
||||
struct timer timer; |
||||
enum euro_call_state state; /* current state */ |
||||
int announcement_count; /* used to replay annoucements */ |
||||
int16_t *announcement_spl; /* current sample */ |
||||
int announcement_size; /* current size */ |
||||
int announcement_index; /* current sample index */ |
||||
} euro_call_t; |
||||
|
||||
/* instance of eurosignal transmitter/receiver */ |
||||
typedef struct eurosignal { |
||||
sender_t sender; |
||||
|
||||
/* system info */ |
||||
int tx; /* can transmit */ |
||||
int rx; /* can receive */ |
||||
int repeat; /* repetitions of ID transmission */ |
||||
int degraded; /* station is degraded */ |
||||
int random; /* random ID transmission */ |
||||
char random_id[7]; /* current ID */ |
||||
char random_count; /* number of transmissions left */ |
||||
uint32_t scan_from; /* scan ID from */ |
||||
uint32_t scan_to; /* scan ID to (scan if not 0) */ |
||||
|
||||
/* calls */ |
||||
euro_call_t *call_list; /* linked list of all calls */ |
||||
|
||||
/* display measurements */ |
||||
dispmeasparam_t *dmp_tone_level; |
||||
dispmeasparam_t *dmp_tone_quality; |
||||
|
||||
/* dsp states */ |
||||
double sample_duration; /* how many seconds lasts a sample */ |
||||
double tx_phaseshift65536; /* current tone's phase shift per sample */ |
||||
double tx_phase; /* current phase of tone */ |
||||
double tx_time; /* current elapsed time of tone */ |
||||
char tx_digits[7]; /* current ID beeing transmitted */ |
||||
int tx_digit_index; /* current digit beein transmitted */ |
||||
int chunk_count; /* current elapsed sample of 20ms audio chunk */ |
||||
fm_demod_t rx_demod; /* demodulator for frequency */ |
||||
iir_filter_t rx_lp; /* low pass to filter the frequency result */ |
||||
int rx_digit_count; /* count the tone until detected */ |
||||
char rx_digit_last; /* last tone, so we detect any change */ |
||||
int rx_digit_receiving; /* we recive digis */ |
||||
char rx_digits[7]; /* current ID being received */ |
||||
int rx_digit_index; /* current digit receiving */ |
||||
int rx_timeout_count; /* count the timeout */ |
||||
} euro_t; |
||||
|
||||
void euro_add_id(const char *id); |
||||
double euro_kanal2freq(const char *kanal, int fm); |
||||
void euro_list_channels(void); |
||||
int euro_init(void); |
||||
void euro_exit(void); |
||||
int euro_create(const char *kanal, const char *audiodev, int use_sdr, int samplerate, double rx_gain, int fm, int tx |