homebridge-solaxcloud-api 2.3.3 → 2.4.1

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.
Files changed (55) hide show
  1. package/CHANGELOG.md +150 -131
  2. package/LICENSE +175 -175
  3. package/README.md +1 -0
  4. package/config.sample +15 -15
  5. package/config.schema.json +113 -106
  6. package/dist/batteryAccessory.d.ts +70 -70
  7. package/dist/batteryAccessory.d.ts.map +1 -1
  8. package/dist/batteryAccessory.js +124 -123
  9. package/dist/batteryAccessory.js.map +1 -1
  10. package/dist/index.d.ts +6 -6
  11. package/dist/index.js +6 -6
  12. package/dist/lightSensorAccessory.d.ts +34 -34
  13. package/dist/lightSensorAccessory.js +58 -58
  14. package/dist/motionAccessory.d.ts +53 -53
  15. package/dist/motionAccessory.js +93 -93
  16. package/dist/outletAccessory.d.ts +104 -100
  17. package/dist/outletAccessory.d.ts.map +1 -1
  18. package/dist/outletAccessory.js +196 -187
  19. package/dist/outletAccessory.js.map +1 -1
  20. package/dist/platform.d.ts +67 -56
  21. package/dist/platform.d.ts.map +1 -1
  22. package/dist/platform.js +320 -265
  23. package/dist/platform.js.map +1 -1
  24. package/dist/platformAccessory.d.ts +70 -70
  25. package/dist/platformAccessory.js +102 -102
  26. package/dist/platformInverter.d.ts +180 -176
  27. package/dist/platformInverter.d.ts.map +1 -1
  28. package/dist/platformInverter.js +348 -343
  29. package/dist/platformInverter.js.map +1 -1
  30. package/dist/platformMeters.d.ts +68 -68
  31. package/dist/platformMeters.js +2 -2
  32. package/dist/settings.d.ts +8 -8
  33. package/dist/settings.js +11 -11
  34. package/dist/solaxcloudfetch.d.ts +1 -1
  35. package/dist/solaxcloudfetch.js +21 -21
  36. package/dist/solaxcloudfetch.js.map +1 -1
  37. package/dist/statistics.d.ts +36 -36
  38. package/dist/statistics.js +88 -88
  39. package/dist/util.d.ts +23 -23
  40. package/dist/util.js +42 -42
  41. package/package.json +55 -54
  42. package/test/batteryAccessory.test.ts +16 -0
  43. package/test/helpers.ts +36 -0
  44. package/test/lightSensorAccessory.test.ts +28 -0
  45. package/test/motionAccessory.test.ts +36 -0
  46. package/test/outletAccessory.test.ts +75 -0
  47. package/test/platformAccessory.test.ts +24 -0
  48. package/test/platformConfig.test.ts +188 -0
  49. package/test/platformTotalizer.test.ts +97 -0
  50. package/config.json +0 -16
  51. package/dist/solaxcloudapi.d.ts +0 -163
  52. package/dist/solaxcloudapi.d.ts.map +0 -1
  53. package/dist/solaxcloudapi.js +0 -226
  54. package/dist/solaxcloudapi.js.map +0 -1
  55. package/test/solaxcloudapi.test.ts +0 -164
package/dist/platform.js CHANGED
@@ -1,266 +1,321 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.SolaxCloudAPIPlatform = void 0;
4
- const util_1 = require("./util");
5
- const platformInverter_1 = require("./platformInverter");
6
- /**
7
- * Valid inverter brands (Solax, QCells)
8
- */
9
- const VALID_INVERTER_BRANDS = ['solax', 'qcells'];
10
- /**
11
- * Valid smoothing methods (Simple Moving Average, Exponential Moving Average).
12
- */
13
- const VALID_SMOOTHING_METHODS = ['sma', 'ema'];
14
- /**
15
- * Default smoothing method (simple moving average).
16
- */
17
- const DEFAULT_SMOOTHING_METHOD = 'sma';
18
- /**
19
- * Default polling frequency from Solax or QCells Cloud (in seconds).
20
- */
21
- const DEFAULT_POLLING_FREQUENCY = 300;
22
- /**
23
- * Max polls / min (obtain frequency need to be lower than 10 times/min).
24
- */
25
- const MAX_POLLS_MIN = 10;
26
- /**
27
- * Max polls / day (obtain frequency need to be lower than 10,000 times/day).
28
- */
29
- const MAX_POLLS_DAY = 10000;
30
- /**
31
- * Maximum polling frequency (in seconds).
32
- */
33
- const MAX_POLLING_FREQUENCY = Math.max(60 / MAX_POLLS_MIN, (24 * 60 * 60) / MAX_POLLS_DAY);
34
- /**
35
- * SolaxCloudAPIPlatform.
36
- * This class is the main constructor for your plugin, this is where you should
37
- * parse the user config and discover/register accessories with Homebridge.
38
- */
39
- class SolaxCloudAPIPlatform {
40
- /**
41
- * Platform constructor.
42
- * @param log
43
- * @param config
44
- * @param api
45
- * @returns
46
- */
47
- constructor(log, config, api) {
48
- /**
49
- * Number of periods to use for data smoothing.
50
- */
51
- this.smoothingWindow = 1;
52
- /**
53
- * Array of inverters.
54
- */
55
- this.inverters = [];
56
- // store values in properties
57
- this.log = log;
58
- this.config = config;
59
- this.api = api;
60
- // sanity check
61
- if (!api || !config) {
62
- return;
63
- }
64
- // check for valid config
65
- if (!this.checkConfig(this.config)) {
66
- return;
67
- }
68
- // window for smoothing (aim for a 15 minute window)
69
- this.smoothingWindow = Math.floor(15 * 60 / this.config.pollingFrequency);
70
- this.log.info(`Window for smoothing series is ${this.smoothingWindow} periods.`);
71
- try {
72
- // loop over configured inverters
73
- this.config.inverters.forEach(inverter => {
74
- // setup new inverter
75
- const platformInverter = new platformInverter_1.SolaxCloudAPIPlatformInverter(log, config, api, VALID_INVERTER_BRANDS.indexOf(inverter.brand), inverter.tokenId, inverter.sn, inverter.name, inverter.hasBattery, this.smoothingWindow);
76
- // add new inverter to list
77
- this.inverters.push(platformInverter);
78
- });
79
- // create inverter totalizers (pick first inverter as brand)
80
- if (this.inverters.length > 1) {
81
- this.allInverters = new platformInverter_1.SolaxCloudAPIPlatformInverter(log, config, api, VALID_INVERTER_BRANDS.indexOf(this.config.inverters[0].brand), this.config.tokenId, 'total', 'All inverters', this.inverters.map(inverter => +inverter.hasBattery()).reduce((a, b) => a + b, 0) > 0, this.smoothingWindow);
82
- }
83
- // start data fetching
84
- this.fetchDataPeriodically();
85
- this.log.debug('Finished initializing platform.');
86
- }
87
- catch (error) {
88
- this.log.error(`Unexpected error: ${error}`);
89
- }
90
- // When this event is fired it means Homebridge has created all accessories.
91
- this.api.on("didFinishLaunching" /* DID_FINISH_LAUNCHING */, () => {
92
- this.log.debug('Executed didFinishLaunching callback');
93
- });
94
- }
95
- /**
96
- * Check whether object implements interface InverterConfig and brand has a valid entry.
97
- * @param obj Any object.
98
- * @returns Whether object implements interface InverterConfig.
99
- */
100
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
101
- static isInverterConfig(obj) {
102
- return 'name' in obj && 'sn' in obj && 'brand' in obj && 'tokenId' in obj && VALID_INVERTER_BRANDS.includes(obj.brand);
103
- }
104
- /**
105
- * Update data for virtual inverter totalizer based on individual inverter data.
106
- */
107
- updateAllInvertersData() {
108
- this.allInverters.setRawPower(platformInverter_1.ACCESSORY_KEYS.pv, this.inverters.map(inverter => inverter.getRawPower(platformInverter_1.ACCESSORY_KEYS.pv)).reduce((a, b) => a + b, 0));
109
- this.allInverters.setRawPower(platformInverter_1.ACCESSORY_KEYS.inverterAC, this.inverters.map(inverter => inverter.getRawPower(platformInverter_1.ACCESSORY_KEYS.inverterAC)).reduce((a, b) => a + b, 0));
110
- this.allInverters.setRawEnergy(platformInverter_1.ACCESSORY_KEYS.inverterAC, this.inverters.map(inverter => inverter.getRawEnergy(platformInverter_1.ACCESSORY_KEYS.inverterAC)).reduce((a, b) => a + b, 0));
111
- this.allInverters.setRawPower(platformInverter_1.ACCESSORY_KEYS.inverterToGrid, this.inverters.map(inverter => inverter.getRawPower(platformInverter_1.ACCESSORY_KEYS.inverterToGrid)).reduce((a, b) => a + b, 0));
112
- this.allInverters.setRawPower(platformInverter_1.ACCESSORY_KEYS.inverterToHouse, this.inverters.map(inverter => inverter.getRawPower(platformInverter_1.ACCESSORY_KEYS.inverterToHouse)).reduce((a, b) => a + b, 0));
113
- this.allInverters.setRawPower(platformInverter_1.ACCESSORY_KEYS.gridToHouse, this.inverters.map(inverter => inverter.getRawPower(platformInverter_1.ACCESSORY_KEYS.gridToHouse)).reduce((a, b) => a + b, 0));
114
- if (this.allInverters.hasBattery()) {
115
- // get array of inverters with battery
116
- const invertersWithBattery = this.inverters.filter(inverter => inverter.hasBattery());
117
- // battery charge / discharge power
118
- this.allInverters.setRawPower(platformInverter_1.ACCESSORY_KEYS.inverterFromBattery, invertersWithBattery.map(inverter => inverter.getRawPower(platformInverter_1.ACCESSORY_KEYS.inverterFromBattery)).reduce((a, b) => a + b, 0));
119
- this.allInverters.setRawPower(platformInverter_1.ACCESSORY_KEYS.inverterToBattery, invertersWithBattery.map(inverter => inverter.getRawPower(platformInverter_1.ACCESSORY_KEYS.inverterToBattery)).reduce((a, b) => a + b, 0));
120
- // battery SoC
121
- this.allInverters.setBatteryChargeState(Math.max(...invertersWithBattery.map(inverter => inverter.getBatteryChargeState())));
122
- const levelSums = invertersWithBattery.map(inverter => inverter.getBatteryLevel()).reduce((a, b) => a + b, 0);
123
- this.allInverters.setBatteryLevel(levelSums / invertersWithBattery.length); // average levels
124
- }
125
- // notify update with motion
126
- this.allInverters.setMotionUpdate();
127
- }
128
- /**
129
- * Sleeps for a number of milliseconds.
130
- * @param millis {number} The number of milliseconds to sleep.
131
- */
132
- async sleep(millis) {
133
- return new Promise(resolve => setTimeout(resolve, millis));
134
- }
135
- /**
136
- * Periodically retrieves inverter data from Solax Cloud API using configured tokenID and SN.
137
- */
138
- async fetchDataPeriodically() {
139
- // loop forever
140
- for (;;) {
141
- // update data for all configured inverters
142
- this.inverters.forEach(inverter => inverter.updateInverterDataFromCloud());
143
- this.log.info(`Updated data from ${this.config.brand} Cloud API, sleeping for ${this.config.pollingFrequency} seconds.`);
144
- // update inverter totalizers if needed
145
- if (this.inverters.length > 1) {
146
- this.log.info('Computing inverter totals...');
147
- this.updateAllInvertersData();
148
- }
149
- // sleep until next data poll
150
- await this.sleep(this.config.pollingFrequency * 1000);
151
- }
152
- }
153
- /**
154
- * Checks platform config validity and sets defaults whenever needed.
155
- * @returns {boolean} Whether the config is valid.
156
- */
157
- checkConfig(config) {
158
- let result = true;
159
- this.log.debug(`Config read: ${JSON.stringify(config)}`);
160
- // check for mandatory parameters
161
- if (!config.inverters) {
162
- this.log.error('Config check: Can\'t find mandatory parameter "inverters" parameter in config file, aborting!');
163
- result = false;
164
- return result;
165
- }
166
- // check for inverters type
167
- if (Array.isArray(config.inverters)) {
168
- // loop over inverters and check config type
169
- config.inverters.forEach(inverter => {
170
- if (!SolaxCloudAPIPlatform.isInverterConfig(inverter)) {
171
- this.log.error('Config check: Invalid type for inverter under "inverters" in config file, aborting!');
172
- result = false;
173
- return result;
174
- }
175
- });
176
- }
177
- else {
178
- this.log.error('Config check: Incorrect type for mandatory parameter "inverters" in config file, aborting!');
179
- result = false;
180
- return result;
181
- }
182
- // check for duplicates
183
- if (util_1.Util.arrayHasDuplicates(config.inverters.map(inverter => inverter.name))) {
184
- this.log.error('Config check: Duplicate inverter names in config file, aborting!');
185
- result = false;
186
- return result;
187
- }
188
- else if (util_1.Util.arrayHasDuplicates(config.inverters.map(inverter => inverter.sn))) {
189
- this.log.error('Config check: Duplicate inverter SNs in config file, aborting!');
190
- result = false;
191
- return result;
192
- }
193
- // check for polling frequency
194
- if (config.pollingFrequency === undefined) {
195
- this.log.info(`Config check: No polling frequency provided, defaulting to ${DEFAULT_POLLING_FREQUENCY} seconds.`);
196
- config.pollingFrequency = DEFAULT_POLLING_FREQUENCY;
197
- }
198
- else {
199
- if (Number.isInteger(config.pollingFrequency) && config.pollingFrequency > 0) {
200
- // all good?
201
- if (config.pollingFrequency < MAX_POLLING_FREQUENCY) {
202
- this.log.info(`Config check: Polling frequency cannot be higher than ${MAX_POLLS_MIN} times/min and ${MAX_POLLS_DAY} ` +
203
- `times/day, defaulting to ${DEFAULT_POLLING_FREQUENCY} seconds.`);
204
- config.pollingFrequency = DEFAULT_POLLING_FREQUENCY;
205
- }
206
- }
207
- else {
208
- this.log.info('Config check: Invalid polling frequency (must be a positive integer number), defaulting to ' +
209
- `${DEFAULT_POLLING_FREQUENCY} seconds.`);
210
- config.pollingFrequency = DEFAULT_POLLING_FREQUENCY;
211
- }
212
- }
213
- // check for smooth meters
214
- if (config.smoothMeters === undefined) {
215
- config.smoothMeters = true;
216
- this.log.info(`Config check: No config for smooth meters, defaulting to ${config.smoothMeters}.`);
217
- }
218
- else {
219
- if (typeof config.smoothMeters !== 'boolean') {
220
- config.smoothMeters = true;
221
- this.log.info(`Config check: Invalid setting for smooth meters, defaulting to ${config.smoothMeters}.`);
222
- }
223
- }
224
- // check for smoothing method
225
- if (config.smoothingMethod === undefined) {
226
- this.log.info(`Config check: No smoothing method provided, defaulting to "${DEFAULT_SMOOTHING_METHOD}".`);
227
- config.smoothingMethod = DEFAULT_SMOOTHING_METHOD;
228
- }
229
- else {
230
- if (!VALID_SMOOTHING_METHODS.includes(config.smoothingMethod)) {
231
- this.log.info(`Config check: Invalid smoothing method, defaulting to "${DEFAULT_SMOOTHING_METHOD}".`);
232
- config.smoothingMethod = DEFAULT_SMOOTHING_METHOD;
233
- }
234
- }
235
- // check for Home app accessories
236
- if (config.pureHomeApp === undefined) {
237
- config.pureHomeApp = false;
238
- this.log.info(`Config check: No config for using pure Home app accessories, defaulting to ${config.pureHomeApp}.`);
239
- }
240
- else {
241
- if (typeof config.pureHomeApp !== 'boolean') {
242
- config.pureHomeApp = false;
243
- this.log.info(`Config check: Invalid setting for using pure Home app accessories, defaulting to ${config.pureHomeApp}.`);
244
- }
245
- }
246
- this.log.info(`Config check: final config is ${JSON.stringify(config)}`);
247
- return result;
248
- }
249
- /*
250
- * This method is called once at startup.
251
- * The Platform should pass all accessories which need to be created to the callback in form of a AccessoryPlugin.
252
- * The Platform must respond in a timely manner as otherwise the startup of the bridge would be unnecessarily delayed.
253
- */
254
- accessories(callback) {
255
- const accessories = [];
256
- // loop over inverters and add accessories
257
- this.inverters.forEach(inverter => accessories.push(...inverter.getAccessories()));
258
- // add inverter totalizer accessories if needed
259
- if (this.inverters.length > 1) {
260
- accessories.push(...this.allInverters.getAccessories());
261
- }
262
- callback(accessories);
263
- }
264
- }
265
- exports.SolaxCloudAPIPlatform = SolaxCloudAPIPlatform;
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SolaxCloudAPIPlatform = void 0;
4
+ const util_1 = require("./util");
5
+ const platformInverter_1 = require("./platformInverter");
6
+ /**
7
+ * Valid inverter brands (Solax, QCells)
8
+ */
9
+ const VALID_INVERTER_BRANDS = ['solax', 'qcells'];
10
+ /**
11
+ * Valid smoothing methods (Simple Moving Average, Exponential Moving Average).
12
+ */
13
+ const VALID_SMOOTHING_METHODS = ['sma', 'ema'];
14
+ /**
15
+ * Default smoothing method (simple moving average).
16
+ */
17
+ const DEFAULT_SMOOTHING_METHOD = 'sma';
18
+ /**
19
+ * Default polling frequency from Solax or QCells Cloud (in seconds).
20
+ */
21
+ const DEFAULT_POLLING_FREQUENCY = 300;
22
+ /**
23
+ * Max polls / min (obtain frequency need to be lower than 10 times/min).
24
+ */
25
+ const MAX_POLLS_MIN = 10;
26
+ /**
27
+ * Max polls / day (obtain frequency need to be lower than 10,000 times/day).
28
+ */
29
+ const MAX_POLLS_DAY = 10000;
30
+ /**
31
+ * Maximum polling frequency (in seconds).
32
+ */
33
+ const MAX_POLLING_FREQUENCY = Math.max(60 / MAX_POLLS_MIN, (24 * 60 * 60) / MAX_POLLS_DAY);
34
+ /**
35
+ * Maximum polling interval that still gives at least one smoothing period.
36
+ */
37
+ const MAX_SMOOTHING_POLLING_FREQUENCY = 15 * 60;
38
+ /**
39
+ * SolaxCloudAPIPlatform.
40
+ * This class is the main constructor for your plugin, this is where you should
41
+ * parse the user config and discover/register accessories with Homebridge.
42
+ */
43
+ class SolaxCloudAPIPlatform {
44
+ /**
45
+ * Platform constructor.
46
+ * @param log
47
+ * @param config
48
+ * @param api
49
+ * @returns
50
+ */
51
+ constructor(log, config, api) {
52
+ /**
53
+ * Number of periods to use for data smoothing.
54
+ */
55
+ this.smoothingWindow = 1;
56
+ /**
57
+ * Array of inverters.
58
+ */
59
+ this.inverters = [];
60
+ // store values in properties
61
+ this.log = log;
62
+ this.config = config;
63
+ this.api = api;
64
+ // sanity check
65
+ if (!api || !config) {
66
+ return;
67
+ }
68
+ // check for valid config
69
+ if (!this.checkConfig(this.config)) {
70
+ return;
71
+ }
72
+ // window for smoothing (aim for a 15 minute window)
73
+ this.smoothingWindow = Math.floor(15 * 60 / this.config.pollingFrequency);
74
+ this.log.info(`Window for smoothing series is ${this.smoothingWindow} periods.`);
75
+ try {
76
+ // loop over configured inverters
77
+ this.config.inverters.forEach(inverter => {
78
+ // setup new inverter
79
+ const platformInverter = this.createPlatformInverter(log, config, api, VALID_INVERTER_BRANDS.indexOf(inverter.brand), inverter.tokenId, inverter.sn, inverter.name, inverter.hasBattery, this.smoothingWindow);
80
+ // add new inverter to list
81
+ this.inverters.push(platformInverter);
82
+ });
83
+ // create inverter totalizers
84
+ if (this.inverters.length > 1) {
85
+ const uniqueInverterBrands = new Set(this.config.inverters.map(inverter => inverter.brand));
86
+ const brandName = uniqueInverterBrands.size === 1
87
+ ? this.config.inverters[0].brand
88
+ : 'Solax/QCells';
89
+ this.allInverters = this.createPlatformInverter(log, config, api, VALID_INVERTER_BRANDS.indexOf(this.config.inverters[0].brand), '', 'total', 'All inverters', this.inverters.map(inverter => +inverter.hasBattery()).reduce((a, b) => a + b, 0) > 0, this.smoothingWindow, true, SolaxCloudAPIPlatform.getInverterBrandDisplayName(brandName));
90
+ }
91
+ // start data fetching
92
+ this.fetchDataPeriodically();
93
+ this.log.debug('Finished initializing platform.');
94
+ }
95
+ catch (error) {
96
+ this.log.error(`Unexpected error: ${error}`);
97
+ }
98
+ // When this event is fired it means Homebridge has created all accessories.
99
+ this.api.on("didFinishLaunching" /* DID_FINISH_LAUNCHING */, () => {
100
+ this.log.debug('Executed didFinishLaunching callback');
101
+ });
102
+ }
103
+ /**
104
+ * Check whether object implements interface InverterConfig and brand has a valid entry.
105
+ * @param obj Any object.
106
+ * @returns Whether object implements interface InverterConfig.
107
+ */
108
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
109
+ static isInverterConfig(obj) {
110
+ return obj !== null &&
111
+ typeof obj === 'object' &&
112
+ 'name' in obj &&
113
+ 'sn' in obj &&
114
+ 'brand' in obj &&
115
+ 'tokenId' in obj &&
116
+ VALID_INVERTER_BRANDS.includes(obj.brand);
117
+ }
118
+ /**
119
+ * Gets an inverter brand display name from config.
120
+ * @param brand Inverter brand from config.
121
+ * @returns Inverter brand display name.
122
+ */
123
+ static getInverterBrandDisplayName(brand) {
124
+ switch (brand) {
125
+ case 'qcells':
126
+ return 'QCells';
127
+ case 'solax':
128
+ return 'Solax';
129
+ default:
130
+ return brand;
131
+ }
132
+ }
133
+ /**
134
+ * Creates an inverter instance. Separated for focused unit tests.
135
+ */
136
+ createPlatformInverter(log, config, api, brand, tokenId, sn, name, hasBattery, smoothingWindow, virtual = false, brandName) {
137
+ return new platformInverter_1.SolaxCloudAPIPlatformInverter(log, config, api, brand, tokenId, sn, name, hasBattery, smoothingWindow, virtual, brandName);
138
+ }
139
+ /**
140
+ * Update data for virtual inverter totalizer based on individual inverter data.
141
+ */
142
+ updateAllInvertersData() {
143
+ this.allInverters.setRawPower(platformInverter_1.ACCESSORY_KEYS.pv, this.inverters.map(inverter => inverter.getRawPower(platformInverter_1.ACCESSORY_KEYS.pv)).reduce((a, b) => a + b, 0));
144
+ this.allInverters.setRawPower(platformInverter_1.ACCESSORY_KEYS.inverterAC, this.inverters.map(inverter => inverter.getRawPower(platformInverter_1.ACCESSORY_KEYS.inverterAC)).reduce((a, b) => a + b, 0));
145
+ this.allInverters.setRawEnergy(platformInverter_1.ACCESSORY_KEYS.inverterAC, this.inverters.map(inverter => inverter.getRawEnergy(platformInverter_1.ACCESSORY_KEYS.inverterAC)).reduce((a, b) => a + b, 0));
146
+ this.allInverters.setRawPower(platformInverter_1.ACCESSORY_KEYS.inverterToGrid, this.inverters.map(inverter => inverter.getRawPower(platformInverter_1.ACCESSORY_KEYS.inverterToGrid)).reduce((a, b) => a + b, 0));
147
+ this.allInverters.setRawPower(platformInverter_1.ACCESSORY_KEYS.inverterToHouse, this.inverters.map(inverter => inverter.getRawPower(platformInverter_1.ACCESSORY_KEYS.inverterToHouse)).reduce((a, b) => a + b, 0));
148
+ this.allInverters.setRawPower(platformInverter_1.ACCESSORY_KEYS.gridToHouse, this.inverters.map(inverter => inverter.getRawPower(platformInverter_1.ACCESSORY_KEYS.gridToHouse)).reduce((a, b) => a + b, 0));
149
+ if (this.allInverters.hasBattery()) {
150
+ // get array of inverters with battery
151
+ const invertersWithBattery = this.inverters.filter(inverter => inverter.hasBattery());
152
+ // battery charge / discharge power
153
+ this.allInverters.setRawPower(platformInverter_1.ACCESSORY_KEYS.inverterFromBattery, invertersWithBattery.map(inverter => inverter.getRawPower(platformInverter_1.ACCESSORY_KEYS.inverterFromBattery)).reduce((a, b) => a + b, 0));
154
+ this.allInverters.setRawPower(platformInverter_1.ACCESSORY_KEYS.inverterToBattery, invertersWithBattery.map(inverter => inverter.getRawPower(platformInverter_1.ACCESSORY_KEYS.inverterToBattery)).reduce((a, b) => a + b, 0));
155
+ // battery SoC
156
+ this.allInverters.setBatteryChargeState(Math.max(...invertersWithBattery.map(inverter => inverter.getBatteryChargeState())));
157
+ const levelSums = invertersWithBattery.map(inverter => inverter.getBatteryLevel()).reduce((a, b) => a + b, 0);
158
+ this.allInverters.setBatteryLevel(levelSums / invertersWithBattery.length); // average levels
159
+ }
160
+ // notify update with motion
161
+ this.allInverters.setMotionUpdate();
162
+ }
163
+ /**
164
+ * Sleeps for a number of milliseconds.
165
+ * @param millis {number} The number of milliseconds to sleep.
166
+ */
167
+ async sleep(millis) {
168
+ return new Promise(resolve => setTimeout(resolve, millis));
169
+ }
170
+ /**
171
+ * Periodically retrieves inverter data from Solax Cloud API using configured tokenID and SN.
172
+ */
173
+ async fetchDataPeriodically() {
174
+ // loop forever
175
+ for (;;) {
176
+ // update data for all configured inverters
177
+ this.inverters.forEach(inverter => inverter.updateInverterDataFromCloud());
178
+ this.log.info(`Updated data from configured Cloud APIs, sleeping for ${this.config.pollingFrequency} seconds.`);
179
+ // update inverter totalizers if needed
180
+ if (this.inverters.length > 1) {
181
+ this.log.info('Computing inverter totals...');
182
+ this.updateAllInvertersData();
183
+ }
184
+ // sleep until next data poll
185
+ await this.sleep(this.config.pollingFrequency * 1000);
186
+ }
187
+ }
188
+ /**
189
+ * Checks platform config validity and sets defaults whenever needed.
190
+ * @returns {boolean} Whether the config is valid.
191
+ */
192
+ checkConfig(config) {
193
+ let result = true;
194
+ this.log.debug(`Config read: ${JSON.stringify(config)}`);
195
+ // check for mandatory parameters
196
+ if (!config.inverters) {
197
+ this.log.error('Config check: Can\'t find mandatory parameter "inverters" parameter in config file, aborting!');
198
+ result = false;
199
+ return result;
200
+ }
201
+ // check for inverters type
202
+ if (Array.isArray(config.inverters)) {
203
+ // loop over inverters and check config type
204
+ config.inverters.forEach(inverter => {
205
+ if (!SolaxCloudAPIPlatform.isInverterConfig(inverter)) {
206
+ this.log.error('Config check: Invalid type for inverter under "inverters" in config file, aborting!');
207
+ result = false;
208
+ return result;
209
+ }
210
+ });
211
+ if (!result) {
212
+ return result;
213
+ }
214
+ }
215
+ else {
216
+ this.log.error('Config check: Incorrect type for mandatory parameter "inverters" in config file, aborting!');
217
+ result = false;
218
+ return result;
219
+ }
220
+ // check for duplicates
221
+ if (util_1.Util.arrayHasDuplicates(config.inverters.map(inverter => inverter.name))) {
222
+ this.log.error('Config check: Duplicate inverter names in config file, aborting!');
223
+ result = false;
224
+ return result;
225
+ }
226
+ else if (util_1.Util.arrayHasDuplicates(config.inverters.map(inverter => inverter.sn))) {
227
+ this.log.error('Config check: Duplicate inverter SNs in config file, aborting!');
228
+ result = false;
229
+ return result;
230
+ }
231
+ // check for polling frequency
232
+ if (config.pollingFrequency === undefined) {
233
+ this.log.info(`Config check: No polling frequency provided, defaulting to ${DEFAULT_POLLING_FREQUENCY} seconds.`);
234
+ config.pollingFrequency = DEFAULT_POLLING_FREQUENCY;
235
+ }
236
+ else {
237
+ if (Number.isInteger(config.pollingFrequency) && config.pollingFrequency > 0) {
238
+ // all good?
239
+ if (config.pollingFrequency < MAX_POLLING_FREQUENCY) {
240
+ this.log.info(`Config check: Polling frequency cannot be higher than ${MAX_POLLS_MIN} times/min and ${MAX_POLLS_DAY} ` +
241
+ `times/day, defaulting to ${DEFAULT_POLLING_FREQUENCY} seconds.`);
242
+ config.pollingFrequency = DEFAULT_POLLING_FREQUENCY;
243
+ }
244
+ else if (config.pollingFrequency > MAX_SMOOTHING_POLLING_FREQUENCY) {
245
+ const message = 'Config check: Polling frequency cannot be higher than ' +
246
+ `${MAX_SMOOTHING_POLLING_FREQUENCY} seconds, aborting!`;
247
+ this.log.error(message);
248
+ throw new Error(message);
249
+ }
250
+ }
251
+ else {
252
+ this.log.info('Config check: Invalid polling frequency (must be a positive integer number), defaulting to ' +
253
+ `${DEFAULT_POLLING_FREQUENCY} seconds.`);
254
+ config.pollingFrequency = DEFAULT_POLLING_FREQUENCY;
255
+ }
256
+ }
257
+ // check for smooth meters
258
+ if (config.smoothMeters === undefined) {
259
+ config.smoothMeters = true;
260
+ this.log.info(`Config check: No config for smooth meters, defaulting to ${config.smoothMeters}.`);
261
+ }
262
+ else {
263
+ if (typeof config.smoothMeters !== 'boolean') {
264
+ config.smoothMeters = true;
265
+ this.log.info(`Config check: Invalid setting for smooth meters, defaulting to ${config.smoothMeters}.`);
266
+ }
267
+ }
268
+ // check for smoothing method
269
+ if (config.smoothingMethod === undefined) {
270
+ this.log.info(`Config check: No smoothing method provided, defaulting to "${DEFAULT_SMOOTHING_METHOD}".`);
271
+ config.smoothingMethod = DEFAULT_SMOOTHING_METHOD;
272
+ }
273
+ else {
274
+ if (!VALID_SMOOTHING_METHODS.includes(config.smoothingMethod)) {
275
+ this.log.info(`Config check: Invalid smoothing method, defaulting to "${DEFAULT_SMOOTHING_METHOD}".`);
276
+ config.smoothingMethod = DEFAULT_SMOOTHING_METHOD;
277
+ }
278
+ }
279
+ // check for Home app accessories
280
+ if (config.pureHomeApp === undefined) {
281
+ config.pureHomeApp = false;
282
+ this.log.info(`Config check: No config for using pure Home app accessories, defaulting to ${config.pureHomeApp}.`);
283
+ }
284
+ else {
285
+ if (typeof config.pureHomeApp !== 'boolean') {
286
+ config.pureHomeApp = false;
287
+ this.log.info(`Config check: Invalid setting for using pure Home app accessories, defaulting to ${config.pureHomeApp}.`);
288
+ }
289
+ }
290
+ // check for fakegato history
291
+ if (config.enableHistory === undefined) {
292
+ config.enableHistory = true;
293
+ this.log.info(`Config check: No config for history, defaulting to ${config.enableHistory}.`);
294
+ }
295
+ else {
296
+ if (typeof config.enableHistory !== 'boolean') {
297
+ config.enableHistory = true;
298
+ this.log.info(`Config check: Invalid setting for history, defaulting to ${config.enableHistory}.`);
299
+ }
300
+ }
301
+ this.log.info(`Config check: final config is ${JSON.stringify(config)}`);
302
+ return result;
303
+ }
304
+ /*
305
+ * This method is called once at startup.
306
+ * The Platform should pass all accessories which need to be created to the callback in form of a AccessoryPlugin.
307
+ * The Platform must respond in a timely manner as otherwise the startup of the bridge would be unnecessarily delayed.
308
+ */
309
+ accessories(callback) {
310
+ const accessories = [];
311
+ // loop over inverters and add accessories
312
+ this.inverters.forEach(inverter => accessories.push(...inverter.getAccessories()));
313
+ // add inverter totalizer accessories if needed
314
+ if (this.inverters.length > 1) {
315
+ accessories.push(...this.allInverters.getAccessories());
316
+ }
317
+ callback(accessories);
318
+ }
319
+ }
320
+ exports.SolaxCloudAPIPlatform = SolaxCloudAPIPlatform;
266
321
  //# sourceMappingURL=platform.js.map