myenergi-api 2.10.0 → 2.11.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 CHANGED
@@ -150,6 +150,43 @@ const setup: LibbiChargeSetup = await account.getLibbiChargeSetup(sno);
150
150
  await account.setLibbiChargeTarget(sno, 4600);
151
151
  ```
152
152
 
153
+ ### Other myenergi account functions
154
+ `MyEnergiAccount` also covers the rest of the known account API. Hubs and devices are identified by their account id here, not by serial number.
155
+ ```typescript
156
+ const hubs = await account.getHubs(); // includes serial number and API key per hub
157
+ const hubId = hubs[0].hubId;
158
+ const zappi = await account.getDevice('1234567'); // look up a device's account id by serial number
159
+ const locations = await account.getLocations(); // sites, with address and energy setup
160
+ const vehicles = await account.getVehicles(); // vehicle profiles
161
+ const schedules = await account.getChargeSchedules(); // all charge schedules, per hub and device
162
+ const history = await account.getHubHourlyHistory(hubId, new Date());
163
+ const tariff = await account.getDualTariff(hubId);
164
+ await account.saveDualTariff(tariff); // overwrites the tariff set in the myenergi app
165
+ ```
166
+ `saveDualTariff` has not been verified against the live API.
167
+
168
+ For a location you were invited to as a guest, set `account.invitationId` to the `invitationData.invitationId` of that location.
169
+
170
+ ### Zappi single charge (myenergi account)
171
+ A single charge is a one-off, date-specific charge: myenergi picks the cheapest times in the window from the tariff on the account. It is not the same as a smart boost. A Zappi that does not support schedules is not listed by `getChargeSchedules`.
172
+ ```typescript
173
+ const zappi = await account.getDevice('1234567');
174
+ const vehicles = await account.getVehicles();
175
+
176
+ // Adds to the single charges the Zappi already has
177
+ await account.addZappiSingleCharge(zappi.id, {
178
+ name: 'Homey',
179
+ vehicleId: vehicles[0].id,
180
+ kWh: 10,
181
+ date: new Date(2026, 9, 12),
182
+ fromTime: '22:00',
183
+ toTime: '07:00',
184
+ });
185
+
186
+ await account.clearZappiSingleCharges(zappi.id);
187
+ ```
188
+ The API only replaces the complete list of a schedule type; `saveChargeSchedules` gives direct access to that call.
189
+
153
190
  ## From myenergi documentation
154
191
  <a name="myenergi_documentation"></a>
155
192
  <a name="charge_mode"></a>
@@ -1,3 +1,5 @@
1
+ import { AccountDevice, AccountHub, AccountHubsAndDevices, AccountLocation, AccountVehicle, DeviceChargeSchedules, DualTariff, HubChargeSchedules, ZappiSingleCharge } from "./models/Account";
2
+ import { HistoryRecord } from "./models/HistoryRecord";
1
3
  import { LibbiChargeSetup } from "./models/LibbiChargeSetup";
2
4
  export declare class MyEnergiAccountError extends Error {
3
5
  statusCode?: number;
@@ -6,17 +8,38 @@ export declare class MyEnergiAccountError extends Error {
6
8
  }
7
9
  export declare class MyEnergiAccount {
8
10
  private _config;
11
+ invitationId?: string;
9
12
  private _accessToken?;
10
13
  private _refreshToken?;
11
14
  private _expiresAt;
15
+ private _signingIn?;
12
16
  constructor(email: string, password: string, accountBaseUrl?: string, authUrl?: string);
13
17
  login(): Promise<void>;
14
18
  getLibbiChargeFromGrid(serialNo: string): Promise<boolean>;
15
19
  setLibbiChargeFromGrid(serialNo: string, chargeFromGrid: boolean): Promise<void>;
16
20
  getLibbiChargeSetup(serialNo: string): Promise<LibbiChargeSetup>;
17
21
  setLibbiChargeTarget(serialNo: string, targetEnergyWh: number): Promise<void>;
22
+ getHubs(): Promise<AccountHub[]>;
23
+ getHubsAndDevices(): Promise<AccountHubsAndDevices>;
24
+ getDevice(serialNo: string): Promise<AccountDevice | null>;
25
+ getLocations(): Promise<AccountLocation[]>;
26
+ getVehicles(): Promise<AccountVehicle[]>;
27
+ getChargeSchedules(): Promise<HubChargeSchedules[]>;
28
+ getDeviceChargeSchedules(deviceId: string): Promise<DeviceChargeSchedules | null>;
29
+ hasTariffPrices(hubId: string): Promise<boolean>;
30
+ saveChargeSchedules(deviceId: string, scheduleType: string, chargeSchedules: any[], isActive?: boolean): Promise<unknown>;
31
+ addZappiSingleCharge(deviceId: string, charge: ZappiSingleCharge): Promise<unknown>;
32
+ clearZappiSingleCharges(deviceId: string): Promise<unknown>;
33
+ getDualTariff(hubId: string): Promise<DualTariff[]>;
34
+ saveDualTariff(tariffs: DualTariff[]): Promise<unknown>;
35
+ getHubHourlyHistory(hubId: string, day: Date, hours?: number): Promise<{
36
+ [key: string]: HistoryRecord[];
37
+ }>;
38
+ private toSingleChargeRequest;
39
+ private formatDay;
18
40
  private call;
19
41
  private getAccessToken;
42
+ private renewAccessToken;
20
43
  private authenticate;
21
44
  private request;
22
45
  }
@@ -53,6 +53,16 @@ class MyEnergiAccount {
53
53
  client_id: "2fup0dhufn5vurmprjkj599041",
54
54
  libbi_mode_url: "/api/AccountAccess/LibbiMode",
55
55
  account_access_url: "/api/AccountAccess/",
56
+ hubs_url: "/api/AccountAccess/Hubs",
57
+ hubs_and_devices_url: "/api/Product/UserHubsAndDevices",
58
+ location_url: "/api/Location",
59
+ vehicles_url: "/api/Vehicle/UserVehicles",
60
+ schedules_url: "/api/ChargeSchedules/GetAllSchedules",
61
+ display_schedules_url: "/api/ChargeSchedules/DisplaySchedules",
62
+ save_schedules_url: "/api/ChargeSchedules/SaveSchedules",
63
+ get_tariff_url: "/api/EnergySetup/GetDualTariffEnergyPrices",
64
+ save_tariff_url: "/api/EnergySetup/SaveDualTariffEnergyPrices",
65
+ hourly_data_url: "/api/Graphs/DevicesOfHubDataHourly",
56
66
  };
57
67
  this._expiresAt = 0;
58
68
  this._config.email = email;
@@ -89,14 +99,146 @@ class MyEnergiAccount {
89
99
  yield this.call("PUT", `${this._config.account_access_url}${encodeURIComponent(serialNo)}/TargetEnergy?targetEnergy=${Math.round(targetEnergyWh)}`);
90
100
  });
91
101
  }
92
- call(method, path, retry = true) {
102
+ getHubs() {
103
+ return __awaiter(this, void 0, void 0, function* () {
104
+ const content = yield this.call("GET", this._config.hubs_url);
105
+ return (content && content.hubs) || [];
106
+ });
107
+ }
108
+ getHubsAndDevices() {
109
+ return __awaiter(this, void 0, void 0, function* () {
110
+ return Object.assign({ hubs: [], devices: [], offlineDevices: [] }, yield this.call("GET", this._config.hubs_and_devices_url));
111
+ });
112
+ }
113
+ getDevice(serialNo) {
114
+ return __awaiter(this, void 0, void 0, function* () {
115
+ const { devices } = yield this.getHubsAndDevices();
116
+ const found = devices.find((d) => d.device && d.device.serialNumber == serialNo);
117
+ return found ? found.device : null;
118
+ });
119
+ }
120
+ getLocations() {
121
+ return __awaiter(this, void 0, void 0, function* () {
122
+ return (yield this.call("GET", this._config.location_url)) || [];
123
+ });
124
+ }
125
+ getVehicles() {
126
+ return __awaiter(this, void 0, void 0, function* () {
127
+ return (yield this.call("GET", this._config.vehicles_url)) || [];
128
+ });
129
+ }
130
+ getChargeSchedules() {
131
+ return __awaiter(this, void 0, void 0, function* () {
132
+ return (yield this.call("GET", this._config.schedules_url)) || [];
133
+ });
134
+ }
135
+ getDeviceChargeSchedules(deviceId) {
136
+ return __awaiter(this, void 0, void 0, function* () {
137
+ for (const hub of yield this.getChargeSchedules()) {
138
+ const device = (hub.devices || []).find((d) => d.deviceId == deviceId);
139
+ if (device)
140
+ return device;
141
+ }
142
+ return null;
143
+ });
144
+ }
145
+ hasTariffPrices(hubId) {
146
+ return __awaiter(this, void 0, void 0, function* () {
147
+ const content = yield this.call("GET", `${this._config.display_schedules_url}?hubId=${encodeURIComponent(hubId)}`);
148
+ return !!(content && content.hasTariffResponse);
149
+ });
150
+ }
151
+ saveChargeSchedules(deviceId, scheduleType, chargeSchedules, isActive = true) {
152
+ return __awaiter(this, void 0, void 0, function* () {
153
+ return this.call("POST", this._config.save_schedules_url, { chargeSchedules, deviceId, isActive, scheduleType });
154
+ });
155
+ }
156
+ addZappiSingleCharge(deviceId, charge) {
157
+ return __awaiter(this, void 0, void 0, function* () {
158
+ const [device, vehicles] = yield Promise.all([this.getDeviceChargeSchedules(deviceId), this.getVehicles()]);
159
+ if (!device)
160
+ throw new MyEnergiAccountError("The device does not support charge schedules.");
161
+ const existing = (device.singleCharges || []).map((c) => this.toSingleChargeRequest(c, vehicles));
162
+ const added = this.toSingleChargeRequest({
163
+ scheduleType: "Single",
164
+ scheduleNickName: charge.name,
165
+ chargingOutputName: charge.vehicleId,
166
+ chargeAmountMinutes: 0,
167
+ chargeAmountKWh: charge.kWh,
168
+ fromTime: charge.fromTime,
169
+ toTime: charge.toTime,
170
+ singleChargeDay: typeof charge.date === "string" ? charge.date : this.formatDay(charge.date),
171
+ }, vehicles);
172
+ return this.saveChargeSchedules(deviceId, "Single", [...existing, added]);
173
+ });
174
+ }
175
+ clearZappiSingleCharges(deviceId) {
176
+ return __awaiter(this, void 0, void 0, function* () {
177
+ return this.saveChargeSchedules(deviceId, "Single", []);
178
+ });
179
+ }
180
+ getDualTariff(hubId) {
181
+ return __awaiter(this, void 0, void 0, function* () {
182
+ return (yield this.call("GET", `${this._config.get_tariff_url}?hubId=${encodeURIComponent(hubId)}`)) || [];
183
+ });
184
+ }
185
+ saveDualTariff(tariffs) {
186
+ return __awaiter(this, void 0, void 0, function* () {
187
+ const body = tariffs.map((t) => ({
188
+ days: t.days,
189
+ energySetupId: t.energySetupId,
190
+ tariffs: t.tariffs.map((b) => ({ fromMinutes: b.fromMinutes, price: b.price, toMinutes: b.toMinutes })),
191
+ }));
192
+ return this.call("POST", this._config.save_tariff_url, body);
193
+ });
194
+ }
195
+ getHubHourlyHistory(hubId, day, hours = 24) {
196
+ return __awaiter(this, void 0, void 0, function* () {
197
+ const start = Date.UTC(day.getUTCFullYear(), day.getUTCMonth(), day.getUTCDate());
198
+ const content = yield this.call("POST", this._config.hourly_data_url, { hubId, dayTimestampsUtc: [start], duration: hours });
199
+ return content || {};
200
+ });
201
+ }
202
+ toSingleChargeRequest(charge, vehicles) {
203
+ const vehicle = vehicles.find((v) => v.id == charge.chargingOutputName);
204
+ if (!vehicle)
205
+ throw new MyEnergiAccountError(`Unknown vehicle profile: ${charge.chargingOutputName}`);
206
+ const day = `${charge.singleChargeDay || ""}`;
207
+ const usDate = /^(\d{2})\/(\d{2})\/(\d{4})$/.exec(day);
208
+ const request = {
209
+ scheduleNickName: charge.scheduleNickName,
210
+ vehicle: { id: vehicle.id, manufacturer: vehicle.manufacturer, model: vehicle.model, batterySize: vehicle.batterySize, chargeRate: vehicle.chargeRate },
211
+ power: charge.chargeAmountKWh,
212
+ singleChargeDay: usDate ? `${usDate[2]}-${usDate[1]}-${usDate[3]}` : day,
213
+ fromTime: charge.fromTime,
214
+ toTime: charge.toTime,
215
+ chargeAmountMinutes: Math.round(charge.chargeAmountKWh / vehicle.chargeRate * 60),
216
+ chargingOutputName: vehicle.id,
217
+ scheduleType: "Single",
218
+ chargeAmountKWh: charge.chargeAmountKWh,
219
+ };
220
+ if (charge.id)
221
+ request.id = charge.id;
222
+ return request;
223
+ }
224
+ formatDay(date) {
225
+ const pad = (n) => `${n}`.padStart(2, "0");
226
+ return `${pad(date.getDate())}-${pad(date.getMonth() + 1)}-${date.getFullYear()}`;
227
+ }
228
+ call(method, path, body, retry = true) {
93
229
  return __awaiter(this, void 0, void 0, function* () {
94
230
  const token = yield this.getAccessToken();
95
- const res = yield this.request(method, new URL(path, this._config.base_url), { Authorization: `Bearer ${token}`, Accept: "application/json" });
231
+ const url = new URL(path, this._config.base_url);
232
+ if (this.invitationId)
233
+ url.searchParams.set("invitationId", this.invitationId);
234
+ const headers = { Authorization: `Bearer ${token}`, Accept: "application/json" };
235
+ if (body !== undefined)
236
+ headers["Content-Type"] = "application/json";
237
+ const res = yield this.request(method, url, headers, body !== undefined ? JSON.stringify(body) : undefined);
96
238
  if (res.statusCode == 401 && retry) {
97
239
  this._accessToken = undefined;
98
240
  this._refreshToken = undefined;
99
- return this.call(method, path, false);
241
+ return this.call(method, path, body, false);
100
242
  }
101
243
  let jsonData = {};
102
244
  try {
@@ -113,22 +255,28 @@ class MyEnergiAccount {
113
255
  getAccessToken() {
114
256
  return __awaiter(this, void 0, void 0, function* () {
115
257
  if (!this._accessToken || Date.now() >= this._expiresAt) {
116
- if (this._refreshToken) {
117
- try {
118
- yield this.authenticate("REFRESH_TOKEN_AUTH", { REFRESH_TOKEN: this._refreshToken });
119
- }
120
- catch (error) {
121
- this._refreshToken = undefined;
122
- yield this.login();
123
- }
124
- }
125
- else {
126
- yield this.login();
258
+ if (!this._signingIn) {
259
+ this._signingIn = this.renewAccessToken().then(() => { this._signingIn = undefined; }, (error) => { this._signingIn = undefined; throw error; });
127
260
  }
261
+ yield this._signingIn;
128
262
  }
129
263
  return this._accessToken;
130
264
  });
131
265
  }
266
+ renewAccessToken() {
267
+ return __awaiter(this, void 0, void 0, function* () {
268
+ if (this._refreshToken) {
269
+ try {
270
+ yield this.authenticate("REFRESH_TOKEN_AUTH", { REFRESH_TOKEN: this._refreshToken });
271
+ return;
272
+ }
273
+ catch (error) {
274
+ this._refreshToken = undefined;
275
+ }
276
+ }
277
+ yield this.login();
278
+ });
279
+ }
132
280
  authenticate(authFlow, authParameters) {
133
281
  return __awaiter(this, void 0, void 0, function* () {
134
282
  const res = yield this.request("POST", new URL(this._config.auth_url), {
@@ -1,3 +1,4 @@
1
+ import { AccountDevice, AccountHub, AccountHubsAndDevices, AccountLocation, AccountVehicle, ChargeSchedule, DeviceChargeSchedules, DualTariff, HubChargeSchedules, TariffBand, ZappiSingleCharge } from "./models/Account";
1
2
  import { Eddi } from "./models/Eddi";
2
3
  import { Harvi } from "./models/Harvi";
3
4
  import { HistoryRecord } from "./models/HistoryRecord";
@@ -7,4 +8,4 @@ import { EddiBoost, EddiHeaterStatus, EddiMode, LibbiMode, ZappiBoostMode, Zappi
7
8
  import { Zappi } from "./models/Zappi";
8
9
  import { MyEnergi } from "./MyEnergi";
9
10
  import { MyEnergiAccount, MyEnergiAccountError } from "./MyEnergiAccount";
10
- export { MyEnergi, MyEnergiAccount, MyEnergiAccountError, Eddi, Zappi, Harvi, Libbi, LibbiChargeSetup, HistoryRecord, EddiBoost, EddiMode, EddiHeaterStatus, LibbiMode, ZappiBoostMode, ZappiChargeMode, ZappiPhaseSetting, ZappiStatus };
11
+ export { MyEnergi, MyEnergiAccount, MyEnergiAccountError, Eddi, Zappi, Harvi, Libbi, LibbiChargeSetup, AccountDevice, AccountHub, AccountHubsAndDevices, AccountLocation, AccountVehicle, ChargeSchedule, DeviceChargeSchedules, DualTariff, HubChargeSchedules, TariffBand, ZappiSingleCharge, HistoryRecord, EddiBoost, EddiMode, EddiHeaterStatus, LibbiMode, ZappiBoostMode, ZappiChargeMode, ZappiPhaseSetting, ZappiStatus };
@@ -0,0 +1,125 @@
1
+ export interface AccountHub {
2
+ hubId: string;
3
+ hubName: string;
4
+ serialNo: string;
5
+ hubPassword: string;
6
+ locationId: string;
7
+ locationName: string;
8
+ isVHub: boolean;
9
+ server: string;
10
+ devices: string[];
11
+ }
12
+ export interface AccountDevice {
13
+ id: string;
14
+ deviceName: string;
15
+ deviceType: string;
16
+ serialNumber: string;
17
+ firmware: string;
18
+ status: number;
19
+ locationId?: string;
20
+ }
21
+ export interface AccountHubsAndDevices {
22
+ hubs: {
23
+ hub: {
24
+ id: string;
25
+ serialNo: string;
26
+ nickName: string;
27
+ firmware: string;
28
+ isVHub: boolean;
29
+ };
30
+ deviceIds: string[];
31
+ locationId: string;
32
+ }[];
33
+ devices: {
34
+ device: AccountDevice;
35
+ locationId: string;
36
+ isVHub: boolean;
37
+ }[];
38
+ offlineDevices: unknown[];
39
+ }
40
+ export interface AccountLocation {
41
+ address: {
42
+ id: string;
43
+ siteName: string;
44
+ countryCode: string;
45
+ currency: string;
46
+ [key: string]: unknown;
47
+ };
48
+ energySetup: {
49
+ id: string;
50
+ energyProvider: string;
51
+ energyTariff: string;
52
+ tariffFriendlyName: string;
53
+ isEconomy: boolean;
54
+ isActive: boolean;
55
+ [key: string]: unknown;
56
+ };
57
+ isGuestLocation: boolean;
58
+ invitationData?: {
59
+ invitationId: string;
60
+ [key: string]: unknown;
61
+ };
62
+ ownerEmail: string;
63
+ }
64
+ export interface AccountVehicle {
65
+ id: string;
66
+ manufacturer: string;
67
+ model: string;
68
+ batterySize: number;
69
+ chargeRate: number;
70
+ manufacturingYear?: number;
71
+ ownership?: number;
72
+ }
73
+ export interface ChargeSchedule {
74
+ id?: string;
75
+ scheduleType: string;
76
+ scheduleNickName: string;
77
+ chargingOutputName: string;
78
+ chargeAmountMinutes: number;
79
+ chargeAmountKWh: number;
80
+ fromTime: string;
81
+ toTime: string;
82
+ singleChargeDay?: string;
83
+ isActive?: boolean;
84
+ [key: string]: unknown;
85
+ }
86
+ export interface DeviceChargeSchedules {
87
+ deviceId: string;
88
+ deviceName: string;
89
+ deviceType: string;
90
+ firmware: string;
91
+ scheduleCharges: ChargeSchedule[];
92
+ scheduleChargesActive: boolean;
93
+ budgetCharges: ChargeSchedule[];
94
+ budgetChargesActive: boolean;
95
+ singleCharges: ChargeSchedule[];
96
+ singleChargesActive: boolean;
97
+ }
98
+ export interface HubChargeSchedules {
99
+ hubId: string;
100
+ hubNickName: string;
101
+ energySetupProvider: string;
102
+ energySetupTarrif: string;
103
+ isEconomy: boolean;
104
+ isGuestUser: boolean;
105
+ devices: DeviceChargeSchedules[];
106
+ }
107
+ export interface ZappiSingleCharge {
108
+ name: string;
109
+ vehicleId: string;
110
+ kWh: number;
111
+ date: Date | string;
112
+ fromTime: string;
113
+ toTime: string;
114
+ }
115
+ export interface TariffBand {
116
+ id?: string;
117
+ fromMinutes: number;
118
+ toMinutes: number;
119
+ price: number;
120
+ }
121
+ export interface DualTariff {
122
+ energySetupId: string;
123
+ days: number[];
124
+ tariffs: TariffBand[];
125
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "myenergi-api",
3
- "version": "2.10.0",
3
+ "version": "2.11.0",
4
4
  "description": "NodeJS implementation of MyEnergi API for controlling Zappi, Eddi and other MyEnergi products",
5
5
  "main": "dist/src/index.js",
6
6
  "types": "dist/src/index.d.ts",