@enyo-energy/energy-app-sdk 1.5.0 → 1.6.0
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 +45 -0
- package/dist/cjs/energy-app-permission.type.cjs +1 -0
- package/dist/cjs/energy-app-permission.type.d.cts +2 -1
- package/dist/cjs/energy-app.cjs +16 -0
- package/dist/cjs/energy-app.d.cts +15 -0
- package/dist/cjs/enyo-energy-app-sdk.d.cts +3 -0
- package/dist/cjs/index.cjs +2 -0
- package/dist/cjs/index.d.cts +2 -0
- package/dist/cjs/packages/energy-app-command-log.cjs +2 -0
- package/dist/cjs/packages/energy-app-command-log.d.cts +108 -0
- package/dist/cjs/types/enyo-battery-appliance.d.cts +23 -0
- package/dist/cjs/types/enyo-command-log.cjs +50 -0
- package/dist/cjs/types/enyo-command-log.d.cts +151 -0
- package/dist/cjs/version.cjs +1 -1
- package/dist/cjs/version.d.cts +1 -1
- package/dist/energy-app-permission.type.d.ts +2 -1
- package/dist/energy-app-permission.type.js +1 -0
- package/dist/energy-app.d.ts +15 -0
- package/dist/energy-app.js +16 -0
- package/dist/enyo-energy-app-sdk.d.ts +3 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/packages/energy-app-command-log.d.ts +108 -0
- package/dist/packages/energy-app-command-log.js +1 -0
- package/dist/types/enyo-battery-appliance.d.ts +23 -0
- package/dist/types/enyo-command-log.d.ts +151 -0
- package/dist/types/enyo-command-log.js +47 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -367,6 +367,7 @@ Energy Apps use a granular permissions system to control access to system resour
|
|
|
367
367
|
- **`Savings`**: Publish and read back day-scoped savings reports
|
|
368
368
|
- **`EpexSpotPrices`**: Read EPEX SPOT day-ahead wholesale market prices
|
|
369
369
|
- **`GridFeeRegister`** / **`GridFeeUse`**: Publish / resolve dynamic grid fees (time-variable network charges)
|
|
370
|
+
- **`CommandLog`**: Record and read back the commands apps issued to appliances
|
|
370
371
|
|
|
371
372
|
#### Site & Identity Permissions
|
|
372
373
|
|
|
@@ -1500,6 +1501,50 @@ Notes worth respecting when producing a report:
|
|
|
1500
1501
|
|
|
1501
1502
|
Requires the `Savings` permission.
|
|
1502
1503
|
|
|
1504
|
+
#### `useCommandLog(): EnergyAppCommandLog`
|
|
1505
|
+
|
|
1506
|
+
Record which register, configuration key or message your app wrote to an appliance — and read those
|
|
1507
|
+
writes back. This is the answer to *"why did the device do that?"*: an appliance's state shows what it
|
|
1508
|
+
is doing now, not that you wrote holding register 40023 four seconds ago because the energy manager cut
|
|
1509
|
+
available power, nor that the write was rejected.
|
|
1510
|
+
|
|
1511
|
+
```typescript
|
|
1512
|
+
const commandLog = energyApp.useCommandLog();
|
|
1513
|
+
|
|
1514
|
+
await commandLog.logCommand({
|
|
1515
|
+
applianceId,
|
|
1516
|
+
protocol: EnyoCommandProtocolEnum.Modbus,
|
|
1517
|
+
operation: 'write-single-register',
|
|
1518
|
+
target: '40023',
|
|
1519
|
+
previousValue: String(before),
|
|
1520
|
+
newValue: String(limitW),
|
|
1521
|
+
outcome: EnyoCommandOutcomeEnum.Success,
|
|
1522
|
+
reason: 'energy manager reduced available power',
|
|
1523
|
+
});
|
|
1524
|
+
|
|
1525
|
+
// Everything we failed to write to this appliance today.
|
|
1526
|
+
const failures = await commandLog.getCommands({
|
|
1527
|
+
applianceId,
|
|
1528
|
+
outcome: EnyoCommandOutcomeEnum.Failed,
|
|
1529
|
+
fromIso: startOfDay,
|
|
1530
|
+
});
|
|
1531
|
+
```
|
|
1532
|
+
|
|
1533
|
+
- **Writes, not reads.** Polling a register every two seconds does not belong here; recording reads
|
|
1534
|
+
would bury the writes the log exists for.
|
|
1535
|
+
- **Log the attempt, not only the success.** `Failed`, `TimedOut` and `Rejected` entries are the ones
|
|
1536
|
+
someone goes looking for. `Rejected` is separate from `Failed` on purpose — a device that answered and
|
|
1537
|
+
refused is a different diagnosis from one that could not be reached.
|
|
1538
|
+
- **Fill `reason`.** *What* was written can be reconstructed from the device; *why* cannot.
|
|
1539
|
+
- **Never gate the command on the log.** Log after the write is attempted; a failure to log must not
|
|
1540
|
+
surface as a failure to control the appliance.
|
|
1541
|
+
- **Append-only.** No update, no delete — a log the app can rewrite cannot explain an incident.
|
|
1542
|
+
Retention is the host's.
|
|
1543
|
+
- **Reads span every app** unless you filter by `packageName`: two packages fighting over the same
|
|
1544
|
+
register are invisible from one app's own entries.
|
|
1545
|
+
|
|
1546
|
+
Requires the `CommandLog` permission.
|
|
1547
|
+
|
|
1503
1548
|
### Operational Utilities
|
|
1504
1549
|
|
|
1505
1550
|
#### `useOnboardingV2(): EnergyAppOnboardingV2`
|
|
@@ -62,4 +62,5 @@ var EnergyAppPermissionTypeEnum;
|
|
|
62
62
|
EnergyAppPermissionTypeEnum["FirmwareRegistry"] = "FirmwareRegistry";
|
|
63
63
|
EnergyAppPermissionTypeEnum["GridFeeRegister"] = "GridFeeRegister";
|
|
64
64
|
EnergyAppPermissionTypeEnum["GridFeeUse"] = "GridFeeUse";
|
|
65
|
+
EnergyAppPermissionTypeEnum["CommandLog"] = "CommandLog";
|
|
65
66
|
})(EnergyAppPermissionTypeEnum || (exports.EnergyAppPermissionTypeEnum = EnergyAppPermissionTypeEnum = {}));
|
|
@@ -57,7 +57,8 @@ export declare enum EnergyAppPermissionTypeEnum {
|
|
|
57
57
|
EpexSpotPrices = "EpexSpotPrices",
|
|
58
58
|
FirmwareRegistry = "FirmwareRegistry",
|
|
59
59
|
GridFeeRegister = "GridFeeRegister",
|
|
60
|
-
GridFeeUse = "GridFeeUse"
|
|
60
|
+
GridFeeUse = "GridFeeUse",
|
|
61
|
+
CommandLog = "CommandLog"
|
|
61
62
|
}
|
|
62
63
|
/**
|
|
63
64
|
* String union of every permission name, derived from
|
package/dist/cjs/energy-app.cjs
CHANGED
|
@@ -465,6 +465,22 @@ class EnergyApp {
|
|
|
465
465
|
useGridFee() {
|
|
466
466
|
return this.energyAppSdk.useGridFee();
|
|
467
467
|
}
|
|
468
|
+
/**
|
|
469
|
+
* Gets the Command Log API for recording the writes this app issued to an
|
|
470
|
+
* appliance — which Modbus register, which OCPP configuration key or
|
|
471
|
+
* message, with what it was before and whether the device took it.
|
|
472
|
+
*
|
|
473
|
+
* An appliance's state shows what it is doing; the command log shows what
|
|
474
|
+
* was done to it and why, which is what an incident investigation actually
|
|
475
|
+
* needs. Entries are append-only, and reads span every app so that two
|
|
476
|
+
* packages fighting over the same register are visible.
|
|
477
|
+
* @returns The Command Log API instance
|
|
478
|
+
* @throws {EnergyAppPermissionNotGrantedError} If the `CommandLog`
|
|
479
|
+
* permission is not granted.
|
|
480
|
+
*/
|
|
481
|
+
useCommandLog() {
|
|
482
|
+
return this.energyAppSdk.useCommandLog();
|
|
483
|
+
}
|
|
468
484
|
/**
|
|
469
485
|
* Gets the current SDK version.
|
|
470
486
|
* @returns The semantic version string of the SDK
|
|
@@ -46,6 +46,7 @@ import { EnergyAppSavings } from "./packages/energy-app-savings.cjs";
|
|
|
46
46
|
import { EnergyAppDeviceTest } from "./packages/energy-app-device-test.cjs";
|
|
47
47
|
import { EnergyAppEpexSpotPrice } from "./packages/energy-app-epex-spot-price.cjs";
|
|
48
48
|
import { EnergyAppGridFee } from "./packages/energy-app-grid-fee.cjs";
|
|
49
|
+
import { EnergyAppCommandLog } from "./packages/energy-app-command-log.cjs";
|
|
49
50
|
import { UseFetchOptions } from "./types/enyo-fetch.cjs";
|
|
50
51
|
/**
|
|
51
52
|
* Concrete implementation of {@link EnyoEnergyAppSdk} that delegates every call
|
|
@@ -387,6 +388,20 @@ export declare class EnergyApp implements EnyoEnergyAppSdk {
|
|
|
387
388
|
* `GridFeeUse` permission required by the called method is not granted.
|
|
388
389
|
*/
|
|
389
390
|
useGridFee(): EnergyAppGridFee;
|
|
391
|
+
/**
|
|
392
|
+
* Gets the Command Log API for recording the writes this app issued to an
|
|
393
|
+
* appliance — which Modbus register, which OCPP configuration key or
|
|
394
|
+
* message, with what it was before and whether the device took it.
|
|
395
|
+
*
|
|
396
|
+
* An appliance's state shows what it is doing; the command log shows what
|
|
397
|
+
* was done to it and why, which is what an incident investigation actually
|
|
398
|
+
* needs. Entries are append-only, and reads span every app so that two
|
|
399
|
+
* packages fighting over the same register are visible.
|
|
400
|
+
* @returns The Command Log API instance
|
|
401
|
+
* @throws {EnergyAppPermissionNotGrantedError} If the `CommandLog`
|
|
402
|
+
* permission is not granted.
|
|
403
|
+
*/
|
|
404
|
+
useCommandLog(): EnergyAppCommandLog;
|
|
390
405
|
/**
|
|
391
406
|
* Gets the current SDK version.
|
|
392
407
|
* @returns The semantic version string of the SDK
|
|
@@ -45,6 +45,7 @@ import { EnergyAppSavings } from "./packages/energy-app-savings.cjs";
|
|
|
45
45
|
import { EnergyAppDeviceTest } from "./packages/energy-app-device-test.cjs";
|
|
46
46
|
import { EnergyAppEpexSpotPrice } from "./packages/energy-app-epex-spot-price.cjs";
|
|
47
47
|
import { EnergyAppGridFee } from "./packages/energy-app-grid-fee.cjs";
|
|
48
|
+
import { EnergyAppCommandLog } from "./packages/energy-app-command-log.cjs";
|
|
48
49
|
import { UseFetchOptions } from "./types/enyo-fetch.cjs";
|
|
49
50
|
export declare enum EnergyAppStateEnum {
|
|
50
51
|
Launching = "launching",
|
|
@@ -166,4 +167,6 @@ export interface EnyoEnergyAppSdk {
|
|
|
166
167
|
useEpexSpotPrices: () => EnergyAppEpexSpotPrice;
|
|
167
168
|
/** Get the Grid Fee API for publishing time-dependent network charges and resolving them to a 15-minute series */
|
|
168
169
|
useGridFee: () => EnergyAppGridFee;
|
|
170
|
+
/** Get the Command Log API for recording which register, configuration key or message this app wrote to an appliance, and reading those commands back */
|
|
171
|
+
useCommandLog: () => EnergyAppCommandLog;
|
|
169
172
|
}
|
package/dist/cjs/index.cjs
CHANGED
|
@@ -147,6 +147,8 @@ __exportStar(require("./packages/energy-app-device-test.cjs"), exports);
|
|
|
147
147
|
__exportStar(require("./implementations/device-test/device-test-validators.cjs"), exports);
|
|
148
148
|
__exportStar(require("./types/enyo-savings.cjs"), exports);
|
|
149
149
|
__exportStar(require("./packages/energy-app-savings.cjs"), exports);
|
|
150
|
+
__exportStar(require("./types/enyo-command-log.cjs"), exports);
|
|
151
|
+
__exportStar(require("./packages/energy-app-command-log.cjs"), exports);
|
|
150
152
|
__exportStar(require("./types/enyo-epex-spot-price.cjs"), exports);
|
|
151
153
|
__exportStar(require("./packages/energy-app-epex-spot-price.cjs"), exports);
|
|
152
154
|
__exportStar(require("./types/enyo-automation.cjs"), exports);
|
package/dist/cjs/index.d.cts
CHANGED
|
@@ -131,6 +131,8 @@ export * from './packages/energy-app-device-test.cjs';
|
|
|
131
131
|
export * from './implementations/device-test/device-test-validators.cjs';
|
|
132
132
|
export * from './types/enyo-savings.cjs';
|
|
133
133
|
export * from './packages/energy-app-savings.cjs';
|
|
134
|
+
export * from './types/enyo-command-log.cjs';
|
|
135
|
+
export * from './packages/energy-app-command-log.cjs';
|
|
134
136
|
export * from './types/enyo-epex-spot-price.cjs';
|
|
135
137
|
export * from './packages/energy-app-epex-spot-price.cjs';
|
|
136
138
|
export * from './types/enyo-automation.cjs';
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { EnyoCommandLogEntry, EnyoCommandLogEntryInput, EnyoCommandLogFilter } from "../types/enyo-command-log.cjs";
|
|
2
|
+
/**
|
|
3
|
+
* Interface for recording and reading back the commands an energy app issued to
|
|
4
|
+
* an appliance.
|
|
5
|
+
*
|
|
6
|
+
* This is the answer to "why did the device do that?". An appliance's own state
|
|
7
|
+
* shows what it is doing now; it does not show that this app wrote holding
|
|
8
|
+
* register 40023 four seconds ago because the energy manager cut the available
|
|
9
|
+
* power, nor that the write was rejected. Without a record of the writes, that
|
|
10
|
+
* has to be reconstructed from log files that may already have rotated away.
|
|
11
|
+
*
|
|
12
|
+
* The unit is a **write, not a read**. Polling a register every two seconds does
|
|
13
|
+
* not belong here — an entry is a command that changed, or tried to change,
|
|
14
|
+
* something on the device. Recording reads would bury the writes.
|
|
15
|
+
*
|
|
16
|
+
* Entries are append-only: there is no update and no delete. A log an app can
|
|
17
|
+
* rewrite cannot be trusted to explain an incident, and retention is the host's
|
|
18
|
+
* to enforce.
|
|
19
|
+
*
|
|
20
|
+
* Access to this API requires the `CommandLog` permission
|
|
21
|
+
* ({@link EnergyAppPermissionType}); {@link EnergyApp.useCommandLog} throws when
|
|
22
|
+
* it has not been granted.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```typescript
|
|
26
|
+
* const commandLog = energyApp.useCommandLog();
|
|
27
|
+
*
|
|
28
|
+
* const before = await modbus.readHoldingRegisters(40023, 1);
|
|
29
|
+
* try {
|
|
30
|
+
* await modbus.writeSingleRegister(40023, limitW);
|
|
31
|
+
* await commandLog.logCommand({
|
|
32
|
+
* applianceId,
|
|
33
|
+
* protocol: EnyoCommandProtocolEnum.Modbus,
|
|
34
|
+
* operation: 'write-single-register',
|
|
35
|
+
* target: '40023',
|
|
36
|
+
* previousValue: String(before.readUInt16BE(0)),
|
|
37
|
+
* newValue: String(limitW),
|
|
38
|
+
* outcome: EnyoCommandOutcomeEnum.Success,
|
|
39
|
+
* reason: 'energy manager reduced available power',
|
|
40
|
+
* });
|
|
41
|
+
* } catch (error) {
|
|
42
|
+
* await commandLog.logCommand({
|
|
43
|
+
* applianceId,
|
|
44
|
+
* protocol: EnyoCommandProtocolEnum.Modbus,
|
|
45
|
+
* operation: 'write-single-register',
|
|
46
|
+
* target: '40023',
|
|
47
|
+
* newValue: String(limitW),
|
|
48
|
+
* outcome: EnyoCommandOutcomeEnum.Failed,
|
|
49
|
+
* errorMessage: String(error),
|
|
50
|
+
* reason: 'energy manager reduced available power',
|
|
51
|
+
* });
|
|
52
|
+
* }
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export interface EnergyAppCommandLog {
|
|
56
|
+
/**
|
|
57
|
+
* Appends one command to the log.
|
|
58
|
+
*
|
|
59
|
+
* Log the attempt, not only the success: a command that timed out or was
|
|
60
|
+
* rejected is the entry someone will actually go looking for, and an app
|
|
61
|
+
* that only records its successes produces a log where nothing ever went
|
|
62
|
+
* wrong. Set {@link EnyoCommandLogEntry.outcome} accordingly and put the
|
|
63
|
+
* detail in `errorMessage`.
|
|
64
|
+
*
|
|
65
|
+
* Fill {@link EnyoCommandLogEntry.reason} whenever the app knows it. What was
|
|
66
|
+
* written can be reconstructed from the device; why it was written cannot.
|
|
67
|
+
*
|
|
68
|
+
* This call is for auditing and must never gate the command itself — log
|
|
69
|
+
* after the write has been attempted, and do not let a failure to log
|
|
70
|
+
* surface as a failure to control the appliance.
|
|
71
|
+
*
|
|
72
|
+
* Requires the `CommandLog` permission.
|
|
73
|
+
*
|
|
74
|
+
* @param entry - The command to record. `id`, `packageName` and (unless
|
|
75
|
+
* supplied) `timestampIso` are assigned by the host.
|
|
76
|
+
* @returns Promise resolving to the stored entry, including its assigned id
|
|
77
|
+
* @throws {EnergyAppPermissionNotGrantedError} If the `CommandLog` permission
|
|
78
|
+
* is not granted.
|
|
79
|
+
*/
|
|
80
|
+
logCommand(entry: EnyoCommandLogEntryInput): Promise<EnyoCommandLogEntry>;
|
|
81
|
+
/**
|
|
82
|
+
* Reads recorded commands back, newest first.
|
|
83
|
+
*
|
|
84
|
+
* Returns entries from **every** app, not just this one, unless
|
|
85
|
+
* {@link EnyoCommandLogFilter.packageName} narrows it. That is deliberate:
|
|
86
|
+
* the failure this log exists to explain is often two apps writing the same
|
|
87
|
+
* register, which is invisible from either app's own entries.
|
|
88
|
+
*
|
|
89
|
+
* Requires the `CommandLog` permission.
|
|
90
|
+
*
|
|
91
|
+
* @param filter - Optional narrowing by appliance, protocol, outcome, time
|
|
92
|
+
* range or package. Fields combine with AND.
|
|
93
|
+
* @returns Promise resolving to the matching entries, newest first
|
|
94
|
+
* @throws {EnergyAppPermissionNotGrantedError} If the `CommandLog` permission
|
|
95
|
+
* is not granted.
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* ```typescript
|
|
99
|
+
* // Everything we failed to write to this appliance today.
|
|
100
|
+
* const failures = await commandLog.getCommands({
|
|
101
|
+
* applianceId,
|
|
102
|
+
* outcome: EnyoCommandOutcomeEnum.Failed,
|
|
103
|
+
* fromIso: startOfDay,
|
|
104
|
+
* });
|
|
105
|
+
* ```
|
|
106
|
+
*/
|
|
107
|
+
getCommands(filter?: EnyoCommandLogFilter): Promise<EnyoCommandLogEntry[]>;
|
|
108
|
+
}
|
|
@@ -65,4 +65,27 @@ export interface EnyoBatteryApplianceMetadata {
|
|
|
65
65
|
* (0–100). The EMS should not charge the battery above this value.
|
|
66
66
|
*/
|
|
67
67
|
maxSoC?: number;
|
|
68
|
+
/**
|
|
69
|
+
* Observed round-trip efficiency as a percentage (0–100): of the energy put
|
|
70
|
+
* into the battery, how much comes back out again.
|
|
71
|
+
*
|
|
72
|
+
* A **calculated** value, not a nameplate figure — derived from measured
|
|
73
|
+
* charge and discharge energy over completed cycles. It therefore drifts
|
|
74
|
+
* with cell age, temperature and depth of discharge, and is absent until
|
|
75
|
+
* enough cycles have been observed to compute it. Consumers should treat a
|
|
76
|
+
* missing value as "not yet known" rather than substituting a datasheet
|
|
77
|
+
* number.
|
|
78
|
+
*
|
|
79
|
+
* This is what decides whether a charge/discharge cycle is worth running at
|
|
80
|
+
* all: arbitrage pays only when the price spread exceeds the energy lost to
|
|
81
|
+
* the round trip, so an optimizer that assumes 100% will schedule cycles
|
|
82
|
+
* that lose money.
|
|
83
|
+
*
|
|
84
|
+
* Measure it at the same boundary the battery reports its power and meter
|
|
85
|
+
* values at. An AC-to-AC round trip includes the inverter's conversion
|
|
86
|
+
* losses in both directions and typically lands several points below the
|
|
87
|
+
* DC-to-DC figure for the same pack — quoting one where the other is
|
|
88
|
+
* expected silently misprices every cycle.
|
|
89
|
+
*/
|
|
90
|
+
roundTripEfficiencyPercent?: number;
|
|
68
91
|
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.EnyoCommandOutcomeEnum = exports.EnyoCommandProtocolEnum = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* The protocol a logged command was issued over.
|
|
6
|
+
*
|
|
7
|
+
* Names the transport, not the appliance: the same wallbox may be written to
|
|
8
|
+
* over Modbus TCP for power limits and over OCPP for configuration keys, and an
|
|
9
|
+
* entry is only interpretable once you know which.
|
|
10
|
+
*/
|
|
11
|
+
var EnyoCommandProtocolEnum;
|
|
12
|
+
(function (EnyoCommandProtocolEnum) {
|
|
13
|
+
/** Modbus TCP */
|
|
14
|
+
EnyoCommandProtocolEnum["Modbus"] = "modbus";
|
|
15
|
+
/** Modbus RTU (serial) */
|
|
16
|
+
EnyoCommandProtocolEnum["ModbusRtu"] = "modbus-rtu";
|
|
17
|
+
/** OCPP — a configuration change or a call sent to a charge point */
|
|
18
|
+
EnyoCommandProtocolEnum["Ocpp"] = "ocpp";
|
|
19
|
+
/** EEBUS SHIP/SPINE */
|
|
20
|
+
EnyoCommandProtocolEnum["Eebus"] = "eebus";
|
|
21
|
+
/** MQTT publish */
|
|
22
|
+
EnyoCommandProtocolEnum["Mqtt"] = "mqtt";
|
|
23
|
+
/** HTTP request against a vendor API or local endpoint */
|
|
24
|
+
EnyoCommandProtocolEnum["Http"] = "http";
|
|
25
|
+
/** Bluetooth Low Energy GATT write */
|
|
26
|
+
EnyoCommandProtocolEnum["Bluetooth"] = "bluetooth";
|
|
27
|
+
/** UDP datagram */
|
|
28
|
+
EnyoCommandProtocolEnum["Udp"] = "udp";
|
|
29
|
+
/** Anything else — describe it in {@link EnyoCommandLogEntry.operation} */
|
|
30
|
+
EnyoCommandProtocolEnum["Other"] = "other";
|
|
31
|
+
})(EnyoCommandProtocolEnum || (exports.EnyoCommandProtocolEnum = EnyoCommandProtocolEnum = {}));
|
|
32
|
+
/**
|
|
33
|
+
* How a logged command ended.
|
|
34
|
+
*
|
|
35
|
+
* {@link Rejected} is deliberately separate from {@link Failed}: a device that
|
|
36
|
+
* answered and refused is a very different diagnosis from one that could not be
|
|
37
|
+
* reached, and collapsing the two is what makes a log unable to answer "did it
|
|
38
|
+
* hear us?".
|
|
39
|
+
*/
|
|
40
|
+
var EnyoCommandOutcomeEnum;
|
|
41
|
+
(function (EnyoCommandOutcomeEnum) {
|
|
42
|
+
/** The device accepted the command. */
|
|
43
|
+
EnyoCommandOutcomeEnum["Success"] = "success";
|
|
44
|
+
/** The command could not be delivered or the device errored. */
|
|
45
|
+
EnyoCommandOutcomeEnum["Failed"] = "failed";
|
|
46
|
+
/** No answer arrived within the app's deadline. */
|
|
47
|
+
EnyoCommandOutcomeEnum["TimedOut"] = "timed-out";
|
|
48
|
+
/** The device answered and refused the command. */
|
|
49
|
+
EnyoCommandOutcomeEnum["Rejected"] = "rejected";
|
|
50
|
+
})(EnyoCommandOutcomeEnum || (exports.EnyoCommandOutcomeEnum = EnyoCommandOutcomeEnum = {}));
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The protocol a logged command was issued over.
|
|
3
|
+
*
|
|
4
|
+
* Names the transport, not the appliance: the same wallbox may be written to
|
|
5
|
+
* over Modbus TCP for power limits and over OCPP for configuration keys, and an
|
|
6
|
+
* entry is only interpretable once you know which.
|
|
7
|
+
*/
|
|
8
|
+
export declare enum EnyoCommandProtocolEnum {
|
|
9
|
+
/** Modbus TCP */
|
|
10
|
+
Modbus = "modbus",
|
|
11
|
+
/** Modbus RTU (serial) */
|
|
12
|
+
ModbusRtu = "modbus-rtu",
|
|
13
|
+
/** OCPP — a configuration change or a call sent to a charge point */
|
|
14
|
+
Ocpp = "ocpp",
|
|
15
|
+
/** EEBUS SHIP/SPINE */
|
|
16
|
+
Eebus = "eebus",
|
|
17
|
+
/** MQTT publish */
|
|
18
|
+
Mqtt = "mqtt",
|
|
19
|
+
/** HTTP request against a vendor API or local endpoint */
|
|
20
|
+
Http = "http",
|
|
21
|
+
/** Bluetooth Low Energy GATT write */
|
|
22
|
+
Bluetooth = "bluetooth",
|
|
23
|
+
/** UDP datagram */
|
|
24
|
+
Udp = "udp",
|
|
25
|
+
/** Anything else — describe it in {@link EnyoCommandLogEntry.operation} */
|
|
26
|
+
Other = "other"
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* How a logged command ended.
|
|
30
|
+
*
|
|
31
|
+
* {@link Rejected} is deliberately separate from {@link Failed}: a device that
|
|
32
|
+
* answered and refused is a very different diagnosis from one that could not be
|
|
33
|
+
* reached, and collapsing the two is what makes a log unable to answer "did it
|
|
34
|
+
* hear us?".
|
|
35
|
+
*/
|
|
36
|
+
export declare enum EnyoCommandOutcomeEnum {
|
|
37
|
+
/** The device accepted the command. */
|
|
38
|
+
Success = "success",
|
|
39
|
+
/** The command could not be delivered or the device errored. */
|
|
40
|
+
Failed = "failed",
|
|
41
|
+
/** No answer arrived within the app's deadline. */
|
|
42
|
+
TimedOut = "timed-out",
|
|
43
|
+
/** The device answered and refused the command. */
|
|
44
|
+
Rejected = "rejected"
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* One recorded command an energy app issued to an appliance.
|
|
48
|
+
*
|
|
49
|
+
* The unit of the log is a **write**, not a read: which register was set, which
|
|
50
|
+
* OCPP configuration key was changed, which message was executed — with what it
|
|
51
|
+
* was before, what it became, and whether the device took it.
|
|
52
|
+
*/
|
|
53
|
+
export interface EnyoCommandLogEntry {
|
|
54
|
+
/** Unique identifier of this entry. Assigned by the host. */
|
|
55
|
+
id: string;
|
|
56
|
+
/** The appliance the command was issued to. */
|
|
57
|
+
applianceId: string;
|
|
58
|
+
/** When the command was issued, in ISO format. */
|
|
59
|
+
timestampIso: string;
|
|
60
|
+
/** The package that issued it. Set by the host, not by the caller. */
|
|
61
|
+
packageName: string;
|
|
62
|
+
/** Which protocol carried the command. */
|
|
63
|
+
protocol: EnyoCommandProtocolEnum;
|
|
64
|
+
/**
|
|
65
|
+
* What was done, in the protocol's own vocabulary — `'write-holding-registers'`,
|
|
66
|
+
* `'ChangeConfiguration'`, `'SetChargingProfile'`, `'gatt-write'`.
|
|
67
|
+
*
|
|
68
|
+
* Use the name the protocol uses rather than a prose description, so entries
|
|
69
|
+
* from one protocol group and compare.
|
|
70
|
+
*/
|
|
71
|
+
operation: string;
|
|
72
|
+
/**
|
|
73
|
+
* What was addressed: a register address (`'40023'`), an OCPP configuration
|
|
74
|
+
* key (`'MeterValueSampleInterval'`), an MQTT topic, a GATT characteristic
|
|
75
|
+
* UUID. Omit for commands that address nothing in particular.
|
|
76
|
+
*/
|
|
77
|
+
target?: string;
|
|
78
|
+
/**
|
|
79
|
+
* The value before the write, rendered as a string. Omit when it was not
|
|
80
|
+
* read back first — do not substitute a value the app merely believed was
|
|
81
|
+
* there, or the log will explain changes that never happened.
|
|
82
|
+
*/
|
|
83
|
+
previousValue?: string;
|
|
84
|
+
/** The value written, rendered as a string. */
|
|
85
|
+
newValue?: string;
|
|
86
|
+
/** How the command ended. */
|
|
87
|
+
outcome: EnyoCommandOutcomeEnum;
|
|
88
|
+
/**
|
|
89
|
+
* Why the app issued the command — `'energy manager reduced available power'`,
|
|
90
|
+
* `'user changed charge mode'`.
|
|
91
|
+
*
|
|
92
|
+
* The single most useful field when reading the log back: the *what* can be
|
|
93
|
+
* reconstructed from the device, the *why* cannot.
|
|
94
|
+
*/
|
|
95
|
+
reason?: string;
|
|
96
|
+
/**
|
|
97
|
+
* The error or rejection detail, for any outcome other than
|
|
98
|
+
* {@link EnyoCommandOutcomeEnum.Success}.
|
|
99
|
+
*/
|
|
100
|
+
errorMessage?: string;
|
|
101
|
+
/** How long the command took, in milliseconds. */
|
|
102
|
+
durationMs?: number;
|
|
103
|
+
/**
|
|
104
|
+
* Anything the flat fields cannot carry — a decoded payload, the full
|
|
105
|
+
* register block written, the OCPP response object.
|
|
106
|
+
*
|
|
107
|
+
* Values above carry strings so entries from different protocols share one
|
|
108
|
+
* shape and can be rendered in one table; structured data belongs here.
|
|
109
|
+
*/
|
|
110
|
+
details?: Record<string, unknown>;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* What an app passes to {@link EnergyAppCommandLog.logCommand}.
|
|
114
|
+
*
|
|
115
|
+
* `id` and `packageName` are assigned by the host. `timestampIso` may be
|
|
116
|
+
* supplied when the command was issued earlier than it is being logged —
|
|
117
|
+
* a batch flushed after a reconnect — and otherwise defaults to the moment the
|
|
118
|
+
* host handles the call.
|
|
119
|
+
*/
|
|
120
|
+
export type EnyoCommandLogEntryInput = Omit<EnyoCommandLogEntry, 'id' | 'packageName' | 'timestampIso'> & {
|
|
121
|
+
timestampIso?: string;
|
|
122
|
+
};
|
|
123
|
+
/**
|
|
124
|
+
* Narrows which entries {@link EnergyAppCommandLog.getCommands} returns.
|
|
125
|
+
*
|
|
126
|
+
* Every field is optional and they combine with AND. With no filter at all, the
|
|
127
|
+
* most recent entries across every appliance are returned.
|
|
128
|
+
*/
|
|
129
|
+
export interface EnyoCommandLogFilter {
|
|
130
|
+
/** Only commands issued to this appliance. */
|
|
131
|
+
applianceId?: string;
|
|
132
|
+
/** Only commands carried over this protocol. */
|
|
133
|
+
protocol?: EnyoCommandProtocolEnum;
|
|
134
|
+
/** Only commands that ended this way — e.g. every failure in the last day. */
|
|
135
|
+
outcome?: EnyoCommandOutcomeEnum;
|
|
136
|
+
/** Only commands issued at or after this ISO timestamp (inclusive). */
|
|
137
|
+
fromIso?: string;
|
|
138
|
+
/** Only commands issued before this ISO timestamp (exclusive). */
|
|
139
|
+
untilIso?: string;
|
|
140
|
+
/**
|
|
141
|
+
* Only commands issued by this package. Omit to see every app's commands —
|
|
142
|
+
* a device misbehaving because two apps are writing the same register is
|
|
143
|
+
* invisible from one app's own entries.
|
|
144
|
+
*/
|
|
145
|
+
packageName?: string;
|
|
146
|
+
/**
|
|
147
|
+
* Maximum number of entries to return, newest first. The host applies its
|
|
148
|
+
* own upper bound when this is omitted or larger than that bound.
|
|
149
|
+
*/
|
|
150
|
+
limit?: number;
|
|
151
|
+
}
|
package/dist/cjs/version.cjs
CHANGED
package/dist/cjs/version.d.cts
CHANGED
|
@@ -57,7 +57,8 @@ export declare enum EnergyAppPermissionTypeEnum {
|
|
|
57
57
|
EpexSpotPrices = "EpexSpotPrices",
|
|
58
58
|
FirmwareRegistry = "FirmwareRegistry",
|
|
59
59
|
GridFeeRegister = "GridFeeRegister",
|
|
60
|
-
GridFeeUse = "GridFeeUse"
|
|
60
|
+
GridFeeUse = "GridFeeUse",
|
|
61
|
+
CommandLog = "CommandLog"
|
|
61
62
|
}
|
|
62
63
|
/**
|
|
63
64
|
* String union of every permission name, derived from
|
|
@@ -59,4 +59,5 @@ export var EnergyAppPermissionTypeEnum;
|
|
|
59
59
|
EnergyAppPermissionTypeEnum["FirmwareRegistry"] = "FirmwareRegistry";
|
|
60
60
|
EnergyAppPermissionTypeEnum["GridFeeRegister"] = "GridFeeRegister";
|
|
61
61
|
EnergyAppPermissionTypeEnum["GridFeeUse"] = "GridFeeUse";
|
|
62
|
+
EnergyAppPermissionTypeEnum["CommandLog"] = "CommandLog";
|
|
62
63
|
})(EnergyAppPermissionTypeEnum || (EnergyAppPermissionTypeEnum = {}));
|
package/dist/energy-app.d.ts
CHANGED
|
@@ -46,6 +46,7 @@ import { EnergyAppSavings } from "./packages/energy-app-savings.js";
|
|
|
46
46
|
import { EnergyAppDeviceTest } from "./packages/energy-app-device-test.js";
|
|
47
47
|
import { EnergyAppEpexSpotPrice } from "./packages/energy-app-epex-spot-price.js";
|
|
48
48
|
import { EnergyAppGridFee } from "./packages/energy-app-grid-fee.js";
|
|
49
|
+
import { EnergyAppCommandLog } from "./packages/energy-app-command-log.js";
|
|
49
50
|
import { UseFetchOptions } from "./types/enyo-fetch.js";
|
|
50
51
|
/**
|
|
51
52
|
* Concrete implementation of {@link EnyoEnergyAppSdk} that delegates every call
|
|
@@ -387,6 +388,20 @@ export declare class EnergyApp implements EnyoEnergyAppSdk {
|
|
|
387
388
|
* `GridFeeUse` permission required by the called method is not granted.
|
|
388
389
|
*/
|
|
389
390
|
useGridFee(): EnergyAppGridFee;
|
|
391
|
+
/**
|
|
392
|
+
* Gets the Command Log API for recording the writes this app issued to an
|
|
393
|
+
* appliance — which Modbus register, which OCPP configuration key or
|
|
394
|
+
* message, with what it was before and whether the device took it.
|
|
395
|
+
*
|
|
396
|
+
* An appliance's state shows what it is doing; the command log shows what
|
|
397
|
+
* was done to it and why, which is what an incident investigation actually
|
|
398
|
+
* needs. Entries are append-only, and reads span every app so that two
|
|
399
|
+
* packages fighting over the same register are visible.
|
|
400
|
+
* @returns The Command Log API instance
|
|
401
|
+
* @throws {EnergyAppPermissionNotGrantedError} If the `CommandLog`
|
|
402
|
+
* permission is not granted.
|
|
403
|
+
*/
|
|
404
|
+
useCommandLog(): EnergyAppCommandLog;
|
|
390
405
|
/**
|
|
391
406
|
* Gets the current SDK version.
|
|
392
407
|
* @returns The semantic version string of the SDK
|
package/dist/energy-app.js
CHANGED
|
@@ -462,6 +462,22 @@ export class EnergyApp {
|
|
|
462
462
|
useGridFee() {
|
|
463
463
|
return this.energyAppSdk.useGridFee();
|
|
464
464
|
}
|
|
465
|
+
/**
|
|
466
|
+
* Gets the Command Log API for recording the writes this app issued to an
|
|
467
|
+
* appliance — which Modbus register, which OCPP configuration key or
|
|
468
|
+
* message, with what it was before and whether the device took it.
|
|
469
|
+
*
|
|
470
|
+
* An appliance's state shows what it is doing; the command log shows what
|
|
471
|
+
* was done to it and why, which is what an incident investigation actually
|
|
472
|
+
* needs. Entries are append-only, and reads span every app so that two
|
|
473
|
+
* packages fighting over the same register are visible.
|
|
474
|
+
* @returns The Command Log API instance
|
|
475
|
+
* @throws {EnergyAppPermissionNotGrantedError} If the `CommandLog`
|
|
476
|
+
* permission is not granted.
|
|
477
|
+
*/
|
|
478
|
+
useCommandLog() {
|
|
479
|
+
return this.energyAppSdk.useCommandLog();
|
|
480
|
+
}
|
|
465
481
|
/**
|
|
466
482
|
* Gets the current SDK version.
|
|
467
483
|
* @returns The semantic version string of the SDK
|
|
@@ -45,6 +45,7 @@ import { EnergyAppSavings } from "./packages/energy-app-savings.js";
|
|
|
45
45
|
import { EnergyAppDeviceTest } from "./packages/energy-app-device-test.js";
|
|
46
46
|
import { EnergyAppEpexSpotPrice } from "./packages/energy-app-epex-spot-price.js";
|
|
47
47
|
import { EnergyAppGridFee } from "./packages/energy-app-grid-fee.js";
|
|
48
|
+
import { EnergyAppCommandLog } from "./packages/energy-app-command-log.js";
|
|
48
49
|
import { UseFetchOptions } from "./types/enyo-fetch.js";
|
|
49
50
|
export declare enum EnergyAppStateEnum {
|
|
50
51
|
Launching = "launching",
|
|
@@ -166,4 +167,6 @@ export interface EnyoEnergyAppSdk {
|
|
|
166
167
|
useEpexSpotPrices: () => EnergyAppEpexSpotPrice;
|
|
167
168
|
/** Get the Grid Fee API for publishing time-dependent network charges and resolving them to a 15-minute series */
|
|
168
169
|
useGridFee: () => EnergyAppGridFee;
|
|
170
|
+
/** Get the Command Log API for recording which register, configuration key or message this app wrote to an appliance, and reading those commands back */
|
|
171
|
+
useCommandLog: () => EnergyAppCommandLog;
|
|
169
172
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -131,6 +131,8 @@ export * from './packages/energy-app-device-test.js';
|
|
|
131
131
|
export * from './implementations/device-test/device-test-validators.js';
|
|
132
132
|
export * from './types/enyo-savings.js';
|
|
133
133
|
export * from './packages/energy-app-savings.js';
|
|
134
|
+
export * from './types/enyo-command-log.js';
|
|
135
|
+
export * from './packages/energy-app-command-log.js';
|
|
134
136
|
export * from './types/enyo-epex-spot-price.js';
|
|
135
137
|
export * from './packages/energy-app-epex-spot-price.js';
|
|
136
138
|
export * from './types/enyo-automation.js';
|
package/dist/index.js
CHANGED
|
@@ -131,6 +131,8 @@ export * from './packages/energy-app-device-test.js';
|
|
|
131
131
|
export * from './implementations/device-test/device-test-validators.js';
|
|
132
132
|
export * from './types/enyo-savings.js';
|
|
133
133
|
export * from './packages/energy-app-savings.js';
|
|
134
|
+
export * from './types/enyo-command-log.js';
|
|
135
|
+
export * from './packages/energy-app-command-log.js';
|
|
134
136
|
export * from './types/enyo-epex-spot-price.js';
|
|
135
137
|
export * from './packages/energy-app-epex-spot-price.js';
|
|
136
138
|
export * from './types/enyo-automation.js';
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { EnyoCommandLogEntry, EnyoCommandLogEntryInput, EnyoCommandLogFilter } from "../types/enyo-command-log.js";
|
|
2
|
+
/**
|
|
3
|
+
* Interface for recording and reading back the commands an energy app issued to
|
|
4
|
+
* an appliance.
|
|
5
|
+
*
|
|
6
|
+
* This is the answer to "why did the device do that?". An appliance's own state
|
|
7
|
+
* shows what it is doing now; it does not show that this app wrote holding
|
|
8
|
+
* register 40023 four seconds ago because the energy manager cut the available
|
|
9
|
+
* power, nor that the write was rejected. Without a record of the writes, that
|
|
10
|
+
* has to be reconstructed from log files that may already have rotated away.
|
|
11
|
+
*
|
|
12
|
+
* The unit is a **write, not a read**. Polling a register every two seconds does
|
|
13
|
+
* not belong here — an entry is a command that changed, or tried to change,
|
|
14
|
+
* something on the device. Recording reads would bury the writes.
|
|
15
|
+
*
|
|
16
|
+
* Entries are append-only: there is no update and no delete. A log an app can
|
|
17
|
+
* rewrite cannot be trusted to explain an incident, and retention is the host's
|
|
18
|
+
* to enforce.
|
|
19
|
+
*
|
|
20
|
+
* Access to this API requires the `CommandLog` permission
|
|
21
|
+
* ({@link EnergyAppPermissionType}); {@link EnergyApp.useCommandLog} throws when
|
|
22
|
+
* it has not been granted.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```typescript
|
|
26
|
+
* const commandLog = energyApp.useCommandLog();
|
|
27
|
+
*
|
|
28
|
+
* const before = await modbus.readHoldingRegisters(40023, 1);
|
|
29
|
+
* try {
|
|
30
|
+
* await modbus.writeSingleRegister(40023, limitW);
|
|
31
|
+
* await commandLog.logCommand({
|
|
32
|
+
* applianceId,
|
|
33
|
+
* protocol: EnyoCommandProtocolEnum.Modbus,
|
|
34
|
+
* operation: 'write-single-register',
|
|
35
|
+
* target: '40023',
|
|
36
|
+
* previousValue: String(before.readUInt16BE(0)),
|
|
37
|
+
* newValue: String(limitW),
|
|
38
|
+
* outcome: EnyoCommandOutcomeEnum.Success,
|
|
39
|
+
* reason: 'energy manager reduced available power',
|
|
40
|
+
* });
|
|
41
|
+
* } catch (error) {
|
|
42
|
+
* await commandLog.logCommand({
|
|
43
|
+
* applianceId,
|
|
44
|
+
* protocol: EnyoCommandProtocolEnum.Modbus,
|
|
45
|
+
* operation: 'write-single-register',
|
|
46
|
+
* target: '40023',
|
|
47
|
+
* newValue: String(limitW),
|
|
48
|
+
* outcome: EnyoCommandOutcomeEnum.Failed,
|
|
49
|
+
* errorMessage: String(error),
|
|
50
|
+
* reason: 'energy manager reduced available power',
|
|
51
|
+
* });
|
|
52
|
+
* }
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export interface EnergyAppCommandLog {
|
|
56
|
+
/**
|
|
57
|
+
* Appends one command to the log.
|
|
58
|
+
*
|
|
59
|
+
* Log the attempt, not only the success: a command that timed out or was
|
|
60
|
+
* rejected is the entry someone will actually go looking for, and an app
|
|
61
|
+
* that only records its successes produces a log where nothing ever went
|
|
62
|
+
* wrong. Set {@link EnyoCommandLogEntry.outcome} accordingly and put the
|
|
63
|
+
* detail in `errorMessage`.
|
|
64
|
+
*
|
|
65
|
+
* Fill {@link EnyoCommandLogEntry.reason} whenever the app knows it. What was
|
|
66
|
+
* written can be reconstructed from the device; why it was written cannot.
|
|
67
|
+
*
|
|
68
|
+
* This call is for auditing and must never gate the command itself — log
|
|
69
|
+
* after the write has been attempted, and do not let a failure to log
|
|
70
|
+
* surface as a failure to control the appliance.
|
|
71
|
+
*
|
|
72
|
+
* Requires the `CommandLog` permission.
|
|
73
|
+
*
|
|
74
|
+
* @param entry - The command to record. `id`, `packageName` and (unless
|
|
75
|
+
* supplied) `timestampIso` are assigned by the host.
|
|
76
|
+
* @returns Promise resolving to the stored entry, including its assigned id
|
|
77
|
+
* @throws {EnergyAppPermissionNotGrantedError} If the `CommandLog` permission
|
|
78
|
+
* is not granted.
|
|
79
|
+
*/
|
|
80
|
+
logCommand(entry: EnyoCommandLogEntryInput): Promise<EnyoCommandLogEntry>;
|
|
81
|
+
/**
|
|
82
|
+
* Reads recorded commands back, newest first.
|
|
83
|
+
*
|
|
84
|
+
* Returns entries from **every** app, not just this one, unless
|
|
85
|
+
* {@link EnyoCommandLogFilter.packageName} narrows it. That is deliberate:
|
|
86
|
+
* the failure this log exists to explain is often two apps writing the same
|
|
87
|
+
* register, which is invisible from either app's own entries.
|
|
88
|
+
*
|
|
89
|
+
* Requires the `CommandLog` permission.
|
|
90
|
+
*
|
|
91
|
+
* @param filter - Optional narrowing by appliance, protocol, outcome, time
|
|
92
|
+
* range or package. Fields combine with AND.
|
|
93
|
+
* @returns Promise resolving to the matching entries, newest first
|
|
94
|
+
* @throws {EnergyAppPermissionNotGrantedError} If the `CommandLog` permission
|
|
95
|
+
* is not granted.
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* ```typescript
|
|
99
|
+
* // Everything we failed to write to this appliance today.
|
|
100
|
+
* const failures = await commandLog.getCommands({
|
|
101
|
+
* applianceId,
|
|
102
|
+
* outcome: EnyoCommandOutcomeEnum.Failed,
|
|
103
|
+
* fromIso: startOfDay,
|
|
104
|
+
* });
|
|
105
|
+
* ```
|
|
106
|
+
*/
|
|
107
|
+
getCommands(filter?: EnyoCommandLogFilter): Promise<EnyoCommandLogEntry[]>;
|
|
108
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -65,4 +65,27 @@ export interface EnyoBatteryApplianceMetadata {
|
|
|
65
65
|
* (0–100). The EMS should not charge the battery above this value.
|
|
66
66
|
*/
|
|
67
67
|
maxSoC?: number;
|
|
68
|
+
/**
|
|
69
|
+
* Observed round-trip efficiency as a percentage (0–100): of the energy put
|
|
70
|
+
* into the battery, how much comes back out again.
|
|
71
|
+
*
|
|
72
|
+
* A **calculated** value, not a nameplate figure — derived from measured
|
|
73
|
+
* charge and discharge energy over completed cycles. It therefore drifts
|
|
74
|
+
* with cell age, temperature and depth of discharge, and is absent until
|
|
75
|
+
* enough cycles have been observed to compute it. Consumers should treat a
|
|
76
|
+
* missing value as "not yet known" rather than substituting a datasheet
|
|
77
|
+
* number.
|
|
78
|
+
*
|
|
79
|
+
* This is what decides whether a charge/discharge cycle is worth running at
|
|
80
|
+
* all: arbitrage pays only when the price spread exceeds the energy lost to
|
|
81
|
+
* the round trip, so an optimizer that assumes 100% will schedule cycles
|
|
82
|
+
* that lose money.
|
|
83
|
+
*
|
|
84
|
+
* Measure it at the same boundary the battery reports its power and meter
|
|
85
|
+
* values at. An AC-to-AC round trip includes the inverter's conversion
|
|
86
|
+
* losses in both directions and typically lands several points below the
|
|
87
|
+
* DC-to-DC figure for the same pack — quoting one where the other is
|
|
88
|
+
* expected silently misprices every cycle.
|
|
89
|
+
*/
|
|
90
|
+
roundTripEfficiencyPercent?: number;
|
|
68
91
|
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The protocol a logged command was issued over.
|
|
3
|
+
*
|
|
4
|
+
* Names the transport, not the appliance: the same wallbox may be written to
|
|
5
|
+
* over Modbus TCP for power limits and over OCPP for configuration keys, and an
|
|
6
|
+
* entry is only interpretable once you know which.
|
|
7
|
+
*/
|
|
8
|
+
export declare enum EnyoCommandProtocolEnum {
|
|
9
|
+
/** Modbus TCP */
|
|
10
|
+
Modbus = "modbus",
|
|
11
|
+
/** Modbus RTU (serial) */
|
|
12
|
+
ModbusRtu = "modbus-rtu",
|
|
13
|
+
/** OCPP — a configuration change or a call sent to a charge point */
|
|
14
|
+
Ocpp = "ocpp",
|
|
15
|
+
/** EEBUS SHIP/SPINE */
|
|
16
|
+
Eebus = "eebus",
|
|
17
|
+
/** MQTT publish */
|
|
18
|
+
Mqtt = "mqtt",
|
|
19
|
+
/** HTTP request against a vendor API or local endpoint */
|
|
20
|
+
Http = "http",
|
|
21
|
+
/** Bluetooth Low Energy GATT write */
|
|
22
|
+
Bluetooth = "bluetooth",
|
|
23
|
+
/** UDP datagram */
|
|
24
|
+
Udp = "udp",
|
|
25
|
+
/** Anything else — describe it in {@link EnyoCommandLogEntry.operation} */
|
|
26
|
+
Other = "other"
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* How a logged command ended.
|
|
30
|
+
*
|
|
31
|
+
* {@link Rejected} is deliberately separate from {@link Failed}: a device that
|
|
32
|
+
* answered and refused is a very different diagnosis from one that could not be
|
|
33
|
+
* reached, and collapsing the two is what makes a log unable to answer "did it
|
|
34
|
+
* hear us?".
|
|
35
|
+
*/
|
|
36
|
+
export declare enum EnyoCommandOutcomeEnum {
|
|
37
|
+
/** The device accepted the command. */
|
|
38
|
+
Success = "success",
|
|
39
|
+
/** The command could not be delivered or the device errored. */
|
|
40
|
+
Failed = "failed",
|
|
41
|
+
/** No answer arrived within the app's deadline. */
|
|
42
|
+
TimedOut = "timed-out",
|
|
43
|
+
/** The device answered and refused the command. */
|
|
44
|
+
Rejected = "rejected"
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* One recorded command an energy app issued to an appliance.
|
|
48
|
+
*
|
|
49
|
+
* The unit of the log is a **write**, not a read: which register was set, which
|
|
50
|
+
* OCPP configuration key was changed, which message was executed — with what it
|
|
51
|
+
* was before, what it became, and whether the device took it.
|
|
52
|
+
*/
|
|
53
|
+
export interface EnyoCommandLogEntry {
|
|
54
|
+
/** Unique identifier of this entry. Assigned by the host. */
|
|
55
|
+
id: string;
|
|
56
|
+
/** The appliance the command was issued to. */
|
|
57
|
+
applianceId: string;
|
|
58
|
+
/** When the command was issued, in ISO format. */
|
|
59
|
+
timestampIso: string;
|
|
60
|
+
/** The package that issued it. Set by the host, not by the caller. */
|
|
61
|
+
packageName: string;
|
|
62
|
+
/** Which protocol carried the command. */
|
|
63
|
+
protocol: EnyoCommandProtocolEnum;
|
|
64
|
+
/**
|
|
65
|
+
* What was done, in the protocol's own vocabulary — `'write-holding-registers'`,
|
|
66
|
+
* `'ChangeConfiguration'`, `'SetChargingProfile'`, `'gatt-write'`.
|
|
67
|
+
*
|
|
68
|
+
* Use the name the protocol uses rather than a prose description, so entries
|
|
69
|
+
* from one protocol group and compare.
|
|
70
|
+
*/
|
|
71
|
+
operation: string;
|
|
72
|
+
/**
|
|
73
|
+
* What was addressed: a register address (`'40023'`), an OCPP configuration
|
|
74
|
+
* key (`'MeterValueSampleInterval'`), an MQTT topic, a GATT characteristic
|
|
75
|
+
* UUID. Omit for commands that address nothing in particular.
|
|
76
|
+
*/
|
|
77
|
+
target?: string;
|
|
78
|
+
/**
|
|
79
|
+
* The value before the write, rendered as a string. Omit when it was not
|
|
80
|
+
* read back first — do not substitute a value the app merely believed was
|
|
81
|
+
* there, or the log will explain changes that never happened.
|
|
82
|
+
*/
|
|
83
|
+
previousValue?: string;
|
|
84
|
+
/** The value written, rendered as a string. */
|
|
85
|
+
newValue?: string;
|
|
86
|
+
/** How the command ended. */
|
|
87
|
+
outcome: EnyoCommandOutcomeEnum;
|
|
88
|
+
/**
|
|
89
|
+
* Why the app issued the command — `'energy manager reduced available power'`,
|
|
90
|
+
* `'user changed charge mode'`.
|
|
91
|
+
*
|
|
92
|
+
* The single most useful field when reading the log back: the *what* can be
|
|
93
|
+
* reconstructed from the device, the *why* cannot.
|
|
94
|
+
*/
|
|
95
|
+
reason?: string;
|
|
96
|
+
/**
|
|
97
|
+
* The error or rejection detail, for any outcome other than
|
|
98
|
+
* {@link EnyoCommandOutcomeEnum.Success}.
|
|
99
|
+
*/
|
|
100
|
+
errorMessage?: string;
|
|
101
|
+
/** How long the command took, in milliseconds. */
|
|
102
|
+
durationMs?: number;
|
|
103
|
+
/**
|
|
104
|
+
* Anything the flat fields cannot carry — a decoded payload, the full
|
|
105
|
+
* register block written, the OCPP response object.
|
|
106
|
+
*
|
|
107
|
+
* Values above carry strings so entries from different protocols share one
|
|
108
|
+
* shape and can be rendered in one table; structured data belongs here.
|
|
109
|
+
*/
|
|
110
|
+
details?: Record<string, unknown>;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* What an app passes to {@link EnergyAppCommandLog.logCommand}.
|
|
114
|
+
*
|
|
115
|
+
* `id` and `packageName` are assigned by the host. `timestampIso` may be
|
|
116
|
+
* supplied when the command was issued earlier than it is being logged —
|
|
117
|
+
* a batch flushed after a reconnect — and otherwise defaults to the moment the
|
|
118
|
+
* host handles the call.
|
|
119
|
+
*/
|
|
120
|
+
export type EnyoCommandLogEntryInput = Omit<EnyoCommandLogEntry, 'id' | 'packageName' | 'timestampIso'> & {
|
|
121
|
+
timestampIso?: string;
|
|
122
|
+
};
|
|
123
|
+
/**
|
|
124
|
+
* Narrows which entries {@link EnergyAppCommandLog.getCommands} returns.
|
|
125
|
+
*
|
|
126
|
+
* Every field is optional and they combine with AND. With no filter at all, the
|
|
127
|
+
* most recent entries across every appliance are returned.
|
|
128
|
+
*/
|
|
129
|
+
export interface EnyoCommandLogFilter {
|
|
130
|
+
/** Only commands issued to this appliance. */
|
|
131
|
+
applianceId?: string;
|
|
132
|
+
/** Only commands carried over this protocol. */
|
|
133
|
+
protocol?: EnyoCommandProtocolEnum;
|
|
134
|
+
/** Only commands that ended this way — e.g. every failure in the last day. */
|
|
135
|
+
outcome?: EnyoCommandOutcomeEnum;
|
|
136
|
+
/** Only commands issued at or after this ISO timestamp (inclusive). */
|
|
137
|
+
fromIso?: string;
|
|
138
|
+
/** Only commands issued before this ISO timestamp (exclusive). */
|
|
139
|
+
untilIso?: string;
|
|
140
|
+
/**
|
|
141
|
+
* Only commands issued by this package. Omit to see every app's commands —
|
|
142
|
+
* a device misbehaving because two apps are writing the same register is
|
|
143
|
+
* invisible from one app's own entries.
|
|
144
|
+
*/
|
|
145
|
+
packageName?: string;
|
|
146
|
+
/**
|
|
147
|
+
* Maximum number of entries to return, newest first. The host applies its
|
|
148
|
+
* own upper bound when this is omitted or larger than that bound.
|
|
149
|
+
*/
|
|
150
|
+
limit?: number;
|
|
151
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The protocol a logged command was issued over.
|
|
3
|
+
*
|
|
4
|
+
* Names the transport, not the appliance: the same wallbox may be written to
|
|
5
|
+
* over Modbus TCP for power limits and over OCPP for configuration keys, and an
|
|
6
|
+
* entry is only interpretable once you know which.
|
|
7
|
+
*/
|
|
8
|
+
export var EnyoCommandProtocolEnum;
|
|
9
|
+
(function (EnyoCommandProtocolEnum) {
|
|
10
|
+
/** Modbus TCP */
|
|
11
|
+
EnyoCommandProtocolEnum["Modbus"] = "modbus";
|
|
12
|
+
/** Modbus RTU (serial) */
|
|
13
|
+
EnyoCommandProtocolEnum["ModbusRtu"] = "modbus-rtu";
|
|
14
|
+
/** OCPP — a configuration change or a call sent to a charge point */
|
|
15
|
+
EnyoCommandProtocolEnum["Ocpp"] = "ocpp";
|
|
16
|
+
/** EEBUS SHIP/SPINE */
|
|
17
|
+
EnyoCommandProtocolEnum["Eebus"] = "eebus";
|
|
18
|
+
/** MQTT publish */
|
|
19
|
+
EnyoCommandProtocolEnum["Mqtt"] = "mqtt";
|
|
20
|
+
/** HTTP request against a vendor API or local endpoint */
|
|
21
|
+
EnyoCommandProtocolEnum["Http"] = "http";
|
|
22
|
+
/** Bluetooth Low Energy GATT write */
|
|
23
|
+
EnyoCommandProtocolEnum["Bluetooth"] = "bluetooth";
|
|
24
|
+
/** UDP datagram */
|
|
25
|
+
EnyoCommandProtocolEnum["Udp"] = "udp";
|
|
26
|
+
/** Anything else — describe it in {@link EnyoCommandLogEntry.operation} */
|
|
27
|
+
EnyoCommandProtocolEnum["Other"] = "other";
|
|
28
|
+
})(EnyoCommandProtocolEnum || (EnyoCommandProtocolEnum = {}));
|
|
29
|
+
/**
|
|
30
|
+
* How a logged command ended.
|
|
31
|
+
*
|
|
32
|
+
* {@link Rejected} is deliberately separate from {@link Failed}: a device that
|
|
33
|
+
* answered and refused is a very different diagnosis from one that could not be
|
|
34
|
+
* reached, and collapsing the two is what makes a log unable to answer "did it
|
|
35
|
+
* hear us?".
|
|
36
|
+
*/
|
|
37
|
+
export var EnyoCommandOutcomeEnum;
|
|
38
|
+
(function (EnyoCommandOutcomeEnum) {
|
|
39
|
+
/** The device accepted the command. */
|
|
40
|
+
EnyoCommandOutcomeEnum["Success"] = "success";
|
|
41
|
+
/** The command could not be delivered or the device errored. */
|
|
42
|
+
EnyoCommandOutcomeEnum["Failed"] = "failed";
|
|
43
|
+
/** No answer arrived within the app's deadline. */
|
|
44
|
+
EnyoCommandOutcomeEnum["TimedOut"] = "timed-out";
|
|
45
|
+
/** The device answered and refused the command. */
|
|
46
|
+
EnyoCommandOutcomeEnum["Rejected"] = "rejected";
|
|
47
|
+
})(EnyoCommandOutcomeEnum || (EnyoCommandOutcomeEnum = {}));
|
package/dist/version.d.ts
CHANGED
package/dist/version.js
CHANGED