thomluther / anker-solix-api

Python library for Anker Solix API
MIT License
74 stars 15 forks source link

Infos on uneven appliance load shares in dual Solarbank setup #97

Closed jonashein closed 4 months ago

jonashein commented 5 months ago

Hi @thomluther ,

after reading your issue #19 and this issue I did some tests to see how the appliance load share can be adjusted. The load shares are indeed set via the _set_deviceparm endpoint. The _power_settingmode defines whether the default mode (even load share) or the advanced mode (custom load shares) is applied. A value of 1 indicates the even load share; a value of 2 indicates the custom load shares.

Example for even load share (default mode):

  "ranges": [
      {
        "id": 0,
        "start_time": "00:00",
        "end_time": "24:00",
        "turn_on": true,
        "appliance_loads": [
          {
            "id": 0,
            "name": "Custom",
            "power": 350,
            "number": 1
          }
        ],
        "charge_priority": 80,
        "power_setting_mode": 1,
        "device_power_loads": [
          {
            "device_sn": "AZV6XXXXXXXXXXXX",
            "power": 175
          },
          {
            "device_sn": "AZV6XXXXXXXXXXXX",
            "power": 175
          }
        ]
      }
    ],
  "min_load": 100,
  "max_load": 800,
  "step": 0,
  "is_charge_priority": 0,
  "default_charge_priority": 0
}

Example for custom load shares:

  "ranges": [
      {
        "id": 0,
        "start_time": "00:00",
        "end_time": "24:00",
        "turn_on": true,
        "appliance_loads": [
          {
            "id": 0,
            "name": "Custom",
            "power": 350,
            "number": 1
          }
        ],
        "charge_priority": 80,
        "power_setting_mode": 2,
        "device_power_loads": [
          {
            "device_sn": "AZV6XXXXXXXXXXXX",
            "power": 150
          },
          {
            "device_sn": "AZV6XXXXXXXXXXXX",
            "power": 200
          }
        ]
      }
    ],
  "min_load": 100,
  "max_load": 800,
  "step": 0,
  "is_charge_priority": 0,
  "default_charge_priority": 0
}

I also did some additional tests to see which power values are chosen in case the power set in _applianceloads is inconsistent with the power values set in _device_powerloads. If _power_settingmode is set to 1 (default mode), the power values in _device_powerloads are ignored and overwritten by 50% of the power given under _applianceloads . If _power_settingmode is set to 2 (advanced mode), the power value given in _applianceloads is ignored. The Solarbanks output whatever power is set under _device_powerloads . However, the power value in _applianceloads is not overwritten in that case, and the Anker App will show this incorrect value on the Home page in the gray bar labelled "Family Load Setting:". On the Device pages, the correct per-device power setpoints are shown.

I hope this is new and useful info to you. We probably don't need any code adjustments in this repo, but feel free to extend the documentation and/or examples - or let me know how you like to you document this and I'll create a PR.

Edit: In response to the open question on the maximum load setting, I quickly tested if I can set my system to 1600W. This is the response I get from the _get_deviceload endpoint:

{
  "site_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "home_load_data": {
    "ranges": [
      {
        "id": 0,
        "start_time": "00:00",
        "end_time": "24:00",
        "turn_on": true,
        "appliance_loads": [
          {
            "id": 0,
            "name": "Custom",
            "power": 1600,
            "number": 1
          }
        ],
        "charge_priority": 80,
        "power_setting_mode": 1,
        "device_power_loads": [
          {
            "device_sn": "AZV6XXXXXXXXXXXX",
            "power": 800
          },
          {
            "device_sn": "AZV6XXXXXXXXXXXX",
            "power": 800
          }
        ]
      }
    ],
    "min_load": 100,
    "max_load": 800,
    "step": 0,
    "is_charge_priority": 1,
    "default_charge_priority": 80,
    "is_zero_output_tips": 0,
    "display_advanced_mode": 1,
    "advanced_mode_min_load": 50
  },
  "current_home_load": "1600W",
  "parallel_home_load": "800W",
  "parallel_display": true
}

I'm not sure how the _minload and _maxload should be interpreted or when they apply, but I can confirm that in the default mode any power values between 100 - 1600W for the appliance load or 50 - 800W for the device load are valid in the dual Solarbank setup with a MI80 inverter configured.

thomluther commented 4 months ago

Hi @jonashein These examples help to understand how to use the advanced mode via API. In the App, can you configure each Bank to 800W at same time, and what is the family load showing then? When you see more than 800W on your inverter the appliance max limit is maybe not enforced in advanced mode. Maybe it is neither in normal mode when 2 banks are installed, but the App does not allow more than 800W in normal mode? I just tested that lower limit of 100 W is enforced. Can you use the advanced mode also with set_device_parm endpoint? The set_device_load endpoint has no effect at all in single bank setup for what I have tested

thomluther commented 4 months ago

At the end this is all pretty confusing and inconsistent for the values to use and present for the Integration. I'm not sure how to implement this reliability that it works for single and dual setups without being able to test and debug it. There is already an active appliance value and a device value in the device details of the Api cache. Depending on which power mode is active in the interval, the correct device or appliance value must be extracted and the other calculated since the api fields may be wrong for either of the two fields. Also the advanced power mode setting needs to be extracted for the device cache to create an extra HA switch or select entity for it. The switch should not be extracted for single bank sites.

jonashein commented 4 months ago

The power limits need to be set with the set_device_parm endpoint. The set_device_load endpoint also has no effect for me, it may be deprecated? In my tests I used set_device_parm to set values and get_device_load to verify that the values have been accepted. I still need to check if we can use the get_device_parm endpoint as an alternative to get_device_load. It'd be cleaner than mixing the endpoints for setter and getter. Can you verify that using set_device_parm and get_device_load works also in a single Solarbank setup?

Regarding the possible App configurations, I can set between 100 - 1600W in the even load mode. The set power value will be shown on the Home page, and 50% of the set power value will be shown on the device pages. In the custom load shares mode I can set between 50 - 800W for each Solarbank independently, i.e. also setting both Solarbanks to 800W is possible. The set power values will be shown on the device pages, and their sum is shown on the Home page. So at least this behavior is intuitive.

For the HA integration I'd propose the following:

The only potential issue is the correct setting of the limits for the power presets. Currently, it seems like the max_load parameter defined the maximum preset per Solarbank, the min_load parameter defines the minimum load in the default power mode, and the advanced_mode_min_load defines the minimal load in advanced power mode. Can you confirm this as well for the single Solarbank setup?

thomluther commented 4 months ago

Both, the get_device_load and get_device_parm work the same way and get_device_load is already used for the regular queries. I think set_device_load is the newer endpoint since it did not exist since the beginning. But I could neither make it effective somehow, so only the set_device_parm seems to be usable as setter for now. Thanks for the info with the 1600W for appliance load setting. I wasn't aware that this is possible with dual bank setup, since the schedule always showed 100-800 as possible range to my knowledge. The 1600W limit is something that needs to be changed for the API and the integration. But it must be in combination for validation of single or dual solarbank setups.

I'm still thinking about the easiest way to implement this in the Api helper method that does all the schedule changes, Your suggestion sounds logical, but it has some drawbacks. Currently the preset entity has an appliance affinity, changing that to a device affinity would require a breaking change (it would also require to rename the unique ids of the entity to avoid confusion and that would remove existing entities upon integration update and invalidate any scripts or automations). Furthermore a switch in the system would remove the flexibility that is currently built into the Api to support schedule settings per device if that will ever be supported in the future. So generally speaking, all schedule related entities should be usable in the device itself to maintain this flexibility, even if the schedule is actually shared between all solarbanks in a system.

HA users needs capability for individual entity changes in current interval as well as for the services which pass a parameter set for an interval. I don't want to add too many extra entities to HA, neither direct changeable entities nor service fields for supporting individual device load settings. I believe a switch for advanced power mode is just necessary for the sake of changing it in the active interval. However, a change of any interval entity has an immediate effect to the Api, e.g. it would need to enable or disable the power mode setting in the active interval. Enabling it is easy, as this does not change the actual power settings, but disabling it may require an undesired power change because both banks have to be adjusted to 50% of the actual appliance load setting. Currently I'm not sure if a switch has any benefit for HA, since in the app it is just used to enable a different panel with more settings. Probably it would be sufficient to show only a new binary sensor in the solarbank device whether advanced power mode is active or not... In HA, it would be sufficient to provide a new number entity for the device load, which is created only for solarbanks in a dual setup. Then users have always the choice whether to change the existing appliance load or the new device load for the solarbank. For schedule services the power mode switch is equally irrelevant. I'm thinking to provide an additional device load field like the new number entity. It will be ignored for single bank setups, but for dual bank setups it can be used with or without the appliance load field to set everything for an interval:

So I don't think the advanced power mode needs to be an extra parameter that must be provided to the Api helper method, but only the new device load parameter (individually or as part of the parameter set). The power mode can be derived from the other parameter combinations and used for the setter method as necessary.

Regarding the limits: These are the ones that are applied, but the Api accepts also values beyond the limits. E.g. I can set 50W min, but 100W will be always applied.

Likewise it would be important to know whether the advanced min limit of 50W is also applied, but 0W can be specified in the Api without error? I assume it will be accepted by the endpoint and shown in the schedule, but when you look on the actual output it (or measure the inverter output) will probably still show 50W as applied minimum. You could test this by setting one to 0W and the other to 50W for example, or both to 25W and check whether this is accepted and what the inverter output result is.

My assumption is that you may be specify < 50W per device, but each device will apply 50W minimal to always discharge/bypass 100W in total to the inverter. I assume there is no way currently in the App to set one solarbank to 0W and the other to 100W or more to use a staggered charge/discharge schedule, correct? Or can you disable export completely for just one of the 2 solarbanks? To my knowledge, this works only with the export switch, but this is only available for the appliance and not for individual devices.

Depending on that, we can assume correct min values for the new device load entity.

jonashein commented 4 months ago

Good point, taking into account the current implementation it makes sense to keep the preset_system_output_power and only add a new preset_device_output_power to the Device class in the HA integration, as well as a new parameter device_preset to the set_home_load function of the python API wrapper. Also, I agree that it makes sense to adjust the appliance load and keep the other Solarbank's preset unchanged in case the set_home_load function is called with only the device_preset.

Regarding the limits, the set_device_param endpoint also accepts values below the minimum (I tested 0W and 25W), but the Solarbanks seem to ignore these presets and output at least 50W. A staggered discharge schedule does not seem to be possible at the moment.

thomluther commented 4 months ago

Thx for confirmation. That gives us flexibility on what limits we set in the library without getting errors on requests. But I think it is best to set the library limits according to the real solarbank limits, since other values are ignored anyway. So it would just cause confusion, if the desired values cannot be applied. I'm pretty much finished with the set_home_load enhancements to support this. Once I'm finished, I will commit it in a new branch, so you can test it against real devices. I can only do mock runs to verify the resulting input schedule is correct.

thomluther commented 4 months ago

@jonashein I comitted brach 1.9 with all changes. https://github.com/thomluther/anker-solix-api/tree/1.9

I did mock tests with the dual solarbank schedule from example2 using the set_home_load helper method, which is used by the HA integration. That seems to work now as desired for individual parameter changes (as used by individual entity changes), insert_slot object (as used by update_schedule service) and set_slot object (as used by set_schedule service).

Please try to do some tests on your real setup using the set_home_load method. Here are some code snippets how you can use it:

import asyncio
from datetime import datetime
import logging
import os
import sys
import datetime
from aiohttp import ClientSession
from api import api, errors
import common

_LOGGER: logging.Logger = logging.getLogger(__name__)
_LOGGER.addHandler(logging.StreamHandler(sys.stdout))

async def main() -> None:
    """Create the aiohttp session and run the example."""
    print("Testing Solix API:")
    try:
        async with ClientSession() as websession:
            myapi = api.AnkerSolixApi("username@domain.com","password","de",websession, _LOGGER)

            deviceSn = "YOUR_SOLARBANK_SN"
            siteId = "YOUR_SITE_ID"

            await myapi.set_home_load(
                siteId=siteId,
                deviceSn=deviceSn,
                preset=None,
                dev_preset=None,
                all_day=None,
                export=None,
                charge_prio=None,
                insert_slot=api.SolarbankTimeslot(
                    start_time=datetime.strptime("19:30", "%H:%M"),
                    end_time=datetime.strptime("23:59", "%H:%M"),
                    appliance_load=300,
                    device_load=None,
                    allow_export=None,
                    charge_priority_limit=None,
                ),
            )

            # print schedule table from requeried and updated schedule object in the device cache
            common.print_schedule((myapi.devices.get(deviceSn) or {}).get("schedule"))

    except Exception as err:
        print(f"{type(err)}: {err}")

You can replace the insert_slot also with set_slot for testing. Both need the same object and mandatory times, but set_slot will delete all existing slots and just create a single slot with the given parameters or defaults. If you want to use individual parameters, the slot object must be None or commented out.

PS: In my setup I noticed that passing a single slot via Api may result in a weird setting by the solarbank, where the Api schedule looks good, but the Schedule in the App shots 0W for the appliance load and thus nothing will be exported, even if the export switch is on. I think this is a firmware bug (have not validated with 1.6.2). Also when passing a single slot to the cloud, the timeinterval is pretty much ignored and a full day interval will be applied by the solarbank.

Following would be good validations for testing with dual bank setup. It will be helpful if you can monitor the inverter output in parallel to validate the change is correctly applied (and has the desired result):

The general behavior should be following:

thomluther commented 4 months ago

BTW, I assume the set_device_load endpoints which do not have any effect with the E1600 are eventually prepared already for the new model. This will need different objects and has another schedule structure, since it will allow different weekday settings in the schedule from what I have seen so far.

thomluther commented 4 months ago

@jonashein Just made a minor fix in the api.py in the 1.9 branch. Can you provide an actual, anonymized system_export of your setup? I need an actual output example for testing the code. The HA integration is also pretty much done. Once I finished more tests, I will provide a branch there as well for testing in your HA.

jonashein commented 4 months ago

Hi @thomluther

thanks for the update! I'll test it now. Also, here's the system export: jon##.zip