Passing EXTEN value to Python script in Dialplan

Hello, how can I execute a Python script in the Dialplan and pass the value of EXTEN as an argument to the Python script?

I have already tried the following:

exten => 12345,1,AGI(/path/to/script.py, ${EXTEN})

The CLI indicates that the script was executed successfully, but the script is not performing its job.

To execute an external program (script (shell or a ‘P’ language) or compiled (c, Go, Rust)) you can use the agi() application or the SHELL() function. Case is significant for function names.

The SHELL() function executes a command line and the output can be returned as a channel variable which you need to parse in the dialplan. Arguments can be passed on the command line.

The agi() application executes an external program written to the AGI protocol. Arguments can be passed as arguments to agi() or as channel variables. Your AGI can return values as channel variables.

SHELL() is used for simple stuff, while agi() allows much more complex operations including executing Asterisk commands.

If you use the agi() application, the agi set debug on command may prove clueful.

That’s not wrong, as far as it goes, subject to correct permissions, #! lines, interpreter names, PATH settings, handling the requirement introduced by using AGI, etc.

To more literally answer the question:

exten => 1234,n,System(“/usr/bin/python3 /pth/to/script.py ${EXTEN}”)

If you do use AGI, there is no point in passing ${EXTEN} as it is passed as part of the AGI protocol.

Both methods do not work.

exten => 1234,1,AGI(/path/to/script.py, ${EXTEN})
Result: No error, but script action does not complete

exten => 1234,1,System(/usr/bin/python3 /path/to/script.py ${EXTEN})
Result: no error, but script action does not complete

That does not work:

exten => 1234,n,System(“/usr/bin/python3 /pth/to/script.py ${EXTEN}”)

Please try this by yourself so u can see.

I would not expect the AGI method to work. Your script does not follow the AGI protocol. An AGI takes a bit more work on the front end, but provides a lot more functionality.

Using SHELL()

extension.conf:

        exten = 12345,1,                verbose(1,[${EXTEN}@${CONTEXT}])
        same = n,                       set(foo=${SHELL(/home/sedwards/script.py ${EXTEN})})
        same = n,                       hangup()

script.py

#!/bin/python3

import	sys

print('Hello ' + ' '.join(sys.argv))

Confirm the script is executable:

ls -l script.py
-rwxrwxr-x 1 sedwards sedwards 65 Dec  5 17:38 script.py

Console output:

    -- Executing [12345@newline:1] Verbose("SIP/poly-77a1-00000057", "1,[12345@newline]") in new stack
 [12345@newline]
    -- Executing [12345@newline:2] Set("SIP/poly-77a1-00000057", "foo=Hello /home/sedwards/script.py 12345
    -- ") in new stack

Note that the <nl> in the output is included in the output captured in the channel variable foo.

If the above does not work for you, I’d look at permissions along the script path and your shell PATH environment variable

exten => 11111,1,verbose(1,[${EXTEN}@${CONTEXT}])
same => n,set(foo=${SHELL(/etc/asterisk/stt-vosk/apn/send_push.py ${EXTEN})})

Executing [11111@11119:1] Verbose(“PJSIP/11119-00000003”, “1,[11111@11119]”) in new stack

[11111@11119]

Executing [11111@11119:2] Set(“PJSIP/11119-00000003”, “foo=”) in new stack

Auto fallthrough, channel ‘PJSIP/11119-00000003’ status is ‘UNKNOWN’

Debugging AGI can be tricky, because Asterisk sets stderr to /dev/null when it spawns your script. This means any error messages will completely disappear.

To get around this, put something like the following close to the top of your script:

import os
log = open("/tmp/agi_try.log", "w")
os.dup2(log.fileno(), 2)
log.close()
del log

FastAGI can give you more control over your own process.

Using the preformatted text tags (or backticks like in Markdown) will make your snippets and output more readable.

What does send_push.py output if you execute it from a shell command line?

If you’re interested in speech recognition, this may help:

Thank you, I solved this by editing my Python script. It no longer produces outputs, so it works now with:

exten => 11111,1,System(/etc/asterisk/stt-vosk/apn/send_push.py ${EXTEN})

The script is for sending push notifications to an iOS app.

The directory name is from an old project. I no longer use STT. The reason why I no longer use STT is that the audio processing takes too long when there are more than 10 words, and it is not processed quickly enough in the dialplan before proceeding.

I’ve found text to speech using Azure and speech to text using Google to be very acceptable.

But you will have delay because it’s not real time audio stream.

We (a company I worked for until recently) wrote our own Asterisk application so we could read frames using ast_read(chan) and send them to Google in ‘real time.’

Google returns incremental text and refines it as more audio is received. For example I would say ‘I want a pony’ and Google may return (while I’m speaking):

‘I’
‘I won’
‘I want a’
‘I want a pony’

The company modified Asterisk 11.25.0 (yep – almost old enough to vote) to provide features specific to their business needs.

There are more modern ways to get real time audio. You may want to investigate websockets and ARI.

This sounds interesting, but what I actually wanted was the following:
Imagine you call the dial plan and say: “Good day, my name is xyz, I didn’t understand your name correctly. I actually wanted to speak with ‘Mr. Mustermann.’”

The system should immediately recognize the phrase “didn’t understand” and wait until the person stops talking. Once they stop, it should immediately respond with: “My name is Mr. Flink.”

The problem I had, as mentioned earlier, is that the entire audio file gets uploaded to Google. Then, the text is evaluated and checked to see if any of the following words are recognized.

Let me give you an example:

same => n,MixMonitor(${FILENAME})

same => n,Set(GLOBAL(INCOMING_FREIZEICHEN)=${IF($[${ISNULL(${GLOBAL(INCOMING_FREIZEICHEN)})}]?1:${IF($[${GLOBAL(INCOMING_FREIZEICHEN)} = 3]?1:${MATH(${GLOBAL(INCOMING_FREIZEICHEN)}+1)})})})

same => n,Set(GLOBAL(INCOMING_FREIZEICHEN)=${CUT(INCOMING_FREIZEICHEN,.,1)})

same => n,Verbose(1, "This is call number ${GLOBAL(INCOMING_FREIZEICHEN)}")

same => n,Playback(/etc/asterisk/sounds/freizeichen/Freizeichen${GLOBAL(INCOMING_FREIZEICHEN)})

same => n,Playback(/etc/asterisk/sounds/annahme_standard/geraeusch)

same => n,Set(GLOBAL(ANNAHME_STANDARD_BEGRUESSUNG)=${IF($[${ISNULL(${GLOBAL(ANNAHME_STANDARD_BEGRUESSUNG)})}]?1:${IF($[${GLOBAL(ANNAHME_STANDARD_BEGRUESSUNG)} = 3]?1:${MATH(${GLOBAL(ANNAHME_STANDARD_BEGRUESSUNG)}+1)})})})

same => n,Set(GLOBAL(ANNAHME_STANDARD_BEGRUESSUNG)=${CUT(ANNAHME_STANDARD_BEGRUESSUNG,.,1)})

same => n,Verbose(1, "This is call number ${GLOBAL(ANNAHME_STANDARD_BEGRUESSUNG)}")

same => n,Playback(/etc/asterisk/sounds/annahme_standard/${GLOBAL(ANNAHME_STANDARD_BEGRUESSUNG)})

same => n,MixMonitor(/etc/asterisk/stt-vosk/audio_stream.wav)

same => n,WaitForSilence(2500,1,120)

same => n,Set(WAITSTATUS=${WAITSTATUS})

same => n,GotoIf($["${WAITSTATUS}" = "SILENCE"]?silence_detected_1:timeout_detected_1)

; Silence Detected Begrüssung 1

same => n(silence_detected_1),AGI(/etc/asterisk/stt-vosk/ivr-stt/server.sh)

same => n,NoOp(Recognized Text: ${RECOGNIZED_TEXT})

same => n,GotoIf($["${RECOGNIZED_TEXT}" =~ "(ihr|verstanden|falsch|verbunden|wer|wie|heissen|nicht)" & "${RECOGNIZED_TEXT}" =~ ".*[aeiouAEIOU].*"]?recognized_question_1)

same => n,GotoIf($["${RECOGNIZED_TEXT}" != "" & "${RECOGNIZED_TEXT}" =~ ".*[aeiouAEIOU].*"]?recognized_hello_1)

same => n,Goto(unrecognized_hello_1)

Correct. You need to stream audio to your STT provider and process responses as they occur.