chan_dahdi: Add DAHDI_CHANNEL function.

Add a dialplan function that can be used to get/set properties of
DAHDI channels (as opposed to Asterisk channels). This exposes
properties that were not previously available, allowing for certain
operations to now be performed in the dialplan.

Resolves: #1455

UserNote: The DAHDI_CHANNEL function allows for getting/setting
certain properties about DAHDI channels from the dialplan.
This commit is contained in:
Naveen Albert
2025-09-11 15:57:03 -04:00
parent 8adbad81f7
commit 131a6730b2
2 changed files with 262 additions and 81 deletions

View File

@@ -220,6 +220,61 @@
</example>
</description>
</function>
<function name="DAHDI_CHANNEL" language="en_US">
<synopsis>
Set or get a property of a DAHDI channel.
</synopsis>
<syntax>
<parameter name="property" required="true">
<para>The property to set or get.</para>
<enumlist>
<enum name="owner">
<para>R/O The name of the active channel on this DAHDI device.</para>
</enum>
<enum name="callwait">
<para>R/O The name of the call waiting channel on this DAHDI device.</para>
</enum>
<enum name="threeway">
<para>R/O The name of the three-way channel on this DAHDI device.</para>
</enum>
<enum name="polarity">
<para>R/W The polarity of the channel (0 or 1, idle or reverse can also be used for setting).</para>
<para>Only valid on FXO-signalled channels.</para>
</enum>
<enum name="dnd">
<para>R/W Whether Do Not Disturb is active.</para>
<para>Only valid on FXO-signalled channels.</para>
</enum>
<enum name="callforward">
<para>R/W The call forwarding target number.</para>
<para>Only valid on FXO-signalled channels.</para>
</enum>
<enum name="lastexten">
<para>R/W The last number dialed.</para>
<para>Only valid on FXO-signalled channels.</para>
</enum>
</enumlist>
</parameter>
<parameter name="channel" required="false">
<para>The DAHDI channel number.</para>
<para>If not specified, the DAHDI channel number of the current channel
will be used (in which case it must be a DAHDI channel).</para>
</parameter>
</syntax>
<description>
<para>The DAHDI_CHANNEL function can be used to set or get properties of DAHDI channels
that are not specific to Asterisk channels.</para>
<para>This function may also be called from non-DAHDI channels.</para>
<example title="Set reverse polarity on current DAHDI channel">
same => n,Set(DAHDI_CHANNEL(polarity)=1)
same => n,NoOp(${DAHDI_CHANNEL(polarity)})
</example>
<example title="Set call forwarding target for channel 1">
same => n,Set(DAHDI_CHANNEL(callforwardnumber,1)=5552368)
same => n,NoOp(Calls now forwarding to ${DAHDI_CHANNEL(callforwardnumber,1)})
</example>
</description>
</function>
<info name="CHANNEL" language="en_US" tech="DAHDI">
<enumlist>
<enum name="dahdi_channel">
@@ -2913,86 +2968,6 @@ static void my_hangup_polarityswitch(void *pvt)
}
}
/*! \brief Return DAHDI pivot if channel is FXO signalled */
static struct dahdi_pvt *fxo_pvt(struct ast_channel *chan)
{
int res;
struct dahdi_params dahdip;
struct dahdi_pvt *pvt = NULL;
if (strcasecmp(ast_channel_tech(chan)->type, "DAHDI")) {
ast_log(LOG_WARNING, "%s is not a DAHDI channel\n", ast_channel_name(chan));
return NULL;
}
memset(&dahdip, 0, sizeof(dahdip));
res = ioctl(ast_channel_fd(chan, 0), DAHDI_GET_PARAMS, &dahdip);
if (res) {
ast_log(LOG_WARNING, "Unable to get parameters of %s: %s\n", ast_channel_name(chan), strerror(errno));
return NULL;
}
if (!(dahdip.sigtype & __DAHDI_SIG_FXO)) {
ast_log(LOG_WARNING, "%s is not FXO signalled\n", ast_channel_name(chan));
return NULL;
}
pvt = ast_channel_tech_pvt(chan);
if (!dahdi_analog_lib_handles(pvt->sig, 0, 0)) {
ast_log(LOG_WARNING, "Channel signalling is not analog");
return NULL;
}
return pvt;
}
static int polarity_read(struct ast_channel *chan, const char *cmd, char *data, char *buffer, size_t buflen)
{
struct dahdi_pvt *pvt;
pvt = fxo_pvt(chan);
if (!pvt) {
return -1;
}
snprintf(buffer, buflen, "%d", pvt->polarity);
return 0;
}
static int polarity_write(struct ast_channel *chan, const char *cmd, char *data, const char *value)
{
struct dahdi_pvt *pvt;
int polarity;
pvt = fxo_pvt(chan);
if (!pvt) {
return -1;
}
if (!strcasecmp(value, "idle")) {
polarity = POLARITY_IDLE;
} else if (!strcasecmp(value, "reverse")) {
polarity = POLARITY_REV;
} else {
polarity = atoi(value);
}
if (polarity != POLARITY_IDLE && polarity != POLARITY_REV) {
ast_log(LOG_WARNING, "Invalid polarity: '%s'\n", value);
return -1;
}
my_set_polarity(pvt, polarity);
return 0;
}
static struct ast_custom_function polarity_function = {
.name = "POLARITY",
.write = polarity_write,
.read = polarity_read,
};
static int my_start(void *pvt)
{
struct dahdi_pvt *p = pvt;
@@ -16905,6 +16880,206 @@ static struct dahdi_pvt *find_channel_from_str(const char *channel)
return find_channel(chan_num);
}
static int print_subchannel(struct dahdi_pvt *p, int subchan, char *buffer, size_t len)
{
if (!p->subs[subchan].owner) {
return -1;
}
ast_channel_lock(p->subs[subchan].owner);
snprintf(buffer, len, "%s", ast_channel_name(p->subs[subchan].owner));
ast_channel_unlock(p->subs[subchan].owner);
return 0;
}
#define REQUIRE_FXO_SIG() \
if (!(dahdip->sigtype & __DAHDI_SIG_FXO)) { \
ast_log(LOG_WARNING, "DAHDI channel %d is not FXO signalled\n", p->channel); \
return -1; \
}
static int dahdichan_read_property(struct dahdi_pvt *p, struct dahdi_params *dahdip, const char *property, char *buffer, size_t len)
{
struct analog_pvt *analog_p = p->sig_pvt;
/* R/O properties */
if (!strcasecmp(property, "owner")) {
return print_subchannel(p, SUB_REAL, buffer, len);
} else if (!strcasecmp(property, "callwait")) {
return print_subchannel(p, SUB_CALLWAIT, buffer, len);
} else if (!strcasecmp(property, "threeway")) {
return print_subchannel(p, SUB_THREEWAY, buffer, len);
/* R/W properties */
} else if (!strcasecmp(property, "polarity")) {
REQUIRE_FXO_SIG();
snprintf(buffer, len, "%d", p->polarity);
} else if (!strcasecmp(property, "dnd")) {
REQUIRE_FXO_SIG();
snprintf(buffer, len, "%d", analog_p->dnd);
} else if (!strcasecmp(property, "callforward")) {
REQUIRE_FXO_SIG();
snprintf(buffer, len, "%s", analog_p->call_forward);
} else if (!strcasecmp(property, "lastexten")) {
REQUIRE_FXO_SIG();
snprintf(buffer, len, "%s", analog_p->lastexten);
} else {
ast_log(LOG_ERROR, "Unknown DAHDI_CHANNEL property '%s'\n", property);
return -1;
}
return 0;
}
static int dahdichan_write_property(struct dahdi_pvt *p, struct dahdi_params *dahdip, const char *property, const char *value)
{
struct analog_pvt *analog_p = p->sig_pvt;
/* We don't need to check ast_strlen_zero(value) because it's obviously not NULL.
* It may even be okay for it to be an empty string, but that's a per-setting thing. */
/* R/O properties */
if (!strcasecmp(property, "owner") || !strcasecmp(property, "callwait") || !strcasecmp(property, "threeway")) {
ast_log(LOG_ERROR, "DAHDI subchannel names are R/O\n");
return -1;
/* R/W properties */
} else if (!strcasecmp(property, "polarity")) {
int polarity = atoi(value);
REQUIRE_FXO_SIG();
if (polarity != POLARITY_IDLE && polarity != POLARITY_REV) {
ast_log(LOG_ERROR, "Invalid polarity: '%s'\n", value);
return -1;
}
my_set_polarity(p, polarity);
} else if (!strcasecmp(property, "dnd")) {
int dnd = atoi(value);
REQUIRE_FXO_SIG();
analog_dnd(analog_p, dnd ? 1 : 0);
} else if (!strcasecmp(property, "callforward")) {
REQUIRE_FXO_SIG();
if (strlen(value) >= sizeof(analog_p->call_forward) - 1) {
ast_log(LOG_ERROR, "Provided call forwarding target '%s' is too long\n", value);
}
ast_copy_string(analog_p->call_forward, value, sizeof(analog_p->call_forward)); /* Could be empty to clear value */
} else if (!strcasecmp(property, "lastexten")) {
REQUIRE_FXO_SIG();
if (strlen(value) >= sizeof(analog_p->lastexten) - 1) {
ast_log(LOG_ERROR, "Provided lastexten target '%s' is too long\n", value);
}
ast_copy_string(analog_p->lastexten, value, sizeof(analog_p->lastexten)); /* Could be empty to clear value */
} else {
ast_log(LOG_ERROR, "Unknown DAHDI_CHANNEL property '%s'\n", property);
return -1;
}
return 0;
}
#undef REQUIRE_FXO_SIG
static int dahdichan_helper(struct ast_channel *chan, char *data, const char *value, char *buffer, size_t buflen)
{
char *parse;
struct dahdi_pvt *pvt;
struct dahdi_params dahdip;
int res;
AST_DECLARE_APP_ARGS(args,
AST_APP_ARG(property);
AST_APP_ARG(dahdichan);
);
parse = ast_strdupa(data);
AST_STANDARD_APP_ARGS(args, parse);
if (buflen > 0) {
*buffer = '\0';
}
if (!ast_strlen_zero(args.dahdichan)) {
/* DAHDI channel number explicitly provided, find it. */
int channo = atoi(args.dahdichan);
pvt = find_channel(channo);
if (!pvt) {
ast_log(LOG_ERROR, "DAHDI channel %d does not exist\n", channo);
return -1;
}
} else {
/* No channel specified explicitly, so implicitly use the current channel, in which case it must be a DAHDI channel. */
if (!chan || !ast_channel_tech(chan) || strcasecmp(ast_channel_tech(chan)->type, "DAHDI")) {
ast_log(LOG_WARNING, "%s is not a DAHDI channel, and no DAHDI channel specified\n", ast_channel_name(chan));
return -1;
}
pvt = ast_channel_tech_pvt(chan);
}
memset(&dahdip, 0, sizeof(dahdip));
if (ioctl(pvt->subs[SUB_REAL].dfd, DAHDI_GET_PARAMS, &dahdip)) {
ast_log(LOG_WARNING, "Unable to get parameters of DAHDI channel %d: %s\n", pvt->channel, strerror(errno));
return -1;
}
/* We have the channel private to use. */
ast_mutex_lock(&pvt->lock);
if (value) {
res = dahdichan_write_property(pvt, &dahdip, args.property, value);
} else {
res = dahdichan_read_property(pvt, &dahdip, args.property, buffer, buflen);
}
ast_mutex_unlock(&pvt->lock);
return res;
}
static int dahdichan_read(struct ast_channel *chan, const char *cmd, char *data, char *buffer, size_t buflen)
{
return dahdichan_helper(chan, data, NULL, buffer, buflen);
}
static int dahdichan_write(struct ast_channel *chan, const char *cmd, char *data, const char *value)
{
return dahdichan_helper(chan, data, value, NULL, 0);
}
static struct ast_custom_function dahdichan_function = {
.name = "DAHDI_CHANNEL",
.write = dahdichan_write,
.read = dahdichan_read,
};
/*! \todo The standalone POLARITY function can and should be deprecated/removed, since its functionality is now part of DAHDI_CHANNEL. */
static int polarity_read(struct ast_channel *chan, const char *cmd, char *data, char *buffer, size_t buflen)
{
struct dahdi_params dahdip;
struct dahdi_pvt *pvt = ast_channel_tech_pvt(chan);
if (strcasecmp(ast_channel_tech(chan)->type, "DAHDI")) {
ast_log(LOG_WARNING, "%s is not a DAHDI channel\n", ast_channel_name(chan));
return -1;
}
memset(&dahdip, 0, sizeof(dahdip));
if (ioctl(pvt->subs[SUB_REAL].dfd, DAHDI_GET_PARAMS, &dahdip)) {
ast_log(LOG_WARNING, "Unable to get parameters of DAHDI channel %d: %s\n", pvt->subs[SUB_REAL].dfd, strerror(errno));
return -1;
}
return dahdichan_read_property(ast_channel_tech_pvt(chan), &dahdip, "polarity", buffer, buflen);
}
static int polarity_write(struct ast_channel *chan, const char *cmd, char *data, const char *value)
{
struct dahdi_params dahdip;
struct dahdi_pvt *pvt = ast_channel_tech_pvt(chan);
if (strcasecmp(ast_channel_tech(chan)->type, "DAHDI")) {
ast_log(LOG_WARNING, "%s is not a DAHDI channel\n", ast_channel_name(chan));
return -1;
}
memset(&dahdip, 0, sizeof(dahdip));
if (ioctl(pvt->subs[SUB_REAL].dfd, DAHDI_GET_PARAMS, &dahdip)) {
ast_log(LOG_WARNING, "Unable to get parameters of DAHDI channel %d: %s\n", pvt->subs[SUB_REAL].dfd, strerror(errno));
return -1;
}
return dahdichan_write_property(ast_channel_tech_pvt(chan), &dahdip, "polarity", value);
}
static struct ast_custom_function polarity_function = {
.name = "POLARITY",
.write = polarity_write,
.read = polarity_read,
};
static int action_dahdidndon(struct mansession *s, const struct message *m)
{
struct dahdi_pvt *p;
@@ -18258,6 +18433,7 @@ static int __unload_module(void)
ast_unregister_application(dahdi_accept_r2_call_app);
#endif
ast_custom_function_unregister(&dahdichan_function);
ast_custom_function_unregister(&polarity_function);
ast_cli_unregister_multiple(dahdi_cli, ARRAY_LEN(dahdi_cli));
@@ -20483,6 +20659,7 @@ static int load_module(void)
ast_register_application_xml(dahdi_accept_r2_call_app, dahdi_accept_r2_call_exec);
#endif
ast_custom_function_register(&dahdichan_function);
ast_custom_function_register(&polarity_function);
ast_cli_register_multiple(dahdi_cli, ARRAY_LEN(dahdi_cli));

View File

@@ -2452,7 +2452,9 @@ static void *__analog_ss_thread(void *data)
/* Last Number Redial */
if (!ast_strlen_zero(p->lastexten)) {
ast_verb(4, "Redialing last number dialed on channel %d\n", p->channel);
analog_lock_private(p);
ast_copy_string(exten, p->lastexten, sizeof(exten));
analog_unlock_private(p);
} else {
ast_verb(3, "Last Number Redial not possible on channel %d (no saved number)\n", p->channel);
res = analog_play_tone(p, idx, ANALOG_TONE_CONGESTION);
@@ -2465,8 +2467,10 @@ static void *__analog_ss_thread(void *data)
if (!res || !ast_matchmore_extension(chan, ast_channel_context(chan), exten, 1, p->cid_num)) {
if (getforward) {
/* Record this as the forwarding extension */
analog_lock_private(p);
ast_copy_string(p->call_forward, exten, sizeof(p->call_forward));
ast_verb(3, "Setting call forward to '%s' on channel %d\n", p->call_forward, p->channel);
analog_unlock_private(p);
ast_verb(3, "Setting call forward to '%s' on channel %d\n", exten, p->channel);
res = analog_play_tone(p, idx, ANALOG_TONE_DIALRECALL);
if (res) {
break;