diff --git a/client/packages/common/src/types/schema.ts b/client/packages/common/src/types/schema.ts index 6df663c8c0..2bc7527972 100644 --- a/client/packages/common/src/types/schema.ts +++ b/client/packages/common/src/types/schema.ts @@ -4426,6 +4426,7 @@ export type SensorNode = { }; export enum SensorNodeType { + Berlinger = 'BERLINGER', BlueMaestro = 'BLUE_MAESTRO', Laird = 'LAIRD' } diff --git a/server/Cargo.lock b/server/Cargo.lock index 79856fe0fa..85392adddf 100644 --- a/server/Cargo.lock +++ b/server/Cargo.lock @@ -1516,6 +1516,17 @@ dependencies = [ "syn 1.0.100", ] +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.100", +] + [[package]] name = "derive_builder" version = "0.12.0" @@ -3001,6 +3012,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "json" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd" + [[package]] name = "jsonschema" version = "0.16.1" @@ -4206,6 +4223,19 @@ dependencies = [ "winapi", ] +[[package]] +name = "rs-drivelist" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f978042716f80a3323d09a63b1ed292b37b6d3225da1148fc8d355e2db9edfc" +dependencies = [ + "anyhow", + "derivative", + "json", + "regex", + "winapi", +] + [[package]] name = "rust-embed" version = "6.4.0" @@ -4614,6 +4644,7 @@ dependencies = [ "serde_json", "serde_yaml", "simple-log", + "temperature-sensor", "tera", "thiserror", "tokio", @@ -4883,6 +4914,18 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "temperature-sensor" +version = "0.1.0" +source = "git+https://github.com/openmsupply/temperature-sensor.git#958c3422b332b516dc9325473d53ab0752c78d54" +dependencies = [ + "chrono", + "json", + "rand", + "rs-drivelist", + "serde_json", +] + [[package]] name = "tempfile" version = "3.2.0" diff --git a/server/graphql/core/src/loader/temperature_breach.rs b/server/graphql/core/src/loader/temperature_breach.rs index 31c87638b9..ff0cdeaca2 100644 --- a/server/graphql/core/src/loader/temperature_breach.rs +++ b/server/graphql/core/src/loader/temperature_breach.rs @@ -1,32 +1,38 @@ -use repository::EqualFilter; -use repository::{ - temperature_breach::{TemperatureBreach, TemperatureBreachFilter, TemperatureBreachRepository}, - RepositoryError, StorageConnectionManager, -}; - -use async_graphql::dataloader::*; -use async_graphql::*; -use std::collections::HashMap; - -pub struct TemperatureBreachByIdLoader { - pub connection_manager: StorageConnectionManager, -} - -#[async_trait::async_trait] -impl Loader for TemperatureBreachByIdLoader { - type Value = TemperatureBreach; - type Error = RepositoryError; - - async fn load(&self, ids: &[String]) -> Result, Self::Error> { - let connection = self.connection_manager.connection()?; - let repo = TemperatureBreachRepository::new(&connection); - - let result = - repo.query_by_filter(TemperatureBreachFilter::new().id(EqualFilter::equal_any(ids.to_owned())))?; - - Ok(result - .into_iter() - .map(|temperature_breach| (temperature_breach.temperature_breach_row.id.clone(), temperature_breach)) - .collect()) - } -} +use repository::EqualFilter; +use repository::{ + temperature_breach::{TemperatureBreach, TemperatureBreachFilter, TemperatureBreachRepository}, + RepositoryError, StorageConnectionManager, +}; + +use async_graphql::dataloader::*; +use async_graphql::*; +use std::collections::HashMap; + +pub struct TemperatureBreachByIdLoader { + pub connection_manager: StorageConnectionManager, +} + +#[async_trait::async_trait] +impl Loader for TemperatureBreachByIdLoader { + type Value = TemperatureBreach; + type Error = RepositoryError; + + async fn load(&self, ids: &[String]) -> Result, Self::Error> { + let connection = self.connection_manager.connection()?; + let repo = TemperatureBreachRepository::new(&connection); + + let result = repo.query_by_filter( + TemperatureBreachFilter::new().id(EqualFilter::equal_any(ids.to_owned())), + )?; + + Ok(result + .into_iter() + .map(|temperature_breach| { + ( + temperature_breach.temperature_breach_row.id.clone(), + temperature_breach, + ) + }) + .collect()) + } +} diff --git a/server/graphql/types/src/types/sensor.rs b/server/graphql/types/src/types/sensor.rs index fd218689a0..a3deb26b17 100644 --- a/server/graphql/types/src/types/sensor.rs +++ b/server/graphql/types/src/types/sensor.rs @@ -68,6 +68,7 @@ pub struct SensorConnector { pub enum SensorNodeType { BlueMaestro, Laird, + Berlinger, } #[Object] @@ -231,6 +232,7 @@ impl SensorNodeType { match from { from::BlueMaestro => to::BlueMaestro, from::Laird => to::Laird, + from::Berlinger => to::Berlinger, } } @@ -241,6 +243,7 @@ impl SensorNodeType { match self { from::BlueMaestro => to::BlueMaestro, from::Laird => to::Laird, + from::Berlinger => to::Berlinger, } } } diff --git a/server/repository/src/db_diesel/mod.rs b/server/repository/src/db_diesel/mod.rs index 5b6eb79956..3f89d60634 100644 --- a/server/repository/src/db_diesel/mod.rs +++ b/server/repository/src/db_diesel/mod.rs @@ -77,6 +77,8 @@ mod sync_buffer; mod sync_log; mod sync_log_row; pub mod temperature_breach; +mod temperature_breach_config; +mod temperature_breach_config_row; mod temperature_breach_row; pub mod temperature_log; mod temperature_log_row; @@ -155,6 +157,8 @@ pub use sync_buffer::*; pub use sync_log::*; pub use sync_log_row::*; pub use temperature_breach::*; +pub use temperature_breach_config::*; +pub use temperature_breach_config_row::*; pub use temperature_breach_row::*; pub use temperature_log::*; pub use temperature_log_row::*; diff --git a/server/repository/src/db_diesel/sensor_row.rs b/server/repository/src/db_diesel/sensor_row.rs index 7f0332a802..d6950c122e 100644 --- a/server/repository/src/db_diesel/sensor_row.rs +++ b/server/repository/src/db_diesel/sensor_row.rs @@ -41,6 +41,7 @@ joinable!(sensor -> location (location_id)); pub enum SensorType { BlueMaestro, Laird, + Berlinger, } // TODO put this somewhere more sensible @@ -49,6 +50,7 @@ pub fn get_sensor_type(serial: &String) -> SensorType { match serial.split('|').nth(1) { Some("BLUE_MAESTRO") => SensorType::BlueMaestro, Some("LAIRD") => SensorType::Laird, + Some("BERLINGER") => SensorType::Berlinger, _ => SensorType::BlueMaestro, } } diff --git a/server/repository/src/db_diesel/temperature_breach.rs b/server/repository/src/db_diesel/temperature_breach.rs index a5c1dbdc19..234ea6fdb1 100644 --- a/server/repository/src/db_diesel/temperature_breach.rs +++ b/server/repository/src/db_diesel/temperature_breach.rs @@ -35,6 +35,12 @@ pub struct TemperatureBreachFilter { pub location: Option, } +impl EqualFilter { + pub fn equal_to_breach_type(value: &TemperatureBreachRowType) -> Self { + inline_init(|r: &mut Self| r.equal_to = Some(value.to_owned())) + } +} + #[derive(PartialEq, Debug)] pub enum TemperatureBreachSortField { Id, diff --git a/server/repository/src/db_diesel/temperature_breach_config.rs b/server/repository/src/db_diesel/temperature_breach_config.rs new file mode 100644 index 0000000000..59cabc358b --- /dev/null +++ b/server/repository/src/db_diesel/temperature_breach_config.rs @@ -0,0 +1,166 @@ +use super::{ + temperature_breach_config_row::{ + temperature_breach_config, temperature_breach_config::dsl as temperature_breach_config_dsl, + }, + DBType, StorageConnection, TemperatureBreachConfigRow, TemperatureBreachRowType, +}; +use diesel::prelude::*; + +use crate::{ + diesel_macros::{apply_equal_filter, apply_sort_no_case}, + repository_error::RepositoryError, +}; + +use crate::{EqualFilter, Pagination, Sort}; + +#[derive(PartialEq, Debug, Clone)] +pub struct TemperatureBreachConfig { + pub temperature_breach_config_row: TemperatureBreachConfigRow, +} + +#[derive(Clone, PartialEq, Debug)] +pub struct TemperatureBreachConfigFilter { + pub id: Option>, + pub r#type: Option>, + pub is_active: Option, + pub store_id: Option>, + pub description: Option>, +} + +#[derive(PartialEq, Debug)] +pub enum TemperatureBreachConfigSortField { + Id, + Description, +} + +pub type TemperatureBreachConfigSort = Sort; + +pub struct TemperatureBreachConfigRepository<'a> { + connection: &'a StorageConnection, +} + +impl<'a> TemperatureBreachConfigRepository<'a> { + pub fn new(connection: &'a StorageConnection) -> Self { + TemperatureBreachConfigRepository { connection } + } + + pub fn count( + &self, + filter: Option, + ) -> Result { + let query = create_filtered_query(filter); + Ok(query.count().get_result(&self.connection.connection)?) + } + + pub fn query_by_filter( + &self, + filter: TemperatureBreachConfigFilter, + ) -> Result, RepositoryError> { + self.query(Pagination::all(), Some(filter), None) + } + + pub fn query( + &self, + pagination: Pagination, + filter: Option, + sort: Option, + ) -> Result, RepositoryError> { + let mut query = create_filtered_query(filter); + if let Some(sort) = sort { + match sort.key { + TemperatureBreachConfigSortField::Id => { + apply_sort_no_case!(query, sort, temperature_breach_config_dsl::id) + } + TemperatureBreachConfigSortField::Description => { + apply_sort_no_case!(query, sort, temperature_breach_config_dsl::description) + } + } + } else { + let sort = TemperatureBreachConfigSort { + key: TemperatureBreachConfigSortField::Description, + desc: Some(false), + }; + apply_sort_no_case!(query, sort, temperature_breach_config_dsl::description) + } + + let result = query + .offset(pagination.offset as i64) + .limit(pagination.limit as i64) + .load::(&self.connection.connection)?; + + Ok(result.into_iter().map(to_domain).collect()) + } +} + +type BoxedLogQuery = temperature_breach_config::BoxedQuery<'static, DBType>; + +fn create_filtered_query(filter: Option) -> BoxedLogQuery { + let mut query = temperature_breach_config::table.into_boxed(); + + if let Some(filter) = filter { + apply_equal_filter!(query, filter.id, temperature_breach_config_dsl::id); + apply_equal_filter!(query, filter.r#type, temperature_breach_config_dsl::type_); + apply_equal_filter!( + query, + filter.description, + temperature_breach_config_dsl::description + ); + + if let Some(value) = filter.is_active { + query = query.filter(temperature_breach_config_dsl::is_active.eq(value)); + } + + apply_equal_filter!( + query, + filter.store_id, + temperature_breach_config_dsl::store_id + ); + } + + query +} + +pub fn to_domain( + temperature_breach_config_row: TemperatureBreachConfigRow, +) -> TemperatureBreachConfig { + TemperatureBreachConfig { + temperature_breach_config_row, + } +} + +impl TemperatureBreachConfigFilter { + pub fn new() -> TemperatureBreachConfigFilter { + TemperatureBreachConfigFilter { + id: None, + is_active: None, + r#type: None, + store_id: None, + description: None, + } + } + + pub fn id(mut self, filter: EqualFilter) -> Self { + self.id = Some(filter); + self + } + + pub fn is_active(mut self, filter: bool) -> Self { + self.is_active = Some(filter); + self + } + + pub fn r#type(mut self, filter: EqualFilter) -> Self { + self.r#type = Some(filter); + self + } + + pub fn store_id(mut self, filter: EqualFilter) -> Self { + self.store_id = Some(filter); + self + } + + pub fn description(mut self, filter: EqualFilter) -> Self { + self.description = Some(filter); + self + } +} diff --git a/server/repository/src/db_diesel/temperature_breach_config_row.rs b/server/repository/src/db_diesel/temperature_breach_config_row.rs new file mode 100644 index 0000000000..5eca63a143 --- /dev/null +++ b/server/repository/src/db_diesel/temperature_breach_config_row.rs @@ -0,0 +1,100 @@ +use super::{ + store_row::store, + temperature_breach_config_row::temperature_breach_config::dsl as temperature_breach_config_dsl, + temperature_breach_row::TemperatureBreachRowType, StorageConnection, +}; + +use crate::repository_error::RepositoryError; +use diesel::prelude::*; + +table! { + temperature_breach_config (id) { + id -> Text, + duration -> Integer, + #[sql_name = "type"] type_ -> crate::db_diesel::temperature_breach_row::TemperatureBreachRowTypeMapping, + description -> Text, + is_active -> Bool, + store_id -> Nullable, + minimum_temperature -> Double, + maximum_temperature -> Double, + } +} + +table! { + #[sql_name = "temperature_breach_config"] + temperature_breach_config_is_sync_update (id) { + id -> Text, + is_sync_update -> Bool, + } +} + +joinable!(temperature_breach_config -> store (store_id)); + +#[derive(Clone, Queryable, Insertable, AsChangeset, Debug, PartialEq, Default)] +#[changeset_options(treat_none_as_null = "true")] +#[table_name = "temperature_breach_config"] +pub struct TemperatureBreachConfigRow { + pub id: String, + pub duration: i32, + #[column_name = "type_"] + pub r#type: TemperatureBreachRowType, + pub description: String, + pub is_active: bool, + pub store_id: Option, + pub minimum_temperature: f64, + pub maximum_temperature: f64, +} + +pub struct TemperatureBreachConfigRowRepository<'a> { + connection: &'a StorageConnection, +} + +impl<'a> TemperatureBreachConfigRowRepository<'a> { + pub fn new(connection: &'a StorageConnection) -> Self { + TemperatureBreachConfigRowRepository { connection } + } + + #[cfg(feature = "postgres")] + pub fn _upsert_one(&self, row: &TemperatureBreachConfigRow) -> Result<(), RepositoryError> { + diesel::insert_into(temperature_breach_config_dsl::temperature_breach_config) + .values(row) + .on_conflict(temperature_breach_config_dsl::id) + .do_update() + .set(row) + .execute(&self.connection.connection)?; + Ok(()) + } + + #[cfg(not(feature = "postgres"))] + pub fn _upsert_one(&self, row: &TemperatureBreachConfigRow) -> Result<(), RepositoryError> { + diesel::replace_into(temperature_breach_config_dsl::temperature_breach_config) + .values(row) + .execute(&self.connection.connection)?; + Ok(()) + } + + pub fn upsert_one(&self, row: &TemperatureBreachConfigRow) -> Result<(), RepositoryError> { + self._upsert_one(row)?; + Ok(()) + } + + pub fn find_one_by_id( + &self, + id: &str, + ) -> Result, RepositoryError> { + let result = temperature_breach_config_dsl::temperature_breach_config + .filter(temperature_breach_config_dsl::id.eq(id)) + .first(&self.connection.connection) + .optional()?; + Ok(result) + } + + pub fn find_many_by_id( + &self, + ids: &[String], + ) -> Result, RepositoryError> { + Ok(temperature_breach_config_dsl::temperature_breach_config + .filter(temperature_breach_config_dsl::id.eq_any(ids)) + .load(&self.connection.connection)?) + } +} diff --git a/server/repository/src/migrations/v1_05_00/sensor.rs b/server/repository/src/migrations/v1_05_00/sensor.rs index 3df95844d3..ba0e92dcbf 100644 --- a/server/repository/src/migrations/v1_05_00/sensor.rs +++ b/server/repository/src/migrations/v1_05_00/sensor.rs @@ -12,7 +12,8 @@ pub(crate) fn migrate(connection: &StorageConnection) -> anyhow::Result<()> { r#" CREATE TYPE {SENSOR_TYPE} AS ENUM ( 'BLUE_MAESTRO', - 'LAIRD' + 'LAIRD', + 'BERLINGER' ); "# )?; @@ -33,6 +34,17 @@ pub(crate) fn migrate(connection: &StorageConnection) -> anyhow::Result<()> { type {SENSOR_TYPE} ); + CREATE TABLE temperature_breach_config ( + id TEXT NOT NULL PRIMARY KEY, + duration INTEGER NOT NULL, + type TEXT NOT NULL, + description TEXT NOT NULL UNIQUE, + is_active BOOLEAN, + store_id TEXT REFERENCES store(id), + minimum_temperature {DOUBLE} NOT NULL, + maximum_temperature {DOUBLE} NOT NULL + ); + CREATE TABLE temperature_breach ( id TEXT NOT NULL PRIMARY KEY, duration INTEGER NOT NULL, @@ -66,6 +78,7 @@ pub(crate) fn migrate(connection: &StorageConnection) -> anyhow::Result<()> { r#" ALTER TYPE changelog_table_name ADD VALUE IF NOT EXISTS 'sensor'; ALTER TYPE changelog_table_name ADD VALUE IF NOT EXISTS 'temperature_breach'; + ALTER TYPE changelog_table_name ADD VALUE IF NOT EXISTS 'temperature_breach_config'; ALTER TYPE changelog_table_name ADD VALUE IF NOT EXISTS 'temperature_log'; @@ -76,6 +89,10 @@ pub(crate) fn migrate(connection: &StorageConnection) -> anyhow::Result<()> { CREATE TRIGGER temperature_breach_trigger AFTER INSERT OR UPDATE OR DELETE ON temperature_breach FOR EACH ROW EXECUTE PROCEDURE update_changelog(); + + CREATE TRIGGER temperature_breach_config_trigger + AFTER INSERT OR UPDATE OR DELETE ON temperature_breach_config + FOR EACH ROW EXECUTE PROCEDURE update_changelog(); CREATE TRIGGER temperature_log_trigger AFTER INSERT OR UPDATE OR DELETE ON temperature_log @@ -100,6 +117,12 @@ pub(crate) fn migrate(connection: &StorageConnection) -> anyhow::Result<()> { VALUES ("temperature_breach", NEW.id, "UPSERT"); END; + CREATE TRIGGER temperature_breach_config_insert_trigger + AFTER INSERT ON temperature_breach_config + BEGIN + INSERT INTO changelog (table_name, record_id, row_action) + VALUES ("temperature_breach_config", NEW.id, "UPSERT"); + END; CREATE TRIGGER temperature_log_insert_trigger AFTER INSERT ON temperature_log @@ -127,6 +150,13 @@ pub(crate) fn migrate(connection: &StorageConnection) -> anyhow::Result<()> { VALUES ('temperature_breach', NEW.id, 'UPSERT'); END; + CREATE TRIGGER temperature_breach_config_update_trigger + AFTER UPDATE ON temperature_breach_config + BEGIN + INSERT INTO changelog (table_name, record_id, row_action) + VALUES ('temperature_breach_config', NEW.id, 'UPSERT'); + END; + CREATE TRIGGER temperature_log_update_trigger AFTER UPDATE ON temperature_log BEGIN @@ -153,6 +183,13 @@ pub(crate) fn migrate(connection: &StorageConnection) -> anyhow::Result<()> { VALUES ('temperature_breach', OLD.id, 'DELETE'); END; + CREATE TRIGGER temperature_breach_config_delete_trigger + AFTER DELETE ON temperature_breach_config + BEGIN + INSERT INTO changelog (table_name, record_id, row_action) + VALUES ('temperature_breach_config', OLD.id, 'DELETE'); + END; + CREATE TRIGGER temperature_log_delete_trigger AFTER DELETE ON temperature_log BEGIN diff --git a/server/server/src/cold_chain/sensor.rs b/server/server/src/cold_chain/sensor.rs index aa703bcca6..9448764bba 100644 --- a/server/server/src/cold_chain/sensor.rs +++ b/server/server/src/cold_chain/sensor.rs @@ -151,7 +151,7 @@ fn upsert_sensor( id: id.clone(), name: Some(sensor.name.clone()), is_active: None, - location_id: None, + location: None, log_interval: Some(sensor.log_interval), battery_level: Some(sensor.battery_level), }; diff --git a/server/service/Cargo.toml b/server/service/Cargo.toml index af966e5c14..32b1b3b576 100644 --- a/server/service/Cargo.toml +++ b/server/service/Cargo.toml @@ -35,6 +35,9 @@ pretty_assertions = "1.3.0" flate2 = "1.0.26" simple-log = { version = "1.6" } +# dependencies for temperature_sensor +temperature-sensor = {git = "https://github.com/openmsupply/temperature-sensor.git" } + [dev-dependencies] actix-rt = "2.6.0" assert-json-diff = "2.0.1" diff --git a/server/service/src/sensor/berlinger.rs b/server/service/src/sensor/berlinger.rs new file mode 100644 index 0000000000..9fcfb0553d --- /dev/null +++ b/server/service/src/sensor/berlinger.rs @@ -0,0 +1,354 @@ +use chrono::NaiveDateTime; +use repository::{DatetimeFilter, EqualFilter}; +use repository::{ + RepositoryError, Sensor, SensorFilter, SensorRepository, SensorRow, SensorRowRepository, + SensorType, StorageConnection, TemperatureBreach, TemperatureBreachConfig, + TemperatureBreachConfigFilter, TemperatureBreachConfigRepository, TemperatureBreachConfigRow, + TemperatureBreachConfigRowRepository, TemperatureBreachFilter, TemperatureBreachRepository, + TemperatureBreachRow, TemperatureBreachRowRepository, TemperatureBreachRowType, TemperatureLog, + TemperatureLogFilter, TemperatureLogRepository, TemperatureLogRow, TemperatureLogRowRepository, +}; +use util::uuid::uuid; + +use crate::{service_provider::ServiceContext, SingleRecordError}; + +extern crate temperature_sensor; +use temperature_sensor::*; + +pub fn get_breach_row_type(breach_type: &BreachType) -> TemperatureBreachRowType { + match breach_type { + BreachType::ColdConsecutive => TemperatureBreachRowType::ColdConsecutive, + BreachType::ColdCumulative => TemperatureBreachRowType::ColdCumulative, + BreachType::HotConsecutive => TemperatureBreachRowType::HotConsecutive, + BreachType::HotCumulative => TemperatureBreachRowType::HotCumulative, + } +} + +pub fn get_matching_sensor_serial( + connection: &StorageConnection, + serial: &str, +) -> Result, RepositoryError> { + SensorRepository::new(connection) + .query_by_filter(SensorFilter::new().serial(EqualFilter::equal_to(&serial))) +} + +pub fn get_matching_sensor_log( + connection: &StorageConnection, + sensor_id: &str, + datetime: NaiveDateTime, +) -> Result, RepositoryError> { + let filter = TemperatureLogFilter::new() + .sensor(SensorFilter::new().id(EqualFilter::equal_to(sensor_id))) + .datetime(DatetimeFilter::equal_to(datetime)); + + TemperatureLogRepository::new(connection).query_by_filter(filter) +} + +pub fn get_matching_sensor_breach_config( + connection: &StorageConnection, + description: &str, + breach_type: &TemperatureBreachRowType, +) -> Result, RepositoryError> { + let filter = TemperatureBreachConfigFilter::new() + .description(EqualFilter::equal_to(description)) + .r#type(EqualFilter::equal_to_breach_type(&breach_type)); + + TemperatureBreachConfigRepository::new(connection).query_by_filter(filter) +} + +pub fn get_matching_sensor_breach( + connection: &StorageConnection, + sensor_id: &str, + start_datetime: NaiveDateTime, + _end_datetime: NaiveDateTime, + breach_type: &TemperatureBreachRowType, +) -> Result, RepositoryError> { + let filter = TemperatureBreachFilter::new() + .sensor(SensorFilter::new().id(EqualFilter::equal_to(sensor_id))) + .r#type(EqualFilter::equal_to_breach_type(&breach_type)) + .start_datetime(DatetimeFilter::equal_to(start_datetime)); + + TemperatureBreachRepository::new(connection).query_by_filter(filter) +} + +pub fn get_logs_for_sensor( + connection: &StorageConnection, + sensor_id: &str, +) -> Result, RepositoryError> { + TemperatureLogRepository::new(connection).query_by_filter( + TemperatureLogFilter::new() + .sensor(SensorFilter::new().id(EqualFilter::equal_to(sensor_id))), + ) +} + +pub fn get_breaches_for_sensor( + connection: &StorageConnection, + sensor_id: &str, +) -> Result, RepositoryError> { + TemperatureBreachRepository::new(connection).query_by_filter( + TemperatureBreachFilter::new() + .sensor(SensorFilter::new().id(EqualFilter::equal_to(sensor_id))) + .acknowledged(false), + ) +} + +pub fn get_breach_configs_for_sensor( + connection: &StorageConnection, + _sensor_id: &str, +) -> Result, RepositoryError> { + TemperatureBreachConfigRepository::new(connection) + .query_by_filter(TemperatureBreachConfigFilter::new().is_active(true)) +} + +fn sensor_add_log_if_new( + connection: &StorageConnection, + sensor_row: &SensorRow, + temperature_log: &temperature_sensor::TemperatureLog, +) -> Result<(), RepositoryError> { + let result = get_matching_sensor_log(connection, &sensor_row.id, temperature_log.timestamp)?; + + if let Some(_record) = result.clone().pop() { + Ok(()) + } else { + let new_temperature_log = TemperatureLogRow { + id: uuid(), + store_id: sensor_row.store_id.clone(), + sensor_id: sensor_row.id.clone(), + location_id: sensor_row.location_id.clone(), + temperature: temperature_log.temperature, + datetime: temperature_log.timestamp, + temperature_breach_id: None, + }; + TemperatureLogRowRepository::new(connection).upsert_one(&new_temperature_log)?; + println!("Added sensor log {:?} ", new_temperature_log); + Ok(()) + } +} + +fn sensor_add_breach_if_new( + connection: &StorageConnection, + sensor_row: &SensorRow, + temperature_breach: &temperature_sensor::TemperatureBreach, + breach_config: &temperature_sensor::TemperatureBreachConfig, +) -> Result<(), RepositoryError> { + let breach_row_type = get_breach_row_type(&temperature_breach.breach_type); + let result = get_matching_sensor_breach( + connection, + &sensor_row.id, + temperature_breach.start_timestamp, + temperature_breach.end_timestamp, + &breach_row_type, + )?; + + if let Some(mut record) = result.clone().pop() { + if record.temperature_breach_row.end_datetime != Some(temperature_breach.end_timestamp) { // Update breach end time if it has changed + record.temperature_breach_row.end_datetime = Some(temperature_breach.end_timestamp); + TemperatureBreachRowRepository::new(connection).upsert_one(&record.temperature_breach_row)?; + } + Ok(()) + } else { + let new_temperature_breach = TemperatureBreachRow { + id: uuid(), + store_id: sensor_row.store_id.clone(), + sensor_id: sensor_row.id.clone(), + location_id: sensor_row.location_id.clone(), + start_datetime: temperature_breach.start_timestamp, + end_datetime: Some(temperature_breach.end_timestamp), + acknowledged: false, + duration: temperature_breach.duration.num_seconds() as i32, + r#type: breach_row_type, + threshold_duration: breach_config.duration.num_seconds() as i32, + threshold_minimum: breach_config.minimum_temperature, + threshold_maximum: breach_config.maximum_temperature, + }; + TemperatureBreachRowRepository::new(connection).upsert_one(&new_temperature_breach)?; + println!("Added sensor breach {:?} ", new_temperature_breach); + Ok(()) + } +} + +fn sensor_add_breach_config_if_new( + connection: &StorageConnection, + sensor_row: &SensorRow, + temperature_breach_config: &temperature_sensor::TemperatureBreachConfig, +) -> Result<(), RepositoryError> { + let mut config_description = format!( + "for {} minutes", + temperature_breach_config.duration.num_minutes() + ); + let breach_row_type = get_breach_row_type(&temperature_breach_config.breach_type); + + match temperature_breach_config.breach_type { + BreachType::ColdConsecutive => { + config_description = format!( + "Consecutive {} colder than {}", + config_description, temperature_breach_config.minimum_temperature + ) + } + BreachType::ColdCumulative => { + config_description = format!( + "Cumulative {} colder than {}", + config_description, temperature_breach_config.minimum_temperature + ) + } + BreachType::HotConsecutive => { + config_description = format!( + "Consecutive {} hotter than {}", + config_description, temperature_breach_config.maximum_temperature + ) + } + BreachType::HotCumulative => { + config_description = format!( + "Cumulative {} hotter than {}", + config_description, temperature_breach_config.maximum_temperature + ) + } + } + + let result = + get_matching_sensor_breach_config(connection, &config_description, &breach_row_type)?; + + if !result.is_empty() { + return Ok(()); + }; + + let new_temperature_breach_config = TemperatureBreachConfigRow { + id: uuid(), + store_id: sensor_row.store_id.clone(), + is_active: true, + description: config_description.clone(), + duration: temperature_breach_config.duration.num_seconds() as i32, + r#type: breach_row_type, + minimum_temperature: temperature_breach_config.minimum_temperature, + maximum_temperature: temperature_breach_config.maximum_temperature, + }; + TemperatureBreachConfigRowRepository::new(connection) + .upsert_one(&new_temperature_breach_config)?; + println!( + "Added sensor breach config {:?} ", + new_temperature_breach_config + ); + Ok(()) +} + +fn sensor_add_if_new( + connection: &StorageConnection, + store_id: &str, + temperature_sensor: &temperature_sensor::Sensor, +) -> Result<(), RepositoryError> { + let result = get_matching_sensor_serial(connection, &temperature_sensor.serial)?; + + if !result.is_empty() { + return Ok(()); + }; + + let mut interval_seconds = None; + if let Some(interval_duration) = temperature_sensor.log_interval { + interval_seconds = Some(interval_duration.num_seconds() as i32); + } + let new_sensor = SensorRow { + id: uuid(), + serial: temperature_sensor.serial.clone(), + name: temperature_sensor.name.clone(), + store_id: Some(store_id.to_string()), + location_id: None, + last_connection_datetime: None, + battery_level: None, + is_active: true, + log_interval: interval_seconds, + r#type: SensorType::Berlinger, + }; + SensorRowRepository::new(connection).upsert_one(&new_sensor)?; + println!("Added sensor {:?} ", new_sensor); + Ok(()) +} + +pub fn read_sensors( + connection: &StorageConnection, + store_id: &str, +) -> Result, SingleRecordError> { + let mut sensors_processed: Vec = Vec::new(); + + let sensor_serials = + read_connected_serials().map_err(|err| SingleRecordError::NotFound(err))?; + let expected_sensor_count = sensor_serials.len(); + + for current_serial in sensor_serials { + let mut temperature_sensor = temperature_sensor::read_sensor(¤t_serial, true) + .map_err(|err| SingleRecordError::NotFound(err))?; + sensor_add_if_new(connection, &store_id, &temperature_sensor)?; + + let result = get_matching_sensor_serial(connection, ¤t_serial)?; + + if let Some(mut record) = result.clone().pop() { + // Filter sensor data by previous last connected time + let last_connected = record.sensor_row.last_connection_datetime; + temperature_sensor = + temperature_sensor::filter_sensor(temperature_sensor, last_connected, None, true); + + if let Some(temperature_sensor_configs) = &temperature_sensor.configs { + for temperature_sensor_config in temperature_sensor_configs { + sensor_add_breach_config_if_new( + connection, + &record.sensor_row, + temperature_sensor_config, + )?; + } + } + + if let Some(temperature_sensor_breaches) = &temperature_sensor.breaches { + for temperature_sensor_breach in temperature_sensor_breaches { + // Look up matching config from the USB data and snapshot it as part of the breach + if let Some(temperature_sensor_configs) = &temperature_sensor.configs { + if let Some(temperature_sensor_config) = temperature_sensor_configs + .iter() + .find(|&t| t.breach_type == temperature_sensor_breach.breach_type) + { + sensor_add_breach_if_new( + connection, + &record.sensor_row, + &temperature_sensor_breach, + &temperature_sensor_config, + )?; + } + } + } + } + + if let Some(temperature_sensor_logs) = &temperature_sensor.logs { + for temperature_sensor_log in temperature_sensor_logs { + sensor_add_log_if_new(connection, &record.sensor_row, temperature_sensor_log)?; + } + } + + // Finally, update sensor's last connected time if it has changed + if record.sensor_row.last_connection_datetime != temperature_sensor.last_connected_timestamp { + record.sensor_row.last_connection_datetime = temperature_sensor.last_connected_timestamp; + SensorRowRepository::new(connection).upsert_one(&record.sensor_row)?; + } + sensors_processed.push(current_serial); + } else { + println!("Sensor {} does not exist in DB", ¤t_serial); + } + } + + if expected_sensor_count == 0 { + println!("No sensors found"); + Err(SingleRecordError::NotFound( + "USB sensor not found".to_string(), + )) + } else if expected_sensor_count > sensors_processed.len() { + Err(SingleRecordError::NotFound( + "At least one sensor not processed".to_string(), + )) + } else { + Ok(sensors_processed) + } +} + +pub fn read_temperature_sensors(ctx: &ServiceContext) -> () { + let _result = match read_sensors(&ctx.connection, &ctx.store_id) { + Err(_) => println!("Sensor error"), + Ok(_sensor_record) => println!("Sensors read"), + }; +} diff --git a/server/service/src/sensor/mod.rs b/server/service/src/sensor/mod.rs index c13d83ec63..91c372bf42 100644 --- a/server/service/src/sensor/mod.rs +++ b/server/service/src/sensor/mod.rs @@ -10,6 +10,7 @@ use crate::{service_provider::ServiceContext, SingleRecordError}; use repository::sensor::{Sensor, SensorFilter, SensorSort}; use repository::PaginationOption; +pub mod berlinger; pub mod insert; pub mod query; pub mod update; diff --git a/server/service/src/sync/translations/sensor.rs b/server/service/src/sync/translations/sensor.rs index 1af158169b..19322514e3 100644 --- a/server/service/src/sync/translations/sensor.rs +++ b/server/service/src/sync/translations/sensor.rs @@ -148,6 +148,7 @@ impl SyncTranslation for SensorTranslation { let r#type = match r#type { SensorType::BlueMaestro => "BLUE_MAESTRO", SensorType::Laird => "LAIRD", + SensorType::Berlinger => "BERLINGER", } .to_string();