diff --git a/.changeset/clear-seas-find.md b/.changeset/clear-seas-find.md
new file mode 100644
index 0000000..9ed6fd4
--- /dev/null
+++ b/.changeset/clear-seas-find.md
@@ -0,0 +1,6 @@
+---
+"@ecoflow-api/rest-client": minor
+"@ecoflow-api/schemas": minor
+---
+
+add support for River 2 Pro
diff --git a/AGENTS.md b/AGENTS.md
index 9491220..c77ba93 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -35,7 +35,7 @@ The `@ecoflow-api` project is a TypeScript-based monorepo. Its primary objective
│ │ ├── __fixtures__/ # Test fixtures
│ │ ├── __test__/ # Shared tests
│ │ ├── lib/
-│ │ │ ├── devices/ # Device implementations (Delta2, Glacier, PowerStream, etc.)
+│ │ │ ├── devices/ # Device implementations (Delta2, Glacier, PowerStream, River2Pro, etc.)
│ │ │ └── signatureBuilder/ # Request signature building logic
│ │ └── index.ts # Main export entrypoint
│ ├── schemas/ # Zod schemas and TypeScript types (@ecoflow-api/schemas)
@@ -44,6 +44,7 @@ The `@ecoflow-api` project is a TypeScript-based monorepo. Its primary objective
│ │ ├── deltaPro/ # Delta Pro schemas
│ │ ├── glacier/ # Glacier schemas
│ │ ├── powerStream/ # PowerStream schemas
+│ │ ├── river2Pro/ # River 2 Pro schemas
│ │ ├── shared/ # Shared/Common Zod schemas
│ │ ├── smartHomePanel/ # Smart Home Panel schemas
│ │ ├── smartPlug/ # Smart Plug schemas
diff --git a/api-docs/river2pro.md b/api-docs/river2pro.md
new file mode 100644
index 0000000..98ee2d7
--- /dev/null
+++ b/api-docs/river2pro.md
@@ -0,0 +1,257 @@
+# River 2 Pro
+
+## ModuleType definition
+
+| Field | Field's Type | Description |
+|------------|--------------|------------------------------------------------------|
+| moduleType | `int` | 1: PD
2: BMS
3: INV
4: BMS_SLAVE
5: MPPT |
+
+
+## HTTP communication mode
+
+### Set & Get Quota
+
+How to transfer request parameters by these two HTTP API.
+
+PUT: /iot-open/sign/device/quota: SetCmdRequest
+
+GET: /iot-open/sign/device/quota: GetCmdRequest, GetCmdResponse
+
+### MPPT
+
+| Set Command | SetCmdRequest | GetCmdRequest | GetCmdResponse |
+|----------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| 12V DC output on/off
(1 on, 0 off) | {
"sn": "R621ZEB1XE8S0029",
"id": 123,
"version": "1.0",
"moduleType": 5,
"operateType": "mpptCar",
"params": {
"enabled": 0
}
} | {
"sn": "R621ZEB1XE8S0029",
"params": {
"quotas": [
"pd.carState"
]
}
} | {
"code": "0",
"message": "Success",
"data": {
"pd.carState": 0
},
"eagleEyeTraceId": "",
"tid": ""
} |
+| AC output on/off (enabled)
X-Boost on/off
AC output voltage (read, unit V)
AC output frequency (out_freq, 1 = 50Hz, 2 = 60Hz) | {
"sn": "R621ZEB1XE8S0029",
"id": 126,
"version": "1.0",
"moduleType": 5,
"operateType": "acOutCfg",
"params": {
"enabled": 0,
"xboost": 0,
"out_voltage": 30,
"out_freq": 1
}
} | {
"sn": "R621ZEB1XE8S0029",
"params": {
"quotas": [
"mppt.cfgAcEnabled",
"mppt.cfgAcXboost",
"mppt.cfgAcOutVol",
"mppt.cfgAcOutFreq"
]
}
} | {
"code": "0",
"message": "Success",
"data": {
"mppt.cfgAcOutVol": 230,
"mppt.cfgAcXboost": 1,
"mppt.cfgAcEnabled": 1,
"mppt.cfgAcOutFreq": 50
},
"eagleEyeTraceId": "",
"tid": ""
} |
+
+### PD
+
+| Set Command | SetCmdRequest | GetCmdRequest | GetCmdResponse |
+|------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| Energy management on/off
isConfig: 0 off, 1 on
bpPowerSoc: backup reserve
minDsgSoc: discharge limit
minChgSoc: charge limit | {
"sn": "R621ZEB1XE8S0029",
"id": 126,
"version": "1.0",
"moduleType": 1,
"operateType": "watthConfig",
"params": {
"isConfig": 0,
"bpPowerSoc": 88,
"minDsgSoc": 30,
"minChgSoc": 88
}
} | {
"sn": "R621ZEB1XE8S0029",
"params": {
"quotas": [
"pd.watchIsConfig",
"pd.bpPowerSoc"
]
}
} | {
"code": "0",
"message": "Success",
"data": {
"pd.watchIsConfig": 0,
"pd.bpPowerSoc": 88
},
"eagleEyeTraceId": "",
"tid": ""
} |
+
+### BMS
+
+| Set Command | SetCmdRequest | GetCmdRequest | GetCmdResponse |
+|-----------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| Charge limit | {
"sn": "R621ZEB1XE8S0029",
"id": 126,
"version": "1.0",
"moduleType": 2,
"operateType": "upsConfig",
"params": {
"maxChgSoc": 88
}
} | {
"sn": "R621ZEB1XE8S0029",
"params": {
"quotas": [
"bms_emsStatus.maxChargeSoc"
]
}
} | {
"code": "0",
"message": "Success",
"data": {
"bms_emsStatus.maxChargeSoc": 88
},
"eagleEyeTraceId": "",
"tid": ""
} |
+| Discharge limit | {
"sn": "R621ZEB1XE8S0029",
"id": 126,
"version": "1.0",
"moduleType": 2,
"operateType": "dsgCfg",
"params": {
"minDsgSoc": 28
}
} | {
"sn": "R621ZEB1XE8S0029",
"params": {
"quotas": [
"bms_emsStatus.minDsgSoc"
]
}
} | {
"code": "0",
"message": "Success",
"data": {
"bms_emsStatus.minDsgSoc": 28
},
"eagleEyeTraceId": "",
"tid": ""
} |
+
+
+
+### GetAllQuotaResponse
+
+How to transfer request parameters by this HTTP API.
+
+GET: /iot-open/sign/device/quota/all: GetAllQuotaResponse
+
+| Field | Type | Description |
+|-------------------------------|-------|-------------------------------|
+| `pd.wattsOutSum` | `int` | Total output real-time power |
+| `pd.carWatts` | `int` | DC output real-time power |
+| `pd.usb1Watts` | `int` | USB-A output real-time power |
+| `pd.typecChaWatts` | `int` | USB-C input real-time power |
+| `pd.typec1Watts` | `int` | USB-C output real-time power |
+| `pd.soc` | `int` | Battery level |
+| `pd.carState` | `int` | 12V DC output on/off |
+| `pd.watchIsConfig` | `int` | Energy management on/off |
+| `pd.bpPowerSoc` | `int` | Backup reserve |
+| `bms_bmsStatus.remainTime` | `int` | Remaining charge time |
+| `bms_emsStatus.dsgRemainTime` | `int` | Battery time remaining |
+| `bms_emsStatus.maxChargeSoc` | `int` | Charge limit |
+| `bms_emsStatus.minDsgSoc` | `int` | Discharge limit |
+| `mppt.cfgAcEnabled` | `int` | AC output on/off, 1 on, 0 off |
+| `mppt.cfgAcXboost` | `int` | X-Boost on/off, 1 on, 0 off |
+| `mppt.cfgAcOutVol` | `int` | AC output voltage |
+| `mppt.cfgAcOutFreq` | `int` | AC output frequency |
+| `mppt.inWatts` | `int` | DC input real-time power |
+| `inv.inputWatts` | `int` | AC input real-time power |
+| `inv.outputWatts` | `int` | AC output real-time power |
+
+
+Example
+
+ {
+ "code": "0",
+ "message": "Success",
+ "data": {
+ "pd.usb1Watts": 0,
+ "bms_emsStatus.maxChargeSoc": 88,
+ "mppt.cfgAcXboost": 1,
+ "mppt.cfgAcEnabled": 1,
+ "pd.bpPowerSoc": 10,
+ "inv.inputWatts": 0,
+ "mppt.cfgAcOutVol": 230,
+ "inv.outputWatts": 0,
+ "pd.carWatts": 0,
+ "pd.watchIsConfig": 0,
+ "bms_emsStatus.minDsgSoc": 28,
+ "pd.typecChaWatts": 0,
+ "pd.typec1Watts": 0,
+ "bms_bmsStatus.remainTime": 0,
+ "pd.soc": 29,
+ "mppt.inWatts": 0,
+ "pd.carState": 0,
+ "pd.wattsOutSum": 0,
+ "mppt.cfgAcOutFreq": 50,
+ "bms_emsStatus.dsgRemainTime": 5999
+ },
+ "eagleEyeTraceId": "",
+ "tid": ""
+ }
+
+## MQTT communication mode
+
+### Set & Set Reply
+
+| Usage of Topic | Topic | From | To |
+|:------------------------|:----------------------------------------------|:-------|:-------|
+| Set device function | `/open/${certificateAccount}/${sn}/set` | app | device |
+| Reply to the set result | `/open/${certificateAccount}/${sn}/set_reply` | device | app |
+
+#### Full Format Example
+
+| Set Data Format | Set Reply Data Format |
+|:----------------------------------------------------------------------------------------------------------|:------------------------------------------------------------------------------------------------------------------------------|
+| `{"id": 123, "version": "1.0", "moduleType": 2, "operateType": "upsConfig", "params": {"maxChgSoc": 20}}` | `{"code": "0", "moduleType": 2, "data": {"ack": 0}, "operateType": "upsConfig", "version": "1.0", "id": 123, "time": 917260}` |
+
+ParamInfo Message Format
+
+### MPPT
+
+| Set Command | SetTopic's ParamInfo | SetReplyTopic's ParamInfo | Observation Indicator |
+|:-----------------------------------------------------------------------------------------------------------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------|:----------------------------------------------------------------------------------------------------------------------------------|:----------------------|
+| 12V DC output on/off (1 on, 0 off) | `{"id": 123456789, "version": "1.0", "moduleType": 5, "operateType": "mpptCar", "params": {"enabled": 1}}` | `{"code": "0", "moduleType": 5, "data": {"ack": 0}, "operateType": "mpptCar", "version": "1.0", "id": 123456789, "time": 592150}` | `mpptCar` |
+| AC output on/off (enabled)
X-Boost on/off
AC output voltage (read, unit V)
AC output frequency (out_freq, 1-50Hz, 2-60Hz) | `{"id": 126, "version": "1.0", "moduleType": 5, "operateType": "acOutCfg", "params": {"enabled": 1, "xboost": 1, "out_voltage": 150, "out_freq": 1}}` | `{"code": "0", "moduleType": 5, "data": {"ack": 0}, "operateType": "acOutCfg", "version": "1.0", "id": 126, "time": 765630}` | `Enabled`, `xboost` |
+
+### PD
+
+| Set Command | SetTopic's ParamInfo | SetReplyTopic's ParamInfo | Observation Indicator |
+|:---------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------------------------------------------------------------------------------------------------------------------------------|:-------------------------|
+| Energy management on/off (isConfig: 0 off 1 on; bpPowerSoc: Backup reserve; minDsgSoc: Discharge limit (Not used yet); minChgSoc: Charge limit (Not used yet)) | `{"id": 126, "version": "1.0", "moduleType": 1, "operateType": "watthConfig", "params": {"isConfig": 1, "bpPowerSoc": 50, "minDsgSoc": 30, "minChgSoc": 88}}` | `{"code": "0", "moduleType": 1, "data": {"ack": 0}, "operateType": "watthConfig", "version": "1.0", "id": 126, "time": 859360}` | `isConfig`, `bpPowerSoc` |
+
+### BMS
+
+| Set Command | SetTopic's ParamInfo | SetReplyTopic's ParamInfo | Observation Indicator |
+|:----------------|:----------------------------------------------------------------------------------------------------------|:------------------------------------------------------------------------------------------------------------------------------|:----------------------|
+| Charge limit | `{"id": 126, "version": "1.0", "moduleType": 2, "operateType": "upsConfig", "params": {"maxChgSoc": 90}}` | `{"code": "0", "moduleType": 2, "data": {"ack": 0}, "operateType": "upsConfig", "version": "1.0", "id": 126, "time": 917260}` | `maxChgSoc` |
+| Discharge limit | `{"id": 126, "version": "1.0", "moduleType": 2, "operateType": "dsgCfg", "params": {"minDsgSoc": 30}}` | `{"code": "0", "moduleType": 2, "data": {"ack": 0}, "operateType": "dsgCfg", "version": "1.0", "id": 126, "time": 900670}` | `minDsgSoc` |
+
+### Report Device Quota
+
+| Usage of Topic | Topic | From | To |
+|:--------------------|:------------------------------------------|:-------|:----|
+| Report device quota | `/open/${certificateAccount}/${sn}/quota` | device | app |
+
+Please see HTTP GetAllQuotaResponse and Set & Set Reply for the fields' definition.
+
+Example
+
+#### PD
+
+ {
+ "moduleType": 1,
+ "needAck": 0,
+ "id": 2935860,
+ "time": 2935860,
+ "params": {
+ "wattsOutSum": 0,
+ "carWatts": 0,
+ "usb1Watts": 0,
+ "typecChaWatts": 0,
+ "typec1Watts": 0,
+ "soc": 28,
+ "carState": 0,
+ "watchIsConfig": 1,
+ "bpPowerSoc": 50
+ },
+ "version": "1.0",
+ "typeCode": "pdStatus"
+ }
+
+#### BMS-bmsStatus
+
+ {
+ "moduleType": 2,
+ "needAck": 1,
+ "id": 571200,
+ "time": 571200,
+ "params": {
+ "remainTime": 0
+ },
+ "version": "1.0",
+ "typeCode": "bmsStatus"
+ }
+
+#### BMS-emsStatus
+
+ {
+ "moduleType": 2,
+ "needAck": 1,
+ "id": 646260,
+ "time": 646260,
+ "params": {
+ "maxChargeSoc": 90,
+ "dsgRemainTime": 5999,
+ "minDsgSoc": 30
+ },
+ "version": "1.0",
+ "typeCode": "emsStatus"
+ }
+
+#### MPPT
+
+ {
+ "moduleType": 5,
+ "needAck": 0,
+ "id": 3430090,
+ "time": 3430090,
+ "params": {
+ "cfgAcEnabled": 0,
+ "cfgAcXboost": 0,
+ "cfgAcOutVol": 230,
+ "cfgAcOutFreq": 50,
+ "inWatts": 0
+ },
+ "version": "1.0",
+ "typeCode": "mpptStatus"
+ }
+
+#### INV
+
+ {
+ "moduleType": 3,
+ "needAck": 0,
+ "id": 24620,
+ "time": 24620,
+ "params": {
+ "outputWatts": 0,
+ "inputWatts": 0
+ },
+ "version": "1.0",
+ "typeCode": "invStatus"
+ }
+
+
+### Report Device status
+
+| Usage of Topic | Topic | From | To |
+|----------------------|------------------------------------------|--------|-----|
+| Report Device status | /open/${certificateAccount}/${sn}/status | device | app |
+
+Message Format
+
+ {
+ "id": "268020839",
+ "params": {
+ "status": 0
+ },
+ "timestamp": 1755833679618,
+ "version": "1.0"
+ }
+
+Field
+
+| Field | Field Type | Description |
+|--------|------------|---------------------------------------|
+| status | int | Device online or not
0: No, 1: Yes |
diff --git a/packages/rest-client/README.md b/packages/rest-client/README.md
index 0eb0158..d7cb80e 100644
--- a/packages/rest-client/README.md
+++ b/packages/rest-client/README.md
@@ -26,13 +26,15 @@ Depending on the serial number of the device, the client will return a specific
At the moment the following devices are supported with specific instances:
-- Smart Plug
-- PowerStream
- Delta 2
-- Glacier
-- Smart Home Panel
- Delta Pro
- Delta Pro 3
+- Glacier
+- PowerStream
+- River 2 Pro
+- Smart Home Panel
+- SmartPlug
+- Wave Air Conditioner
More devices to come.
diff --git a/packages/rest-client/src/lib/devices/DeviceFactory.test.ts b/packages/rest-client/src/lib/devices/DeviceFactory.test.ts
index 35d1208..4225669 100644
--- a/packages/rest-client/src/lib/devices/DeviceFactory.test.ts
+++ b/packages/rest-client/src/lib/devices/DeviceFactory.test.ts
@@ -9,6 +9,7 @@ import { Glacier } from "./Glacier";
import { SmartHomePanel } from "./SmartHomePanel";
import { DeltaPro } from "./DeltaPro";
import { DeltaPro3 } from "./DeltaPro3";
+import { River2Pro } from "./River2Pro";
describe("deviceFactory", () => {
let restClient: RestClient;
@@ -74,4 +75,11 @@ describe("deviceFactory", () => {
expect(device).toBeInstanceOf(DeltaPro3);
expect(device.sn).toBe(sn);
});
+
+ it("returns a River 2 Pro instance", () => {
+ const sn = "R621*****";
+ const device = deviceFactory(sn, restClient);
+ expect(device).toBeInstanceOf(River2Pro);
+ expect(device.sn).toBe(sn);
+ });
});
diff --git a/packages/rest-client/src/lib/devices/DeviceFactory.ts b/packages/rest-client/src/lib/devices/DeviceFactory.ts
index b75b683..d6b5229 100644
--- a/packages/rest-client/src/lib/devices/DeviceFactory.ts
+++ b/packages/rest-client/src/lib/devices/DeviceFactory.ts
@@ -8,9 +8,11 @@ import {
isDeltaProSerialNumber,
isGlacierSerialNumber,
isPowerStreamSerialNumber,
+ isRiver2ProSerialNumber,
isSmartHomePanelSerialNumber,
isSmartPlugSn,
PowerStreamSerialNumber,
+ River2ProSerialNumber,
SmartHomePanelSerialNumber,
SmartPlugSn,
} from "@ecoflow-api/schemas";
@@ -23,6 +25,7 @@ import { Glacier } from "./Glacier";
import { SmartHomePanel } from "./SmartHomePanel";
import { DeltaPro } from "./DeltaPro";
import { DeltaPro3 } from "./DeltaPro3";
+import { River2Pro } from "./River2Pro";
// prettier-ignore
export type DeviceFactoryReturnType =
@@ -33,6 +36,7 @@ export type DeviceFactoryReturnType =
T extends SmartHomePanelSerialNumber ? SmartHomePanel :
T extends DeltaProSerialNumber ? DeltaPro :
T extends DeltaPro3SerialNumber ? DeltaPro3 :
+ T extends River2ProSerialNumber ? River2Pro :
UnknownDevice;
/**
@@ -72,5 +76,9 @@ export function deviceFactory>(
return new DeltaPro3(restClient, sn) as R;
}
+ if (isRiver2ProSerialNumber(sn)) {
+ return new River2Pro(restClient, sn) as R;
+ }
+
return new UnknownDevice(restClient, sn) as R;
}
diff --git a/packages/rest-client/src/lib/devices/River2Pro.test.ts b/packages/rest-client/src/lib/devices/River2Pro.test.ts
new file mode 100644
index 0000000..f0c5e18
--- /dev/null
+++ b/packages/rest-client/src/lib/devices/River2Pro.test.ts
@@ -0,0 +1,156 @@
+import { beforeEach, describe, expect, it, jest } from "@jest/globals";
+import { RestClient, RestClientOptions } from "../RestClient";
+import { River2Pro } from "./River2Pro";
+
+describe("River2Pro", () => {
+ let river2Pro: River2Pro;
+ let restClient: RestClient;
+ const validSn = "R621xxxx";
+
+ beforeEach(() => {
+ const restClientOptions: RestClientOptions = {
+ host: "https://api-a.ecoflow.com",
+ accessKey: "fake_access",
+ secretKey: "fake_secret",
+ };
+
+ restClient = new RestClient(restClientOptions);
+ river2Pro = new River2Pro(restClient, validSn);
+
+ restClient.setCommandPlain = jest.fn();
+ });
+
+ describe("constructor", () => {
+ it("accepts a valid River2Pro serial number", () => {
+ expect.assertions(1);
+
+ expect(() => new River2Pro(restClient, validSn)).not.toThrow();
+ });
+
+ it("throws for an invalid River2Pro serial number", () => {
+ expect.assertions(1);
+
+ expect(() => new River2Pro(restClient, "R331xxxx" as never)).toThrowError(
+ "Invalid serial number for River 2 Pro device.",
+ );
+ });
+ });
+
+ describe(".enable12Vdc()", () => {
+ it("sets 12V DC output", async () => {
+ expect.assertions(1);
+ await river2Pro.enable12Vdc(1);
+
+ expect(restClient.setCommandPlain).toHaveBeenCalledWith({
+ id: 123456789,
+ version: "1.0",
+ moduleType: 5,
+ operateType: "mpptCar",
+ params: {
+ enabled: 1,
+ },
+ sn: validSn,
+ });
+ });
+ });
+
+ describe(".setAcOutCfg()", () => {
+ it("sets AC output configuration", async () => {
+ expect.assertions(1);
+ await river2Pro.setAcOutCfg({
+ enabled: 1,
+ xboost: 1,
+ out_voltage: 30,
+ out_freq: 50,
+ });
+
+ expect(restClient.setCommandPlain).toHaveBeenCalledWith({
+ id: 123456789,
+ version: "1.0",
+ moduleType: 5,
+ operateType: "acOutCfg",
+ params: {
+ enabled: 1,
+ xboost: 1,
+ out_voltage: 30,
+ out_freq: 50,
+ },
+ sn: validSn,
+ });
+ });
+ });
+
+ describe(".setWatthConfigSchema()", () => {
+ it("sets energy management configuration", async () => {
+ expect.assertions(1);
+ await river2Pro.setWatthConfigSchema({
+ isConfig: 1,
+ bpPowerSoc: 88,
+ minDsgSoc: 30,
+ minChgSoc: 88,
+ });
+
+ expect(restClient.setCommandPlain).toHaveBeenCalledWith({
+ id: 123456789,
+ version: "1.0",
+ moduleType: 1,
+ operateType: "watthConfig",
+ params: {
+ isConfig: 1,
+ bpPowerSoc: 88,
+ minDsgSoc: 30,
+ minChgSoc: 88,
+ },
+ sn: validSn,
+ });
+ });
+ });
+
+ describe(".setChargeLimit()", () => {
+ it("sets charge limit", async () => {
+ expect.assertions(1);
+ await river2Pro.setChargeLimit(50);
+
+ expect(restClient.setCommandPlain).toHaveBeenCalledWith({
+ id: 123456789,
+ version: "1.0",
+ moduleType: 2,
+ operateType: "upsConfig",
+ params: {
+ maxChgSoc: 50,
+ },
+ sn: validSn,
+ });
+ });
+
+ it("throws for invalid charge limit values", async () => {
+ expect.assertions(2);
+ await expect(river2Pro.setChargeLimit(101)).rejects.toThrowError();
+ await expect(river2Pro.setChargeLimit(-1)).rejects.toThrowError();
+ });
+ });
+
+ describe(".setDischargeLimit()", () => {
+ it("sets discharge limit", async () => {
+ expect.assertions(1);
+ await river2Pro.setDischargeLimit(40);
+
+ expect(restClient.setCommandPlain).toHaveBeenCalledWith({
+ id: 123456789,
+ version: "1.0",
+ moduleType: 2,
+ operateType: "dsgCfg",
+ params: {
+ minDsgSoc: 40,
+ },
+ sn: validSn,
+ });
+ });
+
+ it("throws for invalid discharge limit values", async () => {
+ expect.assertions(2);
+ await expect(river2Pro.setDischargeLimit(101)).rejects.toThrowError();
+ await expect(river2Pro.setDischargeLimit(-1)).rejects.toThrowError();
+ });
+ });
+});
diff --git a/packages/rest-client/src/lib/devices/River2Pro.ts b/packages/rest-client/src/lib/devices/River2Pro.ts
new file mode 100644
index 0000000..fd7578f
--- /dev/null
+++ b/packages/rest-client/src/lib/devices/River2Pro.ts
@@ -0,0 +1,189 @@
+import {
+ AcOutCfg,
+ acOutCfgSchema,
+ ChargeLimit,
+ DischargeLimit,
+ isRiver2ProSerialNumber,
+ MpptCar,
+ mpptCarSchema,
+ River2ProQuotaAll,
+ river2ProQuotaAllSchema,
+ River2ProSerialNumber,
+ WatthConfig,
+ watthConfigSchema,
+} from "@ecoflow-api/schemas";
+import { Device } from "./Device";
+import { RestClient } from "../RestClient";
+import {
+ chargeLimitSchema,
+ dischargeLimitSchema,
+} from "@ecoflow-api/schemas/src/river2Pro/setCommands/bms";
+
+export class River2Pro extends Device<
+ River2ProSerialNumber,
+ River2ProQuotaAll
+> {
+ constructor(restClient: RestClient, sn: River2ProSerialNumber) {
+ super(restClient, sn);
+
+ if (!isRiver2ProSerialNumber(sn)) {
+ throw new Error("Invalid serial number for River 2 Pro device.");
+ }
+ }
+
+ protected parseProperties(data: any) {
+ return river2ProQuotaAllSchema.parse(data);
+ }
+
+ #payloadDefaults(
+ moduleType: T,
+ ): {
+ moduleType: T;
+ id: number;
+ version: "1.0";
+ sn: River2ProSerialNumber;
+ } {
+ return {
+ moduleType,
+ id: 123456789,
+ version: "1.0",
+ sn: this.sn,
+ };
+ }
+
+ /**************+********************************************************
+ * MPPT Commands
+ **************+********************************************************/
+
+ /**
+ * 12V DC output on/off(1 on 0 off)
+ *
+ * @example
+ * ```typescript
+ * const client = new RestClient(...);
+ * const device = client.getDevice(sn);
+ * await device.setCarStandByDuration(1);
+ * ```
+ */
+ async enable12Vdc(enabled: MpptCar["params"]["enabled"]) {
+ const payload: MpptCar = {
+ ...this.#payloadDefaults(5),
+ operateType: "mpptCar",
+ params: {
+ enabled,
+ },
+ };
+
+ return this.sendCommand(payload, mpptCarSchema);
+ }
+
+ /**
+ * AC output on/off(enabled)
+ * X-Boost on/off
+ * AC output voltage,read,unit V
+ * AC output frequency(out_freq,1-50Hz、2-60Hz)
+ *
+ * @example
+ * ```typescript
+ * const client = new RestClient(...);
+ * const device = client.getDevice(sn);
+ * await device.setAcOutCfg({
+ * enabled: 1,
+ * xboost: 1,
+ * out_voltage: 30,
+ * out_freq: 50,
+ * });
+ * ```
+ */
+ async setAcOutCfg(params: AcOutCfg["params"]) {
+ const payload: AcOutCfg = {
+ ...this.#payloadDefaults(5),
+ operateType: "acOutCfg",
+ params,
+ };
+
+ return this.sendCommand(payload, acOutCfgSchema);
+ }
+
+ /**************+********************************************************
+ * PD Commands
+ **************+********************************************************/
+
+ /**
+ * Energy management on/off
+ *
+ * @param params.isConfig - 0: off; 1: on
+ * @param params.bpPowerSoc - Backup reserve
+ * @param params.minDsgSoc - Discharge limit
+ * @param params.minChgSoc - Charge limit
+ *
+ * @example
+ * ```typescript
+ * const client = new RestClient(...);
+ * const device = client.getDevice(sn);
+ * await device.setWatthConfigSchema({
+ * "isConfig": 0,
+ * "bpPowerSoc": 88,
+ * "minDsgSoc": 30,
+ * "minChgSoc": 88
+ * });
+ * ```
+ */
+ async setWatthConfigSchema(params: WatthConfig["params"]) {
+ const payload: WatthConfig = {
+ ...this.#payloadDefaults(1),
+ operateType: "watthConfig",
+ params,
+ };
+
+ return this.sendCommand(payload, watthConfigSchema);
+ }
+
+ /**************+********************************************************
+ * BMS Commands
+ **************+********************************************************/
+
+ /**
+ * Set the charge limit.
+ * @param limit
+ *
+ * @example
+ * ```typescript
+ * const client = new RestClient(...);
+ * const device = client.getDevice(sn);
+ * await device.setChargeLimit(50);
+ */
+ async setChargeLimit(limit: ChargeLimit["params"]["maxChgSoc"]) {
+ const payload: ChargeLimit = {
+ ...this.#payloadDefaults(2),
+ operateType: "upsConfig",
+ params: {
+ maxChgSoc: limit,
+ },
+ };
+
+ return this.sendCommand(payload, chargeLimitSchema);
+ }
+
+ /**
+ * Set the discharge limit.
+ * @param limit
+ *
+ * @example
+ * ```typescript
+ * const client = new RestClient(...);
+ * const device = client.getDevice(sn);
+ * await device.setDischargeLimit(50);
+ */
+ async setDischargeLimit(limit: DischargeLimit["params"]["minDsgSoc"]) {
+ const payload: DischargeLimit = {
+ ...this.#payloadDefaults(2),
+ operateType: "dsgCfg",
+ params: {
+ minDsgSoc: limit,
+ },
+ };
+
+ return this.sendCommand(payload, dischargeLimitSchema);
+ }
+}
diff --git a/packages/schemas/README.md b/packages/schemas/README.md
index 14a3404..f9bb086 100644
--- a/packages/schemas/README.md
+++ b/packages/schemas/README.md
@@ -4,18 +4,26 @@ Types and schemas for Ecoflow API as documented in https://developer-eu.ecoflow.
## Provided devices
-- PowerStream
-- SmartPlug
- Delta 2
+- Delta Pro
+- Delta Pro 3
- Glacier
-- Wave Air Conditioner
+- PowerStream
+- River 2 Pro
- Smart Home Panel
-- Delta pro
+- SmartPlug
+- Wave Air Conditioner
## Todo
- Delta 2 Max
- Power Kits
+- Smart Home Panel II
+- STREAM
+- Delta Pro Ultra
+- Delta 3 Max Plus
+- Delta 3 Max
+- PowerOcean
## Disclaimer
@@ -25,4 +33,4 @@ Use of the software is at your own risk and discretion,
and I assume no liability for any potential damages or issues that may arise from using the software.
It is important to be aware that using this open-source software comes
-without direct support or guarantees from the company Ecoflow.
\ No newline at end of file
+without direct support or guarantees from the company Ecoflow.
diff --git a/packages/schemas/src/index.ts b/packages/schemas/src/index.ts
index 01f0bc3..f13fd47 100644
--- a/packages/schemas/src/index.ts
+++ b/packages/schemas/src/index.ts
@@ -7,3 +7,4 @@ export * from "./smartHomePanel";
export * from "./deltaPro";
export * from "./shared";
export * from "./deltaPro3";
+export * from "./river2Pro";
diff --git a/packages/schemas/src/river2Pro/getProperties.ts b/packages/schemas/src/river2Pro/getProperties.ts
new file mode 100644
index 0000000..299a1e6
--- /dev/null
+++ b/packages/schemas/src/river2Pro/getProperties.ts
@@ -0,0 +1,68 @@
+import { z } from "zod";
+import { integer, zeroOrOne } from "../shared";
+
+export const river2ProQuotaAllSchema = z
+ .object({
+ /**
+ * Battery Management System
+ */
+
+ // Remaining charge time.
+ "bms_bmsStatus.remainTime": integer,
+ // Battery time remaining.
+ "bms_emsStatus.dsgRemainTime": integer,
+ // Charge limit.
+ "bms_emsStatus.maxChargeSoc": z.number().min(0).max(100),
+ // Discharge limit.
+ "bms_emsStatus.minDsgSoc": z.number().min(0).max(100),
+
+ /**
+ * Inverter
+ */
+
+ // AC input real-time power.
+ "inv.inputWatts": integer,
+ // AC output real-time power.
+ "inv.outputWatts": integer,
+
+ /**
+ * MPPT
+ */
+
+ // AC output on/off: 1 on, 0 off.
+ "mppt.cfgAcEnabled": zeroOrOne,
+ // X-Boost on/off: 1 on, 0 off.
+ "mppt.cfgAcXboost": zeroOrOne,
+ // AC output voltage.
+ "mppt.cfgAcOutVol": integer,
+ // AC output frequency.
+ "mppt.cfgAcOutFreq": integer,
+ // DC input real-time power.
+ "mppt.inWatts": integer,
+
+ /**
+ * PD Module
+ */
+
+ // Total output real-time power.
+ "pd.wattsOutSum": integer,
+ // DC output real-time power.
+ "pd.carWatts": integer,
+ // USB-A output real-time power.
+ "pd.usb1Watts": integer,
+ // USB-C input real-time power.
+ "pd.typecChaWatts": integer,
+ // USB-C output real-time power.
+ "pd.typec1Watts": integer,
+ // Battery level.
+ "pd.soc": z.number().min(0).max(100),
+ // 12V DC output on/off.
+ "pd.carState": zeroOrOne,
+ // Energy management on/off.
+ "pd.watchIsConfig": zeroOrOne,
+ // Backup reserve.
+ "pd.bpPowerSoc": integer,
+ })
+ .passthrough();
+
+export type River2ProQuotaAll = z.infer;
diff --git a/packages/schemas/src/river2Pro/index.ts b/packages/schemas/src/river2Pro/index.ts
new file mode 100644
index 0000000..d40cd65
--- /dev/null
+++ b/packages/schemas/src/river2Pro/index.ts
@@ -0,0 +1,3 @@
+export * from "./serialNumber";
+export * from "./getProperties";
+export * from "./setCommands";
diff --git a/packages/schemas/src/river2Pro/serialNumber.ts b/packages/schemas/src/river2Pro/serialNumber.ts
new file mode 100644
index 0000000..b00f4ff
--- /dev/null
+++ b/packages/schemas/src/river2Pro/serialNumber.ts
@@ -0,0 +1,18 @@
+import { z } from "zod";
+
+/*********************************************
+ * Serial number
+ * River 2 Pro serial number to start with "R621"
+ *********************************************/
+
+export const river2ProSerialNumberSchema = z.custom<`R621${string}`>((val) => {
+ return typeof val === "string" ? val.startsWith("R621") : false;
+});
+
+export type River2ProSerialNumber = z.infer;
+
+export const isRiver2ProSerialNumber = (
+ x: unknown,
+): x is River2ProSerialNumber => {
+ return river2ProSerialNumberSchema.safeParse(x).success;
+};
diff --git a/packages/schemas/src/river2Pro/setCommands/bms.ts b/packages/schemas/src/river2Pro/setCommands/bms.ts
new file mode 100644
index 0000000..a793cd9
--- /dev/null
+++ b/packages/schemas/src/river2Pro/setCommands/bms.ts
@@ -0,0 +1,31 @@
+import { z } from "zod";
+import { defaultSchema } from "./shared";
+import { integer } from "../../shared";
+
+const bmsSchema = defaultSchema.extend({
+ moduleType: z.literal(2),
+});
+
+/**
+ * Charge limit
+ */
+export const chargeLimitSchema = bmsSchema.extend({
+ operateType: z.literal("upsConfig"),
+ params: z.object({
+ maxChgSoc: integer.min(0).max(100),
+ }),
+});
+
+export type ChargeLimit = z.infer;
+
+/**
+ * Discharge limit
+ */
+export const dischargeLimitSchema = bmsSchema.extend({
+ operateType: z.literal("dsgCfg"),
+ params: z.object({
+ minDsgSoc: integer.min(0).max(100),
+ }),
+});
+
+export type DischargeLimit = z.infer;
diff --git a/packages/schemas/src/river2Pro/setCommands/index.ts b/packages/schemas/src/river2Pro/setCommands/index.ts
new file mode 100644
index 0000000..b14898b
--- /dev/null
+++ b/packages/schemas/src/river2Pro/setCommands/index.ts
@@ -0,0 +1,3 @@
+export * from "./mppt";
+export * from "./pd";
+export * from "./bms";
diff --git a/packages/schemas/src/river2Pro/setCommands/mppt.ts b/packages/schemas/src/river2Pro/setCommands/mppt.ts
new file mode 100644
index 0000000..779ebc3
--- /dev/null
+++ b/packages/schemas/src/river2Pro/setCommands/mppt.ts
@@ -0,0 +1,37 @@
+import { z } from "zod";
+import { defaultSchema } from "./shared";
+import { integer, zeroOrOne } from "../../shared";
+
+const mpptSchema = defaultSchema.extend({
+ moduleType: z.literal(5),
+});
+
+/**
+ * 12V DC output on/off(1 on 0 off)
+ */
+export const mpptCarSchema = mpptSchema.extend({
+ operateType: z.literal("mpptCar"),
+ params: z.object({
+ enabled: zeroOrOne,
+ }),
+});
+
+export type MpptCar = z.infer;
+
+/**
+ * AC output on/off(enabled)
+ * X-Boost on/off
+ * AC output voltage,read,unit V
+ * AC output frequency(out_freq,1-50Hz、2-60Hz)
+ */
+export const acOutCfgSchema = mpptSchema.extend({
+ operateType: z.literal("acOutCfg"),
+ params: z.object({
+ enabled: zeroOrOne,
+ xboost: zeroOrOne,
+ out_voltage: integer.min(0),
+ out_freq: integer.min(1).max(60),
+ }),
+});
+
+export type AcOutCfg = z.infer;
diff --git a/packages/schemas/src/river2Pro/setCommands/pd.ts b/packages/schemas/src/river2Pro/setCommands/pd.ts
new file mode 100644
index 0000000..5d3cc3e
--- /dev/null
+++ b/packages/schemas/src/river2Pro/setCommands/pd.ts
@@ -0,0 +1,22 @@
+import { z } from "zod";
+import { defaultSchema } from "./shared";
+import { integer, zeroOrOne } from "../../shared";
+
+/**
+ * Energy management on/off (isConfig: 0 off 1 on;
+ * bpPowerSoc:Backup reserve;
+ * minDsgSoc:Discharge limit
+ * minChgSoc:Charge limit
+ */
+export const watthConfigSchema = defaultSchema.extend({
+ moduleType: z.literal(1),
+ operateType: z.literal("watthConfig"),
+ params: z.object({
+ isConfig: zeroOrOne,
+ bpPowerSoc: integer.min(0).max(100),
+ minDsgSoc: integer.min(0).max(100),
+ minChgSoc: integer.min(0).max(100),
+ }),
+});
+
+export type WatthConfig = z.infer;
diff --git a/packages/schemas/src/river2Pro/setCommands/shared.ts b/packages/schemas/src/river2Pro/setCommands/shared.ts
new file mode 100644
index 0000000..8072085
--- /dev/null
+++ b/packages/schemas/src/river2Pro/setCommands/shared.ts
@@ -0,0 +1,9 @@
+import { z } from "zod";
+import { river2ProSerialNumberSchema } from "../serialNumber";
+import { integer } from "../../shared";
+
+export const defaultSchema = z.object({
+ sn: river2ProSerialNumberSchema,
+ id: integer,
+ version: z.literal("1.0"),
+});