-
Notifications
You must be signed in to change notification settings - Fork 1
/
node_helper.js
164 lines (134 loc) · 5.01 KB
/
node_helper.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
/* Magic Mirror
* Node Helper: MMM-RBB-Weather
*
* By Nikolai Keist (github.com/nkl-kst)
* MIT Licensed.
*/
const Logger = require('./Logger');
const NodeHelper = require('node_helper');
const https = require('https');
const Promise = require('bluebird');
const xmlParser = require('fast-xml-parser');
module.exports = NodeHelper.create({
// Instancevariables
config: null,
cache: null,
socketNotificationReceived: function(notification, payload) {
if (notification === 'LOAD_DATA') {
// Save config
this.config = payload;
if (this.config.days > 7) {
this.config.days = 7; // Maximum days available on RBB
}
// Load data
this.loadData();
}
},
/**
* loadData - Load data for every day set in config send it to module via socket notification.
* If an error occurs, try to use cached data instead, but errors are logged.
*/
loadData: async function() {
Logger.log(`Load data for ID "${this.config.id}" and "${this.config.days}" days ...`);
// Build days array for promise mapping
const daysArray = [];
for (let day = 0; day <= this.config.days; day++) {
daysArray.push(day);
}
const self = this;
try {
// Load data concurrently, data is an array of all days results
const data = await Promise.map(daysArray, (day) => {
// Fetch data for this day
return self.fetchDayData(day);
});
// Cache new data
self.cache = {};
self.cache.data = data;
self.cache.time = Date.now();
// Send data to module
Logger.log('Data received, send to module ...');
self.sendSocketNotification('DATA_LOADED', self.cache);
} catch (error) {
Logger.warn(`Error while fetching data: "${error.message}"`);
// Return cached data
if (self.cache) {
Logger.info('Send cached data to module ...');
self.sendSocketNotification('DATA_LOADED', self.cache);
}
}
},
/**
* fetchDayData - Fetch rbb weather data for given day. Returns a promise which is resolved when
* xml is parsed. The promise is rejected in case of error or timeout.
*
* @param {Number} day Day of weather data to be fetched
* @return {Promise} Promise resolved with xml parsed as object
*/
fetchDayData: function(day) {
Logger.log(`Fetch data for day "${day}" ...`);
return new Promise((resolve, reject) => {
// Request RBB xml data for this day
const url = `https://www.rbb24.de/include/wetter-hr/data/data_bb_${day}.xml`;
https.get(url, (response) => {
let xml = '';
// Concat data chunk
response.on('data', (chunk) => {
xml += chunk;
});
// Error
response.on('error', (error) => {
reject(error);
});
// Timeout
response.on('timeout', (error) => {
reject(error);
});
// Response complete, parse full xml
response.on('end', () => {
const data = this.parseData(xml, day);
resolve(data);
});
});
});
},
/**
* parseData - Parse given XML data into JSON object. Day is used to determine if a possible id
* letter needs to be removed for all days but current data. Return an empty oject if id set in
* config does not match any key (city) in XML.
*
* @param {String} xml XML content fetched from RBB
* @param {Number} day Day of given XML
* @return {Object} XML data as JSON object, may be empty
*/
parseData: function(xml, day) {
// XML parse options
const options = {
attributeNamePrefix: '',
ignoreAttributes: false
};
// Parse XML data to json
const parsedData = xmlParser.parse(xml, options);
if (parsedData === undefined || parsedData.data === undefined) {
Logger.warn(`Error while parsing XML data. XML: "${xml}"`);
return {};
}
// All citys are in the same XML file for one day, loop through them to find
// the city data with correct ID
const citys = parsedData.data.city;
for (const city of Object.values(citys)) {
// Only current data has possible letters in IDs, remove for forecast
let id = this.config.id;
if (day !== 0) {
id = this.config.id.replace('a', '');
}
// Find correct city
if (city.id === id) {
return city;
}
}
// No city found
Logger.warn(`No city found with id "${this.config.id}".`);
return {};
}
});