diff --git a/README.md b/README.md
index ebe1409..5ef8daa 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# PV Opt: Home Assistant Solar/Battery Optimiser v3.18.0
+# PV Opt: Home Assistant Solar/Battery Optimiser v3.18.1
This documentation needs updating!
diff --git a/apps/pv_opt/pv_opt.py b/apps/pv_opt/pv_opt.py
index 640201e..57daece 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.18.0"
+VERSION = "3.18.1"
OCTOPUS_PRODUCT_URL = r"https://api.octopus.energy/v1/products/"
@@ -464,7 +464,7 @@ def initialize(self):
self.log("")
self.log(f"******************* PV Opt v{VERSION} *******************")
self.log("")
-
+ self.io = False
self.debug = DEBUG
self.redact_regex = REDACT_REGEX
@@ -599,6 +599,7 @@ def _run_test(self):
self._log_inverterstatus(self.inverter.status)
def _check_for_io(self):
+ self.io = False
self.ulog("Checking for Intelligent Octopus")
entity_id = f"binary_sensor.octopus_energy_{self.get_config('octopus_account').lower().replace('-', '_')}_intelligent_dispatching"
io_dispatches = self.get_state(entity_id)
@@ -725,7 +726,7 @@ def _load_pv_system_model(self):
self.battery_model = pv.BatteryModel(
capacity=self.get_config("battery_capacity_wh"),
- max_dod=self.get_config("maximum_dod_percent") / 100,
+ max_dod=self.get_config("maximum_dod_percent", 15) / 100,
current_limit_amps=self.get_config("battery_current_limit_amps", default=100),
voltage=self.get_config("battery_voltage", default=50),
)
@@ -989,6 +990,7 @@ def _load_contract(self):
self.rlog("")
self._load_saving_events()
+
self._check_for_io()
self.rlog("Finished loading contract")
@@ -1564,8 +1566,10 @@ def _expose_configs(self, over_write=True):
)
def status(self, status):
- entity_id = f"sensor.{self.prefix.lower()}status"
+ entity_id = f"sensor.{self.prefix.lower()}_status"
attributes = {"last_updated": pd.Timestamp.now().strftime(DATE_TIME_FORMAT_LONG)}
+ # self.log(f">>> {status}")
+ # self.log(f">>> {entity_id}")
self.set_state(state=status, entity_id=entity_id, attributes=attributes)
@ad.app_lock
@@ -2300,9 +2304,16 @@ def _write_output(self):
attributes={"Summary": self.summary_costs},
)
+ if len(self.windows) > 0:
+ hass_start = self.charge_start_datetime
+ hass_end = self.charge_end_datetime
+ else:
+ hass_start = pd.Timestamp.now().floor("1D")
+ hass_end = hass_start
+
self.write_to_hass(
entity=f"sensor.{self.prefix}_charge_start",
- state=self.charge_start_datetime,
+ state=hass_start,
attributes={
"friendly_name": "PV Opt Next Charge Period Start",
"device_class": "timestamp",
@@ -2325,7 +2336,7 @@ def _write_output(self):
self.write_to_hass(
entity=f"sensor.{self.prefix}_charge_end",
- state=self.charge_end_datetime,
+ state=hass_end,
attributes={
"friendly_name": "PV Opt Next Charge Period End",
},
@@ -2582,8 +2593,12 @@ def load_consumption(self, start, end):
consumption_dow = self.get_config("day_of_week_weighting") * dfx.iloc[: len(temp)]
if len(consumption_dow) != len(consumption_mean):
self.log(">>> Inconsistent lengths in consumption arrays")
- self.log(f">>> dow : {consumption_dow}")
- self.log(f">>> mean: {consumption_mean}")
+ self.log(f">>> dow : {len(consumption_dow)}")
+ self.log(f">>> mean: {len(consumption_mean)}")
+ idx = consumption_dow.index.intersection(consumption_mean.index)
+ self.log(f"Clipping the consumption to the overlap ({len(idx)/24:0.1f} days)", level="WARNING")
+ consumption_mean = consumption_mean.loc[idx]
+ consumption_dow = consumption_dow.loc[idx]
consumption["consumption"] += pd.Series(
consumption_dow.to_numpy()
diff --git a/apps/pv_opt/solis.py b/apps/pv_opt/solis.py
index cbba11f..a436fcd 100644
--- a/apps/pv_opt/solis.py
+++ b/apps/pv_opt/solis.py
@@ -258,6 +258,8 @@ class SolisCloud:
"atRead": "/v2/api/atRead",
}
+ MAX_RETRIES = 5
+
def __init__(self, username, password, key_id, key_secret, plant_id, **kwargs):
self.username = username
self.key_id = key_id
@@ -321,6 +323,8 @@ def inverter_details(self):
if response.status_code == HTTPStatus.OK:
return response.json()["data"]
+ else:
+ return {"state": 0}
@property
def is_online(self):
@@ -331,14 +335,25 @@ def last_seen(self):
return pd.to_datetime(int(self.inverter_details["dataTimestamp"]), unit="ms")
def read_code(self, cid):
- if self.token == "":
- self.login()
- body = self.get_body(inverterSn=self.inverter_sn, cid=cid)
- headers = self.header(body, self.URLS["atRead"])
- headers["token"] = self.token
- response = requests.post(self.URLS["root"] + self.URLS["atRead"], data=body, headers=headers)
- if response.status_code == HTTPStatus.OK:
- return response.json()["data"]["msg"]
+ retries = 0
+ data = "ERROR"
+ while (data == "ERROR") and (retries < self.MAX_RETRIES):
+ if self.token == "":
+ self.login()
+ body = self.get_body(inverterSn=self.inverter_sn, cid=cid)
+ headers = self.header(body, self.URLS["atRead"])
+ headers["token"] = self.token
+ response = requests.post(self.URLS["root"] + self.URLS["atRead"], data=body, headers=headers)
+ if response.status_code == HTTPStatus.OK:
+ data = response.json()["data"]["msg"]
+ else:
+ data = "ERROR"
+
+ if data == "ERROR":
+ self.token = ""
+ retries += 1
+ else:
+ return data
def set_code(self, cid, value):
if self.token == "":
@@ -547,7 +562,7 @@ def _solis_control_charge_discharge(self, direction, enable, **kwargs):
"start": kwargs.get("start", None),
"end": kwargs.get("end", None),
}
- power = kwargs.get("power")
+ power = kwargs.get("power", 0)
if times["start"] is not None:
times["start"] = times["start"].floor("1min")