iobroker.weathersense 1.0.3 → 2.0.2

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/main.js CHANGED
@@ -1,30 +1,29 @@
1
- "use strict";
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("@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");
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 = "0";
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: "weathersense",
23
+ name: 'weathersense',
25
24
  });
26
- this.on("ready", this.onReady.bind(this));
27
- this.on("unload", this.onUnload.bind(this));
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("Sensor ID has no value between 1 and 20");
48
- this.terminate ? this.terminate("Sensor ID has no value between 1 and 20", 0) : process.exit(0);
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("Sensor ID has no valid value");
53
- this.terminate ? this.terminate("Sensor ID has no valid value", 0) : process.exit(0);
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("Sensor ID is " + sensor_id);
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("User email and/or user password empty - please check instance configuration");
60
- this.terminate ? this.terminate("User email and/or user password empty - please check instance configuration", 0) : process.exit(0);
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 == "0.0.0.0") {
67
- this.log.error("MQTT IP address is empty - please check instance configuration");
68
- this.terminate ? this.terminate("MQTT IP address is empty - please check instance configuration", 0) : process.exit(0);
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 === "*/10 * * * *") {
81
- instObj.common.schedule = `*/${(Math.floor(Math.random() * 5) + 3)} * * * *`;
82
- this.log.info(`Default schedule found and adjusted to spread calls better over 3-7 minutes!`);
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,269 @@ 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("MQTT active: " + mqtt_active);
92
- this.log.debug("MQTT port: " + mqtt_port);
94
+ this.log.debug(`MQTT active: ${mqtt_active}`);
95
+ this.log.debug(`MQTT port: ${mqtt_port}`);
93
96
 
94
- // Forecast Channel anlegen
95
- const forecastChannelId = `${sensor_id}.forecast`;
97
+ const deviceId = `${this.namespace}.${sensor_id}`;
96
98
 
97
99
  try {
98
- const dataReceived = await this.main(client, username, passwort, mqtt_active, sensor_id, storeJson, storeDir, celsius, forecastChannelId);
99
-
100
- const systemStateId = `${sensor_id}.DataReceived`;
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: "state",
117
+ type: 'state',
103
118
  common: {
104
- name: "Data successfully received",
105
- type: "boolean",
106
- role: "indicator",
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
- // DevData Channel anlegen
118
- const devDataChannelId = `${sensor_id}.devdata`;
119
- await this.setObjectNotExistsAsync(devDataChannelId, {
120
- type: "channel",
121
- common: { name: "DevData" },
122
- native: {},
123
- });
124
-
125
- await this.setObjectNotExistsAsync(forecastChannelId, {
126
- type: "channel",
127
- common: { name: "Forecast" },
128
- native: {},
129
- });
131
+ const devDataChannelId = `${deviceId}.devData`;
132
+ const tempUnit = celsius ? '°C' : '°F';
130
133
 
131
- const tempUnit = celsius ? "°C" : "°F";
132
- this.log.debug(`Unit: ${tempUnit}`);
133
-
134
- // Bekannte Items
135
- const fixedItems = [
136
- { id: "atmospheric_pressure", type: "number", role: "value.pressure", unit: "hPa" },
137
- { id: "indoor_temp", type: "number", role: "value.temperature", unit: tempUnit },
138
- { id: "indoor_humidity", type: "number", role: "value.humidity", unit: "%" },
139
- { id: "outdoor_temp", type: "number", role: "value.temperature", unit: tempUnit },
140
- { id: "outdoor_humidity", type: "number", role: "value.humidity", unit: "%" },
141
- ];
142
-
143
- // Zuerst die festen Items anlegen und setzen
144
- for (const item of fixedItems) {
145
- const id = `${devDataChannelId}.${item.id}`;
146
- await this.setObjectNotExistsAsync(id, {
147
- type: "state",
148
- common: {
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 });
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 });
162
154
  }
163
155
  }
164
156
 
165
- // Jetzt alle zusätzlichen dynamischen Keys aus this.contentDevData durchgehen
166
- for (const key of Object.keys(this.contentDevData || {})) {
167
- // Schon behandelt?
168
- if (fixedItems.find(item => item.id === key)) continue;
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}`;
169
162
 
170
- const val = this.contentDevData[key];
163
+ let unit = '';
164
+ if (key === 'atmos') {
165
+ unit = 'hPa';
166
+ }
171
167
 
172
- // Typ und Rolle
173
- const common = {
174
- name: key,
175
- type: typeof val === "number" ? "number" : "string",
176
- role: "value",
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";
168
+ await this.setObjectNotExistsAsync(id, {
169
+ type: typeof value === 'number' ? 'state' : 'state',
170
+ common: {
171
+ name: key,
172
+ type: typeof value,
173
+ role: 'value',
174
+ unit: unit,
175
+ read: true,
176
+ write: false,
177
+ },
178
+ native: {},
179
+ });
180
+ await this.setStateAsync(id, { val: value, ack: true });
191
181
  }
182
+ }
192
183
 
193
- const id = `${devDataChannelId}.${key}`;
184
+ // Alle Sensoren aus devdata.content.sensorDatas
185
+ const sensor_data = content.sensorDatas || [];
186
+ for (const s of sensor_data) {
187
+ const type_ = s.type;
188
+ const channel = s.channel;
189
+ const cur_val = s.curVal;
190
+ const high_val = s.hihgVal;
191
+ const low_val = s.lowVal;
194
192
 
195
- await this.setObjectNotExistsAsync(id, {
196
- type: "state",
197
- common,
198
- native: {},
199
- });
193
+ let prefix = '';
200
194
 
201
- await this.setStateAsync(id, { val: val, ack: true });
195
+ if (type_ === 1) {
196
+ prefix = '-Temp';
197
+ }
198
+ if (type_ === 2) {
199
+ prefix = '-Hum';
200
+ }
201
+
202
+ const key = `Type${type_}-Channel${channel}${prefix}`;
203
+ const base = `${devDataChannelId}.${key}`;
204
+
205
+ // current
206
+ if (cur_val !== null && cur_val !== undefined && cur_val !== 65535 && cur_val !== 255) {
207
+ const id = `${base}.current`;
208
+ await this.setObjectNotExistsAsync(id, {
209
+ type: 'state',
210
+ common: {
211
+ name: 'current',
212
+ type: 'number',
213
+ role:
214
+ prefix === '-Temp'
215
+ ? 'value.temperature'
216
+ : prefix === '-Hum'
217
+ ? 'value.humidity'
218
+ : 'value',
219
+ unit: prefix === '-Temp' ? tempUnit : prefix === '-Hum' ? '%' : '',
220
+ read: true,
221
+ write: false,
222
+ },
223
+ native: {},
224
+ });
225
+ await this.setStateAsync(id, { val: this.c_f_berechnen(cur_val, prefix, celsius), ack: true });
226
+ }
227
+ // high
228
+ if (high_val !== null && high_val !== undefined && high_val !== 65535 && high_val !== 255) {
229
+ const id = `${base}.high`;
230
+ await this.setObjectNotExistsAsync(id, {
231
+ type: 'state',
232
+ common: {
233
+ name: 'high',
234
+ type: 'number',
235
+ role:
236
+ prefix === '-Temp'
237
+ ? 'value.temperature'
238
+ : prefix === '-Hum'
239
+ ? 'value.humidity'
240
+ : 'value',
241
+ unit: prefix === '-Temp' ? tempUnit : prefix === '-Hum' ? '%' : '',
242
+ read: true,
243
+ write: false,
244
+ },
245
+ native: {},
246
+ });
247
+ await this.setStateAsync(id, { val: this.c_f_berechnen(high_val, prefix, celsius), ack: true });
248
+ }
249
+ // low
250
+ if (low_val !== null && low_val !== undefined && low_val !== 65535 && low_val !== 255) {
251
+ const id = `${base}.low`;
252
+ await this.setObjectNotExistsAsync(id, {
253
+ type: 'state',
254
+ common: {
255
+ name: 'low',
256
+ type: 'number',
257
+ role:
258
+ prefix === '-Temp'
259
+ ? 'value.temperature'
260
+ : prefix === '-Hum'
261
+ ? 'value.humidity'
262
+ : 'value',
263
+ unit: prefix === '-Temp' ? tempUnit : prefix === '-Hum' ? '%' : '',
264
+ read: true,
265
+ write: false,
266
+ },
267
+ native: {},
268
+ });
269
+ await this.setStateAsync(id, { val: this.c_f_berechnen(low_val, prefix, celsius), ack: true });
270
+ }
271
+
272
+ // dev*-Keys im Sensorobjekt
273
+ for (const [k, v] of Object.entries(s)) {
274
+ if (
275
+ k.startsWith('dev') &&
276
+ v !== null &&
277
+ v !== undefined &&
278
+ !(typeof v === 'object' && Object.keys(v).length === 0)
279
+ ) {
280
+ const id = `${base}.${k}`;
281
+ await this.setObjectNotExistsAsync(id, {
282
+ type: 'state',
283
+ common: {
284
+ name: k,
285
+ type: typeof v,
286
+ role: 'value',
287
+ unit: '',
288
+ read: true,
289
+ write: false,
290
+ },
291
+ native: {},
292
+ });
293
+ await this.setStateAsync(id, { val: v, ack: true });
294
+ }
295
+ }
202
296
  }
203
297
  } else {
204
- this.log.error("Error loading data in main()");
298
+ this.log.error('Error loading data in main()');
205
299
  await this.setStateAsync(systemStateId, { val: false, ack: true });
206
300
  }
207
301
  } catch (error) {
208
- this.log.error("Unexpected error in onReady(): " + error.message);
302
+ this.log.error(`Unexpected error in onReady(): ${error.message}`);
209
303
  } finally {
210
304
  if (client) {
211
305
  client.end();
212
306
  }
213
- this.terminate ? this.terminate("Everything done. Going to terminate till next schedule", 0) : process.exit(0);
307
+ this.terminate
308
+ ? this.terminate('Everything done. Going to terminate till next schedule', 0)
309
+ : process.exit(0);
214
310
  }
215
311
  }
216
312
 
313
+ // MQTT senden
217
314
  async sendMqtt(sensor_id, mqtt_active, client, topic, wert) {
218
315
  if (mqtt_active) {
219
- // Wenn wert nicht String ist, in String umwandeln (auch null und undefined abfangen)
220
- if (typeof wert !== "string") {
221
- wert = wert !== null && wert !== undefined ? wert.toString() : "";
316
+ if (typeof wert !== 'string') {
317
+ wert = wert !== null && wert !== undefined ? wert.toString() : '';
222
318
  }
223
- client.publish("WEATHERSENSE/" + sensor_id.toString() + "/" + topic, wert);
319
+ client.publish(`WEATHERSENSE/${sensor_id.toString()}/${topic}`, wert);
224
320
  }
225
321
  }
226
322
 
323
+ // °F nach °C umwandeln, falls notwendig
324
+ c_f_berechnen(temp, prefix, celsius) {
325
+ if (celsius && prefix === '-Temp') {
326
+ if (temp !== null && temp !== undefined) {
327
+ temp = ((temp - 32) / 1.8).toFixed(1);
328
+ }
329
+ }
330
+ return temp;
331
+ }
332
+
333
+ // Forecast Datenpunkte erstellen und schreiben
227
334
  async createOrUpdateForecastDPs(forecastChannelId, forecasts, celsius) {
228
- if (!forecasts || !forecastChannelId) return;
335
+ if (!forecasts || !forecastChannelId) {
336
+ return;
337
+ }
229
338
 
230
- // Struktur der Forecast-Items pro Tag
231
339
  const forecastItems = [
232
- { id: "day", type: "string", role: "value", unit: "" },
233
- { id: "date", type: "string", role: "value", unit: "" },
234
- { id: "high", type: "number", role: "value.temperature", unit: celsius ? "°C" : "°F" },
235
- { id: "low", type: "number", role: "value.temperature", unit: celsius ? "°C" : "°F" },
236
- { id: "text", type: "string", role: "text", unit: "" },
340
+ { id: 'day', type: 'string', role: 'value', unit: '' },
341
+ { id: 'date', type: 'string', role: 'value', unit: '' },
342
+ { id: 'high', type: 'number', role: 'value.temperature', unit: celsius ? '°C' : '°F' },
343
+ { id: 'low', type: 'number', role: 'value.temperature', unit: celsius ? '°C' : '°F' },
344
+ { id: 'text', type: 'string', role: 'text', unit: '' },
237
345
  ];
238
346
 
239
347
  for (let i = 0; i < forecasts.length; i++) {
240
348
  const forecast = forecasts[i];
241
- if (!forecast) continue;
349
+ if (!forecast) {
350
+ continue;
351
+ }
242
352
 
243
353
  for (const item of forecastItems) {
244
- const id = `${forecastChannelId}.${i}.${item.id}`;
354
+ const id = `${forecastChannelId}.day_${i}.${item.id}`;
245
355
  await this.setObjectNotExistsAsync(id, {
246
- type: "state",
356
+ type: 'state',
247
357
  common: {
248
358
  name: item.id,
249
359
  type: item.type,
@@ -256,7 +366,7 @@ class WeatherSense extends utils.Adapter {
256
366
  });
257
367
 
258
368
  let val = forecast[item.id];
259
- if ((item.id === "high" || item.id === "low") && typeof val === "number") {
369
+ if ((item.id === 'high' || item.id === 'low') && typeof val === 'number') {
260
370
  if (celsius) {
261
371
  val = Number(((val - 32) / 1.8).toFixed(1));
262
372
  } else {
@@ -271,21 +381,26 @@ class WeatherSense extends utils.Adapter {
271
381
  }
272
382
  }
273
383
 
384
+ // Forecasts per MQTT senden
274
385
  async sendForecasts(client, forecasts, celsius, sensor_id) {
275
- if (!client || !forecasts) return;
386
+ if (!client || !forecasts) {
387
+ return;
388
+ }
276
389
 
277
390
  for (let i = 0; i < forecasts.length; i++) {
278
391
  const forecast = forecasts[i];
279
- if (!forecast) continue;
392
+ if (!forecast) {
393
+ continue;
394
+ }
280
395
 
281
- await this.sendMqtt(sensor_id, true, client, `forecast/${i}/day`, forecast.day || "");
282
- await this.sendMqtt(sensor_id, true, client, `forecast/${i}/date`, forecast.date || "");
396
+ await this.sendMqtt(sensor_id, true, client, `forecast/day_${i}/day`, forecast.day || '');
397
+ await this.sendMqtt(sensor_id, true, client, `forecast/day_${i}/date`, forecast.date || '');
283
398
 
284
399
  let tempHigh = forecast.high;
285
400
  let tempLow = forecast.low;
286
401
 
287
- if (celsius && typeof tempHigh === "number" && typeof tempLow === "number") {
288
- tempHigh = Number(((tempHigh - 32) / 1.8).toFixed(1)); // Zahl, keine Zeichenkette
402
+ if (celsius && typeof tempHigh === 'number' && typeof tempLow === 'number') {
403
+ tempHigh = Number(((tempHigh - 32) / 1.8).toFixed(1)); // Zahl, keine Zeichenkette
289
404
  tempLow = Number(((tempLow - 32) / 1.8).toFixed(1));
290
405
  } else {
291
406
  // Wenn kein Celsius oder kein Zahlentyp, trotzdem als Zahl (sofern möglich), sonst 0 als Fallback
@@ -293,22 +408,24 @@ class WeatherSense extends utils.Adapter {
293
408
  tempLow = tempLow != null ? Number(tempLow) : 0;
294
409
  }
295
410
 
296
- await this.sendMqtt(sensor_id, true, client, `forecast/${i}/high`, tempHigh);
297
- await this.sendMqtt(sensor_id, true, client, `forecast/${i}/low`, tempLow);
298
- await this.sendMqtt(sensor_id, true, client, `forecast/${i}/text`, forecast.text || "");
411
+ await this.sendMqtt(sensor_id, true, client, `forecast/day_${i}/high`, tempHigh);
412
+ await this.sendMqtt(sensor_id, true, client, `forecast/day_${i}/low`, tempLow);
413
+ await this.sendMqtt(sensor_id, true, client, `forecast/day_${i}/text`, forecast.text || '');
299
414
  }
300
415
  }
301
416
 
417
+ // Alte MQTT Datenpunkte auf Subscriber löschen
302
418
  async clearOldForecasts(sensor_id, client, maxDays = 6) {
303
419
  for (let i = 0; i < maxDays; i++) {
304
- for (const key of ["day", "date", "high", "low", "text"]) {
305
- await this.sendMqtt(sensor_id, true, client, `forecast/${i}/${key}`, "");
420
+ for (const key of ['day', 'date', 'high', 'low', 'text']) {
421
+ await this.sendMqtt(sensor_id, true, client, `forecast/day_${i}/${key}`, '');
306
422
  }
307
423
  }
308
424
  }
309
425
 
310
- printAllKeys(d, prefix = "") {
311
- if (typeof d === "object" && d !== null && !Array.isArray(d)) {
426
+ // Debugausgabe formatieren
427
+ printAllKeys(d, prefix = '') {
428
+ if (typeof d === 'object' && d !== null && !Array.isArray(d)) {
312
429
  for (const [k, v] of Object.entries(d)) {
313
430
  this.printAllKeys(v, `${prefix}${k}/`);
314
431
  }
@@ -321,266 +438,231 @@ class WeatherSense extends utils.Adapter {
321
438
  }
322
439
  }
323
440
 
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
441
  // Funktion zum Erzeugen des PW-Hashes
331
442
  hashPassword(pw) {
332
- const key = Buffer.from("ZW1heEBwd2QxMjM=", "base64").toString("utf8");
443
+ const key = Buffer.from('ZW1heEBwd2QxMjM=', 'base64').toString('utf8');
333
444
  const combined = pw + key;
334
- return crypto.createHash("md5").update(combined, "utf8").digest("hex").toUpperCase();
445
+ return crypto.createHash('md5').update(combined, 'utf8').digest('hex').toUpperCase();
335
446
  }
336
447
 
337
448
  // Login-Funktion
338
449
  async login(USERNAME, PASSWORD) {
339
- const LOGIN_URL = "https://47.52.149.125/V1.0/account/login";
450
+ const LOGIN_URL = 'https://emaxlife.net/V1.0/account/login';
340
451
  const hashed_pw = this.hashPassword(PASSWORD);
341
452
 
342
453
  const headers = {
343
- "Content-Type": "application/json; charset=utf-8",
344
- "User-Agent": "okhttp/3.14.9"
454
+ 'Content-Type': 'application/json; charset=utf-8',
455
+ 'User-Agent': 'okhttp/3.14.9',
345
456
  };
346
457
 
347
458
  const payload = {
348
459
  email: USERNAME,
349
- pwd: hashed_pw
460
+ pwd: hashed_pw,
350
461
  };
351
462
 
352
463
  try {
353
464
  const response = await axios.post(LOGIN_URL, payload, { headers });
354
465
 
355
- this.log.debug("Status code:", response.status);
356
- this.log.debug("Response:", response.data);
466
+ this.log.debug('Status code:', response.status);
467
+ this.log.debug('Response:', response.data);
357
468
 
358
469
  const data = response.data;
359
470
 
360
471
  if (response.status === 200) {
361
472
  if (data.status === 0 && data.content) {
362
473
  const token = data.content.token;
363
- this.log.debug("Login successful. Token: " + token.substring(0, 20) + "...");
474
+ this.log.debug(`Login successful. Token: ${token.substring(0, 20)}...`);
364
475
  return token;
365
- } else {
366
- this.log.error("Login failed:", data.message);
367
476
  }
477
+ this.log.error('Login failed:', data.message);
368
478
  } else {
369
- this.log.error("Server error");
479
+ this.log.error('Server error');
370
480
  }
371
481
  } catch (error) {
372
- this.log.error("Error during login:", error.message);
482
+ this.log.error('Error during login:', error.message);
373
483
  }
374
484
 
375
485
  return null;
376
486
  }
377
487
 
488
+ // Realtime Daten holen
378
489
  async devData(token) {
379
- // Realtime Daten holen
380
- this.log.debug("getRealtime data...");
490
+ this.log.debug('getRealtime data...');
381
491
 
382
- const url = "https://emaxlife.net/V1.0/weather/devData/getRealtime";
492
+ const url = 'https://emaxlife.net/V1.0/weather/devData/getRealtime';
383
493
  const headers = {
384
- "emaxtoken": token,
385
- "Content-Type": "application/json"
494
+ emaxtoken: token,
495
+ 'Content-Type': 'application/json',
386
496
  };
387
497
 
388
498
  try {
389
499
  const response = await axios.get(url, {
390
500
  headers,
391
501
  timeout: 5000,
392
- httpsAgent: new (require("https").Agent)({ rejectUnauthorized: false })
502
+ httpsAgent: new (require('https').Agent)({ rejectUnauthorized: false }),
393
503
  });
394
504
 
395
505
  if (response.status === 200) {
396
- this.log.debug("Data was received");
506
+ this.log.debug('Data was received');
397
507
  return response.data;
398
- } else {
399
- this.log.error(`devData > Status Code: ${response.status}`);
400
- return "error";
401
508
  }
509
+ this.log.error(`devData > Status Code: ${response.status}`);
510
+ return 'error';
402
511
  } catch (error) {
403
512
  if (error.response) {
404
513
  this.log.error(`devData > Status Code: ${error.response.status}`);
405
514
  } else {
406
515
  this.log.error(`Error during request: ${error.message}`);
407
516
  }
408
- return "error";
517
+ return 'error';
409
518
  }
410
519
  }
411
520
 
521
+ // Forecast Daten holen
412
522
  async foreCast(token) {
413
- // Forecast holen
414
- this.log.debug("getForecast data...");
523
+ this.log.debug('getForecast data...');
415
524
 
416
- const url = "https://emaxlife.net/V1.0/weather/netData/getForecast";
525
+ const url = 'https://emaxlife.net/V1.0/weather/netData/getForecast';
417
526
  const headers = {
418
- "emaxtoken": token,
419
- "Content-Type": "application/json"
527
+ emaxtoken: token,
528
+ 'Content-Type': 'application/json',
420
529
  };
421
530
 
422
531
  try {
423
532
  const response = await axios.get(url, {
424
533
  headers,
425
534
  timeout: 5000,
426
- httpsAgent: new https.Agent({ rejectUnauthorized: false }) // entspricht verify=False
535
+ httpsAgent: new https.Agent({ rejectUnauthorized: false }), // entspricht verify=False
427
536
  });
428
537
 
429
538
  if (response.status === 200) {
430
- this.log.debug("Data was received");
539
+ this.log.debug('Data was received');
431
540
  return response.data;
432
- } else {
433
- this.log.error(`foreCast > Status Code: ${response.status}`);
434
- return "error";
435
541
  }
542
+ this.log.error(`foreCast > Status Code: ${response.status}`);
543
+ return 'error';
436
544
  } catch (error) {
437
545
  if (error.response) {
438
546
  this.log.error(`foreCast > Status Code: ${error.response.status}`);
439
547
  } else {
440
548
  this.log.error(`Error during request: ${error.message}`);
441
549
  }
442
- return "error";
550
+ return 'error';
443
551
  }
444
552
  }
445
553
 
446
554
  async main(client, username, passwort, mqtt_active, sensor_id, storeJson, storeDir, celsius, forecastChannelId) {
447
555
  const token = await this.login(username, passwort);
448
556
  if (!token) {
449
- this.log.error("No token received");
557
+ this.log.error('No token received');
450
558
  if (mqtt_active) {
451
- await this.sendMqtt(sensor_id, mqtt_active, client, "dataReceived", "false");
559
+ await this.sendMqtt(sensor_id, mqtt_active, client, 'dataReceived', 'false');
452
560
  client.end();
453
561
  }
454
562
  return false;
455
563
  }
456
564
  const devdata = await this.devData(token);
457
565
  const forecast = await this.foreCast(token);
458
- if (devdata === "error" || forecast === "error") {
459
- this.log.error("No data received");
566
+ if (devdata === 'error' || forecast === 'error') {
567
+ this.log.error('No data received');
460
568
  if (mqtt_active) {
461
- await this.sendMqtt(sensor_id, mqtt_active, client, "dataReceived", "false");
569
+ await this.sendMqtt(sensor_id, mqtt_active, client, 'dataReceived', 'false');
462
570
  client.end();
463
571
  }
464
- return false;
572
+ return { dataReceived: false };
465
573
  }
466
574
 
467
575
  if (storeJson) {
468
- this.log.debug("Save devData.json to " + storeDir);
576
+ this.log.debug(`Save devData.json to ${storeDir}`);
469
577
  const json_object = JSON.stringify(devdata, null, 4);
470
- fs.writeFileSync(path.join(storeDir, "devData.json"), json_object, "utf-8");
578
+ fs.writeFileSync(path.join(storeDir, 'devData.json'), json_object, 'utf-8');
471
579
  }
472
580
 
473
- this.log.debug("devData JSON:");
581
+ this.log.debug('devData JSON:');
474
582
  this.printAllKeys(devdata);
475
583
 
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
584
  if (mqtt_active) {
493
- const error_code = devdata.error;
494
-
495
- if (error_code != null) this.sendMqtt(sensor_id, mqtt_active, client, "devData/error", error_code);
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);
585
+ for (const [key, value] of Object.entries(devdata)) {
586
+ if (key === 'content') {
587
+ continue;
518
588
  }
519
- if (hihgVal != null && hihgVal !== 65535) {
520
- await this.sendMqtt(sensor_id, mqtt_active, client, `${base}/high`, hihgVal);
521
- }
522
- if (lowVal != null && lowVal !== 65535) {
523
- await this.sendMqtt(sensor_id, mqtt_active, client, `${base}/low`, lowVal);
589
+ if (value !== null && value !== undefined) {
590
+ this.sendMqtt(sensor_id, mqtt_active, client, `devData/${key}`, value);
524
591
  }
592
+ }
525
593
 
526
- for (const [nestedKey, nestedVal] of Object.entries(rest)) {
527
- if (nestedVal && typeof nestedVal === "object") {
528
- const entries = Object.entries(nestedVal);
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
- }
594
+ const content = devdata.content || {};
595
+ for (const [key, value] of Object.entries(content)) {
596
+ if (value !== null && value !== undefined) {
597
+ this.sendMqtt(sensor_id, mqtt_active, client, `devData/${key}`, value);
546
598
  }
547
599
  }
548
- }
549
-
550
- // Basiswerte setzen
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
600
 
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
601
+ const sensor_data = content.sensorDatas || [];
564
602
 
565
- const keyBase = `sensor_${type}_${channel}`;
603
+ for (const s of sensor_data) {
604
+ const type_ = s.type;
605
+ const channel = s.channel;
606
+ const cur_val = s.curVal;
607
+ const high_val = s.hihgVal;
608
+ const low_val = s.lowVal;
609
+
610
+ let prefix = '';
611
+ if (type_ === 1) {
612
+ prefix = '-Temp';
613
+ }
614
+ if (type_ === 2) {
615
+ prefix = '-Hum';
616
+ }
566
617
 
567
- if (curVal != null && curVal !== 65535) this.contentDevData[`${keyBase}_current`] = curVal;
568
- if (hihgVal != null && hihgVal !== 65535) this.contentDevData[`${keyBase}_high`] = hihgVal;
569
- if (lowVal != null && lowVal !== 65535) this.contentDevData[`${keyBase}_low`] = lowVal;
618
+ const key = `Type${type_}-Channel${channel}${prefix}`;
619
+ const base = `devData/${key}`;
620
+
621
+ if (cur_val !== null && cur_val !== undefined && cur_val !== 65535 && cur_val !== 255) {
622
+ this.sendMqtt(
623
+ sensor_id,
624
+ mqtt_active,
625
+ client,
626
+ `${base}/current`,
627
+ this.c_f_berechnen(cur_val, prefix, celsius),
628
+ );
629
+ }
630
+ if (high_val !== null && high_val !== undefined && high_val !== 65535 && high_val !== 255) {
631
+ this.sendMqtt(
632
+ sensor_id,
633
+ mqtt_active,
634
+ client,
635
+ `${base}/high`,
636
+ this.c_f_berechnen(high_val, prefix, celsius),
637
+ );
638
+ }
639
+ if (low_val !== null && low_val !== undefined && low_val !== 65535 && low_val !== 255) {
640
+ this.sendMqtt(
641
+ sensor_id,
642
+ mqtt_active,
643
+ client,
644
+ `${base}/low`,
645
+ this.c_f_berechnen(low_val, prefix, celsius),
646
+ );
647
+ }
570
648
 
571
- for (const [k, v] of Object.entries(rest)) {
572
- if (v && typeof v === "object" && Object.keys(v).length === 0) {
573
- this.contentDevData[`${keyBase}_${k}`] = "n/a";
574
- } else if (v != null) {
575
- this.contentDevData[`${keyBase}_${k}`] = v;
649
+ for (const [k, v] of Object.entries(s)) {
650
+ if (
651
+ k.startsWith('dev') &&
652
+ v !== null &&
653
+ v !== undefined &&
654
+ !(typeof v === 'object' && Object.keys(v).length === 0)
655
+ ) {
656
+ this.sendMqtt(sensor_id, mqtt_active, client, `${base}/${k}`, v);
657
+ }
576
658
  }
577
659
  }
578
660
  }
579
661
 
580
662
  if (storeJson) {
581
- this.log.debug("Save forecast.json to " + storeDir);
663
+ this.log.debug(`Save forecast.json to ${storeDir}`);
582
664
  const json_object = JSON.stringify(forecast, null, 4);
583
- fs.writeFileSync(path.join(storeDir, "forecast.json"), json_object, "utf-8");
665
+ fs.writeFileSync(path.join(storeDir, 'forecast.json'), json_object, 'utf-8');
584
666
  }
585
667
 
586
668
  this.printAllKeys(forecast);
@@ -598,20 +680,24 @@ class WeatherSense extends utils.Adapter {
598
680
 
599
681
  await this.createOrUpdateForecastDPs(forecastChannelId, forecasts, celsius);
600
682
 
601
- return true;
683
+ return {
684
+ dataReceived: true,
685
+ devdata,
686
+ forecast,
687
+ };
602
688
  }
603
689
 
604
690
  onUnload(callback) {
605
691
  try {
606
692
  callback();
607
- } catch (e) {
693
+ } catch {
608
694
  callback();
609
695
  }
610
696
  }
611
697
  }
612
698
 
613
699
  if (require.main !== module) {
614
- module.exports = (options) => new WeatherSense(options);
700
+ module.exports = options => new WeatherSense(options);
615
701
  } else {
616
702
  new WeatherSense();
617
703
  }