@enyo-energy/sunspec-sdk 0.0.32 → 0.0.33

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.
@@ -1,10 +1,13 @@
1
- import { SunspecBatteryChargeState, SunspecModelId, SunspecMPPTOperatingState, SunspecStorageMode } from "./sunspec-interfaces.js";
1
+ import { SunspecBatteryChargeState, SunspecModelId, SunspecMPPTOperatingState, SunspecStorageMode, SunspecInverterCapability } from "./sunspec-interfaces.js";
2
2
  import { randomUUID } from "node:crypto";
3
3
  import { EnyoApplianceConnectionType, EnyoApplianceStateEnum, EnyoApplianceTopologyFeatureEnum, EnyoApplianceTypeEnum } from "@enyo-energy/energy-app-sdk/dist/types/enyo-appliance.js";
4
+ import { ConnectionRetryManager } from "./connection-retry-manager.js";
4
5
  import { EnyoBatteryStateEnum, EnyoCommandAcknowledgeAnswerEnum, EnyoDataBusMessageEnum, EnyoInverterStateEnum, EnyoStringStateEnum } from "@enyo-energy/energy-app-sdk/dist/types/enyo-data-bus-value.js";
5
6
  import { EnyoSourceEnum } from "@enyo-energy/energy-app-sdk/dist/types/enyo-source.enum.js";
6
7
  import { EnyoMeterApplianceAvailableFeaturesEnum } from "@enyo-energy/energy-app-sdk/dist/types/enyo-meter-appliance.js";
7
8
  import { EnyoBatteryFeature, EnyoBatteryStorageMode } from "@enyo-energy/energy-app-sdk/dist/types/enyo-battery-appliance.js";
9
+ // TODO: Remove once added to @enyo-energy/energy-app-sdk EnyoDataBusMessageEnum
10
+ export const ENYO_DATA_BUS_SET_INVERTER_FEED_IN_LIMIT_V1 = 'SetInverterFeedInLimitV1';
8
11
  /**
9
12
  * Extract battery discharge power from MPPT data.
10
13
  * Returns the discharge power in Watts (positive value), or 0 if no discharge.
@@ -32,7 +35,10 @@ export class BaseSunspecDevice {
32
35
  baseAddress;
33
36
  applianceId;
34
37
  lastUpdateTime = 0;
35
- constructor(energyApp, name, networkDevice, sunspecClient, applianceManager, unitId = 1, port = 502, baseAddress = 40000) {
38
+ dataBusListenerId;
39
+ dataBus;
40
+ retryManager;
41
+ constructor(energyApp, name, networkDevice, sunspecClient, applianceManager, unitId = 1, port = 502, baseAddress = 40000, retryConfig) {
36
42
  this.energyApp = energyApp;
37
43
  this.name = name;
38
44
  this.networkDevice = networkDevice;
@@ -41,6 +47,7 @@ export class BaseSunspecDevice {
41
47
  this.unitId = unitId;
42
48
  this.port = port;
43
49
  this.baseAddress = baseAddress;
50
+ this.retryManager = new ConnectionRetryManager(retryConfig);
44
51
  }
45
52
  /**
46
53
  * Check if the device is connected
@@ -62,11 +69,86 @@ export class BaseSunspecDevice {
62
69
  await this.sunspecClient.discoverModels(this.baseAddress);
63
70
  }
64
71
  }
72
+ /**
73
+ * Attempt a reconnection if the tiered retry schedule allows it.
74
+ * Called from readData() when the device is disconnected.
75
+ * Returns true if reconnection succeeded.
76
+ */
77
+ async tryReconnect() {
78
+ this.retryManager.markDisconnected();
79
+ if (!this.retryManager.shouldAttemptNow()) {
80
+ return false;
81
+ }
82
+ this.retryManager.recordAttempt();
83
+ const phase = this.retryManager.getCurrentPhase();
84
+ const attempt = this.retryManager.getAttemptCount();
85
+ const elapsed = Math.round(this.retryManager.getElapsedMs() / 1000);
86
+ console.log(`${this.constructor.name} ${this.applianceId}: Reconnect attempt #${attempt} ` +
87
+ `(phase: ${phase.intervalMs / 1000}s interval, elapsed: ${elapsed}s)`);
88
+ try {
89
+ const success = await this.sunspecClient.reconnect();
90
+ if (success) {
91
+ // Re-discover models after reconnect
92
+ await this.sunspecClient.discoverModels(this.baseAddress);
93
+ this.retryManager.reset();
94
+ // Update appliance state to Connected
95
+ if (this.applianceId) {
96
+ await this.applianceManager.updateApplianceState(this.applianceId, EnyoApplianceConnectionType.Connector, EnyoApplianceStateEnum.Connected);
97
+ }
98
+ console.log(`${this.constructor.name} ${this.applianceId}: Reconnection successful after ${attempt} attempt(s)`);
99
+ return true;
100
+ }
101
+ }
102
+ catch (error) {
103
+ console.error(`${this.constructor.name} ${this.applianceId}: Reconnect attempt #${attempt} failed: ${error}`);
104
+ }
105
+ return false;
106
+ }
107
+ /**
108
+ * Mark the device as offline: update appliance state and start tracking disconnection.
109
+ */
110
+ async markOffline() {
111
+ this.retryManager.markDisconnected();
112
+ if (this.applianceId) {
113
+ try {
114
+ await this.applianceManager.updateApplianceState(this.applianceId, EnyoApplianceConnectionType.Connector, EnyoApplianceStateEnum.Offline);
115
+ }
116
+ catch (error) {
117
+ console.error(`${this.constructor.name} ${this.applianceId}: Failed to mark appliance offline: ${error}`);
118
+ }
119
+ }
120
+ }
121
+ sendCommandAcknowledge(messageId, acknowledgeMessage, answer, rejectionReason) {
122
+ if (!this.dataBus || !this.applianceId) {
123
+ return;
124
+ }
125
+ const ackMessage = {
126
+ id: randomUUID(),
127
+ message: EnyoDataBusMessageEnum.CommandAcknowledgeV1,
128
+ type: 'answer',
129
+ source: EnyoSourceEnum.Device,
130
+ applianceId: this.applianceId,
131
+ timestampIso: new Date().toISOString(),
132
+ data: {
133
+ messageId,
134
+ acknowledgeMessage: acknowledgeMessage,
135
+ answer,
136
+ rejectionReason
137
+ }
138
+ };
139
+ console.log(`${this.constructor.name} ${this.applianceId}: sending ${answer} for ${acknowledgeMessage} (messageId=${messageId}${rejectionReason ? `, reason=${rejectionReason}` : ''})`);
140
+ this.dataBus.sendMessage([ackMessage]);
141
+ }
65
142
  }
66
143
  /**
67
144
  * Sunspec Inverter implementation using dynamic model discovery
68
145
  */
69
146
  export class SunspecInverter extends BaseSunspecDevice {
147
+ capabilities;
148
+ constructor(energyApp, name, networkDevice, sunspecClient, applianceManager, unitId = 1, port = 502, baseAddress = 40000, capabilities = [], retryConfig) {
149
+ super(energyApp, name, networkDevice, sunspecClient, applianceManager, unitId, port, baseAddress, retryConfig);
150
+ this.capabilities = capabilities;
151
+ }
70
152
  async connect() {
71
153
  // Ensure Sunspec client is connected
72
154
  if (!this.sunspecClient.isConnected()) {
@@ -107,8 +189,10 @@ export class SunspecInverter extends BaseSunspecDevice {
107
189
  if (mpptModel) {
108
190
  console.log(`MPPT model found for inverter ${this.networkDevice.hostname}`);
109
191
  }
192
+ this.startDataBusListening();
110
193
  }
111
194
  async disconnect() {
195
+ this.stopDataBusListening();
112
196
  if (this.applianceId) {
113
197
  await this.applianceManager.updateApplianceState(this.applianceId, EnyoApplianceConnectionType.Connector, EnyoApplianceStateEnum.Offline);
114
198
  }
@@ -119,7 +203,9 @@ export class SunspecInverter extends BaseSunspecDevice {
119
203
  }
120
204
  async readData(clockId, resolution) {
121
205
  if (!this.isConnected()) {
122
- return [];
206
+ await this.tryReconnect();
207
+ if (!this.isConnected())
208
+ return [];
123
209
  }
124
210
  const messages = [];
125
211
  const timestamp = new Date();
@@ -169,6 +255,7 @@ export class SunspecInverter extends BaseSunspecDevice {
169
255
  }
170
256
  catch (error) {
171
257
  console.error(`Error updating inverter data: ${error}`);
258
+ await this.markOffline();
172
259
  }
173
260
  return messages;
174
261
  }
@@ -239,13 +326,72 @@ export class SunspecInverter extends BaseSunspecDevice {
239
326
  mapDcStringToApplianceMetadata(mpptDataList) {
240
327
  return mpptDataList.map(s => ({ index: s.index, name: s.name }));
241
328
  }
329
+ /**
330
+ * Start listening for inverter commands on the data bus.
331
+ * Idempotent — does nothing if already listening.
332
+ */
333
+ startDataBusListening() {
334
+ if (this.dataBusListenerId) {
335
+ return;
336
+ }
337
+ this.dataBus = this.energyApp.useDataBus();
338
+ this.dataBusListenerId = this.dataBus.listenForMessages([ENYO_DATA_BUS_SET_INVERTER_FEED_IN_LIMIT_V1], (entry) => this.handleInverterCommand(entry));
339
+ console.log(`Inverter ${this.applianceId}: started data bus listening (listener ${this.dataBusListenerId})`);
340
+ }
341
+ /**
342
+ * Stop listening for inverter commands on the data bus.
343
+ */
344
+ stopDataBusListening() {
345
+ if (this.dataBusListenerId && this.dataBus) {
346
+ this.dataBus.unsubscribe(this.dataBusListenerId);
347
+ console.log(`Inverter ${this.applianceId}: stopped data bus listening (listener ${this.dataBusListenerId})`);
348
+ }
349
+ this.dataBusListenerId = undefined;
350
+ this.dataBus = undefined;
351
+ }
352
+ handleInverterCommand(entry) {
353
+ if (entry.applianceId !== this.applianceId) {
354
+ return;
355
+ }
356
+ void (async () => {
357
+ try {
358
+ if (entry.message === ENYO_DATA_BUS_SET_INVERTER_FEED_IN_LIMIT_V1) {
359
+ await this.handleSetFeedInLimit(entry);
360
+ }
361
+ }
362
+ catch (error) {
363
+ console.error(`Inverter ${this.applianceId}: error handling ${entry.message}:`, error);
364
+ }
365
+ })();
366
+ }
367
+ async handleSetFeedInLimit(msg) {
368
+ // Check capability
369
+ if (!this.capabilities.includes(SunspecInverterCapability.FeedInLimit)) {
370
+ this.sendCommandAcknowledge(msg.id, msg.message, EnyoCommandAcknowledgeAnswerEnum.NotSupported, 'FeedInLimit capability not enabled');
371
+ return;
372
+ }
373
+ if (!this.isConnected() || !this.applianceId) {
374
+ this.sendCommandAcknowledge(msg.id, msg.message, EnyoCommandAcknowledgeAnswerEnum.Rejected, 'Not connected');
375
+ return;
376
+ }
377
+ console.log(`Inverter ${this.applianceId}: handling SetInverterFeedInLimitV1 (feedInLimitW=${msg.data.feedInLimitW})`);
378
+ const success = await this.sunspecClient.setFeedInLimit(msg.data.feedInLimitW);
379
+ if (!success) {
380
+ this.sendCommandAcknowledge(msg.id, msg.message, EnyoCommandAcknowledgeAnswerEnum.Rejected, 'Failed to set feed-in limit');
381
+ return;
382
+ }
383
+ this.sendCommandAcknowledge(msg.id, msg.message, EnyoCommandAcknowledgeAnswerEnum.Accepted);
384
+ }
242
385
  }
243
386
  /**
244
387
  * Sunspec Battery implementation
245
388
  */
246
389
  export class SunspecBattery extends BaseSunspecDevice {
247
- dataBusListenerId;
248
- dataBus;
390
+ capabilities;
391
+ constructor(energyApp, name, networkDevice, sunspecClient, applianceManager, unitId = 1, port = 502, baseAddress = 40000, capabilities = [], retryConfig) {
392
+ super(energyApp, name, networkDevice, sunspecClient, applianceManager, unitId, port, baseAddress, retryConfig);
393
+ this.capabilities = capabilities;
394
+ }
249
395
  /**
250
396
  * Connect to the battery and create/update the appliance
251
397
  */
@@ -332,7 +478,9 @@ export class SunspecBattery extends BaseSunspecDevice {
332
478
  */
333
479
  async readData(clockId, resolution) {
334
480
  if (!this.isConnected()) {
335
- return [];
481
+ await this.tryReconnect();
482
+ if (!this.isConnected())
483
+ return [];
336
484
  }
337
485
  const messages = [];
338
486
  const timestamp = new Date();
@@ -390,6 +538,7 @@ export class SunspecBattery extends BaseSunspecDevice {
390
538
  }
391
539
  catch (error) {
392
540
  console.error(`Error updating battery data: ${error}`);
541
+ await this.markOffline();
393
542
  }
394
543
  return messages;
395
544
  }
@@ -706,44 +855,35 @@ export class SunspecBattery extends BaseSunspecDevice {
706
855
  this.sendCommandAcknowledge(msg.id, msg.message, EnyoCommandAcknowledgeAnswerEnum.NotSupported);
707
856
  return;
708
857
  }
709
- console.log(`Battery ${this.applianceId}: handling SetStorageDischargeLimitV1 (dischargeLimitPercent=${msg.data.dischargeLimitPercent})`);
710
- // Read current state for logging
858
+ console.log(`Battery ${this.applianceId}: handling SetStorageDischargeLimitV1 (dischargeLimitW=${msg.data.dischargeLimitW})`);
859
+ // Read current state to get wChaMax for percentage conversion
711
860
  const controls = await this.getBatteryControls();
712
- console.log(`Battery ${this.applianceId}: current state - outWRte=${controls?.outWRte}`);
861
+ console.log(`Battery ${this.applianceId}: current state - outWRte=${controls?.outWRte}, wChaMax=${controls?.wChaMax}`);
862
+ if (!controls?.wChaMax) {
863
+ this.sendCommandAcknowledge(msg.id, msg.message, EnyoCommandAcknowledgeAnswerEnum.Rejected, 'Failed to read wChaMax for discharge limit conversion');
864
+ return;
865
+ }
866
+ // Convert watts to percentage of WDisChaMax (using wChaMax), clamped to 0-100%
867
+ const dischargeLimitPercent = Math.min(100, Math.max(0, (msg.data.dischargeLimitW / controls.wChaMax) * 100));
868
+ console.log(`Battery ${this.applianceId}: calculated discharge limit: ${dischargeLimitPercent.toFixed(1)}% (${msg.data.dischargeLimitW}W / ${controls.wChaMax}W)`);
713
869
  // Set discharge limit (Register 12: outWRte)
714
- const success = await this.writeBatteryControls({ outWRte: msg.data.dischargeLimitPercent });
870
+ const success = await this.writeBatteryControls({ outWRte: dischargeLimitPercent });
715
871
  if (!success) {
716
872
  this.sendCommandAcknowledge(msg.id, msg.message, EnyoCommandAcknowledgeAnswerEnum.Rejected, 'Failed to set discharge limit');
717
873
  return;
718
874
  }
719
875
  this.sendCommandAcknowledge(msg.id, msg.message, EnyoCommandAcknowledgeAnswerEnum.Accepted);
720
876
  }
721
- sendCommandAcknowledge(messageId, acknowledgeMessage, answer, rejectionReason) {
722
- if (!this.dataBus || !this.applianceId) {
723
- return;
724
- }
725
- const ackMessage = {
726
- id: randomUUID(),
727
- message: EnyoDataBusMessageEnum.CommandAcknowledgeV1,
728
- type: 'answer',
729
- source: EnyoSourceEnum.Device,
730
- applianceId: this.applianceId,
731
- timestampIso: new Date().toISOString(),
732
- data: {
733
- messageId,
734
- acknowledgeMessage,
735
- answer,
736
- rejectionReason
737
- }
738
- };
739
- console.log(`Battery ${this.applianceId}: sending ${answer} for ${acknowledgeMessage} (messageId=${messageId}${rejectionReason ? `, reason=${rejectionReason}` : ''})`);
740
- this.dataBus.sendMessage([ackMessage]);
741
- }
742
877
  }
743
878
  /**
744
879
  * Sunspec Meter implementation
745
880
  */
746
881
  export class SunspecMeter extends BaseSunspecDevice {
882
+ capabilities;
883
+ constructor(energyApp, name, networkDevice, sunspecClient, applianceManager, unitId = 1, port = 502, baseAddress = 40000, capabilities = [], retryConfig) {
884
+ super(energyApp, name, networkDevice, sunspecClient, applianceManager, unitId, port, baseAddress, retryConfig);
885
+ this.capabilities = capabilities;
886
+ }
747
887
  /**
748
888
  * Connect to the meter and create/update the appliance
749
889
  */
@@ -800,7 +940,9 @@ export class SunspecMeter extends BaseSunspecDevice {
800
940
  */
801
941
  async readData(clockId, resolution) {
802
942
  if (!this.isConnected()) {
803
- return [];
943
+ await this.tryReconnect();
944
+ if (!this.isConnected())
945
+ return [];
804
946
  }
805
947
  const messages = [];
806
948
  const timestamp = new Date();
@@ -831,6 +973,7 @@ export class SunspecMeter extends BaseSunspecDevice {
831
973
  }
832
974
  catch (error) {
833
975
  console.error(`Error updating meter data: ${error}`);
976
+ await this.markOffline();
834
977
  }
835
978
  return messages;
836
979
  }
@@ -2,13 +2,17 @@
2
2
  * SunSpec block interfaces with block numbers
3
3
  */
4
4
  /**
5
- * Configuration for connection retry with exponential backoff
5
+ * A single phase in the tiered retry schedule
6
+ */
7
+ export interface IRetryPhase {
8
+ intervalMs: number;
9
+ durationMs: number;
10
+ }
11
+ /**
12
+ * Configuration for connection retry with tiered schedule
6
13
  */
7
14
  export interface IRetryConfig {
8
- initialDelayMs: number;
9
- maxDelayMs: number;
10
- backoffFactor: number;
11
- maxAttempts: number;
15
+ phases: IRetryPhase[];
12
16
  }
13
17
  export declare const DEFAULT_RETRY_CONFIG: IRetryConfig;
14
18
  /**
@@ -551,6 +555,15 @@ export declare enum SunspecStorageMode {
551
555
  * 2. Set chaGriSet = 1 to allow grid charging
552
556
  * 3. Set wChaMax to the desired charging power in Watts
553
557
  */
558
+ export declare enum SunspecInverterCapability {
559
+ FeedInLimit = "feed-in-limit"
560
+ }
561
+ export declare enum SunspecBatteryCapability {
562
+ GridCharging = "grid-charging",
563
+ DischargeLimit = "discharge-limit"
564
+ }
565
+ export declare enum SunspecMeterCapability {
566
+ }
554
567
  export interface SunspecBatteryControls {
555
568
  storCtlMod?: number;
556
569
  chaGriSet?: number;
@@ -2,10 +2,12 @@
2
2
  * SunSpec block interfaces with block numbers
3
3
  */
4
4
  export const DEFAULT_RETRY_CONFIG = {
5
- initialDelayMs: 1000,
6
- maxDelayMs: 30000,
7
- backoffFactor: 1.5,
8
- maxAttempts: 10
5
+ phases: [
6
+ { intervalMs: 10_000, durationMs: 60_000 }, // Phase 1: every 10s for 1 minute
7
+ { intervalMs: 30_000, durationMs: 120_000 }, // Phase 2: every 30s for 2 minutes
8
+ { intervalMs: 60_000, durationMs: 300_000 }, // Phase 3: every 1m for 5 minutes
9
+ { intervalMs: 300_000, durationMs: 0 }, // Phase 4: every 5m forever
10
+ ]
9
11
  };
10
12
  /**
11
13
  * Common Sunspec Model IDs
@@ -197,3 +199,24 @@ export var SunspecStorageMode;
197
199
  SunspecStorageMode["HOLDING"] = "holding";
198
200
  SunspecStorageMode["AUTO"] = "auto"; // Both charge and discharge allowed
199
201
  })(SunspecStorageMode || (SunspecStorageMode = {}));
202
+ /**
203
+ * Battery control structure for writing to Model 124
204
+ * Used for controlling battery charge/discharge behavior
205
+ *
206
+ * IMPORTANT: To enable grid charging with specific power:
207
+ * 1. Set storCtlMod with appropriate bits to enable external control
208
+ * 2. Set chaGriSet = 1 to allow grid charging
209
+ * 3. Set wChaMax to the desired charging power in Watts
210
+ */
211
+ export var SunspecInverterCapability;
212
+ (function (SunspecInverterCapability) {
213
+ SunspecInverterCapability["FeedInLimit"] = "feed-in-limit";
214
+ })(SunspecInverterCapability || (SunspecInverterCapability = {}));
215
+ export var SunspecBatteryCapability;
216
+ (function (SunspecBatteryCapability) {
217
+ SunspecBatteryCapability["GridCharging"] = "grid-charging";
218
+ SunspecBatteryCapability["DischargeLimit"] = "discharge-limit";
219
+ })(SunspecBatteryCapability || (SunspecBatteryCapability = {}));
220
+ export var SunspecMeterCapability;
221
+ (function (SunspecMeterCapability) {
222
+ })(SunspecMeterCapability || (SunspecMeterCapability = {}));
@@ -16,9 +16,8 @@
16
16
  * - pad: 0x8000 (always returns this value)
17
17
  * - string: all registers 0x0000 (NULL)
18
18
  */
19
- import { type SunspecInverterControls, type SunspecInverterData, type SunspecInverterSettings, type SunspecMeterData, type SunspecModel, type SunspecMPPTData, type SunspecBatteryData, type SunspecBatteryBaseData, type SunspecBatteryControls, SunspecStorageMode, type IRetryConfig } from "./sunspec-interfaces.js";
20
- import { ConnectionRetryManager } from "./connection-retry-manager.js";
21
- import { IConnectionHealth } from "@enyo-energy/energy-app-sdk/dist/implementations/modbus/interfaces.js";
19
+ import { type SunspecInverterControls, type SunspecInverterData, type SunspecInverterSettings, type SunspecMeterData, type SunspecModel, type SunspecMPPTData, type SunspecBatteryData, type SunspecBatteryBaseData, type SunspecBatteryControls, SunspecStorageMode } from "./sunspec-interfaces.js";
20
+ import { EnergyAppModbusDataType, IConnectionHealth } from "@enyo-energy/energy-app-sdk/dist/implementations/modbus/interfaces.js";
22
21
  import { EnergyApp } from "@enyo-energy/energy-app-sdk";
23
22
  export declare class SunspecModbusClient {
24
23
  private energyApp;
@@ -29,9 +28,8 @@ export declare class SunspecModbusClient {
29
28
  private faultTolerantReader;
30
29
  private modbusDataTypeConverter;
31
30
  private connectionParams;
32
- private retryManager;
33
31
  private autoReconnectEnabled;
34
- constructor(energyApp: EnergyApp, retryConfig?: Partial<IRetryConfig>);
32
+ constructor(energyApp: EnergyApp);
35
33
  /**
36
34
  * Connect to Modbus device
37
35
  * @param host Primary host (hostname) to connect to
@@ -55,11 +53,6 @@ export declare class SunspecModbusClient {
55
53
  * Returns true if successful, false otherwise
56
54
  */
57
55
  private attemptConnection;
58
- /**
59
- * Check connection health and trigger automatic reconnection if unhealthy
60
- * Returns true if connection is healthy or was successfully restored
61
- */
62
- ensureHealthyConnection(): Promise<boolean>;
63
56
  /**
64
57
  * Enable or disable automatic reconnection
65
58
  */
@@ -68,10 +61,6 @@ export declare class SunspecModbusClient {
68
61
  * Check if auto-reconnect is enabled
69
62
  */
70
63
  isAutoReconnectEnabled(): boolean;
71
- /**
72
- * Get the retry manager for advanced configuration
73
- */
74
- getRetryManager(): ConnectionRetryManager;
75
64
  /**
76
65
  * Detect the base address and addressing mode (0-based or 1-based) for SunSpec
77
66
  */
@@ -119,13 +108,13 @@ export declare class SunspecModbusClient {
119
108
  /**
120
109
  * Helper to read register value(s) using the fault-tolerant reader with data type conversion
121
110
  */
122
- private readRegisterValue;
111
+ readRegisterValue(address: number, quantity: number | undefined, dataType: EnergyAppModbusDataType): Promise<number | string | number[]>;
123
112
  /**
124
113
  * Helper to write register value(s)
125
114
  */
126
- private writeRegisterValue;
115
+ writeRegisterValue(address: number, value: number | number[], dataType?: EnergyAppModbusDataType): Promise<boolean>;
127
116
  /**
128
- * Read inverter data from Model 103 (Three Phase)
117
+ * Read inverter data from Model 101 (Single Phase) / Model 103 (Three Phase)
129
118
  */
130
119
  readInverterData(): Promise<SunspecInverterData | null>;
131
120
  /**
@@ -140,11 +129,10 @@ export declare class SunspecModbusClient {
140
129
  * Apply scale factor to a value
141
130
  * Returns undefined if the value is unimplemented or scale factor is out of range
142
131
  */
143
- private applyScaleFactor;
144
- private logRegisterRead;
132
+ applyScaleFactor(value: number, scaleFactor: number, dataType?: 'uint16' | 'int16' | 'acc32', fieldName?: string, offset?: number, modelId?: number): number | undefined;
133
+ logRegisterRead(modelId: number, offset: number, fieldName: string, rawValue: number | string | undefined, dataType?: string): void;
145
134
  /**
146
- * Read MPPT Scale Factors for a specific module
147
- * Returns the scale factors for DC Current, DC Voltage, DC Power, and DC Energy
135
+ * Read scale factors from Model 160 (MPPT)
148
136
  *
149
137
  * MPPT Model 160 Scale Factor Register Offsets (relative to module start):
150
138
  * - DCA_SF (Current Scale Factor): Offset 2
@@ -165,7 +153,7 @@ export declare class SunspecModbusClient {
165
153
  */
166
154
  readMPPTData(moduleId?: number): Promise<SunspecMPPTData | null>;
167
155
  /**
168
- * Read all available MPPT strings
156
+ * Read all MPPT strings from Model 160 (Multiple MPPT)
169
157
  */
170
158
  readAllMPPTData(): Promise<SunspecMPPTData[]>;
171
159
  /**
@@ -191,7 +179,7 @@ export declare class SunspecModbusClient {
191
179
  */
192
180
  readBatteryBaseData(): Promise<SunspecBatteryBaseData | null>;
193
181
  /**
194
- * Read battery data from Model 124 (Basic Storage Controls)
182
+ * Read battery data from Model 124 (Basic Storage) with fallback to Model 802 / Model 803
195
183
  */
196
184
  readBatteryData(): Promise<SunspecBatteryData | null>;
197
185
  /**
@@ -207,11 +195,11 @@ export declare class SunspecModbusClient {
207
195
  */
208
196
  enableGridCharging(enable: boolean): Promise<boolean>;
209
197
  /**
210
- * Read current battery control settings
198
+ * Read battery control settings from Model 124 (Basic Storage Controls)
211
199
  */
212
200
  readBatteryControls(): Promise<SunspecBatteryControls | null>;
213
201
  /**
214
- * Read meter data (Model 203 for 3-phase)
202
+ * Read meter data from Model 201 (Single Phase) / Model 203 (Three Phase) / Model 204 (Split Phase)
215
203
  */
216
204
  readMeterData(): Promise<SunspecMeterData | null>;
217
205
  /**
@@ -235,11 +223,11 @@ export declare class SunspecModbusClient {
235
223
  */
236
224
  getConnectionHealth(): IConnectionHealth;
237
225
  /**
238
- * Read Block 121 - Inverter Basic Settings
226
+ * Read inverter settings from Model 121 (Inverter Settings)
239
227
  */
240
228
  readInverterSettings(): Promise<SunspecInverterSettings | null>;
241
229
  /**
242
- * Read Block 123 - Immediate Inverter Controls
230
+ * Read inverter controls from Model 123 (Immediate Inverter Controls)
243
231
  */
244
232
  readInverterControls(): Promise<SunspecInverterControls | null>;
245
233
  /**
@@ -247,7 +235,18 @@ export declare class SunspecModbusClient {
247
235
  */
248
236
  writeInverterSettings(settings: Partial<SunspecInverterSettings>): Promise<boolean>;
249
237
  /**
250
- * Write Block 123 - Immediate Inverter Controls
238
+ * Write inverter controls to Model 123 (Immediate Inverter Controls)
251
239
  */
252
240
  writeInverterControls(controls: Partial<SunspecInverterControls>): Promise<boolean>;
241
+ /**
242
+ * Set the inverter feed-in power limit using Model 123 (Immediate Inverter Controls)
243
+ *
244
+ * When limitW is a number, reads WMax from Model 121, computes percentage,
245
+ * writes WMaxLimPct and enables WMaxLim_Ena.
246
+ * When limitW is null, disables the limit by setting WMaxLim_Ena = DISABLED.
247
+ *
248
+ * @param limitW - Power limit in Watts, or null to remove the limit
249
+ * @returns true if successful, false otherwise
250
+ */
251
+ setFeedInLimit(limitW: number | null): Promise<boolean>;
253
252
  }