syssi / esphome-jk-bms

ESPHome component to monitor and control a Jikong Battery Management System (JK-BMS) via UART-TTL or BLE
Apache License 2.0
483 stars 163 forks source link

Add UART-TTL command support using authenticated (`0x05`) write instructions #341

Closed jrventer closed 1 year ago

jrventer commented 1 year ago

Hi

After some extensive trial and error I have managed to figure out how to enable the write requests through the TTL interface. For the testing I just did some lambda code to make it easier for the trial and error so will share that. I could go update your code but would likely be much easier for you to make the changes based on my findings. This will now allow us to switch back to fixed connection instead of the BLE connection that take allot of resources and not that stable.

In order to have a successful write request which is acknowledge by the BMS you have to send a request with the 0x05 (Pairing) command. Then when you do a write request the BMS reply. From my testing I can do one 0x05 command request and do multiple writes thereafter. The BMS does not respond to the 0x05 request but as long as it is structured correctly your following write requests will have responses from the BMS. I assume the pairing request will time out at some stage so might be easier just to always issue the pairing before making changes.

Below the Frame structure I used for my testing: Pairing Command Request

- lambda: |-
            ESP_LOGI("main","JK RS485 Pair 0x05 Command");
      - uart.write:
          id: uart_0
          data: !lambda |-
            uint8_t frame[26];
            frame[0] = 0x4E;      // start sequence
            frame[1] = 0x57;      // start sequence
            frame[2] = 0x00;      // data length lb
            frame[3] = 0x14;      // data length hb
            frame[4] = 0x00;      // bms terminal number
            frame[5] = 0x00;      // bms terminal number
            frame[6] = 0x00;      // bms terminal number
            frame[7] = 0x00;      // bms terminal number
            frame[8] = 0x05;  // command word: 0x01 (activation), 0x02 (write), 0x03 (read), 0x05 (password), 0x06 (read all)
            frame[9] = 0x03;      // frame source: 0x00 (bms), 0x01 (bluetooth), 0x02 (gps), 0x03 (computer)
            frame[10] = 0x00;     // frame type: 0x00 (read data), 0x01 (reply frame), 0x02 (BMS active upload)
            frame[11] = 0xB2;  // register: 0x00 (read all registers), 0x8E...0xBF (holding registers)
            frame[12] = 0x00;     // Data
            frame[13] = 0x00;     // record number
            frame[14] = 0x00;     // record number
            frame[15] = 0x00;     // record number
            frame[16] = 0x00;     // record number
            frame[17] = 0x68;     // end sequence
            uint16_t crc = 0;
            for (uint16_t i = 0; i < 18; i++) {
              crc = crc + frame[i];
            }
            frame[18] = 0x00;  // crc unused
            frame[19] = 0x00;  // crc unused
            frame[20] = crc >> 8;
            frame[21] = crc >> 0;
            return {frame[0], frame[1], frame[2], frame[3], frame[4], frame[5], frame[6], frame[7], frame[8], frame[9], frame[10], frame[11], frame[12], frame[13], frame[14], frame[15], frame[16], frame[17], frame[18], frame[19], frame[20], frame[21]};

Write request:

- lambda: |-
            ESP_LOGI("main","JK RS485 Write 0x0AB = 1 Charging MOS Switch");
      - uart.write:
          id: uart_0
          data: !lambda |-
            uint8_t frame[21];
            frame[0] = 0x4E;      // start sequence
            frame[1] = 0x57;      // start sequence
            frame[2] = 0x00;      // data length lb
            frame[3] = 0x14;      // data length hb
            frame[4] = 0x00;      // bms terminal number
            frame[5] = 0x00;      // bms terminal number
            frame[6] = 0x00;      // bms terminal number
            frame[7] = 0x00;      // bms terminal number
            frame[8] = 0x02;  // command word: 0x01 (activation), 0x02 (write), 0x03 (read), 0x05 (password), 0x06 (read all)
            frame[9] = 0x03;      // frame source: 0x00 (bms), 0x01 (bluetooth), 0x02 (gps), 0x03 (computer)
            frame[10] = 0x00;     // frame type: 0x00 (read data), 0x01 (reply frame), 0x02 (BMS active upload)
            frame[11] = 0xAB;  // register: 0x00 (read all registers), 0x8E...0xBF (holding registers)
            frame[12] = 0x01;     // Data
            frame[13] = 0x00;     // record number
            frame[14] = 0x00;     // record number
            frame[15] = 0x00;     // record number
            frame[16] = 0x00;     // record number
            frame[17] = 0x68;     // end sequence
            uint16_t crc = 0;
            for (uint16_t i = 0; i < 18; i++) {
              crc = crc + frame[i];
            }
            frame[18] = 0x00;  // crc unused
            frame[19] = 0x00;  // crc unused
            frame[20] = crc >> 8;
            frame[21] = crc >> 0;
            return {frame[0], frame[1], frame[2], frame[3], frame[4], frame[5], frame[6], frame[7], frame[8], frame[9], frame[10], frame[11], frame[12], frame[13], frame[14], frame[15], frame[16], frame[17], frame[18], frame[19], frame[20], frame[21]};

Below my debug logs showing the requests and responses from the BMS.

[16:25:10][I][main:237]: JK RS485 Read All Data
[16:25:10][D][uart_debug:158]: >>> "NW\x00\x13\x00\x00\x00\x00\x06\x03\x00\x00\x00\x00\x00\x00h\x00\x00\x01)"
[16:25:10][D][uart_debug:158]: <<< "NW\x01!\x00\x00\x00\x00\x06\x00\x01y0\x01\f\xCA\x02\f\xCC\x03\f\xCC\x04\f\xCD\x05\f\xCC\x06\f\xCD\a\f\xCC\b\f\xCD\t\f\xCC\n"
[16:25:10][D][uart_debug:158]: <<< "\f\xCC\v\f\xCC\f\f\xCC\r\f\xCD\x0E\f\xCD\x0F\f\xCC\x10\f\xCC\x80\x00\x16\x81\x00\x12\x82\x00\x12\x83\x14y\x84\x00\x00\x85c\x86\x02\x87\x00\x00\x89\x00\x00\x00\x00\x8A\x00\x10\x8B\x00\x00\x8C\x00\x00\x8E\x16\x80\x8F\x10@\x90\x0E\x10\x91\r\xDE\x92\x00\x05\x93\n"
[16:25:10][D][uart_debug:158]: <<< "(\x94\n"
[16:25:10][D][uart_debug:158]: <<< "Z\x95\x00\x05\x96\x01,\x97\x00\x96\x98\x01,\x99\x00\x19\x9A\x00\x1E\x9B\v\xB8\x9C\x00\n"
[16:25:10][D][uart_debug:158]: <<< "\x9D\x00\x9E\x00d\x9F\x00P\xA0\x00d\xA1\x00d\xA2\x00\x14\xA3\x00F\xA4\x00F\xA5\xFF\xEC\xA6\xFF\xF6\xA7\xFF\xEC\xA8\xFF\xF6\xA9\x10\xAA\x00\x00\x00\xE6\xAB\x00\xAC\x00\xAD\x04\x1C\xAE\x01\xAF\x00\xB0\x00\n"
[16:25:10][W][jk_modbus:095]: Got JkModbus frame from unknown address 0x4E!
[16:25:10][D][uart_debug:158]: <<< "\xB1\x14\xB2301653\x00\x00\x00\x00\xB3\x00\xB4Input Us\xB52306\xB6\x00\x00\x10\xCF\xB711.XW_S11.26___\xB8\x00\xB9\x00\x00\x00\xE6\xBAInput Userda16S-230AH-1\x00\xC0\x01\x00\x00\x00\x00h\x00\x00VL"
[16:25:11][I][main:273]: JK RS485 Read 0x0AB Charging MOS Switch
[16:25:11][D][uart_debug:158]: >>> "NW\x00\x13\x00\x00\x00\x00\x03\x03\x00\xAB\x00\x00\x00\x00h\x00\x00\x01\xD1"
[16:25:11][W][jk_modbus:095]: Got JkModbus frame from unknown address 0x4E!
[16:25:11][D][uart_debug:158]: <<< "NW\x00\x14\x00\x00\x00\x00\x03\x00\x01\xAB\x00\x00\x00\x00\x00h\x00\x00\x01\xD0"
[16:25:12][I][main:309]: JK RS485 Pair 0x05 Command
[16:25:12][D][uart_debug:158]: >>> "NW\x00\x14\x00\x00\x00\x00\x05\x03\x00\xB2\x00\x00\x00\x00\x00h\x00\x00\x01\xDB"
[16:25:13][I][main:345]: JK RS485 Write 0x0AB = 1 Charging MOS Switch
[16:25:13][D][uart_debug:158]: >>> "NW\x00\x14\x00\x00\x00\x00\x02\x03\x00\xAB\x01\x00\x00\x00\x00h\x00\x00\x01\xD2"
[16:25:13][W][jk_modbus:095]: Got JkModbus frame from unknown address 0x4E!
[16:25:13][D][uart_debug:158]: <<< "NW\x00\x13\x00\x00\x00\x00\x02\x00\x01\xAB\x00\x00\x00\x00h\x00\x00\x01\xCE"
[16:25:14][I][main:381]: JK RS485 Read 0x0AB Charging MOS Switch
[16:25:14][D][uart_debug:158]: >>> "NW\x00\x13\x00\x00\x00\x00\x03\x03\x00\xAB\x00\x00\x00\x00h\x00\x00\x01\xD1"
[16:25:14][W][jk_modbus:095]: Got JkModbus frame from unknown address 0x4E!
[16:25:14][D][uart_debug:158]: <<< "NW\x00\x14\x00\x00\x00\x00\x03\x00\x01\xAB\x01\x00\x00\x00\x00h\x00\x00\x01\xD1"
[16:25:15][I][main:417]: JK RS485 Read 0x0AC Discharging MOS Switch
[16:25:15][D][uart_debug:158]: >>> "NW\x00\x13\x00\x00\x00\x00\x03\x03\x00\xAC\x00\x00\x00\x00h\x00\x00\x01\xD2"
[16:25:15][W][jk_modbus:095]: Got JkModbus frame from unknown address 0x4E!
[16:25:15][D][uart_debug:158]: <<< "NW\x00\x14\x00\x00\x00\x00\x03\x00\x01\xAC\x00\x00\x00\x00\x00h\x00\x00\x01\xD1"
[16:25:16][I][main:452]: JK RS485 Write 0x0AC = 1 Discharging MOS Switch
[16:25:16][D][uart_debug:158]: >>> "NW\x00\x14\x00\x00\x00\x00\x02\x03\x00\xAC\x01\x00\x00\x00\x00h\x00\x00\x01\xD3"
[16:25:16][W][jk_modbus:095]: Got JkModbus frame from unknown address 0x4E!
[16:25:16][D][uart_debug:158]: <<< "NW\x00\x13\x00\x00\x00\x00\x02\x00\x01\xAC\x00\x00\x00\x00h\x00\x00\x01\xCF"
[16:25:17][I][main:488]: JK RS485 Read 0x0AC Discharging MOS Switch
[16:25:17][D][uart_debug:158]: >>> "NW\x00\x13\x00\x00\x00\x00\x03\x03\x00\xAC\x00\x00\x00\x00h\x00\x00\x01\xD2"
[16:25:17][W][jk_modbus:095]: Got JkModbus frame from unknown address 0x4E!
[16:25:17][D][uart_debug:158]: <<< "NW\x00\x14\x00\x00\x00\x00\x03\x00\x01\xAC\x01\x00\x00\x00\x00h\x00\x00\x01\xD2"

Let me know if this will be enough to make the updates to the jk_modbus and jk_bms components.

syssi commented 1 year ago

Wow! You did an amazing job!! I will try to reproduce your findings as soon as possible on different BMS versions. This will take some time but it looks very promising.

jrventer commented 1 year ago

Ok thanks. FYI my BMS details: Model: JK_B2A24S15P Hardware Ver: V11.XW Software Ver: V11.26 Mine have CAN customisation as well so ordered model was JK_B2A24S15P-CAN

I am busy discussing with JK to get more info but was lucky to figure this part out before they gave me the final information. I received some new docs but they look mainly the same but will check and compare and share if they are updated.

jrventer commented 1 year ago

@syssi just test but I suspect we will need to make the source GPS. I was testing many combinations and then changed back to PC source to confirm but as we do not get any response for the pairing/write activation and it stays active for a while it might have activated with gps source. I will also try do some testing later to confirm.

syssi commented 1 year ago

Thanks for your feedback! The implementation of the first switches is ready but untested. Please verify the request payloads before issuing the commands on your hardware. I don't want to mess up the registers of your BMS.

syssi commented 1 year ago

Do you know the meaning of the 0xb2 register of the password (0x05) frame?

frame[11] = 0xB2; // register: 0x00 (read all registers), 0x8E...0xBF (holding registers)
jrventer commented 1 year ago

Do you know the meaning of the 0xb2 register of the password (0x05) frame?

frame[11] = 0xB2; // register: 0x00 (read all registers), 0x8E...0xBF (holding registers)

It is the 0xB2 "Modify parameter password" as I initially assumed they would require the password to be passed as data but then removed the password from the data payload. I suspect it is ignored so will do some tests to pass 0x00 instead.

jrventer commented 1 year ago

also had a quick check and seems like you need to update the CRC frame length: ..... frame[11] = address; // register: 0x00 (read all registers), 0x8E...0xBF (holding registers) frame[12] = value; // data frame[13] = 0x00; // record number frame[14] = 0x00; // record number frame[15] = 0x00; // record number frame[16] = 0x00; // record number frame[17] = 0x68; // end sequence auto crc = chksum(frame, 17); <<<<<< should be 18 ....

syssi commented 1 year ago

Good catch!