@enyo-energy/energy-app-sdk 0.0.37 → 0.0.39
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/implementations/appliances/appliance-manager.cjs +399 -0
- package/dist/cjs/implementations/appliances/appliance-manager.d.cts +191 -0
- package/dist/cjs/implementations/appliances/identifier-strategies.cjs +180 -0
- package/dist/cjs/implementations/appliances/identifier-strategies.d.cts +140 -0
- package/dist/cjs/implementations/appliances/in-memory-appliance-manager.cjs +281 -0
- package/dist/cjs/implementations/appliances/in-memory-appliance-manager.d.cts +119 -0
- package/dist/cjs/implementations/data-bus/demo-data-bus.cjs +246 -0
- package/dist/cjs/implementations/data-bus/demo-data-bus.d.cts +111 -0
- package/dist/cjs/implementations/modbus/EnergyAppModbusDataTypeConverter.d.cts +2 -2
- package/dist/cjs/implementations/modbus/EnergyAppModbusFaultTolerantReader.cjs +76 -0
- package/dist/cjs/implementations/modbus/EnergyAppModbusFaultTolerantReader.d.cts +31 -1
- package/dist/cjs/implementations/modbus/EnergyAppModbusRegisterMapper.d.cts +2 -2
- package/dist/cjs/implementations/modbus/interfaces.d.cts +7 -93
- package/dist/cjs/implementations/modbus/sunspec/sunspec-devices.cjs +342 -0
- package/dist/cjs/implementations/modbus/sunspec/sunspec-devices.d.cts +95 -0
- package/dist/cjs/implementations/modbus/sunspec/sunspec-modbus-client.cjs +433 -0
- package/dist/cjs/implementations/modbus/sunspec/sunspec-modbus-client.d.cts +171 -0
- package/dist/cjs/index.cjs +2 -3
- package/dist/cjs/index.d.cts +2 -3
- package/dist/cjs/types/enyo-data-bus-value.d.cts +2 -1
- package/dist/cjs/version.cjs +1 -1
- package/dist/cjs/version.d.cts +1 -1
- package/dist/implementations/appliances/appliance-manager.d.ts +191 -0
- package/dist/implementations/appliances/appliance-manager.js +395 -0
- package/dist/implementations/appliances/demo-appliance-manager.d.ts +118 -0
- package/dist/implementations/appliances/demo-appliance-manager.js +277 -0
- package/dist/implementations/appliances/identifier-strategies.d.ts +140 -0
- package/dist/implementations/appliances/identifier-strategies.js +171 -0
- package/dist/implementations/appliances/in-memory-appliance-manager.d.ts +119 -0
- package/dist/implementations/appliances/in-memory-appliance-manager.js +277 -0
- package/dist/implementations/data-bus/demo-data-bus.d.ts +111 -0
- package/dist/implementations/data-bus/demo-data-bus.js +242 -0
- package/dist/implementations/modbus/EnergyAppModbusDataTypeConverter.d.ts +2 -2
- package/dist/implementations/modbus/EnergyAppModbusFaultTolerantReader.d.ts +31 -1
- package/dist/implementations/modbus/EnergyAppModbusFaultTolerantReader.js +76 -0
- package/dist/implementations/modbus/EnergyAppModbusRegisterMapper.d.ts +2 -2
- package/dist/implementations/modbus/interfaces.d.ts +7 -93
- package/dist/implementations/modbus/sunspec/sunspec-devices.d.ts +95 -0
- package/dist/implementations/modbus/sunspec/sunspec-devices.js +335 -0
- package/dist/implementations/modbus/sunspec/sunspec-modbus-client.d.ts +171 -0
- package/dist/implementations/modbus/sunspec/sunspec-modbus-client.js +429 -0
- package/dist/index.d.ts +2 -3
- package/dist/index.js +2 -3
- package/dist/types/enyo-data-bus-value.d.ts +2 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SunspecModbusClient = exports.SunspecModelId = void 0;
|
|
4
|
+
const EnergyAppModbusConnectionHealth_js_1 = require("../EnergyAppModbusConnectionHealth.cjs");
|
|
5
|
+
const EnergyAppModbusFaultTolerantReader_js_1 = require("../EnergyAppModbusFaultTolerantReader.cjs");
|
|
6
|
+
const EnergyAppModbusRegisterMapper_js_1 = require("../EnergyAppModbusRegisterMapper.cjs");
|
|
7
|
+
const EnergyAppModbusDataTypeConverter_js_1 = require("../EnergyAppModbusDataTypeConverter.cjs");
|
|
8
|
+
// Common Sunspec Model IDs
|
|
9
|
+
var SunspecModelId;
|
|
10
|
+
(function (SunspecModelId) {
|
|
11
|
+
SunspecModelId[SunspecModelId["Common"] = 1] = "Common";
|
|
12
|
+
SunspecModelId[SunspecModelId["Inverter3Phase"] = 103] = "Inverter3Phase";
|
|
13
|
+
SunspecModelId[SunspecModelId["InverterSinglePhase"] = 101] = "InverterSinglePhase";
|
|
14
|
+
SunspecModelId[SunspecModelId["MPPT"] = 160] = "MPPT";
|
|
15
|
+
SunspecModelId[SunspecModelId["Battery"] = 124] = "Battery";
|
|
16
|
+
SunspecModelId[SunspecModelId["BatteryBase"] = 802] = "BatteryBase";
|
|
17
|
+
SunspecModelId[SunspecModelId["BatteryControl"] = 803] = "BatteryControl";
|
|
18
|
+
SunspecModelId[SunspecModelId["MeterSinglePhase"] = 201] = "MeterSinglePhase";
|
|
19
|
+
SunspecModelId[SunspecModelId["Meter3Phase"] = 203] = "Meter3Phase";
|
|
20
|
+
SunspecModelId[SunspecModelId["MeterWye"] = 204] = "MeterWye";
|
|
21
|
+
SunspecModelId[SunspecModelId["Nameplate"] = 120] = "Nameplate";
|
|
22
|
+
SunspecModelId[SunspecModelId["Settings"] = 121] = "Settings";
|
|
23
|
+
SunspecModelId[SunspecModelId["Status"] = 122] = "Status";
|
|
24
|
+
SunspecModelId[SunspecModelId["Controls"] = 123] = "Controls";
|
|
25
|
+
SunspecModelId[SunspecModelId["EndMarker"] = 65535] = "EndMarker";
|
|
26
|
+
})(SunspecModelId || (exports.SunspecModelId = SunspecModelId = {}));
|
|
27
|
+
class SunspecModbusClient {
|
|
28
|
+
energyApp;
|
|
29
|
+
modbusClient = null;
|
|
30
|
+
discoveredModels = new Map();
|
|
31
|
+
scaleFactors = {};
|
|
32
|
+
connected = false;
|
|
33
|
+
baseAddress = 40001;
|
|
34
|
+
connectionHealth;
|
|
35
|
+
faultTolerantReader = null;
|
|
36
|
+
registerMapper;
|
|
37
|
+
dataTypeConverter;
|
|
38
|
+
constructor(energyApp) {
|
|
39
|
+
this.energyApp = energyApp;
|
|
40
|
+
this.connectionHealth = new EnergyAppModbusConnectionHealth_js_1.EnergyAppModbusConnectionHealth();
|
|
41
|
+
this.registerMapper = new EnergyAppModbusRegisterMapper_js_1.EnergyAppModbusRegisterMapper();
|
|
42
|
+
this.dataTypeConverter = new EnergyAppModbusDataTypeConverter_js_1.EnergyAppModbusDataTypeConverter();
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Connect to Modbus device
|
|
46
|
+
*/
|
|
47
|
+
async connect(host, port = 502, unitId = 1) {
|
|
48
|
+
if (this.connected) {
|
|
49
|
+
await this.disconnect();
|
|
50
|
+
}
|
|
51
|
+
this.modbusClient = await this.energyApp.useModbus().connect({
|
|
52
|
+
host,
|
|
53
|
+
port,
|
|
54
|
+
unitId,
|
|
55
|
+
timeout: 5000
|
|
56
|
+
});
|
|
57
|
+
// Create fault-tolerant reader with connection health monitoring
|
|
58
|
+
if (this.modbusClient) {
|
|
59
|
+
this.faultTolerantReader = new EnergyAppModbusFaultTolerantReader_js_1.EnergyAppModbusFaultTolerantReader(this.modbusClient, this.connectionHealth);
|
|
60
|
+
}
|
|
61
|
+
this.connected = true;
|
|
62
|
+
this.connectionHealth.recordSuccess();
|
|
63
|
+
console.log(`Connected to Sunspec device at ${host}:${port} unit ${unitId}`);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Disconnect from Modbus device
|
|
67
|
+
*/
|
|
68
|
+
async disconnect() {
|
|
69
|
+
if (this.modbusClient && this.connected) {
|
|
70
|
+
await this.modbusClient.disconnect();
|
|
71
|
+
this.modbusClient = null;
|
|
72
|
+
this.faultTolerantReader = null;
|
|
73
|
+
this.connected = false;
|
|
74
|
+
this.discoveredModels.clear();
|
|
75
|
+
this.scaleFactors = {};
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Discover all available Sunspec models
|
|
80
|
+
* Scans through the address space starting at 40001
|
|
81
|
+
*/
|
|
82
|
+
async discoverModels() {
|
|
83
|
+
if (!this.connected) {
|
|
84
|
+
throw new Error('Not connected to Modbus device');
|
|
85
|
+
}
|
|
86
|
+
this.discoveredModels.clear();
|
|
87
|
+
let currentAddress = this.baseAddress;
|
|
88
|
+
const maxAddress = 50000; // Safety limit
|
|
89
|
+
console.log('Starting Sunspec model discovery...');
|
|
90
|
+
try {
|
|
91
|
+
// First, check for Sunspec identifier "SunS" at 40001
|
|
92
|
+
if (!this.modbusClient) {
|
|
93
|
+
throw new Error('Modbus client not initialized');
|
|
94
|
+
}
|
|
95
|
+
const sunspecId = await this.modbusClient.readRegisterStringValue(40001, 2);
|
|
96
|
+
if (!sunspecId.includes('SunS')) {
|
|
97
|
+
console.warn('Device may not be Sunspec compliant - missing SunS identifier');
|
|
98
|
+
}
|
|
99
|
+
// Start scanning after the SunS identifier
|
|
100
|
+
currentAddress = 40003;
|
|
101
|
+
while (currentAddress < maxAddress) {
|
|
102
|
+
// Read model ID and length
|
|
103
|
+
if (!this.modbusClient) {
|
|
104
|
+
throw new Error('Modbus client not initialized');
|
|
105
|
+
}
|
|
106
|
+
const buffer = await this.modbusClient.readHoldingRegisters(currentAddress, 2);
|
|
107
|
+
const modelData = [buffer.readUInt16BE(0), buffer.readUInt16BE(2)];
|
|
108
|
+
if (!modelData || modelData.length < 2) {
|
|
109
|
+
console.log(`No data at address ${currentAddress}, ending discovery`);
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
const modelId = modelData[0];
|
|
113
|
+
const modelLength = modelData[1];
|
|
114
|
+
// Check for end marker
|
|
115
|
+
if (modelId === 0xFFFF || modelId === 65535) {
|
|
116
|
+
console.log(`Found end marker at address ${currentAddress}`);
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
// Store discovered model
|
|
120
|
+
const model = {
|
|
121
|
+
id: modelId,
|
|
122
|
+
address: currentAddress,
|
|
123
|
+
length: modelLength
|
|
124
|
+
};
|
|
125
|
+
this.discoveredModels.set(modelId, model);
|
|
126
|
+
console.log(`Discovered Model ${modelId} at address ${currentAddress} with length ${modelLength}`);
|
|
127
|
+
// Jump to next model: current address + 2 (header) + model length
|
|
128
|
+
currentAddress = currentAddress + 2 + modelLength;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
console.error(`Error during model discovery at address ${currentAddress}: ${error}`);
|
|
133
|
+
}
|
|
134
|
+
console.log(`Discovery complete. Found ${this.discoveredModels.size} models`);
|
|
135
|
+
return this.discoveredModels;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Find a specific model by ID
|
|
139
|
+
*/
|
|
140
|
+
findModel(modelId) {
|
|
141
|
+
return this.discoveredModels.get(modelId);
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Read a register value and apply scale factor
|
|
145
|
+
*/
|
|
146
|
+
async readRegisterWithScaleFactor(valueAddress, scaleFactorAddress, quantity = 1) {
|
|
147
|
+
if (!this.connected) {
|
|
148
|
+
throw new Error('Not connected to Modbus device');
|
|
149
|
+
}
|
|
150
|
+
// Read the raw value
|
|
151
|
+
if (!this.modbusClient) {
|
|
152
|
+
throw new Error('Modbus client not initialized');
|
|
153
|
+
}
|
|
154
|
+
const buffer = await this.modbusClient.readHoldingRegisters(valueAddress, quantity);
|
|
155
|
+
let value = buffer.readUInt16BE(0);
|
|
156
|
+
// Apply scale factor if provided
|
|
157
|
+
if (scaleFactorAddress) {
|
|
158
|
+
let scaleFactor = this.scaleFactors[`sf_${scaleFactorAddress}`];
|
|
159
|
+
if (scaleFactor === undefined) {
|
|
160
|
+
if (!this.modbusClient) {
|
|
161
|
+
throw new Error('Modbus client not initialized');
|
|
162
|
+
}
|
|
163
|
+
const sfBuffer = await this.modbusClient.readHoldingRegisters(scaleFactorAddress, 1);
|
|
164
|
+
// Scale factors are signed int16
|
|
165
|
+
scaleFactor = this.convertToSigned16(sfBuffer.readUInt16BE(0));
|
|
166
|
+
this.scaleFactors[`sf_${scaleFactorAddress}`] = scaleFactor;
|
|
167
|
+
}
|
|
168
|
+
// Apply scale factor: value * 10^scaleFactor
|
|
169
|
+
value = value * Math.pow(10, scaleFactor);
|
|
170
|
+
}
|
|
171
|
+
return value;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Convert unsigned 16-bit value to signed
|
|
175
|
+
*/
|
|
176
|
+
convertToSigned16(value) {
|
|
177
|
+
if (value > 32767) {
|
|
178
|
+
return value - 65536;
|
|
179
|
+
}
|
|
180
|
+
return value;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Helper to read register value(s) using the modbus client with error handling
|
|
184
|
+
*/
|
|
185
|
+
async readRegisterValue(address, quantity = 1) {
|
|
186
|
+
if (!this.modbusClient) {
|
|
187
|
+
throw new Error('Modbus client not initialized');
|
|
188
|
+
}
|
|
189
|
+
try {
|
|
190
|
+
const buffer = await this.modbusClient.readHoldingRegisters(address, quantity);
|
|
191
|
+
this.connectionHealth.recordSuccess();
|
|
192
|
+
if (quantity === 1) {
|
|
193
|
+
return buffer.readUInt16BE(0);
|
|
194
|
+
}
|
|
195
|
+
const values = [];
|
|
196
|
+
for (let i = 0; i < quantity; i++) {
|
|
197
|
+
values.push(buffer.readUInt16BE(i * 2));
|
|
198
|
+
}
|
|
199
|
+
return values;
|
|
200
|
+
}
|
|
201
|
+
catch (error) {
|
|
202
|
+
this.connectionHealth.recordFailure(error);
|
|
203
|
+
throw error;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Read inverter data from Model 103 (Three Phase)
|
|
208
|
+
*/
|
|
209
|
+
async readInverterData() {
|
|
210
|
+
const model = this.findModel(SunspecModelId.Inverter3Phase);
|
|
211
|
+
if (!model) {
|
|
212
|
+
console.log('Inverter model 103 not found, trying single phase model 101');
|
|
213
|
+
const singlePhaseModel = this.findModel(SunspecModelId.InverterSinglePhase);
|
|
214
|
+
if (!singlePhaseModel) {
|
|
215
|
+
console.error('No inverter model found');
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
return this.readSinglePhaseInverterData(singlePhaseModel);
|
|
219
|
+
}
|
|
220
|
+
const baseAddr = model.address + 2; // Skip ID and Length
|
|
221
|
+
try {
|
|
222
|
+
// Read all scale factors first (more efficient)
|
|
223
|
+
const scaleFactors = await this.readInverterScaleFactors(baseAddr);
|
|
224
|
+
// Read values and apply scale factors
|
|
225
|
+
const data = {
|
|
226
|
+
acCurrent: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 2, 1), scaleFactors.A_SF),
|
|
227
|
+
phaseACurrent: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 3, 1), scaleFactors.A_SF),
|
|
228
|
+
phaseBCurrent: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 4, 1), scaleFactors.A_SF),
|
|
229
|
+
phaseCCurrent: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 5, 1), scaleFactors.A_SF),
|
|
230
|
+
voltageAB: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 7, 1), scaleFactors.V_SF),
|
|
231
|
+
voltageBC: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 8, 1), scaleFactors.V_SF),
|
|
232
|
+
voltageCA: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 9, 1), scaleFactors.V_SF),
|
|
233
|
+
acPower: this.applyScaleFactor(this.convertToSigned16(await this.readRegisterValue(baseAddr + 14, 1)), scaleFactors.W_SF),
|
|
234
|
+
frequency: this.applyScaleFactor(await this.readRegisterValue(baseAddr + 16, 1), scaleFactors.Hz_SF),
|
|
235
|
+
dcPower: this.applyScaleFactor(this.convertToSigned16(await this.readRegisterValue(baseAddr + 30, 1)), scaleFactors.DCW_SF),
|
|
236
|
+
cabinetTemperature: this.applyScaleFactor(this.convertToSigned16(await this.readRegisterValue(baseAddr + 32, 1)), scaleFactors.Tmp_SF),
|
|
237
|
+
operatingState: await this.readRegisterValue(baseAddr + 37, 1)
|
|
238
|
+
};
|
|
239
|
+
// Read AC Energy (32-bit accumulator)
|
|
240
|
+
const energyRaw = await this.readRegisterValue(baseAddr + 24, 2);
|
|
241
|
+
data.acEnergy = BigInt((energyRaw[0] << 16) | energyRaw[1]) *
|
|
242
|
+
BigInt(Math.pow(10, scaleFactors.WH_SF));
|
|
243
|
+
return data;
|
|
244
|
+
}
|
|
245
|
+
catch (error) {
|
|
246
|
+
console.error(`Error reading inverter data: ${error}`);
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Read single phase inverter data (Model 101)
|
|
252
|
+
*/
|
|
253
|
+
async readSinglePhaseInverterData(model) {
|
|
254
|
+
// Similar to 3-phase but with fewer phase-specific values
|
|
255
|
+
// Implementation would be similar but simplified
|
|
256
|
+
const baseAddr = model.address + 2;
|
|
257
|
+
try {
|
|
258
|
+
// Simplified implementation for single phase
|
|
259
|
+
return {
|
|
260
|
+
acCurrent: await this.readRegisterWithScaleFactor(baseAddr + 2, baseAddr + 6),
|
|
261
|
+
acPower: await this.readRegisterWithScaleFactor(baseAddr + 9, baseAddr + 10),
|
|
262
|
+
frequency: await this.readRegisterWithScaleFactor(baseAddr + 11, baseAddr + 12),
|
|
263
|
+
dcPower: await this.readRegisterWithScaleFactor(baseAddr + 20, baseAddr + 21),
|
|
264
|
+
operatingState: await this.readRegisterValue(baseAddr + 24, 1)
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
catch (error) {
|
|
268
|
+
console.error(`Error reading single phase inverter data: ${error}`);
|
|
269
|
+
return null;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Read inverter scale factors
|
|
274
|
+
*/
|
|
275
|
+
async readInverterScaleFactors(baseAddr) {
|
|
276
|
+
return {
|
|
277
|
+
A_SF: this.convertToSigned16(await this.readRegisterValue(baseAddr + 6, 1)),
|
|
278
|
+
V_SF: this.convertToSigned16(await this.readRegisterValue(baseAddr + 13, 1)),
|
|
279
|
+
W_SF: this.convertToSigned16(await this.readRegisterValue(baseAddr + 15, 1)),
|
|
280
|
+
Hz_SF: this.convertToSigned16(await this.readRegisterValue(baseAddr + 17, 1)),
|
|
281
|
+
VA_SF: this.convertToSigned16(await this.readRegisterValue(baseAddr + 19, 1)),
|
|
282
|
+
VAr_SF: this.convertToSigned16(await this.readRegisterValue(baseAddr + 21, 1)),
|
|
283
|
+
PF_SF: this.convertToSigned16(await this.readRegisterValue(baseAddr + 23, 1)),
|
|
284
|
+
WH_SF: this.convertToSigned16(await this.readRegisterValue(baseAddr + 26, 1)),
|
|
285
|
+
DCA_SF: this.convertToSigned16(await this.readRegisterValue(baseAddr + 28, 1)),
|
|
286
|
+
DCV_SF: this.convertToSigned16(await this.readRegisterValue(baseAddr + 29, 1)),
|
|
287
|
+
DCW_SF: this.convertToSigned16(await this.readRegisterValue(baseAddr + 31, 1)),
|
|
288
|
+
Tmp_SF: this.convertToSigned16(await this.readRegisterValue(baseAddr + 36, 1))
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Apply scale factor to a value
|
|
293
|
+
*/
|
|
294
|
+
applyScaleFactor(value, scaleFactor) {
|
|
295
|
+
return value * Math.pow(10, scaleFactor);
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Read MPPT data from Model 160
|
|
299
|
+
*/
|
|
300
|
+
async readMPPTData(stringId = 1) {
|
|
301
|
+
const model = this.findModel(SunspecModelId.MPPT);
|
|
302
|
+
if (!model) {
|
|
303
|
+
console.log('MPPT model 160 not found');
|
|
304
|
+
return null;
|
|
305
|
+
}
|
|
306
|
+
const baseAddr = model.address + 2;
|
|
307
|
+
try {
|
|
308
|
+
// MPPT modules are repeating blocks, calculate offset for specific string
|
|
309
|
+
const moduleSize = 20; // Typical size of each MPPT module
|
|
310
|
+
const offset = (stringId - 1) * moduleSize;
|
|
311
|
+
const moduleAddr = baseAddr + offset;
|
|
312
|
+
// Read scale factors
|
|
313
|
+
const DCA_SF = this.convertToSigned16(await this.readRegisterValue(moduleAddr + 3, 1));
|
|
314
|
+
const DCV_SF = this.convertToSigned16(await this.readRegisterValue(moduleAddr + 5, 1));
|
|
315
|
+
const DCW_SF = this.convertToSigned16(await this.readRegisterValue(moduleAddr + 7, 1));
|
|
316
|
+
const Tmp_SF = this.convertToSigned16(await this.readRegisterValue(moduleAddr + 11, 1));
|
|
317
|
+
const data = {
|
|
318
|
+
id: stringId,
|
|
319
|
+
dcCurrent: this.applyScaleFactor(await this.readRegisterValue(moduleAddr + 2, 1), DCA_SF),
|
|
320
|
+
dcVoltage: this.applyScaleFactor(await this.readRegisterValue(moduleAddr + 4, 1), DCV_SF),
|
|
321
|
+
dcPower: this.applyScaleFactor(await this.readRegisterValue(moduleAddr + 6, 1), DCW_SF),
|
|
322
|
+
temperature: this.applyScaleFactor(this.convertToSigned16(await this.readRegisterValue(moduleAddr + 10, 1)), Tmp_SF),
|
|
323
|
+
operatingState: await this.readRegisterValue(moduleAddr + 12, 1),
|
|
324
|
+
events: await this.readRegisterValue(moduleAddr + 13, 1)
|
|
325
|
+
};
|
|
326
|
+
// Read DC Energy (32-bit)
|
|
327
|
+
const energyRaw = await this.readRegisterValue(moduleAddr + 8, 2);
|
|
328
|
+
data.dcEnergy = BigInt((energyRaw[0] << 16) | energyRaw[1]);
|
|
329
|
+
return data;
|
|
330
|
+
}
|
|
331
|
+
catch (error) {
|
|
332
|
+
console.error(`Error reading MPPT data for string ${stringId}: ${error}`);
|
|
333
|
+
return null;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Read all available MPPT strings
|
|
338
|
+
*/
|
|
339
|
+
async readAllMPPTData() {
|
|
340
|
+
const mpptData = [];
|
|
341
|
+
// Try to read up to 4 MPPT strings (typical maximum)
|
|
342
|
+
for (let i = 1; i <= 4; i++) {
|
|
343
|
+
const data = await this.readMPPTData(i);
|
|
344
|
+
if (data && data.dcCurrent !== undefined && data.dcCurrent > 0) {
|
|
345
|
+
mpptData.push(data);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
return mpptData;
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Read meter data (Model 203 for 3-phase)
|
|
352
|
+
*/
|
|
353
|
+
async readMeterData() {
|
|
354
|
+
let model = this.findModel(SunspecModelId.Meter3Phase);
|
|
355
|
+
if (!model) {
|
|
356
|
+
model = this.findModel(SunspecModelId.MeterWye);
|
|
357
|
+
}
|
|
358
|
+
if (!model) {
|
|
359
|
+
model = this.findModel(SunspecModelId.MeterSinglePhase);
|
|
360
|
+
}
|
|
361
|
+
if (!model) {
|
|
362
|
+
console.log('No meter model found');
|
|
363
|
+
return null;
|
|
364
|
+
}
|
|
365
|
+
const baseAddr = model.address + 2;
|
|
366
|
+
try {
|
|
367
|
+
// This is a simplified implementation
|
|
368
|
+
// Actual register offsets depend on specific meter model
|
|
369
|
+
return {
|
|
370
|
+
totalPower: await this.readRegisterWithScaleFactor(baseAddr + 10, baseAddr + 11),
|
|
371
|
+
frequency: await this.readRegisterWithScaleFactor(baseAddr + 20, baseAddr + 21)
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
catch (error) {
|
|
375
|
+
console.error(`Error reading meter data: ${error}`);
|
|
376
|
+
return null;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Read common block data (Model 1)
|
|
381
|
+
*/
|
|
382
|
+
async readCommonBlock() {
|
|
383
|
+
const model = this.findModel(SunspecModelId.Common);
|
|
384
|
+
if (!model) {
|
|
385
|
+
console.error('Common block model not found');
|
|
386
|
+
return null;
|
|
387
|
+
}
|
|
388
|
+
const baseAddr = model.address + 2;
|
|
389
|
+
try {
|
|
390
|
+
if (!this.modbusClient) {
|
|
391
|
+
throw new Error('Modbus client not initialized');
|
|
392
|
+
}
|
|
393
|
+
const manufacturer = await this.modbusClient.readRegisterStringValue(baseAddr, 16);
|
|
394
|
+
const modelName = await this.modbusClient.readRegisterStringValue(baseAddr + 16, 16);
|
|
395
|
+
const serialNumber = await this.modbusClient.readRegisterStringValue(baseAddr + 48, 16);
|
|
396
|
+
return {
|
|
397
|
+
manufacturer: manufacturer.replace(/\u0000/gmi, '').trim(),
|
|
398
|
+
model: modelName.replace(/\u0000/gmi, '').trim(),
|
|
399
|
+
serialNumber: serialNumber.replace(/\u0000/gmi, '').trim()
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
catch (error) {
|
|
403
|
+
console.error(`Error reading common block: ${error}`);
|
|
404
|
+
return null;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* Get serial number from device
|
|
409
|
+
*/
|
|
410
|
+
async getSerialNumber() {
|
|
411
|
+
const commonData = await this.readCommonBlock();
|
|
412
|
+
return commonData?.serialNumber;
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Check if connected
|
|
416
|
+
*/
|
|
417
|
+
isConnected() {
|
|
418
|
+
return this.connected;
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Check if connection is healthy
|
|
422
|
+
*/
|
|
423
|
+
isHealthy() {
|
|
424
|
+
return this.connected && this.connectionHealth.isHealthy();
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Get connection health details
|
|
428
|
+
*/
|
|
429
|
+
getConnectionHealth() {
|
|
430
|
+
return this.connectionHealth;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
exports.SunspecModbusClient = SunspecModbusClient;
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import type { IConnectionHealth } from "../interfaces.cjs";
|
|
2
|
+
import { EnergyApp } from "../../../index.cjs";
|
|
3
|
+
export declare enum SunspecModelId {
|
|
4
|
+
Common = 1,
|
|
5
|
+
Inverter3Phase = 103,
|
|
6
|
+
InverterSinglePhase = 101,
|
|
7
|
+
MPPT = 160,
|
|
8
|
+
Battery = 124,
|
|
9
|
+
BatteryBase = 802,
|
|
10
|
+
BatteryControl = 803,
|
|
11
|
+
MeterSinglePhase = 201,
|
|
12
|
+
Meter3Phase = 203,
|
|
13
|
+
MeterWye = 204,
|
|
14
|
+
Nameplate = 120,
|
|
15
|
+
Settings = 121,
|
|
16
|
+
Status = 122,
|
|
17
|
+
Controls = 123,
|
|
18
|
+
EndMarker = 65535
|
|
19
|
+
}
|
|
20
|
+
interface SunspecModel {
|
|
21
|
+
id: number;
|
|
22
|
+
address: number;
|
|
23
|
+
length: number;
|
|
24
|
+
}
|
|
25
|
+
export interface SunspecInverterData {
|
|
26
|
+
acCurrent?: number;
|
|
27
|
+
phaseACurrent?: number;
|
|
28
|
+
phaseBCurrent?: number;
|
|
29
|
+
phaseCCurrent?: number;
|
|
30
|
+
voltageAB?: number;
|
|
31
|
+
voltageBC?: number;
|
|
32
|
+
voltageCA?: number;
|
|
33
|
+
voltageAN?: number;
|
|
34
|
+
voltageBN?: number;
|
|
35
|
+
voltageCN?: number;
|
|
36
|
+
acPower?: number;
|
|
37
|
+
frequency?: number;
|
|
38
|
+
apparentPower?: number;
|
|
39
|
+
reactivePower?: number;
|
|
40
|
+
powerFactor?: number;
|
|
41
|
+
acEnergy?: bigint;
|
|
42
|
+
dcCurrent?: number;
|
|
43
|
+
dcVoltage?: number;
|
|
44
|
+
dcPower?: number;
|
|
45
|
+
cabinetTemperature?: number;
|
|
46
|
+
operatingState?: number;
|
|
47
|
+
events?: number;
|
|
48
|
+
}
|
|
49
|
+
export interface SunspecMPPTData {
|
|
50
|
+
id: number;
|
|
51
|
+
dcCurrent?: number;
|
|
52
|
+
dcVoltage?: number;
|
|
53
|
+
dcPower?: number;
|
|
54
|
+
dcEnergy?: bigint;
|
|
55
|
+
temperature?: number;
|
|
56
|
+
operatingState?: number;
|
|
57
|
+
events?: number;
|
|
58
|
+
}
|
|
59
|
+
export interface SunspecBatteryData {
|
|
60
|
+
soc?: number;
|
|
61
|
+
soh?: number;
|
|
62
|
+
chargePower?: number;
|
|
63
|
+
dischargePower?: number;
|
|
64
|
+
voltage?: number;
|
|
65
|
+
current?: number;
|
|
66
|
+
temperature?: number;
|
|
67
|
+
status?: number;
|
|
68
|
+
}
|
|
69
|
+
export interface SunspecMeterData {
|
|
70
|
+
totalPower?: number;
|
|
71
|
+
phaseAPower?: number;
|
|
72
|
+
phaseBPower?: number;
|
|
73
|
+
phaseCPower?: number;
|
|
74
|
+
totalEnergy?: bigint;
|
|
75
|
+
exportedEnergy?: bigint;
|
|
76
|
+
importedEnergy?: bigint;
|
|
77
|
+
voltage?: number;
|
|
78
|
+
current?: number;
|
|
79
|
+
frequency?: number;
|
|
80
|
+
}
|
|
81
|
+
export declare class SunspecModbusClient {
|
|
82
|
+
private energyApp;
|
|
83
|
+
private modbusClient;
|
|
84
|
+
private discoveredModels;
|
|
85
|
+
private scaleFactors;
|
|
86
|
+
private connected;
|
|
87
|
+
private baseAddress;
|
|
88
|
+
private connectionHealth;
|
|
89
|
+
private faultTolerantReader;
|
|
90
|
+
private registerMapper;
|
|
91
|
+
private dataTypeConverter;
|
|
92
|
+
constructor(energyApp: EnergyApp);
|
|
93
|
+
/**
|
|
94
|
+
* Connect to Modbus device
|
|
95
|
+
*/
|
|
96
|
+
connect(host: string, port?: number, unitId?: number): Promise<void>;
|
|
97
|
+
/**
|
|
98
|
+
* Disconnect from Modbus device
|
|
99
|
+
*/
|
|
100
|
+
disconnect(): Promise<void>;
|
|
101
|
+
/**
|
|
102
|
+
* Discover all available Sunspec models
|
|
103
|
+
* Scans through the address space starting at 40001
|
|
104
|
+
*/
|
|
105
|
+
discoverModels(): Promise<Map<number, SunspecModel>>;
|
|
106
|
+
/**
|
|
107
|
+
* Find a specific model by ID
|
|
108
|
+
*/
|
|
109
|
+
findModel(modelId: number): SunspecModel | undefined;
|
|
110
|
+
/**
|
|
111
|
+
* Read a register value and apply scale factor
|
|
112
|
+
*/
|
|
113
|
+
readRegisterWithScaleFactor(valueAddress: number, scaleFactorAddress?: number, quantity?: number): Promise<number>;
|
|
114
|
+
/**
|
|
115
|
+
* Convert unsigned 16-bit value to signed
|
|
116
|
+
*/
|
|
117
|
+
private convertToSigned16;
|
|
118
|
+
/**
|
|
119
|
+
* Helper to read register value(s) using the modbus client with error handling
|
|
120
|
+
*/
|
|
121
|
+
private readRegisterValue;
|
|
122
|
+
/**
|
|
123
|
+
* Read inverter data from Model 103 (Three Phase)
|
|
124
|
+
*/
|
|
125
|
+
readInverterData(): Promise<SunspecInverterData | null>;
|
|
126
|
+
/**
|
|
127
|
+
* Read single phase inverter data (Model 101)
|
|
128
|
+
*/
|
|
129
|
+
private readSinglePhaseInverterData;
|
|
130
|
+
/**
|
|
131
|
+
* Read inverter scale factors
|
|
132
|
+
*/
|
|
133
|
+
private readInverterScaleFactors;
|
|
134
|
+
/**
|
|
135
|
+
* Apply scale factor to a value
|
|
136
|
+
*/
|
|
137
|
+
private applyScaleFactor;
|
|
138
|
+
/**
|
|
139
|
+
* Read MPPT data from Model 160
|
|
140
|
+
*/
|
|
141
|
+
readMPPTData(stringId?: number): Promise<SunspecMPPTData | null>;
|
|
142
|
+
/**
|
|
143
|
+
* Read all available MPPT strings
|
|
144
|
+
*/
|
|
145
|
+
readAllMPPTData(): Promise<SunspecMPPTData[]>;
|
|
146
|
+
/**
|
|
147
|
+
* Read meter data (Model 203 for 3-phase)
|
|
148
|
+
*/
|
|
149
|
+
readMeterData(): Promise<SunspecMeterData | null>;
|
|
150
|
+
/**
|
|
151
|
+
* Read common block data (Model 1)
|
|
152
|
+
*/
|
|
153
|
+
readCommonBlock(): Promise<any>;
|
|
154
|
+
/**
|
|
155
|
+
* Get serial number from device
|
|
156
|
+
*/
|
|
157
|
+
getSerialNumber(): Promise<string | undefined>;
|
|
158
|
+
/**
|
|
159
|
+
* Check if connected
|
|
160
|
+
*/
|
|
161
|
+
isConnected(): boolean;
|
|
162
|
+
/**
|
|
163
|
+
* Check if connection is healthy
|
|
164
|
+
*/
|
|
165
|
+
isHealthy(): boolean;
|
|
166
|
+
/**
|
|
167
|
+
* Get connection health details
|
|
168
|
+
*/
|
|
169
|
+
getConnectionHealth(): IConnectionHealth;
|
|
170
|
+
}
|
|
171
|
+
export {};
|
package/dist/cjs/index.cjs
CHANGED
|
@@ -17,9 +17,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
17
17
|
exports.EnergyApp = void 0;
|
|
18
18
|
const version_js_1 = require("./version.cjs");
|
|
19
19
|
__exportStar(require("./energy-app-package-definition.cjs"), exports);
|
|
20
|
-
__exportStar(require("./implementations/modbus/EnergyAppModbusBattery.cjs"), exports);
|
|
21
|
-
__exportStar(require("./implementations/modbus/EnergyAppModbusMeter.cjs"), exports);
|
|
22
|
-
__exportStar(require("./implementations/modbus/EnergyAppModbusInverter.cjs"), exports);
|
|
23
20
|
__exportStar(require("./version.cjs"), exports);
|
|
24
21
|
__exportStar(require("./implementations/ocpp/ocpp16.cjs"), exports);
|
|
25
22
|
__exportStar(require("./implementations/ocpp/ocpp201.cjs"), exports);
|
|
@@ -29,6 +26,8 @@ __exportStar(require("./types/enyo-settings.cjs"), exports);
|
|
|
29
26
|
__exportStar(require("./types/enyo-energy-tariff.cjs"), exports);
|
|
30
27
|
__exportStar(require("./types/enyo-electricity-prices.cjs"), exports);
|
|
31
28
|
__exportStar(require("./types/enyo-notification.cjs"), exports);
|
|
29
|
+
__exportStar(require("./implementations/appliances/appliance-manager.cjs"), exports);
|
|
30
|
+
__exportStar(require("./implementations/appliances/identifier-strategies.cjs"), exports);
|
|
32
31
|
class EnergyApp {
|
|
33
32
|
energyAppSdk;
|
|
34
33
|
constructor() {
|
package/dist/cjs/index.d.cts
CHANGED
|
@@ -14,9 +14,6 @@ import { EnergyAppSettings } from "./packages/energy-app-settings.cjs";
|
|
|
14
14
|
import { EnergyAppElectricityPrices } from "./packages/energy-app-electricity-prices.cjs";
|
|
15
15
|
import { EnergyAppNotification } from "./packages/energy-app-notification.cjs";
|
|
16
16
|
export * from './energy-app-package-definition.cjs';
|
|
17
|
-
export * from './implementations/modbus/EnergyAppModbusBattery.cjs';
|
|
18
|
-
export * from './implementations/modbus/EnergyAppModbusMeter.cjs';
|
|
19
|
-
export * from './implementations/modbus/EnergyAppModbusInverter.cjs';
|
|
20
17
|
export * from './version.cjs';
|
|
21
18
|
export * from './implementations/ocpp/ocpp16.cjs';
|
|
22
19
|
export * from './implementations/ocpp/ocpp201.cjs';
|
|
@@ -26,6 +23,8 @@ export * from './types/enyo-settings.cjs';
|
|
|
26
23
|
export * from './types/enyo-energy-tariff.cjs';
|
|
27
24
|
export * from './types/enyo-electricity-prices.cjs';
|
|
28
25
|
export * from './types/enyo-notification.cjs';
|
|
26
|
+
export * from './implementations/appliances/appliance-manager.cjs';
|
|
27
|
+
export * from './implementations/appliances/identifier-strategies.cjs';
|
|
29
28
|
export declare class EnergyApp implements EnyoEnergyAppSdk {
|
|
30
29
|
private readonly energyAppSdk;
|
|
31
30
|
constructor();
|
|
@@ -109,6 +109,7 @@ export declare enum EnyoDataBusMessageEnum {
|
|
|
109
109
|
AggregatedStateUpdateV1 = "AggregatedStateUpdateV1",
|
|
110
110
|
EnergyTariffUpdateV1 = "EnergyTariffUpdateV1"
|
|
111
111
|
}
|
|
112
|
+
export type EnyoDataBusMessageResolution = '10s' | '30s' | '1m' | '15m' | '1h' | '1d' | 'dynamic';
|
|
112
113
|
export interface EnyoDataBusMessage {
|
|
113
114
|
id: string;
|
|
114
115
|
message: EnyoDataBusMessageEnum;
|
|
@@ -118,7 +119,7 @@ export interface EnyoDataBusMessage {
|
|
|
118
119
|
clockId?: string;
|
|
119
120
|
timestampIso: string;
|
|
120
121
|
/** If you just forward events that occur, use dynamic as resolution */
|
|
121
|
-
resolution?:
|
|
122
|
+
resolution?: EnyoDataBusMessageResolution;
|
|
122
123
|
data: object;
|
|
123
124
|
}
|
|
124
125
|
export interface EnyoDataBusMessageAnswer extends EnyoDataBusMessage {
|
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.39';
|
|
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