Reading data from a Solax X1 Hybrid Gen4 & HybridX Inverters

I put together a little script to read from the Solax X1 Hybrid Gen4 Inverter local API to forward to Emoncms. I would like to integrate this in emonhub in due course but here’s a basic functioning initial implementation:

import requests
import json
import time

def to_signed(val):
    if val > 0x7FFF:
        val -= 2**16
    return val

def pack_u16(a, b):
    return (b << 16) + a

def read_from_solax(url, password):
    data = {
        'optType': 'ReadRealTimeData',
        'pwd': password
    }
    try:
        response = requests.post(url, data=data)
    except requests.exceptions.RequestException as ex:
        return None
   
    # Data example 
    #  '{"sn":"SERIAL_NUMBER","ver":"3.003.02","type":15,"Data":[2498,38,928,5005,1688,1691,25,33,426,569,2,7887,0,27,12620,0,0,26,98,1443,0,2195,0,56,100,0,30,3870,0,0,0,0,809,0,50740,0,4576,0,119,36,256,1314,900,30,350,203,189,36,35,31,1,1,8,0,8998,0,64608,65535,65387,65535,0,0,0,0,0,0,0,0,0,0,397,0,62240,65535,1800,0,63736,65535,110,0,40,0,0,0,0,44,9,25,0,0,220,0,180,0,0,0,0,0,0,13831,1036,5895,603,258,9766,0,0,0,0,0,1,1290,0,0,3561,3546,45080,5,21302,14389,18757,12354,16697,12360,14384,21302,14389,18757,12354,16697,12360,14384,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,3585,258,770,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"Information":[3.000,15,"OTHER_SERIAL_NUMBER",8,1.29,0.00,1.27,1.04,0.00,1]}'
    
    # Decode Solar X1 Hybrid Gen4 response
    try: 
        data = json.loads(response.text)
    except ValueError as ex:
        return None

    if 'Data' not in data:
        print ('No data in response')
        return None

    data = data['Data']

    if len(data) < 40:
        print ('Unexpected data length')
        return None

    # From https://github.com/squishykid/solax/blob/master/solax/inverters/x1_hybrid_gen4.py
    return {
        'AC voltage R': data[0]*0.1,
        'AC current': data[1]*0.1,
        'AC power': to_signed(data[2]*1),
        'AC frequency': data[3]*0.01,
        'PV1 voltage': data[4]*0.1,
        'PV2 voltage': data[5]*0.1,
        'PV1 current': data[6]*0.1,
        'PV2 current': data[7]*0.1,
        'PV1 power': data[8]*1,
        'PV2 power': data[9]*1,

        'On-grid total yield': pack_u16(data[11],data[12])*0.1,
        'On-grid daily yield': data[13]*0.1,
        'Battery voltage': data[14]*0.01,
        'Battery current': data[15]*0.01,
        'Battery power': to_signed(data[16]*1),
        'Battery temperature': data[17]*1,
        'Battery SOC': data[18]*1,
        'Grid power': to_signed(data[32]*1),
        'Total feed-in energy': pack_u16(data[34],data[35])*0.01,
        'Total consumption': pack_u16(data[36],data[37])*0.01
    }

last_time = 0

while True:

    if (time.time() - last_time) > 10:
        last_time = time.time()

        # Read from Solax
        output = read_from_solax('http://192.168.0.39', 'SERIAL_NUMBER')
        
        print(output)

        if output is not None:
            emoncms_url = 'https://emoncms.org'

            # use requests to post data to emoncms
            try:
                requests.post(emoncms_url+'/input/post.json', data={
                    'apikey':  'WRITE_API_KWY',
                    'node': "solax",
                    'json': json.dumps(output)
                })
            except requests.exceptions.RequestException as ex:
                pass

    time.sleep(0.1)

This gives the following inputs in emoncms:

A little more detail on the AC power input processing:

PV2 power:

Battery power:

Grid power:

I was thinking perhaps I should do some of this input processing in the script above in order to simplify the input processing in emoncms… I might consider that…

MySolarBattery app

Here’s an example of reading from a Solax HybridX inverter:

import requests
import json
import time

ip = "192.168.1.61"

"""
 Example of data
 {"method":"uploadsn","version":"Solax_SI_CH_2nd_VERSION_NUM","type":"AL_SE","SN":"SERIAL_NUMBER","Data":[2.2,0.0,390.9,0.0,3.5,248.4,700,36,1.0,7722.2,737,859,0,47.96,-0.08,-4,5,,0.0,3347.7,,,,,,,,,,,,,,,,,,,,,,0.00,0.00,,,,,,,,50.02,,,0.0,0.0,0,0.00,0,0,0,0.00,0,8,0,0,0.00,0,8],"Status":"2"}
 """

def read_from_solax(ip):
    # Set IP address of Solax inverter here
    url = 'http://'+ip+':80/api/realTimeData.htm'

    try:
        response = requests.get(url)
    except requests.exceptions.RequestException as ex:
        return None

    try: 
        raw_json = response.text.replace(",,", ",0.0,").replace(",,", ",0.0,")
    except:
        return None

    try:
        data = json.loads(raw_json)
    except ValueError as ex:
        return None

    # Solax HybridX response decoder
    # thanks to:
    # https://github.com/squishykid/solax/blob/master/solax/inverters/x_hybrid.py

    decoder = {
        "PV1 Current": (0, 'A'),
        "PV2 Current": (1, 'A'),
        "PV1 Voltage": (2, 'V'),
        "PV2 Voltage": (3, 'V'),
        "Output Current": (4, 'A'),
        "Network Voltage": (5, 'V'),
        "Power Now": (6, 'W'),
        "Inverter Temperature": (7, 'C'),
        "Today's Energy": (8, 'kWh'),
        "Total Energy": (9, 'kWh'),
        "Exported Power": (10, 'W'),
        "PV1 Power": (11, 'W'),
        "PV2 Power": (12, 'W'),
        "Battery Voltage": (13, 'V'),
        "Battery Current": (14, 'A'),
        "Battery Power": (15, 'W'),
        "Battery Temperature": (16, 'C'),
        "Battery Remaining Capacity": (17, '%'),
        "Month's Energy": (19, 'kWh'),
        "Grid Exported Energy": (41, 'kWh'),
        "Grid Imported Energy": (42, 'kWh'),
        "Grid Frequency": (50, 'Hz'),
        "EPS Voltage": (53, 'V'),
        "EPS Current": (54, 'A'),
        "EPS Power": (55, 'W'),
        "EPS Frequency": (56, 'Hz')
    }

    output = {}
    for key, value in decoder.items():
        output[key] = data['Data'][value[0]]

    return output

last_time = 0

while True:

    if (time.time() - last_time) > 10:
        last_time = time.time()

        output = read_from_solax(ip)
        
        print(output)

        if output is not None:
            emoncms_url = 'https://emoncms.org'

            # use requests to post data to emoncms
            try:
                requests.post(emoncms_url+'/input/post.json', data={
                    'apikey':  'EMONCMS_API_KEY',
                    'node': "solax",
                    'json': json.dumps(output)
                })
            except requests.exceptions.RequestException as ex:
                pass

    time.sleep(0.1)

data output:

{'PV1 Current': 2.5, 'PV2 Current': 0.0, 'PV1 Voltage': 394.6, 'PV2 Voltage': 0.0, 'Output Current': 4.2, 'Network Voltage': 245.8, 'Power Now': 832, 'Inverter Temperature': 36, "Today's Energy": 1.5, 'Total Energy': 7722.7, 'Exported Power': 201, 'PV1 Power': 986, 'PV2 Power': 0, 'Battery Voltage': 47.94, 'Battery Current': -0.1, 'Battery Power': -5, 'Battery Temperature': 5, 'Battery Remaining Capacity': 0.0, "Month's Energy": 3347.7, 'Grid Exported Energy': 0.0, 'Grid Imported Energy': 0.0, 'Grid Frequency': 49.96, 'EPS Voltage': 0.0, 'EPS Current': 0.0, 'EPS Power': 0, 'EPS Frequency': 0.0}

Wow that looks amazing.

Way over my head now :rofl:

1 Like

Cheers @Ken_Bone

I’ve included the above code in emonhub now, testing in the solax_hybridx branch: GitHub - openenergymonitor/emonhub at solax_hybridx.

This is running now on your pi Ken :laughing:

I’ve created the following feeds for you on emoncms.org, not sure what’s with the Battery Remaining Capacity = 0 on the inputs page?

There is no battery connected ATM.

Thank you so much, I know you guys are really busy

1 Like

I really like this look

1 Like

Hi has anybody found that they can not change settings on their solax inverter after dark? I have been playing with trying to adjust the amount of electricity I buy in over night based on the solcast forecast for my house, and I am finding that I have script which works well if I run it during the day but if I run it at night does nothing.
This evening I have also noticed that once the sun goes down I can not change inverter settings from the Solax App, it just returns failed.