homebridge-envoy-solar-sensor 0.1.3 → 0.1.5

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.
@@ -1,74 +1,80 @@
1
1
  {
2
- "pluginAlias": "EnvoySolarSensor",
3
- "pluginType": "platform",
4
- "singular": true,
5
- "schema": {
6
- "type": "object",
7
- "required": ["host"],
8
- "properties": {
9
- "name": {
10
- "title": "Accessory Name",
11
- "type": "string",
12
- "default": "Solar Production",
13
- "description": "Name shown in HomeKit"
14
- },
15
- "host": {
16
- "title": "Envoy IP Address",
17
- "type": "string",
18
- "description": "IP address or hostname of the Enphase Envoy on your local network"
19
- },
20
- "protocol": {
21
- "title": "Protocol",
22
- "type": "string",
23
- "default": "http",
24
- "oneOf": [
25
- { "title": "HTTP", "enum": ["http"] },
26
- { "title": "HTTPS", "enum": ["https"] }
27
- ],
28
- "description": "Protocol used to connect to the Envoy"
29
- },
30
- "mode": {
31
- "title": "Envoy API Mode",
32
- "type": "string",
33
- "default": "productionJson",
34
- "oneOf": [
35
- {
36
- "title": "production.json (older Envoy)",
37
- "enum": ["productionJson"]
38
- },
39
- {
40
- "title": "api/v1/production (newer Envoy)",
41
- "enum": ["v1Production"]
42
- }
43
- ],
44
- "description": "Select which Envoy API endpoint is available on your device"
45
- },
46
- "pollIntervalSeconds": {
47
- "title": "Poll Interval",
48
- "type": "integer",
49
- "default": 10,
50
- "minimum": 5,
51
- "description": "How often the Envoy is polled for production data in seconds"
52
- },
53
- "onThresholdW": {
54
- "title": "On Threshold (Watts)",
55
- "type": "integer",
56
- "default": 80,
57
- "description": "Production level above which the sensor becomes active"
58
- },
59
- "offThresholdW": {
60
- "title": "Off Threshold (Watts)",
61
- "type": "integer",
62
- "default": 30,
63
- "description": "Production level below which the sensor becomes inactive"
64
- },
65
- "token": {
66
- "title": "Authentication Token",
67
- "type": "string",
68
- "default": "",
69
- "description": "Optional bearer token for secured Envoy endpoints"
70
- }
2
+ "pluginAlias": "EnvoySolarSensor",
3
+ "pluginType": "platform",
4
+ "singular": true,
5
+ "schema": {
6
+ "type": "object",
7
+ "required": ["host"],
8
+ "properties": {
9
+ "name": {
10
+ "title": "Accessory Name",
11
+ "type": "string",
12
+ "default": "Solar Production",
13
+ "description": "Name shown in HomeKit"
14
+ },
15
+ "host": {
16
+ "title": "Envoy Address",
17
+ "type": "string",
18
+ "default": "envoy.local",
19
+ "description": "IP address or hostname of the Enphase Envoy on your local network"
20
+ },
21
+ "protocol": {
22
+ "title": "Protocol",
23
+ "type": "string",
24
+ "default": "https",
25
+ "oneOf": [
26
+ { "title": "HTTPS", "enum": ["https"] },
27
+ { "title": "HTTP", "enum": ["http"] }
28
+ ],
29
+ "description": "Protocol used to connect to the Envoy"
30
+ },
31
+ "allowInsecureTLS": {
32
+ "title": "Allow Insecure TLS",
33
+ "type": "boolean",
34
+ "default": true,
35
+ "description": "Enable this if your Envoy uses a self-signed HTTPS certificate"
36
+ },
37
+ "mode": {
38
+ "title": "Envoy API Mode",
39
+ "type": "string",
40
+ "default": "productionJson",
41
+ "oneOf": [
42
+ {
43
+ "title": "production.json",
44
+ "enum": ["productionJson"]
45
+ },
46
+ {
47
+ "title": "api/v1/production",
48
+ "enum": ["v1Production"]
49
+ }
50
+ ],
51
+ "description": "Select the Envoy API endpoint to read production data"
52
+ },
53
+ "pollIntervalSeconds": {
54
+ "title": "Poll Interval (seconds)",
55
+ "type": "integer",
56
+ "default": 10,
57
+ "minimum": 5,
58
+ "description": "How often the Envoy is polled for production data"
59
+ },
60
+ "onThresholdW": {
61
+ "title": "On Threshold (W)",
62
+ "type": "integer",
63
+ "default": 80,
64
+ "description": "Production level above which the sensor becomes active"
65
+ },
66
+ "offThresholdW": {
67
+ "title": "Off Threshold (W)",
68
+ "type": "integer",
69
+ "default": 30,
70
+ "description": "Production level below which the sensor becomes inactive"
71
+ },
72
+ "token": {
73
+ "title": "Authentication Token",
74
+ "type": "string",
75
+ "default": "",
76
+ "description": "Bearer token from Enphase Entrez, paste token only without the word Bearer"
71
77
  }
72
78
  }
73
79
  }
74
-
80
+ }
package/index.js CHANGED
@@ -1,5 +1,7 @@
1
1
  'use strict';
2
2
 
3
+ const { Agent } = require('undici');
4
+
3
5
  const PLUGIN_NAME = 'homebridge-envoy-solar-sensor';
4
6
  const PLATFORM_NAME = 'EnvoySolarSensor';
5
7
 
@@ -31,10 +33,11 @@ class EnvoySolarPlatform {
31
33
  }
32
34
 
33
35
  setupAccessory() {
34
- const name = this.config.name || 'Zon Opwekking';
36
+ const name = this.config.name || 'Solar Production';
35
37
  const host = this.config.host;
38
+
36
39
  if (!host) {
37
- this.log.error('Config mist host. Voorbeeld: "host": "192.168.1.50"');
40
+ this.log.error('Config mist host. Voorbeeld host: "envoy.local" of "192.168.1.50"');
38
41
  return;
39
42
  }
40
43
 
@@ -56,14 +59,18 @@ class EnvoySolarPlatform {
56
59
  .setCharacteristic(this.Characteristic.SerialNumber, String(host));
57
60
 
58
61
  const service = this.accessory.getService(this.Service.ContactSensor)
59
- || this.accessory.addService(this.Service.ContactSensor, 'Opwekking Actief', 'production-active');
62
+ || this.accessory.addService(this.Service.ContactSensor, 'Production Active', 'production-active');
60
63
 
61
- service.setCharacteristic(this.Characteristic.Name, 'Opwekking Actief');
64
+ service.setCharacteristic(this.Characteristic.Name, 'Production Active');
62
65
 
63
66
  if (service.testCharacteristic(this.Characteristic.StatusActive)) {
64
67
  service.updateCharacteristic(this.Characteristic.StatusActive, true);
65
68
  }
66
69
 
70
+ if (service.testCharacteristic(this.Characteristic.StatusFault)) {
71
+ service.updateCharacteristic(this.Characteristic.StatusFault, this.Characteristic.StatusFault.NO_FAULT);
72
+ }
73
+
67
74
  this.log.info(`Accessoire klaar: ${name} op host ${host}`);
68
75
  }
69
76
 
@@ -78,7 +85,8 @@ class EnvoySolarPlatform {
78
85
  const watts = await this.readProductionWatts();
79
86
  this.updateState(watts);
80
87
  } catch (err) {
81
- this.log.warn(`Uitlezen mislukt: ${err && err.message ? err.message : String(err)}`);
88
+ const msg = err && err.message ? err.message : String(err);
89
+ this.log.warn(`Uitlezen mislukt: ${msg}`);
82
90
  this.markFault();
83
91
  }
84
92
  };
@@ -102,12 +110,18 @@ class EnvoySolarPlatform {
102
110
  }
103
111
 
104
112
  getToken() {
105
- return this.config.token;
113
+ const t = this.config.token;
114
+ if (!t) return '';
115
+ return String(t).trim();
106
116
  }
107
117
 
108
118
  getBaseUrl() {
109
- const proto = this.config.protocol || 'http';
110
- return `${proto}://${this.getHost()}`;
119
+ const protocol = this.config.protocol || 'https';
120
+ return `${protocol}://${this.getHost()}`;
121
+ }
122
+
123
+ isInsecureTLSEnabled() {
124
+ return Boolean(this.config.allowInsecureTLS);
111
125
  }
112
126
 
113
127
  markFault() {
@@ -180,10 +194,19 @@ class EnvoySolarPlatform {
180
194
  const productionArray = data && data.production;
181
195
  if (!Array.isArray(productionArray)) throw new Error('production.json mist production array');
182
196
 
183
- const eim = productionArray.find((x) => x && x.type === 'eim');
184
- const wNow = eim && eim.wNow;
197
+ const byType = (t) => productionArray.find((x) => x && x.type === t);
198
+
199
+ const eim = byType('eim');
200
+ const inverters = byType('inverters');
201
+ const production = byType('production');
202
+
203
+ const candidate = eim || production || inverters || productionArray[0];
204
+ const wNow = candidate && candidate.wNow;
205
+
206
+ if (typeof wNow !== 'number') {
207
+ throw new Error('production.json mist wNow in production items');
208
+ }
185
209
 
186
- if (typeof wNow !== 'number') throw new Error('production.json mist wNow (type eim)');
187
210
  return Math.max(0, wNow);
188
211
  }
189
212
 
@@ -199,13 +222,21 @@ class EnvoySolarPlatform {
199
222
 
200
223
  async fetchJson(url) {
201
224
  const headers = { Accept: 'application/json' };
225
+
202
226
  const token = this.getToken();
203
227
  if (token) headers.Authorization = `Bearer ${token}`;
204
228
 
205
- const res = await fetch(url, { headers });
229
+ const allowInsecureTLS = this.isInsecureTLSEnabled();
230
+
231
+ const dispatcher = url.startsWith('https://')
232
+ ? new Agent({ connect: { rejectUnauthorized: !allowInsecureTLS } })
233
+ : undefined;
234
+
235
+ const res = await fetch(url, { headers, dispatcher });
206
236
 
207
237
  if (!res.ok) {
208
- throw new Error(`HTTP ${res.status} op ${url}`);
238
+ const text = await res.text().catch(() => '');
239
+ throw new Error(`HTTP ${res.status} op ${url} ${text}`);
209
240
  }
210
241
 
211
242
  return await res.json();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "homebridge-envoy-solar-sensor",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Expose Enphase Envoy solar production as a HomeKit sensor for daylight-based automations",
5
5
  "main": "index.js",
6
6
  "license": "MIT",
@@ -34,6 +34,9 @@
34
34
  "peerDependencies": {
35
35
  "homebridge": ">=1.6.0"
36
36
  },
37
+ "dependencies": {
38
+ "undici": "^6.20.1"
39
+ },
37
40
  "files": [
38
41
  "index.js",
39
42
  "config.schema.json",