iobroker.mg-saic 0.0.4 → 0.1.1
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 +16 -7
- package/admin/jsonConfig.json +2 -1
- package/build/config.js +5 -2
- package/build/config.js.map +2 -2
- package/build/main.js +96 -24
- package/build/main.js.map +2 -2
- package/build/pending-setting-edits.js +24 -0
- package/build/pending-setting-edits.js.map +2 -2
- package/build/runtime-translations.js +777 -63
- package/build/runtime-translations.js.map +2 -2
- package/build/status-metadata.js +80 -12
- package/build/status-metadata.js.map +2 -2
- package/io-package.json +27 -1
- package/package.json +6 -6
- /package/{MG.png → admin/MG.png} +0 -0
package/README.md
CHANGED
|
@@ -3,15 +3,17 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/iobroker.mg-saic)
|
|
4
4
|
[](https://www.npmjs.com/package/iobroker.mg-saic)
|
|
5
5
|
|
|
6
|
-
<img src="MG.png" alt="MG" width="314">
|
|
6
|
+
<img src="admin/MG.png" alt="MG" width="314">
|
|
7
7
|
|
|
8
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.
|
|
9
9
|
|
|
10
|
+
For manufacturer information, see [MG Motor](https://www.mgmotor.eu/) and [SAIC Motor](https://www.saicmotor.com/).
|
|
11
|
+
|
|
10
12
|
## Setup
|
|
11
13
|
|
|
12
14
|
### Before you start
|
|
13
15
|
|
|
14
|
-
- Node.js
|
|
16
|
+
- Node.js 22.22.2 or later is required.
|
|
15
17
|
- ioBroker js-controller 7.2.2 or later is required.
|
|
16
18
|
- ioBroker Admin 8.2.0 or later is required.
|
|
17
19
|
- 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.
|
|
@@ -23,7 +25,7 @@ The adapter is intended for connecting MG and SAIC vehicles to ioBroker through
|
|
|
23
25
|
1. Install the adapter and create an instance in ioBroker Admin.
|
|
24
26
|
2. Select the account region and login type (email or phone number).
|
|
25
27
|
3. Enter the account's email address or phone number and password. For phone login, also enter the phone country calling code.
|
|
26
|
-
4. Set the status polling interval
|
|
28
|
+
4. Set the status polling interval >60 seconds. The default is 1800 seconds.
|
|
27
29
|
5. Save the settings and start the instance. Vehicles available to the account appear under `vehicles.<VIN>`.
|
|
28
30
|
|
|
29
31
|
## Features
|
|
@@ -41,6 +43,17 @@ The adapter is published on npm but has not yet been added to the ioBroker adapt
|
|
|
41
43
|
[](https://www.paypal.com/donate/?hosted_button_id=3RJQGSJNRSHUJ)
|
|
42
44
|
|
|
43
45
|
## Changelog
|
|
46
|
+
### 0.1.1 (2026-11-07)
|
|
47
|
+
|
|
48
|
+
- (Voodoo2man) Lowered the minimum Node.js requirement to 22.22.2.
|
|
49
|
+
- (Voodoo2man) Improved charging datapoint labels and units, enforced safe polling interval limits, and added manufacturer references.
|
|
50
|
+
|
|
51
|
+
### 0.1.0 (2026-11-01)
|
|
52
|
+
|
|
53
|
+
- (Voodoo2man) Reported real-time charging power in kW using the pack current and voltage.
|
|
54
|
+
- (Voodoo2man) Applied confirmed profile-specific charging conversions.
|
|
55
|
+
- (Voodoo2man) Calculated available battery energy from battery level and usable capacity.
|
|
56
|
+
|
|
44
57
|
### 0.0.4 (2026-10-24)
|
|
45
58
|
|
|
46
59
|
- (Voodoo2man) Corrected GPS coordinates.
|
|
@@ -60,10 +73,6 @@ Older changelog entries are available in [CHANGELOG_OLD.md](CHANGELOG_OLD.md).
|
|
|
60
73
|
|
|
61
74
|
- (Voodoo2man) Added regional iSmart account setup, vehicle status, and supported remote controls.
|
|
62
75
|
|
|
63
|
-
### 0.0.1 (2026-10-20)
|
|
64
|
-
|
|
65
|
-
- (Voodoo2man) Initial Version
|
|
66
|
-
|
|
67
76
|
## License
|
|
68
77
|
|
|
69
78
|
MIT License
|
package/admin/jsonConfig.json
CHANGED
package/build/config.js
CHANGED
|
@@ -19,16 +19,18 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
19
19
|
var config_exports = {};
|
|
20
20
|
__export(config_exports, {
|
|
21
21
|
DEFAULT_POLL_INTERVAL_SECONDS: () => DEFAULT_POLL_INTERVAL_SECONDS,
|
|
22
|
+
MAX_POLL_INTERVAL_SECONDS: () => MAX_POLL_INTERVAL_SECONDS,
|
|
22
23
|
validateConfig: () => validateConfig
|
|
23
24
|
});
|
|
24
25
|
module.exports = __toCommonJS(config_exports);
|
|
25
26
|
var import_regions = require("./regions");
|
|
26
27
|
const DEFAULT_POLL_INTERVAL_SECONDS = 1800;
|
|
28
|
+
const MAX_POLL_INTERVAL_SECONDS = 2147483;
|
|
27
29
|
function validateConfig(config) {
|
|
28
30
|
var _a, _b, _c;
|
|
29
31
|
const errors = [];
|
|
30
|
-
if (config.pollIntervalSeconds !== void 0 && (!Number.isInteger(config.pollIntervalSeconds) || config.pollIntervalSeconds <
|
|
31
|
-
errors.push(
|
|
32
|
+
if (config.pollIntervalSeconds !== void 0 && (!Number.isInteger(config.pollIntervalSeconds) || config.pollIntervalSeconds < 60 || config.pollIntervalSeconds > MAX_POLL_INTERVAL_SECONDS)) {
|
|
33
|
+
errors.push(`Polling interval must be an integer between 60 and ${MAX_POLL_INTERVAL_SECONDS} seconds`);
|
|
32
34
|
}
|
|
33
35
|
if (!((_a = config.region) == null ? void 0 : _a.trim())) {
|
|
34
36
|
errors.push("Region/backend code is required");
|
|
@@ -54,6 +56,7 @@ function validateConfig(config) {
|
|
|
54
56
|
// Annotate the CommonJS export names for ESM import in node:
|
|
55
57
|
0 && (module.exports = {
|
|
56
58
|
DEFAULT_POLL_INTERVAL_SECONDS,
|
|
59
|
+
MAX_POLL_INTERVAL_SECONDS,
|
|
57
60
|
validateConfig
|
|
58
61
|
});
|
|
59
62
|
//# sourceMappingURL=config.js.map
|
package/build/config.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/config.ts"],
|
|
4
|
-
"sourcesContent": ["import { isIndiaRegion, resolveSaicRegion } from './regions';\n\n/** Settings entered for one iSmart account and backend. */\nexport interface MgSaicConfig {\n\t/** Automatic status polling interval in seconds. */\n\tpollIntervalSeconds?: number;\n\t/** Backend region code. */\n\tregion?: string;\n\t/** Whether the account identifier is an email address or phone number. */\n\taccountType?: string;\n\t/** Email address or phone number used to sign in. */\n\taccountIdentifier?: string;\n\t/** International calling code when using a phone account. */\n\tphoneCountryCode?: string;\n\t/** Account password, decrypted by ioBroker for the adapter process. */\n\tpassword?: string;\n}\n\nexport const DEFAULT_POLL_INTERVAL_SECONDS = 1800;\n\n/**\n * Returns configuration problems without logging any credential values.\n *\n * @param config The instance's native configuration.\n */\nexport function validateConfig(config: MgSaicConfig): string[] {\n\tconst errors: string[] = [];\n\tif (\n\t\tconfig.pollIntervalSeconds !== undefined &&\n\t\t(!Number.isInteger(config.pollIntervalSeconds)
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qBAAiD;AAkB1C,MAAM,gCAAgC;
|
|
4
|
+
"sourcesContent": ["import { isIndiaRegion, resolveSaicRegion } from './regions';\n\n/** Settings entered for one iSmart account and backend. */\nexport interface MgSaicConfig {\n\t/** Automatic status polling interval in seconds. */\n\tpollIntervalSeconds?: number;\n\t/** Backend region code. */\n\tregion?: string;\n\t/** Whether the account identifier is an email address or phone number. */\n\taccountType?: string;\n\t/** Email address or phone number used to sign in. */\n\taccountIdentifier?: string;\n\t/** International calling code when using a phone account. */\n\tphoneCountryCode?: string;\n\t/** Account password, decrypted by ioBroker for the adapter process. */\n\tpassword?: string;\n}\n\nexport const DEFAULT_POLL_INTERVAL_SECONDS = 1800;\nexport const MAX_POLL_INTERVAL_SECONDS = 2_147_483;\n\n/**\n * Returns configuration problems without logging any credential values.\n *\n * @param config The instance's native configuration.\n */\nexport function validateConfig(config: MgSaicConfig): string[] {\n\tconst errors: string[] = [];\n\tif (\n\t\tconfig.pollIntervalSeconds !== undefined &&\n\t\t(!Number.isInteger(config.pollIntervalSeconds) ||\n\t\t\tconfig.pollIntervalSeconds < 60 ||\n\t\t\tconfig.pollIntervalSeconds > MAX_POLL_INTERVAL_SECONDS)\n\t) {\n\t\terrors.push(`Polling interval must be an integer between 60 and ${MAX_POLL_INTERVAL_SECONDS} seconds`);\n\t}\n\tif (!config.region?.trim()) {\n\t\terrors.push('Region/backend code is required');\n\t} else if (isIndiaRegion(config.region)) {\n\t\terrors.push('India TAP connectivity is not implemented yet');\n\t} else if (!resolveSaicRegion(config.region)) {\n\t\terrors.push('Unsupported SAIC region');\n\t}\n\tif (config.accountType !== 'email' && config.accountType !== 'phone') {\n\t\terrors.push('Login type must be email or phone');\n\t}\n\tif (!config.accountIdentifier?.trim()) {\n\t\terrors.push('Email address or phone number is required');\n\t}\n\tif (config.accountType === 'phone' && !config.phoneCountryCode?.trim()) {\n\t\terrors.push('Phone country calling code is required');\n\t}\n\tif (!config.password) {\n\t\terrors.push('Password is required');\n\t}\n\treturn errors;\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qBAAiD;AAkB1C,MAAM,gCAAgC;AACtC,MAAM,4BAA4B;AAOlC,SAAS,eAAe,QAAgC;AA1B/D;AA2BC,QAAM,SAAmB,CAAC;AAC1B,MACC,OAAO,wBAAwB,WAC9B,CAAC,OAAO,UAAU,OAAO,mBAAmB,KAC5C,OAAO,sBAAsB,MAC7B,OAAO,sBAAsB,4BAC7B;AACD,WAAO,KAAK,sDAAsD,yBAAyB,UAAU;AAAA,EACtG;AACA,MAAI,GAAC,YAAO,WAAP,mBAAe,SAAQ;AAC3B,WAAO,KAAK,iCAAiC;AAAA,EAC9C,eAAW,8BAAc,OAAO,MAAM,GAAG;AACxC,WAAO,KAAK,+CAA+C;AAAA,EAC5D,WAAW,KAAC,kCAAkB,OAAO,MAAM,GAAG;AAC7C,WAAO,KAAK,yBAAyB;AAAA,EACtC;AACA,MAAI,OAAO,gBAAgB,WAAW,OAAO,gBAAgB,SAAS;AACrE,WAAO,KAAK,mCAAmC;AAAA,EAChD;AACA,MAAI,GAAC,YAAO,sBAAP,mBAA0B,SAAQ;AACtC,WAAO,KAAK,2CAA2C;AAAA,EACxD;AACA,MAAI,OAAO,gBAAgB,WAAW,GAAC,YAAO,qBAAP,mBAAyB,SAAQ;AACvE,WAAO,KAAK,wCAAwC;AAAA,EACrD;AACA,MAAI,CAAC,OAAO,UAAU;AACrB,WAAO,KAAK,sBAAsB;AAAA,EACnC;AACA,SAAO;AACR;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/build/main.js
CHANGED
|
@@ -22,6 +22,8 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
22
22
|
mod
|
|
23
23
|
));
|
|
24
24
|
var utils = __toESM(require("@iobroker/adapter-core"));
|
|
25
|
+
var import_promises = require("node:fs/promises");
|
|
26
|
+
var import_node_path = require("node:path");
|
|
25
27
|
var import_config = require("./config");
|
|
26
28
|
var import_saic_client = require("./saic-client");
|
|
27
29
|
var import_regions = require("./regions");
|
|
@@ -105,6 +107,7 @@ const settingInputsByAction = {
|
|
|
105
107
|
batteryHeatingScheduleApply: ["batteryHeatingScheduleEnabled", "batteryHeatingScheduleStart"]
|
|
106
108
|
};
|
|
107
109
|
const EXPOSE_SOURCE_STATUS_DATAPOINTS = false;
|
|
110
|
+
const PENDING_SETTING_EDITS_FILE = "pending-setting-edits.json";
|
|
108
111
|
class MgSaic extends utils.Adapter {
|
|
109
112
|
constructor(options = {}) {
|
|
110
113
|
super({
|
|
@@ -142,16 +145,12 @@ class MgSaic extends utils.Adapter {
|
|
|
142
145
|
await this.extendObjectAsync("info.backend", { common: { name: (0, import_runtime_translations.getRuntimeName)("backend") } });
|
|
143
146
|
await this.extendObjectAsync("info.connection", { common: { name: (0, import_runtime_translations.getRuntimeName)("connection") } });
|
|
144
147
|
await this.extendObjectAsync("info.lastError", { common: { name: (0, import_runtime_translations.getRuntimeName)("lastError") } });
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
type: "state",
|
|
152
|
-
common: { name: "Pending setting edits", type: "string", role: "json", read: true, write: false },
|
|
153
|
-
native: {}
|
|
154
|
-
});
|
|
148
|
+
try {
|
|
149
|
+
await this.migrateLegacyPendingSettingEdits();
|
|
150
|
+
} catch (error) {
|
|
151
|
+
const details = error instanceof Error ? error.message : "Unknown storage error";
|
|
152
|
+
this.log.warn(`Could not migrate pending setting edits: ${details}`);
|
|
153
|
+
}
|
|
155
154
|
await this.setObjectNotExistsAsync("commands", {
|
|
156
155
|
type: "channel",
|
|
157
156
|
common: { name: (0, import_runtime_translations.getRuntimeName)("commands") },
|
|
@@ -250,7 +249,12 @@ class MgSaic extends utils.Adapter {
|
|
|
250
249
|
}
|
|
251
250
|
await this.delObjectAsync(`vehicles.${vehicle.vin}.canonicalStatus`, { recursive: true });
|
|
252
251
|
}
|
|
253
|
-
|
|
252
|
+
try {
|
|
253
|
+
await this.restorePendingSettingEdits();
|
|
254
|
+
} catch (error) {
|
|
255
|
+
const details = error instanceof Error ? error.message : "Unknown storage error";
|
|
256
|
+
this.log.warn(`Could not restore pending setting edits: ${details}`);
|
|
257
|
+
}
|
|
254
258
|
await this.setState("info.connection", true, true);
|
|
255
259
|
this.log.info(`SAIC account authenticated; discovered ${this.vehicles.length} vehicle(s).`);
|
|
256
260
|
if (this.vehicles.length === 0) {
|
|
@@ -329,7 +333,7 @@ class MgSaic extends utils.Adapter {
|
|
|
329
333
|
vehicle.vin,
|
|
330
334
|
this.abortController.signal
|
|
331
335
|
);
|
|
332
|
-
await this.createVehicleChargingObjects(vehicle.vin, charging);
|
|
336
|
+
await this.createVehicleChargingObjects(vehicle.vin, charging, vehicle.series);
|
|
333
337
|
} catch (error) {
|
|
334
338
|
if (this.stopping) {
|
|
335
339
|
break;
|
|
@@ -554,21 +558,62 @@ class MgSaic extends utils.Adapter {
|
|
|
554
558
|
}
|
|
555
559
|
}
|
|
556
560
|
async persistPendingSettingEdits() {
|
|
557
|
-
const serialized = (0, import_pending_setting_edits.serializePendingSettingEdits)(this.pendingSettingEdits.keys());
|
|
558
561
|
const write = this.pendingSettingEditsWrite.catch(() => void 0).then(async () => {
|
|
559
|
-
await this.
|
|
562
|
+
await this.writePendingSettingEditsFile((0, import_pending_setting_edits.serializePendingSettingEdits)(this.pendingSettingEdits.keys()));
|
|
560
563
|
});
|
|
561
564
|
this.pendingSettingEditsWrite = write;
|
|
562
565
|
await write;
|
|
563
566
|
}
|
|
567
|
+
async migrateLegacyPendingSettingEdits() {
|
|
568
|
+
const legacyObject = await this.getObjectAsync("internal.pendingSettingEdits");
|
|
569
|
+
if (legacyObject) {
|
|
570
|
+
const legacyState = await this.getStateAsync("internal.pendingSettingEdits");
|
|
571
|
+
const savedFileValue = await this.readPendingSettingEditsFile();
|
|
572
|
+
await (0, import_pending_setting_edits.migrateLegacyPendingSettingEdits)(
|
|
573
|
+
savedFileValue,
|
|
574
|
+
{ exists: true, value: legacyState == null ? void 0 : legacyState.val },
|
|
575
|
+
(serialized) => this.writePendingSettingEditsFile(serialized),
|
|
576
|
+
() => this.delObjectAsync("internal.pendingSettingEdits")
|
|
577
|
+
);
|
|
578
|
+
}
|
|
579
|
+
const internalObject = await this.getObjectAsync("internal");
|
|
580
|
+
if (!internalObject) {
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
const children = await this.getObjectListAsync({
|
|
584
|
+
startkey: `${this.namespace}.internal.`,
|
|
585
|
+
endkey: `${this.namespace}.internal.\u9999`
|
|
586
|
+
});
|
|
587
|
+
if (children.rows.length === 0) {
|
|
588
|
+
await this.delObjectAsync("internal");
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
async readPendingSettingEditsFile() {
|
|
592
|
+
try {
|
|
593
|
+
return await (0, import_promises.readFile)(this.getPendingSettingEditsFilePath(), "utf8");
|
|
594
|
+
} catch (error) {
|
|
595
|
+
if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
|
|
596
|
+
return void 0;
|
|
597
|
+
}
|
|
598
|
+
throw error;
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
async writePendingSettingEditsFile(serialized) {
|
|
602
|
+
const directory = utils.getAbsoluteInstanceDataDir(this);
|
|
603
|
+
await (0, import_promises.mkdir)(directory, { recursive: true });
|
|
604
|
+
await (0, import_promises.writeFile)(this.getPendingSettingEditsFilePath(), serialized, "utf8");
|
|
605
|
+
}
|
|
606
|
+
getPendingSettingEditsFilePath() {
|
|
607
|
+
return (0, import_node_path.join)(utils.getAbsoluteInstanceDataDir(this), PENDING_SETTING_EDITS_FILE);
|
|
608
|
+
}
|
|
564
609
|
async restorePendingSettingEdits() {
|
|
565
610
|
const validStateIds = new Set(
|
|
566
611
|
this.vehicles.flatMap(
|
|
567
612
|
({ vin }) => Object.keys(settingGroups).filter((stateId) => stateId !== "driverSeatHeatLevel" && stateId !== "passengerSeatHeatLevel").map((stateId) => vehicleCommandPath(vin, stateId))
|
|
568
613
|
)
|
|
569
614
|
);
|
|
570
|
-
const
|
|
571
|
-
for (const stateId of (0, import_pending_setting_edits.parsePendingSettingEdits)(
|
|
615
|
+
const savedFileValue = await this.readPendingSettingEditsFile();
|
|
616
|
+
for (const stateId of (0, import_pending_setting_edits.parsePendingSettingEdits)(savedFileValue, validStateIds)) {
|
|
572
617
|
this.pendingSettingEdits.set(stateId, ++this.settingEditSequence);
|
|
573
618
|
}
|
|
574
619
|
for (const stateId of validStateIds) {
|
|
@@ -861,16 +906,17 @@ class MgSaic extends utils.Adapter {
|
|
|
861
906
|
await this.delObjectAsync(`${statusId}.${key}`);
|
|
862
907
|
}
|
|
863
908
|
}
|
|
864
|
-
async createVehicleChargingObjects(vin, charging) {
|
|
909
|
+
async createVehicleChargingObjects(vin, charging, series) {
|
|
865
910
|
await this.ensureVehicleChargingGroups(vin);
|
|
866
911
|
const rootId = `vehicles.${vin}.status.charging`;
|
|
867
912
|
for (const { source, id } of chargingGroups) {
|
|
868
913
|
const groupId = `${rootId}.${id}`;
|
|
869
914
|
const excludedFields = id === "session" ? import_status_metadata.excludedChargingSessionFields : import_status_metadata.excludedChargingStatisticsFields;
|
|
915
|
+
const fields = source === "chrgMgmtData" ? (0, import_status_metadata.getChargingManagementFields)(charging.chrgMgmtData) : (0, import_status_metadata.getChargingStatisticsFields)(charging[source]);
|
|
870
916
|
for (const field of excludedFields) {
|
|
871
917
|
await this.delObjectAsync(`${groupId}.${field}`);
|
|
872
918
|
}
|
|
873
|
-
for (const [field, value] of
|
|
919
|
+
for (const [field, value] of fields) {
|
|
874
920
|
if (this.stopping) {
|
|
875
921
|
return;
|
|
876
922
|
}
|
|
@@ -879,15 +925,20 @@ class MgSaic extends utils.Adapter {
|
|
|
879
925
|
continue;
|
|
880
926
|
}
|
|
881
927
|
const stateId = `${groupId}.${field}`;
|
|
882
|
-
if (value === void 0) {
|
|
928
|
+
if (value === void 0 && field !== "realtimePower" && field !== "batteryAvailableEnergy") {
|
|
883
929
|
await this.delObjectAsync(stateId);
|
|
884
930
|
continue;
|
|
885
931
|
}
|
|
886
|
-
const
|
|
932
|
+
const normalizedValue = field === "realtimePower" ? (0, import_status_metadata.calculateRealtimePower)(charging.chrgMgmtData.bmsPackCrnt, charging.chrgMgmtData.bmsPackVol) : value === void 0 ? void 0 : (0, import_status_metadata.normalizeChargingValue)(field, value, series);
|
|
933
|
+
if (normalizedValue === void 0 && field !== "batteryAvailableEnergy") {
|
|
934
|
+
await this.delObjectAsync(stateId);
|
|
935
|
+
continue;
|
|
936
|
+
}
|
|
937
|
+
const metadata = (0, import_status_metadata.getChargingFieldMetadata)(field, series);
|
|
887
938
|
const common = {
|
|
888
939
|
name: (0, import_runtime_translations.getChargingFieldName)(field),
|
|
889
|
-
type: metadata.type === "boolean" ? "boolean" : typeof
|
|
890
|
-
role: typeof
|
|
940
|
+
type: metadata.type === "boolean" ? "boolean" : typeof normalizedValue === "string" ? "string" : "number",
|
|
941
|
+
role: typeof normalizedValue === "string" ? "text" : metadata.role,
|
|
891
942
|
read: true,
|
|
892
943
|
write: false
|
|
893
944
|
};
|
|
@@ -896,8 +947,11 @@ class MgSaic extends utils.Adapter {
|
|
|
896
947
|
}
|
|
897
948
|
await this.setObjectNotExistsAsync(stateId, { type: "state", common, native: {} });
|
|
898
949
|
await this.extendObjectAsync(stateId, { common });
|
|
950
|
+
if (normalizedValue === void 0) {
|
|
951
|
+
continue;
|
|
952
|
+
}
|
|
899
953
|
await this.setStateAsync(stateId, {
|
|
900
|
-
val:
|
|
954
|
+
val: normalizedValue,
|
|
901
955
|
ack: true
|
|
902
956
|
});
|
|
903
957
|
if (field === "bmsOnBdChrgTrgtSOCDspCmd" && typeof value === "number" && value >= 1 && value <= 7) {
|
|
@@ -905,13 +959,31 @@ class MgSaic extends utils.Adapter {
|
|
|
905
959
|
const targetSocState = await this.getStateAsync(targetSocId);
|
|
906
960
|
if ((0, import_status_metadata.shouldApplySettingFeedback)(targetSocState, this.pendingSettingEdits.has(targetSocId))) {
|
|
907
961
|
await this.setStateAsync(targetSocId, {
|
|
908
|
-
val: (0, import_status_metadata.normalizeChargingValue)(field, value),
|
|
962
|
+
val: (0, import_status_metadata.normalizeChargingValue)(field, value, series),
|
|
909
963
|
ack: true
|
|
910
964
|
});
|
|
911
965
|
}
|
|
912
966
|
}
|
|
913
967
|
}
|
|
914
968
|
}
|
|
969
|
+
const capacityState = await this.getStateAsync(`${rootId}.statistics.totalBatteryCapacity`);
|
|
970
|
+
const socState = await this.getStateAsync(`${rootId}.session.bmsPackSOCDsp`);
|
|
971
|
+
const availableEnergy = (0, import_status_metadata.calculateAvailableBatteryEnergy)(
|
|
972
|
+
capacityState == null ? void 0 : capacityState.val,
|
|
973
|
+
socState == null ? void 0 : socState.val,
|
|
974
|
+
charging.rvsChargeStatus.packEnergyKwh
|
|
975
|
+
);
|
|
976
|
+
if (availableEnergy !== void 0) {
|
|
977
|
+
await this.setStateAsync(`${rootId}.statistics.batteryAvailableEnergy`, {
|
|
978
|
+
val: availableEnergy,
|
|
979
|
+
ack: true
|
|
980
|
+
});
|
|
981
|
+
} else {
|
|
982
|
+
await this.setStateAsync(`${rootId}.statistics.batteryAvailableEnergy`, {
|
|
983
|
+
val: null,
|
|
984
|
+
ack: true
|
|
985
|
+
});
|
|
986
|
+
}
|
|
915
987
|
for (const [setting, value] of Object.entries((0, import_status_metadata.getChargingSettingFeedback)(charging.chrgMgmtData))) {
|
|
916
988
|
const stateId = vehicleCommandPath(vin, setting);
|
|
917
989
|
const state = await this.getStateAsync(stateId);
|