Start midea backend

This commit is contained in:
hodasemi 2023-10-05 10:08:57 +02:00
parent f8ef41f59d
commit aff80fa0f7
3 changed files with 154 additions and 2 deletions

View file

@ -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)]

View file

@ -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
View 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)
}
}