// 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();
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"
Source: https://habr.com/ru/post/230927/