Compare commits
29 commits
FrontendAd
...
main
Author | SHA1 | Date | |
---|---|---|---|
MikaPikaDerZerstoerer | 8c32cf1a3d | ||
Mika Bomm | 9c38779205 | ||
9010b5d81e | |||
c355bc8f7d | |||
Mika Bomm | fd98afafb6 | ||
Mika Bomm | 6c7bf0223b | ||
Mikail Killi | b2f6f216f6 | ||
Mika Bomm | fe65349b54 | ||
Mikail Killi | abdc45a799 | ||
Mika Bomm | e7436622f8 | ||
Conner | ef3e253a12 | ||
Conner | 55102027c1 | ||
Mika Bomm | e3e1c4a5b9 | ||
Mika Bomm | fcf449ec8b | ||
Mika Bomm | bc8a300305 | ||
Conner | aab15384d3 | ||
Mika Bomm | de4d5448de | ||
Mika Bomm | 87416e5af9 | ||
Mika Bomm | 38a0f4d189 | ||
Mika Bomm | a696a05595 | ||
Mika Bomm | ec8859b59c | ||
Mika Bomm | a65657aea1 | ||
Conner | c8a70b5a32 | ||
Conner | f3c8d8d9a3 | ||
07f37b7426 | |||
6dc9fc1af4 | |||
b10b02f117 | |||
6761423411 | |||
Mika Bomm | 2e39d9fb56 |
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -1,2 +1,4 @@
|
||||||
/target
|
/target
|
||||||
/build
|
/embedded/build
|
||||||
|
/.idea
|
||||||
|
.vscode
|
11
ApfelBruno/Data/get data from last hour.bru
Normal file
11
ApfelBruno/Data/get data from last hour.bru
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
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 {
|
meta {
|
||||||
name: Create node group
|
name: Create node group
|
||||||
type: http
|
type: http
|
||||||
seq: 3
|
seq: 1
|
||||||
}
|
}
|
||||||
|
|
||||||
post {
|
post {
|
|
@ -12,9 +12,11 @@ post {
|
||||||
|
|
||||||
body:json {
|
body:json {
|
||||||
{
|
{
|
||||||
"name":"some mac address",
|
"id":"04-7c-16-06-b3-53",
|
||||||
"coord_la":1.123123,
|
"coord_la":1,
|
||||||
"coord_lo":5.3123123,
|
"coord_lo":2,
|
||||||
"group":"efbd70a9-dc89-4c8d-9e6c-e7607c823df3"
|
"battery_minimum":3,
|
||||||
|
"battery_maximum":4,
|
||||||
|
"group":"54eccfb5-1d5a-4cad-a1a2-468eca68ffd6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
meta {
|
meta {
|
||||||
name: delete node
|
name: delete node
|
||||||
type: http
|
type: http
|
||||||
seq: 4
|
seq: 3
|
||||||
}
|
}
|
||||||
|
|
||||||
delete {
|
delete {
|
||||||
|
|
25
ApfelBruno/Node/update node.bru
Normal file
25
ApfelBruno/Node/update node.bru
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
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,12 +543,16 @@ dependencies = [
|
||||||
"actix-cors",
|
"actix-cors",
|
||||||
"actix-web",
|
"actix-web",
|
||||||
"argon2",
|
"argon2",
|
||||||
|
"chrono",
|
||||||
|
"deku",
|
||||||
"dotenvy",
|
"dotenvy",
|
||||||
"entity",
|
"entity",
|
||||||
|
"eui48",
|
||||||
"futures",
|
"futures",
|
||||||
"jsonwebtoken",
|
"jsonwebtoken",
|
||||||
"sea-orm",
|
"sea-orm",
|
||||||
"serde",
|
"serde",
|
||||||
|
"tokio",
|
||||||
"uuid",
|
"uuid",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -773,8 +777,10 @@ checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"android-tzdata",
|
"android-tzdata",
|
||||||
"iana-time-zone",
|
"iana-time-zone",
|
||||||
|
"js-sys",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"serde",
|
"serde",
|
||||||
|
"wasm-bindgen",
|
||||||
"windows-targets 0.52.6",
|
"windows-targets 0.52.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -950,6 +956,7 @@ dependencies = [
|
||||||
"ident_case",
|
"ident_case",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
"strsim",
|
||||||
"syn 2.0.79",
|
"syn 2.0.79",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -964,6 +971,31 @@ dependencies = [
|
||||||
"syn 2.0.79",
|
"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]]
|
[[package]]
|
||||||
name = "der"
|
name = "der"
|
||||||
version = "0.7.9"
|
version = "0.7.9"
|
||||||
|
@ -1108,6 +1140,16 @@ dependencies = [
|
||||||
"windows-sys 0.48.0",
|
"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]]
|
[[package]]
|
||||||
name = "event-listener"
|
name = "event-listener"
|
||||||
version = "2.5.3"
|
version = "2.5.3"
|
||||||
|
@ -1758,6 +1800,15 @@ dependencies = [
|
||||||
"libc",
|
"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]]
|
[[package]]
|
||||||
name = "nom"
|
name = "nom"
|
||||||
version = "7.1.3"
|
version = "7.1.3"
|
||||||
|
@ -2313,6 +2364,12 @@ version = "0.1.24"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
|
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustc-serialize"
|
||||||
|
version = "0.3.25"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fe834bc780604f4674073badbad26d7219cadfb4a2275802db12cbae17498401"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustc_version"
|
name = "rustc_version"
|
||||||
version = "0.4.1"
|
version = "0.4.1"
|
||||||
|
@ -2365,6 +2422,12 @@ dependencies = [
|
||||||
"untrusted",
|
"untrusted",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustversion"
|
||||||
|
version = "1.0.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ryu"
|
name = "ryu"
|
||||||
version = "1.0.18"
|
version = "1.0.18"
|
||||||
|
|
90
README.md
90
README.md
|
@ -1,4 +1,90 @@
|
||||||
# ApfelNetzwerk
|
# ApfelNetzwerk
|
||||||
|
|
||||||
[Considerations](docs/considerations.md) \
|
1. [ApfelNetzwerk](#apfelnetzwerk) \
|
||||||
[Setup](docs/SETUP.md)
|
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.
|
|
@ -18,3 +18,7 @@ sea-orm = { version = "1", features = [
|
||||||
dotenvy = "*"
|
dotenvy = "*"
|
||||||
jsonwebtoken = "*"
|
jsonwebtoken = "*"
|
||||||
futures = "*"
|
futures = "*"
|
||||||
|
chrono = "*"
|
||||||
|
eui48 = "*"
|
||||||
|
tokio = { version = "1", features = ["full"] }
|
||||||
|
deku = "*"
|
||||||
|
|
|
@ -1,45 +1,147 @@
|
||||||
use crate::AppState;
|
use crate::AppState;
|
||||||
use actix_web::{error::ErrorInternalServerError, web, HttpResponse, Responder};
|
use actix_web::web::Path;
|
||||||
use entity::node_group;
|
use actix_web::{
|
||||||
use sea_orm::{ActiveModelTrait, ActiveValue, EntityTrait};
|
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 serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use uuid::Uuid;
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct NodeWithSensorData {
|
||||||
|
node: NodeWithMac,
|
||||||
|
sensor_data: Vec<sensor_data::Model>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct CreateGroupWithoutId {
|
pub struct CreateGroupWithoutId {
|
||||||
name: String,
|
name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize, Serialize)]
|
||||||
pub struct CreateLicense {
|
pub struct NodeWithMac {
|
||||||
name: String,
|
id: String,
|
||||||
coord_la: f64,
|
coord_la: f64,
|
||||||
coord_lo: f64,
|
coord_lo: f64,
|
||||||
|
battery_minimum: f64,
|
||||||
|
battery_maximum: f64,
|
||||||
group: uuid::Uuid,
|
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)]
|
#[derive(Serialize)]
|
||||||
struct GroupWithNode {
|
struct GroupWithNode {
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
group: entity::node_group::Model,
|
group: node_group::Model,
|
||||||
node: Vec<entity::node::Model>,
|
node: Vec<NodeWithMac>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_nodes(state: web::Data<AppState>) -> actix_web::Result<impl Responder> {
|
pub async fn get_nodes(state: web::Data<AppState>) -> actix_web::Result<impl Responder> {
|
||||||
let db = &state.db;
|
let db = &state.db;
|
||||||
|
|
||||||
let result = node_group::Entity::find()
|
let result: Vec<GroupWithNode> = node_group::Entity::find()
|
||||||
.find_with_related(entity::prelude::Node)
|
.find_with_related(entity::prelude::Node)
|
||||||
.all(db)
|
.all(db)
|
||||||
.await
|
.await
|
||||||
.map_err(ErrorInternalServerError)?
|
.map_err(ErrorInternalServerError)?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(group, node)| GroupWithNode { group, node })
|
.map(|(group, nodes)| {
|
||||||
|
let nodes = nodes
|
||||||
|
.into_iter()
|
||||||
|
.map(|n| n.into())
|
||||||
|
.collect::<Vec<NodeWithMac>>();
|
||||||
|
GroupWithNode { group, node: nodes }
|
||||||
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
Ok(web::Json(result))
|
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(
|
pub async fn create_group(
|
||||||
state: web::Data<AppState>,
|
state: web::Data<AppState>,
|
||||||
group: web::Json<CreateGroupWithoutId>,
|
group: web::Json<CreateGroupWithoutId>,
|
||||||
|
@ -60,11 +162,11 @@ pub async fn create_group(
|
||||||
|
|
||||||
pub async fn create_node(
|
pub async fn create_node(
|
||||||
state: web::Data<AppState>,
|
state: web::Data<AppState>,
|
||||||
node: web::Json<CreateLicense>,
|
node_request: web::Json<NodeWithMac>,
|
||||||
) -> actix_web::Result<impl Responder> {
|
) -> actix_web::Result<impl Responder> {
|
||||||
let db = &state.db;
|
let db = &state.db;
|
||||||
|
|
||||||
let node = node.into_inner();
|
let node: NodeWithMac = node_request.into_inner();
|
||||||
|
|
||||||
println!("Checking group ID: {:?}", node.group);
|
println!("Checking group ID: {:?}", node.group);
|
||||||
|
|
||||||
|
@ -78,26 +180,41 @@ pub async fn create_node(
|
||||||
return Err(ErrorInternalServerError("Group ID does not exist"));
|
return Err(ErrorInternalServerError("Group ID does not exist"));
|
||||||
}
|
}
|
||||||
|
|
||||||
let node = entity::node::ActiveModel {
|
let node: node::Model = node
|
||||||
id: ActiveValue::NotSet,
|
.try_into()
|
||||||
name: ActiveValue::Set(node.name),
|
.map_err(|_| ErrorBadRequest("Invalid Mac Address"))?;
|
||||||
status: ActiveValue::NotSet,
|
let node = node.into_active_model();
|
||||||
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)?;
|
let result = node.insert(db).await.map_err(ErrorInternalServerError)?;
|
||||||
|
|
||||||
Ok(web::Json(result))
|
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(
|
pub async fn delete_node(
|
||||||
state: web::Data<AppState>,
|
state: web::Data<AppState>,
|
||||||
path: web::Path<Uuid>,
|
path: web::Path<Uuid>,
|
||||||
|
@ -113,3 +230,4 @@ pub async fn delete_node(
|
||||||
|
|
||||||
Ok(HttpResponse::Ok().finish())
|
Ok(HttpResponse::Ok().finish())
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
use actix_web::{web, App, HttpServer};
|
use actix_web::{web, App, HttpServer};
|
||||||
use sea_orm::{Database, DatabaseConnection};
|
use deku::prelude::*;
|
||||||
|
use sea_orm::{ActiveModelTrait, ActiveValue, Database, DatabaseConnection};
|
||||||
use std::env;
|
use std::env;
|
||||||
|
use tokio::{io::AsyncReadExt, net::TcpListener};
|
||||||
|
|
||||||
mod controller;
|
mod controller;
|
||||||
|
|
||||||
|
@ -10,7 +12,16 @@ use routes::config;
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct AppState {
|
struct AppState {
|
||||||
db: DatabaseConnection,
|
db: DatabaseConnection,
|
||||||
secret: String,
|
}
|
||||||
|
|
||||||
|
#[derive(DekuRead, DekuWrite, Debug)]
|
||||||
|
#[deku(endian = "little")]
|
||||||
|
struct Data {
|
||||||
|
#[deku(bytes = 8)]
|
||||||
|
mac: [u8; 8],
|
||||||
|
temp: f32,
|
||||||
|
battery_voltage: f32,
|
||||||
|
up_time: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[actix_web::main]
|
#[actix_web::main]
|
||||||
|
@ -21,7 +32,6 @@ async fn main() -> std::io::Result<()> {
|
||||||
dotenvy::dotenv().ok();
|
dotenvy::dotenv().ok();
|
||||||
|
|
||||||
let db_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");
|
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)
|
let conn = Database::connect(&db_url)
|
||||||
.await
|
.await
|
||||||
|
@ -29,10 +39,55 @@ async fn main() -> std::io::Result<()> {
|
||||||
|
|
||||||
println!("Finished running migrations");
|
println!("Finished running migrations");
|
||||||
|
|
||||||
let state = AppState {
|
let state = AppState { db: conn.clone() };
|
||||||
db: conn,
|
|
||||||
secret: jwt_secret,
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
println!("Listening for connections...");
|
println!("Listening for connections...");
|
||||||
HttpServer::new(move || {
|
HttpServer::new(move || {
|
||||||
|
|
|
@ -19,7 +19,9 @@ pub fn config(cfg: &mut web::ServiceConfig) {
|
||||||
.get(node::get_nodes)
|
.get(node::get_nodes)
|
||||||
.post(node::create_node),
|
.post(node::create_node),
|
||||||
)
|
)
|
||||||
.service(web::resource("/nodes/{id}").delete(node::delete_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("/groups").post(node::create_group)),
|
.service(web::resource("/groups").post(node::create_group)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,4 +4,5 @@ pub mod prelude;
|
||||||
|
|
||||||
pub mod node;
|
pub mod node;
|
||||||
pub mod node_group;
|
pub mod node_group;
|
||||||
|
pub mod sensor_data;
|
||||||
pub mod user;
|
pub mod user;
|
||||||
|
|
|
@ -7,24 +7,15 @@ use serde::{Deserialize, Serialize};
|
||||||
#[sea_orm(table_name = "node")]
|
#[sea_orm(table_name = "node")]
|
||||||
pub struct Model {
|
pub struct Model {
|
||||||
#[sea_orm(primary_key, auto_increment = false)]
|
#[sea_orm(primary_key, auto_increment = false)]
|
||||||
pub id: Uuid,
|
pub id: i64,
|
||||||
pub name: String,
|
|
||||||
pub status: bool,
|
|
||||||
#[sea_orm(column_type = "Double")]
|
#[sea_orm(column_type = "Double")]
|
||||||
pub coord_la: f64,
|
pub coord_la: f64,
|
||||||
#[sea_orm(column_type = "Double")]
|
#[sea_orm(column_type = "Double")]
|
||||||
pub coord_lo: f64,
|
pub coord_lo: f64,
|
||||||
#[sea_orm(column_type = "Float")]
|
|
||||||
pub temperature: f32,
|
|
||||||
#[sea_orm(column_type = "Double")]
|
#[sea_orm(column_type = "Double")]
|
||||||
pub battery_minimum: f64,
|
pub battery_minimum: f64,
|
||||||
#[sea_orm(column_type = "Double")]
|
#[sea_orm(column_type = "Double")]
|
||||||
pub battery_current: f64,
|
|
||||||
#[sea_orm(column_type = "Double")]
|
|
||||||
pub battery_maximum: f64,
|
pub battery_maximum: f64,
|
||||||
#[sea_orm(column_type = "Double")]
|
|
||||||
pub voltage: f64,
|
|
||||||
pub uptime: i64,
|
|
||||||
pub group: Uuid,
|
pub group: Uuid,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,6 +29,8 @@ pub enum Relation {
|
||||||
on_delete = "Cascade"
|
on_delete = "Cascade"
|
||||||
)]
|
)]
|
||||||
NodeGroup,
|
NodeGroup,
|
||||||
|
#[sea_orm(has_many = "super::sensor_data::Entity")]
|
||||||
|
SensorData,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Related<super::node_group::Entity> for Entity {
|
impl Related<super::node_group::Entity> for Entity {
|
||||||
|
@ -46,4 +39,10 @@ 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 {}
|
impl ActiveModelBehavior for ActiveModel {}
|
||||||
|
|
|
@ -2,4 +2,5 @@
|
||||||
|
|
||||||
pub use super::node::Entity as Node;
|
pub use super::node::Entity as Node;
|
||||||
pub use super::node_group::Entity as NodeGroup;
|
pub use super::node_group::Entity as NodeGroup;
|
||||||
|
pub use super::sensor_data::Entity as SensorData;
|
||||||
pub use super::user::Entity as User;
|
pub use super::user::Entity as User;
|
||||||
|
|
38
crates/entity/src/sensor_data.rs
Normal file
38
crates/entity/src/sensor_data.rs
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
//! `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,12 +21,10 @@
|
||||||
|
|
||||||
#![warn(rust_2018_idioms)]
|
#![warn(rust_2018_idioms)]
|
||||||
|
|
||||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
use tokio::io::AsyncReadExt;
|
||||||
use tokio::net::TcpListener;
|
use tokio::net::TcpListener;
|
||||||
|
|
||||||
use core::str;
|
use core::str;
|
||||||
use std::env;
|
|
||||||
use std::error::Error;
|
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> std::io::Result<()> {
|
async fn main() -> std::io::Result<()> {
|
||||||
|
|
|
@ -2,6 +2,7 @@ pub use sea_orm_migration::prelude::*;
|
||||||
|
|
||||||
mod m20241008_091626_create_table_user;
|
mod m20241008_091626_create_table_user;
|
||||||
mod m20241008_095058_create_table_node;
|
mod m20241008_095058_create_table_node;
|
||||||
|
mod m20241013_134422_create_table_sensor_data;
|
||||||
|
|
||||||
pub struct Migrator;
|
pub struct Migrator;
|
||||||
|
|
||||||
|
@ -11,6 +12,7 @@ impl MigratorTrait for Migrator {
|
||||||
vec![
|
vec![
|
||||||
Box::new(m20241008_091626_create_table_user::Migration),
|
Box::new(m20241008_091626_create_table_user::Migration),
|
||||||
Box::new(m20241008_095058_create_table_node::Migration),
|
Box::new(m20241008_095058_create_table_node::Migration),
|
||||||
|
Box::new(m20241013_134422_create_table_sensor_data::Migration),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,21 +26,11 @@ impl MigrationTrait for Migration {
|
||||||
Table::create()
|
Table::create()
|
||||||
.table(Node::Table)
|
.table(Node::Table)
|
||||||
.if_not_exists()
|
.if_not_exists()
|
||||||
.col(
|
.col(big_unsigned(Node::Id).primary_key())
|
||||||
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::CoordLa))
|
||||||
.col(double(Node::CoordLo))
|
.col(double(Node::CoordLo))
|
||||||
.col(float(Node::Temperature).default(-127))
|
|
||||||
.col(double(Node::BatteryMinimum).default(-127))
|
.col(double(Node::BatteryMinimum).default(-127))
|
||||||
.col(double(Node::BatteryCurrent).default(-127))
|
|
||||||
.col(double(Node::BatteryMaximum).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))
|
.col(uuid(Node::Group))
|
||||||
.foreign_key(
|
.foreign_key(
|
||||||
ForeignKey::create()
|
ForeignKey::create()
|
||||||
|
@ -71,25 +61,19 @@ impl MigrationTrait for Migration {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(DeriveIden)]
|
#[derive(DeriveIden)]
|
||||||
enum Node {
|
pub enum Node {
|
||||||
Table,
|
Table,
|
||||||
Id,
|
Id, // Mac address
|
||||||
Name,
|
|
||||||
Status,
|
|
||||||
CoordLa,
|
CoordLa,
|
||||||
CoordLo,
|
CoordLo,
|
||||||
Temperature, // def: -127
|
|
||||||
BatteryMinimum, // def: -127
|
BatteryMinimum, // def: -127
|
||||||
BatteryCurrent, // def: -127
|
|
||||||
BatteryMaximum, // def: -127
|
BatteryMaximum, // def: -127
|
||||||
Voltage, // def: -127
|
|
||||||
Uptime, // def: 0
|
|
||||||
Group,
|
Group,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(DeriveIden)]
|
#[derive(DeriveIden)]
|
||||||
enum NodeGroup {
|
enum NodeGroup {
|
||||||
Table,
|
Table,
|
||||||
Id,
|
Id, // Uuid
|
||||||
Name,
|
Name, // Groupname
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
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
|
||||||
|
}
|
BIN
docs/HACKERSOHN - Frame 4.jpg
Normal file
BIN
docs/HACKERSOHN - Frame 4.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 121 KiB |
|
@ -31,24 +31,24 @@ docker compose -f dev-compose.yml down
|
||||||
# Applying Migrations
|
# Applying Migrations
|
||||||
If migrations haven't been applied yet you can apply them with the following commands:
|
If migrations haven't been applied yet you can apply them with the following commands:
|
||||||
|
|
||||||
*Note: Plase ensure the database is running*
|
*Note: Plase ensure the database and backend is running*
|
||||||
|
|
||||||
1. remove old docker image to rebuild
|
remove old docker image if you have updated the backend
|
||||||
```bash
|
```bash
|
||||||
docker image remove apfelnetzwerk-backend
|
docker image remove apfelnetzwerk-backend
|
||||||
```
|
```
|
||||||
|
|
||||||
2. check which dockers are running
|
1. check which dockers are running (both the database and backend should run otherwise start them as stated above)
|
||||||
```bash
|
```bash
|
||||||
docker ps
|
docker ps
|
||||||
```
|
```
|
||||||
|
|
||||||
3. attach a shell to the running postgres docker container
|
2. attach a shell to the running backend docker container
|
||||||
```bash
|
```bash
|
||||||
docker exec -it <it/name> /bin/bash
|
docker exec -it <id/name> /bin/bash
|
||||||
```
|
```
|
||||||
|
|
||||||
4. apply migrations
|
3. apply migrations
|
||||||
```bash
|
```bash
|
||||||
#(go into the crates folder)
|
#(go into the crates folder)
|
||||||
cd crates
|
cd crates
|
||||||
|
|
BIN
docs/diy-problem-definition.jpg
Normal file
BIN
docs/diy-problem-definition.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 240 KiB |
BIN
docs/empathy-map.jpg
Normal file
BIN
docs/empathy-map.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 207 KiB |
BIN
docs/walt-disney.jpg
Normal file
BIN
docs/walt-disney.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 106 KiB |
23
embedded/README.md
Normal file
23
embedded/README.md
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
# 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**
|
31
embedded/components/README.md
Normal file
31
embedded/components/README.md
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
# 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.
|
5
embedded/components/ds18b20/component.mk
Executable file
5
embedded/components/ds18b20/component.mk
Executable file
|
@ -0,0 +1,5 @@
|
||||||
|
#
|
||||||
|
# Component Makefile
|
||||||
|
#
|
||||||
|
|
||||||
|
COMPONENT_ADD_INCLUDEDIRS := .
|
120
embedded/components/ds18b20/ds18b20.c
Executable file
120
embedded/components/ds18b20/ds18b20.c
Executable file
|
@ -0,0 +1,120 @@
|
||||||
|
#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;
|
||||||
|
}
|
100
embedded/components/ds18b20/ds18b20.h
Executable file
100
embedded/components/ds18b20/ds18b20.h
Executable file
|
@ -0,0 +1,100 @@
|
||||||
|
#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
|
5
embedded/components/onewire/component.mk
Executable file
5
embedded/components/onewire/component.mk
Executable file
|
@ -0,0 +1,5 @@
|
||||||
|
#
|
||||||
|
# Component Makefile
|
||||||
|
#
|
||||||
|
|
||||||
|
COMPONENT_ADD_INCLUDEDIRS := .
|
175
embedded/components/onewire/onewire.c
Executable file
175
embedded/components/onewire/onewire.c
Executable file
|
@ -0,0 +1,175 @@
|
||||||
|
#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);
|
||||||
|
}
|
123
embedded/components/onewire/onewire.h
Executable file
123
embedded/components/onewire/onewire.h
Executable file
|
@ -0,0 +1,123 @@
|
||||||
|
#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,51 +3,84 @@
|
||||||
#include "zh_network.h"
|
#include "zh_network.h"
|
||||||
#include "driver/gpio.h"
|
#include "driver/gpio.h"
|
||||||
#include "lwip/sockets.h"
|
#include "lwip/sockets.h"
|
||||||
|
#include "ds18b20.h"
|
||||||
|
#include "onewire.h"
|
||||||
#include <strings.h>
|
#include <strings.h>
|
||||||
|
|
||||||
static const char *payload = "Nüttchen";
|
// #define EXIT_NODE
|
||||||
|
|
||||||
#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 TAG "TCP"
|
||||||
#define PORT 7999
|
#define PORT 7999
|
||||||
#define HOST_IP_ADDR
|
#define HOST_IP_ADDR
|
||||||
|
#define ESP_WIFI_SSID
|
||||||
|
#define ESP_WIFI_PASS
|
||||||
|
#endif
|
||||||
|
|
||||||
#define EXAMPLE_ESP_WIFI_SSID
|
#define ESP_CHANNEL 7
|
||||||
#define EXAMPLE_ESP_WIFI_PASS
|
|
||||||
|
|
||||||
#define MAC2STR(a) (a)[0], (a)[1], (a)[2], (a)[3], (a)[4], (a)[5]
|
#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);
|
void zh_network_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data);
|
||||||
// bc:dd:c2:82:82:9e
|
|
||||||
uint8_t target[6] = {0xBC, 0xDD, 0xC2, 0x82, 0x82, 0x9E};
|
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};
|
||||||
|
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
char char_value[30];
|
float temperature;
|
||||||
int int_value;
|
float battery_voltage;
|
||||||
float float_value;
|
unsigned long up_time;
|
||||||
bool bool_value;
|
} sensor_message_t;
|
||||||
} example_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;
|
||||||
|
}
|
||||||
|
|
||||||
void app_main(void)
|
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_vector", ESP_LOG_NONE);
|
||||||
esp_log_level_set("zh_network", ESP_LOG_NONE);
|
esp_log_level_set("zh_network", ESP_LOG_NONE);
|
||||||
nvs_flash_init();
|
nvs_flash_init();
|
||||||
|
@ -55,21 +88,22 @@ void app_main(void)
|
||||||
esp_event_loop_create_default();
|
esp_event_loop_create_default();
|
||||||
wifi_init_config_t wifi_init_config = WIFI_INIT_CONFIG_DEFAULT();
|
wifi_init_config_t wifi_init_config = WIFI_INIT_CONFIG_DEFAULT();
|
||||||
esp_wifi_init(&wifi_init_config);
|
esp_wifi_init(&wifi_init_config);
|
||||||
|
esp_wifi_set_mode(WIFI_MODE_STA);
|
||||||
|
|
||||||
|
#ifdef EXIT_NODE
|
||||||
wifi_config_t wifi_config = {
|
wifi_config_t wifi_config = {
|
||||||
.sta = {
|
.sta = {
|
||||||
.ssid = EXAMPLE_ESP_WIFI_SSID,
|
.ssid = ESP_WIFI_SSID,
|
||||||
.password = EXAMPLE_ESP_WIFI_PASS,
|
.password = ESP_WIFI_PASS,
|
||||||
.channel = 7},
|
.channel = ESP_CHANNEL},
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config);
|
esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config);
|
||||||
|
|
||||||
esp_wifi_set_mode(WIFI_MODE_STA);
|
esp_wifi_set_channel(ESP_CHANNEL, 1);
|
||||||
esp_wifi_start();
|
|
||||||
esp_wifi_set_channel(7, 1);
|
|
||||||
esp_wifi_connect();
|
esp_wifi_connect();
|
||||||
|
#endif
|
||||||
|
esp_wifi_start();
|
||||||
// esp_wifi_set_max_tx_power(8); // Power reduction is for example and testing purposes only. Do not use in your own programs!
|
// 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();
|
zh_network_init_config_t network_init_config = ZH_NETWORK_INIT_CONFIG_DEFAULT();
|
||||||
network_init_config.max_waiting_time = 1000;
|
network_init_config.max_waiting_time = 1000;
|
||||||
|
@ -81,10 +115,8 @@ void app_main(void)
|
||||||
esp_event_handler_instance_register(ZH_NETWORK, ESP_EVENT_ANY_ID, &zh_network_event_handler, NULL, NULL);
|
esp_event_handler_instance_register(ZH_NETWORK, ESP_EVENT_ANY_ID, &zh_network_event_handler, NULL, NULL);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
char rx_buffer[128];
|
#ifdef EXIT_NODE
|
||||||
char addr_str[128];
|
uint8_t data = 0xFF;
|
||||||
int addr_family;
|
|
||||||
int ip_protocol;
|
|
||||||
|
|
||||||
for (;;)
|
for (;;)
|
||||||
{
|
{
|
||||||
|
@ -94,7 +126,7 @@ void app_main(void)
|
||||||
destAddr.sin_port = htons(7999);
|
destAddr.sin_port = htons(7999);
|
||||||
inet_pton(AF_INET, HOST_IP_ADDR, &destAddr.sin_addr);
|
inet_pton(AF_INET, HOST_IP_ADDR, &destAddr.sin_addr);
|
||||||
|
|
||||||
int sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||||
if (sock < 0)
|
if (sock < 0)
|
||||||
{
|
{
|
||||||
ESP_LOGE(TAG, "Unable to create socket: errno %d", errno);
|
ESP_LOGE(TAG, "Unable to create socket: errno %d", errno);
|
||||||
|
@ -106,13 +138,20 @@ void app_main(void)
|
||||||
{
|
{
|
||||||
ESP_LOGE(TAG, "Socket unable to connect: errno %d", errno);
|
ESP_LOGE(TAG, "Socket unable to connect: errno %d", errno);
|
||||||
close(sock);
|
close(sock);
|
||||||
|
vTaskDelay(500 / portTICK_PERIOD_MS);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
ESP_LOGI(TAG, "Successfully connected");
|
ESP_LOGI(TAG, "Successfully connected");
|
||||||
for (;;)
|
for (;;)
|
||||||
{
|
{
|
||||||
vTaskDelay(5000 / portTICK_PERIOD_MS);
|
tcp_message_t packet;
|
||||||
int err = send(sock, payload, strlen(payload), 0);
|
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);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
{
|
{
|
||||||
ESP_LOGE(TAG, "Error ocured during sending: errno %d", errno);
|
ESP_LOGE(TAG, "Error ocured during sending: errno %d", errno);
|
||||||
|
@ -121,20 +160,7 @@ void app_main(void)
|
||||||
}
|
}
|
||||||
close(sock);
|
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)
|
void zh_network_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data)
|
||||||
|
@ -143,17 +169,28 @@ void zh_network_event_handler(void *arg, esp_event_base_t event_base, int32_t ev
|
||||||
{
|
{
|
||||||
case ZH_NETWORK_ON_RECV_EVENT:;
|
case ZH_NETWORK_ON_RECV_EVENT:;
|
||||||
zh_network_event_on_recv_t *recv_data = event_data;
|
zh_network_event_on_recv_t *recv_data = event_data;
|
||||||
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);
|
#ifdef EXIT_NODE
|
||||||
example_message_t *recv_message = (example_message_t *)recv_data->data;
|
sensor_message_t *recv_message = (sensor_message_t *)recv_data->data;
|
||||||
printf("Char %s\n", recv_message->char_value);
|
tcp_message_t packet;
|
||||||
printf("Int %d\n", recv_message->int_value);
|
memcpy(packet.mac, recv_data->mac_addr, 6);
|
||||||
printf("Float %f\n", recv_message->float_value);
|
packet.temperature = recv_message->temperature;
|
||||||
printf("Bool %d\n", recv_message->bool_value);
|
packet.battery_voltage = recv_message->battery_voltage;
|
||||||
heap_caps_free(recv_data->data); // Do not delete to avoid memory leaks!
|
packet.up_time = recv_message->up_time;
|
||||||
gpio_set_level(GPIO_OUTPUT_IO_0, 0);
|
heap_caps_free(recv_data->data);
|
||||||
vTaskDelay(100 / portTICK_PERIOD_MS);
|
int err = send(sock, (uint8_t *)&packet, sizeof(packet), 0);
|
||||||
gpio_set_level(GPIO_OUTPUT_IO_0, 1);
|
if (err < 0)
|
||||||
break;
|
{
|
||||||
|
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
|
||||||
case ZH_NETWORK_ON_SEND_EVENT:;
|
case ZH_NETWORK_ON_SEND_EVENT:;
|
||||||
zh_network_event_on_send_t *send_data = event_data;
|
zh_network_event_on_send_t *send_data = event_data;
|
||||||
if (send_data->status == ZH_NETWORK_SEND_SUCCESS)
|
if (send_data->status == ZH_NETWORK_SEND_SUCCESS)
|
||||||
|
@ -168,4 +205,6 @@ void zh_network_event_handler(void *arg, esp_event_base_t event_base, int32_t ev
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BoskoopBase
|
||||||
|
|
|
@ -3,29 +3,9 @@
|
||||||
<v-toolbar color="main" dark prominent>
|
<v-toolbar color="main" dark prominent>
|
||||||
<img src="../assets/turbologo.svg" alt="logo" class="logo" width="75" />
|
<img src="../assets/turbologo.svg" alt="logo" class="logo" width="75" />
|
||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
<!-- Users, Groups, Licenses -->
|
|
||||||
<div>
|
|
||||||
<!-- USER MANAGEMENT -->
|
|
||||||
<template v-if="!user" />
|
|
||||||
<UsersDialog v-else-if="user.admin" />
|
|
||||||
<UsersEditActions v-else :admin-menu="false" :user="user!" />
|
|
||||||
</div>
|
|
||||||
<!-- Search -->
|
|
||||||
<v-text-field
|
|
||||||
class="compact-form mr-2"
|
|
||||||
label="Search"
|
|
||||||
variant="solo"
|
|
||||||
density="compact"
|
|
||||||
prepend-inner-icon="mdi-magnify"
|
|
||||||
hide-details
|
|
||||||
single-line
|
|
||||||
clearable
|
|
||||||
v-model="search"
|
|
||||||
rounded="pill"
|
|
||||||
></v-text-field>
|
|
||||||
|
|
||||||
<!-- Logout -->
|
<!-- Logout -->
|
||||||
<v-btn icon variant="outlined" @click="handlelogout">
|
<v-btn @click="handlelogout">
|
||||||
<LogOut />
|
<LogOut />
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</v-toolbar>
|
</v-toolbar>
|
||||||
|
@ -39,20 +19,6 @@
|
||||||
import { store } from "@/store";
|
import { store } from "@/store";
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
import { LogOut } from "lucide-vue-next";
|
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";
|
|
||||||
|
|
||||||
const { data: user } = useQuery({
|
|
||||||
queryKey: ["user"],
|
|
||||||
queryFn: async () => {
|
|
||||||
const res = await axiosInstance.get<User>("/users/me");
|
|
||||||
return res.data;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
function handlelogout() {
|
function handlelogout() {
|
||||||
store.setToken(null);
|
store.setToken(null);
|
||||||
|
|
|
@ -5,11 +5,11 @@
|
||||||
<v-expansion-panel>
|
<v-expansion-panel>
|
||||||
<v-expansion-panel-title>
|
<v-expansion-panel-title>
|
||||||
<div class="d-flex justify-content-between align-items-center">
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
<div class="mr-3" v-if="node.status === 200">
|
<div class="mr-3" v-if="latestSensorData?.voltage">
|
||||||
Name: {{ node.name }} (Status: online)
|
Name: {{ node.id }} (Status: online)
|
||||||
</div>
|
</div>
|
||||||
<div class="mr-3" v-else>
|
<div class="mr-3" v-else>
|
||||||
Name: {{ node.name }} (Status: offline)
|
Name: {{ node.id }} (Status: offline)
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</v-expansion-panel-title>
|
</v-expansion-panel-title>
|
||||||
|
@ -18,21 +18,21 @@
|
||||||
<div class="d-flex flex-column">
|
<div class="d-flex flex-column">
|
||||||
<div class="d-flex align-items-center mb-2">
|
<div class="d-flex align-items-center mb-2">
|
||||||
<span class="mr-2">Coordinates:</span>
|
<span class="mr-2">Coordinates:</span>
|
||||||
<span>La: {{ node.coordla }}, Long: {{ node.coordlong }}</span>
|
<span>La: {{ node.coord_la }}, Long: {{ node.coord_lo }}</span>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex align-items-center mb-2">
|
<div class="d-flex align-items-center mb-2">
|
||||||
<span class="mr-2">Temperature:</span>
|
<span class="mr-2">Temperature:</span>
|
||||||
<span>{{ node.temperature }}°C</span>
|
<span>{{ latestSensorData?.temperature !== undefined ? latestSensorData.temperature : 'NaN' }}°C</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex align-items-center mb-2">
|
<div class="d-flex align-items-center mb-2">
|
||||||
<span class="mr-2">Battery:</span>
|
<span class="mr-2">Battery Voltage:</span>
|
||||||
<span>{{ node.battery }}%</span>
|
<span>{{ latestSensorData?.voltage || 'N/A' }}V</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex align-items-center mb-2">
|
<div class="d-flex align-items-center mb-2">
|
||||||
<span class="mr-2">Runtime:</span>
|
<span class="mr-2">Runtime:</span>
|
||||||
<span>{{ node.runtime }} hours</span>
|
<span>{{ latestSensorData?.uptime ? (latestSensorData.uptime / 3600).toFixed(2) + ' hours' : 'N/A' }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</v-expansion-panel-text>
|
</v-expansion-panel-text>
|
||||||
</v-expansion-panel>
|
</v-expansion-panel>
|
||||||
</v-expansion-panels>
|
</v-expansion-panels>
|
||||||
|
@ -41,31 +41,46 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Node } from "@/types";
|
import { Node, SensorData } from "@/types";
|
||||||
import { ComputedRef, inject, ref, watch } from "vue";
|
import { ComputedRef, inject, ref, watch, onMounted, computed, onUnmounted } from "vue";
|
||||||
import { key } from "@/store";
|
import { key } from "@/store";
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
const props = defineProps<{ node: Node }>();
|
const props = defineProps<{ node: Node }>();
|
||||||
const visible = ref(true);
|
const visible = ref(true);
|
||||||
|
const sensorDataArray = ref<SensorData[]>([]);
|
||||||
|
|
||||||
const { searching, visibleIds } = inject(key) as {
|
const { searching, visibleIds } = inject(key) as {
|
||||||
searching: ComputedRef<boolean>;
|
searching: ComputedRef<boolean>;
|
||||||
visibleIds: ComputedRef<string[]>;
|
visibleIds: ComputedRef<string[]>;
|
||||||
};
|
};
|
||||||
|
|
||||||
watch([searching, visibleIds], ([searching, visibleIds]) => {
|
watch([searching, visibleIds], ([searching, visibleIds]) => {
|
||||||
visible.value = searching ? visibleIds.includes(props.node.uuid) : true;
|
visible.value = searching ? visibleIds.includes(props.node.id) : 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.values {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.v-text-field {
|
|
||||||
max-width: 200px;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -5,119 +5,140 @@
|
||||||
<tr>
|
<tr>
|
||||||
<th>Node/Name</th>
|
<th>Node/Name</th>
|
||||||
<th>Status</th>
|
<th>Status</th>
|
||||||
<th>Latitude</th> <!-- Separate column for Latitude -->
|
<th>Latitude</th>
|
||||||
<th>Longitude</th> <!-- Separate column for Longitude -->
|
<th>Longitude</th>
|
||||||
<th>Battery</th>
|
<th>Battery</th>
|
||||||
<th>Gemessene - Temperatur</th>
|
<th>Temperature</th>
|
||||||
<th>Laufzeit</th>
|
<th>Runtime</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr v-for="(node, index) in tableData" :key="index">
|
<tr v-for="(node, index) in tableData" :key="index">
|
||||||
<td>{{ node.name }}</td>
|
<td>{{ node.id }}</td>
|
||||||
<td>
|
<td>
|
||||||
<span :class="node.status === 'ONLINE' ? 'status-online' : 'status-offline'">
|
<span :class="getLastSensorData(node)?.voltage !== 'N/A' ? 'status-online' : 'status-offline'">
|
||||||
{{ node.status }}
|
{{ getLastSensorData(node)?.voltage !== 'N/A' ? 'ONLINE' : 'OFFLINE' }}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td>{{ node.position.lat }}</td>
|
<td
|
||||||
<td>{{ node.position.lng }}</td>
|
contenteditable="true"
|
||||||
<td>{{ node.battery }}%</td>
|
@blur="validateAndUpdateLatLng(node, 'coord_la', $event)"
|
||||||
<td>{{ node.temperature }}°C</td>
|
>{{ node.coord_la }}</td>
|
||||||
<td>{{ node.runtime }}</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>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
tableData: [
|
tableData: [],
|
||||||
{
|
updateInterval: null,
|
||||||
name: "XXXX-XXXX-XXXX-XXXX",
|
|
||||||
status: "ONLINE",
|
|
||||||
position: { lat: 40.7128, lng: 74.0012 },
|
|
||||||
battery: 98,
|
|
||||||
temperature: 100,
|
|
||||||
runtime: "12h 30m 12s",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "XXXX-XXXX-XXXX-XXXX",
|
|
||||||
status: "OFFLINE",
|
|
||||||
position: { lat: 40.7128, lng: 74.0012 },
|
|
||||||
battery: 19,
|
|
||||||
temperature: 100,
|
|
||||||
runtime: "30m",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Localnode-3",
|
|
||||||
status: "ONLINE",
|
|
||||||
position: { lat: 40.7128, lng: 74.0012 },
|
|
||||||
battery: 66,
|
|
||||||
temperature: 100,
|
|
||||||
runtime: "12h 30m 12s",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "XXXX-XXXX-XXXX-XXXX",
|
|
||||||
status: "ONLINE",
|
|
||||||
position: { lat: 40.7128, lng: 74.0012 },
|
|
||||||
battery: 79,
|
|
||||||
temperature: 100,
|
|
||||||
runtime: "12h 30m 12s",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "XXXX-XXXX-XXXX-XXXX",
|
|
||||||
status: "ONLINE",
|
|
||||||
position: { lat: 40.7128, lng: 74.0012 },
|
|
||||||
battery: 10,
|
|
||||||
temperature: 100,
|
|
||||||
runtime: "12h 30m 12s",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "XXXX-XXXX-XXXX-XXXX",
|
|
||||||
status: "OFFLINE",
|
|
||||||
position: { lat: 40.7128, lng: 74.0012 },
|
|
||||||
battery: 0,
|
|
||||||
temperature: 100,
|
|
||||||
runtime: "12h 30m 12s",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "XXXX-XXXX-XXXX-XXXX",
|
|
||||||
status: "ONLINE",
|
|
||||||
position: { lat: 40.7128, lng: 74.0012 },
|
|
||||||
battery: 56,
|
|
||||||
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) {
|
||||||
|
return 0;
|
||||||
|
} else if (voltage >= batteryMaximum) {
|
||||||
|
return 100;
|
||||||
|
} else {
|
||||||
|
return ((voltage - batteryMinimum) / (batteryMaximum - batteryMinimum) * 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];
|
||||||
|
let newValue = event.target.innerText;
|
||||||
|
|
||||||
|
newValue = newValue.replace(',', '.');
|
||||||
|
const validNumberRegex = /^-?\d+(\.\d+)?$/;
|
||||||
|
|
||||||
|
if (validNumberRegex.test(newValue)) {
|
||||||
|
const parsedValue = parseFloat(newValue);
|
||||||
|
node[field] = parsedValue;
|
||||||
|
console.log(`Updated ${field} of ${node.id}: ${parsedValue}`);
|
||||||
|
} else {
|
||||||
|
event.target.innerText = originalValue;
|
||||||
|
console.log(`Failed to set ${field} of ${node.id}: Invalid input "${newValue}"`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.table-container {
|
.table-container {
|
||||||
background-color: white;
|
background-color: white;
|
||||||
padding: 0.5rem 1rem; /* Reduce top padding to 0.5rem */
|
padding: 0.5rem 1rem;
|
||||||
margin: 0 auto; /* Remove margin at the top, keep it centered horizontally */
|
margin: 0 auto;
|
||||||
width: 100%; /* Full width of parent */
|
width: 100%;
|
||||||
overflow-x: auto; /* Allow horizontal scroll if necessary */
|
overflow-x: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.responsive-table {
|
.responsive-table {
|
||||||
width: 100%; /* Full width */
|
width: 100%;
|
||||||
border-collapse: collapse; /* Ensure tight borders */
|
border-collapse: collapse;
|
||||||
margin: 0; /* Remove margin at the top of the table */
|
margin: 0;
|
||||||
table-layout: auto; /* Auto layout for slimmer columns */
|
table-layout: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.responsive-table th,
|
.responsive-table th,
|
||||||
.responsive-table td {
|
.responsive-table td {
|
||||||
padding: 0.5rem; /* Maintain slim padding */
|
padding: 0.5rem;
|
||||||
text-align: center; /* Center text */
|
text-align: center;
|
||||||
border-bottom: 1px solid #ddd;
|
border-bottom: 1px solid #ddd;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,7 +147,7 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
.responsive-table tr:nth-child(even) {
|
.responsive-table tr:nth-child(even) {
|
||||||
background-color: #f9f9f9; /* Striped rows */
|
background-color: #f9f9f9;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-online {
|
.status-online {
|
||||||
|
|
|
@ -1,25 +1,32 @@
|
||||||
export interface Node {
|
export interface Node {
|
||||||
uuid: string;
|
id: string;
|
||||||
name: string;
|
coord_la: number;
|
||||||
status: number;
|
coord_lo: number;
|
||||||
coordla: number;
|
battery_minimum: number;
|
||||||
coordlong: number;
|
battery_maximum: number;
|
||||||
temperature: number;
|
group: string;
|
||||||
battery: number;
|
sensorData: SensorData[];
|
||||||
runtime: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface NodeGroup {
|
export interface NodeGroup {
|
||||||
groupId: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
nodes: Node[];
|
node: Node[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SensorData {
|
||||||
|
id: string;
|
||||||
|
timestamp: number;
|
||||||
|
temperature: number;
|
||||||
|
voltage: number;
|
||||||
|
uptime: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface User {
|
export interface User {
|
||||||
uuid: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
email: string;
|
email: string;
|
||||||
admin: boolean;
|
hash: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CreateUserDto {
|
export interface CreateUserDto {
|
||||||
|
|
Loading…
Reference in a new issue