Start midea backend
This commit is contained in:
parent
f8ef41f59d
commit
aff80fa0f7
3 changed files with 154 additions and 2 deletions
61
src/db.rs
61
src/db.rs
|
@ -1,7 +1,7 @@
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use rusqlite::{Connection, ToSql};
|
use rusqlite::{Connection, OptionalExtension, ToSql};
|
||||||
|
|
||||||
use crate::devices::{Devices, DevicesWithName};
|
use crate::devices::{Devices, DevicesWithName};
|
||||||
|
|
||||||
|
@ -54,6 +54,16 @@ impl DataBase {
|
||||||
[],
|
[],
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
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
|
||||||
|
)",
|
||||||
|
[],
|
||||||
|
)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -202,6 +212,55 @@ impl DataBase {
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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()?)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
12
src/main.rs
12
src/main.rs
|
@ -9,6 +9,7 @@ use crate::{db::DataBase, web_server::plug_data_range};
|
||||||
mod data;
|
mod data;
|
||||||
mod db;
|
mod db;
|
||||||
mod devices;
|
mod devices;
|
||||||
|
mod midea_helper;
|
||||||
mod tasmota;
|
mod tasmota;
|
||||||
mod web_server;
|
mod web_server;
|
||||||
|
|
||||||
|
@ -17,6 +18,7 @@ use actix_web::{web::Data, App, HttpServer};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use devices::Devices;
|
use devices::Devices;
|
||||||
use futures::{future::try_join_all, try_join, Future};
|
use futures::{future::try_join_all, try_join, Future};
|
||||||
|
use midea_helper::MideaDishwasher;
|
||||||
use tasmota::Tasmota;
|
use tasmota::Tasmota;
|
||||||
use web_server::*;
|
use web_server::*;
|
||||||
|
|
||||||
|
@ -50,6 +52,7 @@ async fn run_web_server(
|
||||||
devices: Devices,
|
devices: Devices,
|
||||||
plugs: Vec<Tasmota>,
|
plugs: Vec<Tasmota>,
|
||||||
db: Arc<Mutex<DataBase>>,
|
db: Arc<Mutex<DataBase>>,
|
||||||
|
dishwasher: Vec<Arc<MideaDishwasher>>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
const IP: &str = "0.0.0.0";
|
const IP: &str = "0.0.0.0";
|
||||||
const PORT: u16 = 8062;
|
const PORT: u16 = 8062;
|
||||||
|
@ -61,6 +64,7 @@ async fn run_web_server(
|
||||||
.app_data(Data::new(devices.clone()))
|
.app_data(Data::new(devices.clone()))
|
||||||
.app_data(Data::new(db.clone()))
|
.app_data(Data::new(db.clone()))
|
||||||
.app_data(Data::new(plugs.clone()))
|
.app_data(Data::new(plugs.clone()))
|
||||||
|
.app_data(Data::new(dishwasher.clone()))
|
||||||
.service(Files::new("/images", "resources/images/").show_files_listing())
|
.service(Files::new("/images", "resources/images/").show_files_listing())
|
||||||
.service(Files::new("/css", "resources/css").show_files_listing())
|
.service(Files::new("/css", "resources/css").show_files_listing())
|
||||||
.service(Files::new("/js", "resources/js").show_files_listing())
|
.service(Files::new("/js", "resources/js").show_files_listing())
|
||||||
|
@ -96,9 +100,15 @@ async fn main() -> Result<()> {
|
||||||
.map(|(plug, _)| Tasmota::new(plug))
|
.map(|(plug, _)| Tasmota::new(plug))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
let dishwasher = MideaDishwasher::create(shared_db.clone())
|
||||||
|
.await?
|
||||||
|
.into_iter()
|
||||||
|
.map(|d| Arc::new(d))
|
||||||
|
.collect();
|
||||||
|
|
||||||
try_join!(
|
try_join!(
|
||||||
read_power_usage(tasmota_plugs.clone(), shared_db.clone()),
|
read_power_usage(tasmota_plugs.clone(), shared_db.clone()),
|
||||||
run_web_server(devices, tasmota_plugs, shared_db)
|
run_web_server(devices, tasmota_plugs, shared_db, dishwasher)
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
83
src/midea_helper.rs
Normal file
83
src/midea_helper.rs
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
use anyhow::{bail, Result};
|
||||||
|
use midea::*;
|
||||||
|
|
||||||
|
use crate::db::DataBase;
|
||||||
|
|
||||||
|
enum LoginInfo {
|
||||||
|
Cloud { mail: String, password: String },
|
||||||
|
Token { token: String, key: String },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LoginInfo {
|
||||||
|
const MIDEA_KEY_EMAIL: &str = "midea_cloud_mail";
|
||||||
|
const MIDEA_KEY_PW: &str = "midea_cloud_pw";
|
||||||
|
const MIDEA_KEY_TOKEN: &str = "midea_token";
|
||||||
|
const MIDEA_KEY_KEY: &str = "midea_key";
|
||||||
|
|
||||||
|
fn new(db: &Arc<Mutex<DataBase>>, device_id: u64) -> Result<LoginInfo> {
|
||||||
|
let db_lock = db.lock().unwrap();
|
||||||
|
|
||||||
|
let token = db_lock.read_credential(Self::MIDEA_KEY_TOKEN, device_id)?;
|
||||||
|
let key = db_lock.read_credential(Self::MIDEA_KEY_KEY, device_id)?;
|
||||||
|
|
||||||
|
if token.is_none() || key.is_none() {
|
||||||
|
let mail = db_lock.read_credential(Self::MIDEA_KEY_EMAIL, device_id)?;
|
||||||
|
let pw = db_lock.read_credential(Self::MIDEA_KEY_PW, device_id)?;
|
||||||
|
|
||||||
|
if mail.is_none() || pw.is_none() {
|
||||||
|
bail!("missing credentials");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(LoginInfo::Cloud {
|
||||||
|
mail: mail.unwrap(),
|
||||||
|
password: pw.unwrap(),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Ok(LoginInfo::Token {
|
||||||
|
token: token.unwrap(),
|
||||||
|
key: key.unwrap(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct MideaDishwasher {
|
||||||
|
device_info: DeviceInfo,
|
||||||
|
device: Device,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MideaDishwasher {
|
||||||
|
pub async fn create(db: Arc<Mutex<DataBase>>) -> Result<Vec<Self>> {
|
||||||
|
let mut v = Vec::new();
|
||||||
|
|
||||||
|
for device_info in Startup::discover()
|
||||||
|
.await?
|
||||||
|
.into_iter()
|
||||||
|
.filter(|device_info| device_info.device_type == 0xE1)
|
||||||
|
{
|
||||||
|
if let Ok(res) = LoginInfo::new(&db, device_info.id) {
|
||||||
|
let (token, key) = match res {
|
||||||
|
LoginInfo::Cloud { mail, password } => {
|
||||||
|
let mut cloud = Cloud::new(mail, password)?;
|
||||||
|
|
||||||
|
cloud.login().await?;
|
||||||
|
|
||||||
|
cloud.keys(device_info.id).await?
|
||||||
|
}
|
||||||
|
LoginInfo::Token { token, key } => (token, key),
|
||||||
|
};
|
||||||
|
|
||||||
|
let device = Device::connect(device_info.clone(), &token, &key).await?;
|
||||||
|
|
||||||
|
v.push(Self {
|
||||||
|
device_info,
|
||||||
|
device,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(v)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue