[ { "id": "c1a7692d593a9ed7", "type": "tab", "label": "CMS CAN Decode", "disabled": false, "info": "", "env": [] }, { "id": "e97d608a8188b810", "type": "serial in", "z": "c1a7692d593a9ed7", "name": "CAN Serial In", "serial": "9eb956bfdc706f3d", "x": 130, "y": 200, "wires": [ [ "17a95ed467aa2234" ] ] }, { "id": "302181b0f4d07cd1", "type": "serial out", "z": "c1a7692d593a9ed7", "name": "CAN Serial Out", "serial": "9eb956bfdc706f3d", "x": 560, "y": 280, "wires": [] }, { "id": "2a0a27407c87d683", "type": "inject", "z": "c1a7692d593a9ed7", "name": "", "props": [ { "p": "payload" } ], "repeat": "", "crontab": "", "once": false, "onceDelay": "1", "topic": "", "payload": "V", "payloadType": "str", "x": 130, "y": 280, "wires": [ [ "302181b0f4d07cd1", "6a673f504c669773" ] ] }, { "id": "6a673f504c669773", "type": "debug", "z": "c1a7692d593a9ed7", "name": "CAN Message Out", "active": true, "tosidebar": true, "console": false, "tostatus": false, "complete": "true", "targetType": "full", "statusVal": "", "statusType": "auto", "x": 470, "y": 420, "wires": [] }, { "id": "4f1b73766f0d2d57", "type": "inject", "z": "c1a7692d593a9ed7", "name": "", "props": [ { "p": "payload" } ], "repeat": "", "crontab": "", "once": false, "onceDelay": "3", "topic": "", "payload": "S5", "payloadType": "str", "x": 130, "y": 340, "wires": [ [ "6a673f504c669773", "302181b0f4d07cd1" ] ] }, { "id": "ab52b17ac67b47cf", "type": "inject", "z": "c1a7692d593a9ed7", "name": "", "props": [ { "p": "payload" } ], "repeat": "", "crontab": "", "once": false, "onceDelay": "5", "topic": "", "payload": "O", "payloadType": "str", "x": 130, "y": 400, "wires": [ [ "6a673f504c669773", "302181b0f4d07cd1" ] ] }, { "id": "d4dae6b43dcaa326", "type": "inject", "z": "c1a7692d593a9ed7", "name": "", "props": [ { "p": "payload" } ], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "payload": "C", "payloadType": "str", "x": 130, "y": 460, "wires": [ [ "6a673f504c669773", "302181b0f4d07cd1" ] ] }, { "id": "41d39fb5fbd277b5", "type": "inject", "z": "c1a7692d593a9ed7", "name": "", "props": [ { "p": "payload" } ], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "payload": "A0", "payloadType": "str", "x": 130, "y": 520, "wires": [ [ "6a673f504c669773", "302181b0f4d07cd1" ] ] }, { "id": "0642392e0418ea54", "type": "inject", "z": "c1a7692d593a9ed7", "name": "", "props": [ { "p": "payload" } ], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "payload": "A1", "payloadType": "str", "x": 130, "y": 580, "wires": [ [ "6a673f504c669773", "302181b0f4d07cd1" ] ] }, { "id": "17a95ed467aa2234", "type": "function", "z": "c1a7692d593a9ed7", "name": "Decode CAN Messages", "func": "// Decode non-extended ID messages\n\nif(flow.get(\"init\")){\n // Create decode object\n msg.decode = {};\n // Parse message id\n msg.decode.id = msg.payload.slice(1, 4);\n // Parse message length\n msg.decode.length = msg.payload[4];\n // Remove parsed data\n msg.payload = msg.payload.slice(5, msg.payload.length);\n // At this point, the payload consists of message data of 2 hex characters per byte\n let byte = 0;\n for (let i = 0; i < msg.decode.length * 2 - 1; i += 2) {\n // Decode must be done by message ID from here on out\n let paramHex = msg.payload.slice(i, i + 2);\n let paramDec = parseInt(paramHex, 16);\n let paramBin = hex2bin(paramHex);\n switch (msg.decode.id) {\n // Regularly transmitted CAN frames (manual configuration)\n case \"32C\":\n // Custom flag ---- bit 1 = charge power signal\n if (byte === 6) {\n msg.decode.param1 = paramBin[paramBin.length - 1];\n }\n // Pack SOC\n if (byte === 7) {\n msg.decode.param2 = paramDec;\n }\n break;\n // Data logging CANBUS\n case \"6D0\":\n // Parameter #\n if (byte === 0) {\n msg.decode.paramNum = paramHex;\n }\n // Pack Amperage (big endian, 0.1A increment)\n if (byte === 1) {\n msg.decode.packAmperage = paramHex;\n }\n if (byte === 2) {\n msg.decode.packAmperage += paramHex;\n let packAmperageBin = hex2bin(msg.decode.packAmperage);\n let packAmperageHex = msg.decode.packAmperage;\n // If sign is negative, apply mask (sign bit 15)\n if(packAmperageBin[0] == \"1\"){\n msg.decode.packAmperage = -1*(parseInt(msg.decode.packAmperage, 16) - parseInt(\"FFFF\", 16)); // Subtract mask\n } else {\n msg.decode.packAmperage = -1* parseInt(msg.decode.packAmperage, 16);\n }\n msg.decode.packAmperage = msg.decode.packAmperage / 10; // Shift decimal to 0.1\n }\n // Pack Voltage (big endian, 0.1V increment)\n if (byte === 3) {\n msg.decode.packVoltage = paramHex;\n }\n if (byte === 4) {\n msg.decode.packVoltage += paramHex;\n msg.decode.packVoltage = parseInt(msg.decode.packVoltage, 16) / 10; // Shift decimal to 0.1\n }\n // Parameter Value (big endian)\n if (byte === 5) {\n msg.decode.paramValue = paramHex;\n }\n if (byte === 6) {\n msg.decode.paramValue += paramHex;\n }\n break;\n }\n byte += 1;\n }\n\n // Data Logging CANBUS option\n // Using Parameter XML document for decode\n // ex: PID = 0xFOOF --> (0xF00F - 0xF000 = 0xF) for PACK SOC\n if (msg.decode.id === \"6D0\") {\n switch (msg.decode.paramNum) {\n // Pack SOC --> unit %, scale 0.5\n case \"0F\":\n msg.decode.paramValue = parseInt(msg.decode.paramValue, 16) * 0.5;\n break;\n // Pack Summed Voltage --> unit V, scale 0.01\n case \"14\":\n msg.decode.paramValue = parseInt(msg.decode.paramValue, 16) * 0.01;\n break;\n // Average Battery Temperature --> unit C, scale 0 \n case \"2A\":\n msg.decode.paramValue = parseInt(msg.decode.paramValue, 16);\n break;\n // Relays (bitmasked)\n case \"04\":\n // Decode these using bitwise & comparison with bitmask\n let relayStatus = Array(16);\n let paramValueDec = parseInt(msg.decode.paramValue, 16)\n // Is-Ready Power Status --> mask 40\n relayStatus[0] = paramValueDec & parseInt(40, 16) ? 1 : 0;\n // Is-Charging Power Status --> mask 80\n relayStatus[1] = paramValueDec & parseInt(80, 16) ? 1 : 0;\n // Multi-Purpose Input 1 Status -->mask 10\n relayStatus[2] = paramValueDec & parseInt(10, 16) ? 1 : 0;\n // Multi-Purpose Input 2 Status -->mask 100\n relayStatus[3] = paramValueDec & parseInt(100, 16) ? 1 : 0;\n // Multi-Purpose Input 3 Status -->mask 200\n relayStatus[4] = paramValueDec & parseInt(200, 16) ? 1 : 0;\n // Always-on Power Status --> mask 20\n relayStatus[5] = paramValueDec & parseInt(20, 16) ? 1 : 0;\n // Errors Present --> mask 08\n relayStatus[6] = paramValueDec & parseInt(8, 16) ? 1 : 0;\n // Charge-Enable Output Active --> mask 02\n relayStatus[7] = paramValueDec & parseInt(2, 16) ? 1 : 0;\n // Discharge-Enable Output Active --> mask 01\n relayStatus[8] = paramValueDec & parseInt(1, 16) ? 1 : 0;\n // Charger Safety Output Active --> mask 04\n relayStatus[9] = paramValueDec & parseInt(4, 16) ? 1 : 0;\n // RESERVED ---> mask 400\n relayStatus[10] = paramValueDec & parseInt(400, 16) ? 1 : 0;\n // Multi-Purpose Output 1 Active --> mask 8000\n relayStatus[11] = paramValueDec & parseInt(8000, 16) ? 1 : 0;\n // Multi-Purpose Output 2 Active --> mask 800\n relayStatus[12] = paramValueDec & parseInt(800, 16) ? 1 : 0;\n // Multi-Purpose Output 3 Active --> mask 1000\n relayStatus[13] = paramValueDec & parseInt(1000, 16) ? 1 : 0;\n // Multi-Purpose Output 4 Active --> mask 2000\n relayStatus[14] = paramValueDec & parseInt(2000, 16) ? 1 : 0;\n // Multi-Purpose Enable Active --> mask 4000\n relayStatus[15] = paramValueDec & parseInt(4000, 16) ? 1 : 0;\n msg.decode.paramValue = relayStatus;\n break;\n // High Temperature Thermistor ID --> unit #, scale 0\n case \"2E\":\n msg.decode.paramValue = parseInt(msg.decode.paramValue, 16);\n break;\n // Low Temperature Thermistor ID --> unit #, scale 0\n case \"2F\":\n msg.decode.paramValue = parseInt(msg.decode.paramValue, 16);\n break;\n // Highest Battery Temperature --> unit C, scale 0\n case \"28\":\n msg.decode.paramValue = parseInt(msg.decode.paramValue, 16);\n break;\n // Lowest Battery Temperature --> unit C, scale 0\n case \"29\":\n msg.decode.paramValue = parseInt(msg.decode.paramValue, 16);\n break;\n }\n }\n\n function hex2bin(hex) {\n return (parseInt(hex, 16).toString(2)).padStart(8, '0');\n }\n\n return msg;\n}\n", "outputs": 1, "noerr": 0, "initialize": "// Code added here will be run once\n// whenever the node is started.\nflow.set(\"test\",true);\nflow.set(\"init\",false);\nflow.set(\"count\", 0);", "finalize": "", "libs": [], "x": 430, "y": 200, "wires": [ [ "adb50a91a4fb84a5" ] ] }, { "id": "0cc1020ecb94623b", "type": "influxdb out", "z": "c1a7692d593a9ed7", "influxdb": "f3b5313d1b5d91ec", "name": "", "measurement": "Parameters", "precision": "", "retentionPolicy": "", "database": "database", "precisionV18FluxV20": "ms", "retentionPolicyV18Flux": "", "org": "boschrexroth", "bucket": "boschrexroth", "x": 1100, "y": 260, "wires": [] }, { "id": "adb50a91a4fb84a5", "type": "function", "z": "c1a7692d593a9ed7", "name": "Select Desired Parameters", "func": "// Name values for InfluxDB via msg.payload fields\nlet pushData = false;\n// Pack Summed Voltage\nif(msg.decode.paramNum === \"14\"){\n msg.payload = {};\n msg.payload.pack_amperage = msg.decode.packAmperage;\n msg.payload.pack_voltage = msg.decode.packVoltage;\n msg.payload.pack_summed_voltage = msg.decode.paramValue;\n pushData = true;\n}\n// Pack SOC\nif(msg.decode.paramNum === \"0F\"){\n msg.payload = {};\n msg.payload.pack_amperage = msg.decode.packAmperage;\n msg.payload.pack_voltage = msg.decode.packVoltage;\n msg.payload.pack_SOC = msg.decode.paramValue;\n pushData = true;\n}\n// Average Battery Temperature\nif (msg.decode.paramNum === \"2A\") {\n msg.payload = {};\n msg.payload.pack_amperage = msg.decode.packAmperage;\n msg.payload.pack_voltage = msg.decode.packVoltage;\n msg.payload.average_battery_temperature = msg.decode.paramValue;\n pushData = true;\n}\n// Relays\nif (msg.decode.paramNum === \"04\") {\n msg.payload = {};\n msg.payload.pack_amperage = msg.decode.packAmperage;\n msg.payload.pack_voltage = msg.decode.packVoltage;\n msg.payload.isReady = msg.decode.paramValue[0];\n msg.payload.isCharging = msg.decode.paramValue[1];\n msg.payload.mpi1 = msg.decode.paramValue[2];\n msg.payload.mpi2 = msg.decode.paramValue[3];\n msg.payload.mpi3 = msg.decode.paramValue[4];\n msg.payload.alwaysOn = msg.decode.paramValue[5];\n msg.payload.errorsPresent = msg.decode.paramValue[6];\n msg.payload.chargeEnable = msg.decode.paramValue[7];\n msg.payload.dischargeEnable = msg.decode.paramValue[8];\n msg.payload.chargerSafety = msg.decode.paramValue[9];\n // Bit 10 is RESERVED\n msg.payload.mpo1 = msg.decode.paramValue[11];\n msg.payload.mpo2 = msg.decode.paramValue[12];\n msg.payload.mpo3 = msg.decode.paramValue[13];\n msg.payload.mpo4 = msg.decode.paramValue[14];\n msg.payload.mpeState = msg.decode.paramValue[15];\n pushData = true;\n}\nif (msg.decode.paramNum === \"28\") {\n msg.payload = {};\n msg.payload.pack_amperage = msg.decode.packAmperage;\n msg.payload.pack_voltage = msg.decode.packVoltage;\n msg.payload.temperature_high = msg.decode.paramValue;\n pushData = true;\n}\nif (msg.decode.paramNum === \"29\") {\n msg.payload = {};\n msg.payload.pack_amperage = msg.decode.packAmperage;\n msg.payload.pack_voltage = msg.decode.packVoltage;\n msg.payload.temperature_low = msg.decode.paramValue;\n pushData = true;\n}\nif(pushData){\n return msg;\n}\n\n", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 740, "y": 200, "wires": [ [ "0cc1020ecb94623b" ] ] }, { "id": "82379f96780935e1", "type": "function", "z": "c1a7692d593a9ed7", "name": "Auto-start CAN Messaging", "func": "node.warn(msg);\n\nif(msg.payload == \"C\"){\n return msg;\n}\n\nif (msg.request_payload == \"C\") {\n msg.payload = \"V\";\n return msg;\n}\n\nif (msg.request_payload == \"V\") {\n msg.payload = \"S5\";\n return msg;\n}\n\nif(msg.request_payload == \"S5\"){\n msg.payload = \"O\";\n return msg;\n}\n\nif(msg.status == \"OK\"){\n flow.set(\"init\",true);\n} else {\n msg.payload = \"O\";\n return msg;\n}\n\n\n\n\n\n", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 400, "y": 100, "wires": [ [ "e986ee22425ff8ea" ] ] }, { "id": "52305dd6925c8262", "type": "inject", "z": "c1a7692d593a9ed7", "name": "", "props": [ { "p": "payload" } ], "repeat": "", "crontab": "", "once": true, "onceDelay": 0.1, "topic": "", "payload": "C", "payloadType": "str", "x": 150, "y": 100, "wires": [ [ "82379f96780935e1" ] ] }, { "id": "e986ee22425ff8ea", "type": "serial request", "z": "c1a7692d593a9ed7", "name": "CAN Serial Req", "serial": "b9ebbf4ed8d8e13b", "x": 720, "y": 100, "wires": [ [ "82379f96780935e1" ] ] }, { "id": "9eb956bfdc706f3d", "type": "serial-port", "serialport": "/dev/ttyUSB0", "serialbaud": "230400", "databits": "8", "parity": "none", "stopbits": "1", "waitfor": "t", "dtr": "none", "rts": "none", "cts": "none", "dsr": "none", "newline": "\\r", "bin": "false", "out": "char", "addchar": "\\n", "responsetimeout": "10000" }, { "id": "b9ebbf4ed8d8e13b", "type": "serial-port", "serialport": "/dev/ttyUSB0", "serialbaud": "230400", "databits": "8", "parity": "none", "stopbits": "1", "waitfor": "", "dtr": "none", "rts": "none", "cts": "none", "dsr": "none", "newline": "1", "bin": "false", "out": "count", "addchar": "/n", "responsetimeout": "10000" } ]