ARI v0.1.2 ARI.Stasis behaviour View Source

The ARI.Stasis module is used to register a Stasis application with the Asterisk server. It connects to the Asterisk server using a websocket.

The host, username and password are configured in the Asterisk configuration file ari.conf. The name that is registered is provided in the ARI.Stasis.app_config/0 as name.

Once registered it receives all events for all channels associated with a Stasis application. Here's an example application, you can see the Stasis app configured for the built-in ARI.Router application as well as a custom application, in the list of children.

Example

defmodule ExARIExample.Application do
    use Application
    alias ARI.{ChannelRegistrar, Configurator, HTTP, Stasis}

    def start(_, _) do
      un = System.get_env("ASTERISK_USERNAME")
      pw = System.get_env("ASTERISK_PASSWORD")
      ws_host = System.get_env("ASTERISK_WS_HOST")
      rest_host = System.get_env("ASTERISK_REST_HOST")
      rest_port = System.get_env("ASTERISK_REST_PORT") |> String.to_integer()
      name = System.get_env("ASTERISK_NAME")
      transport = System.get_env("ASTERISK_TRANSPORT")
      context = System.get_env("ASTERISK_CONTEXT")
      channel_supervisor = ExARIExample.ChannelSupervisor
      config_module = ExARIExample.Config
      client_config = %{name: "ex_ari", module: ExARIExample.Client}
      router_config = %{
        name: "router", 
        module: ARI.Router, 
        extensions: %{
          "ex_ari" => "ex_ari",
          "+15555550101" => "ex_ari"
        }
      }

      children = [
        ChannelRegistrar,
        {DynamicSupervisor, strategy: :one_for_one, name: channel_supervisor},
        {HTTP.Asterisk, [rest_host, rest_port, un, pw]},
        {HTTP.Channels, [rest_host, rest_port, un, pw]},
        {HTTP.Playbacks, [rest_host, rest_port, un, pw]},
        {Stasis, [channel_supervisor, client_config, ws_host, un, pw]},
        {Stasis, [channel_supervisor, router_config, ws_host, un, pw]},
        {Configurator, [name, transport, context, config_module]},
      ]

      opts = [strategy: :one_for_one, name: ExARIExample.Supervisor]
      Supervisor.start_link(children, opts)
    end
  end

Using this example, all calls to +15555550101 will have a ExARIExample.Client process spawned and subsequent call events will be passed to the indivdual process. More explicitly, each call has a unique process spawned that is supervised by the DynamicSupervisor passed to ARI.Stasis.

For the full example check out the ex_ari_example application, which will get you up and running with making calls on your local machine.

There are two very important events that ARI.Stasis receives that drive an ex_ari application StasisStart and StasisEnd.

StasisStart

When a StasisStart event is received it will spawn a new process under the supervisor provided as the channel_supervisor, this needs to be a DynamicSupervisor, to the ARI.Stasis.start_link/5 function. The process that is spawned is provided in the ARI.Stasis.app_config/0 as the module.

Before actually spawning the app process the WebSocket process will call the function state/5 on the app module with the id of the incoming channel, the caller id (a string of the number that is calling), a list of any arguments passed to the application (an empty list for standard stasis applications), the initial StasisStart event and the ARI.Stasis.app_config/0 that was passed to the ARI.Stasis module on startup. The ARI.Router application provided with ex_ari makes special use of the ARI.Stasis.app_config/0 by pulling extensions from the config to route incoming calls to their respective applications. The state/5 behaviour function is expected to return ARI.Stasis.channel_state/0 a struct or map containing at a minimum :channel, :caller and :start_event keys populated with the data passed to the state/5 function. The rest of the keys can be whatever is needed by your application. This data is then passed to the GenServer.start_link/2 function of your app module in the form [state] and should be used as your processes state for the lifetime of the process.

To map channel ids to pids there is an Agent process that maintains the mappings, ARI.ChannelRegistrar. This is provided by the ex_ari library, but needs to be explicitly started by your application.

StasisEnd

Upon receiving a StasisEnd event the ARI.Stasis module will begin tearing down the process associated with the channel id. It kills the process by calling DynamicSupervisor.terminate_child/3 and deregistering the pid from the ARI.ChannelRegistrar Agent process.

Other Events

The ARI.Stasis process does its best to extract out the channel id from several different types of events in order to route the event to the correct process. All events are sent using send/2 in the form of {:ari, event} where event is the JSON event deserialized with atom keys. Events that are not routable are handled with a Logger.warn/1 message.

Link to this section Summary

Callbacks

Called when an incoming call is received. Arguments are Channel ID, Caller ID, Args, StasisStart Event and App Config

Link to this section Types

Link to this type

app_config()

View Source
app_config() :: %{
  optional(:extensions) => map(),
  :name => String.t(),
  :module => module()
}
Link to this type

channel_state()

View Source
channel_state() :: %{
  optional(atom()) => any(),
  :channel => String.t(),
  :caller => String.t(),
  :start_event => map()
}

Link to this section Functions

Link to this function

start_link(channel_supervisor, app_config, host, un, pw)

View Source
start_link(module(), app_config(), String.t(), String.t(), String.t()) ::
  {:ok, pid()} | {:error, term()}

Link to this section Callbacks

Link to this callback

state(arg1, arg2, list, map, map)

View Source
state(String.t(), String.t(), list(), map(), map()) :: channel_state()

Called when an incoming call is received. Arguments are Channel ID, Caller ID, Args, StasisStart Event and App Config