ResourceHub API. Not for production. Changes in repository, not implemented sqlite, chages sensor data dto, Changes in Readme file, for update Concept for global collecting sensor's data from cities

This commit is contained in:
SocioCyber
2026-04-01 21:46:05 -07:00
parent 21d0a0dd07
commit cb698814ee
14 changed files with 229 additions and 132 deletions

14
ResourceHub/Cargo.lock generated
View File

@@ -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",
]

View File

@@ -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"
serde = "1.0.228"
serde_json = "1.0.149"

View File

@@ -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: "Acidityalkalinity 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: "Acidityalkalinity 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] ----> "**NewYork**"
> - **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 | Acidityalkalinity 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:[MiCS5524] | % vol | % vol | 23656547 |
> | 4 | AQUA:[[64.9631,-19.0208](https://maps.google.com/?q=64.9631,-19.0208)]::S1:C1 | Acidityalkalinity 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 | Acidityalkalinity 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 3Axis 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[ECMeter 5] | 1.8 | dS/m | 60123456 |
```mermaid
sequenceDiagram
@@ -129,15 +168,11 @@ 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
>
> **[ResourceHUB] **save this data in redis dababase

Binary file not shown.

After

Width:  |  Height:  |  Size: 540 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 330 KiB

View File

@@ -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="<json>")]
@@ -25,35 +25,24 @@ pub async fn create(json: Json<SensorData>) -> status::Accepted<Json<SensorData>
// saved_data = database.create(data);
// status::Accepted(Json(saved_data))
}
#[get("/sensors/data")]
pub async fn get() -> status::Accepted<Json<SensorData>> {
// 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/<id>")]
pub async fn get_by_id(id: u32) -> status::Accepted<Json<SensorData>> {
// 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/<id>")]
// pub async fn get_by_id(id: u32) -> status::Accepted<Json<SensorData>> {
// // 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))
// }

View File

@@ -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);
// }
// }
}

View File

@@ -1,22 +1,38 @@
use rocket::serde::{Deserialize, Serialize};
// #[derive(Deserialize, Serialize)]
// #[serde(crate = "rocket::serde")]
// #[derive(Debug)]
// pub struct SensorData {
// pub id: Option<u32>,
// pub name: String,
// pub value: f32,
// pub unit: String,
// }
#[derive(Deserialize, Serialize)]
#[serde(crate = "rocket::serde")]
#[derive(Debug)]
pub struct SensorData {
pub id: Option<u32>,
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
}
}

View File

@@ -1,6 +0,0 @@
// use redis::AsyncCommands;
// ! Load REDIS_PORT from env
// pub mod DBConnector {
// }

View File

@@ -0,0 +1,2 @@
pub mod remote;
// pub mod local;

View File

@@ -0,0 +1,2 @@
pub mod redis;
// pub mod postgres;

View File

@@ -1,2 +1,85 @@
redis
postgresql
// use tokio::sync::{OnceCell, Mutex};
// use redis::{Connection, ConnectionLike, JsonCommands, RedisResult};
// use rocket::serde::{Deserialize, Serialize};
// static DB_POOL: OnceCell<redis::Client> = OnceCell::const_new();
// static DB_CONNECTOR: OnceCell<Mutex<Connection>> = 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
// }
// // Asynccompatible 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<T>(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<T>(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<Client> = OnceCell::const_new();
static DB_CONNECTOR: OnceCell<Mutex<Connection>> = 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(())
}
// Asynccompatible 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<RV>() -> 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(())
// }

View File

@@ -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/";