Imitation of the radio using Freeswitch and a little about voip-conference
It even happens that you need to get a monster like Freeswitch to work on the principle of an ordinary walkie-talkie.
One says everyone is listening.
And NodeJs and npm will help us with this modesl module for interacting with Freeswitch.

At some point in our organization, in a large wireless project, the customer needed to emulate the behavior of the walkie-talkie on top of voip-telephony. The system was based on Freeswitch. In general, the communication system is organized on the basis of the mesh network and accordingly there is no centralized server, each node has its own instance of Freeswitch, which is responsible for various voice scenarios.
As you know, everything is very simple in the radio: one speaks and everyone listens, which is what needs to be implemented. 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, there should be targeted calls on the network.
Available:
The logic is, of course, strange and confusing, but since the customer asks, we must do it. An example structure looks like this:
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 walkie-talkie emulation and switching between global conferences.
Global Conference is a general conference, there can be several of them, which will allow to unite different users into different groups.
You can connect modesl as follows:
Connection features in modesl:
A conference can be created simply by writing everything in the xml config, or you can do this by transferring all control from the xml config to a specific address and port. We will choose the 2nd option.
In Freeswitch configs in the dialplan / public.xml file, you need to write something like:
This means that if they call 5555, then redirect control to the address 127.0.0.1:8087.
You should also write a script to create a conference:
Initially, when the node starts, a call to the local conference occurs. To enter any of the global conferences, the node makes a call from the Local Conference to the Global Conference. In Freeswitch, this can be done like this :
fs_cli
nodejs
This is a very interesting and convenient feature that allows you to combine different conferences into one.
Next, we do a deaf for this Global Conference inside the Local Conference (this will allow us to make sure that the various global conferences into which the node is included do not hear each other).
You can do it like this :
fs_cli
nodejs
Where did memberId come from? You can get it by signing up for the conference_add_member event .
On each node, before the start of the conversation, the Local Conference has the following form:
The host 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 himself mute . This is done simply by going over the list of participants with this function.
Then you need to make undeaf for that global conference where the node will talk
As a result, we schematically obtain the following:
Since all global conferences are mute, an active global conference (one made by undeaf) will not be heard by others. When the conversation ends, we will return everything to its previous state.
Now that all the basic functionality is ready, just write a high-level logic of work. But this is a topic for a separate article :).
The scheme turned out to be quite large and confusing.
It is possible to solve this problem without using local conferences for each node, but you will have to make targeted 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. Such a 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 schemes for working with voice.
One says everyone is listening.
And NodeJs and npm will help us with this modesl module for interacting with Freeswitch.

At some point in our organization, in a large wireless project, the customer needed to emulate the behavior of the walkie-talkie on top of voip-telephony. The system was based on Freeswitch. In general, the communication system is organized on the basis of the mesh network and accordingly there is no centralized server, each node has its own instance of Freeswitch, which is responsible for various voice scenarios.
Task
As you know, everything is very simple in the radio: one speaks and everyone listens, which is what needs to be implemented. 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, there should be targeted calls on the network.
Available:
- Freeswitch is a user agent that with the right selection of modules can even brew coffee.
- modesl - npm module for interacting with Freeswitch using the Event Socket Library .
- mod_conference - FS module for creating and working with voice conferences.
- mod_sofia - FS module for working with SIP.
General scheme
The logic is, of course, strange and confusing, but since the customer asks, we must do it. An example structure looks like this:
The general structure of voice communication.

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 walkie-talkie emulation and switching between global conferences.
Global Conference is a general conference, there can 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 features in modesl:
- Subscribe to events from Freeswitch. Here's an example of a subscription to all events:
connection.on( "esl::event::**", function( event) {}); - Synchronous Freeswitch Command Call
Connection.prototype.api = function(command, args, cb) - Asynchronous Freeswitch Command Call
Connection.prototype.bgapi = function(command, args, jobid, cb)
How to work with conferences in Freeswitch
A conference can be created simply by writing everything in the xml config, or you can do this by transferring all control from the xml config to a specific address and port. We will choose the 2nd option.
In Freeswitch configs in the dialplan / public.xml file, you need to write something like:
This means that if they call 5555, then redirect control to the address 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
Conference entry
Initially, when the node starts, a call to the local conference occurs. To enter any of the global conferences, the node makes a call from the Local Conference to the Global Conference. In Freeswitch, this can be done like 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 feature that allows you to combine different conferences into one.
Next, we do a deaf for this Global Conference inside the Local Conference (this will allow us to make sure that the various global conferences into which the node is included 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 did memberId come from? You can get it by signing up for the conference_add_member event .
Conversation
On each node, before the start of the conversation, the Local Conference has the following form:
The status of the local conference.

The host 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 himself mute . This is done simply by going over 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 make 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 schematically obtain the following:
Scheme at the time of conversation.

Since all global conferences are mute, an active global conference (one made by undeaf) will not be heard by others. When the conversation ends, we will return everything to its previous 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, just write a high-level logic of work. But this is a topic for a separate article :).
conclusions
The scheme turned out to be quite large and confusing.
It is possible to solve this problem without using local conferences for each node, but you will have to make targeted 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. Such a 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 schemes for working with voice.