Skip to content

Commit fa7d147

Browse files
InterLinked1Friendly Automation
authored and
Friendly Automation
committed
app_dtmfstore: New application to store digits
Adds application to asynchronously collect digits dialed on a channel in the TX or RX direction using a framehook and stores them in a specified variable, up to a configurable number of digits. ASTERISK-29477 Change-Id: I51aa93fc9507f7636ac44806c4420ce690423e6f
1 parent de3f535 commit fa7d147

File tree

2 files changed

+292
-0
lines changed

2 files changed

+292
-0
lines changed

apps/app_dtmfstore.c

Lines changed: 286 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,286 @@
1+
/*
2+
* Asterisk -- An open source telephony toolkit.
3+
*
4+
* Copyright (C) 2021, Naveen Albert
5+
*
6+
* Naveen Albert <asterisk@phreaknet.org>
7+
*
8+
* See http://www.asterisk.org for more information about
9+
* the Asterisk project. Please do not directly contact
10+
* any of the maintainers of this project for assistance;
11+
* the project provides a web site, mailing lists and IRC
12+
* channels for your use.
13+
*
14+
* This program is free software, distributed under the terms of
15+
* the GNU General Public License Version 2. See the LICENSE file
16+
* at the top of the source tree.
17+
*/
18+
19+
/*! \file
20+
*
21+
* \brief Technology independent asynchronous DTMF collection
22+
*
23+
* \author Naveen Albert <asterisk@phreaknet.org>
24+
*
25+
* \ingroup functions
26+
*
27+
*/
28+
29+
/*** MODULEINFO
30+
<support_level>extended</support_level>
31+
***/
32+
33+
#include "asterisk.h"
34+
35+
#include "asterisk/module.h"
36+
#include "asterisk/channel.h"
37+
#include "asterisk/pbx.h"
38+
#include "asterisk/framehook.h"
39+
#include "asterisk/app.h"
40+
#include "asterisk/conversions.h"
41+
42+
/*** DOCUMENTATION
43+
<application name="StoreDTMF" language="en_US">
44+
<synopsis>
45+
Stores DTMF digits transmitted or received on a channel.
46+
</synopsis>
47+
<syntax>
48+
<parameter name="direction" required="true">
49+
<para>Must be <literal>TX</literal> or <literal>RX</literal>.</para>
50+
</parameter>
51+
</syntax>
52+
<description>
53+
<para>The StoreDTMF function can be used to obtain digits sent in the
54+
<literal>TX</literal> or <literal>RX</literal> direction of any channel.</para>
55+
<para>The arguments are:</para>
56+
<para><replaceable>var_name</replaceable>: Name of variable to which to append
57+
digits.</para>
58+
<para><replaceable>max_digits</replaceable>: The maximum number of digits to
59+
store in the variable. Defaults to 0 (no maximum). After reading <literal>
60+
maximum</literal> digits, no more digits will be stored.</para>
61+
<para>For example:</para>
62+
<para>StoreDTMF(TX,CDR(digits))</para>
63+
<para>StoreDTMF(RX,testvar,24)</para>
64+
<para>StoreDTMF(remove)</para>
65+
</description>
66+
</application>
67+
***/
68+
69+
static char *app = "StoreDTMF";
70+
71+
/*! \brief Private data structure used with the function's datastore */
72+
struct dtmf_store_data {
73+
int framehook_id;
74+
char *rx_var;
75+
char *tx_var;
76+
int maxdigits;
77+
};
78+
79+
static void datastore_destroy_cb(void *data) {
80+
struct dtmf_store_data *d;
81+
d = data;
82+
if (d) {
83+
if (d->rx_var) {
84+
ast_free(d->rx_var);
85+
}
86+
if (d->tx_var) {
87+
ast_free(d->tx_var);
88+
}
89+
ast_free(data);
90+
}
91+
}
92+
93+
/*! \brief The channel datastore the function uses to store state */
94+
static const struct ast_datastore_info dtmf_store_datastore = {
95+
.type = "dtmf_store",
96+
.destroy = datastore_destroy_cb
97+
};
98+
99+
/*! \internal \brief Store digits tx/rx on the channel */
100+
static int remove_dtmf_store(struct ast_channel *chan)
101+
{
102+
struct ast_datastore *datastore = NULL;
103+
struct dtmf_store_data *data;
104+
SCOPED_CHANNELLOCK(chan_lock, chan);
105+
106+
datastore = ast_channel_datastore_find(chan, &dtmf_store_datastore, NULL);
107+
if (!datastore) {
108+
ast_log(AST_LOG_WARNING, "Cannot remove StoreDTMF from %s: StoreDTMF not currently enabled\n",
109+
ast_channel_name(chan));
110+
return -1;
111+
}
112+
data = datastore->data;
113+
114+
if (ast_framehook_detach(chan, data->framehook_id)) {
115+
ast_log(AST_LOG_WARNING, "Failed to remove StoreDTMF framehook from channel %s\n",
116+
ast_channel_name(chan));
117+
return -1;
118+
}
119+
120+
if (ast_channel_datastore_remove(chan, datastore)) {
121+
ast_log(AST_LOG_WARNING, "Failed to remove StoreDTMF datastore from channel %s\n",
122+
ast_channel_name(chan));
123+
return -1;
124+
}
125+
ast_datastore_free(datastore);
126+
127+
return 0;
128+
}
129+
130+
/*! \brief Frame hook that is called to intercept digit/undigit */
131+
static struct ast_frame *dtmf_store_framehook(struct ast_channel *chan,
132+
struct ast_frame *f, enum ast_framehook_event event, void *data)
133+
{
134+
char currentdata[512];
135+
char varnamesub[64];
136+
char *varname = NULL;
137+
struct dtmf_store_data *framedata = data;
138+
int len;
139+
140+
if (!f || !framedata) {
141+
return f;
142+
}
143+
144+
if ((event != AST_FRAMEHOOK_EVENT_WRITE) && (event != AST_FRAMEHOOK_EVENT_READ)) {
145+
return f;
146+
}
147+
148+
if (f->frametype != AST_FRAME_DTMF_END) {
149+
return f;
150+
}
151+
152+
/* If this is DTMF then store the digits */
153+
if (event == AST_FRAMEHOOK_EVENT_READ && framedata->rx_var) { /* coming from source */
154+
varname = framedata->rx_var;
155+
} else if (event == AST_FRAMEHOOK_EVENT_WRITE && framedata->tx_var) { /* going to source */
156+
varname = framedata->tx_var;
157+
}
158+
159+
if (!varname) {
160+
return f;
161+
}
162+
163+
sprintf(varnamesub, "${%s}", varname);
164+
pbx_substitute_variables_helper(chan, varnamesub, currentdata, 511);
165+
/* pbx_builtin_getvar_helper works for regular vars but not CDR vars */
166+
if (ast_strlen_zero(currentdata)) { /* var doesn't exist yet */
167+
ast_debug(3, "Creating new digit store: %s\n", varname);
168+
}
169+
len = strlen(currentdata);
170+
if (framedata->maxdigits > 0 && len >= framedata->maxdigits) {
171+
ast_debug(3, "Reached digit limit: %d\n", framedata->maxdigits);
172+
remove_dtmf_store(chan); /* reached max digit count, stop now */
173+
return f;
174+
} else {
175+
char newdata[len + 2]; /* one more char + terminator */
176+
if (len > 0) {
177+
ast_copy_string(newdata, currentdata, len + 2);
178+
}
179+
newdata[len] = (unsigned) f->subclass.integer;
180+
newdata[len + 1] = '\0';
181+
ast_debug(3, "Appending to digit store: now %s\n", newdata);
182+
pbx_builtin_setvar_helper(chan, varname, newdata);
183+
}
184+
return f;
185+
}
186+
187+
/*! \internal \brief Enable digit interception on the channel */
188+
static int dtmfstore_exec(struct ast_channel *chan, const char *appdata)
189+
{
190+
struct ast_datastore *datastore;
191+
struct dtmf_store_data *data;
192+
static struct ast_framehook_interface digit_framehook_interface = {
193+
.version = AST_FRAMEHOOK_INTERFACE_VERSION,
194+
.event_cb = dtmf_store_framehook,
195+
.disable_inheritance = 1,
196+
};
197+
char *parse = ast_strdupa(appdata);
198+
AST_DECLARE_APP_ARGS(args,
199+
AST_APP_ARG(direction);
200+
AST_APP_ARG(varname);
201+
AST_APP_ARG(maxdigits);
202+
);
203+
SCOPED_CHANNELLOCK(chan_lock, chan);
204+
AST_STANDARD_APP_ARGS(args, parse);
205+
206+
if (ast_strlen_zero(appdata)) {
207+
ast_log(AST_LOG_WARNING, "StoreDTMF requires an argument\n");
208+
return -1;
209+
}
210+
211+
if (!strcasecmp(args.direction, "remove")) {
212+
return remove_dtmf_store(chan);
213+
}
214+
215+
datastore = ast_channel_datastore_find(chan, &dtmf_store_datastore, NULL);
216+
if (datastore) {
217+
ast_log(AST_LOG_WARNING, "StoreDTMF already set on '%s'\n",
218+
ast_channel_name(chan));
219+
return 0;
220+
}
221+
222+
datastore = ast_datastore_alloc(&dtmf_store_datastore, NULL);
223+
if (!datastore) {
224+
return -1;
225+
}
226+
227+
data = ast_calloc(1, sizeof(*data));
228+
if (!data) {
229+
ast_datastore_free(datastore);
230+
return -1;
231+
}
232+
233+
digit_framehook_interface.data = data;
234+
235+
data->rx_var = NULL;
236+
data->tx_var = NULL;
237+
data->maxdigits = 0;
238+
239+
if (!strcasecmp(args.direction, "tx")) {
240+
data->tx_var = ast_strdup(args.varname);
241+
} else if (!strcasecmp(args.direction, "rx")) {
242+
data->rx_var = ast_strdup(args.varname);
243+
} else {
244+
ast_log(LOG_ERROR, "Direction must be either RX or TX\n");
245+
return -1;
246+
}
247+
248+
if (!ast_strlen_zero(args.maxdigits)) {
249+
if (ast_str_to_int(args.maxdigits,&(data->maxdigits))) {
250+
ast_log(LOG_ERROR, "Invalid integer: %s\n", args.maxdigits);
251+
return -1;
252+
}
253+
if (data->maxdigits < 0) {
254+
ast_log(LOG_ERROR, "Invalid natural number: %d\n", data->maxdigits);
255+
return -1;
256+
} else if (data->maxdigits == 0) {
257+
ast_log(LOG_WARNING, "No maximum digit count set\n");
258+
}
259+
}
260+
261+
data->framehook_id = ast_framehook_attach(chan, &digit_framehook_interface);
262+
if (data->framehook_id < 0) {
263+
ast_log(AST_LOG_WARNING, "Failed to attach StoreDTMF framehook to '%s'\n",
264+
ast_channel_name(chan));
265+
ast_datastore_free(datastore);
266+
ast_free(data);
267+
return -1;
268+
}
269+
datastore->data = data;
270+
271+
ast_channel_datastore_add(chan, datastore);
272+
273+
return 0;
274+
}
275+
276+
static int unload_module(void)
277+
{
278+
return ast_unregister_application(app);
279+
}
280+
281+
static int load_module(void)
282+
{
283+
return ast_register_application_xml(app, dtmfstore_exec);
284+
}
285+
286+
AST_MODULE_INFO_STANDARD_EXTENDED(ASTERISK_GPL_KEY, "Technology independent async DTMF storage");

doc/CHANGES-staging/app_dtmfstore.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Subject: app_dtmfstore
2+
3+
New application which collects digits
4+
dialed and stores them into
5+
a specified variable.
6+

0 commit comments

Comments
 (0)