Skip to content

Commit

Permalink
Merge branch 'main' into td/ha_fudge_factor
Browse files Browse the repository at this point in the history
  • Loading branch information
thdfw authored Jan 8, 2025
2 parents 879a4b6 + 22a85d4 commit 30925b8
Show file tree
Hide file tree
Showing 7 changed files with 107 additions and 9,082 deletions.
23 changes: 15 additions & 8 deletions gw_spaceheat/actors/atomic_ally.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import uuid
from enum import auto
from typing import Sequence
from datetime import datetime

import pytz
from data_classes.house_0_names import H0CN, H0N
Expand All @@ -20,7 +21,7 @@
from actors.scada_actor import ScadaActor
from actors.scada_data import ScadaData
from actors.synth_generator import WeatherForecast
from named_types import RemainingElec
from named_types import RemainingElec, ScadaInit


class AtomicAllyState(GwStrEnum):
Expand Down Expand Up @@ -150,6 +151,7 @@ def process_message(self, message: Message) -> Result[bool, BaseException]:
match message.Payload:
case EnergyInstruction():
self.log(f"Received an EnergyInstruction for {message.Payload.AvgPowerWatts} Watts average power")
self.remaining_elec_wh = message.Payload.AvgPowerWatts
self.check_and_update_state()

case GoDormant():
Expand Down Expand Up @@ -268,14 +270,14 @@ def check_and_update_state(self) -> None:

# 1
elif self.state == AtomicAllyState.HpOnStoreOff.value:
if self.no_more_elec():
if self.no_more_elec() and datetime.now(self.timezone).minute<55:
self.trigger_event(AtomicAllyEvent.NoMoreElec.value)
elif self.is_buffer_full():
self.trigger_event(AtomicAllyEvent.ElecBufferFull.value)

# 2
elif self.state == AtomicAllyState.HpOnStoreCharge.value:
if self.no_more_elec():
if self.no_more_elec() and datetime.now(self.timezone).minute<55:
self.trigger_event(AtomicAllyEvent.NoMoreElec.value)
elif self.is_buffer_empty() or self.is_storage_full():
self.trigger_event(AtomicAllyEvent.ElecBufferEmpty.value)
Expand Down Expand Up @@ -313,6 +315,7 @@ def check_and_update_state(self) -> None:

async def main(self):
await asyncio.sleep(2)
self._send_to(self.primary_scada, ScadaInit(FromGNodeAlias=self.layout.atn_g_node_alias))
# SynthGenerator gets weather ASAP on boot, including various fallbacks
# if the request does not work. So wait a bit if
if self.weather is None:
Expand All @@ -328,17 +331,21 @@ async def main(self):
def update_relays(self, previous_state: str) -> None:
if self.state == AtomicAllyState.WaitingNoElec.value:
self.turn_off_HP()
if (self.state == AtomicAllyState.Dormant.value
or self.state==AtomicAllyState.WaitingElec.value
or self.state==AtomicAllyState.WaitingNoElec.value):
return
if "HpOn" not in previous_state and "HpOn" in self.state:
self.turn_on_HP()
if "HpOff" not in previous_state and "HpOff" in self.state:
self.turn_off_HP()
if "StoreDischarge" in self.state:
self.turn_on_store_pump()
if "StoreDischarge" not in self.state:
self.turn_off_store_pump()
if "StoreCharge" not in previous_state and "StoreCharge" in self.state:
else:
self.turn_off_store_pump()
if "StoreCharge" in self.state:
self.valved_to_charge_store()
if "StoreCharge" in previous_state and "StoreCharge" not in self.state:
else:
self.valved_to_discharge_store()

def fill_missing_store_temps(self):
Expand Down Expand Up @@ -476,7 +483,7 @@ def is_storage_colder_than_buffer(self) -> bool:
else:
self.alert(alias="store_v_buffer_fail", msg="It is impossible to know if the top of the storage is warmer than the top of the buffer!")
return False
if self.latest_temperatures[buffer_top] > self.latest_temperatures[tank_top]:
if self.latest_temperatures[buffer_top] > self.latest_temperatures[tank_top] + 3:
self.log("Storage top colder than buffer top")
return True
else:
Expand Down
14 changes: 8 additions & 6 deletions gw_spaceheat/actors/home_alone.py
Original file line number Diff line number Diff line change
Expand Up @@ -314,8 +314,8 @@ async def main(self):

# Update top state
if self.top_state == HomeAloneTopState.Normal:
if self.house_is_cold_onpeak():
self.trigger_house_cold_onpeak_event()
if self.house_is_cold_onpeak() and self.is_buffer_empty(really_empty=True) and self.is_storage_empty():
self.trigger_house_cold_onpeak_event()

elif self.top_state == HomeAloneTopState.UsingBackupOnpeak:
if just_offpeak:
Expand Down Expand Up @@ -430,17 +430,19 @@ def engage_brain(self) -> None:


def update_relays(self, previous_state) -> None:
if self.state==HomeAloneState.Dormant.value or self.state==HomeAloneState.Initializing.value:
return
if "HpOn" not in previous_state and "HpOn" in self.state:
self.turn_on_HP()
if "HpOff" not in previous_state and "HpOff" in self.state:
self.turn_off_HP()
if "StoreDischarge" in self.state:
self.turn_on_store_pump()
if "StoreDischarge" not in self.state:
self.turn_off_store_pump()
if "StoreCharge" not in previous_state and "StoreCharge" in self.state:
else:
self.turn_off_store_pump()
if "StoreCharge" in self.state:
self.valved_to_charge_store()
if "StoreCharge" in previous_state and "StoreCharge" not in self.state:
else:
self.valved_to_discharge_store()

def trigger_just_offpeak(self):
Expand Down
45 changes: 24 additions & 21 deletions gw_spaceheat/actors/scada.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Scada implementation"""

import os
import asyncio
import enum
import uuid
Expand Down Expand Up @@ -677,16 +678,15 @@ def _process_admin_mqtt_message(
self.log('Admin Wakes Up')
self._renew_admin_timeout(timeout_seconds=decoded.Payload.TimeoutSeconds)
event = decoded.Payload.DispatchTrigger
if event.TypeName != FsmEvent.TypeName:
raise Exception("AdminDispatch DispatchTrigger is FsmEvent!")
self.log(f"AdminDispatch event toype name is {event.TypeName}")
if communicator := self.get_communicator(event.ToHandle.split('.')[-1]):
path_dbg |= 0x00000010
communicator.process_message(
Message(
header=Header(
Src=H0N.admin,
Dst=communicator.name,
MessageType=FsmEvent.TypeName,
MessageType=event.TypeName,
),
Payload=event
)
Expand Down Expand Up @@ -744,54 +744,57 @@ def _renew_admin_timeout(self, timeout_seconds: Optional[int] = None):
self._admin_timeout_task.cancel()
self._admin_timeout_task = asyncio.create_task(self._timeout_admin(timeout_seconds))

def update_env_variable(self, variable, new_value) -> None:
def update_env_variable(self, variable, new_value, testing:bool=False) -> None:
"""
Updates .env with new Scada Params.
TODO: move this somewhere else, like a local sqlite db
"""
dotenv_filepath = dotenv.find_dotenv(usecwd=True)
if not dotenv_filepath:
self.logger.error("Couldn't find a .env file - perhaps because in CI?")
return
if testing:
dotenv_filepath = dotenv.find_dotenv(usecwd=True)
if not dotenv_filepath:
self.logger.error("Couldn't find a .env file - perhaps because in CI?")
return
else:
dotenv_filepath = "/home/pi/gw-scada-spaceheat-python/.env"
if not os.path.isfile(dotenv_filepath):
self.log("Did not find .env file")
return
with open(dotenv_filepath, 'r') as file:
lines = file.readlines()
with open(dotenv_filepath, 'w') as file:
line_exists = False
for line in lines:
if (line.startswith(f"{variable}=")
or line.startswith(f"{variable}= ")
or line.startswith(f"{variable} =")
or line.startswith(f"{variable} = ")):
if line.replace(' ','').startswith(f"{variable}="):
file.write(f"{variable}={new_value}\n")
line_exists = True
else:
file.write(line)
if not line_exists:
file.write(f"\n{variable}={new_value}\n")

def _scada_params_received(self, message: ScadaParams) -> None:
def _scada_params_received(self, message: ScadaParams, testing:bool=False) -> None:
if message.FromGNodeAlias != self.hardware_layout.atn_g_node_alias:
return
new = message.NewParams
if new:
old = self.data.ha1_params
self.data.ha1_params = new
if new.AlphaTimes10 != old.AlphaTimes10:
self.update_env_variable('SCADA_ALPHA', new.AlphaTimes10 / 10)
self.update_env_variable('SCADA_ALPHA', new.AlphaTimes10 / 10, testing)
if new.BetaTimes100 != old.BetaTimes100:
self.update_env_variable('SCADA_BETA', new.BetaTimes100 / 100)
self.update_env_variable('SCADA_BETA', new.BetaTimes100 / 100, testing)
if new.GammaEx6 != old.GammaEx6:
self.update_env_variable('SCADA_GAMMA', new.GammaEx6 / 1e6)
self.update_env_variable('SCADA_GAMMA', new.GammaEx6 / 1e6, testing)
if new.IntermediatePowerKw != old.IntermediatePowerKw:
self.update_env_variable('SCADA_INTERMEDIATE_POWER', new.IntermediatePowerKw)
self.update_env_variable('SCADA_INTERMEDIATE_POWER', new.IntermediatePowerKw, testing)
if new.IntermediateRswtF != old.IntermediateRswtF:
self.update_env_variable('SCADA_INTERMEDIATE_RSWT', new.IntermediateRswtF)
self.update_env_variable('SCADA_INTERMEDIATE_RSWT', new.IntermediateRswtF, testing)
if new.DdPowerKw != old.DdPowerKw:
self.update_env_variable('SCADA_DD_POWER', new.DdPowerKw)
self.update_env_variable('SCADA_DD_POWER', new.DdPowerKw, testing)
if new.DdRswtF != old.DdRswtF:
self.update_env_variable('SCADA_DD_RSWT', new.DdRswtF)
self.update_env_variable('SCADA_DD_RSWT', new.DdRswtF, testing)
if new.DdDeltaTF != old.DdDeltaTF:
self.update_env_variable('SCADA_DD_DELTA_T', new.DdDeltaTF)
self.update_env_variable('SCADA_DD_DELTA_T', new.DdDeltaTF, testing)


response = ScadaParams(
Expand Down
Loading

0 comments on commit 30925b8

Please sign in to comment.