How to Create a Call Processing Script
- Introduction
- Calling a Call Processing Script
- Overview of Call Processing API
- Method group Task
RouteToAsync(this ActiveConnection ac, ) - Method group Task
DivertAsync(this ActiveConnection ac, ) - Method group Task
ReplaceWithAsync(this ActiveConnection ac, ) - Example Call Processing Script
- How to Run it
- Comments for Code
- Sample Code
- See Also
Introduction
Call processing scripts are a powerful new feature of V20. They allow you to capture calls and process them using standard C# code - essentially giving you limitless possibilities to analyze a call and apply custom logic. Here are a couple of examples:
- Analyze caller ID and assign specific agents
- Do customer lookups based on caller ID and route accordingly
- Check time and date and process a call accordingly.
- Check date and play a prompt based on date.
Calling a Call Processing Script
Before you write a script you must think where and how you want this script to be triggered. You can configure a dial code for it, or forward calls to it from by assigning it a DID or forwarding it to the script based on a particular variable.
In update 2, you will be able to trigger the call on each inbound call coming to a SIP Trunk!
Once you have decided how the script will be triggered you will need to filter the calls and write your call processing logic.
Overview of Call Processing API
The API consists of three core methods which are passing calls to the new destination.
Method group Task<CallControlResult> RouteToAsync(this ActiveConnection ac,<Destination>)
This method creates a route which is bound to the specified connection ac
- ac - the active connection owned by RoutePoint. When the new destination answers its connection replaces ac. (participation of RoutePoint).
- Script should handle task failures. Script may simply call MyCall.Return to terminate its own connection with the caller.
- Script can try to build as many routes as it requires, but the first answered route will cancel all others and will replace the participation of RoutePoint in the call (will terminate the call for RoutePoint).
- RouteToAsync can be executed in any state of the RoutePoint connection. So route point may perform background routing when communicating with Caller (play prompts, handle DTMFs etc.).
- When the task succeeds, the call is revoked from RoutePoint (MyCall is disconnected) and the new destination continues handling.
Method group Task<CallControlResult> DivertAsync(this ActiveConnection ac,<Destination>)
This method diverts the call to the new destination without establishing (answering) connection (ringing on RoutePoint).
- If a call is already established with RoutePoint, method will fail and RouteToAsyc/ReplaceWithAsync should be used
- The active connection (in ringing state) owned by RoutePoint will be replaced with a new destination and RoutePoint will be disconnected from the call.
- This method is useful if the route point does not need to interact with the caller.
- If a task has failed, the script may continue to handle connection with the caller.
- When the task succeeds, the call is revoked from RoutePoint (MyCall connection is terminated) and the new destination starts call handling. The RoutePoint script is switched to Wrap Up mode and needs to finish its own job.
Method group Task<CallControlResult> ReplaceWithAsync(this ActiveConnection ac,<Destination>)
Also known as the “blind transfer” method.
- Allowed only in connected mode (RoutePoint accepted the call and interacts with user).
- Caller will be put on hold.
- Task will fail if the destination is not reachable.
- Script can continue processing the call after task failure (if the caller is still connected with RoutePoint).
- When a task succeeds, the script connection (ICallHandler.MyCall) is terminated and the script detached from call handling (new participant will take care of caller).
Example Call Processing Script
This sample demonstrates how to make a custom route point and how to program it:
- Basic structure of C# code supplied to RoutePoint
- Basic Usage of TCX.PBXAPI.CallControlAPI extension methods for ActiveConnection (ICall) interface
- Basic Working with MyCall object supplied by CallFlowScriptingCore
- Basic Working with configuration (PBX parameters)
- Using RouteToAsyc extension method of ActiveConnection Object
An example of RoutePoint functionality:
- Route point is accepting only calls which are sent using blind transfer from the phone(Extension). Direct calls are rejected.
- Unlimited calls can be transferred to this RoutePoint by any extension simultaneously (each call is handled separately)
- Call is returned after 15 seconds to the transferor (extension) directly (no forwarding applied). If the returned call is not answered in 15 seconds, calling is canceled and repeated again in 15 seconds.
- Caller hears music on hold as it is configured for Parking on PBX.
How to Run it
- Create a RoutingPoint with any number - for example #101 - and set RoutePoint.ScriptCode property to the text below (UI still not allow to create routing point with custom (hand written code) it requires some sort of zip file, which is not really required for simple script)
- RoutePoint should appear in the corresponding list (“CFD applications” at this moment) with green dot [compilation should not fail for this code])
- Then:
- If any extension will transfer its call to the number #101, the call will be returned back in 15 seconds.
- Caller will hear music on hold as it is configured for Parking. (Script uses this setting but code can be modified to generate other content for the caller).
- If returned call is not answered, the RoutePoint will try again (and again) 15 seconds after the previous attempt until the caller will not drop the call, or the original transferor will answer (or its call will be picked up).
Comments for Code
- The code of “scripted” object is based on (use, implements and/or inherits)
- CallFlow namespace
- CallFlow.ICall
- CallFlow.ICallHandler
- CallFlow.ICallHandlerEx
- CallFlow.ScriptBase<T>
- “Script object” class must inherit CallFlow.ScriptBase<T> and implement all abstract methods as required for its instance.
- The object is running when the ScriptingHost executes the call handler using ICallHandler.Start method.
- Script must be ended with ICall.Return explicitly.
- Preferred implementation of ICall.Start method is “async void” which runs detached task (must catch all exceptions).
- Script implementation must control only the MyCall object exposed by Scripting Host. It is the only subject of the call script session.
- When MyCall (participation of the route point in the call) is ended, the script implementation should wrap up and finish its session.
- The call flow script is not a way to monitor system configuration or any external resources.
- It is not a way to monitor all calls in the system.
- It is just a RoutePoint logic, which can be integrated with other call flows.
- In other words: script handles one of the calls which are connected with RoutePoint, but never initiates a new call.
- Routing API is encapsulated into static class TCX.PBXAPI.CallControlAPI which exposes extension methods for
- TCX.Configuration.ActiveCannection
- TCX.Configuration.DN
- TCX.Configuration.RegistrarRecord
Sample Code
#nullable disable using CallFlow; using System; using System.Threading; using System.Threading.Tasks; using TCX.Configuration; using TCX.PBXAPI; namespace dummy { public class ParkingRoutePointSample : ScriptBase<ParkingRoutePointSample> { async Task<CallControlResult> ProcessAutoPickup(RoutePoint sp, DestinationStruct returnTo, CancellationToken token) { while (true) try { return await Task.Delay(TimeSpan.FromSeconds(15), token).ContinueWith(x => { MyCall.Trace("{0} - automatic redirection of the call from {1}.{2} to '{3}'", MyCall.DN, MyCall.Caller?.CallerID, MyCall.Caller?.DN, returnTo); return MyCall.RouteToAsync(new RouteRequest { RouteTarget = returnTo, TimeOut = TimeSpan.FromSeconds(15) //will ring until failure } ); } , TaskContinuationOptions.NotOnCanceled).Unwrap(); } catch (OperationFailed ex) { MyCall.Trace("Automatic redirection failed: {0}", ex.TheResult); MyCall.Trace("Continue hold call from {0}({1}) on {2}", MyCall.Caller?.CallerID, MyCall.Caller?.DN, MyCall.DN); continue; } } PhoneSystem ps = null; /// <summary> /// /// </summary> public override async void Start() { await Task.Run(async () => { try { MyCall.Debug($"Script start delay: {DateTime.UtcNow - MyCall.LastChangeStatus}"); MyCall.Debug($"Incoming connection {MyCall}"); ps = MyCall.PS as PhoneSystem; CallControlResult lastresult = null; DN referredBy = null; RoutePoint thisPark = null; string callerID = ""; DN callerDN = null; bool scriptCompleted = true; try { referredBy = MyCall.ReferredByDN?.GetFullSnapshot() as Extension; thisPark = MyCall.DN?.Clone() as RoutePoint; callerID = MyCall.Caller?.CallerID; callerDN = MyCall.Caller?.DN?.Clone() as DN; MyCall.Trace( "Parked call from {0}({1}) on {2}", callerID, callerDN, thisPark ); if (referredBy == null) { MyCall.Trace("{0} rejects call from {1}. Reason: No referrer specified", thisPark, callerDN); return; } var cancelationToken = new CancellationTokenSource(); MyCall.OnTerminated += () => { cancelationToken.Cancel(); }; lastresult = await MyCall.AssureMedia().ContinueWith( x => { if(!string.IsNullOrWhiteSpace(ps.GetParameterValue("PARK_MOH_SOURCE"))) MyCall.SetBackgroundAudio(true, new string[] { ps.GetParameterValue("PARK_MOH_SOURCE") }); else MyCall.SetBackgroundAudio(true, new string[] { ps.GetParameterValue("MUSICONHOLDFILE") }); return ProcessAutoPickup(thisPark, new DestinationStruct(referredBy), cancelationToken.Token); }, TaskContinuationOptions.OnlyOnRanToCompletion).Unwrap(); } catch (PBXIsNotConnected ex) { MyCall.Error($"Call control API is not available:\n{ex}"); scriptCompleted = false; } catch (TaskCanceledException) { MyCall.Trace($"Call was disconnected from parking place"); } catch (Exception ex) { MyCall.Error($"Parking failure:\n{ex}"); scriptCompleted = false; } finally { try { MyCall.Info("Call from {0}({1}) parked by {2} on {3} finished with result={4}", callerID, callerDN, referredBy, thisPark, lastresult?.ToString() ?? "terminated"); } catch (Exception ex) { MyCall.Error($"SharedParkingFlow finalize exception {ex}"); } MyCall.Return(scriptCompleted); } } catch { MyCall.Return(false); } }); } } }
See Also
- Call Control API for Linux
- Call Control API for Windows
- Google Cloud Storage & Speech API
- Call Processing Script for DTMF Input
Last Updated
This document was last updated 5 March 2024