/* jshint -W097 */// jshint strict:false /*jslint node: true */ 'use strict'; const xml2js = require('xml2js'); const http = require('http'); const utils = require(__dirname + '/lib/utils'); // Get common adapter utils const dictionary = require(__dirname + '/lib/words'); const adapter = utils.Adapter({ name: 'yr', // adapter name useFormatDate: true // read date format from config }); adapter.on('ready', main); function main() { const tmp = adapter.config.location.split('/'); const city = decodeURI(tmp.pop()); adapter.config.language = adapter.config.language || 'en'; if (adapter.config.sendTranslations === undefined) adapter.config.sendTranslations = true; if (adapter.config.sendTranslations === 'true') adapter.config.sendTranslations = true; if (adapter.config.sendTranslations === 'false') adapter.config.sendTranslations = false; adapter.getObject('forecast.day0.temperatureActual', (err, obj) => { if (obj && obj.common && obj.common.unit) { if (obj.common.unit === '°C' && adapter.config.nonMetric) { obj.common.unit = '°F'; adapter.setObject(obj._id, obj, () => { adapter.log.info(`Metrics changed for ${obj._id} to ${obj.common.unit}`); }); } else if (obj.common.unit !== '°C' && !adapter.config.nonMetric) { obj.common.unit = '°C'; adapter.setObject(obj._id, obj, () => { adapter.log.info(`Metrics changed for ${obj._id} to ${obj.common.unit}`); }); } } }); for (let d = 0; d < 3; d++) { adapter.getObject('forecast.day' + d + '.windSpeed', (err, obj) => { if (obj && obj.common && obj.common.unit) { if (obj.common.unit === 'km/h' && adapter.config.nonMetric) { obj.common.unit = 'm/h'; adapter.setObject(obj._id, obj, () => { adapter.log.info(`Metrics changed for ${obj._id} to ${obj.common.unit}`); }); } else if (obj.common.unit !== 'km/h' && !adapter.config.nonMetric) { obj.common.unit = 'km/h'; adapter.setObject(obj._id, obj, () => { adapter.log.info(`Metrics changed for ${obj._id} to ${obj.common.unit}`); }); } } }); adapter.getObject('forecast.day' + d + '.temperatureMin', (err, obj) => { if (obj && obj.common && obj.common.unit) { if (obj.common.unit === '°C' && adapter.config.nonMetric) { obj.common.unit = '°F'; adapter.setObject(obj._id, obj, () => { adapter.log.info(`Metrics changed for ${obj._id} to ${obj.common.unit}`); }); } else if (obj.common.unit !== '°C' && !adapter.config.nonMetric) { obj.common.unit = '°C'; adapter.setObject(obj._id, obj, () => { adapter.log.info(`Metrics changed for ${obj._id} to ${obj.common.unit}`); }); } } }); adapter.getObject('forecast.day' + d + '.temperatureMax', (err, obj) => { if (obj && obj.common && obj.common.unit) { if (obj.common.unit === '°C' && adapter.config.nonMetric) { obj.common.unit = '°F'; adapter.setObject(obj._id, obj, () => { adapter.log.info(`Metrics changed for ${obj._id} to ${obj.common.unit}`); }); } else if (obj.common.unit !== '°C' && !adapter.config.nonMetric) { obj.common.unit = '°C'; adapter.setObject(obj._id, obj, () => { adapter.log.info(`Metrics changed for ${obj._id} to ${obj.common.unit}`); }); } } }); adapter.getObject('forecast.day' + d + '.precipitation', (err, obj) => { if (obj && obj.common && obj.common.unit) { if (obj.common.unit === 'mm' && adapter.config.nonMetric) { obj.common.unit = 'in'; adapter.setObject(obj._id, obj, () => { adapter.log.info(`Metrics changed for ${obj._id} to ${obj.common.unit}`); }); } else if (obj.common.unit !== 'mm' && !adapter.config.nonMetric) { obj.common.unit = 'mm'; adapter.setObject(obj._id, obj, () => { adapter.log.info(`Metrics changed for ${obj._id} to ${obj.common.unit}`); }); } } }); } adapter.getObject('forecast', (err, obj) => { if (!obj || !obj.common || obj.common.name !== 'yr.no forecast ' + city) { adapter.setObject('forecast', { type: 'device', role: 'forecast', common: { name: 'yr.no forecast ' + city }, native: { url: adapter.config.location, country: decodeURI(tmp[0]), state: decodeURI(tmp[1]), city: city } }); } }); if (adapter.config.location.indexOf('forecast.xml') === -1) { if (adapter.config.location.indexOf('%') === -1) adapter.config.location = encodeURI(adapter.config.location); adapter.setState('forecast.info.diagram', 'http://www.yr.no/place/' + adapter.config.location + '/avansert_meteogram.png', true); const reqOptions = { hostname: 'www.yr.no', port: 80, path: '/place/' + adapter.config.location + '/forecast.xml', method: 'GET' }; adapter.log.debug('get http://' + reqOptions.hostname + reqOptions.path); const req = http.request(reqOptions, res => { let data = ''; res.on('data', chunk => data += chunk); res.on('end', () => { adapter.log.debug('received data from yr.no'); parseData(data.toString()); }); }); req.on('error', e => { adapter.log.error(e.message); parseData(null); }); req.end(); } else { parseData(require('fs').readFileSync(adapter.config.location).toString()); } // Force terminate after 5min setTimeout(() => { adapter.log.error('force terminate'); process.exit(1); }, 300000); } function _(text) { if (!text) return ''; if (dictionary[text]) { let newText = dictionary[text][adapter.config.language]; if (newText) { return newText; } else if (adapter.config.language !== 'en') { newText = dictionary[text].en; if (newText) { return newText; } } } else { if (adapter.config.sendTranslations) { const options = { hostname: 'download.yunkong2.net', port: 80, path: '/yr.php?word=' + encodeURIComponent(text) }; const req = http.request(options, res => { console.log('STATUS: ' + res.statusCode); adapter.log.info('Missing translation sent to yunkong2.net: "' + text + '"'); }); req.on('error', e => { adapter.log.error('Cannot send to server missing translation for "' + text + '": ' + e.message); }); req.end(); } else { adapter.log.warn('Translate: "' + text + '": {"en": "' + text + '", "cn": "' + text + '"}, please send to developer'); } } return text; } function celsius2fahrenheit(degree, isConvert) { if (isConvert) { return degree * 9 / 5 + 32; } else { return degree; } } function parseData(xml) { if (!xml) { setTimeout(() => process.exit(0), 5000); return; } const options = { explicitArray: false, mergeAttrs: true }; const parser = new xml2js.Parser(options); parser.parseString(xml, (err, result) => { if (err) { adapter.log.error(err); } else { adapter.log.info('got weather data from yr.no'); const forecastArr = result.weatherdata.forecast.tabular.time; let tableDay = ''; let tableHead = ''; let tableMiddle = ''; let tableBottom = ''; const dateObj = new Date(); const dayEnd = dateObj.getFullYear() + '-' + ('0' + (dateObj.getMonth() + 1)).slice(-2) + '-' + ('0' + dateObj.getDate()).slice(-2) + 'T24:00:00'; let daySwitch = false; let day = -1; // Start from today const days = []; for (let i = 0; i < 12 && i < forecastArr.length; i++) { const period = forecastArr[i]; if (!period.period || period.period === '0') day++; // We want to process only today, tomorrow and the day after tomorrow if (day === 3) break; period.symbol.url = '/adapter/yr/icons/' + period.symbol.var + '.svg'; period.symbol.name = _(period.symbol.name); period.windDirection.code = _(period.windDirection.code); period.windDirection.name = _(period.windDirection.name); if (i < 8) { switch (i) { case 0: tableHead += ''; break; default: if (period.from > dayEnd) { if (!daySwitch) { daySwitch = true; tableDay += ''; if (i < 3) tableDay += ''; tableHead += ''; } else { tableHead += ''; } } else { tableHead += ''; } } tableMiddle += ''; } if (day === -1 && !i) day = 0; if (!days[day]) { days[day] = { date: new Date(period.from), icon: period.symbol.url, state: period.symbol.name, temperatureMin: celsius2fahrenheit(parseFloat(period.temperature.value), adapter.config.nonMetric), temperatureMax: celsius2fahrenheit(parseFloat(period.temperature.value), adapter.config.nonMetric), precipitation: adapter.config.nonMetric ? parseFloat(period.precipitation.value) / 25.4 : parseFloat(period.precipitation.value), windDirection: period.windDirection.code, windSpeed: adapter.config.nonMetric ? parseFloat(period.windSpeed.mps) : parseFloat(period.windSpeed.mps) * 3.6, pressure: parseFloat(period.pressure.value), count: 1 }; } else { // Summarize let t; // Take icon for day always from 12:00 to 18:00 if possible if (i === 2) { days[day].icon = period.symbol.url; days[day].state = period.symbol.name; days[day].windDirection = period.windDirection.code; } t = celsius2fahrenheit(parseFloat(period.temperature.value), adapter.config.nonMetric); if (t < days[day].temperatureMin) { days[day].temperatureMin = t; } else if (t > days[day].temperatureMax) { days[day].temperatureMax = t; } days[day].precipitation += adapter.config.nonMetric ? parseFloat(period.precipitation.value) / 25.4 : parseFloat(period.precipitation.value); days[day].windSpeed += adapter.config.nonMetric ? parseFloat(period.windSpeed.mps) : parseFloat(period.windSpeed.mps) * 3.6; days[day].pressure += parseFloat(period.pressure.value); days[day].count++; } // Set actual temperature if (!day && !i) { days[day].temperatureActual = celsius2fahrenheit(parseInt(period.temperature.value, 10), adapter.config.nonMetric); } } const style = ''; const table = style + tableDay + tableHead + tableMiddle + tableBottom + '
' + _('Now') + '' + _('Today') + '' + _('Tomorrow') + '' + _('After tomorrow') + '' + parseInt(period.from.substring(11, 13), 10).toString() + '-' + parseInt(period.to.substring(11, 13), 10).toString() + '' + parseInt(period.from.substring(11, 13), 10).toString() + '-' + parseInt(period.to.substring(11, 13), 10).toString() + '' + parseInt(period.from.substring(11, 13), 10).toString() + '-' + parseInt(period.to.substring(11, 13), 10).toString() + '' + period.symbol.name + '
'; tableBottom += '
' + period.temperature.value + '°C
'; //console.log(JSON.stringify(result, null, " ")); for (day = 0; day < days.length; day++) { // Take the average if (days[day].count > 1) { days[day].precipitation /= days[day].count; days[day].windSpeed /= days[day].count; days[day].pressure /= days[day].count; } days[day].temperatureMin = Math.round(days[day].temperatureMin); days[day].temperatureMax = Math.round(days[day].temperatureMax); days[day].precipitation = Math.round(days[day].precipitation); days[day].windSpeed = Math.round(days[day].windSpeed * 10) / 10; days[day].pressure = Math.round(days[day].pressure); days[day].date = adapter.formatDate(days[day].date); delete days[day].count; for (const name in days[day]) { if (days[day].hasOwnProperty(name)) { adapter.setState('forecast.day' + day + '.' + name, {val: days[day][name], ack: true}); } } } adapter.log.debug('data successfully parsed. setting states'); adapter.setState('forecast.info.html', {val: table, ack: true}); adapter.setState('forecast.info.object', {val: JSON.stringify(days), ack: true}, () => { setTimeout(() => process.exit(0), 5000); }); } }); }