@enyo-energy/energy-app-sdk 0.0.145 → 0.0.147
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +184 -0
- package/dist/cjs/energy-app.cjs +9 -0
- package/dist/cjs/energy-app.d.cts +8 -0
- package/dist/cjs/enyo-energy-app-sdk.d.cts +3 -0
- package/dist/cjs/implementations/appliance-command-forecast/appliance-command-forecast-validators.cjs +228 -0
- package/dist/cjs/implementations/appliance-command-forecast/appliance-command-forecast-validators.d.cts +67 -0
- package/dist/cjs/implementations/energy-manager-diagnostics/energy-manager-diagnostics-validators.cjs +169 -0
- package/dist/cjs/implementations/energy-manager-diagnostics/energy-manager-diagnostics-validators.d.cts +26 -0
- package/dist/cjs/index.cjs +4 -0
- package/dist/cjs/index.d.cts +4 -0
- package/dist/cjs/packages/eebus/eebus-cevc-client.d.cts +12 -11
- package/dist/cjs/packages/eebus/eebus-evcc-client.d.cts +17 -15
- package/dist/cjs/packages/eebus/eebus-evcem-client.d.cts +4 -4
- package/dist/cjs/packages/eebus/eebus-evsoc-client.d.cts +6 -6
- package/dist/cjs/packages/eebus/eebus-lpc-client.d.cts +8 -8
- package/dist/cjs/packages/eebus/eebus-mgcp-client.d.cts +4 -3
- package/dist/cjs/packages/eebus/eebus-mpc-client.d.cts +14 -13
- package/dist/cjs/packages/eebus/eebus-use-case-client.d.cts +11 -7
- package/dist/cjs/packages/eebus/eebus-vabd-client.d.cts +7 -7
- package/dist/cjs/packages/eebus/eebus-vapd-client.d.cts +4 -4
- package/dist/cjs/packages/energy-app-appliance-energy-manager-forecast.cjs +2 -0
- package/dist/cjs/packages/energy-app-appliance-energy-manager-forecast.d.cts +88 -0
- package/dist/cjs/packages/energy-app-diagnostics.d.cts +55 -13
- package/dist/cjs/types/enyo-appliance-command-forecast.cjs +89 -0
- package/dist/cjs/types/enyo-appliance-command-forecast.d.cts +307 -0
- package/dist/cjs/types/enyo-diagnostics.cjs +86 -1
- package/dist/cjs/types/enyo-diagnostics.d.cts +203 -1
- package/dist/cjs/version.cjs +1 -1
- package/dist/cjs/version.d.cts +1 -1
- package/dist/energy-app.d.ts +8 -0
- package/dist/energy-app.js +9 -0
- package/dist/enyo-energy-app-sdk.d.ts +3 -0
- package/dist/implementations/appliance-command-forecast/appliance-command-forecast-validators.d.ts +67 -0
- package/dist/implementations/appliance-command-forecast/appliance-command-forecast-validators.js +217 -0
- package/dist/implementations/energy-manager-diagnostics/energy-manager-diagnostics-validators.d.ts +26 -0
- package/dist/implementations/energy-manager-diagnostics/energy-manager-diagnostics-validators.js +164 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/packages/eebus/eebus-cevc-client.d.ts +12 -11
- package/dist/packages/eebus/eebus-evcc-client.d.ts +17 -15
- package/dist/packages/eebus/eebus-evcem-client.d.ts +4 -4
- package/dist/packages/eebus/eebus-evsoc-client.d.ts +6 -6
- package/dist/packages/eebus/eebus-lpc-client.d.ts +8 -8
- package/dist/packages/eebus/eebus-mgcp-client.d.ts +4 -3
- package/dist/packages/eebus/eebus-mpc-client.d.ts +14 -13
- package/dist/packages/eebus/eebus-use-case-client.d.ts +11 -7
- package/dist/packages/eebus/eebus-vabd-client.d.ts +7 -7
- package/dist/packages/eebus/eebus-vapd-client.d.ts +4 -4
- package/dist/packages/energy-app-appliance-energy-manager-forecast.d.ts +88 -0
- package/dist/packages/energy-app-appliance-energy-manager-forecast.js +1 -0
- package/dist/packages/energy-app-diagnostics.d.ts +55 -13
- package/dist/types/enyo-appliance-command-forecast.d.ts +307 -0
- package/dist/types/enyo-appliance-command-forecast.js +86 -0
- package/dist/types/enyo-diagnostics.d.ts +203 -1
- package/dist/types/enyo-diagnostics.js +85 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -49,6 +49,12 @@ The official TypeScript SDK for building Energy Apps on the enyo platform. Creat
|
|
|
49
49
|
- [EvChargingForecast](#evchargingforecast)
|
|
50
50
|
- [HeatpumpConsumptionForecast](#heatpumpconsumptionforecast)
|
|
51
51
|
- [HeatpumpDhwTemperatureForecast](#heatpumpdhwtemperatureforecast)
|
|
52
|
+
- [Appliance Energy-Manager Forecast](#appliance-energy-manager-forecast)
|
|
53
|
+
- [`useApplianceEnergyManagerForecast()`](#useapplianceenergymanagerforecast-energyappapplianceenergymanagerforecast)
|
|
54
|
+
- [ChargerForecast](#chargerforecast)
|
|
55
|
+
- [BatteryCommandForecast](#batterycommandforecast)
|
|
56
|
+
- [HeatpumpForecast](#heatpumpforecast)
|
|
57
|
+
- [Validators](#validators)
|
|
52
58
|
- [Examples](#examples)
|
|
53
59
|
- [Basic Energy App](#basic-energy-app)
|
|
54
60
|
- [Device Integration](#device-integration)
|
|
@@ -130,6 +136,7 @@ The SDK exposes several layered building blocks. Pick the one that matches the k
|
|
|
130
136
|
| Forecast EV charging demand | [`EvChargingForecast`](#evchargingforecast) |
|
|
131
137
|
| Forecast heatpump electrical consumption | [`HeatpumpConsumptionForecast`](#heatpumpconsumptionforecast) |
|
|
132
138
|
| Forecast heatpump DHW tank temperature | [`HeatpumpDhwTemperatureForecast`](#heatpumpdhwtemperatureforecast) |
|
|
139
|
+
| Announce a charger / battery / heatpump command plan you **intend to apply** | [`useApplianceEnergyManagerForecast()`](#useapplianceenergymanagerforecast-energyappapplianceenergymanagerforecast) |
|
|
133
140
|
| Talk to an EEBUS / SHIP / SPINE device | [`useEebus()`](#useeebus-energyappeebus) |
|
|
134
141
|
| Speak MQTT (SDK broker or external) | [`useMqtt()`](#usemqtt-energyappmqtt) |
|
|
135
142
|
| Scan or talk to Bluetooth LE peripherals | [`useBluetooth()`](#usebluetooth-energyappbluetooth) |
|
|
@@ -1898,6 +1905,183 @@ energyApp.onShutdown(async () => {
|
|
|
1898
1905
|
|
|
1899
1906
|
> **Tip:** if your app needs more than one forecaster, prefer [`EnergyManagerEnergyApp`](#energymanagerenergyapp) — it manages construction, caching, and disposal for you.
|
|
1900
1907
|
|
|
1908
|
+
## Appliance Energy-Manager Forecast
|
|
1909
|
+
|
|
1910
|
+
The [Forecasting](#forecasting) module above predicts what an appliance will **do** based on history. The Appliance Energy-Manager Forecast package goes the other way: it lets an energy-manager app declare what it **intends to command** each appliance to do over the upcoming horizon, plus the temperature trajectories its commands are expected to produce. Three appliance families are supported today — chargers, batteries, and heatpumps — and the heatpump payload can carry any combination of DHW boost, room pre-heating, buffer-tank boost, and a relative power-announcement schedule in one call.
|
|
1911
|
+
|
|
1912
|
+
How the runtime fans these forecasts out to subscribers (data bus, RPC, …) is an internal implementation detail of the SDK runtime — apps just call `publish*` and the SDK takes care of the rest.
|
|
1913
|
+
|
|
1914
|
+
**Required permission:** `EnergyManager`.
|
|
1915
|
+
|
|
1916
|
+
### `useApplianceEnergyManagerForecast(): EnergyAppApplianceEnergyManagerForecast`
|
|
1917
|
+
|
|
1918
|
+
```typescript
|
|
1919
|
+
const forecasts = energyApp.useApplianceEnergyManagerForecast();
|
|
1920
|
+
```
|
|
1921
|
+
|
|
1922
|
+
| Method | Purpose |
|
|
1923
|
+
|---|---|
|
|
1924
|
+
| `publishChargerForecast(applianceId, forecast: ChargerForecast)` | Publish the planned phase / power schedule for a charger. |
|
|
1925
|
+
| `publishBatteryForecast(applianceId, forecast: BatteryCommandForecast)` | Publish the planned charge / discharge / auto cadence for a battery. |
|
|
1926
|
+
| `publishHeatpumpForecast(applianceId, forecast: HeatpumpForecast)` | Publish any combination of DHW boost / room pre-heating / buffer-tank boost / power-announcement schedule for a heatpump. |
|
|
1927
|
+
|
|
1928
|
+
Every call validates the payload first and rejects with `ApplianceCommandForecastValidationError` if any invariant is broken — `publish*` never goes through the runtime with malformed data.
|
|
1929
|
+
|
|
1930
|
+
Every forecast also accepts shared optional metadata via [`ApplianceForecastMetadata`](#validators):
|
|
1931
|
+
|
|
1932
|
+
- `generatedAtIso?: string` — ISO 8601 generation timestamp. Stamped by the runtime when omitted.
|
|
1933
|
+
- `reason?: string` — free-form note (e.g. `"follow PV peak"`, `"§14a DR event"`).
|
|
1934
|
+
- `estimatedSavings?: ApplianceForecastEstimatedSavings` — see below.
|
|
1935
|
+
|
|
1936
|
+
```typescript
|
|
1937
|
+
interface ApplianceForecastEstimatedSavings {
|
|
1938
|
+
costSavings: number; // positive = savings, negative = extra cost (in `currency`)
|
|
1939
|
+
currency: string; // ISO 4217 code
|
|
1940
|
+
co2SavingsGrams?: number;
|
|
1941
|
+
selfConsumptionGainWh?: number;
|
|
1942
|
+
note?: string; // e.g. "vs. flat-tariff baseline"
|
|
1943
|
+
}
|
|
1944
|
+
```
|
|
1945
|
+
|
|
1946
|
+
### ChargerForecast
|
|
1947
|
+
|
|
1948
|
+
Relative phase / power schedule that mirrors an OCPP TxProfile but adds explicit `numberOfPhases` (1 / 2 / 3).
|
|
1949
|
+
|
|
1950
|
+
```typescript
|
|
1951
|
+
import { ChargerForecast } from '@enyo-energy/energy-app-sdk';
|
|
1952
|
+
|
|
1953
|
+
const forecast: ChargerForecast = {
|
|
1954
|
+
relativeSchedule: [
|
|
1955
|
+
// Right now: 11 kW across three phases
|
|
1956
|
+
{ seconds: 0, powerW: 11_000, numberOfPhases: 3 },
|
|
1957
|
+
// In 30 minutes: derate to 3.7 kW on one phase
|
|
1958
|
+
{ seconds: 1800, powerW: 3_700, numberOfPhases: 1 },
|
|
1959
|
+
// In one hour: pause
|
|
1960
|
+
{ seconds: 3600, powerW: 0 },
|
|
1961
|
+
],
|
|
1962
|
+
estimatedSavings: { costSavings: 0.42, currency: 'EUR', co2SavingsGrams: 120 },
|
|
1963
|
+
reason: 'follow PV peak',
|
|
1964
|
+
};
|
|
1965
|
+
|
|
1966
|
+
await forecasts.publishChargerForecast('charger-1', forecast);
|
|
1967
|
+
```
|
|
1968
|
+
|
|
1969
|
+
Per-entry invariants:
|
|
1970
|
+
|
|
1971
|
+
- `seconds`: finite, non-negative; first entry `= 0`; subsequent entries strictly increasing.
|
|
1972
|
+
- `powerW`: finite, non-negative (`0` means "pause").
|
|
1973
|
+
- `numberOfPhases`: optional; if set, must be `1`, `2`, or `3`.
|
|
1974
|
+
|
|
1975
|
+
### BatteryCommandForecast
|
|
1976
|
+
|
|
1977
|
+
Relative `{seconds, mode, powerW}` schedule where `mode` is one of `'charge'`, `'discharge'`, or `'auto'`. `auto` returns control to the appliance and must always carry `powerW = 0`.
|
|
1978
|
+
|
|
1979
|
+
The type is named `BatteryCommandForecast` to make the distinction with the existing [`BatteryForecast`](#batteryforecast) class (which forecasts state-of-charge from history) explicit.
|
|
1980
|
+
|
|
1981
|
+
```typescript
|
|
1982
|
+
import {
|
|
1983
|
+
BatteryCommandForecast,
|
|
1984
|
+
BatteryCommandForecastModeEnum,
|
|
1985
|
+
} from '@enyo-energy/energy-app-sdk';
|
|
1986
|
+
|
|
1987
|
+
const forecast: BatteryCommandForecast = {
|
|
1988
|
+
relativeSchedule: [
|
|
1989
|
+
{ seconds: 0, mode: BatteryCommandForecastModeEnum.Charge, powerW: 3000 },
|
|
1990
|
+
{ seconds: 1800, mode: BatteryCommandForecastModeEnum.Discharge, powerW: 2500 },
|
|
1991
|
+
{ seconds: 3600, mode: BatteryCommandForecastModeEnum.Auto, powerW: 0 },
|
|
1992
|
+
],
|
|
1993
|
+
estimatedSavings: { costSavings: 0.18, currency: 'EUR' },
|
|
1994
|
+
};
|
|
1995
|
+
|
|
1996
|
+
await forecasts.publishBatteryForecast('battery-1', forecast);
|
|
1997
|
+
```
|
|
1998
|
+
|
|
1999
|
+
Per-entry invariants:
|
|
2000
|
+
|
|
2001
|
+
- `seconds`: finite, non-negative; first entry `= 0`; subsequent entries strictly increasing.
|
|
2002
|
+
- `mode`: one of `charge` / `discharge` / `auto`.
|
|
2003
|
+
- `powerW`: finite, non-negative. **MUST be `0` when `mode === 'auto'`.**
|
|
2004
|
+
|
|
2005
|
+
### HeatpumpForecast
|
|
2006
|
+
|
|
2007
|
+
The heatpump payload can carry any combination of the four supported command families in one call — at least one must be present and non-empty. Each command family also accepts its own forecasted temperature trajectory so subscribers can reason about the plan and its expected outcome together.
|
|
2008
|
+
|
|
2009
|
+
```typescript
|
|
2010
|
+
import { HeatpumpForecast } from '@enyo-energy/energy-app-sdk';
|
|
2011
|
+
|
|
2012
|
+
const forecast: HeatpumpForecast = {
|
|
2013
|
+
// ----- DHW boost -----
|
|
2014
|
+
dhwBoosts: [
|
|
2015
|
+
{ startIso: '2026-06-10T13:00:00.000Z', endIso: '2026-06-10T15:00:00.000Z', targetTemperatureC: 60 },
|
|
2016
|
+
],
|
|
2017
|
+
dhwTemperatureForecast: [
|
|
2018
|
+
{ timestampIso: '2026-06-10T12:00:00.000Z', temperatureC: 48 },
|
|
2019
|
+
{ timestampIso: '2026-06-10T13:00:00.000Z', temperatureC: 52 },
|
|
2020
|
+
{ timestampIso: '2026-06-10T15:00:00.000Z', temperatureC: 60 },
|
|
2021
|
+
],
|
|
2022
|
+
|
|
2023
|
+
// ----- Room pre-heating (per heating circuit) -----
|
|
2024
|
+
roomPreHeatings: [
|
|
2025
|
+
{ startIso: '2026-06-10T05:00:00.000Z', endIso: '2026-06-10T07:00:00.000Z', targetTemperatureC: 22, circuitIndex: 0 },
|
|
2026
|
+
],
|
|
2027
|
+
roomTemperatureForecast: [
|
|
2028
|
+
{ timestampIso: '2026-06-10T05:00:00.000Z', temperatureC: 19 },
|
|
2029
|
+
{ timestampIso: '2026-06-10T07:00:00.000Z', temperatureC: 22 },
|
|
2030
|
+
],
|
|
2031
|
+
|
|
2032
|
+
// ----- Buffer-tank boost -----
|
|
2033
|
+
bufferTankBoosts: [
|
|
2034
|
+
{ startIso: '2026-06-10T13:00:00.000Z', endIso: '2026-06-10T14:00:00.000Z', targetTemperatureC: 55 },
|
|
2035
|
+
],
|
|
2036
|
+
bufferTankTemperatureForecast: [
|
|
2037
|
+
{ timestampIso: '2026-06-10T13:00:00.000Z', temperatureC: 45 },
|
|
2038
|
+
{ timestampIso: '2026-06-10T14:00:00.000Z', temperatureC: 55 },
|
|
2039
|
+
],
|
|
2040
|
+
|
|
2041
|
+
// ----- Power-announcement schedule (relative) -----
|
|
2042
|
+
powerAnnouncementSchedule: [
|
|
2043
|
+
{ seconds: 0, powerW: 1500 },
|
|
2044
|
+
{ seconds: 1800, powerW: 3000 },
|
|
2045
|
+
{ seconds: 3600, powerW: 0 },
|
|
2046
|
+
],
|
|
2047
|
+
|
|
2048
|
+
estimatedSavings: { costSavings: 1.05, currency: 'EUR', co2SavingsGrams: 320 },
|
|
2049
|
+
reason: 'soak PV during 13–15h window',
|
|
2050
|
+
};
|
|
2051
|
+
|
|
2052
|
+
await forecasts.publishHeatpumpForecast('heatpump-1', forecast);
|
|
2053
|
+
```
|
|
2054
|
+
|
|
2055
|
+
Per-family invariants:
|
|
2056
|
+
|
|
2057
|
+
- **`dhwBoosts` / `bufferTankBoosts`** — each window must satisfy `startIso < endIso`, sorted ascending and non-overlapping, `targetTemperatureC ∈ [0, 100]`.
|
|
2058
|
+
- **`roomPreHeatings`** — same shape as the boost windows but `targetTemperatureC ∈ [0, 40]`. Non-overlap is enforced **per `circuitIndex`** so different heating circuits can pre-heat in parallel.
|
|
2059
|
+
- **`powerAnnouncementSchedule`** — relative schedule (seconds-since-effective), first entry at `seconds = 0`, strictly increasing thereafter; per-entry `powerW` finite and non-negative.
|
|
2060
|
+
- **Temperature trajectories** — strictly increasing `timestampIso`; `temperatureC ∈ [−50, 150]`.
|
|
2061
|
+
|
|
2062
|
+
### Validators
|
|
2063
|
+
|
|
2064
|
+
The validators that `publish*` runs internally are exported as standalone pure functions so apps can validate forecasts while building them — for instance, to surface user-facing errors in a planning UI before holding the forecast in state.
|
|
2065
|
+
|
|
2066
|
+
```typescript
|
|
2067
|
+
import {
|
|
2068
|
+
validateChargerForecast,
|
|
2069
|
+
validateBatteryCommandForecast,
|
|
2070
|
+
validateHeatpumpForecast,
|
|
2071
|
+
ApplianceCommandForecastValidationError,
|
|
2072
|
+
} from '@enyo-energy/energy-app-sdk';
|
|
2073
|
+
|
|
2074
|
+
try {
|
|
2075
|
+
validateHeatpumpForecast(forecast);
|
|
2076
|
+
} catch (error) {
|
|
2077
|
+
if (error instanceof ApplianceCommandForecastValidationError) {
|
|
2078
|
+
// surface error.message — it names the offending field / index
|
|
2079
|
+
}
|
|
2080
|
+
}
|
|
2081
|
+
```
|
|
2082
|
+
|
|
2083
|
+
Granular helpers are exported alongside the top-level validators: `validateChargerSchedule`, `validateBatterySchedule`, `validateDhwBoostWindows`, `validateRoomPreHeatingWindows`, `validateBufferTankBoostWindows`, `validatePowerAnnouncementSchedule`, `validateTemperatureForecast`.
|
|
2084
|
+
|
|
1901
2085
|
## Examples
|
|
1902
2086
|
|
|
1903
2087
|
### Basic Energy App
|
package/dist/cjs/energy-app.cjs
CHANGED
|
@@ -298,6 +298,15 @@ class EnergyApp {
|
|
|
298
298
|
useConfigurationManager() {
|
|
299
299
|
return this.energyAppSdk.useConfigurationManager();
|
|
300
300
|
}
|
|
301
|
+
/**
|
|
302
|
+
* Gets the Appliance Energy-Manager Forecast API for publishing
|
|
303
|
+
* forecasted command plans per appliance (charger, battery,
|
|
304
|
+
* heatpump). The publisher must hold the `EnergyManager` permission.
|
|
305
|
+
* @returns The Appliance Energy-Manager Forecast API instance
|
|
306
|
+
*/
|
|
307
|
+
useApplianceEnergyManagerForecast() {
|
|
308
|
+
return this.energyAppSdk.useApplianceEnergyManagerForecast();
|
|
309
|
+
}
|
|
301
310
|
/**
|
|
302
311
|
* Gets the current SDK version.
|
|
303
312
|
* @returns The semantic version string of the SDK
|
|
@@ -35,6 +35,7 @@ import { EnergyAppWifi } from "./packages/energy-app-wifi.cjs";
|
|
|
35
35
|
import { EnergyAppUdp } from "./packages/energy-app-udp.cjs";
|
|
36
36
|
import { EnergyAppGridConnectionPoint } from "./packages/energy-app-grid-connection-point.cjs";
|
|
37
37
|
import { EnergyAppConfigurationManager } from "./packages/energy-app-configuration-manager.cjs";
|
|
38
|
+
import { EnergyAppApplianceEnergyManagerForecast } from "./packages/energy-app-appliance-energy-manager-forecast.cjs";
|
|
38
39
|
/**
|
|
39
40
|
* Concrete implementation of {@link EnyoEnergyAppSdk} that delegates every call
|
|
40
41
|
* to the runtime-provided `energyAppSdkInstance` global.
|
|
@@ -232,6 +233,13 @@ export declare class EnergyApp implements EnyoEnergyAppSdk {
|
|
|
232
233
|
* @returns The Configuration Manager API instance
|
|
233
234
|
*/
|
|
234
235
|
useConfigurationManager(): EnergyAppConfigurationManager;
|
|
236
|
+
/**
|
|
237
|
+
* Gets the Appliance Energy-Manager Forecast API for publishing
|
|
238
|
+
* forecasted command plans per appliance (charger, battery,
|
|
239
|
+
* heatpump). The publisher must hold the `EnergyManager` permission.
|
|
240
|
+
* @returns The Appliance Energy-Manager Forecast API instance
|
|
241
|
+
*/
|
|
242
|
+
useApplianceEnergyManagerForecast(): EnergyAppApplianceEnergyManagerForecast;
|
|
235
243
|
/**
|
|
236
244
|
* Gets the current SDK version.
|
|
237
245
|
* @returns The semantic version string of the SDK
|
|
@@ -34,6 +34,7 @@ import { EnergyAppWifi } from "./packages/energy-app-wifi.cjs";
|
|
|
34
34
|
import { EnergyAppUdp } from "./packages/energy-app-udp.cjs";
|
|
35
35
|
import { EnergyAppGridConnectionPoint } from "./packages/energy-app-grid-connection-point.cjs";
|
|
36
36
|
import { EnergyAppConfigurationManager } from "./packages/energy-app-configuration-manager.cjs";
|
|
37
|
+
import { EnergyAppApplianceEnergyManagerForecast } from "./packages/energy-app-appliance-energy-manager-forecast.cjs";
|
|
37
38
|
export declare enum EnergyAppStateEnum {
|
|
38
39
|
Launching = "launching",
|
|
39
40
|
Running = "running",
|
|
@@ -130,4 +131,6 @@ export interface EnyoEnergyAppSdk {
|
|
|
130
131
|
useGridConnectionPoint: () => EnergyAppGridConnectionPoint;
|
|
131
132
|
/** Get the Configuration Manager API for registering internal (non user-facing) package configurations with change notifications */
|
|
132
133
|
useConfigurationManager: () => EnergyAppConfigurationManager;
|
|
134
|
+
/** Get the Appliance Energy-Manager Forecast API for publishing forecasted command plans per appliance (charger, battery, heatpump) */
|
|
135
|
+
useApplianceEnergyManagerForecast: () => EnergyAppApplianceEnergyManagerForecast;
|
|
133
136
|
}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ApplianceCommandForecastValidationError = void 0;
|
|
4
|
+
exports.validateChargerForecast = validateChargerForecast;
|
|
5
|
+
exports.validateBatteryCommandForecast = validateBatteryCommandForecast;
|
|
6
|
+
exports.validateHeatpumpForecast = validateHeatpumpForecast;
|
|
7
|
+
exports.validateChargerSchedule = validateChargerSchedule;
|
|
8
|
+
exports.validateBatterySchedule = validateBatterySchedule;
|
|
9
|
+
exports.validateHeatpumpSchedule = validateHeatpumpSchedule;
|
|
10
|
+
exports.validateHeatpumpScheduleEntry = validateHeatpumpScheduleEntry;
|
|
11
|
+
const enyo_appliance_command_forecast_js_1 = require("../../types/enyo-appliance-command-forecast.cjs");
|
|
12
|
+
/**
|
|
13
|
+
* Thrown when a forecast payload passed to one of the validators (or to
|
|
14
|
+
* {@link EnergyAppApplianceEnergyManagerForecast.publishChargerForecast}
|
|
15
|
+
* and friends) violates the invariants declared on its data interface.
|
|
16
|
+
*
|
|
17
|
+
* The message names the offending field / index so callers can surface it
|
|
18
|
+
* directly to the user.
|
|
19
|
+
*/
|
|
20
|
+
class ApplianceCommandForecastValidationError extends Error {
|
|
21
|
+
constructor(message) {
|
|
22
|
+
super(message);
|
|
23
|
+
this.name = 'ApplianceCommandForecastValidationError';
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
exports.ApplianceCommandForecastValidationError = ApplianceCommandForecastValidationError;
|
|
27
|
+
const TEMPERATURE_TRAJECTORY_MIN_C = -50;
|
|
28
|
+
const TEMPERATURE_TRAJECTORY_MAX_C = 150;
|
|
29
|
+
const RESOLUTION_SECONDS = {
|
|
30
|
+
[enyo_appliance_command_forecast_js_1.ApplianceForecastResolutionEnum.OneMinute]: 60,
|
|
31
|
+
[enyo_appliance_command_forecast_js_1.ApplianceForecastResolutionEnum.FifteenMinutes]: 900,
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Validates a {@link ChargerForecast}. Throws on the first violation —
|
|
35
|
+
* the error message names the offending field / index.
|
|
36
|
+
*/
|
|
37
|
+
function validateChargerForecast(forecast) {
|
|
38
|
+
if (!forecast || typeof forecast !== 'object') {
|
|
39
|
+
throw new ApplianceCommandForecastValidationError('ChargerForecast must be an object.');
|
|
40
|
+
}
|
|
41
|
+
validateMetadata(forecast);
|
|
42
|
+
validateChargerSchedule(forecast.relativeSchedule, forecast.resolution);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Validates a {@link BatteryCommandForecast}. Throws on the first
|
|
46
|
+
* violation — the error message names the offending field / index.
|
|
47
|
+
*
|
|
48
|
+
* The forecast is a discriminated union on
|
|
49
|
+
* {@link BatteryCommandForecastModeEnum}: when `mode = 'auto'` no
|
|
50
|
+
* schedule is expected; when `mode = 'schedule'` the embedded
|
|
51
|
+
* {@link BatteryCommandForecastScheduled.relativeSchedule} is validated
|
|
52
|
+
* by {@link validateBatterySchedule}.
|
|
53
|
+
*/
|
|
54
|
+
function validateBatteryCommandForecast(forecast) {
|
|
55
|
+
if (!forecast || typeof forecast !== 'object') {
|
|
56
|
+
throw new ApplianceCommandForecastValidationError('BatteryCommandForecast must be an object.');
|
|
57
|
+
}
|
|
58
|
+
validateMetadata(forecast);
|
|
59
|
+
const allowedModes = new Set(Object.values(enyo_appliance_command_forecast_js_1.BatteryCommandForecastModeEnum));
|
|
60
|
+
if (!allowedModes.has(forecast.mode)) {
|
|
61
|
+
throw new ApplianceCommandForecastValidationError(`BatteryCommandForecast.mode is invalid: ${forecast.mode}.`);
|
|
62
|
+
}
|
|
63
|
+
if (forecast.mode === enyo_appliance_command_forecast_js_1.BatteryCommandForecastModeEnum.Auto) {
|
|
64
|
+
if (forecast.relativeSchedule !== undefined) {
|
|
65
|
+
throw new ApplianceCommandForecastValidationError("BatteryCommandForecast with mode='auto' must not carry a relativeSchedule.");
|
|
66
|
+
}
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
validateBatterySchedule(forecast.relativeSchedule, forecast.resolution);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Validates a {@link HeatpumpForecast}. Throws on the first violation —
|
|
73
|
+
* the error message names the offending field / index.
|
|
74
|
+
*
|
|
75
|
+
* The forecast carries a single unified relative schedule; every entry
|
|
76
|
+
* is validated by {@link validateHeatpumpScheduleEntry}.
|
|
77
|
+
*/
|
|
78
|
+
function validateHeatpumpForecast(forecast) {
|
|
79
|
+
if (!forecast || typeof forecast !== 'object') {
|
|
80
|
+
throw new ApplianceCommandForecastValidationError('HeatpumpForecast must be an object.');
|
|
81
|
+
}
|
|
82
|
+
validateMetadata(forecast);
|
|
83
|
+
validateHeatpumpSchedule(forecast.relativeSchedule, forecast.resolution);
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Validates a charger relative schedule (the inner schedule used by
|
|
87
|
+
* {@link ChargerForecast.relativeSchedule}). The `resolution` argument
|
|
88
|
+
* is the value declared on
|
|
89
|
+
* {@link ApplianceForecastMetadata.resolution}; consecutive entries'
|
|
90
|
+
* `seconds` must be spaced by exactly that many seconds.
|
|
91
|
+
*/
|
|
92
|
+
function validateChargerSchedule(entries, resolution) {
|
|
93
|
+
const stepSeconds = resolveResolutionSeconds(resolution);
|
|
94
|
+
validateNonEmptySchedule(entries, 'relativeSchedule');
|
|
95
|
+
for (let i = 0; i < entries.length; i++) {
|
|
96
|
+
const entry = entries[i];
|
|
97
|
+
validateSecondsField(entry.seconds, `relativeSchedule[${i}].seconds`);
|
|
98
|
+
validatePowerW(entry.powerW, `relativeSchedule[${i}].powerW`);
|
|
99
|
+
if (entry.numberOfPhases !== undefined && ![1, 2, 3].includes(entry.numberOfPhases)) {
|
|
100
|
+
throw new ApplianceCommandForecastValidationError(`relativeSchedule[${i}].numberOfPhases must be 1, 2, or 3; got ${entry.numberOfPhases}.`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
validateFirstEntryStartsAtZero(entries[0].seconds, 'relativeSchedule');
|
|
104
|
+
validateSecondsMatchResolution(entries.map((e) => e.seconds), stepSeconds, 'relativeSchedule');
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Validates a battery relative schedule (the inner schedule used by
|
|
108
|
+
* {@link BatteryCommandForecastScheduled.relativeSchedule}). The
|
|
109
|
+
* `resolution` argument is the value declared on
|
|
110
|
+
* {@link ApplianceForecastMetadata.resolution}; consecutive entries'
|
|
111
|
+
* `seconds` must be spaced by exactly that many seconds.
|
|
112
|
+
*/
|
|
113
|
+
function validateBatterySchedule(entries, resolution) {
|
|
114
|
+
const stepSeconds = resolveResolutionSeconds(resolution);
|
|
115
|
+
validateNonEmptySchedule(entries, 'relativeSchedule');
|
|
116
|
+
const allowedDirections = new Set(Object.values(enyo_appliance_command_forecast_js_1.BatteryCommandForecastDirectionEnum));
|
|
117
|
+
for (let i = 0; i < entries.length; i++) {
|
|
118
|
+
const entry = entries[i];
|
|
119
|
+
validateSecondsField(entry.seconds, `relativeSchedule[${i}].seconds`);
|
|
120
|
+
validatePowerW(entry.powerW, `relativeSchedule[${i}].powerW`);
|
|
121
|
+
if (!allowedDirections.has(entry.direction)) {
|
|
122
|
+
throw new ApplianceCommandForecastValidationError(`relativeSchedule[${i}].direction is invalid: ${entry.direction}.`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
validateFirstEntryStartsAtZero(entries[0].seconds, 'relativeSchedule');
|
|
126
|
+
validateSecondsMatchResolution(entries.map((e) => e.seconds), stepSeconds, 'relativeSchedule');
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Validates a heatpump unified relative schedule (the schedule used by
|
|
130
|
+
* {@link HeatpumpForecast.relativeSchedule}). Enforces that the
|
|
131
|
+
* schedule is non-empty, starts at `seconds = 0`, has entries spaced
|
|
132
|
+
* by exactly `resolution`, and that every per-entry value falls in the
|
|
133
|
+
* plausible range documented on
|
|
134
|
+
* {@link HeatpumpForecastScheduleEntry}.
|
|
135
|
+
*/
|
|
136
|
+
function validateHeatpumpSchedule(entries, resolution) {
|
|
137
|
+
const stepSeconds = resolveResolutionSeconds(resolution);
|
|
138
|
+
validateNonEmptySchedule(entries, 'relativeSchedule');
|
|
139
|
+
for (let i = 0; i < entries.length; i++) {
|
|
140
|
+
validateHeatpumpScheduleEntry(entries[i], `relativeSchedule[${i}]`);
|
|
141
|
+
}
|
|
142
|
+
validateFirstEntryStartsAtZero(entries[0].seconds, 'relativeSchedule');
|
|
143
|
+
validateSecondsMatchResolution(entries.map((e) => e.seconds), stepSeconds, 'relativeSchedule');
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Validates a single {@link HeatpumpForecastScheduleEntry}. Used by
|
|
147
|
+
* {@link validateHeatpumpSchedule} and exposed for callers that build
|
|
148
|
+
* entries incrementally.
|
|
149
|
+
*/
|
|
150
|
+
function validateHeatpumpScheduleEntry(entry, fieldName) {
|
|
151
|
+
validateSecondsField(entry.seconds, `${fieldName}.seconds`);
|
|
152
|
+
if (entry.powerW !== undefined) {
|
|
153
|
+
validatePowerW(entry.powerW, `${fieldName}.powerW`);
|
|
154
|
+
}
|
|
155
|
+
validateTemperatureField(entry.dhwTemperatureC, `${fieldName}.dhwTemperatureC`);
|
|
156
|
+
validateTemperatureField(entry.roomTemperatureC, `${fieldName}.roomTemperatureC`);
|
|
157
|
+
validateTemperatureField(entry.bufferTankTemperatureC, `${fieldName}.bufferTankTemperatureC`);
|
|
158
|
+
validateBooleanField(entry.dhwBoostActive, `${fieldName}.dhwBoostActive`);
|
|
159
|
+
validateBooleanField(entry.roomPreHeatingActive, `${fieldName}.roomPreHeatingActive`);
|
|
160
|
+
validateBooleanField(entry.bufferTankBoostActive, `${fieldName}.bufferTankBoostActive`);
|
|
161
|
+
}
|
|
162
|
+
function validateMetadata(forecast) {
|
|
163
|
+
if (!(forecast.resolution in RESOLUTION_SECONDS)) {
|
|
164
|
+
throw new ApplianceCommandForecastValidationError(`resolution is invalid: ${forecast.resolution}. Allowed values: ${Object.values(enyo_appliance_command_forecast_js_1.ApplianceForecastResolutionEnum).join(', ')}.`);
|
|
165
|
+
}
|
|
166
|
+
if (forecast.estimatedSavings !== undefined) {
|
|
167
|
+
const savings = forecast.estimatedSavings;
|
|
168
|
+
if (typeof savings.currency !== 'string' || savings.currency.length === 0) {
|
|
169
|
+
throw new ApplianceCommandForecastValidationError('estimatedSavings.currency must be a non-empty string.');
|
|
170
|
+
}
|
|
171
|
+
if (!Number.isFinite(savings.costSavings)) {
|
|
172
|
+
throw new ApplianceCommandForecastValidationError(`estimatedSavings.costSavings must be a finite number; got ${savings.costSavings}.`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
function resolveResolutionSeconds(resolution) {
|
|
177
|
+
const step = RESOLUTION_SECONDS[resolution];
|
|
178
|
+
if (step === undefined) {
|
|
179
|
+
throw new ApplianceCommandForecastValidationError(`resolution is invalid: ${resolution}. Allowed values: ${Object.values(enyo_appliance_command_forecast_js_1.ApplianceForecastResolutionEnum).join(', ')}.`);
|
|
180
|
+
}
|
|
181
|
+
return step;
|
|
182
|
+
}
|
|
183
|
+
function validateNonEmptySchedule(entries, fieldName) {
|
|
184
|
+
if (!Array.isArray(entries) || entries.length === 0) {
|
|
185
|
+
throw new ApplianceCommandForecastValidationError(`${fieldName} must contain at least one entry.`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
function validateSecondsField(seconds, fieldName) {
|
|
189
|
+
if (!Number.isFinite(seconds) || seconds < 0) {
|
|
190
|
+
throw new ApplianceCommandForecastValidationError(`${fieldName}=${seconds} must be a finite non-negative number.`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
function validatePowerW(powerW, fieldName) {
|
|
194
|
+
if (!Number.isFinite(powerW) || powerW < 0) {
|
|
195
|
+
throw new ApplianceCommandForecastValidationError(`${fieldName}=${powerW} must be a finite non-negative number.`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
function validateTemperatureField(value, fieldName) {
|
|
199
|
+
if (value === undefined) {
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
if (!Number.isFinite(value) ||
|
|
203
|
+
value < TEMPERATURE_TRAJECTORY_MIN_C ||
|
|
204
|
+
value > TEMPERATURE_TRAJECTORY_MAX_C) {
|
|
205
|
+
throw new ApplianceCommandForecastValidationError(`${fieldName}=${value} is outside the plausible range [${TEMPERATURE_TRAJECTORY_MIN_C}, ${TEMPERATURE_TRAJECTORY_MAX_C}].`);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
function validateBooleanField(value, fieldName) {
|
|
209
|
+
if (value === undefined) {
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
if (typeof value !== 'boolean') {
|
|
213
|
+
throw new ApplianceCommandForecastValidationError(`${fieldName} must be a boolean when provided; got ${typeof value}.`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
function validateFirstEntryStartsAtZero(firstSeconds, fieldName) {
|
|
217
|
+
if (firstSeconds !== 0) {
|
|
218
|
+
throw new ApplianceCommandForecastValidationError(`${fieldName}[0].seconds must be 0 (got ${firstSeconds}); the receiving appliance needs an authoritative "right now" setpoint.`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
function validateSecondsMatchResolution(secondsList, stepSeconds, fieldName) {
|
|
222
|
+
for (let i = 1; i < secondsList.length; i++) {
|
|
223
|
+
const delta = secondsList[i] - secondsList[i - 1];
|
|
224
|
+
if (delta !== stepSeconds) {
|
|
225
|
+
throw new ApplianceCommandForecastValidationError(`${fieldName}[${i}].seconds (${secondsList[i]}) must be exactly ${stepSeconds}s after the previous entry (${secondsList[i - 1]}); got delta=${delta}s. The forecast's resolution determines the required step.`);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { ApplianceForecastResolutionEnum, BatteryCommandForecast, BatteryCommandForecastScheduleEntry, ChargerForecast, ChargerForecastScheduleEntry, HeatpumpForecast, HeatpumpForecastScheduleEntry } from '../../types/enyo-appliance-command-forecast.cjs';
|
|
2
|
+
/**
|
|
3
|
+
* Thrown when a forecast payload passed to one of the validators (or to
|
|
4
|
+
* {@link EnergyAppApplianceEnergyManagerForecast.publishChargerForecast}
|
|
5
|
+
* and friends) violates the invariants declared on its data interface.
|
|
6
|
+
*
|
|
7
|
+
* The message names the offending field / index so callers can surface it
|
|
8
|
+
* directly to the user.
|
|
9
|
+
*/
|
|
10
|
+
export declare class ApplianceCommandForecastValidationError extends Error {
|
|
11
|
+
constructor(message: string);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Validates a {@link ChargerForecast}. Throws on the first violation —
|
|
15
|
+
* the error message names the offending field / index.
|
|
16
|
+
*/
|
|
17
|
+
export declare function validateChargerForecast(forecast: ChargerForecast): void;
|
|
18
|
+
/**
|
|
19
|
+
* Validates a {@link BatteryCommandForecast}. Throws on the first
|
|
20
|
+
* violation — the error message names the offending field / index.
|
|
21
|
+
*
|
|
22
|
+
* The forecast is a discriminated union on
|
|
23
|
+
* {@link BatteryCommandForecastModeEnum}: when `mode = 'auto'` no
|
|
24
|
+
* schedule is expected; when `mode = 'schedule'` the embedded
|
|
25
|
+
* {@link BatteryCommandForecastScheduled.relativeSchedule} is validated
|
|
26
|
+
* by {@link validateBatterySchedule}.
|
|
27
|
+
*/
|
|
28
|
+
export declare function validateBatteryCommandForecast(forecast: BatteryCommandForecast): void;
|
|
29
|
+
/**
|
|
30
|
+
* Validates a {@link HeatpumpForecast}. Throws on the first violation —
|
|
31
|
+
* the error message names the offending field / index.
|
|
32
|
+
*
|
|
33
|
+
* The forecast carries a single unified relative schedule; every entry
|
|
34
|
+
* is validated by {@link validateHeatpumpScheduleEntry}.
|
|
35
|
+
*/
|
|
36
|
+
export declare function validateHeatpumpForecast(forecast: HeatpumpForecast): void;
|
|
37
|
+
/**
|
|
38
|
+
* Validates a charger relative schedule (the inner schedule used by
|
|
39
|
+
* {@link ChargerForecast.relativeSchedule}). The `resolution` argument
|
|
40
|
+
* is the value declared on
|
|
41
|
+
* {@link ApplianceForecastMetadata.resolution}; consecutive entries'
|
|
42
|
+
* `seconds` must be spaced by exactly that many seconds.
|
|
43
|
+
*/
|
|
44
|
+
export declare function validateChargerSchedule(entries: ChargerForecastScheduleEntry[], resolution: ApplianceForecastResolutionEnum): void;
|
|
45
|
+
/**
|
|
46
|
+
* Validates a battery relative schedule (the inner schedule used by
|
|
47
|
+
* {@link BatteryCommandForecastScheduled.relativeSchedule}). The
|
|
48
|
+
* `resolution` argument is the value declared on
|
|
49
|
+
* {@link ApplianceForecastMetadata.resolution}; consecutive entries'
|
|
50
|
+
* `seconds` must be spaced by exactly that many seconds.
|
|
51
|
+
*/
|
|
52
|
+
export declare function validateBatterySchedule(entries: BatteryCommandForecastScheduleEntry[], resolution: ApplianceForecastResolutionEnum): void;
|
|
53
|
+
/**
|
|
54
|
+
* Validates a heatpump unified relative schedule (the schedule used by
|
|
55
|
+
* {@link HeatpumpForecast.relativeSchedule}). Enforces that the
|
|
56
|
+
* schedule is non-empty, starts at `seconds = 0`, has entries spaced
|
|
57
|
+
* by exactly `resolution`, and that every per-entry value falls in the
|
|
58
|
+
* plausible range documented on
|
|
59
|
+
* {@link HeatpumpForecastScheduleEntry}.
|
|
60
|
+
*/
|
|
61
|
+
export declare function validateHeatpumpSchedule(entries: HeatpumpForecastScheduleEntry[], resolution: ApplianceForecastResolutionEnum): void;
|
|
62
|
+
/**
|
|
63
|
+
* Validates a single {@link HeatpumpForecastScheduleEntry}. Used by
|
|
64
|
+
* {@link validateHeatpumpSchedule} and exposed for callers that build
|
|
65
|
+
* entries incrementally.
|
|
66
|
+
*/
|
|
67
|
+
export declare function validateHeatpumpScheduleEntry(entry: HeatpumpForecastScheduleEntry, fieldName: string): void;
|