@enyo-energy/energy-app-sdk 1.39.0 → 1.41.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 +15 -0
- package/dist/cjs/implementations/appliances/appliance-manager.cjs +180 -1
- package/dist/cjs/implementations/appliances/appliance-manager.d.cts +126 -2
- package/dist/cjs/implementations/appliances/in-memory-appliance-manager.cjs +41 -0
- package/dist/cjs/implementations/appliances/in-memory-appliance-manager.d.cts +19 -1
- package/dist/cjs/packages/energy-app-appliance.d.cts +32 -4
- package/dist/cjs/packages/energy-app-vehicle.d.cts +7 -0
- package/dist/cjs/types/enyo-appliance.d.cts +65 -0
- package/dist/cjs/types/enyo-data-bus-value.cjs +2 -0
- package/dist/cjs/types/enyo-data-bus-value.d.cts +65 -1
- package/dist/cjs/version.cjs +1 -1
- package/dist/cjs/version.d.cts +1 -1
- package/dist/implementations/appliances/appliance-manager.d.ts +126 -2
- package/dist/implementations/appliances/appliance-manager.js +178 -0
- package/dist/implementations/appliances/in-memory-appliance-manager.d.ts +19 -1
- package/dist/implementations/appliances/in-memory-appliance-manager.js +41 -0
- package/dist/packages/energy-app-appliance.d.ts +32 -4
- package/dist/packages/energy-app-vehicle.d.ts +7 -0
- package/dist/types/enyo-appliance.d.ts +65 -0
- package/dist/types/enyo-data-bus-value.d.ts +65 -1
- package/dist/types/enyo-data-bus-value.js +2 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -860,6 +860,8 @@ energyApp.useDataBus().sendMessage([{
|
|
|
860
860
|
data: {
|
|
861
861
|
vehicleId,
|
|
862
862
|
socPercent: 62,
|
|
863
|
+
// optional: remaining range in km, when the source reports it (e.g. Smartcar)
|
|
864
|
+
rangeKm: 280,
|
|
863
865
|
// omit only when the reading came straight off the wire
|
|
864
866
|
measuredAtIso: new Date().toISOString(),
|
|
865
867
|
},
|
|
@@ -870,6 +872,19 @@ That message is the only write path for a state of charge; it needs the
|
|
|
870
872
|
`SendDataBusValues` permission. `getSoc()` resolves to `undefined` when nothing
|
|
871
873
|
is known — an ordinary answer for a car with no SoC source, not an error.
|
|
872
874
|
|
|
875
|
+
The vehicle's battery size is written the same way: publish
|
|
876
|
+
`VehicleBatterySizeUpdateV1` with the usable capacity in watt-hours, and the
|
|
877
|
+
host stores it on the vehicle (replacing the previous value, including one the
|
|
878
|
+
user entered). It is read back as `batterySizeKwh`.
|
|
879
|
+
|
|
880
|
+
```typescript
|
|
881
|
+
energyApp.useDataBus().sendMessage([{
|
|
882
|
+
type: 'message',
|
|
883
|
+
message: 'VehicleBatterySizeUpdateV1',
|
|
884
|
+
data: {vehicleId, batterySizeWh: 75_000},
|
|
885
|
+
}]);
|
|
886
|
+
```
|
|
887
|
+
|
|
873
888
|
##### Car integrations: pairing a vehicle
|
|
874
889
|
|
|
875
890
|
A package in the `vehicle` category talks to a manufacturer's cloud and links
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ApplianceManager = exports.ApplianceManagerDisposedError = exports.DuplicateIdentifierError = exports.MissingIdentifierError = void 0;
|
|
3
|
+
exports.ApplianceManager = exports.ApplianceGroupNotFoundError = exports.ApplianceManagerDisposedError = exports.DuplicateIdentifierError = exports.MissingIdentifierError = void 0;
|
|
4
4
|
const index_js_1 = require("../../index.cjs");
|
|
5
5
|
const enyo_appliance_js_1 = require("../../types/enyo-appliance.cjs");
|
|
6
6
|
/**
|
|
@@ -49,6 +49,20 @@ class ApplianceManagerDisposedError extends Error {
|
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
51
|
exports.ApplianceManagerDisposedError = ApplianceManagerDisposedError;
|
|
52
|
+
/**
|
|
53
|
+
* Thrown by the {@link ApplianceManager} group helpers when no group with the
|
|
54
|
+
* given key exists in the package.
|
|
55
|
+
*/
|
|
56
|
+
class ApplianceGroupNotFoundError extends Error {
|
|
57
|
+
/** The group key that was looked up */
|
|
58
|
+
key;
|
|
59
|
+
constructor(key) {
|
|
60
|
+
super(`No appliance group with key "${key}" exists.`);
|
|
61
|
+
this.name = 'ApplianceGroupNotFoundError';
|
|
62
|
+
this.key = key;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
exports.ApplianceGroupNotFoundError = ApplianceGroupNotFoundError;
|
|
52
66
|
/** Sub-objects on EnyoAppliance that {@link ApplianceManager} shallow-merges during updates. */
|
|
53
67
|
const MERGEABLE_METADATA_KEYS = [
|
|
54
68
|
'metadata',
|
|
@@ -220,6 +234,7 @@ class ApplianceManager {
|
|
|
220
234
|
...(appliance.cloudPackageId !== undefined && { cloudPackageId: appliance.cloudPackageId }),
|
|
221
235
|
...(appliance.availableFeatures !== undefined && { availableFeatures: appliance.availableFeatures }),
|
|
222
236
|
...(appliance.compatibilityModes !== undefined && { compatibilityModes: appliance.compatibilityModes }),
|
|
237
|
+
...(appliance.hidden !== undefined && { hidden: appliance.hidden }),
|
|
223
238
|
};
|
|
224
239
|
let applianceData = newApplianceData;
|
|
225
240
|
if (existingApplianceId) {
|
|
@@ -741,6 +756,170 @@ class ApplianceManager {
|
|
|
741
756
|
}
|
|
742
757
|
}
|
|
743
758
|
}
|
|
759
|
+
/**
|
|
760
|
+
* Creates the group with the given {@link ApplianceGroupConfig.key}, or
|
|
761
|
+
* updates it when it already exists. The group is shown in the UI as one
|
|
762
|
+
* collapsible entry with the summed values of its members.
|
|
763
|
+
*
|
|
764
|
+
* An appliance belongs to at most one group, so members currently listed
|
|
765
|
+
* in another group of the package are taken out of it first; a group left
|
|
766
|
+
* empty by that is removed.
|
|
767
|
+
*
|
|
768
|
+
* @param config The group's key, name and member appliance IDs
|
|
769
|
+
* @returns The ID of the saved group
|
|
770
|
+
* @throws {Error} when `config.applianceIds` is empty
|
|
771
|
+
* @throws {ApplianceManagerDisposedError} when called after {@link dispose}
|
|
772
|
+
*/
|
|
773
|
+
async createOrUpdateGroup(config) {
|
|
774
|
+
this.throwIfDisposed();
|
|
775
|
+
const applianceIds = [...new Set(config.applianceIds)];
|
|
776
|
+
if (applianceIds.length === 0) {
|
|
777
|
+
throw new Error(`Appliance group "${config.key}" needs at least one appliance; use removeGroup to drop it.`);
|
|
778
|
+
}
|
|
779
|
+
const groups = await this.loadGroups();
|
|
780
|
+
const existing = groups.find(g => g.key === config.key);
|
|
781
|
+
// Take the members out of every other group before saving, otherwise
|
|
782
|
+
// the host rejects the save for an appliance being in two groups.
|
|
783
|
+
const members = new Set(applianceIds);
|
|
784
|
+
for (const other of groups) {
|
|
785
|
+
if (other === existing || !other.applianceIds.some(id => members.has(id)))
|
|
786
|
+
continue;
|
|
787
|
+
const remaining = other.applianceIds.filter(id => !members.has(id));
|
|
788
|
+
if (remaining.length === 0) {
|
|
789
|
+
await this.deleteGroup(other.id);
|
|
790
|
+
}
|
|
791
|
+
else {
|
|
792
|
+
await this.persistGroup({ ...this.withoutId(other), applianceIds: remaining }, other.id);
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
return this.persistGroup({ key: config.key, name: config.name, applianceIds }, existing?.id);
|
|
796
|
+
}
|
|
797
|
+
/**
|
|
798
|
+
* Returns all appliance groups of the package.
|
|
799
|
+
*
|
|
800
|
+
* @throws {ApplianceManagerDisposedError} when called after {@link dispose}
|
|
801
|
+
*/
|
|
802
|
+
async getGroups() {
|
|
803
|
+
this.throwIfDisposed();
|
|
804
|
+
return this.loadGroups();
|
|
805
|
+
}
|
|
806
|
+
/**
|
|
807
|
+
* Returns the group with the given key, or `null` when there is none.
|
|
808
|
+
*
|
|
809
|
+
* @param key The app-defined group key
|
|
810
|
+
* @throws {ApplianceManagerDisposedError} when called after {@link dispose}
|
|
811
|
+
*/
|
|
812
|
+
async findGroupByKey(key) {
|
|
813
|
+
this.throwIfDisposed();
|
|
814
|
+
return (await this.loadGroups()).find(g => g.key === key) ?? null;
|
|
815
|
+
}
|
|
816
|
+
/**
|
|
817
|
+
* Returns the group an appliance belongs to, or `null` when it is not
|
|
818
|
+
* grouped.
|
|
819
|
+
*
|
|
820
|
+
* @param applianceId The appliance to look up
|
|
821
|
+
* @throws {ApplianceManagerDisposedError} when called after {@link dispose}
|
|
822
|
+
*/
|
|
823
|
+
async findGroupOfAppliance(applianceId) {
|
|
824
|
+
this.throwIfDisposed();
|
|
825
|
+
return (await this.loadGroups()).find(g => g.applianceIds.includes(applianceId)) ?? null;
|
|
826
|
+
}
|
|
827
|
+
/**
|
|
828
|
+
* Adds an appliance to the end of an existing group, moving it out of any
|
|
829
|
+
* other group it was in. Does nothing when it is already a member.
|
|
830
|
+
*
|
|
831
|
+
* @param key The key of the group to add the appliance to
|
|
832
|
+
* @param applianceId The appliance to add
|
|
833
|
+
* @returns The ID of the group
|
|
834
|
+
* @throws {ApplianceGroupNotFoundError} when no group has the given key
|
|
835
|
+
* @throws {ApplianceManagerDisposedError} when called after {@link dispose}
|
|
836
|
+
*/
|
|
837
|
+
async addApplianceToGroup(key, applianceId) {
|
|
838
|
+
this.throwIfDisposed();
|
|
839
|
+
const group = await this.findGroupByKey(key);
|
|
840
|
+
if (!group) {
|
|
841
|
+
throw new ApplianceGroupNotFoundError(key);
|
|
842
|
+
}
|
|
843
|
+
if (group.applianceIds.includes(applianceId)) {
|
|
844
|
+
return group.id;
|
|
845
|
+
}
|
|
846
|
+
return this.createOrUpdateGroup({
|
|
847
|
+
key,
|
|
848
|
+
name: group.name,
|
|
849
|
+
applianceIds: [...group.applianceIds, applianceId],
|
|
850
|
+
});
|
|
851
|
+
}
|
|
852
|
+
/**
|
|
853
|
+
* Takes an appliance out of its group, so the UI shows it individually
|
|
854
|
+
* again. A group left without members is removed.
|
|
855
|
+
*
|
|
856
|
+
* @param applianceId The appliance to take out of its group
|
|
857
|
+
* @returns `true` when the appliance was in a group, `false` otherwise
|
|
858
|
+
* @throws {ApplianceManagerDisposedError} when called after {@link dispose}
|
|
859
|
+
*/
|
|
860
|
+
async removeApplianceFromGroup(applianceId) {
|
|
861
|
+
this.throwIfDisposed();
|
|
862
|
+
const group = await this.findGroupOfAppliance(applianceId);
|
|
863
|
+
if (!group) {
|
|
864
|
+
return false;
|
|
865
|
+
}
|
|
866
|
+
const remaining = group.applianceIds.filter(id => id !== applianceId);
|
|
867
|
+
if (remaining.length === 0) {
|
|
868
|
+
await this.deleteGroup(group.id);
|
|
869
|
+
}
|
|
870
|
+
else {
|
|
871
|
+
await this.persistGroup({ ...this.withoutId(group), applianceIds: remaining }, group.id);
|
|
872
|
+
}
|
|
873
|
+
return true;
|
|
874
|
+
}
|
|
875
|
+
/**
|
|
876
|
+
* Removes the group with the given key. Its members stay and are shown
|
|
877
|
+
* individually again.
|
|
878
|
+
*
|
|
879
|
+
* @param key The key of the group to remove
|
|
880
|
+
* @returns `true` when a group was removed, `false` when none had the key
|
|
881
|
+
* @throws {ApplianceManagerDisposedError} when called after {@link dispose}
|
|
882
|
+
*/
|
|
883
|
+
async removeGroup(key) {
|
|
884
|
+
this.throwIfDisposed();
|
|
885
|
+
const group = await this.findGroupByKey(key);
|
|
886
|
+
if (!group) {
|
|
887
|
+
return false;
|
|
888
|
+
}
|
|
889
|
+
await this.deleteGroup(group.id);
|
|
890
|
+
return true;
|
|
891
|
+
}
|
|
892
|
+
/**
|
|
893
|
+
* Loads the package's appliance groups from storage. `protected` so
|
|
894
|
+
* subclasses (e.g. {@link InMemoryApplianceManager}) can swap the storage
|
|
895
|
+
* the group helpers run on.
|
|
896
|
+
*/
|
|
897
|
+
async loadGroups() {
|
|
898
|
+
return this.energyApp.useAppliances().listGroups();
|
|
899
|
+
}
|
|
900
|
+
/**
|
|
901
|
+
* Saves an appliance group to storage.
|
|
902
|
+
*
|
|
903
|
+
* @param group The group to save
|
|
904
|
+
* @param groupId ID of the group to update, or `undefined` to create one
|
|
905
|
+
* @returns The ID of the saved group
|
|
906
|
+
*/
|
|
907
|
+
async persistGroup(group, groupId) {
|
|
908
|
+
return this.energyApp.useAppliances().saveGroup(group, groupId);
|
|
909
|
+
}
|
|
910
|
+
/**
|
|
911
|
+
* Deletes an appliance group from storage.
|
|
912
|
+
*
|
|
913
|
+
* @param groupId ID of the group to delete
|
|
914
|
+
*/
|
|
915
|
+
async deleteGroup(groupId) {
|
|
916
|
+
await this.energyApp.useAppliances().removeGroup(groupId);
|
|
917
|
+
}
|
|
918
|
+
/** Returns a copy of the group without its `id`, ready to be saved again. */
|
|
919
|
+
withoutId(group) {
|
|
920
|
+
const { id: _id, ...rest } = group;
|
|
921
|
+
return rest;
|
|
922
|
+
}
|
|
744
923
|
/**
|
|
745
924
|
* Gets the current identifier strategy.
|
|
746
925
|
*/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { EnergyApp } from "../../index.cjs";
|
|
2
2
|
import type { EnyoNetworkDevice } from "../../types/enyo-network-device.cjs";
|
|
3
|
-
import { EnyoAppliance, EnyoApplianceAvailableFeaturesEnum, EnyoApplianceConnectionType, EnyoApplianceMetadata, EnyoApplianceName, EnyoApplianceStateEnum, EnyoApplianceTopology, EnyoApplianceTypeEnum } from "../../types/enyo-appliance.cjs";
|
|
3
|
+
import { EnyoAppliance, EnyoApplianceAvailableFeaturesEnum, EnyoApplianceConnectionType, EnyoApplianceGroup, EnyoApplianceMetadata, EnyoApplianceName, EnyoApplianceStateEnum, EnyoApplianceTopology, EnyoApplianceTypeEnum } from "../../types/enyo-appliance.cjs";
|
|
4
4
|
import type { EnyoApplianceCreatedFilter } from "../../packages/energy-app-appliance.cjs";
|
|
5
5
|
import type { EnyoChargerApplianceMetadata } from "../../types/enyo-charger-appliance.cjs";
|
|
6
6
|
import type { EnyoHeatpumpApplianceDomesticHotWater, EnyoHeatpumpApplianceHeatingCircuit, EnyoHeatpumpApplianceMetadata } from "../../types/enyo-heatpump-appliance.cjs";
|
|
@@ -40,6 +40,36 @@ export declare class DuplicateIdentifierError extends Error {
|
|
|
40
40
|
export declare class ApplianceManagerDisposedError extends Error {
|
|
41
41
|
constructor();
|
|
42
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Thrown by the {@link ApplianceManager} group helpers when no group with the
|
|
45
|
+
* given key exists in the package.
|
|
46
|
+
*/
|
|
47
|
+
export declare class ApplianceGroupNotFoundError extends Error {
|
|
48
|
+
/** The group key that was looked up */
|
|
49
|
+
readonly key: string;
|
|
50
|
+
constructor(key: string);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Configuration for creating or updating an appliance group via
|
|
54
|
+
* {@link ApplianceManager.createOrUpdateGroup}. See {@link EnyoApplianceGroup}
|
|
55
|
+
* for what a group is and how the UI shows it.
|
|
56
|
+
*/
|
|
57
|
+
export interface ApplianceGroupConfig {
|
|
58
|
+
/**
|
|
59
|
+
* Stable, app-defined key identifying the group within the package — e.g.
|
|
60
|
+
* the serial number of the gateway the members sit behind. A group with
|
|
61
|
+
* this key is updated in place; otherwise a new one is created.
|
|
62
|
+
*/
|
|
63
|
+
key: string;
|
|
64
|
+
/** Name of the group shown in the UI, in the supported languages */
|
|
65
|
+
name: EnyoApplianceName[];
|
|
66
|
+
/**
|
|
67
|
+
* IDs of the member appliances (your package's own), in display order.
|
|
68
|
+
* Must not be empty — use {@link ApplianceManager.removeGroup} to drop a
|
|
69
|
+
* group. Appliances currently in another group are moved into this one.
|
|
70
|
+
*/
|
|
71
|
+
applianceIds: string[];
|
|
72
|
+
}
|
|
43
73
|
/**
|
|
44
74
|
* Configuration for creating or updating an appliance.
|
|
45
75
|
*/
|
|
@@ -78,6 +108,12 @@ export interface ApplianceConfig {
|
|
|
78
108
|
* appliance is created or updated.
|
|
79
109
|
*/
|
|
80
110
|
cloudPackageId?: string;
|
|
111
|
+
/**
|
|
112
|
+
* Whether the appliance is hidden. Forwarded to {@link EnyoAppliance.hidden}
|
|
113
|
+
* when the appliance is created or updated. Omit to keep the stored value on
|
|
114
|
+
* an update; pass `false` to unhide it.
|
|
115
|
+
*/
|
|
116
|
+
hidden?: boolean;
|
|
81
117
|
}
|
|
82
118
|
/**
|
|
83
119
|
* Configuration options for the ApplianceManager.
|
|
@@ -434,6 +470,93 @@ export declare class ApplianceManager {
|
|
|
434
470
|
* @throws {ApplianceManagerDisposedError} when called after {@link dispose}
|
|
435
471
|
*/
|
|
436
472
|
setIdentifierStrategy(strategy: IdentifierStrategy, rebuildCache: boolean): Promise<void>;
|
|
473
|
+
/**
|
|
474
|
+
* Creates the group with the given {@link ApplianceGroupConfig.key}, or
|
|
475
|
+
* updates it when it already exists. The group is shown in the UI as one
|
|
476
|
+
* collapsible entry with the summed values of its members.
|
|
477
|
+
*
|
|
478
|
+
* An appliance belongs to at most one group, so members currently listed
|
|
479
|
+
* in another group of the package are taken out of it first; a group left
|
|
480
|
+
* empty by that is removed.
|
|
481
|
+
*
|
|
482
|
+
* @param config The group's key, name and member appliance IDs
|
|
483
|
+
* @returns The ID of the saved group
|
|
484
|
+
* @throws {Error} when `config.applianceIds` is empty
|
|
485
|
+
* @throws {ApplianceManagerDisposedError} when called after {@link dispose}
|
|
486
|
+
*/
|
|
487
|
+
createOrUpdateGroup(config: ApplianceGroupConfig): Promise<string>;
|
|
488
|
+
/**
|
|
489
|
+
* Returns all appliance groups of the package.
|
|
490
|
+
*
|
|
491
|
+
* @throws {ApplianceManagerDisposedError} when called after {@link dispose}
|
|
492
|
+
*/
|
|
493
|
+
getGroups(): Promise<EnyoApplianceGroup[]>;
|
|
494
|
+
/**
|
|
495
|
+
* Returns the group with the given key, or `null` when there is none.
|
|
496
|
+
*
|
|
497
|
+
* @param key The app-defined group key
|
|
498
|
+
* @throws {ApplianceManagerDisposedError} when called after {@link dispose}
|
|
499
|
+
*/
|
|
500
|
+
findGroupByKey(key: string): Promise<EnyoApplianceGroup | null>;
|
|
501
|
+
/**
|
|
502
|
+
* Returns the group an appliance belongs to, or `null` when it is not
|
|
503
|
+
* grouped.
|
|
504
|
+
*
|
|
505
|
+
* @param applianceId The appliance to look up
|
|
506
|
+
* @throws {ApplianceManagerDisposedError} when called after {@link dispose}
|
|
507
|
+
*/
|
|
508
|
+
findGroupOfAppliance(applianceId: string): Promise<EnyoApplianceGroup | null>;
|
|
509
|
+
/**
|
|
510
|
+
* Adds an appliance to the end of an existing group, moving it out of any
|
|
511
|
+
* other group it was in. Does nothing when it is already a member.
|
|
512
|
+
*
|
|
513
|
+
* @param key The key of the group to add the appliance to
|
|
514
|
+
* @param applianceId The appliance to add
|
|
515
|
+
* @returns The ID of the group
|
|
516
|
+
* @throws {ApplianceGroupNotFoundError} when no group has the given key
|
|
517
|
+
* @throws {ApplianceManagerDisposedError} when called after {@link dispose}
|
|
518
|
+
*/
|
|
519
|
+
addApplianceToGroup(key: string, applianceId: string): Promise<string>;
|
|
520
|
+
/**
|
|
521
|
+
* Takes an appliance out of its group, so the UI shows it individually
|
|
522
|
+
* again. A group left without members is removed.
|
|
523
|
+
*
|
|
524
|
+
* @param applianceId The appliance to take out of its group
|
|
525
|
+
* @returns `true` when the appliance was in a group, `false` otherwise
|
|
526
|
+
* @throws {ApplianceManagerDisposedError} when called after {@link dispose}
|
|
527
|
+
*/
|
|
528
|
+
removeApplianceFromGroup(applianceId: string): Promise<boolean>;
|
|
529
|
+
/**
|
|
530
|
+
* Removes the group with the given key. Its members stay and are shown
|
|
531
|
+
* individually again.
|
|
532
|
+
*
|
|
533
|
+
* @param key The key of the group to remove
|
|
534
|
+
* @returns `true` when a group was removed, `false` when none had the key
|
|
535
|
+
* @throws {ApplianceManagerDisposedError} when called after {@link dispose}
|
|
536
|
+
*/
|
|
537
|
+
removeGroup(key: string): Promise<boolean>;
|
|
538
|
+
/**
|
|
539
|
+
* Loads the package's appliance groups from storage. `protected` so
|
|
540
|
+
* subclasses (e.g. {@link InMemoryApplianceManager}) can swap the storage
|
|
541
|
+
* the group helpers run on.
|
|
542
|
+
*/
|
|
543
|
+
protected loadGroups(): Promise<EnyoApplianceGroup[]>;
|
|
544
|
+
/**
|
|
545
|
+
* Saves an appliance group to storage.
|
|
546
|
+
*
|
|
547
|
+
* @param group The group to save
|
|
548
|
+
* @param groupId ID of the group to update, or `undefined` to create one
|
|
549
|
+
* @returns The ID of the saved group
|
|
550
|
+
*/
|
|
551
|
+
protected persistGroup(group: Omit<EnyoApplianceGroup, 'id'>, groupId: string | undefined): Promise<string>;
|
|
552
|
+
/**
|
|
553
|
+
* Deletes an appliance group from storage.
|
|
554
|
+
*
|
|
555
|
+
* @param groupId ID of the group to delete
|
|
556
|
+
*/
|
|
557
|
+
protected deleteGroup(groupId: string): Promise<void>;
|
|
558
|
+
/** Returns a copy of the group without its `id`, ready to be saved again. */
|
|
559
|
+
private withoutId;
|
|
437
560
|
/**
|
|
438
561
|
* Gets the current identifier strategy.
|
|
439
562
|
*/
|
|
@@ -495,5 +618,6 @@ export interface PartialEnyoAppliance {
|
|
|
495
618
|
* Optional identifier of the cloud-deployed energy app package that manages
|
|
496
619
|
* this appliance. Mirrors {@link EnyoAppliance.cloudPackageId}.
|
|
497
620
|
*/
|
|
498
|
-
cloudPackageId?: string;
|
|
621
|
+
cloudPackageId?: string; /** Whether the appliance is hidden. Mirrors {@link EnyoAppliance.hidden}. */
|
|
622
|
+
hidden?: boolean;
|
|
499
623
|
}
|
|
@@ -13,6 +13,7 @@ class InMemoryApplianceManager extends appliance_manager_js_1.ApplianceManager {
|
|
|
13
13
|
memoryStore = new Map();
|
|
14
14
|
nextId = 1;
|
|
15
15
|
networkDevicesStore = new Map();
|
|
16
|
+
groupStore = new Map();
|
|
16
17
|
/**
|
|
17
18
|
* Creates a new DemoApplianceManager instance.
|
|
18
19
|
* @param energyApp The EnergyApp instance (not used for persistence in demo)
|
|
@@ -75,6 +76,7 @@ class InMemoryApplianceManager extends appliance_manager_js_1.ApplianceManager {
|
|
|
75
76
|
...(appliance.cloudPackageId !== undefined && { cloudPackageId: appliance.cloudPackageId }),
|
|
76
77
|
...(appliance.availableFeatures !== undefined && { availableFeatures: appliance.availableFeatures }),
|
|
77
78
|
...(appliance.compatibilityModes !== undefined && { compatibilityModes: appliance.compatibilityModes }),
|
|
79
|
+
...(appliance.hidden !== undefined && { hidden: appliance.hidden }),
|
|
78
80
|
};
|
|
79
81
|
// On update, merge onto the stored appliance so type-specific metadata and
|
|
80
82
|
// top-level fields that were not supplied in this call are preserved.
|
|
@@ -212,6 +214,19 @@ class InMemoryApplianceManager extends appliance_manager_js_1.ApplianceManager {
|
|
|
212
214
|
if (this.memoryStore.has(applianceId)) {
|
|
213
215
|
this.memoryStore.delete(applianceId);
|
|
214
216
|
this.networkDevicesStore.delete(applianceId);
|
|
217
|
+
// Mirror the host: a removed appliance drops out of its group,
|
|
218
|
+
// and a group left without members is removed.
|
|
219
|
+
for (const group of [...this.groupStore.values()]) {
|
|
220
|
+
if (!group.applianceIds.includes(applianceId))
|
|
221
|
+
continue;
|
|
222
|
+
const remaining = group.applianceIds.filter(id => id !== applianceId);
|
|
223
|
+
if (remaining.length === 0) {
|
|
224
|
+
this.groupStore.delete(group.id);
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
this.groupStore.set(group.id, { ...group, applianceIds: remaining });
|
|
228
|
+
}
|
|
229
|
+
}
|
|
215
230
|
// Clean up cache (from parent class)
|
|
216
231
|
this.clearCache();
|
|
217
232
|
console.log(`[DEMO] Removed appliance ${applianceId}`);
|
|
@@ -287,12 +302,38 @@ class InMemoryApplianceManager extends appliance_manager_js_1.ApplianceManager {
|
|
|
287
302
|
this.updateCache(appliance);
|
|
288
303
|
}
|
|
289
304
|
}
|
|
305
|
+
/**
|
|
306
|
+
* Loads the appliance groups from memory.
|
|
307
|
+
* @returns Copies of all stored groups
|
|
308
|
+
*/
|
|
309
|
+
async loadGroups() {
|
|
310
|
+
return Array.from(this.groupStore.values(), g => structuredClone(g));
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Saves an appliance group in memory.
|
|
314
|
+
* @param group The group to save
|
|
315
|
+
* @param groupId ID of the group to update, or `undefined` to create one
|
|
316
|
+
* @returns The ID of the saved group
|
|
317
|
+
*/
|
|
318
|
+
async persistGroup(group, groupId) {
|
|
319
|
+
const id = groupId ?? (0, node_crypto_1.randomUUID)();
|
|
320
|
+
this.groupStore.set(id, { ...structuredClone(group), id });
|
|
321
|
+
return id;
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Deletes an appliance group from memory.
|
|
325
|
+
* @param groupId ID of the group to delete
|
|
326
|
+
*/
|
|
327
|
+
async deleteGroup(groupId) {
|
|
328
|
+
this.groupStore.delete(groupId);
|
|
329
|
+
}
|
|
290
330
|
/**
|
|
291
331
|
* Clears all data from memory (for testing purposes).
|
|
292
332
|
*/
|
|
293
333
|
clearAllData() {
|
|
294
334
|
this.memoryStore.clear();
|
|
295
335
|
this.networkDevicesStore.clear();
|
|
336
|
+
this.groupStore.clear();
|
|
296
337
|
this.clearCache();
|
|
297
338
|
this.nextId = 1;
|
|
298
339
|
console.log('[DEMO] All data cleared from memory');
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { EnergyApp } from "../../index.cjs";
|
|
2
|
-
import { EnyoAppliance, EnyoApplianceConnectionType, EnyoApplianceMetadata, EnyoApplianceStateEnum, EnyoApplianceTypeEnum } from "../../types/enyo-appliance.cjs";
|
|
2
|
+
import { EnyoAppliance, EnyoApplianceConnectionType, EnyoApplianceGroup, EnyoApplianceMetadata, EnyoApplianceStateEnum, EnyoApplianceTypeEnum } from "../../types/enyo-appliance.cjs";
|
|
3
3
|
import { ApplianceConfig, ApplianceManager, ApplianceManagerConfig, FindResult } from "./appliance-manager.cjs";
|
|
4
4
|
import { IdentifierStrategy } from "./identifier-strategies.cjs";
|
|
5
5
|
/**
|
|
@@ -11,6 +11,7 @@ export declare class InMemoryApplianceManager extends ApplianceManager {
|
|
|
11
11
|
private memoryStore;
|
|
12
12
|
private nextId;
|
|
13
13
|
private networkDevicesStore;
|
|
14
|
+
private groupStore;
|
|
14
15
|
/**
|
|
15
16
|
* Creates a new DemoApplianceManager instance.
|
|
16
17
|
* @param energyApp The EnergyApp instance (not used for persistence in demo)
|
|
@@ -99,6 +100,23 @@ export declare class InMemoryApplianceManager extends ApplianceManager {
|
|
|
99
100
|
* Refreshes the cache with all appliances from memory.
|
|
100
101
|
*/
|
|
101
102
|
refreshCache(): Promise<void>;
|
|
103
|
+
/**
|
|
104
|
+
* Loads the appliance groups from memory.
|
|
105
|
+
* @returns Copies of all stored groups
|
|
106
|
+
*/
|
|
107
|
+
protected loadGroups(): Promise<EnyoApplianceGroup[]>;
|
|
108
|
+
/**
|
|
109
|
+
* Saves an appliance group in memory.
|
|
110
|
+
* @param group The group to save
|
|
111
|
+
* @param groupId ID of the group to update, or `undefined` to create one
|
|
112
|
+
* @returns The ID of the saved group
|
|
113
|
+
*/
|
|
114
|
+
protected persistGroup(group: Omit<EnyoApplianceGroup, 'id'>, groupId: string | undefined): Promise<string>;
|
|
115
|
+
/**
|
|
116
|
+
* Deletes an appliance group from memory.
|
|
117
|
+
* @param groupId ID of the group to delete
|
|
118
|
+
*/
|
|
119
|
+
protected deleteGroup(groupId: string): Promise<void>;
|
|
102
120
|
/**
|
|
103
121
|
* Clears all data from memory (for testing purposes).
|
|
104
122
|
*/
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { EnyoAppliance, EnyoApplianceTypeEnum } from "../types/enyo-appliance.cjs";
|
|
1
|
+
import { EnyoAppliance, EnyoApplianceGroup, EnyoApplianceTypeEnum } from "../types/enyo-appliance.cjs";
|
|
2
2
|
/**
|
|
3
3
|
* Ownership scope for an appliance-created subscription.
|
|
4
4
|
* - `'own'`: appliances created by your own package.
|
|
@@ -32,14 +32,41 @@ export interface EnyoApplianceCreatedFilter {
|
|
|
32
32
|
export interface EnergyAppAppliance {
|
|
33
33
|
/** Save or update an appliance in the system */
|
|
34
34
|
save: (appliance: Omit<EnyoAppliance, 'id'>, applianceId: string | undefined) => Promise<string>;
|
|
35
|
-
/**
|
|
35
|
+
/**
|
|
36
|
+
* Get a list of all registered appliances of your package, including the
|
|
37
|
+
* ones marked {@link EnyoAppliance.hidden}.
|
|
38
|
+
*/
|
|
36
39
|
list: () => Promise<EnyoAppliance[]>;
|
|
37
|
-
/**
|
|
40
|
+
/**
|
|
41
|
+
* Get a list of all appliances. Needs a specific permission. Appliances of
|
|
42
|
+
* other packages marked {@link EnyoAppliance.hidden} are left out; your own
|
|
43
|
+
* package's hidden appliances are included.
|
|
44
|
+
*/
|
|
38
45
|
listAll: () => Promise<EnyoAppliance[]>;
|
|
39
46
|
/** Get a specific appliance by its ID */
|
|
40
47
|
getById: (id: string) => Promise<EnyoAppliance | null>;
|
|
41
48
|
/** Remove an appliance by its ID */
|
|
42
49
|
removeById: (id: string) => Promise<void>;
|
|
50
|
+
/**
|
|
51
|
+
* Save or update a group of your package's appliances, shown in the UI as
|
|
52
|
+
* one collapsible entry — see {@link EnyoApplianceGroup}.
|
|
53
|
+
*
|
|
54
|
+
* Rejected when a member is not an appliance of your package or already
|
|
55
|
+
* belongs to another group; move it by first saving that other group
|
|
56
|
+
* without it.
|
|
57
|
+
* @param group - The group to save
|
|
58
|
+
* @param groupId - ID of the group to update, or `undefined` to create one
|
|
59
|
+
* @returns The ID of the saved group
|
|
60
|
+
*/
|
|
61
|
+
saveGroup: (group: Omit<EnyoApplianceGroup, 'id'>, groupId: string | undefined) => Promise<string>;
|
|
62
|
+
/** Get all appliance groups of your package */
|
|
63
|
+
listGroups: () => Promise<EnyoApplianceGroup[]>;
|
|
64
|
+
/**
|
|
65
|
+
* Remove an appliance group. Only the group goes away — its members stay
|
|
66
|
+
* and are shown individually again.
|
|
67
|
+
* @param groupId - ID of the group to remove
|
|
68
|
+
*/
|
|
69
|
+
removeGroup: (groupId: string) => Promise<void>;
|
|
43
70
|
/**
|
|
44
71
|
* Listen for appliance updates (when an appliance is saved or modified).
|
|
45
72
|
* @param listener - Callback invoked with the updated appliance
|
|
@@ -64,7 +91,8 @@ export interface EnergyAppAppliance {
|
|
|
64
91
|
* The optional {@link EnyoApplianceCreatedFilter filter} controls scope and
|
|
65
92
|
* category: by default only your own package's creations are delivered;
|
|
66
93
|
* `scope: 'all'` and `scope: 'external'` observe appliances created by other
|
|
67
|
-
* packages and require the `AllAppliances` permission.
|
|
94
|
+
* packages and require the `AllAppliances` permission. Appliances of other
|
|
95
|
+
* packages marked {@link EnyoAppliance.hidden} are never delivered.
|
|
68
96
|
*
|
|
69
97
|
* @param listener - Callback invoked with each newly-created appliance
|
|
70
98
|
* @param filter - Optional scope/category filter; defaults to `{ scope: 'own' }`
|
|
@@ -29,6 +29,13 @@ import { EnyoLinkedVehicle, EnyoVehicle, EnyoVehiclePackageUnpairReasonEnum, Eny
|
|
|
29
29
|
* That message is the only write path for a state of charge; publishing it
|
|
30
30
|
* requires the `SendDataBusValues` permission.
|
|
31
31
|
*
|
|
32
|
+
* ## Writing a battery size
|
|
33
|
+
*
|
|
34
|
+
* The same holds for the vehicle's battery size: an app that knows it publishes
|
|
35
|
+
* `EnyoDataBusVehicleBatterySizeUpdateV1` with `batterySizeWh`, and the host
|
|
36
|
+
* stores it on the vehicle, replacing the previous value. It is read back as
|
|
37
|
+
* {@link EnyoVehicle.batterySizeKwh}.
|
|
38
|
+
*
|
|
32
39
|
* ## Pairing a car integration
|
|
33
40
|
*
|
|
34
41
|
* A package in the `vehicle` category — a manufacturer cloud integration —
|
|
@@ -392,4 +392,69 @@ export interface EnyoAppliance {
|
|
|
392
392
|
* downstream consumers can correlate the appliance with its owning package.
|
|
393
393
|
*/
|
|
394
394
|
cloudPackageId?: string;
|
|
395
|
+
/**
|
|
396
|
+
* Marks the appliance as hidden: it is kept in the system but treated as
|
|
397
|
+
* if it did not exist everywhere else.
|
|
398
|
+
*
|
|
399
|
+
* A hidden appliance:
|
|
400
|
+
* - is not shown in any UI (appliance lists, live views, reports, onboarding
|
|
401
|
+
* summaries, notifications);
|
|
402
|
+
* - is not used for energy management — it is neither controlled nor
|
|
403
|
+
* counted in energy flows, forecasts, savings or health evaluation;
|
|
404
|
+
* - has its data bus values, timeseries and events ignored by the host
|
|
405
|
+
* and by other packages;
|
|
406
|
+
* - is excluded from `useAppliances().listAll()` and from appliance
|
|
407
|
+
* events delivered to other packages.
|
|
408
|
+
*
|
|
409
|
+
* The owning package still sees it through
|
|
410
|
+
* `useAppliances().list()` and `getById()`,
|
|
411
|
+
* so it can keep recognising the device on rediscovery instead of creating
|
|
412
|
+
* it again, and unhide it later by saving it with `hidden: false`.
|
|
413
|
+
*
|
|
414
|
+
* Use it for appliances the package has to keep track of but that must not
|
|
415
|
+
* surface — e.g. a duplicate entity a device exposes alongside the real one,
|
|
416
|
+
* or a sub-device the user chose to ignore. Differs from
|
|
417
|
+
* {@link EnyoApplianceStatusEnum.Deactivated}, which keeps the appliance
|
|
418
|
+
* visible and only takes it out of energy management. Omitted means visible.
|
|
419
|
+
*/
|
|
420
|
+
hidden?: boolean;
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* A named group of appliances that the app UI shows as one collapsible entry.
|
|
424
|
+
*
|
|
425
|
+
* Grouping is purely visual. The UI shows the group under its {@link name}
|
|
426
|
+
* with the summed values its members share (power, energy, …) and a
|
|
427
|
+
* collapse/expand toggle that reveals the individual members. The members stay
|
|
428
|
+
* ordinary appliances: their data is counted exactly as without the group,
|
|
429
|
+
* commands still target each member, and the group itself is never an
|
|
430
|
+
* appliance — so nothing is counted twice.
|
|
431
|
+
*
|
|
432
|
+
* Groups are defined by the energy app that owns the member appliances, and
|
|
433
|
+
* every user of the site sees the same groups. Use it when an app brings many
|
|
434
|
+
* appliances of one installation — e.g. dozens of micro-inverters — that would
|
|
435
|
+
* otherwise flood the appliance list.
|
|
436
|
+
*
|
|
437
|
+
* Rules enforced by the host:
|
|
438
|
+
* - every member must be an appliance of the package saving the group;
|
|
439
|
+
* - an appliance belongs to at most one group;
|
|
440
|
+
* - an appliance that is removed or marked {@link EnyoAppliance.hidden} drops
|
|
441
|
+
* out of its group, and a group left without members is removed.
|
|
442
|
+
*
|
|
443
|
+
* Members may be of different {@link EnyoApplianceTypeEnum types}; the UI then
|
|
444
|
+
* sums only the values all members share and shows the rest per member.
|
|
445
|
+
*/
|
|
446
|
+
export interface EnyoApplianceGroup {
|
|
447
|
+
/** Unique identifier of the group, assigned by the host on first save */
|
|
448
|
+
id: string;
|
|
449
|
+
/**
|
|
450
|
+
* Stable, app-defined key of the group, unique within the package — e.g.
|
|
451
|
+
* the serial number of the gateway the members sit behind. Lets the app
|
|
452
|
+
* find its group again after a restart instead of creating a second one.
|
|
453
|
+
* Recommended; the `ApplianceManager` group helpers require it.
|
|
454
|
+
*/
|
|
455
|
+
key?: string;
|
|
456
|
+
/** Name of the group shown in the UI, in the supported languages */
|
|
457
|
+
name: EnyoApplianceName[];
|
|
458
|
+
/** IDs of the member appliances, in the order the UI lists them when expanded */
|
|
459
|
+
applianceIds: string[];
|
|
395
460
|
}
|
|
@@ -847,6 +847,8 @@ var EnyoDataBusMessageEnum;
|
|
|
847
847
|
EnyoDataBusMessageEnum["StopAirConditioningV1"] = "StopAirConditioningV1";
|
|
848
848
|
EnyoDataBusMessageEnum["ChangeAirConditioningOptimizationModeV1"] = "ChangeAirConditioningOptimizationModeV1";
|
|
849
849
|
EnyoDataBusMessageEnum["VehicleSocUpdateV1"] = "VehicleSocUpdateV1";
|
|
850
|
+
/** Provide or update the usable traction battery capacity of a vehicle, in Wh. */
|
|
851
|
+
EnyoDataBusMessageEnum["VehicleBatterySizeUpdateV1"] = "VehicleBatterySizeUpdateV1";
|
|
850
852
|
/** Request the current or estimated state of charge of a vehicle, by vehicle id. */
|
|
851
853
|
EnyoDataBusMessageEnum["RequestVehicleSocEstimateV1"] = "RequestVehicleSocEstimateV1";
|
|
852
854
|
/** Answer to {@link RequestVehicleSocEstimateV1} — the estimate with its age and source, or why none could be given. */
|