#Switchy NSG Sample Outbound # #Author: Nenad Corbic # #This application is designed to demonstrate switchy capabilities #and ease of use. The pplication will connect to actively running #NSG application on the local system. It will then originate #a single call and play introductory ivr message. From then #on application will wait for user input via DTMF. # #All user logic should be defined in CallLogic Class. # #Variables # self. are global in nature. # call.vars.['var_name'] should be used for per call info and state # #Switchy Documentation # https://github.com/sangoma/switchy/blob/master/switchy/models.py # class: Session Event Call Job # # https://github.com/sangoma/switchy/blob/master/switchy/observe.py # class: EventListener Client # #Sample Switchy Applications # https://switchy.readthedocs.org/en/latest/apps.html # #License: # BSD License # http://opensource.org/licenses/bsd-license.php # # Copyright (c) 2015, Sangoma Technologies Inc # All rights reserved. # # Redistribution and use in source and binary forms, with or without modification, # are permitted provided that the following conditions are met: # 1. Developer makes use of Sangoma NetBorder Gateway or Sangoma Session Border Controller # 2. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 3. Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS # OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. # IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. import time import switchy from switchy.marks import event_callback from switchy.apps.players import TonePlay #Enable logging to stderr log = switchy.utils.log_to_stderr() #Debug levels: INFO in production, DEBUG in devel log.setLevel("INFO") #Specify NSG IP information #In this example the Switchy sample app is running on #NSG appliance hence the use of local address host = "127.0.0.1" port = 8821 #Setup Switchy listener and connect to NSG Event Socket listener = switchy.EventListener(host=host, port=port) client = switchy.Client(host, port=port, listener=listener) client.connect() listener.connect() #IVR Call Logic #This is the user IVR logic #All custom development should be done here. #Variables # self. are global in nature. # call.vars.['var_name'] should be used for per call info and state # class IVRCallLogic(object): """Play a 'milli-watt' tone on the outbound leg and echo it back on the inbound """ def __init__(self): #Get the install directory of NSG and append recording to it self.recdir = client.cmd('global_getvar base_dir') self.recdir += "/recording" log.info("Setting recording dir to '{}" . format(self.recdir)) #Get the install directory of NSG and append sounds to it self.sound_dir = client.cmd('global_getvar base_dir') self.sound_dir += "/sounds" log.info("Setting sounds dir to '{}" . format(self.sound_dir)) self.stereo=False try: client.cmd('load mod_sndfile') except: pass self.timer = switchy.utils.Timer() self.dtmf_timeout = switchy.utils.Timer() self.dtmf_timeout_period = 3.0 @event_callback('CHANNEL_PARK') def on_park(self, sess): if sess.is_inbound(): sess.answer() @event_callback("CHANNEL_ANSWER") def on_answer(self, sess): call = sess.call # inbound leg simply echos back the tone if sess.is_inbound(): sess.echo() # play infinite tones on calling leg if sess.is_outbound(): #Start recording a call call.vars['record']=True self.rec_filename = '{}/callee_{}.wav'.format(self.recdir, sess.uuid) sess.start_record(self.rec_filename, stereo=self.stereo) #Play initial greeting call.vars['play_welcome']=True play_filename = '{}/en/us/callie/ivr/8000/ivr-welcome.wav'.format(self.sound_dir) sess.playback(play_filename) @event_callback('DTMF') def on_digit(self, sess): call = sess.call digit = int(sess['DTMF-Digit']) if call.vars.get('playing') == True: sess.breakmedia() elapsed=self.dtmf_timeout.elapsed() if elapsed >= self.dtmf_timeout_period: log.debug("'{}': Resetting DTMF queue: timeout" . format(sess.uuid)) call.vars['incoming_dtmf'] = None self.dtmf_timeout.reset() log.info("'{}': DTMF dtmf digit '{}'". format(sess.uuid, digit)) if call.vars.get('incoming_dtmf') == None: call.vars['incoming_dtmf'] = str(digit) else: call.vars['incoming_dtmf'] += str(digit) if call.vars.get('incoming_dtmf') == '911': log.info("'{}': Playing 911 file STARTED" . format(sess.uuid)) call.vars['incoming_dtmf'] = None play_filename = '{}/en/us/callie/ivr/8000/ivr-contact_system_administrator.wav'.format(self.sound_dir) call.vars['playing'] = True sess.playback(play_filename) if call.vars.get('incoming_dtmf') == '811': log.info("'{}': Playing 811 file STARTED" . format(sess.uuid)) call.vars['incoming_dtmf'] = None play_filename = '{}/en/us/callie/ivr/8000/ivr-hello.wav'.format(self.sound_dir) call.vars['playing'] = True sess.playback(play_filename) if call.vars.get('incoming_dtmf') == '111': log.info("'{}': User chose to hangup" . format(sess.uuid)) call.vars['incoming_dtmf'] = None sess.hangup() if call.vars.get('incoming_dtmf') != None and len(call.vars['incoming_dtmf']) >= 3: log.debug("'{}': Resetting DTMF queue" . format(sess.uuid)) call.vars['incoming_dtmf'] = None self.dtmf_timeout.reset() @event_callback("PLAYBACK_START") def on_playback_start(self, sess): call = sess.call log.info("'{}': got PLAYBACK_START ". format(sess.uuid)) if call.vars.get('play_welcome') == True: log.info("'{}': Playing Welcome STARTED" . format(sess.uuid)) @event_callback("PLAYBACK_STOP") def on_playback_stop(self, sess): call = sess.call log.info("'{}': got PLAYBACK_STOP ". format(sess.uuid)) call.vars['playing'] = False if call.vars.get('play_welcome') == True: call.vars['play_welcome']=False log.info("'{}': Playing Welcome STOPPED, Lets Wait for Digits" . format(sess.uuid)) @event_callback("RECORD_START") def on_playback_start(self, sess): call = sess.call log.info("'{}': got RECORD_START ". format(sess.uuid)) @event_callback("RECORD_STOP") def on_playback_stop(self, sess): call = sess.call log.info("'{}': got RECORD_STOP ". format(sess.uuid)) @event_callback('CHANNEL_HANGUP') def on_hangup(self, sess, job): call = sess.call log.info("'{}': got HANGUP ". format(sess.uuid)) if call.vars.get('play_welcome') == True: call.vars['play_welcome']=False log.info("'{}': Got HANGUP while playing" . format(sess.uuid)) #job.events.pprint() #sess.events.pprint() #Load IVRCallLogic application into switchy ivr_app_id = client.load_app(IVRCallLogic) # Listener MUST start after the application is loaded listener.start() # Make an outbound call via NSG # SIP Call # dest_url='did@domain.com' #Remote SIP URI # profile='internal' #NSG defined SIP profile name # proxy='domain.com' #Optional: proxy domain if required # if unsure leave commented out # # FreeTDM Call # dest_url='[A,a]/did' #A=ascedning hunt, a=descending hunt, DID number # endpoint='freetdm" #Default endpoint is SIP, so we have to specify freetdm # profile='g1' #profile is used as trunk group definition: g1 == group 1 # job = client.originate( #dest_url='4113@10.10.48.14', #dest_url='1000@10.10.12.5:6060', #profile="internal", dest_url='a/1000', endpoint='freetdm', profile="g1", #proxy='10.10.12.5', app_id=ivr_app_id ) #job2 = client.originate( #dest_url='4113@10.10.48.14', # dest_url='1000@10.10.12.5:6060', # profile="internal", #dest_url='a/1000', #endpoint='freetdm', #profile="g1", #proxy='10.10.12.5', # app_id=ivr_app_id #) #Wait for job to finish #In this example we sessid = job.get(30) try: if sessid != job.sess_uuid: log.info("Call failed") raise log.debug("Session '{}'". format(job.sess_uuid)) call = client.listener.calls[job.sess_uuid] orig_sess = call.sessions[0] # get the originating session #Wait for call to complete while (orig_sess.hungup == False): time.sleep(1) # let it play a bit except: pass #listener.disconnect()