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
use crate::brain::immersion_heater::config::ImmersionHeaterModelConfig;
use crate::brain::modes::heating_mode::PossibleTemperatureContainer;
use crate::brain::python_like::control::misc_control::ImmersionHeaterControl;
use crate::brain::BrainFailure;
use crate::time_util::mytime::TimeProvider;
use log::{debug, info};

pub mod config;

pub fn follow_ih_model(
    time_provider: &impl TimeProvider,
    temps: &impl PossibleTemperatureContainer,
    immersion_heater_control: &mut dyn ImmersionHeaterControl,
    model: &ImmersionHeaterModelConfig,
) -> Result<(), BrainFailure> {
    let currently_on = immersion_heater_control.try_get_immersion_heater()?;
    let recommendation = model.should_be_on(temps, time_provider.get_local_time().time());
    if let Some((sensor, recommend_temp)) = recommendation {
        debug!(
            "Hope for temp {}: {:.2}, currently {:.2} at this time",
            sensor,
            recommend_temp,
            temps.get_sensor_temp(&sensor).copied().unwrap_or(-10000.0)
        );
        if !currently_on {
            info!("Turning on immersion heater");
            immersion_heater_control.try_set_immersion_heater(true)?;
        }
    } else if currently_on {
        info!("Turning off immersion heater");
        immersion_heater_control.try_set_immersion_heater(false)?;
    }
    Ok(())
}

#[allow(clippy::zero_prefixed_literal)]
#[cfg(test)]
mod test {
    use super::*;
    use crate::brain::immersion_heater::config::ImmersionHeaterModelPart;
    use crate::brain::python_like::control::misc_control::MiscControls;
    use crate::io::dummy::DummyAllOutputs;
    use crate::time_util::mytime::DummyTimeProvider;
    use crate::time_util::test_utils::{date, time};
    use crate::Sensor;
    use chrono::{TimeZone, Utc};
    use std::collections::HashMap;

    #[test]
    fn check_blank_does_nothing() {
        let mut temps = HashMap::new();
        temps.insert(Sensor::TKTP, 40.0);
        temps.insert(Sensor::TKBT, 20.0);

        let model = ImmersionHeaterModelConfig::new(vec![]);

        let mut dummy = DummyAllOutputs::default();
        let datetime = Utc.from_utc_datetime(&date(2022, 10, 03).and_time(time(02, 30, 00)));
        let time_provider = DummyTimeProvider::new(datetime);
        follow_ih_model(&time_provider, &temps, dummy.as_ih(), &model).unwrap();

        assert!(
            !dummy.try_get_immersion_heater().unwrap(),
            "Immersion heater should have been turned on."
        );
    }

    #[test]
    fn check_ih_model_follow() {
        let model_part = ImmersionHeaterModelPart::from_time_points(
            (time(00, 30, 00), 30.0),
            (time(04, 30, 00), 38.0),
            Sensor::TKBT,
        );
        let model = ImmersionHeaterModelConfig::new(vec![model_part]);
        let datetime = Utc.from_utc_datetime(&date(2022, 01, 18).and_time(time(02, 30, 00)));
        let mut temps = HashMap::new();
        temps.insert(Sensor::TKTP, 40.0);
        temps.insert(Sensor::TKBT, 32.0);

        let mut dummy = DummyAllOutputs::default();
        let time_provider = DummyTimeProvider::new(datetime);

        follow_ih_model(&time_provider, &temps, dummy.as_ih(), &model).unwrap();

        assert!(
            dummy.try_get_immersion_heater().unwrap(),
            "Immersion heater should have been turned on."
        );
    }
}