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 CHANGED
@@ -1,5 +1,8 @@
1
1
  # ioBroker MG/SAIC
2
2
 
3
+ [![NPM version](https://img.shields.io/npm/v/iobroker.mg-saic.svg)](https://www.npmjs.com/package/iobroker.mg-saic)
4
+ [![Downloads](https://img.shields.io/npm/dm/iobroker.mg-saic.svg)](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 22.19.0 or later is required.
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 not yet available in the ioBroker adapter catalog. It can be installed through the catalog after publication.
37
+ The adapter is published on npm but has not yet been added to the ioBroker adapter catalog.
38
+
39
+ ## Support
40
+
41
+ [![Donate](https://camo.githubusercontent.com/ef8c60305542390406855547e32850909812a73c4abada236de22a6dc88b313f/68747470733a2f2f7777772e70617970616c6f626a656374732e636f6d2f64655f44452f44452f692f62746e2f62746e5f646f6e61746543435f4c472e676966)](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: "schedules",
72
- chargingScheduleEnd: "schedules",
73
- chargingScheduleMode: "schedules",
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: "schedules",
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 }) => id === `${this.namespace}.${vehicleCommandPath(vin, actionId)}`)
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 && id === `${this.namespace}.${vehicleCommandPath(vin, settingAction.id)}`
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 }) => id === `${this.namespace}.${vehicleCommandPath(vin, stateId)}`);
518
+ ).find(({ vin, stateId }) => eventId === `${this.namespace}.${vehicleCommandPath(vin, stateId)}`);
440
519
  if (settingInput) {
441
- void this.setStateAsync(vehicleCommandPath(settingInput.vin, settingInput.stateId), {
442
- val: state.val,
443
- ack: true
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 }) => id === `${this.namespace}.${vehicleCommandPath(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 }) => id === `${this.namespace}.${vehicleCommandPath(vin, commandId)}`)
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 && id === `${this.namespace}.${vehicleCommandPath(vin, commandEntry.id)}`
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: (0, import_status_metadata.normalizeVehicleStatusValue)(key, value), ack: true });
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: "value.time",
1101
+ role: "text",
847
1102
  def: "00:00"
848
1103
  },
849
1104
  {
850
1105
  id: "chargingScheduleEnd",
851
1106
  type: "string",
852
- role: "value.time",
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: "value.time",
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, { val: (_a = legacyState == null ? void 0 : legacyState.val) != null ? _a : setting.def, ack: true });
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
  }