209 lines
4.2 KiB
Rust
209 lines
4.2 KiB
Rust
use anyhow::Result;
|
|
use serde::Deserialize;
|
|
use serde_json::from_str;
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct Tasmota {
|
|
device: String,
|
|
}
|
|
|
|
impl Tasmota {
|
|
pub fn new(device: impl ToString) -> Self {
|
|
Self {
|
|
device: device.to_string(),
|
|
}
|
|
}
|
|
|
|
pub fn name(&self) -> &str {
|
|
&self.device
|
|
}
|
|
|
|
fn command<'a>(&self, command: impl IntoIterator<Item = &'a str>) -> String {
|
|
let mut str = String::new();
|
|
|
|
for s in command.into_iter() {
|
|
str += s;
|
|
str += "%20";
|
|
}
|
|
|
|
format!("http://{}/cm?cmnd={}", self.device, str)
|
|
}
|
|
|
|
async fn post<'a>(&self, command: impl IntoIterator<Item = &'a str>) -> Result<String> {
|
|
Ok(reqwest::Client::new()
|
|
.post(&self.command(command))
|
|
.send()
|
|
.await?
|
|
.text()
|
|
.await?)
|
|
}
|
|
|
|
async fn get<'a>(&self, command: impl IntoIterator<Item = &'a str>) -> Result<String> {
|
|
Ok(reqwest::Client::new()
|
|
.get(&self.command(command))
|
|
.send()
|
|
.await?
|
|
.text()
|
|
.await?)
|
|
}
|
|
|
|
pub async fn turn_on_led(&self) -> Result<()> {
|
|
self.post(["LedPower", "1"]).await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn turn_off_led(&self) -> Result<()> {
|
|
self.post(["LedPower", "0"]).await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn switch_on(&self) -> Result<()> {
|
|
self.post(["Power0", "1"]).await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn switch_off(&self) -> Result<()> {
|
|
self.post(["Power0", "0"]).await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn power_state(&self) -> Result<bool> {
|
|
let res = self.get(["Power0"]).await?;
|
|
|
|
let state = PowerState::from_str(&res)?;
|
|
|
|
Ok(state.is_on())
|
|
}
|
|
|
|
pub async fn read_power_usage(&self) -> Result<f32> {
|
|
let res = self.get(["Status", "8"]).await?;
|
|
|
|
let status = Status::from_str(&res)?;
|
|
|
|
Ok(status.StatusSNS.ENERGY.Power)
|
|
}
|
|
|
|
pub async fn led_state(&self) -> Result<bool> {
|
|
let res = self.get(["LedState"]).await?;
|
|
|
|
let state = LedState::from_str(&res)?;
|
|
|
|
Ok(state.LedState != 0)
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use std::{thread, time::Duration};
|
|
|
|
use super::*;
|
|
|
|
use anyhow::Result;
|
|
|
|
#[tokio::test]
|
|
async fn test_connection() -> Result<()> {
|
|
let dev = Tasmota::new("Tasmota-Plug-1");
|
|
|
|
let power = dev.read_power_usage().await?;
|
|
|
|
println!("{power}");
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_toggle() -> Result<()> {
|
|
let dev = Tasmota::new("Tasmota-Plug-4");
|
|
|
|
dev.switch_off().await?;
|
|
assert_eq!(dev.power_state().await?, false);
|
|
|
|
thread::sleep(Duration::from_secs(5));
|
|
|
|
dev.switch_on().await?;
|
|
assert_eq!(dev.power_state().await?, true);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_led() -> Result<()> {
|
|
let dev = Tasmota::new("Tasmota-Plug-4");
|
|
|
|
dev.turn_off_led().await?;
|
|
assert_eq!(dev.led_state().await?, false);
|
|
|
|
thread::sleep(Duration::from_secs(5));
|
|
|
|
dev.turn_on_led().await?;
|
|
assert_eq!(dev.led_state().await?, true);
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[allow(non_snake_case)]
|
|
#[derive(Deserialize, Debug)]
|
|
pub struct Status {
|
|
pub StatusSNS: StatusSNS,
|
|
}
|
|
|
|
impl Status {
|
|
fn from_str(s: &str) -> Result<Self> {
|
|
Ok(from_str(s)?)
|
|
}
|
|
}
|
|
|
|
#[allow(non_snake_case)]
|
|
#[derive(Deserialize, Debug)]
|
|
pub struct StatusSNS {
|
|
pub Time: String,
|
|
pub ENERGY: Energy,
|
|
}
|
|
|
|
#[allow(non_snake_case)]
|
|
#[derive(Deserialize, Debug)]
|
|
pub struct Energy {
|
|
pub TotalStartTime: String,
|
|
pub Total: f32,
|
|
pub Yesterday: f32,
|
|
pub Today: f32,
|
|
pub Power: f32,
|
|
pub ApparentPower: u32,
|
|
pub ReactivePower: u32,
|
|
pub Factor: f32,
|
|
pub Voltage: u32,
|
|
pub Current: f32,
|
|
}
|
|
|
|
#[allow(non_snake_case)]
|
|
#[derive(Deserialize, Debug)]
|
|
pub struct LedState {
|
|
LedState: u8,
|
|
}
|
|
|
|
impl LedState {
|
|
fn from_str(s: &str) -> Result<Self> {
|
|
Ok(from_str(s)?)
|
|
}
|
|
}
|
|
|
|
#[allow(non_snake_case)]
|
|
#[derive(Deserialize, Debug)]
|
|
pub struct PowerState {
|
|
POWER: String,
|
|
}
|
|
|
|
impl PowerState {
|
|
fn from_str(s: &str) -> Result<Self> {
|
|
Ok(from_str(s)?)
|
|
}
|
|
|
|
pub fn is_on(&self) -> bool {
|
|
self.POWER == "ON"
|
|
}
|
|
}
|