Compare commits
3 commits
main
...
FrontendAd
Author | SHA1 | Date | |
---|---|---|---|
2a4e032c99 | |||
Mikail Killi | 644dd6394e | ||
ff2d6c82c9 |
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -1,4 +1,2 @@
|
|||
/target
|
||||
/embedded/build
|
||||
/.idea
|
||||
.vscode
|
||||
/build
|
|
@ -1,11 +0,0 @@
|
|||
meta {
|
||||
name: get data from last hour
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
get {
|
||||
url: http://localhost:8080/api/v1/data
|
||||
body: none
|
||||
auth: none
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
meta {
|
||||
name: Create node group
|
||||
type: http
|
||||
seq: 1
|
||||
seq: 3
|
||||
}
|
||||
|
||||
post {
|
|
@ -12,11 +12,9 @@ post {
|
|||
|
||||
body:json {
|
||||
{
|
||||
"id":"04-7c-16-06-b3-53",
|
||||
"coord_la":1,
|
||||
"coord_lo":2,
|
||||
"battery_minimum":3,
|
||||
"battery_maximum":4,
|
||||
"group":"54eccfb5-1d5a-4cad-a1a2-468eca68ffd6"
|
||||
"name":"some mac address",
|
||||
"coord_la":1.123123,
|
||||
"coord_lo":5.3123123,
|
||||
"group":"efbd70a9-dc89-4c8d-9e6c-e7607c823df3"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
meta {
|
||||
name: delete node
|
||||
type: http
|
||||
seq: 3
|
||||
seq: 4
|
||||
}
|
||||
|
||||
delete {
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
meta {
|
||||
name: update node
|
||||
type: http
|
||||
seq: 4
|
||||
}
|
||||
|
||||
put {
|
||||
url: http://localhost:8080/api/v1/nodes/:id
|
||||
body: json
|
||||
auth: none
|
||||
}
|
||||
|
||||
params:path {
|
||||
id: 04-7c-16-06-b3-53
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"coord_la":1,
|
||||
"coord_lo":2,
|
||||
"battery_minimum":99,
|
||||
"battery_maximum":9,
|
||||
"group":"32e0f9af-867b-4888-8261-cb99dfff5675"
|
||||
}
|
||||
}
|
63
Cargo.lock
generated
63
Cargo.lock
generated
|
@ -543,16 +543,12 @@ dependencies = [
|
|||
"actix-cors",
|
||||
"actix-web",
|
||||
"argon2",
|
||||
"chrono",
|
||||
"deku",
|
||||
"dotenvy",
|
||||
"entity",
|
||||
"eui48",
|
||||
"futures",
|
||||
"jsonwebtoken",
|
||||
"sea-orm",
|
||||
"serde",
|
||||
"tokio",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
|
@ -777,10 +773,8 @@ checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401"
|
|||
dependencies = [
|
||||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-traits",
|
||||
"serde",
|
||||
"wasm-bindgen",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
|
@ -956,7 +950,6 @@ dependencies = [
|
|||
"ident_case",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim",
|
||||
"syn 2.0.79",
|
||||
]
|
||||
|
||||
|
@ -971,31 +964,6 @@ dependencies = [
|
|||
"syn 2.0.79",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deku"
|
||||
version = "0.18.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9711031e209dc1306d66985363b4397d4c7b911597580340b93c9729b55f6eb"
|
||||
dependencies = [
|
||||
"bitvec",
|
||||
"deku_derive",
|
||||
"no_std_io2",
|
||||
"rustversion",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deku_derive"
|
||||
version = "0.18.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "58cb0719583cbe4e81fb40434ace2f0d22ccc3e39a74bb3796c22b451b4f139d"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.79",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "der"
|
||||
version = "0.7.9"
|
||||
|
@ -1140,16 +1108,6 @@ dependencies = [
|
|||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "eui48"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "887418ac5e8d57c2e66e04bdc2fe15f9a5407be20b54a82c86bd0e368b709701"
|
||||
dependencies = [
|
||||
"regex",
|
||||
"rustc-serialize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "event-listener"
|
||||
version = "2.5.3"
|
||||
|
@ -1800,15 +1758,6 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "no_std_io2"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a3564ce7035b1e4778d8cb6cacebb5d766b5e8fe5a75b9e441e33fb61a872c6"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.1.3"
|
||||
|
@ -2364,12 +2313,6 @@ version = "0.1.24"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-serialize"
|
||||
version = "0.3.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe834bc780604f4674073badbad26d7219cadfb4a2275802db12cbae17498401"
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.4.1"
|
||||
|
@ -2422,12 +2365,6 @@ dependencies = [
|
|||
"untrusted",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.18"
|
||||
|
|
90
README.md
90
README.md
|
@ -1,90 +1,4 @@
|
|||
# ApfelNetzwerk
|
||||
|
||||
1. [ApfelNetzwerk](#apfelnetzwerk) \
|
||||
1.1 [Individual learning goal & reflection](#individual-learning-goal--reflection) \
|
||||
1.2. [DIY Problem Definition](#diy-problem-definition) \
|
||||
1.3. [Empathy Map](#empathy-map) \
|
||||
1.4. [Walt Disney Method](#walt-disney-method) \
|
||||
1.5. [Business Canvas Model](#business-canvas-model) \
|
||||
1.6. [Go-Viral Post](#go-viral-post) \
|
||||
1.7. [Use Case Diagram](#use-case-diagram)
|
||||
|
||||
2. [Additional infos about the project](#additional-infos-about-the-project) \
|
||||
2.1. [Considerations](#approachesconsiderations)\
|
||||
2.2. [Setup](#setup)\
|
||||
2.3. [Notes](#notes)
|
||||
|
||||
|
||||
|
||||
# Apfelnetzwerk
|
||||
|
||||
This document contains the entire portfolio for our project, "ApfelNetzwerk." It presents a solution to the challenges faced by the "Altes Land" region in recent years.
|
||||
|
||||
Below, you will find individual learning goals, reflections, and important details about the project, including the methods used throughout our work.
|
||||
|
||||
## Individual learning goal & reflection
|
||||
|
||||
**Mikail Killi** \
|
||||
**Goal:** To improve my documentation and communication skills while expanding my technical knowledge regarding the hardware we are documenting. \
|
||||
**Reflection:** Throughout the project, I significantly improved my ability to document technical details clearly and concisely. This process helped me develop stronger communication skills, and I now feel more confident in handling technical documentation tasks.
|
||||
|
||||
**Niklas Wollenberg** \
|
||||
**Goal:** To learn how to work with REST APIs. \
|
||||
**Reflection:** By working on the frontend of our project, I gained a solid understanding of effectively using API calls and handling responses.
|
||||
|
||||
**Mika Bomm** \
|
||||
**Goal:** To program a REST API. \
|
||||
**Reflection:** Through extensive research and trial-and-error, I successfully built a fully functional REST API capable of managing all supported API calls.
|
||||
|
||||
**Conner Bogen** \
|
||||
**Goal:** To develop and implement a functional Ad-Hoc wireless network using multiple microcontrollers, enabling efficient communication between devices without the need for a central controller. \
|
||||
**Reflection:** Through hands-on experimentaion and testing, I successfully built a reliable Ad-Hoc wireless network that allowed multiple microcontrollers to communicate seamlessly. This experience greatly enhanced my understanding of network protocols and microcontroller programming.
|
||||
|
||||
## DIY Problem Definition
|
||||
|
||||
The DIY Problem Definition provides a structured overview of the issues we aim to address in our project.
|
||||
|
||||
![DIY Problem Definition](docs/diy-problem-definition.jpg)
|
||||
|
||||
## Empathy Map
|
||||
|
||||
The Empathy Map helps us understand the needs, thoughts, and emotions of our user, guiding is in designing solutions that truly address their concerns.
|
||||
|
||||
![Empathy Map](docs/empathy-map.jpg)
|
||||
|
||||
## Walt Disney Method
|
||||
|
||||
We utilized the Walt Disney method to brainstorm and evaluate ideas. This process allowed us to explore creative solutions from different perspectives aswell as improving our problem-solving approach.
|
||||
https://miro.com/welcomeonboard/ZHFHc29qcksreUZOQlNmMnpVTVpmRXZ5cldXL3FOcGV3NDd1cU40MmFJempXdUVRTDlCT2EyK2FGTlRZcXpDVTdkS3JDZXV6OGR5NmEzcmczMk43ZlFrTmlxQXZsVmtTL0dERUowTGxHL2Jkb2xnWllTVWplbnB4aXJpY0FPTVghZQ==?share_link_id=164862170873
|
||||
|
||||
![Walt Disney](docs/walt-disney.jpg)
|
||||
|
||||
## Business Canvas Model
|
||||
|
||||
The Business Canvas Model outlines key business aspects of the project, such as key partners, key propositions, customer segments and many more.
|
||||
|
||||
![Business Canvas Model](docs/HACKERSOHN%20-%20Frame%204.jpg)
|
||||
|
||||
## Go-Viral Post
|
||||
|
||||
Our project is featured in a Go-Viral post, which not only provides an overview of the project but also helps spread awareness about our initiative. You can read the full post here:
|
||||
|
||||
[Go-Viral Post](https://www.designentrepreneurshipworkshop.org/2024/10/10/team-16-supporting-the-farmers-on-the-altes-land/)
|
||||
|
||||
## Use Case Diagram
|
||||
|
||||
The Use Case Diagram visually represents the interactions within our system. It was generated using Graphviz, with a Python library that translates code into the DOT language used by Graphviz.
|
||||
|
||||
![Use Case Diagram](docs/use_case_diagram.svg)
|
||||
|
||||
|
||||
# Additional infos about the project
|
||||
|
||||
## Approaches/Considerations
|
||||
In the following [document](docs/considerations.md), we have documented various approaches, challenges and lessons learned during the project.
|
||||
|
||||
## Setup
|
||||
We have created a comprehensive [setup document](docs/SETUP.md) to guide you through setting up the project. This document primarily focuses on using Docker to ensure a smooth configuration process.
|
||||
|
||||
## Notes
|
||||
You may also view our [Miro board](https://miro.com/app/board/uXjVLalWhTw=/?share_link_id=740126325996) for better readiblity.
|
||||
[Considerations](docs/considerations.md) \
|
||||
[Setup](docs/SETUP.md)
|
|
@ -18,7 +18,3 @@ sea-orm = { version = "1", features = [
|
|||
dotenvy = "*"
|
||||
jsonwebtoken = "*"
|
||||
futures = "*"
|
||||
chrono = "*"
|
||||
eui48 = "*"
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
deku = "*"
|
||||
|
|
|
@ -1,147 +1,45 @@
|
|||
use crate::AppState;
|
||||
use actix_web::web::Path;
|
||||
use actix_web::{
|
||||
error::{ErrorBadRequest, ErrorInternalServerError},
|
||||
web, Responder,
|
||||
};
|
||||
use chrono::Utc;
|
||||
use entity::{node, node_group, sensor_data};
|
||||
use eui48::ParseError;
|
||||
use sea_orm::{entity::*, query::*, ActiveModelTrait, ActiveValue, EntityTrait};
|
||||
use actix_web::{error::ErrorInternalServerError, web, HttpResponse, Responder};
|
||||
use entity::node_group;
|
||||
use sea_orm::{ActiveModelTrait, ActiveValue, EntityTrait};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct NodeWithSensorData {
|
||||
node: NodeWithMac,
|
||||
sensor_data: Vec<sensor_data::Model>,
|
||||
}
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct CreateGroupWithoutId {
|
||||
name: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct NodeWithMac {
|
||||
id: String,
|
||||
#[derive(Deserialize)]
|
||||
pub struct CreateLicense {
|
||||
name: String,
|
||||
coord_la: f64,
|
||||
coord_lo: f64,
|
||||
battery_minimum: f64,
|
||||
battery_maximum: f64,
|
||||
group: uuid::Uuid,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct UpdateNode {
|
||||
coord_la: f64,
|
||||
coord_lo: f64,
|
||||
battery_minimum: f64,
|
||||
battery_maximum: f64,
|
||||
group: uuid::Uuid,
|
||||
}
|
||||
|
||||
impl From<node::Model> for NodeWithMac {
|
||||
fn from(value: node::Model) -> Self {
|
||||
let mac = convert_id_to_mac(&value.id);
|
||||
Self {
|
||||
id: mac,
|
||||
coord_la: value.coord_la,
|
||||
coord_lo: value.coord_lo,
|
||||
battery_minimum: value.battery_minimum,
|
||||
battery_maximum: value.battery_maximum,
|
||||
group: value.group,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryInto<node::Model> for NodeWithMac {
|
||||
type Error = eui48::ParseError;
|
||||
|
||||
fn try_into(self) -> Result<node::Model, Self::Error> {
|
||||
let mac = convert_mac_to_id(&self.id)?;
|
||||
Ok(node::Model {
|
||||
id: mac,
|
||||
coord_la: self.coord_la,
|
||||
coord_lo: self.coord_lo,
|
||||
battery_minimum: self.battery_minimum,
|
||||
battery_maximum: self.battery_maximum,
|
||||
group: self.group,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn convert_mac_to_id(mac: &str) -> Result<i64, ParseError> {
|
||||
let mac = eui48::MacAddress::parse_str(mac)?;
|
||||
let mac_bytes = mac.to_array();
|
||||
let mut mac_id_bytes: [u8; 8] = [0; 8];
|
||||
mac_id_bytes[2..].copy_from_slice(&mac_bytes);
|
||||
Ok(i64::from_be_bytes(mac_id_bytes))
|
||||
}
|
||||
|
||||
pub fn convert_id_to_mac(id: &i64) -> String {
|
||||
let mac_id_bytes = id.to_be_bytes();
|
||||
let mut mac_bytes: [u8; 6] = [0; 6];
|
||||
mac_bytes.copy_from_slice(&mac_id_bytes[2..]);
|
||||
eui48::MacAddress::new(mac_bytes).to_string(eui48::MacAddressFormat::Canonical)
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct GroupWithNode {
|
||||
#[serde(flatten)]
|
||||
group: node_group::Model,
|
||||
node: Vec<NodeWithMac>,
|
||||
group: entity::node_group::Model,
|
||||
node: Vec<entity::node::Model>,
|
||||
}
|
||||
|
||||
pub async fn get_nodes(state: web::Data<AppState>) -> actix_web::Result<impl Responder> {
|
||||
let db = &state.db;
|
||||
|
||||
let result: Vec<GroupWithNode> = node_group::Entity::find()
|
||||
let result = node_group::Entity::find()
|
||||
.find_with_related(entity::prelude::Node)
|
||||
.all(db)
|
||||
.await
|
||||
.map_err(ErrorInternalServerError)?
|
||||
.into_iter()
|
||||
.map(|(group, nodes)| {
|
||||
let nodes = nodes
|
||||
.into_iter()
|
||||
.map(|n| n.into())
|
||||
.collect::<Vec<NodeWithMac>>();
|
||||
GroupWithNode { group, node: nodes }
|
||||
})
|
||||
.map(|(group, node)| GroupWithNode { group, node })
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Ok(web::Json(result))
|
||||
}
|
||||
|
||||
pub async fn get_data(state: web::Data<AppState>) -> actix_web::Result<impl Responder> {
|
||||
let db = &state.db;
|
||||
|
||||
let nodes = node::Entity::find()
|
||||
.all(db)
|
||||
.await
|
||||
.map_err(ErrorInternalServerError)?;
|
||||
|
||||
let now = Utc::now();
|
||||
let one_hour_ago = now - chrono::Duration::hours(1);
|
||||
|
||||
let mut result: Vec<NodeWithSensorData> = Vec::new();
|
||||
for node in nodes {
|
||||
let sensor_data = sensor_data::Entity::find()
|
||||
.filter(sensor_data::Column::Id.eq(node.id))
|
||||
.filter(sensor_data::Column::Timestamp.gt(one_hour_ago))
|
||||
.all(db)
|
||||
.await
|
||||
.map_err(ErrorInternalServerError)?;
|
||||
|
||||
result.push(NodeWithSensorData {
|
||||
node: node.into(),
|
||||
sensor_data,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(web::Json(result))
|
||||
}
|
||||
|
||||
pub async fn create_group(
|
||||
state: web::Data<AppState>,
|
||||
group: web::Json<CreateGroupWithoutId>,
|
||||
|
@ -162,11 +60,11 @@ pub async fn create_group(
|
|||
|
||||
pub async fn create_node(
|
||||
state: web::Data<AppState>,
|
||||
node_request: web::Json<NodeWithMac>,
|
||||
node: web::Json<CreateLicense>,
|
||||
) -> actix_web::Result<impl Responder> {
|
||||
let db = &state.db;
|
||||
|
||||
let node: NodeWithMac = node_request.into_inner();
|
||||
let node = node.into_inner();
|
||||
|
||||
println!("Checking group ID: {:?}", node.group);
|
||||
|
||||
|
@ -180,41 +78,26 @@ pub async fn create_node(
|
|||
return Err(ErrorInternalServerError("Group ID does not exist"));
|
||||
}
|
||||
|
||||
let node: node::Model = node
|
||||
.try_into()
|
||||
.map_err(|_| ErrorBadRequest("Invalid Mac Address"))?;
|
||||
let node = node.into_active_model();
|
||||
let node = entity::node::ActiveModel {
|
||||
id: ActiveValue::NotSet,
|
||||
name: ActiveValue::Set(node.name),
|
||||
status: ActiveValue::NotSet,
|
||||
coord_la: ActiveValue::Set(node.coord_la),
|
||||
coord_lo: ActiveValue::Set(node.coord_lo),
|
||||
temperature: ActiveValue::NotSet,
|
||||
battery_minimum: ActiveValue::NotSet,
|
||||
battery_current: ActiveValue::NotSet,
|
||||
battery_maximum: ActiveValue::NotSet,
|
||||
voltage: ActiveValue::NotSet,
|
||||
uptime: ActiveValue::NotSet,
|
||||
group: ActiveValue::Set(node.group),
|
||||
};
|
||||
|
||||
let result = node.insert(db).await.map_err(ErrorInternalServerError)?;
|
||||
|
||||
Ok(web::Json(result))
|
||||
}
|
||||
|
||||
pub async fn update_node(
|
||||
state: web::Data<AppState>,
|
||||
node: web::Json<UpdateNode>,
|
||||
path: Path<String>,
|
||||
) -> actix_web::Result<impl Responder> {
|
||||
let db = &state.db;
|
||||
let node = node.into_inner();
|
||||
let id = path.into_inner();
|
||||
|
||||
let mac_id = convert_mac_to_id(&id).map_err(ErrorBadRequest)?;
|
||||
let node = entity::node::ActiveModel {
|
||||
id: ActiveValue::Unchanged(mac_id),
|
||||
battery_minimum: ActiveValue::Set(node.battery_minimum),
|
||||
battery_maximum: ActiveValue::Set(node.battery_maximum),
|
||||
coord_la: ActiveValue::Set(node.coord_la),
|
||||
coord_lo: ActiveValue::Set(node.coord_lo),
|
||||
group: ActiveValue::Set(node.group),
|
||||
};
|
||||
|
||||
let result = node.update(db).await.map_err(ErrorInternalServerError)?;
|
||||
|
||||
Ok(web::Json(result))
|
||||
}
|
||||
|
||||
/*
|
||||
pub async fn delete_node(
|
||||
state: web::Data<AppState>,
|
||||
path: web::Path<Uuid>,
|
||||
|
@ -230,4 +113,3 @@ pub async fn delete_node(
|
|||
|
||||
Ok(HttpResponse::Ok().finish())
|
||||
}
|
||||
*/
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
use actix_web::{web, App, HttpServer};
|
||||
use deku::prelude::*;
|
||||
use sea_orm::{ActiveModelTrait, ActiveValue, Database, DatabaseConnection};
|
||||
use sea_orm::{Database, DatabaseConnection};
|
||||
use std::env;
|
||||
use tokio::{io::AsyncReadExt, net::TcpListener};
|
||||
|
||||
mod controller;
|
||||
|
||||
|
@ -12,16 +10,7 @@ use routes::config;
|
|||
#[derive(Clone)]
|
||||
struct AppState {
|
||||
db: DatabaseConnection,
|
||||
}
|
||||
|
||||
#[derive(DekuRead, DekuWrite, Debug)]
|
||||
#[deku(endian = "little")]
|
||||
struct Data {
|
||||
#[deku(bytes = 8)]
|
||||
mac: [u8; 8],
|
||||
temp: f32,
|
||||
battery_voltage: f32,
|
||||
up_time: u64,
|
||||
secret: String,
|
||||
}
|
||||
|
||||
#[actix_web::main]
|
||||
|
@ -32,6 +21,7 @@ async fn main() -> std::io::Result<()> {
|
|||
dotenvy::dotenv().ok();
|
||||
|
||||
let db_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");
|
||||
let jwt_secret = env::var("TOKEN_SECRET").expect("TOKEN_SECRET must be set");
|
||||
|
||||
let conn = Database::connect(&db_url)
|
||||
.await
|
||||
|
@ -39,62 +29,17 @@ async fn main() -> std::io::Result<()> {
|
|||
|
||||
println!("Finished running migrations");
|
||||
|
||||
let state = AppState { db: conn.clone() };
|
||||
|
||||
tokio::spawn(async {
|
||||
let db = conn;
|
||||
let listener = TcpListener::bind("0.0.0.0:7999")
|
||||
.await
|
||||
.expect("Couldnt bind to port 7999");
|
||||
|
||||
loop {
|
||||
if let Ok((mut stream, _)) = listener.accept().await {
|
||||
println!("ESP CONNECTED");
|
||||
let mut buffer = vec![0; 24];
|
||||
loop {
|
||||
if let Ok(_) = stream.read(&mut buffer).await {
|
||||
println!("{:#x?}", &buffer);
|
||||
if let Ok((_, value)) = Data::from_bytes((&buffer, 0)) {
|
||||
println!("Received: {:#?}", value);
|
||||
|
||||
let mut mac = value.mac;
|
||||
|
||||
mac.rotate_right(2);
|
||||
|
||||
let mac = i64::from_be_bytes(mac);
|
||||
println!("MAC AS INT: {}", mac);
|
||||
|
||||
let sensor_data = entity::sensor_data::ActiveModel {
|
||||
id: ActiveValue::Set(mac),
|
||||
timestamp: ActiveValue::Set(chrono::Utc::now().naive_utc()),
|
||||
temperature: ActiveValue::Set(value.temp),
|
||||
voltage: ActiveValue::Set(value.battery_voltage),
|
||||
uptime: ActiveValue::Set(value.up_time as i64),
|
||||
};
|
||||
|
||||
let result = sensor_data.insert(&db).await;
|
||||
|
||||
match result {
|
||||
Err(_) => println!(
|
||||
"Failed to insert data (You probably didnt add the node)"
|
||||
),
|
||||
_ => (),
|
||||
}
|
||||
} else {
|
||||
println!("Failed to parse data");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
let state = AppState {
|
||||
db: conn,
|
||||
secret: jwt_secret,
|
||||
};
|
||||
|
||||
println!("Listening for connections...");
|
||||
HttpServer::new(move || {
|
||||
let cors = if cfg!(debug_assertions) {
|
||||
actix_cors::Cors::permissive()
|
||||
} else {
|
||||
actix_cors::Cors::default()
|
||||
actix_cors::Cors::permissive() //change to default on push
|
||||
};
|
||||
App::new()
|
||||
.wrap(cors)
|
||||
|
|
|
@ -19,9 +19,7 @@ pub fn config(cfg: &mut web::ServiceConfig) {
|
|||
.get(node::get_nodes)
|
||||
.post(node::create_node),
|
||||
)
|
||||
.service(web::resource("/nodes/{id}").put(node::update_node))
|
||||
.service(web::resource("/data").get(node::get_data))
|
||||
//.service(web::resource("/nodes/{id}").delete(node::delete_node))
|
||||
.service(web::resource("/nodes/{id}").delete(node::delete_node))
|
||||
.service(web::resource("/groups").post(node::create_group)),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -4,5 +4,4 @@ pub mod prelude;
|
|||
|
||||
pub mod node;
|
||||
pub mod node_group;
|
||||
pub mod sensor_data;
|
||||
pub mod user;
|
||||
|
|
|
@ -7,15 +7,24 @@ use serde::{Deserialize, Serialize};
|
|||
#[sea_orm(table_name = "node")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key, auto_increment = false)]
|
||||
pub id: i64,
|
||||
pub id: Uuid,
|
||||
pub name: String,
|
||||
pub status: bool,
|
||||
#[sea_orm(column_type = "Double")]
|
||||
pub coord_la: f64,
|
||||
#[sea_orm(column_type = "Double")]
|
||||
pub coord_lo: f64,
|
||||
#[sea_orm(column_type = "Float")]
|
||||
pub temperature: f32,
|
||||
#[sea_orm(column_type = "Double")]
|
||||
pub battery_minimum: f64,
|
||||
#[sea_orm(column_type = "Double")]
|
||||
pub battery_current: f64,
|
||||
#[sea_orm(column_type = "Double")]
|
||||
pub battery_maximum: f64,
|
||||
#[sea_orm(column_type = "Double")]
|
||||
pub voltage: f64,
|
||||
pub uptime: i64,
|
||||
pub group: Uuid,
|
||||
}
|
||||
|
||||
|
@ -29,8 +38,6 @@ pub enum Relation {
|
|||
on_delete = "Cascade"
|
||||
)]
|
||||
NodeGroup,
|
||||
#[sea_orm(has_many = "super::sensor_data::Entity")]
|
||||
SensorData,
|
||||
}
|
||||
|
||||
impl Related<super::node_group::Entity> for Entity {
|
||||
|
@ -39,10 +46,4 @@ impl Related<super::node_group::Entity> for Entity {
|
|||
}
|
||||
}
|
||||
|
||||
impl Related<super::sensor_data::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::SensorData.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
|
|
|
@ -2,5 +2,4 @@
|
|||
|
||||
pub use super::node::Entity as Node;
|
||||
pub use super::node_group::Entity as NodeGroup;
|
||||
pub use super::sensor_data::Entity as SensorData;
|
||||
pub use super::user::Entity as User;
|
||||
|
|
|
@ -1,38 +0,0 @@
|
|||
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.1
|
||||
|
||||
use sea_orm::entity::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
|
||||
#[sea_orm(table_name = "sensor_data")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key, auto_increment = false)]
|
||||
pub id: i64,
|
||||
#[sea_orm(primary_key, auto_increment = false)]
|
||||
pub timestamp: DateTime,
|
||||
#[sea_orm(column_type = "Float")]
|
||||
pub temperature: f32,
|
||||
#[sea_orm(column_type = "Float")]
|
||||
pub voltage: f32,
|
||||
pub uptime: i64,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {
|
||||
#[sea_orm(
|
||||
belongs_to = "super::node::Entity",
|
||||
from = "Column::Id",
|
||||
to = "super::node::Column::Id",
|
||||
on_update = "Cascade",
|
||||
on_delete = "Cascade"
|
||||
)]
|
||||
Node,
|
||||
}
|
||||
|
||||
impl Related<super::node::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Node.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
|
@ -21,10 +21,12 @@
|
|||
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use tokio::io::AsyncReadExt;
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
use tokio::net::TcpListener;
|
||||
|
||||
use core::str;
|
||||
use std::env;
|
||||
use std::error::Error;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> std::io::Result<()> {
|
||||
|
|
|
@ -2,7 +2,6 @@ pub use sea_orm_migration::prelude::*;
|
|||
|
||||
mod m20241008_091626_create_table_user;
|
||||
mod m20241008_095058_create_table_node;
|
||||
mod m20241013_134422_create_table_sensor_data;
|
||||
|
||||
pub struct Migrator;
|
||||
|
||||
|
@ -12,7 +11,6 @@ impl MigratorTrait for Migrator {
|
|||
vec![
|
||||
Box::new(m20241008_091626_create_table_user::Migration),
|
||||
Box::new(m20241008_095058_create_table_node::Migration),
|
||||
Box::new(m20241013_134422_create_table_sensor_data::Migration),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,11 +26,21 @@ impl MigrationTrait for Migration {
|
|||
Table::create()
|
||||
.table(Node::Table)
|
||||
.if_not_exists()
|
||||
.col(big_unsigned(Node::Id).primary_key())
|
||||
.col(
|
||||
uuid(Node::Id)
|
||||
.extra("DEFAULT gen_random_uuid()")
|
||||
.primary_key(),
|
||||
)
|
||||
.col(string(Node::Name))
|
||||
.col(boolean(Node::Status).default(false))
|
||||
.col(double(Node::CoordLa))
|
||||
.col(double(Node::CoordLo))
|
||||
.col(float(Node::Temperature).default(-127))
|
||||
.col(double(Node::BatteryMinimum).default(-127))
|
||||
.col(double(Node::BatteryCurrent).default(-127))
|
||||
.col(double(Node::BatteryMaximum).default(-127))
|
||||
.col(double(Node::Voltage).default(-127))
|
||||
.col(big_unsigned(Node::Uptime).default(0))
|
||||
.col(uuid(Node::Group))
|
||||
.foreign_key(
|
||||
ForeignKey::create()
|
||||
|
@ -61,19 +71,25 @@ impl MigrationTrait for Migration {
|
|||
}
|
||||
|
||||
#[derive(DeriveIden)]
|
||||
pub enum Node {
|
||||
enum Node {
|
||||
Table,
|
||||
Id, // Mac address
|
||||
Id,
|
||||
Name,
|
||||
Status,
|
||||
CoordLa,
|
||||
CoordLo,
|
||||
Temperature, // def: -127
|
||||
BatteryMinimum, // def: -127
|
||||
BatteryCurrent, // def: -127
|
||||
BatteryMaximum, // def: -127
|
||||
Voltage, // def: -127
|
||||
Uptime, // def: 0
|
||||
Group,
|
||||
}
|
||||
|
||||
#[derive(DeriveIden)]
|
||||
enum NodeGroup {
|
||||
Table,
|
||||
Id, // Uuid
|
||||
Name, // Groupname
|
||||
Id,
|
||||
Name,
|
||||
}
|
||||
|
|
|
@ -1,55 +0,0 @@
|
|||
use crate::m20241008_095058_create_table_node::Node;
|
||||
use sea_orm_migration::{prelude::*, schema::*};
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(SensorData::Table)
|
||||
.if_not_exists()
|
||||
.col(big_unsigned(Node::Id))
|
||||
.col(timestamp(SensorData::Timestamp))
|
||||
.primary_key(
|
||||
Index::create()
|
||||
.col(SensorData::Id)
|
||||
.col(SensorData::Timestamp),
|
||||
)
|
||||
.col(float(SensorData::Temperature).default(-127))
|
||||
.col(float(SensorData::Voltage).default(-127))
|
||||
.col(big_unsigned(SensorData::Uptime).default(0))
|
||||
.foreign_key(
|
||||
ForeignKey::create()
|
||||
.name("fk-data-node_id")
|
||||
.from(SensorData::Table, SensorData::Id)
|
||||
.to(Node::Table, Node::Id)
|
||||
.on_update(ForeignKeyAction::Cascade)
|
||||
.on_delete(ForeignKeyAction::Cascade),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
// Replace the sample below with your own migration scripts
|
||||
|
||||
manager
|
||||
.drop_table(Table::drop().table(SensorData::Table).to_owned())
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(DeriveIden)]
|
||||
enum SensorData {
|
||||
Table,
|
||||
Id, // Mac address
|
||||
Timestamp,
|
||||
Temperature, // def: -127
|
||||
Voltage, // def: -127
|
||||
Uptime, // def: 0
|
||||
}
|
Binary file not shown.
Before Width: | Height: | Size: 121 KiB |
|
@ -31,24 +31,24 @@ docker compose -f dev-compose.yml down
|
|||
# Applying Migrations
|
||||
If migrations haven't been applied yet you can apply them with the following commands:
|
||||
|
||||
*Note: Plase ensure the database and backend is running*
|
||||
*Note: Plase ensure the database is running*
|
||||
|
||||
remove old docker image if you have updated the backend
|
||||
1. remove old docker image to rebuild
|
||||
```bash
|
||||
docker image remove apfelnetzwerk-backend
|
||||
```
|
||||
|
||||
1. check which dockers are running (both the database and backend should run otherwise start them as stated above)
|
||||
2. check which dockers are running
|
||||
```bash
|
||||
docker ps
|
||||
```
|
||||
|
||||
2. attach a shell to the running backend docker container
|
||||
3. attach a shell to the running backend docker container
|
||||
```bash
|
||||
docker exec -it <id/name> /bin/bash
|
||||
docker exec -it <it/name> /bin/bash
|
||||
```
|
||||
|
||||
3. apply migrations
|
||||
4. apply migrations
|
||||
```bash
|
||||
#(go into the crates folder)
|
||||
cd crates
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 240 KiB |
Binary file not shown.
Before Width: | Height: | Size: 207 KiB |
Binary file not shown.
Before Width: | Height: | Size: 106 KiB |
|
@ -1,23 +0,0 @@
|
|||
# Info
|
||||
## Important Commands
|
||||
Command to configure build options of the project:
|
||||
`make menuconfig`
|
||||
|
||||
Command to flash the firmware:
|
||||
`make flash`
|
||||
|
||||
Command to show the serial monitor:
|
||||
`make monitor`
|
||||
|
||||
## Connection Error
|
||||
If you encounter the error `[Errno 2]`, it likely means the microcontroller is not connected or the wrong tty port is selected.
|
||||
To select the correct tty port, use the command `make menuconfig`, then navigate to **Serial Flasher Config -> Default Serial Port** and choose the correct port.
|
||||
|
||||
## Sensor Issues
|
||||
If the sensor output is around **80°C**, there is likely a power connection issue.
|
||||
If the output is around **-127°C**, the sensor's data wire is probably not connected.
|
||||
|
||||
## Compilation Errors
|
||||
If the temperature input is `none`, it might be due to a compilation error caused by an incorrect SDK configuration.
|
||||
Use the command `make menuconfig` and change the following settings:
|
||||
**Component config -> newlib -> Disable nano formatting**
|
|
@ -1,31 +0,0 @@
|
|||
# DS18B20 Component
|
||||
Simple DS18B20 temperature sensor library for [ESP8266 RTOS SDK](https://github.com/espressif/ESP8266_RTOS_SDK) for reading Celsius temperature with different resolutions from singular device.
|
||||
|
||||
## Usage
|
||||
```
|
||||
// Create variable for handler
|
||||
ds18b20_handler_t sensor;
|
||||
|
||||
// Check for any initialization failures
|
||||
if (!ds18b20_init(&sensor, GPIO_NUM_12, TEMP_RES_12_BIT))
|
||||
{
|
||||
ESP_LOGE("TAG", "Failed to initalize DS18B20!");
|
||||
|
||||
return 0; // Exit
|
||||
}
|
||||
|
||||
float temp = 0;
|
||||
|
||||
// Initalize conversion
|
||||
ds18b20_convert_temp(&sensor);
|
||||
|
||||
// If you doesn't convert temperature you may read 85.0 Celsius,
|
||||
// as it is default temperature set by DS18B20 if convert command wasn't issued.
|
||||
temp = ds18b20_read_temp(&sensor); // Read temperature
|
||||
|
||||
// Print temperature with 4 decimal places
|
||||
// (12 bit resolution measurement accuracy is 0.0625 Celsius)
|
||||
ESP_LOGI("TAG", "Temperature = %.4f", temp);
|
||||
```
|
||||
|
||||
> **_NOTE:_** If last statement doesn't print temperature you may have to disable Newlib nano in `menuconfig` of RTOS SDK.
|
|
@ -1,5 +0,0 @@
|
|||
#
|
||||
# Component Makefile
|
||||
#
|
||||
|
||||
COMPONENT_ADD_INCLUDEDIRS := .
|
|
@ -1,120 +0,0 @@
|
|||
#include "ds18b20.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
static const char *TAG_DS18B20 = "DS18B20";
|
||||
static const uint16_t ds18b20_temp_conv_time[] = {94, 188, 375, 750}; // ms
|
||||
static const uint16_t ds18b20_resolution_val[] = {0x1F, 0x3F, 0x5F, 0x7F};
|
||||
|
||||
uint8_t ds18b20_init(ds18b20_handler_t *device, gpio_num_t pin, ds18b20_temp_res_t resolution)
|
||||
{
|
||||
if (!device)
|
||||
{
|
||||
ESP_LOGW(TAG_DS18B20, "device is null!");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!onewire_init(&device->bus, pin, NULL))
|
||||
{
|
||||
ESP_LOGW(TAG_DS18B20, "Failed to initialize onewire bus");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
device->res = resolution;
|
||||
|
||||
// Configure resolution
|
||||
ds18b20_write_scratchpad(device);
|
||||
ds18b20_read_scratchpad(device);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void ds18b20_send_command(ds18b20_handler_t *device, ds18b20_commands_t command)
|
||||
{
|
||||
uint8_t payload = 0x0 ^ command;
|
||||
|
||||
onewire_write_byte(&device->bus, payload);
|
||||
}
|
||||
|
||||
void ds18b20_convert_temp(ds18b20_handler_t *device)
|
||||
{
|
||||
onewire_reset(&device->bus);
|
||||
onewire_send_command(&device->bus, _ROM_SKIP);
|
||||
|
||||
ds18b20_send_command(device, _CONVERT_T);
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(ds18b20_temp_conv_time[device->res]));
|
||||
}
|
||||
|
||||
void ds18b20_write_scratchpad(ds18b20_handler_t *device)
|
||||
{
|
||||
onewire_reset(&device->bus);
|
||||
onewire_send_command(&device->bus, _ROM_SKIP);
|
||||
|
||||
ds18b20_send_command(device, _SCRATCH_WRITE);
|
||||
|
||||
// Th and Tl registers
|
||||
onewire_write_byte(&device->bus, 0);
|
||||
onewire_write_byte(&device->bus, 0);
|
||||
// Resolution value
|
||||
onewire_write_byte(&device->bus, ds18b20_resolution_val[device->res]);
|
||||
}
|
||||
|
||||
void ds18b20_copy_scratchpad(ds18b20_handler_t *device)
|
||||
{
|
||||
onewire_reset(&device->bus);
|
||||
onewire_send_command(&device->bus, _ROM_SKIP);
|
||||
|
||||
ds18b20_send_command(device, _SCRATCH_COPY);
|
||||
}
|
||||
|
||||
void ds18b20_read_scratchpad(ds18b20_handler_t *device)
|
||||
{
|
||||
onewire_reset(&device->bus);
|
||||
onewire_send_command(&device->bus, _ROM_SKIP);
|
||||
|
||||
ds18b20_send_command(device, _SCRATCH_READ);
|
||||
|
||||
uint8_t i;
|
||||
for (i = 0; i < 9; i++)
|
||||
{
|
||||
device->scratchpad[i] = onewire_read_byte(&device->bus);
|
||||
}
|
||||
}
|
||||
|
||||
void ds18b20_print_scratchpad(ds18b20_handler_t *device)
|
||||
{
|
||||
uint8_t i;
|
||||
for (i = 0; i < 9; i++)
|
||||
{
|
||||
printf("%x ", device->scratchpad[i]);
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
float ds18b20_read_temp(ds18b20_handler_t *device)
|
||||
{
|
||||
ds18b20_read_scratchpad(device);
|
||||
|
||||
uint8_t sign = 0x0;
|
||||
uint8_t lsb = device->scratchpad[0];
|
||||
uint8_t mask = 0xFF << (TEMP_RES_12_BIT - device->res);
|
||||
lsb &= mask; // Mask out last 3 bits accordingly
|
||||
uint8_t msb = device->scratchpad[1];
|
||||
|
||||
sign = msb & 0x80;
|
||||
int16_t temp = 0x0;
|
||||
|
||||
temp = lsb + (msb << 8);
|
||||
|
||||
if (sign)
|
||||
{
|
||||
temp = ~(-temp) + 1; // Convert signed two complement's
|
||||
}
|
||||
|
||||
return temp / 16.0;
|
||||
}
|
|
@ -1,100 +0,0 @@
|
|||
#ifndef DS18B20_H
|
||||
#define DS18B20_H
|
||||
|
||||
#include "onewire.h"
|
||||
|
||||
typedef enum
|
||||
{
|
||||
TEMP_RES_9_BIT = 0,
|
||||
TEMP_RES_10_BIT = 1,
|
||||
TEMP_RES_11_BIT = 2,
|
||||
TEMP_RES_12_BIT = 3
|
||||
} ds18b20_temp_res_t;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
_SCRATCH_WRITE = 0x4E,
|
||||
_SCRATCH_READ = 0xBE,
|
||||
_SCRATCH_COPY = 0x48,
|
||||
_CONVERT_T = 0x44
|
||||
} ds18b20_commands_t;
|
||||
|
||||
typedef uint8_t ds18b20_scratchpad_t[9];
|
||||
|
||||
typedef struct
|
||||
{
|
||||
onewire_bus_handle_t bus;
|
||||
ds18b20_temp_res_t res;
|
||||
ds18b20_scratchpad_t scratchpad;
|
||||
} ds18b20_handler_t;
|
||||
|
||||
/**
|
||||
* @brief Initialize DS18B20
|
||||
*
|
||||
* @param device DS18B20 handler
|
||||
* @param pin Data pin
|
||||
* @param resolution Temperature resolution
|
||||
*
|
||||
* @retval 1: Success
|
||||
* @retval 0: Incorrect pin or gpio configuration failed (Logs tells which happened)
|
||||
*/
|
||||
uint8_t ds18b20_init(ds18b20_handler_t *device, gpio_num_t pin, ds18b20_temp_res_t resolution);
|
||||
|
||||
/**
|
||||
* @brief Send command to DS18B20
|
||||
*
|
||||
* @param device DS18B20 handler
|
||||
* @param command Function command
|
||||
*/
|
||||
void ds18b20_send_command(ds18b20_handler_t *device, ds18b20_commands_t command);
|
||||
|
||||
/**
|
||||
* @brief Write to scratchpad
|
||||
*
|
||||
* @param device DS18B20 handler
|
||||
*/
|
||||
void ds18b20_write_scratchpad(ds18b20_handler_t *device);
|
||||
|
||||
/**
|
||||
* @brief Read from scratchpad
|
||||
*
|
||||
* @param device DS18B20 handler
|
||||
*/
|
||||
void ds18b20_read_scratchpad(ds18b20_handler_t *device);
|
||||
|
||||
/**
|
||||
* @brief Copy to scratchpad
|
||||
*
|
||||
* @param device DS18B20 handler
|
||||
*/
|
||||
void ds18b20_copy_scratchpad(ds18b20_handler_t *device);
|
||||
|
||||
/**
|
||||
* @brief Print scratchpad bytes
|
||||
*
|
||||
* @param device DS18B20 handler
|
||||
*/
|
||||
void ds18b20_print_scratchpad(ds18b20_handler_t *device);
|
||||
|
||||
/**
|
||||
* @brief Initialize temperature conversion and wait for conversion
|
||||
*
|
||||
* Function sends CONV_T command and waits for X ms according to `ds18b20_temp_conv_time` static array
|
||||
*
|
||||
* @warning Should be called before `ds18b20_convert_temp()` function
|
||||
*
|
||||
* @param device DS18B20 handler
|
||||
*/
|
||||
void ds18b20_convert_temp(ds18b20_handler_t *device);
|
||||
|
||||
/**
|
||||
* @brief Read temperature from scratchpad
|
||||
*
|
||||
* Function reads temperature from scratchpad and converts it to Celsius.
|
||||
* @warning `ds18b20_convert_temp()` have to be called before for updated temperature.
|
||||
*
|
||||
* @param device DS18B20 handler
|
||||
*/
|
||||
float ds18b20_read_temp(ds18b20_handler_t *device);
|
||||
|
||||
#endif
|
|
@ -1,5 +0,0 @@
|
|||
#
|
||||
# Component Makefile
|
||||
#
|
||||
|
||||
COMPONENT_ADD_INCLUDEDIRS := .
|
|
@ -1,175 +0,0 @@
|
|||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_log.h"
|
||||
#include "rom/ets_sys.h"
|
||||
#include "onewire.h"
|
||||
|
||||
uint8_t onewire_configure_gpio(gpio_num_t pin, gpio_config_t *custom_config)
|
||||
{
|
||||
if (!GPIO_IS_VALID_GPIO(pin))
|
||||
{
|
||||
ESP_LOGE(TAG_ONEWIRE, "Provided pin is incorrect!");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
gpio_config_t config = {};
|
||||
|
||||
if (!custom_config)
|
||||
{
|
||||
config.intr_type = GPIO_INTR_DISABLE;
|
||||
config.mode = GPIO_MODE_OUTPUT_OD;
|
||||
config.pin_bit_mask = ((uint32_t)1 << pin);
|
||||
config.pull_down_en = 0;
|
||||
config.pull_up_en = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
config = *custom_config;
|
||||
}
|
||||
|
||||
if (gpio_config(&config) != ESP_OK)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
uint8_t onewire_init(onewire_bus_handle_t *bus, gpio_num_t bus_pin, gpio_config_t *custom_config)
|
||||
{
|
||||
if (!bus)
|
||||
{
|
||||
ESP_LOGW(TAG_ONEWIRE, "bus is null! (onewire_init)");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bus->pin = bus_pin;
|
||||
bus->mutex = xSemaphoreCreateMutex();
|
||||
|
||||
// configure GPIO
|
||||
if (!onewire_configure_gpio(bus_pin, custom_config))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
uint8_t onewire_reset(onewire_bus_handle_t *bus)
|
||||
{
|
||||
uint8_t presence;
|
||||
|
||||
if (xSemaphoreTake(bus->mutex, _BLOCK_TIME))
|
||||
{
|
||||
gpio_set_level(bus->pin, 0); // Send reset pulse
|
||||
ets_delay_us(_ONEWIRE_RESET_WAIT);
|
||||
|
||||
gpio_set_level(bus->pin, 1); // Leave floating
|
||||
ets_delay_us(_ONEWIRE_PRESENCE_WAIT);
|
||||
|
||||
presence = !gpio_get_level(bus->pin);
|
||||
|
||||
xSemaphoreGive(bus->mutex);
|
||||
}
|
||||
else
|
||||
{
|
||||
ESP_LOGE(TAG_ONEWIRE, _SEMFAIL_MSG, "onewire_reset");
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
ets_delay_us(_ONEWIRE_RESET_RECOVERY);
|
||||
|
||||
return presence;
|
||||
}
|
||||
|
||||
void onewire_write_bit(onewire_bus_handle_t *bus, uint8_t bit)
|
||||
{
|
||||
if (xSemaphoreTake(bus->mutex, _BLOCK_TIME))
|
||||
{
|
||||
if (bit)
|
||||
{
|
||||
// Write 1
|
||||
gpio_set_level(bus->pin, 0);
|
||||
ets_delay_us(_ONEWIRE_WRITE1_LOW);
|
||||
|
||||
gpio_set_level(bus->pin, 1);
|
||||
ets_delay_us(_ONEWIRE_WRITE1_WAIT);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Write 0
|
||||
gpio_set_level(bus->pin, 0);
|
||||
ets_delay_us(_ONEWIRE_WRITE0_LOW);
|
||||
|
||||
gpio_set_level(bus->pin, 1);
|
||||
ets_delay_us(_ONEWIRE_WRITE0_WAIT);
|
||||
}
|
||||
|
||||
xSemaphoreGive(bus->mutex);
|
||||
}
|
||||
else
|
||||
{
|
||||
ESP_LOGE(TAG_ONEWIRE, _SEMFAIL_MSG, "onewire_write_bit");
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t onewire_read_bit(onewire_bus_handle_t *bus)
|
||||
{
|
||||
uint8_t bit;
|
||||
|
||||
if (xSemaphoreTake(bus->mutex, _BLOCK_TIME))
|
||||
{
|
||||
gpio_set_level(bus->pin, 0);
|
||||
ets_delay_us(_ONEWIRE_WRITE1_LOW);
|
||||
|
||||
gpio_set_level(bus->pin, 1);
|
||||
ets_delay_us(_ONEWIRE_READ_WAIT);
|
||||
|
||||
bit = !gpio_get_level(bus->pin);
|
||||
|
||||
xSemaphoreGive(bus->mutex);
|
||||
|
||||
ets_delay_us(_ONEWIRE_READ_RECOVERY);
|
||||
}
|
||||
else
|
||||
{
|
||||
ESP_LOGE(TAG_ONEWIRE, _SEMFAIL_MSG, "onewire_read_bit");
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
return bit;
|
||||
}
|
||||
|
||||
void onewire_write_byte(onewire_bus_handle_t *bus, uint8_t byte)
|
||||
{
|
||||
uint8_t i;
|
||||
|
||||
for (i = 0; i < 8; i++)
|
||||
{
|
||||
onewire_write_bit(bus, (byte >> i) & 0x01);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t onewire_read_byte(onewire_bus_handle_t *bus)
|
||||
{
|
||||
uint8_t i;
|
||||
uint8_t byte = 0x0;
|
||||
|
||||
for (i = 0; i < 8; i++)
|
||||
{
|
||||
byte |= (!onewire_read_bit(bus) << i);
|
||||
}
|
||||
|
||||
return byte;
|
||||
}
|
||||
|
||||
void onewire_send_command(onewire_bus_handle_t *bus, onewire_rom_commands_t command)
|
||||
{
|
||||
uint8_t payload = 0x0 ^ command;
|
||||
|
||||
onewire_write_byte(bus, payload);
|
||||
}
|
|
@ -1,123 +0,0 @@
|
|||
#ifndef ONEWIRE_H
|
||||
#define ONEWIRE_H
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "esp_types.h"
|
||||
#include "esp_err.h"
|
||||
|
||||
#define _ONEWIRE_WRITE1_LOW 6
|
||||
#define _ONEWIRE_WRITE1_WAIT 64
|
||||
#define _ONEWIRE_WRITE0_LOW 60
|
||||
#define _ONEWIRE_WRITE0_WAIT 10
|
||||
#define _ONEWIRE_READ_WAIT 9
|
||||
#define _ONEWIRE_READ_RECOVERY 55
|
||||
#define _ONEWIRE_RESET_WAIT 480
|
||||
#define _ONEWIRE_PRESENCE_WAIT 70
|
||||
#define _ONEWIRE_RESET_RECOVERY 410
|
||||
|
||||
#define _BLOCK_TIME pdMS_TO_TICKS(1000)
|
||||
#define _SEMFAIL_MSG "Failed to obtain semaphore. (%s)"
|
||||
|
||||
static const char *TAG_ONEWIRE = "ONEWIRE";
|
||||
|
||||
typedef enum
|
||||
{
|
||||
_ROM_READ = 0x33,
|
||||
_ROM_SEARCH = 0xF0,
|
||||
_ROM_MATCH = 0x55,
|
||||
_ROM_SKIP = 0xCC
|
||||
} onewire_rom_commands_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
gpio_num_t pin;
|
||||
SemaphoreHandle_t mutex;
|
||||
} onewire_bus_handle_t;
|
||||
|
||||
/**
|
||||
* @brief Configure gpio pins for onewire communication
|
||||
*
|
||||
* Set `custom_config` to NULL for default config.
|
||||
*
|
||||
* @param pin Bus pin
|
||||
* @param custom_config Custom gpio config
|
||||
*
|
||||
* @retval 1: Success
|
||||
* @retval 0: Incorrect pin or gpio configuration failed (Logs tells which happened)
|
||||
*/
|
||||
uint8_t onewire_configure_gpio(gpio_num_t pin, gpio_config_t *custom_config);
|
||||
|
||||
/**
|
||||
* @brief Initalize onewire bus
|
||||
*
|
||||
* Set `custom_config` to NULL for default config.
|
||||
* @warning MUST be called before any other library function!
|
||||
*
|
||||
* @param bus Bus handle
|
||||
* @param pin Bus pin
|
||||
* @param custom_config Custom gpio config
|
||||
*
|
||||
* @retval 1: Success
|
||||
* @retval 0: `bus` is NULL or gpio configuration failed (Logs tells which happened)
|
||||
*/
|
||||
uint8_t onewire_init(onewire_bus_handle_t *bus, gpio_num_t bus_pin, gpio_config_t *custom_config);
|
||||
|
||||
/**
|
||||
* @brief Send reset pulse
|
||||
*
|
||||
* @param bus Bus handle
|
||||
*
|
||||
* @retval 1: Success (device sent presence pulse)
|
||||
* @retval -1: Failed to obtain semaphore for gpio handling
|
||||
* @retval 0: Device failed to return presence pulse
|
||||
*/
|
||||
uint8_t onewire_reset(onewire_bus_handle_t *bus);
|
||||
|
||||
/**
|
||||
* @brief Write bit
|
||||
*
|
||||
* @param bus Bus handle
|
||||
* @param bit Bit to send
|
||||
*/
|
||||
void onewire_write_bit(onewire_bus_handle_t *bus, uint8_t bit);
|
||||
|
||||
/**
|
||||
* @brief Write byte
|
||||
*
|
||||
* @param bus Bus handle
|
||||
* @param bit Byte to send
|
||||
*/
|
||||
void onewire_write_byte(onewire_bus_handle_t *bus, uint8_t byte);
|
||||
|
||||
/**
|
||||
* @brief Read bit
|
||||
*
|
||||
* @param bus Bus handle
|
||||
*
|
||||
* @retval 1: Device returned 1
|
||||
* @retval 0: Device returned 0
|
||||
* @retval -1: Failed to obtain semaphore for gpio handling
|
||||
*/
|
||||
uint8_t onewire_read_bit(onewire_bus_handle_t *bus);
|
||||
|
||||
/**
|
||||
* @brief Read bit
|
||||
*
|
||||
* @param bus Bus handle
|
||||
*
|
||||
* @return Byte returned by device
|
||||
*/
|
||||
uint8_t onewire_read_byte(onewire_bus_handle_t *bus);
|
||||
|
||||
/**
|
||||
* @brief Send command to device
|
||||
*
|
||||
* @param bus Bus handle
|
||||
* @param command Onewire rom command
|
||||
*
|
||||
*/
|
||||
void onewire_send_command(onewire_bus_handle_t *bus, onewire_rom_commands_t command);
|
||||
|
||||
#endif
|
|
@ -3,84 +3,51 @@
|
|||
#include "zh_network.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "lwip/sockets.h"
|
||||
#include "ds18b20.h"
|
||||
#include "onewire.h"
|
||||
#include <strings.h>
|
||||
|
||||
// #define EXIT_NODE
|
||||
static const char *payload = "Nüttchen";
|
||||
|
||||
#define GPIO_OUTPUT_IO_0 2
|
||||
#define GPIO_OUTPUT_IO_1 16
|
||||
#define GPIO_OUTPUT_PIN_SEL ((1ULL << GPIO_OUTPUT_IO_0) | (1ULL << GPIO_OUTPUT_IO_1))
|
||||
|
||||
#ifdef EXIT_NODE
|
||||
#define TAG "TCP"
|
||||
#define PORT 7999
|
||||
#define HOST_IP_ADDR
|
||||
#define ESP_WIFI_SSID
|
||||
#define ESP_WIFI_PASS
|
||||
#endif
|
||||
|
||||
#define ESP_CHANNEL 7
|
||||
#define EXAMPLE_ESP_WIFI_SSID
|
||||
#define EXAMPLE_ESP_WIFI_PASS
|
||||
|
||||
#define MAC2STR(a) (a)[0], (a)[1], (a)[2], (a)[3], (a)[4], (a)[5]
|
||||
|
||||
void zh_network_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data);
|
||||
|
||||
uint8_t broadcast[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
|
||||
uint8_t test[6] = {0x09, 0xF2, 0x69, 0x42, 0x11, 0xA9};
|
||||
uint8_t _self_mac[6] = {0};
|
||||
// bc:dd:c2:82:82:9e
|
||||
uint8_t target[6] = {0xBC, 0xDD, 0xC2, 0x82, 0x82, 0x9E};
|
||||
|
||||
typedef struct
|
||||
{
|
||||
float temperature;
|
||||
float battery_voltage;
|
||||
unsigned long up_time;
|
||||
} sensor_message_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint8_t mac[6];
|
||||
float temperature;
|
||||
float battery_voltage;
|
||||
unsigned long up_time;
|
||||
} tcp_message_t;
|
||||
|
||||
int sock;
|
||||
|
||||
int getUpTime()
|
||||
{
|
||||
// Get system uptime in milliseconds
|
||||
int uptime = (xTaskGetTickCount() * (1000 / configTICK_RATE_HZ));
|
||||
return uptime;
|
||||
}
|
||||
|
||||
float getTemp()
|
||||
{
|
||||
float temp = 0.0;
|
||||
ds18b20_handler_t sensor;
|
||||
|
||||
// Initialize DS18B20 sensor
|
||||
if (!ds18b20_init(&sensor, GPIO_NUM_2, TEMP_RES_12_BIT))
|
||||
{
|
||||
ESP_LOGE("DS18B20", "Failed to initialize DS18B20 sensor!");
|
||||
return -1.0; // Indicate an error with a negative value
|
||||
}
|
||||
|
||||
// Convert temperature
|
||||
ds18b20_convert_temp(&sensor);
|
||||
|
||||
// Read the temperature
|
||||
temp = ds18b20_read_temp(&sensor);
|
||||
|
||||
// Check if the temperature is within a reasonable range for DS18B20
|
||||
if (temp < -55.0 || temp > 125.0)
|
||||
{
|
||||
ESP_LOGE("DS18B20", "Temperature reading out of range: %.2f", temp);
|
||||
return -1.0; // Indicate invalid reading
|
||||
}
|
||||
|
||||
return temp;
|
||||
}
|
||||
char char_value[30];
|
||||
int int_value;
|
||||
float float_value;
|
||||
bool bool_value;
|
||||
} example_message_t;
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
gpio_config_t io_conf;
|
||||
// disable interrupt
|
||||
io_conf.intr_type = GPIO_INTR_DISABLE;
|
||||
// set as output mode
|
||||
io_conf.mode = GPIO_MODE_OUTPUT;
|
||||
// bit mask of the pins that you want to set,e.g.GPIO15/16
|
||||
io_conf.pin_bit_mask = GPIO_OUTPUT_PIN_SEL;
|
||||
// disable pull-down mode
|
||||
io_conf.pull_down_en = 0;
|
||||
// disable pull-up mode
|
||||
io_conf.pull_up_en = 0;
|
||||
// configure GPIO with the given settings
|
||||
gpio_config(&io_conf);
|
||||
|
||||
esp_log_level_set("zh_vector", ESP_LOG_NONE);
|
||||
esp_log_level_set("zh_network", ESP_LOG_NONE);
|
||||
nvs_flash_init();
|
||||
|
@ -88,22 +55,21 @@ void app_main(void)
|
|||
esp_event_loop_create_default();
|
||||
wifi_init_config_t wifi_init_config = WIFI_INIT_CONFIG_DEFAULT();
|
||||
esp_wifi_init(&wifi_init_config);
|
||||
esp_wifi_set_mode(WIFI_MODE_STA);
|
||||
|
||||
#ifdef EXIT_NODE
|
||||
wifi_config_t wifi_config = {
|
||||
.sta = {
|
||||
.ssid = ESP_WIFI_SSID,
|
||||
.password = ESP_WIFI_PASS,
|
||||
.channel = ESP_CHANNEL},
|
||||
.ssid = EXAMPLE_ESP_WIFI_SSID,
|
||||
.password = EXAMPLE_ESP_WIFI_PASS,
|
||||
.channel = 7},
|
||||
|
||||
};
|
||||
|
||||
esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config);
|
||||
|
||||
esp_wifi_set_channel(ESP_CHANNEL, 1);
|
||||
esp_wifi_connect();
|
||||
#endif
|
||||
esp_wifi_set_mode(WIFI_MODE_STA);
|
||||
esp_wifi_start();
|
||||
esp_wifi_set_channel(7, 1);
|
||||
esp_wifi_connect();
|
||||
// esp_wifi_set_max_tx_power(8); // Power reduction is for example and testing purposes only. Do not use in your own programs!
|
||||
zh_network_init_config_t network_init_config = ZH_NETWORK_INIT_CONFIG_DEFAULT();
|
||||
network_init_config.max_waiting_time = 1000;
|
||||
|
@ -115,8 +81,10 @@ void app_main(void)
|
|||
esp_event_handler_instance_register(ZH_NETWORK, ESP_EVENT_ANY_ID, &zh_network_event_handler, NULL, NULL);
|
||||
#endif
|
||||
|
||||
#ifdef EXIT_NODE
|
||||
uint8_t data = 0xFF;
|
||||
char rx_buffer[128];
|
||||
char addr_str[128];
|
||||
int addr_family;
|
||||
int ip_protocol;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
|
@ -126,7 +94,7 @@ void app_main(void)
|
|||
destAddr.sin_port = htons(7999);
|
||||
inet_pton(AF_INET, HOST_IP_ADDR, &destAddr.sin_addr);
|
||||
|
||||
sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||
int sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||
if (sock < 0)
|
||||
{
|
||||
ESP_LOGE(TAG, "Unable to create socket: errno %d", errno);
|
||||
|
@ -138,20 +106,13 @@ void app_main(void)
|
|||
{
|
||||
ESP_LOGE(TAG, "Socket unable to connect: errno %d", errno);
|
||||
close(sock);
|
||||
vTaskDelay(500 / portTICK_PERIOD_MS);
|
||||
continue;
|
||||
}
|
||||
ESP_LOGI(TAG, "Successfully connected");
|
||||
for (;;)
|
||||
{
|
||||
tcp_message_t packet;
|
||||
esp_read_mac(packet.mac, 0);
|
||||
packet.battery_voltage = 3.3f;
|
||||
packet.temperature = getTemp();
|
||||
packet.up_time = getUpTime();
|
||||
vTaskDelay(10000 / portTICK_PERIOD_MS);
|
||||
zh_network_send(broadcast, (uint8_t *)&data, sizeof(data));
|
||||
int err = send(sock, (uint8_t *)&packet, sizeof(tcp_message_t), 0);
|
||||
vTaskDelay(5000 / portTICK_PERIOD_MS);
|
||||
int err = send(sock, payload, strlen(payload), 0);
|
||||
if (err < 0)
|
||||
{
|
||||
ESP_LOGE(TAG, "Error ocured during sending: errno %d", errno);
|
||||
|
@ -160,7 +121,20 @@ void app_main(void)
|
|||
}
|
||||
close(sock);
|
||||
}
|
||||
#endif
|
||||
|
||||
example_message_t send_message = {0};
|
||||
strcpy(send_message.char_value, "Test Message");
|
||||
send_message.float_value = 1.234;
|
||||
send_message.bool_value = false;
|
||||
for (;;)
|
||||
{
|
||||
printf("Sending Message");
|
||||
zh_network_send(target, (uint8_t *)&send_message, sizeof(send_message));
|
||||
vTaskDelay(5000 / portTICK_PERIOD_MS);
|
||||
gpio_set_level(GPIO_OUTPUT_IO_0, 0);
|
||||
vTaskDelay(100 / portTICK_PERIOD_MS);
|
||||
gpio_set_level(GPIO_OUTPUT_IO_0, 1);
|
||||
}
|
||||
}
|
||||
|
||||
void zh_network_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data)
|
||||
|
@ -169,28 +143,17 @@ void zh_network_event_handler(void *arg, esp_event_base_t event_base, int32_t ev
|
|||
{
|
||||
case ZH_NETWORK_ON_RECV_EVENT:;
|
||||
zh_network_event_on_recv_t *recv_data = event_data;
|
||||
#ifdef EXIT_NODE
|
||||
sensor_message_t *recv_message = (sensor_message_t *)recv_data->data;
|
||||
tcp_message_t packet;
|
||||
memcpy(packet.mac, recv_data->mac_addr, 6);
|
||||
packet.temperature = recv_message->temperature;
|
||||
packet.battery_voltage = recv_message->battery_voltage;
|
||||
packet.up_time = recv_message->up_time;
|
||||
heap_caps_free(recv_data->data);
|
||||
int err = send(sock, (uint8_t *)&packet, sizeof(packet), 0);
|
||||
if (err < 0)
|
||||
{
|
||||
ESP_LOGE(TAG, "Error sending TCP data");
|
||||
break;
|
||||
}
|
||||
#else
|
||||
heap_caps_free(recv_data->data);
|
||||
sensor_message_t message = {0};
|
||||
message.temperature = getTemp();
|
||||
message.battery_voltage = 3.3f;
|
||||
message.up_time = getUpTime();
|
||||
zh_network_send(recv_data->mac_addr, (uint8_t *)&message, sizeof(message));
|
||||
#endif
|
||||
printf("Message from MAC %02X:%02X:%02X:%02X:%02X:%02X is received. Data lenght %d bytes.\n", MAC2STR(recv_data->mac_addr), recv_data->data_len);
|
||||
example_message_t *recv_message = (example_message_t *)recv_data->data;
|
||||
printf("Char %s\n", recv_message->char_value);
|
||||
printf("Int %d\n", recv_message->int_value);
|
||||
printf("Float %f\n", recv_message->float_value);
|
||||
printf("Bool %d\n", recv_message->bool_value);
|
||||
heap_caps_free(recv_data->data); // Do not delete to avoid memory leaks!
|
||||
gpio_set_level(GPIO_OUTPUT_IO_0, 0);
|
||||
vTaskDelay(100 / portTICK_PERIOD_MS);
|
||||
gpio_set_level(GPIO_OUTPUT_IO_0, 1);
|
||||
break;
|
||||
case ZH_NETWORK_ON_SEND_EVENT:;
|
||||
zh_network_event_on_send_t *send_data = event_data;
|
||||
if (send_data->status == ZH_NETWORK_SEND_SUCCESS)
|
||||
|
@ -205,6 +168,4 @@ void zh_network_event_handler(void *arg, esp_event_base_t event_base, int32_t ev
|
|||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// BoskoopBase
|
||||
}
|
|
@ -1,11 +1,9 @@
|
|||
import axios from "axios";
|
||||
import { store } from "@/store";
|
||||
let axiosInstance = axios.create({
|
||||
baseURL: import.meta.env.VITE_BACKEND_URL,
|
||||
});
|
||||
axiosInstance.interceptors.request.use((config) => {
|
||||
config.headers.Authorization = `Bearer ${store.token}`;
|
||||
return config;
|
||||
});
|
||||
|
||||
export { axiosInstance };
|
||||
export { axiosInstance };
|
|
@ -19,6 +19,14 @@
|
|||
import { store } from "@/store";
|
||||
import { ref } from "vue";
|
||||
import { LogOut } from "lucide-vue-next";
|
||||
import { useQuery } from "@tanstack/vue-query";
|
||||
import UsersDialog from "./NavBarIcons/UsersDialog.vue";
|
||||
import UsersEditActions from "./NavBarIcons/UsersEditActions.vue";
|
||||
import { axiosInstance } from "@/client";
|
||||
import { User } from "@/types";
|
||||
import { search } from "@/store";
|
||||
|
||||
|
||||
|
||||
function handlelogout() {
|
||||
store.setToken(null);
|
||||
|
|
|
@ -5,11 +5,11 @@
|
|||
<v-expansion-panel>
|
||||
<v-expansion-panel-title>
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div class="mr-3" v-if="latestSensorData?.voltage">
|
||||
Name: {{ node.id }} (Status: online)
|
||||
<div class="mr-3" v-if="node.status === 200">
|
||||
Name: {{ node.name }} (Status: online)
|
||||
</div>
|
||||
<div class="mr-3" v-else>
|
||||
Name: {{ node.id }} (Status: offline)
|
||||
Name: {{ node.name }} (Status: offline)
|
||||
</div>
|
||||
</div>
|
||||
</v-expansion-panel-title>
|
||||
|
@ -18,21 +18,21 @@
|
|||
<div class="d-flex flex-column">
|
||||
<div class="d-flex align-items-center mb-2">
|
||||
<span class="mr-2">Coordinates:</span>
|
||||
<span>La: {{ node.coord_la }}, Long: {{ node.coord_lo }}</span>
|
||||
<span>La: {{ node.coordla }}, Long: {{ node.coordlong }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex align-items-center mb-2">
|
||||
<span class="mr-2">Temperature:</span>
|
||||
<span>{{ latestSensorData?.temperature !== undefined ? latestSensorData.temperature : 'NaN' }}°C</span>
|
||||
<span>{{ node.temperature }}°C</span>
|
||||
</div>
|
||||
<div class="d-flex align-items-center mb-2">
|
||||
<span class="mr-2">Battery Voltage:</span>
|
||||
<span>{{ latestSensorData?.voltage || 'N/A' }}V</span>
|
||||
<span class="mr-2">Battery:</span>
|
||||
<span>{{ node.batteryCurrent }}%</span>
|
||||
</div>
|
||||
<div class="d-flex align-items-center mb-2">
|
||||
<span class="mr-2">Runtime:</span>
|
||||
<span>{{ latestSensorData?.uptime ? (latestSensorData.uptime / 3600).toFixed(2) + ' hours' : 'N/A' }}</span>
|
||||
<span>{{ node.uptime }} hours</span>
|
||||
</div>
|
||||
</div>
|
||||
</v-expansion-panel-text>
|
||||
</v-expansion-panel>
|
||||
</v-expansion-panels>
|
||||
|
@ -41,46 +41,31 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Node, SensorData } from "@/types";
|
||||
import { ComputedRef, inject, ref, watch, onMounted, computed, onUnmounted } from "vue";
|
||||
import { Node } from "@/types";
|
||||
import { ComputedRef, inject, ref, watch } from "vue";
|
||||
import { key } from "@/store";
|
||||
import axios from "axios";
|
||||
|
||||
const props = defineProps<{ node: Node }>();
|
||||
const visible = ref(true);
|
||||
const sensorDataArray = ref<SensorData[]>([]);
|
||||
|
||||
const { searching, visibleIds } = inject(key) as {
|
||||
searching: ComputedRef<boolean>;
|
||||
visibleIds: ComputedRef<string[]>;
|
||||
};
|
||||
|
||||
watch([searching, visibleIds], ([searching, visibleIds]) => {
|
||||
visible.value = searching ? visibleIds.includes(props.node.id) : true;
|
||||
visible.value = searching ? visibleIds.includes(props.node.uuid) : true;
|
||||
});
|
||||
|
||||
const fetchSensorData = async () => {
|
||||
try {
|
||||
const { data } = await axios.get<SensorData[]>(
|
||||
`http://localhost:8080/api/v1/data?id=${props.node.id}`
|
||||
);
|
||||
sensorDataArray.value = data;
|
||||
} catch (error) {
|
||||
console.error("Error fetching sensor data:", error);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.values {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
fetchSensorData()
|
||||
|
||||
const interval = setInterval(fetchSensorData, 10000)
|
||||
|
||||
onUnmounted(() => clearInterval(interval))
|
||||
});
|
||||
|
||||
const latestSensorData = computed(() => {
|
||||
return sensorDataArray.value.length > 0
|
||||
? sensorDataArray.value[sensorDataArray.value.length - 1]
|
||||
: null;
|
||||
});
|
||||
</script>
|
||||
.v-text-field {
|
||||
max-width: 200px;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -9,54 +9,28 @@
|
|||
|
||||
<script setup lang="ts">
|
||||
import HeaderBar from "./HeaderBar.vue";
|
||||
import CategoryContainer from "./CategoryContainer.vue";
|
||||
import { useQuery } from "@tanstack/vue-query";
|
||||
import { axiosInstance } from "@/client";
|
||||
import { LicenseGroup, License } from "@/types";
|
||||
import { search, key } from "@/store";
|
||||
import MiniSearch from "minisearch";
|
||||
import { computed, provide } from "vue";
|
||||
import { NodeGroup } from "@/types";
|
||||
import TableCategory from "./TableCategory.vue";
|
||||
|
||||
const { isPending, isError, data, error } = useQuery({
|
||||
queryKey: ["licenses"],
|
||||
|
||||
const { data } = useQuery({
|
||||
queryKey: ["nodes"],
|
||||
queryFn: async () => {
|
||||
const res = await axiosInstance.get<LicenseGroup[]>("/licenses");
|
||||
console.log(res.data);
|
||||
const res = await axiosInstance.get<NodeGroup[]>("/nodes");
|
||||
return res.data;
|
||||
},
|
||||
refetchInterval: 60 * 1000,
|
||||
select: (data) => {
|
||||
return data.map((group) => {
|
||||
return {
|
||||
title: group.name,
|
||||
value: group.nodes,
|
||||
};
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const searchEngine = computed(() => {
|
||||
let minisearch = new MiniSearch({
|
||||
fields: ["name", "description", "id"],
|
||||
searchOptions: {
|
||||
boost: { name: 2 },
|
||||
prefix: true,
|
||||
},
|
||||
});
|
||||
let licenses: License[] = [];
|
||||
data.value?.forEach((group) => {
|
||||
group.licenses.forEach((license) => licenses.push(license));
|
||||
});
|
||||
console.log(licenses);
|
||||
minisearch.addAll(licenses);
|
||||
return minisearch;
|
||||
});
|
||||
|
||||
const searching = computed(() => search.value !== "");
|
||||
|
||||
const visibleIds = computed(() => {
|
||||
return searchEngine.value.search(search.value).map((searchResult) => {
|
||||
return searchResult.id;
|
||||
});
|
||||
});
|
||||
|
||||
provide(key, {
|
||||
visibleIds,
|
||||
searching,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
@ -8,114 +8,149 @@
|
|||
<th>Latitude</th>
|
||||
<th>Longitude</th>
|
||||
<th>Battery</th>
|
||||
<th>Temperature</th>
|
||||
<th>Runtime</th>
|
||||
<th>Gemessene - Temperatur</th>
|
||||
<th>Laufzeit</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(node, index) in tableData" :key="index">
|
||||
<td>{{ node.id }}</td>
|
||||
<td>{{ node.name }}</td>
|
||||
<td>
|
||||
<span :class="getLastSensorData(node)?.voltage !== 'N/A' ? 'status-online' : 'status-offline'">
|
||||
{{ getLastSensorData(node)?.voltage !== 'N/A' ? 'ONLINE' : 'OFFLINE' }}
|
||||
<span :class="node.status === 'ONLINE' ? 'status-online' : 'status-offline'">
|
||||
{{ node.status }}
|
||||
</span>
|
||||
</td>
|
||||
<td
|
||||
<td
|
||||
contenteditable="true"
|
||||
@blur="validateAndUpdateLatLng(node, 'coord_la', $event)"
|
||||
>{{ node.coord_la }}</td>
|
||||
<td
|
||||
@blur="validateAndUpdateLatLng(node, 'lat', $event)"
|
||||
>{{ node.position.lat }}</td>
|
||||
<td
|
||||
contenteditable="true"
|
||||
@blur="validateAndUpdateLatLng(node, 'coord_lo', $event)"
|
||||
>{{ node.coord_lo }}</td>
|
||||
<td>{{ calculateBatteryPercentage(getLastSensorData(node)?.voltage, node.battery_minimum, node.battery_maximum) }}%</td>
|
||||
<td>{{ getLastSensorData(node)?.temperature }}°C</td>
|
||||
<td>{{ getLastSensorData(node)?.uptime/1000 }}</td>
|
||||
@blur="validateAndUpdateLatLng(node, 'lng', $event)"
|
||||
>{{ node.position.lng }}</td>
|
||||
<td>{{ calculateBatteryPercentage(node.batteryVoltage, node.minVoltage, node.maxVoltage) }}%</td>
|
||||
<td>{{ node.temperature }}°C</td>
|
||||
<td>{{ node.runtime }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
|
||||
//folgende Mockdaten rausnehmen und mit axios requests abfragen
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
tableData: [],
|
||||
updateInterval: null,
|
||||
tableData: [
|
||||
{
|
||||
name: "XXXX-XXXX-XXXX-XXXX",
|
||||
status: "ONLINE",
|
||||
position: { lat: 40.7128, lng: 74.0012 },
|
||||
batteryVoltage: 3.9,
|
||||
minVoltage: 3.0,
|
||||
maxVoltage: 4.2,
|
||||
temperature: 100,
|
||||
runtime: "12h 30m 12s",
|
||||
},
|
||||
{
|
||||
name: "XXXX-XXXX-XXXX-XXXX",
|
||||
status: "OFFLINE",
|
||||
position: { lat: 40.7128, lng: 74.0012 },
|
||||
batteryVoltage: 3.2,
|
||||
minVoltage: 3.0,
|
||||
maxVoltage: 4.2,
|
||||
temperature: 100,
|
||||
runtime: "30m",
|
||||
},
|
||||
{
|
||||
name: "Localnode-3",
|
||||
status: "ONLINE",
|
||||
position: { lat: 40.7128, lng: 74.0012 },
|
||||
batteryVoltage: 3.7,
|
||||
minVoltage: 3.0,
|
||||
maxVoltage: 4.2,
|
||||
temperature: 100,
|
||||
runtime: "12h 30m 12s",
|
||||
},
|
||||
{
|
||||
name: "XXXX-XXXX-XXXX-XXXX",
|
||||
status: "ONLINE",
|
||||
position: { lat: 40.7128, lng: 74.0012 },
|
||||
batteryVoltage: 3.8,
|
||||
minVoltage: 3.0,
|
||||
maxVoltage: 4.2,
|
||||
temperature: 100,
|
||||
runtime: "12h 30m 12s",
|
||||
},
|
||||
{
|
||||
name: "XXXX-XXXX-XXXX-XXXX",
|
||||
status: "ONLINE",
|
||||
position: { lat: 40.7128, lng: 74.0012 },
|
||||
batteryVoltage: 3.1,
|
||||
minVoltage: 3.0,
|
||||
maxVoltage: 4.2,
|
||||
temperature: 100,
|
||||
runtime: "12h 30m 12s",
|
||||
},
|
||||
{
|
||||
name: "XXXX-XXXX-XXXX-XXXX",
|
||||
status: "OFFLINE",
|
||||
position: { lat: 40.7128, lng: 74.0012 },
|
||||
batteryVoltage: 3.0,
|
||||
minVoltage: 3.0,
|
||||
maxVoltage: 4.2,
|
||||
temperature: 100,
|
||||
runtime: "12h 30m 12s",
|
||||
},
|
||||
{
|
||||
name: "XXXX-XXXX-XXXX-XXXX",
|
||||
status: "ONLINE",
|
||||
position: { lat: 40.7128, lng: 74.0012 },
|
||||
batteryVoltage: 3.6,
|
||||
minVoltage: 3.0,
|
||||
maxVoltage: 4.2,
|
||||
temperature: 100,
|
||||
runtime: "12h 30m 12s",
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.fetchNodesAndData();
|
||||
|
||||
this.updateInterval = setInterval(this.fetchNodesAndData, 10000)
|
||||
},
|
||||
methods: {
|
||||
async fetchNodesAndData() {
|
||||
try {
|
||||
const nodeGroupsResponse = await axios.get('http://localhost:8080/api/v1/nodes');
|
||||
const nodeGroups = nodeGroupsResponse.data;
|
||||
|
||||
const nodes = nodeGroups.flatMap(group => group.node);
|
||||
|
||||
const sensorDataResponse = await axios.get('http://localhost:8080/api/v1/data');
|
||||
const sensorDataArray = sensorDataResponse.data;
|
||||
|
||||
this.tableData = nodes.map(node => {
|
||||
const nodeSensorData = sensorDataArray.find(data => data.node.id === node.id);
|
||||
|
||||
return {
|
||||
...node,
|
||||
sensorData: nodeSensorData ? nodeSensorData.sensor_data : {
|
||||
temperature: 'N/A',
|
||||
voltage: 'N/A',
|
||||
uptime: 'N/A',
|
||||
},
|
||||
};
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error fetching node or sensor data:', error);
|
||||
}
|
||||
},
|
||||
getLastSensorData(node) {
|
||||
return node.sensorData && node.sensorData.length > 0
|
||||
? node.sensorData[node.sensorData.length - 1]
|
||||
: { temperature: 'N/A', voltage: 'N/A', uptime: 'N/A'};
|
||||
},
|
||||
calculateBatteryPercentage(voltage, batteryMinimum, batteryMaximum) {
|
||||
if (voltage <= batteryMinimum) {
|
||||
calculateBatteryPercentage(currentVoltage, minVoltage, maxVoltage) {
|
||||
if (currentVoltage <= minVoltage) {
|
||||
return 0;
|
||||
} else if (voltage >= batteryMaximum) {
|
||||
} else if (currentVoltage >= maxVoltage) {
|
||||
return 100;
|
||||
} else {
|
||||
return ((voltage - batteryMinimum) / (batteryMaximum - batteryMinimum) * 100).toFixed(2);
|
||||
return ((currentVoltage - minVoltage) / (maxVoltage - minVoltage) * 100).toFixed(2);
|
||||
}
|
||||
},
|
||||
formatRuntime(uptime) {
|
||||
const hours = Math.floor(uptime / 3600);
|
||||
const minutes = Math.floor((uptime % 3600) / 60);
|
||||
const seconds = uptime % 60;
|
||||
return `${hours}h ${minutes}m ${seconds}s`;
|
||||
},
|
||||
validateAndUpdateLatLng(node, field, event) {
|
||||
const originalValue = node[field];
|
||||
const originalValue = node.position[field];
|
||||
let newValue = event.target.innerText;
|
||||
|
||||
// normalize seperated values
|
||||
newValue = newValue.replace(',', '.');
|
||||
|
||||
// check if float value
|
||||
const validNumberRegex = /^-?\d+(\.\d+)?$/;
|
||||
|
||||
if (validNumberRegex.test(newValue)) {
|
||||
const parsedValue = parseFloat(newValue);
|
||||
node[field] = parsedValue;
|
||||
console.log(`Updated ${field} of ${node.id}: ${parsedValue}`);
|
||||
|
||||
// Update if valid
|
||||
node.position[field] = parsedValue;
|
||||
console.log(`Updated ${field} of ${node.name}: ${parsedValue}`);
|
||||
} else {
|
||||
// Reset to original value if invalid
|
||||
event.target.innerText = originalValue;
|
||||
console.log(`Failed to set ${field} of ${node.id}: Invalid input "${newValue}"`);
|
||||
console.log(`Failed to set ${field} of ${node.name}: Invalid input "${newValue}"`);
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ export default createVuetify({
|
|||
light: {
|
||||
dark: false,
|
||||
colors: {
|
||||
main: "#024950",
|
||||
main: "#353A43",
|
||||
darker: "#003135",
|
||||
contrast: "#964734",
|
||||
accent: "#0FA4AF",
|
||||
|
|
|
@ -1,32 +1,29 @@
|
|||
export interface Node {
|
||||
id: string;
|
||||
coord_la: number;
|
||||
coord_lo: number;
|
||||
battery_minimum: number;
|
||||
battery_maximum: number;
|
||||
uuid: string;
|
||||
name: string;
|
||||
status: number;
|
||||
coordla: number;
|
||||
coordlong: number;
|
||||
temperature: number;
|
||||
batteryMinimum: number;
|
||||
batteryCurrent: number;
|
||||
batteryMaximum: number;
|
||||
voltage: number;
|
||||
uptime: number;
|
||||
group: string;
|
||||
sensorData: SensorData[];
|
||||
}
|
||||
|
||||
export interface NodeGroup {
|
||||
id: string;
|
||||
groupId: string;
|
||||
name: string;
|
||||
node: Node[];
|
||||
}
|
||||
|
||||
export interface SensorData {
|
||||
id: string;
|
||||
timestamp: number;
|
||||
temperature: number;
|
||||
voltage: number;
|
||||
uptime: number;
|
||||
nodes: Node[];
|
||||
}
|
||||
|
||||
export interface User {
|
||||
id: string;
|
||||
uuid: string;
|
||||
name: string;
|
||||
email: string;
|
||||
hash: string;
|
||||
admin: boolean;
|
||||
}
|
||||
|
||||
export interface CreateUserDto {
|
||||
|
|
Loading…
Reference in a new issue