warhammerkid / bluetti_mqtt

MQTT interface for Bluetti power stations
MIT License
139 stars 52 forks source link

Advanced MODBUS discovery tools #116

Open igor-podpalchenko opened 1 month ago

igor-podpalchenko commented 1 month ago

Hi everyone

I've created a set of tools for better MODBUS register analysis.

1.) Script for BLUETTI Modbus discovery-log parsing to ReadHoldingRegisters parameters. Discovers available ranges. https://gist.github.com/igor-podpalchenko/ee2f919881cc554db70026e803820df6

2.) BLUETTI raw data recorder, records data to text file. It should be run as systemd service. Its output will be used as data input for the next scripts. https://gist.github.com/igor-podpalchenko/010602542441ad35f351d2da98c5858d

3.) All registers change viewer (color codes the changed registers). https://gist.github.com/igor-podpalchenko/9fee7d3e82ed1b315f728d1dac40e188

4.) Specific registers change viewer (with register index offset finder). https://gist.github.com/igor-podpalchenko/35ed3c0c14da59d727b1dd65e79feb9a

The final output results look like:

Pasted Graphic 22 6000, hex 01

And some small present, protocol version 2 discovery & analysis results. AC70P device config.

   # Core (100)
    self.struct.add_uint_field('total_battery_percent', 102)
    self.struct.add_decimal_field('estimated_time_hr', 104,1)
    self.struct.add_swap_string_field('device_type', 110, 6)
    self.struct.add_sn_field('serial_number', 116)
    self.struct.add_decimal_field('power_generation', 154, 1)  # Total power generated since last reset (kwh)
    self.struct.add_sn_field('serial_number', 1107)
    self.struct.add_uint_field('dc_output_power', 140) #y
    self.struct.add_uint_field('ac_output_power', 142) #y
    self.struct.add_uint_field('dc_input_power', 144) #y
    self.struct.add_uint_field('ac_input_power', 146) #y  I keep forgetting to test this one.

    self.struct.add_decimal_field('power_generation', 1202, 1)  # Total power generated since last reset (kwh)

    # Input Details (1100 - 1300)
    self.struct.add_swap_string_field('device_type', 1101, 6)
    self.struct.add_sn_field('serial_number', 1107)
    self.struct.add_uint_field('num_packs_connected', 1209)
    self.struct.add_bool_field('charging_from_internal_dc', 1210)
    self.struct.add_uint_field('internal_dc_input_power', 1212)
    self.struct.add_decimal_field('internal_dc_input_voltage', 1213, 1)
    self.struct.add_decimal_field('internal_dc_input_current', 1214, 1)
    self.struct.add_bool_field('charging_from_pack_dc', 1218)
    self.struct.add_uint_field('pack_dc_input_power', 1220)
    self.struct.add_decimal_field('pack_dc_input_voltage', 1221, 1)
    self.struct.add_decimal_field('pack_dc_input_current', 1222, 1)
    self.struct.add_decimal_field('ac_input_frequency', 1300, 1)
    self.struct.add_uint_field('internal_ac_input_power', 1313)
    self.struct.add_decimal_field('ac_input_voltage', 1314, 1)
    self.struct.add_decimal_field('ac_input_current', 1315, 1)

    # Output Details (1400 - 1500)
    self.struct.add_uint_field('total_dc_output_power', 1400)
    self.struct.add_uint_field('dc_usb_output_power', 1404)
    self.struct.add_uint_field('dc_12v_output_power', 1406)
    self.struct.add_uint_field('dc_output_uptime_minutes', 1410)
    self.struct.add_uint_field('ac_output_power', 1420)
    self.struct.add_uint_field('ac_output_uptime_minutes', 1424)
    self.struct.add_uint_field('ac_output_power', 1430)
    self.struct.add_decimal_field('ac_output_frequency', 1500, 1)
    #self.struct.add_bool_field('ac_output_on', 1509)
    self.struct.add_uint_field('battery_inputoutput_power', 1510)
    self.struct.add_decimal_field('ac_output_voltage', 1511, 1)
    self.struct.add_decimal_field('ac_output_amps', 1512, 1)

    # Controls (2000)
    self.struct.add_bool_field('ac_output_on', 2011)
    self.struct.add_bool_field('dc_output_on', 2012)
    self.struct.add_bool_field('dc_eco_on', 2014)
    self.struct.add_uint_field('dc_eco_hours', 2015)
    self.struct.add_uint_field('dc_eco_watts', 2016)
    self.struct.add_bool_field('ac_eco_on', 2017)
    self.struct.add_uint_field('ac_eco_hours', 2018)
    self.struct.add_uint_field('ac_eco_watts', 2019)
    self.struct.add_enum_field('charging_mode', 2020, ChargingMode)
    self.struct.add_bool_field('power_lifting_on', 2021)

    # More Controls (2200)
    self.struct.add_bool_field('grid_enhancement_mode_on', 2225)

    # Battery Data Register (6000)
    #self.struct.add_decimal_field('total_battery_voltage', 6003, 2)

    # Battery Data Register (6100)
    self.struct.add_swap_string_field('battery_type', 6101, 6)
    self.struct.add_sn_field('pack_serial_number', 6107)
    #self.struct.add_decimal_field('pack_voltage', 6111, 2)
    self.struct.add_decimal_field('bms_output_voltage', 6111, 2)
    self.struct.add_decimal_field('bms_charge_voltage', 6116, 2)
    self.struct.add_decimal_field('bms_current_1', 6112, 1)

    #self.struct.add_uint_field('pack_battery_percent', 6113)
    self.struct.add_uint_field('bms_temp_1', 6120)
    self.struct.add_uint_field('bms_temp_2', 6121)
    self.struct.add_version_field('bcu_version', 6175)

    # Controls (2000)
    #self.struct.add_bool_field('ac_output_on', 2011) #y
    #self.struct.add_bool_field('dc_output_on', 2012) #y

@property
def polling_commands(self) -> List[ReadHoldingRegisters]:
    return [
        #ReadHoldingRegisters(10, 40),
        #ReadHoldingRegisters(70, 21),
        #ReadHoldingRegisters(100, 62),
        ReadHoldingRegisters(100, 50),
        ReadHoldingRegisters(1100, 51),
        ReadHoldingRegisters(1200, 90),
        ReadHoldingRegisters(1300, 31),
        ReadHoldingRegisters(1400, 48),
        ReadHoldingRegisters(1500, 30),
        ReadHoldingRegisters(2000, 67),
        ReadHoldingRegisters(2200, 29),
        ReadHoldingRegisters(6000, 31),
        ReadHoldingRegisters(6100, 100),
        ReadHoldingRegisters(6300, 52),
    ]

@property
def logging_commands(self) -> List[ReadHoldingRegisters]:
    return [
        #ReadHoldingRegisters(70, 66),
        #ReadHoldingRegisters(136, 74),
        #ReadHoldingRegisters(100, 62),
        #ReadHoldingRegisters(1100, 51),
        #ReadHoldingRegisters(1200, 90),
        #ReadHoldingRegisters(2000, 67),
        #ReadHoldingRegisters(2200, 29),
        ReadHoldingRegisters(100, 50),
        ReadHoldingRegisters(1100, 51),
        ReadHoldingRegisters(1200, 90),
        ReadHoldingRegisters(1300, 31),
        ReadHoldingRegisters(1400, 48),
        ReadHoldingRegisters(1500, 30),
        ReadHoldingRegisters(2000, 67),
        ReadHoldingRegisters(2200, 29),
        ReadHoldingRegisters(6000, 31),
        ReadHoldingRegisters(6100, 100),
        ReadHoldingRegisters(6300, 52),
    ]

@property
def writable_ranges(self) -> List[range]:
    return [range(2000, 2225)]

It would be good if someone could integrate my findings into the repo.

ftrueck commented 1 month ago

Would you be willing to contribute to my fork of this repo?

igor-podpalchenko commented 1 month ago

Yep, go ahead. It's up to you to add me in coauthors list.

On 18 Jul 2024, at 13:34, Florian Trück @.***> wrote:

Would you be willing to contribute to my fork of this repo?