commit
c5866eeccc
6 changed files with 129 additions and 66 deletions
5
src/control.rs
Normal file
5
src/control.rs
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
use crate::types::{Error, PowerState};
|
||||
|
||||
pub trait Control {
|
||||
async fn set_power(&self, state: PowerState) -> Result<(), Error>;
|
||||
}
|
||||
23
src/main.rs
23
src/main.rs
|
|
@ -1,21 +1,36 @@
|
|||
use clap::{Parser, Subcommand};
|
||||
use monitor::Monitor;
|
||||
|
||||
mod control;
|
||||
mod monitor;
|
||||
mod system;
|
||||
mod tasmota;
|
||||
mod types;
|
||||
|
||||
#[derive(Parser)]
|
||||
pub struct PowerCommand {
|
||||
index: usize,
|
||||
#[command(subcommand)]
|
||||
state: types::PowerState,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
pub enum Commands {
|
||||
Monitor,
|
||||
Set(PowerCommand),
|
||||
}
|
||||
|
||||
impl Commands {
|
||||
pub async fn execute(self, config_file: &str) {
|
||||
let s = system::System::new_from_file(config_file).unwrap();
|
||||
|
||||
let handle = match self {
|
||||
Self::Monitor => {
|
||||
let m = Monitor::new_from_file(config_file).unwrap();
|
||||
Self::Monitor => tokio::spawn(async move {
|
||||
s.try_get_power().await.unwrap();
|
||||
}),
|
||||
Self::Set(command) => {
|
||||
// let c = Controller::new_from_file(config_file).unwrap();
|
||||
tokio::spawn(async move {
|
||||
m.get_power().await.unwrap();
|
||||
s.try_set_power(command.index, command.state).await.unwrap();
|
||||
})
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,68 +1,9 @@
|
|||
use crate::tasmota::{PowerStatusData, StatusResponse, TasmotaInterface, TasmotaInterfaceConfig};
|
||||
use crate::types::Error;
|
||||
use reqwest::Client;
|
||||
use serde::Deserialize;
|
||||
use std::fs;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum Error {
|
||||
#[error("io error")]
|
||||
IoError(#[from] std::io::Error),
|
||||
#[error("toml parsing error")]
|
||||
ParseError(#[from] toml::de::Error),
|
||||
#[error("request error")]
|
||||
RequestError(#[from] reqwest::Error),
|
||||
#[error("JSON Parse error")]
|
||||
JsonParseError(#[from] serde_json::Error),
|
||||
}
|
||||
|
||||
pub trait Monitoring {
|
||||
async fn get_power(&self) -> Result<isize, Error>;
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct MonitorConfig {
|
||||
targets: Vec<TasmotaInterfaceConfig>,
|
||||
}
|
||||
impl MonitorConfig {
|
||||
fn print(&self) {
|
||||
for t in &self.targets {
|
||||
t.print();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Monitor {
|
||||
targets: Vec<TasmotaInterface>,
|
||||
client: Client,
|
||||
}
|
||||
|
||||
impl Monitor {
|
||||
pub fn new_from_file(config_file: &str) -> Result<Self, Error> {
|
||||
let config_str = fs::read_to_string(config_file)?;
|
||||
let config: MonitorConfig = toml::from_str(&config_str)?;
|
||||
Ok(Self {
|
||||
targets: Monitor::load_targets(&config.targets),
|
||||
client: Client::new(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn load_targets(targets: &Vec<TasmotaInterfaceConfig>) -> Vec<TasmotaInterface> {
|
||||
let mut v = Vec::new();
|
||||
for target in targets {
|
||||
v.push(TasmotaInterface::new(target.clone()));
|
||||
}
|
||||
v
|
||||
}
|
||||
|
||||
pub async fn get_power(&self) -> Result<(), Error> {
|
||||
for target in &self.targets {
|
||||
if let Ok(res) = target.get_power().await {
|
||||
target.print();
|
||||
println!("* POWER: {}W", res);
|
||||
println!("------------------")
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
58
src/system.rs
Normal file
58
src/system.rs
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
use crate::control::Control;
|
||||
use crate::monitor::Monitoring;
|
||||
use crate::tasmota::{TasmotaInterface, TasmotaInterfaceConfig};
|
||||
use crate::types::{self, Error};
|
||||
use reqwest::Client;
|
||||
use serde::Deserialize;
|
||||
use std::fs;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct SystemConfig {
|
||||
components: Vec<TasmotaInterfaceConfig>,
|
||||
}
|
||||
impl SystemConfig {
|
||||
fn print(&self) {
|
||||
for t in &self.components {
|
||||
t.print();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct System {
|
||||
components: Vec<TasmotaInterface>,
|
||||
}
|
||||
|
||||
impl System {
|
||||
pub fn new_from_file(config_file: &str) -> Result<Self, Error> {
|
||||
let config_str = fs::read_to_string(config_file)?;
|
||||
let config: SystemConfig = toml::from_str(&config_str)?;
|
||||
Ok(Self {
|
||||
components: System::load_targets(&config.components),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn load_targets(targets: &Vec<TasmotaInterfaceConfig>) -> Vec<TasmotaInterface> {
|
||||
let mut v = Vec::new();
|
||||
for target in targets {
|
||||
v.push(TasmotaInterface::new(target.clone()));
|
||||
}
|
||||
v
|
||||
}
|
||||
|
||||
pub async fn try_get_power(&self) -> Result<(), Error> {
|
||||
for component in &self.components {
|
||||
if let Ok(res) = component.get_power().await {
|
||||
component.print();
|
||||
println!("* POWER: {}W", res);
|
||||
println!("------------------")
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn try_set_power(&self, index: usize, state: types::PowerState) -> Result<(), Error> {
|
||||
//TODO: check bounds
|
||||
self.components[index].set_power(state).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,11 @@
|
|||
use reqwest::Client;
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::monitor::Error;
|
||||
use crate::monitor::Monitoring;
|
||||
use crate::{
|
||||
control::Control,
|
||||
monitor::Monitoring,
|
||||
types::{Error, PowerState},
|
||||
};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct EnergyData {
|
||||
|
|
@ -69,3 +72,23 @@ impl Monitoring for TasmotaInterface {
|
|||
Ok(data.status.energy.power)
|
||||
}
|
||||
}
|
||||
|
||||
impl Control for TasmotaInterface {
|
||||
async fn set_power(&self, state: PowerState) -> Result<(), Error> {
|
||||
let cmd = match state {
|
||||
PowerState::Off => "OFF",
|
||||
PowerState::On => "ON",
|
||||
};
|
||||
let _res = self
|
||||
.client
|
||||
.get(format!(
|
||||
"http://{}/cm?cmnd=Power%20{}",
|
||||
&self.config.target, cmd
|
||||
))
|
||||
.send()
|
||||
.await?
|
||||
.text()
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
21
src/types.rs
Normal file
21
src/types.rs
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
use clap::Parser;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum Error {
|
||||
#[error("io error")]
|
||||
IoError(#[from] std::io::Error),
|
||||
#[error("toml parsing error")]
|
||||
ParseError(#[from] toml::de::Error),
|
||||
#[error("request error")]
|
||||
RequestError(#[from] reqwest::Error),
|
||||
#[error("JSON Parse error")]
|
||||
JsonParseError(#[from] serde_json::Error),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Parser, Clone)]
|
||||
pub enum PowerState {
|
||||
Off,
|
||||
On,
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue