Compare commits
No commits in common. "c9e81b942e7951c0975308fd2d73ac446a9938ab" and "dc81c21ea965e3d71cf3edb83de8a00a230425d3" have entirely different histories.
c9e81b942e
...
dc81c21ea9
9 changed files with 656 additions and 832 deletions
1298
Cargo.lock
generated
1298
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -6,10 +6,11 @@
|
||||||
// #[serde(rename = "...")]
|
// #[serde(rename = "...")]
|
||||||
// #[serde(alias = "...")]
|
// #[serde(alias = "...")]
|
||||||
|
|
||||||
use crate::utils::{EpochUTC, SupportedUnit};
|
use crate::utils::SupportedUnit;
|
||||||
use crate::ingest_protocol::error::Error;
|
use crate::ingest_protocol::error::Error;
|
||||||
use crate::ingest_protocol::parser::parse_mac_address;
|
use crate::ingest_protocol::parser::parse_mac_address;
|
||||||
|
|
||||||
|
use hifitime::Epoch;
|
||||||
use rust_decimal::Decimal;
|
use rust_decimal::Decimal;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_with::serde_as;
|
use serde_with::serde_as;
|
||||||
|
@ -32,7 +33,7 @@ use std::hash::{Hash, Hasher};
|
||||||
pub struct SensorValue {
|
pub struct SensorValue {
|
||||||
pub mac: String,
|
pub mac: String,
|
||||||
pub value: Decimal,
|
pub value: Decimal,
|
||||||
pub time: Option<EpochUTC>,
|
pub time: Option<Epoch>,
|
||||||
pub unit: Option<SupportedUnit>,
|
pub unit: Option<SupportedUnit>,
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
}
|
}
|
||||||
|
@ -58,7 +59,7 @@ serde_with::serde_conv!(
|
||||||
);
|
);
|
||||||
|
|
||||||
#[serde_as]
|
#[serde_as]
|
||||||
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize)]
|
||||||
pub struct NMDeviceDataPacket {
|
pub struct NMDeviceDataPacket {
|
||||||
#[serde(alias = "ID")]
|
#[serde(alias = "ID")]
|
||||||
#[serde_as(as = "MacAsArray")]
|
#[serde_as(as = "MacAsArray")]
|
||||||
|
@ -79,7 +80,7 @@ pub struct NMDeviceDataPacket {
|
||||||
/// - Если в values есть хотябы один times и этот time не None => Игнорируем этот time
|
/// - Если в values есть хотябы один times и этот time не None => Игнорируем этот time
|
||||||
/// - Если time нет у values => используем этот time (если он None, то соотв. считаем что информации о времени нет)
|
/// - Если time нет у values => используем этот time (если он None, то соотв. считаем что информации о времени нет)
|
||||||
/// TODO: В базе всё должно храниться как секунды по TAI спустя J1900 для возможности сортировки по времени позже.
|
/// TODO: В базе всё должно храниться как секунды по TAI спустя J1900 для возможности сортировки по времени позже.
|
||||||
pub time: Option<EpochUTC>,
|
pub time: Option<Epoch>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, PartialEq, Deserialize)]
|
#[derive(Debug, Clone, Default, PartialEq, Deserialize)]
|
||||||
|
|
|
@ -94,7 +94,7 @@ pub fn parse_packet_body<'a>(
|
||||||
packet.values.insert(SensorValue {
|
packet.values.insert(SensorValue {
|
||||||
mac: sensor_mac.into(),
|
mac: sensor_mac.into(),
|
||||||
value: Decimal::from_str(sensor_value)?,
|
value: Decimal::from_str(sensor_value)?,
|
||||||
time: sensor_time.map(|v| v.into()), // TODO
|
time: sensor_time, // TODO
|
||||||
unit: None,
|
unit: None,
|
||||||
name: sensor_name.map(|v| v.to_string()),
|
name: sensor_name.map(|v| v.to_string()),
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,15 +1,11 @@
|
||||||
use crate::ingest_protocol::{
|
use crate::ingest_protocol::parser::{parse_mac_address, parse_packet};
|
||||||
parser::{parse_mac_address, parse_packet},
|
|
||||||
SensorValue,
|
|
||||||
};
|
|
||||||
use hifitime::Epoch;
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_asd() {
|
fn test_asd() {
|
||||||
let asd = r#"
|
let asd = r#"
|
||||||
#A2-C6-47-01-DF-E1#Метео
|
#A2-C6-47-01-DF-E1#Метео
|
||||||
#OWNER#unknown
|
#OWNER#unknown
|
||||||
#T1#13.44#1000000#Outdoor
|
#T1#13.44#Outdoor
|
||||||
#T2#27.74#Indoor
|
#T2#27.74#Indoor
|
||||||
#P1#691.02#Barometer
|
#P1#691.02#Barometer
|
||||||
#LAT#55.738178
|
#LAT#55.738178
|
||||||
|
@ -18,22 +14,14 @@ fn test_asd() {
|
||||||
##
|
##
|
||||||
"#
|
"#
|
||||||
.trim();
|
.trim();
|
||||||
let packet = parse_packet(asd).unwrap().1;
|
dbg!(parse_packet(asd));
|
||||||
assert!(serde_json::to_string_pretty(&packet)
|
|
||||||
.unwrap()
|
|
||||||
.contains(r#""time": 1000000.0"#));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_mac() {
|
fn test_mac() {
|
||||||
assert_eq!(
|
//assert_eq!(parse_mac_address("12-34-AA-12-55-AA"), Ok(("", [18, 52, 170, 18, 85, 170])) );
|
||||||
parse_mac_address("12-34-AA-12-55-AA").unwrap(),
|
|
||||||
("", [18, 52, 170, 18, 85, 170])
|
|
||||||
);
|
|
||||||
|
|
||||||
dbg!(Epoch::now().unwrap().to_unix_seconds());
|
println!("{:?}", parse_mac_address("12-34-AA-12-55-AA"));
|
||||||
|
|
||||||
//println!("{:?}", parse_mac_address("12-34-AA-12-55-AA"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -47,42 +35,3 @@ fn test_packet() {
|
||||||
|
|
||||||
println!("{:#?}", parse_packet(inp));
|
println!("{:#?}", parse_packet(inp));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_hex_parsing() {
|
|
||||||
let inp = r#"
|
|
||||||
{"mac": "T1", "value": "123", "time": "665B514B"}
|
|
||||||
"#
|
|
||||||
.trim();
|
|
||||||
|
|
||||||
let sensor_value: SensorValue = serde_json::from_str(inp).unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
sensor_value.time.unwrap().0.to_unix_seconds(),
|
|
||||||
1717260619.0,
|
|
||||||
"hex text"
|
|
||||||
);
|
|
||||||
|
|
||||||
let inp = r#"
|
|
||||||
{"mac": "T1", "value": "123", "time": 1717260619}
|
|
||||||
"#
|
|
||||||
.trim();
|
|
||||||
|
|
||||||
let sensor_value: SensorValue = serde_json::from_str(inp).unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
sensor_value.time.unwrap().0.to_unix_seconds(),
|
|
||||||
1717260619.0,
|
|
||||||
"integer test"
|
|
||||||
);
|
|
||||||
|
|
||||||
let inp = r#"
|
|
||||||
{"mac": "T1", "value": "123", "time": 1717260619.0}
|
|
||||||
"#
|
|
||||||
.trim();
|
|
||||||
|
|
||||||
let sensor_value: SensorValue = serde_json::from_str(inp).unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
sensor_value.time.unwrap().0.to_unix_seconds(),
|
|
||||||
1717260619.0,
|
|
||||||
"float test"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
|
@ -3,10 +3,9 @@
|
||||||
#![feature(try_blocks)]
|
#![feature(try_blocks)]
|
||||||
extern crate core;
|
extern crate core;
|
||||||
|
|
||||||
|
mod utils;
|
||||||
mod ingest_protocol;
|
mod ingest_protocol;
|
||||||
mod web_server;
|
mod web_server;
|
||||||
mod utils;
|
|
||||||
|
|
||||||
use crate::web_server::server_main;
|
use crate::web_server::server_main;
|
||||||
|
|
||||||
struct Params {}
|
struct Params {}
|
||||||
|
|
|
@ -2,14 +2,10 @@
|
||||||
//!
|
//!
|
||||||
|
|
||||||
|
|
||||||
mod hifitime_serde;
|
|
||||||
|
|
||||||
use phf::phf_map;
|
use phf::phf_map;
|
||||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
pub use hifitime_serde::EpochUTC;
|
|
||||||
|
|
||||||
|
|
||||||
/// Поддерживаемые типы.
|
/// Поддерживаемые типы.
|
||||||
///
|
///
|
||||||
|
@ -82,5 +78,5 @@ static STR_TO_UNITS: phf::Map<&'static str, SupportedUnit> = phf_map! {
|
||||||
"V" => SupportedUnit::Volts,
|
"V" => SupportedUnit::Volts,
|
||||||
"W" => SupportedUnit::Watts,
|
"W" => SupportedUnit::Watts,
|
||||||
"s" => SupportedUnit::Seconds,
|
"s" => SupportedUnit::Seconds,
|
||||||
"KWh" => SupportedUnit::KWh,
|
"KWh" => SupportedUnit::Seconds,
|
||||||
};
|
};
|
|
@ -1,76 +0,0 @@
|
||||||
use hifitime::Epoch;
|
|
||||||
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
|
|
||||||
use std::fmt;
|
|
||||||
use std::fmt::{Formatter, Write};
|
|
||||||
|
|
||||||
#[derive(PartialOrd, PartialEq, Ord, Eq, Clone, Copy, Debug, Default)]
|
|
||||||
#[repr(transparent)]
|
|
||||||
pub struct EpochUTC(pub Epoch);
|
|
||||||
|
|
||||||
impl From<Epoch> for EpochUTC {
|
|
||||||
#[inline]
|
|
||||||
fn from(value: Epoch) -> Self {
|
|
||||||
EpochUTC(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Serialize for EpochUTC {
|
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
||||||
where
|
|
||||||
S: Serializer,
|
|
||||||
{
|
|
||||||
serializer.serialize_f64(self.0.to_unix_seconds())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'de> Deserialize<'de> for EpochUTC {
|
|
||||||
fn deserialize<D>(deserializer: D) -> Result<EpochUTC, D::Error>
|
|
||||||
where
|
|
||||||
D: Deserializer<'de>,
|
|
||||||
{
|
|
||||||
struct EpochVisitor;
|
|
||||||
|
|
||||||
impl<'de> de::Visitor<'de> for EpochVisitor {
|
|
||||||
type Value = EpochUTC;
|
|
||||||
|
|
||||||
fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
|
|
||||||
formatter.write_str(
|
|
||||||
"a (string, representing a timestamp in decimal or hexadecimal form) or a decimal",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
|
|
||||||
where
|
|
||||||
E: de::Error,
|
|
||||||
{
|
|
||||||
return Ok(Epoch::from_unix_seconds(v as f64).into());
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_f64<E>(self, v: f64) -> Result<Self::Value, E>
|
|
||||||
where
|
|
||||||
E: de::Error,
|
|
||||||
{
|
|
||||||
return Ok(Epoch::from_unix_seconds(v).into());
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
|
|
||||||
where
|
|
||||||
E: de::Error,
|
|
||||||
{
|
|
||||||
if value.len() == 8 {
|
|
||||||
let bytes = hex::decode(value).map_err(de::Error::custom)?;
|
|
||||||
|
|
||||||
return Ok(Epoch::from_unix_seconds(u32::from_be_bytes(
|
|
||||||
bytes.as_slice().try_into().unwrap(),
|
|
||||||
) as f64)
|
|
||||||
.into());
|
|
||||||
}
|
|
||||||
return Ok(
|
|
||||||
Epoch::from_unix_seconds(value.parse().map_err(de::Error::custom)?).into(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
deserializer.deserialize_any(EpochVisitor)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -6,8 +6,9 @@ use crate::ingest_protocol::{NMDeviceDataPacket, NMJsonPacket};
|
||||||
use crate::web_server::app_error::AppError;
|
use crate::web_server::app_error::AppError;
|
||||||
|
|
||||||
|
|
||||||
|
use fred::types::RedisMap;
|
||||||
use ntex::http::{HttpMessage, StatusCode};
|
use ntex::http::{HttpMessage, StatusCode};
|
||||||
use ntex::util::Bytes;
|
use ntex::util::{Bytes, HashMap};
|
||||||
use ntex::{http, web};
|
use ntex::{http, web};
|
||||||
use fred::prelude::*;
|
use fred::prelude::*;
|
||||||
use hifitime::Epoch;
|
use hifitime::Epoch;
|
||||||
|
@ -79,7 +80,7 @@ pub async fn device_handler<'a>(
|
||||||
for device in real_body.devices {
|
for device in real_body.devices {
|
||||||
let mut device_key_str = String::new();
|
let mut device_key_str = String::new();
|
||||||
|
|
||||||
let now = Epoch::now().unwrap().into();
|
let now = Epoch::now().unwrap();
|
||||||
let mut device_time = device.time
|
let mut device_time = device.time
|
||||||
.unwrap_or(now);
|
.unwrap_or(now);
|
||||||
|
|
||||||
|
@ -89,7 +90,7 @@ pub async fn device_handler<'a>(
|
||||||
device_time = now;
|
device_time = now;
|
||||||
}
|
}
|
||||||
|
|
||||||
let device_tai_timestamp = device_time.0
|
let device_tai_timestamp = device_time
|
||||||
.to_duration_since_j1900()
|
.to_duration_since_j1900()
|
||||||
.to_seconds();
|
.to_seconds();
|
||||||
|
|
||||||
|
|
|
@ -85,20 +85,32 @@ pub fn parse_epoch_if_exists(parsed: &mut HashMap<String, String>, key: &str) ->
|
||||||
pub async fn parse_nm_qs_format(input: &str) -> Result<NMDeviceDataPacket, QSParserError> {
|
pub async fn parse_nm_qs_format(input: &str) -> Result<NMDeviceDataPacket, QSParserError> {
|
||||||
let mut parsed: HashMap<String, String> = serde_qs::from_str(input)?;
|
let mut parsed: HashMap<String, String> = serde_qs::from_str(input)?;
|
||||||
|
|
||||||
let (_, device_mac) = if let Some(id) = parsed.get("ID") {
|
let (_, _device_mac) = if let Some(id) = parsed.get("ID") {
|
||||||
parse_mac_address(id)?
|
parse_mac_address(id)?
|
||||||
} else {
|
} else {
|
||||||
return Err(QSParserError::NoMAC);
|
return Err(QSParserError::NoMAC);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
for keys in parsed.keys() {
|
||||||
|
if keys.starts_with("") {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let lat = if let Some(lat) = parsed.remove("lat") {
|
||||||
|
Some(Decimal::from_str(lat.as_str())?)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
let device_data = NMDeviceDataPacket {
|
let device_data = NMDeviceDataPacket {
|
||||||
mac: device_mac,
|
mac: _device_mac,
|
||||||
name: parsed.remove("name").map(|v| v.to_owned()),
|
name: parsed.remove("name").map(|v| v.to_owned()),
|
||||||
owner: parsed.remove("owner").map(|v| v.to_owned()),
|
owner: parsed.remove("owner").map(|v| v.to_owned()),
|
||||||
lat: parse_decimal_if_exists(&mut parsed, "lat")?,
|
lat: parse_decimal_if_exists(&mut parsed, "lat")?,
|
||||||
lon: parse_decimal_if_exists(&mut parsed, "lon")?,
|
lon: parse_decimal_if_exists(&mut parsed, "lon")?,
|
||||||
alt: parse_decimal_if_exists(&mut parsed, "alt")?,
|
alt: parse_decimal_if_exists(&mut parsed, "alt")?,
|
||||||
time: parse_epoch_if_exists(&mut parsed, "time")?.map(|v| v.into()),
|
time: parse_epoch_if_exists(&mut parsed, "time")?,
|
||||||
|
|
||||||
values: qs_rest_to_values(parsed)?,
|
values: qs_rest_to_values(parsed)?,
|
||||||
};
|
};
|
||||||
|
|
Loading…
Add table
Reference in a new issue