diff --git a/src/container.rs b/src/container.rs index 201d00d..91b9717 100644 --- a/src/container.rs +++ b/src/container.rs @@ -1,3 +1,4 @@ +use std::collections::hash_map::Iter; /// Data structures and interfaces to store data /// /// The main workhorses that provide functionality are `Containerized` and `Container`. The `Containerized` @@ -18,11 +19,9 @@ /// store a collection of objects of a specific type `T`, and identified by a specific key type `K`. The relationship /// between `Containerized` and `Container` is that `Containerized` defines how the `Container` should be created /// and used for a specific type, while `Container` actually holds the collection of objects. - use std::collections::HashMap; use std::hash::Hash; - /// A trait for creating a specialized `Container` instance /// /// # Notes @@ -73,7 +72,8 @@ use std::hash::Hash; /// ``` pub trait Containerized - where K: Eq + Hash +where + K: Eq + Hash, { // TODO: add type /// Returns a new instance of the `Container` struct for storing objects of type T @@ -81,7 +81,6 @@ pub trait Containerized fn container() -> Container; } - /// Define a basic interface to interact with underlying data. /// T is the data type being stored and K is the key type to access stored data. pub trait Collection { @@ -109,10 +108,11 @@ pub trait Collection { /// The key only needs to be hashable. #[derive(Debug)] pub struct Container - where K: Eq + Hash +where + K: Eq + Hash, { // The inner field is a HashMap with key type K and value type T - inner: HashMap + inner: HashMap, } impl Container { @@ -123,14 +123,13 @@ impl Container { } /// Return a readonly reference to stored HashMap - pub fn _inner(&self) -> &HashMap { - &self.inner + pub fn iter(&self) -> Iter<'_, K, T> { + self.inner.iter() } } /// Implement the `Collection` interface for `Container` impl Collection for Container { - /// Add a key-value pair to the collection and return a boolean indicating if the value has been added to the collection. /// Using `entry` method on the inner HashMap to check if the key already exists in the HashMap /// - If the key already exists, the returned value is `std::collections::hash_map::Entry::Occupied`, which returns false. diff --git a/src/device.rs b/src/device.rs index a1ae198..e303128 100644 --- a/src/device.rs +++ b/src/device.rs @@ -1,11 +1,9 @@ /// Provide Low-level Device Functionality - -use chrono::{Duration, Utc}; +use chrono::{DateTime, Duration, Utc}; use std::fmt::Formatter; -use crate::io; use crate::container::{Container, Containerized}; - +use crate::io; /// Basic interface for GPIO device metadata pub trait Device { @@ -14,7 +12,6 @@ pub trait Device { fn id(&self) -> i32; } - /// Interface for an input device /// It is used as a trait object and can be stored in a container using the `Containerized` trait. /// @@ -46,20 +43,23 @@ pub trait Device { pub trait Sensor: Device { fn read(&self) -> T; - fn get_event(&self) -> io::IOEvent where Self: Sized { - io::IOEvent::create(self, - Utc::now(), - self.read()) + fn get_event(&self, dt: DateTime) -> io::IOEvent { + io::IOEvent::create(self, dt, self.read()) } } impl std::fmt::Debug for dyn Sensor { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "Sensor {{ name: {}, id: {}, info: {}", self.name(), self.id(), self.get_metadata()) + write!( + f, + "Sensor {{ name: {}, id: {}, info: {}", + self.name(), + self.id(), + self.get_metadata() + ) } } - /// Defines an interface for an input device that needs to be calibrated pub trait Calibrated { /// Initiate the calibration procedures for a specific device instance. @@ -97,7 +97,7 @@ pub struct DeviceMetadata { max_value: T, resolution: T, - min_delay: Duration, + pub min_delay: Duration, } impl DeviceMetadata { @@ -117,11 +117,24 @@ impl DeviceMetadata { /// # Returns /// /// A new instance with given specified parameters - pub fn new(name: String, version_id: i32, sensor_id: i32, kind: io::IOKind, - min_value: T, max_value: T, resolution: T, min_delay: Duration) -> Self { + pub fn new( + name: String, + version_id: i32, + sensor_id: i32, + kind: io::IOKind, + min_value: T, + max_value: T, + resolution: T, + min_delay: Duration, + ) -> Self { DeviceMetadata { - name, version_id, sensor_id, kind, - min_value, max_value, resolution, + name, + version_id, + sensor_id, + kind, + min_value, + max_value, + resolution, min_delay, } } @@ -129,16 +142,21 @@ impl DeviceMetadata { impl std::fmt::Display for DeviceMetadata { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "Device Info {{ Kind: {}, Min. Delay: {} }}", self.kind, self.min_delay.to_string()) + write!( + f, + "Device Info {{ Kind: {}, Min. Delay: {} }}", + self.kind, + self.min_delay.to_string() + ) } } - /// Returns a new instance of `Container` for storing objects which implement the `Sensor` trait which are accessed `` /// Objects are stored as `Box>` impl Containerized>, K> for dyn Sensor - where T: std::fmt::Debug, - K: std::hash::Hash + Eq +where + T: std::fmt::Debug, + K: std::hash::Hash + Eq, { fn container() -> Container>, K> { Container::>, K>::new() diff --git a/src/io.rs b/src/io.rs index d1213a2..cef7ef0 100644 --- a/src/io.rs +++ b/src/io.rs @@ -1,9 +1,9 @@ /// Encapsulate IO for devices +use chrono::{DateTime, Utc}; use std::fmt::Formatter; -use chrono::{Utc, DateTime}; -use crate::device; use crate::container::{Container, Containerized}; +use crate::device; /// Defines sensor type. Used to classify data along with `IOData`. #[derive(Debug, Clone, Copy, PartialEq)] @@ -51,12 +51,14 @@ impl std::fmt::Display for IOKind { // TODO: enum for `IODirection` when implementing control system /// Encapsulates sensor data. Provides a unified data type for returning data. +#[derive(Debug)] pub struct IOData { pub kind: IOKind, - pub data: T + pub data: T, } /// Encapsulates `IOData` alongside of timestamp and device data +#[derive(Debug)] pub struct IOEvent { pub version_id: i32, pub sensor_id: i32, @@ -81,27 +83,31 @@ impl IOEvent { /// ``` /// /// ``` - pub fn create( device: &impl device::Device, timestamp: DateTime, value: T ) -> Self { + pub fn create( + device: &(impl device::Device + ?Sized), + timestamp: DateTime, + value: T, + ) -> Self { let info = device.get_metadata(); let version_id = info.version_id; let sensor_id = info.sensor_id; let data = IOData { kind: info.kind.clone(), - data: value + data: value, }; IOEvent { version_id, sensor_id, timestamp, - data + data, } } } - /// Return a new instance of `Container` with for storing `IOEvent` which are accessed by `DateTime` as keys impl Containerized, DateTime> for IOEvent - where T: std::fmt::Debug +where + T: std::fmt::Debug, { fn container() -> Container, DateTime> { Container::, DateTime>::new() diff --git a/src/lib.rs b/src/lib.rs index 15c1ab1..0d08f52 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,9 @@ extern crate chrono; -pub mod sensors; -pub mod units; -pub mod io; +pub mod container; pub mod device; +pub mod io; +pub mod polling; +pub mod sensors; pub mod settings; -pub mod container; \ No newline at end of file +pub mod units; diff --git a/src/main.rs b/src/main.rs index 69a9da4..441e8e0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,34 +3,37 @@ extern crate chrono; mod container; mod device; mod io; -mod settings; +mod polling; mod sensors; +mod settings; mod units; -use chrono::Duration; +use chrono::{DateTime, Duration, Utc}; use crate::container::{Collection, Container, Containerized}; use crate::device::Sensor; +use crate::polling::Poller; use crate::sensors::ph::MockPhSensor; use crate::settings::Settings; use crate::units::Ph; - fn main() { - let _settings = Settings::initialize(); - - let s0 = MockPhSensor::new( - "test name".to_string(), - 0, - Duration::seconds(5), - ); - let s1 = MockPhSensor::new( - "second sensor".to_string(), - 1, - Duration::seconds(10), - ); - let mut container: Container>, i32> = >::container(); - container.add(0, Box::new(s0)); - container.add(1, Box::new(s1)); - dbg!(container._inner()); + /// # Load Settings + let settings: Settings = Settings::initialize(); + + /// # Setup Poller + let mut poller: Poller = Poller::new(settings.interval, Utc::now() - settings.interval); + + let s0 = MockPhSensor::new("test name".to_string(), 0, Duration::seconds(5)); + let s1 = MockPhSensor::new("second sensor".to_string(), 1, Duration::seconds(10)); + + poller.sensors.add(0, Box::new(s0)); + poller.sensors.add(1, Box::new(s1)); + + loop { + poller.poll(); + std::thread::sleep(std::time::Duration::from_secs(1)); + dbg!(&poller.log) + } + } diff --git a/src/polling.rs b/src/polling.rs new file mode 100644 index 0000000..0eb79bd --- /dev/null +++ b/src/polling.rs @@ -0,0 +1,49 @@ +use chrono::{DateTime, Duration, Utc}; +use std::hash::Hash; + +use crate::container::{Collection, Container, Containerized}; +use crate::device::Sensor; +use crate::io::IOEvent; + +/// Mediator to periodically poll sensors of various types, and store the resulting `IOEvent` objects in a `Container`. +/// +/// `poll()` is the primary callable and iterates through the `Sensor` container to call `get_event()` on each sensor. +/// Resulting `IOEvent` objects are then added to the `log` container. +/// +/// The `interval` field indicates the duration between each poll and the `last_execution` field indicates the last time the poll method was executed +/// +/// TODO: multithreaded polling. Implement `RwLock` or `Mutex` to synchronize access to the sensors and +/// log containers in order to make the poll() function thread-safe. +pub struct Poller { + interval: Duration, + last_execution: DateTime, + + // internal containers + pub sensors: Container>, K>, + pub log: Container, DateTime>, +} + +impl Poller { + /// Iterate through container once. Call `get_event()` on each value. + /// Update according to the lowest rate. + pub fn poll(&mut self) { + let next_execution = self.last_execution + self.interval; + + if next_execution <= Utc::now() { + for (_, sensor) in self.sensors.iter() { + self.last_execution = next_execution; + let event = sensor.get_event(next_execution); + self.log.add(next_execution, event); + dbg!(sensor); + } + } + } + + /// Constructor for `Poller` struct. + /// Internal containers are instantiated as empty. + pub fn new( interval: Duration, last_execution: DateTime ) -> Self { + let sensors: Container>, K> = >::container(); + let log: Container, DateTime> = >::container(); + Self { interval, last_execution, sensors, log } + } +} diff --git a/src/sensors/ph.rs b/src/sensors/ph.rs index b4f4570..5341210 100644 --- a/src/sensors/ph.rs +++ b/src/sensors/ph.rs @@ -4,7 +4,7 @@ use crate::{device, io, units::Ph}; #[derive(Debug, Clone)] pub struct MockPhSensor { - metadata: device::DeviceMetadata + metadata: device::DeviceMetadata, } /** Represents a mock pH sensor. @@ -26,17 +26,14 @@ impl MockPhSensor { let max_value = Ph(14.0); let resolution = Ph(0.1); - let metadata: device::DeviceMetadata = device::DeviceMetadata::new(name, version_id, sensor_id, - kind, min_value, max_value, resolution, - min_delay); + let metadata: device::DeviceMetadata = device::DeviceMetadata::new( + name, version_id, sensor_id, kind, min_value, max_value, resolution, min_delay, + ); - MockPhSensor { - metadata - } + MockPhSensor { metadata } } } - // Implement traits impl device::Device for MockPhSensor { fn get_metadata(&self) -> &device::DeviceMetadata { @@ -55,4 +52,4 @@ impl device::Sensor for MockPhSensor { fn read(&self) -> Ph { Ph::new(1.2).unwrap() } -} \ No newline at end of file +} diff --git a/src/settings.rs b/src/settings.rs index ce478f9..38cdc8a 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -1,16 +1,25 @@ +use chrono::Duration; use dotenv::dotenv; +use std::env::var; +/// Default values const VERSION: &str = "0.0.1-alpha"; +const INTERVAL: i64 = 10; +/// Struct containing settings loaded from ".env" pub struct Settings { - pub version: String + pub version: String, + pub interval: Duration, } impl Settings { /// Read settings from .env file pub fn initialize() -> Self { dotenv().ok(); - let version = std::env::var("VERSION").unwrap_or_else(|_| VERSION.to_string()); - Settings { version } + let version = var("VERSION").unwrap_or_else(|_| VERSION.to_string()); + let interval = Duration::seconds( + var("INTERVAL").unwrap_or(INTERVAL.to_string()).parse::().unwrap()); + + Settings { version, interval } } } diff --git a/src/units.rs b/src/units.rs index 12eccaa..df68a54 100644 --- a/src/units.rs +++ b/src/units.rs @@ -38,6 +38,6 @@ impl From for Ph { impl From for f64 { fn from(value: Ph) -> Self { - value.0 + value.0 } }