pub mod calibration;
pub mod config;
pub mod drivers;
use crate::result::CompilerResult;
use std::fs::File;
use std::path::{Path, PathBuf};
use std::{ffi::OsStr, fs::read_to_string};
use std::collections::{BTreeMap, HashMap, HashSet};
use calibration::{BooleanCalibration, Calibration, NumericCalibration};
use codespan_reporting::files::SimpleFiles;
use config::{Config, Virtual};
use drivers::{Drivers, DriversFile};
use self::drivers::{VirtualRelayDriver, VirtualSensorDriver};
use fixed::traits::ToFixed;
#[derive(Debug, PartialEq)]
pub struct Environment {
pub config: Config,
pub drivers_file: Option<DriversFile>,
pub table_cache: HashMap<PathBuf, Calibration>,
}
impl Environment {
pub fn get_calibration(&self, sensor_id: &str) -> CompilerResult<Option<&Calibration>> {
let mut res = CompilerResult::new("Retrieve calibration for a sensor");
let sensors = &self.config.sensors;
let virtual_sensors = &self.config.virtuals.sensors;
if let Some(record) = sensors.get(sensor_id) {
let table_path = &record.calibration;
if let Some(calibration) = self.table_cache.get(table_path) {
return res.with_value(Some(calibration));
}
res.error(format!(
"Failed to find calibration for the sensor {}",
sensor_id
));
return res;
} else if let Some(record) = virtual_sensors.get(sensor_id) {
let table_path = &record.calibration;
match table_path {
Some(path) => {
if let Some(calibration) = self.table_cache.get(path) {
return res.with_value(Some(calibration));
}
}
None => {
return res.with_value(None);
}
}
}
res.error(format!(
"No sensor or virtual sensor found with name: {}",
sensor_id
));
res
}
pub fn boolean_calibration_lookup(
calibration: &BooleanCalibration,
bool_value: bool,
) -> CompilerResult<(u16, u16)> {
let mut res = CompilerResult::new("Perform calibration lookup for a boolean sensor.");
let left_bound: u16 = calibration.lower_bound(bool_value);
let right_bound: u16 = calibration.upper_bound(bool_value);
res.with_value((left_bound, right_bound))
}
pub fn numeric_calibration_lookup(
calibration: &NumericCalibration,
constraint: (f64, f64),
) -> CompilerResult<(u16, u16)> {
let mut res = CompilerResult::new("Perform calibration lookup for a numeric sensor.");
let left_bound_opt = calibration.inverse_lookup(constraint.0.to_fixed());
let right_bound_opt = calibration.inverse_lookup(constraint.1.to_fixed());
let left_bound: u16 = check!(
res,
Reportable::from((
left_bound_opt,
format!(
"Inverse lookup for sensor constraint left bound: {}",
constraint.0
)
))
);
let right_bound: u16 = check!(
res,
Reportable::from((
right_bound_opt,
format!(
"Inverse lookup for sensor constraint right bound: {}",
constraint.1
)
))
);
res.with_value((left_bound, right_bound))
}
pub fn raw_calibration_lookup(constraint: (f64, f64)) -> CompilerResult<(u16, u16)> {
let mut res = CompilerResult::new(
"Perform raw calibration lookup for a numeric sensor with no calibration file.",
);
if constraint.0.fract() != 0.0 || constraint.1.fract() != 0.0 {
res.error(
"Cannot encode fractional sensor bounds for virtual sensor without a calibration",
);
return res;
}
let left_bound: u16 = constraint.0 as u16;
let right_bound: u16 = constraint.1 as u16;
return res.with_value((left_bound, right_bound));
}
}
pub fn read_environment(
config_path_relative: &Path,
codespan_files: &mut SimpleFiles<String, String>,
) -> CompilerResult<Environment> {
let mut res = CompilerResult::new("Reading in Environment");
let config_path: PathBuf = check!(res, config_path_relative.canonicalize());
let base_path = check!(
res,
config_path.parent(),
"Could not find base path for config file"
);
let config_contents = check!(res, read_to_string(config_path.clone()));
let file_name: String = config_path.to_str().unwrap().into();
let file_id = codespan_files.add(file_name.clone(), config_contents.clone());
let config: Config = check!(
res,
config::parse(file_id, file_name.clone(), &config_contents)
);
let mut drivers_file = None;
if let Some(drivers_path) = config.drivers_path.clone() {
let mut drivers_path = base_path.join(drivers_path.clone());
drivers_path = check!(res, drivers_path.canonicalize());
let drivers_contents = check!(res, read_to_string(drivers_path.clone()));
let drivers_name: String = drivers_path.to_str().unwrap().into();
let drivers_id = codespan_files.add(drivers_name.clone(), drivers_contents.clone());
drivers_file = Some(check!(
res,
Drivers::parse(drivers_id, drivers_name, &drivers_contents)
));
}
let paths = collect_paths(&config);
let cache = check!(res, collect_calibration_tables(base_path, &paths));
let environment = Environment {
config,
drivers_file,
table_cache: cache,
};
res.require(validate_drivers(&environment, &file_name));
res.with_value(environment)
}
pub fn collect_paths(config: &Config) -> HashSet<PathBuf> {
let mut paths = HashSet::new();
for (_name, record) in config.sensors.iter() {
let path: PathBuf = record.calibration.clone();
paths.insert(path);
}
for (_name, record) in config.virtuals.sensors.iter() {
if let Some(path) = &record.calibration {
paths.insert(path.clone());
}
}
paths
}
pub fn collect_calibration_tables(
base_path: &Path,
paths: &HashSet<PathBuf>,
) -> CompilerResult<HashMap<PathBuf, Calibration>> {
let mut res = CompilerResult::new("Read calibration tables from the file system");
let mut cache = HashMap::new();
for path in paths.iter() {
let resolved_path = base_path.join(path);
let mut file = check!(res, File::open(resolved_path.clone()));
match resolved_path.extension().and_then(OsStr::to_str) {
Some("bcf") => {
let table = Calibration::Boolean(check!(
res,
BooleanCalibration::read_from_file(&mut file)
));
cache.insert(path.clone(), table);
}
Some("ncf") => {
let table = Calibration::Numeric(check!(
res,
NumericCalibration::read_from_file(&mut file)
));
cache.insert(path.clone(), table);
}
_ => res.error(format!(
"Path {:?} does not have a valid extension",
resolved_path
)),
}
}
res.with_value(cache)
}
pub fn validate_drivers(environment: &Environment, file_name: &String) -> CompilerResult<()> {
let mut res = CompilerResult::status_only("Check all drivers used in config.toml are valid");
let virtuals: &Virtual = &environment.config.virtuals;
let sensors = &virtuals.sensors;
let relays = &virtuals.relays;
let mut sensor_drivers: HashMap<String, &VirtualSensorDriver> = HashMap::new();
let mut relay_drivers: HashMap<String, &VirtualRelayDriver> = HashMap::new();
if let Some(drivers_file) = &environment.drivers_file {
let drivers = &drivers_file.drivers;
for sensor_driver in &drivers.sensor {
let name = format!("{}/{}", sensor_driver.namespace, sensor_driver.function);
if sensor_drivers.contains_key(&name) {
res.error(format!("Duplicate sensor driver {} in drivers file.", name));
} else {
sensor_drivers.insert(name, sensor_driver);
}
}
for relay_driver in &drivers.relay {
let name = format!("{}/{}", relay_driver.namespace, relay_driver.function);
if relay_drivers.contains_key(&name) {
res.error(format!("Duplicate relay driver {} in drivers file.", name));
} else {
relay_drivers.insert(name, relay_driver);
}
}
} else if sensors.len() > 0 || relays.len() > 0 {
res.error(format!(
"No drivers.toml provided, but config.toml contains virtuals.\n\t{:?}",
file_name
));
return res;
}
for (name, sensor) in sensors.iter() {
let driver = check!(
res,
sensor_drivers.get(&sensor.driver),
format!(
"No virtual sensor driver with name: \"{}\"\n\t{:?}",
sensor.driver, file_name
)
);
let res_args =
CompilerResult::status_only(format!("Validate arguments for sensor: {}", name));
res.require(validate_driver_args(res_args, &sensor.args, &driver.fields));
}
for (name, relay) in relays.iter() {
let driver = check!(
res,
relay_drivers.get(&relay.driver),
format!(
"No virtual relay driver with name: \"{}\"\n\t{:?}",
relay.driver, file_name
)
);
let res_args =
CompilerResult::status_only(format!("Validate arguments for relay: {}", name));
res.require(validate_driver_args(res_args, &relay.args, &driver.fields));
}
res
}
fn validate_driver_args(
mut res: CompilerResult<()>,
given_args: &Option<BTreeMap<String, u16>>,
driver_fields: &Option<Vec<BTreeMap<String, String>>>,
) -> CompilerResult<()> {
let mut args: HashSet<&str> = HashSet::new();
if let Some(given_args) = given_args {
for arg in given_args.keys() {
args.insert(arg);
}
}
if let Some(driver_fields) = driver_fields {
for arg in driver_fields {
if !args.remove(arg.get("name").unwrap().as_str()) {
res.error(format!(
"Required argument: \"{}\" wasn't provided!",
arg.get("name").unwrap()
));
}
}
}
for arg in args {
res.error(format!(
"Argument: \"{}\" was given when its driver doesn't define it!",
arg
));
}
res
}