Interaction avec le serveur Asterisk à partir d'une application Java

Interaction avec un serveur Asterisk à partir d'une application java via l'Asterisk Managment Interface (AMI)


Si vous ne faites que commencer des recherches dans ce domaine, alors l'interaction avec ce serveur peut vous sembler quelque peu déroutante, comme cela m'a paru une fois.

Afin de ne pas chercher les informations nécessaires sur les forums dans le style de réponse-question, je joins un petit tutoriel sur l'interaction avec le serveur Asterisk depuis java.

Important: je suppose qu'une fois que vous avez atteint le stade de l'écriture de code, vous disposez déjà d'un serveur Asterisk opérationnel auquel vous pouvez accéder.

1) Que choisir pour faciliter le travail?

Certainement - Asterisk Managment Interface (AMI): cette interface dispose d'une gamme complète de fonctions qui vous permettent de passer un appel, d'écouter les événements en temps réel du serveur, de recevoir un état d'appel et de l'interrompre si nécessaire.

2) Quelle bibliothèque connecter?

Celui-ci:

<dependency> <groupId>org.asteriskjava</groupId> <artifactId>asterisk-java</artifactId> <version>2.0.4</version> </dependency> 

3) Quelles configurations doivent être affichées sur le serveur?

extensions.conf - la configuration qui décrit le diaplan. Vous serez constamment en contact avec lui. Si dans une langue plus compréhensible, il contient des scripts de ce que le serveur fera lorsqu'il recevra un appel vers un numéro spécifique. Tout d'abord, un contexte spécifique est recherché dans le diaplan - il est écrit entre crochets, après quoi un nombre est recherché sous la balise de ce contexte pour lequel vous contactez.

manager.conf - configuration avec utilisateur et mot de passe pour votre serveur Asterisk

Le contenu de cette configuration devrait être approximativement comme suit:

 [user_name] secret = password read = all write = all deny=0.0.0.0/0.0.0.0 permit=0.0.0.0/255.255.255.0 

  • nom_utilisateur - nom d'utilisateur
  • secret - mot de passe
  • refuser - adresses IP auxquelles l'accès est refusé sous cet utilisateur
  • permis - dont l'accès est autorisé. Assurez-vous de spécifier l'adresse IP avec laquelle vous contactez dans le permis, car l'aster peut repousser votre demande.

sip.conf - tous les troncs sont enregistrés ici. Un coffre est un téléphone à partir duquel nous appellerons un client.

4) Par où commencer à écrire du code?

Il existe deux options: vous devez soit effectuer une action sur le serveur Asterisk, soit écouter les événements sur le serveur. Notre séquence comprend les deux.

Nous décrivons le plan d'action:

  1. Ouvrez la connexion au serveur;
  2. Nous décrivons le scénario de travail;
  3. Nous écoutons les événements;
  4. Fermez la connexion.

En conséquence, la connexion est initialisée lors de la création de l'objet DefaultAsteriskServer:

 import org.asteriskjava.live.AsteriskServer; import org.asteriskjava.live.DefaultAsteriskServer; 

 AsteriskServer asteriskServer = new DefaultAsteriskServer(HOSTNAME, USERNAME, PASSWORD); asteriskServer.initialize(); 

Après avoir ouvert la connexion, nous devons appeler l'utilisateur. Nous appellerons cela un scénario d'action. La description du scénario sera dans une classe distincte:

 /** *    */ public class ScenarioCall extends OriginateAction { private final Logger log = LoggerFactory.getLogger(ScenarioCall.class); private String TRUNK; private final String PHONE_FOR_RINGING; private final String EXTEN_FOR_APP; private final String CONTEXT_FOR_APP; public ScenarioCall(String trunk, String phoneForRinging, String extension, String context) { this.TRUNK = trunk; this.PHONE_FOR_RINGING = phoneForRinging; this.EXTEN_FOR_APP = extension; this.CONTEXT_FOR_APP = context; this.init(); } /** *         OriginateAction */ private void init() { //  String callId = ValidValues.getValidCallId(this.PHONE_FOR_RINGING); //    String channelAsterisk = ValidValues.getValidChannel(this.TRUNK, this.PHONE_FOR_RINGING); this.setContext(CONTEXT_FOR_APP); this.setExten(EXTEN_FOR_APP); this.setPriority(1); this.setAsync(true); this.setCallerId(callId); this.setChannel(channelAsterisk); log.info("Create Scenario Call: phone '{}',chanel '{}',context '{}',extension '{}'", callId, channelAsterisk, CONTEXT_FOR_APP, EXTEN_FOR_APP); } } 

Que devons-nous comprendre dans ce scénario? Tout d'abord, une connexion de jonction est créée. Une jonction est le numéro à partir duquel vous appelez l'abonné. Après cela, une connexion est créée entre le tronc et l'abonné, après cela la connexion entre l'abonné et qui d'autre dont vous avez besoin là-bas.

C'est dans cet ordre.

 this.setContext(CONTEXT_FOR_APP) 
La valeur transmise: le contexte dans lequel nous rechercherons le numéro de téléphone auquel vous souhaitez associer l'abonné (à partir d'extensions.conf).

 this.setExten(EXTEN_FOR_APP) 
Valeur transmise: le script qui sera exécuté après avoir contacté l'abonné (à partir d'extensions.conf).

 this.setCallerId(callId) 
Valeur transmise: le numéro de notre abonné

 this.setChannel(channelAsterisk) 
Valeur transmise: canal de communication établi, ressemble généralement à ceci: trunk_name / phone_user.

Où chercher trunk_name? Il y a une configuration sip.conf sur le serveur d'astérisques - tous les troncs y sont enregistrés.

Créez un appel:

 if (asteriskServer .getManagerConnection().getState().equals(ManagerConnectionState.CONNECTED) || asteriskServer .getManagerConnection().getState().equals(ManagerConnectionState.CONNECTING) || asteriskServer .getManagerConnection().getState().equals(ManagerConnectionState.INITIAL)) { try { ScenarioCall scenarioCall = new ScenarioCall(trank, phone, extension, context); CallBack callBackForScenarioCall = new CallBack(); asteriskServer.originateAsync(scenarioCall, callBackForScenarioCall); } catch (ManagerCommunicationException e) { //   , StateConnection    RECONNECTING,     } } 

Nous avons créé un appel, mais comment le suivre dynamiquement?

Deux choses sont faites pour cela: une instance de la classe CallBack est passée dans la méthode originateAsync
et un écouteur est accroché sur le serveur, qui fusionnera tout ce qui nous arrive.

Un écouteur est nécessaire car la classe CallBack ne vous informera pas de la fin de l'appel lorsque l'utilisateur a déjà parlé, ni ne vous informera que l'utilisateur peut toujours transférer ailleurs.

 /** *        asteriskConnection.originateAsync  *  CallBack -     ,     *  originateAsync. CallBack      , *       ,    onNoAnswer ,  *     onBusy,     ,  onFailure,  . *        . ,      *      (      ,    ) */ public class CallBack implements OriginateCallback { /** *     PRERING,       * OriginateCallback -    */ private ChannelState resultCall = ChannelState.PRERING; /** *    ,   .         */ @Override public void onDialing(AsteriskChannel asteriskChannel) { //   ,  resultCall, //   asteriskChannel    null, //    resultCall   //   } /** *   .      *      6 - setStatus */ @Override public void onSuccess(AsteriskChannel asteriskChannel) { //   , asteriskChannel   null, // asteriskChannel.getState()      ChannelState.UP //   } /** *      ,    *      7 - setStatus () */ @Override public void onNoAnswer(AsteriskChannel asteriskChannel) { //   , //   asteriskChannel    null, //    resultCall   //   } /** *   *      7 - setStatus () */ @Override public void onBusy(AsteriskChannel asteriskChannel) { //   , //   asteriskChannel    null, //    resultCall   //   } /** *      */ @Override public void onFailure(LiveException e) { //     , //     , // onFailure      } } 

Comment accrocher un auditeur sur un astérisque?

Pour ce faire, créez une classe d'implémentation AsteriskServerListener, PropertyChangeListener.
Pour la connexion créée, via une instance de la classe AsteriskConnection, nous implémentons:

  this.asteriskConnection.addAsteriskServerListener(this.callBackEventListener); 

this.callBackEventListener - une instance de notre classe d'écoute, née de:

 ** *    Asterisk *  PropertyChangeListener   ,     . *  AsteriskServerListener   ,     AsteriskConnection. */ public class CallBackEventListener implements AsteriskServerListener, PropertyChangeListener { public void onNewAsteriskChannel(AsteriskChannel channel) { channel.addPropertyChangeListener(this); } public void onNewMeetMeUser(MeetMeUser user) { user.addPropertyChangeListener(this); } public void onNewQueueEntry(AsteriskQueueEntry user) { user.addPropertyChangeListener(this); } public void onNewAgent(AsteriskAgent asteriskAgent) { asteriskAgent.addPropertyChangeListener(this); } /** *    .   {@link PropertyChangeEvent} *    , *        , *        CallBack * * @param propertyChangeEvent      */ public void propertyChange(PropertyChangeEvent propertyChangeEvent) { findEventEndCall(propertyChangeEvent); } private void findEventEndCall(PropertyChangeEvent event) { if (event.getSource() instanceof AsteriskChannel) { AsteriskChannel callBackChannel = (AsteriskChannel) event.getSource(); String callId = getStringWithOnlyDigits(callBackChannel.getCallerId().toString()); callId = ValidValues.getValidCallId(callId); if (callBackChannel.getState().toString().equals("HUNGUP") && event.getOldValue().toString().contains("RINGING")) { //      callBackChannel.removePropertyChangeListener(this); //     } else if (callBackChannel.getState().toString().equals("HUNGUP") && event.getOldValue().toString().contains("UP")) { //     callBackChannel.removePropertyChangeListener(this); //     } else if (callBackChannel.getState().toString().equals("HUNGUP")) { //      callBackChannel.removePropertyChangeListener(this); //     } } } private String getStringWithOnlyDigits(String strForParse) { String result = ""; if (strForParse != null && !strForParse.isEmpty()) { CharMatcher ASCII_DIGITS = CharMatcher.anyOf("<>").precomputed(); result = ASCII_DIGITS.removeFrom(strForParse.replaceAll("[^0-9?!]", "")); } return result; } } 

Je vous conseille au tout début de vous engager, ce qui vient à propertyChange et regardez PropertyChangeEvent, ce sera un tas de tout ce qui se passe sur le serveur. Il ne filtre pas du tout les informations. Par conséquent, la conclusion: suspendez l'auditeur le moins possible. Pas pour chaque appel, car cela peut être fait même dans la classe OriginateCallback, pour autant que j'ai trouvé. C'est inutile. Regardez quels objets PropertyChangeEvent vous parviennent, regardez le type de champs et ceux dont vous avez besoin. Suivant - bienvenue dans le monde du traitement de l'information.

Un peu sur la validation des données.

Dans OriginateAction.setChannel - trunk_name / phone_user est passé
phone_user - s'il s'agit du russe, il doit commencer par un chiffre de huit, si le numéro international est accompagné d'un plus.

Dans OriginateAction.setCallerId - le numéro de téléphone du client est transmis,
puis dans CallBackEventListener, il viendra dans callBackChannel.getCallerId ().

Prendra comme ça:

 String callId = getStringWithOnlyDigits(callBackChannel.getCallerId().toString()); 

Au final, n'oubliez pas:

 asteriskServer.shutdown(); 

Si vous devez interrompre un appel, alors soit dans la classe CallBackEventListener
au canal de communication existant, nous effectuons:

 callBackChannel.hangup(); 

Un tutoriel aussi simple s'est avéré. À première vue, bien sûr, c'est très simple, mais croyez-moi, cela prend beaucoup de temps et de nerfs pour trouver des informations, repousser toutes les méthodes et quitter le travail.

Bonne chance avec vos serveurs Asterisk!

Lectures complémentaires:

1) Tutoriel Asterisk-Java

2) Interface de gestion d'astérisque (AMI)

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


All Articles