📜 ⬆️ ⬇️

Javascript for ... desktop ip phone?



Few people know that Digium IP phones are not ordinary phones. It would seem, why would a manufacturer produce devices of its own brand in such a saturated and low-margin market? But believe me - it was worth it. In addition to excellent physical characteristics: nice plastic, bright screen, excellent speakers and a microphone. These devices have their own API and you can write your application for them!

Who else can boast such functionality?
')



Writing your own application for a new platform is not only interesting and informative, but also useful! I once said in a previous review that the idea of ​​an application can be limited only to your imagination.

My fantasy thought that the following things might be useful for a desktop IP phone:

1) Sync contacts with Google account
2) Synchronization of contacts Facebook, VK
3) the withdrawal of exchange rates with securities
4) Notifications of unread email (integration with mailers by POP3)
5) Weather applications (from Russian-speaking Gismeteo, yandex, etc.)
6) synchronization of contacts with LDAP
7) an application that gives the supervisor to overhear or break into the conversation of the employee.

The last two points are of course very “cool”, but I'm afraid it is intended only for JavaScript gurus and asterisk-part-time.

I would love to read about your ideas such applets here.

Weather app with GISMETEO




It was decided to start with the simplest. Weather widget.
After reviewing the documentation and examples we got this code:

Informer.js
// Lets include the app (for standard settings option): var app = require('app'); // And initialize it as required: app.init(); // Our select tool input: var SelectInput = require('SelectInput').SelectInput; // Include some utilities: var util = require('util'); // Screen instance: var screen = require('screen'); // Idle window reference; var wnd = null; // On foreground event call: var onforeground_called = false; // On foreground event call to display error: var onforeground_error = false; // If app is already running it has true value: var instantiated = false; // Default city for "no-config" app: var DEF_CITY_ID = 27612; // Screen update interval (for 4 different forecasts) var DEF_FORECAST_SCREEN_INT = 15000; // Server request interval (to update weather data): var DEF_FORECAST_UPDATE_INT = 3600000; // Server request interval (to update weather data in case of error): var DEF_FORECAST_ERROR_UPDATE_INT = 60000; // App runtime configuration: var config = {}; // Runtime gathered weather data: var weather_data = {}; // Which forecast should display next refresh; var forecast_display = 0; // Count of available forecasts: var forecasts_count = 0; // Timer id of weather update request timeout var timeout_request = null; // Timer id of forecast display refresh timeout var timeout_refresh = null; // Extended time of day explanations: var tod_extended = { '0': ['', '', ''], '1': ['', '', ''], '2': ['', '', ' '], '3': ['', ' ', ' '] }; // Weekday russian shortcuts (Gismeteo format): var week_days = { '1':'', '2':'', '3':'', '4':'', '5':'', '6':'', '7':'' }; // Gismeteo wind direction map: var wind_dir = { '0':'C', '1':'', '2':'', '3':'', '4':'', '5':'', '6':'', '7':'' }; // Decode CP1251 (name of city): function decode1251 (str) { var i, result = '', l = str.length, cc; for (i = 0; i < l; i++) { cc = str.charCodeAt(i); if ('\r' == str[i]) continue; if (cc < 192) { if (168 == cc) result += ''; else if (184 == cc) result += ''; else result += String.fromCharCode(cc); } else result += String.fromCharCode(cc + 848); } return result; } // Just a little workaround for IDE: function endForegroundMonitor () { onforeground_called = false; onforeground_error = false; } // Show window for selecting user-defined city: function showFormCities () { if (!onforeground_called) return; digium.event.stopObserving({'eventName' : 'digium.app.background'}); digium.event.observe({ 'eventName' : 'digium.app.background', 'callback' : function () { endForegroundMonitor(); digium.event.stopObserving({'eventName' : 'digium.app.background'}); } }); screen.clear(); var i, select_options = []; for (i in cities) { if (cities.hasOwnProperty(i)) select_options.push({'value':i,'display':cities[i]}); } var select = new SelectInput({ 'options':select_options, 'width':window.w - 120, 'title':'   ', 'x':100, 'y':20+Text.LINE_HEIGHT, 'initialValue':config['CITY_ID'].toString() }); select.onFocus = function(){return true}; window.add(screen.setTitleText({'title' : ' '})); window.add(new Text(20, 20+Text.LINE_HEIGHT, 70, Text.LINE_HEIGHT, ' :')); window.add(select.textWidget); select.takeFocus(); select.textWidget.setSoftkey(4, '', showAppWindow); select.textWidget.setSoftkey(1, '', function(){ config['CITY_ID'] = parseInt(select.value); confirmUserCity(); updateWeatherData(); digium.background(); }); } // Saving user's choice: function confirmUserCity () { try { digium.writeFile( 'nv', 'settings.json', JSON.stringify({'CITY_ID':config['CITY_ID']}) ); } catch(e) {} } // Getting app-level configuration: function getApplicationConfig () { return util.defaults(app.getConfig().settings, { 'CITY_ID':DEF_CITY_ID, 'FORECAST_SCREEN_INT':DEF_FORECAST_SCREEN_INT, 'FORECAST_UPDATE_INT':DEF_FORECAST_UPDATE_INT }); } // Getting user-level configuration: function getLocalConfig () { var result; try { var configFile = digium.readFile('nv', 'settings.json'); result = JSON.parse(configFile); } catch (e) { result = {}; } return result; } // Function-helper for parsing single node attrs: function getMappedAttributes (node, map) { var i, result = {}; for (i in map) { if (map.hasOwnProperty(i)) result[map[i]] = node.getAttribute(i); } return result; } // Parse xml weather data to object: function parseWeatherData (src) { var data = {}, nodes, node, l, i, subnode; var parser = new DOMParser(); try { var doc = parser.parseFromString(src, 'application/xml'); nodes = doc.getElementsByTagName('TOWN'); data.city = decode1251(unescape(nodes[0].getAttribute('sname'))).replace('+', ' '); nodes = doc.getElementsByTagName('FORECAST'); l = nodes.length; i = 0; forecasts_count = l; node = nodes[0]; data.day = node.getAttribute('day'); data.month = node.getAttribute('month'); data.weekday = node.getAttribute('weekday'); data.tod = node.getAttribute('tod'); data.forecasts = {}; var forecast, tmp; do { forecast = {}; subnode = node.getElementsByTagName('PHENOMENA')[0]; forecast = util.defaults(forecast, getMappedAttributes(subnode, { 'cloudiness':'clouds', 'precipitation':'precip', 'rpower':'rpower', 'spower':'spower' })); subnode = node.getElementsByTagName('PRESSURE')[0]; tmp = getMappedAttributes(subnode, { 'min':'min', 'max':'max' }); forecast.press = tmp.min + '-' + tmp.max; subnode = node.getElementsByTagName('TEMPERATURE')[0]; tmp = getMappedAttributes(subnode, { 'min':'min', 'max':'max' }); forecast.temp = parseInt((parseInt(tmp.min) + parseInt(tmp.max)) / 2); subnode = node.getElementsByTagName('WIND')[0]; tmp = getMappedAttributes(subnode, { 'min':'min', 'max':'max', 'direction':'wdir' }); forecast.wspeed = parseInt((parseInt(tmp.min) + parseInt(tmp.max)) / 2); forecast.wdir = tmp.wdir; subnode = node.getElementsByTagName('RELWET')[0]; forecast = util.defaults(forecast, getMappedAttributes(subnode, { 'max':'hum' })); data.forecasts[i] = forecast; node = nodes[++i]; } while (i < l); } catch(e) { data = {error:true}; } return data; } // Currently disabled (show error message in window of the app) /*function reportError (info) { endForegroundMonitor(); onforeground_error = true; digium.foreground(); screen.clear(); window.add(new Text(0, 0, window.w,Text.LINE_HEIGHT * 2, info.type + ': ' + info.description)); digium.event.stopObserving({'eventName' : 'digium.app.background'}); digium.event.observe({ 'eventName' : 'digium.app.background', 'callback' : function () { endForegroundMonitor(); digium.event.stopObserving({'eventName' : 'digium.app.background'}); } }); }*/ // Get current no of forecast to display: function getForecastToDisplay () { if (forecast_display > (forecasts_count - 1) ) return forecast_display = 0; else return forecast_display++; } // Get labels values (with updated data): function getUpdatedLabels (forecast_no) { var result = {}, time = ''; var forecast = weather_data.forecasts[forecast_no]; if (!forecast_no) time = ''; else time = tod_extended[weather_data.tod][forecast_no - 1]; result.temp = time + ' ' + forecast.temp + ' °C'; result.press = forecast.press + '  . .'; result.hum = ' ' + forecast.hum + '%'; result.wind = ' ' + forecast.wspeed + ' / ' + wind_dir[forecast.wdir]; return result; } // "Humanize" Gismeteo phenomena: function getPhenomenaObj (forecast, nt) { // Default state of the phenomena part: var result = {icon:'unknown',status:' '}; if ((1 == forecast.spower) && ((8 == forecast.precip) || (9 == forecast.precip))) { result.icon = 'storm'; result.status = ''; } else if (5 == forecast.precip) { result.icon = 'rainfall'; result.status = ''; } else if (4 == forecast.precip) { result.icon = 'rain'; result.status = ''; } else if ((6 == forecast.precip) || (7 == forecast.precip)) { result.icon = 'snow'; result.status = ''; } else if (3 == forecast.clouds) { result.icon = 'mostlycloudy'; result.status = ''; } else if (2 == forecast.clouds) { result.icon = 'cloudy'; result.status = ''; } else if ((1 == forecast.clouds) && (10 == forecast.precip)) { if (nt) result.icon = 'nt_mostlyclear'; else result.icon = 'mostlyclear'; result.status = '. .'; } else if ((0 == forecast.clouds) && (10 == forecast.precip)) { if (nt) result.icon = 'nt_clear'; else result.icon = 'clear'; result.status = ''; } return result; } // Refresh widget contents on the idle display: function idleRefresh (on_timer) { var labels, fno, f, nt = false; if (util.isDef(on_timer) && on_timer) { fno = getForecastToDisplay(); labels = getUpdatedLabels(fno); } else { forecast_display = 0; fno = forecast_display++; labels = getUpdatedLabels(fno); } f = weather_data.forecasts[fno]; if (weather_data.tod == 0 && fno == 0) nt = true; else if (weather_data.tod == 1 && fno == 3) nt = true; else if (weather_data.tod == 2 && fno == 2) nt = true; else if (weather_data.tod == 3 && fno == 1) nt = true; var phen = getPhenomenaObj(f, nt); wnd[0] = new Image('app', phen.icon + '.gif', 15, Text.LINE_HEIGHT, 50, 50); wnd[2].label = labels.temp; wnd[3].label = phen.status; wnd[4].label = labels.press; wnd[5].label = labels.hum; wnd[6].label = labels.wind; clearTimeout(timeout_refresh); timeout_refresh = setTimeout(function(){ idleRefresh(true); }, config['FORECAST_SCREEN_INT']); } // Function to finalize "get weather request" function getWeatherCb (data) { if (data.hasOwnProperty('error') && data.error) { wnd[1].label = ' ...'; setTimeout(updateWeatherData, DEF_FORECAST_ERROR_UPDATE_INT); } else { weather_data = data; // overwrite previous data wnd[1].label = data.city + ', ' + week_days[data.weekday] + ' ' + data.day + '.' + data.month; idleRefresh(); } } // Get weather data from Gismeteo: function getWeather (cb) { wnd[1].label = ' ...'; var uri = 'http://informer.gismeteo.ru/xml/' + config['CITY_ID'] + '.xml'; var request = new NetRequest(); request.open('GET', uri, true); request.onreadystatechange = function () { if (4 == request.readyState) { if (200 == request.status) cb(parseWeatherData(request.responseText)); else { setTimeout(updateWeatherData, DEF_FORECAST_ERROR_UPDATE_INT); } } }; request.send(); clearTimeout(timeout_request); timeout_request = setTimeout( updateWeatherData, config['FORECAST_UPDATE_INT'] ); } // Function for usage in setTimeout func: function updateWeatherData () { getWeather(getWeatherCb); } // Idle window initialization and reference store; function initialize () { var cursor = 0, label; wnd = digium.app.idleWindow; wnd.add(new Image('app', 'unknown.gif', 15, Text.LINE_HEIGHT, 50, 50)); for (var i = 0; i < 6; i++) { if (0 == i) label = new Text(0, Text.LINE_HEIGHT * cursor++, wnd.w, Text.LINE_HEIGHT, ' ...'); else if (2 == i) label = new Text(10, 50, 65, Text.LINE_HEIGHT, ' '); else label = new Text(65, Text.LINE_HEIGHT * cursor++, wnd.w - 65, Text.LINE_HEIGHT); label.align(Widget.CENTER); wnd.add(label); } digium.app.exitAfterBackground = false; wnd.hideBottomBar = true; digium.event.observe({ 'eventName' : 'digium.app.start', 'callback' : function () { setTimeout(function(){instantiated = true;}, 1000); } }); digium.event.observe({ 'eventName' : 'digium.app.idle_screen_show', 'callback' : function () { if (digium.app.idleWindowShown) idleRefresh(); } }); digium.event.observe({ 'eventName' : 'digium.app.foreground', 'callback' : function () { // Stopping recursive call when error message box // should be shown by calling digium.foreground() if (onforeground_called) return ; onforeground_called = true; if (!instantiated) { // bring app to idle on the first launch: digium.background(); instantiated = true; endForegroundMonitor(); } else { // Show select options list: 1. setting up; 2. exit widget // showFormCities(); showAppWindow(); } } }); } function showAppWindow () { if (!onforeground_called) return; digium.event.stopObserving({'eventName' : 'digium.app.background'}); digium.event.observe({ 'eventName' : 'digium.app.background', 'callback' : function () { endForegroundMonitor(); digium.event.stopObserving({'eventName' : 'digium.app.background'}); } }); screen.clear(); window.add(screen.setTitleText({'title' : ''})); try { window.add(new Text(4, 20, window.w, Text.LINE_HEIGHT, ' :')); var menuObj = new List(4, 20 + Text.LINE_HEIGHT, window.w, window.h); menuObj.setProp('cols', 2).setProp('rows', 2); menuObj.set(0, 0, '1.'); menuObj.set(0, 1, ' '); menuObj.set(1, 0, '2.'); menuObj.set(1, 1, ' '); menuObj.setColumnWidths(15, 0); menuObj.select(0); menuObj.onFocus = function() {return true; }; var selected = function() { if (0 == menuObj.selected) showFormCities(); else { digium.app.exitAfterBackground = true; digium.background(); } }; menuObj.onkey1 = function(){ menuObj.select(0); selected(); }; menuObj.onkey2 = function(){ menuObj.select(1); selected(); }; menuObj.onkeyselect = selected; menuObj.setSoftkey(1, 'OK', selected); window.add(menuObj); menuObj.takeFocus(); } catch(e) { window.add(new Text(0, 20, window.w, Text.LINE_HEIGHT, e.message)); } } // Get summary configuration data with user-level priority: config = util.defaults(getLocalConfig(), getApplicationConfig()); // Initialize: initialize(); // Start app main cycle: updateWeatherData(); 



because There is no Cyrillic on the phone buttons, and you need to type them somehow in the search, you had to do some kind of manipulation: namely, to get the entire list of cities and their IDs, translate them into Latin and create a database file for the application.
Looks like that
 var cities = {"20476":"Mys Sterligova","20674":"Dikson","20744":"Malye Karmakuly","20864":"Tambey","20891":"Khatanga","20978":"Karaul","20982":"Volochanka","21802":"Saskylakh","21824":"Tiksi","21946":"Chokurdakh","22004":"Nikel","22019":"Polyarnyy","22028":"Teriberka","22112":"Kola","22113":"Murmansk","22127":"Lovozero","22204":"Kovdor","22213":"Apatity","22216":"Kirovsk (Murm.)","22217":"Kandalaksha","22324":"Umba","22408":"Kalevala","22429":"Solovki","22471":"Mezen","22522":"Kem-Port","22546":"Severodvinsk","22550":"Arkhangelsk","22559":"Kholmogory","22563":"Pinega","22573":"Leshukonskoe","22621":"Segezha","22641":"Onega","22671":"Karpogory","22686":"Vendinga","22695":"Koslan","22717":"Suoyarvi","22721":"Medvezhegorsk","22727":"Kondopoga","22762":"Dvinskoy Bereznik","22768":"Shenkursk","22778":"Verkhnyaya Toyma","22798":"Yarensk","22802":"Sortavala","22807" 

etc.


In short, we can characterize the development as follows:

1. Pros.
1.1 Javascript)
1.2 Availability for interaction with the web object Netrequest (ala XHR);
1.3 Many additional objects to perform routine tasks and interact with the proposed API;
1.4 Hierarchy of embedded objects offered for use;
1.5 Mechanism of events;
1.6 debugging support;

2. Cons.
2.1 A small number of examples on the use of the API, coupled with a minimalist approach to the documentation itself;
2.2 Resource limitations - it is better not to even start thinking about parsing a large amount of data with the help of DomParser, it is quite suitable for accessing small XML;
2.3 Non-standard names for some objects and controls (for example, List, which, generally speaking, can display a table), which, together with clause 2.1, makes “familiarity” with the API a little longer;
2.4 There is no built-in "scrolling" of the text by timer when displayed in sizes limited to the amount of text (it would be useful at least in widgets);
2.5 The hierarchy of available objects is fuzzy, some things are superfluous (in general, the idea is not bad, but it’s possible to consider it a plus only if the developers have a plan for the development of all this);

3. Features.
3.1 The presence of both window and screen objects (in this case, the window is actually a Group widget, not sour, is it? - see paragraphs 2.5, 2.3);
3.2 When developing applications for working with the web, you need to take into account that it is impossible to get cookies for use in javascript.

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


All Articles