FreeSwitch is a full featured PBX capable of routing both Voice and SMS traffic. We use it to provide multi-modal applications that are capable of mixing voice, SMS, location (RRLP), and presence information. As of now, just voice and SMS are implemented.

System Diagram

In this diagram, Black links are network links (SIP) while Red links are filesystem lookups (sqlite3 db access).

Diagram of OpenBTS system using FreeSWITCH as the PBX


FreeSwitch, being a full-featured project outside the scope of OpenBTS, has it's own wiki detailing the installation and use of the system. As such, we  defer to their wisdom. You'll need two FreeSWITCH modules to interoperate with OpenBTS. Those are  mod_python and  mod_sms. Make sure to install (and configure!) those two modules. Python 2.6 or above (2.7.3 recommended) is required to use the supplied OpenBTS scripts.



OpenBTS needs to route all outgoing voice and SMS messages to FreeSWITCH. FreeSWITCH provides multiple SIP endpoints, any of them will do. By default, the "internal" context is port 5060, while the "external" context is 5080. Due to a bug in FreeSWITCH, you have to specify the entire IP address. Localhost/ WILL NOT WORK. To update the required fields, change the two following config fields in /etc/OpenBTS/OpenBTS.db to your local address ( in this example).

  (from OpenBTSCLI)
  config SIP.Proxy.Speech
  config SIP.Proxy.SMS


OpenBTS has a very minimal SIP stack, so make sure that FreeSWITCH does not require any registration to receive SMS or Voice traffic. If you do not, FreeSWITCH will send 401 (Unauthorized) or 407 (Proxy Authentication Required) responses to your REGISTER/INVITE/MESSAGE messages.

Doing this consists of two parts. First, you have to add the OpenBTS IP address to your access control list (in autoload_configs/acl.conf.xml). This allows any traffic from these IP addresses to be accepted without registration. In this example, we added them to the "domains" ACL that already exists:

  <list name="domains" default="deny">
      <!--place your OpenBTS IP here -->
      <node type="allow" cidr=""/>
      <!-- old stuff below... -->

It's worth noting that you CANNOT use the "special" function of the domains ACL that scans all users with CIDR attributed and automatically adds them to the ACL. This doesn't work, as it assumes one IP address per user, a critical flaw when working with OpenBTS.

Following that change, you also need to modify your sip profile to accept connections without authorization. If you are using the internal profile, this is in conf/sip_profiles/internal.xml. Uncomment the following line as follows:

  <param name="apply-register-acl" value="domains"/>

You'll need to set some variables to ensure that FreeSWITCH can communicate with the other services running in the OpenBTS suite. The file conf/vars.xml needs three items defined: The subscriber registry location, smqueue's host and port, and the FreeSWITCH profile you use to communicate with smqueue (this is almost always internal). Add these to the end of vars.xml

  <X-PRE-PROCESS cmd="set" data="openbts_db_loc=/var/lib/asterisk/sqlite3dir/sqlite3.db"/>
  <X-PRE-PROCESS cmd="set" data="smqueue_port=5063"/>
  <X-PRE-PROCESS cmd="set" data="smqueue_host="/>
  <X-PRE-PROCESS cmd="set" data="smqueue_profile=internal"/>

You'll also need to enable python in conf/autoload_configs/modules.conf.xml:

  <load module="mod_python"/>

There are a few utility scripts we've written that should be made accessible to FreeSWITCH. There are located  here. To install them, run the following commands from your source directory:

  git clone git://
  cd libvbts
  sudo python install

Lastly, you need to install  smspdu.

  # download smspdu-1.0.tar.gz 
  tar xf smspdu-1.0.tar.gz
  sudo python install

Restart FreeSWITCH and your system should be receiving and handing OpenBTS traffic.

Using FreeSWITCH with OpenBTS

VBTS_DB_Get allows freeswitch plans to engage with the subscriber registry. The basic commands are used as


API: <action application="python" data='VBTS_DB_Get item|field|qualifier[|table]"'/>

Example: <action application="python" data='VBTS_DB_Get callerid|name|IMSI000000000000000'/>
<!-- _openbts_ret set to 11111111 >
<action application="log" data="${_openbts_ret}


API: python VBTS_DB_Get item|field|qualifier[|table]

EXAMPLE: python VBTS_DB_Get callerid|name|IMSI000000000000000

The set analogue of Get


API: <action application="python" data='VBTS_DB_Set item|value|field|qualifier[|table]"'/>

Example: <action application="python" data='VBTS_DB_Set callerid|1000|name|IMSI000000000000000'/>


API: python VBTS_DB_Get item|value|field|qualifier[|table]

EXAMPLE: python VBTS_DB_Set callerid|1000|name|IMSI000000000000000

OpenBTS_Send_SMS sends an encoded SMS message to smqueue (not OpenBTS!) for storage and eventual forwarding to the client. You are able to specify the target for the SMS (phone number), return number (arbitrary number) and the actual content of the message. Messages over 160 characters are truncated.


API: <action application="python" data="VBTS_Send_SMS TARGET_NUM|RETURN_NUM|TEXT"/>

Example: <action application="python" data="VBTS_Send_SMS 1111111|1000|Hello from OpenBTS!"/>



Example: python VBTS_Send_SMS 1111111|1000|Hello from OpenBTS!

OpenBTS_Send_SMS sends an encoded SMS message directly to OpenBTS without backup or storage. You are able to specify the target for the SMS (IMSI), return number (arbitrary number) and the actual content of the message. Messages over 160 characters are truncated.


API: <action application="python" data="VBTS_Send_Direct_SMS TARGET_NAME|IP_ADDRESS|PORT|RETURN_NUM|TEXT"/>

Example: <action application="python" data="VBTS_Send_Direct_SMS IMSI000000000000||5062|1000|Hello from OpenBTS!"/>



Example: python VBTS_Send_Direct_SMS IMSI000000000000||5062|1000|Hello from OpenBTS!

OpenBTS_Send_SMS sends an empty SMS to SMQ for eventual delivery. This message has no contents, and is not seen by the user. This is used to cause a handset to update itself with the BTS, potentially providing information like location. You are able to specify the target for the SMS (phone number).


API: <action application="python" data="VBTS_Send_Empty_SMS TARGET_NUM"/>

Example: <action application="python" data="VBTS_Send_Empty_SMS 1111111"/>


API: python VBTS_Send_Empty_SMS TARGET_NUM

Example: python VBTS_Send_Empty_SMS 1111111

OpenBTS_Send_SMS sends an empty SMS to OpenBTS for immediate delivery. This is not backed up or stored. This message has no contents, and is not seen by the user. This is used to cause a handset to update itself with the BTS, potentially providing information like location. Eventually we will also add support for detecting if the message is delivered, giving us a mechanism for determining availability. You are able to specify the target for the SMS (IMSI).


API: <action application="python" data="VBTS_Send_Empty_Direct_SMS TARGET_NAME|IP_ADDRESS|PORT"/>

Example: <action application="python" data="VBTS_Send_Empty_Direct_SMS IMSI10000000||5062"/>



Example: python VBTS_Send_Empty_Direct_SMS IMSI10000000||5062

VBTS_New_User is a replacement for smqueue's "register" functionality. It inserts a new user into the subscriber registry. Note that if this is failing, you probably don't have write access to the DB's parent directory (a requirement for writing to sqlite3!)


API: <action application="python" data="VBTS_New_User USER|TARGET_NUM|IP_ADDRESS|PORT"/>
Example: <action application="python" data="IMSI000000000000000|1111111||5062"/>


Example: python VBTS_New_User IMSI000000000000000|1111111||5062

VBTS_Parse_SMS munges an incoming SMS (from OpenBTS) into a format that FreeSWITCH can understand. This means it can only be run from a chatplan.


API/Example: <action application="python" data="VBTS_Parse_SMS"/>
DOES NOT WORK IN DIALPLAN YET. Reads chatplan variables instead of inputting them. 

This sets a number of chatplan variables, with most in Hex.

"vbts_rp_message_type" : RP Message Type
"vbts_rp_message_reference" : RP Message Reference
"vbts_rp_originator_address" : RP Originator Address
"vbts_rp_dest_address_type" : RP Destination Address Type
"vbts_rp_dest_address" : RP Destination Address (SMSC)
"vbts_tp_message_type" : TP Message Type
"vbts_tp_message_reference" : TP Message Reference
"vbts_tp_dest_address_type" : TP Destination Address Type
"vbts_tp_dest_address" : TP Destination Address (SMS target)
"vbts_tp_protocol_id" : TP Protocol ID
"vbts_tp_data_coding_scheme" : TP Data Coding Scheme
"vbts_tp_validity_period" : TP Validity Period
"vbts_tp_user_data" : TP User Data
"vbts_text" : Message Content (Text)



  • Routing
    <extension name="local_call">
      <!-- openbts_db_loc set in vars.xml -->
      <condition field='${python(VBTS_DB_Get name|callerid|${destination_number})}' expression="IMSI\d{15}"/>
      <condition field='${python(VBTS_DB_Get callerid|name|${username})}' expression="\d{7,10}">
        <action application="set" data='target=${python(VBTS_DB_Get name|callerid|${destination_number})}' />
        <action application="set" data='port=${python(VBTS_DB_Get port|callerid|${destination_number})}' />
        <action application="set" data='host=${python(VBTS_DB_Get ipaddr|callerid|${destination_number})}' />
        <action application="set" data='effective_caller_id_number=${python(VBTS_DB_Get callerid|name|${username})}'/>
        <action application="bridge" data="sofia/internal/${target}@${host}:${port}"/>


  • Parsing: This should be done at the header of your chatplan, to ensure these variables can be used inside of it
        <!-- set all the openbts variables -->
        <extension name="openbts" continue="true">
          <condition field="to_user" expression="^smsc$">
    	<!-- first, parse SMS -->
    	<action inline="true" application="python" data="VBTS_Parse_SMS"/>
    	<!-- second, look up sender -->
    	<!-- freeswitch eats 's, switch them up here -->
    	<action inline="true" application="python" data='VBTS_DB_Get callerid|name|${from_user}'/>
    	<!-- result in _openbts_ret -->
    	<action inline="true" application="set" data="openbts_callerid=${_openbts_ret}"/>
  • Echo
        <extension name="echo">
          <condition field="vbts_tp_dest_address" expression="^9189$">
    	<action application="python" data="OpenBTS_Send_SMS ${openbts_callerid}|9189|${vbts_text}"/>
  • Callback: call a user back from an SMS'd number
        <extension name="callbacks">
          <condition field="vbts_tp_dest_address" expression="^919(\d)$">
    	<!-- bgapi lets us finish this without waiting for the originate -->
    	<!-- the space between the args and the dest is important, for some reason -->
    	<action application="set" data="api_result=${bgapi(originate {origination_caller_id_number=${vbts_tp_dest_address}}sofia/internal/${from}:${from_sip_port} ${vbts_tp_dest_address})}"/>
  • Forward: put at the bottom of the dialplan, catch all traffic that didn't hit and forward it to smqueue
       <!-- send any other messages onto smqueue -->
        <!-- reencode for now... though I'll probably write a "forward" script -->
        <extension name="forward">
          <condition field="vbts_tp_dest_address" expression="^(.*)$">
    	<action application="python" data="VBTS_Send_SMS ${vbts_tp_dest_address}|${from_user}|${vbts_text}"/>
  • Registration
        <!-- register a user in the subscriber registry -->
        <extension name="registration">
          <condition field="vbts_tp_dest_address" expression="^101$"/>
          <!-- is it a number? -->
          <condition field="vbts_text" expression="^\d{7,10}$">
    	<action application="python" data="VBTS_New_User ${from_user}|${vbts_text}|$${smqueue_host}|$${smqueue_port}"/>
    	<action application="set" data="response_text=${_openbts_ret}"/>
    	<!-- lookup new number -->
    	<action application="python" data='VBTS_DB_Get callerid|name|${from_user}'/>
    	<!-- text back the return value -->
    	<action application="python" data="VBTS_Send_SMS ${_openbts_ret}|101|${response_text}"/>