Skip to content
/ pv_opt Public

Home Assistant PV Optimisation for Solis Inverters

License

Notifications You must be signed in to change notification settings

fboundy/pv_opt

Repository files navigation

PV Opt: Home Assistant Solar/Battery Optimiser v3.16.1

This documentation needs updating!

Solar / Battery Charging Optimisation for Home Assistant. This appDaemon application attempts to optimise charging and discharging of a home solar/battery system to minimise cost electricity cost on a daily basis using freely available solar forecast data from SolCast. This is particularly beneficial for Octopus Agile but is also benefeficial for other time-of-use tariffs such as Octopus Flux or simple Economy 7.

The application will integrate fully with Solis inverters which are controlled using any of:

Once installed it should require miminal configuration. Other inverters/integrations can be added if required or can be controlled indirectly using automations.

It has been tested primarily with Octopus tariffs but other tariffs can be manually implemented.

Don't Buy Me a Beer or a Coffee...

Although I'm very partial to both, a better home for anything you might like to contribute would be the Mountain Rescue Team that I am currently a probationer (trainee) with. The Team comprises 40 unpaid volunteers and is on call 24/7 365 days a year in all weathers. You can donate via JustGiving by clicking on this link:

Pre-requisites

This app is not stand-alone it requires the following:

Websites
Solcast Hobby Account Required Provides solar PV generation forecasts up to 10 times per day,
Add-ons
HACS Required Home Assistant Community Store - used to distribute the app and updates
AppDaemon Required Python execution environment
Mosquitto MQTT Broker Required Used to create Home Assistant Entities using MQTT Discovery .
File Editor Required Used to edit the appdaemon.yaml and config.yaml files. Alternatively you could use Samba Share or Studio Code Server
Samba Share Alternative Alternative to using File Editor to edit config files. Not convered in this guide.
Studio Code Server Alternative Alternative to using File Editor to edit config files. Not convered in this guide.
Integrations
Solcast PV Solar Integration Required Retrieves solar forecast from Solcast into Home Assistant
Octopus Energy Optional Used to retrieve tariff information and Octopus Saving Session details
Solax Modbus Optional Used to control Solis inverter directly. Support for two other integrations is now available (see below). Support inverter brands is possible using the API described below.

Step by Step Installation Guide

1. Get a Solcast Hobby Account

PV_Opt relies on solar forecasts data from Solcast. You can sign up for a Private User account here. This licence gives you 10 (it used to be 50 🙁) API calls a day.

2. Install HACS

  1. Install HACS: https://hacs.xyz/docs/setup/download
  2. Enable AppDaemon in HACS: https://hacs.xyz/docs/use/repositories/type/appdaemon/#making-appdaemon-apps-visible-in-hacs

3. Install the Solcast PV Solar Integration (v4.0.x)

  1. Install the integation via HACS: https://github.com/oziee/ha-solcast-solar
  2. Add the Integration via Settings: http://homeassistant.local:8123/config/integrations/dashboard
  3. Once installed configure using your Solcast API Key from (1) .
  4. Set up an automation to update according to your desired schedule. Once every 3 hours will work.

4. Install the Octopus Energy Integration (If Required)

This excellent integration will pull Octopus Price data in to Home Assistant. Solar Opt pulls data from Octopus independently of this integration but will extract current tariff codes from it if they are avaiable. If not it will either use account details supplied in secrets.yaml or explicitly defined Octopus tariff codes set in config.yaml.

5. Install the Integration to Control Your Inverter

At present this app only works directly with Solis hybrid inverters using either the Solax Modbus integration (https://github.com/wills106/homeassistant-solax-modbus), the HA Core Modbus as described here: https://github.com/fboundy/ha_solis_modbus, or combining the Solis-Senor and Solis-Control integrations.

Support for the Solarman integration (https://github.com/StephanJoubert/home_assistant_solarman) is in test. At the moment writing to the inverter is disabled pending further testing by Solarman users.

Solax Modbus:

  1. Install the integration via HACS: https://github.com/wills106/homeassistant-solax-modbus
  2. Add the Integration via Settings: http://homeassistant.local:8123/config/integrations/dashboard
  3. Configure the connection:
    Prefix solis
    Interface TCP/Ethernet
    Inverter Type solis
    IP Address IP of your datalogger
    TCP Port 502
    Protocol Modbus TCP
  4. Check that you have comms with the inverter and the various entities in the integration are populated with data

HA Core Modbus

Follow the Github instructions here: https://github.com/fboundy/ha_solis_modbus

Using Solis Cloud

Solis-Sensor

Follow the Github instruction here: https://github.com/hultenvp/solis-sensor

Solis-Control

Follow the Github instruction here: https://github.com/hultenvp/solis_control

Solarman

Follow the Github instructions here: https://github.com/StephanJoubert/home_assistant_solarman

6. Install the MQTT Integraion in Home Assistant

  1. Click on the button below to add the MQTT integration:

7. Install Mosquitto MQTT Broker

  1. Navigate to Settings -> Addons and click "Mosquito Broker"

  2. Click on Install

  3. Configure the Add-On as per the documentation: http://homeassistant.local:8123/hassio/addon/core_mosquitto/documentation

  4. Either save the MQTT username and password in your secrets.yaml file or make a note of them for later.

8. Install File Editor

Follow instructions here: https://github.com/home-assistant/addons/blob/master/configurator/README.md

Navigate to Settings -> Addons -> File editor -> Configuration and set "Enforce Basepath" to "off".

9. Install Samba Share and/or Studio Code Server Add-ons If Required

Both of these add-ons make it easier to edit text files on your HA Install but aren't strictly necessary. Samba Share also makes it easier to access the AppDaemon log files.

10. Install AppDaemon

The PV_Opt python script currently runs under AppDaemon.

AppDaemon is a loosely coupled, multi-threaded, sandboxed python execution environment for writing automation apps for home automation projects, and any environment that requires a robust event driven architecture. The simplest way to install it on Home Assistantt is using the dedicated add-on:

  1. Click the Home Assistant My button below to open the add-on on your Home Assistant instance:

  2. Click on Install

  3. Turn on Auto update

11. Configure AppDaemon

  1. Use File Editor (or one of the alternatives) to open /addon_configs/a0d7b954_appdaemon/appdaemon.yaml.

  2. The suggested configuration is as follows. This assumes that you are using secrets.yaml for your password information. If not then the secrets entry can be deleted and the MQTT client_user and client password will need to be entered explicitly.

     secrets: /homeassistant/secrets.yaml
     appdaemon:
       latitude: 54.729
       longitude: -2.991
       elevation: 175
       time_zone: Europe/London
       thread_duration_warning_threshold: 45
       app_dir: /homeassistant/appdaemon/apps
       plugins:
         HASS:
           type: hass
         MQTT:
           type: mqtt
           namespace: mqtt #
           verbose: True
           client_host: core-mosquitto
           client_port: 1883
           client_id: localad
           event_name: MQTT_MESSAGE 
           client_topics: NONE
           client_user: !secret mqtt-user
           client_password: !secret mqtt-password
    
     http:
       url: http://127.0.0.1:5050
     admin:
     api:
     hadashboard:
    

And add the client_user and client_password keys to secrets.yaml like this:

mqtt-user: some_user
mqtt-password: some_password
  1. It is also recommended that you add the following entries to appdaemon.yaml to improve AppDaemon logging. These settings assume that you have a /share/logs folder setup using Samba Share.

     logs:
       main_log:
         filename: /share/logs/main.log
         date_format: '%H:%M:%S'
       error_log:
         filename: /share/logs/error.log
         date_format: '%H:%M:%S'    
       pv_opt_log:
         name: PV_Opt
         filename: /share/logs/pv_opt.log
         date_format: '%H:%M:%S'      
         format: '{asctime} {levelname:>8s}: {message}'
    
  2. Open the AppDaemon Add-On via Settings: http://homeassistant.local:8123/hassio/addon/a0d7b954_appdaemon/info

  3. Click on Configuration at the top

  4. Click the 3 dots and Edit in YAML to add pandas and numpy as Python packages. Note that numpy has to be set to version 1.26.4 due to an unresolved compatability issue between Home Assistant and 2.0.0:

    init_commands: []
    python_packages:
      - pandas
      - numpy==1.26.4
    system_packages: []
    
    
  5. Go back to the Info page and click on Start

  6. Click on Log. Appdaemon will download and install numpy and pandas. Click on Refresh until you see:

     s6-rc: info: service init-appdaemon successfully started
     s6-rc: info: service appdaemon: starting
     s6-rc: info: service appdaemon successfully started
     s6-rc: info: service legacy-services: starting
     [12:54:30] INFO: Starting AppDaemon...
     s6-rc: info: service legacy-services successfully started
    
  7. Either click on Info followed by OPEN WEB UI and then Logs or open your main_log file from the location specified in step (3) above. You should see:

    13:16:24 INFO AppDaemon: AppDaemon Version 4.4.2 starting
    13:16:24 INFO AppDaemon: Python version is 3.11.6
    13:16:24 INFO AppDaemon: Configuration read from: /config/appdaemon.yaml
    13:16:24 INFO AppDaemon: Added log: AppDaemon
    13:16:24 INFO AppDaemon: Added log: Error
    13:16:24 INFO AppDaemon: Added log: Access
    13:16:24 INFO AppDaemon: Added log: Diag
    13:16:24 INFO AppDaemon: Added log: PV_Opt
    13:16:25 INFO AppDaemon: Loading Plugin HASS using class HassPlugin from module hassplugin
    13:16:25 INFO HASS: HASS Plugin Initializing
    13:16:25 WARNING HASS: ha_url not found in HASS configuration - module not initialized
    13:16:25 INFO HASS: HASS Plugin initialization complete
    13:16:25 INFO AppDaemon: Loading Plugin MQTT using class MqttPlugin from module mqttplugin
    13:16:26 INFO MQTT: MQTT Plugin Initializing
    13:16:26 INFO MQTT: Using 'localad/status' as Will Topic
    13:16:26 INFO MQTT: Using 'localad/status' as Birth Topic
    13:16:26 INFO AppDaemon: Initializing HTTP
    13:16:26 INFO AppDaemon: Using 'ws' for event stream
    13:16:26 INFO AppDaemon: Starting API
    13:16:26 INFO AppDaemon: Starting Admin Interface
    13:16:26 INFO AppDaemon: Starting Dashboards
    13:16:26 INFO HASS: Connected to Home Assistant 2023.11.1
    13:16:26 INFO AppDaemon: Starting Apps with 0 workers and 0 pins
    13:16:26 INFO AppDaemon: Running on port 5050
    13:16:26 INFO MQTT: Connected to Broker at URL core-mosquitto:1883
    13:16:26 INFO AppDaemon: Got initial state from namespace mqtt
    13:16:26 INFO MQTT: MQTT Plugin initialization complete
    13:16:26 INFO HASS: Evaluating startup conditions
    13:16:26 INFO HASS: Startup condition met: hass state=RUNNING
    13:16:26 INFO HASS: All startup conditions met
    13:16:26 INFO AppDaemon: Got initial state from namespace default
    13:16:28 INFO AppDaemon: Scheduler running in realtime
    13:16:28 INFO AppDaemon: Adding /homeassistant/appdaemon/apps to module import path
    13:16:28 INFO AppDaemon: App initialization complete
    

That's it. AppDaemon is up and running. There is futher documentation for the on the Add-on and for AppDaemon

12. Install PV Opt from HACS

  1. Make sure HACS "Enable AppDaemon apps discovery & tracking" is enabled - under integrations in HA https://hacs.xyz/docs/categories/appdaemon_apps/
  2. Go to HACS
  3. Select Automation
  4. Click on the 3 dots top right and Add Custom Repository
  5. Add this repository https://github.com/fboundy/pv_opt and select AppDaemon as the Category
  6. Download the app

Once downloaded AppDaemon should see the app and attempt to load it using the default configuration. Go back to the AppDaemon logs and this time open pv_opt_log. You should see:

16:53:23     INFO: ******************* PV Opt v3.0.1 *******************
16:53:23     INFO: 
16:53:23     INFO: Time Zone Offset: 0.0 minutes
16:53:23     INFO: Reading arguments from YAML:
16:53:23     INFO: -----------------------------------
16:53:23     INFO: 
16:53:23     INFO: Checking config:
16:53:23     INFO: -----------------------
16:53:23  WARNING:     forced_charge       = True   Source: system default. Not in YAML.
16:53:23  WARNING:     forced_discharge    = True   Source: system default. Not in YAML.
16:53:23  WARNING:     read_only           = True   Source: system default. Not in YAML.

13. Add an Automation to Restart AppDAemon when HA Restarts (Optional)

Restarts between Home Assistant and Add-Ons are not synchronised so it is helpful to set up an Automation to restart AppDAemon if HA is restarted. An example is shown below and included in this repo as ha_restart_automation.yaml. The wait_template section ensures that key integrations (in this case Solcast and Solax) have numeric values before AppDaemon is started.

alias: Restart AppDaemon on HA Restart
description: ""
trigger:
  - event: start
    platform: homeassistant
condition: []
action:
  - service: hassio.addon_stop
    data:
      addon: a0d7b954_appdaemon
  - delay:
      hours: 0
      minutes: 1
      seconds: 0
      milliseconds: 0
  - wait_template: >
      {{(states('sensor.solcast_pv_forecast_forecast_today')| float(-1)>0) and
      (states('sensor.solis_battery_soc')| float(-1)>0)}}
    continue_on_timeout: true
  - service: hassio.addon_start
    data:
      addon: a0d7b954_appdaemon
mode: single

14. For Solis-Control: Add Automation to Control Inverter

If you're using the solis-sensor and solis_control integrations through Solis Cloud, you'll need to add the following automation which will send the messages to Solis Cloud in order to control your inverter. N.B: It's important that you've set up the solis_control integration correctly and requested API access via Solis Cloud Technical Support.

alias: "Solis: Use PV_Opt"
description: "Use the output of pv_opt to control your Solis inverter via Solis Cloud."
trigger:
  - platform: state
    entity_id:
      - sensor.pvopt_status
    to: Idle (Read Only)
    for:
      hours: 0
      minutes: 0
      seconds: 5
    enabled: false
  - platform: time_pattern
    hours: /1
    minutes: "00"
    seconds: "05"
  - platform: time_pattern
    hours: /1
    minutes: "30"
    seconds: "05"
  - platform: state
    entity_id:
      - sensor.pvopt_charge_start
condition: []
action:
  - if:
      - condition: template
        value_template: >-
          {{ states('sensor.pvopt_charge_start') | as_datetime | as_local <=
          today_at("23:59") }}
    then:
      - data:
          days:
            - chargeCurrent: >-
                {% set direction = float(states('sensor.pvopt_charge_current'),
                0.0) %} {% set chargeAmps = min((max(direction, 0.0) |
                round(method='floor')), 50)%} {{ chargeAmps }}
              dischargeCurrent: >-
                {% set direction = float(states('sensor.pvopt_charge_current'),
                0.0) %} {% set dischargeAmps = min((min(direction, 0.0) | abs |
                round(method='floor')), 50) %} {{ dischargeAmps }}
              chargeStartTime: >-
                {% set direction = float(states('sensor.pvopt_charge_current'),
                0.0) %} {% set startChargeTime = '00:00' %} {% if direction >=
                0.0 -%}
                  {% set startChargeTime = (as_local(as_datetime(states('sensor.pvopt_charge_start')))|string)[11:16] %}
                {%- endif %} {{ startChargeTime }}
              chargeEndTime: >-
                {% set direction = float(states('sensor.pvopt_charge_current'),
                0.0) %} {% set endChargeTime = '00:00' %} {% if direction >= 0.0
                -%}
                  {% set endChargeTime = (as_local(as_datetime(states('sensor.pvopt_charge_end')))|string)[11:16] %}
                {%- endif %} {{ endChargeTime }}
              dischargeStartTime: >-
                {% set direction = float(states('sensor.pvopt_charge_current'),
                0.0) %} {% set startDischargeTime = '00:00' %} {% if direction <
                0.0 -%}
                  {% set startDischargeTime = (as_local(as_datetime(states('sensor.pvopt_charge_start')))|string)[11:16] %}
                {%- endif %} {{ startDischargeTime }}
              dischargeEndTime: >-
                {% set direction = float(states('sensor.pvopt_charge_current'),
                0.0) %} {% set endDischargeTime = '00:00' %} {% if direction <
                0.0 -%}
                  {% set endDischargeTime = (as_local(as_datetime(states('sensor.pvopt_charge_end')))|string)[11:16] %}
                {%- endif %} {{ endDischargeTime }}
            - chargeCurrent: "0"
              dischargeCurrent: "0"
              chargeStartTime: "00:00"
              chargeEndTime: "00:00"
              dischargeStartTime: "00:00"
              dischargeEndTime: "00:00"
            - chargeCurrent: "0"
              dischargeCurrent: "0"
              chargeStartTime: "00:00"
              chargeEndTime: "00:00"
              dischargeStartTime: "00:00"
              dischargeEndTime: "00:00"
          config:
            secret: <<Your secret without quotes>>
            key_id: "<<Your key id with quotes>>"
            username: <<Your username without quotes>>
            password: <<Your password without quotes>>
            plantId: "<<Your plant_id with quotes>>"
        action: pyscript.solis_control
    else:
      - data:
          days:
            - chargeCurrent: "0"
              dischargeCurrent: "0"
              chargeStartTime: "00:00"
              chargeEndTime: "00:00"
              dischargeStartTime: "00:00"
              dischargeEndTime: "00:00"
            - chargeCurrent: "0"
              dischargeCurrent: "0"
              chargeStartTime: "00:00"
              chargeEndTime: "00:00"
              dischargeStartTime: "00:00"
              dischargeEndTime: "00:00"
            - chargeCurrent: "0"
              dischargeCurrent: "0"
              chargeStartTime: "00:00"
              chargeEndTime: "00:00"
              dischargeStartTime: "00:00"
              dischargeEndTime: "00:00"
          config:
            secret: <<Your secret without quotes>>
            key_id: "<<Your key id with quotes>>"
            username: <<Your username without quotes>>
            password: <<Your password without quotes>>
            plantId: "<<Your plant_id with quotes>>"
        action: pyscript.solis_control
mode: single

Configuration

If you have the Solcast, Octopus and Solax integrations set up as specified above, there should be minimal configuration required.

If you are running a different integration or inverter brand you will need to edit the config.yaml file to select the correct inverter_type. You may also need to change the device_name. This is the name given to your inverter by your integration. The default is solis but this can also be changed in config.yaml.

inverter_type: SOLIS_CORE_MODBUS
device_name: solis

The config.yaml file also includes all the other configuration used by PV Opt. If you are using the default setup you shouldn't need to change this but you can edit anything by un-commenting the relevant line in the file. The configuration is grouped by inverter/integration and should be self-explanatory. Once PV Opt is installed the config is stored within entities in Home Assistant. It you want these over-ritten please ensure that overwrite_ha_on_restart is set to true:

overwrite_ha_on_restart: true

PV_Opt needs to know the size of your battery and the power of your inverter: both when inverting battery to AC power and when chargingh tha battery. It will attempt to work these out from the data it has loaded (WIP) but you should check the following enitities in Home Assistant:

System Parameters

Parameter Units Entity Default Value
Battery Capacity Wh number.pvopt_batter_capacity_wh 10000
Inverter Power W number.pvopt_inverter_power_watts 3600
Charger Power W number.pvopt_charger_power_watts 3500
Inverter Efficiency % number.pvopt_inverter_efficiency 97%
Charger Efficiency % number.pvopt_charger_efficiency 91%

There are then only a few things to control the optimisation process. These have been grouped as follows:

Control Parameters

These are the main parameters that will control how PV Opt runs:
Parameter Units Entity Default Description
Read Only Mode on/off switch.pvopt_read_only On Controls whether the app will actually control the inverter. Start with this on until you are happy the charge/discharge plan makes sense.
Optimise Charging on/off switch.pvopt_forced_charge On Controls whether the app will calculate an Optimised plan. If off only the Base forecast will be updated.
Optimise Discharging on/off switch.pvopt_forced_discharge On Controls whether the app will allow for forced discharge as well as charge
Allow Cyclic on/off switch.pvopt_allow_cyclic On Controls whether the app will allow cycles of alternating charge/discharge
Use Solar on/off switch.pvopt_use_solar On Controls whether the app will use the Solcast solar forecast. If set to Off no solar will be used but battery charging can still be optimised for a time-of use tariff.
Solcast Confidence Level number number.pvopt_solcast_confidence_level Solcast Selects which the Confidence Level for the Solcast forecast. Levels between 10% and 50% are weighted from the Solcast 10% and 50% forecasts. Levels between 50% and 90% are weighted from the Solcast 50% and 10% forecasts.
Optimser Frequency minutes number.pvopt_optimise_frequency_minutes 10 Frequency of Optimiser calculation

Consumption Parameters

These parameters will define how PV Opt estimates daily consumption:
Parameter Units Entity Default Description
Use Consumption History on/off switch.pvopt_use_consumption_history On Toggles whether to use actual consumption history or an estimated daily consumption
Consumption History Parameters
Load History Days days number.pvopt_consumption_history_days 7 Number of days of consumption history to use when predicting future load
Load Margin % number.pvopt_consumption_margin 10% Margin to add to historic load for forecast (safety factor)
Weekday Weighting fraction number.pvopt_day_of_week_weighting 0.5 Defines how much weighting to give to the day of the week when averaging the load. 0.0 will use the simple average of the last n days based on load_history_days and 1.0 will just used the same day of the week within that window. Values inbetween will weight the estimate accordingly. If every day is the same use a low number. If your usage varies daily use a high number.
Daily Consumption Parameters
Daily Consumption kWh number.pvopt_daily_consumption_kwh 17 Estimated daily consumption to use when predicting future load
Shape Consumption Profile on/off switch.pvopt_shape_consumption_profile On Defines whether to shapoe the consumption to a typical daily profile (on) or to assume constant usage (off)

Tuning Parameters

These parameters will tweak how PV Opt runs:
Parameter Units Entity Default Description
Pass threshold % number.pvopt_pass_throshold_p 4p The incremental cost saving that each iteration of the optimiser needs to show to be included. Reducing the threshold may marginally reduce the predicted cost but have more marginal charge windows.
Discharge threshold % number.pvopt_discharge_throshold_p 5p The incremental cost saving that each iteration of the discharge optimiser needs to show to be included. Reducing the threshold may marginally reduce the predicted cost but have more marginal discharge windows.
Slot threshold % number.pvopt_slop_throshold_p 1p The incremental cost saving that each 30 minute slot of the optimiser needs to show to be included. Reducing the threshold may marginally reduce the predicted cost but have more marginal charge/discharge windows.
Power Resolution W number.pvopt_forced_power_group_tolerance 100 The resolution at which forced charging / discharging is reported. Changing this will change the reporting of the charge plan but not the actual detail of it.

Alternative Tariffs

PV Opt can also check what each day would have cost using any combination of Octopus tariffs. Run over time this can give you an idea of whether it would be worth switching. To enable this simply add a block like this to `config.yaml`:
id_daily_solar: sensor.{device_name}_power_generation_today
alt_tariffs:
  - name: Agile_Fix
    octopus_import_tariff_code: E-1R-AGILE-23-12-06-G
    octopus_export_tariff_code: E-1R-OUTGOING-FIX-12M-19-05-13-G

  - name: Eco7_Fix
    octopus_import_tariff_code: E-2R-VAR-22-11-01-G
    octopus_export_tariff_code: E-1R-OUTGOING-FIX-12M-19-05-13-G

  - name: Flux
    octopus_import_tariff_code: E-1R-FLUX-IMPORT-23-02-14-G
    octopus_export_tariff_code: E-1R-FLUX-EXPORT-23-02-14-G

In this example three alternatives are tested. For each tariff pair the Base and Optimised net cost for yesterday are calculated and saved to an entity called sensor.pvopt_opt_cost_name. The state of this entity is the optimised cost and the base cost is saved as the net_base attribute.

Output

The app always produces a Base forecast of future battery SOC and the associated grid flow based on the forecast solar performance, the expected consumption and prices with no forced charging or discharging from the grid.. The total cost for today and tomorrow is written to sensor.pvopt_base_cost and the associated SOC vs time is written to the attributes of this entity allowing it to be graphes using apex-charts.

If Optimise Charging is enabled, an optimsised charging plan is calculated and writtemt to sensor.pvopt_opt_cost. This will also include a list of forced charge and discharge windows.

The easiest way to control and visualise this is through the dashboards/pvopt_dashboard.yaml Lovelace yaml file included in this repo. If you're using the Solis Cloud integration, you can start with the dashboards/pvopt_dashboard_solis_cloud.yaml. Note that you will need to manually paste this into a dashboard and edit the charts to use the correct Octopus Energy sensors:

Alt text

This dashboards uses a couple of template sensors and time which will need adding to /homeassistant/configuration.yaml:

template:
  - sensor:
    - name: "Solis Grid Export Power"
      unique_id: solis_grid_export_power
      unit_of_measurement: W
      device_class: power
      state_class: measurement
      state: >-
        {{max(states('sensor.solis_meter_active_power') | float(0),0)}}    

    - name: "Solis Grid Import Power"
      unique_id: solis_grid_import_power
      unit_of_measurement: W
      device_class: power
      state_class: measurement
      state: >-
        {{max(-(states('sensor.solis_meter_active_power') | float(0)),0)}}

sensor:
  - platform: time_date
    display_options:
      - 'time'
      - 'date'
      - 'date_time'
      - 'date_time_utc'
      - 'date_time_iso'    

The dashboards also depend on the following Frontend components from HACS:

  • template-entity-row
  • bar-card
  • card-mod
  • Stack In Card
  • layout-card
  • apexcharts-card

Development - Adding Additional Inverters: the PV Opt API

PV Opt is designed to be pluggable. A simple API is used to control inverters. This is defined as follows:

Inverter Type

Each inverter type is defined by a string in the config.yaml file. This should be of the format: BRAND_INTEGRATION for example SOLIS_SOLAX_MODBUS.

Inverter Module

PV Opt expects one module per inverter brand named brand.py which includes drives for all integrations/models associated with that brand. For example solis.py includes the drivers for SOLIS_SOLAX_MODBUS, SOLIS_CORE_MODBUS and SOLIS_SOLARMAN

Each module exposes the following:

Classes

The module exposes a single class InverterController(inverter_type, host). The two required initialisation parameters are:

Parameter Type Description
inverter_type str The inverter_type string from the config.yaml file
host PVOpt The instance of PV Opt that has instantiated the inverter class. This allows the class to, for instance, write to the main log file simply be setting self.log=host.log and then calling self.log()

Class Attributes

The InverterController class must expose the following:

Attribute Key Type Description Example from SOLIS_SOLAX_MODBUS
.config dict This dict contains all the names of all the entities that PV Opt requires to run plus a few other parameters that are common to all inverters. Some entries must be an entity_id: keys for these itesms start with id_. Others may be enitity_ids or numbers. entity_ids should ideally include {device_name} to allow for the subsititution of a defined device name where appropriate.
maximum_dod_percent str / int Maximum depth of discharge - nay be an entity_id or a number number.{device_name}_battery_minimum_soc
update_cycle_seconds int Time in seconds between HA updates 15
supports_hold_soc bool Flags whether the integration supports holding a fixed SOC true
id_battery_soc str entity_id of Battery State of Charge number.{device_name}_battery_soc
id_consumption_today str entity_id of Daily Consumption Total sensor.{device_name}_house_load_today
id_grid_import_today str entity_id of Daily Grid Import Total sensor.{device_name}_grid_import_today
id_grid_export_today str entity_id of Daily Grid Export Total sensor.{device_name}_grid_export_today
.brand_config dict This dict contains all the names of all the entities that this brand/integration requires. These are only exposed for logging purposes and to allow the plug-in to use methods from the main app that use query entity_ids such as .get_config(entity_id) . A limited number of examples are given as this will vary for each plug-in.
battery_voltage str / int Battery voltage for converting power to current - nay be an entity_id or a number sensor.{device_name}_battery_voltage
id_timed_charge_start_hours str entity_id of Timed Charge Start Hours number.{device_name}_timed_charge_start_hours
id_timed_charge_start_minutes str entity_id of Timed Charge Start Minutes number.{device_name}_timed_charge_start_minutes
id_timed_charge_end_hours str entity_id of Timed Charge End Hours number.{device_name}_timed_charge_end_hours
id_timed_charge_end_minutes str entity_id of Timed Charge End Minutes number.{device_name}_timed_charge_end_minutes
id_timed_charge_current str entity_id of Timed Charge Current number.{device_name}_timed_charge_current
.status dict This dict reports the current status of the inverter
charge dict Dict of the Timed Charge Status with the following keys: active: bool, start: datetime, end: datetime, power: float
discharge dict Dict of the Timed Discharge Status with the following keys: active: bool, start: datetime, end: datetime, power: float
hold_soc dict Dict of the Hold_SOC Status with the following keys: active: bool, soc: int

Methods

The InverterController class must expose the following:

Method Parameters Returns Description
.enable_timed_mode() - None Switches the inverter mode to support timed changing and discharging
.control_charge() enable: bool None Enable or disable timed charging
start: datetime, optional Start time of timed slot (default = don't set start)
end: datetime, optional End time of timed slot (default = don't set end)
power: float, optional Maximum power of timed slot (default = don't set power)
.control_discharge() enable: bool None Enable or disable timed discharging
start: datetime, optional Start time of timed slot (default = don't set start)
end: datetime, optional End time of timed slot (default = don't set end)
power: float, optional Maximum power of timed slot (default = don't set power)
.hold_soc() soc None Switch inverter mode to hold specified SOC (if supported)

PV Opt Methods Available to the Inverter

The following methods may be useful for the inverter to call. If self.host is initialised to host they can be called using self.host.method(). As PV Opt is a sub-class of hass.HASS it includes all the AppDAemon methods listed here: https://appdaemon.readthedocs.io/en/latest/AD_API_REFERENCE.html

Method Parameters Returns / Decsription
self.host.log string Write string to the log file.
level: str, optional Optionally set the error level (default = INFO)
self.host.get_state() entity_id Home Assitant state of the entity
attributes: str, optional If attributes is set the attribute rather than the state is returned. If attribute is set to all a dict of all attributes is returned.
self.host.set_state() state Set the Home Assitant state of the entity and optionally the attributes. Returns a dict of the new state
entity_id: str
attributes: dict, optional
self.host.entity_exists() entity_id: str bool that confirms whether an entity exists in Home Assistant
self.host.call_service() service: str Call service in Home Assistant
data: dict, optional Data to be supplied to the service e.g. for writing to the Solis Modbus registers: data={"hub": "solis", "slave": 1, "address": 43011, "value": 15}