@enyo-energy/sunspec-sdk 0.0.46 → 0.0.48
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 +20 -3
- package/dist/cjs/sunspec-devices.d.cts +3 -1
- package/dist/cjs/sunspec-modbus-client.cjs +48 -4
- package/dist/cjs/sunspec-modbus-client.d.cts +17 -1
- package/dist/cjs/version.cjs +1 -1
- package/dist/cjs/version.d.cts +1 -1
- package/dist/sunspec-devices.d.ts +3 -1
- package/dist/sunspec-devices.js +20 -3
- package/dist/sunspec-modbus-client.d.ts +17 -1
- package/dist/sunspec-modbus-client.js +48 -4
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -2
|
@@ -74,8 +74,10 @@ class BaseSunspecDevice {
|
|
|
74
74
|
const phase = this.retryManager.getCurrentPhase();
|
|
75
75
|
const attempt = this.retryManager.getAttemptCount();
|
|
76
76
|
const elapsed = Math.round(this.retryManager.getElapsedMs() / 1000);
|
|
77
|
+
const stats = this.sunspecClient.getConnectionStats();
|
|
77
78
|
console.log(`${this.constructor.name} ${this.applianceId}: Reconnect attempt #${attempt} ` +
|
|
78
|
-
`(phase: ${phase.intervalMs / 1000}s interval, elapsed: ${elapsed}s
|
|
79
|
+
`(phase: ${phase.intervalMs / 1000}s interval, elapsed: ${elapsed}s, ` +
|
|
80
|
+
`opens=${stats.opens}, closes=${stats.closes}, current=${stats.currentlyOpen})`);
|
|
79
81
|
try {
|
|
80
82
|
const success = await this.sunspecClient.reconnect();
|
|
81
83
|
if (success) {
|
|
@@ -86,7 +88,8 @@ class BaseSunspecDevice {
|
|
|
86
88
|
if (this.applianceId) {
|
|
87
89
|
await this.applianceManager.updateApplianceState(this.applianceId, enyo_appliance_js_1.EnyoApplianceConnectionType.Connector, enyo_appliance_js_1.EnyoApplianceStateEnum.Connected);
|
|
88
90
|
}
|
|
89
|
-
|
|
91
|
+
const postStats = this.sunspecClient.getConnectionStats();
|
|
92
|
+
console.log(`${this.constructor.name} ${this.applianceId}: Reconnection successful after ${attempt} attempt(s) (opens=${postStats.opens}, closes=${postStats.closes}, current=${postStats.currentlyOpen})`);
|
|
90
93
|
return true;
|
|
91
94
|
}
|
|
92
95
|
}
|
|
@@ -96,10 +99,22 @@ class BaseSunspecDevice {
|
|
|
96
99
|
return false;
|
|
97
100
|
}
|
|
98
101
|
/**
|
|
99
|
-
* Mark the device as offline:
|
|
102
|
+
* Mark the device as offline: close the underlying socket so the next readData()
|
|
103
|
+
* cycle sees isConnected() === false and tryReconnect() can establish a fresh
|
|
104
|
+
* connection after the backoff interval. Then update appliance state.
|
|
100
105
|
*/
|
|
101
106
|
async markOffline() {
|
|
102
107
|
this.retryManager.markDisconnected();
|
|
108
|
+
try {
|
|
109
|
+
if (this.sunspecClient.isConnected()) {
|
|
110
|
+
await this.sunspecClient.disconnect();
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
console.error(`${this.constructor.name} ${this.applianceId}: error closing socket on markOffline: ${error}`);
|
|
115
|
+
}
|
|
116
|
+
const stats = this.sunspecClient.getConnectionStats();
|
|
117
|
+
console.log(`${this.constructor.name} ${this.applianceId}: marked offline (opens=${stats.opens}, closes=${stats.closes}, current=${stats.currentlyOpen})`);
|
|
103
118
|
if (this.applianceId) {
|
|
104
119
|
try {
|
|
105
120
|
await this.applianceManager.updateApplianceState(this.applianceId, enyo_appliance_js_1.EnyoApplianceConnectionType.Connector, enyo_appliance_js_1.EnyoApplianceStateEnum.Offline);
|
|
@@ -248,6 +263,7 @@ class SunspecInverter extends BaseSunspecDevice {
|
|
|
248
263
|
voltageL1: inverterData.voltageAN || 0,
|
|
249
264
|
voltageL2: inverterData.voltageBN ?? undefined,
|
|
250
265
|
voltageL3: inverterData.voltageCN ?? undefined,
|
|
266
|
+
meterValueWh: inverterData.acEnergy,
|
|
251
267
|
strings: dcStrings
|
|
252
268
|
}
|
|
253
269
|
};
|
|
@@ -305,6 +321,7 @@ class SunspecInverter extends BaseSunspecDevice {
|
|
|
305
321
|
current: mppt.dcCurrent,
|
|
306
322
|
voltage: mppt.dcVoltage,
|
|
307
323
|
powerW: mppt.dcPower,
|
|
324
|
+
meterValueWh: mppt.dcEnergy,
|
|
308
325
|
});
|
|
309
326
|
}
|
|
310
327
|
});
|
|
@@ -54,7 +54,9 @@ export declare abstract class BaseSunspecDevice {
|
|
|
54
54
|
*/
|
|
55
55
|
protected tryReconnect(): Promise<boolean>;
|
|
56
56
|
/**
|
|
57
|
-
* Mark the device as offline:
|
|
57
|
+
* Mark the device as offline: close the underlying socket so the next readData()
|
|
58
|
+
* cycle sees isConnected() === false and tryReconnect() can establish a fresh
|
|
59
|
+
* connection after the backoff interval. Then update appliance state.
|
|
58
60
|
*/
|
|
59
61
|
protected markOffline(): Promise<void>;
|
|
60
62
|
protected sendCommandAcknowledge(messageId: string, acknowledgeMessage: EnyoDataBusMessageEnum | string, answer: EnyoCommandAcknowledgeAnswerEnum, rejectionReason?: string): void;
|
|
@@ -33,6 +33,9 @@ class SunspecModbusClient {
|
|
|
33
33
|
modbusDataTypeConverter;
|
|
34
34
|
connectionParams = null;
|
|
35
35
|
autoReconnectEnabled = true;
|
|
36
|
+
openCount = 0;
|
|
37
|
+
closeCount = 0;
|
|
38
|
+
currentlyOpen = 0;
|
|
36
39
|
constructor(energyApp) {
|
|
37
40
|
this.energyApp = energyApp;
|
|
38
41
|
this.connectionHealth = new EnergyAppModbusConnectionHealth_js_1.EnergyAppModbusConnectionHealth();
|
|
@@ -63,10 +66,14 @@ class SunspecModbusClient {
|
|
|
63
66
|
}
|
|
64
67
|
this.connected = true;
|
|
65
68
|
this.connectionHealth.recordSuccess();
|
|
66
|
-
|
|
69
|
+
this.recordOpen();
|
|
70
|
+
console.log(`Connected to Sunspec device at ${host}:${port} unit ${unitId} (opens=${this.openCount}, closes=${this.closeCount}, current=${this.currentlyOpen})`);
|
|
67
71
|
}
|
|
68
72
|
/**
|
|
69
|
-
* Disconnect from Modbus device
|
|
73
|
+
* Disconnect from Modbus device.
|
|
74
|
+
*
|
|
75
|
+
* Note: connection parameters are preserved so reconnect() can be called afterwards.
|
|
76
|
+
* They will be overwritten by the next connect() call anyway.
|
|
70
77
|
*/
|
|
71
78
|
async disconnect() {
|
|
72
79
|
if (this.modbusClient && this.connected) {
|
|
@@ -75,9 +82,12 @@ class SunspecModbusClient {
|
|
|
75
82
|
this.faultTolerantReader = null;
|
|
76
83
|
this.connected = false;
|
|
77
84
|
this.discoveredModels.clear();
|
|
85
|
+
this.recordClose();
|
|
86
|
+
const host = this.connectionParams?.primaryHost ?? 'unknown';
|
|
87
|
+
const port = this.connectionParams?.port ?? 0;
|
|
88
|
+
const unitId = this.connectionParams?.unitId ?? 0;
|
|
89
|
+
console.log(`Disconnected from Sunspec device at ${host}:${port} unit ${unitId} (opens=${this.openCount}, closes=${this.closeCount}, current=${this.currentlyOpen})`);
|
|
78
90
|
}
|
|
79
|
-
// Clear stored connection params
|
|
80
|
-
this.connectionParams = null;
|
|
81
91
|
}
|
|
82
92
|
/**
|
|
83
93
|
* Reconnect using stored connection parameters
|
|
@@ -118,6 +128,7 @@ class SunspecModbusClient {
|
|
|
118
128
|
try {
|
|
119
129
|
// Disconnect existing connection if any
|
|
120
130
|
if (this.modbusClient) {
|
|
131
|
+
const wasConnected = this.connected;
|
|
121
132
|
try {
|
|
122
133
|
await this.modbusClient.disconnect();
|
|
123
134
|
}
|
|
@@ -126,6 +137,10 @@ class SunspecModbusClient {
|
|
|
126
137
|
}
|
|
127
138
|
this.modbusClient = null;
|
|
128
139
|
this.faultTolerantReader = null;
|
|
140
|
+
if (wasConnected) {
|
|
141
|
+
this.connected = false;
|
|
142
|
+
this.recordClose();
|
|
143
|
+
}
|
|
129
144
|
}
|
|
130
145
|
// Attempt connection
|
|
131
146
|
this.modbusClient = await this.energyApp.useModbus().connect({
|
|
@@ -140,6 +155,8 @@ class SunspecModbusClient {
|
|
|
140
155
|
}
|
|
141
156
|
this.connected = true;
|
|
142
157
|
this.connectionHealth.recordSuccess();
|
|
158
|
+
this.recordOpen();
|
|
159
|
+
console.log(`Connection attempt to ${host}:${port} unit ${unitId} succeeded (opens=${this.openCount}, closes=${this.closeCount}, current=${this.currentlyOpen})`);
|
|
143
160
|
return true;
|
|
144
161
|
}
|
|
145
162
|
catch (error) {
|
|
@@ -147,6 +164,25 @@ class SunspecModbusClient {
|
|
|
147
164
|
return false;
|
|
148
165
|
}
|
|
149
166
|
}
|
|
167
|
+
recordOpen() {
|
|
168
|
+
this.openCount++;
|
|
169
|
+
this.currentlyOpen++;
|
|
170
|
+
}
|
|
171
|
+
recordClose() {
|
|
172
|
+
this.closeCount++;
|
|
173
|
+
if (this.currentlyOpen > 0) {
|
|
174
|
+
this.currentlyOpen--;
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
console.warn(`SunspecModbusClient: closeCount incremented while currentlyOpen was 0 — possible double-close`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Get cumulative open/close counts for this client. Useful for spotting connection leaks.
|
|
182
|
+
*/
|
|
183
|
+
getConnectionStats() {
|
|
184
|
+
return { opens: this.openCount, closes: this.closeCount, currentlyOpen: this.currentlyOpen };
|
|
185
|
+
}
|
|
150
186
|
/**
|
|
151
187
|
* Enable or disable automatic reconnection
|
|
152
188
|
*/
|
|
@@ -585,6 +621,7 @@ class SunspecModbusClient {
|
|
|
585
621
|
V_SF: this.extractValue(buffer, 13, 'int16'),
|
|
586
622
|
W_SF: this.extractValue(buffer, 10, 'int16'),
|
|
587
623
|
Hz_SF: this.extractValue(buffer, 12, 'int16'),
|
|
624
|
+
WH_SF: this.extractValue(buffer, 26, 'int16'),
|
|
588
625
|
DCA_SF: this.extractValue(buffer, 18, 'int16'),
|
|
589
626
|
DCV_SF: this.extractValue(buffer, 19, 'int16'),
|
|
590
627
|
DCW_SF: this.extractValue(buffer, 21, 'int16')
|
|
@@ -593,6 +630,7 @@ class SunspecModbusClient {
|
|
|
593
630
|
this.logRegisterRead(101, 13, 'V_SF', scaleFactors.V_SF, 'int16');
|
|
594
631
|
this.logRegisterRead(101, 10, 'W_SF', scaleFactors.W_SF, 'int16');
|
|
595
632
|
this.logRegisterRead(101, 12, 'Hz_SF', scaleFactors.Hz_SF, 'int16');
|
|
633
|
+
this.logRegisterRead(101, 26, 'WH_SF', scaleFactors.WH_SF, 'int16');
|
|
596
634
|
this.logRegisterRead(101, 18, 'DCA_SF', scaleFactors.DCA_SF, 'int16');
|
|
597
635
|
this.logRegisterRead(101, 19, 'DCV_SF', scaleFactors.DCV_SF, 'int16');
|
|
598
636
|
this.logRegisterRead(101, 21, 'DCW_SF', scaleFactors.DCW_SF, 'int16');
|
|
@@ -617,6 +655,12 @@ class SunspecModbusClient {
|
|
|
617
655
|
dcPower: this.applyScaleFactor(dcPowerRaw, scaleFactors.DCW_SF, 'int16', 'DC Power', 20, 101),
|
|
618
656
|
operatingState: stateRaw
|
|
619
657
|
};
|
|
658
|
+
// Read AC Energy (32-bit accumulator) - Offset 24-25
|
|
659
|
+
const acEnergy = this.extractValue(buffer, 24, 'uint32', 2);
|
|
660
|
+
this.logRegisterRead(101, 24, 'AC Energy', acEnergy, 'acc32');
|
|
661
|
+
data.acEnergy = !this.isUnimplementedValue(acEnergy, 'acc32')
|
|
662
|
+
? acEnergy * Math.pow(10, scaleFactors.WH_SF)
|
|
663
|
+
: undefined;
|
|
620
664
|
console.debug('[Model 101] Single Phase Inverter Data:', data);
|
|
621
665
|
return data;
|
|
622
666
|
}
|
|
@@ -29,6 +29,9 @@ export declare class SunspecModbusClient {
|
|
|
29
29
|
private modbusDataTypeConverter;
|
|
30
30
|
private connectionParams;
|
|
31
31
|
private autoReconnectEnabled;
|
|
32
|
+
private openCount;
|
|
33
|
+
private closeCount;
|
|
34
|
+
private currentlyOpen;
|
|
32
35
|
constructor(energyApp: EnergyApp);
|
|
33
36
|
/**
|
|
34
37
|
* Connect to Modbus device
|
|
@@ -39,7 +42,10 @@ export declare class SunspecModbusClient {
|
|
|
39
42
|
*/
|
|
40
43
|
connect(host: string, port?: number, unitId?: number, secondaryHost?: string): Promise<void>;
|
|
41
44
|
/**
|
|
42
|
-
* Disconnect from Modbus device
|
|
45
|
+
* Disconnect from Modbus device.
|
|
46
|
+
*
|
|
47
|
+
* Note: connection parameters are preserved so reconnect() can be called afterwards.
|
|
48
|
+
* They will be overwritten by the next connect() call anyway.
|
|
43
49
|
*/
|
|
44
50
|
disconnect(): Promise<void>;
|
|
45
51
|
/**
|
|
@@ -53,6 +59,16 @@ export declare class SunspecModbusClient {
|
|
|
53
59
|
* Returns true if successful, false otherwise
|
|
54
60
|
*/
|
|
55
61
|
private attemptConnection;
|
|
62
|
+
private recordOpen;
|
|
63
|
+
private recordClose;
|
|
64
|
+
/**
|
|
65
|
+
* Get cumulative open/close counts for this client. Useful for spotting connection leaks.
|
|
66
|
+
*/
|
|
67
|
+
getConnectionStats(): {
|
|
68
|
+
opens: number;
|
|
69
|
+
closes: number;
|
|
70
|
+
currentlyOpen: number;
|
|
71
|
+
};
|
|
56
72
|
/**
|
|
57
73
|
* Enable or disable automatic reconnection
|
|
58
74
|
*/
|
package/dist/cjs/version.cjs
CHANGED
|
@@ -9,7 +9,7 @@ exports.getSdkVersion = getSdkVersion;
|
|
|
9
9
|
/**
|
|
10
10
|
* Current version of the enyo Energy App SDK.
|
|
11
11
|
*/
|
|
12
|
-
exports.SDK_VERSION = '0.0.
|
|
12
|
+
exports.SDK_VERSION = '0.0.48';
|
|
13
13
|
/**
|
|
14
14
|
* Gets the current SDK version.
|
|
15
15
|
* @returns The semantic version string of the SDK
|
package/dist/cjs/version.d.cts
CHANGED
|
@@ -54,7 +54,9 @@ export declare abstract class BaseSunspecDevice {
|
|
|
54
54
|
*/
|
|
55
55
|
protected tryReconnect(): Promise<boolean>;
|
|
56
56
|
/**
|
|
57
|
-
* Mark the device as offline:
|
|
57
|
+
* Mark the device as offline: close the underlying socket so the next readData()
|
|
58
|
+
* cycle sees isConnected() === false and tryReconnect() can establish a fresh
|
|
59
|
+
* connection after the backoff interval. Then update appliance state.
|
|
58
60
|
*/
|
|
59
61
|
protected markOffline(): Promise<void>;
|
|
60
62
|
protected sendCommandAcknowledge(messageId: string, acknowledgeMessage: EnyoDataBusMessageEnum | string, answer: EnyoCommandAcknowledgeAnswerEnum, rejectionReason?: string): void;
|
package/dist/sunspec-devices.js
CHANGED
|
@@ -71,8 +71,10 @@ export class BaseSunspecDevice {
|
|
|
71
71
|
const phase = this.retryManager.getCurrentPhase();
|
|
72
72
|
const attempt = this.retryManager.getAttemptCount();
|
|
73
73
|
const elapsed = Math.round(this.retryManager.getElapsedMs() / 1000);
|
|
74
|
+
const stats = this.sunspecClient.getConnectionStats();
|
|
74
75
|
console.log(`${this.constructor.name} ${this.applianceId}: Reconnect attempt #${attempt} ` +
|
|
75
|
-
`(phase: ${phase.intervalMs / 1000}s interval, elapsed: ${elapsed}s
|
|
76
|
+
`(phase: ${phase.intervalMs / 1000}s interval, elapsed: ${elapsed}s, ` +
|
|
77
|
+
`opens=${stats.opens}, closes=${stats.closes}, current=${stats.currentlyOpen})`);
|
|
76
78
|
try {
|
|
77
79
|
const success = await this.sunspecClient.reconnect();
|
|
78
80
|
if (success) {
|
|
@@ -83,7 +85,8 @@ export class BaseSunspecDevice {
|
|
|
83
85
|
if (this.applianceId) {
|
|
84
86
|
await this.applianceManager.updateApplianceState(this.applianceId, EnyoApplianceConnectionType.Connector, EnyoApplianceStateEnum.Connected);
|
|
85
87
|
}
|
|
86
|
-
|
|
88
|
+
const postStats = this.sunspecClient.getConnectionStats();
|
|
89
|
+
console.log(`${this.constructor.name} ${this.applianceId}: Reconnection successful after ${attempt} attempt(s) (opens=${postStats.opens}, closes=${postStats.closes}, current=${postStats.currentlyOpen})`);
|
|
87
90
|
return true;
|
|
88
91
|
}
|
|
89
92
|
}
|
|
@@ -93,10 +96,22 @@ export class BaseSunspecDevice {
|
|
|
93
96
|
return false;
|
|
94
97
|
}
|
|
95
98
|
/**
|
|
96
|
-
* Mark the device as offline:
|
|
99
|
+
* Mark the device as offline: close the underlying socket so the next readData()
|
|
100
|
+
* cycle sees isConnected() === false and tryReconnect() can establish a fresh
|
|
101
|
+
* connection after the backoff interval. Then update appliance state.
|
|
97
102
|
*/
|
|
98
103
|
async markOffline() {
|
|
99
104
|
this.retryManager.markDisconnected();
|
|
105
|
+
try {
|
|
106
|
+
if (this.sunspecClient.isConnected()) {
|
|
107
|
+
await this.sunspecClient.disconnect();
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
console.error(`${this.constructor.name} ${this.applianceId}: error closing socket on markOffline: ${error}`);
|
|
112
|
+
}
|
|
113
|
+
const stats = this.sunspecClient.getConnectionStats();
|
|
114
|
+
console.log(`${this.constructor.name} ${this.applianceId}: marked offline (opens=${stats.opens}, closes=${stats.closes}, current=${stats.currentlyOpen})`);
|
|
100
115
|
if (this.applianceId) {
|
|
101
116
|
try {
|
|
102
117
|
await this.applianceManager.updateApplianceState(this.applianceId, EnyoApplianceConnectionType.Connector, EnyoApplianceStateEnum.Offline);
|
|
@@ -244,6 +259,7 @@ export class SunspecInverter extends BaseSunspecDevice {
|
|
|
244
259
|
voltageL1: inverterData.voltageAN || 0,
|
|
245
260
|
voltageL2: inverterData.voltageBN ?? undefined,
|
|
246
261
|
voltageL3: inverterData.voltageCN ?? undefined,
|
|
262
|
+
meterValueWh: inverterData.acEnergy,
|
|
247
263
|
strings: dcStrings
|
|
248
264
|
}
|
|
249
265
|
};
|
|
@@ -301,6 +317,7 @@ export class SunspecInverter extends BaseSunspecDevice {
|
|
|
301
317
|
current: mppt.dcCurrent,
|
|
302
318
|
voltage: mppt.dcVoltage,
|
|
303
319
|
powerW: mppt.dcPower,
|
|
320
|
+
meterValueWh: mppt.dcEnergy,
|
|
304
321
|
});
|
|
305
322
|
}
|
|
306
323
|
});
|
|
@@ -29,6 +29,9 @@ export declare class SunspecModbusClient {
|
|
|
29
29
|
private modbusDataTypeConverter;
|
|
30
30
|
private connectionParams;
|
|
31
31
|
private autoReconnectEnabled;
|
|
32
|
+
private openCount;
|
|
33
|
+
private closeCount;
|
|
34
|
+
private currentlyOpen;
|
|
32
35
|
constructor(energyApp: EnergyApp);
|
|
33
36
|
/**
|
|
34
37
|
* Connect to Modbus device
|
|
@@ -39,7 +42,10 @@ export declare class SunspecModbusClient {
|
|
|
39
42
|
*/
|
|
40
43
|
connect(host: string, port?: number, unitId?: number, secondaryHost?: string): Promise<void>;
|
|
41
44
|
/**
|
|
42
|
-
* Disconnect from Modbus device
|
|
45
|
+
* Disconnect from Modbus device.
|
|
46
|
+
*
|
|
47
|
+
* Note: connection parameters are preserved so reconnect() can be called afterwards.
|
|
48
|
+
* They will be overwritten by the next connect() call anyway.
|
|
43
49
|
*/
|
|
44
50
|
disconnect(): Promise<void>;
|
|
45
51
|
/**
|
|
@@ -53,6 +59,16 @@ export declare class SunspecModbusClient {
|
|
|
53
59
|
* Returns true if successful, false otherwise
|
|
54
60
|
*/
|
|
55
61
|
private attemptConnection;
|
|
62
|
+
private recordOpen;
|
|
63
|
+
private recordClose;
|
|
64
|
+
/**
|
|
65
|
+
* Get cumulative open/close counts for this client. Useful for spotting connection leaks.
|
|
66
|
+
*/
|
|
67
|
+
getConnectionStats(): {
|
|
68
|
+
opens: number;
|
|
69
|
+
closes: number;
|
|
70
|
+
currentlyOpen: number;
|
|
71
|
+
};
|
|
56
72
|
/**
|
|
57
73
|
* Enable or disable automatic reconnection
|
|
58
74
|
*/
|
|
@@ -30,6 +30,9 @@ export class SunspecModbusClient {
|
|
|
30
30
|
modbusDataTypeConverter;
|
|
31
31
|
connectionParams = null;
|
|
32
32
|
autoReconnectEnabled = true;
|
|
33
|
+
openCount = 0;
|
|
34
|
+
closeCount = 0;
|
|
35
|
+
currentlyOpen = 0;
|
|
33
36
|
constructor(energyApp) {
|
|
34
37
|
this.energyApp = energyApp;
|
|
35
38
|
this.connectionHealth = new EnergyAppModbusConnectionHealth();
|
|
@@ -60,10 +63,14 @@ export class SunspecModbusClient {
|
|
|
60
63
|
}
|
|
61
64
|
this.connected = true;
|
|
62
65
|
this.connectionHealth.recordSuccess();
|
|
63
|
-
|
|
66
|
+
this.recordOpen();
|
|
67
|
+
console.log(`Connected to Sunspec device at ${host}:${port} unit ${unitId} (opens=${this.openCount}, closes=${this.closeCount}, current=${this.currentlyOpen})`);
|
|
64
68
|
}
|
|
65
69
|
/**
|
|
66
|
-
* Disconnect from Modbus device
|
|
70
|
+
* Disconnect from Modbus device.
|
|
71
|
+
*
|
|
72
|
+
* Note: connection parameters are preserved so reconnect() can be called afterwards.
|
|
73
|
+
* They will be overwritten by the next connect() call anyway.
|
|
67
74
|
*/
|
|
68
75
|
async disconnect() {
|
|
69
76
|
if (this.modbusClient && this.connected) {
|
|
@@ -72,9 +79,12 @@ export class SunspecModbusClient {
|
|
|
72
79
|
this.faultTolerantReader = null;
|
|
73
80
|
this.connected = false;
|
|
74
81
|
this.discoveredModels.clear();
|
|
82
|
+
this.recordClose();
|
|
83
|
+
const host = this.connectionParams?.primaryHost ?? 'unknown';
|
|
84
|
+
const port = this.connectionParams?.port ?? 0;
|
|
85
|
+
const unitId = this.connectionParams?.unitId ?? 0;
|
|
86
|
+
console.log(`Disconnected from Sunspec device at ${host}:${port} unit ${unitId} (opens=${this.openCount}, closes=${this.closeCount}, current=${this.currentlyOpen})`);
|
|
75
87
|
}
|
|
76
|
-
// Clear stored connection params
|
|
77
|
-
this.connectionParams = null;
|
|
78
88
|
}
|
|
79
89
|
/**
|
|
80
90
|
* Reconnect using stored connection parameters
|
|
@@ -115,6 +125,7 @@ export class SunspecModbusClient {
|
|
|
115
125
|
try {
|
|
116
126
|
// Disconnect existing connection if any
|
|
117
127
|
if (this.modbusClient) {
|
|
128
|
+
const wasConnected = this.connected;
|
|
118
129
|
try {
|
|
119
130
|
await this.modbusClient.disconnect();
|
|
120
131
|
}
|
|
@@ -123,6 +134,10 @@ export class SunspecModbusClient {
|
|
|
123
134
|
}
|
|
124
135
|
this.modbusClient = null;
|
|
125
136
|
this.faultTolerantReader = null;
|
|
137
|
+
if (wasConnected) {
|
|
138
|
+
this.connected = false;
|
|
139
|
+
this.recordClose();
|
|
140
|
+
}
|
|
126
141
|
}
|
|
127
142
|
// Attempt connection
|
|
128
143
|
this.modbusClient = await this.energyApp.useModbus().connect({
|
|
@@ -137,6 +152,8 @@ export class SunspecModbusClient {
|
|
|
137
152
|
}
|
|
138
153
|
this.connected = true;
|
|
139
154
|
this.connectionHealth.recordSuccess();
|
|
155
|
+
this.recordOpen();
|
|
156
|
+
console.log(`Connection attempt to ${host}:${port} unit ${unitId} succeeded (opens=${this.openCount}, closes=${this.closeCount}, current=${this.currentlyOpen})`);
|
|
140
157
|
return true;
|
|
141
158
|
}
|
|
142
159
|
catch (error) {
|
|
@@ -144,6 +161,25 @@ export class SunspecModbusClient {
|
|
|
144
161
|
return false;
|
|
145
162
|
}
|
|
146
163
|
}
|
|
164
|
+
recordOpen() {
|
|
165
|
+
this.openCount++;
|
|
166
|
+
this.currentlyOpen++;
|
|
167
|
+
}
|
|
168
|
+
recordClose() {
|
|
169
|
+
this.closeCount++;
|
|
170
|
+
if (this.currentlyOpen > 0) {
|
|
171
|
+
this.currentlyOpen--;
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
console.warn(`SunspecModbusClient: closeCount incremented while currentlyOpen was 0 — possible double-close`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Get cumulative open/close counts for this client. Useful for spotting connection leaks.
|
|
179
|
+
*/
|
|
180
|
+
getConnectionStats() {
|
|
181
|
+
return { opens: this.openCount, closes: this.closeCount, currentlyOpen: this.currentlyOpen };
|
|
182
|
+
}
|
|
147
183
|
/**
|
|
148
184
|
* Enable or disable automatic reconnection
|
|
149
185
|
*/
|
|
@@ -582,6 +618,7 @@ export class SunspecModbusClient {
|
|
|
582
618
|
V_SF: this.extractValue(buffer, 13, 'int16'),
|
|
583
619
|
W_SF: this.extractValue(buffer, 10, 'int16'),
|
|
584
620
|
Hz_SF: this.extractValue(buffer, 12, 'int16'),
|
|
621
|
+
WH_SF: this.extractValue(buffer, 26, 'int16'),
|
|
585
622
|
DCA_SF: this.extractValue(buffer, 18, 'int16'),
|
|
586
623
|
DCV_SF: this.extractValue(buffer, 19, 'int16'),
|
|
587
624
|
DCW_SF: this.extractValue(buffer, 21, 'int16')
|
|
@@ -590,6 +627,7 @@ export class SunspecModbusClient {
|
|
|
590
627
|
this.logRegisterRead(101, 13, 'V_SF', scaleFactors.V_SF, 'int16');
|
|
591
628
|
this.logRegisterRead(101, 10, 'W_SF', scaleFactors.W_SF, 'int16');
|
|
592
629
|
this.logRegisterRead(101, 12, 'Hz_SF', scaleFactors.Hz_SF, 'int16');
|
|
630
|
+
this.logRegisterRead(101, 26, 'WH_SF', scaleFactors.WH_SF, 'int16');
|
|
593
631
|
this.logRegisterRead(101, 18, 'DCA_SF', scaleFactors.DCA_SF, 'int16');
|
|
594
632
|
this.logRegisterRead(101, 19, 'DCV_SF', scaleFactors.DCV_SF, 'int16');
|
|
595
633
|
this.logRegisterRead(101, 21, 'DCW_SF', scaleFactors.DCW_SF, 'int16');
|
|
@@ -614,6 +652,12 @@ export class SunspecModbusClient {
|
|
|
614
652
|
dcPower: this.applyScaleFactor(dcPowerRaw, scaleFactors.DCW_SF, 'int16', 'DC Power', 20, 101),
|
|
615
653
|
operatingState: stateRaw
|
|
616
654
|
};
|
|
655
|
+
// Read AC Energy (32-bit accumulator) - Offset 24-25
|
|
656
|
+
const acEnergy = this.extractValue(buffer, 24, 'uint32', 2);
|
|
657
|
+
this.logRegisterRead(101, 24, 'AC Energy', acEnergy, 'acc32');
|
|
658
|
+
data.acEnergy = !this.isUnimplementedValue(acEnergy, 'acc32')
|
|
659
|
+
? acEnergy * Math.pow(10, scaleFactors.WH_SF)
|
|
660
|
+
: undefined;
|
|
617
661
|
console.debug('[Model 101] Single Phase Inverter Data:', data);
|
|
618
662
|
return data;
|
|
619
663
|
}
|
package/dist/version.d.ts
CHANGED
package/dist/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@enyo-energy/sunspec-sdk",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.48",
|
|
4
4
|
"description": "enyo Energy Sunspec SDK",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"typescript": "^5.8.3"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@enyo-energy/energy-app-sdk": "^0.0.
|
|
40
|
+
"@enyo-energy/energy-app-sdk": "^0.0.113"
|
|
41
41
|
},
|
|
42
42
|
"volta": {
|
|
43
43
|
"node": "22.17.0"
|