Skip to content

Commit

Permalink
Send correct state machine handle for home alone top state and fix Sc…
Browse files Browse the repository at this point in the history
…adaBlind & Onpeak actuation

  - HomeAloneTopState is associated to node "h", not "h.normal"
  - George & I noticed the store pump stayed on when entering UsingBackupOnpeak.
  Indeed we want the store pump off to keep from mixing cold water, and we also
  want the ISO valve open so we don't just heat the store. For both ScadaBlind and
  UsingBackupOnpeak
  - raise exception if H0N.home_alone_onpeak_backup or H0N.home_alone_scada_blind are missing
  • Loading branch information
jessicamillar committed Jan 9, 2025
1 parent 8e85406 commit 0581b42
Show file tree
Hide file tree
Showing 2 changed files with 37 additions and 30 deletions.
61 changes: 31 additions & 30 deletions gw_spaceheat/actors/home_alone.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ class TopStateEvent(GwStrEnum):

class HomeAlone(ScadaActor):
MAIN_LOOP_SLEEP_SECONDS = 60
BLIND_MINUTES = 5
states = [
"Dormant",
"Initializing",
Expand Down Expand Up @@ -161,40 +162,26 @@ def __init__(self, name: str, services: ServicesInterface):
self.log(f"self.is_simulated: {self.is_simulated}")
self.forecasts: HeatingForecast = None
self.zone_setpoints = {}

if H0N.home_alone_scada_blind not in self.layout.nodes:
raise Exception(f"HomeAlone requires {H0N.home_alone_scada_blind} node!!")
if H0N.home_alone_onpeak_backup not in self.layout.nodes:
raise Exception(f"HomeAlone requires {H0N.home_alone_onpeak_backup} node!!")

@property
def normal_node(self) -> ShNode:
"""
Overwrite the standard
"""
return self.layout.node(H0N.home_alone_normal)


@property
def top_node(self) -> ShNode:
"""
The node / state machine responsible
for normal home alone operations
"""
return self.layout.node(H0N.home_alone)


@property
def top_node(self) -> ShNode:
"""
The node / state machine responsible
for normal home alone operations
"""
return self.layout.node(H0N.home_alone)

@property
def onpeak_backup_node(self) -> ShNode:
"""
The node / state machine responsible
for onpeak backup operations
"""
return self.layout.node(H0N.home_alone_onpeak_backup)

@property
def scada_blind_node(self) -> ShNode:
"""
Expand All @@ -203,11 +190,10 @@ def scada_blind_node(self) -> ShNode:
"""
return self.layout.node(H0N.home_alone_scada_blind)


@property
def data(self) -> ScadaData:
return self._services.data

@property
def params(self) -> Ha1Params:
return self.data.ha1_params
Expand Down Expand Up @@ -266,13 +252,13 @@ def set_scadablind_command_tree(self) -> None:

def top_state_update(self, cause: TopStateEvent) -> None:
"""
Report top state and command tree to Scada
Report top state associated to node "h"
"""
now_ms = int(time.time() * 1000)
self._send_to(
self.primary_scada,
SingleMachineState(
MachineHandle=self.normal_node.handle,
MachineHandle=self.node.handle,
StateEnum=HomeAloneTopState.enum_name(),
State=self.top_state,
UnixMs=now_ms,
Expand Down Expand Up @@ -323,6 +309,7 @@ async def main(self):
self.log(f"Top state: {self.top_state}")
self.log(f"State: {self.state}")

# update temperatures_available
self.get_latest_temperatures()

# Update top state
Expand All @@ -347,7 +334,7 @@ async def main(self):
self.scadablind_scada = True

# Update state
if self.state != HomeAloneState.Dormant:
if self.top_state == HomeAloneTopState.Normal:
self.engage_brain(waking_up=(self.state==HomeAloneState.Initializing))
await asyncio.sleep(self.MAIN_LOOP_SLEEP_SECONDS)

Expand Down Expand Up @@ -380,11 +367,11 @@ def engage_brain(self, waking_up: bool = False) -> None:
if not (self.forecasts and self.temperatures_available):
if self.time_since_blind is None:
self.time_since_blind = time.time()
elif time.time() - self.time_since_blind > 5*60 and self.top_state==HomeAloneTopState.Normal:
elif time.time() - self.time_since_blind > self.BLIND_MINUTES*60:
self.log("Scada is missing forecasts and/or critical temperatures since at least 5 min.")
self.log("Moving into ScadaBlind top state")
self.trigger_missing_data()
elif self.time_since_blind is not None and self.top_state==HomeAloneTopState.Normal:
elif self.time_since_blind is not None:
self.log(f"Blind since {int(time.time() - self.time_since_blind)} seconds")
else:
if self.time_since_blind is not None:
Expand Down Expand Up @@ -552,6 +539,14 @@ def trigger_data_available(self):
self.trigger_normal_event(HomeAloneEvent.WakeUp)

def scada_blind_actuator_actions(self) -> None:
"""
Expects self.scada_blind_node as boss. Heats with heat pump:
- turns off store pump
- iso valve open (valved to discharge)
- turn hp failsafe to aquastat
"""
self.turn_off_store_pump(from_node=self.scada_blind_node)
self.valved_to_discharge_store(from_node=self.scada_blind_node)
self.hp_failsafe_switch_to_aquastat(from_node=self.scada_blind_node)

def leaving_scada_blind_actuator_actions(self) -> None:
Expand All @@ -564,10 +559,16 @@ def leaving_onpeak_backup_actuator_actions(self) -> None:

def onpeak_backup_actuator_actions(self) -> None:
"""
Expects set_onpeak_backup_command_tree already called, with self.onpeak_backup_node as boss
WEAKNESS: SENDS MESSAGES TO RELAY ONLY VIA INTERNAL python QUEUE. WILL FAIL
IF HOME ALONE IS PUT IN A DIFFERENT PROCESS THAN THE ACTUATORS
Expects set_onpeak_backup_command_tree already called,
with self.onpeak_backup_node as boss
- turns off store pump
- iso valve open (valved to discharge)
- if using oil boiler, turns hp failsafe to aquastat and aquastat ctrl to boiler
- if not using oil boiler, turns on heat pump
"""
self.turn_off_store_pump(from_node=self.onpeak_backup_node)
self.valved_to_discharge_store(from_node=self.onpeak_backup_node)
if self.settings.oil_boiler_for_onpeak_backup:
self.hp_failsafe_switch_to_aquastat(from_node=self.onpeak_backup_node)
self.aquastat_ctrl_switch_to_boiler(from_node=self.onpeak_backup_node)
Expand Down
6 changes: 6 additions & 0 deletions tests/actors/test_home_alone.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,10 @@ def test_top_state_switches():
# h.top_state goes to Dormant
#
# TopWakeUp: Dormant -> Normal
# with scadablind commmand tree, can set scada_blind_actuator_actions
# and leaving_scada_blind_actuator_actions() without error
# h.set_scadablind_command_tree()
# h.scada_blind_actuator_actions()
# h.leaving_scada_blind_actuator_actions()
# todo: similar for onpeak_backup_actuator_actions

0 comments on commit 0581b42

Please sign in to comment.