Add JollyCom, a simple and experimantal mobile network

This network that can be used with any radio and a DTMF transmitter.
This commit is contained in:
Andreas Eversberg 2017-11-12 15:59:48 +01:00
parent f2eb6b3e70
commit 72bdd3376f
14 changed files with 12514 additions and 12 deletions

1
.gitignore vendored
View File

@ -55,6 +55,7 @@ src/amps/amps
src/tacs/tacs
src/jtacs/jtacs
src/r2000/radiocom2000
src/jolly/jollycom
src/tv/osmotv
sim/cnetz_sim
src/test/test_filter

View File

@ -107,6 +107,7 @@ AC_OUTPUT(
src/tacs/Makefile
src/jtacs/Makefile
src/r2000/Makefile
src/jolly/Makefile
src/tv/Makefile
src/test/Makefile
src/Makefile

View File

@ -39,7 +39,7 @@ SUBDIRS += \
tacs \
jtacs \
r2000 \
jolly \
tv \
test

40
src/jolly/Makefile.am Normal file
View File

@ -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

402
src/jolly/dsp.c Normal file
View File

@ -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];
}
}

7
src/jolly/dsp.h Normal file
View File

@ -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);

636
src/jolly/jolly.c Normal file
View File

@ -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) {}

56
src/jolly/jolly.h Normal file
View File

@ -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);

219
src/jolly/main.c Normal file
View File

@ -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);
}
if (mandatory) {
print_help(argv[-skip_args]);
return 0;
}
/* SDR always requires emphasis */
if (use_sdr) {
do_pre_emphasis = 1;
do_de_emphasis = 1;
}
/* no SDR, no squelch */
if (!use_sdr && !isinf(squelch_db)) {
fprintf(stderr, "Cannot use squelch without SDR! Analog receivers don't give use RSSI.\n");
goto fail;
}
init_voice(samplerate);
dsp_init();
/* create transceiver instance */
for (i = 0; i < num_kanal; i++) {
rc = jolly_create(kanal[i], dl_freq, ul_freq, step, audiodev[i], use_sdr, samplerate, rx_gain, do_pre_emphasis, do_de_emphasis, write_rx_wave, write_tx_wave, read_rx_wave, read_tx_wave, loopback, squelch_db, nbfm, repeater);
if (rc < 0) {
fprintf(stderr, "Failed to create transceiver instance. Quitting!\n");
goto fail;
}
printf("base station on channel %d ready, please tune transmitter to %.4f MHz and receiver to %.4f MHz. (%.4f MHz offset)\n", kanal[i], dl_freq + step / 1e3 * (double)kanal[i], ul_freq + step / 1e3 * (double)kanal[i], ul_freq - dl_freq);
}
main_mobile(&quit, latency, interval, NULL, station_id, 4);
fail:
/* destroy transceiver instance */
while (sender_head)
jolly_destroy(sender_head);
return 0;
}

11128
src/jolly/voice.c Normal file

File diff suppressed because it is too large Load Diff

10
src/jolly/voice.h Normal file
View File

@ -0,0 +1,10 @@
typedef struct jolly_voice {
sample_t *spl[13];
int size[13];
} jolly_voice_t;
extern jolly_voice_t jolly_voice;
int init_voice(int samplerate);

View File

@ -49,6 +49,7 @@ struct debug_cat {
{ "nmt", "\033[1;34m" },
{ "amps", "\033[1;34m" },
{ "r2000", "\033[1;34m" },
{ "jollycom", "\033[1;34m" },
{ "frame", "\033[0;36m" },
{ "call", "\033[0;37m" },
{ "mncc", "\033[1;32m" },

View File

@ -13,16 +13,17 @@
#define DNMT 6
#define DAMPS 7
#define DR2000 8
#define DFRAME 9
#define DCALL 10
#define DMNCC 11
#define DDB 12
#define DTRANS 13
#define DDMS 14
#define DSMS 15
#define DSDR 16
#define DUHD 17
#define DSOAPY 18
#define DJOLLY 9
#define DFRAME 10
#define DCALL 11
#define DMNCC 12
#define DDB 13
#define DTRANS 14
#define DDMS 15
#define DSMS 16
#define DSDR 17
#define DUHD 18
#define DSOAPY 19
#define PDEBUG(cat, level, fmt, arg...) _printdebug(__FILE__, __FUNCTION__, __LINE__, cat, level, -1, fmt, ## arg)
#define PDEBUG_CHAN(cat, level, fmt, arg...) _printdebug(__FILE__, __FUNCTION__, __LINE__, cat, level, CHAN, fmt, ## arg)

View File

@ -50,7 +50,7 @@ enum r2000_chan_type chan_type[MAX_SENDER] = { CHAN_TYPE_CC_TC };
void print_help(const char *arg0)
{
main_mobile_print_help(arg0, "-R <relais number> [option] ");
main_mobile_print_help(arg0, "[-R <relais number>] ");
/* - - */
printf(" -B --band <number> | list\n");
printf(" -B --bande <number> | list\n");