I am going to implement my Asterisk CTI class using the Asterisk REST Interface (ARI). The CTI class has different class objects, for example, a reactor object for websocket event notification, a websocket event handler object for receiving of Asterisk telephony events, a thread with a circular buffer to handle the telephony events, also a thread pool to deliver each telephony event to the calling application.
To get telephony events notification from Asterisk, we need to subscribe endpoint eventsource first. For testing #1, I have setup two extensions 1000 and 1002 in FreePBX, I subscribed evens for the two extensions using my class. I dialed 1002 from 1000, answered the call then hangup the call. I found a lot of events were generated from Asterisk and it seems there is no way to filter the number events from Asterisk. So I wrote a function to filter some unwanted events and remained the following events that are useful in my class
- ChannelCreated
- ChannelConnectedLine
- ChannelDestroyed
- ChannelStateChange
- ChannelHold
- ChannelUnhold
- ChannelTalkingStarted
- ChannelTalkingFinished
- ChannelEnteredBridge
- ChannelLeftBridge
After the filtering applied, the following events in blue are for extension 1000, while the events in red are for extension 1002.
{ “type”: “ChannelCreated”, “timestamp”: “2015-11-02T22:28:38.776+0800”, “channel”: { “id”: “1446474518.30”, “name”: “PJSIP/1000-00000006”, “state”: “Ring”, “caller”: { “name”: “device”, “number”: “1000” }, “connected”: { “name”: “”, “number”: “” }, “accountcode”: “”, “dialplan”: { “context”: “from-internal”, “exten”: “1002”, “priority”: 1 }, “creationtime”: “2015-11-02T22:28:38.774+0800”, “language”: “en” }, “application”: “goanswer” }
{ “type”: “ChannelConnectedLine”, “timestamp”: “2015-11-02T22:28:38.862+0800”, “channel”: { “id”: “1446474518.30”, “name”: “PJSIP/1000-00000006”, “state”: “Ring”, “caller”: { “name”: “Kai”, “number”: “1000” }, “connected”: { “name”: “Ping”, “number”: “” }, “accountcode”: “”, “dialplan”: { “context”: “macro-dial-one”, “exten”: “s”, “priority”: 40 }, “creationtime”: “2015-11-02T22:28:38.774+0800”, “language”: “en” }, “application”: “goanswer” }
{ “type”: “ChannelCreated”, “timestamp”: “2015-11-02T22:28:38.864+0800”, “channel”: { “id”: “1446474518.32”, “name”: “PJSIP/1002-00000007”, “state”: “Down”, “caller”: { “name”: “device”, “number”: “1002” }, “connected”: { “name”: “”, “number”: “” }, “accountcode”: “”, “dialplan”: { “context”: “from-internal”, “exten”: “s”, “priority”: 1 }, “creationtime”: “2015-11-02T22:28:38.864+0800”, “language”: “en” }, “application”: “goanswer” }
{ “type”: “ChannelConnectedLine”, “timestamp”: “2015-11-02T22:28:38.862+0800”, “channel”: { “id”: “1446474518.30”, “name”: “PJSIP/1000-00000006”, “state”: “Ring”, “caller”: { “name”: “Kai”, “number”: “1000” }, “connected”: { “name”: “Ping”, “number”: “1002” }, “accountcode”: “”, “dialplan”: { “context”: “macro-dial-one”, “exten”: “s”, “priority”: 41 }, “creationtime”: “2015-11-02T22:28:38.774+0800”, “language”: “en” }, “application”: “goanswer” }
{ “type”: “ChannelConnectedLine”, “timestamp”: “2015-11-02T22:28:38.865+0800”, “channel”: { “id”: “1446474518.32”, “name”: “PJSIP/1002-00000007”, “state”: “Down”, “caller”: { “name”: “device”, “number”: “1002” }, “connected”: { “name”: “Kai”, “number”: “1000” }, “accountcode”: “”, “dialplan”: { “context”: “from-internal”, “exten”: “1002”, “priority”: 1 }, “creationtime”: “2015-11-02T22:28:38.864+0800”, “language”: “en” }, “application”: “goanswer” }
{ “type”: “ChannelStateChange”, “timestamp”: “2015-11-02T22:28:39.027+0800”, “channel”: { “id”: “1446474518.32”, “name”: “PJSIP/1002-00000007”, “state”: “Ringing”, “caller”: { “name”: “device”, “number”: “1002” }, “connected”: { “name”: “Kai”, “number”: “1000” }, “accountcode”: “”, “dialplan”: { “context”: “from-internal”, “exten”: “1002”, “priority”: 1 }, “creationtime”: “2015-11-02T22:28:38.864+0800”, “language”: “en” }, “application”: “goanswer” }
{ “type”: “ChannelStateChange”, “timestamp”: “2015-11-02T22:28:45.637+0800”, “channel”: { “id”: “1446474518.32”, “name”: “PJSIP/1002-00000007”, “state”: “Up”, “caller”: { “name”: “device”, “number”: “1002” }, “connected”: { “name”: “Kai”, “number”: “1000” }, “accountcode”: “”, “dialplan”: { “context”: “from-internal”, “exten”: “1002”, “priority”: 1 }, “creationtime”: “2015-11-02T22:28:38.864+0800”, “language”: “en” }, “application”: “goanswer” }
{ “type”: “ChannelStateChange”, “timestamp”: “2015-11-02T22:28:45.637+0800”, “channel”: { “id”: “1446474518.30”, “name”: “PJSIP/1000-00000006”, “state”: “Up”, “caller”: { “name”: “Kai”, “number”: “1000” }, “connected”: { “name”: “Ping”, “number”: “1002” }, “accountcode”: “”, “dialplan”: { “context”: “macro-dial-one”, “exten”: “s”, “priority”: 44 }, “creationtime”: “2015-11-02T22:28:38.774+0800”, “language”: “en” }, “application”: “goanswer” }
{ “type”: “ChannelDestroyed”, “timestamp”: “2015-11-02T22:28:50.080+0800”, “cause”: 16, “cause_txt”: “Normal Clearing”, “channel”: { “id”: “1446474518.30”, “name”: “PJSIP/1000-00000006”, “state”: “Up”, “caller”: { “name”: “Kai”, “number”: “1000” }, “connected”: { “name”: “Ping”, “number”: “1002” }, “accountcode”: “”, “dialplan”: { “context”: “from-internal”, “exten”: “h”, “priority”: 1 }, “creationtime”: “2015-11-02T22:28:38.774+0800”, “language”: “en” }, “application”: “goanswer” }
{ “type”: “ChannelDestroyed”, “timestamp”: “2015-11-02T22:28:50.082+0800”, “cause”: 16, “cause_txt”: “Normal Clearing”, “channel”: { “id”: “1446474518.32”, “name”: “PJSIP/1002-00000007”, “state”: “Up”, “caller”: { “name”: “device”, “number”: “1002” }, “connected”: { “name”: “Kai”, “number”: “1000” }, “accountcode”: “”, “dialplan”: { “context”: “from-internal”, “exten”: “”, “priority”: 1 }, “creationtime”: “2015-11-02T22:28:38.864+0800”, “language”: “en” }, “application”: “goanswer” }
For testing #2, I used three extensions, they are 1000, 1002 and 1006. I initiated a call from 1000 to 1002, answered the call by 1002, then initiated another from 1000 to 1006, answered the call by 1006, the last step transferred the call by 1000, leaving 1002 and 1006 in a call, then ended the call by 1002 finally. The text file is here.
For testing #3, I used three extensions, they are 1000, 1002 and 1006. I initiated a call from 1000 to 1002, answered the call by 1002, then initiated another from 1000 to 1006, answered the call by 1006, the last step conference the call by 1000, and finally hangup the call by 1002. The text file is here.
Next step I will add call control functions such as Originate, Hold, Retrieve, Transfer and Conference, etc. After the implementation of the CTI class, I will port my tools such as ScreenPop, ivrSVR to support Asterisk. Good luck to me.