henne49 / dbus-opendtu

Integrate opendtu and ahoy into Victron Energies Venus OS as a pv inverter
MIT License
113 stars 33 forks source link

Report soyo inverter status #94

Closed GWarsow closed 1 year ago

GWarsow commented 1 year ago

Hi all

I use Esp-Soyosource-Controller in order to control 2 soyosource inverters with input from shelly3 em. I already made DBUS aware of the AC input for L1-3. Now I also would like to register both soyos (running on L1 and L2) as inverter/pvinverter via opendtu. The config.ini is set to use only templates and the respective config part looks like this:

Username =
Password =
DigestAuth = False
Host=192.168.x.y
CUST_SN = 12345678
CUST_API_PATH= data
CUST_POLLING = 2000
CUST_Total= none
CUST_Total_Mult = 1
CUST_Power= /WattsOut
CUST_Power_Mult = 1
CUST_Voltage=  none
CUST_Current=  none
Phase=L1
DeviceInstance=47
AcPosition=1
Name= Soyo1
Servicename=com.victronenergy.pvinverter

The idea is to use the json returned when calling 192.168.x.y/data The json looks like { "L1L2L3" : "-4.51" , "ControllerName" : "SOYOSOURCE Controller" , "StartTime" : "Thu Jun 15 12:27:10 2023" , "NotAus" : "0" , "WaitSekunden" : "2" , "MaxPower" : "1000" , "SoyoCount" : "2" , "DCAmps" : "S>1" , "DCVolts" : "S>1" , "DCWatts" : "S>1" , "WattsOut" : "144"}

WattsOut carries the needed information. However, when I try to use CUST_Power= /WattsOut in the config, the received value is 0. The device is successfully registered with victron dbus but V/A/W value are all zero. But I would expect that due to setting of CUST_Power that I should see 144W in the victron console or the dashboard. If I ommit the leading slash (CUST_Power= WattsOut) it does not work at all. Could it be that the parser expects a nested structure and cannot use top level entries of the json?

Do you have any suggestion as of how to achieve what I am aming for?

Thank you Gregor

henne49 commented 1 year ago

let me check the code, yes I think we need a nested one...

henne49 commented 1 year ago

Please try CUST_Power=WattsOut no space please. It should work:

>>> CUST_Power='WattsOut'
>>> CUST_Power.split("/")
['WattsOut']
>>> CUST_Power=' WattsOut'
>>> CUST_Power.split("/")
[' WattsOut']
>>>
GWarsow commented 1 year ago

Good idea! But unfortunately, it still does not work.

# Number ob Template Inverter to query
NumberOfTemplates=1

# Which DTU to be used ahoy, opendtu, template
DTU=template

[TEMPLATE0]
## Tasmota Example
##
## Username/Password leave empty if no authentication is required
Username =
Password =
DigestAuth = False
Host=192.168.x.y
CUST_SN = 12345678
CUST_API_PATH= data
CUST_POLLING = 2000
CUST_Total=none
CUST_Total_Mult = 1
CUST_Power=WattsOut
CUST_Power_Mult = 1
CUST_Voltage=none
CUST_Current=none
Phase=L1
DeviceInstance=47
AcPosition=1
Name= Soyo1
Servicename=com.victronenergy.pvinverter

dbus-spy output:

com.victronenergy.pvinverter.http_47
Ac/Energy/Forward                                              -
Ac/L1/Current                                                  -
Ac/L1/Energy/Forward                                           -
Ac/L1/Power                                                    -
Ac/L1/Voltage                                                  -
Ac/L2/Current                                                  -
Ac/L2/Energy/Forward                                           -
Ac/L2/Power                                                    -
Ac/L2/Voltage                                                  -
Ac/L3/Current                                                  -
Ac/L3/Energy/Forward                                           -
Ac/L3/Power                                                    -
Ac/L3/Voltage                                                  -
Ac/Out/L1/I                                                    -
Ac/Out/L1/V                                                    -
Ac/Power                                                       -
Connected                                                      1
CustomName                                                 Soyo1
Dc/0/Voltage                                                   -
DeviceInstance                                                47
FirmwareVersion                                              0.1
HardwareVersion                                                0

wget 192.168.x.y/data yields: { "L1L2L3" : "685.81" , "ControllerName" : "SOYOSOURCE Controller" , "StartTime" : "Fri Jun 16 04:29:44 2023" , "NotAus" : "0" , "WaitSekunden" : "2" , "MaxPower" : "1000" , "SoyoCount" : "2" , "DCAmps" : "S>1" , "DCVolts" : "S>1" , "DCWatts" : "S>1" , "WattsOut" : "533"}

henne49 commented 1 year ago

Will have a look in the next days and then we fix it.

GWarsow commented 1 year ago

sounds great! thank you so much!

GWarsow commented 1 year ago

I added logging.info("meter data: %s", meter_data); after line 363 of dbus_service.py and got: 2023-06-16 13:15:41,725 root INFO meter data: {'L1L2L3': '-2.29', 'ControllerName': 'SOYOSOURCE Controller', 'StartTime': 'Fri Jun 16 04:29:44 2023', 'NotAus': '0', 'WaitSekunden': '2', 'MaxPower': '1000', 'SoyoCount': '2', 'DCAmps': 'S>1', 'DCVolts': 'S>1', 'DCWatts': 'S>1', 'WattsOut': '271'} So the json was fetched correctly.

0x7878 commented 1 year ago

Check line 599 in dbus_service.py

elif self.dtuvariant == constants.DTUVARIANT_TEMPLATE:
            # logging.debug("JSON data: %s" % meter_data)
            power = float(get_nested(meter_data, self.custpower) * float(self.custpower_factor))
            pvyield = float(get_nested(meter_data, self.custtotal) * float(self.custtotal_factor))
            voltage = float(get_nested(meter_data, self.custvoltage))
            dc_voltage = float(get_nested(meter_data, self.custdcvoltage))
            current = float(get_nested(meter_data, self.custcurrent))

        return (power, pvyield, current, voltage, dc_voltage)

Every value goes to the "get_nested" function

def get_nested(meter_data, path):
    '''Try to extract 'path' from nested array 'meter_data' (derived from json document) and return the found value'''
    value = meter_data
    for path_entry in path:
        try:
            value = value[path_entry]
        except Exception:
            try:
                value = value[int(path_entry)]
            except Exception:
                value = 0
    return value

which returns 0 if there is a problem.

This is a good place to start debugging.

GWarsow commented 1 year ago

I added some log lines in line 600:

            logging.info("JSON data: %s" % meter_data)
            logging.info("custpower: %s", self.custpower)
            logging.info("nested power: '%s'", get_nested(meter_data, self.custpower));
            #logging.info("float power: %s", float(get_nested(meter_data, self.custpower));
            logging.info("power factor: %s", self.custpower_factor);
            logging.info("float power factor: %s", float(self.custpower_factor));

the one line I commented out seems to cause problems, hence, casting e.g. 200 to float does not work

output of above code:

2023-06-16 14:00:31,169 root INFO JSON data: {'L1L2L3': '-9.95', 'ControllerName': 'SOYOSOURCE Controller', 'StartTime': 'Fri Jun 16 04:29:44 2023', 'NotAus': '0', 'WaitSekunden': '2', 'MaxPower': '1000', 'SoyoCount': '2', 'DCAmps': 'S>1', 'DCVolts': 'S>1', 'DCWatts': 'S>1', 'WattsOut': '326'}
2023-06-16 14:00:31,172 root INFO custpower: ['WattsOut']
2023-06-16 14:00:31,174 root INFO nested power: '326'
2023-06-16 14:00:31,175 root INFO power factor: 1
2023-06-16 14:00:31,176 root INFO float power factor: 1.0
GWarsow commented 1 year ago

Assigning the value of get_nested(meter_data, self.custpower) to a variable and casting it to float works well:

custpower = get_nested(meter_data, self.custpower)
custpower = float(custpower)
power = float(custpower * float(self.custpower_factor))
0x7878 commented 1 year ago

I'm sorry for the trouble. Exactly this type of problem is already fixed in my Pullrequest which is not merged yet. There all values goes to the function like this:

 @staticmethod
    def get_processed_meter_value(meter_data: dict, value: str, default_value: any, factor: int = 1) -> any:
        '''return the processed meter value by applying the factor and return a default value due an Exception'''
        get_raw_value = get_value_by_path(meter_data, value)
        raw_value = convert_to_expected_type(get_raw_value, float, default_value)
        if isinstance(raw_value, (float, int)):
            value = float(raw_value * float(factor))
        else:
            value = default_value

        return value
 Maybe we should keep that issue open for others. 
dsteinkopf commented 1 year ago

For reference: This is the mentioned PR which is expected to fix the above problem: #85