HomeServer/src/db.rs

390 lines
10 KiB
Rust
Raw Normal View History

2023-09-19 08:52:12 +00:00
use std::path::Path;
use anyhow::Result;
2023-10-05 08:08:57 +00:00
use rusqlite::{Connection, OptionalExtension, ToSql};
2023-09-19 08:52:12 +00:00
2023-10-10 12:06:26 +00:00
use crate::devices::{DeviceWithName, Devices, DevicesWithName};
2023-09-21 08:08:23 +00:00
2023-09-19 08:52:12 +00:00
pub struct DataBase {
sql: Connection,
}
impl DataBase {
2023-09-21 08:08:23 +00:00
const VERSION_0_1_0: &'static str = "0.1.0";
2023-09-19 08:52:12 +00:00
pub async fn new(path: impl AsRef<Path>) -> Result<Self> {
let me = Self {
sql: Connection::open(path)?,
};
me.generate_tables()?;
2023-09-21 08:08:23 +00:00
me.init()?;
2023-09-19 08:52:12 +00:00
Ok(me)
}
fn generate_tables(&self) -> Result<()> {
2023-09-19 09:18:22 +00:00
self.sql.execute(
2023-09-21 08:08:23 +00:00
"CREATE TABLE IF NOT EXISTS meta (
id INTEGER PRIMARY KEY,
version INTEGER NOT NULL
)",
[],
)?;
self.sql.execute(
2023-10-19 14:26:09 +00:00
"CREATE TABLE IF NOT EXISTS devices (
2023-09-19 09:18:22 +00:00
id INTEGER PRIMARY KEY,
2023-09-21 08:08:23 +00:00
device VARCHAR(60) NOT NULL,
type VARCHAR(30) NOT NULL,
2023-09-22 05:43:56 +00:00
control INTEGER NOT NULL,
2023-09-21 08:08:23 +00:00
name VARCHAR(80)
2023-09-19 09:18:22 +00:00
)",
[],
)?;
2023-09-21 08:08:23 +00:00
self.sql.execute(
"CREATE TABLE IF NOT EXISTS data (
id INTEGER PRIMARY KEY,
time BIGINT NOT NULL,
2023-10-19 14:26:09 +00:00
name VARCHAR(30) NOT NULL,
value REAL NOT NULL,
2023-09-21 08:08:23 +00:00
device_id INTEGER NOT NULL,
FOREIGN KEY(device_id) REFERENCES devices(id)
)",
[],
)?;
2023-10-05 08:08:57 +00:00
self.sql.execute(
"CREATE TABLE IF NOT EXISTS credentials (
id INTEGER PRIMARY KEY,
key VARCHAR(60) NOT NULL,
device_id BIGINT NOT NULL,
cred VARCHAR(256) NOT NULL
)",
[],
)?;
2023-09-21 08:08:23 +00:00
Ok(())
}
pub fn version(&self) -> Result<String> {
Ok(self
.sql
.query_row("SELECT version FROM meta WHERE id=1", [], |row| row.get(0))?)
}
fn init(&self) -> Result<()> {
if self.version().is_err() {
self.sql.execute(
"INSERT INTO meta (version)
VALUES (?1)",
&[Self::VERSION_0_1_0],
)?;
}
Ok(())
}
pub fn register_devices(&self, devices: &Devices) -> Result<()> {
2023-09-22 05:43:56 +00:00
for (device, control) in devices.plugs.iter() {
2023-09-21 08:08:23 +00:00
self.sql.execute(
2023-09-21 08:46:23 +00:00
&format!(
2023-10-03 06:22:21 +00:00
"INSERT INTO devices (device, type, control)
SELECT \"{device}\", \"plug\", ?1
2023-09-21 08:08:23 +00:00
WHERE
NOT EXISTS (
SELECT device
FROM devices
2023-09-21 08:46:23 +00:00
WHERE device=\"{device}\"
2023-09-21 08:08:23 +00:00
)
2023-09-21 08:46:23 +00:00
"
),
2023-10-03 06:22:21 +00:00
&[control],
2023-09-21 08:08:23 +00:00
)?;
2023-09-22 05:43:56 +00:00
let ctl = if *control { 1 } else { 0 };
self.sql.execute(
&format!(
"
UPDATE devices
SET control=\"{ctl}\"
WHERE device=\"{device}\"
"
),
[],
)?;
2023-09-21 08:08:23 +00:00
}
2023-10-19 14:26:09 +00:00
for device in devices.thermostat.iter() {
self.sql.execute(
&format!(
"INSERT INTO devices (device, type, control)
SELECT \"{device}\", \"thermostat\", false
WHERE
NOT EXISTS (
SELECT device
FROM devices
WHERE device=\"{device}\"
)
"
),
[],
)?;
}
for device in devices.thermometer.iter() {
self.sql.execute(
&format!(
"INSERT INTO devices (device, type, control)
SELECT \"{device}\", \"thermometer\", false
WHERE
NOT EXISTS (
SELECT device
FROM devices
WHERE device=\"{device}\"
)
"
),
[],
)?;
}
2023-09-19 09:18:22 +00:00
Ok(())
}
2023-10-19 14:26:09 +00:00
pub fn write(&self, device_name: &str, time: u64, name: &str, value: f32) -> Result<()> {
let params: &[&dyn ToSql] = &[&time, &value];
2023-09-19 09:18:22 +00:00
self.sql.execute(
2023-09-21 08:08:23 +00:00
&format!(
2023-10-19 14:26:09 +00:00
"INSERT INTO data (time, name, value, device_id)
VALUES (?1, \"{name}\", ?2, (SELECT id FROM devices WHERE device=\"{device_name}\") )"
2023-09-21 08:08:23 +00:00
),
2023-09-19 09:18:22 +00:00
params,
)?;
Ok(())
}
2023-09-20 10:19:41 +00:00
2023-09-21 08:08:23 +00:00
pub fn devices(&self) -> Result<DevicesWithName> {
let mut devices = DevicesWithName::default();
for row in self
.sql
.prepare(&format!(
"
2023-09-22 05:43:56 +00:00
SELECT device, type, name, control
2023-09-21 08:08:23 +00:00
FROM devices
"
))?
2023-09-22 05:43:56 +00:00
.query_map([], |row| {
Ok((row.get(0)?, row.get(1)?, row.get(2)?, row.get(3)?))
})?
2023-09-21 08:08:23 +00:00
{
2023-09-22 05:43:56 +00:00
let (device, dev_type, name, control): (String, String, Option<String>, i32) = row?;
2023-09-21 08:08:23 +00:00
match dev_type.as_str() {
2023-10-10 12:06:26 +00:00
"plug" => devices.plugs.push(DeviceWithName {
id: device,
desc: name,
toggle: control != 0,
}),
2023-10-19 14:26:09 +00:00
"thermostat" => devices.thermostat.push(DeviceWithName {
id: device,
desc: name,
toggle: control != 0,
}),
"thermometer" => devices.temperature_and_humidity.push(DeviceWithName {
id: device,
desc: name,
toggle: control != 0,
}),
2023-09-21 08:08:23 +00:00
_ => panic!(),
}
}
Ok(devices)
}
2023-10-19 14:26:09 +00:00
pub fn device_exists(&self, device_id: &str) -> Result<bool> {
Ok(self
.sql
.prepare(&format!(
"
SELECT *
FROM devices
WHERE device=\"{device_id}\"
"
))?
.exists([])?)
}
2023-09-21 08:08:23 +00:00
pub fn change_device_name(&self, device: &str, description: &str) -> Result<()> {
self.sql.execute(
&format!(
"
UPDATE devices
2023-09-21 08:46:23 +00:00
SET name=\"{description}\"
WHERE device=\"{device}\"
2023-09-21 08:08:23 +00:00
"
),
2023-09-21 08:46:23 +00:00
[],
2023-09-21 08:08:23 +00:00
)?;
Ok(())
}
2023-09-20 10:19:41 +00:00
pub fn read(&self, device: &str) -> Result<Vec<(u64, f32)>> {
self._read(&format!(
"
2023-10-19 14:26:09 +00:00
SELECT data.time, data.value
FROM data
INNER JOIN devices
ON data.device_id=devices.id
WHERE devices.device=\"{device}\"
"
))
}
pub fn read_range(&self, device: &str, start: u64, end: u64) -> Result<Vec<(u64, f32)>> {
self._read(&format!(
"
2023-10-19 14:26:09 +00:00
SELECT data.time, data.value
FROM data
INNER JOIN devices
ON data.device_id=devices.id
WHERE devices.device=\"{device}\"
AND data.time>={start}
AND data.time<{end}
"
))
}
fn _read(&self, query: &str) -> Result<Vec<(u64, f32)>> {
2023-09-20 10:19:41 +00:00
self.sql
.prepare(query)?
2023-09-20 10:19:41 +00:00
.query_map([], |row| Ok((row.get(0)?, row.get(1)?)))?
.map(|row| {
let (time, watts) = row?;
Ok((time, watts))
})
.collect()
}
2023-10-05 08:08:57 +00:00
pub fn write_credential(&self, key: &str, device_id: u64, credential: &str) -> Result<()> {
if self
.sql
.prepare(&format!(
"
SELECT cred
FROM credentials
WHERE key=\"{key}\" AND device_id={device_id}
"
))?
.exists([])?
{
self.sql.execute(
&format!(
"
UPDATE credentials
SET cred=\"{credential}\"
WHERE key=\"{key}\" AND device_id={device_id}
"
),
[],
)?;
} else {
self.sql.execute(
&format!(
"INSERT INTO crendetials (key, device_id, cred)
VALUES (\"{key}\", {device_id}, \"{credential}\")"
),
[],
)?;
}
Ok(())
}
pub fn read_credential(&self, key: &str, device_id: u64) -> Result<Option<String>> {
Ok(self
.sql
.prepare(&format!(
"
SELECT cred
FROM credentials
WHERE key=\"{key}\" AND device_id={device_id}
"
))?
.query_row([], |row| Ok(row.get(0)?))
.optional()?)
}
2023-09-19 09:18:22 +00:00
}
#[cfg(test)]
mod test {
use std::fs;
use anyhow::Result;
2023-09-21 08:08:23 +00:00
use crate::devices::Devices;
2023-09-19 09:18:22 +00:00
use super::DataBase;
#[tokio::test]
async fn test_connection() -> Result<()> {
2023-09-21 08:08:23 +00:00
let db = DataBase::new("connection_test.db").await?;
assert_eq!(DataBase::VERSION_0_1_0, db.version()?);
2023-09-19 09:18:22 +00:00
fs::remove_file("connection_test.db")?;
Ok(())
2023-09-19 08:52:12 +00:00
}
2023-09-21 08:08:23 +00:00
#[tokio::test]
async fn test_startup() -> Result<()> {
let db = DataBase::new("startup_test.db").await?;
db.register_devices(&Devices {
2023-09-22 05:43:56 +00:00
plugs: vec![("test".to_string(), true)],
2023-10-19 14:26:09 +00:00
thermostat: Vec::new(),
thermometer: Vec::new(),
2023-09-21 08:08:23 +00:00
})?;
fs::remove_file("startup_test.db")?;
Ok(())
}
2023-09-19 09:18:22 +00:00
#[tokio::test]
async fn test_write() -> Result<()> {
let db = DataBase::new("write_test.db").await?;
2023-09-21 08:08:23 +00:00
let device_name = "test";
db.register_devices(&Devices {
2023-09-22 05:43:56 +00:00
plugs: vec![(device_name.to_string(), true)],
2023-10-19 14:26:09 +00:00
thermostat: Vec::new(),
thermometer: Vec::new(),
2023-09-21 08:08:23 +00:00
})?;
2023-10-19 14:26:09 +00:00
db.write(device_name, 0, "watts", 5.5)?;
2023-09-21 08:46:23 +00:00
let device_descriptor = "udo";
db.change_device_name(device_name, device_descriptor)?;
let devices = db.devices()?;
2023-10-10 12:06:26 +00:00
assert_eq!(devices.plugs[0].desc.as_ref().unwrap(), device_descriptor);
assert_eq!(devices.plugs[0].id, device_name);
2023-09-19 09:18:22 +00:00
fs::remove_file("write_test.db")?;
Ok(())
2023-09-19 08:52:12 +00:00
}
}