This commit is contained in:
Paul Zinselmeyer 2022-11-01 21:23:05 +01:00
commit 26d55c7931
Signed by: pfzetto
GPG Key ID: 4EEF46A5B276E648
6 changed files with 1438 additions and 0 deletions

3
.env.example Normal file
View File

@ -0,0 +1,3 @@
DATABASE_URL=mysql://user:pass@example.com/database
USB_PORT=/dev/ttyUSB0
INSERT_EVERY_NTH=10

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/target
.env

1291
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

12
Cargo.toml Normal file
View File

@ -0,0 +1,12 @@
[package]
name = "stromsensor"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
dotenvy = "0.15"
serial = "0.4"
sqlx = { version = "0.6", features = ["runtime-tokio-rustls", "mysql"], default-features=false }
tokio = { version = "1", features = ["full"]}

120
src/main.rs Normal file
View File

@ -0,0 +1,120 @@
use std::{
env, io,
time::{Duration, Instant},
};
use serial::{CharSize, Parity, SerialPort, StopBits};
use sqlx::{Connection, MySqlConnection};
const PATTERN_COUNT: usize = 1;
const PATTERN: [[u8; 19]; PATTERN_COUNT] = [[
0x77, 0x07, 0x01, 0x00, 0x01, 0x08, 0x00, 0xFF, 0x64, 0x00, 0x01, 0x82, 0x01, 0x62, 0x1E, 0x52,
0xFF, 0x56, 0x00,
]];
#[tokio::main]
async fn main() {
dotenvy::dotenv().ok();
let mut port =
serial::open(&env::var("USB_PORT").expect("USB_PORT environment variable")).unwrap();
let mut connection = MySqlConnection::connect(
&env::var("DATABASE_URL").expect("DATABASE_URL environment variable"),
)
.await
.unwrap();
interact(&mut port, &mut connection).await.unwrap();
}
async fn interact<T: SerialPort>(port: &mut T, connection: &mut MySqlConnection) -> io::Result<()> {
port.reconfigure(&|settings| {
settings.set_baud_rate(serial::Baud9600).unwrap();
settings.set_parity(Parity::ParityNone);
settings.set_char_size(CharSize::Bits8);
settings.set_stop_bits(StopBits::Stop1);
Ok(())
})
.unwrap();
port.set_timeout(Duration::from_millis(250)).unwrap();
let mut read_buf: [u8; 1024] = [0; 1024];
let mut queue: Vec<u8> = vec![];
let mut prev_value = 0;
let mut prev_time = Instant::now();
let mut brake = 0_u32;
let brake_target: u32 = env::var("INSERT_EVERY_NTH")
.expect("INSERT_EVERY_NTH environment variable")
.parse()
.expect("Number in INSERT_EVERY_NTH");
loop {
let read_result = port.read(&mut read_buf);
if let Ok(queue_length) = read_result {
queue.append(
&mut read_buf
.iter()
.take(queue_length)
.copied()
.collect::<Vec<u8>>(),
);
} else if !queue.is_empty() && brake >= brake_target {
let result = extract_values(&mut queue);
if prev_value > 0 {
let val =
((result[0] - prev_value) * 3600) as f64 / (prev_time.elapsed().as_secs_f64());
if val < 0.0 {
println!("received invalid data, dropping it");
continue;
}
println!(
"Zählerstand: {} Wh, Aktueller Verbrauch: {} W",
result[0] / 10,
val / 10.0
);
sqlx::query("INSERT INTO counter_value (timestamp, value) VALUES (NOW(), ?)")
.bind(result[0] / 10)
.execute(&mut *connection)
.await
.unwrap();
sqlx::query("INSERT INTO current_power (timestamp, value) VALUES (NOW(), ?)")
.bind(val as u32 / 10)
.execute(&mut *connection)
.await
.unwrap();
}
prev_value = result[0];
prev_time = Instant::now();
brake = 0;
} else if !queue.is_empty() {
queue.clear();
brake += 1;
}
}
}
fn extract_values(queue: &mut Vec<u8>) -> [i32; PATTERN_COUNT] {
let mut pattern_index: [usize; PATTERN_COUNT] = [0; PATTERN_COUNT];
let mut value: [i32; PATTERN_COUNT] = [0; PATTERN_COUNT];
for queue_item in queue.iter() {
for i in 0..PATTERN_COUNT {
if pattern_index[i] >= 23 {
} else if pattern_index[i] >= 19 {
let e = *queue_item as i32;
value[i] += e << ((22 - pattern_index[i]) * 8);
pattern_index[i] += 1;
} else if *queue_item == PATTERN[i][pattern_index[i]] {
pattern_index[i] += 1;
} else {
pattern_index[i] = 0;
}
}
}
queue.clear();
value
}

10
stromsensor.service Normal file
View File

@ -0,0 +1,10 @@
[Unit]
Name=Stromsensor
Description=SML SmartMeter measurements to SQL
After=network-online.target
[Service]
User=stromsensor
User=stromsensor
ExecStart=/opt/stromsensor
EnvironmentFile=/opt/stromsensor-env