Upload JK Jikong BMS voltage and current to pvoutput

This flow requests the JK Jikong BMS (mine is a JK-B2A24S20p) data via serial USB adapter and uploads it to pvoutput. This enables monitoring the daily battery in and out power.

You have to adapt the serial port and the pvoutput API key and sid in the flow.

The hardware I'm using is a: USB-TTL Serial Modul FT232RL FTDI 3,3V und 5V ISP USB Arduino Pro Mini Adapter that is connected to a Raspberry pi USB port an directly to the TTL out port of the BMS.E.g.: https://www.amazon.de/gp/product/B07G87GW4K/ref=ppx_yo_dt_b_asin_title_o02_s00?ie=UTF8&psc=1

The wiring is described here: https://forum.drbacke.de/viewtopic.php?p=26587&sid=2f287444a5f854d60080d1586b933581#p26587

The TTL Adapter is direclty connected via a 4-pin 1.25 Micro JST plug cable. Like this one: https://www.ebay.de/itm/185411283211?var=693203929249

[{"id":"7edcc5c2b2f40e74","type":"serial request","z":"3e20d16d6d150144","name":"","serial":"33a190bb5f257765","x":690,"y":800,"wires":[["25ab3190f361277a","5a9728861aea2322"]]},{"id":"66240475699d21f7","type":"inject","z":"3e20d16d6d150144","name":"trigger","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"[]","payloadType":"bin","x":110,"y":760,"wires":[["0177aa7fd0a231f2"]]},{"id":"0177aa7fd0a231f2","type":"function","z":"3e20d16d6d150144","name":"","func":"\nmsg.timeout = 10000;\n//msg.payload = [0x55, 0xaa, 0x01, 0xff, 0x00, 0x00, 0xff];\n//msg.payload = [0xdd, 0xa5, 0x03, 0x00, 0xff, 0xfd , 0x77];\n//msg.payload = [0x4E, 0x57, 0x00, 0x13, 0x00, 0x00, 0x00, 0x00, 0x06, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0x00, 0x00, 0x01, 0x29]\n\n//msg.payload = Buffer.from([0x55, 0xaa, 0x01, 0xff, 0x00, 0x00, 0xff])\n//msg.payload = Buffer.from([0xdd, 0xa5, 0x03, 0x00, 0xff, 0xfd , 0x77])\n\nmsg.payload = Buffer.from([0x4E, 0x57, 0x00, 0x13, 0x00, 0x00, 0x00, 0x00, 0x06, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0x00, 0x00, 0x01, 0x29])\n\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":340,"y":800,"wires":[["7edcc5c2b2f40e74","3729c9fbf751f535"]]},{"id":"25ab3190f361277a","type":"debug","z":"3e20d16d6d150144","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1030,"y":800,"wires":[]},{"id":"3729c9fbf751f535","type":"debug","z":"3e20d16d6d150144","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":490,"y":760,"wires":[]},{"id":"5a9728861aea2322","type":"function","z":"3e20d16d6d150144","name":"parse payload","func":"function chksum(buffer, len) {\n  checksum = 0;\n  for (ii = 0; ii < len; ii++) {\n    checksum = checksum + buffer[ii];\n  }\n  return checksum;\n}\n\n\nmsg.buffer = msg.payload;\nif (msg.buffer[0] != 0x4E || msg.buffer[1] != 0x57) {\n    node.warn ('Invalid header')\n    //return;\n}\n\nmsg.jkbms = {};\nmsg.jkbms.data_len = msg.buffer[2] << 8 | msg.buffer[2 + 1] << 0;\nmsg.jkbms.function = msg.buffer[8];\nmsg.jkbms.computed_crc = chksum(msg.buffer, msg.jkbms.data_len);\n//msg.jkbms.computed_crc = chksum(msg.buffer, msg.buffer.length);\n\nmsg.jkbms.remote_crc = msg.buffer[msg.jkbms.data_len] << 8 | msg.buffer[msg.jkbms.data_len + 1] << 0;\n\nif (msg.jkbms.computed_crc != msg.jkbms.remote_crc) {\n    node.warn('JkModbus CRC Check failed!');\n}\n\n//cell voltages\nlet cellbase = 11+1;\nmsg.jkbms.numberOfCells = msg.buffer[cellbase] / 3;\nmsg.jkbms.cell_voltages = {};\nfor (cell = 1; cell <= msg.jkbms.numberOfCells; cell++) {\n    msg.jkbms.cell_voltages['cell' + cell] = (msg.buffer[cellbase + cell * 3 - 1] << 8 | msg.buffer[cellbase + cell * 3] << 0) / 1000;\n}\ncellbase = 11 + 1 + msg.jkbms.numberOfCells * 3;\n\n//temperatures\nmsg.jkbms.temperatur = {};\ncellbase+=1;\nif (msg.buffer[cellbase] != 0x80) {\n    node.warn ('temperatur marker not correct!');\n}\nmsg.jkbms.temperatur.internal = msg.buffer[cellbase + 1] << 8 | msg.buffer[cellbase + 2] << 0;\nmsg.jkbms.temperatur.battery1 = msg.buffer[cellbase + 4] << 8 | msg.buffer[cellbase + 5] << 0;\nmsg.jkbms.temperatur.battery2 = msg.buffer[cellbase + 7] << 8 | msg.buffer[cellbase + 8] << 0;\ncellbase += 8;\n\n//voltage\ncellbase+=1;\nif (msg.buffer[cellbase] != 0x83) {\n    node.warn ('voltage marker not correct!');\n}\nmsg.jkbms.voltage = (msg.buffer[cellbase + 1] << 8 | msg.buffer[cellbase + 2] << 0) / 100;\ncellbase+= 2;\n\n//current\ncellbase+=1;\nif (msg.buffer[cellbase] != 0x84) {\n    node.warn ('current marker not correct!');\n}\nmsg.jkbms.current = (msg.buffer[cellbase + 1] << 8 | msg.buffer[cellbase + 2] << 0);\nmsg.jkbms.current_raw = msg.jkbms.current;\n\nlet CURRENT_ZERO_CONSTANT = 32768\nif (msg.jkbms.current < CURRENT_ZERO_CONSTANT)\n    msg.jkbms.current = msg.jkbms.current / -100;\nelse\n    msg.jkbms.current = (msg.jkbms.current - CURRENT_ZERO_CONSTANT) / 100;\n\n//print(current / -100 if current < CURRENT_ZERO_CONSTANT else (current - CURRENT_ZERO_CONSTANT) / 100)\n\n/*if ((msg.jkbms.current & 0x8000) == 0x8000) {\n    msg.jkbms.current = msg.jkbms.current & 0x7FFF;\n} else {\n    msg.jkbms.current = msg.jkbms.current & 0x7FFF * -1;\n}*/\n\n/*if (msg.jkbms.current > 10000) {\n    msg.jkbms.current-=10000;\n    msg.jkbms.current*=-1;\n}\nmsg.jkbms.current/=100;*/\nmsg.jkbms.power = msg.jkbms.current * msg.jkbms.voltage;\ncellbase+= 2;\n\n//remainig battery\ncellbase+=1;\nif (msg.buffer[cellbase] != 0x85) {\n    node.warn ('remaining battery SOC marker not correct!');\n}\nmsg.jkbms.remainigBattery = msg.buffer[cellbase + 1];\ncellbase+= 1;\n\n//numberof NTC\ncellbase+=1;\nif (msg.buffer[cellbase] != 0x86) {\n    node.warn ('number of NTC marker not correct!');\n}\nmsg.jkbms.numberOfNTC = msg.buffer[cellbase + 1];\ncellbase+= 1;\n\n//numberof battery cycles\ncellbase+=1;\nif (msg.buffer[cellbase] != 0x87) {\n    node.warn ('number of battery cycles marker not correct!');\n}\nmsg.jkbms.numberOfBatteryCycles = (msg.buffer[cellbase + 1] << 8 | msg.buffer[cellbase + 2] << 0);\ncellbase+= 2;\n\n//battery cycle capacity Ah\ncellbase+=1;\nif (msg.buffer[cellbase] != 0x89) {\n    node.warn ('battery cycle capacity Ah marker not correct!');\n}\nmsg.jkbms.batteryCycleCapacityAh = (msg.buffer[cellbase + 1] << 24 | msg.buffer[cellbase + 2] << 16 | msg.buffer[cellbase + 3] << 8 | msg.buffer[cellbase + 4] << 0);\ncellbase+= 4;\n\n//numberof strings\ncellbase+=1;\nif (msg.buffer[cellbase] != 0x8a) {\n    node.warn ('number of strings marker not correct!');\n}\nmsg.jkbms.numberOfStrings = (msg.buffer[cellbase + 1] << 8 | msg.buffer[cellbase + 2] << 0);\ncellbase+= 2;\n\n//battery warning\nmsg.jkbms.warning = {};\n\nconst lowCapacity                         = 0x0000000000000001;\nconst powerTubeOvertemperature            = 0x0000000000000010;\nconst chargingOvervoltage                 = 0x0000000000000100;\nconst dischargingUndervoltage             = 0x0000000000001000;\nconst batteryOverTemperature              = 0x0000000000010000;\nconst chargingOvercurrent                 = 0x0000000000100000;\nconst dischargingOvercurrent              = 0x0000000001000000;\nconst cellPressureDifference              = 0x0000000010000000;\nconst overtemperatureAlarmInTheBatteryBox = 0x0000000100000000;\nconst batteryLowTemperature               = 0x0000001000000000;\nconst cellOvervoltage                     = 0x0000010000000000;\nconst cellUndervoltage                    = 0x0000100000000000;\nconst alarm309_AProtection                = 0x0001000000000000;\nconst alarm309_BProtection                = 0x0010000000000000;\n\ncellbase+=1;\nif (msg.buffer[cellbase] != 0x8b) {\n    node.warn ('battery warning marker not correct!');\n}\nmsg.jkbms.warning.batteryWarning = (msg.buffer[cellbase + 1] << 8 | msg.buffer[cellbase + 2] << 0);\n\nmsg.jkbms.warning.lowCapacity = msg.jkbms.warning.batteryWarning & lowCapacity;\nmsg.jkbms.warning.powerTubeOvertemperature = msg.jkbms.warning.batteryWarning & powerTubeOvertemperature;\nmsg.jkbms.warning.chargingOvervoltage = msg.jkbms.warning.batteryWarning & chargingOvervoltage;\nmsg.jkbms.warning.dischargingUndervoltage = msg.jkbms.warning.batteryWarning & dischargingUndervoltage;\nmsg.jkbms.warning.batteryOverTemperature = msg.jkbms.warning.batteryWarning & batteryOverTemperature;\nmsg.jkbms.warning.chargingOvercurrent = msg.jkbms.warning.batteryWarning & chargingOvercurrent;\nmsg.jkbms.warning.dischargingOvercurrent = msg.jkbms.warning.batteryWarning & dischargingOvercurrent;\nmsg.jkbms.warning.cellPressureDifference = msg.jkbms.warning.batteryWarning & cellPressureDifference;\nmsg.jkbms.warning.overtemperatureAlarmInTheBatteryBox = msg.jkbms.warning.batteryWarning & overtemperatureAlarmInTheBatteryBox;\nmsg.jkbms.warning.batteryLowTemperature = msg.jkbms.warning.batteryWarning & batteryLowTemperature;\nmsg.jkbms.warning.cellOvervoltage = msg.jkbms.warning.batteryWarning & cellOvervoltage;\nmsg.jkbms.warning.cellUndervoltage = msg.jkbms.warning.batteryWarning & cellUndervoltage;\nmsg.jkbms.warning.alarm309_AProtection = msg.jkbms.warning.batteryWarning & alarm309_AProtection;\nmsg.jkbms.warning.alarm309_BProtection = msg.jkbms.warning.batteryWarning & alarm309_BProtection;\n\ncellbase+= 2;\n\n//battery status\nmsg.jkbms.status = {};\n\nconst chargingEnabled                         = 0x0000000000000001;\nconst dischargingEnabled                      = 0x0000000000000010;\nconst balancingEnabled                        = 0x0000000000000100;\nconst batteryConnected                        = 0x0000000000001000;\n\ncellbase+=1;\nif (msg.buffer[cellbase] != 0x8c) {\n    node.warn ('battery warning marker not correct!');\n}\nmsg.jkbms.status.batteryStatus = (msg.buffer[cellbase + 1] << 8 | msg.buffer[cellbase + 2] << 0);\n\nmsg.jkbms.status.chargingEnabled = msg.jkbms.status.batteryStatus & chargingEnabled;\nmsg.jkbms.status.dischargingEnabled = msg.jkbms.status.batteryStatus & dischargingEnabled;\nmsg.jkbms.status.balancingEnabled = msg.jkbms.status.batteryStatus & balancingEnabled;\nmsg.jkbms.status.batteryConnected = msg.jkbms.status.batteryStatus & batteryConnected;\n\ncellbase+= 2;\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":160,"y":900,"wires":[["a6b6a4d7373d9a48","6ca89b6a0455b99d"]]},{"id":"a6b6a4d7373d9a48","type":"debug","z":"3e20d16d6d150144","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":950,"y":900,"wires":[]},{"id":"6ca89b6a0455b99d","type":"function","z":"3e20d16d6d150144","name":"prepare pvoutput upload","func":"if (msg.skip)\n    return;\n\nlet myDate = new Date();\n\nlet currdate = myDate.\n  toLocaleString('en-us', {year: 'numeric', month: '2-digit', day: '2-digit'}).\n  replace(/(\\d+)\\/(\\d+)\\/(\\d+)/, '$3$1$2');\n\nlet currtime = myDate.getHours()+ ':' + myDate.getMinutes();\n\nlet batteryVoltage = msg.jkbms.voltage;\nlet BatteryChargePower = 0;\nlet BatteryDischargePower = 0;\n\nif (msg.jkbms.power > 0)\n    BatteryChargePower = msg.jkbms.power;\nelse\n    BatteryDischargePower = msg.jkbms.power * -1;\n\nmsg.payload = {\n    d: currdate,\n    t: currtime,\n    v2: BatteryChargePower,\n    v4: BatteryDischargePower,\n    v6: batteryVoltage\n};\n\nmsg.requestTimeout = 5000;\nreturn msg;\n\n\n","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":190,"y":960,"wires":[["4bd46a82994a68b7"]]},{"id":"4bd46a82994a68b7","type":"http request","z":"3e20d16d6d150144","name":"pvoutput addstatus","method":"GET","ret":"txt","paytoqs":"query","url":"https://pvoutput.org/service/r2/addstatus.jsp?key=yourapikeyhere&sid=yoursidhere","tls":"","persist":false,"proxy":"","authType":"","senderr":false,"credentials":{"user":"","password":""},"x":430,"y":960,"wires":[["9794e593bdcbbe51"]]},{"id":"9794e593bdcbbe51","type":"debug","z":"3e20d16d6d150144","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":950,"y":960,"wires":[]},{"id":"8c919b0b02dad084","type":"inject","z":"3e20d16d6d150144","name":"every 5 minutes","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"*/5 5-22 * * *","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":150,"y":820,"wires":[["0177aa7fd0a231f2"]]},{"id":"33a190bb5f257765","type":"serial-port","serialport":"/dev/serial/by-id/usb-FTDI_FT232R_USB_UART_A50285BI-if00-port0","serialbaud":"115200","databits":"8","parity":"none","stopbits":"1","waitfor":"","dtr":"none","rts":"none","cts":"none","dsr":"none","newline":"500","bin":"bin","out":"interbyte","addchar":"","responsetimeout":"10"}]

Flow Info

Created 3 years ago
Updated 2 years, 5 months ago
Rating: 5 2

Owner

Actions

Rate:

Node Types

Core
  • debug (x4)
  • function (x3)
  • http request (x1)
  • inject (x2)
Other
  • serial request (x1)
  • serial-port (x1)

Tags

  • JK
  • JIKONG
  • BMS
  • pvoutput
  • photovoltaik
Copy this flow JSON to your clipboard and then import into Node-RED using the Import From > Clipboard (Ctrl-I) menu option