aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--channels/Makefile2
-rw-r--r--channels/chan_dect.c992
-rw-r--r--configs/dect.conf.sample50
-rw-r--r--configure.ac3
4 files changed, 1047 insertions, 0 deletions
diff --git a/channels/Makefile b/channels/Makefile
index f9b5b3ad7..245344e9c 100644
--- a/channels/Makefile
+++ b/channels/Makefile
@@ -90,6 +90,8 @@ chan_h323.so: chan_h323.o h323/libchanh323.a
endif
endif
+chan_dect.so: LIBS+=-ldect
+
chan_misdn.o: _ASTCFLAGS+=-Imisdn
misdn_config.o: _ASTCFLAGS+=-Imisdn
diff --git a/channels/chan_dect.c b/channels/chan_dect.c
new file mode 100644
index 000000000..2d361c87b
--- /dev/null
+++ b/channels/chan_dect.c
@@ -0,0 +1,992 @@
+/*
+ * Asterisk DECT Channel driver
+ *
+ * Copyright (c) 2009 Patrick McHardy <kaber@trash.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include "asterisk.h"
+#include "asterisk/lock.h"
+#include "asterisk/channel.h"
+#include "asterisk/config.h"
+#include "asterisk/module.h"
+#include "asterisk/pbx.h"
+#include "asterisk/sched.h"
+#include "asterisk/io.h"
+#include "asterisk/cli.h"
+#include "asterisk/causes.h"
+#include "asterisk/abstract_jb.h"
+
+#include <dect/libdect.h>
+#include <dect/terminal.h>
+
+#define CONFIG_FILE "dect.conf"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision")
+
+static const struct ast_channel_tech dect_tech;
+static struct dect_handle *dh;
+static struct sched_context *sched;
+static struct io_context *io;
+
+static const struct ast_jb_conf dect_default_jbconf = {
+ .max_size = -1,
+ .resync_threshold = -1,
+ .impl = "",
+};
+
+struct {
+ struct ast_jb_conf jbconf;
+ char default_context[AST_MAX_CONTEXT];
+} dect_cfg;
+
+static AST_LIST_HEAD_STATIC(dect_pt_list, dect_pt);
+
+struct dect_pt {
+ AST_LIST_ENTRY(dect_pt) list;
+ char name[64];
+ struct dect_ipui ipui;
+
+ AST_DECLARE_STRING_FIELDS( AST_STRING_FIELD(regexten);
+ AST_STRING_FIELD(context);
+ AST_STRING_FIELD(language);
+ AST_STRING_FIELD(cid_num);
+ AST_STRING_FIELD(cid_name);
+ AST_STRING_FIELD(ring_pattern);
+ );
+
+ unsigned int display_lines;
+ unsigned int display_columns;
+};
+
+struct dect_pvt {
+ struct dect_pt *pt;
+ struct dect_call *call;
+ struct ast_channel *chan;
+
+ AST_DECLARE_STRING_FIELDS( AST_STRING_FIELD(cid_num);
+ AST_STRING_FIELD(cid_name);
+ AST_STRING_FIELD(display);
+ );
+ int timer_id;
+};
+
+static int dect_release_reason_to_ast(enum dect_release_reasons reason)
+{
+ switch (reason) {
+ case DECT_RELEASE_NORMAL:
+ return AST_CAUSE_NORMAL_CLEARING;
+ case DECT_RELEASE_UNEXPECTED_MESSAGE:
+ case DECT_RELEASE_UNKNOWN_TRANSACTION_IDENTIFIER:
+ return AST_CAUSE_INVALID_MSG_UNSPECIFIED;
+ case DECT_RELEASE_MANDATORY_IE_MISSING:
+ return AST_CAUSE_MANDATORY_IE_MISSING;
+ case DECT_RELEASE_INVALID_IE_CONTENTS:
+ return AST_CAUSE_INVALID_IE_CONTENTS;
+ case DECT_RELEASE_INCOMPATIBLE_SERVICE:
+ case DECT_RELEASE_SERVICE_NOT_IMPLEMENTED:
+ case DECT_RELEASE_NEGOTIATION_NOT_SUPPORTED:
+ case DECT_RELEASE_INVALID_IDENTITY:
+ case DECT_RELEASE_AUTHENTICATION_FAILED:
+ case DECT_RELEASE_UNKNOWN_IDENTITY:
+ case DECT_RELEASE_NEGOTIATION_FAILED:
+ case DECT_RELEASE_TIMER_EXPIRY:
+ case DECT_RELEASE_PARTIAL_RELEASE:
+ case DECT_RELEASE_UNKNOWN:
+ return AST_CAUSE_NORMAL;
+ /* user values */
+ case DECT_RELEASE_USER_DETACHED:
+ case DECT_RELEASE_USER_NOT_IN_RANGE:
+ return AST_CAUSE_SUBSCRIBER_ABSENT;
+ case DECT_RELEASE_USER_UNKNOWN:
+ return AST_CAUSE_NO_ROUTE_DESTINATION;
+ case DECT_RELEASE_USER_ALREADY_ACTIVE:
+ case DECT_RELEASE_USER_BUSY:
+ return AST_CAUSE_USER_BUSY;
+ case DECT_RELEASE_USER_REJECTION:
+ return AST_CAUSE_CALL_REJECTED;
+ case DECT_RELEASE_USER_CALL_MODIFY:
+ /* external handover values */
+ case DECT_RELEASE_EXTERNAL_HANDOVER_NOT_SUPPORTED:
+ case DECT_RELEASE_NETWORK_PARAMETERS_MISSING:
+ case DECT_RELEASE_EXTERNAL_HANDOVER_RELEASE:
+ return AST_CAUSE_NORMAL;
+ /* temporary overload values */
+ case DECT_RELEASE_OVERLOAD:
+ case DECT_RELEASE_INSUFFICIENT_RESOURCES:
+ case DECT_RELEASE_INSUFFICIENT_BEARERS_AVAILABLE:
+ return AST_CAUSE_NORMAL_CIRCUIT_CONGESTION;
+ case DECT_RELEASE_IWU_CONGESTION:
+ return AST_CAUSE_SWITCH_CONGESTION;
+ default:
+ return AST_CAUSE_NORMAL;
+ }
+}
+
+static struct dect_pt *dect_pt_get_by_name(const char *name)
+{
+ struct dect_pt *pt;
+
+ AST_LIST_TRAVERSE(&dect_pt_list, pt, list) {
+ if (!strcasecmp(pt->name, name))
+ return pt;
+ }
+ return NULL;
+}
+
+static struct dect_pt *dect_pt_get_by_ipui(const struct dect_ipui *ipui)
+{
+ struct dect_pt *pt;
+
+ AST_LIST_TRAVERSE(&dect_pt_list, pt, list) {
+ if (!dect_ipui_cmp(&pt->ipui, ipui))
+ return pt;
+ }
+ return NULL;
+}
+
+static struct dect_pt *dect_init_portable(const char *name,
+ const struct ast_variable *v)
+{
+ struct dect_pt *pt;
+
+ pt = ast_malloc(sizeof(*pt));
+ if (pt == NULL)
+ return NULL;
+ memset(pt, 0, sizeof(*pt));
+ ast_copy_string(pt->name, name, sizeof(pt->name));
+
+ ast_string_field_init(pt, 512);
+ ast_string_field_set(pt, context, dect_cfg.default_context);
+ ast_string_field_set(pt, regexten, "");
+ ast_string_field_set(pt, language, "");
+ ast_string_field_set(pt, cid_num, "");
+ ast_string_field_set(pt, cid_name, "");
+ ast_string_field_set(pt, ring_pattern, "0");
+ pt->ipui.put = DECT_IPUI_N;
+
+ for (; v != NULL; v = v->next) {
+ if (!strcasecmp(v->name, "emc")) {
+ pt->ipui.pun.n.ipei.emc = strtoul(v->value, NULL, 16);
+ } else if (!strcasecmp(v->name, "psn")) {
+ pt->ipui.pun.n.ipei.psn = strtoul(v->value, NULL, 16);
+ } else if (!strcasecmp(v->name, "context")) {
+ ast_string_field_set(pt, context, v->value);
+ } else if (!strcasecmp(v->name, "regexten")) {
+ ast_string_field_set(pt, regexten, v->value);
+ } else if (!strcasecmp(v->name, "language")) {
+ ast_string_field_set(pt, language, v->value);
+ } else if (!strcasecmp(v->name, "cid_number")) {
+ ast_string_field_set(pt, cid_num, v->value);
+ } else if (!strcasecmp(v->name, "cid_name")) {
+ ast_string_field_set(pt, cid_name, v->value);
+ } else if (!strcasecmp(v->name, "ring_pattern")) {
+ ast_string_field_set(pt, ring_pattern, v->value);
+ }
+ }
+
+ AST_LIST_INSERT_TAIL(&dect_pt_list, pt, list);
+ return pt;
+}
+
+static void dect_register_extension(const struct dect_pt *pt, bool onoff)
+{
+ struct pbx_find_info q = { .stacklen = 0 };
+ char *ext, *extenp, *context;
+ char exten[256];
+
+ if (ast_strlen_zero(dect_cfg.default_context))
+ return;
+
+ ast_copy_string(exten, S_OR(pt->regexten, pt->name), sizeof(exten));
+ extenp = exten;
+ while ((ext = strsep(&extenp, "&")) != NULL) {
+ context = strchr(ext, '@');
+ if (context != NULL) {
+ *context++ = '\0';
+ if (!ast_context_find(context)) {
+ ast_log(LOG_WARNING, "Context %s for PT %s does not exist",
+ context, pt->name);
+ continue;
+ }
+ } else
+ context = dect_cfg.default_context;
+
+ if (onoff) {
+ if (!ast_exists_extension(NULL, context, ext, 1, NULL)) {
+ ast_add_extension(context, 1, ext, 1, NULL, NULL, "Noop",
+ ast_strdup(pt->name), ast_free, "DECT");
+ }
+ } else if (pbx_find_extension(NULL, NULL, &q, context, ext, 1,
+ NULL, "", E_MATCH)) {
+ ast_context_remove_extension(context, ext, 1, NULL);
+ }
+ }
+}
+
+static int dect_init_call(struct dect_pvt *pvt, enum ast_channel_state state,
+ const char *exten)
+{
+ struct dect_pt *pt = pvt->pt;
+ struct ast_channel *chan;
+
+ ast_string_field_init(pvt, 512);
+
+ chan = ast_channel_alloc(1, state, pt->cid_num, pt->cid_name, "",
+ exten, pt->context, 0, 0,
+ "DECT/%s-%08lx", pt->name, (long)pvt);
+ if (chan == NULL)
+ return -1;
+
+ chan->tech = &dect_tech;
+ chan->nativeformats = AST_FORMAT_G726_AAL2;
+ chan->readformat = AST_FORMAT_G726_AAL2;
+ chan->rawreadformat = AST_FORMAT_G726_AAL2;
+ chan->writeformat = AST_FORMAT_G726_AAL2;
+ chan->rawwriteformat = AST_FORMAT_G726_AAL2;
+ chan->tech_pvt = pvt;
+
+ if (exten != NULL)
+ ast_copy_string(chan->exten, exten, sizeof(chan->exten));
+ if (!ast_strlen_zero(pt->language))
+ ast_string_field_set(chan, language, pt->language);
+
+ ast_jb_configure(chan, &dect_cfg.jbconf);
+
+ ast_module_ref(ast_module_info->self);
+ pvt->chan = chan;
+
+ if (state != AST_STATE_RESERVED && state != AST_STATE_DIALING) {
+ if (ast_pbx_start(chan)) {
+ ast_log(LOG_WARNING, "Unable to start PBX\n");
+ ast_hangup(chan);
+ }
+ }
+ return 0;
+}
+
+static int dect_try_to_connect_call(struct dect_pvt *pvt)
+{
+ struct ast_channel *chan = pvt->chan;
+ struct dect_pt *pt = pvt->pt;
+
+ if (!ast_exists_extension(NULL, pt->context, chan->exten, 1, NULL))
+ return -1;
+ ast_log(LOG_NOTICE, "connecting call\n");
+ if (ast_pbx_start(chan)) {
+ ast_log(LOG_WARNING, "Unable to start PBX\n");
+ ast_hangup(chan);
+ } else
+ ast_setstate(chan, AST_STATE_RING);
+ return 0;
+}
+
+static int dect_answer(struct ast_channel *chan)
+{
+ struct dect_pvt *pvt = chan->tech_pvt;
+ struct dect_mncc_connect_param connect = {};
+
+ ast_log(LOG_NOTICE, "answer call state %u\n", chan->_state);
+ if (chan->_state == AST_STATE_UP || chan->_state == AST_STATE_RINGING)
+ return 0;
+
+ dect_mncc_connect_req(dh, pvt->call, &connect);
+ ast_setstate(chan, AST_STATE_UP);
+ return 0;
+}
+
+static int dect_call(struct ast_channel *chan, char *dest, int timeout)
+{
+ struct dect_pvt *pvt = chan->tech_pvt;
+ struct dect_mncc_setup_param param;
+ struct dect_ie_basic_service service;
+
+ /* Store CallerID for ALERTING state */
+ ast_string_field_set(pvt, cid_num, chan->cid.cid_num);
+ ast_string_field_set(pvt, cid_name, chan->cid.cid_name);
+
+ dect_ie_init(&service);
+ service.class = DECT_CALL_CLASS_NORMAL;
+ service.service = DECT_SERVICE_BASIC_SPEECH_DEFAULT;
+
+ memset(&param, 0, sizeof(param));
+ param.basic_service = &service;
+ dect_mncc_setup_req(dh, pvt->call, &pvt->pt->ipui, &param);
+
+ ast_setstate(pvt->chan, AST_STATE_DIALING);
+ chan->hangupcause = AST_CAUSE_NORMAL_CLEARING;
+ return 0;
+}
+
+static struct ast_channel *dect_request_call(const char *type, format_t format,
+ const struct ast_channel *requestor,
+ void *data, int *cause)
+{
+ struct dect_pvt *pvt;
+ struct dect_call *call;
+ struct dect_pt *pt;
+ const char *name = data;
+
+ pt = dect_pt_get_by_name(name);
+ if (pt == NULL) {
+ ast_log(LOG_NOTICE, "Call to unknown PT '%s' requested\n", name);
+ return NULL;
+ }
+
+ call = dect_call_alloc(dh);
+ if (call == NULL)
+ return NULL;
+ pvt = dect_call_priv(call);
+
+ ast_log(LOG_NOTICE, "Outgoing call to %s\n", pt->name);
+ pvt->pt = pt;
+ pvt->call = call;
+ if (dect_init_call(pvt, AST_STATE_RESERVED, NULL) < 0)
+ return NULL;
+
+ return pvt->chan;
+}
+
+static int dect_hangup(struct ast_channel *chan)
+{
+ struct dect_pvt *pvt = chan->tech_pvt;
+ struct dect_ie_release_reason release_reason;
+ struct dect_mncc_release_param param;
+
+ ast_log(LOG_NOTICE, "Hangup\n");
+
+ if (chan->_state != AST_STATE_DOWN) {
+ dect_ie_init(&release_reason);
+ release_reason.reason = DECT_RELEASE_NORMAL;
+
+ memset(&param, 0, sizeof(param));
+ param.release_reason = &release_reason;
+ dect_mncc_release_req(dh, pvt->call, &param);
+ }
+
+ chan->tech_pvt = NULL;
+ ast_setstate(chan, AST_STATE_DOWN);
+ return 0;
+}
+
+static int dect_indicate(struct ast_channel *chan, int condition,
+ const void *data, size_t datalen)
+{
+ struct dect_pvt *pvt = chan->tech_pvt;
+ struct dect_mncc_alert_param alert;
+ struct dect_ie_progress_indicator progress;
+ struct dect_ie_signal signal;
+ int res = 0;
+
+ switch (condition) {
+ case AST_CONTROL_RINGING:
+ ast_log(LOG_NOTICE, "Ringing\n");
+ memset(&alert, 0, sizeof(alert));
+ if (0) {
+ alert.signal = dect_signal_init(&signal, DECT_SIGNAL_RING_BACK_TONE_ON);
+ } else {
+ dect_ie_init(&progress);
+ progress.location = DECT_LOCATION_PRIVATE_NETWORK_SERVING_LOCAL_USER;
+ progress.progress = DECT_PROGRESS_INBAND_INFORMATION_NOW_AVAILABLE;
+ dect_ie_list_add(&progress, &alert.progress_indicator);
+ }
+ dect_mncc_alert_req(dh, pvt->call, &alert);
+ ast_setstate(chan, AST_STATE_RINGING);
+ res = -1;
+ break;
+ case AST_CONTROL_BUSY:
+ case AST_CONTROL_CONGESTION:
+ case AST_CONTROL_PROCEEDING:
+ case AST_CONTROL_PROGRESS:
+ case AST_CONTROL_HOLD:
+ case AST_CONTROL_UNHOLD:
+ case AST_CONTROL_VIDUPDATE:
+ case AST_CONTROL_T38_PARAMETERS:
+ case AST_CONTROL_SRCUPDATE:
+ case -1:
+ res = -1;
+ ast_log(LOG_NOTICE, "Indicate %d\n", condition);
+ break;
+ default:
+ ast_log(LOG_NOTICE, "Indicate unknown condition %d\n", condition);
+ res = -1;
+ break;
+ }
+ return res;
+}
+
+static int dect_write(struct ast_channel *chan, struct ast_frame *f)
+{
+ struct dect_pvt *pvt = chan->tech_pvt;
+ struct dect_msg_buf mb;
+ unsigned int len = f->datalen;
+
+ mb.data = f->data.ptr;
+ mb.len = 40;
+ while (len >= 40) {
+ dect_dl_u_data_req(dh, pvt->call, &mb);
+ mb.data += 40;
+ len -= 40;
+ }
+ return 0;
+}
+
+static struct ast_frame *dect_read(struct ast_channel *chan)
+{
+ ast_log(LOG_NOTICE, "read\n");
+ return NULL;
+}
+
+static int dect_fixup(struct ast_channel *old, struct ast_channel *new)
+{
+ ast_log(LOG_NOTICE, "fixup\n");
+ return 0;
+}
+
+static const struct ast_channel_tech dect_tech = {
+ .type = "DECT",
+ .description = "Digital Enhanced Cordless Telecommunications (DECT)",
+ .capabilities = AST_FORMAT_G726_AAL2,
+ .answer = dect_answer,
+ .requester = dect_request_call,
+ .call = dect_call,
+ .hangup = dect_hangup,
+ .indicate = dect_indicate,
+ .write = dect_write,
+ .read = dect_read,
+ .fixup = dect_fixup,
+};
+
+static void dect_mncc_setup_ind(struct dect_handle *dh, struct dect_call *call,
+ struct dect_mncc_setup_param *param)
+{
+ struct dect_mncc_setup_ack_param setup_ack;
+ struct dect_mncc_call_proc_param call_proc;
+ struct dect_mncc_release_param release;
+ struct dect_ie_release_reason release_reason;
+ struct dect_ie_delimiter_request delimiter_request;
+ struct dect_ie_signal signal;
+ struct dect_pvt *pvt = dect_call_priv(call);
+ struct dect_pt *pt;
+ enum ast_channel_state state;
+ const char *exten;
+
+ pt = dect_pt_get_by_ipui(dect_call_portable_identity(call));
+ if (pt == NULL) {
+ ast_log(LOG_NOTICE, "Incoming call from unknown PT\n");
+
+ memset(&release, 0, sizeof(release));
+ release.release_reason = dect_ie_init(&release_reason);
+ release_reason.reason = DECT_RELEASE_UNKNOWN_IDENTITY;
+ dect_mncc_reject_req(dh, call, &release);
+ return;
+ }
+
+ ast_log(LOG_NOTICE, "Incoming call from %s\n", pt->name);
+
+ /* If the phone supplies the entire number en-bloc, enter call
+ * proceeding state. A number is considered complete when contained
+ * in a called party number IE, or a multi-keypad IE and the extension
+ * exists. When no number is supplied and an 's' extension exists in
+ * the context, the call is also moved to call proceeding state and
+ * further decisions are left to the PBX.
+ *
+ * Otherwise enter overlap sending and wait for further information.
+ */
+ state = AST_STATE_RING;
+ exten = NULL;
+
+ if (param->called_party_number != NULL)
+ ; //ast_copy_string(exten, param->called_party_number.info, sizeof(exten));
+ else if (param->keypad != NULL && param->keypad->info[0] < 0x80)
+ exten = (char *)param->keypad->info;
+ else {
+ exten = "s";
+ if (!ast_exists_extension(NULL, pt->context, exten, 1, NULL)) {
+ state = AST_STATE_DIALING;
+ exten = "";
+ }
+ }
+
+ if (state == AST_STATE_RING) {
+ memset(&call_proc, 0, sizeof(call_proc));
+ dect_mncc_call_proc_req(dh, call, &call_proc);
+ } else {
+ dect_ie_init(&delimiter_request);
+ dect_signal_init(&signal, DECT_SIGNAL_DIAL_TONE_ON);
+
+ memset(&setup_ack, 0, sizeof(setup_ack));
+ setup_ack.delimiter_request = &delimiter_request;
+ setup_ack.signal = &signal;
+ dect_mncc_setup_ack_req(dh, call, &setup_ack);
+ }
+
+ pvt->pt = pt;
+ pvt->call = call;
+ dect_init_call(pvt, state, exten);
+
+ /* WTF? Asterisk eats (actually leaks) the first queued frame when
+ * answering a channel. Give it something to eat :| */
+ if (state == AST_STATE_DIALING)
+ ast_queue_control(pvt->chan, -1);
+}
+
+static void dect_mncc_setup_ack_ind(struct dect_handle *dh, struct dect_call *call,
+ struct dect_mncc_setup_ack_param *param)
+{
+ ast_log(LOG_NOTICE, "MNCC_SETUP_ACK-ind\n");
+}
+
+static void dect_mncc_reject_ind(struct dect_handle *dh, struct dect_call *call,
+ struct dect_mncc_release_param *param)
+{
+ ast_log(LOG_NOTICE, "MNCC_REJECT-ind\n");
+}
+
+#if 0
+static int dect_display_timer(const void *data)
+{
+ struct dect_pvt *pvt = (struct dect_pvt *)data;
+ struct dect_ie_display display;
+ struct dect_mncc_info_param info = { .display = &display};
+
+ dect_display_init(&display);
+ dect_display_append_char(&display, DECT_C_RETURN);
+ dect_display_append_char(&display, DECT_C_RETURN);
+
+ dect_mncc_info_req(dh, pvt->call, &info);
+ return 1;
+}
+#endif
+
+static void dect_mncc_alert_ind(struct dect_handle *dh, struct dect_call *call,
+ struct dect_mncc_alert_param *param)
+{
+ struct dect_pvt *pvt = dect_call_priv(call);
+ struct dect_ie_display display;
+ struct dect_ie_signal signal;
+ struct dect_mncc_info_param info = { .signal = &signal, .display = &display};
+ unsigned int name_len, num_len;
+ char pattern[16];
+ const char *c;
+
+ ast_log(LOG_NOTICE, "MNCC_ALERT-ind\n");
+
+ ast_copy_string(pattern, pvt->pt->ring_pattern, sizeof(pattern));
+ c = pbx_builtin_getvar_helper(pvt->chan, "RING_PATTERN");
+ if (c != NULL)
+ ast_copy_string(pattern, c, sizeof(pattern));
+
+ if (strcasecmp(pattern, "silent")) {
+ dect_ie_init(&signal);
+ signal.code = DECT_SIGNAL_ALERTING_BASE | (atoi(c) & 0xf);
+ }
+
+ dect_display_init(&display);
+ name_len = strlen(pvt->cid_name);
+ num_len = strlen(pvt->cid_num);
+ if (name_len > 0 || num_len > 0) {
+ dect_display_append_char(&display, DECT_C_CLEAR_DISPLAY);
+ if (name_len > 0) {
+ dect_display_append(&display, pvt->cid_name, name_len);
+ dect_display_append(&display, " <", 2);
+ }
+
+ if (num_len > 0)
+ dect_display_append(&display, pvt->cid_num, num_len);
+
+ if (name_len > 0)
+ dect_display_append(&display, ">", 1);
+ //pvt->pt->timer_id = ast_sched_add(sched, 500, dect_display_timer, pvt);
+ }
+
+ dect_mncc_info_req(dh, call, &info);
+
+ ast_queue_control(pvt->chan, AST_CONTROL_RINGING);
+ ast_setstate(pvt->chan, AST_STATE_RINGING);
+}
+
+static void dect_mncc_connect_ind(struct dect_handle *dh, struct dect_call *call,
+ struct dect_mncc_connect_param *param)
+{
+ struct dect_pvt *pvt = dect_call_priv(call);
+
+ ast_log(LOG_NOTICE, "MNCC_CONNECT-ind\n");
+ ast_queue_control(pvt->chan, AST_CONTROL_ANSWER);
+ dect_mncc_connect_res(dh, call, param);
+}
+
+static void dect_mncc_release_ind(struct dect_handle *dh, struct dect_call *call,
+ struct dect_mncc_release_param *param)
+{
+ struct dect_pvt *pvt = dect_call_priv(call);
+
+ ast_log(LOG_NOTICE, "MNCC_RELEASE-ind\n");
+ ast_setstate(pvt->chan, AST_STATE_DOWN);
+ pvt->chan->hangupcause =
+ dect_release_reason_to_ast(param->release_reason->reason);
+ ast_queue_hangup(pvt->chan);
+
+ dect_mncc_release_res(dh, call, param);
+}
+
+static void dect_mncc_release_cfm(struct dect_handle *dh, struct dect_call *call,
+ struct dect_mncc_release_param *param)
+{
+ ast_log(LOG_NOTICE, "MNCC_RELEASE-cfm\n");
+}
+
+static void dect_dl_u_data_ind(struct dect_handle *dh, struct dect_call *call,
+ struct dect_msg_buf *mb)
+{
+ struct dect_pvt *pvt = dect_call_priv(call);
+ struct ast_frame f;
+
+ if (pvt->chan->_state == AST_STATE_DOWN)
+ return;
+
+ memset(&f, 0, sizeof(f));
+ f.frametype = AST_FRAME_VOICE;
+ f.subclass.integer = AST_FORMAT_G726_AAL2;
+ f.samples = mb->len * 2;
+ f.datalen = mb->len;
+ f.data.ptr = mb->data;
+ f.offset = AST_FRIENDLY_OFFSET;
+
+ ast_queue_frame(pvt->chan, &f);
+}
+
+static void dect_keypad_info(struct dect_pvt *pvt,
+ const struct dect_ie_keypad *keypad)
+{
+ struct ast_frame f = { .frametype = AST_FRAME_DTMF, };
+ struct ast_channel *chan = pvt->chan;
+ unsigned int i;
+
+ if (chan->_state == AST_STATE_DIALING) {
+ strncat(chan->exten, (char *)keypad->info, keypad->len);
+ dect_try_to_connect_call(pvt);
+ } else {
+ for (i = 0; i < keypad->len; i++) {
+ ast_verbose("keypad: '%c' (%x)\n", keypad->info[i], keypad->info[i]);
+ f.subclass.integer = keypad->info[i];
+ ast_queue_frame(chan, &f);
+ }
+ }
+}
+
+static void dect_mncc_info_ind(struct dect_handle *dh, struct dect_call *call,
+ struct dect_mncc_info_param *param)
+{
+ struct dect_pvt *pvt = dect_call_priv(call);
+
+ if (param->keypad != NULL)
+ dect_keypad_info(pvt, param->keypad);
+}
+
+static const struct dect_cc_ops dect_cc_ops = {
+ .priv_size = sizeof(struct dect_pvt),
+ .mncc_setup_ind = dect_mncc_setup_ind,
+ .mncc_setup_ack_ind = dect_mncc_setup_ack_ind,
+ .mncc_reject_ind = dect_mncc_reject_ind,
+ .mncc_call_proc_ind = NULL,
+ .mncc_alert_ind = dect_mncc_alert_ind,
+ .mncc_connect_ind = dect_mncc_connect_ind,
+ .mncc_connect_cfm = NULL,
+ .mncc_release_ind = dect_mncc_release_ind,
+ .mncc_release_cfm = dect_mncc_release_cfm,
+ .mncc_facility_ind = NULL,
+ .mncc_info_ind = dect_mncc_info_ind,
+ .mncc_modify_ind = NULL,
+ .mncc_modify_cfm = NULL,
+ .mncc_hold_ind = NULL,
+ .mncc_hold_cfm = NULL,
+ .mncc_retrieve_ind = NULL,
+ .mncc_retrieve_cfm = NULL,
+ .mncc_iwu_info_ind = NULL,
+ .dl_u_data_ind = dect_dl_u_data_ind,
+};
+
+static void dect_mm_locate_ind(struct dect_handle *dh,
+ struct dect_mm_endpoint *mme,
+ struct dect_mm_locate_param *param)
+{
+ ast_log(LOG_NOTICE, "MM_LOCATE-ind\n");
+}
+
+static void dect_mm_identity_assign_cfm(struct dect_handle *dh,
+ struct dect_mm_endpoint *mme, bool accept,
+ struct dect_mm_identity_assign_param *param)
+{
+ ast_log(LOG_NOTICE, "MN_IDENTITY_ASSIGN-cfm\n");
+}
+
+static const struct dect_mm_ops dect_mm_ops = {
+ .mm_locate_ind = dect_mm_locate_ind,
+ .mm_identity_assign_cfm = dect_mm_identity_assign_cfm,
+};
+
+static int dect_show_debug(const char *fmt, va_list ap)
+{
+ ast_verbose_ap(fmt, ap);
+ return 0;
+}
+
+static int dect_load_config(void)
+{
+ struct ast_flags cfg_flags = {};
+ struct ast_config *cfg;
+ struct ast_variable *v;
+ struct dect_pt *pt;
+ char *cat;
+
+ memcpy(&dect_cfg.jbconf, &dect_default_jbconf, sizeof(struct ast_jb_conf));
+
+ cfg = ast_config_load(CONFIG_FILE, cfg_flags);
+ if (cfg == CONFIG_STATUS_FILEINVALID) {
+ ast_log(LOG_ERROR, "Config file %s is in an invalid format.\n",
+ CONFIG_FILE);
+ return AST_MODULE_LOAD_DECLINE;
+ }
+
+ if (cfg == NULL) {
+ ast_log(LOG_ERROR, "Unable to load config %s\n", CONFIG_FILE);
+ return AST_MODULE_LOAD_DECLINE;
+ }
+
+ for (v = ast_variable_browse(cfg, "general"); v != NULL; v = v->next) {
+ /* jitter buffer configuration */
+ if (!ast_jb_read_conf(&dect_cfg.jbconf, v->name, v->value))
+ continue;
+
+ if (!strcasecmp(v->name, "context")) {
+ ast_copy_string(dect_cfg.default_context, v->value,
+ sizeof(dect_cfg.default_context));
+ }
+ }
+
+ /* Phone configuration */
+ cat = NULL;
+ while ((cat = ast_category_browse(cfg, cat)) != NULL) {
+ if (!strcasecmp(cat, "general"))
+ continue;
+ pt = dect_init_portable(cat, ast_variable_browse(cfg, cat));
+ if (pt == NULL)
+ return AST_MODULE_LOAD_DECLINE;
+ dect_register_extension(pt, true);
+ }
+ return 0;
+}
+
+/*
+ * Asterisk CLI commands
+ */
+
+static char *dect_cli_debug(struct ast_cli_entry *e, int cmd,
+ struct ast_cli_args *a)
+{
+ const char *arg;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "dect set debug {on|off}";
+ e->usage = "Usage: dect set debug {on|off}\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ arg = a->argv[e->args - 1];
+ if (!strcasecmp(arg, "on")) {
+ dect_set_debug_hook(dect_show_debug);
+ ast_cli(a->fd, "DECT debugging enabled\n");
+ } else if (!strcasecmp(arg, "off")) {
+ dect_set_debug_hook(NULL);
+ ast_cli(a->fd, "DECT debugging disabled\n");
+ } else
+ return CLI_SHOWUSAGE;
+
+ return CLI_SUCCESS;
+}
+
+static struct ast_cli_entry dect_cli_cmds[] = {
+ AST_CLI_DEFINE(dect_cli_debug, "Enable/Disable DECT debugging"),
+};
+
+/*
+ * libdect event ops
+ */
+
+struct dect_fd_priv {
+ int *id;
+};
+
+static int dect_io_callback(int *id, int fd, short io_events, void *data)
+{
+ struct dect_fd *dfd = data;
+ uint32_t events = 0;
+
+ if (io_events & (AST_IO_IN | AST_IO_ERR | AST_IO_HUP))
+ events |= DECT_FD_READ;
+ if (io_events & AST_IO_OUT)
+ events |= DECT_FD_WRITE;
+ dfd->callback(dh, dfd, events);
+ return 1;
+}
+
+static int dect_register_fd(const struct dect_handle *dh, struct dect_fd *dfd,
+ uint32_t events)
+{
+ struct dect_fd_priv *priv = (struct dect_fd_priv *)dfd->priv;
+ short io_events = 0;
+
+ if (events & DECT_FD_READ)
+ io_events |= AST_IO_IN;
+ if (events & DECT_FD_WRITE)
+ io_events |= AST_IO_OUT;
+
+ priv->id = ast_io_add(io, dfd->fd, dect_io_callback, io_events, dfd);;
+ return priv->id ? 0 : -1;
+}
+
+static void dect_unregister_fd(const struct dect_handle *dh, struct dect_fd *dfd)
+{
+ struct dect_fd_priv *priv = (struct dect_fd_priv *)dfd->priv;
+
+ ast_io_remove(io, priv->id);
+}
+
+struct dect_timer_priv {
+ int id;
+};
+
+static int dect_timer_callback(const void *data)
+{
+ struct dect_timer *timer = (struct dect_timer *)data;;
+
+ timer->callback(dh, timer);
+ return 0;
+}
+
+static void dect_start_timer(const struct dect_handle *dh,
+ struct dect_timer *timer,
+ const struct timeval *tv)
+{
+ struct dect_timer_priv *priv = (struct dect_timer_priv *)timer->priv;
+
+ priv->id = ast_sched_add(sched, tv->tv_sec * 1000 + tv->tv_usec / 1000,
+ dect_timer_callback, timer);
+}
+
+static void dect_stop_timer(const struct dect_handle *dh,
+ struct dect_timer *timer)
+{
+ struct dect_timer_priv *priv = (struct dect_timer_priv *)timer->priv;
+
+ AST_SCHED_DEL(sched, priv->id);
+}
+
+static const struct dect_event_ops dect_event_ops = {
+ .fd_priv_size = sizeof(struct dect_fd_priv),
+ .register_fd = dect_register_fd,
+ .unregister_fd = dect_unregister_fd,
+ .timer_priv_size = sizeof(struct dect_timer_priv),
+ .start_timer = dect_start_timer,
+ .stop_timer = dect_stop_timer,
+};
+
+static struct dect_ops dect_ops = {
+ .event_ops = &dect_event_ops,
+ .cc_ops = &dect_cc_ops,
+ .mm_ops = &dect_mm_ops,
+};
+
+static pthread_t io_thread = AST_PTHREADT_NULL;
+
+static void *dect_io_thread(void *ignore)
+{
+ int timeout;
+
+ while (1) {
+ pthread_testcancel();
+ timeout = ast_sched_wait(sched);
+ if (timeout < 0)
+ timeout = 1000;
+ ast_io_wait(io, timeout);
+ ast_sched_runq(sched);
+ }
+ return NULL;
+}
+
+static int dect_io_thread_start(void)
+{
+ int err;
+
+ err = ast_pthread_create_background(&io_thread, NULL, dect_io_thread, NULL);
+ if (err < 0) {
+ ast_log(LOG_ERROR, "Unable to start IO thread\n");
+ return err;
+ }
+ return 0;
+}
+
+static int dect_load_module(void)
+{
+ sched = sched_context_create();
+ if (sched == NULL) {
+ ast_log(LOG_ERROR, "Unable to create scheduler context\n");
+ goto err1;
+ }
+
+ io = io_context_create();
+ if (io == NULL) {
+ ast_log(LOG_ERROR, "Unable to create IO context\n");
+ goto err2;
+ }
+
+ if (ast_channel_register(&dect_tech)) {
+ ast_log(LOG_ERROR, "Unable to register 'DECT' channel\n");
+ goto err3;
+ }
+
+ ast_cli_register_multiple(dect_cli_cmds, ARRAY_LEN(dect_cli_cmds));
+
+ dect_load_config();
+
+ if (dect_io_thread_start() < 0)
+ goto err4;
+
+ dh = dect_alloc_handle(&dect_ops);
+ if (dh == NULL) {
+ ast_log(LOG_ERROR, "Unable to allocate DECT handle\n");
+ goto err5;
+ }
+
+ if (dect_init(dh) < 0) {
+ ast_log(LOG_ERROR, "Unable to initialize DECT handle\n");
+ goto err6;
+ }
+
+ return AST_MODULE_LOAD_SUCCESS;
+err6:
+err5:
+err4:
+err3:
+err2:
+ sched_context_destroy(sched);
+err1:
+ return AST_MODULE_LOAD_FAILURE;
+}
+
+static int dect_unload_module(void)
+{
+ return 0;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "DECT",
+ .load = dect_load_module,
+ .unload = dect_unload_module,
+);
diff --git a/configs/dect.conf.sample b/configs/dect.conf.sample
new file mode 100644
index 000000000..778df689b
--- /dev/null
+++ b/configs/dect.conf.sample
@@ -0,0 +1,50 @@
+[general]
+context = default
+
+;------------------------------ JITTER BUFFER CONFIGURATION --------------------------
+; jbenable = yes ; Enables the use of a jitterbuffer on the receiving side of an
+ ; DECT channel. Defaults to "no". An enabled jitterbuffer will
+ ; be used only if the sending side can create and the receiving
+ ; side can not accept jitter. The DECT channel can't accept jitter,
+ ; thus an enabled jitterbuffer on the receive DECT side will always
+ ; be used if the sending side can create jitter.
+
+; jbmaxsize = 200 ; Max length of the jitterbuffer in milliseconds.
+
+; jbresyncthreshold = 1000 ; Jump in the frame timestamps over which the jitterbuffer is
+ ; resynchronized. Useful to improve the quality of the voice, with
+ ; big jumps in/broken timestamps, usually sent from exotic devices
+ ; and programs. Defaults to 1000.
+
+; jbimpl = fixed ; Jitterbuffer implementation, used on the receiving side of a DECT
+ ; channel. Two implementations are currently available - "fixed"
+ ; (with size always equals to jbmax-size) and "adaptive" (with
+ ; variable size, actually the new jb of IAX2). Defaults to fixed.
+
+; jblog = no ; Enables jitterbuffer frame logging. Defaults to "no".
+;-----------------------------------------------------------------------------------
+
+[phone1]
+emc = 08ae
+psn = 83d1e
+regexten = 601
+
+language = de
+cid_number = 601
+cid_name = kaber
+ring_pattern = 3
+
+display_lines = 1
+display_columns = 11
+
+[phone2]
+emc = 08ae
+psn = 8969f
+regexten = 602
+language = de
+
+[phone3]
+emc = 08ae
+psn = 5b9a0
+regexten = 603
+language = de
diff --git a/configure.ac b/configure.ac
index 9e8a2df81..6f65e0f7f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -377,6 +377,7 @@ AST_EXT_LIB_SETUP([CAP], [POSIX 1.e capabilities], [cap])
AST_EXT_LIB_SETUP([CURSES], [curses], [curses])
AST_EXT_LIB_SETUP([CRYPTO], [OpenSSL Cryptography], [crypto])
AST_EXT_LIB_SETUP([DAHDI], [DAHDI], [dahdi])
+AST_EXT_LIB_SETUP([DECT], [libdect], [dect])
AST_EXT_LIB_SETUP([FFMPEG], [Ffmpeg and avcodec], [avcodec])
AST_EXT_LIB_SETUP([GSM], [External GSM], [gsm], [, use 'internal' GSM otherwise])
AST_EXT_LIB_SETUP([GTK2], [gtk2], [gtk2])
@@ -1895,6 +1896,8 @@ if test "${PBX_PWLIB}" = "1" -a "${USE_OPENH323}" != "no" ; then
[${PWLIB_INCLUDE}], [${PWLIB_LIB}])
fi
+AST_EXT_LIB_CHECK([DECT], [dect], [dect_alloc_handle])
+
AST_EXT_LIB_CHECK([LUA], [lua5.1], [luaL_newstate], [lua5.1/lua.h], [-lm])
if test "x${PBX_LUA}" = "x1" ; then
if test x"${LUA_DIR}" = x; then