… oder zum Importieren in NodeRed (sorry, GitHub ist Neuland für mich):
[
{
"id": "1c5b57320a0e5fd6",
"type": "tab",
"label": "Polestar SoC from FMC003",
"disabled": false,
"info": "",
"env": []
},
{
"id": "f6f7ac8c3e9cc4b1",
"type": "debug",
"z": "1c5b57320a0e5fd6",
"name": "RAW data",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 1000,
"y": 260,
"wires": []
},
{
"id": "fa702d0bc584f592",
"type": "function",
"z": "1c5b57320a0e5fd6",
"name": "Filter PING packet",
"func": "if (msg.payload.length == 1) {\n msg.payload = \"PING. \" + msg.ip + \":\" + msg.port;\n return [null, msg];\n}\n\nreturn [msg, null];",
"outputs": 2,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 850,
"y": 320,
"wires": [
[
"f6f7ac8c3e9cc4b1",
"9d0b1c897bac8bf3"
],
[
"ad5d5bd57d71a025"
]
]
},
{
"id": "ad5d5bd57d71a025",
"type": "debug",
"z": "1c5b57320a0e5fd6",
"name": "PING packets",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "payload",
"targetType": "msg",
"statusVal": "",
"statusType": "auto",
"x": 1020,
"y": 380,
"wires": []
},
{
"id": "9d0b1c897bac8bf3",
"type": "function",
"z": "1c5b57320a0e5fd6",
"name": "PARSE codec8_ext TCP",
"func": "var inputMsg = msg.payload;\n\nvar inputDataPtr = 0;\nvar fullPacketLength = 0;\n\nvar parsedMsg = {};\nparsedMsg.avl = [];\nvar avl = {};\n\nparseAVLfull();\n\n// create TCP response\nvar tcpResponse = createTcpResponse(parsedMsg.numberOfData, msg._session);\n\nmsg.payload = parsedMsg;\n\nreturn [msg, tcpResponse]; // response\n\n//**********************************************************************************************\n// createTcpResponse\n//**********************************************************************************************\nfunction createTcpResponse(receivedDataCount, session) {\n var countHex = dec2Hex32(receivedDataCount);\n var tcpResponse = {};\n tcpResponse._session = session;\n tcpResponse.payload = new Buffer([Number(countHex.substring(6, 8)), Number(countHex.substring(4, 6)),\n Number(countHex.substring(2, 4)), Number(countHex.substring(0, 2))]);\n\n return tcpResponse;\n}\n\n//**********************************************************************************************\n// dec2Hex32\n//**********************************************************************************************\nfunction dec2Hex32(dec) {\n return Math.abs(dec).toString(32);\n}\n\n//**********************************************************************************************\n// parseAVLfull\n//**********************************************************************************************\nfunction parseAVLfull() {\n //preamble\n var preamble = getNextInt32();\n if (preamble != 0x00000000) {\n node.warn(\"Wrong preamble\");\n return [null, null];\n }\n\n // get packet length\n parsedMsg.fullPacketLength = getNextInt32();\n\n var calculatedCrc16 = crc16(inputMsg, inputDataPtr, (inputMsg.length - inputDataPtr - 4));\n\n // AVL codec ID\n parsedMsg.codecId = getNextInt8();\n if (parsedMsg.codecId != 0x8E) {\n node.warn(\"Wrong codecId: \" + parsedMsg.codecId);\n return [null, null];\n }\n\n // AVL number of data\n parsedMsg.numberOfData = getNextInt8();\n\n // AVL Data array \n\n for (var i = 0; i < parsedMsg.numberOfData; i++) {\n parseAVLpacketDataArrayElement();\n }\n\n parsedMsg.numberOfRecords = getNextInt8();\n\n if (parsedMsg.numberOfData != parsedMsg.numberOfRecords) {\n node.warn(\"numberOfData1 not equals numberOfData2\");\n return [null, null];\n }\n\n parsedMsg.crc16 = getNextInt32();\n if (calculatedCrc16 != parsedMsg.crc16) {\n node.warn(\"Wrong crc16.\");\n return [null, null];\n }\n}\n\n//**********************************************************************************************\n// parseAVLpacketDataArrayElement\n//**********************************************************************************************\nfunction parseAVLpacketDataArrayElement() {\n avl = {};\n avl.timestamp = getTimestamp();\n avl.priority = getNextInt8();\n avl.GPS = parseGPSelement();\n parseIOelement();\n parsedMsg.avl.push(avl);\n}\n\n//**********************************************************************************************\n// parseAVLpacket\n//**********************************************************************************************\nfunction getTimestamp() {\n var timestampArray = getNextSubArray(8);\n var value = 0;\n for (var i = 0; i < timestampArray.length; i++) {\n value = (value * 256) + timestampArray[i];\n }\n return value;\n}\n\n//**********************************************************************************************\n// parseGPSelement\n//**********************************************************************************************\nfunction parseGPSelement() {\n var GPS = {};\n GPS.longitude = getCoordinate(getNextSubArray(4));\n GPS.latitude = getCoordinate(getNextSubArray(4));\n GPS.altitude = getNextInt16();\n GPS.angle = getNextInt16();\n GPS.satellites = getNextInt8();\n GPS.speed = getNextInt16();\n\n return GPS;\n}\n\n//**********************************************************************************************\n// getCoordinate\n//**********************************************************************************************\nfunction getCoordinate(array) {\n var value = 0;\n if (array[0] > 127) { //negative\n value = (array[0] << 24) + (array[1] << 16) + (array[2] << 8) + array[3];\n value -= parseInt(\"ffffffff\", 16);\n } else { //positive\n value = (array[0] << 24) + (array[1] << 16) + (array[2] << 8) + array[3];\n }\n return value / 10000000;\n}\n\n//**********************************************************************************************\n// parseIOelement\n//**********************************************************************************************\nfunction parseIOelement() {\n avl.eventIoID = getNextInt16();\n\n //if (avl.eventIoID == 385) {\n // parseBeacon();\n // getNextInt8();\n //} else {\n parseUsualIOelements();\n decodeUsualIOelements();\n //}\n}\n\n//**********************************************************************************************\n// parseBeacon\n//**********************************************************************************************\nfunction parseBeacon(array) {\n const BEACON_LENGTH = 20;\n const EDDY_LENGTH = 16;\n\n var avl = {};\n\n avl.beaconLength = array.length;\n\n var ptr = 0;\n\n // dataPart\n avl.dataPart = array[ptr++];\n avl.beaconRecordsCount = avl.dataPart & 0x0F;\n avl.recordNumber = (avl.dataPart & 0xF0) >> 4;\n\n if (avl.beaconLength < EDDY_LENGTH) {\n return avl;\n }\n\n // flags\n avl.flag = array[ptr++];\n avl.sygnalStrenghAvailable = ((avl.flag & 0x01) > 0) ? 1 : 0;\n avl.beaconDataSent = ((avl.flag & 0x20) > 0) ? 1 : 0;\n\n // beacons\n if (avl.beaconDataSent != 0) {\n // beacon data\n avl.beaconId = array.slice(ptr, ptr + BEACON_LENGTH);\n ptr += BEACON_LENGTH;\n if (avl.sygnalStrenghAvailable != 0) {\n avl.BeaconRssi = array[ptr++];\n }\n }\n\n return avl;\n}\n\n//**********************************************************************************************\n// parseUsualIOelements\n//**********************************************************************************************\nfunction parseUsualIOelements() {\n avl.nTotalIo = getNextInt16();\n avl.n1Io = getNextInt16();\n avl.ioData = new Map();\n var i = 0;\n for (i = 0; i < avl.n1Io; i++) {\n var n1IoId = getNextInt16();\n var n1IoValue = getNextInt8();\n if (n1IoValue >= 128) {\n n1IoValue = n1IoValue - 256;\n }\n avl.ioData.set(n1IoId, n1IoValue);\n }\n\n if (inputDataPtr >= parsedMsg.fullPacketLength) { return [null, null]; }\n avl.n2Io = getNextInt16();\n for (i = 0; i < avl.n2Io; i++) {\n var n2IoId = getNextInt16();\n var n2IoValue = getNextInt16();\n if (n2IoValue >= 0x8000) {\n n2IoValue = n2IoValue - 0x8000;\n }\n avl.ioData.set(n2IoId, n2IoValue);\n }\n\n if (inputDataPtr >= parsedMsg.fullPacketLength) { return [null, null]; }\n avl.n4Io = getNextInt16();\n for (i = 0; i < avl.n4Io; i++) {\n var n4IoId = getNextInt16();\n var n4IoValue = getNextInt32();\n if (n4IoValue >= 0x80000000) {\n n4IoValue = n4IoValue - 0x80000000;\n }\n avl.ioData.set(n4IoId, n4IoValue);\n }\n\n if (inputDataPtr >= parsedMsg.fullPacketLength) { return [null, null]; }\n avl.n8Io = getNextInt16();\n for (i = 0; i < avl.n8Io; i++) {\n var n8IoId = getNextInt16();\n //var n8IoSubArray = getNextSubArray(8);\n var n8IoValue = getNextInt64();\n if (n8IoValue >= 0x8000000000000000) {\n n8IoValue = n8IoValue - 0x8000000000000000;\n }\n //node.warn(n8IoValue);\n avl.ioData.set(n8IoId, n8IoValue);\n }\n avl.nxIo = getNextInt16();\n for (i = 0; i < avl.nxIo; i++) {\n var nxIoId = getNextInt16();\n var nxIoLength = getNextInt16();\n if (nxIoId == 385) {\n avl.ioData.set(nxIoId, parseBeacon(getNextSubArray(nxIoLength)));\n } else {\n avl.ioData.set(nxIoId, getNextSubArray(nxIoLength));\n }\n }\n}\n\n//**********************************************************************************************\n// decodeUsualIOelements\n//**********************************************************************************************\nfunction decodeUsualIOelements() {\n avl.axis = {};\n avl.axis.x = avl.ioData.get(17);\n avl.axis.y = avl.ioData.get(18);\n avl.axis.z = avl.ioData.get(19);\n}\n\n//**********************************************************************************************\n// getCharArray\n//**********************************************************************************************\nfunction getCharArray(array) {\n const result = [];\n for (var i = 0; i < array.length; i++) {\n result.push(String.fromCharCode(array[i]));\n }\n return result;\n}\n\n//**********************************************************************************************\n// getNextSubArray\n//**********************************************************************************************\nfunction getNextSubArray(length) {\n var subarray = inputMsg.slice(inputDataPtr, inputDataPtr + length);\n inputDataPtr += length;\n return subarray;\n}\n\n//**********************************************************************************************\n// getNextInt8\n//**********************************************************************************************\nfunction getNextInt8() {\n return inputMsg[inputDataPtr++];\n}\n\n//**********************************************************************************************\n// getNextInt16\n//**********************************************************************************************\nfunction getNextInt16() {\n var value = inputMsg[inputDataPtr++];\n value = (value << 8) + inputMsg[inputDataPtr++];\n return value;\n}\n\n//**********************************************************************************************\n// getNextInt32\n//**********************************************************************************************\nfunction getNextInt32() {\n var value = inputMsg[inputDataPtr++];\n value = (value << 8) + inputMsg[inputDataPtr++];\n value = (value << 8) + inputMsg[inputDataPtr++];\n value = (value << 8) + inputMsg[inputDataPtr++];\n return value;\n}\n\n//**********************************************************************************************\n// getNextInt64\n//**********************************************************************************************\nfunction getNextInt64() {\n var value = inputMsg[inputDataPtr++];\n value = (value << 8) + inputMsg[inputDataPtr++];\n value = (value << 8) + inputMsg[inputDataPtr++];\n value = (value << 8) + inputMsg[inputDataPtr++];\n value = (value << 8) + inputMsg[inputDataPtr++];\n value = (value << 8) + inputMsg[inputDataPtr++];\n value = (value << 8) + inputMsg[inputDataPtr++];\n value = (value << 8) + inputMsg[inputDataPtr++];\n return value;\n}\n\n//**********************************************************************************************\n// Calculates the buffers CRC-16/IBM.\n//**********************************************************************************************\nfunction crc16(buffer, startPtr, length) {\n var crc = 0;\n var odd;\n\n for (var i = 0; i < length; i++) {\n crc = crc ^ buffer[i + startPtr];\n\n var numBit = 0;\n do {\n odd = crc & 0x0001;\n crc = crc >> 1;\n if (odd == 1) {\n crc = crc ^ 0xA001;\n }\n numBit++;\n } while (numBit < 8);\n }\n\n return crc;\n};",
"outputs": 2,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1090,
"y": 320,
"wires": [
[
"f23873949525accd",
"70adddf1bbb56e80"
],
[
"5c4b988d5d63501a"
]
]
},
{
"id": "f23873949525accd",
"type": "debug",
"z": "1c5b57320a0e5fd6",
"name": "PARSED data",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 1180,
"y": 200,
"wires": []
},
{
"id": "477efac798c8c72c",
"type": "inject",
"z": "1c5b57320a0e5fd6",
"name": "Trigger",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": true,
"onceDelay": "",
"topic": "",
"payload": "",
"payloadType": "date",
"x": 120,
"y": 80,
"wires": [
[
"f4c9fdc8f4bd3ef0"
]
]
},
{
"id": "f4c9fdc8f4bd3ef0",
"type": "function",
"z": "1c5b57320a0e5fd6",
"name": "INIT flow context",
"func": "const tcpSessions = new Map();\nflow.set(\"TCP_SESSIONS\", tcpSessions);\nreturn msg;",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 320,
"y": 80,
"wires": [
[]
]
},
{
"id": "edabd16cc9f5515a",
"type": "function",
"z": "1c5b57320a0e5fd6",
"name": "SAVE IMEI and send TCP reply",
"func": "var inputDataPtr = 0;\n\nvar inputMsg = msg.payload;\n\n// IMEI length & IMEI\nvar imeiLength = getNextInt16();\nif (imeiLength != 15) {\n node.error(\"Wrong imei length: \" + imeiLength);\n return null;\n}\nvar moduleIMEI = toImeiString(getNextSubArray(imeiLength));\n\nif (msg._session.type !== \"tcp\") {\n node.warn(\"Wrong session type: \" + msg.session.type);\n return;\n}\n\nconst TCP_SESSIONS = flow.get(\"TCP_SESSIONS\");\nif (TCP_SESSIONS.get(moduleIMEI)) {\n node.debug(\"Update session for the IMEI: \" + moduleIMEI);\n TCP_SESSIONS.set(moduleIMEI, msg._session);\n} else {\n node.debug(\"Create session for the IMEI: \" + moduleIMEI);\n TCP_SESSIONS.set(moduleIMEI, msg._session);\n}\nflow.set(\"TCP_SESSIONS\", TCP_SESSIONS);\n\nmsg.payload = Buffer.from(\"\\x01\");\n\nreturn msg;\n\n\n//**********************************************************************************************\n// toImeiString\n//**********************************************************************************************\nfunction toImeiString(IMEIarray) {\n var result = \"\";\n for (var i = 0; i < IMEIarray.length; i++) {\n result += String.fromCharCode(IMEIarray[i]);\n }\n return result;\n}\n\n//**********************************************************************************************\n// getNextSubArray\n//**********************************************************************************************\nfunction getNextSubArray(length) {\n var subarray = inputMsg.slice(inputDataPtr, inputDataPtr + length);\n inputDataPtr += length;\n return subarray;\n}\n\n//**********************************************************************************************\n// getNextInt16\n//**********************************************************************************************\nfunction getNextInt16() {\n var value = inputMsg[inputDataPtr++];\n value = value * 256 + inputMsg[inputDataPtr++];\n return value;\n}",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 630,
"y": 360,
"wires": [
[
"5c4b988d5d63501a"
]
]
},
{
"id": "5c4b988d5d63501a",
"type": "tcp out",
"z": "1c5b57320a0e5fd6",
"name": "TCP reply",
"host": "",
"port": "",
"beserver": "reply",
"base64": false,
"end": false,
"tls": "",
"x": 1140,
"y": 480,
"wires": []
},
{
"id": "8f8f4e1925c9acfb",
"type": "function",
"z": "1c5b57320a0e5fd6",
"name": "Filter CONNECT packet",
"func": "if (msg.payload.length == 17) {\n return [null, msg];\n}\n\nreturn [msg, null];",
"outputs": 2,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 350,
"y": 340,
"wires": [
[
"b60f9a85f3b0b907",
"d1fa3d4f36d49b7e"
],
[
"708e6312cced8abc",
"edabd16cc9f5515a"
]
]
},
{
"id": "708e6312cced8abc",
"type": "debug",
"z": "1c5b57320a0e5fd6",
"name": "CONNECT packet",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "payload",
"targetType": "msg",
"statusVal": "",
"statusType": "auto",
"x": 550,
"y": 420,
"wires": []
},
{
"id": "b60f9a85f3b0b907",
"type": "function",
"z": "1c5b57320a0e5fd6",
"name": "Filter existing sessionID",
"func": "var IMEI = lookForImeiBySession(msg._session);\n\nif (IMEI != null) {\n return [msg, null];\n}\n\nnode.warn(\"Session with ID: \" + msg._session.id + \" not found\");\nreturn [null, msg];\n\nfunction lookForImeiBySession(session) {\n var tempIMEI = null;\n const TCP_SESSIONS = flow.get(\"TCP_SESSIONS\");\n TCP_SESSIONS.forEach(function (value, key, map) {\n if(value.id === session.id) {\n tempIMEI = key;\n }\n });\n return tempIMEI;\n}",
"outputs": 2,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 610,
"y": 320,
"wires": [
[
"fa702d0bc584f592"
],
[
"5799fdc1c5dfbc8e"
]
]
},
{
"id": "5799fdc1c5dfbc8e",
"type": "debug",
"z": "1c5b57320a0e5fd6",
"name": "UNKNOWN session packets",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "payload",
"targetType": "msg",
"statusVal": "",
"statusType": "auto",
"x": 820,
"y": 220,
"wires": []
},
{
"id": "a8fe1dbcf21ace0d",
"type": "debug",
"z": "1c5b57320a0e5fd6",
"name": "TCP input",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "payload",
"targetType": "msg",
"statusVal": "",
"statusType": "auto",
"x": 300,
"y": 260,
"wires": []
},
{
"id": "d36be9f4053834d0",
"type": "tcp in",
"z": "1c5b57320a0e5fd6",
"name": "FMC-Input TCP",
"server": "server",
"host": "",
"port": "55555",
"datamode": "stream",
"datatype": "buffer",
"newline": "",
"topic": "",
"trim": false,
"base64": false,
"tls": "",
"x": 120,
"y": 340,
"wires": [
[
"a8fe1dbcf21ace0d",
"8f8f4e1925c9acfb"
]
]
},
{
"id": "7c776e6973dba7ea",
"type": "mqtt out",
"z": "1c5b57320a0e5fd6",
"name": "LP2-SoC",
"topic": "openWB/set/lp/2/%Soc",
"qos": "",
"retain": "",
"respTopic": "",
"contentType": "",
"userProps": "",
"correl": "",
"expiry": "",
"broker": "707cfaa6c63d77f1",
"x": 1720,
"y": 320,
"wires": []
},
{
"id": "d1fa3d4f36d49b7e",
"type": "debug",
"z": "1c5b57320a0e5fd6",
"name": "DATA packet",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "payload",
"targetType": "msg",
"statusVal": "",
"statusType": "auto",
"x": 530,
"y": 260,
"wires": []
},
{
"id": "f0a4c2034771fcc4",
"type": "comment",
"z": "1c5b57320a0e5fd6",
"name": "Src: https://github.com/kaaproject/kaa/blob/master/doc/Tutorials/device-integration/hardware-guides/connect-teltonika-to-kaa-platform/attach/code/node-red-teltonika-tcp-flow.json",
"info": "",
"x": 650,
"y": 580,
"wires": []
},
{
"id": "70adddf1bbb56e80",
"type": "function",
"z": "1c5b57320a0e5fd6",
"name": "Extract SoC for Polestar 2",
"func": "if (msg.payload.avl[0].ioData.get(256) != undefined) { // VIN enthalten\n \n if (msg.payload.avl[0].ioData.get(256).toString() == \"Yxxxxxxxxxxxxxxxx\") { // VIN des eigenen Fahrzeugs\n \n if (msg.payload.avl[0].ioData.get(57) != undefined) { // SoC enthalten\n\n var outMsg = { payload: msg.payload.avl[0].ioData.get(57) + 1 }; // Hybrid battery pack life (= SoC)\n return outMsg;\n }\n }\n}",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1450,
"y": 320,
"wires": [
[
"9ee2e18f8e060ec7",
"7c776e6973dba7ea"
]
]
},
{
"id": "9ee2e18f8e060ec7",
"type": "debug",
"z": "1c5b57320a0e5fd6",
"name": "SoC2",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "payload",
"targetType": "msg",
"statusVal": "",
"statusType": "auto",
"x": 1710,
"y": 380,
"wires": []
},
{
"id": "707cfaa6c63d77f1",
"type": "mqtt-broker",
"name": "openWB",
"broker": "192.168.0.41",
"port": "1883",
"clientid": "",
"autoConnect": true,
"usetls": false,
"protocolVersion": "4",
"keepalive": "60",
"cleansession": true,
"birthTopic": "",
"birthQos": "0",
"birthPayload": "",
"birthMsg": {},
"closeTopic": "",
"closeQos": "0",
"closePayload": "",
"closeMsg": {},
"willTopic": "",
"willQos": "0",
"willPayload": "",
"willMsg": {},
"userProps": "",
"sessionExpiry": ""
}
]
Nur der banale rechte Teil ab „Extract SoC …“ ist wirklich von mir. Dabei war lediglich zu beachten, dass beim PS der SoC im Feld „Hybrid battery pack life“ steckt, was eigentlich irreführend ist. Bitte die eigene VIN im Quelltext noch eintragen, sonst kommt nie etwas an.
Vom Rest des Flows ist die Quelle bereits oben genannt und auch im Export enthalten.
Damit etwas empfangen wird, müssen folgende Voraussetzungen erfüllt sein:
-
SIM-Karte mit etwas Datentraffic muss im FMC stecken (ich habe eine Netzclub-Karte mit monatlich kostenlosen 200MB Traffic, gibt es leider seit heute nicht mehr neu. Mal schauen, wann ich mich nach einer anderen Lösung umschauen muss. Sämtlich IoT-SIMs, die ich gefunden habe, sind signifikant teurer als die billigsten Varianten der diversen Discounter).
-
SIM-Karte muss PIN-befreit sein (Lt. FMC-Anleitung kann man die PIN auch im Gerät hinterlegen, hat bei mir aber nicht zuverlässig funktioniert)
-
Irgendein Server muss im Netz erreichbar sein und den Traffic an den Input-Port des Flows weiterreichen. Dessen Adresse und Port muss via Teltonika Configurator und USB bzw. Bluetooth in den FMC Settings unter GPRS / Server Settings (sowie Protocol „TCP“) eingetragen werden. Eine TLS-verschlüsselte Verbindung habe ich noch nicht hinbekommen, würde auch hier konfiguriert
-
Unter OBDII sollten „OBD (Auto)“, VIN Source „Manual“ und die VIN selbst eingetragen werden. Weiter unten wird eingestellt, welche Daten im Paket geliefert werden sollen. Hier ist mindestens „Hybrid Battery Pack Remaining Life“ auf Low zu setzen.
-
FMC muss im Fahrzeug am OBD-Stecker angesteckt sein
Zum Schluss muss ich mir noch selbst widersprechen und zurückrudern. Der SoH ist beim Polestar tatsächlich nicht verfügbar. Den habe ich nur bei meinem Peugeot e208 gesehen (98%). Peugeot verwendet aber auch die m.E. richtigen Felder, nämlich „OEM Battery Charge Level“ und „OEM Battery State Of Health“. Bei dem seltsamen Datenfeld „Hybrid …“ gibt es nichts in der Nähe, was nach SoH aussieht. Also sorry für die unnötige Vorfreude, aber vielleicht ist das ganze auch so hilfreich.
Gruß
Ralph