Skip to content

Conversation

@wolfmon
Copy link

@wolfmon wolfmon commented Aug 5, 2024

Added Megarevo Inverter Defination

(Mostly via solarman app pcap, so might not be 100% accurate)

Confirmed most registers in this commit working on 15KW 3 phase hybrid model (R15k3H) with Solarman V5 Wifi Logger, more fields to be added and validated.
image

Sidenote: A potential interesting V2G/V2H/V2L feature this can enable:

Still a PoC, but I am able to get the inverter to relatively safely? to connect and disconnect an EV to be used as energy storage battery, EV HV battery can both be charged and discharged via inverter, tested with GB/T 20234 connector (maybe this will work with ChaDeMo too? yet to be tested), inverter has issue detecting disconnected battery: when battery is disconnected, inverter still tries to energize battery bus with lethal DC voltage (tires to charge battery), this can be addressed by enabling BMS communication on the inverter to inject a fault so the inverter will stop putting voltage on battery bus, this can be done with the following so that the connector and cable can be removed safely:
image
image

USE PROPER PPE, THIS SETUP IS NOT 100% SAFE YET, DO NOT TRY THIS AT HOME

`modbus.send_raw_modbus_frame_parsed(b"\x01\x10\x34\x0f\x00\x01\x02\x00\x02\x52\xad")`
# DEBUG:pysolarmanv5.pysolarmanv5:SENT: a5 1a 00 10 45 FF FF FF FF FF FF 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 10 34 0f 00 01 02 00 02 52 ad f6 15 (changed a few random bits to redact my SN, checksum will not match)
# DEBUG:pysolarmanv5.pysolarmanv5:RECD: a5 16 00 10 15 FF FF FF FF FF FF 02 01 07 2b 06 00 ff 17 00 00 f7 08 91 66 01 10 34 0f 00 01 3f fa d3 15  (changed a few random bits to redact my SN, checksum will not match)

Inverter mode can also be changed to force charge the car HV battery via grid (storm watch mode) or only via PV (like Tesla's drive on sunshine), but was not able to write with the current HA_solarman service call, any help would be appreciated (maybe create an separate issue for this?).

@davidrapan
Copy link

Hey @wolfmon, I also added your profile into my integration: davidrapan/ha-solarman

Hope you don't mind! 😉

Have fun!

@jabezz
Copy link

jabezz commented Dec 3, 2024

This Megarevo definition works great, it really should get merged into the repo.

Any luck on writing the Inverter/System mode back to the inverter yet?

@davidrapan
Copy link

It's not that much about this being merged or not but rather maintaner doesn't get back to this repo that often..

Any luck on writing the Inverter/System mode back to the inverter yet?

If you tell me more (registers, values, etc) I can add it into mine. 😉

@wolfmon
Copy link
Author

wolfmon commented May 1, 2025

This Megarevo definition works great, it really should get merged into the repo.

Any luck on writing the Inverter/System mode back to the inverter yet?

Yes, was able to write mode and other settings back to the inverter, Solarman actually support this natively, here is a snippet I used to control the inverter mode and battery type over WiFi, I use it to control my EV's DC fast charge/discharge. Works quiet well.

`def inverter_battery_mode_process(mode):
    """
    Process to set the inverter battery mode.
    Retries every 10 seconds until confirmation is received or desired mode changes.
    mode should be "lead-acid" or "li-ion".
    """
    global desired_inverter_mode
    expected_value = 1 if mode == "lead-acid" else 2 if mode == "li-ion" else None
    if expected_value is None:
        logging.error("[INVERTER] Invalid battery mode specified.")
        return
    query_frame = b"\x01\x03\x34\x01\x00\x1e\x9a\x32"
    while True:
        if desired_inverter_mode != mode:
            logging.info(f"[INVERTER] Aborting battery mode switch to {mode} (desired: {desired_inverter_mode}).")
            return
        modbus = None
        try:
            logger_sn = int(DC_OUTPUT_LOGGER_SN)
            modbus = PySolarmanV5(DC_OUTPUT_LOGGER_IP, logger_sn,
                                   port=8899, mb_slave_id=1, verbose=True)
            if mode == "lead-acid":
                cmd_frame = b"\x01\x10\x34\x0f\x00\x01\x02\x00\x01\x12\xac"
            elif mode == "li-ion":
                cmd_frame = b"\x01\x10\x34\x0f\x00\x01\x02\x00\x02\x52\xad"
            else:
                logging.error("[INVERTER] Invalid battery mode specified.")
                return
            resp_set = modbus.send_raw_modbus_frame_parsed(cmd_frame)
            logging.info(f"[INVERTER] Battery mode command for {mode} sent; response: {resp_set}")
            resp_query = modbus.send_raw_modbus_frame_parsed(query_frame)
            logging.info(f"[NETWORK] Inverter query response: {resp_query}")
            if isinstance(resp_query, list) and len(resp_query) > 14:
                mode_value = resp_query[14]
                if mode_value == expected_value:
                    logging.info(f"[INVERTER] Battery mode set to {mode} confirmed (value: {mode_value}).")
                    return
                else:
                    logging.info(f"[INVERTER] Battery mode not confirmed; expected {expected_value}, got {mode_value}.")
            else:
                logging.error(f"[NETWORK] Unexpected response length: {resp_query}")
        except Exception as e:
            logging.error("[INVERTER] Error in battery mode process: " + str(e))
        finally:
            try:
                if modbus is not None and hasattr(modbus, "client") and modbus.client is not None:
                    modbus.client.close()
                del modbus
            except Exception as e:
                logging.error("[INVERTER] Error during modbus cleanup: " + str(e))
        for i in range(10):
            time.sleep(1)
            if desired_inverter_mode != mode:
                logging.info(f"[INVERTER] Aborting retry for battery mode {mode} due to mode change.")
                return

# ---------------------------
# Inverter Operation Mode Process Function (renamed)
# ---------------------------
def inverter_operation_mode_process(mode_value):
    """
    Process to set the inverter operation mode.
    Retries every 10 seconds until confirmation is received or desired mode changes.
    Valid mode values: 0 for self-sustain, 2 for battery priority.
    """
    global desired_inverter_opmode
    expected_value = mode_value
    query_frame = b"\x01\x03\x34\x00\x00\x01"
    crc_bytes = calculate_crc(query_frame)
    query_frame = query_frame + crc_bytes
    while True:
        if desired_inverter_opmode != mode_value:
            logging.info(f"[OP_MODE] Aborting op mode switch to {mode_value} (desired: {desired_inverter_opmode}).")
            return
        modbus = None
        try:
            logger_sn = int(DC_OUTPUT_LOGGER_SN)
            modbus = PySolarmanV5(DC_OUTPUT_LOGGER_IP, logger_sn,
                                   port=8899, mb_slave_id=1, verbose=True)
            value_bytes = b"\x00\x00" if mode_value == 0 else b"\x00\x02" if mode_value == 2 else None
            if value_bytes is None:
                logging.error("[OP_MODE] Invalid op mode value specified.")
                return
            cmd_frame = b"\x01\x10\x34\x00\x00\x01\x02" + value_bytes
            crc_bytes = calculate_crc(cmd_frame)
            cmd_frame = cmd_frame + crc_bytes
            resp_set = modbus.send_raw_modbus_frame_parsed(cmd_frame)
            logging.info(f"[OP_MODE] Operation mode command sent, value {mode_value}; response: {resp_set}")
            query_frame = b"\x01\x03\x34\x00\x00\x01"
            crc_bytes = calculate_crc(query_frame)
            query_frame = query_frame + crc_bytes
            resp_query = modbus.send_raw_modbus_frame_parsed(query_frame)
            logging.info(f"[NETWORK] Inverter op mode query response: {resp_query}")
            if isinstance(resp_query, list) and len(resp_query) > 0:
                op_mode_value = resp_query[0]
                if op_mode_value == expected_value:
                    logging.info(f"[OP_MODE] Inverter op mode set to {mode_value} confirmed (value: {op_mode_value}).")
                    return
                else:
                    logging.info(f"[OP_MODE] Operation mode not confirmed; expected {expected_value}, got {op_mode_value}.")
            else:
                logging.error(f"[NETWORK] Unexpected op mode response length: {resp_query}")
        except Exception as e:
            logging.error("[OP_MODE] Error in op mode process: " + str(e))
        finally:
            try:
                if modbus is not None and hasattr(modbus, "client") and modbus.client is not None:
                    modbus.client.close()
                del modbus
            except Exception as e:
                logging.error("[OP_MODE] Error during modbus cleanup: " + str(e))
        for i in range(10):
            time.sleep(1)
            if desired_inverter_opmode != mode_value:
                logging.info(f"[OP_MODE] Aborting retry for op mode {mode_value} due to mode change.")
                return` 

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants