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"), +});