homebridge-pvs6 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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 dacarson
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,198 @@
1
+ # homebridge-pvs6
2
+
3
+ A [Homebridge](https://homebridge.io) plugin for the [SunStrong PVS6](https://sunstrong.com) solar monitoring system. Exposes solar production and grid metering as **Eve Energy** accessories in Apple HomeKit, with real-time power, cumulative energy, voltage, and native Eve app history.
4
+
5
+ ---
6
+
7
+ ## Features
8
+
9
+ - **Solar Production** accessory — always registered; real-time PV output (W), lifetime generation (kWh), line voltage (V)
10
+ - **Grid Import + Grid Export** accessory pair — optional (default enabled); each shows non-negative watts and its own Eve app history
11
+ - Import and Export accessories are always registered together as a pair, making import/export history immediately readable as separate graphs
12
+ - Up to 7 days of native power history in the Eve app via [fakegato-history](https://github.com/simont77/fakegato-history)
13
+ - Polls the PVS6 **local** FCGI API — no SunStrong Connect, no cloud dependency
14
+ - Automatically identifies production and consumption CT meters from the PVS6 device list
15
+ - Designed to complement [homebridge-rainforest-eagle3](https://github.com/dacarson/homebridge-rainforest-eagle3) for a complete solar + grid picture in Apple Home
16
+
17
+ ---
18
+
19
+ ## Supported Hardware
20
+
21
+ | Device | Status |
22
+ |---|---|
23
+ | SunStrong PVS6 (firmware build 61840+) | Supported |
24
+ | PVS5 | Not supported |
25
+ | PVS2 | Not supported |
26
+
27
+ ---
28
+
29
+ ## How It Works
30
+
31
+ The PVS6 exposes a local HTTPS varserver API that this plugin polls on a configurable interval. It reads two data streams:
32
+
33
+ - **Livedata** — aggregate solar production and net grid power computed by the PVS6
34
+ - **Meter data** — per-phase detail from the CT meters (voltage, cumulative energy)
35
+
36
+ The plugin automatically identifies the production meter (model suffix `p`, e.g. `PVS6M0400p`) and consumption meter (model suffix `c`, e.g. `PVS6M0400c`) from the device list.
37
+
38
+ All accessories render as **smart plugs** in Apple Home:
39
+
40
+ - **Solar Production** — always registered; `On` when the panels are producing power
41
+ - **Grid Import** — `On` when importing from the grid; zero watts when net-exporting
42
+ - **Grid Export** — `On` when net-exporting solar to the grid; zero watts when importing
43
+
44
+ Grid Import and Grid Export are always registered together — enabling `accessories.grid` creates both.
45
+
46
+ ---
47
+
48
+ ## Requirements
49
+
50
+ - [Homebridge](https://homebridge.io) v1.6 or later
51
+ - Node.js 18 or later
52
+ - SunStrong PVS6 on the same local network as your Homebridge host
53
+ - The PVS6's **full serial number** (printed on the unit label)
54
+
55
+ ---
56
+
57
+ ## Installation
58
+
59
+ Install via the Homebridge UI plugin search, or from the command line:
60
+
61
+ ```bash
62
+ npm install -g homebridge-pvs6
63
+ ```
64
+
65
+ ---
66
+
67
+ ## Configuration
68
+
69
+ Add a platform entry to your Homebridge `config.json`:
70
+
71
+ Minimal config (solar only — grid disabled):
72
+
73
+ ```json
74
+ {
75
+ "platforms": [
76
+ {
77
+ "platform": "PVS6",
78
+ "name": "PVS6",
79
+ "host": "192.168.1.x",
80
+ "serialNumber": "ZT231385000549A1234",
81
+ "accessories": { "grid": false }
82
+ }
83
+ ]
84
+ }
85
+ ```
86
+
87
+ Standard config (solar + grid import/export pair):
88
+
89
+ ```json
90
+ {
91
+ "platforms": [
92
+ {
93
+ "platform": "PVS6",
94
+ "name": "PVS6",
95
+ "host": "192.168.1.x",
96
+ "serialNumber": "ZT231385000549A1234",
97
+ "pollInterval": 10,
98
+ "solarName": "Solar Production",
99
+ "gridName": "Grid Meter - Import",
100
+ "gridExportName": "Grid Meter - Export"
101
+ }
102
+ ]
103
+ }
104
+ ```
105
+
106
+ ### Configuration Options
107
+
108
+ | Field | Type | Required | Default | Description |
109
+ |---|---|---|---|---|
110
+ | `platform` | string | yes | — | Must be `PVS6` |
111
+ | `host` | string | yes | — | IP address or hostname of the PVS6 on your local network |
112
+ | `serialNumber` | string | yes | — | Full PVS6 serial number. The last 5 characters are used as the API password |
113
+ | `pollInterval` | integer | no | `10` | Seconds between polls. Minimum enforced: `5` |
114
+ | `accessories.grid` | boolean | no | `true` | Enable the Grid Import + Grid Export accessory pair. Set `false` to disable both |
115
+ | `solarName` | string | no | `"Solar Production"` | HomeKit display name for the Solar Production accessory |
116
+ | `gridName` | string | no | `"Grid Meter - Import"` | HomeKit display name for the Grid Import accessory |
117
+ | `gridExportName` | string | no | `"Grid Meter - Export"` | HomeKit display name for the Grid Export accessory |
118
+
119
+ ### Finding Your Serial Number
120
+
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 of the serial number are used as the local API login password — no separate password configuration is required.
122
+
123
+ ---
124
+
125
+ ## HomeKit Accessories
126
+
127
+ ### Solar Production
128
+
129
+ | Characteristic | Source | Notes |
130
+ |---|---|---|
131
+ | On | `pv_p > 0` | True when panels are actively generating |
132
+ | OutletInUse | Always `true` | Required by Eve Energy |
133
+ | Eve Watts | `pv_p × 1000` | Real-time solar output in Watts |
134
+ | Eve kWh | `pv_en` | Lifetime generation in kWh |
135
+ | Eve Voltage | Production meter `v12V` | AC line voltage |
136
+
137
+ ### Grid Import
138
+
139
+ | Characteristic | Source | Notes |
140
+ |---|---|---|
141
+ | On | `net_p > 0` | True when importing from grid |
142
+ | OutletInUse | Always `true` | Required by Eve Energy |
143
+ | Eve Watts | `max(0, net_p) × 1000` | Import watts; zero when net-exporting |
144
+ | Eve kWh | `posLtea3phsumKwh` | Lifetime imported energy from consumption CT meter |
145
+ | Eve Voltage | Consumption meter `v12V` | AC line voltage |
146
+
147
+ ### Grid Export *(optional — enabled with `showGridExport: true`)*
148
+
149
+ | Characteristic | Source | Notes |
150
+ |---|---|---|
151
+ | On | `net_p < 0` | True when net-exporting solar to the grid |
152
+ | OutletInUse | Always `true` | Required by Eve Energy |
153
+ | Eve Watts | `max(0, −net_p) × 1000` | Export watts; zero when importing |
154
+ | Eve kWh | `negLtea3phsumKwh` | Lifetime exported energy from consumption CT meter |
155
+ | Eve Voltage | Consumption meter `v12V` | AC line voltage |
156
+
157
+ ---
158
+
159
+ ## Error Handling
160
+
161
+ The plugin is designed to be resilient to transient PVS6 failures:
162
+
163
+ - If the PVS6 is unreachable at startup, authentication retries every 30 seconds
164
+ - Overlapping poll cycles are skipped — only one request is in flight at a time
165
+ - Poll cycles are skipped (not fatal) on timeout (>10 s), JSON parse errors, or HTTP 5xx responses
166
+ - HTTP 5xx triggers a short backoff to avoid overwhelming the PVS6's embedded HTTP server
167
+ - HTTP 401 during polling triggers silent re-authentication with a 60-second backoff
168
+ - Missing fields in a partial response retain their last known-good values
169
+ - HomeKit characteristics remain visible and frozen at last-known values during outages
170
+
171
+ ---
172
+
173
+ ## Building from Source
174
+
175
+ ```bash
176
+ git clone https://github.com/dacarson/homebridge-pvs6.git
177
+ cd homebridge-pvs6
178
+ npm install
179
+ npm run build
180
+ ```
181
+
182
+ To develop with live rebuilds:
183
+
184
+ ```bash
185
+ npm run watch
186
+ ```
187
+
188
+ ---
189
+
190
+ ## Related Plugins
191
+
192
+ - [homebridge-rainforest-eagle3](https://github.com/dacarson/homebridge-rainforest-eagle3) — Rainforest EAGLE-200 grid meter, designed to sit alongside this plugin in Apple Home
193
+
194
+ ---
195
+
196
+ ## License
197
+
198
+ Apache-2.0
@@ -0,0 +1,64 @@
1
+ {
2
+ "pluginAlias": "PVS6",
3
+ "pluginType": "platform",
4
+ "singular": true,
5
+ "schema": {
6
+ "type": "object",
7
+ "properties": {
8
+ "name": {
9
+ "title": "Name",
10
+ "type": "string",
11
+ "default": "PVS6"
12
+ },
13
+ "host": {
14
+ "title": "Host",
15
+ "type": "string",
16
+ "placeholder": "192.168.1.x",
17
+ "description": "IP address or hostname of the PVS6 on your local network."
18
+ },
19
+ "serialNumber": {
20
+ "title": "Serial Number",
21
+ "type": "string",
22
+ "placeholder": "ZT231385000549F1084",
23
+ "description": "Full PVS6 serial number. The last 5 characters are used as the login password."
24
+ },
25
+ "pollInterval": {
26
+ "title": "Poll Interval (seconds)",
27
+ "type": "integer",
28
+ "default": 10,
29
+ "minimum": 5,
30
+ "description": "How often to poll the PVS6 for new data. Minimum 5 seconds."
31
+ },
32
+ "accessories": {
33
+ "title": "Accessories",
34
+ "type": "object",
35
+ "properties": {
36
+ "grid": {
37
+ "title": "Enable Grid Meter accessories (Import + Export pair)",
38
+ "type": "boolean",
39
+ "default": true
40
+ }
41
+ }
42
+ },
43
+ "solarName": {
44
+ "title": "Solar Accessory Display Name",
45
+ "type": "string",
46
+ "default": "Solar Production",
47
+ "description": "HomeKit display name for the Solar Production accessory."
48
+ },
49
+ "gridName": {
50
+ "title": "Grid Import Accessory Display Name",
51
+ "type": "string",
52
+ "default": "Grid Meter - Import",
53
+ "description": "HomeKit display name for the Grid Import accessory."
54
+ },
55
+ "gridExportName": {
56
+ "title": "Grid Export Accessory Display Name",
57
+ "type": "string",
58
+ "default": "Grid Meter - Export",
59
+ "description": "HomeKit display name for the Grid Export accessory."
60
+ }
61
+ },
62
+ "required": ["host", "serialNumber"]
63
+ }
64
+ }
@@ -0,0 +1,14 @@
1
+ import { API, Characteristic } from 'homebridge';
2
+ export declare const EVE_ENERGY_SERVICE_UUID = "E863F10A-079E-48FF-8F27-9C2605A29F52";
3
+ export declare const EVE_WATT_UUID = "E863F10D-079E-48FF-8F27-9C2605A29F52";
4
+ export declare const EVE_KWH_UUID = "E863F10C-079E-48FF-8F27-9C2605A29F52";
5
+ export type EveCharClass = {
6
+ new (): Characteristic;
7
+ UUID: string;
8
+ };
9
+ export interface EveChars {
10
+ EveWatts: EveCharClass;
11
+ EveKWh: EveCharClass;
12
+ }
13
+ export declare function createEveCharacteristics(api: API): EveChars;
14
+ //# sourceMappingURL=eveCharacteristics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eveCharacteristics.d.ts","sourceRoot":"","sources":["../src/eveCharacteristics.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,cAAc,EAAkB,MAAM,YAAY,CAAC;AAEjE,eAAO,MAAM,uBAAuB,yCAAyC,CAAC;AAC9E,eAAO,MAAM,aAAa,yCAAyC,CAAC;AACpE,eAAO,MAAM,YAAY,yCAAyC,CAAC;AAInE,MAAM,MAAM,YAAY,GAAG;IAAE,QAAO,cAAc,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAEnE,MAAM,WAAW,QAAQ;IACvB,QAAQ,EAAE,YAAY,CAAC;IACvB,MAAM,EAAE,YAAY,CAAC;CACtB;AAED,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,GAAG,GAAG,QAAQ,CAuC3D"}
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EVE_KWH_UUID = exports.EVE_WATT_UUID = exports.EVE_ENERGY_SERVICE_UUID = void 0;
4
+ exports.createEveCharacteristics = createEveCharacteristics;
5
+ exports.EVE_ENERGY_SERVICE_UUID = 'E863F10A-079E-48FF-8F27-9C2605A29F52';
6
+ exports.EVE_WATT_UUID = 'E863F10D-079E-48FF-8F27-9C2605A29F52';
7
+ exports.EVE_KWH_UUID = 'E863F10C-079E-48FF-8F27-9C2605A29F52';
8
+ function createEveCharacteristics(api) {
9
+ const { hap } = api;
10
+ class EveWatts extends hap.Characteristic {
11
+ constructor() {
12
+ super('Eve Watts', EveWatts.UUID, {
13
+ format: "float" /* Formats.FLOAT */,
14
+ unit: 'W',
15
+ minValue: -100000,
16
+ maxValue: 100000,
17
+ minStep: 0.1,
18
+ perms: ["pr" /* Perms.PAIRED_READ */, "ev" /* Perms.NOTIFY */],
19
+ });
20
+ this.value = this.getDefaultValue();
21
+ }
22
+ }
23
+ EveWatts.UUID = exports.EVE_WATT_UUID;
24
+ class EveKWh extends hap.Characteristic {
25
+ constructor() {
26
+ super('Eve kWh', EveKWh.UUID, {
27
+ format: "float" /* Formats.FLOAT */,
28
+ unit: 'kWh',
29
+ minValue: 0,
30
+ maxValue: 1000000,
31
+ minStep: 0.001,
32
+ perms: ["pr" /* Perms.PAIRED_READ */, "ev" /* Perms.NOTIFY */],
33
+ });
34
+ this.value = this.getDefaultValue();
35
+ }
36
+ }
37
+ EveKWh.UUID = exports.EVE_KWH_UUID;
38
+ return {
39
+ EveWatts: EveWatts,
40
+ EveKWh: EveKWh,
41
+ };
42
+ }
43
+ //# sourceMappingURL=eveCharacteristics.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eveCharacteristics.js","sourceRoot":"","sources":["../src/eveCharacteristics.ts"],"names":[],"mappings":";;;AAeA,4DAuCC;AApDY,QAAA,uBAAuB,GAAG,sCAAsC,CAAC;AACjE,QAAA,aAAa,GAAG,sCAAsC,CAAC;AACvD,QAAA,YAAY,GAAG,sCAAsC,CAAC;AAWnE,SAAgB,wBAAwB,CAAC,GAAQ;IAC/C,MAAM,EAAE,GAAG,EAAE,GAAG,GAAG,CAAC;IAEpB,MAAM,QAAS,SAAQ,GAAG,CAAC,cAAc;QAGvC;YACE,KAAK,CAAC,WAAW,EAAE,QAAQ,CAAC,IAAI,EAAE;gBAChC,MAAM,6BAAe;gBACrB,IAAI,EAAE,GAAa;gBACnB,QAAQ,EAAE,CAAC,MAAM;gBACjB,QAAQ,EAAE,MAAM;gBAChB,OAAO,EAAE,GAAG;gBACZ,KAAK,EAAE,uDAAiC;aACzC,CAAC,CAAC;YACH,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,eAAe,EAAE,CAAC;QACtC,CAAC;;IAZe,aAAI,GAAG,qBAAa,CAAC;IAevC,MAAM,MAAO,SAAQ,GAAG,CAAC,cAAc;QAGrC;YACE,KAAK,CAAC,SAAS,EAAE,MAAM,CAAC,IAAI,EAAE;gBAC5B,MAAM,6BAAe;gBACrB,IAAI,EAAE,KAAe;gBACrB,QAAQ,EAAE,CAAC;gBACX,QAAQ,EAAE,OAAO;gBACjB,OAAO,EAAE,KAAK;gBACd,KAAK,EAAE,uDAAiC;aACzC,CAAC,CAAC;YACH,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,eAAe,EAAE,CAAC;QACtC,CAAC;;IAZe,WAAI,GAAG,oBAAY,CAAC;IAetC,OAAO;QACL,QAAQ,EAAE,QAAmC;QAC7C,MAAM,EAAE,MAAiC;KAC1C,CAAC;AACJ,CAAC"}
@@ -0,0 +1,13 @@
1
+ import { PlatformAccessory } from 'homebridge';
2
+ import { PVS6Platform } from './platform';
3
+ import { PVS6Reading } from './pvs6Client';
4
+ export declare class GridExportAccessory {
5
+ private readonly platform;
6
+ private readonly service;
7
+ private readonly historyService;
8
+ private lastPowerW;
9
+ private lastEnergyKWh;
10
+ constructor(platform: PVS6Platform, accessory: PlatformAccessory, FakeGatoHistoryService: any, displayName: string, serialNumber: string);
11
+ updateValues(reading: PVS6Reading): void;
12
+ }
13
+ //# sourceMappingURL=gridExportAccessory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gridExportAccessory.d.ts","sourceRoot":"","sources":["../src/gridExportAccessory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAW,MAAM,YAAY,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAG3C,qBAAa,mBAAmB;IAS5B,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAR3B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAU;IAElC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAM;IAErC,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,aAAa,CAAK;gBAGP,QAAQ,EAAE,YAAY,EACvC,SAAS,EAAE,iBAAiB,EAE5B,sBAAsB,EAAE,GAAG,EAC3B,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM;IA2CtB,YAAY,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI;CAoBzC"}
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GridExportAccessory = void 0;
4
+ const eveCharacteristics_1 = require("./eveCharacteristics");
5
+ class GridExportAccessory {
6
+ constructor(platform, accessory,
7
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
8
+ FakeGatoHistoryService, displayName, serialNumber) {
9
+ this.platform = platform;
10
+ this.lastPowerW = 0;
11
+ this.lastEnergyKWh = 0;
12
+ const { Characteristic } = platform;
13
+ const { EveWatts, EveKWh } = platform.eveChars;
14
+ const infoService = accessory.getService(platform.Service.AccessoryInformation) ??
15
+ accessory.addService(platform.Service.AccessoryInformation);
16
+ infoService
17
+ .setCharacteristic(Characteristic.Manufacturer, 'SunStrong')
18
+ .setCharacteristic(Characteristic.Model, 'PVS6')
19
+ .setCharacteristic(Characteristic.SerialNumber, `${serialNumber}-grid-export`);
20
+ const existingService = accessory.services.find(s => s.UUID === eveCharacteristics_1.EVE_ENERGY_SERVICE_UUID);
21
+ this.service = existingService ??
22
+ accessory.addService(new platform.api.hap.Service(displayName, eveCharacteristics_1.EVE_ENERGY_SERVICE_UUID));
23
+ this.service.setCharacteristic(Characteristic.Name, displayName);
24
+ // On = true when exporting (lastPowerW > 0); always non-negative so the comparison is natural.
25
+ this.service
26
+ .getCharacteristic(Characteristic.On)
27
+ .onGet(() => this.lastPowerW > 0)
28
+ .onSet(async () => {
29
+ this.service.updateCharacteristic(Characteristic.On, this.lastPowerW > 0);
30
+ });
31
+ this.service
32
+ .getCharacteristic(Characteristic.OutletInUse)
33
+ .onGet(() => true);
34
+ this.service
35
+ .getCharacteristic(EveWatts)
36
+ .onGet(() => this.lastPowerW);
37
+ this.service
38
+ .getCharacteristic(EveKWh)
39
+ .onGet(() => this.lastEnergyKWh);
40
+ this.historyService = new FakeGatoHistoryService('energy', accessory, { storage: 'fs' });
41
+ }
42
+ updateValues(reading) {
43
+ const { Characteristic } = this.platform;
44
+ const { EveWatts, EveKWh } = this.platform.eveChars;
45
+ // Non-negative: positive when net-exporting, zero when importing.
46
+ this.lastPowerW = Math.max(0, -reading.netPowerW);
47
+ this.lastEnergyKWh = reading.gridExportKWh;
48
+ this.service.updateCharacteristic(Characteristic.On, this.lastPowerW > 0);
49
+ this.service.updateCharacteristic(Characteristic.OutletInUse, true);
50
+ this.service.updateCharacteristic(EveWatts, this.lastPowerW);
51
+ this.service.updateCharacteristic(EveKWh, this.lastEnergyKWh);
52
+ this.historyService.addEntry({
53
+ time: Math.round(Date.now() / 1000),
54
+ power: this.lastPowerW,
55
+ });
56
+ this.platform.log.debug(`Grid Export: ${this.lastPowerW}W ${this.lastEnergyKWh}kWh`);
57
+ }
58
+ }
59
+ exports.GridExportAccessory = GridExportAccessory;
60
+ //# sourceMappingURL=gridExportAccessory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gridExportAccessory.js","sourceRoot":"","sources":["../src/gridExportAccessory.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import { PlatformAccessory } from 'homebridge';
2
+ import { PVS6Platform } from './platform';
3
+ import { PVS6Reading } from './pvs6Client';
4
+ export declare class GridImportAccessory {
5
+ private readonly platform;
6
+ private readonly service;
7
+ private readonly historyService;
8
+ private lastPowerW;
9
+ private lastEnergyKWh;
10
+ constructor(platform: PVS6Platform, accessory: PlatformAccessory, FakeGatoHistoryService: any, displayName: string, serialNumber: string);
11
+ updateValues(reading: PVS6Reading): void;
12
+ }
13
+ //# sourceMappingURL=gridImportAccessory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gridImportAccessory.d.ts","sourceRoot":"","sources":["../src/gridImportAccessory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAW,MAAM,YAAY,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAG3C,qBAAa,mBAAmB;IAS5B,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAR3B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAU;IAElC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAM;IAErC,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,aAAa,CAAK;gBAGP,QAAQ,EAAE,YAAY,EACvC,SAAS,EAAE,iBAAiB,EAE5B,sBAAsB,EAAE,GAAG,EAC3B,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM;IA2CtB,YAAY,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI;CAoBzC"}
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GridImportAccessory = void 0;
4
+ const eveCharacteristics_1 = require("./eveCharacteristics");
5
+ class GridImportAccessory {
6
+ constructor(platform, accessory,
7
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
8
+ FakeGatoHistoryService, displayName, serialNumber) {
9
+ this.platform = platform;
10
+ this.lastPowerW = 0;
11
+ this.lastEnergyKWh = 0;
12
+ const { Characteristic } = platform;
13
+ const { EveWatts, EveKWh } = platform.eveChars;
14
+ const infoService = accessory.getService(platform.Service.AccessoryInformation) ??
15
+ accessory.addService(platform.Service.AccessoryInformation);
16
+ infoService
17
+ .setCharacteristic(Characteristic.Manufacturer, 'SunStrong')
18
+ .setCharacteristic(Characteristic.Model, 'PVS6')
19
+ .setCharacteristic(Characteristic.SerialNumber, `${serialNumber}-grid`);
20
+ const existingService = accessory.services.find(s => s.UUID === eveCharacteristics_1.EVE_ENERGY_SERVICE_UUID);
21
+ this.service = existingService ??
22
+ accessory.addService(new platform.api.hap.Service(displayName, eveCharacteristics_1.EVE_ENERGY_SERVICE_UUID));
23
+ this.service.setCharacteristic(Characteristic.Name, displayName);
24
+ // On = true when importing (lastPowerW > 0); always non-negative so the comparison is natural.
25
+ this.service
26
+ .getCharacteristic(Characteristic.On)
27
+ .onGet(() => this.lastPowerW > 0)
28
+ .onSet(async () => {
29
+ this.service.updateCharacteristic(Characteristic.On, this.lastPowerW > 0);
30
+ });
31
+ this.service
32
+ .getCharacteristic(Characteristic.OutletInUse)
33
+ .onGet(() => true);
34
+ this.service
35
+ .getCharacteristic(EveWatts)
36
+ .onGet(() => this.lastPowerW);
37
+ this.service
38
+ .getCharacteristic(EveKWh)
39
+ .onGet(() => this.lastEnergyKWh);
40
+ this.historyService = new FakeGatoHistoryService('energy', accessory, { storage: 'fs' });
41
+ }
42
+ updateValues(reading) {
43
+ const { Characteristic } = this.platform;
44
+ const { EveWatts, EveKWh } = this.platform.eveChars;
45
+ // Non-negative: zero when net-exporting, positive when importing.
46
+ this.lastPowerW = Math.max(0, reading.netPowerW);
47
+ this.lastEnergyKWh = reading.gridImportKWh;
48
+ this.service.updateCharacteristic(Characteristic.On, this.lastPowerW > 0);
49
+ this.service.updateCharacteristic(Characteristic.OutletInUse, true);
50
+ this.service.updateCharacteristic(EveWatts, this.lastPowerW);
51
+ this.service.updateCharacteristic(EveKWh, this.lastEnergyKWh);
52
+ this.historyService.addEntry({
53
+ time: Math.round(Date.now() / 1000),
54
+ power: this.lastPowerW,
55
+ });
56
+ this.platform.log.debug(`Grid Import: ${this.lastPowerW}W ${this.lastEnergyKWh}kWh`);
57
+ }
58
+ }
59
+ exports.GridImportAccessory = GridImportAccessory;
60
+ //# sourceMappingURL=gridImportAccessory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gridImportAccessory.js","sourceRoot":"","sources":["../src/gridImportAccessory.ts"],"names":[],"mappings":";;;AAGA,6DAA+D;AAE/D,MAAa,mBAAmB;IAQ9B,YACmB,QAAsB,EACvC,SAA4B;IAC5B,8DAA8D;IAC9D,sBAA2B,EAC3B,WAAmB,EACnB,YAAoB;QALH,aAAQ,GAAR,QAAQ,CAAc;QAJjC,eAAU,GAAG,CAAC,CAAC;QACf,kBAAa,GAAG,CAAC,CAAC;QAUxB,MAAM,EAAE,cAAc,EAAE,GAAG,QAAQ,CAAC;QACpC,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,QAAQ,CAAC,QAAQ,CAAC;QAE/C,MAAM,WAAW,GACf,SAAS,CAAC,UAAU,CAAC,QAAQ,CAAC,OAAO,CAAC,oBAAoB,CAAC;YAC3D,SAAS,CAAC,UAAU,CAAC,QAAQ,CAAC,OAAO,CAAC,oBAAoB,CAAC,CAAC;QAE9D,WAAW;aACR,iBAAiB,CAAC,cAAc,CAAC,YAAY,EAAE,WAAW,CAAC;aAC3D,iBAAiB,CAAC,cAAc,CAAC,KAAK,EAAE,MAAM,CAAC;aAC/C,iBAAiB,CAAC,cAAc,CAAC,YAAY,EAAE,GAAG,YAAY,OAAO,CAAC,CAAC;QAE1E,MAAM,eAAe,GAAG,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,4CAAuB,CAAC,CAAC;QACzF,IAAI,CAAC,OAAO,GAAG,eAAe;YAC5B,SAAS,CAAC,UAAU,CAAC,IAAI,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,WAAW,EAAE,4CAAuB,CAAC,CAAC,CAAC;QAE3F,IAAI,CAAC,OAAO,CAAC,iBAAiB,CAAC,cAAc,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;QAEjE,+FAA+F;QAC/F,IAAI,CAAC,OAAO;aACT,iBAAiB,CAAC,cAAc,CAAC,EAAE,CAAC;aACpC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,UAAU,GAAG,CAAC,CAAC;aAChC,KAAK,CAAC,KAAK,IAAI,EAAE;YAChB,IAAI,CAAC,OAAO,CAAC,oBAAoB,CAAC,cAAc,CAAC,EAAE,EAAE,IAAI,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC;QAC5E,CAAC,CAAC,CAAC;QAEL,IAAI,CAAC,OAAO;aACT,iBAAiB,CAAC,cAAc,CAAC,WAAW,CAAC;aAC7C,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;QAErB,IAAI,CAAC,OAAO;aACT,iBAAiB,CAAC,QAAQ,CAAC;aAC3B,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAEhC,IAAI,CAAC,OAAO;aACT,iBAAiB,CAAC,MAAM,CAAC;aACzB,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QAEnC,IAAI,CAAC,cAAc,GAAG,IAAI,sBAAsB,CAAC,QAAQ,EAAE,SAAS,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3F,CAAC;IAED,YAAY,CAAC,OAAoB;QAC/B,MAAM,EAAE,cAAc,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC;QACzC,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC;QAEpD,kEAAkE;QAClE,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC;QACjD,IAAI,CAAC,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC;QAE3C,IAAI,CAAC,OAAO,CAAC,oBAAoB,CAAC,cAAc,CAAC,EAAE,EAAE,IAAI,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC;QAC1E,IAAI,CAAC,OAAO,CAAC,oBAAoB,CAAC,cAAc,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;QACpE,IAAI,CAAC,OAAO,CAAC,oBAAoB,CAAC,QAAQ,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;QAC7D,IAAI,CAAC,OAAO,CAAC,oBAAoB,CAAC,MAAM,EAAE,IAAI,CAAC,aAAa,CAAC,CAAC;QAE9D,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC;YAC3B,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;YACnC,KAAK,EAAE,IAAI,CAAC,UAAU;SACvB,CAAC,CAAC;QAEH,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,gBAAgB,IAAI,CAAC,UAAU,MAAM,IAAI,CAAC,aAAa,KAAK,CAAC,CAAC;IACxF,CAAC;CACF;AA7ED,kDA6EC"}
@@ -0,0 +1,4 @@
1
+ import { API } from 'homebridge';
2
+ declare const _default: (api: API) => void;
3
+ export = _default;
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;yBAIvB,KAAK,GAAG,KAAG,IAAI;AAAzB,kBAEE"}
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ const settings_1 = require("./settings");
3
+ const platform_1 = require("./platform");
4
+ module.exports = (api) => {
5
+ api.registerPlatform(settings_1.PLATFORM_NAME, platform_1.PVS6Platform);
6
+ };
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,yCAA2C;AAC3C,yCAA0C;AAE1C,iBAAS,CAAC,GAAQ,EAAQ,EAAE;IAC1B,GAAG,CAAC,gBAAgB,CAAC,wBAAa,EAAE,uBAAY,CAAC,CAAC;AACpD,CAAC,CAAC"}
@@ -0,0 +1,27 @@
1
+ import { API, Characteristic, DynamicPlatformPlugin, Logger, PlatformAccessory, PlatformConfig, Service } from 'homebridge';
2
+ import { EveChars } from './eveCharacteristics';
3
+ export declare class PVS6Platform implements DynamicPlatformPlugin {
4
+ readonly Service: typeof Service;
5
+ readonly Characteristic: typeof Characteristic;
6
+ readonly api: API;
7
+ readonly eveChars: EveChars;
8
+ readonly log: Logger;
9
+ readonly accessories: PlatformAccessory[];
10
+ private readonly client;
11
+ private readonly config;
12
+ private readonly pollIntervalMs;
13
+ private solarAccessory?;
14
+ private gridImportAccessory?;
15
+ private gridExportAccessory?;
16
+ private pollTimer?;
17
+ private pollInFlight;
18
+ private backedOff;
19
+ constructor(log: Logger, config: PlatformConfig, api: API);
20
+ configureAccessory(accessory: PlatformAccessory): void;
21
+ private setupAccessories;
22
+ private getOrCreateAccessory;
23
+ private startAuthAndPolling;
24
+ private startPolling;
25
+ private enterBackoff;
26
+ }
27
+ //# sourceMappingURL=platform.d.ts.map
@@ -0,0 +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;AAO1E,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;IAEtD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAa;IACpC,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;gBAGxB,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,cAAc,EACtB,GAAG,EAAE,GAAG;IA8CV,kBAAkB,CAAC,SAAS,EAAE,iBAAiB,GAAG,IAAI;IAKtD,OAAO,CAAC,gBAAgB;IAyCxB,OAAO,CAAC,oBAAoB;IAgB5B,OAAO,CAAC,mBAAmB;IAa3B,OAAO,CAAC,YAAY;IAmDpB,OAAO,CAAC,YAAY;CAOrB"}