iobroker.weathersense 1.0.3 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -1
- package/admin/i18n/de/translations.json +1 -1
- package/admin/i18n/en/translations.json +1 -1
- package/admin/i18n/es/translations.json +1 -1
- package/admin/i18n/fr/translations.json +1 -1
- package/admin/i18n/it/translations.json +1 -1
- package/admin/i18n/nl/translations.json +1 -1
- package/admin/i18n/pl/translations.json +1 -1
- package/admin/i18n/pt/translations.json +1 -1
- package/admin/i18n/ru/translations.json +1 -1
- package/admin/i18n/uk/translations.json +1 -1
- package/admin/i18n/zh-cn/translations.json +1 -1
- package/admin/jsonConfig.json +115 -115
- package/io-package.json +30 -4
- package/main.js +371 -291
- package/package.json +9 -18
- package/lib/adapter-config.d.ts +0 -19
package/main.js
CHANGED
|
@@ -1,30 +1,29 @@
|
|
|
1
|
-
|
|
1
|
+
'use strict';
|
|
2
2
|
|
|
3
3
|
/*
|
|
4
4
|
* Created with @iobroker/create-adapter v2.6.2
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
const utils = require(
|
|
8
|
-
const axios = require(
|
|
9
|
-
const mqtt = require(
|
|
10
|
-
const fs = require(
|
|
11
|
-
const crypto = require(
|
|
12
|
-
const https = require(
|
|
13
|
-
const path = require(
|
|
7
|
+
const utils = require('@iobroker/adapter-core');
|
|
8
|
+
const axios = require('axios');
|
|
9
|
+
const mqtt = require('mqtt');
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const crypto = require('crypto');
|
|
12
|
+
const https = require('https');
|
|
13
|
+
const path = require('path');
|
|
14
14
|
|
|
15
|
-
process.env.NODE_TLS_REJECT_UNAUTHORIZED =
|
|
15
|
+
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
|
|
16
16
|
|
|
17
17
|
axios.defaults.timeout = 2000;
|
|
18
18
|
|
|
19
19
|
class WeatherSense extends utils.Adapter {
|
|
20
|
-
|
|
21
20
|
constructor(options) {
|
|
22
21
|
super({
|
|
23
22
|
...options,
|
|
24
|
-
name:
|
|
23
|
+
name: 'weathersense',
|
|
25
24
|
});
|
|
26
|
-
this.on(
|
|
27
|
-
this.on(
|
|
25
|
+
this.on('ready', this.onReady.bind(this));
|
|
26
|
+
this.on('unload', this.onUnload.bind(this));
|
|
28
27
|
}
|
|
29
28
|
|
|
30
29
|
async onReady() {
|
|
@@ -44,42 +43,46 @@ class WeatherSense extends utils.Adapter {
|
|
|
44
43
|
if (Number(sensor_in)) {
|
|
45
44
|
sensor_id = parseInt(sensor_in);
|
|
46
45
|
if (sensor_id < 1 || sensor_id > 20) {
|
|
47
|
-
this.log.error(
|
|
48
|
-
this.terminate ? this.terminate(
|
|
46
|
+
this.log.error('Sensor ID has no value between 1 and 20');
|
|
47
|
+
this.terminate ? this.terminate('Sensor ID has no value between 1 and 20', 0) : process.exit(0);
|
|
49
48
|
return;
|
|
50
49
|
}
|
|
51
50
|
} else {
|
|
52
|
-
this.log.error(
|
|
53
|
-
this.terminate ? this.terminate(
|
|
51
|
+
this.log.error('Sensor ID has no valid value');
|
|
52
|
+
this.terminate ? this.terminate('Sensor ID has no valid value', 0) : process.exit(0);
|
|
54
53
|
return;
|
|
55
54
|
}
|
|
56
|
-
this.log.debug(
|
|
55
|
+
this.log.debug(`Sensor ID is ${sensor_id}`);
|
|
57
56
|
|
|
58
57
|
if (username.trim().length === 0 || passwort.trim().length === 0) {
|
|
59
|
-
this.log.error(
|
|
60
|
-
this.terminate
|
|
58
|
+
this.log.error('User email and/or user password empty - please check instance configuration');
|
|
59
|
+
this.terminate
|
|
60
|
+
? this.terminate('User email and/or user password empty - please check instance configuration', 0)
|
|
61
|
+
: process.exit(0);
|
|
61
62
|
return;
|
|
62
63
|
}
|
|
63
64
|
|
|
64
65
|
let client = null;
|
|
65
66
|
if (mqtt_active) {
|
|
66
|
-
if (broker_address.trim().length === 0 || broker_address ==
|
|
67
|
-
this.log.error(
|
|
68
|
-
this.terminate
|
|
67
|
+
if (broker_address.trim().length === 0 || broker_address == '0.0.0.0') {
|
|
68
|
+
this.log.error('MQTT IP address is empty - please check instance configuration');
|
|
69
|
+
this.terminate
|
|
70
|
+
? this.terminate('MQTT IP address is empty - please check instance configuration', 0)
|
|
71
|
+
: process.exit(0);
|
|
69
72
|
return;
|
|
70
73
|
}
|
|
71
74
|
client = mqtt.connect(`mqtt://${broker_address}:${mqtt_port}`, {
|
|
72
75
|
connectTimeout: 4000,
|
|
73
76
|
username: mqtt_user,
|
|
74
|
-
password: mqtt_pass
|
|
77
|
+
password: mqtt_pass,
|
|
75
78
|
});
|
|
76
79
|
}
|
|
77
80
|
|
|
78
81
|
try {
|
|
79
82
|
const instObj = await this.getForeignObjectAsync(`system.adapter.${this.namespace}`);
|
|
80
|
-
if (instObj && instObj.common && instObj.common.schedule && instObj.common.schedule ===
|
|
81
|
-
instObj.common.schedule = `*/${
|
|
82
|
-
this.log.info(`Default schedule found and adjusted to spread calls better over
|
|
83
|
+
if (instObj && instObj.common && instObj.common.schedule && instObj.common.schedule === '*/10 * * * *') {
|
|
84
|
+
instObj.common.schedule = `*/${Math.floor(Math.random() * 3) + 6} * * * *`;
|
|
85
|
+
this.log.info(`Default schedule found and adjusted to spread calls better over 6-9 minutes!`);
|
|
83
86
|
await this.setForeignObjectAsync(`system.adapter.${this.namespace}`, instObj);
|
|
84
87
|
this.terminate ? this.terminate() : process.exit(0);
|
|
85
88
|
return;
|
|
@@ -88,162 +91,263 @@ class WeatherSense extends utils.Adapter {
|
|
|
88
91
|
this.log.error(`Could not check or adjust the schedule: ${err.message}`);
|
|
89
92
|
}
|
|
90
93
|
|
|
91
|
-
this.log.debug(
|
|
92
|
-
this.log.debug(
|
|
94
|
+
this.log.debug(`MQTT active: ${mqtt_active}`);
|
|
95
|
+
this.log.debug(`MQTT port: ${mqtt_port}`);
|
|
93
96
|
|
|
94
|
-
|
|
95
|
-
const forecastChannelId = `${sensor_id}.forecast`;
|
|
97
|
+
const deviceId = `${this.namespace}.${sensor_id}`;
|
|
96
98
|
|
|
97
99
|
try {
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
|
|
100
|
+
const mainResult = await this.main(
|
|
101
|
+
client,
|
|
102
|
+
username,
|
|
103
|
+
passwort,
|
|
104
|
+
mqtt_active,
|
|
105
|
+
sensor_id,
|
|
106
|
+
storeJson,
|
|
107
|
+
storeDir,
|
|
108
|
+
celsius,
|
|
109
|
+
`${deviceId}.forecast`,
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
const dataReceived = mainResult.dataReceived;
|
|
113
|
+
const devdata = mainResult.devdata;
|
|
114
|
+
|
|
115
|
+
const systemStateId = `${deviceId}.DataReceived`;
|
|
101
116
|
await this.setObjectNotExistsAsync(systemStateId, {
|
|
102
|
-
type:
|
|
117
|
+
type: 'state',
|
|
103
118
|
common: {
|
|
104
|
-
name:
|
|
105
|
-
type:
|
|
106
|
-
role:
|
|
119
|
+
name: 'Data successfully received',
|
|
120
|
+
type: 'boolean',
|
|
121
|
+
role: 'indicator',
|
|
107
122
|
read: true,
|
|
108
|
-
write: false
|
|
123
|
+
write: false,
|
|
109
124
|
},
|
|
110
125
|
native: {},
|
|
111
126
|
});
|
|
112
127
|
|
|
113
128
|
if (dataReceived === true) {
|
|
114
|
-
|
|
115
129
|
await this.setStateAsync(systemStateId, { val: true, ack: true });
|
|
116
130
|
|
|
117
|
-
|
|
118
|
-
const
|
|
119
|
-
await this.setObjectNotExistsAsync(devDataChannelId, {
|
|
120
|
-
type: "channel",
|
|
121
|
-
common: { name: "DevData" },
|
|
122
|
-
native: {},
|
|
123
|
-
});
|
|
131
|
+
const devDataChannelId = `${deviceId}.devData`;
|
|
132
|
+
const tempUnit = celsius ? '°C' : '°F';
|
|
124
133
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
134
|
+
// Alle Werte aus devdata (außer content)
|
|
135
|
+
for (const [key, value] of Object.entries(devdata)) {
|
|
136
|
+
if (key === 'content') {
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
if (value !== null && value !== undefined) {
|
|
140
|
+
const id = `${devDataChannelId}.${key}`;
|
|
141
|
+
await this.setObjectNotExistsAsync(id, {
|
|
142
|
+
type: typeof value === 'number' ? 'state' : 'state',
|
|
143
|
+
common: {
|
|
144
|
+
name: key,
|
|
145
|
+
type: typeof value,
|
|
146
|
+
role: 'value',
|
|
147
|
+
unit: '',
|
|
148
|
+
read: true,
|
|
149
|
+
write: false,
|
|
150
|
+
},
|
|
151
|
+
native: {},
|
|
152
|
+
});
|
|
153
|
+
await this.setStateAsync(id, { val: value, ack: true });
|
|
154
|
+
}
|
|
155
|
+
}
|
|
130
156
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
name: item.id,
|
|
150
|
-
type: item.type,
|
|
151
|
-
role: item.role,
|
|
152
|
-
unit: item.unit,
|
|
153
|
-
read: true,
|
|
154
|
-
write: false,
|
|
155
|
-
},
|
|
156
|
-
native: {},
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
const val = this.contentDevData ? this.contentDevData[item.id] : null;
|
|
160
|
-
if (val != null) {
|
|
161
|
-
await this.setStateAsync(id, { val: val, ack: true });
|
|
157
|
+
// Alle Werte aus devdata.content
|
|
158
|
+
const content = devdata.content || {};
|
|
159
|
+
for (const [key, value] of Object.entries(content)) {
|
|
160
|
+
if (value !== null && value !== undefined && key !== 'sensorDatas') {
|
|
161
|
+
const id = `${devDataChannelId}.${key}`;
|
|
162
|
+
await this.setObjectNotExistsAsync(id, {
|
|
163
|
+
type: typeof value === 'number' ? 'state' : 'state',
|
|
164
|
+
common: {
|
|
165
|
+
name: key,
|
|
166
|
+
type: typeof value,
|
|
167
|
+
role: 'value',
|
|
168
|
+
unit: '',
|
|
169
|
+
read: true,
|
|
170
|
+
write: false,
|
|
171
|
+
},
|
|
172
|
+
native: {},
|
|
173
|
+
});
|
|
174
|
+
await this.setStateAsync(id, { val: value, ack: true });
|
|
162
175
|
}
|
|
163
176
|
}
|
|
164
177
|
|
|
165
|
-
//
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
178
|
+
// Alle Sensoren aus devdata.content.sensorDatas
|
|
179
|
+
const sensor_data = content.sensorDatas || [];
|
|
180
|
+
for (const s of sensor_data) {
|
|
181
|
+
const type_ = s.type;
|
|
182
|
+
const channel = s.channel;
|
|
183
|
+
const cur_val = s.curVal;
|
|
184
|
+
const high_val = s.hihgVal;
|
|
185
|
+
const low_val = s.lowVal;
|
|
169
186
|
|
|
170
|
-
|
|
187
|
+
let prefix = '';
|
|
171
188
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
unit: "",
|
|
178
|
-
read: true,
|
|
179
|
-
write: false,
|
|
180
|
-
};
|
|
181
|
-
|
|
182
|
-
if (key.includes("temp")) {
|
|
183
|
-
common.role = "value.temperature";
|
|
184
|
-
common.unit = tempUnit;
|
|
185
|
-
} else if (key.includes("humidity")) {
|
|
186
|
-
common.role = "value.humidity";
|
|
187
|
-
common.unit = "%";
|
|
188
|
-
} else if (key.includes("pressure")) {
|
|
189
|
-
common.role = "value.pressure";
|
|
190
|
-
common.unit = "hPa";
|
|
189
|
+
if (type_ === 1) {
|
|
190
|
+
prefix = '-Temp';
|
|
191
|
+
}
|
|
192
|
+
if (type_ === 2) {
|
|
193
|
+
prefix = '-Hum';
|
|
191
194
|
}
|
|
192
195
|
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
196
|
+
const key = `Type${type_}-Channel${channel}${prefix}`;
|
|
197
|
+
const base = `${devDataChannelId}.${key}`;
|
|
198
|
+
|
|
199
|
+
// current
|
|
200
|
+
if (cur_val !== null && cur_val !== undefined && cur_val !== 65535 && cur_val !== 255) {
|
|
201
|
+
const id = `${base}.current`;
|
|
202
|
+
await this.setObjectNotExistsAsync(id, {
|
|
203
|
+
type: 'state',
|
|
204
|
+
common: {
|
|
205
|
+
name: 'current',
|
|
206
|
+
type: 'number',
|
|
207
|
+
role:
|
|
208
|
+
prefix === '-Temp'
|
|
209
|
+
? 'value.temperature'
|
|
210
|
+
: prefix === '-Hum'
|
|
211
|
+
? 'value.humidity'
|
|
212
|
+
: 'value',
|
|
213
|
+
unit: prefix === '-Temp' ? tempUnit : prefix === '-Hum' ? '%' : '',
|
|
214
|
+
read: true,
|
|
215
|
+
write: false,
|
|
216
|
+
},
|
|
217
|
+
native: {},
|
|
218
|
+
});
|
|
219
|
+
await this.setStateAsync(id, { val: this.c_f_berechnen(cur_val, prefix, celsius), ack: true });
|
|
220
|
+
}
|
|
221
|
+
// high
|
|
222
|
+
if (high_val !== null && high_val !== undefined && high_val !== 65535 && high_val !== 255) {
|
|
223
|
+
const id = `${base}.high`;
|
|
224
|
+
await this.setObjectNotExistsAsync(id, {
|
|
225
|
+
type: 'state',
|
|
226
|
+
common: {
|
|
227
|
+
name: 'high',
|
|
228
|
+
type: 'number',
|
|
229
|
+
role:
|
|
230
|
+
prefix === '-Temp'
|
|
231
|
+
? 'value.temperature'
|
|
232
|
+
: prefix === '-Hum'
|
|
233
|
+
? 'value.humidity'
|
|
234
|
+
: 'value',
|
|
235
|
+
unit: prefix === '-Temp' ? tempUnit : prefix === '-Hum' ? '%' : '',
|
|
236
|
+
read: true,
|
|
237
|
+
write: false,
|
|
238
|
+
},
|
|
239
|
+
native: {},
|
|
240
|
+
});
|
|
241
|
+
await this.setStateAsync(id, { val: this.c_f_berechnen(high_val, prefix, celsius), ack: true });
|
|
242
|
+
}
|
|
243
|
+
// low
|
|
244
|
+
if (low_val !== null && low_val !== undefined && low_val !== 65535 && low_val !== 255) {
|
|
245
|
+
const id = `${base}.low`;
|
|
246
|
+
await this.setObjectNotExistsAsync(id, {
|
|
247
|
+
type: 'state',
|
|
248
|
+
common: {
|
|
249
|
+
name: 'low',
|
|
250
|
+
type: 'number',
|
|
251
|
+
role:
|
|
252
|
+
prefix === '-Temp'
|
|
253
|
+
? 'value.temperature'
|
|
254
|
+
: prefix === '-Hum'
|
|
255
|
+
? 'value.humidity'
|
|
256
|
+
: 'value',
|
|
257
|
+
unit: prefix === '-Temp' ? tempUnit : prefix === '-Hum' ? '%' : '',
|
|
258
|
+
read: true,
|
|
259
|
+
write: false,
|
|
260
|
+
},
|
|
261
|
+
native: {},
|
|
262
|
+
});
|
|
263
|
+
await this.setStateAsync(id, { val: this.c_f_berechnen(low_val, prefix, celsius), ack: true });
|
|
264
|
+
}
|
|
200
265
|
|
|
201
|
-
|
|
266
|
+
// dev*-Keys im Sensorobjekt
|
|
267
|
+
for (const [k, v] of Object.entries(s)) {
|
|
268
|
+
if (
|
|
269
|
+
k.startsWith('dev') &&
|
|
270
|
+
v !== null &&
|
|
271
|
+
v !== undefined &&
|
|
272
|
+
!(typeof v === 'object' && Object.keys(v).length === 0)
|
|
273
|
+
) {
|
|
274
|
+
const id = `${base}.${k}`;
|
|
275
|
+
await this.setObjectNotExistsAsync(id, {
|
|
276
|
+
type: 'state',
|
|
277
|
+
common: {
|
|
278
|
+
name: k,
|
|
279
|
+
type: typeof v,
|
|
280
|
+
role: 'value',
|
|
281
|
+
unit: '',
|
|
282
|
+
read: true,
|
|
283
|
+
write: false,
|
|
284
|
+
},
|
|
285
|
+
native: {},
|
|
286
|
+
});
|
|
287
|
+
await this.setStateAsync(id, { val: v, ack: true });
|
|
288
|
+
}
|
|
289
|
+
}
|
|
202
290
|
}
|
|
203
291
|
} else {
|
|
204
|
-
this.log.error(
|
|
292
|
+
this.log.error('Error loading data in main()');
|
|
205
293
|
await this.setStateAsync(systemStateId, { val: false, ack: true });
|
|
206
294
|
}
|
|
207
295
|
} catch (error) {
|
|
208
|
-
this.log.error(
|
|
296
|
+
this.log.error(`Unexpected error in onReady(): ${error.message}`);
|
|
209
297
|
} finally {
|
|
210
298
|
if (client) {
|
|
211
299
|
client.end();
|
|
212
300
|
}
|
|
213
|
-
this.terminate
|
|
301
|
+
this.terminate
|
|
302
|
+
? this.terminate('Everything done. Going to terminate till next schedule', 0)
|
|
303
|
+
: process.exit(0);
|
|
214
304
|
}
|
|
215
305
|
}
|
|
216
306
|
|
|
307
|
+
// MQTT senden
|
|
217
308
|
async sendMqtt(sensor_id, mqtt_active, client, topic, wert) {
|
|
218
309
|
if (mqtt_active) {
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
310
|
+
if (typeof wert !== 'string') {
|
|
311
|
+
wert = wert !== null && wert !== undefined ? wert.toString() : '';
|
|
312
|
+
}
|
|
313
|
+
client.publish(`WEATHERSENSE/${sensor_id.toString()}/${topic}`, wert);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// °F nach °C umwandeln, falls notwendig
|
|
318
|
+
c_f_berechnen(temp, prefix, celsius) {
|
|
319
|
+
if (celsius && prefix === '-Temp') {
|
|
320
|
+
if (temp !== null && temp !== undefined) {
|
|
321
|
+
temp = ((temp - 32) / 1.8).toFixed(1);
|
|
222
322
|
}
|
|
223
|
-
client.publish("WEATHERSENSE/" + sensor_id.toString() + "/" + topic, wert);
|
|
224
323
|
}
|
|
324
|
+
return temp;
|
|
225
325
|
}
|
|
226
326
|
|
|
327
|
+
// Forecast Datenpunkte erstellen und schreiben
|
|
227
328
|
async createOrUpdateForecastDPs(forecastChannelId, forecasts, celsius) {
|
|
228
|
-
if (!forecasts || !forecastChannelId)
|
|
329
|
+
if (!forecasts || !forecastChannelId) {
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
229
332
|
|
|
230
|
-
// Struktur der Forecast-Items pro Tag
|
|
231
333
|
const forecastItems = [
|
|
232
|
-
{ id:
|
|
233
|
-
{ id:
|
|
234
|
-
{ id:
|
|
235
|
-
{ id:
|
|
236
|
-
{ id:
|
|
334
|
+
{ id: 'day', type: 'string', role: 'value', unit: '' },
|
|
335
|
+
{ id: 'date', type: 'string', role: 'value', unit: '' },
|
|
336
|
+
{ id: 'high', type: 'number', role: 'value.temperature', unit: celsius ? '°C' : '°F' },
|
|
337
|
+
{ id: 'low', type: 'number', role: 'value.temperature', unit: celsius ? '°C' : '°F' },
|
|
338
|
+
{ id: 'text', type: 'string', role: 'text', unit: '' },
|
|
237
339
|
];
|
|
238
340
|
|
|
239
341
|
for (let i = 0; i < forecasts.length; i++) {
|
|
240
342
|
const forecast = forecasts[i];
|
|
241
|
-
if (!forecast)
|
|
343
|
+
if (!forecast) {
|
|
344
|
+
continue;
|
|
345
|
+
}
|
|
242
346
|
|
|
243
347
|
for (const item of forecastItems) {
|
|
244
|
-
const id = `${forecastChannelId}
|
|
348
|
+
const id = `${forecastChannelId}.day_${i}.${item.id}`;
|
|
245
349
|
await this.setObjectNotExistsAsync(id, {
|
|
246
|
-
type:
|
|
350
|
+
type: 'state',
|
|
247
351
|
common: {
|
|
248
352
|
name: item.id,
|
|
249
353
|
type: item.type,
|
|
@@ -256,7 +360,7 @@ class WeatherSense extends utils.Adapter {
|
|
|
256
360
|
});
|
|
257
361
|
|
|
258
362
|
let val = forecast[item.id];
|
|
259
|
-
if ((item.id ===
|
|
363
|
+
if ((item.id === 'high' || item.id === 'low') && typeof val === 'number') {
|
|
260
364
|
if (celsius) {
|
|
261
365
|
val = Number(((val - 32) / 1.8).toFixed(1));
|
|
262
366
|
} else {
|
|
@@ -271,21 +375,26 @@ class WeatherSense extends utils.Adapter {
|
|
|
271
375
|
}
|
|
272
376
|
}
|
|
273
377
|
|
|
378
|
+
// Forecasts per MQTT senden
|
|
274
379
|
async sendForecasts(client, forecasts, celsius, sensor_id) {
|
|
275
|
-
if (!client || !forecasts)
|
|
380
|
+
if (!client || !forecasts) {
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
276
383
|
|
|
277
384
|
for (let i = 0; i < forecasts.length; i++) {
|
|
278
385
|
const forecast = forecasts[i];
|
|
279
|
-
if (!forecast)
|
|
386
|
+
if (!forecast) {
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
280
389
|
|
|
281
|
-
await this.sendMqtt(sensor_id, true, client, `forecast
|
|
282
|
-
await this.sendMqtt(sensor_id, true, client, `forecast
|
|
390
|
+
await this.sendMqtt(sensor_id, true, client, `forecast/day_${i}/day`, forecast.day || '');
|
|
391
|
+
await this.sendMqtt(sensor_id, true, client, `forecast/day_${i}/date`, forecast.date || '');
|
|
283
392
|
|
|
284
393
|
let tempHigh = forecast.high;
|
|
285
394
|
let tempLow = forecast.low;
|
|
286
395
|
|
|
287
|
-
if (celsius && typeof tempHigh ===
|
|
288
|
-
tempHigh = Number(((tempHigh - 32) / 1.8).toFixed(1));
|
|
396
|
+
if (celsius && typeof tempHigh === 'number' && typeof tempLow === 'number') {
|
|
397
|
+
tempHigh = Number(((tempHigh - 32) / 1.8).toFixed(1)); // Zahl, keine Zeichenkette
|
|
289
398
|
tempLow = Number(((tempLow - 32) / 1.8).toFixed(1));
|
|
290
399
|
} else {
|
|
291
400
|
// Wenn kein Celsius oder kein Zahlentyp, trotzdem als Zahl (sofern möglich), sonst 0 als Fallback
|
|
@@ -293,22 +402,24 @@ class WeatherSense extends utils.Adapter {
|
|
|
293
402
|
tempLow = tempLow != null ? Number(tempLow) : 0;
|
|
294
403
|
}
|
|
295
404
|
|
|
296
|
-
await this.sendMqtt(sensor_id, true, client, `forecast
|
|
297
|
-
await this.sendMqtt(sensor_id, true, client, `forecast
|
|
298
|
-
await this.sendMqtt(sensor_id, true, client, `forecast
|
|
405
|
+
await this.sendMqtt(sensor_id, true, client, `forecast/day_${i}/high`, tempHigh);
|
|
406
|
+
await this.sendMqtt(sensor_id, true, client, `forecast/day_${i}/low`, tempLow);
|
|
407
|
+
await this.sendMqtt(sensor_id, true, client, `forecast/day_${i}/text`, forecast.text || '');
|
|
299
408
|
}
|
|
300
409
|
}
|
|
301
410
|
|
|
411
|
+
// Alte MQTT Datenpunkte auf Subscriber löschen
|
|
302
412
|
async clearOldForecasts(sensor_id, client, maxDays = 6) {
|
|
303
413
|
for (let i = 0; i < maxDays; i++) {
|
|
304
|
-
for (const key of [
|
|
305
|
-
await this.sendMqtt(sensor_id, true, client, `forecast
|
|
414
|
+
for (const key of ['day', 'date', 'high', 'low', 'text']) {
|
|
415
|
+
await this.sendMqtt(sensor_id, true, client, `forecast/day_${i}/${key}`, '');
|
|
306
416
|
}
|
|
307
417
|
}
|
|
308
418
|
}
|
|
309
419
|
|
|
310
|
-
|
|
311
|
-
|
|
420
|
+
// Debugausgabe formatieren
|
|
421
|
+
printAllKeys(d, prefix = '') {
|
|
422
|
+
if (typeof d === 'object' && d !== null && !Array.isArray(d)) {
|
|
312
423
|
for (const [k, v] of Object.entries(d)) {
|
|
313
424
|
this.printAllKeys(v, `${prefix}${k}/`);
|
|
314
425
|
}
|
|
@@ -321,266 +432,231 @@ class WeatherSense extends utils.Adapter {
|
|
|
321
432
|
}
|
|
322
433
|
}
|
|
323
434
|
|
|
324
|
-
findValue(sensor_data, type, channel) {
|
|
325
|
-
const entry = sensor_data.find(item => item.type === type && item.channel === channel);
|
|
326
|
-
return entry ? entry.curVal : null;
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
|
|
330
435
|
// Funktion zum Erzeugen des PW-Hashes
|
|
331
436
|
hashPassword(pw) {
|
|
332
|
-
const key = Buffer.from(
|
|
437
|
+
const key = Buffer.from('ZW1heEBwd2QxMjM=', 'base64').toString('utf8');
|
|
333
438
|
const combined = pw + key;
|
|
334
|
-
return crypto.createHash(
|
|
439
|
+
return crypto.createHash('md5').update(combined, 'utf8').digest('hex').toUpperCase();
|
|
335
440
|
}
|
|
336
441
|
|
|
337
442
|
// Login-Funktion
|
|
338
443
|
async login(USERNAME, PASSWORD) {
|
|
339
|
-
const LOGIN_URL =
|
|
444
|
+
const LOGIN_URL = 'https://emaxlife.net/V1.0/account/login';
|
|
340
445
|
const hashed_pw = this.hashPassword(PASSWORD);
|
|
341
446
|
|
|
342
447
|
const headers = {
|
|
343
|
-
|
|
344
|
-
|
|
448
|
+
'Content-Type': 'application/json; charset=utf-8',
|
|
449
|
+
'User-Agent': 'okhttp/3.14.9',
|
|
345
450
|
};
|
|
346
451
|
|
|
347
452
|
const payload = {
|
|
348
453
|
email: USERNAME,
|
|
349
|
-
pwd: hashed_pw
|
|
454
|
+
pwd: hashed_pw,
|
|
350
455
|
};
|
|
351
456
|
|
|
352
457
|
try {
|
|
353
458
|
const response = await axios.post(LOGIN_URL, payload, { headers });
|
|
354
459
|
|
|
355
|
-
this.log.debug(
|
|
356
|
-
this.log.debug(
|
|
460
|
+
this.log.debug('Status code:', response.status);
|
|
461
|
+
this.log.debug('Response:', response.data);
|
|
357
462
|
|
|
358
463
|
const data = response.data;
|
|
359
464
|
|
|
360
465
|
if (response.status === 200) {
|
|
361
466
|
if (data.status === 0 && data.content) {
|
|
362
467
|
const token = data.content.token;
|
|
363
|
-
this.log.debug(
|
|
468
|
+
this.log.debug(`Login successful. Token: ${token.substring(0, 20)}...`);
|
|
364
469
|
return token;
|
|
365
|
-
} else {
|
|
366
|
-
this.log.error("Login failed:", data.message);
|
|
367
470
|
}
|
|
471
|
+
this.log.error('Login failed:', data.message);
|
|
368
472
|
} else {
|
|
369
|
-
this.log.error(
|
|
473
|
+
this.log.error('Server error');
|
|
370
474
|
}
|
|
371
475
|
} catch (error) {
|
|
372
|
-
this.log.error(
|
|
476
|
+
this.log.error('Error during login:', error.message);
|
|
373
477
|
}
|
|
374
478
|
|
|
375
479
|
return null;
|
|
376
480
|
}
|
|
377
481
|
|
|
482
|
+
// Realtime Daten holen
|
|
378
483
|
async devData(token) {
|
|
379
|
-
|
|
380
|
-
this.log.debug("getRealtime data...");
|
|
484
|
+
this.log.debug('getRealtime data...');
|
|
381
485
|
|
|
382
|
-
const url =
|
|
486
|
+
const url = 'https://emaxlife.net/V1.0/weather/devData/getRealtime';
|
|
383
487
|
const headers = {
|
|
384
|
-
|
|
385
|
-
|
|
488
|
+
emaxtoken: token,
|
|
489
|
+
'Content-Type': 'application/json',
|
|
386
490
|
};
|
|
387
491
|
|
|
388
492
|
try {
|
|
389
493
|
const response = await axios.get(url, {
|
|
390
494
|
headers,
|
|
391
495
|
timeout: 5000,
|
|
392
|
-
httpsAgent: new (require(
|
|
496
|
+
httpsAgent: new (require('https').Agent)({ rejectUnauthorized: false }),
|
|
393
497
|
});
|
|
394
498
|
|
|
395
499
|
if (response.status === 200) {
|
|
396
|
-
this.log.debug(
|
|
500
|
+
this.log.debug('Data was received');
|
|
397
501
|
return response.data;
|
|
398
|
-
} else {
|
|
399
|
-
this.log.error(`devData > Status Code: ${response.status}`);
|
|
400
|
-
return "error";
|
|
401
502
|
}
|
|
503
|
+
this.log.error(`devData > Status Code: ${response.status}`);
|
|
504
|
+
return 'error';
|
|
402
505
|
} catch (error) {
|
|
403
506
|
if (error.response) {
|
|
404
507
|
this.log.error(`devData > Status Code: ${error.response.status}`);
|
|
405
508
|
} else {
|
|
406
509
|
this.log.error(`Error during request: ${error.message}`);
|
|
407
510
|
}
|
|
408
|
-
return
|
|
511
|
+
return 'error';
|
|
409
512
|
}
|
|
410
513
|
}
|
|
411
514
|
|
|
515
|
+
// Forecast Daten holen
|
|
412
516
|
async foreCast(token) {
|
|
413
|
-
|
|
414
|
-
this.log.debug("getForecast data...");
|
|
517
|
+
this.log.debug('getForecast data...');
|
|
415
518
|
|
|
416
|
-
const url =
|
|
519
|
+
const url = 'https://emaxlife.net/V1.0/weather/netData/getForecast';
|
|
417
520
|
const headers = {
|
|
418
|
-
|
|
419
|
-
|
|
521
|
+
emaxtoken: token,
|
|
522
|
+
'Content-Type': 'application/json',
|
|
420
523
|
};
|
|
421
524
|
|
|
422
525
|
try {
|
|
423
526
|
const response = await axios.get(url, {
|
|
424
527
|
headers,
|
|
425
528
|
timeout: 5000,
|
|
426
|
-
httpsAgent: new https.Agent({ rejectUnauthorized: false }) // entspricht verify=False
|
|
529
|
+
httpsAgent: new https.Agent({ rejectUnauthorized: false }), // entspricht verify=False
|
|
427
530
|
});
|
|
428
531
|
|
|
429
532
|
if (response.status === 200) {
|
|
430
|
-
this.log.debug(
|
|
533
|
+
this.log.debug('Data was received');
|
|
431
534
|
return response.data;
|
|
432
|
-
} else {
|
|
433
|
-
this.log.error(`foreCast > Status Code: ${response.status}`);
|
|
434
|
-
return "error";
|
|
435
535
|
}
|
|
536
|
+
this.log.error(`foreCast > Status Code: ${response.status}`);
|
|
537
|
+
return 'error';
|
|
436
538
|
} catch (error) {
|
|
437
539
|
if (error.response) {
|
|
438
540
|
this.log.error(`foreCast > Status Code: ${error.response.status}`);
|
|
439
541
|
} else {
|
|
440
542
|
this.log.error(`Error during request: ${error.message}`);
|
|
441
543
|
}
|
|
442
|
-
return
|
|
544
|
+
return 'error';
|
|
443
545
|
}
|
|
444
546
|
}
|
|
445
547
|
|
|
446
548
|
async main(client, username, passwort, mqtt_active, sensor_id, storeJson, storeDir, celsius, forecastChannelId) {
|
|
447
549
|
const token = await this.login(username, passwort);
|
|
448
550
|
if (!token) {
|
|
449
|
-
this.log.error(
|
|
551
|
+
this.log.error('No token received');
|
|
450
552
|
if (mqtt_active) {
|
|
451
|
-
await this.sendMqtt(sensor_id, mqtt_active, client,
|
|
553
|
+
await this.sendMqtt(sensor_id, mqtt_active, client, 'dataReceived', 'false');
|
|
452
554
|
client.end();
|
|
453
555
|
}
|
|
454
556
|
return false;
|
|
455
557
|
}
|
|
456
558
|
const devdata = await this.devData(token);
|
|
457
559
|
const forecast = await this.foreCast(token);
|
|
458
|
-
if (devdata ===
|
|
459
|
-
this.log.error(
|
|
560
|
+
if (devdata === 'error' || forecast === 'error') {
|
|
561
|
+
this.log.error('No data received');
|
|
460
562
|
if (mqtt_active) {
|
|
461
|
-
await this.sendMqtt(sensor_id, mqtt_active, client,
|
|
563
|
+
await this.sendMqtt(sensor_id, mqtt_active, client, 'dataReceived', 'false');
|
|
462
564
|
client.end();
|
|
463
565
|
}
|
|
464
|
-
return false;
|
|
566
|
+
return { dataReceived: false };
|
|
465
567
|
}
|
|
466
568
|
|
|
467
569
|
if (storeJson) {
|
|
468
|
-
this.log.debug(
|
|
570
|
+
this.log.debug(`Save devData.json to ${storeDir}`);
|
|
469
571
|
const json_object = JSON.stringify(devdata, null, 4);
|
|
470
|
-
fs.writeFileSync(path.join(storeDir,
|
|
572
|
+
fs.writeFileSync(path.join(storeDir, 'devData.json'), json_object, 'utf-8');
|
|
471
573
|
}
|
|
472
574
|
|
|
473
|
-
this.log.debug(
|
|
575
|
+
this.log.debug('devData JSON:');
|
|
474
576
|
this.printAllKeys(devdata);
|
|
475
577
|
|
|
476
|
-
const content = devdata?.content || {};
|
|
477
|
-
const sensor_data = content.sensorDatas || [];
|
|
478
|
-
|
|
479
|
-
const luftdruck = content.atmos;
|
|
480
|
-
let temp_innen = this.findValue(sensor_data, 1, 0);
|
|
481
|
-
const feuchte_innen = this.findValue(sensor_data, 2, 0);
|
|
482
|
-
let temp_aussen = this.findValue(sensor_data, 1, 2);
|
|
483
|
-
const feuchte_aussen = this.findValue(sensor_data, 2, 2);
|
|
484
|
-
|
|
485
|
-
const skipCombinations = new Set(["1_0", "1_2", "2_0", "2_2"]);
|
|
486
|
-
|
|
487
|
-
if (celsius) {
|
|
488
|
-
if (temp_innen != null) temp_innen = ((temp_innen - 32) / 1.8).toFixed(1);
|
|
489
|
-
if (temp_aussen != null) temp_aussen = ((temp_aussen - 32) / 1.8).toFixed(1);
|
|
490
|
-
}
|
|
491
|
-
|
|
492
578
|
if (mqtt_active) {
|
|
493
|
-
const
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
if (content.devTime) this.sendMqtt(sensor_id, mqtt_active, client, "devData/devtime", content.devTime);
|
|
497
|
-
if (content.updateTime) this.sendMqtt(sensor_id, mqtt_active, client, "devData/updateTime", content.updateTime);
|
|
498
|
-
if (content.deviceMac) this.sendMqtt(sensor_id, mqtt_active, client, "devData/deviceMac", content.deviceMac);
|
|
499
|
-
if (content.devTimezone != null) this.sendMqtt(sensor_id, mqtt_active, client, "devData/devTimezone", content.devTimezone);
|
|
500
|
-
if (content.wirelessStatus != null) this.sendMqtt(sensor_id, mqtt_active, client, "devData/wirelessStatus", content.wirelessStatus);
|
|
501
|
-
if (content.powerStatus != null) this.sendMqtt(sensor_id, mqtt_active, client, "devData/powerStatus", content.powerStatus);
|
|
502
|
-
if (content.weatherStatus != null) this.sendMqtt(sensor_id, mqtt_active, client, "devData/weatherStatus", content.weatherStatus);
|
|
503
|
-
if (luftdruck != null) this.sendMqtt(sensor_id, mqtt_active, client, "devData/atmospheric_pressure", luftdruck);
|
|
504
|
-
if (temp_innen != null) this.sendMqtt(sensor_id, mqtt_active, client, "devData/indoor_temp", temp_innen);
|
|
505
|
-
if (feuchte_innen != null) this.sendMqtt(sensor_id, mqtt_active, client, "devData/indoor_humidity", feuchte_innen);
|
|
506
|
-
if (temp_aussen != null) this.sendMqtt(sensor_id, mqtt_active, client, "devData/outdoor_temp", temp_aussen);
|
|
507
|
-
if (feuchte_aussen != null) this.sendMqtt(sensor_id, mqtt_active, client, "devData/outdoor_humidity", feuchte_aussen);
|
|
508
|
-
|
|
509
|
-
// >>> Zusätzliche dynamische Sensoren ausgeben:
|
|
510
|
-
for (const s of sensor_data) {
|
|
511
|
-
const { type, channel, curVal, hihgVal, lowVal, ...rest } = s;
|
|
512
|
-
const key = `${type}_${channel}`;
|
|
513
|
-
if (skipCombinations.has(key)) continue;
|
|
514
|
-
const base = `devData/sensor_${type}_${channel}`;
|
|
515
|
-
|
|
516
|
-
if (curVal != null && curVal !== 65535) {
|
|
517
|
-
await this.sendMqtt(sensor_id, mqtt_active, client, `${base}/current`, curVal);
|
|
518
|
-
}
|
|
519
|
-
if (hihgVal != null && hihgVal !== 65535) {
|
|
520
|
-
await this.sendMqtt(sensor_id, mqtt_active, client, `${base}/high`, hihgVal);
|
|
579
|
+
for (const [key, value] of Object.entries(devdata)) {
|
|
580
|
+
if (key === 'content') {
|
|
581
|
+
continue;
|
|
521
582
|
}
|
|
522
|
-
if (
|
|
523
|
-
|
|
583
|
+
if (value !== null && value !== undefined) {
|
|
584
|
+
this.sendMqtt(sensor_id, mqtt_active, client, `devData/${key}`, value);
|
|
524
585
|
}
|
|
586
|
+
}
|
|
525
587
|
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
if (entries.length === 0) {
|
|
531
|
-
// Leeres Objekt → Platzhalter senden
|
|
532
|
-
const topic = `${base}/${nestedKey}`;
|
|
533
|
-
await this.sendMqtt(sensor_id, mqtt_active, client, topic, "n/a");
|
|
534
|
-
this.log.debug(`Send MQTT: ${topic}: n/a (empty object)`);
|
|
535
|
-
} else {
|
|
536
|
-
// Inhaltliches Objekt → Einzeldaten senden
|
|
537
|
-
for (const [k, v] of entries) {
|
|
538
|
-
if (v != null) {
|
|
539
|
-
const topic = `${base}/${nestedKey}/${k}`;
|
|
540
|
-
await this.sendMqtt(sensor_id, mqtt_active, client, topic, v);
|
|
541
|
-
this.log.debug(`Send MQTT: ${topic}: ${v}`);
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
}
|
|
545
|
-
}
|
|
588
|
+
const content = devdata.content || {};
|
|
589
|
+
for (const [key, value] of Object.entries(content)) {
|
|
590
|
+
if (value !== null && value !== undefined) {
|
|
591
|
+
this.sendMqtt(sensor_id, mqtt_active, client, `devData/${key}`, value);
|
|
546
592
|
}
|
|
547
593
|
}
|
|
548
|
-
}
|
|
549
594
|
|
|
550
|
-
|
|
551
|
-
this.contentDevData = {
|
|
552
|
-
atmospheric_pressure: luftdruck,
|
|
553
|
-
indoor_temp: temp_innen,
|
|
554
|
-
indoor_humidity: feuchte_innen,
|
|
555
|
-
outdoor_temp: temp_aussen,
|
|
556
|
-
outdoor_humidity: feuchte_aussen,
|
|
557
|
-
};
|
|
558
|
-
|
|
559
|
-
// Zusätzliche Sensoren ergänzen (alle type/channel Kombinationen)
|
|
560
|
-
for (const s of sensor_data) {
|
|
561
|
-
const { type, channel, curVal, hihgVal, lowVal, ...rest } = s;
|
|
562
|
-
const key = `${type}_${channel}`;
|
|
563
|
-
if (skipCombinations.has(key)) continue; // Überspringen, wenn schon bekannt
|
|
595
|
+
const sensor_data = content.sensorDatas || [];
|
|
564
596
|
|
|
565
|
-
const
|
|
597
|
+
for (const s of sensor_data) {
|
|
598
|
+
const type_ = s.type;
|
|
599
|
+
const channel = s.channel;
|
|
600
|
+
const cur_val = s.curVal;
|
|
601
|
+
const high_val = s.hihgVal;
|
|
602
|
+
const low_val = s.lowVal;
|
|
603
|
+
|
|
604
|
+
let prefix = '';
|
|
605
|
+
if (type_ === 1) {
|
|
606
|
+
prefix = '-Temp';
|
|
607
|
+
}
|
|
608
|
+
if (type_ === 2) {
|
|
609
|
+
prefix = '-Hum';
|
|
610
|
+
}
|
|
566
611
|
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
612
|
+
const key = `Type${type_}-Channel${channel}${prefix}`;
|
|
613
|
+
const base = `devData/${key}`;
|
|
614
|
+
|
|
615
|
+
if (cur_val !== null && cur_val !== undefined && cur_val !== 65535 && cur_val !== 255) {
|
|
616
|
+
this.sendMqtt(
|
|
617
|
+
sensor_id,
|
|
618
|
+
mqtt_active,
|
|
619
|
+
client,
|
|
620
|
+
`${base}/current`,
|
|
621
|
+
this.c_f_berechnen(cur_val, prefix, celsius),
|
|
622
|
+
);
|
|
623
|
+
}
|
|
624
|
+
if (high_val !== null && high_val !== undefined && high_val !== 65535 && high_val !== 255) {
|
|
625
|
+
this.sendMqtt(
|
|
626
|
+
sensor_id,
|
|
627
|
+
mqtt_active,
|
|
628
|
+
client,
|
|
629
|
+
`${base}/high`,
|
|
630
|
+
this.c_f_berechnen(high_val, prefix, celsius),
|
|
631
|
+
);
|
|
632
|
+
}
|
|
633
|
+
if (low_val !== null && low_val !== undefined && low_val !== 65535 && low_val !== 255) {
|
|
634
|
+
this.sendMqtt(
|
|
635
|
+
sensor_id,
|
|
636
|
+
mqtt_active,
|
|
637
|
+
client,
|
|
638
|
+
`${base}/low`,
|
|
639
|
+
this.c_f_berechnen(low_val, prefix, celsius),
|
|
640
|
+
);
|
|
641
|
+
}
|
|
570
642
|
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
643
|
+
for (const [k, v] of Object.entries(s)) {
|
|
644
|
+
if (
|
|
645
|
+
k.startsWith('dev') &&
|
|
646
|
+
v !== null &&
|
|
647
|
+
v !== undefined &&
|
|
648
|
+
!(typeof v === 'object' && Object.keys(v).length === 0)
|
|
649
|
+
) {
|
|
650
|
+
this.sendMqtt(sensor_id, mqtt_active, client, `${base}/${k}`, v);
|
|
651
|
+
}
|
|
576
652
|
}
|
|
577
653
|
}
|
|
578
654
|
}
|
|
579
655
|
|
|
580
656
|
if (storeJson) {
|
|
581
|
-
this.log.debug(
|
|
657
|
+
this.log.debug(`Save forecast.json to ${storeDir}`);
|
|
582
658
|
const json_object = JSON.stringify(forecast, null, 4);
|
|
583
|
-
fs.writeFileSync(path.join(storeDir,
|
|
659
|
+
fs.writeFileSync(path.join(storeDir, 'forecast.json'), json_object, 'utf-8');
|
|
584
660
|
}
|
|
585
661
|
|
|
586
662
|
this.printAllKeys(forecast);
|
|
@@ -598,20 +674,24 @@ class WeatherSense extends utils.Adapter {
|
|
|
598
674
|
|
|
599
675
|
await this.createOrUpdateForecastDPs(forecastChannelId, forecasts, celsius);
|
|
600
676
|
|
|
601
|
-
return
|
|
677
|
+
return {
|
|
678
|
+
dataReceived: true,
|
|
679
|
+
devdata,
|
|
680
|
+
forecast,
|
|
681
|
+
};
|
|
602
682
|
}
|
|
603
683
|
|
|
604
684
|
onUnload(callback) {
|
|
605
685
|
try {
|
|
606
686
|
callback();
|
|
607
|
-
} catch
|
|
687
|
+
} catch {
|
|
608
688
|
callback();
|
|
609
689
|
}
|
|
610
690
|
}
|
|
611
691
|
}
|
|
612
692
|
|
|
613
693
|
if (require.main !== module) {
|
|
614
|
-
module.exports =
|
|
694
|
+
module.exports = options => new WeatherSense(options);
|
|
615
695
|
} else {
|
|
616
696
|
new WeatherSense();
|
|
617
697
|
}
|