iobroker.weathersense 1.0.0
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/LICENSE +21 -0
- package/README.md +52 -0
- package/admin/i18n/de/translations.json +13 -0
- package/admin/i18n/en/translations.json +13 -0
- package/admin/jsonConfig.json +118 -0
- package/admin/weathersense.png +0 -0
- package/io-package.json +81 -0
- package/lib/adapter-config.d.ts +19 -0
- package/main.js +617 -0
- package/package.json +79 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Daniel Luginbühl <webmaster@ltspiceusers.ch>
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+

|
|
2
|
+
# ioBroker.weathersense
|
|
3
|
+
|
|
4
|
+
[](https://www.npmjs.com/package/iobroker.weathersense)
|
|
5
|
+
[](https://www.npmjs.com/package/iobroker.weathersense)
|
|
6
|
+

|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
[](https://nodei.co/npm/iobroker.weathersense/)
|
|
10
|
+
|
|
11
|
+
**Tests:** 
|
|
12
|
+
|
|
13
|
+
## weathersense adapter for ioBroker
|
|
14
|
+
|
|
15
|
+
WeatherSense is a cloud for weather stations. This adapter reads data from the WeatherSense server.
|
|
16
|
+
|
|
17
|
+
See: https://play.google.com/store/apps/details?id=com.emax.weahter&hl=de_CH
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
## Use:
|
|
21
|
+
Simply enter your WeatherSense account login details (email and password).
|
|
22
|
+
The weather station data is stored in the weathersense data point.
|
|
23
|
+
The data can also be sent via MQTT.
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
## Changelog
|
|
27
|
+
### 1.0.0 (2025-07-01)
|
|
28
|
+
|
|
29
|
+
- Initial release
|
|
30
|
+
|
|
31
|
+
## License
|
|
32
|
+
MIT License
|
|
33
|
+
|
|
34
|
+
Copyright (c) 2025 Daniel Luginbühl <webmaster@ltspiceusers.ch>
|
|
35
|
+
|
|
36
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
37
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
38
|
+
in the Software without restriction, including without limitation the rights
|
|
39
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
40
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
41
|
+
furnished to do so, subject to the following conditions:
|
|
42
|
+
|
|
43
|
+
The above copyright notice and this permission notice shall be included in all
|
|
44
|
+
copies or substantial portions of the Software.
|
|
45
|
+
|
|
46
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
47
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
48
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
49
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
50
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
51
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
52
|
+
SOFTWARE.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"heizoel24-mex adapter settings": "Adaptereinstellungen für WeatherSense",
|
|
3
|
+
"username": "WeatherSense Username (Email)",
|
|
4
|
+
"passwort": "WeatherSense Passwort",
|
|
5
|
+
"mqtt_active": "MQTT benutzen",
|
|
6
|
+
"broker_address": "MQTT Broker IP Adresse",
|
|
7
|
+
"mqtt_user": "MQTT User",
|
|
8
|
+
"mqtt_pass": "MQTT Passwort",
|
|
9
|
+
"mqtt_port": "MQTT Port",
|
|
10
|
+
"sensor_id": "Sensor ID (1-20)",
|
|
11
|
+
"storeJson": "Json speichern",
|
|
12
|
+
"storeDir": "Verz. für Json Datei"
|
|
13
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"heizoel24-mex adapter settings": "Adapter settings for WeatherSense",
|
|
3
|
+
"username": "WeatherSense username (email)",
|
|
4
|
+
"passwort": "WeatherSense password",
|
|
5
|
+
"mqtt_active": "Use MQTT",
|
|
6
|
+
"broker_address": "MQTT broker IP address",
|
|
7
|
+
"mqtt_user": "MQTT user",
|
|
8
|
+
"mqtt_pass": "MQTT password",
|
|
9
|
+
"mqtt_port": "MQTT port",
|
|
10
|
+
"sensor_id": "Sensor ID (1-20)",
|
|
11
|
+
"storeJson": "Save Json",
|
|
12
|
+
"storeDir": "Folder for Json file"
|
|
13
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
{
|
|
2
|
+
"i18n": true,
|
|
3
|
+
"type": "panel",
|
|
4
|
+
"items": {
|
|
5
|
+
"username": {
|
|
6
|
+
"type": "text",
|
|
7
|
+
"label": "WeatherSense username (email)",
|
|
8
|
+
"newLine": true,
|
|
9
|
+
"xs": 12,
|
|
10
|
+
"sm": 12,
|
|
11
|
+
"md": 6,
|
|
12
|
+
"lg": 4,
|
|
13
|
+
"xl": 4
|
|
14
|
+
},
|
|
15
|
+
"passwort": {
|
|
16
|
+
"type": "text",
|
|
17
|
+
"label": "WeatherSense password",
|
|
18
|
+
"xs": 12,
|
|
19
|
+
"sm": 12,
|
|
20
|
+
"md": 6,
|
|
21
|
+
"lg": 4,
|
|
22
|
+
"xl": 4
|
|
23
|
+
},
|
|
24
|
+
"mqtt_active": {
|
|
25
|
+
"type": "checkbox",
|
|
26
|
+
"label": "Use MQTT",
|
|
27
|
+
"default": false,
|
|
28
|
+
"newLine": true,
|
|
29
|
+
"xs": 12,
|
|
30
|
+
"sm": 12,
|
|
31
|
+
"md": 6,
|
|
32
|
+
"lg": 4,
|
|
33
|
+
"xl": 4
|
|
34
|
+
},
|
|
35
|
+
"celsius": {
|
|
36
|
+
"type": "checkbox",
|
|
37
|
+
"label": "°C (unchecked = °F)",
|
|
38
|
+
"default": true,
|
|
39
|
+
"newLine": true,
|
|
40
|
+
"xs": 12,
|
|
41
|
+
"sm": 12,
|
|
42
|
+
"md": 6,
|
|
43
|
+
"lg": 4,
|
|
44
|
+
"xl": 4
|
|
45
|
+
},
|
|
46
|
+
"broker_address": {
|
|
47
|
+
"type": "text",
|
|
48
|
+
"label": "MQTT broker IP address",
|
|
49
|
+
"newLine": true,
|
|
50
|
+
"xs": 12,
|
|
51
|
+
"sm": 12,
|
|
52
|
+
"md": 6,
|
|
53
|
+
"lg": 4,
|
|
54
|
+
"xl": 4
|
|
55
|
+
},
|
|
56
|
+
"mqtt_port": {
|
|
57
|
+
"type": "number",
|
|
58
|
+
"label": "MQTT port",
|
|
59
|
+
"default": 1883,
|
|
60
|
+
"xs": 12,
|
|
61
|
+
"sm": 12,
|
|
62
|
+
"md": 6,
|
|
63
|
+
"lg": 4,
|
|
64
|
+
"xl": 4
|
|
65
|
+
},
|
|
66
|
+
"mqtt_user": {
|
|
67
|
+
"type": "text",
|
|
68
|
+
"label": "MQTT user",
|
|
69
|
+
"newLine": true,
|
|
70
|
+
"xs": 12,
|
|
71
|
+
"sm": 12,
|
|
72
|
+
"md": 6,
|
|
73
|
+
"lg": 4,
|
|
74
|
+
"xl": 4
|
|
75
|
+
},
|
|
76
|
+
"mqtt_pass": {
|
|
77
|
+
"type": "text",
|
|
78
|
+
"label": "MQTT password",
|
|
79
|
+
"xs": 12,
|
|
80
|
+
"sm": 12,
|
|
81
|
+
"md": 6,
|
|
82
|
+
"lg": 4,
|
|
83
|
+
"xl": 4
|
|
84
|
+
},
|
|
85
|
+
"sensor_id": {
|
|
86
|
+
"type": "number",
|
|
87
|
+
"label": "Sensor ID (1-20)",
|
|
88
|
+
"default": 1,
|
|
89
|
+
"newLine": true,
|
|
90
|
+
"xs": 12,
|
|
91
|
+
"sm": 12,
|
|
92
|
+
"md": 6,
|
|
93
|
+
"lg": 4,
|
|
94
|
+
"xl": 4
|
|
95
|
+
},
|
|
96
|
+
"storeJson": {
|
|
97
|
+
"type": "checkbox",
|
|
98
|
+
"label": "Save Json file",
|
|
99
|
+
"default": false,
|
|
100
|
+
"newLine": true,
|
|
101
|
+
"xs": 12,
|
|
102
|
+
"sm": 12,
|
|
103
|
+
"md": 6,
|
|
104
|
+
"lg": 4,
|
|
105
|
+
"xl": 4
|
|
106
|
+
},
|
|
107
|
+
"storeDir": {
|
|
108
|
+
"type": "text",
|
|
109
|
+
"label": "Folder for JSON file",
|
|
110
|
+
"default": "/home/iobroker",
|
|
111
|
+
"xs": 12,
|
|
112
|
+
"sm": 12,
|
|
113
|
+
"md": 6,
|
|
114
|
+
"lg": 4,
|
|
115
|
+
"xl": 4
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
Binary file
|
package/io-package.json
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
{
|
|
2
|
+
"common": {
|
|
3
|
+
"name": "weathersense",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"news": {
|
|
6
|
+
"1.0.0": {
|
|
7
|
+
"en": "First release",
|
|
8
|
+
"de": "Erstes Release"
|
|
9
|
+
}
|
|
10
|
+
},
|
|
11
|
+
"titleLang": {
|
|
12
|
+
"en": "WeatherSense",
|
|
13
|
+
"de": "WeatherSense"
|
|
14
|
+
},
|
|
15
|
+
"desc": {
|
|
16
|
+
"en": "Read in data from WeatherSense",
|
|
17
|
+
"de": "Daten von WeatherSense einlesen"
|
|
18
|
+
},
|
|
19
|
+
"authors": [
|
|
20
|
+
"Daniel Luginbühl <webmaster@ltspiceusers.ch>"
|
|
21
|
+
],
|
|
22
|
+
"keywords": [
|
|
23
|
+
"weather",
|
|
24
|
+
"sense",
|
|
25
|
+
"weathersense",
|
|
26
|
+
"ioBroker.weathersense"
|
|
27
|
+
],
|
|
28
|
+
"licenseInformation": {
|
|
29
|
+
"type": "free",
|
|
30
|
+
"license": "MIT"
|
|
31
|
+
},
|
|
32
|
+
"platform": "Javascript/Node.js",
|
|
33
|
+
"icon": "weathersense.png",
|
|
34
|
+
"enabled": false,
|
|
35
|
+
"extIcon": "https://raw.githubusercontent.com/ltspicer/ioBroker.weathersense/main/admin/weathersense.png",
|
|
36
|
+
"readme": "https://github.com/ltspicer/ioBroker.weathersense/blob/main/README.md",
|
|
37
|
+
"loglevel": "info",
|
|
38
|
+
"tier": 3,
|
|
39
|
+
"mode": "schedule",
|
|
40
|
+
"schedule": "*/5 * * * *",
|
|
41
|
+
"allowInit": true,
|
|
42
|
+
"type": "metering",
|
|
43
|
+
"compact": true,
|
|
44
|
+
"connectionType": "cloud",
|
|
45
|
+
"dataSource": "poll",
|
|
46
|
+
"adminUI": {
|
|
47
|
+
"config": "json"
|
|
48
|
+
},
|
|
49
|
+
"dependencies": [
|
|
50
|
+
{
|
|
51
|
+
"js-controller": ">=5.0.19"
|
|
52
|
+
}
|
|
53
|
+
],
|
|
54
|
+
"globalDependencies": [
|
|
55
|
+
{
|
|
56
|
+
"admin": ">=7.4.10"
|
|
57
|
+
}
|
|
58
|
+
],
|
|
59
|
+
"installedFrom": "file:/opt/iobroker/ioBroker.weathersense"
|
|
60
|
+
},
|
|
61
|
+
"encryptedNative": [
|
|
62
|
+
"passwort",
|
|
63
|
+
"mqtt_pass"
|
|
64
|
+
],
|
|
65
|
+
"protectedNative": [
|
|
66
|
+
"passwort",
|
|
67
|
+
"mqtt_pass"
|
|
68
|
+
],
|
|
69
|
+
"native": {
|
|
70
|
+
"username": "",
|
|
71
|
+
"passwort": "",
|
|
72
|
+
"mqtt_active": false,
|
|
73
|
+
"broker_address": "0.0.0.0",
|
|
74
|
+
"mqtt_user": "",
|
|
75
|
+
"mqtt_pass": "",
|
|
76
|
+
"mqtt_port": "1883",
|
|
77
|
+
"sensor_id": "1",
|
|
78
|
+
"storeJson": false,
|
|
79
|
+
"storeDir": "/home/iobroker"
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// This file extends the AdapterConfig type from "@types/iobroker"
|
|
2
|
+
// using the actual properties present in io-package.json
|
|
3
|
+
// in order to provide typings for adapter.config properties
|
|
4
|
+
|
|
5
|
+
import { native } from "../io-package.json";
|
|
6
|
+
|
|
7
|
+
type _AdapterConfig = typeof native;
|
|
8
|
+
|
|
9
|
+
// Augment the globally declared type ioBroker.AdapterConfig
|
|
10
|
+
declare global {
|
|
11
|
+
namespace ioBroker {
|
|
12
|
+
interface AdapterConfig extends _AdapterConfig {
|
|
13
|
+
// Do not enter anything here!
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// this is required so the above AdapterConfig is found by TypeScript / type checking
|
|
19
|
+
export {};
|
package/main.js
ADDED
|
@@ -0,0 +1,617 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* Created with @iobroker/create-adapter v2.6.2
|
|
5
|
+
*/
|
|
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");
|
|
14
|
+
|
|
15
|
+
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
|
16
|
+
|
|
17
|
+
axios.defaults.timeout = 2000;
|
|
18
|
+
|
|
19
|
+
class WeatherSense extends utils.Adapter {
|
|
20
|
+
|
|
21
|
+
constructor(options) {
|
|
22
|
+
super({
|
|
23
|
+
...options,
|
|
24
|
+
name: "weathersense",
|
|
25
|
+
});
|
|
26
|
+
this.on("ready", this.onReady.bind(this));
|
|
27
|
+
this.on("unload", this.onUnload.bind(this));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async onReady() {
|
|
31
|
+
const username = this.config.username;
|
|
32
|
+
const passwort = this.config.passwort;
|
|
33
|
+
const broker_address = this.config.broker_address;
|
|
34
|
+
const mqtt_active = this.config.mqtt_active;
|
|
35
|
+
const celsius = this.config.celsius;
|
|
36
|
+
const mqtt_user = this.config.mqtt_user;
|
|
37
|
+
const mqtt_pass = this.config.mqtt_pass;
|
|
38
|
+
const mqtt_port = this.config.mqtt_port;
|
|
39
|
+
const sensor_in = this.config.sensor_id;
|
|
40
|
+
let sensor_id = 1;
|
|
41
|
+
const storeJson = this.config.storeJson;
|
|
42
|
+
const storeDir = this.config.storeDir;
|
|
43
|
+
|
|
44
|
+
if (Number(sensor_in)) {
|
|
45
|
+
sensor_id = parseInt(sensor_in);
|
|
46
|
+
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);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
} 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);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
this.log.debug("Sensor ID is " + sensor_id);
|
|
57
|
+
|
|
58
|
+
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);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
let client = null;
|
|
65
|
+
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);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
client = mqtt.connect(`mqtt://${broker_address}:${mqtt_port}`, {
|
|
72
|
+
connectTimeout: 4000,
|
|
73
|
+
username: mqtt_user,
|
|
74
|
+
password: mqtt_pass
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
const instObj = await this.getForeignObjectAsync(`system.adapter.${this.namespace}`);
|
|
80
|
+
if (instObj && instObj.common && instObj.common.schedule && instObj.common.schedule === "*/5 * * * *") {
|
|
81
|
+
instObj.common.schedule = `*/${Math.floor(Math.random() * 6)} * * * *`;
|
|
82
|
+
this.log.info(`Default schedule found and adjusted to spread calls better over the full hour!`);
|
|
83
|
+
await this.setForeignObjectAsync(`system.adapter.${this.namespace}`, instObj);
|
|
84
|
+
this.terminate ? this.terminate() : process.exit(0);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
} catch (err) {
|
|
88
|
+
this.log.error(`Could not check or adjust the schedule: ${err.message}`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
this.log.debug("MQTT active: " + mqtt_active);
|
|
92
|
+
this.log.debug("MQTT port: " + mqtt_port);
|
|
93
|
+
|
|
94
|
+
// Forecast Channel anlegen
|
|
95
|
+
const forecastChannelId = `${sensor_id}.forecast`;
|
|
96
|
+
|
|
97
|
+
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`;
|
|
101
|
+
await this.setObjectNotExistsAsync(systemStateId, {
|
|
102
|
+
type: "state",
|
|
103
|
+
common: {
|
|
104
|
+
name: "Data successfully received",
|
|
105
|
+
type: "boolean",
|
|
106
|
+
role: "indicator",
|
|
107
|
+
read: true,
|
|
108
|
+
write: false
|
|
109
|
+
},
|
|
110
|
+
native: {},
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
if (dataReceived === true) {
|
|
114
|
+
|
|
115
|
+
await this.setStateAsync(systemStateId, { val: true, ack: true });
|
|
116
|
+
|
|
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
|
+
});
|
|
130
|
+
|
|
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 });
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
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;
|
|
169
|
+
|
|
170
|
+
const val = this.contentDevData[key];
|
|
171
|
+
|
|
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";
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const id = `${devDataChannelId}.${key}`;
|
|
194
|
+
|
|
195
|
+
await this.setObjectNotExistsAsync(id, {
|
|
196
|
+
type: "state",
|
|
197
|
+
common,
|
|
198
|
+
native: {},
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
await this.setStateAsync(id, { val: val, ack: true });
|
|
202
|
+
}
|
|
203
|
+
} else {
|
|
204
|
+
this.log.error("Error loading data in main()");
|
|
205
|
+
await this.setStateAsync(systemStateId, { val: false, ack: true });
|
|
206
|
+
}
|
|
207
|
+
} catch (error) {
|
|
208
|
+
this.log.error("Unexpected error in onReady(): " + error.message);
|
|
209
|
+
} finally {
|
|
210
|
+
if (client) {
|
|
211
|
+
client.end();
|
|
212
|
+
}
|
|
213
|
+
this.terminate ? this.terminate("Everything done. Going to terminate till next schedule", 0) : process.exit(0);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
async sendMqtt(sensor_id, mqtt_active, client, topic, wert) {
|
|
218
|
+
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() : "";
|
|
222
|
+
}
|
|
223
|
+
client.publish("WEATHERSENSE/" + sensor_id.toString() + "/" + topic, wert);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
async createOrUpdateForecastDPs(forecastChannelId, forecasts, celsius) {
|
|
228
|
+
if (!forecasts || !forecastChannelId) return;
|
|
229
|
+
|
|
230
|
+
// Struktur der Forecast-Items pro Tag
|
|
231
|
+
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: "" },
|
|
237
|
+
];
|
|
238
|
+
|
|
239
|
+
for (let i = 0; i < forecasts.length; i++) {
|
|
240
|
+
const forecast = forecasts[i];
|
|
241
|
+
if (!forecast) continue;
|
|
242
|
+
|
|
243
|
+
for (const item of forecastItems) {
|
|
244
|
+
const id = `${forecastChannelId}.${i}.${item.id}`;
|
|
245
|
+
await this.setObjectNotExistsAsync(id, {
|
|
246
|
+
type: "state",
|
|
247
|
+
common: {
|
|
248
|
+
name: item.id,
|
|
249
|
+
type: item.type,
|
|
250
|
+
role: item.role,
|
|
251
|
+
unit: item.unit,
|
|
252
|
+
read: true,
|
|
253
|
+
write: false,
|
|
254
|
+
},
|
|
255
|
+
native: {},
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
let val = forecast[item.id];
|
|
259
|
+
if ((item.id === "high" || item.id === "low") && typeof val === "number") {
|
|
260
|
+
if (celsius) {
|
|
261
|
+
val = Number(((val - 32) / 1.8).toFixed(1));
|
|
262
|
+
} else {
|
|
263
|
+
val = Number(val);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (val != null) {
|
|
268
|
+
await this.setStateAsync(id, { val: val, ack: true });
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
async sendForecasts(client, forecasts, celsius, sensor_id) {
|
|
275
|
+
if (!client || !forecasts) return;
|
|
276
|
+
|
|
277
|
+
for (let i = 0; i < forecasts.length; i++) {
|
|
278
|
+
const forecast = forecasts[i];
|
|
279
|
+
if (!forecast) continue;
|
|
280
|
+
|
|
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 || "");
|
|
283
|
+
|
|
284
|
+
let tempHigh = forecast.high;
|
|
285
|
+
let tempLow = forecast.low;
|
|
286
|
+
|
|
287
|
+
if (celsius && typeof tempHigh === "number" && typeof tempLow === "number") {
|
|
288
|
+
tempHigh = Number(((tempHigh - 32) / 1.8).toFixed(1)); // Zahl, keine Zeichenkette
|
|
289
|
+
tempLow = Number(((tempLow - 32) / 1.8).toFixed(1));
|
|
290
|
+
} else {
|
|
291
|
+
// Wenn kein Celsius oder kein Zahlentyp, trotzdem als Zahl (sofern möglich), sonst 0 als Fallback
|
|
292
|
+
tempHigh = tempHigh != null ? Number(tempHigh) : 0;
|
|
293
|
+
tempLow = tempLow != null ? Number(tempLow) : 0;
|
|
294
|
+
}
|
|
295
|
+
|
|
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 || "");
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
async clearOldForecasts(sensor_id, client, maxDays = 6) {
|
|
303
|
+
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}`, "");
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
printAllKeys(d, prefix = "") {
|
|
311
|
+
if (typeof d === "object" && d !== null && !Array.isArray(d)) {
|
|
312
|
+
for (const [k, v] of Object.entries(d)) {
|
|
313
|
+
this.printAllKeys(v, `${prefix}${k}/`);
|
|
314
|
+
}
|
|
315
|
+
} else if (Array.isArray(d)) {
|
|
316
|
+
d.forEach((item, i) => {
|
|
317
|
+
this.printAllKeys(item, `${prefix}${i}/`);
|
|
318
|
+
});
|
|
319
|
+
} else {
|
|
320
|
+
this.log.debug(`${prefix}: ${d}`);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
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
|
+
// Funktion zum Erzeugen des MD5-Hashes
|
|
331
|
+
hashPassword(pw, key) {
|
|
332
|
+
const combined = pw + key;
|
|
333
|
+
return crypto.createHash("md5").update(combined, "utf8").digest("hex").toUpperCase();
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Login-Funktion
|
|
337
|
+
async login(USERNAME, PASSWORD) {
|
|
338
|
+
const MD5_KEY = "emax@pwd123";
|
|
339
|
+
const LOGIN_URL = "https://47.52.149.125/V1.0/account/login";
|
|
340
|
+
const hashed_pw = this.hashPassword(PASSWORD, MD5_KEY);
|
|
341
|
+
|
|
342
|
+
const headers = {
|
|
343
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
344
|
+
"User-Agent": "okhttp/3.14.9"
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
const payload = {
|
|
348
|
+
email: USERNAME,
|
|
349
|
+
pwd: hashed_pw
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
try {
|
|
353
|
+
const response = await axios.post(LOGIN_URL, payload, { headers });
|
|
354
|
+
|
|
355
|
+
this.log.debug("Status code:", response.status);
|
|
356
|
+
this.log.debug("Response:", response.data);
|
|
357
|
+
|
|
358
|
+
const data = response.data;
|
|
359
|
+
|
|
360
|
+
if (response.status === 200) {
|
|
361
|
+
if (data.status === 0 && data.content) {
|
|
362
|
+
const token = data.content.token;
|
|
363
|
+
this.log.debug("Login successful. Token: " + token.substring(0, 20) + "...");
|
|
364
|
+
return token;
|
|
365
|
+
} else {
|
|
366
|
+
this.log.error("Login failed:", data.message);
|
|
367
|
+
}
|
|
368
|
+
} else {
|
|
369
|
+
this.log.error("Server error");
|
|
370
|
+
}
|
|
371
|
+
} catch (error) {
|
|
372
|
+
this.log.error("Error during login:", error.message);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
return null;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
async devData(token) {
|
|
379
|
+
// Realtime Daten holen
|
|
380
|
+
this.log.debug("getRealtime data...");
|
|
381
|
+
|
|
382
|
+
const url = "https://emaxlife.net/V1.0/weather/devData/getRealtime";
|
|
383
|
+
const headers = {
|
|
384
|
+
"emaxtoken": token,
|
|
385
|
+
"Content-Type": "application/json"
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
try {
|
|
389
|
+
const response = await axios.get(url, {
|
|
390
|
+
headers,
|
|
391
|
+
timeout: 5000,
|
|
392
|
+
httpsAgent: new (require("https").Agent)({ rejectUnauthorized: false })
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
if (response.status === 200) {
|
|
396
|
+
this.log.debug("Data was received");
|
|
397
|
+
return response.data;
|
|
398
|
+
} else {
|
|
399
|
+
this.log.error(`devData > Status Code: ${response.status}`);
|
|
400
|
+
return "error";
|
|
401
|
+
}
|
|
402
|
+
} catch (error) {
|
|
403
|
+
if (error.response) {
|
|
404
|
+
this.log.error(`devData > Status Code: ${error.response.status}`);
|
|
405
|
+
} else {
|
|
406
|
+
this.log.error(`Error during request: ${error.message}`);
|
|
407
|
+
}
|
|
408
|
+
return "error";
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
async foreCast(token) {
|
|
413
|
+
// Forecast holen
|
|
414
|
+
this.log.debug("getForecast data...");
|
|
415
|
+
|
|
416
|
+
const url = "https://emaxlife.net/V1.0/weather/netData/getForecast";
|
|
417
|
+
const headers = {
|
|
418
|
+
"emaxtoken": token,
|
|
419
|
+
"Content-Type": "application/json"
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
try {
|
|
423
|
+
const response = await axios.get(url, {
|
|
424
|
+
headers,
|
|
425
|
+
timeout: 5000,
|
|
426
|
+
httpsAgent: new https.Agent({ rejectUnauthorized: false }) // entspricht verify=False
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
if (response.status === 200) {
|
|
430
|
+
this.log.debug("Data was received");
|
|
431
|
+
return response.data;
|
|
432
|
+
} else {
|
|
433
|
+
this.log.error(`foreCast > Status Code: ${response.status}`);
|
|
434
|
+
return "error";
|
|
435
|
+
}
|
|
436
|
+
} catch (error) {
|
|
437
|
+
if (error.response) {
|
|
438
|
+
this.log.error(`foreCast > Status Code: ${error.response.status}`);
|
|
439
|
+
} else {
|
|
440
|
+
this.log.error(`Error during request: ${error.message}`);
|
|
441
|
+
}
|
|
442
|
+
return "error";
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
async main(client, username, passwort, mqtt_active, sensor_id, storeJson, storeDir, celsius, forecastChannelId) {
|
|
447
|
+
const token = await this.login(username, passwort);
|
|
448
|
+
if (!token) {
|
|
449
|
+
this.log.error("No token received");
|
|
450
|
+
if (mqtt_active) {
|
|
451
|
+
await this.sendMqtt(sensor_id, mqtt_active, client, "dataReceived", "false");
|
|
452
|
+
client.end();
|
|
453
|
+
}
|
|
454
|
+
return false;
|
|
455
|
+
}
|
|
456
|
+
const devdata = await this.devData(token);
|
|
457
|
+
const forecast = await this.foreCast(token);
|
|
458
|
+
if (devdata === "error" || forecast === "error") {
|
|
459
|
+
this.log.error("No data received");
|
|
460
|
+
if (mqtt_active) {
|
|
461
|
+
await this.sendMqtt(sensor_id, mqtt_active, client, "dataReceived", "false");
|
|
462
|
+
client.end();
|
|
463
|
+
}
|
|
464
|
+
return false;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
if (storeJson) {
|
|
468
|
+
this.log.debug("Save devData.json to " + storeDir);
|
|
469
|
+
const json_object = JSON.stringify(devdata, null, 4);
|
|
470
|
+
fs.writeFileSync(path.join(storeDir, "devData.json"), json_object, "utf-8");
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
this.log.debug("devData JSON:");
|
|
474
|
+
this.printAllKeys(devdata);
|
|
475
|
+
|
|
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
|
+
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);
|
|
518
|
+
}
|
|
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);
|
|
524
|
+
}
|
|
525
|
+
|
|
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
|
+
}
|
|
546
|
+
}
|
|
547
|
+
}
|
|
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
|
+
|
|
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
|
|
564
|
+
|
|
565
|
+
const keyBase = `sensor_${type}_${channel}`;
|
|
566
|
+
|
|
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;
|
|
570
|
+
|
|
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;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
if (storeJson) {
|
|
581
|
+
this.log.debug("Save forecast.json to " + storeDir);
|
|
582
|
+
const json_object = JSON.stringify(forecast, null, 4);
|
|
583
|
+
fs.writeFileSync(path.join(storeDir, "forecast.json"), json_object, "utf-8");
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
this.printAllKeys(forecast);
|
|
587
|
+
|
|
588
|
+
const forecasts = forecast?.content?.forecast?.forecasts || [];
|
|
589
|
+
|
|
590
|
+
if (mqtt_active) {
|
|
591
|
+
await this.clearOldForecasts(sensor_id, client, 6);
|
|
592
|
+
await new Promise(r => setTimeout(r, 2000)); // sleep 2s
|
|
593
|
+
|
|
594
|
+
await this.sendForecasts(client, forecasts, celsius, sensor_id);
|
|
595
|
+
|
|
596
|
+
client.end(); // wie client.disconnect()
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
await this.createOrUpdateForecastDPs(forecastChannelId, forecasts, celsius);
|
|
600
|
+
|
|
601
|
+
return true;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
onUnload(callback) {
|
|
605
|
+
try {
|
|
606
|
+
callback();
|
|
607
|
+
} catch (e) {
|
|
608
|
+
callback();
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
if (require.main !== module) {
|
|
614
|
+
module.exports = (options) => new WeatherSense(options);
|
|
615
|
+
} else {
|
|
616
|
+
new WeatherSense();
|
|
617
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "iobroker.weathersense",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Read in data from WeatherSense",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Daniel Luginbühl",
|
|
7
|
+
"email": "webmaster@ltspiceusers.ch"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://github.com/ltspicer/ioBroker.weathersense",
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"keywords": [
|
|
12
|
+
"ioBroker",
|
|
13
|
+
"weathersense",
|
|
14
|
+
"weather",
|
|
15
|
+
"sense"
|
|
16
|
+
],
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git@github.com:ltspicer/ioBroker.weathersense.git"
|
|
20
|
+
},
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">= 20"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@iobroker/adapter-core": "^3.2.3",
|
|
26
|
+
"axios": "^1.10.0",
|
|
27
|
+
"mqtt": "^5.13.0"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@alcalzone/release-script": "^3.8.0",
|
|
31
|
+
"@alcalzone/release-script-plugin-iobroker": "^3.7.2",
|
|
32
|
+
"@alcalzone/release-script-plugin-license": "^3.7.0",
|
|
33
|
+
"@alcalzone/release-script-plugin-manual-review": "^3.7.0",
|
|
34
|
+
"@iobroker/adapter-dev": "^1.4.0",
|
|
35
|
+
"@iobroker/dev-server": "^0.7.8",
|
|
36
|
+
"@iobroker/testing": "^5.0.4",
|
|
37
|
+
"@tsconfig/node18": "^18.2.4",
|
|
38
|
+
"@types/chai": "^4.3.12",
|
|
39
|
+
"@types/chai-as-promised": "^8.0.1",
|
|
40
|
+
"@types/mocha": "^10.0.10",
|
|
41
|
+
"@types/node": "^24.0.8",
|
|
42
|
+
"@types/proxyquire": "^1.3.31",
|
|
43
|
+
"@types/sinon": "^17.0.4",
|
|
44
|
+
"@types/sinon-chai": "^3.2.12",
|
|
45
|
+
"chai": "^4.5.0",
|
|
46
|
+
"chai-as-promised": "^8.0.1",
|
|
47
|
+
"eslint": "^9.28.0",
|
|
48
|
+
"mocha": "^11.5.0",
|
|
49
|
+
"proxyquire": "^2.1.3",
|
|
50
|
+
"sinon": "^21.0.0",
|
|
51
|
+
"sinon-chai": "^3.7.0",
|
|
52
|
+
"typescript": "~5.8.3"
|
|
53
|
+
},
|
|
54
|
+
"main": "main.js",
|
|
55
|
+
"files": [
|
|
56
|
+
"admin{,/!(src)/**}/!(tsconfig|tsconfig.*|.eslintrc).{json,json5}",
|
|
57
|
+
"admin{,/!(src)/**}/*.{html,css,png,svg,jpg,js}",
|
|
58
|
+
"lib/",
|
|
59
|
+
"www/",
|
|
60
|
+
"io-package.json",
|
|
61
|
+
"LICENSE",
|
|
62
|
+
"main.js"
|
|
63
|
+
],
|
|
64
|
+
"scripts": {
|
|
65
|
+
"test:js": "mocha --config test/mocharc.custom.json \"{!(node_modules|test)/**/*.test.js,*.test.js,test/**/test!(PackageFiles|Startup).js}\"",
|
|
66
|
+
"test:package": "mocha test/package --exit",
|
|
67
|
+
"test:integration": "mocha test/integration --exit",
|
|
68
|
+
"test": "npm run test:js && npm run test:package",
|
|
69
|
+
"check": "tsc --noEmit -p tsconfig.check.json",
|
|
70
|
+
"lint": "eslint .",
|
|
71
|
+
"translate": "translate-adapter",
|
|
72
|
+
"release": "release-script",
|
|
73
|
+
"dev": "dev-server"
|
|
74
|
+
},
|
|
75
|
+
"bugs": {
|
|
76
|
+
"url": "https://github.com/ltspicer/ioBroker.weathersense/issues"
|
|
77
|
+
},
|
|
78
|
+
"readmeFilename": "README.md"
|
|
79
|
+
}
|