diff --git a/.gitignore b/.gitignore
index 76a29a7..0176dba 100644
--- a/.gitignore
+++ b/.gitignore
@@ -160,4 +160,18 @@ cython_debug/
#.idea/
nordpool.py
-brm.py
\ No newline at end of file
+brm.py
+
+# Ignore files specific to VSCode
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+!.vscode/*.code-snippets
+
+# Local History for Visual Studio Code
+.history/
+
+# Built Visual Studio Code Extensions
+*.vsix
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..9b38853
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,7 @@
+{
+ "python.testing.pytestArgs": [
+ "tests"
+ ],
+ "python.testing.unittestEnabled": false,
+ "python.testing.pytestEnabled": true
+}
\ No newline at end of file
diff --git a/README.md b/README.md
index 8724332..7471520 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# PV Opt: Home Assistant Solar/Battery Optimiser v3.15.5
+# PV Opt: Home Assistant Solar/Battery Optimiser v3.16.1
This documentation needs updating!
@@ -9,6 +9,8 @@ The application will integrate fully with Solis inverters which are controlled u
- [Home Assistant Solax Modbus Integration](https://github.com/wills106/homeassistant-solax-modbus)
- [Home Assistant Core Modbus Integration](https://github.com/fboundy/ha_solis_modbus)
- [Home Assistant Solarman Integration](https://github.com/StephanJoubert/home_assistant_solarman)
+- [Home Assistant Solis Sensor Integration](https://github.com/hultenvp/solis-sensor) (read-only mode)
+- [Home Assistant Solis Control Integration](https://github.com/stevegal/solis_control) (allows inverter control via solis_cloud and HA automations)
Once installed it should require miminal configuration. Other inverters/integrations can be added if required or can be controlled indirectly using automations.
@@ -49,7 +51,7 @@ This app is not stand-alone it requires the following:
2. Install HACS
1. Install HACS: https://hacs.xyz/docs/setup/download
-2. Enable AppDaemon in HACS: https://hacs.xyz/docs/categories/appdaemon_apps/
+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)
@@ -67,7 +69,9 @@ This excellent integration will pull Octopus Price data in to Home Assistant. So
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) or the HA Core Modbus as described here: https://github.com/fboundy/ha_solis_modbus. 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.
+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](https://github.com/hultenvp/solis-sensor) and [Solis-Control](https://github.com/hultenvp/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:
@@ -88,6 +92,15 @@ At present this app only works directly with Solis hybrid inverters using either
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
@@ -101,9 +114,7 @@ Follow the Github instructions here: https://github.com/StephanJoubert/home_assi
7. Install Mosquitto MQTT Broker
-1. Click the Home Assistant My button below to open the add-on on your Home Assistant instance:
-
- [![](https://camo.githubusercontent.com/c16bd5d7acfc6d5163636b546783e9217e27a401c1ac5bfd93a2ef5fa23e15fe/68747470733a2f2f6d792e686f6d652d617373697374616e742e696f2f6261646765732f73757065727669736f725f6164646f6e2e737667)](https://my.home-assistant.io/redirect/supervisor_addon/?addon=a0d7b954_appdaemon&repository_url=https%3A%2F%2Fgithub.com%2Fhassio-addons%2Frepository)
+1. Navigate to Settings -> Addons and click "Mosquito Broker"
2. Click on Install
@@ -113,6 +124,10 @@ Follow the Github instructions here: https://github.com/StephanJoubert/home_assi
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.
@@ -125,7 +140,7 @@ AppDaemon is a loosely coupled, multi-threaded, sandboxed python execution envir
1. Click the Home Assistant My button below to open the add-on on your Home Assistant instance:
- [![](https://camo.githubusercontent.com/c16bd5d7acfc6d5163636b546783e9217e27a401c1ac5bfd93a2ef5fa23e15fe/68747470733a2f2f6d792e686f6d652d617373697374616e742e696f2f6261646765732f73757065727669736f725f6164646f6e2e737667)](http://homeassistant.local:8123/hassio/addon/core_mosquitto/info)
+ [![](https://camo.githubusercontent.com/a54868bd2c4edb2d623ab2fef3d074fe711b45c2c1cdc0fbe4dfd296faa594f8/68747470733a2f2f6d792e686f6d652d617373697374616e742e696f2f6261646765732f73757065727669736f725f6164646f6e2e737667)](https://my.home-assistant.io/redirect/supervisor_addon/?addon=a0d7b954_appdaemon&repository_url=https%3A%2F%2Fgithub.com%2Fhassio-addons%2Frepository)
2. Click on Install
@@ -166,6 +181,11 @@ AppDaemon is a loosely coupled, multi-threaded, sandboxed python execution envir
api:
hadashboard:
+And add the `client_user` and `client_password` keys to `secrets.yaml` like this:
+
+ mqtt-user: some_user
+ mqtt-password: some_password
+
3. 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:
@@ -303,6 +323,126 @@ Restarts between Home Assistant and Add-Ons are not synchronised so it is helpfu
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: <>
+ key_id: "<>"
+ username: <>
+ password: <>
+ plantId: "<>"
+ 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: <>
+ key_id: "<>"
+ username: <>
+ password: <>
+ plantId: "<>"
+ action: pyscript.solis_control
+mode: single
+```
+
Configuration
@@ -394,7 +534,7 @@ The app always produces a Base forecast of future battery SOC and the associated
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 `pvopt_dashboard.yaml` Lovelace yaml file included in this repo. Note that you will need to manually paste this into a dashboard and edit the charts to use the correct Octopus Energy sensors:
+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](image-1.png)
diff --git a/apps/pv_opt/config/config.yaml b/apps/pv_opt/config/config.yaml
index eb84d15..ebe51e8 100644
--- a/apps/pv_opt/config/config.yaml
+++ b/apps/pv_opt/config/config.yaml
@@ -35,6 +35,28 @@ pv_opt:
# If true the personal data will be redacted from the log files.
# redact_personal_data_from_log: false
+ #=======================================
+ #Logging Category Control
+ #=======================================
+ #Defines Logging subjects to add to logfile
+ #If commented out, everything is logged
+ #Ignored if "debug" is set to False above
+ #
+ # S = Startup/Initialisation Logging
+ # T = Tariff loading Logging
+ # P = Power consumption history Logging
+ # C = Charge algorithm Logging
+ # D = Discharge algorithm Logging
+ # W = Charge/Discharge Windows Logging
+ # F = Power Flows Logging
+ # V = Power Flows debugging (extra verbose)
+ # I = inverter control/commands Logging
+ # E = EV debugging
+
+ # Letters can be added to "debug_categories" in any order
+
+ debug_categories: W
+
# ========================================
# Basic parameters
# ========================================
@@ -71,28 +93,28 @@ pv_opt:
# Consumption forecast parameters
# ========================================
#
- use_consumption_history: false
- # consumption_history_days: 6
- #
- daily_consumption_kwh: 17
- shape_consumption_profile: true
- consumption_shape:
- - hour: 0
- consumption: 300
- - hour: 0.5
- consumption: 200
- - hour: 6
- consumption: 150
- - hour: 8
- consumption: 500
- - hour: 15.5
- consumption: 500
- - hour: 17
- consumption: 750
- - hour: 22
- consumption: 750
- - hour: 24
- consumption: 300
+ # use_consumption_history: false
+ # # consumption_history_days: 6
+ # #
+ # daily_consumption_kwh: 17
+ # shape_consumption_profile: true
+ # consumption_shape:
+ # - hour: 0
+ # consumption: 300
+ # - hour: 0.5
+ # consumption: 200
+ # - hour: 6
+ # consumption: 150
+ # - hour: 8
+ # consumption: 500
+ # - hour: 15.5
+ # consumption: 500
+ # - hour: 17
+ # consumption: 750
+ # - hour: 22
+ # consumption: 750
+ # - hour: 24
+ # consumption: 300
# ========================================
# Octopus account parameters
@@ -117,6 +139,22 @@ pv_opt:
# octopus_import_tariff_code: E-1R-GO-VAR-22-10-14-N
# octopus_export_tariff_code: E-1R-OUTGOING-LITE-FIX-12M-23-09-12-N
+
+ # ========================================
+ # EV parameters
+ # ========================================
+ #
+ # If you have a Zappi Charger, uncomment next line to allow Pv_opt to detect car plugin status
+ # for use with the Octopus Intelligent Tariff and to allow EV consumption data to be read (if needed).
+ #
+ # ev_charger: Zappi # Default = None
+ #
+ # By default, Zappi is set to seen as part of the house load to prevent house battery discharge during car charging and allow discounting of EV consumption
+ # from house charging calculations.
+ #
+ # If your Zappi is not seen as part of the house load, uncomment next line to set to False.
+ #
+ # ev_part_of_house_load = False # Default = True
# ===============================================================================================================
# Brand / Integration Specific Config: SOLIS_SOLAX_MODBUS: https://github.com/wills106/homeassistant-solax-modbus
@@ -253,15 +291,15 @@ pv_opt:
# # - sensor.{device_name}_pv_power_2
id_solar_power: sensor.{device_name}_pv_total_power
- 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
+ # 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
\ No newline at end of file
+ # - 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
diff --git a/apps/pv_opt/pv_opt.py b/apps/pv_opt/pv_opt.py
index 2ac2468..094e3e5 100644
--- a/apps/pv_opt/pv_opt.py
+++ b/apps/pv_opt/pv_opt.py
@@ -12,7 +12,7 @@
from numpy import nan
import re
-VERSION = "3.15.5"
+VERSION = "3.16.1"
OCTOPUS_PRODUCT_URL = r"https://api.octopus.energy/v1/products/"
@@ -183,7 +183,7 @@
"default": 4.0,
"attributes": {
"min": 0.0,
- "max": 1.0,
+ "max": 10.0,
"step": 0.5,
"mode": "slider",
},
@@ -1185,6 +1185,9 @@ def _load_args(self, items=None):
self.yaml_config[item] = self.config[item]
elif "id_" in item:
+ self.log(f">>> Test: {self.entity_exists('update.home_assistant_core_update')}")
+ for v in values:
+ self.log(f">>> {item} {v} {self.entity_exists(v)}")
if min([self.entity_exists(v) for v in values]):
if len(values) == 1:
self.config[item] = values[0]
@@ -2755,8 +2758,7 @@ def _check_tariffs_vs_bottlecap(self):
df = pd.DataFrame(
self.get_state_retry(self.bottlecap_entities[direction], attribute=("rates"))
).set_index("start")["value_inc_vat"]
- df.index = pd.to_datetime(df.index)
- df.index = df.index.tz_convert("UTC")
+ df.index = pd.to_datetime(df.index, utc=True)
df *= 100
df = pd.concat(
[
diff --git a/apps/pv_opt/pvpy.py b/apps/pv_opt/pvpy.py
index d60cdb8..c9dd91b 100644
--- a/apps/pv_opt/pvpy.py
+++ b/apps/pv_opt/pvpy.py
@@ -306,7 +306,7 @@ def _get_agile_predict(self):
return
df = pd.DataFrame(r.json()[0]["prices"]).set_index("date_time")
- df.index = pd.to_datetime(df.index).tz_convert("UTC")
+ df.index = pd.to_datetime(df.index, utc=True)
return df["agile_pred"]
def get_day_ahead(self, start):
diff --git a/pvopt_control_card.yaml b/dashboards/pvopt_control_card.yaml
similarity index 100%
rename from pvopt_control_card.yaml
rename to dashboards/pvopt_control_card.yaml
diff --git a/pvopt_dashboard.yaml b/dashboards/pvopt_dashboard.yaml
similarity index 100%
rename from pvopt_dashboard.yaml
rename to dashboards/pvopt_dashboard.yaml
diff --git a/dashboards/pvopt_dashboard_solis_cloud.yaml b/dashboards/pvopt_dashboard_solis_cloud.yaml
new file mode 100644
index 0000000..081ed9f
--- /dev/null
+++ b/dashboards/pvopt_dashboard_solis_cloud.yaml
@@ -0,0 +1,666 @@
+views:
+ - theme: Backend-selected
+ title: PV Opt Default
+ path: pv_opt_default
+ subview: false
+ type: custom:masonry-layout
+ layout:
+ max_cols: 4
+ reflow: true
+ icon: mdi:solar-power-variant
+ badges: []
+ cards:
+ - type: vertical-stack
+ cards:
+ - type: custom:bar-card
+ title: Status
+ entities:
+ - entity: sensor.solis_ac_output_total_power
+ unit_of_measurement: W
+ target: '{{states(''sensor.solcast_forecast_today'')}}'
+ name: Solar
+ max: 5000
+ icon: mdi:solar-power-variant
+ - entity: sensor.solis_total_consumption_power
+ name: Load
+ max: 5000
+ - entity: sensor.solis_remaining_battery_capacity
+ name: SOC
+ max: 100
+ icon: mdi:battery
+ - entity: sensor.solis_grid_import_power
+ name: Import Power
+ max: 5000
+ min: 0
+ - entity: sensor.solis_grid_export_power
+ name: Export Power
+ max: 5000
+ min: 0
+ - entity: sensor.solis_battery_power
+ name: Battery Charge/Discharge
+ max: 5000
+ min: -5000
+ - type: entities
+ entities:
+ - type: custom:template-entity-row
+ name: Inverter Time
+ state: >-
+ {{((as_local(as_datetime(states('sensor.solis_rtc'))))|string)[11:16]}}
+ - type: markdown
+ content: >
+ Cost Summary (GBP)
+
+
+ | | Today | Tomorrow | Total |
+
+ |:--|---:|---:|---:|
+
+ |Base | {{'%8.2f' |
+ format(state_attr('sensor.pvopt_base_cost','cost_today')| float)}}
+ | {{'%0.2f' |
+ format(state_attr('sensor.pvopt_base_cost','cost_tomorrow')|
+ float)}} | {{'%0.2f' | format(states('sensor.pvopt_base_cost')|
+ float)}} |
+
+ |Optimised | {{'%8.2f' |
+ format(state_attr('sensor.pvopt_opt_cost','cost_today')| float)}}
+ | {{'%0.2f' |
+ format(state_attr('sensor.pvopt_opt_cost','cost_tomorrow')|
+ float)}} | {{'%0.2f' | format(states('sensor.pvopt_opt_cost')|
+ float)}} |
+
+ |Cost Saving | {{'%8.2f' |
+ format((state_attr('sensor.pvopt_base_cost','cost_today')|
+ float-state_attr('sensor.pvopt_opt_cost','cost_today')| float) |
+ round(2))}} |{{'%0.2f' |
+ format((state_attr('sensor.pvopt_base_cost','cost_tomorrow')|
+ float-state_attr('sensor.pvopt_opt_cost','cost_tomorrow')| float)
+ | round(2))}} | {{'%0.2f' |
+ format((states('sensor.pvopt_base_cost')| float -
+ states('sensor.pvopt_opt_cost')| float) | round(2)) }} |
+
+
+ Optimisation Breakdown (GBP)
+
+
+ | | Cost ||
+
+ |:--|:--|:--|
+
+ {% set x = state_attr("sensor.pvopt_opt_cost","Summary")%}{%for y
+ in x%}|{{y}}|{{('%0.2f' |
+ format(x[y]['cost']))}}|{{x[y]['Selected']}}|
+
+ {%endfor%}
+
+
+
+ Charge Plan
+
+
+ | Start | | | End ||| Power ||| Start SOC ||| End SOC | Hold SOC
+ |
+
+ |:-------|--|--|:---------|--|--|:--------:|--|--|:--------:|--|--|:----------:|:--|{%
+ for a in state_attr('sensor.pvopt_charge_start', 'windows') %}
+
+ {% set tf = '%d-%b %H:%M %Z'%} |
+ {{as_local(as_datetime(a['start'])).strftime(tf)}} |||
+ {{as_local(as_datetime(a['end'])).strftime(tf)}} ||| {{a['forced']
+ | float | round(0)}}W ||| {{a['soc'] | float | round(1)}}% |||
+ {{a['soc_end'] | float | round(1)}}% | {{a['hold_soc']}}
+ |{%endfor%}
+ title: PV Opt Results
+ - type: custom:stack-in-card
+ title: Optimised Charging
+ cards:
+ - type: entities
+ entities:
+ - entity: sensor.pvopt_status
+ name: Status
+ - type: markdown
+ content: Control Parameters
+ - type: entities
+ entities:
+ - entity: number.pvopt_optimise_frequency_minutes
+ name: Optimiser Freq (mins)
+ - entity: switch.pvopt_read_only
+ name: Read Only Mode
+ - entity: switch.pvopt_include_export
+ name: Include Export
+ - type: conditional
+ conditions:
+ - condition: state
+ entity: switch.pvopt_include_export
+ state: 'on'
+ card:
+ type: entities
+ entities:
+ - entity: switch.pvopt_forced_discharge
+ name: Optimise Discharging
+ - type: conditional
+ conditions:
+ - condition: state
+ entity: switch.pvopt_forced_discharge
+ state: 'on'
+ - condition: state
+ entity: switch.pvopt_include_export
+ state: 'on'
+ card:
+ type: entities
+ entities:
+ - entity: switch.pvopt_allow_cyclic
+ name: Allow Cyclic Charge/Discharge
+ - type: markdown
+ content: Solar
+ - type: entities
+ entities:
+ - entity: switch.pvopt_use_solar
+ name: Use Solar
+ - type: conditional
+ conditions:
+ - condition: state
+ entity: switch.pvopt_use_solar
+ state: 'on'
+ card:
+ type: entities
+ entities:
+ - entity: number.pvopt_solcast_confidence_level
+ name: Solcast Confidence Level
+ - type: markdown
+ content: Consumption and EV
+ - type: entities
+ entities:
+ - entity: switch.pvopt_use_consumption_history
+ name: Use Consumption History
+ - type: conditional
+ conditions:
+ - condition: state
+ entity: switch.pvopt_use_consumption_history
+ state: 'on'
+ card:
+ type: entities
+ entities:
+ - entity: number.pvopt_consumption_history_days
+ name: Load History Days
+ - entity: number.pvopt_consumption_margin
+ name: Load Margin
+ - entity: number.pvopt_day_of_week_weighting
+ name: Weekday Weighting
+ - type: conditional
+ conditions:
+ - condition: state
+ entity: switch.pvopt_use_consumption_history
+ state: 'off'
+ card:
+ type: entities
+ entities:
+ - entity: number.pvopt_daily_consumption_kwh
+ name: Daily Consumption (kWh)
+ - entity: switch.pvopt_shape_consumption_profile
+ name: Shape Consumption Profile
+ - type: entities
+ entities:
+ - entity: select.pvopt_ev_charger
+ name: EV Charger
+ - type: conditional
+ conditions:
+ - condition: state
+ entity: select.pvopt_ev_charger
+ state_not: None
+ card:
+ type: entities
+ entities:
+ - entity: number.pvopt_ev_charger_power_watts
+ name: EV Charger Power
+ - entity: number.pvopt_ev_battery_capacity_kwh
+ name: EV Battery Capacity
+ - type: markdown
+ content: System Parameters
+ - type: entities
+ entities:
+ - entity: number.pvopt_battery_capacity_wh
+ name: Battery Capacity
+ - entity: number.pvopt_inverter_power_watts
+ name: Inverter Power
+ - entity: number.pvopt_charger_power_watts
+ name: Charger Power
+ - entity: number.pvopt_inverter_efficiency_percent
+ name: Inverter Efficiency
+ - entity: number.pvopt_charger_efficiency_percent
+ name: Charger Efficiency
+ - entity: number.pvopt_battery_current_limit_amps
+ name: Battery Current Limit
+ - type: markdown
+ content: Tuning Parameters
+ - type: entities
+ entities:
+ - entity: number.pvopt_pass_threshold_p
+ name: Charge threshold (p)
+ - entity: number.pvopt_discharge_threshold_p
+ name: Discharge threshold (p)
+ - entity: number.pvopt_plunge_threshold_p_kwh
+ name: Plunge threshold (p/kWh)
+ - entity: number.pvopt_slot_threshold_p
+ name: Threshold per slot (p)
+ - entity: number.pvopt_forced_power_group_tolerance
+ name: Power Resolution
+ - type: custom:stack-in-card
+ cards:
+ - type: vertical-stack
+ cards:
+ - type: markdown
+ content: Results
+ - type: entities
+ entities:
+ - entity: sensor.solis_remaining_battery_capacity
+ name: Current Battery SOC
+ - type: custom:template-entity-row
+ name: Next Charge/Discharge Slot Start
+ state: >-
+ {{(as_local(as_datetime(states('sensor.pvopt_charge_start')))|string)[:16]}}
+ icon: mdi:timer-sand-complete
+ - type: custom:template-entity-row
+ name: Next Charge/Discharge Slot End
+ state: >-
+ {{(as_local(as_datetime(states('sensor.pvopt_charge_end')))|string)[:16]}}
+ icon: mdi:timer-sand-complete
+ - entity: sensor.pvopt_charge_current
+ name: Optimum Charge Current
+ - entity: sensor.solis_battery_current
+ name: Battery Current
+ state_color: true
+ show_header_toggle: false
+ - type: custom:apexcharts-card
+ apex_config:
+ chart:
+ height: 234px
+ yaxis:
+ - id: power
+ show: true
+ decimals: 0
+ apex_config:
+ forceNiceScale: true
+ header:
+ show: true
+ show_states: true
+ colorize_states: true
+ title: Solar Forecasts vs Actual
+ graph_span: 2d
+ stacked: false
+ span:
+ start: day
+ series:
+ - entity: sensor.solis_total_consumption_power
+ float_precision: 0
+ extend_to: now
+ group_by:
+ func: avg
+ duration: 30min
+ - entity: sensor.solis_ac_output_total_power
+ float_precision: 0
+ extend_to: now
+ name: Actual
+ stroke_width: 1
+ type: column
+ color: '#ff7f00'
+ group_by:
+ func: avg
+ duration: 30min
+ offset: +15min
+ show:
+ name_in_header: true
+ in_header: true
+ in_chart: true
+ legend_value: true
+ offset_in_name: false
+ - entity: sensor.pvopt_opt_cost
+ type: line
+ name: Forecast Consumption
+ color: yellow
+ opacity: 0.7
+ stroke_width: 2
+ offset: +15min
+ show:
+ in_header: false
+ legend_value: false
+ data_generator: |
+ return entity.attributes.consumption.map((entry) => {
+ return [new Date(entry.period_start), entry.consumption];
+ });
+ - entity: sensor.solcast_pv_forecast_forecast_today
+ type: area
+ name: ''
+ color: cyan
+ opacity: 0.1
+ stroke_width: 0
+ unit: W
+ show:
+ in_header: false
+ legend_value: false
+ offset_in_name: false
+ data_generator: |
+ return entity.attributes.detailedForecast.map((entry) => {
+ return [new Date(entry.period_start), entry.pv_estimate90*1000];
+ });
+ offset: +15min
+ - entity: sensor.solcast_pv_forecast_forecast_today
+ type: area
+ name: ' '
+ color: '#1c1c1c'
+ opacity: 1
+ stroke_width: 0
+ unit: W
+ show:
+ in_header: false
+ legend_value: false
+ offset_in_name: false
+ data_generator: |
+ return entity.attributes.detailedForecast.map((entry) => {
+ return [new Date(entry.period_start), entry.pv_estimate10*1000];
+ });
+ offset: +15min
+ - entity: sensor.solcast_pv_forecast_forecast_today
+ type: line
+ name: Solcast
+ color: cyan
+ opacity: 1
+ stroke_width: 3
+ unit: W
+ show:
+ in_header: false
+ legend_value: false
+ offset_in_name: false
+ data_generator: |
+ return entity.attributes.detailedForecast.map((entry) => {
+ return [new Date(entry.period_start), entry.pv_estimate*1000];
+ });
+ offset: +15min
+ - entity: sensor.solcast_pv_forecast_forecast_tomorrow
+ type: area
+ name: Solcast
+ color: cyan
+ opacity: 0.1
+ stroke_width: 0
+ unit: W
+ show:
+ in_header: false
+ legend_value: false
+ offset_in_name: false
+ data_generator: |
+ return entity.attributes.detailedForecast.map((entry) => {
+ return [new Date(entry.period_start), entry.pv_estimate90*1000];
+ });
+ offset: +15min
+ - entity: sensor.solcast_pv_forecast_forecast_tomorrow
+ type: area
+ name: Solcast
+ color: '#1c1c1c'
+ opacity: 1
+ stroke_width: 0
+ unit: W
+ show:
+ in_header: false
+ offset_in_name: false
+ legend_value: false
+ data_generator: |
+ return entity.attributes.detailedForecast.map((entry) => {
+ return [new Date(entry.period_start), entry.pv_estimate10*1000];
+ });
+ offset: +15min
+ - entity: sensor.solcast_pv_forecast_forecast_tomorrow
+ type: line
+ name: Solcast
+ color: cyan
+ opacity: 1
+ stroke_width: 3
+ unit: W
+ show:
+ in_header: false
+ legend_value: false
+ offset_in_name: false
+ data_generator: |
+ return entity.attributes.detailedForecast.map((entry) => {
+ return [new Date(entry.period_start), entry.pv_estimate*1000];
+ });
+ offset: +15min
+ - type: custom:apexcharts-card
+ apex_config:
+ chart:
+ height: 234px
+ yaxis:
+ - id: soc
+ show: true
+ min: 0
+ max: 100
+ decimals: 0
+ apex_config:
+ tickAmount: 5
+ header:
+ show: true
+ show_states: true
+ colorize_states: true
+ title: Battery SOC Forecast vs Actual
+ graph_span: 2d
+ span:
+ start: day
+ series:
+ - entity: sensor.solis_remaining_battery_capacity
+ extend_to: now
+ name: Actual
+ stroke_width: 1
+ type: area
+ color: '#ff7f00'
+ opacity: 0.4
+ yaxis_id: soc
+ - entity: sensor.pvopt_opt_cost
+ type: line
+ name: Optimised
+ color: '#7f7fff'
+ opacity: 0.7
+ stroke_width: 2
+ unit: '%'
+ show:
+ in_header: false
+ legend_value: false
+ data_generator: |
+ return entity.attributes.soc.map((entry) => {
+ return [new Date(entry.period_start), entry.soc];
+ });
+ yaxis_id: soc
+ - entity: sensor.pvopt_base_cost
+ type: line
+ name: Initial
+ color: '#7fff7f'
+ opacity: 0.7
+ stroke_width: 2
+ unit: '%'
+ show:
+ in_header: false
+ legend_value: false
+ data_generator: |
+ return entity.attributes.soc.map((entry) => {
+ return [new Date(entry.period_start), entry.soc];
+ });
+ yaxis_id: soc
+ - type: custom:apexcharts-card
+ apex_config:
+ chart:
+ height: 230px
+ header:
+ show: true
+ show_states: true
+ colorize_states: true
+ title: Pricing and Forced Charge/Discharge
+ graph_span: 2d
+ yaxis:
+ - id: price
+ decimals: 0
+ min: -10
+ max: 70
+ apex_config:
+ tickAmount: 8
+ - id: charge
+ decimals: 0
+ opposite: true
+ show: true
+ min: -4000
+ max: 4000
+ apex_config:
+ tickAmount: 8
+ stacked: false
+ span:
+ start: day
+ series:
+ - entity: sensor.pvopt_opt_cost
+ type: column
+ name: Forced Charge
+ yaxis_id: charge
+ color: orange
+ opacity: 0.7
+ stroke_width: 2
+ unit: W
+ offset: '-15min'
+ show:
+ in_header: false
+ legend_value: false
+ offset_in_name: false
+ data_generator: |
+ return entity.attributes.forced.map((entry) => {
+ return [new Date(entry.period_start), entry.forced];
+ });
+ - entity: >-
+ event.octopus_energy_electricity_19m1337498_1610032016836_current_day_rates
+ yaxis_id: price
+ name: Historic Import Price
+ color: yellow
+ opacity: 1
+ stroke_width: 3
+ extend_to: now
+ unit: p/kWh
+ data_generator: |
+ return entity.attributes.rates.map((entry) => {
+ return [new Date(entry.start), entry.value_inc_vat*100];
+ });
+ offset: '-15min'
+ show:
+ in_header: false
+ legend_value: false
+ offset_in_name: false
+ - entity: >-
+ event.octopus_energy_electricity_19m1337498_1610032016836_export_current_day_rates
+ yaxis_id: price
+ name: Historic Export Price
+ color: cyan
+ opacity: 1
+ stroke_width: 3
+ extend_to: now
+ unit: p/kWh
+ data_generator: |
+ return entity.attributes.rates.map((entry) => {
+ return [new Date(entry.start), entry.value_inc_vat*100];
+ });
+ offset: '-15min'
+ show:
+ in_header: false
+ legend_value: false
+ offset_in_name: false
+ - entity: sensor.pvopt_opt_cost
+ type: line
+ name: Future Import Price
+ float_precision: 1
+ color: white
+ opacity: 1
+ stroke_width: 3
+ extend_to: now
+ unit: p/kWh
+ offset: '-15min'
+ show:
+ in_header: true
+ legend_value: false
+ offset_in_name: false
+ data_generator: |
+ return entity.attributes.import.map((entry) => {
+ return [new Date(entry.period_start), entry.import];
+ });
+ yaxis_id: price
+ - entity: sensor.pvopt_opt_cost
+ float_precision: 1
+ type: line
+ name: Future Export Price
+ color: green
+ opacity: 1
+ stroke_width: 3
+ extend_to: now
+ unit: p/kWh
+ offset: '-15min'
+ show:
+ in_header: true
+ legend_value: false
+ offset_in_name: false
+ data_generator: |
+ return entity.attributes.export.map((entry) => {
+ return [new Date(entry.period_start), entry.export];
+ });
+ yaxis_id: price
+ - type: custom:octopus-energy-rates-card
+ currentEntity: >-
+ event.octopus_energy_electricity_19m1337498_1610032016836_current_day_rates
+ futureEntity: >-
+ event.octopus_energy_electricity_19m1337498_1610032016836_next_day_rates
+ cols: 2
+ hour12: false
+ showday: true
+ showpast: false
+ title: Octopus Import
+ unitstr: p
+ lowlimit: 15
+ mediumlimit: 20
+ highlimit: 30
+ roundUnits: 2
+ cheapest: true
+ multiplier: 100
+ - type: custom:stack-in-card
+ cards:
+ - type: custom:apexcharts-card
+ apex_config:
+ chart:
+ height: 234px
+ header:
+ show: true
+ show_states: true
+ colorize_states: true
+ title: Optimised Daily Cost
+ graph_span: 7d
+ span:
+ start: day
+ offset: '-6d'
+ series:
+ - entity: sensor.pvopt_opt_cost_actual
+ name: Actual
+ extend_to: false
+ curve: stepline
+ float_precision: 2
+ show:
+ legend_value: false
+ - entity: sensor.pvopt_opt_cost_current
+ name: Current Contract
+ curve: stepline
+ float_precision: 2
+ extend_to: false
+ show:
+ legend_value: false
+ - entity: sensor.pvopt_opt_cost_eco7_fix
+ name: Eco7 / Fix
+ curve: stepline
+ float_precision: 2
+ extend_to: false
+ show:
+ legend_value: false
+ - entity: sensor.pvopt_opt_cost_flux
+ curve: stepline
+ name: Flux
+ float_precision: 2
+ show:
+ legend_value: false
+ extend_to: false
diff --git a/pvopt_test_card.yaml b/dashboards/pvopt_test_card.yaml
similarity index 100%
rename from pvopt_test_card.yaml
rename to dashboards/pvopt_test_card.yaml