osmo-cc-pstn-endpoint/src/pstn/callerid.c

401 lines
11 KiB
C

/* caller ID transmission
*
* (C) 2022 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/>.
*/
/* see:
* ETSI EN 300 659-1
* ETSI EN 300 659-2
* ETSI EN 300 659-3
*/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <math.h>
#include <errno.h>
#include <time.h>
#include "../libsample/sample.h"
#include "../libdebug/debug.h"
#include "../libtimer/timer.h"
#include "../libosmocc/message.h"
#include "callerid.h"
#define db2level(db) pow(10, (double)(db) / 20.0)
/* Notes for dBV:
* See TR 101 182 for complex impedance, that is 1020 Ohms.
* This is close to 1mW.
*/
#define DBV_TO_DBM -0.086 /* impedance 1020 Ohms */
#define DTAS_TX_DBV -16.0 /* relative to 1 Volt RMS */
#define DTAS_DURATION 0.100 /* 100 ms */
#define FSK_TX_DBV -15.5 /* relative to 1 Volt RMS */
#define FSK_BAUD_RATE 1200
#define FSK_BIT_ADJUST 0.5 /* must be 0.5 to completely sync each bit */
#define DTMF_TONE_ON 0.070 /* 70 ms */
#define DTMF_TONE_OFF 0.070 /* 70 ms */
#define TONE_DTAS_1 2130.0
#define TONE_DTAS_2 2750.0
#define TONE_V23_F0 2100.0
#define TONE_V23_F1 1300.0
#define TONE_BELL_F0 2200.0
#define TONE_BELL_F1 1200.0
#define WAIT_RING_FSK 0.700 /* wait 500ms-2000ms after ring before sending FSK */
#define WAIT_CW_DTAS 0.050 /* wait 50ms after CW before sending DT_AS */
#define WAIT_DTAS_TEACK 0.210 /* wait 160ms + 50ms(latency) for TE-ACK after DT_AS */
#define WAIT_TEACK_FSK 0.055 /* wait 55ms after recognition of TE-ACK before sending FSK */
#define WAIT_RING_DTMF 0.500 /* wait after 1st ring before sending DTMF */
#define WAIT_FSK_END 0.200 /* wait after FSK until restoring line condition */
#define WAIT_DTMF_END 1.000 /* wait after FSK until restoring line condition */
/* provide bit to FSK modulator */
static int fsk_send_bit(void *inst)
{
callerid_t *cid = (callerid_t *)inst;
/* FSK transmission done */
if (cid->pos >= cid->len + 3)
return -1;
/* send 3 extra bit to make sure that CID passes all filers */
if (cid->pos >= cid->len) {
cid->pos++;
return 1;
}
/* channel seizure */
if (cid->seize)
return (cid->seize-- & 1);
/* mark */
if (cid->mark) {
cid->mark--;
return 1;
}
/* start bit */
if (cid->bpos == 0) {
cid->bpos++;
return 0;
}
/* stop bit */
if (cid->bpos > 8) {
cid->bpos = 0;
cid->pos++;
return 1;
}
/* data bit */
cid->bpos++;
return (cid->data[cid->pos] >> (cid->bpos - 2)) & 1;
}
/* init caller ID processor */
int callerid_init(callerid_t *cid, int samplerate, int bell, char dtmf)
{
double f0, f1;
int i;
int rc;
memset(cid, 0, sizeof(*cid));
cid->samplerate = samplerate;
cid->use_dtmf = dtmf;
for (i = 0; i < 65536; i++)
cid->dtas_sine[i] = sin((double)i / 65536.0 * 2.0 * M_PI) * db2level(DTAS_TX_DBV + DBV_TO_DBM);
if (!cid->use_dtmf) {
/* FSK */
if (bell) {
/* BELL 202 */
f0 = TONE_BELL_F0;
f1 = TONE_BELL_F1;
} else {
/* V.23 (used by Telekom) */
f0 = TONE_V23_F0;
f1 = TONE_V23_F1;
}
rc = fsk_mod_init(&cid->fsk, cid, fsk_send_bit, samplerate, FSK_BAUD_RATE, f0, f1, db2level(FSK_TX_DBV + DBV_TO_DBM), 0, 1);
if (rc < 0) {
PDEBUG(DDSP, DEBUG_ERROR, "FSK init failed!\n");
return rc;
}
} else {
/* DTMF or DT_AS */
/* because we render with 1 mW, we need to set level to 1.0 */
dtmf_encode_init(&cid->dtmf, samplerate, 1.0);
}
return 0;
}
/* exit caller ID processor */
void callerid_exit(callerid_t *cid)
{
if (!cid->use_dtmf)
fsk_mod_cleanup(&cid->fsk);
// no samplerate cleanup
// no DTMF cleanup
}
/* subroutine to add information element (TLV) */
static uint8_t *add_ie(uint8_t *p, uint8_t type, int len, const uint8_t *val)
{
*p++ = type;
*p++ = len;
memcpy(p, val, len);
return p + len;
}
/* set caller ID and start transmission */
int callerid_set(callerid_t *cid, int cw, int dt_as, const char *callerid, uint8_t caller_type, int use_date)
{
int clen = strlen(callerid);
if (clen > 32) {
PDEBUG(DDSP, DEBUG_ERROR, "Callerid too long!\n");
return -EINVAL;
}
if (!cid->use_dtmf) {
uint8_t *p;
uint8_t par = 0;
int i;
time_t time_sec;
struct tm *tm;
uint8_t data[8];
if (caller_type == OSMO_CC_PRESENT_RESTRICTED)
PDEBUG(DDSP, DEBUG_INFO, "Sending restricted caller ID reason.\n");
else if (caller_type == OSMO_CC_PRESENT_ALLOWED && callerid[0])
PDEBUG(DDSP, DEBUG_INFO, "Sending caller ID '%s' via FSK.\n", callerid);
else
PDEBUG(DDSP, DEBUG_INFO, "Sending unavailable caller ID reason.\n");
p = cid->data + 2;
if (use_date) {
/* add IE (data) */
time_sec = get_time();
tm = localtime(&time_sec);
data[0] = '0' + (tm->tm_mon + 1) / 10;
data[1] = '0' + (tm->tm_mon + 1) % 10;
data[2] = '0' + (tm->tm_mday) / 10;
data[3] = '0' + (tm->tm_mday) % 10;
data[4] = '0' + (tm->tm_hour) / 10;
data[5] = '0' + (tm->tm_hour) % 10;
data[6] = '0' + (tm->tm_min) / 10;
data[7] = '0' + (tm->tm_min) % 10;
p = add_ie(p, 0x01, 8, data);
}
if (caller_type == OSMO_CC_PRESENT_ALLOWED && callerid[0]) {
/* add IE (cid) */
p = add_ie(p, 0x02, strlen(callerid), (const uint8_t *)callerid);
} else {
/* add IE (unavailable reason) */
if (caller_type == OSMO_CC_PRESENT_RESTRICTED)
data[0] = 0x50; /* restricted */
else
data[0] = 0x4f; /* not available */
p = add_ie(p, 0x04, 1, data);
}
/* preceed type+length */
cid->len = p - cid->data - 2;
cid->data[0] = (cw) ? 0x82 : 0x80;
cid->data[1] = cid->len;
cid->len += 2;
/* append parity */
p = cid->data;
for(i = 0; i < cid->len; i++)
par += *p++;
par = (~par) + 1;
*p++ = par;
cid->len++;
cid->pos = 0;
cid->bpos = 0;
cid->seize = (cw) ? 0 : 300;
cid->mark = 180;
if (dt_as) {
cid->state = CID_STATE_WAIT_DTAS;
cid->wait = (int)(WAIT_CW_DTAS * (double)cid->samplerate);
PDEBUG(DDSP, DEBUG_DEBUG, "Start sending callerid '%s' via FSK, waiting to start DT-AS transmission.\n", callerid);
} else {
cid->state = CID_STATE_WAIT_FSK;
cid->wait = (int)(WAIT_RING_FSK * (double)cid->samplerate);
PDEBUG(DDSP, DEBUG_DEBUG, "Start sending callerid '%s' via FSK, waiting to start FSK transmission.\n", callerid);
}
} else {
if (caller_type == OSMO_CC_PRESENT_RESTRICTED) {
PDEBUG(DDSP, DEBUG_INFO, "Not sending restricted caller ID.\n");
return 0;
} else if (caller_type == OSMO_CC_PRESENT_ALLOWED && callerid[0]) {
PDEBUG(DDSP, DEBUG_INFO, "Sending caller ID '%s' via DTMF.\n", callerid);
} else {
PDEBUG(DDSP, DEBUG_INFO, "Not sending unavailable caller ID.\n");
return 0;
}
cid->data[0] = cid->use_dtmf;
memcpy(cid->data, callerid, clen);
cid->data[clen + 1] = 'C';
cid->pos = 0;
cid->wait = (int)(WAIT_RING_DTMF * (double)cid->samplerate);
cid->state = CID_STATE_WAIT_DTMF;
PDEBUG(DDSP, DEBUG_DEBUG, "Start sending callerid '%s' via DTMF, waiting to start DTMF transmission.\n", callerid);
}
return 0;
}
/* TE-ACK received, digit 'D' shall be recognised */
void callerid_te_ack(callerid_t *cid, char digit)
{
if (cid->state != CID_STATE_WAIT_TE_ACK)
return;
if (digit < 'A' || digit > 'D') {
PDEBUG(DDSP, DEBUG_DEBUG, "Ignoring digit '%c', this is not a valid TE-ACK signal.\n", digit);
return;
}
PDEBUG(DDSP, DEBUG_DEBUG, "Received valid TE-ACK digit '%c', wait to send FSK transmission.\n", digit);
/* wait for FSK */
cid->state = CID_STATE_WAIT_FSK;
cid->wait = (int)(WAIT_TEACK_FSK * (double)cid->samplerate);
}
/* send audio chunk.
* if return value is less than given length, the transmission is complete.
*/
int callerid_send(callerid_t *cid, sample_t *samples, int length)
{
int count = 0, ret;
int i;
again:
switch (cid->state) {
case CID_STATE_NULL:
break;
case CID_STATE_WAIT_DTAS:
case CID_STATE_WAIT_DTMF:
case CID_STATE_WAIT_FSK:
case CID_STATE_WAIT_TE_ACK:
case CID_STATE_WAIT_END:
if (length <= cid->wait) {
memset(samples, 0, sizeof(*samples) * length);
count += length;
cid->wait -= length;
break;
}
memset(samples, 0, sizeof(*samples) * cid->wait);
count += cid->wait;
samples += cid->wait;
length -= cid->wait;
cid->wait = 0;
switch (cid->state) {
case CID_STATE_WAIT_DTAS:
cid->state = CID_STATE_SEND_DTAS;
cid->dtas_phaseshift65536[0] = TONE_DTAS_1 / (double)cid->samplerate * 65536.0;
cid->dtas_phaseshift65536[1] = TONE_DTAS_2 / (double)cid->samplerate * 65536.0;
cid->dtas_phase65536[0] = 0.0;
cid->dtas_phase65536[1] = 0.0;
cid->dt_as_count = (int)(DTAS_DURATION * (double)cid->samplerate);
PDEBUG(DDSP, DEBUG_DEBUG, "Now start DT_AS transmission.\n");
break;
case CID_STATE_WAIT_DTMF:
cid->state = CID_STATE_SEND_DTMF;
PDEBUG(DDSP, DEBUG_DEBUG, "Now start DTMF transmission.\n");
dtmf_encode_set_tone(&cid->dtmf, cid->data[cid->pos++], DTMF_TONE_ON, DTMF_TONE_OFF);
break;
case CID_STATE_WAIT_FSK:
cid->state = CID_STATE_SEND_FSK;
PDEBUG(DDSP, DEBUG_DEBUG, "Now start FSK transmission.\n");
break;
case CID_STATE_WAIT_TE_ACK:
PDEBUG(DDSP, DEBUG_INFO, "No TE-ACK received. Phone does not seem to support off-hook caller ID.\n");
cid->state = CID_STATE_NULL;
break;
case CID_STATE_WAIT_END:
PDEBUG(DDSP, DEBUG_DEBUG, "Reestablish audio.\n");
cid->state = CID_STATE_NULL;
break;
default:
; /* should never happen */
}
if (cid->state == CID_STATE_NULL)
break;
goto again;
case CID_STATE_SEND_DTAS:
for (i = 0; i < length; i++) {
if (cid->dt_as_count == 0)
break;
cid->dt_as_count--;
*samples++ = cid->dtas_sine[(uint16_t)cid->dtas_phase65536[0]] + cid->dtas_sine[(uint16_t)cid->dtas_phase65536[1]];
cid->dtas_phase65536[0] += cid->dtas_phaseshift65536[0];
if (cid->dtas_phase65536[0] >= 65536.0)
cid->dtas_phase65536[0] -= 65536.0;
cid->dtas_phase65536[1] += cid->dtas_phaseshift65536[1];
if (cid->dtas_phase65536[1] >= 65536.0)
cid->dtas_phase65536[1] -= 65536.0;
}
length -= i;
count += i;
if (cid->dt_as_count == 0) {
/* wait for TE ACK */
cid->state = CID_STATE_WAIT_TE_ACK;
cid->wait = (int)(WAIT_DTAS_TEACK * (double)cid->samplerate);
goto again;
}
break;
case CID_STATE_SEND_DTMF:
ret = dtmf_encode(&cid->dtmf, samples, length);
count += ret;
samples += ret;
length -= ret;
if (!length)
break;
if (!cid->data[cid->pos]) {
PDEBUG(DDSP, DEBUG_DEBUG, "DTMF transmission done, waiting to re-establish audio.\n");
cid->wait = (int)(WAIT_DTMF_END * (double)cid->samplerate);
cid->state = CID_STATE_WAIT_END;
break;
}
dtmf_encode_set_tone(&cid->dtmf, cid->data[cid->pos++], DTMF_TONE_ON, DTMF_TONE_OFF);
goto again;
case CID_STATE_SEND_FSK:
ret = fsk_mod_send(&cid->fsk, samples, length, 0);
count += ret;
samples += ret;
length -= ret;
if (length) {
PDEBUG(DDSP, DEBUG_DEBUG, "FSK transmission done, waiting to re-establish audio.\n");
cid->wait = (int)(WAIT_FSK_END * (double)cid->samplerate);
cid->state = CID_STATE_WAIT_END;
break;
}
break;
}
return count;
}