Modbus Profile Mapping to SolarEdge Inverter

Discussion in 'C-Bus Automation Controllers' started by pspeirs, May 1, 2019.

  1. pspeirs

    pspeirs

    Joined:
    Nov 23, 2013
    Messages:
    138
    Likes Received:
    5
    Location:
    Sydney
    Hi All,

    Has anyone played with setting up profiles for mapping to a SolarEdge inverter, or perhaps can mke some suggestions. BTW, anyone with an SE5000 be aware that the second RS485 port is hardware disabled an it is mnot possible to enable it even though the settings can be configured :) So, the switch to TCP/IP Modbus.

    I've set up a profile based on the information in the sunspec specification notes (https://www.solaredge.com/sites/default/files/sunspec-implementation-technical-note.pdf)

    however, any results that are returned don't appear to be valid. I'm sure there is somewhere I'm going wrong with this.

    Another thing, it doesn't seem possible to simple update the profile, or if the profile is deleted and readded, the device doesn't pick up the changes, so I need to delete both the device and profile before re adding.

    Code:
    {
        "manufacturer": "SolarEdge",
        "name": "SE5000",
        "product_range": "SE",
        "description": "SolarEdge SE5000 Inverter",
        "mapping": [
            {
                "name": "I_AC_Power",
                "bus_datatype": "uint32",
                "type": "register",
                "address": 40083,
                "datatype": "uint16",
                "units": "W"
            },
            {
                "name": "I_Status",
                "bus_datatype": "uint32",
                "type": "register",
                "address": 40108,
                "datatype": "uint16"
            },
            {
                "name": "M_AC_Freq",
                "bus_datatype": "int32",
                "type": "register",
                "address": 40204,
                "datatype": "int16"
            }
        ]
    }
     
    pspeirs, May 1, 2019
    #1
    1. Advertisements

  2. pspeirs

    znelbok

    Joined:
    Aug 3, 2004
    Messages:
    1,112
    Likes Received:
    11
    What are the results you are seeing that you think are not valid?

    Have you tried the adjacent address? It is base 0 or base 1 addressing. You appear to have taken this into consideration by changing the address by one (40083 is I_AC_Voltage_SF and 40084 is I_AC_Power)

    Not familiar with these units but know a smidge of MODBUS.

    Try a MODBUS test utility. I use MODBUSViewTCP. Its free for 15 minutes. There are others, just google them. Its a way to do a sanity check on what you are seeing.
     
    znelbok, May 2, 2019
    #2
    1. Advertisements

  3. pspeirs

    pspeirs

    Joined:
    Nov 23, 2013
    Messages:
    138
    Likes Received:
    5
    Location:
    Sydney
    Hey Mick,

    I've cleaned up the profile to a consecutive list of registers as shown below and the values returned are as below. I'll download your suggested test app and try it when I get home. As you can see from the values below, doesn't seem to make much sense, even when using the consecutive registers in case of the offsets.

    upload_2019-5-2_12-28-42.png


    Cheers,
    Paul
     
    pspeirs, May 2, 2019
    #3
  4. pspeirs

    Ashley

    Joined:
    Dec 1, 2005
    Messages:
    972
    Likes Received:
    73
    Location:
    Adelaide, Australia
    Just guessing but I assume you have a single phase inverter. The offsets are out by 1, so then first one is AC current which would make the last on the current scale factor. For a single phase inverter, current and current_A would be the same (they are!). Current B and C are not implemented so would probably return the maximum value for a 16 bit number (NOT_IMPLEMENTED) which is 65535. The scale factor is a signed integer so 65534 is actually -2. applying that to the current you get 6.43A. Would that be correct?
     
    Ashley, May 2, 2019
    #4
  5. pspeirs

    pspeirs

    Joined:
    Nov 23, 2013
    Messages:
    138
    Likes Received:
    5
    Location:
    Sydney
    So far so good, the util mentioned above allowed me to scan a bunch of addresses once I found the correct ID. Frequency and its scaling factor are correct so I'll work on creating the profile for the other values. Yes Ashley, they are out by one.

    upload_2019-5-2_20-22-21.png


    Cheers,
    Paul
     
    pspeirs, May 2, 2019
    #5
  6. pspeirs

    znelbok

    Joined:
    Aug 3, 2004
    Messages:
    1,112
    Likes Received:
    11
    You have to watch the base addressing with Modbus. That's why I asked if it was base 0 or base 1 (offset is often used as well) becasue you can be out by 1 as you have just found out. Just watch your data types as well. Signed and unsigned ints as you see can be very different to the eye.
     
    znelbok, May 3, 2019
    #6
  7. pspeirs

    pspeirs

    Joined:
    Nov 23, 2013
    Messages:
    138
    Likes Received:
    5
    Location:
    Sydney
    My issue now is more around getting the meter values for each phase back. This is connected to the inverter via RS485 and while I can return the meter model for example, there are no values available for the phase values. Probably best to wait until I'm at home the same time as the sun is up though to be doubly sure, but I suspect its proprietry sent to the monitoring portal.

    I could use the CMU to measure each phase but the measurements compared to whats being returned by the inverter are out by 150-200 watts.
     
    pspeirs, May 3, 2019
    #7
  8. pspeirs

    pspeirs

    Joined:
    Nov 23, 2013
    Messages:
    138
    Likes Received:
    5
    Location:
    Sydney
    Speaking of mappings, there appear to be a number of items not adequately explained in the manual. For starters, the cbus data types in some of the installed profiles contain numbers lie 1009, 1001, etc. What do these translate to as the listing of data types in the scripting module does not define these.

    How does the value_custom tag work? For the below profile entry, is it saying to set the mapped value to true or false based on the string contained at address 3?

    Code:
    {
        "name": "Remote Temp Sensor Status",
        "bus_datatype": "bool",
        "type": "discreteinput",
        "address": 3,
        "value_custom": {
            "0": "OK",
            "1": "Fault"
        }
    }
    In the following piece, I assume we're reading 14 registers from starting from address. What is datatype 7, is this a string that each value gets parsed and thrown into?

    Code:
    {
        "value_bitmask": "0x7F00",
        "read_count": 14,
        "read_offset": 4,
        "value_custom": { "22", "PM1200" }.
        "bus_datatype": 7,
        "address": 80,
        "name": "Product Code",
        "datatype": "uint16"
    }


    Cheers,
    Paul
     
    pspeirs, May 6, 2019
    #8
  9. pspeirs

    pspeirs

    Joined:
    Nov 23, 2013
    Messages:
    138
    Likes Received:
    5
    Location:
    Sydney
    More help needed guys. After giving up with reading the meter via the inverter, I've connected directly to the meters RS485 port. I cn connect with Modbus Poll and read the correct registers, and running the test via the SHAC gives the following results.

    upload_2019-5-14_19-12-34.png

    which looks pretty good for the frequency. I'm having no luck configuring the profile to correctly read this (and other) regsters. I have tried the following without success.

    If someone can give me some guidance as I'm not convinced the documentation really explains it all that well. Would also be nice to see some examples that use all variations of the possible configuration options.

    Code:
            {
                "name": "Freq 1(1033)",
                "type": "register",
                "address": 41033,
                "read_count": 2,
                "read_swap":  true,
                "datatype": "float32",
                "bus_datatype": 14,
                "units": "Hz"
            },
            {
                "name": "Freq 2(1033)",
                "type": "register",
                "address": 41033,
                "read_count":  2,
                "datatype": "float32",
                "bus_datatype": 14,
                "units": "Hz"
            },
    
    Regs,
    Paul
     
    pspeirs, May 14, 2019
    #9
  10. pspeirs

    pspeirs

    Joined:
    Nov 23, 2013
    Messages:
    138
    Likes Received:
    5
    Location:
    Sydney
    I do seem to be talking to myself quite a bit lately, nevertheless, it seems to work if the profile is set as follows.

    {
    "bus_datatype": 14,
    "address": 1032,
    "type": "register",
    "units": "Hz",
    "name": "Freq",
    "read_swap": true,
    "datatype": "float32"
    }

    So, even though the RTU Read Test wants the actual register number 1033, the profile still needs the base 0 number 1032. This is pretty confusing to have to work out. Anyway, frequency seems to work, time to plug the rest in.

    Cheers,
    Paul
     
    pspeirs, May 14, 2019
    #10
  11. pspeirs

    pspeirs

    Joined:
    Nov 23, 2013
    Messages:
    138
    Likes Received:
    5
    Location:
    Sydney
    Sorted out the modbus profile .json file for SolarEdge inverters and the WattNode WND-3Y-400-MB meter that works with the inverter via the first RS485 port. The second port is not connected and cannot be used, and unfortunately no one will tell you this little gem of info, so was unable to connect the second port to the SHAC. Since the wifi module was installed and talking to the Solaredge web portal, the LAN port was available. Plugged that into the network and set up the modbus configuration using the attached profile file.

    Set the mappings, and most of the User Parameter objects will be of type float for obvious reasons.

    upload_2019-7-13_9-32-12.png

    And using some images, etc you can end up with a display much like the SolarEdge one. The difference of course is that you can make some smart decisions in logic rather than just looking at a useless online portal.

    upload_2019-7-13_9-36-1.png


    upload_2019-7-13_9-36-34.png

    Using some of the code below can give you some readable values.

    Code:
    -- ****** POWER TOTALS ******
    local i_ac_power = GetUserParam('Local Network', 'I_AC_Power')                                        -- Inverter Generated Power (W)
    --local m_ac_power = math.abs(GetUserParam('Local Network', 'M_AC_Power'))                            -- Meter Power (adjusted to positive value) (W)
    local m_ac_power = GetUserParam('Local Network', 'M_AC_Power')
    local m_ac_power_a = GetUserParam('Local Network', 'M_AC_Power_A')                        -- Meter Phase A Power (W)
    local m_ac_power_b = GetUserParam('Local Network', 'M_AC_Power_B')                                    -- Meter Phase B Power (W)
    local m_ac_power_c = GetUserParam('Local Network', 'M_AC_Power_C')                                    -- Meter Phase C Power (W)
    
    -- Calculate phase A minus any solar
    local m_ac_power_a_real = m_ac_power_a - i_ac_power
    
    local load_ac_power = math.abs(i_ac_power - m_ac_power)                                                        -- Calculate the load (house) = inverter power minus grid power
    --log(string.format('Real A: %f   Solar: %f   Grid: %f   Load: %f', m_ac_power_a_real, i_ac_power, m_ac_power, load_ac_power))
        
        
    if(m_ac_power <= 0) then
        SetUserParam('Local Network', 'AC_Power_Self_Consumption_KW', m_ac_power / 1000)
    else
        SetUserParam('Local Network', 'AC_Power_Self_Consumption_KW', (i_ac_power - GetUserParam('Local Network', 'M_AC_Power_A')) / 1000)
    end   
        
        
        
    SetUserParam('Local Network', 'I_AC_Power_KW', math.abs(i_ac_power) / 1000)                            -- Save Inverter Generated Power (kW)
    SetUserParam('Local Network', 'M_AC_Power_A_KW', math.abs(m_ac_power_a) / 1000)                        -- Save Meter Phase A Power (kW)
    SetUserParam('Local Network', 'M_AC_Power_B_KW', math.abs(m_ac_power_b) / 1000)                        -- Save Meter Phase B Power (kW)
    SetUserParam('Local Network', 'M_AC_Power_C_KW', math.abs(m_ac_power_c) / 1000)                        -- Save Meter Phase C Power (kW)
    SetUserParam('Local Network', 'M_AC_Power_A_Real', m_ac_power_a_real)                        -- Save Meter Phase C Power (kW)
    SetUserParam('Local Network', 'M_AC_Power_A_Real_KW', math.abs(m_ac_power_a_real) / 1000)                        -- Save Meter Phase C Power (kW)
    
    
    -- Parameters to display on images
    SetUserParam('Local Network', 'Load_AC_Power', load_ac_power)                                        -- Save the load value (W)
    SetUserParam('Local Network', 'Load_AC_Power_KW', math.abs(load_ac_power) / 1000)                    -- Save the load value (kW)
    SetUserParam('Local Network', 'M_AC_Power_KW', math.abs(m_ac_power) / 1000)                            -- Save Meter Power (adjusted to positive value) (kW)
    
    
    --If inverter is generating power then set direction from inverter to load (house
    if (i_ac_power == 0) then                                                                            -- Inverter is NOT generating power
      SetUserParam('Local Network', 'energy_pv_load_direction', 0)
    else                                                                                                -- Inverter is generating power
      SetUserParam('Local Network', 'energy_pv_load_direction', 1)
    end
    
    
    -- Set the direction of power flow between load (house/inverter) and grid
    if (m_ac_power > 0) then                                                                           
        SetUserParam('Local Network', 'energy_load_grid_direction', 1)    -- Exporting to Grid
    else
        SetUserParam('Local Network', 'energy_load_grid_direction', 2)    -- Importing from grid
    end
    
    
    
    
    -- ****** PRODUCED ENERGY TOTALS FOR PERIOD ******
    local i_ac_energy_wh = GetUserParam('Local Network', 'I_AC_Energy_WH')                                -- Inverter Accumulated Energy (Wh)
    SetUserParam('Local Network', 'I_AC_Energy_MWH', math.abs(i_ac_energy_wh) / 1000000)                -- Set all time production (MWh)
    
    energy_total_yearly = i_ac_energy_wh - GetUserParam('Local Network', 'Energy_Total_Year_Previous')    -- Inverter Accumulated Yearly Energy (Wh)
    SetUserParam('Local Network', 'Energy_Total_Year_Current', energy_total_yearly)                        -- Set yearly accumulative total (Wh)
    SetUserParam('Local Network', 'Energy_Total_Year_MWh', energy_total_yearly / 1000000)                -- Set yearly accumulative total (MWh)
    
    energy_total_month = i_ac_energy_wh - GetUserParam('Local Network', 'Energy_Total_Month_Previous')    -- Inverter Accumulated Monthly Energy (Wh)
    SetUserParam('Local Network', 'Energy_Total_Month_Current', energy_total_month)                        -- Set monthly accumulative total (Wh)
    SetUserParam('Local Network', 'Energy_Total_Month_kWh', energy_total_month / 1000)                    -- Set monthly accumulative total (kWh)
    
    energy_total_day = i_ac_energy_wh - GetUserParam('Local Network', 'Energy_Total_Day_Previous')        -- Inverter Accumulated Daily Energy (Wh)
    SetUserParam('Local Network', 'Energy_Total_Day_Current', energy_total_day)                            -- Set daily accumulative total (Wh)
    SetUserParam('Local Network', 'Energy_Total_Day_kWh', energy_total_day / 1000)                        -- Set daily accumulative total (kWh)
    
    
    
    
    -- ****** IMPORTED ENERGY TOTALS FOR PERIOD ******
    local m_imported = GetUserParam('Local Network', 'M_Imported')                                        -- Imported energy total (Wh)
    local m_imported_a = GetUserParam('Local Network', 'M_Imported_A')                                    -- Imported energy A phase (Wh)
    local m_imported_b = GetUserParam('Local Network', 'M_Imported_B')                                    -- Imported energy B phase (Wh)
    local m_imported_c = GetUserParam('Local Network', 'M_Imported_C')                                    -- Imported energy C phase (Wh)
    
    local m_imported_day = m_imported - GetUserParam('Local Network', 'M_Imported_Day_Previous')        -- Imported daily energy total (Wh)
    local m_imported_day_a = m_imported_a - GetUserParam('Local Network', 'M_Imported_Day_Previous_A')    -- Imported daily energy total phase A (Wh)
    local m_imported_day_b = m_imported_b - GetUserParam('Local Network', 'M_Imported_Day_Previous_B')    -- Imported daily energy total phase B (Wh)
    local m_imported_day_c = m_imported_c - GetUserParam('Local Network', 'M_Imported_Day_Previous_C')    -- Imported daily energy total phase C (Wh)
    
    -- *********** Save daily Imported Energy Values **********
    SetUserParam('Local Network', 'M_Imported_Day_Current', m_imported_day)                                -- Daily imported energy (Wh)
    SetUserParam('Local Network', 'M_Imported_Day_Current_KWH', m_imported_day / 1000)                    -- Daily imported energy (kWh)
    SetUserParam('Local Network', 'M_Imported_Day_Current_A', m_imported_day_a)                            -- Daily imported energy phase A (Wh)
    SetUserParam('Local Network', 'M_Imported_Day_Current_B', m_imported_day_b)                            -- Daily imported energy phase B (Wh)
    SetUserParam('Local Network', 'M_Imported_Day_Current_C', m_imported_day_c)                            -- Daily imported energy phase C (Wh)
    
    --###################################
    
    
    -- ****** EXPORTED ENERGY TOTALS FOR PERIOD ******
    local m_exported = GetUserParam('Local Network', 'M_Exported')                                            -- Total meter exported energy (Wh)
    local m_exported_day = m_exported - GetUserParam('Local Network', 'M_Exported_Day_Previous')            -- Total exported energy today (Wh)
    
    
    
    -- Running to see which is more accurate this, or m_exported
    local m_exported_a = GetUserParam('Local Network', 'M_Exported_A')    -- Exported energy total (Wh)        -- Total meter exported energy (Wh)
    local m_exported_a_day = m_exported_a - GetUserParam('Local Network', 'M_Exported_A_Day_Previous')        -- Total exported energy today (Wh)
    
    
    local m_self_consumption = energy_total_day - m_exported_day                                            -- Total self consumption energy today (Wh)
    local energy_prod_self_cons_percent = math.floor(((m_self_consumption / energy_total_day) * 100) + 0.5)    -- Percentage of self consumption today (%)
    local energy_export_percent = 100 - energy_prod_self_cons_percent                                            -- Percentage of exported energy today (Wh)
    
    
    SetUserParam('Local Network', 'M_Exported_Day_Current', m_exported_day)
    SetUserParam('Local Network', 'M_Exported_Day_Current_KWH', m_exported_day / 1000)
    
    SetUserParam('Local Network', 'M_Exported_A_Day_Current', m_exported_a_day)
    SetUserParam('Local Network', 'M_Exported_A_Day_Current_KWH', m_exported_a_day / 1000)
    
    SetUserParam('Local Network', 'M_Energy_Self_Consumption', m_self_consumption)                            -- Save self consumption vakye (Wh)
    SetUserParam('Local Network', 'M_Energy_Self_Consumption_KWH', m_self_consumption / 1000)                -- Save self consumption vakye (kWh)
    SetUserParam('Local Network', 'Energy_Production_Self_Cons_%', energy_prod_self_cons_percent)            -- Save self consumption percent
    SetUserParam('Local Network', 'Energy_Production_Export_%', energy_export_percent)            -- Save self consumption percent
    
    
    -- *************** ENERGY IMPORT CALCULATIONS *****************
    local energy_consumption = m_imported_day + m_self_consumption
    SetUserParam('Local Network', 'M_Energy_Consumption', energy_consumption)
    SetUserParam('Local Network', 'M_Energy_Consumption_KWH', (energy_consumption) / 1000)
    
    local energy_consumption_percent = math.floor(((m_self_consumption / energy_consumption) * 100) + 0.5)    -- Percentage of self consumption today (%)
    local energy_import_percent = 100 - energy_consumption_percent                                            -- Percentage of exported energy today (Wh)
    
    SetUserParam('Local Network', 'Energy_Consumption_Self_Cons_%', energy_consumption_percent)
    SetUserParam('Local Network', 'Energy_Consumption_Import_%', energy_import_percent)
    
    SetUserParam('Local Network', 'Energy_Production_Index', ConvertRange(energy_prod_self_cons_percent, 0, 100, 1, 20))
    SetUserParam('Local Network', 'Energy_Consumption_Index', ConvertRange(energy_consumption_percent, 0, 100, 1, 20))
    

    Sorry guys, this is supplied as is, I don't have the time or energy to support it.


    Cheers,
    Paul
     

    Attached Files:

    pspeirs, Jul 13, 2019
    #11
  12. pspeirs

    pspeirs

    Joined:
    Nov 23, 2013
    Messages:
    138
    Likes Received:
    5
    Location:
    Sydney
    Oh, and if you want a more accurate visualization of the usage graphs, just create 100 different images with the meter colour increasing on each image. Guess you could use a gauge from 0 - 100, but better yet, the guys who write this firmware could actually supply some decent gauges to use.
     
    pspeirs, Jul 13, 2019
    #12
    1. Advertisements

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments (here). After that, you can post your question and our members will help you out.