@enyo-energy/sunspec-sdk 0.0.21 → 0.0.22
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/dist/cjs/sunspec-devices.cjs +183 -2
- package/dist/cjs/sunspec-devices.d.cts +68 -1
- package/dist/cjs/sunspec-interfaces.cjs +27 -2
- package/dist/cjs/sunspec-interfaces.d.cts +39 -1
- package/dist/cjs/sunspec-modbus-client.cjs +185 -0
- package/dist/cjs/sunspec-modbus-client.d.cts +21 -1
- package/dist/cjs/version.cjs +1 -1
- package/dist/cjs/version.d.cts +1 -1
- package/dist/sunspec-devices.d.ts +68 -1
- package/dist/sunspec-devices.js +184 -3
- package/dist/sunspec-interfaces.d.ts +39 -1
- package/dist/sunspec-interfaces.js +26 -1
- package/dist/sunspec-modbus-client.d.ts +21 -1
- package/dist/sunspec-modbus-client.js +186 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -2
|
@@ -7,6 +7,7 @@ const enyo_appliance_js_1 = require("@enyo-energy/energy-app-sdk/dist/types/enyo
|
|
|
7
7
|
const enyo_data_bus_value_js_1 = require("@enyo-energy/energy-app-sdk/dist/types/enyo-data-bus-value.js");
|
|
8
8
|
const enyo_source_enum_js_1 = require("@enyo-energy/energy-app-sdk/dist/types/enyo-source.enum.js");
|
|
9
9
|
const enyo_meter_appliance_js_1 = require("@enyo-energy/energy-app-sdk/dist/types/enyo-meter-appliance.js");
|
|
10
|
+
const enyo_battery_appliance_js_1 = require("@enyo-energy/energy-app-sdk/dist/types/enyo-battery-appliance.js");
|
|
10
11
|
/**
|
|
11
12
|
* Base abstract class for all Sunspec devices
|
|
12
13
|
*/
|
|
@@ -60,6 +61,7 @@ class SunspecInverter extends BaseSunspecDevice {
|
|
|
60
61
|
// Get device info from common block
|
|
61
62
|
const commonData = await this.sunspecClient.readCommonBlock();
|
|
62
63
|
const inverterSettings = await this.sunspecClient.readInverterSettings();
|
|
64
|
+
const mpptDataList = await this.sunspecClient.readAllMPPTData();
|
|
63
65
|
// Create or update appliance
|
|
64
66
|
try {
|
|
65
67
|
this.applianceId = await this.applianceManager.createOrUpdateAppliance({
|
|
@@ -74,6 +76,7 @@ class SunspecInverter extends BaseSunspecDevice {
|
|
|
74
76
|
vendorName: commonData?.manufacturer,
|
|
75
77
|
},
|
|
76
78
|
inverter: {
|
|
79
|
+
dcStrings: this.mapDcStringToApplianceMetadata(this.mapMPPTToStrings(mpptDataList)),
|
|
77
80
|
maxPvProductionW: inverterSettings?.WMax
|
|
78
81
|
}
|
|
79
82
|
});
|
|
@@ -108,6 +111,7 @@ class SunspecInverter extends BaseSunspecDevice {
|
|
|
108
111
|
const inverterData = await this.sunspecClient.readInverterData();
|
|
109
112
|
const mpptDataList = await this.sunspecClient.readAllMPPTData();
|
|
110
113
|
const inverterSettings = await this.sunspecClient.readInverterSettings();
|
|
114
|
+
const dcStrings = this.mapMPPTToStrings(mpptDataList);
|
|
111
115
|
if (inverterData) {
|
|
112
116
|
const inverterMessage = {
|
|
113
117
|
id: (0, node_crypto_1.randomUUID)(),
|
|
@@ -125,11 +129,20 @@ class SunspecInverter extends BaseSunspecDevice {
|
|
|
125
129
|
voltageL1: inverterData.voltageAN || 0,
|
|
126
130
|
voltageL2: inverterData.voltageBN ?? undefined,
|
|
127
131
|
voltageL3: inverterData.voltageCN ?? undefined,
|
|
128
|
-
strings:
|
|
132
|
+
strings: dcStrings
|
|
129
133
|
}
|
|
130
134
|
};
|
|
131
135
|
messages.push(inverterMessage);
|
|
132
136
|
}
|
|
137
|
+
if (this.applianceId) {
|
|
138
|
+
const appliance = await this.applianceManager.findApplianceById(this.applianceId);
|
|
139
|
+
await this.applianceManager.updateAppliance(this.applianceId, {
|
|
140
|
+
inverter: {
|
|
141
|
+
...appliance?.inverter,
|
|
142
|
+
dcStrings: this.mapDcStringToApplianceMetadata(dcStrings)
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
}
|
|
133
146
|
this.lastUpdateTime = timestamp.getTime();
|
|
134
147
|
}
|
|
135
148
|
catch (error) {
|
|
@@ -201,6 +214,9 @@ class SunspecInverter extends BaseSunspecDevice {
|
|
|
201
214
|
return undefined;
|
|
202
215
|
}
|
|
203
216
|
}
|
|
217
|
+
mapDcStringToApplianceMetadata(mpptDataList) {
|
|
218
|
+
return mpptDataList.map(s => ({ index: s.index, name: s.name }));
|
|
219
|
+
}
|
|
204
220
|
}
|
|
205
221
|
exports.SunspecInverter = SunspecInverter;
|
|
206
222
|
/**
|
|
@@ -210,7 +226,7 @@ class SunspecBattery extends BaseSunspecDevice {
|
|
|
210
226
|
/**
|
|
211
227
|
* Connect to the battery and create/update the appliance
|
|
212
228
|
*/
|
|
213
|
-
async connect() {
|
|
229
|
+
async connect(inverterApplianceId) {
|
|
214
230
|
// Ensure Sunspec client is connected
|
|
215
231
|
await this.ensureConnected();
|
|
216
232
|
// Check if battery models exist
|
|
@@ -222,7 +238,12 @@ class SunspecBattery extends BaseSunspecDevice {
|
|
|
222
238
|
// Get device info
|
|
223
239
|
const commonData = await this.sunspecClient.readCommonBlock();
|
|
224
240
|
const batteryData = await this.sunspecClient.readBatteryData();
|
|
241
|
+
const storageMode = this.determineStorageMode(batteryData);
|
|
242
|
+
const features = [];
|
|
225
243
|
// Create or update appliance
|
|
244
|
+
if (batteryData?.chaGriSet !== undefined) {
|
|
245
|
+
features.push(enyo_battery_appliance_js_1.EnyoBatteryFeature.GridCharging);
|
|
246
|
+
}
|
|
226
247
|
try {
|
|
227
248
|
this.applianceId = await this.applianceManager.createOrUpdateAppliance({
|
|
228
249
|
name: this.name,
|
|
@@ -234,6 +255,14 @@ class SunspecBattery extends BaseSunspecDevice {
|
|
|
234
255
|
serialNumber: commonData?.serialNumber,
|
|
235
256
|
modelName: commonData?.model,
|
|
236
257
|
vendorName: commonData?.manufacturer,
|
|
258
|
+
},
|
|
259
|
+
battery: {
|
|
260
|
+
connectedToApplianceId: inverterApplianceId,
|
|
261
|
+
storageMode: this.mapToEnyoStorageMode(storageMode),
|
|
262
|
+
maxChargingPowerW: batteryData && batteryData.wChaMax !== undefined && batteryData.inWRte !== undefined ? batteryData.wChaMax * batteryData.inWRte : undefined,
|
|
263
|
+
maxDischargePowerW: batteryData && batteryData.wChaMax !== undefined && batteryData.outWRte !== undefined ? batteryData.wChaMax * batteryData.outWRte : undefined,
|
|
264
|
+
features,
|
|
265
|
+
gridChargingEnabled: batteryData?.chaGriSet === 1
|
|
237
266
|
}
|
|
238
267
|
});
|
|
239
268
|
console.log(`Sunspec Battery connected: ${this.networkDevice.hostname} (${this.applianceId})`);
|
|
@@ -309,6 +338,19 @@ class SunspecBattery extends BaseSunspecDevice {
|
|
|
309
338
|
else {
|
|
310
339
|
console.warn('No battery data available from device');
|
|
311
340
|
}
|
|
341
|
+
if (this.applianceId) {
|
|
342
|
+
const appliance = await this.applianceManager.findApplianceById(this.applianceId);
|
|
343
|
+
const storageMode = this.determineStorageMode(batteryData);
|
|
344
|
+
await this.applianceManager.updateAppliance(this.applianceId, {
|
|
345
|
+
battery: {
|
|
346
|
+
...appliance?.battery,
|
|
347
|
+
storageMode: this.mapToEnyoStorageMode(storageMode),
|
|
348
|
+
maxChargingPowerW: batteryData && batteryData.wChaMax !== undefined && batteryData.inWRte !== undefined ? batteryData.wChaMax * batteryData.inWRte : undefined,
|
|
349
|
+
maxDischargePowerW: batteryData && batteryData.wChaMax !== undefined && batteryData.outWRte !== undefined ? batteryData.wChaMax * batteryData.outWRte : undefined,
|
|
350
|
+
gridChargingEnabled: batteryData?.chaGriSet === 1
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
}
|
|
312
354
|
this.lastUpdateTime = timestamp.getTime();
|
|
313
355
|
}
|
|
314
356
|
catch (error) {
|
|
@@ -359,6 +401,145 @@ class SunspecBattery extends BaseSunspecDevice {
|
|
|
359
401
|
return undefined;
|
|
360
402
|
}
|
|
361
403
|
}
|
|
404
|
+
/**
|
|
405
|
+
* Set the battery storage mode
|
|
406
|
+
*
|
|
407
|
+
* @param mode - The storage mode to set:
|
|
408
|
+
* - 'charge': Allow charging only
|
|
409
|
+
* - 'discharge': Allow discharging only
|
|
410
|
+
* - 'holding': Prevent both charging and discharging (to avoid battery drain)
|
|
411
|
+
* - 'auto': Allow both charging and discharging (normal operation)
|
|
412
|
+
* @returns Promise<boolean> - true if successful, false otherwise
|
|
413
|
+
*/
|
|
414
|
+
async setStorageMode(mode) {
|
|
415
|
+
if (!this.isConnected()) {
|
|
416
|
+
console.error('Battery not connected');
|
|
417
|
+
return false;
|
|
418
|
+
}
|
|
419
|
+
console.log(`Setting battery storage mode to: ${mode}`);
|
|
420
|
+
return this.sunspecClient.setStorageMode(mode);
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Enable or disable grid charging
|
|
424
|
+
*
|
|
425
|
+
* When enabled, the battery can charge from the grid.
|
|
426
|
+
* When disabled, the battery only charges from PV.
|
|
427
|
+
*
|
|
428
|
+
* Note: When grid charging is enabled, you should also set WChaMax
|
|
429
|
+
* to control the charging power from the grid.
|
|
430
|
+
*
|
|
431
|
+
* @param enable - true to enable grid charging, false to disable
|
|
432
|
+
* @returns Promise<boolean> - true if successful, false otherwise
|
|
433
|
+
*/
|
|
434
|
+
async enableGridCharging(enable) {
|
|
435
|
+
if (!this.isConnected()) {
|
|
436
|
+
console.error('Battery not connected');
|
|
437
|
+
return false;
|
|
438
|
+
}
|
|
439
|
+
console.log(`${enable ? 'Enabling' : 'Disabling'} grid charging for battery`);
|
|
440
|
+
return this.sunspecClient.enableGridCharging(enable);
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* Set battery charging power from grid
|
|
444
|
+
*
|
|
445
|
+
* This sets the maximum charging power (WChaMax) that the battery
|
|
446
|
+
* will draw when charging from the grid. Should be used together
|
|
447
|
+
* with enableGridCharging(true).
|
|
448
|
+
*
|
|
449
|
+
* @param powerW - Maximum charging power in Watts
|
|
450
|
+
* @returns Promise<boolean> - true if successful, false otherwise
|
|
451
|
+
*/
|
|
452
|
+
async setChargingPower(powerW) {
|
|
453
|
+
if (!this.isConnected()) {
|
|
454
|
+
console.error('Battery not connected');
|
|
455
|
+
return false;
|
|
456
|
+
}
|
|
457
|
+
console.log(`Setting battery charging power to: ${powerW}W`);
|
|
458
|
+
return this.sunspecClient.writeBatteryControls({ wChaMax: powerW });
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* Get current battery control settings
|
|
462
|
+
*
|
|
463
|
+
* @returns Promise<SunspecBatteryControls | null> - Current control settings or null if error
|
|
464
|
+
*/
|
|
465
|
+
async getBatteryControls() {
|
|
466
|
+
if (!this.isConnected()) {
|
|
467
|
+
console.error('Battery not connected');
|
|
468
|
+
return null;
|
|
469
|
+
}
|
|
470
|
+
return this.sunspecClient.readBatteryControls();
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Write custom battery control settings
|
|
474
|
+
*
|
|
475
|
+
* Advanced method for direct control of battery parameters.
|
|
476
|
+
* Use the simplified methods (setStorageMode, enableGridCharging) for common operations.
|
|
477
|
+
*
|
|
478
|
+
* @param controls - Partial battery control settings to write
|
|
479
|
+
* @returns Promise<boolean> - true if successful, false otherwise
|
|
480
|
+
*/
|
|
481
|
+
async writeBatteryControls(controls) {
|
|
482
|
+
if (!this.isConnected()) {
|
|
483
|
+
console.error('Battery not connected');
|
|
484
|
+
return false;
|
|
485
|
+
}
|
|
486
|
+
console.log('Writing battery controls:', controls);
|
|
487
|
+
return this.sunspecClient.writeBatteryControls(controls);
|
|
488
|
+
}
|
|
489
|
+
mapToEnyoStorageMode(storageMode) {
|
|
490
|
+
switch (storageMode) {
|
|
491
|
+
case sunspec_interfaces_js_1.SunspecStorageMode.AUTO:
|
|
492
|
+
return enyo_battery_appliance_js_1.EnyoBatteryStorageMode.AUTO;
|
|
493
|
+
case sunspec_interfaces_js_1.SunspecStorageMode.CHARGE:
|
|
494
|
+
return enyo_battery_appliance_js_1.EnyoBatteryStorageMode.CHARGE;
|
|
495
|
+
case sunspec_interfaces_js_1.SunspecStorageMode.DISCHARGE:
|
|
496
|
+
return enyo_battery_appliance_js_1.EnyoBatteryStorageMode.DISCHARGE;
|
|
497
|
+
case sunspec_interfaces_js_1.SunspecStorageMode.HOLDING:
|
|
498
|
+
return enyo_battery_appliance_js_1.EnyoBatteryStorageMode.HOLDING;
|
|
499
|
+
default:
|
|
500
|
+
return undefined;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* Get the current storage mode based on StorCtl_Mod register
|
|
505
|
+
*
|
|
506
|
+
* @returns Promise<SunspecStorageMode | null> - Current mode or null if error
|
|
507
|
+
*/
|
|
508
|
+
async getStorageMode() {
|
|
509
|
+
const controls = await this.getBatteryControls();
|
|
510
|
+
return this.determineStorageMode(controls);
|
|
511
|
+
}
|
|
512
|
+
determineStorageMode(controls) {
|
|
513
|
+
if (!controls || controls.storCtlMod === undefined) {
|
|
514
|
+
return null;
|
|
515
|
+
}
|
|
516
|
+
const chargeEnabled = (controls.storCtlMod & 0x0001) !== 0;
|
|
517
|
+
const dischargeEnabled = (controls.storCtlMod & 0x0002) !== 0;
|
|
518
|
+
if (chargeEnabled && dischargeEnabled) {
|
|
519
|
+
return sunspec_interfaces_js_1.SunspecStorageMode.AUTO;
|
|
520
|
+
}
|
|
521
|
+
else if (chargeEnabled && !dischargeEnabled) {
|
|
522
|
+
return sunspec_interfaces_js_1.SunspecStorageMode.CHARGE;
|
|
523
|
+
}
|
|
524
|
+
else if (!chargeEnabled && dischargeEnabled) {
|
|
525
|
+
return sunspec_interfaces_js_1.SunspecStorageMode.DISCHARGE;
|
|
526
|
+
}
|
|
527
|
+
else {
|
|
528
|
+
return sunspec_interfaces_js_1.SunspecStorageMode.HOLDING;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
/**
|
|
532
|
+
* Check if grid charging is enabled
|
|
533
|
+
*
|
|
534
|
+
* @returns Promise<boolean | null> - true if enabled, false if disabled, null if error
|
|
535
|
+
*/
|
|
536
|
+
async isGridChargingEnabled() {
|
|
537
|
+
const controls = await this.getBatteryControls();
|
|
538
|
+
if (!controls || controls.chaGriSet === undefined) {
|
|
539
|
+
return null;
|
|
540
|
+
}
|
|
541
|
+
return controls.chaGriSet === 1; // 1 = GRID, 0 = PV
|
|
542
|
+
}
|
|
362
543
|
}
|
|
363
544
|
exports.SunspecBattery = SunspecBattery;
|
|
364
545
|
/**
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type SunspecBatteryControls, SunspecStorageMode } from "./sunspec-interfaces.cjs";
|
|
1
2
|
import { ApplianceManager, EnergyApp } from "@enyo-energy/energy-app-sdk";
|
|
2
3
|
import { EnyoApplianceName } from "@enyo-energy/energy-app-sdk/dist/types/enyo-appliance.js";
|
|
3
4
|
import { EnyoNetworkDevice } from "@enyo-energy/energy-app-sdk/dist/types/enyo-network-device.js";
|
|
@@ -55,6 +56,7 @@ export declare class SunspecInverter extends BaseSunspecDevice {
|
|
|
55
56
|
*/
|
|
56
57
|
private mapMPPTToStrings;
|
|
57
58
|
private mapMPPTOperatingState;
|
|
59
|
+
private mapDcStringToApplianceMetadata;
|
|
58
60
|
}
|
|
59
61
|
/**
|
|
60
62
|
* Sunspec Battery implementation
|
|
@@ -63,7 +65,7 @@ export declare class SunspecBattery extends BaseSunspecDevice {
|
|
|
63
65
|
/**
|
|
64
66
|
* Connect to the battery and create/update the appliance
|
|
65
67
|
*/
|
|
66
|
-
connect(): Promise<void>;
|
|
68
|
+
connect(inverterApplianceId?: string): Promise<void>;
|
|
67
69
|
disconnect(): Promise<void>;
|
|
68
70
|
/**
|
|
69
71
|
* Map SunSpec battery charge state to Enyo battery state
|
|
@@ -83,6 +85,71 @@ export declare class SunspecBattery extends BaseSunspecDevice {
|
|
|
83
85
|
* Determine battery state from MPPT strings
|
|
84
86
|
*/
|
|
85
87
|
private determineBatteryStateFromMPPT;
|
|
88
|
+
/**
|
|
89
|
+
* Set the battery storage mode
|
|
90
|
+
*
|
|
91
|
+
* @param mode - The storage mode to set:
|
|
92
|
+
* - 'charge': Allow charging only
|
|
93
|
+
* - 'discharge': Allow discharging only
|
|
94
|
+
* - 'holding': Prevent both charging and discharging (to avoid battery drain)
|
|
95
|
+
* - 'auto': Allow both charging and discharging (normal operation)
|
|
96
|
+
* @returns Promise<boolean> - true if successful, false otherwise
|
|
97
|
+
*/
|
|
98
|
+
setStorageMode(mode: SunspecStorageMode): Promise<boolean>;
|
|
99
|
+
/**
|
|
100
|
+
* Enable or disable grid charging
|
|
101
|
+
*
|
|
102
|
+
* When enabled, the battery can charge from the grid.
|
|
103
|
+
* When disabled, the battery only charges from PV.
|
|
104
|
+
*
|
|
105
|
+
* Note: When grid charging is enabled, you should also set WChaMax
|
|
106
|
+
* to control the charging power from the grid.
|
|
107
|
+
*
|
|
108
|
+
* @param enable - true to enable grid charging, false to disable
|
|
109
|
+
* @returns Promise<boolean> - true if successful, false otherwise
|
|
110
|
+
*/
|
|
111
|
+
enableGridCharging(enable: boolean): Promise<boolean>;
|
|
112
|
+
/**
|
|
113
|
+
* Set battery charging power from grid
|
|
114
|
+
*
|
|
115
|
+
* This sets the maximum charging power (WChaMax) that the battery
|
|
116
|
+
* will draw when charging from the grid. Should be used together
|
|
117
|
+
* with enableGridCharging(true).
|
|
118
|
+
*
|
|
119
|
+
* @param powerW - Maximum charging power in Watts
|
|
120
|
+
* @returns Promise<boolean> - true if successful, false otherwise
|
|
121
|
+
*/
|
|
122
|
+
setChargingPower(powerW: number): Promise<boolean>;
|
|
123
|
+
/**
|
|
124
|
+
* Get current battery control settings
|
|
125
|
+
*
|
|
126
|
+
* @returns Promise<SunspecBatteryControls | null> - Current control settings or null if error
|
|
127
|
+
*/
|
|
128
|
+
getBatteryControls(): Promise<SunspecBatteryControls | null>;
|
|
129
|
+
/**
|
|
130
|
+
* Write custom battery control settings
|
|
131
|
+
*
|
|
132
|
+
* Advanced method for direct control of battery parameters.
|
|
133
|
+
* Use the simplified methods (setStorageMode, enableGridCharging) for common operations.
|
|
134
|
+
*
|
|
135
|
+
* @param controls - Partial battery control settings to write
|
|
136
|
+
* @returns Promise<boolean> - true if successful, false otherwise
|
|
137
|
+
*/
|
|
138
|
+
writeBatteryControls(controls: Partial<SunspecBatteryControls>): Promise<boolean>;
|
|
139
|
+
private mapToEnyoStorageMode;
|
|
140
|
+
/**
|
|
141
|
+
* Get the current storage mode based on StorCtl_Mod register
|
|
142
|
+
*
|
|
143
|
+
* @returns Promise<SunspecStorageMode | null> - Current mode or null if error
|
|
144
|
+
*/
|
|
145
|
+
getStorageMode(): Promise<SunspecStorageMode | null>;
|
|
146
|
+
private determineStorageMode;
|
|
147
|
+
/**
|
|
148
|
+
* Check if grid charging is enabled
|
|
149
|
+
*
|
|
150
|
+
* @returns Promise<boolean | null> - true if enabled, false if disabled, null if error
|
|
151
|
+
*/
|
|
152
|
+
isGridChargingEnabled(): Promise<boolean | null>;
|
|
86
153
|
}
|
|
87
154
|
/**
|
|
88
155
|
* Sunspec Meter implementation
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* SunSpec block interfaces with block numbers
|
|
4
4
|
*/
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.SunspecVArPctMode = exports.SunspecEnableControl = exports.SunspecConnectionControl = exports.SunspecStorageControlMode = exports.SunspecBatteryChargeState = exports.SunspecMPPTOperatingState = exports.SunspecModelId = void 0;
|
|
6
|
+
exports.SunspecStorageMode = exports.SunspecChargeSource = exports.SunspecVArPctMode = exports.SunspecEnableControl = exports.SunspecConnectionControl = exports.SunspecStorageControlMode = exports.SunspecBatteryChargeState = exports.SunspecMPPTOperatingState = exports.SunspecModelId = void 0;
|
|
7
7
|
/**
|
|
8
8
|
* Common Sunspec Model IDs
|
|
9
9
|
*/
|
|
@@ -57,11 +57,18 @@ var SunspecBatteryChargeState;
|
|
|
57
57
|
})(SunspecBatteryChargeState || (exports.SunspecBatteryChargeState = SunspecBatteryChargeState = {}));
|
|
58
58
|
/**
|
|
59
59
|
* Storage Control Mode bitfield values for Model 124
|
|
60
|
+
*
|
|
61
|
+
* The StorCtl_Mod register controls who has control over the battery:
|
|
62
|
+
* - When bits are set, external control is enabled for that function
|
|
63
|
+
* - When bits are clear, the battery may be in autonomous/self-consumption mode
|
|
64
|
+
* - To ensure external control works properly, the appropriate bits must be set
|
|
60
65
|
*/
|
|
61
66
|
var SunspecStorageControlMode;
|
|
62
67
|
(function (SunspecStorageControlMode) {
|
|
63
68
|
SunspecStorageControlMode[SunspecStorageControlMode["CHARGE"] = 1] = "CHARGE";
|
|
64
|
-
SunspecStorageControlMode[SunspecStorageControlMode["DISCHARGE"] = 2] = "DISCHARGE"; // Bit 1 - Enable discharge mode
|
|
69
|
+
SunspecStorageControlMode[SunspecStorageControlMode["DISCHARGE"] = 2] = "DISCHARGE"; // Bit 1 - Enable discharge mode (allow external discharge control)
|
|
70
|
+
// Note: Higher bits may control other modes like autonomous/self-consumption
|
|
71
|
+
// but these are device-specific and not standardized in SunSpec
|
|
65
72
|
})(SunspecStorageControlMode || (exports.SunspecStorageControlMode = SunspecStorageControlMode = {}));
|
|
66
73
|
/**
|
|
67
74
|
* Enum values for connection control
|
|
@@ -88,3 +95,21 @@ var SunspecVArPctMode;
|
|
|
88
95
|
SunspecVArPctMode[SunspecVArPctMode["WMAX"] = 1] = "WMAX";
|
|
89
96
|
SunspecVArPctMode[SunspecVArPctMode["VARMAX"] = 2] = "VARMAX";
|
|
90
97
|
})(SunspecVArPctMode || (exports.SunspecVArPctMode = SunspecVArPctMode = {}));
|
|
98
|
+
/**
|
|
99
|
+
* Enum values for battery charge source (Model 124, Register 17)
|
|
100
|
+
*/
|
|
101
|
+
var SunspecChargeSource;
|
|
102
|
+
(function (SunspecChargeSource) {
|
|
103
|
+
SunspecChargeSource[SunspecChargeSource["PV"] = 0] = "PV";
|
|
104
|
+
SunspecChargeSource[SunspecChargeSource["GRID"] = 1] = "GRID"; // Charge from Grid
|
|
105
|
+
})(SunspecChargeSource || (exports.SunspecChargeSource = SunspecChargeSource = {}));
|
|
106
|
+
/**
|
|
107
|
+
* Storage modes for simplified battery control
|
|
108
|
+
*/
|
|
109
|
+
var SunspecStorageMode;
|
|
110
|
+
(function (SunspecStorageMode) {
|
|
111
|
+
SunspecStorageMode["CHARGE"] = "charge";
|
|
112
|
+
SunspecStorageMode["DISCHARGE"] = "discharge";
|
|
113
|
+
SunspecStorageMode["HOLDING"] = "holding";
|
|
114
|
+
SunspecStorageMode["AUTO"] = "auto"; // Both charge and discharge allowed
|
|
115
|
+
})(SunspecStorageMode || (exports.SunspecStorageMode = SunspecStorageMode = {}));
|
|
@@ -170,9 +170,14 @@ export declare enum SunspecBatteryChargeState {
|
|
|
170
170
|
}
|
|
171
171
|
/**
|
|
172
172
|
* Storage Control Mode bitfield values for Model 124
|
|
173
|
+
*
|
|
174
|
+
* The StorCtl_Mod register controls who has control over the battery:
|
|
175
|
+
* - When bits are set, external control is enabled for that function
|
|
176
|
+
* - When bits are clear, the battery may be in autonomous/self-consumption mode
|
|
177
|
+
* - To ensure external control works properly, the appropriate bits must be set
|
|
173
178
|
*/
|
|
174
179
|
export declare enum SunspecStorageControlMode {
|
|
175
|
-
CHARGE = 1,// Bit 0 - Enable charge mode
|
|
180
|
+
CHARGE = 1,// Bit 0 - Enable charge mode (allow external charge control)
|
|
176
181
|
DISCHARGE = 2
|
|
177
182
|
}
|
|
178
183
|
/**
|
|
@@ -338,3 +343,36 @@ export declare enum SunspecVArPctMode {
|
|
|
338
343
|
WMAX = 1,
|
|
339
344
|
VARMAX = 2
|
|
340
345
|
}
|
|
346
|
+
/**
|
|
347
|
+
* Enum values for battery charge source (Model 124, Register 17)
|
|
348
|
+
*/
|
|
349
|
+
export declare enum SunspecChargeSource {
|
|
350
|
+
PV = 0,// Charge from PV only
|
|
351
|
+
GRID = 1
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Storage modes for simplified battery control
|
|
355
|
+
*/
|
|
356
|
+
export declare enum SunspecStorageMode {
|
|
357
|
+
CHARGE = "charge",// Allow charging only
|
|
358
|
+
DISCHARGE = "discharge",// Allow discharging only
|
|
359
|
+
HOLDING = "holding",// No charge or discharge (holding mode)
|
|
360
|
+
AUTO = "auto"
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Battery control structure for writing to Model 124
|
|
364
|
+
* Used for controlling battery charge/discharge behavior
|
|
365
|
+
*
|
|
366
|
+
* IMPORTANT: To enable grid charging with specific power:
|
|
367
|
+
* 1. Set storCtlMod with appropriate bits to enable external control
|
|
368
|
+
* 2. Set chaGriSet = 1 to allow grid charging
|
|
369
|
+
* 3. Set wChaMax to the desired charging power in Watts
|
|
370
|
+
*/
|
|
371
|
+
export interface SunspecBatteryControls {
|
|
372
|
+
storCtlMod?: number;
|
|
373
|
+
chaGriSet?: number;
|
|
374
|
+
wChaMax?: number;
|
|
375
|
+
inWRte?: number;
|
|
376
|
+
outWRte?: number;
|
|
377
|
+
minRsvPct?: number;
|
|
378
|
+
}
|
|
@@ -265,6 +265,52 @@ class SunspecModbusClient {
|
|
|
265
265
|
throw error;
|
|
266
266
|
}
|
|
267
267
|
}
|
|
268
|
+
/**
|
|
269
|
+
* Helper to write register value(s)
|
|
270
|
+
*/
|
|
271
|
+
async writeRegisterValue(address, value, dataType = 'uint16') {
|
|
272
|
+
if (!this.modbusClient) {
|
|
273
|
+
throw new Error('Modbus client not initialized');
|
|
274
|
+
}
|
|
275
|
+
try {
|
|
276
|
+
// Convert value to array of register values
|
|
277
|
+
let registerValues;
|
|
278
|
+
if (Array.isArray(value)) {
|
|
279
|
+
// For arrays, use directly
|
|
280
|
+
registerValues = value;
|
|
281
|
+
}
|
|
282
|
+
else {
|
|
283
|
+
// For single values, convert based on data type
|
|
284
|
+
switch (dataType) {
|
|
285
|
+
case 'uint32':
|
|
286
|
+
case 'int32':
|
|
287
|
+
// Split 32-bit value into two 16-bit registers
|
|
288
|
+
const high = (value >>> 16) & 0xFFFF;
|
|
289
|
+
const low = value & 0xFFFF;
|
|
290
|
+
registerValues = [high, low];
|
|
291
|
+
break;
|
|
292
|
+
case 'int16':
|
|
293
|
+
// For signed 16-bit, ensure proper representation
|
|
294
|
+
registerValues = [value & 0xFFFF];
|
|
295
|
+
break;
|
|
296
|
+
case 'uint16':
|
|
297
|
+
default:
|
|
298
|
+
registerValues = [value & 0xFFFF];
|
|
299
|
+
break;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
// Write to holding registers
|
|
303
|
+
await this.modbusClient.writeMultipleRegisters(address, registerValues);
|
|
304
|
+
this.connectionHealth.recordSuccess();
|
|
305
|
+
console.log(`Successfully wrote value ${value} to register ${address}`);
|
|
306
|
+
return true;
|
|
307
|
+
}
|
|
308
|
+
catch (error) {
|
|
309
|
+
this.connectionHealth.recordFailure(error);
|
|
310
|
+
console.error(`Error writing register at address ${address}: ${error}`);
|
|
311
|
+
throw error;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
268
314
|
/**
|
|
269
315
|
* Read inverter data from Model 103 (Three Phase)
|
|
270
316
|
*/
|
|
@@ -797,6 +843,145 @@ class SunspecModbusClient {
|
|
|
797
843
|
return null;
|
|
798
844
|
}
|
|
799
845
|
}
|
|
846
|
+
/**
|
|
847
|
+
* Write battery control settings to Model 124
|
|
848
|
+
*/
|
|
849
|
+
async writeBatteryControls(controls) {
|
|
850
|
+
const model = this.findModel(sunspec_interfaces_js_1.SunspecModelId.Battery);
|
|
851
|
+
if (!model) {
|
|
852
|
+
console.error('Battery model 124 not found');
|
|
853
|
+
return false;
|
|
854
|
+
}
|
|
855
|
+
const baseAddr = model.address;
|
|
856
|
+
console.log(`Writing Battery Controls to Model 124 at base address: ${baseAddr}`);
|
|
857
|
+
try {
|
|
858
|
+
// Write storage control mode (Register 5)
|
|
859
|
+
if (controls.storCtlMod !== undefined) {
|
|
860
|
+
await this.writeRegisterValue(baseAddr + 5, controls.storCtlMod, 'uint16');
|
|
861
|
+
console.log(`Set storage control mode to 0x${controls.storCtlMod.toString(16)}`);
|
|
862
|
+
}
|
|
863
|
+
// Write charge source setting (Register 17)
|
|
864
|
+
if (controls.chaGriSet !== undefined) {
|
|
865
|
+
await this.writeRegisterValue(baseAddr + 17, controls.chaGriSet, 'uint16');
|
|
866
|
+
console.log(`Set charge source to ${controls.chaGriSet === sunspec_interfaces_js_1.SunspecChargeSource.GRID ? 'GRID' : 'PV'}`);
|
|
867
|
+
}
|
|
868
|
+
// Write maximum charge power (Register 2) - needs scale factor
|
|
869
|
+
if (controls.wChaMax !== undefined) {
|
|
870
|
+
const scaleFactorAddr = baseAddr + 18;
|
|
871
|
+
const scaleFactor = await this.readRegisterValue(scaleFactorAddr, 1, 'int16');
|
|
872
|
+
const scaledValue = Math.round(controls.wChaMax / Math.pow(10, scaleFactor));
|
|
873
|
+
await this.writeRegisterValue(baseAddr + 2, scaledValue, 'uint16');
|
|
874
|
+
console.log(`Set max charge power to ${controls.wChaMax}W (scaled: ${scaledValue})`);
|
|
875
|
+
}
|
|
876
|
+
// Write charge rate (Register 13) - needs scale factor
|
|
877
|
+
if (controls.inWRte !== undefined) {
|
|
878
|
+
const scaleFactorAddr = baseAddr + 25;
|
|
879
|
+
const scaleFactor = await this.readRegisterValue(scaleFactorAddr, 1, 'int16');
|
|
880
|
+
const scaledValue = Math.round(controls.inWRte / Math.pow(10, scaleFactor));
|
|
881
|
+
await this.writeRegisterValue(baseAddr + 13, scaledValue, 'int16');
|
|
882
|
+
console.log(`Set charge rate to ${controls.inWRte}% (scaled: ${scaledValue})`);
|
|
883
|
+
}
|
|
884
|
+
// Write discharge rate (Register 12) - needs scale factor
|
|
885
|
+
if (controls.outWRte !== undefined) {
|
|
886
|
+
const scaleFactorAddr = baseAddr + 25;
|
|
887
|
+
const scaleFactor = await this.readRegisterValue(scaleFactorAddr, 1, 'int16');
|
|
888
|
+
const scaledValue = Math.round(controls.outWRte / Math.pow(10, scaleFactor));
|
|
889
|
+
await this.writeRegisterValue(baseAddr + 12, scaledValue, 'int16');
|
|
890
|
+
console.log(`Set discharge rate to ${controls.outWRte}% (scaled: ${scaledValue})`);
|
|
891
|
+
}
|
|
892
|
+
// Write minimum reserve percentage (Register 7) - needs scale factor
|
|
893
|
+
if (controls.minRsvPct !== undefined) {
|
|
894
|
+
const scaleFactorAddr = baseAddr + 21;
|
|
895
|
+
const scaleFactor = await this.readRegisterValue(scaleFactorAddr, 1, 'int16');
|
|
896
|
+
const scaledValue = Math.round(controls.minRsvPct / Math.pow(10, scaleFactor));
|
|
897
|
+
await this.writeRegisterValue(baseAddr + 7, scaledValue, 'uint16');
|
|
898
|
+
console.log(`Set minimum reserve to ${controls.minRsvPct}% (scaled: ${scaledValue})`);
|
|
899
|
+
}
|
|
900
|
+
console.log('Battery controls written successfully');
|
|
901
|
+
return true;
|
|
902
|
+
}
|
|
903
|
+
catch (error) {
|
|
904
|
+
console.error(`Error writing battery controls: ${error}`);
|
|
905
|
+
return false;
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
/**
|
|
909
|
+
* Set battery storage mode (simplified interface)
|
|
910
|
+
*/
|
|
911
|
+
async setStorageMode(mode) {
|
|
912
|
+
let storCtlMod;
|
|
913
|
+
switch (mode) {
|
|
914
|
+
case sunspec_interfaces_js_1.SunspecStorageMode.CHARGE:
|
|
915
|
+
// Enable charge only (bit 0 = 1, bit 1 = 0)
|
|
916
|
+
storCtlMod = sunspec_interfaces_js_1.SunspecStorageControlMode.CHARGE;
|
|
917
|
+
break;
|
|
918
|
+
case sunspec_interfaces_js_1.SunspecStorageMode.DISCHARGE:
|
|
919
|
+
// Enable discharge only (bit 0 = 0, bit 1 = 1)
|
|
920
|
+
storCtlMod = sunspec_interfaces_js_1.SunspecStorageControlMode.DISCHARGE;
|
|
921
|
+
break;
|
|
922
|
+
case sunspec_interfaces_js_1.SunspecStorageMode.HOLDING:
|
|
923
|
+
// Disable both charge and discharge (bits 0 and 1 = 0)
|
|
924
|
+
storCtlMod = 0;
|
|
925
|
+
break;
|
|
926
|
+
case sunspec_interfaces_js_1.SunspecStorageMode.AUTO:
|
|
927
|
+
// Enable both charge and discharge (bits 0 and 1 = 1)
|
|
928
|
+
storCtlMod = sunspec_interfaces_js_1.SunspecStorageControlMode.CHARGE | sunspec_interfaces_js_1.SunspecStorageControlMode.DISCHARGE;
|
|
929
|
+
break;
|
|
930
|
+
default:
|
|
931
|
+
console.error(`Invalid storage mode: ${mode}`);
|
|
932
|
+
return false;
|
|
933
|
+
}
|
|
934
|
+
console.log(`Setting storage mode to ${mode} (control bits: 0x${storCtlMod.toString(16)})`);
|
|
935
|
+
return this.writeBatteryControls({ storCtlMod });
|
|
936
|
+
}
|
|
937
|
+
/**
|
|
938
|
+
* Enable or disable grid charging
|
|
939
|
+
*/
|
|
940
|
+
async enableGridCharging(enable) {
|
|
941
|
+
const chaGriSet = enable ? sunspec_interfaces_js_1.SunspecChargeSource.GRID : sunspec_interfaces_js_1.SunspecChargeSource.PV;
|
|
942
|
+
console.log(`${enable ? 'Enabling' : 'Disabling'} grid charging`);
|
|
943
|
+
return this.writeBatteryControls({ chaGriSet });
|
|
944
|
+
}
|
|
945
|
+
/**
|
|
946
|
+
* Read current battery control settings
|
|
947
|
+
*/
|
|
948
|
+
async readBatteryControls() {
|
|
949
|
+
const model = this.findModel(sunspec_interfaces_js_1.SunspecModelId.Battery);
|
|
950
|
+
if (!model) {
|
|
951
|
+
console.log('Battery model 124 not found');
|
|
952
|
+
return null;
|
|
953
|
+
}
|
|
954
|
+
const baseAddr = model.address;
|
|
955
|
+
console.log(`Reading Battery Controls from Model 124 at base address: ${baseAddr}`);
|
|
956
|
+
try {
|
|
957
|
+
// Read scale factors
|
|
958
|
+
const scaleFactors = {
|
|
959
|
+
WChaMax_SF: await this.readRegisterValue(baseAddr + 18, 1, 'int16'),
|
|
960
|
+
MinRsvPct_SF: await this.readRegisterValue(baseAddr + 21, 1, 'int16'),
|
|
961
|
+
InOutWRte_SF: await this.readRegisterValue(baseAddr + 25, 1, 'int16')
|
|
962
|
+
};
|
|
963
|
+
// Read raw values
|
|
964
|
+
const wChaMaxRaw = await this.readRegisterValue(baseAddr + 2, 1, 'uint16');
|
|
965
|
+
const storCtlModRaw = await this.readRegisterValue(baseAddr + 5, 1, 'uint16');
|
|
966
|
+
const minRsvPctRaw = await this.readRegisterValue(baseAddr + 7, 1, 'uint16');
|
|
967
|
+
const outWRteRaw = await this.readRegisterValue(baseAddr + 12, 1, 'int16');
|
|
968
|
+
const inWRteRaw = await this.readRegisterValue(baseAddr + 13, 1, 'int16');
|
|
969
|
+
const chaGriSetRaw = await this.readRegisterValue(baseAddr + 17, 1, 'uint16');
|
|
970
|
+
// Apply scale factors and return control settings
|
|
971
|
+
return {
|
|
972
|
+
storCtlMod: !this.isUnimplementedValue(storCtlModRaw, 'bitfield16') ? storCtlModRaw : undefined,
|
|
973
|
+
chaGriSet: !this.isUnimplementedValue(chaGriSetRaw, 'enum16') ? chaGriSetRaw : undefined,
|
|
974
|
+
wChaMax: this.applyScaleFactor(wChaMaxRaw, scaleFactors.WChaMax_SF, 'uint16'),
|
|
975
|
+
inWRte: this.applyScaleFactor(inWRteRaw, scaleFactors.InOutWRte_SF, 'int16'),
|
|
976
|
+
outWRte: this.applyScaleFactor(outWRteRaw, scaleFactors.InOutWRte_SF, 'int16'),
|
|
977
|
+
minRsvPct: this.applyScaleFactor(minRsvPctRaw, scaleFactors.MinRsvPct_SF, 'uint16')
|
|
978
|
+
};
|
|
979
|
+
}
|
|
980
|
+
catch (error) {
|
|
981
|
+
console.error(`Error reading battery controls: ${error}`);
|
|
982
|
+
return null;
|
|
983
|
+
}
|
|
984
|
+
}
|
|
800
985
|
/**
|
|
801
986
|
* Read meter data (Model 203 for 3-phase)
|
|
802
987
|
*/
|