📜 ⬆️ ⬇️

Imitation of the radio using Freeswitch and a little about voip-conference

It even happens that you need to make such a monster like the Freeswitch work on the principle of a normal walkie-talkie.
One says everyone is listening.

And the NodesJs and npm modesl module will help us with this to interact with Freeswitch.


')

At some point in our organization in a large wireless project, the customer needed to emulate the behavior of the radio over voip telephony. The basis of the system was taken Freeswitch. In general, the communication system is organized on the basis of a mesh network and, accordingly, there is no centralized server; each node has its own Freeswitch instance, which is responsible for different voice scenarios.

Task


In the radio, as is well known, everything is very simple: one says and everyone listens, which is what is required to be realized. It is also necessary to make a conference call for several independent groups, and any subscriber can simultaneously be in several of them. And of course the network should be address calls.
Available:



General scheme


The logic is of course strange and confusing, but once the customer asks, it must be done. The approximate structure looks like this:
The overall structure of voice communications.

Sip client - it can be both mod_portaudio and linphone or for example baresip.

Local Conference is an internal conference for each node, the task of which is to support the cunning logic of working with the emulation of the radio and switching between global conferences.

Global Conference is a general conference, there may be several of them, which will allow to unite different users into different groups.

Training


How to connect modesl


You can connect modesl as follows:
var esl = require( "modesl"); var localServer = "localhost"; var localServerPort = 8021; var localServerUser = "ClueCon"; var connectionCallback = function() { // ,   connection.on( "esl::end", function( event) { //  ,    }); } //  var connection = new esl.Connection( localServer, localServerPort, localServerUser, connectionCallback); connection.once( "error", function() { //   }); 


Connection capabilities in modesl:


How to work with conferences in Freeswitch


You can create a conference simply by registering everything in xml configs, or you can do this by transferring all control from a xml configs to a specific address and port. We will choose the 2nd option.
In the Freeswitch configurations in the dialplan / public.xml file you need to write something like:
  <extension name="conference_server"> <condition field="destination_number" expression="^(5555)$"> <action application="socket" data="127.0.0.1:8087 async full"/> </condition> </extension> 

This means that if they call 5555, then redirect control to 127.0.0.1:8087.
You should also write a script to create a conference:
 var esl = require('modesl'); var esl_server = new esl.Server({port: 8087, myevents:true}, function(){ console.log("ConferenceServer server is up"); }); esl_server.on( 'connection::ready', function( conn, id) { console.log( 'ConferenceServer new call', id); conn.execute( 'conference', 'ConfName@default', function( err, result){ console.log( arguments); }); conn.on('esl::end', function( evt, body) { console.log( "ConferenceServer call ended ", id); }); }); 


Implementation


Entry to the conference


Initially, when the node starts, the call to the local conference occurs. To enter one of the global conferences, the node makes a call from the Local Conference to the Global Conference. In Freeswitch, you can do this :
fs_cli
 conference [conference name] dial sofia/internal/[sip address] 

nodejs
  self.dial = function( sipAddress, callback){ self.connection.bgapi( "conference", conferenceName + " dial sofia/internal/" + sipAddress, function( result){ var resultId = result.getBody().indexOf( "SUCCESS") if( resultId == -1){ var body = result.getBody(); var startIndex = body.indexOf( '['); var result = body.substring( startIndex + 1, body.length - 2); callbackHelper.call( callback, "Conference call error: " + result); } else callbackHelper.call( callback, null); }); }; 

This is a very interesting and convenient function that allows you to combine different conferences into one.
Then we do a deaf for this Global Conference inside the Local Conference (this allows us to ensure that the different global conferences that the node is in do not hear each other).
You can do it like this :
fs_cli
 conference [conference name] deaf [memberId] 

nodejs
  self.deaf = function( conferenceName, memberId, callback){ self.connection.bgapi( "conference", conferenceName + " deaf " + memberId, function( result){ callbackHelper.call( callback, null); }); }; 

Where memberId comes from, you can get it by subscribing to the conference_add_member event

Conversation


At each node before the start of a conversation, the Local Conference has the following form:
The state of the local conference.

The node hears everyone, and global conferences do not hear each other.
When a node needs to say something in one of the global conferences, you must first make everyone a participant except yourself a mute . This is done simply by running through the list of participants with this function.
  self.mute = function( conferenceName, memberId, callback){ self.connection.bgapi( "conference", conferenceName + " mute " + memberId, function( result){ callbackHelper.call( callback, null); }); }; 
.
Then you need to do undeaf for that global conference where the node will talk
  self.undeaf = function( conferenceName, memberId, callback){ self.connection.bgapi( "conference", conferenceName + " undeaf " + memberId, function( result){ callbackHelper.call( callback, null); }); }; 
.
As a result, we obtain the following schematically:
The scheme at the time of conversation.


Since all global conferences have a mute made, the active global conference (the one that is made undeaf) will not be heard by others. When the conversation ends we will return everything to its former state.

This is how you can summarize the code for working with conference participants.
 function FsConferenceAPI( connection){ var self = this; self.connection = connection; self.unmuteAll = function( conferenceName){ self.connection.bgapi( "conference", conferenceName + " unmute all", function( result){}); }; self.dial = function( sipAddress, callback){ self.connection.bgapi( "conference", conferenceName + " dial sofia/internal/" + sipAddress, function( result){ var resultId = result.getBody().indexOf( "SUCCESS") if( resultId == -1){ var body = result.getBody(); var startIndex = body.indexOf( '['); var result = body.substring( startIndex + 1, body.length - 2); callbackHelper.call( callback, "Conference call error: " + result); } else callbackHelper.call( callback, null); }); }; self.kick = function( conferenceName, memberId, callback){ self.connection.bgapi( "conference", conferenceName + " kick " + memberId, function( result){ var body = result.getBody(); if( body.indexOf( "OK kicked " + memberId) != -1) callbackHelper.call( callback, null); else callbackHelper.call( callback, body); }); }; self.kickAll = function( conferenceName, callback){ self.connection.bgapi( "conference", conferenceName + " kick all", function( result){ var body = result.getBody(); if( body.indexOf( "OK kicked") != -1) callbackHelper.call( callback, null); else callbackHelper.call( callback, body); }); }; self.deaf = function( conferenceName, memberId, callback){ self.connection.bgapi( "conference", conferenceName + " deaf " + memberId, function( result){ callbackHelper.call( callback, null); }); }; self.undeaf = function( conferenceName, memberId, callback){ self.connection.bgapi( "conference", conferenceName + " undeaf " + memberId, function( result){ callbackHelper.call( callback, null); }); }; self.mute = function( conferenceName, memberId, callback){ self.connection.bgapi( "conference", conferenceName + " mute " + memberId, function( result){ callbackHelper.call( callback, null); }); }; self.unmute = function( conferenceName, memberId, callback){ self.connection.bgapi( "conference", conferenceName + " unmute " + memberId, function( result){ callbackHelper.call( callback, null); }); }; } 


Now that all the basic functionality is ready, it is enough to write a high-level logic of operation. But this is a topic for a separate article :).

findings



The scheme turned out quite large and confusing.
You can solve this problem without using local conferences for each node, but you will have to make address calls to all global conferences in which we want to participate. In this case, we will encounter another problem: the sip client should not put calls on hold and it will be necessary to use switching between active calls. This scheme will also have its pros and cons.
An important conclusion is that Freeswitch is a unique tool that allows you to implement a wide variety of voice work schemes.

Source: https://habr.com/ru/post/268773/


All Articles