diff --git a/ResourceHub/Cargo.lock b/ResourceHub/Cargo.lock index 834fc1e..1f5b4a9 100644 --- a/ResourceHub/Cargo.lock +++ b/ResourceHub/Cargo.lock @@ -8,6 +8,8 @@ version = "0.1.0" dependencies = [ "redis", "rocket", + "serde", + "serde_json", "tinterm", "tokio", ] @@ -134,11 +136,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" dependencies = [ "bytes", - "futures-core", "memchr", - "pin-project-lite", - "tokio", - "tokio-util", ] [[package]] @@ -983,19 +981,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b36964393906eb775b89b25b05b7b95685b8dd14062f1663a31ff93e75c452e5" dependencies = [ "arcstr", - "bytes", - "cfg-if", "combine", - "futures-util", "itoa", "num-bigint", "percent-encoding", - "pin-project-lite", "ryu", + "serde", + "serde_json", "sha1_smol", "socket2 0.6.3", - "tokio", - "tokio-util", "url", "xxhash-rust", ] diff --git a/ResourceHub/Cargo.toml b/ResourceHub/Cargo.toml index d3a057f..1c2b2f2 100644 --- a/ResourceHub/Cargo.toml +++ b/ResourceHub/Cargo.toml @@ -4,7 +4,9 @@ version = "0.1.0" edition = "2024" [dependencies] -tokio = { version = "1.32.0", features = ["full"] } -redis = { version = "1.0.4", features = ["tokio-comp"] } +tokio = { version = "1.50.0", features = ["full"] } +redis = { version = "1.0.4", features = ["json"] } rocket = { version = "0.5.1", features = ["json"] } -tinterm = "0.2.0" \ No newline at end of file +tinterm = "0.2.0" +serde = "1.0.228" +serde_json = "1.0.149" \ No newline at end of file diff --git a/ResourceHub/Readme.md b/ResourceHub/Readme.md index f61ac80..d6c8702 100644 --- a/ResourceHub/Readme.md +++ b/ResourceHub/Readme.md @@ -2,45 +2,84 @@ > [!NOTE] > **[DataCollector]** sends [**sensor's data] **like as *PH Level*, *Humidity*, *Temperature*, etc) as ++JSON++ to this **[API]** -> **RESOURCE HUB - means a Public Storage with Sensors Data** -> ![Resource hub](./images/Resource-hub-2.png) +> **RESOURCE HUB - means a Public Storage with Sensors Data,.**![Resource-hub-2](./images/Resource-hub-2-2.png)** +> ![image](./images/pasted_20260318-150812.png) > ```json > { > { -> name: "pH sensor", +> id: 1, +> city_codename_cords_sector_cell: "CERCELL-A1:[40.7128,-74.0060]::S1:C1" +> name: "Acidity‑alkalinity of a solution:[PH 0-14]", > value: "5.5", -> unit: "pH" // to measure the acidity or alkalinity of a solution +> unit: "pH", // to measure the acidity or alkalinity of a solution +> timestamp: 17000002 // decode_timestamp(17000002) -> (2024, 3, 8, 12, 34, 52) -> "2024.03.8:12:34:52" [year.month.day:hour:min:sec] > }, > { -> name: "DHT22", +> id: 1, +> city_codename_cords_sector_cell: "CERCELL-A2:[70.7128,-56,00012]::S1:C1" +> name: "Acidity‑alkalinity of a solution:[PH 0-14]", +> value: "5.7", +> unit: "pH", // to measure the acidity or alkalinity of a solution +> timestamp: 17000035 +> }, +> .... +> { +> id: 2, +> city_codename_cords_sector_cell: "BETTA-A1:[40.7128,-74.0060]::S1:C1" +> name: "Humidity:[DHT22]", > value: "20.7", -> unit: "RH" // Relative Humidity (RH) +> unit: "RH", // Relative Humidity (RH) +> timestamp: 17000002 > }, +> ... > { -> name: "DHT22", +> id: 3, +> city_codename_cords_sector_cell: "ANLATIC-mAI:[40.7128,-74.0060]::S1:C1" +> name: "Temperature:[DHT22]", > value: "35.0", -> unit: "Celsius" // or Fahrenheit Temperature of room +> unit: "°C", // or Fahrenheit Temperature of room '°C' preffered for UI Charts +> timestamp: 17000002 > } +> ... > } > ``` -> # Redis database save recevied [sensor-data] into Table Sensor_data[S5-1] -> - **[S5]** - means SECTOR-5, -> - **"-1"** in [S5**-1**] means SECTOR-5-CELL-1 -> ### In concept -> - SECTOR-5-CELL-1 can collect data about tomatos -> - SECTOR-5-CELL-2 can collect data about potatos -> > **In future can be added 'resource' journals,.** -> > **a. "how much" system make a 'tomatos', 'gabbages,.' : 10 tomatos/month, 40 gabbeges/month, etc,.)** -> > **b. "n/year"** -> > **c. ** +> # Redis database save recevied [sensor-data] into Table Sensor_data +> # CERCELL-A1:[40.7128,-74.0060]::S1:C1 +> - **CERCELL** - "**City** **name**" +> - **-A1** - " City **codename" (can be empty)** +> - **[40.7128,-74.0060] **- City,. **Coordinates** [° N,° W] +> - [40.7128,-74.0060] ----> "**New York**" +> - **S1:C1** - Means **SECTOR**:**CELL** +> - **S1** - Means: **Sector** **1** +> - **C1** - Means: **Cell** **1** +> - **:** - field splitter (for future. May be parsers can use this for getting fields: cityid, cords, sector, cell) > -> | id | Sector | Cell | sensor_name | value | unit | -> | --- | ------ | ---- | ----------- | ----- | ------- | -> | 1 | 1 | 1 | pH sensor | 5.5 | pH | -> | 2 | 1 | 1 | DHT22 | 20.7 | RH | -> | 3 | 1 | 1 | DHT22 | 35.0 | Celsius | -> | 4 | 1 | 2 | pH sensor | 2.5 | pH | -> | 5 | 1 | 2 | DHT22 | 20.0 | Celsius | +> ### In concept +> > SECTOR-5-CELL-1 can collect data about tomatos +> > SECTOR-5-CELL-2 can collect data about potatos +> > **In future can be added 'resource' journals,.** +> > **a. "how much" system make a 'tomatos', 'gabbages,.' : 10 tomatos/month, 40 gabbeges/month, etc,.)** +> > **b. "n/year"** +> > **c. ** +> ## * Sensors Data Table * [**Example**] +> +> | id | city_codename_cords_sector_cell | sensor_name_and_model | value | unit | timestamp | +> | --- | -------------------------------------------------------------------------------- | ---------------------------------------------------- | ----- | ------- | --------- | +> | 1 | AQUA-A1:[[-18.7669,46.8691](https://maps.google.com/?q=-18.7669,46.8691)]:S1:C1 | Acidity‑alkalinity of a solution:[PH 0-14] | 5.5 | pH | 17000002 | +> | 2 | CERCELL:[40.7128,-74.0060]::S1:C1 | Humidity:[DHT22] | 20.7 | % RH | 158371335 | +> | 3 | AQUA:[[64.9631,-19.0208](https://maps.google.com/?q=64.9631,-19.0208)]::S2:C2 | O₂ sensor:[MiCS‑5524] | % vol | % vol | 23656547 | +> | 4 | AQUA:[[64.9631,-19.0208](https://maps.google.com/?q=64.9631,-19.0208)]::S1:C1 | Acidity‑alkalinity of a solution:[PH 0-14] | 2.5 | pH | 23657524 | +> | 5 | OCEAN:[[35.6895,139.6917](https://maps.google.com/?q=35.6895,139.6917)]::S1:C1 | Temperature:[DHT22] | 20.0 | °C | 45557562 | +> | 6 | AQUA-B2:[[12.9716,77.5946](https://maps.google.com/?q=12.9716,77.5946)]:S2:C2 | Humidity:[DHT22] | 45.2 | % RH | 51234567 | +> | 7 | DESERT:[[23.4241,53.8478](https://maps.google.com/?q=23.4241,53.8478)]:S1:C1 | Soil moisture:[Capacitive Soil Moisture Sensor v1.2] | 12.3 | % vol | 52345678 | +> | 8 | MOUNTAIN:[[35.3606,138.7274](https://maps.google.com/?q=35.3606,138.7274)]:S3:C3 | Sound level:[Adafruit MEMS Mic] | 68 | dB SPL | 53456789 | +> | 9 | RIVER:[[48.8566,2.3522](https://maps.google.com/?q=48.8566,2.3522)]:S1:C2 | Acidity‑alkalinity of a solution:[PH 0-14] | 6.8 | pH | 54567890 | +> | 10 | OCEAN:[[34.0522,-118.2437](https://maps.google.com/?q=34.0522,-118.2437)]:S2:C1 | Pressure:[BMP280] | 22.1 | hPa | 55678901 | +> | 11 | FOREST:[[51.5074,-0.1278](https://maps.google.com/?q=51.5074,-0.1278)]:S1:C3 | Wind speed:[Anemometer 3‑Axis 3000] | 30.5 | m / s | 56789012 | +> | 12 | TUNDRA:[[55.7558,37.6173](https://maps.google.com/?q=55.7558,37.6173)]:S2:C2 | Light sensor:[TSL2591] | 5.9 | lux | 57890123 | +> | 13 | VALLEY:[[40.7306,-73.9352](https://maps.google.com/?q=40.7306,-73.9352)]:S3:C1 | CO₂ sensor:[SenseAir S8] | 45.2 | RH | 58901234 | +> | 14 | PLAINS:[[41.8781,-87.6298](https://maps.google.com/?q=41.8781,-87.6298)]:S1:C2 | UV sensor:[VEML6075] | 7.1 | mW /cm² | 59012345 | +> | 15 | COAST:[[37.7749,-122.4194](https://maps.google.com/?q=37.7749,-122.4194)]:S2:C3 | Soil EC[EC‑Meter 5] | 1.8 | dS/m | 60123456 | ```mermaid sequenceDiagram @@ -127,17 +166,13 @@ cargo run > ``` > [!NOTE] -> **DATA COLLECTOR - means a mini PC or Micro-controller with connected sensors, get data from all connected sensors and sends data in json format** -> **to web server, likes RESOURCE HUB [REST API]** -> -> [**DataCollector**] -> **Arduino->[USB]->OrangePiPC->Wifi/Eth - - -> [ResourceHUB API]** -> -> **In my system now i use "arduino nano with DHT22 and pH sensor" connected to Orange Pi PC, arduino sends all data from sensors to Orange Pi PC** -> **Arduino nano connected by usb and use UART - serial port to send data to minipc, and then** -> **Orange Pi PC use wifi/ethernet to sends getted data to web server (To our RESOURCE HUB API)** -> -> **[DataCollector]** sends a **Post** **requests** -> **json** - - **- > **http:123.123.123/api/v1/sensor/data -> +> **DATA COLLECTOR - means a mini PC or Micro-controller with connected sensors, get data from all connected sensors and sends data in json format** +> **to web server, likes RESOURCE HUB [REST API]** +> [**DataCollector**] +> **Arduino->[USB]->OrangePiPC->Wifi/Eth - - -> [ResourceHUB API]** +> **In my system now i use "arduino nano with DHT22 and pH sensor" connected to Orange Pi PC, arduino sends all data from sensors to Orange Pi PC** +> **Arduino nano connected by usb and use UART - serial port to send data to minipc, and then** +> **Orange Pi PC use wifi/ethernet to sends getted data to web server (To our RESOURCE HUB API)** +> **[DataCollector]** sends a **Post** **requests** +> **json** - - **- > **http:123.123.123/api/v1/sensor/data > **[ResourceHUB] **save this data in redis dababase \ No newline at end of file diff --git a/ResourceHub/images/Resource-hub-2-2.png b/ResourceHub/images/Resource-hub-2-2.png new file mode 100644 index 0000000..7748f95 Binary files /dev/null and b/ResourceHub/images/Resource-hub-2-2.png differ diff --git a/ResourceHub/images/pasted_20260318-150812.png b/ResourceHub/images/pasted_20260318-150812.png new file mode 100644 index 0000000..ca1a5a9 Binary files /dev/null and b/ResourceHub/images/pasted_20260318-150812.png differ diff --git a/ResourceHub/src/endpoints/sensors/data.rs b/ResourceHub/src/endpoints/sensors/data.rs index fad917a..260a7d2 100644 --- a/ResourceHub/src/endpoints/sensors/data.rs +++ b/ResourceHub/src/endpoints/sensors/data.rs @@ -5,7 +5,7 @@ use rocket::{get, post}; use crate::models::sensors::SensorData; use crate::models::sensors::get_sensor_data; -// use repository::Redis:: +use crate::repository::remote::redis; // get data json obj // database.getSensorData() @@ -13,7 +13,7 @@ use crate::models::sensors::get_sensor_data; // 500 // create data json obj // database.create(obj) - // send 200 http response with json obj + // send 200 http response // 500 #[post("/sensors/data", format="json", data="")] @@ -25,35 +25,24 @@ pub async fn create(json: Json) -> status::Accepted // saved_data = database.create(data); // status::Accepted(Json(saved_data)) - } #[get("/sensors/data")] pub async fn get() -> status::Accepted> { // get all data from DB + let data = get_sensor_data(); - let data = SensorData { - id: Some(1), - name: String::from("Sensor name"), - value: 4.5, - unit: String::from("C") - }; status::Accepted(Json(data)) } -#[get("/sensors/data/")] -pub async fn get_by_id(id: u32) -> status::Accepted> { - // Vulnirability: danger if ID in sql requsets - // http://127.0.0.1:8000/api/v1/sensors/data/?query=SELECT%20id%20FROM%20sensors +// #[get("/sensors/data/")] +// pub async fn get_by_id(id: u32) -> status::Accepted> { +// // Vulnirability: danger if ID in sql requsets +// // http://127.0.0.1:8000/api/v1/sensors/data/?query=SELECT%20id%20FROM%20sensors - // get data from DB +// // get data from DB +// let data = get_sensor_data(); - let data: SensorData = SensorData { - id: Some(id), - name: String::from("Sensor name"), - value: 4.5, - unit: String::from("PH") - }; - status::Accepted(Json(data)) -} +// status::Accepted(Json(data)) +// } diff --git a/ResourceHub/src/main.rs b/ResourceHub/src/main.rs index 6cbdc46..ee2719f 100644 --- a/ResourceHub/src/main.rs +++ b/ResourceHub/src/main.rs @@ -1,32 +1,14 @@ mod util; mod endpoints; mod models; +mod repository; use rocket::{routes}; use util::constants; use util::debug; +use repository::remote::redis; -pub fn connect() -> redis::RedisResult<()> { - let client = redis::Client::open("redis://127.0.0.1:7878/")?; - let mut connector = client.get_connection()?; - // let mut connector = client.get_connection(); - // connector.set("Name", "Redis"); - // println!("DB name:: {}", connector.get("Name")); - - match client.get_connection() { - Ok(mut connector) => { - util::colored_text::print_colored_text_info("[DB] Connected "); - - Ok(()) - }, - Err(e) => { - util::colored_text::print_colored_text_err("[DB] Error, check redis database is running? (./Database/run.sh) Fix REDIS_PORT if not correctly setted;\n[Wiki] Instructions: https://git.sociocyber.site:5000/SocioCybereeng/Hydroponic_systems/src/branch/master/ResourceHub/Readme.md ", e.to_string()); - Err(e) - } - } -} - #[rocket::main] async fn main() -> Result<(), rocket::Error> { util::colored_text::print_colored_text_brand("> SocioCybereeng "); @@ -37,35 +19,33 @@ pub fn connect() -> redis::RedisResult<()> { debug::print_debug_info(); } + // Get database pool + // let pool = redis::get_pool(constants::DB_URI).await; + - // let DBconnector = ; - // Attempt to connect to DB and check the result - let conection = connect(); - match conection { - // match DBconnector::connect() { - - Ok(conection) => { + // Ok(conection) => { // If the connection is successful, store it or use it as needed - let mut _db = conection; + // let mut _db = conection; + // } - // Start server - let _rocket = rocket::build() - .mount(format!("/api/v{}/", constants::API_VERSION), routes![ - endpoints::sensors::data::create, - endpoints::sensors::data::get, - endpoints::sensors::data::get_by_id, - ]) - .launch() - .await?; + // Start server + let _rocket = rocket::build() + .mount(format!("/api/v{}/", constants::API_VERSION), routes![ + endpoints::sensors::data::create, + endpoints::sensors::data::get, + // endpoints::sensors::data::get_by_id, + ]) + .launch() + .await?; - Ok(()) - }, - Err(err) => { - // Handle connection error and stop execution - std::process::exit(1); - } - } + Ok(()) + // }, + // Err(err) => { + // // Handle connection error and stop execution + // std::process::exit(1); + // } + // } } diff --git a/ResourceHub/src/models/sensors.rs b/ResourceHub/src/models/sensors.rs index 08cef3f..f3d6925 100644 --- a/ResourceHub/src/models/sensors.rs +++ b/ResourceHub/src/models/sensors.rs @@ -1,22 +1,38 @@ use rocket::serde::{Deserialize, Serialize}; +// #[derive(Deserialize, Serialize)] +// #[serde(crate = "rocket::serde")] +// #[derive(Debug)] +// pub struct SensorData { +// pub id: Option, +// pub name: String, +// pub value: f32, +// pub unit: String, +// } + #[derive(Deserialize, Serialize)] #[serde(crate = "rocket::serde")] #[derive(Debug)] pub struct SensorData { pub id: Option, - pub name: String, + pub city_codename_cords_sector_cell: String, // CERCELL::[S1:C1] + pub sensor_name_and_model: String, pub value: f32, pub unit: String, + pub timestamp: u32 } // #[derive(Debug)] pub fn get_sensor_data() -> SensorData { SensorData { id: Some(1), - name: String::from("Sensor name"), - value: 4.5, - unit: String::from("C") + city_codename_cords_sector_cell: String::from("CERCELL:[40.7128,-74.0060]::S1:C1"), + sensor_name_and_model: String::from("Humidity:[DHT22]"), + value: 20.7, + unit: String::from("RH"), + timestamp: 17000002 } -} \ No newline at end of file +} + + diff --git a/ResourceHub/src/repository/DBConnector.rs b/ResourceHub/src/repository/DBConnector.rs deleted file mode 100644 index 047968d..0000000 --- a/ResourceHub/src/repository/DBConnector.rs +++ /dev/null @@ -1,6 +0,0 @@ -// use redis::AsyncCommands; -// ! Load REDIS_PORT from env - -// pub mod DBConnector { - -// } diff --git a/ResourceHub/src/repository/mod.rs b/ResourceHub/src/repository/mod.rs new file mode 100644 index 0000000..c4fc481 --- /dev/null +++ b/ResourceHub/src/repository/mod.rs @@ -0,0 +1,2 @@ +pub mod remote; +// pub mod local; diff --git a/ResourceHub/src/repository/remote/mod.rs b/ResourceHub/src/repository/remote/mod.rs new file mode 100644 index 0000000..6cd5cb8 --- /dev/null +++ b/ResourceHub/src/repository/remote/mod.rs @@ -0,0 +1,2 @@ +pub mod redis; +// pub mod postgres; diff --git a/ResourceHub/src/repository/remote/redis.rs b/ResourceHub/src/repository/remote/redis.rs index fc9acb0..aa8b17c 100644 --- a/ResourceHub/src/repository/remote/redis.rs +++ b/ResourceHub/src/repository/remote/redis.rs @@ -1,2 +1,85 @@ -redis -postgresql \ No newline at end of file +// use tokio::sync::{OnceCell, Mutex}; +// use redis::{Connection, ConnectionLike, JsonCommands, RedisResult}; +// use rocket::serde::{Deserialize, Serialize}; + + +// static DB_POOL: OnceCell = OnceCell::const_new(); +// static DB_CONNECTOR: OnceCell> = OnceCell::const_new(); + +// pub async fn get_pool(URI: &str) -> &'static redis::Client { +// DB_POOL.get_or_init(|| async { +// redis::Client::open(URI).expect("[ERR] Can't connect to database.") +// }).await +// } + + +// // Async‑compatible accessor +// pub async fn get_conn() -> tokio::sync::MutexGuard<'static, Connection> { +// DB_CONNECTOR +// .get() +// .expect("Redis not initialised") +// .lock() +// .await +// } + + +// pub async fn get_data(key: &str, con: &mut Connection) -> RedisResult<(T)> +// where T: Deserialize<'static> { +// let raw_data: String = con.json_get(&key, ".")?; +// Ok( serde_json::from_str(&raw_data)? ) +// } + +// pub async fn set_data(key: &str, data: &T, con: &mut Connection) -> RedisResult<()> +// where T: Serialize, +// { +// Ok( con.json_set(&key, "$", &data)? ) +// } + +use tokio::sync::OnceCell; +use tokio::sync::Mutex; +use redis::{Client, Connection}; + +static DB_POOL: OnceCell = OnceCell::const_new(); +static DB_CONNECTOR: OnceCell> = OnceCell::const_new(); + +pub async fn get_pool(uri: &str) -> redis::RedisResult<()> { + let client = Client::open(uri)?; + DB_POOL.set(client).ok(); + + let conn = DB_POOL + .get() + .unwrap() + .get_connection() + ?; + DB_CONNECTOR.set(Mutex::new(conn)).ok(); + + Ok(()) +} + +// Async‑compatible accessor +pub async fn get_conn() -> tokio::sync::MutexGuard<'static, Connection> { + DB_CONNECTOR + .get() + .expect("Redis not initialised") + .lock() + .await +} + +// TODO TOKIIO AND MUTEX + +// async fn test() -> RedisResult<()> { +// let _pool = redis::get_pool(constants::DB_URI).await; +// let mut con = redis::get_conn(); + +// let data1 = SensorData { +// id: Some(1), +// city_codename_cords_sector_cell: String::from("CERCELL:[40.7128,-74.0060]::S1:C1"), +// sensor_name_and_model: String::from("Humidity:[DHT22]"), +// value: 20.7, +// unit: String::from("RH"), +// timestamp: 17000002 +// }; + +// con.son_set::<&str, &str, SensorData, RV>("asd", "$", &data1); +// Ok(()) +// } diff --git a/ResourceHub/src/util/constants.rs b/ResourceHub/src/util/constants.rs index 85a0ec4..51a93e6 100644 --- a/ResourceHub/src/util/constants.rs +++ b/ResourceHub/src/util/constants.rs @@ -1,5 +1,5 @@ pub const IS_DEBUG: bool = true; // set to 'false' in production! pub const API_VERSION: &str = "1"; pub const DB_NAME: &str = "Redis"; -pub const DB_URL: &str = "redis://127.0.0.1:7878"; +pub const DB_URI: &str = "redis://:fswerfds@127.0.0.1:7878/"; diff --git a/ResourceHub/src/util/mod.rs b/ResourceHub/src/util/mod.rs index 355dcfa..b9ff34a 100644 --- a/ResourceHub/src/util/mod.rs +++ b/ResourceHub/src/util/mod.rs @@ -1,3 +1,3 @@ pub mod constants; pub mod colored_text; -pub mod debug; \ No newline at end of file +pub mod debug;