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
151
152
use crate::environment::calibration::{BooleanCalibration, NumericCalibration};
use crate::result::CompilerResult;
use crate::test_binary::{RelayAction, RelayInstr, SensorAction, SensorInstr};
use crate::test_descriptor::ast::{Abort, RelayOp, SensorBound, Test};
use crate::{
    environment::{calibration::Calibration, Environment},
    test_binary::DeviceAddress,
};

use super::ast::{RelayStatement, SensorConstraint};

/// Struct containing the contents of the environment
/// and the ast test
pub struct ConcreteTest {
    pub environment: Environment,
    pub test: Test,
}

impl ConcreteTest {
    /// Construct concrete test representation from environment and ast test struct
    pub fn try_new(environment: Environment, test: Test) -> CompilerResult<Self> {
        let res = CompilerResult::new("Constructing concrete test model");
        res.with_value(ConcreteTest { environment, test })
    }
    /// Convert sensor id used in TDF file to virtual address
    pub fn get_sensor_device_address(&self, sensor_id: &str) -> CompilerResult<DeviceAddress> {
        self.environment.config.get_sensor_device_address(sensor_id)
    }
    /// Convert relay id used in TDF file to virtual address
    pub fn get_relay_device_address(&self, relay_id: &str) -> CompilerResult<DeviceAddress> {
        self.environment.config.get_relay_device_address(relay_id)
    }
    /// Convert abort name used in TDF file to abort index
    pub fn get_abort_id(&self, abort_name: &str) -> CompilerResult<u32> {
        let mut res = CompilerResult::new("Get abort index for an abort");
        if abort_name.eq(Abort::HARD_ABORT) {
            //Return 0 for hard abort
            return res.with_value(0);
        }
        for abort_idx in 0..self.test.aborts.len() {
            if abort_name == self.test.aborts[abort_idx].name {
                //return idx + 1 because idx 0 is always hard abort
                return res.with_value((abort_idx + 1) as u32);
            }
        }
        res.error(format!(
            "No abort with abort_name: {} found in test",
            abort_name
        ));
        res
    }
    /// Get calibration for provided sensor id
    pub fn get_calibration(&self, sensor_id: &str) -> CompilerResult<Option<&Calibration>> {
        self.environment.get_calibration(sensor_id)
    }
}

impl RelayInstr {
    /// Attempts to create a new Relay Instruction from a relay statement in the AST,
    /// Fails if the relay statement cannot be found in the concrete test or
    /// if it has a device address larger than 10 bits.
    pub fn try_from(concrete: &ConcreteTest, statement: &RelayStatement) -> CompilerResult<Self> {
        let mut res = CompilerResult::new("Construct RelayInstr from RelayStatement");
        let relay_device_address = check!(res, concrete.get_relay_device_address(&statement.id));

        let action = match statement.op {
            RelayOp::Set => RelayAction::Set,
            RelayOp::Unset => RelayAction::Unset,
        };

        RelayInstr::try_new(action, relay_device_address)
    }
}

impl SensorInstr {
    /// Tries to create a new SensorInstr from a Sensor constraint in the AST
    pub fn try_from(
        concrete: &ConcreteTest,
        constraint: &SensorConstraint,
        action: SensorAction,
    ) -> CompilerResult<Self> {
        let mut res = CompilerResult::new("Construct SensorInstr from SensorConstraint");

        let abort_idx = check!(res, concrete.get_abort_id(&constraint.abort)) as u8;
        let device_address = check!(res, concrete.get_sensor_device_address(&constraint.id));

        let (left_bound, right_bound) = check!(
            res,
            SensorInstr::calibrated_bounds_from_constraint(concrete, &constraint)
        );

        match action {
            SensorAction::stop_check => SensorInstr::try_new_stop(device_address),
            _ => SensorInstr::try_new(action, abort_idx, device_address, left_bound, right_bound),
        }
    }

    /// Convert bounds from the sensor constraint into raw values
    /// to be encoded in TBF file using the calibration of the sensor
    fn calibrated_bounds_from_constraint(
        concrete: &ConcreteTest,
        constraint: &SensorConstraint,
    ) -> CompilerResult<(u16, u16)> {
        let mut res = CompilerResult::new("Compute calibrated bounds for sensor constraint");

        let calibration_opt = check!(res, concrete.get_calibration(&constraint.id));
        match calibration_opt {
            Some(Calibration::Boolean(calibration)) => {
                if let SensorBound::Boolean(bool_value) = constraint.sensor_bound {
                    return Environment::boolean_calibration_lookup(calibration, bool_value);
                } else {
                    res.error((
                        constraint.metadata,
                        "Must use boolean sensor bounds for a sensor with a boolean calibration file!",
                    ));
                    return res;
                }
            }
            Some(Calibration::Numeric(calibration)) => {
                if let SensorBound::Numeric(numeric_bound) = &constraint.sensor_bound {
                    return Environment::numeric_calibration_lookup(
                        calibration,
                        (numeric_bound.left, numeric_bound.right),
                    );
                } else {
                    res.error((
                        constraint.metadata,
                        "Must use numeric sensor bounds for a sensor with a numeric calibration file!",
                    ));
                    return res;
                }
            }
            None => {
                // This branch will only be hit for virtual sensors without a calibration file
                // We ensure the sensor bound is not a boolean value then encode the raw integer value
                // from the constraint
                if let SensorBound::Numeric(numeric_bound) = &constraint.sensor_bound {
                    return Environment::raw_calibration_lookup((
                        numeric_bound.left,
                        numeric_bound.right,
                    ));
                } else {
                    res.error((
                        constraint.metadata,
                        "Cannot encode a boolean value without a calibration file",
                    ));
                    return res;
                }
            }
        }
    }
}