homebridge-pvs6 1.0.0 → 1.1.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/README.md +42 -17
- package/config.schema.json +10 -4
- package/dist/platform.d.ts +9 -1
- package/dist/platform.d.ts.map +1 -1
- package/dist/platform.js +107 -14
- package/dist/platform.js.map +1 -1
- package/dist/pvs6Discovery.d.ts +7 -0
- package/dist/pvs6Discovery.d.ts.map +1 -0
- package/dist/pvs6Discovery.js +95 -0
- package/dist/pvs6Discovery.js.map +1 -0
- package/dist/settings.d.ts +3 -2
- package/dist/settings.d.ts.map +1 -1
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -6,11 +6,12 @@ A [Homebridge](https://homebridge.io) plugin for the [SunStrong PVS6](https://su
|
|
|
6
6
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
9
|
-
- **Solar Production** accessory — always registered; real-time PV output (W), lifetime generation (kWh)
|
|
9
|
+
- **Solar Production** accessory — always registered; real-time PV output (W), lifetime generation (kWh)
|
|
10
10
|
- **Grid Import + Grid Export** accessory pair — optional (default enabled); each shows non-negative watts and its own Eve app history
|
|
11
11
|
- Import and Export accessories are always registered together as a pair, making import/export history immediately readable as separate graphs
|
|
12
12
|
- Up to 7 days of native power history in the Eve app via [fakegato-history](https://github.com/simont77/fakegato-history)
|
|
13
13
|
- Polls the PVS6 **local** FCGI API — no SunStrong Connect, no cloud dependency
|
|
14
|
+
- **Auto-discovers** the PVS6 on your local network via mDNS — no IP address or serial number required in config
|
|
14
15
|
- Automatically identifies production and consumption CT meters from the PVS6 device list
|
|
15
16
|
- Designed to complement [homebridge-rainforest-eagle3](https://github.com/dacarson/homebridge-rainforest-eagle3) for a complete solar + grid picture in Apple Home
|
|
16
17
|
|
|
@@ -47,10 +48,9 @@ Grid Import and Grid Export are always registered together — enabling `accesso
|
|
|
47
48
|
|
|
48
49
|
## Requirements
|
|
49
50
|
|
|
50
|
-
- [Homebridge](https://homebridge.io)
|
|
51
|
+
- [Homebridge](https://homebridge.io) v2.0 or later
|
|
51
52
|
- Node.js 18 or later
|
|
52
53
|
- SunStrong PVS6 on the same local network as your Homebridge host
|
|
53
|
-
- The PVS6's **full serial number** (printed on the unit label)
|
|
54
54
|
|
|
55
55
|
---
|
|
56
56
|
|
|
@@ -66,9 +66,11 @@ npm install -g homebridge-pvs6
|
|
|
66
66
|
|
|
67
67
|
## Configuration
|
|
68
68
|
|
|
69
|
-
|
|
69
|
+
The Homebridge UI exposes an **Auto Discover** checkbox (on by default). When checked, the plugin finds the PVS6 on your local network automatically via mDNS — Host and Serial Number fields are ignored even if filled in. When unchecked, Host and Serial Number are used directly. Either way, both fields are always visible and their values are preserved in `config.json`.
|
|
70
70
|
|
|
71
|
-
|
|
71
|
+
### Auto Discover (default)
|
|
72
|
+
|
|
73
|
+
The checkbox is on by default. No other fields are required:
|
|
72
74
|
|
|
73
75
|
```json
|
|
74
76
|
{
|
|
@@ -76,15 +78,35 @@ Minimal config (solar only — grid disabled):
|
|
|
76
78
|
{
|
|
77
79
|
"platform": "PVS6",
|
|
78
80
|
"name": "PVS6",
|
|
79
|
-
"
|
|
80
|
-
|
|
81
|
-
|
|
81
|
+
"autoDiscover": true
|
|
82
|
+
}
|
|
83
|
+
]
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
At startup the plugin scans for `_pvs6._tcp` on the local network. The PVS6 broadcasts its hostname (`pvs.local`) and serial number via mDNS, so both are discovered automatically. Discovery times out after 30 seconds with an actionable error if no device is found.
|
|
88
|
+
|
|
89
|
+
If the device becomes unreachable for 2 hours (e.g. the router assigned it a new IP), the plugin runs mDNS discovery again automatically and reconnects.
|
|
90
|
+
|
|
91
|
+
### Manual config
|
|
92
|
+
|
|
93
|
+
Uncheck **Auto Discover** and supply the host and serial number directly. This skips mDNS entirely and is useful if you assign the PVS6 a static IP or DHCP reservation:
|
|
94
|
+
|
|
95
|
+
```json
|
|
96
|
+
{
|
|
97
|
+
"platforms": [
|
|
98
|
+
{
|
|
99
|
+
"platform": "PVS6",
|
|
100
|
+
"name": "PVS6",
|
|
101
|
+
"autoDiscover": false,
|
|
102
|
+
"host": "pvs.local",
|
|
103
|
+
"serialNumber": "ZT231385000549A1234"
|
|
82
104
|
}
|
|
83
105
|
]
|
|
84
106
|
}
|
|
85
107
|
```
|
|
86
108
|
|
|
87
|
-
|
|
109
|
+
### Full config (all options)
|
|
88
110
|
|
|
89
111
|
```json
|
|
90
112
|
{
|
|
@@ -92,7 +114,8 @@ Standard config (solar + grid import/export pair):
|
|
|
92
114
|
{
|
|
93
115
|
"platform": "PVS6",
|
|
94
116
|
"name": "PVS6",
|
|
95
|
-
"
|
|
117
|
+
"autoDiscover": true,
|
|
118
|
+
"host": "pvs.local",
|
|
96
119
|
"serialNumber": "ZT231385000549A1234",
|
|
97
120
|
"pollInterval": 10,
|
|
98
121
|
"solarName": "Solar Production",
|
|
@@ -108,17 +131,20 @@ Standard config (solar + grid import/export pair):
|
|
|
108
131
|
| Field | Type | Required | Default | Description |
|
|
109
132
|
|---|---|---|---|---|
|
|
110
133
|
| `platform` | string | yes | — | Must be `PVS6` |
|
|
111
|
-
| `
|
|
112
|
-
| `
|
|
134
|
+
| `autoDiscover` | boolean | no | `true` | When `true`, find the PVS6 via mDNS — `host` and `serialNumber` are ignored. Re-discovers automatically after 2 hours of unreachability |
|
|
135
|
+
| `host` | string | no* | — | IP address or hostname of the PVS6. Used only when `autoDiscover` is `false` |
|
|
136
|
+
| `serialNumber` | string | no* | — | Full PVS6 serial number. The last 5 characters are used as the API password. Used only when `autoDiscover` is `false` |
|
|
113
137
|
| `pollInterval` | integer | no | `10` | Seconds between polls. Minimum enforced: `5` |
|
|
114
138
|
| `accessories.grid` | boolean | no | `true` | Enable the Grid Import + Grid Export accessory pair. Set `false` to disable both |
|
|
115
139
|
| `solarName` | string | no | `"Solar Production"` | HomeKit display name for the Solar Production accessory |
|
|
116
140
|
| `gridName` | string | no | `"Grid Meter - Import"` | HomeKit display name for the Grid Import accessory |
|
|
117
141
|
| `gridExportName` | string | no | `"Grid Meter - Export"` | HomeKit display name for the Grid Export accessory |
|
|
118
142
|
|
|
143
|
+
\* Required when `autoDiscover` is `false`.
|
|
144
|
+
|
|
119
145
|
### Finding Your Serial Number
|
|
120
146
|
|
|
121
|
-
The serial number is printed on the label on the PVS6 unit (format: `ZT...`). It is also visible in the SunStrong Connect app. The last 5 characters
|
|
147
|
+
The serial number is printed on the label on the PVS6 unit (format: `ZT...`). It is also visible in the SunStrong Connect app and is broadcast by the PVS6 itself via mDNS (which is how Auto Discover retrieves it). The last 5 characters are used as the local API login password — no separate password configuration is required.
|
|
122
148
|
|
|
123
149
|
---
|
|
124
150
|
|
|
@@ -132,7 +158,6 @@ The serial number is printed on the label on the PVS6 unit (format: `ZT...`). It
|
|
|
132
158
|
| OutletInUse | Always `true` | Required by Eve Energy |
|
|
133
159
|
| Eve Watts | `pv_p × 1000` | Real-time solar output in Watts |
|
|
134
160
|
| Eve kWh | `pv_en` | Lifetime generation in kWh |
|
|
135
|
-
| Eve Voltage | Production meter `v12V` | AC line voltage |
|
|
136
161
|
|
|
137
162
|
### Grid Import
|
|
138
163
|
|
|
@@ -142,9 +167,8 @@ The serial number is printed on the label on the PVS6 unit (format: `ZT...`). It
|
|
|
142
167
|
| OutletInUse | Always `true` | Required by Eve Energy |
|
|
143
168
|
| Eve Watts | `max(0, net_p) × 1000` | Import watts; zero when net-exporting |
|
|
144
169
|
| Eve kWh | `posLtea3phsumKwh` | Lifetime imported energy from consumption CT meter |
|
|
145
|
-
| Eve Voltage | Consumption meter `v12V` | AC line voltage |
|
|
146
170
|
|
|
147
|
-
### Grid Export *(optional — enabled with `
|
|
171
|
+
### Grid Export *(optional — enabled with `accessories.grid: true`)*
|
|
148
172
|
|
|
149
173
|
| Characteristic | Source | Notes |
|
|
150
174
|
|---|---|---|
|
|
@@ -152,7 +176,6 @@ The serial number is printed on the label on the PVS6 unit (format: `ZT...`). It
|
|
|
152
176
|
| OutletInUse | Always `true` | Required by Eve Energy |
|
|
153
177
|
| Eve Watts | `max(0, −net_p) × 1000` | Export watts; zero when importing |
|
|
154
178
|
| Eve kWh | `negLtea3phsumKwh` | Lifetime exported energy from consumption CT meter |
|
|
155
|
-
| Eve Voltage | Consumption meter `v12V` | AC line voltage |
|
|
156
179
|
|
|
157
180
|
---
|
|
158
181
|
|
|
@@ -161,6 +184,8 @@ The serial number is printed on the label on the PVS6 unit (format: `ZT...`). It
|
|
|
161
184
|
The plugin is designed to be resilient to transient PVS6 failures:
|
|
162
185
|
|
|
163
186
|
- If the PVS6 is unreachable at startup, authentication retries every 30 seconds
|
|
187
|
+
- If mDNS discovery times out (30 s) at startup, the platform logs an error and does not start — set `host` and `serialNumber` with `autoDiscover: false` to bypass discovery
|
|
188
|
+
- If the device is unreachable for 2 hours and `autoDiscover` is not `false`, mDNS re-discovery runs automatically to pick up a changed IP or serial
|
|
164
189
|
- Overlapping poll cycles are skipped — only one request is in flight at a time
|
|
165
190
|
- Poll cycles are skipped (not fatal) on timeout (>10 s), JSON parse errors, or HTTP 5xx responses
|
|
166
191
|
- HTTP 5xx triggers a short backoff to avoid overwhelming the PVS6's embedded HTTP server
|
package/config.schema.json
CHANGED
|
@@ -10,17 +10,23 @@
|
|
|
10
10
|
"type": "string",
|
|
11
11
|
"default": "PVS6"
|
|
12
12
|
},
|
|
13
|
+
"autoDiscover": {
|
|
14
|
+
"title": "Auto Discover",
|
|
15
|
+
"type": "boolean",
|
|
16
|
+
"default": true,
|
|
17
|
+
"description": "Automatically find the PVS6 on your local network via mDNS. When enabled, Host and Serial Number are ignored. If the device becomes unreachable for 2 hours, discovery runs again automatically."
|
|
18
|
+
},
|
|
13
19
|
"host": {
|
|
14
20
|
"title": "Host",
|
|
15
21
|
"type": "string",
|
|
16
|
-
"placeholder": "
|
|
17
|
-
"description": "IP address or hostname of the PVS6
|
|
22
|
+
"placeholder": "pvs.local",
|
|
23
|
+
"description": "IP address or hostname of the PVS6. Ignored when Auto Discover is enabled."
|
|
18
24
|
},
|
|
19
25
|
"serialNumber": {
|
|
20
26
|
"title": "Serial Number",
|
|
21
27
|
"type": "string",
|
|
22
28
|
"placeholder": "ZT231385000549F1084",
|
|
23
|
-
"description": "Full PVS6 serial number. The last 5 characters are used as the login password."
|
|
29
|
+
"description": "Full PVS6 serial number. The last 5 characters are used as the login password. Ignored when Auto Discover is enabled."
|
|
24
30
|
},
|
|
25
31
|
"pollInterval": {
|
|
26
32
|
"title": "Poll Interval (seconds)",
|
|
@@ -59,6 +65,6 @@
|
|
|
59
65
|
"description": "HomeKit display name for the Grid Export accessory."
|
|
60
66
|
}
|
|
61
67
|
},
|
|
62
|
-
"required": [
|
|
68
|
+
"required": []
|
|
63
69
|
}
|
|
64
70
|
}
|
package/dist/platform.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ export declare class PVS6Platform implements DynamicPlatformPlugin {
|
|
|
7
7
|
readonly eveChars: EveChars;
|
|
8
8
|
readonly log: Logger;
|
|
9
9
|
readonly accessories: PlatformAccessory[];
|
|
10
|
-
private
|
|
10
|
+
private client;
|
|
11
11
|
private readonly config;
|
|
12
12
|
private readonly pollIntervalMs;
|
|
13
13
|
private solarAccessory?;
|
|
@@ -16,10 +16,18 @@ export declare class PVS6Platform implements DynamicPlatformPlugin {
|
|
|
16
16
|
private pollTimer?;
|
|
17
17
|
private pollInFlight;
|
|
18
18
|
private backedOff;
|
|
19
|
+
private authGeneration;
|
|
20
|
+
private lastSuccessfulPollTime;
|
|
21
|
+
private rediscovering;
|
|
19
22
|
constructor(log: Logger, config: PlatformConfig, api: API);
|
|
20
23
|
configureAccessory(accessory: PlatformAccessory): void;
|
|
24
|
+
private resolveConfig;
|
|
21
25
|
private setupAccessories;
|
|
22
26
|
private getOrCreateAccessory;
|
|
27
|
+
private get discoveryEnabled();
|
|
28
|
+
private checkRediscovery;
|
|
29
|
+
private triggerRediscovery;
|
|
30
|
+
private stopPolling;
|
|
23
31
|
private startAuthAndPolling;
|
|
24
32
|
private startPolling;
|
|
25
33
|
private enterBackoff;
|
package/dist/platform.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"platform.d.ts","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,GAAG,EACH,cAAc,EACd,qBAAqB,EACrB,MAAM,EACN,iBAAiB,EACjB,cAAc,EACd,OAAO,EACR,MAAM,YAAY,CAAC;AAOpB,OAAO,EAA4B,QAAQ,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"platform.d.ts","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,GAAG,EACH,cAAc,EACd,qBAAqB,EACrB,MAAM,EACN,iBAAiB,EACjB,cAAc,EACd,OAAO,EACR,MAAM,YAAY,CAAC;AAOpB,OAAO,EAA4B,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAS1E,qBAAa,YAAa,YAAW,qBAAqB;IACxD,SAAgB,OAAO,EAAE,OAAO,OAAO,CAAC;IACxC,SAAgB,cAAc,EAAE,OAAO,cAAc,CAAC;IACtD,SAAgB,GAAG,EAAE,GAAG,CAAC;IACzB,SAAgB,QAAQ,EAAE,QAAQ,CAAC;IACnC,SAAgB,GAAG,EAAE,MAAM,CAAC;IAE5B,SAAgB,WAAW,EAAE,iBAAiB,EAAE,CAAM;IAGtD,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAa;IACpC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IAExC,OAAO,CAAC,cAAc,CAAC,CAAiB;IACxC,OAAO,CAAC,mBAAmB,CAAC,CAAsB;IAClD,OAAO,CAAC,mBAAmB,CAAC,CAAsB;IAElD,OAAO,CAAC,SAAS,CAAC,CAAiC;IACnD,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,SAAS,CAAS;IAG1B,OAAO,CAAC,cAAc,CAAK;IAG3B,OAAO,CAAC,sBAAsB,CAAc;IAC5C,OAAO,CAAC,aAAa,CAAS;gBAG5B,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,cAAc,EACtB,GAAG,EAAE,GAAG;IA2CV,kBAAkB,CAAC,SAAS,EAAE,iBAAiB,GAAG,IAAI;YAUxC,aAAa;IAyB3B,OAAO,CAAC,gBAAgB;IAwCxB,OAAO,CAAC,oBAAoB;IAiB5B,OAAO,KAAK,gBAAgB,GAE3B;IAID,OAAO,CAAC,gBAAgB;YAQV,kBAAkB;IAwBhC,OAAO,CAAC,WAAW;IASnB,OAAO,CAAC,mBAAmB;IAsB3B,OAAO,CAAC,YAAY;IAsDpB,OAAO,CAAC,YAAY;CAOrB"}
|
package/dist/platform.js
CHANGED
|
@@ -7,15 +7,22 @@ const solarAccessory_1 = require("./solarAccessory");
|
|
|
7
7
|
const gridImportAccessory_1 = require("./gridImportAccessory");
|
|
8
8
|
const gridExportAccessory_1 = require("./gridExportAccessory");
|
|
9
9
|
const eveCharacteristics_1 = require("./eveCharacteristics");
|
|
10
|
+
const pvs6Discovery_1 = require("./pvs6Discovery");
|
|
10
11
|
const MIN_POLL_INTERVAL = 5;
|
|
11
12
|
const AUTH_RETRY_MS = 30000;
|
|
12
13
|
const AUTH_BACKOFF_MS = 60000;
|
|
13
14
|
const OVERLOAD_BACKOFF_MS = 15000;
|
|
15
|
+
const REDISCOVERY_THRESHOLD_MS = 2 * 60 * 60 * 1000; // 2 hours
|
|
14
16
|
class PVS6Platform {
|
|
15
17
|
constructor(log, config, api) {
|
|
16
18
|
this.accessories = [];
|
|
17
19
|
this.pollInFlight = false;
|
|
18
20
|
this.backedOff = false;
|
|
21
|
+
// Incremented when re-discovery fires so stale auth-retry timeouts know to abort.
|
|
22
|
+
this.authGeneration = 0;
|
|
23
|
+
// Tracks when the last successful poll data was received, for re-discovery gating.
|
|
24
|
+
this.lastSuccessfulPollTime = Date.now();
|
|
25
|
+
this.rediscovering = false;
|
|
19
26
|
this.log = log;
|
|
20
27
|
this.api = api;
|
|
21
28
|
this.Service = api.hap.Service;
|
|
@@ -23,27 +30,26 @@ class PVS6Platform {
|
|
|
23
30
|
this.eveChars = (0, eveCharacteristics_1.createEveCharacteristics)(api);
|
|
24
31
|
const pvs6Config = config;
|
|
25
32
|
this.config = pvs6Config;
|
|
26
|
-
if (!pvs6Config.host || !pvs6Config.serialNumber) {
|
|
27
|
-
this.log.error('Missing required config: host and serialNumber are required.');
|
|
28
|
-
this.client = new pvs6Client_1.PVS6Client('', '', log);
|
|
29
|
-
this.pollIntervalMs = MIN_POLL_INTERVAL * 1000;
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
33
|
let pollInterval = pvs6Config.pollInterval ?? 10;
|
|
33
34
|
if (pollInterval < MIN_POLL_INTERVAL) {
|
|
34
35
|
this.log.warn(`pollInterval ${pollInterval}s is below minimum; clamping to ${MIN_POLL_INTERVAL}s`);
|
|
35
36
|
pollInterval = MIN_POLL_INTERVAL;
|
|
36
37
|
}
|
|
37
38
|
this.pollIntervalMs = pollInterval * 1000;
|
|
38
|
-
this.client = new pvs6Client_1.PVS6Client(pvs6Config.host, pvs6Config.serialNumber, log);
|
|
39
39
|
this.log.info('Finished initializing platform:', config.name ?? settings_1.PLATFORM_NAME);
|
|
40
40
|
this.api.on('didFinishLaunching', () => {
|
|
41
|
-
this.log.debug('didFinishLaunching — setting up accessories
|
|
42
|
-
// fakegato-history must be loaded after the api is fully initialised
|
|
41
|
+
this.log.debug('didFinishLaunching — resolving config, setting up accessories');
|
|
43
42
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
44
43
|
const FakeGatoHistoryService = require('fakegato-history')(this.api);
|
|
45
|
-
this.
|
|
46
|
-
|
|
44
|
+
this.resolveConfig()
|
|
45
|
+
.then(({ host, serialNumber }) => {
|
|
46
|
+
this.client = new pvs6Client_1.PVS6Client(host, serialNumber, this.log);
|
|
47
|
+
this.setupAccessories(FakeGatoHistoryService, serialNumber);
|
|
48
|
+
this.startAuthAndPolling();
|
|
49
|
+
})
|
|
50
|
+
.catch((err) => {
|
|
51
|
+
this.log.error(`Cannot start PVS6 platform: ${err.message}`);
|
|
52
|
+
});
|
|
47
53
|
});
|
|
48
54
|
this.api.on('shutdown', () => {
|
|
49
55
|
if (this.pollTimer) {
|
|
@@ -56,10 +62,35 @@ class PVS6Platform {
|
|
|
56
62
|
this.log.info('Loading accessory from cache:', accessory.displayName);
|
|
57
63
|
this.accessories.push(accessory);
|
|
58
64
|
}
|
|
65
|
+
// Determines host + serialNumber from config or mDNS discovery.
|
|
66
|
+
//
|
|
67
|
+
// autoDiscover: true → always run mDNS discovery; host/serialNumber in config are ignored.
|
|
68
|
+
// autoDiscover: false → must supply both host and serialNumber manually; no mDNS.
|
|
69
|
+
// autoDiscover: unset → use config values if both are present, otherwise discover what's missing.
|
|
70
|
+
async resolveConfig() {
|
|
71
|
+
const { host, serialNumber, autoDiscover } = this.config;
|
|
72
|
+
if (autoDiscover === false) {
|
|
73
|
+
if (!host || !serialNumber) {
|
|
74
|
+
throw new Error('host and serialNumber are required when Auto Discover is disabled');
|
|
75
|
+
}
|
|
76
|
+
return { host, serialNumber };
|
|
77
|
+
}
|
|
78
|
+
if (autoDiscover === true) {
|
|
79
|
+
return (0, pvs6Discovery_1.discoverPVS6)(this.log);
|
|
80
|
+
}
|
|
81
|
+
// autoDiscover not set: use config if complete, otherwise discover what's missing.
|
|
82
|
+
if (host && serialNumber) {
|
|
83
|
+
return { host, serialNumber };
|
|
84
|
+
}
|
|
85
|
+
const discovered = await (0, pvs6Discovery_1.discoverPVS6)(this.log);
|
|
86
|
+
return {
|
|
87
|
+
host: host ?? discovered.host,
|
|
88
|
+
serialNumber: serialNumber ?? discovered.serialNumber,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
59
91
|
setupAccessories(
|
|
60
92
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
61
|
-
FakeGatoHistoryService) {
|
|
62
|
-
const { serialNumber } = this.config;
|
|
93
|
+
FakeGatoHistoryService, serialNumber) {
|
|
63
94
|
// Solar Production is always registered.
|
|
64
95
|
const solarName = this.config.solarName ?? 'Solar Production';
|
|
65
96
|
const solarUuid = this.api.hap.uuid.generate(`${serialNumber}-solar`);
|
|
@@ -88,20 +119,81 @@ class PVS6Platform {
|
|
|
88
119
|
this.accessories.push(accessory);
|
|
89
120
|
return accessory;
|
|
90
121
|
}
|
|
122
|
+
// autoDiscover === false means the user pinned specific host/serial — never re-discover.
|
|
123
|
+
get discoveryEnabled() {
|
|
124
|
+
return this.config.autoDiscover !== false;
|
|
125
|
+
}
|
|
126
|
+
// If the device has been unreachable for 2 hours, attempt mDNS re-discovery.
|
|
127
|
+
// Not triggered when autoDiscover is explicitly false.
|
|
128
|
+
checkRediscovery() {
|
|
129
|
+
if (!this.discoveryEnabled)
|
|
130
|
+
return;
|
|
131
|
+
if (this.rediscovering)
|
|
132
|
+
return;
|
|
133
|
+
if (Date.now() - this.lastSuccessfulPollTime > REDISCOVERY_THRESHOLD_MS) {
|
|
134
|
+
void this.triggerRediscovery();
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
async triggerRediscovery() {
|
|
138
|
+
if (this.rediscovering)
|
|
139
|
+
return;
|
|
140
|
+
this.rediscovering = true;
|
|
141
|
+
this.log.warn('PVS6 unreachable for 2+ hours — attempting mDNS re-discovery...');
|
|
142
|
+
try {
|
|
143
|
+
const { host, serialNumber } = await (0, pvs6Discovery_1.discoverPVS6)(this.log);
|
|
144
|
+
this.log.info(`Re-discovery found PVS6 at ${host} (serial: ${serialNumber}) — reconnecting`);
|
|
145
|
+
// Increment generation to cancel any pending auth-retry timeouts.
|
|
146
|
+
this.authGeneration++;
|
|
147
|
+
this.stopPolling();
|
|
148
|
+
this.client = new pvs6Client_1.PVS6Client(host, serialNumber, this.log);
|
|
149
|
+
this.lastSuccessfulPollTime = Date.now();
|
|
150
|
+
this.startAuthAndPolling();
|
|
151
|
+
}
|
|
152
|
+
catch (err) {
|
|
153
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
154
|
+
this.log.warn(`mDNS re-discovery failed: ${msg} — will try again in 2 hours`);
|
|
155
|
+
// Reset the clock so the next attempt is 2 hours from now, not immediately.
|
|
156
|
+
this.lastSuccessfulPollTime = Date.now();
|
|
157
|
+
}
|
|
158
|
+
finally {
|
|
159
|
+
this.rediscovering = false;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
stopPolling() {
|
|
163
|
+
if (this.pollTimer) {
|
|
164
|
+
clearInterval(this.pollTimer);
|
|
165
|
+
this.pollTimer = undefined;
|
|
166
|
+
}
|
|
167
|
+
this.pollInFlight = false;
|
|
168
|
+
this.backedOff = false;
|
|
169
|
+
}
|
|
91
170
|
startAuthAndPolling() {
|
|
171
|
+
if (this.rediscovering)
|
|
172
|
+
return;
|
|
173
|
+
const generation = this.authGeneration;
|
|
92
174
|
this.client
|
|
93
175
|
.authenticate()
|
|
94
176
|
.then(() => {
|
|
177
|
+
if (generation !== this.authGeneration)
|
|
178
|
+
return;
|
|
95
179
|
this.log.info('Authenticated with PVS6 — starting polling');
|
|
96
180
|
this.startPolling();
|
|
97
181
|
})
|
|
98
182
|
.catch((err) => {
|
|
183
|
+
if (generation !== this.authGeneration)
|
|
184
|
+
return;
|
|
185
|
+
this.checkRediscovery();
|
|
99
186
|
this.log.error(`Authentication failed: ${err.message}. Retrying in ${AUTH_RETRY_MS / 1000}s`);
|
|
100
|
-
setTimeout(() =>
|
|
187
|
+
setTimeout(() => {
|
|
188
|
+
if (generation !== this.authGeneration)
|
|
189
|
+
return;
|
|
190
|
+
this.startAuthAndPolling();
|
|
191
|
+
}, AUTH_RETRY_MS);
|
|
101
192
|
});
|
|
102
193
|
}
|
|
103
194
|
startPolling() {
|
|
104
195
|
this.pollTimer = setInterval(async () => {
|
|
196
|
+
this.checkRediscovery();
|
|
105
197
|
if (this.pollInFlight) {
|
|
106
198
|
this.log.debug('Poll skipped — previous request still in flight');
|
|
107
199
|
return;
|
|
@@ -113,6 +205,7 @@ class PVS6Platform {
|
|
|
113
205
|
this.pollInFlight = true;
|
|
114
206
|
try {
|
|
115
207
|
const reading = await this.client.poll();
|
|
208
|
+
this.lastSuccessfulPollTime = Date.now();
|
|
116
209
|
this.solarAccessory?.updateValues(reading);
|
|
117
210
|
this.gridImportAccessory?.updateValues(reading);
|
|
118
211
|
this.gridExportAccessory?.updateValues(reading);
|
package/dist/platform.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"platform.js","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":";;;AAUA,yCAAoE;AACpE,6CAAqD;AACrD,qDAAkD;AAClD,+DAA4D;AAC5D,+DAA4D;AAC5D,6DAA0E;
|
|
1
|
+
{"version":3,"file":"platform.js","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pvs6Discovery.d.ts","sourceRoot":"","sources":["../src/pvs6Discovery.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAEpC,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;CACtB;AA2GD,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAEtE"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.discoverPVS6 = discoverPVS6;
|
|
7
|
+
const bonjour_hap_1 = __importDefault(require("bonjour-hap"));
|
|
8
|
+
const child_process_1 = require("child_process");
|
|
9
|
+
const DISCOVERY_TIMEOUT_MS = 30000;
|
|
10
|
+
const PVS6_SERVICE_TYPE = 'pvs6';
|
|
11
|
+
function discoverPVS6Bonjour(log) {
|
|
12
|
+
return new Promise((resolve, reject) => {
|
|
13
|
+
log.info(`mDNS: scanning for _${PVS6_SERVICE_TYPE}._tcp — will time out in ${DISCOVERY_TIMEOUT_MS / 1000}s`);
|
|
14
|
+
const bonjour = (0, bonjour_hap_1.default)();
|
|
15
|
+
const browser = bonjour.find({ type: PVS6_SERVICE_TYPE });
|
|
16
|
+
const cleanup = (cb) => {
|
|
17
|
+
browser.stop();
|
|
18
|
+
bonjour.destroy(cb);
|
|
19
|
+
};
|
|
20
|
+
const timer = setTimeout(() => {
|
|
21
|
+
cleanup(() => {
|
|
22
|
+
reject(new Error(`mDNS discovery timed out after ${DISCOVERY_TIMEOUT_MS / 1000}s — ` +
|
|
23
|
+
'set host and serialNumber in config to skip auto-discovery'));
|
|
24
|
+
});
|
|
25
|
+
}, DISCOVERY_TIMEOUT_MS);
|
|
26
|
+
browser.on('up', (service) => {
|
|
27
|
+
// Strip trailing dot that mDNS hostnames include (e.g. "pvs.local." → "pvs.local")
|
|
28
|
+
const host = service.host?.replace(/\.$/, '');
|
|
29
|
+
const serialNumber = service.txt?.serialnum;
|
|
30
|
+
if (!host || !serialNumber) {
|
|
31
|
+
log.warn(`mDNS: PVS6 service found but missing host or serialnum — host=${host}, txt=${JSON.stringify(service.txt)}`);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
clearTimeout(timer);
|
|
35
|
+
cleanup(() => {
|
|
36
|
+
log.info(`mDNS: discovered PVS6 at ${host} (serial: ${serialNumber})`);
|
|
37
|
+
resolve({ host, serialNumber });
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
function discoverPVS6Avahi(log) {
|
|
43
|
+
return new Promise((resolve, reject) => {
|
|
44
|
+
log.info(`mDNS: using avahi-browse for Linux discovery — will time out in ${DISCOVERY_TIMEOUT_MS / 1000}s`);
|
|
45
|
+
const proc = (0, child_process_1.spawn)('avahi-browse', ['-r', '-p', `_${PVS6_SERVICE_TYPE}._tcp`]);
|
|
46
|
+
let resolved = false;
|
|
47
|
+
const timer = setTimeout(() => {
|
|
48
|
+
proc.kill();
|
|
49
|
+
reject(new Error(`mDNS discovery timed out after ${DISCOVERY_TIMEOUT_MS / 1000}s — ` +
|
|
50
|
+
'set host and serialNumber in config to skip auto-discovery'));
|
|
51
|
+
}, DISCOVERY_TIMEOUT_MS);
|
|
52
|
+
proc.stdout.on('data', (data) => {
|
|
53
|
+
if (resolved)
|
|
54
|
+
return;
|
|
55
|
+
for (const line of data.toString().split('\n')) {
|
|
56
|
+
if (resolved || !line.startsWith('='))
|
|
57
|
+
continue;
|
|
58
|
+
// Parseable resolved format: =;iface;proto;name;type;domain;hostname;address;port;txt...
|
|
59
|
+
const parts = line.split(';');
|
|
60
|
+
if (parts.length < 10)
|
|
61
|
+
continue;
|
|
62
|
+
const host = parts[6]?.replace(/\.$/, '');
|
|
63
|
+
const txtField = parts.slice(9).join(';');
|
|
64
|
+
const serialNumber = txtField.match(/"serialnum=([^"]+)"/)?.[1];
|
|
65
|
+
if (!host || !serialNumber) {
|
|
66
|
+
log.warn(`mDNS: PVS6 found but missing host or serialnum — host=${host}, txt=${txtField}`);
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
resolved = true;
|
|
70
|
+
clearTimeout(timer);
|
|
71
|
+
proc.kill();
|
|
72
|
+
log.info(`mDNS: discovered PVS6 at ${host} (serial: ${serialNumber})`);
|
|
73
|
+
resolve({ host, serialNumber });
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
proc.stderr.on('data', (data) => {
|
|
77
|
+
log.debug(`avahi-browse: ${data.toString().trim()}`);
|
|
78
|
+
});
|
|
79
|
+
proc.on('error', (err) => {
|
|
80
|
+
clearTimeout(timer);
|
|
81
|
+
reject(new Error(`avahi-browse failed: ${err.message}`));
|
|
82
|
+
});
|
|
83
|
+
proc.on('close', (code) => {
|
|
84
|
+
if (!resolved) {
|
|
85
|
+
clearTimeout(timer);
|
|
86
|
+
reject(new Error(`avahi-browse exited (code ${code}) without finding PVS6 — ` +
|
|
87
|
+
'set host and serialNumber in config to skip auto-discovery'));
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
function discoverPVS6(log) {
|
|
93
|
+
return process.platform === 'linux' ? discoverPVS6Avahi(log) : discoverPVS6Bonjour(log);
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=pvs6Discovery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pvs6Discovery.js","sourceRoot":"","sources":["../src/pvs6Discovery.ts"],"names":[],"mappings":"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"}
|
package/dist/settings.d.ts
CHANGED
|
@@ -2,8 +2,9 @@ import { PlatformConfig } from 'homebridge';
|
|
|
2
2
|
export declare const PLATFORM_NAME = "PVS6";
|
|
3
3
|
export declare const PLUGIN_NAME = "homebridge-pvs6";
|
|
4
4
|
export interface PVS6Config extends PlatformConfig {
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
autoDiscover?: boolean;
|
|
6
|
+
host?: string;
|
|
7
|
+
serialNumber?: string;
|
|
7
8
|
pollInterval?: number;
|
|
8
9
|
accessories?: {
|
|
9
10
|
grid?: boolean;
|
package/dist/settings.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../src/settings.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAE5C,eAAO,MAAM,aAAa,SAAS,CAAC;AACpC,eAAO,MAAM,WAAW,oBAAoB,CAAC;AAE7C,MAAM,WAAW,UAAW,SAAQ,cAAc;IAChD,IAAI,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../src/settings.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAE5C,eAAO,MAAM,aAAa,SAAS,CAAC;AACpC,eAAO,MAAM,WAAW,oBAAoB,CAAC;AAE7C,MAAM,WAAW,UAAW,SAAQ,cAAc;IAChD,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE;QACZ,IAAI,CAAC,EAAE,OAAO,CAAC;KAChB,CAAC;IACF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "homebridge-pvs6",
|
|
3
3
|
"displayName": "PVS6 Solar",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.1.0",
|
|
5
5
|
"description": "Homebridge plugin for SunStrong PVS6 solar monitoring — Eve Energy accessories with fakegato history.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "David Carson",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
},
|
|
19
19
|
"engines": {
|
|
20
20
|
"node": ">=18.0.0",
|
|
21
|
-
"homebridge": ">=
|
|
21
|
+
"homebridge": ">=2.0.0"
|
|
22
22
|
},
|
|
23
23
|
"main": "dist/index.js",
|
|
24
24
|
"files": [
|
|
@@ -39,9 +39,10 @@
|
|
|
39
39
|
"energy"
|
|
40
40
|
],
|
|
41
41
|
"peerDependencies": {
|
|
42
|
-
"homebridge": ">=
|
|
42
|
+
"homebridge": ">=2.0.0"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
+
"bonjour-hap": "^3.0.0",
|
|
45
46
|
"fakegato-history": "^0.6.7"
|
|
46
47
|
},
|
|
47
48
|
"devDependencies": {
|