Действительно полезное приложение для Digium телефонов

image

Приветствую, хабрасообщество.

Чуть более года назад мы разрабатывали приложения для Digium телефонов. Несмотря на то, что планы были обширными, мы остановились только на следующих вариациях:

  • Погода с сайта гисметео
  • Курс валют с сайта центробанка
  • RSS лента с новостных порталов

Данные приложения были написаны, чтобы ознакомить сообщество с API и примерами, даже больше just for fun. Cофт, если так можно его назвать, не несет себе никакого уникального применения, которое было бы полезно реальному бизнесу.

Сегодня мы решили вернуться к этой теме, и поделиться другим, на наш взгляд намного более интересным приложением, которое отображает вызов на экране телефона, если пользователи находятся в одной пикап группе и позволяет его перехватить.

За подробностями — > хабракат

Ввиду того, что телефон сам не может обратиться к астериску по HTTP, была выбрана структура клиент-сервер. Сервер на питоне, который «ловит» от астериска CURL запросы при звонке, и javascript на телефоне, который с некоторой периодичностью опрашивает о наличии новых записей.

server.py
from BaseHTTPServer import BaseHTTPRequestHandler,HTTPServer

import urlparse

import json

import shelve

import datetime

import sys

import os



TIME_FORMAT = '%d.%m.%Y %H:%M:%S.%f'

DB_NAME =  "db.db"


class HttpProcessor(BaseHTTPRequestHandler):

    def do_GET(self):

        if "/get" in self.path:

            fields = self.get_fields()

            if not fields:

                print "There are no parameters"

                return

            callgroup = fields.get("callgroup")

            pickupgroup = fields.get("pickupgroup")

            if callgroup is None or pickupgroup is None:

                print "There are no pickupgroup or no callgroup"

                self.send_error(404)

                return None

            db = shelve.open( DB_NAME)

            data = []

            for key in db.keys():

                if "callgroup" in db[key] and "pickupgroup" in db[key] and str(db[key]["callgroup"]) == str(callgroup) and str(db[key]["pickupgroup"]) == str(pickupgroup):

                    entry = db[key]

                    data.append(entry)

            # Make simple dict for json

            self.send_json(data)

            db.close()



    def do_POST(self):

        data = self.get_json_data()

        if "uid" not in data:

            print "There are no a number"

            return

        uid = str(data["uid"])

        print data

        if "/put" in self.path:

            db = shelve.open( DB_NAME)

            db[uid] = data

            db.close()

            data = {"status": 200, "message": "OK"}

            self.send_json(data)

        elif "/del" in self.path:

            db = shelve.open( DB_NAME)

            if uid in db:

                del db[uid]

            data = {"status": 200, "message": "OK"}

            self.send_json(data)



    def send_json(self, data):

        self.send_response(200)

        self.send_header('Content-Type', 'application/json')

        self.end_headers()

        self.wfile.write(json.dumps(data))



    def get_json_data(self):

        length = int(self.headers.getheader('content-length'))

        field_data = self.rfile.read(length)

        try:

            data = json.loads(field_data)

        except:

            print "JSON error:" + field_data

            self.send_error(404)

            return None

        return data



    def get_fields(self):

        fields_data = self.path.split("?")

        if len(fields_data) < 2:

            return

        GET = {}

        args = fields_data[1].split('&')

        for arg in args:

            t = arg.split('=')

            if len(t) > 1:

                k, v = arg.split('=')

                GET[k] = v

        return GET



if __name__ == "__main__":

    if os.path.exists( DB_NAME):

        os.remove( DB_NAME)

    host = "192.168.1.254"

    port = 8000

    serv = HTTPServer((host, port), HttpProcessor)

    print "Server running at {}:{}".format(host, port)

    serv.serve_forever()



Не забудьте дать этому файлу права на исполнение (chmod +x), запустить и добавить в автозапуск.

Установка приложения на телефон

Как вы наверняка знаете, или слышали, Digium телефон можно настроить с помощью веб-интерфейса, либо с помощью некоего проприетарного провиженинга DPMA. (Digium Phone Module Asterisk).
Если у вас самая простая настройка (через веб), то вам необходимо перейти в меню телефона System tools и нажать Enable App Development, затем залогиниться на телефон по адресу phone_ip-address/app_dev (по дефолту: Юзер: admin, Пароль: 789) и там нажать большую зеленую кнопку Add App.

0add326b89b74ea4b54ce8f117deb973.png

Вот собственно файл на скачивание самого приложения: pbxware.ru

Сам код
var incomingGroupCall = {}
var screen = require('screen');
//util for debugging
var util = require('util');
var app = require('app');
//we needs to get all info about app
app.init();
screen.clear();

//Get config of app(we needs settings)
var config = app.getConfig();
var callgroup =  config.settings.callgroup; //Get callgroup
var pickupgroup =  config.settings.pickupgroup; //Get pickupgroup
var server = config.settings.server; //server uri, like http://{host}:{port}
var app_name = config.settings.id; //App name from json file
var phonePrefix = config.settings.prefix; //App name from json file
var language = config.settings.language || "ru";

var uids = []; // This list contains all uids server give us at runtime
var phonesCount = 0; // This variable needs to watch uids missing from uids list
var timer;
var currentListPos = 0;
var listWidget = new List(0, 0, window.w, window.h);
var lang = digium.readFile("app", language + ".json");
language = JSON.parse(lang);

incomingGroupCall.show = function () {
        util.debug("Call show");
        if (this.visible) {
                window.add(listWidget);         
        }
        this.update();
        if (timer) {
                clearInterval(timer);
        }
        timer = setInterval(this.update, 1100);
};

incomingGroupCall.showGui = function(message, params) {
        util.debug("Show gui");
        var lastSelected = listWidget.selected;
        listWidget.clear();
        listWidget.set(0,0, message);
        var i=1;
        if(!params){
                return;
        }
        params.forEach(function(entry) {
                var msg = entry.from + " --> " + entry.to;
                if(i<=9){
                        msg = "[" + i + "]  " + msg;
                } else if(i === 10) {
                        msg = "[0]  " + msg;
                } else if(i === 11) {
                        msg = "[*]  " + msg;
                } else if(i === 12) {
                        msg = "[#]  " + msg;
                }
                listWidget.set(i, 0, msg);
                listWidget.set(i, 1, entry.to); //container to get value in key handler
                i++;
        });
        listWidget.select(lastSelected);
}


incomingGroupCall.update = function() {
        var request = new NetRequest();
        request.open("GET", server + "/get?callgroup="+callgroup+"&pickupgroup="+pickupgroup);
        request.onreadystatechange = function() {
                //(readyState === 4) indicates a completed request              
                if (4 === request.readyState) {
                        if (200 === request.status) {                           
                                try {                           
                                        var data = JSON.parse(request.responseText);
                                        if (!data || data.length === 0) {
                                                if (!digium.app.inForeground) {
                                                        return;
                                                }
                                                incomingGroupCall.showGui(language["NO_CALLS"]);
                                                return;
                                        }
                                        //Remove ended calls
                                        var currentUids = data.map( function(item) { 
                                                return item.uid; 
                                        });
                                        var needsToRefresh = false;
                                        var newEntryAvailable = false;
                                        uids.forEach(function(uid) {
                                                if(currentUids.indexOf(uid) === -1) {
                                                        uids.splice(uids.indexOf(uid), 1);
                                                        needsToRefresh = true;
                                                }
                                        });                                     
                                        // Add new phones to list                               
                                        data.forEach(function(entry) {
                                                if (uids.indexOf(entry.uid) === -1) {
                                                        needsToRefresh = true;
                                                        newEntryAvailable = true;
                                                        uids.push(entry.uid);
                                                }
                                        });
                                        util.debug("New entry:" + newEntryAvailable);
                                        if (!digium.app.inForeground && newEntryAvailable) {
                                                digium.foreground();
                                        }
                                        if(needsToRefresh) {
                                                incomingGroupCall.showGui(language["INCOMING_CALLS"], data);
                                        }
                                        
                                } catch (e) {
                                        util.debug('request error: ' + JSON.stringify(e));
                                        incomingGroupCall.showGui(language["SERVER_UNAVAILABLE"]);
                                }
                        } else {
                                util.debug('request error1: ' + request.status);                                
                                incomingGroupCall.showGui(language["SERVER_UNAVAILABLE"]);
                        }
                }                       
        }.bind(this);
        request.setTimeout(1000);
        request.send();
};

//initialize variables
incomingGroupCall.init = function () {
        this.widgets = {};

        //stay open when the app is backgrounded
        digium.app.exitAfterBackground = false;

        this.visible = digium.app.inForeground;
        incomingGroupCall.listeners();

        // setInterval(digium.restart, 180000);
};


incomingGroupCall.listeners = function () {
        //show the full window when the app is foregrounded
        digium.event.observe({
                'eventName'             : 'digium.app.foreground',
                'callback'              : function () {
                        util.debug("app.foregrounded");
                        window.clear();
                        this.visible = digium.app.inForeground;
                        this.setButtons();
                        this.show();
                }.bind(this)
        });

        //show the idle window when the idleScreen is shown
        digium.event.observe({
                'eventName'             : 'digium.app.background',
                'callback'              : function () {
                        util.debug("app.background");
                        this.visible = digium.app.inForeground;
                        window.clearSoftkeys();
                        this.show();
                }.bind(this)
        });
};

incomingGroupCall.setButtons = function () {
        window.onkeyselect = function() {
                var phone = listWidget.get(listWidget.selected, 1);
                util.debug("Selected " + phone);
                digium.phone.dial({
                        "number": phonePrefix+phone
                })
        }
        window.onkey = function(e) {
                //Digits keyboard handler
                try {
                        var key = e.key;
                        if(e.key == "0") {
                                key = 10;
                        } else if (e.key == "*") {
                                key = 11;
                        } else if (e.key == "#") {
                                key = 12;
                        }
                        var phone = listWidget.get(key, 1);
                        if (phone) {
                                digium.phone.dial({
                                        "number": phonePrefix+phone
                                })
                        }
                } catch(e) {
                        util.debug("Error in trying to dial");
                }
                util.debug(JSON.stringify(e));
        }
        window.setSoftkey(4, language['EXIT'], function() {
                digium.app.exitAfterBackground = true;
                digium.background();
        }.bind(this));
        window.setSoftkey(3, language['HIDE'], function() {
                digium.background();
        }.bind(this));
}

incomingGroupCall.init();
incomingGroupCall.show();



В настройках нового приложения необходимо указать следующие опции:

callgroup: 1
pickupgroup: 1
server: 192.168.1.254:8000 (ну или любой другой IP, где запущен питоновский скрипт)
prefix: *8 (префик для перехвата звонка)
language: ru (поддерживается en / ru)

Скриншот настройки телефона
27a7a8e8cfa44edc92638ab29c88fc72.png

Если вы используете DPMA, то необходимо загрузить приложение на телефоны с помощью этой инструкции

Запускаете приложение. Можете оставить открытым, либо свернуть в фон.

423cd49a3afb4b829926f2abec0021d7.png

Изменение диалплана Asterisk

Итак, приложение мы записали на телефон, ретранслятор на сервере тоже запустили. Осталось внести изменения в диалплан астериска, чтобы он сообщал, что на добавочный номер пришел звонок с нужными нам параметрами callgroup и PickupGroup.

Например, вы можете использовать наш рабочий диалплан

exten => _7XX,1, NoOp (Call from ${CALLERID (num)} to ${EXTEN})
same => n, Set (CallGroup=${SIPPEER (${EXTEN}, callgroup)})
same => n, NoOp (Callgroup = ${CallGroup})
same => n, Set (PickupGroup=${SIPPEER (${EXTEN}, pickupgroup)})
same => n, NoOp (PickupGroup = ${PickupGroup})
same => n, System (curl -i -H «Accept: application/json» -H «Content-Type: application/json» -X POST -d '{«uid»:»${UNIQUEID}», «callgroup»:»${CallGroup}», «pickupgroup»:»${PickupGroup}», «from»:»${CALLERID (num)}», «to»:»${EXTEN}»}' 192.168.1.254:8000/put)
same => n, Dial (SIP/${EXTEN},60, Tt)
same => n, Set (CallGroup=${SIPPEER (${EXTEN}, callgroup)})
same => n, Hangup ()

exten => h,1, NoOp (END of App)
same => n, System (curl -i -H «Accept: application/json» -H «Content-Type: application/json» -X POST -d '{«uid»:»${UNIQUEID}», «callgroup»:»${CallGroup}», «pickupgroup»:»${PickupGroup}», «from»:»${CALLERID (num)}», «to»:»${TARGETNO}»}' 192.168.1.254:8000/del)
same => n, Hangup ()

Приложение в действии

7fc475ad93d14fd58a6ec7a61228fa62.png

При входящем звонке на экране телефона всплывает наше приложение, которое показывает, звонки в данной колл-группе и позволяет его перехватить. Никакой другой сигнализации нет (например мелодии или световой индикации)

По номерам можно перемещаться с помощью кнопок «вверх» и «вниз», можно перехватить вызов нажатием на кнопку «ОК», тогда перехватится выделенный номер, либо с помощью цифр и *, #.

Запускаешь приложение, сворачиваешь его и ждешь, когда позвонят). При новом звонке приложение развернется. Можно опять свернуть его.

© Habrahabr.ru