I wanted to set up my Asterisk feature codes with the following characteristics:
- Can be completely disabled before making a call
- Can be toggled off and back on during a call
- Can be completely disabled during a call
- Available to any local station, whether calling or called
Some might question the usefulness of doing some of those things, but I did it anyhow. Thanks to useful contributions in Bypass feature codes during call and Per-channel FEATUREMAP settings, without which I might have stayed stuck until I gave up. It took me so much trouble to figure this out that I thought I’d share it. Someone is sure to disappoint me by pointing out an obviously simpler way to do things. But, seriously, comments are welcome.
I implemented this almost completely in my dialplan except for a couple of custom feature codes in the applicationmap
in features.conf
. AFAIK, there is no simple way to have Asterisk listening for DTMF except via the feature code stuff.
Requirement #1 was straightforward. I defined a pattern that looked for the “disable feature codes” prefix, *70 in my case. I made note of that in a local variable and then dealt with the real called number.
;disable feature codes
exten => _*70.,1,Verbose(0,---> ${CONTEXT},${EXTEN}: from:${CALLERID(all)})
same => n,Set(featureCodeState=DEAD)
same => n,Goto(${CONTEXT},${EXTEN:3},1)
The local variable featureCodeState
has 3 possible values (though it starts out with a 4th pseudo-value of UNKNOWN
):
-
ON
: feature codes enabled -
OFF
: feature codes disabled -
DEAD
: feature codes permanently disabled (for the duration of the call)
ON
and OFF
are the toggled states. DEAD
is permanently disabled, so no feature code stuff is done if that is seen, regardless of any other bugs I might have put into the dialplan.
Here are the two custom feature codes I defined in features.conf
for toggling the feature code state and for permanently disabling feature codes.
[applicationmap]
ToggleFeatureCodes => *70,self,GoSub(FeatureCodeFu,toggleFeatureCodes,1)
DisableFeatureCodes => **70,self,GoSub(FeatureCodeFu,disableFeatureCodes,1)
While we’re in features.conf
, I’ll mention that I set these to nothing as a reminder that everything is controlled by the dialplan. I think this was not technically necessary, but I’m not sure.
[featuremap]
blindxfer => ;
disconnect => ;
automon => ;
atxfer => ;
parkcall => ;
automixmon => ;
Most of the magic happens in context FeatureCodeFu
in the dialplan. Here it is in its entirety:
[FeatureCodeFu]
exten => InitCalledChannel,1,Verbose(0,--- CalledIsLocal ${CONTEXT},${EXTEN} for chan:${CHANNEL})
same => n,Gosub(FeatureCodeFu,turnFeatureCodesOn,1)
same => n,Return()
exten => simpleReturn,1,Verbose(0, - ${CONTEXT},${EXTEN})
same => n,Return()
exten => toggleFeatureCodes,1,Verbose(0, - Toggling feature codes, was ${featureCodeState} for chan:${CHANNEL})
same => n,GoToIf($[${featureCodeState}=DEAD]?simpleReturn,1)
same => n,GoSubIf($[${featureCodeState}=ON]?turnFeatureCodesOff,1:turnFeatureCodesOn,1)
same => n,Return()
exten => disableFeatureCodes,1,Verbose(0, - Disabling feature codes permanently, was ${featureCodeState} for chan:${CHANNEL})
same => n,GoToIf($[${featureCodeState}=DEAD]?simpleReturn,1)
same => n,GoSub(turnFeatureCodesOff,1)
same => n,SET(featureCodeState=DEAD)
same => n,SET(DYNAMIC_FEATURES=)
same => n,SET(FEATURE(featuredigittimeout)=1)
same => n,Verbose(0, - ${CONTEXT},${EXTEN}: feature codes now ${featureCodeState}, ${FEATURE(featuredigittimeout)}ms)
same => n,Return()
exten => turnFeatureCodesOff,1,Verbose(0, - Setting feature codes OFF, was ${featureCodeState} for chan:${CHANNEL})
same => n,GoToIf($[${featureCodeState}=DEAD]?simpleReturn,1)
same => n,GoToIf($[${featureCodeState}=OFF]?simpleReturn,1)
same => n,SET(FEATUREMAP(atxfer)=)
same => n,SET(FEATUREMAP(automixmon)=)
same => n,SET(FEATUREMAP(automon)=)
same => n,SET(FEATUREMAP(blindxfer)=)
same => n,SET(FEATUREMAP(disconnect)=)
same => n,SET(FEATUREMAP(parkcall)=)
same => n,SET(featureCodeState=OFF)
same => n,Verbose(0, - ${CONTEXT},${EXTEN}: feature codes now ${featureCodeState}, ${FEATURE(featuredigittimeout)}ms)
same => n,Return()
exten => turnFeatureCodesOn,1,Verbose(0, - Setting feature codes ON, was ${featureCodeState} for chan:${CHANNEL})
same => n,GoToIf($[${featureCodeState}=DEAD]?simpleReturn,1)
same => n,GoToIf($[${featureCodeState}=ON]?simpleReturn,1)
same => n,SET(FEATUREMAP(atxfer)=${atxfer})
same => n,SET(FEATUREMAP(automixmon)=${automixmon})
same => n,SET(FEATUREMAP(automon)=${automon})
same => n,SET(FEATUREMAP(blindxfer)=${blindxfer})
same => n,SET(FEATUREMAP(disconnect)=${disconnect})
same => n,SET(FEATUREMAP(parkcall)=${parkcall})
same => n,SET(featureCodeState=ON)
same => n,Verbose(0, - ${CONTEXT},${EXTEN}: feature codes now ${featureCodeState}, ${FEATURE(featuredigittimeout)}ms)
same => n,Return()
The extensions/subroutines turnFeatureCodesOn
and turnFeatureCodesOff
are obedient workers who do what their names imply. They do that by manipulating the FEATUREMAP(...)
settings. When feature codes are off, each of those items is set to nothing. When feature codes are on, they are set to values of my choosing. (The variables like ${atxfer}
are actually constants that we’ll see in a moment.) Additionally, when disableFeatureCodes
is used, it sets the interdigit timer to 1ms; even if there is a bug in this configuration, nobody is going to punch buttons fast enough to beat that timer.
(A couple of side notes: First, at the beginning, I wasn’t sure what would happen if I set those items to nothing, so I set them to values like A1
, A2
, etc, instead. None of my stations have the ABCD column, so they would be impossible for users to dial. Second, I had originally save the values of each FEATUREMAP(...)
item into a local variable when toggling feature codes off. Then I used those local variable values when toggling them back on. That just seemed kind of cluttery, so I switched to the constants. Third, I couldn’t figure out a simple way to express “if X is true, then return”. Instead, I had to use GoToIf
to do that.)
While we’re talking about those constants, I might as well show the values:
[globals]
DYNAMIC_FEATURES=ToggleFeatureCodes#DisableFeatureCodes
featureCodeState=OFF
LocalCallingFlags=KTWX
LocalCalledFlags=ktwxb(FeatureCodeFu^InitCalledChannel^1)
blindxfer = *71
disconnect =
automon = *74
atxfer = *72
parkcall = *77
Those are all constants except for featureCodeState
, which I mentioned earlier. (I don’t have a feature code defined for disconnect
because I don’t really understand what the use case would be. It must be useful for something or it wouldn’t exist.)
(Another side note about specific values. I used *70
as the dial prefix to turn off feature codes as well as the code for toggling. To turn off feature codes during a call is **70
. My hope is that helps people remember them. If they can remember one, there’s a good chance they can remember all.)
I’m not showing all the details here, but I figure out in my dialplan whether the calling and called numbers are local. Then I separately enable feature codes for local numbers, whether they are calling or called. Why shouldn’t a local calling extension be able to prank a local called extension?) Here is a piece of it to give the flavor. All local stations have this as an initial context:
[CallingIsLocal]
exten => _[*#0-9]!,1,Verbose(0,---> ${CONTEXT},${EXTEN}: from:${CALLERID(all)})
same => n,Set(CallingFlags=${LocalCallingFlags})
same => n,Goto(AllTheThings,${EXTEN},1)
That tells us it’s a local caller, so we turn on flags for using feature codes. Then we jump to a context that has all of the things that a local station can possibly call.