From 1363f91d89ad83f0a79c06675811a14df664e8cc Mon Sep 17 00:00:00 2001 From: Felix Hofmann Date: Fri, 3 Jul 2026 21:16:33 +0200 Subject: [PATCH 1/3] refactor: rename `upsLowerLimitSchema` to `minDischargeSocSchema` and deprecate old schema --- .changeset/short-sites-strive.md | 5 ++++ .../schemas/src/delta2/setCommands/bms.ts | 27 ++++++++++++++++--- 2 files changed, 28 insertions(+), 4 deletions(-) create mode 100644 .changeset/short-sites-strive.md diff --git a/.changeset/short-sites-strive.md b/.changeset/short-sites-strive.md new file mode 100644 index 00000000..de9bf8c6 --- /dev/null +++ b/.changeset/short-sites-strive.md @@ -0,0 +1,5 @@ +--- +"@ecoflow-api/schemas": minor +--- + +refactor: rename `upsLowerLimitSchema` to `minDischargeSocSchema` and deprecate old schema diff --git a/packages/schemas/src/delta2/setCommands/bms.ts b/packages/schemas/src/delta2/setCommands/bms.ts index b577bebe..aa63afee 100644 --- a/packages/schemas/src/delta2/setCommands/bms.ts +++ b/packages/schemas/src/delta2/setCommands/bms.ts @@ -48,14 +48,33 @@ export type UpsUpperLimit = z.infer; * } * ``` */ -export const upsLowerLimitSchema = bmsCommandSchema.extend({ +export const minDischargeSocSchema = bmsCommandSchema.extend({ operateType: z.literal("dsgCfg"), params: z.object({ - minDsgSoc: z.number().positive(), + minDsgSoc: z.number().min(0).max(100), }), }); -export type UpsLowerLimit = z.infer; +export type MinDischargeSoc = z.infer; + +/** + * State of charge (SOC) lower limit when discharging + * ```json + * { + * "id":123456789, + * "version":"1.0", + * "sn":"R331XXXXXXX", + * "moduleType":2, + * "operateType":"dsgCfg", + * "params":{ "minDsgSoc":19 } + * } + * ``` + * @deprecated use `minDischargeSocSchema` instead + */ +export const upsLowerLimitSchema = minDischargeSocSchema; + +/** @deprecated use `minDischargeSocSchema` instead **/ +export type UpsLowerLimit = MinDischargeSoc; /** * SoC that triggers EMS to turn on Smart Generator @@ -105,7 +124,7 @@ export type SocTurnOffGenerator = z.infer; export const delta2BMSCommandSchema = z.discriminatedUnion("operateType", [ upsUpperLimitSchema, - upsLowerLimitSchema, + minDischargeSocSchema, socTurnOnGeneratorSchema, socTurnOffGeneratorSchema, ]); From a139e353e182a4c4173d44bce2212456eca2acfa Mon Sep 17 00:00:00 2001 From: Felix Hofmann Date: Fri, 3 Jul 2026 21:18:51 +0200 Subject: [PATCH 2/3] fix: update BMS schema constraints to enforce 0-100 range for SOC values --- .changeset/tangy-flowers-argue.md | 5 +++++ packages/schemas/src/delta2/setCommands/bms.ts | 6 +++--- 2 files changed, 8 insertions(+), 3 deletions(-) create mode 100644 .changeset/tangy-flowers-argue.md diff --git a/.changeset/tangy-flowers-argue.md b/.changeset/tangy-flowers-argue.md new file mode 100644 index 00000000..efc52431 --- /dev/null +++ b/.changeset/tangy-flowers-argue.md @@ -0,0 +1,5 @@ +--- +"@ecoflow-api/schemas": minor +--- + +fix: update BMS schema constraints to enforce 0-100 range for SOC values diff --git a/packages/schemas/src/delta2/setCommands/bms.ts b/packages/schemas/src/delta2/setCommands/bms.ts index aa63afee..2e41a8a1 100644 --- a/packages/schemas/src/delta2/setCommands/bms.ts +++ b/packages/schemas/src/delta2/setCommands/bms.ts @@ -29,7 +29,7 @@ const bmsCommandSchema = defaultSchema.extend({ export const upsUpperLimitSchema = bmsCommandSchema.extend({ operateType: z.literal("upsConfig"), params: z.object({ - maxChgSoc: z.number().positive(), + maxChgSoc: z.number().min(0).max(100), }), }); @@ -93,7 +93,7 @@ export type UpsLowerLimit = MinDischargeSoc; export const socTurnOnGeneratorSchema = bmsCommandSchema.extend({ operateType: z.literal("openOilSoc"), params: z.object({ - openOilSoc: z.number().positive(), + openOilSoc: z.number().min(0).max(100), }), }); @@ -116,7 +116,7 @@ export type SocTurnOnGenerator = z.infer; export const socTurnOffGeneratorSchema = bmsCommandSchema.extend({ operateType: z.literal("closeOilSoc"), params: z.object({ - closeOilSoc: z.number().positive(), + closeOilSoc: z.number().min(0).max(100), }), }); From 4b04c09e69b5e940515a9dc079cd54d89967f5a9 Mon Sep 17 00:00:00 2001 From: Felix Hofmann Date: Fri, 3 Jul 2026 21:20:21 +0200 Subject: [PATCH 3/3] feat: implement additional command methods for Delta2 device, including generator controls, AC charge/discharge settings, and UPS configuration --- .changeset/open-nails-lick.md | 5 + packages/rest-client/README.md | 10 - .../src/lib/devices/Delta2.test.ts | 162 +++++++++++++ .../rest-client/src/lib/devices/Delta2.ts | 213 +++++++++++++++++- 4 files changed, 372 insertions(+), 18 deletions(-) create mode 100644 .changeset/open-nails-lick.md diff --git a/.changeset/open-nails-lick.md b/.changeset/open-nails-lick.md new file mode 100644 index 00000000..87e94343 --- /dev/null +++ b/.changeset/open-nails-lick.md @@ -0,0 +1,5 @@ +--- +"@ecoflow-api/rest-client": minor +--- + +feat: implement additional command methods for Delta2 device, including generator controls, AC charge/discharge settings, and UPS configuration diff --git a/packages/rest-client/README.md b/packages/rest-client/README.md index bc983989..0eb01589 100644 --- a/packages/rest-client/README.md +++ b/packages/rest-client/README.md @@ -173,16 +173,6 @@ await powerStream.deleteTask(1) Serial number must start with _R331_ -Note: not all commands have been implemented yet. - -Todo: -- "operateType":"acOutCfg" -- "operateType":"acChgCfg" -- "operateType":"upsConfig" -- "operateType":"dsgCfg" -- "operateType":"openOilSoc" -- "operateType":"closeOilSoc" - ```ts import {RestClient} from '@ecoflow-api/rest-client'; diff --git a/packages/rest-client/src/lib/devices/Delta2.test.ts b/packages/rest-client/src/lib/devices/Delta2.test.ts index 82d1964d..74af80ca 100644 --- a/packages/rest-client/src/lib/devices/Delta2.test.ts +++ b/packages/rest-client/src/lib/devices/Delta2.test.ts @@ -263,6 +263,54 @@ describe("Delta2", () => { }); }); + describe(".setAcDischarge()", () => { + it("sets ac discharge", async () => { + expect.assertions(1); + await delta2.setAcDischarge({ + enabled: 1, + out_freq: 1, + out_voltage: 30, + xboost: 1, + }); + + expect(restClient.setCommandPlain).toHaveBeenCalledWith({ + id: 123456789, + version: "1.0", + moduleType: 5, + operateType: "acOutCfg", + params: { + enabled: 1, + out_freq: 1, + out_voltage: 30, + xboost: 1, + }, + sn: validSn, + }); + }); + }); + + describe(".setAcCharge()", () => { + it("sets ac charge", async () => { + expect.assertions(1); + await delta2.setAcCharge({ + chgWatts: 100, + chgPauseFlag: 0, + }); + + expect(restClient.setCommandPlain).toHaveBeenCalledWith({ + id: 123456789, + version: "1.0", + moduleType: 5, + operateType: "acChgCfg", + params: { + chgWatts: 100, + chgPauseFlag: 0, + }, + sn: validSn, + }); + }); + }); + describe(".setCarStandByDuration()", () => { it("sets car standby duration", async () => { expect.assertions(1); @@ -311,4 +359,118 @@ describe("Delta2", () => { }); }); }); + + describe(".setUpsMaxSoc()", () => { + it("sets upper SoC for UPS", async () => { + expect.assertions(2); + await delta2.setUpsMaxSoc(0); + + expect(restClient.setCommandPlain).toHaveBeenCalledWith({ + id: 123456789, + version: "1.0", + moduleType: 2, + operateType: "upsConfig", + params: { + maxChgSoc: 0, + }, + sn: validSn, + }); + + await delta2.setUpsMaxSoc(100); + + expect(restClient.setCommandPlain).nthCalledWith(2, { + id: 123456789, + version: "1.0", + moduleType: 2, + operateType: "upsConfig", + params: { + maxChgSoc: 100, + }, + sn: validSn, + }); + }); + + it("throws error for invalid values", async () => { + expect.assertions(2); + + await expect(delta2.setUpsMaxSoc(-1)).rejects.toThrowError(); + await expect(delta2.setUpsMaxSoc(101)).rejects.toThrowError(); + }); + }); + + describe(".setSocTurnOnGenerator()", () => { + it("sets SoC to start generator", async () => { + expect.assertions(2); + await delta2.setSocTurnOnGenerator(0); + + expect(restClient.setCommandPlain).toHaveBeenCalledWith({ + id: 123456789, + version: "1.0", + moduleType: 2, + operateType: "openOilSoc", + params: { + openOilSoc: 0, + }, + sn: validSn, + }); + + await delta2.setSocTurnOnGenerator(100); + + expect(restClient.setCommandPlain).nthCalledWith(2, { + id: 123456789, + version: "1.0", + moduleType: 2, + operateType: "openOilSoc", + params: { + openOilSoc: 100, + }, + sn: validSn, + }); + }); + + it("throws error for invalid values", async () => { + expect.assertions(2); + + await expect(delta2.setMinDischargeSoc(-1)).rejects.toThrowError(); + await expect(delta2.setMinDischargeSoc(101)).rejects.toThrowError(); + }); + }); + + describe(".setSocTurnOffGenerator()", () => { + it("sets SoC to turn off generator", async () => { + expect.assertions(2); + await delta2.setSocTurnOffGenerator(0); + + expect(restClient.setCommandPlain).toHaveBeenCalledWith({ + id: 123456789, + version: "1.0", + moduleType: 2, + operateType: "closeOilSoc", + params: { + closeOilSoc: 0, + }, + sn: validSn, + }); + + await delta2.setSocTurnOffGenerator(100); + + expect(restClient.setCommandPlain).nthCalledWith(2, { + id: 123456789, + version: "1.0", + moduleType: 2, + operateType: "closeOilSoc", + params: { + closeOilSoc: 100, + }, + sn: validSn, + }); + }); + + it("throws error for invalid values", async () => { + expect.assertions(2); + + await expect(delta2.setSocTurnOffGenerator(-1)).rejects.toThrowError(); + await expect(delta2.setSocTurnOffGenerator(101)).rejects.toThrowError(); + }); + }); }); diff --git a/packages/rest-client/src/lib/devices/Delta2.ts b/packages/rest-client/src/lib/devices/Delta2.ts index 381d9231..72583963 100644 --- a/packages/rest-client/src/lib/devices/Delta2.ts +++ b/packages/rest-client/src/lib/devices/Delta2.ts @@ -1,6 +1,10 @@ import { AcAlwaysOn, acAlwaysOnSchema, + AcChargingSettings, + acChargingSettingsSchema, + AcDischarge, + acDischargeSchema, AcStandbyTime, acStandbyTimeSchema, BuzzerSilentMode, @@ -22,10 +26,18 @@ import { isDelta2SerialNumber, LcdConfig, lcdConfigSchema, + MinDischargeSoc, + minDischargeSocSchema, pdStandByTime, PdStandByTime, PvPriority, pvPrioritySchema, + SocTurnOffGenerator, + socTurnOffGeneratorSchema, + SocTurnOnGenerator, + socTurnOnGeneratorSchema, + UpsUpperLimit, + upsUpperLimitSchema, } from "@ecoflow-api/schemas"; import { Device } from "./Device"; import { RestClient } from "../RestClient"; @@ -61,11 +73,77 @@ export class Delta2 extends Device { /**************+******************************************************** * MPPT Commands - * @todo Implement MPPT commands: - * - "operateType":"acOutCfg" - * - "operateType":"acChgCfg" **************+********************************************************/ + /** + * Set AC discharge ("enabled" and X-Boost switch settings) + * + * @example + * ```typescript + * const sn = "R331xxxx"; + * const client = new RestClient({ + * accessKey: "my-access-key", + * secretKey: "my-secret-key", + * host: "https://api-e.ecoflow.com", + * }); + * + * const delta2 = client.getDevice(sn); + * await delta2.setCarStandByDuration({ + * enabled: 1, + * out_freq: 1, + * out_voltage: 30, + * xboost: 1, + * }); + * ``` + */ + async setAcDischarge(params: { + enabled: 0 | 1; + xboost: 0 | 1; + out_voltage: number; + out_freq: number; + }) { + const payload: AcDischarge = { + ...this.#payloadDefaults(5), + operateType: "acOutCfg", + params, + }; + + return this.sendCommand(payload, acDischargeSchema); + } + + /** + * AC charging settings + * chgPauseFlag: + * - 0: AC charging in normal operation + * - 1: AC charging paused + * (not saved, restored by plugging) + * + * @example + * ```typescript + * const sn = "R331xxxx"; + * const client = new RestClient({ + * accessKey: "my-access-key", + * secretKey: "my-secret-key", + * host: "https://api-e.ecoflow.com", + * }); + * + * const delta2 = client.getDevice(sn); + * await delta2.setAcCharge({ + * "chgWatts":100, + * "chgPauseFlag":0 + * }); + * ``` + */ + async setAcCharge(params: { chgWatts: number; chgPauseFlag: 0 | 1 }) { + const payload: AcChargingSettings = { + ...this.#payloadDefaults(5), + operateType: "acChgCfg", + params, + }; + + return this.sendCommand(payload, acChargingSettingsSchema); + } + /** * Set the car standby duration. * @@ -216,13 +294,132 @@ export class Delta2 extends Device { /**************+******************************************************** * BMS Commands - * @todo Implement BMS commands: - * - "operateType":"upsConfig" - * - "operateType":"dsgCfg" - * - "operateType":"openOilSoc" - * - "operateType":"closeOilSoc" **************+********************************************************/ + /** + * UPS settings(UPS, upper SoC limit when charging) + * + * @example + * ```typescript + * const sn = "R331xxxx"; + * const client = new RestClient({ + * accessKey: "my-access-key", + * secretKey: "my-secret-key", + * host: "https://api-e.ecoflow.com", + * }); + * + * const delta2 = client.getDevice(sn); + * + * await delta2.setUpsMaxSoc(50); + * ``` + * + * @param soc + */ + async setUpsMaxSoc(soc: number) { + const payload: UpsUpperLimit = { + ...this.#payloadDefaults(2), + operateType: "upsConfig", + params: { + maxChgSoc: soc, + }, + }; + + return this.sendCommand(payload, upsUpperLimitSchema); + } + + /** + * SOC lower limit when discharging + * + * @example + * ```typescript + * const sn = "R331xxxx"; + * const client = new RestClient({ + * accessKey: "my-access-key", + * secretKey: "my-secret-key", + * host: "https://api-e.ecoflow.com", + * }); + * + * const delta2 = client.getDevice(sn); + * + * await delta2.setMinDischargeSoc(50); + * ``` + * + * @param soc + */ + async setMinDischargeSoc(soc: number) { + const payload: MinDischargeSoc = { + ...this.#payloadDefaults(2), + operateType: "dsgCfg", + params: { + minDsgSoc: soc, + }, + }; + + return this.sendCommand(payload, minDischargeSocSchema); + } + + /** + * SoC that triggers EMS to turn on Smart Generator + * + * @example + * ```typescript + * const sn = "R331xxxx"; + * const client = new RestClient({ + * accessKey: "my-access-key", + * secretKey: "my-secret-key", + * host: "https://api-e.ecoflow.com", + * }); + * + * const delta2 = client.getDevice(sn); + * + * await delta2.setSocTurnOnGenerator(50); + * ``` + * + * @param soc + */ + async setSocTurnOnGenerator(soc: number) { + const payload: SocTurnOnGenerator = { + ...this.#payloadDefaults(2), + operateType: "openOilSoc", + params: { + openOilSoc: soc, + }, + }; + + return this.sendCommand(payload, socTurnOnGeneratorSchema); + } + + /** + * SOC that triggers EMS to turn off Smart Generator + * + * @example + * ```typescript + * const sn = "R331xxxx"; + * const client = new RestClient({ + * accessKey: "my-access-key", + * secretKey: "my-secret-key", + * host: "https://api-e.ecoflow.com", + * }); + * + * const delta2 = client.getDevice(sn); + * + * await delta2.setSocTurnOffGenerator(50); + * ``` + * + * @param soc + */ + async setSocTurnOffGenerator(soc: number) { + const payload: SocTurnOffGenerator = { + ...this.#payloadDefaults(2), + operateType: "closeOilSoc", + params: { + closeOilSoc: soc, + }, + }; + + return this.sendCommand(payload, socTurnOffGeneratorSchema); + } + /**************+******************************************************** * PD Commands **************+********************************************************/