Skip to content

Commit 2064fdf

Browse files
Component start order fixes (#2832)
* Phased start * Component order * [pre-commit.ci lite] apply automatic fixes --------- Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
1 parent 7efd550 commit 2064fdf

4 files changed

Lines changed: 52 additions & 21 deletions

File tree

‎apps/predbat/components.py‎

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
"db_days": {"required": False, "config": "db_days", "default": 30},
3434
},
3535
"can_restart": False,
36+
"phase": 0,
3637
},
3738
"ha": {
3839
"class": HAInterface,
@@ -45,14 +46,23 @@
4546
"db_primary": {"required": False, "config": "db_primary", "default": False},
4647
},
4748
"can_restart": False,
49+
"phase": 0,
4850
},
4951
"ha_history": {
5052
"class": HAHistory,
5153
"name": "Home Assistant History",
5254
"args": {},
5355
"can_restart": False,
56+
"phase": 0,
57+
},
58+
"web": {
59+
"class": WebInterface,
60+
"name": "Web Interface",
61+
"args": {
62+
"port": {"required": False, "config": "web_port", "default": 5052},
63+
},
64+
"phase": 0,
5465
},
55-
"web": {"class": WebInterface, "name": "Web Interface", "args": {"port": {"required": False, "config": "web_port", "default": 5052}}},
5666
"mcp": {
5767
"class": PredbatMCPServer,
5868
"name": "MCP Server",
@@ -61,6 +71,7 @@
6171
"mcp_secret": {"required": False, "config": "mcp_secret", "default": "predbat_mcp_secret"},
6272
"mcp_port": {"required": False, "config": "mcp_port", "default": 8199},
6373
},
74+
"phase": 1,
6475
},
6576
"solar": {
6677
"class": SolarAPI,
@@ -80,6 +91,7 @@
8091
"pv_scaling": {"required": False, "config": "pv_scaling", "default": 1.0},
8192
},
8293
"required_or": ["solcast_host", "forecast_solar", "pv_forecast_today"],
94+
"phase": 1,
8395
},
8496
"gecloud": {
8597
"class": GECloudDirect,
@@ -100,6 +112,7 @@
100112
"config": "ge_cloud_automatic",
101113
},
102114
},
115+
"phase": 1,
103116
},
104117
"gecloud_data": {
105118
"class": GECloudData,
@@ -123,6 +136,7 @@
123136
"config": "days_previous",
124137
},
125138
},
139+
"phase": 1,
126140
},
127141
"octopus": {
128142
"class": OctopusAPI,
@@ -143,6 +157,7 @@
143157
"config": "octopus_automatic",
144158
},
145159
},
160+
"phase": 1,
146161
},
147162
"ohme": {
148163
"class": OhmeAPI,
@@ -162,6 +177,7 @@
162177
"config": "ohme_automatic_octopus_intelligent",
163178
},
164179
},
180+
"phase": 1,
165181
},
166182
"fox": {
167183
"class": FoxAPI,
@@ -178,6 +194,7 @@
178194
"config": "fox_automatic",
179195
},
180196
},
197+
"phase": 1,
181198
},
182199
"alert_feed": {
183200
"class": AlertFeed,
@@ -190,6 +207,7 @@
190207
"config": "alerts",
191208
},
192209
},
210+
"phase": 1,
193211
},
194212
"carbon": {
195213
"class": CarbonAPI,
@@ -198,6 +216,7 @@
198216
"postcode": {"required": True, "config": "carbon_postcode"},
199217
"automatic": {"required": False, "config": "carbon_automatic", "default": False},
200218
},
219+
"phase": 1,
201220
},
202221
}
203222

@@ -209,11 +228,13 @@ def __init__(self, base):
209228
self.base = base
210229
self.log = base.log
211230

212-
def initialize(self, only=None):
231+
def initialize(self, only=None, phase=0):
213232
"""Initialize components without starting them"""
214233
for component_name, component_info in COMPONENT_LIST.items():
215234
if only and component_name != only:
216235
continue
236+
if component_info.get("phase", 0) != phase:
237+
continue
217238

218239
have_all_args = True
219240
self.components[component_name] = None
@@ -246,14 +267,16 @@ def initialize(self, only=None):
246267
self.log(f"Initializing {component_info['name']} interface")
247268
self.components[component_name] = component_info["class"](*arg_dict.values(), self.base)
248269

249-
def start(self, only=None):
270+
def start(self, only=None, phase=0):
250271
"""Start all initialized components"""
251272
failed = False
252273
for component_name, component_info in COMPONENT_LIST.items():
253274
if only and component_name != only:
254275
continue
255276
component = self.components.get(component_name)
256277
if component:
278+
if component_info.get("phase", 0) != phase:
279+
continue
257280
if self.component_tasks.get(component_name, None) and self.component_tasks[component_name].is_alive():
258281
self.log(f"Info: {component_info['name']} interface already started")
259282
continue

‎apps/predbat/predbat.py‎

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1320,7 +1320,7 @@ def initialize(self):
13201320

13211321
# Start all sub-components
13221322
self.components = Components(self)
1323-
self.components.initialize()
1323+
self.components.initialize(phase=0)
13241324

13251325
# Initialize plugin system early so plugins can register web endpoints
13261326
# before the web server starts
@@ -1333,9 +1333,9 @@ def initialize(self):
13331333
self.plugin_system = None
13341334

13351335
# Now start all sub-components (including web server which will pick up registered endpoints)
1336-
if not self.components.start():
1337-
self.log("Error: Some components failed to start")
1338-
self.record_status("Error: Some components failed to start", had_errors=True)
1336+
if not self.components.start(phase=0):
1337+
self.log("Error: Some components failed to start (phase0)")
1338+
self.record_status("Error: Some components failed to start (phase0)", had_errors=True)
13391339

13401340
if not self.ha_interface:
13411341
raise ValueError("HA interface not found")
@@ -1358,6 +1358,14 @@ def initialize(self):
13581358
self.load_user_config(quiet=False, register=True)
13591359
self.validate_config()
13601360
self.comparison = Compare(self)
1361+
1362+
self.components.initialize(phase=1)
1363+
if not self.components.start(phase=1):
1364+
self.log("Error: Some components failed to start (phase1)")
1365+
self.record_status("Error: Some components failed to start (phase1)", had_errors=True)
1366+
1367+
self.auto_config(final=True)
1368+
13611369
except Exception as e:
13621370
self.log("Error: Exception raised {}".format(e))
13631371
self.log("Error: " + traceback.format_exc())

‎apps/predbat/solcast.py‎

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -491,7 +491,12 @@ def fetch_pv_datapoints(self, argname, entity_id):
491491
for entry in data:
492492
total_data += entry["pv_estimate"]
493493
total_data = dp2(total_data)
494-
total_sensor = dp2(self.base.get_state_wrapper(entity_id=entity_id, default=1.0))
494+
total_sensor = self.base.get_state_wrapper(entity_id=entity_id, default=1.0)
495+
try:
496+
total_sensor = dp2(float(total_sensor))
497+
except (ValueError, TypeError):
498+
total_sensor = 1.0
499+
495500
return data, total_data, total_sensor
496501

497502
def publish_pv_stats(self, pv_forecast_data, divide_by, period):
@@ -873,8 +878,10 @@ def fetch_pv_forecast(self):
873878

874879
# Fetch data from each sensor
875880
for argname in ["pv_forecast_today", "pv_forecast_tomorrow", "pv_forecast_d3", "pv_forecast_d4"]:
876-
arg_value = getattr(self, argname, None)
877-
data, total_data, total_sensor = self.fetch_pv_datapoints(argname, arg_value)
881+
# We have to re-get the arg here as the regexp wouldn't be resolved earlier
882+
entity_id = getattr(self, argname, None)
883+
884+
data, total_data, total_sensor = self.fetch_pv_datapoints(argname, entity_id)
878885
if data:
879886
self.log("PV Data for {} total {} kWh".format(argname, total_sensor))
880887
pv_forecast_data += data

‎apps/predbat/userinterface.py‎

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1008,7 +1008,7 @@ def resolve_arg_re(self, arg, arg_value, state_keys):
10081008
break
10091009
return matched, arg_value
10101010

1011-
def auto_config(self):
1011+
def auto_config(self, final=False):
10121012
"""
10131013
Auto configure
10141014
match arguments with sensors
@@ -1019,21 +1019,14 @@ def auto_config(self):
10191019
disabled = []
10201020
self.unmatched_args = {}
10211021

1022-
if 0:
1023-
predbat_keys = []
1024-
for key in state_keys:
1025-
if "predbat" in str(key):
1026-
predbat_keys.append(key)
1027-
predbat_keys.sort()
1028-
self.log("Keys:\n - entity: {}".format("\n - entity: ".join(predbat_keys)))
1029-
10301022
# Find each arg re to match
10311023
for arg in self.args:
10321024
arg_value = self.args[arg]
10331025
matched, arg_value = self.resolve_arg_re(arg, arg_value, state_keys)
10341026
if not matched:
1035-
self.log("Warn: Regular expression argument: {} unable to match {}, now will disable".format(arg, arg_value))
1036-
disabled.append(arg)
1027+
if final:
1028+
self.log("Warn: Regular expression argument: {} unable to match {}, now will disable".format(arg, arg_value))
1029+
disabled.append(arg)
10371030
else:
10381031
self.args[arg] = arg_value
10391032

0 commit comments

Comments
 (0)