@enyo-energy/sunspec-sdk 0.0.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.
- package/README.md +1 -0
- package/dist/cjs/sunspec-devices.cjs +334 -0
- package/dist/cjs/sunspec-devices.d.cts +87 -0
- package/dist/cjs/sunspec-interfaces.cjs +52 -0
- package/dist/cjs/sunspec-interfaces.d.cts +240 -0
- package/dist/cjs/sunspec-modbus-client.cjs +715 -0
- package/dist/cjs/sunspec-modbus-client.d.cts +111 -0
- package/dist/cjs/version.cjs +19 -0
- package/dist/cjs/version.d.cts +13 -0
- package/dist/sunspec-devices.d.ts +87 -0
- package/dist/sunspec-devices.js +327 -0
- package/dist/sunspec-interfaces.d.ts +240 -0
- package/dist/sunspec-interfaces.js +49 -0
- package/dist/sunspec-modbus-client.d.ts +111 -0
- package/dist/sunspec-modbus-client.js +711 -0
- package/dist/version.d.ts +13 -0
- package/dist/version.js +15 -0
- package/package.json +45 -0
|
@@ -0,0 +1,711 @@
|
|
|
1
|
+
import { SunspecModelId } from "./sunspec-interfaces.js";
|
|
2
|
+
import { EnergyAppModbusConnectionHealth } from "@enyo-energy/energy-app-sdk/dist/implementations/modbus/EnergyAppModbusConnectionHealth.js";
|
|
3
|
+
import { EnergyAppModbusFaultTolerantReader } from "@enyo-energy/energy-app-sdk/dist/implementations/modbus/EnergyAppModbusFaultTolerantReader.js";
|
|
4
|
+
export class SunspecModbusClient {
|
|
5
|
+
energyApp;
|
|
6
|
+
modbusClient = null;
|
|
7
|
+
discoveredModels = new Map();
|
|
8
|
+
scaleFactors = {};
|
|
9
|
+
connected = false;
|
|
10
|
+
baseAddress = 40001;
|
|
11
|
+
connectionHealth;
|
|
12
|
+
faultTolerantReader = null;
|
|
13
|
+
constructor(energyApp) {
|
|
14
|
+
this.energyApp = energyApp;
|
|
15
|
+
this.connectionHealth = new EnergyAppModbusConnectionHealth();
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Connect to Modbus device
|
|
19
|
+
*/
|
|
20
|
+
async connect(host, port = 502, unitId = 1) {
|
|
21
|
+
if (this.connected) {
|
|
22
|
+
await this.disconnect();
|
|
23
|
+
}
|
|
24
|
+
this.modbusClient = await this.energyApp.useModbus().connect({
|
|
25
|
+
host,
|
|
26
|
+
port,
|
|
27
|
+
unitId,
|
|
28
|
+
timeout: 5000
|
|
29
|
+
});
|
|
30
|
+
// Create fault-tolerant reader with connection health monitoring
|
|
31
|
+
if (this.modbusClient) {
|
|
32
|
+
this.faultTolerantReader = new EnergyAppModbusFaultTolerantReader(this.modbusClient, this.connectionHealth);
|
|
33
|
+
}
|
|
34
|
+
this.connected = true;
|
|
35
|
+
this.connectionHealth.recordSuccess();
|
|
36
|
+
console.log(`Connected to Sunspec device at ${host}:${port} unit ${unitId}`);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Disconnect from Modbus device
|
|
40
|
+
*/
|
|
41
|
+
async disconnect() {
|
|
42
|
+
if (this.modbusClient && this.connected) {
|
|
43
|
+
await this.modbusClient.disconnect();
|
|
44
|
+
this.modbusClient = null;
|
|
45
|
+
this.faultTolerantReader = null;
|
|
46
|
+
this.connected = false;
|
|
47
|
+
this.discoveredModels.clear();
|
|
48
|
+
this.scaleFactors = {};
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Discover all available Sunspec models
|
|
53
|
+
* Scans through the address space starting at 40001
|
|
54
|
+
*/
|
|
55
|
+
async discoverModels() {
|
|
56
|
+
if (!this.connected) {
|
|
57
|
+
throw new Error('Not connected to Modbus device');
|
|
58
|
+
}
|
|
59
|
+
this.discoveredModels.clear();
|
|
60
|
+
let currentAddress = this.baseAddress;
|
|
61
|
+
const maxAddress = 50000; // Safety limit
|
|
62
|
+
console.log('Starting Sunspec model discovery...');
|
|
63
|
+
try {
|
|
64
|
+
// First, check for Sunspec identifier "SunS" at 40001
|
|
65
|
+
if (!this.modbusClient) {
|
|
66
|
+
throw new Error('Modbus client not initialized');
|
|
67
|
+
}
|
|
68
|
+
const sunspecId = await this.modbusClient.readRegisterStringValue(40001, 2);
|
|
69
|
+
if (!sunspecId.includes('SunS')) {
|
|
70
|
+
console.warn('Device may not be Sunspec compliant - missing SunS identifier');
|
|
71
|
+
}
|
|
72
|
+
// Start scanning after the SunS identifier
|
|
73
|
+
currentAddress = 40003;
|
|
74
|
+
while (currentAddress < maxAddress) {
|
|
75
|
+
// Read model ID and length
|
|
76
|
+
if (!this.modbusClient) {
|
|
77
|
+
throw new Error('Modbus client not initialized');
|
|
78
|
+
}
|
|
79
|
+
const buffer = await this.modbusClient.readHoldingRegisters(currentAddress, 2);
|
|
80
|
+
const modelData = [buffer.readUInt16BE(0), buffer.readUInt16BE(2)];
|
|
81
|
+
if (!modelData || modelData.length < 2) {
|
|
82
|
+
console.log(`No data at address ${currentAddress}, ending discovery`);
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
const modelId = modelData[0];
|
|
86
|
+
const modelLength = modelData[1];
|
|
87
|
+
// Check for end marker
|
|
88
|
+
if (modelId === 0xFFFF || modelId === 65535) {
|
|
89
|
+
console.log(`Found end marker at address ${currentAddress}`);
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
// Store discovered model
|
|
93
|
+
const model = {
|
|
94
|
+
id: modelId,
|
|
95
|
+
address: currentAddress,
|
|
96
|
+
length: modelLength
|
|
97
|
+
};
|
|
98
|
+
this.discoveredModels.set(modelId, model);
|
|
99
|
+
console.log(`Discovered Model ${modelId} at address ${currentAddress} with length ${modelLength}`);
|
|
100
|
+
// Jump to next model: current address + 2 (header) + model length
|
|
101
|
+
currentAddress = currentAddress + 2 + modelLength;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
console.error(`Error during model discovery at address ${currentAddress}: ${error}`);
|
|
106
|
+
}
|
|
107
|
+
console.log(`Discovery complete. Found ${this.discoveredModels.size} models`);
|
|
108
|
+
return this.discoveredModels;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Find a specific model by ID
|
|
112
|
+
*/
|
|
113
|
+
findModel(modelId) {
|
|
114
|
+
return this.discoveredModels.get(modelId);
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Read a register value and apply scale factor
|
|
118
|
+
*/
|
|
119
|
+
async readRegisterWithScaleFactor(valueAddress, scaleFactorAddress, quantity = 1) {
|
|
120
|
+
if (!this.connected) {
|
|
121
|
+
throw new Error('Not connected to Modbus device');
|
|
122
|
+
}
|
|
123
|
+
// Read the raw value
|
|
124
|
+
if (!this.modbusClient) {
|
|
125
|
+
throw new Error('Modbus client not initialized');
|
|
126
|
+
}
|
|
127
|
+
const buffer = await this.modbusClient.readHoldingRegisters(valueAddress, quantity);
|
|
128
|
+
let value = buffer.readUInt16BE(0);
|
|
129
|
+
// Apply scale factor if provided
|
|
130
|
+
if (scaleFactorAddress) {
|
|
131
|
+
let scaleFactor = this.scaleFactors[`sf_${scaleFactorAddress}`];
|
|
132
|
+
if (scaleFactor === undefined) {
|
|
133
|
+
if (!this.modbusClient) {
|
|
134
|
+
throw new Error('Modbus client not initialized');
|
|
135
|
+
}
|
|
136
|
+
const sfBuffer = await this.modbusClient.readHoldingRegisters(scaleFactorAddress, 1);
|
|
137
|
+
// Scale factors are signed int16
|
|
138
|
+
scaleFactor = this.convertToSigned16(sfBuffer.readUInt16BE(0));
|
|
139
|
+
this.scaleFactors[`sf_${scaleFactorAddress}`] = scaleFactor;
|
|
140
|
+
}
|
|
141
|
+
// Apply scale factor: value * 10^scaleFactor
|
|
142
|
+
value = value * Math.pow(10, scaleFactor);
|
|
143
|
+
}
|
|
144
|
+
return value;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Convert unsigned 16-bit value to signed
|
|
148
|
+
*/
|
|
149
|
+
convertToSigned16(value) {
|
|
150
|
+
if (value > 32767) {
|
|
151
|
+
return value - 65536;
|
|
152
|
+
}
|
|
153
|
+
return value;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Helper to clean string values by removing null characters
|
|
157
|
+
*/
|
|
158
|
+
cleanString(value) {
|
|
159
|
+
return value.replace(/\u0000/g, '').trim();
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Helper to read register value(s) using the fault-tolerant reader with data type conversion
|
|
163
|
+
*/
|
|
164
|
+
async readRegisterValue(address, quantity = 1, dataType = 'uint16') {
|
|
165
|
+
if (!this.faultTolerantReader) {
|
|
166
|
+
throw new Error('Fault-tolerant reader not initialized');
|
|
167
|
+
}
|
|
168
|
+
try {
|
|
169
|
+
const result = await this.faultTolerantReader.readHoldingRegisters(address, quantity);
|
|
170
|
+
// Check if the read was successful
|
|
171
|
+
if (!result.success || !result.value) {
|
|
172
|
+
throw new Error(`Failed to read register at address ${address}: ${result.error?.message || 'Unknown error'}`);
|
|
173
|
+
}
|
|
174
|
+
const buffer = result.value;
|
|
175
|
+
this.connectionHealth.recordSuccess();
|
|
176
|
+
switch (dataType) {
|
|
177
|
+
case 'string':
|
|
178
|
+
// Convert buffer to string and clean null characters
|
|
179
|
+
let str = '';
|
|
180
|
+
for (let i = 0; i < buffer.length; i += 2) {
|
|
181
|
+
const char1 = buffer[i];
|
|
182
|
+
const char2 = buffer[i + 1];
|
|
183
|
+
if (char1 !== 0)
|
|
184
|
+
str += String.fromCharCode(char1);
|
|
185
|
+
if (char2 !== 0)
|
|
186
|
+
str += String.fromCharCode(char2);
|
|
187
|
+
}
|
|
188
|
+
return this.cleanString(str);
|
|
189
|
+
case 'int16':
|
|
190
|
+
return this.convertToSigned16(buffer.readUInt16BE(0));
|
|
191
|
+
case 'uint32':
|
|
192
|
+
case 'acc32':
|
|
193
|
+
// 32-bit values use 2 registers
|
|
194
|
+
return (buffer.readUInt16BE(0) << 16) | buffer.readUInt16BE(2);
|
|
195
|
+
case 'int32':
|
|
196
|
+
const val = (buffer.readUInt16BE(0) << 16) | buffer.readUInt16BE(2);
|
|
197
|
+
return val > 0x7FFFFFFF ? val - 0x100000000 : val;
|
|
198
|
+
case 'uint16':
|
|
199
|
+
default:
|
|
200
|
+
if (quantity === 1) {
|
|
201
|
+
return buffer.readUInt16BE(0);
|
|
202
|
+
}
|
|
203
|
+
const values = [];
|
|
204
|
+
for (let i = 0; i < quantity; i++) {
|
|
205
|
+
values.push(buffer.readUInt16BE(i * 2));
|
|
206
|
+
}
|
|
207
|
+
return values;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
catch (error) {
|
|
211
|
+
this.connectionHealth.recordFailure(error);
|
|
212
|
+
throw error;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Read inverter data from Model 103 (Three Phase)
|
|
217
|
+
*/
|
|
218
|
+
async readInverterData() {
|
|
219
|
+
const model = this.findModel(SunspecModelId.Inverter3Phase);
|
|
220
|
+
if (!model) {
|
|
221
|
+
console.log('Inverter model 103 not found, trying single phase model 101');
|
|
222
|
+
const singlePhaseModel = this.findModel(SunspecModelId.InverterSinglePhase);
|
|
223
|
+
if (!singlePhaseModel) {
|
|
224
|
+
console.error('No inverter model found');
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
return this.readSinglePhaseInverterData(singlePhaseModel);
|
|
228
|
+
}
|
|
229
|
+
const baseAddr = model.address + 2; // Skip ID and Length
|
|
230
|
+
try {
|
|
231
|
+
// Read all scale factors first using fault-tolerant reader
|
|
232
|
+
const scaleFactors = await this.readInverterScaleFactors(baseAddr);
|
|
233
|
+
// Read values using fault-tolerant reader with proper data types
|
|
234
|
+
const data = {
|
|
235
|
+
blockNumber: 103,
|
|
236
|
+
blockAddress: model.address,
|
|
237
|
+
blockLength: model.length,
|
|
238
|
+
// AC Current values - Offsets 2-5
|
|
239
|
+
acCurrent: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 2, 1, 'uint16'), scaleFactors.A_SF),
|
|
240
|
+
phaseACurrent: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 3, 1, 'uint16'), scaleFactors.A_SF),
|
|
241
|
+
phaseBCurrent: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 4, 1, 'uint16'), scaleFactors.A_SF),
|
|
242
|
+
phaseCCurrent: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 5, 1, 'uint16'), scaleFactors.A_SF),
|
|
243
|
+
// Voltage values - Offsets 7-12
|
|
244
|
+
voltageAB: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 7, 1, 'uint16'), scaleFactors.V_SF),
|
|
245
|
+
voltageBC: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 8, 1, 'uint16'), scaleFactors.V_SF),
|
|
246
|
+
voltageCA: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 9, 1, 'uint16'), scaleFactors.V_SF),
|
|
247
|
+
voltageAN: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 10, 1, 'uint16'), scaleFactors.V_SF),
|
|
248
|
+
voltageBN: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 11, 1, 'uint16'), scaleFactors.V_SF),
|
|
249
|
+
voltageCN: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 12, 1, 'uint16'), scaleFactors.V_SF),
|
|
250
|
+
// Power values - Offsets 14, 18, 20, 22
|
|
251
|
+
acPower: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 14, 1, 'int16'), scaleFactors.W_SF),
|
|
252
|
+
apparentPower: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 18, 1, 'uint16'), scaleFactors.VA_SF),
|
|
253
|
+
reactivePower: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 20, 1, 'int16'), scaleFactors.VAr_SF),
|
|
254
|
+
powerFactor: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 22, 1, 'int16'), scaleFactors.PF_SF),
|
|
255
|
+
// Frequency - Offset 16
|
|
256
|
+
frequency: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 16, 1, 'uint16'), scaleFactors.Hz_SF),
|
|
257
|
+
// DC values - Offsets 27, 28, 30
|
|
258
|
+
dcCurrent: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 27, 1, 'uint16'), scaleFactors.DCA_SF),
|
|
259
|
+
dcVoltage: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 28, 1, 'uint16'), scaleFactors.DCV_SF),
|
|
260
|
+
dcPower: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 30, 1, 'int16'), scaleFactors.DCW_SF),
|
|
261
|
+
// Temperature values - Offsets 32, 34, 35, 36
|
|
262
|
+
cabinetTemperature: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 32, 1, 'int16'), scaleFactors.Tmp_SF),
|
|
263
|
+
heatSinkTemperature: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 34, 1, 'int16'), scaleFactors.Tmp_SF),
|
|
264
|
+
transformerTemperature: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 35, 1, 'int16'), scaleFactors.Tmp_SF),
|
|
265
|
+
otherTemperature: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 36, 1, 'int16'), scaleFactors.Tmp_SF),
|
|
266
|
+
// Status values - Offsets 38, 39
|
|
267
|
+
operatingState: await this.readRegisterValue(baseAddr + 38, 1, 'uint16'),
|
|
268
|
+
vendorState: await this.readRegisterValue(baseAddr + 39, 1, 'uint16'),
|
|
269
|
+
// Event bitfields - Offsets 40-51
|
|
270
|
+
events: await this.readRegisterValue(baseAddr + 40, 2, 'uint32'),
|
|
271
|
+
events2: await this.readRegisterValue(baseAddr + 42, 2, 'uint32'),
|
|
272
|
+
vendorEvents1: await this.readRegisterValue(baseAddr + 44, 2, 'uint32'),
|
|
273
|
+
vendorEvents2: await this.readRegisterValue(baseAddr + 46, 2, 'uint32'),
|
|
274
|
+
vendorEvents3: await this.readRegisterValue(baseAddr + 48, 2, 'uint32'),
|
|
275
|
+
vendorEvents4: await this.readRegisterValue(baseAddr + 50, 2, 'uint32')
|
|
276
|
+
};
|
|
277
|
+
// Read AC Energy (32-bit accumulator) - Offset 24-25
|
|
278
|
+
const acEnergy = await this.readRegisterValue(baseAddr + 24, 2, 'acc32');
|
|
279
|
+
data.acEnergy = BigInt(acEnergy) * BigInt(Math.pow(10, scaleFactors.WH_SF));
|
|
280
|
+
return data;
|
|
281
|
+
}
|
|
282
|
+
catch (error) {
|
|
283
|
+
console.error(`Error reading inverter data: ${error}`);
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Read single phase inverter data (Model 101)
|
|
289
|
+
*/
|
|
290
|
+
async readSinglePhaseInverterData(model) {
|
|
291
|
+
// Similar to 3-phase but with fewer phase-specific values
|
|
292
|
+
// Implementation would be similar but simplified
|
|
293
|
+
const baseAddr = model.address + 2;
|
|
294
|
+
try {
|
|
295
|
+
// Simplified implementation for single phase
|
|
296
|
+
return {
|
|
297
|
+
blockNumber: 101,
|
|
298
|
+
voltageAN: await this.readRegisterWithScaleFactor(baseAddr + 7, baseAddr + 13), // PhVphA with V_SF
|
|
299
|
+
acCurrent: await this.readRegisterWithScaleFactor(baseAddr + 2, baseAddr + 6),
|
|
300
|
+
acPower: await this.readRegisterWithScaleFactor(baseAddr + 9, baseAddr + 10),
|
|
301
|
+
frequency: await this.readRegisterWithScaleFactor(baseAddr + 11, baseAddr + 12),
|
|
302
|
+
dcPower: await this.readRegisterWithScaleFactor(baseAddr + 20, baseAddr + 21),
|
|
303
|
+
operatingState: await this.readRegisterValue(baseAddr + 24, 1)
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
catch (error) {
|
|
307
|
+
console.error(`Error reading single phase inverter data: ${error}`);
|
|
308
|
+
return null;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Read inverter scale factors
|
|
313
|
+
*/
|
|
314
|
+
async readInverterScaleFactors(baseAddr) {
|
|
315
|
+
return {
|
|
316
|
+
A_SF: await this.readRegisterValue(baseAddr + 6, 1, 'int16'), // Offset 6
|
|
317
|
+
V_SF: await this.readRegisterValue(baseAddr + 13, 1, 'int16'), // Offset 13
|
|
318
|
+
W_SF: await this.readRegisterValue(baseAddr + 15, 1, 'int16'), // Offset 15
|
|
319
|
+
Hz_SF: await this.readRegisterValue(baseAddr + 17, 1, 'int16'), // Offset 17
|
|
320
|
+
VA_SF: await this.readRegisterValue(baseAddr + 19, 1, 'int16'), // Offset 19
|
|
321
|
+
VAr_SF: await this.readRegisterValue(baseAddr + 21, 1, 'int16'), // Offset 21
|
|
322
|
+
PF_SF: await this.readRegisterValue(baseAddr + 23, 1, 'int16'), // Offset 23
|
|
323
|
+
WH_SF: await this.readRegisterValue(baseAddr + 26, 1, 'int16'), // Offset 26
|
|
324
|
+
DCA_SF: await this.readRegisterValue(baseAddr + 28, 1, 'int16'), // Offset 28
|
|
325
|
+
DCV_SF: await this.readRegisterValue(baseAddr + 29, 1, 'int16'), // Offset 29
|
|
326
|
+
DCW_SF: await this.readRegisterValue(baseAddr + 31, 1, 'int16'), // Offset 31
|
|
327
|
+
Tmp_SF: await this.readRegisterValue(baseAddr + 36, 1, 'int16') // Offset 36
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Apply scale factor to a value
|
|
332
|
+
*/
|
|
333
|
+
applyScaleFactor(value, scaleFactor) {
|
|
334
|
+
return value * Math.pow(10, scaleFactor);
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Read MPPT data from Model 160
|
|
338
|
+
*/
|
|
339
|
+
async readMPPTData(moduleId = 1) {
|
|
340
|
+
const model = this.findModel(SunspecModelId.MPPT);
|
|
341
|
+
if (!model) {
|
|
342
|
+
console.log('MPPT model 160 not found');
|
|
343
|
+
return null;
|
|
344
|
+
}
|
|
345
|
+
const baseAddr = model.address + 2; // Skip ID and Length
|
|
346
|
+
try {
|
|
347
|
+
// MPPT modules are repeating blocks, calculate offset for specific module
|
|
348
|
+
const moduleSize = 26; // Size of each MPPT module based on CSV (offsets 0-25)
|
|
349
|
+
const offset = (moduleId - 1) * moduleSize;
|
|
350
|
+
const moduleAddr = baseAddr + offset;
|
|
351
|
+
// Read scale factors first (offsets 10, 12, 14, 17, 21)
|
|
352
|
+
const scaleFactors = {
|
|
353
|
+
DCA_SF: await this.readRegisterValue(moduleAddr + 10, 1, 'int16'),
|
|
354
|
+
DCV_SF: await this.readRegisterValue(moduleAddr + 12, 1, 'int16'),
|
|
355
|
+
DCW_SF: await this.readRegisterValue(moduleAddr + 14, 1, 'int16'),
|
|
356
|
+
DCWH_SF: await this.readRegisterValue(moduleAddr + 17, 1, 'int16'),
|
|
357
|
+
Tmp_SF: await this.readRegisterValue(moduleAddr + 21, 1, 'int16')
|
|
358
|
+
};
|
|
359
|
+
const data = {
|
|
360
|
+
blockNumber: 160,
|
|
361
|
+
blockAddress: model.address,
|
|
362
|
+
blockLength: model.length,
|
|
363
|
+
id: moduleId,
|
|
364
|
+
// String ID - Offset 1 (8 registers for string)
|
|
365
|
+
stringId: await this.readRegisterValue(moduleAddr + 1, 8, 'string'),
|
|
366
|
+
// DC Current - Offset 9
|
|
367
|
+
dcCurrent: this.applyScaleFactor(await this.readRegisterValue(moduleAddr + 9, 1, 'uint16'), scaleFactors.DCA_SF),
|
|
368
|
+
dcCurrentSF: scaleFactors.DCA_SF,
|
|
369
|
+
// DC Voltage - Offset 11
|
|
370
|
+
dcVoltage: this.applyScaleFactor(await this.readRegisterValue(moduleAddr + 11, 1, 'uint16'), scaleFactors.DCV_SF),
|
|
371
|
+
dcVoltageSF: scaleFactors.DCV_SF,
|
|
372
|
+
// DC Power - Offset 13
|
|
373
|
+
dcPower: this.applyScaleFactor(await this.readRegisterValue(moduleAddr + 13, 1, 'uint16'), scaleFactors.DCW_SF),
|
|
374
|
+
dcPowerSF: scaleFactors.DCW_SF,
|
|
375
|
+
// DC Energy - Offset 15-16 (32-bit accumulator)
|
|
376
|
+
dcEnergy: BigInt(await this.readRegisterValue(moduleAddr + 15, 2, 'acc32')) *
|
|
377
|
+
BigInt(Math.pow(10, scaleFactors.DCWH_SF)),
|
|
378
|
+
dcEnergySF: scaleFactors.DCWH_SF,
|
|
379
|
+
// Timestamp - Offset 18-19 (32-bit)
|
|
380
|
+
timestamp: await this.readRegisterValue(moduleAddr + 18, 2, 'uint32'),
|
|
381
|
+
// Temperature - Offset 20
|
|
382
|
+
temperature: this.applyScaleFactor(await this.readRegisterValue(moduleAddr + 20, 1, 'int16'), scaleFactors.Tmp_SF),
|
|
383
|
+
temperatureSF: scaleFactors.Tmp_SF,
|
|
384
|
+
// Operating State - Offset 22
|
|
385
|
+
operatingState: await this.readRegisterValue(moduleAddr + 22, 1, 'uint16'),
|
|
386
|
+
// Vendor State - Offset 23
|
|
387
|
+
vendorState: await this.readRegisterValue(moduleAddr + 23, 1, 'uint16'),
|
|
388
|
+
// Events - Offset 24-25 (32-bit bitfield)
|
|
389
|
+
events: await this.readRegisterValue(moduleAddr + 24, 2, 'uint32')
|
|
390
|
+
};
|
|
391
|
+
return data;
|
|
392
|
+
}
|
|
393
|
+
catch (error) {
|
|
394
|
+
console.error(`Error reading MPPT data for module ${moduleId}: ${error}`);
|
|
395
|
+
return null;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Read all available MPPT strings
|
|
400
|
+
*/
|
|
401
|
+
async readAllMPPTData() {
|
|
402
|
+
const mpptData = [];
|
|
403
|
+
// Try to read up to 4 MPPT strings (typical maximum)
|
|
404
|
+
for (let i = 1; i <= 4; i++) {
|
|
405
|
+
const data = await this.readMPPTData(i);
|
|
406
|
+
if (data && data.dcCurrent !== undefined && data.dcCurrent > 0) {
|
|
407
|
+
mpptData.push(data);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
return mpptData;
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* Read meter data (Model 203 for 3-phase)
|
|
414
|
+
*/
|
|
415
|
+
async readMeterData() {
|
|
416
|
+
let model = this.findModel(SunspecModelId.Meter3Phase);
|
|
417
|
+
if (!model) {
|
|
418
|
+
model = this.findModel(SunspecModelId.MeterWye);
|
|
419
|
+
}
|
|
420
|
+
if (!model) {
|
|
421
|
+
model = this.findModel(SunspecModelId.MeterSinglePhase);
|
|
422
|
+
}
|
|
423
|
+
if (!model) {
|
|
424
|
+
console.log('No meter model found');
|
|
425
|
+
return null;
|
|
426
|
+
}
|
|
427
|
+
const baseAddr = model.address + 2;
|
|
428
|
+
try {
|
|
429
|
+
// This is a simplified implementation
|
|
430
|
+
// Actual register offsets depend on specific meter model
|
|
431
|
+
return {
|
|
432
|
+
blockNumber: model.id,
|
|
433
|
+
totalPower: await this.readRegisterWithScaleFactor(baseAddr + 10, baseAddr + 11),
|
|
434
|
+
frequency: await this.readRegisterWithScaleFactor(baseAddr + 20, baseAddr + 21)
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
catch (error) {
|
|
438
|
+
console.error(`Error reading meter data: ${error}`);
|
|
439
|
+
return null;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* Read common block data (Model 1)
|
|
444
|
+
*/
|
|
445
|
+
async readCommonBlock() {
|
|
446
|
+
const model = this.findModel(SunspecModelId.Common);
|
|
447
|
+
if (!model) {
|
|
448
|
+
console.error('Common block model not found');
|
|
449
|
+
return null;
|
|
450
|
+
}
|
|
451
|
+
const baseAddr = model.address + 2; // Skip ID and Length
|
|
452
|
+
try {
|
|
453
|
+
// Read all strings using fault-tolerant reader with proper data type conversion
|
|
454
|
+
const manufacturer = await this.readRegisterValue(baseAddr + 2, 16, 'string'); // Offset 2-17
|
|
455
|
+
const modelName = await this.readRegisterValue(baseAddr + 18, 16, 'string'); // Offset 18-33
|
|
456
|
+
const options = await this.readRegisterValue(baseAddr + 34, 8, 'string'); // Offset 34-41
|
|
457
|
+
const version = await this.readRegisterValue(baseAddr + 42, 8, 'string'); // Offset 42-49
|
|
458
|
+
const serialNumber = await this.readRegisterValue(baseAddr + 50, 16, 'string'); // Offset 50-65
|
|
459
|
+
const deviceAddress = await this.readRegisterValue(baseAddr + 66, 1, 'uint16'); // Offset 66
|
|
460
|
+
return {
|
|
461
|
+
manufacturer: manufacturer,
|
|
462
|
+
model: modelName,
|
|
463
|
+
options: options,
|
|
464
|
+
version: version,
|
|
465
|
+
serialNumber: serialNumber,
|
|
466
|
+
deviceAddress: deviceAddress
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
catch (error) {
|
|
470
|
+
console.error(`Error reading common block: ${error}`);
|
|
471
|
+
return null;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
475
|
+
* Get serial number from device
|
|
476
|
+
*/
|
|
477
|
+
async getSerialNumber() {
|
|
478
|
+
const commonData = await this.readCommonBlock();
|
|
479
|
+
return commonData?.serialNumber;
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* Check if connected
|
|
483
|
+
*/
|
|
484
|
+
isConnected() {
|
|
485
|
+
return this.connected;
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* Check if connection is healthy
|
|
489
|
+
*/
|
|
490
|
+
isHealthy() {
|
|
491
|
+
return this.connected && this.connectionHealth.isHealthy();
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* Get connection health details
|
|
495
|
+
*/
|
|
496
|
+
getConnectionHealth() {
|
|
497
|
+
return this.connectionHealth;
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* Read Block 121 - Inverter Basic Settings
|
|
501
|
+
*/
|
|
502
|
+
async readInverterSettings() {
|
|
503
|
+
const model = this.findModel(SunspecModelId.Settings);
|
|
504
|
+
if (!model) {
|
|
505
|
+
console.log('Settings model 121 not found');
|
|
506
|
+
return null;
|
|
507
|
+
}
|
|
508
|
+
const baseAddr = model.address + 2; // Skip ID and Length
|
|
509
|
+
try {
|
|
510
|
+
// Read scale factors first (offsets 22-31)
|
|
511
|
+
const scaleFactors = {
|
|
512
|
+
WMax_SF: await this.readRegisterValue(baseAddr + 22, 1, 'int16'),
|
|
513
|
+
VRef_SF: await this.readRegisterValue(baseAddr + 23, 1, 'int16'),
|
|
514
|
+
VRefOfs_SF: await this.readRegisterValue(baseAddr + 24, 1, 'int16'),
|
|
515
|
+
VMinMax_SF: await this.readRegisterValue(baseAddr + 25, 1, 'int16'),
|
|
516
|
+
VAMax_SF: await this.readRegisterValue(baseAddr + 26, 1, 'int16'),
|
|
517
|
+
VArMax_SF: await this.readRegisterValue(baseAddr + 27, 1, 'int16'),
|
|
518
|
+
WGra_SF: await this.readRegisterValue(baseAddr + 28, 1, 'int16'),
|
|
519
|
+
PFMin_SF: await this.readRegisterValue(baseAddr + 29, 1, 'int16'),
|
|
520
|
+
MaxRmpRte_SF: await this.readRegisterValue(baseAddr + 30, 1, 'int16'),
|
|
521
|
+
ECPNomHz_SF: await this.readRegisterValue(baseAddr + 31, 1, 'int16')
|
|
522
|
+
};
|
|
523
|
+
const settings = {
|
|
524
|
+
blockNumber: 121,
|
|
525
|
+
blockAddress: model.address,
|
|
526
|
+
blockLength: model.length,
|
|
527
|
+
// Power settings - Offset 2
|
|
528
|
+
WMax: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 2, 1, 'uint16'), scaleFactors.WMax_SF),
|
|
529
|
+
WMax_SF: scaleFactors.WMax_SF,
|
|
530
|
+
// Voltage settings - Offsets 3-6
|
|
531
|
+
VRef: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 3, 1, 'uint16'), scaleFactors.VRef_SF),
|
|
532
|
+
VRef_SF: scaleFactors.VRef_SF,
|
|
533
|
+
VRefOfs: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 4, 1, 'int16'), scaleFactors.VRefOfs_SF),
|
|
534
|
+
VRefOfs_SF: scaleFactors.VRefOfs_SF,
|
|
535
|
+
VMax: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 5, 1, 'uint16'), scaleFactors.VMinMax_SF),
|
|
536
|
+
VMin: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 6, 1, 'uint16'), scaleFactors.VMinMax_SF),
|
|
537
|
+
VMinMax_SF: scaleFactors.VMinMax_SF,
|
|
538
|
+
// Apparent power settings - Offset 7
|
|
539
|
+
VAMax: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 7, 1, 'uint16'), scaleFactors.VAMax_SF),
|
|
540
|
+
VAMax_SF: scaleFactors.VAMax_SF,
|
|
541
|
+
// Reactive power settings - Offsets 8-11
|
|
542
|
+
VArMaxQ1: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 8, 1, 'int16'), scaleFactors.VArMax_SF),
|
|
543
|
+
VArMaxQ2: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 9, 1, 'int16'), scaleFactors.VArMax_SF),
|
|
544
|
+
VArMaxQ3: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 10, 1, 'int16'), scaleFactors.VArMax_SF),
|
|
545
|
+
VArMaxQ4: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 11, 1, 'int16'), scaleFactors.VArMax_SF),
|
|
546
|
+
VArMax_SF: scaleFactors.VArMax_SF,
|
|
547
|
+
// Ramp rate settings - Offset 12
|
|
548
|
+
WGra: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 12, 1, 'uint16'), scaleFactors.WGra_SF),
|
|
549
|
+
WGra_SF: scaleFactors.WGra_SF,
|
|
550
|
+
// Power factor settings - Offsets 13-16
|
|
551
|
+
PFMinQ1: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 13, 1, 'int16'), scaleFactors.PFMin_SF),
|
|
552
|
+
PFMinQ2: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 14, 1, 'int16'), scaleFactors.PFMin_SF),
|
|
553
|
+
PFMinQ3: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 15, 1, 'int16'), scaleFactors.PFMin_SF),
|
|
554
|
+
PFMinQ4: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 16, 1, 'int16'), scaleFactors.PFMin_SF),
|
|
555
|
+
PFMin_SF: scaleFactors.PFMin_SF,
|
|
556
|
+
// Other settings - Offsets 17-21
|
|
557
|
+
VArAct: await this.readRegisterValue(baseAddr + 17, 1, 'uint16'),
|
|
558
|
+
ClcTotVA: await this.readRegisterValue(baseAddr + 18, 1, 'uint16'),
|
|
559
|
+
MaxRmpRte: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 19, 1, 'uint16'), scaleFactors.MaxRmpRte_SF),
|
|
560
|
+
MaxRmpRte_SF: scaleFactors.MaxRmpRte_SF,
|
|
561
|
+
ECPNomHz: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 20, 1, 'uint16'), scaleFactors.ECPNomHz_SF),
|
|
562
|
+
ECPNomHz_SF: scaleFactors.ECPNomHz_SF,
|
|
563
|
+
ConnPh: await this.readRegisterValue(baseAddr + 21, 1, 'uint16')
|
|
564
|
+
};
|
|
565
|
+
return settings;
|
|
566
|
+
}
|
|
567
|
+
catch (error) {
|
|
568
|
+
console.error(`Error reading inverter settings: ${error}`);
|
|
569
|
+
return null;
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
/**
|
|
573
|
+
* Read Block 123 - Immediate Inverter Controls
|
|
574
|
+
*/
|
|
575
|
+
async readInverterControls() {
|
|
576
|
+
const model = this.findModel(SunspecModelId.Controls);
|
|
577
|
+
if (!model) {
|
|
578
|
+
console.log('Controls model 123 not found');
|
|
579
|
+
return null;
|
|
580
|
+
}
|
|
581
|
+
const baseAddr = model.address + 2; // Skip ID and Length
|
|
582
|
+
try {
|
|
583
|
+
// Read scale factors first (offsets 21-23)
|
|
584
|
+
const scaleFactors = {
|
|
585
|
+
WMaxLimPct_SF: await this.readRegisterValue(baseAddr + 21, 1, 'int16'),
|
|
586
|
+
OutPFSet_SF: await this.readRegisterValue(baseAddr + 22, 1, 'int16'),
|
|
587
|
+
VArPct_SF: await this.readRegisterValue(baseAddr + 23, 1, 'int16')
|
|
588
|
+
};
|
|
589
|
+
const controls = {
|
|
590
|
+
blockNumber: 123,
|
|
591
|
+
blockAddress: model.address,
|
|
592
|
+
blockLength: model.length,
|
|
593
|
+
// Connection control - Offsets 0-2
|
|
594
|
+
Conn_WinTms: await this.readRegisterValue(baseAddr + 0, 1, 'uint16'),
|
|
595
|
+
Conn_RvrtTms: await this.readRegisterValue(baseAddr + 1, 1, 'uint16'),
|
|
596
|
+
Conn: await this.readRegisterValue(baseAddr + 2, 1, 'uint16'),
|
|
597
|
+
// Power limit control - Offsets 3-7
|
|
598
|
+
WMaxLimPct: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 3, 1, 'uint16'), scaleFactors.WMaxLimPct_SF),
|
|
599
|
+
WMaxLimPct_SF: scaleFactors.WMaxLimPct_SF,
|
|
600
|
+
WMaxLimPct_WinTms: await this.readRegisterValue(baseAddr + 4, 1, 'uint16'),
|
|
601
|
+
WMaxLimPct_RvrtTms: await this.readRegisterValue(baseAddr + 5, 1, 'uint16'),
|
|
602
|
+
WMaxLimPct_RmpTms: await this.readRegisterValue(baseAddr + 6, 1, 'uint16'),
|
|
603
|
+
WMaxLim_Ena: await this.readRegisterValue(baseAddr + 7, 1, 'uint16'),
|
|
604
|
+
// Power factor control - Offsets 8-12
|
|
605
|
+
OutPFSet: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 8, 1, 'int16'), scaleFactors.OutPFSet_SF),
|
|
606
|
+
OutPFSet_SF: scaleFactors.OutPFSet_SF,
|
|
607
|
+
OutPFSet_WinTms: await this.readRegisterValue(baseAddr + 9, 1, 'uint16'),
|
|
608
|
+
OutPFSet_RvrtTms: await this.readRegisterValue(baseAddr + 10, 1, 'uint16'),
|
|
609
|
+
OutPFSet_RmpTms: await this.readRegisterValue(baseAddr + 11, 1, 'uint16'),
|
|
610
|
+
OutPFSet_Ena: await this.readRegisterValue(baseAddr + 12, 1, 'uint16'),
|
|
611
|
+
// Reactive power control - Offsets 13-20
|
|
612
|
+
VArWMaxPct: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 13, 1, 'int16'), scaleFactors.VArPct_SF),
|
|
613
|
+
VArMaxPct: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 14, 1, 'int16'), scaleFactors.VArPct_SF),
|
|
614
|
+
VArAvalPct: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 15, 1, 'int16'), scaleFactors.VArPct_SF),
|
|
615
|
+
VArPct_SF: scaleFactors.VArPct_SF,
|
|
616
|
+
VArPct_WinTms: await this.readRegisterValue(baseAddr + 16, 1, 'uint16'),
|
|
617
|
+
VArPct_RvrtTms: await this.readRegisterValue(baseAddr + 17, 1, 'uint16'),
|
|
618
|
+
VArPct_RmpTms: await this.readRegisterValue(baseAddr + 18, 1, 'uint16'),
|
|
619
|
+
VArPct_Mod: await this.readRegisterValue(baseAddr + 19, 1, 'uint16'),
|
|
620
|
+
VArPct_Ena: await this.readRegisterValue(baseAddr + 20, 1, 'uint16')
|
|
621
|
+
};
|
|
622
|
+
return controls;
|
|
623
|
+
}
|
|
624
|
+
catch (error) {
|
|
625
|
+
console.error(`Error reading inverter controls: ${error}`);
|
|
626
|
+
return null;
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
/**
|
|
630
|
+
* Write Block 121 - Inverter Basic Settings
|
|
631
|
+
*/
|
|
632
|
+
async writeInverterSettings(settings) {
|
|
633
|
+
const model = this.findModel(SunspecModelId.Settings);
|
|
634
|
+
if (!model) {
|
|
635
|
+
console.error('Settings model 121 not found');
|
|
636
|
+
return false;
|
|
637
|
+
}
|
|
638
|
+
const baseAddr = model.address + 2;
|
|
639
|
+
try {
|
|
640
|
+
// For each setting, write the value if provided
|
|
641
|
+
// Note: This is a simplified implementation. In production, you'd batch writes
|
|
642
|
+
if (settings.WMax !== undefined && this.modbusClient) {
|
|
643
|
+
// Need to read scale factor first if not provided
|
|
644
|
+
const sfBuffer = await this.modbusClient.readHoldingRegisters(baseAddr + 22, 1);
|
|
645
|
+
const scaleFactor = this.convertToSigned16(sfBuffer.readUInt16BE(0));
|
|
646
|
+
const scaledValue = Math.round(settings.WMax / Math.pow(10, scaleFactor));
|
|
647
|
+
// Writing registers needs to be implemented in EnergyAppModbusInstance
|
|
648
|
+
// For now, log the write operation
|
|
649
|
+
console.log(`Would write value ${scaledValue} to register ${baseAddr}`);
|
|
650
|
+
}
|
|
651
|
+
if (settings.VRef !== undefined && this.modbusClient) {
|
|
652
|
+
const sfBuffer = await this.modbusClient.readHoldingRegisters(baseAddr + 23, 1);
|
|
653
|
+
const scaleFactor = this.convertToSigned16(sfBuffer.readUInt16BE(0));
|
|
654
|
+
const scaledValue = Math.round(settings.VRef / Math.pow(10, scaleFactor));
|
|
655
|
+
console.log(`Would write value ${scaledValue} to register ${baseAddr + 1}`);
|
|
656
|
+
}
|
|
657
|
+
// Add more write operations for other settings as needed
|
|
658
|
+
console.log('Inverter settings written successfully');
|
|
659
|
+
return true;
|
|
660
|
+
}
|
|
661
|
+
catch (error) {
|
|
662
|
+
console.error(`Error writing inverter settings: ${error}`);
|
|
663
|
+
return false;
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
/**
|
|
667
|
+
* Write Block 123 - Immediate Inverter Controls
|
|
668
|
+
*/
|
|
669
|
+
async writeInverterControls(controls) {
|
|
670
|
+
const model = this.findModel(SunspecModelId.Controls);
|
|
671
|
+
if (!model) {
|
|
672
|
+
console.error('Controls model 123 not found');
|
|
673
|
+
return false;
|
|
674
|
+
}
|
|
675
|
+
const baseAddr = model.address + 2;
|
|
676
|
+
try {
|
|
677
|
+
// Connection control
|
|
678
|
+
if (controls.Conn !== undefined && this.modbusClient) {
|
|
679
|
+
// Writing registers needs to be implemented in EnergyAppModbusInstance
|
|
680
|
+
console.log(`Would write connection control ${controls.Conn} to register ${baseAddr + 2}`);
|
|
681
|
+
}
|
|
682
|
+
// Power limit control
|
|
683
|
+
if (controls.WMaxLimPct !== undefined && this.modbusClient) {
|
|
684
|
+
const sfBuffer = await this.modbusClient.readHoldingRegisters(baseAddr + 21, 1);
|
|
685
|
+
const scaleFactor = this.convertToSigned16(sfBuffer.readUInt16BE(0));
|
|
686
|
+
const scaledValue = Math.round(controls.WMaxLimPct / Math.pow(10, scaleFactor));
|
|
687
|
+
console.log(`Would write power limit ${scaledValue} to register ${baseAddr + 3}`);
|
|
688
|
+
}
|
|
689
|
+
if (controls.WMaxLim_Ena !== undefined && this.modbusClient) {
|
|
690
|
+
console.log(`Would write throttle enable ${controls.WMaxLim_Ena} to register ${baseAddr + 7}`);
|
|
691
|
+
}
|
|
692
|
+
// Power factor control
|
|
693
|
+
if (controls.OutPFSet !== undefined && this.modbusClient) {
|
|
694
|
+
const sfBuffer = await this.modbusClient.readHoldingRegisters(baseAddr + 22, 1);
|
|
695
|
+
const scaleFactor = this.convertToSigned16(sfBuffer.readUInt16BE(0));
|
|
696
|
+
const scaledValue = Math.round(controls.OutPFSet / Math.pow(10, scaleFactor));
|
|
697
|
+
console.log(`Would write power factor ${scaledValue} to register ${baseAddr + 8}`);
|
|
698
|
+
}
|
|
699
|
+
if (controls.OutPFSet_Ena !== undefined && this.modbusClient) {
|
|
700
|
+
console.log(`Would write PF enable ${controls.OutPFSet_Ena} to register ${baseAddr + 12}`);
|
|
701
|
+
}
|
|
702
|
+
// Add more control writes as needed
|
|
703
|
+
console.log('Inverter controls written successfully');
|
|
704
|
+
return true;
|
|
705
|
+
}
|
|
706
|
+
catch (error) {
|
|
707
|
+
console.error(`Error writing inverter controls: ${error}`);
|
|
708
|
+
return false;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
}
|