Действительно полезное приложение для 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.



    Вот собственно файл на скачивание самого приложения: 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)

    Скриншот настройки телефона



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

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



    Изменение диалплана 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()

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





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

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

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

    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 1

      –4
      Гисметео — не лучший выбор. Лучше данными с сайта гидрометцентра.

      Only users with full accounts can post comments. Log in, please.