iobroker.mg-saic 0.0.2 → 0.0.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 +26 -2
- package/build/main.js +288 -21
- package/build/main.js.map +2 -2
- package/build/pending-setting-edits.js +47 -0
- package/build/pending-setting-edits.js.map +7 -0
- package/build/refresh-command.js +8 -3
- package/build/refresh-command.js.map +2 -2
- package/build/runtime-translations.js +103 -0
- package/build/runtime-translations.js.map +2 -2
- package/build/saic-client.js +147 -26
- package/build/saic-client.js.map +3 -3
- package/build/status-metadata.js +181 -2
- package/build/status-metadata.js.map +2 -2
- package/io-package.json +29 -16
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# ioBroker MG/SAIC
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/iobroker.mg-saic)
|
|
4
|
+
[](https://www.npmjs.com/package/iobroker.mg-saic)
|
|
5
|
+
|
|
3
6
|
<img src="MG.png" alt="MG" width="314">
|
|
4
7
|
|
|
5
8
|
The adapter is intended for connecting MG and SAIC vehicles to ioBroker through the iSmart cloud service. It is not affiliated with or endorsed by MG, SAIC, or iSmart.
|
|
@@ -8,7 +11,9 @@ The adapter is intended for connecting MG and SAIC vehicles to ioBroker through
|
|
|
8
11
|
|
|
9
12
|
### Before you start
|
|
10
13
|
|
|
11
|
-
- Node.js
|
|
14
|
+
- Node.js 24.0.0 or later is required.
|
|
15
|
+
- ioBroker js-controller 7.2.2 or later is required.
|
|
16
|
+
- ioBroker Admin 8.2.0 or later is required.
|
|
12
17
|
- Use an iSmart account that has access to the vehicle. A separate account shared with the vehicle is recommended to avoid interfering with the mobile app session.
|
|
13
18
|
- Have the account password ready. The adapter stores it as a protected setting.
|
|
14
19
|
- Select the region for the account. Available regions are European Union, China, Australia, Brazil, Israel, Turkey, Thailand, and Rest of World. India is not supported.
|
|
@@ -29,9 +34,28 @@ The adapter is intended for connecting MG and SAIC vehicles to ioBroker through
|
|
|
29
34
|
|
|
30
35
|
## Installation
|
|
31
36
|
|
|
32
|
-
The adapter is
|
|
37
|
+
The adapter is published on npm but has not yet been added to the ioBroker adapter catalog.
|
|
38
|
+
|
|
39
|
+
## Support
|
|
40
|
+
|
|
41
|
+
[](https://www.paypal.com/donate/?hosted_button_id=3RJQGSJNRSHUJ)
|
|
33
42
|
|
|
34
43
|
## Changelog
|
|
44
|
+
### 0.0.4 (2026-10-24)
|
|
45
|
+
|
|
46
|
+
- (Voodoo2man) Corrected GPS coordinates.
|
|
47
|
+
- (Voodoo2man) Fixed incorrect datapoint values.
|
|
48
|
+
- (Voodoo2man) Removed duplicate and irrelevant datapoints.
|
|
49
|
+
- (Voodoo2man) Set min version of admin to 8.2.0, js-controller 7.2.2, node.js 24
|
|
50
|
+
|
|
51
|
+
### 0.0.3 (2026-10-21)
|
|
52
|
+
|
|
53
|
+
- (Voodoo2man) Improved release automation, updated Node.js release checks, and removed unpublished version metadata.
|
|
54
|
+
- (Voodoo2man) Added charging data
|
|
55
|
+
- (Voodoo2man) Fixed vehicle datapoint units and roles
|
|
56
|
+
|
|
57
|
+
Older changelog entries are available in [CHANGELOG_OLD.md](CHANGELOG_OLD.md).
|
|
58
|
+
|
|
35
59
|
### 0.0.2 (2026-10-20)
|
|
36
60
|
|
|
37
61
|
- (Voodoo2man) Added regional iSmart account setup, vehicle status, and supported remote controls.
|
package/build/main.js
CHANGED
|
@@ -26,8 +26,13 @@ var import_config = require("./config");
|
|
|
26
26
|
var import_saic_client = require("./saic-client");
|
|
27
27
|
var import_regions = require("./regions");
|
|
28
28
|
var import_refresh_command = require("./refresh-command");
|
|
29
|
+
var import_pending_setting_edits = require("./pending-setting-edits");
|
|
29
30
|
var import_status_metadata = require("./status-metadata");
|
|
30
31
|
var import_runtime_translations = require("./runtime-translations");
|
|
32
|
+
const chargingGroups = [
|
|
33
|
+
{ source: "chrgMgmtData", id: "session", name: "chargingManagement" },
|
|
34
|
+
{ source: "rvsChargeStatus", id: "statistics", name: "chargeStatus" }
|
|
35
|
+
];
|
|
31
36
|
const vehicleCommands = [
|
|
32
37
|
{ id: "lock" },
|
|
33
38
|
{ id: "unlock" },
|
|
@@ -68,16 +73,16 @@ const settingGroups = {
|
|
|
68
73
|
climateTemperature: "climate",
|
|
69
74
|
chargeTargetSoc: "charging",
|
|
70
75
|
chargingCurrentLimit: "charging",
|
|
71
|
-
chargingScheduleStart: "
|
|
72
|
-
chargingScheduleEnd: "
|
|
73
|
-
chargingScheduleMode: "
|
|
76
|
+
chargingScheduleStart: "charging",
|
|
77
|
+
chargingScheduleEnd: "charging",
|
|
78
|
+
chargingScheduleMode: "charging",
|
|
74
79
|
batteryHeatingScheduleEnabled: "schedules",
|
|
75
80
|
batteryHeatingScheduleStart: "schedules"
|
|
76
81
|
};
|
|
77
82
|
const actionGroups = {
|
|
78
83
|
climateTemperatureApply: "climate",
|
|
79
84
|
chargingSettingsApply: "charging",
|
|
80
|
-
chargingScheduleApply: "
|
|
85
|
+
chargingScheduleApply: "charging",
|
|
81
86
|
batteryHeatingScheduleApply: "schedules"
|
|
82
87
|
};
|
|
83
88
|
function vehicleCommandPath(vin, id) {
|
|
@@ -93,6 +98,12 @@ const settingActions = [
|
|
|
93
98
|
{ id: "chargingScheduleApply" },
|
|
94
99
|
{ id: "batteryHeatingScheduleApply" }
|
|
95
100
|
];
|
|
101
|
+
const settingInputsByAction = {
|
|
102
|
+
climateTemperatureApply: ["climateTemperature"],
|
|
103
|
+
chargingSettingsApply: ["chargeTargetSoc", "chargingCurrentLimit"],
|
|
104
|
+
chargingScheduleApply: ["chargingScheduleStart", "chargingScheduleEnd", "chargingScheduleMode"],
|
|
105
|
+
batteryHeatingScheduleApply: ["batteryHeatingScheduleEnabled", "batteryHeatingScheduleStart"]
|
|
106
|
+
};
|
|
96
107
|
const EXPOSE_SOURCE_STATUS_DATAPOINTS = false;
|
|
97
108
|
class MgSaic extends utils.Adapter {
|
|
98
109
|
constructor(options = {}) {
|
|
@@ -118,6 +129,9 @@ class MgSaic extends utils.Adapter {
|
|
|
118
129
|
lastStatusTimes = /* @__PURE__ */ new Map();
|
|
119
130
|
lastCanonicalStatusTimes = /* @__PURE__ */ new Map();
|
|
120
131
|
vehicleCommandsInProgress = /* @__PURE__ */ new Set();
|
|
132
|
+
pendingSettingEdits = /* @__PURE__ */ new Map();
|
|
133
|
+
pendingSettingEditsWrite = Promise.resolve();
|
|
134
|
+
settingEditSequence = 0;
|
|
121
135
|
async onReady() {
|
|
122
136
|
var _a, _b, _c, _d, _e;
|
|
123
137
|
if (this.stopping) {
|
|
@@ -128,6 +142,26 @@ class MgSaic extends utils.Adapter {
|
|
|
128
142
|
await this.extendObjectAsync("info.backend", { common: { name: (0, import_runtime_translations.getRuntimeName)("backend") } });
|
|
129
143
|
await this.extendObjectAsync("info.connection", { common: { name: (0, import_runtime_translations.getRuntimeName)("connection") } });
|
|
130
144
|
await this.extendObjectAsync("info.lastError", { common: { name: (0, import_runtime_translations.getRuntimeName)("lastError") } });
|
|
145
|
+
await this.setObjectNotExistsAsync("internal", {
|
|
146
|
+
type: "channel",
|
|
147
|
+
common: { name: "Internal" },
|
|
148
|
+
native: {}
|
|
149
|
+
});
|
|
150
|
+
await this.setObjectNotExistsAsync("internal.pendingSettingEdits", {
|
|
151
|
+
type: "state",
|
|
152
|
+
common: { name: "Pending setting edits", type: "string", role: "json", read: true, write: false },
|
|
153
|
+
native: {}
|
|
154
|
+
});
|
|
155
|
+
await this.setObjectNotExistsAsync("commands", {
|
|
156
|
+
type: "channel",
|
|
157
|
+
common: { name: (0, import_runtime_translations.getRuntimeName)("commands") },
|
|
158
|
+
native: {}
|
|
159
|
+
});
|
|
160
|
+
await this.setObjectAsync("vehicles", {
|
|
161
|
+
type: "folder",
|
|
162
|
+
common: { name: "Vehicles" },
|
|
163
|
+
native: {}
|
|
164
|
+
});
|
|
131
165
|
await this.setObjectNotExistsAsync("commands.refresh", {
|
|
132
166
|
type: "state",
|
|
133
167
|
common: {
|
|
@@ -216,6 +250,7 @@ class MgSaic extends utils.Adapter {
|
|
|
216
250
|
}
|
|
217
251
|
await this.delObjectAsync(`vehicles.${vehicle.vin}.canonicalStatus`, { recursive: true });
|
|
218
252
|
}
|
|
253
|
+
await this.restorePendingSettingEdits();
|
|
219
254
|
await this.setState("info.connection", true, true);
|
|
220
255
|
this.log.info(`SAIC account authenticated; discovered ${this.vehicles.length} vehicle(s).`);
|
|
221
256
|
if (this.vehicles.length === 0) {
|
|
@@ -287,6 +322,22 @@ class MgSaic extends utils.Adapter {
|
|
|
287
322
|
break;
|
|
288
323
|
}
|
|
289
324
|
}
|
|
325
|
+
try {
|
|
326
|
+
const charging = await this.client.getVehicleChargingStatus(
|
|
327
|
+
this.loginOptions,
|
|
328
|
+
this.accessToken,
|
|
329
|
+
vehicle.vin,
|
|
330
|
+
this.abortController.signal
|
|
331
|
+
);
|
|
332
|
+
await this.createVehicleChargingObjects(vehicle.vin, charging);
|
|
333
|
+
} catch (error) {
|
|
334
|
+
if (this.stopping) {
|
|
335
|
+
break;
|
|
336
|
+
}
|
|
337
|
+
const details = error instanceof Error ? error.message : "Unknown charging data error";
|
|
338
|
+
statusErrors.push(`Charging data request failed for ${vehicle.vin}: ${details}`);
|
|
339
|
+
this.log.warn(`Could not retrieve charging data for vehicle ${vehicle.vin}: ${details}`);
|
|
340
|
+
}
|
|
290
341
|
let status;
|
|
291
342
|
try {
|
|
292
343
|
this.log.debug(`Refreshing live status for ${vehicle.vin}`);
|
|
@@ -372,6 +423,33 @@ class MgSaic extends utils.Adapter {
|
|
|
372
423
|
statusErrors.push(`Ignored older live status for ${vehicle.vin}`);
|
|
373
424
|
continue;
|
|
374
425
|
}
|
|
426
|
+
if (status.basicVehicleStatus.mileage === void 0) {
|
|
427
|
+
try {
|
|
428
|
+
const cachedStatus = await this.client.getCachedVehicleStatus(
|
|
429
|
+
this.loginOptions,
|
|
430
|
+
this.accessToken,
|
|
431
|
+
vehicle.vin,
|
|
432
|
+
this.abortController.signal
|
|
433
|
+
);
|
|
434
|
+
const cachedMileage = cachedStatus.basicVehicleStatus.mileage;
|
|
435
|
+
const mileage = (0, import_status_metadata.resolveOdometerValue)(void 0, cachedMileage);
|
|
436
|
+
if (mileage !== void 0) {
|
|
437
|
+
status = {
|
|
438
|
+
...status,
|
|
439
|
+
basicVehicleStatus: { ...status.basicVehicleStatus, mileage }
|
|
440
|
+
};
|
|
441
|
+
this.log.debug(
|
|
442
|
+
`Used cached odometer for ${vehicle.vin} because live status omitted mileage`
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
} catch (cacheError) {
|
|
446
|
+
if (this.stopping) {
|
|
447
|
+
break;
|
|
448
|
+
}
|
|
449
|
+
const details = cacheError instanceof Error ? cacheError.message : "Unknown cache error";
|
|
450
|
+
this.log.debug(`Could not retrieve cached odometer for ${vehicle.vin}: ${details}`);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
375
453
|
try {
|
|
376
454
|
this.log.debug(
|
|
377
455
|
`Received live status for ${vehicle.vin}: ${Object.keys(status.basicVehicleStatus).length} basic fields, statusTime ${(_a = status.statusTime) != null ? _a : "missing"}`
|
|
@@ -413,6 +491,7 @@ class MgSaic extends utils.Adapter {
|
|
|
413
491
|
if (this.stopping) {
|
|
414
492
|
return;
|
|
415
493
|
}
|
|
494
|
+
const eventId = (0, import_refresh_command.normalizeStateChangeId)(id, this.namespace);
|
|
416
495
|
if ((0, import_refresh_command.isRefreshCommand)(id, this.namespace, state)) {
|
|
417
496
|
void this.setStateAsync("commands.refresh", { val: false, ack: true }).catch((error) => {
|
|
418
497
|
const details = error instanceof Error ? error.message : "Unknown state error";
|
|
@@ -425,10 +504,10 @@ class MgSaic extends utils.Adapter {
|
|
|
425
504
|
return;
|
|
426
505
|
}
|
|
427
506
|
const settingAction = settingActions.find(
|
|
428
|
-
({ id: actionId }) => this.vehicles.some(({ vin }) =>
|
|
507
|
+
({ id: actionId }) => this.vehicles.some(({ vin }) => eventId === `${this.namespace}.${vehicleCommandPath(vin, actionId)}`)
|
|
429
508
|
);
|
|
430
509
|
const settingVehicle = this.vehicles.find(
|
|
431
|
-
({ vin }) => settingAction &&
|
|
510
|
+
({ vin }) => settingAction && eventId === `${this.namespace}.${vehicleCommandPath(vin, settingAction.id)}`
|
|
432
511
|
);
|
|
433
512
|
if (settingAction && settingVehicle) {
|
|
434
513
|
void this.executeVehicleSetting(settingVehicle.vin, settingAction.id);
|
|
@@ -436,17 +515,16 @@ class MgSaic extends utils.Adapter {
|
|
|
436
515
|
}
|
|
437
516
|
const settingInput = this.vehicles.flatMap(
|
|
438
517
|
({ vin }) => Object.keys(settingGroups).filter((stateId) => stateId !== "driverSeatHeatLevel" && stateId !== "passengerSeatHeatLevel").map((stateId) => ({ vin, stateId }))
|
|
439
|
-
).find(({ vin, stateId }) =>
|
|
518
|
+
).find(({ vin, stateId }) => eventId === `${this.namespace}.${vehicleCommandPath(vin, stateId)}`);
|
|
440
519
|
if (settingInput) {
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
});
|
|
520
|
+
const stateId = vehicleCommandPath(settingInput.vin, settingInput.stateId);
|
|
521
|
+
this.pendingSettingEdits.set(stateId, ++this.settingEditSequence);
|
|
522
|
+
void this.acknowledgeSettingEdit(stateId, state.val);
|
|
445
523
|
return;
|
|
446
524
|
}
|
|
447
525
|
const seatHeating = this.vehicles.flatMap(
|
|
448
526
|
({ vin }) => ["driverSeatHeatLevel", "passengerSeatHeatLevel"].map((stateId) => ({ vin, stateId }))
|
|
449
|
-
).find(({ vin, stateId }) =>
|
|
527
|
+
).find(({ vin, stateId }) => eventId === `${this.namespace}.${vehicleCommandPath(vin, stateId)}`);
|
|
450
528
|
if (seatHeating) {
|
|
451
529
|
const seat = seatHeating.stateId === "driverSeatHeatLevel" ? "driver" : "passenger";
|
|
452
530
|
void this.executeSeatHeating(seatHeating.vin, seat, state.val);
|
|
@@ -456,16 +534,51 @@ class MgSaic extends utils.Adapter {
|
|
|
456
534
|
return;
|
|
457
535
|
}
|
|
458
536
|
const commandEntry = vehicleCommands.find(
|
|
459
|
-
({ id: commandId }) => this.vehicles.some(({ vin }) =>
|
|
537
|
+
({ id: commandId }) => this.vehicles.some(({ vin }) => eventId === `${this.namespace}.${vehicleCommandPath(vin, commandId)}`)
|
|
460
538
|
);
|
|
461
539
|
const vehicle = this.vehicles.find(
|
|
462
|
-
({ vin }) => commandEntry &&
|
|
540
|
+
({ vin }) => commandEntry && eventId === `${this.namespace}.${vehicleCommandPath(vin, commandEntry.id)}`
|
|
463
541
|
);
|
|
464
542
|
if (!commandEntry || !vehicle) {
|
|
465
543
|
return;
|
|
466
544
|
}
|
|
467
545
|
void this.executeVehicleCommand(vehicle.vin, commandEntry.id);
|
|
468
546
|
}
|
|
547
|
+
async acknowledgeSettingEdit(stateId, value) {
|
|
548
|
+
try {
|
|
549
|
+
await this.persistPendingSettingEdits();
|
|
550
|
+
await this.setStateAsync(stateId, { val: value, ack: true });
|
|
551
|
+
} catch (error) {
|
|
552
|
+
const details = error instanceof Error ? error.message : "Unknown state error";
|
|
553
|
+
this.log.error(`Could not persist pending setting ${stateId}: ${details}`);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
async persistPendingSettingEdits() {
|
|
557
|
+
const serialized = (0, import_pending_setting_edits.serializePendingSettingEdits)(this.pendingSettingEdits.keys());
|
|
558
|
+
const write = this.pendingSettingEditsWrite.catch(() => void 0).then(async () => {
|
|
559
|
+
await this.setStateAsync("internal.pendingSettingEdits", { val: serialized, ack: true });
|
|
560
|
+
});
|
|
561
|
+
this.pendingSettingEditsWrite = write;
|
|
562
|
+
await write;
|
|
563
|
+
}
|
|
564
|
+
async restorePendingSettingEdits() {
|
|
565
|
+
const validStateIds = new Set(
|
|
566
|
+
this.vehicles.flatMap(
|
|
567
|
+
({ vin }) => Object.keys(settingGroups).filter((stateId) => stateId !== "driverSeatHeatLevel" && stateId !== "passengerSeatHeatLevel").map((stateId) => vehicleCommandPath(vin, stateId))
|
|
568
|
+
)
|
|
569
|
+
);
|
|
570
|
+
const savedState = await this.getStateAsync("internal.pendingSettingEdits");
|
|
571
|
+
for (const stateId of (0, import_pending_setting_edits.parsePendingSettingEdits)(savedState == null ? void 0 : savedState.val, validStateIds)) {
|
|
572
|
+
this.pendingSettingEdits.set(stateId, ++this.settingEditSequence);
|
|
573
|
+
}
|
|
574
|
+
for (const stateId of validStateIds) {
|
|
575
|
+
const currentState = await this.getStateAsync(stateId);
|
|
576
|
+
if (currentState && !currentState.ack && !this.pendingSettingEdits.has(stateId)) {
|
|
577
|
+
this.pendingSettingEdits.set(stateId, ++this.settingEditSequence);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
await this.persistPendingSettingEdits();
|
|
581
|
+
}
|
|
469
582
|
async executeSeatHeating(vin, seat, value) {
|
|
470
583
|
const stateId = vehicleCommandPath(vin, `${seat}SeatHeatLevel`);
|
|
471
584
|
if (typeof value !== "number" || !Number.isInteger(value) || value < 0 || value > 3) {
|
|
@@ -508,6 +621,9 @@ class MgSaic extends utils.Adapter {
|
|
|
508
621
|
}
|
|
509
622
|
async executeVehicleSetting(vin, action) {
|
|
510
623
|
const stateId = vehicleCommandPath(vin, action);
|
|
624
|
+
const editVersions = new Map(
|
|
625
|
+
settingInputsByAction[action].map((settingId) => vehicleCommandPath(vin, settingId)).filter((settingId) => this.pendingSettingEdits.has(settingId)).map((settingId) => [settingId, this.pendingSettingEdits.get(settingId)])
|
|
626
|
+
);
|
|
511
627
|
if (this.vehicleCommandsInProgress.has(vin)) {
|
|
512
628
|
await this.setStateAsync(stateId, { val: false, ack: true });
|
|
513
629
|
this.log.warn(`Ignored ${action} for ${vin}: another vehicle command is still running`);
|
|
@@ -530,6 +646,26 @@ class MgSaic extends utils.Adapter {
|
|
|
530
646
|
setting,
|
|
531
647
|
this.abortController.signal
|
|
532
648
|
);
|
|
649
|
+
const clearedEdits = [];
|
|
650
|
+
for (const [settingId, version] of editVersions) {
|
|
651
|
+
if (this.pendingSettingEdits.get(settingId) === version) {
|
|
652
|
+
this.pendingSettingEdits.delete(settingId);
|
|
653
|
+
clearedEdits.push([settingId, version]);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
try {
|
|
657
|
+
await this.persistPendingSettingEdits();
|
|
658
|
+
} catch (error) {
|
|
659
|
+
for (const [settingId, version] of clearedEdits) {
|
|
660
|
+
if (!this.pendingSettingEdits.has(settingId)) {
|
|
661
|
+
this.pendingSettingEdits.set(settingId, version);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
const details = error instanceof Error ? error.message : "Unknown state error";
|
|
665
|
+
this.log.warn(
|
|
666
|
+
`SAIC ${action} completed for ${vin}, but pending edits could not be persisted: ${details}`
|
|
667
|
+
);
|
|
668
|
+
}
|
|
533
669
|
this.log.info(`SAIC ${action} completed for ${vin}`);
|
|
534
670
|
} catch (error) {
|
|
535
671
|
if (!this.stopping) {
|
|
@@ -628,6 +764,10 @@ class MgSaic extends utils.Adapter {
|
|
|
628
764
|
return;
|
|
629
765
|
}
|
|
630
766
|
const statusId = `vehicles.${vin}.${channel}`;
|
|
767
|
+
for (const key of import_status_metadata.excludedVehicleStatusFields) {
|
|
768
|
+
const stateId = `${statusId}.${(0, import_status_metadata.getVehicleStatusGroup)(key)}.${key}`;
|
|
769
|
+
await this.delObjectAsync(stateId);
|
|
770
|
+
}
|
|
631
771
|
await this.setObjectNotExistsAsync(statusId, {
|
|
632
772
|
type: "channel",
|
|
633
773
|
common: { name: (0, import_runtime_translations.getRuntimeName)(channelName) },
|
|
@@ -649,8 +789,29 @@ class MgSaic extends utils.Adapter {
|
|
|
649
789
|
if (this.stopping) {
|
|
650
790
|
return;
|
|
651
791
|
}
|
|
792
|
+
if (import_status_metadata.excludedVehicleStatusFields.some((excludedKey) => excludedKey === key)) {
|
|
793
|
+
await this.delObjectAsync(`${statusId}.${(0, import_status_metadata.getVehicleStatusGroup)(key)}.${key}`);
|
|
794
|
+
continue;
|
|
795
|
+
}
|
|
652
796
|
const group = (0, import_status_metadata.getVehicleStatusGroup)(key);
|
|
653
797
|
const groupId = `${statusId}.${group}`;
|
|
798
|
+
const stateId = `${groupId}.${key}`;
|
|
799
|
+
if (value === void 0) {
|
|
800
|
+
if ((0, import_status_metadata.shouldDeleteUnavailableStatusValue)(key)) {
|
|
801
|
+
await this.delObjectAsync(stateId);
|
|
802
|
+
} else {
|
|
803
|
+
const previousState = await this.getStateAsync(stateId);
|
|
804
|
+
if ((previousState == null ? void 0 : previousState.val) === null) {
|
|
805
|
+
await this.delObjectAsync(stateId);
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
continue;
|
|
809
|
+
}
|
|
810
|
+
const normalizedValue = (0, import_status_metadata.normalizeVehicleStatusValue)(key, value);
|
|
811
|
+
if (normalizedValue === void 0) {
|
|
812
|
+
await this.delObjectAsync(stateId);
|
|
813
|
+
continue;
|
|
814
|
+
}
|
|
654
815
|
await this.setObjectNotExistsAsync(groupId, {
|
|
655
816
|
type: "channel",
|
|
656
817
|
common: { name: (0, import_runtime_translations.getRuntimeName)(group) },
|
|
@@ -661,7 +822,6 @@ class MgSaic extends utils.Adapter {
|
|
|
661
822
|
return;
|
|
662
823
|
}
|
|
663
824
|
const metadata = (0, import_status_metadata.getVehicleStatusMetadata)(key);
|
|
664
|
-
const stateId = `${groupId}.${key}`;
|
|
665
825
|
const common = {
|
|
666
826
|
name: metadata.name,
|
|
667
827
|
type: metadata.type,
|
|
@@ -686,13 +846,107 @@ class MgSaic extends utils.Adapter {
|
|
|
686
846
|
if (this.stopping) {
|
|
687
847
|
return;
|
|
688
848
|
}
|
|
689
|
-
await this.setStateAsync(stateId, { val:
|
|
849
|
+
await this.setStateAsync(stateId, { val: normalizedValue, ack: true });
|
|
690
850
|
if (this.stopping) {
|
|
691
851
|
return;
|
|
692
852
|
}
|
|
853
|
+
const seatSetting = key === "frontLeftSeatHeatLevel" ? "driverSeatHeatLevel" : key === "frontRightSeatHeatLevel" ? "passengerSeatHeatLevel" : void 0;
|
|
854
|
+
if (seatSetting && Number.isInteger(value) && value >= 0 && value <= 3) {
|
|
855
|
+
const settingId = vehicleCommandPath(vin, seatSetting);
|
|
856
|
+
const settingState = await this.getStateAsync(settingId);
|
|
857
|
+
if (!settingState || settingState.ack) {
|
|
858
|
+
await this.setStateAsync(settingId, { val: value, ack: true });
|
|
859
|
+
}
|
|
860
|
+
}
|
|
693
861
|
await this.delObjectAsync(`${statusId}.${key}`);
|
|
694
862
|
}
|
|
695
863
|
}
|
|
864
|
+
async createVehicleChargingObjects(vin, charging) {
|
|
865
|
+
await this.ensureVehicleChargingGroups(vin);
|
|
866
|
+
const rootId = `vehicles.${vin}.status.charging`;
|
|
867
|
+
for (const { source, id } of chargingGroups) {
|
|
868
|
+
const groupId = `${rootId}.${id}`;
|
|
869
|
+
const excludedFields = id === "session" ? import_status_metadata.excludedChargingSessionFields : import_status_metadata.excludedChargingStatisticsFields;
|
|
870
|
+
for (const field of excludedFields) {
|
|
871
|
+
await this.delObjectAsync(`${groupId}.${field}`);
|
|
872
|
+
}
|
|
873
|
+
for (const [field, value] of Object.entries(charging[source])) {
|
|
874
|
+
if (this.stopping) {
|
|
875
|
+
return;
|
|
876
|
+
}
|
|
877
|
+
if (excludedFields.some((excludedField) => excludedField === field)) {
|
|
878
|
+
await this.delObjectAsync(`${groupId}.${field}`);
|
|
879
|
+
continue;
|
|
880
|
+
}
|
|
881
|
+
const stateId = `${groupId}.${field}`;
|
|
882
|
+
if (value === void 0) {
|
|
883
|
+
await this.delObjectAsync(stateId);
|
|
884
|
+
continue;
|
|
885
|
+
}
|
|
886
|
+
const metadata = (0, import_status_metadata.getChargingFieldMetadata)(field);
|
|
887
|
+
const common = {
|
|
888
|
+
name: (0, import_runtime_translations.getChargingFieldName)(field),
|
|
889
|
+
type: metadata.type === "boolean" ? "boolean" : typeof value === "string" ? "string" : "number",
|
|
890
|
+
role: typeof value === "string" ? "text" : metadata.role,
|
|
891
|
+
read: true,
|
|
892
|
+
write: false
|
|
893
|
+
};
|
|
894
|
+
if (metadata.unit) {
|
|
895
|
+
common.unit = metadata.unit;
|
|
896
|
+
}
|
|
897
|
+
await this.setObjectNotExistsAsync(stateId, { type: "state", common, native: {} });
|
|
898
|
+
await this.extendObjectAsync(stateId, { common });
|
|
899
|
+
await this.setStateAsync(stateId, {
|
|
900
|
+
val: (0, import_status_metadata.normalizeChargingValue)(field, value),
|
|
901
|
+
ack: true
|
|
902
|
+
});
|
|
903
|
+
if (field === "bmsOnBdChrgTrgtSOCDspCmd" && typeof value === "number" && value >= 1 && value <= 7) {
|
|
904
|
+
const targetSocId = vehicleCommandPath(vin, "chargeTargetSoc");
|
|
905
|
+
const targetSocState = await this.getStateAsync(targetSocId);
|
|
906
|
+
if ((0, import_status_metadata.shouldApplySettingFeedback)(targetSocState, this.pendingSettingEdits.has(targetSocId))) {
|
|
907
|
+
await this.setStateAsync(targetSocId, {
|
|
908
|
+
val: (0, import_status_metadata.normalizeChargingValue)(field, value),
|
|
909
|
+
ack: true
|
|
910
|
+
});
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
for (const [setting, value] of Object.entries((0, import_status_metadata.getChargingSettingFeedback)(charging.chrgMgmtData))) {
|
|
916
|
+
const stateId = vehicleCommandPath(vin, setting);
|
|
917
|
+
const state = await this.getStateAsync(stateId);
|
|
918
|
+
if ((0, import_status_metadata.shouldApplySettingFeedback)(state, this.pendingSettingEdits.has(stateId))) {
|
|
919
|
+
await this.setStateAsync(stateId, { val: value, ack: true });
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
await this.delObjectAsync(`vehicles.${vin}.charging`, { recursive: true });
|
|
923
|
+
}
|
|
924
|
+
async ensureVehicleChargingGroups(vin) {
|
|
925
|
+
const statusId = `vehicles.${vin}.status`;
|
|
926
|
+
const rootId = `${statusId}.charging`;
|
|
927
|
+
await this.setObjectNotExistsAsync(statusId, {
|
|
928
|
+
type: "channel",
|
|
929
|
+
common: { name: (0, import_runtime_translations.getRuntimeName)("currentStatus") },
|
|
930
|
+
native: {}
|
|
931
|
+
});
|
|
932
|
+
await this.setObjectNotExistsAsync(rootId, {
|
|
933
|
+
type: "channel",
|
|
934
|
+
common: { name: (0, import_runtime_translations.getRuntimeName)("commandGroup_charging") },
|
|
935
|
+
native: {}
|
|
936
|
+
});
|
|
937
|
+
await this.extendObjectAsync(rootId, { common: { name: (0, import_runtime_translations.getRuntimeName)("commandGroup_charging") } });
|
|
938
|
+
for (const { source, id, name } of chargingGroups) {
|
|
939
|
+
const groupId = `${rootId}.${id}`;
|
|
940
|
+
const groupName = (0, import_runtime_translations.getRuntimeName)(name);
|
|
941
|
+
await this.setObjectNotExistsAsync(groupId, {
|
|
942
|
+
type: "channel",
|
|
943
|
+
common: { name: groupName },
|
|
944
|
+
native: {}
|
|
945
|
+
});
|
|
946
|
+
await this.extendObjectAsync(groupId, { common: { name: groupName } });
|
|
947
|
+
await this.delObjectAsync(`${rootId}.${source}`, { recursive: true });
|
|
948
|
+
}
|
|
949
|
+
}
|
|
696
950
|
async createCanonicalVehicleStatusObjects(vin, status, source) {
|
|
697
951
|
if (this.stopping || !(0, import_saic_client.isVehicleStatusNotOlder)(status.statusTime, this.lastCanonicalStatusTimes.get(vin))) {
|
|
698
952
|
return;
|
|
@@ -733,7 +987,7 @@ class MgSaic extends utils.Adapter {
|
|
|
733
987
|
}
|
|
734
988
|
}
|
|
735
989
|
async createVehicleObjects(vehicle) {
|
|
736
|
-
var _a;
|
|
990
|
+
var _a, _b;
|
|
737
991
|
if (this.stopping) {
|
|
738
992
|
return;
|
|
739
993
|
}
|
|
@@ -757,6 +1011,7 @@ class MgSaic extends utils.Adapter {
|
|
|
757
1011
|
if (this.stopping) {
|
|
758
1012
|
return;
|
|
759
1013
|
}
|
|
1014
|
+
await this.ensureVehicleChargingGroups(vehicle.vin);
|
|
760
1015
|
const commandsId = `${deviceId}.commands`;
|
|
761
1016
|
await this.setObjectNotExistsAsync(commandsId, {
|
|
762
1017
|
type: "channel",
|
|
@@ -843,13 +1098,13 @@ class MgSaic extends utils.Adapter {
|
|
|
843
1098
|
{
|
|
844
1099
|
id: "chargingScheduleStart",
|
|
845
1100
|
type: "string",
|
|
846
|
-
role: "
|
|
1101
|
+
role: "text",
|
|
847
1102
|
def: "00:00"
|
|
848
1103
|
},
|
|
849
1104
|
{
|
|
850
1105
|
id: "chargingScheduleEnd",
|
|
851
1106
|
type: "string",
|
|
852
|
-
role: "
|
|
1107
|
+
role: "text",
|
|
853
1108
|
def: "06:00"
|
|
854
1109
|
},
|
|
855
1110
|
{
|
|
@@ -870,7 +1125,7 @@ class MgSaic extends utils.Adapter {
|
|
|
870
1125
|
{
|
|
871
1126
|
id: "batteryHeatingScheduleStart",
|
|
872
1127
|
type: "string",
|
|
873
|
-
role: "
|
|
1128
|
+
role: "text",
|
|
874
1129
|
def: "00:00"
|
|
875
1130
|
}
|
|
876
1131
|
];
|
|
@@ -879,6 +1134,7 @@ class MgSaic extends utils.Adapter {
|
|
|
879
1134
|
const legacyStateId = `${commandsId}.${setting.id}`;
|
|
880
1135
|
const stateObject = await this.getObjectAsync(stateId);
|
|
881
1136
|
const legacyState = await this.getStateAsync(legacyStateId);
|
|
1137
|
+
const previousScheduleState = setting.id.startsWith("chargingSchedule") ? await this.getStateAsync(`${commandsId}.schedules.${setting.id}`) : null;
|
|
882
1138
|
await this.setObjectNotExistsAsync(stateId, {
|
|
883
1139
|
type: "state",
|
|
884
1140
|
common: {
|
|
@@ -898,7 +1154,10 @@ class MgSaic extends utils.Adapter {
|
|
|
898
1154
|
});
|
|
899
1155
|
const currentState = await this.getStateAsync(stateId);
|
|
900
1156
|
if (!currentState || currentState.val === null) {
|
|
901
|
-
await this.setStateAsync(stateId, {
|
|
1157
|
+
await this.setStateAsync(stateId, {
|
|
1158
|
+
val: (_b = (_a = legacyState == null ? void 0 : legacyState.val) != null ? _a : previousScheduleState == null ? void 0 : previousScheduleState.val) != null ? _b : setting.def,
|
|
1159
|
+
ack: true
|
|
1160
|
+
});
|
|
902
1161
|
} else if (typeof currentState.val === "number" && (stateObject == null ? void 0 : stateObject.common.unit) !== setting.unit) {
|
|
903
1162
|
if (setting.id === "chargeTargetSoc" && currentState.val >= 1 && currentState.val <= 7) {
|
|
904
1163
|
await this.setStateAsync(stateId, { val: (currentState.val + 3) * 10, ack: true });
|
|
@@ -934,6 +1193,14 @@ class MgSaic extends utils.Adapter {
|
|
|
934
1193
|
await this.setStateAsync(actionId, { val: false, ack: true });
|
|
935
1194
|
await this.delObjectAsync(`${commandsId}.${action.id}`);
|
|
936
1195
|
}
|
|
1196
|
+
for (const id of [
|
|
1197
|
+
"chargingScheduleStart",
|
|
1198
|
+
"chargingScheduleEnd",
|
|
1199
|
+
"chargingScheduleMode",
|
|
1200
|
+
"chargingScheduleApply"
|
|
1201
|
+
]) {
|
|
1202
|
+
await this.delObjectAsync(`${commandsId}.schedules.${id}`);
|
|
1203
|
+
}
|
|
937
1204
|
for (const command of vehicleCommands) {
|
|
938
1205
|
await this.delObjectAsync(`${commandsId}.${command.id}`);
|
|
939
1206
|
}
|