base-call: handle multiple DTMF characters

Send each character through the state machine one at a time. Some
modems (various Quectel ones for example) can process multiple at
the same time, and while that should be easily implemented using
the new changes to send_dtmf() it's not done in any plugin yet.

Signed-off-by: Dan Williams <dan@ioncontrol.co>
This commit is contained in:
Dan Williams
2025-05-30 08:02:48 -05:00
parent 56fe8e7d31
commit ae3d37030a
5 changed files with 81 additions and 23 deletions
@@ -103,10 +103,15 @@
<!-- <!--
SendDtmf: SendDtmf:
@dtmf: DTMF tone identifier [0-9A-D*#] or pause character [,]. @dtmf: A string of DTMF tone identifiers [0-9A-D*#] and/or pause characters [,].
Send a DTMF tone (Dual Tone Multi-Frequency) (only on supported modems). Send one or more DTMF tones (Dual Tone Multi-Frequency) (only on supported modems).
Since 1.26 the comma [,] is interpreted as a two-second pause. Before 1.26 only the first character in @dtmf was sent to the modem;
all others were discarded.
Since 1.26 up to 50 tone identifiers are accepted and each will be
sent to the modem in the order given. The comma [,] character pauses
DTMF tones for two-seconds then continues with the remaining characters.
Applicable only if state is <link linkend="MM-CALL-STATE-ACTIVE:CAPS"><constant>MM_CALL_STATE_ACTIVE</constant></link>. Applicable only if state is <link linkend="MM-CALL-STATE-ACTIVE:CAPS"><constant>MM_CALL_STATE_ACTIVE</constant></link>.
+64 -13
View File
@@ -736,6 +736,7 @@ typedef enum {
DTMF_STEP_START, DTMF_STEP_START,
DTMF_STEP_TIMEOUT, DTMF_STEP_TIMEOUT,
DTMF_STEP_STOP, DTMF_STEP_STOP,
DTMF_STEP_NEXT,
DTMF_STEP_LAST, DTMF_STEP_LAST,
} DtmfStep; } DtmfStep;
@@ -743,7 +744,12 @@ typedef struct {
DtmfStep step; DtmfStep step;
GError *saved_error; GError *saved_error;
guint8 call_id; guint8 call_id;
gchar *dtmf; /* Array of DTMF runs; split by pauses */
GPtrArray *dtmfs;
/* index into dtmfs */
guint cur_dtmf;
/* index into cur dtmf run string */
gchar *cur_tone;
guint timeout_id; guint timeout_id;
} SendDtmfContext; } SendDtmfContext;
@@ -760,7 +766,8 @@ static void
send_dtmf_context_free (SendDtmfContext *ctx) send_dtmf_context_free (SendDtmfContext *ctx)
{ {
send_dtmf_context_clear_timeout (ctx); send_dtmf_context_clear_timeout (ctx);
g_free (ctx->dtmf); g_ptr_array_foreach (ctx->dtmfs, (GFunc) g_free, NULL);
g_ptr_array_free (ctx->dtmfs, TRUE);
g_assert (!ctx->saved_error); g_assert (!ctx->saved_error);
g_slice_free (SendDtmfContext, ctx); g_slice_free (SendDtmfContext, ctx);
} }
@@ -831,6 +838,10 @@ dtmf_timeout (GTask *task)
ctx = g_task_get_task_data (task); ctx = g_task_get_task_data (task);
/* If this was a pause character; move past it */
if (ctx->cur_tone[0] == MM_CALL_DTMF_PAUSE_CHAR)
ctx->cur_tone++;
send_dtmf_context_clear_timeout (ctx); send_dtmf_context_clear_timeout (ctx);
ctx->step++; ctx->step++;
send_dtmf_task_step_next (task); send_dtmf_task_step_next (task);
@@ -845,17 +856,21 @@ send_dtmf_ready (MMBaseCall *self,
{ {
SendDtmfContext *ctx; SendDtmfContext *ctx;
GError *error = NULL; GError *error = NULL;
gboolean success; gssize num_sent;
ctx = g_task_get_task_data (task); ctx = g_task_get_task_data (task);
success = MM_BASE_CALL_GET_CLASS (self)->send_dtmf_finish (self, res, &error); num_sent = MM_BASE_CALL_GET_CLASS (self)->send_dtmf_finish (self, res, &error);
if (ctx->step == DTMF_STEP_START) { if (ctx->step == DTMF_STEP_START) {
if (!success) { if (num_sent < 0) {
g_propagate_error (&ctx->saved_error, error); g_propagate_error (&ctx->saved_error, error);
ctx->step = DTMF_STEP_LAST; ctx->step = DTMF_STEP_LAST;
} else } else {
g_assert (num_sent > 0);
g_assert ((guint) num_sent <= strlen (ctx->cur_tone));
ctx->cur_tone += num_sent;
ctx->step++; ctx->step++;
}
send_dtmf_task_step_next (task); send_dtmf_task_step_next (task);
} }
@@ -875,7 +890,7 @@ send_dtmf_task_step_next (GTask *task)
self = g_task_get_source_object (task); self = g_task_get_source_object (task);
ctx = g_task_get_task_data (task); ctx = g_task_get_task_data (task);
is_pause = (ctx->dtmf[0] == MM_CALL_DTMF_PAUSE_CHAR); is_pause = (ctx->cur_tone[0] == MM_CALL_DTMF_PAUSE_CHAR);
need_stop = MM_BASE_CALL_GET_CLASS (self)->stop_dtmf && need_stop = MM_BASE_CALL_GET_CLASS (self)->stop_dtmf &&
MM_BASE_CALL_GET_CLASS (self)->stop_dtmf_finish; MM_BASE_CALL_GET_CLASS (self)->stop_dtmf_finish;
@@ -886,7 +901,7 @@ send_dtmf_task_step_next (GTask *task)
case DTMF_STEP_START: case DTMF_STEP_START:
if (!is_pause) { if (!is_pause) {
MM_BASE_CALL_GET_CLASS (self)->send_dtmf (self, MM_BASE_CALL_GET_CLASS (self)->send_dtmf (self,
ctx->dtmf, ctx->cur_tone,
(GAsyncReadyCallback)send_dtmf_ready, (GAsyncReadyCallback)send_dtmf_ready,
g_object_ref (task)); g_object_ref (task));
return; return;
@@ -914,6 +929,22 @@ send_dtmf_task_step_next (GTask *task)
return; return;
} }
/* Fall through */ /* Fall through */
case DTMF_STEP_NEXT:
/* Advance to next DTMF run? */
if (ctx->cur_tone[0] == '\0') {
ctx->cur_dtmf++;
if (ctx->cur_dtmf < ctx->dtmfs->len)
ctx->cur_tone = g_ptr_array_index (ctx->dtmfs, ctx->cur_dtmf);
}
/* More to send? */
if (ctx->cur_tone[0]) {
ctx->step = DTMF_STEP_START;
send_dtmf_task_step_next (task);
return;
}
/* no more DTMF characters to send */
/* Fall through */
case DTMF_STEP_LAST: case DTMF_STEP_LAST:
send_dtmf_context_clear_timeout (ctx); send_dtmf_context_clear_timeout (ctx);
if (ctx->saved_error) if (ctx->saved_error)
@@ -948,9 +979,9 @@ send_dtmf_task_new (MMBaseCall *self,
gpointer user_data, gpointer user_data,
GError **error) GError **error)
{ {
GTask *task; GTask *task;
SendDtmfContext *ctx; SendDtmfContext *ctx;
guint8 call_id; guint8 call_id;
call_id = mm_base_call_get_index (self); call_id = mm_base_call_get_index (self);
if (call_id == 0) { if (call_id == 0) {
@@ -962,9 +993,13 @@ send_dtmf_task_new (MMBaseCall *self,
} }
task = g_task_new (self, NULL, callback, user_data); task = g_task_new (self, NULL, callback, user_data);
ctx = g_slice_new0 (SendDtmfContext); ctx = g_slice_new0 (SendDtmfContext);
ctx->call_id = call_id; ctx->call_id = call_id;
ctx->dtmf = g_strdup (dtmf); /* Split DTMF into runs of DTMF characters interrupted by pauses */
ctx->dtmfs = mm_dtmf_split (dtmf);
g_assert (ctx->dtmfs->len > 0);
ctx->cur_tone = g_ptr_array_index (ctx->dtmfs, ctx->cur_dtmf);
g_task_set_task_data (task, ctx, (GDestroyNotify) send_dtmf_context_free); g_task_set_task_data (task, ctx, (GDestroyNotify) send_dtmf_context_free);
return task; return task;
@@ -1021,6 +1056,22 @@ handle_send_dtmf_auth_ready (MMAuthProvider *authp,
state = mm_gdbus_call_get_state (MM_GDBUS_CALL (ctx->self)); state = mm_gdbus_call_get_state (MM_GDBUS_CALL (ctx->self));
/* Ensure there are DTMF characters to send */
if (!ctx->dtmf || !ctx->dtmf[0]) {
mm_dbus_method_invocation_return_error_literal (ctx->invocation, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS,
"No DTMF characters given");
handle_send_dtmf_context_free (ctx);
return;
}
/* And that there aren't too many */
if (strlen (ctx->dtmf) > 50) {
mm_dbus_method_invocation_return_error_literal (ctx->invocation, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS,
"Too many DTMF characters");
handle_send_dtmf_context_free (ctx);
return;
}
/* Check if we do support doing DTMF at all */ /* Check if we do support doing DTMF at all */
if (!MM_BASE_CALL_GET_CLASS (ctx->self)->send_dtmf || if (!MM_BASE_CALL_GET_CLASS (ctx->self)->send_dtmf ||
!MM_BASE_CALL_GET_CLASS (ctx->self)->send_dtmf_finish) { !MM_BASE_CALL_GET_CLASS (ctx->self)->send_dtmf_finish) {
@@ -1031,7 +1082,7 @@ handle_send_dtmf_auth_ready (MMAuthProvider *authp,
} }
/* We can only send_dtmf when call is in ACTIVE state */ /* We can only send_dtmf when call is in ACTIVE state */
if (state != MM_CALL_STATE_ACTIVE ){ if (state != MM_CALL_STATE_ACTIVE) {
mm_dbus_method_invocation_return_error_literal (ctx->invocation, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, mm_dbus_method_invocation_return_error_literal (ctx->invocation, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
"This call was not active, cannot send dtmf"); "This call was not active, cannot send dtmf");
handle_send_dtmf_context_free (ctx); handle_send_dtmf_context_free (ctx);
+2 -1
View File
@@ -95,7 +95,8 @@ struct _MMBaseCallClass {
const gchar *dtmf, const gchar *dtmf,
GAsyncReadyCallback callback, GAsyncReadyCallback callback,
gpointer user_data); gpointer user_data);
gboolean (* send_dtmf_finish) (MMBaseCall *self, /* Returns the number of DTMF characters sent, or < 1 on error */
gssize (* send_dtmf_finish) (MMBaseCall *self,
GAsyncResult *res, GAsyncResult *res,
GError **error); GError **error);
+4 -3
View File
@@ -312,12 +312,12 @@ call_hangup (MMBaseCall *_self,
/*****************************************************************************/ /*****************************************************************************/
/* Send DTMF tone to call */ /* Send DTMF tone to call */
static gboolean static gssize
call_send_dtmf_finish (MMBaseCall *self, call_send_dtmf_finish (MMBaseCall *self,
GAsyncResult *res, GAsyncResult *res,
GError **error) GError **error)
{ {
return g_task_propagate_boolean (G_TASK (res), error); return g_task_propagate_int (G_TASK (res), error);
} }
static void static void
@@ -338,7 +338,8 @@ call_send_dtmf_ready (MMBaseModem *modem,
return; return;
} }
g_task_return_boolean (task, TRUE); /* We sent one character */
g_task_return_int (task, 1);
g_object_unref (task); g_object_unref (task);
} }
+3 -3
View File
@@ -432,12 +432,12 @@ call_stop_dtmf (MMBaseCall *_self,
task); task);
} }
static gboolean static gssize
call_send_dtmf_finish (MMBaseCall *call, call_send_dtmf_finish (MMBaseCall *call,
GAsyncResult *res, GAsyncResult *res,
GError **error) GError **error)
{ {
return g_task_propagate_boolean (G_TASK (res), error); return g_task_propagate_int (G_TASK (res), error);
} }
static void static void
@@ -456,7 +456,7 @@ voice_start_continuous_dtmf_ready (QmiClientVoice *client,
g_prefix_error (&error, "Couldn't send DTMF character: "); g_prefix_error (&error, "Couldn't send DTMF character: ");
g_task_return_error (task, error); g_task_return_error (task, error);
} else { } else {
g_task_return_boolean (task, TRUE); g_task_return_int (task, 1);
} }
g_object_unref (task); g_object_unref (task);
} }