1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
use std::time::Instant;

use crate::brain::modes::dhw_only::DhwOnlyMode;
use crate::brain::modes::heating_mode::HeatingMode;
use crate::brain::modes::intention::Intention;
use crate::brain::modes::{InfoCache, Mode};
use crate::brain::python_like::config::PythonBrainConfig;
use crate::brain::python_like::config::overrun_config::DhwTemps;
use crate::brain::python_like::control::heating_control::HeatPumpMode;
use crate::brain::BrainFailure;
use crate::expect_available;
use crate::io::temperatures::Sensor;
use crate::io::IOBundle;
use crate::time_util::mytime::TimeProvider;
use log::{debug, error, info, warn};
use tokio::runtime::Runtime;

use super::working_temp::{find_working_temp_action, CurrentHeatDirection, WorkingTempAction, MixedState};

#[derive(Debug, PartialEq)]
pub struct OnMode {
    circulation_pump_on: bool,

    // TODO: This is one of the root causes of reported On => On transitions when it
    // looks like it might be able to go into MixedMode. Also it seem less than
    // foolproof is it needs to be carefully retained between states (which it isn't,
    // as those falsely reported transitions demontrate).
    // It would probably be better to have values for "last on" and "last off" state
    // in IoBundle with a function that determines whether the HP is actually on and
    // how long it has been on for.
    started: Instant,
}

impl OnMode {
    pub fn create(circulation_pump_on: bool) -> Self {
        Self {
            circulation_pump_on,
            started: Instant::now(),
        }
    }

    pub fn new(circulation_pump_on: bool, started: Instant) -> Self {
        Self {
            circulation_pump_on, started,
        }
    }
}

impl Default for OnMode {
    fn default() -> Self {
        Self::create(false)
    }
}

impl Mode for OnMode {
    fn enter(
        &mut self,
        _config: &PythonBrainConfig,
        _runtime: &Runtime,
        io_bundle: &mut IOBundle,
    ) -> Result<(), BrainFailure> {
        let heating = expect_available!(io_bundle.heating_control())?;

        match heating.try_get_heat_pump()? {
            HeatPumpMode::HeatingOnly | HeatPumpMode::BoostedHeating => {},
            _ => {
                debug!("Turning on HP when entering mode.");
                heating.try_set_heat_pump(HeatPumpMode::HeatingOnly)?;
            }
        }

        let cp = heating.try_get_heat_circulation_pump()?;
        if self.circulation_pump_on != cp {
            debug!("Setting internal circulation pump on to {}", cp);
            self.circulation_pump_on = cp;
        }
        Ok(())
    }

    fn update(
        &mut self,
        rt: &Runtime,
        config: &PythonBrainConfig,
        info_cache: &mut InfoCache,
        io_bundle: &mut IOBundle,
        time: &impl TimeProvider,
    ) -> Result<Intention, BrainFailure> {
        let temps = rt.block_on(info_cache.get_temps(io_bundle.temperature_manager()));
        if let Err(err) = temps {
            error!("Failed to retrieve temperatures {}. Turning off.", err);
            return Ok(Intention::off_now());
        }
        let temps = temps.unwrap();

        if !info_cache.heating_on() {
            // Finish mode should pick up any overrun whether considering
            // minimum run time or not.
            // TODO: config.get_min_hp_runtime();
            // min_runtime.get_safety_cut_off().get_target_sensor().clone(),
            return Ok(Intention::finish());
        }

        let slot = config.get_overrun_during().find_matching_slot(&time.get_utc_time(), &temps,
            |_temps, _temp| true
        );

        let heating = expect_available!(io_bundle.heating_control())?;
        match find_working_temp_action(
            &temps,
            &info_cache.get_working_temp_range(),
            &config.hp_circulation,
            CurrentHeatDirection::Climbing,
            Some(if heating.try_get_heat_pump()? == HeatPumpMode::BoostedHeating { MixedState::BoostedHeating } else { MixedState::NotMixed }),
            slot,
        ) {
            Ok(WorkingTempAction::Heat { mixed_state: MixedState::MixedHeating }) => {
                debug!("Finishing On mode to check for mixed mode.");
                return Ok(Intention::finish());
            }
            Ok(WorkingTempAction::Heat { mixed_state: MixedState::NotMixed }) => {               
                heating.set_heat_pump(HeatPumpMode::HeatingOnly, Some("Disabling boost from hot water tank"))?;
            }
            Ok(WorkingTempAction::Heat { mixed_state: MixedState::BoostedHeating }) => {
                heating.set_heat_pump(HeatPumpMode::BoostedHeating, Some("Enabling boost from hot water tank"))?;
            }
            Ok(WorkingTempAction::Cool { .. }) => {
                info!("Hit top of working range - should no longer heat");
                return Ok(Intention::finish());
            }
            Err(missing_sensor) => {
                error!(
                    "Can't check whether to circulate due to missing sensor: {}",
                    missing_sensor
                );
                return Ok(Intention::off_now());
            }
        }
        if !self.circulation_pump_on {
            if let Some(temp) = temps.get(&Sensor::HPRT) {
                if *temp > config.temp_before_circulate {
                    info!("Reached min circulation temp.");
                    let gpio = expect_available!(io_bundle.heating_control())?;
                    gpio.try_set_heat_circulation_pump(true)?;
                    self.circulation_pump_on = true;
                }
            }
        }
        Ok(Intention::YieldHeatUps)
    }
}