...

понедельник, 28 июля 2014 г.

JavaScript для… настольного IP телефона?


// 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://ift.tt/1pvigeb' + 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();




This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


Комментариев нет:

Отправить комментарий