From 757441b693fc940a75d7fdcfa0daa356e2577e23 Mon Sep 17 00:00:00 2001
From: Adam Borowski <adam.borowski@sigma.se>
Date: Fri, 14 May 2021 13:49:58 +0200
Subject: [PATCH] Feature 4599 - Integration of call clearing tones into iopsys
 voice

Play howler tone in case user forgot to put handset on hook
as a reminder to end the call properly.
---
 channels/chan_brcm.c | 132 +++++++++++++++++++++++++++++++++++--------
 channels/chan_brcm.h |   8 ++-
 2 files changed, 115 insertions(+), 25 deletions(-)

diff --git a/channels/chan_brcm.c b/channels/chan_brcm.c
index e541526738..1ac3fba3d1 100644
--- a/channels/chan_brcm.c
+++ b/channels/chan_brcm.c
@@ -105,6 +105,7 @@ static void *pe_base_run(void *unused);
 static int brcm_create_connection(struct brcm_subchannel *p);
 static int brcm_signal_congestion(struct brcm_pvt *p);
 static int brcm_stop_dialtone(struct brcm_pvt *p);
+static void brcm_signal_howler(struct brcm_pvt *p);
 static int brcm_signal_ringing(struct brcm_pvt *p);
 static int brcm_stop_ringing(struct brcm_pvt *p);
 static int brcm_signal_ringing_callerid_pending(struct brcm_pvt *p);
@@ -122,6 +123,7 @@ static int brcm_should_relay_dtmf(const struct brcm_subchannel *sub);
 static struct ast_channel *brcm_new(struct brcm_subchannel *subchan, int state, const char *ext, const char *context,
 		const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor,
 		struct ast_format_cap *format);
+static int handle_dialtone_timeout(const void *data);
 
 /* Global brcm channel parameters */
 static const char tdesc[] = "Broadcom SLIC Driver";
@@ -832,7 +834,9 @@ static int brcm_hangup(struct ast_channel *ast)
 
 	p = sub->parent;
 	sub_peer = brcm_subchannel_get_peer(sub);
-	ast_debug(1, "brcm_hangup(%s) line_id=%d connection_id=%d\n", ast_channel_name(ast), p->line_id, sub->connection_id);
+	ast_debug(1, "brcm_hangup(%s) line_id=%d connection_id=%d dialtone=%s channel_state=%s\n",
+		ast_channel_name(ast), p->line_id, sub->connection_id, dialtone_map[p->dialtone].str,
+		state2str(sub->channel_state));
 
 	if (sub->channel_state == CALLWAITING) {
 		brcm_stop_callwaiting(p);
@@ -877,7 +881,18 @@ static int brcm_hangup(struct ast_channel *ast)
 	p->lastformat = NULL;
 	p->lastinput = NULL;
 	p->hf_detected = 0;
-	sub->channel_state = CALLENDED;
+
+	/**
+	 * Howler tone is played using a media file on an active channel
+	 * Treated like a normal call but we want to preserve the channel state
+	 * Additional check for ONHOOK channel state so that handle_dialtone_timeout()
+	 * isn't called again when going on-hook
+	 */
+	if(p->dialtone != DIALTONE_HOWLER && sub->channel_state != ONHOOK) {
+		sub->channel_state = CALLENDED;
+		ast_debug(6, "Setting channel state to %s\n", state2str(sub->channel_state));
+	}
+
 	if (terminate_conference && sub->conference_initiator && brcm_in_conference(p)) {
 		/* Switch still active call leg out of conference mode */
 		brcm_stop_conference(sub);
@@ -895,6 +910,12 @@ static int brcm_hangup(struct ast_channel *ast)
 	ast_channel_tech_pvt_set(ast, NULL);
 	brcm_close_connection(sub);
 
+	if(sub->channel_state == CALLENDED) {
+		/* If we hangup but not playing howler, start playing timeout tones */
+		p->dialtone = DIALTONE_ON;
+		handle_dialtone_timeout(p);
+	}
+
 	pvt_unlock(p);
 	return 0;
 }
@@ -927,7 +948,15 @@ static int brcm_answer(struct ast_channel *ast)
 		ast_debug(2, "brcm_answer(%s) set state to up\n", ast_channel_name(ast));
 	}
 	ast_channel_rings_set(ast, 0);
-	sub->channel_state = INCALL;
+
+	/**
+	 * Howler tone is played using a media file on an active channel
+	 * Treated like a normal call but we want to preserve the channel_state
+	 */
+	if(pvt->dialtone != DIALTONE_HOWLER) {
+		sub->channel_state = INCALL;
+	}
+
 	endpt_signal(pvt->line_id, "ringback", "off", NULL);
 
 	pvt_unlock(pvt);
@@ -1130,11 +1159,13 @@ struct brcm_subchannel *brcm_subchannel_get_peer(const struct brcm_subchannel *s
 
 /* Tell endpoint to play country specific dialtone. */
 static int brcm_signal_dialtone(struct brcm_pvt *p) {
-	
+
+	ast_verbose("Setting dialtone to %s\n", dialtone_map[p->dialtone].str);	
 	switch (p->dialtone) {
 		case DIALTONE_OFF:
 			endpt_signal(p->line_id, "dial", "off", NULL);
 			endpt_signal(p->line_id, "stutter", "off", NULL);
+			endpt_signal(p->line_id, "unobtainable", "off", NULL);
 			break;
 		case DIALTONE_ON:
 			endpt_signal(p->line_id, "dial", "on", NULL);
@@ -1145,6 +1176,13 @@ static int brcm_signal_dialtone(struct brcm_pvt *p) {
 		case DIALTONE_SPECIAL_CONDITION:
 			endpt_signal(p->line_id, "stutter", "on", NULL);
 			break;
+		case DIALTONE_UNOBTAINABLE:
+			endpt_signal(p->line_id, "unobtainable", "on", NULL);
+			break;
+		case DIALTONE_HOWLER:
+			ast_debug(9, "Trigger howler tone from Asterisk\n");
+			brcm_signal_howler(p);
+			break;
 		default:
 			ast_log(LOG_ERROR, "Requested to signal unknown dialtone\n");
 			return -1;
@@ -1152,10 +1190,29 @@ static int brcm_signal_dialtone(struct brcm_pvt *p) {
 	return 0;
 }
 
+static void brcm_signal_howler(struct brcm_pvt *p) {
+	struct brcm_subchannel *sub = brcm_get_active_subchannel(p);
+
+	if(!sub) {
+		ast_debug(9, "Unable to find active subchannel\n");
+		return;
+	}
+
+	/* Start the pbx */
+	if (!sub->connection_init) {
+		sub->connection_id = ast_atomic_fetchadd_int((int *)&current_connection_id, +1);
+		brcm_create_connection(sub);
+	}
+
+	/* Create a new channel to context 'howler' handled by extensions.conf */
+	brcm_new(sub, AST_STATE_RING, "0", "howler", NULL, NULL, NULL);
+}
+
 int brcm_stop_dialtone(struct brcm_pvt *p) {
 	endpt_signal(p->line_id, "dial", "off", NULL);
 	endpt_signal(p->line_id, "stutter", "off", NULL);
-
+	p->dialtone = DIALTONE_OFF;
+	brcm_signal_dialtone(p);
 	return 0;
 }
 
@@ -1537,10 +1594,29 @@ static int handle_dialtone_timeout(const void *data)
 	p->dialtone_timeout_timer_id = -1;
 
 	struct brcm_subchannel *sub = brcm_get_active_subchannel(p);
-	if (sub && sub->channel_state == OFFHOOK) {
-		/* Enter state where nothing else than ONHOOK is accepted and play congestion tone */
+	if (sub && (sub->channel_state == OFFHOOK || sub->channel_state == AWAITONHOOK || sub->channel_state == CALLENDED)) {
+		/* Enter state where nothing else other than ONHOOK is accepted and play series of tones */
 		sub->channel_state = AWAITONHOOK;
-		brcm_signal_congestion(p);
+		line_settings *s = &line_config[p->line_id];
+
+		switch (p->dialtone) {
+			case DIALTONE_ON:
+				p->dialtone = DIALTONE_UNOBTAINABLE;
+				p->dialtone_timeout_timer_id = ast_sched_add(sched, s->offhook_nu_timeoutmsec, handle_dialtone_timeout, p);
+				break;
+			case DIALTONE_UNOBTAINABLE:
+				p->dialtone = DIALTONE_OFF;
+				p->dialtone_timeout_timer_id = ast_sched_add(sched, s->offhook_silence_timeoutmsec, handle_dialtone_timeout, p);
+				break;
+			case DIALTONE_OFF:
+				p->dialtone = DIALTONE_HOWLER;
+				break;
+			default:
+				p->dialtone = DIALTONE_OFF;
+				ast_log(LOG_ERROR, "Invalid dialtone timeout state\n");
+				break;
+		}
+		brcm_signal_dialtone(p);
 	}
 
 	//ast_mutex_unlock(&p->lock);
@@ -1638,6 +1714,7 @@ static void handle_hookflash(struct brcm_subchannel *sub, struct brcm_subchannel
 			}
 
 			/* Provide new line */
+			p->dialtone = DIALTONE_ON;
 			brcm_signal_dialtone(p);
 			sub_peer->channel_state = OFFHOOK;
 
@@ -2140,7 +2217,7 @@ static void *brcm_process_event(struct endpt_event *ev) {
 	struct brcm_pvt *p = NULL;
 	struct brcm_subchannel *sub = NULL;
 
-	ast_debug(9, "Event %s detected\n", ev->name);
+	ast_debug(9, "Event %s detected from line %d\n", ev->name, ev->line);
 	p = brcm_get_pvt_from_lineid(iflist, ev->line);
 	if (!p) {
 		ast_debug(3, "No pvt with the correct line_id %d found!\n", ev->line);
@@ -2234,21 +2311,21 @@ static void *brcm_process_event(struct endpt_event *ev) {
 			}
 			else if (sub->channel_state == OFFHOOK) {
 				/* EVENT_OFFHOOK changed endpoint state to OFFHOOK, apply dialtone */
-                                ast_debug(9, "Resetting dial tones.\n");
-                                if ( p->context[0] != '\0' && is_sip_account_registered(p->context)) {
-                                    p->dialtone = DIALTONE_ON;
-                                    brcm_signal_dialtone(p);
-                                }
-
-				line_settings *s = &line_config[p->line_id];
-
-				if (strlen(s->autodial_ext)) {
-					/* Schedule autodial timeout if autodial extension is set */
-					p->autodial_timer_id = ast_sched_add(sched, s->autodial_timeoutmsec, handle_autodial_timeout, p);
-				}
-				else {
-					/* No autodial, schedule dialtone timeout */
-					p->dialtone_timeout_timer_id = ast_sched_add(sched, s->dialtone_timeoutmsec, handle_dialtone_timeout, p);
+				if ( p->context[0] != '\0' && is_sip_account_registered(p->context)) {
+					ast_debug(9, "Resetting dial tones.\n");                                    
+ 					p->dialtone = DIALTONE_ON;
+					brcm_signal_dialtone(p);
+					line_settings *s = &line_config[p->line_id];
+					if (strlen(s->autodial_ext)) {
+						/* Schedule autodial timeout if autodial extension is set */
+						p->autodial_timer_id = ast_sched_add(sched, s->autodial_timeoutmsec, handle_autodial_timeout, p);
+					} else {
+						/* No autodial, schedule dialtone timeout */
+						ast_verbose("Scheduling dialtone timeout in %dms\n", s->dialtone_timeoutmsec);
+						p->dialtone_timeout_timer_id = ast_sched_add(sched, s->dialtone_timeoutmsec, handle_dialtone_timeout, p);
+					}
+				} else {
+					ast_debug(9, "OFFHOOK but SIP account not registered\n");
 				}
 			}
 			break;
@@ -2678,6 +2755,7 @@ static void brcm_show_pvts(struct ast_cli_args *a)
 			dialtone++;
 		}
 		ast_cli(a->fd, "%s\n", dialtone->str);
+		ast_cli(a->fd, "Dialtone Timer id   : %d\n", p->dialtone_timeout_timer_id);
 
 		/* Print status for subchannels */
 		brcm_show_subchannels(a, p);
@@ -3014,6 +3092,8 @@ static line_settings line_settings_create(void)
 		.period = 20,
 		.hangup_xfer = 0,
 		.dialtone_timeoutmsec = 20000,
+		.offhook_nu_timeoutmsec = 60000,
+		.offhook_silence_timeoutmsec = 180000,		
 		.callwaiting = 1,
 		.do_not_disturb = 0,
 		.clir = 0,
@@ -3052,6 +3132,10 @@ static void line_settings_load(line_settings *line_config, struct ast_variable *
 			line_config->autodial_timeoutmsec = atoi(v->value);
 		} else if (!strcasecmp(v->name, "dialtone_timeoutmsec")) {
 			line_config->dialtone_timeoutmsec = atoi(v->value);
+		} else if (!strcasecmp(v->name, "offhook_nu_timeoutmsec")) {
+			line_config->offhook_nu_timeoutmsec = atoi(v->value);
+		} else if (!strcasecmp(v->name, "offhook_silence_timeoutmsec")) {
+			line_config->offhook_silence_timeoutmsec = atoi(v->value);
 		}
 		else if (!strcasecmp(v->name, "hangup_xfer")) {
 			line_config->hangup_xfer = ast_true(v->value)?1:0;
diff --git a/channels/chan_brcm.h b/channels/chan_brcm.h
index 695cd4c7bd..aa5ff7c06b 100644
--- a/channels/chan_brcm.h
+++ b/channels/chan_brcm.h
@@ -86,6 +86,8 @@ typedef enum dialtone_state {
 	DIALTONE_ON,
 	DIALTONE_CONGESTION,
 	DIALTONE_SPECIAL_CONDITION,
+	DIALTONE_UNOBTAINABLE,
+	DIALTONE_HOWLER,
 	DIALTONE_UNKNOWN,
 	DIALTONE_LAST,
 } dialtone_state;
@@ -175,7 +177,7 @@ typedef struct DTMF_CHARNAME_MAP
 typedef struct DIALTONE_MAP
 {
 	dialtone_state	state;
-	char		str[11];
+	char		str[24];
 } DIALTONE_MAP;
 
 static const DIALTONE_MAP dialtone_map[] =
@@ -184,6 +186,8 @@ static const DIALTONE_MAP dialtone_map[] =
 	{DIALTONE_ON,		"on"},
 	{DIALTONE_CONGESTION,	"congestion"},
 	{DIALTONE_SPECIAL_CONDITION, "special"},
+	{DIALTONE_UNOBTAINABLE, "number unobtainable"},
+	{DIALTONE_HOWLER, "howler"},
 	{DIALTONE_UNKNOWN,	"unknown"},
 	{DIALTONE_LAST,		"-"},
 };
@@ -205,6 +209,8 @@ typedef struct {
 	int period;
 	int hangup_xfer;
 	int dialtone_timeoutmsec;
+	int offhook_nu_timeoutmsec;
+	int offhook_silence_timeoutmsec;
 	int callwaiting;
 	int do_not_disturb;
 	int clir;
-- 
GitLab