SolarConvert Posted July 11, 2022 Share Posted July 11, 2022 (edited) Hi all, I thought I would document my findings of the Tian Power BMS protocol used in my Narada 48NPFC100 battery whilst I was attempting to establish why it did not work with my Deye inverter. Of note is that the exact same battery model is known to use a Shinwa BMS as well, but mine uses a Tian Power one, specifically TP-ND1530. Firstly I wanted to thank @shanghailoz for documenting the same thing here for the Revov batteries, which also use a Tian Power BMS, without which I would have struggled a lot more. Also thank you @zivva for pointing me in the right direction for the BMS manufacturer. I also peeked at the code of the BMS software to decipher how certain fields are calculated. The protocol follows. Preamble Firstly, all data sent appears to have a header, body and CRC check. For example: 7e 01 01 00 fe 0d The first byte is either 7c or 7e according to the software. In my case it is always 7e. The second byte is the DIP switch setting on the battery and corresponds to the following in the software: The third byte is the function code, which you will see several examples of below, such as "read data", "read BMS time", "read BMS version", "read serial number". The fourth byte is the length of the payload supplied as part of the function call - most "read" type functions will not include a payload and this parameter will be 00, but the "write" functions would have a payload. If there is a payload, it would follow this byte. The last two bytes are the CRC check. The response is structured in a similar way: 7e 01 01 56 ... f2 0d The first three bytes are an echo of what the request was, the fourth byte indicates the response length followed by the response bytes, and finally we have the CRC check. Function 1 - Read data 7e 01 01 00 fe 0d This allows you to read almost everything that is displayed on the first tab of the BMS software. The response will look as follows: 7e 01 01 56 01 0f 0c e1 0c e2 0c e2 0c e3 0c e3 0c e5 0c e5 0c e4 0c e4 0c e4 0c e2 0c e2 0c e2 0c e3 0c e5 02 01 75 30 03 01 16 74 04 01 27 10 05 06 00 44 00 45 00 45 00 44 40 46 20 45 06 05 00 00 00 00 00 00 00 00 00 00 07 01 00 81 08 01 13 54 09 01 27 10 0a 01 00 00 f2 0d The response body for this function is a data map structured as follows: data entry index, number of high/low byte pairs, the high/low byte pairs. Data 1 - Cell count and cell voltages 01 0f 0c e1 0c e2 0c e2 0c e3 0c e3 0c e5 0c e5 0c e4 0c e4 0c e4 0c e2 0c e2 0c e2 0c e3 0c e5 The second byte is the number of cells - this is 15 for a 15 cell battery like mine or 16 cells for Revov. The individual cell voltages follow as high/low pairs which need to be divided by 1000, for example, 0ce1 maps to 3297 which means 3.297V. Since my battery has 15 cells, there would be 15 individual cell voltages. Data 2 - Current 02 01 75 30 This is a tricky one. 7530 hex maps to 30000 decimal. The BMS software works it out as: current_in_amps = (30000 - value_of_item_2) / 100 This item therefore has an offset of 30000 and a scaling factor of 0.01. So when the charge current is 0, the BMS will return 30000, therefore (30000 - 30000) / 100 = 0 amps. When the charge current is 10 amps, the BMS would return 29000, therefore (30000 - 29000) / 100 = 10 amps. When the charge current is -10 amps, the BMS would return 31000, therefore (30000 - 31000) / 100 = -10 amps. Furthermore, charge/discharge time remaining is calculated based on max battery capacity in amps divided by current in amps. Data 3 - Remaining capacity 03 01 16 74 This value needs to be divided by 100 to get Ah. In this case I have 1674 hex or 5748 decimal, so 57.48Ah left of my 100Ah battery, therefore the SOC is 57.48%. Data 4 - Full capacity 04 01 27 10 Divide by 100 to get full battery capacity in Ah. 2710 in hex is 10000 in decimal, so I have a 100Ah battery. Data 5 - Temperatures 05 06 00 44 00 45 00 45 00 44 40 46 20 45 In my case there are 6 temperatures corresponding to the following screenshot: In the Revovs the number of temperatures listed are different. The last two appear to be important ones. Each temperature value is calculated as: temp_value = (bms_temp_value & 0xFF) - 50 Data 6 - Alarm bits 06 05 00 00 00 00 00 00 00 00 00 00 These can be mapped to (a) very specific alarm code(s). I can get these if there is interest. Data 7 - Cycles 07 01 00 81 This represents the number of cycles of the battery, so 129 in my case. Data 8 - Voltage 08 01 13 54 The total voltage of the battery. The number needs to be divided by 100, so 49.48V in my case. Data 9 - State of Health 09 01 27 10 The SOH of the battery. Needs to be divided by 100, so 100% in my case. Data 10 - ALM bytes 0a 01 00 00 This corresponds to the ALM light on the battery and what it means. Function 67 - Read protection parameters Request: 7e 01 43 00 fe 0d Response: 7e 01 43 94 00 02 0e 10 01 02 03 e8 02 02 0d ac 03 02 0a f0 04 02 03 e8 05 02 0c 1c 06 02 15 4a 07 02 03 e8 08 02 14 c8 09 02 11 94 0a 02 03 e8 0b 02 13 88 0c 02 1f 40 0d 02 03 e8 0e 02 1b 58 0f 02 1f 40 10 02 03 e8 11 02 1b 58 12 02 00 69 13 02 0f a0 14 02 00 5f 15 02 00 32 16 02 0f a0 17 02 00 3c 18 02 00 69 19 02 0f a0 1a 02 00 5f 1b 02 00 32 1c 02 0f a0 1d 02 00 3c 1e 02 00 8c 1f 02 0f a0 20 02 00 87 21 02 00 0a 22 02 00 0f 23 02 03 20 24 02 01 f4 de 0d This is another data map which ultimately gets translated into the following, with the necessary scaling factors applied: { "cell_ov_start": 3.6, "cell_ov_delay": 1000, "cell_ov_stop": 3.5, "cell_uv_start": 2.8, "cell_uv_delay": 1000, "cell_uv_stop": 3.1, "pack_ov_start": 54.5, "pack_ov_delay": 1000, "pack_ov_stop": 53.2, "pack_uv_start": 45.0, "pack_uv_delay": 1000, "pack_uv_stop": 50.0, "charge_oc_start": 80.0, "charge_oc_delay": 1000, "charge_oc_stop": 70.0, "discharge_oc_start": 80.0, "discharge_oc_delay": 1000, "discharge_oc_stop": 70.0, "cell_ot_start": 55, "cell_ot_delay": 4000, "cell_ot_stop": 45, "cell_ut_start": 0, "cell_ut_delay": 4000, "cell_ut_stop": 10, "env_ot_start": 55, "env_ot_delay": 4000, "env_ot_stop": 45, "env_ut_start": 0, "env_ut_delay": 4000, "env_ut_stop": 10, "mos_ot_start": 90, "mos_ot_delay": 4000, "mos_ot_stop": 85, "capacity_low_start": 10, "capacity_low_stop": 15, "volt_diff_start": 800, "volt_diff_stop": 500 } Function 51 - Read BMS version The request is as follows: 7e 01 33 00 fe 0d The response body is a string, such as: 7e 01 33 18 54 50 2d 4e 44 31 35 33 30 2d 31 35 53 31 30 30 41 2d 56 31 2e 30 2e 30 2e 0d Which in my case maps to: TP-ND1530-15S100A-V1.0.0 Function 66 - Read PCB barcode Request: 7e 01 42 00 fc 0d Response body is a string as above. Function 220 - Read serial number Request: 7e 01 dc 03 06 00 00 c2 0d Response body is a string as above. Function 69 - Read BMS time Request: 7e 01 45 00 fe 0d Response: 7e 01 45 06 16 07 08 14 3b 16 48 0d To convert the above to a valid date time, prepend "20" and then concatenate the rest of the bytes which represent yy, MM, dd, hh, mm, ss, so in the above example: 2022-07-08 20:59:22. There are many "write"-type functions too but I would be wary of using those. I hope the above is useful to someone. I will post more useful ones if I find any. Edited September 13, 2022 by SolarConvert Clarified charge current and temperatures, added protection data jumper, WannabeSolarSparky, idiot-ranch and 2 others 1 4 Quote Link to comment Share on other sites More sharing options...
shanghailoz Posted August 17, 2022 Share Posted August 17, 2022 Nicely done. I should revisit some of the values on mine, see if i can map more fields to actual values. Quote Link to comment Share on other sites More sharing options...
SolarConvert Posted August 19, 2022 Author Share Posted August 19, 2022 Thanks @shanghailoz One command that the Deye inverter does issue is the following, but I have yet to decipher what this one does. Command: 7e 01 43 00 fe 0d Response: 7e 01 43 94 00 02 0e 10 01 02 03 e8 02 02 0d ac 03 02 0a f0 04 02 03 e8 05 02 0c 1c 06 02 15 4a 07 02 03 e8 08 02 14 c8 09 02 11 94 0a 02 03 e8 0b 02 13 88 0c 02 1f 40 0d 02 03 e8 0e 02 1b 58 0f 02 1f 40 10 02 03 e8 11 02 1b 58 12 02 00 69 13 02 0f a0 14 02 00 5f 15 02 00 32 16 02 0f a0 17 02 00 3c 18 02 00 69 19 02 0f a0 1a 02 00 5f 1b 02 00 32 1c 02 0f a0 1d 02 00 3c 1e 02 00 8c 1f 02 0f a0 20 02 00 87 21 02 00 0a 22 02 00 0f 23 02 03 20 24 02 01 f4 de 0d I can see the data map but do not yet know what each item means. Reformatted: 7e 01 43 94 00 02 0e 10 01 02 03 e8 02 02 0d ac 03 02 0a f0 04 02 03 e8 05 02 0c 1c 06 02 15 4a 07 02 03 e8 08 02 14 c8 09 02 11 94 0a 02 03 e8 0b 02 13 88 0c 02 1f 40 0d 02 03 e8 0e 02 1b 58 0f 02 1f 40 10 02 03 e8 11 02 1b 58 12 02 00 69 13 02 0f a0 14 02 00 5f 15 02 00 32 16 02 0f a0 17 02 00 3c 18 02 00 69 19 02 0f a0 1a 02 00 5f 1b 02 00 32 1c 02 0f a0 1d 02 00 3c 1e 02 00 8c 1f 02 0f a0 20 02 00 87 21 02 00 0a 22 02 00 0f 23 02 03 20 24 02 01 f4 de 0d Since this is the first call that the inverter makes to the BMS, these must be the parameters that the inverter expects back from the BMS. When I have time I will try to dig deeper, just posting it here for now. Quote Link to comment Share on other sites More sharing options...
jetlee Posted September 12, 2022 Share Posted September 12, 2022 Hey, can you please elaborate on that current calculation with an example .. its not making sense to me ? I assume this could be negative (discharging) or positive (charging) ? But I cant follow your logic ? Thx Quote Link to comment Share on other sites More sharing options...
SolarConvert Posted September 13, 2022 Author Share Posted September 13, 2022 So to get the current in amps, you need the following formula Current = (30000 - value_of_item_2) / 100 30000 is a static value from which you need to subtract the value of item 2 returned by the BMS. I assume that Tian Power did this so that only unsigned values are returned by the BMS when queried, therefore some items not only have a scaling factor but also an offset to subtract from, if that makes sense? I'll update the first post with the latest info I have - there is a lot of new info. Quote Link to comment Share on other sites More sharing options...
SolarConvert Posted September 13, 2022 Author Share Posted September 13, 2022 On 2022/08/19 at 3:43 PM, SolarConvert said: One command that the Deye inverter does issue is the following, but I have yet to decipher what this one does. Command: 7e 01 43 00 fe 0d I have managed to decode the response and have amended my first post to include BMS protection data. Quote Link to comment Share on other sites More sharing options...
jetlee Posted September 13, 2022 Share Posted September 13, 2022 3 hours ago, SolarConvert said: So to get the current in amps, you need the following formula Current = (30000 - value_of_item_2) / 100 30000 is a static value from which you need to subtract the value of item 2 returned by the BMS. I assume that Tian Power did this so that only unsigned values are returned by the BMS when queried, therefore some items not only have a scaling factor but also an offset to subtract from, if that makes sense? I'll update the first post with the latest info I have - there is a lot of new info. Awesome, got it now .. thx Quote Link to comment Share on other sites More sharing options...
tnkhoa Posted December 20, 2022 Share Posted December 20, 2022 I am trying to send command 7e 01 01 00 fe 0d to BMS in c+ programming language. But all that sin received was silence. I have reached the end. Hope to get help from you Quote Link to comment Share on other sites More sharing options...
mkran6220 Posted January 25, 2023 Share Posted January 25, 2023 On 2022/07/12 at 3:08 AM, SolarConvert said: Hi all, I thought I would document my findings of the Tian Power BMS protocol used in my Narada 48NPFC100 battery whilst I was attempting to establish why it did not work with my Deye inverter. Of note is that the exact same battery model is known to use a Shinwa BMS as well, but mine uses a Tian Power one, specifically TP-ND1530. Firstly I wanted to thank @shanghailoz for documenting the same thing here for the Revov batteries, which also use a Tian Power BMS, without which I would have struggled a lot more. Also thank you @zivva for pointing me in the right direction for the BMS manufacturer. I also peeked at the code of the BMS software to decipher how certain fields are calculated. The protocol follows. Preamble Firstly, all data sent appears to have a header, body and CRC check. For example: 7e 01 01 00 fe 0d The first byte is either 7c or 7e according to the software. In my case it is always 7e. The second byte is the DIP switch setting on the battery and corresponds to the following in the software: The third byte is the function code, which you will see several examples of below, such as "read data", "read BMS time", "read BMS version", "read serial number". The fourth byte is the length of the payload supplied as part of the function call - most "read" type functions will not include a payload and this parameter will be 00, but the "write" functions would have a payload. If there is a payload, it would follow this byte. The last two bytes are the CRC check. The response is structured in a similar way: 7e 01 01 56 ... f2 0d The first three bytes are an echo of what the request was, the fourth byte indicates the response length followed by the response bytes, and finally we have the CRC check. Function 1 - Read data 7e 01 01 00 fe 0d This allows you to read almost everything that is displayed on the first tab of the BMS software. The response will look as follows: 7e 01 01 56 01 0f 0c e1 0c e2 0c e2 0c e3 0c e3 0c e5 0c e5 0c e4 0c e4 0c e4 0c e2 0c e2 0c e2 0c e3 0c e5 02 01 75 30 03 01 16 74 04 01 27 10 05 06 00 44 00 45 00 45 00 44 40 46 20 45 06 05 00 00 00 00 00 00 00 00 00 00 07 01 00 81 08 01 13 54 09 01 27 10 0a 01 00 00 f2 0d The response body for this function is a data map structured as follows: data entry index, number of high/low byte pairs, the high/low byte pairs. Data 1 - Cell count and cell voltages 01 0f 0c e1 0c e2 0c e2 0c e3 0c e3 0c e5 0c e5 0c e4 0c e4 0c e4 0c e2 0c e2 0c e2 0c e3 0c e5 The second byte is the number of cells - this is 15 for a 15 cell battery like mine or 16 cells for Revov. The individual cell voltages follow as high/low pairs which need to be divided by 1000, for example, 0ce1 maps to 3297 which means 3.297V. Since my battery has 15 cells, there would be 15 individual cell voltages. Data 2 - Current 02 01 75 30 This is a tricky one. 7530 hex maps to 30000 decimal. The BMS software works it out as: current_in_amps = (30000 - value_of_item_2) / 100 This item therefore has an offset of 30000 and a scaling factor of 0.01. So when the charge current is 0, the BMS will return 30000, therefore (30000 - 30000) / 100 = 0 amps. When the charge current is 10 amps, the BMS would return 29000, therefore (30000 - 29000) / 100 = 10 amps. When the charge current is -10 amps, the BMS would return 31000, therefore (30000 - 31000) / 100 = -10 amps. Furthermore, charge/discharge time remaining is calculated based on max battery capacity in amps divided by current in amps. Data 3 - Remaining capacity 03 01 16 74 This value needs to be divided by 100 to get Ah. In this case I have 1674 hex or 5748 decimal, so 57.48Ah left of my 100Ah battery, therefore the SOC is 57.48%. Data 4 - Full capacity 04 01 27 10 Divide by 100 to get full battery capacity in Ah. 2710 in hex is 10000 in decimal, so I have a 100Ah battery. Data 5 - Temperatures 05 06 00 44 00 45 00 45 00 44 40 46 20 45 In my case there are 6 temperatures corresponding to the following screenshot: In the Revovs the number of temperatures listed are different. The last two appear to be important ones. Each temperature value is calculated as: temp_value = (bms_temp_value & 0xFF) - 50 Data 6 - Alarm bits 06 05 00 00 00 00 00 00 00 00 00 00 These can be mapped to (a) very specific alarm code(s). I can get these if there is interest. Data 7 - Cycles 07 01 00 81 This represents the number of cycles of the battery, so 129 in my case. Data 8 - Voltage 08 01 13 54 The total voltage of the battery. The number needs to be divided by 100, so 49.48V in my case. Data 9 - State of Health 09 01 27 10 The SOH of the battery. Needs to be divided by 100, so 100% in my case. Data 10 - ALM bytes 0a 01 00 00 This corresponds to the ALM light on the battery and what it means. Function 67 - Read protection parameters Request: 7e 01 43 00 fe 0d Response: 7e 01 43 94 00 02 0e 10 01 02 03 e8 02 02 0d ac 03 02 0a f0 04 02 03 e8 05 02 0c 1c 06 02 15 4a 07 02 03 e8 08 02 14 c8 09 02 11 94 0a 02 03 e8 0b 02 13 88 0c 02 1f 40 0d 02 03 e8 0e 02 1b 58 0f 02 1f 40 10 02 03 e8 11 02 1b 58 12 02 00 69 13 02 0f a0 14 02 00 5f 15 02 00 32 16 02 0f a0 17 02 00 3c 18 02 00 69 19 02 0f a0 1a 02 00 5f 1b 02 00 32 1c 02 0f a0 1d 02 00 3c 1e 02 00 8c 1f 02 0f a0 20 02 00 87 21 02 00 0a 22 02 00 0f 23 02 03 20 24 02 01 f4 de 0d This is another data map which ultimately gets translated into the following, with the necessary scaling factors applied: { "cell_ov_start": 3.6, "cell_ov_delay": 1000, "cell_ov_stop": 3.5, "cell_uv_start": 2.8, "cell_uv_delay": 1000, "cell_uv_stop": 3.1, "pack_ov_start": 54.5, "pack_ov_delay": 1000, "pack_ov_stop": 53.2, "pack_uv_start": 45.0, "pack_uv_delay": 1000, "pack_uv_stop": 50.0, "charge_oc_start": 80.0, "charge_oc_delay": 1000, "charge_oc_stop": 70.0, "discharge_oc_start": 80.0, "discharge_oc_delay": 1000, "discharge_oc_stop": 70.0, "cell_ot_start": 55, "cell_ot_delay": 4000, "cell_ot_stop": 45, "cell_ut_start": 0, "cell_ut_delay": 4000, "cell_ut_stop": 10, "env_ot_start": 55, "env_ot_delay": 4000, "env_ot_stop": 45, "env_ut_start": 0, "env_ut_delay": 4000, "env_ut_stop": 10, "mos_ot_start": 90, "mos_ot_delay": 4000, "mos_ot_stop": 85, "capacity_low_start": 10, "capacity_low_stop": 15, "volt_diff_start": 800, "volt_diff_stop": 500 } Function 51 - Read BMS version The request is as follows: 7e 01 33 00 fe 0d The response body is a string, such as: 7e 01 33 18 54 50 2d 4e 44 31 35 33 30 2d 31 35 53 31 30 30 41 2d 56 31 2e 30 2e 30 2e 0d Which in my case maps to: TP-ND1530-15S100A-V1.0.0 Function 66 - Read PCB barcode Request: 7e 01 42 00 fc 0d Response body is a string as above. Function 220 - Read serial number Request: 7e 01 dc 03 06 00 00 c2 0d Response body is a string as above. Function 69 - Read BMS time Request: 7e 01 45 00 fe 0d Response: 7e 01 45 06 16 07 08 14 3b 16 48 0d To convert the above to a valid date time, prepend "20" and then concatenate the rest of the bytes which represent yy, MM, dd, hh, mm, ss, so in the above example: 2022-07-08 20:59:22. There are many "write"-type functions too but I would be wary of using those. I hope the above is useful to someone. I will post more useful ones if I find any. I want Alarm code message. from Alarm code byte and Alarm bit. thank you Quote Link to comment Share on other sites More sharing options...
idiot-ranch Posted March 18, 2023 Share Posted March 18, 2023 Thanks so much for this post @SolarConvert – this was key in getting prometheus hooked up to my system. Have you (or anybody else here) been able to reverse engineer the CRC or checksum algorithm? I've spent hours on this and have been totally defeated. Right now I have a hardcoded map of requests I care about for each battery address, which I just copied from the software... but it's a very dirty way to go about things. Quote Link to comment Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.