Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file added callautomation-Simple-IVR/Audio/agent.wav
Binary file not shown.
Binary file added callautomation-Simple-IVR/Audio/customercare.wav
Binary file not shown.
Binary file added callautomation-Simple-IVR/Audio/invalid.wav
Binary file not shown.
Binary file added callautomation-Simple-IVR/Audio/mainmenu.wav
Binary file not shown.
Binary file added callautomation-Simple-IVR/Audio/marketing.wav
Binary file not shown.
Binary file added callautomation-Simple-IVR/Audio/sales.wav
Binary file not shown.
23 changes: 23 additions & 0 deletions callautomation-Simple-IVR/ConfigurationManager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import configparser


class ConfigurationManager:
__configuration = None
__instance = None

def __init__(self):
if(self.__configuration == None):
self.__configuration = configparser.ConfigParser()
self.__configuration.read('config.ini')
@staticmethod
def get_instance():
if(ConfigurationManager.__instance == None):
ConfigurationManager.__instance = ConfigurationManager()
return ConfigurationManager.__instance

def get_app_settings(self, key):
if (key != None):
return self.__configuration.get('default', key)
return None


12 changes: 12 additions & 0 deletions callautomation-Simple-IVR/Logger.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import enum


class Logger(enum.Enum):

INFORMATION = 1
ERROR = 2

@staticmethod
def log_message(message_type, message):
log_message = message_type.name + " : " + message
print(log_message)
Empty file.
16 changes: 16 additions & 0 deletions callautomation-Simple-IVR/config.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# app settings
[default]
Connectionstring=%Connectionstring%
#<!-- Phone number provisioned for the resource (in E.164 Format, e.g. +14251002000). -->
ACSAlternatePhoneNumber=%ACSAlternatePhoneNumber%
ParticipantToAdd=%ParticipantToAdd%
BaseUri=%BaseUri%
MainMenuAudio = /Audio/mainmenu.wav
SalesAudio= /Audio/sales.wav
MarketingAudio= /Audio/marketing.wav
CustomerCareAudio=/Audio/customercare.wav
AddParticipant = /Audio/AddParticipant.wav
AgentAudio= /Audio/agent.wav
InvalidInputAudio= /Audio/InvalidInputAudio.wav
TimedoutAudio = /Audio/TimedoutAudio.wav
AllowedHosts = *
177 changes: 177 additions & 0 deletions callautomation-Simple-IVR/program.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import re
import azure
import ast
from azure.eventgrid import EventGridEvent
from azure.communication.identity._shared.models import PhoneNumberIdentifier,\
CommunicationUserIdentifier,CommunicationIdentifierKind,identifier_from_raw_id
import json
from aiohttp import web
from Logger import Logger
from ConfigurationManager import ConfigurationManager
from azure.communication.callautomation import CallAutomationClient,CallInvite,\
CallAutomationEventParser,CallConnected,CallMediaRecognizeDtmfOptions,\
FileSource,DtmfTone,PlayCompleted,PlayFailed,AddParticipantSucceeded,AddParticipantFailed,\
RecognizeCompleted,RecognizeFailed\

configuration_manager = ConfigurationManager.get_instance()
calling_Automation_client = CallAutomationClient.from_connection_string(configuration_manager.get_app_settings("Connectionstring"))
base_uri=configuration_manager.get_app_settings('base_uri')
caller_id = None
user_identity_regex: str = "8:acs:[0-9a-fA-F]{8}\\-[0-9a-fA-F]{4}\\-[0-9a-fA-F]{4}\\-[0-9a-fA-F]{4}\\-[0-9a-fA-F]{12}_[0-9a-fA-F]{8}\\-[0-9a-fA-F]{4}\\-[0-9a-fA-F]{4}\\-[0-9a-fA-F]{4}\\-[0-9a-fA-F]{12}"
phone_identity_regex: str = "^\\+\\d{10,14}$"

def get_identifier_kind(self, participantnumber: str):
# checks the identity type returns as string
if (re.search(self.user_identity_regex, participantnumber)):
return CommunicationIdentifierKind.COMMUNICATION_USER
elif (re.search(self.phone_identity_regex, participantnumber)):
return CommunicationIdentifierKind.PHONE_NUMBER
else:
return CommunicationIdentifierKind.UNKNOWN

class Program():

def __init__(self):
app = web.Application()
app.add_routes([web.post('/api/incomingCall', self.run_sample)])
app.add_routes([web.get('/Audio/{file_name}', self.load_file)])
app.add_routes([web.post('/api/calls/{contextId}', self.start_callBack)])
web.run_app(app, port=58963)

async def run_sample(self,request):
try:
self.source_identity=CommunicationUserIdentifier(configuration_manager.get_app_settings("Connectionstring"))
content = await request.content.read()
post_data = str(content.decode('UTF-8'))
if post_data:
json_data = ast.literal_eval(json.dumps(post_data))
event = EventGridEvent.from_dict(ast.literal_eval(json_data)[0])
event_data = event.data
if event.event_type == 'Microsoft.EventGrid.SubscriptionValidationEvent':
try:
subscription_validation_event = event_data
code = subscription_validation_event['validationCode']
if code:
data = {"validationResponse": code}
Logger.log_message(Logger.INFORMATION,
"Successfully Subscribed EventGrid.ValidationEvent --> " + str(data))
return web.Response(body=str(data), status=200)
except Exception as ex:
Logger.log_message(
Logger.ERROR, "Failed to Subscribe EventGrid.ValidationEvent --> " + str(ex))
return web.Response(text=str(ex), status=500)

callerId = str(event_data['from']['rawId'])
incoming_call_context = event_data['incomingCallContext']
callback_uri = base_uri + '/api/calls/callerId=?'+ callerId
answer_call_result = calling_Automation_client.answer_call(incoming_call_context, callback_uri)
return web.Response(status=200)
except Exception as ex:
Logger.log_message(
Logger.ERROR, "Failed to start Call Connection --> " + str(ex))

async def load_file(self, request):
file_name = request.match_info.get('file_name', 'Anonymous')
resp = web.FileResponse(f'Audio/{file_name}')
return resp

async def start_callBack(self,request):
try:
content = await request.content.read()
event = CallAutomationEventParser.parse(content)
Logger.log_message(Logger.INFORMATION,'Event Kind IS :'
+ event.kind)
call_connection = calling_Automation_client.get_call_connection(event.call_connection_id)
call_connection_media =call_connection.get_call_media()
if event.__class__ == CallConnected:
Logger.log_message(Logger.INFORMATION,'CallConnected event received for call connection id --> '
+ event.call_connection_id)

recognize_Options = CallMediaRecognizeDtmfOptions(identifier_from_raw_id(request.query_string),max_tones_to_collect=1)
recognize_Options.interrupt_prompt = True
recognize_Options.inter_tone_timeout = 30
recognize_Options.initial_silence_timeout=5
File_source=FileSource(uri=(base_uri + configuration_manager.get_app_settings('MainMenuAudio')))
File_source.play_source_id= 'MainMenu'
recognize_Options.play_prompt = File_source
recognize_Options.operation_context= 'MainMenu'
call_connection_media.start_recognizing(recognize_Options)

if event.__class__ == RecognizeCompleted and event.operation_context == 'MainMenu' :
Logger.log_message(Logger.INFORMATION,'RecognizeCompleted event received for call connection id --> '+ event.call_connection_id
+'Correlation id:'+event.correlation_id)
tone_detected=event.collect_tones_result.tones[0]
if tone_detected == DtmfTone.ONE:
play_source = FileSource(uri=(base_uri + configuration_manager.get_app_settings('SalesAudio')))
play_source.play_source_id='SimpleIVR'
call_connection_media.play_to_all(play_source,operation_context='SimpleIVR')

elif tone_detected == DtmfTone.TWO :
play_source = FileSource(uri=(base_uri + configuration_manager.get_app_settings('MarketingAudio')))
call_connection_media.play_to_all(play_source,operation_context='SimpleIVR')
elif tone_detected == DtmfTone.THREE :
play_source = FileSource(uri=(base_uri + configuration_manager.get_app_settings('CustomerCareAudio')))
call_connection_media.play_to_all(play_source, operation_context='SimpleIVR')
elif tone_detected == DtmfTone.FOUR :
play_source = FileSource(uri=(base_uri + configuration_manager.get_app_settings('AgentAudio')))
call_connection_media.play_to_all(play_source, operation_context='AgentConnect')
elif tone_detected == DtmfTone.FIVE :
call_connection.hang_up(True)
else:
play_source = FileSource(uri=(base_uri + configuration_manager.get_app_settings('InvalidAudio')))
call_connection_media.play_to_all(play_source, operation_context='SimpleIVR')


if event.__class__ == RecognizeFailed and event.operation_context == 'MainMenu' :
Logger.log_message(Logger.INFORMATION,'Recognition timed out for call connection id --> '+ event.call_connection_id
+'Correlation id:'+event.correlation_id)
play_source = FileSource(uri=(base_uri + configuration_manager.get_app_settings('InvalidAudio')))
call_connection_media.play_to_all(play_source,operation_context='SimpleIVR')
if event.__class__ == PlayCompleted:

if event.operation_context == 'AgentConnect':
participant_to_add = configuration_manager.get_app_settings('ParticipantToAdd')

if(participant_to_add and len(participant_to_add)):

Participant_Identity = get_identifier_kind(participant_to_add)
if Participant_Identity == CommunicationIdentifierKind.COMMUNICATION_USER :
self.Participant_Add=CommunicationUserIdentifier(participant_to_add)
call_invite=CallInvite(self.Participant_Add)
if Participant_Identity == CommunicationIdentifierKind.PHONE_NUMBER :
self.Participant_Add=PhoneNumberIdentifier(participant_to_add)
call_invite=CallInvite(self.Participant_Add,sourceCallIdNumber=self.source_identity)

Logger.log_message(Logger.INFORMATION,'Performing add Participant operation')
self.add_participant_response=call_connection.add_participant(call_invite)
Logger.log_message(
Logger.INFORMATION, 'Call initiated with Call Leg id -- >' + self.add_participant_response.participant)

if event.operation_context == 'SimpleIVR':
Logger.log_message(Logger.INFORMATION,'PlayCompleted event received for call connection id --> '+ event.call_connection_id
+'Call Connection Properties :'+event.correlation_id)
call_connection.hang_up(True)

if event.__class__ == PlayFailed:
Logger.log_message(Logger.INFORMATION,'PlayFailed event received for call connection id --> '+ event.call_connection_id
+'Call Connection Properties :'+event.correlation_id)
call_connection.hang_up(True)
if event.__class__ == AddParticipantSucceeded:
Logger.log_message(Logger.INFORMATION,'AddParticipantSucceeded event received for call connection id --> '+ event.call_connection_id
+'Call Connection Properties :'+event.correlation_id)
call_connection.hang_up(True)
if event.__class__ == AddParticipantFailed:
Logger.log_message(Logger.INFORMATION,'AddParticipantFailed event received for call connection id --> '+ event.call_connection_id
+'Call Connection Properties :'+event.correlation_id)
call_connection.hang_up(True)

except Exception as ex:
Logger.log_message(
Logger.ERROR, "Call objects failed to get for connection id --> " + str(ex))



if __name__ == '__main__':
Program()


50 changes: 50 additions & 0 deletions callautomation-Simple-IVR/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
---
page_type: sample
languages:
- python
products:
- azure
- azure-communication-CallAutomation
---

# Call Automation - Simple IVR Solution

The purpose of this sample application is to demonstrate the usage of the Azure Communication Call Automation SDK for building solutions related to Interactive Voice Response (IVR). The application accepts an incoming call when an callee dialed in to either ACS Communication Identifier or ACS acquired phone number. Application prompt the Dual-Tone Multi-Frequency (DTMF) tones to select, and then plays the appropriate audio file based on the key pressed by the callee. The application has been configured to accept tone-1 through tone-5, and if any other key is pressed, the callee will hear an invalid tone and the call will be disconnected.
The application is a console based application build using Python 3.9 and above.

## Getting started

### Prerequisites

- Create an Azure account with an active subscription. For details, see [Create an account for free](https://azure.microsoft.com/free/)
- [Python](https://www.python.org/downloads/) 3.9 and above
- Create an Azure Communication Services resource. For details, see [Create an Azure Communication Resource](https://docs.microsoft.com/azure/communication-services/quickstarts/create-communication-resource). You'll need to record your resource **connection string** for this sample.
- Get a phone number for your new Azure Communication Services resource. For details, see [Get a phone number](https://docs.microsoft.com/azure/communication-services/quickstarts/telephony-sms/get-phone-number?pivots=platform-azp)

- Download and install [VS Code](https://code.visualstudio.com/download) or [Visual Studio (2022 v17.4.0 and above)](https://visualstudio.microsoft.com/vs/)
-[Python311](https://www.python.org/downloads/) (Make sure to install version that corresponds with your visual studio instance, 32 vs 64 bit)
- Download and install [Ngrok](https://www.ngrok.com/download). As the sample is run locally, Ngrok will enable the receiving of all the events.
- Generate Ngrok Url by using below steps.
- Open command prompt or powershell window on the machine using to run the sample.
- Navigate to directory path where Ngrok.exe file is located. Then, run:
- ngrok http {portNumber}(For e.g. ngrok http 8080)
- Get Ngrok Url generated. Ngrok Url will be in the form of e.g. "https://95b6-43-230-212-228.ngrok-free.app"




### Configuring application

- Open the config.ini file to configure the following settings

- Connection String: Azure Communication Service resource's connection string.
- Source Phone: Phone number associated with the Azure Communication Service resource.
- ParticipantToAdd: Target phone number to add as participant.
- Base Uri: Base url of the app. (For local development replace the Ngrok url.For e.g. "https://95b6-43-230-212-228.ngrok-free.app")

### Run the Application

- Add azure communication CallAutomation wheel file path in requirement.txt
- Navigate to the directory containing the requirements.txt file and use the following commands for installing all the dependencies and for running the application respectively:
- pip install -r requirements.txt
- python program.py
3 changes: 3 additions & 0 deletions callautomation-Simple-IVR/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
aiohttp==3.7.4.post0
azure-communication-callautomation @file:///C:/Users/v-moirf/pyenv311/azure_communication_callautomation-1.0.0a20230413001-py3-none-any.whl
azure-communication-identity==1.3.1