iobroker.mg-saic 0.1.3 → 0.1.4
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 +7 -8
- package/build/initialization-retry.js +37 -0
- package/build/initialization-retry.js.map +7 -0
- package/build/main.js +83 -62
- package/build/main.js.map +2 -2
- package/build/runtime-translations.js +13 -13
- package/build/runtime-translations.js.map +2 -2
- package/build/saic-client.js +39 -18
- package/build/saic-client.js.map +3 -3
- package/build/status-metadata.js +9 -3
- package/build/status-metadata.js.map +2 -2
- package/build/vehicle-object-ids.js +53 -0
- package/build/vehicle-object-ids.js.map +7 -0
- package/io-package.json +14 -14
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -25,14 +25,16 @@ For manufacturer information, see [MG Motor](https://www.mgmotor.eu/) and [SAIC
|
|
|
25
25
|
1. Install the adapter and create an instance in ioBroker Admin.
|
|
26
26
|
2. Select the account region and login type (email or phone number).
|
|
27
27
|
3. Enter the account's email address or phone number and password. For phone login, also enter the phone country calling code.
|
|
28
|
-
4. Set the status polling interval
|
|
28
|
+
4. Set the status polling interval to 60 seconds or more. The default is 1800 seconds.
|
|
29
29
|
5. Save the settings and start the instance. Vehicles available to the account appear under `vehicles.<VIN>`.
|
|
30
|
+
Existing vehicle object IDs are reused if the cloud API changes the VIN casing. Device labels use the localized generic vehicle name; the VIN, brand, model, and other available vehicle metadata are provided as read-only states.
|
|
30
31
|
|
|
31
32
|
## Features
|
|
32
33
|
|
|
33
34
|
- Connects ioBroker to the iSmart cloud service and retrieves vehicle status.
|
|
34
35
|
- Provides remote controls for supported vehicle functions.
|
|
35
36
|
- Supports climate, heating, and charging features where available for the vehicle.
|
|
37
|
+
- Omits unrecognized free-form charging text while retaining numeric measurements and validated charging-point identifiers.
|
|
36
38
|
|
|
37
39
|
## Installation
|
|
38
40
|
|
|
@@ -43,6 +45,10 @@ The adapter is published on npm and available from ioBroker's Latest adapter rep
|
|
|
43
45
|
[](https://www.paypal.com/donate/?hosted_button_id=3RJQGSJNRSHUJ)
|
|
44
46
|
|
|
45
47
|
## Changelog
|
|
48
|
+
### 0.1.4 (2026-11-11)
|
|
49
|
+
|
|
50
|
+
- (Voodoo2man) Corrected command-response validation, status values, charging-field handling, vehicle ID reuse, and cloud polling intervals; removed the non-informative vehicle name state.
|
|
51
|
+
|
|
46
52
|
### 0.1.3 (2026-11-08)
|
|
47
53
|
|
|
48
54
|
- (Voodoo2man) Retry startup initialization after temporary cloud login and vehicle-list outages with bounded backoff, prevent false setting writes from triggering remote setting actions, and add the missing region labels in the Admin UI.
|
|
@@ -64,13 +70,6 @@ The adapter is published on npm and available from ioBroker's Latest adapter rep
|
|
|
64
70
|
- (Voodoo2man) Applied confirmed profile-specific charging conversions.
|
|
65
71
|
- (Voodoo2man) Calculated available battery energy from battery level and usable capacity.
|
|
66
72
|
|
|
67
|
-
### 0.0.4 (2026-10-24)
|
|
68
|
-
|
|
69
|
-
- (Voodoo2man) Corrected GPS coordinates.
|
|
70
|
-
- (Voodoo2man) Fixed incorrect datapoint values.
|
|
71
|
-
- (Voodoo2man) Removed duplicate and irrelevant datapoints.
|
|
72
|
-
- (Voodoo2man) Set min version of admin to 8.2.0, js-controller 7.2.2, node.js 24
|
|
73
|
-
|
|
74
73
|
Older changelog entries are available in [CHANGELOG_OLD.md](CHANGELOG_OLD.md).
|
|
75
74
|
|
|
76
75
|
## License
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var initialization_retry_exports = {};
|
|
20
|
+
__export(initialization_retry_exports, {
|
|
21
|
+
calculateInitializationRetryDelayMs: () => calculateInitializationRetryDelayMs
|
|
22
|
+
});
|
|
23
|
+
module.exports = __toCommonJS(initialization_retry_exports);
|
|
24
|
+
const INITIALIZATION_RETRY_DELAY_MS = 6e4;
|
|
25
|
+
const MAX_INITIALIZATION_RETRY_DELAY_MS = 5 * INITIALIZATION_RETRY_DELAY_MS;
|
|
26
|
+
function calculateInitializationRetryDelayMs(retryCount) {
|
|
27
|
+
const attempts = Math.max(1, retryCount);
|
|
28
|
+
if (attempts <= 3) {
|
|
29
|
+
return INITIALIZATION_RETRY_DELAY_MS * attempts;
|
|
30
|
+
}
|
|
31
|
+
return MAX_INITIALIZATION_RETRY_DELAY_MS;
|
|
32
|
+
}
|
|
33
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
34
|
+
0 && (module.exports = {
|
|
35
|
+
calculateInitializationRetryDelayMs
|
|
36
|
+
});
|
|
37
|
+
//# sourceMappingURL=initialization-retry.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/initialization-retry.ts"],
|
|
4
|
+
"sourcesContent": ["const INITIALIZATION_RETRY_DELAY_MS = 60_000;\nconst MAX_INITIALIZATION_RETRY_DELAY_MS = 5 * INITIALIZATION_RETRY_DELAY_MS;\n\n/**\n * Returns the bounded delay before retrying initialization.\n *\n * @param retryCount Number of initialization failures.\n * @returns Retry delay in milliseconds.\n */\nexport function calculateInitializationRetryDelayMs(retryCount: number): number {\n\tconst attempts = Math.max(1, retryCount);\n\tif (attempts <= 3) {\n\t\treturn INITIALIZATION_RETRY_DELAY_MS * attempts;\n\t}\n\treturn MAX_INITIALIZATION_RETRY_DELAY_MS;\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAAM,gCAAgC;AACtC,MAAM,oCAAoC,IAAI;AAQvC,SAAS,oCAAoC,YAA4B;AAC/E,QAAM,WAAW,KAAK,IAAI,GAAG,UAAU;AACvC,MAAI,YAAY,GAAG;AAClB,WAAO,gCAAgC;AAAA,EACxC;AACA,SAAO;AACR;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/build/main.js
CHANGED
|
@@ -5,10 +5,6 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
5
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
6
|
var __getProtoOf = Object.getPrototypeOf;
|
|
7
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
var __export = (target, all) => {
|
|
9
|
-
for (var name in all)
|
|
10
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
-
};
|
|
12
8
|
var __copyProps = (to, from, except, desc) => {
|
|
13
9
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
10
|
for (let key of __getOwnPropNames(from))
|
|
@@ -25,16 +21,11 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
25
21
|
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
22
|
mod
|
|
27
23
|
));
|
|
28
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
-
var main_exports = {};
|
|
30
|
-
__export(main_exports, {
|
|
31
|
-
calculateInitializationRetryDelayMs: () => calculateInitializationRetryDelayMs
|
|
32
|
-
});
|
|
33
|
-
module.exports = __toCommonJS(main_exports);
|
|
34
24
|
var utils = __toESM(require("@iobroker/adapter-core"));
|
|
35
25
|
var import_promises = require("node:fs/promises");
|
|
36
26
|
var import_node_path = require("node:path");
|
|
37
27
|
var import_config = require("./config");
|
|
28
|
+
var import_initialization_retry = require("./initialization-retry");
|
|
38
29
|
var import_saic_client = require("./saic-client");
|
|
39
30
|
var import_regions = require("./regions");
|
|
40
31
|
var import_refresh_command = require("./refresh-command");
|
|
@@ -43,6 +34,7 @@ var import_refresh_poll_timer = require("./refresh-poll-timer");
|
|
|
43
34
|
var import_pending_setting_edits = require("./pending-setting-edits");
|
|
44
35
|
var import_status_metadata = require("./status-metadata");
|
|
45
36
|
var import_runtime_translations = require("./runtime-translations");
|
|
37
|
+
var import_vehicle_object_ids = require("./vehicle-object-ids");
|
|
46
38
|
const chargingGroups = [
|
|
47
39
|
{ source: "chrgMgmtData", id: "session", name: "chargingManagement" },
|
|
48
40
|
{ source: "rvsChargeStatus", id: "statistics", name: "chargeStatus" }
|
|
@@ -120,15 +112,6 @@ const settingInputsByAction = {
|
|
|
120
112
|
};
|
|
121
113
|
const EXPOSE_SOURCE_STATUS_DATAPOINTS = false;
|
|
122
114
|
const PENDING_SETTING_EDITS_FILE = "pending-setting-edits.json";
|
|
123
|
-
const INITIALIZATION_RETRY_DELAY_MS = 6e4;
|
|
124
|
-
const MAX_INITIALIZATION_RETRY_DELAY_MS = 5 * INITIALIZATION_RETRY_DELAY_MS;
|
|
125
|
-
function calculateInitializationRetryDelayMs(retryCount) {
|
|
126
|
-
const attempts = Math.max(1, retryCount);
|
|
127
|
-
if (attempts <= 3) {
|
|
128
|
-
return INITIALIZATION_RETRY_DELAY_MS * attempts;
|
|
129
|
-
}
|
|
130
|
-
return MAX_INITIALIZATION_RETRY_DELAY_MS;
|
|
131
|
-
}
|
|
132
115
|
class MgSaic extends utils.Adapter {
|
|
133
116
|
constructor(options = {}) {
|
|
134
117
|
super({
|
|
@@ -156,6 +139,7 @@ class MgSaic extends utils.Adapter {
|
|
|
156
139
|
accessToken;
|
|
157
140
|
accessTokenExpiresAt = 0;
|
|
158
141
|
vehicles = [];
|
|
142
|
+
vehicleObjectIds = /* @__PURE__ */ new Map();
|
|
159
143
|
vehicleStatusFieldIds = /* @__PURE__ */ new Map();
|
|
160
144
|
lastStatusTimes = /* @__PURE__ */ new Map();
|
|
161
145
|
lastCanonicalStatusTimes = /* @__PURE__ */ new Map();
|
|
@@ -268,15 +252,17 @@ class MgSaic extends utils.Adapter {
|
|
|
268
252
|
return;
|
|
269
253
|
}
|
|
270
254
|
try {
|
|
255
|
+
await this.loadVehicleObjectIds();
|
|
271
256
|
for (const vehicle of this.vehicles) {
|
|
272
257
|
if (this.stopping) {
|
|
273
258
|
return;
|
|
274
259
|
}
|
|
275
260
|
await this.createVehicleObjects(vehicle);
|
|
261
|
+
const vehicleObjectId = this.getVehicleObjectId(vehicle.vin);
|
|
276
262
|
if (!EXPOSE_SOURCE_STATUS_DATAPOINTS) {
|
|
277
|
-
await this.delObjectAsync(`vehicles.${
|
|
263
|
+
await this.delObjectAsync(`vehicles.${vehicleObjectId}.cachedStatus`, { recursive: true });
|
|
278
264
|
}
|
|
279
|
-
await this.delObjectAsync(`vehicles.${
|
|
265
|
+
await this.delObjectAsync(`vehicles.${vehicleObjectId}.canonicalStatus`, { recursive: true });
|
|
280
266
|
}
|
|
281
267
|
try {
|
|
282
268
|
await this.restorePendingSettingEdits();
|
|
@@ -309,7 +295,7 @@ class MgSaic extends utils.Adapter {
|
|
|
309
295
|
return;
|
|
310
296
|
}
|
|
311
297
|
this.initializationRetryCount += 1;
|
|
312
|
-
const retryDelayMs = calculateInitializationRetryDelayMs(this.initializationRetryCount);
|
|
298
|
+
const retryDelayMs = (0, import_initialization_retry.calculateInitializationRetryDelayMs)(this.initializationRetryCount);
|
|
313
299
|
if (this.initializationRetryTimer !== void 0) {
|
|
314
300
|
this.clearTimeout(this.initializationRetryTimer);
|
|
315
301
|
}
|
|
@@ -564,10 +550,12 @@ class MgSaic extends utils.Adapter {
|
|
|
564
550
|
return;
|
|
565
551
|
}
|
|
566
552
|
const settingAction = settingActions.find(
|
|
567
|
-
({ id: actionId }) => this.vehicles.some(
|
|
553
|
+
({ id: actionId }) => this.vehicles.some(
|
|
554
|
+
({ vin }) => eventId === `${this.namespace}.${this.vehicleCommandStatePath(vin, actionId)}`
|
|
555
|
+
)
|
|
568
556
|
);
|
|
569
557
|
const settingVehicle = this.vehicles.find(
|
|
570
|
-
({ vin }) => settingAction && eventId === `${this.namespace}.${
|
|
558
|
+
({ vin }) => settingAction && eventId === `${this.namespace}.${this.vehicleCommandStatePath(vin, settingAction.id)}`
|
|
571
559
|
);
|
|
572
560
|
if (settingAction && settingVehicle) {
|
|
573
561
|
if (state.val === true) {
|
|
@@ -577,16 +565,16 @@ class MgSaic extends utils.Adapter {
|
|
|
577
565
|
}
|
|
578
566
|
const settingInput = this.vehicles.flatMap(
|
|
579
567
|
({ vin }) => Object.keys(settingGroups).filter((stateId) => stateId !== "driverSeatHeatLevel" && stateId !== "passengerSeatHeatLevel").map((stateId) => ({ vin, stateId }))
|
|
580
|
-
).find(({ vin, stateId }) => eventId === `${this.namespace}.${
|
|
568
|
+
).find(({ vin, stateId }) => eventId === `${this.namespace}.${this.vehicleCommandStatePath(vin, stateId)}`);
|
|
581
569
|
if (settingInput) {
|
|
582
|
-
const stateId =
|
|
570
|
+
const stateId = this.vehicleCommandStatePath(settingInput.vin, settingInput.stateId);
|
|
583
571
|
this.pendingSettingEdits.set(stateId, ++this.settingEditSequence);
|
|
584
572
|
void this.acknowledgeSettingEdit(stateId, state.val);
|
|
585
573
|
return;
|
|
586
574
|
}
|
|
587
575
|
const seatHeating = this.vehicles.flatMap(
|
|
588
576
|
({ vin }) => ["driverSeatHeatLevel", "passengerSeatHeatLevel"].map((stateId) => ({ vin, stateId }))
|
|
589
|
-
).find(({ vin, stateId }) => eventId === `${this.namespace}.${
|
|
577
|
+
).find(({ vin, stateId }) => eventId === `${this.namespace}.${this.vehicleCommandStatePath(vin, stateId)}`);
|
|
590
578
|
if (seatHeating) {
|
|
591
579
|
const seat = seatHeating.stateId === "driverSeatHeatLevel" ? "driver" : "passenger";
|
|
592
580
|
void this.executeSeatHeating(seatHeating.vin, seat, state.val);
|
|
@@ -596,10 +584,12 @@ class MgSaic extends utils.Adapter {
|
|
|
596
584
|
return;
|
|
597
585
|
}
|
|
598
586
|
const commandEntry = vehicleCommands.find(
|
|
599
|
-
({ id: commandId }) => this.vehicles.some(
|
|
587
|
+
({ id: commandId }) => this.vehicles.some(
|
|
588
|
+
({ vin }) => eventId === `${this.namespace}.${this.vehicleCommandStatePath(vin, commandId)}`
|
|
589
|
+
)
|
|
600
590
|
);
|
|
601
591
|
const vehicle = this.vehicles.find(
|
|
602
|
-
({ vin }) => commandEntry && eventId === `${this.namespace}.${
|
|
592
|
+
({ vin }) => commandEntry && eventId === `${this.namespace}.${this.vehicleCommandStatePath(vin, commandEntry.id)}`
|
|
603
593
|
);
|
|
604
594
|
if (!commandEntry || !vehicle) {
|
|
605
595
|
return;
|
|
@@ -667,7 +657,7 @@ class MgSaic extends utils.Adapter {
|
|
|
667
657
|
async restorePendingSettingEdits() {
|
|
668
658
|
const validStateIds = new Set(
|
|
669
659
|
this.vehicles.flatMap(
|
|
670
|
-
({ vin }) => Object.keys(settingGroups).filter((stateId) => stateId !== "driverSeatHeatLevel" && stateId !== "passengerSeatHeatLevel").map((stateId) =>
|
|
660
|
+
({ vin }) => Object.keys(settingGroups).filter((stateId) => stateId !== "driverSeatHeatLevel" && stateId !== "passengerSeatHeatLevel").map((stateId) => this.vehicleCommandStatePath(vin, stateId))
|
|
671
661
|
)
|
|
672
662
|
);
|
|
673
663
|
const savedFileValue = await this.readPendingSettingEditsFile();
|
|
@@ -682,8 +672,28 @@ class MgSaic extends utils.Adapter {
|
|
|
682
672
|
}
|
|
683
673
|
await this.persistPendingSettingEdits();
|
|
684
674
|
}
|
|
675
|
+
async loadVehicleObjectIds() {
|
|
676
|
+
const objects = await this.getObjectListAsync({
|
|
677
|
+
startkey: `${this.namespace}.vehicles.`,
|
|
678
|
+
endkey: `${this.namespace}.vehicles.\u9999`
|
|
679
|
+
});
|
|
680
|
+
const namespacePrefix = `${this.namespace}.`;
|
|
681
|
+
const existingDeviceIds = objects.rows.filter((row) => row.value.type === "device" && row.id.startsWith(namespacePrefix)).map((row) => row.id.slice(namespacePrefix.length));
|
|
682
|
+
const existingVehicleObjectIds = (0, import_vehicle_object_ids.indexExistingVehicleObjectIds)(existingDeviceIds);
|
|
683
|
+
this.vehicleObjectIds = /* @__PURE__ */ new Map();
|
|
684
|
+
for (const { vin } of this.vehicles) {
|
|
685
|
+
this.vehicleObjectIds.set(vin.toLowerCase(), (0, import_vehicle_object_ids.resolveVehicleObjectId)(existingVehicleObjectIds, vin));
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
getVehicleObjectId(vin) {
|
|
689
|
+
var _a;
|
|
690
|
+
return (_a = this.vehicleObjectIds.get(vin.toLowerCase())) != null ? _a : vin;
|
|
691
|
+
}
|
|
692
|
+
vehicleCommandStatePath(vin, id) {
|
|
693
|
+
return vehicleCommandPath(this.getVehicleObjectId(vin), id);
|
|
694
|
+
}
|
|
685
695
|
async executeSeatHeating(vin, seat, value) {
|
|
686
|
-
const stateId =
|
|
696
|
+
const stateId = this.vehicleCommandStatePath(vin, `${seat}SeatHeatLevel`);
|
|
687
697
|
if (typeof value !== "number" || !Number.isInteger(value) || value < 0 || value > 3) {
|
|
688
698
|
await this.setStateAsync(stateId, { ack: true });
|
|
689
699
|
this.log.warn(`Ignored ${seat} seat heating level for ${vin}: expected an integer from 0 to 3`);
|
|
@@ -723,9 +733,9 @@ class MgSaic extends utils.Adapter {
|
|
|
723
733
|
}
|
|
724
734
|
}
|
|
725
735
|
async executeVehicleSetting(vin, action) {
|
|
726
|
-
const stateId =
|
|
736
|
+
const stateId = this.vehicleCommandStatePath(vin, action);
|
|
727
737
|
const editVersions = new Map(
|
|
728
|
-
settingInputsByAction[action].map((settingId) =>
|
|
738
|
+
settingInputsByAction[action].map((settingId) => this.vehicleCommandStatePath(vin, settingId)).filter((settingId) => this.pendingSettingEdits.has(settingId)).map((settingId) => [settingId, this.pendingSettingEdits.get(settingId)])
|
|
729
739
|
);
|
|
730
740
|
if (this.vehicleCommandsInProgress.has(vin)) {
|
|
731
741
|
await this.setStateAsync(stateId, { val: false, ack: true });
|
|
@@ -781,14 +791,14 @@ class MgSaic extends utils.Adapter {
|
|
|
781
791
|
}
|
|
782
792
|
async readVehicleSetting(vin, action) {
|
|
783
793
|
const readNumber = async (id) => {
|
|
784
|
-
const state = await this.getStateAsync(
|
|
794
|
+
const state = await this.getStateAsync(this.vehicleCommandStatePath(vin, id));
|
|
785
795
|
if (!state || typeof state.val !== "number" || !Number.isFinite(state.val)) {
|
|
786
796
|
throw new Error(`Setting ${id} must contain a finite number`);
|
|
787
797
|
}
|
|
788
798
|
return state.val;
|
|
789
799
|
};
|
|
790
800
|
const readString = async (id) => {
|
|
791
|
-
const state = await this.getStateAsync(
|
|
801
|
+
const state = await this.getStateAsync(this.vehicleCommandStatePath(vin, id));
|
|
792
802
|
if (!state || typeof state.val !== "string") {
|
|
793
803
|
throw new Error(`Setting ${id} must contain a time in HH:MM format`);
|
|
794
804
|
}
|
|
@@ -816,7 +826,7 @@ class MgSaic extends utils.Adapter {
|
|
|
816
826
|
mode: await readNumber("chargingScheduleMode")
|
|
817
827
|
};
|
|
818
828
|
}
|
|
819
|
-
const enabled = await this.getStateAsync(
|
|
829
|
+
const enabled = await this.getStateAsync(this.vehicleCommandStatePath(vin, "batteryHeatingScheduleEnabled"));
|
|
820
830
|
if (!enabled || typeof enabled.val !== "boolean") {
|
|
821
831
|
throw new Error("Battery heating schedule enabled must be boolean");
|
|
822
832
|
}
|
|
@@ -827,7 +837,7 @@ class MgSaic extends utils.Adapter {
|
|
|
827
837
|
};
|
|
828
838
|
}
|
|
829
839
|
async executeVehicleCommand(vin, command) {
|
|
830
|
-
const stateId =
|
|
840
|
+
const stateId = this.vehicleCommandStatePath(vin, command);
|
|
831
841
|
if (this.vehicleCommandsInProgress.has(vin)) {
|
|
832
842
|
await this.setStateAsync(stateId, { val: false, ack: true });
|
|
833
843
|
this.log.warn(`Ignored ${command} command for ${vin}: another vehicle command is still running`);
|
|
@@ -881,7 +891,8 @@ class MgSaic extends utils.Adapter {
|
|
|
881
891
|
if (!EXPOSE_SOURCE_STATUS_DATAPOINTS && (channel === "liveStatus" || channel === "cachedStatus")) {
|
|
882
892
|
return;
|
|
883
893
|
}
|
|
884
|
-
const
|
|
894
|
+
const vehicleObjectId = this.getVehicleObjectId(vin);
|
|
895
|
+
const statusId = `vehicles.${vehicleObjectId}.${channel}`;
|
|
885
896
|
await this.setObjectNotExistsAsync(statusId, {
|
|
886
897
|
type: "channel",
|
|
887
898
|
common: { name: (0, import_runtime_translations.getRuntimeName)(channelName) },
|
|
@@ -973,7 +984,7 @@ class MgSaic extends utils.Adapter {
|
|
|
973
984
|
}
|
|
974
985
|
const seatSetting = fieldName === "frontLeftSeatHeatLevel" ? "driverSeatHeatLevel" : fieldName === "frontRightSeatHeatLevel" ? "passengerSeatHeatLevel" : void 0;
|
|
975
986
|
if (seatSetting && Number.isInteger(value) && value >= 0 && value <= 3) {
|
|
976
|
-
const settingId =
|
|
987
|
+
const settingId = this.vehicleCommandStatePath(vin, seatSetting);
|
|
977
988
|
const settingState = await this.getStateAsync(settingId);
|
|
978
989
|
if (!settingState || settingState.ack) {
|
|
979
990
|
await this.setStateAsync(settingId, { val: value, ack: true });
|
|
@@ -983,9 +994,9 @@ class MgSaic extends utils.Adapter {
|
|
|
983
994
|
}
|
|
984
995
|
}
|
|
985
996
|
async createVehicleChargingObjects(vin, charging, series) {
|
|
986
|
-
var _a, _b;
|
|
997
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
987
998
|
await this.ensureVehicleChargingGroups(vin);
|
|
988
|
-
const rootId = `vehicles.${vin}.status.charging`;
|
|
999
|
+
const rootId = `vehicles.${this.getVehicleObjectId(vin)}.status.charging`;
|
|
989
1000
|
const existingObjects = await this.getObjectListAsync({
|
|
990
1001
|
startkey: `${this.namespace}.${rootId}.`,
|
|
991
1002
|
endkey: `${this.namespace}.${rootId}.\u9999`
|
|
@@ -1057,8 +1068,8 @@ class MgSaic extends utils.Adapter {
|
|
|
1057
1068
|
val: normalizedValue,
|
|
1058
1069
|
ack: true
|
|
1059
1070
|
});
|
|
1060
|
-
if (
|
|
1061
|
-
const targetSocId =
|
|
1071
|
+
if (canonicalField === "bmsOnBdChrgTrgtSOCDspCmd" && typeof value === "number" && value >= 1 && value <= 7) {
|
|
1072
|
+
const targetSocId = this.vehicleCommandStatePath(vin, "chargeTargetSoc");
|
|
1062
1073
|
const targetSocState = await this.getStateAsync(targetSocId);
|
|
1063
1074
|
if ((0, import_status_metadata.shouldApplySettingFeedback)(targetSocState, this.pendingSettingEdits.has(targetSocId))) {
|
|
1064
1075
|
await this.setStateAsync(targetSocId, {
|
|
@@ -1069,35 +1080,41 @@ class MgSaic extends utils.Adapter {
|
|
|
1069
1080
|
}
|
|
1070
1081
|
}
|
|
1071
1082
|
}
|
|
1072
|
-
const
|
|
1073
|
-
const
|
|
1083
|
+
const statisticsFields = (_c = existingFieldIds.get("statistics")) != null ? _c : [];
|
|
1084
|
+
const sessionFields = (_d = existingFieldIds.get("session")) != null ? _d : [];
|
|
1085
|
+
const capacityField = (_e = statisticsFields.find((field) => field.toLowerCase() === "totalbatterycapacity")) != null ? _e : "totalBatteryCapacity";
|
|
1086
|
+
const availableEnergyField = (_f = statisticsFields.find((field) => field.toLowerCase() === "batteryavailableenergy")) != null ? _f : "batteryAvailableEnergy";
|
|
1087
|
+
const socField = (_g = sessionFields.find((field) => field.toLowerCase() === "bmspacksocdsp")) != null ? _g : "bmsPackSOCDsp";
|
|
1088
|
+
const capacityState = await this.getStateAsync(`${rootId}.statistics.${capacityField}`);
|
|
1089
|
+
const socState = await this.getStateAsync(`${rootId}.session.${socField}`);
|
|
1074
1090
|
const availableEnergy = (0, import_status_metadata.calculateAvailableBatteryEnergy)(
|
|
1075
1091
|
capacityState == null ? void 0 : capacityState.val,
|
|
1076
1092
|
socState == null ? void 0 : socState.val,
|
|
1077
1093
|
charging.rvsChargeStatus.packEnergyKwh
|
|
1078
1094
|
);
|
|
1079
1095
|
if (availableEnergy !== void 0) {
|
|
1080
|
-
await this.setStateAsync(`${rootId}.statistics
|
|
1096
|
+
await this.setStateAsync(`${rootId}.statistics.${availableEnergyField}`, {
|
|
1081
1097
|
val: availableEnergy,
|
|
1082
1098
|
ack: true
|
|
1083
1099
|
});
|
|
1084
1100
|
} else {
|
|
1085
|
-
await this.setStateAsync(`${rootId}.statistics
|
|
1101
|
+
await this.setStateAsync(`${rootId}.statistics.${availableEnergyField}`, {
|
|
1086
1102
|
val: null,
|
|
1087
1103
|
ack: true
|
|
1088
1104
|
});
|
|
1089
1105
|
}
|
|
1090
1106
|
for (const [setting, value] of Object.entries((0, import_status_metadata.getChargingSettingFeedback)(charging.chrgMgmtData))) {
|
|
1091
|
-
const stateId =
|
|
1107
|
+
const stateId = this.vehicleCommandStatePath(vin, setting);
|
|
1092
1108
|
const state = await this.getStateAsync(stateId);
|
|
1093
1109
|
if ((0, import_status_metadata.shouldApplySettingFeedback)(state, this.pendingSettingEdits.has(stateId))) {
|
|
1094
1110
|
await this.setStateAsync(stateId, { val: value, ack: true });
|
|
1095
1111
|
}
|
|
1096
1112
|
}
|
|
1097
|
-
await this.delObjectAsync(`vehicles.${vin}.charging`, { recursive: true });
|
|
1113
|
+
await this.delObjectAsync(`vehicles.${this.getVehicleObjectId(vin)}.charging`, { recursive: true });
|
|
1098
1114
|
}
|
|
1099
1115
|
async ensureVehicleChargingGroups(vin) {
|
|
1100
|
-
const
|
|
1116
|
+
const vehicleObjectId = this.getVehicleObjectId(vin);
|
|
1117
|
+
const statusId = `vehicles.${vehicleObjectId}.status`;
|
|
1101
1118
|
const rootId = `${statusId}.charging`;
|
|
1102
1119
|
await this.setObjectNotExistsAsync(statusId, {
|
|
1103
1120
|
type: "channel",
|
|
@@ -1130,7 +1147,8 @@ class MgSaic extends utils.Adapter {
|
|
|
1130
1147
|
if (this.stopping) {
|
|
1131
1148
|
return;
|
|
1132
1149
|
}
|
|
1133
|
-
const
|
|
1150
|
+
const vehicleObjectId = this.getVehicleObjectId(vin);
|
|
1151
|
+
const sourceId = `vehicles.${vehicleObjectId}.status.source`;
|
|
1134
1152
|
await this.setObjectNotExistsAsync(sourceId, {
|
|
1135
1153
|
type: "state",
|
|
1136
1154
|
common: { name: (0, import_runtime_translations.getRuntimeName)("statusSource"), type: "string", role: "text", read: true, write: false },
|
|
@@ -1142,7 +1160,7 @@ class MgSaic extends utils.Adapter {
|
|
|
1142
1160
|
}
|
|
1143
1161
|
await this.setStateAsync(sourceId, { val: source, ack: true });
|
|
1144
1162
|
if (status.statusTime !== void 0) {
|
|
1145
|
-
const sourceTimestampId = `vehicles.${
|
|
1163
|
+
const sourceTimestampId = `vehicles.${vehicleObjectId}.status.sourceTimestamp`;
|
|
1146
1164
|
await this.setObjectNotExistsAsync(sourceTimestampId, {
|
|
1147
1165
|
type: "state",
|
|
1148
1166
|
common: {
|
|
@@ -1166,10 +1184,11 @@ class MgSaic extends utils.Adapter {
|
|
|
1166
1184
|
if (this.stopping) {
|
|
1167
1185
|
return;
|
|
1168
1186
|
}
|
|
1169
|
-
const
|
|
1187
|
+
const vehicleObjectId = this.getVehicleObjectId(vehicle.vin);
|
|
1188
|
+
this.vehicleObjectIds.set(vehicle.vin.toLowerCase(), vehicleObjectId);
|
|
1189
|
+
const deviceId = `vehicles.${vehicleObjectId}`;
|
|
1170
1190
|
const values = {
|
|
1171
1191
|
vin: vehicle.vin,
|
|
1172
|
-
name: vehicle.name,
|
|
1173
1192
|
brandName: vehicle.brandName,
|
|
1174
1193
|
modelName: vehicle.modelName,
|
|
1175
1194
|
series: vehicle.series,
|
|
@@ -1178,14 +1197,20 @@ class MgSaic extends utils.Adapter {
|
|
|
1178
1197
|
isCurrentVehicle: vehicle.isCurrentVehicle,
|
|
1179
1198
|
isSubaccount: vehicle.isSubaccount
|
|
1180
1199
|
};
|
|
1200
|
+
const existingDevice = await this.getObjectAsync(deviceId);
|
|
1181
1201
|
await this.setObjectNotExistsAsync(deviceId, {
|
|
1182
1202
|
type: "device",
|
|
1183
|
-
common: { name:
|
|
1203
|
+
common: { name: (0, import_runtime_translations.getRuntimeName)("vehicle") },
|
|
1184
1204
|
native: {}
|
|
1185
1205
|
});
|
|
1206
|
+
const existingName = existingDevice == null ? void 0 : existingDevice.common.name;
|
|
1207
|
+
if (typeof existingName === "string" && [vehicle.name, vehicle.modelName, vehicle.vin].includes(existingName)) {
|
|
1208
|
+
await this.extendObjectAsync(deviceId, { common: { name: (0, import_runtime_translations.getRuntimeName)("vehicle") } });
|
|
1209
|
+
}
|
|
1186
1210
|
if (this.stopping) {
|
|
1187
1211
|
return;
|
|
1188
1212
|
}
|
|
1213
|
+
await this.delObjectAsync(`${deviceId}.name`);
|
|
1189
1214
|
await this.ensureVehicleChargingGroups(vehicle.vin);
|
|
1190
1215
|
const commandsId = `${deviceId}.commands`;
|
|
1191
1216
|
await this.setObjectNotExistsAsync(commandsId, {
|
|
@@ -1205,7 +1230,7 @@ class MgSaic extends utils.Adapter {
|
|
|
1205
1230
|
await this.extendObjectAsync(groupId, { common: { name: groupName } });
|
|
1206
1231
|
}
|
|
1207
1232
|
for (const command of vehicleCommands) {
|
|
1208
|
-
const commandId =
|
|
1233
|
+
const commandId = this.vehicleCommandStatePath(vehicle.vin, command.id);
|
|
1209
1234
|
await this.setObjectNotExistsAsync(commandId, {
|
|
1210
1235
|
type: "state",
|
|
1211
1236
|
common: {
|
|
@@ -1305,7 +1330,7 @@ class MgSaic extends utils.Adapter {
|
|
|
1305
1330
|
}
|
|
1306
1331
|
];
|
|
1307
1332
|
for (const setting of settingStates) {
|
|
1308
|
-
const stateId =
|
|
1333
|
+
const stateId = this.vehicleCommandStatePath(vehicle.vin, setting.id);
|
|
1309
1334
|
const legacyStateId = `${commandsId}.${setting.id}`;
|
|
1310
1335
|
const stateObject = await this.getObjectAsync(stateId);
|
|
1311
1336
|
const legacyState = await this.getStateAsync(legacyStateId);
|
|
@@ -1356,7 +1381,7 @@ class MgSaic extends utils.Adapter {
|
|
|
1356
1381
|
await this.delObjectAsync(legacyStateId);
|
|
1357
1382
|
}
|
|
1358
1383
|
for (const action of settingActions) {
|
|
1359
|
-
const actionId =
|
|
1384
|
+
const actionId = this.vehicleCommandStatePath(vehicle.vin, action.id);
|
|
1360
1385
|
await this.setObjectNotExistsAsync(actionId, {
|
|
1361
1386
|
type: "state",
|
|
1362
1387
|
common: { name: (0, import_runtime_translations.getRuntimeName)(action.id), type: "boolean", role: "button", read: false, write: true },
|
|
@@ -1431,8 +1456,4 @@ if (require.main !== module) {
|
|
|
1431
1456
|
} else {
|
|
1432
1457
|
(() => new MgSaic())();
|
|
1433
1458
|
}
|
|
1434
|
-
// Annotate the CommonJS export names for ESM import in node:
|
|
1435
|
-
0 && (module.exports = {
|
|
1436
|
-
calculateInitializationRetryDelayMs
|
|
1437
|
-
});
|
|
1438
1459
|
//# sourceMappingURL=main.js.map
|